@statelyai/graph 0.1.0 → 0.3.1

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 (52) hide show
  1. package/README.md +65 -15
  2. package/dist/{adjacency-list-CXpOCibq.mjs → adjacency-list-ITO40kmn.mjs} +34 -1
  3. package/dist/{algorithms-R35X6ro4.mjs → algorithms-NWSB2RWj.mjs} +753 -19
  4. package/dist/algorithms.d.mts +488 -11
  5. package/dist/algorithms.mjs +2 -2
  6. package/dist/converter-CchokMDg.mjs +67 -0
  7. package/dist/edge-list-CgX6bBIF.mjs +71 -0
  8. package/dist/formats/adjacency-list/index.d.mts +44 -0
  9. package/dist/formats/adjacency-list/index.mjs +3 -0
  10. package/dist/formats/converter/index.d.mts +61 -0
  11. package/dist/formats/converter/index.mjs +3 -0
  12. package/dist/formats/cytoscape/index.d.mts +83 -0
  13. package/dist/formats/cytoscape/index.mjs +135 -0
  14. package/dist/formats/d3/index.d.mts +68 -0
  15. package/dist/formats/d3/index.mjs +111 -0
  16. package/dist/formats/dot/index.d.mts +63 -0
  17. package/dist/formats/dot/index.mjs +288 -0
  18. package/dist/formats/edge-list/index.d.mts +43 -0
  19. package/dist/formats/edge-list/index.mjs +3 -0
  20. package/dist/formats/gexf/index.d.mts +9 -0
  21. package/dist/formats/gexf/index.mjs +249 -0
  22. package/dist/formats/gml/index.d.mts +65 -0
  23. package/dist/formats/gml/index.mjs +291 -0
  24. package/dist/formats/graphml/index.d.mts +9 -0
  25. package/dist/{graphml-CUTNRXqd.mjs → formats/graphml/index.mjs} +18 -4
  26. package/dist/formats/jgf/index.d.mts +79 -0
  27. package/dist/formats/jgf/index.mjs +134 -0
  28. package/dist/formats/mermaid/index.d.mts +381 -0
  29. package/dist/formats/mermaid/index.mjs +2237 -0
  30. package/dist/formats/tgf/index.d.mts +54 -0
  31. package/dist/formats/tgf/index.mjs +111 -0
  32. package/dist/index.d.mts +332 -21
  33. package/dist/index.mjs +117 -13
  34. package/dist/{indexing-BHg1VhqN.mjs → indexing-eNDrXdDA.mjs} +31 -2
  35. package/dist/queries.d.mts +430 -9
  36. package/dist/queries.mjs +472 -9
  37. package/dist/{types-XV3S5Jnh.d.mts → types-BDXC1O5b.d.mts} +37 -2
  38. package/package.json +43 -17
  39. package/dist/adjacency-list-DW-lAUe8.d.mts +0 -10
  40. package/dist/dot-BRtq3e3c.mjs +0 -59
  41. package/dist/dot-HmJeUMsj.d.mts +0 -6
  42. package/dist/edge-list-BRujEnnU.mjs +0 -39
  43. package/dist/edge-list-CJmfoNu2.d.mts +0 -10
  44. package/dist/formats/adjacency-list.d.mts +0 -2
  45. package/dist/formats/adjacency-list.mjs +0 -3
  46. package/dist/formats/dot.d.mts +0 -2
  47. package/dist/formats/dot.mjs +0 -3
  48. package/dist/formats/edge-list.d.mts +0 -2
  49. package/dist/formats/edge-list.mjs +0 -3
  50. package/dist/formats/graphml.d.mts +0 -2
  51. package/dist/formats/graphml.mjs +0 -3
  52. package/dist/graphml-CMjPzSfY.d.mts +0 -7
