@eventcatalog/core 5.0.0-beta.8 → 5.0.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 (467) hide show
  1. package/LICENSE +234 -134
  2. package/README.md +5 -5
  3. package/dist/analytics/analytics.cjs +18 -3
  4. package/dist/analytics/analytics.d.cts +1 -1
  5. package/dist/analytics/analytics.d.ts +1 -1
  6. package/dist/analytics/analytics.js +3 -2
  7. package/dist/analytics/log-build.cjs +157 -76
  8. package/dist/analytics/log-build.d.cts +2 -2
  9. package/dist/analytics/log-build.d.ts +2 -2
  10. package/dist/analytics/log-build.js +7 -5
  11. package/dist/analytics/studio-usage.cjs +90 -0
  12. package/dist/analytics/studio-usage.d.cts +5 -0
  13. package/dist/analytics/studio-usage.d.ts +5 -0
  14. package/dist/analytics/studio-usage.js +10 -0
  15. package/dist/analytics/telemetry.cjs +42 -0
  16. package/dist/analytics/telemetry.d.cts +20 -0
  17. package/dist/analytics/telemetry.d.ts +20 -0
  18. package/dist/analytics/telemetry.js +8 -0
  19. package/dist/catalog-runtime.cjs +1 -1
  20. package/dist/{chunk-RFIXKATC.js → chunk-4G7FSRPN.js} +35 -42
  21. package/dist/{chunk-AWOO63ME.js → chunk-AD7CL772.js} +11 -4
  22. package/dist/chunk-CA6WCQ2T.js +54 -0
  23. package/dist/chunk-CPW6G62V.js +17 -0
  24. package/dist/{chunk-PU25TOOJ.js → chunk-JZN4RSMF.js} +1 -1
  25. package/dist/{chunk-4W55ZOOS.js → chunk-NEAVAFSJ.js} +66 -35
  26. package/dist/{chunk-C6T2AAFR.js → chunk-NIVST4WY.js} +9 -3
  27. package/dist/{chunk-ENAG6UH7.js → chunk-NKNOXQ62.js} +1 -6
  28. package/dist/{chunk-AYNZXUB5.js → chunk-OX6TSVHY.js} +5 -5
  29. package/dist/{chunk-36RYTYT5.js → chunk-PD7KTSYN.js} +1 -1
  30. package/dist/{chunk-RDIZJ7UT.js → chunk-RE7UTPWH.js} +1 -1
  31. package/dist/chunk-S5GWAUG5.js +29 -0
  32. package/dist/{chunk-T4G3VVZ4.js → chunk-YQGRMDRB.js} +4 -2
  33. package/dist/constants.cjs +1 -1
  34. package/dist/constants.js +1 -1
  35. package/dist/docs/api/02-config.md +57 -7
  36. package/dist/docs/development/00-why-eventcatalog.md +11 -11
  37. package/dist/docs/development/01-fundamentals.md +20 -13
  38. package/dist/docs/development/01-getting-started/configuration-overview.md +2 -2
  39. package/dist/docs/development/01-getting-started/installation.md +9 -1
  40. package/dist/docs/development/_getting-started.mdx +1 -1
  41. package/dist/docs/development/ask-your-architecture/01-intro.md +5 -5
  42. package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/01-what-is-eventcatalog-assistant.md +0 -4
  43. package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/02-configuration.md +15 -3
  44. package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/03-bring-your-own-tools.md +0 -3
  45. package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/_category_.json +1 -1
  46. package/dist/docs/development/ask-your-architecture/02-llms.txt.md +1 -1
  47. package/dist/docs/development/ask-your-architecture/{05-skills/01-introduction.md → 03-install-skills.md} +57 -7
  48. package/dist/docs/development/ask-your-architecture/03-mcp-server/_category_.json +1 -1
  49. package/dist/docs/development/ask-your-architecture/03-mcp-server/getting-started.md +86 -33
  50. package/dist/docs/development/ask-your-architecture/03-mcp-server/introduction.md +14 -4
  51. package/dist/docs/development/ask-your-architecture/03-schemas.txt.md +1 -1
  52. package/dist/docs/development/ask-your-architecture/04-agents/_category_.json +1 -1
  53. package/dist/docs/development/authentication/01-introduction.md +2 -23
  54. package/dist/docs/development/authentication/02-enabling-authentication.md +0 -20
  55. package/dist/docs/development/authentication/07-rbac-middleware.md +0 -2
  56. package/dist/docs/development/authentication/providers/03-setting-up-github.md +0 -2
  57. package/dist/docs/development/authentication/providers/03a-setting-up-google.md +0 -2
  58. package/dist/docs/development/authentication/providers/04-setting-up-azure-ad.md +0 -2
  59. package/dist/docs/development/authentication/providers/05-setting-up-okta.md +0 -3
  60. package/dist/docs/development/authentication/providers/06-setting-up-auth0.md +0 -2
  61. package/dist/docs/development/bring-your-own-documentation/01-introduction.md +0 -4
  62. package/dist/docs/development/bring-your-own-documentation/custom-pages/01-introduction.md +0 -3
  63. package/dist/docs/development/bring-your-own-documentation/custom-pages/02-adding-custom-docs.md +0 -17
  64. package/dist/docs/development/bring-your-own-documentation/custom-pages/03-components.md +0 -4
  65. package/dist/docs/development/bring-your-own-documentation/custom-pages/04-owners.md +0 -2
  66. package/dist/docs/development/bring-your-own-documentation/resource-docs/01-introduction.md +1 -3
  67. package/dist/docs/development/bring-your-own-documentation/resource-docs/02-adding-resource-docs.md +1 -4
  68. package/dist/docs/development/bring-your-own-documentation/resource-docs/03-categories.md +0 -2
  69. package/dist/docs/development/bring-your-own-documentation/resource-docs/04-versioning.md +1 -3
  70. package/dist/docs/development/components/components/11-flow.md +17 -0
  71. package/dist/docs/development/components/components/15-nodegraph.md +43 -10
  72. package/dist/docs/development/components/components/18-remote-schema.md +1 -4
  73. package/dist/docs/development/components/custom-components/07-reference.md +14 -0
  74. package/dist/docs/development/customization/02-themes.md +0 -5
  75. package/dist/docs/development/customization/03-search.md +1 -1
  76. package/dist/docs/development/customization/05-custom-pages-and-api-routes/01-introduction.md +0 -2
  77. package/dist/docs/development/customization/05-custom-pages-and-api-routes/04-reference.md +7 -1
  78. package/dist/docs/development/customization/05-resource-sidebar.md +1 -1
  79. package/dist/docs/development/customization/09-embed-eventcatalog.md +20 -25
  80. package/dist/docs/development/deployment/hosting-options.md +7 -2
  81. package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/05-fix-common-problems.md +16 -0
  82. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/03-rules.md +31 -1
  83. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/05-supported-resources.md +1 -1
  84. package/dist/docs/development/governance/architecture-change-detection/01-introduction.md +0 -10
  85. package/dist/docs/development/governance/architecture-change-detection/06-pipeline-gates.md +0 -4
  86. package/dist/docs/development/guides/97-architecture-graph.md +1 -1
  87. package/dist/docs/development/guides/99-adding-analytics.md +0 -7
  88. package/dist/docs/development/guides/domains/03-model-subdomains.md +2 -2
  89. package/dist/docs/development/guides/domains/04-add-resources-to-domains/01-add-systems-to-domains.md +3 -1
  90. package/dist/docs/development/guides/domains/04-add-resources-to-domains/02-add-services-to-domains.md +5 -1
  91. package/dist/docs/development/guides/domains/04-add-resources-to-domains/03-add-messages-to-domains.md +2 -2
  92. package/dist/docs/development/guides/domains/04-add-resources-to-domains/05-add-entities-to-domains.md +1 -1
  93. package/dist/docs/development/guides/domains/04-add-resources-to-domains/06-domain-resources-page.md +40 -0
  94. package/dist/docs/development/guides/domains/06-visualize-domains/01-domain-diagram.md +89 -0
  95. package/dist/docs/development/guides/domains/06-visualize-domains/_category_.json +1 -2
  96. package/dist/docs/development/guides/domains/09-reference.md +4 -4
  97. package/dist/docs/development/guides/owners/automated-teams-and-users/create-your-own-connector.md +0 -2
  98. package/dist/docs/development/guides/owners/automated-teams-and-users/sync-from-github.md +0 -3
  99. package/dist/docs/development/guides/owners/automated-teams-and-users/sync-from-microsoft-entra.md +0 -2
  100. package/dist/docs/development/guides/resources/adrs/03-linking-resources.md +8 -0
  101. package/dist/docs/development/guides/resources/adrs/06-reference.md +4 -4
  102. package/dist/docs/development/guides/resources/agents/11-reference.md +1 -1
  103. package/dist/docs/development/guides/resources/data/04-reference.md +1 -1
  104. package/dist/docs/development/guides/resources/data-products/07-reference.md +1 -1
  105. package/dist/docs/development/guides/resources/entities/05-entity-maps.md +1 -1
  106. package/dist/docs/development/guides/resources/entities/06-reference.md +2 -2
  107. package/dist/docs/development/guides/resources/flows/01-introduction.md +11 -1
  108. package/dist/docs/development/guides/resources/flows/03-model-flow-steps.md +65 -6
  109. package/dist/docs/development/guides/resources/flows/05-explore-flows.md +112 -0
  110. package/dist/docs/development/guides/resources/flows/06-reference.md +25 -4
  111. package/dist/docs/development/guides/resources/messages/01-what-are-messages.md +19 -0
  112. package/dist/docs/development/guides/resources/messages/04-message-channels/01-introduction.md +16 -0
  113. package/dist/docs/development/guides/resources/messages/04-message-channels/05-reference.md +2 -1
  114. package/dist/docs/development/guides/resources/messages/10-reference.md +6 -3
  115. package/dist/docs/development/guides/resources/schemas/04-explore-schemas/01-schema-explorer.md +2 -2
  116. package/dist/docs/development/guides/resources/schemas/04-explore-schemas/02-fields-explorer.md +0 -9
  117. package/dist/docs/development/guides/resources/schemas/05-schema-api.md +0 -3
  118. package/dist/docs/development/guides/resources/schemas/06-schema-mcp.md +11 -3
  119. package/dist/docs/development/guides/resources/services/04-add-resources-to-services/03-add-entities-to-services.md +1 -1
  120. package/dist/docs/development/guides/resources/services/05-resources-page.md +50 -0
  121. package/dist/docs/development/guides/resources/services/08-reference.md +2 -1
  122. package/dist/docs/development/guides/systems/01-introduction.md +1 -1
  123. package/dist/docs/development/guides/systems/02-create-system.md +2 -2
  124. package/dist/docs/development/guides/systems/03-add-resources-to-systems.md +35 -9
  125. package/dist/docs/development/guides/systems/04-model-relationships-and-actors.md +5 -3
  126. package/dist/docs/development/guides/systems/05-system-context-maps.md +39 -22
  127. package/dist/docs/development/guides/systems/06-reference.md +4 -4
  128. package/dist/docs/development/license/01-license-file.md +151 -0
  129. package/dist/docs/development/license/_category_.json +12 -0
  130. package/dist/docs/development/license-keys/_category_.json +3 -3
  131. package/dist/docs/development/license-keys/integrations.md +7 -37
  132. package/dist/docs/development/license-keys/license-validation.md +4 -0
  133. package/dist/docs/development/license-keys/overview.md +5 -1
  134. package/dist/docs/development/license-keys/plans.md +4 -2
  135. package/dist/docs/development/studio/01-introduction.md +60 -0
  136. package/dist/docs/development/studio/02-create-a-canvas.md +103 -0
  137. package/dist/docs/development/studio/03-set-up-persistent-storage.md +59 -0
  138. package/dist/docs/development/studio/04-collaborate-on-a-canvas.md +78 -0
  139. package/dist/docs/development/studio/05-connect-your-agent.md +102 -0
  140. package/dist/docs/development/studio/06-run-studio-in-production.md +54 -0
  141. package/dist/docs/development/studio/07-reference.md +294 -0
  142. package/dist/docs/development/studio/_category_.json +12 -0
  143. package/dist/docs/development/upgrading/v5.md +345 -0
  144. package/dist/docs/federation/00-overview.md +2 -8
  145. package/dist/docs/federation/01-first-federation.md +6 -18
  146. package/dist/docs/federation/explanation/how-it-works.md +0 -4
  147. package/dist/docs/federation/explanation/lockfile-and-cache.md +0 -4
  148. package/dist/docs/federation/explanation/mvp-status-and-feedback.md +0 -4
  149. package/dist/docs/federation/explanation/ownership-and-references.md +0 -4
  150. package/dist/docs/federation/how-to/configure-github-sources.md +6 -5
  151. package/dist/docs/federation/how-to/configure-validation-rules.md +0 -4
  152. package/dist/docs/federation/how-to/run-in-ci.md +2 -9
  153. package/dist/docs/federation/how-to/share-assets-and-components.md +0 -4
  154. package/dist/docs/federation/how-to/use-local-sources.md +0 -4
  155. package/dist/docs/federation/legacy-federation/00-introduction.md +4 -22
  156. package/dist/docs/federation/legacy-federation/01-installation.md +7 -58
  157. package/dist/docs/federation/legacy-federation/02-plugin-configuration.md +3 -21
  158. package/dist/docs/federation/legacy-federation/03-examples.md +3 -5
  159. package/dist/docs/federation/legacy-federation/04-configuration.md +3 -8
  160. package/dist/docs/federation/legacy-federation/05-setup-team-catalog.md +3 -6
  161. package/dist/docs/federation/reference/cli.md +2 -8
  162. package/dist/docs/federation/reference/configuration.md +0 -4
  163. package/dist/docs/federation/reference/diagnostic-rules.md +0 -5
  164. package/dist/docs/federation/reference/generated-output.md +0 -4
  165. package/dist/docs/federation/reference/supported-content.md +0 -4
  166. package/dist/docs/federation/reference/troubleshooting.md +0 -14
  167. package/dist/docs/plugins/02-generators.md +1 -1
  168. package/dist/docs/plugins/amazon-apigateway/00-intro.md +3 -28
  169. package/dist/docs/plugins/amazon-apigateway/01-installation.md +4 -56
  170. package/dist/docs/plugins/amazon-apigateway/02-plugin-configuration.md +1 -6
  171. package/dist/docs/plugins/apicurio/00-intro.md +2 -31
  172. package/dist/docs/plugins/apicurio/01-installation.md +2 -26
  173. package/dist/docs/plugins/apicurio/02-plugin-configuration.md +5 -13
  174. package/dist/docs/plugins/apicurio/03-features.md +1 -3
  175. package/dist/docs/plugins/asyncapi/00-intro.md +2 -28
  176. package/dist/docs/plugins/asyncapi/01-installation.md +3 -54
  177. package/dist/docs/plugins/asyncapi/02-plugin-configuration.md +2 -1
  178. package/dist/docs/plugins/aws-glue-registry/00-intro.md +4 -32
  179. package/dist/docs/plugins/aws-glue-registry/00a-installation.md +3 -53
  180. package/dist/docs/plugins/azure-schema-registry/00-intro.md +4 -32
  181. package/dist/docs/plugins/azure-schema-registry/01-installation.md +6 -33
  182. package/dist/docs/plugins/azure-schema-registry/02-plugin-configuration.md +1 -14
  183. package/dist/docs/plugins/azure-schema-registry/04-examples.md +0 -2
  184. package/dist/docs/plugins/backstage/00-overview.md +1 -7
  185. package/dist/docs/plugins/backstage/01-get-started.md +6 -15
  186. package/dist/docs/plugins/backstage/06-configuration.md +2 -8
  187. package/dist/docs/plugins/confluent-schema-registry/00-intro.md +2 -29
  188. package/dist/docs/plugins/confluent-schema-registry/01-installation.md +2 -26
  189. package/dist/docs/plugins/eventbridge/00-intro.md +0 -4
  190. package/dist/docs/plugins/eventbridge/00a-installation.md +1 -87
  191. package/dist/docs/plugins/eventbridge/03-api.md +2 -20
  192. package/dist/docs/plugins/github/00-intro.md +0 -31
  193. package/dist/docs/plugins/github/01-installation.md +2 -34
  194. package/dist/docs/plugins/graphql/00-intro.md +0 -30
  195. package/dist/docs/plugins/graphql/01-installation.md +1 -53
  196. package/dist/docs/plugins/graphql/02-plugin-configuration.md +0 -1
  197. package/dist/docs/plugins/hookdeck/01-intro.md +1 -1
  198. package/dist/docs/plugins/openapi/00-intro.md +0 -30
  199. package/dist/docs/plugins/openapi/01-installation.md +1 -57
  200. package/dist/docs/plugins/openapi/02-plugin-configuration.md +0 -5
  201. package/dist/docs/plugins/openapi/03-features.md +0 -4
  202. package/dist/eventcatalog-config-file-utils.cjs +34 -41
  203. package/dist/eventcatalog-config-file-utils.d.cts +56 -58
  204. package/dist/eventcatalog-config-file-utils.d.ts +56 -58
  205. package/dist/eventcatalog-config-file-utils.js +1 -1
  206. package/dist/eventcatalog.cjs +408 -281
  207. package/dist/eventcatalog.config.d.cts +26 -15
  208. package/dist/eventcatalog.config.d.ts +26 -15
  209. package/dist/eventcatalog.js +46 -27
  210. package/dist/features.cjs +36 -49
  211. package/dist/features.d.cts +1 -3
  212. package/dist/features.d.ts +1 -3
  213. package/dist/features.js +4 -6
  214. package/dist/federation/federate.cjs +37 -44
  215. package/dist/federation/federate.js +5 -5
  216. package/dist/federation/source-provider.js +2 -2
  217. package/dist/generate.cjs +37 -42
  218. package/dist/generate.js +4 -4
  219. package/dist/resolve-catalog-dependencies.cjs +34 -41
  220. package/dist/resolve-catalog-dependencies.js +2 -2
  221. package/dist/runtime-dependencies/@eventcatalog__visualiser__studio-fonts.css.mjs +3 -0
  222. package/dist/runtime-dependencies/@eventcatalog__visualiser__studio.css.mjs +3 -0
  223. package/dist/runtime-dependencies/@eventcatalog__visualiser__studio.mjs +3 -0
  224. package/dist/runtime-dependencies/htmlparser2.mjs +3 -0
  225. package/dist/runtime-dependencies/manifest.json +5 -1
  226. package/dist/runtime-dependencies/react-syntax-highlighter__dist__cjs__create-element.js.mjs +3 -0
  227. package/dist/utils/child-process-signals.cjs +53 -0
  228. package/dist/utils/child-process-signals.d.cts +19 -0
  229. package/dist/utils/child-process-signals.d.ts +19 -0
  230. package/dist/utils/child-process-signals.js +6 -0
  231. package/dist/utils/cli-logger.cjs +1 -1
  232. package/dist/utils/cli-logger.js +2 -2
  233. package/dist/utils/license-status.cjs +11 -4
  234. package/dist/utils/license-status.d.cts +3 -2
  235. package/dist/utils/license-status.d.ts +3 -2
  236. package/dist/utils/license-status.js +1 -1
  237. package/eventcatalog/astro.config.mjs +21 -1
  238. package/eventcatalog/integrations/config-loader.mjs +56 -58
  239. package/eventcatalog/integrations/skip-startup-reload.mjs +31 -0
  240. package/eventcatalog/integrations/stable-dependency-order.mjs +29 -0
  241. package/eventcatalog/integrations/storage.mjs +20 -0
  242. package/eventcatalog/integrations/studio-server.mjs +4 -1
  243. package/eventcatalog/src/components/CatalogGraph/CatalogForceGraph.tsx +39 -0
  244. package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +2 -0
  245. package/eventcatalog/src/components/Homepage/CatalogDashboard.astro +342 -0
  246. package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +60 -4
  247. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +5 -1
  248. package/eventcatalog/src/components/NavigationProgress.astro +7 -0
  249. package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +2 -0
  250. package/eventcatalog/src/components/SchemaExplorer/ExamplesViewer.tsx +2 -0
  251. package/eventcatalog/src/components/SchemaExplorer/SchemaCodeModal.tsx +2 -0
  252. package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +2 -0
  253. package/eventcatalog/src/components/Search/SearchDataLoader.astro +16 -28
  254. package/eventcatalog/src/components/Search/SearchModal.tsx +2 -10
  255. package/eventcatalog/src/components/Search/search-utils.ts +26 -0
  256. package/eventcatalog/src/components/Seo.astro +5 -1
  257. package/eventcatalog/src/components/Settings/AssistantSettingsForm.tsx +53 -48
  258. package/eventcatalog/src/components/Settings/GeneralSettingsForm.tsx +183 -178
  259. package/eventcatalog/src/components/Settings/LicenseSettings.tsx +140 -31
  260. package/eventcatalog/src/components/Settings/LlmAccessSettingsForm.tsx +40 -35
  261. package/eventcatalog/src/components/Settings/LogoUpload.tsx +1 -1
  262. package/eventcatalog/src/components/Settings/McpSettingsForm.tsx +9 -8
  263. package/eventcatalog/src/components/Settings/Row.tsx +7 -4
  264. package/eventcatalog/src/components/Settings/SettingsShared.tsx +3 -3
  265. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +37 -76
  266. package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +2 -0
  267. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +122 -1
  268. package/eventcatalog/src/components/SideNav/SideNav.astro +9 -1
  269. package/eventcatalog/src/components/TrialBadge.astro +20 -9
  270. package/eventcatalog/src/content.config.ts +19 -2
  271. package/eventcatalog/src/features/auth/error.astro +4 -2
  272. package/eventcatalog/src/features/auth/login.astro +2 -1
  273. package/eventcatalog/src/features/auth/middleware/middleware-auth.ts +21 -1
  274. package/eventcatalog/src/features/auth/unauthorized.astro +1 -1
  275. package/eventcatalog/src/features/fields/pages/fields.astro +1 -1
  276. package/eventcatalog/src/features/integrations/eventcatalog-features.ts +13 -7
  277. package/eventcatalog/src/features/mcp/apps/architecture-diagram/node-chat.tsx +6 -45
  278. package/eventcatalog/src/features/mcp/apps/canvas/shared.ts +3 -3
  279. package/eventcatalog/src/features/mcp/apps/canvas/styles.css +3 -1
  280. package/eventcatalog/src/features/mcp/apps/canvas/view.tsx +7 -5
  281. package/eventcatalog/src/features/mcp/apps/generated/canvas.html +123 -98
  282. package/eventcatalog/src/features/mcp/apps/generated/viewer.html +118 -101
  283. package/eventcatalog/src/features/mcp/mcp-server.ts +20 -4
  284. package/eventcatalog/src/features/server/start.ts +25 -0
  285. package/eventcatalog/src/features/storage/database.ts +88 -0
  286. package/eventcatalog/src/features/storage/migrations.ts +85 -0
  287. package/eventcatalog/src/features/storage/sql/database.sql +5 -0
  288. package/eventcatalog/src/features/storage/sql/migrations/0001-studio-canvases.sql +12 -0
  289. package/eventcatalog/src/features/storage/sql/migrations.sql +15 -0
  290. package/eventcatalog/src/features/storage/sql.ts +28 -0
  291. package/eventcatalog/src/features/studio/agent-choreography.ts +13 -10
  292. package/eventcatalog/src/features/studio/agents.ts +28 -0
  293. package/eventcatalog/src/features/studio/api/canvases-api.ts +219 -0
  294. package/eventcatalog/src/features/studio/api/canvases.ts +25 -0
  295. package/eventcatalog/src/features/studio/api/client.ts +40 -0
  296. package/eventcatalog/src/features/studio/canvas-actions.ts +160 -34
  297. package/eventcatalog/src/features/studio/canvas-preview.ts +1 -2
  298. package/eventcatalog/src/features/studio/catalog.ts +16 -14
  299. package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +274 -105
  300. package/eventcatalog/src/features/studio/components/CanvasStatusMenu.tsx +15 -14
  301. package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +272 -119
  302. package/eventcatalog/src/features/studio/components/ConnectionsChooser.tsx +1 -2
  303. package/eventcatalog/src/features/studio/components/DeleteCanvasDialog.tsx +65 -0
  304. package/eventcatalog/src/features/studio/components/Dialog.tsx +60 -0
  305. package/eventcatalog/src/features/studio/components/JoinForm.tsx +101 -26
  306. package/eventcatalog/src/features/studio/components/LeftPanel.tsx +122 -230
  307. package/eventcatalog/src/features/studio/components/NodeDetails.tsx +183 -0
  308. package/eventcatalog/src/features/studio/components/OpenInStudioDialog.tsx +79 -0
  309. package/eventcatalog/src/features/studio/components/Presence.tsx +52 -45
  310. package/eventcatalog/src/features/studio/components/ShareDialog.tsx +277 -141
  311. package/eventcatalog/src/features/studio/components/ShortcutsDialog.tsx +41 -0
  312. package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +1314 -385
  313. package/eventcatalog/src/features/studio/components/StudioNotice.astro +24 -0
  314. package/eventcatalog/src/features/studio/document-name.ts +7 -0
  315. package/eventcatalog/src/features/studio/from-visualiser.ts +138 -0
  316. package/eventcatalog/src/features/studio/hooks/presence-sender.ts +19 -2
  317. package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +9 -3
  318. package/eventcatalog/src/features/studio/hooks/use-comments.ts +2 -2
  319. package/eventcatalog/src/features/studio/hooks/use-follow-camera.ts +8 -3
  320. package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +244 -32
  321. package/eventcatalog/src/features/studio/identity.ts +25 -1
  322. package/eventcatalog/src/features/studio/limits.ts +2 -0
  323. package/eventcatalog/src/features/studio/pages/[id].astro +42 -16
  324. package/eventcatalog/src/features/studio/pages/index.astro +596 -373
  325. package/eventcatalog/src/features/studio/pages/webmcp-relay.ts +12 -15
  326. package/eventcatalog/src/features/studio/server/canvas-mcp.ts +8 -23
  327. package/eventcatalog/src/features/studio/server/runtime.ts +150 -46
  328. package/eventcatalog/src/features/studio/server/sql/storage.sql +18 -0
  329. package/eventcatalog/src/features/studio/server/storage.ts +53 -73
  330. package/eventcatalog/src/features/studio/webmcp-relay/LICENSE +21 -0
  331. package/eventcatalog/src/features/studio/webmcp-relay/README.md +5 -0
  332. package/eventcatalog/src/features/studio/webmcp-relay/embed.js +2 -0
  333. package/eventcatalog/src/features/studio/webmcp-relay/widget.html +10 -0
  334. package/eventcatalog/src/features/studio/webmcp-relay/widget.js +1 -0
  335. package/eventcatalog/src/features/tools/catalog-tools.ts +3 -1
  336. package/eventcatalog/src/layouts/BaseLayout.astro +4 -4
  337. package/eventcatalog/src/layouts/SettingsLayout.astro +2 -2
  338. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +3 -2
  339. package/eventcatalog/src/pages/_index.astro +5 -342
  340. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.spec.ts +2 -3
  341. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +0 -7
  342. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +3 -1
  343. package/eventcatalog/src/pages/diagrams/[id]/[version]/_index.data.ts +0 -7
  344. package/eventcatalog/src/pages/diagrams/[id]/[version]/embed.astro +1 -0
  345. package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +1 -0
  346. package/eventcatalog/src/pages/directory/[type]/_index.data.ts +5 -7
  347. package/eventcatalog/src/pages/directory/[type]/index.astro +3 -1
  348. package/eventcatalog/src/pages/discover/[type]/_index.data.ts +5 -7
  349. package/eventcatalog/src/pages/discover/[type]/index.astro +3 -1
  350. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/_index.data.ts +0 -7
  351. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +1 -0
  352. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/_index.data.ts +0 -7
  353. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +1 -0
  354. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +0 -7
  355. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +3 -1
  356. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/_[filename].data.ts +0 -7
  357. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +0 -7
  358. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +1 -0
  359. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/examples/[...filename].astro +1 -0
  360. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/examples/_[...filename].data.ts +0 -4
  361. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/field-lineage/_index.data.ts +0 -7
  362. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/field-lineage/index.astro +1 -0
  363. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +3 -1
  364. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/_[filename].data.ts +0 -7
  365. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +1 -0
  366. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.spec.ts +2 -3
  367. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.ts +0 -7
  368. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +1 -0
  369. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/[filename].astro +1 -0
  370. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/_[filename].data.ts +0 -7
  371. package/eventcatalog/src/pages/docs/[type]/[id]/_index.data.ts +0 -7
  372. package/eventcatalog/src/pages/docs/[type]/[id]/index.astro +2 -1
  373. package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/_index.data.ts +0 -7
  374. package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/index.astro +1 -0
  375. package/eventcatalog/src/pages/docs/[type]/[id]/language/_index.data.ts +0 -7
  376. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +1 -0
  377. package/eventcatalog/src/pages/docs/custom/[...path]/_index.data.ts +0 -7
  378. package/eventcatalog/src/pages/docs/custom/[...path]/index.astro +1 -0
  379. package/eventcatalog/src/pages/docs/services/[id]/[docType]/[docId]/index.astro +1 -1
  380. package/eventcatalog/src/pages/docs/services/[id]/[version]/resources/index.astro +1 -0
  381. package/eventcatalog/src/pages/docs/services/[id]/asyncapi/[filename].astro +1 -1
  382. package/eventcatalog/src/pages/docs/services/[id]/changelog/index.astro +1 -1
  383. package/eventcatalog/src/pages/docs/services/[id]/graphql/[filename].astro +1 -1
  384. package/eventcatalog/src/pages/docs/services/[id]/spec/[filename].astro +1 -1
  385. package/eventcatalog/src/pages/docs/teams/[id]/_index.data.ts +0 -7
  386. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +1 -0
  387. package/eventcatalog/src/pages/docs/users/[id]/_index.data.ts +0 -7
  388. package/eventcatalog/src/pages/docs/users/[id]/index.astro +1 -0
  389. package/eventcatalog/src/pages/index.astro +4 -2
  390. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/_index.data.ts +0 -7
  391. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +9 -1
  392. package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +0 -7
  393. package/eventcatalog/src/pages/schemas/explorer/index.astro +4 -2
  394. package/eventcatalog/src/pages/settings/license.astro +9 -1
  395. package/eventcatalog/src/pages/triggers/[type]/[id]/[version]/_index.data.ts +0 -7
  396. package/eventcatalog/src/pages/triggers/[type]/[id]/[version]/index.astro +1 -0
  397. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +0 -7
  398. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/_index.data.ts +0 -7
  399. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/index.astro +1 -0
  400. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/_index.data.ts +0 -7
  401. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/index.astro +1 -0
  402. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/index.astro +1 -0
  403. package/eventcatalog/src/pages/visualiser/[type]/[id]/_index.data.ts +38 -0
  404. package/eventcatalog/src/pages/visualiser/[type]/[id]/index.astro +18 -41
  405. package/eventcatalog/src/pages/visualiser/designs/[id]/_index.data.ts +0 -7
  406. package/eventcatalog/src/pages/visualiser/designs/[id]/index.astro +2 -1
  407. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/_index.data.ts +0 -7
  408. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/index.astro +1 -0
  409. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/index.astro +1 -0
  410. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/_index.data.ts +0 -7
  411. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/index.astro +1 -0
  412. package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/_index.data.ts +0 -7
  413. package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/index.astro +1 -0
  414. package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/index.astro +1 -0
  415. package/eventcatalog/src/types/react-syntax-highlighter.d.ts +10 -0
  416. package/eventcatalog/src/utils/collections/flows.ts +20 -7
  417. package/eventcatalog/src/utils/collections/resource-owners.ts +8 -2
  418. package/eventcatalog/src/utils/collections/resources.ts +2 -0
  419. package/eventcatalog/src/utils/collections/util.ts +8 -4
  420. package/eventcatalog/src/utils/git.ts +11 -0
  421. package/eventcatalog/src/utils/homepage-dashboard.ts +143 -0
  422. package/eventcatalog/src/utils/license.ts +1 -1
  423. package/eventcatalog/src/utils/link-validation.ts +25 -19
  424. package/eventcatalog/src/utils/node-graphs/domain-entity-map.ts +3 -1
  425. package/eventcatalog/src/utils/node-graphs/domain-levels-node-graph.ts +3 -3
  426. package/eventcatalog/src/utils/node-graphs/field-node-graph.ts +4 -2
  427. package/eventcatalog/src/utils/node-graphs/layout-node-graph.ts +20 -1
  428. package/eventcatalog/src/utils/node-graphs/node-resource.ts +67 -0
  429. package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +1 -1
  430. package/eventcatalog/src/utils/page-loaders/hybrid-page.ts +4 -15
  431. package/eventcatalog/src/utils/page-title.ts +15 -0
  432. package/eventcatalog/src/utils/syntax-highlighter-renderer.ts +64 -0
  433. package/eventcatalog/src/utils/trial.ts +13 -0
  434. package/package.json +11 -11
  435. package/dist/docs/development/ask-your-architecture/05-skills/02-installation.md +0 -60
  436. package/dist/docs/development/ask-your-architecture/05-skills/_category_.json +0 -11
  437. package/dist/docs/development/ask-your-architecture/06-slack-integration/01-introduction.md +0 -63
  438. package/dist/docs/development/ask-your-architecture/06-slack-integration/02-slack-app-setup.md +0 -154
  439. package/dist/docs/development/ask-your-architecture/06-slack-integration/03-installation.md +0 -169
  440. package/dist/docs/development/ask-your-architecture/06-slack-integration/04-deployment.md +0 -236
  441. package/dist/docs/development/ask-your-architecture/06-slack-integration/05-usage.md +0 -140
  442. package/dist/docs/development/ask-your-architecture/06-slack-integration/06-troubleshooting.md +0 -268
  443. package/dist/docs/development/ask-your-architecture/06-slack-integration/_category_.json +0 -13
  444. package/dist/docs/development/guides/domains/06-visualize-domains/01-domain-integration-map.md +0 -40
  445. package/dist/runtime-dependencies/@mcp-b__webmcp-local-relay.mjs +0 -3
  446. package/eventcatalog/src/features/studio/canvas-doc.ts +0 -409
  447. package/eventcatalog/src/features/studio/clipboard.ts +0 -97
  448. package/eventcatalog/src/features/studio/components/CanvasControls.tsx +0 -154
  449. package/eventcatalog/src/features/studio/components/Comments.tsx +0 -770
  450. package/eventcatalog/src/features/studio/components/ContainerChooser.tsx +0 -145
  451. package/eventcatalog/src/features/studio/components/DragGhost.tsx +0 -115
  452. package/eventcatalog/src/features/studio/components/Picture.tsx +0 -21
  453. package/eventcatalog/src/features/studio/components/PropertiesPanel.tsx +0 -212
  454. package/eventcatalog/src/features/studio/components/canvas-nodes.tsx +0 -255
  455. package/eventcatalog/src/features/studio/components/icons.ts +0 -53
  456. package/eventcatalog/src/features/studio/components/status.ts +0 -51
  457. package/eventcatalog/src/features/studio/edges.ts +0 -56
  458. package/eventcatalog/src/features/studio/flow-state.ts +0 -92
  459. package/eventcatalog/src/features/studio/grouping.ts +0 -226
  460. package/eventcatalog/src/features/studio/layout.ts +0 -42
  461. package/eventcatalog/src/features/studio/levels.ts +0 -282
  462. package/eventcatalog/src/features/studio/node-types.ts +0 -205
  463. package/eventcatalog/src/features/studio/pages/new.astro +0 -24
  464. package/eventcatalog/src/features/studio/pages/wake.ts +0 -15
  465. package/eventcatalog/src/features/studio/placement.ts +0 -197
  466. package/eventcatalog/src/features/studio/studio.css +0 -273
  467. package/dist/{chunk-A2RZR3U4.js → chunk-6K7XZAYI.js} +3 -3
