@eventcatalog/core 3.7.2 → 3.8.1
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-M7EPRGHR.js → chunk-4BEERWPE.js} +1 -1
- package/dist/{chunk-GQZVIS3Z.js → chunk-NV7H3356.js} +1 -1
- package/dist/{chunk-7CTNGTBB.js → chunk-POQENB7N.js} +1 -1
- package/dist/{chunk-WAX3S32H.js → chunk-SL2YYN6D.js} +1 -1
- package/dist/{chunk-O6SRHGZ7.js → chunk-ZBC6HM3V.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/ChatPanel/ChatPanel.tsx +13 -1
- package/eventcatalog/src/components/Grids/DomainGrid.tsx +109 -6
- package/eventcatalog/src/components/Grids/utils.tsx +10 -1
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +2 -0
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +4 -0
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/DataProduct.tsx +132 -0
- package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +29 -2
- package/eventcatalog/src/components/SchemaExplorer/types.ts +5 -1
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +3 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +1 -0
- package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +23 -1
- package/eventcatalog/src/components/Tables/Discover/columns.tsx +62 -0
- package/eventcatalog/src/content.config.ts +34 -0
- package/eventcatalog/src/enterprise/ai/chat-api.ts +26 -0
- package/eventcatalog/src/enterprise/custom-documentation/utils/custom-docs.ts +1 -1
- package/eventcatalog/src/enterprise/tools/catalog-tools.ts +169 -2
- package/eventcatalog/src/pages/discover/[type]/_index.data.ts +5 -1
- package/eventcatalog/src/pages/discover/[type]/index.astro +57 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +5 -1
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/_index.data.ts +27 -3
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +74 -25
- package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +55 -1
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +10 -1
- package/eventcatalog/src/stores/sidebar-store/builders/container.ts +23 -16
- package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +130 -0
- package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +11 -0
- package/eventcatalog/src/stores/sidebar-store/state.ts +68 -13
- package/eventcatalog/src/styles/theme.css +4 -0
- package/eventcatalog/src/styles/themes/forest.css +4 -0
- package/eventcatalog/src/styles/themes/ocean.css +4 -0
- package/eventcatalog/src/styles/themes/sapphire.css +4 -0
- package/eventcatalog/src/styles/themes/sunset.css +4 -0
- package/eventcatalog/src/types/index.ts +4 -2
- package/eventcatalog/src/utils/collections/commands.ts +11 -29
- package/eventcatalog/src/utils/collections/containers.ts +25 -1
- package/eventcatalog/src/utils/collections/data-products.ts +85 -0
- package/eventcatalog/src/utils/collections/domains.ts +28 -10
- package/eventcatalog/src/utils/collections/events.ts +11 -29
- package/eventcatalog/src/utils/collections/icons.ts +5 -0
- package/eventcatalog/src/utils/collections/messages.ts +68 -0
- package/eventcatalog/src/utils/collections/queries.ts +11 -29
- package/eventcatalog/src/utils/collections/util.ts +11 -2
- package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +91 -3
- package/eventcatalog/src/utils/node-graphs/data-products-node-graph.ts +225 -0
- package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +28 -2
- package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +74 -20
- package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +2 -0
- package/package.json +2 -2
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ChatBubbleLeftIcon,
|
|
6
6
|
MagnifyingGlassIcon as MagnifyingGlassSolidIcon,
|
|
7
7
|
CodeBracketIcon,
|
|
8
|
+
DocumentCheckIcon,
|
|
8
9
|
} from '@heroicons/react/24/solid';
|
|
9
10
|
import type { CollectionMessageTypes } from '@types';
|
|
10
11
|
|
|
@@ -30,7 +31,7 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
|
|
|
30
31
|
}
|
|
31
32
|
return '';
|
|
32
33
|
});
|
|
33
|
-
const [selectedTypes, setSelectedTypes] = useState<Set<CollectionMessageTypes | 'specifications'>>(() => {
|
|
34
|
+
const [selectedTypes, setSelectedTypes] = useState<Set<CollectionMessageTypes | 'specifications' | 'data-contracts'>>(() => {
|
|
34
35
|
// Load from localStorage
|
|
35
36
|
if (typeof window !== 'undefined') {
|
|
36
37
|
const stored = localStorage.getItem('schemaRegistrySelectedTypes');
|
|
@@ -151,6 +152,10 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
|
|
|
151
152
|
if (selectedTypes.has('specifications') && SPEC_TYPES.includes(msg.schemaExtension?.toLowerCase() || '')) {
|
|
152
153
|
return true;
|
|
153
154
|
}
|
|
155
|
+
// Check if 'data-contracts' is selected and this is a data product contract
|
|
156
|
+
if (selectedTypes.has('data-contracts') && msg.collection === 'data-products') {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
154
159
|
return false;
|
|
155
160
|
});
|
|
156
161
|
}
|
|
@@ -325,11 +330,12 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
|
|
|
325
330
|
commands: latestMessages.filter((m) => m.collection === 'commands').length,
|
|
326
331
|
queries: latestMessages.filter((m) => m.collection === 'queries').length,
|
|
327
332
|
specifications: latestMessages.filter((m) => SPEC_TYPES.includes(m.schemaExtension?.toLowerCase() || '')).length,
|
|
333
|
+
dataContracts: latestMessages.filter((m) => m.collection === 'data-products').length,
|
|
328
334
|
};
|
|
329
335
|
}, [latestMessages]);
|
|
330
336
|
|
|
331
337
|
// Toggle type selection (multi-select)
|
|
332
|
-
const toggleType = (type: CollectionMessageTypes | 'specifications') => {
|
|
338
|
+
const toggleType = (type: CollectionMessageTypes | 'specifications' | 'data-contracts') => {
|
|
333
339
|
setSelectedTypes((prev) => {
|
|
334
340
|
const next = new Set(prev);
|
|
335
341
|
if (next.has(type)) {
|
|
@@ -485,6 +491,27 @@ export default function SchemaExplorer({ schemas, apiAccessEnabled = false }: Sc
|
|
|
485
491
|
</span>
|
|
486
492
|
</button>
|
|
487
493
|
)}
|
|
494
|
+
{stats.dataContracts > 0 && (
|
|
495
|
+
<button
|
|
496
|
+
onClick={() => toggleType('data-contracts')}
|
|
497
|
+
className={`inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium transition-all border ${
|
|
498
|
+
selectedTypes.has('data-contracts')
|
|
499
|
+
? 'bg-purple-50 dark:bg-purple-500/10 text-purple-700 dark:text-purple-300 border-purple-200 dark:border-purple-500/30'
|
|
500
|
+
: 'text-[rgb(var(--ec-page-text-muted))] border-[rgb(var(--ec-page-border))] hover:bg-[rgb(var(--ec-content-hover))]'
|
|
501
|
+
}`}
|
|
502
|
+
title="Data Contracts from Data Products"
|
|
503
|
+
>
|
|
504
|
+
<DocumentCheckIcon
|
|
505
|
+
className={`h-3.5 w-3.5 ${selectedTypes.has('data-contracts') ? 'text-purple-500' : 'text-purple-400'}`}
|
|
506
|
+
/>
|
|
507
|
+
<span>Data Contracts</span>
|
|
508
|
+
<span
|
|
509
|
+
className={`tabular-nums ${selectedTypes.has('data-contracts') ? 'text-purple-500' : 'text-[rgb(var(--ec-icon-color))]'}`}
|
|
510
|
+
>
|
|
511
|
+
{stats.dataContracts}
|
|
512
|
+
</span>
|
|
513
|
+
</button>
|
|
514
|
+
)}
|
|
488
515
|
</div>
|
|
489
516
|
</div>
|
|
490
517
|
|
|
@@ -18,7 +18,7 @@ export interface Owner {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export interface SchemaItem {
|
|
21
|
-
collection: CollectionMessageTypes | 'services';
|
|
21
|
+
collection: CollectionMessageTypes | 'services' | 'data-products';
|
|
22
22
|
data: {
|
|
23
23
|
id: string;
|
|
24
24
|
name: string;
|
|
@@ -34,6 +34,10 @@ export interface SchemaItem {
|
|
|
34
34
|
specType?: string;
|
|
35
35
|
specName?: string;
|
|
36
36
|
specFilenameWithoutExtension?: string;
|
|
37
|
+
// For data contracts
|
|
38
|
+
contractType?: string;
|
|
39
|
+
dataProductId?: string;
|
|
40
|
+
dataProductVersion?: string;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
export interface VersionDiff {
|
|
@@ -222,6 +222,9 @@ export default function NestedSideBar() {
|
|
|
222
222
|
// Flows
|
|
223
223
|
{ pattern: /^\/docs\/flows\/([^/]+)\/([^/]+)/, type: 'flow' },
|
|
224
224
|
{ pattern: /^\/visualiser\/flows\/([^/]+)\/([^/]+)/, type: 'flow' },
|
|
225
|
+
// Data Products
|
|
226
|
+
{ pattern: /^\/docs\/data-products\/([^/]+)\/([^/]+)/, type: 'data-product' },
|
|
227
|
+
{ pattern: /^\/visualiser\/data-products\/([^/]+)\/([^/]+)/, type: 'data-product' },
|
|
225
228
|
];
|
|
226
229
|
|
|
227
230
|
// URL patterns without version (language pages, etc)
|
|
@@ -14,6 +14,7 @@ export const getBadgeClasses = (badge: string): string => {
|
|
|
14
14
|
message: 'bg-[rgb(var(--ec-badge-message-bg))] text-[rgb(var(--ec-badge-message-text))]',
|
|
15
15
|
design: 'bg-[rgb(var(--ec-badge-design-bg))] text-[rgb(var(--ec-badge-design-text))]',
|
|
16
16
|
channel: 'bg-[rgb(var(--ec-badge-channel-bg))] text-[rgb(var(--ec-badge-channel-text))]',
|
|
17
|
+
'data product': 'bg-[rgb(var(--ec-badge-data-product-bg))] text-[rgb(var(--ec-badge-data-product-text))]',
|
|
17
18
|
};
|
|
18
19
|
return badgeColors[badge.toLowerCase()] || 'bg-[rgb(var(--ec-badge-default-bg))] text-[rgb(var(--ec-badge-default-text))]';
|
|
19
20
|
};
|
|
@@ -18,7 +18,15 @@ import { FilterDropdown, CheckboxItem } from './FilterComponents';
|
|
|
18
18
|
import DebouncedInput from '../DebouncedInput';
|
|
19
19
|
import { getDiscoverColumns } from './columns';
|
|
20
20
|
|
|
21
|
-
export type CollectionType =
|
|
21
|
+
export type CollectionType =
|
|
22
|
+
| 'events'
|
|
23
|
+
| 'commands'
|
|
24
|
+
| 'queries'
|
|
25
|
+
| 'services'
|
|
26
|
+
| 'domains'
|
|
27
|
+
| 'flows'
|
|
28
|
+
| 'containers'
|
|
29
|
+
| 'data-products';
|
|
22
30
|
|
|
23
31
|
export interface DiscoverTableData {
|
|
24
32
|
collection: string;
|
|
@@ -29,6 +37,8 @@ export interface DiscoverTableData {
|
|
|
29
37
|
hasRepository?: boolean;
|
|
30
38
|
isDeprecated?: boolean;
|
|
31
39
|
hasDataDependencies?: boolean;
|
|
40
|
+
hasInputs?: boolean;
|
|
41
|
+
hasOutputs?: boolean;
|
|
32
42
|
data: {
|
|
33
43
|
id: string;
|
|
34
44
|
name: string;
|
|
@@ -49,6 +59,8 @@ export interface DiscoverTableData {
|
|
|
49
59
|
services?: Array<any>;
|
|
50
60
|
servicesThatWriteToContainer?: Array<any>;
|
|
51
61
|
servicesThatReadFromContainer?: Array<any>;
|
|
62
|
+
inputs?: Array<any>;
|
|
63
|
+
outputs?: Array<any>;
|
|
52
64
|
};
|
|
53
65
|
}
|
|
54
66
|
|
|
@@ -232,6 +244,16 @@ export function DiscoverTable<T extends DiscoverTableData>({
|
|
|
232
244
|
const readers = row.data.servicesThatReadFromContainer || [];
|
|
233
245
|
if (readers.length === 0) return false;
|
|
234
246
|
}
|
|
247
|
+
|
|
248
|
+
// Data-product-specific checks
|
|
249
|
+
if (prop === 'hasInputs') {
|
|
250
|
+
const inputs = row.data.inputs || [];
|
|
251
|
+
if (inputs.length === 0) return false;
|
|
252
|
+
}
|
|
253
|
+
if (prop === 'hasOutputs') {
|
|
254
|
+
const outputs = row.data.outputs || [];
|
|
255
|
+
if (outputs.length === 0) return false;
|
|
256
|
+
}
|
|
235
257
|
}
|
|
236
258
|
}
|
|
237
259
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
QueueListIcon,
|
|
10
10
|
DocumentTextIcon,
|
|
11
11
|
MapIcon,
|
|
12
|
+
CubeIcon,
|
|
12
13
|
} from '@heroicons/react/24/solid';
|
|
13
14
|
import { ArrowDownIcon, ArrowUpIcon } from '@heroicons/react/24/outline';
|
|
14
15
|
import { DatabaseIcon } from 'lucide-react';
|
|
@@ -30,6 +31,7 @@ const colorClasses: Record<string, string> = {
|
|
|
30
31
|
purple: 'text-purple-500',
|
|
31
32
|
red: 'text-red-500',
|
|
32
33
|
gray: 'text-gray-500',
|
|
34
|
+
cyan: 'text-cyan-500',
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
// Reusable tooltip wrapper component
|
|
@@ -540,6 +542,64 @@ export const getContainerColumns = (tableConfiguration: TableConfiguration) => [
|
|
|
540
542
|
createActionsColumn('containers', tableConfiguration),
|
|
541
543
|
];
|
|
542
544
|
|
|
545
|
+
// ============================================================================
|
|
546
|
+
// DATA PRODUCT COLUMNS
|
|
547
|
+
// ============================================================================
|
|
548
|
+
export const getDataProductColumns = (tableConfiguration: TableConfiguration) => [
|
|
549
|
+
columnHelper.accessor('data.name', {
|
|
550
|
+
id: 'name',
|
|
551
|
+
header: () => <span>{tableConfiguration?.columns?.name?.label || 'Data Product'}</span>,
|
|
552
|
+
cell: (info) => {
|
|
553
|
+
const item = info.row.original;
|
|
554
|
+
const isLatestVersion = item.data.version === item.data.latestVersion;
|
|
555
|
+
return (
|
|
556
|
+
<a
|
|
557
|
+
href={buildUrl(`/docs/${item.collection}/${item.data.id}/${item.data.version}`)}
|
|
558
|
+
className="group inline-flex items-center gap-2 hover:text-[rgb(var(--ec-accent))] transition-colors"
|
|
559
|
+
>
|
|
560
|
+
<CubeIcon className="h-4 w-4 text-cyan-500 flex-shrink-0" />
|
|
561
|
+
<span className="text-sm font-semibold text-[rgb(var(--ec-page-text))] group-hover:text-[rgb(var(--ec-accent))]">
|
|
562
|
+
{item.data.name}
|
|
563
|
+
</span>
|
|
564
|
+
{!isLatestVersion && <span className="text-xs text-[rgb(var(--ec-icon-color))]">v{item.data.version}</span>}
|
|
565
|
+
</a>
|
|
566
|
+
);
|
|
567
|
+
},
|
|
568
|
+
meta: {
|
|
569
|
+
filterVariant: 'name',
|
|
570
|
+
},
|
|
571
|
+
}),
|
|
572
|
+
createSummaryColumn(tableConfiguration),
|
|
573
|
+
columnHelper.accessor('data.inputs', {
|
|
574
|
+
id: 'inputs',
|
|
575
|
+
header: () => (
|
|
576
|
+
<span className="flex items-center gap-1">
|
|
577
|
+
<ArrowDownIcon className="w-3.5 h-3.5" />
|
|
578
|
+
Inputs
|
|
579
|
+
</span>
|
|
580
|
+
),
|
|
581
|
+
cell: (info) => <CollectionListCell items={info.getValue()} />,
|
|
582
|
+
meta: {
|
|
583
|
+
showFilter: false,
|
|
584
|
+
},
|
|
585
|
+
}),
|
|
586
|
+
columnHelper.accessor('data.outputs', {
|
|
587
|
+
id: 'outputs',
|
|
588
|
+
header: () => (
|
|
589
|
+
<span className="flex items-center gap-1">
|
|
590
|
+
<ArrowUpIcon className="w-3.5 h-3.5" />
|
|
591
|
+
Outputs
|
|
592
|
+
</span>
|
|
593
|
+
),
|
|
594
|
+
cell: (info) => <CollectionListCell items={info.getValue()} />,
|
|
595
|
+
meta: {
|
|
596
|
+
showFilter: false,
|
|
597
|
+
},
|
|
598
|
+
}),
|
|
599
|
+
createBadgesColumn(tableConfiguration),
|
|
600
|
+
createActionsColumn('data-products', tableConfiguration),
|
|
601
|
+
];
|
|
602
|
+
|
|
543
603
|
// ============================================================================
|
|
544
604
|
// COLUMN GETTER BY COLLECTION TYPE
|
|
545
605
|
// ============================================================================
|
|
@@ -559,6 +619,8 @@ export const getDiscoverColumns = (collectionType: CollectionType, tableConfigur
|
|
|
559
619
|
return getFlowColumns(tableConfiguration);
|
|
560
620
|
case 'containers':
|
|
561
621
|
return getContainerColumns(tableConfiguration);
|
|
622
|
+
case 'data-products':
|
|
623
|
+
return getDataProductColumns(tableConfiguration);
|
|
562
624
|
default:
|
|
563
625
|
return [];
|
|
564
626
|
}
|
|
@@ -362,6 +362,34 @@ const queries = defineCollection({
|
|
|
362
362
|
.merge(baseSchema),
|
|
363
363
|
});
|
|
364
364
|
|
|
365
|
+
const dataProductOutputPointer = z.object({
|
|
366
|
+
id: z.string(),
|
|
367
|
+
version: z.string().optional().default('latest'),
|
|
368
|
+
contract: z
|
|
369
|
+
.object({
|
|
370
|
+
path: z.string(),
|
|
371
|
+
name: z.string(),
|
|
372
|
+
type: z.string().optional(),
|
|
373
|
+
})
|
|
374
|
+
.optional(),
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const dataProducts = defineCollection({
|
|
378
|
+
loader: glob({
|
|
379
|
+
pattern: ['**/data-products/*/index.(md|mdx)', '**/data-products/*/versioned/*/index.(md|mdx)'],
|
|
380
|
+
base: projectDirBase,
|
|
381
|
+
generateId: ({ data }) => {
|
|
382
|
+
return `${data.id}-${data.version}`;
|
|
383
|
+
},
|
|
384
|
+
}),
|
|
385
|
+
schema: z
|
|
386
|
+
.object({
|
|
387
|
+
inputs: z.array(pointer).optional(),
|
|
388
|
+
outputs: z.array(dataProductOutputPointer).optional(),
|
|
389
|
+
})
|
|
390
|
+
.merge(baseSchema),
|
|
391
|
+
});
|
|
392
|
+
|
|
365
393
|
const services = defineCollection({
|
|
366
394
|
loader: glob({
|
|
367
395
|
pattern: [
|
|
@@ -455,6 +483,8 @@ const containers = defineCollection({
|
|
|
455
483
|
|
|
456
484
|
servicesThatWriteToContainer: z.array(reference('services')).optional(),
|
|
457
485
|
servicesThatReadFromContainer: z.array(reference('services')).optional(),
|
|
486
|
+
dataProductsThatWriteToContainer: z.array(reference('data-products')).optional(),
|
|
487
|
+
dataProductsThatReadFromContainer: z.array(reference('data-products')).optional(),
|
|
458
488
|
})
|
|
459
489
|
.merge(baseSchema),
|
|
460
490
|
});
|
|
@@ -489,6 +519,7 @@ const domains = defineCollection({
|
|
|
489
519
|
services: z.array(pointer).optional(),
|
|
490
520
|
domains: z.array(pointer).optional(),
|
|
491
521
|
entities: z.array(pointer).optional(),
|
|
522
|
+
'data-products': z.array(pointer).optional(),
|
|
492
523
|
flows: z.array(pointer).optional(),
|
|
493
524
|
sends: z.array(sendsPointer).optional(),
|
|
494
525
|
receives: z.array(receivesPointer).optional(),
|
|
@@ -725,6 +756,9 @@ export const collections = {
|
|
|
725
756
|
changelogs,
|
|
726
757
|
containers,
|
|
727
758
|
|
|
759
|
+
// Data Product Collections
|
|
760
|
+
'data-products': dataProducts,
|
|
761
|
+
|
|
728
762
|
// DDD Collections
|
|
729
763
|
ubiquitousLanguages,
|
|
730
764
|
entities,
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
getResource as getResourceImpl,
|
|
10
10
|
getMessagesProducedOrConsumedByResource as getMessagesImpl,
|
|
11
11
|
getSchemaForResource as getSchemaImpl,
|
|
12
|
+
getDataProductInputs as getDataProductInputsImpl,
|
|
13
|
+
getDataProductOutputs as getDataProductOutputsImpl,
|
|
12
14
|
collectionSchema,
|
|
13
15
|
resourceCollectionSchema,
|
|
14
16
|
messageCollectionSchema,
|
|
@@ -57,6 +59,8 @@ const builtInToolsMetadata = [
|
|
|
57
59
|
{ name: 'getProducerAndConsumerForMessage', description: 'Get the producers and consumers for a message' },
|
|
58
60
|
{ name: 'getConsumersOfMessage', description: 'Get the consumers for a message' },
|
|
59
61
|
{ name: 'getSchemaForResource', description: 'Get the schema or specifications (OpenAPI, AsyncAPI, GraphQL) for a resource' },
|
|
62
|
+
{ name: 'getDataProductInputs', description: 'Get the inputs (resources consumed) for a data product' },
|
|
63
|
+
{ name: 'getDataProductOutputs', description: 'Get the outputs (resources produced) for a data product with data contracts' },
|
|
60
64
|
];
|
|
61
65
|
|
|
62
66
|
// Get extended tools metadata from user configuration
|
|
@@ -94,6 +98,8 @@ There are many different resource types in EventCatalog, including:
|
|
|
94
98
|
- example docs url: /docs/entities/MyEntity/1.0.0
|
|
95
99
|
- Containers (collection name 'containers') (at the moment these are data stores (databases))
|
|
96
100
|
- example docs url: /docs/containers/MyContainer/1.0.0
|
|
101
|
+
- Data Products (collection name 'data-products') (data products that have inputs and outputs, and may have data contracts)
|
|
102
|
+
- example docs url: /docs/data-products/MyDataProduct/1.0.0
|
|
97
103
|
|
|
98
104
|
The user will ask you some questions about the software architecture catalog, you should use the tools provided to you to get the information they need.
|
|
99
105
|
|
|
@@ -291,6 +297,26 @@ export const POST = async ({ request }: APIContext<{ question: string; messages:
|
|
|
291
297
|
return await getSchemaImpl({ resourceId, resourceVersion, resourceCollection });
|
|
292
298
|
},
|
|
293
299
|
}),
|
|
300
|
+
getDataProductInputs: tool({
|
|
301
|
+
description: toolDescriptions.getDataProductInputs,
|
|
302
|
+
inputSchema: z.object({
|
|
303
|
+
dataProductId: z.string().describe('The id of the data product to get the inputs for'),
|
|
304
|
+
dataProductVersion: z.string().describe('The version of the data product to get the inputs for'),
|
|
305
|
+
}),
|
|
306
|
+
execute: async ({ dataProductId, dataProductVersion }) => {
|
|
307
|
+
return await getDataProductInputsImpl({ dataProductId, dataProductVersion });
|
|
308
|
+
},
|
|
309
|
+
}),
|
|
310
|
+
getDataProductOutputs: tool({
|
|
311
|
+
description: toolDescriptions.getDataProductOutputs,
|
|
312
|
+
inputSchema: z.object({
|
|
313
|
+
dataProductId: z.string().describe('The id of the data product to get the outputs for'),
|
|
314
|
+
dataProductVersion: z.string().describe('The version of the data product to get the outputs for'),
|
|
315
|
+
}),
|
|
316
|
+
execute: async ({ dataProductId, dataProductVersion }) => {
|
|
317
|
+
return await getDataProductOutputsImpl({ dataProductId, dataProductVersion });
|
|
318
|
+
},
|
|
319
|
+
}),
|
|
294
320
|
suggestFollowUpQuestions: tool({
|
|
295
321
|
description:
|
|
296
322
|
'Use this tool after answering a question to suggest 2-3 relevant follow-up questions the user might want to ask. These will be displayed as clickable suggestions.',
|
|
@@ -210,6 +210,6 @@ export const getAdjacentPages = async (slug: string): Promise<AdjacentPages> =>
|
|
|
210
210
|
};
|
|
211
211
|
|
|
212
212
|
export const getNavigationItems = async (): Promise<SidebarItem[]> => {
|
|
213
|
-
const configuredSidebar = config.customDocs
|
|
213
|
+
const configuredSidebar = config.customDocs?.sidebar || [];
|
|
214
214
|
return processSidebarItems(configuredSidebar as SideBarConfigurationItem[]);
|
|
215
215
|
};
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { getCollection, getEntry } from 'astro:content';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
import { getSchemasFromResource } from '@utils/collections/schemas';
|
|
7
|
+
import { getSchemasFromResource, getSchemaFormatFromURL } from '@utils/collections/schemas';
|
|
8
8
|
import { getItemsFromCollectionByIdAndSemverOrLatest } from '@utils/collections/util';
|
|
9
9
|
import { getUbiquitousLanguageWithSubdomains } from '@utils/collections/domains';
|
|
10
|
+
import { getAbsoluteFilePathForAstroFile } from '@utils/files';
|
|
10
11
|
import fs from 'node:fs';
|
|
11
12
|
|
|
12
13
|
// ============================================
|
|
@@ -92,6 +93,7 @@ export const collectionSchema = z.enum([
|
|
|
92
93
|
'entities',
|
|
93
94
|
'containers',
|
|
94
95
|
'diagrams',
|
|
96
|
+
'data-products',
|
|
95
97
|
]);
|
|
96
98
|
|
|
97
99
|
export const messageCollectionSchema = z.enum(['events', 'commands', 'queries']);
|
|
@@ -105,6 +107,7 @@ export const resourceCollectionSchema = z.enum([
|
|
|
105
107
|
'domains',
|
|
106
108
|
'channels',
|
|
107
109
|
'entities',
|
|
110
|
+
'data-products',
|
|
108
111
|
]);
|
|
109
112
|
|
|
110
113
|
// ============================================
|
|
@@ -208,7 +211,17 @@ export async function getSchemaForResource(params: { resourceId: string; resourc
|
|
|
208
211
|
* Find all resources owned by a specific team or user
|
|
209
212
|
*/
|
|
210
213
|
export async function findResourcesByOwner(params: { ownerId: string }) {
|
|
211
|
-
const collectionsToSearch = [
|
|
214
|
+
const collectionsToSearch = [
|
|
215
|
+
'events',
|
|
216
|
+
'commands',
|
|
217
|
+
'queries',
|
|
218
|
+
'services',
|
|
219
|
+
'domains',
|
|
220
|
+
'flows',
|
|
221
|
+
'channels',
|
|
222
|
+
'entities',
|
|
223
|
+
'data-products',
|
|
224
|
+
] as const;
|
|
212
225
|
|
|
213
226
|
const results: Array<{ collection: string; id: string; version?: string; name: string }> = [];
|
|
214
227
|
|
|
@@ -658,6 +671,156 @@ export async function explainUbiquitousLanguageTerms(params: { domainId: string;
|
|
|
658
671
|
};
|
|
659
672
|
}
|
|
660
673
|
|
|
674
|
+
// ============================================
|
|
675
|
+
// Data Product tools
|
|
676
|
+
// ============================================
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Get the inputs (resources consumed) for a data product
|
|
680
|
+
* Returns fully hydrated input resources
|
|
681
|
+
*/
|
|
682
|
+
export async function getDataProductInputs(params: { dataProductId: string; dataProductVersion: string }) {
|
|
683
|
+
const dataProduct = await getEntry('data-products', `${params.dataProductId}-${params.dataProductVersion}`);
|
|
684
|
+
|
|
685
|
+
if (!dataProduct) {
|
|
686
|
+
return {
|
|
687
|
+
error: `Data product not found: ${params.dataProductId}-${params.dataProductVersion}`,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const inputPointers = (dataProduct.data as any).inputs || [];
|
|
692
|
+
|
|
693
|
+
if (inputPointers.length === 0) {
|
|
694
|
+
return {
|
|
695
|
+
dataProductId: params.dataProductId,
|
|
696
|
+
dataProductVersion: params.dataProductVersion,
|
|
697
|
+
message: 'No inputs found for this data product',
|
|
698
|
+
inputs: [],
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Fetch all collections that can be inputs (messages, channels, containers, services)
|
|
703
|
+
const [allEvents, allCommands, allQueries, allChannels, allContainers, allServices] = await Promise.all([
|
|
704
|
+
getCollection('events'),
|
|
705
|
+
getCollection('commands'),
|
|
706
|
+
getCollection('queries'),
|
|
707
|
+
getCollection('channels'),
|
|
708
|
+
getCollection('containers'),
|
|
709
|
+
getCollection('services'),
|
|
710
|
+
]);
|
|
711
|
+
|
|
712
|
+
const allResources = [...allEvents, ...allCommands, ...allQueries, ...allChannels, ...allContainers, ...allServices];
|
|
713
|
+
|
|
714
|
+
// Hydrate inputs by finding matching resources
|
|
715
|
+
const hydratedInputs = inputPointers
|
|
716
|
+
.map((pointer: { id: string; version?: string }) => {
|
|
717
|
+
const matches = getItemsFromCollectionByIdAndSemverOrLatest(allResources, pointer.id, pointer.version);
|
|
718
|
+
const resource = matches[0];
|
|
719
|
+
if (!resource) return null;
|
|
720
|
+
|
|
721
|
+
return {
|
|
722
|
+
id: (resource.data as any).id,
|
|
723
|
+
version: (resource.data as any).version,
|
|
724
|
+
name: (resource.data as any).name || (resource.data as any).id,
|
|
725
|
+
summary: (resource.data as any).summary,
|
|
726
|
+
collection: resource.collection,
|
|
727
|
+
};
|
|
728
|
+
})
|
|
729
|
+
.filter(Boolean);
|
|
730
|
+
|
|
731
|
+
return {
|
|
732
|
+
dataProductId: params.dataProductId,
|
|
733
|
+
dataProductVersion: params.dataProductVersion,
|
|
734
|
+
dataProductName: (dataProduct.data as any).name || params.dataProductId,
|
|
735
|
+
inputs: hydratedInputs,
|
|
736
|
+
totalCount: hydratedInputs.length,
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Get the outputs (resources produced) for a data product
|
|
742
|
+
* Returns fully hydrated output resources with their contracts
|
|
743
|
+
*/
|
|
744
|
+
export async function getDataProductOutputs(params: { dataProductId: string; dataProductVersion: string }) {
|
|
745
|
+
const dataProduct = await getEntry('data-products', `${params.dataProductId}-${params.dataProductVersion}`);
|
|
746
|
+
|
|
747
|
+
if (!dataProduct) {
|
|
748
|
+
return {
|
|
749
|
+
error: `Data product not found: ${params.dataProductId}-${params.dataProductVersion}`,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const outputPointers = (dataProduct.data as any).outputs || [];
|
|
754
|
+
|
|
755
|
+
if (outputPointers.length === 0) {
|
|
756
|
+
return {
|
|
757
|
+
dataProductId: params.dataProductId,
|
|
758
|
+
dataProductVersion: params.dataProductVersion,
|
|
759
|
+
message: 'No outputs found for this data product',
|
|
760
|
+
outputs: [],
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Fetch all collections that can be outputs (messages, channels, containers, services)
|
|
765
|
+
const [allEvents, allCommands, allQueries, allChannels, allContainers, allServices] = await Promise.all([
|
|
766
|
+
getCollection('events'),
|
|
767
|
+
getCollection('commands'),
|
|
768
|
+
getCollection('queries'),
|
|
769
|
+
getCollection('channels'),
|
|
770
|
+
getCollection('containers'),
|
|
771
|
+
getCollection('services'),
|
|
772
|
+
]);
|
|
773
|
+
|
|
774
|
+
const allResources = [...allEvents, ...allCommands, ...allQueries, ...allChannels, ...allContainers, ...allServices];
|
|
775
|
+
|
|
776
|
+
// Hydrate outputs by finding matching resources and including contract info
|
|
777
|
+
const hydratedOutputs = outputPointers
|
|
778
|
+
.map((pointer: { id: string; version?: string; contract?: { path: string; name: string; type?: string } }) => {
|
|
779
|
+
const matches = getItemsFromCollectionByIdAndSemverOrLatest(allResources, pointer.id, pointer.version);
|
|
780
|
+
const resource = matches[0];
|
|
781
|
+
if (!resource) return null;
|
|
782
|
+
|
|
783
|
+
const result: any = {
|
|
784
|
+
id: (resource.data as any).id,
|
|
785
|
+
version: (resource.data as any).version,
|
|
786
|
+
name: (resource.data as any).name || (resource.data as any).id,
|
|
787
|
+
summary: (resource.data as any).summary,
|
|
788
|
+
collection: resource.collection,
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
// Include contract information if present
|
|
792
|
+
if (pointer.contract) {
|
|
793
|
+
const absoluteContractPath = getAbsoluteFilePathForAstroFile(dataProduct.filePath ?? '', pointer.contract.path);
|
|
794
|
+
let contractContent: string | null = null;
|
|
795
|
+
|
|
796
|
+
try {
|
|
797
|
+
contractContent = fs.readFileSync(absoluteContractPath, 'utf-8');
|
|
798
|
+
} catch {
|
|
799
|
+
// File may not be accessible
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
result.contract = {
|
|
803
|
+
name: pointer.contract.name,
|
|
804
|
+
path: pointer.contract.path,
|
|
805
|
+
format: getSchemaFormatFromURL(pointer.contract.path),
|
|
806
|
+
type: pointer.contract.type,
|
|
807
|
+
content: contractContent,
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return result;
|
|
812
|
+
})
|
|
813
|
+
.filter(Boolean);
|
|
814
|
+
|
|
815
|
+
return {
|
|
816
|
+
dataProductId: params.dataProductId,
|
|
817
|
+
dataProductVersion: params.dataProductVersion,
|
|
818
|
+
dataProductName: (dataProduct.data as any).name || params.dataProductId,
|
|
819
|
+
outputs: hydratedOutputs,
|
|
820
|
+
totalCount: hydratedOutputs.length,
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
|
|
661
824
|
// ============================================
|
|
662
825
|
// Tool metadata (descriptions)
|
|
663
826
|
// ============================================
|
|
@@ -687,4 +850,8 @@ export const toolDescriptions = {
|
|
|
687
850
|
'Use this tool when a user shares a schema file (Avro, JSON Schema, Protobuf) and wants to find it in EventCatalog. Look for "x-eventcatalog-id" and "x-eventcatalog-version" in the schema - these may be properties in the schema OR in comments (e.g. // x-eventcatalog-id: OrderCreated). Pass the id as messageId. If version exists, pass it as messageVersion, otherwise omit it to get the latest version. Returns the message resource along with its producers and consumers.',
|
|
688
851
|
explainUbiquitousLanguageTerms:
|
|
689
852
|
'Use this tool to explain ubiquitous language terms from Domain-Driven Design for a specific domain. Returns the glossary of terms defined for the domain and its subdomains, including duplicate term detection.',
|
|
853
|
+
getDataProductInputs:
|
|
854
|
+
'Use this tool to get the inputs (resources consumed) for a data product. Returns fully hydrated input resources with their id, version, name, summary, and collection type.',
|
|
855
|
+
getDataProductOutputs:
|
|
856
|
+
'Use this tool to get the outputs (resources produced) for a data product. Returns fully hydrated output resources with their id, version, name, summary, collection type, and data contracts (if defined). Data contracts include the contract name, path, format, type, and content.',
|
|
690
857
|
};
|
|
@@ -11,14 +11,16 @@ export class Page extends HybridPage {
|
|
|
11
11
|
static async getStaticPaths(): Promise<Array<{ params: any; props: any }>> {
|
|
12
12
|
const { getFlows } = await import('@utils/collections/flows');
|
|
13
13
|
const { getServices } = await import('@utils/collections/services');
|
|
14
|
+
const { getDataProducts } = await import('@utils/collections/data-products');
|
|
14
15
|
|
|
15
16
|
const loaders = {
|
|
16
17
|
...pageDataLoader,
|
|
17
18
|
flows: getFlows,
|
|
18
19
|
services: getServices,
|
|
20
|
+
'data-products': getDataProducts,
|
|
19
21
|
};
|
|
20
22
|
|
|
21
|
-
const itemTypes = ['events', 'commands', 'queries', 'domains', 'services', 'flows', 'containers'] as const;
|
|
23
|
+
const itemTypes = ['events', 'commands', 'queries', 'domains', 'services', 'flows', 'containers', 'data-products'] as const;
|
|
22
24
|
const allItems = await Promise.all(itemTypes.map((type) => loaders[type]()));
|
|
23
25
|
|
|
24
26
|
return allItems.flatMap((items, index) => ({
|
|
@@ -41,11 +43,13 @@ export class Page extends HybridPage {
|
|
|
41
43
|
|
|
42
44
|
const { getFlows } = await import('@utils/collections/flows');
|
|
43
45
|
const { getServices } = await import('@utils/collections/services');
|
|
46
|
+
const { getDataProducts } = await import('@utils/collections/data-products');
|
|
44
47
|
|
|
45
48
|
const loaders = {
|
|
46
49
|
...pageDataLoader,
|
|
47
50
|
flows: getFlows,
|
|
48
51
|
services: getServices,
|
|
52
|
+
'data-products': getDataProducts,
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
// @ts-ignore
|