@eventcatalog/core 4.6.2 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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-352FGP6W.js +123 -0
- package/dist/{chunk-LQLAEEOV.js → chunk-3HMY77MN.js} +1 -1
- package/dist/chunk-5EPNFDT5.js +133 -0
- package/dist/{chunk-AT3AQ3PK.js → chunk-CZ6C3FBV.js} +1 -1
- package/dist/{chunk-U6E25IFA.js → chunk-HRSTURLY.js} +1 -1
- package/dist/chunk-R7Z5ALYI.js +44 -0
- package/dist/{chunk-MDCIQJRX.js → chunk-WJVRBHVC.js} +1 -1
- package/dist/chunk-ZR6AH5Z2.js +204 -0
- package/dist/{chunk-LZDDSWXC.js → chunk-ZU5WMIH4.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +651 -51
- package/dist/eventcatalog.config.d.cts +18 -1
- package/dist/eventcatalog.config.d.ts +18 -1
- package/dist/eventcatalog.js +133 -6
- package/dist/federation/content-cache.cjs +78 -0
- package/dist/federation/content-cache.d.cts +9 -0
- package/dist/federation/content-cache.d.ts +9 -0
- package/dist/federation/content-cache.js +6 -0
- package/dist/federation/federate.cjs +588 -0
- package/dist/federation/federate.d.cts +81 -0
- package/dist/federation/federate.d.ts +81 -0
- package/dist/federation/federate.js +12 -0
- package/dist/federation/github-source-provider.cjs +157 -0
- package/dist/federation/github-source-provider.d.cts +24 -0
- package/dist/federation/github-source-provider.d.ts +24 -0
- package/dist/federation/github-source-provider.js +6 -0
- package/dist/federation/public-assets.cjs +167 -0
- package/dist/federation/public-assets.d.cts +24 -0
- package/dist/federation/public-assets.d.ts +24 -0
- package/dist/federation/public-assets.js +6 -0
- package/dist/federation/types.cjs +18 -0
- package/dist/federation/types.d.cts +19 -0
- package/dist/federation/types.d.ts +19 -0
- package/dist/federation/types.js +0 -0
- 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/MDX/NodeGraph/NodeGraph.astro +2 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +8 -85
- package/eventcatalog/src/components/SideNav/NestedSideBar/utils.spec.ts +19 -1
- package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +80 -0
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +1 -0
- package/eventcatalog/src/stores/sidebar-store/state.ts +14 -1
- package/eventcatalog/src/utils/node-graphs/channel-node-graph.ts +395 -3
- package/package.json +4 -4
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
removeFavorite as removeFavoriteAction,
|
|
14
14
|
type FavoriteItem,
|
|
15
15
|
} from '@stores/favorites-store';
|
|
16
|
-
import { canCollapseGroup, getBadgeClasses, getGroupLabel, isGroupCollapsed } from './utils';
|
|
16
|
+
import { canCollapseGroup, findNodeKeyByUrl, getBadgeClasses, getGroupLabel, isGroupCollapsed } from './utils';
|
|
17
17
|
import { resolveIconUrl } from '@utils/icon';
|
|
18
18
|
|
|
19
19
|
const cn = (...classes: (string | false | undefined)[]) => classes.filter(Boolean).join(' ');
|
|
@@ -201,86 +201,9 @@ export default function NestedSideBar() {
|
|
|
201
201
|
/**
|
|
202
202
|
* Find a node key by matching URL patterns
|
|
203
203
|
*/
|
|
204
|
-
const
|
|
204
|
+
const findNodeKeyForUrl = useCallback(
|
|
205
205
|
(url: string): string | null => {
|
|
206
|
-
|
|
207
|
-
const normalizedUrl = stripBasePath(url);
|
|
208
|
-
|
|
209
|
-
// URL patterns to match resources with version
|
|
210
|
-
const urlPatternsWithVersion = [
|
|
211
|
-
// Domains
|
|
212
|
-
{ pattern: /^\/docs\/domains\/([^/]+)\/([^/]+)/, type: 'domain' },
|
|
213
|
-
{ pattern: /^\/visualiser\/domains\/([^/]+)\/([^/]+)/, type: 'domain' },
|
|
214
|
-
{ pattern: /^\/architecture\/domains\/([^/]+)\/([^/]+)/, type: 'domain' },
|
|
215
|
-
// Systems (key prefix is `system`, url segment is `systems`).
|
|
216
|
-
// The visualiser pattern also matches the System Diagram
|
|
217
|
-
// (/visualiser/systems/:id/:version/context) since it is not end-anchored.
|
|
218
|
-
{ pattern: /^\/docs\/systems\/([^/]+)\/([^/]+)/, type: 'system' },
|
|
219
|
-
{ pattern: /^\/visualiser\/systems\/([^/]+)\/([^/]+)/, type: 'system' },
|
|
220
|
-
{ pattern: /^\/architecture\/systems\/([^/]+)\/([^/]+)/, type: 'system' },
|
|
221
|
-
// Agents
|
|
222
|
-
{ pattern: /^\/docs\/agents\/([^/]+)\/([^/]+)/, type: 'agent' },
|
|
223
|
-
{ pattern: /^\/visualiser\/agents\/([^/]+)\/([^/]+)/, type: 'agent' },
|
|
224
|
-
// Decision Records
|
|
225
|
-
{ pattern: /^\/docs\/adrs\/([^/]+)\/([^/]+)/, type: 'adr' },
|
|
226
|
-
// Services
|
|
227
|
-
{ pattern: /^\/docs\/services\/([^/]+)\/([^/]+)/, type: 'service' },
|
|
228
|
-
{ pattern: /^\/architecture\/services\/([^/]+)\/([^/]+)/, type: 'service' },
|
|
229
|
-
{ pattern: /^\/visualiser\/services\/([^/]+)\/([^/]+)/, type: 'service' },
|
|
230
|
-
// Messages (events, commands, queries) - note: keys use singular form
|
|
231
|
-
{ pattern: /^\/docs\/events\/([^/]+)\/([^/]+)/, type: 'event' },
|
|
232
|
-
{ pattern: /^\/docs\/commands\/([^/]+)\/([^/]+)/, type: 'command' },
|
|
233
|
-
{ pattern: /^\/docs\/queries\/([^/]+)\/([^/]+)/, type: 'query' },
|
|
234
|
-
{ pattern: /^\/visualiser\/messages\/([^/]+)\/([^/]+)/, type: 'message' },
|
|
235
|
-
{ pattern: /^\/visualiser\/events\/([^/]+)\/([^/]+)/, type: 'event' },
|
|
236
|
-
{ pattern: /^\/visualiser\/commands\/([^/]+)\/([^/]+)/, type: 'command' },
|
|
237
|
-
{ pattern: /^\/visualiser\/queries\/([^/]+)\/([^/]+)/, type: 'query' },
|
|
238
|
-
// Containers
|
|
239
|
-
{ pattern: /^\/docs\/containers\/([^/]+)\/([^/]+)/, type: 'container' },
|
|
240
|
-
{ pattern: /^\/visualiser\/containers\/([^/]+)\/([^/]+)/, type: 'container' },
|
|
241
|
-
// Flows
|
|
242
|
-
{ pattern: /^\/docs\/flows\/([^/]+)\/([^/]+)/, type: 'flow' },
|
|
243
|
-
{ pattern: /^\/visualiser\/flows\/([^/]+)\/([^/]+)/, type: 'flow' },
|
|
244
|
-
// Data Products
|
|
245
|
-
{ pattern: /^\/docs\/data-products\/([^/]+)\/([^/]+)/, type: 'data-product' },
|
|
246
|
-
{ pattern: /^\/visualiser\/data-products\/([^/]+)\/([^/]+)/, type: 'data-product' },
|
|
247
|
-
// Entities
|
|
248
|
-
{ pattern: /^\/docs\/entities\/([^/]+)\/([^/]+)/, type: 'entity' },
|
|
249
|
-
];
|
|
250
|
-
|
|
251
|
-
// URL patterns without version (language pages, etc)
|
|
252
|
-
const urlPatternsWithoutVersion = [{ pattern: /^\/docs\/domains\/([^/]+)\/language/, type: 'domain' }];
|
|
253
|
-
|
|
254
|
-
// First try to match patterns with version
|
|
255
|
-
for (const { pattern, type } of urlPatternsWithVersion) {
|
|
256
|
-
const match = normalizedUrl.match(pattern);
|
|
257
|
-
if (match) {
|
|
258
|
-
const id = match[1];
|
|
259
|
-
const version = match[2];
|
|
260
|
-
|
|
261
|
-
// First try with version
|
|
262
|
-
const keyWithVersion = `${type}:${id}:${version}`;
|
|
263
|
-
if (nodes[keyWithVersion]) {
|
|
264
|
-
return keyWithVersion;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Fallback to lookup without version (for latest)
|
|
268
|
-
const foundNodeKey = nodeLookup.get(`${type}:${id}`);
|
|
269
|
-
if (foundNodeKey) return foundNodeKey;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Then try patterns without version
|
|
274
|
-
for (const { pattern, type } of urlPatternsWithoutVersion) {
|
|
275
|
-
const match = normalizedUrl.match(pattern);
|
|
276
|
-
if (match) {
|
|
277
|
-
const id = match[1];
|
|
278
|
-
const foundNodeKey = nodeLookup.get(`${type}:${id}`);
|
|
279
|
-
if (foundNodeKey) return foundNodeKey;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return null;
|
|
206
|
+
return findNodeKeyByUrl(stripBasePath(url), nodes, nodeLookup);
|
|
284
207
|
},
|
|
285
208
|
[nodeLookup, nodes]
|
|
286
209
|
);
|
|
@@ -337,7 +260,7 @@ export default function NestedSideBar() {
|
|
|
337
260
|
*/
|
|
338
261
|
const findAndNavigateToUrl = useCallback(
|
|
339
262
|
(url: string) => {
|
|
340
|
-
const foundNodeKey =
|
|
263
|
+
const foundNodeKey = findNodeKeyForUrl(url);
|
|
341
264
|
|
|
342
265
|
if (foundNodeKey) {
|
|
343
266
|
setNavigationStack((currentStack) => {
|
|
@@ -373,7 +296,7 @@ export default function NestedSideBar() {
|
|
|
373
296
|
}
|
|
374
297
|
return false;
|
|
375
298
|
},
|
|
376
|
-
[
|
|
299
|
+
[findNodeKeyForUrl, tryConnectStack, resolveRef, roots]
|
|
377
300
|
);
|
|
378
301
|
|
|
379
302
|
/**
|
|
@@ -393,7 +316,7 @@ export default function NestedSideBar() {
|
|
|
393
316
|
}
|
|
394
317
|
|
|
395
318
|
const savedState = loadState();
|
|
396
|
-
const targetKey =
|
|
319
|
+
const targetKey = findNodeKeyForUrl(currentUrl);
|
|
397
320
|
|
|
398
321
|
let finalStack: NavigationLevel[] | null = null;
|
|
399
322
|
|
|
@@ -432,7 +355,7 @@ export default function NestedSideBar() {
|
|
|
432
355
|
}
|
|
433
356
|
|
|
434
357
|
setIsInitialized(true);
|
|
435
|
-
}, [data, roots, isInitialized, buildStackFromPath,
|
|
358
|
+
}, [data, roots, isInitialized, buildStackFromPath, findNodeKeyForUrl, tryConnectStack, resolveRef]);
|
|
436
359
|
|
|
437
360
|
/**
|
|
438
361
|
* Save state whenever navigation changes
|
|
@@ -510,7 +433,7 @@ export default function NestedSideBar() {
|
|
|
510
433
|
return stripBasePath(left).replace(/\/$/, '') === stripBasePath(right).replace(/\/$/, '');
|
|
511
434
|
}, []);
|
|
512
435
|
|
|
513
|
-
const activeNodeKey = useMemo(() =>
|
|
436
|
+
const activeNodeKey = useMemo(() => findNodeKeyForUrl(currentPath), [currentPath, findNodeKeyForUrl]);
|
|
514
437
|
|
|
515
438
|
// Show loading state if no data yet
|
|
516
439
|
if (!data || roots.length === 0) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { canCollapseGroup, getGroupLabel, isGroupCollapsed } from './utils';
|
|
2
|
+
import { canCollapseGroup, findNodeKeyByUrl, getGroupLabel, isGroupCollapsed } from './utils';
|
|
3
3
|
|
|
4
4
|
const preferences = (expanded: string[] = []) => ({
|
|
5
5
|
expanded: new Set(expanded),
|
|
@@ -41,3 +41,21 @@ describe('isGroupCollapsed', () => {
|
|
|
41
41
|
expect(isGroupCollapsed(true, 'outbound-messages', preferences(['outbound-messages']))).toBe(false);
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
|
+
|
|
45
|
+
describe('findNodeKeyByUrl', () => {
|
|
46
|
+
const channelKey = 'channel:product-events:1.0.0';
|
|
47
|
+
const nodes = { [channelKey]: {} };
|
|
48
|
+
const nodeLookup = new Map([['channel:product-events', channelKey]]);
|
|
49
|
+
|
|
50
|
+
it('selects the channel sidebar for channel documentation', () => {
|
|
51
|
+
expect(findNodeKeyByUrl('/docs/channels/product-events/1.0.0', nodes, nodeLookup)).toBe(channelKey);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('keeps the channel sidebar selected on the channel map', () => {
|
|
55
|
+
expect(findNodeKeyByUrl('/visualiser/channels/product-events/1.0.0', nodes, nodeLookup)).toBe(channelKey);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('falls back to the available channel version', () => {
|
|
59
|
+
expect(findNodeKeyByUrl('/docs/channels/product-events/latest', nodes, nodeLookup)).toBe(channelKey);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -17,6 +17,86 @@ export const isGroupCollapsed = (canCollapse: boolean, groupId: string, preferen
|
|
|
17
17
|
return canCollapse && !preferences.expanded.has(groupId);
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Finds the sidebar node represented by a resource URL.
|
|
22
|
+
* The URL must have its configured base path removed before it is passed in.
|
|
23
|
+
*/
|
|
24
|
+
export const findNodeKeyByUrl = (
|
|
25
|
+
url: string,
|
|
26
|
+
nodes: Record<string, unknown>,
|
|
27
|
+
nodeLookup: ReadonlyMap<string, string>
|
|
28
|
+
): string | null => {
|
|
29
|
+
const urlPatternsWithVersion = [
|
|
30
|
+
// Domains
|
|
31
|
+
{ pattern: /^\/docs\/domains\/([^/]+)\/([^/]+)/, type: 'domain' },
|
|
32
|
+
{ pattern: /^\/visualiser\/domains\/([^/]+)\/([^/]+)/, type: 'domain' },
|
|
33
|
+
{ pattern: /^\/architecture\/domains\/([^/]+)\/([^/]+)/, type: 'domain' },
|
|
34
|
+
// Systems (key prefix is `system`, url segment is `systems`).
|
|
35
|
+
// The visualiser pattern also matches the System Diagram
|
|
36
|
+
// (/visualiser/systems/:id/:version/context) since it is not end-anchored.
|
|
37
|
+
{ pattern: /^\/docs\/systems\/([^/]+)\/([^/]+)/, type: 'system' },
|
|
38
|
+
{ pattern: /^\/visualiser\/systems\/([^/]+)\/([^/]+)/, type: 'system' },
|
|
39
|
+
{ pattern: /^\/architecture\/systems\/([^/]+)\/([^/]+)/, type: 'system' },
|
|
40
|
+
// Agents
|
|
41
|
+
{ pattern: /^\/docs\/agents\/([^/]+)\/([^/]+)/, type: 'agent' },
|
|
42
|
+
{ pattern: /^\/visualiser\/agents\/([^/]+)\/([^/]+)/, type: 'agent' },
|
|
43
|
+
// Decision Records
|
|
44
|
+
{ pattern: /^\/docs\/adrs\/([^/]+)\/([^/]+)/, type: 'adr' },
|
|
45
|
+
// Services
|
|
46
|
+
{ pattern: /^\/docs\/services\/([^/]+)\/([^/]+)/, type: 'service' },
|
|
47
|
+
{ pattern: /^\/architecture\/services\/([^/]+)\/([^/]+)/, type: 'service' },
|
|
48
|
+
{ pattern: /^\/visualiser\/services\/([^/]+)\/([^/]+)/, type: 'service' },
|
|
49
|
+
// Messages (events, commands, queries) - note: keys use singular form
|
|
50
|
+
{ pattern: /^\/docs\/events\/([^/]+)\/([^/]+)/, type: 'event' },
|
|
51
|
+
{ pattern: /^\/docs\/commands\/([^/]+)\/([^/]+)/, type: 'command' },
|
|
52
|
+
{ pattern: /^\/docs\/queries\/([^/]+)\/([^/]+)/, type: 'query' },
|
|
53
|
+
{ pattern: /^\/visualiser\/messages\/([^/]+)\/([^/]+)/, type: 'message' },
|
|
54
|
+
{ pattern: /^\/visualiser\/events\/([^/]+)\/([^/]+)/, type: 'event' },
|
|
55
|
+
{ pattern: /^\/visualiser\/commands\/([^/]+)\/([^/]+)/, type: 'command' },
|
|
56
|
+
{ pattern: /^\/visualiser\/queries\/([^/]+)\/([^/]+)/, type: 'query' },
|
|
57
|
+
// Channels
|
|
58
|
+
{ pattern: /^\/docs\/channels\/([^/]+)\/([^/]+)/, type: 'channel' },
|
|
59
|
+
{ pattern: /^\/visualiser\/channels\/([^/]+)\/([^/]+)/, type: 'channel' },
|
|
60
|
+
// Containers
|
|
61
|
+
{ pattern: /^\/docs\/containers\/([^/]+)\/([^/]+)/, type: 'container' },
|
|
62
|
+
{ pattern: /^\/visualiser\/containers\/([^/]+)\/([^/]+)/, type: 'container' },
|
|
63
|
+
// Flows
|
|
64
|
+
{ pattern: /^\/docs\/flows\/([^/]+)\/([^/]+)/, type: 'flow' },
|
|
65
|
+
{ pattern: /^\/visualiser\/flows\/([^/]+)\/([^/]+)/, type: 'flow' },
|
|
66
|
+
// Data Products
|
|
67
|
+
{ pattern: /^\/docs\/data-products\/([^/]+)\/([^/]+)/, type: 'data-product' },
|
|
68
|
+
{ pattern: /^\/visualiser\/data-products\/([^/]+)\/([^/]+)/, type: 'data-product' },
|
|
69
|
+
// Entities
|
|
70
|
+
{ pattern: /^\/docs\/entities\/([^/]+)\/([^/]+)/, type: 'entity' },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const urlPatternsWithoutVersion = [{ pattern: /^\/docs\/domains\/([^/]+)\/language/, type: 'domain' }];
|
|
74
|
+
|
|
75
|
+
for (const { pattern, type } of urlPatternsWithVersion) {
|
|
76
|
+
const match = url.match(pattern);
|
|
77
|
+
if (match) {
|
|
78
|
+
const id = match[1];
|
|
79
|
+
const version = match[2];
|
|
80
|
+
const keyWithVersion = `${type}:${id}:${version}`;
|
|
81
|
+
|
|
82
|
+
if (nodes[keyWithVersion]) return keyWithVersion;
|
|
83
|
+
|
|
84
|
+
const foundNodeKey = nodeLookup.get(`${type}:${id}`);
|
|
85
|
+
if (foundNodeKey) return foundNodeKey;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const { pattern, type } of urlPatternsWithoutVersion) {
|
|
90
|
+
const match = url.match(pattern);
|
|
91
|
+
if (match) {
|
|
92
|
+
const foundNodeKey = nodeLookup.get(`${type}:${match[1]}`);
|
|
93
|
+
if (foundNodeKey) return foundNodeKey;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return null;
|
|
98
|
+
};
|
|
99
|
+
|
|
20
100
|
/**
|
|
21
101
|
* Returns Tailwind classes for badge styling based on badge type.
|
|
22
102
|
* Uses CSS variables from theme.css for proper theming support.
|
|
@@ -764,6 +764,8 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
|
|
|
764
764
|
{} as Record<string, NavNode>
|
|
765
765
|
);
|
|
766
766
|
|
|
767
|
+
const visualiserEnabled = config?.visualiser?.enabled !== false;
|
|
768
|
+
|
|
767
769
|
const channelNodes = channels.reduce(
|
|
768
770
|
(acc, channel) => {
|
|
769
771
|
const versionedKey = `channel:${channel.data.id}:${channel.data.version}`;
|
|
@@ -796,6 +798,18 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
|
|
|
796
798
|
].filter(Boolean) as { title: string; href: string }[]
|
|
797
799
|
),
|
|
798
800
|
docsSection,
|
|
801
|
+
visualiserEnabled && {
|
|
802
|
+
type: 'group',
|
|
803
|
+
title: 'Architecture',
|
|
804
|
+
icon: 'Workflow',
|
|
805
|
+
pages: [
|
|
806
|
+
{
|
|
807
|
+
type: 'item',
|
|
808
|
+
title: 'Map',
|
|
809
|
+
href: buildUrl(`/visualiser/${channel.collection}/${channel.data.id}/${channel.data.version}`),
|
|
810
|
+
},
|
|
811
|
+
],
|
|
812
|
+
},
|
|
799
813
|
].filter(Boolean) as ChildRef[],
|
|
800
814
|
},
|
|
801
815
|
channel,
|
|
@@ -827,7 +841,6 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
|
|
|
827
841
|
{} as Record<string, NavNode>
|
|
828
842
|
);
|
|
829
843
|
|
|
830
|
-
const visualiserEnabled = config?.visualiser?.enabled !== false;
|
|
831
844
|
const rootDomainsNodes: Record<string, NavNode> = {};
|
|
832
845
|
|
|
833
846
|
if (rootDomains.length > 0) {
|