@@ -1,27 +1,24 @@
1
1
  import type { APIRoute } from 'astro';
2
- import fs from 'node:fs/promises';
3
- import path from 'node:path';
4
- import { createRequire } from 'node:module';
2
+ import embed from '../webmcp-relay/embed.js?raw';
3
+ import widgetHtml from '../webmcp-relay/widget.html?raw';
4
+ import widgetJs from '../webmcp-relay/widget.js?raw';
5
5
 
6
6
  /**
7
7
  * Serves the WebMCP local relay's browser files (embed.js loads widget.html next to it), so the canvas page
8
8
  * can hand its WebMCP tools to desktop MCP clients (Claude Code, Cursor...) running
9
- * `npx -y @mcp-b/webmcp-local-relay@5.1.0`. Served from the installed package, so the version is pinned.
9
+ * `npx -y @mcp-b/webmcp-local-relay@5.1.0`. The files are copies of that version's (see webmcp-relay/README.md),
10
+ * bundled with the page, so the version is pinned.
10
11
  */
11
- const FILES: Record<string, string> = {
12
- 'embed.js': 'text/javascript',
13
- 'widget.html': 'text/html',
14
- 'widget.js': 'text/javascript',
12
+ const FILES: Record<string, { body: string; contentType: string }> = {
13
+ 'embed.js': { body: embed, contentType: 'text/javascript' },
14
+ 'widget.html': { body: widgetHtml, contentType: 'text/html' },
15
+ 'widget.js': { body: widgetJs, contentType: 'text/javascript' },
15
16
  };
16
17
 
17
- const browserDirectory = path.join(path.dirname(createRequire(import.meta.url).resolve('@mcp-b/webmcp-local-relay')), 'browser');
18
-
19
18
  export const GET: APIRoute = async ({ params }) => {
20
- const file = params.file ?? '';
21
- const contentType = FILES[file];
22
- if (!contentType) return new Response('Not found', { status: 404 });
23
- const body = await fs.readFile(path.join(browserDirectory, file), 'utf-8');
24
- return new Response(body, { headers: { 'Content-Type': contentType, 'Cache-Control': 'no-cache' } });
19
+ const file = FILES[params.file ?? ''];
20
+ if (!file) return new Response('Not found', { status: 404 });
21
+ return new Response(file.body, { headers: { 'Content-Type': file.contentType, 'Cache-Control': 'no-cache' } });
25
22
  };
26
23
 
27
24
  export const prerender = false;
@@ -21,10 +21,10 @@ import {
21
21
  setCanvasStatus,
22
22
  setThreadResolved,
23
23
  type Author,
24
- } from '../canvas-doc';
24
+ getLayoutPositions,
25
+ } from '@eventcatalog/visualiser/studio';
25
26
  import { ADD_TO_CANVAS_DESCRIPTION, describeCanvas, edgeSpecsSchema, indexCatalog, nodeSpecsSchema } from '../canvas-actions';
