@eventcatalog/core 3.7.2 → 3.8.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 (66) 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-O6SRHGZ7.js → chunk-4EJDLNIX.js} +1 -1
  6. package/dist/{chunk-WAX3S32H.js → chunk-EG36OTR7.js} +1 -1
  7. package/dist/{chunk-GQZVIS3Z.js → chunk-GITARDPK.js} +1 -1
  8. package/dist/{chunk-7CTNGTBB.js → chunk-IEEU454Z.js} +1 -1
  9. package/dist/{chunk-M7EPRGHR.js → chunk-ZIG6J4R2.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/ChatPanel/ChatPanel.tsx +13 -1
  19. package/eventcatalog/src/components/Grids/DomainGrid.tsx +109 -6
  20. package/eventcatalog/src/components/Grids/utils.tsx +10 -1
  21. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +2 -0
  22. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +4 -0
  23. package/eventcatalog/src/components/MDX/NodeGraph/Nodes/DataProduct.tsx +132 -0
  24. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +29 -2
  25. package/eventcatalog/src/components/SchemaExplorer/types.ts +5 -1
  26. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +3 -0
  27. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +1 -0
  28. package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +23 -1
  29. package/eventcatalog/src/components/Tables/Discover/columns.tsx +62 -0
  30. package/eventcatalog/src/content.config.ts +34 -0
  31. package/eventcatalog/src/enterprise/ai/chat-api.ts +26 -0
  32. package/eventcatalog/src/enterprise/custom-documentation/utils/custom-docs.ts +1 -1
  33. package/eventcatalog/src/enterprise/tools/catalog-tools.ts +169 -2
  34. package/eventcatalog/src/pages/discover/[type]/_index.data.ts +5 -1
  35. package/eventcatalog/src/pages/discover/[type]/index.astro +57 -1
  36. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +1 -0
  37. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +5 -1
  38. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/_index.data.ts +27 -3
  39. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +74 -25
  40. package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +55 -1
  41. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +10 -1
  42. package/eventcatalog/src/stores/sidebar-store/builders/container.ts +23 -16
  43. package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +130 -0
  44. package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +11 -0
  45. package/eventcatalog/src/stores/sidebar-store/state.ts +68 -13
  46. package/eventcatalog/src/styles/theme.css +4 -0
  47. package/eventcatalog/src/styles/themes/forest.css +4 -0
  48. package/eventcatalog/src/styles/themes/ocean.css +4 -0
  49. package/eventcatalog/src/styles/themes/sapphire.css +4 -0
  50. package/eventcatalog/src/styles/themes/sunset.css +4 -0
  51. package/eventcatalog/src/types/index.ts +4 -2
  52. package/eventcatalog/src/utils/collections/commands.ts +11 -29
  53. package/eventcatalog/src/utils/collections/containers.ts +25 -1
  54. package/eventcatalog/src/utils/collections/data-products.ts +85 -0
  55. package/eventcatalog/src/utils/collections/domains.ts +28 -10
  56. package/eventcatalog/src/utils/collections/events.ts +11 -29
  57. package/eventcatalog/src/utils/collections/icons.ts +5 -0
  58. package/eventcatalog/src/utils/collections/messages.ts +68 -0
  59. package/eventcatalog/src/utils/collections/queries.ts +11 -29
  60. package/eventcatalog/src/utils/collections/util.ts +11 -2
  61. package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +91 -3
  62. package/eventcatalog/src/utils/node-graphs/data-products-node-graph.ts +225 -0
  63. package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +28 -2
  64. package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +74 -20
  65. package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +2 -0
  66. package/package.json +2 -2
@@ -7,6 +7,7 @@ import { getFlows } from '@utils/collections/flows';
7
7
  import { getUsers } from '@utils/collections/users';
8
8
  import { getTeams } from '@utils/collections/teams';
9
9
  import { getDiagrams } from '@utils/collections/diagrams';
10
+ import { getDataProducts } from '@utils/collections/data-products';
10
11
  import { buildUrl } from '@utils/url-builder';
11
12
  import type { NavigationData, NavNode, ChildRef } from './builders/shared';
12
13
  import { buildDomainNode } from './builders/domain';
@@ -14,6 +15,7 @@ import { buildServiceNode } from './builders/service';
14
15
  import { buildMessageNode } from './builders/message';
15
16
  import { buildContainerNode } from './builders/container';
16
17
  import { buildFlowNode } from './builders/flow';
18
+ import { buildDataProductNode } from './builders/data-product';
17
19
  import config from '@config';
18
20
  import { getDesigns } from '@utils/collections/designs';
19
21
  import { getChannels } from '@utils/collections/channels';
@@ -31,19 +33,31 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
31
33
  return memoryCache;
32
34
  }
