@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.
Files changed (51) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/chunk-352FGP6W.js +123 -0
  6. package/dist/{chunk-LQLAEEOV.js → chunk-3HMY77MN.js} +1 -1
  7. package/dist/chunk-5EPNFDT5.js +133 -0
  8. package/dist/{chunk-AT3AQ3PK.js → chunk-CZ6C3FBV.js} +1 -1
  9. package/dist/{chunk-U6E25IFA.js → chunk-HRSTURLY.js} +1 -1
  10. package/dist/chunk-R7Z5ALYI.js +44 -0
  11. package/dist/{chunk-MDCIQJRX.js → chunk-WJVRBHVC.js} +1 -1
  12. package/dist/chunk-ZR6AH5Z2.js +204 -0
  13. package/dist/{chunk-LZDDSWXC.js → chunk-ZU5WMIH4.js} +1 -1
  14. package/dist/constants.cjs +1 -1
  15. package/dist/constants.js +1 -1
  16. package/dist/eventcatalog.cjs +651 -51
  17. package/dist/eventcatalog.config.d.cts +18 -1
  18. package/dist/eventcatalog.config.d.ts +18 -1
  19. package/dist/eventcatalog.js +133 -6
  20. package/dist/federation/content-cache.cjs +78 -0
  21. package/dist/federation/content-cache.d.cts +9 -0
  22. package/dist/federation/content-cache.d.ts +9 -0
  23. package/dist/federation/content-cache.js +6 -0
  24. package/dist/federation/federate.cjs +588 -0
  25. package/dist/federation/federate.d.cts +81 -0
  26. package/dist/federation/federate.d.ts +81 -0
  27. package/dist/federation/federate.js +12 -0
  28. package/dist/federation/github-source-provider.cjs +157 -0
  29. package/dist/federation/github-source-provider.d.cts +24 -0
  30. package/dist/federation/github-source-provider.d.ts +24 -0
  31. package/dist/federation/github-source-provider.js +6 -0
  32. package/dist/federation/public-assets.cjs +167 -0
  33. package/dist/federation/public-assets.d.cts +24 -0
  34. package/dist/federation/public-assets.d.ts +24 -0
  35. package/dist/federation/public-assets.js +6 -0
  36. package/dist/federation/types.cjs +18 -0
  37. package/dist/federation/types.d.cts +19 -0
  38. package/dist/federation/types.d.ts +19 -0
  39. package/dist/federation/types.js +0 -0
  40. package/dist/generate.cjs +1 -1
  41. package/dist/generate.js +3 -3
  42. package/dist/utils/cli-logger.cjs +1 -1
  43. package/dist/utils/cli-logger.js +2 -2
  44. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +2 -0
  45. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +8 -85
  46. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.spec.ts +19 -1
  47. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +80 -0
  48. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +1 -0
  49. package/eventcatalog/src/stores/sidebar-store/state.ts +14 -1
  50. package/eventcatalog/src/utils/node-graphs/channel-node-graph.ts +395 -3
  51. 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 findNodeKeyByUrl = useCallback(
204
+ const findNodeKeyForUrl = useCallback(
205
205
  (url: string): string | null => {
206
- // Strip the base path so patterns always match against /docs/..., /visualiser/..., etc.
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 = findNodeKeyByUrl(url);
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
- [findNodeKeyByUrl, tryConnectStack, resolveRef, roots]
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 = findNodeKeyByUrl(currentUrl);
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, findNodeKeyByUrl, tryConnectStack, resolveRef]);
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(() => findNodeKeyByUrl(currentPath), [currentPath, findNodeKeyByUrl]);
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.
@@ -29,6 +29,7 @@ export class Page extends HybridPage {
29
29
  'domains',
30
30
  'flows',
31
31
  'containers',
32
+ 'channels',
32
33
  'data-products',
33
34
  'systems',
34
35
  ];
@@ -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) {