@statelyai/graph 0.12.0 → 0.13.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 +23 -15
- package/dist/{algorithms-BHHg7lGq.mjs → algorithms-BNDQcHU3.mjs} +7 -7
- package/dist/algorithms.mjs +1 -1
- package/dist/format-support.d.mts +6 -0
- package/dist/format-support.mjs +38 -32
- package/dist/formats/cytoscape/index.mjs +16 -0
- package/dist/formats/d3/index.d.mts +7 -0
- package/dist/formats/d3/index.mjs +34 -6
- package/dist/formats/elk/index.mjs +86 -23
- package/dist/formats/gexf/index.mjs +93 -1
- package/dist/formats/gml/index.mjs +40 -13
- package/dist/formats/jgf/index.mjs +16 -0
- package/dist/formats/mermaid/index.d.mts +1 -0
- package/dist/formats/mermaid/index.mjs +39 -9
- package/dist/formats/xyflow/index.d.mts +1 -0
- package/dist/formats/xyflow/index.mjs +97 -40
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{indexing-CJc-ul8e.mjs → indexing-DUl3kTqm.mjs} +18 -4
- package/dist/queries.mjs +1 -1
- package/dist/schemas.d.mts +8 -7
- package/dist/schemas.mjs +127 -4
- package/package.json +1 -1
- package/schemas/graph.schema.json +1 -0
- package/schemas/node.schema.json +1 -0
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
//#region src/formats/xyflow/index.ts
|
|
2
|
+
const STATELYAI_METADATA_KEY = "__statelyai";
|
|
3
|
+
function toDataObject(value) {
|
|
4
|
+
return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
5
|
+
}
|
|
6
|
+
function withMetadata(value, metadata) {
|
|
7
|
+
return {
|
|
8
|
+
...toDataObject(value),
|
|
9
|
+
[STATELYAI_METADATA_KEY]: {
|
|
10
|
+
...value !== void 0 && { data: value },
|
|
11
|
+
...metadata
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function readMetadata(value) {
|
|
16
|
+
if (!value || typeof value !== "object") return void 0;
|
|
17
|
+
const metadata = value[STATELYAI_METADATA_KEY];
|
|
18
|
+
return metadata && typeof metadata === "object" ? metadata : void 0;
|
|
19
|
+
}
|
|
20
|
+
function readUserData(value) {
|
|
21
|
+
const metadata = readMetadata(value);
|
|
22
|
+
if (metadata && "data" in metadata) return metadata.data;
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
2
25
|
/**
|
|
3
26
|
* Converts a visual graph to xyflow (React Flow / Svelte Flow) format.
|
|
4
27
|
*
|
|
@@ -21,6 +44,14 @@
|
|
|
21
44
|
*/
|
|
22
45
|
function toXYFlow(graph) {
|
|
23
46
|
return {
|
|
47
|
+
data: { [STATELYAI_METADATA_KEY]: { graph: {
|
|
48
|
+
id: graph.id,
|
|
49
|
+
type: graph.type,
|
|
50
|
+
initialNodeId: graph.initialNodeId,
|
|
51
|
+
data: graph.data,
|
|
52
|
+
direction: graph.direction,
|
|
53
|
+
style: graph.style
|
|
54
|
+
} } },
|
|
24
55
|
nodes: graph.nodes.map((n) => {
|
|
25
56
|
const node = {
|
|
26
57
|
id: n.id,
|
|
@@ -28,12 +59,18 @@ function toXYFlow(graph) {
|
|
|
28
59
|
x: n.x,
|
|
29
60
|
y: n.y
|
|
30
61
|
},
|
|
31
|
-
data: n.data
|
|
62
|
+
data: withMetadata(n.data, { node: {
|
|
63
|
+
initialNodeId: n.initialNodeId,
|
|
64
|
+
label: n.label,
|
|
65
|
+
color: n.color,
|
|
66
|
+
style: n.style,
|
|
67
|
+
ports: n.ports
|
|
68
|
+
} })
|
|
32
69
|
};
|
|
33
70
|
if (n.parentId) node.parentId = n.parentId;
|
|
34
71
|
if (n.shape) node.type = n.shape;
|
|
35
|
-
if (n.width) node.width = n.width;
|
|
36
|
-
if (n.height) node.height = n.height;
|
|
72
|
+
if (n.width !== void 0) node.width = n.width;
|
|
73
|
+
if (n.height !== void 0) node.height = n.height;
|
|
37
74
|
return node;
|
|
38
75
|
}),
|
|
39
76
|
edges: graph.edges.map((e) => {
|
|
@@ -44,11 +81,17 @@ function toXYFlow(graph) {
|
|
|
44
81
|
};
|
|
45
82
|
if (e.sourcePort) edge.sourceHandle = e.sourcePort;
|
|
46
83
|
if (e.targetPort) edge.targetHandle = e.targetPort;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
84
|
+
edge.data = withMetadata(e.data, { edge: {
|
|
85
|
+
label: e.label,
|
|
86
|
+
weight: e.weight,
|
|
87
|
+
color: e.color,
|
|
88
|
+
style: e.style,
|
|
89
|
+
x: e.x,
|
|
90
|
+
y: e.y,
|
|
91
|
+
width: e.width,
|
|
92
|
+
height: e.height
|
|
93
|
+
} });
|
|
94
|
+
if (e.label) edge.data.label = e.label;
|
|
52
95
|
return edge;
|
|
53
96
|
})
|
|
54
97
|
};
|
|
@@ -73,39 +116,53 @@ function fromXYFlow(flow) {
|
|
|
73
116
|
if (!flow || typeof flow !== "object") throw new Error("XYFlow: expected an object");
|
|
74
117
|
if (!Array.isArray(flow.nodes)) throw new Error("XYFlow: \"nodes\" must be an array");
|
|
75
118
|
if (!Array.isArray(flow.edges)) throw new Error("XYFlow: \"edges\" must be an array");
|
|
119
|
+
const graphMetadata = readMetadata(flow.data)?.graph;
|
|
76
120
|
return {
|
|
77
|
-
id: "",
|
|
78
|
-
type: "directed",
|
|
79
|
-
initialNodeId: null,
|
|
80
|
-
data: void 0,
|
|
81
|
-
direction: "down",
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
121
|
+
id: graphMetadata?.id?.toString() ?? "",
|
|
122
|
+
type: graphMetadata?.type === "undirected" ? "undirected" : "directed",
|
|
123
|
+
initialNodeId: graphMetadata && "initialNodeId" in graphMetadata ? graphMetadata.initialNodeId : null,
|
|
124
|
+
data: graphMetadata && "data" in graphMetadata ? graphMetadata.data : void 0,
|
|
125
|
+
direction: graphMetadata?.direction ?? "down",
|
|
126
|
+
...graphMetadata?.style !== void 0 && { style: graphMetadata.style },
|
|
127
|
+
nodes: flow.nodes.map((n) => {
|
|
128
|
+
const metadata = readMetadata(n.data)?.node;
|
|
129
|
+
return {
|
|
130
|
+
type: "node",
|
|
131
|
+
id: n.id,
|
|
132
|
+
parentId: n.parentId ?? null,
|
|
133
|
+
initialNodeId: metadata && "initialNodeId" in metadata ? metadata.initialNodeId : null,
|
|
134
|
+
label: metadata && "label" in metadata ? metadata.label : "",
|
|
135
|
+
data: readUserData(n.data),
|
|
136
|
+
x: n.position.x,
|
|
137
|
+
y: n.position.y,
|
|
138
|
+
width: n.measured?.width ?? n.width ?? n.initialWidth ?? 0,
|
|
139
|
+
height: n.measured?.height ?? n.height ?? n.initialHeight ?? 0,
|
|
140
|
+
...n.type && { shape: n.type },
|
|
141
|
+
...metadata?.color !== void 0 && { color: metadata.color },
|
|
142
|
+
...metadata?.style !== void 0 && { style: metadata.style },
|
|
143
|
+
...metadata?.ports !== void 0 && { ports: metadata.ports }
|
|
144
|
+
};
|
|
145
|
+
}),
|
|
146
|
+
edges: flow.edges.map((e, i) => {
|
|
147
|
+
const metadata = readMetadata(e.data)?.edge;
|
|
148
|
+
return {
|
|
149
|
+
type: "edge",
|
|
150
|
+
id: e.id ?? `e${i}`,
|
|
151
|
+
sourceId: e.source,
|
|
152
|
+
targetId: e.target,
|
|
153
|
+
label: metadata && "label" in metadata ? metadata.label : e.data?.label?.toString() ?? "",
|
|
154
|
+
...e.sourceHandle && { sourcePort: e.sourceHandle },
|
|
155
|
+
...e.targetHandle && { targetPort: e.targetHandle },
|
|
156
|
+
data: readUserData(e.data),
|
|
157
|
+
x: metadata?.x ?? 0,
|
|
158
|
+
y: metadata?.y ?? 0,
|
|
159
|
+
width: metadata?.width ?? 0,
|
|
160
|
+
height: metadata?.height ?? 0,
|
|
161
|
+
...metadata?.weight !== void 0 && { weight: metadata.weight },
|
|
162
|
+
...metadata?.color !== void 0 && { color: metadata.color },
|
|
163
|
+
...metadata?.style !== void 0 && { style: metadata.style }
|
|
164
|
+
};
|
|
165
|
+
})
|
|
109
166
|
};
|
|
110
167
|
}
|
|
111
168
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -11,7 +11,7 @@ import { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdge
|
|
|
11
11
|
* @example
|
|
12
12
|
* ```ts
|
|
13
13
|
* const port = createGraphPort({ name: 'output', direction: 'out' });
|
|
14
|
-
* // { name: 'output', direction: 'out', data:
|
|
14
|
+
* // { name: 'output', direction: 'out', data: null }
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
17
|
declare function createGraphPort<P = any>(config: PortConfig<P>): GraphPort<P>;
|
|
@@ -31,7 +31,7 @@ declare function createGraphNode<N = any, P = any>(config: NodeConfig<N, P>): Gr
|
|
|
31
31
|
* @example
|
|
32
32
|
* ```ts
|
|
33
33
|
* const edge = createGraphEdge({ id: 'e1', sourceId: 'a', targetId: 'b' });
|
|
34
|
-
* // { type: 'edge', id: 'e1', sourceId: 'a', targetId: 'b', label: null, data:
|
|
34
|
+
* // { type: 'edge', id: 'e1', sourceId: 'a', targetId: 'b', label: null, data: null }
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
37
|
declare function createGraphEdge<T = any>(config: EdgeConfig<T>): GraphEdge<T>;
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as invalidateIndex, t as getIndex } from "./indexing-
|
|
2
|
-
import { $ as createGraphPort, A as isAcyclic, B as getShortestPaths, C as getPreorder, D as getConnectedComponents, E as dfs, F as genSimplePaths, G as GraphInstance, H as getSimplePaths, I as getAStarPath, J as addNode, K as addEdge, L as getAllPairsShortestPaths, M as isTree, N as genCycles, O as getTopologicalSort, P as genShortestPaths, Q as createGraphNode, R as getCycles, S as getPostorders, T as bfs, U as getStronglyConnectedComponents, V as getSimplePath, W as joinPaths, X as createGraphEdge, Y as createGraph, Z as createGraphFromTransition, _ as getPageRank, a as genGirvanNewmanCommunities, at as getNode, b as genPreorders, c as getLabelPropagationCommunities, ct as updateEdge, d as getClosenessCentrality, et as createVisualGraph, f as getDegreeCentrality, g as getOutDegreeCentrality, h as getInDegreeCentrality, i as getBridges, it as getEdge, j as isConnected, k as hasPath, l as getModularity, lt as updateEntities, m as getHITS, n as getArticulationPoints, nt as deleteEntities, o as getGirvanNewmanCommunities, ot as hasEdge, p as getEigenvectorCentrality, q as addEntities, r as getBiconnectedComponents, rt as deleteNode, s as getGreedyModularityCommunities, st as hasNode, t as isIsomorphic, tt as deleteEdge, u as getBetweennessCentrality, ut as updateNode, v as getMinimumSpanningTree, w as getPreorders, x as getPostorder, y as genPostorders, z as getShortestPath } from "./algorithms-
|
|
1
|
+
import { o as invalidateIndex, t as getIndex } from "./indexing-DUl3kTqm.mjs";
|
|
2
|
+
import { $ as createGraphPort, A as isAcyclic, B as getShortestPaths, C as getPreorder, D as getConnectedComponents, E as dfs, F as genSimplePaths, G as GraphInstance, H as getSimplePaths, I as getAStarPath, J as addNode, K as addEdge, L as getAllPairsShortestPaths, M as isTree, N as genCycles, O as getTopologicalSort, P as genShortestPaths, Q as createGraphNode, R as getCycles, S as getPostorders, T as bfs, U as getStronglyConnectedComponents, V as getSimplePath, W as joinPaths, X as createGraphEdge, Y as createGraph, Z as createGraphFromTransition, _ as getPageRank, a as genGirvanNewmanCommunities, at as getNode, b as genPreorders, c as getLabelPropagationCommunities, ct as updateEdge, d as getClosenessCentrality, et as createVisualGraph, f as getDegreeCentrality, g as getOutDegreeCentrality, h as getInDegreeCentrality, i as getBridges, it as getEdge, j as isConnected, k as hasPath, l as getModularity, lt as updateEntities, m as getHITS, n as getArticulationPoints, nt as deleteEntities, o as getGirvanNewmanCommunities, ot as hasEdge, p as getEigenvectorCentrality, q as addEntities, r as getBiconnectedComponents, rt as deleteNode, s as getGreedyModularityCommunities, st as hasNode, t as isIsomorphic, tt as deleteEdge, u as getBetweennessCentrality, ut as updateNode, v as getMinimumSpanningTree, w as getPreorders, x as getPostorder, y as genPostorders, z as getShortestPath } from "./algorithms-BNDQcHU3.mjs";
|
|
3
3
|
import { getAncestors, getChildren, getDegree, getDepth, getDescendants, getEdgesBetween, getEdgesByPort, getEdgesOf, getInDegree, getInEdges, getLCA, getNeighbors, getOutDegree, getOutEdges, getParent, getPort, getPorts, getPredecessors, getRelativeDistance, getRelativeDistanceMap, getRoots, getSiblings, getSinks, getSources, getSuccessors, isCompound, isLeaf } from "./queries.mjs";
|
|
4
4
|
import { n as createFormatConverter } from "./converter-Dspillnn.mjs";
|
|
5
5
|
|
|
@@ -20,8 +20,10 @@ const indexes = /* @__PURE__ */ new WeakMap();
|
|
|
20
20
|
*/
|
|
21
21
|
function getIndex(graph) {
|
|
22
22
|
let idx = indexes.get(graph);
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const sameStructure = idx && idx.nodeCount === graph.nodes.length && idx.edgeCount === graph.edges.length;
|
|
24
|
+
const signature = idx !== void 0 && sameStructure && idx.nodesRef === graph.nodes && idx.edgesRef === graph.edges ? getIndexSignature(graph) : void 0;
|
|
25
|
+
if (!idx || idx.nodeCount !== graph.nodes.length || idx.edgeCount !== graph.edges.length || signature !== void 0 && idx.signature !== signature) {
|
|
26
|
+
idx = buildIndex(graph, signature ?? getIndexSignature(graph));
|
|
25
27
|
indexes.set(graph, idx);
|
|
26
28
|
}
|
|
27
29
|
return idx;
|
|
@@ -42,7 +44,12 @@ function getIndex(graph) {
|
|
|
42
44
|
function invalidateIndex(graph) {
|
|
43
45
|
indexes.delete(graph);
|
|
44
46
|
}
|
|
45
|
-
function
|
|
47
|
+
function getIndexSignature(graph) {
|
|
48
|
+
const nodeParts = graph.nodes.map((node) => `${node.id}\u0000${node.parentId ?? ""}`);
|
|
49
|
+
const edgeParts = graph.edges.map((edge) => `${edge.id}\u0000${edge.sourceId}\u0000${edge.targetId}`);
|
|
50
|
+
return `${nodeParts.join("")}\u0002${edgeParts.join("")}`;
|
|
51
|
+
}
|
|
52
|
+
function buildIndex(graph, signature) {
|
|
46
53
|
const nodeById = /* @__PURE__ */ new Map();
|
|
47
54
|
const edgeById = /* @__PURE__ */ new Map();
|
|
48
55
|
const outEdges = /* @__PURE__ */ new Map();
|
|
@@ -70,7 +77,10 @@ function buildIndex(graph) {
|
|
|
70
77
|
inEdges,
|
|
71
78
|
childNodes,
|
|
72
79
|
nodeCount: graph.nodes.length,
|
|
73
|
-
edgeCount: graph.edges.length
|
|
80
|
+
edgeCount: graph.edges.length,
|
|
81
|
+
signature,
|
|
82
|
+
nodesRef: graph.nodes,
|
|
83
|
+
edgesRef: graph.edges
|
|
74
84
|
};
|
|
75
85
|
}
|
|
76
86
|
function indexAddNode(idx, node, arrayIndex) {
|
|
@@ -81,12 +91,14 @@ function indexAddNode(idx, node, arrayIndex) {
|
|
|
81
91
|
if (!idx.childNodes.has(parent)) idx.childNodes.set(parent, []);
|
|
82
92
|
idx.childNodes.get(parent).push(node.id);
|
|
83
93
|
idx.nodeCount++;
|
|
94
|
+
idx.signature = "";
|
|
84
95
|
}
|
|
85
96
|
function indexAddEdge(idx, edge, arrayIndex) {
|
|
86
97
|
idx.edgeById.set(edge.id, arrayIndex);
|
|
87
98
|
idx.outEdges.get(edge.sourceId)?.push(edge.id);
|
|
88
99
|
idx.inEdges.get(edge.targetId)?.push(edge.id);
|
|
89
100
|
idx.edgeCount++;
|
|
101
|
+
idx.signature = "";
|
|
90
102
|
}
|
|
91
103
|
/** Update childNodes index when a node's parentId changes. */
|
|
92
104
|
function indexReparentNode(idx, nodeId, oldParentId, newParentId) {
|
|
@@ -98,6 +110,7 @@ function indexReparentNode(idx, nodeId, oldParentId, newParentId) {
|
|
|
98
110
|
const np = newParentId ?? null;
|
|
99
111
|
if (!idx.childNodes.has(np)) idx.childNodes.set(np, []);
|
|
100
112
|
idx.childNodes.get(np).push(nodeId);
|
|
113
|
+
idx.signature = "";
|
|
101
114
|
}
|
|
102
115
|
/** Update adjacency lists when an edge's sourceId/targetId changes. */
|
|
103
116
|
function indexUpdateEdgeEndpoints(idx, edgeId, oldSourceId, oldTargetId, newSourceId, newTargetId) {
|
|
@@ -117,6 +130,7 @@ function indexUpdateEdgeEndpoints(idx, edgeId, oldSourceId, oldTargetId, newSour
|
|
|
117
130
|
}
|
|
118
131
|
idx.inEdges.get(newTargetId)?.push(edgeId);
|
|
119
132
|
}
|
|
133
|
+
idx.signature = "";
|
|
120
134
|
}
|
|
121
135
|
|
|
122
136
|
//#endregion
|
package/dist/queries.mjs
CHANGED
package/dist/schemas.d.mts
CHANGED
|
@@ -4,11 +4,11 @@ import * as z from "zod";
|
|
|
4
4
|
//#region src/schemas.d.ts
|
|
5
5
|
declare const PortSchema: z.ZodObject<{
|
|
6
6
|
name: z.ZodString;
|
|
7
|
-
direction: z.
|
|
7
|
+
direction: z.ZodEnum<{
|
|
8
8
|
in: "in";
|
|
9
9
|
out: "out";
|
|
10
10
|
inout: "inout";
|
|
11
|
-
}
|
|
11
|
+
}>;
|
|
12
12
|
label: z.ZodOptional<z.ZodString>;
|
|
13
13
|
data: z.ZodAny;
|
|
14
14
|
x: z.ZodOptional<z.ZodNumber>;
|
|
@@ -33,11 +33,11 @@ declare const NodeSchema: z.ZodObject<{
|
|
|
33
33
|
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
34
34
|
ports: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
35
35
|
name: z.ZodString;
|
|
36
|
-
direction: z.
|
|
36
|
+
direction: z.ZodEnum<{
|
|
37
37
|
in: "in";
|
|
38
38
|
out: "out";
|
|
39
39
|
inout: "inout";
|
|
40
|
-
}
|
|
40
|
+
}>;
|
|
41
41
|
label: z.ZodOptional<z.ZodString>;
|
|
42
42
|
data: z.ZodAny;
|
|
43
43
|
x: z.ZodOptional<z.ZodNumber>;
|
|
@@ -87,11 +87,11 @@ declare const GraphSchema: z.ZodObject<{
|
|
|
87
87
|
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
88
88
|
ports: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
89
89
|
name: z.ZodString;
|
|
90
|
-
direction: z.
|
|
90
|
+
direction: z.ZodEnum<{
|
|
91
91
|
in: "in";
|
|
92
92
|
out: "out";
|
|
93
93
|
inout: "inout";
|
|
94
|
-
}
|
|
94
|
+
}>;
|
|
95
95
|
label: z.ZodOptional<z.ZodString>;
|
|
96
96
|
data: z.ZodAny;
|
|
97
97
|
x: z.ZodOptional<z.ZodNumber>;
|
|
@@ -140,5 +140,6 @@ declare function getGraphPortIssues(value: unknown): GraphValidationIssue[];
|
|
|
140
140
|
declare function getGraphNodeIssues(value: unknown): GraphValidationIssue[];
|
|
141
141
|
declare function getGraphEdgeIssues(value: unknown): GraphValidationIssue[];
|
|
142
142
|
declare function getGraphIssues(value: unknown): GraphValidationIssue[];
|
|
143
|
+
declare function validateGraph(value: unknown): GraphValidationIssue[];
|
|
143
144
|
//#endregion
|
|
144
|
-
export { EdgeSchema, GraphSchema, GraphValidationIssue, NodeSchema, PortSchema, getGraphEdgeIssues, getGraphIssues, getGraphNodeIssues, getGraphPortIssues, isGraph, isGraphEdge, isGraphNode, isGraphPort };
|
|
145
|
+
export { EdgeSchema, GraphSchema, GraphValidationIssue, NodeSchema, PortSchema, getGraphEdgeIssues, getGraphIssues, getGraphNodeIssues, getGraphPortIssues, isGraph, isGraphEdge, isGraphNode, isGraphPort, validateGraph };
|
package/dist/schemas.mjs
CHANGED
|
@@ -9,7 +9,7 @@ const PortDirectionSchema = z.enum([
|
|
|
9
9
|
]);
|
|
10
10
|
const PortSchema = z.object({
|
|
11
11
|
name: z.string(),
|
|
12
|
-
direction: PortDirectionSchema
|
|
12
|
+
direction: PortDirectionSchema,
|
|
13
13
|
label: z.string().optional(),
|
|
14
14
|
data: z.any(),
|
|
15
15
|
x: z.number().optional(),
|
|
@@ -85,7 +85,7 @@ function isGraphEdge(value) {
|
|
|
85
85
|
return EdgeSchema.safeParse(value).success;
|
|
86
86
|
}
|
|
87
87
|
function isGraph(value) {
|
|
88
|
-
return
|
|
88
|
+
return validateGraph(value).length === 0;
|
|
89
89
|
}
|
|
90
90
|
function getGraphPortIssues(value) {
|
|
91
91
|
return getValidationIssues(PortSchema, value);
|
|
@@ -97,8 +97,131 @@ function getGraphEdgeIssues(value) {
|
|
|
97
97
|
return getValidationIssues(EdgeSchema, value);
|
|
98
98
|
}
|
|
99
99
|
function getGraphIssues(value) {
|
|
100
|
-
return
|
|
100
|
+
return validateGraph(value);
|
|
101
|
+
}
|
|
102
|
+
function createIssue(code, message, path) {
|
|
103
|
+
return {
|
|
104
|
+
code,
|
|
105
|
+
message,
|
|
106
|
+
path
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function getDuplicateIndexes(items, getKey) {
|
|
110
|
+
const indexesByKey = /* @__PURE__ */ new Map();
|
|
111
|
+
items.forEach((item, index) => {
|
|
112
|
+
const key = getKey(item);
|
|
113
|
+
if (key == null) return;
|
|
114
|
+
const indexes = indexesByKey.get(key) ?? [];
|
|
115
|
+
indexes.push(index);
|
|
116
|
+
indexesByKey.set(key, indexes);
|
|
117
|
+
});
|
|
118
|
+
for (const [key, indexes] of indexesByKey) if (indexes.length < 2) indexesByKey.delete(key);
|
|
119
|
+
return indexesByKey;
|
|
120
|
+
}
|
|
121
|
+
function getGraphInvariantIssues(graph) {
|
|
122
|
+
const issues = [];
|
|
123
|
+
const nodeIndexes = /* @__PURE__ */ new Map();
|
|
124
|
+
const nodesById = /* @__PURE__ */ new Map();
|
|
125
|
+
for (const [id, indexes] of getDuplicateIndexes(graph.nodes, (node) => node.id)) for (const index of indexes) issues.push(createIssue("duplicate_node_id", `Duplicate node id "${id}"`, [
|
|
126
|
+
"nodes",
|
|
127
|
+
index,
|
|
128
|
+
"id"
|
|
129
|
+
]));
|
|
130
|
+
for (const [id, indexes] of getDuplicateIndexes(graph.edges, (edge) => edge.id)) for (const index of indexes) issues.push(createIssue("duplicate_edge_id", `Duplicate edge id "${id}"`, [
|
|
131
|
+
"edges",
|
|
132
|
+
index,
|
|
133
|
+
"id"
|
|
134
|
+
]));
|
|
135
|
+
graph.nodes.forEach((node, index) => {
|
|
136
|
+
nodeIndexes.set(node.id, index);
|
|
137
|
+
nodesById.set(node.id, node);
|
|
138
|
+
});
|
|
139
|
+
if (graph.initialNodeId && !nodeIndexes.has(graph.initialNodeId)) issues.push(createIssue("missing_initial_node", `Initial node "${graph.initialNodeId}" does not exist`, ["initialNodeId"]));
|
|
140
|
+
graph.nodes.forEach((node, index) => {
|
|
141
|
+
if (node.id === "") issues.push(createIssue("empty_node_id", "Node id must be a non-empty string", [
|
|
142
|
+
"nodes",
|
|
143
|
+
index,
|
|
144
|
+
"id"
|
|
145
|
+
]));
|
|
146
|
+
if (node.parentId === "") issues.push(createIssue("empty_parent_id", "Node parentId must be a non-empty string", [
|
|
147
|
+
"nodes",
|
|
148
|
+
index,
|
|
149
|
+
"parentId"
|
|
150
|
+
]));
|
|
151
|
+
else if (node.parentId != null && !nodeIndexes.has(node.parentId)) issues.push(createIssue("missing_parent", `Parent node "${node.parentId}" does not exist`, [
|
|
152
|
+
"nodes",
|
|
153
|
+
index,
|
|
154
|
+
"parentId"
|
|
155
|
+
]));
|
|
156
|
+
if (node.initialNodeId && !nodeIndexes.has(node.initialNodeId)) issues.push(createIssue("missing_node_initial", `Initial node "${node.initialNodeId}" does not exist`, [
|
|
157
|
+
"nodes",
|
|
158
|
+
index,
|
|
159
|
+
"initialNodeId"
|
|
160
|
+
]));
|
|
161
|
+
for (const [name, indexes] of getDuplicateIndexes(node.ports ?? [], (port) => port.name)) for (const portIndex of indexes) issues.push(createIssue("duplicate_port_name", `Duplicate port name "${name}" on node "${node.id}"`, [
|
|
162
|
+
"nodes",
|
|
163
|
+
index,
|
|
164
|
+
"ports",
|
|
165
|
+
portIndex,
|
|
166
|
+
"name"
|
|
167
|
+
]));
|
|
168
|
+
});
|
|
169
|
+
for (const node of graph.nodes) {
|
|
170
|
+
const seen = /* @__PURE__ */ new Set();
|
|
171
|
+
let current = node.parentId;
|
|
172
|
+
while (current != null) {
|
|
173
|
+
if (current === node.id || seen.has(current)) {
|
|
174
|
+
issues.push(createIssue("parent_cycle", `Node "${node.id}" is part of a parent cycle`, [
|
|
175
|
+
"nodes",
|
|
176
|
+
nodeIndexes.get(node.id) ?? 0,
|
|
177
|
+
"parentId"
|
|
178
|
+
]));
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
seen.add(current);
|
|
182
|
+
current = nodesById.get(current)?.parentId;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
graph.edges.forEach((edge, index) => {
|
|
186
|
+
if (edge.id === "") issues.push(createIssue("empty_edge_id", "Edge id must be a non-empty string", [
|
|
187
|
+
"edges",
|
|
188
|
+
index,
|
|
189
|
+
"id"
|
|
190
|
+
]));
|
|
191
|
+
const source = nodesById.get(edge.sourceId);
|
|
192
|
+
const target = nodesById.get(edge.targetId);
|
|
193
|
+
if (!source) issues.push(createIssue("missing_source_node", `Source node "${edge.sourceId}" does not exist`, [
|
|
194
|
+
"edges",
|
|
195
|
+
index,
|
|
196
|
+
"sourceId"
|
|
197
|
+
]));
|
|
198
|
+
if (!target) issues.push(createIssue("missing_target_node", `Target node "${edge.targetId}" does not exist`, [
|
|
199
|
+
"edges",
|
|
200
|
+
index,
|
|
201
|
+
"targetId"
|
|
202
|
+
]));
|
|
203
|
+
if (source && edge.sourcePort !== void 0 && !source.ports?.some((port) => port.name === edge.sourcePort)) issues.push(createIssue("missing_source_port", `Port "${edge.sourcePort}" does not exist on source node "${edge.sourceId}"`, [
|
|
204
|
+
"edges",
|
|
205
|
+
index,
|
|
206
|
+
"sourcePort"
|
|
207
|
+
]));
|
|
208
|
+
if (target && edge.targetPort !== void 0 && !target.ports?.some((port) => port.name === edge.targetPort)) issues.push(createIssue("missing_target_port", `Port "${edge.targetPort}" does not exist on target node "${edge.targetId}"`, [
|
|
209
|
+
"edges",
|
|
210
|
+
index,
|
|
211
|
+
"targetPort"
|
|
212
|
+
]));
|
|
213
|
+
});
|
|
214
|
+
return issues;
|
|
215
|
+
}
|
|
216
|
+
function validateGraph(value) {
|
|
217
|
+
const shapeResult = GraphSchema.safeParse(value);
|
|
218
|
+
if (!shapeResult.success) return shapeResult.error.issues.map((issue) => ({
|
|
219
|
+
code: issue.code,
|
|
220
|
+
message: issue.message,
|
|
221
|
+
path: issue.path.map((segment) => typeof segment === "symbol" ? String(segment) : segment)
|
|
222
|
+
}));
|
|
223
|
+
return getGraphInvariantIssues(shapeResult.data);
|
|
101
224
|
}
|
|
102
225
|
|
|
103
226
|
//#endregion
|
|
104
|
-
export { EdgeSchema, GraphSchema, NodeSchema, PortSchema, getGraphEdgeIssues, getGraphIssues, getGraphNodeIssues, getGraphPortIssues, isGraph, isGraphEdge, isGraphNode, isGraphPort };
|
|
227
|
+
export { EdgeSchema, GraphSchema, NodeSchema, PortSchema, getGraphEdgeIssues, getGraphIssues, getGraphNodeIssues, getGraphPortIssues, isGraph, isGraphEdge, isGraphNode, isGraphPort, validateGraph };
|
package/package.json
CHANGED