@eventcatalog/visualiser 5.0.0 → 5.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.
@@ -0,0 +1,1196 @@
1
+ // src/nodes/node-types.ts
2
+ var SERVICE = ["service", "services"];
3
+ var AGENT = ["agent", "agents"];
4
+ var AGENT_TOOL = ["agentTool", "agent-tool"];
5
+ var EVENT = ["event", "events"];
6
+ var QUERY = ["queries", "query", "querie"];
7
+ var COMMAND = ["command", "commands"];
8
+ var CHANNEL = ["channel", "channels"];
9
+ var ACTOR = ["actor", "actors"];
10
+ var DATA = ["data"];
11
+ var VIEW = ["view"];
12
+ var MESSAGE = [...EVENT, ...COMMAND, ...QUERY];
13
+
14
+ // src/utils/utils/utils.ts
15
+ import { MarkerType, Position } from "@xyflow/react";
16
+ var generateIdForNode = (node) => {
17
+ return `${node.data.id}-${node.data.version}`;
18
+ };
19
+ var generateIdForNodes = (nodes) => {
20
+ return nodes.map((node) => `${node.data.id}-${node.data.version}`).join("-");
21
+ };
22
+ var generatedIdForEdge = (source, target) => {
23
+ return `${source.data.id}-${source.data.version}-${target.data.id}-${target.data.version}`;
24
+ };
25
+ var getColorFromString = (id) => {
26
+ let hash = 0;
27
+ for (let i = 0; i < id.length; i++) {
28
+ hash = id.charCodeAt(i) + ((hash << 5) - hash);
29
+ }
30
+ let color = "#";
31
+ for (let i = 0; i < 3; i++) {
32
+ const value = hash >> i * 8 & 255;
33
+ color += value.toString(16).padStart(2, "0");
34
+ }
35
+ return color;
36
+ };
37
+ var getEdgeLabelForServiceAsTarget = (data) => {
38
+ const type = data.collection;
39
+ switch (type) {
40
+ case "commands":
41
+ return "invokes";
42
+ case "events":
43
+ return "publishes \nevent";
44
+ case "queries":
45
+ return "requests";
46
+ default:
47
+ return "sends to";
48
+ }
49
+ };
50
+ var getEdgeLabelForMessageAsSource = (data, throughChannel = false) => {
51
+ const type = data.collection;
52
+ switch (type) {
53
+ case "commands":
54
+ return "accepts";
55
+ case "events":
56
+ return throughChannel ? "subscribed to" : "subscribed by";
57
+ case "queries":
58
+ return "accepts";
59
+ default:
60
+ return "sends to";
61
+ }
62
+ };
63
+ var getEdgeLabelSize = (label) => {
64
+ if (typeof label !== "string" || !label.trim())
65
+ return { width: 0, height: 0 };
66
+ const lines = label.split("\n");
67
+ const longestLine = Math.max(...lines.map((line) => line.trim().length));
68
+ return { width: longestLine * 6 + 20, height: lines.length * 14 + 12 };
69
+ };
70
+ var createEdge = (edgeOptions) => {
71
+ return {
72
+ label: "subscribed by",
73
+ animated: false,
74
+ // markerStart: {
75
+ // type: MarkerType.Arrow,
76
+ // width: 40,
77
+ // height: 40,
78
+ // },
79
+ markerEnd: {
80
+ type: MarkerType.ArrowClosed,
81
+ width: 40,
82
+ height: 40,
83
+ color: "rgb(var(--ec-page-text-muted))"
84
+ },
85
+ style: {
86
+ strokeWidth: 1.5,
87
+ stroke: "rgb(var(--ec-page-text-muted))",
88
+ strokeDasharray: "5 5"
89
+ },
90
+ ...edgeOptions
91
+ };
92
+ };
93
+ var createNode = (values) => {
94
+ return {
95
+ sourcePosition: Position.Right,
96
+ targetPosition: Position.Left,
97
+ ...values
98
+ };
99
+ };
100
+
101
+ // src/utils/elk-layout.ts
102
+ var DEFAULT_SIZE = { width: 240, height: 112 };
103
+ var NODE_SIZES = {
104
+ events: { width: 240, height: 104 },
105
+ commands: { width: 240, height: 126 },
106
+ queries: { width: 240, height: 126 },
107
+ services: { width: 240, height: 112 },
108
+ data: { width: 240, height: 108 },
109
+ systems: { width: 240, height: 120 },
110
+ system: { width: 240, height: 120 },
111
+ "context-domain": { width: 240, height: 112 },
112
+ "context-actor": { width: 180, height: 80 },
113
+ actor: { width: 180, height: 80 },
114
+ note: { width: 200, height: 150 },
115
+ step: { width: 240, height: 80 },
116
+ // Groups with nothing in them (groups with children are sized to fit them)
117
+ "domain-group": { width: 320, height: 160 },
118
+ "system-group": { width: 320, height: 160 }
119
+ };
120
+ var GROUP_TYPES = /* @__PURE__ */ new Set(["domain-group", "system-group", "group"]);
121
+ var getNodeSize = (node) => {
122
+ if (node.measured?.width && node.measured?.height) {
123
+ return { width: node.measured.width, height: node.measured.height };
124
+ }
125
+ if (typeof node.width === "number" && typeof node.height === "number") {
126
+ return { width: node.width, height: node.height };
127
+ }
128
+ if (node.type === "domains" || node.type === "domain") {
129
+ const services = node.data?.domain?.data?.services || [];
130
+ return { width: 260, height: 60 + services.length * 40 };
131
+ }
132
+ return NODE_SIZES[node.type || ""] ?? DEFAULT_SIZE;
133
+ };
134
+ var GROUP_PADDING = "[top=100,left=40,bottom=40,right=40]";
135
+ var SPACING = {
136
+ "elk.layered.spacing.nodeNodeBetweenLayers": "60",
137
+ "elk.spacing.nodeNode": "40",
138
+ "elk.spacing.edgeNode": "25",
139
+ "elk.spacing.edgeEdge": "15"
140
+ };
141
+ var LARGE_GRAPH_NODES = 80;
142
+ var LARGE_GRAPH_EDGES = 200;
143
+ var LARGE_GRAPH_OPTIONS = {
144
+ "elk.layered.nodePlacement.strategy": "BRANDES_KOEPF",
145
+ "elk.layered.thoroughness": "1",
146
+ "elk.layered.crossingMinimization.greedySwitch.type": "OFF"
147
+ };
148
+ var elk;
149
+ var getElk = async () => {
150
+ if (!elk) {
151
+ const { default: ELK } = await import("elkjs/lib/elk.bundled.js");
152
+ elk = new ELK();
153
+ }
154
+ return elk;
155
+ };
156
+ var layoutWithElk = async ({ nodes, edges }, { sizeOf = getNodeSize } = {}) => {
157
+ const nodesById = new Map(nodes.map((node) => [node.id, node]));
158
+ const groupIds = new Set(
159
+ nodes.map((node) => node.parentId).filter((id) => !!id && nodesById.has(id))
160
+ );
161
+ const parentOf = (node) => node.parentId && groupIds.has(node.parentId) ? node.parentId : void 0;
162
+ const layoutEdges = edges.filter(
163
+ (edge) => nodesById.has(edge.source) && nodesById.has(edge.target)
164
+ );
165
+ const ports = /* @__PURE__ */ new Map();
166
+ const addPort = (nodeId, id, side) => {
167
+ const nodePorts = ports.get(nodeId) || [];
168
+ if (!nodePorts.some((port) => port.id === id)) {
169
+ ports.set(nodeId, [
170
+ ...nodePorts,
171
+ { id, layoutOptions: { "elk.port.side": side } }
172
+ ]);
173
+ }
174
+ return id;
175
+ };
176
+ const edgePorts = layoutEdges.map((edge, index) => ({
177
+ source: addPort(
178
+ edge.source,
179
+ edge.sourceHandle ? `${edge.source}__${edge.sourceHandle}` : `e${index}__out`,
180
+ "EAST"
181
+ ),
182
+ target: addPort(
183
+ edge.target,
184
+ edge.targetHandle ? `${edge.target}__${edge.targetHandle}` : `e${index}__in`,
185
+ "WEST"
186
+ )
187
+ }));
188
+ const toElkNode = (node) => ({
189
+ id: node.id,
190
+ ...groupIds.has(node.id) ? {
191
+ children: nodes.filter((child) => parentOf(child) === node.id).map(toElkNode)
192
+ } : sizeOf(node),
193
+ layoutOptions: {
194
+ "elk.portConstraints": "FIXED_SIDE",
195
+ ...groupIds.has(node.id) ? { "elk.padding": GROUP_PADDING, ...SPACING } : {},
196
+ // A node's own options (e.g. a group's padding) win
197
+ ...node.data?.layoutOptions ?? {}
198
+ },
199
+ ports: ports.get(node.id) || []
200
+ });
201
+ const layout = await (await getElk()).layout({
202
+ id: "root",
203
+ layoutOptions: {
204
+ "elk.algorithm": "layered",
205
+ "elk.direction": "RIGHT",
206
+ "elk.hierarchyHandling": "INCLUDE_CHILDREN",
207
+ "elk.edgeRouting": "ORTHOGONAL",
208
+ // Edge routes in absolute coordinates (node positions stay relative to their parent)
209
+ "elk.json.edgeCoords": "ROOT",
210
+ "elk.edgeLabels.placement": "CENTER",
211
+ ...SPACING,
212
+ ...nodes.length >= LARGE_GRAPH_NODES || edges.length >= LARGE_GRAPH_EDGES ? LARGE_GRAPH_OPTIONS : { "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX" }
213
+ },
214
+ children: nodes.filter((node) => !parentOf(node)).map(toElkNode),
215
+ edges: layoutEdges.map((edge, index) => ({
216
+ id: `e${index}`,
217
+ sources: [edgePorts[index].source],
218
+ targets: [edgePorts[index].target],
219
+ // Labels sit on their edge, rather than beside it (only works set per label)
220
+ labels: edge.label ? [
221
+ {
222
+ text: String(edge.label),
223
+ ...getEdgeLabelSize(edge.label),
224
+ layoutOptions: { "elk.edgeLabels.inline": "true" }
225
+ }
226
+ ] : []
227
+ }))
228
+ });
229
+ const laidOut = /* @__PURE__ */ new Map();
230
+ const collect = (elkNode) => (elkNode.children || []).forEach((child) => {
231
+ laidOut.set(child.id, child);
232
+ collect(child);
233
+ });
234
+ collect(layout);
235
+ const absolute = (nodeId) => {
236
+ const node = nodesById.get(nodeId);
237
+ const parent = node && parentOf(node);
238
+ const offset = parent ? absolute(parent) : { x: 0, y: 0 };
239
+ const elkNode = laidOut.get(nodeId);
240
+ return { x: offset.x + (elkNode?.x ?? 0), y: offset.y + (elkNode?.y ?? 0) };
241
+ };
242
+ const laidOutAt = (nodeId) => ({
243
+ ...absolute(nodeId),
244
+ height: laidOut.get(nodeId)?.height
245
+ });
246
+ const routes = /* @__PURE__ */ new Map();
247
+ (layout.edges || []).forEach((elkEdge) => {
248
+ const section = elkEdge.sections?.[0];
249
+ if (!section) return;
250
+ const label = elkEdge.labels?.[0];
251
+ const edge = layoutEdges[Number(elkEdge.id.slice(1))];
252
+ routes.set(edge, {
253
+ points: [
254
+ section.startPoint,
255
+ ...section.bendPoints || [],
256
+ section.endPoint
257
+ ],
258
+ label: label ? {
259
+ x: (label.x ?? 0) + (label.width ?? 0) / 2,
260
+ y: (label.y ?? 0) + (label.height ?? 0) / 2
261
+ } : void 0,
262
+ source: laidOutAt(edge.source),
263
+ target: laidOutAt(edge.target)
264
+ });
265
+ });
266
+ const depth = (node) => {
267
+ const parent = node && parentOf(node);
268
+ return parent ? depth(nodesById.get(parent)) + 1 : 0;
269
+ };
270
+ return {
271
+ // Parents must come before their children in the array for React Flow
272
+ nodes: [...nodes].sort((a, b) => depth(a) - depth(b)).map((node) => {
273
+ const elkNode = laidOut.get(node.id);
274
+ if (!elkNode) return node;
275
+ return {
276
+ ...node,
277
+ origin: [0, 0],
278
+ position: { x: elkNode.x ?? 0, y: elkNode.y ?? 0 },
279
+ ...groupIds.has(node.id) || GROUP_TYPES.has(node.type || "") && typeof node.style?.width !== "number" ? {
280
+ style: {
281
+ ...node.style,
282
+ width: elkNode.width,
283
+ height: elkNode.height
284
+ },
285
+ // Re-measured at its new size
286
+ measured: void 0
287
+ } : {}
288
+ };
289
+ }),
290
+ edges: edges.map((edge) => {
291
+ const route = routes.get(edge);
292
+ return route ? { ...edge, data: { ...edge.data, route } } : edge;
293
+ })
294
+ };
295
+ };
296
+
297
+ // src/utils/swimlanes.ts
298
+ var SWIMLANE_NODE_TYPE = "swimlane";
299
+ var FLOW_GROUP_BYS = [
300
+ "domain",
301
+ "system",
302
+ "team",
303
+ "domain-system"
304
+ ];
305
+ var isFlowGroupBy = (value) => FLOW_GROUP_BYS.includes(value);
306
+ var isFlowGroupStyle = (value) => value === "lanes" || value === "boxes";
307
+ var SWIMLANE_GROUP_BY_LABELS = {
308
+ domain: "Domain",
309
+ system: "System",
310
+ team: "Team",
311
+ "domain-system": "Domain, then system"
312
+ };
313
+ var EXTERNAL_LANE = "__external";
314
+ var UNASSIGNED_LANE = "__unassigned";
315
+ var LANE_HEADER_WIDTH = 96;
316
+ var LANE_PADDING = { x: 48, y: 48 };
317
+ var ROW_GAP = 32;
318
+ var COLUMN_GAP = 24;
319
+ var LANE_LABEL = { fontSize: 20, iconSize: 20, gap: 10 };
320
+ var LANE_LABEL_PADDING = 40;
321
+ var labelLength = (label) => LANE_LABEL.iconSize + LANE_LABEL.gap + label.length * LANE_LABEL.fontSize * 0.62;
322
+ var lanesOf = (node) => node.data?.lanes;
323
+ var isSwimlaneNode = (node) => node.type === SWIMLANE_NODE_TYPE;
324
+ var hasSwimlanes = (nodes, groupBy) => groupBy === "domain-system" ? hasSwimlanes(nodes, "domain") && hasSwimlanes(nodes, "system") : nodes.some((node) => !!lanesOf(node)?.[groupBy]);
325
+ var countGroups = (nodes, groupBy) => {
326
+ const level = groupBy === "domain-system" ? "domain" : groupBy;
327
+ return new Set(
328
+ nodes.map((node) => lanesOf(node)?.[level]?.id).filter(Boolean)
329
+ ).size;
330
+ };
331
+ var assignLanes = (nodes, edges, groupBy) => {
332
+ const laneOf = /* @__PURE__ */ new Map();
333
+ const names = /* @__PURE__ */ new Map();
334
+ nodes.forEach((node) => {
335
+ const lanes = lanesOf(node);
336
+ const lane = lanes?.[groupBy];
337
+ if (lane) {
338
+ laneOf.set(node.id, lane.id);
339
+ names.set(lane.id, lane.name);
340
+ } else if (lanes?.external) {
341
+ laneOf.set(node.id, EXTERNAL_LANE);
342
+ }
343
+ });
344
+ const ids = new Set(nodes.map((node) => node.id));
345
+ const ownLanes = new Set(
346
+ nodes.filter((node) => lanesOf(node)).map((node) => node.id)
347
+ );
348
+ const links = edges.filter(
349
+ (edge) => ids.has(edge.source) && ids.has(edge.target)
350
+ );
351
+ const inherit = (from, to) => {
352
+ let changed = true;
353
+ while (changed) {
354
+ changed = false;
355
+ links.forEach((edge) => {
356
+ const lane = laneOf.get(edge[from]);
357
+ if (lane && !laneOf.has(edge[to]) && !ownLanes.has(edge[to])) {
358
+ laneOf.set(edge[to], lane);
359
+ changed = true;
360
+ }
361
+ });
362
+ }
363
+ };
364
+ inherit("source", "target");
365
+ inherit("target", "source");
366
+ nodes.forEach((node) => {
367
+ if (!laneOf.has(node.id)) laneOf.set(node.id, UNASSIGNED_LANE);
368
+ });
369
+ return { laneOf, names };
370
+ };
371
+ var layoutSwimlanes = ({ nodes, edges }, groupBy, { sizeOf = getNodeSize } = {}) => {
372
+ const topLevel = nodes.filter((node) => !node.parentId);
373
+ if (topLevel.length === 0) return { nodes, edges };
374
+ const { laneOf, names } = assignLanes(topLevel, edges, groupBy);
375
+ const boxes = new Map(
376
+ topLevel.map((node) => [node.id, { ...node.position, ...sizeOf(node) }])
377
+ );
378
+ const order = (lane) => lane === EXTERNAL_LANE ? -Infinity : lane === UNASSIGNED_LANE ? Infinity : Math.min(
379
+ ...topLevel.filter((node) => laneOf.get(node.id) === lane).map((node) => boxes.get(node.id).x)
380
+ );
381
+ const lanes = [...new Set(laneOf.values())].sort(
382
+ (a, b) => order(a) - order(b)
383
+ );
384
+ const minX = Math.min(...[...boxes.values()].map((box) => box.x));
385
+ const maxX = Math.max(...[...boxes.values()].map((box) => box.x + box.width));
386
+ const laneX = minX - LANE_HEADER_WIDTH - LANE_PADDING.x;
387
+ const laneWidth = maxX - laneX + LANE_PADDING.x;
388
+ const laneNodes = [];
389
+ const positions = /* @__PURE__ */ new Map();
390
+ let laneY = 0;
391
+ lanes.forEach((lane, index) => {
392
+ const members = topLevel.filter((node) => laneOf.get(node.id) === lane).sort((a, b) => boxes.get(a.id).y - boxes.get(b.id).y);
393
+ const rows = [];
394
+ const rowOf = /* @__PURE__ */ new Map();
395
+ members.forEach((node) => {
396
+ const box = boxes.get(node.id);
397
+ const overlaps = (other) => box.x < other.x + other.width + COLUMN_GAP && other.x < box.x + box.width + COLUMN_GAP;
398
+ let row = rows.findIndex((r) => !r.boxes.some(overlaps));
399
+ if (row === -1) {
400
+ rows.push({ boxes: [], height: 0 });
401
+ row = rows.length - 1;
402
+ }
403
+ rows[row].boxes.push(box);
404
+ rows[row].height = Math.max(rows[row].height, box.height);
405
+ rowOf.set(node.id, row);
406
+ });
407
+ const label = lane === EXTERNAL_LANE ? "External" : lane === UNASSIGNED_LANE ? `No ${SWIMLANE_GROUP_BY_LABELS[groupBy].toLowerCase()}` : names.get(lane) ?? lane;
408
+ const rowsHeight = rows.reduce((total, row) => total + row.height, 0) + ROW_GAP * (rows.length - 1);
409
+ const laneHeight = Math.max(
410
+ rowsHeight + LANE_PADDING.y * 2,
411
+ labelLength(label) + LANE_LABEL_PADDING * 2
412
+ );
413
+ const rowTops = [];
414
+ let rowY = (laneHeight - rowsHeight) / 2;
415
+ rows.forEach((row) => {
416
+ rowTops.push(rowY);
417
+ rowY += row.height + ROW_GAP;
418
+ });
419
+ const laneId = `swimlane-${lane}`;
420
+ members.forEach((node) => {
421
+ const box = boxes.get(node.id);
422
+ const row = rowOf.get(node.id);
423
+ positions.set(node.id, {
424
+ x: box.x - laneX,
425
+ // Centred in its row
426
+ y: rowTops[row] + (rows[row].height - box.height) / 2
427
+ });
428
+ });
429
+ laneNodes.push({
430
+ id: laneId,
431
+ type: SWIMLANE_NODE_TYPE,
432
+ position: { x: laneX, y: laneY },
433
+ origin: [0, 0],
434
+ style: { width: laneWidth, height: laneHeight },
435
+ draggable: false,
436
+ selectable: false,
437
+ focusable: false,
438
+ data: {
439
+ label,
440
+ groupBy,
441
+ level: groupBy,
442
+ style: "lanes",
443
+ kind: lane === EXTERNAL_LANE ? "external" : lane === UNASSIGNED_LANE ? "unassigned" : "lane",
444
+ index
445
+ }
446
+ });
447
+ laneY += laneHeight;
448
+ });
449
+ return {
450
+ // Lanes come before their nodes, as React Flow needs parents first
451
+ nodes: [
452
+ ...laneNodes,
453
+ ...nodes.map((node) => {
454
+ const position = positions.get(node.id);
455
+ if (!position) return node;
456
+ return {
457
+ ...node,
458
+ parentId: `swimlane-${laneOf.get(node.id)}`,
459
+ origin: [0, 0],
460
+ position
461
+ };
462
+ })
463
+ ],
464
+ edges: edges.map((edge) => {
465
+ if (!edge.data?.route) return edge;
466
+ const { route: _route, ...data } = edge.data;
467
+ return { ...edge, data };
468
+ })
469
+ };
470
+ };
471
+ var getGrouping = (nodes) => {
472
+ const data = nodes.find(isSwimlaneNode)?.data;
473
+ return data ? { groupBy: data.groupBy, style: data.style } : null;
474
+ };
475
+ var removeSwimlanes = ({
476
+ nodes,
477
+ edges
478
+ }) => {
479
+ const groups = new Map(
480
+ nodes.filter(isSwimlaneNode).map((group) => [group.id, group])
481
+ );
482
+ if (groups.size === 0) return { nodes, edges };
483
+ const offset = (id) => {
484
+ const group = id ? groups.get(id) : void 0;
485
+ if (!group) return { x: 0, y: 0 };
486
+ const parent = offset(group.parentId);
487
+ return { x: parent.x + group.position.x, y: parent.y + group.position.y };
488
+ };
489
+ return {
490
+ nodes: nodes.filter((node) => !groups.has(node.id)).map((node) => {
491
+ if (!node.parentId || !groups.has(node.parentId)) return node;
492
+ const { parentId, ...rest } = node;
493
+ const at = offset(parentId);
494
+ return {
495
+ ...rest,
496
+ position: { x: at.x + node.position.x, y: at.y + node.position.y }
497
+ };
498
+ }),
499
+ edges
500
+ };
501
+ };
502
+ var BOX_PADDING = 40;
503
+ var groupIntoBoxes = async ({ nodes, edges }, groupBy, { sizeOf = getNodeSize } = {}) => {
504
+ const topLevel = nodes.filter((node) => !node.parentId);
505
+ const levels = groupBy === "domain-system" ? ["domain", "system"] : [groupBy];
506
+ const assignments = levels.map(
507
+ (level) => assignLanes(topLevel, edges, level)
508
+ );
509
+ const groups = /* @__PURE__ */ new Map();
510
+ const parentOf = /* @__PURE__ */ new Map();
511
+ topLevel.forEach((node) => {
512
+ let parentId;
513
+ assignments.forEach(({ laneOf, names }, depth) => {
514
+ const lane = laneOf.get(node.id);
515
+ if (lane === UNASSIGNED_LANE || depth > 0 && lane === EXTERNAL_LANE)
516
+ return;
517
+ const level = levels[depth];
518
+ const id = `${parentId ?? "swimlane"}__${level}-${lane}`;
519
+ if (!groups.has(id)) {
520
+ const label = lane === EXTERNAL_LANE ? "External" : names.get(lane) ?? lane;
521
+ const minHeight = labelLength(label) + LANE_LABEL_PADDING * 2;
522
+ groups.set(id, {
523
+ id,
524
+ type: SWIMLANE_NODE_TYPE,
525
+ ...parentId && { parentId },
526
+ position: { x: 0, y: 0 },
527
+ selectable: false,
528
+ focusable: false,
529
+ data: {
530
+ label,
531
+ groupBy,
532
+ level,
533
+ style: "boxes",
534
+ kind: lane === EXTERNAL_LANE ? "external" : "lane",
535
+ index: groups.size,
536
+ // Room for its name on the left, and at least as tall as it
537
+ layoutOptions: {
538
+ "elk.padding": `[top=${BOX_PADDING},left=${LANE_HEADER_WIDTH + BOX_PADDING},bottom=${BOX_PADDING},right=${BOX_PADDING}]`,
539
+ "elk.nodeSize.constraints": "MINIMUM_SIZE",
540
+ "elk.nodeSize.minimum": `(0, ${Math.ceil(minHeight)})`
541
+ }
542
+ }
543
+ });
544
+ }
545
+ parentId = id;
546
+ });
547
+ if (parentId) parentOf.set(node.id, parentId);
548
+ });
549
+ return layoutWithElk(
550
+ {
551
+ nodes: [
552
+ ...groups.values(),
553
+ ...nodes.map((node) => {
554
+ const parentId = parentOf.get(node.id);
555
+ return parentId ? { ...node, parentId } : node;
556
+ })
557
+ ],
558
+ edges
559
+ },
560
+ { sizeOf }
561
+ );
562
+ };
563
+
564
+ // src/utils/export-mermaid.ts
565
+ var NODE_SHAPE_MAP = {
566
+ agents: ["[[", "]]"],
567
+ agent: ["[[", "]]"],
568
+ agentTool: ["[", "]"],
569
+ "agent-tool": ["[", "]"],
570
+ services: ["[[", "]]"],
571
+ // stadium shape
572
+ service: ["[[", "]]"],
573
+ events: [">", "]"],
574
+ // flag/asymmetric shape (message-like)
575
+ event: [">", "]"],
576
+ commands: [">", "]"],
577
+ // flag/asymmetric shape (message-like)
578
+ command: [">", "]"],
579
+ queries: ["{{", "}}"],
580
+ // hexagon
581
+ query: ["{{", "}}"],
582
+ // Parallelogram: what messages pass through, not a cylinder, which reads as
583
+ // a database (and is what data stores are drawn as)
584
+ channels: ["[/", "/]"],
585
+ channel: ["[/", "/]"],
586
+ domains: ["[", "]"],
587
+ // rectangle
588
+ domain: ["[", "]"],
589
+ "context-domain": ["[", "]"],
590
+ // another domain, shown as a single card
591
+ flows: ["([", "])"],
592
+ // stadium (rounded)
593
+ flow: ["([", "])"],
594
+ flowExpanded: ["([", "])"],
595
+ systems: ["[[", "]]"],
596
+ // a system shown as a single node
597
+ system: ["[[", "]]"],
598
+ step: ["[", "]"],
599
+ // rectangle
600
+ user: ["((", "))"],
601
+ // circle
602
+ actor: ["((", "))"],
603
+ // circle
604
+ "context-actor": ["((", "))"],
605
+ // circle
606
+ externalSystem: ["[[", "]]"],
607
+ // stadium
608
+ "external-system": ["[[", "]]"],
609
+ data: ["[(", ")]"],
610
+ // cylinder (database)
611
+ container: ["[(", ")]"],
612
+ "data-product": ["[[", "]]"],
613
+ // stadium
614
+ "data-products": ["[[", "]]"],
615
+ entities: ["[", "]"],
616
+ // rectangle
617
+ entity: ["[", "]"],
618
+ custom: ["[", "]"],
619
+ // rectangle
620
+ view: ["[", "]"],
621
+ // rectangle
622
+ note: ["[", "]"],
623
+ // rectangle
624
+ field: ["[(", ")]"],
625
+ // cylinder
626
+ fields: ["[(", ")]"],
627
+ // cylinder
628
+ messageGroup: ["[[", "]]"],
629
+ messageGroupExpanded: ["[[", "]]"]
630
+ };
631
+ var NODE_STYLE_CLASSES = {
632
+ agents: "fill:#0ea5e9,stroke:#0369a1,color:#fff",
633
+ agent: "fill:#0ea5e9,stroke:#0369a1,color:#fff",
634
+ agentTool: "fill:#8b5cf6,stroke:#6d28d9,color:#fff",
635
+ "agent-tool": "fill:#8b5cf6,stroke:#6d28d9,color:#fff",
636
+ services: "fill:#ec4899,stroke:#be185d,color:#fff",
637
+ service: "fill:#ec4899,stroke:#be185d,color:#fff",
638
+ events: "fill:#f97316,stroke:#c2410c,color:#fff",
639
+ event: "fill:#f97316,stroke:#c2410c,color:#fff",
640
+ commands: "fill:#3b82f6,stroke:#1d4ed8,color:#fff",
641
+ command: "fill:#3b82f6,stroke:#1d4ed8,color:#fff",
642
+ queries: "fill:#22c55e,stroke:#15803d,color:#fff",
643
+ query: "fill:#22c55e,stroke:#15803d,color:#fff",
644
+ channels: "fill:#6b7280,stroke:#374151,color:#fff",
645
+ channel: "fill:#6b7280,stroke:#374151,color:#fff",
646
+ domains: "fill:#eab308,stroke:#a16207,color:#000",
647
+ domain: "fill:#eab308,stroke:#a16207,color:#000",
648
+ "context-domain": "fill:#eab308,stroke:#a16207,color:#000",
649
+ flows: "fill:#14b8a6,stroke:#0f766e,color:#fff",
650
+ flow: "fill:#14b8a6,stroke:#0f766e,color:#fff",
651
+ flowExpanded: "fill:#14b8a6,stroke:#0f766e,color:#fff",
652
+ systems: "fill:#8b5cf6,stroke:#6d28d9,color:#fff",
653
+ system: "fill:#8b5cf6,stroke:#6d28d9,color:#fff",
654
+ step: "fill:#374151,stroke:#1f2937,color:#fff",
655
+ user: "fill:#8b5cf6,stroke:#6d28d9,color:#fff",
656
+ actor: "fill:#eab308,stroke:#a16207,color:#000",
657
+ "context-actor": "fill:#eab308,stroke:#a16207,color:#000",
658
+ // yellow, as actors are on the diagram
659
+ externalSystem: "fill:#ec4899,stroke:#be185d,color:#fff",
660
+ "external-system": "fill:#ec4899,stroke:#be185d,color:#fff",
661
+ data: "fill:#3b82f6,stroke:#1d4ed8,color:#fff",
662
+ container: "fill:#3b82f6,stroke:#1d4ed8,color:#fff",
663
+ "data-product": "fill:#6366f1,stroke:#4338ca,color:#fff",
664
+ "data-products": "fill:#6366f1,stroke:#4338ca,color:#fff",
665
+ entities: "fill:#6b7280,stroke:#374151,color:#fff",
666
+ entity: "fill:#6b7280,stroke:#374151,color:#fff",
667
+ custom: "fill:#9ca3af,stroke:#6b7280,color:#000",
668
+ view: "fill:#9ca3af,stroke:#6b7280,color:#000",
669
+ note: "fill:#fef3c7,stroke:#d97706,color:#000",
670
+ field: "fill:#06b6d4,stroke:#0891b2,color:#fff",
671
+ fields: "fill:#06b6d4,stroke:#0891b2,color:#fff",
672
+ messageGroup: "fill:#7c3aed,stroke:#5b21b6,color:#fff",
673
+ messageGroupExpanded: "fill:#7c3aed,stroke:#5b21b6,color:#fff"
674
+ };
675
+ var GROUP_NODE_TYPES = /* @__PURE__ */ new Set(["system-group", "domain-group", "group"]);
676
+ var GROUP_STYLE_CLASSES = {
677
+ domainGroup: "fill:#eab30814,stroke:#ca8a04,stroke-width:2px,stroke-dasharray:6 4,color:#a16207",
678
+ subdomainGroup: "fill:#eab3080d,stroke:#ca8a04,stroke-dasharray:4 4,color:#a16207",
679
+ otherDomainGroup: "fill:#9ca3af14,stroke:#9ca3af,stroke-dasharray:4 4,color:#6b7280",
680
+ systemGroup: "fill:#8b5cf614,stroke:#8b5cf6,stroke-width:2px,color:#7c3aed",
681
+ laneGroup: "fill:#9ca3af0d,stroke:#9ca3af,stroke-dasharray:4 4,color:#6b7280"
682
+ };
683
+ var FOCUSED_STYLE = "stroke-width:4px";
684
+ var CROSS_DOMAIN_LINK_STYLE = "stroke:#4f46e5,stroke-width:3px";
685
+ var RESERVED_IDS = /* @__PURE__ */ new Set([
686
+ "end",
687
+ "subgraph",
688
+ "graph",
689
+ "flowchart",
690
+ "style",
691
+ "class",
692
+ "classdef",
693
+ "click",
694
+ "linkstyle",
695
+ "call",
696
+ "href"
697
+ ]);
698
+ function sanitizeMermaidId(id) {
699
+ const sanitized = id.replace(/[^a-zA-Z0-9_]/g, "_");
700
+ return RESERVED_IDS.has(sanitized.toLowerCase()) ? `node_${sanitized}` : sanitized;
701
+ }
702
+ function normaliseLines(label) {
703
+ return label.split("\n").map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean);
704
+ }
705
+ function escapeMermaidLabel(label) {
706
+ return label.replace(/#/g, "#35;").replace(/"/g, "#quot;");
707
+ }
708
+ function getMermaidNodeShape(type) {
709
+ return NODE_SHAPE_MAP[type] || ["[", "]"];
710
+ }
711
+ function formatLabelWithVersion(name, version) {
712
+ if (version) {
713
+ return `${name} (${version})`;
714
+ }
715
+ return name;
716
+ }
717
+ function resourceLabel(resource, fallback) {
718
+ const name = resource?.name || resource?.data?.name || resource?.id || resource?.data?.id;
719
+ const version = resource?.version || resource?.data?.version;
720
+ return formatLabelWithVersion(name || fallback, version);
721
+ }
722
+ function getNodeLabel(node) {
723
+ const { type } = node;
724
+ const data = node.data;
725
+ if (!data) return node.id;
726
+ if (type === "agents" || type === "agent") {
727
+ return resourceLabel(data.agent, node.id);
728
+ }
729
+ if (type === "agentTool" || type === "agent-tool") {
730
+ const agentTool = data.agentTool;
731
+ return agentTool?.name || agentTool?.id || node.id;
732
+ }
733
+ if (type === "services" || type === "service") {
734
+ return resourceLabel(data.service, node.id);
735
+ }
736
+ if (type === "events" || type === "event" || type === "commands" || type === "command" || type === "queries" || type === "query") {
737
+ return resourceLabel(data.message, node.id);
738
+ }
739
+ if (type === "channels" || type === "channel") {
740
+ return resourceLabel(data.channel, node.id);
741
+ }
742
+ if (type === "domains" || type === "domain" || type === "context-domain" || type === "domain-group" || type === "group") {
743
+ return resourceLabel(data.domain, node.id);
744
+ }
745
+ if (type === "systems" || type === "system" || type === "system-group") {
746
+ return resourceLabel(data.system, node.id);
747
+ }
748
+ if (type === "flows" || type === "flow") {
749
+ return resourceLabel(data.flow, node.id);
750
+ }
751
+ if (type === "flowExpanded") {
752
+ return formatLabelWithVersion(data.flowName || node.id, data.version);
753
+ }
754
+ if (type === "step") {
755
+ const step = data.step;
756
+ return step?.title || step?.name || step?.id || node.id;
757
+ }
758
+ if (type === "custom") {
759
+ return data.custom?.title || data.step?.title || data.label || node.id;
760
+ }
761
+ if (type === "externalSystem" || type === "external-system") {
762
+ const system = data.externalSystem || data;
763
+ return system?.name || system?.id || node.id;
764
+ }
765
+ if (type === "data" || type === "container") {
766
+ const dataNode = data.data;
767
+ return dataNode?.name || dataNode?.id || node.id;
768
+ }
769
+ if (type === "data-product" || type === "data-products") {
770
+ return resourceLabel(data.dataProduct, node.id);
771
+ }
772
+ if (type === "entities" || type === "entity") {
773
+ const entity = data.entity;
774
+ return entity?.name || entity?.id || node.id;
775
+ }
776
+ if (type === "view") {
777
+ return data.view?.name || data.view?.id || node.id;
778
+ }
779
+ if (type === "note") {
780
+ return data.text || data.label || "Note";
781
+ }
782
+ if (type === "field" || type === "fields") {
783
+ const name = data.name || node.id;
784
+ return data.type ? `${name} (${data.type})` : name;
785
+ }
786
+ if (type === "messageGroup" || type === "messageGroupExpanded") {
787
+ const groupName = data.groupName || node.id;
788
+ const count = data.messageCount || 0;
789
+ return `${groupName} (${count} messages)`;
790
+ }
791
+ return data.name || data.label || data.title || data.id || node.id;
792
+ }
793
+ function getEdgeLabel(edge) {
794
+ if (edge.label && typeof edge.label === "string") {
795
+ return edge.label;
796
+ }
797
+ if (edge.data?.label && typeof edge.data.label === "string") {
798
+ return edge.data.label;
799
+ }
800
+ return void 0;
801
+ }
802
+ function getMermaidIds(nodes) {
803
+ const mermaidIds = /* @__PURE__ */ new Map();
804
+ const usedIds = /* @__PURE__ */ new Set();
805
+ nodes.forEach((node) => {
806
+ const base = sanitizeMermaidId(node.id);
807
+ let id = base;
808
+ for (let n = 2; usedIds.has(id); n++) id = `${base}_${n}`;
809
+ usedIds.add(id);
810
+ mermaidIds.set(node.id, id);
811
+ });
812
+ return mermaidIds;
813
+ }
814
+ var isEntityNode = (node) => node.type === "entities" || node.type === "entity";
815
+ var CARDINALITY_RELATIONS = /* @__PURE__ */ new Set([
816
+ "hasOne",
817
+ "hasMany",
818
+ "belongsTo",
819
+ "references"
820
+ ]);
821
+ var ENTITY_STYLE_CLASSES = {
822
+ externalEntity: "fill:#9ca3af1a,stroke:#9ca3af,stroke-dasharray:4 4",
823
+ aggregateRoot: "stroke-width:3px"
824
+ };
825
+ function sanitizeErToken(value, fallback) {
826
+ const token = value.trim().replace(/[^A-Za-z0-9_\-[\]().]/g, "_");
827
+ return /^[A-Za-z_]/.test(token) ? token : `${fallback}_${token}`;
828
+ }
829
+ function getErCardinality(property) {
830
+ const required = !!property.required;
831
+ if (property.relationType === "hasMany" || property.type === "array") {
832
+ return required ? "||--|{" : "||--o{";
833
+ }
834
+ if (property.relationType === "hasOne") {
835
+ return required ? "||--||" : "||--o|";
836
+ }
837
+ return required ? "}o--||" : "}o--o|";
838
+ }
839
+ function convertEntitiesToMermaid(nodes, edges, { includeStyles = true } = {}) {
840
+ const mermaidIds = getMermaidIds(nodes);
841
+ const lines = ["erDiagram"];
842
+ const classes = /* @__PURE__ */ new Map();
843
+ const addClass = (name, id) => classes.set(name, [...classes.get(name) ?? [], id]);
844
+ nodes.forEach((node) => {
845
+ const data = node.data;
846
+ const entity = data?.entity?.data ?? {};
847
+ const id = mermaidIds.get(node.id);
848
+ const name = entity.name || data?.label || node.id;
849
+ const label = data?.externalToDomain && data?.domainName ? `${name} (${data.domainName})` : name;
850
+ const references = new Set(data?.referencePropertyNames ?? []);
851
+ const properties = entity.properties ?? [];
852
+ const attributes = properties.map((property) => {
853
+ const type = property.type === "array" && property.items?.type ? `${property.items.type}[]` : property.type || "unknown";
854
+ const keys = [
855
+ property.name === entity.identifier && "PK",
856
+ (property.references || references.has(property.name)) && "FK"
857
+ ].filter(Boolean);
858
+ return ` ${sanitizeErToken(type, "t")} ${sanitizeErToken(property.name, "p")}${keys.length ? ` ${keys.join(", ")}` : ""}`;
859
+ });
860
+ const definition = ` ${id}["${escapeMermaidLabel(label)}"]`;
861
+ lines.push(
862
+ ...attributes.length ? [`${definition} {`, ...attributes, " }"] : [definition]
863
+ );
864
+ if (data?.externalToDomain) addClass("externalEntity", id);
865
+ if (entity.aggregateRoot) addClass("aggregateRoot", id);
866
+ });
867
+ const nodesById = new Map(nodes.map((node) => [node.id, node]));
868
+ const relationships = edges.filter(
869
+ (edge) => !edge.hidden && mermaidIds.has(edge.source) && mermaidIds.has(edge.target)
870
+ ).map((edge) => {
871
+ const propertyName = edge.sourceHandle?.replace(/-source$/, "");
872
+ const properties = nodesById.get(edge.source)?.data?.entity?.data?.properties ?? [];
873
+ const property = properties.find((p) => p.name === propertyName);
874
+ const relation = property?.relationType?.trim();
875
+ const label = relation && !CARDINALITY_RELATIONS.has(relation) ? relation : propertyName || getEdgeLabel(edge) || "references";
876
+ const cardinality = property ? getErCardinality(property) : "}o--o|";
877
+ return ` ${mermaidIds.get(edge.source)} ${cardinality} ${mermaidIds.get(edge.target)} : "${escapeMermaidLabel(label)}"`;
878
+ });
879
+ if (relationships.length > 0) lines.push("", ...relationships);
880
+ if (includeStyles && classes.size > 0) {
881
+ lines.push("");
882
+ classes.forEach((ids, name) => {
883
+ lines.push(` classDef ${name} ${ENTITY_STYLE_CLASSES[name]}`);
884
+ lines.push(` class ${ids.join(",")} ${name}`);
885
+ });
886
+ }
887
+ return lines.join("\n");
888
+ }
889
+ function getGroupClass(node) {
890
+ const data = node.data;
891
+ if (isSwimlaneNode(node)) return "laneGroup";
892
+ if (node.type === "system-group") return "systemGroup";
893
+ if (data?.otherDomain) return "otherDomainGroup";
894
+ if (data?.subdomain) return "subdomainGroup";
895
+ return "domainGroup";
896
+ }
897
+ var isCrossDomain = (edge) => !!edge.data?.crossDomain;
898
+ function convertToMermaid(graphNodes, graphEdges, options = {}) {
899
+ const { includeStyles = true, direction = "LR" } = options;
900
+ const nodes = graphNodes.filter((node) => !node.hidden);
901
+ if (nodes.length > 0 && nodes.every(isEntityNode)) {
902
+ return convertEntitiesToMermaid(nodes, graphEdges, { includeStyles });
903
+ }
904
+ const mermaidIds = getMermaidIds(nodes);
905
+ const lines = [];
906
+ lines.push(`flowchart ${direction}`);
907
+ const childrenOf = /* @__PURE__ */ new Map();
908
+ const topLevelNodes = [];
909
+ nodes.forEach((node) => {
910
+ if (node.parentId && mermaidIds.has(node.parentId)) {
911
+ childrenOf.set(node.parentId, [
912
+ ...childrenOf.get(node.parentId) ?? [],
913
+ node
914
+ ]);
915
+ } else {
916
+ topLevelNodes.push(node);
917
+ }
918
+ });
919
+ const isGroup = (node) => childrenOf.has(node.id) || GROUP_NODE_TYPES.has(node.type ?? "") || isSwimlaneNode(node);
920
+ const usedTypes = /* @__PURE__ */ new Set();
921
+ const groupsByClass = /* @__PURE__ */ new Map();
922
+ const focusedIds = [];
923
+ nodes.forEach((node) => {
924
+ const id = mermaidIds.get(node.id);
925
+ if (isGroup(node)) {
926
+ const groupClass = getGroupClass(node);
927
+ groupsByClass.set(groupClass, [
928
+ ...groupsByClass.get(groupClass) ?? [],
929
+ id
930
+ ]);
931
+ return;
932
+ }
933
+ if (node.type && NODE_STYLE_CLASSES[node.type]) usedTypes.add(node.type);
934
+ if (node.data?.isFocused) focusedIds.push(id);
935
+ });
936
+ if (includeStyles) {
937
+ lines.push("");
938
+ lines.push(" %% Style definitions");
939
+ usedTypes.forEach((type) => {
940
+ lines.push(
941
+ ` classDef ${sanitizeMermaidId(type)} ${NODE_STYLE_CLASSES[type]}`
942
+ );
943
+ });
944
+ groupsByClass.forEach((_, groupClass) => {
945
+ lines.push(
946
+ ` classDef ${groupClass} ${GROUP_STYLE_CLASSES[groupClass]}`
947
+ );
948
+ });
949
+ if (focusedIds.length > 0) {
950
+ lines.push(` classDef focused ${FOCUSED_STYLE}`);
951
+ }
952
+ }
953
+ lines.push("");
954
+ lines.push(" %% Nodes");
955
+ const addNode = (node, depth) => {
956
+ const indent = " ".repeat(depth);
957
+ const id = mermaidIds.get(node.id);
958
+ const label = escapeMermaidLabel(
959
+ normaliseLines(getNodeLabel(node)).join("<br/>")
960
+ );
961
+ if (isGroup(node)) {
962
+ lines.push(`${indent}subgraph ${id}["${label}"]`);
963
+ childrenOf.get(node.id)?.forEach((child) => addNode(child, depth + 1));
964
+ lines.push(`${indent}end`);
965
+ return;
966
+ }
967
+ const [prefix, suffix] = getMermaidNodeShape(node.type || "custom");
968
+ let nodeLine = `${indent}${id}${prefix}"${label}"${suffix}`;
969
+ if (includeStyles && node.type && usedTypes.has(node.type)) {
970
+ nodeLine += `:::${sanitizeMermaidId(node.type)}`;
971
+ }
972
+ lines.push(nodeLine);
973
+ };
974
+ topLevelNodes.forEach((node) => addNode(node, 1));
975
+ lines.push("");
976
+ lines.push(" %% Edges");
977
+ const crossDomainLinks = [];
978
+ graphEdges.filter(
979
+ (edge) => !edge.hidden && mermaidIds.has(edge.source) && mermaidIds.has(edge.target)
980
+ ).forEach((edge, index) => {
981
+ const sourceId = mermaidIds.get(edge.source);
982
+ const targetId = mermaidIds.get(edge.target);
983
+ const label = normaliseLines(getEdgeLabel(edge) ?? "").join(" ");
984
+ lines.push(
985
+ label ? ` ${sourceId} -->|"${escapeMermaidLabel(label)}"| ${targetId}` : ` ${sourceId} --> ${targetId}`
986
+ );
987
+ if (isCrossDomain(edge)) crossDomainLinks.push(index);
988
+ });
989
+ if (includeStyles) {
990
+ const styleLines = [];
991
+ groupsByClass.forEach((ids, groupClass) => {
992
+ styleLines.push(` class ${ids.join(",")} ${groupClass}`);
993
+ });
994
+ if (focusedIds.length > 0) {
995
+ styleLines.push(` class ${focusedIds.join(",")} focused`);
996
+ }
997
+ if (crossDomainLinks.length > 0) {
998
+ styleLines.push(
999
+ ` linkStyle ${crossDomainLinks.join(",")} ${CROSS_DOMAIN_LINK_STYLE}`
1000
+ );
1001
+ }
1002
+ if (styleLines.length > 0) {
1003
+ lines.push("");
1004
+ lines.push(" %% Boundaries and highlights");
1005
+ lines.push(...styleLines);
1006
+ }
1007
+ }
1008
+ return lines.join("\n");
1009
+ }
1010
+
1011
+ // src/utils/hide-messages.ts
1012
+ import { MarkerType as MarkerType2 } from "@xyflow/react";
1013
+ var MESSAGE_NODE_TYPES = [...MESSAGE, "messageGroup", "messageGroupExpanded"];
1014
+ var isMessageNode = (node) => MESSAGE_NODE_TYPES.includes(node.type || "");
1015
+ var getCollection = (type = "") => {
1016
+ if (EVENT.includes(type)) return "events";
1017
+ if (COMMAND.includes(type)) return "commands";
1018
+ if (QUERY.includes(type)) return "queries";
1019
+ return "messages";
1020
+ };
1021
+ var getMessages = (node) => {
1022
+ const data = node.data;
1023
+ if (node.type === "messageGroup") {
1024
+ return (data?.messages || []).map(({ message: message2 }) => [
1025
+ message2?.data?.name || message2?.data?.id,
1026
+ getCollection(message2?.collection)
1027
+ ]);
1028
+ }
1029
+ const message = data?.message;
1030
+ return message ? [[message.name || message.id, getCollection(node.type)]] : [];
1031
+ };
1032
+ var VERBS = {
1033
+ events: "publishes",
1034
+ commands: "invokes",
1035
+ queries: "requests"
1036
+ };
1037
+ var getMessagesLabel = (messages) => {
1038
+ if (messages.size === 0) return void 0;
1039
+ const collections = new Set(messages.values());
1040
+ const collection = collections.size === 1 ? Array.from(collections)[0] : "messages";
1041
+ const verb = VERBS[collection] || "sends";
1042
+ const sent = messages.size === 1 ? Array.from(messages.keys())[0] : `${messages.size} ${collection}`;
1043
+ return `${verb}
1044
+ ${sent}`;
1045
+ };
1046
+ var isCarrier = (node) => isMessageNode(node) || node.type === "channels";
1047
+ var isCrossDomain2 = (edge) => !!edge.data?.crossDomain;
1048
+ var getRoute = (edge) => edge.data?.route;
1049
+ var joinRoutes = (routes) => {
1050
+ if (!routes.every((route) => route && route.points.length > 0)) return;
1051
+ const laidOut = routes;
1052
+ const arrives = laidOut[0].points[laidOut[0].points.length - 1];
1053
+ const leaves = laidOut[1].points[0];
1054
+ return {
1055
+ points: laidOut.flatMap((route) => route.points),
1056
+ label: { x: (arrives.x + leaves.x) / 2, y: (arrives.y + leaves.y) / 2 },
1057
+ source: laidOut[0].source,
1058
+ target: laidOut[laidOut.length - 1].target
1059
+ };
1060
+ };
1061
+ var hideNodes = (nodes, edges, isHidden) => {
1062
+ const hiddenNodes = new Map(
1063
+ nodes.filter(isHidden).map((node) => [node.id, node])
1064
+ );
1065
+ if (hiddenNodes.size === 0) return { nodes, edges };
1066
+ const outgoingEdges = /* @__PURE__ */ new Map();
1067
+ for (const edge of edges) {
1068
+ const outgoing = outgoingEdges.get(edge.source) || [];
1069
+ outgoing.push(edge);
1070
+ outgoingEdges.set(edge.source, outgoing);
1071
+ }
1072
+ const keptEdges = [];
1073
+ const bridgedEdges = /* @__PURE__ */ new Map();
1074
+ for (const edge of edges) {
1075
+ if (hiddenNodes.has(edge.source)) continue;
1076
+ const target = hiddenNodes.get(edge.target);
1077
+ if (!target) {
1078
+ keptEdges.push(edge);
1079
+ continue;
1080
+ }
1081
+ if (!isCarrier(target)) continue;
1082
+ const stack = [
1083
+ {
1084
+ id: edge.target,
1085
+ messages: [],
1086
+ routes: [getRoute(edge)],
1087
+ crossDomain: isCrossDomain2(edge)
1088
+ }
1089
+ ];
1090
+ const visited = /* @__PURE__ */ new Set();
1091
+ while (stack.length > 0) {
1092
+ const current = stack.pop();
1093
+ if (visited.has(current.id)) continue;
1094
+ visited.add(current.id);
1095
+ const messages = [
1096
+ ...current.messages,
1097
+ ...getMessages(hiddenNodes.get(current.id))
1098
+ ];
1099
+ for (const next of outgoingEdges.get(current.id) || []) {
1100
+ const routes = [...current.routes, getRoute(next)];
1101
+ const crossDomain = current.crossDomain || isCrossDomain2(next);
1102
+ const nextNode = hiddenNodes.get(next.target);
1103
+ if (nextNode) {
1104
+ if (isCarrier(nextNode))
1105
+ stack.push({ id: next.target, messages, routes, crossDomain });
1106
+ continue;
1107
+ }
1108
+ if (next.target === edge.source) continue;
1109
+ const id = `bridged-${edge.source}-${next.target}`;
1110
+ const bridged = bridgedEdges.get(id) || {
1111
+ source: edge.source,
1112
+ target: next.target,
1113
+ messages: /* @__PURE__ */ new Map(),
1114
+ route: joinRoutes(routes),
1115
+ crossDomain: false
1116
+ };
1117
+ bridged.crossDomain || (bridged.crossDomain = crossDomain);
1118
+ messages.forEach(
1119
+ ([name, collection]) => bridged.messages.set(name, collection)
1120
+ );
1121
+ bridgedEdges.set(id, bridged);
1122
+ }
1123
+ }
1124
+ }
1125
+ const newEdges = Array.from(
1126
+ bridgedEdges,
1127
+ ([id, bridged]) => createEdge({
1128
+ id,
1129
+ source: bridged.source,
1130
+ target: bridged.target,
1131
+ label: getMessagesLabel(bridged.messages),
1132
+ // The same size arrow as the rest of the graph's edges
1133
+ markerEnd: {
1134
+ type: MarkerType2.ArrowClosed,
1135
+ width: 20,
1136
+ height: 20,
1137
+ color: "rgb(var(--ec-page-text-muted))"
1138
+ },
1139
+ // Along the same route, and crossing domains if any edge it replaces did
1140
+ data: {
1141
+ ...bridged.route ? { route: bridged.route } : {},
1142
+ ...bridged.crossDomain ? { crossDomain: true } : {}
1143
+ }
1144
+ })
1145
+ );
1146
+ return {
1147
+ nodes: nodes.filter((node) => !hiddenNodes.has(node.id)),
1148
+ edges: [...keptEdges, ...newEdges]
1149
+ };
1150
+ };
1151
+ var hideMessageNodes = (nodes, edges) => hideNodes(nodes, edges, isCarrier);
1152
+
1153
+ export {
1154
+ SERVICE,
1155
+ AGENT,
1156
+ AGENT_TOOL,
1157
+ EVENT,
1158
+ QUERY,
1159
+ COMMAND,
1160
+ CHANNEL,
1161
+ ACTOR,
1162
+ DATA,
1163
+ VIEW,
1164
+ MESSAGE,
1165
+ generateIdForNode,
1166
+ generateIdForNodes,
1167
+ generatedIdForEdge,
1168
+ getColorFromString,
1169
+ getEdgeLabelForServiceAsTarget,
1170
+ getEdgeLabelForMessageAsSource,
1171
+ getEdgeLabelSize,
1172
+ createEdge,
1173
+ createNode,
1174
+ getNodeSize,
1175
+ layoutWithElk,
1176
+ FLOW_GROUP_BYS,
1177
+ isFlowGroupBy,
1178
+ isFlowGroupStyle,
1179
+ SWIMLANE_GROUP_BY_LABELS,
1180
+ LANE_HEADER_WIDTH,
1181
+ LANE_LABEL,
1182
+ isSwimlaneNode,
1183
+ hasSwimlanes,
1184
+ countGroups,
1185
+ layoutSwimlanes,
1186
+ getGrouping,
1187
+ removeSwimlanes,
1188
+ groupIntoBoxes,
1189
+ convertToMermaid,
1190
+ isMessageNode,
1191
+ getMessagesLabel,
1192
+ isCarrier,
1193
+ hideNodes,
1194
+ hideMessageNodes
1195
+ };
1196
+ //# sourceMappingURL=chunk-P2DH6CFV.mjs.map