stateflowjs
v2.0.0
Published
A simple state machine to create workflows in JavaScript
Maintainers
Readme
stateflowjs
A simple state machine (FSM) for creating workflows in JavaScript.
Requires Node.js ≥ 18. ESM only.
Installation
npm install stateflowjsUsage
import StateFlow from 'stateflowjs'
const fsm = new StateFlow({
name: 'my-machine',
initialState: 'idle',
states: {
idle: {
onEnter () { console.log('entered idle') },
start () { this.transition('running') }
},
running: {
onExit () { console.log('leaving running') },
stop () { this.transition('idle') }
}
}
})
fsm.start()
fsm.handle('start')Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| name | string | "unknown" | Human-readable machine name |
| initialState | string | "uninitialized" | State entered when start() is called |
| states | object | — | Map of state names to state definitions |
State definition
Each state is an object with optional lifecycle hooks and any number of action handlers. Inside every handler, this refers to the StateFlow instance.
{
stateName: {
onEnter (...payload) { /* called when entering this state */ },
onExit () { /* called when leaving this state */ },
someAction (...args) { this.transition('otherState') }
}
}onEnter and onExit are reserved — they cannot be invoked via handle().
Methods
start(parent?, ...payload) → boolean
Starts the machine and enters initialState. payload is forwarded to onEnter. Returns false (and emits already_running) if already running.
stop(parentAction?, ...payload) → boolean
Stops the machine and resets to idle. If parentAction is provided and a parent machine is set, the parent will handle that action with payload.
transition(toState, ...payload) → boolean
Transitions to toState. Calls onExit on the current state and onEnter on the target state. Returns false and emits error if toState is not defined.
handle(action, ...payload) → boolean
Invokes the named action on the current state. Returns false if the action is not defined.
getCurrentState() → string
Returns the current state key.
isRunning() → boolean
Returns true if the machine has been started and not yet stopped.
Events
| Event | Payload | Description |
|-------|---------|-------------|
| started | { name, parent } | Machine started |
| transition | { name, prevState, nextState, parent } | State changed |
| action_handled | { name, state, action } | Action executed |
| already_running | { name, parent } | start() called while running |
| error | { code, name, prevState, nextState, parent } | Transition to undefined state |
Parent / child machines
A child machine can reference its parent by passing it to start(). When the child stops with a parentAction, the parent will handle that action automatically.
const child = new StateFlow({ name: 'child', initialState: 'init', states: { init: {} } })
const parent = new StateFlow({
name: 'parent',
initialState: 'waiting',
states: {
waiting: {
onEnter () { child.start(this) },
childDone (result) { console.log('child finished with', result) }
}
}
})
parent.start()
child.stop('childDone', { status: 'ok' })Example — ATM
See examples/atm/atm.js for a three-state machine (uninitialized → unauthorized → authorized) with deposit and withdrawal actions.
node examples/atm/atm.jsMigrating from v1
| v1 | v2 |
|----|----|
| require('stateflowjs') | import StateFlow from 'stateflowjs' |
| options.intialstate | options.initialState |
| Node.js any | Node.js ≥ 18 |
