@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,5 +1,5 @@
1
- import { n as toAdjacencyList, t as fromAdjacencyList } from "./adjacency-list-VsUaH9SJ.mjs";
2
- import { n as toEdgeList, t as fromEdgeList } from "./edge-list-DP4otyPU.mjs";
1
+ import { n as toAdjacencyList, t as fromAdjacencyList } from "./adjacency-list-DQ32Mmhx.mjs";
2
+ import { n as toEdgeList, t as fromEdgeList } from "./edge-list-CA9UTvn2.mjs";
3
3
 
4
4
  //#region src/formats/converter/index.ts
5
5
  /**
@@ -34,7 +34,11 @@ const FORMAT_SUPPORT_MATRIX = [
34
34
  weight: "full",
35
35
  roundTrip: "full"
36
36
  },
37
- notes: ["Uses Cytoscape JSON element data with graph, node, and edge metadata stored in element data.", "Ports round-trip through element data as `ports`, `sourcePort`, and `targetPort`."]
37
+ notes: [
38
+ "Uses Cytoscape JSON element data with graph, node, and edge metadata stored in element data.",
39
+ "Ports round-trip through element data as `ports`, `sourcePort`, and `targetPort`.",
40
+ "Per-edge `mode` overrides (including bidirectional) round-trip through element data."
41
+ ]
38
42
  },
39
43
  {
40
44
  id: "d3",
@@ -49,7 +53,11 @@ const FORMAT_SUPPORT_MATRIX = [
49
53
  weight: "full",
50
54
  roundTrip: "full"
51
55
  },
52
- notes: ["Targets force-graph structures, but graph, node, and edge metadata can be preserved on the loose JSON shape.", "Ports round-trip through node/link objects."]
56
+ notes: [
57
+ "Targets force-graph structures, but graph, node, and edge metadata can be preserved on the loose JSON shape.",
58
+ "Ports round-trip through node/link objects.",
59
+ "Per-edge `mode` overrides (including bidirectional) round-trip through link objects."
60
+ ]
53
61
  },
54
62
  {
55
63
  id: "d2",
@@ -69,6 +77,7 @@ const FORMAT_SUPPORT_MATRIX = [
69
77
  "sql_table/class fields map to ports; node.field connections round-trip as sourcePort/targetPort.",
70
78
  "Per-edge connectors (->, <-, --, <->) map to edge.mode; the authored glyph is preserved in _d2.arrow.",
71
79
  "vars/classes/imports are preserved on graph data; comments attach to the following entity (best-effort).",
80
+ "Only flat key/value vars round-trip; nested sub-blocks inside vars are dropped.",
72
81
  "d2 has no native edge weight."
73
82
  ]
74
83
  },
@@ -115,7 +124,11 @@ const FORMAT_SUPPORT_MATRIX = [
115
124
  weight: "full",
116
125
  roundTrip: "full"
117
126
  },
118
- notes: ["ELK-native layout fields are preserved directly; graph, node, port, and edge metadata round-trip through reserved layout options."]
127
+ notes: [
128
+ "ELK-native layout fields are preserved directly; graph, node, port, and edge metadata round-trip through reserved layout options.",
129
+ "Per-edge `mode` overrides (including bidirectional) round-trip through reserved layout options.",
130
+ "Port ids are emitted as `nodeId__portName` so they are document-unique as ELK requires; original port names round-trip through reserved layout options."
131
+ ]
119
132
  },
120
133
  {
121
134
  id: "gexf",
@@ -149,7 +162,11 @@ const FORMAT_SUPPORT_MATRIX = [
149
162
  weight: "full",
150
163
  roundTrip: "full"
151
164
  },
152
- notes: ["GML stores graph, node, and edge metadata directly or as JSON-stringified fields.", "Ports round-trip through JSON-stringified node metadata and edge fields."]
165
+ notes: [
166
+ "GML stores graph, node, and edge metadata directly or as JSON-stringified fields.",
167
+ "Ports round-trip through JSON-stringified node metadata and edge fields.",
168
+ "Per-edge `mode` overrides and graph-level bidirectional mode round-trip through a dialect `mode` key (GML `directed` is binary)."
169
+ ]
153
170
  },
154
171
  {
155
172
  id: "graphml",
@@ -162,11 +179,13 @@ const FORMAT_SUPPORT_MATRIX = [
162
179
  visual: "partial",
163
180
  style: "partial",
164
181
  weight: "full",
165
- roundTrip: "partial"
182
+ roundTrip: "full"
166
183
  },
167
184
  notes: [
168
- "GraphML attribute fidelity is good, but not every extension is represented.",
169
- "Ports round-trip through node and edge `<data>` fields.",
185
+ "Emit is own-dialect: a flat structure with hierarchy, ports, and metadata in `<data>` fields; the emitter does not write nested `<graph>` or native `<port>` elements.",
186
+ "Import handles both dialects: own-dialect `<data>` fields plus standard nested `<graph>` hierarchy, native `<port>` elements (imported as direction `inout`), and `sourceport`/`targetport` edge attributes. `<data>` fields take precedence.",
187
+ "Multi-graph documents import the first `<graph>` element only.",
188
+ "Vendor extensions (e.g. yEd visual attributes) are not represented.",
170
189
  "Per-edge directedness round-trips via the `directed` edge attribute; bidirectional maps to directed."
171
190
  ]
172
191
  },
@@ -183,7 +202,11 @@ const FORMAT_SUPPORT_MATRIX = [
183
202
  weight: "full",
184
203
  roundTrip: "full"
185
204
  },
186
- notes: ["JGF preserves graph, node, and edge metadata via `metadata` objects.", "Ports round-trip through node and edge metadata."]
205
+ notes: [
206
+ "JGF preserves graph, node, and edge metadata via `metadata` objects.",
207
+ "Ports round-trip through node and edge metadata.",
208
+ "Per-edge `mode` overrides and graph-level bidirectional mode round-trip through `metadata` (JGF `directed` is binary)."
209
+ ]
187
210
  },
188
211
  {
189
212
  id: "tgf",
@@ -213,7 +236,7 @@ const FORMAT_SUPPORT_MATRIX = [
213
236
  weight: "full",
214
237
  roundTrip: "full"
215
238
  },
216
- notes: ["xyflow-native fields are preserved directly; graph, node, edge, style, weight, and port metadata round-trip through reserved data fields."]
239
+ notes: ["xyflow-native fields are preserved directly; graph, node, edge, style, weight, port, and per-edge `mode` metadata round-trip through reserved data fields."]
217
240
  },
218
241
  {
219
242
  id: "mermaid/block",
@@ -331,9 +354,13 @@ const FORMAT_SUPPORT_MATRIX = [
331
354
  visual: "partial",
332
355
  style: "partial",
333
356
  weight: "none",
334
- roundTrip: "full"
357
+ roundTrip: "partial"
335
358
  },
336
- notes: ["State-specific syntax such as notes, classes, descriptions, directions, hierarchy, and parallel regions round-trips through node and graph data."]
359
+ notes: [
360
+ "State-specific syntax such as notes, classes, descriptions, directions, hierarchy, and parallel regions round-trips through node and graph data.",
361
+ "Isolated plain states emit as bare state lines, and node labels emit via the `state \"label\" as id` description form — a distinct label is lost when a description is also present.",
362
+ "`graph.initialNodeId` round-trips as a top-level `[*] -->` transition."
363
+ ]
337
364
  }
338
365
  ];
339
366
  function getFormatSupportEntry(id) {
@@ -1,4 +1,4 @@
1
- import { u as Graph } from "../../types-3-FS9NV2.mjs";
1
+ import { f as Graph } from "../../types-BAEQTwK_.mjs";
2
2
 
3
3
  //#region src/formats/adjacency-list/index.d.ts
4
4
 
@@ -1,3 +1,3 @@
1
- import { n as toAdjacencyList, t as fromAdjacencyList } from "../../adjacency-list-VsUaH9SJ.mjs";
1
+ import { n as toAdjacencyList, t as fromAdjacencyList } from "../../adjacency-list-DQ32Mmhx.mjs";
2
2
 
3
3
  export { fromAdjacencyList, toAdjacencyList };
@@ -1,2 +1,2 @@
1
- import { n as createFormatConverter, r as edgeListConverter, t as adjacencyListConverter } from "../../index-D9Kj6Fe3.mjs";
1
+ import { n as createFormatConverter, r as edgeListConverter, t as adjacencyListConverter } from "../../index-BlbSWUvH.mjs";
2
2
  export { adjacencyListConverter, createFormatConverter, edgeListConverter };
@@ -1,3 +1,3 @@
1
- import { n as createFormatConverter, r as edgeListConverter, t as adjacencyListConverter } from "../../converter-udLITX36.mjs";
1
+ import { n as createFormatConverter, r as edgeListConverter, t as adjacencyListConverter } from "../../converter-DB6Rg6Vd.mjs";
2
2
 
3
3
  export { adjacencyListConverter, createFormatConverter, edgeListConverter };
@@ -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/cytoscape/index.d.ts
4
4
  interface CytoscapeNode {
@@ -33,7 +33,7 @@ interface CytoscapeJSON {
33
33
  * @example
34
34
  * ```ts
35
35
  * import { createGraph } from '@statelyai/graph';
36
- * import { toCytoscapeJSON } from '@statelyai/graph/formats/cytoscape';
36
+ * import { toCytoscapeJSON } from '@statelyai/graph/cytoscape';
37
37
  *
38
38
  * const graph = createGraph({
39
39
  * nodes: [{ id: 'a' }, { id: 'b' }],
@@ -50,7 +50,7 @@ declare function toCytoscapeJSON(graph: Graph): CytoscapeJSON;
50
50
  *
51
51
  * @example
52
52
  * ```ts
53
- * import { fromCytoscapeJSON } from '@statelyai/graph/formats/cytoscape';
53
+ * import { fromCytoscapeJSON } from '@statelyai/graph/cytoscape';
54
54
  *
55
55
  * const graph = fromCytoscapeJSON({
56
56
  * elements: {
@@ -67,7 +67,7 @@ declare function fromCytoscapeJSON(cyto: CytoscapeJSON): Graph;
67
67
  * @example
68
68
  * ```ts
69
69
  * import { createGraph } from '@statelyai/graph';
70
- * import { cytoscapeConverter } from '@statelyai/graph/formats/cytoscape';
70
+ * import { cytoscapeConverter } from '@statelyai/graph/cytoscape';
71
71
  *
72
72
  * const graph = createGraph({
73
73
  * 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/cytoscape/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 { toCytoscapeJSON } from '@statelyai/graph/formats/cytoscape';
10
+ * import { toCytoscapeJSON } from '@statelyai/graph/cytoscape';
11
11
  *
12
12
  * const graph = createGraph({
13
13
  * nodes: [{ id: 'a' }, { id: 'b' }],
@@ -55,8 +55,11 @@ function toCytoscapeJSON(graph) {
55
55
  target: e.targetId
56
56
  };
57
57
  if (e.label) data.label = e.label;
58
+ if (e.mode) data.mode = e.mode;
58
59
  if (e.data !== void 0) data.edgeData = e.data;
59
60
  if (e.weight !== void 0) data.weight = e.weight;
61
+ if (e.points !== void 0) data.points = e.points;
62
+ if (e.routing !== void 0) data.routing = e.routing;
60
63
  if (e.x !== void 0) data.x = e.x;
61
64
  if (e.y !== void 0) data.y = e.y;
62
65
  if (e.width !== void 0) data.width = e.width;
@@ -75,7 +78,7 @@ function toCytoscapeJSON(graph) {
75
78
  *
76
79
  * @example
77
80
  * ```ts
78
- * import { fromCytoscapeJSON } from '@statelyai/graph/formats/cytoscape';
81
+ * import { fromCytoscapeJSON } from '@statelyai/graph/cytoscape';
79
82
  *
80
83
  * const graph = fromCytoscapeJSON({
81
84
  * elements: {
@@ -121,8 +124,11 @@ function fromCytoscapeJSON(cyto) {
121
124
  sourceId: e.data.source,
122
125
  targetId: e.data.target,
123
126
  label: e.data.label ?? "",
127
+ ...e.data.mode && { mode: e.data.mode },
124
128
  data: e.data.edgeData,
125
129
  ...e.data.weight !== void 0 && { weight: e.data.weight },
130
+ ...e.data.points !== void 0 && { points: e.data.points },
131
+ ...e.data.routing !== void 0 && { routing: e.data.routing },
126
132
  ...e.data.x !== void 0 && { x: e.data.x },
127
133
  ...e.data.y !== void 0 && { y: e.data.y },
128
134
  ...e.data.width !== void 0 && { width: e.data.width },
@@ -140,7 +146,7 @@ function fromCytoscapeJSON(cyto) {
140
146
  * @example
141
147
  * ```ts
142
148
  * import { createGraph } from '@statelyai/graph';
143
- * import { cytoscapeConverter } from '@statelyai/graph/formats/cytoscape';
149
+ * import { cytoscapeConverter } from '@statelyai/graph/cytoscape';
144
150
  *
145
151
  * const graph = createGraph({
146
152
  * 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/d2/shared.d.ts
4
4
  type D2Arrow = '->' | '<-' | '--' | '<->';
@@ -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/d2/shared.ts
4
4
  /** Map a d2 connector glyph to a graph edge mode. */
