# state-machine _(no submodules)_ ## Purpose Strictly typed lifecycle state machine for campaigns. Owns the set of nine legal transitions between the seven `CampaignStatus` values and rejects every other combination with a typed `CampaignsError`. The state machine is consumed by `CampaignEngine` for every status-changing operation (`schedule`, `send`, `pause`, `resume`, `cancel`, `complete`, `fail`) and by UI layers that need to enumerate the available next-step actions for the user. By centralising the transition rules here we keep the rest of the package free of conditional `if (status === ...)` ladders; new lifecycle events are added by appending to the `TRANSITIONS` table. ## Features - Nine canonical transitions covering draft, scheduled, sending, paused, plus the three terminal states (completed, cancelled, failed). - `transition(currentStatus, event)` returns the next status or throws `CampaignsError(INVALID_TRANSITION)` with the offending `{from, event}` in `context`. - `canTransition(currentStatus, event)` boolean predicate for UI affordances. - `getAvailableEvents(currentStatus)` lists every legal event for the current status — terminal states return `[]`. - Records a `campaigns_transitions_total` counter point per successful transition tagged with `from`, `to`, and `event` labels. ## Usage: Basic ```ts import { CampaignStateMachine } from "@suaveplan/campaigns"; const sm = new CampaignStateMachine(); const next = sm.transition("draft", "send"); // "sending" ``` ## Usage: Advanced Drive UI buttons from the available-events list: ```ts import { CampaignStateMachine, type CampaignEvent } from "@suaveplan/campaigns"; const sm = new CampaignStateMachine(); const available: CampaignEvent[] = sm.getAvailableEvents(campaign.status); return available.map((event) => ( )); ``` ## API Reference - `CampaignEvent` — `"schedule" | "send" | "start" | "cancel" | "pause" | "complete" | "fail" | "resume"`. - `CampaignStateMachine.transition(currentStatus, event): CampaignStatus` — throws `CampaignsError` with code `CAMPAIGNS.INVALID_TRANSITION` if the combination is illegal. - `CampaignStateMachine.canTransition(currentStatus, event): boolean`. - `CampaignStateMachine.getAvailableEvents(currentStatus): CampaignEvent[]`. ## Implementation Notes The `TRANSITIONS` table is a `readonly Transition[]` — frozen at module load. Lookups are linear (n=9), which is faster in practice than a nested `Record>` lookup for this size. Adding a new event means appending to the array; removing one is a public API change and requires an OpenSpec proposal. The telemetry record is fired only on success, so the counter never double-counts a rejected transition.