Handlers
Graph guards and event work can call injected handlers without closing over application services. This keeps graph definitions reusable and makes guard decisions straightforward to test.
Declare handlers
Section titled “Declare handlers”type Handlers = { canApprove(role: string): boolean;};
const definition = { initial: "review" as const, context: { role: "member" }, handlers: { canApprove: (role: string) => role === "admin" } satisfies Handlers, steps: { review: {}, approved: {} }, steps: { review: { on: { APPROVE: [ { to: "approved" as const, when: ({ context, handlers }: { context: { role: string }; handlers: Handlers }) => handlers.canApprove(context.role) } ] } } }};Override at creation
Section titled “Override at creation”Creation options take precedence over handlers stored in the definition:
const production = createGraphJourney(definition);
const test = createGraphJourney(definition, { handlers: { canApprove: () => true }});Handlers reach the places where a definition decides something:
- graph
whenguards — including the candidates of a work send, whose guards also receive the runresult; - event work — the
runandcommitof a transactional send.
Step hooks (onEnter, onLeave) and onTransition deliberately receive no handlers. This is a
design decision, not an omission. Hooks react to a move that has already committed: they get the
snapshot plus updateContext/raise, and nothing they do can influence routing or be rolled back.
Async that needs an injected client belongs in event work, where the transaction stages its outcome
before the guards route on it. A hook that must call a service can close over it at module scope —
and that closure is usually a hint the call wants to move into work.
Guards must stay synchronous because they are used to derive available transitions. Caller-driven next/previous operations can attach asynchronous navigation work.
The bag declares handler types, so guards and work see them typed:
type AppBag = { context: Context; stepId: StepId; events: Event; handlers: Handlers;};
const machine = withGraphTypes<AppBag>()(definition);