Skip to main content

Diff & Patches

The Diff & Patches API provides functions to compare graphs, compute structured differences, and apply incremental changes. This is useful for version control, undo/redo systems, and synchronization.

Overview

  • getDiff - Compute structured diff between two graphs
  • isEmptyDiff - Check if a diff has zero changes
  • invertDiff - Reverse a diff (swap added/removed, old/new)
  • getPatches - Generate ordered patch list from two graphs
  • applyPatches - Apply patches to a graph (mutable)
  • toPatches - Convert diff to ordered patch list
  • toDiff - Convert patch list to structured diff

Diff Functions

getDiff

Compute a structured diff from graph a to graph b by matching entity IDs.
function getDiff<N, E>(
  a: Graph<N, E>,
  b: Graph<N, E>
): GraphDiff<N, E>
a
Graph<N, E>
required
The source graph (before changes)
b
Graph<N, E>
required
The target graph (after changes)
Returns GraphDiff<N, E> - Structured diff with added, removed, and updated entities

Example

import { createGraph, getDiff } from '@statelyai/graph';

const a = createGraph({
  nodes: [{ id: 'n1' }],
  edges: []
});

const b = createGraph({
  nodes: [
    { id: 'n1', label: 'Updated' },
    { id: 'n2' }
  ],
  edges: [{ id: 'e1', sourceId: 'n1', targetId: 'n2' }]
});

const diff = getDiff(a, b);
console.log(diff);
// {
//   nodes: {
//     added: [{ id: 'n2' }],
//     removed: [],
//     updated: [{ id: 'n1', old: { label: '' }, new: { label: 'Updated' } }]
//   },
//   edges: {
//     added: [{ id: 'e1', sourceId: 'n1', targetId: 'n2' }],
//     removed: [],
//     updated: []
//   }
// }

isEmptyDiff

Check if a diff has zero changes (no additions, removals, or updates).
function isEmptyDiff(diff: GraphDiff): boolean
diff
GraphDiff
required
The diff to check
Returns boolean - true if diff has no changes

Example

import { createGraph, getDiff, isEmptyDiff } from '@statelyai/graph';

const graph = createGraph({ nodes: [{ id: 'n1' }], edges: [] });
const diff = getDiff(graph, graph);

if (isEmptyDiff(diff)) {
  console.log('No changes detected');
}

invertDiff

Invert a diff by swapping added/removed arrays and old/new values in updates.
function invertDiff<N, E>(diff: GraphDiff<N, E>): GraphDiff<N, E>
diff
GraphDiff<N, E>
required
The diff to invert
Returns GraphDiff<N, E> - Inverted diff (reverses the changes)

Example

import { createGraph, getDiff, invertDiff } from '@statelyai/graph';

const a = createGraph({ nodes: [{ id: 'n1' }], edges: [] });
const b = createGraph({ nodes: [{ id: 'n2' }], edges: [] });

const forward = getDiff(a, b);
// forward.nodes.added → [{ id: 'n2' }]
// forward.nodes.removed → [{ id: 'n1' }]

const backward = invertDiff(forward);
// backward.nodes.added → [{ id: 'n1' }]
// backward.nodes.removed → [{ id: 'n2' }]

Patch Functions

getPatches

Compute an ordered patch list from graph a to graph b. Patches are ordered to avoid referential integrity violations. Patch order: delete edges → delete nodes → add nodes → add edges → update nodes → update edges
function getPatches<N, E>(
  a: Graph<N, E>,
  b: Graph<N, E>
): GraphPatch<N, E>[]
a
Graph<N, E>
required
The source graph
b
Graph<N, E>
required
The target graph
Returns GraphPatch<N, E>[] - Ordered array of patches

Example

import { createGraph, getPatches } from '@statelyai/graph';

const a = createGraph({
  nodes: [{ id: 'n1' }],
  edges: []
});

const b = createGraph({
  nodes: [{ id: 'n1' }, { id: 'n2' }],
  edges: [{ id: 'e1', sourceId: 'n1', targetId: 'n2' }]
});

const patches = getPatches(a, b);
// [
//   { op: 'addNode', node: { id: 'n2' } },
//   { op: 'addEdge', edge: { id: 'e1', sourceId: 'n1', targetId: 'n2' } }
// ]

applyPatches

Mutable. Apply an ordered list of patches to a graph. Delegates to addNode, deleteNode, updateNode, addEdge, deleteEdge, and updateEdge.
function applyPatches<N, E>(
  graph: Graph<N, E>,
  patches: GraphPatch<N, E>[]
): void
graph
Graph<N, E>
required
The graph to modify in place
patches
GraphPatch<N, E>[]
required
Ordered array of patches to apply

Example

import { createGraph, getPatches, applyPatches } from '@statelyai/graph';

const a = createGraph({ nodes: [{ id: 'n1' }], edges: [] });
const b = createGraph({ nodes: [{ id: 'n1' }, { id: 'n2' }], edges: [] });

