Serialization
A running machine is not just “which state am I in” — it is the whole stack of active states from the root down to the current leaf, plus any data those states recorded while active. StateWalker can snapshot all of it to a plain object and rebuild it later, so a process can be paused, persisted to disk or a database, and resumed exactly where it left off.
Dump and restore
dump() walks the active-state stack root→leaf and returns a plain object. restore() takes that object and rebuilds the machine — recreating each state (which fires onStateCreate, so your handlers re-attach) and replaying whatever each state saved.
import { FsmProcess } from "@statewalker/fsm";
const process = new FsmProcess(config);
// … register handlers, dispatch some events …
const snapshot = await process.dump();
// snapshot is JSON-serializable: persist it anywhere
localStorage.setItem("machine", JSON.stringify(snapshot));
// Later, in a fresh process:
const revived = new FsmProcess(config);
// re-register the SAME onStateCreate handlers, then:
await revived.restore(JSON.parse(localStorage.getItem("machine")));
// `revived` now rests on the same leaf, ready to dispatch again
Restore into a fresh, not-yet-started process, and register the same onStateCreate handlers before restoring — restore fires them for each recreated state so the machine comes back fully wired.
The dump shape
type FsmProcessDump = {
status: number; // internal traversal status
event?: string; // the last event
stack: { key: string; data: Record<string, unknown> }[]; // root → leaf
};
The stack is the chain of active states. Each entry has the state’s key and a data bag — empty unless that state’s dump hooks filled it.
Saving per-state data
By default a dump records only which states are active. If a state carries data that must survive — a retry counter, a form draft, a timestamp — record it with the state’s dump hook and read it back with restore:
process.onStateCreate((state) => {
if (state.key !== "Retrying") return;
let attempts = 0;
state.onEnter(() => { attempts++; });
// Contribute to this state's snapshot:
state.dump((_state, d) => { d.attempts = attempts; });
// Rehydrate from this state's snapshot:
state.restore((_state, d) => { attempts = (d.attempts as number) ?? 0; });
});
The data object passed to dump is the same bag that appears in the corresponding stack entry, and the same object handed back to restore. Keep it JSON-serializable if you plan to persist across processes.
Both dump() and restore() accept extra ...args that are forwarded to every hook, so you can pass a serializer, a store, or a context object through to your per-state logic.
Through the runner
If you use startProcess, the same capability is on the returned handle — handle.dump() and handle.restore(dump) — so you can persist and resume without holding the underlying FsmProcess.