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.| Feature | Clockwork | xstate | zustand |
|---|---|---|---|
| Bundle size | 3.1 KB | ~15 KB | ~2 KB |
| Zero dependencies | |||
| Typed discriminated events | |||
| Reactive signals | |||
| Persistence adapter | |||
| Hierarchical states | |||
| Interceptor pipeline | |||
| Context isolation |
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/clockworksh
npm install @vielzeug/clockworksh
yarn add @vielzeug/clockworkQuick 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); // 5Features
machine()— Validate config and start an instance in one calldefine()— Validate once, call.start()to spawn independent instances- Shorthand transitions — Single transition or array, your choice
- Typed events — Discriminated unions with TypeScript inference
SendResult—send()returns{ ok, queued, status }wherestatusis'transitioned'|'queued'|'rejected'- Reactive state — State and context are
@vielzeug/ripplesignals - Async invokes — Native Promise support;
onDone/onErrorreceive(result, context) - Delayed transitions — Timer-based
afterwith guards and actions - Hierarchical states — Compound states with automatic leaf resolution
- Interceptors — Pure event interceptors — return event or
nullto block - Persistence — Snapshot save/load adapter
- Tracing — Ring buffer; auto-enabled (50 entries) when
onDebugis set - Debug events — Discriminated union callback with zero overhead when omitted; use
debugInterpret()from@vielzeug/clockwork/devtoolsfor 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
| Import | Purpose |
|---|---|
@vielzeug/clockwork | All exports and types |
@vielzeug/clockwork/devtools | debugInterpret — 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