@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 +191 -0
- package/dist/adjacency-list-CXpOCibq.mjs +49 -0
- package/dist/adjacency-list-DW-lAUe8.d.mts +10 -0
- package/dist/algorithms-R35X6ro4.mjs +1197 -0
- package/dist/algorithms.d.mts +82 -0
- package/dist/algorithms.mjs +3 -0
- package/dist/dot-BRtq3e3c.mjs +59 -0
- package/dist/dot-HmJeUMsj.d.mts +6 -0
- package/dist/edge-list-BRujEnnU.mjs +39 -0
- package/dist/edge-list-CJmfoNu2.d.mts +10 -0
- package/dist/formats/adjacency-list.d.mts +2 -0
- package/dist/formats/adjacency-list.mjs +3 -0
- package/dist/formats/dot.d.mts +2 -0
- package/dist/formats/dot.mjs +3 -0
- package/dist/formats/edge-list.d.mts +2 -0
- package/dist/formats/edge-list.mjs +3 -0
- package/dist/formats/graphml.d.mts +2 -0
- package/dist/formats/graphml.mjs +3 -0
- package/dist/graphml-CMjPzSfY.d.mts +7 -0
- package/dist/graphml-CUTNRXqd.mjs +240 -0
- package/dist/index.d.mts +142 -0
- package/dist/index.mjs +356 -0
- package/dist/indexing-BHg1VhqN.mjs +93 -0
- package/dist/queries.d.mts +37 -0
- package/dist/queries.mjs +221 -0
- package/dist/schemas.d.mts +46 -0
- package/dist/schemas.mjs +30 -0
- package/dist/types-XV3S5Jnh.d.mts +219 -0
- package/package.json +77 -0
- package/schemas/edge.schema.json +32 -0
- package/schemas/graph.schema.json +96 -0
- package/schemas/node.schema.json +35 -0
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 };
|