Interceptor Pipeline
Problem
You need to intercept machine events for cross-cutting concerns — logging, analytics, authorization — without polluting state transition logic.
Solution
Pass an interceptors array to machine(). Each interceptor is a pure function (event, snapshot) => Ev | null. Return the event (or a transformed event) to allow it; return null to block the chain:
ts
import { machine, type InterceptorFn } from '@vielzeug/clockwork';
type Event = { type: 'ADMIN_ACTION' } | { type: 'GO' } | { type: 'STOP' };
type Context = { isAdmin: boolean };
type State = 'active' | 'idle';
// Logging interceptor — observes all events, passes them through
const logger: InterceptorFn<State, Context, Event> = (event, snapshot) => {
console.log(`[${snapshot.state}] ${event.type}`);
return event; // must return the event to allow it
};
// Authorization interceptor — blocks events based on context
const authGuard: InterceptorFn<State, Context, Event> = (event, snapshot) => {
if (event.type === 'ADMIN_ACTION' && !snapshot.context.isAdmin) {
console.warn('Blocked: insufficient permissions');
return null; // null blocks the event
}
return event;
};
const config = {
context: { isAdmin: false },
initial: 'idle',
states: {
active: { on: { STOP: { target: 'idle' } } },
idle: {
on: {
ADMIN_ACTION: { target: 'active' },
GO: { target: 'active' },
},
},
},
};
// Interceptors run left-to-right: logger runs first, then authGuard
const m = machine(config, { interceptors: [logger, authGuard] });
console.log(m.send({ type: 'ADMIN_ACTION' })); // 'rejected' — blocked by authGuard
console.log(m.send({ type: 'GO' })); // 'transitioned'
m[Symbol.dispose]();Pitfalls
- Interceptors run left-to-right. The first
nullin the chain stops all subsequent interceptors. - Returning
nullswallows the event silently.send()returns'rejected'. Log or emit a signal if you need UI feedback. - Interceptors are synchronous. Do not
awaitinside interceptors; use entry/exit actions for async side effects. - Interceptors can transform events. Return a new event object to change its type or payload before it reaches the machine.