@eventcatalog/core 4.9.0 → 4.10.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.
Files changed (60) 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-XQHS36GO.js → chunk-5XURY2JV.js} +1 -1
  6. package/dist/{chunk-3CGSSMTK.js → chunk-BDFTJVGN.js} +1 -1
  7. package/dist/{chunk-HKVOSQTD.js → chunk-BQXEWQ5J.js} +1 -1
  8. package/dist/{chunk-MPHX5C7M.js → chunk-HFAQXAG3.js} +1 -1
  9. package/dist/{chunk-LVGQ23LR.js → chunk-IC6XSXJO.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/eventcatalog.cjs +1 -1
  13. package/dist/eventcatalog.js +5 -5
  14. package/dist/generate.cjs +1 -1
  15. package/dist/generate.js +3 -3
  16. package/dist/utils/cli-logger.cjs +1 -1
  17. package/dist/utils/cli-logger.js +2 -2
  18. package/eventcatalog/src/components/Badge.astro +3 -1
  19. package/eventcatalog/src/components/FieldsExplorer/FieldNodeGraph.tsx +0 -5
  20. package/eventcatalog/src/components/MDX/Design/Design.astro +2 -0
  21. package/eventcatalog/src/components/MDX/EntityMap/EntityMap.astro +2 -0
  22. package/eventcatalog/src/components/MDX/Flow/Flow.astro +2 -0
  23. package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +5 -7
  24. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +5 -0
  25. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +121 -88
  26. package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +55 -7
  27. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.spec.ts +59 -15
  28. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +53 -7
  29. package/eventcatalog/src/content.config.ts +70 -0
  30. package/eventcatalog/src/enterprise/collections/resource-docs-utils.ts +19 -0
  31. package/eventcatalog/src/enterprise/fields/pages/fields.astro +2 -0
  32. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +1 -1
  33. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +16 -15
  34. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +21 -17
  35. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +1 -1
  36. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +4 -4
  37. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +16 -8
  38. package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/index.astro +48 -65
  39. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +45 -18
  40. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +1 -1
  41. package/eventcatalog/src/pages/docs/users/[id]/index.astro +1 -1
  42. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +5 -1
  43. package/eventcatalog/src/pages/triggers/[type]/[id]/[version]/index.astro +2 -0
  44. package/eventcatalog/src/pages/visualiser/designs/[id]/index.astro +2 -0
  45. package/eventcatalog/src/stores/sidebar-store/builders/adr.ts +96 -34
  46. package/eventcatalog/src/stores/sidebar-store/builders/agent.ts +167 -75
  47. package/eventcatalog/src/stores/sidebar-store/builders/container.ts +135 -59
  48. package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +144 -58
  49. package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +306 -188
  50. package/eventcatalog/src/stores/sidebar-store/builders/entity.ts +108 -39
  51. package/eventcatalog/src/stores/sidebar-store/builders/flow.ts +147 -63
  52. package/eventcatalog/src/stores/sidebar-store/builders/message.ts +212 -121
  53. package/eventcatalog/src/stores/sidebar-store/builders/service.ts +223 -127
  54. package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +3 -23
  55. package/eventcatalog/src/stores/sidebar-store/builders/system.ts +187 -97
  56. package/eventcatalog/src/stores/sidebar-store/custom-sidebar.ts +678 -0
  57. package/eventcatalog/src/stores/sidebar-store/state.ts +66 -36
  58. package/eventcatalog/src/styles/tailwind.css +82 -0
  59. package/eventcatalog/src/utils/collections/resource-docs.ts +1 -0
  60. package/package.json +3 -3
@@ -12,20 +12,70 @@ import {
12
12
  buildAttachmentsSection,
13
13
  buildDiagramNavItems,
14
14
  buildResourceDocsSection,
15
+ buildArchitectureDecisionsSection,
15
16
  } from './shared';
16
17
  import { isVisualiserEnabled, isChangelogEnabled } from '@utils/feature';
17
18
  import { pluralizeMessageType } from '@utils/collections/messages';
18
19
  import { iconFieldsForResource } from '@utils/icon';
20
+ import { resolveSidebarPages, toCustomSidebarContext, type SidebarSpec } from '../custom-sidebar';
19
21
 
20
22
  const uniqueRefs = (refs: string[]) => [...new Set(refs)];
21
23
 
22
- export const buildServiceNode = (
24
+ /**
25
+ * Predefined sidebar sections for a service, keyed by the token users reference from
26
+ * `sidebar.json` (e.g. `$quick-reference`). Each token is the kebab-cased title of the
27
+ * section as it appears in the default sidebar.
28
+ */
29
+ export type ServiceSectionKey =
30
+ | 'quick-reference'
31
+ | 'documentation'
32
+ | 'architecture'
33
+ | 'diagrams'
34
+ | 'api-and-contracts'
35
+ | 'resource-groups'
36
+ | 'state-and-persistence'
37
+ | 'entities'
38
+ | 'outbound-messages'
39
+ | 'inbound-messages'
40
+ | 'channels'
41
+ | 'flows'
42
+ | 'decision-records'
43
+ | 'owners'
44
+ | 'code'
45
+ | 'attachments';
46
+
47
+ export type ServiceSections = Record<ServiceSectionKey, NavNode | NavNode[] | null>;
48
+
49
+ /**
50
+ * Order of sections in the default (generated) sidebar. `decision-records` is deliberately
51
+ * absent — in default mode it is inserted before Owners by `withArchitectureDecisionsSection`
52
+ * in state.ts, and only becomes a first-class token for custom sidebars.
53
+ */
54
+ export const DEFAULT_SERVICE_SECTION_ORDER: ServiceSectionKey[] = [
55
+ 'quick-reference',
56
+ 'documentation',
57
+ 'architecture',
58
+ 'diagrams',
59
+ 'api-and-contracts',
60
+ 'resource-groups',
61
+ 'state-and-persistence',
62
+ 'entities',
63
+ 'outbound-messages',
64
+ 'inbound-messages',
65
+ 'channels',
66
+ 'flows',
67
+ 'owners',
68
+ 'code',
69
+ 'attachments',
70
+ ];
71
+
72
+ export const buildServiceSections = (
23
73
  service: CollectionEntry<'services'>,
24
74
  owners: any[],
25
75
  context: ResourceGroupContext,
26
76
  serviceChannels: CollectionEntry<'channels'>[] = [],
27
77
  flowRefs: string[] = []
28
- ): NavNode => {
78
+ ): ServiceSections => {
29
79
  const sendsMessages = service.data.sends || [];
30
80
  const receivesMessages = service.data.receives || [];
31
81
  const serviceEntities = service.data.entities || [];
@@ -72,140 +122,186 @@ export const buildServiceNode = (
72
122
  const diagramNavItems = buildDiagramNavItems(serviceDiagrams, context.diagrams);
73
123
  const hasDiagrams = diagramNavItems.length > 0;
74
124
 
75
- const isExternalSystem = !!service.data.externalSystem;
76
-
77
125
  return {
78
- type: 'item',
79
- title: service.data.name,
80
- badge: isExternalSystem ? 'External System' : 'Service',
81
- summary: service.data.summary,
82
- ...iconFieldsForResource(service.data, isExternalSystem ? 'Globe' : 'Server'),
83
- pages: [
84
- buildQuickReferenceSection(
85
- [
86
- { title: 'Overview', href: buildUrl(docsBasePath) },
87
- isChangelogEnabled() &&
88
- shouldRenderSideBarSection(service, 'changelog') && {
89
- title: 'Changelog',
90
- href: buildUrl(`${docsBasePath}/changelog`),
91
- },
92
- ].filter(Boolean) as { title: string; href: string }[]
93
- ),
94
- docsSection,
95
- {
96
- type: 'group',
97
- title: 'Architecture',
98
- icon: 'Workflow',
99
- pages: [
100
- {
126
+ 'quick-reference': buildQuickReferenceSection(
127
+ [
128
+ { title: 'Overview', href: buildUrl(docsBasePath) },
129
+ isChangelogEnabled() &&
130
+ shouldRenderSideBarSection(service, 'changelog') && {
131
+ title: 'Changelog',
132
+ href: buildUrl(`${docsBasePath}/changelog`),
133
+ },
134
+ ].filter(Boolean) as { title: string; href: string }[]
135
+ ),
136
+ documentation: docsSection,
137
+ architecture: {
138
+ type: 'group',
139
+ title: 'Architecture',
140
+ icon: 'Workflow',
141
+ pages: [
142
+ {
143
+ type: 'item',
144
+ title: 'Overview',
145
+ href: buildUrl(`/architecture/services/${service.data.id}/${service.data.version}`),
146
+ },
147
+ renderVisualiser && {
148
+ type: 'item',
149
+ title: 'Map',
150
+ href: buildUrl(`/visualiser/services/${service.data.id}/${service.data.version}`),
151
+ },
152
+ renderVisualiser &&
153
+ renderEntities && {
101
154
  type: 'item',
102
- title: 'Overview',
103
- href: buildUrl(`/architecture/services/${service.data.id}/${service.data.version}`),
155
+ title: 'Entity Map',
156
+ href: buildUrl(`/visualiser/services/${service.data.id}/${service.data.version}/entity-map`),
104
157
  },
105
- renderVisualiser && {
158
+ renderVisualiser &&
159
+ hasDataStores && {
106
160
  type: 'item',
107
- title: 'Map',
108
- href: buildUrl(`/visualiser/services/${service.data.id}/${service.data.version}`),
161
+ title: 'Data Dependency Graph',
162
+ href: buildUrl(`/visualiser/services/${service.data.id}/${service.data.version}/data`),
109
163
  },
110
- renderVisualiser &&
111
- renderEntities && {
164
+ ].filter(Boolean) as ChildRef[],
165
+ },
166
+ diagrams: hasDiagrams
167
+ ? {
168
+ type: 'group',
169
+ title: 'Diagrams',
170
+ icon: 'FileImage',
171
+ pages: diagramNavItems,
172
+ }
173
+ : null,
174
+ 'api-and-contracts': renderSpecifications
175
+ ? {
176
+ type: 'group',
177
+ title: 'API & Contracts',
178
+ icon: 'FileCode',
179
+ pages: [
180
+ ...openAPISpecifications.map((specification) => ({
112
181
  type: 'item',
113
- title: 'Entity Map',
114
- href: buildUrl(`/visualiser/services/${service.data.id}/${service.data.version}/entity-map`),
115
- },
116
- renderVisualiser &&
117
- hasDataStores && {
182
+ title: `${specification.name}`,
183
+ leftIcon: '/icons/openapi-black.svg',
184
+ href: buildUrl(`${docsBasePath}/spec/${specification.filenameWithoutExtension}`),
185
+ })),
186
+ ...asyncAPISpecifications.map((specification) => ({
118
187
  type: 'item',
119
- title: 'Data Dependency Graph',
120
- href: buildUrl(`/visualiser/services/${service.data.id}/${service.data.version}/data`),
121
- },
122
- ].filter(Boolean) as ChildRef[],
123
- },
124
- hasDiagrams && {
125
- type: 'group',
126
- title: 'Diagrams',
127
- icon: 'FileImage',
128
- pages: diagramNavItems,
129
- },
130
- renderSpecifications && {
131
- type: 'group',
132
- title: 'API & Contracts',
133
- icon: 'FileCode',
134
- pages: [
135
- ...openAPISpecifications.map((specification) => ({
136
- type: 'item',
137
- title: `${specification.name}`,
138
- leftIcon: '/icons/openapi-black.svg',
139
- href: buildUrl(`${docsBasePath}/spec/${specification.filenameWithoutExtension}`),
140
- })),
141
- ...asyncAPISpecifications.map((specification) => ({
142
- type: 'item',
143
- title: `${specification.name}`,
144
- leftIcon: '/icons/asyncapi-black.svg',
145
- href: buildUrl(`${docsBasePath}/asyncapi/${specification.filenameWithoutExtension}`),
146
- })),
147
- ...graphQLSpecifications.map((specification) => ({
148
- type: 'item',
149
- title: `${specification.name}`,
150
- leftIcon: '/icons/graphql-black.svg',
151
- href: buildUrl(`${docsBasePath}/graphql/${specification.filenameWithoutExtension}`),
152
- })),
153
- ],
154
- },
155
- renderResourceGroups && buildResourceGroupSections(resourceGroups, context),
156
- hasDataStores && {
157
- type: 'group',
158
- title: 'State and Persistence',
159
- icon: 'Database',
160
- pages: dataStoresInService.map(
161
- (dataStore) => `container:${(dataStore as any).data.id}:${(dataStore as any).data.version}`
162
- ),
163
- },
164
- renderEntities && {
165
- type: 'group',
166
- title: 'Entities',
167
- icon: 'Box',
168
- pages: serviceEntities.map((entity) => ({
169
- type: 'item',
170
- title: (entity as any).data?.name || (entity as any).data.id,
171
- href: buildUrl(`/docs/entities/${(entity as any).data.id}/${(entity as any).data.version}`),
172
- })),
173
- },
174
- sendsMessages.length > 0 &&
175
- renderMessages && {
188
+ title: `${specification.name}`,
189
+ leftIcon: '/icons/asyncapi-black.svg',
190
+ href: buildUrl(`${docsBasePath}/asyncapi/${specification.filenameWithoutExtension}`),
191
+ })),
192
+ ...graphQLSpecifications.map((specification) => ({
193
+ type: 'item',
194
+ title: `${specification.name}`,
195
+ leftIcon: '/icons/graphql-black.svg',
196
+ href: buildUrl(`${docsBasePath}/graphql/${specification.filenameWithoutExtension}`),
197
+ })),
198
+ ],
199
+ }
200
+ : null,
201
+ 'resource-groups': renderResourceGroups
202
+ ? (buildResourceGroupSections(resourceGroups, context).filter(Boolean) as NavNode[])
203
+ : null,
204
+ 'state-and-persistence': hasDataStores
205
+ ? {
176
206
  type: 'group',
177
- title: 'Outbound Messages',
178
- icon: 'Mail',
179
- pages: sendsMessages.map(
180
- (message) => `${pluralizeMessageType(message as any)}:${(message as any).data.id}:${(message as any).data.version}`
207
+ title: 'State and Persistence',
208
+ icon: 'Database',
209
+ pages: dataStoresInService.map(
210
+ (dataStore) => `container:${(dataStore as any).data.id}:${(dataStore as any).data.version}`
181
211
  ),
182
- },
183
- receivesMessages.length > 0 &&
184
- renderMessages && {
212
+ }
213
+ : null,
214
+ entities: renderEntities
215
+ ? {
185
216
  type: 'group',
186
- title: 'Inbound Messages',
187
- icon: 'Mail',
188
- pages: receivesMessages.map(
189
- (receive) => `${pluralizeMessageType(receive as any)}:${(receive as any).data.id}:${(receive as any).data.version}`
190
- ),
191
- },
192
- serviceChannels.length > 0 && {
193
- type: 'group',
194
- title: 'Channels',
195
- icon: 'ArrowRightLeft',
196
- pages: serviceChannels.map((channel) => `channel:${(channel as any).data.id}:${(channel as any).data.version}`),
197
- },
198
- hasFlows && {
199
- type: 'group',
200
- // If the service declares its own flows it owns them ("Flows"); otherwise it is
201
- // only referenced as a step in someone else's flow ("Appears in flows").
202
- title: serviceFlows.length > 0 ? 'Flows' : 'Appears in flows',
203
- icon: 'Waypoints',
204
- pages: serviceFlowRefs,
205
- },
206
- renderOwners && buildOwnersSection(owners),
207
- renderRepository && buildRepositorySection(service.data.repository as { url: string; language: string }),
208
- hasAttachments && buildAttachmentsSection(service.data.attachments as any[]),
209
- ].filter(Boolean) as ChildRef[],
217
+ title: 'Entities',
218
+ icon: 'Box',
219
+ pages: serviceEntities.map((entity) => ({
220
+ type: 'item',
221
+ title: (entity as any).data?.name || (entity as any).data.id,
222
+ href: buildUrl(`/docs/entities/${(entity as any).data.id}/${(entity as any).data.version}`),
223
+ })),
224
+ }
225
+ : null,
226
+ 'outbound-messages':
227
+ sendsMessages.length > 0 && renderMessages
228
+ ? {
229
+ type: 'group',
230
+ title: 'Outbound Messages',
231
+ icon: 'Mail',
232
+ pages: sendsMessages.map(
233
+ (message) => `${pluralizeMessageType(message as any)}:${(message as any).data.id}:${(message as any).data.version}`
234
+ ),
235
+ }
236
+ : null,
237
+ 'inbound-messages':
238
+ receivesMessages.length > 0 && renderMessages
239
+ ? {
240
+ type: 'group',
241
+ title: 'Inbound Messages',
242
+ icon: 'Mail',
243
+ pages: receivesMessages.map(
244
+ (receive) => `${pluralizeMessageType(receive as any)}:${(receive as any).data.id}:${(receive as any).data.version}`
245
+ ),
246
+ }
247
+ : null,
248
+ channels:
249
+ serviceChannels.length > 0
250
+ ? {
251
+ type: 'group',
252
+ title: 'Channels',
253
+ icon: 'ArrowRightLeft',
254
+ pages: serviceChannels.map((channel) => `channel:${(channel as any).data.id}:${(channel as any).data.version}`),
255
+ }
256
+ : null,
257
+ flows: hasFlows
258
+ ? {
259
+ type: 'group',
260
+ // If the service declares its own flows it owns them ("Flows"); otherwise it is
261
+ // only referenced as a step in someone else's flow ("Appears in flows").
262
+ title: serviceFlows.length > 0 ? 'Flows' : 'Appears in flows',
263
+ icon: 'Waypoints',
264
+ pages: serviceFlowRefs,
265
+ }
266
+ : null,
267
+ 'decision-records': shouldRenderSideBarSection(service, 'architectureDecisions')
268
+ ? buildArchitectureDecisionsSection(service, context.adrs || [])
269
+ : null,
270
+ owners: renderOwners ? buildOwnersSection(owners) : null,
271
+ code: renderRepository ? buildRepositorySection(service.data.repository as { url: string; language: string }) : null,
272
+ attachments: hasAttachments ? buildAttachmentsSection(service.data.attachments as any[]) : null,
273
+ };
274
+ };
275
+
276
+ export type BuildServiceNodeOptions = {
277
+ /** A parsed `sidebar.json` for this service. When present it replaces the generated sidebar. */
278
+ sidebar?: SidebarSpec;
279
+ };
280
+
281
+ export const buildServiceNode = (
282
+ service: CollectionEntry<'services'>,
283
+ owners: any[],
284
+ context: ResourceGroupContext,
285
+ serviceChannels: CollectionEntry<'channels'>[] = [],
286
+ flowRefs: string[] = [],
287
+ options: BuildServiceNodeOptions = {}
288
+ ): NavNode => {
289
+ const sections = buildServiceSections(service, owners, context, serviceChannels, flowRefs);
290
+
291
+ const pages = resolveSidebarPages(sections, DEFAULT_SERVICE_SECTION_ORDER, {
292
+ sidebar: options.sidebar,
293
+ resource: { collection: 'services', id: service.data.id, version: service.data.version, entry: service },
294
+ context: toCustomSidebarContext(context),
295
+ });
296
+
297
+ const isExternalSystem = !!service.data.externalSystem;
298
+
299
+ return {
300
+ type: 'item',
301
+ title: service.data.name,
302
+ badge: isExternalSystem ? 'External System' : 'Service',
303
+ summary: service.data.summary,
304
+ ...iconFieldsForResource(service.data, isExternalSystem ? 'Globe' : 'Server'),
305
+ pages,
210
306
  };
211
307
  };
@@ -5,6 +5,7 @@ import { getAdrNodeKey, getAdrsForResource, type Adr, type AdrResource } from '@
5
5
  import { buildUrl } from '@utils/url-builder';
6
6
  import {
7
7
  getGroupedResourceDocsByType,
8
+ getResourceDocTypeLabel,
8
9
  type ResourceCollection,
9
10
  type ResourceDocEntry,
10
11
  type ResourceDocCategoryEntry,
@@ -25,6 +26,7 @@ export type NavNode = {
25
26
  title: string;
26
27
  collapseKey?: string; // Stable key used to persist collapse state for inline groups
27
28
  collapsible?: boolean; // Set to false to keep a group expanded regardless of its size
29
+ collapsed?: boolean; // Explicit initial collapse state (from sidebar.json). Overrides size/collapsible heuristics.
28
30
  icon?: string; // Lucide icon name
29
31
  subtle?: boolean; // Render lightweight styling for nested subgroup headers
30
32
  leftIcon?: string; // Path to SVG icon shown on the left of the label
@@ -218,35 +220,13 @@ export const buildResourceDocsSection = (
218
220
  return null;
219
221
  }
220
222
 
221
- const typeLabelMap: Record<string, string> = {
222
- adrs: 'ADR',
223
- runbooks: 'Runbook',
224
- contracts: 'Contract',
225
- troubleshooting: 'Troubleshooting',
226
- guides: 'Guide',
227
- };
228
-
229
- const toTypeLabel = (value: string) => {
230
- if (typeLabelMap[value]) {
231
- return typeLabelMap[value];
232
- }
233
-
234
- const normalized = value
235
- .split(/[-_\s]+/)
236
- .filter(Boolean)
237
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
238
- .join(' ');
239
-
240
- return normalized || 'Doc';
241
- };
242
-
243
223
  return {
244
224
  type: 'group',
245
225
  title: 'Documentation',
246
226
  icon: 'BookText',
247
227
  pages: groupedDocs.map((group) => ({
248
228
  type: 'group',
249
- title: group.label || toTypeLabel(group.type),
229
+ title: group.label || getResourceDocTypeLabel(group.type),
250
230
  subtle: true,
251
231
  pages: group.docs.map((doc) => ({
252
232
  type: 'item',