@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
package/dist/queries.mjs CHANGED
@@ -1,6 +1,22 @@
1
- import { t as getIndex } from "./indexing-BHg1VhqN.mjs";
1
+ import { t as getIndex } from "./indexing-eNDrXdDA.mjs";
2
2
 
3
3
  //#region src/queries.ts
4
+ /**
5
+ * Returns all edges (incoming + outgoing) connected to a node.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * const graph = createGraph({
10
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
11
+ * edges: [
12
+ * { id: 'e1', sourceId: 'a', targetId: 'b' },
13
+ * { id: 'e2', sourceId: 'c', targetId: 'b' },
14
+ * ],
15
+ * });
16
+ * getEdgesOf(graph, 'b');
17
+ * // => [edge e1, edge e2]
18
+ * ```
19
+ */
4
20
  function getEdgesOf(graph, nodeId) {
5
21
  const idx = getIndex(graph);
6
22
  const outIds = idx.outEdges.get(nodeId) ?? [];
@@ -18,14 +34,60 @@ function getEdgesOf(graph, nodeId) {
18
34
  }
19
35
  return result;
20
36
  }
37
+ /**
38
+ * Returns incoming edges to a node.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * const graph = createGraph({
43
+ * nodes: [{ id: 'a' }, { id: 'b' }],
44
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
45
+ * });
46
+ * getInEdges(graph, 'b');
47
+ * // => [edge e1]
48
+ * getInEdges(graph, 'a');
49
+ * // => []
50
+ * ```
51
+ */
21
52
  function getInEdges(graph, nodeId) {
22
53
  const idx = getIndex(graph);
23
54
  return (idx.inEdges.get(nodeId) ?? []).map((eid) => graph.edges[idx.edgeById.get(eid)]);
24
55
  }
56
+ /**
57
+ * Returns outgoing edges from a node.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const graph = createGraph({
62
+ * nodes: [{ id: 'a' }, { id: 'b' }],
63
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
64
+ * });
65
+ * getOutEdges(graph, 'a');
66
+ * // => [edge e1]
67
+ * getOutEdges(graph, 'b');
68
+ * // => []
69
+ * ```
70
+ */
25
71
  function getOutEdges(graph, nodeId) {
26
72
  const idx = getIndex(graph);
27
73
  return (idx.outEdges.get(nodeId) ?? []).map((eid) => graph.edges[idx.edgeById.get(eid)]);
28
74
  }
75
+ /**
76
+ * Returns the edge from `sourceId` to `targetId`, or `undefined` if none exists.
77
+ * For undirected graphs, checks both directions.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * const graph = createGraph({
82
+ * nodes: [{ id: 'a' }, { id: 'b' }],
83
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
84
+ * });
85
+ * getEdgeBetween(graph, 'a', 'b');
86
+ * // => edge e1
87
+ * getEdgeBetween(graph, 'b', 'a');
88
+ * // => undefined (directed graph)
89
+ * ```
90
+ */
29
91
  function getEdgeBetween(graph, sourceId, targetId) {
30
92
  const idx = getIndex(graph);
31
93
  const outIds = idx.outEdges.get(sourceId) ?? [];
@@ -43,6 +105,22 @@ function getEdgeBetween(graph, sourceId, targetId) {
43
105
  }
44
106
  }
45
107
  }
108
+ /**
109
+ * Returns direct successor nodes (targets of outgoing edges).
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * const graph = createGraph({
114
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
115
+ * edges: [
116
+ * { id: 'e1', sourceId: 'a', targetId: 'b' },
117
+ * { id: 'e2', sourceId: 'a', targetId: 'c' },
118
+ * ],
119
+ * });
120
+ * getSuccessors(graph, 'a');
121
+ * // => [node b, node c]
122
+ * ```
123
+ */
46
124
  function getSuccessors(graph, nodeId) {
47
125
  const idx = getIndex(graph);
48
126
  const edgeIds = idx.outEdges.get(nodeId) ?? [];
@@ -58,6 +136,22 @@ function getSuccessors(graph, nodeId) {
58
136
  }
59
137
  return result;
60
138
  }
