@eventcatalog/core 3.48.4 → 4.0.0-beta.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 (109) 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/catalog-to-astro-content-directory.cjs +1 -1
  6. package/dist/chunk-BWN544LB.js +73 -0
  7. package/dist/{chunk-XNVSSL4W.js → chunk-JTZT2SDF.js} +1 -1
  8. package/dist/{chunk-JY4H7A4S.js → chunk-M2ZRT7KQ.js} +1 -1
  9. package/dist/{chunk-SY4XL6OQ.js → chunk-MBVTS76Q.js} +1 -1
  10. package/dist/{chunk-KDLSBQRP.js → chunk-TZTPGQGL.js} +1 -1
  11. package/dist/{chunk-FNDSHOV2.js → chunk-U5OTNDVQ.js} +1 -1
  12. package/dist/constants.cjs +1 -1
  13. package/dist/constants.js +1 -1
  14. package/dist/core-node-modules.cjs +108 -0
  15. package/dist/core-node-modules.d.cts +7 -0
  16. package/dist/core-node-modules.d.ts +7 -0
  17. package/dist/core-node-modules.js +8 -0
  18. package/dist/docs/development/components/components/15-prompt.md +5 -1
  19. package/dist/eventcatalog.cjs +124 -48
  20. package/dist/eventcatalog.js +26 -15
  21. package/dist/generate.cjs +1 -1
  22. package/dist/generate.js +3 -3
  23. package/dist/utils/cli-logger.cjs +1 -1
  24. package/dist/utils/cli-logger.js +2 -2
  25. package/eventcatalog/astro.config.mjs +27 -23
  26. package/eventcatalog/public/icons/languages/java.svg +1 -0
  27. package/eventcatalog/public/icons/payments/stripe.svg +1 -0
  28. package/eventcatalog/src/components/Badge.astro +10 -10
  29. package/eventcatalog/src/components/CopyAsMarkdown.tsx +154 -0
  30. package/eventcatalog/src/components/Grids/DomainGrid.tsx +207 -44
  31. package/eventcatalog/src/components/Grids/MessageGrid.tsx +0 -20
  32. package/eventcatalog/src/components/Grids/SystemGrid.tsx +185 -0
  33. package/eventcatalog/src/components/MDX/ADRTable/ADRTable.astro +99 -0
  34. package/eventcatalog/src/components/MDX/ADRTable/ADRTable.client.tsx +400 -0
  35. package/eventcatalog/src/components/MDX/ContextDiagram/ContextDiagramPortal.tsx +21 -0
  36. package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +1 -0
  37. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +42 -6
  38. package/eventcatalog/src/components/MDX/Prompt/Prompt.astro +62 -9
  39. package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +8 -1
  40. package/eventcatalog/src/components/MDX/SystemContextMap/SystemContextMapPortal.tsx +22 -0
  41. package/eventcatalog/src/components/MDX/components.tsx +7 -0
  42. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +1 -1
  43. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +67 -37
  44. package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +47 -0
  45. package/eventcatalog/src/components/Tables/Discover/FilterComponents.tsx +10 -2
  46. package/eventcatalog/src/components/Tables/Discover/columns.tsx +115 -27
  47. package/eventcatalog/src/components/Tables/Discover/index.ts +7 -1
  48. package/eventcatalog/src/components/Tables/SystemResources/SystemResourcesTable.tsx +267 -0
  49. package/eventcatalog/src/content.config.ts +96 -8
  50. package/eventcatalog/src/enterprise/collections/resource-docs-utils.ts +4 -1
  51. package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +12 -13
  52. package/eventcatalog/src/enterprise/custom-documentation/utils/custom-docs.ts +6 -0
  53. package/eventcatalog/src/enterprise/feature.ts +3 -1
  54. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +96 -140
  55. package/eventcatalog/src/layouts/VisualiserLayout.astro +66 -0
  56. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +4 -2
  57. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +2 -0
  58. package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +15 -15
  59. package/eventcatalog/src/pages/discover/[type]/_index.data.ts +1 -0
  60. package/eventcatalog/src/pages/discover/[type]/index.astro +47 -2
  61. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +15 -14
  62. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +13 -14
  63. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +1 -0
  64. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +11 -1
  65. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +81 -26
  66. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.ts +89 -0
  67. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +85 -0
  68. package/eventcatalog/src/pages/docs/[type]/[id]/[version].md.ts +2 -0
  69. package/eventcatalog/src/pages/docs/[type]/[id]/[version].mdx.ts +2 -0
  70. package/eventcatalog/src/pages/docs/[type]/[id]/_index.data.ts +1 -0
  71. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +24 -1
  72. package/eventcatalog/src/pages/docs/users/[id]/index.astro +24 -1
  73. package/eventcatalog/src/pages/index.astro +30 -7
  74. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +1 -0
  75. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/index.astro +1 -1
  76. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version].mermaid.ts +3 -0
  77. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/_index.data.ts +94 -0
  78. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/index.astro +51 -0
  79. package/eventcatalog/src/pages/visualiser/system-context-map/index.astro +41 -0
  80. package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/_index.data.ts +91 -0
  81. package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/index.astro +51 -0
  82. package/eventcatalog/src/stores/sidebar-store/builders/adr.ts +3 -1
  83. package/eventcatalog/src/stores/sidebar-store/builders/agent.ts +3 -1
  84. package/eventcatalog/src/stores/sidebar-store/builders/container.ts +1 -1
  85. package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +1 -1
  86. package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +127 -49
  87. package/eventcatalog/src/stores/sidebar-store/builders/message.ts +1 -1
  88. package/eventcatalog/src/stores/sidebar-store/builders/service.ts +4 -2
  89. package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +2 -1
  90. package/eventcatalog/src/stores/sidebar-store/builders/system.ts +166 -0
  91. package/eventcatalog/src/stores/sidebar-store/state.ts +99 -6
  92. package/eventcatalog/src/types/index.ts +2 -0
  93. package/eventcatalog/src/utils/collection-colors.ts +6 -1
  94. package/eventcatalog/src/utils/collections/domains.ts +35 -0
  95. package/eventcatalog/src/utils/collections/icons.ts +4 -5
  96. package/eventcatalog/src/utils/collections/systems.ts +110 -0
  97. package/eventcatalog/src/utils/collections/teams.ts +15 -10
  98. package/eventcatalog/src/utils/collections/users.ts +7 -4
  99. package/eventcatalog/src/utils/collections/util.ts +2 -0
  100. package/eventcatalog/src/utils/icon-map.ts +20 -0
  101. package/eventcatalog/src/utils/icon.ts +11 -0
  102. package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +12 -2
  103. package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +0 -170
  104. package/eventcatalog/src/utils/node-graphs/system-context-node-graph.ts +400 -0
  105. package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +208 -0
  106. package/eventcatalog/src/utils/node-graphs/utils/utils.ts +12 -0
  107. package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +2 -0
  108. package/package.json +17 -14
  109. package/eventcatalog/src/pages/visualiser/context-map/index.astro +0 -30
