Files
skills/docs/foundation/flow/fsm/README.md
2026-05-29 08:20:56 +01:00

174 lines
7.9 KiB
Markdown

# @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`.
<!-- begin:auto-docs -->
## Documentation tree
- [interpreter](./src/core/interpreter/fsm-interpreter.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [fsm-interpreter](./src/core/interpreter/fsm-interpreter.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [errors](./src/errors/fsm-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [fsm-error](./src/errors/fsm-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [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…
<!-- end:auto-docs --> - [fsm-interpreter](./src/core/interpreter/fsm-interpreter.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/fsm-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [fsm-error](./src/errors/fsm-error.md) — <!-- begin:auto-docs --> _(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) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/fsm-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [fsm-error](./src/errors/fsm-error.md) — <!-- begin:auto-docs --> _(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) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/fsm-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [fsm-error](./src/errors/fsm-error.md) — <!-- begin:auto-docs --> _(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) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/fsm-error.md) — <!-- begin:auto-docs --> _(no submodules)_ - [fsm-error](./src/errors/fsm-error.md) — <!-- begin:auto-docs --> _(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<TrafficCtx>({
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<TrafficCtx>(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<TContext> {
id?: string; // used in OTel metric labels
initial: string; // must exist in states
context: TContext; // initial extended context
states: Record<string, StateDefinition<TContext>>;
}
```
### 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 => ({ ...ctx, count: ctx.count + 1 }));
const logEvt = pure<Ctx>((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)