@eventcatalog/core 4.6.0 → 4.6.1

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 (31) 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-BYW3E5I3.js → chunk-MTXVQ4VP.js} +1 -1
  6. package/dist/{chunk-DIVHIZPM.js → chunk-N3YKQTGX.js} +1 -1
  7. package/dist/{chunk-SXQSNSUH.js → chunk-U6VOVEVZ.js} +1 -1
  8. package/dist/{chunk-XBFXBRPC.js → chunk-X7XI7NEP.js} +1 -1
  9. package/dist/{chunk-VD4WSIHN.js → chunk-Y7JJ2B3L.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/eventcatalog.cjs +1 -1
  13. package/dist/eventcatalog.js +5 -5
  14. package/dist/generate.cjs +1 -1
  15. package/dist/generate.js +3 -3
  16. package/dist/utils/cli-logger.cjs +1 -1
  17. package/dist/utils/cli-logger.js +2 -2
  18. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +2 -8
  19. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.spec.ts +4 -0
  20. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +2 -2
  21. package/eventcatalog/src/components/Tables/SystemResources/SystemResourcesTable.tsx +29 -2
  22. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.spec.ts +28 -0
  23. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.ts +7 -22
  24. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_resources.spec.ts +65 -0
  25. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_resources.ts +46 -0
  26. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +7 -29
  27. package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +7 -3
  28. package/eventcatalog/src/stores/sidebar-store/builders/service.ts +2 -4
  29. package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +1 -0
  30. package/eventcatalog/src/stores/sidebar-store/state.ts +1 -0
  31. package/package.json +10 -10
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(analytics_exports);
36
36
  var import_os = __toESM(require("os"), 1);
37
37
 
38
38
  // package.json
39
- var version = "4.6.0";
39
+ var version = "4.6.1";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-SXQSNSUH.js";
4
- import "../chunk-XBFXBRPC.js";
3
+ } from "../chunk-U6VOVEVZ.js";
4
+ import "../chunk-X7XI7NEP.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -140,7 +140,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
140
140
  var import_os = __toESM(require("os"), 1);
141
141
 
142
142
  // package.json
143
- var version = "4.6.0";
143
+ var version = "4.6.1";
144
144
 
145
145
  // src/constants.ts
146
146
  var VERSION = version;
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  log_build_default
3
- } from "../chunk-VD4WSIHN.js";
3
+ } from "../chunk-Y7JJ2B3L.js";
4
4
  import "../chunk-K2XIENVT.js";
5
- import "../chunk-SXQSNSUH.js";
6
- import "../chunk-XBFXBRPC.js";
5
+ import "../chunk-U6VOVEVZ.js";
6
+ import "../chunk-X7XI7NEP.js";
7
7
  import "../chunk-6QENHZZP.js";
8
8
  export {
9
9
  log_build_default as default
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-XBFXBRPC.js";
3
+ } from "./chunk-X7XI7NEP.js";
4
4
 
5
5
  // src/utils/cli-logger.ts
6
6
  import pc from "picocolors";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  logger
3
- } from "./chunk-BYW3E5I3.js";
3
+ } from "./chunk-MTXVQ4VP.js";
4
4
  import {
5
5
  cleanup,
6
6
  getEventCatalogConfigFile
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-XBFXBRPC.js";
3
+ } from "./chunk-X7XI7NEP.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import os from "os";
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "4.6.0";
2
+ var version = "4.6.1";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-K2XIENVT.js";
5
5
  import {
6
6
  raiseEvent
7
- } from "./chunk-SXQSNSUH.js";
7
+ } from "./chunk-U6VOVEVZ.js";
8
8
  import {
9
9
  getEventCatalogConfigFile,
10
10
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "4.6.0";
28
+ var version = "4.6.1";
29
29
 
30
30
  // src/constants.ts
31
31
  var VERSION = version;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-XBFXBRPC.js";
3
+ } from "./chunk-X7XI7NEP.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -144,7 +144,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
144
144
  var import_picocolors = __toESM(require("picocolors"), 1);
