@eventcatalog/core 2.65.0 → 3.0.0-beta.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 (130) hide show
  1. package/README.md +1 -26
  2. package/dist/analytics/analytics.cjs +1 -1
  3. package/dist/analytics/analytics.js +2 -2
  4. package/dist/analytics/log-build.cjs +1 -1
  5. package/dist/analytics/log-build.js +3 -3
  6. package/dist/{chunk-NK6OYMRD.js → chunk-JB4YT5JY.js} +1 -1
  7. package/dist/{chunk-BMDTX5IN.js → chunk-TQ4HZREX.js} +1 -1
  8. package/dist/{chunk-IJRFYF4B.js → chunk-X4W4YC3U.js} +1 -1
  9. package/dist/constants.cjs +1 -1
  10. package/dist/constants.js +1 -1
  11. package/dist/eventcatalog.cjs +1 -21
  12. package/dist/eventcatalog.config.d.cts +10 -0
  13. package/dist/eventcatalog.config.d.ts +10 -0
  14. package/dist/eventcatalog.js +3 -20
  15. package/eventcatalog/src/components/CopyAsMarkdown.tsx +19 -1
  16. package/eventcatalog/src/components/FavoriteButton.tsx +54 -0
  17. package/eventcatalog/src/components/Grids/DomainGrid.tsx +386 -362
  18. package/eventcatalog/src/components/Grids/MessageGrid.tsx +166 -518
  19. package/eventcatalog/src/components/Header.astro +48 -23
  20. package/eventcatalog/src/components/Lists/VersionList.astro +2 -2
  21. package/eventcatalog/src/components/MDX/Design/Design.astro +4 -1
  22. package/eventcatalog/src/components/MDX/Flow/Flow.astro +2 -1
  23. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +3 -3
  24. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +8 -2
  25. package/eventcatalog/src/components/SchemaExplorer/SchemaPageViewer.tsx +37 -0
  26. package/eventcatalog/src/components/Search/Search.astro +48 -28
  27. package/eventcatalog/src/components/Search/SearchModal.tsx +393 -702
  28. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +298 -0
  29. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/container.ts +66 -0
  30. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/domain.ts +101 -0
  31. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/flow.ts +29 -0
  32. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/message.ts +84 -0
  33. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/service.ts +147 -0
  34. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/shared.ts +146 -0
  35. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +1073 -0
  36. package/eventcatalog/src/components/SideNav/NestedSideBar/sidebar-builder.ts +365 -0
  37. package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +90 -0
  38. package/eventcatalog/src/components/SideNav/SideNav.astro +18 -28
  39. package/eventcatalog/src/content.config.ts +2 -0
  40. package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +10 -4
  41. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +3 -3
  42. package/eventcatalog/src/layouts/DirectoryLayout.astro +2 -2
  43. package/eventcatalog/src/layouts/DiscoverLayout.astro +3 -3
  44. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +85 -63
  45. package/eventcatalog/src/layouts/VisualiserLayout.astro +3 -3
  46. package/eventcatalog/src/pages/_index.astro +530 -110
  47. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +64 -0
  48. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +29 -0
  49. package/eventcatalog/src/pages/directory/[type]/_index.data.ts +4 -4
  50. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +1 -4
  51. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +3 -3
  52. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +1 -5
  53. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +362 -190
  54. package/eventcatalog/src/pages/docs/[type]/[id]/[version].md.ts +1 -1
  55. package/eventcatalog/src/pages/docs/[type]/[id]/index.astro +4 -4
  56. package/eventcatalog/src/pages/docs/[type]/[id]/language/_index.data.ts +1 -4
  57. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +3 -27
  58. package/eventcatalog/src/pages/docs/teams/[id]/_index.data.ts +2 -2
  59. package/eventcatalog/src/pages/docs/users/[id]/_index.data.ts +2 -2
  60. package/eventcatalog/src/pages/index.astro +14 -5
  61. package/eventcatalog/src/pages/nav-index.json.ts +30 -0
  62. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/_index.data.ts +77 -0
  63. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +90 -0
  64. package/eventcatalog/src/pages/schemas/{index.astro → explorer/index.astro} +3 -3
  65. package/eventcatalog/src/pages/studio.astro +3 -3
  66. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +4 -3
  67. package/eventcatalog/src/pages/visualiser/[type]/[id]/index.astro +2 -2
  68. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/_index.data.ts +4 -3
  69. package/eventcatalog/src/stores/favorites-store.ts +83 -0
  70. package/eventcatalog/src/stores/sidebar-store.ts +8 -0
  71. package/eventcatalog/src/utils/collections/changelogs.ts +7 -4
  72. package/eventcatalog/src/utils/{channels.ts → collections/channels.ts} +81 -31
  73. package/eventcatalog/src/utils/collections/commands.ts +134 -0
  74. package/eventcatalog/src/utils/collections/containers.ts +44 -33
  75. package/eventcatalog/src/utils/collections/domains.ts +204 -62
  76. package/eventcatalog/src/utils/{entities.ts → collections/entities.ts} +44 -24
  77. package/eventcatalog/src/utils/collections/events.ts +136 -0
  78. package/eventcatalog/src/utils/collections/flows.ts +59 -25
  79. package/eventcatalog/src/utils/{messages.ts → collections/messages.ts} +13 -4
  80. package/eventcatalog/src/utils/{queries.ts → collections/queries.ts} +49 -28
  81. package/eventcatalog/src/utils/collections/services.ts +100 -68
  82. package/eventcatalog/src/utils/collections/teams.ts +94 -0
  83. package/eventcatalog/src/utils/collections/users.ts +122 -0
  84. package/eventcatalog/src/utils/collections/util.ts +57 -1
  85. package/eventcatalog/src/utils/feature.ts +3 -1
  86. package/eventcatalog/src/utils/{collections/file-diffs.ts → file-diffs.ts} +1 -1
  87. package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +2 -0
  88. package/eventcatalog/src/utils/node-graphs/domain-entity-map.ts +16 -6
  89. package/eventcatalog/src/utils/node-graphs/domains-canvas.ts +14 -10
  90. package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +36 -64
  91. package/eventcatalog/src/utils/node-graphs/flows-node-graph.ts +23 -19
  92. package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +36 -49
  93. package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +22 -18
  94. package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +4 -4
  95. package/eventcatalog/tailwind.config.mjs +14 -0
  96. package/eventcatalog/tsconfig.json +2 -1
  97. package/package.json +7 -4
  98. package/eventcatalog/public/logo_old.png +0 -0
  99. package/eventcatalog/src/components/DiscoverInsight.astro +0 -61
  100. package/eventcatalog/src/components/Grids/ServiceGrid.tsx +0 -534
  101. package/eventcatalog/src/components/Lists/CustomSideBarSectionList.astro +0 -55
  102. package/eventcatalog/src/components/Lists/ProtocolList.tsx +0 -74
  103. package/eventcatalog/src/components/Lists/RepositoryList.astro +0 -37
  104. package/eventcatalog/src/components/Lists/SpecificationsList.astro +0 -67
  105. package/eventcatalog/src/components/SideBars/ChannelSideBar.astro +0 -204
  106. package/eventcatalog/src/components/SideBars/ContainerSideBar.astro +0 -180
  107. package/eventcatalog/src/components/SideBars/DomainSideBar.astro +0 -273
  108. package/eventcatalog/src/components/SideBars/EntitySideBar.astro +0 -139
  109. package/eventcatalog/src/components/SideBars/FlowSideBar.astro +0 -128
  110. package/eventcatalog/src/components/SideBars/MessageSideBar.astro +0 -248
  111. package/eventcatalog/src/components/SideBars/ServiceSideBar.astro +0 -294
  112. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/CollapsibleGroup.tsx +0 -46
  113. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/MessageList.tsx +0 -78
  114. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/SpecificationList.tsx +0 -83
  115. package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +0 -1250
  116. package/eventcatalog/src/components/SideNav/ListViewSideBar/types.ts +0 -91
  117. package/eventcatalog/src/components/SideNav/ListViewSideBar/utils.ts +0 -201
  118. package/eventcatalog/src/components/SideNav/TreeView/getTreeView.ts +0 -190
  119. package/eventcatalog/src/components/SideNav/TreeView/index.tsx +0 -94
  120. package/eventcatalog/src/components/TreeView/index.tsx +0 -328
  121. package/eventcatalog/src/components/TreeView/styles.module.css +0 -264
  122. package/eventcatalog/src/components/TreeView/useSlots.ts +0 -95
  123. package/eventcatalog/src/pages/architecture/[type]/index.astro +0 -14
  124. package/eventcatalog/src/pages/architecture/architecture.astro +0 -101
  125. package/eventcatalog/src/pages/architecture/docs/[type]/index.astro +0 -14
  126. package/eventcatalog/src/utils/commands.ts +0 -112
  127. package/eventcatalog/src/utils/events.ts +0 -108
  128. package/eventcatalog/src/utils/generators/index.ts +0 -10
  129. package/eventcatalog/src/utils/teams.ts +0 -72
  130. package/eventcatalog/src/utils/users.ts +0 -72
