Skip to content

Setup

machine() builds and runs a machine. setup() is how you author the config with full type-checking — inferred types, compile-checked names, no manual type arguments.

import { machine, act } from '@dunky-dev/state-machine'
const toggle = machine({
initial: 'inactive',
context: { count: 0 },
states: {
inactive: {
on: {
flip: { target: 'active', actions: act($ => ({ count: $.context.count + 1 })) },
},
},
active: {
on: { flip: { target: 'inactive' } },
},
},
})
toggle.start()
toggle.send({ type: 'flip' })
toggle.state // 'active'
toggle.context // { count: 1 }

Returns a service — built but not running. Call .start() to boot effects and watchers, .stop() to tear them down. .send() dispatches events; .state and .context are always readable.

Infers State, Context, and Event from the literal. Named guards / actions / effects / delays stay as loose strings.

import { setup } from '@dunky-dev/state-machine'
const config = setup().createMachine({
initial: 'closed',
context: { open: false },
states: {
closed: { on: { toggle: { target: 'open' } } },
open: { on: { toggle: { target: 'closed' } } },
},
})

Name a registry of guards / actions / effects / delays. Every reference in the config is compile-checked and autocompleted against the registry keys — a typo is a type error.

import { setup } from '@dunky-dev/state-machine'
type Ctx = { open: boolean; locked: boolean }
type Ev = { type: 'toggle' }
const { createMachine } = setup<Ctx, Ev>().config({
guards: {
isUnlocked: ({ context }) => !context.locked,
},
actions: {
logOpen: ({ context }) => console.log('open:', context.open),
},
})
const config = createMachine({
initial: 'closed',
context: { open: false, locked: false },
states: {
closed: {
on: {
toggle: { target: 'open', guard: 'isUnlocked', actions: 'logOpen' },
},
},
open: {
on: { toggle: { target: 'closed' } },
},
},
})