145
145
 
146
146
  // package.json
147
- var version = "4.6.0";
147
+ var version = "4.6.1";
148
148
 
149
149
  // src/constants.ts
150
150
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  log_build_default
3
- } from "./chunk-VD4WSIHN.js";
3
+ } from "./chunk-Y7JJ2B3L.js";
4
4
  import "./chunk-K2XIENVT.js";
5
5
  import {
6
6
  runMigrations
@@ -14,10 +14,10 @@ import {
14
14
  } from "./chunk-B7HCX5HM.js";
15
15
  import {
16
16
  generate
17
- } from "./chunk-DIVHIZPM.js";
17
+ } from "./chunk-N3YKQTGX.js";
18
18
  import {
19
19
  logger
20
- } from "./chunk-BYW3E5I3.js";
20
+ } from "./chunk-MTXVQ4VP.js";
21
21
  import {
22
22
  resolve_catalog_dependencies_default
23
23
  } from "./chunk-LHR4G2UO.js";
@@ -27,7 +27,7 @@ import {
27
27
  import {
28
28
  watch
29
29
  } from "./chunk-CAJUIMDJ.js";
30
- import "./chunk-SXQSNSUH.js";
30
+ import "./chunk-U6VOVEVZ.js";
31
31
  import {
32
32
  createAstroDevLineFilter,
33
33
  createAstroLineFilter
@@ -38,7 +38,7 @@ import {
38
38
  import "./chunk-W5JQON7Z.js";
39
39
  import {
40
40
  VERSION
41
- } from "./chunk-XBFXBRPC.js";
41
+ } from "./chunk-X7XI7NEP.js";
42
42
  import {
43
43
  linkCoreNodeModules,
44
44
  resolveInstalledCoreNodeModules
package/dist/generate.cjs CHANGED
@@ -108,7 +108,7 @@ var getEventCatalogConfigFile = async (projectDirectory) => {
108
108
  var import_picocolors = __toESM(require("picocolors"), 1);
109
109
 
110
110
  // package.json
111
- var version = "4.6.0";
111
+ var version = "4.6.1";
112
112
 
113
113
  // src/constants.ts
114
114
  var VERSION = version;
package/dist/generate.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  generate
3
- } from "./chunk-DIVHIZPM.js";
4
- import "./chunk-BYW3E5I3.js";
5
- import "./chunk-XBFXBRPC.js";
3
+ } from "./chunk-N3YKQTGX.js";
4
+ import "./chunk-MTXVQ4VP.js";
5
+ import "./chunk-X7XI7NEP.js";
6
6
  import "./chunk-6QENHZZP.js";
7
7
  export {
8
8
  generate
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(cli_logger_exports);
36
36
  var import_picocolors = __toESM(require("picocolors"), 1);
37
37
 
38
38
  // package.json
39
- var version = "4.6.0";
39
+ var version = "4.6.1";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  logger
3
- } from "../chunk-BYW3E5I3.js";
4
- import "../chunk-XBFXBRPC.js";
3
+ } from "../chunk-MTXVQ4VP.js";
4
+ import "../chunk-X7XI7NEP.js";
5
5
  export {
6
6
  logger
7
7
  };
@@ -749,7 +749,7 @@ export default function NestedSideBar() {
749
749
  }) ?? [];
750
750
 
751
751
  const groupId = groupKey || group.collapseKey || `${currentLevel.key ?? 'root'}:group:${group.title}`;
752
- const canCollapse = canCollapseGroup(visibleChildren.length, isTopLevel);
752
+ const canCollapse = canCollapseGroup(visibleChildren.length, isTopLevel, group.collapsible);
753
753
  const isCollapsed = isGroupCollapsed(canCollapse, groupId, sectionCollapsePreferences);
754
754
 
755
755
  // When a group's children are subtle subgroups (e.g. Resources > Services/Flows/Data Stores),
