Skip to content

Define a sequential flow

The minimal pattern: a straight line of steps, each waiting for a specific action before advancing.

The pattern

s1 ──ACTION_A──▶ s2 ──ACTION_B──▶ s3 ✓

Code

ts
import { z } from 'zod';
import { createWorkflow } from 'flowyd';

const linear = createWorkflow({ name: 'my-workflow' })
  .defineAction('ACTION_A', z.object({ actorId: z.string() }))
  .defineAction('ACTION_B', z.object({ actorId: z.string() }))

  .addStep('s1')
  .addStep('s2')
  .addStep('s3')

  .setInitial('s1')
  .setTerminal(['s3'])

  .addTransition({ from: 's1', to: 's2', on: 'ACTION_A' })
  .addTransition({ from: 's2', to: 's3', on: 'ACTION_B' })

  .build();

const inst = linear.createInstance('run-001');

await inst.dispatch('ACTION_A', { actorId: 'alice' });
console.log(inst.getCurrentStates()); // ['s2']

await inst.dispatch('ACTION_B', { actorId: 'bob' });
console.log(inst.getCurrentStates()); // ['s3']
console.log(inst.isTerminal()); // true

Rules

  • Call order matters. The fluent chain must follow: defineActionaddStep/addFork/addJoin/addWaitsetInitial/setTerminaladdTransitionbuild.
  • Every used state must be registered. Each ID referenced in setInitial, setTerminal, or addTransition must have been registered via addStep, addFork, addJoin, or addWait before build() is called.
  • At least one terminal state is required. A workflow with no terminal state throws at build().
  • Dispatch on a terminal instance always returns { success: false, reason: 'terminal-state' }.

Multiple transitions from one state

A state can have transitions for different actions, enabling branching:

ts
const approval = createWorkflow({ name: 'approval' })
  .defineAction('APPROVE', z.object({}))
  .defineAction('REJECT', z.object({ reason: z.string() }))

  .addStep('draft')
  .addStep('approved')
  .addStep('rejected')

  .setInitial('draft')
  .setTerminal(['approved', 'rejected'])

  .addTransition({ from: 'draft', to: 'approved', on: 'APPROVE' })
  .addTransition({ from: 'draft', to: 'rejected', on: 'REJECT' })

  .build();

The first action dispatched from draft determines which branch is taken. The other transition is never used for that instance.

Released under the MIT License.