@statelyai/graph 0.1.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 (52) hide show
  1. package/README.md +65 -15
  2. package/dist/{adjacency-list-CXpOCibq.mjs → adjacency-list-ITO40kmn.mjs} +34 -1
  3. package/dist/{algorithms-R35X6ro4.mjs → algorithms-NWSB2RWj.mjs} +753 -19
  4. package/dist/algorithms.d.mts +488 -11
  5. package/dist/algorithms.mjs +2 -2
  6. package/dist/converter-CchokMDg.mjs +67 -0
  7. package/dist/edge-list-CgX6bBIF.mjs +71 -0
  8. package/dist/formats/adjacency-list/index.d.mts +44 -0
  9. package/dist/formats/adjacency-list/index.mjs +3 -0
  10. package/dist/formats/converter/index.d.mts +61 -0
  11. package/dist/formats/converter/index.mjs +3 -0
  12. package/dist/formats/cytoscape/index.d.mts +83 -0
  13. package/dist/formats/cytoscape/index.mjs +135 -0
  14. package/dist/formats/d3/index.d.mts +68 -0
  15. package/dist/formats/d3/index.mjs +111 -0
  16. package/dist/formats/dot/index.d.mts +63 -0
  17. package/dist/formats/dot/index.mjs +288 -0
  18. package/dist/formats/edge-list/index.d.mts +43 -0
  19. package/dist/formats/edge-list/index.mjs +3 -0
  20. package/dist/formats/gexf/index.d.mts +9 -0
  21. package/dist/formats/gexf/index.mjs +249 -0
  22. package/dist/formats/gml/index.d.mts +65 -0
  23. package/dist/formats/gml/index.mjs +291 -0
  24. package/dist/formats/graphml/index.d.mts +9 -0
  25. package/dist/{graphml-CUTNRXqd.mjs → formats/graphml/index.mjs} +18 -4
  26. package/dist/formats/jgf/index.d.mts +79 -0
  27. package/dist/formats/jgf/index.mjs +134 -0
  28. package/dist/formats/mermaid/index.d.mts +381 -0
  29. package/dist/formats/mermaid/index.mjs +2237 -0
  30. package/dist/formats/tgf/index.d.mts +54 -0
  31. package/dist/formats/tgf/index.mjs +111 -0
  32. package/dist/index.d.mts +332 -21
  33. package/dist/index.mjs +117 -13
  34. package/dist/{indexing-BHg1VhqN.mjs → indexing-eNDrXdDA.mjs} +31 -2
  35. package/dist/queries.d.mts +430 -9
  36. package/dist/queries.mjs +472 -9
  37. package/dist/{types-XV3S5Jnh.d.mts → types-BDXC1O5b.d.mts} +37 -2
  38. package/package.json +43 -17
  39. package/dist/adjacency-list-DW-lAUe8.d.mts +0 -10
  40. package/dist/dot-BRtq3e3c.mjs +0 -59
  41. package/dist/dot-HmJeUMsj.d.mts +0 -6
  42. package/dist/edge-list-BRujEnnU.mjs +0 -39
  43. package/dist/edge-list-CJmfoNu2.d.mts +0 -10
  44. package/dist/formats/adjacency-list.d.mts +0 -2
  45. package/dist/formats/adjacency-list.mjs +0 -3
  46. package/dist/formats/dot.d.mts +0 -2
  47. package/dist/formats/dot.mjs +0 -3
  48. package/dist/formats/edge-list.d.mts +0 -2
  49. package/dist/formats/edge-list.mjs +0 -3
  50. package/dist/formats/graphml.d.mts +0 -2
  51. package/dist/formats/graphml.mjs +0 -3
  52. package/dist/graphml-CMjPzSfY.d.mts +0 -7
@@ -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-BHg1VhqN.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,
@@ -73,27 +94,161 @@ function createVisualGraph(config) {
73
94
  }))
74
95
  };
75
96
  }