@@ -866,12 +866,6 @@ export default function NestedSideBar() {
866
866
  // a custom icon (item.leftIcon) is always still shown.
867
867
  const IconComponent =
868
868
  item.icon && !suppressDefaultIcon ? (LucideIcons as unknown as Record<string, LucideIcons.LucideIcon>)[item.icon] : null;
869
- // Some glyphs fill their box more than others, so they read larger at the shared
870
- // item size. Render these ~25% smaller (matching the ADR icon) to visually balance:
871
- // ClipboardList (ADRs) and the message glyphs (events/commands/queries).
872
- const smallerIcons = ['ClipboardList', 'Zap', 'MessageSquare', 'Search', 'Mail'];
873
- const iconSizeClass = item.icon && smallerIcons.includes(item.icon) ? 'w-3 h-3' : 'w-4 h-4';
874
-
875
869
  const handleStarClick = (e: React.MouseEvent) => {
876
870
  e.preventDefault();
877
871
  e.stopPropagation();
@@ -888,7 +882,7 @@ export default function NestedSideBar() {
888
882
  isActive ? 'text-[rgb(var(--ec-accent-text))]' : 'text-[rgb(var(--ec-content-text-muted))]'
889
883
  )}
890
884
  >
891
- <IconComponent className={iconSizeClass} />
885
+ <IconComponent className="w-4 h-4" />
892
886
  </span>
893
887
  )}
894
888
  {item.leftIcon && <img src={resolveIconUrl(item.leftIcon)} alt="" loading="lazy" className="w-4 h-4 flex-shrink-0" />}
@@ -22,6 +22,10 @@ describe('sidebar group presentation', () => {
22
22
  expect(canCollapseGroup(5, false)).toBe(false);
23
23
  expect(canCollapseGroup(6, false)).toBe(true);
24
24
  });
25
+
26
+ it('keeps groups marked as non-collapsible expanded regardless of their size', () => {
27
+ expect(canCollapseGroup(100, false, false)).toBe(false);
28
+ });
25
29
  });
26
30
 
