Rxova
Skip to content

Snapshot

machine.getSnapshot() returns the complete immutable read model for one point in time.

const snapshot = machine.getSnapshot();
snapshot.type; // "linear" | "graph"
snapshot.status; // "idle" | "running" | "paused" | "completed" | "terminated"
snapshot.context;
snapshot.currentStep;
snapshot.transition;
snapshot.history;
snapshot.machine;
snapshot.plugins;

currentStep is null before initial entry. Otherwise it contains:

FieldMeaning
idCurrent step id.
metadataStatic definition metadata.
isFirstTimeVisittrue only on the first entry of this step in the run.
asyncLoading, success, and error state for current entry work.

Linear current steps add index, isFirstStep, and isLastStep. Graph current steps add isTerminal.

snapshot.transition = {
pending: false,
phase: null, // "working" | "leaving" | "entering" | null
from: null,
to: null
};
snapshot.history = {
timeline: ["account", "review"],
currentIndex: 1,
visited: { account: true, review: true },
canGoBack: true,
canGoForward: false
};

visited has an entry for every declared step.

snapshot.machine carries one field, outcome — how the run ended, once it has.

It used to carry six more: isLoading and one boolean per status. Every one of them restated something the snapshot already said, so they are gone. Read snapshot.status directly for the lifecycle ("idle" | "running" | "paused" | "completed" | "terminated"), and snapshot.transition.pending for “is a move in flight” — transition also tells you the phase and the from/to of that move, which a bare boolean could not. snapshot.currentStep.async is the separate, step-level question: whether the current entry’s own work settled or failed.

snapshot.machine.outcome = null; // or { type: "completed" | "terminated", payload }

Completion and termination set snapshot.machine.outcome; only restart() clears it back to null.

if (snapshot.type === "linear") {
snapshot.steps.stepOrder;
snapshot.steps.totalSteps;
snapshot.steps.visitedStepCount;
snapshot.currentStep?.isLastStep;
}
if (snapshot.type === "graph") {
snapshot.declaredEvents;
snapshot.availableEvents;
snapshot.availableSteps;
snapshot.outgoingTransitions;
snapshot.steps.totalSteps;
snapshot.steps.visitedStepCount;
snapshot.currentStep?.isTerminal;
}

declaredEvents includes every event declared from the current step. availableEvents and availableSteps include only candidates whose guard currently passes. outgoingTransitions explains both projections with each candidate’s target, priority, guard result, enabled state, and whether first-enabled event dispatch would select it. A terminal step has no declared outgoing transitions, regardless of guard results.

Introspection shows the resting-state answer. A work send’s candidate guard that reads the run result is evaluated here with result: undefined — outside a send there is no result yet. During the send itself the same guard sees the live result, so a result-dependent candidate can report guard: "failed" in the snapshot and still win the route once the work has run.

Do not mutate a snapshot or its context. Use machine.context.update() and read the next snapshot. Read the slice a consumer needs inside the listener:

machine.subscriptions.subscribe(() => renderAsyncState(machine.getSnapshot().currentStep?.async));