@k2b/cloud 0.6.0-rc.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/README.md +98 -0
- package/package.json +128 -0
- package/public/logo.svg +1 -0
- package/scripts/app-favicon.test.ts +49 -0
- package/scripts/app-favicon.ts +51 -0
- package/scripts/build.ts +205 -0
- package/scripts/preload.ts +164 -0
- package/scripts/sync-recovery-smoke.ts +200 -0
- package/src/_internal/app-favicon.ts +2 -0
- package/src/_internal/app-presentation.ts +61 -0
- package/src/_internal/app-workspace-state.ts +24 -0
- package/src/_internal/bounded-json.ts +47 -0
- package/src/_internal/build-metadata.ts +9 -0
- package/src/_internal/capabilities.ts +1118 -0
- package/src/_internal/define-app.ts +801 -0
- package/src/_internal/heartbeat.ts +167 -0
- package/src/_internal/help-catalog.ts +241 -0
- package/src/_internal/help.ts +95 -0
- package/src/_internal/nats-connection.ts +34 -0
- package/src/_internal/page-responses.ts +40 -0
- package/src/_internal/process-sync.ts +117 -0
- package/src/_internal/readiness.ts +3 -0
- package/src/_internal/registry-validation.ts +136 -0
- package/src/_internal/registry-watch.ts +52 -0
- package/src/_internal/registry.ts +338 -0
- package/src/_internal/runtime-compatibility.ts +64 -0
- package/src/_internal/runtime-context.ts +84 -0
- package/src/_internal/runtime-watcher.ts +78 -0
- package/src/_internal/static-assets.ts +86 -0
- package/src/_internal/status-preserving-ssr.ts +49 -0
- package/src/access/PermissionEditor.tsx +486 -0
- package/src/access/ResourceApiKeys.tsx +271 -0
- package/src/access/messages.ts +121 -0
- package/src/access/ui.ts +16 -0
- package/src/account/Avatar.tsx +28 -0
- package/src/account/EntitySearch.tsx +363 -0
- package/src/account/avatar-upload-dialog.tsx +197 -0
- package/src/account/avatar-upload.ts +149 -0
- package/src/account/ui.ts +3 -0
- package/src/ai/admin.ts +47 -0
- package/src/ai/approval-routes.ts +130 -0
- package/src/ai/approvals.ts +139 -0
- package/src/ai/assistant-models.ts +61 -0
- package/src/ai/attachments.ts +40 -0
- package/src/ai/browser.ts +92 -0
- package/src/ai/capabilities.ts +714 -0
- package/src/ai/capability-execution.ts +151 -0
- package/src/ai/chat/blocks.render.test.tsx +1108 -0
- package/src/ai/chat/blocks.tsx +730 -0
- package/src/ai/chat/builtin-tools.tsx +428 -0
- package/src/ai/chat/capability-table.tsx +87 -0
- package/src/ai/chat/composer-adapter.ts +169 -0
- package/src/ai/chat/file-tools.tsx +81 -0
- package/src/ai/chat/message-actions.tsx +370 -0
- package/src/ai/chat/message-utils.ts +479 -0
- package/src/ai/chat/messages.ts +43 -0
- package/src/ai/chat/presentation.tsx +395 -0
- package/src/ai/chat/primitives.tsx +3 -0
- package/src/ai/chat/tool-disclosure.tsx +41 -0
- package/src/ai/chat/turn-navigator-utils.ts +28 -0
- package/src/ai/chat/turn-navigator.tsx +164 -0
- package/src/ai/chat/user-message.tsx +149 -0
- package/src/ai/chat/visual-tools.tsx +435 -0
- package/src/ai/chat/web-tools.tsx +202 -0
- package/src/ai/chat-task-contracts.ts +113 -0
- package/src/ai/chat-tasks.ts +952 -0
- package/src/ai/client/controller.ts +1211 -0
- package/src/ai/client/file-source.ts +95 -0
- package/src/ai/client/live-connection.ts +140 -0
- package/src/ai/client/projection.ts +178 -0
- package/src/ai/client/transport.ts +111 -0
- package/src/ai/compaction.ts +182 -0
- package/src/ai/credentials.ts +124 -0
- package/src/ai/default-tool-contracts.ts +108 -0
- package/src/ai/default-tools.ts +127 -0
- package/src/ai/enrich.ts +280 -0
- package/src/ai/executor.ts +1624 -0
- package/src/ai/fetch-file-tool.ts +313 -0
- package/src/ai/file-context.ts +92 -0
- package/src/ai/file-media-type.ts +28 -0
- package/src/ai/file-mount.ts +16 -0
- package/src/ai/file-tools.ts +566 -0
- package/src/ai/files-store.ts +390 -0
- package/src/ai/firecrawl-tools.ts +305 -0
- package/src/ai/grids-skill.ts +126 -0
- package/src/ai/http.ts +265 -0
- package/src/ai/index.ts +337 -0
- package/src/ai/kit-skill.ts +22 -0
- package/src/ai/limits.ts +5 -0
- package/src/ai/live-events.ts +289 -0
- package/src/ai/live-messages.ts +45 -0
- package/src/ai/live-outbox.ts +102 -0
- package/src/ai/live-routes.ts +412 -0
- package/src/ai/live.ts +2 -0
- package/src/ai/maintenance.ts +302 -0
- package/src/ai/markdown-pdf-tool.ts +95 -0
- package/src/ai/memories.ts +605 -0
- package/src/ai/memory-learning-runs.ts +222 -0
- package/src/ai/memory-learning.ts +619 -0
- package/src/ai/memory-tool.ts +120 -0
- package/src/ai/memory-workflow-evidence.ts +158 -0
- package/src/ai/migrate.ts +2082 -0
- package/src/ai/model-access-migrate.ts +30 -0
- package/src/ai/model-access.ts +191 -0
- package/src/ai/personal-agent.ts +6 -0
- package/src/ai/prefs.ts +72 -0
- package/src/ai/project-tool.ts +94 -0
- package/src/ai/projects-routes.ts +277 -0
- package/src/ai/projects.ts +902 -0
- package/src/ai/protocol.ts +279 -0
- package/src/ai/provider.ts +73 -0
- package/src/ai/public-projection.ts +25 -0
- package/src/ai/resource-markers.ts +31 -0
- package/src/ai/resource-refs.ts +70 -0
- package/src/ai/routes.ts +1131 -0
- package/src/ai/runtime-tools.ts +39 -0
- package/src/ai/runtime.ts +544 -0
- package/src/ai/settings.ts +529 -0
- package/src/ai/short-id.ts +45 -0
- package/src/ai/skill-catalog.ts +73 -0
- package/src/ai/skill-format.ts +145 -0
- package/src/ai/skill-seeds.ts +430 -0
- package/src/ai/skill-tool.ts +81 -0
- package/src/ai/skills-routes.ts +143 -0
- package/src/ai/skills.ts +934 -0
- package/src/ai/solid.ts +27 -0
- package/src/ai/store.ts +3113 -0
- package/src/ai/stream.ts +308 -0
- package/src/ai/structured-runs.ts +66 -0
- package/src/ai/structured.ts +186 -0
- package/src/ai/system-prompt.ts +170 -0
- package/src/ai/task-prompt.ts +22 -0
- package/src/ai/timeline.ts +148 -0
- package/src/ai/tool-audit.ts +177 -0
- package/src/ai/tool-result-budget.ts +16 -0
- package/src/ai/tools.ts +155 -0
- package/src/ai/types.ts +967 -0
- package/src/ai/ui.tsx +39 -0
- package/src/ai/usage-migrate.ts +81 -0
- package/src/ai/usage.ts +315 -0
- package/src/ai/validate.ts +54 -0
- package/src/ai/vision-tool.ts +79 -0
- package/src/api/accounts-entities.ts +174 -0
- package/src/api/admin-ai-projects.ts +74 -0
- package/src/api/admin-ai-skills.ts +76 -0
- package/src/api/admin-ai-usage.ts +42 -0
- package/src/api/admin-core-settings.ts +508 -0
- package/src/api/admin-identity.ts +148 -0
- package/src/api/admin-lifecycle.ts +206 -0
- package/src/api/admin-linux-identities.ts +66 -0
- package/src/api/announcements.ts +131 -0
- package/src/api/app-approval.ts +129 -0
- package/src/api/apps.ts +51 -0
- package/src/api/auth/schemas.ts +54 -0
- package/src/api/auth.ts +370 -0
- package/src/api/capabilities.ts +658 -0
- package/src/api/help-messages.ts +19 -0
- package/src/api/help.ts +78 -0
- package/src/api/index.ts +108 -0
- package/src/api/mcp.ts +744 -0
- package/src/api/me-rail.ts +45 -0
- package/src/api/me.ts +753 -0
- package/src/api/search/schemas.ts +79 -0
- package/src/api/search.bench.ts +185 -0
- package/src/api/search.ts +407 -0
- package/src/api/sync-ops.ts +79 -0
- package/src/api/widgets.ts +143 -0
- package/src/browser/CloudResourceSearch.behavior.test.tsx +74 -0
- package/src/browser/CloudResourceSearch.tsx +791 -0
- package/src/browser/app-approval-vault.ts +305 -0
- package/src/browser/app-approval.ts +234 -0
- package/src/browser/live-websocket.ts +268 -0
- package/src/browser/locale-preference.ts +25 -0
- package/src/browser/notification-target.ts +27 -0
- package/src/browser/notification-timeout.ts +14 -0
- package/src/browser/notifications.ts +181 -0
- package/src/browser/resource-clipboard.ts +70 -0
- package/src/browser/resource-picker.tsx +39 -0
- package/src/browser/resource-search-messages.ts +55 -0
- package/src/browser/resource-search.ts +27 -0
- package/src/capabilities/client.ts +149 -0
- package/src/capabilities/response.ts +39 -0
- package/src/capabilities/server.ts +215 -0
- package/src/capabilities/signals.ts +4 -0
- package/src/capabilities/testing.ts +12 -0
- package/src/capabilities/types.ts +32 -0
- package/src/cli/access.ts +499 -0
- package/src/cli/account.ts +382 -0
- package/src/cli/admin/account-administration.ts +45 -0
- package/src/cli/admin/account-categories.ts +49 -0
- package/src/cli/admin/ai-usage.ts +118 -0
- package/src/cli/admin/app-sign-in.ts +49 -0
- package/src/cli/admin/data.ts +343 -0
- package/src/cli/admin/documentation.ts +34 -0
- package/src/cli/admin/gateway.ts +155 -0
- package/src/cli/admin/index.ts +61 -0
- package/src/cli/admin/instance.ts +250 -0
- package/src/cli/admin/jobs.ts +223 -0
- package/src/cli/admin/legal.ts +122 -0
- package/src/cli/admin/linux.ts +53 -0
- package/src/cli/admin/logs.ts +324 -0
- package/src/cli/admin/metrics.ts +142 -0
- package/src/cli/admin/nats.ts +167 -0
- package/src/cli/admin/notifications.ts +516 -0
- package/src/cli/admin/shared.ts +113 -0
- package/src/cli/admin/sync.ts +188 -0
- package/src/cli/admin/telemetry.ts +314 -0
- package/src/cli/admin/webhooks.ts +222 -0
- package/src/cli/admin/workflows.ts +367 -0
- package/src/cli/apps.ts +40 -0
- package/src/cli/capabilities.ts +239 -0
- package/src/cli/commands.ts +604 -0
- package/src/cli/index.ts +61 -0
- package/src/cli/locale.ts +31 -0
- package/src/cli/output.ts +58 -0
- package/src/clients/core.ts +20 -0
- package/src/config/env.ts +54 -0
- package/src/config/index.ts +6 -0
- package/src/contracts/account-categories.ts +62 -0
- package/src/contracts/announcements.ts +121 -0
- package/src/contracts/app-approval.ts +197 -0
- package/src/contracts/app.ts +162 -0
- package/src/contracts/capabilities.ts +484 -0
- package/src/contracts/index.ts +14 -0
- package/src/contracts/notification-live.ts +66 -0
- package/src/contracts/notification-types.ts +235 -0
- package/src/contracts/posix.ts +69 -0
- package/src/contracts/profile.ts +85 -0
- package/src/contracts/rail-preferences.ts +53 -0
- package/src/contracts/registry.ts +110 -0
- package/src/contracts/resource-clipboard.ts +33 -0
- package/src/contracts/settings-types.ts +98 -0
- package/src/contracts/shared.ts +408 -0
- package/src/contracts/user-notifications.ts +101 -0
- package/src/contracts/widgets.ts +183 -0
- package/src/desktop/index.ts +704 -0
- package/src/desktop/solid.tsx +938 -0
- package/src/index.ts +58 -0
- package/src/server/actor.ts +41 -0
- package/src/server/api/index.ts +2 -0
- package/src/server/api/respond.ts +96 -0
- package/src/server/api-client.ts +54 -0
- package/src/server/app-context.ts +39 -0
- package/src/server/help.ts +187 -0
- package/src/server/index.ts +89 -0
- package/src/server/locale.ts +55 -0
- package/src/server/middleware/auth.ts +386 -0
- package/src/server/middleware/index.ts +28 -0
- package/src/server/middleware/invocation.ts +55 -0
- package/src/server/middleware/middleware.ts +65 -0
- package/src/server/middleware/openapi.ts +126 -0
- package/src/server/middleware/rate-limit.ts +125 -0
- package/src/server/middleware/request-logger.ts +41 -0
- package/src/server/middleware/route-template.ts +46 -0
- package/src/server/middleware/runtime.ts +22 -0
- package/src/server/middleware/settings.ts +55 -0
- package/src/server/middleware/validator.ts +49 -0
- package/src/server/middleware/workload.ts +22 -0
- package/src/server/ratelimit.ts +151 -0
- package/src/server/request-cookies.ts +18 -0
- package/src/server/services/access.ts +644 -0
- package/src/server/services/freeipa/client.ts +137 -0
- package/src/server/services/freeipa/index.ts +9 -0
- package/src/server/services/freeipa/session.ts +123 -0
- package/src/server/services/freeipa/test-certificates.ts +44 -0
- package/src/server/services/freeipa/tls.ts +44 -0
- package/src/server/services/freeipa/transport.ts +116 -0
- package/src/server/services/freeipa/util.ts +59 -0
- package/src/server/services/geo.ts +198 -0
- package/src/server/services/index.ts +48 -0
- package/src/server/services/pagination.ts +22 -0
- package/src/server/services/services.ts +13 -0
- package/src/server/time.ts +33 -0
- package/src/services/account-category-policy.ts +51 -0
- package/src/services/account-lifecycle/audit.ts +41 -0
- package/src/services/account-lifecycle/index.ts +940 -0
- package/src/services/account-lifecycle/ipa-backfill.ts +152 -0
- package/src/services/account-lifecycle/notification-sender.ts +13 -0
- package/src/services/account-lifecycle/scheduler.ts +506 -0
- package/src/services/account-model.ts +22 -0
- package/src/services/accounts/app.ts +1671 -0
- package/src/services/accounts/authz.ts +44 -0
- package/src/services/accounts/avatar.ts +120 -0
- package/src/services/accounts/base-group.ts +11 -0
- package/src/services/accounts/base-user.ts +46 -0
- package/src/services/accounts/duplicate-emails.ts +82 -0
- package/src/services/accounts/entities.ts +622 -0
- package/src/services/accounts/group-sql.ts +106 -0
- package/src/services/accounts/groups.ts +272 -0
- package/src/services/accounts/index.ts +14 -0
- package/src/services/accounts/ipa-data.ts +66 -0
- package/src/services/accounts/lifecycle.ts +2 -0
- package/src/services/accounts/local-groups.ts +499 -0
- package/src/services/accounts/model.ts +129 -0
- package/src/services/accounts/notification-sender.ts +31 -0
- package/src/services/accounts/posix.ts +386 -0
- package/src/services/accounts/request-policy.ts +11 -0
- package/src/services/accounts/switching.ts +126 -0
- package/src/services/accounts/users.ts +745 -0
- package/src/services/announcements/index.ts +224 -0
- package/src/services/app-approval.ts +498 -0
- package/src/services/audit/index.ts +438 -0
- package/src/services/auth-flows/index.ts +15 -0
- package/src/services/auth-flows/ipa.ts +197 -0
- package/src/services/auth-flows/magic-link.ts +185 -0
- package/src/services/auth-flows/notification-sender.ts +10 -0
- package/src/services/auth-flows/password-reset.ts +268 -0
- package/src/services/auth-flows/proxy-return.ts +49 -0
- package/src/services/document-extraction.ts +148 -0
- package/src/services/freeipa-config.ts +179 -0
- package/src/services/gateway.ts +186 -0
- package/src/services/identity/constants.ts +18 -0
- package/src/services/identity/index.ts +52 -0
- package/src/services/identity/invocation-actor.ts +120 -0
- package/src/services/identity/invocation-authority.ts +53 -0
- package/src/services/identity/invocation-operations.ts +15 -0
- package/src/services/identity/invocation-token.ts +276 -0
- package/src/services/identity/key-config.ts +41 -0
- package/src/services/identity/key-ring.ts +530 -0
- package/src/services/identity/metrics.ts +29 -0
- package/src/services/identity/routes.ts +18 -0
- package/src/services/identity/runtime-config.ts +88 -0
- package/src/services/identity/session-token.ts +152 -0
- package/src/services/identity/signer-refresh.ts +44 -0
- package/src/services/identity/workload-auth.ts +64 -0
- package/src/services/index.ts +187 -0
- package/src/services/ipa/auth.ts +216 -0
- package/src/services/ipa/connection.ts +57 -0
- package/src/services/ipa/effective-groups.ts +70 -0
- package/src/services/ipa/groups.ts +722 -0
- package/src/services/ipa/guard.ts +17 -0
- package/src/services/ipa/index.ts +17 -0
- package/src/services/ipa/posix.ts +19 -0
- package/src/services/ipa/profile.ts +132 -0
- package/src/services/ipa/runtime.ts +28 -0
- package/src/services/ipa/search.ts +154 -0
- package/src/services/ipa/service-account.ts +15 -0
- package/src/services/ipa/sync-planning.ts +100 -0
- package/src/services/ipa/sync.ts +862 -0
- package/src/services/ipa/users.ts +806 -0
- package/src/services/legal-consent.ts +76 -0
- package/src/services/logging/index.ts +477 -0
- package/src/services/logging/redaction.ts +20 -0
- package/src/services/logging/trace.ts +1231 -0
- package/src/services/mandates/index.ts +855 -0
- package/src/services/mandates/policy.ts +85 -0
- package/src/services/network-security.ts +31 -0
- package/src/services/notifications/batches.ts +722 -0
- package/src/services/notifications/browser.ts +252 -0
- package/src/services/notifications/catalog.ts +113 -0
- package/src/services/notifications/channels.ts +104 -0
- package/src/services/notifications/dispatcher.ts +139 -0
- package/src/services/notifications/email.ts +118 -0
- package/src/services/notifications/index.ts +633 -0
- package/src/services/notifications/live.ts +50 -0
- package/src/services/notifications/observability.ts +407 -0
- package/src/services/notifications/platform.ts +421 -0
- package/src/services/notifications/runtime.ts +120 -0
- package/src/services/notifications/user.ts +248 -0
- package/src/services/notifications/web-push-transport.ts +135 -0
- package/src/services/oauth-tokens.ts +209 -0
- package/src/services/outbox.ts +187 -0
- package/src/services/pdf/gotenberg.ts +290 -0
- package/src/services/pdf/index.ts +43 -0
- package/src/services/pdf/markdown.ts +209 -0
- package/src/services/pdf/template-preview.ts +122 -0
- package/src/services/postgres.ts +76 -0
- package/src/services/providers/index.ts +27 -0
- package/src/services/providers/local/auth.ts +51 -0
- package/src/services/providers/local/index.ts +4 -0
- package/src/services/providers/local/users.ts +263 -0
- package/src/services/rail-preferences.ts +27 -0
- package/src/services/runtime-lifecycle.ts +191 -0
- package/src/services/secrets.ts +10 -0
- package/src/services/service-account-credentials.ts +891 -0
- package/src/services/service-accounts.ts +184 -0
- package/src/services/session/index.ts +236 -0
- package/src/services/session/test-fixture.ts +18 -0
- package/src/services/session/user.ts +144 -0
- package/src/services/settings/api.ts +61 -0
- package/src/services/settings/app.ts +86 -0
- package/src/services/settings/core-settings.ts +796 -0
- package/src/services/settings/crypto.ts +69 -0
- package/src/services/settings/defaults.ts +374 -0
- package/src/services/settings/index.ts +215 -0
- package/src/services/settings/namespace.ts +9 -0
- package/src/services/settings/snapshot.ts +49 -0
- package/src/services/settings/store.ts +241 -0
- package/src/services/settings/templates.ts +4 -0
- package/src/services/sync-ops.ts +403 -0
- package/src/services/topic-cursor.ts +22 -0
- package/src/services/weather/forecast.ts +323 -0
- package/src/services/weather/geo.ts +119 -0
- package/src/services/weather/index.ts +67 -0
- package/src/services/weather/location.ts +24 -0
- package/src/services/weather/locations.ts +133 -0
- package/src/services/weather/migrate.ts +45 -0
- package/src/services/weather/settings.ts +66 -0
- package/src/services/weather/short-id.ts +57 -0
- package/src/services/weather/types.ts +61 -0
- package/src/services/weather/ui.ts +50 -0
- package/src/services/webauthn.ts +385 -0
- package/src/shared/account-action-notice.ts +70 -0
- package/src/shared/account-display.ts +15 -0
- package/src/shared/account-session.ts +15 -0
- package/src/shared/ai-platform-prompt.ts +134 -0
- package/src/shared/ai-usage.ts +49 -0
- package/src/shared/app-presentation.ts +48 -0
- package/src/shared/app-url.ts +12 -0
- package/src/shared/branding.ts +14 -0
- package/src/shared/capability-messages.ts +70 -0
- package/src/shared/email-html.ts +54 -0
- package/src/shared/format.ts +91 -0
- package/src/shared/help.ts +68 -0
- package/src/shared/icons.ts +400 -0
- package/src/shared/index.ts +54 -0
- package/src/shared/locale.ts +48 -0
- package/src/shared/login-method.ts +44 -0
- package/src/shared/markdown/client.ts +130 -0
- package/src/shared/markdown/extensions/code.ts +105 -0
- package/src/shared/markdown/extensions/guided-help.ts +100 -0
- package/src/shared/markdown/extensions/images.ts +79 -0
- package/src/shared/markdown/extensions/info-blocks.ts +97 -0
- package/src/shared/markdown/extensions/katex.ts +120 -0
- package/src/shared/markdown/extensions/links.ts +43 -0
- package/src/shared/markdown/extensions/mark.ts +48 -0
- package/src/shared/markdown/extensions/sub-sup.ts +60 -0
- package/src/shared/markdown/extensions/tables.ts +109 -0
- package/src/shared/markdown/extensions/task-list.ts +53 -0
- package/src/shared/markdown/formula.ts +1187 -0
- package/src/shared/markdown/index.ts +193 -0
- package/src/shared/markdown/shared.ts +36 -0
- package/src/shared/mock-cover.ts +130 -0
- package/src/shared/network-address.ts +106 -0
- package/src/shared/redirect.ts +56 -0
- package/src/shared/template-rendering.ts +140 -0
- package/src/shared/theme.ts +68 -0
- package/src/shared/time.ts +13 -0
- package/src/ssr/AdminLayout.tsx +53 -0
- package/src/ssr/AdminSidebar.tsx +50 -0
- package/src/ssr/AppLaunchpad.island.tsx +236 -0
- package/src/ssr/AppWorkspaceController.island.tsx +21 -0
- package/src/ssr/BrowserNotifications.island.tsx +135 -0
- package/src/ssr/GlobalAnnouncements.island.tsx +142 -0
- package/src/ssr/GlobalSearchDialog.behavior.test.tsx +96 -0
- package/src/ssr/GlobalSearchDialog.tsx +35 -0
- package/src/ssr/GlobalSearchHelpDialog.tsx +104 -0
- package/src/ssr/GlobalSearchTrigger.island.tsx +55 -0
- package/src/ssr/HotkeysHelpRail.island.tsx +55 -0
- package/src/ssr/Layout.render.test.tsx +148 -0
- package/src/ssr/Layout.tsx +292 -0
- package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
- package/src/ssr/LayoutFooter.tsx +22 -0
- package/src/ssr/LayoutHeader.tsx +81 -0
- package/src/ssr/LayoutHelp.tsx +869 -0
- package/src/ssr/LayoutPreferences.island.tsx +36 -0
- package/src/ssr/LayoutRail.tsx +58 -0
- package/src/ssr/MinimalLayout.tsx +46 -0
- package/src/ssr/PageError.tsx +29 -0
- package/src/ssr/ProfilePreferences.island.tsx +92 -0
- package/src/ssr/RailApps.island.tsx +61 -0
- package/src/ssr/RailEditor.tsx +293 -0
- package/src/ssr/RegisteredHelpDocuments.island.tsx +3 -0
- package/src/ssr/TimezoneCookie.island.tsx +23 -0
- package/src/ssr/admin-active-link.ts +47 -0
- package/src/ssr/admin-navigation.ts +78 -0
- package/src/ssr/app-appearance.ts +23 -0
- package/src/ssr/app-navigation.ts +41 -0
- package/src/ssr/help-messages.ts +104 -0
- package/src/ssr/index.ts +14 -0
- package/src/ssr/islands/SearchBar.island.tsx +80 -0
- package/src/ssr/islands/index.ts +16 -0
- package/src/ssr/layout-context.ts +17 -0
- package/src/ssr/layout-help-markdown.ts +9 -0
- package/src/ssr/layout-help-navigation.ts +24 -0
- package/src/ssr/layout-help-url.ts +5 -0
- package/src/ssr/layout-runtime.ts +22 -0
- package/src/ssr/page-error-messages.ts +25 -0
- package/src/ssr/platform-messages.ts +135 -0
- package/src/ssr/preference-controller.ts +44 -0
- package/src/ssr/profile-preferences-messages.ts +29 -0
- package/src/ssr/rail-context.ts +21 -0
- package/src/ssr/rail-messages.ts +66 -0
- package/src/ssr/rail-navigation.ts +60 -0
- package/src/ssr/runtime.ts +31 -0
- package/src/ssr/url-filter.ts +127 -0
- package/src/styles/base-popover.css +36 -0
- package/src/styles/effects.css +851 -0
- package/src/styles/global.css +268 -0
- package/src/styles/input.css +58 -0
- package/src/styles/tokens.css +230 -0
- package/src/styles/utilities-buttons.css +405 -0
- package/src/styles/utilities-data.css +33 -0
- package/src/styles/utilities-feedback.css +232 -0
- package/src/styles/utilities-layout.css +603 -0
- package/src/styles/utilities-markdown-editor.css +525 -0
- package/src/styles/utilities-markdown-table.css +234 -0
- package/src/styles/utilities-navigation.css +1054 -0
- package/src/types/ambient.d.ts +24 -0
- package/src/workflows/ai/index.ts +17 -0
- package/src/workflows/ai/runtime.ts +286 -0
- package/src/workflows/ai/store.ts +314 -0
- package/src/workflows/ai/types.ts +111 -0
- package/src/workflows/ai-actions.ts +269 -0
- package/src/workflows/authoring.ts +88 -0
- package/src/workflows/builtins.ts +193 -0
- package/src/workflows/contracts.ts +280 -0
- package/src/workflows/definition.ts +334 -0
- package/src/workflows/editor.ts +113 -0
- package/src/workflows/index.ts +71 -0
- package/src/workflows/language/binder.ts +45 -0
- package/src/workflows/language/canonical.ts +29 -0
- package/src/workflows/language/compiler.ts +396 -0
- package/src/workflows/language/expressions.ts +34 -0
- package/src/workflows/language/index.ts +20 -0
- package/src/workflows/language/references.ts +55 -0
- package/src/workflows/language/schema.ts +134 -0
- package/src/workflows/language/strict-yaml.ts +131 -0
- package/src/workflows/manifest.ts +24 -0
- package/src/workflows/module.ts +12 -0
- package/src/workflows/runtime/coordinator.ts +115 -0
- package/src/workflows/runtime/executor.ts +1003 -0
- package/src/workflows/runtime/index.ts +60 -0
- package/src/workflows/runtime/instant.ts +8 -0
- package/src/workflows/runtime/ports.ts +213 -0
- package/src/workflows/runtime/schedule.ts +203 -0
- package/src/workflows/runtime/trigger.ts +37 -0
- package/src/workflows/runtime/wake.ts +13 -0
- package/src/workflows/store/actions.ts +438 -0
- package/src/workflows/store/budget.ts +145 -0
- package/src/workflows/store/definitions.ts +345 -0
- package/src/workflows/store/events.ts +383 -0
- package/src/workflows/store/index.ts +107 -0
- package/src/workflows/store/observability.ts +594 -0
- package/src/workflows/store/runs.ts +975 -0
- package/src/workflows/store/transaction.ts +23 -0
- package/src/workflows/store/worker.ts +439 -0
- package/src/workflows/testing/conformance.ts +72 -0
- package/src/workflows/testing/index.ts +11 -0
- package/src/workflows/testing/process-fixtures.ts +275 -0
|
@@ -0,0 +1,801 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineApp() — The single entry point for every cloud app.
|
|
3
|
+
*
|
|
4
|
+
* Merges SSR config, app meta, and server bootstrap into one call.
|
|
5
|
+
* Returns `{ ssr, plugin, config, meta, start }`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SsrConfig } from "@k2b/ssr";
|
|
9
|
+
import { createConfig as createSsrConfig } from "@k2b/ssr";
|
|
10
|
+
import { routes } from "@k2b/ssr/hono";
|
|
11
|
+
import type { Sync } from "@k2b/sync";
|
|
12
|
+
import { type Context, type Handler, Hono } from "hono";
|
|
13
|
+
import { generateSpecs } from "hono-openapi";
|
|
14
|
+
import { env } from "../config/env";
|
|
15
|
+
import type {
|
|
16
|
+
AppAdminNavigationGroup,
|
|
17
|
+
AppAppearance,
|
|
18
|
+
AppLifecycle,
|
|
19
|
+
AppMeta,
|
|
20
|
+
AppPresentationCatalog,
|
|
21
|
+
CloudLifecycleContext,
|
|
22
|
+
WidgetEndpoint,
|
|
23
|
+
} from "../contracts/app";
|
|
24
|
+
import { CAPABILITY_FRAMEWORK_ERROR_CODES, CAPABILITY_MAX_REQUEST_BYTES, type CapabilityDefinitions } from "../contracts/capabilities";
|
|
25
|
+
import { type BoundNotificationMap, bindNotificationDefinitions, type NotificationDefinitionMap } from "../contracts/notification-types";
|
|
26
|
+
import type { AppRegistryEntry } from "../contracts/registry";
|
|
27
|
+
import type { AppSettingsMap, KindToType } from "../contracts/settings-types";
|
|
28
|
+
import type { Role } from "../contracts/shared";
|
|
29
|
+
import type { HelpDefinition } from "../server/help";
|
|
30
|
+
import { getLocale, resolveLocale } from "../server/locale";
|
|
31
|
+
import { type AuthContext, auth } from "../server/middleware/auth";
|
|
32
|
+
import { requireInvocation } from "../server/middleware/invocation";
|
|
33
|
+
import { routeTemplate } from "../server/middleware/route-template";
|
|
34
|
+
import { runtime as runtimeMiddleware } from "../server/middleware/runtime";
|
|
35
|
+
import { settings as settingsMiddleware } from "../server/middleware/settings";
|
|
36
|
+
import {
|
|
37
|
+
capabilityInvocationOperation,
|
|
38
|
+
searchInvocationOperation,
|
|
39
|
+
syncInvocationOperation,
|
|
40
|
+
widgetInvocationOperation,
|
|
41
|
+
} from "../services/identity/invocation-operations";
|
|
42
|
+
import { logger } from "../services/logging";
|
|
43
|
+
import { startNotificationDefinitionRegistration } from "../services/notifications/catalog";
|
|
44
|
+
import { railPreferences } from "../services/rail-preferences";
|
|
45
|
+
import { get, loadCache as loadSettingsCache, set } from "../services/settings";
|
|
46
|
+
import { createSettingsAPI, type SettingsAPI } from "../services/settings/api";
|
|
47
|
+
import { registerSettings, toLegacySettingDefs } from "../services/settings/defaults";
|
|
48
|
+
import { createSyncOpsRoutes } from "../services/sync-ops";
|
|
49
|
+
import { capabilityMessages } from "../shared/capability-messages";
|
|
50
|
+
import { normalizeLocale } from "../shared/locale";
|
|
51
|
+
import { escapeHtml } from "../shared/markdown/shared";
|
|
52
|
+
import { themeBootstrapScript } from "../shared/theme";
|
|
53
|
+
import { appFaviconHref } from "./app-favicon";
|
|
54
|
+
import { compileAppPresentation } from "./app-presentation";
|
|
55
|
+
import { readBoundedJson } from "./bounded-json";
|
|
56
|
+
import { appRuntimeMetadata } from "./build-metadata";
|
|
57
|
+
import { compileCapabilities, invokeCompiledCapability, reviewCompiledCapability, serializeCapabilityProviderResult } from "./capabilities";
|
|
58
|
+
import { createHeartbeat } from "./heartbeat";
|
|
59
|
+
import { compileHelp } from "./help";
|
|
60
|
+
import { createPageResponses } from "./page-responses";
|
|
61
|
+
import { getProcessSync, startProcessSync } from "./process-sync";
|
|
62
|
+
import { APP_READINESS_PATH, appReadinessResponse } from "./readiness";
|
|
63
|
+
import { appRegistry, type CapabilityRegistryRecord, capabilityRegistry, helpRegistry } from "./registry";
|
|
64
|
+
import { ensureRuntimeWatcher, getCurrentRuntime, stopRuntimeWatcher } from "./runtime-watcher";
|
|
65
|
+
import { servePublicAsset } from "./static-assets";
|
|
66
|
+
import { createStatusPreservingSsrHandler } from "./status-preserving-ssr";
|
|
67
|
+
|
|
68
|
+
/** Cache-busting version stamp — changes on every server start / rebuild. */
|
|
69
|
+
const v = Date.now();
|
|
70
|
+
|
|
71
|
+
export type PageOptions = {
|
|
72
|
+
title?: string;
|
|
73
|
+
description?: string;
|
|
74
|
+
theme?: "light" | "dark";
|
|
75
|
+
/** Framework-resolved BCP 47 document language — always the canonical request locale, never a page override. */
|
|
76
|
+
lang?: string;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// ── Public types ────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* App definition options.
|
|
83
|
+
*
|
|
84
|
+
* `S` is the inferred per-app settings map (see `AppSettingsMap`). Apps that
|
|
85
|
+
* declare `settings: { ... } as const` get S inferred to the literal shape;
|
|
86
|
+
* `AppContext<typeof app>` then exposes the typed snapshot on `c.get("settings")`.
|
|
87
|
+
*
|
|
88
|
+
* Apps that omit `settings` get S = {} (no own settings — only core's are
|
|
89
|
+
* available in their snapshot, populated by core's own defineApp.settings).
|
|
90
|
+
*/
|
|
91
|
+
export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefinitionMap = {}, AppId extends string = string> = {
|
|
92
|
+
id: AppId;
|
|
93
|
+
name: string;
|
|
94
|
+
icon: string;
|
|
95
|
+
description: string;
|
|
96
|
+
/** Optional localized overlays for app-owned presentation metadata. */
|
|
97
|
+
presentation?: AppPresentationCatalog;
|
|
98
|
+
appearance?: AppAppearance;
|
|
99
|
+
/** URL prefix for SSR asset isolation. Omit for the global `/_ssr/` path (core). */
|
|
100
|
+
basePath?: string;
|
|
101
|
+
/** Base URL as seen by other containers (e.g. "http://app-notebooks:3000"). */
|
|
102
|
+
baseUrl: string;
|
|
103
|
+
adminHref?: string;
|
|
104
|
+
/** Multi-link admin navigation contributed by this app. */
|
|
105
|
+
adminNav?: ReadonlyArray<{
|
|
106
|
+
id?: string;
|
|
107
|
+
label: string;
|
|
108
|
+
links: ReadonlyArray<{ label: string; href: string; icon: string }>;
|
|
109
|
+
}>;
|
|
110
|
+
nav?: {
|
|
111
|
+
href: string;
|
|
112
|
+
match?: string;
|
|
113
|
+
section: "primary" | "more" | "hidden";
|
|
114
|
+
requiresAuth?: boolean;
|
|
115
|
+
requiresRoles?: Role[];
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Settings owned by this app, declared as a map of dotted-key → definition.
|
|
119
|
+
*
|
|
120
|
+
* Example: `{ "files.filegate_url": { kind: "url", default: "" } }`.
|
|
121
|
+
*
|
|
122
|
+
* These keys are exposed as a typed nested snapshot on `c.get("settings")`
|
|
123
|
+
* for any Hono route using `Hono<AppContext<typeof app>>`. Writes go through
|
|
124
|
+
* `app.settings.set(key, value)` (also typed). The runtime registry
|
|
125
|
+
* (`SETTINGS_MAP` in `services/settings/defaults.ts`) is populated from
|
|
126
|
+
* this map automatically on `defineApp()` call.
|
|
127
|
+
*/
|
|
128
|
+
settings?: S;
|
|
129
|
+
/** Notification kinds owned by this app, conventionally imported from `src/notifications.ts`. */
|
|
130
|
+
notifications?: N;
|
|
131
|
+
/**
|
|
132
|
+
* Legal/info links contributed by this app — aggregated app-wide via
|
|
133
|
+
* `listLegalLinks()` and rendered in login footer, app Footer, rail more
|
|
134
|
+
* dropdown. Each app contributes its own (e.g. core owns
|
|
135
|
+
* Imprint/Privacy/Terms; faq owns FAQ). KISS: no `external` flag, links
|
|
136
|
+
* always open in a new tab from the login footer.
|
|
137
|
+
*/
|
|
138
|
+
legalLinks?: ReadonlyArray<{ label: string; href: string; icon?: string }>;
|
|
139
|
+
/**
|
|
140
|
+
* Dashboard widget endpoints this app exposes. Each entry references an
|
|
141
|
+
* HTTP path on this app that returns a `WidgetResponse`. The dashboard
|
|
142
|
+
* reaches the declared handler through Core; the endpoint remains
|
|
143
|
+
* responsible for permission gating (200 = render, 403 = unavailable at
|
|
144
|
+
* the user's access level, 204 = no content).
|
|
145
|
+
*/
|
|
146
|
+
widgets?: ReadonlyArray<WidgetEndpoint>;
|
|
147
|
+
/**
|
|
148
|
+
* Top-level URL prefixes the gateway should route to this app.
|
|
149
|
+
*
|
|
150
|
+
* Standard apps follow a four-prefix convention:
|
|
151
|
+
* `/api/<id>` — widget, admin, ws, crud — everything HTTP API
|
|
152
|
+
* `/app/<id>` — user-facing SSR pages
|
|
153
|
+
* `/admin/<id>` — admin SSR pages
|
|
154
|
+
* `/public/<id>` — built CSS and other static assets
|
|
155
|
+
*
|
|
156
|
+
* Apps with non-standard URLs (core's `/auth`, `/me`, `/legal/*`, `/impressum`;
|
|
157
|
+
* oauth's `/oauth`, `/.well-known/...`) list whatever
|
|
158
|
+
* top-level paths they own. The gateway is dumb — it just builds a
|
|
159
|
+
* prefix-trie from these strings.
|
|
160
|
+
*/
|
|
161
|
+
routes: readonly string[];
|
|
162
|
+
/**
|
|
163
|
+
* Gateway-relative URL at which this app's OpenAPI 3.x JSON spec is
|
|
164
|
+
* served, e.g. `"/api/notebooks/openapi.json"`. Opt-in: only set this
|
|
165
|
+
* for apps whose api router is documented with `middleware.openapi()`
|
|
166
|
+
* (i.e. `describeRoute()`) and worth surfacing in the api-docs aggregator.
|
|
167
|
+
*
|
|
168
|
+
* Pair this with `app.start({ openapi: <api router> })` — `defineApp`
|
|
169
|
+
* generates the spec from that router at boot, mounts it on the
|
|
170
|
+
* framework server (before the user's fetch, so it bypasses any
|
|
171
|
+
* auth/rate-limit middleware), and advertises the URL via the registry
|
|
172
|
+
* so `app-api-docs` picks it up automatically.
|
|
173
|
+
*
|
|
174
|
+
* The path must be reachable through the gateway — usually that means
|
|
175
|
+
* the standard form `"/api/<id>/openapi.json"` (covered by the
|
|
176
|
+
* `/api/<id>` entry in `routes`).
|
|
177
|
+
*/
|
|
178
|
+
openapi?: string;
|
|
179
|
+
/**
|
|
180
|
+
* Project root used by the SSR plugin to discover island/client files.
|
|
181
|
+
* Defaults to `process.cwd()`. Override only if you run the entrypoint
|
|
182
|
+
* from a directory other than the project root.
|
|
183
|
+
*/
|
|
184
|
+
appRoot?: string;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export type StartOptions = {
|
|
188
|
+
/**
|
|
189
|
+
* Web-standard fetch handler. Mounted at `/` of the app's container.
|
|
190
|
+
* Typically you pass a Hono instance's `.fetch`:
|
|
191
|
+
*
|
|
192
|
+
* const router = new Hono<AuthContext>()
|
|
193
|
+
* .use("*", middleware.runtime())
|
|
194
|
+
* .use("*", middleware.settings())
|
|
195
|
+
* .route("/api/<id>", apiRoutes)
|
|
196
|
+
* .route("/app/<id>", pageRoutes);
|
|
197
|
+
*
|
|
198
|
+
* app.start({ fetch: router.fetch });
|
|
199
|
+
*
|
|
200
|
+
* The framework owns `/_ssr/*`, `/public/*`, and the versioned internal
|
|
201
|
+
* capability and widget endpoints when their handlers are declared, and
|
|
202
|
+
* registers them before this fetch — they take precedence over any
|
|
203
|
+
* catch-all the app might register.
|
|
204
|
+
*/
|
|
205
|
+
fetch: (req: Request, env?: unknown) => Response | Promise<Response>;
|
|
206
|
+
/**
|
|
207
|
+
* Hono router to scan for OpenAPI route metadata. When set together with
|
|
208
|
+
* `defineApp({ openapi: "..." })`, the framework generates an OpenAPI
|
|
209
|
+
* spec from this router and mounts it at the configured URL on the
|
|
210
|
+
* framework server (before the user's fetch, so the spec stays public).
|
|
211
|
+
*
|
|
212
|
+
* Pass the BARE api router — the one with `describeRoute()` annotations.
|
|
213
|
+
* `generateSpecs` walks the route tree without executing middleware, so
|
|
214
|
+
* the inner auth/rate-limit `.use(...)` calls don't matter for spec
|
|
215
|
+
* generation; they only run if the router is hit by an actual request.
|
|
216
|
+
*/
|
|
217
|
+
openapi?: Hono<any>;
|
|
218
|
+
lifecycle?: AppLifecycle;
|
|
219
|
+
capabilities?: CapabilityDefinitions;
|
|
220
|
+
/**
|
|
221
|
+
* Handlers for declared dashboard widgets, keyed by widget id. The
|
|
222
|
+
* framework exposes these only on its invocation-authenticated internal
|
|
223
|
+
* route; applications may keep mounting the same handlers on their public
|
|
224
|
+
* compatibility paths during the rolling migration.
|
|
225
|
+
*/
|
|
226
|
+
widgets?: Readonly<Record<string, Handler<AuthContext>>>;
|
|
227
|
+
help?: HelpDefinition;
|
|
228
|
+
port?: number;
|
|
229
|
+
skipSetup?: boolean;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export type StartResult = {
|
|
233
|
+
hostname: "0.0.0.0";
|
|
234
|
+
port: number;
|
|
235
|
+
development: boolean;
|
|
236
|
+
fetch: Hono["fetch"];
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export type AppDefinition<S extends AppSettingsMap = {}, N extends NotificationDefinitionMap = {}, AppId extends string = string> = {
|
|
240
|
+
// Bind the generic explicitly — without it, ssr collapses to the constraint
|
|
241
|
+
// `object` and apps lose the typed `c.get("page")` (title/description/theme).
|
|
242
|
+
ssr: ReturnType<typeof createStatusPreservingSsrHandler<PageOptions>> & ReturnType<typeof createPageResponses>;
|
|
243
|
+
plugin: () => import("bun").BunPlugin;
|
|
244
|
+
config: SsrConfig;
|
|
245
|
+
meta: AppMeta;
|
|
246
|
+
baseUrl: string;
|
|
247
|
+
start: (opts: StartOptions) => Promise<StartResult>;
|
|
248
|
+
/**
|
|
249
|
+
* The process-wide @k2b/sync instance, connected and ready once `start()`
|
|
250
|
+
* resolved. Reading it earlier throws: declare primitives lazily with
|
|
251
|
+
* `lazySync((sync) => sync.job(...))` and use them from `lifecycle.start`
|
|
252
|
+
* or request handlers.
|
|
253
|
+
*/
|
|
254
|
+
readonly sync: Sync;
|
|
255
|
+
/**
|
|
256
|
+
* Phantom field — type-only carrier for the per-app settings shape. Always
|
|
257
|
+
* `undefined` at runtime; do not read or assign. Used by `AppContext<App>`
|
|
258
|
+
* to extract the inferred settings map via `App["_settings"]`.
|
|
259
|
+
*/
|
|
260
|
+
readonly _settings: S;
|
|
261
|
+
/**
|
|
262
|
+
* Typed async settings API for this app. Keys constrained to those declared
|
|
263
|
+
* in `defineApp({ settings: ... })`. Backed by Redis cache-aside (see store.ts).
|
|
264
|
+
*
|
|
265
|
+
* Use for read/write outside of request-scoped sync access. Inside HTTP
|
|
266
|
+
* handlers, prefer `c.get("settings").x.y` (the per-request snapshot, sync,
|
|
267
|
+
* frozen for the duration of the request).
|
|
268
|
+
*/
|
|
269
|
+
readonly settings: SettingsAPI<{ [K in keyof S]: KindToType<S[K]["kind"]> }>;
|
|
270
|
+
/** Typed notification descriptors declared by this app. */
|
|
271
|
+
readonly notifications: BoundNotificationMap<AppId, N>;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// ── Implementation ──────────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
export const defineApp = <
|
|
277
|
+
const S extends AppSettingsMap = {},
|
|
278
|
+
const N extends NotificationDefinitionMap = {},
|
|
279
|
+
const AppId extends string = string,
|
|
280
|
+
>(
|
|
281
|
+
opts: AppOptions<S, N, AppId>,
|
|
282
|
+
): AppDefinition<S, N, AppId> => {
|
|
283
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
284
|
+
const notifications = bindNotificationDefinitions(opts.id, opts.notifications);
|
|
285
|
+
|
|
286
|
+
// ── 0. Register declared settings into the runtime registry ──────────
|
|
287
|
+
// SETTINGS_MAP is the single source of truth for validation in store.ts
|
|
288
|
+
// (writeKey checks SETTINGS_MAP.get(key)) and for snapshot.ts (allKnownKeys
|
|
289
|
+
// returns SETTINGS.map(d => d.key)). Without this registration, app-declared
|
|
290
|
+
// settings would be type-known but runtime-unknown.
|
|
291
|
+
if (opts.settings) {
|
|
292
|
+
registerSettings(toLegacySettingDefs(opts.settings as Record<string, unknown>, opts.presentation?.baseLocale ?? "en"));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ── 1. SSR config ─────────────────────────────────────────────────────
|
|
296
|
+
const { config, plugin, html } = createSsrConfig<PageOptions>({
|
|
297
|
+
dev: isDevelopment,
|
|
298
|
+
verbose: true,
|
|
299
|
+
rootDir: opts.appRoot ?? process.cwd(),
|
|
300
|
+
basePath: opts.basePath,
|
|
301
|
+
template: ({ body, scripts, title, description, theme, lang }) => {
|
|
302
|
+
const themeFixed = theme !== undefined;
|
|
303
|
+
return `<!DOCTYPE html>
|
|
304
|
+
<html lang="${normalizeLocale(lang)}" class="${theme ?? "light"}"${themeFixed ? " data-theme-fixed" : ""}>
|
|
305
|
+
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
306
|
+
<meta name="view-transition" content="same-origin">
|
|
307
|
+
<title>${escapeHtml(title ?? "Cloud")}</title>
|
|
308
|
+
<meta name="description" content="${escapeHtml(description ?? "Cloud workspace")}">
|
|
309
|
+
<meta name="theme-color" content="#09090b">
|
|
310
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
311
|
+
<link rel="icon" href="${appFaviconHref(opts.id, v)}">
|
|
312
|
+
<style data-cloud-css-layers>@layer theme, base, components, utilities;</style>
|
|
313
|
+
<link rel="preload" href="/public/tabler-icons.woff2" as="font" type="font/woff2" crossorigin>
|
|
314
|
+
<link rel="stylesheet" href="/public/fonts.css?v=${v}">
|
|
315
|
+
<link rel="stylesheet" href="/public/tabler-icons.css?v=${v}">
|
|
316
|
+
<link rel="stylesheet" href="/public/${opts.id}/app.css?v=${v}">
|
|
317
|
+
<link rel="stylesheet" href="/public/global.css?v=${v}">
|
|
318
|
+
<script>${themeBootstrapScript}</script>
|
|
319
|
+
</head>
|
|
320
|
+
<body class="k2b-ui" data-k2b-app-workspace-controller="global">
|
|
321
|
+
${body}
|
|
322
|
+
</body>
|
|
323
|
+
${scripts}
|
|
324
|
+
</html>`;
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Pass PageOptions explicitly so c.get("page") in apps' SSR handlers is
|
|
329
|
+
// typed as Partial<PageOptions> (with title/description/theme), not the
|
|
330
|
+
// bare `object` fallback the constraint would otherwise produce.
|
|
331
|
+
//
|
|
332
|
+
// The finalize hook resolves the request locale for `<html lang>` after all
|
|
333
|
+
// route middlewares ran (so the settings snapshot is present), keeping the
|
|
334
|
+
// document language request-scoped without per-page plumbing. It assigns
|
|
335
|
+
// unconditionally: `<html lang>`, the Layout LocaleProvider, and
|
|
336
|
+
// `getDateConfig` share one canonical `getLocale(c)` that page handlers
|
|
337
|
+
// cannot override.
|
|
338
|
+
const ssr = Object.assign(
|
|
339
|
+
createStatusPreservingSsrHandler<PageOptions>(html, async (c) => {
|
|
340
|
+
c.get("page").lang = getLocale(c);
|
|
341
|
+
const user = c.get("user");
|
|
342
|
+
if (user) c.set("railPreferences", await railPreferences.get(user.id));
|
|
343
|
+
}),
|
|
344
|
+
createPageResponses(html),
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
// ── 2. Meta ───────────────────────────────────────────────────────────
|
|
348
|
+
const baseMeta: AppMeta = {
|
|
349
|
+
id: opts.id,
|
|
350
|
+
name: opts.name,
|
|
351
|
+
icon: opts.icon,
|
|
352
|
+
description: opts.description,
|
|
353
|
+
appearance: opts.appearance,
|
|
354
|
+
adminHref: opts.adminHref,
|
|
355
|
+
adminNav: opts.adminNav?.map(
|
|
356
|
+
(group): AppAdminNavigationGroup => ({
|
|
357
|
+
id: group.id,
|
|
358
|
+
label: group.label,
|
|
359
|
+
links: group.links.map((link) => ({ ...link })),
|
|
360
|
+
}),
|
|
361
|
+
),
|
|
362
|
+
routes: [...opts.routes],
|
|
363
|
+
nav: opts.nav,
|
|
364
|
+
legalLinks: opts.legalLinks ? [...opts.legalLinks] : undefined,
|
|
365
|
+
widgets: opts.widgets ? opts.widgets.map((w) => ({ ...w })) : undefined,
|
|
366
|
+
settingKeys: opts.settings ? Object.keys(opts.settings) : undefined,
|
|
367
|
+
openapi: opts.openapi,
|
|
368
|
+
};
|
|
369
|
+
const meta: AppMeta = { ...baseMeta, presentation: compileAppPresentation(baseMeta, opts.presentation) };
|
|
370
|
+
|
|
371
|
+
// ── 3. start() — builds and boots the Hono server ────────────────────
|
|
372
|
+
const start = async (startOpts: StartOptions): Promise<StartResult> => {
|
|
373
|
+
const port = startOpts.port ?? 3000;
|
|
374
|
+
const baseUrl = opts.baseUrl;
|
|
375
|
+
const log = logger("app");
|
|
376
|
+
|
|
377
|
+
// Settings are encrypted at rest, so every container needs the secret. It
|
|
378
|
+
// is only read on the first decrypt, which meant a container without it
|
|
379
|
+
// booted cleanly and failed much later with an unrelated-looking error.
|
|
380
|
+
if (!env.APP_SECRET.trim()) {
|
|
381
|
+
throw new Error(
|
|
382
|
+
`APP_SECRET is not set (app "${meta.id}"). Settings are encrypted at rest and every app in the ` +
|
|
383
|
+
"deployment must share the same value; without it the first settings read fails.",
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// One NATS connection and one Sync instance per process. Ready before
|
|
388
|
+
// anything registers, so a discoverable app can always serve sync work.
|
|
389
|
+
const processSync = await startProcessSync({ application: meta.id });
|
|
390
|
+
const cleanup: Array<() => void | Promise<void>> = [() => processSync.stop()];
|
|
391
|
+
let cleanupPromise: Promise<void> | undefined;
|
|
392
|
+
const stopStartedResources = (): Promise<void> =>
|
|
393
|
+
(cleanupPromise ??= (async () => {
|
|
394
|
+
for (const stop of cleanup.toReversed()) {
|
|
395
|
+
try {
|
|
396
|
+
await stop();
|
|
397
|
+
} catch (error) {
|
|
398
|
+
log.error("Application cleanup failed", { appId: meta.id, error: error instanceof Error ? error.message : String(error) });
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
})());
|
|
402
|
+
try {
|
|
403
|
+
const startedAt = Date.now();
|
|
404
|
+
|
|
405
|
+
// OpenAPI advertised in the registry only when there's a router to
|
|
406
|
+
// derive the spec from. The mount block lower down uses the same
|
|
407
|
+
// flag so the registry never points at a URL that 404s.
|
|
408
|
+
const advertiseOpenapi = !!(opts.openapi && startOpts.openapi);
|
|
409
|
+
const compiledCapabilities = startOpts.capabilities ? compileCapabilities(meta.id, startOpts.capabilities) : undefined;
|
|
410
|
+
const compiledHelp = startOpts.help
|
|
411
|
+
? compileHelp({
|
|
412
|
+
appId: meta.id,
|
|
413
|
+
appName: meta.name,
|
|
414
|
+
appIcon: meta.icon,
|
|
415
|
+
basePath: opts.basePath,
|
|
416
|
+
definition: startOpts.help,
|
|
417
|
+
})
|
|
418
|
+
: undefined;
|
|
419
|
+
|
|
420
|
+
// Registry entry
|
|
421
|
+
const entry: AppRegistryEntry = {
|
|
422
|
+
id: meta.id,
|
|
423
|
+
name: meta.name,
|
|
424
|
+
icon: meta.icon,
|
|
425
|
+
description: meta.description,
|
|
426
|
+
presentation: meta.presentation,
|
|
427
|
+
appearance: meta.appearance,
|
|
428
|
+
baseUrl,
|
|
429
|
+
runtime: appRuntimeMetadata,
|
|
430
|
+
startedAt,
|
|
431
|
+
routes: [...meta.routes],
|
|
432
|
+
nav:
|
|
433
|
+
meta.nav || meta.adminHref
|
|
434
|
+
? {
|
|
435
|
+
href: meta.nav?.href ?? "",
|
|
436
|
+
match: meta.nav?.match,
|
|
437
|
+
section: meta.nav?.section ?? "hidden",
|
|
438
|
+
requiresAuth: meta.nav?.requiresAuth,
|
|
439
|
+
requiresRoles: meta.nav?.requiresRoles,
|
|
440
|
+
adminHref: meta.adminHref,
|
|
441
|
+
}
|
|
442
|
+
: undefined,
|
|
443
|
+
adminNav: meta.adminNav?.map((group) => ({
|
|
444
|
+
id: group.id,
|
|
445
|
+
label: group.label,
|
|
446
|
+
links: group.links.map((link) => ({ ...link })),
|
|
447
|
+
})),
|
|
448
|
+
capabilities: compiledCapabilities
|
|
449
|
+
? {
|
|
450
|
+
protocolVersion: compiledCapabilities.manifest.protocolVersion,
|
|
451
|
+
manifestHash: compiledCapabilities.manifest.manifestHash,
|
|
452
|
+
}
|
|
453
|
+
: undefined,
|
|
454
|
+
help: compiledHelp?.summary,
|
|
455
|
+
legalLinks: meta.legalLinks ? meta.legalLinks.map((l) => ({ ...l })) : undefined,
|
|
456
|
+
widgets: meta.widgets ? meta.widgets.map((w) => ({ ...w })) : undefined,
|
|
457
|
+
settingKeys: meta.settingKeys ? [...meta.settingKeys] : undefined,
|
|
458
|
+
openapi: advertiseOpenapi ? opts.openapi : undefined,
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// Heartbeat
|
|
462
|
+
const heartbeat = createHeartbeat(meta.id, entry, {
|
|
463
|
+
registry: appRegistry(),
|
|
464
|
+
onError: (error) =>
|
|
465
|
+
log.error("Registry heartbeat failed", {
|
|
466
|
+
appId: meta.id,
|
|
467
|
+
error: error instanceof Error ? error.message : String(error),
|
|
468
|
+
}),
|
|
469
|
+
onStale: (error) => {
|
|
470
|
+
log.error("Registry heartbeat could not renew the app lease; restarting", {
|
|
471
|
+
appId: meta.id,
|
|
472
|
+
error: error instanceof Error ? error.message : String(error),
|
|
473
|
+
});
|
|
474
|
+
process.exit(1);
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
const capabilityEntry: CapabilityRegistryRecord | undefined = compiledCapabilities
|
|
478
|
+
? {
|
|
479
|
+
appId: meta.id,
|
|
480
|
+
manifest: compiledCapabilities.manifest,
|
|
481
|
+
presentation: compiledCapabilities.presentation,
|
|
482
|
+
}
|
|
483
|
+
: undefined;
|
|
484
|
+
const capabilityHeartbeat = capabilityEntry
|
|
485
|
+
? createHeartbeat(meta.id, capabilityEntry, {
|
|
486
|
+
key: `capabilities/${meta.id}`,
|
|
487
|
+
registry: capabilityRegistry(),
|
|
488
|
+
onError: (error) =>
|
|
489
|
+
log.error("Capability registry heartbeat failed", {
|
|
490
|
+
appId: meta.id,
|
|
491
|
+
error: error instanceof Error ? error.message : String(error),
|
|
492
|
+
}),
|
|
493
|
+
onStale: (error) => {
|
|
494
|
+
log.error("Capability registry lease expired; restarting", {
|
|
495
|
+
appId: meta.id,
|
|
496
|
+
error: error instanceof Error ? error.message : String(error),
|
|
497
|
+
});
|
|
498
|
+
process.exit(1);
|
|
499
|
+
},
|
|
500
|
+
})
|
|
501
|
+
: undefined;
|
|
502
|
+
const helpHeartbeat = compiledHelp
|
|
503
|
+
? createHeartbeat(meta.id, compiledHelp.registryEntry, {
|
|
504
|
+
key: `help/${meta.id}`,
|
|
505
|
+
registry: helpRegistry(),
|
|
506
|
+
onError: (error) =>
|
|
507
|
+
log.error("Help registry heartbeat failed", {
|
|
508
|
+
appId: meta.id,
|
|
509
|
+
error: error instanceof Error ? error.message : String(error),
|
|
510
|
+
}),
|
|
511
|
+
onStale: (error) => {
|
|
512
|
+
log.error("Help registry lease expired; restarting", {
|
|
513
|
+
appId: meta.id,
|
|
514
|
+
error: error instanceof Error ? error.message : String(error),
|
|
515
|
+
});
|
|
516
|
+
process.exit(1);
|
|
517
|
+
},
|
|
518
|
+
})
|
|
519
|
+
: undefined;
|
|
520
|
+
cleanup.push(async () => {
|
|
521
|
+
await helpHeartbeat?.stop();
|
|
522
|
+
});
|
|
523
|
+
cleanup.push(async () => {
|
|
524
|
+
await capabilityHeartbeat?.stop();
|
|
525
|
+
});
|
|
526
|
+
cleanup.push(() => heartbeat.stop());
|
|
527
|
+
|
|
528
|
+
// Runtime context — start the registry watcher so middleware.runtime() and
|
|
529
|
+
// the lifecycle context below see populated cluster state. Idempotent: the
|
|
530
|
+
// watcher is a module-level singleton, only one runs per process.
|
|
531
|
+
cleanup.push(() => stopRuntimeWatcher());
|
|
532
|
+
await ensureRuntimeWatcher();
|
|
533
|
+
|
|
534
|
+
// Build Hono server. Framework owns these mounts (registered first so
|
|
535
|
+
// they take precedence over any catch-all in the user's fetch):
|
|
536
|
+
// /_ssr/* island chunks (SSR adapter)
|
|
537
|
+
// /public/* serveStatic + terminal 404
|
|
538
|
+
// /api/_internal/capabilities/v1/* when capabilities are declared
|
|
539
|
+
// /api/_internal/widgets/v1/* when widget handlers are bound
|
|
540
|
+
// /_internal/sync/* process-local sync operations (admin only)
|
|
541
|
+
// <opts.openapi> OpenAPI JSON spec, when both opts.openapi
|
|
542
|
+
// and startOpts.openapi are set
|
|
543
|
+
const ssrMountPath = config.basePath ? `${config.basePath}/_ssr` : "/_ssr";
|
|
544
|
+
|
|
545
|
+
// Framework-owned mounts answer before the app's router ever runs, so the
|
|
546
|
+
// app middleware cannot report their template. Without this, every hashed
|
|
547
|
+
// island chunk would land in telemetry as its own route.
|
|
548
|
+
const server = new Hono<AuthContext>()
|
|
549
|
+
.use("*", routeTemplate)
|
|
550
|
+
.get(APP_READINESS_PATH, () => appReadinessResponse(meta.id))
|
|
551
|
+
.route(ssrMountPath, routes(config))
|
|
552
|
+
.all(`${ssrMountPath}/*`, (c) => c.notFound())
|
|
553
|
+
.all("/public/*", servePublicAsset(isDevelopment))
|
|
554
|
+
.use(
|
|
555
|
+
"/_internal/sync/*",
|
|
556
|
+
requireInvocation((c) => ({
|
|
557
|
+
targetAppId: meta.id,
|
|
558
|
+
operation: syncInvocationOperation(c.req.method, new URL(c.req.url).pathname.slice("/_internal/sync".length)),
|
|
559
|
+
schemaHash: null,
|
|
560
|
+
})),
|
|
561
|
+
)
|
|
562
|
+
.use("/_internal/sync/*", auth.requireOAuthScope("admin"))
|
|
563
|
+
.use("/_internal/sync/*", async (c, next) => {
|
|
564
|
+
if (!c.get("user")?.roles.includes("admin")) return c.json({ message: "Insufficient permissions" }, 403);
|
|
565
|
+
return next();
|
|
566
|
+
})
|
|
567
|
+
.route("/_internal/sync", createSyncOpsRoutes(getProcessSync));
|
|
568
|
+
|
|
569
|
+
if (compiledHelp) {
|
|
570
|
+
const pageBase = compiledHelp.summary.pageBase;
|
|
571
|
+
const centralPageBase = `/help/apps/${encodeURIComponent(meta.id)}`;
|
|
572
|
+
server.get(pageBase, (c) => c.redirect(centralPageBase, 302));
|
|
573
|
+
server.get(`${pageBase}/:topic`, (c) => c.redirect(`${centralPageBase}/${encodeURIComponent(c.req.param("topic"))}`, 302));
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (compiledCapabilities) {
|
|
577
|
+
const invoke = async (c: Context<AuthContext>, kind: "query" | "action" | "review") => {
|
|
578
|
+
// Framework-owned endpoints run before the app's settings middleware,
|
|
579
|
+
// so the operator default is read directly instead of via a snapshot.
|
|
580
|
+
const requestLocale = resolveLocale(c.req.raw.headers, await get<string>("app.locale"));
|
|
581
|
+
const messages = capabilityMessages(requestLocale);
|
|
582
|
+
const parsedBody = await readBoundedJson(c.req.raw, CAPABILITY_MAX_REQUEST_BYTES);
|
|
583
|
+
if (!parsedBody.ok) {
|
|
584
|
+
const message = parsedBody.reason === "too_large" ? messages.requestTooLarge : messages.requestBodyJson;
|
|
585
|
+
return c.json({ code: CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, message }, 400);
|
|
586
|
+
}
|
|
587
|
+
if (
|
|
588
|
+
typeof parsedBody.data !== "object" ||
|
|
589
|
+
parsedBody.data === null ||
|
|
590
|
+
Array.isArray(parsedBody.data) ||
|
|
591
|
+
!Object.hasOwn(parsedBody.data, "input") ||
|
|
592
|
+
Object.keys(parsedBody.data).length !== 1
|
|
593
|
+
) {
|
|
594
|
+
return c.json(
|
|
595
|
+
{
|
|
596
|
+
code: CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed,
|
|
597
|
+
message: messages.requestInputOnly,
|
|
598
|
+
},
|
|
599
|
+
400,
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
const body = parsedBody.data as { input: unknown };
|
|
603
|
+
const actor = c.get("actor");
|
|
604
|
+
const user = actor.kind === "user" ? actor.user : actor.delegatedUser;
|
|
605
|
+
const idempotencyKey = c.req.header("idempotency-key") || undefined;
|
|
606
|
+
const invocation = {
|
|
607
|
+
compiled: compiledCapabilities,
|
|
608
|
+
localId: c.req.param("capabilityId") ?? "",
|
|
609
|
+
input: body.input,
|
|
610
|
+
expectedSchemaHash: c.req.header("x-cloud-capability-schema-hash") ?? null,
|
|
611
|
+
context: {
|
|
612
|
+
actor,
|
|
613
|
+
accessSubject: c.get("accessSubject"),
|
|
614
|
+
user,
|
|
615
|
+
idempotencyKey,
|
|
616
|
+
locale: requestLocale,
|
|
617
|
+
signal: c.req.raw.signal,
|
|
618
|
+
},
|
|
619
|
+
onUnexpectedError: (error: unknown) =>
|
|
620
|
+
log.error(kind === "review" ? "Capability review failed" : "Capability execution failed", {
|
|
621
|
+
appId: meta.id,
|
|
622
|
+
kind,
|
|
623
|
+
capabilityId: c.req.param("capabilityId") ?? "",
|
|
624
|
+
error: error instanceof Error ? error.message : String(error),
|
|
625
|
+
}),
|
|
626
|
+
};
|
|
627
|
+
const result =
|
|
628
|
+
kind === "review" ? await reviewCompiledCapability(invocation) : await invokeCompiledCapability({ ...invocation, kind });
|
|
629
|
+
const operation = kind === "action" ? compiledCapabilities.actions.get(invocation.localId) : undefined;
|
|
630
|
+
const serialized = serializeCapabilityProviderResult(result, {
|
|
631
|
+
nonIdempotentAction: kind === "action" && operation?.manifest.idempotency === "none",
|
|
632
|
+
});
|
|
633
|
+
return new Response(serialized.body, {
|
|
634
|
+
status: serialized.status,
|
|
635
|
+
headers: { "content-type": "application/json" },
|
|
636
|
+
});
|
|
637
|
+
};
|
|
638
|
+
const capabilityAuth = (kind: "queries" | "actions", review = false) =>
|
|
639
|
+
requireInvocation((c) => {
|
|
640
|
+
const localId = c.req.param("capabilityId") ?? "";
|
|
641
|
+
const operation = kind === "queries" ? compiledCapabilities.queries.get(localId) : compiledCapabilities.actions.get(localId);
|
|
642
|
+
if (!operation) return null;
|
|
643
|
+
const requestedOperation = c.req.header("x-cloud-invocation-operation");
|
|
644
|
+
const invocationOperation =
|
|
645
|
+
kind === "queries" &&
|
|
646
|
+
requestedOperation === searchInvocationOperation &&
|
|
647
|
+
"universalSearch" in operation.manifest &&
|
|
648
|
+
operation.manifest.universalSearch
|
|
649
|
+
? searchInvocationOperation
|
|
650
|
+
: capabilityInvocationOperation(kind, localId, review);
|
|
651
|
+
return {
|
|
652
|
+
targetAppId: meta.id,
|
|
653
|
+
operation: invocationOperation,
|
|
654
|
+
schemaHash: operation.manifest.schemaHash,
|
|
655
|
+
};
|
|
656
|
+
});
|
|
657
|
+
const capabilityReadScope = auth.requireOAuthScope("read", "admin");
|
|
658
|
+
const capabilityWriteScope = auth.requireOAuthScope("write", "admin");
|
|
659
|
+
server.post("/api/_internal/capabilities/v1/queries/:capabilityId", capabilityAuth("queries"), capabilityReadScope, (c) =>
|
|
660
|
+
invoke(c, "query"),
|
|
661
|
+
);
|
|
662
|
+
server.post("/api/_internal/capabilities/v1/actions/:capabilityId", capabilityAuth("actions"), capabilityWriteScope, (c) =>
|
|
663
|
+
invoke(c, "action"),
|
|
664
|
+
);
|
|
665
|
+
server.post(
|
|
666
|
+
"/api/_internal/capabilities/v1/actions/:capabilityId/review",
|
|
667
|
+
capabilityAuth("actions", true),
|
|
668
|
+
capabilityReadScope,
|
|
669
|
+
(c) => invoke(c, "review"),
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (startOpts.widgets) {
|
|
674
|
+
const declaredWidgetIds = new Set(meta.widgets?.map((widget) => widget.id) ?? []);
|
|
675
|
+
for (const widgetId of Object.keys(startOpts.widgets)) {
|
|
676
|
+
if (!declaredWidgetIds.has(widgetId)) throw new Error(`Widget handler "${widgetId}" is not declared by app "${meta.id}"`);
|
|
677
|
+
}
|
|
678
|
+
server.get(
|
|
679
|
+
"/api/_internal/widgets/v1/:widgetId",
|
|
680
|
+
requireInvocation((c) => {
|
|
681
|
+
const widgetId = c.req.param("widgetId") ?? "";
|
|
682
|
+
if (!declaredWidgetIds.has(widgetId) || !startOpts.widgets?.[widgetId]) return null;
|
|
683
|
+
return { targetAppId: meta.id, operation: widgetInvocationOperation(widgetId), schemaHash: null };
|
|
684
|
+
}),
|
|
685
|
+
auth.requireOAuthScope("read", "admin"),
|
|
686
|
+
runtimeMiddleware(),
|
|
687
|
+
settingsMiddleware(),
|
|
688
|
+
async (c) => {
|
|
689
|
+
const handler = startOpts.widgets?.[c.req.param("widgetId") ?? ""];
|
|
690
|
+
return handler ? handler(c, async () => {}) : c.json({ message: "Widget not found" }, 404);
|
|
691
|
+
},
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// OpenAPI spec mount. Registered on the framework server (before the
|
|
696
|
+
// user-fetch catch-all below) so it bypasses any auth / rate-limit
|
|
697
|
+
// middleware on the api router — the spec must stay reachable without
|
|
698
|
+
// a session for `app-api-docs` to render it.
|
|
699
|
+
//
|
|
700
|
+
// The `servers` override is load-bearing: hono-openapi walks the
|
|
701
|
+
// BARE api router and emits paths relative to its own root (e.g.
|
|
702
|
+
// `/{id}`, `/{id}/notes`), without the `/api/<id>` prefix it ends
|
|
703
|
+
// up under in the user's outer router. We derive that prefix from
|
|
704
|
+
// `opts.openapi` (everything before the trailing `/openapi.json`)
|
|
705
|
+
// so combined Scalar URLs resolve to the real public paths.
|
|
706
|
+
if (advertiseOpenapi) {
|
|
707
|
+
const apiPrefix = opts.openapi!.replace(/\/openapi\.json$/, "") || "/";
|
|
708
|
+
const spec = await generateSpecs(startOpts.openapi!, {
|
|
709
|
+
documentation: {
|
|
710
|
+
info: {
|
|
711
|
+
title: meta.name,
|
|
712
|
+
version: "0.0.1",
|
|
713
|
+
description: meta.description,
|
|
714
|
+
},
|
|
715
|
+
servers: [{ url: apiPrefix }],
|
|
716
|
+
},
|
|
717
|
+
});
|
|
718
|
+
server.get(opts.openapi!, (c) => c.json(spec));
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// User's fetch handles everything else. The framework doesn't inject any
|
|
722
|
+
// context vars here — the user's router is expected to register the
|
|
723
|
+
// middlewares it needs (middleware.runtime, middleware.settings, …).
|
|
724
|
+
//
|
|
725
|
+
// env is threaded through so Bun-specific helpers inside the user's
|
|
726
|
+
// router still work — most importantly `upgradeWebSocket` from hono/bun,
|
|
727
|
+
// which reads the Bun server off `c.env`.
|
|
728
|
+
server.all("*", (c) => Promise.resolve(startOpts.fetch(c.req.raw, c.env)));
|
|
729
|
+
|
|
730
|
+
// Lifecycle
|
|
731
|
+
const cloudCtx: CloudLifecycleContext = {
|
|
732
|
+
logger,
|
|
733
|
+
settings: { get, set },
|
|
734
|
+
runtime: getCurrentRuntime(),
|
|
735
|
+
sync: processSync.sync,
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
let stopNotificationRegistration: (() => void) | undefined;
|
|
739
|
+
cleanup.push(() => stopNotificationRegistration?.());
|
|
740
|
+
const stopLifecycle = startOpts.lifecycle?.stop;
|
|
741
|
+
if (stopLifecycle) cleanup.push(() => stopLifecycle(cloudCtx));
|
|
742
|
+
|
|
743
|
+
if (!startOpts.skipSetup && startOpts.lifecycle?.setup) {
|
|
744
|
+
log.info(`Setup: ${meta.id}`);
|
|
745
|
+
await startOpts.lifecycle.setup(cloudCtx);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
stopNotificationRegistration = await startNotificationDefinitionRegistration(meta.id, notifications);
|
|
749
|
+
|
|
750
|
+
await loadSettingsCache();
|
|
751
|
+
|
|
752
|
+
if (startOpts.lifecycle?.start) {
|
|
753
|
+
log.info(`Start: ${meta.id}`);
|
|
754
|
+
await startOpts.lifecycle.start(cloudCtx);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Provision resources declared during application setup before exposing readiness.
|
|
758
|
+
await processSync.sync.ready();
|
|
759
|
+
|
|
760
|
+
// Only advertise after setup, workers, and their declared resources are ready.
|
|
761
|
+
// Publish supporting entries first; the app entry makes them discoverable.
|
|
762
|
+
await helpHeartbeat?.start();
|
|
763
|
+
await capabilityHeartbeat?.start();
|
|
764
|
+
await heartbeat.start();
|
|
765
|
+
log.info(`Registered "${meta.id}"`, { baseUrl });
|
|
766
|
+
|
|
767
|
+
// Graceful shutdown
|
|
768
|
+
let stopping = false;
|
|
769
|
+
const shutdown = async () => {
|
|
770
|
+
if (stopping) return;
|
|
771
|
+
stopping = true;
|
|
772
|
+
log.info(`Stopping: ${meta.id}`);
|
|
773
|
+
await stopStartedResources();
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
process.on("SIGTERM", () => void shutdown().then(() => process.exit(0)));
|
|
777
|
+
process.on("SIGINT", () => void shutdown().then(() => process.exit(0)));
|
|
778
|
+
|
|
779
|
+
return { hostname: "0.0.0.0", port, development: isDevelopment, fetch: server.fetch };
|
|
780
|
+
} catch (error) {
|
|
781
|
+
await stopStartedResources();
|
|
782
|
+
throw error;
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
return {
|
|
787
|
+
ssr,
|
|
788
|
+
plugin,
|
|
789
|
+
config,
|
|
790
|
+
meta,
|
|
791
|
+
baseUrl: opts.baseUrl,
|
|
792
|
+
start,
|
|
793
|
+
get sync() {
|
|
794
|
+
return getProcessSync();
|
|
795
|
+
},
|
|
796
|
+
// Phantom — see AppDefinition._settings doc. Do not read at runtime.
|
|
797
|
+
_settings: undefined as unknown as S,
|
|
798
|
+
settings: createSettingsAPI<S>(),
|
|
799
|
+
notifications,
|
|
800
|
+
};
|
|
801
|
+
};
|