@eventcatalog/core 3.14.6 → 3.15.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) 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-6NRRWRLT.js → chunk-4JTLFCQ7.js} +1 -1
  6. package/dist/{chunk-TERDXO46.js → chunk-7RPTQBIY.js} +1 -1
  7. package/dist/{chunk-LNIPDPVB.js → chunk-CVH4LGYA.js} +1 -1
  8. package/dist/{chunk-NYVQSLA5.js → chunk-FRILQLHV.js} +1 -1
  9. package/dist/{chunk-KWCAGR52.js → chunk-VUARMJ2E.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/SideNav/NestedSideBar/index.tsx +56 -10
  19. package/eventcatalog/src/content.config.ts +53 -0
  20. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/_index.data.ts +85 -0
  21. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +195 -0
  22. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/_index.data.ts +86 -0
  23. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +195 -0
  24. package/eventcatalog/src/stores/sidebar-store/builders/container.ts +9 -0
  25. package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +15 -9
  26. package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +9 -0
  27. package/eventcatalog/src/stores/sidebar-store/builders/flow.ts +13 -4
  28. package/eventcatalog/src/stores/sidebar-store/builders/message.ts +9 -0
  29. package/eventcatalog/src/stores/sidebar-store/builders/service.ts +9 -0
  30. package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +82 -0
  31. package/eventcatalog/src/stores/sidebar-store/state.ts +27 -2
  32. package/eventcatalog/src/utils/collections/data-products.ts +6 -2
  33. package/eventcatalog/src/utils/collections/resource-docs.ts +601 -0
  34. package/eventcatalog/src/utils/feature.ts +1 -0
  35. package/package.json +3 -3
@@ -8,6 +8,8 @@ interface Props {
8
8
  getAllVersions?: boolean;
9
9
  }
10
10
 
11
+ const CACHE_ENABLED = process.env.DISABLE_EVENTCATALOG_CACHE !== 'true';
12
+
11
13
  // cache for build time
12
14
  let memoryCache: Record<string, DataProduct[]> = {};
13
15
 
@@ -15,7 +17,7 @@ export const getDataProducts = async ({ getAllVersions = true }: Props = {}): Pr
15
17
  // console.time('✅ New getEntities');
16
18
  const cacheKey = getAllVersions ? 'allVersions' : 'currentVersions';
17
19
 
18
- if (memoryCache[cacheKey] && memoryCache[cacheKey].length > 0) {
20
+ if (CACHE_ENABLED && memoryCache[cacheKey] && memoryCache[cacheKey].length > 0) {
19
21
  // console.timeEnd('✅ New getEntities');
20
22
  return memoryCache[cacheKey];
21
23
  }
@@ -60,7 +62,9 @@ export const getDataProducts = async ({ getAllVersions = true }: Props = {}): Pr
60
62
  return (a.data.name || a.data.id).localeCompare(b.data.name || b.data.id);
61
63
  });
62
64
 
63
- memoryCache[cacheKey] = processedDataProducts;
65
+ if (CACHE_ENABLED) {
66
+ memoryCache[cacheKey] = processedDataProducts;
67
+ }
64
68
  // console.timeEnd('✅ New getDataProducts');
65
69
 
66
70
  return processedDataProducts as DataProduct[];
