@eventcatalog/core 5.0.0-beta.9 → 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.
- package/LICENSE +13 -12
- package/README.md +5 -5
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +91 -44
- package/dist/analytics/log-build.d.cts +2 -2
- package/dist/analytics/log-build.d.ts +2 -2
- package/dist/analytics/log-build.js +6 -5
- package/dist/analytics/studio-usage.cjs +90 -0
- package/dist/analytics/studio-usage.d.cts +5 -0
- package/dist/analytics/studio-usage.d.ts +5 -0
- package/dist/analytics/studio-usage.js +10 -0
- package/dist/catalog-runtime.cjs +1 -1
- package/dist/{chunk-RFIXKATC.js → chunk-4G7FSRPN.js} +35 -42
- package/dist/{chunk-AWOO63ME.js → chunk-AD7CL772.js} +11 -4
- package/dist/chunk-CA6WCQ2T.js +54 -0
- package/dist/{chunk-GAHKCOQM.js → chunk-JZN4RSMF.js} +1 -1
- package/dist/{chunk-JQMYHZWI.js → chunk-NEAVAFSJ.js} +16 -8
- package/dist/{chunk-4CFZUHGG.js → chunk-NIVST4WY.js} +1 -1
- package/dist/{chunk-ENAG6UH7.js → chunk-NKNOXQ62.js} +1 -6
- package/dist/{chunk-ZEY2XUJN.js → chunk-OX6TSVHY.js} +6 -6
- package/dist/{chunk-36RYTYT5.js → chunk-PD7KTSYN.js} +1 -1
- package/dist/{chunk-EUNQJO3J.js → chunk-RE7UTPWH.js} +1 -1
- package/dist/chunk-S5GWAUG5.js +29 -0
- package/dist/{chunk-TZVDMWQX.js → chunk-YQGRMDRB.js} +4 -2
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/docs/api/02-config.md +57 -7
- package/dist/docs/development/00-why-eventcatalog.md +11 -11
- package/dist/docs/development/01-fundamentals.md +20 -13
- package/dist/docs/development/01-getting-started/configuration-overview.md +2 -2
- package/dist/docs/development/01-getting-started/installation.md +9 -1
- package/dist/docs/development/_getting-started.mdx +1 -1
- package/dist/docs/development/ask-your-architecture/01-intro.md +5 -5
- package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/01-what-is-eventcatalog-assistant.md +0 -4
- package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/02-configuration.md +15 -3
- package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/03-bring-your-own-tools.md +0 -3
- package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/_category_.json +1 -1
- package/dist/docs/development/ask-your-architecture/02-llms.txt.md +1 -1
- package/dist/docs/development/ask-your-architecture/{05-skills/01-introduction.md → 03-install-skills.md} +57 -7
- package/dist/docs/development/ask-your-architecture/03-mcp-server/_category_.json +1 -1
- package/dist/docs/development/ask-your-architecture/03-mcp-server/getting-started.md +86 -33
- package/dist/docs/development/ask-your-architecture/03-mcp-server/introduction.md +14 -4
- package/dist/docs/development/ask-your-architecture/03-schemas.txt.md +1 -1
- package/dist/docs/development/ask-your-architecture/04-agents/_category_.json +1 -1
- package/dist/docs/development/authentication/01-introduction.md +2 -23
- package/dist/docs/development/authentication/02-enabling-authentication.md +0 -20
- package/dist/docs/development/authentication/07-rbac-middleware.md +0 -2
- package/dist/docs/development/authentication/providers/03-setting-up-github.md +0 -2
- package/dist/docs/development/authentication/providers/03a-setting-up-google.md +0 -2
- package/dist/docs/development/authentication/providers/04-setting-up-azure-ad.md +0 -2
- package/dist/docs/development/authentication/providers/05-setting-up-okta.md +0 -3
- package/dist/docs/development/authentication/providers/06-setting-up-auth0.md +0 -2
- package/dist/docs/development/bring-your-own-documentation/01-introduction.md +0 -4
- package/dist/docs/development/bring-your-own-documentation/custom-pages/01-introduction.md +0 -3
- package/dist/docs/development/bring-your-own-documentation/custom-pages/02-adding-custom-docs.md +0 -17
- package/dist/docs/development/bring-your-own-documentation/custom-pages/03-components.md +0 -4
- package/dist/docs/development/bring-your-own-documentation/custom-pages/04-owners.md +0 -2
- package/dist/docs/development/bring-your-own-documentation/resource-docs/01-introduction.md +1 -3
- package/dist/docs/development/bring-your-own-documentation/resource-docs/02-adding-resource-docs.md +1 -4
- package/dist/docs/development/bring-your-own-documentation/resource-docs/03-categories.md +0 -2
- package/dist/docs/development/bring-your-own-documentation/resource-docs/04-versioning.md +1 -3
- package/dist/docs/development/components/components/11-flow.md +17 -0
- package/dist/docs/development/components/components/15-nodegraph.md +43 -10
- package/dist/docs/development/components/components/18-remote-schema.md +1 -4
- package/dist/docs/development/components/custom-components/07-reference.md +14 -0
- package/dist/docs/development/customization/02-themes.md +0 -5
- package/dist/docs/development/customization/03-search.md +1 -1
- package/dist/docs/development/customization/05-custom-pages-and-api-routes/01-introduction.md +0 -2
- package/dist/docs/development/customization/05-custom-pages-and-api-routes/04-reference.md +7 -1
- package/dist/docs/development/customization/05-resource-sidebar.md +1 -1
- package/dist/docs/development/customization/09-embed-eventcatalog.md +20 -25
- package/dist/docs/development/deployment/hosting-options.md +7 -2
- package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/05-fix-common-problems.md +16 -0
- package/dist/docs/development/developer-tools/eventcatalog-linter/reference/03-rules.md +31 -1
- package/dist/docs/development/developer-tools/eventcatalog-linter/reference/05-supported-resources.md +1 -1
- package/dist/docs/development/governance/architecture-change-detection/01-introduction.md +0 -10
- package/dist/docs/development/governance/architecture-change-detection/06-pipeline-gates.md +0 -4
- package/dist/docs/development/guides/97-architecture-graph.md +1 -1
- package/dist/docs/development/guides/99-adding-analytics.md +0 -7
- package/dist/docs/development/guides/domains/03-model-subdomains.md +2 -2
- package/dist/docs/development/guides/domains/04-add-resources-to-domains/01-add-systems-to-domains.md +3 -1
- package/dist/docs/development/guides/domains/04-add-resources-to-domains/02-add-services-to-domains.md +5 -1
- package/dist/docs/development/guides/domains/04-add-resources-to-domains/03-add-messages-to-domains.md +2 -2
- package/dist/docs/development/guides/domains/04-add-resources-to-domains/05-add-entities-to-domains.md +1 -1
- package/dist/docs/development/guides/domains/04-add-resources-to-domains/06-domain-resources-page.md +40 -0
- package/dist/docs/development/guides/domains/06-visualize-domains/01-domain-diagram.md +89 -0
- package/dist/docs/development/guides/domains/06-visualize-domains/_category_.json +1 -2
- package/dist/docs/development/guides/domains/09-reference.md +4 -4
- package/dist/docs/development/guides/owners/automated-teams-and-users/create-your-own-connector.md +0 -2
- package/dist/docs/development/guides/owners/automated-teams-and-users/sync-from-github.md +0 -3
- package/dist/docs/development/guides/owners/automated-teams-and-users/sync-from-microsoft-entra.md +0 -2
- package/dist/docs/development/guides/resources/adrs/03-linking-resources.md +8 -0
- package/dist/docs/development/guides/resources/adrs/06-reference.md +4 -4
- package/dist/docs/development/guides/resources/agents/11-reference.md +1 -1
- package/dist/docs/development/guides/resources/data/04-reference.md +1 -1
- package/dist/docs/development/guides/resources/data-products/07-reference.md +1 -1
- package/dist/docs/development/guides/resources/entities/05-entity-maps.md +1 -1
- package/dist/docs/development/guides/resources/entities/06-reference.md +2 -2
- package/dist/docs/development/guides/resources/flows/01-introduction.md +11 -1
- package/dist/docs/development/guides/resources/flows/03-model-flow-steps.md +65 -6
- package/dist/docs/development/guides/resources/flows/05-explore-flows.md +112 -0
- package/dist/docs/development/guides/resources/flows/06-reference.md +25 -4
- package/dist/docs/development/guides/resources/messages/01-what-are-messages.md +19 -0
- package/dist/docs/development/guides/resources/messages/04-message-channels/01-introduction.md +16 -0
- package/dist/docs/development/guides/resources/messages/04-message-channels/05-reference.md +2 -1
- package/dist/docs/development/guides/resources/messages/10-reference.md +6 -3
- package/dist/docs/development/guides/resources/schemas/04-explore-schemas/01-schema-explorer.md +2 -2
- package/dist/docs/development/guides/resources/schemas/04-explore-schemas/02-fields-explorer.md +0 -9
- package/dist/docs/development/guides/resources/schemas/05-schema-api.md +0 -3
- package/dist/docs/development/guides/resources/schemas/06-schema-mcp.md +11 -3
- package/dist/docs/development/guides/resources/services/04-add-resources-to-services/03-add-entities-to-services.md +1 -1
- package/dist/docs/development/guides/resources/services/05-resources-page.md +50 -0
- package/dist/docs/development/guides/resources/services/08-reference.md +2 -1
- package/dist/docs/development/guides/systems/01-introduction.md +1 -1
- package/dist/docs/development/guides/systems/02-create-system.md +2 -2
- package/dist/docs/development/guides/systems/03-add-resources-to-systems.md +35 -9
- package/dist/docs/development/guides/systems/04-model-relationships-and-actors.md +5 -3
- package/dist/docs/development/guides/systems/05-system-context-maps.md +39 -22
- package/dist/docs/development/guides/systems/06-reference.md +4 -4
- package/dist/docs/development/license/01-license-file.md +151 -0
- package/dist/docs/development/license/_category_.json +12 -0
- package/dist/docs/development/license-keys/_category_.json +3 -3
- package/dist/docs/development/license-keys/integrations.md +7 -37
- package/dist/docs/development/license-keys/license-validation.md +4 -0
- package/dist/docs/development/license-keys/overview.md +5 -1
- package/dist/docs/development/license-keys/plans.md +4 -2
- package/dist/docs/development/studio/01-introduction.md +60 -0
- package/dist/docs/development/studio/02-create-a-canvas.md +103 -0
- package/dist/docs/development/studio/03-set-up-persistent-storage.md +59 -0
- package/dist/docs/development/studio/04-collaborate-on-a-canvas.md +78 -0
- package/dist/docs/development/studio/05-connect-your-agent.md +102 -0
- package/dist/docs/development/studio/06-run-studio-in-production.md +54 -0
- package/dist/docs/development/studio/07-reference.md +294 -0
- package/dist/docs/development/studio/_category_.json +12 -0
- package/dist/docs/development/upgrading/v5.md +345 -0
- package/dist/docs/federation/00-overview.md +2 -8
- package/dist/docs/federation/01-first-federation.md +6 -18
- package/dist/docs/federation/explanation/how-it-works.md +0 -4
- package/dist/docs/federation/explanation/lockfile-and-cache.md +0 -4
- package/dist/docs/federation/explanation/mvp-status-and-feedback.md +0 -4
- package/dist/docs/federation/explanation/ownership-and-references.md +0 -4
- package/dist/docs/federation/how-to/configure-github-sources.md +6 -5
- package/dist/docs/federation/how-to/configure-validation-rules.md +0 -4
- package/dist/docs/federation/how-to/run-in-ci.md +2 -9
- package/dist/docs/federation/how-to/share-assets-and-components.md +0 -4
- package/dist/docs/federation/how-to/use-local-sources.md +0 -4
- package/dist/docs/federation/legacy-federation/00-introduction.md +4 -22
- package/dist/docs/federation/legacy-federation/01-installation.md +7 -58
- package/dist/docs/federation/legacy-federation/02-plugin-configuration.md +3 -21
- package/dist/docs/federation/legacy-federation/03-examples.md +3 -5
- package/dist/docs/federation/legacy-federation/04-configuration.md +3 -8
- package/dist/docs/federation/legacy-federation/05-setup-team-catalog.md +3 -6
- package/dist/docs/federation/reference/cli.md +2 -8
- package/dist/docs/federation/reference/configuration.md +0 -4
- package/dist/docs/federation/reference/diagnostic-rules.md +0 -5
- package/dist/docs/federation/reference/generated-output.md +0 -4
- package/dist/docs/federation/reference/supported-content.md +0 -4
- package/dist/docs/federation/reference/troubleshooting.md +0 -14
- package/dist/docs/plugins/02-generators.md +1 -1
- package/dist/docs/plugins/amazon-apigateway/00-intro.md +3 -28
- package/dist/docs/plugins/amazon-apigateway/01-installation.md +4 -56
- package/dist/docs/plugins/amazon-apigateway/02-plugin-configuration.md +1 -6
- package/dist/docs/plugins/apicurio/00-intro.md +2 -31
- package/dist/docs/plugins/apicurio/01-installation.md +2 -26
- package/dist/docs/plugins/apicurio/02-plugin-configuration.md +5 -13
- package/dist/docs/plugins/apicurio/03-features.md +1 -3
- package/dist/docs/plugins/asyncapi/00-intro.md +2 -28
- package/dist/docs/plugins/asyncapi/01-installation.md +3 -54
- package/dist/docs/plugins/asyncapi/02-plugin-configuration.md +2 -1
- package/dist/docs/plugins/aws-glue-registry/00-intro.md +4 -32
- package/dist/docs/plugins/aws-glue-registry/00a-installation.md +3 -53
- package/dist/docs/plugins/azure-schema-registry/00-intro.md +4 -32
- package/dist/docs/plugins/azure-schema-registry/01-installation.md +6 -33
- package/dist/docs/plugins/azure-schema-registry/02-plugin-configuration.md +1 -14
- package/dist/docs/plugins/azure-schema-registry/04-examples.md +0 -2
- package/dist/docs/plugins/backstage/00-overview.md +1 -7
- package/dist/docs/plugins/backstage/01-get-started.md +6 -15
- package/dist/docs/plugins/backstage/06-configuration.md +2 -8
- package/dist/docs/plugins/confluent-schema-registry/00-intro.md +2 -29
- package/dist/docs/plugins/confluent-schema-registry/01-installation.md +2 -26
- package/dist/docs/plugins/eventbridge/00-intro.md +0 -4
- package/dist/docs/plugins/eventbridge/00a-installation.md +1 -87
- package/dist/docs/plugins/eventbridge/03-api.md +2 -20
- package/dist/docs/plugins/github/00-intro.md +0 -31
- package/dist/docs/plugins/github/01-installation.md +2 -34
- package/dist/docs/plugins/graphql/00-intro.md +0 -30
- package/dist/docs/plugins/graphql/01-installation.md +1 -53
- package/dist/docs/plugins/graphql/02-plugin-configuration.md +0 -1
- package/dist/docs/plugins/hookdeck/01-intro.md +1 -1
- package/dist/docs/plugins/openapi/00-intro.md +0 -30
- package/dist/docs/plugins/openapi/01-installation.md +1 -57
- package/dist/docs/plugins/openapi/02-plugin-configuration.md +0 -5
- package/dist/docs/plugins/openapi/03-features.md +0 -4
- package/dist/eventcatalog-config-file-utils.cjs +34 -41
- package/dist/eventcatalog-config-file-utils.d.cts +56 -58
- package/dist/eventcatalog-config-file-utils.d.ts +56 -58
- package/dist/eventcatalog-config-file-utils.js +1 -1
- package/dist/eventcatalog.cjs +340 -249
- package/dist/eventcatalog.config.d.cts +18 -15
- package/dist/eventcatalog.config.d.ts +18 -15
- package/dist/eventcatalog.js +51 -33
- package/dist/features.cjs +36 -49
- package/dist/features.d.cts +1 -3
- package/dist/features.d.ts +1 -3
- package/dist/features.js +4 -6
- package/dist/federation/federate.cjs +37 -44
- package/dist/federation/federate.js +5 -5
- package/dist/federation/source-provider.js +2 -2
- package/dist/generate.cjs +37 -42
- package/dist/generate.js +4 -4
- package/dist/resolve-catalog-dependencies.cjs +34 -41
- package/dist/resolve-catalog-dependencies.js +2 -2
- package/dist/runtime-dependencies/@eventcatalog__visualiser__studio-fonts.css.mjs +3 -0
- package/dist/runtime-dependencies/@eventcatalog__visualiser__studio.css.mjs +3 -0
- package/dist/runtime-dependencies/@eventcatalog__visualiser__studio.mjs +3 -0
- package/dist/runtime-dependencies/htmlparser2.mjs +3 -0
- package/dist/runtime-dependencies/manifest.json +5 -1
- package/dist/runtime-dependencies/react-syntax-highlighter__dist__cjs__create-element.js.mjs +3 -0
- package/dist/utils/child-process-signals.cjs +53 -0
- package/dist/utils/child-process-signals.d.cts +19 -0
- package/dist/utils/child-process-signals.d.ts +19 -0
- package/dist/utils/child-process-signals.js +6 -0
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/dist/utils/license-status.cjs +11 -4
- package/dist/utils/license-status.d.cts +3 -2
- package/dist/utils/license-status.d.ts +3 -2
- package/dist/utils/license-status.js +1 -1
- package/eventcatalog/astro.config.mjs +21 -1
- package/eventcatalog/integrations/config-loader.mjs +56 -58
- package/eventcatalog/integrations/skip-startup-reload.mjs +31 -0
- package/eventcatalog/integrations/stable-dependency-order.mjs +29 -0
- package/eventcatalog/integrations/storage.mjs +20 -0
- package/eventcatalog/integrations/studio-server.mjs +4 -1
- package/eventcatalog/src/components/CatalogGraph/CatalogForceGraph.tsx +39 -0
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +2 -0
- package/eventcatalog/src/components/Homepage/CatalogDashboard.astro +342 -0
- package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +60 -4
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +5 -1
- package/eventcatalog/src/components/NavigationProgress.astro +7 -0
- package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +2 -0
- package/eventcatalog/src/components/SchemaExplorer/ExamplesViewer.tsx +2 -0
- package/eventcatalog/src/components/SchemaExplorer/SchemaCodeModal.tsx +2 -0
- package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +2 -0
- package/eventcatalog/src/components/Search/SearchDataLoader.astro +16 -28
- package/eventcatalog/src/components/Search/SearchModal.tsx +2 -10
- package/eventcatalog/src/components/Search/search-utils.ts +26 -0
- package/eventcatalog/src/components/Seo.astro +5 -1
- package/eventcatalog/src/components/Settings/AssistantSettingsForm.tsx +53 -48
- package/eventcatalog/src/components/Settings/GeneralSettingsForm.tsx +183 -178
- package/eventcatalog/src/components/Settings/LicenseSettings.tsx +140 -31
- package/eventcatalog/src/components/Settings/LlmAccessSettingsForm.tsx +40 -35
- package/eventcatalog/src/components/Settings/LogoUpload.tsx +1 -1
- package/eventcatalog/src/components/Settings/McpSettingsForm.tsx +9 -8
- package/eventcatalog/src/components/Settings/Row.tsx +7 -4
- package/eventcatalog/src/components/Settings/SettingsShared.tsx +3 -3
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +37 -76
- package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +2 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +122 -1
- package/eventcatalog/src/components/SideNav/SideNav.astro +9 -1
- package/eventcatalog/src/components/TrialBadge.astro +20 -9
- package/eventcatalog/src/content.config.ts +19 -2
- package/eventcatalog/src/features/auth/error.astro +4 -2
- package/eventcatalog/src/features/auth/login.astro +2 -1
- package/eventcatalog/src/features/auth/middleware/middleware-auth.ts +21 -1
- package/eventcatalog/src/features/auth/unauthorized.astro +1 -1
- package/eventcatalog/src/features/fields/pages/fields.astro +1 -1
- package/eventcatalog/src/features/integrations/eventcatalog-features.ts +13 -7
- package/eventcatalog/src/features/mcp/apps/architecture-diagram/node-chat.tsx +6 -45
- package/eventcatalog/src/features/mcp/apps/canvas/shared.ts +3 -3
- package/eventcatalog/src/features/mcp/apps/canvas/styles.css +3 -1
- package/eventcatalog/src/features/mcp/apps/canvas/view.tsx +7 -5
- package/eventcatalog/src/features/mcp/apps/generated/canvas.html +123 -98
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +118 -101
- package/eventcatalog/src/features/mcp/mcp-server.ts +20 -4
- package/eventcatalog/src/features/server/start.ts +25 -0
- package/eventcatalog/src/features/storage/database.ts +88 -0
- package/eventcatalog/src/features/storage/migrations.ts +85 -0
- package/eventcatalog/src/features/storage/sql/database.sql +5 -0
- package/eventcatalog/src/features/storage/sql/migrations/0001-studio-canvases.sql +12 -0
- package/eventcatalog/src/features/storage/sql/migrations.sql +15 -0
- package/eventcatalog/src/features/storage/sql.ts +28 -0
- package/eventcatalog/src/features/studio/agent-choreography.ts +13 -10
- package/eventcatalog/src/features/studio/agents.ts +28 -0
- package/eventcatalog/src/features/studio/api/canvases-api.ts +219 -0
- package/eventcatalog/src/features/studio/api/canvases.ts +25 -0
- package/eventcatalog/src/features/studio/api/client.ts +40 -0
- package/eventcatalog/src/features/studio/canvas-actions.ts +160 -34
- package/eventcatalog/src/features/studio/canvas-preview.ts +1 -2
- package/eventcatalog/src/features/studio/catalog.ts +16 -14
- package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +274 -105
- package/eventcatalog/src/features/studio/components/CanvasStatusMenu.tsx +15 -14
- package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +272 -119
- package/eventcatalog/src/features/studio/components/ConnectionsChooser.tsx +1 -2
- package/eventcatalog/src/features/studio/components/DeleteCanvasDialog.tsx +65 -0
- package/eventcatalog/src/features/studio/components/Dialog.tsx +60 -0
- package/eventcatalog/src/features/studio/components/JoinForm.tsx +101 -26
- package/eventcatalog/src/features/studio/components/LeftPanel.tsx +122 -230
- package/eventcatalog/src/features/studio/components/NodeDetails.tsx +183 -0
- package/eventcatalog/src/features/studio/components/OpenInStudioDialog.tsx +79 -0
- package/eventcatalog/src/features/studio/components/Presence.tsx +52 -45
- package/eventcatalog/src/features/studio/components/ShareDialog.tsx +277 -141
- package/eventcatalog/src/features/studio/components/ShortcutsDialog.tsx +41 -0
- package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +1314 -385
- package/eventcatalog/src/features/studio/components/StudioNotice.astro +24 -0
- package/eventcatalog/src/features/studio/document-name.ts +7 -0
- package/eventcatalog/src/features/studio/from-visualiser.ts +138 -0
- package/eventcatalog/src/features/studio/hooks/presence-sender.ts +19 -2
- package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +9 -3
- package/eventcatalog/src/features/studio/hooks/use-comments.ts +2 -2
- package/eventcatalog/src/features/studio/hooks/use-follow-camera.ts +8 -3
- package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +244 -32
- package/eventcatalog/src/features/studio/identity.ts +25 -1
- package/eventcatalog/src/features/studio/limits.ts +2 -0
- package/eventcatalog/src/features/studio/pages/[id].astro +42 -16
- package/eventcatalog/src/features/studio/pages/index.astro +596 -373
- package/eventcatalog/src/features/studio/pages/webmcp-relay.ts +12 -15
- package/eventcatalog/src/features/studio/server/canvas-mcp.ts +8 -23
- package/eventcatalog/src/features/studio/server/runtime.ts +150 -46
- package/eventcatalog/src/features/studio/server/sql/storage.sql +18 -0
- package/eventcatalog/src/features/studio/server/storage.ts +53 -73
- package/eventcatalog/src/features/studio/webmcp-relay/LICENSE +21 -0
- package/eventcatalog/src/features/studio/webmcp-relay/README.md +5 -0
- package/eventcatalog/src/features/studio/webmcp-relay/embed.js +2 -0
- package/eventcatalog/src/features/studio/webmcp-relay/widget.html +10 -0
- package/eventcatalog/src/features/studio/webmcp-relay/widget.js +1 -0
- package/eventcatalog/src/features/tools/catalog-tools.ts +3 -1
- package/eventcatalog/src/layouts/BaseLayout.astro +4 -4
- package/eventcatalog/src/layouts/SettingsLayout.astro +2 -2
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +3 -2
- package/eventcatalog/src/pages/_index.astro +5 -342
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.spec.ts +2 -3
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +3 -1
- package/eventcatalog/src/pages/diagrams/[id]/[version]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/diagrams/[id]/[version]/embed.astro +1 -0
- package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +1 -0
- package/eventcatalog/src/pages/directory/[type]/_index.data.ts +5 -7
- package/eventcatalog/src/pages/directory/[type]/index.astro +3 -1
- package/eventcatalog/src/pages/discover/[type]/_index.data.ts +5 -7
- package/eventcatalog/src/pages/discover/[type]/index.astro +3 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +3 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/_[filename].data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/examples/[...filename].astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/examples/_[...filename].data.ts +0 -4
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/field-lineage/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/field-lineage/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +3 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/_[filename].data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.spec.ts +2 -3
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/[filename].astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/_[filename].data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/index.astro +2 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/language/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +1 -0
- package/eventcatalog/src/pages/docs/custom/[...path]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/custom/[...path]/index.astro +1 -0
- package/eventcatalog/src/pages/docs/services/[id]/[docType]/[docId]/index.astro +1 -1
- package/eventcatalog/src/pages/docs/services/[id]/[version]/resources/index.astro +1 -0
- package/eventcatalog/src/pages/docs/services/[id]/asyncapi/[filename].astro +1 -1
- package/eventcatalog/src/pages/docs/services/[id]/changelog/index.astro +1 -1
- package/eventcatalog/src/pages/docs/services/[id]/graphql/[filename].astro +1 -1
- package/eventcatalog/src/pages/docs/services/[id]/spec/[filename].astro +1 -1
- package/eventcatalog/src/pages/docs/teams/[id]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/teams/[id]/index.astro +1 -0
- package/eventcatalog/src/pages/docs/users/[id]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/users/[id]/index.astro +1 -0
- package/eventcatalog/src/pages/index.astro +4 -2
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +9 -1
- package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +0 -7
- package/eventcatalog/src/pages/schemas/explorer/index.astro +4 -2
- package/eventcatalog/src/pages/settings/license.astro +9 -1
- package/eventcatalog/src/pages/triggers/[type]/[id]/[version]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/triggers/[type]/[id]/[version]/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/[type]/[id]/_index.data.ts +38 -0
- package/eventcatalog/src/pages/visualiser/[type]/[id]/index.astro +18 -41
- package/eventcatalog/src/pages/visualiser/designs/[id]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/designs/[id]/index.astro +2 -1
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/index.astro +1 -0
- package/eventcatalog/src/types/react-syntax-highlighter.d.ts +10 -0
- package/eventcatalog/src/utils/collections/flows.ts +20 -7
- package/eventcatalog/src/utils/collections/resource-owners.ts +8 -2
- package/eventcatalog/src/utils/collections/resources.ts +2 -0
- package/eventcatalog/src/utils/collections/util.ts +8 -4
- package/eventcatalog/src/utils/git.ts +11 -0
- package/eventcatalog/src/utils/homepage-dashboard.ts +143 -0
- package/eventcatalog/src/utils/license.ts +1 -1
- package/eventcatalog/src/utils/link-validation.ts +25 -19
- package/eventcatalog/src/utils/node-graphs/domain-entity-map.ts +3 -1
- package/eventcatalog/src/utils/node-graphs/domain-levels-node-graph.ts +3 -3
- package/eventcatalog/src/utils/node-graphs/field-node-graph.ts +4 -2
- package/eventcatalog/src/utils/node-graphs/layout-node-graph.ts +20 -1
- package/eventcatalog/src/utils/node-graphs/node-resource.ts +67 -0
- package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +1 -1
- package/eventcatalog/src/utils/page-loaders/hybrid-page.ts +4 -15
- package/eventcatalog/src/utils/page-title.ts +15 -0
- package/eventcatalog/src/utils/syntax-highlighter-renderer.ts +64 -0
- package/eventcatalog/src/utils/trial.ts +13 -0
- package/package.json +11 -11
- package/dist/docs/development/ask-your-architecture/05-skills/02-installation.md +0 -60
- package/dist/docs/development/ask-your-architecture/05-skills/_category_.json +0 -11
- package/dist/docs/development/ask-your-architecture/06-slack-integration/01-introduction.md +0 -63
- package/dist/docs/development/ask-your-architecture/06-slack-integration/02-slack-app-setup.md +0 -154
- package/dist/docs/development/ask-your-architecture/06-slack-integration/03-installation.md +0 -169
- package/dist/docs/development/ask-your-architecture/06-slack-integration/04-deployment.md +0 -236
- package/dist/docs/development/ask-your-architecture/06-slack-integration/05-usage.md +0 -140
- package/dist/docs/development/ask-your-architecture/06-slack-integration/06-troubleshooting.md +0 -268
- package/dist/docs/development/ask-your-architecture/06-slack-integration/_category_.json +0 -13
- package/dist/docs/development/guides/domains/06-visualize-domains/01-domain-integration-map.md +0 -40
- package/dist/runtime-dependencies/@mcp-b__webmcp-local-relay.mjs +0 -3
- package/eventcatalog/src/features/studio/canvas-doc.ts +0 -409
- package/eventcatalog/src/features/studio/clipboard.ts +0 -97
- package/eventcatalog/src/features/studio/components/CanvasControls.tsx +0 -154
- package/eventcatalog/src/features/studio/components/Comments.tsx +0 -770
- package/eventcatalog/src/features/studio/components/ContainerChooser.tsx +0 -145
- package/eventcatalog/src/features/studio/components/DragGhost.tsx +0 -115
- package/eventcatalog/src/features/studio/components/Picture.tsx +0 -21
- package/eventcatalog/src/features/studio/components/PropertiesPanel.tsx +0 -212
- package/eventcatalog/src/features/studio/components/canvas-nodes.tsx +0 -255
- package/eventcatalog/src/features/studio/components/icons.ts +0 -53
- package/eventcatalog/src/features/studio/components/status.ts +0 -51
- package/eventcatalog/src/features/studio/edges.ts +0 -56
- package/eventcatalog/src/features/studio/flow-state.ts +0 -92
- package/eventcatalog/src/features/studio/grouping.ts +0 -226
- package/eventcatalog/src/features/studio/layout.ts +0 -42
- package/eventcatalog/src/features/studio/levels.ts +0 -282
- package/eventcatalog/src/features/studio/node-types.ts +0 -205
- package/eventcatalog/src/features/studio/pages/new.astro +0 -24
- package/eventcatalog/src/features/studio/pages/wake.ts +0 -15
- package/eventcatalog/src/features/studio/placement.ts +0 -197
- package/eventcatalog/src/features/studio/studio.css +0 -273
- package/dist/{chunk-A2RZR3U4.js → chunk-6K7XZAYI.js} +3 -3
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
import type { APIRoute } from 'astro';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
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`.
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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 '
|
|
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
|
|
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
|
-
|
|
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 (
|
|
87
|
-
|
|
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 (`
|
|
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:
|
|
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((
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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]
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
|
2
|
-
import
|
|
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 (`
|
|
6
|
-
* the collaboration server holds in memory, so storage is only read when the server starts, and
|
|
7
|
-
* after a canvas changes: it doesn't slow down editing. Canvases are stored as their whole Yjs
|
|
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
|
|
32
|
-
loadAll: () => Promise<
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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:
|
|
53
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
statements.recordDeleted.run(documentName, Date.now());
|
|
76
|
+
statements.removeCanvas.run(documentName);
|
|
77
|
+
database.exec('COMMIT');
|
|
67
78
|
} catch (error) {
|
|
68
|
-
|
|
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
|
-
/**
|
|
99
|
-
export const createStorage = (config:
|
|
100
|
-
const
|
|
101
|
-
|
|
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.
|