const patches = getPatches(a, b);
applyPatches(a, patches);

console.log(a.nodes.length); // 2
console.log(a.nodes[1].id);  // 'n2'

Conversion Functions

toPatches

Convert a structured diff to an ordered patch list. The patch order ensures referential integrity:
  1. Add nodes (so new edges can reference them)
  2. Update edges (move endpoints away from deleted nodes)
  3. Delete edges (before their nodes are deleted)
  4. Delete nodes (safe now that edges are gone)
  5. Add edges (all referenced nodes exist)
  6. Update nodes
function toPatches<N, E>(diff: GraphDiff<N, E>): GraphPatch<N, E>[]
diff
GraphDiff<N, E>
required
The structured diff to convert
Returns GraphPatch<N, E>[] - Ordered patch array

Example

import { createGraph, getDiff, toPatches } from '@statelyai/graph';

const a = createGraph({ nodes: [{ id: 'n1' }], edges: [] });
const b = createGraph({ nodes: [{ id: 'n2' }], edges: [] });

const diff = getDiff(a, b);
const patches = toPatches(diff);
// [
//   { op: 'addNode', node: { id: 'n2' } },
//   { op: 'deleteNode', id: 'n1' }
// ]

toDiff

Convert an ordered patch list back into a structured diff.
function toDiff<N, E>(patches: GraphPatch<N, E>[]): GraphDiff<N, E>
patches
GraphPatch<N, E>[]
required
Array of patches to group
Returns GraphDiff<N, E> - Structured diff

Example

import { createGraph, getPatches, toDiff } from '@statelyai/graph';

const a = createGraph({ nodes: [{ id: 'n1' }], edges: [] });
const b = createGraph({ nodes: [{ id: 'n1' }, { id: 'n2' }], edges: [] });

const patches = getPatches(a, b);
const diff = toDiff(patches);

console.log(diff.nodes.added);  // [{ id: 'n2' }]
console.log(diff.nodes.removed); // []

Types

GraphDiff

Structured representation of changes between two graphs:
interface GraphDiff<N = any, E = any> {
  nodes: {
    added: NodeConfig<N>[];
    removed: NodeConfig<N>[];
    updated: NodeChange<N>[];
  };
  edges: {
    added: EdgeConfig<E>[];
    removed: EdgeConfig<E>[];
    updated: EdgeChange<E>[];
  };
}

NodeChange / EdgeChange

interface NodeChange<N = any> {
  id: string;
  old: Partial<GraphNode<N>>;
  new: Partial<GraphNode<N>>;
}

interface EdgeChange<E = any> {
  id: string;
  old: Partial<GraphEdge<E>>;
  new: Partial<GraphEdge<E>>;
}

GraphPatch

Union type representing individual patch operations:
type GraphPatch<N = any, E = any> =
  | { op: 'addNode'; node: NodeConfig<N> }
  | { op: 'deleteNode'; id: string }
  | { op: 'updateNode'; id: string; data: Partial<Omit<NodeConfig<N>, 'id'>> }
  | { op: 'addEdge'; edge: EdgeConfig<E> }
  | { op: 'deleteEdge'; id: string }
  | { op: 'updateEdge'; id: string; data: Partial<Omit<EdgeConfig<E>, 'id'>> };

Use Cases

Version Control

Track changes between graph versions:
import { createGraph, getDiff, getNode, addNode } from '@statelyai/graph';

const v1 = createGraph({ nodes: [{ id: 'a' }], edges: [] });
const v2 = structuredClone(v1);

addNode(v2, { id: 'b' });

const changelog = getDiff(v1, v2);
console.log(`Added ${changelog.nodes.added.length} nodes`);

Undo/Redo System

Use invertDiff to implement undo:
import { createGraph, getDiff, invertDiff, applyPatches, toPatches } from '@statelyai/graph';

const history: GraphPatch[][] = [];
const graph = createGraph({ nodes: [{ id: 'a' }], edges: [] });

function doAction(newGraph: Graph) {
  const patches = getPatches(graph, newGraph);
  history.push(patches);
  applyPatches(graph, patches);
}

function undo() {
  const lastPatches = history.pop();
  if (!lastPatches) return;
  
  const diff = toDiff(lastPatches);
  const inverseDiff = invertDiff(diff);
  const undoPatches = toPatches(inverseDiff);
  
  applyPatches(graph, undoPatches);
}

Synchronization

Sync graphs across network or storage:
import { createGraph, getPatches, applyPatches } from '@statelyai/graph';

// Client side
const local = createGraph({ nodes: [{ id: 'a' }], edges: [] });
const remote = createGraph({ nodes: [{ id: 'a' }, { id: 'b' }], edges: [] });

const patches = getPatches(local, remote);
sendToServer(patches); // Send minimal delta

// Server side
receiveFromClient((patches) => {
  applyPatches(serverGraph, patches);
});

Build docs developers (and LLMs) love