@@ -5,6 +5,7 @@ import { getAdrAliasNodeKey, getAdrNodeKey, getAdrs, type Adr } from '@utils/col
5
5
  import { ADR_STATUS_VALUES, formatAdrStatus } from '@utils/collections/adr-constants';
6
6
  import { getContainers } from '@utils/collections/containers';
7
7
  import { getDomains } from '@utils/collections/domains';
8
+ import { getSystems } from '@utils/collections/systems';
8
9
  import { getServices } from '@utils/collections/services';
9
10
  import { getMessages, pluralizeMessageType } from '@utils/collections/messages';
10
11
  import { getOwner } from '@utils/collections/owners';
@@ -19,6 +20,7 @@ import { buildUrl } from '@utils/url-builder';
19
20
  import type { NavigationData, NavNode, ChildRef } from './builders/shared';
20
21
  import { buildAgentNode } from './builders/agent';
21
22
  import { buildDomainNode } from './builders/domain';
23
+ import { buildSystemNode } from './builders/system';
22
24
  import { buildServiceNode } from './builders/service';
23
25
  import { buildMessageNode } from './builders/message';
24
26
  import { buildContainerNode } from './builders/container';
@@ -274,6 +276,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
274
276
 
275
277
  const [
276
278
  domains,
279
+ systems,
277
280
  agents,
278
281
  services,
279
282
  { events, commands, queries },
@@ -293,6 +296,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
293
296
  resourceDocCategories,
294
297
  ] = await Promise.all([
295
298
  getDomains({ getAllVersions: false, includeServicesInSubdomains: false }),
299
+ getSystems({ getAllVersions: false }),
296
300
  getAgents({ getAllVersions: false }),
297
301
  getServices({ getAllVersions: false }),
298
302
  getMessages({ getAllVersions: false }),
@@ -322,6 +326,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
322
326
  agents,
323
327
  services,
324
328
  domains,
329
+ systems,
325
330
  events,
326
331
  commands,
327
332
  queries,
@@ -392,6 +397,15 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
392
397
  })
393
398
  );
394
399
 
