@statelyai/graph 0.13.0 → 2.0.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 +57 -26
- package/dist/{adjacency-list-Ca0VjKIf.mjs → adjacency-list-GeL1Cu-L.mjs} +5 -3
- package/dist/{algorithms-BlM-qoJb.d.mts → algorithms-CsGNehct.d.mts} +137 -2
- package/dist/{algorithms-BNDQcHU3.mjs → algorithms-DF1pSQGv.mjs} +1494 -357
- package/dist/algorithms.d.mts +2 -2
- package/dist/algorithms.mjs +2 -2
- package/dist/{converter-Dspillnn.mjs → converter-DyCJJfTe.mjs} +2 -2
- package/dist/{edge-list-gKe8-iRa.mjs → edge-list-BcZ0h6zz.mjs} +1 -1
- package/dist/format-support.mjs +67 -11
- package/dist/formats/adjacency-list/index.d.mts +1 -1
- package/dist/formats/adjacency-list/index.mjs +1 -1
- package/dist/formats/converter/index.d.mts +1 -60
- package/dist/formats/converter/index.mjs +1 -1
- package/dist/formats/cytoscape/index.d.mts +1 -1
- package/dist/formats/cytoscape/index.mjs +5 -3
- package/dist/formats/d2/index.d.mts +109 -0
- package/dist/formats/d2/index.mjs +1100 -0
- package/dist/formats/d3/index.d.mts +2 -2
- package/dist/formats/d3/index.mjs +5 -3
- package/dist/formats/dot/index.d.mts +1 -1
- package/dist/formats/dot/index.mjs +24 -8
- package/dist/formats/edge-list/index.d.mts +1 -1
- package/dist/formats/edge-list/index.mjs +1 -1
- package/dist/formats/elk/index.d.mts +1 -1
- package/dist/formats/elk/index.mjs +23 -16
- package/dist/formats/gexf/index.d.mts +1 -1
- package/dist/formats/gexf/index.mjs +30 -17
- package/dist/formats/gml/index.d.mts +1 -1
- package/dist/formats/gml/index.mjs +22 -13
- package/dist/formats/graphml/index.d.mts +1 -1
- package/dist/formats/graphml/index.mjs +83 -25
- package/dist/formats/jgf/index.d.mts +1 -1
- package/dist/formats/jgf/index.mjs +6 -3
- package/dist/formats/mermaid/index.d.mts +1 -1
- package/dist/formats/mermaid/index.mjs +57 -20
- package/dist/formats/tgf/index.d.mts +1 -1
- package/dist/formats/tgf/index.mjs +2 -2
- package/dist/formats/xyflow/index.d.mts +1 -1
- package/dist/formats/xyflow/index.mjs +33 -6
- package/dist/index-D51lJnt2.d.mts +61 -0
- package/dist/index-DWmo1mIp.d.mts +697 -0
- package/dist/index.d.mts +6 -631
- package/dist/index.mjs +144 -295
- package/dist/mode-D8OnHFBk.mjs +15 -0
- package/dist/queries-BfXeTXRf.d.mts +547 -0
- package/dist/queries-KirMDR7e.mjs +980 -0
- package/dist/queries.d.mts +1 -514
- package/dist/queries.mjs +1 -766
- package/dist/schemas.d.mts +21 -10
- package/dist/schemas.mjs +35 -86
- package/dist/{types-CnZ01raw.d.mts → types-DNYdIU21.d.mts} +83 -11
- package/dist/validate-TtH-x3JV.mjs +190 -0
- package/package.json +14 -3
- package/schemas/edge.schema.json +11 -0
- package/schemas/graph.schema.json +24 -3
- package/schemas/node.schema.json +6 -0
- package/dist/indexing-DUl3kTqm.mjs +0 -137
package/dist/schemas.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as Graph, m as GraphEdge, v as GraphNode, x as GraphPort } from "./types-DNYdIU21.mjs";
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
|
|
4
4
|
//#region src/schemas.d.ts
|
|
@@ -15,7 +15,7 @@ declare const PortSchema: z.ZodObject<{
|
|
|
15
15
|
y: z.ZodOptional<z.ZodNumber>;
|
|
16
16
|
width: z.ZodOptional<z.ZodNumber>;
|
|
17
17
|
height: z.ZodOptional<z.ZodNumber>;
|
|
18
|
-
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
18
|
+
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
19
19
|
}, z.core.$strip>;
|
|
20
20
|
declare const NodeSchema: z.ZodObject<{
|
|
21
21
|
type: z.ZodLiteral<"node">;
|
|
@@ -30,7 +30,7 @@ declare const NodeSchema: z.ZodObject<{
|
|
|
30
30
|
height: z.ZodOptional<z.ZodNumber>;
|
|
31
31
|
shape: z.ZodOptional<z.ZodString>;
|
|
32
32
|
color: z.ZodOptional<z.ZodString>;
|
|
33
|
-
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
33
|
+
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
34
34
|
ports: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
35
35
|
name: z.ZodString;
|
|
36
36
|
direction: z.ZodEnum<{
|
|
@@ -44,7 +44,7 @@ declare const NodeSchema: z.ZodObject<{
|
|
|
44
44
|
y: z.ZodOptional<z.ZodNumber>;
|
|
45
45
|
width: z.ZodOptional<z.ZodNumber>;
|
|
46
46
|
height: z.ZodOptional<z.ZodNumber>;
|
|
47
|
-
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
47
|
+
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
48
48
|
}, z.core.$strip>>>;
|
|
49
49
|
}, z.core.$strip>;
|
|
50
50
|
declare const EdgeSchema: z.ZodObject<{
|
|
@@ -56,19 +56,25 @@ declare const EdgeSchema: z.ZodObject<{
|
|
|
56
56
|
weight: z.ZodOptional<z.ZodNumber>;
|
|
57
57
|
sourcePort: z.ZodOptional<z.ZodString>;
|
|
58
58
|
targetPort: z.ZodOptional<z.ZodString>;
|
|
59
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
60
|
+
directed: "directed";
|
|
61
|
+
undirected: "undirected";
|
|
62
|
+
bidirectional: "bidirectional";
|
|
63
|
+
}>>;
|
|
59
64
|
data: z.ZodAny;
|
|
60
65
|
x: z.ZodOptional<z.ZodNumber>;
|
|
61
66
|
y: z.ZodOptional<z.ZodNumber>;
|
|
62
67
|
width: z.ZodOptional<z.ZodNumber>;
|
|
63
68
|
height: z.ZodOptional<z.ZodNumber>;
|
|
64
69
|
color: z.ZodOptional<z.ZodString>;
|
|
65
|
-
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
70
|
+
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
66
71
|
}, z.core.$strip>;
|
|
67
72
|
declare const GraphSchema: z.ZodObject<{
|
|
68
73
|
id: z.ZodString;
|
|
69
|
-
|
|
74
|
+
mode: z.ZodEnum<{
|
|
70
75
|
directed: "directed";
|
|
71
76
|
undirected: "undirected";
|
|
77
|
+
bidirectional: "bidirectional";
|
|
72
78
|
}>;
|
|
73
79
|
initialNodeId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
74
80
|
nodes: z.ZodArray<z.ZodObject<{
|
|
@@ -84,7 +90,7 @@ declare const GraphSchema: z.ZodObject<{
|
|
|
84
90
|
height: z.ZodOptional<z.ZodNumber>;
|
|
85
91
|
shape: z.ZodOptional<z.ZodString>;
|
|
86
92
|
color: z.ZodOptional<z.ZodString>;
|
|
87
|
-
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
93
|
+
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
88
94
|
ports: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
89
95
|
name: z.ZodString;
|
|
90
96
|
direction: z.ZodEnum<{
|
|
@@ -98,7 +104,7 @@ declare const GraphSchema: z.ZodObject<{
|
|
|
98
104
|
y: z.ZodOptional<z.ZodNumber>;
|
|
99
105
|
width: z.ZodOptional<z.ZodNumber>;
|
|
100
106
|
height: z.ZodOptional<z.ZodNumber>;
|
|
101
|
-
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
107
|
+
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
102
108
|
}, z.core.$strip>>>;
|
|
103
109
|
}, z.core.$strip>>;
|
|
104
110
|
edges: z.ZodArray<z.ZodObject<{
|
|
@@ -110,13 +116,18 @@ declare const GraphSchema: z.ZodObject<{
|
|
|
110
116
|
weight: z.ZodOptional<z.ZodNumber>;
|
|
111
117
|
sourcePort: z.ZodOptional<z.ZodString>;
|
|
112
118
|
targetPort: z.ZodOptional<z.ZodString>;
|
|
119
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
120
|
+
directed: "directed";
|
|
121
|
+
undirected: "undirected";
|
|
122
|
+
bidirectional: "bidirectional";
|
|
123
|
+
}>>;
|
|
113
124
|
data: z.ZodAny;
|
|
114
125
|
x: z.ZodOptional<z.ZodNumber>;
|
|
115
126
|
y: z.ZodOptional<z.ZodNumber>;
|
|
116
127
|
width: z.ZodOptional<z.ZodNumber>;
|
|
117
128
|
height: z.ZodOptional<z.ZodNumber>;
|
|
118
129
|
color: z.ZodOptional<z.ZodString>;
|
|
119
|
-
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
130
|
+
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
120
131
|
}, z.core.$strip>>;
|
|
121
132
|
data: z.ZodAny;
|
|
122
133
|
direction: z.ZodOptional<z.ZodEnum<{
|
|
@@ -125,7 +136,7 @@ declare const GraphSchema: z.ZodObject<{
|
|
|
125
136
|
left: "left";
|
|
126
137
|
right: "right";
|
|
127
138
|
}>>;
|
|
128
|
-
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
139
|
+
style: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
129
140
|
}, z.core.$strip>;
|
|
130
141
|
interface GraphValidationIssue {
|
|
131
142
|
code: string;
|
package/dist/schemas.mjs
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import { t as getGraphIssues$1 } from "./validate-TtH-x3JV.mjs";
|
|
1
2
|
import * as z from "zod";
|
|
2
3
|
|
|
3
4
|
//#region src/schemas.ts
|
|
4
|
-
const StyleSchema = z.record(z.string(), z.union([
|
|
5
|
+
const StyleSchema = z.record(z.string(), z.union([
|
|
6
|
+
z.string(),
|
|
7
|
+
z.number(),
|
|
8
|
+
z.boolean()
|
|
9
|
+
]));
|
|
10
|
+
const ModeSchema = z.enum([
|
|
11
|
+
"directed",
|
|
12
|
+
"undirected",
|
|
13
|
+
"bidirectional"
|
|
14
|
+
]);
|
|
5
15
|
const PortDirectionSchema = z.enum([
|
|
6
16
|
"in",
|
|
7
17
|
"out",
|
|
@@ -43,6 +53,7 @@ const EdgeSchema = z.object({
|
|
|
43
53
|
weight: z.number().optional(),
|
|
44
54
|
sourcePort: z.string().optional(),
|
|
45
55
|
targetPort: z.string().optional(),
|
|
56
|
+
mode: ModeSchema.optional(),
|
|
46
57
|
data: z.any(),
|
|
47
58
|
x: z.number().optional(),
|
|
48
59
|
y: z.number().optional(),
|
|
@@ -53,7 +64,7 @@ const EdgeSchema = z.object({
|
|
|
53
64
|
});
|
|
54
65
|
const GraphSchema = z.object({
|
|
55
66
|
id: z.string(),
|
|
56
|
-
|
|
67
|
+
mode: ModeSchema,
|
|
57
68
|
initialNodeId: z.string().nullable().optional(),
|
|
58
69
|
nodes: z.array(NodeSchema),
|
|
59
70
|
edges: z.array(EdgeSchema),
|
|
@@ -106,37 +117,30 @@ function createIssue(code, message, path) {
|
|
|
106
117
|
path
|
|
107
118
|
};
|
|
108
119
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
/** Maps structural issue codes from `validate.ts` to this module's codes. */
|
|
121
|
+
const STRUCTURAL_CODE_MAP = {
|
|
122
|
+
"duplicate-node-id": "duplicate_node_id",
|
|
123
|
+
"duplicate-edge-id": "duplicate_edge_id",
|
|
124
|
+
"missing-initial-node": "missing_initial_node",
|
|
125
|
+
"missing-parent": "missing_parent",
|
|
126
|
+
"missing-node-initial": "missing_node_initial",
|
|
127
|
+
"duplicate-port-name": "duplicate_port_name",
|
|
128
|
+
"parent-cycle": "parent_cycle",
|
|
129
|
+
"missing-source-port": "missing_source_port",
|
|
130
|
+
"missing-target-port": "missing_target_port"
|
|
131
|
+
};
|
|
132
|
+
function toValidationIssue(issue) {
|
|
133
|
+
const path = issue.path ?? [];
|
|
134
|
+
let code = STRUCTURAL_CODE_MAP[issue.code] ?? issue.code;
|
|
135
|
+
if (issue.code === "dangling-edge-endpoint") code = path[path.length - 1] === "sourceId" ? "missing_source_node" : "missing_target_node";
|
|
136
|
+
return {
|
|
137
|
+
code,
|
|
138
|
+
message: issue.message,
|
|
139
|
+
path
|
|
140
|
+
};
|
|
120
141
|
}
|
|
121
142
|
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"]));
|
|
143
|
+
const issues = getGraphIssues$1(graph).map(toValidationIssue);
|
|
140
144
|
graph.nodes.forEach((node, index) => {
|
|
141
145
|
if (node.id === "") issues.push(createIssue("empty_node_id", "Node id must be a non-empty string", [
|
|
142
146
|
"nodes",
|
|
@@ -148,68 +152,13 @@ function getGraphInvariantIssues(graph) {
|
|
|
148
152
|
index,
|
|
149
153
|
"parentId"
|
|
150
154
|
]));
|
|
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
155
|
});
|
|
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
156
|
graph.edges.forEach((edge, index) => {
|
|
186
157
|
if (edge.id === "") issues.push(createIssue("empty_edge_id", "Edge id must be a non-empty string", [
|
|
187
158
|
"edges",
|
|
188
159
|
index,
|
|
189
160
|
"id"
|
|
190
161
|
]));
|
|
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
162
|
});
|
|
214
163
|
return issues;
|
|
215
164
|
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
//#region src/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Directedness of a graph or an individual edge.
|
|
4
|
+
*
|
|
5
|
+
* - `'directed'` — edge points from source to target.
|
|
6
|
+
* - `'undirected'` — edge has no direction; traversable both ways.
|
|
7
|
+
* - `'bidirectional'` — edge points both ways (arrows on both ends).
|
|
8
|
+
*
|
|
9
|
+
* Set at the graph level as the default ({@link Graph.mode}); individual edges
|
|
10
|
+
* may override it ({@link GraphEdge.mode}).
|
|
11
|
+
*/
|
|
12
|
+
type GraphMode = 'directed' | 'undirected' | 'bidirectional';
|
|
2
13
|
interface EntityRect {
|
|
3
14
|
x: number;
|
|
4
15
|
y: number;
|
|
@@ -11,7 +22,7 @@ interface GraphEntity {
|
|
|
11
22
|
y?: number;
|
|
12
23
|
width?: number;
|
|
13
24
|
height?: number;
|
|
14
|
-
style?: Record<string, string | number>;
|
|
25
|
+
style?: Record<string, string | number | boolean>;
|
|
15
26
|
}
|
|
16
27
|
/** Visual entity base — required position/size. */
|
|
17
28
|
interface VisualGraphEntity {
|
|
@@ -19,7 +30,7 @@ interface VisualGraphEntity {
|
|
|
19
30
|
y: number;
|
|
20
31
|
width: number;
|
|
21
32
|
height: number;
|
|
22
|
-
style?: Record<string, string | number>;
|
|
33
|
+
style?: Record<string, string | number | boolean>;
|
|
23
34
|
}
|
|
24
35
|
type PortDirection = 'in' | 'out' | 'inout';
|
|
25
36
|
interface PortConfig<TPortData = any> extends GraphEntity {
|
|
@@ -42,13 +53,14 @@ interface VisualPort<TPortData = any> extends GraphPort<TPortData> {
|
|
|
42
53
|
}
|
|
43
54
|
interface GraphConfig<TNodeData = any, TEdgeData = any, TGraphData = any, TPortData = any> {
|
|
44
55
|
id?: string;
|
|
45
|
-
|
|
56
|
+
/** Default directedness for all edges. Defaults to `'directed'`. */
|
|
57
|
+
mode?: GraphMode;
|
|
46
58
|
initialNodeId?: string;
|
|
47
59
|
nodes?: NodeConfig<TNodeData, TPortData>[];
|
|
48
60
|
edges?: EdgeConfig<TEdgeData>[];
|
|
49
61
|
data?: TGraphData;
|
|
50
62
|
direction?: 'up' | 'down' | 'left' | 'right';
|
|
51
|
-
style?: Record<string, string | number>;
|
|
63
|
+
style?: Record<string, string | number | boolean>;
|
|
52
64
|
}
|
|
53
65
|
interface NodeConfig<TNodeData = any, TPortData = any> extends GraphEntity {
|
|
54
66
|
id: string;
|
|
@@ -87,18 +99,24 @@ interface EdgeConfig<TEdgeData = any> extends GraphEntity {
|
|
|
87
99
|
sourcePort?: string;
|
|
88
100
|
/** Port name on the target node this edge connects to. */
|
|
89
101
|
targetPort?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Per-edge directedness override. When absent, the edge inherits the graph's
|
|
104
|
+
* {@link GraphConfig.mode}.
|
|
105
|
+
*/
|
|
106
|
+
mode?: GraphMode;
|
|
90
107
|
data?: TEdgeData;
|
|
91
108
|
color?: string;
|
|
92
109
|
}
|
|
93
110
|
interface Graph<TNodeData = any, TEdgeData = any, TGraphData = any, TPortData = any> {
|
|
94
111
|
id: string;
|
|
95
|
-
|
|
112
|
+
/** Default directedness for all edges. */
|
|
113
|
+
mode: GraphMode;
|
|
96
114
|
initialNodeId?: string | null;
|
|
97
115
|
nodes: GraphNode<TNodeData, TPortData>[];
|
|
98
116
|
edges: GraphEdge<TEdgeData>[];
|
|
99
117
|
data: TGraphData;
|
|
100
118
|
direction?: 'up' | 'down' | 'left' | 'right';
|
|
101
|
-
style?: Record<string, string | number>;
|
|
119
|
+
style?: Record<string, string | number | boolean>;
|
|
102
120
|
}
|
|
103
121
|
interface GraphNode<TNodeData = any, TPortData = any> extends GraphEntity {
|
|
104
122
|
type: 'node';
|
|
@@ -127,6 +145,11 @@ interface GraphEdge<TEdgeData = any> extends GraphEntity {
|
|
|
127
145
|
sourcePort?: string;
|
|
128
146
|
/** Port name on the target node this edge connects to. */
|
|
129
147
|
targetPort?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Per-edge directedness override. When absent, the edge inherits the graph's
|
|
150
|
+
* {@link Graph.mode}.
|
|
151
|
+
*/
|
|
152
|
+
mode?: GraphMode;
|
|
130
153
|
data: TEdgeData;
|
|
131
154
|
color?: string;
|
|
132
155
|
}
|
|
@@ -154,15 +177,64 @@ interface VisualGraphConfig<TNodeData = any, TEdgeData = any, TGraphData = any,
|
|
|
154
177
|
interface DeleteNodeOptions {
|
|
155
178
|
reparent?: boolean;
|
|
156
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Update payload for {@link updateNode}/`updateEntities`.
|
|
182
|
+
*
|
|
183
|
+
* Optional fields (`x`, `y`, `width`, `height`, `shape`, `color`, `style`,
|
|
184
|
+
* `ports`) accept `null` to **unset** the field. `undefined` (or omitting the
|
|
185
|
+
* key) leaves the field unchanged. `null` is used for unsetting so update
|
|
186
|
+
* payloads stay JSON-serializable.
|
|
187
|
+
*/
|
|
188
|
+
interface NodeUpdate<TNodeData = any, TPortData = any> {
|
|
189
|
+
parentId?: string | null;
|
|
190
|
+
initialNodeId?: string | null;
|
|
191
|
+
label?: string | null;
|
|
192
|
+
data?: TNodeData;
|
|
193
|
+
/** New ports for the node, or `null` to remove all ports. */
|
|
194
|
+
ports?: PortConfig<TPortData>[] | null;
|
|
195
|
+
x?: number | null;
|
|
196
|
+
y?: number | null;
|
|
197
|
+
width?: number | null;
|
|
198
|
+
height?: number | null;
|
|
199
|
+
shape?: string | null;
|
|
200
|
+
color?: string | null;
|
|
201
|
+
style?: Record<string, string | number | boolean> | null;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Update payload for {@link updateEdge}/`updateEntities`.
|
|
205
|
+
*
|
|
206
|
+
* Optional fields (`weight`, `mode`, `sourcePort`, `targetPort`, `x`, `y`,
|
|
207
|
+
* `width`, `height`, `color`, `style`) accept `null` to **unset** the field.
|
|
208
|
+
* `undefined` (or omitting the key) leaves the field unchanged. `null` is
|
|
209
|
+
* used for unsetting so update payloads stay JSON-serializable.
|
|
210
|
+
*/
|
|
211
|
+
interface EdgeUpdate<TEdgeData = any> {
|
|
212
|
+
sourceId?: string;
|
|
213
|
+
targetId?: string;
|
|
214
|
+
label?: string | null;
|
|
215
|
+
data?: TEdgeData;
|
|
216
|
+
weight?: number | null;
|
|
217
|
+
mode?: GraphMode | null;
|
|
218
|
+
/** Port name on the source node, or `null` to clear the port reference. */
|
|
219
|
+
sourcePort?: string | null;
|
|
220
|
+
/** Port name on the target node, or `null` to clear the port reference. */
|
|
221
|
+
targetPort?: string | null;
|
|
222
|
+
x?: number | null;
|
|
223
|
+
y?: number | null;
|
|
224
|
+
width?: number | null;
|
|
225
|
+
height?: number | null;
|
|
226
|
+
color?: string | null;
|
|
227
|
+
style?: Record<string, string | number | boolean> | null;
|
|
228
|
+
}
|
|
157
229
|
interface EntitiesConfig<TNodeData = any, TEdgeData = any, TPortData = any> {
|
|
158
230
|
nodes?: NodeConfig<TNodeData, TPortData>[];
|
|
159
231
|
edges?: EdgeConfig<TEdgeData>[];
|
|
160
232
|
}
|
|
161
233
|
interface EntitiesUpdate<TNodeData = any, TEdgeData = any, TPortData = any> {
|
|
162
|
-
nodes?: (
|
|
234
|
+
nodes?: (NodeUpdate<TNodeData, TPortData> & {
|
|
163
235
|
id: string;
|
|
164
236
|
})[];
|
|
165
|
-
edges?: (
|
|
237
|
+
edges?: (EdgeUpdate<TEdgeData> & {
|
|
166
238
|
id: string;
|
|
167
239
|
})[];
|
|
168
240
|
}
|
|
@@ -258,7 +330,7 @@ type GraphPatch<TNodeData = any, TEdgeData = any> = {
|
|
|
258
330
|
} | {
|
|
259
331
|
op: 'updateNode';
|
|
260
332
|
id: string;
|
|
261
|
-
data:
|
|
333
|
+
data: NodeUpdate<TNodeData>;
|
|
262
334
|
description?: string;
|
|
263
335
|
} | {
|
|
264
336
|
op: 'deleteNode';
|
|
@@ -271,7 +343,7 @@ type GraphPatch<TNodeData = any, TEdgeData = any> = {
|
|
|
271
343
|
} | {
|
|
272
344
|
op: 'updateEdge';
|
|
273
345
|
id: string;
|
|
274
|
-
data:
|
|
346
|
+
data: EdgeUpdate<TEdgeData>;
|
|
275
347
|
description?: string;
|
|
276
348
|
} | {
|
|
277
349
|
op: 'deleteEdge';
|
|
@@ -352,4 +424,4 @@ interface TransitionOptions<TState, TEvent> {
|
|
|
352
424
|
id?: string;
|
|
353
425
|
}
|
|
354
426
|
//#endregion
|
|
355
|
-
export {
|
|
427
|
+
export { SinglePathOptions as A, WalkContext as B, MSTOptions as C, PathOptions as D, NodeUpdate as E, VisualGraphConfig as F, WeightedWalkOptions as H, VisualGraphEntity as I, VisualGraphFormatConverter as L, TraversalOptions as M, VisualEdge as N, PortConfig as O, VisualGraph as P, VisualNode as R, GraphStep as S, NodeConfig as T, WalkOptions as V, GraphMode as _, EdgeChange as a, GraphPath as b, EntitiesConfig as c, Graph as d, GraphConfig as f, GraphFormatConverter as g, GraphEntity as h, DeleteNodeOptions as i, TransitionOptions as j, PortDirection as k, EntitiesUpdate as l, GraphEdge as m, AllPairsShortestPathsOptions as n, EdgeConfig as o, GraphDiff as p, CoverageStats as r, EdgeUpdate as s, AStarOptions as t, EntityRect as u, GraphNode as v, NodeChange as w, GraphPort as x, GraphPatch as y, VisualPort as z };
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
//#region src/validate.ts
|
|
2
|
+
function getDuplicateIndexes(items, getKey) {
|
|
3
|
+
const indexesByKey = /* @__PURE__ */ new Map();
|
|
4
|
+
items.forEach((item, index) => {
|
|
5
|
+
const key = getKey(item);
|
|
6
|
+
if (key == null) return;
|
|
7
|
+
const indexes = indexesByKey.get(key) ?? [];
|
|
8
|
+
indexes.push(index);
|
|
9
|
+
indexesByKey.set(key, indexes);
|
|
10
|
+
});
|
|
11
|
+
for (const [key, indexes] of indexesByKey) if (indexes.length < 2) indexesByKey.delete(key);
|
|
12
|
+
return indexesByKey;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Returns the parent cycles in a graph's hierarchy, each cycle reported once
|
|
16
|
+
* as the list of node ids forming the cycle (cycle members only — nodes whose
|
|
17
|
+
* ancestry merely *leads into* a cycle are not included).
|
|
18
|
+
*/
|
|
19
|
+
function getParentCycles(nodesById) {
|
|
20
|
+
const cycles = [];
|
|
21
|
+
const visited = /* @__PURE__ */ new Set();
|
|
22
|
+
for (const startId of nodesById.keys()) {
|
|
23
|
+
if (visited.has(startId)) continue;
|
|
24
|
+
const path = [];
|
|
25
|
+
const positions = /* @__PURE__ */ new Map();
|
|
26
|
+
let currentId = startId;
|
|
27
|
+
while (currentId != null && !visited.has(currentId) && nodesById.has(currentId)) {
|
|
28
|
+
const position = positions.get(currentId);
|
|
29
|
+
if (position !== void 0) {
|
|
30
|
+
cycles.push(path.slice(position));
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
positions.set(currentId, path.length);
|
|
34
|
+
path.push(currentId);
|
|
35
|
+
currentId = nodesById.get(currentId).parentId;
|
|
36
|
+
}
|
|
37
|
+
for (const id of path) visited.add(id);
|
|
38
|
+
}
|
|
39
|
+
return cycles;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Validates the structural invariants of a graph and returns the issues
|
|
43
|
+
* found, or `[]` when the graph is valid. Pure — never throws, never mutates.
|
|
44
|
+
*
|
|
45
|
+
* This is the recommended gate for untrusted or imported graphs (e.g. parsed
|
|
46
|
+
* from a file or received over the wire) before handing them to queries and
|
|
47
|
+
* algorithms: the mutation APIs (`addNode`, `addEdge`, `updateNode`, …)
|
|
48
|
+
* validate incrementally, but `createGraph` does **not** — it accepts
|
|
49
|
+
* dangling `parentId`/edge references and even `parentId` cycles as-is.
|
|
50
|
+
*
|
|
51
|
+
* For Zod-based shape validation of arbitrary unknown values, see
|
|
52
|
+
* `validateGraph` in `@statelyai/graph/schemas` (which reuses these checks).
|
|
53
|
+
*
|
|
54
|
+
* Issue codes: `duplicate-node-id`, `duplicate-edge-id`,
|
|
55
|
+
* `missing-initial-node`, `missing-parent`, `missing-node-initial`,
|
|
56
|
+
* `duplicate-port-name`, `parent-cycle`, `dangling-edge-endpoint`,
|
|
57
|
+
* `missing-source-port`, `missing-target-port`.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const graph = createGraph({
|
|
62
|
+
* nodes: [{ id: 'a', parentId: 'ghost' }],
|
|
63
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
64
|
+
* });
|
|
65
|
+
* getGraphIssues(graph);
|
|
66
|
+
* // => [
|
|
67
|
+
* // { code: 'missing-parent', message: '...', path: ['nodes', 0, 'parentId'] },
|
|
68
|
+
* // { code: 'dangling-edge-endpoint', message: '...', path: ['edges', 0, 'targetId'] },
|
|
69
|
+
* // ]
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
function getGraphIssues(graph) {
|
|
73
|
+
const issues = [];
|
|
74
|
+
const nodeIndexes = /* @__PURE__ */ new Map();
|
|
75
|
+
const nodesById = /* @__PURE__ */ new Map();
|
|
76
|
+
graph.nodes.forEach((node, index) => {
|
|
77
|
+
nodeIndexes.set(node.id, index);
|
|
78
|
+
nodesById.set(node.id, node);
|
|
79
|
+
});
|
|
80
|
+
for (const [id, indexes] of getDuplicateIndexes(graph.nodes, (node) => node.id)) for (const index of indexes) issues.push({
|
|
81
|
+
code: "duplicate-node-id",
|
|
82
|
+
message: `Duplicate node id "${id}". Node ids must be unique; rename or remove the duplicates.`,
|
|
83
|
+
path: [
|
|
84
|
+
"nodes",
|
|
85
|
+
index,
|
|
86
|
+
"id"
|
|
87
|
+
]
|
|
88
|
+
});
|
|
89
|
+
for (const [id, indexes] of getDuplicateIndexes(graph.edges, (edge) => edge.id)) for (const index of indexes) issues.push({
|
|
90
|
+
code: "duplicate-edge-id",
|
|
91
|
+
message: `Duplicate edge id "${id}". Edge ids must be unique; rename or remove the duplicates.`,
|
|
92
|
+
path: [
|
|
93
|
+
"edges",
|
|
94
|
+
index,
|
|
95
|
+
"id"
|
|
96
|
+
]
|
|
97
|
+
});
|
|
98
|
+
if (graph.initialNodeId && !nodesById.has(graph.initialNodeId)) issues.push({
|
|
99
|
+
code: "missing-initial-node",
|
|
100
|
+
message: `Graph initialNodeId references missing node "${graph.initialNodeId}". Add that node or update the graph's initialNodeId.`,
|
|
101
|
+
path: ["initialNodeId"]
|
|
102
|
+
});
|
|
103
|
+
graph.nodes.forEach((node, index) => {
|
|
104
|
+
if (node.parentId != null && !nodesById.has(node.parentId)) issues.push({
|
|
105
|
+
code: "missing-parent",
|
|
106
|
+
message: `Node "${node.id}" has parentId "${node.parentId}", which does not exist. Add that node or remove the parentId.`,
|
|
107
|
+
path: [
|
|
108
|
+
"nodes",
|
|
109
|
+
index,
|
|
110
|
+
"parentId"
|
|
111
|
+
]
|
|
112
|
+
});
|
|
113
|
+
if (node.initialNodeId && !nodesById.has(node.initialNodeId)) issues.push({
|
|
114
|
+
code: "missing-node-initial",
|
|
115
|
+
message: `Node "${node.id}" has initialNodeId "${node.initialNodeId}", which does not exist. Add that node or remove the initialNodeId.`,
|
|
116
|
+
path: [
|
|
117
|
+
"nodes",
|
|
118
|
+
index,
|
|
119
|
+
"initialNodeId"
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
for (const [name, portIndexes] of getDuplicateIndexes(node.ports ?? [], (port) => port.name)) for (const portIndex of portIndexes) issues.push({
|
|
123
|
+
code: "duplicate-port-name",
|
|
124
|
+
message: `Duplicate port name "${name}" on node "${node.id}". Port names must be unique per node; rename or remove the duplicates.`,
|
|
125
|
+
path: [
|
|
126
|
+
"nodes",
|
|
127
|
+
index,
|
|
128
|
+
"ports",
|
|
129
|
+
portIndex,
|
|
130
|
+
"name"
|
|
131
|
+
]
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
for (const cycle of getParentCycles(nodesById)) {
|
|
135
|
+
const chain = [...cycle, cycle[0]].join(" → ");
|
|
136
|
+
issues.push({
|
|
137
|
+
code: "parent-cycle",
|
|
138
|
+
message: `Parent cycle detected: ${chain}. Break the cycle by changing the parentId of one of these nodes.`,
|
|
139
|
+
path: [
|
|
140
|
+
"nodes",
|
|
141
|
+
nodeIndexes.get(cycle[0]) ?? 0,
|
|
142
|
+
"parentId"
|
|
143
|
+
]
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
graph.edges.forEach((edge, index) => {
|
|
147
|
+
const source = nodesById.get(edge.sourceId);
|
|
148
|
+
const target = nodesById.get(edge.targetId);
|
|
149
|
+
if (!source) issues.push({
|
|
150
|
+
code: "dangling-edge-endpoint",
|
|
151
|
+
message: `Edge "${edge.id}" has sourceId "${edge.sourceId}", which references a missing node. Add that node or fix the edge's sourceId.`,
|
|
152
|
+
path: [
|
|
153
|
+
"edges",
|
|
154
|
+
index,
|
|
155
|
+
"sourceId"
|
|
156
|
+
]
|
|
157
|
+
});
|
|
158
|
+
if (!target) issues.push({
|
|
159
|
+
code: "dangling-edge-endpoint",
|
|
160
|
+
message: `Edge "${edge.id}" has targetId "${edge.targetId}", which references a missing node. Add that node or fix the edge's targetId.`,
|
|
161
|
+
path: [
|
|
162
|
+
"edges",
|
|
163
|
+
index,
|
|
164
|
+
"targetId"
|
|
165
|
+
]
|
|
166
|
+
});
|
|
167
|
+
if (source && edge.sourcePort !== void 0 && !source.ports?.some((port) => port.name === edge.sourcePort)) issues.push({
|
|
168
|
+
code: "missing-source-port",
|
|
169
|
+
message: `Edge "${edge.id}" has sourcePort "${edge.sourcePort}", but source node "${edge.sourceId}" has no port with that name. Add the port or fix the edge's sourcePort.`,
|
|
170
|
+
path: [
|
|
171
|
+
"edges",
|
|
172
|
+
index,
|
|
173
|
+
"sourcePort"
|
|
174
|
+
]
|
|
175
|
+
});
|
|
176
|
+
if (target && edge.targetPort !== void 0 && !target.ports?.some((port) => port.name === edge.targetPort)) issues.push({
|
|
177
|
+
code: "missing-target-port",
|
|
178
|
+
message: `Edge "${edge.id}" has targetPort "${edge.targetPort}", but target node "${edge.targetId}" has no port with that name. Add the port or fix the edge's targetPort.`,
|
|
179
|
+
path: [
|
|
180
|
+
"edges",
|
|
181
|
+
index,
|
|
182
|
+
"targetPort"
|
|
183
|
+
]
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
return issues;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
//#endregion
|
|
190
|
+
export { getGraphIssues as t };
|