@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/dist/index.mjs ADDED
@@ -0,0 +1,356 @@
1
+ import { o as invalidateIndex, t as getIndex } from "./indexing-BHg1VhqN.mjs";
2
+ import { A as createGraph, B as updateEntities, C as isAcyclic, D as addEdge, E as GraphInstance, F as getEdge, I as getNode, L as hasEdge, M as deleteEdge, N as deleteEntities, O as addEntities, P as deleteNode, R as hasNode, S as hasPath, T as isTree, V as updateNode, _ 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 createVisualGraph, k as addNode, 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 updateEdge } from "./algorithms-R35X6ro4.mjs";
3
+ import { EdgeSchema, GraphSchema, NodeSchema } from "./schemas.mjs";
4
+ import { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getNeighbors, getOutDegree, getOutEdges, getParent, getPredecessors, getRoots, getSiblings, getSinks, getSources, getSuccessors, isCompound, isLeaf } from "./queries.mjs";
5
+ import { n as toGraphML, t as fromGraphML } from "./graphml-CUTNRXqd.mjs";
6
+ import { t as toDOT } from "./dot-BRtq3e3c.mjs";
7
+ import { n as toAdjacencyList, t as fromAdjacencyList } from "./adjacency-list-CXpOCibq.mjs";
8
+ import { n as toEdgeList, t as fromEdgeList } from "./edge-list-BRujEnnU.mjs";
9
+
10
+ //#region src/diff.ts
11
+ function nodeToConfig(node) {
12
+ const config = { id: node.id };
13
+ if (node.parentId !== null) config.parentId = node.parentId;
14
+ if (node.initialNodeId !== null) config.initialNodeId = node.initialNodeId;
15
+ if (node.label !== "") config.label = node.label;
16
+ if (node.data !== void 0) config.data = node.data;
17
+ if (node.x !== void 0) config.x = node.x;
18
+ if (node.y !== void 0) config.y = node.y;
19
+ if (node.width !== void 0) config.width = node.width;
20
+ if (node.height !== void 0) config.height = node.height;
21
+ if (node.shape !== void 0) config.shape = node.shape;
22
+ if (node.color !== void 0) config.color = node.color;
23
+ if (node.style !== void 0) config.style = node.style;
24
+ return config;
25
+ }
26
+ function edgeToConfig(edge) {
27
+ const config = {
28
+ id: edge.id,
29
+ sourceId: edge.sourceId,
30
+ targetId: edge.targetId
31
+ };
32
+ if (edge.label !== "") config.label = edge.label;
33
+ if (edge.data !== void 0) config.data = edge.data;
34
+ if (edge.x !== void 0) config.x = edge.x;
35
+ if (edge.y !== void 0) config.y = edge.y;
36
+ if (edge.width !== void 0) config.width = edge.width;
37
+ if (edge.height !== void 0) config.height = edge.height;
38
+ if (edge.color !== void 0) config.color = edge.color;
39
+ if (edge.style !== void 0) config.style = edge.style;
40
+ return config;
41
+ }
42
+ /** Shallow-compare two values, returning true if they differ. */
43
+ function differs(a, b) {
44
+ if (a === b) return false;
45
+ if (a == null || b == null) return a !== b;
46
+ if (typeof a === "object" && typeof b === "object") return JSON.stringify(a) !== JSON.stringify(b);
47
+ return true;
48
+ }
49
+ const NODE_COMPARE_KEYS = [
50
+ "parentId",
51
+ "initialNodeId",
52
+ "label",
53
+ "data",
54
+ "x",
55
+ "y",
56
+ "width",
57
+ "height",
58
+ "shape",
59
+ "color",
60
+ "style"
61
+ ];
62
+ const EDGE_COMPARE_KEYS = [
63
+ "sourceId",
64
+ "targetId",
65
+ "label",
66
+ "data",
67
+ "x",
68
+ "y",
69
+ "width",
70
+ "height",
71
+ "color",
72
+ "style"
73
+ ];
74
+ /** Compute a structured diff from graph `a` to graph `b` by matching IDs. */
75
+ function getDiff(a, b) {
76
+ const aNodeMap = new Map(a.nodes.map((n) => [n.id, n]));
77
+ const bNodeMap = new Map(b.nodes.map((n) => [n.id, n]));
78
+ const aEdgeMap = new Map(a.edges.map((e) => [e.id, e]));
79
+ const bEdgeMap = new Map(b.edges.map((e) => [e.id, e]));
80
+ const diff = {
81
+ nodes: {
82
+ added: [],
83
+ removed: [],
84
+ updated: []
85
+ },
86
+ edges: {
87
+ added: [],
88
+ removed: [],
89
+ updated: []
90
+ }
91
+ };
92
+ for (const [id, nodeB] of bNodeMap) {
93
+ const nodeA = aNodeMap.get(id);
94
+ if (!nodeA) diff.nodes.added.push(nodeToConfig(nodeB));
95
+ else {
96
+ const oldPartial = {};
97
+ const newPartial = {};
98
+ for (const key of NODE_COMPARE_KEYS) if (differs(nodeA[key], nodeB[key])) {
99
+ oldPartial[key] = nodeA[key];
100
+ newPartial[key] = nodeB[key];
101
+ }
102
+ if (Object.keys(oldPartial).length > 0) diff.nodes.updated.push({
103
+ id,
104
+ old: oldPartial,
105
+ new: newPartial
106
+ });
107
+ }
108
+ }
109
+ for (const [id, nodeA] of aNodeMap) if (!bNodeMap.has(id)) diff.nodes.removed.push(nodeToConfig(nodeA));
110
+ for (const [id, edgeB] of bEdgeMap) {
111
+ const edgeA = aEdgeMap.get(id);
112
+ if (!edgeA) diff.edges.added.push(edgeToConfig(edgeB));
113
+ else {
114
+ const oldPartial = {};
115
+ const newPartial = {};
116
+ for (const key of EDGE_COMPARE_KEYS) if (differs(edgeA[key], edgeB[key])) {
117
+ oldPartial[key] = edgeA[key];
118
+ newPartial[key] = edgeB[key];
119
+ }
120
+ if (Object.keys(oldPartial).length > 0) diff.edges.updated.push({
121
+ id,
122
+ old: oldPartial,
123
+ new: newPartial
124
+ });
125
+ }
126
+ }
127
+ for (const [id, edgeA] of aEdgeMap) if (!bEdgeMap.has(id)) diff.edges.removed.push(edgeToConfig(edgeA));
128
+ return diff;
129
+ }
130
+ /** Check if a diff has zero changes. */
131
+ function isEmptyDiff(diff) {
132
+ return diff.nodes.added.length === 0 && diff.nodes.removed.length === 0 && diff.nodes.updated.length === 0 && diff.edges.added.length === 0 && diff.edges.removed.length === 0 && diff.edges.updated.length === 0;
133
+ }
134
+ /** Invert a diff: swap added↔removed, swap old↔new in updates. */
135
+ function invertDiff(diff) {
136
+ return {
137
+ nodes: {
138
+ added: diff.nodes.removed,
139
+ removed: diff.nodes.added,
140
+ updated: diff.nodes.updated.map((c) => ({
141
+ id: c.id,
142
+ old: c.new,
143
+ new: c.old
144
+ }))
145
+ },
146
+ edges: {
147
+ added: diff.edges.removed,
148
+ removed: diff.edges.added,
149
+ updated: diff.edges.updated.map((c) => ({
150
+ id: c.id,
151
+ old: c.new,
152
+ new: c.old
153
+ }))
154
+ }
155
+ };
156
+ }
157
+ /**
158
+ * Compute an ordered patch list from graph `a` to graph `b`.
159
+ * Order: delete edges → delete nodes → add nodes → add edges → update nodes → update edges.
160
+ */
161
+ function getPatches(a, b) {
162
+ return toPatches(getDiff(a, b));
163
+ }
164
+ /**
165
+ * **Mutable.** Apply patches to a graph in order.
166
+ * Delegates to addNode/deleteNode/updateNode/addEdge/deleteEdge/updateEdge.
167
+ */
168
+ function applyPatches(graph, patches) {
169
+ for (const patch of patches) switch (patch.op) {
170
+ case "addNode":
171
+ addNode(graph, patch.node);
172
+ break;
173
+ case "deleteNode":
174
+ deleteNode(graph, patch.id);
175
+ break;
176
+ case "updateNode":
177
+ updateNode(graph, patch.id, patch.data);
178
+ break;
179
+ case "addEdge":
180
+ addEdge(graph, patch.edge);
181
+ break;
182
+ case "deleteEdge":
183
+ deleteEdge(graph, patch.id);
184
+ break;
185
+ case "updateEdge":
186
+ updateEdge(graph, patch.id, patch.data);
187
+ break;
188
+ }
189
+ }
190
+ /**
191
+ * Flatten a structured diff into an ordered patch list.
192
+ * Order: add nodes → update edges → delete edges → delete nodes → add edges → update nodes.
193
+ * This avoids cascading deletes removing edges that are being updated,
194
+ * and ensures new nodes exist before edges reference them.
195
+ */
196
+ function toPatches(diff) {
197
+ const patches = [];
198
+ for (const node of diff.nodes.added) patches.push({
199
+ op: "addNode",
200
+ node
201
+ });
202
+ for (const change of diff.edges.updated) {
203
+ const data = {};
204
+ for (const [key, value] of Object.entries(change.new)) data[key] = value;
205
+ patches.push({
206
+ op: "updateEdge",
207
+ id: change.id,
208
+ data
209
+ });
210
+ }
211
+ for (const edge of diff.edges.removed) patches.push({
212
+ op: "deleteEdge",
213
+ id: edge.id
214
+ });
215
+ for (const node of diff.nodes.removed) patches.push({
216
+ op: "deleteNode",
217
+ id: node.id
218
+ });
219
+ for (const edge of diff.edges.added) patches.push({
220
+ op: "addEdge",
221
+ edge
222
+ });
223
+ for (const change of diff.nodes.updated) {
224
+ const data = {};
225
+ for (const [key, value] of Object.entries(change.new)) data[key] = value;
226
+ patches.push({
227
+ op: "updateNode",
228
+ id: change.id,
229
+ data
230
+ });
231
+ }
232
+ return patches;
233
+ }
234
+ /** Group a patch list into a structured diff. */
235
+ function toDiff(patches) {
236
+ const diff = {
237
+ nodes: {
238
+ added: [],
239
+ removed: [],
240
+ updated: []
241
+ },
242
+ edges: {
243
+ added: [],
244
+ removed: [],
245
+ updated: []
246
+ }
247
+ };
248
+ for (const patch of patches) switch (patch.op) {
249
+ case "addNode":
250
+ diff.nodes.added.push(patch.node);
251
+ break;
252
+ case "deleteNode":
253
+ diff.nodes.removed.push({ id: patch.id });
254
+ break;
255
+ case "updateNode": {
256
+ const newPartial = {};
257
+ for (const [key, value] of Object.entries(patch.data)) newPartial[key] = value;
258
+ diff.nodes.updated.push({
259
+ id: patch.id,
260
+ old: {},
261
+ new: newPartial
262
+ });
263
+ break;
264
+ }
265
+ case "addEdge":
266
+ diff.edges.added.push(patch.edge);
267
+ break;
268
+ case "deleteEdge":
269
+ diff.edges.removed.push({ id: patch.id });
270
+ break;
271
+ case "updateEdge": {
272
+ const newPartial = {};
273
+ for (const [key, value] of Object.entries(patch.data)) newPartial[key] = value;
274
+ diff.edges.updated.push({
275
+ id: patch.id,
276
+ old: {},
277
+ new: newPartial
278
+ });
279
+ break;
280
+ }
281
+ }
282
+ return diff;
283
+ }
284
+
285
+ //#endregion
286
+ //#region src/transforms.ts
287
+ /**
288
+ * Flattens a hierarchical graph into a flat graph with only leaf nodes.
289
+ *
290
+ * - Edges targeting a compound node resolve to its initial child (recursively).
291
+ * - Edges originating from a compound node expand to all leaf descendants.
292
+ * - Only leaf nodes (nodes with no children) appear in the result.
293
+ * - Duplicate edges (same source + target) are deduplicated.
294
+ */
295
+ function flatten(graph) {
296
+ const idx = getIndex(graph);
297
+ const leaves = /* @__PURE__ */ new Set();
298
+ for (const node of graph.nodes) if ((idx.childNodes.get(node.id) ?? []).length === 0) leaves.add(node.id);
299
+ function resolveInitial(nodeId) {
300
+ if (leaves.has(nodeId)) return nodeId;
301
+ const ni = idx.nodeById.get(nodeId);
302
+ if (ni === void 0) return null;
303
+ const node = graph.nodes[ni];
304
+ if (node.initialNodeId) return resolveInitial(node.initialNodeId);
305
+ const childIds = idx.childNodes.get(nodeId) ?? [];
306
+ if (childIds.length > 0) return resolveInitial(childIds[0]);
307
+ return nodeId;
308
+ }
309
+ function getLeafDescendants(nodeId) {
310
+ if (leaves.has(nodeId)) return [nodeId];
311
+ const result = [];
312
+ const collect = (id) => {
313
+ const childIds = idx.childNodes.get(id) ?? [];
314
+ for (const childId of childIds) if (leaves.has(childId)) result.push(childId);
315
+ else collect(childId);
316
+ };
317
+ collect(nodeId);
318
+ return result;
319
+ }
320
+ const edgeSeen = /* @__PURE__ */ new Set();
321
+ const flatEdges = [];
322
+ for (const edge of graph.edges) {
323
+ const sources = leaves.has(edge.sourceId) ? [edge.sourceId] : getLeafDescendants(edge.sourceId);
324
+ const target = leaves.has(edge.targetId) ? edge.targetId : resolveInitial(edge.targetId);
325
+ if (target === null) continue;
326
+ for (const source of sources) {
327
+ if (source === target) continue;
328
+ const key = `${source}->${target}`;
329
+ if (edgeSeen.has(key)) continue;
330
+ edgeSeen.add(key);
331
+ flatEdges.push({
332
+ type: "edge",
333
+ id: `${edge.id}:${source}->${target}`,
334
+ sourceId: source,
335
+ targetId: target,
336
+ label: edge.label,
337
+ data: edge.data
338
+ });
339
+ }
340
+ }
341
+ const leafNodes = graph.nodes.filter((n) => leaves.has(n.id)).map((n) => ({
342
+ id: n.id,
343
+ label: n.label,
344
+ data: n.data
345
+ }));
346
+ return createGraph({
347
+ id: graph.id,
348
+ type: graph.type,
349
+ nodes: leafNodes,
350
+ edges: flatEdges,
351
+ data: graph.data
352
+ });
353
+ }
354
+
355
+ //#endregion
356
+ export { EdgeSchema, GraphInstance, GraphSchema, NodeSchema, addEdge, addEntities, addNode, applyPatches, bfs, createGraph, createVisualGraph, deleteEdge, deleteEntities, deleteNode, dfs, flatten, fromAdjacencyList, fromEdgeList, fromGraphML, 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, getRoots, getShortestPath, getShortestPaths, getSiblings, getSimplePath, getSimplePaths, getSinks, getSources, getStronglyConnectedComponents, getSuccessors, getTopologicalSort, hasEdge, hasNode, hasPath, invalidateIndex, invertDiff, isAcyclic, isCompound, isConnected, isEmptyDiff, isLeaf, isTree, toAdjacencyList, toDOT, toDiff, toEdgeList, toGraphML, toPatches, updateEdge, updateEntities, updateNode };
@@ -0,0 +1,93 @@
1
+ //#region src/indexing.ts
2
+ const indexes = /* @__PURE__ */ new WeakMap();
3
+ /** Get or lazily build the index for a graph. */
4
+ function getIndex(graph) {
5
+ let idx = indexes.get(graph);
6
+ if (!idx || idx.nodeCount !== graph.nodes.length || idx.edgeCount !== graph.edges.length) {
7
+ idx = buildIndex(graph);
8
+ indexes.set(graph, idx);
9
+ }
10
+ return idx;
11
+ }
12
+ /** Clear the cached index. Call this if you mutate graph.nodes/edges directly. */
13
+ function invalidateIndex(graph) {
14
+ indexes.delete(graph);
15
+ }
16
+ function buildIndex(graph) {
17
+ const nodeById = /* @__PURE__ */ new Map();
18
+ const edgeById = /* @__PURE__ */ new Map();
19
+ const outEdges = /* @__PURE__ */ new Map();
20
+ const inEdges = /* @__PURE__ */ new Map();
21
+ const childNodes = /* @__PURE__ */ new Map();
22
+ for (let i = 0; i < graph.nodes.length; i++) {
23
+ const n = graph.nodes[i];
24
+ nodeById.set(n.id, i);
25
+ outEdges.set(n.id, []);
26
+ inEdges.set(n.id, []);
27
+ const parent = n.parentId;
28
+ if (!childNodes.has(parent)) childNodes.set(parent, []);
29
+ childNodes.get(parent).push(n.id);
30
+ }
31
+ for (let i = 0; i < graph.edges.length; i++) {
32
+ const e = graph.edges[i];
33
+ edgeById.set(e.id, i);
34
+ outEdges.get(e.sourceId)?.push(e.id);
35
+ inEdges.get(e.targetId)?.push(e.id);
36
+ }
37
+ return {
38
+ nodeById,
39
+ edgeById,
40
+ outEdges,
41
+ inEdges,
42
+ childNodes,
43
+ nodeCount: graph.nodes.length,
44
+ edgeCount: graph.edges.length
45
+ };
46
+ }
47
+ function indexAddNode(idx, node, arrayIndex) {
48
+ idx.nodeById.set(node.id, arrayIndex);
49
+ idx.outEdges.set(node.id, []);
50
+ idx.inEdges.set(node.id, []);
51
+ const parent = node.parentId;
52
+ if (!idx.childNodes.has(parent)) idx.childNodes.set(parent, []);
53
+ idx.childNodes.get(parent).push(node.id);
54
+ idx.nodeCount++;
55
+ }
56
+ function indexAddEdge(idx, edge, arrayIndex) {
57
+ idx.edgeById.set(edge.id, arrayIndex);
58
+ idx.outEdges.get(edge.sourceId)?.push(edge.id);
59
+ idx.inEdges.get(edge.targetId)?.push(edge.id);
60
+ idx.edgeCount++;
61
+ }
62
+ /** Update childNodes index when a node's parentId changes. */
63
+ function indexReparentNode(idx, nodeId, oldParentId, newParentId) {
64
+ const oldSiblings = idx.childNodes.get(oldParentId);
65
+ if (oldSiblings) {
66
+ const pos = oldSiblings.indexOf(nodeId);
67
+ if (pos !== -1) oldSiblings.splice(pos, 1);
68
+ }
69
+ if (!idx.childNodes.has(newParentId)) idx.childNodes.set(newParentId, []);
70
+ idx.childNodes.get(newParentId).push(nodeId);
71
+ }
72
+ /** Update adjacency lists when an edge's sourceId/targetId changes. */
73
+ function indexUpdateEdgeEndpoints(idx, edgeId, oldSourceId, oldTargetId, newSourceId, newTargetId) {
74
+ if (oldSourceId !== newSourceId) {
75
+ const oldOut = idx.outEdges.get(oldSourceId);
76
+ if (oldOut) {
77
+ const pos = oldOut.indexOf(edgeId);
78
+ if (pos !== -1) oldOut.splice(pos, 1);
79
+ }
80
+ idx.outEdges.get(newSourceId)?.push(edgeId);
81
+ }
82
+ if (oldTargetId !== newTargetId) {
83
+ const oldIn = idx.inEdges.get(oldTargetId);
84
+ if (oldIn) {
85
+ const pos = oldIn.indexOf(edgeId);
86
+ if (pos !== -1) oldIn.splice(pos, 1);
87
+ }
88
+ idx.inEdges.get(newTargetId)?.push(edgeId);
89
+ }
90
+ }
91
+
92
+ //#endregion
93
+ export { indexUpdateEdgeEndpoints as a, indexReparentNode as i, indexAddEdge as n, invalidateIndex as o, indexAddNode as r, getIndex as t };
@@ -0,0 +1,37 @@
1
+ import { c as Graph, d as GraphEdge, f as GraphNode } from "./types-XV3S5Jnh.mjs";
2
+
3
+ //#region src/queries.d.ts
4
+ declare function getEdgesOf<E>(graph: Graph<any, E>, nodeId: string): GraphEdge<E>[];
5
+ declare function getInEdges<E>(graph: Graph<any, E>, nodeId: string): GraphEdge<E>[];
6
+ declare function getOutEdges<E>(graph: Graph<any, E>, nodeId: string): GraphEdge<E>[];
7
+ declare function getEdgeBetween<E>(graph: Graph<any, E>, sourceId: string, targetId: string): GraphEdge<E> | undefined;
8
+ declare function getSuccessors<N>(graph: Graph<N>, nodeId: string): GraphNode<N>[];
9
+ declare function getPredecessors<N>(graph: Graph<N>, nodeId: string): GraphNode<N>[];
10
+ declare function getNeighbors<N>(graph: Graph<N>, nodeId: string): GraphNode<N>[];
11
+ declare function getDegree(graph: Graph, nodeId: string): number;
12
+ declare function getInDegree(graph: Graph, nodeId: string): number;
13
+ declare function getOutDegree(graph: Graph, nodeId: string): number;
14
+ declare function getChildren<N>(graph: Graph<N>, nodeId: string | null): GraphNode<N>[];
15
+ declare function getParent<N>(graph: Graph<N>, nodeId: string): GraphNode<N> | undefined;
16
+ declare function getAncestors<N>(graph: Graph<N>, nodeId: string): GraphNode<N>[];
17
+ declare function getDescendants<N>(graph: Graph<N>, nodeId: string): GraphNode<N>[];
18
+ declare function getRoots<N>(graph: Graph<N>): GraphNode<N>[];
19
+ /** Whether a node has children (is a compound/group node). */
20
+ declare function isCompound(graph: Graph, nodeId: string): boolean;
21
+ /** Whether a node has no children (is a leaf/atomic node). */
22
+ declare function isLeaf(graph: Graph, nodeId: string): boolean;
23
+ /** Depth of a node in the hierarchy (root = 0). */
24
+ declare function getDepth(graph: Graph, nodeId: string): number;
25
+ /** Sibling nodes (same parentId, excluding the node itself). */
26
+ declare function getSiblings<N>(graph: Graph<N>, nodeId: string): GraphNode<N>[];
27
+ /**
28
+ * Least Common Ancestor — deepest proper ancestor of all given nodes.
29
+ * A proper ancestor excludes the input nodes themselves.
30
+ */
31
+ declare function getLCA<N>(graph: Graph<N>, ...nodeIds: string[]): GraphNode<N> | undefined;
32
+ /** Nodes with no incoming edges (inDegree 0). */
33
+ declare function getSources<N>(graph: Graph<N>): GraphNode<N>[];
34
+ /** Nodes with no outgoing edges (outDegree 0). */
35
+ declare function getSinks<N>(graph: Graph<N>): GraphNode<N>[];
36
+ //#endregion
37
+ export { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdgeBetween, getEdgesOf, getInDegree, getInEdges, getLCA, getNeighbors, getOutDegree, getOutEdges, getParent, getPredecessors, getRoots, getSiblings, getSinks, getSources, getSuccessors, isCompound, isLeaf };