@eventcatalog/core 4.10.9 → 4.10.11

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 (39) 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-3EEFSHHW.js → chunk-3XTFNVGA.js} +1 -1
  6. package/dist/{chunk-ONRJA5DX.js → chunk-N6U5CNT7.js} +1 -1
  7. package/dist/{chunk-BYPWHDX6.js → chunk-RLGMIZSH.js} +1 -1
  8. package/dist/{chunk-5WB6CXZH.js → chunk-TWZKZIRW.js} +1 -1
  9. package/dist/{chunk-EG62NTWS.js → chunk-U52FCZQ6.js} +7 -7
  10. package/dist/{chunk-WD5UW4A4.js → chunk-VFRR3M72.js} +1 -1
  11. package/dist/constants.cjs +1 -1
  12. package/dist/constants.js +1 -1
  13. package/dist/eventcatalog.cjs +1 -1
  14. package/dist/eventcatalog.js +13 -13
  15. package/dist/federation/federate.js +5 -5
  16. package/dist/federation/source-provider.js +2 -2
  17. package/dist/generate.cjs +1 -1
  18. package/dist/generate.js +3 -3
  19. package/dist/utils/cli-logger.cjs +1 -1
  20. package/dist/utils/cli-logger.js +2 -2
  21. package/eventcatalog/src/components/Grids/DomainGrid.tsx +20 -12
  22. package/eventcatalog/src/components/Grids/message-link.spec.ts +59 -0
  23. package/eventcatalog/src/components/Grids/message-link.ts +38 -0
  24. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +58 -12
  25. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +5 -5
  26. package/eventcatalog/src/components/SchemaExplorer/SchemaListItem.tsx +2 -0
  27. package/eventcatalog/src/components/SchemaExplorer/types.ts +10 -1
  28. package/eventcatalog/src/components/SchemaExplorer/useSchemaDetails.ts +69 -0
  29. package/eventcatalog/src/components/SchemaExplorer/utils.ts +12 -0
  30. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.spec.ts +88 -0
  31. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +19 -8
  32. package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +3 -269
  33. package/eventcatalog/src/pages/schemas/explorer/content/[key].json.ts +17 -0
  34. package/eventcatalog/src/utils/collections/domains.ts +1 -80
  35. package/eventcatalog/src/utils/collections/hydrate-services.ts +51 -0
  36. package/eventcatalog/src/utils/collections/systems.ts +17 -7
  37. package/eventcatalog/src/utils/schema-explorer.ts +276 -0
  38. package/package.json +3 -3
  39. package/dist/{chunk-A2RZR3U4.js → chunk-6K7XZAYI.js} +3 -3
@@ -0,0 +1,51 @@
1
+ import { findInMap } from '@utils/collections/util';
2
+
3
+ /**
4
+ * Resolve service/agent pointers to collection entries and hydrate their
5
+ * sends/receives/readsFrom/writesTo relationships.
6
+ */
7
+ export const hydrateServices = (
8
+ servicesList: any[],
9
+ serviceMap: Map<string, any[]>,
10
+ messageMap: Map<string, any[]>,
11
+ containerMap: Map<string, any[]>
12
+ ) => {
13
+ return servicesList
14
+ .map((service: { id: string; version: string | undefined }) => findInMap(serviceMap, service.id, service.version))
15
+ .filter((s) => !!s)
16
+ .map((service) => {
17
+ const sends = (service.data.sends || [])
18
+ .map((msg: any) => findInMap(messageMap, msg.id, msg.version))
19
+ .filter((m: any) => !!m);
20
+
21
+ const receives = (service.data.receives || [])
22
+ .map((msg: any) => findInMap(messageMap, msg.id, msg.version))
23
+ .filter((m: any) => !!m);
24
+
25
+ const readsFrom = (service.data.readsFrom || [])
26
+ .map((c: any) => findInMap(containerMap, c.id, c.version))
27
+ .filter((c: any) => !!c);
28
+
29
+ const writesTo = (service.data.writesTo || [])
30
+ .map((c: any) => findInMap(containerMap, c.id, c.version))
31
+ .filter((c: any) => !!c);
32
+
33
+ return {
34
+ ...service,
35
+ data: {
36
+ ...service.data,
37
+ sends: sends as any,
38
+ receives: receives as any,
39
+ readsFrom: readsFrom as any,
40
+ writesTo: writesTo as any,
41
+ },
42
+ };
43
+ });
44
+ };
45
+
46
+ export const hydrateAgents = (
47
+ agentsList: any[],
48
+ agentMap: Map<string, any[]>,
49
+ messageMap: Map<string, any[]>,
50
+ containerMap: Map<string, any[]>
51
+ ) => hydrateServices(agentsList, agentMap, messageMap, containerMap);
@@ -1,5 +1,6 @@
1
1
  import { getCollection } from 'astro:content';