33
35
 
34
- const [domains, services, { events, commands, queries }, containers, flows, users, teams, designs, channels, diagrams] =
35
- await Promise.all([
36
- getDomains({ getAllVersions: false, includeServicesInSubdomains: false }),
37
- getServices({ getAllVersions: false }),
38
- getMessages({ getAllVersions: false }),
39
- getContainers({ getAllVersions: false }),
40
- getFlows({ getAllVersions: false }),
41
- getUsers(),
42
- getTeams(),
43
- getDesigns(),
44
- getChannels({ getAllVersions: false }),
45
- getDiagrams({ getAllVersions: false }),
46
- ]);
36
+ const [
37
+ domains,
38
+ services,
39
+ { events, commands, queries },
40
+ containers,
41
+ flows,
42
+ users,
43
+ teams,
44
+ designs,
45
+ channels,
46
+ diagrams,
47
+ dataProducts,
48
+ ] = await Promise.all([
49
+ getDomains({ getAllVersions: false, includeServicesInSubdomains: false }),
50
+ getServices({ getAllVersions: false }),
51
+ getMessages({ getAllVersions: false }),
52
+ getContainers({ getAllVersions: false }),
53
+ getFlows({ getAllVersions: false }),
54
+ getUsers(),
55
+ getTeams(),
56
+ getDesigns(),
57
+ getChannels({ getAllVersions: false }),
58
+ getDiagrams({ getAllVersions: false }),
59
+ getDataProducts({ getAllVersions: false }),
60
+ ]);
47
61
 
48
62
  // Calculate derived lists to avoid extra fetches
49
63
  const allSubDomainIds = new Set(domains.flatMap((d) => (d.data.domains || []).map((sd: any) => sd.data.id)));
@@ -60,6 +74,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
60
74
  flows,
61
75
  containers,
62
76
  diagrams,
77
+ dataProducts,
63
78
  };
64
79
 
65
80
  // Process all domains with their owners first (async)
@@ -166,6 +181,35 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
166
181
  {} as Record<string, NavNode | string>
167
182
  );
168
183
 
