@eventcatalog/core 2.35.10 → 2.36.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.
Files changed (38) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-BLST6HNF.js → chunk-MLYXGJNO.js} +1 -1
  6. package/dist/{chunk-APUSQIXG.js → chunk-TBBPXKB3.js} +1 -1
  7. package/dist/{chunk-X5PED26N.js → chunk-WT5JS7WG.js} +1 -1
  8. package/dist/constants.cjs +1 -1
  9. package/dist/constants.js +1 -1
  10. package/dist/eventcatalog.cjs +1 -1
  11. package/dist/eventcatalog.js +3 -3
  12. package/eventcatalog/src/components/Grids/DomainGrid.tsx +9 -0
  13. package/eventcatalog/src/components/Grids/ServiceGrid.tsx +19 -0
  14. package/eventcatalog/src/components/MDX/EntityPropertiesTable/EntityPropertiesTable.astro +106 -0
  15. package/eventcatalog/src/components/MDX/components.tsx +2 -0
  16. package/eventcatalog/src/components/SideBars/DomainSideBar.astro +22 -0
  17. package/eventcatalog/src/components/SideBars/EntitySideBar.astro +94 -0
  18. package/eventcatalog/src/components/SideBars/ServiceSideBar.astro +23 -0
  19. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/MessageList.tsx +1 -1
  20. package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +59 -12
  21. package/eventcatalog/src/components/SideNav/ListViewSideBar/types.ts +8 -0
  22. package/eventcatalog/src/components/SideNav/ListViewSideBar/utils.ts +14 -6
  23. package/eventcatalog/src/components/SideNav/TreeView/getTreeView.ts +1 -1
  24. package/eventcatalog/src/content.config.ts +34 -0
  25. package/eventcatalog/src/icons/protocols/index.ts +2 -0
  26. package/eventcatalog/src/icons/protocols/tibo-ftl.svg +1 -0
  27. package/eventcatalog/src/icons/protocols/tibo-rv.svg +1 -0
  28. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +37 -18
  29. package/eventcatalog/src/pages/docs/[type]/[id]/[version].md.ts +3 -1
  30. package/eventcatalog/src/pages/docs/[type]/[id]/[version].mdx.ts +2 -0
  31. package/eventcatalog/src/pages/docs/[type]/[id]/index.astro +4 -1
  32. package/eventcatalog/src/types/index.ts +2 -2
  33. package/eventcatalog/src/utils/collections/domains.ts +9 -1
  34. package/eventcatalog/src/utils/collections/icons.ts +5 -1
  35. package/eventcatalog/src/utils/collections/services.ts +8 -1
  36. package/eventcatalog/src/utils/entities.ts +85 -0
  37. package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +2 -0
  38. package/package.json +1 -1
@@ -37,7 +37,7 @@ var import_axios = __toESM(require("axios"), 1);
37
37
  var import_os = __toESM(require("os"), 1);
38
38
 
39
39
  // package.json
40
- var version = "2.35.10";
40
+ var version = "2.36.1";
41
41
 
42
42
  // src/constants.ts
43
43
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-APUSQIXG.js";
4
- import "../chunk-BLST6HNF.js";
3
+ } from "../chunk-TBBPXKB3.js";
4
+ import "../chunk-MLYXGJNO.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -106,7 +106,7 @@ var import_axios = __toESM(require("axios"), 1);
106
106
  var import_os = __toESM(require("os"), 1);
107
107
 
108
108
  // package.json
109
- var version = "2.35.10";
109
+ var version = "2.36.1";
110
110
 
111
111
  // src/constants.ts
112
112
  var VERSION = version;
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  log_build_default
3
- } from "../chunk-X5PED26N.js";
4
- import "../chunk-APUSQIXG.js";
5
- import "../chunk-BLST6HNF.js";
3
+ } from "../chunk-WT5JS7WG.js";
4
+ import "../chunk-TBBPXKB3.js";
5
+ import "../chunk-MLYXGJNO.js";
6
6
  import "../chunk-E7TXTI7G.js";
