@eventcatalog/visualiser 5.2.1 → 5.2.3

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/dist/index.mjs CHANGED
@@ -79,7 +79,7 @@ import {
79
79
  nodeConfigs,
80
80
  normalizeOwners,
81
81
  pauseEdgeAnimations
82
- } from "./chunk-QQUJTD6O.mjs";
82
+ } from "./chunk-K37IHEO3.mjs";
83
83
  import {
84
84
  ACTOR,
85
85
  AGENT,
@@ -103,7 +103,7 @@ import {
103
103
  getEdgeLabelForServiceAsTarget,
104
104
  getEdgeLabelSize,
105
105
  isFlowGroupBy
106
- } from "./chunk-P2DH6CFV.mjs";
106
+ } from "./chunk-N4AJCJPU.mjs";
107
107
  export {
108
108
  ACTOR,
109
109
  AGENT,
package/dist/layout.d.mts CHANGED
@@ -21,7 +21,7 @@ declare const hideNodes: (nodes: Node[], edges: Edge[], isHidden: (node: Node) =
21
21
  };
22
22
  /**
23
23
  * Removes message nodes (events, commands, queries) and the channels they
24
- * travel through, connecting the nodes on either side directly.
24
+ * travel through, connecting the nodes on either side directly, and agents' tools.
25
25
  * e.g. ServiceA -> OrderPlaced -> orders-topic -> ServiceB becomes ServiceA -> ServiceB
26
26
  */
27
27
  declare const hideMessageNodes: (nodes: Node[], edges: Edge[]) => {
package/dist/layout.d.ts CHANGED
@@ -20,7 +20,7 @@ declare const hideNodes: (nodes: Node[], edges: Edge[], isHidden: (node: Node) =
20
20
  };
21
21
  /**
22
22
  * Removes message nodes (events, commands, queries) and the channels they
23
- * travel through, connecting the nodes on either side directly.
23
+ * travel through, connecting the nodes on either side directly, and agents' tools.
24
24
  * e.g. ServiceA -> OrderPlaced -> orders-topic -> ServiceB becomes ServiceA -> ServiceB
25
25
  */
26
26
  declare const hideMessageNodes: (nodes: Node[], edges: Edge[]) => {
package/dist/layout.js CHANGED
@@ -314,6 +314,7 @@ var getMessagesLabel = (messages) => {
314
314
  ${sent}`;
315
315
  };
316
316
  var isCarrier = (node) => isMessageNode(node) || node.type === "channels";
317
+ var isLevel3Detail = (node) => isCarrier(node) || node.type === "agentTool";
317
318
  var isCrossDomain = (edge) => !!edge.data?.crossDomain;
318
319
  var getRoute = (edge) => edge.data?.route;
319
320
  var joinRoutes = (routes) => {
@@ -418,7 +419,7 @@ var hideNodes = (nodes, edges, isHidden) => {
418
419
  edges: [...keptEdges, ...newEdges]
419
420
  };
420
421
  };
421
- var hideMessageNodes = (nodes, edges) => hideNodes(nodes, edges, isCarrier);
422
+ var hideMessageNodes = (nodes, edges) => hideNodes(nodes, edges, isLevel3Detail);
422
423
 
423
424
  // src/utils/swimlanes.ts
424
425
  var SWIMLANE_NODE_TYPE = "swimlane";
@@ -1 +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","../src/utils/swimlanes.ts","../src/utils/export-mermaid.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 type CopiedEdgeRoute,\n} from \"./utils/elk-layout\";\nexport {\n hideMessageNodes,\n hideNodes,\n getMessagesLabel,\n isCarrier,\n isMessageNode,\n} from \"./utils/hide-messages\";\nexport {\n convertToMermaid,\n type MermaidExportOptions,\n} from \"./utils/export-mermaid\";\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 /**\n * Where the edge's source and target were laid out (top-left, in the flow),\n * and how tall: nodes often render shorter than the layout estimated\n */\n source?: { x: number; y: number; height?: number };\n target?: { x: number; y: number; height?: number };\n};\n\n/**\n * A route copied to another canvas (e.g. a diagram opened in Studio), which\n * places the nodes itself: where it goes, and how tall its nodes were laid out\n */\nexport type CopiedEdgeRoute = Pick<EdgeRoute, \"points\" | \"label\"> & {\n sourceHeight?: number;\n targetHeight?: 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 laidOutAt = (nodeId: string) => ({\n ...absolute(nodeId),\n height: laidOut.get(nodeId)?.height,\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: laidOutAt(edge.source),\n target: laidOutAt(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","import type { Edge, Node } from \"@xyflow/react\";\nimport { getNodeSize, layoutWithElk, type LayoutSize } from \"./elk-layout\";\n\n/** The size a node is laid out at (by default, as last rendered or estimated) */\ntype SizeOf = (node: Node) => LayoutSize;\n\n/** What a flow's swimlanes group its steps by */\nexport type SwimlaneGroupBy = \"domain\" | \"system\" | \"team\";\n/** What a flow's steps can be grouped by: one of those, or domains with their systems in them */\nexport type FlowGroupBy = SwimlaneGroupBy | \"domain-system\";\n/**\n * How the groups are shown: full-width lanes stacked top to bottom, or boxes\n * around their steps (like the pools of a process diagram), placed by the layout\n */\nexport type FlowGroupStyle = \"lanes\" | \"boxes\";\n\nexport const SWIMLANE_NODE_TYPE = \"swimlane\";\n\n/** Every way a flow's steps can be grouped */\nexport const FLOW_GROUP_BYS: FlowGroupBy[] = [\n \"domain\",\n \"system\",\n \"team\",\n \"domain-system\",\n];\nexport const isFlowGroupBy = (value: unknown): value is FlowGroupBy =>\n FLOW_GROUP_BYS.includes(value as FlowGroupBy);\nexport const isFlowGroupStyle = (value: unknown): value is FlowGroupStyle =>\n value === \"lanes\" || value === \"boxes\";\n\nexport const SWIMLANE_GROUP_BY_LABELS: Record<FlowGroupBy, string> = {\n domain: \"Domain\",\n system: \"System\",\n team: \"Team\",\n \"domain-system\": \"Domain, then system\",\n};\n\n/** The lanes a node's resource belongs to (`data.lanes`, set by the catalog) */\ntype NodeLanes = Partial<\n Record<SwimlaneGroupBy, { id: string; name: string }>\n> & { external?: boolean };\n\nexport type SwimlaneNodeData = {\n label: string;\n /** How the graph is grouped */\n groupBy: FlowGroupBy;\n /** What this group is (a domain, a system or a team), for its colour and icon */\n level: SwimlaneGroupBy;\n style: FlowGroupStyle;\n /** The lane holds actors and external systems, or steps with no lane */\n kind: \"lane\" | \"external\" | \"unassigned\";\n /** Its place from the top, so neighbouring lanes can be told apart */\n index: number;\n};\n\nconst EXTERNAL_LANE = \"__external\";\nconst UNASSIGNED_LANE = \"__unassigned\";\n\n// Room on the left of each lane for its name, around its nodes, and between rows\nexport const LANE_HEADER_WIDTH = 96;\nconst LANE_PADDING = { x: 48, y: 48 };\nconst ROW_GAP = 32;\nconst COLUMN_GAP = 24;\n\n// The lane's name runs down its header, next to an icon\nexport const LANE_LABEL = { fontSize: 20, iconSize: 20, gap: 10 };\n// Room above and below the name, so it never touches the lane's edges\nconst LANE_LABEL_PADDING = 40;\n// How long a name renders, roughly (bold, about 0.62em a character)\nconst labelLength = (label: string) =>\n LANE_LABEL.iconSize +\n LANE_LABEL.gap +\n label.length * LANE_LABEL.fontSize * 0.62;\n\nconst lanesOf = (node: Node) =>\n (node.data as { lanes?: NodeLanes } | undefined)?.lanes;\n\nexport const isSwimlaneNode = (node: Node) => node.type === SWIMLANE_NODE_TYPE;\n\n/** Whether any of the nodes belong to a group when grouped this way */\nexport const hasSwimlanes = (nodes: Node[], groupBy: FlowGroupBy): boolean =>\n groupBy === \"domain-system\"\n ? hasSwimlanes(nodes, \"domain\") && hasSwimlanes(nodes, \"system\")\n : nodes.some((node) => !!lanesOf(node)?.[groupBy]);\n\n/**\n * How many groups the nodes make when grouped this way (domains, for domains\n * with their systems in them). Actors, external systems and steps that only\n * take a neighbour's group aren't counted.\n */\nexport const countGroups = (nodes: Node[], groupBy: FlowGroupBy): number => {\n const level = groupBy === \"domain-system\" ? \"domain\" : groupBy;\n return new Set(\n nodes.map((node) => lanesOf(node)?.[level]?.id).filter(Boolean),\n ).size;\n};\n\n/**\n * Which lane each node goes in. Nodes whose resource belongs to a domain,\n * system or team (services, agents, data stores...) go in its lane; actors\n * and external systems in the external lane. Other nodes (messages, custom\n * steps, sub-flows) go in the lane of the step that leads to them, or, for\n * steps at the start of the flow, of the step they lead to. Resources that\n * don't belong to a group of this kind are in no lane.\n */\nexport const assignLanes = (\n nodes: Node[],\n edges: Edge[],\n groupBy: SwimlaneGroupBy,\n) => {\n const laneOf = new Map<string, string>();\n const names = new Map<string, string>();\n nodes.forEach((node) => {\n const lanes = lanesOf(node);\n const lane = lanes?.[groupBy];\n if (lane) {\n laneOf.set(node.id, lane.id);\n names.set(lane.id, lane.name);\n } else if (lanes?.external) {\n laneOf.set(node.id, EXTERNAL_LANE);\n }\n });\n\n const ids = new Set(nodes.map((node) => node.id));\n // Steps for resources belong where the catalog says (or nowhere), never\n // where a neighbour does: a service with a domain but no system isn't in\n // the system of the step before it\n const ownLanes = new Set(\n nodes.filter((node) => lanesOf(node)).map((node) => node.id),\n );\n const links = edges.filter(\n (edge) => ids.has(edge.source) && ids.has(edge.target),\n );\n // Take the lane of a neighbour until nothing changes (a chain of messages\n // and custom steps takes as many passes as it's long)\n const inherit = (from: \"source\" | \"target\", to: \"source\" | \"target\") => {\n let changed = true;\n while (changed) {\n changed = false;\n links.forEach((edge) => {\n const lane = laneOf.get(edge[from]);\n if (lane && !laneOf.has(edge[to]) && !ownLanes.has(edge[to])) {\n laneOf.set(edge[to], lane);\n changed = true;\n }\n });\n }\n };\n inherit(\"source\", \"target\");\n inherit(\"target\", \"source\");\n nodes.forEach((node) => {\n if (!laneOf.has(node.id)) laneOf.set(node.id, UNASSIGNED_LANE);\n });\n\n return { laneOf, names };\n};\n\ntype Box = { x: number; y: number; width: number; height: number };\n\n/**\n * Lays a laid out flow out again in swimlanes: one full-width band per lane,\n * stacked top to bottom in the order the flow first reaches them (external\n * first, unassigned last). Nodes keep their place along the flow (x) and are\n * stacked in rows inside their lane where they'd overlap. Lanes are group\n * nodes their nodes are children of, so edges are drawn over them. Edges lose\n * the route the layout gave them, and are drawn as smooth steps instead.\n *\n * Only top level nodes are put in lanes (children of an expanded sub-flow stay\n * in it).\n */\nexport const layoutSwimlanes = (\n { nodes, edges }: { nodes: Node[]; edges: Edge[] },\n groupBy: SwimlaneGroupBy,\n { sizeOf = getNodeSize }: { sizeOf?: SizeOf } = {},\n): { nodes: Node[]; edges: Edge[] } => {\n const topLevel = nodes.filter((node) => !node.parentId);\n if (topLevel.length === 0) return { nodes, edges };\n const { laneOf, names } = assignLanes(topLevel, edges, groupBy);\n\n const boxes = new Map<string, Box>(\n topLevel.map((node) => [node.id, { ...node.position, ...sizeOf(node) }]),\n );\n\n // Lanes in the order the flow reaches them, external first, unassigned last\n const order = (lane: string) =>\n lane === EXTERNAL_LANE\n ? -Infinity\n : lane === UNASSIGNED_LANE\n ? Infinity\n : Math.min(\n ...topLevel\n .filter((node) => laneOf.get(node.id) === lane)\n .map((node) => boxes.get(node.id)!.x),\n );\n const lanes = [...new Set(laneOf.values())].sort(\n (a, b) => order(a) - order(b),\n );\n\n const minX = Math.min(...[...boxes.values()].map((box) => box.x));\n const maxX = Math.max(...[...boxes.values()].map((box) => box.x + box.width));\n const laneX = minX - LANE_HEADER_WIDTH - LANE_PADDING.x;\n const laneWidth = maxX - laneX + LANE_PADDING.x;\n\n const laneNodes: Node[] = [];\n const positions = new Map<string, { x: number; y: number }>();\n let laneY = 0;\n lanes.forEach((lane, index) => {\n // Top to bottom as laid out, each in the first row it doesn't overlap\n const members = topLevel\n .filter((node) => laneOf.get(node.id) === lane)\n .sort((a, b) => boxes.get(a.id)!.y - boxes.get(b.id)!.y);\n const rows: { boxes: Box[]; height: number }[] = [];\n const rowOf = new Map<string, number>();\n members.forEach((node) => {\n const box = boxes.get(node.id)!;\n const overlaps = (other: Box) =>\n box.x < other.x + other.width + COLUMN_GAP &&\n other.x < box.x + box.width + COLUMN_GAP;\n let row = rows.findIndex((r) => !r.boxes.some(overlaps));\n if (row === -1) {\n rows.push({ boxes: [], height: 0 });\n row = rows.length - 1;\n }\n rows[row].boxes.push(box);\n rows[row].height = Math.max(rows[row].height, box.height);\n rowOf.set(node.id, row);\n });\n\n const label =\n lane === EXTERNAL_LANE\n ? \"External\"\n : lane === UNASSIGNED_LANE\n ? `No ${SWIMLANE_GROUP_BY_LABELS[groupBy].toLowerCase()}`\n : (names.get(lane) ?? lane);\n\n // Tall enough for its rows and for its name, with the rows centred\n const rowsHeight =\n rows.reduce((total, row) => total + row.height, 0) +\n ROW_GAP * (rows.length - 1);\n const laneHeight = Math.max(\n rowsHeight + LANE_PADDING.y * 2,\n labelLength(label) + LANE_LABEL_PADDING * 2,\n );\n const rowTops: number[] = [];\n let rowY = (laneHeight - rowsHeight) / 2;\n rows.forEach((row) => {\n rowTops.push(rowY);\n rowY += row.height + ROW_GAP;\n });\n\n const laneId = `swimlane-${lane}`;\n members.forEach((node) => {\n const box = boxes.get(node.id)!;\n const row = rowOf.get(node.id)!;\n positions.set(node.id, {\n x: box.x - laneX,\n // Centred in its row\n y: rowTops[row] + (rows[row].height - box.height) / 2,\n });\n });\n\n laneNodes.push({\n id: laneId,\n type: SWIMLANE_NODE_TYPE,\n position: { x: laneX, y: laneY },\n origin: [0, 0],\n style: { width: laneWidth, height: laneHeight },\n draggable: false,\n selectable: false,\n focusable: false,\n data: {\n label,\n groupBy,\n level: groupBy,\n style: \"lanes\",\n kind:\n lane === EXTERNAL_LANE\n ? \"external\"\n : lane === UNASSIGNED_LANE\n ? \"unassigned\"\n : \"lane\",\n index,\n } satisfies SwimlaneNodeData,\n });\n laneY += laneHeight;\n });\n\n return {\n // Lanes come before their nodes, as React Flow needs parents first\n nodes: [\n ...laneNodes,\n ...nodes.map((node) => {\n const position = positions.get(node.id);\n if (!position) return node;\n return {\n ...node,\n parentId: `swimlane-${laneOf.get(node.id)}`,\n origin: [0, 0] as [number, number],\n position,\n };\n }),\n ],\n edges: edges.map((edge) => {\n if (!(edge.data as { route?: unknown } | undefined)?.route) return edge;\n const { route: _route, ...data } = edge.data as Record<string, unknown>;\n return { ...edge, data };\n }),\n };\n};\n\n/** How a graph is grouped, if it is */\nexport const getGrouping = (\n nodes: Node[],\n): { groupBy: FlowGroupBy; style: FlowGroupStyle } | null => {\n const data = nodes.find(isSwimlaneNode)?.data as SwimlaneNodeData | undefined;\n return data ? { groupBy: data.groupBy, style: data.style } : null;\n};\n\n/**\n * Takes a graph out of its groups (lanes or boxes): the groups go, and their\n * nodes stay where they are (their positions no longer relative to their\n * group). For laying a grouped graph out again, which lays it out without them.\n */\nexport const removeSwimlanes = ({\n nodes,\n edges,\n}: {\n nodes: Node[];\n edges: Edge[];\n}): { nodes: Node[]; edges: Edge[] } => {\n const groups = new Map(\n nodes.filter(isSwimlaneNode).map((group) => [group.id, group]),\n );\n if (groups.size === 0) return { nodes, edges };\n // Where a group is in the flow (groups can be in groups)\n const offset = (id: string | undefined): { x: number; y: number } => {\n const group = id ? groups.get(id) : undefined;\n if (!group) return { x: 0, y: 0 };\n const parent = offset(group.parentId);\n return { x: parent.x + group.position.x, y: parent.y + group.position.y };\n };\n return {\n nodes: nodes\n .filter((node) => !groups.has(node.id))\n .map((node) => {\n if (!node.parentId || !groups.has(node.parentId)) return node;\n const { parentId, ...rest } = node;\n const at = offset(parentId);\n return {\n ...rest,\n position: { x: at.x + node.position.x, y: at.y + node.position.y },\n };\n }),\n edges,\n };\n};\n\n// Room around the steps in a box, and on its left for its name\nconst BOX_PADDING = 40;\n\n/**\n * Groups a flow's steps into boxes, one per domain, system or team (for\n * domains with their systems, a box per system inside its domain's box), and\n * lays the flow out again with them, so edges are routed around the boxes.\n * Steps go in the same group as they would in lanes; steps in no group stay\n * outside the boxes. Boxes are at least as tall as their name.\n *\n * Only top level nodes are put in boxes (children of an expanded sub-flow stay\n * in it).\n */\nexport const groupIntoBoxes = async (\n { nodes, edges }: { nodes: Node[]; edges: Edge[] },\n groupBy: FlowGroupBy,\n { sizeOf = getNodeSize }: { sizeOf?: SizeOf } = {},\n): Promise<{ nodes: Node[]; edges: Edge[] }> => {\n const topLevel = nodes.filter((node) => !node.parentId);\n const levels: SwimlaneGroupBy[] =\n groupBy === \"domain-system\" ? [\"domain\", \"system\"] : [groupBy];\n const assignments = levels.map((level) =>\n assignLanes(topLevel, edges, level),\n );\n\n const groups = new Map<string, Node>();\n const parentOf = new Map<string, string>();\n topLevel.forEach((node) => {\n let parentId: string | undefined;\n assignments.forEach(({ laneOf, names }, depth) => {\n const lane = laneOf.get(node.id)!;\n // External steps have one box, whatever they're grouped by\n if (lane === UNASSIGNED_LANE || (depth > 0 && lane === EXTERNAL_LANE))\n return;\n const level = levels[depth];\n const id = `${parentId ?? \"swimlane\"}__${level}-${lane}`;\n if (!groups.has(id)) {\n const label =\n lane === EXTERNAL_LANE ? \"External\" : (names.get(lane) ?? lane);\n const minHeight = labelLength(label) + LANE_LABEL_PADDING * 2;\n groups.set(id, {\n id,\n type: SWIMLANE_NODE_TYPE,\n ...(parentId && { parentId }),\n position: { x: 0, y: 0 },\n selectable: false,\n focusable: false,\n data: {\n label,\n groupBy,\n level,\n style: \"boxes\",\n kind: lane === EXTERNAL_LANE ? \"external\" : \"lane\",\n index: groups.size,\n // Room for its name on the left, and at least as tall as it\n layoutOptions: {\n \"elk.padding\": `[top=${BOX_PADDING},left=${LANE_HEADER_WIDTH + BOX_PADDING},bottom=${BOX_PADDING},right=${BOX_PADDING}]`,\n \"elk.nodeSize.constraints\": \"MINIMUM_SIZE\",\n \"elk.nodeSize.minimum\": `(0, ${Math.ceil(minHeight)})`,\n },\n } satisfies SwimlaneNodeData & { layoutOptions: object },\n });\n }\n parentId = id;\n });\n if (parentId) parentOf.set(node.id, parentId);\n });\n\n return layoutWithElk(\n {\n nodes: [\n ...groups.values(),\n ...nodes.map((node) => {\n const parentId = parentOf.get(node.id);\n return parentId ? { ...node, parentId } : node;\n }),\n ],\n edges,\n },\n { sizeOf },\n );\n};\n","import type { Node, Edge } from \"@xyflow/react\";\nimport { isSwimlaneNode } from \"./swimlanes\";\n\n/**\n * Mermaid Export Utility\n * Converts React Flow nodes and edges to Mermaid flowchart syntax. Used by the\n * visualiser's Mermaid view and by EventCatalog (the `.mermaid` routes, AI Chat\n * and the MCP server), so every level of a diagram exports the same way.\n */\n\nexport interface MermaidExportOptions {\n /** Include class definitions for styling */\n includeStyles?: boolean;\n /** Direction of the flowchart: LR (left-right), TB (top-bottom), etc. */\n direction?: \"LR\" | \"TB\" | \"RL\" | \"BT\";\n}\n\n/**\n * Mapping of node types to their Mermaid shape syntax\n * Format: [prefix, suffix]\n */\nconst NODE_SHAPE_MAP: Record<string, [string, string]> = {\n agents: [\"[[\", \"]]\"],\n agent: [\"[[\", \"]]\"],\n agentTool: [\"[\", \"]\"],\n \"agent-tool\": [\"[\", \"]\"],\n services: [\"[[\", \"]]\"], // stadium shape\n service: [\"[[\", \"]]\"],\n events: [\">\", \"]\"], // flag/asymmetric shape (message-like)\n event: [\">\", \"]\"],\n commands: [\">\", \"]\"], // flag/asymmetric shape (message-like)\n command: [\">\", \"]\"],\n queries: [\"{{\", \"}}\"], // hexagon\n query: [\"{{\", \"}}\"],\n // Parallelogram: what messages pass through, not a cylinder, which reads as\n // a database (and is what data stores are drawn as)\n channels: [\"[/\", \"/]\"],\n channel: [\"[/\", \"/]\"],\n domains: [\"[\", \"]\"], // rectangle\n domain: [\"[\", \"]\"],\n \"context-domain\": [\"[\", \"]\"], // another domain, shown as a single card\n flows: [\"([\", \"])\"], // stadium (rounded)\n flow: [\"([\", \"])\"],\n flowExpanded: [\"([\", \"])\"],\n systems: [\"[[\", \"]]\"], // a system shown as a single node\n system: [\"[[\", \"]]\"],\n step: [\"[\", \"]\"], // rectangle\n user: [\"((\", \"))\"], // circle\n actor: [\"((\", \"))\"], // circle\n \"context-actor\": [\"((\", \"))\"], // circle\n externalSystem: [\"[[\", \"]]\"], // stadium\n \"external-system\": [\"[[\", \"]]\"],\n data: [\"[(\", \")]\"], // cylinder (database)\n container: [\"[(\", \")]\"],\n \"data-product\": [\"[[\", \"]]\"], // stadium\n \"data-products\": [\"[[\", \"]]\"],\n entities: [\"[\", \"]\"], // rectangle\n entity: [\"[\", \"]\"],\n custom: [\"[\", \"]\"], // rectangle\n view: [\"[\", \"]\"], // rectangle\n note: [\"[\", \"]\"], // rectangle\n field: [\"[(\", \")]\"], // cylinder\n fields: [\"[(\", \")]\"], // cylinder\n messageGroup: [\"[[\", \"]]\"],\n messageGroupExpanded: [\"[[\", \"]]\"],\n};\n\n/**\n * Mermaid class definitions for styling different node types\n */\nconst NODE_STYLE_CLASSES: Record<string, string> = {\n agents: \"fill:#0ea5e9,stroke:#0369a1,color:#fff\",\n agent: \"fill:#0ea5e9,stroke:#0369a1,color:#fff\",\n agentTool: \"fill:#8b5cf6,stroke:#6d28d9,color:#fff\",\n \"agent-tool\": \"fill:#8b5cf6,stroke:#6d28d9,color:#fff\",\n services: \"fill:#ec4899,stroke:#be185d,color:#fff\",\n service: \"fill:#ec4899,stroke:#be185d,color:#fff\",\n events: \"fill:#f97316,stroke:#c2410c,color:#fff\",\n event: \"fill:#f97316,stroke:#c2410c,color:#fff\",\n commands: \"fill:#3b82f6,stroke:#1d4ed8,color:#fff\",\n command: \"fill:#3b82f6,stroke:#1d4ed8,color:#fff\",\n queries: \"fill:#22c55e,stroke:#15803d,color:#fff\",\n query: \"fill:#22c55e,stroke:#15803d,color:#fff\",\n channels: \"fill:#6b7280,stroke:#374151,color:#fff\",\n channel: \"fill:#6b7280,stroke:#374151,color:#fff\",\n domains: \"fill:#eab308,stroke:#a16207,color:#000\",\n domain: \"fill:#eab308,stroke:#a16207,color:#000\",\n \"context-domain\": \"fill:#eab308,stroke:#a16207,color:#000\",\n flows: \"fill:#14b8a6,stroke:#0f766e,color:#fff\",\n flow: \"fill:#14b8a6,stroke:#0f766e,color:#fff\",\n flowExpanded: \"fill:#14b8a6,stroke:#0f766e,color:#fff\",\n systems: \"fill:#8b5cf6,stroke:#6d28d9,color:#fff\",\n system: \"fill:#8b5cf6,stroke:#6d28d9,color:#fff\",\n step: \"fill:#374151,stroke:#1f2937,color:#fff\",\n user: \"fill:#8b5cf6,stroke:#6d28d9,color:#fff\",\n actor: \"fill:#eab308,stroke:#a16207,color:#000\",\n \"context-actor\": \"fill:#eab308,stroke:#a16207,color:#000\", // yellow, as actors are on the diagram\n externalSystem: \"fill:#ec4899,stroke:#be185d,color:#fff\",\n \"external-system\": \"fill:#ec4899,stroke:#be185d,color:#fff\",\n data: \"fill:#3b82f6,stroke:#1d4ed8,color:#fff\",\n container: \"fill:#3b82f6,stroke:#1d4ed8,color:#fff\",\n \"data-product\": \"fill:#6366f1,stroke:#4338ca,color:#fff\",\n \"data-products\": \"fill:#6366f1,stroke:#4338ca,color:#fff\",\n entities: \"fill:#6b7280,stroke:#374151,color:#fff\",\n entity: \"fill:#6b7280,stroke:#374151,color:#fff\",\n custom: \"fill:#9ca3af,stroke:#6b7280,color:#000\",\n view: \"fill:#9ca3af,stroke:#6b7280,color:#000\",\n note: \"fill:#fef3c7,stroke:#d97706,color:#000\",\n field: \"fill:#06b6d4,stroke:#0891b2,color:#fff\",\n fields: \"fill:#06b6d4,stroke:#0891b2,color:#fff\",\n messageGroup: \"fill:#7c3aed,stroke:#5b21b6,color:#fff\",\n messageGroupExpanded: \"fill:#7c3aed,stroke:#5b21b6,color:#fff\",\n};\n\n/** Node types that are boundaries around other nodes, rendered as subgraphs */\nconst GROUP_NODE_TYPES = new Set([\"system-group\", \"domain-group\", \"group\"]);\n\n/**\n * Classes for the boundaries (subgraphs): the domain viewed, its subdomains,\n * other domains, systems and the lanes a flow's steps are grouped into\n */\nconst GROUP_STYLE_CLASSES = {\n domainGroup:\n \"fill:#eab30814,stroke:#ca8a04,stroke-width:2px,stroke-dasharray:6 4,color:#a16207\",\n subdomainGroup:\n \"fill:#eab3080d,stroke:#ca8a04,stroke-dasharray:4 4,color:#a16207\",\n otherDomainGroup:\n \"fill:#9ca3af14,stroke:#9ca3af,stroke-dasharray:4 4,color:#6b7280\",\n systemGroup: \"fill:#8b5cf614,stroke:#8b5cf6,stroke-width:2px,color:#7c3aed\",\n laneGroup: \"fill:#9ca3af0d,stroke:#9ca3af,stroke-dasharray:4 4,color:#6b7280\",\n} as const;\n\ntype GroupClass = keyof typeof GROUP_STYLE_CLASSES;\n\n/** The resource the diagram is about */\nconst FOCUSED_STYLE = \"stroke-width:4px\";\n\n/** Edges to or from another domain, highlighted as the visualiser does */\nconst CROSS_DOMAIN_LINK_STYLE = \"stroke:#4f46e5,stroke-width:3px\";\n\n/** Words Mermaid reads as keywords, so they can't be node ids */\nconst RESERVED_IDS = new Set([\n \"end\",\n \"subgraph\",\n \"graph\",\n \"flowchart\",\n \"style\",\n \"class\",\n \"classdef\",\n \"click\",\n \"linkstyle\",\n \"call\",\n \"href\",\n]);\n\n/**\n * Sanitize a string to be a valid Mermaid node ID\n * Mermaid IDs should be alphanumeric with underscores\n */\nexport function sanitizeMermaidId(id: string): string {\n // Replace any non-alphanumeric characters (except underscores) with underscores\n // Also handle dots and hyphens which are common in version strings\n const sanitized = id.replace(/[^a-zA-Z0-9_]/g, \"_\");\n return RESERVED_IDS.has(sanitized.toLowerCase())\n ? `node_${sanitized}`\n : sanitized;\n}\n\n/**\n * Collapses whitespace in each line of a label (e.g. \"reads from \\n (postgres)\"\n * from the graph builders), dropping empty lines\n */\nfunction normaliseLines(label: string): string[] {\n return label\n .split(\"\\n\")\n .map((line) => line.replace(/\\s+/g, \" \").trim())\n .filter(Boolean);\n}\n\n/**\n * Escape special characters in Mermaid labels\n */\nfunction escapeMermaidLabel(label: string): string {\n // `#` first, as the escapes themselves start with it\n return label.replace(/#/g, \"#35;\").replace(/\"/g, \"#quot;\");\n}\n\n/**\n * Get the Mermaid shape syntax for a node type\n */\nexport function getMermaidNodeShape(type: string): [string, string] {\n return NODE_SHAPE_MAP[type] || [\"[\", \"]\"];\n}\n\n/**\n * Helper to format label with version\n */\nfunction formatLabelWithVersion(name: string, version?: string): string {\n if (version) {\n return `${name} (${version})`;\n }\n return name;\n}\n\n/** Name and version of a resource, whether flat or nested in `.data` (a collection entry) */\nfunction resourceLabel(resource: any, fallback: string): string {\n const name =\n resource?.name ||\n resource?.data?.name ||\n resource?.id ||\n resource?.data?.id;\n const version = resource?.version || resource?.data?.version;\n return formatLabelWithVersion(name || fallback, version);\n}\n\n/**\n * Extract the display label from a node based on its type and data\n * Includes version information when available\n */\nexport function getNodeLabel(node: Node): string {\n const { type } = node;\n const data = node.data as any;\n\n if (!data) return node.id;\n\n // Handle different node types and their data structures\n if (type === \"agents\" || type === \"agent\") {\n return resourceLabel(data.agent, node.id);\n }\n\n if (type === \"agentTool\" || type === \"agent-tool\") {\n const agentTool = data.agentTool;\n return agentTool?.name || agentTool?.id || node.id;\n }\n\n if (type === \"services\" || type === \"service\") {\n return resourceLabel(data.service, node.id);\n }\n\n if (\n type === \"events\" ||\n type === \"event\" ||\n type === \"commands\" ||\n type === \"command\" ||\n type === \"queries\" ||\n type === \"query\"\n ) {\n return resourceLabel(data.message, node.id);\n }\n\n if (type === \"channels\" || type === \"channel\") {\n return resourceLabel(data.channel, node.id);\n }\n\n // A domain: as a node, a card (e.g. another domain) or a boundary around its systems\n if (\n type === \"domains\" ||\n type === \"domain\" ||\n type === \"context-domain\" ||\n type === \"domain-group\" ||\n type === \"group\"\n ) {\n return resourceLabel(data.domain, node.id);\n }\n\n // A system: as a node, or a boundary around what's in it\n if (type === \"systems\" || type === \"system\" || type === \"system-group\") {\n return resourceLabel(data.system, node.id);\n }\n\n if (type === \"flows\" || type === \"flow\") {\n return resourceLabel(data.flow, node.id);\n }\n\n if (type === \"flowExpanded\") {\n return formatLabelWithVersion(data.flowName || node.id, data.version);\n }\n\n if (type === \"step\") {\n const step = data.step;\n return step?.title || step?.name || step?.id || node.id;\n }\n\n if (type === \"custom\") {\n return data.custom?.title || data.step?.title || data.label || node.id;\n }\n\n if (type === \"externalSystem\" || type === \"external-system\") {\n const system = data.externalSystem || data;\n return system?.name || system?.id || node.id;\n }\n\n if (type === \"data\" || type === \"container\") {\n const dataNode = data.data;\n return dataNode?.name || dataNode?.id || node.id;\n }\n\n if (type === \"data-product\" || type === \"data-products\") {\n return resourceLabel(data.dataProduct, node.id);\n }\n\n if (type === \"entities\" || type === \"entity\") {\n const entity = data.entity;\n return entity?.name || entity?.id || node.id;\n }\n\n if (type === \"view\") {\n return data.view?.name || data.view?.id || node.id;\n }\n\n if (type === \"note\") {\n return data.text || data.label || \"Note\";\n }\n\n if (type === \"field\" || type === \"fields\") {\n const name = data.name || node.id;\n return data.type ? `${name} (${data.type})` : name;\n }\n\n if (type === \"messageGroup\" || type === \"messageGroupExpanded\") {\n const groupName = data.groupName || node.id;\n const count = data.messageCount || 0;\n return `${groupName} (${count} messages)`;\n }\n\n // Fallback: try common data patterns (e.g. users, actors, swimlanes)\n return data.name || data.label || data.title || data.id || node.id;\n}\n\n/**\n * Extract label from an edge\n */\nfunction getEdgeLabel(edge: Edge): string | undefined {\n if (edge.label && typeof edge.label === \"string\") {\n return edge.label;\n }\n if (edge.data?.label && typeof edge.data.label === \"string\") {\n return edge.data.label;\n }\n return undefined;\n}\n\n/** Mermaid ids for the nodes, kept unique when different ids sanitize the same way */\nfunction getMermaidIds(nodes: Node[]): Map<string, string> {\n const mermaidIds = new Map<string, string>();\n const usedIds = new Set<string>();\n nodes.forEach((node) => {\n const base = sanitizeMermaidId(node.id);\n let id = base;\n for (let n = 2; usedIds.has(id); n++) id = `${base}_${n}`;\n usedIds.add(id);\n mermaidIds.set(node.id, id);\n });\n return mermaidIds;\n}\n\nconst isEntityNode = (node: Node) =>\n node.type === \"entities\" || node.type === \"entity\";\n\n/** An entity's property, as in its frontmatter */\ntype EntityProperty = {\n name: string;\n type: string;\n required?: boolean;\n references?: string;\n relationType?: string;\n items?: { type: string };\n};\n\n/** Relationships named only for their cardinality: labelled with the property instead */\nconst CARDINALITY_RELATIONS = new Set([\n \"hasOne\",\n \"hasMany\",\n \"belongsTo\",\n \"references\",\n]);\n\n/** External entities (from other domains) and aggregate roots, styled as on the diagram */\nconst ENTITY_STYLE_CLASSES = {\n externalEntity: \"fill:#9ca3af1a,stroke:#9ca3af,stroke-dasharray:4 4\",\n aggregateRoot: \"stroke-width:3px\",\n} as const;\n\n/**\n * An attribute's type or name, as Mermaid allows them: letters, digits,\n * hyphens, underscores, brackets and dots, starting with a letter\n */\nfunction sanitizeErToken(value: string, fallback: string): string {\n const token = value.trim().replace(/[^A-Za-z0-9_\\-[\\]().]/g, \"_\");\n return /^[A-Za-z_]/.test(token) ? token : `${fallback}_${token}`;\n}\n\n/**\n * Crow's foot notation for a relationship, from the entity holding the\n * property: it has many (an array, or `hasMany`), it has one, or (for\n * `belongsTo`, `references` and other relations) many of it point at one\n */\nfunction getErCardinality(property: EntityProperty): string {\n const required = !!property.required;\n if (property.relationType === \"hasMany\" || property.type === \"array\") {\n return required ? \"||--|{\" : \"||--o{\";\n }\n if (property.relationType === \"hasOne\") {\n return required ? \"||--||\" : \"||--o|\";\n }\n return required ? \"}o--||\" : \"}o--o|\";\n}\n\n/**\n * An entity map as a Mermaid entity relationship diagram: each entity with its\n * properties (the identifier as PK, references as FK), and a relationship for\n * each edge drawn between them, with its cardinality\n */\nexport function convertEntitiesToMermaid(\n nodes: Node[],\n edges: Edge[],\n { includeStyles = true }: Pick<MermaidExportOptions, \"includeStyles\"> = {},\n): string {\n const mermaidIds = getMermaidIds(nodes);\n const lines = [\"erDiagram\"];\n const classes = new Map<keyof typeof ENTITY_STYLE_CLASSES, string[]>();\n const addClass = (name: keyof typeof ENTITY_STYLE_CLASSES, id: string) =>\n classes.set(name, [...(classes.get(name) ?? []), id]);\n\n nodes.forEach((node) => {\n const data = node.data as any;\n const entity = data?.entity?.data ?? {};\n const id = mermaidIds.get(node.id)!;\n const name = entity.name || data?.label || node.id;\n // Entities from other domains say which domain they're in\n const label =\n data?.externalToDomain && data?.domainName\n ? `${name} (${data.domainName})`\n : name;\n const references = new Set<string>(data?.referencePropertyNames ?? []);\n const properties: EntityProperty[] = entity.properties ?? [];\n\n const attributes = properties.map((property) => {\n const type =\n property.type === \"array\" && property.items?.type\n ? `${property.items.type}[]`\n : property.type || \"unknown\";\n const keys = [\n property.name === entity.identifier && \"PK\",\n (property.references || references.has(property.name)) && \"FK\",\n ].filter(Boolean);\n return ` ${sanitizeErToken(type, \"t\")} ${sanitizeErToken(property.name, \"p\")}${keys.length ? ` ${keys.join(\", \")}` : \"\"}`;\n });\n\n const definition = ` ${id}[\"${escapeMermaidLabel(label)}\"]`;\n lines.push(\n ...(attributes.length\n ? [`${definition} {`, ...attributes, \" }\"]\n : [definition]),\n );\n if (data?.externalToDomain) addClass(\"externalEntity\", id);\n if (entity.aggregateRoot) addClass(\"aggregateRoot\", id);\n });\n\n const nodesById = new Map(nodes.map((node) => [node.id, node]));\n const relationships = edges\n .filter(\n (edge) =>\n !edge.hidden &&\n mermaidIds.has(edge.source) &&\n mermaidIds.has(edge.target),\n )\n .map((edge) => {\n // The property the relationship is drawn from (its handle is `{name}-source`)\n const propertyName = edge.sourceHandle?.replace(/-source$/, \"\");\n const properties: EntityProperty[] =\n (nodesById.get(edge.source)?.data as any)?.entity?.data?.properties ??\n [];\n const property = properties.find((p) => p.name === propertyName);\n const relation = property?.relationType?.trim();\n const label =\n relation && !CARDINALITY_RELATIONS.has(relation)\n ? relation\n : propertyName || getEdgeLabel(edge) || \"references\";\n const cardinality = property ? getErCardinality(property) : \"}o--o|\";\n return ` ${mermaidIds.get(edge.source)} ${cardinality} ${mermaidIds.get(edge.target)} : \"${escapeMermaidLabel(label)}\"`;\n });\n\n if (relationships.length > 0) lines.push(\"\", ...relationships);\n\n if (includeStyles && classes.size > 0) {\n lines.push(\"\");\n classes.forEach((ids, name) => {\n lines.push(` classDef ${name} ${ENTITY_STYLE_CLASSES[name]}`);\n lines.push(` class ${ids.join(\",\")} ${name}`);\n });\n }\n\n return lines.join(\"\\n\");\n}\n\n/** The class of a boundary: what kind of domain it is, a system, or a flow's lane */\nfunction getGroupClass(node: Node): GroupClass {\n const data = node.data as any;\n if (isSwimlaneNode(node)) return \"laneGroup\";\n if (node.type === \"system-group\") return \"systemGroup\";\n if (data?.otherDomain) return \"otherDomainGroup\";\n if (data?.subdomain) return \"subdomainGroup\";\n return \"domainGroup\";\n}\n\nconst isCrossDomain = (edge: Edge) =>\n !!(edge.data as { crossDomain?: boolean } | undefined)?.crossDomain;\n\n/**\n * Convert React Flow nodes and edges to Mermaid flowchart syntax. Domains,\n * systems and a flow's lanes are subgraphs around what's in them (nested, as\n * on the diagram), so every level of a domain or system diagram keeps its\n * boundaries. Edges between domains are highlighted.\n */\nexport function convertToMermaid(\n graphNodes: Node[],\n graphEdges: Edge[],\n options: MermaidExportOptions = {},\n): string {\n const { includeStyles = true, direction = \"LR\" } = options;\n const nodes = graphNodes.filter((node) => !node.hidden);\n\n // An entity map is a data model, so it's an entity relationship diagram\n if (nodes.length > 0 && nodes.every(isEntityNode)) {\n return convertEntitiesToMermaid(nodes, graphEdges, { includeStyles });\n }\n\n const mermaidIds = getMermaidIds(nodes);\n\n const lines: string[] = [];\n\n // Add flowchart header\n lines.push(`flowchart ${direction}`);\n\n // Nodes inside a group (a domain, system or lane) are nested in a subgraph\n // for that group, so containment is kept\n const childrenOf = new Map<string, Node[]>();\n const topLevelNodes: Node[] = [];\n nodes.forEach((node) => {\n if (node.parentId && mermaidIds.has(node.parentId)) {\n childrenOf.set(node.parentId, [\n ...(childrenOf.get(node.parentId) ?? []),\n node,\n ]);\n } else {\n topLevelNodes.push(node);\n }\n });\n const isGroup = (node: Node) =>\n childrenOf.has(node.id) ||\n GROUP_NODE_TYPES.has(node.type ?? \"\") ||\n isSwimlaneNode(node);\n\n // Collect used node types and boundary classes for class definitions\n const usedTypes = new Set<string>();\n const groupsByClass = new Map<GroupClass, string[]>();\n const focusedIds: string[] = [];\n nodes.forEach((node) => {\n const id = mermaidIds.get(node.id)!;\n if (isGroup(node)) {\n const groupClass = getGroupClass(node);\n groupsByClass.set(groupClass, [\n ...(groupsByClass.get(groupClass) ?? []),\n id,\n ]);\n return;\n }\n if (node.type && NODE_STYLE_CLASSES[node.type]) usedTypes.add(node.type);\n if ((node.data as any)?.isFocused) focusedIds.push(id);\n });\n\n // Add class definitions if styles are enabled\n if (includeStyles) {\n lines.push(\"\");\n lines.push(\" %% Style definitions\");\n usedTypes.forEach((type) => {\n lines.push(\n ` classDef ${sanitizeMermaidId(type)} ${NODE_STYLE_CLASSES[type]}`,\n );\n });\n groupsByClass.forEach((_, groupClass) => {\n lines.push(\n ` classDef ${groupClass} ${GROUP_STYLE_CLASSES[groupClass]}`,\n );\n });\n if (focusedIds.length > 0) {\n lines.push(` classDef focused ${FOCUSED_STYLE}`);\n }\n }\n\n // Add node definitions\n lines.push(\"\");\n lines.push(\" %% Nodes\");\n\n const addNode = (node: Node, depth: number) => {\n const indent = \" \".repeat(depth);\n const id = mermaidIds.get(node.id)!;\n const label = escapeMermaidLabel(\n normaliseLines(getNodeLabel(node)).join(\"<br/>\"),\n );\n\n if (isGroup(node)) {\n lines.push(`${indent}subgraph ${id}[\"${label}\"]`);\n childrenOf.get(node.id)?.forEach((child) => addNode(child, depth + 1));\n lines.push(`${indent}end`);\n return;\n }\n\n const [prefix, suffix] = getMermaidNodeShape(node.type || \"custom\");\n let nodeLine = `${indent}${id}${prefix}\"${label}\"${suffix}`;\n\n // Add class reference if styles are enabled\n if (includeStyles && node.type && usedTypes.has(node.type)) {\n nodeLine += `:::${sanitizeMermaidId(node.type)}`;\n }\n\n lines.push(nodeLine);\n };\n\n topLevelNodes.forEach((node) => addNode(node, 1));\n\n // Add edge definitions, leaving out edges to nodes that aren't shown\n lines.push(\"\");\n lines.push(\" %% Edges\");\n\n const crossDomainLinks: number[] = [];\n graphEdges\n .filter(\n (edge) =>\n !edge.hidden &&\n mermaidIds.has(edge.source) &&\n mermaidIds.has(edge.target),\n )\n .forEach((edge, index) => {\n const sourceId = mermaidIds.get(edge.source)!;\n const targetId = mermaidIds.get(edge.target)!;\n const label = normaliseLines(getEdgeLabel(edge) ?? \"\").join(\" \");\n\n lines.push(\n label\n ? ` ${sourceId} -->|\"${escapeMermaidLabel(label)}\"| ${targetId}`\n : ` ${sourceId} --> ${targetId}`,\n );\n if (isCrossDomain(edge)) crossDomainLinks.push(index);\n });\n\n // Boundaries, the resource viewed and edges between domains\n if (includeStyles) {\n const styleLines: string[] = [];\n groupsByClass.forEach((ids, groupClass) => {\n styleLines.push(` class ${ids.join(\",\")} ${groupClass}`);\n });\n if (focusedIds.length > 0) {\n styleLines.push(` class ${focusedIds.join(\",\")} focused`);\n }\n if (crossDomainLinks.length > 0) {\n styleLines.push(\n ` linkStyle ${crossDomainLinks.join(\",\")} ${CROSS_DOMAIN_LINK_STYLE}`,\n );\n }\n if (styleLines.length > 0) {\n lines.push(\"\");\n lines.push(\" %% Boundaries and highlights\");\n lines.push(...styleLines);\n }\n }\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":["import_react","message","isCrossDomain"]}
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","../src/utils/swimlanes.ts","../src/utils/export-mermaid.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 type CopiedEdgeRoute,\n} from \"./utils/elk-layout\";\nexport {\n hideMessageNodes,\n hideNodes,\n getMessagesLabel,\n isCarrier,\n isMessageNode,\n} from \"./utils/hide-messages\";\nexport {\n convertToMermaid,\n type MermaidExportOptions,\n} from \"./utils/export-mermaid\";\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 /**\n * Where the edge's source and target were laid out (top-left, in the flow),\n * and how tall: nodes often render shorter than the layout estimated\n */\n source?: { x: number; y: number; height?: number };\n target?: { x: number; y: number; height?: number };\n};\n\n/**\n * A route copied to another canvas (e.g. a diagram opened in Studio), which\n * places the nodes itself: where it goes, and how tall its nodes were laid out\n */\nexport type CopiedEdgeRoute = Pick<EdgeRoute, \"points\" | \"label\"> & {\n sourceHeight?: number;\n targetHeight?: 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 laidOutAt = (nodeId: string) => ({\n ...absolute(nodeId),\n height: laidOut.get(nodeId)?.height,\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: laidOutAt(edge.source),\n target: laidOutAt(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\n// What level 2 leaves out: messages and channels, and agents' tools (how an\n// agent works, L3 detail like its messages)\nexport const isLevel3Detail = (node: Node) =>\n isCarrier(node) || node.type === \"agentTool\";\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, and agents' tools.\n * e.g. ServiceA -> OrderPlaced -> orders-topic -> ServiceB becomes ServiceA -> ServiceB\n */\nexport const hideMessageNodes = (nodes: Node[], edges: Edge[]) =>\n hideNodes(nodes, edges, isLevel3Detail);\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","import type { Edge, Node } from \"@xyflow/react\";\nimport { getNodeSize, layoutWithElk, type LayoutSize } from \"./elk-layout\";\n\n/** The size a node is laid out at (by default, as last rendered or estimated) */\ntype SizeOf = (node: Node) => LayoutSize;\n\n/** What a flow's swimlanes group its steps by */\nexport type SwimlaneGroupBy = \"domain\" | \"system\" | \"team\";\n/** What a flow's steps can be grouped by: one of those, or domains with their systems in them */\nexport type FlowGroupBy = SwimlaneGroupBy | \"domain-system\";\n/**\n * How the groups are shown: full-width lanes stacked top to bottom, or boxes\n * around their steps (like the pools of a process diagram), placed by the layout\n */\nexport type FlowGroupStyle = \"lanes\" | \"boxes\";\n\nexport const SWIMLANE_NODE_TYPE = \"swimlane\";\n\n/** Every way a flow's steps can be grouped */\nexport const FLOW_GROUP_BYS: FlowGroupBy[] = [\n \"domain\",\n \"system\",\n \"team\",\n \"domain-system\",\n];\nexport const isFlowGroupBy = (value: unknown): value is FlowGroupBy =>\n FLOW_GROUP_BYS.includes(value as FlowGroupBy);\nexport const isFlowGroupStyle = (value: unknown): value is FlowGroupStyle =>\n value === \"lanes\" || value === \"boxes\";\n\nexport const SWIMLANE_GROUP_BY_LABELS: Record<FlowGroupBy, string> = {\n domain: \"Domain\",\n system: \"System\",\n team: \"Team\",\n \"domain-system\": \"Domain, then system\",\n};\n\n/** The lanes a node's resource belongs to (`data.lanes`, set by the catalog) */\ntype NodeLanes = Partial<\n Record<SwimlaneGroupBy, { id: string; name: string }>\n> & { external?: boolean };\n\nexport type SwimlaneNodeData = {\n label: string;\n /** How the graph is grouped */\n groupBy: FlowGroupBy;\n /** What this group is (a domain, a system or a team), for its colour and icon */\n level: SwimlaneGroupBy;\n style: FlowGroupStyle;\n /** The lane holds actors and external systems, or steps with no lane */\n kind: \"lane\" | \"external\" | \"unassigned\";\n /** Its place from the top, so neighbouring lanes can be told apart */\n index: number;\n};\n\nconst EXTERNAL_LANE = \"__external\";\nconst UNASSIGNED_LANE = \"__unassigned\";\n\n// Room on the left of each lane for its name, around its nodes, and between rows\nexport const LANE_HEADER_WIDTH = 96;\nconst LANE_PADDING = { x: 48, y: 48 };\nconst ROW_GAP = 32;\nconst COLUMN_GAP = 24;\n\n// The lane's name runs down its header, next to an icon\nexport const LANE_LABEL = { fontSize: 20, iconSize: 20, gap: 10 };\n// Room above and below the name, so it never touches the lane's edges\nconst LANE_LABEL_PADDING = 40;\n// How long a name renders, roughly (bold, about 0.62em a character)\nconst labelLength = (label: string) =>\n LANE_LABEL.iconSize +\n LANE_LABEL.gap +\n label.length * LANE_LABEL.fontSize * 0.62;\n\nconst lanesOf = (node: Node) =>\n (node.data as { lanes?: NodeLanes } | undefined)?.lanes;\n\nexport const isSwimlaneNode = (node: Node) => node.type === SWIMLANE_NODE_TYPE;\n\n/** Whether any of the nodes belong to a group when grouped this way */\nexport const hasSwimlanes = (nodes: Node[], groupBy: FlowGroupBy): boolean =>\n groupBy === \"domain-system\"\n ? hasSwimlanes(nodes, \"domain\") && hasSwimlanes(nodes, \"system\")\n : nodes.some((node) => !!lanesOf(node)?.[groupBy]);\n\n/**\n * How many groups the nodes make when grouped this way (domains, for domains\n * with their systems in them). Actors, external systems and steps that only\n * take a neighbour's group aren't counted.\n */\nexport const countGroups = (nodes: Node[], groupBy: FlowGroupBy): number => {\n const level = groupBy === \"domain-system\" ? \"domain\" : groupBy;\n return new Set(\n nodes.map((node) => lanesOf(node)?.[level]?.id).filter(Boolean),\n ).size;\n};\n\n/**\n * Which lane each node goes in. Nodes whose resource belongs to a domain,\n * system or team (services, agents, data stores...) go in its lane; actors\n * and external systems in the external lane. Other nodes (messages, custom\n * steps, sub-flows) go in the lane of the step that leads to them, or, for\n * steps at the start of the flow, of the step they lead to. Resources that\n * don't belong to a group of this kind are in no lane.\n */\nexport const assignLanes = (\n nodes: Node[],\n edges: Edge[],\n groupBy: SwimlaneGroupBy,\n) => {\n const laneOf = new Map<string, string>();\n const names = new Map<string, string>();\n nodes.forEach((node) => {\n const lanes = lanesOf(node);\n const lane = lanes?.[groupBy];\n if (lane) {\n laneOf.set(node.id, lane.id);\n names.set(lane.id, lane.name);\n } else if (lanes?.external) {\n laneOf.set(node.id, EXTERNAL_LANE);\n }\n });\n\n const ids = new Set(nodes.map((node) => node.id));\n // Steps for resources belong where the catalog says (or nowhere), never\n // where a neighbour does: a service with a domain but no system isn't in\n // the system of the step before it\n const ownLanes = new Set(\n nodes.filter((node) => lanesOf(node)).map((node) => node.id),\n );\n const links = edges.filter(\n (edge) => ids.has(edge.source) && ids.has(edge.target),\n );\n // Take the lane of a neighbour until nothing changes (a chain of messages\n // and custom steps takes as many passes as it's long)\n const inherit = (from: \"source\" | \"target\", to: \"source\" | \"target\") => {\n let changed = true;\n while (changed) {\n changed = false;\n links.forEach((edge) => {\n const lane = laneOf.get(edge[from]);\n if (lane && !laneOf.has(edge[to]) && !ownLanes.has(edge[to])) {\n laneOf.set(edge[to], lane);\n changed = true;\n }\n });\n }\n };\n inherit(\"source\", \"target\");\n inherit(\"target\", \"source\");\n nodes.forEach((node) => {\n if (!laneOf.has(node.id)) laneOf.set(node.id, UNASSIGNED_LANE);\n });\n\n return { laneOf, names };\n};\n\ntype Box = { x: number; y: number; width: number; height: number };\n\n/**\n * Lays a laid out flow out again in swimlanes: one full-width band per lane,\n * stacked top to bottom in the order the flow first reaches them (external\n * first, unassigned last). Nodes keep their place along the flow (x) and are\n * stacked in rows inside their lane where they'd overlap. Lanes are group\n * nodes their nodes are children of, so edges are drawn over them. Edges lose\n * the route the layout gave them, and are drawn as smooth steps instead.\n *\n * Only top level nodes are put in lanes (children of an expanded sub-flow stay\n * in it).\n */\nexport const layoutSwimlanes = (\n { nodes, edges }: { nodes: Node[]; edges: Edge[] },\n groupBy: SwimlaneGroupBy,\n { sizeOf = getNodeSize }: { sizeOf?: SizeOf } = {},\n): { nodes: Node[]; edges: Edge[] } => {\n const topLevel = nodes.filter((node) => !node.parentId);\n if (topLevel.length === 0) return { nodes, edges };\n const { laneOf, names } = assignLanes(topLevel, edges, groupBy);\n\n const boxes = new Map<string, Box>(\n topLevel.map((node) => [node.id, { ...node.position, ...sizeOf(node) }]),\n );\n\n // Lanes in the order the flow reaches them, external first, unassigned last\n const order = (lane: string) =>\n lane === EXTERNAL_LANE\n ? -Infinity\n : lane === UNASSIGNED_LANE\n ? Infinity\n : Math.min(\n ...topLevel\n .filter((node) => laneOf.get(node.id) === lane)\n .map((node) => boxes.get(node.id)!.x),\n );\n const lanes = [...new Set(laneOf.values())].sort(\n (a, b) => order(a) - order(b),\n );\n\n const minX = Math.min(...[...boxes.values()].map((box) => box.x));\n const maxX = Math.max(...[...boxes.values()].map((box) => box.x + box.width));\n const laneX = minX - LANE_HEADER_WIDTH - LANE_PADDING.x;\n const laneWidth = maxX - laneX + LANE_PADDING.x;\n\n const laneNodes: Node[] = [];\n const positions = new Map<string, { x: number; y: number }>();\n let laneY = 0;\n lanes.forEach((lane, index) => {\n // Top to bottom as laid out, each in the first row it doesn't overlap\n const members = topLevel\n .filter((node) => laneOf.get(node.id) === lane)\n .sort((a, b) => boxes.get(a.id)!.y - boxes.get(b.id)!.y);\n const rows: { boxes: Box[]; height: number }[] = [];\n const rowOf = new Map<string, number>();\n members.forEach((node) => {\n const box = boxes.get(node.id)!;\n const overlaps = (other: Box) =>\n box.x < other.x + other.width + COLUMN_GAP &&\n other.x < box.x + box.width + COLUMN_GAP;\n let row = rows.findIndex((r) => !r.boxes.some(overlaps));\n if (row === -1) {\n rows.push({ boxes: [], height: 0 });\n row = rows.length - 1;\n }\n rows[row].boxes.push(box);\n rows[row].height = Math.max(rows[row].height, box.height);\n rowOf.set(node.id, row);\n });\n\n const label =\n lane === EXTERNAL_LANE\n ? \"External\"\n : lane === UNASSIGNED_LANE\n ? `No ${SWIMLANE_GROUP_BY_LABELS[groupBy].toLowerCase()}`\n : (names.get(lane) ?? lane);\n\n // Tall enough for its rows and for its name, with the rows centred\n const rowsHeight =\n rows.reduce((total, row) => total + row.height, 0) +\n ROW_GAP * (rows.length - 1);\n const laneHeight = Math.max(\n rowsHeight + LANE_PADDING.y * 2,\n labelLength(label) + LANE_LABEL_PADDING * 2,\n );\n const rowTops: number[] = [];\n let rowY = (laneHeight - rowsHeight) / 2;\n rows.forEach((row) => {\n rowTops.push(rowY);\n rowY += row.height + ROW_GAP;\n });\n\n const laneId = `swimlane-${lane}`;\n members.forEach((node) => {\n const box = boxes.get(node.id)!;\n const row = rowOf.get(node.id)!;\n positions.set(node.id, {\n x: box.x - laneX,\n // Centred in its row\n y: rowTops[row] + (rows[row].height - box.height) / 2,\n });\n });\n\n laneNodes.push({\n id: laneId,\n type: SWIMLANE_NODE_TYPE,\n position: { x: laneX, y: laneY },\n origin: [0, 0],\n style: { width: laneWidth, height: laneHeight },\n draggable: false,\n selectable: false,\n focusable: false,\n data: {\n label,\n groupBy,\n level: groupBy,\n style: \"lanes\",\n kind:\n lane === EXTERNAL_LANE\n ? \"external\"\n : lane === UNASSIGNED_LANE\n ? \"unassigned\"\n : \"lane\",\n index,\n } satisfies SwimlaneNodeData,\n });\n laneY += laneHeight;\n });\n\n return {\n // Lanes come before their nodes, as React Flow needs parents first\n nodes: [\n ...laneNodes,\n ...nodes.map((node) => {\n const position = positions.get(node.id);\n if (!position) return node;\n return {\n ...node,\n parentId: `swimlane-${laneOf.get(node.id)}`,\n origin: [0, 0] as [number, number],\n position,\n };\n }),\n ],\n edges: edges.map((edge) => {\n if (!(edge.data as { route?: unknown } | undefined)?.route) return edge;\n const { route: _route, ...data } = edge.data as Record<string, unknown>;\n return { ...edge, data };\n }),\n };\n};\n\n/** How a graph is grouped, if it is */\nexport const getGrouping = (\n nodes: Node[],\n): { groupBy: FlowGroupBy; style: FlowGroupStyle } | null => {\n const data = nodes.find(isSwimlaneNode)?.data as SwimlaneNodeData | undefined;\n return data ? { groupBy: data.groupBy, style: data.style } : null;\n};\n\n/**\n * Takes a graph out of its groups (lanes or boxes): the groups go, and their\n * nodes stay where they are (their positions no longer relative to their\n * group). For laying a grouped graph out again, which lays it out without them.\n */\nexport const removeSwimlanes = ({\n nodes,\n edges,\n}: {\n nodes: Node[];\n edges: Edge[];\n}): { nodes: Node[]; edges: Edge[] } => {\n const groups = new Map(\n nodes.filter(isSwimlaneNode).map((group) => [group.id, group]),\n );\n if (groups.size === 0) return { nodes, edges };\n // Where a group is in the flow (groups can be in groups)\n const offset = (id: string | undefined): { x: number; y: number } => {\n const group = id ? groups.get(id) : undefined;\n if (!group) return { x: 0, y: 0 };\n const parent = offset(group.parentId);\n return { x: parent.x + group.position.x, y: parent.y + group.position.y };\n };\n return {\n nodes: nodes\n .filter((node) => !groups.has(node.id))\n .map((node) => {\n if (!node.parentId || !groups.has(node.parentId)) return node;\n const { parentId, ...rest } = node;\n const at = offset(parentId);\n return {\n ...rest,\n position: { x: at.x + node.position.x, y: at.y + node.position.y },\n };\n }),\n edges,\n };\n};\n\n// Room around the steps in a box, and on its left for its name\nconst BOX_PADDING = 40;\n\n/**\n * Groups a flow's steps into boxes, one per domain, system or team (for\n * domains with their systems, a box per system inside its domain's box), and\n * lays the flow out again with them, so edges are routed around the boxes.\n * Steps go in the same group as they would in lanes; steps in no group stay\n * outside the boxes. Boxes are at least as tall as their name.\n *\n * Only top level nodes are put in boxes (children of an expanded sub-flow stay\n * in it).\n */\nexport const groupIntoBoxes = async (\n { nodes, edges }: { nodes: Node[]; edges: Edge[] },\n groupBy: FlowGroupBy,\n { sizeOf = getNodeSize }: { sizeOf?: SizeOf } = {},\n): Promise<{ nodes: Node[]; edges: Edge[] }> => {\n const topLevel = nodes.filter((node) => !node.parentId);\n const levels: SwimlaneGroupBy[] =\n groupBy === \"domain-system\" ? [\"domain\", \"system\"] : [groupBy];\n const assignments = levels.map((level) =>\n assignLanes(topLevel, edges, level),\n );\n\n const groups = new Map<string, Node>();\n const parentOf = new Map<string, string>();\n topLevel.forEach((node) => {\n let parentId: string | undefined;\n assignments.forEach(({ laneOf, names }, depth) => {\n const lane = laneOf.get(node.id)!;\n // External steps have one box, whatever they're grouped by\n if (lane === UNASSIGNED_LANE || (depth > 0 && lane === EXTERNAL_LANE))\n return;\n const level = levels[depth];\n const id = `${parentId ?? \"swimlane\"}__${level}-${lane}`;\n if (!groups.has(id)) {\n const label =\n lane === EXTERNAL_LANE ? \"External\" : (names.get(lane) ?? lane);\n const minHeight = labelLength(label) + LANE_LABEL_PADDING * 2;\n groups.set(id, {\n id,\n type: SWIMLANE_NODE_TYPE,\n ...(parentId && { parentId }),\n position: { x: 0, y: 0 },\n selectable: false,\n focusable: false,\n data: {\n label,\n groupBy,\n level,\n style: \"boxes\",\n kind: lane === EXTERNAL_LANE ? \"external\" : \"lane\",\n index: groups.size,\n // Room for its name on the left, and at least as tall as it\n layoutOptions: {\n \"elk.padding\": `[top=${BOX_PADDING},left=${LANE_HEADER_WIDTH + BOX_PADDING},bottom=${BOX_PADDING},right=${BOX_PADDING}]`,\n \"elk.nodeSize.constraints\": \"MINIMUM_SIZE\",\n \"elk.nodeSize.minimum\": `(0, ${Math.ceil(minHeight)})`,\n },\n } satisfies SwimlaneNodeData & { layoutOptions: object },\n });\n }\n parentId = id;\n });\n if (parentId) parentOf.set(node.id, parentId);\n });\n\n return layoutWithElk(\n {\n nodes: [\n ...groups.values(),\n ...nodes.map((node) => {\n const parentId = parentOf.get(node.id);\n return parentId ? { ...node, parentId } : node;\n }),\n ],\n edges,\n },\n { sizeOf },\n );\n};\n","import type { Node, Edge } from \"@xyflow/react\";\nimport { isSwimlaneNode } from \"./swimlanes\";\n\n/**\n * Mermaid Export Utility\n * Converts React Flow nodes and edges to Mermaid flowchart syntax. Used by the\n * visualiser's Mermaid view and by EventCatalog (the `.mermaid` routes, AI Chat\n * and the MCP server), so every level of a diagram exports the same way.\n */\n\nexport interface MermaidExportOptions {\n /** Include class definitions for styling */\n includeStyles?: boolean;\n /** Direction of the flowchart: LR (left-right), TB (top-bottom), etc. */\n direction?: \"LR\" | \"TB\" | \"RL\" | \"BT\";\n}\n\n/**\n * Mapping of node types to their Mermaid shape syntax\n * Format: [prefix, suffix]\n */\nconst NODE_SHAPE_MAP: Record<string, [string, string]> = {\n agents: [\"[[\", \"]]\"],\n agent: [\"[[\", \"]]\"],\n agentTool: [\"[\", \"]\"],\n \"agent-tool\": [\"[\", \"]\"],\n services: [\"[[\", \"]]\"], // stadium shape\n service: [\"[[\", \"]]\"],\n events: [\">\", \"]\"], // flag/asymmetric shape (message-like)\n event: [\">\", \"]\"],\n commands: [\">\", \"]\"], // flag/asymmetric shape (message-like)\n command: [\">\", \"]\"],\n queries: [\"{{\", \"}}\"], // hexagon\n query: [\"{{\", \"}}\"],\n // Parallelogram: what messages pass through, not a cylinder, which reads as\n // a database (and is what data stores are drawn as)\n channels: [\"[/\", \"/]\"],\n channel: [\"[/\", \"/]\"],\n domains: [\"[\", \"]\"], // rectangle\n domain: [\"[\", \"]\"],\n \"context-domain\": [\"[\", \"]\"], // another domain, shown as a single card\n flows: [\"([\", \"])\"], // stadium (rounded)\n flow: [\"([\", \"])\"],\n flowExpanded: [\"([\", \"])\"],\n systems: [\"[[\", \"]]\"], // a system shown as a single node\n system: [\"[[\", \"]]\"],\n step: [\"[\", \"]\"], // rectangle\n user: [\"((\", \"))\"], // circle\n actor: [\"((\", \"))\"], // circle\n \"context-actor\": [\"((\", \"))\"], // circle\n externalSystem: [\"[[\", \"]]\"], // stadium\n \"external-system\": [\"[[\", \"]]\"],\n data: [\"[(\", \")]\"], // cylinder (database)\n container: [\"[(\", \")]\"],\n \"data-product\": [\"[[\", \"]]\"], // stadium\n \"data-products\": [\"[[\", \"]]\"],\n entities: [\"[\", \"]\"], // rectangle\n entity: [\"[\", \"]\"],\n custom: [\"[\", \"]\"], // rectangle\n view: [\"[\", \"]\"], // rectangle\n note: [\"[\", \"]\"], // rectangle\n field: [\"[(\", \")]\"], // cylinder\n fields: [\"[(\", \")]\"], // cylinder\n messageGroup: [\"[[\", \"]]\"],\n messageGroupExpanded: [\"[[\", \"]]\"],\n};\n\n/**\n * Mermaid class definitions for styling different node types\n */\nconst NODE_STYLE_CLASSES: Record<string, string> = {\n agents: \"fill:#0ea5e9,stroke:#0369a1,color:#fff\",\n agent: \"fill:#0ea5e9,stroke:#0369a1,color:#fff\",\n agentTool: \"fill:#8b5cf6,stroke:#6d28d9,color:#fff\",\n \"agent-tool\": \"fill:#8b5cf6,stroke:#6d28d9,color:#fff\",\n services: \"fill:#ec4899,stroke:#be185d,color:#fff\",\n service: \"fill:#ec4899,stroke:#be185d,color:#fff\",\n events: \"fill:#f97316,stroke:#c2410c,color:#fff\",\n event: \"fill:#f97316,stroke:#c2410c,color:#fff\",\n commands: \"fill:#3b82f6,stroke:#1d4ed8,color:#fff\",\n command: \"fill:#3b82f6,stroke:#1d4ed8,color:#fff\",\n queries: \"fill:#22c55e,stroke:#15803d,color:#fff\",\n query: \"fill:#22c55e,stroke:#15803d,color:#fff\",\n channels: \"fill:#6b7280,stroke:#374151,color:#fff\",\n channel: \"fill:#6b7280,stroke:#374151,color:#fff\",\n domains: \"fill:#eab308,stroke:#a16207,color:#000\",\n domain: \"fill:#eab308,stroke:#a16207,color:#000\",\n \"context-domain\": \"fill:#eab308,stroke:#a16207,color:#000\",\n flows: \"fill:#14b8a6,stroke:#0f766e,color:#fff\",\n flow: \"fill:#14b8a6,stroke:#0f766e,color:#fff\",\n flowExpanded: \"fill:#14b8a6,stroke:#0f766e,color:#fff\",\n systems: \"fill:#8b5cf6,stroke:#6d28d9,color:#fff\",\n system: \"fill:#8b5cf6,stroke:#6d28d9,color:#fff\",\n step: \"fill:#374151,stroke:#1f2937,color:#fff\",\n user: \"fill:#8b5cf6,stroke:#6d28d9,color:#fff\",\n actor: \"fill:#eab308,stroke:#a16207,color:#000\",\n \"context-actor\": \"fill:#eab308,stroke:#a16207,color:#000\", // yellow, as actors are on the diagram\n externalSystem: \"fill:#ec4899,stroke:#be185d,color:#fff\",\n \"external-system\": \"fill:#ec4899,stroke:#be185d,color:#fff\",\n data: \"fill:#3b82f6,stroke:#1d4ed8,color:#fff\",\n container: \"fill:#3b82f6,stroke:#1d4ed8,color:#fff\",\n \"data-product\": \"fill:#6366f1,stroke:#4338ca,color:#fff\",\n \"data-products\": \"fill:#6366f1,stroke:#4338ca,color:#fff\",\n entities: \"fill:#6b7280,stroke:#374151,color:#fff\",\n entity: \"fill:#6b7280,stroke:#374151,color:#fff\",\n custom: \"fill:#9ca3af,stroke:#6b7280,color:#000\",\n view: \"fill:#9ca3af,stroke:#6b7280,color:#000\",\n note: \"fill:#fef3c7,stroke:#d97706,color:#000\",\n field: \"fill:#06b6d4,stroke:#0891b2,color:#fff\",\n fields: \"fill:#06b6d4,stroke:#0891b2,color:#fff\",\n messageGroup: \"fill:#7c3aed,stroke:#5b21b6,color:#fff\",\n messageGroupExpanded: \"fill:#7c3aed,stroke:#5b21b6,color:#fff\",\n};\n\n/** Node types that are boundaries around other nodes, rendered as subgraphs */\nconst GROUP_NODE_TYPES = new Set([\"system-group\", \"domain-group\", \"group\"]);\n\n/**\n * Classes for the boundaries (subgraphs): the domain viewed, its subdomains,\n * other domains, systems and the lanes a flow's steps are grouped into\n */\nconst GROUP_STYLE_CLASSES = {\n domainGroup:\n \"fill:#eab30814,stroke:#ca8a04,stroke-width:2px,stroke-dasharray:6 4,color:#a16207\",\n subdomainGroup:\n \"fill:#eab3080d,stroke:#ca8a04,stroke-dasharray:4 4,color:#a16207\",\n otherDomainGroup:\n \"fill:#9ca3af14,stroke:#9ca3af,stroke-dasharray:4 4,color:#6b7280\",\n systemGroup: \"fill:#8b5cf614,stroke:#8b5cf6,stroke-width:2px,color:#7c3aed\",\n laneGroup: \"fill:#9ca3af0d,stroke:#9ca3af,stroke-dasharray:4 4,color:#6b7280\",\n} as const;\n\ntype GroupClass = keyof typeof GROUP_STYLE_CLASSES;\n\n/** The resource the diagram is about */\nconst FOCUSED_STYLE = \"stroke-width:4px\";\n\n/** Edges to or from another domain, highlighted as the visualiser does */\nconst CROSS_DOMAIN_LINK_STYLE = \"stroke:#4f46e5,stroke-width:3px\";\n\n/** Words Mermaid reads as keywords, so they can't be node ids */\nconst RESERVED_IDS = new Set([\n \"end\",\n \"subgraph\",\n \"graph\",\n \"flowchart\",\n \"style\",\n \"class\",\n \"classdef\",\n \"click\",\n \"linkstyle\",\n \"call\",\n \"href\",\n]);\n\n/**\n * Sanitize a string to be a valid Mermaid node ID\n * Mermaid IDs should be alphanumeric with underscores\n */\nexport function sanitizeMermaidId(id: string): string {\n // Replace any non-alphanumeric characters (except underscores) with underscores\n // Also handle dots and hyphens which are common in version strings\n const sanitized = id.replace(/[^a-zA-Z0-9_]/g, \"_\");\n return RESERVED_IDS.has(sanitized.toLowerCase())\n ? `node_${sanitized}`\n : sanitized;\n}\n\n/**\n * Collapses whitespace in each line of a label (e.g. \"reads from \\n (postgres)\"\n * from the graph builders), dropping empty lines\n */\nfunction normaliseLines(label: string): string[] {\n return label\n .split(\"\\n\")\n .map((line) => line.replace(/\\s+/g, \" \").trim())\n .filter(Boolean);\n}\n\n/**\n * Escape special characters in Mermaid labels\n */\nfunction escapeMermaidLabel(label: string): string {\n // `#` first, as the escapes themselves start with it\n return label.replace(/#/g, \"#35;\").replace(/\"/g, \"#quot;\");\n}\n\n/**\n * Get the Mermaid shape syntax for a node type\n */\nexport function getMermaidNodeShape(type: string): [string, string] {\n return NODE_SHAPE_MAP[type] || [\"[\", \"]\"];\n}\n\n/**\n * Helper to format label with version\n */\nfunction formatLabelWithVersion(name: string, version?: string): string {\n if (version) {\n return `${name} (${version})`;\n }\n return name;\n}\n\n/** Name and version of a resource, whether flat or nested in `.data` (a collection entry) */\nfunction resourceLabel(resource: any, fallback: string): string {\n const name =\n resource?.name ||\n resource?.data?.name ||\n resource?.id ||\n resource?.data?.id;\n const version = resource?.version || resource?.data?.version;\n return formatLabelWithVersion(name || fallback, version);\n}\n\n/**\n * Extract the display label from a node based on its type and data\n * Includes version information when available\n */\nexport function getNodeLabel(node: Node): string {\n const { type } = node;\n const data = node.data as any;\n\n if (!data) return node.id;\n\n // Handle different node types and their data structures\n if (type === \"agents\" || type === \"agent\") {\n return resourceLabel(data.agent, node.id);\n }\n\n if (type === \"agentTool\" || type === \"agent-tool\") {\n const agentTool = data.agentTool;\n return agentTool?.name || agentTool?.id || node.id;\n }\n\n if (type === \"services\" || type === \"service\") {\n return resourceLabel(data.service, node.id);\n }\n\n if (\n type === \"events\" ||\n type === \"event\" ||\n type === \"commands\" ||\n type === \"command\" ||\n type === \"queries\" ||\n type === \"query\"\n ) {\n return resourceLabel(data.message, node.id);\n }\n\n if (type === \"channels\" || type === \"channel\") {\n return resourceLabel(data.channel, node.id);\n }\n\n // A domain: as a node, a card (e.g. another domain) or a boundary around its systems\n if (\n type === \"domains\" ||\n type === \"domain\" ||\n type === \"context-domain\" ||\n type === \"domain-group\" ||\n type === \"group\"\n ) {\n return resourceLabel(data.domain, node.id);\n }\n\n // A system: as a node, or a boundary around what's in it\n if (type === \"systems\" || type === \"system\" || type === \"system-group\") {\n return resourceLabel(data.system, node.id);\n }\n\n if (type === \"flows\" || type === \"flow\") {\n return resourceLabel(data.flow, node.id);\n }\n\n if (type === \"flowExpanded\") {\n return formatLabelWithVersion(data.flowName || node.id, data.version);\n }\n\n if (type === \"step\") {\n const step = data.step;\n return step?.title || step?.name || step?.id || node.id;\n }\n\n if (type === \"custom\") {\n return data.custom?.title || data.step?.title || data.label || node.id;\n }\n\n if (type === \"externalSystem\" || type === \"external-system\") {\n const system = data.externalSystem || data;\n return system?.name || system?.id || node.id;\n }\n\n if (type === \"data\" || type === \"container\") {\n const dataNode = data.data;\n return dataNode?.name || dataNode?.id || node.id;\n }\n\n if (type === \"data-product\" || type === \"data-products\") {\n return resourceLabel(data.dataProduct, node.id);\n }\n\n if (type === \"entities\" || type === \"entity\") {\n const entity = data.entity;\n return entity?.name || entity?.id || node.id;\n }\n\n if (type === \"view\") {\n return data.view?.name || data.view?.id || node.id;\n }\n\n if (type === \"note\") {\n return data.text || data.label || \"Note\";\n }\n\n if (type === \"field\" || type === \"fields\") {\n const name = data.name || node.id;\n return data.type ? `${name} (${data.type})` : name;\n }\n\n if (type === \"messageGroup\" || type === \"messageGroupExpanded\") {\n const groupName = data.groupName || node.id;\n const count = data.messageCount || 0;\n return `${groupName} (${count} messages)`;\n }\n\n // Fallback: try common data patterns (e.g. users, actors, swimlanes)\n return data.name || data.label || data.title || data.id || node.id;\n}\n\n/**\n * Extract label from an edge\n */\nfunction getEdgeLabel(edge: Edge): string | undefined {\n if (edge.label && typeof edge.label === \"string\") {\n return edge.label;\n }\n if (edge.data?.label && typeof edge.data.label === \"string\") {\n return edge.data.label;\n }\n return undefined;\n}\n\n/** Mermaid ids for the nodes, kept unique when different ids sanitize the same way */\nfunction getMermaidIds(nodes: Node[]): Map<string, string> {\n const mermaidIds = new Map<string, string>();\n const usedIds = new Set<string>();\n nodes.forEach((node) => {\n const base = sanitizeMermaidId(node.id);\n let id = base;\n for (let n = 2; usedIds.has(id); n++) id = `${base}_${n}`;\n usedIds.add(id);\n mermaidIds.set(node.id, id);\n });\n return mermaidIds;\n}\n\nconst isEntityNode = (node: Node) =>\n node.type === \"entities\" || node.type === \"entity\";\n\n/** An entity's property, as in its frontmatter */\ntype EntityProperty = {\n name: string;\n type: string;\n required?: boolean;\n references?: string;\n relationType?: string;\n items?: { type: string };\n};\n\n/** Relationships named only for their cardinality: labelled with the property instead */\nconst CARDINALITY_RELATIONS = new Set([\n \"hasOne\",\n \"hasMany\",\n \"belongsTo\",\n \"references\",\n]);\n\n/** External entities (from other domains) and aggregate roots, styled as on the diagram */\nconst ENTITY_STYLE_CLASSES = {\n externalEntity: \"fill:#9ca3af1a,stroke:#9ca3af,stroke-dasharray:4 4\",\n aggregateRoot: \"stroke-width:3px\",\n} as const;\n\n/**\n * An attribute's type or name, as Mermaid allows them: letters, digits,\n * hyphens, underscores, brackets and dots, starting with a letter\n */\nfunction sanitizeErToken(value: string, fallback: string): string {\n const token = value.trim().replace(/[^A-Za-z0-9_\\-[\\]().]/g, \"_\");\n return /^[A-Za-z_]/.test(token) ? token : `${fallback}_${token}`;\n}\n\n/**\n * Crow's foot notation for a relationship, from the entity holding the\n * property: it has many (an array, or `hasMany`), it has one, or (for\n * `belongsTo`, `references` and other relations) many of it point at one\n */\nfunction getErCardinality(property: EntityProperty): string {\n const required = !!property.required;\n if (property.relationType === \"hasMany\" || property.type === \"array\") {\n return required ? \"||--|{\" : \"||--o{\";\n }\n if (property.relationType === \"hasOne\") {\n return required ? \"||--||\" : \"||--o|\";\n }\n return required ? \"}o--||\" : \"}o--o|\";\n}\n\n/**\n * An entity map as a Mermaid entity relationship diagram: each entity with its\n * properties (the identifier as PK, references as FK), and a relationship for\n * each edge drawn between them, with its cardinality\n */\nexport function convertEntitiesToMermaid(\n nodes: Node[],\n edges: Edge[],\n { includeStyles = true }: Pick<MermaidExportOptions, \"includeStyles\"> = {},\n): string {\n const mermaidIds = getMermaidIds(nodes);\n const lines = [\"erDiagram\"];\n const classes = new Map<keyof typeof ENTITY_STYLE_CLASSES, string[]>();\n const addClass = (name: keyof typeof ENTITY_STYLE_CLASSES, id: string) =>\n classes.set(name, [...(classes.get(name) ?? []), id]);\n\n nodes.forEach((node) => {\n const data = node.data as any;\n const entity = data?.entity?.data ?? {};\n const id = mermaidIds.get(node.id)!;\n const name = entity.name || data?.label || node.id;\n // Entities from other domains say which domain they're in\n const label =\n data?.externalToDomain && data?.domainName\n ? `${name} (${data.domainName})`\n : name;\n const references = new Set<string>(data?.referencePropertyNames ?? []);\n const properties: EntityProperty[] = entity.properties ?? [];\n\n const attributes = properties.map((property) => {\n const type =\n property.type === \"array\" && property.items?.type\n ? `${property.items.type}[]`\n : property.type || \"unknown\";\n const keys = [\n property.name === entity.identifier && \"PK\",\n (property.references || references.has(property.name)) && \"FK\",\n ].filter(Boolean);\n return ` ${sanitizeErToken(type, \"t\")} ${sanitizeErToken(property.name, \"p\")}${keys.length ? ` ${keys.join(\", \")}` : \"\"}`;\n });\n\n const definition = ` ${id}[\"${escapeMermaidLabel(label)}\"]`;\n lines.push(\n ...(attributes.length\n ? [`${definition} {`, ...attributes, \" }\"]\n : [definition]),\n );\n if (data?.externalToDomain) addClass(\"externalEntity\", id);\n if (entity.aggregateRoot) addClass(\"aggregateRoot\", id);\n });\n\n const nodesById = new Map(nodes.map((node) => [node.id, node]));\n const relationships = edges\n .filter(\n (edge) =>\n !edge.hidden &&\n mermaidIds.has(edge.source) &&\n mermaidIds.has(edge.target),\n )\n .map((edge) => {\n // The property the relationship is drawn from (its handle is `{name}-source`)\n const propertyName = edge.sourceHandle?.replace(/-source$/, \"\");\n const properties: EntityProperty[] =\n (nodesById.get(edge.source)?.data as any)?.entity?.data?.properties ??\n [];\n const property = properties.find((p) => p.name === propertyName);\n const relation = property?.relationType?.trim();\n const label =\n relation && !CARDINALITY_RELATIONS.has(relation)\n ? relation\n : propertyName || getEdgeLabel(edge) || \"references\";\n const cardinality = property ? getErCardinality(property) : \"}o--o|\";\n return ` ${mermaidIds.get(edge.source)} ${cardinality} ${mermaidIds.get(edge.target)} : \"${escapeMermaidLabel(label)}\"`;\n });\n\n if (relationships.length > 0) lines.push(\"\", ...relationships);\n\n if (includeStyles && classes.size > 0) {\n lines.push(\"\");\n classes.forEach((ids, name) => {\n lines.push(` classDef ${name} ${ENTITY_STYLE_CLASSES[name]}`);\n lines.push(` class ${ids.join(\",\")} ${name}`);\n });\n }\n\n return lines.join(\"\\n\");\n}\n\n/** The class of a boundary: what kind of domain it is, a system, or a flow's lane */\nfunction getGroupClass(node: Node): GroupClass {\n const data = node.data as any;\n if (isSwimlaneNode(node)) return \"laneGroup\";\n if (node.type === \"system-group\") return \"systemGroup\";\n if (data?.otherDomain) return \"otherDomainGroup\";\n if (data?.subdomain) return \"subdomainGroup\";\n return \"domainGroup\";\n}\n\nconst isCrossDomain = (edge: Edge) =>\n !!(edge.data as { crossDomain?: boolean } | undefined)?.crossDomain;\n\n/**\n * Convert React Flow nodes and edges to Mermaid flowchart syntax. Domains,\n * systems and a flow's lanes are subgraphs around what's in them (nested, as\n * on the diagram), so every level of a domain or system diagram keeps its\n * boundaries. Edges between domains are highlighted.\n */\nexport function convertToMermaid(\n graphNodes: Node[],\n graphEdges: Edge[],\n options: MermaidExportOptions = {},\n): string {\n const { includeStyles = true, direction = \"LR\" } = options;\n const nodes = graphNodes.filter((node) => !node.hidden);\n\n // An entity map is a data model, so it's an entity relationship diagram\n if (nodes.length > 0 && nodes.every(isEntityNode)) {\n return convertEntitiesToMermaid(nodes, graphEdges, { includeStyles });\n }\n\n const mermaidIds = getMermaidIds(nodes);\n\n const lines: string[] = [];\n\n // Add flowchart header\n lines.push(`flowchart ${direction}`);\n\n // Nodes inside a group (a domain, system or lane) are nested in a subgraph\n // for that group, so containment is kept\n const childrenOf = new Map<string, Node[]>();\n const topLevelNodes: Node[] = [];\n nodes.forEach((node) => {\n if (node.parentId && mermaidIds.has(node.parentId)) {\n childrenOf.set(node.parentId, [\n ...(childrenOf.get(node.parentId) ?? []),\n node,\n ]);\n } else {\n topLevelNodes.push(node);\n }\n });\n const isGroup = (node: Node) =>\n childrenOf.has(node.id) ||\n GROUP_NODE_TYPES.has(node.type ?? \"\") ||\n isSwimlaneNode(node);\n\n // Collect used node types and boundary classes for class definitions\n const usedTypes = new Set<string>();\n const groupsByClass = new Map<GroupClass, string[]>();\n const focusedIds: string[] = [];\n nodes.forEach((node) => {\n const id = mermaidIds.get(node.id)!;\n if (isGroup(node)) {\n const groupClass = getGroupClass(node);\n groupsByClass.set(groupClass, [\n ...(groupsByClass.get(groupClass) ?? []),\n id,\n ]);\n return;\n }\n if (node.type && NODE_STYLE_CLASSES[node.type]) usedTypes.add(node.type);\n if ((node.data as any)?.isFocused) focusedIds.push(id);\n });\n\n // Add class definitions if styles are enabled\n if (includeStyles) {\n lines.push(\"\");\n lines.push(\" %% Style definitions\");\n usedTypes.forEach((type) => {\n lines.push(\n ` classDef ${sanitizeMermaidId(type)} ${NODE_STYLE_CLASSES[type]}`,\n );\n });\n groupsByClass.forEach((_, groupClass) => {\n lines.push(\n ` classDef ${groupClass} ${GROUP_STYLE_CLASSES[groupClass]}`,\n );\n });\n if (focusedIds.length > 0) {\n lines.push(` classDef focused ${FOCUSED_STYLE}`);\n }\n }\n\n // Add node definitions\n lines.push(\"\");\n lines.push(\" %% Nodes\");\n\n const addNode = (node: Node, depth: number) => {\n const indent = \" \".repeat(depth);\n const id = mermaidIds.get(node.id)!;\n const label = escapeMermaidLabel(\n normaliseLines(getNodeLabel(node)).join(\"<br/>\"),\n );\n\n if (isGroup(node)) {\n lines.push(`${indent}subgraph ${id}[\"${label}\"]`);\n childrenOf.get(node.id)?.forEach((child) => addNode(child, depth + 1));\n lines.push(`${indent}end`);\n return;\n }\n\n const [prefix, suffix] = getMermaidNodeShape(node.type || \"custom\");\n let nodeLine = `${indent}${id}${prefix}\"${label}\"${suffix}`;\n\n // Add class reference if styles are enabled\n if (includeStyles && node.type && usedTypes.has(node.type)) {\n nodeLine += `:::${sanitizeMermaidId(node.type)}`;\n }\n\n lines.push(nodeLine);\n };\n\n topLevelNodes.forEach((node) => addNode(node, 1));\n\n // Add edge definitions, leaving out edges to nodes that aren't shown\n lines.push(\"\");\n lines.push(\" %% Edges\");\n\n const crossDomainLinks: number[] = [];\n graphEdges\n .filter(\n (edge) =>\n !edge.hidden &&\n mermaidIds.has(edge.source) &&\n mermaidIds.has(edge.target),\n )\n .forEach((edge, index) => {\n const sourceId = mermaidIds.get(edge.source)!;\n const targetId = mermaidIds.get(edge.target)!;\n const label = normaliseLines(getEdgeLabel(edge) ?? \"\").join(\" \");\n\n lines.push(\n label\n ? ` ${sourceId} -->|\"${escapeMermaidLabel(label)}\"| ${targetId}`\n : ` ${sourceId} --> ${targetId}`,\n );\n if (isCrossDomain(edge)) crossDomainLinks.push(index);\n });\n\n // Boundaries, the resource viewed and edges between domains\n if (includeStyles) {\n const styleLines: string[] = [];\n groupsByClass.forEach((ids, groupClass) => {\n styleLines.push(` class ${ids.join(\",\")} ${groupClass}`);\n });\n if (focusedIds.length > 0) {\n styleLines.push(` class ${focusedIds.join(\",\")} focused`);\n }\n if (crossDomainLinks.length > 0) {\n styleLines.push(\n ` linkStyle ${crossDomainLinks.join(\",\")} ${CROSS_DOMAIN_LINK_STYLE}`,\n );\n }\n if (styleLines.length > 0) {\n lines.push(\"\");\n lines.push(\" %% Boundaries and highlights\");\n lines.push(...styleLines);\n }\n }\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":["import_react","message","isCrossDomain"]}
package/dist/layout.mjs CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  isCarrier,
9
9
  isMessageNode,
10
10
  layoutWithElk
11
- } from "./chunk-P2DH6CFV.mjs";
11
+ } from "./chunk-N4AJCJPU.mjs";
12
12
  export {
13
13
  convertToMermaid,
14
14
  getMessagesLabel,
package/dist/studio.css CHANGED
@@ -320,6 +320,11 @@ html .studio-drag-preview.eventcatalog-visualizer .react-flow {
320
320
  /* The text tool: click anywhere on the canvas to write there */
321
321
  .studio-canvas.is-placing .react-flow__pane,
322
322
  .studio-canvas.is-placing .react-flow__node { cursor: crosshair !important; }
323
+ /* The hotspot tool: the pointer is a faded marker (a warning's, as one starts), centred where the marker will go */
324
+ .studio-canvas.is-placing-hotspot .react-flow__pane,
325
+ .studio-canvas.is-placing-hotspot .react-flow__node {
326
+ cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28'%3E%3Cg opacity='0.75'%3E%3Ccircle cx='14' cy='14' r='12.5' fill='%23d97706' stroke='white' stroke-width='2'/%3E%3Cg transform='translate(7.5 7.5) scale(0.5417)' fill='none' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E") 14 14, crosshair !important;
327
+ }
323
328
  /* Text being written has no box around it, like Excalidraw's */
324
329
  .studio-text textarea.studio-text-body { display: block; width: 100%; border: 0; outline: none; padding: 0; resize: none; overflow: hidden; background: transparent; }
325
330
 
@@ -430,11 +435,10 @@ html .studio-drag-preview.eventcatalog-visualizer .react-flow {
430
435
  transition: transform 120ms;
431
436
  }
432
437
  .studio-hotspot-marker:hover { transform: scale(1.05); }
433
- /* Zoomed well out: just each marker's icon (its title is in the list, and on hover) */
434
- .is-icons-only .studio-hotspot-label { display: none; }
435
- .is-icons-only .studio-hotspot-marker { padding-right: 4px; }
436
- /* An open marker's panel (or the form adding one): its full size on screen however far out the canvas is */
437
- .studio-hotspot-panel { transform: scale(var(--studio-hotspot-panel-scale, 1)); transform-origin: 0 -34px; }
438
+ /* An open marker's panel (or the form adding one), beside it: its full size on screen however far out the canvas is.
439
+ Scaled from the middle of the marker's right edge (12px to the panel's left, 22px down it, where its arrow is), which
440
+ shrinks with the canvas, so the panel stays the same distance from the marker with its arrow pointing at it */
441
+ .studio-hotspot-panel { transform: scale(var(--studio-hotspot-panel-scale, 1)); transform-origin: -12px 22px; }
438
442
 
439
443
  /* A hotspot being dragged over a node: the node lights up in its colour, to show it'll go on it when dropped */
440
444
  .studio-canvas .react-flow__node.studio-hotspot-drop { --studio-hotspot-drop: 217 119 6; border-radius: 14px; }
package/dist/studio.d.mts CHANGED
@@ -935,7 +935,14 @@ declare const UpdateNodeDataContext: react.Context<UpdateNodeData>;
935
935
  declare const EditingContext: react.Context<(editing: Editing | null) => void>;
936
936
  declare const DropTargetContext: react.Context<string | null>;
937
937
  declare const StartWritingContext: react.Context<string | null>;
938
+ /** Starts writing on the note or text with this id */
939
+ declare const startWriting: (id: string) => boolean;
938
940
  declare const LockedContext: react.Context<boolean>;
941
+ /** A sticky note's colours: soft paper colours, readable in light and dark themes (the names are the visualiser's) */
942
+ declare const NOTE_COLORS: Record<string, {
943
+ fill: string;
944
+ border: string;
945
+ }>;
939
946
  /**
940
947
  * Text written straight on the canvas (titles, labels, notes to self), as Excalidraw's: as big as what's written,
941
948
  * in a box with corners to scale it (its font grows with it) and a handle above to turn it. Double click it to write.
@@ -999,6 +1006,8 @@ declare const ContextMenu: react.NamedExoticComponent<{
999
1006
  /** For the node right clicked */
1000
1007
  questions?: MenuQuestion[];
1001
1008
  onCopy: () => void;
1009
+ /** Copies, then deletes, as ⌘X does (only where that works: not offered when not given) */
1010
+ onCut?: () => void;
1002
1011
  onDuplicate: () => void;
1003
1012
  /** Paste what was last copied where the menu was opened, if anything was */
1004
1013
  onPaste?: () => void;
@@ -1089,14 +1098,18 @@ declare const HotspotLayer: react.NamedExoticComponent<{
1089
1098
  onUpdate: (id: string, fields: HotspotFields) => void;
1090
1099
  onDelete: (id: string) => void;
1091
1100
  onGo: (hotspot: Hotspot) => void;
1101
+ /** The hotspot being added, dragged onto a node or to a place (its form stays open, with what's written in it) */
1102
+ onMoveDraft?: (to: HotspotPlace) => void;
1092
1103
  /** In a live room: markers the others are dragging move here as they drag, and mine move for them */
1093
1104
  live?: LiveMoves;
1094
1105
  }>;
1095
- /** All of them, most important first, for the panel at the bottom left: clicking one goes to it */
1096
- declare function HotspotList({ hotspots, nameOf, onOpen, }: {
1106
+ /** All of them, most important first, for the panel at the bottom left: clicking one goes to it (and opens it) */
1107
+ declare function HotspotList({ hotspots, nameOf, onOpen, goOnHover, }: {
1097
1108
  hotspots: Hotspot[];
1098
1109
  nameOf: (nodeId: string) => string | undefined;
1099
1110
  onOpen: (hotspot: Hotspot) => void;
1111
+ /** Resting the pointer on one goes to it (off while one's being added: going there would throw it away) */
1112
+ goOnHover?: boolean;
1100
1113
  }): react_jsx_runtime.JSX.Element;
1101
1114
 
1102
1115
  /** How each kind of node, and each catalog collection, looks in the canvas's lists (the colours are each kind's own) */
@@ -1275,6 +1288,8 @@ declare const _default: react.NamedExoticComponent<{
1275
1288
 
1276
1289
  /** What's dragged from the palette onto the canvas: a component's key (its type, or its kind of data store) */
1277
1290
  declare const COMPONENT_DRAG_TYPE = "application/eventcatalog-studio";
1291
+ /** Whether the palette is folded away (false when there's none) */
1292
+ declare const usePaletteCollapsed: () => boolean;
1278
1293
  /**
1279
1294
  * A tile in the palette: its icon, its name and what it is, picked up and dragged onto the canvas (or clicked, to
1280
1295
  * add it). Greyed out (`usable` false) where it can't be added, still saying why.
@@ -1455,4 +1470,4 @@ declare function Picture({ src, className, }: {
1455
1470
 
1456
1471
  declare const TeamNode: react.NamedExoticComponent<NodeProps>;
1457
1472
 
1458
- export { AgentMark, type Author, CANVAS_STATUSES, CANVAS_STATUS_LOOK, CATALOG_COLLECTIONS, CATALOG_GROUPS, CLIPBOARD_TYPE, COMPONENT_DRAG_TYPE, COMPONENT_TYPES, CONNECT_MIN_DISTANCE, CONNECT_PLACEHOLDER, CONNECT_PLACEHOLDER_EDGE, CONNECT_PLACEHOLDER_SIZE, COUNTER_SCALE, type CanvasContent, _default$1 as CanvasControls, type CanvasMeta, CanvasPortal, type CanvasStatus, CardPreview, type CatalogCollection, ClaudeCodeLogo, ClaudeLogo, type ClipboardContent, type CommentAnchor, CommentLayer, CommentList, ConnectChooser, type ConnectOption, type ConnectPlaceholderData, ConnectPlaceholderNode, type ContainerChoice, ContainerChooser, ContextMenu, type ContextMenuState, CursorLogo, DEFAULT_NODE_SIZE, DIAGRAM_FILE_EXTENSION, DROP_GAP, type DiagramLevel, type DiagramLevels, type DragPreview, DropTargetContext, EDGE_COLOR, EDGE_MARKER, ENTITY_SOURCE_HANDLE, EdgeLabelPortal, EdgeRoute, _default as EditButton, EditStatus, EditableText, type Editing, EditingContext, EmptyCanvas, FIT_VIEW_OPTIONS, Field, GROUP_PADDING, GROUP_TYPES, type Hotspot, type HotspotDraft, type HotspotFields, HotspotLayer, HotspotList, type HotspotPlace, type HotspotSeverity, type HotspotSpot, KEEP_IN_CONTAINER_MARGIN, LEVELS, LOCAL_EDGE_KEYS, LOCAL_NODE_KEYS, type LayoutResult, type Level, type LevelPlace, type Link, type LiveMoves, type LivePeer, LockedContext, MESSAGE_COLLECTIONS, type MenuQuestion, type Message, type ModelCanvasContent, type Moves, type Moving, NODE_CATEGORIES, NODE_ICONS, type NewLink, NodeCard, type NodeCategory, type NodeDefinition, type NodeIcon, type NodePreview, OpenAILogo, Palette, PaletteItem, PaletteSection, type PaletteTab, Picture, type PresenceSource, RemeasureHandles, type RenderedSizes, SEVERITIES, SHORTCUTS, STATUS, type SavedFileHandle, ShortcutsList, type Smoother, type StartDrag, StartWritingContext, type StatusChange, TEXT_FONT_SIZE, TeamNode, type Thread, type UpdateNodeData, UpdateNodeDataContext, ViewControls, addCanvasContent, addHotspots, addNodes, addThreads, agentLogoFor, anchorAt, arrangeLevel, buildNode, bySeverity, canGoInContainer, canHold, canPickWhereToSave, canvasImage, canvasToModel, centreBeyond, changesNodesOrEdges, cleanFileName, componentKey, connectNodes, connectedType, containerFor, copyNodes, createEdge, createHotspot, createSmoother, createThread, deleteEdges, deleteHotspot, deleteNodes, deleteThread, deleteThreads, downloadFile, fileNameFor, filterConnectOptions, findDropTarget, findDropTargets, findGroupAtPoint, fitContainersAround, fitGroup, fitGroupToChildren, getAbsolutePosition, getAbsoluteRect, getAnchorPosition, getCanvasMaps, getCanvasStatus, getConnectOptions, getDiagramLevel, getDiagramLevelUnavailable, getEdgeLabel, getEdgeRoute, getLayoutPositions, getLevelGraph, getLevelNodeSize, getLevelUnavailableReason, getLock, getNodeDefinition, getNodeLabel, getNodeName, getNodeSize, getNoteLevel, getResource, getStructureKey, isAwayFrom, isCardType, isCatalogCollection, isDecided, isEmptyCanvasAt, isGroupType, isOnLevel, isShownOnLevel, isVersioned, isWrittenOnCanvas, keepDragged, keepUnchanged, layoutGraph, levelPlaces, linkConnection, modelToCanvas, moveNode, moveThread, moveThreads, newCardData, nodeDefinitions, nodeTypes, notPlaceholder, pasteNodes, patchShared, pickDiagramFile, placeDropped, placeNode, placeNodes, previewContainerGrowth, readCanvas, readClipboard, readDiagramFile, readHotspots, readLevelLayout, readMeta, readThreads, reconnectEdge, reopenIfDecided, reopeningIfEdited, replyToThread, resizeNode, resourceNodeData, resourceTypeOf, saveToPickedFile, sendMove, setCanvasStatus, setEdgeLabel, setEdgeRoutes, setLevelPlaces, setLock, setMeta, setNodeParent, setNodeParents, setThreadResolved, shortId, sizeOf, sortByHierarchy, timeAgo, toDiagramFile, toRelativePosition, toSharedNode, topSeverity, updateHotspot, updateNodeData, updateResource, useConnectNew, useCounterScale, useDragGhost, useNodeAnchor, useRemoteMoves, useSavedFlash, withDescendants, withDiagramRoutes, withGroupSizes, withLocalState, withParent, withPositions };
1473
+ export { AgentMark, type Author, CANVAS_STATUSES, CANVAS_STATUS_LOOK, CATALOG_COLLECTIONS, CATALOG_GROUPS, CLIPBOARD_TYPE, COMPONENT_DRAG_TYPE, COMPONENT_TYPES, CONNECT_MIN_DISTANCE, CONNECT_PLACEHOLDER, CONNECT_PLACEHOLDER_EDGE, CONNECT_PLACEHOLDER_SIZE, COUNTER_SCALE, type CanvasContent, _default$1 as CanvasControls, type CanvasMeta, CanvasPortal, type CanvasStatus, CardPreview, type CatalogCollection, ClaudeCodeLogo, ClaudeLogo, type ClipboardContent, type CommentAnchor, CommentLayer, CommentList, ConnectChooser, type ConnectOption, type ConnectPlaceholderData, ConnectPlaceholderNode, type ContainerChoice, ContainerChooser, ContextMenu, type ContextMenuState, CursorLogo, DEFAULT_NODE_SIZE, DIAGRAM_FILE_EXTENSION, DROP_GAP, type DiagramLevel, type DiagramLevels, type DragPreview, DropTargetContext, EDGE_COLOR, EDGE_MARKER, ENTITY_SOURCE_HANDLE, EdgeLabelPortal, EdgeRoute, _default as EditButton, EditStatus, EditableText, type Editing, EditingContext, EmptyCanvas, FIT_VIEW_OPTIONS, Field, GROUP_PADDING, GROUP_TYPES, type Hotspot, type HotspotDraft, type HotspotFields, HotspotLayer, HotspotList, type HotspotPlace, type HotspotSeverity, type HotspotSpot, KEEP_IN_CONTAINER_MARGIN, LEVELS, LOCAL_EDGE_KEYS, LOCAL_NODE_KEYS, type LayoutResult, type Level, type LevelPlace, type Link, type LiveMoves, type LivePeer, LockedContext, MESSAGE_COLLECTIONS, type MenuQuestion, type Message, type ModelCanvasContent, type Moves, type Moving, NODE_CATEGORIES, NODE_ICONS, NOTE_COLORS, type NewLink, NodeCard, type NodeCategory, type NodeDefinition, type NodeIcon, type NodePreview, OpenAILogo, Palette, PaletteItem, PaletteSection, type PaletteTab, Picture, type PresenceSource, RemeasureHandles, type RenderedSizes, SEVERITIES, SHORTCUTS, STATUS, type SavedFileHandle, ShortcutsList, type Smoother, type StartDrag, StartWritingContext, type StatusChange, TEXT_FONT_SIZE, TeamNode, type Thread, type UpdateNodeData, UpdateNodeDataContext, ViewControls, addCanvasContent, addHotspots, addNodes, addThreads, agentLogoFor, anchorAt, arrangeLevel, buildNode, bySeverity, canGoInContainer, canHold, canPickWhereToSave, canvasImage, canvasToModel, centreBeyond, changesNodesOrEdges, cleanFileName, componentKey, connectNodes, connectedType, containerFor, copyNodes, createEdge, createHotspot, createSmoother, createThread, deleteEdges, deleteHotspot, deleteNodes, deleteThread, deleteThreads, downloadFile, fileNameFor, filterConnectOptions, findDropTarget, findDropTargets, findGroupAtPoint, fitContainersAround, fitGroup, fitGroupToChildren, getAbsolutePosition, getAbsoluteRect, getAnchorPosition, getCanvasMaps, getCanvasStatus, getConnectOptions, getDiagramLevel, getDiagramLevelUnavailable, getEdgeLabel, getEdgeRoute, getLayoutPositions, getLevelGraph, getLevelNodeSize, getLevelUnavailableReason, getLock, getNodeDefinition, getNodeLabel, getNodeName, getNodeSize, getNoteLevel, getResource, getStructureKey, isAwayFrom, isCardType, isCatalogCollection, isDecided, isEmptyCanvasAt, isGroupType, isOnLevel, isShownOnLevel, isVersioned, isWrittenOnCanvas, keepDragged, keepUnchanged, layoutGraph, levelPlaces, linkConnection, modelToCanvas, moveNode, moveThread, moveThreads, newCardData, nodeDefinitions, nodeTypes, notPlaceholder, pasteNodes, patchShared, pickDiagramFile, placeDropped, placeNode, placeNodes, previewContainerGrowth, readCanvas, readClipboard, readDiagramFile, readHotspots, readLevelLayout, readMeta, readThreads, reconnectEdge, reopenIfDecided, reopeningIfEdited, replyToThread, resizeNode, resourceNodeData, resourceTypeOf, saveToPickedFile, sendMove, setCanvasStatus, setEdgeLabel, setEdgeRoutes, setLevelPlaces, setLock, setMeta, setNodeParent, setNodeParents, setThreadResolved, shortId, sizeOf, sortByHierarchy, startWriting, timeAgo, toDiagramFile, toRelativePosition, toSharedNode, topSeverity, updateHotspot, updateNodeData, updateResource, useConnectNew, useCounterScale, useDragGhost, useNodeAnchor, usePaletteCollapsed, useRemoteMoves, useSavedFlash, withDescendants, withDiagramRoutes, withGroupSizes, withLocalState, withParent, withPositions };