@statelyai/graph 1.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +121 -44
  2. package/dist/{adjacency-list-VsUaH9SJ.mjs → adjacency-list-DQ32Mmhx.mjs} +3 -1
  3. package/dist/algorithms-D1cgly0g.d.mts +452 -0
  4. package/dist/algorithms-DBpH74hR.mjs +3309 -0
  5. package/dist/algorithms.d.mts +2 -2
  6. package/dist/algorithms.mjs +2 -2
  7. package/dist/config-Dt5u1gSf.mjs +793 -0
  8. package/dist/{converter-udLITX36.mjs → converter-DB6Rg6Vd.mjs} +2 -2
  9. package/dist/format-support.mjs +38 -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 -1
  13. package/dist/formats/converter/index.mjs +1 -1
  14. package/dist/formats/cytoscape/index.d.mts +4 -4
  15. package/dist/formats/cytoscape/index.mjs +10 -4
  16. package/dist/formats/d2/index.d.mts +1 -1
  17. package/dist/formats/d2/index.mjs +26 -12
  18. package/dist/formats/d3/index.d.mts +4 -4
  19. package/dist/formats/d3/index.mjs +10 -4
  20. package/dist/formats/dot/index.d.mts +1 -1
  21. package/dist/formats/dot/index.mjs +22 -6
  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 +63 -24
  26. package/dist/formats/gexf/index.d.mts +1 -1
  27. package/dist/formats/gexf/index.mjs +43 -16
  28. package/dist/formats/gml/index.d.mts +4 -4
  29. package/dist/formats/gml/index.mjs +28 -15
  30. package/dist/formats/graphml/index.d.mts +1 -1
  31. package/dist/formats/graphml/index.mjs +96 -23
  32. package/dist/formats/jgf/index.d.mts +4 -4
  33. package/dist/formats/jgf/index.mjs +12 -5
  34. package/dist/formats/mermaid/index.d.mts +1 -1
  35. package/dist/formats/mermaid/index.mjs +49 -12
  36. package/dist/formats/tgf/index.d.mts +4 -4
  37. package/dist/formats/tgf/index.mjs +4 -4
  38. package/dist/formats/xyflow/index.d.mts +12 -6
  39. package/dist/formats/xyflow/index.mjs +42 -10
  40. package/dist/{index-D9Kj6Fe3.d.mts → index-BlbSWUvH.d.mts} +1 -1
  41. package/dist/{index-CHoriXZD.d.mts → index-CNvqxPLJ.d.mts} +157 -30
  42. package/dist/index.d.mts +6 -6
  43. package/dist/index.mjs +290 -307
  44. package/dist/layout/cytoscape.d.mts +66 -0
  45. package/dist/layout/cytoscape.mjs +114 -0
  46. package/dist/layout/d3-force.d.mts +52 -0
  47. package/dist/layout/d3-force.mjs +127 -0
  48. package/dist/layout/d3-hierarchy.d.mts +39 -0
  49. package/dist/layout/d3-hierarchy.mjs +135 -0
  50. package/dist/layout/dagre.d.mts +32 -0
  51. package/dist/layout/dagre.mjs +99 -0
  52. package/dist/layout/elk.d.mts +47 -0
  53. package/dist/layout/elk.mjs +73 -0
  54. package/dist/layout/forceatlas2.d.mts +48 -0
  55. package/dist/layout/forceatlas2.mjs +100 -0
  56. package/dist/layout/graphviz.d.mts +50 -0
  57. package/dist/layout/graphviz.mjs +179 -0
  58. package/dist/layout/index.d.mts +185 -0
  59. package/dist/layout/index.mjs +181 -0
  60. package/dist/layout/webcola.d.mts +40 -0
  61. package/dist/layout/webcola.mjs +104 -0
  62. package/dist/{queries-BlkA1HAN.d.mts → queries-B6quF529.d.mts} +43 -12
  63. package/dist/queries-BMM0XAv_.mjs +986 -0
  64. package/dist/queries.d.mts +1 -1
  65. package/dist/queries.mjs +1 -768
  66. package/dist/schemas.d.mts +19 -1
  67. package/dist/schemas.mjs +32 -84
  68. package/dist/{types-3-FS9NV2.d.mts → types-BAEQTwK_.d.mts} +99 -7
  69. package/dist/validate-BsfSOv0S.mjs +190 -0
  70. package/package.json +59 -7
  71. package/schemas/edge.schema.json +27 -0
  72. package/schemas/graph.schema.json +27 -0
  73. package/dist/algorithms-Ba7o7niK.mjs +0 -2394
  74. package/dist/algorithms-fTqmvhzP.d.mts +0 -178
  75. package/dist/indexing-DR8M1vBy.mjs +0 -137
  76. /package/dist/{edge-list-DP4otyPU.mjs → edge-list-CA9UTvn2.mjs} +0 -0
  77. /package/dist/{mode-D8OnHFBk.mjs → mode-gu_mhKKs.mjs} +0 -0