7
7
  export {
8
8
  log_build_default as default
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "2.35.10";
2
+ var version = "2.36.1";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-BLST6HNF.js";
3
+ } from "./chunk-MLYXGJNO.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import axios from "axios";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-APUSQIXG.js";
3
+ } from "./chunk-TBBPXKB3.js";
4
4
  import {
5
5
  getEventCatalogConfigFile,
6
6
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "2.35.10";
28
+ var version = "2.36.1";
29
29
 
30
30
  // src/constants.ts
31
31
  var VERSION = version;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-BLST6HNF.js";
3
+ } from "./chunk-MLYXGJNO.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -157,7 +157,7 @@ var import_axios = __toESM(require("axios"), 1);
157
157
  var import_os = __toESM(require("os"), 1);
158
158
 
159
159
  // package.json
160
- var version = "2.35.10";
160
+ var version = "2.36.1";
161
161
 
162
162
  // src/constants.ts
163
163
  var VERSION = version;
@@ -6,15 +6,15 @@ import {
6
6
  } from "./chunk-DCLTVJDP.js";
7
7
  import {
8
8
  log_build_default
9
- } from "./chunk-X5PED26N.js";
10
- import "./chunk-APUSQIXG.js";
9
+ } from "./chunk-WT5JS7WG.js";
10
+ import "./chunk-TBBPXKB3.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-BLST6HNF.js";
17
+ } from "./chunk-MLYXGJNO.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&lt;<code class="text-sm bg-gray-100 rounded px-1 py-0.5">{prop.items.type}</code>&gt;
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
- <a
345
- href={buildUrl(`/docs/domains/${item.id}/language`)}
346
- className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
347
- decodedCurrentPath.includes(`/docs/domains/${item.id}/language`)
348
- ? 'bg-purple-100 '
349
- : 'hover:bg-purple-100'
350
- }`}
351
- >
352
- <span className="truncate">Ubiquitous Language</span>
353
- </a>
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 servicesCount = item.collection === 'domains' ? item.data.services?.length || 0 : 0;
40
- const sends = item.collection === 'services' ? item.data.sends || null : null;
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: item.collection === 'domains' ? item.data.services : null,
68
- domains: item.collection === 'domains' ? item.data.domains : null,
74
+ services: isCollectionDomain ? item.data.services : null,
75
+ domains: isCollectionDomain ? item.data.domains : null,
69
76
  sends: sendsWithHref,
70
77
  receives: receivesWithHref,
71
- specifications: item.collection === 'services' ? item.data.specifications : null,
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,
@@ -16,3 +16,5 @@ export { default as GooglePubSub } from '@icons/protocols/googlepubsub.svg?raw';
16
16
  export { default as Kinesis } from '@icons/protocols/kinesis.svg?raw';
17
17
  export { default as GRPC } from '@icons/protocols/grpc.svg?raw';
18
18
  export { default as ZMQ } from '@icons/protocols/zmq.svg?raw';
19
+ export { default as TibcoFtl } from '@icons/protocols/tibo-ftl.svg?raw';
20
+ export { default as TibcoRv } from '@icons/protocols/tibo-rv.svg?raw';
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mailbox-icon lucide-mailbox"><path d="M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z"/><polyline points="15,9 18,9 18,11"/><path d="M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2"/><line x1="6" x2="7" y1="10" y2="10"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mailbox-icon lucide-mailbox"><path d="M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z"/><polyline points="15,9 18,9 18,11"/><path d="M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2"/><line x1="6" x2="7" y1="10" y2="10"/></svg>
@@ -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 { backgroundColor: 'green', textColor: 'green', content: 'Query', icon: MagnifyingGlassIcon, class: 'text-green-400' };
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
- backgroundColor: 'yellow',
91
- textColor: 'yellow',
92
- content: 'Domain',
93
- icon: RectangleGroupIcon,
94
- class: 'text-yellow-400',
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';
@@ -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
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/event-catalog/eventcatalog.git"
7
7
  },
8
8
  "type": "module",
9
- "version": "2.35.10",
9
+ "version": "2.36.1",
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },