npm package: @pehotin/fsm
Description: Simple Fine State Machine
Author: Artem P
Latest version: 1.0.7

## 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

```bash
npm install fsm
```

Or, if you are working in this repo directly:

```bash
npm install
```

### Basic Usage

```js
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 -> idle
```

### Core 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)` → `State`
  - `addState(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 current `State` instance.
  - `isStarted()` – returns `true` if the machine has been started.

- **State**
  - Created via `state = machine.createState(name, entryAction, exitAction)` or directly via `new State(...)` and then `machine.addState(state, isStart)`.
  - **`id`**: read‑only string identifier (the `name` passed 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}`.
  - `getTransition(triggerId)` – returns the `Transition` registered for the given local trigger ID.

- **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.

  **Key methods:**

  - `getTriggerId()` – returns the transition ID.
  - `getTargetState()` – returns the target `State` instance.

### Trigger IDs and Transitions

When you call:

```js
idle.addTransition('start', active, () => { /* ... */ })
```

the `State` converts the provided trigger id to a local ID:

```js
`${state.id}:${triggerId}` // e.g. "idle:start"
```

When you later call:

```js
machine.trigger('start')
```

the state machine builds the same local ID based on the current state's id:

```js
`${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:

```text
State Machine (<name>) - <message>
```

### Development

- **Main entry point**: `src/index.js` (exports the `StateMachine` class).
- **Core classes**:
  - `src/stateMachine.js`
  - `src/State.js`
  - `src/Transition.js`

To run the example entry script defined in `package.json`:

```bash
npm start
```

This will execute `node ./src/index.js`.