@opengeni/api-router 3.0.1 → 3.1.2
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/dist/access-grant-rls.d.ts +8 -1
- package/dist/admission-trace.d.ts +8 -0
- package/dist/app.d.ts +50 -3
- package/dist/app.js +10 -1
- package/dist/artifact-catalog-cursor.d.ts +1 -0
- package/dist/attached-browser-viewer.d.ts +11 -0
- package/dist/auth/managed-auth-attempt-context.d.ts +2 -0
- package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +9 -1
- package/dist/auth/managed-auth.d.ts +72 -5
- package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
- package/dist/auth/new-signups-gate.d.ts +23 -0
- package/dist/auth/signup-funnel-metrics.d.ts +44 -0
- package/dist/azure-live.d.ts +28 -0
- package/dist/browser-action-fences.d.ts +13 -0
- package/dist/browser-controller-authority.d.ts +1 -61
- package/dist/browser-ephemeral.d.ts +5 -0
- package/dist/browser-lost-runtime-end.d.ts +24 -0
- package/dist/browser-state-authority.d.ts +1 -27
- package/dist/chunk-OGQDXVXY.js +18 -0
- package/dist/chunk-OGQDXVXY.js.map +1 -0
- package/dist/{chunk-AJ62T5QN.js → chunk-OT56OY4V.js} +80503 -39869
- package/dist/chunk-OT56OY4V.js.map +1 -0
- package/dist/claude-default-models.d.ts +19 -0
- package/dist/claude-subscription-core.d.ts +50 -0
- package/dist/claude-subscription-oauth.d.ts +36 -0
- package/dist/claude-subscription-profile.d.ts +9 -0
- package/dist/claude-subscription-usage.d.ts +6 -0
- package/dist/claude-workspace-connection.d.ts +15 -0
- package/dist/codemode.d.ts +40 -5
- package/dist/codex-realtime.d.ts +29 -3
- package/dist/codex-session-pointers.d.ts +19 -0
- package/dist/connection-ownership.d.ts +6 -3
- package/dist/controller-data-plane.d.ts +16 -0
- package/dist/credit-promotion-snapshot.d.ts +6 -0
- package/dist/editable-artifact-source-session.d.ts +3 -0
- package/dist/editable-artifact-websocket.d.ts +5 -3
- package/dist/fatal-process-boundary.d.ts +9 -1
- package/dist/github-access.d.ts +7 -1
- package/dist/github-browser-flow.d.ts +1 -1
- package/dist/http/acting-person.d.ts +36 -0
- package/dist/http/api-contract-compat.d.ts +19 -0
- package/dist/http/api-error.d.ts +50 -1
- package/dist/http/auth.d.ts +8 -1
- package/dist/http/cors.d.ts +12 -0
- package/dist/http/deprecation.d.ts +42 -0
- package/dist/http/keyed-admission.d.ts +12 -0
- package/dist/http/local-browser-boundary.d.ts +72 -0
- package/dist/http/metrics-listener.d.ts +22 -0
- package/dist/http/path-identifier.d.ts +17 -0
- package/dist/http/registered-route-label.d.ts +16 -0
- package/dist/http/rejection-telemetry.d.ts +34 -0
- package/dist/http/request-body.d.ts +27 -0
- package/dist/http/request-source.d.ts +41 -3
- package/dist/http/sse.d.ts +12 -1
- package/dist/http/unmatched-route.d.ts +18 -0
- package/dist/http/user-content.d.ts +36 -0
- package/dist/http/variable-sets-removed.d.ts +41 -0
- package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
- package/dist/index.js +1279 -108
- package/dist/index.js.map +1 -1
- package/dist/integration-connect-metrics.d.ts +35 -0
- package/dist/integration-endpoint-test.d.ts +20 -0
- package/dist/integrations/api-integrations.d.ts +2 -2
- package/dist/integrations/atlassian.d.ts +8 -3
- package/dist/integrations/fiken.d.ts +3 -1
- package/dist/integrations/github-lens-connect.d.ts +37 -66
- package/dist/integrations/google-drive.d.ts +2 -0
- package/dist/integrations/integration-commit-authority.d.ts +13 -0
- package/dist/integrations/marketplace-plugin.d.ts +43 -43
- package/dist/integrations/oauth-client.d.ts +171 -12
- package/dist/integrations/oauth-profiles.d.ts +42 -13
- package/dist/integrations/onepassword-vault-provider.d.ts +9 -0
- package/dist/integrations/slack-bot.d.ts +285 -7
- package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
- package/dist/integrations/slack-interactions.d.ts +89 -10
- package/dist/integrations/slack-mrkdwn.d.ts +41 -0
- package/dist/integrations/slack-routing.d.ts +1 -1
- package/dist/integrations/slack-session-defaults.d.ts +58 -0
- package/dist/integrations/slack-task-file-upload.d.ts +36 -0
- package/dist/integrations/slack-tool-review.d.ts +5 -0
- package/dist/interaction-agent-access.d.ts +13 -8
- package/dist/interaction-agent-version.d.ts +21 -0
- package/dist/interaction-holder-heartbeat.d.ts +5 -0
- package/dist/interaction-metrics.d.ts +27 -2
- package/dist/interaction-route-stages.d.ts +25 -0
- package/dist/interaction-secrets-vault.d.ts +20 -0
- package/dist/interaction-secrets.d.ts +103 -0
- package/dist/mcp/action-catalog-types.d.ts +14 -0
- package/dist/mcp/action-catalog.gen.d.ts +2 -0
- package/dist/mcp/action-descriptions.d.ts +4 -0
- package/dist/mcp/action-schema-imports.gen.d.ts +1 -0
- package/dist/mcp/capability-account-readiness.d.ts +7 -0
- package/dist/mcp/child-read-evidence.d.ts +9 -0
- package/dist/mcp/company-profile-agent-admin.d.ts +12 -0
- package/dist/mcp/contract-input.d.ts +13 -0
- package/dist/mcp/documents.d.ts +2 -0
- package/dist/mcp/first-party-tool-permissions.d.ts +3 -606
- package/dist/mcp/notification-tools.d.ts +26 -0
- package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
- package/dist/mcp/scheduled-task-input.d.ts +1309 -0
- package/dist/mcp/scheduled-task-view.d.ts +217 -3
- package/dist/mcp/secret-tools.d.ts +14 -0
- package/dist/mcp/server.d.ts +108 -5
- package/dist/mcp/session-admin-access-tools.d.ts +33 -0
- package/dist/mcp/session-event-errors.d.ts +28 -0
- package/dist/mcp/session-event-view.d.ts +62 -0
- package/dist/mcp/session-view.d.ts +15 -0
- package/dist/mcp/session-wait.d.ts +18 -0
- package/dist/mcp/site-agent-clamp-tools.d.ts +38 -0
- package/dist/mcp/site-data-tools.d.ts +22 -0
- package/dist/mcp/site-publication-tools.d.ts +19 -0
- package/dist/mcp/site-sql-tools.d.ts +27 -0
- package/dist/mcp-oauth.d.ts +37 -4
- package/dist/model-calls/chat-completions.d.ts +87 -0
- package/dist/model-calls/pending-usage-settlements.d.ts +57 -0
- package/dist/model-calls/service.d.ts +36 -0
- package/dist/model-catalog.d.ts +9 -3
- package/dist/native-push-dispatch.d.ts +69 -0
- package/dist/observability.d.ts +3 -1
- package/dist/organization-mcp.d.ts +82 -0
- package/dist/prepared-mcp-actor.d.ts +77 -0
- package/dist/prepared-mcp-permissions.d.ts +8 -0
- package/dist/routes/analytics-consent.d.ts +26 -0
- package/dist/routes/api-integrations.d.ts +11 -0
- package/dist/routes/api-keys.d.ts +7 -7
- package/dist/routes/automations.d.ts +2 -0
- package/dist/routes/billing.d.ts +59 -0
- package/dist/routes/browser-sessions.d.ts +65 -2
- package/dist/routes/chat-completions.d.ts +11 -0
- package/dist/routes/claude-core-usage.d.ts +20 -0
- package/dist/routes/claude-subscription-accounts.d.ts +3 -0
- package/dist/routes/claude-subscription-oauth.d.ts +3 -0
- package/dist/routes/client-errors.d.ts +182 -0
- package/dist/routes/codex-core.d.ts +401 -0
- package/dist/routes/codex.d.ts +64 -2
- package/dist/routes/computer-sessions.d.ts +6 -1
- package/dist/routes/desktop-distribution.d.ts +32 -0
- package/dist/routes/github-browser-fonts.gen.d.ts +2 -0
- package/dist/routes/github-browser-pages.d.ts +27 -0
- package/dist/routes/inbox.d.ts +13 -0
- package/dist/routes/insights-response-cache.d.ts +10 -0
- package/dist/routes/insights-usage.d.ts +12 -0
- package/dist/routes/insights.d.ts +28 -0
- package/dist/routes/interaction-placement-message.d.ts +7 -0
- package/dist/routes/managed-sign-in-methods.d.ts +6 -0
- package/dist/routes/model-account-choice.d.ts +13 -0
- package/dist/routes/model-account-settings.d.ts +19 -0
- package/dist/routes/native-app-auth.d.ts +12 -0
- package/dist/routes/organization-integration-policy.d.ts +8 -0
- package/dist/routes/organization-integrations.d.ts +6 -0
- package/dist/routes/organization-mcp-connections.d.ts +3 -0
- package/dist/routes/organization-model-defaults.d.ts +9 -0
- package/dist/routes/organization-service-accounts.d.ts +3 -0
- package/dist/routes/personal-api-keys.d.ts +3 -0
- package/dist/routes/session-admin-access.d.ts +3 -0
- package/dist/routes/session-artifact-associations.d.ts +4 -0
- package/dist/routes/session-history-imports.d.ts +20 -0
- package/dist/routes/sessions.d.ts +38 -2
- package/dist/routes/site-data.d.ts +18 -0
- package/dist/routes/site-publishing.d.ts +5 -0
- package/dist/routes/site-runtime.d.ts +31 -0
- package/dist/routes/site-sql.d.ts +16 -0
- package/dist/routes/slack-bot-access.d.ts +3 -0
- package/dist/routes/subscription-account-pools.d.ts +21 -0
- package/dist/routes/subscription-core-http.d.ts +5 -0
- package/dist/routes/subscription-pool-access.d.ts +37 -0
- package/dist/routes/subscription-pool-core.d.ts +153 -0
- package/dist/routes/supergrok.d.ts +6 -6
- package/dist/routes/usage-allowances.d.ts +12 -0
- package/dist/routes/variable-sets-removed.d.ts +2 -0
- package/dist/routes/vault-providers.d.ts +34 -0
- package/dist/routes/vaults.d.ts +3 -0
- package/dist/routes/workspace-integrations.d.ts +35 -0
- package/dist/routes/workspace-model-providers.d.ts +4 -0
- package/dist/routes/workspaces.d.ts +11 -2
- package/dist/sandbox/agent-update.d.ts +64 -0
- package/dist/sandbox/auth-callout.d.ts +8 -0
- package/dist/sandbox/channel-a.d.ts +15 -1
- package/dist/sandbox/enrollment.d.ts +26 -4
- package/dist/sandbox/machine-audience.d.ts +29 -0
- package/dist/sandbox/machines.d.ts +15 -2
- package/dist/sandbox/metrics-ingestion.d.ts +6 -2
- package/dist/sandbox/screen-control.d.ts +85 -0
- package/dist/sandbox/viewer.d.ts +5 -1
- package/dist/sandbox-file-artifacts.d.ts +17 -1
- package/dist/scheduled-task-blockers.d.ts +39 -0
- package/dist/scheduled-task-deletion.d.ts +3 -4
- package/dist/scheduled-task-lifecycle.d.ts +14 -0
- package/dist/secret-answers.d.ts +40 -0
- package/dist/session-create-observability.d.ts +5 -0
- package/dist/session-event-view-36GO3IL7.js +528 -0
- package/dist/session-event-view-36GO3IL7.js.map +1 -0
- package/dist/signed-state-secret.d.ts +18 -0
- package/dist/site-agent-guard.d.ts +53 -0
- package/dist/site-data/operations.d.ts +103 -0
- package/dist/site-data/service.d.ts +203 -0
- package/dist/site-frame-guard.d.ts +32 -0
- package/dist/site-publishing/agent-data-tools.d.ts +84 -0
- package/dist/site-publishing/agent-model.d.ts +58 -0
- package/dist/site-publishing/agent-session.d.ts +16 -0
- package/dist/site-publishing/content-scanner.d.ts +31 -0
- package/dist/site-publishing/edge-sync.d.ts +48 -0
- package/dist/site-publishing/gateway-signature.d.ts +56 -0
- package/dist/site-publishing/limits.d.ts +63 -0
- package/dist/site-publishing/page-tool-authority.d.ts +30 -0
- package/dist/site-publishing/platform-usage.d.ts +113 -0
- package/dist/site-publishing/platform.d.ts +21 -0
- package/dist/site-publishing/readiness.d.ts +62 -0
- package/dist/site-publishing/runtime.d.ts +179 -0
- package/dist/site-publishing/service.d.ts +264 -0
- package/dist/site-publishing/session-events.d.ts +22 -0
- package/dist/site-publishing/session-views.d.ts +36 -0
- package/dist/site-publishing/viewer.d.ts +40 -0
- package/dist/site-session-origin.d.ts +31 -6
- package/dist/site-sql/format.d.ts +47 -0
- package/dist/site-sql/gateway.d.ts +50 -0
- package/dist/site-sql/service.d.ts +213 -0
- package/dist/temporal-schedule-sync.d.ts +26 -0
- package/dist/transcription/billing-refusal.d.ts +8 -0
- package/dist/transcription/normalize.d.ts +23 -0
- package/dist/transcription/providers/azure-mai.d.ts +12 -0
- package/dist/transcription/providers/azure-openai.d.ts +4 -0
- package/dist/transcription/providers/openai.d.ts +16 -0
- package/dist/transcription/providers/subscription-core.d.ts +39 -0
- package/dist/transcription/service.d.ts +10 -1
- package/dist/transcription/wav-duration.d.ts +8 -0
- package/dist/workspace-tool-gateway-attestations.d.ts +37 -0
- package/dist/workspace-tool-gateway-observability.d.ts +16 -0
- package/dist/workspace-tool-gateway-secrets.d.ts +27 -0
- package/dist/workspace-tool-gateway.d.ts +102 -6
- package/dist/workspace-tool-target-fence.d.ts +116 -0
- package/dist/workspace-tool-target.d.ts +225 -0
- package/dist/workspace-webhook-dispatch.d.ts +26 -0
- package/dist/xai-subscription-core.d.ts +60 -0
- package/package.json +25 -20
- package/src/access-grant-rls.ts +64 -14
- package/src/admission-trace.ts +14 -0
- package/src/app.ts +1684 -252
- package/src/artifact-catalog-cursor.ts +1 -0
- package/src/attached-browser-viewer.ts +26 -0
- package/src/auth/managed-auth-attempt-context.ts +9 -0
- package/src/auth/managed-auth-rate-limits.ts +252 -0
- package/src/auth/managed-auth-session-adapter.ts +64 -29
- package/src/auth/managed-auth.ts +303 -35
- package/src/auth/managed-email.ts +18 -1
- package/src/auth/managed-sign-in-notifications.ts +81 -0
- package/src/auth/new-signups-gate.ts +49 -0
- package/src/auth/organization-user-setup.ts +3 -3
- package/src/auth/signup-funnel-metrics.ts +164 -0
- package/src/azure-live.ts +103 -0
- package/src/browser-action-fences.ts +45 -0
- package/src/browser-controller-authority.ts +1 -186
- package/src/browser-ephemeral.ts +39 -0
- package/src/browser-lost-runtime-end.ts +71 -0
- package/src/browser-state-authority.ts +1 -236
- package/src/claude-default-models.ts +37 -0
- package/src/claude-subscription-core.ts +154 -0
- package/src/claude-subscription-oauth.ts +352 -0
- package/src/claude-subscription-profile.ts +73 -0
- package/src/claude-subscription-usage.ts +51 -0
- package/src/claude-workspace-connection.ts +82 -0
- package/src/codemode.ts +236 -39
- package/src/codex-realtime.ts +181 -53
- package/src/codex-session-pointers.ts +80 -0
- package/src/company-brain-okf.ts +1 -1
- package/src/connected-machine-computer-access.ts +2 -1
- package/src/connection-ownership.ts +19 -13
- package/src/controller-data-plane.ts +34 -0
- package/src/credit-promotion-snapshot.ts +36 -0
- package/src/editable-artifact-production.ts +10 -0
- package/src/editable-artifact-source-session.ts +123 -0
- package/src/editable-artifact-websocket.ts +16 -3
- package/src/fatal-process-boundary.ts +67 -3
- package/src/gateway-realtime.ts +12 -8
- package/src/github-access.ts +31 -5
- package/src/github-browser-flow.ts +1 -1
- package/src/http/acting-person.ts +105 -0
- package/src/http/api-contract-compat.ts +49 -0
- package/src/http/api-error.ts +210 -2
- package/src/http/auth.ts +68 -4
- package/src/http/cors.ts +37 -5
- package/src/http/deprecation.ts +118 -0
- package/src/http/keyed-admission.ts +29 -0
- package/src/http/local-browser-boundary.ts +343 -0
- package/src/http/metrics-listener.ts +57 -0
- package/src/http/path-identifier.ts +54 -0
- package/src/http/registered-route-label.ts +34 -0
- package/src/http/rejection-telemetry.ts +120 -0
- package/src/http/request-body.ts +127 -0
- package/src/http/request-source.ts +154 -13
- package/src/http/sse.ts +88 -28
- package/src/http/unmatched-route.ts +60 -0
- package/src/http/user-content.ts +158 -0
- package/src/http/variable-sets-removed.ts +122 -0
- package/src/http/websocket-upgrade-dispatch.ts +29 -0
- package/src/index.ts +107 -57
- package/src/integration-connect-metrics.ts +228 -0
- package/src/integration-endpoint-test.ts +291 -0
- package/src/integrations/api-integrations.ts +81 -7
- package/src/integrations/atlassian.ts +202 -83
- package/src/integrations/fiken.ts +66 -38
- package/src/integrations/github-app-connect.ts +64 -18
- package/src/integrations/github-lens-connect.ts +8 -29
- package/src/integrations/google-drive.ts +448 -152
- package/src/integrations/integration-commit-authority.ts +69 -0
- package/src/integrations/marketplace-plugin.ts +1 -1
- package/src/integrations/oauth-client.ts +733 -193
- package/src/integrations/oauth-profiles.ts +116 -45
- package/src/integrations/onepassword-vault-provider.ts +16 -0
- package/src/integrations/personal-github-repositories.ts +1 -1
- package/src/integrations/personal-github.ts +61 -35
- package/src/integrations/provider-oauth.ts +67 -85
- package/src/integrations/slack-app-home.ts +5 -5
- package/src/integrations/slack-bot.ts +893 -201
- package/src/integrations/slack-file-upload-flow.ts +147 -0
- package/src/integrations/slack-install.ts +7 -7
- package/src/integrations/slack-interactions.ts +1589 -299
- package/src/integrations/slack-mrkdwn.ts +193 -0
- package/src/integrations/slack-routing.ts +2 -2
- package/src/integrations/slack-session-defaults.ts +224 -0
- package/src/integrations/slack-task-file-upload.ts +323 -0
- package/src/integrations/slack-tool-review.ts +65 -0
- package/src/integrations/social-oauth.ts +90 -58
- package/src/interaction-agent-access.ts +40 -20
- package/src/interaction-agent-version.ts +85 -0
- package/src/interaction-holder-heartbeat.ts +24 -8
- package/src/interaction-metrics.ts +164 -2
- package/src/interaction-route-stages.ts +94 -0
- package/src/interaction-secrets-vault.ts +213 -0
- package/src/interaction-secrets.ts +426 -0
- package/src/mcp/action-catalog-types.ts +14 -0
- package/src/mcp/action-catalog.gen.ts +8014 -0
- package/src/mcp/action-descriptions.ts +299 -0
- package/src/mcp/action-schema-imports.gen.ts +304 -0
- package/src/mcp/capability-account-readiness.ts +17 -0
- package/src/mcp/child-read-evidence.ts +19 -0
- package/src/mcp/company-profile-agent-admin.ts +13 -0
- package/src/mcp/contract-input.ts +165 -0
- package/src/mcp/documents.ts +9 -2
- package/src/mcp/editable-artifacts.ts +29 -11
- package/src/mcp/files.ts +5 -1
- package/src/mcp/first-party-tool-permissions.ts +7 -285
- package/src/mcp/knowledge-entries.ts +108 -34
- package/src/mcp/notification-tools.ts +480 -0
- package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
- package/src/mcp/scheduled-task-input.ts +89 -0
- package/src/mcp/scheduled-task-view.ts +63 -3
- package/src/mcp/secret-tools.ts +185 -0
- package/src/mcp/server.ts +2213 -1116
- package/src/mcp/session-admin-access-tools.ts +163 -0
- package/src/mcp/session-event-errors.ts +123 -0
- package/src/mcp/session-event-view.ts +395 -13
- package/src/mcp/session-view.ts +3 -0
- package/src/mcp/session-wait.ts +67 -7
- package/src/mcp/site-agent-clamp-tools.ts +140 -0
- package/src/mcp/site-data-tools.ts +339 -0
- package/src/mcp/site-publication-tools.ts +201 -0
- package/src/mcp/site-sql-tools.ts +427 -0
- package/src/mcp-oauth.ts +635 -45
- package/src/model-calls/chat-completions.ts +352 -0
- package/src/model-calls/pending-usage-settlements.ts +125 -0
- package/src/model-calls/service.ts +694 -0
- package/src/model-catalog.ts +68 -17
- package/src/native-push-dispatch.ts +457 -0
- package/src/observability.ts +15 -1
- package/src/organization-mcp.ts +485 -0
- package/src/prepared-mcp-actor.ts +154 -0
- package/src/prepared-mcp-permissions.ts +38 -0
- package/src/routes/analytics-consent.ts +123 -0
- package/src/routes/api-integrations.ts +268 -9
- package/src/routes/api-keys.ts +164 -25
- package/src/routes/artifact-catalog.ts +111 -0
- package/src/routes/automations.ts +163 -90
- package/src/routes/billing.ts +535 -59
- package/src/routes/browser-sessions.ts +966 -342
- package/src/routes/canonical-human-identities.ts +61 -0
- package/src/routes/capabilities.ts +112 -5
- package/src/routes/channels.ts +4 -3
- package/src/routes/chat-completions.ts +235 -0
- package/src/routes/claude-core-usage.ts +147 -0
- package/src/routes/claude-subscription-accounts.ts +233 -0
- package/src/routes/claude-subscription-oauth.ts +175 -0
- package/src/routes/client-errors.ts +394 -0
- package/src/routes/codex-core.ts +687 -0
- package/src/routes/codex.ts +1277 -1119
- package/src/routes/company-brain.ts +4 -0
- package/src/routes/computer-sessions.ts +319 -85
- package/src/routes/connect.ts +277 -175
- package/src/routes/connections.ts +690 -363
- package/src/routes/desktop-distribution.ts +217 -0
- package/src/routes/documents.ts +13 -7
- package/src/routes/editable-artifacts.ts +74 -4
- package/src/routes/enrollments.ts +110 -16
- package/src/routes/external-identity-links.ts +13 -12
- package/src/routes/files.ts +41 -16
- package/src/routes/github-browser-fonts.gen.ts +12 -0
- package/src/routes/github-browser-pages.ts +258 -0
- package/src/routes/github.ts +200 -75
- package/src/routes/inbox.ts +353 -0
- package/src/routes/insights-response-cache.ts +83 -0
- package/src/routes/insights-usage.ts +345 -0
- package/src/routes/insights.ts +103 -8
- package/src/routes/install.ts +51 -10
- package/src/routes/integration-facets.ts +70 -28
- package/src/routes/interaction-placement-message.ts +12 -0
- package/src/routes/knowledge.ts +136 -27
- package/src/routes/machines.ts +189 -135
- package/src/routes/managed-auth-session-sets.ts +43 -10
- package/src/routes/managed-onboarding.ts +39 -19
- package/src/routes/managed-sign-in-methods.ts +433 -0
- package/src/routes/memory-slack-publications.ts +3 -2
- package/src/routes/model-account-choice.ts +194 -0
- package/src/routes/model-account-settings.ts +235 -0
- package/src/routes/model-connection-access.ts +210 -48
- package/src/routes/native-app-auth.ts +296 -0
- package/src/routes/organization-integration-policy.ts +116 -0
- package/src/routes/organization-integrations.ts +326 -0
- package/src/routes/organization-mcp-connections.ts +183 -0
- package/src/routes/organization-memberships.ts +205 -19
- package/src/routes/organization-model-defaults.ts +110 -0
- package/src/routes/organization-model-providers.ts +74 -3
- package/src/routes/organization-service-accounts.ts +128 -0
- package/src/routes/organization-sessions.ts +21 -5
- package/src/routes/personal-api-keys.ts +184 -0
- package/src/routes/personal-github-git-broker.ts +2 -2
- package/src/routes/personal-github.ts +9 -4
- package/src/routes/plugins.ts +46 -7
- package/src/routes/pr-review-github.ts +105 -118
- package/src/routes/pr-review.ts +88 -82
- package/src/routes/preference-registry.ts +18 -0
- package/src/routes/rigs.ts +48 -13
- package/src/routes/scheduled-tasks.ts +347 -27
- package/src/routes/session-admin-access.ts +271 -0
- package/src/routes/session-artifact-associations.ts +164 -0
- package/src/routes/session-history-imports.ts +305 -0
- package/src/routes/sessions.ts +1682 -363
- package/src/routes/site-data.ts +340 -0
- package/src/routes/site-publishing.ts +584 -0
- package/src/routes/site-runtime.ts +1944 -0
- package/src/routes/site-sql.ts +263 -0
- package/src/routes/skill-content.ts +72 -7
- package/src/routes/skills.ts +5 -4
- package/src/routes/slack-bot-access.ts +108 -0
- package/src/routes/social.ts +27 -15
- package/src/routes/subscription-account-pools.ts +209 -0
- package/src/routes/subscription-core-http.ts +18 -0
- package/src/routes/subscription-pool-access.ts +180 -0
- package/src/routes/subscription-pool-core.ts +552 -0
- package/src/routes/supergrok.ts +174 -655
- package/src/routes/transcription-recordings.ts +71 -4
- package/src/routes/transcriptions.ts +26 -2
- package/src/routes/usage-allowances.ts +381 -0
- package/src/routes/variable-sets-removed.ts +19 -0
- package/src/routes/vault-providers.ts +782 -0
- package/src/routes/vaults.ts +421 -0
- package/src/routes/video-generation.ts +30 -12
- package/src/routes/workspace-artifacts.ts +165 -53
- package/src/routes/workspace-integrations.ts +576 -0
- package/src/routes/workspace-model-providers.ts +148 -0
- package/src/routes/workspace-state.ts +4 -0
- package/src/routes/workspaces.ts +625 -117
- package/src/sandbox/agent-update.ts +315 -0
- package/src/sandbox/auth-callout.ts +104 -21
- package/src/sandbox/channel-a.ts +165 -55
- package/src/sandbox/enrollment.ts +131 -22
- package/src/sandbox/machine-audience.ts +192 -0
- package/src/sandbox/machines.ts +75 -31
- package/src/sandbox/metrics-ingestion.ts +133 -2
- package/src/sandbox/rematerialize.ts +51 -21
- package/src/sandbox/screen-control.ts +289 -0
- package/src/sandbox/viewer.ts +33 -58
- package/src/sandbox-file-artifacts.ts +53 -27
- package/src/scheduled-task-blockers.ts +293 -0
- package/src/scheduled-task-deletion.ts +13 -3
- package/src/scheduled-task-lifecycle.ts +84 -0
- package/src/secret-answers.ts +119 -0
- package/src/session-create-observability.ts +46 -0
- package/src/session-realtime-context.ts +26 -4
- package/src/signed-state-secret.ts +58 -0
- package/src/site-agent-guard.ts +261 -0
- package/src/site-data/operations.ts +777 -0
- package/src/site-data/service.ts +1043 -0
- package/src/site-frame-guard.ts +107 -0
- package/src/site-publishing/agent-data-tools.ts +250 -0
- package/src/site-publishing/agent-model.ts +127 -0
- package/src/site-publishing/agent-session.ts +148 -0
- package/src/site-publishing/content-scanner.ts +93 -0
- package/src/site-publishing/edge-sync.ts +148 -0
- package/src/site-publishing/gateway-signature.ts +198 -0
- package/src/site-publishing/limits.ts +315 -0
- package/src/site-publishing/page-tool-authority.ts +111 -0
- package/src/site-publishing/platform-usage.ts +479 -0
- package/src/site-publishing/platform.ts +98 -0
- package/src/site-publishing/readiness.ts +330 -0
- package/src/site-publishing/runtime.ts +831 -0
- package/src/site-publishing/service.ts +1282 -0
- package/src/site-publishing/session-events.ts +262 -0
- package/src/site-publishing/session-views.ts +262 -0
- package/src/site-publishing/viewer.ts +75 -0
- package/src/site-session-origin.ts +91 -6
- package/src/site-sql/format.ts +134 -0
- package/src/site-sql/gateway.ts +168 -0
- package/src/site-sql/service.ts +1636 -0
- package/src/site-uploads.ts +27 -5
- package/src/temporal-schedule-sync.ts +74 -0
- package/src/transcription/billing-refusal.ts +14 -0
- package/src/transcription/normalize.ts +77 -0
- package/src/transcription/providers/azure-mai.ts +98 -0
- package/src/transcription/providers/azure-openai.ts +16 -7
- package/src/transcription/providers/codex-subscription.ts +190 -67
- package/src/transcription/providers/openai.ts +79 -5
- package/src/transcription/providers/subscription-core.ts +125 -0
- package/src/transcription/providers/xai-subscription.ts +81 -83
- package/src/transcription/segmenter.ts +7 -1
- package/src/transcription/service.ts +239 -10
- package/src/transcription/wav-duration.ts +39 -0
- package/src/workspace-deletion.ts +25 -1
- package/src/workspace-tool-gateway-attestations.ts +133 -0
- package/src/workspace-tool-gateway-observability.ts +64 -0
- package/src/workspace-tool-gateway-secrets.ts +104 -0
- package/src/workspace-tool-gateway.ts +921 -103
- package/src/workspace-tool-target-fence.ts +380 -0
- package/src/workspace-tool-target.ts +554 -0
- package/src/workspace-webhook-dispatch.ts +225 -0
- package/src/xai-realtime.ts +85 -81
- package/src/xai-subscription-core.ts +172 -0
- package/dist/browser-auth-broker.d.ts +0 -20
- package/dist/chunk-AJ62T5QN.js.map +0 -1
- package/dist/connection-authority-owner.d.ts +0 -59
- package/dist/routes/connection-authorities.d.ts +0 -3
- package/dist/routes/environments.d.ts +0 -5
- package/dist/routes/host-mcp-bindings.d.ts +0 -5
- package/dist/routes/host-mcp-resolvers.d.ts +0 -3
- package/dist/routes/packs.d.ts +0 -3
- package/dist/session-event-view-MU3HMNV3.js +0 -264
- package/dist/session-event-view-MU3HMNV3.js.map +0 -1
- package/dist/xai-subscription-auth.d.ts +0 -23
- package/src/browser-auth-broker.ts +0 -155
- package/src/connection-authority-owner.ts +0 -61
- package/src/routes/connection-authorities.ts +0 -139
- package/src/routes/environments.ts +0 -388
- package/src/routes/host-mcp-bindings.ts +0 -122
- package/src/routes/host-mcp-resolvers.ts +0 -41
- package/src/routes/packs.ts +0 -784
- package/src/xai-subscription-auth.ts +0 -139
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
import type { ApiRouteDeps } from "@opengeni/core";
|
|
2
|
+
import type {
|
|
3
|
+
SiteDataCollectionDefinitionRequest,
|
|
4
|
+
SiteDataDescribeResponse,
|
|
5
|
+
SiteDataFileScope,
|
|
6
|
+
SiteDataImportRequest,
|
|
7
|
+
SiteDataQueryRequest,
|
|
8
|
+
SiteDataWriteOperation,
|
|
9
|
+
} from "@opengeni/contracts";
|
|
10
|
+
import {
|
|
11
|
+
parseSiteDataPath,
|
|
12
|
+
readSiteDataPath,
|
|
13
|
+
SiteDataError,
|
|
14
|
+
SITE_DATA_MAX_BATCH,
|
|
15
|
+
type SiteDataFile,
|
|
16
|
+
type SiteDataRecord,
|
|
17
|
+
type SiteDataTouchedRecord,
|
|
18
|
+
type SiteDataValidationReservation,
|
|
19
|
+
type SiteDataWriteOp,
|
|
20
|
+
} from "@opengeni/db";
|
|
21
|
+
import { userContentSignedGetUrlOptions } from "../http/user-content";
|
|
22
|
+
import { requireSitePlatformQuota } from "../site-publishing/platform-usage";
|
|
23
|
+
import {
|
|
24
|
+
assertSiteDataSchema,
|
|
25
|
+
meterSiteData,
|
|
26
|
+
publicSiteDataLimits,
|
|
27
|
+
resolveSiteDataAccess,
|
|
28
|
+
sha256Hex,
|
|
29
|
+
prevalidateSiteDataWrite,
|
|
30
|
+
siteDataFileContentType,
|
|
31
|
+
siteDataFileName,
|
|
32
|
+
siteDataObjectKey,
|
|
33
|
+
siteDataValidationBytes,
|
|
34
|
+
siteDataValidationClock,
|
|
35
|
+
siteDataValidationReservationMs,
|
|
36
|
+
SITE_DATA_VALIDATION_BUDGET_MS,
|
|
37
|
+
SITE_DATA_VALIDATION_RESERVE_MS_PER_BYTE,
|
|
38
|
+
type SiteDataAccess,
|
|
39
|
+
type SiteDataAuthority,
|
|
40
|
+
type SiteDataDeps,
|
|
41
|
+
type SiteDataSurface,
|
|
42
|
+
type SiteDataValidationMeter,
|
|
43
|
+
} from "./service";
|
|
44
|
+
|
|
45
|
+
/** Shared Site Data operations for HTTP routes, the Site bridge and agent tools. */
|
|
46
|
+
|
|
47
|
+
export const SITE_DATA_EXPORT_MAX_RECORDS = 10_000;
|
|
48
|
+
export const SITE_DATA_IMPORT_MAX_RECORDS = 1_000;
|
|
49
|
+
/** CSV imports accept at most this many columns. */
|
|
50
|
+
export const SITE_DATA_IMPORT_MAX_COLUMNS = 1_000;
|
|
51
|
+
/** CSV exports name at most this many data columns; wider data goes in one JSON column. */
|
|
52
|
+
export const SITE_DATA_EXPORT_MAX_COLUMNS = 200;
|
|
53
|
+
/** Exports stop adding records past this many bytes and report truncation. */
|
|
54
|
+
export const SITE_DATA_EXPORT_MAX_BYTES = 16 * 1024 * 1024;
|
|
55
|
+
const FILE_URL_SECONDS = 300;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Count an attempt before any work, in its own transaction, so failed and
|
|
59
|
+
* refused attempts count too. Writes always count: viewers and Site frames
|
|
60
|
+
* per person and per Site, managers in one generous window per Site. Reads
|
|
61
|
+
* count only for Site frames.
|
|
62
|
+
*
|
|
63
|
+
* A write that will validate (`validates`: the collections whose records it
|
|
64
|
+
* checks, or a schema it defines, and the bytes of JSON it will check) also
|
|
65
|
+
* reserves validation time sized to those bytes, capped at the request
|
|
66
|
+
* budget, on the Site's and workspace's validation windows in the same
|
|
67
|
+
* transaction; it returns the reservation, for meteredWrite to settle.
|
|
68
|
+
*/
|
|
69
|
+
async function consume(
|
|
70
|
+
access: SiteDataAccess,
|
|
71
|
+
kind: "write" | "read",
|
|
72
|
+
operations: number,
|
|
73
|
+
validates?: {
|
|
74
|
+
collections?: readonly string[];
|
|
75
|
+
schema?: boolean;
|
|
76
|
+
bytes: number;
|
|
77
|
+
touched?: readonly SiteDataTouchedRecord[];
|
|
78
|
+
},
|
|
79
|
+
): Promise<SiteDataValidationReservation> {
|
|
80
|
+
if (kind === "read" && !access.caller.framed) return NOTHING_RESERVED;
|
|
81
|
+
return access.store.consumeRate(
|
|
82
|
+
access.caller,
|
|
83
|
+
kind,
|
|
84
|
+
Math.max(1, operations),
|
|
85
|
+
access.limits,
|
|
86
|
+
validates
|
|
87
|
+
? {
|
|
88
|
+
...(validates.collections ? { collections: validates.collections } : {}),
|
|
89
|
+
...(validates.schema !== undefined ? { schema: validates.schema } : {}),
|
|
90
|
+
reserveMs: siteDataValidationReservationMs(validates.bytes),
|
|
91
|
+
...(validates.touched?.length
|
|
92
|
+
? {
|
|
93
|
+
touched: validates.touched,
|
|
94
|
+
reserveMsPerStoredByte: SITE_DATA_VALIDATION_RESERVE_MS_PER_BYTE,
|
|
95
|
+
maxReserveMs: SITE_DATA_VALIDATION_BUDGET_MS,
|
|
96
|
+
}
|
|
97
|
+
: {}),
|
|
98
|
+
}
|
|
99
|
+
: undefined,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const NOTHING_RESERVED: SiteDataValidationReservation = { reservedMs: 0, windowStartedAt: null };
|
|
104
|
+
|
|
105
|
+
/** The record data these operations carry to be validated. */
|
|
106
|
+
function* validatedData(operations: readonly unknown[]): Iterable<unknown> {
|
|
107
|
+
for (const op of operations)
|
|
108
|
+
if (
|
|
109
|
+
op !== null &&
|
|
110
|
+
typeof op === "object" &&
|
|
111
|
+
["insert", "put", "patch"].includes(String((op as { op?: unknown }).op))
|
|
112
|
+
)
|
|
113
|
+
yield (op as { data?: unknown }).data;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The stored records patches and increments merge into and validate whole. */
|
|
117
|
+
function mergedRecords(operations: readonly unknown[]): SiteDataTouchedRecord[] {
|
|
118
|
+
const touched: SiteDataTouchedRecord[] = [];
|
|
119
|
+
for (const op of operations) {
|
|
120
|
+
if (op === null || typeof op !== "object") continue;
|
|
121
|
+
const { op: kind, collection, id, key } = op as Record<string, unknown>;
|
|
122
|
+
if ((kind !== "patch" && kind !== "increment") || typeof collection !== "string") continue;
|
|
123
|
+
if (typeof id === "string") touched.push({ collection, id });
|
|
124
|
+
else if (typeof key === "string") touched.push({ collection, key });
|
|
125
|
+
}
|
|
126
|
+
return touched;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Collections whose records these operations validate. */
|
|
130
|
+
function validatedCollections(operations: readonly unknown[]): string[] {
|
|
131
|
+
const names = new Set<string>();
|
|
132
|
+
for (const op of operations)
|
|
133
|
+
if (
|
|
134
|
+
op !== null &&
|
|
135
|
+
typeof op === "object" &&
|
|
136
|
+
typeof (op as { collection?: unknown }).collection === "string" &&
|
|
137
|
+
["insert", "put", "patch", "increment"].includes(String((op as { op?: unknown }).op))
|
|
138
|
+
)
|
|
139
|
+
names.add((op as { collection: string }).collection);
|
|
140
|
+
return [...names];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Settle the validation time a request reserved against what it spent,
|
|
145
|
+
* whatever its outcome: refund or charge the difference on the validation
|
|
146
|
+
* windows and add operations for the time spent to the caller's write
|
|
147
|
+
* windows. Nothing reserved and nothing spent touches nothing. A failure to
|
|
148
|
+
* settle is logged, never reported in place of the request's own result.
|
|
149
|
+
*/
|
|
150
|
+
async function settleValidation(
|
|
151
|
+
access: SiteDataAccess,
|
|
152
|
+
reservation: SiteDataValidationReservation,
|
|
153
|
+
meter: SiteDataValidationMeter,
|
|
154
|
+
) {
|
|
155
|
+
if (!(reservation.reservedMs > 0) && !(meter.ms > 0)) return;
|
|
156
|
+
try {
|
|
157
|
+
await access.store.settleValidation(
|
|
158
|
+
access.caller,
|
|
159
|
+
{ ...reservation, spentMs: meter.ms },
|
|
160
|
+
access.limits,
|
|
161
|
+
);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.warn("[site-data] could not settle validation time", {
|
|
164
|
+
siteId: access.caller.siteId,
|
|
165
|
+
reservedMs: reservation.reservedMs,
|
|
166
|
+
validationMs: meter.ms,
|
|
167
|
+
error: error instanceof Error ? error.message : String(error),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Run a write whose validation time is metered, then settle it against the
|
|
174
|
+
* reservation, including for refused and failed writes.
|
|
175
|
+
*/
|
|
176
|
+
async function meteredWrite<T>(
|
|
177
|
+
access: SiteDataAccess,
|
|
178
|
+
reservation: SiteDataValidationReservation,
|
|
179
|
+
run: (meter: SiteDataValidationMeter) => Promise<T>,
|
|
180
|
+
): Promise<T> {
|
|
181
|
+
const meter: SiteDataValidationMeter = { ms: 0 };
|
|
182
|
+
try {
|
|
183
|
+
return await run(meter);
|
|
184
|
+
} finally {
|
|
185
|
+
await settleValidation(access, reservation, meter);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function logCleanupFailure(what: string, objectKey: string, error: unknown): void {
|
|
190
|
+
console.warn(`[site-data] ${what} left an unreachable object`, {
|
|
191
|
+
objectKey,
|
|
192
|
+
error: error instanceof Error ? error.message : String(error),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function describeSiteData(
|
|
197
|
+
deps: SiteDataDeps,
|
|
198
|
+
grant: SiteDataAuthority,
|
|
199
|
+
siteId: string,
|
|
200
|
+
): Promise<SiteDataDescribeResponse> {
|
|
201
|
+
const access = await resolveSiteDataAccess(deps, grant, siteId, { management: true });
|
|
202
|
+
const described = await access.store.describe(access.caller);
|
|
203
|
+
return { siteId, ...described, limits: publicSiteDataLimits(access.limits) };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function defineSiteDataCollection(
|
|
207
|
+
deps: SiteDataDeps,
|
|
208
|
+
grant: SiteDataAuthority,
|
|
209
|
+
siteId: string,
|
|
210
|
+
name: string,
|
|
211
|
+
definition: SiteDataCollectionDefinitionRequest,
|
|
212
|
+
) {
|
|
213
|
+
const access = await resolveSiteDataAccess(deps, grant, siteId, { management: true });
|
|
214
|
+
const hasSchema = definition.jsonSchema !== undefined && definition.jsonSchema !== null;
|
|
215
|
+
const reserved = await consume(access, "write", 1, {
|
|
216
|
+
schema: hasSchema,
|
|
217
|
+
bytes: hasSchema ? siteDataValidationBytes([definition.jsonSchema]) : 0,
|
|
218
|
+
});
|
|
219
|
+
// Only a schema is checked and metered; defining without one touches no
|
|
220
|
+
// validation window.
|
|
221
|
+
if (hasSchema)
|
|
222
|
+
await meteredWrite(access, reserved, async (meter) => {
|
|
223
|
+
const started = siteDataValidationClock.now();
|
|
224
|
+
try {
|
|
225
|
+
assertSiteDataSchema(definition.jsonSchema);
|
|
226
|
+
} finally {
|
|
227
|
+
meter.ms += Math.max(0, siteDataValidationClock.now() - started);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
return access.store.defineCollection(
|
|
231
|
+
access.caller,
|
|
232
|
+
{
|
|
233
|
+
name,
|
|
234
|
+
accessMode: definition.accessMode,
|
|
235
|
+
...(definition.privateRecords === undefined
|
|
236
|
+
? {}
|
|
237
|
+
: { privateRecords: definition.privateRecords }),
|
|
238
|
+
...(definition.guestAccess === undefined ? {} : { guestAccess: definition.guestAccess }),
|
|
239
|
+
...(definition.jsonSchema === undefined ? {} : { jsonSchema: definition.jsonSchema }),
|
|
240
|
+
...(definition.indexedFields ? { indexedFields: definition.indexedFields } : {}),
|
|
241
|
+
...(definition.uniqueFields ? { uniqueFields: definition.uniqueFields } : {}),
|
|
242
|
+
},
|
|
243
|
+
access.limits,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export async function deleteSiteDataCollection(
|
|
248
|
+
deps: SiteDataDeps,
|
|
249
|
+
grant: SiteDataAuthority,
|
|
250
|
+
siteId: string,
|
|
251
|
+
name: string,
|
|
252
|
+
) {
|
|
253
|
+
const access = await resolveSiteDataAccess(deps, grant, siteId, { management: true });
|
|
254
|
+
await consume(access, "write", 1);
|
|
255
|
+
return access.store.deleteCollection(access.caller, name);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function resetSiteDataCollection(
|
|
259
|
+
deps: SiteDataDeps,
|
|
260
|
+
grant: SiteDataAuthority,
|
|
261
|
+
siteId: string,
|
|
262
|
+
name: string,
|
|
263
|
+
) {
|
|
264
|
+
const access = await resolveSiteDataAccess(deps, grant, siteId, { management: true });
|
|
265
|
+
await consume(access, "write", 1);
|
|
266
|
+
return access.store.resetCollection(access.caller, name);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export async function querySiteData(
|
|
270
|
+
deps: SiteDataDeps,
|
|
271
|
+
grant: SiteDataAuthority,
|
|
272
|
+
siteId: string,
|
|
273
|
+
query: SiteDataQueryRequest,
|
|
274
|
+
surface: SiteDataSurface = {},
|
|
275
|
+
) {
|
|
276
|
+
const access = await resolveSiteDataAccess(deps, grant, siteId, surface);
|
|
277
|
+
await consume(access, "read", 1);
|
|
278
|
+
return access.store.query(access.caller, {
|
|
279
|
+
collection: query.collection,
|
|
280
|
+
...(query.where ? { where: query.where } : {}),
|
|
281
|
+
...(query.sort ? { sort: query.sort } : {}),
|
|
282
|
+
...(query.limit === undefined ? {} : { limit: query.limit }),
|
|
283
|
+
...(query.cursor ? { cursor: query.cursor } : {}),
|
|
284
|
+
...(query.count ? { count: true } : {}),
|
|
285
|
+
...(query.scope ? { scope: query.scope } : {}),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function getSiteDataRecord(
|
|
290
|
+
deps: SiteDataDeps,
|
|
291
|
+
grant: SiteDataAuthority,
|
|
292
|
+
siteId: string,
|
|
293
|
+
request: { collection: string; id?: string | undefined; key?: string | undefined },
|
|
294
|
+
surface: SiteDataSurface = {},
|
|
295
|
+
): Promise<SiteDataRecord | null> {
|
|
296
|
+
const access = await resolveSiteDataAccess(deps, grant, siteId, surface);
|
|
297
|
+
await consume(access, "read", 1);
|
|
298
|
+
return access.store.get(
|
|
299
|
+
access.caller,
|
|
300
|
+
request.collection,
|
|
301
|
+
request.id !== undefined ? { id: request.id } : { key: request.key! },
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export async function writeSiteData(
|
|
306
|
+
deps: SiteDataDeps,
|
|
307
|
+
grant: SiteDataAuthority,
|
|
308
|
+
siteId: string,
|
|
309
|
+
operations: SiteDataWriteOperation[],
|
|
310
|
+
surface: SiteDataSurface = {},
|
|
311
|
+
) {
|
|
312
|
+
const access = await resolveSiteDataAccess(deps, grant, siteId, surface);
|
|
313
|
+
await requireSitePlatformQuota(deps, access.caller, ["storage_bytes"]);
|
|
314
|
+
const reserved = await consume(access, "write", operations.length, {
|
|
315
|
+
collections: validatedCollections(operations),
|
|
316
|
+
bytes: siteDataValidationBytes(validatedData(operations)),
|
|
317
|
+
touched: mergedRecords(operations),
|
|
318
|
+
});
|
|
319
|
+
return meteredWrite(access, reserved, async (meter) => {
|
|
320
|
+
const options = await prevalidateSiteDataWrite(access, operations, {
|
|
321
|
+
meter,
|
|
322
|
+
reservedMs: reserved.reservedMs,
|
|
323
|
+
});
|
|
324
|
+
return access.store.write(access.caller, operations as SiteDataWriteOp[], options);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// --- import and export -------------------------------------------------------
|
|
329
|
+
|
|
330
|
+
/** RFC 4180 CSV: quoted fields, doubled quotes, CRLF or LF rows. */
|
|
331
|
+
/**
|
|
332
|
+
* Parse CSV text into rows (the first is the header). Optional limits stop
|
|
333
|
+
* parsing as soon as a row has too many cells or there are too many records,
|
|
334
|
+
* before any record is built.
|
|
335
|
+
*/
|
|
336
|
+
export function parseSiteDataCsv(
|
|
337
|
+
text: string,
|
|
338
|
+
limits: { maxColumns?: number; maxRecords?: number } = {},
|
|
339
|
+
): string[][] {
|
|
340
|
+
const maxColumns = limits.maxColumns ?? Number.POSITIVE_INFINITY;
|
|
341
|
+
// One header row plus the records.
|
|
342
|
+
const maxRows = (limits.maxRecords ?? Number.POSITIVE_INFINITY) + 1;
|
|
343
|
+
const rows: string[][] = [];
|
|
344
|
+
let row: string[] = [];
|
|
345
|
+
let field = "";
|
|
346
|
+
let quoted = false;
|
|
347
|
+
const pushField = () => {
|
|
348
|
+
if (row.length >= maxColumns)
|
|
349
|
+
throw new SiteDataError("validation", `The CSV has more than ${maxColumns} columns in a row`);
|
|
350
|
+
row.push(field);
|
|
351
|
+
field = "";
|
|
352
|
+
};
|
|
353
|
+
const pushRow = () => {
|
|
354
|
+
if (!row.some((value) => value !== "")) return;
|
|
355
|
+
if (rows.length >= maxRows)
|
|
356
|
+
throw new SiteDataError("validation", `Import at most ${maxRows - 1} records at a time`);
|
|
357
|
+
rows.push(row);
|
|
358
|
+
};
|
|
359
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
360
|
+
const char = text[index]!;
|
|
361
|
+
if (quoted) {
|
|
362
|
+
if (char === '"') {
|
|
363
|
+
if (text[index + 1] === '"') {
|
|
364
|
+
field += '"';
|
|
365
|
+
index += 1;
|
|
366
|
+
} else quoted = false;
|
|
367
|
+
} else field += char;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (char === '"' && field === "") quoted = true;
|
|
371
|
+
else if (char === ",") pushField();
|
|
372
|
+
else if (char === "\n" || char === "\r") {
|
|
373
|
+
if (char === "\r" && text[index + 1] === "\n") index += 1;
|
|
374
|
+
pushField();
|
|
375
|
+
pushRow();
|
|
376
|
+
row = [];
|
|
377
|
+
} else field += char;
|
|
378
|
+
}
|
|
379
|
+
if (quoted) throw new SiteDataError("validation", "The CSV has an unterminated quoted field");
|
|
380
|
+
pushField();
|
|
381
|
+
pushRow();
|
|
382
|
+
return rows;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Build one record from CSV cells in a single mutable null-prototype object:
|
|
387
|
+
* linear in the number of cells, never copying the row per cell. Paths are
|
|
388
|
+
* validated field names, so no segment can reach a prototype.
|
|
389
|
+
*/
|
|
390
|
+
function siteDataCsvRecord(paths: readonly string[][], cells: readonly string[]) {
|
|
391
|
+
const define = (target: Record<string, unknown>, key: string, value: unknown) =>
|
|
392
|
+
Object.defineProperty(target, key, {
|
|
393
|
+
value,
|
|
394
|
+
enumerable: true,
|
|
395
|
+
writable: true,
|
|
396
|
+
configurable: true,
|
|
397
|
+
});
|
|
398
|
+
const record = Object.create(null) as Record<string, unknown>;
|
|
399
|
+
paths.forEach((segments, column) => {
|
|
400
|
+
let current = record;
|
|
401
|
+
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
402
|
+
const segment = segments[index]!;
|
|
403
|
+
const next = Object.hasOwn(current, segment) ? current[segment] : undefined;
|
|
404
|
+
if (typeof next === "object" && next !== null && !Array.isArray(next))
|
|
405
|
+
current = next as Record<string, unknown>;
|
|
406
|
+
else {
|
|
407
|
+
const child = Object.create(null) as Record<string, unknown>;
|
|
408
|
+
define(current, segment, child);
|
|
409
|
+
current = child;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
define(current, segments[segments.length - 1]!, cells[column] ?? "");
|
|
413
|
+
});
|
|
414
|
+
return record;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function siteDataCsvCell(value: unknown): string {
|
|
418
|
+
if (value === undefined || value === null) return "";
|
|
419
|
+
let text = typeof value === "string" ? value : JSON.stringify(value);
|
|
420
|
+
// Spreadsheet formula injection: stored strings are untrusted. Numbers
|
|
421
|
+
// (including negative ones) and other JSON values are left as they are.
|
|
422
|
+
if (typeof value === "string" && /^[=+\-@\t\r]/u.test(text)) text = `'${text}`;
|
|
423
|
+
return /[",\r\n]/u.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Turn import CSV into records: the header names dotted data paths. Column
|
|
428
|
+
* and row caps apply while parsing, before any record is built, and each
|
|
429
|
+
* record is built in one pass.
|
|
430
|
+
*/
|
|
431
|
+
export function siteDataCsvRecords(csv: string): Record<string, unknown>[] {
|
|
432
|
+
const rows = parseSiteDataCsv(csv, {
|
|
433
|
+
maxColumns: SITE_DATA_IMPORT_MAX_COLUMNS,
|
|
434
|
+
maxRecords: SITE_DATA_IMPORT_MAX_RECORDS,
|
|
435
|
+
});
|
|
436
|
+
const [header, ...body] = rows;
|
|
437
|
+
if (!header || header.length === 0)
|
|
438
|
+
throw new SiteDataError("validation", "The CSV needs a header row");
|
|
439
|
+
// parseSiteDataPath refuses __proto__, prototype and constructor.
|
|
440
|
+
const paths = header.map((name) => {
|
|
441
|
+
const segments = parseSiteDataPath(name);
|
|
442
|
+
if (!segments) throw new SiteDataError("validation", `Invalid CSV column name: ${name}`);
|
|
443
|
+
return segments;
|
|
444
|
+
});
|
|
445
|
+
return body.map((cells) => siteDataCsvRecord(paths, cells));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export async function importSiteData(
|
|
449
|
+
deps: SiteDataDeps,
|
|
450
|
+
grant: SiteDataAuthority,
|
|
451
|
+
siteId: string,
|
|
452
|
+
collection: string,
|
|
453
|
+
request: SiteDataImportRequest,
|
|
454
|
+
): Promise<{ imported: number }> {
|
|
455
|
+
const access = await resolveSiteDataAccess(deps, grant, siteId, { management: true });
|
|
456
|
+
// Counted before parsing: at most one record per CSV line after the header.
|
|
457
|
+
const reserved = await consume(
|
|
458
|
+
access,
|
|
459
|
+
"write",
|
|
460
|
+
request.format === "csv"
|
|
461
|
+
? Math.min(SITE_DATA_IMPORT_MAX_RECORDS, csvLineBreaks(request.csv ?? ""))
|
|
462
|
+
: Math.min(SITE_DATA_IMPORT_MAX_RECORDS, request.records?.length ?? 0),
|
|
463
|
+
{
|
|
464
|
+
collections: [collection],
|
|
465
|
+
// The JSON to check: estimated from the CSV, or the records' JSON.
|
|
466
|
+
bytes:
|
|
467
|
+
request.format === "csv"
|
|
468
|
+
? csvValidationBytes(request.csv ?? "")
|
|
469
|
+
: siteDataValidationBytes(request.records ?? []),
|
|
470
|
+
},
|
|
471
|
+
);
|
|
472
|
+
const imported = { count: 0 };
|
|
473
|
+
const failed = (error: unknown): never => {
|
|
474
|
+
throw siteDataImportFailure(error, imported.count);
|
|
475
|
+
};
|
|
476
|
+
// Everything after admission settles the reservation, including parse errors.
|
|
477
|
+
return meteredWrite(access, reserved, async (meter) => {
|
|
478
|
+
let records: Record<string, unknown>[];
|
|
479
|
+
try {
|
|
480
|
+
if (request.format === "csv") {
|
|
481
|
+
records = siteDataCsvRecords(request.csv ?? "");
|
|
482
|
+
} else records = request.records ?? [];
|
|
483
|
+
if (records.length > SITE_DATA_IMPORT_MAX_RECORDS)
|
|
484
|
+
throw new SiteDataError(
|
|
485
|
+
"validation",
|
|
486
|
+
`Import at most ${SITE_DATA_IMPORT_MAX_RECORDS} records at a time`,
|
|
487
|
+
);
|
|
488
|
+
} catch (error) {
|
|
489
|
+
return failed(error);
|
|
490
|
+
}
|
|
491
|
+
const keySegments = request.keyField ? parseSiteDataPath(request.keyField) : null;
|
|
492
|
+
if (request.keyField && !keySegments)
|
|
493
|
+
throw new SiteDataError("validation", `Invalid keyField: ${request.keyField}`);
|
|
494
|
+
const operations: SiteDataWriteOp[] = records.map((data, index) => {
|
|
495
|
+
if (!keySegments) return { op: "insert", collection, data };
|
|
496
|
+
const key = readSiteDataPath(data, keySegments);
|
|
497
|
+
if ((typeof key !== "string" && typeof key !== "number") || String(key).length === 0)
|
|
498
|
+
throw new SiteDataError("validation", `Record ${index + 1} has no ${request.keyField}`, {
|
|
499
|
+
index,
|
|
500
|
+
});
|
|
501
|
+
return { op: "put", collection, key: String(key), data };
|
|
502
|
+
});
|
|
503
|
+
// Every record is checked against the schema before anything is written.
|
|
504
|
+
const options = await prevalidateSiteDataWrite(access, operations, {
|
|
505
|
+
meter,
|
|
506
|
+
reservedMs: reserved.reservedMs,
|
|
507
|
+
}).catch(failed);
|
|
508
|
+
// Each batch is atomic; a failure reports how many records were imported.
|
|
509
|
+
for (let start = 0; start < operations.length; start += SITE_DATA_MAX_BATCH) {
|
|
510
|
+
await access.store
|
|
511
|
+
.write(access.caller, operations.slice(start, start + SITE_DATA_MAX_BATCH), options)
|
|
512
|
+
.catch(failed);
|
|
513
|
+
imported.count += Math.min(SITE_DATA_MAX_BATCH, operations.length - start);
|
|
514
|
+
}
|
|
515
|
+
return { imported: imported.count };
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* An import's error, with how many records it had already imported. Once a
|
|
521
|
+
* batch committed, sending the same import again would duplicate it, so the
|
|
522
|
+
* error is no longer retryable and its message says what was imported.
|
|
523
|
+
*/
|
|
524
|
+
export function siteDataImportFailure(error: unknown, imported: number): unknown {
|
|
525
|
+
if (!(error instanceof SiteDataError)) return error;
|
|
526
|
+
if (imported === 0)
|
|
527
|
+
return new SiteDataError(error.code, error.message, { ...error.details, imported });
|
|
528
|
+
const { retryable: _retryable, ...details } = error.details;
|
|
529
|
+
return new SiteDataError(
|
|
530
|
+
error.code,
|
|
531
|
+
`${error.message.replace(/; send it again$/, "")}. ${imported} ${imported === 1 ? "record was" : "records were"} imported before this failure; import only the rest`,
|
|
532
|
+
{ ...details, imported },
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* About how much JSON a CSV becomes once parsed: its text, plus the header
|
|
538
|
+
* repeated as field names on every row.
|
|
539
|
+
*/
|
|
540
|
+
export function csvValidationBytes(csv: string): number {
|
|
541
|
+
const headerEnd = csv.search(/\r|\n/);
|
|
542
|
+
const headerLength = headerEnd < 0 ? csv.length : headerEnd;
|
|
543
|
+
return csv.length + csvLineBreaks(csv) * headerLength;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** Line breaks in CSV text: an upper bound on its records after the header. */
|
|
547
|
+
function csvLineBreaks(csv: string): number {
|
|
548
|
+
let breaks = 0;
|
|
549
|
+
for (let index = 0; index < csv.length; index += 1) {
|
|
550
|
+
const char = csv.charCodeAt(index);
|
|
551
|
+
// CR, or LF not preceded by CR.
|
|
552
|
+
if (char === 13 || (char === 10 && csv.charCodeAt(index - 1) !== 13)) breaks += 1;
|
|
553
|
+
}
|
|
554
|
+
return breaks;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export async function exportSiteData(
|
|
558
|
+
deps: SiteDataDeps,
|
|
559
|
+
grant: SiteDataAuthority,
|
|
560
|
+
siteId: string,
|
|
561
|
+
collection: string,
|
|
562
|
+
format: "json" | "csv",
|
|
563
|
+
): Promise<{
|
|
564
|
+
collection: string;
|
|
565
|
+
format: "json" | "csv";
|
|
566
|
+
records: number;
|
|
567
|
+
truncated: boolean;
|
|
568
|
+
content: string;
|
|
569
|
+
}> {
|
|
570
|
+
const access = await resolveSiteDataAccess(deps, grant, siteId, { management: true });
|
|
571
|
+
const records: SiteDataRecord[] = [];
|
|
572
|
+
let cursor: string | null = null;
|
|
573
|
+
let truncated = false;
|
|
574
|
+
let bytes = 0;
|
|
575
|
+
// Collected up front and capped by count and bytes; the result is one bounded string.
|
|
576
|
+
pages: do {
|
|
577
|
+
const page = await access.store.query(access.caller, {
|
|
578
|
+
collection,
|
|
579
|
+
sort: [{ field: "$createdAt", direction: "asc" }],
|
|
580
|
+
limit: 200,
|
|
581
|
+
...(cursor ? { cursor } : {}),
|
|
582
|
+
});
|
|
583
|
+
for (const record of page.records) {
|
|
584
|
+
bytes += record.sizeBytes + 256;
|
|
585
|
+
if (records.length >= SITE_DATA_EXPORT_MAX_RECORDS || bytes > SITE_DATA_EXPORT_MAX_BYTES) {
|
|
586
|
+
truncated = true;
|
|
587
|
+
break pages;
|
|
588
|
+
}
|
|
589
|
+
records.push(record);
|
|
590
|
+
}
|
|
591
|
+
cursor = page.nextCursor;
|
|
592
|
+
} while (cursor);
|
|
593
|
+
const exported = records;
|
|
594
|
+
if (format === "json")
|
|
595
|
+
return {
|
|
596
|
+
collection,
|
|
597
|
+
format,
|
|
598
|
+
records: exported.length,
|
|
599
|
+
truncated,
|
|
600
|
+
content: JSON.stringify(
|
|
601
|
+
exported.map((record) => ({
|
|
602
|
+
id: record.id,
|
|
603
|
+
key: record.key,
|
|
604
|
+
owner: record.owner,
|
|
605
|
+
version: record.version,
|
|
606
|
+
createdAt: record.createdAt,
|
|
607
|
+
updatedAt: record.updatedAt,
|
|
608
|
+
data: record.data,
|
|
609
|
+
})),
|
|
610
|
+
),
|
|
611
|
+
};
|
|
612
|
+
// Viewers can shape records, so the column set is bounded: past the cap,
|
|
613
|
+
// every record's data goes into a single JSON column instead.
|
|
614
|
+
const seen = new Set<string>();
|
|
615
|
+
let wide = false;
|
|
616
|
+
for (const record of exported) {
|
|
617
|
+
for (const key of Object.keys(record.data)) {
|
|
618
|
+
seen.add(key);
|
|
619
|
+
if (seen.size > SITE_DATA_EXPORT_MAX_COLUMNS) {
|
|
620
|
+
wide = true;
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (wide) break;
|
|
625
|
+
}
|
|
626
|
+
const columns = wide ? [] : [...seen].sort();
|
|
627
|
+
const header = [
|
|
628
|
+
"$id",
|
|
629
|
+
"$key",
|
|
630
|
+
"$owner",
|
|
631
|
+
"$createdAt",
|
|
632
|
+
"$updatedAt",
|
|
633
|
+
...(wide ? ["$data"] : columns),
|
|
634
|
+
];
|
|
635
|
+
const lines = [
|
|
636
|
+
header.map(siteDataCsvCell).join(","),
|
|
637
|
+
...exported.map((record) =>
|
|
638
|
+
[
|
|
639
|
+
record.id,
|
|
640
|
+
record.key,
|
|
641
|
+
record.owner,
|
|
642
|
+
record.createdAt,
|
|
643
|
+
record.updatedAt,
|
|
644
|
+
...(wide
|
|
645
|
+
? [JSON.stringify(record.data)]
|
|
646
|
+
: columns.map((column) =>
|
|
647
|
+
Object.hasOwn(record.data, column) ? record.data[column] : undefined,
|
|
648
|
+
)),
|
|
649
|
+
]
|
|
650
|
+
.map(siteDataCsvCell)
|
|
651
|
+
.join(","),
|
|
652
|
+
),
|
|
653
|
+
];
|
|
654
|
+
return {
|
|
655
|
+
collection,
|
|
656
|
+
format,
|
|
657
|
+
records: exported.length,
|
|
658
|
+
truncated,
|
|
659
|
+
content: `${lines.join("\r\n")}\r\n`,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// --- files -------------------------------------------------------------------
|
|
664
|
+
|
|
665
|
+
function storage(deps: Pick<ApiRouteDeps, "objectStorage">) {
|
|
666
|
+
if (!deps.objectStorage)
|
|
667
|
+
throw new SiteDataError("not_found", "File storage is not configured for this deployment");
|
|
668
|
+
return deps.objectStorage;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
export async function uploadSiteDataFile(
|
|
672
|
+
deps: SiteDataDeps & Pick<ApiRouteDeps, "objectStorage">,
|
|
673
|
+
grant: SiteDataAuthority,
|
|
674
|
+
siteId: string,
|
|
675
|
+
input: {
|
|
676
|
+
name: string | null | undefined;
|
|
677
|
+
contentType: string | null | undefined;
|
|
678
|
+
/**
|
|
679
|
+
* The file's bytes, or a reader for a request body: it is called only
|
|
680
|
+
* after access, the rate window and the quota (with the declared size,
|
|
681
|
+
* when known) have passed, so refused uploads are never read.
|
|
682
|
+
*/
|
|
683
|
+
body: Uint8Array | { declaredBytes?: number | undefined; read: () => Promise<Uint8Array> };
|
|
684
|
+
scope?: SiteDataFileScope | undefined;
|
|
685
|
+
},
|
|
686
|
+
surface: SiteDataSurface = {},
|
|
687
|
+
): Promise<SiteDataFile> {
|
|
688
|
+
const access = await resolveSiteDataAccess(deps, grant, siteId, surface);
|
|
689
|
+
const objects = storage(deps);
|
|
690
|
+
// Access, rate, then quota, before the body is read or reaches object storage.
|
|
691
|
+
await requireSitePlatformQuota(deps, access.caller, ["storage_bytes"]);
|
|
692
|
+
await consume(access, "write", 1);
|
|
693
|
+
const scope = input.scope ?? "shared";
|
|
694
|
+
if (scope !== "shared" && scope !== "private")
|
|
695
|
+
throw new SiteDataError("validation", "scope must be shared or private");
|
|
696
|
+
let bytes: Uint8Array;
|
|
697
|
+
if (input.body instanceof Uint8Array) bytes = input.body;
|
|
698
|
+
else {
|
|
699
|
+
if (input.body.declaredBytes !== undefined)
|
|
700
|
+
await access.store.checkFileQuota(access.caller, input.body.declaredBytes, access.limits);
|
|
701
|
+
bytes = await input.body.read();
|
|
702
|
+
}
|
|
703
|
+
await access.store.checkFileQuota(access.caller, bytes.byteLength, access.limits);
|
|
704
|
+
const name = siteDataFileName(input.name);
|
|
705
|
+
const contentType = siteDataFileContentType(input.contentType, bytes);
|
|
706
|
+
const id = crypto.randomUUID();
|
|
707
|
+
const objectKey = siteDataObjectKey(grant.workspaceId, siteId, id);
|
|
708
|
+
const sha256 = sha256Hex(bytes);
|
|
709
|
+
await objects.putObject({ key: objectKey, contentType, body: bytes, sha256 });
|
|
710
|
+
try {
|
|
711
|
+
return await access.store.insertFile(
|
|
712
|
+
access.caller,
|
|
713
|
+
{ id, name, contentType, sizeBytes: bytes.byteLength, sha256, objectKey, scope },
|
|
714
|
+
{
|
|
715
|
+
limits: access.limits,
|
|
716
|
+
afterWrite: (tx, summary) =>
|
|
717
|
+
meterSiteData(tx, grant, access.caller, access.subjectId, "upload", summary),
|
|
718
|
+
},
|
|
719
|
+
);
|
|
720
|
+
} catch (error) {
|
|
721
|
+
await objects
|
|
722
|
+
.deleteObject(objectKey)
|
|
723
|
+
.catch((cleanup: unknown) => logCleanupFailure("A refused upload", objectKey, cleanup));
|
|
724
|
+
throw error;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
export async function listSiteDataFiles(
|
|
729
|
+
deps: SiteDataDeps,
|
|
730
|
+
grant: SiteDataAuthority,
|
|
731
|
+
siteId: string,
|
|
732
|
+
options: { limit?: number; cursor?: string | null },
|
|
733
|
+
surface: SiteDataSurface = {},
|
|
734
|
+
) {
|
|
735
|
+
const access = await resolveSiteDataAccess(deps, grant, siteId, surface);
|
|
736
|
+
await consume(access, "read", 1);
|
|
737
|
+
return access.store.listFiles(access.caller, options);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
export async function getSiteDataFileUrl(
|
|
741
|
+
deps: SiteDataDeps & Pick<ApiRouteDeps, "objectStorage">,
|
|
742
|
+
grant: SiteDataAuthority,
|
|
743
|
+
siteId: string,
|
|
744
|
+
fileId: string,
|
|
745
|
+
surface: SiteDataSurface = {},
|
|
746
|
+
audience: "public" | "sandbox" = "public",
|
|
747
|
+
): Promise<{ file: SiteDataFile; url: string; expiresAt: string }> {
|
|
748
|
+
const access = await resolveSiteDataAccess(deps, grant, siteId, surface);
|
|
749
|
+
await consume(access, "read", 1);
|
|
750
|
+
const stored = await access.store.getFile(access.caller, fileId);
|
|
751
|
+
if (!stored) throw new SiteDataError("not_found", "File not found");
|
|
752
|
+
const { objectKey, ...file } = stored;
|
|
753
|
+
const signed = await storage(deps).createGetUrl({
|
|
754
|
+
key: objectKey,
|
|
755
|
+
audience,
|
|
756
|
+
expiresInSeconds: FILE_URL_SECONDS,
|
|
757
|
+
...userContentSignedGetUrlOptions(file.contentType, file.name),
|
|
758
|
+
});
|
|
759
|
+
return { file, url: signed.url, expiresAt: signed.expiresAt.toISOString() };
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
export async function deleteSiteDataFile(
|
|
763
|
+
deps: SiteDataDeps & Pick<ApiRouteDeps, "objectStorage">,
|
|
764
|
+
grant: SiteDataAuthority,
|
|
765
|
+
siteId: string,
|
|
766
|
+
fileId: string,
|
|
767
|
+
surface: SiteDataSurface = {},
|
|
768
|
+
): Promise<{ deleted: { id: string } }> {
|
|
769
|
+
const access = await resolveSiteDataAccess(deps, grant, siteId, surface);
|
|
770
|
+
await consume(access, "write", 1);
|
|
771
|
+
const removed = await access.store.deleteFile(access.caller, fileId);
|
|
772
|
+
// The metadata row is gone; a failed object delete leaves an unreachable blob.
|
|
773
|
+
await storage(deps)
|
|
774
|
+
.deleteObject(removed.objectKey)
|
|
775
|
+
.catch((cleanup: unknown) => logCleanupFailure("A file delete", removed.objectKey, cleanup));
|
|
776
|
+
return { deleted: { id: removed.id } };
|
|
777
|
+
}
|