76
- /** Get a node by id, or `undefined` if not found. */
97
+ /**
98
+ * Create a graph by BFS exploration of a transition function.
99
+ * Each unique state becomes a node; each (state, event) -> nextState becomes an edge.
100
+ *
101
+ * - Node IDs are determined by `serializeState` (default: `JSON.stringify`).
102
+ * - Edge IDs use the format `sourceId|serializedEvent|targetId` for uniqueness
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
+ * ```
123
+ */
124
+ function createGraphFromTransition(transition, options) {
125
+ const serializeState = options.serializeState ?? JSON.stringify;
126
+ const serializeEvent = options.serializeEvent ?? JSON.stringify;
127
+ const limit = options.limit ?? Infinity;
128
+ const getEvents = typeof options.events === "function" ? options.events : () => options.events;
129
+ const nodes = [];
130
+ const edges = [];
131
+ const visited = /* @__PURE__ */ new Set();
132
+ const edgeSet = /* @__PURE__ */ new Set();
133
+ const queue = [options.initialState];
134
+ const initialStateId = serializeState(options.initialState);
135
+ visited.add(initialStateId);
136
+ nodes.push({
137
+ id: initialStateId,
138
+ label: initialStateId,
139
+ data: options.initialState
140
+ });
141
+ let iterations = 0;
142
+ while (queue.length > 0) {
143
+ const state = queue.shift();
144
+ const stateId = serializeState(state);
145
+ if (++iterations > limit) throw new Error("Traversal limit exceeded");
146
+ if (options.stopWhen?.(state)) continue;
147
+ const events = getEvents(state);
148
+ for (const event of events) {
149
+ const nextState = transition(state, event);
150
+ const nextStateId = serializeState(nextState);
151
+ const eventStr = serializeEvent(event);
152
+ if (!visited.has(nextStateId)) {
153
+ visited.add(nextStateId);
154
+ nodes.push({
155
+ id: nextStateId,
156
+ label: nextStateId,
157
+ data: nextState
158
+ });
159
+ queue.push(nextState);
160
+ }
161
+ const edgeKey = `${stateId}|${eventStr}|${nextStateId}`;
162
+ if (!edgeSet.has(edgeKey)) {
163
+ edgeSet.add(edgeKey);
164
+ edges.push({
165
+ id: edgeKey,
166
+ sourceId: stateId,
167
+ targetId: nextStateId,
168
+ label: eventStr,
169
+ data: event
170
+ });
171
+ }
172
+ }
173
+ }
174
+ return createGraph({
175
+ id: options.id ?? "",
176
+ type: "directed",
177
+ initialNodeId: initialStateId,
178
+ nodes,
179
+ edges
180
+ });
181
+ }
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
+ */
77
192
  function getNode(graph, id) {
78
193
  const arrayIdx = getIndex(graph).nodeById.get(id);
79
194
  return arrayIdx !== void 0 ? graph.nodes[arrayIdx] : void 0;
80
195
  }
81
- /** 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
+ */
82
209
  function getEdge(graph, id) {
83
210
  const arrayIdx = getIndex(graph).edgeById.get(id);
84
211
  return arrayIdx !== void 0 ? graph.edges[arrayIdx] : void 0;
85
212
  }
86
- /** 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
+ */
87
223
  function hasNode(graph, id) {
88
224
  return getIndex(graph).nodeById.has(id);
89
225
  }
90
- /** 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
+ */
91
239
  function hasEdge(graph, id) {
92
240
  return getIndex(graph).edgeById.has(id);
93
241
  }
94
242
  /**
95
243
  * **Mutable.** Add a node to the graph. Mutates `graph.nodes` in place.
96
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
+ * ```
97
252
  */
