@eventcatalog/core 5.7.1 → 5.7.4
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/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-GRC67WHC.js → chunk-GOIDVIMH.js} +1 -1
- package/dist/{chunk-HZ3XZKUT.js → chunk-L2PFSX4L.js} +1 -1
- package/dist/{chunk-36XVRD36.js → chunk-PLTHZFTW.js} +1 -1
- package/dist/{chunk-CPF5WE3R.js → chunk-PXYVJK54.js} +1 -1
- package/dist/{chunk-AXWTR2FV.js → chunk-RUG7AIT5.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/01-what-is-eventcatalog-assistant.md +8 -0
- package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/02-configuration.md +24 -0
- package/dist/docs/development/studio/01-introduction.md +2 -1
- package/dist/docs/development/studio/04-collaborate-on-a-canvas.md +10 -2
- package/dist/docs/development/studio/05-connect-your-agent.md +4 -0
- package/dist/docs/development/studio/07-reference.md +40 -7
- package/dist/docs/development/studio/draw-with-ai.md +96 -0
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +113 -8
- package/eventcatalog/src/components/ChatPanel/panel-width.ts +28 -0
- package/eventcatalog/src/components/MDX/Steps/Step.astro +4 -2
- package/eventcatalog/src/components/SchemaExplorer/DiffViewer.tsx +2 -103
- package/eventcatalog/src/components/SchemaExplorer/diff-dark-styles.ts +104 -0
- package/eventcatalog/src/features/mcp/apps/generated/canvas.html +71 -71
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +1 -1
- package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +25 -3
- package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +1 -1
- package/eventcatalog/src/features/studio/server/canvas-mcp.ts +1 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_ChangelogPage.astro +297 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.spec.ts +101 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +76 -47
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +4 -168
- package/eventcatalog/src/pages/docs/services/[id]/[version]/changelog/index.astro +14 -0
- package/eventcatalog/src/pages/docs/services/[id]/changelog/index.astro +3 -3
- package/eventcatalog/src/utils/node-graphs/domain-levels-node-graph.ts +112 -28
- package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +24 -10
- package/package.json +4 -4
|
@@ -1,176 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
3
|
-
import { getChangeLogs } from '@utils/collections/changelogs';
|
|
4
|
-
import { getChangelogResourceBadge } from '@utils/changelog-resource-badge';
|
|
5
|
-
import { render, getEntry } from 'astro:content';
|
|
6
|
-
import mdxComponents from '@components/MDX/components';
|
|
7
|
-
import 'diff2html/bundles/css/diff2html.min.css';
|
|
8
|
-
|
|
9
|
-
import { buildUrl } from '@utils/url-builder';
|
|
10
|
-
import { getPreviousVersion } from '@utils/collections/util';
|
|
11
|
-
import { getDiffsForCurrentAndPreviousVersion } from '@utils/file-diffs';
|
|
12
|
-
import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
|
|
13
|
-
import Badge from '@components/Badge.astro';
|
|
14
|
-
import { ClientRouter } from 'astro:transitions';
|
|
15
|
-
|
|
2
|
+
import ChangelogPage from './_ChangelogPage.astro';
|
|
16
3
|
import { Page } from './_index.data';
|
|
17
4
|
|
|
18
5
|
export const prerender = Page.prerender;
|
|
19
6
|
export const getStaticPaths = Page.getStaticPaths;
|
|
20
7
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (!props) return Astro.rewrite('/404');
|
|
24
|
-
|
|
25
|
-
let collectionItem = props;
|
|
26
|
-
const logs = await getChangeLogs(props);
|
|
27
|
-
|
|
28
|
-
const { data } = props;
|
|
29
|
-
const latestVersion = data.latestVersion;
|
|
30
|
-
|
|
31
|
-
const renderedLogs = logs.map(async (log) => {
|
|
32
|
-
const logEntry = await getEntry('changelogs', log.id);
|
|
33
|
-
const { Content } = await render(logEntry as any);
|
|
34
|
-
return {
|
|
35
|
-
Content,
|
|
36
|
-
...log,
|
|
37
|
-
};
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const logsToRender = await Promise.all(renderedLogs);
|
|
41
|
-
|
|
42
|
-
const logListPromise = logsToRender.map(async (log, index) => {
|
|
43
|
-
const currentLogVersion = log.data.version;
|
|
44
|
-
const previousLogVersion = log.data.version ? getPreviousVersion(log.data.version, data.versions || []) : '';
|
|
45
|
-
return {
|
|
46
|
-
id: log.id,
|
|
47
|
-
url: buildUrl(`/docs/${props.collection}/${props.data.id}`),
|
|
48
|
-
isLatest: log.data.version === latestVersion,
|
|
49
|
-
version: log.data.version,
|
|
50
|
-
createdAt: log.data.createdAt,
|
|
51
|
-
badges: log.data.badges || [],
|
|
52
|
-
Content: log.Content,
|
|
53
|
-
diffs:
|
|
54
|
-
currentLogVersion && previousLogVersion
|
|
55
|
-
? await getDiffsForCurrentAndPreviousVersion(
|
|
56
|
-
currentLogVersion,
|
|
57
|
-
previousLogVersion,
|
|
58
|
-
collectionItem.data.id,
|
|
59
|
-
props.allCollectionItems
|
|
60
|
-
)
|
|
61
|
-
: [],
|
|
62
|
-
};
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const logList = await Promise.all(logListPromise);
|
|
66
|
-
|
|
67
|
-
const badges = [getChangelogResourceBadge(props.collection)].filter((badge) => badge != null);
|
|
8
|
+
const changelogOwner = await Page.getData(Astro);
|
|
9
|
+
if (!changelogOwner) return Astro.rewrite('/404');
|
|
68
10
|
---
|
|
69
11
|
|
|
70
|
-
<
|
|
71
|
-
<main class="w-full md:pt-4 px-8 overflow-y-auto h-full max-w-[95%]">
|
|
72
|
-
<nav aria-label="Breadcrumb" class="flex">
|
|
73
|
-
<ol role="list" class="flex items-center space-x-2">
|
|
74
|
-
<li>
|
|
75
|
-
<div>
|
|
76
|
-
<a
|
|
77
|
-
href={buildUrl(`/docs/${props.collection}/${props.data.id}/${props.data.version}`)}
|
|
78
|
-
class="text-sm font-medium text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]"
|
|
79
|
-
>
|
|
80
|
-
{props.data.name}
|
|
81
|
-
</a>
|
|
82
|
-
</div>
|
|
83
|
-
</li>
|
|
84
|
-
<li>
|
|
85
|
-
<div class="flex items-center">
|
|
86
|
-
<svg
|
|
87
|
-
fill="currentColor"
|
|
88
|
-
viewBox="0 0 20 20"
|
|
89
|
-
aria-hidden="true"
|
|
90
|
-
class="h-5 w-5 flex-shrink-0 text-[rgb(var(--ec-page-border))]"
|
|
91
|
-
>
|
|
92
|
-
<path d="M5.555 17.776l8-16 .894.448-8 16-.894-.448z"></path>
|
|
93
|
-
</svg>
|
|
94
|
-
<a
|
|
95
|
-
href={buildUrl(`/docs/${props.collection}/${props.data.id}/${props.data.version}`)}
|
|
96
|
-
class="text-sm font-medium text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]"
|
|
97
|
-
>
|
|
98
|
-
Changelog
|
|
99
|
-
</a>
|
|
100
|
-
</div>
|
|
101
|
-
</li>
|
|
102
|
-
</ol>
|
|
103
|
-
</nav>
|
|
104
|
-
<div class="border-b border-[rgb(var(--ec-page-border))] flex justify-between items-start py-4 w-full">
|
|
105
|
-
<div>
|
|
106
|
-
<h2 class="text-2xl md:text-4xl font-bold text-[rgb(var(--ec-page-text))]">{props.data.name} (Changelog)</h2>
|
|
107
|
-
<h2 class="text-lg pt-2 text-[rgb(var(--ec-page-text-muted))] font-light">{props.data.summary}</h2>
|
|
108
|
-
{
|
|
109
|
-
badges.length > 0 && (
|
|
110
|
-
<div class="flex flex-wrap py-2 pt-4">
|
|
111
|
-
{badges.map((badge) => (
|
|
112
|
-
<Badge badge={badge} className="mr-2 px-2 py-1 rounded-md" />
|
|
113
|
-
))}
|
|
114
|
-
</div>
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
{
|
|
120
|
-
logList.length === 0 && (
|
|
121
|
-
<div class="py-4 text-[rgb(var(--ec-page-text-muted))] prose prose-md">
|
|
122
|
-
<p>No changelogs found.</p>
|
|
123
|
-
</div>
|
|
124
|
-
)
|
|
125
|
-
}
|
|
126
|
-
<div class="flow-root py-8 w-full">
|
|
127
|
-
<ul role="list" class="-mb-8">
|
|
128
|
-
{
|
|
129
|
-
logList.map((log, index) => (
|
|
130
|
-
<li>
|
|
131
|
-
<div class="relative pb-8">
|
|
132
|
-
{index !== logList.length - 1 ? (
|
|
133
|
-
<span aria-hidden="true" class="absolute left-6 top-4 -ml-px h-full w-0.5 bg-[rgb(var(--ec-page-border))]" />
|
|
134
|
-
) : null}
|
|
135
|
-
<div class="relative flex space-x-3">
|
|
136
|
-
<div>
|
|
137
|
-
<a
|
|
138
|
-
href={buildUrl(`${log.url}/${log.version}`)}
|
|
139
|
-
class="bg-primary hover:bg-primary/80 text-white flex h-8 w-14 items-center justify-center rounded-full ring-8 ring-[rgb(var(--ec-page-bg))]"
|
|
140
|
-
>
|
|
141
|
-
{log.version}
|
|
142
|
-
</a>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
<div class="pl-[70px] py-2 -mt-10">
|
|
146
|
-
{log.createdAt && (
|
|
147
|
-
<div class="pb-2">
|
|
148
|
-
<h3 class="text-2xl text-[rgb(var(--ec-page-text))] font-bold">
|
|
149
|
-
<span>
|
|
150
|
-
{log.createdAt.toISOString().split('T')[0]} {`${log.isLatest ? '(latest)' : ''}`}
|
|
151
|
-
</span>
|
|
152
|
-
</h3>
|
|
153
|
-
</div>
|
|
154
|
-
)}
|
|
155
|
-
{log.badges && (
|
|
156
|
-
<div class="flex flex-wrap">
|
|
157
|
-
{log.badges.filter(Boolean).map((badge: any) => (
|
|
158
|
-
<Badge badge={badge} className="mr-2 px-2 py-1 rounded-md" />
|
|
159
|
-
))}
|
|
160
|
-
</div>
|
|
161
|
-
)}
|
|
162
|
-
<div class="prose prose-md max-w-none! py-2">
|
|
163
|
-
<log.Content components={mdxComponents(props)} />
|
|
164
|
-
</div>
|
|
165
|
-
{log.diffs && log.diffs.map((diff) => <div class="bg-white rounded-lg p-2 my-2" set:html={diff} />)}
|
|
166
|
-
</div>
|
|
167
|
-
</div>
|
|
168
|
-
</li>
|
|
169
|
-
))
|
|
170
|
-
}
|
|
171
|
-
</ul>
|
|
172
|
-
</div>
|
|
173
|
-
<Footer />
|
|
174
|
-
</main>
|
|
175
|
-
<ClientRouter />
|
|
176
|
-
</VerticalSideBarLayout>
|
|
12
|
+
<ChangelogPage changelogOwner={changelogOwner} />
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Services need their own Changelog route: /docs/services/[id]/[docType]/[docId] outranks
|
|
3
|
+
// the generic /docs/[type]/[id]/[version]/changelog route and would catch these URLs.
|
|
4
|
+
import ChangelogPage from '../../../../[type]/[id]/[version]/changelog/_ChangelogPage.astro';
|
|
5
|
+
import { ServicePage } from '../../../../[type]/[id]/[version]/changelog/_index.data';
|
|
6
|
+
|
|
7
|
+
export const prerender = ServicePage.prerender;
|
|
8
|
+
export const getStaticPaths = ServicePage.getStaticPaths;
|
|
9
|
+
|
|
10
|
+
const changelogOwner = await ServicePage.getData(Astro);
|
|
11
|
+
if (!changelogOwner) return Astro.rewrite('/404');
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<ChangelogPage changelogOwner={changelogOwner} />
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import LatestVersionRedirect from '@components/LatestVersionRedirect.astro';
|
|
3
3
|
import { isChangelogEnabled, isSSR } from '@utils/feature';
|
|
4
4
|
import { buildUrl } from '@utils/url-builder';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { getLatestServiceChangelogPaths } from '../../../[type]/[id]/[version]/changelog/_index.data';
|
|
6
|
+
import { getLatestServiceVersion } from '../../_latest-version-route';
|
|
7
7
|
|
|
8
8
|
export const prerender = !isSSR();
|
|
9
|
-
export const getStaticPaths =
|
|
9
|
+
export const getStaticPaths = getLatestServiceChangelogPaths;
|
|
10
10
|
|
|
11
11
|
const { id } = Astro.params;
|
|
12
12
|
const version = Astro.props.redirectVersion ?? (id ? await getLatestServiceVersion(id) : undefined);
|
|
@@ -4,6 +4,7 @@ import { getNodesAndEdges as getServiceNodesAndEdges } from './services-node-gra
|
|
|
4
4
|
import { getNodesAndEdgesForDomainSystems as getDomainContextNodesAndEdges } from './system-context-node-graph';
|
|
5
5
|
import { getServices } from '@utils/collections/services';
|
|
6
6
|
import { getAgents } from '@utils/collections/agents';
|
|
7
|
+
import { versionMatches } from '@utils/collections/util';
|
|
7
8
|
import { createTriggerMessageNode } from './message-node-graph';
|
|
8
9
|
import {
|
|
9
10
|
createEdge,
|
|
@@ -97,14 +98,16 @@ const createDomainCard = (domain: any, { parentId, subdomain = false }: { parent
|
|
|
97
98
|
* systems is shown as a card. Messages a (sub)domain sends or receives itself
|
|
98
99
|
* (`domainMessages`: by message, the boxes drawn with it) fold into it, as a
|
|
99
100
|
* system's do into the system, when only one does (more than one, they connect
|
|
100
|
-
* through the message instead, rather than to each other).
|
|
101
|
+
* through the message instead, rather than to each other). `receivedBy` has
|
|
102
|
+
* what the services other domains stand for receive, by the node shown for them.
|
|
101
103
|
*/
|
|
102
104
|
export const collapseSystems = (
|
|
103
105
|
graph: Graph,
|
|
104
106
|
systems: any[],
|
|
105
107
|
contextNodes: Map<string, any>,
|
|
106
108
|
subdomains: Map<string, any>,
|
|
107
|
-
domainMessages: Map<string, Set<string>> = new Map()
|
|
109
|
+
domainMessages: Map<string, Set<string>> = new Map(),
|
|
110
|
+
receivedBy: Map<string, any[]> = new Map()
|
|
108
111
|
): Graph => {
|
|
109
112
|
const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
110
113
|
const systemsByGroupId = new Map<string, any>(systems.map((system: any) => [systemGroupId(system), system]));
|
|
@@ -123,11 +126,32 @@ export const collapseSystems = (
|
|
|
123
126
|
cards.set(groupId, createDomainCard(subdomain, { parentId: nodesById.get(groupId)?.parentId, subdomain: true }));
|
|
124
127
|
});
|
|
125
128
|
|
|
129
|
+
const sendersOf = new Map<string, string[]>();
|
|
130
|
+
graph.edges.forEach((edge) => sendersOf.set(edge.target, [...(sendersOf.get(edge.target) ?? []), edge.source]));
|
|
131
|
+
const carries = (nodeId: string) => nodesById.has(nodeId) && isCarrier(nodesById.get(nodeId));
|
|
132
|
+
const isMessage = (nodeId: string) => MESSAGE_TYPES.has(nodesById.get(nodeId)?.type);
|
|
133
|
+
const isChannel = (nodeId: string) => nodesById.get(nodeId)?.type === 'channels';
|
|
134
|
+
|
|
135
|
+
// A message is shown as what sends it, not where it's drawn, which can be with
|
|
136
|
+
// one of its consumers. Sent from more than one, it's left as it is (connecting
|
|
137
|
+
// each sender to what receives it, rather than one to the rest). None when nothing sends it
|
|
138
|
+
const senderHomes = new Map<string, string | undefined>();
|
|
139
|
+
const senderHome = (nodeId: string): string | undefined => {
|
|
140
|
+
if (!senderHomes.has(nodeId)) {
|
|
141
|
+
const senders = new Set((sendersOf.get(nodeId) ?? []).filter((sender) => !carries(sender)).map(representative));
|
|
142
|
+
const [sender] = [...senders];
|
|
143
|
+
senderHomes.set(nodeId, senders.size > 1 ? nodeId : sender);
|
|
144
|
+
}
|
|
145
|
+
return senderHomes.get(nodeId);
|
|
146
|
+
};
|
|
147
|
+
|
|
126
148
|
// The node something is shown as: its system, the card of the subdomain it's
|
|
127
149
|
// in, the (sub)domain it's in, or itself
|
|
128
150
|
const representative = (nodeId: string): string => {
|
|
129
151
|
const boxes = domainMessages.get(nodeId);
|
|
130
152
|
if (boxes?.size === 1) return representative([...boxes][0]);
|
|
153
|
+
const home = isMessage(nodeId) ? senderHome(nodeId) : undefined;
|
|
154
|
+
if (home) return home;
|
|
131
155
|
for (let node = nodesById.get(nodeId); node; node = node.parentId ? nodesById.get(node.parentId) : undefined) {
|
|
132
156
|
const system: any = systemsByGroupId.get(node.id);
|
|
133
157
|
if (system) return generateIdForNode(system);
|
|
@@ -149,6 +173,37 @@ export const collapseSystems = (
|
|
|
149
173
|
);
|
|
150
174
|
const shownNodes = new Map([...otherNodes, ...systemNodes, ...cards.values()].map((node) => [node.id, node]));
|
|
151
175
|
|
|
176
|
+
// The messages sent through a channel (and the channels before it)
|
|
177
|
+
const messagesThrough = (channelId: string, seen = new Set<string>()): string[] => {
|
|
178
|
+
if (seen.has(channelId)) return [];
|
|
179
|
+
seen.add(channelId);
|
|
180
|
+
return (sendersOf.get(channelId) ?? []).flatMap((sender) =>
|
|
181
|
+
isMessage(sender) ? [sender] : isChannel(sender) ? messagesThrough(sender, seen) : []
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
// Whether a node (or the services another domain stands for) says it receives a
|
|
185
|
+
// message: its id, and its version (or range, or latest)
|
|
186
|
+
const receives = (nodeId: string, messageId: string) => {
|
|
187
|
+
const data = nodesById.get(nodeId)?.data;
|
|
188
|
+
const message = nodesById.get(messageId)?.data?.message;
|
|
189
|
+
return ((data?.service ?? data?.agent)?.receives ?? receivedBy.get(nodeId) ?? []).some((pointer: any) => {
|
|
190
|
+
const { id, version } = pointer.data ?? pointer;
|
|
191
|
+
return id === message?.id && versionMatches(message?.version ?? '', version ?? 'latest');
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Where an edge is from and the messages it carries. A channel is one node for
|
|
196
|
+
// all its messages, so an edge out of one is from each message sent through it
|
|
197
|
+
// that its target receives (all of them when that isn't known), as those
|
|
198
|
+
// messages are shown. Edges into a channel are shown by the edges out of it
|
|
199
|
+
const edgeSources = (edge: any): { from: string; messages: string[] }[] => {
|
|
200
|
+
if (isChannel(edge.target) && carries(edge.source)) return [];
|
|
201
|
+
const through = isChannel(edge.source) ? messagesThrough(edge.source) : [];
|
|
202
|
+
if (through.length === 0) return [{ from: edge.source, messages: [edge.source, edge.target].filter(isMessage) }];
|
|
203
|
+
const sent = through.filter((messageId) => receives(edge.target, messageId));
|
|
204
|
+
return (sent.length > 0 ? sent : through).map((messageId) => ({ from: messageId, messages: [messageId] }));
|
|
205
|
+
};
|
|
206
|
+
|
|
152
207
|
// Edges between systems / actors / domains: context relationships as they
|
|
153
208
|
// are, edges to nodes kept as their first label, the rest labelled with the
|
|
154
209
|
// messages they carry
|
|
@@ -156,28 +211,30 @@ export const collapseSystems = (
|
|
|
156
211
|
string,
|
|
157
212
|
{ source: string; target: string; messages: Map<string, string>; relationship?: any; label?: string; crossDomain?: boolean }
|
|
158
213
|
>();
|
|
159
|
-
graph.edges.forEach((edge) =>
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
214
|
+
graph.edges.forEach((edge) =>
|
|
215
|
+
edgeSources(edge).forEach(({ from, messages }) => {
|
|
216
|
+
const source = representative(from);
|
|
217
|
+
const target = representative(edge.target);
|
|
218
|
+
if (
|
|
219
|
+
source === target ||
|
|
220
|
+
!shownNodes.has(source) ||
|
|
221
|
+
!shownNodes.has(target) ||
|
|
222
|
+
// One shown node inside another (e.g. a system in its domain)
|
|
223
|
+
isInside(shownNodes, source, target) ||
|
|
224
|
+
isInside(shownNodes, target, source)
|
|
225
|
+
)
|
|
226
|
+
return;
|
|
227
|
+
const key = `${source}->${target}`;
|
|
228
|
+
const connection = connections.get(key) ?? { source, target, messages: new Map<string, string>() };
|
|
229
|
+
if (edge.id.startsWith(CONTEXT_EDGE_PREFIX)) connection.relationship = edge;
|
|
230
|
+
if (edge.data?.crossDomain) connection.crossDomain = true;
|
|
231
|
+
messages.forEach((messageId) => {
|
|
232
|
+
const node = nodesById.get(messageId);
|
|
233
|
+
connection.messages.set(node.data?.message?.name ?? messageId, node.type);
|
|
234
|
+
});
|
|
235
|
+
connections.set(key, connection);
|
|
236
|
+
})
|
|
237
|
+
);
|
|
181
238
|
|
|
182
239
|
// A relationship already connects the pair (either way), so skip messages
|
|
183
240
|
const related = new Set(
|
|
@@ -298,8 +355,10 @@ export const buildDomainGraph = ({
|
|
|
298
355
|
])
|
|
299
356
|
);
|
|
300
357
|
const edges: any[] = [];
|
|
301
|
-
// Nodes replaced by another (services in other domains become the domain)
|
|
358
|
+
// Nodes replaced by another (services in other domains become the domain),
|
|
359
|
+
// and what each of them receives
|
|
302
360
|
const replacedBy = new Map<string, string>();
|
|
361
|
+
const replacedReceives = new Map<string, any[]>();
|
|
303
362
|
|
|
304
363
|
// Other domains are shown as a single card, with counts of what's in them. A
|
|
305
364
|
// subdomain of another domain is shown inside a box for that domain (and
|
|
@@ -364,6 +423,7 @@ export const buildDomainGraph = ({
|
|
|
364
423
|
const owner = SERVICE_TYPES.has(node.type) ? owners.get(node.id) : undefined;
|
|
365
424
|
if (owner && !groupOfDomain.has(owner.domain.data.id)) {
|
|
366
425
|
replacedBy.set(node.id, addOtherDomain(owner.domain));
|
|
426
|
+
replacedReceives.set(node.id, (node.data?.service ?? node.data?.agent)?.receives ?? []);
|
|
367
427
|
return;
|
|
368
428
|
}
|
|
369
429
|
if (owner && owner.system) {
|
|
@@ -440,11 +500,17 @@ export const buildDomainGraph = ({
|
|
|
440
500
|
|
|
441
501
|
const graphEdges = connectEdges(edges, new Map([...cardBoxes, ...replacedBy]), nodes);
|
|
442
502
|
placeMessagesWithPublishers(nodes, graphEdges, new Set(domainMessages.keys()));
|
|
503
|
+
// What the services each other domain stands for receive, by the node shown for the domain
|
|
504
|
+
const receivedBy = new Map<string, any[]>();
|
|
505
|
+
replacedBy.forEach((nodeId, replaced) =>
|
|
506
|
+
receivedBy.set(nodeId, [...(receivedBy.get(nodeId) ?? []), ...(replacedReceives.get(replaced) ?? [])])
|
|
507
|
+
);
|
|
443
508
|
return {
|
|
444
509
|
nodes,
|
|
445
510
|
edges: graphEdges,
|
|
446
511
|
otherDomainIds: new Set([...replacedBy.values(), ...[...otherDomainIds].map((id) => cardBoxes.get(id) ?? id)]),
|
|
447
512
|
domainMessages,
|
|
513
|
+
receivedBy,
|
|
448
514
|
};
|
|
449
515
|
};
|
|
450
516
|
|
|
@@ -609,7 +675,8 @@ const addDomainMessages = ({
|
|
|
609
675
|
/**
|
|
610
676
|
* Messages are placed in the first graph they appear in, which can be a
|
|
611
677
|
* system consuming them. Moves each message into the system (or domain) of the
|
|
612
|
-
* services publishing it, when they're all in the same one
|
|
678
|
+
* services publishing it, when they're all in the same one, and each channel
|
|
679
|
+
* with the messages sent through it. Messages a domain
|
|
613
680
|
* sends or receives itself (`kept`) stay outside its box, where its edge to them
|
|
614
681
|
* is drawn.
|
|
615
682
|
*/
|
|
@@ -630,6 +697,22 @@ export const placeMessagesWithPublishers = (nodes: Map<string, any>, edges: any[
|
|
|
630
697
|
nodes.set(messageId, { ...message, parentId, extent: undefined });
|
|
631
698
|
}
|
|
632
699
|
});
|
|
700
|
+
|
|
701
|
+
// Channels go with what sends through them (messages, or channels before them), when that's all in one place
|
|
702
|
+
const sendersOf = new Map<string, string[]>();
|
|
703
|
+
edges.forEach((edge) => sendersOf.set(edge.target, [...(sendersOf.get(edge.target) ?? []), edge.source]));
|
|
704
|
+
for (let moved = true; moved; ) {
|
|
705
|
+
moved = false;
|
|
706
|
+
nodes.forEach((channel, channelId) => {
|
|
707
|
+
if (channel.type !== 'channels' || kept.has(channelId)) return;
|
|
708
|
+
const groups = new Set((sendersOf.get(channelId) ?? []).map((sender) => nodes.get(sender)?.parentId));
|
|
709
|
+
const [parentId] = [...groups];
|
|
710
|
+
if (groups.size === 1 && parentId && channel.parentId !== parentId) {
|
|
711
|
+
nodes.set(channelId, { ...channel, parentId, extent: undefined });
|
|
712
|
+
moved = true;
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
}
|
|
633
716
|
};
|
|
634
717
|
|
|
635
718
|
/**
|
|
@@ -860,7 +943,7 @@ const buildLevels = async ({ id, version, mode = 'full', channelRenderMode }: No
|
|
|
860
943
|
),
|
|
861
944
|
]);
|
|
862
945
|
|
|
863
|
-
const { nodes, edges, otherDomainIds, domainMessages } = buildDomainGraph({
|
|
946
|
+
const { nodes, edges, otherDomainIds, domainMessages, receivedBy } = buildDomainGraph({
|
|
864
947
|
tree,
|
|
865
948
|
owners,
|
|
866
949
|
parentDomainOf,
|
|
@@ -888,7 +971,8 @@ const buildLevels = async ({ id, version, mode = 'full', channelRenderMode }: No
|
|
|
888
971
|
systems.map(({ system }) => system),
|
|
889
972
|
context.contextNodesById,
|
|
890
973
|
new Map(tree.filter((entry) => entry.parentId).map((entry) => [entry.groupId, entry.domain])),
|
|
891
|
-
domainMessages
|
|
974
|
+
domainMessages,
|
|
975
|
+
receivedBy
|
|
892
976
|
);
|
|
893
977
|
const overview = hideMessageNodes(collapsed.nodes, collapsed.edges);
|
|
894
978
|
|
|
@@ -126,17 +126,21 @@ export const getNodesAndEdges = async ({
|
|
|
126
126
|
|
|
127
127
|
// Wrap the graph in one parent "system group" boundary node. Messages,
|
|
128
128
|
// channels and data stores stay in it. With confineToMembers, only this
|
|
129
|
-
// system's own services are children; other services
|
|
129
|
+
// system's own services are children; other services (and their agent tools)
|
|
130
|
+
// are left outside.
|
|
131
|
+
const graphNodes = [...nodes.values()];
|
|
132
|
+
const graphEdges = [...edges.values()];
|
|
130
133
|
const graph = {
|
|
131
134
|
nodes:
|
|
132
135
|
wrapInSystemGroup && nodes.size > 0
|
|
133
136
|
? wrapNodesInSystemGroup(
|
|
134
|
-
|
|
137
|
+
graphNodes,
|
|
138
|
+
graphEdges,
|
|
135
139
|
system,
|
|
136
140
|
confineToMembers ? memberServiceNodeIds(system, serviceMap) : undefined
|
|
137
141
|
)
|
|
138
|
-
:
|
|
139
|
-
edges:
|
|
142
|
+
: graphNodes,
|
|
143
|
+
edges: graphEdges,
|
|
140
144
|
};
|
|
141
145
|
|
|
142
146
|
return layout ? layoutNodeGraph(graph) : graph;
|
|
@@ -167,11 +171,22 @@ const withoutGroupParent = (node: any) => {
|
|
|
167
171
|
* Wraps a system's nodes in one parent `system-group` node (sized to fit them
|
|
168
172
|
* when the graph is laid out), so they visibly belong to the system.
|
|
169
173
|
* When `memberIds` is set, service and agent nodes that aren't members are
|
|
170
|
-
* left outside the group.
|
|
174
|
+
* left outside the group, and so are the tools of agents left outside.
|
|
171
175
|
*/
|
|
172
|
-
const wrapNodesInSystemGroup = (systemNodes: any[], system: any, memberIds?: Set<string>) => {
|
|
176
|
+
const wrapNodesInSystemGroup = (systemNodes: any[], systemEdges: any[], system: any, memberIds?: Set<string>) => {
|
|
173
177
|
const groupId = `system-group-${system.data.id}-${system.data.version}`;
|
|
174
178
|
|
|
179
|
+
const outside = new Set<string>(
|
|
180
|
+
memberIds
|
|
181
|
+
? systemNodes.filter((node) => SERVICE_NODE_TYPES.has(node.type) && !memberIds.has(node.id)).map((node) => node.id)
|
|
182
|
+
: []
|
|
183
|
+
);
|
|
184
|
+
// An agent's tools sit wherever the agent does
|
|
185
|
+
const toolIds = new Set(systemNodes.filter((node) => node.type === 'agentTool').map((node) => node.id));
|
|
186
|
+
for (const edge of systemEdges) {
|
|
187
|
+
if (outside.has(edge.source) && toolIds.has(edge.target)) outside.add(edge.target);
|
|
188
|
+
}
|
|
189
|
+
|
|
175
190
|
const groupNode = {
|
|
176
191
|
id: groupId,
|
|
177
192
|
type: 'system-group',
|
|
@@ -188,9 +203,8 @@ const wrapNodesInSystemGroup = (systemNodes: any[], system: any, memberIds?: Set
|
|
|
188
203
|
// Parent must come before its children in the array for React Flow
|
|
189
204
|
return [
|
|
190
205
|
groupNode,
|
|
191
|
-
...systemNodes.map((node) =>
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}),
|
|
206
|
+
...systemNodes.map((node) =>
|
|
207
|
+
outside.has(node.id) ? withoutGroupParent(node) : { ...node, parentId: groupId, extent: 'parent' }
|
|
208
|
+
),
|
|
195
209
|
];
|
|
196
210
|
};
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"license": "BUSL-1.1",
|
|
9
9
|
"type": "module",
|
|
10
|
-
"version": "5.7.
|
|
10
|
+
"version": "5.7.4",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
@@ -142,10 +142,10 @@
|
|
|
142
142
|
"yjs": "^13.6.33",
|
|
143
143
|
"zod": "^4.6.6",
|
|
144
144
|
"@eventcatalog/exporters": "0.1.4",
|
|
145
|
-
"@eventcatalog/linter": "1.3.1",
|
|
146
145
|
"@eventcatalog/fitness": "0.1.1",
|
|
147
|
-
"@eventcatalog/
|
|
148
|
-
"@eventcatalog/visualiser": "^5.2.
|
|
146
|
+
"@eventcatalog/linter": "1.3.1",
|
|
147
|
+
"@eventcatalog/visualiser": "^5.2.2",
|
|
148
|
+
"@eventcatalog/sdk": "2.32.0"
|
|
149
149
|
},
|
|
150
150
|
"devDependencies": {
|
|
151
151
|
"@astrojs/check": "^0.9.10",
|