184
+ // Get owners for data products
185
+ const dataProductWithOwners = await Promise.all(
186
+ dataProducts.map(async (dataProduct) => {
187
+ const owners = await Promise.all((dataProduct.data.owners || []).map((owner) => getOwner(owner)));
188
+ return { dataProduct, owners: owners.filter((o) => o !== undefined) };
189
+ })
190
+ );
191
+
192
+ const dataProductContext = {
193
+ events,
194
+ commands,
195
+ queries,
196
+ services,
197
+ containers,
198
+ channels,
199
+ };
200
+
201
+ const dataProductNodes = dataProductWithOwners.reduce(
202
+ (acc, { dataProduct, owners }) => {
203
+ const versionedKey = `data-product:${dataProduct.data.id}:${dataProduct.data.version}`;
204
+ acc[versionedKey] = buildDataProductNode(dataProduct, owners, dataProductContext);
205
+ if (dataProduct.data.latestVersion === dataProduct.data.version) {
206
+ acc[`data-product:${dataProduct.data.id}`] = versionedKey;
207
+ }
208
+ return acc;
209
+ },
210
+ {} as Record<string, NavNode | string>
211
+ );
212
+
169
213
  const designNodes = designs.reduce(
170
214
  (acc, design) => {
171
215
  acc[`design:${design.data.id}`] = {
@@ -285,6 +329,13 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
285
329
  pages: containers.map((container) => `container:${container.data.id}:${container.data.version}`),
286
330
  });
287
331
 
332
+ const dataProductsList = createLeaf(dataProducts, {
333
+ type: 'item',
334
+ title: 'Data Products',
335
+ icon: 'Package',
336
+ pages: dataProducts.map((dataProduct) => `data-product:${dataProduct.data.id}:${dataProduct.data.version}`),
337
+ });
338
+
288
339
  const designsList = createLeaf(designs, {
289
340
  type: 'item',
290
341
  title: 'Designs',
@@ -346,6 +397,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
346
397
  'list:channels',
347
398
  'list:flows',
348
399
  'list:containers',
400
+ 'list:data-products',
349
401
  'list:designs',
350
402
  'list:people',
351
403
  ];
@@ -356,6 +408,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
356
408
  channelList,
357
409
  flowsList,
358
410
  containersList,
411
+ dataProductsList,
359
412
  designsList,
360
413
  peopleList,
361
414
  ];
@@ -381,6 +434,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
381
434
  ...(messagesList ? { 'list:messages': messagesList as NavNode } : {}),
382
435
  ...(flowsList ? { 'list:flows': flowsList } : {}),
383
436
  ...(containersList ? { 'list:containers': containersList } : {}),
437
+ ...(dataProductsList ? { 'list:data-products': dataProductsList } : {}),
384
438
  ...(designsList ? { 'list:designs': designsList } : {}),
385
439
  ...(teamsList ? { 'list:teams': teamsList } : {}),
386
440
  ...(usersList ? { 'list:users': usersList } : {}),
@@ -415,6 +469,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
415
469
  ...messageNodes,
416
470
  ...channelNodes,
417
471
  ...containerNodes,
472
+ ...dataProductNodes,
418
473
  ...flowNodes,
419
474
  ...userNodes,
420
475
  ...teamNodes,
@@ -103,6 +103,8 @@
103
103
  --ec-badge-design-text: 15 118 110; /* teal-700 */
104
104
  --ec-badge-channel-bg: 224 231 255; /* indigo-100 */
105
105
  --ec-badge-channel-text: 67 56 202; /* indigo-700 */
106
+ --ec-badge-data-product-bg: 219 234 254; /* blue-100 */
107
+ --ec-badge-data-product-text: 30 64 175; /* blue-800 */
106
108
  --ec-badge-default-bg: 243 244 246; /* gray-100 */
107
109
  --ec-badge-default-text: 75 85 99; /* gray-600 */
108
110
  }
@@ -199,6 +201,8 @@
199
201
  --ec-badge-design-text: 94 234 212; /* teal-300 */
200
202
  --ec-badge-channel-bg: 49 46 129 / 0.3; /* indigo-900/30 */
201
203
  --ec-badge-channel-text: 165 180 252; /* indigo-300 */
204
+ --ec-badge-data-product-bg: 30 64 175 / 0.3; /* blue-800/30 */
205
+ --ec-badge-data-product-text: 147 197 253; /* blue-300 */
202
206
  --ec-badge-default-bg: 63 63 70 / 0.3; /* zinc-700/30 */
203
207
  --ec-badge-default-text: 212 212 216; /* zinc-300 */
204
208
  }
@@ -97,6 +97,8 @@
97
97
  --ec-badge-design-text: 22 101 52; /* green-800 */
98
98
  --ec-badge-channel-bg: 224 231 255; /* indigo-100 */
99
99
  --ec-badge-channel-text: 67 56 202; /* indigo-700 */
100
+ --ec-badge-data-product-bg: 219 234 254; /* blue-100 */
101
+ --ec-badge-data-product-text: 30 64 175; /* blue-800 */
100
102
  --ec-badge-default-bg: 240 253 244; /* green-50 */
101
103
  --ec-badge-default-text: 75 85 99; /* gray-600 */
102
104
  }
@@ -188,6 +190,8 @@
188
190
  --ec-badge-design-text: 134 239 172; /* green-300 */
189
191
  --ec-badge-channel-bg: 49 46 129 / 0.3; /* indigo-900/30 */
190
192
  --ec-badge-channel-text: 165 180 252; /* indigo-300 */
193
+ --ec-badge-data-product-bg: 30 64 175 / 0.3; /* blue-800/30 */
194
+ --ec-badge-data-product-text: 147 197 253; /* blue-300 */
191
195
  --ec-badge-default-bg: 55 65 81 / 0.3; /* gray-700/30 */
