@eventcatalog/core 2.35.9 → 2.36.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-RKOF335E.js → chunk-4UMY62TN.js} +1 -1
- package/dist/{chunk-K2XHQGHN.js → chunk-56VOUKDM.js} +1 -1
- package/dist/{chunk-KEOGLT6Y.js → chunk-J5VDZAYA.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 +3 -3
- package/eventcatalog/src/components/Grids/DomainGrid.tsx +9 -0
- package/eventcatalog/src/components/Grids/ServiceGrid.tsx +19 -0
- package/eventcatalog/src/components/MDX/EntityPropertiesTable/EntityPropertiesTable.astro +106 -0
- package/eventcatalog/src/components/MDX/components.tsx +2 -0
- package/eventcatalog/src/components/SideBars/DomainSideBar.astro +22 -0
- package/eventcatalog/src/components/SideBars/EntitySideBar.astro +94 -0
- package/eventcatalog/src/components/SideBars/ServiceSideBar.astro +23 -0
- package/eventcatalog/src/components/SideNav/ListViewSideBar/components/MessageList.tsx +1 -1
- package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +59 -12
- package/eventcatalog/src/components/SideNav/ListViewSideBar/types.ts +8 -0
- package/eventcatalog/src/components/SideNav/ListViewSideBar/utils.ts +14 -6
- package/eventcatalog/src/components/SideNav/TreeView/getTreeView.ts +1 -1
- package/eventcatalog/src/content.config.ts +34 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +37 -18
- package/eventcatalog/src/pages/docs/[type]/[id]/[version].md.ts +3 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version].mdx.ts +2 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/index.astro +4 -1
- package/eventcatalog/src/types/index.ts +2 -2
- package/eventcatalog/src/utils/badges.tsx +22 -0
- package/eventcatalog/src/utils/collections/domains.ts +9 -1
- package/eventcatalog/src/utils/collections/icons.ts +5 -1
- package/eventcatalog/src/utils/collections/services.ts +8 -1
- package/eventcatalog/src/utils/entities.ts +85 -0
- package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +2 -0
- package/package.json +1 -1
- package/eventcatalog/src/utils/badges.ts +0 -5
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log_build_default
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-56VOUKDM.js";
|
|
4
|
+
import "../chunk-J5VDZAYA.js";
|
|
5
|
+
import "../chunk-4UMY62TN.js";
|
|
6
6
|
import "../chunk-E7TXTI7G.js";
|
|
7
7
|
export {
|
|
8
8
|
log_build_default as default
|
package/dist/constants.cjs
CHANGED
package/dist/constants.js
CHANGED
package/dist/eventcatalog.cjs
CHANGED
package/dist/eventcatalog.js
CHANGED
|
@@ -6,15 +6,15 @@ import {
|
|
|
6
6
|
} from "./chunk-DCLTVJDP.js";
|
|
7
7
|
import {
|
|
8
8
|
log_build_default
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-56VOUKDM.js";
|
|
10
|
+
import "./chunk-J5VDZAYA.js";
|
|
11
11
|
import {
|
|
12
12
|
catalogToAstro,
|
|
13
13
|
checkAndConvertMdToMdx
|
|
14
14
|
} from "./chunk-SLEMYHTU.js";
|
|
15
15
|
import {
|
|
16
16
|
VERSION
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-4UMY62TN.js";
|
|
18
18
|
import {
|
|
19
19
|
isBackstagePluginEnabled,
|
|
20
20
|
isEventCatalogScaleEnabled,
|
|
@@ -5,6 +5,7 @@ import type { CollectionEntry } from 'astro:content';
|
|
|
5
5
|
import { type CollectionMessageTypes } from '@types';
|
|
6
6
|
import { getCollectionStyles } from './utils';
|
|
7
7
|
import { SearchBar } from './components';
|
|
8
|
+
import { BoxIcon } from 'lucide-react';
|
|
8
9
|
|
|
9
10
|
export interface ExtendedDomain extends CollectionEntry<'domains'> {
|
|
10
11
|
sends: CollectionEntry<CollectionMessageTypes>[];
|
|
@@ -123,6 +124,14 @@ export default function DomainGrid({ domains, embeded }: DomainGridProps) {
|
|
|
123
124
|
</p>
|
|
124
125
|
</div>
|
|
125
126
|
</div>
|
|
127
|
+
{domain.data.entities && domain.data.entities.length > 0 && (
|
|
128
|
+
<div className="flex items-center gap-2 bg-white rounded-lg px-3 py-2 border border-gray-200 ">
|
|
129
|
+
<BoxIcon className="h-4 w-4 text-purple-500" />
|
|
130
|
+
<div>
|
|
131
|
+
<p className="text-sm font-medium text-gray-900">{domain.data.entities?.length} Entities</p>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
126
135
|
</div>
|
|
127
136
|
|
|
128
137
|
<div className="space-y-6">
|
|
@@ -7,6 +7,7 @@ import type { CollectionMessageTypes } from '@types';
|
|
|
7
7
|
import { getCollectionStyles } from './utils';
|
|
8
8
|
import { SearchBar, TypeFilters, Pagination } from './components';
|
|
9
9
|
import type { ExtendedDomain } from './DomainGrid';
|
|
10
|
+
import { BoxIcon } from 'lucide-react';
|
|
10
11
|
|
|
11
12
|
// Message component for reuse
|
|
12
13
|
const Message = memo(({ message, collection }: { message: any; collection: string }) => {
|
|
@@ -157,6 +158,24 @@ const DomainSection = memo(
|
|
|
157
158
|
</div>
|
|
158
159
|
</div>
|
|
159
160
|
|
|
161
|
+
{/* Entities */}
|
|
162
|
+
{subdomain.data.entities && subdomain.data.entities.length > 0 && (
|
|
163
|
+
<div className="grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-1 xl:grid-cols-4 gap-6">
|
|
164
|
+
{subdomain.data.entities.map((entity: any) => (
|
|
165
|
+
<a
|
|
166
|
+
key={entity.id}
|
|
167
|
+
href={buildUrl(`/docs/entities/${entity.id}`)}
|
|
168
|
+
className="bg-white border-2 border-dashed border-purple-400 rounded-lg p-4 space-y-4 hover:bg-purple-50 transition-colors duration-200"
|
|
169
|
+
>
|
|
170
|
+
<div className="flex items-center gap-2">
|
|
171
|
+
<BoxIcon className="h-5 w-5 text-purple-500" />
|
|
172
|
+
<h3 className="text-lg font-semibold text-gray-900">{entity.id} (Entity)</h3>
|
|
173
|
+
</div>
|
|
174
|
+
</a>
|
|
175
|
+
))}
|
|
176
|
+
</div>
|
|
177
|
+
)}
|
|
178
|
+
|
|
160
179
|
<div className="grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-1 xl:grid-cols-2 gap-6">
|
|
161
180
|
{subdomainServices.map((service) => (
|
|
162
181
|
<ServiceCard
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { CollectionEntry } from 'astro:content';
|
|
3
|
+
import Admonition from '../Admonition';
|
|
4
|
+
|
|
5
|
+
// Define the shape for a single property used in this component
|
|
6
|
+
type EntityProperty = {
|
|
7
|
+
name: string;
|
|
8
|
+
type: string;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
description?: string;
|
|
11
|
+
enum?: string[];
|
|
12
|
+
items?: {
|
|
13
|
+
type: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Expects a CollectionEntry for 'entities'.
|
|
18
|
+
// The actual properties structure might differ slightly from the base type,
|
|
19
|
+
// so we handle it below.
|
|
20
|
+
export interface Props extends CollectionEntry<'entities'> {}
|
|
21
|
+
|
|
22
|
+
const { data, collection } = Astro.props;
|
|
23
|
+
// Cast the properties to our expected type for use in the template
|
|
24
|
+
const properties = data?.properties as EntityProperty[] | undefined;
|
|
25
|
+
const isComponentEnabled = collection === 'entities';
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
!isComponentEnabled && (
|
|
30
|
+
<Admonition type="warning">
|
|
31
|
+
<div>
|
|
32
|
+
<span class="font-bold">
|
|
33
|
+
{`<MessageTable/>`} component is not supported for resources of type {collection}.
|
|
34
|
+
</span>
|
|
35
|
+
<span class="block">This component is only supported for services and domains.</span>
|
|
36
|
+
</div>
|
|
37
|
+
</Admonition>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
isComponentEnabled && properties && properties.length > 0 ? (
|
|
43
|
+
<div class="overflow-x-auto relative not-prose">
|
|
44
|
+
<table class="w-full text-sm text-left text-gray-500 border border-gray-200 rounded-lg shadow-sm">
|
|
45
|
+
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
|
|
46
|
+
<tr>
|
|
47
|
+
<th scope="col" class="py-3 px-6">
|
|
48
|
+
Name
|
|
49
|
+
</th>
|
|
50
|
+
<th scope="col" class="py-3 px-6">
|
|
51
|
+
Type
|
|
52
|
+
</th>
|
|
53
|
+
<th scope="col" class="py-3 px-6">
|
|
54
|
+
Required
|
|
55
|
+
</th>
|
|
56
|
+
<th scope="col" class="py-3 px-6 min-w-[250px]">
|
|
57
|
+
Description
|
|
58
|
+
</th>
|
|
59
|
+
</tr>
|
|
60
|
+
</thead>
|
|
61
|
+
<tbody>
|
|
62
|
+
{properties.map((prop) => (
|
|
63
|
+
<tr class="bg-white border-b hover:bg-gray-50 align-top">
|
|
64
|
+
<td class="py-4 px-6 font-medium text-gray-900 whitespace-nowrap">
|
|
65
|
+
<code class="text-sm bg-gray-100 rounded px-1 py-0.5">{prop.name}</code>
|
|
66
|
+
</td>
|
|
67
|
+
<td class="py-4 px-6">
|
|
68
|
+
{prop.type === 'array' && prop.items ? (
|
|
69
|
+
<span>
|
|
70
|
+
array<<code class="text-sm bg-gray-100 rounded px-1 py-0.5">{prop.items.type}</code>>
|
|
71
|
+
</span>
|
|
72
|
+
) : (
|
|
73
|
+
<code class="text-sm bg-gray-100 rounded px-1 py-0.5">{prop.type}</code>
|
|
74
|
+
)}
|
|
75
|
+
{prop.enum && (
|
|
76
|
+
<div class="text-xs text-gray-500 mt-2">
|
|
77
|
+
<span class="font-semibold block mb-1">Enum:</span>
|
|
78
|
+
<ul class="list-disc list-inside ml-2 space-y-1">
|
|
79
|
+
{prop.enum.map((enumValue: string) => (
|
|
80
|
+
<li>
|
|
81
|
+
<code class="text-xs bg-gray-100 rounded px-1 py-0.5">{enumValue}</code>
|
|
82
|
+
</li>
|
|
83
|
+
))}
|
|
84
|
+
</ul>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
</td>
|
|
88
|
+
<td class="py-4 px-6">
|
|
89
|
+
{prop.required ? (
|
|
90
|
+
<span class="bg-purple-100 text-purple-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">Required</span>
|
|
91
|
+
) : (
|
|
92
|
+
<span class="bg-gray-100 text-gray-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">Optional</span>
|
|
93
|
+
)}
|
|
94
|
+
</td>
|
|
95
|
+
<td class="py-4 px-6">{prop.description || <span class="text-gray-400 italic">No description provided.</span>}</td>
|
|
96
|
+
</tr>
|
|
97
|
+
))}
|
|
98
|
+
</tbody>
|
|
99
|
+
</table>
|
|
100
|
+
</div>
|
|
101
|
+
) : (
|
|
102
|
+
<Admonition type="note" title="No Properties Defined">
|
|
103
|
+
<p>There are no properties defined for this entity.</p>
|
|
104
|
+
</Admonition>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
@@ -14,6 +14,7 @@ import AsyncAPI from '@components/MDX/AsyncAPI/AsyncAPI.astro';
|
|
|
14
14
|
import ChannelInformation from '@components/MDX/ChannelInformation/ChannelInformation';
|
|
15
15
|
import MessageTable from '@components/MDX/MessageTable/MessageTable.astro';
|
|
16
16
|
import ResourceGroupTable from '@components/MDX/ResourceGroupTable/ResourceGroupTable.astro';
|
|
17
|
+
import EntityPropertiesTable from '@components/MDX/EntityPropertiesTable/EntityPropertiesTable.astro';
|
|
17
18
|
import Tabs from '@components/MDX/Tabs/Tabs.astro';
|
|
18
19
|
import TabItem from '@components/MDX/Tabs/TabItem.astro';
|
|
19
20
|
import ResourceLink from '@components/MDX/ResourceLink/ResourceLink.astro';
|
|
@@ -38,6 +39,7 @@ const components = (props: any) => {
|
|
|
38
39
|
Flow,
|
|
39
40
|
Link: (mdxProp: any) => jsx(Link, { ...props, ...mdxProp }),
|
|
40
41
|
MessageTable: (mdxProp: any) => jsx(MessageTable, { ...props, ...mdxProp }),
|
|
42
|
+
EntityPropertiesTable: (mdxProp: any) => jsx(EntityPropertiesTable, { ...props, ...mdxProp }),
|
|
41
43
|
NodeGraph: (mdxProp: any) => jsx(NodeGraphPortal, { ...props.data, ...mdxProp, props, mdxProp }),
|
|
42
44
|
OpenAPI,
|
|
43
45
|
ResourceGroupTable: (mdxProp: any) => jsx(ResourceGroupTable, { ...props, ...mdxProp }),
|
|
@@ -21,6 +21,9 @@ const services = (domain.data.services as CollectionEntry<'services'>[]) || [];
|
|
|
21
21
|
// @ts-ignore
|
|
22
22
|
const subDomains = (domain.data.domains as CollectionEntry<'domains'>[]) || [];
|
|
23
23
|
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
const entities = (domain.data.entities as CollectionEntry<'entities'>[]) || [];
|
|
26
|
+
|
|
24
27
|
const ubiquitousLanguage = await getUbiquitousLanguage(domain);
|
|
25
28
|
const hasUbiquitousLanguage = ubiquitousLanguage.length > 0;
|
|
26
29
|
const ubiquitousLanguageDictionary = hasUbiquitousLanguage ? ubiquitousLanguage[0].data.dictionary : [];
|
|
@@ -86,6 +89,14 @@ const ubiquitousLanguageList = ubiquitousLanguageDictionary?.map((l) => ({
|
|
|
86
89
|
href: buildUrl(`/docs/${domain.collection}/${domain.data.id}/language?id=${l.id}`),
|
|
87
90
|
}));
|
|
88
91
|
|
|
92
|
+
const entityList = entities.map((p) => ({
|
|
93
|
+
label: p.data.name,
|
|
94
|
+
badge: p.collection,
|
|
95
|
+
tag: `v${p.data.version}`,
|
|
96
|
+
collection: p.collection,
|
|
97
|
+
href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
|
|
98
|
+
}));
|
|
99
|
+
|
|
89
100
|
const ownersList = filteredOwners.map((o) => ({
|
|
90
101
|
label: o.data.name,
|
|
91
102
|
type: o.collection,
|
|
@@ -160,6 +171,17 @@ const ownersList = filteredOwners.map((o) => ({
|
|
|
160
171
|
/>
|
|
161
172
|
)
|
|
162
173
|
}
|
|
174
|
+
{
|
|
175
|
+
entities.length > 0 && (
|
|
176
|
+
<PillListFlat
|
|
177
|
+
title={`Entities (${entities.length})`}
|
|
178
|
+
pills={entityList}
|
|
179
|
+
emptyMessage={`This domain does not contain any entities.`}
|
|
180
|
+
color="pink"
|
|
181
|
+
client:load
|
|
182
|
+
/>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
163
185
|
{domain.data.versions && <VersionList versions={domain.data.versions} collectionItem={domain} />}
|
|
164
186
|
{
|
|
165
187
|
domain.data.repository && (
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { CollectionEntry } from 'astro:content';
|
|
3
|
+
import PillListFlat from '@components/Lists/PillListFlat';
|
|
4
|
+
import OwnersList from '@components/Lists/OwnersList';
|
|
5
|
+
import VersionList from '@components/Lists/VersionList.astro';
|
|
6
|
+
import { buildUrl } from '@utils/url-builder';
|
|
7
|
+
import { ScrollText } from 'lucide-react';
|
|
8
|
+
import { getOwner } from '@utils/collections/owners';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
entity: CollectionEntry<'entities'>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { entity } = Astro.props;
|
|
15
|
+
|
|
16
|
+
const ownersRaw = entity.data?.owners || [];
|
|
17
|
+
const owners = await Promise.all<ReturnType<typeof getOwner>>(ownersRaw.map(getOwner));
|
|
18
|
+
const filteredOwners = owners.filter((o) => o !== undefined);
|
|
19
|
+
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
const services = (entity.data.services as CollectionEntry<'services'>[]) || [];
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
const domains = (entity.data.domains as CollectionEntry<'domains'>[]) || [];
|
|
24
|
+
|
|
25
|
+
const ownersList = filteredOwners.map((o) => ({
|
|
26
|
+
label: o.data.name,
|
|
27
|
+
type: o.collection,
|
|
28
|
+
badge: o.collection === 'users' ? o.data.role : 'Team',
|
|
29
|
+
avatarUrl: o.collection === 'users' ? o.data.avatarUrl : '',
|
|
30
|
+
href: buildUrl(`/docs/${o.collection}/${o.data.id}`),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const servicesList = services.map((p) => ({
|
|
34
|
+
label: p.data.name,
|
|
35
|
+
badge: p.collection,
|
|
36
|
+
color: 'pink',
|
|
37
|
+
collection: p.collection,
|
|
38
|
+
tag: `v${p.data.version}`,
|
|
39
|
+
href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
const domainsList = domains.map((p) => ({
|
|
43
|
+
label: p.data.name,
|
|
44
|
+
badge: p.collection,
|
|
45
|
+
color: 'blue',
|
|
46
|
+
collection: p.collection,
|
|
47
|
+
tag: `v${p.data.version}`,
|
|
48
|
+
href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
|
|
49
|
+
}));
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
<aside class="sticky top-28 left-0 space-y-8 h-full overflow-y-auto py-4">
|
|
53
|
+
<div class="">
|
|
54
|
+
{
|
|
55
|
+
(
|
|
56
|
+
<PillListFlat
|
|
57
|
+
title={`Domains (${domainsList.length})`}
|
|
58
|
+
pills={domainsList}
|
|
59
|
+
emptyMessage={`This entity is not used in any domains.`}
|
|
60
|
+
color="blue"
|
|
61
|
+
client:load
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
{
|
|
66
|
+
servicesList.length > 0 && (
|
|
67
|
+
<PillListFlat
|
|
68
|
+
title={`Services (${servicesList.length})`}
|
|
69
|
+
pills={servicesList}
|
|
70
|
+
emptyMessage={`This entity is not used in any services.`}
|
|
71
|
+
color="pink"
|
|
72
|
+
client:load
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
{entity.data.versions && <VersionList versions={entity.data.versions} collectionItem={entity} />}
|
|
77
|
+
<OwnersList
|
|
78
|
+
title={`Owners (${filteredOwners.length})`}
|
|
79
|
+
owners={ownersList}
|
|
80
|
+
emptyMessage={`This entity does not have any documented owners.`}
|
|
81
|
+
client:load
|
|
82
|
+
/>
|
|
83
|
+
|
|
84
|
+
<div class="space-y-2">
|
|
85
|
+
<a
|
|
86
|
+
href={buildUrl(`/docs/${entity.collection}/${entity.data.id}/${entity.data.latestVersion}/changelog`)}
|
|
87
|
+
class="flex items-center space-x-2 justify-center text-center rounded-md w-full bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100/60 hover:text-primary"
|
|
88
|
+
>
|
|
89
|
+
<ScrollText strokeWidth={2} size={16} />
|
|
90
|
+
<span class="block">Read changelog</span>
|
|
91
|
+
</a>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</aside>
|
|
@@ -23,6 +23,9 @@ const sends = (service.data.sends as CollectionEntry<'events'>[]) || [];
|
|
|
23
23
|
// @ts-ignore
|
|
24
24
|
const receives = (service.data.receives as CollectionEntry<'events'>[]) || [];
|
|
25
25
|
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
const entities = (service.data.entities as CollectionEntry<'entities'>[]) || [];
|
|
28
|
+
|
|
26
29
|
const ownersRaw = service.data?.owners || [];
|
|
27
30
|
const owners = await Promise.all<ReturnType<typeof getOwner>>(ownersRaw.map(getOwner));
|
|
28
31
|
const filteredOwners = owners.filter((o) => o !== undefined);
|
|
@@ -69,6 +72,14 @@ const domainList = domainsServiceBelongsTo.map((d) => ({
|
|
|
69
72
|
href: buildUrl(`/docs/${d.collection}/${d.data.id}/${d.data.version}`),
|
|
70
73
|
}));
|
|
71
74
|
|
|
75
|
+
const entityList = entities.map((p) => ({
|
|
76
|
+
label: p.data.name,
|
|
77
|
+
badge: p.collection,
|
|
78
|
+
tag: `v${p.data.version}`,
|
|
79
|
+
collection: p.collection,
|
|
80
|
+
href: buildUrl(`/docs/${p.collection}/${p.data.id}/${p.data.version}`),
|
|
81
|
+
}));
|
|
82
|
+
|
|
72
83
|
const isRSSEnabled = config.rss?.enabled;
|
|
73
84
|
|
|
74
85
|
// @ts-ignore
|
|
@@ -113,6 +124,18 @@ const schemaURL = join(publicPath, schemaFilePath || '');
|
|
|
113
124
|
|
|
114
125
|
{service.data.specifications && <SpecificationsList collectionItem={service} />}
|
|
115
126
|
|
|
127
|
+
{
|
|
128
|
+
entities.length > 0 && (
|
|
129
|
+
<PillListFlat
|
|
130
|
+
title={`Entities (${entities.length})`}
|
|
131
|
+
pills={entityList}
|
|
132
|
+
emptyMessage={`This service does not contain any entities.`}
|
|
133
|
+
color="pink"
|
|
134
|
+
client:load
|
|
135
|
+
/>
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
116
139
|
<OwnersList
|
|
117
140
|
title={`Owners (${ownersList.length})`}
|
|
118
141
|
owners={ownersList}
|
|
@@ -39,7 +39,7 @@ const MessageList: React.FC<MessageListProps> = ({ messages, decodedCurrentPath
|
|
|
39
39
|
<span
|
|
40
40
|
className={`ml-2 text-[10px] flex items-center gap-1 font-medium px-2 uppercase py-0.5 rounded ${getMessageColorByLabelOrCollection(message.collection, message.data?.sidebar?.badge)}`}
|
|
41
41
|
>
|
|
42
|
-
{message.data?.sidebar?.badge || getMessageCollectionName(message.collection)}
|
|
42
|
+
{message.data?.sidebar?.badge || getMessageCollectionName(message.collection, message)}
|
|
43
43
|
</span>
|
|
44
44
|
</a>
|
|
45
45
|
</li>
|
|
@@ -11,13 +11,16 @@ export const getMessageColorByCollection = (collection: string) => {
|
|
|
11
11
|
if (collection === 'commands') return 'bg-blue-50 text-blue-600';
|
|
12
12
|
if (collection === 'queries') return 'bg-green-50 text-green-600';
|
|
13
13
|
if (collection === 'events') return 'bg-orange-50 text-orange-600';
|
|
14
|
+
if (collection === 'entities') return 'bg-purple-50 text-purple-600';
|
|
14
15
|
return 'text-gray-600';
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
export const getMessageCollectionName = (collection: string) => {
|
|
18
|
+
export const getMessageCollectionName = (collection: string, item: any) => {
|
|
18
19
|
if (collection === 'commands') return 'Command';
|
|
19
20
|
if (collection === 'queries') return 'Query';
|
|
20
21
|
if (collection === 'events') return 'Event';
|
|
22
|
+
if (collection === 'entities' && item.data.aggregateRoot) return 'Entity (Root)';
|
|
23
|
+
if (collection === 'entities') return 'Entity';
|
|
21
24
|
return collection.slice(0, collection.length - 1).toUpperCase();
|
|
22
25
|
};
|
|
23
26
|
|
|
@@ -41,11 +44,13 @@ const ServiceItem = React.memo(
|
|
|
41
44
|
decodedCurrentPath,
|
|
42
45
|
collapsedGroups,
|
|
43
46
|
toggleGroupCollapse,
|
|
47
|
+
isVisualizer,
|
|
44
48
|
}: {
|
|
45
49
|
item: ServiceItem;
|
|
46
50
|
decodedCurrentPath: string;
|
|
47
51
|
collapsedGroups: { [key: string]: boolean };
|
|
48
52
|
toggleGroupCollapse: (group: string) => void;
|
|
53
|
+
isVisualizer: boolean;
|
|
49
54
|
}) => (
|
|
50
55
|
<CollapsibleGroup
|
|
51
56
|
isCollapsed={collapsedGroups[item.href]}
|
|
@@ -148,6 +153,25 @@ const ServiceItem = React.memo(
|
|
|
148
153
|
>
|
|
149
154
|
<MessageList messages={item.sends} decodedCurrentPath={decodedCurrentPath} />
|
|
150
155
|
</CollapsibleGroup>
|
|
156
|
+
{!isVisualizer && item.entities.length > 0 && (
|
|
157
|
+
<CollapsibleGroup
|
|
158
|
+
isCollapsed={collapsedGroups[`${item.href}-entities`]}
|
|
159
|
+
onToggle={() => toggleGroupCollapse(`${item.href}-entities`)}
|
|
160
|
+
title={
|
|
161
|
+
<button
|
|
162
|
+
onClick={(e) => {
|
|
163
|
+
e.stopPropagation();
|
|
164
|
+
toggleGroupCollapse(`${item.href}-entities`);
|
|
165
|
+
}}
|
|
166
|
+
className="truncate underline ml-2 text-xs mb-1 py-1"
|
|
167
|
+
>
|
|
168
|
+
Entities ({item.entities.length})
|
|
169
|
+
</button>
|
|
170
|
+
}
|
|
171
|
+
>
|
|
172
|
+
<MessageList messages={item.entities} decodedCurrentPath={decodedCurrentPath} />
|
|
173
|
+
</CollapsibleGroup>
|
|
174
|
+
)}
|
|
151
175
|
</div>
|
|
152
176
|
</CollapsibleGroup>
|
|
153
177
|
)
|
|
@@ -169,6 +193,7 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
|
|
|
169
193
|
});
|
|
170
194
|
|
|
171
195
|
const decodedCurrentPath = window.location.pathname;
|
|
196
|
+
const isVisualizer = window.location.pathname.includes('/visualiser/');
|
|
172
197
|
|
|
173
198
|
useEffect(() => {
|
|
174
199
|
const timer = setTimeout(() => {
|
|
@@ -341,16 +366,37 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
|
|
|
341
366
|
>
|
|
342
367
|
<span className="truncate">Architecture</span>
|
|
343
368
|
</a>
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
369
|
+
{!isVisualizer && (
|
|
370
|
+
<a
|
|
371
|
+
href={buildUrl(`/docs/domains/${item.id}/language`)}
|
|
372
|
+
className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
|
|
373
|
+
decodedCurrentPath.includes(`/docs/domains/${item.id}/language`)
|
|
374
|
+
? 'bg-purple-100 '
|
|
375
|
+
: 'hover:bg-purple-100'
|
|
376
|
+
}`}
|
|
377
|
+
>
|
|
378
|
+
<span className="truncate">Ubiquitous Language</span>
|
|
379
|
+
</a>
|
|
380
|
+
)}
|
|
381
|
+
{item.entities.length > 0 && !isVisualizer && (
|
|
382
|
+
<CollapsibleGroup
|
|
383
|
+
isCollapsed={collapsedGroups[`${item.href}-entities`]}
|
|
384
|
+
onToggle={() => toggleGroupCollapse(`${item.href}-entities`)}
|
|
385
|
+
title={
|
|
386
|
+
<button
|
|
387
|
+
onClick={(e) => {
|
|
388
|
+
e.stopPropagation();
|
|
389
|
+
toggleGroupCollapse(`${item.href}-entities`);
|
|
390
|
+
}}
|
|
391
|
+
className="truncate underline ml-2 text-xs mb-1 py-1"
|
|
392
|
+
>
|
|
393
|
+
Entities ({item.entities.length})
|
|
394
|
+
</button>
|
|
395
|
+
}
|
|
396
|
+
>
|
|
397
|
+
<MessageList messages={item.entities} decodedCurrentPath={decodedCurrentPath} />
|
|
398
|
+
</CollapsibleGroup>
|
|
399
|
+
)}
|
|
354
400
|
</div>
|
|
355
401
|
</div>
|
|
356
402
|
</li>
|
|
@@ -369,6 +415,7 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
|
|
|
369
415
|
decodedCurrentPath={decodedCurrentPath}
|
|
370
416
|
collapsedGroups={collapsedGroups}
|
|
371
417
|
toggleGroupCollapse={toggleGroupCollapse}
|
|
418
|
+
isVisualizer={isVisualizer}
|
|
372
419
|
/>
|
|
373
420
|
))}
|
|
374
421
|
</ul>
|
|
@@ -390,7 +437,7 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
|
|
|
390
437
|
<span
|
|
391
438
|
className={`ml-2 text-[10px] font-medium px-2 uppercase py-0.5 rounded ${getMessageColorByCollection(item.collection)}`}
|
|
392
439
|
>
|
|
393
|
-
{getMessageCollectionName(item.collection)}
|
|
440
|
+
{getMessageCollectionName(item.collection, item)}
|
|
394
441
|
</span>
|
|
395
442
|
</a>
|
|
396
443
|
</li>
|
|
@@ -10,6 +10,13 @@ export interface MessageItem {
|
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export interface EntityItem {
|
|
14
|
+
href: string;
|
|
15
|
+
label: string;
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
export interface ServiceItem {
|
|
14
21
|
href: string;
|
|
15
22
|
label: string;
|
|
@@ -18,6 +25,7 @@ export interface ServiceItem {
|
|
|
18
25
|
version: string;
|
|
19
26
|
sends: MessageItem[];
|
|
20
27
|
receives: MessageItem[];
|
|
28
|
+
entities: EntityItem[];
|
|
21
29
|
specifications?: {
|
|
22
30
|
asyncapiPath: string;
|
|
23
31
|
openapiPath: string;
|
|
@@ -36,10 +36,13 @@ export async function getResourcesForNavigation({ currentPath }: { currentPath:
|
|
|
36
36
|
const title = item.collection;
|
|
37
37
|
const group = acc[title] || [];
|
|
38
38
|
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const receives = item.collection === 'services' ? item.data.receives || null : null;
|
|
39
|
+
const isCollectionDomain = item.collection === 'domains';
|
|
40
|
+
const isCollectionService = item.collection === 'services';
|
|
42
41
|
|
|
42
|
+
const servicesCount = isCollectionDomain ? item.data.services?.length || 0 : 0;
|
|
43
|
+
const sends = isCollectionService ? item.data.sends || null : null;
|
|
44
|
+
const receives = isCollectionService ? item.data.receives || null : null;
|
|
45
|
+
const entities = isCollectionDomain || isCollectionService ? item.data.entities || null : null;
|
|
43
46
|
// Add href to the sends and receives
|
|
44
47
|
const sendsWithHref = sends?.map((send: any) => ({
|
|
45
48
|
...send,
|
|
@@ -49,6 +52,10 @@ export async function getResourcesForNavigation({ currentPath }: { currentPath:
|
|
|
49
52
|
...receive,
|
|
50
53
|
href: buildUrl(`/${route}/${receive.collection}/${receive.data.id}/${receive.data.version}`),
|
|
51
54
|
}));
|
|
55
|
+
const entitiesWithHref = entities?.map((entity: any) => ({
|
|
56
|
+
...entity,
|
|
57
|
+
href: buildUrl(`/${route}/${entity.collection}/${entity.data.id}/${entity.data.version}`),
|
|
58
|
+
}));
|
|
52
59
|
|
|
53
60
|
const navigationItem = {
|
|
54
61
|
label: item.data.name,
|
|
@@ -64,11 +71,12 @@ export async function getResourcesForNavigation({ currentPath }: { currentPath:
|
|
|
64
71
|
servicesCount,
|
|
65
72
|
id: item.data.id,
|
|
66
73
|
name: item.data.name,
|
|
67
|
-
services:
|
|
68
|
-
domains:
|
|
74
|
+
services: isCollectionDomain ? item.data.services : null,
|
|
75
|
+
domains: isCollectionDomain ? item.data.domains : null,
|
|
69
76
|
sends: sendsWithHref,
|
|
70
77
|
receives: receivesWithHref,
|
|
71
|
-
|
|
78
|
+
entities: entitiesWithHref,
|
|
79
|
+
specifications: isCollectionService ? item.data.specifications : null,
|
|
72
80
|
sidebar: item.data?.sidebar,
|
|
73
81
|
};
|
|
74
82
|
|
|
@@ -18,7 +18,7 @@ export type TreeNode = {
|
|
|
18
18
|
/**
|
|
19
19
|
* Resource types that should be in the sidenav
|
|
20
20
|
*/
|
|
21
|
-
const RESOURCE_TYPES = ['domains', 'services', 'events', 'commands', 'queries', 'flows', 'channels'];
|
|
21
|
+
const RESOURCE_TYPES = ['domains', 'entities', 'services', 'events', 'commands', 'queries', 'flows', 'channels'];
|
|
22
22
|
// const RESOURCE_TYPES = ['domains', 'services', 'events', 'commands', 'queries', 'flows', 'channels'];
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -295,6 +295,7 @@ const services = defineCollection({
|
|
|
295
295
|
.object({
|
|
296
296
|
sends: z.array(pointer).optional(),
|
|
297
297
|
receives: z.array(pointer).optional(),
|
|
298
|
+
entities: z.array(pointer).optional(),
|
|
298
299
|
})
|
|
299
300
|
.merge(baseSchema),
|
|
300
301
|
});
|
|
@@ -332,6 +333,7 @@ const domains = defineCollection({
|
|
|
332
333
|
.object({
|
|
333
334
|
services: z.array(pointer).optional(),
|
|
334
335
|
domains: z.array(pointer).optional(),
|
|
336
|
+
entities: z.array(pointer).optional(),
|
|
335
337
|
})
|
|
336
338
|
.merge(baseSchema),
|
|
337
339
|
});
|
|
@@ -387,6 +389,35 @@ const ubiquitousLanguages = defineCollection({
|
|
|
387
389
|
}),
|
|
388
390
|
});
|
|
389
391
|
|
|
392
|
+
const entities = defineCollection({
|
|
393
|
+
loader: glob({
|
|
394
|
+
pattern: ['**/entities/*/index.(md|mdx)', '**/entities/*/versioned/*/index.(md|mdx)'],
|
|
395
|
+
base: projectDirBase,
|
|
396
|
+
generateId: ({ data, ...rest }) => {
|
|
397
|
+
return `${data.id}-${data.version}`;
|
|
398
|
+
},
|
|
399
|
+
}),
|
|
400
|
+
schema: z
|
|
401
|
+
.object({
|
|
402
|
+
aggregateRoot: z.boolean().optional(),
|
|
403
|
+
identifier: z.string().optional(),
|
|
404
|
+
properties: z
|
|
405
|
+
.array(
|
|
406
|
+
z.object({
|
|
407
|
+
name: z.string(),
|
|
408
|
+
type: z.string(),
|
|
409
|
+
required: z.boolean().optional(),
|
|
410
|
+
description: z.string().optional(),
|
|
411
|
+
})
|
|
412
|
+
)
|
|
413
|
+
.optional(),
|
|
414
|
+
services: z.array(reference('services')).optional(),
|
|
415
|
+
domains: z.array(reference('domains')).optional(),
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
.merge(baseSchema),
|
|
419
|
+
});
|
|
420
|
+
|
|
390
421
|
const users = defineCollection({
|
|
391
422
|
loader: glob({ pattern: 'users/*.(md|mdx)', base: projectDirBase, generateId: ({ data }) => data.id as string }),
|
|
392
423
|
schema: z.object({
|
|
@@ -438,7 +469,10 @@ export const collections = {
|
|
|
438
469
|
flows,
|
|
439
470
|
pages,
|
|
440
471
|
changelogs,
|
|
472
|
+
|
|
473
|
+
// DDD Collections
|
|
441
474
|
ubiquitousLanguages,
|
|
475
|
+
entities,
|
|
442
476
|
|
|
443
477
|
// EventCatalog Pro Collections
|
|
444
478
|
customPages,
|
|
@@ -14,7 +14,7 @@ import MessageSideBar from '@components/SideBars/MessageSideBar.astro';
|
|
|
14
14
|
import DomainSideBar from '@components/SideBars/DomainSideBar.astro';
|
|
15
15
|
import ChannelSideBar from '@components/SideBars/ChannelSideBar.astro';
|
|
16
16
|
import FlowSideBar from '@components/SideBars/FlowSideBar.astro';
|
|
17
|
-
|
|
17
|
+
import EntitySideBar from '@components/SideBars/EntitySideBar.astro';
|
|
18
18
|
import {
|
|
19
19
|
QueueListIcon,
|
|
20
20
|
RectangleGroupIcon,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
MagnifyingGlassIcon,
|
|
25
25
|
} from '@heroicons/react/24/outline';
|
|
26
26
|
import { ArrowsRightLeftIcon } from '@heroicons/react/20/solid';
|
|
27
|
-
|
|
27
|
+
import { Box, Boxes } from 'lucide-react';
|
|
28
28
|
import type { PageTypes } from '@types';
|
|
29
29
|
import type { CollectionTypes } from '@types';
|
|
30
30
|
|
|
@@ -39,7 +39,7 @@ import { buildUrl } from '@utils/url-builder';
|
|
|
39
39
|
import config from '@config';
|
|
40
40
|
|
|
41
41
|
export async function getStaticPaths() {
|
|
42
|
-
const itemTypes: PageTypes[] = ['events', 'commands', 'queries', 'services', 'domains', 'flows', 'channels'];
|
|
42
|
+
const itemTypes: PageTypes[] = ['events', 'commands', 'queries', 'services', 'domains', 'flows', 'channels', 'entities'];
|
|
43
43
|
const allItems = await Promise.all(itemTypes.map((type) => pageDataLoader[type]()));
|
|
44
44
|
|
|
45
45
|
return allItems.flatMap((items, index) =>
|
|
@@ -74,36 +74,54 @@ const getContentBadges = () =>
|
|
|
74
74
|
|
|
75
75
|
const getBadge = () => {
|
|
76
76
|
if (props.collection === 'services') {
|
|
77
|
-
return { backgroundColor: 'pink', textColor: 'pink', content: 'Service', icon: ServerIcon, class: 'text-pink-400' };
|
|
77
|
+
return [{ backgroundColor: 'pink', textColor: 'pink', content: 'Service', icon: ServerIcon, class: 'text-pink-400' }];
|
|
78
78
|
}
|
|
79
79
|
if (props.collection === 'events') {
|
|
80
|
-
return { backgroundColor: 'orange', textColor: 'orange', content: 'Event', icon: BoltIcon, class: 'text-orange-400' };
|
|
80
|
+
return [{ backgroundColor: 'orange', textColor: 'orange', content: 'Event', icon: BoltIcon, class: 'text-orange-400' }];
|
|
81
81
|
}
|
|
82
82
|
if (props.collection === 'commands') {
|
|
83
|
-
return { backgroundColor: 'blue', textColor: 'blue', content: 'Command', icon: ChatBubbleLeftIcon, class: 'text-blue-400' };
|
|
83
|
+
return [{ backgroundColor: 'blue', textColor: 'blue', content: 'Command', icon: ChatBubbleLeftIcon, class: 'text-blue-400' }];
|
|
84
84
|
}
|
|
85
85
|
if (props.collection === 'queries') {
|
|
86
|
-
return
|
|
86
|
+
return [
|
|
87
|
+
{ backgroundColor: 'green', textColor: 'green', content: 'Query', icon: MagnifyingGlassIcon, class: 'text-green-400' },
|
|
88
|
+
];
|
|
87
89
|
}
|
|
88
90
|
if (props.collection === 'domains') {
|
|
89
|
-
return
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
return [
|
|
92
|
+
{
|
|
93
|
+
backgroundColor: 'yellow',
|
|
94
|
+
textColor: 'yellow',
|
|
95
|
+
content: 'Domain',
|
|
96
|
+
icon: RectangleGroupIcon,
|
|
97
|
+
class: 'text-yellow-400',
|
|
98
|
+
},
|
|
99
|
+
];
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
if (props.collection === 'flows') {
|
|
99
|
-
return { backgroundColor: 'teal', textColor: 'teal', content: 'Flow', icon: QueueListIcon, class: 'text-gray' };
|
|
103
|
+
return [{ backgroundColor: 'teal', textColor: 'teal', content: 'Flow', icon: QueueListIcon, class: 'text-gray' }];
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
if (props.collection === 'channels') {
|
|
103
|
-
return { backgroundColor: 'teal', textColor: 'teal', content: 'Channel', icon: ArrowsRightLeftIcon, class: 'text-gray' };
|
|
107
|
+
return [{ backgroundColor: 'teal', textColor: 'teal', content: 'Channel', icon: ArrowsRightLeftIcon, class: 'text-gray' }];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (props.collection === 'entities') {
|
|
111
|
+
const entityBadges = [{ backgroundColor: 'purple', textColor: 'purple', content: 'Entity', icon: Box, class: 'text-gray' }];
|
|
112
|
+
if (props.data.aggregateRoot) {
|
|
113
|
+
entityBadges.push({
|
|
114
|
+
backgroundColor: 'purple',
|
|
115
|
+
textColor: 'purple',
|
|
116
|
+
content: '(Aggregate Root)',
|
|
117
|
+
icon: Boxes,
|
|
118
|
+
class: 'text-gray',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return entityBadges;
|
|
104
122
|
}
|
|
105
123
|
|
|
106
|
-
return { backgroundColor: 'teal', textColor: 'teal', content: '', icon: QueueListIcon, class: 'text-gray' };
|
|
124
|
+
return [{ backgroundColor: 'teal', textColor: 'teal', content: '', icon: QueueListIcon, class: 'text-gray' }];
|
|
107
125
|
};
|
|
108
126
|
|
|
109
127
|
const getSpecificationBadges = () => {
|
|
@@ -138,7 +156,7 @@ const getSpecificationBadges = () => {
|
|
|
138
156
|
return badges;
|
|
139
157
|
};
|
|
140
158
|
|
|
141
|
-
const badges = [getBadge(), ...getContentBadges(), ...getSpecificationBadges()];
|
|
159
|
+
const badges = [...getBadge(), ...getContentBadges(), ...getSpecificationBadges()];
|
|
142
160
|
|
|
143
161
|
// Index only the latest version
|
|
144
162
|
const pagefindAttributes =
|
|
@@ -299,6 +317,7 @@ friendlyCollectionName = friendlyCollectionName === 'querie' ? 'query' : friendl
|
|
|
299
317
|
{props?.collection === 'domains' && <DomainSideBar domain={props} />}
|
|
300
318
|
{props?.collection === 'channels' && <ChannelSideBar channel={props} />}
|
|
301
319
|
{props?.collection === 'flows' && <FlowSideBar flow={props} />}
|
|
320
|
+
{props?.collection === 'entities' && <EntitySideBar entity={props} />}
|
|
302
321
|
</aside>
|
|
303
322
|
</div>
|
|
304
323
|
<ClientRouter />
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { APIRoute } from 'astro';
|
|
6
6
|
import { getCollection } from 'astro:content';
|
|
7
|
+
import { getEntities } from '@utils/entities';
|
|
7
8
|
import config from '@config';
|
|
8
9
|
import fs from 'fs';
|
|
9
10
|
|
|
@@ -14,7 +15,7 @@ const services = await getCollection('services');
|
|
|
14
15
|
const domains = await getCollection('domains');
|
|
15
16
|
const flows = await getCollection('flows');
|
|
16
17
|
const channels = await getCollection('channels');
|
|
17
|
-
|
|
18
|
+
const entities = await getEntities();
|
|
18
19
|
export async function getStaticPaths() {
|
|
19
20
|
// Just return empty array if LLMs are not enabled
|
|
20
21
|
if (!config.llmsTxt?.enabled) {
|
|
@@ -29,6 +30,7 @@ export async function getStaticPaths() {
|
|
|
29
30
|
domains,
|
|
30
31
|
flows,
|
|
31
32
|
channels,
|
|
33
|
+
entities,
|
|
32
34
|
};
|
|
33
35
|
const paths = Object.keys(collections).map((type) => {
|
|
34
36
|
return collections[type as keyof typeof collections].map((item: { data: { id: string; version: string } }) => ({
|
|
@@ -14,6 +14,7 @@ const services = await getCollection('services');
|
|
|
14
14
|
const domains = await getCollection('domains');
|
|
15
15
|
const flows = await getCollection('flows');
|
|
16
16
|
const channels = await getCollection('channels');
|
|
17
|
+
const entities = await getCollection('entities');
|
|
17
18
|
|
|
18
19
|
export async function getStaticPaths() {
|
|
19
20
|
// Just return empty array if LLMs are not enabled
|
|
@@ -29,6 +30,7 @@ export async function getStaticPaths() {
|
|
|
29
30
|
domains,
|
|
30
31
|
flows,
|
|
31
32
|
channels,
|
|
33
|
+
entities,
|
|
32
34
|
};
|
|
33
35
|
const paths = Object.keys(collections).map((type) => {
|
|
34
36
|
return collections[type as keyof typeof collections].map((item: { data: { id: string; version: string } }) => ({
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import Seo from '@components/Seo.astro';
|
|
3
3
|
import { buildUrl } from '@utils/url-builder';
|
|
4
4
|
import { getEvents } from '@utils/events';
|
|
5
|
+
import { getEntities } from '@utils/entities';
|
|
5
6
|
import { getCommands } from '@utils/commands';
|
|
6
7
|
import { getServices } from '@utils/collections/services';
|
|
7
8
|
import { getDomains } from '@utils/collections/domains';
|
|
@@ -10,12 +11,13 @@ import type { CollectionTypes } from '@types';
|
|
|
10
11
|
import { getChannels } from '@utils/channels';
|
|
11
12
|
|
|
12
13
|
export async function getStaticPaths() {
|
|
13
|
-
const [events, commands, services, domains, channels] = await Promise.all([
|
|
14
|
+
const [events, commands, services, domains, channels, entities] = await Promise.all([
|
|
14
15
|
getEvents(),
|
|
15
16
|
getCommands(),
|
|
16
17
|
getServices(),
|
|
17
18
|
getDomains(),
|
|
18
19
|
getChannels(),
|
|
20
|
+
getEntities(),
|
|
19
21
|
]);
|
|
20
22
|
|
|
21
23
|
const buildPages = (collection: CollectionEntry<CollectionTypes>[]) => {
|
|
@@ -37,6 +39,7 @@ export async function getStaticPaths() {
|
|
|
37
39
|
...buildPages(services),
|
|
38
40
|
...buildPages(commands),
|
|
39
41
|
...buildPages(channels),
|
|
42
|
+
...buildPages(entities),
|
|
40
43
|
];
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type CollectionTypes = 'commands' | 'events' | 'queries' | 'domains' | 'services' | 'flows' | 'channels';
|
|
1
|
+
export type CollectionTypes = 'commands' | 'events' | 'queries' | 'domains' | 'services' | 'flows' | 'channels' | 'entities';
|
|
2
2
|
export type CollectionMessageTypes = 'commands' | 'events' | 'queries';
|
|
3
3
|
export type CollectionUserTypes = 'users';
|
|
4
|
-
export type PageTypes = 'events' | 'commands' | 'queries' | 'services' | 'domains' | 'channels' | 'flows';
|
|
4
|
+
export type PageTypes = 'events' | 'commands' | 'queries' | 'services' | 'domains' | 'channels' | 'flows' | 'entities';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as HeroIcons from '@heroicons/react/24/outline';
|
|
2
|
+
import * as ProtocolIcons from '@icons/protocols';
|
|
3
|
+
|
|
4
|
+
// const allIcons = [...HeroIcons, ...ProtocolIcons];
|
|
5
|
+
|
|
6
|
+
const protocolIcons = Object.keys(ProtocolIcons).reduce(
|
|
7
|
+
(icons, key) => {
|
|
8
|
+
const iconKey = key as keyof typeof ProtocolIcons;
|
|
9
|
+
icons[key.toLowerCase()] = ProtocolIcons[iconKey];
|
|
10
|
+
return icons;
|
|
11
|
+
},
|
|
12
|
+
{} as { [key: string]: string }
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const getIconForProtocol = (icon: keyof typeof protocolIcons) => {
|
|
16
|
+
const Icon = protocolIcons[icon];
|
|
17
|
+
return Icon ? (props: any) => <span {...props} dangerouslySetInnerHTML={{ __html: Icon }} /> : null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function getIcon(iconName: string) {
|
|
21
|
+
return getIconForProtocol(iconName as keyof typeof protocolIcons) || HeroIcons[iconName as keyof typeof HeroIcons] || null;
|
|
22
|
+
}
|
|
@@ -34,6 +34,7 @@ export const getDomains = async ({ getAllVersions = true }: Props = {}): Promise
|
|
|
34
34
|
|
|
35
35
|
// Get all the services that are not versioned
|
|
36
36
|
const servicesCollection = await getCollection('services');
|
|
37
|
+
const entitiesCollection = await getCollection('entities');
|
|
37
38
|
|
|
38
39
|
// @ts-ignore // TODO: Fix this type
|
|
39
40
|
cachedDomains[cacheKey] = domains.map((domain) => {
|
|
@@ -42,7 +43,7 @@ export const getDomains = async ({ getAllVersions = true }: Props = {}): Promise
|
|
|
42
43
|
// const receives = service.data.receives || [];
|
|
43
44
|
const servicesInDomain = domain.data.services || [];
|
|
44
45
|
const subDomainsInDomain = domain.data.domains || [];
|
|
45
|
-
|
|
46
|
+
const entitiesInDomain = domain.data.entities || [];
|
|
46
47
|
const subDomains = subDomainsInDomain
|
|
47
48
|
.map((_subDomain: { id: string; version: string | undefined }) =>
|
|
48
49
|
getItemsFromCollectionByIdAndSemverOrLatest(domains, _subDomain.id, _subDomain.version)
|
|
@@ -60,12 +61,19 @@ export const getDomains = async ({ getAllVersions = true }: Props = {}): Promise
|
|
|
60
61
|
)
|
|
61
62
|
.flat();
|
|
62
63
|
|
|
64
|
+
const entities = [...entitiesInDomain]
|
|
65
|
+
.map((_entity: { id: string; version: string | undefined }) =>
|
|
66
|
+
getItemsFromCollectionByIdAndSemverOrLatest(entitiesCollection, _entity.id, _entity.version)
|
|
67
|
+
)
|
|
68
|
+
.flat();
|
|
69
|
+
|
|
63
70
|
return {
|
|
64
71
|
...domain,
|
|
65
72
|
data: {
|
|
66
73
|
...domain.data,
|
|
67
74
|
services: services,
|
|
68
75
|
domains: subDomains,
|
|
76
|
+
entities: entities,
|
|
69
77
|
latestVersion,
|
|
70
78
|
versions,
|
|
71
79
|
},
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
VariableIcon,
|
|
12
12
|
MapIcon,
|
|
13
13
|
} from '@heroicons/react/24/outline';
|
|
14
|
-
import { BookText } from 'lucide-react';
|
|
14
|
+
import { BookText, Box } from 'lucide-react';
|
|
15
15
|
|
|
16
16
|
export const getIconForCollection = (collection: string) => {
|
|
17
17
|
switch (collection) {
|
|
@@ -39,6 +39,8 @@ export const getIconForCollection = (collection: string) => {
|
|
|
39
39
|
return BookText;
|
|
40
40
|
case 'bounded-context-map':
|
|
41
41
|
return MapIcon;
|
|
42
|
+
case 'entities':
|
|
43
|
+
return Box;
|
|
42
44
|
default:
|
|
43
45
|
return ServerIcon;
|
|
44
46
|
}
|
|
@@ -64,6 +66,8 @@ export const getColorAndIconForCollection = (collection: string) => {
|
|
|
64
66
|
return { color: 'purple', Icon: icon };
|
|
65
67
|
case 'ubiquitousLanguages':
|
|
66
68
|
return { color: 'green', Icon: icon };
|
|
69
|
+
case 'entities':
|
|
70
|
+
return { color: 'purple', Icon: icon };
|
|
67
71
|
case 'domains':
|
|
68
72
|
return { color: 'yellow', Icon: icon };
|
|
69
73
|
case 'services':
|
|
@@ -34,7 +34,7 @@ export const getServices = async ({ getAllVersions = true }: Props = {}): Promis
|
|
|
34
34
|
const events = await getCollection('events');
|
|
35
35
|
const commands = await getCollection('commands');
|
|
36
36
|
const queries = await getCollection('queries');
|
|
37
|
-
|
|
37
|
+
const entities = await getCollection('entities');
|
|
38
38
|
const allMessages = [...events, ...commands, ...queries];
|
|
39
39
|
|
|
40
40
|
// @ts-ignore // TODO: Fix this type
|
|
@@ -43,6 +43,7 @@ export const getServices = async ({ getAllVersions = true }: Props = {}): Promis
|
|
|
43
43
|
|
|
44
44
|
const sendsMessages = service.data.sends || [];
|
|
45
45
|
const receivesMessages = service.data.receives || [];
|
|
46
|
+
const serviceEntities = service.data.entities || [];
|
|
46
47
|
|
|
47
48
|
const sends = sendsMessages
|
|
48
49
|
.map((message: any) => getItemsFromCollectionByIdAndSemverOrLatest(allMessages, message.id, message.version))
|
|
@@ -54,6 +55,11 @@ export const getServices = async ({ getAllVersions = true }: Props = {}): Promis
|
|
|
54
55
|
.flat()
|
|
55
56
|
.filter((e: any) => e !== undefined);
|
|
56
57
|
|
|
58
|
+
const mappedEntities = serviceEntities
|
|
59
|
+
.map((entity: any) => getItemsFromCollectionByIdAndSemverOrLatest(entities, entity.id, entity.version))
|
|
60
|
+
.flat()
|
|
61
|
+
.filter((e: any) => e !== undefined);
|
|
62
|
+
|
|
57
63
|
return {
|
|
58
64
|
...service,
|
|
59
65
|
data: {
|
|
@@ -62,6 +68,7 @@ export const getServices = async ({ getAllVersions = true }: Props = {}): Promis
|
|
|
62
68
|
sends,
|
|
63
69
|
versions,
|
|
64
70
|
latestVersion,
|
|
71
|
+
entities: mappedEntities,
|
|
65
72
|
},
|
|
66
73
|
// TODO: verify if it could be deleted.
|
|
67
74
|
nodes: {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getCollection } from 'astro:content';
|
|
2
|
+
import type { CollectionEntry } from 'astro:content';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { getVersionForCollectionItem, satisfies } from './collections/util';
|
|
5
|
+
|
|
6
|
+
const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
|
|
7
|
+
|
|
8
|
+
type Entity = CollectionEntry<'entities'> & {
|
|
9
|
+
catalog: {
|
|
10
|
+
path: string;
|
|
11
|
+
filePath: string;
|
|
12
|
+
type: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
getAllVersions?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// cache for build time
|
|
21
|
+
let cachedEntities: Record<string, Entity[]> = {
|
|
22
|
+
allVersions: [],
|
|
23
|
+
currentVersions: [],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const getEntities = async ({ getAllVersions = true }: Props = {}): Promise<Entity[]> => {
|
|
27
|
+
const cacheKey = getAllVersions ? 'allVersions' : 'currentVersions';
|
|
28
|
+
|
|
29
|
+
if (cachedEntities[cacheKey].length > 0) {
|
|
30
|
+
return cachedEntities[cacheKey];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const entities = await getCollection('entities', (entity) => {
|
|
34
|
+
return (getAllVersions || !entity.filePath?.includes('versioned')) && entity.data.hidden !== true;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const services = await getCollection('services');
|
|
38
|
+
const domains = await getCollection('domains');
|
|
39
|
+
|
|
40
|
+
cachedEntities[cacheKey] = entities.map((entity) => {
|
|
41
|
+
const { latestVersion, versions } = getVersionForCollectionItem(entity, entities);
|
|
42
|
+
|
|
43
|
+
const servicesThatReferenceEntity = services.filter((service) =>
|
|
44
|
+
service.data.entities?.some((item) => {
|
|
45
|
+
if (item.id != entity.data.id) return false;
|
|
46
|
+
if (item.version == 'latest' || item.version == undefined) return entity.data.version == latestVersion;
|
|
47
|
+
return satisfies(entity.data.version, item.version);
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const domainsThatReferenceEntity = domains.filter((domain) =>
|
|
52
|
+
domain.data.entities?.some((item) => {
|
|
53
|
+
if (item.id != entity.data.id) return false;
|
|
54
|
+
if (item.version == 'latest' || item.version == undefined) return entity.data.version == latestVersion;
|
|
55
|
+
return satisfies(entity.data.version, item.version);
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
...entity,
|
|
61
|
+
data: {
|
|
62
|
+
...entity.data,
|
|
63
|
+
versions,
|
|
64
|
+
latestVersion,
|
|
65
|
+
services: servicesThatReferenceEntity,
|
|
66
|
+
domains: domainsThatReferenceEntity,
|
|
67
|
+
},
|
|
68
|
+
catalog: {
|
|
69
|
+
path: path.join(entity.collection, entity.id.replace('/index.mdx', '')),
|
|
70
|
+
absoluteFilePath: path.join(PROJECT_DIR, entity.collection, entity.id.replace('/index.mdx', '/index.md')),
|
|
71
|
+
astroContentFilePath: path.join(process.cwd(), 'src', 'content', entity.collection, entity.id),
|
|
72
|
+
filePath: path.join(process.cwd(), 'src', 'catalog-files', entity.collection, entity.id.replace('/index.mdx', '')),
|
|
73
|
+
publicPath: path.join('/generated', entity.collection, entity.id.replace(`-${entity.data.version}`, '')),
|
|
74
|
+
type: 'entity',
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// order them by the name of the event
|
|
80
|
+
cachedEntities[cacheKey].sort((a, b) => {
|
|
81
|
+
return (a.data.name || a.data.id).localeCompare(b.data.name || b.data.id);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return cachedEntities[cacheKey];
|
|
85
|
+
};
|
|
@@ -5,6 +5,7 @@ import { getCommands, getEvents } from '@utils/messages';
|
|
|
5
5
|
import { getQueries } from '@utils/queries';
|
|
6
6
|
import { getServices } from '@utils/collections/services';
|
|
7
7
|
import { getFlows } from '@utils/collections/flows';
|
|
8
|
+
import { getEntities } from '@utils/entities';
|
|
8
9
|
import type { CollectionEntry } from 'astro:content';
|
|
9
10
|
|
|
10
11
|
export const pageDataLoader: Record<PageTypes, () => Promise<CollectionEntry<CollectionTypes>[]>> = {
|
|
@@ -15,4 +16,5 @@ export const pageDataLoader: Record<PageTypes, () => Promise<CollectionEntry<Col
|
|
|
15
16
|
domains: getDomains,
|
|
16
17
|
channels: getChannels,
|
|
17
18
|
flows: getFlows,
|
|
19
|
+
entities: getEntities,
|
|
18
20
|
};
|
package/package.json
CHANGED