174 lines
7.9 KiB
Markdown
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)
|