192
196
  --ec-badge-default-text: 209 213 219; /* gray-300 */
193
197
  }
@@ -97,6 +97,8 @@
97
97
  --ec-badge-design-text: 15 118 110; /* teal-700 */
98
98
  --ec-badge-channel-bg: 224 231 255; /* indigo-100 */
99
99
  --ec-badge-channel-text: 67 56 202; /* indigo-700 */
100
+ --ec-badge-data-product-bg: 219 234 254; /* blue-100 */
101
+ --ec-badge-data-product-text: 30 64 175; /* blue-800 */
100
102
  --ec-badge-default-bg: 240 253 250; /* teal-50 */
101
103
  --ec-badge-default-text: 71 85 105; /* slate-600 */
102
104
  }
@@ -193,6 +195,8 @@
193
195
  --ec-badge-design-text: 94 234 212; /* teal-300 */
194
196
  --ec-badge-channel-bg: 49 46 129 / 0.3; /* indigo-900/30 */
195
197
  --ec-badge-channel-text: 165 180 252; /* indigo-300 */
198
+ --ec-badge-data-product-bg: 30 64 175 / 0.3; /* blue-800/30 */
199
+ --ec-badge-data-product-text: 147 197 253; /* blue-300 */
196
200
  --ec-badge-default-bg: 51 65 85 / 0.3; /* slate-700/30 */
197
201
  --ec-badge-default-text: 203 213 225; /* slate-300 */
198
202
  }
@@ -97,6 +97,8 @@
97
97
  --ec-badge-design-text: 37 99 235; /* blue-600 */
98
98
  --ec-badge-channel-bg: 224 231 255; /* indigo-100 */
99
99
  --ec-badge-channel-text: 67 56 202; /* indigo-700 */
100
+ --ec-badge-data-product-bg: 219 234 254; /* blue-100 */
101
+ --ec-badge-data-product-text: 30 64 175; /* blue-800 */
100
102
  --ec-badge-default-bg: 239 246 255; /* blue-50 */
101
103
  --ec-badge-default-text: 71 85 105; /* slate-600 */
102
104
  }
@@ -188,6 +190,8 @@
188
190
  --ec-badge-design-text: 147 197 253; /* blue-300 */
189
191
  --ec-badge-channel-bg: 49 46 129 / 0.3; /* indigo-900/30 */
190
192
  --ec-badge-channel-text: 165 180 252; /* indigo-300 */
193
+ --ec-badge-data-product-bg: 30 64 175 / 0.3; /* blue-800/30 */
194
+ --ec-badge-data-product-text: 147 197 253; /* blue-300 */
191
195
  --ec-badge-default-bg: 51 65 85 / 0.3; /* slate-700/30 */
192
196
  --ec-badge-default-text: 203 213 225; /* slate-300 */
193
197
  }
@@ -97,6 +97,8 @@
97
97
  --ec-badge-design-text: 234 88 12; /* orange-600 */
98
98
  --ec-badge-channel-bg: 224 231 255; /* indigo-100 */
99
99
  --ec-badge-channel-text: 67 56 202; /* indigo-700 */
100
+ --ec-badge-data-product-bg: 219 234 254; /* blue-100 */
101
+ --ec-badge-data-product-text: 30 64 175; /* blue-800 */
100
102
  --ec-badge-default-bg: 255 247 237; /* orange-50 */
101
103
  --ec-badge-default-text: 82 82 82; /* neutral-600 */
102
104
  }
@@ -188,6 +190,8 @@
188
190
  --ec-badge-design-text: 253 186 116; /* orange-300 */
189
191
  --ec-badge-channel-bg: 49 46 129 / 0.3; /* indigo-900/30 */
190
192
  --ec-badge-channel-text: 165 180 252; /* indigo-300 */
193
+ --ec-badge-data-product-bg: 30 64 175 / 0.3; /* blue-800/30 */
194
+ --ec-badge-data-product-text: 147 197 253; /* blue-300 */
191
195
  --ec-badge-default-bg: 64 64 64 / 0.3; /* neutral-700/30 */
192
196
  --ec-badge-default-text: 212 212 212; /* neutral-300 */
193
197
  }
@@ -8,7 +8,8 @@ export type CollectionTypes =
8
8
  | 'channels'
