@statelyai/graph 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +12 -11
  2. package/dist/{adjacency-list-A4_Eiwj3.mjs → adjacency-list-Bv4tfiM3.mjs} +33 -0
  3. package/dist/{algorithms-DBU7nmIV.mjs → algorithms-CnTmuX9t.mjs} +682 -24
  4. package/dist/algorithms.d.mts +479 -10
  5. package/dist/algorithms.mjs +1 -1
  6. package/dist/converter-C5DlzzHs.mjs +67 -0
  7. package/dist/{edge-list-DuHMz8hf.mjs → edge-list-R1SUbHwe.mjs} +32 -0
  8. package/dist/formats/adjacency-list/index.d.mts +35 -1
  9. package/dist/formats/adjacency-list/index.mjs +1 -1
  10. package/dist/formats/converter/index.d.mts +37 -3
  11. package/dist/formats/converter/index.mjs +1 -1
  12. package/dist/formats/cytoscape/index.d.mts +50 -2
  13. package/dist/formats/cytoscape/index.mjs +53 -5
  14. package/dist/formats/d3/index.d.mts +48 -2
  15. package/dist/formats/d3/index.mjs +48 -2
  16. package/dist/formats/dot/index.d.mts +56 -2
  17. package/dist/formats/dot/index.mjs +55 -2
  18. package/dist/formats/edge-list/index.d.mts +34 -1
  19. package/dist/formats/edge-list/index.mjs +1 -1
  20. package/dist/formats/gexf/index.d.mts +1 -1
  21. package/dist/formats/gexf/index.mjs +5 -5
  22. package/dist/formats/gml/index.d.mts +58 -2
  23. package/dist/formats/gml/index.mjs +59 -4
  24. package/dist/formats/graphml/index.d.mts +1 -1
  25. package/dist/formats/graphml/index.mjs +2 -2
  26. package/dist/formats/jgf/index.d.mts +51 -2
  27. package/dist/formats/jgf/index.mjs +54 -5
  28. package/dist/formats/mermaid/index.d.mts +201 -8
  29. package/dist/formats/mermaid/index.mjs +365 -26
  30. package/dist/formats/tgf/index.d.mts +47 -2
  31. package/dist/formats/tgf/index.mjs +46 -2
  32. package/dist/formats/xyflow/index.d.mts +73 -0
  33. package/dist/formats/xyflow/index.mjs +133 -0
  34. package/dist/index.d.mts +320 -14
  35. package/dist/index.mjs +117 -9
  36. package/dist/{indexing-BFFVMnjF.mjs → indexing-DitHphT7.mjs} +37 -7
  37. package/dist/queries.d.mts +353 -8
  38. package/dist/queries.mjs +359 -15
  39. package/dist/{types-B6Tpeerk.d.mts → types-Bq_fmLwW.d.mts} +15 -4
  40. package/package.json +3 -1
  41. package/dist/converter-DnbeyE_p.mjs +0 -33
@@ -1,12 +1,14 @@
1
- import { a as indexUpdateEdgeEndpoints, i as indexReparentNode, n as indexAddEdge, o as invalidateIndex, r as indexAddNode, t as getIndex } from "./indexing-BFFVMnjF.mjs";
1
+ import { a as indexUpdateEdgeEndpoints, i as indexReparentNode, n as indexAddEdge, o as invalidateIndex, r as indexAddNode, t as getIndex } from "./indexing-DitHphT7.mjs";
2
2
 
3
3
  //#region src/graph.ts
