@eventcatalog/core 3.46.0 → 3.47.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-FNOYJEUK.js → chunk-ALXVETEP.js} +1 -1
- package/dist/{chunk-DOHA5HNJ.js → chunk-GBC637Z2.js} +1 -1
- package/dist/{chunk-JS6IYB55.js → chunk-XYCPSZ4V.js} +1 -1
- package/dist/{chunk-TLLUDBO4.js → chunk-ZM5P2252.js} +1 -1
- package/dist/{chunk-X4AESI6E.js → chunk-ZRLFPUCO.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 +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/src/components/MDX/SchemaViewer/SchemaViewerRoot.astro +11 -1
- package/eventcatalog/src/components/MDX/SchemaViewer/schema-viewer-utils.spec.ts +63 -0
- package/eventcatalog/src/components/MDX/SchemaViewer/schema-viewer-utils.ts +20 -7
- package/eventcatalog/src/components/SchemaExplorer/ProtobufSchemaViewer.tsx +532 -0
- package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +6 -0
- package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +20 -2
- package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +14 -5
- package/eventcatalog/src/components/SchemaExplorer/SchemaViewerModal.tsx +8 -2
- package/eventcatalog/src/components/SchemaExplorer/utils.ts +4 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/language/_index.data.ts +19 -4
- package/eventcatalog/src/pages/docs/[type]/[id]/language.mdx.ts +16 -5
- package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +4 -2
- package/eventcatalog/src/stores/sidebar-store/builders/entity.ts +87 -0
- package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +1 -0
- package/eventcatalog/src/stores/sidebar-store/state.ts +15 -14
- package/eventcatalog/src/styles/tailwind.css +18 -0
- package/eventcatalog/src/utils/collections/domains.ts +39 -0
- package/eventcatalog/src/utils/collections/entities.ts +3 -1
- package/eventcatalog/src/utils/files.ts +9 -0
- package/eventcatalog/src/utils/node-graphs/domain-entity-map.ts +24 -12
- package/eventcatalog/src/utils/protobuf-schema.ts +476 -0
- package/package.json +3 -4
|
@@ -2,6 +2,7 @@ import * as Dialog from '@radix-ui/react-dialog';
|
|
|
2
2
|
import { XMarkIcon, ArrowsPointingOutIcon } from '@heroicons/react/24/outline';
|
|
3
3
|
import JSONSchemaViewer from './JSONSchemaViewer';
|
|
4
4
|
import AvroSchemaViewer from './AvroSchemaViewer';
|
|
5
|
+
import ProtobufSchemaViewer from './ProtobufSchemaViewer';
|
|
5
6
|
import type { SchemaItem } from './types';
|
|
6
7
|
|
|
7
8
|
interface SchemaViewerModalProps {
|
|
@@ -10,6 +11,7 @@ interface SchemaViewerModalProps {
|
|
|
10
11
|
message: SchemaItem;
|
|
11
12
|
parsedSchema: any;
|
|
12
13
|
parsedAvroSchema?: any;
|
|
14
|
+
parsedProtoSchema?: any;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export default function SchemaViewerModal({
|
|
@@ -18,10 +20,12 @@ export default function SchemaViewerModal({
|
|
|
18
20
|
message,
|
|
19
21
|
parsedSchema,
|
|
20
22
|
parsedAvroSchema,
|
|
23
|
+
parsedProtoSchema,
|
|
21
24
|
}: SchemaViewerModalProps) {
|
|
22
|
-
if (!parsedSchema && !parsedAvroSchema) return null;
|
|
25
|
+
if (!parsedSchema && !parsedAvroSchema && !parsedProtoSchema) return null;
|
|
23
26
|
|
|
24
27
|
const isAvro = !!parsedAvroSchema;
|
|
28
|
+
const isProto = !!parsedProtoSchema;
|
|
25
29
|
|
|
26
30
|
return (
|
|
27
31
|
<Dialog.Root open={isOpen} onOpenChange={onOpenChange}>
|
|
@@ -35,7 +39,7 @@ export default function SchemaViewerModal({
|
|
|
35
39
|
<div>
|
|
36
40
|
<Dialog.Title className="text-xl font-semibold text-[rgb(var(--ec-page-text))]">{message.data.name}</Dialog.Title>
|
|
37
41
|
<Dialog.Description className="text-sm text-[rgb(var(--ec-page-text-muted))] mt-1">
|
|
38
|
-
v{message.data.version} · {isAvro ? 'Avro' : 'JSON'} Schema
|
|
42
|
+
v{message.data.version} · {isAvro ? 'Avro' : isProto ? 'Protobuf' : 'JSON'} Schema
|
|
39
43
|
</Dialog.Description>
|
|
40
44
|
</div>
|
|
41
45
|
</div>
|
|
@@ -54,6 +58,8 @@ export default function SchemaViewerModal({
|
|
|
54
58
|
<div className="flex-1 overflow-hidden p-6">
|
|
55
59
|
{isAvro ? (
|
|
56
60
|
<AvroSchemaViewer schema={parsedAvroSchema} expand={true} search={true} />
|
|
61
|
+
) : isProto ? (
|
|
62
|
+
<ProtobufSchemaViewer schema={parsedProtoSchema} expand={true} search={true} />
|
|
57
63
|
) : (
|
|
58
64
|
<JSONSchemaViewer schema={parsedSchema} expand={true} search={true} />
|
|
59
65
|
)}
|
|
@@ -7,6 +7,7 @@ export const ICON_SPECS: Record<string, string> = {
|
|
|
7
7
|
avro: 'avro',
|
|
8
8
|
avsc: 'avro',
|
|
9
9
|
proto: 'proto',
|
|
10
|
+
protobuf: 'proto',
|
|
10
11
|
json: 'json-schema',
|
|
11
12
|
};
|
|
12
13
|
|
|
@@ -20,6 +21,7 @@ export function getFormatBadge(ext?: string): { label: string; color: string } {
|
|
|
20
21
|
case 'avsc':
|
|
21
22
|
return { label: 'avro', color: 'text-blue-400' };
|
|
22
23
|
case 'proto':
|
|
24
|
+
case 'protobuf':
|
|
23
25
|
return { label: 'proto', color: 'text-orange-400' };
|
|
24
26
|
case 'yaml':
|
|
25
27
|
case 'yml':
|
|
@@ -48,6 +50,7 @@ export const getLanguageForHighlight = (extension?: string): string => {
|
|
|
48
50
|
case 'json':
|
|
49
51
|
return 'json';
|
|
50
52
|
case 'proto':
|
|
53
|
+
case 'protobuf':
|
|
51
54
|
return 'protobuf';
|
|
52
55
|
case 'xsd':
|
|
53
56
|
case 'xml':
|
|
@@ -79,6 +82,7 @@ export const getSchemaTypeLabel = (extension?: string): string => {
|
|
|
79
82
|
case 'avsc':
|
|
80
83
|
return 'Avro';
|
|
81
84
|
case 'proto':
|
|
85
|
+
case 'protobuf':
|
|
82
86
|
return 'Protobuf';
|
|
83
87
|
case 'xsd':
|
|
84
88
|
return 'XML Schema';
|
|
@@ -10,10 +10,19 @@ export class Page extends HybridPage {
|
|
|
10
10
|
return [];
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const { getDomains } = await import('@utils/collections/domains');
|
|
13
|
+
const { getDomains, hasUbiquitousLanguageTermsWithSubdomains } = await import('@utils/collections/domains');
|
|
14
14
|
const domains = await getDomains({ getAllVersions: false });
|
|
15
|
+
const domainsWithUbiquitousLanguage = await domains.reduce<Promise<typeof domains>>(async (acc, domain) => {
|
|
16
|
+
const accumulator = await acc;
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
if (await hasUbiquitousLanguageTermsWithSubdomains(domain)) {
|
|
19
|
+
return [...accumulator, domain];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return accumulator;
|
|
23
|
+
}, Promise.resolve([]));
|
|
24
|
+
|
|
25
|
+
return domainsWithUbiquitousLanguage.map((item) => ({
|
|
17
26
|
params: {
|
|
18
27
|
type: item.collection,
|
|
19
28
|
id: item.data.id,
|
|
@@ -23,9 +32,15 @@ export class Page extends HybridPage {
|
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
protected static async fetchData(params: any) {
|
|
26
|
-
const { getDomains } = await import('@utils/collections/domains');
|
|
35
|
+
const { getDomains, hasUbiquitousLanguageTermsWithSubdomains } = await import('@utils/collections/domains');
|
|
27
36
|
const domains = await getDomains({ getAllVersions: false });
|
|
28
|
-
|
|
37
|
+
const domain = domains.find((d) => d.data.id === params.id && d.collection === params.type);
|
|
38
|
+
|
|
39
|
+
if (!domain || !(await hasUbiquitousLanguageTermsWithSubdomains(domain))) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return domain;
|
|
29
44
|
}
|
|
30
45
|
|
|
31
46
|
protected static createNotFoundResponse(): Response {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getDomains, getUbiquitousLanguage } from '@utils/collections/domains';
|
|
1
|
+
import { getDomains, getUbiquitousLanguage, hasUbiquitousLanguageTerms } from '@utils/collections/domains';
|
|
2
2
|
import type { CollectionEntry } from 'astro:content';
|
|
3
3
|
import type { APIRoute } from 'astro';
|
|
4
4
|
import config from '@config';
|
|
@@ -9,8 +9,19 @@ import { filterMarkdownForAgents } from '@utils/llms';
|
|
|
9
9
|
export async function getStaticPaths() {
|
|
10
10
|
const domains = await getDomains({ getAllVersions: false });
|
|
11
11
|
|
|
12
|
-
const buildPages = (collection: CollectionEntry<'domains'>[]) => {
|
|
13
|
-
|
|
12
|
+
const buildPages = async (collection: CollectionEntry<'domains'>[]) => {
|
|
13
|
+
const collectionWithUbiquitousLanguage = await collection.reduce<Promise<CollectionEntry<'domains'>[]>>(async (acc, item) => {
|
|
14
|
+
const accumulator = await acc;
|
|
15
|
+
const ubiquitousLanguages = await getUbiquitousLanguage(item);
|
|
16
|
+
|
|
17
|
+
if (ubiquitousLanguages.some(hasUbiquitousLanguageTerms)) {
|
|
18
|
+
return [...accumulator, item];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return accumulator;
|
|
22
|
+
}, Promise.resolve([]));
|
|
23
|
+
|
|
24
|
+
return collectionWithUbiquitousLanguage.map((item) => ({
|
|
14
25
|
params: {
|
|
15
26
|
type: item.collection,
|
|
16
27
|
id: item.data.id,
|
|
@@ -22,7 +33,7 @@ export async function getStaticPaths() {
|
|
|
22
33
|
}));
|
|
23
34
|
};
|
|
24
35
|
|
|
25
|
-
return [...buildPages(domains)];
|
|
36
|
+
return [...(await buildPages(domains))];
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
export const GET: APIRoute = async ({ params, props }) => {
|
|
@@ -40,7 +51,7 @@ export const GET: APIRoute = async ({ params, props }) => {
|
|
|
40
51
|
const ubiquitousLanguages = await getUbiquitousLanguage(domain as CollectionEntry<'domains'>);
|
|
41
52
|
const ubiquitousLanguage = ubiquitousLanguages[0];
|
|
42
53
|
|
|
43
|
-
if (ubiquitousLanguage?.filePath) {
|
|
54
|
+
if (ubiquitousLanguage?.filePath && hasUbiquitousLanguageTerms(ubiquitousLanguage)) {
|
|
44
55
|
let file = filterMarkdownForAgents(fs.readFileSync(ubiquitousLanguage.filePath, 'utf8'));
|
|
45
56
|
|
|
46
57
|
return new Response(file, { status: 200 });
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from './shared';
|
|
14
14
|
import { isVisualiserEnabled, isChangelogEnabled } from '@utils/feature';
|
|
15
15
|
import { pluralizeMessageType } from '@utils/collections/messages';
|
|
16
|
-
import { getSpecificationsForDomain } from '@utils/collections/domains';
|
|
16
|
+
import { getSpecificationsForDomain, hasUbiquitousLanguageTermsWithSubdomainsInCollection } from '@utils/collections/domains';
|
|
17
17
|
import { iconFieldsForResource } from '@utils/icon';
|
|
18
18
|
|
|
19
19
|
export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[], context: ResourceGroupContext): NavNode => {
|
|
@@ -41,7 +41,9 @@ export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[
|
|
|
41
41
|
const resourceGroups = domain.data.resourceGroups || [];
|
|
42
42
|
const hasResourceGroups = resourceGroups.length > 0;
|
|
43
43
|
|
|
44
|
-
const renderUbiquitousLanguage =
|
|
44
|
+
const renderUbiquitousLanguage =
|
|
45
|
+
hasUbiquitousLanguageTermsWithSubdomainsInCollection(domain, context.ubiquitousLanguages || []) &&
|
|
46
|
+
shouldRenderSideBarSection(domain, 'ubiquitousLanguage');
|
|
45
47
|
const renderOwners = owners.length > 0 && shouldRenderSideBarSection(domain, 'owners');
|
|
46
48
|
|
|
47
49
|
const renderVisualiser = isVisualiserEnabled();
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { CollectionEntry } from 'astro:content';
|
|
2
|
+
import { buildUrl } from '@utils/url-builder';
|
|
3
|
+
import type { NavNode, ChildRef, ResourceGroupContext } from './shared';
|
|
4
|
+
import {
|
|
5
|
+
buildAttachmentsSection,
|
|
6
|
+
buildOwnersSection,
|
|
7
|
+
buildQuickReferenceSection,
|
|
8
|
+
buildResourceDocsSection,
|
|
9
|
+
shouldRenderSideBarSection,
|
|
10
|
+
} from './shared';
|
|
11
|
+
import { isChangelogEnabled, isVisualiserEnabled } from '@utils/feature';
|
|
12
|
+
import { iconFieldsForResource } from '@utils/icon';
|
|
13
|
+
|
|
14
|
+
export const buildEntityNode = (entity: CollectionEntry<'entities'>, owners: any[], context: ResourceGroupContext): NavNode => {
|
|
15
|
+
const domains = entity.data.domains || [];
|
|
16
|
+
const services = entity.data.services || [];
|
|
17
|
+
|
|
18
|
+
const entityMapTargets = [
|
|
19
|
+
domains.length === 1 && {
|
|
20
|
+
label: 'Domain',
|
|
21
|
+
href: buildUrl(`/visualiser/domains/${(domains[0] as any).data.id}/${(domains[0] as any).data.version}/entity-map`),
|
|
22
|
+
},
|
|
23
|
+
services.length === 1 && {
|
|
24
|
+
label: 'Service',
|
|
25
|
+
href: buildUrl(`/visualiser/services/${(services[0] as any).data.id}/${(services[0] as any).data.version}/entity-map`),
|
|
26
|
+
},
|
|
27
|
+
].filter(Boolean) as { label: string; href: string }[];
|
|
28
|
+
|
|
29
|
+
const renderArchitecture = isVisualiserEnabled() && entityMapTargets.length > 0;
|
|
30
|
+
const renderDomains = domains.length > 0 && shouldRenderSideBarSection(entity, 'domains');
|
|
31
|
+
const renderServices = services.length > 0 && shouldRenderSideBarSection(entity, 'services');
|
|
32
|
+
const renderOwners = owners.length > 0 && shouldRenderSideBarSection(entity, 'owners');
|
|
33
|
+
const hasAttachments = entity.data.attachments && entity.data.attachments.length > 0;
|
|
34
|
+
|
|
35
|
+
const docsSection = buildResourceDocsSection(
|
|
36
|
+
'entities',
|
|
37
|
+
entity.data.id,
|
|
38
|
+
entity.data.version,
|
|
39
|
+
context.resourceDocs,
|
|
40
|
+
context.resourceDocCategories
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
type: 'item',
|
|
45
|
+
title: entity.data.name,
|
|
46
|
+
badge: 'Entity',
|
|
47
|
+
summary: entity.data.summary,
|
|
48
|
+
...iconFieldsForResource(entity.data, 'Box'),
|
|
49
|
+
pages: [
|
|
50
|
+
buildQuickReferenceSection(
|
|
51
|
+
[
|
|
52
|
+
{ title: 'Overview', href: buildUrl(`/docs/entities/${entity.data.id}/${entity.data.version}`) },
|
|
53
|
+
isChangelogEnabled() &&
|
|
54
|
+
shouldRenderSideBarSection(entity, 'changelog') && {
|
|
55
|
+
title: 'Changelog',
|
|
56
|
+
href: buildUrl(`/docs/entities/${entity.data.id}/${entity.data.version}/changelog`),
|
|
57
|
+
},
|
|
58
|
+
].filter(Boolean) as { title: string; href: string }[]
|
|
59
|
+
),
|
|
60
|
+
docsSection,
|
|
61
|
+
renderArchitecture && {
|
|
62
|
+
type: 'group',
|
|
63
|
+
title: 'Architecture',
|
|
64
|
+
icon: 'Workflow',
|
|
65
|
+
pages: entityMapTargets.map((target) => ({
|
|
66
|
+
type: 'item',
|
|
67
|
+
title: entityMapTargets.length === 1 ? 'Entity Map' : `${target.label} Entity Map`,
|
|
68
|
+
href: target.href,
|
|
69
|
+
})),
|
|
70
|
+
},
|
|
71
|
+
renderDomains && {
|
|
72
|
+
type: 'group',
|
|
73
|
+
title: 'Domains',
|
|
74
|
+
icon: 'Boxes',
|
|
75
|
+
pages: domains.map((domain: any) => `domain:${domain.data.id}:${domain.data.version}`),
|
|
76
|
+
},
|
|
77
|
+
renderServices && {
|
|
78
|
+
type: 'group',
|
|
79
|
+
title: 'Services',
|
|
80
|
+
icon: 'Server',
|
|
81
|
+
pages: services.map((service: any) => `service:${service.data.id}:${service.data.version}`),
|
|
82
|
+
},
|
|
83
|
+
renderOwners && buildOwnersSection(owners),
|
|
84
|
+
hasAttachments && buildAttachmentsSection(entity.data.attachments as any[]),
|
|
85
|
+
].filter(Boolean) as ChildRef[],
|
|
86
|
+
};
|
|
87
|
+
};
|
|
@@ -66,6 +66,7 @@ export type ResourceGroupContext = {
|
|
|
66
66
|
containers: CollectionEntry<'containers'>[];
|
|
67
67
|
entities?: CollectionEntry<'entities'>[];
|
|
68
68
|
dataProducts: CollectionEntry<'data-products'>[];
|
|
69
|
+
ubiquitousLanguages?: CollectionEntry<'ubiquitousLanguages'>[];
|
|
69
70
|
diagrams: CollectionEntry<'diagrams'>[];
|
|
70
71
|
schemas?: CollectionEntry<'schemas'>[];
|
|
71
72
|
adrs: Adr[];
|
|
@@ -25,6 +25,7 @@ import { buildContainerNode } from './builders/container';
|
|
|
25
25
|
import { buildFlowNode } from './builders/flow';
|
|
26
26
|
import { buildDataProductNode } from './builders/data-product';
|
|
27
27
|
import { buildAdrNode } from './builders/adr';
|
|
28
|
+
import { buildEntityNode } from './builders/entity';
|
|
28
29
|
import config from '@config';
|
|
29
30
|
import { getDesigns } from '@utils/collections/designs';
|
|
30
31
|
import { getChannels } from '@utils/collections/channels';
|
|
@@ -293,6 +294,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
|
|
|
293
294
|
entities,
|
|
294
295
|
adrs,
|
|
295
296
|
schemas,
|
|
297
|
+
ubiquitousLanguages,
|
|
296
298
|
resourceDocs,
|
|
297
299
|
resourceDocCategories,
|
|
298
300
|
] = await Promise.all([
|
|
@@ -311,6 +313,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
|
|
|
311
313
|
getEntities({ getAllVersions: false }),
|
|
312
314
|
getAdrs({ getAllVersions: false }),
|
|
313
315
|
getCollection('schemas'),
|
|
316
|
+
getCollection('ubiquitousLanguages'),
|
|
314
317
|
getResourceDocs(),
|
|
315
318
|
getResourceDocCategories(),
|
|
316
319
|
]);
|
|
@@ -333,6 +336,7 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
|
|
|
333
336
|
channels,
|
|
334
337
|
diagrams,
|
|
335
338
|
schemas,
|
|
339
|
+
ubiquitousLanguages,
|
|
336
340
|
dataProducts,
|
|
337
341
|
entities,
|
|
338
342
|
adrs,
|
|
@@ -406,6 +410,14 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
|
|
|
406
410
|
})
|
|
407
411
|
);
|
|
408
412
|
|
|
413
|
+
const entitiesWithOwners = await Promise.all(
|
|
414
|
+
entities.map(async (entity) => {
|
|
415
|
+
const owners = await Promise.all((entity.data.owners || []).map((owner) => getOwner(owner)));
|
|
416
|
+
const filteredOwners = owners.filter((o) => o !== undefined) as Array<NonNullable<(typeof owners)[0]>>;
|
|
417
|
+
return { entity, owners: filteredOwners };
|
|
418
|
+
})
|
|
419
|
+
);
|
|
420
|
+
|
|
409
421
|
const flowNodes = flows.reduce(
|
|
410
422
|
(acc, flow) => {
|
|
411
423
|
acc[`flow:${flow.data.id}:${flow.data.version}`] = withArchitectureDecisionsSection(
|
|
@@ -650,21 +662,10 @@ export const getNestedSideBarData = async (): Promise<NavigationData> => {
|
|
|
650
662
|
{} as Record<string, NavNode | string>
|
|
651
663
|
);
|
|
652
664
|
|
|
653
|
-
const entityNodes =
|
|
654
|
-
(acc, entity) => {
|
|
665
|
+
const entityNodes = entitiesWithOwners.reduce(
|
|
666
|
+
(acc, { entity, owners }) => {
|
|
655
667
|
const versionedKey = `entity:${entity.data.id}:${entity.data.version}`;
|
|
656
|
-
acc[versionedKey] = withArchitectureDecisionsSection(
|
|
657
|
-
{
|
|
658
|
-
type: 'item',
|
|
659
|
-
title: entity.data.name,
|
|
660
|
-
badge: 'Entity',
|
|
661
|
-
summary: entity.data.summary,
|
|
662
|
-
icon: 'Box',
|
|
663
|
-
href: buildUrl(`/docs/entities/${entity.data.id}/${entity.data.version}`),
|
|
664
|
-
},
|
|
665
|
-
entity,
|
|
666
|
-
adrs
|
|
667
|
-
);
|
|
668
|
+
acc[versionedKey] = withArchitectureDecisionsSection(buildEntityNode(entity, owners, context), entity, adrs);
|
|
668
669
|
if (entity.data.latestVersion === entity.data.version) {
|
|
669
670
|
acc[`entity:${entity.data.id}`] = versionedKey;
|
|
670
671
|
}
|
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
@plugin "@tailwindcss/typography";
|
|
4
4
|
|
|
5
|
+
/*
|
|
6
|
+
* EventCatalog renders MDX inside Tailwind Typography's `.prose` wrapper.
|
|
7
|
+
* Typography adds decorative backticks around inline code via pseudo-elements;
|
|
8
|
+
* docs should show only the code content itself.
|
|
9
|
+
*/
|
|
10
|
+
.prose code::before,
|
|
11
|
+
.prose code::after {
|
|
12
|
+
content: '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.prose :not(pre) > code {
|
|
16
|
+
background-color: oklab(0.97 0 0.0013 / 0.5);
|
|
17
|
+
border-radius: 0.25rem;
|
|
18
|
+
font-family: var(--font-mono);
|
|
19
|
+
padding: 0.125rem 0.375rem;
|
|
20
|
+
font-weight: 400;
|
|
21
|
+
}
|
|
22
|
+
|
|
5
23
|
/* Dark mode uses data-theme attribute, not prefers-color-scheme */
|
|
6
24
|
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
|
|
7
25
|
|
|
@@ -367,6 +367,36 @@ export const getUbiquitousLanguage = async (domain: Domain): Promise<UbiquitousL
|
|
|
367
367
|
return ubiquitousLanguages;
|
|
368
368
|
};
|
|
369
369
|
|
|
370
|
+
export const hasUbiquitousLanguageTerms = (ubiquitousLanguage: UbiquitousLanguage | null | undefined): boolean =>
|
|
371
|
+
(ubiquitousLanguage?.data?.dictionary?.length || 0) > 0;
|
|
372
|
+
|
|
373
|
+
export const getUbiquitousLanguageFromCollection = (
|
|
374
|
+
domain: Domain,
|
|
375
|
+
ubiquitousLanguages: UbiquitousLanguage[]
|
|
376
|
+
): UbiquitousLanguage[] => {
|
|
377
|
+
const domainFolder = path.dirname(domain.filePath || '');
|
|
378
|
+
|
|
379
|
+
return ubiquitousLanguages.filter((ubiquitousLanguage) => {
|
|
380
|
+
const ubiquitousLanguageFolder = path.dirname(ubiquitousLanguage.filePath || '');
|
|
381
|
+
return domainFolder === ubiquitousLanguageFolder;
|
|
382
|
+
});
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
export const hasUbiquitousLanguageTermsInCollection = (domain: Domain, ubiquitousLanguages: UbiquitousLanguage[]): boolean =>
|
|
386
|
+
getUbiquitousLanguageFromCollection(domain, ubiquitousLanguages).some(hasUbiquitousLanguageTerms);
|
|
387
|
+
|
|
388
|
+
export const hasUbiquitousLanguageTermsWithSubdomainsInCollection = (
|
|
389
|
+
domain: Domain,
|
|
390
|
+
ubiquitousLanguages: UbiquitousLanguage[]
|
|
391
|
+
): boolean => {
|
|
392
|
+
const subdomains = (domain.data.domains as unknown as Domain[]) || [];
|
|
393
|
+
|
|
394
|
+
return (
|
|
395
|
+
hasUbiquitousLanguageTermsInCollection(domain, ubiquitousLanguages) ||
|
|
396
|
+
subdomains.some((subdomain) => hasUbiquitousLanguageTermsInCollection(subdomain, ubiquitousLanguages))
|
|
397
|
+
);
|
|
398
|
+
};
|
|
399
|
+
|
|
370
400
|
export const getUbiquitousLanguageWithSubdomains = async (
|
|
371
401
|
domain: Domain
|
|
372
402
|
): Promise<{
|
|
@@ -428,6 +458,15 @@ export const getUbiquitousLanguageWithSubdomains = async (
|
|
|
428
458
|
};
|
|
429
459
|
};
|
|
430
460
|
|
|
461
|
+
export const hasUbiquitousLanguageTermsWithSubdomains = async (domain: Domain): Promise<boolean> => {
|
|
462
|
+
const { domain: domainUbiquitousLanguage, subdomains } = await getUbiquitousLanguageWithSubdomains(domain);
|
|
463
|
+
|
|
464
|
+
return (
|
|
465
|
+
hasUbiquitousLanguageTerms(domainUbiquitousLanguage) ||
|
|
466
|
+
subdomains.some(({ ubiquitousLanguage }) => hasUbiquitousLanguageTerms(ubiquitousLanguage))
|
|
467
|
+
);
|
|
468
|
+
};
|
|
469
|
+
|
|
431
470
|
export const getParentDomains = async (domain: Domain): Promise<Domain[]> => {
|
|
432
471
|
const domains = await getDomains({ getAllVersions: false });
|
|
433
472
|
return domains.filter((d) => {
|
|
@@ -2,6 +2,8 @@ import { getCollection } from 'astro:content';
|
|
|
2
2
|
import type { CollectionEntry } from 'astro:content';
|
|
3
3
|
import { createVersionedMap, satisfies } from './util';
|
|
4
4
|
|
|
5
|
+
const CACHE_ENABLED = process.env.DISABLE_EVENTCATALOG_CACHE !== 'true';
|
|
6
|
+
|
|
5
7
|
export type Entity = CollectionEntry<'entities'>;
|
|
6
8
|
|
|
7
9
|
interface Props {
|
|
@@ -15,7 +17,7 @@ export const getEntities = async ({ getAllVersions = true }: Props = {}): Promis
|
|
|
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 (memoryCache[cacheKey] && memoryCache[cacheKey].length > 0 && CACHE_ENABLED) {
|
|
19
21
|
// console.timeEnd('✅ New getEntities');
|
|
20
22
|
return memoryCache[cacheKey];
|
|
21
23
|
}
|
|
@@ -59,3 +59,12 @@ export const getAbsoluteFilePathForAstroFile = (filePath: string, fileName?: str
|
|
|
59
59
|
export const isAvroSchema = (filePath: string): boolean => {
|
|
60
60
|
return filePath.endsWith('.avro') || filePath.endsWith('.avsc');
|
|
61
61
|
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Checks if a file path is a Protocol Buffers schema based on its extension
|
|
65
|
+
* @param filePath - The file path to check
|
|
66
|
+
* @returns True if the file is a Protocol Buffers schema (.proto)
|
|
67
|
+
*/
|
|
68
|
+
export const isProtobufSchema = (filePath: string): boolean => {
|
|
69
|
+
return filePath.endsWith('.proto');
|
|
70
|
+
};
|
|
@@ -10,6 +10,18 @@ import { getServices, type Service } from '@utils/collections/services';
|
|
|
10
10
|
|
|
11
11
|
const elk = new ELK();
|
|
12
12
|
|
|
13
|
+
const getReferencedEntityId = (property: any, entityMap: Map<string, Entity[]>) => {
|
|
14
|
+
if (property.references) return property.references;
|
|
15
|
+
if (property.type === 'array' && property.items?.type && entityMap.has(property.items.type)) return property.items.type;
|
|
16
|
+
return undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const getRelationType = (property: any) => {
|
|
20
|
+
if (property.relationType) return property.relationType;
|
|
21
|
+
if (property.type === 'array' && property.items?.type) return 'hasMany';
|
|
22
|
+
return 'references';
|
|
23
|
+
};
|
|
24
|
+
|
|
13
25
|
interface Props {
|
|
14
26
|
id: string;
|
|
15
27
|
version: string;
|
|
@@ -23,6 +35,7 @@ export const getNodesAndEdges = async ({ id, version, entities, type = 'domains'
|
|
|
23
35
|
|
|
24
36
|
// 1. Fetch all collections in parallel
|
|
25
37
|
const [allDomains, allEntities, allServices] = await Promise.all([getDomains(), getEntities(), getServices()]);
|
|
38
|
+
const entityMap = createVersionedMap(allEntities);
|
|
26
39
|
|
|
27
40
|
let resource = null;
|
|
28
41
|
|
|
@@ -42,7 +55,7 @@ export const getNodesAndEdges = async ({ id, version, entities, type = 'domains'
|
|
|
42
55
|
}
|
|
43
56
|
|
|
44
57
|
const entitiesWithReferences = resourceEntities.filter((entity: Entity) =>
|
|
45
|
-
entity.data.properties?.some((property: any) => property
|
|
58
|
+
entity.data.properties?.some((property: any) => getReferencedEntityId(property, entityMap))
|
|
46
59
|
);
|
|
47
60
|
// Creates all the entity nodes for the domain
|
|
48
61
|
for (const entity of resourceEntities) {
|
|
@@ -57,7 +70,7 @@ export const getNodesAndEdges = async ({ id, version, entities, type = 'domains'
|
|
|
57
70
|
|
|
58
71
|
// Create entities that are referenced but not owned by this domain
|
|
59
72
|
const listOfReferencedEntities = entitiesWithReferences
|
|
60
|
-
.map((entity: Entity) => entity.data.properties?.map((property: any) => property
|
|
73
|
+
.map((entity: Entity) => entity.data.properties?.map((property: any) => getReferencedEntityId(property, entityMap)))
|
|
61
74
|
.flat()
|
|
62
75
|
.filter((ref: any) => ref !== undefined);
|
|
63
76
|
|
|
@@ -67,8 +80,6 @@ export const getNodesAndEdges = async ({ id, version, entities, type = 'domains'
|
|
|
67
80
|
// 2. Build optimized maps
|
|
68
81
|
// Only build domain map if we have domains to search
|
|
69
82
|
// Only build entity map if we have entities to search
|
|
70
|
-
const entityMap = createVersionedMap(allEntities);
|
|
71
|
-
|
|
72
83
|
// Helper function to find which domain an entity belongs to
|
|
73
84
|
// Optimized to use direct iteration over domains (domains usually contain entity arrays)
|
|
74
85
|
// We can't easily map entity->domain without scanning domains first unless we build a reverse index.
|
|
@@ -123,12 +134,15 @@ export const getNodesAndEdges = async ({ id, version, entities, type = 'domains'
|
|
|
123
134
|
// Go through any entities that are related to other entities
|
|
124
135
|
for (const entity of entitiesWithReferences) {
|
|
125
136
|
// Get a list of properties that reference other entities
|
|
126
|
-
const allReferencesForEntity =
|
|
137
|
+
const allReferencesForEntity =
|
|
138
|
+
entity.data.properties?.filter((property: any) => getReferencedEntityId(property, entityMap)) ?? [];
|
|
127
139
|
|
|
128
140
|
for (const referenceProperty of allReferencesForEntity) {
|
|
141
|
+
const referencedEntityId = getReferencedEntityId(referenceProperty, entityMap);
|
|
142
|
+
|
|
129
143
|
// Find the referenced entity by matching the references field with entity IDs
|
|
130
144
|
// Look in both domain entities and external entities
|
|
131
|
-
const referencedEntity = allEntitiesInGraph.find((targetEntity) => targetEntity.data.id ===
|
|
145
|
+
const referencedEntity = allEntitiesInGraph.find((targetEntity) => targetEntity.data.id === referencedEntityId);
|
|
132
146
|
|
|
133
147
|
if (referencedEntity) {
|
|
134
148
|
const sourceNodeId = generateIdForNode(entity);
|
|
@@ -164,7 +178,7 @@ export const getNodesAndEdges = async ({ id, version, entities, type = 'domains'
|
|
|
164
178
|
targetHandle: targetHandle,
|
|
165
179
|
type: 'animated',
|
|
166
180
|
animated: true,
|
|
167
|
-
label: referenceProperty
|
|
181
|
+
label: getRelationType(referenceProperty),
|
|
168
182
|
style: {
|
|
169
183
|
strokeWidth: 2,
|
|
170
184
|
strokeDasharray: '5,5', // dashed line
|
|
@@ -176,9 +190,7 @@ export const getNodesAndEdges = async ({ id, version, entities, type = 'domains'
|
|
|
176
190
|
},
|
|
177
191
|
});
|
|
178
192
|
} else {
|
|
179
|
-
console.warn(
|
|
180
|
-
`Referenced entity "${referenceProperty.references}" not found for ${entity.data.name}.${referenceProperty.name}`
|
|
181
|
-
);
|
|
193
|
+
console.warn(`Referenced entity "${referencedEntityId}" not found for ${entity.data.name}.${referenceProperty.name}`);
|
|
182
194
|
}
|
|
183
195
|
}
|
|
184
196
|
}
|
|
@@ -188,10 +200,10 @@ export const getNodesAndEdges = async ({ id, version, entities, type = 'domains'
|
|
|
188
200
|
// Separate entities with and without relationships (including external entities)
|
|
189
201
|
const entitiesWithRelationships = allEntitiesInGraph.filter((entity) => {
|
|
190
202
|
// Has outgoing references
|
|
191
|
-
const hasOutgoingRefs = entity.data.properties?.some((property: any) => property
|
|
203
|
+
const hasOutgoingRefs = entity.data.properties?.some((property: any) => getReferencedEntityId(property, entityMap));
|
|
192
204
|
// Has incoming references (is referenced by others)
|
|
193
205
|
const hasIncomingRefs = entitiesWithReferences.some((e: any) =>
|
|
194
|
-
e.data.properties?.some((prop: any) => prop
|
|
206
|
+
e.data.properties?.some((prop: any) => getReferencedEntityId(prop, entityMap) === entity.data.id)
|
|
195
207
|
);
|
|
196
208
|
return hasOutgoingRefs || hasIncomingRefs;
|
|
197
209
|
});
|