/fsmachine

A simple and small TypeScript finite state machine

Primary LanguageTypeScript

fsmachine

A simple and small TypeScript finite state machine

Goals

  • Complete type safety
  • Type inference when registering transitions and dispatching events
  • Easily and cheaply create several state machines of the same type

API

createMachine()

  • Returns the transition function for registering valid transitions
  • Returns the create factory function which returns a finite state machine object

The function expects a State and Event generic.
This is so we can provide type safety and inference for the dispatch and transition functions

createMachineAsync()

create

Create Machine Options

A custom callback can be invoked if an invalid transition occurs by providing onInvalid when creating a machine:

interface CreateOptions {
  name?: string
  throw?: boolean
  onInvalid?: (from: string, event: string) => void
}

The create function is a factory function that creates an instance of the finite state machine.

const fsm = createMachine<State, Event>('unlocked', {
  onInvalid: (from, event) => {
    console.warn(`Invalid state transition ${from}::${event}`)
  },
  throw: false
})
fsm.transition([...], [...], [...])
const window = fsm.create()
window.dispatch('open')

transition

The transition function registers valid transitions.
The fourth parameter is an optional callback which is called when the transition is invoked.

Asynchronous state machines only differ by allowing asynchronous callbacks.

type Transition = [State, Event, State, Callback] | [State, Event, State]

function transition(...transition: Transition[])

Example

import { createMachine } from 'fsmachine'

type State = 'opened' | 'unlocked' | 'locked' | 'broken'
type Event = 'open' | 'close' | 'lock' | 'unlock' | 'break'

// "throw: false" disables throwing InvalidTransition errors and returns false instead
const { transition, create } = createMachine<State, Event>('unlocked', {
  name: 'window',
  throw: false,
})

// These calls are all completely type safe due to the State and Event generics provided earlier
transition(
  ['locked', 'unlock', 'unlocked', (from, event, to) => console.log({ from, event, to })],
  ['unlocked', 'open', 'opened'],
  ['opened', 'close', 'unlocked'],
  ['unlocked', 'lock', 'locked'],
  ['locked', 'break', 'broken'],
  ['unlocked', 'break', 'broken'],
)

// (Optional) We can override the options here or provide nothing to inherit the original options.
const window = create({ name: 'my-first-window', throw: false })

// Invalid state transitions throw by default
// We can disable this behaviour by providing { throw: false } to either createMachine() or to create()

window.dispatch('lock') // returns true
window.getState() // returns 'locked'
window.dispatch('open') // returns false
window.dispatch('break') // returns true
window.getState() // returns 'broken'