Skip to content
clockwork logoClockworkState
Framework-neutral typed state machines with pure transitions, actor-owned runtime work, timers, invokes, and explicit effects.
Version
v3.0.0
Size
2.2 KB gzip
BrowserNode ≥22SSRDeno
defineMachineClockworkErrorMachineActorMachineConfig View all 6 exports

Why Clockwork? ​

Application workflows often mix state changes with timers, requests, rendering, and cleanup. Clockwork keeps transition logic pure while each disposable actor owns runtime work. You can test state decisions without starting effects or invokes.

ts
import { defineMachine } from '@vielzeug/clockwork';

// Before
if (status === 'idle') status = 'loading';
fetchItems().then((items) => {
  status = 'ready';
  data = items;
});

// After
type Event = { type: 'FETCH' } | { items: string[]; type: 'DONE' };
const machine = defineMachine<{ items: string[] }, Event>()({
  context: { items: [] },
  initial: 'idle',
  states: {
    idle: { on: { FETCH: { target: 'loading' } } },
    loading: {
      invoke: [{
        src: ({ signal }) => fetch('/api/items', { signal }).then((response) => response.json() as Promise<string[]>),
        onDone: ({ result }) => ({ items: result, type: 'DONE' }),
      }],
      on: { DONE: { reduce: ({ event }) => ({ items: event.items }), target: 'ready' } },
    },
    ready: {},
  },
});
FeatureClockworkXStateZustand
Bundle size2.2 KBLarger actor/statechart runtimeSmaller store runtime
Zero dependencies
Pure transition API Statechart-focused
Owned cancellation Actor disposal
Framework coupling None None None

Use Clockwork when your feature has explicit workflow states, cancellable work, or effects that must run after a state commit.

Consider XState when you need statecharts, visual tooling, or its broader actor ecosystem.

Installation ​

sh
pnpm add @vielzeug/clockwork
sh
npm install @vielzeug/clockwork
sh
yarn add @vielzeug/clockwork

Quick Start ​

Define the context and event union, create an actor, observe its snapshot, then dispose it when its owner ends.

ts
import { defineMachine } from '@vielzeug/clockwork';

type Event = { type: 'DEC' } | { type: 'INC' };

const counter = defineMachine<{ count: number }, Event>()({
  context: { count: 0 },
  initial: 'idle',
  states: {
    idle: {
      on: {
        DEC: { reduce: ({ context }) => ({ count: context.count - 1 }), target: 'idle' },
        INC: { reduce: ({ context }) => ({ count: context.count + 1 }), target: 'idle' },
      },
    },
  },
});

using actor = counter.createActor();
actor.subscribe((snapshot) => console.log(snapshot));
actor.send({ type: 'INC' });
// { context: { count: 1 }, state: 'idle' }

Features ​

  • defineMachine() — defines one flat, typed machine and rejects the footguns types cannot express.
  • machine.transition() — evaluates a transition without actor runtime work.
  • machine.createActor() — creates isolated, disposable runtime ownership.
  • reduce — returns a replacement context from a transition.
  • effects — run after commit and stable subscriber notification.
  • invoke — runs cancellable asynchronous work on state entry.
  • after — schedules cancellable delayed transitions.
  • actor.snapshot — exposes the current shallow-frozen state/context value.

Documentation ​

See Also ​

  • Herald — publish events between independent actors without coupling machine definitions.
  • Ripple — bridge actor snapshots into a reactive graph when you need fine-grained rendering.
  • Ward — call authorization predicates from transition guards.