@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.
- 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-3EEFSHHW.js → chunk-3XTFNVGA.js} +1 -1
- package/dist/{chunk-ONRJA5DX.js → chunk-N6U5CNT7.js} +1 -1
- package/dist/{chunk-BYPWHDX6.js → chunk-RLGMIZSH.js} +1 -1
- package/dist/{chunk-5WB6CXZH.js → chunk-TWZKZIRW.js} +1 -1
- package/dist/{chunk-EG62NTWS.js → chunk-U52FCZQ6.js} +7 -7
- package/dist/{chunk-WD5UW4A4.js → chunk-VFRR3M72.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +13 -13
- package/dist/federation/federate.js +5 -5
- package/dist/federation/source-provider.js +2 -2
- 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/src/components/Grids/DomainGrid.tsx +20 -12
- package/eventcatalog/src/components/Grids/message-link.spec.ts +59 -0
- package/eventcatalog/src/components/Grids/message-link.ts +38 -0
- package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +58 -12
- package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +5 -5
- package/eventcatalog/src/components/SchemaExplorer/SchemaListItem.tsx +2 -0
- package/eventcatalog/src/components/SchemaExplorer/types.ts +10 -1
- package/eventcatalog/src/components/SchemaExplorer/useSchemaDetails.ts +69 -0
- package/eventcatalog/src/components/SchemaExplorer/utils.ts +12 -0
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.spec.ts +88 -0
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +19 -8
- package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +3 -269
- package/eventcatalog/src/pages/schemas/explorer/content/[key].json.ts +17 -0
- package/eventcatalog/src/utils/collections/domains.ts +1 -80
- package/eventcatalog/src/utils/collections/hydrate-services.ts +51 -0
- package/eventcatalog/src/utils/collections/systems.ts +17 -7
- package/eventcatalog/src/utils/schema-explorer.ts +276 -0
- package/package.json +3 -3
- package/dist/{chunk-A2RZR3U4.js → chunk-6K7XZAYI.js} +3 -3
|
@@ -20,7 +20,7 @@ export interface Owner {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export interface SchemaItem {
|
|
23
|
-
collection: CollectionMessageTypes | 'services' | 'data-products';
|
|
23
|
+
collection: CollectionMessageTypes | 'services' | 'domains' | 'data-products';
|
|
24
24
|
data: {
|
|
25
25
|
id: string;
|
|
26
26
|
name: string;
|
|
@@ -29,9 +29,12 @@ export interface SchemaItem {
|
|
|
29
29
|
schemaPath?: string;
|
|
30
30
|
producers?: Producer[];
|
|
31
31
|
consumers?: Consumer[];
|
|
32
|
+
producerName?: string;
|
|
32
33
|
owners?: Owner[];
|
|
33
34
|
};
|
|
34
35
|
schemaContent?: string;
|
|
36
|
+
/** Internal URL for loading this version's content without embedding it in the page. */
|
|
37
|
+
contentUrl?: string;
|
|
35
38
|
schemaExtension?: string;
|
|
36
39
|
specType?: string;
|
|
37
40
|
specName?: string;
|
|
@@ -44,6 +47,12 @@ export interface SchemaItem {
|
|
|
44
47
|
examples?: MessageExample[];
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
export interface SchemaDetails {
|
|
51
|
+
schemaContent: string;
|
|
52
|
+
examples: MessageExample[];
|
|
53
|
+
data?: Pick<SchemaItem['data'], 'producers' | 'consumers'>;
|
|
54
|
+
}
|
|
55
|
+
|
|
47
56
|
export interface VersionDiff {
|
|
48
57
|
fromVersion: string;
|
|
49
58
|
toVersion: string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import type { SchemaDetails, SchemaItem } from './types';
|
|
3
|
+
|
|
4
|
+
// Scoped to the explorer component's lifetime by the caller. Also deduplicates
|
|
5
|
+
// requests when the selected version is one of the comparison versions.
|
|
6
|
+
export const createSchemaDetailsLoader = () => {
|
|
7
|
+
const requests = new Map<string, Promise<SchemaDetails>>();
|
|
8
|
+
const resolved = new Map<string, SchemaDetails>();
|
|
9
|
+
const load = (url: string): Promise<SchemaDetails> => {
|
|
10
|
+
const existing = requests.get(url);
|
|
11
|
+
if (existing) return existing;
|
|
12
|
+
const request = fetch(url)
|
|
13
|
+
.then(async (response) => {
|
|
14
|
+
if (!response.ok) throw new Error('Unable to load schema content. Please try again.');
|
|
15
|
+
const details = await response.json();
|
|
16
|
+
if (typeof details.schemaContent !== 'string' || !Array.isArray(details.examples)) {
|
|
17
|
+
throw new Error('Unable to load schema content. Please try again.');
|
|
18
|
+
}
|
|
19
|
+
resolved.set(url, details);
|
|
20
|
+
return details as SchemaDetails;
|
|
21
|
+
})
|
|
22
|
+
.catch((error) => {
|
|
23
|
+
requests.delete(url);
|
|
24
|
+
throw error;
|
|
25
|
+
});
|
|
26
|
+
requests.set(url, request);
|
|
27
|
+
return request;
|
|
28
|
+
};
|
|
29
|
+
return Object.assign(load, { peek: (url: string) => resolved.get(url) });
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function useSchemaDetails(
|
|
33
|
+
item: SchemaItem | undefined,
|
|
34
|
+
load: ReturnType<typeof createSchemaDetailsLoader>,
|
|
35
|
+
enabled = true
|
|
36
|
+
) {
|
|
37
|
+
const url = enabled ? item?.contentUrl : undefined;
|
|
38
|
+
const [attempt, setAttempt] = useState(0);
|
|
39
|
+
const [state, setState] = useState<{ url: string; details?: SchemaDetails; error?: string }>();
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!url || load.peek(url)) return;
|
|
43
|
+
let current = true;
|
|
44
|
+
setState(undefined);
|
|
45
|
+
load(url).then(
|
|
46
|
+
(details) => current && setState({ url, details }),
|
|
47
|
+
() => current && setState({ url, error: 'Unable to load schema content. Please try again.' })
|
|
48
|
+
);
|
|
49
|
+
return () => {
|
|
50
|
+
current = false;
|
|
51
|
+
};
|
|
52
|
+
}, [url, load, attempt]);
|
|
53
|
+
|
|
54
|
+
const matchingState = state?.url === url ? state : undefined;
|
|
55
|
+
const details = url ? matchingState?.details || load.peek(url) : undefined;
|
|
56
|
+
const message = useMemo(
|
|
57
|
+
() => (item && details ? { ...item, ...details, data: { ...item.data, ...details.data } } : item),
|
|
58
|
+
[item, details]
|
|
59
|
+
);
|
|
60
|
+
return {
|
|
61
|
+
message,
|
|
62
|
+
loading: !!url && !details && !matchingState?.error,
|
|
63
|
+
error: details ? undefined : matchingState?.error,
|
|
64
|
+
retry: () => {
|
|
65
|
+
setState(undefined);
|
|
66
|
+
setAttempt((value) => value + 1);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -41,6 +41,18 @@ export function extractServiceName(refId: string): string {
|
|
|
41
41
|
return match ? match[1] : refId;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/** Resolve both enriched collection entries and compact explorer references. */
|
|
45
|
+
export function getSchemaRelationshipReference(reference: {
|
|
46
|
+
id: string;
|
|
47
|
+
version?: string;
|
|
48
|
+
data?: { id: string; version: string };
|
|
49
|
+
}) {
|
|
50
|
+
return {
|
|
51
|
+
id: reference.data?.id ?? reference.id,
|
|
52
|
+
version: reference.data?.version ?? reference.version,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
44
56
|
export const getLanguageForHighlight = (extension?: string): string => {
|
|
45
57
|
if (!extension) return 'json';
|
|
46
58
|
const ext = extension.toLowerCase();
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { loadArchitectureItems, Page } from './_index.data';
|
|
3
|
+
|
|
4
|
+
const collectionMocks = vi.hoisted(() => ({
|
|
5
|
+
getDomains: vi.fn(),
|
|
6
|
+
getServices: vi.fn(),
|
|
7
|
+
getSystems: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock('@utils/feature', () => ({
|
|
11
|
+
isSSR: () => false,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock('@utils/collections/domains', () => ({ getDomains: collectionMocks.getDomains }));
|
|
15
|
+
vi.mock('@utils/collections/systems', () => ({ getSystems: collectionMocks.getSystems }));
|
|
16
|
+
vi.mock('@utils/page-loaders/page-data-loader', () => ({
|
|
17
|
+
pageDataLoader: {
|
|
18
|
+
services: (...args: unknown[]) => collectionMocks.getServices(...args),
|
|
19
|
+
domains: (...args: unknown[]) => collectionMocks.getDomains(...args),
|
|
20
|
+
systems: (...args: unknown[]) => collectionMocks.getSystems(...args),
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const domain = {
|
|
25
|
+
collection: 'domains',
|
|
26
|
+
data: { id: 'ordering', name: 'Ordering', version: '1.0.0' },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const service = {
|
|
30
|
+
collection: 'services',
|
|
31
|
+
data: { id: 'order-service', name: 'Order Service', version: '1.0.0' },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const system = {
|
|
35
|
+
collection: 'systems',
|
|
36
|
+
data: { id: 'order-management-system', name: 'Order Management', version: '1.0.0' },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe('architecture page data', () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
collectionMocks.getDomains.mockReset().mockResolvedValue([domain]);
|
|
42
|
+
collectionMocks.getServices.mockReset().mockResolvedValue([service]);
|
|
43
|
+
collectionMocks.getSystems.mockReset().mockResolvedValue([system]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('hydrates domain and system services for static architecture paths', async () => {
|
|
47
|
+
const paths = await Page.getStaticPaths();
|
|
48
|
+
|
|
49
|
+
expect(collectionMocks.getDomains).toHaveBeenCalledWith({ enrichServices: true });
|
|
50
|
+
expect(collectionMocks.getSystems).toHaveBeenCalledWith({ enrichServices: true });
|
|
51
|
+
expect(paths).toEqual(
|
|
52
|
+
expect.arrayContaining([
|
|
53
|
+
expect.objectContaining({
|
|
54
|
+
params: { type: 'domains', id: 'ordering', version: '1.0.0' },
|
|
55
|
+
}),
|
|
56
|
+
expect.objectContaining({
|
|
57
|
+
params: { type: 'systems', id: 'order-management-system', version: '1.0.0' },
|
|
58
|
+
}),
|
|
59
|
+
expect.objectContaining({
|
|
60
|
+
params: { type: 'services', id: 'order-service', version: '1.0.0' },
|
|
61
|
+
}),
|
|
62
|
+
])
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('hydrates domain services when architecture pages are fetched in SSR', async () => {
|
|
67
|
+
await Page.getData({
|
|
68
|
+
props: {},
|
|
69
|
+
params: { type: 'domains', id: 'ordering', version: '1.0.0' },
|
|
70
|
+
} as any);
|
|
71
|
+
|
|
72
|
+
expect(collectionMocks.getDomains).toHaveBeenCalledWith({ enrichServices: true });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('hydrates system services when architecture pages are fetched in SSR', async () => {
|
|
76
|
+
await Page.getData({
|
|
77
|
+
props: {},
|
|
78
|
+
params: { type: 'systems', id: 'order-management-system', version: '1.0.0' },
|
|
79
|
+
} as any);
|
|
80
|
+
|
|
81
|
+
expect(collectionMocks.getSystems).toHaveBeenCalledWith({ enrichServices: true });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('loads domains with enrichServices so DomainGrid command links keep their collection', async () => {
|
|
85
|
+
await loadArchitectureItems('domains');
|
|
86
|
+
expect(collectionMocks.getDomains).toHaveBeenCalledWith({ enrichServices: true });
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -3,11 +3,27 @@ import { HybridPage } from '@utils/page-loaders/hybrid-page';
|
|
|
3
3
|
import type { PageTypes } from '@types';
|
|
4
4
|
import { pageDataLoader } from '@utils/page-loaders/page-data-loader';
|
|
5
5
|
import { getDomains } from '@utils/collections/domains';
|
|
6
|
-
import { getServices } from '@utils/collections/services';
|
|
7
6
|
import { getSystems } from '@utils/collections/systems';
|
|
8
7
|
|
|
9
8
|
const architecturePageTypes: PageTypes[] = ['services', 'domains', 'systems'];
|
|
10
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Architecture grids render service sends/receives as docs links, so domains and
|
|
12
|
+
* systems must hydrate those messages (collection + name). `pageDataLoader`
|
|
13
|
+
* uses the cheaper unenriched path used by docs/sidebar.
|
|
14
|
+
*/
|
|
15
|
+
export const loadArchitectureItems = (type: PageTypes) => {
|
|
16
|
+
if (type === 'domains') {
|
|
17
|
+
return getDomains({ enrichServices: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (type === 'systems') {
|
|
21
|
+
return getSystems({ enrichServices: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return pageDataLoader[type as PageTypes]();
|
|
25
|
+
};
|
|
26
|
+
|
|
11
27
|
/**
|
|
12
28
|
* Documentation page class for all collection types with versioning
|
|
13
29
|
*/
|
|
@@ -17,11 +33,7 @@ export class Page extends HybridPage {
|
|
|
17
33
|
return [];
|
|
18
34
|
}
|
|
19
35
|
|
|
20
|
-
const
|
|
21
|
-
const services = await getServices();
|
|
22
|
-
const systems = await getSystems();
|
|
23
|
-
|
|
24
|
-
const pageData = [services, domains, systems];
|
|
36
|
+
const pageData = await Promise.all(architecturePageTypes.map((type) => loadArchitectureItems(type)));
|
|
25
37
|
|
|
26
38
|
return pageData.flatMap((items, index) =>
|
|
27
39
|
items.map((item) => ({
|
|
@@ -47,8 +59,7 @@ export class Page extends HybridPage {
|
|
|
47
59
|
return null;
|
|
48
60
|
}
|
|
49
61
|
|
|
50
|
-
|
|
51
|
-
const items = await pageDataLoader[type as PageTypes]();
|
|
62
|
+
const items = await loadArchitectureItems(type as PageTypes);
|
|
52
63
|
|
|
53
64
|
// Find the specific item by id and version
|
|
54
65
|
const item = items.find((i) => i.data.id === id && i.data.version === version);
|
|
@@ -1,272 +1,6 @@
|
|
|
1
1
|
import { isSSR } from '@utils/feature';
|
|
2
2
|
import { HybridPage } from '@utils/page-loaders/hybrid-page';
|
|
3
|
-
import {
|
|
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 fetchAllSchemas() {
|
|
32
|
-
// Fetch all messages
|
|
33
|
-
const events = await getEvents({ getAllVersions: true });
|
|
34
|
-
const commands = await getCommands({ getAllVersions: true });
|
|
35
|
-
const queries = await getQueries({ getAllVersions: true });
|
|
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
|
-
|
|
56
|
-
if (message) {
|
|
57
|
-
try {
|
|
58
|
-
const enrichedOwners = await enrichOwners(schema.data.message.owners || []);
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
collection: message.collection,
|
|
62
|
-
data: {
|
|
63
|
-
id: message.data.id,
|
|
64
|
-
name: schema.data.message.name || message.data.name,
|
|
65
|
-
version: message.data.version,
|
|
66
|
-
summary: schema.data.message.summary || message.data.summary,
|
|
67
|
-
schemaPath,
|
|
68
|
-
producers: message.data.producers || [],
|
|
69
|
-
consumers: message.data.consumers || [],
|
|
70
|
-
owners: enrichedOwners,
|
|
71
|
-
},
|
|
72
|
-
schemaContent: schema.data.content || '',
|
|
73
|
-
schemaExtension,
|
|
74
|
-
examples: getExamplesForResource(message),
|
|
75
|
-
};
|
|
76
|
-
} catch (error) {
|
|
77
|
-
console.error(`Error reading schema metadata for ${message.data.id}:`, error);
|
|
78
|
-
const enrichedOwners = await enrichOwners(schema.data.message.owners || []);
|
|
79
|
-
return {
|
|
80
|
-
collection: message.collection,
|
|
81
|
-
data: {
|
|
82
|
-
id: message.data.id,
|
|
83
|
-
name: schema.data.message.name || schema.data.name || message.data.name,
|
|
84
|
-
version: message.data.version,
|
|
85
|
-
summary: schema.data.message.summary || message.data.summary,
|
|
86
|
-
schemaPath,
|
|
87
|
-
producers: message.data.producers || [],
|
|
88
|
-
consumers: message.data.consumers || [],
|
|
89
|
-
owners: enrichedOwners,
|
|
90
|
-
},
|
|
91
|
-
schemaContent: schema.data.content || '',
|
|
92
|
-
schemaExtension,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
collection: schema.data.message.collectionName,
|
|
99
|
-
data: {
|
|
100
|
-
id: schema.data.message.id,
|
|
101
|
-
name: schema.data.message.name || schema.data.name || schema.data.message.id,
|
|
102
|
-
version: schema.data.message.version,
|
|
103
|
-
summary: schema.data.message.summary,
|
|
104
|
-
schemaPath,
|
|
105
|
-
owners: await enrichOwners(schema.data.message.owners || []),
|
|
106
|
-
producers: [],
|
|
107
|
-
consumers: [],
|
|
108
|
-
},
|
|
109
|
-
schemaContent: schema.data.content || '',
|
|
110
|
-
schemaExtension,
|
|
111
|
-
examples: [],
|
|
112
|
-
};
|
|
113
|
-
})
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
// Filter services with specifications and read spec content - only keep essential data
|
|
117
|
-
const servicesWithSpecs = await Promise.all(
|
|
118
|
-
services.map(async (service) => {
|
|
119
|
-
try {
|
|
120
|
-
const specifications = getSpecificationsForService(service);
|
|
121
|
-
|
|
122
|
-
if (specifications.length === 0) {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return await Promise.all(
|
|
127
|
-
specifications.map(async (spec) => {
|
|
128
|
-
if (!resourceFileExists(service, spec.path)) {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const schemaContent = readResourceFile(service, spec.path) ?? '';
|
|
133
|
-
const schemaExtension = spec.type;
|
|
134
|
-
const enrichedOwners = await enrichOwners(service.data.owners || []);
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
collection: 'services',
|
|
138
|
-
data: {
|
|
139
|
-
id: `${service.data.id}`,
|
|
140
|
-
name: `${service.data.name} - ${spec.name}`,
|
|
141
|
-
version: service.data.version,
|
|
142
|
-
summary: service.data.summary,
|
|
143
|
-
schemaPath: spec.path,
|
|
144
|
-
owners: enrichedOwners,
|
|
145
|
-
},
|
|
146
|
-
schemaContent,
|
|
147
|
-
schemaExtension,
|
|
148
|
-
specType: spec.type,
|
|
149
|
-
specName: spec.name,
|
|
150
|
-
specFilenameWithoutExtension: spec.filenameWithoutExtension,
|
|
151
|
-
};
|
|
152
|
-
})
|
|
153
|
-
);
|
|
154
|
-
} catch (error) {
|
|
155
|
-
console.error(`Error reading specifications for service ${service.data.id}:`, error);
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
})
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
// Flatten and filter out null values
|
|
162
|
-
const flatServicesWithSpecs = servicesWithSpecs.flat().filter((service) => service !== null);
|
|
163
|
-
|
|
164
|
-
// Fetch all domains
|
|
165
|
-
const domains = await getDomains({ getAllVersions: true });
|
|
166
|
-
|
|
167
|
-
// Filter domains with specifications and read spec content - only keep essential data
|
|
168
|
-
const domainsWithSpecs = await Promise.all(
|
|
169
|
-
domains.map(async (domain) => {
|
|
170
|
-
try {
|
|
171
|
-
const specifications = getSpecificationsForDomain(domain);
|
|
172
|
-
|
|
173
|
-
if (specifications.length === 0) {
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return await Promise.all(
|
|
178
|
-
specifications.map(async (spec) => {
|
|
179
|
-
if (!resourceFileExists(domain, spec.path)) {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const schemaContent = readResourceFile(domain, spec.path) ?? '';
|
|
184
|
-
const schemaExtension = spec.type;
|
|
185
|
-
const enrichedOwners = await enrichOwners(domain.data.owners || []);
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
collection: 'domains',
|
|
189
|
-
data: {
|
|
190
|
-
id: `${domain.data.id}`,
|
|
191
|
-
name: `${domain.data.name} - ${spec.name}`,
|
|
192
|
-
version: domain.data.version,
|
|
193
|
-
summary: domain.data.summary,
|
|
194
|
-
schemaPath: spec.path,
|
|
195
|
-
owners: enrichedOwners,
|
|
196
|
-
},
|
|
197
|
-
schemaContent,
|
|
198
|
-
schemaExtension,
|
|
199
|
-
specType: spec.type,
|
|
200
|
-
specName: spec.name,
|
|
201
|
-
specFilenameWithoutExtension: spec.filenameWithoutExtension,
|
|
202
|
-
};
|
|
203
|
-
})
|
|
204
|
-
);
|
|
205
|
-
} catch (error) {
|
|
206
|
-
console.error(`Error reading specifications for domain ${domain.data.id}:`, error);
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
})
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
// Flatten and filter out null values for domains
|
|
213
|
-
const flatDomainsWithSpecs = domainsWithSpecs.flat().filter((domain) => domain !== null);
|
|
214
|
-
|
|
215
|
-
// Fetch all data products and extract contracts from outputs
|
|
216
|
-
const dataProducts = await getDataProducts({ getAllVersions: true });
|
|
217
|
-
|
|
218
|
-
// Filter data products with contracts in outputs and read contract content
|
|
219
|
-
const dataProductsWithContracts = await Promise.all(
|
|
220
|
-
dataProducts.map(async (dataProduct) => {
|
|
221
|
-
try {
|
|
222
|
-
const outputs = dataProduct.data.outputs || [];
|
|
223
|
-
const outputsWithContracts = outputs.filter((output) => output.contract);
|
|
224
|
-
|
|
225
|
-
if (outputsWithContracts.length === 0) {
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return await Promise.all(
|
|
230
|
-
outputsWithContracts.map(async (output) => {
|
|
231
|
-
const contract = output.contract!;
|
|
232
|
-
if (!resourceFileExists(dataProduct, contract.path)) {
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const schemaContent = readResourceFile(dataProduct, contract.path) ?? '';
|
|
237
|
-
const schemaExtension = path.extname(contract.path).slice(1) || 'json';
|
|
238
|
-
const enrichedOwners = await enrichOwners(dataProduct.data.owners || []);
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
collection: 'data-products',
|
|
242
|
-
data: {
|
|
243
|
-
id: `${dataProduct.data.id}__${contract.path}`,
|
|
244
|
-
name: contract.name,
|
|
245
|
-
version: dataProduct.data.version,
|
|
246
|
-
summary: `Data contract for ${dataProduct.data.name}`,
|
|
247
|
-
schemaPath: contract.path,
|
|
248
|
-
owners: enrichedOwners,
|
|
249
|
-
},
|
|
250
|
-
schemaContent,
|
|
251
|
-
schemaExtension,
|
|
252
|
-
contractType: contract.type,
|
|
253
|
-
dataProductId: dataProduct.data.id,
|
|
254
|
-
dataProductVersion: dataProduct.data.version,
|
|
255
|
-
};
|
|
256
|
-
})
|
|
257
|
-
);
|
|
258
|
-
} catch (error) {
|
|
259
|
-
console.error(`Error reading contracts for data product ${dataProduct.data.id}:`, error);
|
|
260
|
-
return null;
|
|
261
|
-
}
|
|
262
|
-
})
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
// Flatten and filter out null values for data product contracts
|
|
266
|
-
const flatDataProductContracts = dataProductsWithContracts.flat().filter((contract) => contract !== null);
|
|
267
|
-
|
|
268
|
-
return [...messagesWithSchemas, ...flatServicesWithSpecs, ...flatDomainsWithSpecs, ...flatDataProductContracts];
|
|
269
|
-
}
|
|
3
|
+
import { getSchemaMetadata } from '@utils/schema-explorer';
|
|
270
4
|
|
|
271
5
|
export class Page extends HybridPage {
|
|
272
6
|
static get prerender(): boolean {
|
|
@@ -278,7 +12,7 @@ export class Page extends HybridPage {
|
|
|
278
12
|
return [];
|
|
279
13
|
}
|
|
280
14
|
|
|
281
|
-
const allSchemas = await
|
|
15
|
+
const allSchemas = await getSchemaMetadata();
|
|
282
16
|
|
|
283
17
|
return [
|
|
284
18
|
{
|
|
@@ -291,7 +25,7 @@ export class Page extends HybridPage {
|
|
|
291
25
|
}
|
|
292
26
|
|
|
293
27
|
protected static async fetchData(_params: any) {
|
|
294
|
-
const allSchemas = await
|
|
28
|
+
const allSchemas = await getSchemaMetadata();
|
|
295
29
|
return {
|
|
296
30
|
schemas: allSchemas,
|
|
297
31
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
import { isSSR } from '@utils/feature';
|
|
3
|
+
import { getSchemaRegistry, getSchemaDetails } from '@utils/schema-explorer';
|
|
4
|
+
|
|
5
|
+
export const prerender = !isSSR();
|
|
6
|
+
|
|
7
|
+
export async function getStaticPaths() {
|
|
8
|
+
return [...(await getSchemaRegistry()).keys()].map((key) => ({ params: { key } }));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const GET: APIRoute = async ({ params }) => {
|
|
12
|
+
const details = params.key ? await getSchemaDetails(params.key) : undefined;
|
|
13
|
+
if (!details) return new Response('Schema not found', { status: 404 });
|
|
14
|
+
return new Response(JSON.stringify(details), {
|
|
15
|
+
headers: { 'Content-Type': 'application/json' },
|
|
16
|
+
});
|
|
17
|
+
};
|
|
@@ -4,6 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import type { CollectionMessageTypes } from '@types';
|
|
5
5
|
import type { Agent, Service } from './types';
|
|
6
6
|
import { createVersionedMap, findInMap, processSpecifications } from '@utils/collections/util';
|
|
7
|
+
import { hydrateAgents, hydrateServices } from '@utils/collections/hydrate-services';
|
|
7
8
|
|
|
8
9
|
const CACHE_ENABLED = process.env.DISABLE_EVENTCATALOG_CACHE !== 'true';
|
|
9
10
|
|
|
@@ -19,86 +20,6 @@ interface Props {
|
|
|
19
20
|
// Simple in-memory cache variable
|
|
20
21
|
let memoryCache: Record<string, Domain[]> = {};
|
|
21
22
|
|
|
22
|
-
// Helper to hydrate services
|
|
23
|
-
const hydrateServices = (
|
|
24
|
-
servicesList: any[],
|
|
25
|
-
serviceMap: Map<string, any[]>,
|
|
26
|
-
messageMap: Map<string, any[]>,
|
|
27
|
-
containerMap: Map<string, any[]>
|
|
28
|
-
) => {
|
|
29
|
-
return servicesList
|
|
30
|
-
.map((service: { id: string; version: string | undefined }) => findInMap(serviceMap, service.id, service.version))
|
|
31
|
-
.filter((s) => !!s)
|
|
32
|
-
.map((service) => {
|
|
33
|
-
// Hydrate service messages and containers
|
|
34
|
-
const sends = (service.data.sends || [])
|
|
35
|
-
.map((msg: any) => findInMap(messageMap, msg.id, msg.version))
|
|
36
|
-
.filter((m: any) => !!m);
|
|
37
|
-
|
|
38
|
-
const receives = (service.data.receives || [])
|
|
39
|
-
.map((msg: any) => findInMap(messageMap, msg.id, msg.version))
|
|
40
|
-
.filter((m: any) => !!m);
|
|
41
|
-
|
|
42
|
-
const readsFrom = (service.data.readsFrom || [])
|
|
43
|
-
.map((c: any) => findInMap(containerMap, c.id, c.version))
|
|
44
|
-
.filter((c: any) => !!c);
|
|
45
|
-
|
|
46
|
-
const writesTo = (service.data.writesTo || [])
|
|
47
|
-
.map((c: any) => findInMap(containerMap, c.id, c.version))
|
|
48
|
-
.filter((c: any) => !!c);
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
...service,
|
|
52
|
-
data: {
|
|
53
|
-
...service.data,
|
|
54
|
-
sends: sends as any,
|
|
55
|
-
receives: receives as any,
|
|
56
|
-
readsFrom: readsFrom as any,
|
|
57
|
-
writesTo: writesTo as any,
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
});
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const hydrateAgents = (
|
|
64
|
-
agentsList: any[],
|
|
65
|
-
agentMap: Map<string, any[]>,
|
|
66
|
-
messageMap: Map<string, any[]>,
|
|
67
|
-
containerMap: Map<string, any[]>
|
|
68
|
-
) => {
|
|
69
|
-
return agentsList
|
|
70
|
-
.map((agent: { id: string; version: string | undefined }) => findInMap(agentMap, agent.id, agent.version))
|
|
71
|
-
.filter((a) => !!a)
|
|
72
|
-
.map((agent) => {
|
|
73
|
-
const sends = (agent.data.sends || [])
|
|
74
|
-
.map((msg: any) => findInMap(messageMap, msg.id, msg.version))
|
|
75
|
-
.filter((m: any) => !!m);
|
|
76
|
-
|
|
77
|
-
const receives = (agent.data.receives || [])
|
|
78
|
-
.map((msg: any) => findInMap(messageMap, msg.id, msg.version))
|
|
79
|
-
.filter((m: any) => !!m);
|
|
80
|
-
|
|
81
|
-
const readsFrom = (agent.data.readsFrom || [])
|
|
82
|
-
.map((c: any) => findInMap(containerMap, c.id, c.version))
|
|
83
|
-
.filter((c: any) => !!c);
|
|
84
|
-
|
|
85
|
-
const writesTo = (agent.data.writesTo || [])
|
|
86
|
-
.map((c: any) => findInMap(containerMap, c.id, c.version))
|
|
87
|
-
.filter((c: any) => !!c);
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
...agent,
|
|
91
|
-
data: {
|
|
92
|
-
...agent.data,
|
|
93
|
-
sends: sends as any,
|
|
94
|
-
receives: receives as any,
|
|
95
|
-
readsFrom: readsFrom as any,
|
|
96
|
-
writesTo: writesTo as any,
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
});
|
|
100
|
-
};
|
|
101
|
-
|
|
102
23
|
// --- MAIN FUNCTION ---
|
|
103
24
|
|
|
104
25
|
export const getDomains = async ({
|