4
4
  function resolveNode(config) {
5
+ if (!config.id) throw new Error("Node id must be a non-empty string");
6
+ if (config.parentId === "") throw new Error("Node parentId must be a non-empty string");
5
7
  const node = {
6
8
  type: "node",
7
9
  id: config.id,
8
- parentId: config.parentId ?? null,
9
- initialNodeId: config.initialNodeId ?? null,
10
+ ...config.parentId !== void 0 && { parentId: config.parentId ?? null },
11
+ ...config.initialNodeId !== void 0 && { initialNodeId: config.initialNodeId ?? null },
10
12
  label: config.label ?? "",
11
13
  data: config.data
12
14
  };
@@ -20,6 +22,9 @@ function resolveNode(config) {
20
22
  return node;
21
23
  }
22
24
  function resolveEdge(config) {
25
+ if (!config.id) throw new Error("Edge id must be a non-empty string");
26
+ if (!config.sourceId) throw new Error("Edge sourceId must be a non-empty string");
27
+ if (!config.targetId) throw new Error("Edge targetId must be a non-empty string");
23
28
  const edge = {
24
29
  type: "edge",
25
30
  id: config.id,
@@ -36,7 +41,17 @@ function resolveEdge(config) {
36
41
  if (config.style !== void 0) edge.style = config.style;
37
42
  return edge;
38
43
  }
39
- /** Create a graph from a config. Resolves defaults for all fields. */
44
+ /**
45
+ * Create a graph from a config. Resolves defaults for all fields.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * const graph = createGraph({
50
+ * nodes: [{ id: 'a' }, { id: 'b' }],
51
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
52
+ * });
53
+ * ```
54
+ */
40
55
  function createGraph(config) {
41
56
  const graph = {
42
57
  id: config?.id ?? "",
@@ -50,7 +65,18 @@ function createGraph(config) {
50
65
  if (config?.style !== void 0) graph.style = config.style;
51
66
  return graph;
52
67
  }
53
- /** Create a visual graph with required position/size on all nodes and edges. */
68
+ /**
69
+ * Create a visual graph with required position/size on all nodes and edges.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * const graph = createVisualGraph({
74
+ * nodes: [{ id: 'a', x: 0, y: 0, width: 100, height: 50 }],
75
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'a', x: 0, y: 0, width: 0, height: 0 }],
76
+ * });
77
+ * // graph.nodes[0].x === 0
78
+ * ```
79
+ */
54
80
  function createVisualGraph(config) {
55
81
  const base = createGraph(config);
56
82
  return {
@@ -62,7 +88,7 @@ function createVisualGraph(config) {
62
88
  y: n.y ?? 0,
63
89
  width: n.width ?? 0,
64
90
  height: n.height ?? 0,
65
- shape: n.shape ?? "rectangle"
91
+ ...n.shape !== void 0 && { shape: n.shape }
66
92
  })),
67
93
  edges: base.edges.map((e) => ({
68
94
  ...e,
@@ -75,11 +101,30 @@ function createVisualGraph(config) {
75
101
  }
76
102
  /**
77
103
  * Create a graph by BFS exploration of a transition function.
78
- * Each unique state becomes a node; each (state, event) nextState becomes an edge.
104
+ * Each unique state becomes a node; each (state, event) -> nextState becomes an edge.
79
105
  *
80
106
  * - Node IDs are determined by `serializeState` (default: `JSON.stringify`).
81
107
  * - Edge IDs use the format `sourceId|serializedEvent|targetId` for uniqueness
82
108
  * and debuggability. Edge labels are just the serialized event string.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * const graph = createGraphFromTransition(
113
+ * (state, event) => {
114
+ * if (state === 'green' && event === 'TIMER') return 'yellow';
115
+ * if (state === 'yellow' && event === 'TIMER') return 'red';
116
+ * if (state === 'red' && event === 'TIMER') return 'green';
117
+ * return state;
118
+ * },
119
+ * {
120
+ * initialState: 'green',
121
+ * events: ['TIMER'],
122
+ * serializeState: (s) => s,
123
+ * serializeEvent: (e) => e,
124
+ * },
125
+ * );
126
+ * // graph.nodes.length === 3
127
+ * ```
83
128
  */
84
129
  function createGraphFromTransition(transition, options) {
85
130
  const serializeState = options.serializeState ?? JSON.stringify;
@@ -139,46 +184,102 @@ function createGraphFromTransition(transition, options) {
139
184
  edges
140
185
  });
141
186
  }
142
- /** Get a node by id, or `undefined` if not found. */
187
+ /**
188
+ * Get a node by id, or `undefined` if not found.
189
+ *
190
+ * @example
191
+ * ```ts
192
+ * const graph = createGraph({ nodes: [{ id: 'a' }] });
193
+ * const node = getNode(graph, 'a'); // GraphNode
194
+ * const missing = getNode(graph, 'z'); // undefined
195
+ * ```
196
+ */
143
197
  function getNode(graph, id) {
144
198
  const arrayIdx = getIndex(graph).nodeById.get(id);
145
199
  return arrayIdx !== void 0 ? graph.nodes[arrayIdx] : void 0;
146
200
  }
147
- /** Get an edge by id, or `undefined` if not found. */
201
+ /**
202
+ * Get an edge by id, or `undefined` if not found.
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * const graph = createGraph({
207
+ * nodes: [{ id: 'a' }, { id: 'b' }],
208
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
209
+ * });
210
+ * const edge = getEdge(graph, 'e1'); // GraphEdge
211
+ * const missing = getEdge(graph, 'z'); // undefined
212
+ * ```
213
+ */
148
214
  function getEdge(graph, id) {
149
215
  const arrayIdx = getIndex(graph).edgeById.get(id);
150
216
  return arrayIdx !== void 0 ? graph.edges[arrayIdx] : void 0;
151
217
  }
152
- /** Check if a node exists in the graph. */
218
+ /**
219
+ * Check if a node exists in the graph.
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * const graph = createGraph({ nodes: [{ id: 'a' }] });
224
+ * hasNode(graph, 'a'); // true
225
+ * hasNode(graph, 'z'); // false
226
+ * ```
227
+ */
153
228
  function hasNode(graph, id) {
154
229
  return getIndex(graph).nodeById.has(id);
155
230
  }
156
- /** Check if an edge exists in the graph. */
231
+ /**
232
+ * Check if an edge exists in the graph.
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * const graph = createGraph({
237
+ * nodes: [{ id: 'a' }, { id: 'b' }],
238
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
239
+ * });
240
+ * hasEdge(graph, 'e1'); // true
241
+ * hasEdge(graph, 'z'); // false
242
+ * ```
243
+ */
157
244
  function hasEdge(graph, id) {
158
245
  return getIndex(graph).edgeById.has(id);
159
246
  }
160
247
  /**
161
248
  * **Mutable.** Add a node to the graph. Mutates `graph.nodes` in place.
162
249
  * @returns The resolved node that was added.
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * const graph = createGraph();
254
+ * const node = addNode(graph, { id: 'a', label: 'Node A' });
255
+ * // graph.nodes.length === 1
256
+ * ```
163
257
  */
164
258
  function addNode(graph, config) {
259
+ const node = resolveNode(config);
165
260
  const idx = getIndex(graph);
166
261
  if (idx.nodeById.has(config.id)) throw new Error(`Node "${config.id}" already exists`);
167
- if (config.parentId != null && !idx.nodeById.has(config.parentId)) throw new Error(`Parent node "${config.parentId}" does not exist`);
168
- const node = resolveNode(config);
262
+ if (config.parentId && !idx.nodeById.has(config.parentId)) throw new Error(`Parent node "${config.parentId}" does not exist`);
169
263
  indexAddNode(idx, node, graph.nodes.push(node) - 1);
170
264
  return node;
171
265
  }
172
266
  /**
173
267
  * **Mutable.** Add an edge to the graph. Mutates `graph.edges` in place.
174
268
  * @returns The resolved edge that was added.
269
+ *
270
+ * @example
271
+ * ```ts
272
+ * const graph = createGraph({ nodes: [{ id: 'a' }, { id: 'b' }] });
273
+ * const edge = addEdge(graph, { id: 'e1', sourceId: 'a', targetId: 'b' });
274
+ * // graph.edges.length === 1
275
+ * ```
175
276
  */
176
277
  function addEdge(graph, config) {
278
+ const edge = resolveEdge(config);
177
279
  const idx = getIndex(graph);
178
280
  if (idx.edgeById.has(config.id)) throw new Error(`Edge "${config.id}" already exists`);
179
281
  if (!idx.nodeById.has(config.sourceId)) throw new Error(`Source node "${config.sourceId}" does not exist`);
180
282
  if (!idx.nodeById.has(config.targetId)) throw new Error(`Target node "${config.targetId}" does not exist`);
181
- const edge = resolveEdge(config);
182
283
  indexAddEdge(idx, edge, graph.edges.push(edge) - 1);
183
284
  return edge;
184
285
  }
@@ -188,6 +289,16 @@ function addEdge(graph, config) {
188
289
  *
189
290
  * By default, children are deleted recursively.
190
291
  * With `{ reparent: true }`, children are re-parented to the deleted node's parent.
292
+ *
293
+ * @example
294
+ * ```ts
295
+ * const graph = createGraph({
296
+ * nodes: [{ id: 'a' }, { id: 'b' }],
297
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
298
+ * });
299
+ * deleteNode(graph, 'a');
300
+ * // graph.nodes.length === 1, edge e1 also removed
301
+ * ```
191
302
  */
192
303
  function deleteNode(graph, id, opts) {
193
304
  const node = getNode(graph, id);
@@ -205,6 +316,16 @@ function deleteNode(graph, id, opts) {
205
316
  }
206
317
  /**
207
318
  * **Mutable.** Delete an edge. Mutates `graph.edges` in place.
319
+ *
320
+ * @example
321
+ * ```ts
322
+ * const graph = createGraph({
323
+ * nodes: [{ id: 'a' }, { id: 'b' }],
324
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
325
+ * });
326
+ * deleteEdge(graph, 'e1');
327
+ * // graph.edges.length === 0
328
+ * ```
208
329
  */
209
330
  function deleteEdge(graph, id) {
210
331
  if (!hasEdge(graph, id)) throw new Error(`Edge "${id}" does not exist`);
@@ -214,6 +335,13 @@ function deleteEdge(graph, id) {
214
335
  /**
215
336
  * **Mutable.** Update a node in place.
216
337
  * @returns The updated node.
338
+ *
339
+ * @example
340
+ * ```ts
341
+ * const graph = createGraph({ nodes: [{ id: 'a', label: 'old' }] });
342
+ * const updated = updateNode(graph, 'a', { label: 'new' });
343
+ * // updated.label === 'new'
344
+ * ```
217
345
  */
218
346
  function updateNode(graph, id, update) {
219
347
  const idx = getIndex(graph);
@@ -238,6 +366,16 @@ function updateNode(graph, id, update) {
238
366
  /**
239
367
  * **Mutable.** Update an edge in place.
240
368
  * @returns The updated edge.
369
+ *
370
+ * @example
371
+ * ```ts
372
+ * const graph = createGraph({
373
+ * nodes: [{ id: 'a' }, { id: 'b' }],
374
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b', label: 'old' }],
375
+ * });
376
+ * const updated = updateEdge(graph, 'e1', { label: 'new' });
377
+ * // updated.label === 'new'
378
+ * ```
241
379
  */
242
380
  function updateEdge(graph, id, update) {
243
381
  const idx = getIndex(graph);
@@ -262,6 +400,16 @@ function updateEdge(graph, id, update) {
262
400
  /**
263
401
  * **Mutable.** Add multiple nodes and edges to the graph.
264
402
  * Nodes are added first, then edges (so edges can reference new nodes).
403
+ *
404
+ * @example
405
+ * ```ts
406
+ * const graph = createGraph();
407
+ * addEntities(graph, {
408
+ * nodes: [{ id: 'a' }, { id: 'b' }],
409
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
410
+ * });
411
+ * // graph.nodes.length === 2, graph.edges.length === 1
412
+ * ```
265
413
  */
266
414
  function addEntities(graph, entities) {
267
415
  for (const nodeConfig of entities.nodes ?? []) addNode(graph, nodeConfig);
@@ -270,6 +418,16 @@ function addEntities(graph, entities) {
270
418
  /**
271
419
  * **Mutable.** Delete entities by id(s). Automatically detects whether each id
272
420
  * is a node or edge. Node deletions cascade to children and connected edges.
421
+ *
422
+ * @example
423
+ * ```ts
424
+ * const graph = createGraph({
425
+ * nodes: [{ id: 'a' }, { id: 'b' }],
426
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
427
+ * });
428
+ * deleteEntities(graph, ['a', 'e1']);
429
+ * // graph.nodes.length === 1, graph.edges.length === 0
430
+ * ```
273
431
  */
274
432
  function deleteEntities(graph, ids, opts) {
275
433
  const idArray = Array.isArray(ids) ? ids : [ids];
@@ -279,6 +437,18 @@ function deleteEntities(graph, ids, opts) {
279
437
  /**
280
438
  * **Mutable.** Update multiple nodes and edges in place.
281
439
  * Each entry must include an `id` to identify which entity to update.
440
+ *
441
+ * @example
442
+ * ```ts
443
+ * const graph = createGraph({
444
+ * nodes: [{ id: 'a', label: 'old' }],
445
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'a', label: 'old' }],
446
+ * });
447
+ * updateEntities(graph, {
448
+ * nodes: [{ id: 'a', label: 'new' }],
449
+ * edges: [{ id: 'e1', label: 'new' }],
450
+ * });
451
+ * ```
282
452
  */
283
453
  function updateEntities(graph, updates) {
284
454
  for (const nodeUpdate of updates.nodes ?? []) {
@@ -293,13 +463,33 @@ function updateEntities(graph, updates) {
293
463
  /**
294
464
  * OOP wrapper around a plain `Graph` object.
295
465
  * Delegates to the standalone mutable functions.
466
+ *
467
+ * @example
468
+ * ```ts
469
+ * const instance = new GraphInstance({
470
+ * nodes: [{ id: 'a' }, { id: 'b' }],
471
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
472
+ * });
473
+ * instance.addNode({ id: 'c' });
474
+ * instance.hasNode('c'); // true
475
+ * instance.toJSON(); // plain Graph object
476
+ * ```
296
477
  */
297
478
  var GraphInstance = class GraphInstance {
298
479
  graph;
299
480
  constructor(config) {
300
481
  this.graph = createGraph(config);
301
482
  }
302
- /** Wrap an existing plain graph object. */
483
+ /**
484
+ * Wrap an existing plain graph object.
485
+ *
486
+ * @example
487
+ * ```ts
488
+ * const graph = createGraph({ nodes: [{ id: 'a' }] });
489
+ * const instance = GraphInstance.from(graph);
490
+ * instance.hasNode('a'); // true
491
+ * ```
492
+ */
303
493
  static from(graph) {
304
494
  const instance = Object.create(GraphInstance.prototype);
305
495
  instance.graph = graph;
@@ -377,6 +567,23 @@ function collectDescendants(graph, id) {
377
567
 
378
568
  //#endregion
379
569
  //#region src/algorithms.ts
570
+ /**
571
+ * Breadth-first traversal generator yielding nodes level by level.
572
+ *
573
+ * @example
574
+ * ```ts
575
+ * import { createGraph, bfs } from '@statelyai/graph';
576
+ *
577
+ * const graph = createGraph({
578
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
579
+ * edges: [{ id: 'ab', sourceId: 'a', targetId: 'b' }, { id: 'bc', sourceId: 'b', targetId: 'c' }],
580
+ * });
581
+ *
582
+ * for (const node of bfs(graph, 'a')) {
583
+ * console.log(node.id); // 'a', 'b', 'c'
584
+ * }
585
+ * ```
586
+ */
380
587
  function* bfs(graph, startId) {
381
588
  const idx = getIndex(graph);
382
589
  const visited = /* @__PURE__ */ new Set();
@@ -393,6 +600,23 @@ function* bfs(graph, startId) {
393
600
  }
394
601
  }
395
602
  }
603
+ /**
604
+ * Depth-first traversal generator yielding nodes as visited.
605
+ *
606
+ * @example
607
+ * ```ts
608
+ * import { createGraph, dfs } from '@statelyai/graph';
609
+ *
610
+ * const graph = createGraph({
611
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
612
+ * edges: [{ id: 'ab', sourceId: 'a', targetId: 'b' }, { id: 'bc', sourceId: 'b', targetId: 'c' }],
613
+ * });
614
+ *
615
+ * for (const node of dfs(graph, 'a')) {
616
+ * console.log(node.id); // 'a', 'b', 'c'
617
+ * }
618
+ * ```
619
+ */
396
620
  function* dfs(graph, startId) {
397
621
  const idx = getIndex(graph);
398
622
  const visited = /* @__PURE__ */ new Set();
@@ -424,6 +648,21 @@ function getSuccessorIds(graph, nodeId) {
424
648
  const idx = getIndex(graph);
425
649
  return (idx.outEdges.get(nodeId) ?? []).map((eid) => graph.edges[idx.edgeById.get(eid)].targetId);
426
650
  }
651
+ /**
652
+ * Checks whether the graph contains no cycles.
653
+ *
654
+ * @example
655
+ * ```ts
656
+ * import { createGraph, isAcyclic } from '@statelyai/graph';
657
+ *
658
+ * const dag = createGraph({
659
+ * nodes: [{ id: 'a' }, { id: 'b' }],
660
+ * edges: [{ id: 'ab', sourceId: 'a', targetId: 'b' }],
661
+ * });
662
+ *
663
+ * isAcyclic(dag); // true
664
+ * ```
665
+ */
427
666
  function isAcyclic(graph) {
428
667
  if (graph.type === "undirected") return isAcyclicUndirected(graph);
429
668
  const WHITE = 0, GRAY = 1, BLACK = 2;
@@ -468,6 +707,23 @@ function isAcyclicUndirected(graph) {
468
707
  for (const n of graph.nodes) if (!visited.has(n.id) && hasCycle(n.id, null)) return false;
469
708
  return true;
470
709
  }
710
+ /**
711
+ * Returns connected components as arrays of nodes.
712
+ * Treats all edges as undirected for connectivity.
713
+ *
714
+ * @example
715
+ * ```ts
716
+ * import { createGraph, getConnectedComponents } from '@statelyai/graph';
717
+ *
718
+ * const graph = createGraph({
719
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
720
+ * edges: [{ id: 'ab', sourceId: 'a', targetId: 'b' }],
721
+ * });
722
+ *
723
+ * const components = getConnectedComponents(graph);
724
+ * // [[nodeA, nodeB], [nodeC]]
725
+ * ```
726
+ */
471
727
  function getConnectedComponents(graph) {
472
728
  const idx = getIndex(graph);
473
729
  const visited = /* @__PURE__ */ new Set();
@@ -504,6 +760,25 @@ function getConnectedComponents(graph) {
504
760
  }
505
761
  return components;
506
762
  }
763
+ /**
764
+ * Returns a topological ordering of nodes, or `null` if the graph is cyclic.
765
+ *
766
+ * @example
767
+ * ```ts
768
+ * import { createGraph, getTopologicalSort } from '@statelyai/graph';
769
+ *
770
+ * const graph = createGraph({
771
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
772
+ * edges: [
773
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
774
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
775
+ * ],
776
+ * });
777
+ *
778
+ * const sorted = getTopologicalSort(graph);
779
+ * // [nodeA, nodeB, nodeC]
780
+ * ```
781
+ */
507
782
  function getTopologicalSort(graph) {
508
783
  const idx = getIndex(graph);
509
784
  const inDeg = /* @__PURE__ */ new Map();
@@ -528,16 +803,65 @@ function getTopologicalSort(graph) {
528
803
  if (result.length !== graph.nodes.length) return null;
529
804
  return result;
530
805
  }
806
+ /**
807
+ * Checks whether a path exists between two nodes.
808
+ *
809
+ * @example
810
+ * ```ts
811
+ * import { createGraph, hasPath } from '@statelyai/graph';
812
+ *
813
+ * const graph = createGraph({
814
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
815
+ * edges: [{ id: 'ab', sourceId: 'a', targetId: 'b' }],
816
+ * });
817
+ *
818
+ * hasPath(graph, 'a', 'b'); // true
819
+ * hasPath(graph, 'a', 'c'); // false
820
+ * ```
821
+ */
531
822
  function hasPath(graph, sourceId, targetId) {
532
823
  return getShortestPaths(graph, {
533
824
  from: sourceId,
534
825
  to: targetId
535
826
  }).length > 0;
536
827
  }
828
+ /**
829
+ * Checks whether the graph is connected (all nodes reachable from any node).
830
+ *
831
+ * @example
832
+ * ```ts
833
+ * import { createGraph, isConnected } from '@statelyai/graph';
834
+ *
835
+ * const graph = createGraph({
836
+ * nodes: [{ id: 'a' }, { id: 'b' }],
837
+ * edges: [{ id: 'ab', sourceId: 'a', targetId: 'b' }],
838
+ * });
839
+ *
840
+ * isConnected(graph); // true
841
+ * ```
842
+ */
537
843
  function isConnected(graph) {
538
844
  if (graph.nodes.length === 0) return true;
539
845
  return getConnectedComponents(graph).length <= 1;
540
846
  }
847
+ /**
848
+ * Checks whether the graph is a tree (connected and acyclic).
849
+ *
850
+ * @example
851
+ * ```ts
852
+ * import { createGraph, isTree } from '@statelyai/graph';
853
+ *
854
+ * const tree = createGraph({
855
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
856
+ * edges: [
857
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
858
+ * { id: 'ac', sourceId: 'a', targetId: 'c' },
859
+ * ],
860
+ * });
861
+ *
862
+ * isTree(tree); // true
863
+ * ```
864
+ */
541
865
  function isTree(graph) {
542
866
  return isConnected(graph) && isAcyclic(graph);
543
867
  }
@@ -671,6 +995,24 @@ function* reconstructPaths(graph, prev, sourceNode, targetId) {
671
995
  /**
672
996
  * Lazily yields all shortest paths from a source node.
673
997
  * Use `getShortestPaths` for the full array.
998
+ *
999
+ * @example
1000
+ * ```ts
1001
+ * import { createGraph, genShortestPaths } from '@statelyai/graph';
1002
+ *
1003
+ * const graph = createGraph({
1004
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1005
+ * edges: [
1006
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1007
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
1008
+ * ],
1009
+ * initialNodeId: 'a',
1010
+ * });
1011
+ *
1012
+ * for (const path of genShortestPaths(graph)) {
1013
+ * console.log(path.steps.map(s => s.node.id));
1014
+ * }
1015
+ * ```
674
1016
  */
675
1017
  function* genShortestPaths(graph, opts) {
676
1018
  const idx = getIndex(graph);
@@ -681,25 +1023,101 @@ function* genShortestPaths(graph, opts) {
681
1023
  const sourceNode = sourceNi !== void 0 ? graph.nodes[sourceNi] : graph.nodes.find((n) => n.id === sourceId);
682
1024
  for (const targetId of targets) yield* reconstructPaths(graph, prev, sourceNode, targetId);
683
1025
  }
1026
+ /**
1027
+ * Returns all shortest paths from a source node as an array.
1028
+ * Delegates to `genShortestPaths` internally.
1029
+ *
1030
+ * @example
1031
+ * ```ts
1032
+ * import { createGraph, getShortestPaths } from '@statelyai/graph';
1033
+ *
1034
+ * const graph = createGraph({
1035
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1036
+ * edges: [
1037
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1038
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
1039
+ * ],
1040
+ * initialNodeId: 'a',
1041
+ * });
1042
+ *
1043
+ * const paths = getShortestPaths(graph);
1044
+ * // paths to 'b' and 'c' from 'a'
1045
+ * ```
1046
+ */
684
1047
  function getShortestPaths(graph, opts) {
685
1048
  return [...genShortestPaths(graph, opts)];
686
1049
  }
687
1050
  /**
688
- * Returns a single shortest path from source to target, or undefined if unreachable.
1051
+ * Returns a single shortest path from source to target, or `undefined` if unreachable.
1052
+ *
1053
+ * @example
1054
+ * ```ts
1055
+ * import { createGraph, getShortestPath } from '@statelyai/graph';
1056
+ *
1057
+ * const graph = createGraph({
1058
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1059
+ * edges: [
1060
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1061
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
1062
+ * ],
1063
+ * initialNodeId: 'a',
1064
+ * });
1065
+ *
1066
+ * const path = getShortestPath(graph, { to: 'c' });
1067
+ * // path.steps -> [{node: nodeB, edge: ...}, {node: nodeC, edge: ...}]
1068
+ * ```
689
1069
  */
690
1070
  function getShortestPath(graph, opts) {
691
1071
  for (const path of genShortestPaths(graph, opts)) return path;
692
1072
  }
693
1073
  /**
694
- * Returns all simple (acyclic) paths from a source node.
695
- * Uses DFS with backtracking.
1074
+ * Returns all simple (acyclic) paths from a source node as an array.
1075
+ * Delegates to `genSimplePaths` internally.
1076
+ *
1077
+ * @example
1078
+ * ```ts
1079
+ * import { createGraph, getSimplePaths } from '@statelyai/graph';
1080
+ *
1081
+ * const graph = createGraph({
1082
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1083
+ * edges: [
1084
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1085
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
1086
+ * { id: 'ac', sourceId: 'a', targetId: 'c' },
1087
+ * ],
1088
+ * initialNodeId: 'a',
1089
+ * });
1090
+ *
1091
+ * const paths = getSimplePaths(graph, { to: 'c' });
1092
+ * // two paths: a->b->c and a->c
1093
+ * ```
696
1094
  */
697
1095
  function getSimplePaths(graph, opts) {
698
1096
  return [...genSimplePaths(graph, opts)];
699
1097
  }
700
1098
  /**
701
- * Lazily yields all simple (acyclic) paths from a source node.
1099
+ * Lazily yields all simple (acyclic) paths from a source node via DFS backtracking.
702
1100
  * Use `getSimplePaths` for the full array.
1101
+ *
1102
+ * @example
1103
+ * ```ts
1104
+ * import { createGraph, genSimplePaths } from '@statelyai/graph';
1105
+ *
1106
+ * const graph = createGraph({
1107
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1108
+ * edges: [
1109
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1110
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
1111
+ * { id: 'ac', sourceId: 'a', targetId: 'c' },
1112
+ * ],
1113
+ * initialNodeId: 'a',
1114
+ * });
1115
+ *
1116
+ * for (const path of genSimplePaths(graph, { to: 'c' })) {
1117
+ * console.log(path.steps.map(s => s.node.id));
1118
+ * // ['b', 'c'] or ['c']
1119
+ * }
1120
+ * ```
703
1121
  */
704
1122
  function* genSimplePaths(graph, opts) {
705
1123
  const idx = getIndex(graph);
@@ -741,11 +1159,49 @@ function* genSimplePaths(graph, opts) {
741
1159
  yield* found;
742
1160
  }
743
1161
  /**
744
- * Returns a single simple (acyclic) path from source to target, or undefined if unreachable.
1162
+ * Returns a single simple (acyclic) path from source to target, or `undefined` if unreachable.
1163
+ *
1164
+ * @example
1165
+ * ```ts
1166
+ * import { createGraph, getSimplePath } from '@statelyai/graph';
1167
+ *
1168
+ * const graph = createGraph({
1169
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1170
+ * edges: [
1171
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1172
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
1173
+ * ],
1174
+ * initialNodeId: 'a',
1175
+ * });
1176
+ *
1177
+ * const path = getSimplePath(graph, { to: 'c' });
1178
+ * // path.steps -> [{node: nodeB, edge: ...}, {node: nodeC, edge: ...}]
1179
+ * ```
745
1180
  */
746
1181
  function getSimplePath(graph, opts) {
747
1182
  for (const path of genSimplePaths(graph, opts)) return path;
748
1183
  }
1184
+ /**
1185
+ * Returns strongly connected components using Tarjan's algorithm.
1186
+ * Only meaningful for directed graphs.
1187
+ *
1188
+ * @example
1189
+ * ```ts
1190
+ * import { createGraph, getStronglyConnectedComponents } from '@statelyai/graph';
1191
+ *
1192
+ * const graph = createGraph({
1193
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1194
+ * edges: [
1195
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1196
+ * { id: 'ba', sourceId: 'b', targetId: 'a' },
1197
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
1198
+ * ],
1199
+ * });
1200
+ *
1201
+ * const sccs = getStronglyConnectedComponents(graph);
1202
+ * // [[nodeA, nodeB], [nodeC]]
1203
+ * ```
1204
+ */
749
1205
  function getStronglyConnectedComponents(graph) {
750
1206
  const idx = getIndex(graph);
751
1207
  let indexCounter = 0;
@@ -784,12 +1240,49 @@ function getStronglyConnectedComponents(graph) {
784
1240
  for (const n of graph.nodes) if (!nodeIndex.has(n.id)) strongconnect(n.id);
785
1241
  return result;
786
1242
  }
1243
+ /**
1244
+ * Returns all elementary cycles as an array of paths.
1245
+ * Delegates to `genCycles` internally.
1246
+ *
1247
+ * @example
1248
+ * ```ts
1249
+ * import { createGraph, getCycles } from '@statelyai/graph';
1250
+ *
1251
+ * const graph = createGraph({
1252
+ * nodes: [{ id: 'a' }, { id: 'b' }],
1253
+ * edges: [
1254
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1255
+ * { id: 'ba', sourceId: 'b', targetId: 'a' },
1256
+ * ],
1257
+ * });
1258
+ *
1259
+ * const cycles = getCycles(graph);
1260
+ * // one cycle: a -> b -> a
1261
+ * ```
1262
+ */
787
1263
  function getCycles(graph) {
788
1264
  return [...genCycles(graph)];
789
1265
  }
790
1266
  /**
791
- * Lazily yields cycles one at a time.
1267
+ * Lazily yields elementary cycles one at a time.
792
1268
  * Use `getCycles` for the full array.
1269
+ *
1270
+ * @example
1271
+ * ```ts
1272
+ * import { createGraph, genCycles } from '@statelyai/graph';
1273
+ *
1274
+ * const graph = createGraph({
1275
+ * nodes: [{ id: 'a' }, { id: 'b' }],
1276
+ * edges: [
1277
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1278
+ * { id: 'ba', sourceId: 'b', targetId: 'a' },
1279
+ * ],
1280
+ * });
1281
+ *
1282
+ * for (const cycle of genCycles(graph)) {
1283
+ * console.log(cycle.steps.map(s => s.node.id)); // ['b', 'a']
1284
+ * }
1285
+ * ```
793
1286
  */
794
1287
  function* genCycles(graph) {
795
1288
  if (graph.type === "undirected") yield* genCyclesUndirected(graph);
@@ -912,6 +1405,23 @@ function getNeighborEdgesAll(graph, nodeId) {
912
1405
  /**
913
1406
  * Returns a single canonical preorder (DFS visit-order) sequence.
914
1407
  * Visits neighbors in the order they appear in the adjacency list.
1408
+ *
1409
+ * @example
1410
+ * ```ts
1411
+ * import { createGraph, getPreorder } from '@statelyai/graph';
1412
+ *
1413
+ * const graph = createGraph({
1414
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1415
+ * edges: [
1416
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1417
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
1418
+ * ],
1419
+ * initialNodeId: 'a',
1420
+ * });
1421
+ *
1422
+ * const order = getPreorder(graph);
1423
+ * // [nodeA, nodeB, nodeC]
1424
+ * ```
915
1425
  */
916
1426
  function getPreorder(graph, opts) {
917
1427
  const idx = getIndex(graph);
@@ -938,6 +1448,23 @@ function getPreorder(graph, opts) {
938
1448
  /**
939
1449
  * Returns a single canonical postorder (DFS finish-order) sequence.
940
1450
  * Visits neighbors in the order they appear in the adjacency list.
1451
+ *
1452
+ * @example
1453
+ * ```ts
1454
+ * import { createGraph, getPostorder } from '@statelyai/graph';
1455
+ *
1456
+ * const graph = createGraph({
1457
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1458
+ * edges: [
1459
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1460
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
1461
+ * ],
1462
+ * initialNodeId: 'a',
1463
+ * });
1464
+ *
1465
+ * const order = getPostorder(graph);
1466
+ * // [nodeC, nodeB, nodeA]
1467
+ * ```
941
1468
  */
942
1469
  function getPostorder(graph, opts) {
943
1470
  const idx = getIndex(graph);
@@ -960,11 +1487,49 @@ function getPostorder(graph, opts) {
960
1487
  }
961
1488
  return result;
962
1489
  }
963
- /** Returns all possible preorder sequences as an array. Can be exponential — prefer `genPreorders`. */
1490
+ /**
1491
+ * Returns all possible preorder sequences as an array. Can be exponential -- prefer `genPreorders`.
1492
+ *
1493
+ * @example
1494
+ * ```ts
1495
+ * import { createGraph, getPreorders } from '@statelyai/graph';
1496
+ *
1497
+ * const graph = createGraph({
1498
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1499
+ * edges: [
1500
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1501
+ * { id: 'ac', sourceId: 'a', targetId: 'c' },
1502
+ * ],
1503
+ * initialNodeId: 'a',
1504
+ * });
1505
+ *
1506
+ * const allOrders = getPreorders(graph);
1507
+ * // [[nodeA, nodeB, nodeC], [nodeA, nodeC, nodeB]]
1508
+ * ```
1509
+ */
964
1510
  function getPreorders(graph, opts) {
965
1511
  return [...genPreorders(graph, opts)];
966
1512
  }
967
- /** Returns all possible postorder sequences as an array. Can be exponential — prefer `genPostorders`. */
1513
+ /**
1514
+ * Returns all possible postorder sequences as an array. Can be exponential -- prefer `genPostorders`.
1515
+ *
1516
+ * @example
1517
+ * ```ts
1518
+ * import { createGraph, getPostorders } from '@statelyai/graph';
1519
+ *
1520
+ * const graph = createGraph({
1521
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1522
+ * edges: [
1523
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1524
+ * { id: 'ac', sourceId: 'a', targetId: 'c' },
1525
+ * ],
1526
+ * initialNodeId: 'a',
1527
+ * });
1528
+ *
1529
+ * const allOrders = getPostorders(graph);
1530
+ * // [[nodeB, nodeC, nodeA], [nodeC, nodeB, nodeA]]
1531
+ * ```
1532
+ */
968
1533
  function getPostorders(graph, opts) {
969
1534
  return [...genPostorders(graph, opts)];
970
1535
  }
@@ -972,6 +1537,25 @@ function getPostorders(graph, opts) {
972
1537
  * Lazily yields all possible preorder (DFS visit-order) sequences.
973
1538
  * Different neighbor exploration orders yield different sequences.
974
1539
  * Use `getPreorder()` for a single canonical ordering.
1540
+ *
1541
+ * @example
1542
+ * ```ts
1543
+ * import { createGraph, genPreorders } from '@statelyai/graph';
1544
+ *
1545
+ * const graph = createGraph({
1546
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1547
+ * edges: [
1548
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1549
+ * { id: 'ac', sourceId: 'a', targetId: 'c' },
1550
+ * ],
1551
+ * initialNodeId: 'a',
1552
+ * });
1553
+ *
1554
+ * for (const order of genPreorders(graph)) {
1555
+ * console.log(order.map(n => n.id));
1556
+ * // ['a', 'b', 'c'] or ['a', 'c', 'b']
1557
+ * }
1558
+ * ```
975
1559
  */
976
1560
  function* genPreorders(graph, opts) {
977
1561
  const idx = getIndex(graph);
@@ -1017,6 +1601,25 @@ function* genPreorders(graph, opts) {
1017
1601
  * Lazily yields all possible postorder (DFS finish-order) sequences.
1018
1602
  * Different neighbor exploration orders yield different sequences.
1019
1603
  * Use `getPostorder()` for a single canonical ordering.
1604
+ *
1605
+ * @example
1606
+ * ```ts
1607
+ * import { createGraph, genPostorders } from '@statelyai/graph';
1608
+ *
1609
+ * const graph = createGraph({
1610
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1611
+ * edges: [
1612
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1613
+ * { id: 'ac', sourceId: 'a', targetId: 'c' },
1614
+ * ],
1615
+ * initialNodeId: 'a',
1616
+ * });
1617
+ *
1618
+ * for (const order of genPostorders(graph)) {
1619
+ * console.log(order.map(n => n.id));
1620
+ * // ['b', 'c', 'a'] or ['c', 'b', 'a']
1621
+ * }
1622
+ * ```
1020
1623
  */
1021
1624
  function* genPostorders(graph, opts) {
1022
1625
  const idx = getIndex(graph);
@@ -1061,6 +1664,26 @@ function* genPostorders(graph, opts) {
1061
1664
  * Returns a minimum spanning tree of the graph.
1062
1665
  * Only meaningful for connected undirected graphs (or the component reachable
1063
1666
  * from an arbitrary start node in directed graphs).
1667
+ *
1668
+ * @example
1669
+ * ```ts
1670
+ * import { createGraph, getMinimumSpanningTree } from '@statelyai/graph';
1671
+ *
1672
+ * const graph = createGraph({
1673
+ * type: 'undirected',
1674
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1675
+ * edges: [
1676
+ * { id: 'ab', sourceId: 'a', targetId: 'b', data: { weight: 1 } },
1677
+ * { id: 'bc', sourceId: 'b', targetId: 'c', data: { weight: 2 } },
1678
+ * { id: 'ac', sourceId: 'a', targetId: 'c', data: { weight: 3 } },
1679
+ * ],
1680
+ * });
1681
+ *
1682
+ * const mst = getMinimumSpanningTree(graph, {
1683
+ * getWeight: (e) => e.data.weight,
1684
+ * });
1685
+ * // mst has edges 'ab' and 'bc' (total weight 3)
1686
+ * ```
1064
1687
  */
1065
1688
  function getMinimumSpanningTree(graph, opts) {
1066
1689
  const algorithm = opts?.algorithm ?? "prim";
@@ -1157,7 +1780,23 @@ function kruskalMST(graph, getWeight) {
1157
1780
  /**
1158
1781
  * Returns shortest paths between all pairs of nodes.
1159
1782
  * Algorithm 'dijkstra' (default): runs getShortestPaths per source node.
1160
- * Algorithm 'floyd-warshall': classic O(V³) dynamic programming.
1783
+ * Algorithm 'floyd-warshall': classic O(V^3) dynamic programming.
1784
+ *
1785
+ * @example
1786
+ * ```ts
1787
+ * import { createGraph, getAllPairsShortestPaths } from '@statelyai/graph';
1788
+ *
1789
+ * const graph = createGraph({
1790
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1791
+ * edges: [
1792
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1793
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
1794
+ * ],
1795
+ * });
1796
+ *
1797
+ * const allPaths = getAllPairsShortestPaths(graph);
1798
+ * // paths for every reachable (source, target) pair
1799
+ * ```
1161
1800
  */
1162
1801
  function getAllPairsShortestPaths(graph, opts) {
1163
1802
  if ((opts?.algorithm ?? "dijkstra") === "floyd-warshall") return floydWarshallAllPaths(graph, opts?.getWeight);
@@ -1264,6 +1903,25 @@ function fwReconstruct(graph, prev, idxOf, nodeIds, sourceNode, sourceIdx, targe
1264
1903
  *
1265
1904
  * Steps are concatenated: head.steps ++ tail.steps (tail already starts
1266
1905
  * from the overlap node, so no slicing is needed).
1906
+ *
1907
+ * @example
1908
+ * ```ts
1909
+ * import { createGraph, getShortestPath, joinPaths } from '@statelyai/graph';
1910
+ *
1911
+ * const graph = createGraph({
1912
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
1913
+ * edges: [
1914
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
1915
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
1916
+ * ],
1917
+ * initialNodeId: 'a',
1918
+ * });
1919
+ *
1920
+ * const ab = getShortestPath(graph, { to: 'b' })!;
1921
+ * const bc = getShortestPath(graph, { from: 'b', to: 'c' })!;
1922
+ * const ac = joinPaths(ab, bc);
1923
+ * // ac: a -> b -> c
1924
+ * ```
1267
1925
  */
1268
1926
  function joinPaths(headPath, tailPath) {
1269
1927
  const headEnd = headPath.steps.length > 0 ? headPath.steps[headPath.steps.length - 1].node : headPath.source;