# 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.