@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.
Files changed (57) hide show
  1. package/README.md +57 -26
  2. package/dist/{adjacency-list-Ca0VjKIf.mjs → adjacency-list-GeL1Cu-L.mjs} +5 -3
  3. package/dist/{algorithms-BlM-qoJb.d.mts → algorithms-CsGNehct.d.mts} +137 -2
  4. package/dist/{algorithms-BNDQcHU3.mjs → algorithms-DF1pSQGv.mjs} +1494 -357
  5. package/dist/algorithms.d.mts +2 -2
  6. package/dist/algorithms.mjs +2 -2
  7. package/dist/{converter-Dspillnn.mjs → converter-DyCJJfTe.mjs} +2 -2
  8. package/dist/{edge-list-gKe8-iRa.mjs → edge-list-BcZ0h6zz.mjs} +1 -1
  9. package/dist/format-support.mjs +67 -11
  10. package/dist/formats/adjacency-list/index.d.mts +1 -1
  11. package/dist/formats/adjacency-list/index.mjs +1 -1
  12. package/dist/formats/converter/index.d.mts +1 -60
  13. package/dist/formats/converter/index.mjs +1 -1
  14. package/dist/formats/cytoscape/index.d.mts +1 -1
  15. package/dist/formats/cytoscape/index.mjs +5 -3
  16. package/dist/formats/d2/index.d.mts +109 -0
  17. package/dist/formats/d2/index.mjs +1100 -0
  18. package/dist/formats/d3/index.d.mts +2 -2
  19. package/dist/formats/d3/index.mjs +5 -3
  20. package/dist/formats/dot/index.d.mts +1 -1
  21. package/dist/formats/dot/index.mjs +24 -8
  22. package/dist/formats/edge-list/index.d.mts +1 -1
  23. package/dist/formats/edge-list/index.mjs +1 -1
  24. package/dist/formats/elk/index.d.mts +1 -1
  25. package/dist/formats/elk/index.mjs +23 -16
  26. package/dist/formats/gexf/index.d.mts +1 -1
  27. package/dist/formats/gexf/index.mjs +30 -17
  28. package/dist/formats/gml/index.d.mts +1 -1
  29. package/dist/formats/gml/index.mjs +22 -13
  30. package/dist/formats/graphml/index.d.mts +1 -1
  31. package/dist/formats/graphml/index.mjs +83 -25
  32. package/dist/formats/jgf/index.d.mts +1 -1
  33. package/dist/formats/jgf/index.mjs +6 -3
  34. package/dist/formats/mermaid/index.d.mts +1 -1
  35. package/dist/formats/mermaid/index.mjs +57 -20
  36. package/dist/formats/tgf/index.d.mts +1 -1
  37. package/dist/formats/tgf/index.mjs +2 -2
  38. package/dist/formats/xyflow/index.d.mts +1 -1
  39. package/dist/formats/xyflow/index.mjs +33 -6
  40. package/dist/index-D51lJnt2.d.mts +61 -0
  41. package/dist/index-DWmo1mIp.d.mts +697 -0
  42. package/dist/index.d.mts +6 -631
  43. package/dist/index.mjs +144 -295
  44. package/dist/mode-D8OnHFBk.mjs +15 -0
  45. package/dist/queries-BfXeTXRf.d.mts +547 -0
  46. package/dist/queries-KirMDR7e.mjs +980 -0
  47. package/dist/queries.d.mts +1 -514
  48. package/dist/queries.mjs +1 -766
  49. package/dist/schemas.d.mts +21 -10
  50. package/dist/schemas.mjs +35 -86
  51. package/dist/{types-CnZ01raw.d.mts → types-DNYdIU21.d.mts} +83 -11
  52. package/dist/validate-TtH-x3JV.mjs +190 -0
  53. package/package.json +14 -3
  54. package/schemas/edge.schema.json +11 -0
  55. package/schemas/graph.schema.json +24 -3
  56. package/schemas/node.schema.json +6 -0
  57. package/dist/indexing-DUl3kTqm.mjs +0 -137
@@ -1,4 +1,5 @@
1
- import { n as createFormatConverter } from "../../converter-Dspillnn.mjs";
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.type === "directed" ? "directed" : "undirected",
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
- ].includes(name)
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 nodes = asArray(graphEl.node).map((nodeEl) => {
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: String(nodeEl["@_id"]),
300
- parentId: dataMap.parentId ?? null,
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 edges = asArray(graphEl.edge).map((edgeEl) => {
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: String(edgeEl["@_id"]),
320
- sourceId: String(edgeEl["@_source"]),
321
- targetId: String(edgeEl["@_target"]),
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
- type: graphType,
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
- function parseNumber(value) {
365
- return Number(value);
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 { h as GraphFormatConverter, u as Graph } from "../../types-CnZ01raw.mjs";
1
+ import { d as Graph, g as GraphFormatConverter } from "../../types-DNYdIU21.mjs";
2
2
 
3
3
  //#region src/formats/jgf/index.d.ts
4
4
  interface JGFNode {
@@ -1,4 +1,4 @@
1
- import { n as createFormatConverter } from "../../converter-Dspillnn.mjs";
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.type === "directed",
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
- type: g.directed === false ? "undirected" : "directed",
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 { h as GraphFormatConverter, u as Graph } from "../../types-CnZ01raw.mjs";
1
+ import { d as Graph, g as GraphFormatConverter } from "../../types-DNYdIU21.mjs";
2
2
 
3
3
  //#region src/formats/mermaid/sequence.d.ts
4
4
  interface SequenceNodeData {
@@ -1,4 +1,4 @@
1
- import { n as createFormatConverter } from "../../converter-Dspillnn.mjs";
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
- type: "directed",
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
- type: "directed",
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 && top.includes("_region_")) parentStack.pop();
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 && !id.includes("_region_")) return 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 && top.includes("_region_")) parentStack.pop();
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
- type: "directed",
1679
- initialNodeId: null,
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.id.includes("_region_")) continue;
1715
- if (node.data?.stateType && node.data.stateType !== "parallel") lines.push(`${indent}state ${node.id} <<${node.data.stateType}>>`);
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 = node.data?.description ? `state "${escapeMermaidLabel(node.data.description)}" as ${node.id} {` : `state ${node.id} {`;
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.id.includes("_region_"));
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
- } else if (node.data?.description) lines.push(`${indent}state "${escapeMermaidLabel(node.data.description)}" as ${node.id}`);
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
- type: "directed",
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
- type: "directed",
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
- type: "directed",
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
- type: "directed",
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
- type: "directed",
2760
+ mode: "directed",
2724
2761
  initialNodeId: null,
2725
2762
  nodes,
2726
2763
  edges,
@@ -1,4 +1,4 @@
1
- import { h as GraphFormatConverter, u as Graph } from "../../types-CnZ01raw.mjs";
1
+ import { d as Graph, g as GraphFormatConverter } from "../../types-DNYdIU21.mjs";
2
2
 
3
3
  //#region src/formats/tgf/index.d.ts
4
4
 
@@ -1,4 +1,4 @@
1
- import { n as createFormatConverter } from "../../converter-Dspillnn.mjs";
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
- type: "directed",
84
+ mode: "directed",
85
85
  initialNodeId: null,
86
86
  nodes,
87
87
  edges,
@@ -1,4 +1,4 @@
1
- import { P as VisualGraphFormatConverter, j as VisualGraph } from "../../types-CnZ01raw.mjs";
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 && "data" in metadata) return metadata.data;
23
- return value;
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
- type: graph.type,
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
- type: graphMetadata?.type === "undirected" ? "undirected" : "directed",
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 };