@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
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as getEdgeMode } from "../../mode-D8OnHFBk.mjs";
|
|
2
|
+
import { n as createFormatConverter } from "../../converter-DyCJJfTe.mjs";
|
|
2
3
|
import { XMLBuilder, XMLParser } from "fast-xml-parser";
|
|
3
4
|
|
|
4
5
|
//#region src/formats/graphml/index.ts
|
|
@@ -175,7 +176,9 @@ function toGraphML(graph) {
|
|
|
175
176
|
...data.length > 0 && { data }
|
|
176
177
|
};
|
|
177
178
|
});
|
|
179
|
+
const graphDirected = graph.mode !== "undirected";
|
|
178
180
|
const edges = graph.edges.map((edge) => {
|
|
181
|
+
const edgeDirected = getEdgeMode(graph, edge) !== "undirected";
|
|
179
182
|
const data = [];
|
|
180
183
|
if (edge.label) data.push({
|
|
181
184
|
"@_key": "label",
|
|
@@ -225,6 +228,7 @@ function toGraphML(graph) {
|
|
|
225
228
|
"@_id": edge.id,
|
|
226
229
|
"@_source": edge.sourceId,
|
|
227
230
|
"@_target": edge.targetId,
|
|
231
|
+
...edgeDirected !== graphDirected && { "@_directed": edgeDirected ? "true" : "false" },
|
|
228
232
|
...data.length > 0 && { data }
|
|
229
233
|
};
|
|
230
234
|
});
|
|
@@ -255,7 +259,7 @@ function toGraphML(graph) {
|
|
|
255
259
|
key: keys,
|
|
256
260
|
graph: {
|
|
257
261
|
"@_id": graph.id,
|
|
258
|
-
"@_edgedefault": graph.
|
|
262
|
+
"@_edgedefault": graph.mode === "undirected" ? "undirected" : "directed",
|
|
259
263
|
...graphData.length > 0 && { data: graphData },
|
|
260
264
|
node: nodes,
|
|
261
265
|
edge: edges
|
|
@@ -265,7 +269,8 @@ function toGraphML(graph) {
|
|
|
265
269
|
return new XMLBuilder({
|
|
266
270
|
ignoreAttributes: false,
|
|
267
271
|
format: true,
|
|
268
|
-
suppressEmptyNode: true
|
|
272
|
+
suppressEmptyNode: true,
|
|
273
|
+
suppressBooleanAttributes: false
|
|
269
274
|
}).build(obj);
|
|
270
275
|
}
|
|
271
276
|
function fromGraphML(xml) {
|
|
@@ -276,8 +281,12 @@ function fromGraphML(xml) {
|
|
|
276
281
|
"node",
|
|
277
282
|
"edge",
|
|
278
283
|
"data",
|
|
279
|
-
"key"
|
|
280
|
-
|
|
284
|
+
"key",
|
|
285
|
+
"graph",
|
|
286
|
+
"port"
|
|
287
|
+
].includes(name),
|
|
288
|
+
parseTagValue: false,
|
|
289
|
+
trimValues: false
|
|
281
290
|
});
|
|
282
291
|
let parsed;
|
|
283
292
|
try {
|
|
@@ -287,55 +296,84 @@ function fromGraphML(xml) {
|
|
|
287
296
|
}
|
|
288
297
|
const graphml = parsed?.graphml;
|
|
289
298
|
if (!graphml) throw new Error("GraphML: missing <graphml> root element");
|
|
290
|
-
const graphEl = graphml.graph;
|
|
299
|
+
const graphEl = asArray(graphml.graph)[0];
|
|
291
300
|
if (!graphEl) throw new Error("GraphML: missing <graph> element");
|
|
292
301
|
const graphType = graphEl["@_edgedefault"] === "undirected" ? "undirected" : "directed";
|
|
293
302
|
const graphDataMap = parseDataElements(graphEl.data);
|
|
294
303
|
const graphData = graphDataMap.graphData !== void 0 ? tryParseJSON(graphDataMap.graphData) : void 0;
|
|
295
|
-
const
|
|
304
|
+
const nodeEntries = [];
|
|
305
|
+
const edgeEls = [];
|
|
306
|
+
function collectGraphContents(gEl, parentId) {
|
|
307
|
+
for (const nodeEl of asArray(gEl.node)) {
|
|
308
|
+
nodeEntries.push({
|
|
309
|
+
el: nodeEl,
|
|
310
|
+
structuralParentId: parentId
|
|
311
|
+
});
|
|
312
|
+
for (const subgraphEl of asArray(nodeEl.graph)) collectGraphContents(subgraphEl, String(nodeEl["@_id"]));
|
|
313
|
+
}
|
|
314
|
+
edgeEls.push(...asArray(gEl.edge));
|
|
315
|
+
}
|
|
316
|
+
collectGraphContents(graphEl, null);
|
|
317
|
+
const nodes = nodeEntries.map(({ el: nodeEl, structuralParentId }) => {
|
|
296
318
|
const dataMap = parseDataElements(nodeEl.data);
|
|
319
|
+
const id = String(nodeEl["@_id"]);
|
|
297
320
|
const node = {
|
|
298
321
|
type: "node",
|
|
299
|
-
id
|
|
300
|
-
parentId: dataMap.parentId ??
|
|
322
|
+
id,
|
|
323
|
+
parentId: dataMap.parentId ?? structuralParentId,
|
|
301
324
|
initialNodeId: dataMap.initialNodeId ?? null,
|
|
302
325
|
label: dataMap.label ?? "",
|
|
303
326
|
data: dataMap.data !== void 0 ? tryParseJSON(dataMap.data) : void 0
|
|
304
327
|
};
|
|
305
|
-
if (dataMap.x !== void 0) node.x = parseNumber(dataMap.x);
|
|
306
|
-
if (dataMap.y !== void 0) node.y = parseNumber(dataMap.y);
|
|
307
|
-
if (dataMap.width !== void 0) node.width = parseNumber(dataMap.width);
|
|
308
|
-
if (dataMap.height !== void 0) node.height = parseNumber(dataMap.height);
|
|
328
|
+
if (dataMap.x !== void 0) node.x = parseNumber(dataMap.x, "x", "node", id);
|
|
329
|
+
if (dataMap.y !== void 0) node.y = parseNumber(dataMap.y, "y", "node", id);
|
|
330
|
+
if (dataMap.width !== void 0) node.width = parseNumber(dataMap.width, "width", "node", id);
|
|
331
|
+
if (dataMap.height !== void 0) node.height = parseNumber(dataMap.height, "height", "node", id);
|
|
309
332
|
if (dataMap.shape !== void 0) node.shape = dataMap.shape;
|
|
310
333
|
if (dataMap.color !== void 0) node.color = dataMap.color;
|
|
311
334
|
if (dataMap.style !== void 0) node.style = tryParseJSON(dataMap.style);
|
|
312
335
|
if (dataMap.ports !== void 0) node.ports = tryParseJSON(dataMap.ports);
|
|
336
|
+
else if (nodeEl.port !== void 0) node.ports = collectPorts(nodeEl.port);
|
|
313
337
|
return node;
|
|
314
338
|
});
|
|
315
|
-
const
|
|
339
|
+
const usedEdgeIds = new Set(edgeEls.filter((edgeEl) => edgeEl["@_id"] != null).map((edgeEl) => String(edgeEl["@_id"])));
|
|
340
|
+
const edges = edgeEls.map((edgeEl, i) => {
|
|
316
341
|
const dataMap = parseDataElements(edgeEl.data);
|
|
342
|
+
const source = String(edgeEl["@_source"]);
|
|
343
|
+
const target = String(edgeEl["@_target"]);
|
|
344
|
+
let id;
|
|
345
|
+
if (edgeEl["@_id"] != null) id = String(edgeEl["@_id"]);
|
|
346
|
+
else {
|
|
347
|
+
id = `${source}-${target}-${i}`;
|
|
348
|
+
for (let suffix = 0; usedEdgeIds.has(id); suffix++) id = `${source}-${target}-${i}#e${suffix}`;
|
|
349
|
+
usedEdgeIds.add(id);
|
|
350
|
+
}
|
|
317
351
|
const edge = {
|
|
318
352
|
type: "edge",
|
|
319
|
-
id
|
|
320
|
-
sourceId:
|
|
321
|
-
targetId:
|
|
353
|
+
id,
|
|
354
|
+
sourceId: source,
|
|
355
|
+
targetId: target,
|
|
322
356
|
label: dataMap.label ?? "",
|
|
323
357
|
data: dataMap.data !== void 0 ? tryParseJSON(dataMap.data) : void 0
|
|
324
358
|
};
|
|
325
|
-
if (dataMap.weight !== void 0) edge.weight = parseNumber(dataMap.weight);
|
|
326
|
-
if (dataMap.x !== void 0) edge.x = parseNumber(dataMap.x);
|
|
327
|
-
if (dataMap.y !== void 0) edge.y = parseNumber(dataMap.y);
|
|
328
|
-
if (dataMap.width !== void 0) edge.width = parseNumber(dataMap.width);
|
|
329
|
-
if (dataMap.height !== void 0) edge.height = parseNumber(dataMap.height);
|
|
359
|
+
if (dataMap.weight !== void 0) edge.weight = parseNumber(dataMap.weight, "weight", "edge", id);
|
|
360
|
+
if (dataMap.x !== void 0) edge.x = parseNumber(dataMap.x, "x", "edge", id);
|
|
361
|
+
if (dataMap.y !== void 0) edge.y = parseNumber(dataMap.y, "y", "edge", id);
|
|
362
|
+
if (dataMap.width !== void 0) edge.width = parseNumber(dataMap.width, "width", "edge", id);
|
|
363
|
+
if (dataMap.height !== void 0) edge.height = parseNumber(dataMap.height, "height", "edge", id);
|
|
330
364
|
if (dataMap.color !== void 0) edge.color = dataMap.color;
|
|
331
365
|
if (dataMap.style !== void 0) edge.style = tryParseJSON(dataMap.style);
|
|
332
366
|
if (dataMap.sourcePort !== void 0) edge.sourcePort = dataMap.sourcePort;
|
|
367
|
+
else if (edgeEl["@_sourceport"] != null) edge.sourcePort = String(edgeEl["@_sourceport"]);
|
|
333
368
|
if (dataMap.targetPort !== void 0) edge.targetPort = dataMap.targetPort;
|
|
369
|
+
else if (edgeEl["@_targetport"] != null) edge.targetPort = String(edgeEl["@_targetport"]);
|
|
370
|
+
const directedAttr = edgeEl["@_directed"];
|
|
371
|
+
if (directedAttr !== void 0) edge.mode = String(directedAttr) === "false" ? "undirected" : "directed";
|
|
334
372
|
return edge;
|
|
335
373
|
});
|
|
336
374
|
const graph = {
|
|
337
375
|
id: String(graphEl["@_id"] ?? ""),
|
|
338
|
-
|
|
376
|
+
mode: graphType,
|
|
339
377
|
initialNodeId: graphDataMap.graphInitialNodeId ?? null,
|
|
340
378
|
nodes,
|
|
341
379
|
edges,
|
|
@@ -361,8 +399,28 @@ function tryParseJSON(str) {
|
|
|
361
399
|
return str;
|
|
362
400
|
}
|
|
363
401
|
}
|
|
364
|
-
|
|
365
|
-
|
|
402
|
+
/**
|
|
403
|
+
* Flattens native GraphML <port> elements (which may nest) into our port
|
|
404
|
+
* shape. Standard GraphML ports carry only a name; direction is unknowable,
|
|
405
|
+
* so they import as advisory 'inout' with null data.
|
|
406
|
+
*/
|
|
407
|
+
function collectPorts(portEls) {
|
|
408
|
+
const ports = [];
|
|
409
|
+
for (const portEl of asArray(portEls)) {
|
|
410
|
+
if (portEl?.["@_name"] == null) continue;
|
|
411
|
+
ports.push({
|
|
412
|
+
name: String(portEl["@_name"]),
|
|
413
|
+
direction: "inout",
|
|
414
|
+
data: null
|
|
415
|
+
});
|
|
416
|
+
if (portEl.port !== void 0) ports.push(...collectPorts(portEl.port) ?? []);
|
|
417
|
+
}
|
|
418
|
+
return ports;
|
|
419
|
+
}
|
|
420
|
+
function parseNumber(value, key, kind, ownerId) {
|
|
421
|
+
const parsed = Number(value);
|
|
422
|
+
if (Number.isNaN(parsed)) throw new Error(`GraphML: <data key="${key}"> value "${value}" on ${kind} "${ownerId}" is not a number. Fix the value or remove the attribute.`);
|
|
423
|
+
return parsed;
|
|
366
424
|
}
|
|
367
425
|
function parseDirection(value) {
|
|
368
426
|
return [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createFormatConverter } from "../../converter-
|
|
1
|
+
import { n as createFormatConverter } from "../../converter-DyCJJfTe.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/formats/jgf/index.ts
|
|
4
4
|
/**
|
|
@@ -20,13 +20,14 @@ import { n as createFormatConverter } from "../../converter-Dspillnn.mjs";
|
|
|
20
20
|
*/
|
|
21
21
|
function toJGF(graph) {
|
|
22
22
|
const metadata = {};
|
|
23
|
+
if (graph.mode === "bidirectional") metadata.mode = graph.mode;
|
|
23
24
|
if (graph.initialNodeId) metadata.initialNodeId = graph.initialNodeId;
|
|
24
25
|
if (graph.data !== void 0) metadata.data = graph.data;
|
|
25
26
|
if (graph.direction) metadata.direction = graph.direction;
|
|
26
27
|
if (graph.style !== void 0) metadata.style = graph.style;
|
|
27
28
|
return { graph: {
|
|
28
29
|
id: graph.id || void 0,
|
|
29
|
-
directed: graph.
|
|
30
|
+
directed: graph.mode !== "undirected",
|
|
30
31
|
...Object.keys(metadata).length > 0 && { metadata },
|
|
31
32
|
nodes: graph.nodes.map((n) => {
|
|
32
33
|
const meta = {};
|
|
@@ -49,6 +50,7 @@ function toJGF(graph) {
|
|
|
49
50
|
}),
|
|
50
51
|
edges: graph.edges.map((e) => {
|
|
51
52
|
const meta = {};
|
|
53
|
+
if (e.mode) meta.mode = e.mode;
|
|
52
54
|
if (e.data !== void 0) meta.data = e.data;
|
|
53
55
|
if (e.weight !== void 0) meta.weight = e.weight;
|
|
54
56
|
if (e.x !== void 0) meta.x = e.x;
|
|
@@ -93,7 +95,7 @@ function fromJGF(jgf) {
|
|
|
93
95
|
if (!Array.isArray(g.edges)) throw new Error("JGF: \"graph.edges\" must be an array");
|
|
94
96
|
return {
|
|
95
97
|
id: g.id ?? "",
|
|
96
|
-
|
|
98
|
+
mode: g.metadata?.mode ?? (g.directed === false ? "undirected" : "directed"),
|
|
97
99
|
initialNodeId: g.metadata?.initialNodeId ?? null,
|
|
98
100
|
data: g.metadata?.data,
|
|
99
101
|
...g.metadata?.direction && { direction: g.metadata.direction },
|
|
@@ -120,6 +122,7 @@ function fromJGF(jgf) {
|
|
|
120
122
|
sourceId: e.source,
|
|
121
123
|
targetId: e.target,
|
|
122
124
|
label: e.label ?? "",
|
|
125
|
+
...e.metadata?.mode && { mode: e.metadata.mode },
|
|
123
126
|
data: e.metadata?.data,
|
|
124
127
|
...e.metadata?.weight !== void 0 && { weight: e.metadata.weight },
|
|
125
128
|
...e.metadata?.x !== void 0 && { x: e.metadata.x },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createFormatConverter } from "../../converter-
|
|
1
|
+
import { n as createFormatConverter } from "../../converter-DyCJJfTe.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/formats/mermaid/shared.ts
|
|
4
4
|
const MERMAID_TO_DIRECTION = {
|
|
@@ -16,11 +16,11 @@ const DIRECTION_TO_MERMAID = {
|
|
|
16
16
|
};
|
|
17
17
|
/** Escape a label for Mermaid output (quotes special chars). */
|
|
18
18
|
function escapeMermaidLabel(s) {
|
|
19
|
-
return s.replace(/\\/g, "\\\\").replace(/"/g, "#quot;").replace(/;/g, "#59;").replace(/#(?!quot;|59;|35;)/g, "#35;");
|
|
19
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, "#quot;").replace(/;/g, "#59;").replace(/\|/g, "#124;").replace(/#(?!quot;|59;|35;|124;)/g, "#35;");
|
|
20
20
|
}
|
|
21
21
|
/** Unescape a Mermaid label back to plain text. */
|
|
22
22
|
function unescapeMermaidLabel(s) {
|
|
23
|
-
return s.replace(/#quot;/g, "\"").replace(/#59;/g, ";").replace(/#35;/g, "#");
|
|
23
|
+
return s.replace(/#quot;/g, "\"").replace(/#59;/g, ";").replace(/#124;/g, "|").replace(/#35;/g, "#");
|
|
24
24
|
}
|
|
25
25
|
/** Generate a deterministic edge ID from source, target, and index. */
|
|
26
26
|
function generateEdgeId(sourceId, targetId, index) {
|
|
@@ -497,7 +497,7 @@ function fromMermaidSequence(input) {
|
|
|
497
497
|
}
|
|
498
498
|
return {
|
|
499
499
|
id: "",
|
|
500
|
-
|
|
500
|
+
mode: "directed",
|
|
501
501
|
initialNodeId: null,
|
|
502
502
|
nodes: Array.from(nodeMap.values()),
|
|
503
503
|
edges,
|
|
@@ -1292,7 +1292,7 @@ function fromMermaidFlowchart(input) {
|
|
|
1292
1292
|
}
|
|
1293
1293
|
return {
|
|
1294
1294
|
id: "",
|
|
1295
|
-
|
|
1295
|
+
mode: "directed",
|
|
1296
1296
|
initialNodeId: null,
|
|
1297
1297
|
nodes: Array.from(nodeMap.values()),
|
|
1298
1298
|
edges,
|
|
@@ -1389,6 +1389,19 @@ const mermaidFlowchartConverter = createFormatConverter(toMermaidFlowchart, from
|
|
|
1389
1389
|
//#endregion
|
|
1390
1390
|
//#region src/formats/mermaid/state.ts
|
|
1391
1391
|
/**
|
|
1392
|
+
* Whether a node is a parallel-region marker. Region nodes are generated with
|
|
1393
|
+
* the exact id `${parentId}_region_${integer}`, so a node only counts when its
|
|
1394
|
+
* id matches that structure for its *actual* parent and that parent is a
|
|
1395
|
+
* parallel state. User ids that merely contain `_region_` (e.g.
|
|
1396
|
+
* `foo_region_bar`) are ordinary states.
|
|
1397
|
+
*/
|
|
1398
|
+
function isParallelRegionNode(node, nodesById) {
|
|
1399
|
+
if (!node || !node.parentId) return false;
|
|
1400
|
+
if (nodesById.get(node.parentId)?.data?.stateType !== "parallel") return false;
|
|
1401
|
+
const prefix = `${node.parentId}_region_`;
|
|
1402
|
+
return node.id.startsWith(prefix) && /^\d+$/.test(node.id.slice(prefix.length));
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1392
1405
|
* Parses a Mermaid state diagram string into a Graph.
|
|
1393
1406
|
*
|
|
1394
1407
|
* @example
|
|
@@ -1410,6 +1423,7 @@ function fromMermaidState(input) {
|
|
|
1410
1423
|
let startCounter = 0;
|
|
1411
1424
|
let endCounter = 0;
|
|
1412
1425
|
let graphDirection;
|
|
1426
|
+
let initialNodeId = null;
|
|
1413
1427
|
const classDefs = {};
|
|
1414
1428
|
const classAssignments = {};
|
|
1415
1429
|
const parentStack = [null];
|
|
@@ -1474,6 +1488,7 @@ function fromMermaidState(input) {
|
|
|
1474
1488
|
const stateId = compositeStateAsMatch[2];
|
|
1475
1489
|
const node = ensureNode(stateId);
|
|
1476
1490
|
node.data.description = description;
|
|
1491
|
+
node.label = description;
|
|
1477
1492
|
parentStack.push(stateId);
|
|
1478
1493
|
continue;
|
|
1479
1494
|
}
|
|
@@ -1492,12 +1507,13 @@ function fromMermaidState(input) {
|
|
|
1492
1507
|
const stateId = stateAsMatch[2];
|
|
1493
1508
|
const node = ensureNode(stateId);
|
|
1494
1509
|
node.data.description = description;
|
|
1510
|
+
node.label = description;
|
|
1495
1511
|
continue;
|
|
1496
1512
|
}
|
|
1497
1513
|
if (line === "}" || line === "end") {
|
|
1498
1514
|
if (parentStack.length > 1) {
|
|
1499
1515
|
const top = parentStack[parentStack.length - 1];
|
|
1500
|
-
if (top &&
|
|
1516
|
+
if (top && isParallelRegionNode(nodeMap.get(top), nodeMap)) parentStack.pop();
|
|
1501
1517
|
parentStack.pop();
|
|
1502
1518
|
}
|
|
1503
1519
|
continue;
|
|
@@ -1506,7 +1522,7 @@ function fromMermaidState(input) {
|
|
|
1506
1522
|
const compositeParent = (() => {
|
|
1507
1523
|
for (let s = parentStack.length - 1; s >= 0; s--) {
|
|
1508
1524
|
const id = parentStack[s];
|
|
1509
|
-
if (id && !
|
|
1525
|
+
if (id && !isParallelRegionNode(nodeMap.get(id), nodeMap)) return id;
|
|
1510
1526
|
}
|
|
1511
1527
|
return null;
|
|
1512
1528
|
})();
|
|
@@ -1540,7 +1556,7 @@ function fromMermaidState(input) {
|
|
|
1540
1556
|
regionCounters.set(compositeParent, 2);
|
|
1541
1557
|
} else {
|
|
1542
1558
|
const top = parentStack[parentStack.length - 1];
|
|
1543
|
-
if (top &&
|
|
1559
|
+
if (top && isParallelRegionNode(nodeMap.get(top), nodeMap)) parentStack.pop();
|
|
1544
1560
|
const nextRegionId = `${compositeParent}_region_${regionIndex}`;
|
|
1545
1561
|
const nextRegion = {
|
|
1546
1562
|
type: "node",
|
|
@@ -1588,6 +1604,7 @@ function fromMermaidState(input) {
|
|
|
1588
1604
|
let targetId = transMatch[3];
|
|
1589
1605
|
const targetClass = transMatch[4];
|
|
1590
1606
|
const label = transMatch[5]?.trim() ?? "";
|
|
1607
|
+
const isTopLevelStart = sourceId === "[*]" && parentStack[parentStack.length - 1] === null;
|
|
1591
1608
|
if (sourceId === "[*]") sourceId = resolveStarNode("source");
|
|
1592
1609
|
else {
|
|
1593
1610
|
ensureNode(sourceId);
|
|
@@ -1603,6 +1620,7 @@ function fromMermaidState(input) {
|
|
|
1603
1620
|
if (!classAssignments[targetId]) classAssignments[targetId] = [];
|
|
1604
1621
|
classAssignments[targetId].push(targetClass);
|
|
1605
1622
|
}
|
|
1623
|
+
if (isTopLevelStart && initialNodeId === null) initialNodeId = targetId;
|
|
1606
1624
|
}
|
|
1607
1625
|
const edgeId = generateEdgeId(sourceId, targetId, edgeCounter++);
|
|
1608
1626
|
edges.push({
|
|
@@ -1675,8 +1693,8 @@ function fromMermaidState(input) {
|
|
|
1675
1693
|
}
|
|
1676
1694
|
return {
|
|
1677
1695
|
id: "",
|
|
1678
|
-
|
|
1679
|
-
initialNodeId
|
|
1696
|
+
mode: "directed",
|
|
1697
|
+
initialNodeId,
|
|
1680
1698
|
nodes: Array.from(nodeMap.values()),
|
|
1681
1699
|
edges,
|
|
1682
1700
|
data: {
|
|
@@ -1699,6 +1717,7 @@ function toMermaidState(graph) {
|
|
|
1699
1717
|
const mDir = DIRECTION_TO_MERMAID[graph.direction];
|
|
1700
1718
|
if (mDir) lines.push(` direction ${mDir}`);
|
|
1701
1719
|
}
|
|
1720
|
+
const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
1702
1721
|
const childrenMap = /* @__PURE__ */ new Map();
|
|
1703
1722
|
for (const node of graph.nodes) {
|
|
1704
1723
|
const pid = node.parentId ?? null;
|
|
@@ -1707,28 +1726,44 @@ function toMermaidState(graph) {
|
|
|
1707
1726
|
}
|
|
1708
1727
|
const isParent = /* @__PURE__ */ new Set();
|
|
1709
1728
|
for (const node of graph.nodes) if (childrenMap.has(node.id)) isParent.add(node.id);
|
|
1729
|
+
const referencedByEdge = /* @__PURE__ */ new Set();
|
|
1730
|
+
for (const edge of graph.edges) {
|
|
1731
|
+
referencedByEdge.add(edge.sourceId);
|
|
1732
|
+
referencedByEdge.add(edge.targetId);
|
|
1733
|
+
}
|
|
1734
|
+
if (graph.initialNodeId) referencedByEdge.add(graph.initialNodeId);
|
|
1710
1735
|
function writeNodes(parentId, indent) {
|
|
1711
1736
|
const children = childrenMap.get(parentId) ?? [];
|
|
1712
1737
|
for (const node of children) {
|
|
1713
1738
|
if (node.data?.isStart || node.data?.isEnd) continue;
|
|
1714
|
-
if (node
|
|
1715
|
-
|
|
1739
|
+
if (isParallelRegionNode(node, nodesById)) continue;
|
|
1740
|
+
const description = node.data?.description ?? (node.label && node.label !== node.id ? node.label : void 0);
|
|
1741
|
+
let declared = false;
|
|
1742
|
+
if (node.data?.stateType && node.data.stateType !== "parallel") {
|
|
1743
|
+
lines.push(`${indent}state ${node.id} <<${node.data.stateType}>>`);
|
|
1744
|
+
declared = true;
|
|
1745
|
+
}
|
|
1716
1746
|
if (isParent.has(node.id)) {
|
|
1717
|
-
const stateDecl =
|
|
1747
|
+
const stateDecl = description ? `state "${escapeMermaidLabel(description)}" as ${node.id} {` : `state ${node.id} {`;
|
|
1718
1748
|
lines.push(`${indent}${stateDecl}`);
|
|
1719
1749
|
if (node.data?.direction) {
|
|
1720
1750
|
const mDir = DIRECTION_TO_MERMAID[node.data.direction];
|
|
1721
1751
|
if (mDir) lines.push(`${indent} direction ${mDir}`);
|
|
1722
1752
|
}
|
|
1723
1753
|
if (node.data?.stateType === "parallel") {
|
|
1724
|
-
const regions = (childrenMap.get(node.id) ?? []).filter((r) => r
|
|
1754
|
+
const regions = (childrenMap.get(node.id) ?? []).filter((r) => isParallelRegionNode(r, nodesById));
|
|
1725
1755
|
for (let ri = 0; ri < regions.length; ri++) {
|
|
1726
1756
|
if (ri > 0) lines.push(`${indent} --`);
|
|
1727
1757
|
writeNodes(regions[ri].id, indent + " ");
|
|
1728
1758
|
}
|
|
1729
1759
|
} else writeNodes(node.id, indent + " ");
|
|
1730
1760
|
lines.push(`${indent}}`);
|
|
1731
|
-
|
|
1761
|
+
declared = true;
|
|
1762
|
+
} else if (description) {
|
|
1763
|
+
lines.push(`${indent}state "${escapeMermaidLabel(description)}" as ${node.id}`);
|
|
1764
|
+
declared = true;
|
|
1765
|
+
}
|
|
1766
|
+
if (!declared && !referencedByEdge.has(node.id)) lines.push(`${indent}${node.id}`);
|
|
1732
1767
|
if (node.data?.notes) for (const note of node.data.notes) if (note.format === "block" || note.text.includes("\n")) {
|
|
1733
1768
|
lines.push(`${indent}note ${note.position} of ${node.id}`);
|
|
1734
1769
|
for (const noteLine of note.text.split("\n")) lines.push(`${indent} ${escapeMermaidLabel(noteLine)}`);
|
|
@@ -1737,6 +1772,8 @@ function toMermaidState(graph) {
|
|
|
1737
1772
|
}
|
|
1738
1773
|
}
|
|
1739
1774
|
writeNodes(null, " ");
|
|
1775
|
+
const hasTopLevelStart = graph.nodes.some((n) => n.data?.isStart && (n.parentId ?? null) === null);
|
|
1776
|
+
if (graph.initialNodeId && !hasTopLevelStart) lines.push(` [*] --> ${graph.initialNodeId}`);
|
|
1740
1777
|
for (const edge of graph.edges) {
|
|
1741
1778
|
let sourceId = edge.sourceId;
|
|
1742
1779
|
let targetId = edge.targetId;
|
|
@@ -2053,7 +2090,7 @@ function fromMermaidClass(input) {
|
|
|
2053
2090
|
}
|
|
2054
2091
|
return {
|
|
2055
2092
|
id: "",
|
|
2056
|
-
|
|
2093
|
+
mode: "directed",
|
|
2057
2094
|
initialNodeId: null,
|
|
2058
2095
|
nodes: Array.from(nodeMap.values()),
|
|
2059
2096
|
edges,
|
|
@@ -2251,7 +2288,7 @@ function fromMermaidER(input) {
|
|
|
2251
2288
|
}
|
|
2252
2289
|
return {
|
|
2253
2290
|
id: "",
|
|
2254
|
-
|
|
2291
|
+
mode: "directed",
|
|
2255
2292
|
initialNodeId: null,
|
|
2256
2293
|
nodes: Array.from(nodeMap.values()),
|
|
2257
2294
|
edges,
|
|
@@ -2409,7 +2446,7 @@ function fromMermaidMindmap(input) {
|
|
|
2409
2446
|
}
|
|
2410
2447
|
return {
|
|
2411
2448
|
id: "",
|
|
2412
|
-
|
|
2449
|
+
mode: "directed",
|
|
2413
2450
|
initialNodeId: null,
|
|
2414
2451
|
nodes: Array.from(nodeMap.values()),
|
|
2415
2452
|
edges,
|
|
@@ -2608,7 +2645,7 @@ function fromMermaidBlock(input) {
|
|
|
2608
2645
|
}
|
|
2609
2646
|
return {
|
|
2610
2647
|
id: "",
|
|
2611
|
-
|
|
2648
|
+
mode: "directed",
|
|
2612
2649
|
initialNodeId: null,
|
|
2613
2650
|
nodes: Array.from(nodeMap.values()),
|
|
2614
2651
|
edges,
|
|
@@ -2720,7 +2757,7 @@ function fromMermaidIshikawa(input) {
|
|
|
2720
2757
|
}
|
|
2721
2758
|
return {
|
|
2722
2759
|
id: "",
|
|
2723
|
-
|
|
2760
|
+
mode: "directed",
|
|
2724
2761
|
initialNodeId: null,
|
|
2725
2762
|
nodes,
|
|
2726
2763
|
edges,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createFormatConverter } from "../../converter-
|
|
1
|
+
import { n as createFormatConverter } from "../../converter-DyCJJfTe.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/formats/tgf/index.ts
|
|
4
4
|
/**
|
|
@@ -81,7 +81,7 @@ function fromTGF(tgf) {
|
|
|
81
81
|
}
|
|
82
82
|
return {
|
|
83
83
|
id: "",
|
|
84
|
-
|
|
84
|
+
mode: "directed",
|
|
85
85
|
initialNodeId: null,
|
|
86
86
|
nodes,
|
|
87
87
|
edges,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { L as VisualGraphFormatConverter, P as VisualGraph } from "../../types-DNYdIU21.mjs";
|
|
2
2
|
import { EdgeBase, NodeBase } from "@xyflow/system";
|
|
3
3
|
|
|
4
4
|
//#region src/formats/xyflow/index.d.ts
|
|
@@ -19,8 +19,33 @@ function readMetadata(value) {
|
|
|
19
19
|
}
|
|
20
20
|
function readUserData(value) {
|
|
21
21
|
const metadata = readMetadata(value);
|
|
22
|
-
if (metadata
|
|
23
|
-
return
|
|
22
|
+
if (!metadata) return value;
|
|
23
|
+
return "data" in metadata ? metadata.data : void 0;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* React Flow requires parent nodes to appear before their children in the
|
|
27
|
+
* nodes array. Reorders iteratively, keeping authored order otherwise: each
|
|
28
|
+
* pass emits nodes whose parent is already emitted (or absent). Nodes left
|
|
29
|
+
* over by a parentId cycle are appended in authored order rather than hanging.
|
|
30
|
+
*/
|
|
31
|
+
function orderParentsFirst(nodes) {
|
|
32
|
+
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
33
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
34
|
+
const result = [];
|
|
35
|
+
let remaining = nodes;
|
|
36
|
+
while (remaining.length > 0) {
|
|
37
|
+
const deferred = [];
|
|
38
|
+
for (const node of remaining) if (!node.parentId || emitted.has(node.parentId) || !nodeIds.has(node.parentId)) {
|
|
39
|
+
result.push(node);
|
|
40
|
+
emitted.add(node.id);
|
|
41
|
+
} else deferred.push(node);
|
|
42
|
+
if (deferred.length === remaining.length) {
|
|
43
|
+
result.push(...deferred);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
remaining = deferred;
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
24
49
|
}
|
|
25
50
|
/**
|
|
26
51
|
* Converts a visual graph to xyflow (React Flow / Svelte Flow) format.
|
|
@@ -46,13 +71,13 @@ function toXYFlow(graph) {
|
|
|
46
71
|
return {
|
|
47
72
|
data: { [STATELYAI_METADATA_KEY]: { graph: {
|
|
48
73
|
id: graph.id,
|
|
49
|
-
|
|
74
|
+
mode: graph.mode,
|
|
50
75
|
initialNodeId: graph.initialNodeId,
|
|
51
76
|
data: graph.data,
|
|
52
77
|
direction: graph.direction,
|
|
53
78
|
style: graph.style
|
|
54
79
|
} } },
|
|
55
|
-
nodes: graph.nodes.map((n) => {
|
|
80
|
+
nodes: orderParentsFirst(graph.nodes.map((n) => {
|
|
56
81
|
const node = {
|
|
57
82
|
id: n.id,
|
|
58
83
|
position: {
|
|
@@ -72,7 +97,7 @@ function toXYFlow(graph) {
|
|
|
72
97
|
if (n.width !== void 0) node.width = n.width;
|
|
73
98
|
if (n.height !== void 0) node.height = n.height;
|
|
74
99
|
return node;
|
|
75
|
-
}),
|
|
100
|
+
})),
|
|
76
101
|
edges: graph.edges.map((e) => {
|
|
77
102
|
const edge = {
|
|
78
103
|
id: e.id,
|
|
@@ -83,6 +108,7 @@ function toXYFlow(graph) {
|
|
|
83
108
|
if (e.targetPort) edge.targetHandle = e.targetPort;
|
|
84
109
|
edge.data = withMetadata(e.data, { edge: {
|
|
85
110
|
label: e.label,
|
|
111
|
+
mode: e.mode,
|
|
86
112
|
weight: e.weight,
|
|
87
113
|
color: e.color,
|
|
88
114
|
style: e.style,
|
|
@@ -119,7 +145,7 @@ function fromXYFlow(flow) {
|
|
|
119
145
|
const graphMetadata = readMetadata(flow.data)?.graph;
|
|
120
146
|
return {
|
|
121
147
|
id: graphMetadata?.id?.toString() ?? "",
|
|
122
|
-
|
|
148
|
+
mode: graphMetadata?.mode ?? "directed",
|
|
123
149
|
initialNodeId: graphMetadata && "initialNodeId" in graphMetadata ? graphMetadata.initialNodeId : null,
|
|
124
150
|
data: graphMetadata && "data" in graphMetadata ? graphMetadata.data : void 0,
|
|
125
151
|
direction: graphMetadata?.direction ?? "down",
|
|
@@ -158,6 +184,7 @@ function fromXYFlow(flow) {
|
|
|
158
184
|
y: metadata?.y ?? 0,
|
|
159
185
|
width: metadata?.width ?? 0,
|
|
160
186
|
height: metadata?.height ?? 0,
|
|
187
|
+
...metadata?.mode !== void 0 && { mode: metadata.mode },
|
|
161
188
|
...metadata?.weight !== void 0 && { weight: metadata.weight },
|
|
162
189
|
...metadata?.color !== void 0 && { color: metadata.color },
|
|
163
190
|
...metadata?.style !== void 0 && { style: metadata.style }
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { d as Graph, g as GraphFormatConverter } from "./types-DNYdIU21.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/formats/converter/index.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a `GraphFormatConverter` from a pair of `to`/`from` functions.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { createFormatConverter } from '@statelyai/graph';
|
|
11
|
+
*
|
|
12
|
+
* const yamlConverter = createFormatConverter(
|
|
13
|
+
* (graph) => toYAML(graph),
|
|
14
|
+
* (yaml) => fromYAML(yaml),
|
|
15
|
+
* );
|
|
16
|
+
*
|
|
17
|
+
* const yaml = yamlConverter.to(graph);
|
|
18
|
+
* const graph = yamlConverter.from(yaml);
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
declare function createFormatConverter<TSerial, N = any, E = any, G = any>(to: (graph: Graph<N, E, G>) => TSerial, from: (input: TSerial) => Graph<N, E, G>): GraphFormatConverter<TSerial, N, E, G>;
|
|
22
|
+
/**
|
|
23
|
+
* Bidirectional converter for adjacency-list format (`Record<string, string[]>`).
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { adjacencyListConverter, createGraph } from '@statelyai/graph';
|
|
28
|
+
*
|
|
29
|
+
* const graph = createGraph({
|
|
30
|
+
* nodes: { a: {}, b: {} },
|
|
31
|
+
* edges: [{ source: 'a', target: 'b' }],
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* const adj = adjacencyListConverter.to(graph);
|
|
35
|
+
* // { a: ['b'], b: [] }
|
|
36
|
+
*
|
|
37
|
+
* const roundTripped = adjacencyListConverter.from(adj);
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare const adjacencyListConverter: GraphFormatConverter<Record<string, string[]>>;
|
|
41
|
+
/**
|
|
42
|
+
* Bidirectional converter for edge-list format (`[source, target][]`).
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* import { edgeListConverter, createGraph } from '@statelyai/graph';
|
|
47
|
+
*
|
|
48
|
+
* const graph = createGraph({
|
|
49
|
+
* nodes: { a: {}, b: {} },
|
|
50
|
+
* edges: [{ source: 'a', target: 'b' }],
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* const edges = edgeListConverter.to(graph);
|
|
54
|
+
* // [['a', 'b']]
|
|
55
|
+
*
|
|
56
|
+
* const roundTripped = edgeListConverter.from(edges);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
declare const edgeListConverter: GraphFormatConverter<[string, string][]>;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { createFormatConverter as n, edgeListConverter as r, adjacencyListConverter as t };
|