@statelyai/graph 0.1.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.
package/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # @statelyai/graph
2
+
3
+ A TypeScript graph library built on plain JSON objects. Supports directed/undirected graphs, hierarchical nodes, graph algorithms, visual properties, and serialization to DOT, GraphML, and more.
4
+
5
+ Made from our experience at [stately.ai](https://stately.ai), where we build visual tools for complex systems.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @statelyai/graph
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```ts
16
+ import { createGraph, addNode, addEdge, getShortestPath } from '@statelyai/graph';
17
+
18
+ const graph = createGraph({
19
+ nodes: [
20
+ { id: 'a', label: 'Start' },
21
+ { id: 'b', label: 'Middle' },
22
+ { id: 'c', label: 'End' },
23
+ ],
24
+ edges: [
25
+ { id: 'e1', sourceId: 'a', targetId: 'b' },
26
+ { id: 'e2', sourceId: 'b', targetId: 'c' },
27
+ ],
28
+ });
29
+
30
+ // Mutate in place
31
+ addNode(graph, { id: 'd', label: 'Shortcut' });
32
+ addEdge(graph, { id: 'e3', sourceId: 'a', targetId: 'd' });
33
+
34
+ // Algorithms work on the plain object
35
+ const path = getShortestPath(graph, { from: 'a', to: 'c' });
36
+ ```
37
+
38
+ ### Hierarchical Graphs
39
+
40
+ Nodes support parent-child relationships. Use `flatten()` to decompose compound nodes into a flat graph.
41
+
42
+ ```ts
43
+ import { createGraph, flatten } from '@statelyai/graph';
44
+
45
+ const graph = createGraph({
46
+ nodes: [
47
+ { id: 'a' },
48
+ { id: 'b', initialNodeId: 'b1' },
49
+ { id: 'b1', parentId: 'b' },
50
+ { id: 'b2', parentId: 'b' },
51
+ { id: 'c' },
52
+ ],
53
+ edges: [
54
+ { id: 'e1', sourceId: 'a', targetId: 'b' }, // resolves to a -> b1
55
+ { id: 'e2', sourceId: 'b1', targetId: 'b2' },
56
+ { id: 'e3', sourceId: 'b', targetId: 'c' }, // expands from all leaves of b
57
+ ],
58
+ });
59
+
60
+ const flat = flatten(graph); // only leaf nodes, edges resolved
61
+ ```
62
+
63
+ ### Visual Graphs
64
+
65
+ `createVisualGraph()` guarantees `x`, `y`, `width`, `height` on all nodes and edges (default `0`).
66
+
67
+ ```ts
68
+ import { createVisualGraph } from '@statelyai/graph';
69
+
70
+ const diagram = createVisualGraph({
71
+ direction: 'right',
72
+ nodes: [
73
+ { id: 'a', x: 0, y: 0, width: 120, height: 60, shape: 'rectangle' },
74
+ { id: 'b', x: 200, y: 0, width: 120, height: 60, shape: 'ellipse', color: '#3b82f6' },
75
+ ],
76
+ edges: [{ id: 'e1', sourceId: 'a', targetId: 'b', width: 100, height: 100 }],
77
+ });
78
+ ```
79
+
80
+ ### Serialization
81
+
82
+ ```ts
83
+ import { toDOT, toGraphML, toAdjacencyList, toEdgeList } from '@statelyai/graph';
84
+
85
+ console.log(toDOT(graph));
86
+ // Logs a Graphviz DOT string
87
+
88
+ console.log(toGraphML(graph));
89
+ // Logs a GraphML XML string
90
+
91
+ console.log(toAdjacencyList(graph));
92
+ // Logs an adjacency object, e.g. { a: ['b'], b: ['c'] }
93
+
94
+ console.log(toEdgeList(graph));
95
+ // Logs an array of [sourceId, targetId] pairs, e.g. [['a', 'b'], ['b', 'c']]
96
+ ```
97
+
98
+ ## API
99
+
100
+ ### Graph Creation
101
+
102
+ | Function | Description |
103
+ |----------|-------------|
104
+ | `createGraph(config?)` | Create a graph |
105
+ | `createVisualGraph(config?)` | Create a graph with required position/size on nodes and edges |
106
+
107
+ ### Lookups & Mutations
108
+
109
+ | Function | Description |
110
+ |----------|-------------|
111
+ | `getNode(graph, id)` | Node by id, or `undefined` |
112
+ | `getEdge(graph, id)` | Edge by id, or `undefined` |
113
+ | `hasNode(graph, id)` | Node exists? |
114
+ | `hasEdge(graph, id)` | Edge exists? |
115
+ | `addNode(graph, config)` | Add a node |
116
+ | `addEdge(graph, config)` | Add an edge |
117
+ | `deleteNode(graph, id, opts?)` | Delete node + connected edges |
118
+ | `deleteEdge(graph, id)` | Delete an edge |
119
+ | `updateNode(graph, id, patch)` | Update node fields |
120
+ | `updateEdge(graph, id, patch)` | Update edge fields |
121
+ | `addEntities(graph, entities)` | Batch add |
122
+ | `deleteEntities(graph, ids)` | Batch delete |
123
+ | `updateEntities(graph, updates)` | Batch update |
124
+
125
+ ### Queries
126
+
127
+ | Function | Description |
128
+ |----------|-------------|
129
+ | `getNeighbors(graph, nodeId)` | Adjacent nodes |
130
+ | `getSuccessors(graph, nodeId)` | Outgoing neighbors |
131
+ | `getPredecessors(graph, nodeId)` | Incoming neighbors |
132
+ | `getDegree(graph, nodeId)` | Connected edge count |
133
+ | `getInDegree` / `getOutDegree` | Directed edge counts |
134
+ | `getEdgesOf(graph, nodeId)` | All connected edges |
135
+ | `getInEdges` / `getOutEdges` | Directed edges |
136
+ | `getEdgeBetween(graph, src, tgt)` | Edge between two nodes |
137
+ | `getSources(graph)` | Nodes with inDegree 0 |
138
+ | `getSinks(graph)` | Nodes with outDegree 0 |
139
+
140
+ ### Hierarchy
141
+
142
+ | Function | Description |
143
+ |----------|-------------|
144
+ | `getChildren(graph, nodeId)` | Direct children |
145
+ | `getParent(graph, nodeId)` | Parent node |
146
+ | `getAncestors(graph, nodeId)` | All ancestors |
147
+ | `getDescendants(graph, nodeId)` | All descendants |
148
+ | `getSiblings(graph, nodeId)` | Same-parent nodes |
149
+ | `getRoots(graph)` | Top-level nodes |
150
+ | `getDepth(graph, nodeId)` | Hierarchy depth (root = 0) |
151
+ | `getLCA(graph, ...nodeIds)` | Least Common Ancestor |
152
+ | `isCompound(graph, nodeId)` | Has children? |
153
+ | `isLeaf(graph, nodeId)` | No children? |
154
+
155
+ ### Algorithms
156
+
157
+ | Function | Description |
158
+ |----------|-------------|
159
+ | `bfs(graph, startId)` | Breadth-first traversal (generator) |
160
+ | `dfs(graph, startId)` | Depth-first traversal (generator) |
161
+ | `hasPath(graph, src, tgt)` | Reachability check |
162
+ | `isAcyclic(graph)` | No cycles? |
163
+ | `isConnected(graph)` | Single connected component? |
164
+ | `isTree(graph)` | Connected + acyclic? |
165
+ | `getConnectedComponents(graph)` | Connected components |
166
+ | `getStronglyConnectedComponents(graph)` | SCCs (directed) |
167
+ | `getTopologicalSort(graph)` | Topological order, or `null` if cyclic |
168
+ | `getShortestPath(graph, opts)` | Single shortest path |
169
+ | `getShortestPaths(graph, opts)` | All shortest paths from source |
170
+ | `getSimplePath(graph, opts)` | Single simple path |
171
+ | `getSimplePaths(graph, opts)` | All simple paths |
172
+ | `getCycles(graph)` | All cycles |
173
+ | `getPreorder` / `getPostorder` | DFS orderings |
174
+ | `getMinimumSpanningTree(graph, opts)` | MST (Prim's or Kruskal's) |
175
+ | `getAllPairsShortestPaths(graph, opts)` | Floyd-Warshall or Dijkstra |
176
+
177
+ Generator variants: `genShortestPaths`, `genSimplePaths`, `genCycles`, `genPreorders`, `genPostorders`.
178
+
179
+ ### Transforms & Formats
180
+
181
+ | Function | Description |
182
+ |----------|-------------|
183
+ | `flatten(graph)` | Decompose hierarchy into flat leaf-node graph |
184
+ | `toDOT` / `toGraphML` | Export to Graphviz DOT / GraphML |
185
+ | `fromGraphML` | Import from GraphML |
186
+ | `toAdjacencyList` / `fromAdjacencyList` | Adjacency list conversion |
187
+ | `toEdgeList` / `fromEdgeList` | Edge list conversion |
188
+
189
+ ## License
190
+
191
+ MIT
@@ -0,0 +1,49 @@
1
+ //#region src/formats/adjacency-list.ts
2
+ function toAdjacencyList(graph) {
3
+ const adj = {};
4
+ for (const node of graph.nodes) adj[node.id] = [];
5
+ for (const edge of graph.edges) {
6
+ adj[edge.sourceId]?.push(edge.targetId);
7
+ if (graph.type === "undirected") adj[edge.targetId]?.push(edge.sourceId);
8
+ }
9
+ return adj;
10
+ }
11
+ function fromAdjacencyList(adj, options) {
12
+ const directed = options?.directed ?? true;
13
+ const seen = /* @__PURE__ */ new Set();
14
+ const nodes = Object.keys(adj).map((id) => ({
15
+ type: "node",
16
+ id,
17
+ parentId: null,
18
+ initialNodeId: null,
19
+ label: "",
20
+ data: void 0
21
+ }));
22
+ const edges = [];
23
+ let edgeIdx = 0;
24
+ for (const [sourceId, targets] of Object.entries(adj)) for (const targetId of targets) {
25
+ const key = directed ? `${sourceId}->${targetId}` : [sourceId, targetId].sort().join("<->");
26
+ if (!seen.has(key)) {
27
+ seen.add(key);
28
+ edges.push({
29
+ type: "edge",
30
+ id: `e${edgeIdx++}`,
31
+ sourceId,
32
+ targetId,
33
+ label: "",
34
+ data: void 0
35
+ });
36
+ }
37
+ }
38
+ return {
39
+ id: options?.id ?? "",
40
+ type: directed ? "directed" : "undirected",
41
+ initialNodeId: null,
42
+ nodes,
43
+ edges,
44
+ data: void 0
45
+ };
46
+ }
47
+
48
+ //#endregion
49
+ export { toAdjacencyList as n, fromAdjacencyList as t };
@@ -0,0 +1,10 @@
1
+ import { c as Graph } from "./types-XV3S5Jnh.mjs";
2
+
3
+ //#region src/formats/adjacency-list.d.ts
4
+ declare function toAdjacencyList(graph: Graph): Record<string, string[]>;
5
+ declare function fromAdjacencyList(adj: Record<string, string[]>, options?: {
6
+ directed?: boolean;
7
+ id?: string;
8
+ }): Graph;
9
+ //#endregion
10
+ export { toAdjacencyList as n, fromAdjacencyList as t };