nextNodes is used only by graph.toMermaid() to draw diagram edges. It is not enforced at runtime — any string is a valid nodeId in a next() call, as long as a node with that ID is registered in the Graph.
Node errors propagate out of graph.run() as normal exceptions. Wrap the run() call to catch and handle them:
import { Graph, MaxGraphIterationsError, UnknownNodeError } from "jsr:@vibesjs/sdk";const graph = new Graph([new FetchNode(), new ProcessNode()]);try { const result = await graph.run(initialState, "fetch"); console.log(result);} catch (err) { if (err instanceof MaxGraphIterationsError) { // A node was visited too many times - likely a cycle console.error("Possible infinite loop in graph:", err.message); } else if (err instanceof UnknownNodeError) { // A next() call referenced a node ID not registered in the Graph console.error("Missing node:", err.message); } else { throw err; }}
Graph state is JSON-serialised when you use FileStatePersistence. Avoid storing class instances, functions, or Date objects — use plain primitives, arrays, and objects.
// Good: flat, serialisabletype State = { url: string; content?: string; attempts: number; error?: string;};// Avoid: non-serialisable valuestype BadState = { db: DatabaseConnection; // not JSON-serialisable createdAt: Date; // loses type on deserialise};
Always spread the previous state when building the next one. BaseNode.run() receives the state by value, but adopting an immutable style prevents subtle bugs, especially when nodes are used in multiple branches.
async run(state: State) { // Create a new object - do not mutate the incoming state return next("next-node", { ...state, content: "new value" });}
Combine FileStatePersistence with a stable graphId to make a graph resumable across process restarts. The graph saves state after each node and resumes from the last checkpoint on restart:
import { FileStatePersistence, Graph } from "jsr:@vibesjs/sdk";const persistence = new FileStatePersistence<State>("./graph-checkpoints");// First run (or resume after crash)const result = await graph.run(initialState, "fetch", { persistence, graphId: "pipeline-run-42",});// State file is deleted on successful completion
For testing, use MemoryStatePersistence to avoid writing files:
import { MemoryStatePersistence } from "jsr:@vibesjs/sdk";const persistence = new MemoryStatePersistence<State>();const result = await graph.run(initialState, "fetch", { persistence, graphId: "test-run",});