Version v2.1.0 Size 2.7 KB gzip Dependencies Zero dependencies
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: {},
},
});| Feature | Clockwork | XState | Zustand |
|---|---|---|---|
| Bundle size | 2.7 KB | Larger actor/statechart runtime | Smaller store runtime |
| Zero dependencies | |||
| Pure transition API | |||
| Owned cancellation | |||
| Framework coupling |
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/clockworksh
npm install @vielzeug/clockworksh
yarn add @vielzeug/clockworkQuick 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.