@statelyai/graph 2.0.0 → 2.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 +67 -19
- package/dist/{algorithms-CsGNehct.d.mts → algorithms-D1cgly0g.d.mts} +145 -6
- package/dist/{algorithms-DF1pSQGv.mjs → algorithms-DBpH74hR.mjs} +673 -891
- package/dist/algorithms.d.mts +2 -2
- package/dist/algorithms.mjs +2 -2
- package/dist/config-Dt5u1gSf.mjs +793 -0
- package/dist/{converter-DyCJJfTe.mjs → converter-DB6Rg6Vd.mjs} +2 -2
- 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 -1
- package/dist/formats/converter/index.mjs +1 -1
- package/dist/formats/cytoscape/index.d.mts +4 -4
- package/dist/formats/cytoscape/index.mjs +8 -4
- package/dist/formats/d2/index.d.mts +1 -1
- package/dist/formats/d2/index.mjs +1 -1
- package/dist/formats/d3/index.d.mts +4 -4
- package/dist/formats/d3/index.mjs +8 -4
- package/dist/formats/dot/index.d.mts +1 -1
- package/dist/formats/dot/index.mjs +1 -1
- 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 +43 -11
- package/dist/formats/gexf/index.d.mts +1 -1
- package/dist/formats/gexf/index.mjs +22 -2
- package/dist/formats/gml/index.d.mts +4 -4
- package/dist/formats/gml/index.mjs +8 -4
- package/dist/formats/graphml/index.d.mts +1 -1
- package/dist/formats/graphml/index.mjs +24 -2
- package/dist/formats/jgf/index.d.mts +4 -4
- package/dist/formats/jgf/index.mjs +8 -4
- package/dist/formats/mermaid/index.d.mts +1 -1
- package/dist/formats/mermaid/index.mjs +1 -1
- package/dist/formats/tgf/index.d.mts +4 -4
- package/dist/formats/tgf/index.mjs +4 -4
- package/dist/formats/xyflow/index.d.mts +12 -6
- package/dist/formats/xyflow/index.mjs +11 -6
- package/dist/{index-D51lJnt2.d.mts → index-BlbSWUvH.d.mts} +1 -1
- package/dist/{index-DWmo1mIp.d.mts → index-CNvqxPLJ.d.mts} +82 -14
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +152 -17
- package/dist/layout/cytoscape.d.mts +66 -0
- package/dist/layout/cytoscape.mjs +114 -0
- package/dist/layout/d3-force.d.mts +52 -0
- package/dist/layout/d3-force.mjs +127 -0
- package/dist/layout/d3-hierarchy.d.mts +39 -0
- package/dist/layout/d3-hierarchy.mjs +135 -0
- package/dist/layout/dagre.d.mts +32 -0
- package/dist/layout/dagre.mjs +99 -0
- package/dist/layout/elk.d.mts +47 -0
- package/dist/layout/elk.mjs +73 -0
- package/dist/layout/forceatlas2.d.mts +48 -0
- package/dist/layout/forceatlas2.mjs +100 -0
- package/dist/layout/graphviz.d.mts +50 -0
- package/dist/layout/graphviz.mjs +179 -0
- package/dist/layout/index.d.mts +185 -0
- package/dist/layout/index.mjs +181 -0
- package/dist/layout/webcola.d.mts +40 -0
- package/dist/layout/webcola.mjs +104 -0
- package/dist/{queries-BfXeTXRf.d.mts → queries-B6quF529.d.mts} +1 -1
- package/dist/{queries-KirMDR7e.mjs → queries-BMM0XAv_.mjs} +23 -17
- package/dist/queries.d.mts +1 -1
- package/dist/queries.mjs +1 -1
- package/dist/schemas.d.mts +19 -1
- package/dist/schemas.mjs +10 -1
- package/dist/{types-DNYdIU21.d.mts → types-BAEQTwK_.d.mts} +46 -3
- package/package.json +47 -5
- package/schemas/edge.schema.json +27 -0
- package/schemas/graph.schema.json +27 -0
- /package/dist/{adjacency-list-GeL1Cu-L.mjs → adjacency-list-DQ32Mmhx.mjs} +0 -0
- /package/dist/{edge-list-BcZ0h6zz.mjs → edge-list-CA9UTvn2.mjs} +0 -0
- /package/dist/{mode-D8OnHFBk.mjs → mode-gu_mhKKs.mjs} +0 -0
- /package/dist/{validate-TtH-x3JV.mjs → validate-BsfSOv0S.mjs} +0 -0
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
import { A as indexAddNode, M as indexUpdateEdgeEndpoints, N as invalidateIndex, O as getIndex, P as touchIndex, j as indexReparentNode, k as indexAddEdge } from "./queries-BMM0XAv_.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/graph.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create a resolved graph port from a config. Fills in defaults.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const port = createGraphPort({ name: 'output', direction: 'out' });
|
|
10
|
+
* // { name: 'output', direction: 'out', data: null }
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
function createGraphPort(config) {
|
|
14
|
+
if (!config.name) throw new Error("Port name must be a non-empty string");
|
|
15
|
+
const port = {
|
|
16
|
+
name: config.name,
|
|
17
|
+
direction: config.direction ?? "inout",
|
|
18
|
+
data: config.data ?? null
|
|
19
|
+
};
|
|
20
|
+
if (config.label !== void 0) port.label = config.label;
|
|
21
|
+
if (config.x !== void 0) port.x = config.x;
|
|
22
|
+
if (config.y !== void 0) port.y = config.y;
|
|
23
|
+
if (config.width !== void 0) port.width = config.width;
|
|
24
|
+
if (config.height !== void 0) port.height = config.height;
|
|
25
|
+
if (config.style !== void 0) port.style = config.style;
|
|
26
|
+
return port;
|
|
27
|
+
}
|
|
28
|
+
function validatePortNames(ports) {
|
|
29
|
+
const seen = /* @__PURE__ */ new Set();
|
|
30
|
+
for (const port of ports) {
|
|
31
|
+
if (seen.has(port.name)) throw new Error(`Duplicate port name "${port.name}" on node`);
|
|
32
|
+
seen.add(port.name);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function validateNodeReference(nodeIds, ref, message) {
|
|
36
|
+
if (ref != null && !nodeIds.has(ref)) throw new Error(message(ref));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a resolved graph node from a config. Fills in defaults.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const node = createGraphNode({ id: 'a', data: { label: 'hi' } });
|
|
44
|
+
* // { type: 'node', id: 'a', label: '', data: { label: 'hi' } }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
function createGraphNode(config) {
|
|
48
|
+
if (!config.id) throw new Error("Node id must be a non-empty string");
|
|
49
|
+
if (config.parentId === "") throw new Error("Node parentId must be a non-empty string");
|
|
50
|
+
const node = {
|
|
51
|
+
type: "node",
|
|
52
|
+
id: config.id,
|
|
53
|
+
...config.parentId !== void 0 && { parentId: config.parentId ?? null },
|
|
54
|
+
...config.initialNodeId !== void 0 && { initialNodeId: config.initialNodeId ?? null },
|
|
55
|
+
label: config.label ?? null,
|
|
56
|
+
data: config.data ?? null
|
|
57
|
+
};
|
|
58
|
+
if (config.ports !== void 0 && config.ports.length > 0) {
|
|
59
|
+
validatePortNames(config.ports);
|
|
60
|
+
node.ports = config.ports.map(createGraphPort);
|
|
61
|
+
}
|
|
62
|
+
if (config.x !== void 0) node.x = config.x;
|
|
63
|
+
if (config.y !== void 0) node.y = config.y;
|
|
64
|
+
if (config.width !== void 0) node.width = config.width;
|
|
65
|
+
if (config.height !== void 0) node.height = config.height;
|
|
66
|
+
if (config.shape !== void 0) node.shape = config.shape;
|
|
67
|
+
if (config.color !== void 0) node.color = config.color;
|
|
68
|
+
if (config.style !== void 0) node.style = config.style;
|
|
69
|
+
return node;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create a resolved graph edge from a config. Fills in defaults.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* const edge = createGraphEdge({ id: 'e1', sourceId: 'a', targetId: 'b' });
|
|
77
|
+
* // { type: 'edge', id: 'e1', sourceId: 'a', targetId: 'b', label: null, data: null }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
function createGraphEdge(config) {
|
|
81
|
+
if (!config.id) throw new Error("Edge id must be a non-empty string");
|
|
82
|
+
if (!config.sourceId) throw new Error("Edge sourceId must be a non-empty string");
|
|
83
|
+
if (!config.targetId) throw new Error("Edge targetId must be a non-empty string");
|
|
84
|
+
const edge = {
|
|
85
|
+
type: "edge",
|
|
86
|
+
id: config.id,
|
|
87
|
+
sourceId: config.sourceId,
|
|
88
|
+
targetId: config.targetId,
|
|
89
|
+
label: config.label ?? null,
|
|
90
|
+
data: config.data ?? null
|
|
91
|
+
};
|
|
92
|
+
if (config.sourcePort !== void 0) edge.sourcePort = config.sourcePort;
|
|
93
|
+
if (config.targetPort !== void 0) edge.targetPort = config.targetPort;
|
|
94
|
+
if (config.mode !== void 0) edge.mode = config.mode;
|
|
95
|
+
if (config.weight !== void 0) edge.weight = config.weight;
|
|
96
|
+
if (config.points !== void 0) edge.points = config.points.map((p) => ({ ...p }));
|
|
97
|
+
if (config.routing !== void 0) edge.routing = config.routing;
|
|
98
|
+
if (config.x !== void 0) edge.x = config.x;
|
|
99
|
+
if (config.y !== void 0) edge.y = config.y;
|
|
100
|
+
if (config.width !== void 0) edge.width = config.width;
|
|
101
|
+
if (config.height !== void 0) edge.height = config.height;
|
|
102
|
+
if (config.color !== void 0) edge.color = config.color;
|
|
103
|
+
if (config.style !== void 0) edge.style = config.style;
|
|
104
|
+
return edge;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create a graph from a config. Resolves defaults for all fields.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* const graph = createGraph({
|
|
112
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
113
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
114
|
+
* });
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
function createGraph(config) {
|
|
118
|
+
const graph = {
|
|
119
|
+
id: config?.id ?? "",
|
|
120
|
+
mode: config?.mode ?? "directed",
|
|
121
|
+
initialNodeId: config?.initialNodeId ?? null,
|
|
122
|
+
nodes: (config?.nodes ?? []).map(createGraphNode),
|
|
123
|
+
edges: (config?.edges ?? []).map(createGraphEdge),
|
|
124
|
+
data: config?.data ?? null
|
|
125
|
+
};
|
|
126
|
+
if (config?.direction !== void 0) graph.direction = config.direction;
|
|
127
|
+
if (config?.style !== void 0) graph.style = config.style;
|
|
128
|
+
return graph;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Create a visual graph with required position/size on all nodes and edges.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* const graph = createVisualGraph({
|
|
136
|
+
* nodes: [{ id: 'a', x: 0, y: 0, width: 100, height: 50 }],
|
|
137
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'a', x: 0, y: 0, width: 0, height: 0 }],
|
|
138
|
+
* });
|
|
139
|
+
* // graph.nodes[0].x === 0
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
function createVisualGraph(config) {
|
|
143
|
+
const base = createGraph(config);
|
|
144
|
+
return {
|
|
145
|
+
...base,
|
|
146
|
+
direction: config?.direction ?? "down",
|
|
147
|
+
nodes: base.nodes.map((n) => {
|
|
148
|
+
const { ports, ...rest } = n;
|
|
149
|
+
return {
|
|
150
|
+
...rest,
|
|
151
|
+
x: n.x ?? 0,
|
|
152
|
+
y: n.y ?? 0,
|
|
153
|
+
width: n.width ?? 0,
|
|
154
|
+
height: n.height ?? 0,
|
|
155
|
+
...n.shape !== void 0 && { shape: n.shape },
|
|
156
|
+
...ports !== void 0 && { ports: ports.map((p) => ({
|
|
157
|
+
...p,
|
|
158
|
+
x: p.x ?? 0,
|
|
159
|
+
y: p.y ?? 0,
|
|
160
|
+
width: p.width ?? 0,
|
|
161
|
+
height: p.height ?? 0
|
|
162
|
+
})) }
|
|
163
|
+
};
|
|
164
|
+
}),
|
|
165
|
+
edges: base.edges.map((e) => ({
|
|
166
|
+
...e,
|
|
167
|
+
x: e.x ?? 0,
|
|
168
|
+
y: e.y ?? 0,
|
|
169
|
+
width: e.width ?? 0,
|
|
170
|
+
height: e.height ?? 0
|
|
171
|
+
}))
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Create a graph by BFS exploration of a transition function.
|
|
176
|
+
* Each unique state becomes a node; each (state, event) -> nextState becomes an edge.
|
|
177
|
+
*
|
|
178
|
+
* - Node IDs are determined by `serializeState` (default: `JSON.stringify`).
|
|
179
|
+
* - Edge IDs use the format `sourceId|serializedEvent|targetId` for uniqueness
|
|
180
|
+
* and debuggability. Edge labels are just the serialized event string.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* const graph = createGraphFromTransition(
|
|
185
|
+
* (state, event) => {
|
|
186
|
+
* if (state === 'green' && event === 'TIMER') return 'yellow';
|
|
187
|
+
* if (state === 'yellow' && event === 'TIMER') return 'red';
|
|
188
|
+
* if (state === 'red' && event === 'TIMER') return 'green';
|
|
189
|
+
* return state;
|
|
190
|
+
* },
|
|
191
|
+
* {
|
|
192
|
+
* initialState: 'green',
|
|
193
|
+
* events: ['TIMER'],
|
|
194
|
+
* serializeState: (s) => s,
|
|
195
|
+
* serializeEvent: (e) => e,
|
|
196
|
+
* },
|
|
197
|
+
* );
|
|
198
|
+
* // graph.nodes.length === 3
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
function createGraphFromTransition(transition, options) {
|
|
202
|
+
const serializeState = options.serializeState ?? JSON.stringify;
|
|
203
|
+
const serializeEvent = options.serializeEvent ?? JSON.stringify;
|
|
204
|
+
const limit = options.limit ?? Infinity;
|
|
205
|
+
const getEvents = typeof options.events === "function" ? options.events : () => options.events;
|
|
206
|
+
const nodes = [];
|
|
207
|
+
const edges = [];
|
|
208
|
+
const visited = /* @__PURE__ */ new Set();
|
|
209
|
+
const edgeSet = /* @__PURE__ */ new Set();
|
|
210
|
+
const queue = [options.initialState];
|
|
211
|
+
const initialStateId = serializeState(options.initialState);
|
|
212
|
+
visited.add(initialStateId);
|
|
213
|
+
nodes.push({
|
|
214
|
+
id: initialStateId,
|
|
215
|
+
label: initialStateId,
|
|
216
|
+
data: options.initialState
|
|
217
|
+
});
|
|
218
|
+
let iterations = 0;
|
|
219
|
+
while (queue.length > 0) {
|
|
220
|
+
const state = queue.shift();
|
|
221
|
+
const stateId = serializeState(state);
|
|
222
|
+
if (++iterations > limit) throw new Error("Traversal limit exceeded");
|
|
223
|
+
if (options.stopWhen?.(state)) continue;
|
|
224
|
+
const events = getEvents(state);
|
|
225
|
+
for (const event of events) {
|
|
226
|
+
const nextState = transition(state, event);
|
|
227
|
+
const nextStateId = serializeState(nextState);
|
|
228
|
+
const eventStr = serializeEvent(event);
|
|
229
|
+
if (!visited.has(nextStateId)) {
|
|
230
|
+
visited.add(nextStateId);
|
|
231
|
+
nodes.push({
|
|
232
|
+
id: nextStateId,
|
|
233
|
+
label: nextStateId,
|
|
234
|
+
data: nextState
|
|
235
|
+
});
|
|
236
|
+
queue.push(nextState);
|
|
237
|
+
}
|
|
238
|
+
const edgeKey = `${stateId}|${eventStr}|${nextStateId}`;
|
|
239
|
+
if (!edgeSet.has(edgeKey)) {
|
|
240
|
+
edgeSet.add(edgeKey);
|
|
241
|
+
edges.push({
|
|
242
|
+
id: edgeKey,
|
|
243
|
+
sourceId: stateId,
|
|
244
|
+
targetId: nextStateId,
|
|
245
|
+
label: eventStr,
|
|
246
|
+
data: event
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return createGraph({
|
|
252
|
+
id: options.id ?? "",
|
|
253
|
+
mode: "directed",
|
|
254
|
+
initialNodeId: initialStateId,
|
|
255
|
+
nodes,
|
|
256
|
+
edges
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get a node by id, or `undefined` if not found.
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```ts
|
|
264
|
+
* const graph = createGraph({ nodes: [{ id: 'a' }] });
|
|
265
|
+
* const node = getNode(graph, 'a'); // GraphNode
|
|
266
|
+
* const missing = getNode(graph, 'z'); // undefined
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
function getNode(graph, id) {
|
|
270
|
+
const arrayIdx = getIndex(graph).nodeById.get(id);
|
|
271
|
+
return arrayIdx !== void 0 ? graph.nodes[arrayIdx] : void 0;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Get an edge by id, or `undefined` if not found.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* const graph = createGraph({
|
|
279
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
280
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
281
|
+
* });
|
|
282
|
+
* const edge = getEdge(graph, 'e1'); // GraphEdge
|
|
283
|
+
* const missing = getEdge(graph, 'z'); // undefined
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
function getEdge(graph, id) {
|
|
287
|
+
const arrayIdx = getIndex(graph).edgeById.get(id);
|
|
288
|
+
return arrayIdx !== void 0 ? graph.edges[arrayIdx] : void 0;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Check if a node exists in the graph.
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```ts
|
|
295
|
+
* const graph = createGraph({ nodes: [{ id: 'a' }] });
|
|
296
|
+
* hasNode(graph, 'a'); // true
|
|
297
|
+
* hasNode(graph, 'z'); // false
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
function hasNode(graph, id) {
|
|
301
|
+
return getIndex(graph).nodeById.has(id);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Check if an edge exists in the graph.
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```ts
|
|
308
|
+
* const graph = createGraph({
|
|
309
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
310
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
311
|
+
* });
|
|
312
|
+
* hasEdge(graph, 'e1'); // true
|
|
313
|
+
* hasEdge(graph, 'z'); // false
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
function hasEdge(graph, id) {
|
|
317
|
+
return getIndex(graph).edgeById.has(id);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* **Mutable.** Add a node to the graph. Mutates `graph.nodes` in place.
|
|
321
|
+
* @returns The resolved node that was added.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```ts
|
|
325
|
+
* const graph = createGraph();
|
|
326
|
+
* const node = addNode(graph, { id: 'a', label: 'Node A' });
|
|
327
|
+
* // graph.nodes.length === 1
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
function addNode(graph, config) {
|
|
331
|
+
const node = createGraphNode(config);
|
|
332
|
+
const idx = getIndex(graph);
|
|
333
|
+
if (idx.nodeById.has(config.id)) throw new Error(`Node "${config.id}" already exists`);
|
|
334
|
+
if (config.parentId && !idx.nodeById.has(config.parentId)) throw new Error(`Parent node "${config.parentId}" does not exist`);
|
|
335
|
+
validateNodeReference(new Set([...idx.nodeById.keys(), config.id]), config.initialNodeId, (initialNodeId) => `Initial node "${initialNodeId}" does not exist`);
|
|
336
|
+
indexAddNode(idx, node, graph.nodes.push(node) - 1);
|
|
337
|
+
return node;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* **Mutable.** Add an edge to the graph. Mutates `graph.edges` in place.
|
|
341
|
+
* @returns The resolved edge that was added.
|
|
342
|
+
*
|
|
343
|
+
* @example
|
|
344
|
+
* ```ts
|
|
345
|
+
* const graph = createGraph({ nodes: [{ id: 'a' }, { id: 'b' }] });
|
|
346
|
+
* const edge = addEdge(graph, { id: 'e1', sourceId: 'a', targetId: 'b' });
|
|
347
|
+
* // graph.edges.length === 1
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
350
|
+
function addEdge(graph, config) {
|
|
351
|
+
const edge = createGraphEdge(config);
|
|
352
|
+
const idx = getIndex(graph);
|
|
353
|
+
if (idx.edgeById.has(config.id)) throw new Error(`Edge "${config.id}" already exists`);
|
|
354
|
+
if (!idx.nodeById.has(config.sourceId)) throw new Error(`Source node "${config.sourceId}" does not exist`);
|
|
355
|
+
if (!idx.nodeById.has(config.targetId)) throw new Error(`Target node "${config.targetId}" does not exist`);
|
|
356
|
+
if (config.sourcePort !== void 0) {
|
|
357
|
+
if (!graph.nodes[idx.nodeById.get(config.sourceId)].ports?.some((p) => p.name === config.sourcePort)) throw new Error(`Port "${config.sourcePort}" does not exist on source node "${config.sourceId}"`);
|
|
358
|
+
}
|
|
359
|
+
if (config.targetPort !== void 0) {
|
|
360
|
+
if (!graph.nodes[idx.nodeById.get(config.targetId)].ports?.some((p) => p.name === config.targetPort)) throw new Error(`Port "${config.targetPort}" does not exist on target node "${config.targetId}"`);
|
|
361
|
+
}
|
|
362
|
+
indexAddEdge(idx, edge, graph.edges.push(edge) - 1);
|
|
363
|
+
return edge;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* **Mutable.** Delete a node and its connected edges. Mutates `graph.nodes`
|
|
367
|
+
* and `graph.edges` in place.
|
|
368
|
+
*
|
|
369
|
+
* By default, children are deleted recursively.
|
|
370
|
+
* With `{ reparent: true }`, children are re-parented to the deleted node's parent.
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```ts
|
|
374
|
+
* const graph = createGraph({
|
|
375
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
376
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
377
|
+
* });
|
|
378
|
+
* deleteNode(graph, 'a');
|
|
379
|
+
* // graph.nodes.length === 1, edge e1 also removed
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
function deleteNode(graph, id, opts) {
|
|
383
|
+
const node = getNode(graph, id);
|
|
384
|
+
if (!node) throw new Error(`Node "${id}" does not exist`);
|
|
385
|
+
if (opts?.reparent) {
|
|
386
|
+
for (const n of graph.nodes) if (n.parentId === id) n.parentId = node.parentId;
|
|
387
|
+
graph.nodes = graph.nodes.filter((n) => n.id !== id);
|
|
388
|
+
graph.edges = graph.edges.filter((e) => e.sourceId !== id && e.targetId !== id);
|
|
389
|
+
} else {
|
|
390
|
+
const toDelete = collectDescendants(graph, id);
|
|
391
|
+
graph.nodes = graph.nodes.filter((n) => !toDelete.has(n.id));
|
|
392
|
+
graph.edges = graph.edges.filter((e) => !toDelete.has(e.sourceId) && !toDelete.has(e.targetId));
|
|
393
|
+
}
|
|
394
|
+
invalidateIndex(graph);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* **Mutable.** Delete an edge. Mutates `graph.edges` in place.
|
|
398
|
+
*
|
|
399
|
+
* @example
|
|
400
|
+
* ```ts
|
|
401
|
+
* const graph = createGraph({
|
|
402
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
403
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
404
|
+
* });
|
|
405
|
+
* deleteEdge(graph, 'e1');
|
|
406
|
+
* // graph.edges.length === 0
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
function deleteEdge(graph, id) {
|
|
410
|
+
if (!hasEdge(graph, id)) throw new Error(`Edge "${id}" does not exist`);
|
|
411
|
+
graph.edges = graph.edges.filter((e) => e.id !== id);
|
|
412
|
+
invalidateIndex(graph);
|
|
413
|
+
}
|
|
414
|
+
/** Optional fields where `null` in an update unsets the field. */
|
|
415
|
+
const NODE_OPTIONAL_KEYS = [
|
|
416
|
+
"x",
|
|
417
|
+
"y",
|
|
418
|
+
"width",
|
|
419
|
+
"height",
|
|
420
|
+
"shape",
|
|
421
|
+
"color",
|
|
422
|
+
"style"
|
|
423
|
+
];
|
|
424
|
+
const EDGE_OPTIONAL_KEYS = [
|
|
425
|
+
"weight",
|
|
426
|
+
"mode",
|
|
427
|
+
"points",
|
|
428
|
+
"routing",
|
|
429
|
+
"x",
|
|
430
|
+
"y",
|
|
431
|
+
"width",
|
|
432
|
+
"height",
|
|
433
|
+
"color",
|
|
434
|
+
"style"
|
|
435
|
+
];
|
|
436
|
+
/** Apply optional-field updates: `null` unsets, a value sets, `undefined` is ignored. */
|
|
437
|
+
function applyOptionalUpdates(target, update, keys) {
|
|
438
|
+
for (const key of keys) {
|
|
439
|
+
const value = update[key];
|
|
440
|
+
if (value === void 0) continue;
|
|
441
|
+
if (value === null) delete target[key];
|
|
442
|
+
else target[key] = value;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* **Mutable.** Update a node in place.
|
|
447
|
+
* Optional fields (`x`, `y`, `width`, `height`, `shape`, `color`, `style`,
|
|
448
|
+
* `ports`) accept `null` to unset; `undefined` leaves them unchanged.
|
|
449
|
+
* @returns The updated node.
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* ```ts
|
|
453
|
+
* const graph = createGraph({ nodes: [{ id: 'a', label: 'old' }] });
|
|
454
|
+
* const updated = updateNode(graph, 'a', { label: 'new', x: 100 });
|
|
455
|
+
* // updated.label === 'new', updated.x === 100
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
458
|
+
function updateNode(graph, id, update) {
|
|
459
|
+
const idx = getIndex(graph);
|
|
460
|
+
const arrayIdx = idx.nodeById.get(id);
|
|
461
|
+
if (arrayIdx === void 0) throw new Error(`Node "${id}" does not exist`);
|
|
462
|
+
if (update.parentId !== void 0 && update.parentId !== null) {
|
|
463
|
+
if (!idx.nodeById.has(update.parentId)) throw new Error(`Parent node "${update.parentId}" does not exist`);
|
|
464
|
+
let ancestorId = update.parentId;
|
|
465
|
+
const seen = /* @__PURE__ */ new Set();
|
|
466
|
+
while (ancestorId !== null && !seen.has(ancestorId)) {
|
|
467
|
+
if (ancestorId === id) throw new Error(`Cannot set parentId of node "${id}" to "${update.parentId}": "${update.parentId}" is "${id}" or one of its descendants, which would create a hierarchy cycle. Reparent "${update.parentId}" elsewhere first.`);
|
|
468
|
+
seen.add(ancestorId);
|
|
469
|
+
const ai = idx.nodeById.get(ancestorId);
|
|
470
|
+
ancestorId = ai !== void 0 ? graph.nodes[ai].parentId ?? null : null;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
validateNodeReference(new Set(idx.nodeById.keys()), update.initialNodeId, (initialNodeId) => `Initial node "${initialNodeId}" does not exist`);
|
|
474
|
+
if (update.ports != null && update.ports.length > 0) validatePortNames(update.ports);
|
|
475
|
+
const node = graph.nodes[arrayIdx];
|
|
476
|
+
if (update.ports !== void 0) {
|
|
477
|
+
const newPortNames = new Set((update.ports ?? []).map((p) => p.name));
|
|
478
|
+
for (const eid of idx.outEdges.get(id) ?? []) {
|
|
479
|
+
const e = graph.edges[idx.edgeById.get(eid)];
|
|
480
|
+
if (e.sourcePort !== void 0 && !newPortNames.has(e.sourcePort)) throw new Error(`Cannot update ports of node "${id}": edge "${e.id}" references port "${e.sourcePort}" via sourcePort. Keep that port, or update/delete the edge first.`);
|
|
481
|
+
}
|
|
482
|
+
for (const eid of idx.inEdges.get(id) ?? []) {
|
|
483
|
+
const e = graph.edges[idx.edgeById.get(eid)];
|
|
484
|
+
if (e.targetPort !== void 0 && !newPortNames.has(e.targetPort)) throw new Error(`Cannot update ports of node "${id}": edge "${e.id}" references port "${e.targetPort}" via targetPort. Keep that port, or update/delete the edge first.`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const oldParentId = node.parentId;
|
|
488
|
+
const updated = {
|
|
489
|
+
...node,
|
|
490
|
+
...update.parentId !== void 0 && { parentId: update.parentId ?? null },
|
|
491
|
+
...update.initialNodeId !== void 0 && { initialNodeId: update.initialNodeId ?? null },
|
|
492
|
+
...update.label !== void 0 && { label: update.label },
|
|
493
|
+
...update.data !== void 0 && { data: update.data }
|
|
494
|
+
};
|
|
495
|
+
if (update.ports !== void 0) if (update.ports === null) delete updated.ports;
|
|
496
|
+
else updated.ports = update.ports.map(createGraphPort);
|
|
497
|
+
applyOptionalUpdates(updated, update, NODE_OPTIONAL_KEYS);
|
|
498
|
+
graph.nodes[arrayIdx] = updated;
|
|
499
|
+
if (update.parentId !== void 0 && updated.parentId !== oldParentId) indexReparentNode(idx, id, oldParentId, updated.parentId);
|
|
500
|
+
return updated;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* **Mutable.** Update an edge in place.
|
|
504
|
+
* Optional fields (`weight`, `mode`, `sourcePort`, `targetPort`, `x`, `y`,
|
|
505
|
+
* `width`, `height`, `color`, `style`) accept `null` to unset; `undefined`
|
|
506
|
+
* leaves them unchanged.
|
|
507
|
+
* @returns The updated edge.
|
|
508
|
+
*
|
|
509
|
+
* @example
|
|
510
|
+
* ```ts
|
|
511
|
+
* const graph = createGraph({
|
|
512
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
513
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b', label: 'old' }],
|
|
514
|
+
* });
|
|
515
|
+
* const updated = updateEdge(graph, 'e1', { label: 'new', weight: 2 });
|
|
516
|
+
* // updated.label === 'new', updated.weight === 2
|
|
517
|
+
* ```
|
|
518
|
+
*/
|
|
519
|
+
function updateEdge(graph, id, update) {
|
|
520
|
+
const idx = getIndex(graph);
|
|
521
|
+
const arrayIdx = idx.edgeById.get(id);
|
|
522
|
+
if (arrayIdx === void 0) throw new Error(`Edge "${id}" does not exist`);
|
|
523
|
+
if (update.sourceId !== void 0 && !idx.nodeById.has(update.sourceId)) throw new Error(`Source node "${update.sourceId}" does not exist`);
|
|
524
|
+
if (update.targetId !== void 0 && !idx.nodeById.has(update.targetId)) throw new Error(`Target node "${update.targetId}" does not exist`);
|
|
525
|
+
const edge = graph.edges[arrayIdx];
|
|
526
|
+
const oldSourceId = edge.sourceId;
|
|
527
|
+
const oldTargetId = edge.targetId;
|
|
528
|
+
const effectiveSourceId = update.sourceId ?? edge.sourceId;
|
|
529
|
+
const effectiveTargetId = update.targetId ?? edge.targetId;
|
|
530
|
+
const effectiveSourcePort = update.sourcePort !== void 0 ? update.sourcePort ?? void 0 : edge.sourcePort;
|
|
531
|
+
const effectiveTargetPort = update.targetPort !== void 0 ? update.targetPort ?? void 0 : edge.targetPort;
|
|
532
|
+
if (effectiveSourcePort !== void 0) {
|
|
533
|
+
if (!graph.nodes[idx.nodeById.get(effectiveSourceId)].ports?.some((p) => p.name === effectiveSourcePort)) throw new Error(update.sourcePort !== void 0 ? `Port "${effectiveSourcePort}" does not exist on source node "${effectiveSourceId}"` : `Cannot update edge "${id}": its sourcePort "${effectiveSourcePort}" does not exist on the new source node "${effectiveSourceId}". Include sourcePort in the update (a port on "${effectiveSourceId}", or null to clear it).`);
|
|
534
|
+
}
|
|
535
|
+
if (effectiveTargetPort !== void 0) {
|
|
536
|
+
if (!graph.nodes[idx.nodeById.get(effectiveTargetId)].ports?.some((p) => p.name === effectiveTargetPort)) throw new Error(update.targetPort !== void 0 ? `Port "${effectiveTargetPort}" does not exist on target node "${effectiveTargetId}"` : `Cannot update edge "${id}": its targetPort "${effectiveTargetPort}" does not exist on the new target node "${effectiveTargetId}". Include targetPort in the update (a port on "${effectiveTargetId}", or null to clear it).`);
|
|
537
|
+
}
|
|
538
|
+
const updated = {
|
|
539
|
+
...edge,
|
|
540
|
+
...update.sourceId !== void 0 && { sourceId: update.sourceId },
|
|
541
|
+
...update.targetId !== void 0 && { targetId: update.targetId },
|
|
542
|
+
...update.label !== void 0 && { label: update.label },
|
|
543
|
+
...update.data !== void 0 && { data: update.data }
|
|
544
|
+
};
|
|
545
|
+
if (update.sourcePort !== void 0) if (update.sourcePort === null) delete updated.sourcePort;
|
|
546
|
+
else updated.sourcePort = update.sourcePort;
|
|
547
|
+
if (update.targetPort !== void 0) if (update.targetPort === null) delete updated.targetPort;
|
|
548
|
+
else updated.targetPort = update.targetPort;
|
|
549
|
+
applyOptionalUpdates(updated, update, EDGE_OPTIONAL_KEYS);
|
|
550
|
+
graph.edges[arrayIdx] = updated;
|
|
551
|
+
if (update.mode !== void 0 || update.weight !== void 0) touchIndex(idx);
|
|
552
|
+
if (updated.sourceId !== oldSourceId || updated.targetId !== oldTargetId) indexUpdateEdgeEndpoints(idx, id, oldSourceId, oldTargetId, updated.sourceId, updated.targetId);
|
|
553
|
+
return updated;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* **Mutable.** Add multiple nodes and edges to the graph.
|
|
557
|
+
* Nodes are added first, then edges (so edges can reference new nodes).
|
|
558
|
+
*
|
|
559
|
+
* @example
|
|
560
|
+
* ```ts
|
|
561
|
+
* const graph = createGraph();
|
|
562
|
+
* addEntities(graph, {
|
|
563
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
564
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
565
|
+
* });
|
|
566
|
+
* // graph.nodes.length === 2, graph.edges.length === 1
|
|
567
|
+
* ```
|
|
568
|
+
*/
|
|
569
|
+
function addEntities(graph, entities) {
|
|
570
|
+
const nodeConfigs = entities.nodes ?? [];
|
|
571
|
+
if (nodeConfigs.length > 0) {
|
|
572
|
+
const idx = getIndex(graph);
|
|
573
|
+
const nodeIds = new Set(idx.nodeById.keys());
|
|
574
|
+
for (const nodeConfig of nodeConfigs) {
|
|
575
|
+
if (nodeIds.has(nodeConfig.id)) throw new Error(`Node "${nodeConfig.id}" already exists`);
|
|
576
|
+
nodeIds.add(nodeConfig.id);
|
|
577
|
+
}
|
|
578
|
+
const nodes = nodeConfigs.map(createGraphNode);
|
|
579
|
+
for (const nodeConfig of nodeConfigs) {
|
|
580
|
+
validateNodeReference(nodeIds, nodeConfig.parentId, (parentId) => `Parent node "${parentId}" does not exist`);
|
|
581
|
+
validateNodeReference(nodeIds, nodeConfig.initialNodeId, (initialNodeId) => `Initial node "${initialNodeId}" does not exist`);
|
|
582
|
+
}
|
|
583
|
+
for (const node of nodes) indexAddNode(idx, node, graph.nodes.push(node) - 1);
|
|
584
|
+
}
|
|
585
|
+
for (const edgeConfig of entities.edges ?? []) addEdge(graph, edgeConfig);
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* **Mutable.** Delete entities by id(s). Automatically detects whether each id
|
|
589
|
+
* is a node or edge. Node deletions cascade to children and connected edges.
|
|
590
|
+
*
|
|
591
|
+
* @example
|
|
592
|
+
* ```ts
|
|
593
|
+
* const graph = createGraph({
|
|
594
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
595
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
596
|
+
* });
|
|
597
|
+
* deleteEntities(graph, ['a', 'e1']);
|
|
598
|
+
* // graph.nodes.length === 1, graph.edges.length === 0
|
|
599
|
+
* ```
|
|
600
|
+
*/
|
|
601
|
+
function deleteEntities(graph, ids, opts) {
|
|
602
|
+
const idArray = Array.isArray(ids) ? ids : [ids];
|
|
603
|
+
for (const id of idArray) if (hasNode(graph, id)) deleteNode(graph, id, opts);
|
|
604
|
+
else if (hasEdge(graph, id)) deleteEdge(graph, id);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* **Mutable.** Update multiple nodes and edges in place.
|
|
608
|
+
* Each entry must include an `id` to identify which entity to update.
|
|
609
|
+
*
|
|
610
|
+
* @example
|
|
611
|
+
* ```ts
|
|
612
|
+
* const graph = createGraph({
|
|
613
|
+
* nodes: [{ id: 'a', label: 'old' }],
|
|
614
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'a', label: 'old' }],
|
|
615
|
+
* });
|
|
616
|
+
* updateEntities(graph, {
|
|
617
|
+
* nodes: [{ id: 'a', label: 'new' }],
|
|
618
|
+
* edges: [{ id: 'e1', label: 'new' }],
|
|
619
|
+
* });
|
|
620
|
+
* ```
|
|
621
|
+
*/
|
|
622
|
+
function updateEntities(graph, updates) {
|
|
623
|
+
for (const nodeUpdate of updates.nodes ?? []) {
|
|
624
|
+
const { id, ...patch } = nodeUpdate;
|
|
625
|
+
updateNode(graph, id, patch);
|
|
626
|
+
}
|
|
627
|
+
for (const edgeUpdate of updates.edges ?? []) {
|
|
628
|
+
const { id, ...patch } = edgeUpdate;
|
|
629
|
+
updateEdge(graph, id, patch);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* OOP wrapper around a plain `Graph` object.
|
|
634
|
+
* Delegates to the standalone mutable functions.
|
|
635
|
+
*
|
|
636
|
+
* @example
|
|
637
|
+
* ```ts
|
|
638
|
+
* const instance = new GraphInstance({
|
|
639
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
640
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
641
|
+
* });
|
|
642
|
+
* instance.addNode({ id: 'c' });
|
|
643
|
+
* instance.hasNode('c'); // true
|
|
644
|
+
* instance.toJSON(); // plain Graph object
|
|
645
|
+
* ```
|
|
646
|
+
*/
|
|
647
|
+
var GraphInstance = class GraphInstance {
|
|
648
|
+
graph;
|
|
649
|
+
constructor(config) {
|
|
650
|
+
this.graph = createGraph(config);
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Wrap an existing plain graph object.
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* ```ts
|
|
657
|
+
* const graph = createGraph({ nodes: [{ id: 'a' }] });
|
|
658
|
+
* const instance = GraphInstance.from(graph);
|
|
659
|
+
* instance.hasNode('a'); // true
|
|
660
|
+
* ```
|
|
661
|
+
*/
|
|
662
|
+
static from(graph) {
|
|
663
|
+
const instance = Object.create(GraphInstance.prototype);
|
|
664
|
+
instance.graph = graph;
|
|
665
|
+
return instance;
|
|
666
|
+
}
|
|
667
|
+
get id() {
|
|
668
|
+
return this.graph.id;
|
|
669
|
+
}
|
|
670
|
+
/** Default directedness for all edges. */
|
|
671
|
+
get mode() {
|
|
672
|
+
return this.graph.mode;
|
|
673
|
+
}
|
|
674
|
+
get nodes() {
|
|
675
|
+
return this.graph.nodes;
|
|
676
|
+
}
|
|
677
|
+
get edges() {
|
|
678
|
+
return this.graph.edges;
|
|
679
|
+
}
|
|
680
|
+
get data() {
|
|
681
|
+
return this.graph.data;
|
|
682
|
+
}
|
|
683
|
+
getNode(id) {
|
|
684
|
+
return getNode(this.graph, id);
|
|
685
|
+
}
|
|
686
|
+
getEdge(id) {
|
|
687
|
+
return getEdge(this.graph, id);
|
|
688
|
+
}
|
|
689
|
+
hasNode(id) {
|
|
690
|
+
return hasNode(this.graph, id);
|
|
691
|
+
}
|
|
692
|
+
hasEdge(id) {
|
|
693
|
+
return hasEdge(this.graph, id);
|
|
694
|
+
}
|
|
695
|
+
addNode(config) {
|
|
696
|
+
return addNode(this.graph, config);
|
|
697
|
+
}
|
|
698
|
+
addEdge(config) {
|
|
699
|
+
return addEdge(this.graph, config);
|
|
700
|
+
}
|
|
701
|
+
deleteNode(id, opts) {
|
|
702
|
+
return deleteNode(this.graph, id, opts);
|
|
703
|
+
}
|
|
704
|
+
deleteEdge(id) {
|
|
705
|
+
return deleteEdge(this.graph, id);
|
|
706
|
+
}
|
|
707
|
+
updateNode(id, update) {
|
|
708
|
+
return updateNode(this.graph, id, update);
|
|
709
|
+
}
|
|
710
|
+
updateEdge(id, update) {
|
|
711
|
+
return updateEdge(this.graph, id, update);
|
|
712
|
+
}
|
|
713
|
+
addEntities(entities) {
|
|
714
|
+
return addEntities(this.graph, entities);
|
|
715
|
+
}
|
|
716
|
+
deleteEntities(ids, opts) {
|
|
717
|
+
return deleteEntities(this.graph, ids, opts);
|
|
718
|
+
}
|
|
719
|
+
updateEntities(updates) {
|
|
720
|
+
return updateEntities(this.graph, updates);
|
|
721
|
+
}
|
|
722
|
+
toJSON() {
|
|
723
|
+
return this.graph;
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
function collectDescendants(graph, id) {
|
|
727
|
+
const idx = getIndex(graph);
|
|
728
|
+
const toDelete = /* @__PURE__ */ new Set();
|
|
729
|
+
const walk = (nodeId) => {
|
|
730
|
+
toDelete.add(nodeId);
|
|
731
|
+
const childIds = idx.childNodes.get(nodeId) ?? [];
|
|
732
|
+
for (const childId of childIds) if (!toDelete.has(childId)) walk(childId);
|
|
733
|
+
};
|
|
734
|
+
walk(id);
|
|
735
|
+
return toDelete;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
//#endregion
|
|
739
|
+
//#region src/config.ts
|
|
740
|
+
/**
|
|
741
|
+
* Convert a resolved {@link GraphNode} back into a {@link NodeConfig}.
|
|
742
|
+
*
|
|
743
|
+
* Faithful and complete: round-tripping through `createGraphNode` yields a
|
|
744
|
+
* deep-equal node. Optional fields are only included when present; ports are
|
|
745
|
+
* deep-copied so the config does not share port objects with the source node.
|
|
746
|
+
*/
|
|
747
|
+
function toNodeConfig(node) {
|
|
748
|
+
const config = { id: node.id };
|
|
749
|
+
if (node.parentId != null) config.parentId = node.parentId;
|
|
750
|
+
if (node.initialNodeId != null) config.initialNodeId = node.initialNodeId;
|
|
751
|
+
if (node.label != null) config.label = node.label;
|
|
752
|
+
if (node.data != null) config.data = node.data;
|
|
753
|
+
if (node.ports !== void 0) config.ports = node.ports.map((p) => ({ ...p }));
|
|
754
|
+
if (node.x !== void 0) config.x = node.x;
|
|
755
|
+
if (node.y !== void 0) config.y = node.y;
|
|
756
|
+
if (node.width !== void 0) config.width = node.width;
|
|
757
|
+
if (node.height !== void 0) config.height = node.height;
|
|
758
|
+
if (node.shape !== void 0) config.shape = node.shape;
|
|
759
|
+
if (node.color !== void 0) config.color = node.color;
|
|
760
|
+
if (node.style !== void 0) config.style = node.style;
|
|
761
|
+
return config;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Convert a resolved {@link GraphEdge} back into an {@link EdgeConfig}.
|
|
765
|
+
*
|
|
766
|
+
* Faithful and complete: round-tripping through `createGraphEdge` yields a
|
|
767
|
+
* deep-equal edge. Optional fields are only included when present.
|
|
768
|
+
*/
|
|
769
|
+
function toEdgeConfig(edge) {
|
|
770
|
+
const config = {
|
|
771
|
+
id: edge.id,
|
|
772
|
+
sourceId: edge.sourceId,
|
|
773
|
+
targetId: edge.targetId
|
|
774
|
+
};
|
|
775
|
+
if (edge.label != null) config.label = edge.label;
|
|
776
|
+
if (edge.data != null) config.data = edge.data;
|
|
777
|
+
if (edge.weight !== void 0) config.weight = edge.weight;
|
|
778
|
+
if (edge.mode !== void 0) config.mode = edge.mode;
|
|
779
|
+
if (edge.sourcePort !== void 0) config.sourcePort = edge.sourcePort;
|
|
780
|
+
if (edge.targetPort !== void 0) config.targetPort = edge.targetPort;
|
|
781
|
+
if (edge.points !== void 0) config.points = edge.points.map((p) => ({ ...p }));
|
|
782
|
+
if (edge.routing !== void 0) config.routing = edge.routing;
|
|
783
|
+
if (edge.x !== void 0) config.x = edge.x;
|
|
784
|
+
if (edge.y !== void 0) config.y = edge.y;
|
|
785
|
+
if (edge.width !== void 0) config.width = edge.width;
|
|
786
|
+
if (edge.height !== void 0) config.height = edge.height;
|
|
787
|
+
if (edge.color !== void 0) config.color = edge.color;
|
|
788
|
+
if (edge.style !== void 0) config.style = edge.style;
|
|
789
|
+
return config;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
//#endregion
|
|
793
|
+
export { updateNode as S, getNode as _, addEntities as a, updateEdge as b, createGraphEdge as c, createGraphPort as d, createVisualGraph as f, getEdge as g, deleteNode as h, addEdge as i, createGraphFromTransition as l, deleteEntities as m, toNodeConfig as n, addNode as o, deleteEdge as p, GraphInstance as r, createGraph as s, toEdgeConfig as t, createGraphNode as u, hasEdge as v, updateEntities as x, hasNode as y };
|