@eventcatalog/core 5.0.0-beta.1 → 5.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/LICENSE +227 -128
  2. package/dist/analytics/analytics.cjs +18 -3
  3. package/dist/analytics/analytics.d.cts +1 -1
  4. package/dist/analytics/analytics.d.ts +1 -1
  5. package/dist/analytics/analytics.js +3 -2
  6. package/dist/analytics/log-build.cjs +71 -35
  7. package/dist/analytics/log-build.js +5 -4
  8. package/dist/analytics/telemetry.cjs +42 -0
  9. package/dist/analytics/telemetry.d.cts +20 -0
  10. package/dist/analytics/telemetry.d.ts +20 -0
  11. package/dist/analytics/telemetry.js +8 -0
  12. package/dist/{chunk-K2ACDO7Y.js → chunk-2LBDRHE4.js} +1 -1
  13. package/dist/{chunk-TQLRG35T.js → chunk-AWOO63ME.js} +71 -45
  14. package/dist/chunk-CPW6G62V.js +17 -0
  15. package/dist/{chunk-UDS3SYLZ.js → chunk-CRKJBDN7.js} +56 -33
  16. package/dist/{chunk-Q6SD5SNQ.js → chunk-EM36OL6A.js} +1 -1
  17. package/dist/{chunk-S2QGT7ZF.js → chunk-IONYKKOY.js} +1 -1
  18. package/dist/{chunk-KYJZJLBD.js → chunk-LJMXOEEC.js} +9 -3
  19. package/dist/constants.cjs +1 -1
  20. package/dist/constants.js +1 -1
  21. package/dist/eventcatalog.cjs +204 -124
  22. package/dist/eventcatalog.config.d.cts +59 -1
  23. package/dist/eventcatalog.config.d.ts +59 -1
  24. package/dist/eventcatalog.js +30 -11
  25. package/dist/federation/federate.js +2 -2
  26. package/dist/generate.cjs +1 -1
  27. package/dist/generate.js +3 -3
  28. package/dist/runtime-dependencies/@hocuspocus__provider.mjs +3 -0
  29. package/dist/runtime-dependencies/@hocuspocus__server.mjs +3 -0
  30. package/dist/runtime-dependencies/@mcp-b__webmcp-local-relay.mjs +3 -0
  31. package/dist/runtime-dependencies/@mcp-b__webmcp-polyfill.mjs +3 -0
  32. package/dist/runtime-dependencies/manifest.json +8 -1
  33. package/dist/runtime-dependencies/react-hotkeys-hook.mjs +3 -0
  34. package/dist/runtime-dependencies/y-protocols.mjs +3 -0
  35. package/dist/runtime-dependencies/y-protocols__awareness.mjs +3 -0
  36. package/dist/runtime-dependencies/yjs.mjs +3 -0
  37. package/dist/utils/cli-logger.cjs +1 -1
  38. package/dist/utils/cli-logger.js +2 -2
  39. package/dist/utils/license-status.cjs +71 -45
  40. package/dist/utils/license-status.d.cts +14 -5
  41. package/dist/utils/license-status.d.ts +14 -5
  42. package/dist/utils/license-status.js +1 -1
  43. package/eventcatalog/astro.config.mjs +5 -0
  44. package/eventcatalog/integrations/storage.mjs +20 -0
  45. package/eventcatalog/integrations/studio-server.mjs +32 -0
  46. package/eventcatalog/src/components/Grids/utils.tsx +0 -23
  47. package/eventcatalog/src/components/Header.astro +3 -3
  48. package/eventcatalog/src/components/MDX/CodeGroup/CodeGroup.tsx +6 -1
  49. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +12 -4
  50. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +16 -7
  51. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +10 -0
  52. package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +4 -62
  53. package/eventcatalog/src/components/Tables/Discover/columns.tsx +17 -150
  54. package/eventcatalog/src/components/Tables/Resources/ResourcesTable.tsx +425 -0
  55. package/eventcatalog/src/components/Tables/RowActionsMenu.tsx +157 -0
  56. package/eventcatalog/src/components/Tables/TablePagination.tsx +82 -0
  57. package/eventcatalog/src/features/ai/chat-api.ts +3 -3
  58. package/eventcatalog/src/features/auth/middleware/middleware-auth.ts +10 -0
  59. package/eventcatalog/src/features/integrations/eventcatalog-features.ts +34 -1
  60. package/eventcatalog/src/features/mcp/apps/canvas/shared.ts +47 -0
  61. package/eventcatalog/src/features/mcp/apps/canvas/styles.css +22 -0
  62. package/eventcatalog/src/features/mcp/apps/canvas/view.tsx +276 -0
  63. package/eventcatalog/src/features/mcp/apps/generated/canvas.html +210 -0
  64. package/eventcatalog/src/features/mcp/apps/generated/viewer.html +75 -74
  65. package/eventcatalog/src/features/mcp/apps/shared/app-view.tsx +2 -1
  66. package/eventcatalog/src/features/mcp/mcp-apps.ts +6 -3
  67. package/eventcatalog/src/features/mcp/mcp-server.ts +36 -5
  68. package/eventcatalog/src/features/server/start.ts +25 -0
  69. package/eventcatalog/src/features/storage/database.ts +88 -0
  70. package/eventcatalog/src/features/storage/migrations.ts +85 -0
  71. package/eventcatalog/src/features/storage/sql/database.sql +5 -0
  72. package/eventcatalog/src/features/storage/sql/migrations/0001-studio-canvases.sql +12 -0
  73. package/eventcatalog/src/features/storage/sql/migrations.sql +15 -0
  74. package/eventcatalog/src/features/storage/sql.ts +28 -0
  75. package/eventcatalog/src/features/studio/agent-choreography.ts +381 -0
  76. package/eventcatalog/src/features/studio/api/canvases-api.ts +170 -0
  77. package/eventcatalog/src/features/studio/api/canvases.ts +25 -0
  78. package/eventcatalog/src/features/studio/api/client.ts +35 -0
  79. package/eventcatalog/src/features/studio/canvas-actions.ts +414 -0
  80. package/eventcatalog/src/features/studio/canvas-doc.ts +414 -0
  81. package/eventcatalog/src/features/studio/canvas-preview.ts +53 -0
  82. package/eventcatalog/src/features/studio/catalog-resources.ts +155 -0
  83. package/eventcatalog/src/features/studio/catalog.ts +171 -0
  84. package/eventcatalog/src/features/studio/clipboard.ts +97 -0
  85. package/eventcatalog/src/features/studio/components/CanvasControls.tsx +154 -0
  86. package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +370 -0
  87. package/eventcatalog/src/features/studio/components/CanvasStatusMenu.tsx +117 -0
  88. package/eventcatalog/src/features/studio/components/Comments.tsx +770 -0
  89. package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +281 -0
  90. package/eventcatalog/src/features/studio/components/ConnectionsChooser.tsx +144 -0
  91. package/eventcatalog/src/features/studio/components/ContainerChooser.tsx +145 -0
  92. package/eventcatalog/src/features/studio/components/DeleteCanvasDialog.tsx +81 -0
  93. package/eventcatalog/src/features/studio/components/DragGhost.tsx +115 -0
  94. package/eventcatalog/src/features/studio/components/JoinForm.tsx +34 -0
  95. package/eventcatalog/src/features/studio/components/LeftPanel.tsx +314 -0
  96. package/eventcatalog/src/features/studio/components/Picture.tsx +21 -0
  97. package/eventcatalog/src/features/studio/components/Presence.tsx +286 -0
  98. package/eventcatalog/src/features/studio/components/PropertiesPanel.tsx +212 -0
  99. package/eventcatalog/src/features/studio/components/ShareDialog.tsx +260 -0
  100. package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +1216 -0
  101. package/eventcatalog/src/features/studio/components/StudioNotice.astro +24 -0
  102. package/eventcatalog/src/features/studio/components/canvas-nodes.tsx +255 -0
  103. package/eventcatalog/src/features/studio/components/icons.ts +53 -0
  104. package/eventcatalog/src/features/studio/components/status.ts +53 -0
  105. package/eventcatalog/src/features/studio/edges.ts +56 -0
  106. package/eventcatalog/src/features/studio/flow-state.ts +92 -0
  107. package/eventcatalog/src/features/studio/grouping.ts +226 -0
  108. package/eventcatalog/src/features/studio/hooks/presence-sender.ts +46 -0
  109. package/eventcatalog/src/features/studio/hooks/presence-store.ts +119 -0
  110. package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +347 -0
  111. package/eventcatalog/src/features/studio/hooks/use-comments.ts +54 -0
  112. package/eventcatalog/src/features/studio/hooks/use-follow-camera.ts +104 -0
  113. package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +589 -0
  114. package/eventcatalog/src/features/studio/identity.ts +37 -0
  115. package/eventcatalog/src/features/studio/layout.ts +42 -0
  116. package/eventcatalog/src/features/studio/levels.ts +282 -0
  117. package/eventcatalog/src/features/studio/node-types.ts +205 -0
  118. package/eventcatalog/src/features/studio/pages/[id].astro +68 -0
  119. package/eventcatalog/src/features/studio/pages/index.astro +784 -0
  120. package/eventcatalog/src/features/studio/pages/webmcp-relay.ts +27 -0
  121. package/eventcatalog/src/features/studio/placement.ts +197 -0
  122. package/eventcatalog/src/features/studio/server/canvas-mcp.ts +510 -0
  123. package/eventcatalog/src/features/studio/server/runtime.ts +491 -0
  124. package/eventcatalog/src/features/studio/server/sign-in.ts +18 -0
  125. package/eventcatalog/src/features/studio/server/sql/storage.sql +18 -0
  126. package/eventcatalog/src/features/studio/server/storage.ts +90 -0
  127. package/eventcatalog/src/features/studio/server/tool-icons.ts +30 -0
  128. package/eventcatalog/src/features/studio/studio.css +273 -0
  129. package/eventcatalog/src/features/studio/tool-sync.ts +75 -0
  130. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +10 -5
  131. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.spec.ts +52 -63
  132. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +34 -46
  133. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +11 -29
  134. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_ResourcesPage.astro +79 -0
  135. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.spec.ts +32 -14
  136. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.ts +63 -58
  137. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +2 -62
  138. package/eventcatalog/src/pages/docs/services/[id]/[version]/resources/index.astro +13 -0
  139. package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +24 -42
  140. package/eventcatalog/src/stores/sidebar-store/builders/flow.ts +2 -0
  141. package/eventcatalog/src/stores/sidebar-store/builders/message.ts +2 -0
  142. package/eventcatalog/src/stores/sidebar-store/builders/service.ts +23 -23
  143. package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +13 -0
  144. package/eventcatalog/src/stores/sidebar-store/builders/system.ts +18 -27
  145. package/eventcatalog/src/stores/sidebar-store/state.ts +5 -0
  146. package/eventcatalog/src/styles/tailwind.css +2 -2
  147. package/eventcatalog/src/utils/collections/channels.ts +56 -1
  148. package/eventcatalog/src/utils/collections/flows.ts +50 -1
  149. package/eventcatalog/src/utils/collections/message-connections.ts +60 -0
  150. package/eventcatalog/src/utils/collections/message-triggers.ts +2 -1
  151. package/eventcatalog/src/utils/collections/resource-owners.ts +193 -0
  152. package/eventcatalog/src/utils/collections/resources.ts +310 -0
  153. package/eventcatalog/src/utils/collections/util.ts +31 -0
  154. package/eventcatalog/src/utils/feature.ts +8 -0
  155. package/eventcatalog/src/utils/git.ts +11 -5
  156. package/eventcatalog/src/utils/node-graphs/export-mermaid.ts +0 -2
  157. package/eventcatalog/src/utils/trial.ts +1 -1
  158. package/package.json +17 -12
  159. package/dist/runtime-dependencies/shelljs.mjs +0 -3
  160. package/eventcatalog/public/studio-bg.png +0 -0
  161. package/eventcatalog/src/components/Grids/DomainGrid.tsx +0 -838
  162. package/eventcatalog/src/components/Grids/MessageGrid.tsx +0 -344
  163. package/eventcatalog/src/components/Grids/SystemGrid.tsx +0 -185
  164. package/eventcatalog/src/components/Grids/components.tsx +0 -170
  165. package/eventcatalog/src/components/Grids/message-link.ts +0 -38
  166. package/eventcatalog/src/components/Studio/StudioPageModal.tsx +0 -150
  167. package/eventcatalog/src/components/Tables/SystemResources/SystemResourcesTable.tsx +0 -294
  168. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_resources.spec.ts +0 -65
  169. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_resources.ts +0 -46
  170. package/eventcatalog/src/pages/studio.astro +0 -316
  171. package/dist/{chunk-AYNZXUB5.js → chunk-KHACCTW2.js} +3 -3