@@ -0,0 +1,122 @@
1
+ import type { CollectionTypes } from '@types';
2
+ import { getCollection } from 'astro:content';
3
+ import type { CollectionEntry } from 'astro:content';
4
+ import path from 'path';
5
+
6
+ export type User = CollectionEntry<'users'>;
7
+
8
+ // Simple in-memory cache
9
+ let memoryCache: User[] = [];
10
+
11
+ export const getUsers = async (): Promise<User[]> => {
12
+ // console.time('✅ New getUsers');
13
+
14
+ if (memoryCache.length > 0) {
15
+ // console.timeEnd('✅ New getUsers');
16
+ return memoryCache;
17
+ }
18
+
19
+ // 1. Fetch all collections in parallel
20
+ const [allUsers, allDomains, allServices, allEvents, allCommands, allQueries, allTeams] = await Promise.all([
21
+ getCollection('users'),
22
+ getCollection('domains'),
23
+ getCollection('services'),
24
+ getCollection('events'),
25
+ getCollection('commands'),
26
+ getCollection('queries'),
27
+ getCollection('teams'),
28
+ ]);
29
+
30
+ // 2. Filter users
31
+ const targetUsers = allUsers.filter((user) => user.data.hidden !== true);
32
+ const visibleTeams = allTeams.filter((team) => team.data.hidden !== true);
33
+
34
+ // 3. Process users (Optimization: Iterate once over relationships if possible,
35
+ // but since we need to check ownership for EACH user against ALL items,
36
+ // we can't easily invert the map without building an "owner" index first.
37
+ // Given users/teams count is usually lower than events/services, iterating users and filtering items is acceptable,
38
+ // OR we can index items by ownerID for O(1) lookup. Let's try indexing items by ownerID.)
39
+
40
+ // Build Owner Index: Map<OwnerID, Item[]>
41
+ const ownershipMap = new Map<string, CollectionEntry<CollectionTypes>[]>();
42
+
43
+ const addToIndex = (items: CollectionEntry<CollectionTypes>[]) => {
44
+ for (const item of items) {
45
+ if (item.data.owners) {
46
+ for (const owner of item.data.owners) {
47
+ if (!ownershipMap.has(owner.id)) {
48
+ ownershipMap.set(owner.id, []);
49
+ }
50
+ ownershipMap.get(owner.id)!.push(item);
51
+ }
52
+ }
53
+ }
54
+ };
55
+
56
+ addToIndex(allDomains);
57
+ addToIndex(allServices);
58
+ addToIndex(allEvents);
59
+ addToIndex(allCommands);
60
+ addToIndex(allQueries);
61
+
62
+ // Team Membership Index: Map<UserID, Team[]>
63
+ const teamMembershipMap = new Map<string, typeof visibleTeams>();
64
+ for (const team of visibleTeams) {
65
+ if (team.data.members) {
66
+ for (const member of team.data.members) {
67
+ if (!teamMembershipMap.has(member.id)) {
68
+ teamMembershipMap.set(member.id, []);
69
+ }
70
+ teamMembershipMap.get(member.id)!.push(team);
71
+ }
72
+ }
73
+ }
74
+
75
+ const mappedUsers = targetUsers.map((user) => {
76
+ const userId = user.data.id;
77
+ const associatedTeams = teamMembershipMap.get(userId) || [];
78
+ const associatedTeamIds = associatedTeams.map((t) => t.data.id);
79
+
80
+ // Collect all owned items directly owned by user OR by their teams
81
+ const directOwnedItems = ownershipMap.get(userId) || [];
82
+ const teamOwnedItems = associatedTeamIds.flatMap((teamId) => ownershipMap.get(teamId) || []);
83
+
84
+ // Combine and deduplicate items (by ID+Version or just reference equality since they come from same source arrays)
85
+ const allOwnedItems = Array.from(new Set([...directOwnedItems, ...teamOwnedItems]));
86
+
87
+ // Categorize items
88
+ const ownedDomains = allOwnedItems.filter((i) => i.collection === 'domains') as CollectionEntry<'domains'>[];
89
+ const ownedServices = allOwnedItems.filter((i) => i.collection === 'services') as CollectionEntry<'services'>[];
90
+ const ownedEvents = allOwnedItems.filter((i) => i.collection === 'events') as CollectionEntry<'events'>[];
91
+ const ownedCommands = allOwnedItems.filter((i) => i.collection === 'commands') as CollectionEntry<'commands'>[];
92
+ const ownedQueries = allOwnedItems.filter((i) => i.collection === 'queries') as CollectionEntry<'queries'>[];
93
+
94
+ return {
95
+ ...user,
96
+ data: {
97
+ ...user.data,
98
+ ownedDomains,
99
+ ownedServices,
100
+ ownedEvents,
101
+ ownedCommands,
102
+ ownedQueries,
103
+ associatedTeams,
104
+ },
105
+ catalog: {
106
+ path: path.join(user.collection, user.id.replace('/index.mdx', '')),
107
+ filePath: path.join(process.cwd(), 'src', 'catalog-files', user.collection, user.id.replace('/index.mdx', '')),
108
+ type: 'user',
109
+ },
110
+ };
111
+ });
112
+
113
+ // order them by the name of the user
114
+ mappedUsers.sort((a, b) => {
115
+ return (a.data.name || a.data.id).localeCompare(b.data.name || b.data.id);
116
+ });
117
+
118
+ memoryCache = mappedUsers;
119
+ // console.timeEnd('✅ New getUsers');
120
+
121
+ return mappedUsers;
122
+ };
@@ -1,6 +1,6 @@
1
1
  import type { CollectionTypes } from '@types';