98
253
  function addNode(graph, config) {
99
254
  const idx = getIndex(graph);
@@ -106,6 +261,13 @@ function addNode(graph, config) {
106
261
  /**
107
262
  * **Mutable.** Add an edge to the graph. Mutates `graph.edges` in place.
108
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
+ * ```
109
271
  */
110
272
  function addEdge(graph, config) {
111
273
  const idx = getIndex(graph);
@@ -122,6 +284,16 @@ function addEdge(graph, config) {
122
284
  *
123
285
  * By default, children are deleted recursively.
124
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
+ * ```
125
297
  */
126
298
  function deleteNode(graph, id, opts) {
127
299
  const node = getNode(graph, id);
@@ -139,6 +311,16 @@ function deleteNode(graph, id, opts) {
139
311
  }
140
312
  /**
141
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
+ * ```
142
324
  */
143
325
  function deleteEdge(graph, id) {
144
326
  if (!hasEdge(graph, id)) throw new Error(`Edge "${id}" does not exist`);
@@ -148,6 +330,13 @@ function deleteEdge(graph, id) {
148
330
  /**
149
331
  * **Mutable.** Update a node in place.
150
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
+ * ```
151
340
  */
152
341
  function updateNode(graph, id, update) {
153
342
  const idx = getIndex(graph);
@@ -172,6 +361,16 @@ function updateNode(graph, id, update) {
172
361
  /**
173
362
  * **Mutable.** Update an edge in place.
174
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
+ * ```
175
374
  */
176
375
  function updateEdge(graph, id, update) {
177
376
  const idx = getIndex(graph);
@@ -196,6 +395,16 @@ function updateEdge(graph, id, update) {
196
395
  /**
197
396
  * **Mutable.** Add multiple nodes and edges to the graph.
198
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
+ * ```
199
408
  */
200
409
  function addEntities(graph, entities) {
201
410
  for (const nodeConfig of entities.nodes ?? []) addNode(graph, nodeConfig);
@@ -204,6 +413,16 @@ function addEntities(graph, entities) {
204
413
  /**
205
414
  * **Mutable.** Delete entities by id(s). Automatically detects whether each id
206
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
+ * ```
207
426
  */
208
427
  function deleteEntities(graph, ids, opts) {
209
428
  const idArray = Array.isArray(ids) ? ids : [ids];
@@ -213,6 +432,18 @@ function deleteEntities(graph, ids, opts) {
213
432
  /**
214
433
  * **Mutable.** Update multiple nodes and edges in place.
215
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
+ * ```
216
447
  */
217
448
  function updateEntities(graph, updates) {
218
449
  for (const nodeUpdate of updates.nodes ?? []) {
@@ -227,13 +458,33 @@ function updateEntities(graph, updates) {
227
458
  /**
228
459
  * OOP wrapper around a plain `Graph` object.
229
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
+ * ```
230
472
  */
231
473
  var GraphInstance = class GraphInstance {
232
474
  graph;
233
475
  constructor(config) {
234
476
  this.graph = createGraph(config);
235
477
  }
236
- /** 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
+ */
237
488
  static from(graph) {
238
489
  const instance = Object.create(GraphInstance.prototype);
239
490
  instance.graph = graph;
@@ -311,6 +562,23 @@ function collectDescendants(graph, id) {
311
562
 
312
563
  //#endregion
313
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
+ */
314
582
  function* bfs(graph, startId) {
315
583
  const idx = getIndex(graph);
316
584
  const visited = /* @__PURE__ */ new Set();
@@ -327,6 +595,23 @@ function* bfs(graph, startId) {
327
595
  }
328
596
  }
329
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
+ */
330
615
  function* dfs(graph, startId) {
331
616
  const idx = getIndex(graph);
332
617
  const visited = /* @__PURE__ */ new Set();
@@ -358,6 +643,21 @@ function getSuccessorIds(graph, nodeId) {
358
643
  const idx = getIndex(graph);
359
644
  return (idx.outEdges.get(nodeId) ?? []).map((eid) => graph.edges[idx.edgeById.get(eid)].targetId);
360
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
+ */
361
661
  function isAcyclic(graph) {
362
662
  if (graph.type === "undirected") return isAcyclicUndirected(graph);
363
663
  const WHITE = 0, GRAY = 1, BLACK = 2;
@@ -402,6 +702,23 @@ function isAcyclicUndirected(graph) {
402
702
  for (const n of graph.nodes) if (!visited.has(n.id) && hasCycle(n.id, null)) return false;
403
703
  return true;
404
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
+ */
405
722
  function getConnectedComponents(graph) {
406
723
  const idx = getIndex(graph);
407
724
  const visited = /* @__PURE__ */ new Set();
@@ -438,6 +755,25 @@ function getConnectedComponents(graph) {
438
755
  }
439
756
  return components;
440
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
+ */
441
777
  function getTopologicalSort(graph) {
442
778
  const idx = getIndex(graph);
443
779
  const inDeg = /* @__PURE__ */ new Map();
@@ -462,16 +798,65 @@ function getTopologicalSort(graph) {
462
798
  if (result.length !== graph.nodes.length) return null;
463
799
  return result;
464
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
+ */
465
817
  function hasPath(graph, sourceId, targetId) {
466
818
  return getShortestPaths(graph, {
467
819
  from: sourceId,
468
820
  to: targetId
469
821
  }).length > 0;
470
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
+ */
471
838
  function isConnected(graph) {
472
839
  if (graph.nodes.length === 0) return true;
473
840
  return getConnectedComponents(graph).length <= 1;
474
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
+ */
475
860
  function isTree(graph) {
476
861
  return isConnected(graph) && isAcyclic(graph);
477
862
  }
@@ -605,6 +990,24 @@ function* reconstructPaths(graph, prev, sourceNode, targetId) {
605
990
  /**
606
991
  * Lazily yields all shortest paths from a source node.
607
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
+ * ```
608
1011
  */
609
1012
  function* genShortestPaths(graph, opts) {
610
1013
  const idx = getIndex(graph);
@@ -615,25 +1018,101 @@ function* genShortestPaths(graph, opts) {
615
1018
  const sourceNode = sourceNi !== void 0 ? graph.nodes[sourceNi] : graph.nodes.find((n) => n.id === sourceId);
616
1019
  for (const targetId of targets) yield* reconstructPaths(graph, prev, sourceNode, targetId);
617
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
+ */
618
1042
  function getShortestPaths(graph, opts) {
619
1043
  return [...genShortestPaths(graph, opts)];
620
1044
  }
621
1045
  /**
622
- * 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
+ * ```
623
1064
  */
624
1065
  function getShortestPath(graph, opts) {
625
1066
  for (const path of genShortestPaths(graph, opts)) return path;
626
1067
  }
627
1068
  /**
628
- * Returns all simple (acyclic) paths from a source node.
629
- * 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
+ * ```
630
1089
  */
631
1090
  function getSimplePaths(graph, opts) {
632
1091
  return [...genSimplePaths(graph, opts)];
633
1092
  }
634
1093
  /**
635
- * Lazily yields all simple (acyclic) paths from a source node.
1094
+ * Lazily yields all simple (acyclic) paths from a source node via DFS backtracking.
636
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
+ * ```
637
1116
  */
638
1117
  function* genSimplePaths(graph, opts) {
639
1118
  const idx = getIndex(graph);
@@ -675,11 +1154,49 @@ function* genSimplePaths(graph, opts) {
675
1154
  yield* found;
676
1155
  }
677
1156
  /**
678
- * 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
+ * ```
679
1175
  */
680
1176
  function getSimplePath(graph, opts) {
681
1177
  for (const path of genSimplePaths(graph, opts)) return path;
682
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
+ */
683
1200
  function getStronglyConnectedComponents(graph) {
684
1201
  const idx = getIndex(graph);
685
1202
  let indexCounter = 0;
@@ -718,12 +1235,49 @@ function getStronglyConnectedComponents(graph) {
718
1235
  for (const n of graph.nodes) if (!nodeIndex.has(n.id)) strongconnect(n.id);
719
1236
  return result;
720
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
+ */
721
1258
  function getCycles(graph) {
722
1259
  return [...genCycles(graph)];
723
1260
  }
724
1261
  /**
725
- * Lazily yields cycles one at a time.
1262
+ * Lazily yields elementary cycles one at a time.
726
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
+ * ```
727
1281
  */
728
1282
  function* genCycles(graph) {
729
1283
  if (graph.type === "undirected") yield* genCyclesUndirected(graph);
@@ -846,6 +1400,23 @@ function getNeighborEdgesAll(graph, nodeId) {
846
1400
  /**
847
1401
  * Returns a single canonical preorder (DFS visit-order) sequence.
848
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
+ * ```
849
1420
  */
850
1421
  function getPreorder(graph, opts) {
851
1422
  const idx = getIndex(graph);
@@ -872,6 +1443,23 @@ function getPreorder(graph, opts) {
872
1443
  /**
873
1444
  * Returns a single canonical postorder (DFS finish-order) sequence.
874
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
+ * ```
875
1463
  */
876
1464
  function getPostorder(graph, opts) {
877
1465
  const idx = getIndex(graph);
@@ -894,11 +1482,49 @@ function getPostorder(graph, opts) {
894
1482
  }
895
1483
  return result;
896
1484
  }
897
- /** 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
+ */
898
1505
  function getPreorders(graph, opts) {
899
1506
  return [...genPreorders(graph, opts)];
900
1507
  }
901
- /** 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
+ */
902
1528
  function getPostorders(graph, opts) {
903
1529
  return [...genPostorders(graph, opts)];
904
1530
  }
@@ -906,6 +1532,25 @@ function getPostorders(graph, opts) {
906
1532
  * Lazily yields all possible preorder (DFS visit-order) sequences.
907
1533
  * Different neighbor exploration orders yield different sequences.
908
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
+ * ```
909
1554
  */
910
1555
  function* genPreorders(graph, opts) {
911
1556
  const idx = getIndex(graph);
@@ -951,6 +1596,25 @@ function* genPreorders(graph, opts) {
951
1596
  * Lazily yields all possible postorder (DFS finish-order) sequences.
952
1597
  * Different neighbor exploration orders yield different sequences.
953
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
+ * ```
954
1618
  */
955
1619
  function* genPostorders(graph, opts) {
956
1620
  const idx = getIndex(graph);
@@ -995,6 +1659,26 @@ function* genPostorders(graph, opts) {
995
1659
  * Returns a minimum spanning tree of the graph.
996
1660
  * Only meaningful for connected undirected graphs (or the component reachable
997
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
+ * ```
998
1682
  */
999
1683
  function getMinimumSpanningTree(graph, opts) {
1000
1684
  const algorithm = opts?.algorithm ?? "prim";
@@ -1091,7 +1775,23 @@ function kruskalMST(graph, getWeight) {
1091
1775
  /**
1092
1776
  * Returns shortest paths between all pairs of nodes.
1093
1777
  * Algorithm 'dijkstra' (default): runs getShortestPaths per source node.
1094
- * 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
+ * ```
1095
1795
  */
1096
1796
  function getAllPairsShortestPaths(graph, opts) {
1097
1797
  if ((opts?.algorithm ?? "dijkstra") === "floyd-warshall") return floydWarshallAllPaths(graph, opts?.getWeight);
@@ -1192,6 +1892,40 @@ function fwReconstruct(graph, prev, idxOf, nodeIds, sourceNode, sourceIdx, targe
1192
1892
  }
1193
1893
  return results;
1194
1894
  }
1895
+ /**
1896
+ * Joins two paths end-to-end. The last node of the head path must equal
1897
+ * the source of the tail path (the overlap node).
1898
+ *
1899
+ * Steps are concatenated: head.steps ++ tail.steps (tail already starts
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
+ * ```
1920
+ */
1921
+ function joinPaths(headPath, tailPath) {
1922
+ const headEnd = headPath.steps.length > 0 ? headPath.steps[headPath.steps.length - 1].node : headPath.source;
1923
+ if (headEnd.id !== tailPath.source.id) throw new Error(`Paths cannot be joined: head path ends at "${headEnd.id}" but tail path starts at "${tailPath.source.id}"`);
1924
+ return {
1925
+ source: headPath.source,
1926
+ steps: [...headPath.steps, ...tailPath.steps]
1927
+ };
1928
+ }
1195
1929
 
1196
1930
  //#endregion
1197
- export { createGraph as A, updateEntities as B, isAcyclic as C, addEdge as D, GraphInstance as E, getEdge as F, getNode as I, hasEdge as L, deleteEdge as M, deleteEntities as N, addEntities as O, deleteNode as P, hasNode as R, hasPath as S, isTree as T, updateNode as V, getShortestPaths as _, genPreorders as a, getStronglyConnectedComponents as b, getAllPairsShortestPaths as c, getMinimumSpanningTree as d, getPostorder as f, getShortestPath as g, getPreorders as h, genPostorders as i, createVisualGraph as j, addNode as k, getConnectedComponents as l, getPreorder as m, dfs as n, genShortestPaths as o, getPostorders as p, genCycles as r, genSimplePaths as s, bfs as t, getCycles as u, getSimplePath as v, isConnected as w, getTopologicalSort as x, getSimplePaths as y, updateEdge as z };
1931
+ export { addNode as A, hasNode as B, isAcyclic as C, GraphInstance as D, joinPaths as E, deleteEntities as F, updateEntities as H, deleteNode as I, getEdge as L, createGraphFromTransition as M, createVisualGraph as N, addEdge as O, deleteEdge as P, getNode as R, hasPath as S, isTree as T, updateNode as U, updateEdge as V, getShortestPaths as _, genPreorders as a, getStronglyConnectedComponents as b, getAllPairsShortestPaths as c, getMinimumSpanningTree as d, getPostorder as f, getShortestPath as g, getPreorders as h, genPostorders as i, createGraph as j, addEntities as k, getConnectedComponents as l, getPreorder as m, dfs as n, genShortestPaths as o, getPostorders as p, genCycles as r, genSimplePaths as s, bfs as t, getCycles as u, getSimplePath as v, isConnected as w, getTopologicalSort as x, getSimplePaths as y, hasEdge as z };