9
9
  | 'entities'
10
10
  | 'containers'
11
- | 'diagrams';
11
+ | 'diagrams'
12
+ | 'data-products';
12
13
  export type CollectionMessageTypes = 'commands' | 'events' | 'queries';
13
14
  export type CollectionUserTypes = 'users';
14
15
  export type PageTypes =
@@ -21,7 +22,8 @@ export type PageTypes =
21
22
  | 'flows'
22
23
  | 'entities'
23
24
  | 'containers'
24
- | 'diagrams';
25
+ | 'diagrams'
26
+ | 'data-products';
25
27
 
26
28
  export type TableConfiguration = {
27
29
  columns: {
@@ -1,7 +1,8 @@
1
1
  import { getCollection } from 'astro:content';
2
2
  import type { CollectionEntry } from 'astro:content';
3
3
  import path from 'path';
4
- import { createVersionedMap, satisfies } from './util';
4
+ import { createVersionedMap } from './util';
5
+ import { hydrateProducersAndConsumers } from './messages';
5
6
  import utils from '@eventcatalog/sdk';
6
7
 
7
8
  const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
@@ -35,10 +36,11 @@ export const getCommands = async ({ getAllVersions = true, hydrateServices = tru
35
36
  }
36
37
 
37
38
  // 1. Fetch collections in parallel
38
- const [allCommands, allServices, allChannels] = await Promise.all([
39
+ const [allCommands, allServices, allChannels, allDataProducts] = await Promise.all([
39
40
  getCollection('commands'),
40
41
  getCollection('services'),
41
42
  getCollection('channels'),
43
+ getCollection('data-products'),
42
44
  ]);
43
45
 
44
46
  // 2. Build optimized maps
@@ -61,33 +63,13 @@ export const getCommands = async ({ getAllVersions = true, hydrateServices = tru
61
63
  const latestVersion = commandVersions[0]?.data.version || command.data.version;
62
64
  const versions = commandVersions.map((e) => e.data.version);
63
65
 
64
- // Find Producers (Services that send this command)
65
- const producers = allServices
66
- .filter((service) =>
67
- service.data.sends?.some((item) => {
68
- if (item.id !== command.data.id) return false;
69
- if (item.version === 'latest' || item.version === undefined) return command.data.version === latestVersion;
70
- return satisfies(command.data.version, item.version);
71
- })
72
- )
73
- .map((service) => {
74
- if (!hydrateServices) return { id: service.data.id, version: service.data.version };
75
- return service;
76
- });
77
-
78
- // Find Consumers (Services that receive this command)
79
- const consumers = allServices
80
- .filter((service) =>
81
- service.data.receives?.some((item) => {
82
- if (item.id !== command.data.id) return false;
83
- if (item.version === 'latest' || item.version === undefined) return command.data.version === latestVersion;
84
- return satisfies(command.data.version, item.version);
85
- })
86
- )
87
- .map((service) => {
88
- if (!hydrateServices) return { id: service.data.id, version: service.data.version };
89
- return service;
90
- });
66
+ // Find producers and consumers (services + data products)
67
+ const { producers, consumers } = hydrateProducersAndConsumers({
68
+ message: { data: { ...command.data, latestVersion } },
69
+ services: allServices,
70
+ dataProducts: allDataProducts,
71
+ hydrate: hydrateServices,
72
+ });
91
73
 
92
74
  // Find Channels
93
75
  const messageChannels = command.data.channels || [];
@@ -32,7 +32,11 @@ export const getContainers = async ({ getAllVersions = true }: Props = {}): Prom
32
32
  }
33
33
 
34
34
  // 1. Fetch collections in parallel
35
- const [allContainers, allServices] = await Promise.all([getCollection('containers'), getCollection('services')]);
35
+ const [allContainers, allServices, allDataProducts] = await Promise.all([
36
+ getCollection('containers'),
37
+ getCollection('services'),
38
+ getCollection('data-products'),
39
+ ]);
36
40
 
37
41
  // 2. Build optimized maps
38
42
  const containerMap = createVersionedMap(allContainers);
@@ -72,6 +76,24 @@ export const getContainers = async ({ getAllVersions = true }: Props = {}): Prom
72
76
  });
73
77
  });
74
78
 
79
+ // Find Data Products that write to this container (have it in outputs)
80
+ const dataProductsThatWriteToContainer = allDataProducts.filter((dataProduct) => {
81
+ return dataProduct.data?.outputs?.some((item) => {
82
+ if (item.id !== container.data.id) return false;
83
+ if (item.version === 'latest' || item.version === undefined) return container.data.version === latestVersion;
84
+ return satisfies(container.data.version, item.version);
85
+ });
86
+ });
87
+
88
+ // Find Data Products that read from this container (have it in inputs)
89
+ const dataProductsThatReadFromContainer = allDataProducts.filter((dataProduct) => {
90
+ return dataProduct.data?.inputs?.some((item) => {
91
+ if (item.id !== container.data.id) return false;
92
+ if (item.version === 'latest' || item.version === undefined) return container.data.version === latestVersion;
93
+ return satisfies(container.data.version, item.version);
94
+ });
95
+ });
96
+
75
97
  // Combine references
76
98
  const servicesThatReferenceContainer = [...new Set([...servicesThatWriteToContainer, ...servicesThatReadFromContainer])];
77
99
 
@@ -91,6 +113,8 @@ export const getContainers = async ({ getAllVersions = true }: Props = {}): Prom
91
113
  services: servicesThatReferenceContainer,
92
114
  servicesThatWriteToContainer,
93
115
  servicesThatReadFromContainer,
116
+ dataProductsThatWriteToContainer,
117
+ dataProductsThatReadFromContainer,
94
118
  },
95
119
  catalog: {
96
120
  path: path.join(container.collection, container.id.replace('/index.mdx', '')),
@@ -0,0 +1,85 @@
1
+ import { getCollection } from 'astro:content';
2
+ import type { CollectionEntry } from 'astro:content';
3
+ import path from 'path';
4
+ import { createVersionedMap, satisfies } from './util';
5
+
6
+ const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
7
+
8
+ export type DataProduct = CollectionEntry<'data-products'> & {
9
+ catalog: {
10
+ path: string;
11
+ filePath: string;
12
+ type: string;
13
+ publicPath: string;
14
+ };
15
+ };
16
+
17
+ interface Props {
18
+ getAllVersions?: boolean;
19
+ }
20
+
21
+ // cache for build time
22
+ let memoryCache: Record<string, DataProduct[]> = {};
23
+
24
+ export const getDataProducts = async ({ getAllVersions = true }: Props = {}): Promise<DataProduct[]> => {
25
+ // console.time('✅ New getEntities');
26
+ const cacheKey = getAllVersions ? 'allVersions' : 'currentVersions';
27
+
28
+ if (memoryCache[cacheKey] && memoryCache[cacheKey].length > 0) {
29
+ // console.timeEnd('✅ New getEntities');
30
+ return memoryCache[cacheKey];
31
+ }
32
+
33
+ // 1. Fetch collections in parallel
34
+ const [allDataProducts, allDomains] = await Promise.all([getCollection('data-products'), getCollection('domains')]);
35
+
36
+ // 2. Build optimized maps
37
+ const dataProductMap = createVersionedMap(allDataProducts);
38
+
39
+ // 3. Enrich data products
40
+ const processedDataProducts = await Promise.all(
41
+ allDataProducts.map(async (dataProduct) => {
42
+ // Version info
43
+ const dataProductVersions = dataProductMap.get(dataProduct.data.id) || [];
44
+ const latestVersion = dataProductVersions[0]?.data.version || dataProduct.data.version;
45
+ const versions = dataProductVersions.map((e) => e.data.version);
46
+
47
+ // Find Domains that reference this data product
48
+ const domainsThatReferenceDataProduct = allDomains.filter((domain) =>
49
+ domain.data['data-products']?.some((item) => {
50
+ if (item.id !== dataProduct.data.id) return false;
51
+ if (item.version === 'latest' || item.version === undefined) return dataProduct.data.version === latestVersion;
52
+ return satisfies(dataProduct.data.version, item.version);
53
+ })
54
+ );
55
+
56
+ const dataProductFolderName = dataProduct.id.replace('/index.mdx', '');
57
+
58
+ return {
59
+ ...dataProduct,
60
+ data: {
61
+ ...dataProduct.data,
62
+ versions,
63
+ latestVersion,
64
+ domains: domainsThatReferenceDataProduct,
65
+ },
66
+ catalog: {
67
+ path: path.join(dataProduct.collection, dataProductFolderName),
68
+ filePath: path.join(process.cwd(), 'src', 'catalog-files', dataProduct.collection, dataProductFolderName),
69
+ publicPath: path.join('/generated', dataProduct.collection, dataProductFolderName),
70
+ type: 'dataProduct',
71
+ },
72
+ };
73
+ })
74
+ );
75
+
76
+ // order them by the name of the data product
77
+ processedDataProducts.sort((a, b) => {
78
+ return (a.data.name || a.data.id).localeCompare(b.data.name || b.data.id);
79
+ });
80
+
81
+ memoryCache[cacheKey] = processedDataProducts;
82
+ // console.timeEnd('✅ New getDataProducts');
83
+
84
+ return processedDataProducts as DataProduct[];
85
+ };
@@ -80,16 +80,18 @@ export const getDomains = async ({
80
80
  }
81
81
 
82
82
  // 1. Fetch collections (always fetch messages to hydrate domain-level sends/receives)
83
- const [allDomains, allServices, allEntities, allFlows, allEvents, allCommands, allQueries, allContainers] = await Promise.all([
84
- getCollection('domains'),
85
- getCollection('services'),
86
- getCollection('entities'),
87
- getCollection('flows'),
88
- getCollection('events'),
89
- getCollection('commands'),
90
- getCollection('queries'),
91
- getCollection('containers'),
92
- ]);
83
+ const [allDomains, allServices, allEntities, allFlows, allEvents, allCommands, allQueries, allContainers, allDataProducts] =
84
+ await Promise.all([
85
+ getCollection('domains'),
86
+ getCollection('services'),
87
+ getCollection('entities'),
88
+ getCollection('flows'),
89
+ getCollection('events'),
90
+ getCollection('commands'),
91
+ getCollection('queries'),
92
+ getCollection('containers'),
93
+ getCollection('data-products'),
94
+ ]);
93
95
 
94
96
  const allMessages = [...allEvents, ...allCommands, ...allQueries];
95
97
  const messageMap = createVersionedMap(allMessages);
@@ -100,6 +102,7 @@ export const getDomains = async ({
100
102
  const serviceMap = createVersionedMap(allServices);
101
103
  const entityMap = createVersionedMap(allEntities);
102
104
  const flowMap = createVersionedMap(allFlows);
105
+ const dataProductMap = createVersionedMap(allDataProducts);
103
106
 
104
107
  // 3. Filter the domains we actually want to process/return
105
108
  const targetDomains = allDomains.filter((domain: Domain) => {
@@ -137,11 +140,17 @@ export const getDomains = async ({
137
140
  .filter((s: any) => !!s);
138
141
  }
139
142
 
143
+ // Hydrate data products for the subdomain
144
+ const subdomainDataProducts = (subDomain.data['data-products'] || [])
145
+ .map((dp: { id: string; version: string | undefined }) => findInMap(dataProductMap, dp.id, dp.version))
146
+ .filter((dp: any) => !!dp);
147
+
140
148
  return {
141
149
  ...subDomain,
142
150
  data: {
143
151
  ...subDomain.data,
144
152
  services: hydratedServices as any,
153
+ 'data-products': subdomainDataProducts as any,
145
154
  },
146
155
  };
147
156
  });
@@ -158,6 +167,14 @@ export const getDomains = async ({
158
167
  .map((flow: { id: string; version: string | undefined }) => findInMap(flowMap, flow.id, flow.version))
159
168
  .filter((f): f is CollectionEntry<'flows'> => !!f);
160
169
 
170
+ // Resolve Data Products
171
+ const dataProductsInDomain = domain.data['data-products'] || [];
172
+ const dataProducts = dataProductsInDomain
173
+ .map((dataProduct: { id: string; version: string | undefined }) =>
174
+ findInMap(dataProductMap, dataProduct.id, dataProduct.version)
175
+ )
176
+ .filter((dp): dp is CollectionEntry<'data-products'> => !!dp);
177
+
161
178
  // Resolve Services for Main Domain
162
179
  const servicesInDomain = domain.data.services || [];
163
180
 
@@ -203,6 +220,7 @@ export const getDomains = async ({
203
220
  domains: subDomains as any,
204
221
  entities: entities as any,
205
222
  flows: flows as any,
223
+ 'data-products': dataProducts as any,
206
224
  sends: domainSends as any,
207
225
  receives: domainReceives as any,
208
226
  latestVersion,
@@ -1,7 +1,8 @@
1
1
  import { getCollection } from 'astro:content';
2
2
  import type { CollectionEntry } from 'astro:content';
3
3
  import path from 'path';
4
- import { createVersionedMap, findInMap, satisfies } from './util';
4
+ import { createVersionedMap } from './util';
5
+ import { hydrateProducersAndConsumers } from './messages';
5
6
  import utils from '@eventcatalog/sdk';
6
7
 
7
8
  const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
@@ -35,10 +36,11 @@ export const getEvents = async ({ getAllVersions = true, hydrateServices = true
35
36
  }
36
37
 
37
38
  // 1. Fetch collections in parallel
38
- const [allEvents, allServices, allChannels] = await Promise.all([
39
+ const [allEvents, allServices, allChannels, allDataProducts] = await Promise.all([
39
40
  getCollection('events'),
40
41
  getCollection('services'),
41
42
  getCollection('channels'),
43
+ getCollection('data-products'),
42
44
  ]);
43
45
 
44
46
  // 2. Build optimized maps
@@ -64,33 +66,13 @@ export const getEvents = async ({ getAllVersions = true, hydrateServices = true
64
66
  const latestVersion = eventVersions[0]?.data.version || event.data.version;
65
67
  const versions = eventVersions.map((e) => e.data.version);
66
68
 
67
- // Find Producers (Services that send this event)
68
- const producers = allServices
69
- .filter((service) =>
70
- service.data.sends?.some((item) => {
71
- if (item.id !== event.data.id) return false;
72
- if (item.version === 'latest' || item.version === undefined) return event.data.version === latestVersion;
73
- return satisfies(event.data.version, item.version);
74
- })
75
- )
76
- .map((service) => {
77
- if (!hydrateServices) return { id: service.data.id, version: service.data.version };
78
- return service;
79
- });
80
-
81
- // Find Consumers (Services that receive this event)
82
- const consumers = allServices
83
- .filter((service) =>
84
- service.data.receives?.some((item) => {
85
- if (item.id !== event.data.id) return false;
86
- if (item.version === 'latest' || item.version === undefined) return event.data.version === latestVersion;
87
- return satisfies(event.data.version, item.version);
88
- })
89
- )
90
- .map((service) => {
91
- if (!hydrateServices) return { id: service.data.id, version: service.data.version };
92
- return service;
93
- });
69
+ // Find producers and consumers (services + data products)
70
+ const { producers, consumers } = hydrateProducersAndConsumers({
71
+ message: { data: { ...event.data, latestVersion } },
72
+ services: allServices,
73
+ dataProducts: allDataProducts,
74
+ hydrate: hydrateServices,
75
+ });
94
76
 
95
77
  // Find Channels
96
78
  const messageChannels = event.data.channels || [];
@@ -10,6 +10,7 @@ import {
10
10
  ArrowsRightLeftIcon,
11
11
  VariableIcon,
12
12
  MapIcon,
13
+ CubeIcon,
13
14
  } from '@heroicons/react/24/outline';
14
15
  import { BookText, Box, DatabaseIcon } from 'lucide-react';
15
16
 
@@ -43,6 +44,8 @@ export const getIconForCollection = (collection: string) => {
43
44
  return Box;
44
45
  case 'containers':
45
46
  return DatabaseIcon;
47
+ case 'data-products':
48
+ return CubeIcon;
46
49
  default:
47
50
  return ServerIcon;
48
51
  }
@@ -74,6 +77,8 @@ export const getColorAndIconForCollection = (collection: string) => {
74
77
  return { color: 'yellow', Icon: icon };
75
78
  case 'services':
76
79
  return { color: 'pink', Icon: icon };
80
+ case 'data-products':
81
+ return { color: 'cyan', Icon: icon };
77
82
  default:
78
83
  return { color: 'gray', Icon: icon };
79
84
  }