@statelyai/graph 0.3.0 → 0.3.1

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