26
27
  import { getCatalogResources } from '../catalog-resources';
27
- import { getLayoutPositions } from '../layout';
28
28
  import {
29
29
  playAddToCanvas,
30
30
  playComment,
@@ -35,7 +35,8 @@ import {
35
35
  playUpdateNode,
36
36
  type AgentStage,
37
37
  } from '../agent-choreography';
38
- import { STUDIO_SOCKET_PATH, findStudioRuntime, type AgentIdentity } from './runtime';
38
+ import { STUDIO_SOCKET_PATH, findStudioRuntime } from './runtime';
39
+ import { getAgent } from '../agents';
39
40
  import { addToolIcons } from './tool-icons';
40
41
 
41
42
  /**
@@ -46,21 +47,11 @@ import { addToolIcons } from './tool-icons';
46
47
 
47
48
  const loadCanvasView = getMcpAppViewLoader('canvas');
48
49
 
49
- const AGENT_COLORS: Record<string, string> = { ChatGPT: '#10a37f', Claude: '#d97757' };
50
-
51
50
  // The canvases entrypoint's icon (OpenAI MCP extensions: a 20x20, monochrome SVG in currentColor, 1.33px strokes)
52
51
  const CANVAS_ICON_SVG =
53
52
  '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="3.5" width="5.5" height="4.5" rx="1"/><rect x="12" y="3.5" width="5.5" height="4.5" rx="1"/><rect x="12" y="12" width="5.5" height="4.5" rx="1"/><path d="M8 5.75h4M14.75 8v4"/></svg>';
54
53
  const CANVAS_ICON = { src: `data:image/svg+xml;base64,${btoa(CANVAS_ICON_SVG)}`, mimeType: 'image/svg+xml', sizes: ['20x20'] };
55
54
 
56
- /** Who the agent is, from what it tells us or its client's user agent */
57
- const getAgent = (agentName: string | undefined, userAgent = ''): AgentIdentity => {
58
- const name =
59
- agentName?.trim() ||
60
- (/openai|chatgpt/i.test(userAgent) ? 'ChatGPT' : /claude|anthropic/i.test(userAgent) ? 'Claude' : 'AI agent');
61
- return { name, color: AGENT_COLORS[name] ?? '#8b5cf6' };
62
- };
63
-
64
55
  const text = (value: unknown, isError = false) => ({
65
56
  content: [{ type: 'text' as const, text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
66
57
  ...(isError && { isError }),
@@ -151,12 +142,6 @@ export function registerCanvasTools(server: McpServer, { catalogUrl, userAgent }
151
142
  input: {
152
143
  canvasId: canvasId.optional(),
153
144
  title: z.string().optional().describe('For a new canvas: what it is for'),
154
- userName: z
155
- .string()
156
- .optional()
157
- .describe(
158
- 'The name of the person you are working with, if you know it: others on the canvas see them by this name. Leave it out if you do not know it.'
159
- ),
160
145
  agentName,
161
146
  },
162
147
  },
@@ -170,7 +155,7 @@ export function registerCanvasTools(server: McpServer, { catalogUrl, userAgent }
170
155
  annotations: writes,
171
156
  errorMessage: 'Failed to open the canvas',
172
157
  load: async (params) => {
173
- const { title: newTitle, agentName: name, userName } = params as { title?: string; agentName?: string; userName?: string };
158
+ const { title: newTitle, agentName: name } = params as { title?: string; agentName?: string };
174
159
 
175
160
  // Opened without a canvas (e.g. from an entrypoint): the canvases to pick from, or start one
176
161
  if (!params.canvasId && !newTitle) {
@@ -207,7 +192,7 @@ export function registerCanvasTools(server: McpServer, { catalogUrl, userAgent }
207
192
  socketUrl,
208
193
  canvasUrl: canvasUrl(id),
209
194
  mcpUrl: `${catalogUrl}${buildUrl('/docs/mcp', true)}`,
210
- userName,
195
+ agent: getAgent(name, userAgent),
211
196
  };
212
197
  return {
213
198
  forModel: {
@@ -418,7 +403,7 @@ export function registerCanvasTools(server: McpServer, { catalogUrl, userAgent }
418
403
  {
419
404
  title: 'Tidy a canvas',
420
405
  description:
421
- "Lays the whole canvas out again with EventCatalog's visualiser layout (left to right, connections routed around nodes); everyone on it sees the nodes glide into place. It moves everything, including what people placed themselves, so only use it when the user asks to tidy up: addToCanvas already places what you add next to what it connects to.",
406
+ "Lays the whole canvas out again with EventCatalog's visualiser layout (left to right, connections routed around nodes); everyone on it sees the nodes glide into place. Call it once when you finish a round of changes, before you hand back to the user, so the canvas is tidy for everyone, and whenever the user asks to tidy up. Don't call it after every change (addToCanvas already places what you add next to what it connects to while you work), and skip it if the user asked to keep the layout as it is: it moves everything, including what people placed themselves.",
422
407
  inputSchema: z.object({ canvasId, agentName }),
423
408
  annotations: writes,
424
409
  },
@@ -427,7 +412,7 @@ export function registerCanvasTools(server: McpServer, { catalogUrl, userAgent }
427
412
  const { nodes, edges } = await runtime.withCanvas(id, readCanvas);
428
413
  const positions = await getLayoutPositions(nodes, edges);
429
414
  await playLayout(asAgent(id, name).stage, positions);
430
- return text({ laidOut: positions.size });
415
+ return text({ laidOut: positions.nodes.size });
431
416
  }
432
417
  );
433
418
 
@@ -5,27 +5,34 @@ import { Awareness, applyAwarenessUpdate, encodeAwarenessUpdate, removeAwareness
5
5
  import type { XYPosition } from '@xyflow/react';
6
6
  import type { AgentPresence } from '../agent-choreography';
7
7
  import {
8
- canvasDocumentName,
9
- canvasIdFromDocumentName,
8
+ addCanvasContent,
10
9
  getCanvasMaps,
11
10
  readMeta,
12
11
  setMeta,
13
12
  getCanvasStatus,
13
+ type CanvasContent,
14
14
  type CanvasStatus,
15
- } from '../canvas-doc';
15
+ } from '@eventcatalog/visualiser/studio';
16
+ import { CANVAS_DELETED_REASON, canvasDocumentName, canvasIdFromDocumentName } from '../document-name';
16
17
  import { getCanvasPreview, type CanvasPreview } from '../canvas-preview';
18
+ import { MAX_TITLE_LENGTH } from '../limits';
17
19
  import { fromBase64, toBase64, type SyncRequest, type SyncResponse } from '../tool-sync';
18
20
  import config from '../../../utils/eventcatalog-config/source';
19
21
  import { isAuthEnabled } from '../../../utils/feature';
20
- import { createStorage, memoryStorage, type CanvasStorage, type StudioStorageConfig } from './storage';
22
+ import { createStorage, memoryStorage, type CanvasStorage } from './storage';
23
+ import type { StorageConfig } from '../../storage/database';
24
+ import { isDatabaseTooNew } from '../../storage/migrations';
21
25
 
22
26
  export const STUDIO_SOCKET_PATH = '/_eventcatalog/studio';
23
27
  const SOCKET_PATH_PATTERN = /\/_eventcatalog\/studio\/?$/;
24
28
  // Agents show as present while they work, and leave a while after their last change (before awareness's own
25
29
  // 30 second timeout would drop them anyway)
26
30
  const AGENT_PRESENCE_TTL_MS = 25_000;
31
+ // Application close codes are 4000-4999
32
+ const CANVAS_DELETED_CLOSE_CODE = 4410;
27
33
 
28
- export type AgentIdentity = { name: string; color: string };
34
+ import type { AgentIdentity } from '../agents';
35
+ export type { AgentIdentity };
29
36
  export type CanvasSummary = {
30
37
  canvasId: string;
31
38
  title?: string;
@@ -53,6 +60,8 @@ type RuntimeState = {
53
60
  snapshots: Map<string, Uint8Array>;
54
61
  /** When each canvas last changed (kept with it in storage) */
55
62
  updatedAt: Map<string, number>;
63
+ /** Canvases deleted (by document name): refused from then on, so a browser still holding one can't bring it back */
64
+ deleted: Set<string>;
56
65
  agents: Map<string, { awareness: Awareness; expires: ReturnType<typeof setTimeout> }>;
57
66
  attached: WeakSet<HttpServer>;
58
67
  storage: CanvasStorage;
@@ -74,6 +83,7 @@ const createState = (): RuntimeState => {
74
83
  const state: RuntimeState = {
75
84
  snapshots: new Map(),
76
85
  updatedAt: new Map(),
86
+ deleted: new Set(),
77
87
  agents: new Map(),
78
88
  attached: new WeakSet(),
79
89
  storage: memoryStorage(),
@@ -82,9 +92,13 @@ const createState = (): RuntimeState => {
82
92
  server: new Server({
83
93
  quiet: true,
84
94
  // With sign-in on, only signed-in people can open a canvas (refused until the pages have said how to check)
85
- onConnect: async ({ request }) => {
86
- if (!isAuthEnabled()) return;
87
- if (!(await state.isSignedIn?.(request).catch(() => false))) throw new Error('Sign in to open this canvas');
95
+ onConnect: async ({ request, documentName }) => {
96
+ if (isAuthEnabled() && !(await state.isSignedIn?.(request).catch(() => false))) {
97
+ throw new Error('Sign in to open this canvas');
98
+ }
99
+ if (state.deleted.has(documentName)) {
100
+ throw Object.assign(new Error('This canvas was deleted'), { reason: CANVAS_DELETED_REASON });
101
+ }
88
102
  },
89
103
  onLoadDocument: async ({ documentName, document }) => {
90
104
  // A hook that throws leaves the document half loaded in Hocuspocus (ueberdosis/hocuspocus#1156)
@@ -106,8 +120,18 @@ const createState = (): RuntimeState => {
106
120
  return state;
107
121
  };
108
122
 
123
+ /** A canvas just started: its title, and when and by whom (a person, or an agent) */
124
+ const setStarted = (doc: Y.Doc, { title, createdBy }: { title?: string; createdBy?: string }) =>
125
+ setMeta(doc, { ...(title && { title }), createdAt: Date.now(), ...(createdBy && { createdBy }) });
126
+
127
+ /** Nothing on it, no title, and nobody on it */
128
+ const isBlank = (canvas: CanvasSummary) =>
129
+ canvas.nodeCount === 0 && canvas.openComments === 0 && !canvas.title && canvas.people.length === 0;
130
+
109
131
  /** Keeps a canvas's snapshot, and writes it to storage (failing to store it doesn't lose it from memory) */
110
132
  const saveSnapshot = (state: RuntimeState, documentName: string, document: Y.Doc) => {
133
+ // Deleted while it was still open (e.g. waiting to be stored)
134
+ if (state.deleted.has(documentName)) return;
111
135
  const snapshot = Y.encodeStateAsUpdate(document);
112
136
  state.snapshots.set(documentName, snapshot);
113
137
  try {
@@ -121,7 +145,7 @@ const saveSnapshot = (state: RuntimeState, documentName: string, document: Y.Doc
121
145
  * The collaboration server, shared (through globalThis) by the WebSocket upgrade handler and server code such
122
146
  * as MCP tools, which edit canvases as agents. Only the state is shared, so code changes apply without a restart.
123
147
  */
124
- class StudioRuntime {
148
+ export class StudioRuntime {
125
149
  constructor(private readonly state: RuntimeState) {}
126
150
 
127
151
  get server() {
@@ -144,19 +168,20 @@ class StudioRuntime {
144
168
  }
145
169
 
146
170
  /**
147
- * Keeps canvases in the storage configured (`studio.storage`, memory by default), and loads the ones
171
+ * Keeps canvases in the storage configured (the catalog's `storage`, memory by default), and loads the ones
148
172
  * stored there. Set once when the server starts; calling it again with the same config does nothing.
149
173
  */
150
- async useStorage(config: StudioStorageConfig | undefined, projectDirectory: string) {
174
+ async useStorage(config: StorageConfig | undefined, projectDirectory: string) {
151
175
  const key = JSON.stringify({ config: config ?? null, projectDirectory });
152
176
  if (this.state.storageKey === key) return this.state.ready;
153
177
  const storage = createStorage(config, projectDirectory);
154
178
  this.state.storageKey = key;
155
179
  this.state.storage = storage;
156
- this.state.ready = storage.loadAll().then((stored) => {
180
+ this.state.ready = storage.loadAll().then(({ canvases, deleted }) => {
181
+ deleted.forEach((name) => this.state.deleted.add(name));
157
182
  // Canvases already open here (e.g. the dev server reloading) are kept as they are
158
- stored.forEach(({ state, updatedAt }, name) => {
159
- if (this.snapshots.has(name)) return;
183
+ canvases.forEach(({ state, updatedAt }, name) => {
184
+ if (this.snapshots.has(name) || this.state.deleted.has(name)) return;
160
185
  this.snapshots.set(name, state);
161
186
  if (updatedAt) this.state.updatedAt.set(name, updatedAt);
162
187
  });
@@ -173,8 +198,13 @@ class StudioRuntime {
173
198
  this.state.started ??= (async () => {
174
199
  const projectDirectory = process.env.PROJECT_DIR || process.cwd();
175
200
  try {
176
- await this.useStorage(config.studio?.storage, projectDirectory);
201
+ await this.useStorage(config.storage, projectDirectory);
177
202
  } catch (error) {
203
+ // Not kept in memory instead: everyone's changes would quietly stop being saved
204
+ if (isDatabaseTooNew(error)) {
205
+ console.error(`[studio] Studio isn't available: ${(error as Error).message}`);
206
+ throw error;
207
+ }
178
208
  console.error(
179
209
  `[studio] Could not use the Studio storage configured, keeping canvases in memory: ${(error as Error).message}`
180
210
  );
@@ -186,7 +216,7 @@ class StudioRuntime {
186
216
  const { type, location } = this.storage;
187
217
  console.log(
188
218
  type === 'memory'
189
- ? `[studio] Collaboration server ready at ${STUDIO_SOCKET_PATH}. Canvases are kept in memory and lost when the server restarts or redeploys: set studio.storage in eventcatalog.config.js to keep them.`
219
+ ? `[studio] Collaboration server ready at ${STUDIO_SOCKET_PATH}. Canvases are kept in memory and lost when the server restarts or redeploys: set storage in eventcatalog.config.js to keep them.`
190
220
  : `[studio] Collaboration server ready at ${STUDIO_SOCKET_PATH} (canvases stored in ${location})`
191
221
  );
192
222
  }
@@ -232,18 +262,77 @@ class StudioRuntime {
232
262
 
233
263
  exists(canvasId: string) {
234
264
  const name = canvasDocumentName(canvasId);
235
- return this.server.hocuspocus.documents.has(name) || this.snapshots.has(name);
265
+ return !this.state.deleted.has(name) && (this.server.hocuspocus.documents.has(name) || this.snapshots.has(name));
236
266
  }
237
267
 
238
- /** Starts a canvas with a title, recording when and by whom (a person, or an agent). Returns its id. */
239
- async createCanvas({ title, createdBy }: { title?: string; createdBy?: string }) {
268
+ isDeleted(canvasId: string) {
269
+ return this.state.deleted.has(canvasDocumentName(canvasId));
270
+ }
271
+
272
+ /**
273
+ * Deletes a canvas for everyone: from storage, and from the browsers that have it open (they're disconnected, told
274
+ * why, and refused from then on, so their copy can't bring it back). Returns false if there's no such canvas.
275
+ * Throws if storage can't delete it, leaving it as it was.
276
+ */
277
+ deleteCanvas(canvasId: string) {
278
+ if (!this.exists(canvasId)) return false;
279
+ const name = canvasDocumentName(canvasId);
280
+ this.state.storage.remove(name);
281
+ this.state.deleted.add(name);
282
+ this.snapshots.delete(name);
283
+ this.state.updatedAt.delete(name);
284
+ this.agents.forEach(({ awareness, expires }, key) => {
285
+ if (!key.startsWith(`${name}::`)) return;
286
+ clearTimeout(expires);
287
+ awareness.destroy();
288
+ this.agents.delete(key);
289
+ });
290
+ const { hocuspocus } = this.server;
291
+ const document = hocuspocus.documents.get(name);
292
+ if (document) {
293
+ // Told why, so they can say so (Hocuspocus's closeConnections only says it reset them)
294
+ document.connections.forEach((_clients, connection) =>
295
+ connection.close({ code: CANVAS_DELETED_CLOSE_CODE, reason: CANVAS_DELETED_REASON })
296
+ );
297
+ // Unloaded now, or once it's done storing (which skips deleted canvases)
298
+ void hocuspocus.unloadDocument(document);
299
+ }
300
+ return true;
301
+ }
302
+
303
+ /**
304
+ * Starts a canvas with a title, recording when and by whom (a person, or an agent), and what's on it to begin
305
+ * with, if anything. Returns its id.
306
+ */
307
+ async createCanvas(started: { title?: string; createdBy?: string }, content?: CanvasContent) {
240
308
  const canvasId = crypto.randomUUID();
241
- await this.withCanvas(canvasId, (doc) =>
242
- setMeta(doc, { ...(title && { title }), createdAt: Date.now(), ...(createdBy && { createdBy }) })
243
- );
309
+ await this.withCanvas(canvasId, (doc) => {
310
+ setStarted(doc, started);
311
+ if (content) addCanvasContent(doc, content);
312
+ });
244
313
  return canvasId;
245
314
  }
246
315
 
316
+ /**
317
+ * Makes a copy of a canvas's design (its nodes and connections, not its comments or status) as a new draft, called
318
+ * "Copy of" the original unless given a title. Returns the copy's id, or undefined if there's no such canvas.
319
+ */
320
+ async copyCanvas(canvasId: string, { title, createdBy }: { title?: string; createdBy?: string } = {}) {
321
+ if (!this.exists(canvasId)) return undefined;
322
+ const original = await this.withCanvas(canvasId, (doc) => {
323
+ const { nodes, edges } = getCanvasMaps(doc);
324
+ return { title: readMeta(doc).title, nodes: [...nodes.entries()], edges: [...edges.entries()] };
325
+ });
326
+ const copyId = crypto.randomUUID();
327
+ await this.withCanvas(copyId, (doc) => {
328
+ const { nodes, edges } = getCanvasMaps(doc);
329
+ setStarted(doc, { title: title ?? `Copy of ${original.title ?? 'Untitled canvas'}`.slice(0, MAX_TITLE_LENGTH), createdBy });
330
+ original.nodes.forEach(([id, node]) => nodes.set(id, structuredClone(node)));
331
+ original.edges.forEach(([id, edge]) => edges.set(id, structuredClone(edge)));
332
+ });
333
+ return copyId;
334
+ }
335
+
247
336
  /** Read or change a canvas as the server. Changes sync to everyone on it, like anyone else's. */
248
337
  async withCanvas<T>(canvasId: string, fn: (doc: Y.Doc) => T): Promise<T> {
249
338
  const connection = await this.server.hocuspocus.openDirectConnection(canvasDocumentName(canvasId));
@@ -286,32 +375,46 @@ class StudioRuntime {
286
375
  return response;
287
376
  }
288
377
 
378
+ /**
379
+ * The canvases, the most recently changed first. Ones started and left empty (e.g. a made-up link opened, then left)
380
+ * aren't listed: they can still be opened by their link.
381
+ */
289
382
  listCanvases({ withPreview = false }: { withPreview?: boolean } = {}): CanvasSummary[] {
290
383
  const names = new Set([...this.server.hocuspocus.documents.keys(), ...this.snapshots.keys()]);
291
- return [...names].flatMap((name) => {
292
- const canvasId = canvasIdFromDocumentName(name);
293
- if (!canvasId) return [];
294
- const live = this.server.hocuspocus.documents.get(name);
295
- const doc = live ?? new Y.Doc();
296
- if (!live) Y.applyUpdate(doc, this.snapshots.get(name)!);
297
- const { nodes, edges, threads } = getCanvasMaps(doc);
298
- const meta = readMeta(doc);
299
- return [
300
- {
301
- canvasId,
302
- title: meta.title,
303
- status: getCanvasStatus(meta),
304
- createdAt: meta.createdAt,
305
- ...(meta.createdBy && { createdBy: meta.createdBy }),
306
- updatedAt: this.state.updatedAt.get(name) ?? meta.createdAt,
307
- nodeCount: nodes.size,
308
- edgeCount: edges.size,
309
- openComments: Array.from(threads.values()).filter((thread) => !thread.get('resolved')).length,
310
- people: live ? this.peopleOn(canvasId) : [],
311
- ...(withPreview && { preview: getCanvasPreview(Array.from(nodes.values()), Array.from(edges.values())) }),
312
- },
313
- ];
314
- });
384
+ return [...names]
385
+ .flatMap((name) => {
386
+ const canvasId = canvasIdFromDocumentName(name);
387
+ return canvasId && this.exists(canvasId) ? [this.summarize(canvasId, { withPreview })] : [];
388
+ })
389
+ .filter((canvas) => !isBlank(canvas))
390
+ .sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
391
+ }
392
+
393
+ /** One canvas, as listed (undefined if there's no such canvas) */
394
+ getCanvas(canvasId: string): CanvasSummary | undefined {
395
+ return this.exists(canvasId) ? this.summarize(canvasId) : undefined;
396
+ }
397
+
398
+ private summarize(canvasId: string, { withPreview = false }: { withPreview?: boolean } = {}): CanvasSummary {
399
+ const name = canvasDocumentName(canvasId);
400
+ const live = this.server.hocuspocus.documents.get(name);
401
+ const doc = live ?? new Y.Doc();
402
+ if (!live) Y.applyUpdate(doc, this.snapshots.get(name)!);
403
+ const { nodes, edges, threads } = getCanvasMaps(doc);
404
+ const meta = readMeta(doc);
405
+ return {
406
+ canvasId,
407
+ title: meta.title,
408
+ status: getCanvasStatus(meta),
409
+ createdAt: meta.createdAt,
410
+ ...(meta.createdBy && { createdBy: meta.createdBy }),
411
+ updatedAt: this.state.updatedAt.get(name) ?? meta.createdAt,
412
+ nodeCount: nodes.size,
413
+ edgeCount: edges.size,
414
+ openComments: Array.from(threads.values()).filter((thread) => !thread.get('resolved')).length,
415
+ people: live ? this.peopleOn(canvasId) : [],
416
+ ...(withPreview && { preview: getCanvasPreview(Array.from(nodes.values()), Array.from(edges.values())) }),
417
+ };
315
418
  }
316
419
 
317
420
  /** Names of the people and agents on a canvas right now */
@@ -364,6 +467,7 @@ const store = globalThis as typeof globalThis & { [RUNTIME_KEY]?: RuntimeState }
364
467
  */
365
468
  const upgrade = (state: RuntimeState) => {
366
469
  state.updatedAt ??= new Map();
470
+ state.deleted ??= new Set();
367
471
  return state;
368
472
  };
369
473
 
@@ -0,0 +1,18 @@
1
+ -- The statements Studio's SQLite storage runs (see storage.ts), each named by the `-- name:` line above it. The tables
2
+ -- are made by the catalog's migrations (features/storage/sql/migrations).
3
+
4
+ -- name: saveCanvas
5
+ INSERT INTO studio_canvases (name, state, updated_at) VALUES (?, ?, ?)
6
+ ON CONFLICT (name) DO UPDATE SET state = excluded.state, updated_at = excluded.updated_at;
7
+
8
+ -- name: removeCanvas
9
+ DELETE FROM studio_canvases WHERE name = ?;
10
+
11
+ -- name: recordDeleted
12
+ INSERT OR IGNORE INTO studio_deleted_canvases (name, deleted_at) VALUES (?, ?);
13
+
14
+ -- name: allCanvases
15
+ SELECT name, state, updated_at FROM studio_canvases;
16
+
17
+ -- name: allDeleted
18
+ SELECT name FROM studio_deleted_canvases;
@@ -1,110 +1,90 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
1
+ import { getDatabasePath, openDatabase, type Database, type StorageConfig, type StorageType } from '../../storage/database';
2
+ import { namedStatements } from '../../storage/sql';
3
+ import storageSql from './sql/storage.sql?raw';
3
4
 
4
5
  /**
5
- * Where Studio keeps canvases (`studio.storage` in eventcatalog.config.js). People and agents always work on the copy
6
- * the collaboration server holds in memory, so storage is only read when the server starts, and written a moment
7
- * after a canvas changes: it doesn't slow down editing. Canvases are stored as their whole Yjs document
8
- * (Y.encodeStateAsUpdate), so nothing is lost between restarts.
6
+ * Where Studio keeps canvases: the catalog's storage (`storage` in eventcatalog.config.js). People and agents always
7
+ * work on the copy the collaboration server holds in memory, so storage is only read when the server starts, and
8
+ * written a moment after a canvas changes: it doesn't slow down editing. Canvases are stored as their whole Yjs
9
+ * document (Y.encodeStateAsUpdate), so nothing is lost between restarts.
9
10
  */
10
- export type StudioStorageConfig =
11
- /** Kept only while the server runs: canvases are lost when it restarts or redeploys (the default) */
12
- | { type: 'memory' }
13
- /**
14
- * One SQLite database file (default `.eventcatalog/studio.db` in the catalog). Needs Node.js 22.13 or later. In a
15
- * container, put it on a volume so it survives redeploys.
16
- */
17
- | { type: 'sqlite'; path?: string };
18
-
19
- export type StorageType = StudioStorageConfig['type'];
20
-
21
- export const DEFAULT_STORAGE: StudioStorageConfig = { type: 'memory' };
22
- const DEFAULT_SQLITE_PATH = '.eventcatalog/studio.db';
23
11
 
24
12
  /** A canvas as stored: its whole document, and when it last changed */
25
13
  export type StoredCanvas = { state: Uint8Array; updatedAt?: number };
26
14
 
15
+ /** What's stored: every canvas, and the canvases deleted (so a browser still holding one can't bring it back) */
16
+ export type StoredCanvases = { canvases: Map<string, StoredCanvas>; deleted: Set<string> };
17
+
27
18
  export type CanvasStorage = {
28
19
  type: StorageType;
29
20
  /** Where it is, for logs (e.g. the database file) */
30
21
  location?: string;
31
- /** Every stored canvas, by document name (read once, when the server starts) */
32
- loadAll: () => Promise<Map<string, StoredCanvas>>;
22
+ /** Every stored canvas by document name, and the deleted ones (read once, when the server starts) */
23
+ loadAll: () => Promise<StoredCanvases>;
33
24
  /**
34
25
  * Stores a canvas's document, with when it last changed (now when not known). Synchronous, so canvases can still
35
26
  * be saved as the process exits.
36
27
  */
37
28
  save: (documentName: string, state: Uint8Array, updatedAt?: number) => void;
29
+ /** Deletes a canvas, and records that it was deleted */
30
+ remove: (documentName: string) => void;
38
31
  };
39
32
 
40
33
  export const memoryStorage = (): CanvasStorage => ({
41
34
  type: 'memory',
42
- loadAll: async () => new Map(),
35
+ loadAll: async () => ({ canvases: new Map(), deleted: new Set() }),
43
36
  save: () => {},
37
+ remove: () => {},
44
38
  });
45
39
 
46
- type SqliteDatabase = {
47
- exec: (sql: string) => void;
48
- prepare: (sql: string) => { run: (...values: unknown[]) => unknown; all: () => unknown[] };
49
- };
40
+ /** The queries Studio runs, from ./sql/storage.sql (its tables are made by the catalog's migrations) */
41
+ const QUERIES = ['saveCanvas', 'removeCanvas', 'recordDeleted', 'allCanvases', 'allDeleted'] as const;
42
+ const SQL = namedStatements(storageSql, QUERIES);
50
43
 
44
+ type Statements = Record<(typeof QUERIES)[number], ReturnType<Database['prepare']>>;
45
+
46
+ /** Canvases in the catalog's SQLite database, in `file` */
51
47
  export const sqliteStorage = (file: string): CanvasStorage => {
52
- let database: SqliteDatabase | undefined;
53
- let upsert: ReturnType<SqliteDatabase['prepare']> | undefined;
48
+ let opened: { database: Database; statements: Statements } | undefined;
49
+ const use = () => {
50
+ if (!opened) throw new Error('SQLite storage was used before it was opened');
51
+ return opened;
52
+ };
54
53
  return {
55
54
  type: 'sqlite',
56
55
  location: file,
57
56
  loadAll: async () => {
58
- // Node's built-in SQLite (no native package to install), asked of Node itself: the dev server loads this
59
- // through Vite, which doesn't know node:sqlite and fails to import it
60
- type Sqlite = { DatabaseSync: new (file: string) => SqliteDatabase };
61
- const getBuiltinModule = (process as { getBuiltinModule?: (id: string) => unknown }).getBuiltinModule;
62
- let sqlite: Sqlite | undefined;
57
+ const database = await openDatabase(file);
58
+ const statements = Object.fromEntries(QUERIES.map((name) => [name, database.prepare(SQL[name])])) as Statements;
59
+ opened = { database, statements };
60
+
61
+ const rows = statements.allCanvases.all() as { name: string; state: Uint8Array; updated_at: number }[];
62
+ const deleted = statements.allDeleted.all() as { name: string }[];
63
+ return {
64
+ canvases: new Map(rows.map((row) => [row.name, { state: new Uint8Array(row.state), updatedAt: Number(row.updated_at) }])),
65
+ deleted: new Set(deleted.map((row) => row.name)),
66
+ };
67
+ },
68
+ save: (documentName, state, updatedAt = Date.now()) => {
69
+ use().statements.saveCanvas.run(documentName, state, updatedAt);
70
+ },
71
+ remove: (documentName) => {
72
+ const { database, statements } = use();
73
+ database.exec('BEGIN');
63
74
  try {
64
- sqlite = getBuiltinModule
65
- ? (getBuiltinModule('node:sqlite') as Sqlite | undefined)
66
- : await import(/* @vite-ignore */ ['node', 'sqlite'].join(':'));
75
+ statements.recordDeleted.run(documentName, Date.now());
76
+ statements.removeCanvas.run(documentName);
77
+ database.exec('COMMIT');
67
78
  } catch (error) {
68
- throw new Error(`Could not load node:sqlite (${(error as Error).message})`);
79
+ database.exec('ROLLBACK');
80
+ throw error;
69
81
  }
70
- if (!sqlite?.DatabaseSync) {
71
- throw new Error(
72
- `SQLite storage needs Node.js 22.13 or later (it uses node:sqlite); this is Node.js ${process.versions.node}`
73
- );
74
- }
75
- fs.mkdirSync(path.dirname(file), { recursive: true });
76
- database = new sqlite.DatabaseSync(file);
77
- database.exec('PRAGMA journal_mode = WAL');
78
- database.exec(
79
- 'CREATE TABLE IF NOT EXISTS canvases (name TEXT PRIMARY KEY, state BLOB NOT NULL, updated_at INTEGER NOT NULL)'
80
- );
81
- upsert = database.prepare(
82
- 'INSERT INTO canvases (name, state, updated_at) VALUES (?, ?, ?) ON CONFLICT(name) DO UPDATE SET state = excluded.state, updated_at = excluded.updated_at'
83
- );
84
- const rows = database.prepare('SELECT name, state, updated_at FROM canvases').all() as {
85
- name: string;
86
- state: Uint8Array;
87
- updated_at: number;
88
- }[];
89
- return new Map(rows.map((row) => [row.name, { state: new Uint8Array(row.state), updatedAt: Number(row.updated_at) }]));
90
- },
91
- save: (documentName, state, updatedAt = Date.now()) => {
92
- if (!upsert) throw new Error('SQLite storage was used before it was opened');
93
- upsert.run(documentName, state, updatedAt);
94
82
  },
95
83
  };
96
84
  };
97
85
 
98
- /** The storage a config asks for, with paths relative to the catalog. Unknown types are an error. */
99
- export const createStorage = (config: StudioStorageConfig | undefined, projectDirectory: string): CanvasStorage => {
100
- const chosen = config ?? DEFAULT_STORAGE;
101
- const resolve = (location: string | undefined, fallback: string) => path.resolve(projectDirectory, location ?? fallback);
102
- switch (chosen.type) {
103
- case 'memory':
104
- return memoryStorage();
105
- case 'sqlite':
106
- return sqliteStorage(resolve(chosen.path, DEFAULT_SQLITE_PATH));
107
- default:
108
- throw new Error(`Unknown studio.storage type "${(chosen as { type?: string }).type}": use "memory" or "sqlite"`);
109
- }
86
+ /** Canvases in the storage a config asks for (paths relative to the catalog). Unknown types are an error. */
87
+ export const createStorage = (config: StorageConfig | undefined, projectDirectory: string): CanvasStorage => {
88
+ const file = getDatabasePath(config, projectDirectory);
89
+ return file ? sqliteStorage(file) : memoryStorage();
110
90
  };
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 mcp-b contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.