# `fsm-factory` _(no submodules)_ ## Purpose Binds the `@suaveplan/api-lifecycle` definitions together — states, transitions, side-effects, and the per-plugin mutex — into a running FSM backed by `@suaveplan/fsm`'s `FSMInterpreter`. The factory is the single entry point `api-host` driver imports; everything else in this package is reachable through the returned `PluginFsm` handle. ## Features - Builds a `FSMDefinition` from the 13-state union and the 22-edge legal transitions table. - Wraps every `transition(trigger)` call in a mutex acquire/release pair so concurrent calls observe FIFO ordering. - Pre-resolves the destination via `transitions.resolve` so illegal pairs throw `LIFECYCLE.ILLEGAL_TRANSITION` (not the upstream `FSMError`), keeping the error namespace clean. - Appends the matching `SideEffectEntry` to the FSM's append-only log on every commit; the host runtime reads the log and dispatches the ops. - `onTransition` listener API for admin UI subscriptions; returns an unsubscribe function. ## Usage: Basic ```ts import { createPluginFsm } from "@suaveplan/api-lifecycle/fsm-factory"; const fsm = createPluginFsm({ plugin: "billing" }); await fsm.transition("install"); await fsm.transition("installCompleted"); console.log(fsm.state); // "installed" ``` ## Usage: Advanced Wire a custom mutex (e.g. Redis-backed) plus an external subscriber that forwards transition snapshots to OpenTelemetry: ```ts import { createPluginFsm, type PluginFsm } from "@suaveplan/api-lifecycle/fsm-factory"; declare const redisMutex: { acquire(p: string): Promise<{ release(): void }>; tryAcquire(p: string): { release(): void } | null }; const fsm: PluginFsm = createPluginFsm({ plugin: "billing", initial: "uninstalled", mutex: redisMutex, }); const off = fsm.onTransition(({ from, to, trigger }) => { span.addEvent("plugin.transition", { from, to, trigger }); }); await fsm.transition("install"); off(); ``` ## API Reference - `createPluginFsm(options): PluginFsm` — entry point. - `interface CreatePluginFsmOptions` — `plugin`, optional `initial`, `transitions`, `mutex`. - `interface PluginFsm` — `state`, `sideEffects`, `transition(trigger)`, `onTransition(listener)`. - `interface PluginFsmContext` — `plugin`, `sideEffects` (carried by the underlying FSM definition for traceability). ## Implementation Notes The factory builds the `FSMDefinition.states` map exhaustively from `PLUGIN_STATES` so every state has a `StateDefinition` — `@suaveplan/fsm` otherwise throws `INVALID_STATE` when entering a state that was never declared. The transition-table iteration deliberately scans `edges()` once per state during construction; the resulting per-state `on` map is then served O(1) by `FSMInterpreter`. The mutex acquire happens before the `transitions.resolve` call so a queued caller never sees the FSM in a half-mutated state.