2
2
  import type { CollectionEntry } from 'astro:content';
3
- import { coerce, compare, eq, satisfies as satisfiesRange } from 'semver';
3
+ import semver, { coerce, compare, eq, satisfies as satisfiesRange } from 'semver';
4
4
 
5
5
  export const getPreviousVersion = (version: string, versions: string[]) => {
6
6
  const index = versions.indexOf(version);
@@ -116,6 +116,7 @@ export const resourceToCollectionMap = {
116
116
  user: 'users',
117
117
  team: 'teams',
118
118
  container: 'containers',
119
+ entity: 'entities',
119
120
  } as const;
120
121
 
121
122
  export const collectionToResourceMap = {
@@ -129,6 +130,7 @@ export const collectionToResourceMap = {
129
130
  users: 'user',
130
131
  teams: 'team',
131
132
  containers: 'container',
133
+ entities: 'entity',
132
134
  } as const;
133
135
 
134
136
  export const getDeprecatedDetails = (item: CollectionEntry<CollectionTypes>) => {
@@ -165,3 +167,57 @@ export const removeContentFromCollection = (collection: CollectionEntry<Collecti
165
167
  catalog: undefined,
166
168
  }));
167
169
  };
170
+
171
+ // --- OPTIMIZATION HELPERS ---
172
+
173
+ /**
174
+ * Groups items by ID and sorts them by version (Newest first).
175
+ * This allows O(1) lookup for "latest" (index 0) and specific versions.
176
+ */
177
+ export const createVersionedMap = <T extends { data: { id: string; version?: string } }>(items: T[]) => {
178
+ const map = new Map<string, T[]>();
179
+
180
+ for (const item of items) {
181
+ const id = item.data.id;
182
+ if (!map.has(id)) map.set(id, []);
183
+ map.get(id)!.push(item);
184
+ }
185
+
186
+ // Sort every entry so index [0] is always the latest version
187
+ for (const [key, list] of map.entries()) {
188
+ list.sort((a, b) => {
189
+ // specific version sorting logic (fallback to string compare if not valid semver)
190
+ const vA = a.data.version || '0.0.0';
191
+ const vB = b.data.version || '0.0.0';
192
+ return semver.valid(vB) && semver.valid(vA) ? semver.rcompare(vA, vB) : vB.localeCompare(vA);
193
+ });
194
+ }
195
+ return map;
196
+ };
197
+
198
+ /**
199
+ * Fast lookup helper.
200
+ * If version is provided, find it. If not, return the first (latest) item.
201
+ */
202
+ export const findInMap = <T extends { data: { version?: string } }>(
203
+ map: Map<string, T[]>,
204
+ id: string,
205
+ version?: string
206
+ ): T | undefined => {
207
+ const items = map.get(id);
208
+ if (!items || items.length === 0) return undefined;
209
+
210
+ // If no version specified or 'latest', return the first item (which is sorted to be latest)
211
+ if (!version || version === 'latest') return items[0];
212
+
213
+ // Try exact match
214
+ const exactMatch = items.find((i) => i.data.version === version);
215
+ if (exactMatch) return exactMatch;
216
+
217
+ // Try semver match if not exact
218
+ if (semver.validRange(version)) {
219
+ return items.find((i) => semver.satisfies(i.data.version || '0.0.0', version));
220
+ }
221
+
222
+ return undefined;
223
+ };
@@ -34,7 +34,7 @@ export const showCustomBranding = () => {
34
34
  return isEventCatalogStarterEnabled() || isEventCatalogScaleEnabled();
35
35
  };
