@eventcatalog/core 3.44.2 → 3.46.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.
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-THDZCUFV.js → chunk-DOHA5HNJ.js} +1 -1
- package/dist/{chunk-BLJ5FRR3.js → chunk-FNOYJEUK.js} +1 -1
- package/dist/{chunk-LIIOK6SZ.js → chunk-JS6IYB55.js} +1 -1
- package/dist/{chunk-IU3D7JRW.js → chunk-TLLUDBO4.js} +1 -1
- package/dist/{chunk-RUACTEJR.js → chunk-X4AESI6E.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.config.d.cts +29 -0
- package/dist/eventcatalog.config.d.ts +29 -0
- package/dist/eventcatalog.js +10 -10
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/astro.config.mjs +7 -10
- package/eventcatalog/ec.config.mjs +20 -0
- package/eventcatalog/src/components/MDX/SchemaViewer/SchemaViewerPortal.tsx +1 -1
- package/eventcatalog/src/components/MDX/SchemaViewer/SchemaViewerRoot.astro +19 -47
- package/eventcatalog/src/components/MDX/SchemaViewer/schema-viewer-utils.spec.ts +53 -0
- package/eventcatalog/src/components/MDX/SchemaViewer/schema-viewer-utils.ts +133 -0
- package/eventcatalog/src/content.config.ts +65 -0
- package/eventcatalog/src/enterprise/api/schemas/[collection]/[id]/[version]/index.ts +42 -50
- package/eventcatalog/src/enterprise/tools/catalog-tools.ts +23 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +1 -1
- package/eventcatalog/src/pages/docs/llm/schemas.txt.ts +25 -17
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/_index.data.ts +33 -16
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +18 -8
- package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +45 -21
- package/eventcatalog/src/stores/sidebar-store/builders/message.ts +25 -2
- package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +1 -0
- package/eventcatalog/src/stores/sidebar-store/state.ts +3 -0
- package/eventcatalog/src/utils/collections/schema-loader.ts +523 -0
- package/eventcatalog/src/utils/files.ts +1 -1
- package/package.json +4 -4
|
@@ -67,6 +67,7 @@ export type ResourceGroupContext = {
|
|
|
67
67
|
entities?: CollectionEntry<'entities'>[];
|
|
68
68
|
dataProducts: CollectionEntry<'data-products'>[];
|
|
69
69
|
diagrams: CollectionEntry<'diagrams'>[];
|
|
70
|
+
schemas?: CollectionEntry<'schemas'>[];
|
|
70
71
|
adrs: Adr[];
|
|
71
72
|
users?: CollectionEntry<'users'>[];
|
|
72
73
|
teams?: CollectionEntry<'teams'>[];
|
|
@@ -292,6 +292,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
|
|
|
292
292
|
dataProducts,
|
|
293
293
|
entities,
|
|
294
294
|
adrs,
|
|
295
|
+
schemas,
|
|
295
296
|
resourceDocs,
|
|
296
297
|
resourceDocCategories,
|
|
297
298
|
] = await Promise.all([
|
|
@@ -309,6 +310,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
|
|
|
309
310
|
getDataProducts({ getAllVersions: false }),
|
|
310
311
|
getEntities({ getAllVersions: false }),
|
|
311
312
|
getAdrs({ getAllVersions: false }),
|
|
313
|
+
getCollection('schemas'),
|
|
312
314
|
getResourceDocs(),
|
|
313
315
|
getResourceDocCategories(),
|
|
314
316
|
]);
|
|
@@ -330,6 +332,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
|
|
|
330
332
|
containers,
|
|
331
333
|
channels,
|
|
332
334
|
diagrams,
|
|
335
|
+
schemas,
|
|
333
336
|
dataProducts,
|
|
334
337
|
entities,
|
|
335
338
|
adrs,
|
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
import type { Loader } from 'astro/loaders';
|
|
2
|
+
import { glob } from 'glob';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import fsSync from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import pc from 'picocolors';
|
|
9
|
+
import { isEventCatalogScaleEnabled } from '../feature';
|
|
10
|
+
import { sortVersioned } from './util';
|
|
11
|
+
|
|
12
|
+
const colors = pc.createColors(true);
|
|
13
|
+
|
|
14
|
+
type MessageCollection = 'events' | 'commands' | 'queries';
|
|
15
|
+
|
|
16
|
+
type SchemaReference = {
|
|
17
|
+
id?: string;
|
|
18
|
+
ref?: string;
|
|
19
|
+
file?: string;
|
|
20
|
+
path?: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
format?: string;
|
|
23
|
+
environments?: string[];
|
|
24
|
+
default?: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type SchemaSourceEntry = {
|
|
28
|
+
provider: string;
|
|
29
|
+
id?: string;
|
|
30
|
+
path?: string;
|
|
31
|
+
url?: string;
|
|
32
|
+
ref?: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type SchemaSource = {
|
|
37
|
+
type: 'schemas';
|
|
38
|
+
name: string;
|
|
39
|
+
canResolve: (ref: string) => boolean;
|
|
40
|
+
resolve: (
|
|
41
|
+
ref: string,
|
|
42
|
+
context?: { messageFilePath?: string }
|
|
43
|
+
) => Promise<
|
|
44
|
+
| {
|
|
45
|
+
id: string;
|
|
46
|
+
name?: string;
|
|
47
|
+
format?: string;
|
|
48
|
+
content: string;
|
|
49
|
+
source: SchemaSourceEntry;
|
|
50
|
+
}
|
|
51
|
+
| undefined
|
|
52
|
+
>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type MessageFrontmatter = {
|
|
56
|
+
id?: string;
|
|
57
|
+
name?: string;
|
|
58
|
+
version?: string;
|
|
59
|
+
summary?: string;
|
|
60
|
+
owners?: string[];
|
|
61
|
+
schemaPath?: string;
|
|
62
|
+
schemas?: SchemaReference[];
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type MessageSchemaResource = {
|
|
66
|
+
id: string;
|
|
67
|
+
schemaId?: string;
|
|
68
|
+
ref?: string;
|
|
69
|
+
name?: string;
|
|
70
|
+
version?: string;
|
|
71
|
+
format: string;
|
|
72
|
+
content?: string;
|
|
73
|
+
file?: string;
|
|
74
|
+
filePath?: string;
|
|
75
|
+
environments?: string[];
|
|
76
|
+
default?: boolean;
|
|
77
|
+
latest?: boolean;
|
|
78
|
+
message: {
|
|
79
|
+
collection: MessageCollection;
|
|
80
|
+
id: string;
|
|
81
|
+
name?: string;
|
|
82
|
+
version: string;
|
|
83
|
+
summary?: string;
|
|
84
|
+
owners?: string[];
|
|
85
|
+
};
|
|
86
|
+
source: {
|
|
87
|
+
provider: string;
|
|
88
|
+
id?: string;
|
|
89
|
+
path?: string;
|
|
90
|
+
url?: string;
|
|
91
|
+
ref?: string;
|
|
92
|
+
[key: string]: unknown;
|
|
93
|
+
};
|
|
94
|
+
readOnly?: boolean;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
type InternalMessageSchemaResource = MessageSchemaResource & {
|
|
98
|
+
_context?: {
|
|
99
|
+
messageFilePath?: string;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
type SchemaLoaderOptions = {
|
|
104
|
+
messages: {
|
|
105
|
+
pattern: string[];
|
|
106
|
+
base?: string;
|
|
107
|
+
};
|
|
108
|
+
sources?: SchemaSource[];
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
type LoaderContext = Parameters<Loader['load']>[0];
|
|
112
|
+
|
|
113
|
+
const MESSAGE_COLLECTIONS: MessageCollection[] = ['events', 'commands', 'queries'];
|
|
114
|
+
const FILE_SCHEMA_REF_PREFIX = 'file://';
|
|
115
|
+
|
|
116
|
+
const normalizePath = (value: string) => value.replace(/\\/g, '/');
|
|
117
|
+
|
|
118
|
+
const getMessageCollectionFromPath = (filePath: string): MessageCollection | undefined => {
|
|
119
|
+
const parts = normalizePath(filePath).split('/');
|
|
120
|
+
return MESSAGE_COLLECTIONS.find((collection) => parts.includes(collection));
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const getSchemaFormat = (schemaPath: string) => {
|
|
124
|
+
const extension = path.extname(schemaPath).replace('.', '').toLowerCase();
|
|
125
|
+
|
|
126
|
+
if (extension === 'avsc' || extension === 'avro') return 'avro';
|
|
127
|
+
if (extension === 'proto') return 'protobuf';
|
|
128
|
+
if (extension === 'json') return 'jsonschema';
|
|
129
|
+
if (extension === 'yaml' || extension === 'yml') return 'yaml';
|
|
130
|
+
|
|
131
|
+
return extension || 'unknown';
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const buildGeneratedSchemaId = (message: { collection: MessageCollection; id: string; version: string }, schemaPath: string) =>
|
|
135
|
+
`schema:${message.collection}:${message.id}:${message.version}:${schemaPath}`;
|
|
136
|
+
|
|
137
|
+
const getSchemaCollectionId = ({
|
|
138
|
+
message,
|
|
139
|
+
reference,
|
|
140
|
+
schemaRef,
|
|
141
|
+
schemaFile,
|
|
142
|
+
}: {
|
|
143
|
+
message: { collection: MessageCollection; id: string; version: string };
|
|
144
|
+
reference: SchemaReference;
|
|
145
|
+
schemaRef?: string;
|
|
146
|
+
schemaFile?: string;
|
|
147
|
+
}) => {
|
|
148
|
+
if (schemaRef) return buildGeneratedSchemaId(message, schemaRef);
|
|
149
|
+
if (reference.id) return buildGeneratedSchemaId(message, reference.id);
|
|
150
|
+
return buildGeneratedSchemaId(message, schemaFile as string);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const getSchemaFile = (reference: SchemaReference) => reference.file ?? reference.path;
|
|
154
|
+
|
|
155
|
+
const getSchemaRef = (reference: SchemaReference) => {
|
|
156
|
+
if (reference.ref) return reference.ref;
|
|
157
|
+
if (!getSchemaFile(reference)) return reference.id;
|
|
158
|
+
return undefined;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const getSchemaDisplayName = ({
|
|
162
|
+
referenceName,
|
|
163
|
+
resolvedName,
|
|
164
|
+
schemaFile,
|
|
165
|
+
filePath,
|
|
166
|
+
schemaRef,
|
|
167
|
+
sourcePath,
|
|
168
|
+
schemaId,
|
|
169
|
+
messageName,
|
|
170
|
+
}: {
|
|
171
|
+
referenceName?: string;
|
|
172
|
+
resolvedName?: string;
|
|
173
|
+
schemaFile?: string;
|
|
174
|
+
filePath?: string;
|
|
175
|
+
schemaRef?: string;
|
|
176
|
+
sourcePath?: string;
|
|
177
|
+
schemaId: string;
|
|
178
|
+
messageName?: string;
|
|
179
|
+
}) => {
|
|
180
|
+
const pathLikeName = schemaFile ?? filePath ?? sourcePath ?? schemaRef ?? schemaId;
|
|
181
|
+
return referenceName ?? resolvedName ?? path.basename(pathLikeName) ?? messageName ?? 'Schema';
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const schemaFileExists = (schema: MessageSchemaResource): schema is MessageSchemaResource & { filePath: string } =>
|
|
185
|
+
Boolean(schema.filePath && fsSync.existsSync(schema.filePath));
|
|
186
|
+
|
|
187
|
+
const getSchemaProvider = (id: string) => {
|
|
188
|
+
try {
|
|
189
|
+
const url = new URL(id);
|
|
190
|
+
return url.protocol.replace(':', '') || 'external';
|
|
191
|
+
} catch {
|
|
192
|
+
return 'external';
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const isFileSchemaRef = (ref?: string) => ref?.startsWith(FILE_SCHEMA_REF_PREFIX);
|
|
197
|
+
|
|
198
|
+
const getFileSchemaRefPath = (ref: string, messageFilePath: string) => {
|
|
199
|
+
if (ref.startsWith('file:///') || ref.startsWith('file://localhost/')) {
|
|
200
|
+
try {
|
|
201
|
+
const filePath = fileURLToPath(ref);
|
|
202
|
+
return {
|
|
203
|
+
filePath,
|
|
204
|
+
sourcePath: filePath,
|
|
205
|
+
};
|
|
206
|
+
} catch {
|
|
207
|
+
throw new Error(`Invalid file schema ref "${ref}". Expected file://<relative-path> or file:///absolute/path.`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const sourcePath = decodeURIComponent(ref.slice(FILE_SCHEMA_REF_PREFIX.length));
|
|
212
|
+
if (!sourcePath) {
|
|
213
|
+
throw new Error(`Invalid file schema ref "${ref}". Expected file://<relative-path> or file:///absolute/path.`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
filePath: path.resolve(path.dirname(messageFilePath), sourcePath),
|
|
218
|
+
sourcePath,
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const getTimestamp = () => {
|
|
223
|
+
const now = new Date();
|
|
224
|
+
return now.toLocaleTimeString('en-US', { hour12: false });
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const logSchemaInfo = (message: string) => {
|
|
228
|
+
console.log(`${colors.dim(getTimestamp())} ${colors.blue('[schemas]')} ${message}`);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const getMessageTypeLabel = (collection: MessageCollection) => {
|
|
232
|
+
if (collection === 'events') return 'event';
|
|
233
|
+
if (collection === 'commands') return 'command';
|
|
234
|
+
return 'query';
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const getErrorMessage = (error: unknown) => {
|
|
238
|
+
return error instanceof Error ? error.message : String(error);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const getSchemaResolveRef = (schema: MessageSchemaResource) => schema.ref ?? schema.id;
|
|
242
|
+
|
|
243
|
+
const buildSchemaSourceErrorMessage = ({
|
|
244
|
+
schema,
|
|
245
|
+
source,
|
|
246
|
+
error,
|
|
247
|
+
}: {
|
|
248
|
+
schema: MessageSchemaResource;
|
|
249
|
+
source: SchemaSource;
|
|
250
|
+
error: unknown;
|
|
251
|
+
}) => {
|
|
252
|
+
const messageType = getMessageTypeLabel(schema.message.collection);
|
|
253
|
+
|
|
254
|
+
return [
|
|
255
|
+
'',
|
|
256
|
+
colors.red(colors.bold('[schemas] Failed to resolve schema')),
|
|
257
|
+
'',
|
|
258
|
+
` Message: ${messageType} "${schema.message.id}" version "${schema.message.version}"`,
|
|
259
|
+
` Schema: ${getSchemaResolveRef(schema)}`,
|
|
260
|
+
` Source: ${source.name}`,
|
|
261
|
+
'',
|
|
262
|
+
colors.bold('Reason:'),
|
|
263
|
+
` ${getErrorMessage(error)}`,
|
|
264
|
+
'',
|
|
265
|
+
].join('\n');
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const addLatestMetadata = (schemas: MessageSchemaResource[]) => {
|
|
269
|
+
const schemasByMessage = schemas.reduce(
|
|
270
|
+
(acc, schema) => {
|
|
271
|
+
const key = `${schema.message.collection}:${schema.message.id}`;
|
|
272
|
+
acc[key] = [...(acc[key] ?? []), schema];
|
|
273
|
+
return acc;
|
|
274
|
+
},
|
|
275
|
+
{} as Record<string, MessageSchemaResource[]>
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const latestVersionsByMessage = Object.entries(schemasByMessage).reduce(
|
|
279
|
+
(acc, [key, messageSchemas]) => {
|
|
280
|
+
acc[key] = sortVersioned(messageSchemas, (schema) => schema.message.version)[0]?.message.version;
|
|
281
|
+
return acc;
|
|
282
|
+
},
|
|
283
|
+
{} as Record<string, string | undefined>
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
return schemas.map((schema) => {
|
|
287
|
+
const key = `${schema.message.collection}:${schema.message.id}`;
|
|
288
|
+
return {
|
|
289
|
+
...schema,
|
|
290
|
+
latest: schema.message.version === latestVersionsByMessage[key],
|
|
291
|
+
};
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export const getMessageSchemasFromFrontmatter = ({
|
|
296
|
+
data,
|
|
297
|
+
collection,
|
|
298
|
+
messageFilePath,
|
|
299
|
+
}: {
|
|
300
|
+
data: MessageFrontmatter;
|
|
301
|
+
collection: MessageCollection;
|
|
302
|
+
messageFilePath: string;
|
|
303
|
+
}): MessageSchemaResource[] => {
|
|
304
|
+
if (!data.id || !data.version) return [];
|
|
305
|
+
|
|
306
|
+
const message = {
|
|
307
|
+
collection,
|
|
308
|
+
id: data.id,
|
|
309
|
+
name: data.name,
|
|
310
|
+
version: data.version,
|
|
311
|
+
summary: data.summary,
|
|
312
|
+
owners: data.owners,
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const schemaReferences =
|
|
316
|
+
data.schemas?.filter((schema) => getSchemaRef(schema) || getSchemaFile(schema)) ??
|
|
317
|
+
(data.schemaPath
|
|
318
|
+
? [
|
|
319
|
+
{
|
|
320
|
+
file: data.schemaPath,
|
|
321
|
+
name: 'Schema',
|
|
322
|
+
default: true,
|
|
323
|
+
},
|
|
324
|
+
]
|
|
325
|
+
: []);
|
|
326
|
+
|
|
327
|
+
return schemaReferences.map((reference) => {
|
|
328
|
+
const schemaFile = getSchemaFile(reference);
|
|
329
|
+
const schemaRef = getSchemaRef(reference);
|
|
330
|
+
const fileSchemaRef = isFileSchemaRef(schemaRef) ? getFileSchemaRefPath(schemaRef as string, messageFilePath) : undefined;
|
|
331
|
+
const schemaFilePath = schemaFile ? path.resolve(path.dirname(messageFilePath), schemaFile) : fileSchemaRef?.filePath;
|
|
332
|
+
const schemaId = getSchemaCollectionId({ message, reference, schemaRef, schemaFile });
|
|
333
|
+
const schemaPathForFormat = schemaFile ?? fileSchemaRef?.filePath;
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
id: schemaId,
|
|
337
|
+
...(reference.id && !schemaRef ? { schemaId: reference.id } : {}),
|
|
338
|
+
...(schemaRef ? { ref: schemaRef } : {}),
|
|
339
|
+
name: getSchemaDisplayName({
|
|
340
|
+
referenceName: reference.name,
|
|
341
|
+
schemaFile,
|
|
342
|
+
filePath: fileSchemaRef?.filePath,
|
|
343
|
+
schemaRef,
|
|
344
|
+
schemaId,
|
|
345
|
+
messageName: data.name,
|
|
346
|
+
}),
|
|
347
|
+
version: data.version,
|
|
348
|
+
format: reference.format ?? (schemaPathForFormat ? getSchemaFormat(schemaPathForFormat) : 'unknown'),
|
|
349
|
+
file: schemaFile,
|
|
350
|
+
filePath: schemaFilePath,
|
|
351
|
+
environments: reference.environments,
|
|
352
|
+
default: reference.default,
|
|
353
|
+
message,
|
|
354
|
+
source: {
|
|
355
|
+
provider: schemaFile || fileSchemaRef ? 'file' : getSchemaProvider(schemaRef ?? schemaId),
|
|
356
|
+
path: schemaFile ?? fileSchemaRef?.sourcePath,
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
});
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const resolveSchemaSource = async (
|
|
363
|
+
schema: InternalMessageSchemaResource,
|
|
364
|
+
source: SchemaSource
|
|
365
|
+
): Promise<InternalMessageSchemaResource | undefined> => {
|
|
366
|
+
if (schemaFileExists(schema)) return schema;
|
|
367
|
+
if (schema.filePath) return undefined;
|
|
368
|
+
|
|
369
|
+
let resolvedSchema: Awaited<ReturnType<SchemaSource['resolve']>>;
|
|
370
|
+
const schemaResolveRef = getSchemaResolveRef(schema);
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
resolvedSchema = await source.resolve(schemaResolveRef, schema._context);
|
|
374
|
+
} catch (error) {
|
|
375
|
+
throw new Error(buildSchemaSourceErrorMessage({ schema, source, error }));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (!resolvedSchema) return undefined;
|
|
379
|
+
|
|
380
|
+
const resolvedMessageSchema: InternalMessageSchemaResource = {
|
|
381
|
+
...schema,
|
|
382
|
+
format: schema.format !== 'unknown' ? schema.format : (resolvedSchema.format ?? 'unknown'),
|
|
383
|
+
content: resolvedSchema.content,
|
|
384
|
+
source: resolvedSchema.source,
|
|
385
|
+
readOnly: true,
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
resolvedMessageSchema.name = getSchemaDisplayName({
|
|
389
|
+
referenceName: schema.name,
|
|
390
|
+
resolvedName: resolvedSchema.name,
|
|
391
|
+
schemaRef: schema.ref,
|
|
392
|
+
sourcePath: resolvedSchema.source.path,
|
|
393
|
+
schemaId: schema.id,
|
|
394
|
+
messageName: schema.message.name,
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
return resolvedMessageSchema;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const resolveSchemaSources = async (schemas: InternalMessageSchemaResource[], sources: SchemaSource[] = []) => {
|
|
401
|
+
if (sources.length > 0 && !isEventCatalogScaleEnabled()) {
|
|
402
|
+
throw new Error('Schema sources require EventCatalog Scale.');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const localSchemas = schemas.filter(schemaFileExists);
|
|
406
|
+
const externalSchemas = schemas.filter((schema) => !schema.filePath);
|
|
407
|
+
const resolvedExternalSchemas: InternalMessageSchemaResource[] = [];
|
|
408
|
+
const resolvedExternalSchemaIds = new Set<string>();
|
|
409
|
+
|
|
410
|
+
for (const source of sources) {
|
|
411
|
+
const sourceSchemas = externalSchemas.filter((schema) => source.canResolve(getSchemaResolveRef(schema)));
|
|
412
|
+
if (sourceSchemas.length === 0) continue;
|
|
413
|
+
|
|
414
|
+
logSchemaInfo(
|
|
415
|
+
`Loading ${sourceSchemas.length} schema${sourceSchemas.length === 1 ? '' : 's'} from schema source "${source.name}"`
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
const resolvedSchemas = await Promise.all(sourceSchemas.map((schema) => resolveSchemaSource(schema, source)));
|
|
419
|
+
const syncedSchemas = resolvedSchemas.filter((schema): schema is InternalMessageSchemaResource => schema !== undefined);
|
|
420
|
+
|
|
421
|
+
for (const schema of syncedSchemas) {
|
|
422
|
+
resolvedExternalSchemaIds.add(schema.id);
|
|
423
|
+
resolvedExternalSchemas.push(schema);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const skippedSchemas = sourceSchemas.length - syncedSchemas.length;
|
|
427
|
+
logSchemaInfo(
|
|
428
|
+
`Synced ${syncedSchemas.length} schema${syncedSchemas.length === 1 ? '' : 's'} from schema source "${source.name}"${
|
|
429
|
+
skippedSchemas > 0 ? ` (${skippedSchemas} skipped)` : ''
|
|
430
|
+
}`
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return [
|
|
435
|
+
...localSchemas,
|
|
436
|
+
...resolvedExternalSchemas,
|
|
437
|
+
...schemas.filter((schema) => schema.filePath && !schemaFileExists(schema)),
|
|
438
|
+
].filter((schema) => {
|
|
439
|
+
if (schema.filePath) return schemaFileExists(schema);
|
|
440
|
+
return resolvedExternalSchemaIds.has(schema.id);
|
|
441
|
+
});
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const loadMessageSchemaResources = async ({ pattern, base }: SchemaLoaderOptions['messages']) => {
|
|
445
|
+
if (!base) return [];
|
|
446
|
+
|
|
447
|
+
const files = await glob(pattern, {
|
|
448
|
+
cwd: base,
|
|
449
|
+
absolute: true,
|
|
450
|
+
nodir: true,
|
|
451
|
+
ignore: ['dist/**', '**/dist/**'],
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const schemas = await Promise.all(
|
|
455
|
+
files.map(async (file) => {
|
|
456
|
+
const collection = getMessageCollectionFromPath(file);
|
|
457
|
+
if (!collection) return [];
|
|
458
|
+
|
|
459
|
+
const { data } = matter.read(file) as { data: MessageFrontmatter };
|
|
460
|
+
return getMessageSchemasFromFrontmatter({ data, collection, messageFilePath: file }).map((schema) => ({
|
|
461
|
+
...schema,
|
|
462
|
+
_context: {
|
|
463
|
+
messageFilePath: file,
|
|
464
|
+
},
|
|
465
|
+
}));
|
|
466
|
+
})
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
return schemas.flat();
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const stripSchemaLoaderContext = (schema: InternalMessageSchemaResource): MessageSchemaResource => {
|
|
473
|
+
const { _context, ...publicSchema } = schema;
|
|
474
|
+
return publicSchema;
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
export const loadMessageSchemas = async (messages: SchemaLoaderOptions['messages'], sources: SchemaSource[] = []) => {
|
|
478
|
+
const schemas = await loadMessageSchemaResources(messages);
|
|
479
|
+
const resolvedSchemas = await resolveSchemaSources(schemas, sources);
|
|
480
|
+
|
|
481
|
+
return addLatestMetadata(resolvedSchemas.map(stripSchemaLoaderContext));
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const getSchemaBody = async (schema: MessageSchemaResource) => {
|
|
485
|
+
if (schema.content !== undefined) return schema.content;
|
|
486
|
+
if (!schemaFileExists(schema)) return undefined;
|
|
487
|
+
return fs.readFile(schema.filePath, 'utf8');
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const setSchema = async (context: LoaderContext, schema: MessageSchemaResource) => {
|
|
491
|
+
const body = await getSchemaBody(schema);
|
|
492
|
+
if (body === undefined) return;
|
|
493
|
+
|
|
494
|
+
const schemaWithContent = {
|
|
495
|
+
...schema,
|
|
496
|
+
content: body,
|
|
497
|
+
};
|
|
498
|
+
const parsedData = await context.parseData({
|
|
499
|
+
id: schema.id,
|
|
500
|
+
data: schemaWithContent,
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
context.store.set({
|
|
504
|
+
id: schema.id,
|
|
505
|
+
data: parsedData,
|
|
506
|
+
body,
|
|
507
|
+
digest: context.generateDigest(schemaWithContent),
|
|
508
|
+
});
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
export const schemaLoader = ({ messages, sources = [] }: SchemaLoaderOptions): Loader => {
|
|
512
|
+
return {
|
|
513
|
+
name: 'eventcatalog-schema-loader',
|
|
514
|
+
load: async (context) => {
|
|
515
|
+
context.store.clear();
|
|
516
|
+
const schemas = await loadMessageSchemas(messages, sources);
|
|
517
|
+
|
|
518
|
+
for (const schema of schemas) {
|
|
519
|
+
await setSchema(context, schema);
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
};
|
|
523
|
+
};
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"license": "SEE LICENSE IN LICENSE",
|
|
9
9
|
"type": "module",
|
|
10
|
-
"version": "3.
|
|
10
|
+
"version": "3.46.0",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"elkjs": "^0.10.0",
|
|
75
75
|
"glob": "^13.0.6",
|
|
76
76
|
"gray-matter": "^4.0.3",
|
|
77
|
-
"hono": "4.12.
|
|
77
|
+
"hono": "4.12.21",
|
|
78
78
|
"html-to-image": "^1.11.11",
|
|
79
79
|
"js-yaml": "^4.1.1",
|
|
80
80
|
"jsonpath-plus": "^10.4.0",
|
|
@@ -112,9 +112,9 @@
|
|
|
112
112
|
"update-notifier": "^7.3.1",
|
|
113
113
|
"uuid": "^10.0.0",
|
|
114
114
|
"zod": "^4.3.6",
|
|
115
|
+
"@eventcatalog/linter": "1.0.29",
|
|
115
116
|
"@eventcatalog/sdk": "2.24.1",
|
|
116
|
-
"@eventcatalog/visualiser": "^3.22.1"
|
|
117
|
-
"@eventcatalog/linter": "1.0.29"
|
|
117
|
+
"@eventcatalog/visualiser": "^3.22.1"
|
|
118
118
|
},
|
|
119
119
|
"devDependencies": {
|
|
120
120
|
"@astrojs/check": "^0.9.9",
|