139
+ /**
140
+ * Returns direct predecessor nodes (sources of incoming edges).
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * const graph = createGraph({
145
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
146
+ * edges: [
147
+ * { id: 'e1', sourceId: 'a', targetId: 'c' },
148
+ * { id: 'e2', sourceId: 'b', targetId: 'c' },
149
+ * ],
150
+ * });
151
+ * getPredecessors(graph, 'c');
152
+ * // => [node a, node b]
153
+ * ```
154
+ */
61
155
  function getPredecessors(graph, nodeId) {
62
156
  const idx = getIndex(graph);
63
157
  const edgeIds = idx.inEdges.get(nodeId) ?? [];
@@ -73,6 +167,22 @@ function getPredecessors(graph, nodeId) {
73
167
  }
74
168
  return result;
75
169
  }
170
+ /**
171
+ * Returns all neighbor nodes (successors + predecessors).
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * const graph = createGraph({
176
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
177
+ * edges: [
178
+ * { id: 'e1', sourceId: 'a', targetId: 'b' },
179
+ * { id: 'e2', sourceId: 'c', targetId: 'b' },
180
+ * ],
181
+ * });
182
+ * getNeighbors(graph, 'b');
183
+ * // => [node a, node c]
184
+ * ```
185
+ */
76
186
  function getNeighbors(graph, nodeId) {
77
187
  const idx = getIndex(graph);
78
188
  const ids = /* @__PURE__ */ new Set();
@@ -80,6 +190,23 @@ function getNeighbors(graph, nodeId) {
80
190
  for (const eid of idx.inEdges.get(nodeId) ?? []) ids.add(graph.edges[idx.edgeById.get(eid)].sourceId);
81
191
  return [...ids].map((id) => graph.nodes[idx.nodeById.get(id)]).filter(Boolean);
82
192
  }
193
+ /**
194
+ * Returns the total degree of a node (inDegree + outDegree).
195
+ * For undirected graphs, each edge is counted once.
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * const graph = createGraph({
200
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
201
+ * edges: [
202
+ * { id: 'e1', sourceId: 'a', targetId: 'b' },
203
+ * { id: 'e2', sourceId: 'c', targetId: 'b' },
204
+ * ],
205
+ * });
206
+ * getDegree(graph, 'b'); // => 2
207
+ * getDegree(graph, 'a'); // => 1
208
+ * ```
209
+ */
83
210
  function getDegree(graph, nodeId) {
84
211
  const idx = getIndex(graph);
85
212
  if (graph.type === "undirected") {
@@ -89,16 +216,78 @@ function getDegree(graph, nodeId) {
89
216
  }
90
217
  return (idx.inEdges.get(nodeId)?.length ?? 0) + (idx.outEdges.get(nodeId)?.length ?? 0);
91
218
  }
219
+ /**
220
+ * Returns the in-degree of a node (number of incoming edges).
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * const graph = createGraph({
225
+ * nodes: [{ id: 'a' }, { id: 'b' }],
226
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
227
+ * });
228
+ * getInDegree(graph, 'b'); // => 1
229
+ * getInDegree(graph, 'a'); // => 0
230
+ * ```
231
+ */
92
232
  function getInDegree(graph, nodeId) {
93
233
  return getIndex(graph).inEdges.get(nodeId)?.length ?? 0;
94
234
  }
235
+ /**
236
+ * Returns the out-degree of a node (number of outgoing edges).
237
+ *
238
+ * @example
239
+ * ```ts
240
+ * const graph = createGraph({
241
+ * nodes: [{ id: 'a' }, { id: 'b' }],
242
+ * edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
243
+ * });
244
+ * getOutDegree(graph, 'a'); // => 1
245
+ * getOutDegree(graph, 'b'); // => 0
246
+ * ```
247
+ */
95
248
  function getOutDegree(graph, nodeId) {
96
249
  return getIndex(graph).outEdges.get(nodeId)?.length ?? 0;
97
250
  }
251
+ /**
252
+ * Returns direct children of a node in the hierarchy.
253
+ * Pass `null` to get root-level nodes.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * const graph = createGraph({
258
+ * nodes: [
259
+ * { id: 'parent' },
260
+ * { id: 'child1', parentId: 'parent' },
261
+ * { id: 'child2', parentId: 'parent' },
262
+ * ],
263
+ * });
264
+ * getChildren(graph, 'parent');
265
+ * // => [node child1, node child2]
266
+ * getChildren(graph, null);
267
+ * // => [node parent]
268
+ * ```
269
+ */
98
270
  function getChildren(graph, nodeId) {
99
271
  const idx = getIndex(graph);
100
272
  return (idx.childNodes.get(nodeId) ?? []).map((id) => graph.nodes[idx.nodeById.get(id)]).filter(Boolean);
101
273
  }
274
+ /**
275
+ * Returns the parent node in the hierarchy, or `undefined` if root-level.
276
+ *
277
+ * @example
278
+ * ```ts
279
+ * const graph = createGraph({
280
+ * nodes: [
281
+ * { id: 'parent' },
282
+ * { id: 'child', parentId: 'parent' },
283
+ * ],
284
+ * });
285
+ * getParent(graph, 'child');
286
+ * // => node parent
287
+ * getParent(graph, 'parent');
288
+ * // => undefined
289
+ * ```
290
+ */
102
291
  function getParent(graph, nodeId) {
103
292
  const idx = getIndex(graph);
104
293
  const ni = idx.nodeById.get(nodeId);
@@ -108,6 +297,22 @@ function getParent(graph, nodeId) {
108
297
  const pi = idx.nodeById.get(node.parentId);
109
298
  return pi !== void 0 ? graph.nodes[pi] : void 0;
110
299
  }
300
+ /**
301
+ * Returns all ancestors from the node up to the root (nearest parent first).
302
+ *
303
+ * @example
304
+ * ```ts
305
+ * const graph = createGraph({
306
+ * nodes: [
307
+ * { id: 'root' },
308
+ * { id: 'mid', parentId: 'root' },
309
+ * { id: 'leaf', parentId: 'mid' },
310
+ * ],
311
+ * });
312
+ * getAncestors(graph, 'leaf');
313
+ * // => [node mid, node root]
314
+ * ```
315
+ */
111
316
  function getAncestors(graph, nodeId) {
112
317
  const idx = getIndex(graph);
113
318
  const result = [];
@@ -123,6 +328,22 @@ function getAncestors(graph, nodeId) {
123
328
  }
124
329
  return result;
125
330
  }
331
+ /**
332
+ * Returns all descendants recursively (depth-first).
333
+ *
334
+ * @example
335
+ * ```ts
336
+ * const graph = createGraph({
337
+ * nodes: [
338
+ * { id: 'root' },
339
+ * { id: 'child', parentId: 'root' },
340
+ * { id: 'grandchild', parentId: 'child' },
341
+ * ],
342
+ * });
343
+ * getDescendants(graph, 'root');
344
+ * // => [node child, node grandchild]
345
+ * ```
346
+ */
126
347
  function getDescendants(graph, nodeId) {
127
348
  const idx = getIndex(graph);
128
349
  const result = [];
@@ -139,19 +360,80 @@ function getDescendants(graph, nodeId) {
139
360
  collect(nodeId);
140
361
  return result;
141
362
  }
363
+ /**
364
+ * Returns all root nodes (nodes with no parent, i.e. `parentId === null`).
365
+ *
366
+ * @example
367
+ * ```ts
368
+ * const graph = createGraph({
369
+ * nodes: [
370
+ * { id: 'root1' },
371
+ * { id: 'root2' },
372
+ * { id: 'child', parentId: 'root1' },
373
+ * ],
374
+ * });
375
+ * getRoots(graph);
376
+ * // => [node root1, node root2]
377
+ * ```
378
+ */
142
379
  function getRoots(graph) {
143
380
  const idx = getIndex(graph);
144
381
  return idx.childNodes.get(null)?.map((id) => graph.nodes[idx.nodeById.get(id)]).filter(Boolean) ?? [];
145
382
  }
146
- /** Whether a node has children (is a compound/group node). */
383
+ /**
384
+ * Whether a node has children (is a compound/group node).
385
+ *
386
+ * @example
387
+ * ```ts
388
+ * const graph = createGraph({
389
+ * nodes: [
390
+ * { id: 'parent' },
391
+ * { id: 'child', parentId: 'parent' },
392
+ * ],
393
+ * });
394
+ * isCompound(graph, 'parent'); // => true
395
+ * isCompound(graph, 'child'); // => false
396
+ * ```
397
+ */
147
398
  function isCompound(graph, nodeId) {
148
399
  return (getIndex(graph).childNodes.get(nodeId) ?? []).length > 0;
149
400
  }
150
- /** Whether a node has no children (is a leaf/atomic node). */
401
+ /**
402
+ * Whether a node has no children (is a leaf/atomic node).
403
+ *
404
+ * @example
405
+ * ```ts
406
+ * const graph = createGraph({
407
+ * nodes: [
408
+ * { id: 'parent' },
409
+ * { id: 'child', parentId: 'parent' },
410
+ * ],
411
+ * });
412
+ * isLeaf(graph, 'child'); // => true
413
+ * isLeaf(graph, 'parent'); // => false
414
+ * ```
415
+ */
151
416
  function isLeaf(graph, nodeId) {
152
417
  return !isCompound(graph, nodeId);
153
418
  }
154
- /** Depth of a node in the hierarchy (root = 0). */
419
+ /**
420
+ * Depth of a node in the hierarchy (root = 0).
421
+ * Returns -1 if the node is not found.
422
+ *
423
+ * @example
424
+ * ```ts
425
+ * const graph = createGraph({
426
+ * nodes: [
427
+ * { id: 'root' },
428
+ * { id: 'child', parentId: 'root' },
429
+ * { id: 'grandchild', parentId: 'child' },
430
+ * ],
431
+ * });
432
+ * getDepth(graph, 'root'); // => 0
433
+ * getDepth(graph, 'child'); // => 1
434
+ * getDepth(graph, 'grandchild'); // => 2
435
+ * ```
436
+ */
155
437
  function getDepth(graph, nodeId) {
156
438
  const idx = getIndex(graph);
157
439
  let d = 0;
@@ -166,7 +448,23 @@ function getDepth(graph, nodeId) {
166
448
  }
167
449
  return d;
168
450
  }
169
- /** Sibling nodes (same parentId, excluding the node itself). */
451
+ /**
452
+ * Sibling nodes (same parentId, excluding the node itself).
453
+ *
454
+ * @example
455
+ * ```ts
456
+ * const graph = createGraph({
457
+ * nodes: [
458
+ * { id: 'parent' },
459
+ * { id: 'a', parentId: 'parent' },
460
+ * { id: 'b', parentId: 'parent' },
461
+ * { id: 'c', parentId: 'parent' },
462
+ * ],
463
+ * });
464
+ * getSiblings(graph, 'a');
465
+ * // => [node b, node c]
466
+ * ```
467
+ */
170
468
  function getSiblings(graph, nodeId) {
171
469
  const idx = getIndex(graph);
172
470
  const ni = idx.nodeById.get(nodeId);
@@ -175,8 +473,24 @@ function getSiblings(graph, nodeId) {
175
473
  return (idx.childNodes.get(node.parentId) ?? []).filter((id) => id !== nodeId).map((id) => graph.nodes[idx.nodeById.get(id)]).filter(Boolean);
176
474
  }
177
475
  /**
178
- * Least Common Ancestor deepest proper ancestor of all given nodes.
476
+ * Least Common Ancestor -- deepest proper ancestor of all given nodes.
179
477
  * A proper ancestor excludes the input nodes themselves.
478
+ *
479
+ * @example
480
+ * ```ts
481
+ * const graph = createGraph({
482
+ * nodes: [
483
+ * { id: 'root' },
484
+ * { id: 'a', parentId: 'root' },
485
+ * { id: 'b', parentId: 'root' },
486
+ * { id: 'a1', parentId: 'a' },
487
+ * ],
488
+ * });
489
+ * getLCA(graph, 'a1', 'b');
490
+ * // => node root
491
+ * getLCA(graph, 'a', 'b');
492
+ * // => node root
493
+ * ```
180
494
  */
181
495
  function getLCA(graph, ...nodeIds) {
182
496
  if (nodeIds.length === 0) return void 0;
@@ -206,16 +520,165 @@ function getLCA(graph, ...nodeIds) {
206
520
  const ni = idx.nodeById.get(lcaId);
207
521
  return ni !== void 0 ? graph.nodes[ni] : void 0;
208
522
  }
209
- /** Nodes with no incoming edges (inDegree 0). */
523
+ /**
524
+ * Returns a map of nodeId → shortest-path distance for all sibling nodes
525
+ * (same parentId). Distance is measured from the parent's `initialNodeId`
526
+ * (or `graph.initialNodeId` for root-level nodes).
527
+ *
528
+ * Only follows edges between siblings. Unreachable siblings are omitted.
529
+ *
530
+ * @example Root-level nodes (uses `graph.initialNodeId`):
531
+ * ```ts
532
+ * const graph = createGraph({
533
+ * initialNodeId: 'a',
534
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
535
+ * edges: [
536
+ * { id: 'e1', sourceId: 'a', targetId: 'b' },
537
+ * { id: 'e2', sourceId: 'b', targetId: 'c' },
538
+ * ],
539
+ * });
540
+ * getRelativeDistanceMap(graph, null);
541
+ * // => { a: 0, b: 1, c: 2 }
542
+ * ```
543
+ *
544
+ * @example Nested nodes (uses parent's `initialNodeId`):
545
+ * ```ts
546
+ * const graph = createGraph({
547
+ * nodes: [
548
+ * { id: 'parent', initialNodeId: 's1' },
549
+ * { id: 's1', parentId: 'parent' },
550
+ * { id: 's2', parentId: 'parent' },
551
+ * { id: 's3', parentId: 'parent' },
552
+ * ],
553
+ * edges: [
554
+ * { id: 'e1', sourceId: 's1', targetId: 's2' },
555
+ * { id: 'e2', sourceId: 's2', targetId: 's3' },
556
+ * ],
557
+ * });
558
+ * getRelativeDistanceMap(graph, 'parent');
559
+ * // => { s1: 0, s2: 1, s3: 2 }
560
+ * ```
561
+ */
562
+ function getRelativeDistanceMap(graph, parentId) {
563
+ const idx = getIndex(graph);
564
+ let sourceId = null;
565
+ if (parentId !== null) {
566
+ const pi = idx.nodeById.get(parentId);
567
+ if (pi !== void 0) sourceId = graph.nodes[pi].initialNodeId;
568
+ } else sourceId = graph.initialNodeId;
569
+ if (!sourceId) return {};
570
+ const siblingSet = new Set(idx.childNodes.get(parentId) ?? []);
571
+ if (!siblingSet.has(sourceId)) return {};
572
+ const dist = /* @__PURE__ */ new Map();
573
+ dist.set(sourceId, 0);
574
+ const queue = [sourceId];
575
+ while (queue.length > 0) {
576
+ const id = queue.shift();
577
+ const d = dist.get(id);
578
+ for (const eid of idx.outEdges.get(id) ?? []) {
579
+ const ai = idx.edgeById.get(eid);
580
+ if (ai === void 0) continue;
581
+ const neighborId = graph.edges[ai].targetId;
582
+ if (siblingSet.has(neighborId) && !dist.has(neighborId)) {
583
+ dist.set(neighborId, d + 1);
584
+ queue.push(neighborId);
585
+ }
586
+ }
587
+ if (graph.type === "undirected") for (const eid of idx.inEdges.get(id) ?? []) {
588
+ const ai = idx.edgeById.get(eid);
589
+ if (ai === void 0) continue;
590
+ const neighborId = graph.edges[ai].sourceId;
591
+ if (siblingSet.has(neighborId) && !dist.has(neighborId)) {
592
+ dist.set(neighborId, d + 1);
593
+ queue.push(neighborId);
594
+ }
595
+ }
596
+ }
597
+ const result = {};
598
+ for (const [id, d] of dist) result[id] = d;
599
+ return result;
600
+ }
601
+ /**
602
+ * Returns the shortest-path distance of a node from its parent's initial node.
603
+ * Automatically scopes to the node's sibling group (same `parentId`).
604
+ *
605
+ * Returns `undefined` if the node is not found or unreachable.
606
+ *
607
+ * @example
608
+ * ```ts
609
+ * const graph = createGraph({
610
+ * initialNodeId: 'a',
611
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
612
+ * edges: [
613
+ * { id: 'e1', sourceId: 'a', targetId: 'b' },
614
+ * { id: 'e2', sourceId: 'b', targetId: 'c' },
615
+ * ],
616
+ * });
617
+ * getRelativeDistance(graph, 'a'); // => 0
618
+ * getRelativeDistance(graph, 'b'); // => 1
619
+ * getRelativeDistance(graph, 'c'); // => 2
620
+ * ```
621
+ *
622
+ * @example Nested nodes:
623
+ * ```ts
624
+ * const graph = createGraph({
625
+ * nodes: [
626
+ * { id: 'parent', initialNodeId: 's1' },
627
+ * { id: 's1', parentId: 'parent' },
628
+ * { id: 's2', parentId: 'parent' },
629
+ * ],
630
+ * edges: [{ id: 'e1', sourceId: 's1', targetId: 's2' }],
631
+ * });
632
+ * getRelativeDistance(graph, 's1'); // => 0
633
+ * getRelativeDistance(graph, 's2'); // => 1
634
+ * ```
635
+ */
636
+ function getRelativeDistance(graph, nodeId) {
637
+ const ni = getIndex(graph).nodeById.get(nodeId);
638
+ if (ni === void 0) return void 0;
639
+ const node = graph.nodes[ni];
640
+ return getRelativeDistanceMap(graph, node.parentId)[nodeId];
641
+ }
642
+ /**
643
+ * Nodes with no incoming edges (inDegree 0).
644
+ *
645
+ * @example
646
+ * ```ts
647
+ * const graph = createGraph({
648
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
649
+ * edges: [
650
+ * { id: 'e1', sourceId: 'a', targetId: 'b' },
651
+ * { id: 'e2', sourceId: 'b', targetId: 'c' },
652
+ * ],
653
+ * });
654
+ * getSources(graph);
655
+ * // => [node a]
656
+ * ```
657
+ */
210
658
  function getSources(graph) {
211
659
  const idx = getIndex(graph);
212
660
  return graph.nodes.filter((n) => (idx.inEdges.get(n.id)?.length ?? 0) === 0);
213
661
  }
214
- /** Nodes with no outgoing edges (outDegree 0). */
662
+ /**
663
+ * Nodes with no outgoing edges (outDegree 0).
664
+ *
665
+ * @example
666
+ * ```ts
667
+ * const graph = createGraph({
668
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
669
+ * edges: [
670
+ * { id: 'e1', sourceId: 'a', targetId: 'b' },
671
+ * { id: 'e2', sourceId: 'b', targetId: 'c' },
672
+ * ],
673
+ * });
674
+ * getSinks(graph);
675
+ * // => [node c]
676
+ * ```
677
+ */
215
678
  function getSinks(graph) {
216
679
  const idx = getIndex(graph);
217
680
  return graph.nodes.filter((n) => (idx.outEdges.get(n.id)?.length ?? 0) === 0);
218
681
  }
219
682
 
220
683
  //#endregion
221
- export { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getNeighbors, getOutDegree, getOutEdges, getParent, getPredecessors, getRoots, getSiblings, getSinks, getSources, getSuccessors, isCompound, isLeaf };
684
+ export { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getNeighbors, getOutDegree, getOutEdges, getParent, getPredecessors, getRelativeDistance, getRelativeDistanceMap, getRoots, getSiblings, getSinks, getSources, getSuccessors, isCompound, isLeaf };
@@ -94,7 +94,7 @@ interface GraphEdge<TEdgeData = any> {
94
94
  style?: Record<string, string | number>;
95
95
  }
96
96
  interface VisualNode<TNodeData = any> extends Omit<GraphNode<TNodeData>, keyof EntityRect>, EntityRect {
97
- shape: string;
97
+ shape?: string;
98
98
  }
99
99
  interface VisualEdge<TEdgeData = any> extends Omit<GraphEdge<TEdgeData>, keyof EntityRect>, EntityRect {}
100
100
  interface VisualGraph<TNodeData = any, TEdgeData = any, TGraphData = any> extends Omit<Graph<TNodeData, TEdgeData, TGraphData>, 'nodes' | 'edges'> {
@@ -215,5 +215,40 @@ type GraphPatch<TNodeData = any, TEdgeData = any> = {
215
215
  id: string;
216
216
  description?: string;
217
217
  };
218
+ /**
219
+ * A bidirectional converter between `Graph` and a serialized format.
220
+ *
221
+ * Implement this interface to create a custom format converter.
222
+ *
223
+ * @example
224
+ * ```ts
225
+ * const myConverter: GraphFormatConverter<string> = {
226
+ * to(graph) { return JSON.stringify(graph); },
227
+ * from(input) { return JSON.parse(input); },
228
+ * };
229
+ * ```
230
+ */
231
+ interface GraphFormatConverter<TSerial> {
232
+ /** Convert a Graph to the serialized format. */
233
+ to(graph: Graph): TSerial;
234
+ /** Convert from the serialized format to a Graph. */
235
+ from(input: TSerial): Graph;
236
+ }
237
+ interface TransitionOptions<TState, TEvent> {
238
+ /** Initial state to begin BFS exploration from. */
239
+ initialState: TState;
240
+ /** Events to try at each state. Array or function of state. */
241
+ events: TEvent[] | ((state: TState) => TEvent[]);
242
+ /** Serialize state to unique string for node dedup. Default: JSON.stringify */
243
+ serializeState?: (state: TState) => string;
244
+ /** Serialize event to string for edge labels/IDs. Default: JSON.stringify */
245
+ serializeEvent?: (event: TEvent) => string;
246
+ /** Max BFS iterations before throwing. Default: Infinity */
247
+ limit?: number;
248
+ /** When true, node is kept but outgoing transitions are not explored. */
249
+ stopWhen?: (state: TState) => boolean;
250
+ /** Optional graph ID. */
251
+ id?: string;
252
+ }
218
253
  //#endregion
219
- export { VisualGraph as C, VisualEdge as S, VisualNode as T, NodeChange as _, EntitiesConfig as a, SinglePathOptions as b, Graph as c, GraphEdge as d, GraphNode as f, MSTOptions as g, GraphStep as h, EdgeConfig as i, GraphConfig as l, GraphPath as m, DeleteNodeOptions as n, EntitiesUpdate as o, GraphPatch as p, EdgeChange as r, EntityRect as s, AllPairsShortestPathsOptions as t, GraphDiff as u, NodeConfig as v, VisualGraphConfig as w, TraversalOptions as x, PathOptions as y };
254
+ export { TraversalOptions as C, VisualNode as D, VisualGraphConfig as E, TransitionOptions as S, VisualGraph as T, MSTOptions as _, EntitiesConfig as a, PathOptions as b, Graph as c, GraphEdge as d, GraphFormatConverter as f, GraphStep as g, GraphPath as h, EdgeConfig as i, GraphConfig as l, GraphPatch as m, DeleteNodeOptions as n, EntitiesUpdate as o, GraphNode as p, EdgeChange as r, EntityRect as s, AllPairsShortestPathsOptions as t, GraphDiff as u, NodeChange as v, VisualEdge as w, SinglePathOptions as x, NodeConfig as y };