@@ -0,0 +1,2237 @@
1
+ import { n as createFormatConverter } from "../../converter-CchokMDg.mjs";
2
+
3
+ //#region src/formats/mermaid/shared.ts
4
+ const MERMAID_TO_DIRECTION = {
5
+ TB: "down",
6
+ TD: "down",
7
+ BT: "up",
8
+ LR: "right",
9
+ RL: "left"
10
+ };
11
+ const DIRECTION_TO_MERMAID = {
12
+ down: "TD",
13
+ up: "BT",
14
+ right: "LR",
15
+ left: "RL"
16
+ };
17
+ /** Escape a label for Mermaid output (quotes special chars). */
18
+ function escapeMermaidLabel(s) {
19
+ return s.replace(/\\/g, "\\\\").replace(/"/g, "#quot;").replace(/;/g, "#59;").replace(/#(?!quot;|59;|35;)/g, "#35;");
20
+ }
21
+ /** Unescape a Mermaid label back to plain text. */
22
+ function unescapeMermaidLabel(s) {
23
+ return s.replace(/#quot;/g, "\"").replace(/#59;/g, ";").replace(/#35;/g, "#");
24
+ }
25
+ /** Generate a deterministic edge ID from source, target, and index. */
26
+ function generateEdgeId(sourceId, targetId, index) {
27
+ return `${sourceId}-${targetId}-${index}`;
28
+ }
29
+ /** Strip `%%` single-line comments from Mermaid input. */
30
+ function stripComments(input) {
31
+ return input.split("\n").map((line) => {
32
+ const idx = line.indexOf("%%");
33
+ if (idx === -1) return line;
34
+ if ((line.slice(0, idx).match(/"/g) || []).length % 2 !== 0) return line;
35
+ return line.slice(0, idx);
36
+ }).join("\n");
37
+ }
38
+ /** Strip `%%{init: ...}%%` directives and return them separately. */
39
+ function stripDirectives(input) {
40
+ const directives = {};
41
+ return {
42
+ directives,
43
+ cleaned: input.replace(/%%\{[\s]*init[\s]*:[\s]*(.*?)[\s]*\}%%/gs, (_match, json) => {
44
+ try {
45
+ Object.assign(directives, JSON.parse(`{${json}}`));
46
+ } catch {}
47
+ return "";
48
+ })
49
+ };
50
+ }
51
+ /**
52
+ * Split input into non-empty trimmed lines, stripping comments and directives.
53
+ * Returns the cleaned lines and any extracted directives.
54
+ */
55
+ function prepareLines(input) {
56
+ const { directives, cleaned } = stripDirectives(input);
57
+ return {
58
+ lines: stripComments(cleaned).split("\n").map((l) => l.trimEnd()).filter((l) => l.trim() !== ""),
59
+ directives
60
+ };
61
+ }
62
+ /** Validate input is a non-empty string, throw with prefix if not. */
63
+ function validateInput(input, prefix) {
64
+ if (typeof input !== "string") throw new Error(`${prefix}: expected a string`);
65
+ if (!input.trim()) throw new Error(`${prefix}: input is empty`);
66
+ }
67
+
68
+ //#endregion
69
+ //#region src/formats/mermaid/sequence.ts
70
+ const ARROW_PATTERNS = [
71
+ ["<<-->>", {
72
+ stroke: "dotted",
73
+ arrowType: "filled",
74
+ bidirectional: true
75
+ }],
76
+ ["<<->>", {
77
+ stroke: "solid",
78
+ arrowType: "filled",
79
+ bidirectional: true
80
+ }],
81
+ ["-->>", {
82
+ stroke: "dotted",
83
+ arrowType: "filled",
84
+ bidirectional: false
85
+ }],
86
+ ["-->", {
87
+ stroke: "dotted",
88
+ arrowType: "open",
89
+ bidirectional: false
90
+ }],
91
+ ["--x", {
92
+ stroke: "dotted",
93
+ arrowType: "cross",
94
+ bidirectional: false
95
+ }],
96
+ ["--)", {
97
+ stroke: "dotted",
98
+ arrowType: "async",
99
+ bidirectional: false
100
+ }],
101
+ ["->>", {
102
+ stroke: "solid",
103
+ arrowType: "filled",
104
+ bidirectional: false
105
+ }],
106
+ ["->", {
107
+ stroke: "solid",
108
+ arrowType: "open",
109
+ bidirectional: false
110
+ }],
111
+ ["-x", {
112
+ stroke: "solid",
113
+ arrowType: "cross",
114
+ bidirectional: false
115
+ }],
116
+ ["-)", {
117
+ stroke: "solid",
118
+ arrowType: "async",
119
+ bidirectional: false
120
+ }]
121
+ ];
122
+ function parseArrow(arrow) {
123
+ for (const [pattern, info] of ARROW_PATTERNS) if (arrow === pattern) return info;
124
+ }
125
+ const MESSAGE_RE = /^(\S+?)\s*(<<-->>|<<->>|-->>|-->|--x|--\)|->>|->|-x|-\))\s*(\S+?)\s*:\s*(.*)$/;
126
+ /**
127
+ * Parses a Mermaid sequence diagram string into a Graph.
128
+ *
129
+ * @example
130
+ * const graph = fromMermaidSequence(`
131
+ * sequenceDiagram
132
+ * participant Alice
133
+ * participant Bob
134
+ * Alice->>Bob: Hello
135
+ * Bob-->>Alice: Hi back
136
+ * `);
137
+ */
138
+ function fromMermaidSequence(input) {
139
+ validateInput(input, "Mermaid sequence");
140
+ const { lines } = prepareLines(input);
141
+ const header = lines[0]?.trim();
142
+ if (!header || !header.startsWith("sequenceDiagram")) throw new Error("Mermaid sequence: expected \"sequenceDiagram\" header");
143
+ const nodeMap = /* @__PURE__ */ new Map();
144
+ const edges = [];
145
+ const blocks = [];
146
+ let autonumber = false;
147
+ let edgeCounter = 0;
148
+ let seqNum = 0;
149
+ const blockStack = [];
150
+ function ensureNode(id, actorType = "participant") {
151
+ if (!nodeMap.has(id)) nodeMap.set(id, {
152
+ type: "node",
153
+ id,
154
+ parentId: null,
155
+ initialNodeId: null,
156
+ label: id,
157
+ data: { actorType }
158
+ });
159
+ }
160
+ function addEdge(edge) {
161
+ edges.push(edge);
162
+ if (blockStack.length > 0) {
163
+ const top = blockStack[blockStack.length - 1];
164
+ if (top.branches && top.branches.length > 0) top.branches[top.branches.length - 1].edgeIds.push(edge.id);
165
+ else if (top.options && top.options.length > 0) top.options[top.options.length - 1].edgeIds.push(edge.id);
166
+ else top.edgeIds.push(edge.id);
167
+ }
168
+ }
169
+ for (let i = 1; i < lines.length; i++) {
170
+ const line = lines[i].trim();
171
+ if (!line) continue;
172
+ if (line === "autonumber") {
173
+ autonumber = true;
174
+ continue;
175
+ }
176
+ const participantMatch = line.match(/^(participant|actor)\s+(\S+?)(?:\s+as\s+(.+))?$/);
177
+ if (participantMatch) {
178
+ const actorType = participantMatch[1];
179
+ const id = participantMatch[2];
180
+ const alias = participantMatch[3]?.trim();
181
+ ensureNode(id, actorType);
182
+ const node = nodeMap.get(id);
183
+ node.data.actorType = actorType;
184
+ if (alias) {
185
+ node.data.alias = alias;
186
+ node.label = alias;
187
+ }
188
+ continue;
189
+ }
190
+ const createMatch = line.match(/^create\s+(participant|actor)\s+(\S+?)(?:\s+as\s+(.+))?$/);
191
+ if (createMatch) {
192
+ const actorType = createMatch[1];
193
+ const id = createMatch[2];
194
+ const alias = createMatch[3]?.trim();
195
+ ensureNode(id, actorType);
196
+ const node = nodeMap.get(id);
197
+ node.data.created = true;
198
+ if (alias) {
199
+ node.data.alias = alias;
200
+ node.label = alias;
201
+ }
202
+ continue;
203
+ }
204
+ const destroyMatch = line.match(/^destroy\s+(\S+)$/);
205
+ if (destroyMatch) {
206
+ const id = destroyMatch[1];
207
+ ensureNode(id);
208
+ nodeMap.get(id).data.destroyed = true;
209
+ continue;
210
+ }
211
+ const activateMatch = line.match(/^(activate|deactivate)\s+(\S+)$/);
212
+ if (activateMatch) {
213
+ const kind = activateMatch[1] === "activate" ? "activation" : "deactivation";
214
+ const actorId = activateMatch[2];
215
+ ensureNode(actorId);
216
+ addEdge({
217
+ type: "edge",
218
+ id: generateEdgeId(actorId, actorId, edgeCounter++),
219
+ sourceId: actorId,
220
+ targetId: actorId,
221
+ label: "",
222
+ data: {
223
+ kind,
224
+ ...autonumber && { sequenceNumber: ++seqNum }
225
+ }
226
+ });
227
+ continue;
228
+ }
229
+ if (/^(loop|alt|opt|par|critical|break)\s+/.test(line) || line.startsWith("rect ")) {
230
+ const spaceIdx = line.indexOf(" ");
231
+ const keyword = line.slice(0, spaceIdx);
232
+ const label = line.slice(spaceIdx + 1).trim();
233
+ if (keyword === "alt" || keyword === "par" || keyword === "critical") blockStack.push({
234
+ type: keyword,
235
+ label,
236
+ edgeIds: [],
237
+ branches: keyword === "par" ? [{
238
+ label,
239
+ edgeIds: []
240
+ }] : keyword === "alt" ? [{
241
+ label,
242
+ edgeIds: []
243
+ }] : void 0,
244
+ options: keyword === "critical" ? [] : void 0
245
+ });
246
+ else if (keyword === "rect") blockStack.push({
247
+ type: "rect",
248
+ label: "",
249
+ edgeIds: [],
250
+ color: label
251
+ });
252
+ else blockStack.push({
253
+ type: keyword,
254
+ label,
255
+ edgeIds: []
256
+ });
257
+ continue;
258
+ }
259
+ if (line.startsWith("else") || line === "else") {
260
+ if (blockStack.length > 0) {
261
+ const top = blockStack[blockStack.length - 1];
262
+ if (top.branches) {
263
+ const elseLabel = line.length > 4 ? line.slice(5).trim() : void 0;
264
+ top.branches.push({
265
+ label: elseLabel,
266
+ edgeIds: []
267
+ });
268
+ }
269
+ }
270
+ continue;
271
+ }
272
+ if (line.startsWith("and ")) {
273
+ if (blockStack.length > 0) {
274
+ const top = blockStack[blockStack.length - 1];
275
+ if (top.branches) top.branches.push({
276
+ label: line.slice(4).trim(),
277
+ edgeIds: []
278
+ });
279
+ }
280
+ continue;
281
+ }
282
+ if (line.startsWith("option ")) {
283
+ if (blockStack.length > 0) {
284
+ const top = blockStack[blockStack.length - 1];
285
+ if (top.options) top.options.push({
286
+ label: line.slice(7).trim(),
287
+ edgeIds: []
288
+ });
289
+ }
290
+ continue;
291
+ }
292
+ if (line === "end") {
293
+ if (blockStack.length > 0) {
294
+ const finished = blockStack.pop();
295
+ const block = buildBlock(finished);
296
+ if (block) {
297
+ if (blockStack.length > 0) blockStack[blockStack.length - 1].edgeIds.push(...finished.edgeIds, ...finished.branches?.flatMap((b) => b.edgeIds) ?? [], ...finished.options?.flatMap((o) => o.edgeIds) ?? []);
298
+ blocks.push(block);
299
+ }
300
+ }
301
+ continue;
302
+ }
303
+ if (/^Note\s+(left|right|over)\s+/i.test(line)) continue;
304
+ const msgMatch = line.match(MESSAGE_RE);
305
+ if (msgMatch) {
306
+ let sourceId = msgMatch[1];
307
+ const arrowStr = msgMatch[2];
308
+ let targetId = msgMatch[3];
309
+ const messageText = msgMatch[4].trim();
310
+ let activationOnTarget = null;
311
+ if (targetId.startsWith("+")) {
312
+ activationOnTarget = "activation";
313
+ targetId = targetId.slice(1);
314
+ } else if (targetId.startsWith("-")) {
315
+ activationOnTarget = "deactivation";
316
+ targetId = targetId.slice(1);
317
+ }
318
+ let activationOnSource = null;
319
+ if (sourceId.endsWith("+")) {
320
+ activationOnSource = "activation";
321
+ sourceId = sourceId.slice(0, -1);
322
+ } else if (sourceId.endsWith("-")) {
323
+ activationOnSource = "deactivation";
324
+ sourceId = sourceId.slice(0, -1);
325
+ }
326
+ ensureNode(sourceId);
327
+ ensureNode(targetId);
328
+ const arrowInfo = parseArrow(arrowStr);
329
+ if (!arrowInfo) continue;
330
+ const edgeId = generateEdgeId(sourceId, targetId, edgeCounter++);
331
+ const data = {
332
+ kind: "message",
333
+ stroke: arrowInfo.stroke,
334
+ arrowType: arrowInfo.arrowType,
335
+ ...arrowInfo.bidirectional && { bidirectional: true },
336
+ ...autonumber && { sequenceNumber: ++seqNum }
337
+ };
338
+ addEdge({
339
+ type: "edge",
340
+ id: edgeId,
341
+ sourceId,
342
+ targetId,
343
+ label: unescapeMermaidLabel(messageText),
344
+ data
345
+ });
346
+ if (activationOnTarget) addEdge({
347
+ type: "edge",
348
+ id: generateEdgeId(targetId, targetId, edgeCounter++),
349
+ sourceId: targetId,
350
+ targetId,
351
+ label: "",
352
+ data: { kind: activationOnTarget }
353
+ });
354
+ if (activationOnSource) addEdge({
355
+ type: "edge",
356
+ id: generateEdgeId(sourceId, sourceId, edgeCounter++),
357
+ sourceId,
358
+ targetId: sourceId,
359
+ label: "",
360
+ data: { kind: activationOnSource }
361
+ });
362
+ continue;
363
+ }
364
+ }
365
+ return {
366
+ id: "",
367
+ type: "directed",
368
+ initialNodeId: null,
369
+ nodes: Array.from(nodeMap.values()),
370
+ edges,
371
+ data: {
372
+ diagramType: "sequence",
373
+ ...autonumber && { autonumber: true },
374
+ ...blocks.length > 0 && { blocks }
375
+ }
376
+ };
377
+ }
378
+ function buildBlock(raw) {
379
+ switch (raw.type) {
380
+ case "loop": return {
381
+ type: "loop",
382
+ label: raw.label,
383
+ edgeIds: raw.edgeIds
384
+ };
385
+ case "alt": return {
386
+ type: "alt",
387
+ label: raw.label,
388
+ branches: raw.branches ?? [{ edgeIds: raw.edgeIds }]
389
+ };
390
+ case "opt": return {
391
+ type: "opt",
392
+ label: raw.label,
393
+ edgeIds: raw.edgeIds
394
+ };
395
+ case "par": return {
396
+ type: "par",
397
+ branches: (raw.branches ?? []).map((b) => ({
398
+ label: b.label ?? "",
399
+ edgeIds: b.edgeIds
400
+ }))
401
+ };
402
+ case "critical": return {
403
+ type: "critical",
404
+ label: raw.label,
405
+ edgeIds: raw.edgeIds,
406
+ ...raw.options && raw.options.length > 0 && { options: raw.options }
407
+ };
408
+ case "break": return {
409
+ type: "break",
410
+ label: raw.label,
411
+ edgeIds: raw.edgeIds
412
+ };
413
+ case "rect": return {
414
+ type: "rect",
415
+ color: raw.color ?? "",
416
+ edgeIds: raw.edgeIds
417
+ };
418
+ default: return null;
419
+ }
420
+ }
421
+ const ARROW_MAP = {
422
+ solid: {
423
+ open: "->",
424
+ filled: "->>",
425
+ cross: "-x",
426
+ async: "-)"
427
+ },
428
+ dotted: {
429
+ open: "-->",
430
+ filled: "-->>",
431
+ cross: "--x",
432
+ async: "--)"
433
+ }
434
+ };
435
+ /**
436
+ * Converts a sequence diagram Graph to a Mermaid sequence diagram string.
437
+ *
438
+ * @example
439
+ * const mermaid = toMermaidSequence(graph);
440
+ * // "sequenceDiagram\n participant Alice\n ..."
441
+ */
442
+ function toMermaidSequence(graph) {
443
+ const lines = ["sequenceDiagram"];
444
+ const gd = graph.data;
445
+ if (gd?.autonumber) lines.push(" autonumber");
446
+ for (const node of graph.nodes) {
447
+ const d = node.data;
448
+ const keyword = d?.actorType === "actor" ? "actor" : "participant";
449
+ const alias = d?.alias ? ` as ${escapeMermaidLabel(d.alias)}` : "";
450
+ if (d?.created) lines.push(` create ${keyword} ${node.id}${alias}`);
451
+ else lines.push(` ${keyword} ${node.id}${alias}`);
452
+ }
453
+ const blocks = gd?.blocks ?? [];
454
+ const edgeIdSet = new Set(graph.edges.map((e) => e.id));
455
+ const beforeEdge = /* @__PURE__ */ new Map();
456
+ const afterEdge = /* @__PURE__ */ new Map();
457
+ for (const block of blocks) if (block.type === "alt") {
458
+ const firstEdge = block.branches[0]?.edgeIds[0];
459
+ if (firstEdge && edgeIdSet.has(firstEdge)) {
460
+ const events = beforeEdge.get(firstEdge) ?? [];
461
+ events.push({
462
+ type: "open",
463
+ block
464
+ });
465
+ beforeEdge.set(firstEdge, events);
466
+ }
467
+ for (let bi = 1; bi < block.branches.length; bi++) {
468
+ const branchFirstEdge = block.branches[bi]?.edgeIds[0];
469
+ if (branchFirstEdge && edgeIdSet.has(branchFirstEdge)) {
470
+ const events = beforeEdge.get(branchFirstEdge) ?? [];
471
+ events.push({
472
+ type: "branch",
473
+ block,
474
+ branchIndex: bi,
475
+ label: block.branches[bi].label
476
+ });
477
+ beforeEdge.set(branchFirstEdge, events);
478
+ }
479
+ }
480
+ const lastBranch = block.branches[block.branches.length - 1];
481
+ const lastEdge = lastBranch?.edgeIds[lastBranch.edgeIds.length - 1];
482
+ if (lastEdge && edgeIdSet.has(lastEdge)) {
483
+ const events = afterEdge.get(lastEdge) ?? [];
484
+ events.push({
485
+ type: "close",
486
+ block
487
+ });
488
+ afterEdge.set(lastEdge, events);
489
+ }
490
+ } else if (block.type === "par") {
491
+ const firstEdge = block.branches[0]?.edgeIds[0];
492
+ if (firstEdge && edgeIdSet.has(firstEdge)) {
493
+ const events = beforeEdge.get(firstEdge) ?? [];
494
+ events.push({
495
+ type: "open",
496
+ block
497
+ });
498
+ beforeEdge.set(firstEdge, events);
499
+ }
500
+ for (let bi = 1; bi < block.branches.length; bi++) {
501
+ const branchFirstEdge = block.branches[bi]?.edgeIds[0];
502
+ if (branchFirstEdge && edgeIdSet.has(branchFirstEdge)) {
503
+ const events = beforeEdge.get(branchFirstEdge) ?? [];
504
+ events.push({
505
+ type: "branch",
506
+ block,
507
+ branchIndex: bi,
508
+ label: block.branches[bi].label
509
+ });
510
+ beforeEdge.set(branchFirstEdge, events);
511
+ }
512
+ }
513
+ const lastBranch = block.branches[block.branches.length - 1];
514
+ const lastEdge = lastBranch?.edgeIds[lastBranch.edgeIds.length - 1];
515
+ if (lastEdge && edgeIdSet.has(lastEdge)) {
516
+ const events = afterEdge.get(lastEdge) ?? [];
517
+ events.push({
518
+ type: "close",
519
+ block
520
+ });
521
+ afterEdge.set(lastEdge, events);
522
+ }
523
+ } else if (block.type === "critical") {
524
+ const firstEdge = block.edgeIds[0];
525
+ if (firstEdge && edgeIdSet.has(firstEdge)) {
526
+ const events = beforeEdge.get(firstEdge) ?? [];
527
+ events.push({
528
+ type: "open",
529
+ block
530
+ });
531
+ beforeEdge.set(firstEdge, events);
532
+ }
533
+ if (block.options) for (const opt of block.options) {
534
+ const optFirstEdge = opt.edgeIds[0];
535
+ if (optFirstEdge && edgeIdSet.has(optFirstEdge)) {
536
+ const events = beforeEdge.get(optFirstEdge) ?? [];
537
+ events.push({
538
+ type: "branch",
539
+ block,
540
+ label: opt.label
541
+ });
542
+ beforeEdge.set(optFirstEdge, events);
543
+ }
544
+ }
545
+ const allEdgeIds = [...block.edgeIds, ...block.options?.flatMap((o) => o.edgeIds) ?? []];
546
+ const lastEdge = allEdgeIds[allEdgeIds.length - 1];
547
+ if (lastEdge && edgeIdSet.has(lastEdge)) {
548
+ const events = afterEdge.get(lastEdge) ?? [];
549
+ events.push({
550
+ type: "close",
551
+ block
552
+ });
553
+ afterEdge.set(lastEdge, events);
554
+ }
555
+ } else {
556
+ const edgeIds = block.edgeIds;
557
+ const firstEdge = edgeIds[0];
558
+ const lastEdge = edgeIds[edgeIds.length - 1];
559
+ if (firstEdge && edgeIdSet.has(firstEdge)) {
560
+ const events = beforeEdge.get(firstEdge) ?? [];
561
+ events.push({
562
+ type: "open",
563
+ block
564
+ });
565
+ beforeEdge.set(firstEdge, events);
566
+ }
567
+ if (lastEdge && edgeIdSet.has(lastEdge)) {
568
+ const events = afterEdge.get(lastEdge) ?? [];
569
+ events.push({
570
+ type: "close",
571
+ block
572
+ });
573
+ afterEdge.set(lastEdge, events);
574
+ }
575
+ }
576
+ let depth = 1;
577
+ const indent = () => " ".repeat(depth);
578
+ for (const edge of graph.edges) {
579
+ const befores = beforeEdge.get(edge.id);
580
+ if (befores) {
581
+ for (const ev of befores) if (ev.type === "open") {
582
+ const b = ev.block;
583
+ if (b.type === "alt") lines.push(`${indent()}alt ${b.label}`);
584
+ else if (b.type === "par") lines.push(`${indent()}par ${b.branches[0].label}`);
585
+ else if (b.type === "critical") lines.push(`${indent()}critical ${b.label}`);
586
+ else if (b.type === "rect") lines.push(`${indent()}rect ${b.color}`);
587
+ else if (b.type === "loop" || b.type === "opt" || b.type === "break") lines.push(`${indent()}${b.type} ${b.label}`);
588
+ depth++;
589
+ } else if (ev.type === "branch") {
590
+ depth--;
591
+ const b = ev.block;
592
+ if (b.type === "alt") lines.push(`${indent()}else${ev.label ? ` ${ev.label}` : ""}`);
593
+ else if (b.type === "par") lines.push(`${indent()}and ${ev.label ?? ""}`);
594
+ else if (b.type === "critical") lines.push(`${indent()}option ${ev.label ?? ""}`);
595
+ depth++;
596
+ }
597
+ }
598
+ const d = edge.data;
599
+ if (!d) continue;
600
+ if (d.kind === "activation") lines.push(`${indent()}activate ${edge.sourceId}`);
601
+ else if (d.kind === "deactivation") lines.push(`${indent()}deactivate ${edge.sourceId}`);
602
+ else {
603
+ const stroke = d.stroke ?? "solid";
604
+ const arrowType = d.arrowType ?? "filled";
605
+ let arrow;
606
+ if (d.bidirectional) arrow = stroke === "dotted" ? "<<-->>" : "<<->>";
607
+ else arrow = ARROW_MAP[stroke]?.[arrowType] ?? "->>";
608
+ const label = edge.label ? `: ${escapeMermaidLabel(edge.label)}` : ":";
609
+ lines.push(`${indent()}${edge.sourceId}${arrow}${edge.targetId}${label}`);
610
+ }
611
+ const afters = afterEdge.get(edge.id);
612
+ if (afters) for (const _ev of afters) {
613
+ depth--;
614
+ lines.push(`${indent()}end`);
615
+ }
616
+ }
617
+ return lines.join("\n");
618
+ }
619
+ /**
620
+ * Bidirectional converter for Mermaid sequence diagram format.
621
+ *
622
+ * @example
623
+ * const graph = mermaidSequenceConverter.from(`
624
+ * sequenceDiagram
625
+ * Alice->>Bob: Hello
626
+ * `);
627
+ * const str = mermaidSequenceConverter.to(graph);
628
+ */
629
+ const mermaidSequenceConverter = createFormatConverter(toMermaidSequence, fromMermaidSequence);
630
+
631
+ //#endregion
632
+ //#region src/formats/mermaid/flowchart.ts
633
+ const SHAPE_OPENERS = [
634
+ [
635
+ "(((",
636
+ ")))",
637
+ "double-circle"
638
+ ],
639
+ [
640
+ "((",
641
+ "))",
642
+ "circle"
643
+ ],
644
+ [
645
+ "([",
646
+ "])",
647
+ "stadium"
648
+ ],
649
+ [
650
+ "[(",
651
+ ")]",
652
+ "cylinder"
653
+ ],
654
+ [
655
+ "[[",
656
+ "]]",
657
+ "subroutine"
658
+ ],
659
+ [
660
+ "{{",
661
+ "}}",
662
+ "hexagon"
663
+ ],
664
+ [
665
+ "[/",
666
+ "/]",
667
+ "parallelogram"
668
+ ],
669
+ [
670
+ "[\\",
671
+ "\\]",
672
+ "parallelogram-alt"
673
+ ],
674
+ [
675
+ "[/",
676
+ "\\]",
677
+ "trapezoid"
678
+ ],
679
+ [
680
+ "[\\",
681
+ "/]",
682
+ "trapezoid-alt"
683
+ ],
684
+ [
685
+ "(",
686
+ ")",
687
+ "rounded"
688
+ ],
689
+ [
690
+ "{",
691
+ "}",
692
+ "diamond"
693
+ ],
694
+ [
695
+ ">",
696
+ "]",
697
+ "asymmetric"
698
+ ],
699
+ [
700
+ "[",
701
+ "]",
702
+ "rectangle"
703
+ ]
704
+ ];
705
+ const SHAPE_TO_BRACKETS$1 = {};
706
+ for (const [opener, closer, name] of SHAPE_OPENERS) SHAPE_TO_BRACKETS$1[name] = [opener, closer];
707
+ function parseNodeDecl(text) {
708
+ for (const [opener, closer, shapeName] of SHAPE_OPENERS) {
709
+ const opIdx = text.indexOf(opener);
710
+ if (opIdx < 0) continue;
711
+ const id = text.slice(0, opIdx);
712
+ if (!id) continue;
713
+ if (!text.endsWith(closer)) continue;
714
+ return {
715
+ id,
716
+ label: text.slice(opIdx + opener.length, text.length - closer.length).trim(),
717
+ shape: shapeName
718
+ };
719
+ }
720
+ if (/^[a-zA-Z_][\w]*$/.test(text)) return {
721
+ id: text,
722
+ label: "",
723
+ shape: "rectangle"
724
+ };
725
+ return null;
726
+ }
727
+ const EDGE_ARROWS = [
728
+ [/^<===>$/, {
729
+ stroke: "thick",
730
+ arrowType: "arrow",
731
+ endMarker: "arrow",
732
+ startMarker: "arrow",
733
+ bidirectional: true
734
+ }],
735
+ [/^<==>$/, {
736
+ stroke: "thick",
737
+ arrowType: "arrow",
738
+ endMarker: "arrow",
739
+ startMarker: "arrow",
740
+ bidirectional: true
741
+ }],
742
+ [/^<-.->$/, {
743
+ stroke: "dotted",
744
+ arrowType: "arrow",
745
+ endMarker: "arrow",
746
+ startMarker: "arrow",
747
+ bidirectional: true
748
+ }],
749
+ [/^<-->$/, {
750
+ stroke: "normal",
751
+ arrowType: "arrow",
752
+ endMarker: "arrow",
753
+ startMarker: "arrow",
754
+ bidirectional: true
755
+ }],
756
+ [/^===>$/, {
757
+ stroke: "thick",
758
+ arrowType: "arrow",
759
+ endMarker: "arrow",
760
+ bidirectional: false
761
+ }],
762
+ [/^==>$/, {
763
+ stroke: "thick",
764
+ arrowType: "arrow",
765
+ endMarker: "arrow",
766
+ bidirectional: false
767
+ }],
768
+ [/^===$/, {
769
+ stroke: "thick",
770
+ arrowType: "none",
771
+ endMarker: "arrow",
772
+ bidirectional: false
773
+ }],
774
+ [/^-\.->$/, {
775
+ stroke: "dotted",
776
+ arrowType: "arrow",
777
+ endMarker: "arrow",
778
+ bidirectional: false
779
+ }],
780
+ [/^\.-\.>$/, {
781
+ stroke: "dotted",
782
+ arrowType: "arrow",
783
+ endMarker: "arrow",
784
+ bidirectional: false
785
+ }],
786
+ [/^-\.-$/, {
787
+ stroke: "dotted",
788
+ arrowType: "none",
789
+ endMarker: "arrow",
790
+ bidirectional: false
791
+ }],
792
+ [/^---o$/, {
793
+ stroke: "normal",
794
+ arrowType: "arrow",
795
+ endMarker: "circle",
796
+ bidirectional: false
797
+ }],
798
+ [/^--o$/, {
799
+ stroke: "normal",
800
+ arrowType: "arrow",
801
+ endMarker: "circle",
802
+ bidirectional: false
803
+ }],
804
+ [/^---x$/, {
805
+ stroke: "normal",
806
+ arrowType: "arrow",
807
+ endMarker: "cross",
808
+ bidirectional: false
809
+ }],
810
+ [/^--x$/, {
811
+ stroke: "normal",
812
+ arrowType: "arrow",
813
+ endMarker: "cross",
814
+ bidirectional: false
815
+ }],
816
+ [/^--->$/, {
817
+ stroke: "normal",
818
+ arrowType: "arrow",
819
+ endMarker: "arrow",
820
+ bidirectional: false
821
+ }],
822
+ [/^-->$/, {
823
+ stroke: "normal",
824
+ arrowType: "arrow",
825
+ endMarker: "arrow",
826
+ bidirectional: false
827
+ }],
828
+ [/^---$/, {
829
+ stroke: "normal",
830
+ arrowType: "none",
831
+ endMarker: "arrow",
832
+ bidirectional: false
833
+ }],
834
+ [/^--$/, {
835
+ stroke: "normal",
836
+ arrowType: "none",
837
+ endMarker: "arrow",
838
+ bidirectional: false
839
+ }]
840
+ ];
841
+ function findEdge(line) {
842
+ const pipeMatch = line.match(/^(.+?)\s*([-=.<>ox]+)\|([^|]*)\|\s*(.+)$/);
843
+ if (pipeMatch) {
844
+ const arrowStr = pipeMatch[2];
845
+ for (const [re, info] of EDGE_ARROWS) if (re.test(arrowStr)) return {
846
+ sourceText: pipeMatch[1].trim(),
847
+ targetText: pipeMatch[4].trim(),
848
+ info: {
849
+ ...info,
850
+ label: pipeMatch[3].trim()
851
+ },
852
+ rest: ""
853
+ };
854
+ }
855
+ const inlineMatch = line.match(/^(.+?)\s*(--)\s+([^-][^>]*?)\s*(-->)\s*(.+)$/);
856
+ if (inlineMatch) return {
857
+ sourceText: inlineMatch[1].trim(),
858
+ targetText: inlineMatch[5].trim(),
859
+ info: {
860
+ stroke: "normal",
861
+ arrowType: "arrow",
862
+ endMarker: "arrow",
863
+ bidirectional: false,
864
+ label: inlineMatch[3].trim()
865
+ },
866
+ rest: ""
867
+ };
868
+ for (const [re, info] of EDGE_ARROWS) {
869
+ const arrowSource = re.source.replace(/^\^/, "").replace(/\$$/, "");
870
+ const fullRe = /* @__PURE__ */ new RegExp(`^(.+?)\\s*(${arrowSource})\\s*(.+)$`);
871
+ const m = line.match(fullRe);
872
+ if (m) return {
873
+ sourceText: m[1].trim(),
874
+ targetText: m[3].trim(),
875
+ info: {
876
+ ...info,
877
+ label: ""
878
+ },
879
+ rest: ""
880
+ };
881
+ }
882
+ return null;
883
+ }
884
+ /**
885
+ * Parses a Mermaid flowchart string into a Graph.
886
+ *
887
+ * @example
888
+ * const graph = fromMermaidFlowchart(`
889
+ * flowchart TD
890
+ * A[Start] --> B{Decision}
891
+ * B -->|Yes| C[End]
892
+ * `);
893
+ */
894
+ function fromMermaidFlowchart(input) {
895
+ validateInput(input, "Mermaid flowchart");
896
+ const { lines } = prepareLines(input);
897
+ const headerMatch = (lines[0]?.trim())?.match(/^(graph|flowchart)\s+(TD|TB|BT|LR|RL)\s*$/);
898
+ if (!headerMatch) throw new Error("Mermaid flowchart: expected \"graph <direction>\" or \"flowchart <direction>\" header");
899
+ const direction = MERMAID_TO_DIRECTION[headerMatch[2]] ?? "down";
900
+ const nodeMap = /* @__PURE__ */ new Map();
901
+ const edges = [];
902
+ const classDefs = {};
903
+ const classAssignments = {};
904
+ let edgeCounter = 0;
905
+ const parentStack = [null];
906
+ function ensureNode(id, label, shape) {
907
+ if (!nodeMap.has(id)) nodeMap.set(id, {
908
+ type: "node",
909
+ id,
910
+ parentId: parentStack[parentStack.length - 1],
911
+ initialNodeId: null,
912
+ label: label ?? "",
913
+ data: {},
914
+ ...shape && shape !== "rectangle" && { shape }
915
+ });
916
+ const node = nodeMap.get(id);
917
+ if (label && !node.label) node.label = label;
918
+ if (shape && shape !== "rectangle" && !node.shape) node.shape = shape;
919
+ return node;
920
+ }
921
+ for (let i = 1; i < lines.length; i++) {
922
+ const line = lines[i].trim();
923
+ if (!line) continue;
924
+ const subgraphMatch = line.match(/^subgraph\s+(\S+?)(?:\s*\[(.+)\])?\s*$/);
925
+ if (subgraphMatch) {
926
+ const subId = subgraphMatch[1];
927
+ ensureNode(subId, subgraphMatch[2]?.trim() ?? subId);
928
+ parentStack.push(subId);
929
+ continue;
930
+ }
931
+ if (/^direction\s+(TD|TB|BT|LR|RL)\s*$/.test(line)) continue;
932
+ if (line === "end") {
933
+ if (parentStack.length > 1) parentStack.pop();
934
+ continue;
935
+ }
936
+ const classDefMatch = line.match(/^classDef\s+(\S+)\s+(.+)$/);
937
+ if (classDefMatch) {
938
+ const className = classDefMatch[1];
939
+ const propsStr = classDefMatch[2];
940
+ const props = {};
941
+ for (const pair of propsStr.split(",")) {
942
+ const [k, v] = pair.split(":").map((s) => s.trim());
943
+ if (k && v) props[k] = v;
944
+ }
945
+ classDefs[className] = props;
946
+ continue;
947
+ }
948
+ const classAssignMatch = line.match(/^class\s+(.+)\s+(\S+)\s*$/);
949
+ if (classAssignMatch) {
950
+ const nodeIds = classAssignMatch[1].split(",").map((s) => s.trim());
951
+ const className = classAssignMatch[2];
952
+ for (const nid of nodeIds) {
953
+ if (!classAssignments[nid]) classAssignments[nid] = [];
954
+ classAssignments[nid].push(className);
955
+ }
956
+ continue;
957
+ }
958
+ const styleMatch = line.match(/^style\s+(\S+)\s+(.+)$/);
959
+ if (styleMatch) {
960
+ const nodeId = styleMatch[1];
961
+ const propsStr = styleMatch[2];
962
+ ensureNode(nodeId);
963
+ const node = nodeMap.get(nodeId);
964
+ const style = {};
965
+ for (const pair of propsStr.split(",")) {
966
+ const [k, v] = pair.split(":").map((s) => s.trim());
967
+ if (k && v) {
968
+ if (k === "fill") node.color = v;
969
+ style[k] = v;
970
+ }
971
+ }
972
+ if (Object.keys(style).length > 0) node.style = {
973
+ ...node.style ?? {},
974
+ ...style
975
+ };
976
+ continue;
977
+ }
978
+ if (line.startsWith("linkStyle ")) continue;
979
+ const clickMatch = line.match(/^click\s+(\S+)\s+"([^"]*)"(?:\s+"([^"]*)")?\s*$/);
980
+ if (clickMatch) {
981
+ const nodeId = clickMatch[1];
982
+ ensureNode(nodeId);
983
+ const node = nodeMap.get(nodeId);
984
+ node.data.link = clickMatch[2];
985
+ if (clickMatch[3]) node.data.tooltip = clickMatch[3];
986
+ continue;
987
+ }
988
+ let remaining = line;
989
+ let foundEdge = false;
990
+ while (remaining) {
991
+ const edgeResult = findEdge(remaining);
992
+ if (!edgeResult) break;
993
+ foundEdge = true;
994
+ const sourceDecl = parseNodeDecl(edgeResult.sourceText);
995
+ if (sourceDecl) ensureNode(sourceDecl.id, sourceDecl.label, sourceDecl.shape);
996
+ const targetDecl = parseNodeDecl(edgeResult.targetText);
997
+ let targetId;
998
+ if (targetDecl) {
999
+ ensureNode(targetDecl.id, targetDecl.label, targetDecl.shape);
1000
+ targetId = targetDecl.id;
1001
+ remaining = "";
1002
+ } else {
1003
+ const nextEdge = findEdge(edgeResult.targetText);
1004
+ if (nextEdge) {
1005
+ const tDecl = parseNodeDecl(nextEdge.sourceText);
1006
+ if (tDecl) {
1007
+ ensureNode(tDecl.id, tDecl.label, tDecl.shape);
1008
+ targetId = tDecl.id;
1009
+ } else {
1010
+ targetId = nextEdge.sourceText;
1011
+ ensureNode(targetId);
1012
+ }
1013
+ remaining = edgeResult.targetText;
1014
+ } else {
1015
+ targetId = edgeResult.targetText;
1016
+ ensureNode(targetId);
1017
+ remaining = "";
1018
+ }
1019
+ }
1020
+ const sourceId = sourceDecl ? sourceDecl.id : edgeResult.sourceText;
1021
+ ensureNode(sourceId);
1022
+ const edgeId = generateEdgeId(sourceId, targetId, edgeCounter++);
1023
+ const info = edgeResult.info;
1024
+ edges.push({
1025
+ type: "edge",
1026
+ id: edgeId,
1027
+ sourceId,
1028
+ targetId,
1029
+ label: info.label ? unescapeMermaidLabel(info.label) : "",
1030
+ data: {
1031
+ stroke: info.stroke,
1032
+ arrowType: info.arrowType,
1033
+ ...info.endMarker !== "arrow" && { endMarker: info.endMarker },
1034
+ ...info.startMarker && { startMarker: info.startMarker },
1035
+ ...info.bidirectional && { bidirectional: true }
1036
+ }
1037
+ });
1038
+ }
1039
+ if (foundEdge) continue;
1040
+ const nodeDecl = parseNodeDecl(line);
1041
+ if (nodeDecl) {
1042
+ ensureNode(nodeDecl.id, nodeDecl.label, nodeDecl.shape);
1043
+ continue;
1044
+ }
1045
+ if (line.includes(";")) {
1046
+ const stmts = line.split(";").map((s) => s.trim()).filter(Boolean);
1047
+ for (const stmt of stmts) {
1048
+ const nd = parseNodeDecl(stmt);
1049
+ if (nd) ensureNode(nd.id, nd.label, nd.shape);
1050
+ }
1051
+ }
1052
+ }
1053
+ for (const [nodeId, classes] of Object.entries(classAssignments)) {
1054
+ const node = nodeMap.get(nodeId);
1055
+ if (node) {
1056
+ node.data.classes = classes;
1057
+ for (const cls of classes) {
1058
+ const def = classDefs[cls];
1059
+ if (def) {
1060
+ if (def.fill) node.color = def.fill;
1061
+ const style = {};
1062
+ for (const [k, v] of Object.entries(def)) style[k] = v;
1063
+ if (Object.keys(style).length > 0) node.style = {
1064
+ ...node.style ?? {},
1065
+ ...style
1066
+ };
1067
+ }
1068
+ }
1069
+ }
1070
+ }
1071
+ return {
1072
+ id: "",
1073
+ type: "directed",
1074
+ initialNodeId: null,
1075
+ nodes: Array.from(nodeMap.values()),
1076
+ edges,
1077
+ data: {
1078
+ diagramType: "flowchart",
1079
+ ...Object.keys(classDefs).length > 0 && { classDefs }
1080
+ },
1081
+ direction
1082
+ };
1083
+ }
1084
+ /**
1085
+ * Converts a flowchart Graph to a Mermaid flowchart string.
1086
+ *
1087
+ * @example
1088
+ * const mermaid = toMermaidFlowchart(graph);
1089
+ * // "flowchart TD\n A[Start] --> B{Decision}\n ..."
1090
+ */
1091
+ function toMermaidFlowchart(graph) {
1092
+ const lines = [`flowchart ${DIRECTION_TO_MERMAID[graph.direction ?? "down"] ?? "TD"}`];
1093
+ const gd = graph.data;
1094
+ if (gd?.classDefs) for (const [name, props] of Object.entries(gd.classDefs)) {
1095
+ const propsStr = Object.entries(props).map(([k, v]) => `${k}:${v}`).join(",");
1096
+ lines.push(` classDef ${name} ${propsStr}`);
1097
+ }
1098
+ const childrenMap = /* @__PURE__ */ new Map();
1099
+ for (const node of graph.nodes) {
1100
+ const pid = node.parentId;
1101
+ if (!childrenMap.has(pid)) childrenMap.set(pid, []);
1102
+ childrenMap.get(pid).push(node);
1103
+ }
1104
+ const isParent = /* @__PURE__ */ new Set();
1105
+ for (const node of graph.nodes) if (childrenMap.has(node.id)) isParent.add(node.id);
1106
+ function writeNodes(parentId, indent) {
1107
+ const children = childrenMap.get(parentId) ?? [];
1108
+ for (const node of children) if (isParent.has(node.id)) {
1109
+ const label = node.label ? `[${escapeMermaidLabel(node.label)}]` : "";
1110
+ lines.push(`${indent}subgraph ${node.id}${label}`);
1111
+ writeNodes(node.id, indent + " ");
1112
+ lines.push(`${indent}end`);
1113
+ } else {
1114
+ const brackets = SHAPE_TO_BRACKETS$1[node.shape ?? "rectangle"] ?? ["[", "]"];
1115
+ const label = node.label ? `${brackets[0]}${escapeMermaidLabel(node.label)}${brackets[1]}` : "";
1116
+ lines.push(`${indent}${node.id}${label}`);
1117
+ }
1118
+ }
1119
+ writeNodes(null, " ");
1120
+ for (const edge of graph.edges) {
1121
+ const d = edge.data;
1122
+ let arrow;
1123
+ if (d?.bidirectional) arrow = d.stroke === "thick" ? "<==>" : d.stroke === "dotted" ? "<-.->" : "<-->";
1124
+ else if (d?.stroke === "thick") arrow = d.arrowType === "none" ? "===" : "==>";
1125
+ else if (d?.stroke === "dotted") arrow = d.arrowType === "none" ? "-.-" : "-.->";
1126
+ else if (d?.endMarker === "circle") arrow = "--o";
1127
+ else if (d?.endMarker === "cross") arrow = "--x";
1128
+ else arrow = d?.arrowType === "none" ? "---" : "-->";
1129
+ let labelStr = "";
1130
+ if (edge.label) labelStr = `|${escapeMermaidLabel(edge.label)}|`;
1131
+ lines.push(` ${edge.sourceId} ${arrow}${labelStr} ${edge.targetId}`);
1132
+ }
1133
+ const classAssignments = /* @__PURE__ */ new Map();
1134
+ for (const node of graph.nodes) if (node.data?.classes) for (const cls of node.data.classes) {
1135
+ if (!classAssignments.has(cls)) classAssignments.set(cls, []);
1136
+ classAssignments.get(cls).push(node.id);
1137
+ }
1138
+ for (const [cls, nodeIds] of classAssignments) lines.push(` class ${nodeIds.join(",")} ${cls}`);
1139
+ for (const node of graph.nodes) if (node.data?.link) {
1140
+ const tip = node.data.tooltip ? ` "${escapeMermaidLabel(node.data.tooltip)}"` : "";
1141
+ lines.push(` click ${node.id} "${escapeMermaidLabel(node.data.link)}"${tip}`);
1142
+ }
1143
+ return lines.join("\n");
1144
+ }
1145
+ /**
1146
+ * Bidirectional converter for Mermaid flowchart format.
1147
+ *
1148
+ * @example
1149
+ * const graph = mermaidFlowchartConverter.from(`
1150
+ * flowchart TD
1151
+ * A --> B
1152
+ * `);
1153
+ * const str = mermaidFlowchartConverter.to(graph);
1154
+ */
1155
+ const mermaidFlowchartConverter = createFormatConverter(toMermaidFlowchart, fromMermaidFlowchart);
1156
+
1157
+ //#endregion
1158
+ //#region src/formats/mermaid/state.ts
1159
+ /**
1160
+ * Parses a Mermaid state diagram string into a Graph.
1161
+ *
1162
+ * @example
1163
+ * const graph = fromMermaidState(`
1164
+ * stateDiagram-v2
1165
+ * [*] --> Idle
1166
+ * Idle --> Running : start
1167
+ * Running --> [*]
1168
+ * `);
1169
+ */
1170
+ function fromMermaidState(input) {
1171
+ validateInput(input, "Mermaid state");
1172
+ const { lines } = prepareLines(input);
1173
+ const header = lines[0]?.trim();
1174
+ if (!header || !header.startsWith("stateDiagram-v2") && !header.startsWith("stateDiagram")) throw new Error("Mermaid state: expected \"stateDiagram\" or \"stateDiagram-v2\" header");
1175
+ const nodeMap = /* @__PURE__ */ new Map();
1176
+ const edges = [];
1177
+ let edgeCounter = 0;
1178
+ let startCounter = 0;
1179
+ let endCounter = 0;
1180
+ const parentStack = [null];
1181
+ function ensureNode(id) {
1182
+ if (!nodeMap.has(id)) nodeMap.set(id, {
1183
+ type: "node",
1184
+ id,
1185
+ parentId: parentStack[parentStack.length - 1],
1186
+ initialNodeId: null,
1187
+ label: id,
1188
+ data: {}
1189
+ });
1190
+ return nodeMap.get(id);
1191
+ }
1192
+ function resolveStarNode(position) {
1193
+ if (position === "source") {
1194
+ const id = `[*]_start_${startCounter++}`;
1195
+ const node = ensureNode(id);
1196
+ node.label = "[*]";
1197
+ node.data.isStart = true;
1198
+ node.shape = "start";
1199
+ return id;
1200
+ } else {
1201
+ const id = `[*]_end_${endCounter++}`;
1202
+ const node = ensureNode(id);
1203
+ node.label = "[*]";
1204
+ node.data.isEnd = true;
1205
+ node.shape = "end";
1206
+ return id;
1207
+ }
1208
+ }
1209
+ for (let i = 1; i < lines.length; i++) {
1210
+ const line = lines[i].trim();
1211
+ if (!line) continue;
1212
+ if (/^direction\s+(TD|TB|BT|LR|RL)\s*$/.test(line)) continue;
1213
+ const compositeMatch = line.match(/^state\s+(\S+)\s*\{?\s*$/);
1214
+ if (compositeMatch && line.includes("{")) {
1215
+ const stateId = compositeMatch[1];
1216
+ ensureNode(stateId);
1217
+ parentStack.push(stateId);
1218
+ continue;
1219
+ }
1220
+ const stereotypeMatch = line.match(/^state\s+(\S+)\s+<<(choice|fork|join)>>\s*$/);
1221
+ if (stereotypeMatch) {
1222
+ const stateId = stereotypeMatch[1];
1223
+ const stateType = stereotypeMatch[2];
1224
+ const node = ensureNode(stateId);
1225
+ node.data.stateType = stateType;
1226
+ node.shape = stateType;
1227
+ continue;
1228
+ }
1229
+ const stateAsMatch = line.match(/^state\s+"([^"]+)"\s+as\s+(\S+)\s*$/);
1230
+ if (stateAsMatch) {
1231
+ const description = stateAsMatch[1];
1232
+ const stateId = stateAsMatch[2];
1233
+ const node = ensureNode(stateId);
1234
+ node.data.description = description;
1235
+ continue;
1236
+ }
1237
+ if (line === "}" || line === "end") {
1238
+ if (parentStack.length > 1) parentStack.pop();
1239
+ continue;
1240
+ }
1241
+ const noteMatch = line.match(/^note\s+(left|right)\s+of\s+(\S+)\s*:\s*(.+)$/i);
1242
+ if (noteMatch) {
1243
+ const position = noteMatch[1].toLowerCase();
1244
+ const stateId = noteMatch[2];
1245
+ const text = noteMatch[3].trim();
1246
+ const node = ensureNode(stateId);
1247
+ if (!node.data.notes) node.data.notes = [];
1248
+ node.data.notes.push({
1249
+ position,
1250
+ text
1251
+ });
1252
+ continue;
1253
+ }
1254
+ const transMatch = line.match(/^(\S+)\s*-->\s*(\S+)\s*(?::\s*(.+))?$/);
1255
+ if (transMatch) {
1256
+ let sourceId = transMatch[1];
1257
+ let targetId = transMatch[2];
1258
+ const label = transMatch[3]?.trim() ?? "";
1259
+ if (sourceId === "[*]") sourceId = resolveStarNode("source");
1260
+ else ensureNode(sourceId);
1261
+ if (targetId === "[*]") targetId = resolveStarNode("target");
1262
+ else ensureNode(targetId);
1263
+ const edgeId = generateEdgeId(sourceId, targetId, edgeCounter++);
1264
+ edges.push({
1265
+ type: "edge",
1266
+ id: edgeId,
1267
+ sourceId,
1268
+ targetId,
1269
+ label: label ? unescapeMermaidLabel(label) : "",
1270
+ data: {}
1271
+ });
1272
+ continue;
1273
+ }
1274
+ const descMatch = line.match(/^(\S+)\s*:\s*(.+)$/);
1275
+ if (descMatch) {
1276
+ const stateId = descMatch[1];
1277
+ const description = descMatch[2].trim();
1278
+ const node = ensureNode(stateId);
1279
+ node.data.description = description;
1280
+ continue;
1281
+ }
1282
+ if (/^[a-zA-Z_][\w]*$/.test(line)) {
1283
+ ensureNode(line);
1284
+ continue;
1285
+ }
1286
+ }
1287
+ return {
1288
+ id: "",
1289
+ type: "directed",
1290
+ initialNodeId: null,
1291
+ nodes: Array.from(nodeMap.values()),
1292
+ edges,
1293
+ data: { diagramType: "stateDiagram" }
1294
+ };
1295
+ }
1296
+ /**
1297
+ * Converts a state diagram Graph to a Mermaid state diagram string.
1298
+ *
1299
+ * @example
1300
+ * const mermaid = toMermaidState(graph);
1301
+ * // "stateDiagram-v2\n [*] --> Idle\n ..."
1302
+ */
1303
+ function toMermaidState(graph) {
1304
+ const lines = ["stateDiagram-v2"];
1305
+ const childrenMap = /* @__PURE__ */ new Map();
1306
+ for (const node of graph.nodes) {
1307
+ const pid = node.parentId;
1308
+ if (!childrenMap.has(pid)) childrenMap.set(pid, []);
1309
+ childrenMap.get(pid).push(node);
1310
+ }
1311
+ const isParent = /* @__PURE__ */ new Set();
1312
+ for (const node of graph.nodes) if (childrenMap.has(node.id)) isParent.add(node.id);
1313
+ function writeNodes(parentId, indent) {
1314
+ const children = childrenMap.get(parentId) ?? [];
1315
+ for (const node of children) {
1316
+ if (node.data?.isStart || node.data?.isEnd) continue;
1317
+ if (node.data?.stateType) lines.push(`${indent}state ${node.id} <<${node.data.stateType}>>`);
1318
+ if (node.data?.description) lines.push(`${indent}state "${escapeMermaidLabel(node.data.description)}" as ${node.id}`);
1319
+ if (isParent.has(node.id)) {
1320
+ lines.push(`${indent}state ${node.id} {`);
1321
+ writeNodes(node.id, indent + " ");
1322
+ lines.push(`${indent}}`);
1323
+ }
1324
+ if (node.data?.notes) for (const note of node.data.notes) lines.push(`${indent}note ${note.position} of ${node.id} : ${escapeMermaidLabel(note.text)}`);
1325
+ }
1326
+ }
1327
+ writeNodes(null, " ");
1328
+ for (const edge of graph.edges) {
1329
+ let sourceId = edge.sourceId;
1330
+ let targetId = edge.targetId;
1331
+ const sourceNode = graph.nodes.find((n) => n.id === sourceId);
1332
+ const targetNode = graph.nodes.find((n) => n.id === targetId);
1333
+ if (sourceNode?.data?.isStart) sourceId = "[*]";
1334
+ if (targetNode?.data?.isEnd) targetId = "[*]";
1335
+ const label = edge.label ? ` : ${escapeMermaidLabel(edge.label)}` : "";
1336
+ lines.push(` ${sourceId} --> ${targetId}${label}`);
1337
+ }
1338
+ return lines.join("\n");
1339
+ }
1340
+ /**
1341
+ * Bidirectional converter for Mermaid state diagram format.
1342
+ *
1343
+ * @example
1344
+ * const graph = mermaidStateConverter.from(`
1345
+ * stateDiagram-v2
1346
+ * [*] --> Active
1347
+ * `);
1348
+ * const str = mermaidStateConverter.to(graph);
1349
+ */
1350
+ const mermaidStateConverter = createFormatConverter(toMermaidState, fromMermaidState);
1351
+
1352
+ //#endregion
1353
+ //#region src/formats/mermaid/class-diagram.ts
1354
+ const RELATIONSHIP_ARROWS = [
1355
+ [
1356
+ "<|--",
1357
+ "inheritance",
1358
+ true
1359
+ ],
1360
+ [
1361
+ "--|>",
1362
+ "inheritance",
1363
+ false
1364
+ ],
1365
+ [
1366
+ "<|..",
1367
+ "realization",
1368
+ true
1369
+ ],
1370
+ [
1371
+ "..|>",
1372
+ "realization",
1373
+ false
1374
+ ],
1375
+ [
1376
+ "*--",
1377
+ "composition",
1378
+ true
1379
+ ],
1380
+ [
1381
+ "--*",
1382
+ "composition",
1383
+ false
1384
+ ],
1385
+ [
1386
+ "o--",
1387
+ "aggregation",
1388
+ true
1389
+ ],
1390
+ [
1391
+ "--o",
1392
+ "aggregation",
1393
+ false
1394
+ ],
1395
+ [
1396
+ "<--",
1397
+ "association",
1398
+ true
1399
+ ],
1400
+ [
1401
+ "-->",
1402
+ "association",
1403
+ false
1404
+ ],
1405
+ [
1406
+ "<..",
1407
+ "dependency",
1408
+ true
1409
+ ],
1410
+ [
1411
+ "..>",
1412
+ "dependency",
1413
+ false
1414
+ ],
1415
+ [
1416
+ "--",
1417
+ "link",
1418
+ false
1419
+ ],
1420
+ [
1421
+ "..",
1422
+ "dashed",
1423
+ false
1424
+ ]
1425
+ ];
1426
+ function parseRelationship(line) {
1427
+ for (const [arrow, relationType, reversed] of RELATIONSHIP_ARROWS) {
1428
+ const idx = line.indexOf(arrow);
1429
+ if (idx < 0) continue;
1430
+ let left = line.slice(0, idx).trim();
1431
+ let right = line.slice(idx + arrow.length).trim();
1432
+ let label = "";
1433
+ const colonIdx = right.indexOf(":");
1434
+ if (colonIdx >= 0) {
1435
+ label = right.slice(colonIdx + 1).trim();
1436
+ right = right.slice(0, colonIdx).trim();
1437
+ }
1438
+ let leftCard;
1439
+ let rightCard;
1440
+ const leftCardMatch = left.match(/^"([^"]+)"\s+(.+)$/);
1441
+ if (leftCardMatch) {
1442
+ leftCard = leftCardMatch[1];
1443
+ left = leftCardMatch[2].trim();
1444
+ } else {
1445
+ const leftCardTrail = left.match(/^(.+?)\s+"([^"]+)"$/);
1446
+ if (leftCardTrail) {
1447
+ leftCard = leftCardTrail[2];
1448
+ left = leftCardTrail[1].trim();
1449
+ }
1450
+ }
1451
+ const rightCardMatch = right.match(/^"([^"]+)"\s+(.+)$/);
1452
+ if (rightCardMatch) {
1453
+ rightCard = rightCardMatch[1];
1454
+ right = rightCardMatch[2].trim();
1455
+ } else {
1456
+ const rightCardTrail = right.match(/^(.+?)\s+"([^"]+)"$/);
1457
+ if (rightCardTrail) {
1458
+ rightCard = rightCardTrail[2];
1459
+ right = rightCardTrail[1].trim();
1460
+ }
1461
+ }
1462
+ if (!left || !right) continue;
1463
+ if (reversed) return {
1464
+ leftClass: right,
1465
+ rightClass: left,
1466
+ relationType,
1467
+ label,
1468
+ leftCard: rightCard,
1469
+ rightCard: leftCard
1470
+ };
1471
+ return {
1472
+ leftClass: left,
1473
+ rightClass: right,
1474
+ relationType,
1475
+ label,
1476
+ leftCard,
1477
+ rightCard
1478
+ };
1479
+ }
1480
+ return null;
1481
+ }
1482
+ function parseMember(line) {
1483
+ const trimmed = line.trim();
1484
+ let visibility = "+";
1485
+ let rest = trimmed;
1486
+ if (/^[+\-#~]/.test(rest)) {
1487
+ visibility = rest[0];
1488
+ rest = rest.slice(1).trim();
1489
+ }
1490
+ const isMethod = rest.includes("(");
1491
+ let name = rest;
1492
+ let type;
1493
+ if (isMethod) {
1494
+ const methodMatch = rest.match(/^(.+\))\s*(.+)?$/);
1495
+ if (methodMatch) {
1496
+ name = methodMatch[1];
1497
+ type = methodMatch[2];
1498
+ }
1499
+ } else {
1500
+ const fieldMatch = rest.match(/^(\S+)\s+(\S+)$/);
1501
+ if (fieldMatch) {
1502
+ type = fieldMatch[1];
1503
+ name = fieldMatch[2];
1504
+ }
1505
+ }
1506
+ return {
1507
+ visibility,
1508
+ name,
1509
+ type,
1510
+ isMethod
1511
+ };
1512
+ }
1513
+ /**
1514
+ * Parses a Mermaid class diagram string into a Graph.
1515
+ *
1516
+ * @example
1517
+ * const graph = fromMermaidClass(`
1518
+ * classDiagram
1519
+ * class Animal {
1520
+ * +String name
1521
+ * +eat() void
1522
+ * }
1523
+ * Animal <|-- Dog
1524
+ * `);
1525
+ */
1526
+ function fromMermaidClass(input) {
1527
+ validateInput(input, "Mermaid class");
1528
+ const { lines } = prepareLines(input);
1529
+ const header = lines[0]?.trim();
1530
+ if (!header || !header.startsWith("classDiagram")) throw new Error("Mermaid class: expected \"classDiagram\" header");
1531
+ const nodeMap = /* @__PURE__ */ new Map();
1532
+ const edges = [];
1533
+ let edgeCounter = 0;
1534
+ function ensureNode(id) {
1535
+ if (!nodeMap.has(id)) nodeMap.set(id, {
1536
+ type: "node",
1537
+ id,
1538
+ parentId: null,
1539
+ initialNodeId: null,
1540
+ label: id,
1541
+ data: {}
1542
+ });
1543
+ return nodeMap.get(id);
1544
+ }
1545
+ let currentClass = null;
1546
+ let inClassBlock = false;
1547
+ let braceDepth = 0;
1548
+ for (let i = 1; i < lines.length; i++) {
1549
+ const line = lines[i].trim();
1550
+ if (!line) continue;
1551
+ const classBlockMatch = line.match(/^class\s+(\S+?)(?:~(.+?)~)?\s*\{\s*$/);
1552
+ if (classBlockMatch) {
1553
+ currentClass = classBlockMatch[1];
1554
+ const node = ensureNode(currentClass);
1555
+ if (classBlockMatch[2]) node.data.genericType = classBlockMatch[2];
1556
+ inClassBlock = true;
1557
+ braceDepth = 1;
1558
+ continue;
1559
+ }
1560
+ if (inClassBlock && currentClass) {
1561
+ if (line === "}") {
1562
+ braceDepth--;
1563
+ if (braceDepth <= 0) {
1564
+ inClassBlock = false;
1565
+ currentClass = null;
1566
+ }
1567
+ continue;
1568
+ }
1569
+ const annotMatch = line.match(/^<<(.+)>>$/);
1570
+ if (annotMatch) {
1571
+ nodeMap.get(currentClass).data.annotation = annotMatch[1];
1572
+ continue;
1573
+ }
1574
+ const node = nodeMap.get(currentClass);
1575
+ if (!node.data.members) node.data.members = [];
1576
+ node.data.members.push(parseMember(line));
1577
+ continue;
1578
+ }
1579
+ const classInlineMatch = line.match(/^class\s+(\S+?)(?:~(.+?)~)?\s*$/);
1580
+ if (classInlineMatch) {
1581
+ const node = ensureNode(classInlineMatch[1]);
1582
+ if (classInlineMatch[2]) node.data.genericType = classInlineMatch[2];
1583
+ continue;
1584
+ }
1585
+ const annotLineMatch = line.match(/^<<(.+)>>\s+(\S+)\s*$/);
1586
+ if (annotLineMatch) {
1587
+ const node = ensureNode(annotLineMatch[2]);
1588
+ node.data.annotation = annotLineMatch[1];
1589
+ continue;
1590
+ }
1591
+ const inlineMemberMatch = line.match(/^(\S+)\s*:\s*(.+)$/);
1592
+ if (inlineMemberMatch) {
1593
+ if (!parseRelationship(line)) {
1594
+ const node = ensureNode(inlineMemberMatch[1]);
1595
+ if (!node.data.members) node.data.members = [];
1596
+ node.data.members.push(parseMember(inlineMemberMatch[2]));
1597
+ continue;
1598
+ }
1599
+ }
1600
+ const rel = parseRelationship(line);
1601
+ if (rel) {
1602
+ ensureNode(rel.leftClass);
1603
+ ensureNode(rel.rightClass);
1604
+ const edgeId = generateEdgeId(rel.leftClass, rel.rightClass, edgeCounter++);
1605
+ edges.push({
1606
+ type: "edge",
1607
+ id: edgeId,
1608
+ sourceId: rel.leftClass,
1609
+ targetId: rel.rightClass,
1610
+ label: rel.label ? unescapeMermaidLabel(rel.label) : "",
1611
+ data: {
1612
+ relationType: rel.relationType,
1613
+ ...rel.leftCard && { sourceCardinality: rel.leftCard },
1614
+ ...rel.rightCard && { targetCardinality: rel.rightCard }
1615
+ }
1616
+ });
1617
+ continue;
1618
+ }
1619
+ }
1620
+ return {
1621
+ id: "",
1622
+ type: "directed",
1623
+ initialNodeId: null,
1624
+ nodes: Array.from(nodeMap.values()),
1625
+ edges,
1626
+ data: { diagramType: "classDiagram" }
1627
+ };
1628
+ }
1629
+ const RELATION_TO_ARROW = {
1630
+ inheritance: "--|>",
1631
+ composition: "--*",
1632
+ aggregation: "--o",
1633
+ association: "-->",
1634
+ dependency: "..>",
1635
+ realization: "..|>",
1636
+ link: "--",
1637
+ dashed: ".."
1638
+ };
1639
+ const VISIBILITY_SYMBOLS = {
1640
+ "+": "+",
1641
+ "-": "-",
1642
+ "#": "#",
1643
+ "~": "~"
1644
+ };
1645
+ /**
1646
+ * Converts a class diagram Graph to a Mermaid class diagram string.
1647
+ *
1648
+ * @example
1649
+ * const mermaid = toMermaidClass(graph);
1650
+ * // "classDiagram\n class Animal {\n ..."
1651
+ */
1652
+ function toMermaidClass(graph) {
1653
+ const lines = ["classDiagram"];
1654
+ for (const node of graph.nodes) if (node.data?.members && node.data.members.length > 0) {
1655
+ const generic = node.data.genericType ? `~${node.data.genericType}~` : "";
1656
+ lines.push(` class ${node.id}${generic} {`);
1657
+ if (node.data.annotation) lines.push(` <<${node.data.annotation}>>`);
1658
+ for (const m of node.data.members) {
1659
+ const vis = VISIBILITY_SYMBOLS[m.visibility] ?? "+";
1660
+ const typeStr = m.type ? m.isMethod ? ` ${m.type}` : `${m.type} ` : "";
1661
+ if (m.isMethod) lines.push(` ${vis}${m.name}${typeStr}`);
1662
+ else lines.push(` ${vis}${typeStr}${m.name}`);
1663
+ }
1664
+ lines.push(` }`);
1665
+ } else {
1666
+ const generic = node.data?.genericType ? `~${node.data.genericType}~` : "";
1667
+ lines.push(` class ${node.id}${generic}`);
1668
+ if (node.data?.annotation) lines.push(` <<${node.data.annotation}>> ${node.id}`);
1669
+ }
1670
+ for (const edge of graph.edges) {
1671
+ const arrow = RELATION_TO_ARROW[edge.data?.relationType ?? "association"] ?? "-->";
1672
+ const srcCard = edge.data?.sourceCardinality ? `"${edge.data.sourceCardinality}" ` : "";
1673
+ const tgtCard = edge.data?.targetCardinality ? ` "${edge.data.targetCardinality}"` : "";
1674
+ const label = edge.label ? ` : ${escapeMermaidLabel(edge.label)}` : "";
1675
+ lines.push(` ${srcCard}${edge.sourceId} ${arrow} ${edge.targetId}${tgtCard}${label}`);
1676
+ }
1677
+ return lines.join("\n");
1678
+ }
1679
+ /**
1680
+ * Bidirectional converter for Mermaid class diagram format.
1681
+ *
1682
+ * @example
1683
+ * const graph = mermaidClassConverter.from(`
1684
+ * classDiagram
1685
+ * Animal <|-- Dog
1686
+ * `);
1687
+ * const str = mermaidClassConverter.to(graph);
1688
+ */
1689
+ const mermaidClassConverter = createFormatConverter(toMermaidClass, fromMermaidClass);
1690
+
1691
+ //#endregion
1692
+ //#region src/formats/mermaid/er-diagram.ts
1693
+ const LEFT_CARDINALITY = {
1694
+ "||": "one",
1695
+ "|o": "zero-or-one",
1696
+ "}|": "one-or-more",
1697
+ "}o": "zero-or-more"
1698
+ };
1699
+ const RIGHT_CARDINALITY = {
1700
+ "||": "one",
1701
+ "o|": "zero-or-one",
1702
+ "|{": "one-or-more",
1703
+ "o{": "zero-or-more"
1704
+ };
1705
+ const CARDINALITY_TO_LEFT = {
1706
+ one: "||",
1707
+ "zero-or-one": "|o",
1708
+ "one-or-more": "}|",
1709
+ "zero-or-more": "}o"
1710
+ };
1711
+ const CARDINALITY_TO_RIGHT = {
1712
+ one: "||",
1713
+ "zero-or-one": "o|",
1714
+ "one-or-more": "|{",
1715
+ "zero-or-more": "o{"
1716
+ };
1717
+ function parseERRelationship(symbol) {
1718
+ if (symbol.length < 6) return null;
1719
+ const left = symbol.slice(0, 2);
1720
+ const mid = symbol.slice(2, 4);
1721
+ const right = symbol.slice(4, 6);
1722
+ const srcCard = LEFT_CARDINALITY[left];
1723
+ const tgtCard = RIGHT_CARDINALITY[right];
1724
+ if (!srcCard || !tgtCard) return null;
1725
+ let identifying;
1726
+ if (mid === "--") identifying = true;
1727
+ else if (mid === "..") identifying = false;
1728
+ else return null;
1729
+ return {
1730
+ sourceCardinality: srcCard,
1731
+ targetCardinality: tgtCard,
1732
+ identifying
1733
+ };
1734
+ }
1735
+ const ER_LINE_RE = /^(\S+)\s+([|}{o.][|}{o.][-.][-.][|}{o.][|}{o.])\s+(\S+)\s*:\s*"?([^"]*)"?\s*$/;
1736
+ /**
1737
+ * Parses a Mermaid ER diagram string into a Graph.
1738
+ *
1739
+ * @example
1740
+ * const graph = fromMermaidER(`
1741
+ * erDiagram
1742
+ * CUSTOMER ||--o{ ORDER : places
1743
+ * ORDER ||--|{ LINE_ITEM : contains
1744
+ * `);
1745
+ */
1746
+ function fromMermaidER(input) {
1747
+ validateInput(input, "Mermaid ER");
1748
+ const { lines } = prepareLines(input);
1749
+ const header = lines[0]?.trim();
1750
+ if (!header || !header.startsWith("erDiagram")) throw new Error("Mermaid ER: expected \"erDiagram\" header");
1751
+ const nodeMap = /* @__PURE__ */ new Map();
1752
+ const edges = [];
1753
+ let edgeCounter = 0;
1754
+ function ensureNode(id) {
1755
+ if (!nodeMap.has(id)) nodeMap.set(id, {
1756
+ type: "node",
1757
+ id,
1758
+ parentId: null,
1759
+ initialNodeId: null,
1760
+ label: id,
1761
+ data: {}
1762
+ });
1763
+ return nodeMap.get(id);
1764
+ }
1765
+ let currentEntity = null;
1766
+ let inBlock = false;
1767
+ for (let i = 1; i < lines.length; i++) {
1768
+ const line = lines[i].trim();
1769
+ if (!line) continue;
1770
+ const blockMatch = line.match(/^(\S+)\s*\{\s*$/);
1771
+ if (blockMatch) {
1772
+ currentEntity = blockMatch[1];
1773
+ ensureNode(currentEntity);
1774
+ inBlock = true;
1775
+ continue;
1776
+ }
1777
+ if (inBlock && currentEntity) {
1778
+ if (line === "}") {
1779
+ inBlock = false;
1780
+ currentEntity = null;
1781
+ continue;
1782
+ }
1783
+ const attrMatch = line.match(/^(\S+)\s+(\S+)(?:\s+(PK|FK|UK))?(?:\s+"([^"]*)")?$/);
1784
+ if (attrMatch) {
1785
+ const node = nodeMap.get(currentEntity);
1786
+ if (!node.data.attributes) node.data.attributes = [];
1787
+ node.data.attributes.push({
1788
+ type: attrMatch[1],
1789
+ name: attrMatch[2],
1790
+ ...attrMatch[3] && { key: attrMatch[3] },
1791
+ ...attrMatch[4] && { comment: attrMatch[4] }
1792
+ });
1793
+ }
1794
+ continue;
1795
+ }
1796
+ const relMatch = line.match(ER_LINE_RE);
1797
+ if (relMatch) {
1798
+ const leftEntity = relMatch[1];
1799
+ const symbol = relMatch[2];
1800
+ const rightEntity = relMatch[3];
1801
+ const label = relMatch[4].trim();
1802
+ ensureNode(leftEntity);
1803
+ ensureNode(rightEntity);
1804
+ const rel = parseERRelationship(symbol);
1805
+ if (rel) {
1806
+ const edgeId = generateEdgeId(leftEntity, rightEntity, edgeCounter++);
1807
+ edges.push({
1808
+ type: "edge",
1809
+ id: edgeId,
1810
+ sourceId: leftEntity,
1811
+ targetId: rightEntity,
1812
+ label: label ? unescapeMermaidLabel(label) : "",
1813
+ data: rel
1814
+ });
1815
+ }
1816
+ continue;
1817
+ }
1818
+ if (/^[A-Z_][\w]*$/i.test(line)) ensureNode(line);
1819
+ }
1820
+ return {
1821
+ id: "",
1822
+ type: "directed",
1823
+ initialNodeId: null,
1824
+ nodes: Array.from(nodeMap.values()),
1825
+ edges,
1826
+ data: { diagramType: "erDiagram" }
1827
+ };
1828
+ }
1829
+ /**
1830
+ * Converts an ER diagram Graph to a Mermaid ER diagram string.
1831
+ *
1832
+ * @example
1833
+ * const mermaid = toMermaidER(graph);
1834
+ * // "erDiagram\n CUSTOMER ||--o{ ORDER : \"places\"\n ..."
1835
+ */
1836
+ function toMermaidER(graph) {
1837
+ const lines = ["erDiagram"];
1838
+ for (const node of graph.nodes) if (node.data?.attributes && node.data.attributes.length > 0) {
1839
+ lines.push(` ${node.id} {`);
1840
+ for (const attr of node.data.attributes) {
1841
+ const keyStr = attr.key ? ` ${attr.key}` : "";
1842
+ const commentStr = attr.comment ? ` "${escapeMermaidLabel(attr.comment)}"` : "";
1843
+ lines.push(` ${attr.type} ${attr.name}${keyStr}${commentStr}`);
1844
+ }
1845
+ lines.push(` }`);
1846
+ }
1847
+ for (const edge of graph.edges) {
1848
+ const d = edge.data;
1849
+ if (!d) continue;
1850
+ const leftMark = CARDINALITY_TO_LEFT[d.sourceCardinality] ?? "||";
1851
+ const rightMark = CARDINALITY_TO_RIGHT[d.targetCardinality] ?? "||";
1852
+ const symbol = `${leftMark}${d.identifying ? "--" : ".."}${rightMark}`;
1853
+ const label = edge.label ? `"${escapeMermaidLabel(edge.label)}"` : "\"\"";
1854
+ lines.push(` ${edge.sourceId} ${symbol} ${edge.targetId} : ${label}`);
1855
+ }
1856
+ return lines.join("\n");
1857
+ }
1858
+ /**
1859
+ * Bidirectional converter for Mermaid ER diagram format.
1860
+ *
1861
+ * @example
1862
+ * const graph = mermaidERConverter.from(`
1863
+ * erDiagram
1864
+ * CUSTOMER ||--o{ ORDER : places
1865
+ * `);
1866
+ * const str = mermaidERConverter.to(graph);
1867
+ */
1868
+ const mermaidERConverter = createFormatConverter(toMermaidER, fromMermaidER);
1869
+
1870
+ //#endregion
1871
+ //#region src/formats/mermaid/mindmap.ts
1872
+ const SHAPE_PATTERNS = [
1873
+ [
1874
+ "(((",
1875
+ ")))",
1876
+ "double-circle"
1877
+ ],
1878
+ [
1879
+ "((",
1880
+ "))",
1881
+ "circle"
1882
+ ],
1883
+ [
1884
+ "([",
1885
+ "])",
1886
+ "stadium"
1887
+ ],
1888
+ [
1889
+ "{{",
1890
+ "}}",
1891
+ "hexagon"
1892
+ ],
1893
+ [
1894
+ "(",
1895
+ ")",
1896
+ "rounded"
1897
+ ],
1898
+ [
1899
+ "[",
1900
+ "]",
1901
+ "rectangle"
1902
+ ]
1903
+ ];
1904
+ const SHAPE_TO_BRACKETS = {};
1905
+ for (const [opener, closer, name] of SHAPE_PATTERNS) SHAPE_TO_BRACKETS[name] = [opener, closer];
1906
+ function parseNodeText(text) {
1907
+ for (const [opener, closer, shapeName] of SHAPE_PATTERNS) if (text.startsWith(opener) && text.endsWith(closer)) return {
1908
+ label: text.slice(opener.length, text.length - closer.length).trim(),
1909
+ shape: shapeName
1910
+ };
1911
+ return { label: text.trim() };
1912
+ }
1913
+ /**
1914
+ * Parses a Mermaid mindmap string into a Graph.
1915
+ *
1916
+ * @example
1917
+ * const graph = fromMermaidMindmap(`
1918
+ * mindmap
1919
+ * Root
1920
+ * Child A
1921
+ * Grandchild
1922
+ * Child B
1923
+ * `);
1924
+ */
1925
+ function fromMermaidMindmap(input) {
1926
+ validateInput(input, "Mermaid mindmap");
1927
+ const { lines } = prepareLines(input);
1928
+ const header = lines[0]?.trim();
1929
+ if (!header || !header.startsWith("mindmap")) throw new Error("Mermaid mindmap: expected \"mindmap\" header");
1930
+ const nodeMap = /* @__PURE__ */ new Map();
1931
+ const edges = [];
1932
+ let nodeCounter = 0;
1933
+ let edgeCounter = 0;
1934
+ const stack = [];
1935
+ for (let i = 1; i < lines.length; i++) {
1936
+ const rawLine = lines[i];
1937
+ if (!rawLine.trim()) continue;
1938
+ const indent = rawLine.length - rawLine.trimStart().length;
1939
+ const content = rawLine.trim();
1940
+ let icon;
1941
+ let cleanContent = content;
1942
+ const iconMatch = content.match(/::icon\(([^)]+)\)/);
1943
+ if (iconMatch) {
1944
+ icon = iconMatch[1];
1945
+ cleanContent = content.replace(/::icon\([^)]+\)/, "").trim();
1946
+ }
1947
+ const { label, shape } = parseNodeText(cleanContent);
1948
+ const id = `mm_${nodeCounter++}`;
1949
+ const node = {
1950
+ type: "node",
1951
+ id,
1952
+ parentId: null,
1953
+ initialNodeId: null,
1954
+ label,
1955
+ data: { ...icon && { icon } },
1956
+ ...shape && { shape }
1957
+ };
1958
+ while (stack.length > 0 && stack[stack.length - 1].indent >= indent) stack.pop();
1959
+ if (stack.length > 0) {
1960
+ const parent = stack[stack.length - 1];
1961
+ node.parentId = parent.id;
1962
+ const edgeId = generateEdgeId(parent.id, id, edgeCounter++);
1963
+ edges.push({
1964
+ type: "edge",
1965
+ id: edgeId,
1966
+ sourceId: parent.id,
1967
+ targetId: id,
1968
+ label: "",
1969
+ data: {}
1970
+ });
1971
+ }
1972
+ nodeMap.set(id, node);
1973
+ stack.push({
1974
+ id,
1975
+ indent
1976
+ });
1977
+ }
1978
+ return {
1979
+ id: "",
1980
+ type: "directed",
1981
+ initialNodeId: null,
1982
+ nodes: Array.from(nodeMap.values()),
1983
+ edges,
1984
+ data: { diagramType: "mindmap" }
1985
+ };
1986
+ }
1987
+ /**
1988
+ * Converts a mindmap Graph to a Mermaid mindmap string.
1989
+ *
1990
+ * @example
1991
+ * const mermaid = toMermaidMindmap(graph);
1992
+ * // "mindmap\n Root\n Child A\n ..."
1993
+ */
1994
+ function toMermaidMindmap(graph) {
1995
+ const lines = ["mindmap"];
1996
+ const childrenMap = /* @__PURE__ */ new Map();
1997
+ for (const node of graph.nodes) {
1998
+ const pid = node.parentId;
1999
+ if (!childrenMap.has(pid)) childrenMap.set(pid, []);
2000
+ childrenMap.get(pid).push(node);
2001
+ }
2002
+ function writeNode(nodeId, depth) {
2003
+ const children = childrenMap.get(nodeId) ?? [];
2004
+ for (const child of children) {
2005
+ const indent = " ".repeat(depth + 1);
2006
+ const shape = child.shape;
2007
+ const brackets = shape ? SHAPE_TO_BRACKETS[shape] : void 0;
2008
+ const label = escapeMermaidLabel(child.label);
2009
+ const text = brackets ? `${brackets[0]}${label}${brackets[1]}` : label;
2010
+ let extra = "";
2011
+ if (child.data?.icon) extra = `\n${" ".repeat(depth + 2)}::icon(${child.data.icon})`;
2012
+ lines.push(`${indent}${text}${extra}`);
2013
+ writeNode(child.id, depth + 1);
2014
+ }
2015
+ }
2016
+ writeNode(null, 0);
2017
+ return lines.join("\n");
2018
+ }
2019
+ /**
2020
+ * Bidirectional converter for Mermaid mindmap format.
2021
+ *
2022
+ * @example
2023
+ * const graph = mermaidMindmapConverter.from(`
2024
+ * mindmap
2025
+ * Root
2026
+ * Branch
2027
+ * `);
2028
+ * const str = mermaidMindmapConverter.to(graph);
2029
+ */
2030
+ const mermaidMindmapConverter = createFormatConverter(toMermaidMindmap, fromMermaidMindmap);
2031
+
2032
+ //#endregion
2033
+ //#region src/formats/mermaid/block.ts
2034
+ const BLOCK_SHAPES = [
2035
+ [
2036
+ "((",
2037
+ "))",
2038
+ "circle"
2039
+ ],
2040
+ [
2041
+ "(",
2042
+ ")",
2043
+ "rounded"
2044
+ ],
2045
+ [
2046
+ "{{",
2047
+ "}}",
2048
+ "hexagon"
2049
+ ],
2050
+ [
2051
+ "{",
2052
+ "}",
2053
+ "diamond"
2054
+ ],
2055
+ [
2056
+ "[[",
2057
+ "]]",
2058
+ "subroutine"
2059
+ ],
2060
+ [
2061
+ "[",
2062
+ "]",
2063
+ "rectangle"
2064
+ ]
2065
+ ];
2066
+ function parseBlockNode(text) {
2067
+ for (const [opener, closer, shapeName] of BLOCK_SHAPES) {
2068
+ const idx = text.indexOf(opener);
2069
+ if (idx < 0) continue;
2070
+ const id = text.slice(0, idx).trim();
2071
+ if (!id) continue;
2072
+ if (!text.endsWith(closer)) continue;
2073
+ return {
2074
+ id,
2075
+ label: text.slice(idx + opener.length, text.length - closer.length).trim(),
2076
+ shape: shapeName
2077
+ };
2078
+ }
2079
+ if (/^[a-zA-Z_][\w]*$/.test(text.trim())) return {
2080
+ id: text.trim(),
2081
+ label: "",
2082
+ shape: void 0
2083
+ };
2084
+ return null;
2085
+ }
2086
+ /**
2087
+ * Parses a Mermaid block diagram string into a Graph.
2088
+ *
2089
+ * @example
2090
+ * const graph = fromMermaidBlock(`
2091
+ * block-beta
2092
+ * columns 2
2093
+ * a["Task A"] b["Task B"]
2094
+ * a --> b
2095
+ * `);
2096
+ */
2097
+ function fromMermaidBlock(input) {
2098
+ validateInput(input, "Mermaid block");
2099
+ const { lines } = prepareLines(input);
2100
+ const header = lines[0]?.trim();
2101
+ if (!header || !header.startsWith("block-beta")) throw new Error("Mermaid block: expected \"block-beta\" header");
2102
+ const nodeMap = /* @__PURE__ */ new Map();
2103
+ const edges = [];
2104
+ let edgeCounter = 0;
2105
+ let columns;
2106
+ const parentStack = [null];
2107
+ let blockCounter = 0;
2108
+ function ensureNode(id, label, shape) {
2109
+ if (!nodeMap.has(id)) nodeMap.set(id, {
2110
+ type: "node",
2111
+ id,
2112
+ parentId: parentStack[parentStack.length - 1],
2113
+ initialNodeId: null,
2114
+ label: label ?? id,
2115
+ data: {},
2116
+ ...shape && { shape }
2117
+ });
2118
+ return nodeMap.get(id);
2119
+ }
2120
+ for (let i = 1; i < lines.length; i++) {
2121
+ const line = lines[i].trim();
2122
+ if (!line) continue;
2123
+ const colsMatch = line.match(/^columns\s+(\d+)\s*$/);
2124
+ if (colsMatch) {
2125
+ columns = parseInt(colsMatch[1], 10);
2126
+ continue;
2127
+ }
2128
+ if (line === "block" || line.startsWith("block:")) {
2129
+ const blockId = line.includes(":") ? line.split(":")[1].trim() : `__block_${blockCounter++}`;
2130
+ ensureNode(blockId);
2131
+ parentStack.push(blockId);
2132
+ continue;
2133
+ }
2134
+ if (line === "end") {
2135
+ if (parentStack.length > 1) parentStack.pop();
2136
+ continue;
2137
+ }
2138
+ const edgeMatch = line.match(/^(\S+)\s*(-->|---|==>|-\.->)\s*(\S+)\s*$/);
2139
+ if (edgeMatch) {
2140
+ const sourceId = edgeMatch[1];
2141
+ const targetId = edgeMatch[3];
2142
+ ensureNode(sourceId);
2143
+ ensureNode(targetId);
2144
+ const edgeId = generateEdgeId(sourceId, targetId, edgeCounter++);
2145
+ edges.push({
2146
+ type: "edge",
2147
+ id: edgeId,
2148
+ sourceId,
2149
+ targetId,
2150
+ label: "",
2151
+ data: {}
2152
+ });
2153
+ continue;
2154
+ }
2155
+ const spanMatch = line.match(/^(\S+?)(?:\["([^"]*)"\])?:(\d+)\s*$/);
2156
+ if (spanMatch) {
2157
+ const id = spanMatch[1];
2158
+ const label = spanMatch[2] ?? id;
2159
+ const span = parseInt(spanMatch[3], 10);
2160
+ const node = ensureNode(id, label);
2161
+ node.data.span = span;
2162
+ continue;
2163
+ }
2164
+ const parts = line.split(/\s+/);
2165
+ let consumed = false;
2166
+ for (const part of parts) {
2167
+ const parsed = parseBlockNode(part);
2168
+ if (parsed) {
2169
+ ensureNode(parsed.id, parsed.label, parsed.shape);
2170
+ consumed = true;
2171
+ }
2172
+ }
2173
+ if (consumed) continue;
2174
+ const nodeDecl = parseBlockNode(line);
2175
+ if (nodeDecl) ensureNode(nodeDecl.id, nodeDecl.label, nodeDecl.shape);
2176
+ }
2177
+ return {
2178
+ id: "",
2179
+ type: "directed",
2180
+ initialNodeId: null,
2181
+ nodes: Array.from(nodeMap.values()),
2182
+ edges,
2183
+ data: {
2184
+ diagramType: "block",
2185
+ ...columns !== void 0 && { columns }
2186
+ }
2187
+ };
2188
+ }
2189
+ /**
2190
+ * Converts a block diagram Graph to a Mermaid block diagram string.
2191
+ *
2192
+ * @example
2193
+ * const mermaid = toMermaidBlock(graph);
2194
+ * // "block-beta\n columns 2\n a[\"Task A\"]\n ..."
2195
+ */
2196
+ function toMermaidBlock(graph) {
2197
+ const lines = ["block-beta"];
2198
+ if (graph.data?.columns) lines.push(` columns ${graph.data.columns}`);
2199
+ const childrenMap = /* @__PURE__ */ new Map();
2200
+ for (const node of graph.nodes) {
2201
+ const pid = node.parentId;
2202
+ if (!childrenMap.has(pid)) childrenMap.set(pid, []);
2203
+ childrenMap.get(pid).push(node);
2204
+ }
2205
+ const isParent = /* @__PURE__ */ new Set();
2206
+ for (const node of graph.nodes) if (childrenMap.has(node.id)) isParent.add(node.id);
2207
+ function writeNodes(parentId, indent) {
2208
+ const children = childrenMap.get(parentId) ?? [];
2209
+ for (const child of children) if (isParent.has(child.id)) {
2210
+ lines.push(`${indent}block:${child.id}`);
2211
+ writeNodes(child.id, indent + " ");
2212
+ lines.push(`${indent}end`);
2213
+ } else {
2214
+ const label = child.label && child.label !== child.id ? `["${escapeMermaidLabel(child.label)}"]` : "";
2215
+ const span = child.data?.span ? `:${child.data.span}` : "";
2216
+ lines.push(`${indent}${child.id}${label}${span}`);
2217
+ }
2218
+ }
2219
+ writeNodes(null, " ");
2220
+ for (const edge of graph.edges) lines.push(` ${edge.sourceId} --> ${edge.targetId}`);
2221
+ return lines.join("\n");
2222
+ }
2223
+ /**
2224
+ * Bidirectional converter for Mermaid block diagram format.
2225
+ *
2226
+ * @example
2227
+ * const graph = mermaidBlockConverter.from(`
2228
+ * block-beta
2229
+ * columns 2
2230
+ * a b
2231
+ * `);
2232
+ * const str = mermaidBlockConverter.to(graph);
2233
+ */
2234
+ const mermaidBlockConverter = createFormatConverter(toMermaidBlock, fromMermaidBlock);
2235
+
2236
+ //#endregion
2237
+ export { fromMermaidBlock, fromMermaidClass, fromMermaidER, fromMermaidFlowchart, fromMermaidMindmap, fromMermaidSequence, fromMermaidState, mermaidBlockConverter, mermaidClassConverter, mermaidERConverter, mermaidFlowchartConverter, mermaidMindmapConverter, mermaidSequenceConverter, mermaidStateConverter, toMermaidBlock, toMermaidClass, toMermaidER, toMermaidFlowchart, toMermaidMindmap, toMermaidSequence, toMermaidState };