Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
# `fsm-factory`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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.