createBehavioralFsm
Overview
Section titled “Overview”createBehavioralFsm defines a set of states and transitions once, then applies that behavior to any number of independent client objects. Per-client state is tracked in a WeakMap — nothing is stamped onto the client object itself. The client IS the context; every handler receives it as ctx.
When to use it
Section titled “When to use it”Use createBehavioralFsm when:
- You have many instances that share the same state machine logic — network connections, game entities, UI components
- You don’t want the FSM to own or modify the client object’s shape
- The client already exists and you want to layer FSM behavior onto it without touching its structure
Use createFsm when you have a single instance with its own dedicated context object.
Config
Section titled “Config”The config is the same as createFsm with one difference: there’s no context property. The client object IS the context. Because the client type can’t be inferred from the config, you provide it as an explicit type parameter:
State names, input names, and transition target validation all work identically — inferred from the states config at compile time.
Every method takes the client object as its first argument. The rest of the API mirrors Fsm.
| Method | Description |
|---|---|
handle(client, inputName, ...args) | Dispatch an input to the client’s current state handler |
canHandle(client, inputName) | True if the client’s current state has a handler for this input |
transition(client, toState) | Directly transition the client; fires _onExit, _onEnter, events |
reset(client) | Transition the client back to initialState |
currentState(client) | Returns the client’s current state, or undefined if never initialized |
compositeState(client) | Dot-delimited path including active child FSM states |
on(eventName, callback) | Subscribe to a lifecycle event — returns { off() } |
emit(eventName, data?) | Emit a custom event to subscribers |
dispose(client, options?) | Dispose a single client’s state entry |
dispose() | Permanently shut down the entire FSM; cascades to child FSMs by default |
State storage
Section titled “State storage”Client state is tracked in a WeakMap<TClient, ClientMeta> inside the FSM instance — no properties are added to the client object. When a client is garbage collected, its state entry goes with it automatically. You don’t need to clean up.
First contact with a new client (via handle(), transition(), or reset()) runs the full initialization: transitions into initialState, fires _onEnter, emits lifecycle events. A client the FSM has never seen is transparently initialized on the first call.
Events
Section titled “Events”BehavioralFsm emits the same lifecycle events as Fsm. The difference is every payload includes a client field so you can identify which client the event pertains to:
| Event | Payload | Fired when |
|---|---|---|
transitioning | { fromState, toState, client } | A transition is about to occur |
transitioned | { fromState, toState, client } | A transition completed |
handling | { inputName, client } | An input is about to be dispatched |
handled | { inputName, client } | An input was successfully handled |
nohandler | { inputName, args, client } | No handler found in current state |
invalidstate | { stateName, client } | Transition targeted a nonexistent state |
deferred | { inputName, client } | An input was deferred |
Full example
Section titled “Full example”TypeScript
Section titled “TypeScript”The client type is the only thing you need to supply explicitly. State names and input names are inferred from the states config the same way createFsm handles it.
String shorthand transition targets are validated against actual state keys at compile time. A typo like connect: "conecting" is a type error.