@@ -800,6 +800,17 @@ function fromD2(input) {
800
800
  //#endregion
801
801
  //#region src/formats/d2/emitter.ts
802
802
  const STRUCTURED_SHAPES = new Set(["sql_table", "class"]);
803
+ /**
804
+ * Parser-shaped node data, tolerating graphs not produced by `fromD2` whose
805
+ * `data` may be `undefined`, `null`, or an arbitrary user object.
806
+ */
807
+ function nodeData(node) {
808
+ return node.data ?? {};
809
+ }
810
+ /** Parser-shaped edge data; see {@link nodeData}. */
811
+ function edgeData(edge) {
812
+ return edge.data ?? {};
813
+ }
803
814
  function splitId(id) {
804
815
  const out = [];
805
816
  let buf = "";
@@ -835,7 +846,7 @@ function emitComments(comments, ind, lines) {
835
846
  }
836
847
  function nodeAttrLines(node, ind) {
837
848
  const lines = [];
838
- const d = node.data;
849
+ const d = nodeData(node);
839
850
  if (node.shape) lines.push(`${indent(ind)}shape: ${escapeD2Label(node.shape)}`);
840
851
  if (d.near) lines.push(`${indent(ind)}near: ${escapeD2Label(d.near)}`);
841
852
  if (d.icon) lines.push(`${indent(ind)}icon: ${d.icon}`);
@@ -869,7 +880,8 @@ function portLines(node, ind) {
869
880
  }
870
881
  function labelHeader(node) {
871
882
  if (node.label == null || node.label === "") return "";
872
- if (node.data.labelBlock) return emitLabelBlock(node.label, node.data.labelBlock);
883
+ const labelBlock = nodeData(node).labelBlock;
884
+ if (labelBlock) return emitLabelBlock(node.label, labelBlock);
873
885
  return escapeD2Label(node.label);
874
886
  }
875
887
  /** Whether a node carries its own attributes/ports/label (needs a line of its own). */
@@ -883,7 +895,7 @@ function hasOwnContent(node) {
883
895
  */
884
896
  function isPurePrefix(ctx, node) {
885
897
  const children = ctx.childrenOf.get(node.id) ?? [];
886
- return node.data.declarationForm !== "block" && children.length > 0 && !hasOwnContent(node);
898
+ return nodeData(node).declarationForm !== "block" && children.length > 0 && !hasOwnContent(node);
887
899
  }
888
900
  /**
889
901
  * Emit the items (child nodes + owned edges) of a scope, in `data.order` when
@@ -893,7 +905,7 @@ function isPurePrefix(ctx, node) {
893
905
  function emitScope(ctx, container, containerId, scopeSegs, ind, lines) {
894
906
  const children = ctx.childrenOf.get(containerId) ?? [];
895
907
  const ownedEdges = container ? ctx.edgesByOwner.get(container.id) ?? [] : [];
896
- const order = container?.data.order;
908
+ const order = container ? nodeData(container).order : void 0;
897
909
  if (order) {
898
910
  const childById = new Map(children.map((c) => [c.id, c]));
899
911
  const edgeById = new Map(ownedEdges.map((e) => [e.id, e]));
@@ -918,7 +930,7 @@ function emitItem(ctx, node, scopeSegs, ind, lines) {
918
930
  emitScope(ctx, node, node.id, scopeSegs, ind, lines);
919
931
  return;
920
932
  }
921
- emitComments(node.data.commentsBefore, ind, lines);
933
+ emitComments(nodeData(node).commentsBefore, ind, lines);
922
934
  const label = labelHeader(node);
923
935
  const structured = node.shape && STRUCTURED_SHAPES.has(node.shape);
924
936
  const attrLines = nodeAttrLines(node, ind + 1);
@@ -946,8 +958,9 @@ function endpointRef(nodeId$1, port, scopeSegs) {
946
958
  }
947
959
  function emitEdge(ctx, edge, ind, lines, scoped) {
948
960
  ctx.emittedEdges.add(edge.id);
949
- emitComments(edge.data.commentsBefore, ind, lines);
950
- const arrow = edge.data.arrow ?? modeToArrow(edge);
961
+ const d = edgeData(edge);
962
+ emitComments(d.commentsBefore, ind, lines);
963
+ const arrow = d.arrow ?? modeToArrow(edge);
951
964
  const owner = scoped ? ctx.ownerOfEdge.get(edge.id) : void 0;
952
965
  const scopeSegs = owner ? splitId(owner) : [];
953
966
  let sId = edge.sourceId;
@@ -962,7 +975,7 @@ function emitEdge(ctx, edge, ind, lines, scoped) {
962
975
  const right = endpointRef(tId, tPort, scopeSegs);
963
976
  let line = `${indent(ind)}${left} ${arrow} ${right}`;
964
977
  if (edge.label != null && edge.label !== "") {
965
- const lbl = edge.data.labelBlock ? emitLabelBlock(edge.label, edge.data.labelBlock) : escapeD2Label(edge.label);
978
+ const lbl = d.labelBlock ? emitLabelBlock(edge.label, d.labelBlock) : escapeD2Label(edge.label);
966
979
  line += `: ${lbl}`;
967
980
  }
968
981
  const blockLines = edgeBlockLines(edge);
@@ -975,10 +988,11 @@ function emitEdge(ctx, edge, ind, lines, scoped) {
975
988
  function edgeBlockLines(edge) {
976
989
  const out = [];
977
990
  if (edge.style) for (const [k, v] of Object.entries(edge.style)) out.push(`style.${k}: ${styleValueToD2(v)}`);
978
- const { sourceArrowhead, targetArrowhead } = edge.data;
991
+ const d = edgeData(edge);
992
+ const { sourceArrowhead, targetArrowhead } = d;
979
993
  if (sourceArrowhead?.shape) out.push(`source-arrowhead.shape: ${sourceArrowhead.shape}`);
980
994
  if (targetArrowhead?.shape) out.push(`target-arrowhead.shape: ${targetArrowhead.shape}`);
981
- if (edge.data.reserved) for (const [k, v] of Object.entries(edge.data.reserved)) out.push(`${k}: ${styleValueToD2(v)}`);
995
+ if (d.reserved) for (const [k, v] of Object.entries(d.reserved)) out.push(`${k}: ${styleValueToD2(v)}`);
982
996
  return out;
983
997
  }
984
998
  function modeToArrow(edge) {
@@ -999,7 +1013,7 @@ function buildEdgeOwnership(graph) {
999
1013
  const ownerOfEdge = /* @__PURE__ */ new Map();
1000
1014
  const edgeById = new Map(graph.edges.map((e) => [e.id, e]));
1001
1015
  for (const node of graph.nodes) {
1002
- const order = node.data.order;
1016
+ const order = nodeData(node).order;
1003
1017
  if (!order) continue;
1004
1018
  for (const refId of order) {
1005
1019
  const edge = edgeById.get(refId);
@@ -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/d3/index.d.ts
4
4
  interface D3Node {
@@ -27,7 +27,7 @@ interface D3Graph {
27
27
  * @example
28
28
  * ```ts
29
29
  * import { createGraph } from '@statelyai/graph';
30
- * import { toD3Graph } from '@statelyai/graph/formats/d3';
30
+ * import { toD3Graph } from '@statelyai/graph/d3';
31
31
  *
32
32
  * const graph = createGraph({
33
33
  * nodes: [{ id: 'a' }, { id: 'b' }],
@@ -44,7 +44,7 @@ declare function toD3Graph(graph: Graph): D3Graph;
44
44
  *
45
45
  * @example
46
46
  * ```ts
47
- * import { fromD3Graph } from '@statelyai/graph/formats/d3';
47
+ * import { fromD3Graph } from '@statelyai/graph/d3';
48
48
  *
49
49
  * const graph = fromD3Graph({
50
50
  * nodes: [{ id: 'a' }, { id: 'b' }],
@@ -59,7 +59,7 @@ declare function fromD3Graph(d3: D3Graph): Graph;
59
59
  * @example
60
60
  * ```ts
61
61
  * import { createGraph } from '@statelyai/graph';
62
- * import { d3Converter } from '@statelyai/graph/formats/d3';
62
+ * import { d3Converter } from '@statelyai/graph/d3';
63
63
  *
64
64
  * const graph = createGraph({
65
65
  * 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/d3/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 { toD3Graph } from '@statelyai/graph/formats/d3';
10
+ * import { toD3Graph } from '@statelyai/graph/d3';
11
11
  *
12
12
  * const graph = createGraph({
13
13
  * nodes: [{ id: 'a' }, { id: 'b' }],
@@ -49,8 +49,11 @@ function toD3Graph(graph) {
49
49
  };
50
50
  if (e.id) link.id = e.id;
51
51
  if (e.label) link.label = e.label;
52
+ if (e.mode) link.mode = e.mode;
52
53
  if (e.data !== void 0) link.data = e.data;
53
54
  if (e.weight !== void 0) link.weight = e.weight;
55
+ if (e.points !== void 0) link.points = e.points;
56
+ if (e.routing !== void 0) link.routing = e.routing;
54
57
  if (e.x !== void 0) link.x = e.x;
55
58
  if (e.y !== void 0) link.y = e.y;
56
59
  if (e.width !== void 0) link.width = e.width;
@@ -68,7 +71,7 @@ function toD3Graph(graph) {
68
71
  *
69
72
  * @example
70
73
  * ```ts
71
- * import { fromD3Graph } from '@statelyai/graph/formats/d3';
74
+ * import { fromD3Graph } from '@statelyai/graph/d3';
72
75
  *
73
76
  * const graph = fromD3Graph({
74
77
  * nodes: [{ id: 'a' }, { id: 'b' }],
@@ -109,8 +112,11 @@ function fromD3Graph(d3) {
109
112
  sourceId: typeof l.source === "string" ? l.source : l.source.id,
110
113
  targetId: typeof l.target === "string" ? l.target : l.target.id,
111
114
  label: l.label ?? "",
115
+ ...l.mode && { mode: l.mode },
112
116
  data: l.data,
113
117
  ...l.weight !== void 0 && { weight: l.weight },
118
+ ...l.points !== void 0 && { points: l.points },
119
+ ...l.routing !== void 0 && { routing: l.routing },
114
120
  ...l.x !== void 0 && { x: l.x },
115
121
  ...l.y !== void 0 && { y: l.y },
116
122
  ...l.width !== void 0 && { width: l.width },
@@ -128,7 +134,7 @@ function fromD3Graph(d3) {
128
134
  * @example
129
135
  * ```ts
130
136
  * import { createGraph } from '@statelyai/graph';
131
- * import { d3Converter } from '@statelyai/graph/formats/d3';
137
+ * import { d3Converter } from '@statelyai/graph/d3';
132
138
  *
133
139
  * const graph = createGraph({
134
140
  * 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/dot/index.d.ts
4
4
 
@@ -1,15 +1,31 @@
1
- import { n as createFormatConverter } from "../../converter-udLITX36.mjs";
1
+ import { n as createFormatConverter } from "../../converter-DB6Rg6Vd.mjs";
2
2
  import parse from "dotparser";
3
3
 
4
4
  //#region src/formats/dot/index.ts
5
+ /** DOT reserved keywords — must be quoted when used as identifiers. */
6
+ const DOT_KEYWORDS = new Set([
7
+ "node",
8
+ "edge",
9
+ "graph",
10
+ "digraph",
11
+ "subgraph",
12
+ "strict"
13
+ ]);
5
14
  /** Escape a DOT identifier */
6
15
  function escapeId(id) {
7
- if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(id)) return id;
16
+ if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(id) && !DOT_KEYWORDS.has(id.toLowerCase())) return id;
8
17
  return `"${id.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
9
18
  }
10
19
  /** Escape a DOT label string */
11
20
  function escapeLabel(label) {
12
- return label.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
21
+ return label.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
22
+ }
23
+ /**
24
+ * Invert {@link escapeLabel}. dotparser unescapes `\"` itself but passes
25
+ * `\\` and `\n` through verbatim.
26
+ */
27
+ function unescapeLabel(label) {
28
+ return label.replace(/\\(\\|n)/g, (_, ch) => ch === "n" ? "\n" : "\\");
13
29
  }
14
30
  function formatEndpoint(id, port) {
15
31
  return `${escapeId(id)}${port ? `:${escapeId(port)}` : ""}`;
@@ -112,7 +128,7 @@ function nodeFromAttrs(id, attrs, defaults, parentId) {
112
128
  ...defaults,
113
129
  ...attrs
114
130
  };
115
- const label = merged["label"] ?? "";
131
+ const label = unescapeLabel(merged["label"] ?? "");
116
132
  const rawShape = merged["shape"];
117
133
  const shape = rawShape ? DOT_TO_SHAPE[rawShape] ?? rawShape : void 0;
118
134
  const color = merged["fillcolor"] ?? merged["color"] ?? void 0;
@@ -235,7 +251,7 @@ function fromDOT(dot) {
235
251
  id: `e${edgeIdx++}`,
236
252
  sourceId: source.id,
237
253
  targetId: target.id,
238
- label: mergedEdgeAttrs["label"] ?? "",
254
+ label: unescapeLabel(mergedEdgeAttrs["label"] ?? ""),
239
255
  data: void 0,
240
256
  ...source.port && { sourcePort: source.port },
241
257
  ...target.port && { targetPort: target.port },
@@ -251,7 +267,7 @@ function fromDOT(dot) {
251
267
  let subLabel = "";
252
268
  for (const child of stmt.children) if (child.type === "attr_stmt" && child.target === "graph") {
253
269
  const ga = attrsToMap(child.attr_list);
254
- if (ga["label"]) subLabel = ga["label"];
270
+ if (ga["label"]) subLabel = unescapeLabel(ga["label"]);
255
271
  }
256
272
  const subNode = {
257
273
  type: "node",
@@ -1,4 +1,4 @@
1
- import { u as Graph } from "../../types-3-FS9NV2.mjs";
1
+ import { f as Graph } from "../../types-BAEQTwK_.mjs";
2
2
 
3
3
  //#region src/formats/edge-list/index.d.ts
4
4
 
@@ -1,3 +1,3 @@
1
- import { n as toEdgeList, t as fromEdgeList } from "../../edge-list-DP4otyPU.mjs";
1
+ import { n as toEdgeList, t as fromEdgeList } from "../../edge-list-CA9UTvn2.mjs";
2
2
 
3
3
  export { fromEdgeList, toEdgeList };
@@ -1,4 +1,4 @@
1
- import { F as VisualGraphFormatConverter, M as VisualGraph } from "../../types-3-FS9NV2.mjs";
1
+ import { I as VisualGraph, z as VisualGraphFormatConverter } from "../../types-BAEQTwK_.mjs";
2
2
  import { ElkEdge, ElkEdgeSection, ElkExtendedEdge, ElkGraphElement, ElkLabel, ElkNode, ElkNode as ElkNode$1, ElkPoint, ElkPort, ElkPrimitiveEdge, ElkShape, LayoutOptions } from "elkjs/lib/elk-api";
3
3
 
4
4
  //#region src/formats/elk/index.d.ts