@eventcatalog/core 4.10.8 → 4.10.10

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 (44) 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-W6IMDOJJ.js → chunk-K45ABG6N.js} +1 -1
  6. package/dist/{chunk-LB5EMDAY.js → chunk-PR77FB54.js} +1 -1
  7. package/dist/{chunk-CDWE6WWB.js → chunk-QXANW76J.js} +1 -1
  8. package/dist/{chunk-W5C4K7F2.js → chunk-U6R6BFJ5.js} +1 -1
  9. package/dist/{chunk-DRK5QSQC.js → chunk-Y6VX2CGL.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/MDX/NodeGraph/NodeGraph.astro +6 -2
  19. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +58 -12
  20. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +5 -5
  21. package/eventcatalog/src/components/SchemaExplorer/SchemaListItem.tsx +2 -0
  22. package/eventcatalog/src/components/SchemaExplorer/types.ts +10 -1
  23. package/eventcatalog/src/components/SchemaExplorer/useSchemaDetails.ts +69 -0
  24. package/eventcatalog/src/components/SchemaExplorer/utils.ts +12 -0
  25. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/field-lineage/_index.data.spec.ts +52 -0
  26. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/field-lineage/_index.data.ts +20 -11
  27. package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +3 -269
  28. package/eventcatalog/src/pages/schemas/explorer/content/[key].json.ts +17 -0
  29. package/eventcatalog/src/stores/sidebar-store/state.ts +3 -28
  30. package/eventcatalog/src/utils/collections/field-usage.ts +48 -0
  31. package/eventcatalog/src/utils/node-graphs/channel-node-graph.ts +2 -2
  32. package/eventcatalog/src/utils/node-graphs/compact-visualiser-graph.ts +246 -0
  33. package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +2 -2
  34. package/eventcatalog/src/utils/node-graphs/data-products-node-graph.ts +2 -2
  35. package/eventcatalog/src/utils/node-graphs/domains-canvas.ts +2 -2
  36. package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +2 -2
  37. package/eventcatalog/src/utils/node-graphs/flows-node-graph.ts +2 -2
  38. package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +2 -2
  39. package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +2 -2
  40. package/eventcatalog/src/utils/node-graphs/system-context-node-graph.ts +2 -2
  41. package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +8 -3
  42. package/eventcatalog/src/utils/node-graphs/utils/utils.ts +30 -2
  43. package/eventcatalog/src/utils/schema-explorer.ts +276 -0
  44. package/package.json +3 -3
@@ -1,5 +1,4 @@
1
1
  import { getCollection, type CollectionEntry } from 'astro:content';
2
- import dagre from 'dagre';
3
2
  import {
4
3
  createDagreGraph,
5
4
  generateIdForNode,
@@ -10,6 +9,7 @@ import {
10
9
  buildContextMenuForResource,
11
10
  DEFAULT_NODE_WIDTH,
12
11
  DEFAULT_NODE_HEIGHT,
12
+ layoutDagreGraph,
13
13
  } from '@utils/node-graphs/utils/utils';
14
14
 
15
15
  import { findInMap, createVersionedMap, mergeMaps, collectionToResourceMap } from '@utils/collections/util';
@@ -220,7 +220,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
220
220
 
221
221
  if (layout) {
222
222
  // Render the diagram in memory getting the X and Y
223
- dagre.layout(flow);
223
+ layoutDagreGraph(flow);
224
224
  }
225
225
 
226
226
  // Find any duplicated edges, and merge them into one edge
@@ -1,11 +1,11 @@
1
1
  import { getCollection, type CollectionEntry } from 'astro:content';
2
- import dagre from 'dagre';
3
2
  import {
4
3
  generateIdForNode,
5
4
  createDagreGraph,
6
5
  calculatedNodes,
7
6
  createEdge,
8
7
  getOperationFields,
8
+ layoutDagreGraph,
9
9
  } from '@utils/node-graphs/utils/utils';
10
10
  import { findInMap, createVersionedMap } from '@utils/collections/util';
11
11
  import type { Node, Edge } from '@xyflow/react';
@@ -247,7 +247,7 @@ export const getDomainsCanvasData = async (): Promise<DomainCanvasData> => {
247
247
  });
248
248
 
249
249
  // Calculate layout using dagre
250
- dagre.layout(dagreGraph);
250
+ layoutDagreGraph(dagreGraph);
251
251
 
252
252
  // Apply calculated positions to nodes
253
253
  const layoutedDomainNodes = calculatedNodes(dagreGraph, domainNodes);
@@ -1,5 +1,4 @@
1
1
  import { getCollection } from 'astro:content';
2
- import dagre from 'dagre';
3
2
  import {
4
3
  createDagreGraph,
5
4
  calculatedNodes,
@@ -7,6 +6,7 @@ import {
7
6
  getEdgeLabelForServiceAsTarget,
8
7
  generatedIdForEdge,
9
8
  createEdge,
9
+ layoutDagreGraph,
10
10
  } from '@utils/node-graphs/utils/utils';
11
11
  import { getNodesAndEdges as getServicesNodeAndEdges } from './services-node-graph';
12
12
  import { getNodesAndEdges as getAgentsNodeAndEdges } from './agents-node-graph';
@@ -176,7 +176,7 @@ export const getNodesAndEdges = async ({
176
176
  }
177
177
 
178
178
  if (layout) {
179
- dagre.layout(flow);
179
+ layoutDagreGraph(flow);
180
180
  }
181
181
 
182
182
  return {
@@ -1,5 +1,4 @@
1
1
  import { getCollection, type CollectionEntry } from 'astro:content';
2
- import dagre from 'dagre';
3
2
  import {
4
3
  createDagreGraph,
5
4
  calculatedNodes,
@@ -7,6 +6,7 @@ import {
7
6
  DEFAULT_NODE_HEIGHT,
8
7
  buildContextMenuForResource,
9
8
  buildContextMenuForService,
9
+ layoutDagreGraph,
10
10
  } from '@utils/node-graphs/utils/utils';
11
11
  import { MarkerType } from '@xyflow/react';
12
12
  import type { Node as NodeType } from '@xyflow/react';
@@ -321,7 +321,7 @@ export const getNodesAndEdges = async ({ id, defaultFlow, version, mode = 'simpl
321
321
  graph.setEdge(edge.source, edge.target);
322
322
  });
323
323
 
324
- dagre.layout(graph);
324
+ layoutDagreGraph(graph);
325
325
 
326
326
  return {
327
327
  nodes: calculatedNodes(graph, nodes),
@@ -1,7 +1,6 @@
1
1
  // import { getColor } from '@utils/colors';
2
2
  import { getEvents } from '@utils/collections/events';
3
3
  import { getCollection, type CollectionEntry } from 'astro:content';
4
- import dagre from 'dagre';
5
4
  import {
6
5
  calculatedNodes,
7
6
  createDagreGraph,
@@ -12,6 +11,7 @@ import {
12
11
  getEdgeLabelForMessageAsSource,
13
12
  getEdgeLabelForServiceAsTarget,
14
13
  versionMatches,
14
+ layoutDagreGraph,
15
15
  } from './utils/utils';
16
16
  import { MarkerType, type Node, type Edge } from '@xyflow/react';
17
17
  import {
@@ -691,7 +691,7 @@ const getNodesAndEdges = async ({
691
691
  });
692
692
 
693
693
  // Render the diagram in memory getting hte X and Y
694
- dagre.layout(flow);
694
+ layoutDagreGraph(flow);
695
695
 
696
696
  return {
697
697
  nodes: calculatedNodes(flow, nodes),
@@ -1,5 +1,4 @@
1
1
  import { getCollection, type CollectionEntry } from 'astro:content';
2
- import dagre from 'dagre';
3
2
  import {
4
3
  createDagreGraph,
5
4
  generateIdForNode,
@@ -14,6 +13,7 @@ import {
14
13
  DEFAULT_NODE_HEIGHT,
15
14
  partitionMessagesByGroup,
16
15
  getOperationFields,
16
+ layoutDagreGraph,
17
17
  } from '@utils/node-graphs/utils/utils';
18
18
 
19
19
  const sanitizeGroupId = (name: string) => name.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase();
@@ -636,7 +636,7 @@ export const getNodesAndEdges = async ({
636
636
 
637
637
  if (layout) {
638
638
  // Render the diagram in memory getting the X and Y
639
- dagre.layout(flow);
639
+ layoutDagreGraph(flow);
640
640
  }
641
641
 
642
642
  // Find any duplicated edges, and merge them into one edge
@@ -1,6 +1,5 @@
1
1
  import { getCollection } from 'astro:content';
2
2
  import type { CollectionEntry } from 'astro:content';
3
- import dagre from 'dagre';
4
3
  import {
5
4
  createDagreGraph,
6
5
  calculatedNodes,
@@ -8,6 +7,7 @@ import {
8
7
  generateIdForNode,
9
8
  buildContextMenuForSystem,
10
9
  DEFAULT_NODE_HEIGHT,
10
+ layoutDagreGraph,
11
11
  } from '@utils/node-graphs/utils/utils';
12
12
  import { MarkerType } from '@xyflow/react';
13
13
  import { createVersionedMap, findInMap } from '@utils/collections/util';
@@ -302,7 +302,7 @@ const buildContextGraphFromSeeds = ({
302
302
  edges.forEach((edge) => flow.setEdge(edge.source, edge.target));
303
303
 
304
304
  if (layout) {
305
- dagre.layout(flow);
305
+ layoutDagreGraph(flow);
306
306
  }
307
307
 
308
308
  return {
@@ -1,6 +1,11 @@
1
1
  import { getCollection } from 'astro:content';
2
- import dagre from 'dagre';
3
- import { createDagreGraph, calculatedNodes, DEFAULT_NODE_WIDTH, DEFAULT_NODE_HEIGHT } from '@utils/node-graphs/utils/utils';
2
+ import {
3
+ createDagreGraph,
4
+ calculatedNodes,
5
+ DEFAULT_NODE_WIDTH,
6
+ DEFAULT_NODE_HEIGHT,
7
+ layoutDagreGraph,
8
+ } from '@utils/node-graphs/utils/utils';
4
9
  import { getNodesAndEdges as getServicesNodeAndEdges } from './services-node-graph';
5
10
  import { getNodesAndEdges as getContainerNodeAndEdges } from './container-node-graph';
6
11
  import merge from 'lodash.merge';
@@ -121,7 +126,7 @@ export const getNodesAndEdges = async ({
121
126
  }
122
127
 
123
128
  if (layout) {
124
- dagre.layout(flow);
129
+ layoutDagreGraph(flow);
125
130
  }
126
131
 
127
132
  let laidOutNodes = calculatedNodes(flow, Array.from(nodes.values()));
@@ -109,6 +109,34 @@ export const createDagreGraph = ({ ranksep = 180, nodesep = 50, ...rest }: any)
109
109
  return graph;
110
110
  };
111
111
 
112
+ /**
113
+ * network-simplex produces the nicest layered layout but is O(V·E) and hangs on
114
+ * large architecture maps. tight-tree keeps the same LR layering with far less
115
+ * work. Callers can still force a ranker via createDagreGraph({ ranker }).
116
+ */
117
+ export const LARGE_GRAPH_NODE_THRESHOLD = 80;
118
+ export const LARGE_GRAPH_EDGE_THRESHOLD = 200;
119
+ export const LARGE_GRAPH_RANKER = 'tight-tree';
120
+
121
+ export const selectDagreRanker = (nodeCount: number, edgeCount: number, explicitRanker?: string) => {
122
+ if (explicitRanker) return explicitRanker;
123
+ if (nodeCount >= LARGE_GRAPH_NODE_THRESHOLD || edgeCount >= LARGE_GRAPH_EDGE_THRESHOLD) {
124
+ return LARGE_GRAPH_RANKER;
125
+ }
126
+ return undefined;
127
+ };
128
+
129
+ export const layoutDagreGraph = (flow: dagre.graphlib.Graph) => {
130
+ const label = (flow.graph() || {}) as { ranker?: string };
131
+ const nodeCount = flow.nodeCount();
132
+ const edgeCount = flow.edgeCount();
133
+ const ranker = selectDagreRanker(nodeCount, edgeCount, label.ranker);
134
+ if (ranker && label.ranker !== ranker) {
135
+ flow.setGraph({ ...label, ranker });
136
+ }
137
+ dagre.layout(flow);
138
+ };
139
+
112
140
  export const createEdge = (edgeOptions: Edge) => {
113
141
  return {
114
142
  label: 'subscribed by',
@@ -430,8 +458,8 @@ export const getNodesAndEdgesFromDagre = ({
430
458
  flow.setEdge(edge.source, edge.target);
431
459
  });
432
460
 
433
- // Render the diagram in memory getting hte X and Y
434
- dagre.layout(flow);
461
+ // Render the diagram in memory getting the X and Y
462
+ layoutDagreGraph(flow);
435
463
 
436
464
  return {
437
465
  nodes: calculatedNodes(flow, nodes),
@@ -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.8",
10
+ "version": "4.10.10",
11
11
  "publishConfig": {
12
12
  "access": "public"
13
13
  },
@@ -118,9 +118,9 @@
118
118
  "update-notifier": "^7.3.1",
119
119
  "uuid": "^11.1.1",
120
120
  "zod": "^4.3.6",
121
- "@eventcatalog/linter": "1.1.18",
122
121
  "@eventcatalog/sdk": "2.29.0",
123
- "@eventcatalog/visualiser": "^4.1.3"
122
+ "@eventcatalog/visualiser": "^4.1.4",
123
+ "@eventcatalog/linter": "1.1.18"
124
124
  },
125
125
  "devDependencies": {
126
126
  "@astrojs/check": "^0.9.10",