27
31
  describe('isGroupCollapsed', () => {
@@ -7,8 +7,8 @@ export type SectionCollapsePreferences = {
7
7
  expanded: Set<string>;
8
8
  };
9
9
 
10
- export const canCollapseGroup = (childCount: number, isTopLevel: boolean): boolean =>
11
- !isTopLevel && childCount > SIDEBAR_GROUP_COLLAPSE_THRESHOLD;
10
+ export const canCollapseGroup = (childCount: number, isTopLevel: boolean, collapsible = true): boolean =>
11
+ collapsible && !isTopLevel && childCount > SIDEBAR_GROUP_COLLAPSE_THRESHOLD;
12
12
 
13
13
  export const getGroupLabel = (title: string, childCount: number): string =>
14
14
  GROUP_TITLES_WITHOUT_COUNT.has(title) ? title : `${title} (${childCount})`;
@@ -12,7 +12,18 @@ import { getColorAndIconForCollection } from '@utils/collections/icons';
12
12
  import { getCollectionTextColorClass } from '@utils/collection-colors';
13
13
  import { isIconPath, resolveIconUrl } from '@utils/icon';
14
14
 
15
- export type SystemResourceCollection = 'services' | 'flows' | 'entities' | 'containers' | 'events' | 'commands' | 'queries';
15
+ export type SystemResourceCollection =
16
+ | 'domains'
17
+ | 'systems'
18
+ | 'agents'
19
+ | 'services'
20
+ | 'flows'
21
+ | 'entities'
22
+ | 'data-products'
23
+ | 'containers'
24
+ | 'events'
25
+ | 'commands'
26
+ | 'queries';
16
27
 
17
28
  export interface SystemResourceItem {
18
29
  collection: SystemResourceCollection;
@@ -29,9 +40,13 @@ interface SystemResourcesTableProps {
29
40
  }
30
41
 
31
42
  const TYPE_LABELS: Record<SystemResourceCollection, string> = {
43
+ domains: 'Subdomains',
44
+ systems: 'Systems',
45
+ agents: 'Agents',
32
46
  services: 'Services',
33
47
  flows: 'Flows',
34
48
  entities: 'Entities',
49
+ 'data-products': 'Data Products',
35
50
  containers: 'Data Stores',
36
51
  events: 'Events',
37
52
  commands: 'Commands',
@@ -39,7 +54,19 @@ const TYPE_LABELS: Record<SystemResourceCollection, string> = {
39
54
  };
40
55
 
41
56
  // Order the type filter pills follow on the page.
42
- const TYPE_ORDER: SystemResourceCollection[] = ['services', 'flows', 'entities', 'containers', 'events', 'commands', 'queries'];
57
+ const TYPE_ORDER: SystemResourceCollection[] = [
58
+ 'domains',
59
+ 'systems',
60
+ 'agents',
61
+ 'services',
62
+ 'flows',
63
+ 'entities',
64
+ 'data-products',
65
+ 'containers',
66
+ 'events',
67
+ 'commands',
68
+ 'queries',
69
+ ];
43
70
 
44
71
  // Small icon for a collection type (used in filter pills and the type column).
45
72
  const CollectionTypeIcon = ({ collection, className }: { collection: SystemResourceCollection; className?: string }) => {
@@ -0,0 +1,28 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { loadResourceOwner } from './_index.data';
3
+
4
+ const collectionMocks = vi.hoisted(() => ({
5
+ getDomains: vi.fn(() => Promise.resolve([])),
6
+ getSystems: vi.fn(() => Promise.resolve([])),
7
+ }));
8
+
9
+ vi.mock('@utils/collections/domains', () => ({ getDomains: collectionMocks.getDomains }));
10
+ vi.mock('@utils/collections/systems', () => ({ getSystems: collectionMocks.getSystems }));
11
+
12
+ describe('Resources page owner loader', () => {
13
+ beforeEach(() => {
14
+ vi.clearAllMocks();
15
+ });
16
+
17
+ it('loads domains without flattening child-subdomain services and agents', async () => {
18
+ await loadResourceOwner('domains');
19
+
20
+ expect(collectionMocks.getDomains).toHaveBeenCalledWith({ includeServicesInSubdomains: false });
21
+ });
22
+
23
+ it('keeps the system loader unchanged', async () => {
24
+ await loadResourceOwner('systems');
25
+
26
+ expect(collectionMocks.getSystems).toHaveBeenCalledWith();
27
+ });
28
+ });
@@ -1,36 +1,21 @@
1
1
  import { isSSR } from '@utils/feature';
2
2
  import { HybridPage } from '@utils/page-loaders/hybrid-page';
3
+ import { hasResources } from './_resources';
3
4
 
4
- // The Resources page lists the top-level resources (services, flows, entities and
5
- // data stores) attached to a single system or domain. It is only available for
6
- // systems and domains — every other resource type 404s.
5
+ // The Resources page lists the resources directly attached to a single system or
6
+ // domain. It is only available for systems and domains every other resource type 404s.
7
7
  const SUPPORTED_TYPES = ['systems', 'domains'] as const;
8
8
  type SupportedType = (typeof SUPPORTED_TYPES)[number];
9
9
 
10
10
  const isSupportedType = (type: string): type is SupportedType => SUPPORTED_TYPES.includes(type as SupportedType);
11
11
 
12
- const loadResourceOwner = async (type: SupportedType) => {
12
+ export const loadResourceOwner = async (type: SupportedType) => {
13
13
  if (type === 'systems') {
14
14
  const { getSystems } = await import('@utils/collections/systems');
15
15
  return getSystems();
16
16
  }
17
17
  const { getDomains } = await import('@utils/collections/domains');
18
- return getDomains();
19
- };
20
-
21
- // A system/domain only gets a Resources page when it actually has resources attached.
22
- // Mirrors what the page renders: services, flows, entities, data stores and (domains
23
- // only) the domain's own messages.
24
- const hasResources = (owner: any): boolean => {
25
- const data = owner?.data ?? {};
26
- return (
27
- (data.services || []).length > 0 ||
28
- (data.flows || []).length > 0 ||
29
- (data.entities || []).length > 0 ||
30
- (data.containers || []).length > 0 ||
31
- (data.sends || []).length > 0 ||
32
- (data.receives || []).length > 0
33
- );
18
+ return getDomains({ includeServicesInSubdomains: false });
34
19
  };
35
20
 
36
21
  export class Page extends HybridPage {
@@ -47,7 +32,7 @@ export class Page extends HybridPage {
47
32
 
48
33
  return SUPPORTED_TYPES.flatMap((type, index) =>
49
34
  owners[index]
50
- .filter((owner) => hasResources(owner))
35
+ .filter((owner) => hasResources(owner, type))
51
36
  .map((owner) => ({
52
37
  params: {
53
38
  type,
@@ -71,7 +56,7 @@ export class Page extends HybridPage {
71
56
  const owners = await loadResourceOwner(type);
72
57
  const owner = owners.find((o) => o.data.id === id && o.data.version === version);
73
58
 
74
- if (!owner || !hasResources(owner)) {
59
+ if (!owner || !hasResources(owner, type)) {
75
60
  return null;
76
61
  }
77
62
 
@@ -0,0 +1,65 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getResourceGroups, hasResources } from './_resources';
3
+
4
+ const resource = (id: string, version = '1.0.0') => ({ data: { id, name: id, version } });
5
+ const message = (collection: 'events' | 'commands' | 'queries', id: string, version = '1.0.0') => ({
6
+ collection,
7
+ data: { id, name: id, version },
8
+ });
9
+
10
+ describe('resource page groups', () => {
11
+ it('includes every resource directly attached to a domain', () => {
12
+ const sharedEvent = message('events', 'OrderPlaced');
13
+ const groups = getResourceGroups(
14
+ {
15
+ domains: [resource('Fulfilment')],
16
+ systems: [resource('OrderingSystem')],
17
+ agents: [resource('OrderAgent')],
18
+ 'data-products': [resource('OrderAnalytics')],
19
+ services: [resource('OrdersService')],
20
+ flows: [resource('PlaceOrder')],
21
+ entities: [resource('Order')],
22
+ sends: [sharedEvent, message('commands', 'PlaceOrder')],
23
+ receives: [sharedEvent, message('queries', 'GetOrder')],
24
+ },
25
+ 'domains'
26
+ );
27
+
28
+ expect(Object.fromEntries(groups.map(({ collection, items }) => [collection, items.map((item) => item.data.id)]))).toEqual({
29
+ domains: ['Fulfilment'],
30
+ systems: ['OrderingSystem'],
31
+ agents: ['OrderAgent'],
32
+ 'data-products': ['OrderAnalytics'],
33
+ services: ['OrdersService'],
34
+ flows: ['PlaceOrder'],
35
+ entities: ['Order'],
36
+ containers: [],
37
+ events: ['OrderPlaced'],
38
+ commands: ['PlaceOrder'],
39
+ queries: ['GetOrder'],
40
+ });
41
+ });
42
+
43
+ it('keeps domain-only resources off system resource pages', () => {
44
+ const collections = getResourceGroups(
45
+ {
46
+ domains: [resource('Fulfilment')],
47
+ systems: [resource('OrderingSystem')],
48
+ agents: [resource('OrderAgent')],
49
+ 'data-products': [resource('OrderAnalytics')],
50
+ services: [resource('OrdersService')],
51
+ containers: [resource('OrdersDatabase')],
52
+ },
53
+ 'systems'
54
+ ).map(({ collection }) => collection);
55
+
56
+ expect(collections).toEqual(['services', 'flows', 'entities', 'containers', 'events', 'commands', 'queries']);
57
+ });
58
+
59
+ it.each(['domains', 'systems', 'agents', 'data-products'] as const)(
60
+ 'makes a domain with only %s eligible for a Resources page',
61
+ (collection) => {
62
+ expect(hasResources({ data: { [collection]: [resource('direct-resource')] } }, 'domains')).toBe(true);
63
+ }
64
+ );
65
+ });
@@ -0,0 +1,46 @@
1
+ import type { SystemResourceCollection } from '@components/Tables/SystemResources/SystemResourcesTable';
2
+
3
+ export type ResourceOwnerCollection = 'systems' | 'domains';
4
+
5
+ export interface ResourceGroup {
6
+ collection: SystemResourceCollection;
7
+ items: any[];
8
+ }
9
+
10
+ const getDomainMessages = (data: any) => {
11
+ const seen = new Map<string, any>();
12
+
13
+ for (const message of [...(data.sends || []), ...(data.receives || [])]) {
14
+ if (!message?.data?.id || !message?.collection) continue;
15
+ seen.set(`${message.collection}:${message.data.id}:${message.data.version}`, message);
16
+ }
17
+
18
+ return Array.from(seen.values());
19
+ };
20
+
21
+ export const getResourceGroups = (data: any, ownerCollection: ResourceOwnerCollection): ResourceGroup[] => {
22
+ const domainMessages = ownerCollection === 'domains' ? getDomainMessages(data) : [];
23
+ const domainOnlyGroups: ResourceGroup[] =
24
+ ownerCollection === 'domains'
25
+ ? [
26
+ { collection: 'domains', items: data.domains || [] },
27
+ { collection: 'systems', items: data.systems || [] },
28
+ { collection: 'agents', items: data.agents || [] },
29
+ { collection: 'data-products', items: data['data-products'] || [] },
30
+ ]
31
+ : [];
32
+
33
+ return [
34
+ ...domainOnlyGroups,
35
+ { collection: 'services', items: data.services || [] },
36
+ { collection: 'flows', items: data.flows || [] },
37
+ { collection: 'entities', items: data.entities || [] },
38
+ { collection: 'containers', items: data.containers || [] },
39
+ { collection: 'events', items: domainMessages.filter((message) => message.collection === 'events') },
40
+ { collection: 'commands', items: domainMessages.filter((message) => message.collection === 'commands') },
41
+ { collection: 'queries', items: domainMessages.filter((message) => message.collection === 'queries') },
42
+ ];
43
+ };
44
+
45
+ export const hasResources = (owner: any, ownerCollection: ResourceOwnerCollection): boolean =>
46
+ getResourceGroups(owner?.data ?? {}, ownerCollection).some(({ items }) => items.length > 0);
@@ -7,6 +7,7 @@ import {
7
7
  type SystemResourceItem,
8
8
  type SystemResourceCollection,
9
9
  } from '@components/Tables/SystemResources/SystemResourcesTable';
10
+ import { getResourceGroups, type ResourceOwnerCollection } from './_resources';
10
11
 
11
12
  import { Page } from './_index.data';
12
13
 
@@ -15,35 +16,11 @@ export const getStaticPaths = Page.getStaticPaths;
15
16
 
16
17
  const resourceOwner = await Page.getData(Astro);
17
18
  const { data } = resourceOwner;
18
- const ownerCollection = resourceOwner.collection as 'systems' | 'domains';
19
+ const ownerCollection = resourceOwner.collection as ResourceOwnerCollection;
19
20
 
20
- // Domain-level messages come from the domain's own `sends` (Domain Events) and
21
- // `receives` (External Events). Each resolved entry already carries its own
22
- // collection (events / commands / queries). Dedupe across sends + receives.
23
- const messageEntries = (() => {
24
- const seen = new Map<string, any>();
25
- for (const message of [...(data.sends || []), ...(data.receives || [])]) {
26
- if (!message?.data?.id || !message?.collection) continue;
27
- seen.set(`${message.collection}:${message.data.id}:${message.data.version}`, message);
28
- }
29
- return Array.from(seen.values());
30
- })();
31
-
32
- const messagesByCollection = (collection: SystemResourceCollection) =>
33
- messageEntries.filter((message: any) => message.collection === collection);
34
-
35
- // Only show resources that are directly attached to the system/domain — not resources
36
- // nested further downstream. Domains can't hold containers directly, so a domain's
37
- // Resources page shows services, flows, entities and its own messages.
38
- const resourceGroups: Array<{ collection: SystemResourceCollection; items: any[] }> = [
39
- { collection: 'services', items: data.services || [] },
40
- { collection: 'flows', items: data.flows || [] },
41
- { collection: 'entities', items: data.entities || [] },
42
- { collection: 'containers', items: data.containers || [] },
43
- { collection: 'events', items: messagesByCollection('events') },
44
- { collection: 'commands', items: messagesByCollection('commands') },
45
- { collection: 'queries', items: messagesByCollection('queries') },
46
- ];
21
+ // Only show resources directly attached to this system/domain. Domain child resources
22
+ // are not flattened, so services inside a child system remain on that system's page.
23
+ const resourceGroups = getResourceGroups(data, ownerCollection);
47
24
 
48
25
  const hrefForResource = (collection: SystemResourceCollection, id: string, version: string) => {
49
26
  if (collection === 'entities') {
@@ -72,7 +49,8 @@ const resources: SystemResourceItem[] = resourceGroups.flatMap(({ collection, it
72
49
  <div class="border-b border-[rgb(var(--ec-page-border))] pb-4">
73
50
  <h2 class="text-2xl md:text-4xl font-bold text-[rgb(var(--ec-page-text))]">{data.name} Resources</h2>
74
51
  <h2 class="text-lg pt-2 text-[rgb(var(--ec-page-text-muted))] font-light">
75
- A group of resources that are mapped to the {data.name} {ownerCollection === 'domains' ? 'domain' : 'system'}.
52
+ A group of resources that are mapped to the {data.name}{' '}
53
+ {ownerCollection === 'domains' ? 'domain' : 'system'}.
76
54
  </h2>
77
55
  </div>
78
56
 
@@ -81,10 +81,14 @@ export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[
81
81
  const sortedReceivesMessages = [...receivesMessages].sort(byResourceName);
82
82
  const renderMessages = shouldRenderSideBarSection(domain, 'messages');
83
83
 
84
- // The Resources page/link only makes sense when the domain actually has resources
85
- // attached (services, flows, entities or its own messages). Mirrors what the page renders.
84
+ // The Resources page/link only makes sense when the domain has direct resources.
85
+ // Keep this aligned with the resource groups rendered by the page.
86
86
  const hasResources =
87
- servicesInDomain.length > 0 ||
87
+ subDomains.length > 0 ||
88
+ systemsInDomain.length > 0 ||
89
+ agentsInDomain.length > 0 ||
90
+ dataProductsInDomain.length > 0 ||
91
+ allServicesInDomain.length > 0 ||
88
92
  domainFlows.length > 0 ||
89
93
  entitiesInDomain.length > 0 ||
90
94
  sendsMessages.length > 0 ||
@@ -58,15 +58,13 @@ export const buildServiceNode = (
58
58
  const renderEntities = serviceEntities.length > 0 && shouldRenderSideBarSection(service, 'entities');
59
59
  const renderOwners = owners.length > 0 && shouldRenderSideBarSection(service, 'owners');
60
60
  const renderRepository = service.data.repository && shouldRenderSideBarSection(service, 'repository');
61
- const isLatestVersion = service.data.version === service.data.latestVersion;
62
- const docsBasePath = `/docs/services/${service.data.id}${isLatestVersion ? '' : `/${service.data.version}`}`;
61
+ const docsBasePath = `/docs/services/${service.data.id}/${service.data.version}`;
63
62
  const docsSection = buildResourceDocsSection(
64
63
  'services',
65
64
  service.data.id,
66
65
  service.data.version,
67
66
  context.resourceDocs,
68
- context.resourceDocCategories,
69
- { includeVersionInUrl: !isLatestVersion }
67
+ context.resourceDocCategories
70
68
  );
71
69
 
72
70
  // Diagrams
@@ -24,6 +24,7 @@ export type NavNode = {
24
24
  type: 'group' | 'item';
25
25
  title: string;
26
26
  collapseKey?: string; // Stable key used to persist collapse state for inline groups
27
+ collapsible?: boolean; // Set to false to keep a group expanded regardless of its size
27
28
  icon?: string; // Lucide icon name
28
29
  subtle?: boolean; // Render lightweight styling for nested subgroup headers
29
30
  leftIcon?: string; // Path to SVG icon shown on the left of the label
@@ -1104,6 +1104,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
1104
1104
  allList = {
1105
1105
  type: 'group',
1106
1106
  title: 'Browse',
1107
+ collapsible: false,
1107
1108
  icon: 'Telescope',
1108
1109
  pages: validAllChildren,
1109
1110
  };
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "license": "SEE LICENSE IN LICENSE",
9
9
  "type": "module",
10
- "version": "4.6.0",
10
+ "version": "4.6.1",
11
11
  "publishConfig": {
12
12
  "access": "public"
13
13
  },
@@ -32,10 +32,10 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "@ai-sdk/react": "^3.0.17",
35
- "@astrojs/markdown-remark": "^7.2.1",
36
- "@astrojs/mdx": "^7.0.3",
37
- "@astrojs/node": "^11.0.2",
38
- "@astrojs/react": "^6.0.1",
35
+ "@astrojs/markdown-remark": "^7.2.2",
36
+ "@astrojs/mdx": "^7.0.5",
37
+ "@astrojs/node": "^11.1.0",
38
+ "@astrojs/react": "^6.0.2",
39
39
  "@astrojs/rss": "^4.0.19",
40
40
  "@asyncapi/avro-schema-parser": "3.0.24",
41
41
  "@asyncapi/parser": "^3.6.0",
@@ -61,7 +61,7 @@
61
61
  "@tanstack/react-table": "^8.17.3",
62
62
  "@xyflow/react": "^12.3.6",
63
63
  "ai": "^6.0.17",
64
- "astro": "^7.1.1",
64
+ "astro": "^7.2.0",
65
65
  "astro-expressive-code": "^0.44.0",
66
66
  "astro-seo": "^0.8.4",
67
67
  "auth-astro": "^4.2.0",
@@ -81,7 +81,7 @@
81
81
  "gray-matter": "^4.0.3",
82
82
  "hono": "4.12.27",
83
83
  "html-to-image": "^1.11.11",
84
- "js-yaml": "^4.3.0",
84
+ "js-yaml": "^4.3.1",
85
85
  "jsonpath-plus": "^10.4.0",
86
86
  "jsonwebtoken": "^9.0.2",
87
87
  "lodash.debounce": "^4.0.8",
@@ -120,11 +120,11 @@
120
120
  "uuid": "^10.0.0",
121
121
  "zod": "^4.3.6",
122
122
  "@eventcatalog/linter": "1.1.10",
123
- "@eventcatalog/visualiser": "^4.1.1",
124
- "@eventcatalog/sdk": "2.27.1"
123
+ "@eventcatalog/sdk": "2.27.1",
124
+ "@eventcatalog/visualiser": "^4.1.1"
125
125
  },
126
126
  "devDependencies": {
127
- "@astrojs/check": "^0.9.9",
127
+ "@astrojs/check": "^0.9.10",
128
128
  "@types/d3-drag": "^3.0.7",
129
129
  "@types/d3-force": "^3.0.10",
130
130
  "@types/d3-selection": "^3.0.11",