400
+ const systemWithOwners = await Promise.all(
401
+ systems.map(async (system) => {
402
+ const ownersInSystem = system.data.owners || [];
403
+ const owners = await Promise.all(ownersInSystem.map((owner) => getOwner(owner)));
404
+ const filteredOwners = owners.filter((o) => o !== undefined) as Array<NonNullable<(typeof owners)[0]>>;
405
+ return { system, owners: filteredOwners };
406
+ })
407
+ );
408
+
395
409
  const adrsWithOwners = await Promise.all(
396
410
  adrs.map(async (adr) => {
397
411
  const owners = await Promise.all((adr.data.owners || []).map((owner) => getOwner(owner)));
@@ -607,6 +621,19 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
607
621
  {} as Record<string, NavNode | string>
608
622
  );
609
623
 
624
+ const systemNodes = systemWithOwners.reduce(
625
+ (acc, { system, owners }) => {
626
+ const versionedKey = `system:${system.data.id}:${system.data.version}`;
627
+ acc[versionedKey] = withArchitectureDecisionsSection(buildSystemNode(system, owners, context), system, adrs);
628
+ if (system.data.latestVersion === system.data.version) {
629
+ // Store reference to versioned key instead of duplicating the full node
630
+ acc[`system:${system.data.id}`] = versionedKey;
631
+ }
632
+ return acc;
633
+ },
634
+ {} as Record<string, NavNode | string>
635
+ );
636
+
610
637
  // Get owners for data products
611
638
  const dataProductWithOwners = await Promise.all(
612
639
  dataProducts.map(async (dataProduct) => {
@@ -781,6 +808,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
781
808
  {} as Record<string, NavNode>
782
809
  );
783
810
 
811
+ const visualiserEnabled = config?.visualiser?.enabled !== false;
784
812
  const rootDomainsNodes: Record<string, NavNode> = {};
785
813
 
786
814
  if (rootDomains.length > 0) {
@@ -792,6 +820,22 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
792
820
  };
793
821
  }
794
822
 
823
+ const topLevelDiagramsNode =
824
+ visualiserEnabled && systems.length > 0
825
+ ? {
826
+ type: 'group' as const,
827
+ title: 'Top level diagrams',
828
+ icon: 'Workflow',
829
+ pages: [
830
+ {
831
+ type: 'item' as const,
832
+ title: 'System Context Map',
833
+ href: buildUrl('/visualiser/system-context-map'),
834
+ },
835
+ ],
836
+ }
837
+ : undefined;
838
+
795
839
  const createLeaf = (items: any[], node: NavNode) => (items.length > 0 ? node : undefined);
796
840
 
797
841
  const domainsList = createLeaf(domains, {
@@ -801,6 +845,38 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
801
845
  pages: sortByResourceName(domains).map((domain) => `domain:${domain.data.id}:${domain.data.version}`),
802
846
  });
803
847
 
848
+ // Split systems by scope (defaults to internal). When there are systems of both
849
+ // scopes we show two sub-sections (Internal / External), each ordered by name;
850
+ // otherwise we keep a flat list so a single-scope catalog isn't over-nested.
851
+ const internalSystems = systems.filter((system) => (system.data.scope ?? 'internal') !== 'external');
852
+ const externalSystems = systems.filter((system) => system.data.scope === 'external');
853
+
854
+ const systemPageKeys = (items: typeof systems) =>
855
+ sortByResourceName(items).map((system) => `system:${system.data.id}:${system.data.version}`);
856
+
857
+ const internalSystemsList = createLeaf(internalSystems, {
858
+ type: 'group',
859
+ title: 'Internal',
860
+ icon: 'Group',
861
+ pages: systemPageKeys(internalSystems),
862
+ });
863
+
864
+ const externalSystemsScopedList = createLeaf(externalSystems, {
865
+ type: 'group',
866
+ title: 'External',
867
+ icon: 'Group',
868
+ pages: systemPageKeys(externalSystems),
869
+ });
870
+
871
+ const hasBothSystemScopes = internalSystems.length > 0 && externalSystems.length > 0;
872
+
873
+ const systemsList = createLeaf(systems, {
874
+ type: 'item',
875
+ title: 'Systems',
876
+ icon: 'Group',
877
+ pages: hasBothSystemScopes ? ['list:systems-internal', 'list:systems-external'] : systemPageKeys(systems),
878
+ });
879
+
804
880
  const agentsList = createLeaf(agents, {
805
881
  type: 'item',
806
882
  title: 'Agents',
@@ -811,7 +887,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
811
887
  const adrsList = createLeaf(adrs, {
812
888
  type: 'item',
813
889
  title: 'Decision Records',
814
- icon: 'BookText',
890
+ icon: 'ClipboardList',
815
891
  pages: groupAdrsByStatus(adrs),
816
892
  });
817
893
 
@@ -939,6 +1015,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
939
1015
 
940
1016
  const allChildrenKeys = [
941
1017
  'list:domains',
1018
+ 'list:systems',
942
1019
  'list:services',
943
1020
  'list:agents',
944
1021
  'list:adrs',
@@ -954,6 +1031,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
954
1031
  ];
955
1032
  const allChildrenNodes = [
956
1033
  domainsList,
1034
+ systemsList,
957
1035
  servicesList,
958
1036
  agentsList,
959
1037
  adrsList,
@@ -968,10 +1046,22 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
968
1046
  peopleList,
969
1047
  ];
970
1048
 
1049
+ // The Browse list leads with the resources people reach for most — Domains,
1050
+ // Systems, Services then Messages — and falls back to A-Z for everything else.
1051
+ const browseOrderPriority = ['list:domains', 'list:systems', 'list:services', 'list:messages'];
1052
+ const browseRank = (key: string) => {
1053
+ const index = browseOrderPriority.indexOf(key);
1054
+ return index === -1 ? browseOrderPriority.length : index;
1055
+ };
1056
+
971
1057
  const validAllChildren = allChildrenKeys
972
1058
  .map((key, idx) => ({ key, node: allChildrenNodes[idx] }))
973
1059
  .filter((item): item is { key: string; node: NavNode } => item.node !== undefined)
974
- .sort((a, b) => a.node.title.localeCompare(b.node.title))
1060
+ .sort((a, b) => {
1061
+ const rankDifference = browseRank(a.key) - browseRank(b.key);
1062
+ if (rankDifference !== 0) return rankDifference;
1063
+ return a.node.title.localeCompare(b.node.title);
1064
+ })
975
1065
  .map((item) => item.key);
976
1066
 
977
1067
  let allList;
@@ -986,6 +1076,9 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
986
1076
 
987
1077
  const allNodes: Record<string, NavNode> = {
988
1078
  ...(domainsList ? { 'list:domains': domainsList } : {}),
1079
+ ...(systemsList ? { 'list:systems': systemsList } : {}),
1080
+ ...(internalSystemsList ? { 'list:systems-internal': internalSystemsList } : {}),
1081
+ ...(externalSystemsScopedList ? { 'list:systems-external': externalSystemsScopedList } : {}),
989
1082
  ...(servicesList ? { 'list:services': servicesList } : {}),
990
1083
  ...(agentsList ? { 'list:agents': agentsList } : {}),
991
1084
  ...(adrsList ? { 'list:adrs': adrsList } : {}),
@@ -1008,7 +1101,6 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
1008
1101
 
1009
1102
  // System-level views (only show if visualiser is enabled and there are domains)
1010
1103
  const systemNode: Record<string, NavNode> = {};
1011
- const visualiserEnabled = config?.visualiser?.enabled !== false;
1012
1104
 
1013
1105
  if (visualiserEnabled && domains.length > 0) {
1014
1106
  systemNode['list:system'] = {
@@ -1027,7 +1119,9 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
1027
1119
 
1028
1120
  const allGeneratedNodes = {
1029
1121
  ...rootDomainsNodes,
1122
+ ...(topLevelDiagramsNode ? { 'list:top-level-diagrams': topLevelDiagramsNode } : {}),
1030
1123
  ...domainNodes,
1124
+ ...systemNodes,
1031
1125
  ...agentNodes,
1032
1126
  ...adrNodes,
1033
1127
  ...serviceNodes,
@@ -1047,9 +1141,8 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
1047
1141
 
1048
1142
  // only filter if child is string
1049
1143
  const defaultPages = ['list:top-level-domains', 'list:all'];
1050
- // Add system section if it exists
1051
- if (systemNode['list:system']) {
1052
- defaultPages.push('list:system');
1144
+ if (topLevelDiagramsNode) {
1145
+ defaultPages.splice(1, 0, 'list:top-level-diagrams');
1053
1146
  }
1054
1147
  const rootNavigationConfig = config?.navigation?.pages || defaultPages;
1055
1148
 
@@ -5,6 +5,7 @@ export type CollectionTypes =
5
5
  | 'events'
6
6
  | 'queries'
7
7
  | 'domains'
8
+ | 'systems'
8
9
  | 'services'
9
10
  | 'flows'
10
11
  | 'channels'
@@ -22,6 +23,7 @@ export type PageTypes =
22
23
  | 'queries'
23
24
  | 'services'
24
25
  | 'domains'
26
+ | 'systems'
25
27
  | 'channels'
26
28
  | 'flows'
27
29
  | 'entities'
@@ -10,7 +10,8 @@ export type CollectionColor =
10
10
  | 'gray'
11
11
  | 'cyan'
12
12
  | 'sky'
13
- | 'indigo';
13
+ | 'indigo'
14
+ | 'violet';
14
15
 
15
16
  export const getColorForCollection = (collection: string): CollectionColor => {
16
17
  switch (collection) {
@@ -34,6 +35,8 @@ export const getColorForCollection = (collection: string): CollectionColor => {
34
35
  return 'purple';
35
36
  case 'domains':
36
37
  return 'yellow';
38
+ case 'systems':
39
+ return 'violet';
37
40
  case 'agents':
38
41
  return 'sky';
39
42
  case 'adrs':
@@ -62,6 +65,7 @@ export const tailwind500RgbByColor: Record<CollectionColor, string> = {
62
65
  cyan: '6 182 212',
63
66
  sky: '14 165 233',
64
67
  indigo: '99 102 241',
68
+ violet: '139 92 246',
65
69
  };
66
70
 
67
71
  export const collectionTextColorClassByColor: Record<CollectionColor, string> = {
@@ -77,6 +81,7 @@ export const collectionTextColorClassByColor: Record<CollectionColor, string> =
77
81
  cyan: 'text-cyan-500',
78
82
  sky: 'text-sky-500',
79
83
  indigo: 'text-indigo-500',
84
+ violet: 'text-violet-500',
80
85
  };
81
86
 
82
87
  export const getCollectionTextColorClass = (color: string, fallback = 'text-gray-500'): string =>
@@ -123,6 +123,7 @@ export const getDomains = async ({
123
123
  allServices,
124
124
  allEntities,
125
125
  allFlows,
126
+ allSystems,
126
127
  allEvents,
127
128
  allCommands,
128
129
  allQueries,
@@ -134,6 +135,7 @@ export const getDomains = async ({
134
135
  getCollection('services'),
135
136
  getCollection('entities'),
136
137
  getCollection('flows'),
138
+ getCollection('systems'),
137
139
  getCollection('events'),
138
140
  getCollection('commands'),
139
141
  getCollection('queries'),
@@ -151,6 +153,7 @@ export const getDomains = async ({
151
153
  const serviceMap = createVersionedMap(allServices);
152
154
  const entityMap = createVersionedMap(allEntities);
153
155
  const flowMap = createVersionedMap(allFlows);
156
+ const systemMap = createVersionedMap(allSystems);
154
157
  const dataProductMap = createVersionedMap(allDataProducts);
155
158
 
156
159
  // 3. Filter the domains we actually want to process/return
@@ -220,6 +223,37 @@ export const getDomains = async ({
220
223
  .map((flow: { id: string; version: string | undefined }) => findInMap(flowMap, flow.id, flow.version))
221
224
  .filter((f): f is CollectionEntry<'flows'> => !!f);
222
225
 
226
+ // Resolve Systems (and hydrate each system's services so they can be
227
+ // rendered in the domain's architecture view, like subdomains)
228
+ const systemsInDomain = domain.data.systems || [];
229
+ const systems = systemsInDomain
230
+ .map((system: { id: string; version: string | undefined }) => findInMap(systemMap, system.id, system.version))
231
+ .filter((s): s is CollectionEntry<'systems'> => !!s)
232
+ .map((system: any) => {
233
+ const systemServices = enrichServices
234
+ ? hydrateServices(system.data.services || [], serviceMap, messageMap, containerMap)
235
+ : (system.data.services || [])
236
+ .map((service: { id: string; version: string | undefined }) => findInMap(serviceMap, service.id, service.version))
237
+ .filter((s: any) => !!s);
238
+
239
+ // Hydrate the system's data stores (containers) so they can be
240
+ // rendered in the domain's architecture view.
241
+ const systemContainers = (system.data.containers || [])
242
+ .map((container: { id: string; version: string | undefined }) =>
243
+ findInMap(containerMap, container.id, container.version)
244
+ )
245
+ .filter((c: any) => !!c);
246
+
247
+ return {
248
+ ...system,
249
+ data: {
250
+ ...system.data,
251
+ services: systemServices as any,
252
+ containers: systemContainers as any,
253
+ },
254
+ };
255
+ });
256
+
223
257
  // Resolve Data Products
224
258
  const dataProductsInDomain = domain.data['data-products'] || [];
225
259
  const dataProducts = dataProductsInDomain
@@ -274,6 +308,7 @@ export const getDomains = async ({
274
308
  agents: agents as any,
275
309
  services: services as any, // Cast to avoid deep type issues with enriched data
276
310
  domains: subDomains as any,
311
+ systems: systems as any,
277
312
  entities: entities as any,
278
313
  flows: flows as any,
279
314
  'data-products': dataProducts as any,
@@ -9,20 +9,21 @@ import {
9
9
  UserIcon,
10
10
  ArrowsRightLeftIcon,
11
11
  VariableIcon,
12
- MapIcon,
13
12
  CubeIcon,
14
13
  } from '@heroicons/react/24/outline';
15
- import { BookText, Bot, Box, DatabaseIcon } from 'lucide-react';
14
+ import { BookText, Bot, Box, ClipboardList, DatabaseIcon, Group } from 'lucide-react';
16
15
  import { getColorForCollection } from '@utils/collection-colors';
17
16
 
18
17
  export const getIconForCollection = (collection: string) => {
19
18
  switch (collection) {
20
19
  case 'domains':
21
20
  return RectangleGroupIcon;
21
+ case 'systems':
22
+ return Group;
22
23
  case 'agents':
23
24
  return Bot;
24
25
  case 'adrs':
25
- return BookText;
26
+ return ClipboardList;
26
27
  case 'services':
27
28
  return ServerIcon;
28
29
  case 'events':
@@ -43,8 +44,6 @@ export const getIconForCollection = (collection: string) => {
43
44
  return VariableIcon;
44
45
  case 'ubiquitousLanguages':
45
46
  return BookText;
46
- case 'bounded-context-map':
47
- return MapIcon;
48
47
  case 'entities':
49
48
  return Box;
50
49
  case 'containers':
@@ -0,0 +1,110 @@
1
+ import { getCollection } from 'astro:content';
2
+ import type { CollectionEntry } from 'astro:content';
3
+ import { createVersionedMap, findInMap } from './util';
4
+
5
+ const CACHE_ENABLED = process.env.DISABLE_EVENTCATALOG_CACHE !== 'true';
6
+ export type System = CollectionEntry<'systems'>;
7
+
8
+ interface Props {
9
+ getAllVersions?: boolean;
10
+ }
11
+
12
+ // cache for build time
13
+ let memoryCache: Record<string, System[]> = {};
14
+
15
+ export const getSystems = async ({ getAllVersions = true }: Props = {}): Promise<System[]> => {
16
+ const cacheKey = getAllVersions ? 'allVersions' : 'currentVersions';
17
+
18
+ if (memoryCache[cacheKey] && memoryCache[cacheKey].length > 0 && CACHE_ENABLED) {
19
+ return memoryCache[cacheKey];
20
+ }
21
+
22
+ const [allSystems, allServices, allFlows, allEntities, allContainers] = await Promise.all([
23
+ getCollection('systems'),
24
+ getCollection('services'),
25
+ getCollection('flows'),
26
+ getCollection('entities'),
27
+ getCollection('containers'),
28
+ ]);
29
+
30
+ // Build optimized map of id -> versions (sorted latest first)
31
+ const systemMap = createVersionedMap(allSystems);
32
+ const serviceMap = createVersionedMap(allServices);
33
+ const flowMap = createVersionedMap(allFlows);
34
+ const entityMap = createVersionedMap(allEntities);
35
+ const containerMap = createVersionedMap(allContainers);
36
+
37
+ // Filter systems
38
+ const targetSystems = allSystems.filter((system) => {
39
+ if (system.data.hidden === true) return false;
40
+ if (!getAllVersions && system.filePath?.includes('versioned')) return false;
41
+ return true;
42
+ });
43
+
44
+ // Enrich systems with version info
45
+ const processedSystems = targetSystems.map((system) => {
46
+ const systemVersions = systemMap.get(system.data.id) || [];
47
+ const latestVersion = systemVersions[0]?.data.version || system.data.version;
48
+ const versions = systemVersions.map((s) => s.data.version);
49
+
50
+ // Resolve service pointers to their full collection entries
51
+ const services = (system.data.services || [])
52
+ .map((service: { id: string; version?: string }) => findInMap(serviceMap, service.id, service.version))
53
+ .filter((s): s is NonNullable<typeof s> => !!s);
54
+
55
+ // Resolve flow pointers to their full collection entries
56
+ const flows = (system.data.flows || [])
57
+ .map((flow: { id: string; version?: string }) => findInMap(flowMap, flow.id, flow.version))
58
+ .filter((f): f is NonNullable<typeof f> => !!f);
59
+
60
+ // Resolve entity pointers to their full collection entries
61
+ const entities = (system.data.entities || [])
62
+ .map((entity: { id: string; version?: string }) => findInMap(entityMap, entity.id, entity.version))
63
+ .filter((e): e is NonNullable<typeof e> => !!e);
64
+
65
+ // Resolve container (data store) pointers to their full collection entries
66
+ const containers = (system.data.containers || [])
67
+ .map((container: { id: string; version?: string }) => findInMap(containerMap, container.id, container.version))
68
+ .filter((c): c is NonNullable<typeof c> => !!c);
69
+
70
+ // Resolve system-to-system relationship pointers (used by the System Diagram).
71
+ // Each relationship resolves to its target system while keeping the edge `label`.
72
+ // Dangling targets are dropped; relationships keep their label even if absent (the
73
+ // diagram decides whether to draw an edge based on the presence of a label).
74
+ const relationships = (system.data.relationships || [])
75
+ .map((relationship: { id: string; version?: string; label?: string }) => {
76
+ const target = findInMap(systemMap, relationship.id, relationship.version);
77
+ if (!target) return null;
78
+ return {
79
+ id: target.data.id,
80
+ version: target.data.version,
81
+ label: relationship.label,
82
+ data: target.data,
83
+ };
84
+ })
85
+ .filter((r): r is NonNullable<typeof r> => !!r);
86
+
87
+ return {
88
+ ...system,
89
+ data: {
90
+ ...system.data,
91
+ versions,
92
+ latestVersion,
93
+ services: services as any,
94
+ flows: flows as any,
95
+ entities: entities as any,
96
+ containers: containers as any,
97
+ relationships: relationships as any,
98
+ },
99
+ };
100
+ });
101
+
102
+ // order them by the name of the system
103
+ processedSystems.sort((a, b) => {
104
+ return (a.data.name || a.data.id).localeCompare(b.data.name || b.data.id);
105
+ });
106
+
107
+ memoryCache[cacheKey] = processedSystems;
108
+
109
+ return processedSystems;
110
+ };
@@ -16,16 +16,18 @@ export const getTeams = async (): Promise<Team[]> => {
16
16
  }
17
17
 
18
18
  // 1. Fetch all collections in parallel
19
- const [allTeams, allAgents, allDomains, allServices, allEvents, allCommands, allQueries, allAdrs] = await Promise.all([
20
- getCollection('teams'),
21
- getCollection('agents'),
22
- getCollection('domains'),
23
- getCollection('services'),
24
- getCollection('events'),
25
- getCollection('commands'),
26
- getCollection('queries'),
27
- getAdrs({ getAllVersions: false }),
28
- ]);
19
+ const [allTeams, allAgents, allDomains, allSystems, allServices, allEvents, allCommands, allQueries, allAdrs] =
20
+ await Promise.all([
21
+ getCollection('teams'),
22
+ getCollection('agents'),
23
+ getCollection('domains'),
24
+ getCollection('systems'),
25
+ getCollection('services'),
26
+ getCollection('events'),
27
+ getCollection('commands'),
28
+ getCollection('queries'),
29
+ getAdrs({ getAllVersions: false }),
30
+ ]);
29
31
 
30
32
  // 2. Filter teams
31
33
  const targetTeams = allTeams.filter((team) => team.data.hidden !== true);
@@ -50,6 +52,7 @@ export const getTeams = async (): Promise<Team[]> => {
50
52
 
51
53
  addToIndex(allAgents);
52
54
  addToIndex(allDomains);
55
+ addToIndex(allSystems);
53
56
  addToIndex(allServices);
54
57
  addToIndex(allEvents);
55
58
  addToIndex(allCommands);
@@ -64,6 +67,7 @@ export const getTeams = async (): Promise<Team[]> => {
64
67
  // Categorize items
65
68
  const ownedAgents = ownedItems.filter((i) => i.collection === 'agents') as CollectionEntry<'agents'>[];
66
69
  const ownedDomains = ownedItems.filter((i) => i.collection === 'domains') as CollectionEntry<'domains'>[];
70
+ const ownedSystems = ownedItems.filter((i) => i.collection === 'systems') as CollectionEntry<'systems'>[];
67
71
  const ownedServices = ownedItems.filter((i) => i.collection === 'services') as CollectionEntry<'services'>[];
68
72
  const ownedEvents = ownedItems.filter((i) => i.collection === 'events') as CollectionEntry<'events'>[];
69
73
  const ownedCommands = ownedItems.filter((i) => i.collection === 'commands') as CollectionEntry<'commands'>[];
@@ -76,6 +80,7 @@ export const getTeams = async (): Promise<Team[]> => {
76
80
  ...team.data,
77
81
  ownedAgents,
78
82
  ownedDomains,
83
+ ownedSystems,
79
84
  ownedServices,
80
85
  ownedCommands,
81
86
  ownedQueries,
@@ -17,19 +17,19 @@ export const getUsers = async (): Promise<User[]> => {
17
17
  }
18
18
 
19
19
  // 1. Fetch all collections in parallel
20
- const [allUsers, allAgents, allDomains, allServices, allEvents, allCommands, allQueries, allTeams, allAdrs] = await Promise.all(
21
- [
20
+ const [allUsers, allAgents, allDomains, allSystems, allServices, allEvents, allCommands, allQueries, allTeams, allAdrs] =
21
+ await Promise.all([
22
22
  getCollection('users'),
23
23
  getCollection('agents'),
24
24
  getCollection('domains'),
25
+ getCollection('systems'),
25
26
  getCollection('services'),
26
27
  getCollection('events'),
27
28
  getCollection('commands'),
28
29
  getCollection('queries'),
29
30
  getCollection('teams'),
30
31
  getAdrs({ getAllVersions: false }),
31
- ]
32
- );
32
+ ]);
33
33
 
34
34
  // 2. Filter users
35
35
  const targetUsers = allUsers.filter((user) => user.data.hidden !== true);
@@ -59,6 +59,7 @@ export const getUsers = async (): Promise<User[]> => {
59
59
 
60
60
  addToIndex(allAgents);
61
61
  addToIndex(allDomains);
62
+ addToIndex(allSystems);
62
63
  addToIndex(allServices);
63
64
  addToIndex(allEvents);
64
65
  addToIndex(allCommands);
@@ -93,6 +94,7 @@ export const getUsers = async (): Promise<User[]> => {
93
94
  // Categorize items
94
95
  const ownedAgents = allOwnedItems.filter((i) => i.collection === 'agents') as CollectionEntry<'agents'>[];
95
96
  const ownedDomains = allOwnedItems.filter((i) => i.collection === 'domains') as CollectionEntry<'domains'>[];
97
+ const ownedSystems = allOwnedItems.filter((i) => i.collection === 'systems') as CollectionEntry<'systems'>[];
96
98
  const ownedServices = allOwnedItems.filter((i) => i.collection === 'services') as CollectionEntry<'services'>[];
97
99
  const ownedEvents = allOwnedItems.filter((i) => i.collection === 'events') as CollectionEntry<'events'>[];
98
100
  const ownedCommands = allOwnedItems.filter((i) => i.collection === 'commands') as CollectionEntry<'commands'>[];
@@ -105,6 +107,7 @@ export const getUsers = async (): Promise<User[]> => {
105
107
  ...user.data,
106
108
  ownedAgents,
107
109
  ownedDomains,
110
+ ownedSystems,
108
111
  ownedServices,
109
112
  ownedEvents,
110
113
  ownedCommands,
@@ -180,6 +180,7 @@ export const resourceToCollectionMap = {
180
180
  command: 'commands',
181
181
  query: 'queries',
182
182
  domain: 'domains',
183
+ system: 'systems',
183
184
  flow: 'flows',
184
185
  channel: 'channels',
185
186
  user: 'users',
@@ -198,6 +199,7 @@ export const collectionToResourceMap = {
198
199
  commands: 'command',
199
200
  queries: 'query',
200
201
  domains: 'domain',
202
+ systems: 'system',
201
203
  flows: 'flow',
202
204
  channels: 'channel',
203
205
  users: 'user',
@@ -14,10 +14,13 @@ import {
14
14
  ArrowDownToLine,
15
15
  ArrowRightLeft,
16
16
  ArrowUpFromLine,
17
+ BotMessageSquare,
17
18
  BookOpen,
18
19
  BookText,
19
20
  Box,
20
21
  Boxes,
22
+ ClipboardCheck,
23
+ ClipboardList,
21
24
  Code,
22
25
  Database,
23
26
  File,
@@ -25,12 +28,19 @@ import {
25
28
  FileCode,
26
29
  FileImage,
27
30
  FileJson,
31
+ FileWarning,
28
32
  Globe,
33
+ ListChecks,
29
34
  Mail,
30
35
  Package,
36
+ Radar,
37
+ Radio,
38
+ Route,
31
39
  Search,
32
40
  Server,
41
+ ShieldCheck,
33
42
  ShoppingCart,
43
+ Sparkles,
34
44
  SquareMousePointer,
35
45
  Telescope,
36
46
  Terminal,
@@ -47,10 +57,13 @@ export const LUCIDE_ICON_MAP: Record<string, LucideIcon> = {
47
57
  ArrowDownToLine,
48
58
  ArrowRightLeft,
49
59
  ArrowUpFromLine,
60
+ BotMessageSquare,
50
61
  BookOpen,
51
62
  BookText,
52
63
  Box,
53
64
  Boxes,
65
+ ClipboardCheck,
66
+ ClipboardList,
54
67
  Code,
55
68
  Database,
56
69
  File,
@@ -58,12 +71,19 @@ export const LUCIDE_ICON_MAP: Record<string, LucideIcon> = {
58
71
  FileCode,
59
72
  FileImage,
60
73
  FileJson,
74
+ FileWarning,
61
75
  Globe,
76
+ ListChecks,
62
77
  Mail,
63
78
  Package,
79
+ Radar,
80
+ Radio,
81
+ Route,
64
82
  Search,
65
83
  Server,
84
+ ShieldCheck,
66
85
  ShoppingCart,
86
+ Sparkles,
67
87
  SquareMousePointer,
68
88
  Telescope,
69
89
  Terminal,