@@ -0,0 +1,601 @@
1
+ import { getCollection, type CollectionEntry } from 'astro:content';
2
+ import { sortVersioned } from './util';
3
+ import { isResourceDocsEnabled } from '@utils/feature';
4
+
5
+ const CACHE_ENABLED = process.env.DISABLE_EVENTCATALOG_CACHE !== 'true';
6
+
7
+ export type ResourceCollection =
8
+ | 'domains'
9
+ | 'services'
10
+ | 'events'
11
+ | 'commands'
12
+ | 'queries'
13
+ | 'flows'
14
+ | 'containers'
15
+ | 'channels'
16
+ | 'entities'
17
+ | 'data-products';
18
+
19
+ type BaseResourceDocEntry = CollectionEntry<'resourceDocs'>;
20
+ type BaseResourceDocCategoryEntry = CollectionEntry<'resourceDocCategories'>;
21
+
22
+ export type ResourceDocEntry = Omit<BaseResourceDocEntry, 'data'> & {
23
+ data: BaseResourceDocEntry['data'] & {
24
+ id: string;
25
+ type: string;
26
+ version: string;
27
+ order?: number;
28
+ resourceCollection: ResourceCollection;
29
+ resourceId: string;
30
+ resourceVersion: string;
31
+ versions: string[];
32
+ latestVersion: string;
33
+ };
34
+ };
35
+
36
+ export type ResourceDocCategoryEntry = Omit<BaseResourceDocCategoryEntry, 'data'> & {
37
+ data: BaseResourceDocCategoryEntry['data'] & {
38
+ type: string;
39
+ resourceCollection: ResourceCollection;
40
+ resourceId: string;
41
+ resourceVersion: string;
42
+ };
43
+ };
44
+
45
+ export type ResourceDocGroup = {
46
+ type: string;
47
+ docs: ResourceDocEntry[];
48
+ label?: string;
49
+ position?: number;
50
+ };
51
+
52
+ type ResourceLookup = {
53
+ latestById: Map<string, string>;
54
+ versionsById: Map<string, Set<string>>;
55
+ };
56
+
57
+ type InferredResource = {
58
+ resourceCollection: ResourceCollection;
59
+ resourceId: string;
60
+ resourceVersion: string;
61
+ };
62
+
63
+ let memoryCache: ResourceDocEntry[] | null = null;
64
+ let memoryCategoryCache: ResourceDocCategoryEntry[] | null = null;
65
+ let memoryResourceLookupCache: Record<ResourceCollection, ResourceLookup> | null = null;
66
+ let memoryResourceLookupPromise: Promise<Record<ResourceCollection, ResourceLookup>> | null = null;
67
+
68
+ const normalizePath = (value: string) => value.replace(/\\/g, '/').replace(/^\.\//, '');
69
+ const normalizeTypeName = (value: string) => value.trim().toLowerCase();
70
+
71
+ const inferOrderFromFilePath = (filePath: string): number | undefined => {
72
+ const normalizedPath = normalizePath(filePath);
73
+ const fileName = normalizedPath.split('/').pop();
74
+
75
+ if (!fileName) {
76
+ return undefined;
77
+ }
78
+
79
+ const fileNameWithoutExtension = fileName.replace(/\.(md|mdx)$/i, '');
80
+ const orderMatch = fileNameWithoutExtension.match(/^(\d+)(?:[-_.\s]|$)/);
81
+
82
+ if (!orderMatch) {
83
+ return undefined;
84
+ }
85
+
86
+ const parsedOrder = Number.parseInt(orderMatch[1], 10);
87
+ return Number.isFinite(parsedOrder) ? parsedOrder : undefined;
88
+ };
89
+
90
+ const inferDocIdFromFilePath = (filePath: string): string | undefined => {
91
+ const normalizedPath = normalizePath(filePath);
92
+ const fileName = normalizedPath.split('/').pop();
93
+
94
+ if (!fileName) {
95
+ return undefined;
96
+ }
97
+
98
+ const fileNameWithoutExtension = fileName.replace(/\.(md|mdx)$/i, '');
99
+ if (!fileNameWithoutExtension) {
100
+ return undefined;
101
+ }
102
+
103
+ // Support ordered filenames like "01-my-doc" while keeping stable ids.
104
+ const idWithoutNumericPrefix = fileNameWithoutExtension.replace(/^\d+(?:[-_.\s]+)?/, '');
105
+ return idWithoutNumericPrefix || fileNameWithoutExtension;
106
+ };
107
+
108
+ const inferDocTypeFromFilePath = (filePath: string): string | undefined => {
109
+ const normalizedPath = normalizePath(filePath);
110
+ const docsMarker = '/docs/';
111
+ const docsIndex = normalizedPath.indexOf(docsMarker);
112
+
113
+ if (docsIndex === -1) {
114
+ return undefined;
115
+ }
116
+
117
+ const docsRelativePath = normalizedPath.slice(docsIndex + docsMarker.length);
118
+ const segments = docsRelativePath.split('/').filter(Boolean);
119
+ const firstSegment = segments[0];
120
+
121
+ if (!firstSegment || firstSegment === 'versioned') {
122
+ return undefined;
123
+ }
124
+
125
+ if (/\.[a-z0-9]+$/i.test(firstSegment)) {
126
+ return undefined;
127
+ }
128
+
129
+ return firstSegment;
130
+ };
131
+
132
+ const getCategoryFilePriority = (filePath: string): number => {
133
+ const fileName = normalizePath(filePath).split('/').pop()?.toLowerCase();
134
+ if (fileName === 'category.json') {
135
+ return 2;
136
+ }
137
+ if (fileName === '_category_.json') {
138
+ return 1;
139
+ }
140
+ return 0;
141
+ };
142
+
143
+ const inferResourceFromFilePath = (filePath: string): InferredResource | null => {
144
+ const normalizedPath = normalizePath(filePath);
145
+ const docsIndex = normalizedPath.indexOf('/docs/');
146
+
147
+ if (docsIndex === -1) {
148
+ return null;
149
+ }
150
+
151
+ const resourcePath = normalizedPath.slice(0, docsIndex);
152
+ const segments = resourcePath.split('/').filter(Boolean);
153
+
154
+ const parseVersion = (index: number) => {
155
+ if (segments[index + 2] === 'versioned' && segments[index + 3]) {
156
+ return segments[index + 3];
157
+ }
158
+ return 'latest';
159
+ };
160
+
161
+ const segmentMappings: Array<{ segment: string; collection: ResourceCollection }> = [
162
+ { segment: 'events', collection: 'events' },
163
+ { segment: 'commands', collection: 'commands' },
164
+ { segment: 'queries', collection: 'queries' },
165
+ { segment: 'services', collection: 'services' },
166
+ { segment: 'domains', collection: 'domains' },
167
+ { segment: 'subdomains', collection: 'domains' },
168
+ { segment: 'flows', collection: 'flows' },
169
+ { segment: 'containers', collection: 'containers' },
170
+ { segment: 'channels', collection: 'channels' },
171
+ { segment: 'entities', collection: 'entities' },
172
+ { segment: 'data-products', collection: 'data-products' },
173
+ ];
174
+
175
+ let matched: { index: number; collection: ResourceCollection } | null = null;
176
+
177
+ for (let index = 0; index < segments.length; index++) {
178
+ const match = segmentMappings.find((mapping) => mapping.segment === segments[index]);
179
+ if (!match) {
180
+ continue;
181
+ }
182
+
183
+ if (!segments[index + 1]) {
184
+ continue;
185
+ }
186
+
187
+ if (!matched || index > matched.index) {
188
+ matched = { index, collection: match.collection };
189
+ }
190
+ }
191
+
192
+ if (!matched) {
193
+ return null;
194
+ }
195
+
196
+ return {
197
+ resourceCollection: matched.collection,
198
+ resourceId: segments[matched.index + 1],
199
+ resourceVersion: parseVersion(matched.index),
200
+ };
201
+ };
202
+
203
+ const buildLookup = (
204
+ resources: Array<{
205
+ data: { id: string; version: string; hidden?: boolean };
206
+ }>
207
+ ): ResourceLookup => {
208
+ const groupedById = new Map<string, string[]>();
209
+
210
+ for (const resource of resources) {
211
+ if (resource.data.hidden === true) {
212
+ continue;
213
+ }
214
+ const list = groupedById.get(resource.data.id) || [];
215
+ list.push(resource.data.version);
216
+ groupedById.set(resource.data.id, list);
217
+ }
218
+
219
+ const latestById = new Map<string, string>();
220
+ const versionsById = new Map<string, Set<string>>();
221
+
222
+ for (const [id, versions] of groupedById.entries()) {
223
+ const sorted = sortVersioned([...new Set(versions)], (version) => version);
224
+ if (sorted.length > 0) {
225
+ latestById.set(id, sorted[0]);
226
+ versionsById.set(id, new Set(sorted));
227
+ }
228
+ }
229
+
230
+ return { latestById, versionsById };
231
+ };
232
+
233
+ const getResourceLookups = async (): Promise<Record<ResourceCollection, ResourceLookup>> => {
234
+ if (CACHE_ENABLED && memoryResourceLookupCache) {
235
+ return memoryResourceLookupCache;
236
+ }
237
+
238
+ if (CACHE_ENABLED && memoryResourceLookupPromise) {
239
+ return memoryResourceLookupPromise;
240
+ }
241
+
242
+ const lookupPromise = (async () => {
243
+ const [domains, services, events, commands, queries, flows, containers, channels, entities, dataProducts] = await Promise.all(
244
+ [
245
+ getCollection('domains'),
246
+ getCollection('services'),
247
+ getCollection('events'),
248
+ getCollection('commands'),
249
+ getCollection('queries'),
250
+ getCollection('flows'),
251
+ getCollection('containers'),
252
+ getCollection('channels'),
253
+ getCollection('entities'),
254
+ getCollection('data-products'),
255
+ ]
256
+ );
257
+
258
+ return {
259
+ domains: buildLookup(domains),
260
+ services: buildLookup(services),
261
+ events: buildLookup(events),
262
+ commands: buildLookup(commands),
263
+ queries: buildLookup(queries),
264
+ flows: buildLookup(flows),
265
+ containers: buildLookup(containers),
266
+ channels: buildLookup(channels),
267
+ entities: buildLookup(entities),
268
+ 'data-products': buildLookup(dataProducts),
269
+ };
270
+ })();
271
+
272
+ if (CACHE_ENABLED) {
273
+ memoryResourceLookupPromise = lookupPromise;
274
+ }
275
+
276
+ try {
277
+ const lookups = await lookupPromise;
278
+ if (CACHE_ENABLED) {
279
+ memoryResourceLookupCache = lookups;
280
+ }
281
+ return lookups;
282
+ } finally {
283
+ if (CACHE_ENABLED) {
284
+ memoryResourceLookupPromise = null;
285
+ }
286
+ }
287
+ };
288
+
289
+ const resolveResourceFromPath = (
290
+ filePath: string,
291
+ lookups: Record<ResourceCollection, ResourceLookup>
292
+ ): InferredResource | null => {
293
+ const inferredResource = inferResourceFromFilePath(filePath);
294
+ if (!inferredResource) {
295
+ return null;
296
+ }
297
+
298
+ const { resourceCollection, resourceId, resourceVersion } = inferredResource;
299
+ const lookup = lookups[resourceCollection];
300
+ const knownVersions = lookup.versionsById.get(resourceId);
301
+
302
+ if (!knownVersions || knownVersions.size === 0) {
303
+ return null;
304
+ }
305
+
306
+ const resolvedResourceVersion = resourceVersion === 'latest' ? (lookup.latestById.get(resourceId) ?? null) : resourceVersion;
307
+
308
+ if (!resolvedResourceVersion || !knownVersions.has(resolvedResourceVersion)) {
309
+ return null;
310
+ }
311
+
312
+ return {
313
+ resourceCollection,
314
+ resourceId,
315
+ resourceVersion: resolvedResourceVersion,
316
+ };
317
+ };
318
+
319
+ export const getResourceDocs = async (): Promise<ResourceDocEntry[]> => {
320
+ if (!isResourceDocsEnabled()) {
321
+ return [];
322
+ }
323
+
324
+ if (memoryCache && CACHE_ENABLED) {
325
+ return memoryCache;
326
+ }
327
+
328
+ const [docs, lookups] = await Promise.all([getCollection('resourceDocs'), getResourceLookups()]);
329
+
330
+ const docsWithResources = docs
331
+ .filter((doc) => doc.data.hidden !== true)
332
+ .map((doc) => {
333
+ if (!doc.filePath) {
334
+ return null;
335
+ }
336
+
337
+ const resolvedResource = resolveResourceFromPath(doc.filePath, lookups);
338
+ if (!resolvedResource) {
339
+ return null;
340
+ }
341
+
342
+ const inferredOrder = inferOrderFromFilePath(doc.filePath);
343
+ const resolvedOrder = typeof doc.data.order === 'number' ? doc.data.order : inferredOrder;
344
+ const inferredType = inferDocTypeFromFilePath(doc.filePath);
345
+ const resolvedType = doc.data.type || inferredType || 'pages';
346
+ const resolvedDocId = doc.data.id || inferDocIdFromFilePath(doc.filePath);
347
+ const resolvedDocVersion = doc.data.version;
348
+ const { resourceCollection, resourceId, resourceVersion } = resolvedResource;
349
+
350
+ if (!resolvedDocId || !resolvedDocVersion) {
351
+ return null;
352
+ }
353
+
354
+ return {
355
+ ...doc,
356
+ data: {
357
+ ...doc.data,
358
+ id: resolvedDocId,
359
+ type: resolvedType,
360
+ version: resolvedDocVersion,
361
+ order: resolvedOrder,
362
+ resourceCollection,
363
+ resourceId,
364
+ resourceVersion,
365
+ versions: [],
366
+ latestVersion: resolvedDocVersion,
367
+ },
368
+ } as ResourceDocEntry;
369
+ })
370
+ .filter((doc): doc is ResourceDocEntry => doc !== null);
371
+
372
+ const docsByResourceAndId = new Map<string, ResourceDocEntry[]>();
373
+
374
+ for (const doc of docsWithResources) {
375
+ const key = `${doc.data.resourceCollection}:${doc.data.resourceId}:${doc.data.resourceVersion}:${doc.data.type}:${doc.data.id}`;
376
+ const versions = docsByResourceAndId.get(key) || [];
377
+ versions.push(doc);
378
+ docsByResourceAndId.set(key, versions);
379
+ }
380
+
381
+ const enrichedDocs: ResourceDocEntry[] = [];
382
+
383
+ for (const docsForResource of docsByResourceAndId.values()) {
384
+ const sortedByVersion = sortVersioned(docsForResource, (doc) => doc.data.version);
385
+ const allVersions = sortedByVersion.map((doc) => doc.data.version);
386
+ const latestVersion = allVersions[0];
387
+
388
+ for (const doc of sortedByVersion) {
389
+ enrichedDocs.push({
390
+ ...doc,
391
+ data: {
392
+ ...doc.data,
393
+ versions: allVersions,
394
+ latestVersion,
395
+ },
396
+ });
397
+ }
398
+ }
399
+
400
+ enrichedDocs.sort((a, b) => {
401
+ const resourceKeyA = `${a.data.resourceCollection}:${a.data.resourceId}:${a.data.resourceVersion}`;
402
+ const resourceKeyB = `${b.data.resourceCollection}:${b.data.resourceId}:${b.data.resourceVersion}`;
403
+ if (resourceKeyA !== resourceKeyB) {
404
+ return resourceKeyA.localeCompare(resourceKeyB);
405
+ }
406
+
407
+ const typeCompare = a.data.type.localeCompare(b.data.type);
408
+ if (typeCompare !== 0) {
409
+ return typeCompare;
410
+ }
411
+
412
+ const titleA = (a.data.title || a.data.id).toLowerCase();
413
+ const titleB = (b.data.title || b.data.id).toLowerCase();
414
+ if (titleA !== titleB) {
415
+ return titleA.localeCompare(titleB);
416
+ }
417
+
418
+ return b.data.version.localeCompare(a.data.version);
419
+ });
420
+
421
+ memoryCache = enrichedDocs;
422
+ return enrichedDocs;
423
+ };
424
+
425
+ export const getResourceDocCategories = async (): Promise<ResourceDocCategoryEntry[]> => {
426
+ if (!isResourceDocsEnabled()) {
427
+ return [];
428
+ }
429
+
430
+ if (memoryCategoryCache && CACHE_ENABLED) {
431
+ return memoryCategoryCache;
432
+ }
433
+
434
+ const [categories, lookups] = await Promise.all([getCollection('resourceDocCategories'), getResourceLookups()]);
435
+ const categoriesByKey = new Map<string, ResourceDocCategoryEntry>();
436
+
437
+ for (const category of categories) {
438
+ if (!category.filePath) {
439
+ continue;
440
+ }
441
+
442
+ const resolvedResource = resolveResourceFromPath(category.filePath, lookups);
443
+ if (!resolvedResource) {
444
+ continue;
445
+ }
446
+
447
+ const categoryType = inferDocTypeFromFilePath(category.filePath) || 'pages';
448
+ const key = `${resolvedResource.resourceCollection}:${resolvedResource.resourceId}:${resolvedResource.resourceVersion}:${categoryType}`;
449
+ const existing = categoriesByKey.get(key);
450
+
451
+ const nextEntry: ResourceDocCategoryEntry = {
452
+ ...category,
453
+ data: {
454
+ ...category.data,
455
+ type: categoryType,
456
+ resourceCollection: resolvedResource.resourceCollection,
457
+ resourceId: resolvedResource.resourceId,
458
+ resourceVersion: resolvedResource.resourceVersion,
459
+ },
460
+ };
461
+
462
+ if (!existing) {
463
+ categoriesByKey.set(key, nextEntry);
464
+ continue;
465
+ }
466
+
467
+ const existingPriority = existing.filePath ? getCategoryFilePriority(existing.filePath) : 0;
468
+ const nextPriority = getCategoryFilePriority(category.filePath);
469
+
470
+ if (nextPriority >= existingPriority) {
471
+ if (
472
+ existing.filePath &&
473
+ category.filePath &&
474
+ getCategoryFilePriority(existing.filePath) !== getCategoryFilePriority(category.filePath)
475
+ ) {
476
+ // Prefer category.json over _category_.json when both exist in the same folder.
477
+ console.warn(
478
+ `[resource-docs] Both category.json and _category_.json found for ${key}. Using ${
479
+ nextPriority > existingPriority ? category.filePath : existing.filePath
480
+ }.`
481
+ );
482
+ }
483
+ categoriesByKey.set(key, nextEntry);
484
+ }
485
+ }
486
+
487
+ const resolvedCategories = [...categoriesByKey.values()].sort((a, b) => {
488
+ const resourceKeyA = `${a.data.resourceCollection}:${a.data.resourceId}:${a.data.resourceVersion}`;
489
+ const resourceKeyB = `${b.data.resourceCollection}:${b.data.resourceId}:${b.data.resourceVersion}`;
490
+ if (resourceKeyA !== resourceKeyB) {
491
+ return resourceKeyA.localeCompare(resourceKeyB);
492
+ }
493
+
494
+ const positionA = typeof a.data.position === 'number' ? a.data.position : Number.POSITIVE_INFINITY;
495
+ const positionB = typeof b.data.position === 'number' ? b.data.position : Number.POSITIVE_INFINITY;
496
+ if (positionA !== positionB) {
497
+ return positionA - positionB;
498
+ }
499
+
500
+ return a.data.type.localeCompare(b.data.type);
501
+ });
502
+
503
+ memoryCategoryCache = resolvedCategories;
504
+ return resolvedCategories;
505
+ };
506
+
507
+ export const getResourceDocCategoriesForResource = async (
508
+ resourceCollection: ResourceCollection,
509
+ resourceId: string,
510
+ resourceVersion: string
511
+ ): Promise<ResourceDocCategoryEntry[]> => {
512
+ const categories = await getResourceDocCategories();
513
+ return categories.filter(
514
+ (category) =>
515
+ category.data.resourceCollection === resourceCollection &&
516
+ category.data.resourceId === resourceId &&
517
+ category.data.resourceVersion === resourceVersion
518
+ );
519
+ };
520
+
521
+ export const getResourceDocsForResource = async (
522
+ resourceCollection: ResourceCollection,
523
+ resourceId: string,
524
+ resourceVersion: string
525
+ ): Promise<ResourceDocEntry[]> => {
526
+ const docs = await getResourceDocs();
527
+ return docs.filter(
528
+ (doc) =>
529
+ doc.data.resourceCollection === resourceCollection &&
530
+ doc.data.resourceId === resourceId &&
531
+ doc.data.resourceVersion === resourceVersion
532
+ );
533
+ };
534
+
535
+ export const getGroupedResourceDocsByType = (
536
+ docs: ResourceDocEntry[],
537
+ { latestOnly = true, categories = [] }: { latestOnly?: boolean; categories?: ResourceDocCategoryEntry[] } = {}
538
+ ): ResourceDocGroup[] => {
539
+ const docsByType = new Map<string, ResourceDocEntry[]>();
540
+ const categoriesByType = new Map(categories.map((category) => [normalizeTypeName(category.data.type), category]));
541
+
542
+ const findCategoryForType = (type: string): ResourceDocCategoryEntry | undefined => {
543
+ const normalizedType = normalizeTypeName(type);
544
+ const directMatch = categoriesByType.get(normalizedType);
545
+ if (directMatch) {
546
+ return directMatch;
547
+ }
548
+
549
+ if (normalizedType.endsWith('s')) {
550
+ return categoriesByType.get(normalizedType.slice(0, -1));
551
+ }
552
+
553
+ return categoriesByType.get(`${normalizedType}s`);
554
+ };
555
+
556
+ for (const doc of docs) {
557
+ if (latestOnly && doc.data.version !== doc.data.latestVersion) {
558
+ continue;
559
+ }
560
+ const list = docsByType.get(doc.data.type) || [];
561
+ list.push(doc);
562
+ docsByType.set(doc.data.type, list);
563
+ }
564
+
565
+ return [...docsByType.entries()]
566
+ .map(([type, typeDocs]) => {
567
+ const category = findCategoryForType(type);
568
+ return {
569
+ type,
570
+ label: category?.data.label,
571
+ position: category?.data.position,
572
+ docs: [...typeDocs].sort((a, b) => {
573
+ const orderA = typeof a.data.order === 'number' ? a.data.order : Number.POSITIVE_INFINITY;
574
+ const orderB = typeof b.data.order === 'number' ? b.data.order : Number.POSITIVE_INFINITY;
575
+
576
+ if (orderA !== orderB) {
577
+ return orderA - orderB;
578
+ }
579
+
580
+ const titleA = (a.data.title || a.data.id).toLowerCase();
581
+ const titleB = (b.data.title || b.data.id).toLowerCase();
582
+ const titleCompare = titleA.localeCompare(titleB);
583
+ if (titleCompare !== 0) {
584
+ return titleCompare;
585
+ }
586
+
587
+ return b.data.version.localeCompare(a.data.version);
588
+ }),
589
+ };
590
+ })
591
+ .sort((a, b) => {
592
+ const positionA = typeof a.position === 'number' ? a.position : Number.POSITIVE_INFINITY;
593
+ const positionB = typeof b.position === 'number' ? b.position : Number.POSITIVE_INFINITY;
594
+
595
+ if (positionA !== positionB) {
596
+ return positionA - positionB;
597
+ }
598
+
599
+ return a.type.localeCompare(b.type);
600
+ });
601
+ };
@@ -39,6 +39,7 @@ export const showCustomBranding = () => {
39
39
  export const isChangelogEnabled = () => config?.changelog?.enabled ?? false;
40
40
 
41
41
  export const isCustomDocsEnabled = () => isEventCatalogStarterEnabled() || isEventCatalogScaleEnabled();
42
+ export const isResourceDocsEnabled = () => isEventCatalogScaleEnabled();
42
43
 
43
44
  export const isEventCatalogChatEnabled = () => {
44
45
  const isFeatureEnabledFromPlan = isEventCatalogStarterEnabled() || isEventCatalogScaleEnabled();
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/event-catalog/eventcatalog.git"
7
7
  },
8
8
  "type": "module",
9
- "version": "3.14.6",
9
+ "version": "3.15.0-beta.0",
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
@@ -101,9 +101,9 @@
101
101
  "update-notifier": "^7.3.1",
102
102
  "uuid": "^10.0.0",
103
103
  "zod": "^3.25.0",
104
- "@eventcatalog/linter": "1.0.5",
105
104
  "@eventcatalog/sdk": "2.14.1",
106
- "@eventcatalog/visualiser": "^3.14.0"
105
+ "@eventcatalog/visualiser": "^3.14.0",
106
+ "@eventcatalog/linter": "1.0.5"
107
107
  },
108
108
  "devDependencies": {
109
109
  "@astrojs/check": "^0.9.6",