@eventcatalog/visualiser 4.1.4 → 5.0.0-beta.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.
- package/LICENSE +0 -10
- package/dist/index.d.mts +82 -63
- package/dist/index.d.ts +82 -63
- package/dist/index.js +4585 -2734
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4364 -2486
- package/dist/index.mjs.map +1 -1
- package/dist/layout.d.mts +74 -0
- package/dist/layout.d.ts +74 -0
- package/dist/layout.js +427 -0
- package/dist/layout.js.map +1 -0
- package/dist/layout.mjs +384 -0
- package/dist/layout.mjs.map +1 -0
- package/dist/styles-core.css +361 -160
- package/dist/styles.css +362 -160
- package/package.json +7 -4
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Node, Edge } from '@xyflow/react';
|
|
2
|
+
|
|
3
|
+
type LayoutGraph = {
|
|
4
|
+
nodes: Node[];
|
|
5
|
+
edges: Edge[];
|
|
6
|
+
};
|
|
7
|
+
type LayoutSize = {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
};
|
|
11
|
+
type EdgeRoute = {
|
|
12
|
+
points: {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
}[];
|
|
16
|
+
label?: {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
};
|
|
20
|
+
/** Where the edge's source and target were laid out (top-left, in the flow) */
|
|
21
|
+
source?: {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
};
|
|
25
|
+
target?: {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/** The size a node is laid out at: as rendered, or an estimate for its type */
|
|
31
|
+
declare const getNodeSize: (node: Node) => LayoutSize;
|
|
32
|
+
/**
|
|
33
|
+
* Lays a graph out with ELK (left to right). Nested groups (nodes other nodes
|
|
34
|
+
* have as their `parentId`) are laid out in one pass with their children.
|
|
35
|
+
* Edges are routed at right angles around nodes, each edge with its own
|
|
36
|
+
* connection point so edges don't run on top of each other, and labels are
|
|
37
|
+
* placed on their edge. Each edge keeps its type and gets its route (and label
|
|
38
|
+
* position) in `data.route`, which the visualiser's edges are drawn along.
|
|
39
|
+
*
|
|
40
|
+
* Node positions are top-left corners (nodes get `origin: [0, 0]`), children
|
|
41
|
+
* relative to their group. Groups are sized to fit their children.
|
|
42
|
+
*/
|
|
43
|
+
declare const layoutWithElk: ({ nodes, edges }: LayoutGraph, { sizeOf }?: {
|
|
44
|
+
sizeOf?: (node: Node) => LayoutSize;
|
|
45
|
+
}) => Promise<LayoutGraph>;
|
|
46
|
+
|
|
47
|
+
declare const isMessageNode: (node: Node) => boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Label for an edge carrying messages (name -> collection), e.g.
|
|
50
|
+
* "publishes\nProduct Created", "publishes\n3 events", "sends\n3 messages"
|
|
51
|
+
*/
|
|
52
|
+
declare const getMessagesLabel: (messages: Map<string, string>) => string | undefined;
|
|
53
|
+
declare const isCarrier: (node: Node) => boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Removes the nodes `isHidden` picks. Messages and channels removed are
|
|
56
|
+
* bridged: the nodes either side of them are connected directly, labelled with
|
|
57
|
+
* the messages between them. Other edges to removed nodes go with them.
|
|
58
|
+
* e.g. hiding events, ServiceA -> OrderPlaced -> ServiceB becomes ServiceA -> ServiceB
|
|
59
|
+
*/
|
|
60
|
+
declare const hideNodes: (nodes: Node[], edges: Edge[], isHidden: (node: Node) => boolean) => {
|
|
61
|
+
nodes: Node[];
|
|
62
|
+
edges: Edge[];
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Removes message nodes (events, commands, queries) and the channels they
|
|
66
|
+
* travel through, connecting the nodes on either side directly.
|
|
67
|
+
* e.g. ServiceA -> OrderPlaced -> orders-topic -> ServiceB becomes ServiceA -> ServiceB
|
|
68
|
+
*/
|
|
69
|
+
declare const hideMessageNodes: (nodes: Node[], edges: Edge[]) => {
|
|
70
|
+
nodes: Node[];
|
|
71
|
+
edges: Edge[];
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export { type EdgeRoute, type LayoutGraph, type LayoutSize, getMessagesLabel, getNodeSize, hideMessageNodes, hideNodes, isCarrier, isMessageNode, layoutWithElk };
|
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Node, Edge } from '@xyflow/react';
|
|
2
|
+
|
|
3
|
+
type LayoutGraph = {
|
|
4
|
+
nodes: Node[];
|
|
5
|
+
edges: Edge[];
|
|
6
|
+
};
|
|
7
|
+
type LayoutSize = {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
};
|
|
11
|
+
type EdgeRoute = {
|
|
12
|
+
points: {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
}[];
|
|
16
|
+
label?: {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
};
|
|
20
|
+
/** Where the edge's source and target were laid out (top-left, in the flow) */
|
|
21
|
+
source?: {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
};
|
|
25
|
+
target?: {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/** The size a node is laid out at: as rendered, or an estimate for its type */
|
|
31
|
+
declare const getNodeSize: (node: Node) => LayoutSize;
|
|
32
|
+
/**
|
|
33
|
+
* Lays a graph out with ELK (left to right). Nested groups (nodes other nodes
|
|
34
|
+
* have as their `parentId`) are laid out in one pass with their children.
|
|
35
|
+
* Edges are routed at right angles around nodes, each edge with its own
|
|
36
|
+
* connection point so edges don't run on top of each other, and labels are
|
|
37
|
+
* placed on their edge. Each edge keeps its type and gets its route (and label
|
|
38
|
+
* position) in `data.route`, which the visualiser's edges are drawn along.
|
|
39
|
+
*
|
|
40
|
+
* Node positions are top-left corners (nodes get `origin: [0, 0]`), children
|
|
41
|
+
* relative to their group. Groups are sized to fit their children.
|
|
42
|
+
*/
|
|
43
|
+
declare const layoutWithElk: ({ nodes, edges }: LayoutGraph, { sizeOf }?: {
|
|
44
|
+
sizeOf?: (node: Node) => LayoutSize;
|
|
45
|
+
}) => Promise<LayoutGraph>;
|
|
46
|
+
|
|
47
|
+
declare const isMessageNode: (node: Node) => boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Label for an edge carrying messages (name -> collection), e.g.
|
|
50
|
+
* "publishes\nProduct Created", "publishes\n3 events", "sends\n3 messages"
|
|
51
|
+
*/
|
|
52
|
+
declare const getMessagesLabel: (messages: Map<string, string>) => string | undefined;
|
|
53
|
+
declare const isCarrier: (node: Node) => boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Removes the nodes `isHidden` picks. Messages and channels removed are
|
|
56
|
+
* bridged: the nodes either side of them are connected directly, labelled with
|
|
57
|
+
* the messages between them. Other edges to removed nodes go with them.
|
|
58
|
+
* e.g. hiding events, ServiceA -> OrderPlaced -> ServiceB becomes ServiceA -> ServiceB
|
|
59
|
+
*/
|
|
60
|
+
declare const hideNodes: (nodes: Node[], edges: Edge[], isHidden: (node: Node) => boolean) => {
|
|
61
|
+
nodes: Node[];
|
|
62
|
+
edges: Edge[];
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Removes message nodes (events, commands, queries) and the channels they
|
|
66
|
+
* travel through, connecting the nodes on either side directly.
|
|
67
|
+
* e.g. ServiceA -> OrderPlaced -> orders-topic -> ServiceB becomes ServiceA -> ServiceB
|
|
68
|
+
*/
|
|
69
|
+
declare const hideMessageNodes: (nodes: Node[], edges: Edge[]) => {
|
|
70
|
+
nodes: Node[];
|
|
71
|
+
edges: Edge[];
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export { type EdgeRoute, type LayoutGraph, type LayoutSize, getMessagesLabel, getNodeSize, hideMessageNodes, hideNodes, isCarrier, isMessageNode, layoutWithElk };
|
package/dist/layout.js
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/layout.ts
|
|
31
|
+
var layout_exports = {};
|
|
32
|
+
__export(layout_exports, {
|
|
33
|
+
getMessagesLabel: () => getMessagesLabel,
|
|
34
|
+
getNodeSize: () => getNodeSize,
|
|
35
|
+
hideMessageNodes: () => hideMessageNodes,
|
|
36
|
+
hideNodes: () => hideNodes,
|
|
37
|
+
isCarrier: () => isCarrier,
|
|
38
|
+
isMessageNode: () => isMessageNode,
|
|
39
|
+
layoutWithElk: () => layoutWithElk
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(layout_exports);
|
|
42
|
+
|
|
43
|
+
// src/utils/utils/utils.ts
|
|
44
|
+
var import_react = require("@xyflow/react");
|
|
45
|
+
var getEdgeLabelSize = (label) => {
|
|
46
|
+
if (typeof label !== "string" || !label.trim())
|
|
47
|
+
return { width: 0, height: 0 };
|
|
48
|
+
const lines = label.split("\n");
|
|
49
|
+
const longestLine = Math.max(...lines.map((line) => line.trim().length));
|
|
50
|
+
return { width: longestLine * 6 + 20, height: lines.length * 14 + 12 };
|
|
51
|
+
};
|
|
52
|
+
var createEdge = (edgeOptions) => {
|
|
53
|
+
return {
|
|
54
|
+
label: "subscribed by",
|
|
55
|
+
animated: false,
|
|
56
|
+
// markerStart: {
|
|
57
|
+
// type: MarkerType.Arrow,
|
|
58
|
+
// width: 40,
|
|
59
|
+
// height: 40,
|
|
60
|
+
// },
|
|
61
|
+
markerEnd: {
|
|
62
|
+
type: import_react.MarkerType.ArrowClosed,
|
|
63
|
+
width: 40,
|
|
64
|
+
height: 40,
|
|
65
|
+
color: "rgb(var(--ec-page-text-muted))"
|
|
66
|
+
},
|
|
67
|
+
style: {
|
|
68
|
+
strokeWidth: 1.5,
|
|
69
|
+
stroke: "rgb(var(--ec-page-text-muted))",
|
|
70
|
+
strokeDasharray: "5 5"
|
|
71
|
+
},
|
|
72
|
+
...edgeOptions
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// src/utils/elk-layout.ts
|
|
77
|
+
var DEFAULT_SIZE = { width: 240, height: 112 };
|
|
78
|
+
var NODE_SIZES = {
|
|
79
|
+
events: { width: 240, height: 104 },
|
|
80
|
+
commands: { width: 240, height: 126 },
|
|
81
|
+
queries: { width: 240, height: 126 },
|
|
82
|
+
services: { width: 240, height: 112 },
|
|
83
|
+
data: { width: 240, height: 108 },
|
|
84
|
+
systems: { width: 240, height: 120 },
|
|
85
|
+
system: { width: 240, height: 120 },
|
|
86
|
+
"context-domain": { width: 240, height: 112 },
|
|
87
|
+
"context-actor": { width: 180, height: 80 },
|
|
88
|
+
actor: { width: 180, height: 80 },
|
|
89
|
+
note: { width: 200, height: 150 },
|
|
90
|
+
step: { width: 240, height: 80 },
|
|
91
|
+
// Groups with nothing in them (groups with children are sized to fit them)
|
|
92
|
+
"domain-group": { width: 320, height: 160 },
|
|
93
|
+
"system-group": { width: 320, height: 160 }
|
|
94
|
+
};
|
|
95
|
+
var GROUP_TYPES = /* @__PURE__ */ new Set(["domain-group", "system-group", "group"]);
|
|
96
|
+
var getNodeSize = (node) => {
|
|
97
|
+
if (node.measured?.width && node.measured?.height) {
|
|
98
|
+
return { width: node.measured.width, height: node.measured.height };
|
|
99
|
+
}
|
|
100
|
+
if (typeof node.width === "number" && typeof node.height === "number") {
|
|
101
|
+
return { width: node.width, height: node.height };
|
|
102
|
+
}
|
|
103
|
+
if (node.type === "domains" || node.type === "domain") {
|
|
104
|
+
const services = node.data?.domain?.data?.services || [];
|
|
105
|
+
return { width: 260, height: 60 + services.length * 40 };
|
|
106
|
+
}
|
|
107
|
+
return NODE_SIZES[node.type || ""] ?? DEFAULT_SIZE;
|
|
108
|
+
};
|
|
109
|
+
var GROUP_PADDING = "[top=100,left=40,bottom=40,right=40]";
|
|
110
|
+
var SPACING = {
|
|
111
|
+
"elk.layered.spacing.nodeNodeBetweenLayers": "60",
|
|
112
|
+
"elk.spacing.nodeNode": "40",
|
|
113
|
+
"elk.spacing.edgeNode": "25",
|
|
114
|
+
"elk.spacing.edgeEdge": "15"
|
|
115
|
+
};
|
|
116
|
+
var LARGE_GRAPH_NODES = 80;
|
|
117
|
+
var LARGE_GRAPH_EDGES = 200;
|
|
118
|
+
var LARGE_GRAPH_OPTIONS = {
|
|
119
|
+
"elk.layered.nodePlacement.strategy": "BRANDES_KOEPF",
|
|
120
|
+
"elk.layered.thoroughness": "1",
|
|
121
|
+
"elk.layered.crossingMinimization.greedySwitch.type": "OFF"
|
|
122
|
+
};
|
|
123
|
+
var elk;
|
|
124
|
+
var getElk = async () => {
|
|
125
|
+
if (!elk) {
|
|
126
|
+
const { default: ELK } = await import("elkjs/lib/elk.bundled.js");
|
|
127
|
+
elk = new ELK();
|
|
128
|
+
}
|
|
129
|
+
return elk;
|
|
130
|
+
};
|
|
131
|
+
var layoutWithElk = async ({ nodes, edges }, { sizeOf = getNodeSize } = {}) => {
|
|
132
|
+
const nodesById = new Map(nodes.map((node) => [node.id, node]));
|
|
133
|
+
const groupIds = new Set(
|
|
134
|
+
nodes.map((node) => node.parentId).filter((id) => !!id && nodesById.has(id))
|
|
135
|
+
);
|
|
136
|
+
const parentOf = (node) => node.parentId && groupIds.has(node.parentId) ? node.parentId : void 0;
|
|
137
|
+
const layoutEdges = edges.filter(
|
|
138
|
+
(edge) => nodesById.has(edge.source) && nodesById.has(edge.target)
|
|
139
|
+
);
|
|
140
|
+
const ports = /* @__PURE__ */ new Map();
|
|
141
|
+
const addPort = (nodeId, id, side) => {
|
|
142
|
+
const nodePorts = ports.get(nodeId) || [];
|
|
143
|
+
if (!nodePorts.some((port) => port.id === id)) {
|
|
144
|
+
ports.set(nodeId, [
|
|
145
|
+
...nodePorts,
|
|
146
|
+
{ id, layoutOptions: { "elk.port.side": side } }
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
return id;
|
|
150
|
+
};
|
|
151
|
+
const edgePorts = layoutEdges.map((edge, index) => ({
|
|
152
|
+
source: addPort(
|
|
153
|
+
edge.source,
|
|
154
|
+
edge.sourceHandle ? `${edge.source}__${edge.sourceHandle}` : `e${index}__out`,
|
|
155
|
+
"EAST"
|
|
156
|
+
),
|
|
157
|
+
target: addPort(
|
|
158
|
+
edge.target,
|
|
159
|
+
edge.targetHandle ? `${edge.target}__${edge.targetHandle}` : `e${index}__in`,
|
|
160
|
+
"WEST"
|
|
161
|
+
)
|
|
162
|
+
}));
|
|
163
|
+
const toElkNode = (node) => ({
|
|
164
|
+
id: node.id,
|
|
165
|
+
...groupIds.has(node.id) ? {
|
|
166
|
+
children: nodes.filter((child) => parentOf(child) === node.id).map(toElkNode)
|
|
167
|
+
} : sizeOf(node),
|
|
168
|
+
layoutOptions: {
|
|
169
|
+
"elk.portConstraints": "FIXED_SIDE",
|
|
170
|
+
...groupIds.has(node.id) ? { "elk.padding": GROUP_PADDING, ...SPACING } : {},
|
|
171
|
+
// A node's own options (e.g. a group's padding) win
|
|
172
|
+
...node.data?.layoutOptions ?? {}
|
|
173
|
+
},
|
|
174
|
+
ports: ports.get(node.id) || []
|
|
175
|
+
});
|
|
176
|
+
const layout = await (await getElk()).layout({
|
|
177
|
+
id: "root",
|
|
178
|
+
layoutOptions: {
|
|
179
|
+
"elk.algorithm": "layered",
|
|
180
|
+
"elk.direction": "RIGHT",
|
|
181
|
+
"elk.hierarchyHandling": "INCLUDE_CHILDREN",
|
|
182
|
+
"elk.edgeRouting": "ORTHOGONAL",
|
|
183
|
+
// Edge routes in absolute coordinates (node positions stay relative to their parent)
|
|
184
|
+
"elk.json.edgeCoords": "ROOT",
|
|
185
|
+
"elk.edgeLabels.placement": "CENTER",
|
|
186
|
+
...SPACING,
|
|
187
|
+
...nodes.length >= LARGE_GRAPH_NODES || edges.length >= LARGE_GRAPH_EDGES ? LARGE_GRAPH_OPTIONS : { "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX" }
|
|
188
|
+
},
|
|
189
|
+
children: nodes.filter((node) => !parentOf(node)).map(toElkNode),
|
|
190
|
+
edges: layoutEdges.map((edge, index) => ({
|
|
191
|
+
id: `e${index}`,
|
|
192
|
+
sources: [edgePorts[index].source],
|
|
193
|
+
targets: [edgePorts[index].target],
|
|
194
|
+
// Labels sit on their edge, rather than beside it (only works set per label)
|
|
195
|
+
labels: edge.label ? [
|
|
196
|
+
{
|
|
197
|
+
text: String(edge.label),
|
|
198
|
+
...getEdgeLabelSize(edge.label),
|
|
199
|
+
layoutOptions: { "elk.edgeLabels.inline": "true" }
|
|
200
|
+
}
|
|
201
|
+
] : []
|
|
202
|
+
}))
|
|
203
|
+
});
|
|
204
|
+
const laidOut = /* @__PURE__ */ new Map();
|
|
205
|
+
const collect = (elkNode) => (elkNode.children || []).forEach((child) => {
|
|
206
|
+
laidOut.set(child.id, child);
|
|
207
|
+
collect(child);
|
|
208
|
+
});
|
|
209
|
+
collect(layout);
|
|
210
|
+
const absolute = (nodeId) => {
|
|
211
|
+
const node = nodesById.get(nodeId);
|
|
212
|
+
const parent = node && parentOf(node);
|
|
213
|
+
const offset = parent ? absolute(parent) : { x: 0, y: 0 };
|
|
214
|
+
const elkNode = laidOut.get(nodeId);
|
|
215
|
+
return { x: offset.x + (elkNode?.x ?? 0), y: offset.y + (elkNode?.y ?? 0) };
|
|
216
|
+
};
|
|
217
|
+
const routes = /* @__PURE__ */ new Map();
|
|
218
|
+
(layout.edges || []).forEach((elkEdge) => {
|
|
219
|
+
const section = elkEdge.sections?.[0];
|
|
220
|
+
if (!section) return;
|
|
221
|
+
const label = elkEdge.labels?.[0];
|
|
222
|
+
const edge = layoutEdges[Number(elkEdge.id.slice(1))];
|
|
223
|
+
routes.set(edge, {
|
|
224
|
+
points: [
|
|
225
|
+
section.startPoint,
|
|
226
|
+
...section.bendPoints || [],
|
|
227
|
+
section.endPoint
|
|
228
|
+
],
|
|
229
|
+
label: label ? {
|
|
230
|
+
x: (label.x ?? 0) + (label.width ?? 0) / 2,
|
|
231
|
+
y: (label.y ?? 0) + (label.height ?? 0) / 2
|
|
232
|
+
} : void 0,
|
|
233
|
+
source: absolute(edge.source),
|
|
234
|
+
target: absolute(edge.target)
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
const depth = (node) => {
|
|
238
|
+
const parent = node && parentOf(node);
|
|
239
|
+
return parent ? depth(nodesById.get(parent)) + 1 : 0;
|
|
240
|
+
};
|
|
241
|
+
return {
|
|
242
|
+
// Parents must come before their children in the array for React Flow
|
|
243
|
+
nodes: [...nodes].sort((a, b) => depth(a) - depth(b)).map((node) => {
|
|
244
|
+
const elkNode = laidOut.get(node.id);
|
|
245
|
+
if (!elkNode) return node;
|
|
246
|
+
return {
|
|
247
|
+
...node,
|
|
248
|
+
origin: [0, 0],
|
|
249
|
+
position: { x: elkNode.x ?? 0, y: elkNode.y ?? 0 },
|
|
250
|
+
...groupIds.has(node.id) || GROUP_TYPES.has(node.type || "") && typeof node.style?.width !== "number" ? {
|
|
251
|
+
style: {
|
|
252
|
+
...node.style,
|
|
253
|
+
width: elkNode.width,
|
|
254
|
+
height: elkNode.height
|
|
255
|
+
},
|
|
256
|
+
// Re-measured at its new size
|
|
257
|
+
measured: void 0
|
|
258
|
+
} : {}
|
|
259
|
+
};
|
|
260
|
+
}),
|
|
261
|
+
edges: edges.map((edge) => {
|
|
262
|
+
const route = routes.get(edge);
|
|
263
|
+
return route ? { ...edge, data: { ...edge.data, route } } : edge;
|
|
264
|
+
})
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// src/utils/hide-messages.ts
|
|
269
|
+
var import_react2 = require("@xyflow/react");
|
|
270
|
+
|
|
271
|
+
// src/nodes/node-types.ts
|
|
272
|
+
var EVENT = ["event", "events"];
|
|
273
|
+
var QUERY = ["queries", "query", "querie"];
|
|
274
|
+
var COMMAND = ["command", "commands"];
|
|
275
|
+
var MESSAGE = [...EVENT, ...COMMAND, ...QUERY];
|
|
276
|
+
|
|
277
|
+
// src/utils/hide-messages.ts
|
|
278
|
+
var MESSAGE_NODE_TYPES = [...MESSAGE, "messageGroup", "messageGroupExpanded"];
|
|
279
|
+
var isMessageNode = (node) => MESSAGE_NODE_TYPES.includes(node.type || "");
|
|
280
|
+
var getCollection = (type = "") => {
|
|
281
|
+
if (EVENT.includes(type)) return "events";
|
|
282
|
+
if (COMMAND.includes(type)) return "commands";
|
|
283
|
+
if (QUERY.includes(type)) return "queries";
|
|
284
|
+
return "messages";
|
|
285
|
+
};
|
|
286
|
+
var getMessages = (node) => {
|
|
287
|
+
const data = node.data;
|
|
288
|
+
if (node.type === "messageGroup") {
|
|
289
|
+
return (data?.messages || []).map(({ message: message2 }) => [
|
|
290
|
+
message2?.data?.name || message2?.data?.id,
|
|
291
|
+
getCollection(message2?.collection)
|
|
292
|
+
]);
|
|
293
|
+
}
|
|
294
|
+
const message = data?.message;
|
|
295
|
+
return message ? [[message.name || message.id, getCollection(node.type)]] : [];
|
|
296
|
+
};
|
|
297
|
+
var VERBS = {
|
|
298
|
+
events: "publishes",
|
|
299
|
+
commands: "invokes",
|
|
300
|
+
queries: "requests"
|
|
301
|
+
};
|
|
302
|
+
var getMessagesLabel = (messages) => {
|
|
303
|
+
if (messages.size === 0) return void 0;
|
|
304
|
+
const collections = new Set(messages.values());
|
|
305
|
+
const collection = collections.size === 1 ? Array.from(collections)[0] : "messages";
|
|
306
|
+
const verb = VERBS[collection] || "sends";
|
|
307
|
+
const sent = messages.size === 1 ? Array.from(messages.keys())[0] : `${messages.size} ${collection}`;
|
|
308
|
+
return `${verb}
|
|
309
|
+
${sent}`;
|
|
310
|
+
};
|
|
311
|
+
var isCarrier = (node) => isMessageNode(node) || node.type === "channels";
|
|
312
|
+
var isCrossDomain = (edge) => !!edge.data?.crossDomain;
|
|
313
|
+
var getRoute = (edge) => edge.data?.route;
|
|
314
|
+
var joinRoutes = (routes) => {
|
|
315
|
+
if (!routes.every((route) => route && route.points.length > 0)) return;
|
|
316
|
+
const laidOut = routes;
|
|
317
|
+
const arrives = laidOut[0].points[laidOut[0].points.length - 1];
|
|
318
|
+
const leaves = laidOut[1].points[0];
|
|
319
|
+
return {
|
|
320
|
+
points: laidOut.flatMap((route) => route.points),
|
|
321
|
+
label: { x: (arrives.x + leaves.x) / 2, y: (arrives.y + leaves.y) / 2 },
|
|
322
|
+
source: laidOut[0].source,
|
|
323
|
+
target: laidOut[laidOut.length - 1].target
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
var hideNodes = (nodes, edges, isHidden) => {
|
|
327
|
+
const hiddenNodes = new Map(
|
|
328
|
+
nodes.filter(isHidden).map((node) => [node.id, node])
|
|
329
|
+
);
|
|
330
|
+
if (hiddenNodes.size === 0) return { nodes, edges };
|
|
331
|
+
const outgoingEdges = /* @__PURE__ */ new Map();
|
|
332
|
+
for (const edge of edges) {
|
|
333
|
+
const outgoing = outgoingEdges.get(edge.source) || [];
|
|
334
|
+
outgoing.push(edge);
|
|
335
|
+
outgoingEdges.set(edge.source, outgoing);
|
|
336
|
+
}
|
|
337
|
+
const keptEdges = [];
|
|
338
|
+
const bridgedEdges = /* @__PURE__ */ new Map();
|
|
339
|
+
for (const edge of edges) {
|
|
340
|
+
if (hiddenNodes.has(edge.source)) continue;
|
|
341
|
+
const target = hiddenNodes.get(edge.target);
|
|
342
|
+
if (!target) {
|
|
343
|
+
keptEdges.push(edge);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (!isCarrier(target)) continue;
|
|
347
|
+
const stack = [
|
|
348
|
+
{
|
|
349
|
+
id: edge.target,
|
|
350
|
+
messages: [],
|
|
351
|
+
routes: [getRoute(edge)],
|
|
352
|
+
crossDomain: isCrossDomain(edge)
|
|
353
|
+
}
|
|
354
|
+
];
|
|
355
|
+
const visited = /* @__PURE__ */ new Set();
|
|
356
|
+
while (stack.length > 0) {
|
|
357
|
+
const current = stack.pop();
|
|
358
|
+
if (visited.has(current.id)) continue;
|
|
359
|
+
visited.add(current.id);
|
|
360
|
+
const messages = [
|
|
361
|
+
...current.messages,
|
|
362
|
+
...getMessages(hiddenNodes.get(current.id))
|
|
363
|
+
];
|
|
364
|
+
for (const next of outgoingEdges.get(current.id) || []) {
|
|
365
|
+
const routes = [...current.routes, getRoute(next)];
|
|
366
|
+
const crossDomain = current.crossDomain || isCrossDomain(next);
|
|
367
|
+
const nextNode = hiddenNodes.get(next.target);
|
|
368
|
+
if (nextNode) {
|
|
369
|
+
if (isCarrier(nextNode))
|
|
370
|
+
stack.push({ id: next.target, messages, routes, crossDomain });
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (next.target === edge.source) continue;
|
|
374
|
+
const id = `bridged-${edge.source}-${next.target}`;
|
|
375
|
+
const bridged = bridgedEdges.get(id) || {
|
|
376
|
+
source: edge.source,
|
|
377
|
+
target: next.target,
|
|
378
|
+
messages: /* @__PURE__ */ new Map(),
|
|
379
|
+
route: joinRoutes(routes),
|
|
380
|
+
crossDomain: false
|
|
381
|
+
};
|
|
382
|
+
bridged.crossDomain || (bridged.crossDomain = crossDomain);
|
|
383
|
+
messages.forEach(
|
|
384
|
+
([name, collection]) => bridged.messages.set(name, collection)
|
|
385
|
+
);
|
|
386
|
+
bridgedEdges.set(id, bridged);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const newEdges = Array.from(
|
|
391
|
+
bridgedEdges,
|
|
392
|
+
([id, bridged]) => createEdge({
|
|
393
|
+
id,
|
|
394
|
+
source: bridged.source,
|
|
395
|
+
target: bridged.target,
|
|
396
|
+
label: getMessagesLabel(bridged.messages),
|
|
397
|
+
// The same size arrow as the rest of the graph's edges
|
|
398
|
+
markerEnd: {
|
|
399
|
+
type: import_react2.MarkerType.ArrowClosed,
|
|
400
|
+
width: 20,
|
|
401
|
+
height: 20,
|
|
402
|
+
color: "rgb(var(--ec-page-text-muted))"
|
|
403
|
+
},
|
|
404
|
+
// Along the same route, and crossing domains if any edge it replaces did
|
|
405
|
+
data: {
|
|
406
|
+
...bridged.route ? { route: bridged.route } : {},
|
|
407
|
+
...bridged.crossDomain ? { crossDomain: true } : {}
|
|
408
|
+
}
|
|
409
|
+
})
|
|
410
|
+
);
|
|
411
|
+
return {
|
|
412
|
+
nodes: nodes.filter((node) => !hiddenNodes.has(node.id)),
|
|
413
|
+
edges: [...keptEdges, ...newEdges]
|
|
414
|
+
};
|
|
415
|
+
};
|
|
416
|
+
var hideMessageNodes = (nodes, edges) => hideNodes(nodes, edges, isCarrier);
|
|
417
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
418
|
+
0 && (module.exports = {
|
|
419
|
+
getMessagesLabel,
|
|
420
|
+
getNodeSize,
|
|
421
|
+
hideMessageNodes,
|
|
422
|
+
hideNodes,
|
|
423
|
+
isCarrier,
|
|
424
|
+
isMessageNode,
|
|
425
|
+
layoutWithElk
|
|
426
|
+
});
|
|
427
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/layout.ts","../src/utils/utils/utils.ts","../src/utils/elk-layout.ts","../src/utils/hide-messages.ts","../src/nodes/node-types.ts"],"sourcesContent":["/**\n * Graph layout and transforms, without React components: used by EventCatalog\n * to lay graphs out on the server (`@eventcatalog/visualiser/layout`).\n */\nexport {\n layoutWithElk,\n getNodeSize,\n type LayoutGraph,\n type LayoutSize,\n type EdgeRoute,\n} from \"./utils/elk-layout\";\nexport {\n hideMessageNodes,\n hideNodes,\n getMessagesLabel,\n isCarrier,\n isMessageNode,\n} from \"./utils/hide-messages\";\n","// Can't use the CollectionEntry type from astro:content because a client component is using this util\n\nimport { MarkerType, Position, type Edge, type Node } from \"@xyflow/react\";\n\ninterface BaseCollectionData {\n id: string;\n version: string;\n}\n\ninterface CollectionItem {\n collection: string;\n data: BaseCollectionData;\n}\n\ninterface MessageCollectionItem extends CollectionItem {\n collection: \"commands\" | \"events\" | \"queries\";\n}\n\nexport const generateIdForNode = (node: CollectionItem) => {\n return `${node.data.id}-${node.data.version}`;\n};\nexport const generateIdForNodes = (nodes: any) => {\n return nodes\n .map((node: any) => `${node.data.id}-${node.data.version}`)\n .join(\"-\");\n};\nexport const generatedIdForEdge = (\n source: CollectionItem,\n target: CollectionItem,\n) => {\n return `${source.data.id}-${source.data.version}-${target.data.id}-${target.data.version}`;\n};\n\nexport const getColorFromString = (id: string) => {\n // Takes the given id (string) and returns a custom hex color based on the id\n // Create a hash from the string\n let hash = 0;\n for (let i = 0; i < id.length; i++) {\n hash = id.charCodeAt(i) + ((hash << 5) - hash);\n }\n\n // Convert the hash into a hex color\n let color = \"#\";\n for (let i = 0; i < 3; i++) {\n const value = (hash >> (i * 8)) & 0xff;\n color += value.toString(16).padStart(2, \"0\");\n }\n\n return color;\n};\n\nexport const getEdgeLabelForServiceAsTarget = (data: MessageCollectionItem) => {\n const type = data.collection;\n switch (type) {\n case \"commands\":\n return \"invokes\";\n case \"events\":\n return \"publishes \\nevent\";\n case \"queries\":\n return \"requests\";\n default:\n return \"sends to\";\n }\n};\nexport const getEdgeLabelForMessageAsSource = (\n data: MessageCollectionItem,\n throughChannel = false,\n) => {\n const type = data.collection;\n switch (type) {\n case \"commands\":\n return \"accepts\";\n case \"events\":\n return throughChannel ? \"subscribed to\" : \"subscribed by\";\n case \"queries\":\n return \"accepts\";\n default:\n return \"sends to\";\n }\n};\n\n/**\n * Rough size of an edge label, so the layout can leave room for it on its edge\n */\nexport const getEdgeLabelSize = (label: unknown) => {\n if (typeof label !== \"string\" || !label.trim())\n return { width: 0, height: 0 };\n const lines = label.split(\"\\n\");\n const longestLine = Math.max(...lines.map((line) => line.trim().length));\n return { width: longestLine * 6 + 20, height: lines.length * 14 + 12 };\n};\n\nexport const createEdge = (edgeOptions: Edge): Edge => {\n return {\n label: \"subscribed by\",\n animated: false,\n // markerStart: {\n // type: MarkerType.Arrow,\n // width: 40,\n // height: 40,\n // },\n markerEnd: {\n type: MarkerType.ArrowClosed,\n width: 40,\n height: 40,\n color: \"rgb(var(--ec-page-text-muted))\",\n },\n style: {\n strokeWidth: 1.5,\n stroke: \"rgb(var(--ec-page-text-muted))\",\n strokeDasharray: \"5 5\",\n },\n ...edgeOptions,\n };\n};\n\nexport const createNode = (values: Node): Node => {\n return {\n sourcePosition: Position.Right,\n targetPosition: Position.Left,\n ...values,\n };\n};\n","import type { Edge, Node } from \"@xyflow/react\";\nimport type {\n ELK,\n ElkExtendedEdge,\n ElkNode,\n ElkPort,\n LayoutOptions,\n} from \"elkjs/lib/elk-api\";\nimport { getEdgeLabelSize } from \"./utils/utils\";\n\nexport type LayoutGraph = { nodes: Node[]; edges: Edge[] };\nexport type LayoutSize = { width: number; height: number };\nexport type EdgeRoute = {\n points: { x: number; y: number }[];\n label?: { x: number; y: number };\n /** Where the edge's source and target were laid out (top-left, in the flow) */\n source?: { x: number; y: number };\n target?: { x: number; y: number };\n};\n\n// The size each node type renders at (at most), for nodes that haven't been\n// rendered (and measured) yet, e.g. when laying a graph out on the server\nconst DEFAULT_SIZE: LayoutSize = { width: 240, height: 112 };\nconst NODE_SIZES: Record<string, LayoutSize> = {\n events: { width: 240, height: 104 },\n commands: { width: 240, height: 126 },\n queries: { width: 240, height: 126 },\n services: { width: 240, height: 112 },\n data: { width: 240, height: 108 },\n systems: { width: 240, height: 120 },\n system: { width: 240, height: 120 },\n \"context-domain\": { width: 240, height: 112 },\n \"context-actor\": { width: 180, height: 80 },\n actor: { width: 180, height: 80 },\n note: { width: 200, height: 150 },\n step: { width: 240, height: 80 },\n // Groups with nothing in them (groups with children are sized to fit them)\n \"domain-group\": { width: 320, height: 160 },\n \"system-group\": { width: 320, height: 160 },\n};\n\n// Nodes sized by their style, like groups: an empty one needs a size too\nconst GROUP_TYPES = new Set([\"domain-group\", \"system-group\", \"group\"]);\n\n/** The size a node is laid out at: as rendered, or an estimate for its type */\nexport const getNodeSize = (node: Node): LayoutSize => {\n if (node.measured?.width && node.measured?.height) {\n return { width: node.measured.width, height: node.measured.height };\n }\n if (typeof node.width === \"number\" && typeof node.height === \"number\") {\n return { width: node.width, height: node.height };\n }\n // Domains list their services, so grow with them\n if (node.type === \"domains\" || node.type === \"domain\") {\n const services = (node.data as any)?.domain?.data?.services || [];\n return { width: 260, height: 60 + services.length * 40 };\n }\n return NODE_SIZES[node.type || \"\"] ?? DEFAULT_SIZE;\n};\n\n// Room for a group's header above its children\nconst GROUP_PADDING = \"[top=100,left=40,bottom=40,right=40]\";\n\n// Space between nodes and edges. Set on every group as well as the graph, as\n// groups don't take it from the graph (they'd use ELK's much tighter defaults)\nconst SPACING = {\n \"elk.layered.spacing.nodeNodeBetweenLayers\": \"60\",\n \"elk.spacing.nodeNode\": \"40\",\n \"elk.spacing.edgeNode\": \"25\",\n \"elk.spacing.edgeEdge\": \"15\",\n};\n\n// Large graphs (e.g. a big domain) use cheaper settings: the nicest node\n// placement takes minutes on a few thousand edges, these take a second or two\nconst LARGE_GRAPH_NODES = 80;\nconst LARGE_GRAPH_EDGES = 200;\nconst LARGE_GRAPH_OPTIONS: LayoutOptions = {\n \"elk.layered.nodePlacement.strategy\": \"BRANDES_KOEPF\",\n \"elk.layered.thoroughness\": \"1\",\n \"elk.layered.crossingMinimization.greedySwitch.type\": \"OFF\",\n};\n\nlet elk: ELK | undefined;\n// Loaded when first needed, so pages that never lay out in the browser don't download it\nconst getElk = async () => {\n if (!elk) {\n const { default: ELK } = await import(\"elkjs/lib/elk.bundled.js\");\n elk = new ELK();\n }\n return elk;\n};\n\n/**\n * Lays a graph out with ELK (left to right). Nested groups (nodes other nodes\n * have as their `parentId`) are laid out in one pass with their children.\n * Edges are routed at right angles around nodes, each edge with its own\n * connection point so edges don't run on top of each other, and labels are\n * placed on their edge. Each edge keeps its type and gets its route (and label\n * position) in `data.route`, which the visualiser's edges are drawn along.\n *\n * Node positions are top-left corners (nodes get `origin: [0, 0]`), children\n * relative to their group. Groups are sized to fit their children.\n */\nexport const layoutWithElk = async (\n { nodes, edges }: LayoutGraph,\n { sizeOf = getNodeSize }: { sizeOf?: (node: Node) => LayoutSize } = {},\n): Promise<LayoutGraph> => {\n const nodesById = new Map(nodes.map((node) => [node.id, node]));\n const groupIds = new Set(\n nodes\n .map((node) => node.parentId)\n .filter((id): id is string => !!id && nodesById.has(id)),\n );\n const parentOf = (node: Node) =>\n node.parentId && groupIds.has(node.parentId) ? node.parentId : undefined;\n\n // Each edge gets its own port, leaving from the right of its source and\n // arriving at the left of its target, so ELK spreads edges out along a\n // node's side rather than routing them all through one point. Edges to a\n // specific handle (e.g. a service listed in a domain) share that handle's port.\n const layoutEdges = edges.filter(\n (edge) => nodesById.has(edge.source) && nodesById.has(edge.target),\n );\n const ports = new Map<string, ElkPort[]>();\n const addPort = (\n nodeId: string,\n id: string,\n side: \"EAST\" | \"WEST\",\n ): string => {\n const nodePorts = ports.get(nodeId) || [];\n if (!nodePorts.some((port) => port.id === id)) {\n ports.set(nodeId, [\n ...nodePorts,\n { id, layoutOptions: { \"elk.port.side\": side } },\n ]);\n }\n return id;\n };\n const edgePorts = layoutEdges.map((edge, index) => ({\n source: addPort(\n edge.source,\n edge.sourceHandle\n ? `${edge.source}__${edge.sourceHandle}`\n : `e${index}__out`,\n \"EAST\",\n ),\n target: addPort(\n edge.target,\n edge.targetHandle\n ? `${edge.target}__${edge.targetHandle}`\n : `e${index}__in`,\n \"WEST\",\n ),\n }));\n\n const toElkNode = (node: Node): ElkNode => ({\n id: node.id,\n ...(groupIds.has(node.id)\n ? {\n children: nodes\n .filter((child) => parentOf(child) === node.id)\n .map(toElkNode),\n }\n : sizeOf(node)),\n layoutOptions: {\n \"elk.portConstraints\": \"FIXED_SIDE\",\n ...(groupIds.has(node.id)\n ? { \"elk.padding\": GROUP_PADDING, ...SPACING }\n : {}),\n // A node's own options (e.g. a group's padding) win\n ...((node.data as { layoutOptions?: LayoutOptions } | undefined)\n ?.layoutOptions ?? {}),\n },\n ports: ports.get(node.id) || [],\n });\n\n const layout = await (\n await getElk()\n ).layout({\n id: \"root\",\n layoutOptions: {\n \"elk.algorithm\": \"layered\",\n \"elk.direction\": \"RIGHT\",\n \"elk.hierarchyHandling\": \"INCLUDE_CHILDREN\",\n \"elk.edgeRouting\": \"ORTHOGONAL\",\n // Edge routes in absolute coordinates (node positions stay relative to their parent)\n \"elk.json.edgeCoords\": \"ROOT\",\n \"elk.edgeLabels.placement\": \"CENTER\",\n ...SPACING,\n ...(nodes.length >= LARGE_GRAPH_NODES || edges.length >= LARGE_GRAPH_EDGES\n ? LARGE_GRAPH_OPTIONS\n : { \"elk.layered.nodePlacement.strategy\": \"NETWORK_SIMPLEX\" }),\n },\n children: nodes.filter((node) => !parentOf(node)).map(toElkNode),\n edges: layoutEdges.map((edge, index) => ({\n id: `e${index}`,\n sources: [edgePorts[index].source],\n targets: [edgePorts[index].target],\n // Labels sit on their edge, rather than beside it (only works set per label)\n labels: edge.label\n ? [\n {\n text: String(edge.label),\n ...getEdgeLabelSize(edge.label),\n layoutOptions: { \"elk.edgeLabels.inline\": \"true\" },\n },\n ]\n : [],\n })),\n });\n\n const laidOut = new Map<string, ElkNode>();\n const collect = (elkNode: ElkNode) =>\n (elkNode.children || []).forEach((child) => {\n laidOut.set(child.id, child);\n collect(child);\n });\n collect(layout);\n\n // Where a node was laid out in the flow (ELK positions are relative to the parent)\n const absolute = (nodeId: string): { x: number; y: number } => {\n const node = nodesById.get(nodeId);\n const parent = node && parentOf(node);\n const offset = parent ? absolute(parent) : { x: 0, y: 0 };\n const elkNode = laidOut.get(nodeId);\n return { x: offset.x + (elkNode?.x ?? 0), y: offset.y + (elkNode?.y ?? 0) };\n };\n\n const routes = new Map<Edge, EdgeRoute>();\n (layout.edges || []).forEach((elkEdge: ElkExtendedEdge) => {\n const section = elkEdge.sections?.[0];\n if (!section) return;\n const label = elkEdge.labels?.[0];\n const edge = layoutEdges[Number(elkEdge.id.slice(1))];\n routes.set(edge, {\n points: [\n section.startPoint,\n ...(section.bendPoints || []),\n section.endPoint,\n ],\n label: label\n ? {\n x: (label.x ?? 0) + (label.width ?? 0) / 2,\n y: (label.y ?? 0) + (label.height ?? 0) / 2,\n }\n : undefined,\n source: absolute(edge.source),\n target: absolute(edge.target),\n });\n });\n\n const depth = (node?: Node): number => {\n const parent = node && parentOf(node);\n return parent ? depth(nodesById.get(parent)) + 1 : 0;\n };\n\n return {\n // Parents must come before their children in the array for React Flow\n nodes: [...nodes]\n .sort((a, b) => depth(a) - depth(b))\n .map((node) => {\n const elkNode = laidOut.get(node.id);\n if (!elkNode) return node;\n return {\n ...node,\n origin: [0, 0] as [number, number],\n position: { x: elkNode.x ?? 0, y: elkNode.y ?? 0 },\n ...(groupIds.has(node.id) ||\n (GROUP_TYPES.has(node.type || \"\") &&\n typeof node.style?.width !== \"number\")\n ? {\n style: {\n ...node.style,\n width: elkNode.width,\n height: elkNode.height,\n },\n // Re-measured at its new size\n measured: undefined,\n }\n : {}),\n };\n }),\n edges: edges.map((edge) => {\n const route = routes.get(edge);\n return route ? { ...edge, data: { ...edge.data, route } } : edge;\n }),\n };\n};\n","import { MarkerType, type Edge, type Node } from \"@xyflow/react\";\nimport { COMMAND, EVENT, MESSAGE, QUERY } from \"../nodes/node-types\";\nimport { createEdge } from \"./utils/utils\";\nimport type { EdgeRoute } from \"./elk-layout\";\n\nconst MESSAGE_NODE_TYPES = [...MESSAGE, \"messageGroup\", \"messageGroupExpanded\"];\n\nexport const isMessageNode = (node: Node) =>\n MESSAGE_NODE_TYPES.includes(node.type || \"\");\n\nconst getCollection = (type = \"\") => {\n if (EVENT.includes(type)) return \"events\";\n if (COMMAND.includes(type)) return \"commands\";\n if (QUERY.includes(type)) return \"queries\";\n return \"messages\";\n};\n\n// Returns the messages a node represents, as [name, collection] pairs\nconst getMessages = (node: Node): [string, string][] => {\n const data = node.data as any;\n if (node.type === \"messageGroup\") {\n return (data?.messages || []).map(({ message }: any) => [\n message?.data?.name || message?.data?.id,\n getCollection(message?.collection),\n ]);\n }\n const message = data?.message;\n return message\n ? [[message.name || message.id, getCollection(node.type)]]\n : [];\n};\n\nconst VERBS: Record<string, string> = {\n events: \"publishes\",\n commands: \"invokes\",\n queries: \"requests\",\n};\n\n/**\n * Label for an edge carrying messages (name -> collection), e.g.\n * \"publishes\\nProduct Created\", \"publishes\\n3 events\", \"sends\\n3 messages\"\n */\nexport const getMessagesLabel = (messages: Map<string, string>) => {\n if (messages.size === 0) return undefined;\n const collections = new Set(messages.values());\n const collection =\n collections.size === 1 ? Array.from(collections)[0] : \"messages\";\n const verb = VERBS[collection] || \"sends\";\n const sent =\n messages.size === 1\n ? Array.from(messages.keys())[0]\n : `${messages.size} ${collection}`;\n return `${verb}\\n${sent}`;\n};\n\n// Messages and channels carry what's sent between the nodes either side of them\nexport const isCarrier = (node: Node) =>\n isMessageNode(node) || node.type === \"channels\";\n\nconst isCrossDomain = (edge: Edge) =>\n !!(edge.data as { crossDomain?: boolean } | undefined)?.crossDomain;\n\nconst getRoute = (edge: Edge) =>\n (edge.data as { route?: EdgeRoute } | undefined)?.route;\n\n/**\n * The route of an edge bridging hidden nodes: along the routes of the edges it\n * replaces (through where the hidden nodes were), labelled where the first\n * hidden node was. None unless they're all laid out.\n */\nconst joinRoutes = (routes: (EdgeRoute | undefined)[]) => {\n if (!routes.every((route) => route && route.points.length > 0)) return;\n const laidOut = routes as EdgeRoute[];\n const arrives = laidOut[0].points[laidOut[0].points.length - 1];\n const leaves = laidOut[1].points[0];\n return {\n points: laidOut.flatMap((route) => route.points),\n label: { x: (arrives.x + leaves.x) / 2, y: (arrives.y + leaves.y) / 2 },\n source: laidOut[0].source,\n target: laidOut[laidOut.length - 1].target,\n };\n};\n\n/**\n * Removes the nodes `isHidden` picks. Messages and channels removed are\n * bridged: the nodes either side of them are connected directly, labelled with\n * the messages between them. Other edges to removed nodes go with them.\n * e.g. hiding events, ServiceA -> OrderPlaced -> ServiceB becomes ServiceA -> ServiceB\n */\nexport const hideNodes = (\n nodes: Node[],\n edges: Edge[],\n isHidden: (node: Node) => boolean,\n) => {\n const hiddenNodes = new Map(\n nodes.filter(isHidden).map((node) => [node.id, node]),\n );\n if (hiddenNodes.size === 0) return { nodes, edges };\n\n const outgoingEdges = new Map<string, Edge[]>();\n for (const edge of edges) {\n const outgoing = outgoingEdges.get(edge.source) || [];\n outgoing.push(edge);\n outgoingEdges.set(edge.source, outgoing);\n }\n\n const keptEdges: Edge[] = [];\n const bridgedEdges = new Map<\n string,\n {\n source: string;\n target: string;\n messages: Map<string, string>;\n route?: EdgeRoute;\n crossDomain: boolean;\n }\n >();\n\n for (const edge of edges) {\n if (hiddenNodes.has(edge.source)) continue;\n const target = hiddenNodes.get(edge.target);\n if (!target) {\n keptEdges.push(edge);\n continue;\n }\n if (!isCarrier(target)) continue;\n\n // Follow the edge through hidden messages and channels until we reach\n // visible nodes, collecting the names of the messages along the way\n const stack = [\n {\n id: edge.target,\n messages: [] as [string, string][],\n routes: [getRoute(edge)],\n crossDomain: isCrossDomain(edge),\n },\n ];\n const visited = new Set<string>();\n while (stack.length > 0) {\n const current = stack.pop()!;\n if (visited.has(current.id)) continue;\n visited.add(current.id);\n const messages = [\n ...current.messages,\n ...getMessages(hiddenNodes.get(current.id)!),\n ];\n\n for (const next of outgoingEdges.get(current.id) || []) {\n const routes = [...current.routes, getRoute(next)];\n const crossDomain = current.crossDomain || isCrossDomain(next);\n const nextNode = hiddenNodes.get(next.target);\n if (nextNode) {\n if (isCarrier(nextNode))\n stack.push({ id: next.target, messages, routes, crossDomain });\n continue;\n }\n if (next.target === edge.source) continue;\n\n // Prefixed, so it can't clash with an edge directly between the two\n const id = `bridged-${edge.source}-${next.target}`;\n const bridged = bridgedEdges.get(id) || {\n source: edge.source,\n target: next.target,\n messages: new Map<string, string>(),\n route: joinRoutes(routes),\n crossDomain: false,\n };\n bridged.crossDomain ||= crossDomain;\n messages.forEach(([name, collection]) =>\n bridged.messages.set(name, collection),\n );\n bridgedEdges.set(id, bridged);\n }\n }\n }\n\n const newEdges = Array.from(bridgedEdges, ([id, bridged]) =>\n createEdge({\n id,\n source: bridged.source,\n target: bridged.target,\n label: getMessagesLabel(bridged.messages),\n // The same size arrow as the rest of the graph's edges\n markerEnd: {\n type: MarkerType.ArrowClosed,\n width: 20,\n height: 20,\n color: \"rgb(var(--ec-page-text-muted))\",\n },\n // Along the same route, and crossing domains if any edge it replaces did\n data: {\n ...(bridged.route ? { route: bridged.route } : {}),\n ...(bridged.crossDomain ? { crossDomain: true } : {}),\n },\n }),\n );\n\n return {\n nodes: nodes.filter((node) => !hiddenNodes.has(node.id)),\n edges: [...keptEdges, ...newEdges],\n };\n};\n\n/**\n * Removes message nodes (events, commands, queries) and the channels they\n * travel through, connecting the nodes on either side directly.\n * e.g. ServiceA -> OrderPlaced -> orders-topic -> ServiceB becomes ServiceA -> ServiceB\n */\nexport const hideMessageNodes = (nodes: Node[], edges: Edge[]) =>\n hideNodes(nodes, edges, isCarrier);\n","export const SERVICE = [\"service\", \"services\"];\nexport const AGENT = [\"agent\", \"agents\"];\nexport const AGENT_TOOL = [\"agentTool\", \"agent-tool\"];\nexport const EVENT = [\"event\", \"events\"];\nexport const QUERY = [\"queries\", \"query\", \"querie\"];\nexport const COMMAND = [\"command\", \"commands\"];\nexport const CHANNEL = [\"channel\", \"channels\"];\nexport const ACTOR = [\"actor\", \"actors\"];\nexport const DATA = [\"data\"];\nexport const VIEW = [\"view\"];\n\nexport const MESSAGE = [...EVENT, ...COMMAND, ...QUERY];\n"],"mappings":"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","names":["import_react","message"]}
|