@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.
- 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-W6IMDOJJ.js → chunk-K45ABG6N.js} +1 -1
- package/dist/{chunk-LB5EMDAY.js → chunk-PR77FB54.js} +1 -1
- package/dist/{chunk-CDWE6WWB.js → chunk-QXANW76J.js} +1 -1
- package/dist/{chunk-W5C4K7F2.js → chunk-U6R6BFJ5.js} +1 -1
- package/dist/{chunk-DRK5QSQC.js → chunk-Y6VX2CGL.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 +5 -5
- 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/MDX/NodeGraph/NodeGraph.astro +6 -2
- 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/docs/[type]/[id]/[version]/field-lineage/_index.data.spec.ts +52 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/field-lineage/_index.data.ts +20 -11
- 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/stores/sidebar-store/state.ts +3 -28
- package/eventcatalog/src/utils/collections/field-usage.ts +48 -0
- package/eventcatalog/src/utils/node-graphs/channel-node-graph.ts +2 -2
- package/eventcatalog/src/utils/node-graphs/compact-visualiser-graph.ts +246 -0
- package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +2 -2
- package/eventcatalog/src/utils/node-graphs/data-products-node-graph.ts +2 -2
- package/eventcatalog/src/utils/node-graphs/domains-canvas.ts +2 -2
- package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +2 -2
- package/eventcatalog/src/utils/node-graphs/flows-node-graph.ts +2 -2
- package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +2 -2
- package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +2 -2
- package/eventcatalog/src/utils/node-graphs/system-context-node-graph.ts +2 -2
- package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +8 -3
- package/eventcatalog/src/utils/node-graphs/utils/utils.ts +30 -2
- package/eventcatalog/src/utils/schema-explorer.ts +276 -0
- package/package.json +3 -3
|
@@ -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
|
+
};
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
withArchitectureDecisionsSection,
|
|
43
43
|
} from './builders/shared';
|
|
44
44
|
import { isArchitectureGraphEnabled, isChangelogEnabled, isResourceDocsEnabled } from '@utils/feature';
|
|
45
|
+
import { collectMessageIdsWithFieldUsage } from '@utils/collections/field-usage';
|
|
45
46
|
|
|
46
47
|
export type { NavigationData, NavNode, ChildRef };
|
|
47
48
|
|
|
@@ -580,34 +581,8 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
|
|
|
580
581
|
{} as Record<string, NavNode | string>
|
|
581
582
|
);
|
|
582
583
|
|
|
583
|
-
//
|
|
584
|
-
|
|
585
|
-
// sends/receives pointers with resolved message entries, which strips the fields property.
|
|
586
|
-
const messagesWithFieldUsage = new Set<string>();
|
|
587
|
-
for (const agent of rawAgents) {
|
|
588
|
-
for (const pointer of agent.data.sends || []) {
|
|
589
|
-
if (pointer.fields?.length) messagesWithFieldUsage.add(pointer.id);
|
|
590
|
-
}
|
|
591
|
-
for (const pointer of agent.data.receives || []) {
|
|
592
|
-
if (pointer.fields?.length) messagesWithFieldUsage.add(pointer.id);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
for (const service of rawServices) {
|
|
596
|
-
for (const pointer of service.data.sends || []) {
|
|
597
|
-
if (pointer.fields?.length) messagesWithFieldUsage.add(pointer.id);
|
|
598
|
-
}
|
|
599
|
-
for (const pointer of service.data.receives || []) {
|
|
600
|
-
if (pointer.fields?.length) messagesWithFieldUsage.add(pointer.id);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
for (const domain of rawDomains) {
|
|
604
|
-
for (const pointer of domain.data.sends || []) {
|
|
605
|
-
if (pointer.fields?.length) messagesWithFieldUsage.add(pointer.id);
|
|
606
|
-
}
|
|
607
|
-
for (const pointer of domain.data.receives || []) {
|
|
608
|
-
if (pointer.fields?.length) messagesWithFieldUsage.add(pointer.id);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
584
|
+
// Raw collections keep send/receive pointers (and their `fields`) which hydration strips.
|
|
585
|
+
const messagesWithFieldUsage = collectMessageIdsWithFieldUsage(rawAgents, rawServices, rawDomains);
|
|
611
586
|
|
|
612
587
|
const flowRefsByMessage = buildFlowReferencesByMessage({
|
|
613
588
|
flows,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getCollection } from 'astro:content';
|
|
2
|
+
|
|
3
|
+
type PointerWithFields = { id?: string; fields?: string[] };
|
|
4
|
+
|
|
5
|
+
type ResourceWithMessagePointers = {
|
|
6
|
+
data: {
|
|
7
|
+
sends?: PointerWithFields[];
|
|
8
|
+
receives?: PointerWithFields[];
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const addFieldUsageFromPointers = (messageIds: Set<string>, pointers: PointerWithFields[] | undefined) => {
|
|
13
|
+
for (const pointer of pointers || []) {
|
|
14
|
+
if (pointer.id && pointer.fields?.length) {
|
|
15
|
+
messageIds.add(pointer.id);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Message ids that have field-level lineage declared on a send/receive pointer.
|
|
22
|
+
* Uses raw (unhydrated) resources because hydration replaces those pointers and drops `fields`.
|
|
23
|
+
* Matches the sidebar "Field Usage" link gate.
|
|
24
|
+
*/
|
|
25
|
+
export const collectMessageIdsWithFieldUsage = (
|
|
26
|
+
agents: ResourceWithMessagePointers[],
|
|
27
|
+
services: ResourceWithMessagePointers[],
|
|
28
|
+
domains: ResourceWithMessagePointers[]
|
|
29
|
+
): Set<string> => {
|
|
30
|
+
const messageIds = new Set<string>();
|
|
31
|
+
|
|
32
|
+
for (const resource of [...agents, ...services, ...domains]) {
|
|
33
|
+
addFieldUsageFromPointers(messageIds, resource.data.sends);
|
|
34
|
+
addFieldUsageFromPointers(messageIds, resource.data.receives);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return messageIds;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const getMessageIdsWithFieldUsage = async (): Promise<Set<string>> => {
|
|
41
|
+
const [agents, services, domains] = await Promise.all([
|
|
42
|
+
getCollection('agents'),
|
|
43
|
+
getCollection('services'),
|
|
44
|
+
getCollection('domains'),
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
return collectMessageIdsWithFieldUsage(agents, services, domains);
|
|
48
|
+
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getCollection, type CollectionEntry } from 'astro:content';
|
|
2
|
-
import dagre from 'dagre';
|
|
3
2
|
import {
|
|
4
3
|
buildContextMenuForAgent,
|
|
5
4
|
buildContextMenuForMessage,
|
|
@@ -17,6 +16,7 @@ import {
|
|
|
17
16
|
getOperationFields,
|
|
18
17
|
DEFAULT_NODE_WIDTH,
|
|
19
18
|
DEFAULT_NODE_HEIGHT,
|
|
19
|
+
layoutDagreGraph,
|
|
20
20
|
} from './utils/utils';
|
|
21
21
|
import { createVersionedMap, findInMap } from '@utils/collections/util';
|
|
22
22
|
import { getChannelChain, getChannels } from '@utils/collections/channels';
|
|
@@ -457,7 +457,7 @@ export const getNodesAndEdges = async ({ id, version, defaultFlow, mode = 'simpl
|
|
|
457
457
|
});
|
|
458
458
|
|
|
459
459
|
if (layout) {
|
|
460
|
-
|
|
460
|
+
layoutDagreGraph(flow);
|
|
461
461
|
}
|
|
462
462
|
|
|
463
463
|
return {
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import type { Edge, Node } from '@xyflow/react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fields the visualiser nodes actually read from a resource. Everything else
|
|
5
|
+
* (markdown bodies, file paths, hydrated producers/consumers, full send/receive
|
|
6
|
+
* collections) is dropped before Astro serialises the graph into the page.
|
|
7
|
+
*/
|
|
8
|
+
const RESOURCE_DATA_KEYS = [
|
|
9
|
+
'id',
|
|
10
|
+
'name',
|
|
11
|
+
'version',
|
|
12
|
+
'summary',
|
|
13
|
+
'owners',
|
|
14
|
+
'deprecated',
|
|
15
|
+
'draft',
|
|
16
|
+
'notes',
|
|
17
|
+
'styles',
|
|
18
|
+
'badges',
|
|
19
|
+
'latestVersion',
|
|
20
|
+
'method',
|
|
21
|
+
'path',
|
|
22
|
+
'statusCodes',
|
|
23
|
+
'specifications',
|
|
24
|
+
'externalSystem',
|
|
25
|
+
'repository',
|
|
26
|
+
'model',
|
|
27
|
+
'tools',
|
|
28
|
+
'parameters',
|
|
29
|
+
'protocols',
|
|
30
|
+
'address',
|
|
31
|
+
'deliveryGuarantee',
|
|
32
|
+
'type',
|
|
33
|
+
'container_type',
|
|
34
|
+
'schemas',
|
|
35
|
+
'properties',
|
|
36
|
+
'aggregateRoot',
|
|
37
|
+
'sidebar',
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
const RESOURCE_OBJECT_KEYS = new Set([
|
|
41
|
+
'message',
|
|
42
|
+
'service',
|
|
43
|
+
'agent',
|
|
44
|
+
'domain',
|
|
45
|
+
'channel',
|
|
46
|
+
'dataProduct',
|
|
47
|
+
'container',
|
|
48
|
+
'entity',
|
|
49
|
+
'flow',
|
|
50
|
+
'data',
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
const DROP_EDGE_RESOURCE_KEYS = new Set(['publisherService', 'consumerService']);
|
|
54
|
+
|
|
55
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
56
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
57
|
+
|
|
58
|
+
const isCollectionEntry = (value: Record<string, unknown>): boolean =>
|
|
59
|
+
isPlainObject(value.data) &&
|
|
60
|
+
('collection' in value || 'filePath' in value || 'digest' in value || 'body' in value || 'rendered' in value);
|
|
61
|
+
|
|
62
|
+
const compactDomainService = (service: unknown) => {
|
|
63
|
+
if (!isPlainObject(service)) return service;
|
|
64
|
+
|
|
65
|
+
const data = isPlainObject(service.data) ? service.data : service;
|
|
66
|
+
const id = data.id ?? service.id;
|
|
67
|
+
const name = data.name ?? service.name;
|
|
68
|
+
const version = data.version ?? service.version;
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
data: {
|
|
72
|
+
...(id !== undefined ? { id } : {}),
|
|
73
|
+
...(name !== undefined ? { name } : {}),
|
|
74
|
+
...(version !== undefined ? { version } : {}),
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const compactOwners = (owners: unknown): unknown => {
|
|
80
|
+
if (!Array.isArray(owners)) return owners;
|
|
81
|
+
return owners.map((owner) => {
|
|
82
|
+
if (typeof owner === 'string') return owner;
|
|
83
|
+
if (!isPlainObject(owner)) return owner;
|
|
84
|
+
const data = isPlainObject(owner.data) ? owner.data : owner;
|
|
85
|
+
const id = data.id ?? owner.id;
|
|
86
|
+
const name = data.name ?? owner.name;
|
|
87
|
+
if (id === undefined && name === undefined) return owner;
|
|
88
|
+
return {
|
|
89
|
+
...(id !== undefined ? { id } : {}),
|
|
90
|
+
...(name !== undefined ? { name } : {}),
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const compactVisualiserResource = (value: unknown): unknown => {
|
|
96
|
+
if (value == null || typeof value !== 'object') return value;
|
|
97
|
+
if (Array.isArray(value)) return value.map(compactVisualiserResource);
|
|
98
|
+
|
|
99
|
+
const obj = value as Record<string, unknown>;
|
|
100
|
+
|
|
101
|
+
if (isCollectionEntry(obj)) {
|
|
102
|
+
const data = compactVisualiserResourceData(obj.data);
|
|
103
|
+
const resourceId = isPlainObject(data) && data.id !== undefined ? data.id : obj.id;
|
|
104
|
+
return {
|
|
105
|
+
...(resourceId !== undefined ? { id: resourceId } : {}),
|
|
106
|
+
...(obj.collection !== undefined ? { collection: obj.collection } : {}),
|
|
107
|
+
data,
|
|
108
|
+
// EventNode / ServiceNode / ChannelNode read name/version/summary from the
|
|
109
|
+
// top level. Collection-entry shaped payloads only have those on `.data`.
|
|
110
|
+
...(isPlainObject(data) ? data : {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return compactVisualiserResourceData(obj);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const compactVisualiserResourceData = (value: unknown): unknown => {
|
|
118
|
+
if (!isPlainObject(value)) return value;
|
|
119
|
+
|
|
120
|
+
const out: Record<string, unknown> = {};
|
|
121
|
+
for (const key of RESOURCE_DATA_KEYS) {
|
|
122
|
+
if (value[key] === undefined) continue;
|
|
123
|
+
if (key === 'owners') {
|
|
124
|
+
out[key] = compactOwners(value[key]);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (key === 'tools' && Array.isArray(value[key])) {
|
|
128
|
+
out[key] = value[key];
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
out[key] = value[key];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Domain nodes render `domain.data.services[].data.{id,name,version}`.
|
|
135
|
+
if (Array.isArray(value.services)) {
|
|
136
|
+
out.services = value.services.map(compactDomainService);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return out;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const compactEndpoint = (endpoint: unknown): unknown => {
|
|
143
|
+
if (!isPlainObject(endpoint)) return endpoint;
|
|
144
|
+
|
|
145
|
+
const data = isPlainObject(endpoint.data) ? endpoint.data : undefined;
|
|
146
|
+
const collection = endpoint.collection ?? data?.collection;
|
|
147
|
+
const id = endpoint.id ?? (data?.id !== undefined && data?.version !== undefined ? `${data.id}-${data.version}` : data?.id);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
...(id !== undefined ? { id } : {}),
|
|
151
|
+
...(collection !== undefined ? { collection } : {}),
|
|
152
|
+
...(data
|
|
153
|
+
? {
|
|
154
|
+
data: {
|
|
155
|
+
...(data.id !== undefined ? { id: data.id } : {}),
|
|
156
|
+
...(data.version !== undefined ? { version: data.version } : {}),
|
|
157
|
+
...(collection !== undefined ? { collection } : {}),
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
: {}),
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const compactNodeData = (data: unknown): unknown => {
|
|
165
|
+
if (!isPlainObject(data)) return data;
|
|
166
|
+
|
|
167
|
+
const out: Record<string, unknown> = {};
|
|
168
|
+
|
|
169
|
+
for (const [key, value] of Object.entries(data)) {
|
|
170
|
+
if (value === undefined) continue;
|
|
171
|
+
|
|
172
|
+
if (RESOURCE_OBJECT_KEYS.has(key)) {
|
|
173
|
+
out[key] = compactVisualiserResource(value);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (key === 'messages' && Array.isArray(value)) {
|
|
178
|
+
out[key] = value.map((entry) => {
|
|
179
|
+
if (!isPlainObject(entry)) return entry;
|
|
180
|
+
return {
|
|
181
|
+
...entry,
|
|
182
|
+
...(entry.message !== undefined ? { message: compactVisualiserResource(entry.message) } : {}),
|
|
183
|
+
...(Array.isArray(entry.channels) ? { channels: entry.channels.map(compactVisualiserResource) } : {}),
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
out[key] = value;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return out;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const compactEdgeData = (data: unknown): unknown => {
|
|
196
|
+
if (!isPlainObject(data)) return data;
|
|
197
|
+
|
|
198
|
+
const out: Record<string, unknown> = {};
|
|
199
|
+
|
|
200
|
+
for (const [key, value] of Object.entries(data)) {
|
|
201
|
+
if (value === undefined || DROP_EDGE_RESOURCE_KEYS.has(key)) continue;
|
|
202
|
+
|
|
203
|
+
if (key === 'rootSourceAndTarget' && isPlainObject(value)) {
|
|
204
|
+
out[key] = {
|
|
205
|
+
source: compactEndpoint(value.source),
|
|
206
|
+
target: compactEndpoint(value.target),
|
|
207
|
+
};
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (RESOURCE_OBJECT_KEYS.has(key) || key === 'message') {
|
|
212
|
+
out[key] = compactVisualiserResource(value);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
out[key] = value;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return out;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const compactNode = <T extends Node>(node: T): T => {
|
|
223
|
+
const { data, ...rest } = node;
|
|
224
|
+
return {
|
|
225
|
+
...rest,
|
|
226
|
+
...(data !== undefined ? { data: compactNodeData(data) } : {}),
|
|
227
|
+
} as T;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const compactEdge = <T extends Edge>(edge: T): T => {
|
|
231
|
+
const { data, ...rest } = edge;
|
|
232
|
+
return {
|
|
233
|
+
...rest,
|
|
234
|
+
...(data !== undefined ? { data: compactEdgeData(data) } : {}),
|
|
235
|
+
} as T;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Strip collection-entry weight from a React Flow graph before it is serialised
|
|
240
|
+
* into an Astro island. Node/edge structure and the fields visualiser components
|
|
241
|
+
* read are preserved.
|
|
242
|
+
*/
|
|
243
|
+
export const compactVisualiserGraph = <N extends Node, E extends Edge>(nodes: N[], edges: E[]): { nodes: N[]; edges: E[] } => ({
|
|
244
|
+
nodes: nodes.map(compactNode),
|
|
245
|
+
edges: edges.map(compactEdge),
|
|
246
|
+
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { CollectionEntry } from 'astro:content';
|
|
2
|
-
import dagre from 'dagre';
|
|
3
2
|
import {
|
|
4
3
|
calculatedNodes,
|
|
5
4
|
createDagreGraph,
|
|
@@ -10,6 +9,7 @@ import {
|
|
|
10
9
|
buildContextMenuForResource,
|
|
11
10
|
DEFAULT_NODE_WIDTH,
|
|
12
11
|
DEFAULT_NODE_HEIGHT,
|
|
12
|
+
layoutDagreGraph,
|
|
13
13
|
} from './utils/utils';
|
|
14
14
|
import { MarkerType } from '@xyflow/react';
|
|
15
15
|
import { findMatchingNodes } from '@utils/collections/util';
|
|
@@ -299,7 +299,7 @@ export const getNodesAndEdges = async ({
|
|
|
299
299
|
|
|
300
300
|
// Render the diagram in memory getting hte X and Y
|
|
301
301
|
if (layout) {
|
|
302
|
-
|
|
302
|
+
layoutDagreGraph(flow);
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
return {
|