@eventcatalog/core 4.10.9 → 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-ONRJA5DX.js → chunk-K45ABG6N.js} +1 -1
- package/dist/{chunk-3EEFSHHW.js → chunk-PR77FB54.js} +1 -1
- package/dist/{chunk-5WB6CXZH.js → chunk-QXANW76J.js} +1 -1
- package/dist/{chunk-EG62NTWS.js → chunk-U52FCZQ6.js} +7 -7
- package/dist/{chunk-BYPWHDX6.js → chunk-U6R6BFJ5.js} +1 -1
- package/dist/{chunk-WD5UW4A4.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 +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/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/schemas/explorer/_index.data.ts +3 -269
- package/eventcatalog/src/pages/schemas/explorer/content/[key].json.ts +17 -0
- package/eventcatalog/src/utils/schema-explorer.ts +276 -0
- package/package.json +3 -3
- package/dist/{chunk-A2RZR3U4.js → chunk-6K7XZAYI.js} +3 -3
|
@@ -140,7 +140,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
|
|
|
140
140
|
var import_os = __toESM(require("os"), 1);
|
|
141
141
|
|
|
142
142
|
// package.json
|
|
143
|
-
var version = "4.10.
|
|
143
|
+
var version = "4.10.10";
|
|
144
144
|
|
|
145
145
|
// src/constants.ts
|
|
146
146
|
var VERSION = version;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log_build_default
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-K45ABG6N.js";
|
|
4
|
+
import "../chunk-QXANW76J.js";
|
|
5
5
|
import "../chunk-ZAZHAVKB.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-PR77FB54.js";
|
|
7
7
|
import "../chunk-6QENHZZP.js";
|
|
8
8
|
export {
|
|
9
9
|
log_build_default as default
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withFederationOutputTransaction
|
|
3
|
+
} from "./chunk-WRNH5C3U.js";
|
|
4
|
+
import {
|
|
5
|
+
composePublicAssets
|
|
6
|
+
} from "./chunk-5EPNFDT5.js";
|
|
1
7
|
import {
|
|
2
8
|
createFederationSourceProvider
|
|
3
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-6K7XZAYI.js";
|
|
4
10
|
import {
|
|
5
11
|
createFederationContentCache
|
|
6
12
|
} from "./chunk-R7Z5ALYI.js";
|
|
@@ -11,12 +17,6 @@ import {
|
|
|
11
17
|
import {
|
|
12
18
|
isFederationEnabled
|
|
13
19
|
} from "./chunk-FV56YFM4.js";
|
|
14
|
-
import {
|
|
15
|
-
withFederationOutputTransaction
|
|
16
|
-
} from "./chunk-WRNH5C3U.js";
|
|
17
|
-
import {
|
|
18
|
-
composePublicAssets
|
|
19
|
-
} from "./chunk-5EPNFDT5.js";
|
|
20
20
|
import {
|
|
21
21
|
getEventCatalogConfigFile
|
|
22
22
|
} from "./chunk-6QENHZZP.js";
|
package/dist/constants.cjs
CHANGED
package/dist/constants.js
CHANGED
package/dist/eventcatalog.cjs
CHANGED
|
@@ -144,7 +144,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
|
|
|
144
144
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
145
145
|
|
|
146
146
|
// package.json
|
|
147
|
-
var version = "4.10.
|
|
147
|
+
var version = "4.10.10";
|
|
148
148
|
|
|
149
149
|
// src/constants.ts
|
|
150
150
|
var VERSION = version;
|
package/dist/eventcatalog.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
log_build_default
|
|
3
|
-
} from "./chunk-ONRJA5DX.js";
|
|
4
|
-
import "./chunk-5WB6CXZH.js";
|
|
5
|
-
import "./chunk-ZAZHAVKB.js";
|
|
6
1
|
import {
|
|
7
2
|
runMigrations
|
|
8
3
|
} from "./chunk-XUAF2H54.js";
|
|
9
4
|
import "./chunk-CA4U2JP7.js";
|
|
5
|
+
import {
|
|
6
|
+
log_build_default
|
|
7
|
+
} from "./chunk-K45ABG6N.js";
|
|
8
|
+
import "./chunk-QXANW76J.js";
|
|
9
|
+
import "./chunk-ZAZHAVKB.js";
|
|
10
10
|
import {
|
|
11
11
|
FederationConflictError,
|
|
12
12
|
FederationDiagnosticError,
|
|
13
13
|
federateCatalog
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-U52FCZQ6.js";
|
|
15
|
+
import "./chunk-WRNH5C3U.js";
|
|
16
|
+
import "./chunk-5EPNFDT5.js";
|
|
17
|
+
import "./chunk-6K7XZAYI.js";
|
|
18
|
+
import "./chunk-352FGP6W.js";
|
|
16
19
|
import "./chunk-R7Z5ALYI.js";
|
|
17
20
|
import {
|
|
18
21
|
getFederationDiagnosticCounts,
|
|
@@ -20,9 +23,6 @@ import {
|
|
|
20
23
|
} from "./chunk-JJPB6EOZ.js";
|
|
21
24
|
import "./chunk-FV56YFM4.js";
|
|
22
25
|
import "./chunk-SDZQJTJW.js";
|
|
23
|
-
import "./chunk-352FGP6W.js";
|
|
24
|
-
import "./chunk-WRNH5C3U.js";
|
|
25
|
-
import "./chunk-5EPNFDT5.js";
|
|
26
26
|
import {
|
|
27
27
|
getProjectOutDir,
|
|
28
28
|
isAuthEnabled,
|
|
@@ -31,10 +31,10 @@ import {
|
|
|
31
31
|
} from "./chunk-B7HCX5HM.js";
|
|
32
32
|
import {
|
|
33
33
|
generate
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-Y6VX2CGL.js";
|
|
35
35
|
import {
|
|
36
36
|
logger
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-U6R6BFJ5.js";
|
|
38
38
|
import {
|
|
39
39
|
resolve_catalog_dependencies_default
|
|
40
40
|
} from "./chunk-LHR4G2UO.js";
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
import "./chunk-W5JQON7Z.js";
|
|
55
55
|
import {
|
|
56
56
|
VERSION
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-PR77FB54.js";
|
|
58
58
|
import {
|
|
59
59
|
linkCoreNodeModules,
|
|
60
60
|
resolveInstalledCoreNodeModules
|
|
@@ -2,15 +2,15 @@ import {
|
|
|
2
2
|
FederationConflictError,
|
|
3
3
|
FederationDiagnosticError,
|
|
4
4
|
federateCatalog
|
|
5
|
-
} from "../chunk-
|
|
6
|
-
import "../chunk-
|
|
5
|
+
} from "../chunk-U52FCZQ6.js";
|
|
6
|
+
import "../chunk-WRNH5C3U.js";
|
|
7
|
+
import "../chunk-5EPNFDT5.js";
|
|
8
|
+
import "../chunk-6K7XZAYI.js";
|
|
9
|
+
import "../chunk-352FGP6W.js";
|
|
7
10
|
import "../chunk-R7Z5ALYI.js";
|
|
8
11
|
import "../chunk-JJPB6EOZ.js";
|
|
9
12
|
import "../chunk-FV56YFM4.js";
|
|
10
13
|
import "../chunk-SDZQJTJW.js";
|
|
11
|
-
import "../chunk-352FGP6W.js";
|
|
12
|
-
import "../chunk-WRNH5C3U.js";
|
|
13
|
-
import "../chunk-5EPNFDT5.js";
|
|
14
14
|
import "../chunk-6QENHZZP.js";
|
|
15
15
|
export {
|
|
16
16
|
FederationConflictError,
|
package/dist/generate.cjs
CHANGED
|
@@ -108,7 +108,7 @@ var getEventCatalogConfigFile = async (projectDirectory) => {
|
|
|
108
108
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
109
109
|
|
|
110
110
|
// package.json
|
|
111
|
-
var version = "4.10.
|
|
111
|
+
var version = "4.10.10";
|
|
112
112
|
|
|
113
113
|
// src/constants.ts
|
|
114
114
|
var VERSION = version;
|
package/dist/generate.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generate
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-Y6VX2CGL.js";
|
|
4
|
+
import "./chunk-U6R6BFJ5.js";
|
|
5
|
+
import "./chunk-PR77FB54.js";
|
|
6
6
|
import "./chunk-6QENHZZP.js";
|
|
7
7
|
export {
|
|
8
8
|
generate
|
package/dist/utils/cli-logger.js
CHANGED
|
@@ -24,7 +24,8 @@ import ExamplesViewer from './ExamplesViewer';
|
|
|
24
24
|
import VersionHistoryModal from './VersionHistoryModal';
|
|
25
25
|
import SchemaCodeModal from './SchemaCodeModal';
|
|
26
26
|
import SchemaViewerModal from './SchemaViewerModal';
|
|
27
|
-
import { copyToClipboard, downloadSchema, getSchemaTypeLabel, ICON_SPECS,
|
|
27
|
+
import { copyToClipboard, downloadSchema, getSchemaTypeLabel, ICON_SPECS, getSchemaRelationshipReference } from './utils';
|
|
28
|
+
import { createSchemaDetailsLoader, useSchemaDetails } from './useSchemaDetails';
|
|
28
29
|
import { parseProtobufSchema } from '@utils/protobuf-schema';
|
|
29
30
|
import type { SchemaItem, VersionDiff, Owner, Producer, Consumer } from './types';
|
|
30
31
|
|
|
@@ -39,7 +40,7 @@ interface SchemaDetailsPanelProps {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
export default function SchemaDetailsPanel({
|
|
42
|
-
message,
|
|
43
|
+
message: metadataMessage,
|
|
43
44
|
availableVersions,
|
|
44
45
|
selectedVersion,
|
|
45
46
|
onVersionChange,
|
|
@@ -47,6 +48,9 @@ export default function SchemaDetailsPanel({
|
|
|
47
48
|
showOwners = true,
|
|
48
49
|
showProducersConsumers = true,
|
|
49
50
|
}: SchemaDetailsPanelProps) {
|
|
51
|
+
const [loadDetails] = useState(createSchemaDetailsLoader);
|
|
52
|
+
const content = useSchemaDetails(metadataMessage, loadDetails);
|
|
53
|
+
const message = content.message!;
|
|
50
54
|
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
51
55
|
const [activeTab, setActiveTab] = useState<'code' | 'schema' | 'diff' | 'api' | 'examples'>('code');
|
|
52
56
|
const [isDiffModalOpen, setIsDiffModalOpen] = useState(false);
|
|
@@ -88,18 +92,23 @@ export default function SchemaDetailsPanel({
|
|
|
88
92
|
setIsDiffModalOpen(false);
|
|
89
93
|
}, [schemaResourceKey, defaultFromVersion, defaultToVersion]);
|
|
90
94
|
|
|
91
|
-
const
|
|
95
|
+
const diffFromMetadata = useMemo(
|
|
92
96
|
() => uniqueAvailableVersions.find((version) => version.data.version === diffFromVersion),
|
|
93
97
|
[diffFromVersion, uniqueAvailableVersions]
|
|
94
98
|
);
|
|
95
|
-
const
|
|
99
|
+
const diffToMetadata = useMemo(
|
|
96
100
|
() => uniqueAvailableVersions.find((version) => version.data.version === diffToVersion),
|
|
97
101
|
[diffToVersion, uniqueAvailableVersions]
|
|
98
102
|
);
|
|
103
|
+
const comparing = activeTab === 'diff' || isDiffModalOpen;
|
|
104
|
+
const fromContent = useSchemaDetails(diffFromMetadata, loadDetails, comparing);
|
|
105
|
+
const toContent = useSchemaDetails(diffToMetadata, loadDetails, comparing);
|
|
106
|
+
const diffFromItem = fromContent.message;
|
|
107
|
+
const diffToItem = toContent.message;
|
|
99
108
|
const hasDiffFromContent = !!diffFromItem?.schemaContent?.trim();
|
|
100
109
|
const hasDiffToContent = !!diffToItem?.schemaContent?.trim();
|
|
101
110
|
const selectedDiff: VersionDiff | null = useMemo(() => {
|
|
102
|
-
if (!diffFromItem || !diffToItem) return null;
|
|
111
|
+
if (!comparing || !diffFromItem || !diffToItem) return null;
|
|
103
112
|
if (diffFromItem.data.version === diffToItem.data.version) return null;
|
|
104
113
|
if (!diffFromItem.schemaContent?.trim() || !diffToItem.schemaContent?.trim()) return null;
|
|
105
114
|
|
|
@@ -126,7 +135,7 @@ export default function SchemaDetailsPanel({
|
|
|
126
135
|
fromContent: diffFromItem.schemaContent,
|
|
127
136
|
toContent: diffToItem.schemaContent,
|
|
128
137
|
};
|
|
129
|
-
}, [diffFromItem, diffToItem]);
|
|
138
|
+
}, [comparing, diffFromItem, diffToItem]);
|
|
130
139
|
const selectedDiffs = selectedDiff ? [selectedDiff] : [];
|
|
131
140
|
|
|
132
141
|
// Check if this is a JSON schema
|
|
@@ -359,7 +368,9 @@ export default function SchemaDetailsPanel({
|
|
|
359
368
|
|
|
360
369
|
{/* Tab content */}
|
|
361
370
|
<div className="flex-1 min-h-0 overflow-hidden p-6">
|
|
362
|
-
{
|
|
371
|
+
{content.loading || content.error ? (
|
|
372
|
+
<SchemaLoadingState loading={content.loading} error={content.error} retry={content.retry} />
|
|
373
|
+
) : activeTab === 'examples' && examples.length > 0 ? (
|
|
363
374
|
<ExamplesViewer examples={examples} />
|
|
364
375
|
) : activeTab === 'api' ? (
|
|
365
376
|
<ApiContentViewer
|
|
@@ -416,7 +427,16 @@ export default function SchemaDetailsPanel({
|
|
|
416
427
|
</button>
|
|
417
428
|
</div>
|
|
418
429
|
<div className="min-h-0 flex-1 overflow-hidden">
|
|
419
|
-
{
|
|
430
|
+
{fromContent.loading || toContent.loading || fromContent.error || toContent.error ? (
|
|
431
|
+
<SchemaLoadingState
|
|
432
|
+
loading={fromContent.loading || toContent.loading}
|
|
433
|
+
error={fromContent.error || toContent.error}
|
|
434
|
+
retry={() => {
|
|
435
|
+
fromContent.retry();
|
|
436
|
+
toContent.retry();
|
|
437
|
+
}}
|
|
438
|
+
/>
|
|
439
|
+
) : diffFromVersion === diffToVersion ? (
|
|
420
440
|
<div className="flex h-full items-center justify-center text-[rgb(var(--ec-page-text-muted))]">
|
|
421
441
|
<p className="text-sm">Select two different versions</p>
|
|
422
442
|
</div>
|
|
@@ -525,11 +545,11 @@ export default function SchemaDetailsPanel({
|
|
|
525
545
|
</div>
|
|
526
546
|
<div className="space-y-1">
|
|
527
547
|
{producers.map((producer: Producer, idx: number) => {
|
|
528
|
-
const serviceName =
|
|
548
|
+
const { id: serviceName, version } = getSchemaRelationshipReference(producer);
|
|
529
549
|
return (
|
|
530
550
|
<a
|
|
531
551
|
key={`${producer.id}-${idx}`}
|
|
532
|
-
href={buildUrl(`/docs/services/${serviceName}/${
|
|
552
|
+
href={buildUrl(`/docs/services/${serviceName}/${version}`)}
|
|
533
553
|
className="flex items-center gap-2 rounded-lg px-2 py-1.5 text-xs font-medium text-[rgb(var(--ec-page-text-muted))] transition-colors hover:bg-[rgb(var(--ec-page-bg)/0.55)] hover:text-[rgb(var(--ec-accent))]"
|
|
534
554
|
>
|
|
535
555
|
<ServerIcon className="h-3.5 w-3.5 flex-shrink-0 text-[rgb(var(--ec-page-text-muted))]" />
|
|
@@ -551,11 +571,11 @@ export default function SchemaDetailsPanel({
|
|
|
551
571
|
</div>
|
|
552
572
|
<div className="space-y-1">
|
|
553
573
|
{consumers.map((consumer: Consumer, idx: number) => {
|
|
554
|
-
const serviceName =
|
|
574
|
+
const { id: serviceName, version } = getSchemaRelationshipReference(consumer);
|
|
555
575
|
return (
|
|
556
576
|
<a
|
|
557
577
|
key={`${consumer.id}-${idx}`}
|
|
558
|
-
href={buildUrl(`/docs/services/${serviceName}/${
|
|
578
|
+
href={buildUrl(`/docs/services/${serviceName}/${version}`)}
|
|
559
579
|
className="flex items-center gap-2 rounded-lg px-2 py-1.5 text-xs font-medium text-[rgb(var(--ec-page-text-muted))] transition-colors hover:bg-[rgb(var(--ec-page-bg)/0.55)] hover:text-[rgb(var(--ec-accent))]"
|
|
560
580
|
>
|
|
561
581
|
<ServerIcon className="h-3.5 w-3.5 flex-shrink-0 text-[rgb(var(--ec-page-text-muted))]" />
|
|
@@ -615,3 +635,29 @@ export default function SchemaDetailsPanel({
|
|
|
615
635
|
</div>
|
|
616
636
|
);
|
|
617
637
|
}
|
|
638
|
+
|
|
639
|
+
function SchemaLoadingState({ loading, error, retry }: { loading: boolean; error?: string; retry: () => void }) {
|
|
640
|
+
if (!error) {
|
|
641
|
+
if (!loading) return null;
|
|
642
|
+
return (
|
|
643
|
+
<div role="status" className="flex h-full items-center justify-center gap-2 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
644
|
+
<span
|
|
645
|
+
aria-hidden="true"
|
|
646
|
+
className="h-4 w-4 rounded-full border-2 border-[rgb(var(--ec-page-border))] border-t-[rgb(var(--ec-page-text-muted))] motion-safe:animate-spin"
|
|
647
|
+
/>
|
|
648
|
+
<span>Loading schema…</span>
|
|
649
|
+
</div>
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return (
|
|
654
|
+
<div className="flex h-full flex-col items-center justify-center gap-3 text-[rgb(var(--ec-page-text-muted))]" role="alert">
|
|
655
|
+
<p>{error}</p>
|
|
656
|
+
{error && (
|
|
657
|
+
<button type="button" onClick={retry} className="text-[rgb(var(--ec-accent))] hover:underline">
|
|
658
|
+
Try again
|
|
659
|
+
</button>
|
|
660
|
+
)}
|
|
661
|
+
</div>
|
|
662
|
+
);
|
|
663
|
+
}
|
|
@@ -36,10 +36,10 @@ function getSpecFile(item: SchemaItem): string {
|
|
|
36
36
|
* multiple specs of the same type (e.g. two OpenAPI specs).
|
|
37
37
|
*/
|
|
38
38
|
function getGroupKey(item: SchemaItem): string {
|
|
39
|
-
if (item.collection === 'services') {
|
|
40
|
-
return `${item.data.id}__${item.specType || 'unknown'}__${getSpecFile(item)}`;
|
|
39
|
+
if (item.collection === 'services' || item.collection === 'domains') {
|
|
40
|
+
return `${item.collection}__${item.data.id}__${item.specType || 'unknown'}__${getSpecFile(item)}`;
|
|
41
41
|
}
|
|
42
|
-
return item.data.id
|
|
42
|
+
return `${item.collection}__${item.data.id}`;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
interface SchemaExplorerProps {
|
|
@@ -110,7 +110,7 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
|
|
|
110
110
|
params.set('version', message.data.version);
|
|
111
111
|
params.set('collection', message.collection);
|
|
112
112
|
|
|
113
|
-
if (message.collection === 'services') {
|
|
113
|
+
if (message.collection === 'services' || message.collection === 'domains') {
|
|
114
114
|
params.set('specType', message.specType || 'unknown');
|
|
115
115
|
const specFile = getSpecFile(message);
|
|
116
116
|
if (specFile) {
|
|
@@ -242,7 +242,7 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
|
|
|
242
242
|
const versionMatch = msg.data.version === version;
|
|
243
243
|
const collectionMatch = !collection || msg.collection === collection;
|
|
244
244
|
|
|
245
|
-
if (msg.collection === 'services') {
|
|
245
|
+
if (msg.collection === 'services' || msg.collection === 'domains') {
|
|
246
246
|
const specTypeMatch = !specType || msg.specType === specType;
|
|
247
247
|
const msgSpecFile = getSpecFile(msg);
|
|
248
248
|
const specFilenameMatch = !specFilename || msgSpecFile === specFilename;
|
|
@@ -11,6 +11,8 @@ interface SchemaListItemProps {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
function getNamespace(message: SchemaItem): string | null {
|
|
14
|
+
if (message.data.producerName) return message.data.producerName;
|
|
15
|
+
|
|
14
16
|
const producers = message.data.producers || [];
|
|
15
17
|
const firstProducer = producers[0];
|
|
16
18
|
|
|
@@ -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();
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import type { SchemaItem, SchemaDetails, Producer, Consumer } from '@components/SchemaExplorer/types';
|
|
3
|
+
import { getEvents } from '@utils/collections/events';
|
|
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 buildRegistry() {
|
|
32
|
+
// Fetch all messages
|
|
33
|
+
const events = await getEvents({ getAllVersions: true, hydrateServices: false });
|
|
34
|
+
const commands = await getCommands({ getAllVersions: true, hydrateServices: false });
|
|
35
|
+
const queries = await getQueries({ getAllVersions: true, hydrateServices: false });
|
|
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
|
+
// The collection types describe raw content references. With
|
|
56
|
+
// hydrateServices: false, the loaders return compact { id, version } pairs.
|
|
57
|
+
const producers = (message?.data.producers || []) as unknown as Producer[];
|
|
58
|
+
const consumers = (message?.data.consumers || []) as unknown as Consumer[];
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
collection: schema.data.message.collectionName,
|
|
62
|
+
data: {
|
|
63
|
+
id: schema.data.message.id,
|
|
64
|
+
name: schema.data.message.name || message?.data.name || schema.data.name || schema.data.message.id,
|
|
65
|
+
version: schema.data.message.version,
|
|
66
|
+
summary: schema.data.message.summary || message?.data.summary,
|
|
67
|
+
schemaPath,
|
|
68
|
+
owners: await enrichOwners(schema.data.message.owners || []),
|
|
69
|
+
// The list shows only the first producer's label; keep this bounded
|
|
70
|
+
// regardless of how many resources reference the message.
|
|
71
|
+
producerName: producers[0]?.id,
|
|
72
|
+
},
|
|
73
|
+
loadDetails: () => {
|
|
74
|
+
let examples: SchemaDetails['examples'] = [];
|
|
75
|
+
if (message) {
|
|
76
|
+
try {
|
|
77
|
+
examples = getExamplesForResource(message);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(`Error reading examples for ${message.data.id}:`, error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
schemaContent: schema.data.content || '',
|
|
84
|
+
examples,
|
|
85
|
+
data: { producers, consumers },
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
schemaExtension,
|
|
89
|
+
};
|
|
90
|
+
})
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Filter services with specifications and read spec content - only keep essential data
|
|
94
|
+
const servicesWithSpecs = await Promise.all(
|
|
95
|
+
services.map(async (service) => {
|
|
96
|
+
try {
|
|
97
|
+
const specifications = getSpecificationsForService(service);
|
|
98
|
+
|
|
99
|
+
if (specifications.length === 0) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return await Promise.all(
|
|
104
|
+
specifications.map(async (spec) => {
|
|
105
|
+
if (!resourceFileExists(service, spec.path)) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const schemaExtension = spec.type;
|
|
110
|
+
const enrichedOwners = await enrichOwners(service.data.owners || []);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
collection: 'services',
|
|
114
|
+
data: {
|
|
115
|
+
id: `${service.data.id}`,
|
|
116
|
+
name: `${service.data.name} - ${spec.name}`,
|
|
117
|
+
version: service.data.version,
|
|
118
|
+
summary: service.data.summary,
|
|
119
|
+
schemaPath: spec.path,
|
|
120
|
+
owners: enrichedOwners,
|
|
121
|
+
},
|
|
122
|
+
loadDetails: () => ({ schemaContent: readResourceFile(service, spec.path) ?? '', examples: [] }),
|
|
123
|
+
schemaExtension,
|
|
124
|
+
specType: spec.type,
|
|
125
|
+
specName: spec.name,
|
|
126
|
+
specFilenameWithoutExtension: spec.filenameWithoutExtension,
|
|
127
|
+
};
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(`Error reading specifications for service ${service.data.id}:`, error);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Flatten and filter out null values
|
|
138
|
+
const flatServicesWithSpecs = servicesWithSpecs.flat().filter((service) => service !== null);
|
|
139
|
+
|
|
140
|
+
// Fetch all domains
|
|
141
|
+
const domains = await getDomains({ getAllVersions: true });
|
|
142
|
+
|
|
143
|
+
// Filter domains with specifications and read spec content - only keep essential data
|
|
144
|
+
const domainsWithSpecs = await Promise.all(
|
|
145
|
+
domains.map(async (domain) => {
|
|
146
|
+
try {
|
|
147
|
+
const specifications = getSpecificationsForDomain(domain);
|
|
148
|
+
|
|
149
|
+
if (specifications.length === 0) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return await Promise.all(
|
|
154
|
+
specifications.map(async (spec) => {
|
|
155
|
+
if (!resourceFileExists(domain, spec.path)) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const schemaExtension = spec.type;
|
|
160
|
+
const enrichedOwners = await enrichOwners(domain.data.owners || []);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
collection: 'domains',
|
|
164
|
+
data: {
|
|
165
|
+
id: `${domain.data.id}`,
|
|
166
|
+
name: `${domain.data.name} - ${spec.name}`,
|
|
167
|
+
version: domain.data.version,
|
|
168
|
+
summary: domain.data.summary,
|
|
169
|
+
schemaPath: spec.path,
|
|
170
|
+
owners: enrichedOwners,
|
|
171
|
+
},
|
|
172
|
+
loadDetails: () => ({ schemaContent: readResourceFile(domain, spec.path) ?? '', examples: [] }),
|
|
173
|
+
schemaExtension,
|
|
174
|
+
specType: spec.type,
|
|
175
|
+
specName: spec.name,
|
|
176
|
+
specFilenameWithoutExtension: spec.filenameWithoutExtension,
|
|
177
|
+
};
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error(`Error reading specifications for domain ${domain.data.id}:`, error);
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Flatten and filter out null values for domains
|
|
188
|
+
const flatDomainsWithSpecs = domainsWithSpecs.flat().filter((domain) => domain !== null);
|
|
189
|
+
|
|
190
|
+
// Fetch all data products and extract contracts from outputs
|
|
191
|
+
const dataProducts = await getDataProducts({ getAllVersions: true });
|
|
192
|
+
|
|
193
|
+
// Filter data products with contracts in outputs and read contract content
|
|
194
|
+
const dataProductsWithContracts = await Promise.all(
|
|
195
|
+
dataProducts.map(async (dataProduct) => {
|
|
196
|
+
try {
|
|
197
|
+
const outputs = dataProduct.data.outputs || [];
|
|
198
|
+
const outputsWithContracts = outputs.filter((output) => output.contract);
|
|
199
|
+
|
|
200
|
+
if (outputsWithContracts.length === 0) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return await Promise.all(
|
|
205
|
+
outputsWithContracts.map(async (output) => {
|
|
206
|
+
const contract = output.contract!;
|
|
207
|
+
if (!resourceFileExists(dataProduct, contract.path)) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const schemaExtension = path.extname(contract.path).slice(1) || 'json';
|
|
212
|
+
const enrichedOwners = await enrichOwners(dataProduct.data.owners || []);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
collection: 'data-products',
|
|
216
|
+
data: {
|
|
217
|
+
id: `${dataProduct.data.id}__${contract.path}`,
|
|
218
|
+
name: contract.name,
|
|
219
|
+
version: dataProduct.data.version,
|
|
220
|
+
summary: `Data contract for ${dataProduct.data.name}`,
|
|
221
|
+
schemaPath: contract.path,
|
|
222
|
+
owners: enrichedOwners,
|
|
223
|
+
},
|
|
224
|
+
loadDetails: () => ({ schemaContent: readResourceFile(dataProduct, contract.path) ?? '', examples: [] }),
|
|
225
|
+
schemaExtension,
|
|
226
|
+
contractType: contract.type,
|
|
227
|
+
dataProductId: dataProduct.data.id,
|
|
228
|
+
dataProductVersion: dataProduct.data.version,
|
|
229
|
+
};
|
|
230
|
+
})
|
|
231
|
+
);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error(`Error reading contracts for data product ${dataProduct.data.id}:`, error);
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// Flatten and filter out null values for data product contracts
|
|
240
|
+
const flatDataProductContracts = dataProductsWithContracts.flat().filter((contract) => contract !== null);
|
|
241
|
+
|
|
242
|
+
return new Map(
|
|
243
|
+
[...messagesWithSchemas, ...flatServicesWithSpecs, ...flatDomainsWithSpecs, ...flatDataProductContracts].map((entry) => {
|
|
244
|
+
const { loadDetails, ...item } = entry;
|
|
245
|
+
const key = createHash('sha256')
|
|
246
|
+
.update(
|
|
247
|
+
JSON.stringify([
|
|
248
|
+
item.collection,
|
|
249
|
+
item.data.id,
|
|
250
|
+
item.data.version,
|
|
251
|
+
item.data.schemaPath,
|
|
252
|
+
'specType' in item ? item.specType : '',
|
|
253
|
+
])
|
|
254
|
+
)
|
|
255
|
+
.digest('hex');
|
|
256
|
+
return [
|
|
257
|
+
key,
|
|
258
|
+
{ item: { ...item, contentUrl: buildUrl(`/schemas/explorer/content/${key}.json`, true) } as SchemaItem, loadDetails },
|
|
259
|
+
];
|
|
260
|
+
})
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Share metadata during production rendering; dev rebuilds it to reflect content edits.
|
|
265
|
+
let registry: ReturnType<typeof buildRegistry> | undefined;
|
|
266
|
+
export const getSchemaRegistry = () => {
|
|
267
|
+
if (import.meta.env.DEV || process.env.DISABLE_EVENTCATALOG_CACHE === 'true') return buildRegistry();
|
|
268
|
+
return (registry ??= buildRegistry().catch((error) => {
|
|
269
|
+
registry = undefined;
|
|
270
|
+
throw error;
|
|
271
|
+
}));
|
|
272
|
+
};
|
|
273
|
+
export const getSchemaMetadata = async (): Promise<SchemaItem[]> =>
|
|
274
|
+
[...(await getSchemaRegistry()).values()].map(({ item }) => item);
|
|
275
|
+
export const getSchemaDetails = async (key: string): Promise<SchemaDetails | undefined> =>
|
|
276
|
+
(await getSchemaRegistry()).get(key)?.loadDetails();
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"license": "SEE LICENSE IN LICENSE",
|
|
9
9
|
"type": "module",
|
|
10
|
-
"version": "4.10.
|
|
10
|
+
"version": "4.10.10",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
@@ -118,9 +118,9 @@
|
|
|
118
118
|
"update-notifier": "^7.3.1",
|
|
119
119
|
"uuid": "^11.1.1",
|
|
120
120
|
"zod": "^4.3.6",
|
|
121
|
-
"@eventcatalog/
|
|
121
|
+
"@eventcatalog/sdk": "2.29.0",
|
|
122
122
|
"@eventcatalog/visualiser": "^4.1.4",
|
|
123
|
-
"@eventcatalog/
|
|
123
|
+
"@eventcatalog/linter": "1.1.18"
|
|
124
124
|
},
|
|
125
125
|
"devDependencies": {
|
|
126
126
|
"@astrojs/check": "^0.9.10",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createFileSystemSourceProvider
|
|
3
|
-
} from "./chunk-SDZQJTJW.js";
|
|
4
1
|
import {
|
|
5
2
|
createGitHubSourceProvider
|
|
6
3
|
} from "./chunk-352FGP6W.js";
|
|
4
|
+
import {
|
|
5
|
+
createFileSystemSourceProvider
|
|
6
|
+
} from "./chunk-SDZQJTJW.js";
|
|
7
7
|
|
|
8
8
|
// src/federation/source-provider.ts
|
|
9
9
|
var createFederationSourceProvider = (projectDirectory, providers = {}) => {
|