1state logo

<1state>

A small finite state machine for JavaScript, shaped after SCXML. For multi-step forms: flows where the next question depends on the answers already given, where the user leaves halfway through, and where the flow gains a step while they are away.

Get started

At a glance

  • Zero

    Dependencies in the engine

  • 100%

    Line, branch and function coverage

  • SCXML

    The shape it follows

One property, three features

Show me the code!

A two-question signup, resumed from a database row:

  import { createMachine, assign } from '@1state/core'

const signup = {
  context: { email: null, password: null },
  initial: 'email',
  states: {
    email: {
      on: { answer: { target: 'email', actions: assign({ email: (_ctx, e) => e.value }) } },
      go: [{ target: 'password', cond: (ctx) => ctx.email !== null }],
    },
    password: {
      on: { answer: { target: 'password', actions: assign({ password: (_ctx, e) => e.value }) } },
      go: [{ target: 'done', cond: (ctx) => ctx.password !== null }],
    },
    done: { type: 'final' },
  },
}

const machine = createMachine(signup, { context: await load(accountId) })

await machine.start() // exactly where they left off

Packages

Three of them. No dependencies in the engine, ESM only, Node 20+.

There is no persistence adapter

Deliberately. There is no machine state to persist — the position is derived from the answers, and the answers are your row. Persist the row, which you were going to do anyway, and you are done.

Why

Ready to get started?

Install the engine and write your first flow.

Read the docs