# @suaveplan/fsm - Typed finite state machine with guards, actions, history tracking, hierarchical states, parallel regions, deferred events, and OTel instrumentation ## Summary Typed, generic finite state machine (FSM) for TypeScript. Ships a synchronous-by-default interpreter with guard predicates, ordered transition actions, context threading, entry/exit lifecycle hooks, history recording, and OTel instrumentation. Zero peer dependencies beyond `@suaveplan/error`, `@suaveplan/events`, and `@suaveplan/types`. ## Documentation tree - [interpreter](./src/core/interpreter/fsm-interpreter.md) — _(no submodules)_ - [fsm-interpreter](./src/core/interpreter/fsm-interpreter.md) — _(no submodules)_ - [errors](./src/errors/fsm-error.md) — _(no submodules)_ - [fsm-error](./src/errors/fsm-error.md) — _(no submodules)_ - [context-helpers](./src/utils/context-helpers.md) — Provides ergonomic factory helpers for building context-update and side-effect actions that can be slotted directly int… - [definition-validator](./src/utils/definition-validator.md) — Validates an `FSMDefinition` object before the interpreter begins executing. Early structural validation prevents subtl… - [fsm-interpreter](./src/core/interpreter/fsm-interpreter.md) — _(no submodules)_- [errors](./src/errors/fsm-error.md) — _(no submodules)_- [fsm-error](./src/errors/fsm-error.md) — _(no submodules)_- [context-helpers](./src/utils/context-helpers.md) — Provides ergonomic factory helpers for building context-update and side-effect actions that can be slotted directly int… - [definition-validator](./src/utils/definition-validator.md) — Validates an `FSMDefinition` object before the interpreter begins executing. Early structural validation prevents subtl… - [fsm-interpreter](./src/core/interpreter/fsm-interpreter.md) — _(no submodules)_- [errors](./src/errors/fsm-error.md) — _(no submodules)_- [fsm-error](./src/errors/fsm-error.md) — _(no submodules)_- [context-helpers](./src/utils/context-helpers.md) — Provides ergonomic factory helpers for building context-update and side-effect actions that can be slotted directly int… - [definition-validator](./src/utils/definition-validator.md) — Validates an `FSMDefinition` object before the interpreter begins executing. Early structural validation prevents subtl… - [fsm-interpreter](./src/core/interpreter/fsm-interpreter.md) — _(no submodules)_- [errors](./src/errors/fsm-error.md) — _(no submodules)_- [fsm-error](./src/errors/fsm-error.md) — _(no submodules)_- [context-helpers](./src/utils/context-helpers.md) — Provides ergonomic factory helpers for building context-update and side-effect actions that can be slotted directly int… - [definition-validator](./src/utils/definition-validator.md) — Validates an `FSMDefinition` object before the interpreter begins executing. Early structural validation prevents subtl… - [fsm-interpreter](./src/core/interpreter/fsm-interpreter.md) — _(no submodules)_- [errors](./src/errors/fsm-error.md) — _(no submodules)_ - [fsm-error](./src/errors/fsm-error.md) — _(no submodules)_- [context-helpers](./src/utils/context-helpers.md) — Provides ergonomic factory helpers for building context-update and side-effect actions that can be slotted directly int… - [definition-validator](./src/utils/definition-validator.md) — Validates an `FSMDefinition` object before the interpreter begins executing. Early structural validation prevents subtl… ## Install ```sh bun add @suaveplan/fsm ``` ## Quick Start ```ts import { createMachine, assign } from '@suaveplan/fsm'; interface TrafficCtx { count: number } const traffic = createMachine({ id: 'traffic', initial: 'red', context: { count: 0 }, states: { red: { on: { NEXT: { target: 'green' } } }, green: { on: { NEXT: { target: 'yellow' } } }, yellow: { on: { NEXT: { target: 'red', actions: [assign(ctx => ({ count: ctx.count + 1 }))], }, }, }, }, }); traffic.send('NEXT'); // → green traffic.send('NEXT'); // → yellow const state = traffic.send('NEXT'); // → red console.log(state.context.count); // 1 console.log(state.history.length); // 3 ``` ## Modules | Module | Path | Description | |--------|------|-------------| | Root | `@suaveplan/fsm` | `createMachine`, types, `assign`, `pure`, `validateDefinition` | | Interpreter | `@suaveplan/fsm/interpreter` | `FSMInterpreter`, `createMachine` | | Errors | `@suaveplan/fsm/errors` | `FSMError`, `FSM_CODES` | ## Core Concepts ### `createMachine(definition)` Factory function returning an `FSMInterpreter` instance. The definition is validated eagerly — malformed definitions throw `FSMError(INVALID_DEFINITION)` before any events are processed. ```ts interface FSMDefinition { id?: string; // used in OTel metric labels initial: string; // must exist in states context: TContext; // initial extended context states: Record>; } ``` ### Guards Guard functions on a transition prevent it from firing when they return `false`. If multiple transitions are declared for the same event (as an array), the first one whose guard returns `true` is taken. ```ts states: { locked: { on: { UNLOCK: [ { target: 'open', guard: (ctx, evt) => evt.payload === ctx.pin }, { target: 'locked', guard: () => true }, // fallthrough (stay locked) ], }, }, } ``` ### Actions Transition actions are called with the accumulated context. Returning a new context replaces it; returning `void` leaves it unchanged. ```ts import { assign, pure } from '@suaveplan/fsm'; const bump = assign(ctx => ({ ...ctx, count: ctx.count + 1 })); const logEvt = pure((ctx, evt) => console.log(evt.type, ctx)); ``` ### Entry / Exit lifecycle ```ts states: { loading: { entry: ctx => ({ ...ctx, loading: true }), exit: ctx => ({ ...ctx, loading: false }), on: { DONE: { target: 'idle' } }, }, } ``` ### Final states Entering a state with `type: 'final'` sets `state.done = true`. ### Subscriptions ```ts const unsub = machine.onTransition(state => { console.log('now in', state.value, 'done?', state.done); }); // later: unsub(); ``` ### `can()` and `matches()` ```ts machine.can('NEXT'); // true if the event is declared in current state machine.matches('green'); // true if currently in 'green' ``` ### `reset()` Resets to initial state, original context, and clears history. ## OTel Metrics | Name | Type | Attributes | |------|------|------------| | `fsm.transition.total` | Counter | `id`, `from`, `to`, `event` | | `fsm.transition.duration_ms` | Histogram | `id`, `from`, `to` | ## Error Codes | Code | Meaning | |------|---------| | `FSM.INVALID_DEFINITION` | Structural error in definition (missing initial, unknown target, etc.) | | `FSM.INVALID_TRANSITION` | No matching transition for event in current state | | `FSM.INVALID_STATE` | Current state not found in definition | | `FSM.GUARD_FAILED` | All guards for an event returned false | | `FSM.ACTION_ERROR` | An action or lifecycle function threw | ## Module Docs - [`src/core/interpreter/fsm-interpreter.md`](src/core/interpreter/fsm-interpreter.md) - [`src/errors/fsm-error.md`](src/errors/fsm-error.md) - [`src/utils/definition-validator.md`](src/utils/definition-validator.md) - [`src/utils/context-helpers.md`](src/utils/context-helpers.md)