2
2
  import type { CollectionEntry } from 'astro:content';
3
+ import { hydrateServices } from '@utils/collections/hydrate-services';
3
4
  import { createVersionedMap, findInMap } from './util';
4
5
 
5
6
  const CACHE_ENABLED = process.env.DISABLE_EVENTCATALOG_CACHE !== 'true';
@@ -7,24 +8,28 @@ export type System = CollectionEntry<'systems'>;
7
8
 
8
9
  interface Props {
9
10
  getAllVersions?: boolean;
11
+ enrichServices?: boolean;
10
12
  }
11
13
 
12
14
  // cache for build time
13
15
  let memoryCache: Record<string, System[]> = {};
14
16
 
15
- export const getSystems = async ({ getAllVersions = true }: Props = {}): Promise<System[]> => {
16
- const cacheKey = getAllVersions ? 'allVersions' : 'currentVersions';
17
+ export const getSystems = async ({ getAllVersions = true, enrichServices = false }: Props = {}): Promise<System[]> => {
18
+ const cacheKey = `${getAllVersions ? 'allVersions' : 'currentVersions'}-${enrichServices ? 'enriched' : 'simple'}`;
17
19
 
18
20
  if (memoryCache[cacheKey] && memoryCache[cacheKey].length > 0 && CACHE_ENABLED) {
19
21
  return memoryCache[cacheKey];
20
22
  }
21
23
 
22
- const [allSystems, allServices, allFlows, allEntities, allContainers] = await Promise.all([
24
+ const [allSystems, allServices, allFlows, allEntities, allContainers, allEvents, allCommands, allQueries] = await Promise.all([
23
25
  getCollection('systems'),
24
26
  getCollection('services'),
25
27
  getCollection('flows'),
26
28
  getCollection('entities'),
27
29
  getCollection('containers'),
30
+ enrichServices ? getCollection('events') : Promise.resolve([]),
31
+ enrichServices ? getCollection('commands') : Promise.resolve([]),
32
+ enrichServices ? getCollection('queries') : Promise.resolve([]),
28
33
  ]);
29
34
 
30
35
  // Build optimized map of id -> versions (sorted latest first)
@@ -33,6 +38,7 @@ export const getSystems = async ({ getAllVersions = true }: Props = {}): Promise
33
38
  const flowMap = createVersionedMap(allFlows);
34
39
  const entityMap = createVersionedMap(allEntities);
35
40
  const containerMap = createVersionedMap(allContainers);
41
+ const messageMap = createVersionedMap([...allEvents, ...allCommands, ...allQueries]);
36
42
 
37
43
  // Filter systems
38
44
  const targetSystems = allSystems.filter((system) => {
@@ -47,10 +53,14 @@ export const getSystems = async ({ getAllVersions = true }: Props = {}): Promise
47
53
  const latestVersion = systemVersions[0]?.data.version || system.data.version;
48
54
  const versions = systemVersions.map((s) => s.data.version);
49
55
 
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);
56
+ // Resolve service pointers to their full collection entries.
57
+ // Architecture grids need sends/receives hydrated so command/query links
58
+ // keep the correct collection instead of falling back to events.
59
+ const services = enrichServices
60
+ ? hydrateServices(system.data.services || [], serviceMap, messageMap, containerMap)
61
+ : (system.data.services || [])
62
+ .map((service: { id: string; version?: string }) => findInMap(serviceMap, service.id, service.version))
63
+ .filter((s): s is NonNullable<typeof s> => !!s);
54
64
 
55
65
  // Resolve flow pointers to their full collection entries
56
66
  const flows = (system.data.flows || [])
@@ -0,0 +1,276 @@
1
+ import { createHash } from 'node:crypto';
2
+ import type { SchemaItem, SchemaDetails, Producer, Consumer } from '@components/SchemaExplorer/types';
3
+ import { getEvents } from '@utils/collections/events';
4
+ import { getCommands } from '@utils/collections/commands';
5
+ import { getQueries } from '@utils/collections/queries';
6
+ import { getServices, getSpecificationsForService } from '@utils/collections/services';
7
+ import { getDomains, getSpecificationsForDomain } from '@utils/collections/domains';
8
+ import { getDataProducts } from '@utils/collections/data-products';
9
+ import { getOwner } from '@utils/collections/owners';
10
+ import { buildUrl } from '@utils/url-builder';
11
+ import { resourceFileExists, readResourceFile } from '@utils/resource-files';
12
+ import { getExamplesForResource } from '@utils/collections/examples';
13
+ import { getCollection } from 'astro:content';
14
+ import path from 'path';
15
+
16
+ // Helper function to enrich owners with full details
17
+ async function enrichOwners(ownersRaw: any[]) {
18
+ if (!ownersRaw || ownersRaw.length === 0) return [];
19
+
20
+ const owners = await Promise.all(ownersRaw.map(getOwner));
21
+ const filteredOwners = owners.filter((o) => o !== undefined);
22
+
23
+ return filteredOwners.map((o) => ({
24
+ id: o.data.id,
25
+ name: o.data.name,
26
+ type: o.collection,
27
+ href: buildUrl(`/docs/${o.collection}/${o.data.id}`),
28
+ }));
29
+ }
30
+
31
+ async function buildRegistry() {
32
+ // Fetch all messages
33
+ const events = await getEvents({ getAllVersions: true, hydrateServices: false });
34
+ const commands = await getCommands({ getAllVersions: true, hydrateServices: false });
35
+ const queries = await getQueries({ getAllVersions: true, hydrateServices: false });
36
+ const schemaEntries = await getCollection('schemas');
37
+
38
+ // Fetch all services
39
+ const services = await getServices({ getAllVersions: true });
40
+
41
+ // Combine all messages
42
+ const allMessages = [...events, ...commands, ...queries];
43
+ const messagesBySchemaReference = new Map(
44
+ allMessages.map((message) => [`${message.collection}:${message.data.id}:${message.data.version}`, message])
45
+ );
46
+
47
+ // Read message schemas from the generated schemas collection.
48
+ const messagesWithSchemas = await Promise.all(
49
+ schemaEntries.map(async (schema) => {
50
+ const message = messagesBySchemaReference.get(
51
+ `${schema.data.message.collectionName}:${schema.data.message.id}:${schema.data.message.version}`
52
+ );
53
+ const schemaPath = schema.data.file || schema.data.source.path || '';
54
+ const schemaExtension = path.extname(schemaPath).slice(1) || schema.data.format;
55
+ // The collection types describe raw content references. With
56
+ // hydrateServices: false, the loaders return compact { id, version } pairs.
57
+ const producers = (message?.data.producers || []) as unknown as Producer[];
58
+ const consumers = (message?.data.consumers || []) as unknown as Consumer[];
59
+
60
+ return {
61
+ collection: schema.data.message.collectionName,
62
+ data: {
63
+ id: schema.data.message.id,
64
+ name: schema.data.message.name || message?.data.name || schema.data.name || schema.data.message.id,
65
+ version: schema.data.message.version,
66
+ summary: schema.data.message.summary || message?.data.summary,
67
+ schemaPath,
68
+ owners: await enrichOwners(schema.data.message.owners || []),
69
+ // The list shows only the first producer's label; keep this bounded
70
+ // regardless of how many resources reference the message.
71
+ producerName: producers[0]?.id,
72
+ },
73
+ loadDetails: () => {
74
+ let examples: SchemaDetails['examples'] = [];
75
+ if (message) {
76
+ try {
77
+ examples = getExamplesForResource(message);
78
+ } catch (error) {
79
+ console.error(`Error reading examples for ${message.data.id}:`, error);
80
+ }
81
+ }
82
+ return {
83
+ schemaContent: schema.data.content || '',
84
+ examples,
85
+ data: { producers, consumers },
86
+ };
87
+ },
88
+ schemaExtension,
89
+ };
90
+ })
91
+ );
92
+
93
+ // Filter services with specifications and read spec content - only keep essential data
94
+ const servicesWithSpecs = await Promise.all(
95
+ services.map(async (service) => {
96
+ try {
97
+ const specifications = getSpecificationsForService(service);
98
+
99
+ if (specifications.length === 0) {
100
+ return null;
101
+ }
102
+
103
+ return await Promise.all(
104
+ specifications.map(async (spec) => {
105
+ if (!resourceFileExists(service, spec.path)) {
106
+ return null;
107
+ }
108
+
109
+ const schemaExtension = spec.type;
110
+ const enrichedOwners = await enrichOwners(service.data.owners || []);
111
+
112
+ return {
113
+ collection: 'services',
114
+ data: {
115
+ id: `${service.data.id}`,
116
+ name: `${service.data.name} - ${spec.name}`,
117
+ version: service.data.version,
118
+ summary: service.data.summary,
119
+ schemaPath: spec.path,
120
+ owners: enrichedOwners,
121
+ },
122
+ loadDetails: () => ({ schemaContent: readResourceFile(service, spec.path) ?? '', examples: [] }),
123
+ schemaExtension,
124
+ specType: spec.type,
125
+ specName: spec.name,
126
+ specFilenameWithoutExtension: spec.filenameWithoutExtension,
127
+ };
128
+ })
129
+ );
130
+ } catch (error) {
131
+ console.error(`Error reading specifications for service ${service.data.id}:`, error);
132
+ return null;
133
+ }
134
+ })
135
+ );
136
+
137
+ // Flatten and filter out null values
138
+ const flatServicesWithSpecs = servicesWithSpecs.flat().filter((service) => service !== null);
139
+
140
+ // Fetch all domains
141
+ const domains = await getDomains({ getAllVersions: true });
142
+
143
+ // Filter domains with specifications and read spec content - only keep essential data
144
+ const domainsWithSpecs = await Promise.all(
145
+ domains.map(async (domain) => {
146
+ try {
147
+ const specifications = getSpecificationsForDomain(domain);
148
+
149
+ if (specifications.length === 0) {
150
+ return null;
151
+ }
152
+
153
+ return await Promise.all(
154
+ specifications.map(async (spec) => {
155
+ if (!resourceFileExists(domain, spec.path)) {
156
+ return null;
157
+ }
158
+
159
+ const schemaExtension = spec.type;
160
+ const enrichedOwners = await enrichOwners(domain.data.owners || []);
161
+
162
+ return {
163
+ collection: 'domains',
164
+ data: {
165
+ id: `${domain.data.id}`,
166
+ name: `${domain.data.name} - ${spec.name}`,
167
+ version: domain.data.version,
168
+ summary: domain.data.summary,
169
+ schemaPath: spec.path,
170
+ owners: enrichedOwners,
171
+ },
172
+ loadDetails: () => ({ schemaContent: readResourceFile(domain, spec.path) ?? '', examples: [] }),
173
+ schemaExtension,
174
+ specType: spec.type,
175
+ specName: spec.name,
176
+ specFilenameWithoutExtension: spec.filenameWithoutExtension,
177
+ };
178
+ })
179
+ );
180
+ } catch (error) {
181
+ console.error(`Error reading specifications for domain ${domain.data.id}:`, error);
182
+ return null;
183
+ }
184
+ })
185
+ );
186
+
187
+ // Flatten and filter out null values for domains
188
+ const flatDomainsWithSpecs = domainsWithSpecs.flat().filter((domain) => domain !== null);
189
+
190
+ // Fetch all data products and extract contracts from outputs
191
+ const dataProducts = await getDataProducts({ getAllVersions: true });
192
+
193
+ // Filter data products with contracts in outputs and read contract content
194
+ const dataProductsWithContracts = await Promise.all(
195
+ dataProducts.map(async (dataProduct) => {
196
+ try {
197
+ const outputs = dataProduct.data.outputs || [];
198
+ const outputsWithContracts = outputs.filter((output) => output.contract);
199
+
200
+ if (outputsWithContracts.length === 0) {
201
+ return null;
202
+ }
203
+
204
+ return await Promise.all(
205
+ outputsWithContracts.map(async (output) => {
206
+ const contract = output.contract!;
207
+ if (!resourceFileExists(dataProduct, contract.path)) {
208
+ return null;
209
+ }
210
+
211
+ const schemaExtension = path.extname(contract.path).slice(1) || 'json';
212
+ const enrichedOwners = await enrichOwners(dataProduct.data.owners || []);
213
+
214
+ return {
215
+ collection: 'data-products',
216
+ data: {
217
+ id: `${dataProduct.data.id}__${contract.path}`,
218
+ name: contract.name,
219
+ version: dataProduct.data.version,
220
+ summary: `Data contract for ${dataProduct.data.name}`,
221
+ schemaPath: contract.path,
222
+ owners: enrichedOwners,
223
+ },
224
+ loadDetails: () => ({ schemaContent: readResourceFile(dataProduct, contract.path) ?? '', examples: [] }),
225
+ schemaExtension,
226
+ contractType: contract.type,
227
+ dataProductId: dataProduct.data.id,
228
+ dataProductVersion: dataProduct.data.version,
229
+ };
230
+ })
231
+ );
232
+ } catch (error) {
233
+ console.error(`Error reading contracts for data product ${dataProduct.data.id}:`, error);
234
+ return null;
235
+ }
236
+ })
237
+ );
238
+
239
+ // Flatten and filter out null values for data product contracts
240
+ const flatDataProductContracts = dataProductsWithContracts.flat().filter((contract) => contract !== null);
241
+
242
+ return new Map(
243
+ [...messagesWithSchemas, ...flatServicesWithSpecs, ...flatDomainsWithSpecs, ...flatDataProductContracts].map((entry) => {
244
+ const { loadDetails, ...item } = entry;
245
+ const key = createHash('sha256')
246
+ .update(
247
+ JSON.stringify([
248
+ item.collection,
249
+ item.data.id,
250
+ item.data.version,
251
+ item.data.schemaPath,
252
+ 'specType' in item ? item.specType : '',
253
+ ])
254
+ )
255
+ .digest('hex');
256
+ return [
257
+ key,
258
+ { item: { ...item, contentUrl: buildUrl(`/schemas/explorer/content/${key}.json`, true) } as SchemaItem, loadDetails },
259
+ ];
260
+ })
261
+ );
262
+ }
263
+
264
+ // Share metadata during production rendering; dev rebuilds it to reflect content edits.
265
+ let registry: ReturnType<typeof buildRegistry> | undefined;
266
+ export const getSchemaRegistry = () => {
267
+ if (import.meta.env.DEV || process.env.DISABLE_EVENTCATALOG_CACHE === 'true') return buildRegistry();
268
+ return (registry ??= buildRegistry().catch((error) => {
269
+ registry = undefined;
270
+ throw error;
271
+ }));
272
+ };
273
+ export const getSchemaMetadata = async (): Promise<SchemaItem[]> =>
274
+ [...(await getSchemaRegistry()).values()].map(({ item }) => item);
275
+ export const getSchemaDetails = async (key: string): Promise<SchemaDetails | undefined> =>
276
+ (await getSchemaRegistry()).get(key)?.loadDetails();
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "license": "SEE LICENSE IN LICENSE",
9
9
  "type": "module",
10
- "version": "4.10.9",
10
+ "version": "4.10.11",
11
11
  "publishConfig": {
12
12
  "access": "public"
13
13
  },
@@ -119,8 +119,8 @@
119
119
  "uuid": "^11.1.1",
120
120
  "zod": "^4.3.6",
121
121
  "@eventcatalog/linter": "1.1.18",
122
- "@eventcatalog/visualiser": "^4.1.4",
123
- "@eventcatalog/sdk": "2.29.0"
122
+ "@eventcatalog/sdk": "2.29.0",
123
+ "@eventcatalog/visualiser": "^4.1.4"
124
124
  },
125
125
  "devDependencies": {
126
126
  "@astrojs/check": "^0.9.10",
@@ -1,9 +1,9 @@
1
- import {
2
- createFileSystemSourceProvider
3
- } from "./chunk-SDZQJTJW.js";
4
1
  import {
5
2
  createGitHubSourceProvider
6
3
  } from "./chunk-352FGP6W.js";
4
+ import {
5
+ createFileSystemSourceProvider
6
+ } from "./chunk-SDZQJTJW.js";
7
7
 
8
8
  // src/federation/source-provider.ts
9
9
  var createFederationSourceProvider = (projectDirectory, providers = {}) => {