Files
2026-05-27 23:29:53 +01:00

49 lines
3.5 KiB
Markdown

# Change: campaigns
- **Change ID**: campaigns
- **Package**: `@suaveplan/campaigns`
- **Path**: `packages/communication/emails/campaigns/`
- **Tier**: universal
- **Date**: 2026-05-24
## Summary
Land `@suaveplan/campaigns` on genesis at `packages/communication/emails/campaigns/`. The package combines audience targeting, template binding, channel dispatch, and schedule management into managed send workflows. It exposes a state-machine-driven `CampaignEngine`, a throttle-aware `BatchPlanner`, a composable six-step `CampaignWorkflow`, and a pluggable `CampaignStore` interface with an in-memory reference implementation.
## Why
`@suaveplan/campaigns` is the orchestration layer above `@suaveplan/emailer`, `@suaveplan/email-templates`, `@suaveplan/suppression`, and `@suaveplan/contacts`. Without it, callers have to hand-roll lifecycle FSMs and throttle math, which has caused drift between historical implementations.
## Scope
- **Engine**: `CampaignEngine` with `create`, `schedule`, `send`, `pause`, `resume`, `cancel`, `complete`, `fail`, `duplicate`, `updateStats`, `getById`, `list`. Injectable `Clock`/`IdGenerator`/`LoggerLike`.
- **State machine**: `CampaignStateMachine` with nine canonical transitions, `transition`/`canTransition`/`getAvailableEvents`.
- **Batch planner**: `BatchPlanner` with `planBatches` and `calculateTotalDuration`, throttle-aware delay calculation, injectable `Clock`.
- **Workflow**: `CampaignWorkflow` with `buildSendPipeline` (6 steps) and `run` (sequential executor that wraps step errors in `CampaignsError(WORKFLOW_STEP_FAILED)`).
- **Store**: `CampaignStore` interface + `InMemoryCampaignStore` reference implementation.
- **Errors**: `CAMPAIGNS_CODES` (8 codes) + `CampaignsError` with explicit public ctor.
- **Telemetry**: 5 counters + 2 histograms via `@opentelemetry/api`.
- **Types**: 13 exported types/interfaces including `Campaign`, `CampaignStatus`, `CampaignType`, `AudienceConfig`, `ThrottleConfig`, `ABTestConfig`, `CampaignStats`, `CreateCampaignInput`, etc.
## Out of scope (deliberate)
None. The package lands at full functional completeness. Storage adapters (SurrealDB, Postgres) and the HTTP plugin live in sibling packages and are tracked by their own OpenSpec changes.
## Deviations from upstream
- Single-folder feature layout (`engine/`, `state-machine/`, `batch-planner/`, `workflow/`, `store/`, `types/`, `error-codes/`, `telemetry/`) instead of `core/<feature>/` nested under upstream's `core/`. Aligns with the 3-level package layout rule.
- Errors thrown by every public method go through `CampaignsError` + `CAMPAIGNS_CODES` instead of raw `Error`.
- Zod-validated boundaries on `CampaignEngineOptions`, `CreateCampaignInput`, `BatchPlannerOptions`, `BatchConfig`, `ThrottleConfig`.
- OTel instrumentation added throughout (5 counters + 2 histograms).
- Injectable `Clock`, `IdGenerator`, `LoggerLike` on engine; injectable `Clock` on batch planner and in-memory store.
- `CampaignWorkflow.run` added so the package owns its own sequential executor.
## Verified by
- `bunx turbo lint typecheck test --filter=@suaveplan/campaigns` — green.
- `bunx turbo test:coverage --filter=@suaveplan/campaigns` — 100/100/100/100 (statements/branches/functions/lines).
- `bunx turbo build --filter=@suaveplan/campaigns` — emits `dist/`.
- `bun scripts/lint/check-feature-layout.ts` — no campaigns violations.
- `bun scripts/lint/check-test-runner-imports.ts` — no campaigns violations.
- `grep -rn "biome-ignore\|TODO\|FIXME\|XXX" packages/communication/emails/campaigns/src/` — empty.