@statelyai/graph 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { c as Graph, f as GraphFormatConverter } from "../../types-FBZCrmnG.mjs";
1
+ import { m as GraphFormatConverter, u as Graph } from "../../types-DkKjaQW3.mjs";
2
2
 
3
3
  //#region src/formats/mermaid/sequence.d.ts
4
4
  interface SequenceNodeData {
@@ -23,6 +23,11 @@ interface SequenceEdgeData {
23
23
  bidirectional?: boolean;
24
24
  sequenceNumber?: number;
25
25
  }
26
+ /**
27
+ * Control-flow blocks (loop/alt/par/opt/critical/break/rect).
28
+ * Not graph topology — they describe ordering constraints.
29
+ * Stored in `graphData.blocks` for round-trip fidelity.
30
+ */
26
31
  type SequenceBlock = {
27
32
  type: 'loop';
28
33
  label: string;
@@ -1836,7 +1836,10 @@ function fromMermaidClass(input) {
1836
1836
  if (classBlockMatch) {
1837
1837
  currentClass = classBlockMatch[1];
1838
1838
  const node = ensureNode(currentClass);
1839
- if (classBlockMatch[2]) node.data.genericType = classBlockMatch[2];
1839
+ if (classBlockMatch[2]) {
1840
+ node.data ??= {};
1841
+ node.data.genericType = classBlockMatch[2];
1842
+ }
1840
1843
  inClassBlock = true;
1841
1844
  braceDepth = 1;
1842
1845
  continue;
@@ -1852,10 +1855,13 @@ function fromMermaidClass(input) {
1852
1855
  }
1853
1856
  const annotMatch = line.match(/^<<(.+)>>$/);
1854
1857
  if (annotMatch) {
1855
- nodeMap.get(currentClass).data.annotation = annotMatch[1];
1858
+ const node$1 = nodeMap.get(currentClass);
1859
+ node$1.data ??= {};
1860
+ node$1.data.annotation = annotMatch[1];
1856
1861
  continue;
1857
1862
  }
1858
1863
  const node = nodeMap.get(currentClass);
1864
+ node.data ??= {};
1859
1865
  if (!node.data.members) node.data.members = [];
1860
1866
  node.data.members.push(parseMember(line));
1861
1867
  continue;
@@ -1863,12 +1869,16 @@ function fromMermaidClass(input) {
1863
1869
  const classInlineMatch = line.match(/^class\s+(\S+?)(?:~(.+?)~)?\s*$/);
1864
1870
  if (classInlineMatch) {
1865
1871
  const node = ensureNode(classInlineMatch[1]);
1866
- if (classInlineMatch[2]) node.data.genericType = classInlineMatch[2];
1872
+ if (classInlineMatch[2]) {
1873
+ node.data ??= {};
1874
+ node.data.genericType = classInlineMatch[2];
1875
+ }
1867
1876
  continue;
1868
1877
  }
1869
1878
  const annotLineMatch = line.match(/^<<(.+)>>\s+(\S+)\s*$/);
1870
1879
  if (annotLineMatch) {
1871
1880
  const node = ensureNode(annotLineMatch[2]);
1881
+ node.data ??= {};
1872
1882
  node.data.annotation = annotLineMatch[1];
1873
1883
  continue;
1874
1884
  }
@@ -1876,6 +1886,7 @@ function fromMermaidClass(input) {
1876
1886
  if (inlineMemberMatch) {
1877
1887
  if (!parseRelationship(line)) {
1878
1888
  const node = ensureNode(inlineMemberMatch[1]);
1889
+ node.data ??= {};
1879
1890
  if (!node.data.members) node.data.members = [];
1880
1891
  node.data.members.push(parseMember(inlineMemberMatch[2]));
1881
1892
  continue;
@@ -2067,7 +2078,10 @@ function fromMermaidER(input) {
2067
2078
  const attrMatch = line.match(/^(\S+)\s+(\S+)(?:\s+(PK|FK|UK))?(?:\s+"([^"]*)")?$/);
2068
2079
  if (attrMatch) {
2069
2080
  const node = nodeMap.get(currentEntity);
2070
- if (!node.data.attributes) node.data.attributes = [];
2081
+ if (!node.data?.attributes) {
2082
+ if (!node.data) node.data = {};
2083
+ node.data.attributes = [];
2084
+ }
2071
2085
  node.data.attributes.push({
2072
2086
  type: attrMatch[1],
2073
2087
  name: attrMatch[2],
@@ -2289,7 +2303,7 @@ function toMermaidMindmap(graph) {
2289
2303
  const indent = " ".repeat(depth + 1);
2290
2304
  const shape = child.shape;
2291
2305
  const brackets = shape ? SHAPE_TO_BRACKETS[shape] : void 0;
2292
- const label = escapeMermaidLabel(child.label);
2306
+ const label = escapeMermaidLabel(child.label ?? child.id);
2293
2307
  const text = brackets ? `${brackets[0]}${label}${brackets[1]}` : label;
2294
2308
  let extra = "";
2295
2309
  if (child.data?.icon) extra = `\n${" ".repeat(depth + 2)}::icon(${child.data.icon})`;
@@ -2442,7 +2456,7 @@ function fromMermaidBlock(input) {
2442
2456
  const label = spanMatch[2] ?? id;
2443
2457
  const span = parseInt(spanMatch[3], 10);
2444
2458
  const node = ensureNode(id, label);
2445
- node.data.span = span;
2459
+ if (node.data) node.data.span = span;
2446
2460
  continue;
2447
2461
  }
2448
2462
  const parts = line.split(/\s+/);
@@ -1,4 +1,4 @@
1
- import { c as Graph, f as GraphFormatConverter } from "../../types-FBZCrmnG.mjs";
1
+ import { m as GraphFormatConverter, u as Graph } from "../../types-DkKjaQW3.mjs";
2
2
 
3
3
  //#region src/formats/tgf/index.d.ts
4
4
 
@@ -1,4 +1,4 @@
1
- import { D as VisualGraphFormatConverter, T as VisualGraph } from "../../types-FBZCrmnG.mjs";
1
+ import { D as VisualGraph, k as VisualGraphFormatConverter } from "../../types-DkKjaQW3.mjs";
2
2
  import { EdgeBase, NodeBase } from "@xyflow/system";
3
3
 
4
4
  //#region src/formats/xyflow/index.d.ts
package/dist/index.d.mts CHANGED
@@ -1,10 +1,30 @@
1
- import { C as TraversalOptions, E as VisualGraphConfig, O as VisualNode, S as TransitionOptions, T as VisualGraph, _ as MSTOptions, a as EntitiesConfig, b as PathOptions, c as Graph, d as GraphEdge, f as GraphFormatConverter, g as GraphStep, h as GraphPath, i as EdgeConfig, l as GraphConfig, m as GraphPatch, n as DeleteNodeOptions, o as EntitiesUpdate, p as GraphNode, r as EdgeChange, s as EntityRect, t as AllPairsShortestPathsOptions, u as GraphDiff, v as NodeChange, w as VisualEdge, x as SinglePathOptions, y as NodeConfig } from "./types-FBZCrmnG.mjs";
2
- import { bfs, dfs, genCycles, genPostorders, genPreorders, genShortestPaths, genSimplePaths, getAllPairsShortestPaths, getConnectedComponents, getCycles, getMinimumSpanningTree, getPostorder, getPostorders, getPreorder, getPreorders, getShortestPath, getShortestPaths, getSimplePath, getSimplePaths, getStronglyConnectedComponents, getTopologicalSort, hasPath, isAcyclic, isConnected, isTree, joinPaths } from "./algorithms.mjs";
1
+ import { A as VisualNode, C as SinglePathOptions, D as VisualGraph, E as VisualEdge, M as WalkOptions, N as WeightedWalkOptions, O as VisualGraphConfig, S as PathOptions, T as TraversalOptions, _ as GraphPath, a as EdgeChange, b as NodeChange, c as EntitiesUpdate, d as GraphConfig, f as GraphDiff, g as GraphPatch, h as GraphNode, i as DeleteNodeOptions, j as WalkContext, l as EntityRect, m as GraphFormatConverter, n as AllPairsShortestPathsOptions, o as EdgeConfig, p as GraphEdge, r as CoverageStats, s as EntitiesConfig, t as AStarOptions, u as Graph, v as GraphStep, w as TransitionOptions, x as NodeConfig, y as MSTOptions } from "./types-DkKjaQW3.mjs";
2
+ import { bfs, dfs, genCycles, genPostorders, genPreorders, genShortestPaths, genSimplePaths, getAStarPath, getAllPairsShortestPaths, getConnectedComponents, getCycles, getMinimumSpanningTree, getPostorder, getPostorders, getPreorder, getPreorders, getShortestPath, getShortestPaths, getSimplePath, getSimplePaths, getStronglyConnectedComponents, getTopologicalSort, hasPath, isAcyclic, isConnected, isTree, joinPaths } from "./algorithms.mjs";
3
3
  import { createFormatConverter } from "./formats/converter/index.mjs";
4
4
  import { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getNeighbors, getOutDegree, getOutEdges, getParent, getPredecessors, getRelativeDistance, getRelativeDistanceMap, getRoots, getSiblings, getSinks, getSources, getSuccessors, isCompound, isLeaf } from "./queries.mjs";
5
5
 
6
6
  //#region src/graph.d.ts
7
7
 
8
+ /**
9
+ * Create a resolved graph node from a config. Fills in defaults.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const node = createGraphNode({ id: 'a', data: { label: 'hi' } });
14
+ * // { type: 'node', id: 'a', label: '', data: { label: 'hi' } }
15
+ * ```
16
+ */
17
+ declare function createGraphNode<T = any>(config: NodeConfig<T>): GraphNode<T>;
18
+ /**
19
+ * Create a resolved graph edge from a config. Fills in defaults.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const edge = createGraphEdge({ id: 'e1', sourceId: 'a', targetId: 'b' });
24
+ * // { type: 'edge', id: 'e1', sourceId: 'a', targetId: 'b', label: null, data: undefined }
25
+ * ```
26
+ */
27
+ declare function createGraphEdge<T = any>(config: EdgeConfig<T>): GraphEdge<T>;
8
28
  /**
9
29
  * Create a graph from a config. Resolves defaults for all fields.
10
30
  *
@@ -449,5 +469,102 @@ declare function toDiff<N, E>(patches: GraphPatch<N, E>[]): GraphDiff<N, E>;
449
469
  * ```
450
470
  */
451
471
  declare function flatten<N, E, G>(graph: Graph<N, E, G>): Graph<N, E, G>;
472
+ /**
473
+ * Returns the induced subgraph containing only the given node IDs
474
+ * and edges whose endpoints are both in the set.
475
+ *
476
+ * Parent references to nodes outside the set are removed.
477
+ *
478
+ * @example
479
+ * ```ts
480
+ * import { createGraph, getSubgraph } from '@statelyai/graph';
481
+ *
482
+ * const graph = createGraph({
483
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
484
+ * edges: [
485
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
486
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
487
+ * ],
488
+ * });
489
+ *
490
+ * const sub = getSubgraph(graph, ['a', 'b']);
491
+ * // sub.nodes: [a, b], sub.edges: [ab]
492
+ * ```
493
+ */
494
+ declare function getSubgraph<N, E, G>(graph: Graph<N, E, G>, nodeIds: string[]): Graph<N, E, G>;
495
+ /**
496
+ * Returns a new graph with all edge directions flipped (source ↔ target).
497
+ * Optionally filters which edges to include.
498
+ *
499
+ * @example
500
+ * ```ts
501
+ * import { createGraph, reverseGraph } from '@statelyai/graph';
502
+ *
503
+ * const graph = createGraph({
504
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
505
+ * edges: [
506
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
507
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
508
+ * ],
509
+ * });
510
+ *
511
+ * const rev = reverseGraph(graph);
512
+ * // rev edges: b→a, c→b
513
+ *
514
+ * const filtered = reverseGraph(graph, (e) => e.id !== 'bc');
515
+ * // filtered edges: b→a (only ab reversed, bc excluded)
516
+ * ```
517
+ */
518
+ declare function reverseGraph<N, E, G>(graph: Graph<N, E, G>, filterEdge?: (edge: GraphEdge<E>) => boolean): Graph<N, E, G>;
519
+ //#endregion
520
+ //#region src/walks.d.ts
521
+ /**
522
+ * Random walk. At each node, picks a uniformly random outgoing edge.
523
+ * Yields steps indefinitely (may revisit nodes) until a sink node is reached.
524
+ */
525
+ declare function genRandomWalk<N, E>(graph: Graph<N, E>, options?: WalkOptions<E>): Generator<GraphStep<N, E>>;
526
+ /**
527
+ * Weighted random walk. Edge selection probability proportional to weight.
528
+ */
529
+ declare function genWeightedRandomWalk<N, E>(graph: Graph<N, E>, options?: WeightedWalkOptions<E>): Generator<GraphStep<N, E>>;
530
+ /**
531
+ * Quick random walk targeting unvisited edges.
532
+ * If unvisited outgoing edges exist, picks one randomly.
533
+ * Otherwise, finds shortest path to a node with unvisited outgoing edges.
534
+ */
535
+ declare function genQuickRandomWalk<N, E>(graph: Graph<N, E>, options?: WalkOptions<E>): Generator<GraphStep<N, E>>;
536
+ /**
537
+ * Walk a predefined sequence of edge IDs.
538
+ * Validates each edge exists and connects from the current position.
539
+ */
540
+ declare function genPredefinedWalk<N, E>(graph: Graph<N, E>, edgeIds: string[], options?: Pick<WalkOptions<E>, 'from'>): Generator<GraphStep<N, E>>;
541
+ /**
542
+ * Yield at most `n` steps from the source generator.
543
+ */
544
+ declare function takeSteps<N, E>(gen: Generator<GraphStep<N, E>>, n: number): Generator<GraphStep<N, E>>;
545
+ /**
546
+ * Yield steps until a specific node is reached.
547
+ */
548
+ declare function takeUntilNode<N, E>(gen: Generator<GraphStep<N, E>>, nodeId: string): Generator<GraphStep<N, E>>;
549
+ /**
550
+ * Yield steps until a specific edge is traversed.
551
+ */
552
+ declare function takeUntilEdge<N, E>(gen: Generator<GraphStep<N, E>>, edgeId: string): Generator<GraphStep<N, E>>;
553
+ /**
554
+ * Yield steps until node coverage reaches the target (0–1).
555
+ */
556
+ declare function takeUntilNodeCoverage<N, E>(gen: Generator<GraphStep<N, E>>, graph: Graph<N, E>, coverage: number, options?: {
557
+ from?: string;
558
+ }): Generator<GraphStep<N, E>>;
559
+ /**
560
+ * Yield steps until edge coverage reaches the target (0–1).
561
+ */
562
+ declare function takeUntilEdgeCoverage<N, E>(gen: Generator<GraphStep<N, E>>, graph: Graph<N, E>, coverage: number): Generator<GraphStep<N, E>>;
563
+ /**
564
+ * Compute coverage statistics for a completed walk.
565
+ */
566
+ declare function getCoverage<N, E>(graph: Graph<N, E>, steps: GraphStep<N, E>[], options?: {
567
+ from?: string;
568
+ }): CoverageStats;
452
569
  //#endregion
453
- export { type AllPairsShortestPathsOptions, type DeleteNodeOptions, type EdgeChange, type EdgeConfig, type EntitiesConfig, type EntitiesUpdate, type Graph, type GraphConfig, type GraphDiff, type GraphEdge, type GraphFormatConverter, GraphInstance, type GraphNode, type GraphPatch, type GraphPath, type GraphStep, type MSTOptions, type NodeChange, type NodeConfig, type PathOptions, type EntityRect as Positioned, type SinglePathOptions, type TransitionOptions, type TraversalOptions, type VisualEdge, type VisualGraph, type VisualGraphConfig, type VisualNode, addEdge, addEntities, addNode, applyPatches, bfs, createFormatConverter, createGraph, createGraphFromTransition, createVisualGraph, deleteEdge, deleteEntities, deleteNode, dfs, flatten, genCycles, genPostorders, genPreorders, genShortestPaths, genSimplePaths, getAllPairsShortestPaths, getAncestors, getChildren, getConnectedComponents, getCycles, getDegree, getDepth, getDescendants, getDiff, getEdge, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getMinimumSpanningTree, getNeighbors, getNode, getOutDegree, getOutEdges, getParent, getPatches, getPostorder, getPostorders, getPredecessors, getPreorder, getPreorders, getRelativeDistance, getRelativeDistanceMap, getRoots, getShortestPath, getShortestPaths, getSiblings, getSimplePath, getSimplePaths, getSinks, getSources, getStronglyConnectedComponents, getSuccessors, getTopologicalSort, hasEdge, hasNode, hasPath, invalidateIndex, invertDiff, isAcyclic, isCompound, isConnected, isEmptyDiff, isLeaf, isTree, joinPaths, toDiff, toPatches, updateEdge, updateEntities, updateNode };
570
+ export { type AStarOptions, type AllPairsShortestPathsOptions, type CoverageStats, type DeleteNodeOptions, type EdgeChange, type EdgeConfig, type EntitiesConfig, type EntitiesUpdate, type Graph, type GraphConfig, type GraphDiff, type GraphEdge, type GraphFormatConverter, GraphInstance, type GraphNode, type GraphPatch, type GraphPath, type GraphStep, type MSTOptions, type NodeChange, type NodeConfig, type PathOptions, type EntityRect as Positioned, type SinglePathOptions, type TransitionOptions, type TraversalOptions, type VisualEdge, type VisualGraph, type VisualGraphConfig, type VisualNode, type WalkContext, type WalkOptions, type WeightedWalkOptions, addEdge, addEntities, addNode, applyPatches, bfs, createFormatConverter, createGraph, createGraphEdge, createGraphFromTransition, createGraphNode, createVisualGraph, deleteEdge, deleteEntities, deleteNode, dfs, flatten, genCycles, genPostorders, genPredefinedWalk, genPreorders, genQuickRandomWalk, genRandomWalk, genShortestPaths, genSimplePaths, genWeightedRandomWalk, getAStarPath, getAllPairsShortestPaths, getAncestors, getChildren, getConnectedComponents, getCoverage, getCycles, getDegree, getDepth, getDescendants, getDiff, getEdge, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getMinimumSpanningTree, getNeighbors, getNode, getOutDegree, getOutEdges, getParent, getPatches, getPostorder, getPostorders, getPredecessors, getPreorder, getPreorders, getRelativeDistance, getRelativeDistanceMap, getRoots, getShortestPath, getShortestPaths, getSiblings, getSimplePath, getSimplePaths, getSinks, getSources, getStronglyConnectedComponents, getSubgraph, getSuccessors, getTopologicalSort, hasEdge, hasNode, hasPath, invalidateIndex, invertDiff, isAcyclic, isCompound, isConnected, isEmptyDiff, isLeaf, isTree, joinPaths, reverseGraph, takeSteps, takeUntilEdge, takeUntilEdgeCoverage, takeUntilNode, takeUntilNodeCoverage, toDiff, toPatches, updateEdge, updateEntities, updateNode };
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import { o as invalidateIndex, t as getIndex } from "./indexing-DyfgLuzw.mjs";
2
- import { A as addNode, B as hasNode, C as isAcyclic, D as GraphInstance, E as joinPaths, F as deleteEntities, H as updateEntities, I as deleteNode, L as getEdge, M as createGraphFromTransition, N as createVisualGraph, O as addEdge, P as deleteEdge, R as getNode, S as hasPath, T as isTree, U as updateNode, V as updateEdge, _ as getShortestPaths, a as genPreorders, b as getStronglyConnectedComponents, c as getAllPairsShortestPaths, d as getMinimumSpanningTree, f as getPostorder, g as getShortestPath, h as getPreorders, i as genPostorders, j as createGraph, k as addEntities, l as getConnectedComponents, m as getPreorder, n as dfs, o as genShortestPaths, p as getPostorders, r as genCycles, s as genSimplePaths, t as bfs, u as getCycles, v as getSimplePath, w as isConnected, x as getTopologicalSort, y as getSimplePaths, z as hasEdge } from "./algorithms-DldwenLt.mjs";
2
+ import { A as addEntities, B as getEdge, C as hasPath, D as joinPaths, E as isTree, F as createGraphNode, G as updateEntities, H as hasEdge, I as createVisualGraph, K as updateNode, L as deleteEdge, M as createGraph, N as createGraphEdge, O as GraphInstance, P as createGraphFromTransition, R as deleteEntities, S as getTopologicalSort, T as isConnected, U as hasNode, V as getNode, W as updateEdge, _ as getShortestPath, a as genPreorders, b as getSimplePaths, c as getAStarPath, d as getCycles, f as getMinimumSpanningTree, g as getPreorders, h as getPreorder, i as genPostorders, j as addNode, k as addEdge, l as getAllPairsShortestPaths, m as getPostorders, n as dfs, o as genShortestPaths, p as getPostorder, r as genCycles, s as genSimplePaths, t as bfs, u as getConnectedComponents, v as getShortestPaths, w as isAcyclic, x as getStronglyConnectedComponents, y as getSimplePath, z as deleteNode } from "./algorithms-Dw5jEwDK.mjs";
3
3
  import { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getNeighbors, getOutDegree, getOutEdges, getParent, getPredecessors, getRelativeDistance, getRelativeDistanceMap, getRoots, getSiblings, getSinks, getSources, getSuccessors, isCompound, isLeaf } from "./queries.mjs";
4
4
  import { n as createFormatConverter } from "./converter-B5CUD0r9.mjs";
5
5
 
6
6
  //#region src/diff.ts
7
- function nodeToConfig(node) {
7
+ function nodeToConfig$1(node) {
8
8
  const config = { id: node.id };
9
9
  if (node.parentId) config.parentId = node.parentId;
10
10
  if (node.initialNodeId) config.initialNodeId = node.initialNodeId;
@@ -19,7 +19,7 @@ function nodeToConfig(node) {
19
19
  if (node.style !== void 0) config.style = node.style;
20
20
  return config;
21
21
  }
22
- function edgeToConfig(edge) {
22
+ function edgeToConfig$1(edge) {
23
23
  const config = {
24
24
  id: edge.id,
25
25
  sourceId: edge.sourceId,
@@ -101,7 +101,7 @@ function getDiff(a, b) {
101
101
  };
102
102
  for (const [id, nodeB] of bNodeMap) {
103
103
  const nodeA = aNodeMap.get(id);
104
- if (!nodeA) diff.nodes.added.push(nodeToConfig(nodeB));
104
+ if (!nodeA) diff.nodes.added.push(nodeToConfig$1(nodeB));
105
105
  else {
106
106
  const oldPartial = {};
107
107
  const newPartial = {};
@@ -116,10 +116,10 @@ function getDiff(a, b) {
116
116
  });
117
117
  }
118
118
  }
119
- for (const [id, nodeA] of aNodeMap) if (!bNodeMap.has(id)) diff.nodes.removed.push(nodeToConfig(nodeA));
119
+ for (const [id, nodeA] of aNodeMap) if (!bNodeMap.has(id)) diff.nodes.removed.push(nodeToConfig$1(nodeA));
120
120
  for (const [id, edgeB] of bEdgeMap) {
121
121
  const edgeA = aEdgeMap.get(id);
122
- if (!edgeA) diff.edges.added.push(edgeToConfig(edgeB));
122
+ if (!edgeA) diff.edges.added.push(edgeToConfig$1(edgeB));
123
123
  else {
124
124
  const oldPartial = {};
125
125
  const newPartial = {};
@@ -134,7 +134,7 @@ function getDiff(a, b) {
134
134
  });
135
135
  }
136
136
  }
137
- for (const [id, edgeA] of aEdgeMap) if (!bEdgeMap.has(id)) diff.edges.removed.push(edgeToConfig(edgeA));
137
+ for (const [id, edgeA] of aEdgeMap) if (!bEdgeMap.has(id)) diff.edges.removed.push(edgeToConfig$1(edgeA));
138
138
  return diff;
139
139
  }
140
140
  /**
@@ -455,6 +455,362 @@ function flatten(graph) {
455
455
  data: graph.data
456
456
  });
457
457
  }
458
+ function nodeToConfig(node, nodeIdSet) {
459
+ const config = {
460
+ id: node.id,
461
+ label: node.label,
462
+ data: node.data
463
+ };
464
+ if (node.parentId !== void 0 && node.parentId !== null) config.parentId = nodeIdSet && !nodeIdSet.has(node.parentId) ? void 0 : node.parentId;
465
+ if (node.initialNodeId !== void 0) config.initialNodeId = node.initialNodeId ?? void 0;
466
+ if (node.x !== void 0) config.x = node.x;
467
+ if (node.y !== void 0) config.y = node.y;
468
+ if (node.width !== void 0) config.width = node.width;
469
+ if (node.height !== void 0) config.height = node.height;
470
+ if (node.shape !== void 0) config.shape = node.shape;
471
+ if (node.color !== void 0) config.color = node.color;
472
+ if (node.style !== void 0) config.style = node.style;
473
+ return config;
474
+ }
475
+ function edgeToConfig(edge) {
476
+ const config = {
477
+ id: edge.id,
478
+ sourceId: edge.sourceId,
479
+ targetId: edge.targetId,
480
+ label: edge.label,
481
+ data: edge.data
482
+ };
483
+ if (edge.weight !== void 0) config.weight = edge.weight;
484
+ if (edge.x !== void 0) config.x = edge.x;
485
+ if (edge.y !== void 0) config.y = edge.y;
486
+ if (edge.width !== void 0) config.width = edge.width;
487
+ if (edge.height !== void 0) config.height = edge.height;
488
+ if (edge.color !== void 0) config.color = edge.color;
489
+ if (edge.style !== void 0) config.style = edge.style;
490
+ return config;
491
+ }
492
+ /**
493
+ * Returns the induced subgraph containing only the given node IDs
494
+ * and edges whose endpoints are both in the set.
495
+ *
496
+ * Parent references to nodes outside the set are removed.
497
+ *
498
+ * @example
499
+ * ```ts
500
+ * import { createGraph, getSubgraph } from '@statelyai/graph';
501
+ *
502
+ * const graph = createGraph({
503
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
504
+ * edges: [
505
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
506
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
507
+ * ],
508
+ * });
509
+ *
510
+ * const sub = getSubgraph(graph, ['a', 'b']);
511
+ * // sub.nodes: [a, b], sub.edges: [ab]
512
+ * ```
513
+ */
514
+ function getSubgraph(graph, nodeIds) {
515
+ const nodeIdSet = new Set(nodeIds);
516
+ return createGraph({
517
+ id: graph.id,
518
+ type: graph.type,
519
+ initialNodeId: graph.initialNodeId && nodeIdSet.has(graph.initialNodeId) ? graph.initialNodeId : void 0,
520
+ nodes: graph.nodes.filter((n) => nodeIdSet.has(n.id)).map((n) => nodeToConfig(n, nodeIdSet)),
521
+ edges: graph.edges.filter((e) => nodeIdSet.has(e.sourceId) && nodeIdSet.has(e.targetId)).map(edgeToConfig),
522
+ data: graph.data
523
+ });
524
+ }
525
+ /**
526
+ * Returns a new graph with all edge directions flipped (source ↔ target).
527
+ * Optionally filters which edges to include.
528
+ *
529
+ * @example
530
+ * ```ts
531
+ * import { createGraph, reverseGraph } from '@statelyai/graph';
532
+ *
533
+ * const graph = createGraph({
534
+ * nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
535
+ * edges: [
536
+ * { id: 'ab', sourceId: 'a', targetId: 'b' },
537
+ * { id: 'bc', sourceId: 'b', targetId: 'c' },
538
+ * ],
539
+ * });
540
+ *
541
+ * const rev = reverseGraph(graph);
542
+ * // rev edges: b→a, c→b
543
+ *
544
+ * const filtered = reverseGraph(graph, (e) => e.id !== 'bc');
545
+ * // filtered edges: b→a (only ab reversed, bc excluded)
546
+ * ```
547
+ */
548
+ function reverseGraph(graph, filterEdge) {
549
+ const edges = filterEdge ? graph.edges.filter(filterEdge) : graph.edges;
550
+ return createGraph({
551
+ id: graph.id,
552
+ type: graph.type,
553
+ initialNodeId: graph.initialNodeId ?? void 0,
554
+ nodes: graph.nodes.map((n) => nodeToConfig(n)),
555
+ edges: edges.map((e) => {
556
+ const config = edgeToConfig(e);
557
+ config.sourceId = e.targetId;
558
+ config.targetId = e.sourceId;
559
+ return config;
560
+ }),
561
+ data: graph.data
562
+ });
563
+ }
564
+
565
+ //#endregion
566
+ //#region src/walks.ts
567
+ function mulberry32(seed) {
568
+ let s = seed | 0;
569
+ return () => {
570
+ s = s + 1831565813 | 0;
571
+ let t = Math.imul(s ^ s >>> 15, 1 | s);
572
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
573
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
574
+ };
575
+ }
576
+ function makeRng(seed) {
577
+ return seed !== void 0 ? mulberry32(seed) : Math.random;
578
+ }
579
+ function resolveFrom(graph, from) {
580
+ if (from) return from;
581
+ if (graph.initialNodeId) return graph.initialNodeId;
582
+ const inDeg = /* @__PURE__ */ new Map();
583
+ for (const n of graph.nodes) inDeg.set(n.id, 0);
584
+ for (const e of graph.edges) inDeg.set(e.targetId, (inDeg.get(e.targetId) ?? 0) + 1);
585
+ const roots = [...inDeg.entries()].filter(([, d]) => d === 0);
586
+ if (roots.length === 1) return roots[0][0];
587
+ throw new Error("Cannot determine start node: provide `from`, set graph.initialNodeId, or have exactly one source node.");
588
+ }
589
+ /**
590
+ * Random walk. At each node, picks a uniformly random outgoing edge.
591
+ * Yields steps indefinitely (may revisit nodes) until a sink node is reached.
592
+ */
593
+ function* genRandomWalk(graph, options) {
594
+ const rng = makeRng(options?.seed);
595
+ let currentId = resolveFrom(graph, options?.from);
596
+ const ctx = {
597
+ currentNodeId: currentId,
598
+ visitedNodes: new Set([currentId]),
599
+ visitedEdges: /* @__PURE__ */ new Set(),
600
+ stepCount: 0
601
+ };
602
+ while (true) {
603
+ let edges = getOutEdges(graph, currentId);
604
+ if (options?.filter) edges = edges.filter((e) => options.filter(e, ctx));
605
+ if (edges.length === 0) return;
606
+ const edge = edges[Math.floor(rng() * edges.length)];
607
+ const node = getNode(graph, edge.targetId);
608
+ const step = {
609
+ edge,
610
+ node
611
+ };
612
+ currentId = node.id;
613
+ ctx.currentNodeId = currentId;
614
+ ctx.visitedNodes.add(currentId);
615
+ ctx.visitedEdges.add(edge.id);
616
+ ctx.stepCount++;
617
+ options?.onStep?.(step, ctx);
618
+ yield step;
619
+ }
620
+ }
621
+ /**
622
+ * Weighted random walk. Edge selection probability proportional to weight.
623
+ */
624
+ function* genWeightedRandomWalk(graph, options) {
625
+ const rng = makeRng(options?.seed);
626
+ const getWeight = options?.getWeight ?? ((e) => e.weight ?? 1);
627
+ let currentId = resolveFrom(graph, options?.from);
628
+ const ctx = {
629
+ currentNodeId: currentId,
630
+ visitedNodes: new Set([currentId]),
631
+ visitedEdges: /* @__PURE__ */ new Set(),
632
+ stepCount: 0
633
+ };
634
+ while (true) {
635
+ let edges = getOutEdges(graph, currentId);
636
+ if (options?.filter) edges = edges.filter((e) => options.filter(e, ctx));
637
+ if (edges.length === 0) return;
638
+ const weights = edges.map((e) => Math.max(0, getWeight(e)));
639
+ const total = weights.reduce((a, b) => a + b, 0);
640
+ if (total === 0) return;
641
+ let r = rng() * total;
642
+ let chosen = edges[0];
643
+ for (let i = 0; i < edges.length; i++) {
644
+ r -= weights[i];
645
+ if (r <= 0) {
646
+ chosen = edges[i];
647
+ break;
648
+ }
649
+ }
650
+ const node = getNode(graph, chosen.targetId);
651
+ const step = {
652
+ edge: chosen,
653
+ node
654
+ };
655
+ currentId = node.id;
656
+ ctx.currentNodeId = currentId;
657
+ ctx.visitedNodes.add(currentId);
658
+ ctx.visitedEdges.add(chosen.id);
659
+ ctx.stepCount++;
660
+ options?.onStep?.(step, ctx);
661
+ yield step;
662
+ }
663
+ }
664
+ /**
665
+ * Quick random walk targeting unvisited edges.
666
+ * If unvisited outgoing edges exist, picks one randomly.
667
+ * Otherwise, finds shortest path to a node with unvisited outgoing edges.
668
+ */
669
+ function* genQuickRandomWalk(graph, options) {
670
+ const rng = makeRng(options?.seed);
671
+ let currentId = resolveFrom(graph, options?.from);
672
+ const visitedEdges = /* @__PURE__ */ new Set();
673
+ const allEdgeIds = new Set(graph.edges.map((e) => e.id));
674
+ const ctx = {
675
+ currentNodeId: currentId,
676
+ visitedNodes: new Set([currentId]),
677
+ visitedEdges,
678
+ stepCount: 0
679
+ };
680
+ while (visitedEdges.size < allEdgeIds.size) {
681
+ let edges = getOutEdges(graph, currentId);
682
+ if (options?.filter) edges = edges.filter((e) => options.filter(e, ctx));
683
+ const unvisited = edges.filter((e) => !visitedEdges.has(e.id));
684
+ if (unvisited.length > 0) {
685
+ const edge = unvisited[Math.floor(rng() * unvisited.length)];
686
+ const node = getNode(graph, edge.targetId);
687
+ const step = {
688
+ edge,
689
+ node
690
+ };
691
+ currentId = node.id;
692
+ ctx.currentNodeId = currentId;
693
+ ctx.visitedNodes.add(currentId);
694
+ visitedEdges.add(edge.id);
695
+ ctx.stepCount++;
696
+ options?.onStep?.(step, ctx);
697
+ yield step;
698
+ } else {
699
+ let targetNodeId;
700
+ for (const n of graph.nodes) if (getOutEdges(graph, n.id).some((e) => !visitedEdges.has(e.id))) {
701
+ targetNodeId = n.id;
702
+ break;
703
+ }
704
+ if (!targetNodeId) return;
705
+ const path = getShortestPath(graph, {
706
+ from: currentId,
707
+ to: targetNodeId
708
+ });
709
+ if (!path || path.steps.length === 0) return;
710
+ for (const step of path.steps) {
711
+ currentId = step.node.id;
712
+ ctx.currentNodeId = currentId;
713
+ ctx.visitedNodes.add(currentId);
714
+ visitedEdges.add(step.edge.id);
715
+ ctx.stepCount++;
716
+ options?.onStep?.(step, ctx);
717
+ yield step;
718
+ }
719
+ }
720
+ }
721
+ }
722
+ /**
723
+ * Walk a predefined sequence of edge IDs.
724
+ * Validates each edge exists and connects from the current position.
725
+ */
726
+ function* genPredefinedWalk(graph, edgeIds, options) {
727
+ let currentId = resolveFrom(graph, options?.from);
728
+ for (const edgeId of edgeIds) {
729
+ const edge = graph.edges.find((e) => e.id === edgeId);
730
+ if (!edge) throw new Error(`Edge "${edgeId}" not found in graph.`);
731
+ if (edge.sourceId !== currentId) throw new Error(`Edge "${edgeId}" starts at "${edge.sourceId}" but current position is "${currentId}".`);
732
+ const node = getNode(graph, edge.targetId);
733
+ currentId = node.id;
734
+ yield {
735
+ edge,
736
+ node
737
+ };
738
+ }
739
+ }
740
+ /**
741
+ * Yield at most `n` steps from the source generator.
742
+ */
743
+ function* takeSteps(gen, n) {
744
+ let count = 0;
745
+ for (const step of gen) {
746
+ yield step;
747
+ if (++count >= n) return;
748
+ }
749
+ }
750
+ /**
751
+ * Yield steps until a specific node is reached.
752
+ */
753
+ function* takeUntilNode(gen, nodeId) {
754
+ for (const step of gen) {
755
+ yield step;
756
+ if (step.node.id === nodeId) return;
757
+ }
758
+ }
759
+ /**
760
+ * Yield steps until a specific edge is traversed.
761
+ */
762
+ function* takeUntilEdge(gen, edgeId) {
763
+ for (const step of gen) {
764
+ yield step;
765
+ if (step.edge.id === edgeId) return;
766
+ }
767
+ }
768
+ /**
769
+ * Yield steps until node coverage reaches the target (0–1).
770
+ */
771
+ function* takeUntilNodeCoverage(gen, graph, coverage, options) {
772
+ const totalNodes = graph.nodes.length;
773
+ const target = Math.ceil(coverage * totalNodes);
774
+ const startId = options?.from ?? graph.initialNodeId ?? graph.nodes[0]?.id;
775
+ const visited = new Set(startId ? [startId] : []);
776
+ for (const step of gen) {
777
+ visited.add(step.node.id);
778
+ yield step;
779
+ if (visited.size >= target) return;
780
+ }
781
+ }
782
+ /**
783
+ * Yield steps until edge coverage reaches the target (0–1).
784
+ */
785
+ function* takeUntilEdgeCoverage(gen, graph, coverage) {
786
+ const totalEdges = graph.edges.length;
787
+ const target = Math.ceil(coverage * totalEdges);
788
+ const visited = /* @__PURE__ */ new Set();
789
+ for (const step of gen) {
790
+ visited.add(step.edge.id);
791
+ yield step;
792
+ if (visited.size >= target) return;
793
+ }
794
+ }
795
+ /**
796
+ * Compute coverage statistics for a completed walk.
797
+ */
798
+ function getCoverage(graph, steps, options) {
799
+ const startId = options?.from ?? graph.initialNodeId ?? graph.nodes[0]?.id;
800
+ const visitedNodes = new Set(startId ? [startId] : []);
801
+ const visitedEdges = /* @__PURE__ */ new Set();
802
+ for (const step of steps) {
803
+ visitedNodes.add(step.node.id);
804
+ visitedEdges.add(step.edge.id);
805
+ }
806
+ return {
807
+ nodeCoverage: graph.nodes.length > 0 ? visitedNodes.size / graph.nodes.length : 1,
808
+ edgeCoverage: graph.edges.length > 0 ? visitedEdges.size / graph.edges.length : 1,
809
+ visitedNodes: [...visitedNodes],
810
+ visitedEdges: [...visitedEdges],
811
+ totalSteps: steps.length
812
+ };
813
+ }
458
814
 
459
815
  //#endregion
460
- export { GraphInstance, addEdge, addEntities, addNode, applyPatches, bfs, createFormatConverter, createGraph, createGraphFromTransition, createVisualGraph, deleteEdge, deleteEntities, deleteNode, dfs, flatten, genCycles, genPostorders, genPreorders, genShortestPaths, genSimplePaths, getAllPairsShortestPaths, getAncestors, getChildren, getConnectedComponents, getCycles, getDegree, getDepth, getDescendants, getDiff, getEdge, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getMinimumSpanningTree, getNeighbors, getNode, getOutDegree, getOutEdges, getParent, getPatches, getPostorder, getPostorders, getPredecessors, getPreorder, getPreorders, getRelativeDistance, getRelativeDistanceMap, getRoots, getShortestPath, getShortestPaths, getSiblings, getSimplePath, getSimplePaths, getSinks, getSources, getStronglyConnectedComponents, getSuccessors, getTopologicalSort, hasEdge, hasNode, hasPath, invalidateIndex, invertDiff, isAcyclic, isCompound, isConnected, isEmptyDiff, isLeaf, isTree, joinPaths, toDiff, toPatches, updateEdge, updateEntities, updateNode };
816
+ export { GraphInstance, addEdge, addEntities, addNode, applyPatches, bfs, createFormatConverter, createGraph, createGraphEdge, createGraphFromTransition, createGraphNode, createVisualGraph, deleteEdge, deleteEntities, deleteNode, dfs, flatten, genCycles, genPostorders, genPredefinedWalk, genPreorders, genQuickRandomWalk, genRandomWalk, genShortestPaths, genSimplePaths, genWeightedRandomWalk, getAStarPath, getAllPairsShortestPaths, getAncestors, getChildren, getConnectedComponents, getCoverage, getCycles, getDegree, getDepth, getDescendants, getDiff, getEdge, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getMinimumSpanningTree, getNeighbors, getNode, getOutDegree, getOutEdges, getParent, getPatches, getPostorder, getPostorders, getPredecessors, getPreorder, getPreorders, getRelativeDistance, getRelativeDistanceMap, getRoots, getShortestPath, getShortestPaths, getSiblings, getSimplePath, getSimplePaths, getSinks, getSources, getStronglyConnectedComponents, getSubgraph, getSuccessors, getTopologicalSort, hasEdge, hasNode, hasPath, invalidateIndex, invertDiff, isAcyclic, isCompound, isConnected, isEmptyDiff, isLeaf, isTree, joinPaths, reverseGraph, takeSteps, takeUntilEdge, takeUntilEdgeCoverage, takeUntilNode, takeUntilNodeCoverage, toDiff, toPatches, updateEdge, updateEntities, updateNode };