@@ -0,0 +1,310 @@
1
+ import { collectionToResourceMap, resourceKey, uniqueResources } from '@utils/collections/util';
2
+
3
+ // What the Resources pages show: which resources each owner (domain, system, service, flow,
4
+ // channel or message) lists, how each relates to the owner, and the links for each row.
5
+ // Loading owners and their related resources lives in ./resource-owners.
6
+
7
+ /** The kinds of resource a Resources page can list. */
8
+ export type ResourceCollection =
9
+ | 'domains'
10
+ | 'systems'
11
+ | 'agents'
12
+ | 'services'
13
+ | 'flows'
14
+ | 'entities'
15
+ | 'data-products'
16
+ | 'containers'
17
+ | 'events'
18
+ | 'commands'
19
+ | 'queries'
20
+ | 'channels'
21
+ | 'adrs';
22
+
23
+ /** How a resource relates to the page's owner: which way a message flows, how a data store is used, or how it belongs to the owner. */
24
+ export type ResourceRelationship =
25
+ | 'receives'
26
+ | 'sends'
27
+ | 'sends-and-receives'
28
+ | 'reads'
29
+ | 'writes'
30
+ | 'reads-and-writes'
31
+ | 'contains'
32
+ | 'owns'
33
+ | 'appears-in'
34
+ | 'governed-by'
35
+ | 'includes'
36
+ | 'transports'
37
+ | 'produces-messages'
38
+ | 'receives-messages'
39
+ | 'produces-and-receives-messages'
40
+ | 'produces'
41
+ | 'consumes'
42
+ | 'produces-and-consumes'
43
+ | 'triggers'
44
+ | 'triggered-by'
45
+ | 'triggers-and-triggered-by';
46
+
47
+ /** Any catalog entry a Resources page reads: a collection entry, possibly hydrated with extra fields. */
48
+ export type ResourceEntry = {
49
+ collection: string;
50
+ data: { id: string; version: string; name?: string; summary?: string; [key: string]: any };
51
+ [key: string]: any;
52
+ };
53
+
54
+ export type ResourceOwnerCollection = 'systems' | 'domains' | 'services' | 'flows' | 'channels' | MessageOwnerCollection;
55
+ type MessageOwnerCollection = 'events' | 'commands' | 'queries';
56
+ const isMessageOwner = (collection: ResourceOwnerCollection): collection is MessageOwnerCollection =>
57
+ collection === 'events' || collection === 'commands' || collection === 'queries';
58
+
59
+ /** Resources that point at the owner, rather than being listed in the owner's own frontmatter. */
60
+ export interface RelatedResources {
61
+ /** Decision records whose `appliesTo` includes the owner. */
62
+ adrs?: ResourceEntry[];
63
+ /** Flows with a step that points at the owner (services only). */
64
+ stepFlows?: ResourceEntry[];
65
+ /** The resources a flow's steps point at (flows only). */
66
+ stepResources?: ResourceEntry[];
67
+ /** Services and agents that send to the channel, those that receive from it, and the messages on it (channels only). */
68
+ channelProducers?: ResourceEntry[];
69
+ channelConsumers?: ResourceEntry[];
70
+ channelMessages?: ResourceEntry[];
71
+ /** The channels, flows and messages a message connects to (messages only; its producers and consumers are on `data`). */
72
+ messageChannels?: ResourceEntry[];
73
+ messageFlows?: ResourceEntry[];
74
+ messageTriggers?: ResourceEntry[];
75
+ messageTriggeredBy?: ResourceEntry[];
76
+ }
77
+
78
+ /** A resource owner as the Resources page receives it: the collection entry with its related resources attached. */
79
+ export type ResourceOwner = ResourceEntry & RelatedResources;
80
+
81
+ export interface ResourceGroup {
82
+ collection: ResourceCollection;
83
+ items: ResourceEntry[];
84
+ }
85
+
86
+ const getMessages = (data: any) =>
87
+ uniqueResources([...(data.sends || []), ...(data.receives || [])].filter((message) => message?.collection));
88
+
89
+ const messageGroups = (messages: any[]): ResourceGroup[] => [
90
+ { collection: 'events', items: messages.filter((message) => message.collection === 'events') },
91
+ { collection: 'commands', items: messages.filter((message) => message.collection === 'commands') },
92
+ { collection: 'queries', items: messages.filter((message) => message.collection === 'queries') },
93
+ ];
94
+
95
+ // The collections a flow step can point at, in the order the page groups them.
96
+ const FLOW_STEP_COLLECTIONS: ResourceCollection[] = [
97
+ 'services',
98
+ 'agents',
99
+ 'flows',
100
+ 'containers',
101
+ 'data-products',
102
+ 'events',
103
+ 'commands',
104
+ 'queries',
105
+ ];
106
+
107
+ export const getResourceGroups = (
108
+ data: any,
109
+ ownerCollection: ResourceOwnerCollection,
110
+ related: RelatedResources = {}
111
+ ): ResourceGroup[] => {
112
+ const adrGroup: ResourceGroup = { collection: 'adrs', items: related.adrs || [] };
113
+
114
+ // A message's resources are what produces or consumes it, the channels it travels on, the
115
+ // flows it appears in, and the messages it triggers or is triggered by.
116
+ if (isMessageOwner(ownerCollection)) {
117
+ const endpoints = uniqueResources([...(data.producers || []), ...(data.consumers || [])]);
118
+ const endpointGroup = (collection: ResourceCollection): ResourceGroup => ({
119
+ collection,
120
+ items: endpoints.filter((endpoint) => endpoint.collection === collection),
121
+ });
122
+ return [
123
+ endpointGroup('services'),
124
+ endpointGroup('agents'),
125
+ endpointGroup('data-products'),
126
+ { collection: 'channels', items: related.messageChannels || [] },
127
+ { collection: 'flows', items: related.messageFlows || [] },
128
+ ...messageGroups(uniqueResources([...(related.messageTriggers || []), ...(related.messageTriggeredBy || [])])),
129
+ adrGroup,
130
+ ];
131
+ }
132
+
133
+ // A channel's resources are the services and agents that send to or receive from it, and the messages on it.
134
+ if (ownerCollection === 'channels') {
135
+ const endpoints = uniqueResources([...(related.channelProducers || []), ...(related.channelConsumers || [])]);
136
+ return [
137
+ { collection: 'services', items: endpoints.filter((endpoint) => endpoint.collection === 'services') },
138
+ { collection: 'agents', items: endpoints.filter((endpoint) => endpoint.collection === 'agents') },
139
+ ...messageGroups(related.channelMessages || []),
140
+ adrGroup,
141
+ ];
142
+ }
143
+
144
+ // A flow's resources are what its steps point at.
145
+ if (ownerCollection === 'flows') {
146
+ const stepResources = related.stepResources || [];
147
+ return [
148
+ ...FLOW_STEP_COLLECTIONS.map((collection) => ({
149
+ collection,
150
+ items: stepResources.filter((resource) => resource.collection === collection),
151
+ })),
152
+ adrGroup,
153
+ ];
154
+ }
155
+
156
+ // A service's resources are what it uses: the messages it sends and receives, its
157
+ // entities, the data stores it reads from or writes to, and the flows it appears in.
158
+ if (ownerCollection === 'services') {
159
+ return [
160
+ { collection: 'flows', items: uniqueResources([...(data.flows || []), ...(related.stepFlows || [])]) },
161
+ { collection: 'entities', items: data.entities || [] },
162
+ { collection: 'containers', items: uniqueResources([...(data.writesTo || []), ...(data.readsFrom || [])]) },
163
+ ...messageGroups(getMessages(data)),
164
+ adrGroup,
165
+ ];
166
+ }
167
+
168
+ const domainOnlyGroups: ResourceGroup[] =
169
+ ownerCollection === 'domains'
170
+ ? [
171
+ { collection: 'domains', items: data.domains || [] },
172
+ { collection: 'systems', items: data.systems || [] },
173
+ { collection: 'agents', items: data.agents || [] },
174
+ { collection: 'data-products', items: data['data-products'] || [] },
175
+ ]
176
+ : [];
177
+
178
+ return [
179
+ ...domainOnlyGroups,
180
+ { collection: 'services', items: data.services || [] },
181
+ { collection: 'flows', items: data.flows || [] },
182
+ { collection: 'entities', items: data.entities || [] },
183
+ { collection: 'containers', items: data.containers || [] },
184
+ ...messageGroups(ownerCollection === 'domains' ? getMessages(data) : []),
185
+ adrGroup,
186
+ ];
187
+ };
188
+
189
+ // The page loader attaches the RelatedResources fields to the owner (see _index.data.ts).
190
+ export const hasResources = (owner: any, ownerCollection: ResourceOwnerCollection): boolean =>
191
+ getResourceGroups(owner?.data ?? {}, ownerCollection, owner ?? {}).some(({ items }) => items.length > 0);
192
+
193
+ const includesResource = (resources: any[] | undefined, item: any) =>
194
+ (resources || []).some((resource) => resource?.data && resourceKey(resource) === resourceKey(item));
195
+
196
+ /**
197
+ * How a resource relates to the page's owner: which way a message flows, how a data store
198
+ * is used, or whether the owner contains, owns or appears in it.
199
+ */
200
+ export const getResourceRelationship = (
201
+ data: any,
202
+ ownerCollection: ResourceOwnerCollection,
203
+ collection: ResourceCollection,
204
+ item: any,
205
+ related: RelatedResources = {}
206
+ ): ResourceRelationship | undefined => {
207
+ if (collection === 'adrs') return 'governed-by';
208
+ if (ownerCollection === 'flows') return 'includes';
209
+ if (isMessageOwner(ownerCollection)) {
210
+ if (collection === 'channels') return 'transports';
211
+ if (collection === 'flows') return 'appears-in';
212
+ if (collection === 'events' || collection === 'commands' || collection === 'queries') {
213
+ const triggers = includesResource(related.messageTriggers, item);
214
+ const triggeredBy = includesResource(related.messageTriggeredBy, item);
215
+ if (triggers && triggeredBy) return 'triggers-and-triggered-by';
216
+ if (triggers) return 'triggers';
217
+ if (triggeredBy) return 'triggered-by';
218
+ return undefined;
219
+ }
220
+ const produces = includesResource(data.producers, item);
221
+ const consumes = includesResource(data.consumers, item);
222
+ if (produces && consumes) return 'produces-and-consumes';
223
+ if (produces) return 'produces';
224
+ if (consumes) return 'consumes';
225
+ return undefined;
226
+ }
227
+ // A channel transports messages; services and agents produce messages onto it or receive them from it.
228
+ if (ownerCollection === 'channels') {
229
+ if (collection !== 'services' && collection !== 'agents') return 'transports';
230
+ const produces = includesResource(related.channelProducers, item);
231
+ const receives = includesResource(related.channelConsumers, item);
232
+ if (produces && receives) return 'produces-and-receives-messages';
233
+ if (produces) return 'produces-messages';
234
+ if (receives) return 'receives-messages';
235
+ return undefined;
236
+ }
237
+ if (collection === 'events' || collection === 'commands' || collection === 'queries') {
238
+ const sends = includesResource(data.sends, item);
239
+ const receives = includesResource(data.receives, item);
240
+ if (sends && receives) return 'sends-and-receives';
241
+ if (sends) return 'sends';
242
+ if (receives) return 'receives';
243
+ return undefined;
244
+ }
245
+ if (collection === 'entities') return 'owns';
246
+ if (ownerCollection === 'services') {
247
+ if (collection === 'flows') return 'appears-in';
248
+ if (collection === 'containers') {
249
+ const writes = includesResource(data.writesTo, item);
250
+ const reads = includesResource(data.readsFrom, item);
251
+ if (writes && reads) return 'reads-and-writes';
252
+ if (writes) return 'writes';
253
+ if (reads) return 'reads';
254
+ }
255
+ return undefined;
256
+ }
257
+ // Everything else on a domain or system page is part of it.
258
+ return 'contains';
259
+ };
260
+
261
+ /**
262
+ * The schema page for a resource, matching the sidebar's Schema link: messages with an entry
263
+ * in the schemas collection (keyed `collection:id:version`), and data products with an output
264
+ * contract. Undefined for everything else.
265
+ */
266
+ export const getSchemaPath = (collection: ResourceCollection, item: any, schemaKeys: Set<string>) => {
267
+ const { id, version } = item.data;
268
+ if (collection === 'events' || collection === 'commands' || collection === 'queries') {
269
+ return schemaKeys.has(`${collection}:${id}:${version}`) ? `/schemas/${collection}/${id}/${version}` : undefined;
270
+ }
271
+ if (collection === 'data-products') {
272
+ const contract = (item.data.outputs || []).find((output: any) => output.contract)?.contract;
273
+ return contract ? `/schemas/data-products/${id}/${version}?contract=${encodeURIComponent(contract.path)}` : undefined;
274
+ }
275
+ return undefined;
276
+ };
277
+
278
+ export const getDocsPath = (collection: ResourceCollection, item: any) =>
279
+ `/docs/${collection}/${item.data.id}/${item.data.version}`;
280
+
281
+ // Collections the visualiser has a page for (domains and systems through their own routes).
282
+ // Entities and decision records have none.
283
+ const VISUALISER_COLLECTIONS = new Set<ResourceCollection>([
284
+ 'domains',
285
+ 'systems',
286
+ 'agents',
287
+ 'services',
288
+ 'flows',
289
+ 'containers',
290
+ 'channels',
291
+ 'data-products',
292
+ 'events',
293
+ 'commands',
294
+ 'queries',
295
+ ]);
296
+
297
+ /** The resource's visualiser page, unless the visualiser is off, has no page for it, or the resource opts out. */
298
+ export const getVisualiserPath = (collection: ResourceCollection, item: any, visualiserEnabled: boolean) => {
299
+ if (!visualiserEnabled || !VISUALISER_COLLECTIONS.has(collection) || item.data.visualiser === false) return undefined;
300
+ return `/visualiser/${collection}/${item.data.id}/${item.data.version}`;
301
+ };
302
+
303
+ /** Same key and badge as the favorite button on the resource's docs page. */
304
+ export const getFavorite = (collection: ResourceCollection, item: any) => {
305
+ const resourceType = collectionToResourceMap[collection];
306
+ return {
307
+ nodeKey: `${resourceType}:${item.data.id}:${item.data.version}`,
308
+ badge: resourceType.charAt(0).toUpperCase() + resourceType.slice(1),
309
+ };
310
+ };
@@ -291,6 +291,37 @@ export const createVersionedMap = <T extends { data: { id: string; version?: str
291
291
  return map;
292
292
  };
293
293
 
294
+ const versionedMapCache = new WeakMap<object, Map<string, any[]>>();
295
+
296
+ /**
297
+ * createVersionedMap, built once per list. Use it in helpers that search the same list once per
298
+ * resource, so a catalog-wide pass does not rebuild the map every time.
299
+ */
300
+ export const getVersionedMap = <T extends { data: { id: string; version?: string } }>(items: T[]): Map<string, T[]> => {
301
+ let map = versionedMapCache.get(items);
302
+ if (!map) {
303
+ map = createVersionedMap(items);
304
+ versionedMapCache.set(items, map);
305
+ }
306
+ return map as Map<string, T[]>;
307
+ };
308
+
309
+ type KeyedResource = { collection: string; data: { id: string; version?: string } };
310
+
311
+ /** Identifies a resource across collections: a service and an agent can share an id and version. */
312
+ export const resourceKey = (resource: KeyedResource) => `${resource.collection}:${resource.data.id}:${resource.data.version}`;
313
+
314
+ /** The resources without repeats (by collection, id and version), keeping the first of each in order. */
315
+ export const uniqueResources = <T extends KeyedResource>(resources: T[]): T[] => {
316
+ const seen = new Map<string, T>();
317
+ for (const resource of resources) {
318
+ if (!resource?.data?.id) continue;
319
+ const key = resourceKey(resource);
320
+ if (!seen.has(key)) seen.set(key, resource);
321
+ }
322
+ return [...seen.values()];
323
+ };
324
+
294
325
  // Merge as many given maps as you want
295
326
  export const mergeMaps = <T>(...maps: Map<string, T[]>[]): Map<string, T[]> => {
296
327
  return maps.reduce((acc, map) => {
@@ -27,4 +27,12 @@ export const isAuthEnabled = () =>
27
27
  (config?.auth?.enabled ?? false) && fs.existsSync(join(projectDirectory(), 'eventcatalog.auth.js')) && isSSR();
28
28
 
29
29
  export const isEventCatalogMCPEnabled = () => isSSR() && (config?.mcp?.enabled ?? true);
30
+
31
+ // Collaborative canvases (EventCatalog Studio, /studio) are an experiment: on the dev server (unless studio.enabled is
32
+ // false), and in production (server output) when studio.enabled is set. With sign-in on, only signed-in people can
33
+ // open them.
34
+ export const isCanvasEnabled = () => {
35
+ const enabled = config?.studio?.enabled;
36
+ return enabled !== false && (isDevMode() || (isSSR() && enabled === true));
37
+ };
30
38
  export const isEventCatalogMCPAuthEnabled = () => isEventCatalogMCPEnabled() && (config?.mcp?.auth?.enabled ?? false);
@@ -1,7 +1,6 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
- import shell from 'shelljs';
5
4
 
6
5
  type Commit = { timestamp: number; author: string };
7
6
 
@@ -9,10 +8,6 @@ const COMMIT_MARKER = '\x1e';
9
8
 
10
9
  const newestCommitsByRepository = new Map<string, Map<string, Commit> | undefined>();
11
10
 
12
- export function hasGit() {
13
- return !!shell.which('git');
14
- }
15
-
16
11
  const runGit = (args: string[]) => {
17
12
  try {
18
13
  return execFileSync('git', ['-c', 'log.showSignature=false', ...args], {
@@ -25,6 +20,17 @@ const runGit = (args: string[]) => {
25
20
  }
26
21
  };
27
22
 
23
+ // Checked once per PATH, as git history is read for every page
24
+ const gitInstalledByPath = new Map<string | undefined, boolean>();
25
+
26
+ export function hasGit() {
27
+ const searchPath = process.env.PATH;
28
+ if (!gitInstalledByPath.has(searchPath)) {
29
+ gitInstalledByPath.set(searchPath, runGit(['--version']) !== undefined);
30
+ }
31
+ return gitInstalledByPath.get(searchPath)!;
32
+ }
33
+
28
34
  const parseCommit = (header: string): Commit => {
29
35
  const [timestamp, ...author] = header.split(',');
30
36
  return { timestamp: Number(timestamp) * 1_000, author: author.join(',') };
@@ -38,7 +38,6 @@ const NODE_SHAPE_MAP: Record<string, [string, string]> = {
38
38
  'context-actor': ['((', '))'], // circle
39
39
  flows: ['([', '])'], // stadium (rounded)
40
40
  flow: ['([', '])'],
41
- systems: ['[[', ']]'],
42
41
  system: ['[[', ']]'],
43
42
  step: ['[', ']'], // rectangle
44
43
  user: ['((', '))'], // circle
@@ -79,7 +78,6 @@ const NODE_STYLE_CLASSES: Record<string, string> = {
79
78
  domains: 'fill:#eab308,stroke:#a16207,color:#000',
80
79
  domain: 'fill:#eab308,stroke:#a16207,color:#000',
81
80
  'context-domain': 'fill:#eab308,stroke:#a16207,color:#000',
82
- systems: 'fill:#7c3aed,stroke:#5b21b6,color:#fff',
83
81
  'context-actor': 'fill:#8b5cf6,stroke:#6d28d9,color:#fff',
84
82
  flows: 'fill:#14b8a6,stroke:#0f766e,color:#fff',
85
83
  flow: 'fill:#14b8a6,stroke:#0f766e,color:#fff',
@@ -1,4 +1,4 @@
1
- export const TRIAL_LENGTH_DAYS = 90;
1
+ export const TRIAL_LENGTH_DAYS = 60;
2
2
 
3
3
  export const LICENSE_FAQ_URL = 'https://www.eventcatalog.dev/license-faq';
4
4
 
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "license": "BUSL-1.1",
9
9
  "type": "module",
10
- "version": "5.0.0-beta.1",
10
+ "version": "5.0.0-beta.10",
11
11
  "publishConfig": {
12
12
  "access": "public"
13
13
  },
@@ -54,13 +54,17 @@
54
54
  "@astrojs/rss": "^4.0.19",
55
55
  "@asyncapi/avro-schema-parser": "3.0.24",
56
56
  "@asyncapi/parser": "^3.6.0",
57
- "@asyncapi/react-component": "3.1.0",
57
+ "@asyncapi/react-component": "3.2.1",
58
58
  "@auth/core": "^0.41.3",
59
59
  "@eventcatalog/license": "^0.0.9",
60
60
  "@fontsource/inter": "^5.2.5",
61
61
  "@headlessui/react": "^2.0.3",
62
62
  "@heroicons/react": "^2.1.3",
63
+ "@hocuspocus/provider": "^4.7.0",
64
+ "@hocuspocus/server": "^4.7.0",
63
65
  "@iconify-json/logos": "^1.2.4",
66
+ "@mcp-b/webmcp-local-relay": "5.1.0",
67
+ "@mcp-b/webmcp-polyfill": "5.1.0",
64
68
  "@mermaid-js/layout-elk": "^0.2.0",
65
69
  "@modelcontextprotocol/ext-apps": "^1.7.5",
66
70
  "@modelcontextprotocol/sdk": "^1.30.0",
@@ -75,11 +79,11 @@
75
79
  "@tailwindcss/typography": "^0.5.16",
76
80
  "@tailwindcss/vite": "^4.3.1",
77
81
  "@tanstack/react-table": "^8.17.3",
78
- "@xyflow/react": "^12.3.6",
82
+ "@xyflow/react": "^12.12.0",
79
83
  "ai": "^7.0.106",
80
84
  "astro": "7.3.3",
81
85
  "astro-expressive-code": "^0.44.0",
82
- "astro-seo": "^0.8.4",
86
+ "astro-seo": "^1.2.0",
83
87
  "boxen": "^8.0.1",
84
88
  "commander": "^12.1.0",
85
89
  "cross-env": "^7.0.3",
@@ -93,7 +97,7 @@
93
97
  "elkjs": "^0.10.0",
94
98
  "glob": "^13.0.6",
95
99
  "gray-matter": "^4.0.3",
96
- "hono": "4.13.5",
100
+ "hono": "^4.13.7",
97
101
  "html-to-image": "^1.11.11",
98
102
  "js-yaml": "^4.3.2",
99
103
  "jsonpath-plus": "^10.4.0",
@@ -111,8 +115,9 @@
111
115
  "parse5": "^7.3.0",
112
116
  "picocolors": "^1.1.1",
113
117
  "picomatch": "^4.0.4",
114
- "react": "^18.3.1",
115
- "react-dom": "^18.3.1",
118
+ "react": "^19.2.0",
119
+ "react-dom": "^19.2.0",
120
+ "react-hotkeys-hook": "^5.3.3",
116
121
  "react-markdown": "^10.1.0",
117
122
  "react-syntax-highlighter": "^16.1.1",
118
123
  "recast": "^0.23.11",
@@ -125,7 +130,6 @@
125
130
  "rimraf": "^6.1.3",
126
131
  "semver": "7.6.3",
127
132
  "set-cookie-parser": "^2.7.1",
128
- "shelljs": "^0.9.0",
129
133
  "sonner": "^2.0.7",
130
134
  "sql.js": "^1.12.0",
131
135
  "tailwindcss": "^4.3.1",
@@ -133,10 +137,12 @@
133
137
  "unist-util-visit": "^5.0.0",
134
138
  "update-notifier": "^7.3.1",
135
139
  "uuid": "^11.1.1",
140
+ "y-protocols": "^1.0.7",
141
+ "yjs": "^13.6.33",
136
142
  "zod": "^4.3.6",
137
- "@eventcatalog/linter": "1.2.0-beta.0",
138
- "@eventcatalog/visualiser": "^5.0.0-beta.0",
139
- "@eventcatalog/sdk": "2.30.0-beta.0"
143
+ "@eventcatalog/visualiser": "^5.0.0-beta.4",
144
+ "@eventcatalog/sdk": "2.30.0-beta.1",
145
+ "@eventcatalog/linter": "1.2.0-beta.2"
140
146
  },
141
147
  "devDependencies": {
142
148
  "@astrojs/check": "^0.9.10",
@@ -156,7 +162,6 @@
156
162
  "@types/react-syntax-highlighter": "^15.5.13",
157
163
  "@types/semver": "^7.5.8",
158
164
  "@types/set-cookie-parser": "^2.4.10",
159
- "@types/shelljs": "^0.8.15",
160
165
  "@types/update-notifier": "^6.0.8",
161
166
  "@types/uuid": "^10.0.0",
162
167
  "jsdom": "27.0.0",
@@ -1,3 +0,0 @@
1
- export * from "shelljs";
2
- import * as dependency from "shelljs";
3
- export default dependency.default;
Binary file