@pehotin/fsm
v1.0.7
Published
Simple Fine State Machine
Readme
fsm
Simple finite state machine (FSM) library for Node.js, built around explicit State and Transition objects with optional entry/exit and transition actions.
The package exposes a single StateMachine class from src/index.js.
Installation
npm install fsmOr, if you are working in this repo directly:
npm installBasic Usage
const StateMachine = require('fsm')
// Optional shared context passed to actions
const context = { count: 0 }
const machine = new StateMachine(
'counter',
(state, ctx) => console.log('Global entry:', state.id, ctx),
(state, ctx) => console.log('Global exit:', state.id, ctx),
context
)
// Create states
const idle = machine.createState(
'idle',
(state, ctx) => console.log('Enter idle'),
(state, ctx) => console.log('Exit idle')
)
const active = machine.createState(
'active',
(state, ctx) => console.log('Enter active'),
(state, ctx) => console.log('Exit active')
)
// Register start state
machine.addState(idle, true)
machine.addState(active)
// Add transitions
// Trigger IDs are local to each state: internally they are stored as `${state.id}:${triggerId}`
idle.addTransition(
'start',
active,
() => console.log('Transition idle -> active')
)
active.addTransition(
'stop',
idle,
() => console.log('Transition active -> idle')
)
// Start the machine and trigger transitions
machine.start()
machine.trigger('start') // idle -> active
machine.trigger('stop') // active -> idleCore Concepts
StateMachine
- Constructed as
new StateMachine(name, globalEntryAction, globalExitAction, context). name: string used in error messages.globalEntryAction(state, context): called whenever any state is entered.globalExitAction(state, context): called whenever any state is exited.context: arbitrary object passed to all actions.
Key methods:
createState(name, entryAction, exitAction)→StateaddState(state, isStart)– registers a state, optionally marking it as the start state.start()– starts the machine in the configured start state (or the first added state).reset(restart = false)– clears current/previous state, and optionally restarts.trigger(triggerId)– performs a transition based on the current state and trigger.gotoPrevious()– moves back to the previous state if it exists.getCurrentState()– returns the currentStateinstance.isStarted()– returnstrueif the machine has been started.
- Constructed as
State
- Created via
state = machine.createState(name, entryAction, exitAction)or directly vianew State(...)and thenmachine.addState(state, isStart). id: read‑only string identifier (thenamepassed in).entryAction(state, context): optional function called on entry.exitAction(state, context): optional function called on exit.
Key methods:
addTransition(triggerId, targetState, transitionAction)- Internally, transitions use IDs of the form
${state.id}:${triggerId}.
- Internally, transitions use IDs of the form
getTransition(triggerId)– returns theTransitionregistered for the given local trigger ID.
- Created via
Transition
- Created internally when you call
state.addTransition(...). - Holds:
- An ID (local trigger ID, e.g.
idle:start). - A
targetState. - An optional
transitionAction()function, called during the transition.
- An ID (local trigger ID, e.g.
Key methods:
getTriggerId()– returns the transition ID.getTargetState()– returns the targetStateinstance.
- Created internally when you call
Trigger IDs and Transitions
When you call:
idle.addTransition('start', active, () => { /* ... */ })the State converts the provided trigger id to a local ID:
`${state.id}:${triggerId}` // e.g. "idle:start"When you later call:
machine.trigger('start')the state machine builds the same local ID based on the current state's id:
`${currentState.id}:${triggerId}`and uses that to find the correct Transition. This ensures trigger IDs are namespaced per state.
Error Handling
The StateMachine performs several runtime checks and throws descriptive errors:
- Starting twice:
"The state machine has already started." - Starting without any states:
"No states have been defined. The state machine cannot be started." - Transitioning when not started:
"State machine <name> not started." - Invalid transitions (no matching transition for the trigger):
"Invalid Transition - triggerId: <id>." - Attempting to add a duplicate state ID.
All errors are thrown as standard Error instances with messages of the form:
State Machine (<name>) - <message>Development
- Main entry point:
src/index.js(exports theStateMachineclass). - Core classes:
src/stateMachine.jssrc/State.jssrc/Transition.js
To run the example entry script defined in package.json:
npm startThis will execute node ./src/index.js.
