@statelyai/graph 1.0.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 (53) hide show
  1. package/README.md +55 -26
  2. package/dist/{adjacency-list-VsUaH9SJ.mjs → adjacency-list-GeL1Cu-L.mjs} +3 -1
  3. package/dist/{algorithms-fTqmvhzP.d.mts → algorithms-CsGNehct.d.mts} +137 -2
  4. package/dist/{algorithms-Ba7o7niK.mjs → algorithms-DF1pSQGv.mjs} +1476 -343
  5. package/dist/algorithms.d.mts +2 -2
  6. package/dist/algorithms.mjs +2 -2
  7. package/dist/{converter-udLITX36.mjs → converter-DyCJJfTe.mjs} +2 -2
  8. package/dist/format-support.mjs +38 -11
  9. package/dist/formats/adjacency-list/index.d.mts +1 -1
  10. package/dist/formats/adjacency-list/index.mjs +1 -1
  11. package/dist/formats/converter/index.d.mts +1 -1
  12. package/dist/formats/converter/index.mjs +1 -1
  13. package/dist/formats/cytoscape/index.d.mts +1 -1
  14. package/dist/formats/cytoscape/index.mjs +3 -1
  15. package/dist/formats/d2/index.d.mts +1 -1
  16. package/dist/formats/d2/index.mjs +26 -12
  17. package/dist/formats/d3/index.d.mts +1 -1
  18. package/dist/formats/d3/index.mjs +3 -1
  19. package/dist/formats/dot/index.d.mts +1 -1
  20. package/dist/formats/dot/index.mjs +22 -6
  21. package/dist/formats/edge-list/index.d.mts +1 -1
  22. package/dist/formats/edge-list/index.mjs +1 -1
  23. package/dist/formats/elk/index.d.mts +1 -1
  24. package/dist/formats/elk/index.mjs +21 -14
  25. package/dist/formats/gexf/index.d.mts +1 -1
  26. package/dist/formats/gexf/index.mjs +22 -15
  27. package/dist/formats/gml/index.d.mts +1 -1
  28. package/dist/formats/gml/index.mjs +21 -12
  29. package/dist/formats/graphml/index.d.mts +1 -1
  30. package/dist/formats/graphml/index.mjs +73 -22
  31. package/dist/formats/jgf/index.d.mts +1 -1
  32. package/dist/formats/jgf/index.mjs +5 -2
  33. package/dist/formats/mermaid/index.d.mts +1 -1
  34. package/dist/formats/mermaid/index.mjs +49 -12
  35. package/dist/formats/tgf/index.d.mts +1 -1
  36. package/dist/formats/tgf/index.mjs +1 -1
  37. package/dist/formats/xyflow/index.d.mts +1 -1
  38. package/dist/formats/xyflow/index.mjs +31 -4
  39. package/dist/{index-D9Kj6Fe3.d.mts → index-D51lJnt2.d.mts} +1 -1
  40. package/dist/{index-CHoriXZD.d.mts → index-DWmo1mIp.d.mts} +77 -18
  41. package/dist/index.d.mts +6 -6
  42. package/dist/index.mjs +143 -295
  43. package/dist/{queries-BlkA1HAN.d.mts → queries-BfXeTXRf.d.mts} +43 -12
  44. package/dist/queries-KirMDR7e.mjs +980 -0
  45. package/dist/queries.d.mts +1 -1
  46. package/dist/queries.mjs +1 -768
  47. package/dist/schemas.d.mts +1 -1
  48. package/dist/schemas.mjs +23 -84
  49. package/dist/{types-3-FS9NV2.d.mts → types-DNYdIU21.d.mts} +54 -5
  50. package/dist/validate-TtH-x3JV.mjs +190 -0
  51. package/package.json +13 -3
  52. package/dist/indexing-DR8M1vBy.mjs +0 -137
  53. /package/dist/{edge-list-DP4otyPU.mjs → edge-list-BcZ0h6zz.mjs} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { t as getEdgeMode } from "../../mode-D8OnHFBk.mjs";
2
- import { n as createFormatConverter } from "../../converter-udLITX36.mjs";
2
+ import { n as createFormatConverter } from "../../converter-DyCJJfTe.mjs";
3
3
  import { XMLBuilder, XMLParser } from "fast-xml-parser";
4
4
 
5
5
  //#region src/formats/gexf/index.ts
@@ -129,7 +129,7 @@ function toGEXF(graph) {
129
129
  });
