Skip to content
clockwork logoClockworkState
Framework-neutral typed state machines with pure transitions, actor-owned runtime work, timers, invokes, and explicit effects.
Version
v2.1.0
Size
2.7 KB gzip
Dependencies
Zero dependencies
BrowserNode ≥22SSRDeno
defineMachineClockworkErrorMachineActorMachineConfig View all 7 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.7 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() — validates and compiles one flat machine definition.
  • 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 only after the actor commits and notifies subscribers.
  • invoke — runs cancellable asynchronous work on state entry.
  • after — schedules cancellable delayed transitions.
  • actor.snapshot — exposes the current readonly 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.