Skip to content
clockwork logoClockworkState
Zero-dependency typed FSM with async invokes, delayed transitions, hierarchical states, middleware, reactive state, persistence, tracing, and full TypeScript support.
v0.0.13.1 KB gzip Browser · Node.js · SSR · Deno
machinedefineresolveTransitionMachineErrorSendResultInterceptorFnInvokeArgsAfterEvent +2 more →

Why Clockwork?

Manual state management leads to invalid state combinations, unreachable code paths, and complex conditional logic. FSMs eliminate these bugs by making state transitions explicit and exhaustive.

ts
// Before — manual state management
type LoaderState = {
  data?: string;
  error?: Error;
  isRetrying?: boolean;
  status: 'error' | 'idle' | 'loading' | 'success';
};
// Multiple invalid state combinations are possible.

// After — FSM enforces valid state combinations
import { machine } from '@vielzeug/clockwork';
type Event = { type: 'FETCH' } | { type: 'DONE'; data: string } | { type: 'FAIL'; error: Error };

const loader = machine({
  context: { data: '' as string, error: undefined as Error | undefined },
  initial: 'idle',
  states: {
    error: { on: { FETCH: { target: 'loading' } } },
    idle: { on: { FETCH: { target: 'loading' } } },
    loading: { on: { DONE: { target: 'success' }, FAIL: { target: 'error' } } },
    success: { on: { FETCH: { target: 'loading' } } },
  },
});
// Now success && error is impossible. State is always valid.
FeatureClockworkxstatezustand
Bundle size3.1 KB~15 KB~2 KB
Zero dependencies 5+ deps
Typed discriminated events Partial
Reactive signals Native Observer pattern Native
Persistence adapter Pluggable
Hierarchical states Compound + leaf resolution
Interceptor pipeline Pure functions
Context isolation Cloned on every transition

Use Clockwork when you need predictable state machines with strict type safety, reactive integrations, and a minimal footprint in applications where state is defined upfront.

Consider xstate when you need visual state machine tooling or already have a large bundle budget.

Installation

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

Quick Start

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

type Event = { type: 'START' } | { type: 'COMPLETE'; result: string };

const m = machine({
  context: { count: 0 },
  initial: 'idle',
  states: {
    active: {
      on: {
        COMPLETE: {
          actions: [
            ({ context, event }) => {
              context.count = event.result.length;
            },
          ],
          target: 'idle',
        },
      },
    },
    idle: {
      on: { START: { target: 'active' } },
    },
  },
});

console.log(m.state.value); // 'idle'
console.log(m.context.value.count); // 0

console.log(m.send({ type: 'START' }).status); // 'transitioned'
console.log(m.send({ type: 'COMPLETE', result: 'hello' }).status); // 'transitioned'

console.log(m.state.value); // 'idle'
console.log(m.context.value.count); // 5

Features

  • machine() — Validate config and start an instance in one call
  • define() — Validate once, call .start() to spawn independent instances
  • Shorthand transitions — Single transition or array, your choice
  • Typed events — Discriminated unions with TypeScript inference
  • SendResultsend() returns { ok, queued, status } where status is 'transitioned' | 'queued' | 'rejected'
  • Reactive state — State and context are @vielzeug/ripple signals
  • Async invokes — Native Promise support; onDone/onError receive (result, context)
  • Delayed transitions — Timer-based after with guards and actions
  • Hierarchical states — Compound states with automatic leaf resolution
  • Interceptors — Pure event interceptors — return event or null to block
  • Persistence — Snapshot save/load adapter
  • Tracing — Ring buffer; auto-enabled (50 entries) when onDebug is set
  • Debug events — Discriminated union callback with zero overhead when omitted; use debugInterpret() from @vielzeug/clockwork/devtools for pre-wired console logging
  • Event queue — FIFO processing with configurable infinite-loop guard
  • Context isolation — Cloned draft before every commit; machine is unchanged on validation failure
  • Subscribe — Change-detection subscription without direct ripple dependency
  • Pure resolver — Test transition logic independently with resolveTransition()

Sub-paths

ImportPurpose
@vielzeug/clockworkAll exports and types
@vielzeug/clockwork/devtoolsdebugInterpret — pre-wired console logging (dev only)

Documentation

See Also

  • Ripple — Reactive signals and effects; core reactivity layer for Clockwork
  • Herald — Typed event bus; complementary for event-driven architectures
  • Ward — RBAC engine; use alongside Clockwork for state-dependent permissions
  • Forge — Form state management; integrates with Clockwork for multi-step workflows