2.9 KiB
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
FSMDefinitionfrom 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.resolveso illegal pairs throwLIFECYCLE.ILLEGAL_TRANSITION(not the upstreamFSMError), keeping the error namespace clean. - Appends the matching
SideEffectEntryto the FSM's append-only log on every commit; the host runtime reads the log and dispatches the ops. onTransitionlistener API for admin UI subscriptions; returns an unsubscribe function.
Usage: Basic
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:
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, optionalinitial,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.