@@ -1,4 +1,4 @@
1
- import { getChildren } from "../../queries.mjs";
1
+ import { n as getChildren } from "../../queries-BMM0XAv_.mjs";
2
2
 
3
3
  //#region src/formats/elk/index.ts
4
4
  const STATELYAI_METADATA_KEY = "statelyai.metadata";
@@ -33,16 +33,21 @@ const ELK_TO_DIRECTION = {
33
33
  function convertEdge(edge) {
34
34
  const elkEdge = {
35
35
  id: edge.id,
36
- sources: [edge.sourcePort ?? edge.sourceId],
37
- targets: [edge.targetPort ?? edge.targetId]
36
+ sources: [edge.sourcePort != null ? `${edge.sourceId}__${edge.sourcePort}` : edge.sourceId],
37
+ targets: [edge.targetPort != null ? `${edge.targetId}__${edge.targetPort}` : edge.targetId]
38
38
  };
39
- if (edge.label) elkEdge.labels = [{ text: edge.label }];
39
+ if (edge.label) elkEdge.labels = [{
40
+ text: edge.label,
41
+ ...edge.width > 0 && { width: edge.width },
42
+ ...edge.height > 0 && { height: edge.height }
43
+ }];
40
44
  return addMetadata(elkEdge, { edge: {
41
45
  sourceId: edge.sourceId,
42
46
  targetId: edge.targetId,
43
47
  sourcePort: edge.sourcePort,
44
48
  targetPort: edge.targetPort,
45
49
  label: edge.label,
50
+ mode: edge.mode,
46
51
  data: edge.data,
47
52
  weight: edge.weight,
48
53
  color: edge.color,
@@ -50,12 +55,14 @@ function convertEdge(edge) {
50
55
  x: edge.x,
51
56
  y: edge.y,
52
57
  width: edge.width,
53
- height: edge.height
58
+ height: edge.height,
59
+ points: edge.points,
60
+ routing: edge.routing
54
61
  } });
55
62
  }
56
- function convertPort(port) {
63
+ function convertPort(nodeId, port) {
57
64
  const elkPort = {
58
- id: port.name,
65
+ id: `${nodeId}__${port.name}`,
59
66
  x: port.x,
60
67
  y: port.y,
61
68
  width: port.width,
@@ -64,6 +71,7 @@ function convertPort(port) {
64
71
  if (port.label) elkPort.labels = [{ text: port.label }];
65
72
  if (port.direction !== "inout") elkPort.layoutOptions = { "org.eclipse.elk.port.side": port.direction === "in" ? "WEST" : "EAST" };
66
73
  return addMetadata(elkPort, { port: {
74
+ name: port.name,
67
75
  data: port.data,
68
76
  style: port.style
69
77
  } });
@@ -77,7 +85,7 @@ function convertNode(graph, node) {
77
85
  height: node.height
78
86
  };
79
87
  if (node.label) elkNode.labels = [{ text: node.label }];
80
- if (node.ports && node.ports.length > 0) elkNode.ports = node.ports.map(convertPort);
88
+ if (node.ports && node.ports.length > 0) elkNode.ports = node.ports.map((port) => convertPort(node.id, port));
81
89
  addMetadata(elkNode, { node: {
82
90
  initialNodeId: node.initialNodeId,
83
91
  data: node.data,
@@ -146,7 +154,7 @@ function toELK(graph) {
146
154
  if (rootEdges.length > 0) root.edges = rootEdges.map(convertEdge);
147
155
  return root;
148
156
  }
149
- function flattenElkNodes(elkNode, parentId, nodes, edges, edgeIdx, portOwner) {
157
+ function flattenElkNodes(elkNode, parentId, nodes, edges, edgeIdx, portOwner, edgeRouting) {
150
158
  if (elkNode.children) for (const child of elkNode.children) {
151
159
  const label = child.labels?.[0]?.text ?? "";
152
160
  const metadata = readMetadata(child)?.node;
@@ -166,14 +174,18 @@ function flattenElkNodes(elkNode, parentId, nodes, edges, edgeIdx, portOwner) {
166
174
  ...metadata?.style !== void 0 && { style: metadata.style }
167
175
  };
168
176
  if (child.ports && child.ports.length > 0) node.ports = child.ports.map((elkPort) => {
169
- portOwner.set(elkPort.id, child.id);
170
177
  const metadata$1 = readMetadata(elkPort)?.port;
178
+ const portName = metadata$1?.name ?? elkPort.id;
179
+ portOwner.set(elkPort.id, {
180
+ nodeId: child.id,
181
+ portName
182
+ });
171
183
  const sideOpt = elkPort.layoutOptions?.["org.eclipse.elk.port.side"];
172
184
  let direction = "inout";
173
185
  if (sideOpt === "WEST") direction = "in";
174
186
  else if (sideOpt === "EAST") direction = "out";
175
187
  return {
176
- name: elkPort.id,
188
+ name: portName,
177
189
  direction,
178
190
  label: elkPort.labels?.[0]?.text,
179
191
  data: metadata$1 && "data" in metadata$1 ? metadata$1.data : void 0,
@@ -185,31 +197,55 @@ function flattenElkNodes(elkNode, parentId, nodes, edges, edgeIdx, portOwner) {
185
197
  };
186
198
  });
187
199
  nodes.push(node);
188
- flattenElkNodes(child, child.id, nodes, edges, edgeIdx, portOwner);
200
+ flattenElkNodes(child, child.id, nodes, edges, edgeIdx, portOwner, edgeRouting);
189
201
  }
190
202
  if (elkNode.edges) for (const elkEdge of elkNode.edges) for (const source of elkEdge.sources) for (const target of elkEdge.targets) {
191
203
  const metadata = readMetadata(elkEdge)?.edge;
192
- const sourceNodeId = portOwner.get(source);
193
- const targetNodeId = portOwner.get(target);
204
+ const sourceOwner = portOwner.get(source);
205
+ const targetOwner = portOwner.get(target);
206
+ const elkLabel = elkEdge.labels?.[0];
207
+ const hasComputedLabel = elkLabel?.x !== void 0 && elkLabel?.y !== void 0;
208
+ const section = elkEdge.sections?.[0];
194
209
  const edge = {
195
210
  type: "edge",
196
211
  id: elkEdge.id ?? `e${edgeIdx.value++}`,
197
- sourceId: metadata?.sourceId ?? sourceNodeId ?? source,
198
- targetId: metadata?.targetId ?? targetNodeId ?? target,
199
- label: metadata && "label" in metadata ? metadata.label : elkEdge.labels?.[0]?.text ?? "",
212
+ sourceId: metadata?.sourceId ?? sourceOwner?.nodeId ?? source,
213
+ targetId: metadata?.targetId ?? targetOwner?.nodeId ?? target,
214
+ label: metadata && "label" in metadata ? metadata.label : elkLabel?.text ?? "",
200
215
  data: metadata && "data" in metadata ? metadata.data : void 0,
201
- x: metadata?.x ?? 0,
202
- y: metadata?.y ?? 0,
203
- width: metadata?.width ?? 0,
204
- height: metadata?.height ?? 0,
216
+ x: hasComputedLabel ? elkLabel.x : metadata?.x ?? 0,
217
+ y: hasComputedLabel ? elkLabel.y : metadata?.y ?? 0,
218
+ width: hasComputedLabel ? elkLabel.width ?? metadata?.width ?? 0 : metadata?.width ?? 0,
219
+ height: hasComputedLabel ? elkLabel.height ?? metadata?.height ?? 0 : metadata?.height ?? 0,
220
+ ...metadata?.mode !== void 0 && { mode: metadata.mode },
205
221
  ...metadata?.weight !== void 0 && { weight: metadata.weight },
206
222
  ...metadata?.color !== void 0 && { color: metadata.color },
207
223
  ...metadata?.style !== void 0 && { style: metadata.style }
208
224
  };
225
+ if (section?.startPoint && section.endPoint) {
226
+ edge.points = [
227
+ {
228
+ x: section.startPoint.x,
229
+ y: section.startPoint.y
230
+ },
231
+ ...(section.bendPoints ?? []).map((p) => ({
232
+ x: p.x,
233
+ y: p.y
234
+ })),
235
+ {
236
+ x: section.endPoint.x,
237
+ y: section.endPoint.y
238
+ }
239
+ ];
240
+ edge.routing = edgeRouting;
241
+ } else if (metadata?.points !== void 0) {
242
+ edge.points = metadata.points;
243
+ if (metadata.routing !== void 0) edge.routing = metadata.routing;
244
+ }
209
245
  if (metadata && "sourcePort" in metadata) edge.sourcePort = metadata.sourcePort;
210
- else if (sourceNodeId) edge.sourcePort = source;
246
+ else if (sourceOwner) edge.sourcePort = sourceOwner.portName;
211
247
  if (metadata && "targetPort" in metadata) edge.targetPort = metadata.targetPort;
212
- else if (targetNodeId) edge.targetPort = target;
248
+ else if (targetOwner) edge.targetPort = targetOwner.portName;
213
249
  edges.push(edge);
214
250
  }
215
251
  }
@@ -236,7 +272,10 @@ function flattenElkNodes(elkNode, parentId, nodes, edges, edgeIdx, portOwner) {
236
272
  function fromELK(elkRoot) {
237
273
  const nodes = [];
238
274
  const edges = [];
239
- flattenElkNodes(elkRoot, null, nodes, edges, { value: 0 }, /* @__PURE__ */ new Map());
275
+ const edgeIdx = { value: 0 };
276
+ const portOwner = /* @__PURE__ */ new Map();
277
+ const elkEdgeRouting = String(elkRoot.layoutOptions?.["elk.edgeRouting"] ?? elkRoot.layoutOptions?.["org.eclipse.elk.edgeRouting"] ?? "").toUpperCase();
278
+ flattenElkNodes(elkRoot, null, nodes, edges, edgeIdx, portOwner, elkEdgeRouting === "SPLINES" ? "splines" : elkEdgeRouting === "POLYLINE" ? "polyline" : "orthogonal");
240
279
  const seenEdges = /* @__PURE__ */ new Map();
241
280
  for (const edge of edges) if (!seenEdges.has(edge.id)) seenEdges.set(edge.id, edge);
242
281
  const elkDir = elkRoot.layoutOptions?.["elk.direction"];
@@ -1,4 +1,4 @@
1
- import { h as GraphFormatConverter, u as Graph } from "../../types-3-FS9NV2.mjs";
1
+ import { _ as GraphFormatConverter, f as Graph } from "../../types-BAEQTwK_.mjs";
2
2
 
3
3
  //#region src/formats/gexf/index.d.ts
4
4
  declare function toGEXF(graph: Graph): string;
@@ -1,5 +1,5 @@
1
- import { t as getEdgeMode } from "../../mode-D8OnHFBk.mjs";
2
- import { n as createFormatConverter } from "../../converter-udLITX36.mjs";
1
+ import { t as getEdgeMode } from "../../mode-gu_mhKKs.mjs";
2
+ import { n as createFormatConverter } from "../../converter-DB6Rg6Vd.mjs";
3
3
  import { XMLBuilder, XMLParser } from "fast-xml-parser";
4
4
 
5
5
  //#region src/formats/gexf/index.ts
@@ -82,6 +82,16 @@ function toGEXF(graph) {
82
82
  "@_title": "style",
83
83
  "@_type": "string"
84
84
  },
85
+ {
86
+ "@_id": "a_edgePoints",
87
+ "@_title": "points",
88
+ "@_type": "string"
89
+ },
90
+ {
91
+ "@_id": "a_edgeRouting",
92
+ "@_title": "routing",
93
+ "@_type": "string"
94
+ },
85
95
  {
86
96
  "@_id": "a_sourcePort",
87
97
  "@_title": "sourcePort",
@@ -129,7 +139,7 @@ function toGEXF(graph) {
129
139
  });
130
140
  const node = {
131
141
  "@_id": n.id,
132
- "@_label": n.label || n.id
142
+ "@_label": n.label ?? ""
133
143
  };
134
144
  if (n.parentId) node["@_pid"] = n.parentId;
135
145
  if (attvalues.length > 0) node.attvalues = { attvalue: attvalues };
@@ -167,6 +177,14 @@ function toGEXF(graph) {
167
177
  "@_for": "a_edgeWeight",
168
178
  "@_value": e.weight
169
179
  });
180
+ if (e.points !== void 0) edgeAttvalues.push({
181
+ "@_for": "a_edgePoints",
182
+ "@_value": JSON.stringify(e.points)
183
+ });
184
+ if (e.routing !== void 0) edgeAttvalues.push({
185
+ "@_for": "a_edgeRouting",
186
+ "@_value": e.routing
187
+ });
170
188
  if (e.x !== void 0) edgeAttvalues.push({
171
189
  "@_for": "a_edgeX",
172
190
  "@_value": e.x
@@ -250,7 +268,8 @@ function fromGEXF(xml) {
250
268
  "attribute",
251
269
  "attvalue",
252
270
  "attributes"
253
- ].includes(name)
271
+ ].includes(name),
272
+ trimValues: false
254
273
  });
255
274
  let parsed;
256
275
  try {
@@ -283,16 +302,16 @@ function fromGEXF(xml) {
283
302
  if (attvals["ports"] !== void 0) node.ports = tryParseJSON(attvals["ports"]);
284
303
  const pos = n["viz:position"];
285
304
  if (pos) {
286
- node.x = Number(pos["@_x"] ?? 0);
287
- node.y = Number(pos["@_y"] ?? 0);
305
+ node.x = parseNumber(pos["@_x"] ?? 0, "<viz:position> x", "node", id);
306
+ node.y = parseNumber(pos["@_y"] ?? 0, "<viz:position> y", "node", id);
288
307
  }
289
308
  const size = n["viz:size"];
290
309
  if (size) {
291
- node.width = Number(size["@_value"] ?? 0);
292
- node.height = Number(size["@_value"] ?? 0);
310
+ node.width = parseNumber(size["@_value"] ?? 0, "<viz:size>", "node", id);
311
+ node.height = node.width;
293
312
  }
294
- if (attvals["width"] !== void 0) node.width = Number(attvals["width"]);
295
- if (attvals["height"] !== void 0) node.height = Number(attvals["height"]);
313
+ if (attvals["width"] !== void 0) node.width = parseNumber(attvals["width"], "width attribute", "node", id);
314
+ if (attvals["height"] !== void 0) node.height = parseNumber(attvals["height"], "height attribute", "node", id);
296
315
  const color = n["viz:color"];
297
316
  if (color) {
298
317
  const r = Number(color["@_r"] ?? 0);
@@ -308,18 +327,21 @@ function fromGEXF(xml) {
308
327
  parseNodes(graphEl.nodes?.node ?? graphEl.node, null);
309
328
  const edges = asArray(graphEl.edges?.edge ?? graphEl.edge).map((e, i) => {
310
329
  const attvals = getAttValues(e, attrMap);
330
+ const id = String(e["@_id"] ?? `e${i}`);
311
331
  const edge = {
312
332
  type: "edge",
313
- id: String(e["@_id"] ?? `e${i}`),
333
+ id,
314
334
  sourceId: String(e["@_source"]),
315
335
  targetId: String(e["@_target"]),
316
336
  label: e["@_label"] ?? "",
317
337
  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"]) },
338
+ ...attvals["weight"] !== void 0 && { weight: parseNumber(attvals["weight"], "weight attribute", "edge", id) },
339
+ ...attvals["points"] !== void 0 && { points: tryParseJSON(attvals["points"]) },
340
+ ...attvals["routing"] !== void 0 && { routing: attvals["routing"] },
341
+ ...attvals["x"] !== void 0 && { x: parseNumber(attvals["x"], "x attribute", "edge", id) },
342
+ ...attvals["y"] !== void 0 && { y: parseNumber(attvals["y"], "y attribute", "edge", id) },
343
+ ...attvals["width"] !== void 0 && { width: parseNumber(attvals["width"], "width attribute", "edge", id) },
344
+ ...attvals["height"] !== void 0 && { height: parseNumber(attvals["height"], "height attribute", "edge", id) },
323
345
  ...attvals["style"] !== void 0 && { style: tryParseJSON(attvals["style"]) },
324
346
  ...attvals["sourcePort"] !== void 0 && { sourcePort: attvals["sourcePort"] },
325
347
  ...attvals["targetPort"] !== void 0 && { targetPort: attvals["targetPort"] }
@@ -366,6 +388,11 @@ function tryParseJSON(str) {
366
388
  return str;
367
389
  }
368
390
  }
391
+ function parseNumber(value, field, kind, ownerId) {
392
+ const parsed = Number(value);
393
+ 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.`);
394
+ return parsed;
395
+ }
369
396
  function hex(n) {
370
397
  return n.toString(16).padStart(2, "0");
371
398
  }
@@ -1,4 +1,4 @@
1
- import { h as GraphFormatConverter, u as Graph } from "../../types-3-FS9NV2.mjs";
1
+ import { _ as GraphFormatConverter, f as Graph } from "../../types-BAEQTwK_.mjs";
2
2
 
3
3
  //#region src/formats/gml/index.d.ts
4
4
 
@@ -8,7 +8,7 @@ import { h as GraphFormatConverter, u as Graph } from "../../types-3-FS9NV2.mjs"
8
8
  * @example
9
9
  * ```ts
10
10
  * import { createGraph } from '@statelyai/graph';
11
- * import { toGML } from '@statelyai/graph/formats/gml';
11
+ * import { toGML } from '@statelyai/graph/gml';
12
12
  *
13
13
  * const graph = createGraph({
14
14
  * nodes: [{ id: 'a' }, { id: 'b' }],
@@ -30,7 +30,7 @@ declare function toGML(graph: Graph): string;
30
30
  *
31
31
  * @example
32
32
  * ```ts
33
- * import { fromGML } from '@statelyai/graph/formats/gml';
33
+ * import { fromGML } from '@statelyai/graph/gml';
34
34
  *
35
35
  * const graph = fromGML(`
36
36
  * graph [
@@ -49,7 +49,7 @@ declare function fromGML(gml: string): Graph;
49
49
  * @example
50
50
  * ```ts
51
51
  * import { createGraph } from '@statelyai/graph';
52
- * import { gmlConverter } from '@statelyai/graph/formats/gml';
52
+ * import { gmlConverter } from '@statelyai/graph/gml';
53
53
  *
54
54
  * const graph = createGraph({
55
55
  * nodes: [{ id: 'a' }, { id: 'b' }],
@@ -1,4 +1,4 @@
1
- import { n as createFormatConverter } from "../../converter-udLITX36.mjs";
1
+ import { n as createFormatConverter } from "../../converter-DB6Rg6Vd.mjs";
2
2
 
3
3
  //#region src/formats/gml/index.ts
4
4
  /**
@@ -7,7 +7,7 @@ import { n as createFormatConverter } from "../../converter-udLITX36.mjs";
7
7
  * @example
8
8
  * ```ts
9
9
  * import { createGraph } from '@statelyai/graph';
10
- * import { toGML } from '@statelyai/graph/formats/gml';
10
+ * import { toGML } from '@statelyai/graph/gml';
11
11
  *
12
12
  * const graph = createGraph({
13
13
  * nodes: [{ id: 'a' }, { id: 'b' }],
@@ -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,9 +68,12 @@ 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}`);
75
+ if (edge.points !== void 0) lines.push(` points ${gmlString(JSON.stringify(edge.points))}`);
76
+ if (edge.routing !== void 0) lines.push(` routing ${gmlString(edge.routing)}`);
73
77
  if (edge.sourcePort !== void 0) lines.push(` sourcePort ${gmlString(edge.sourcePort)}`);
74
78
  if (edge.targetPort !== void 0) lines.push(` targetPort ${gmlString(edge.targetPort)}`);
75
79
  if (edge.color) lines.push(` color ${gmlString(edge.color)}`);
@@ -95,7 +99,7 @@ function gmlString(s) {
95
99
  *
96
100
  * @example
97
101
  * ```ts
98
- * import { fromGML } from '@statelyai/graph/formats/gml';
102
+ * import { fromGML } from '@statelyai/graph/gml';
99
103
  *
100
104
  * const graph = fromGML(`
101
105
  * graph [
@@ -134,10 +138,10 @@ function fromGML(gml) {
134
138
  ...n["shape"] && { shape: n["shape"] },
135
139
  ...n["color"] && { color: n["color"] },
136
140
  ...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 }
141
+ ...gfx?.x !== void 0 && { x: parseNumber(gfx.x, "graphics x", "node", id) },
142
+ ...gfx?.y !== void 0 && { y: parseNumber(gfx.y, "graphics y", "node", id) },
143
+ ...gfx?.w !== void 0 && { width: parseNumber(gfx.w, "graphics w", "node", id) },
144
+ ...gfx?.h !== void 0 && { height: parseNumber(gfx.h, "graphics h", "node", id) }
141
145
  });
142
146
  if (n["node"] !== void 0) parseNodes(n, id);
143
147
  }
@@ -146,27 +150,31 @@ function fromGML(gml) {
146
150
  const edgeEntries = asArray(graphBlock["edge"]);
147
151
  for (const e of edgeEntries) {
148
152
  const gfx = e["graphics"];
153
+ const id = String(e["id"] ?? `e${edges.length}`);
149
154
  edges.push({
150
155
  type: "edge",
151
- id: String(e["id"] ?? `e${edges.length}`),
156
+ id,
152
157
  sourceId: String(e["source"] ?? ""),
153
158
  targetId: String(e["target"] ?? ""),
154
159
  label: e["label"] ?? "",
160
+ ...e["mode"] && { mode: String(e["mode"]) },
155
161
  data: e["data"] !== void 0 ? tryParseJSON(e["data"]) : void 0,
156
- ...e["weight"] !== void 0 && { weight: Number(e["weight"]) },
162
+ ...e["weight"] !== void 0 && { weight: parseNumber(e["weight"], "weight", "edge", id) },
163
+ ...e["points"] !== void 0 && { points: tryParseJSON(e["points"]) },
164
+ ...e["routing"] !== void 0 && { routing: String(e["routing"]) },
157
165
  ...e["sourcePort"] !== void 0 && { sourcePort: String(e["sourcePort"]) },
158
166
  ...e["targetPort"] !== void 0 && { targetPort: String(e["targetPort"]) },
159
167
  ...e["color"] && { color: e["color"] },
160
168
  ...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 }
169
+ ...gfx?.x !== void 0 && { x: parseNumber(gfx.x, "graphics x", "edge", id) },
170
+ ...gfx?.y !== void 0 && { y: parseNumber(gfx.y, "graphics y", "edge", id) },
171
+ ...gfx?.w !== void 0 && { width: parseNumber(gfx.w, "graphics w", "edge", id) },
172
+ ...gfx?.h !== void 0 && { height: parseNumber(gfx.h, "graphics h", "edge", id) }
165
173
  });
166
174
  }
167
175
  return {
168
176
  id: graphId,
169
- mode: directed ? "directed" : "undirected",
177
+ mode: graphBlock["mode"] ? String(graphBlock["mode"]) : directed ? "directed" : "undirected",
170
178
  initialNodeId: graphBlock["initialNodeId"] ?? null,
171
179
  nodes,
172
180
  edges,
@@ -301,13 +309,18 @@ function tryParseJSON(str) {
301
309
  return str;
302
310
  }
303
311
  }
312
+ function parseNumber(value, field, kind, ownerId) {
313
+ const parsed = Number(value);
314
+ 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.`);
315
+ return parsed;
316
+ }
304
317
  /**
305
318
  * Bidirectional converter for GML (Graph Modelling Language) format.
306
319
  *
307
320
  * @example
308
321
  * ```ts
309
322
  * import { createGraph } from '@statelyai/graph';
310
- * import { gmlConverter } from '@statelyai/graph/formats/gml';
323
+ * import { gmlConverter } from '@statelyai/graph/gml';
311
324
  *
312
325
  * const graph = createGraph({
313
326
  * nodes: [{ id: 'a' }, { id: 'b' }],
@@ -1,4 +1,4 @@
1
- import { h as GraphFormatConverter, u as Graph } from "../../types-3-FS9NV2.mjs";
1
+ import { _ as GraphFormatConverter, f as Graph } from "../../types-BAEQTwK_.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
- import { t as getEdgeMode } from "../../mode-D8OnHFBk.mjs";
2
- import { n as createFormatConverter } from "../../converter-udLITX36.mjs";
1
+ import { t as getEdgeMode } from "../../mode-gu_mhKKs.mjs";
2
+ import { n as createFormatConverter } from "../../converter-DB6Rg6Vd.mjs";
3
3
  import { XMLBuilder, XMLParser } from "fast-xml-parser";
4
4
 
5
5
  //#region src/formats/graphml/index.ts
@@ -102,6 +102,18 @@ function toGraphML(graph) {
102
102
  "@_attr.name": "weight",
103
103
  "@_attr.type": "double"
104
104
  },
105
+ {
106
+ "@_id": "points",
107
+ "@_for": "edge",
108
+ "@_attr.name": "points",
109
+ "@_attr.type": "string"
110
+ },
111
+ {
112
+ "@_id": "routing",
113
+ "@_for": "edge",
114
+ "@_attr.name": "routing",
115
+ "@_attr.type": "string"
116
+ },
105
117
  {
106
118
  "@_id": "ports",
107
119
  "@_for": "node",
@@ -216,6 +228,14 @@ function toGraphML(graph) {
216
228
  "@_key": "weight",
217
229
  "#text": edge.weight
218
230
  });
231
+ if (edge.points !== void 0) data.push({
232
+ "@_key": "points",
233
+ "#text": JSON.stringify(edge.points)
234
+ });
235
+ if (edge.routing !== void 0) data.push({
236
+ "@_key": "routing",
237
+ "#text": edge.routing
238
+ });
219
239
  if (edge.sourcePort !== void 0) data.push({
220
240
  "@_key": "sourcePort",
221
241
  "#text": edge.sourcePort
@@ -281,8 +301,12 @@ function fromGraphML(xml) {
281
301
  "node",
282
302
  "edge",
283
303
  "data",
284
- "key"
285
- ].includes(name)
304
+ "key",
305
+ "graph",
306
+ "port"
307
+ ].includes(name),
308
+ parseTagValue: false,
309
+ trimValues: false
286
310
  });
287
311
  let parsed;
288
312
  try {
@@ -292,50 +316,79 @@ function fromGraphML(xml) {
292
316
  }
293
317
  const graphml = parsed?.graphml;
294
318
  if (!graphml) throw new Error("GraphML: missing <graphml> root element");
295
- const graphEl = graphml.graph;
319
+ const graphEl = asArray(graphml.graph)[0];
296
320
  if (!graphEl) throw new Error("GraphML: missing <graph> element");
297
321
  const graphType = graphEl["@_edgedefault"] === "undirected" ? "undirected" : "directed";
298
322
  const graphDataMap = parseDataElements(graphEl.data);
299
323
  const graphData = graphDataMap.graphData !== void 0 ? tryParseJSON(graphDataMap.graphData) : void 0;
300
- const nodes = asArray(graphEl.node).map((nodeEl) => {
324
+ const nodeEntries = [];
325
+ const edgeEls = [];
326
+ function collectGraphContents(gEl, parentId) {
327
+ for (const nodeEl of asArray(gEl.node)) {
328
+ nodeEntries.push({
329
+ el: nodeEl,
330
+ structuralParentId: parentId
331
+ });
332
+ for (const subgraphEl of asArray(nodeEl.graph)) collectGraphContents(subgraphEl, String(nodeEl["@_id"]));
333
+ }
334
+ edgeEls.push(...asArray(gEl.edge));
335
+ }
336
+ collectGraphContents(graphEl, null);
337
+ const nodes = nodeEntries.map(({ el: nodeEl, structuralParentId }) => {
301
338
  const dataMap = parseDataElements(nodeEl.data);
339
+ const id = String(nodeEl["@_id"]);
302
340
  const node = {
303
341
  type: "node",
304
- id: String(nodeEl["@_id"]),
305
- parentId: dataMap.parentId ?? null,
342
+ id,
343
+ parentId: dataMap.parentId ?? structuralParentId,
306
344
  initialNodeId: dataMap.initialNodeId ?? null,
307
345
  label: dataMap.label ?? "",
308
346
  data: dataMap.data !== void 0 ? tryParseJSON(dataMap.data) : void 0
309
347
  };
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);
348
+ if (dataMap.x !== void 0) node.x = parseNumber(dataMap.x, "x", "node", id);
349
+ if (dataMap.y !== void 0) node.y = parseNumber(dataMap.y, "y", "node", id);
350
+ if (dataMap.width !== void 0) node.width = parseNumber(dataMap.width, "width", "node", id);
351
+ if (dataMap.height !== void 0) node.height = parseNumber(dataMap.height, "height", "node", id);
314
352
  if (dataMap.shape !== void 0) node.shape = dataMap.shape;
315
353
  if (dataMap.color !== void 0) node.color = dataMap.color;
316
354
  if (dataMap.style !== void 0) node.style = tryParseJSON(dataMap.style);
317
355
  if (dataMap.ports !== void 0) node.ports = tryParseJSON(dataMap.ports);
356
+ else if (nodeEl.port !== void 0) node.ports = collectPorts(nodeEl.port);
318
357
  return node;
319
358
  });
320
- const edges = asArray(graphEl.edge).map((edgeEl) => {
359
+ const usedEdgeIds = new Set(edgeEls.filter((edgeEl) => edgeEl["@_id"] != null).map((edgeEl) => String(edgeEl["@_id"])));
360
+ const edges = edgeEls.map((edgeEl, i) => {
321
361
  const dataMap = parseDataElements(edgeEl.data);
362
+ const source = String(edgeEl["@_source"]);
363
+ const target = String(edgeEl["@_target"]);
364
+ let id;
365
+ if (edgeEl["@_id"] != null) id = String(edgeEl["@_id"]);
366
+ else {
367
+ id = `${source}-${target}-${i}`;
368
+ for (let suffix = 0; usedEdgeIds.has(id); suffix++) id = `${source}-${target}-${i}#e${suffix}`;
369
+ usedEdgeIds.add(id);
370
+ }
322
371
  const edge = {
323
372
  type: "edge",
324
- id: String(edgeEl["@_id"]),
325
- sourceId: String(edgeEl["@_source"]),
326
- targetId: String(edgeEl["@_target"]),
373
+ id,
374
+ sourceId: source,
375
+ targetId: target,
327
376
  label: dataMap.label ?? "",
328
377
  data: dataMap.data !== void 0 ? tryParseJSON(dataMap.data) : void 0
329
378
  };
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);
379
+ if (dataMap.weight !== void 0) edge.weight = parseNumber(dataMap.weight, "weight", "edge", id);
380
+ if (dataMap.points !== void 0) edge.points = tryParseJSON(dataMap.points);
381
+ if (dataMap.routing !== void 0) edge.routing = dataMap.routing;
382
+ if (dataMap.x !== void 0) edge.x = parseNumber(dataMap.x, "x", "edge", id);
383
+ if (dataMap.y !== void 0) edge.y = parseNumber(dataMap.y, "y", "edge", id);
384
+ if (dataMap.width !== void 0) edge.width = parseNumber(dataMap.width, "width", "edge", id);
385
+ if (dataMap.height !== void 0) edge.height = parseNumber(dataMap.height, "height", "edge", id);
335
386
  if (dataMap.color !== void 0) edge.color = dataMap.color;
336
387
  if (dataMap.style !== void 0) edge.style = tryParseJSON(dataMap.style);
337
388
  if (dataMap.sourcePort !== void 0) edge.sourcePort = dataMap.sourcePort;
389
+ else if (edgeEl["@_sourceport"] != null) edge.sourcePort = String(edgeEl["@_sourceport"]);
338
390
  if (dataMap.targetPort !== void 0) edge.targetPort = dataMap.targetPort;
391
+ else if (edgeEl["@_targetport"] != null) edge.targetPort = String(edgeEl["@_targetport"]);
339
392
  const directedAttr = edgeEl["@_directed"];
340
393
  if (directedAttr !== void 0) edge.mode = String(directedAttr) === "false" ? "undirected" : "directed";
341
394
  return edge;
@@ -368,8 +421,28 @@ function tryParseJSON(str) {
368
421
  return str;
369
422
  }
370
423
  }
371
- function parseNumber(value) {
372
- return Number(value);
424
+ /**
425
+ * Flattens native GraphML <port> elements (which may nest) into our port
426
+ * shape. Standard GraphML ports carry only a name; direction is unknowable,
427
+ * so they import as advisory 'inout' with null data.
428
+ */
429
+ function collectPorts(portEls) {
430
+ const ports = [];
431
+ for (const portEl of asArray(portEls)) {
432
+ if (portEl?.["@_name"] == null) continue;
433
+ ports.push({
434
+ name: String(portEl["@_name"]),
435
+ direction: "inout",
436
+ data: null
437
+ });
438
+ if (portEl.port !== void 0) ports.push(...collectPorts(portEl.port) ?? []);
439
+ }
440
+ return ports;
441
+ }
442
+ function parseNumber(value, key, kind, ownerId) {
443
+ const parsed = Number(value);
444
+ 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.`);
445
+ return parsed;
373
446
  }
374
447
  function parseDirection(value) {
375
448
  return [