2.9 KiB
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 throwsCampaignsError(INVALID_TRANSITION)with the offending{from, event}incontext.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_totalcounter point per successful transition tagged withfrom,to, andeventlabels.
Usage: Basic
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:
import { CampaignStateMachine, type CampaignEvent } from "@suaveplan/campaigns";
const sm = new CampaignStateMachine();
const available: CampaignEvent[] = sm.getAvailableEvents(campaign.status);
return available.map((event) => (
<Button
key={event}
disabled={!sm.canTransition(campaign.status, event)}
onClick={() => onEvent(event)}
>
{event}
</Button>
));
API Reference
CampaignEvent—"schedule" | "send" | "start" | "cancel" | "pause" | "complete" | "fail" | "resume".CampaignStateMachine.transition(currentStatus, event): CampaignStatus— throwsCampaignsErrorwith codeCAMPAIGNS.INVALID_TRANSITIONif 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<Status, Record<Event, Status>> 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.