130
130
  const node = {
131
131
  "@_id": n.id,
132
- "@_label": n.label || n.id
132
+ "@_label": n.label ?? ""
133
133
  };
134
134
  if (n.parentId) node["@_pid"] = n.parentId;
135
135
  if (attvalues.length > 0) node.attvalues = { attvalue: attvalues };
@@ -250,7 +250,8 @@ function fromGEXF(xml) {
250
250
  "attribute",
251
251
  "attvalue",
252
252
  "attributes"
253
- ].includes(name)
253
+ ].includes(name),
254
+ trimValues: false
254
255
  });
255
256
  let parsed;
256
257
  try {
@@ -283,16 +284,16 @@ function fromGEXF(xml) {
283
284
  if (attvals["ports"] !== void 0) node.ports = tryParseJSON(attvals["ports"]);
284
285
  const pos = n["viz:position"];
285
286
  if (pos) {
286
- node.x = Number(pos["@_x"] ?? 0);
287
- node.y = Number(pos["@_y"] ?? 0);
287
+ node.x = parseNumber(pos["@_x"] ?? 0, "<viz:position> x", "node", id);
288
+ node.y = parseNumber(pos["@_y"] ?? 0, "<viz:position> y", "node", id);
288
289
  }
289
290
  const size = n["viz:size"];
290
291
  if (size) {
291
- node.width = Number(size["@_value"] ?? 0);
292
- node.height = Number(size["@_value"] ?? 0);
292
+ node.width = parseNumber(size["@_value"] ?? 0, "<viz:size>", "node", id);
293
+ node.height = node.width;
293
294
  }
294
- if (attvals["width"] !== void 0) node.width = Number(attvals["width"]);
295
- if (attvals["height"] !== void 0) node.height = Number(attvals["height"]);
295
+ if (attvals["width"] !== void 0) node.width = parseNumber(attvals["width"], "width attribute", "node", id);
296
+ if (attvals["height"] !== void 0) node.height = parseNumber(attvals["height"], "height attribute", "node", id);
296
297
  const color = n["viz:color"];
297
298
  if (color) {
298
299
  const r = Number(color["@_r"] ?? 0);
@@ -308,18 +309,19 @@ function fromGEXF(xml) {
308
309
  parseNodes(graphEl.nodes?.node ?? graphEl.node, null);
309
310
  const edges = asArray(graphEl.edges?.edge ?? graphEl.edge).map((e, i) => {
310
311
  const attvals = getAttValues(e, attrMap);
312
+ const id = String(e["@_id"] ?? `e${i}`);
311
313
  const edge = {
312
314
  type: "edge",
313
- id: String(e["@_id"] ?? `e${i}`),
315
+ id,
314
316
  sourceId: String(e["@_source"]),
315
317
  targetId: String(e["@_target"]),
316
318
  label: e["@_label"] ?? "",
317
319
  data: attvals["data"] !== void 0 ? tryParseJSON(attvals["data"]) : void 0,
318
- ...attvals["weight"] !== void 0 && { weight: Number(attvals["weight"]) },
319
- ...attvals["x"] !== void 0 && { x: Number(attvals["x"]) },
320
- ...attvals["y"] !== void 0 && { y: Number(attvals["y"]) },
321
- ...attvals["width"] !== void 0 && { width: Number(attvals["width"]) },
322
- ...attvals["height"] !== void 0 && { height: Number(attvals["height"]) },
320
+ ...attvals["weight"] !== void 0 && { weight: parseNumber(attvals["weight"], "weight attribute", "edge", id) },
321
+ ...attvals["x"] !== void 0 && { x: parseNumber(attvals["x"], "x attribute", "edge", id) },
322
+ ...attvals["y"] !== void 0 && { y: parseNumber(attvals["y"], "y attribute", "edge", id) },
323
+ ...attvals["width"] !== void 0 && { width: parseNumber(attvals["width"], "width attribute", "edge", id) },
324
+ ...attvals["height"] !== void 0 && { height: parseNumber(attvals["height"], "height attribute", "edge", id) },
323
325
  ...attvals["style"] !== void 0 && { style: tryParseJSON(attvals["style"]) },
324
326
  ...attvals["sourcePort"] !== void 0 && { sourcePort: attvals["sourcePort"] },
325
327
  ...attvals["targetPort"] !== void 0 && { targetPort: attvals["targetPort"] }
@@ -366,6 +368,11 @@ function tryParseJSON(str) {
366
368
  return str;
367
369
  }
368
370
  }
371
+ function parseNumber(value, field, kind, ownerId) {
372
+ const parsed = Number(value);
373
+ if (Number.isNaN(parsed)) throw new Error(`GEXF: ${field} value "${value}" on ${kind} "${ownerId}" is not a number. Fix the value or remove the attribute.`);
374
+ return parsed;
375
+ }
369
376
  function hex(n) {
370
377
  return n.toString(16).padStart(2, "0");
371
378
  }
@@ -1,4 +1,4 @@
1
- import { h as GraphFormatConverter, u as Graph } from "../../types-3-FS9NV2.mjs";
1
+ import { d as Graph, g as GraphFormatConverter } from "../../types-DNYdIU21.mjs";
2
2
 
3
3
  //#region src/formats/gml/index.d.ts
4
4
 
@@ -1,4 +1,4 @@
1
- import { n as createFormatConverter } from "../../converter-udLITX36.mjs";
1
+ import { n as createFormatConverter } from "../../converter-DyCJJfTe.mjs";
2
2
 
3
3
  //#region src/formats/gml/index.ts
4
4
  /**
@@ -27,6 +27,7 @@ function toGML(graph) {
27
27
  const lines = [];
28
28
  lines.push("graph [");
29
29
  lines.push(` directed ${graph.mode === "undirected" ? 0 : 1}`);
30
+ if (graph.mode === "bidirectional") lines.push(` mode ${gmlString(graph.mode)}`);
30
31
  if (graph.id) lines.push(` id ${gmlString(graph.id)}`);
31
32
  if (graph.initialNodeId) lines.push(` initialNodeId ${gmlString(graph.initialNodeId)}`);
32
33
  if (graph.data !== void 0) lines.push(` data ${gmlString(JSON.stringify(graph.data))}`);
@@ -67,6 +68,7 @@ function toGML(graph) {
67
68
  lines.push(` id ${gmlString(edge.id)}`);
68
69
  lines.push(` source ${gmlString(edge.sourceId)}`);
69
70
  lines.push(` target ${gmlString(edge.targetId)}`);
71
+ if (edge.mode) lines.push(` mode ${gmlString(edge.mode)}`);
70
72
  if (edge.label) lines.push(` label ${gmlString(edge.label)}`);
71
73
  if (edge.data !== void 0) lines.push(` data ${gmlString(JSON.stringify(edge.data))}`);
72
74
  if (edge.weight !== void 0) lines.push(` weight ${edge.weight}`);
@@ -134,10 +136,10 @@ function fromGML(gml) {
134
136
  ...n["shape"] && { shape: n["shape"] },
135
137
  ...n["color"] && { color: n["color"] },
136
138
  ...n["style"] !== void 0 && { style: tryParseJSON(n["style"]) },
137
- ...gfx?.x !== void 0 && { x: gfx.x },
138
- ...gfx?.y !== void 0 && { y: gfx.y },
139
- ...gfx?.w !== void 0 && { width: gfx.w },
140
- ...gfx?.h !== void 0 && { height: gfx.h }
139
+ ...gfx?.x !== void 0 && { x: parseNumber(gfx.x, "graphics x", "node", id) },
140
+ ...gfx?.y !== void 0 && { y: parseNumber(gfx.y, "graphics y", "node", id) },
141
+ ...gfx?.w !== void 0 && { width: parseNumber(gfx.w, "graphics w", "node", id) },
142
+ ...gfx?.h !== void 0 && { height: parseNumber(gfx.h, "graphics h", "node", id) }
141
143
  });
142
144
  if (n["node"] !== void 0) parseNodes(n, id);
143
145
  }
@@ -146,27 +148,29 @@ function fromGML(gml) {
146
148
  const edgeEntries = asArray(graphBlock["edge"]);
147
149
  for (const e of edgeEntries) {
148
150
  const gfx = e["graphics"];
151
+ const id = String(e["id"] ?? `e${edges.length}`);
149
152
  edges.push({
150
153
  type: "edge",
151
- id: String(e["id"] ?? `e${edges.length}`),
154
+ id,
152
155
  sourceId: String(e["source"] ?? ""),
153
156
  targetId: String(e["target"] ?? ""),
154
157
  label: e["label"] ?? "",
158
+ ...e["mode"] && { mode: String(e["mode"]) },
155
159
  data: e["data"] !== void 0 ? tryParseJSON(e["data"]) : void 0,
156
- ...e["weight"] !== void 0 && { weight: Number(e["weight"]) },
160
+ ...e["weight"] !== void 0 && { weight: parseNumber(e["weight"], "weight", "edge", id) },
157
161
  ...e["sourcePort"] !== void 0 && { sourcePort: String(e["sourcePort"]) },
158
162
  ...e["targetPort"] !== void 0 && { targetPort: String(e["targetPort"]) },
159
163
  ...e["color"] && { color: e["color"] },
160
164
  ...e["style"] !== void 0 && { style: tryParseJSON(e["style"]) },
161
- ...gfx?.x !== void 0 && { x: gfx.x },
162
- ...gfx?.y !== void 0 && { y: gfx.y },
163
- ...gfx?.w !== void 0 && { width: gfx.w },
164
- ...gfx?.h !== void 0 && { height: gfx.h }
165
+ ...gfx?.x !== void 0 && { x: parseNumber(gfx.x, "graphics x", "edge", id) },
166
+ ...gfx?.y !== void 0 && { y: parseNumber(gfx.y, "graphics y", "edge", id) },
167
+ ...gfx?.w !== void 0 && { width: parseNumber(gfx.w, "graphics w", "edge", id) },
168
+ ...gfx?.h !== void 0 && { height: parseNumber(gfx.h, "graphics h", "edge", id) }
165
169
  });
166
170
  }
167
171
  return {
168
172
  id: graphId,
169
- mode: directed ? "directed" : "undirected",
173
+ mode: graphBlock["mode"] ? String(graphBlock["mode"]) : directed ? "directed" : "undirected",
170
174
  initialNodeId: graphBlock["initialNodeId"] ?? null,
171
175
  nodes,
172
176
  edges,
@@ -301,6 +305,11 @@ function tryParseJSON(str) {
301
305
  return str;
302
306
  }
303
307
  }
308
+ function parseNumber(value, field, kind, ownerId) {
309
+ const parsed = Number(value);
310
+ if (Number.isNaN(parsed)) throw new Error(`GML: ${field} value "${value}" on ${kind} "${ownerId}" is not a number. Fix the value or remove the attribute.`);
311
+ return parsed;
312
+ }
304
313
  /**
305
314
  * Bidirectional converter for GML (Graph Modelling Language) format.
306
315
  *
@@ -1,4 +1,4 @@
1
- import { h as GraphFormatConverter, u as Graph } from "../../types-3-FS9NV2.mjs";
1
+ import { d as Graph, g as GraphFormatConverter } from "../../types-DNYdIU21.mjs";
2
2
 
3
3
  //#region src/formats/graphml/index.d.ts
4
4
  declare function toGraphML(graph: Graph): string;
@@ -1,5 +1,5 @@
1
1
  import { t as getEdgeMode } from "../../mode-D8OnHFBk.mjs";
2
- import { n as createFormatConverter } from "../../converter-udLITX36.mjs";
2
+ import { n as createFormatConverter } from "../../converter-DyCJJfTe.mjs";
3
3
  import { XMLBuilder, XMLParser } from "fast-xml-parser";
4
4
 
5
5
  //#region src/formats/graphml/index.ts
@@ -281,8 +281,12 @@ function fromGraphML(xml) {
281
281
  "node",
282
282
  "edge",
283
283
  "data",
284
- "key"
285
- ].includes(name)
284
+ "key",
285
+ "graph",
286
+ "port"
287
+ ].includes(name),
288
+ parseTagValue: false,
289
+ trimValues: false
286
290
  });
287
291
  let parsed;
288
292
  try {
@@ -292,50 +296,77 @@ function fromGraphML(xml) {
292
296
  }
293
297
  const graphml = parsed?.graphml;
294
298
  if (!graphml) throw new Error("GraphML: missing <graphml> root element");
295
- const graphEl = graphml.graph;
299
+ const graphEl = asArray(graphml.graph)[0];
296
300
  if (!graphEl) throw new Error("GraphML: missing <graph> element");
297
301
  const graphType = graphEl["@_edgedefault"] === "undirected" ? "undirected" : "directed";
298
302
  const graphDataMap = parseDataElements(graphEl.data);
299
303
  const graphData = graphDataMap.graphData !== void 0 ? tryParseJSON(graphDataMap.graphData) : void 0;
300
- 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 }) => {
301
318
  const dataMap = parseDataElements(nodeEl.data);
319
+ const id = String(nodeEl["@_id"]);
302
320
  const node = {
303
321
  type: "node",
304
- id: String(nodeEl["@_id"]),
305
- parentId: dataMap.parentId ?? null,
322
+ id,
323
+ parentId: dataMap.parentId ?? structuralParentId,
306
324
  initialNodeId: dataMap.initialNodeId ?? null,
307
325
  label: dataMap.label ?? "",
308
326
  data: dataMap.data !== void 0 ? tryParseJSON(dataMap.data) : void 0
309
327
  };
310
- if (dataMap.x !== void 0) node.x = parseNumber(dataMap.x);
311
- if (dataMap.y !== void 0) node.y = parseNumber(dataMap.y);
312
- if (dataMap.width !== void 0) node.width = parseNumber(dataMap.width);
313
- 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);
314
332
  if (dataMap.shape !== void 0) node.shape = dataMap.shape;
315
333
  if (dataMap.color !== void 0) node.color = dataMap.color;
316
334
  if (dataMap.style !== void 0) node.style = tryParseJSON(dataMap.style);
317
335
  if (dataMap.ports !== void 0) node.ports = tryParseJSON(dataMap.ports);
336
+ else if (nodeEl.port !== void 0) node.ports = collectPorts(nodeEl.port);
318
337
  return node;
319
338
  });
320
- 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) => {
321
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
+ }
322
351
  const edge = {
323
352
  type: "edge",
324
- id: String(edgeEl["@_id"]),
325
- sourceId: String(edgeEl["@_source"]),
326
- targetId: String(edgeEl["@_target"]),
353
+ id,
354
+ sourceId: source,
355
+ targetId: target,
327
356
  label: dataMap.label ?? "",
328
357
  data: dataMap.data !== void 0 ? tryParseJSON(dataMap.data) : void 0
329
358
  };
330
- if (dataMap.weight !== void 0) edge.weight = parseNumber(dataMap.weight);
331
- if (dataMap.x !== void 0) edge.x = parseNumber(dataMap.x);
332
- if (dataMap.y !== void 0) edge.y = parseNumber(dataMap.y);
333
- if (dataMap.width !== void 0) edge.width = parseNumber(dataMap.width);
334
- 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);
335
364
  if (dataMap.color !== void 0) edge.color = dataMap.color;
336
365
  if (dataMap.style !== void 0) edge.style = tryParseJSON(dataMap.style);
337
366
  if (dataMap.sourcePort !== void 0) edge.sourcePort = dataMap.sourcePort;
367
+ else if (edgeEl["@_sourceport"] != null) edge.sourcePort = String(edgeEl["@_sourceport"]);
338
368
  if (dataMap.targetPort !== void 0) edge.targetPort = dataMap.targetPort;
369
+ else if (edgeEl["@_targetport"] != null) edge.targetPort = String(edgeEl["@_targetport"]);
339
370
  const directedAttr = edgeEl["@_directed"];
340
371
  if (directedAttr !== void 0) edge.mode = String(directedAttr) === "false" ? "undirected" : "directed";
341
372
  return edge;
@@ -368,8 +399,28 @@ function tryParseJSON(str) {
368
399
  return str;
369
400
  }
370
401
  }
371
- function parseNumber(value) {
372
- 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;
373
424
  }
374
425
  function parseDirection(value) {
375
426
  return [
@@ -1,4 +1,4 @@
1
- import { h as GraphFormatConverter, u as Graph } from "../../types-3-FS9NV2.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-udLITX36.mjs";
1
+ import { n as createFormatConverter } from "../../converter-DyCJJfTe.mjs";
2
2
 
3
3
  //#region src/formats/jgf/index.ts
4
4
  /**
@@ -20,6 +20,7 @@ import { n as createFormatConverter } from "../../converter-udLITX36.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;
@@ -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
- mode: 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-3-FS9NV2.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-udLITX36.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) {
@@ -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({
@@ -1676,7 +1694,7 @@ function fromMermaidState(input) {
1676
1694
  return {
1677
1695
  id: "",
1678
1696
  mode: "directed",
1679
- initialNodeId: null,
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;
@@ -1,4 +1,4 @@
1
- import { h as GraphFormatConverter, u as Graph } from "../../types-3-FS9NV2.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-udLITX36.mjs";
1
+ import { n as createFormatConverter } from "../../converter-DyCJJfTe.mjs";
2
2
 
3
3
  //#region src/formats/tgf/index.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { F as VisualGraphFormatConverter, M as VisualGraph } from "../../types-3-FS9NV2.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.
@@ -52,7 +77,7 @@ function toXYFlow(graph) {
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,
@@ -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 }