Defining Machines
defineMachine
Pure factory function. Validates the config and returns it with improved TypeScript types. Zero Vue dependency — can be called and tested in Node without a Vue app.
ts
function defineMachine<TState, TEvent, TContext>(
config: MachineConfig<TState, TEvent, TContext>,
): MachineConfig<TState, TEvent, TContext>Config shape
ts
const machine = defineMachine({
id: 'login', // unique identifier (required, used by DevTools and MachineStore)
initial: 'idle', // starting state
context: {
// optional initial context (deep-cloned per instance)
attempts: 0,
error: null as string | null,
},
states: {
idle: {
on: {
// event name → transition config
SUBMIT: { target: 'loading', actions: [resetError] },
},
},
loading: {
on: {
SUCCESS: { target: 'success' },
FAILURE: { target: 'error', actions: [incrementAttempts] },
},
},
error: {
on: {
RETRY: { target: 'idle', guard: canRetry },
},
},
success: { type: 'final' }, // terminal — further send() calls are no-ops
},
})StateConfig options
| Field | Type | Description |
|---|---|---|
on | Record<TEvent, TransitionConfig> | Event handlers |
entry | Action[] | Invoked when the machine enters this state |
exit | Action[] | Invoked when the machine leaves this state |
type | 'final' | Terminal state — isDone becomes true, send() is ignored |
parallel | Record<string, SubMachineConfig> | Parallel regions (see Parallel states) |
TransitionConfig options
| Field | Type | Description |
|---|---|---|
target | TState | Destination state (TypeScript-checked against config) |
guard | Guard<TContext, TEvent> | Synchronous predicate; false or thrown exception blocks the transition |
actions | Action<TContext, TEvent>[] | Side-effects executed during the transition |
Dev-time validation
In development (import.meta.env.DEV !== false), defineMachine throws descriptive errors for:
- Empty
config.id initialnot found instates- Any
targetreferencing a non-existent state
Validation is tree-shaken away in production builds.
Guards and Actions
ts
type Guard<TContext, TEvent> = (context: TContext, event: EventObject<TEvent>) => boolean
type Action<TContext, TEvent> = (
context: TContext,
event: EventObject<TEvent>,
) => void | Partial<TContext> | Promise<Partial<TContext> | void>Guard rules:
- Must be synchronous and side-effect-free — it is also called by
can()reactively - An exception thrown inside a guard is caught and treated as
false Promisereturn values are not awaited — use actions for async work
Action rules:
- May be
async— the event queue awaits each action before executing the next - Return
Partial<TContext>to merge updates into context; returnvoidfor side-effects only - Actions execute in order:
exit→transition.actions→entry
ts
// Action that updates context
const incrementAttempts = (ctx: { attempts: number }) => ({
attempts: ctx.attempts + 1,
})
// Async action — fetch result is merged into context
const loadUser = async (ctx, event: { type: 'LOAD'; id: number }) => {
const user = await api.getUser(event.id)
return { user }
}
// Guard
const canRetry = (ctx: { attempts: number }) => ctx.attempts < 3