36
36
 
37
- export const isChangelogEnabled = () => config?.changelog?.enabled ?? true;
37
+ export const isChangelogEnabled = () => config?.changelog?.enabled ?? false;
38
38
 
39
39
  export const isCustomDocsEnabled = () => isEventCatalogStarterEnabled() || isEventCatalogScaleEnabled();
40
40
  export const isEventCatalogChatEnabled = () => {
@@ -55,3 +55,5 @@ export const isAuthEnabled = () => {
55
55
  };
56
56
 
57
57
  export const isSSR = () => config?.output === 'server';
58
+ export const isRSSEnabled = () => config?.rss?.enabled ?? false;
59
+ export const isVisualiserEnabled = () => config?.visualiser?.enabled ?? true;
@@ -2,7 +2,7 @@ import { readdir, readFile } from 'node:fs/promises';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { formatPatch, structuredPatch } from 'diff';
4
4
  import { html, parse } from 'diff2html';
5
- import { getItemsFromCollectionByIdAndSemverOrLatest } from './util';
5
+ import { getItemsFromCollectionByIdAndSemverOrLatest } from './collections/util';
6
6
  import type { CollectionEntry } from 'astro:content';
7
7
  import type { CollectionTypes } from '@types';
8
8
 
@@ -17,12 +17,14 @@ interface Props {
17
17
  }
18
18
 
19
19
  export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simple', channelRenderMode = 'flat' }: Props) => {
20
+ // 1. Fetch data
20
21
  const containers = await getContainers();
21
22
 
22
23
  const flow = defaultFlow || createDagreGraph({ ranksep: 300, nodesep: 50 });
23
24
  const nodes = [] as any,
24
25
  edges = [] as any;
25
26
 
27
+ // Optimized: Use find since we're looking for a specific item
26
28
  const container = containers.find((container) => container.data.id === id && container.data.version === version);
27
29
 
28
30
  // Nothing found...
@@ -2,9 +2,9 @@ import { getCollection, getEntry } from 'astro:content';
2
2
  import { generateIdForNode } from './utils/utils';
3
3
  import ELK from 'elkjs/lib/elk.bundled.js';
4
4
  import { MarkerType } from '@xyflow/react';
5
- import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
5
+ import { createVersionedMap, findInMap } from '@utils/collections/util';
6
6
  import { getVersionFromCollection } from '@utils/collections/versions';
7
- import { getEntities, type Entity } from '@utils/entities';
7
+ import { getEntities, type Entity } from '@utils/collections/entities';
8
8
  import { getDomains, type Domain } from '@utils/collections/domains';
9
9
  import { getServices, type Service } from '@utils/collections/services';
10
10
 
@@ -21,9 +21,8 @@ export const getNodesAndEdges = async ({ id, version, entities, type = 'domains'
21
21
  let nodes = [] as any,
22
22
  edges = [] as any;
23
23
 
24
- const allDomains = await getDomains();
25
- const allEntities = await getEntities();
26
- const allServices = await getServices();
24
+ // 1. Fetch all collections in parallel
25
+ const [allDomains, allEntities, allServices] = await Promise.all([getDomains(), getEntities(), getServices()]);
27
26
 
28
27
  let resource = null;
29
28
 
@@ -65,14 +64,25 @@ export const getNodesAndEdges = async ({ id, version, entities, type = 'domains'
65
64
  const externalToDomain = Array.from(new Set<string>(listOfReferencedEntities as string[])) // Remove duplicates
66
65
  .filter((entityId: any) => !resourceEntities.some((domainEntity: any) => domainEntity.id === entityId));
67
66
 
67
+ // 2. Build optimized maps
68
+ // Only build domain map if we have domains to search
69
+ // Only build entity map if we have entities to search
70
+ const entityMap = createVersionedMap(allEntities);
71
+
68
72
  // Helper function to find which domain an entity belongs to
73
+ // Optimized to use direct iteration over domains (domains usually contain entity arrays)
74
+ // We can't easily map entity->domain without scanning domains first unless we build a reverse index.
75
+ // Given domains count is usually manageable, scanning is acceptable, OR we could build an index if needed.
76
+ // For now, let's keep the scan but make it efficient.
69
77
  const findEntityDomain = (entityId: string) => {
70
78
  return allDomains.find((domain) => domain.data.entities?.some((domainEntity: any) => domainEntity.data.id === entityId));
71
79
  };
72
80
 
73
81
  const addedExternalEntities = [];
82
+
74
83
  for (const entityId of externalToDomain) {
75
- const externalEntity = getItemsFromCollectionByIdAndSemverOrLatest(allEntities, entityId as string, 'latest')[0] as Entity;
84
+ // 3. Use map lookup
85
+ const externalEntity = findInMap(entityMap, entityId as string, 'latest') as Entity;
76
86
 
77
87
  if (externalEntity) {
78
88
  const nodeId = generateIdForNode(externalEntity);
@@ -1,9 +1,10 @@
1
1
  import { getCollection, type CollectionEntry } from 'astro:content';
2
2
  import dagre from 'dagre';
3
3
  import { generateIdForNode, createDagreGraph, calculatedNodes, createEdge } from '@utils/node-graphs/utils/utils';
4
- import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
4
+ import { findInMap, createVersionedMap } from '@utils/collections/util';
5
5
  import type { Node, Edge } from '@xyflow/react';
6
6
  import { getDomains } from '@utils/collections/domains';
7
+ import type { CollectionMessageTypes } from '@types';
7
8
 
8
9
  interface DomainCanvasData {
9
10
  domainNodes: Node[];
@@ -69,10 +70,14 @@ export const getDomainsCanvasData = async (): Promise<DomainCanvasData> => {
69
70
  } as Node);
70
71
  }
71
72
 
72
- // Get all messages for version resolution
73
- const allMessages = await getCollection('events')
74
- .then((events) => Promise.all([events, getCollection('commands'), getCollection('queries')]))
75
- .then(([events, commands, queries]) => [...events, ...commands, ...queries]);
73
+ // Get all messages for version resolution in parallel
74
+ const [events, commands, queries] = await Promise.all([
75
+ getCollection('events'),
76
+ getCollection('commands'),
77
+ getCollection('queries'),
78
+ ]);
79
+ const allMessages = [...events, ...commands, ...queries];
80
+ const messageMap = createVersionedMap(allMessages);
76
81
 
77
82
  // Map to track unique messages and their publishers/consumers across domains
78
83
  const messageRelationships = new Map<
@@ -89,9 +94,9 @@ export const getDomainsCanvasData = async (): Promise<DomainCanvasData> => {
89
94
  domainData.services.forEach((service: any) => {
90
95
  // Track messages this service sends
91
96
  const sendsRaw = service.data.sends ?? [];
97
+
92
98
  const sendsHydrated = sendsRaw
93
- .map((message: any) => getItemsFromCollectionByIdAndSemverOrLatest(allMessages, message.id, message.version))
94
- .flat()
99
+ .map((message: any) => findInMap(messageMap, message.id, message.version))
95
100
  .filter((e: any) => e !== undefined);
96
101
 
97
102
  sendsHydrated.forEach((sentMessage: any) => {
@@ -115,8 +120,7 @@ export const getDomainsCanvasData = async (): Promise<DomainCanvasData> => {
115
120
  // Track messages this service receives
116
121
  const receivesRaw = service.data.receives ?? [];
117
122
  const receivesHydrated = receivesRaw
118
- .map((message: any) => getItemsFromCollectionByIdAndSemverOrLatest(allMessages, message.id, message.version))
119
- .flat()
123
+ .map((message: any) => findInMap(messageMap, message.id, message.version))
120
124
  .filter((e: any) => e !== undefined);
121
125
 
122
126
  receivesHydrated.forEach((receivedMessage: any) => {
@@ -154,7 +158,7 @@ export const getDomainsCanvasData = async (): Promise<DomainCanvasData> => {
154
158
 
155
159
  if (crossesDomainBoundary) {
156
160
  // Find the actual message object
157
- const messageObject = allMessages.find((m) => m.data.id === message.id && m.data.version === message.version);
161
+ const messageObject = findInMap(messageMap, message.id, message.version) as CollectionEntry<CollectionMessageTypes>;
158
162
 
159
163
  if (messageObject) {
160
164
  const messageNodeId = `message-${messageKey}`;
@@ -9,7 +9,7 @@ import {
9
9
  } from '@utils/node-graphs/utils/utils';
10
10
  import { getNodesAndEdges as getServicesNodeAndEdges } from './services-node-graph';
11
11
  import merge from 'lodash.merge';
12
- import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
12
+ import { createVersionedMap, findInMap } from '@utils/collections/util';
13
13
  import type { Node } from '@xyflow/react';
14
14
  import { getProducersOfMessage } from '@utils/collections/services';
15
15
 
@@ -25,22 +25,29 @@ export const getNodesAndEdgesForDomainContextMap = async ({ defaultFlow = null }
25
25
  let nodes = [] as any,
26
26
  edges = [] as any;
27
27
 
28
- const allDomains = await getCollection('domains');
29
- const domains = allDomains.filter((domain) => !domain.id.includes('/versioned'));
30
- const services = await getCollection('services');
31
-
32
- const events = await getCollection('events');
33
- const commands = await getCollection('commands');
34
- const queries = await getCollection('queries');
28
+ // 1. Parallel Fetching
29
+ const [allDomains, services, events, commands, queries] = await Promise.all([
30
+ getCollection('domains'),
31
+ getCollection('services'),
32
+ getCollection('events'),
33
+ getCollection('commands'),
34
+ getCollection('queries'),
35
+ ]);
35
36
 
37
+ const domains = allDomains.filter((domain) => !domain.id.includes('/versioned'));
36
38
  const messages = [...events, ...commands, ...queries];
37
39
 
40
+ // 2. Build optimized maps
41
+ const serviceMap = createVersionedMap(services);
42
+ const messageMap = createVersionedMap(messages);
43
+
38
44
  domains.forEach((domain, index) => {
39
45
  const nodeId = generateIdForNode(domain);
40
46
  const rawServices = domain.data.services ?? [];
47
+
48
+ // Optimized service resolution
41
49
  const domainServices = rawServices
42
- .map((service) => getItemsFromCollectionByIdAndSemverOrLatest(services, service.id, service.version))
43
- .flat()
50
+ .map((service) => findInMap(serviceMap, service.id, service.version))
44
51
  .filter((e) => e !== undefined);
45
52
 
46
53
  // Calculate domain node size based on services
@@ -60,14 +67,12 @@ export const getNodesAndEdgesForDomainContextMap = async ({ defaultFlow = null }
60
67
  const rowIndex = Math.floor(index / DOMAINS_PER_ROW);
61
68
  const colIndex = index % DOMAINS_PER_ROW;
62
69
 
63
- const test = servicesCount * SERVICE_HEIGHT + PADDING * 2;
64
-
65
70
  nodes.push({
66
71
  id: nodeId,
67
72
  type: 'group',
68
73
  position: {
69
- x: colIndex * (domainWidth + 400), // Increased from 100 to 400px gap between domains
70
- y: rowIndex * (domainHeight + 300), // Increased from 100 to 300px gap between rows
74
+ x: colIndex * (domainWidth + 400),
75
+ y: rowIndex * (domainHeight + 300),
71
76
  },
72
77
  style: {
73
78
  width: domainWidth,
@@ -107,7 +112,6 @@ export const getNodesAndEdgesForDomainContextMap = async ({ defaultFlow = null }
107
112
  domainServices.forEach((service, serviceIndex) => {
108
113
  const row = Math.floor(serviceIndex / SERVICES_PER_ROW);
109
114
  const col = serviceIndex % SERVICES_PER_ROW;
110
- const serviceNodeId = `service-${domain.id}-${service.id}`;
111
115
 
112
116
  // Add spacing between services
113
117
  const SERVICE_MARGIN = 25;
@@ -136,10 +140,13 @@ export const getNodesAndEdgesForDomainContextMap = async ({ defaultFlow = null }
136
140
  const rawReceives = service.data.receives ?? [];
137
141
  const rawSends = service.data.sends ?? [];
138
142
 
143
+ // Optimized message resolution
139
144
  const receives = rawReceives
140
- .map((receive) => getItemsFromCollectionByIdAndSemverOrLatest(messages, receive.id, receive.version))
141
- .flat();
142
- const sends = rawSends.map((send) => getItemsFromCollectionByIdAndSemverOrLatest(messages, send.id, send.version)).flat();
145
+ .map((receive) => findInMap(messageMap, receive.id, receive.version))
146
+ .filter((msg): msg is any => !!msg); // Filter undefined
147
+
148
+ // Note: 'sends' was defined but not used in the original loop logic for edges?
149
+ // Based on original code, it iterates `receives`.
143
150
 
144
151
  for (const receive of receives) {
145
152
  const producers = getProducersOfMessage(services, receive);
@@ -148,19 +155,6 @@ export const getNodesAndEdgesForDomainContextMap = async ({ defaultFlow = null }
148
155
  const isSameDomain = domainServices.some((domainService) => domainService.data.id === producer.data.id);
149
156
 
150
157
  if (!isSameDomain) {
151
- // WIP... adding messages?
152
- // edges.push(createEdge({
153
- // id: generatedIdForEdge(receive, service),
154
- // source: generateIdForNode(receive),
155
- // target: generateIdForNode(service),
156
- // label: getEdgeLabelForServiceAsTarget(receive),
157
- // zIndex: 1000,
158
- // }));
159
-
160
- // Find the producer and consumer nodes to get their positions
161
- // const producerNode = nodes.find(n => n.id === generateIdForNode(producer));
162
- // const consumerNode = nodes.find(n => n.id === generateIdForNode(service));
163
-
164
158
  edges.push(
165
159
  createEdge({
166
160
  id: generatedIdForEdge(producer, service),
@@ -170,32 +164,6 @@ export const getNodesAndEdgesForDomainContextMap = async ({ defaultFlow = null }
170
164
  zIndex: 1000,
171
165
  })
172
166
  );
173
-
174
- // // Calculate middle position between producer and consumer
175
- // const messageX = (producerNode?.position?.x ?? 0) +
176
- // ((consumerNode?.position?.x ?? 0) - (producerNode?.position?.x ?? 0)) / 2;
177
- // const messageY = (producerNode?.position?.y ?? 0) +
178
- // ((consumerNode?.position?.y ?? 0) - (producerNode?.position?.y ?? 0)) / 2;
179
-
180
- // nodes.push({
181
- // id: generateIdForNode(receive),
182
- // type: receive.collection,
183
- // sourcePosition: 'right',
184
- // targetPosition: 'left',
185
- // data: {
186
- // message: receive,
187
- // mode: 'full',
188
- // },
189
- // position: { x: messageX, y: messageY },
190
- // });
191
-
192
- // edges.push(createEdge({
193
- // id: generatedIdForEdge(producer, receive),
194
- // source: generateIdForNode(producer),
195
- // target: generateIdForNode(receive),
196
- // label: getEdgeLabelForServiceAsTarget(receive),
197
- // zIndex: 1000,
198
- // }));
199
167
  }
200
168
  }
201
169
  }
@@ -230,7 +198,8 @@ export const getNodesAndEdges = async ({
230
198
  let nodes = new Map(),
231
199
  edges = new Map();
232
200
 
233
- const domains = await getCollection('domains');
201
+ // 1. Parallel Fetching
202
+ const [domains, services] = await Promise.all([getCollection('domains'), getCollection('services')]);
234
203
 
235
204
  const domain = domains.find((service) => service.data.id === id && service.data.version === version);
236
205
 
@@ -242,19 +211,22 @@ export const getNodesAndEdges = async ({
242
211
  };
243
212
  }
244
213
 
214
+ // 2. Build optimized maps
215
+ const serviceMap = createVersionedMap(services);
216
+ const domainMap = createVersionedMap(domains);
217
+
245
218
  const rawServices = domain?.data.services || [];
246
219
  const rawSubDomains = domain?.data.domains || [];
247
220
 
248
- const servicesCollection = await getCollection('services');
249
-
221
+ // Optimized hydration
250
222
  const domainServicesWithVersion = rawServices
251
- .map((service) => getItemsFromCollectionByIdAndSemverOrLatest(servicesCollection, service.id, service.version))
252
- .flat()
223
+ .map((service) => findInMap(serviceMap, service.id, service.version))
224
+ .filter((s): s is any => !!s)
253
225
  .map((svc) => ({ id: svc.data.id, version: svc.data.version }));
254
226
 
255
227
  const domainSubDomainsWithVersion = rawSubDomains
256
- .map((subDomain) => getItemsFromCollectionByIdAndSemverOrLatest(domains, subDomain.id, subDomain.version))
257
- .flat()
228
+ .map((subDomain) => findInMap(domainMap, subDomain.id, subDomain.version))
229
+ .filter((d): d is any => !!d)
258
230
  .map((svc) => ({ id: svc.data.id, version: svc.data.version }));
259
231
 
260
232
  // Get all the nodes for everyhing
@@ -3,7 +3,7 @@ import dagre from 'dagre';
3
3
  import { createDagreGraph, calculatedNodes } from '@utils/node-graphs/utils/utils';
4
4
  import { MarkerType } from '@xyflow/react';
5
5
  import type { Node as NodeType } from '@xyflow/react';
6
- import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
6
+ import { createVersionedMap, findInMap } from '@utils/collections/util';
7
7
 
8
8
  type DagreGraph = any;
9
9
 
@@ -15,9 +15,8 @@ interface Props {
15
15
  renderAllEdges?: boolean;
16
16
  }
17
17
 
18
- const getServiceNode = (step: any, services: CollectionEntry<'services'>[]) => {
19
- const servicesForVersion = getItemsFromCollectionByIdAndSemverOrLatest(services, step.service.id, step.service.version);
20
- const service = servicesForVersion?.[0];
18
+ const getServiceNode = (step: any, serviceMap: Map<string, any[]>) => {
19
+ const service = findInMap(serviceMap, step.service.id, step.service.version);
21
20
  return {
22
21
  ...step,
23
22
  type: service ? service.collection : 'step',
@@ -25,9 +24,8 @@ const getServiceNode = (step: any, services: CollectionEntry<'services'>[]) => {
25
24
  };
26
25
  };
27
26
 
28
- const getFlowNode = (step: any, flows: CollectionEntry<'flows'>[]) => {
29
- const flowsForVersion = getItemsFromCollectionByIdAndSemverOrLatest(flows, step.flow.id, step.flow.version);
30
- const flow = flowsForVersion?.[0];
27
+ const getFlowNode = (step: any, flowMap: Map<string, any[]>) => {
28
+ const flow = findInMap(flowMap, step.flow.id, step.flow.version);
31
29
  return {
32
30
  ...step,
33
31
  type: flow ? flow.collection : 'step',
@@ -35,9 +33,8 @@ const getFlowNode = (step: any, flows: CollectionEntry<'flows'>[]) => {
35
33
  };
36
34
  };
37
35
 
38
- const getMessageNode = (step: any, messages: CollectionEntry<'events' | 'commands' | 'queries'>[]) => {
39
- const messagesForVersion = getItemsFromCollectionByIdAndSemverOrLatest(messages, step.message.id, step.message.version);
40
- const message = messagesForVersion[0];
36
+ const getMessageNode = (step: any, messageMap: Map<string, any[]>) => {
37
+ const message = findInMap(messageMap, step.message.id, step.message.version);
41
38
  return {
42
39
  ...step,
43
40
  type: message ? message.collection : 'step',
@@ -50,7 +47,15 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
50
47
  const nodes = [] as any,
51
48
  edges = [] as any;
52
49
 
53
- const flows = await getCollection('flows');
50
+ // Fetch all collections in parallel
51
+ const [flows, events, commands, queries, services] = await Promise.all([
52
+ getCollection('flows'),
53
+ getCollection('events'),
54
+ getCollection('commands'),
55
+ getCollection('queries'),
56
+ getCollection('services'),
57
+ ]);
58
+
54
59
  const flow = flows.find((flow) => flow.data.id === id && flow.data.version === version);
55
60
 
56
61
  // Nothing found...
@@ -61,20 +66,19 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
61
66
  };
62
67
  }
63
68
 
64
- const events = await getCollection('events');
65
- const commands = await getCollection('commands');
66
- const queries = await getCollection('queries');
67
- const services = await getCollection('services');
68
-
69
+ // Build maps for O(1) lookups
69
70
  const messages = [...events, ...commands, ...queries];
71
+ const messageMap = createVersionedMap(messages);
72
+ const serviceMap = createVersionedMap(services);
73
+ const flowMap = createVersionedMap(flows);
70
74
 
71
75
  const steps = flow?.data.steps || [];
72
76
 
73
77
  // Hydrate the steps with information they may need.
74
78
  const hydratedSteps = steps.map((step: any) => {
75
- if (step.service) return getServiceNode(step, services);
76
- if (step.flow) return getFlowNode(step, flows);
77
- if (step.message) return getMessageNode(step, messages);
79
+ if (step.service) return getServiceNode(step, serviceMap);
80
+ if (step.flow) return getFlowNode(step, flowMap);
81
+ if (step.message) return getMessageNode(step, messageMap);
78
82
  if (step.actor) return { ...step, type: 'actor', actor: step.actor };
79
83
  if (step.custom) return { ...step, type: 'custom', custom: step.custom };
80
84
  if (step.externalSystem) return { ...step, type: 'externalSystem', externalSystem: step.externalSystem };