@openlup/platform 0.0.0 → 0.16.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/CHANGELOG.md +59 -0
- package/LICENSE +204 -0
- package/README.md +471 -1
- package/dist/bff/dispatch.d.ts +27 -0
- package/dist/bff/dispatch.js +47 -0
- package/dist/bff/envelope.d.ts +46 -0
- package/dist/bff/envelope.js +80 -0
- package/dist/bff/health.d.ts +24 -0
- package/dist/bff/health.js +19 -0
- package/dist/bff/index.d.ts +6 -0
- package/dist/bff/index.js +6 -0
- package/dist/bff/lazyRoute.d.ts +4 -0
- package/dist/bff/lazyRoute.js +13 -0
- package/dist/bff/requestContext.d.ts +17 -0
- package/dist/bff/requestContext.js +26 -0
- package/dist/bff/routerRewrite.d.ts +2 -0
- package/dist/bff/routerRewrite.js +31 -0
- package/dist/blob/binding.d.ts +16 -0
- package/dist/blob/binding.js +37 -0
- package/dist/blob/filesystem.d.ts +8 -0
- package/dist/blob/filesystem.js +71 -0
- package/dist/blob/index.d.ts +6 -0
- package/dist/blob/index.js +6 -0
- package/dist/blob/s3.d.ts +32 -0
- package/dist/blob/s3.js +56 -0
- package/dist/blob/s3Client.d.ts +25 -0
- package/dist/blob/s3Client.js +76 -0
- package/dist/blob/supabase.d.ts +40 -0
- package/dist/blob/supabase.js +59 -0
- package/dist/blob/vercel.d.ts +63 -0
- package/dist/blob/vercel.js +69 -0
- package/dist/composition/composeBundle.d.ts +47 -0
- package/dist/composition/composeBundle.js +45 -0
- package/dist/composition/httpBinding.d.ts +8 -0
- package/dist/composition/httpBinding.js +18 -0
- package/dist/composition/index.d.ts +3 -0
- package/dist/composition/index.js +3 -0
- package/dist/composition/nodeHost.d.ts +103 -0
- package/dist/composition/nodeHost.js +145 -0
- package/dist/data/binding.d.ts +34 -0
- package/dist/data/binding.js +92 -0
- package/dist/data/index.d.ts +5 -0
- package/dist/data/index.js +5 -0
- package/dist/data/postgresGateway.d.ts +42 -0
- package/dist/data/postgresGateway.js +135 -0
- package/dist/data/queryBuilder.d.ts +54 -0
- package/dist/data/queryBuilder.js +240 -0
- package/dist/data/queryBuilderSql.d.ts +76 -0
- package/dist/data/queryBuilderSql.js +145 -0
- package/dist/data/supabaseClientFactory.d.ts +34 -0
- package/dist/data/supabaseClientFactory.js +56 -0
- package/dist/data/supabaseGateway.d.ts +22 -0
- package/dist/data/supabaseGateway.js +49 -0
- package/dist/http/deadline.d.ts +20 -0
- package/dist/http/deadline.js +93 -0
- package/dist/http/index.d.ts +6 -0
- package/dist/http/index.js +5 -0
- package/dist/http/requestAdapter.d.ts +38 -0
- package/dist/http/requestAdapter.js +203 -0
- package/dist/http/runtime.d.ts +83 -0
- package/dist/http/runtime.js +199 -0
- package/dist/http/security.d.ts +44 -0
- package/dist/http/security.js +114 -0
- package/dist/http/staticAssets.d.ts +3 -0
- package/dist/http/staticAssets.js +42 -0
- package/dist/http/types.d.ts +23 -0
- package/dist/http/types.js +6 -0
- package/dist/identity/adminAuthBinding.d.ts +28 -0
- package/dist/identity/adminAuthBinding.js +66 -0
- package/dist/identity/adminAuthorization.d.ts +34 -0
- package/dist/identity/adminAuthorization.js +72 -0
- package/dist/identity/adminContracts.d.ts +70 -0
- package/dist/identity/adminContracts.js +43 -0
- package/dist/identity/adminMagicLinkHandler.d.ts +39 -0
- package/dist/identity/adminMagicLinkHandler.js +94 -0
- package/dist/identity/adminMeHandler.d.ts +15 -0
- package/dist/identity/adminMeHandler.js +49 -0
- package/dist/identity/adminPreflight.d.ts +16 -0
- package/dist/identity/adminPreflight.js +38 -0
- package/dist/identity/adminUserActionPorts.d.ts +31 -0
- package/dist/identity/adminUserActionPorts.js +51 -0
- package/dist/identity/adminUserHandlers.d.ts +38 -0
- package/dist/identity/adminUserHandlers.js +140 -0
- package/dist/identity/authVerifyUrl.d.ts +6 -0
- package/dist/identity/authVerifyUrl.js +54 -0
- package/dist/identity/contracts/contracts.d.ts +8 -0
- package/dist/identity/contracts/contracts.js +2 -0
- package/dist/identity/contracts/emailTrust.d.ts +3 -0
- package/dist/identity/contracts/emailTrust.js +4 -0
- package/dist/identity/contracts/index.d.ts +8 -0
- package/dist/identity/contracts/index.js +8 -0
- package/dist/identity/contracts/oidc.d.ts +3 -0
- package/dist/identity/contracts/oidc.js +9 -0
- package/dist/identity/contracts/operatorAuth.d.ts +35 -0
- package/dist/identity/contracts/operatorAuth.js +53 -0
- package/dist/identity/contracts/operatorInvitations.d.ts +37 -0
- package/dist/identity/contracts/operatorInvitations.js +1 -0
- package/dist/identity/contracts/ports.d.ts +22 -0
- package/dist/identity/contracts/ports.js +1 -0
- package/dist/identity/contracts/privacy.d.ts +62 -0
- package/dist/identity/contracts/privacy.js +99 -0
- package/dist/identity/contracts/providerList.d.ts +3 -0
- package/dist/identity/contracts/providerList.js +11 -0
- package/dist/identity/contracts/types.d.ts +16 -0
- package/dist/identity/contracts/types.js +4 -0
- package/dist/identity/contribution.d.ts +8 -0
- package/dist/identity/contribution.js +22 -0
- package/dist/identity/customerAuth.d.ts +8 -0
- package/dist/identity/customerAuth.js +1 -0
- package/dist/identity/customerAuthorization.d.ts +5 -0
- package/dist/identity/customerAuthorization.js +22 -0
- package/dist/identity/customerContracts.d.ts +42 -0
- package/dist/identity/customerContracts.js +43 -0
- package/dist/identity/customerIdentityBinding.d.ts +44 -0
- package/dist/identity/customerIdentityBinding.js +60 -0
- package/dist/identity/customerIdentityVerifier.d.ts +8 -0
- package/dist/identity/customerIdentityVerifier.js +101 -0
- package/dist/identity/customerMagicLinkHandler.d.ts +38 -0
- package/dist/identity/customerMagicLinkHandler.js +115 -0
- package/dist/identity/customerMeHandler.d.ts +11 -0
- package/dist/identity/customerMeHandler.js +46 -0
- package/dist/identity/customerPorts.d.ts +4 -0
- package/dist/identity/customerPorts.js +1 -0
- package/dist/identity/customerSessionBinding.d.ts +38 -0
- package/dist/identity/customerSessionBinding.js +80 -0
- package/dist/identity/customerSessionIssuer.d.ts +10 -0
- package/dist/identity/customerSessionIssuer.js +57 -0
- package/dist/identity/customerVerifyOtpHandler.d.ts +8 -0
- package/dist/identity/customerVerifyOtpHandler.js +41 -0
- package/dist/identity/directOperatorInvitationStore.d.ts +19 -0
- package/dist/identity/directOperatorInvitationStore.js +193 -0
- package/dist/identity/emailTrustPolicy.d.ts +14 -0
- package/dist/identity/emailTrustPolicy.js +16 -0
- package/dist/identity/identityPersonalDataContributor.d.ts +7 -0
- package/dist/identity/identityPersonalDataContributor.js +46 -0
- package/dist/identity/identityReadiness.d.ts +10 -0
- package/dist/identity/identityReadiness.js +25 -0
- package/dist/identity/index.d.ts +67 -0
- package/dist/identity/index.js +66 -0
- package/dist/identity/inviteAdminUserUseCase.d.ts +89 -0
- package/dist/identity/inviteAdminUserUseCase.js +75 -0
- package/dist/identity/jwtLibrary.d.ts +54 -0
- package/dist/identity/jwtLibrary.js +1 -0
- package/dist/identity/machineActorWriteFence.d.ts +4 -0
- package/dist/identity/machineActorWriteFence.js +13 -0
- package/dist/identity/magicLinkPorts.d.ts +28 -0
- package/dist/identity/magicLinkPorts.js +1 -0
- package/dist/identity/managedAccountLinkStore.d.ts +2 -0
- package/dist/identity/managedAccountLinkStore.js +93 -0
- package/dist/identity/managedAdminAuthVerifier.d.ts +7 -0
- package/dist/identity/managedAdminAuthVerifier.js +27 -0
- package/dist/identity/managedAdminUserGateways.d.ts +123 -0
- package/dist/identity/managedAdminUserGateways.js +127 -0
- package/dist/identity/managedClient.d.ts +90 -0
- package/dist/identity/managedClient.js +10 -0
- package/dist/identity/managedClientAuthLink.d.ts +3 -0
- package/dist/identity/managedClientAuthLink.js +115 -0
- package/dist/identity/managedCustomerMe.d.ts +13 -0
- package/dist/identity/managedCustomerMe.js +30 -0
- package/dist/identity/managedEmailTrustPolicy.d.ts +11 -0
- package/dist/identity/managedEmailTrustPolicy.js +105 -0
- package/dist/identity/managedIdentityVerifier.d.ts +2 -0
- package/dist/identity/managedIdentityVerifier.js +16 -0
- package/dist/identity/managedPrivacy.d.ts +4 -0
- package/dist/identity/managedPrivacy.js +39 -0
- package/dist/identity/messages.d.ts +29 -0
- package/dist/identity/messages.js +39 -0
- package/dist/identity/oidcIdentityVerifier.d.ts +45 -0
- package/dist/identity/oidcIdentityVerifier.js +309 -0
- package/dist/identity/oidcPrincipal.d.ts +2 -0
- package/dist/identity/oidcPrincipal.js +10 -0
- package/dist/identity/operatorAdminAuth.d.ts +6 -0
- package/dist/identity/operatorAdminAuth.js +13 -0
- package/dist/identity/operatorAuthHandlers.d.ts +18 -0
- package/dist/identity/operatorAuthHandlers.js +71 -0
- package/dist/identity/operatorIdentityVerifier.d.ts +8 -0
- package/dist/identity/operatorIdentityVerifier.js +19 -0
- package/dist/identity/operatorInvitationLifecycle.d.ts +7 -0
- package/dist/identity/operatorInvitationLifecycle.js +15 -0
- package/dist/identity/operatorInvitationPorts.d.ts +31 -0
- package/dist/identity/operatorInvitationPorts.js +1 -0
- package/dist/identity/operatorPorts.d.ts +37 -0
- package/dist/identity/operatorPorts.js +1 -0
- package/dist/identity/operatorSession.d.ts +13 -0
- package/dist/identity/operatorSession.js +31 -0
- package/dist/identity/operatorTokenAdminAuth.d.ts +15 -0
- package/dist/identity/operatorTokenAdminAuth.js +50 -0
- package/dist/identity/personalDataRegistry.d.ts +4 -0
- package/dist/identity/personalDataRegistry.js +11 -0
- package/dist/identity/platformAdminAuth.d.ts +15 -0
- package/dist/identity/platformAdminAuth.js +1 -0
- package/dist/identity/ports.d.ts +127 -0
- package/dist/identity/ports.js +38 -0
- package/dist/identity/postgresAccountLinkStore.d.ts +7 -0
- package/dist/identity/postgresAccountLinkStore.js +52 -0
- package/dist/identity/postgresCustomerMe.d.ts +3 -0
- package/dist/identity/postgresCustomerMe.js +21 -0
- package/dist/identity/postgresCustomerRecovery.d.ts +11 -0
- package/dist/identity/postgresCustomerRecovery.js +57 -0
- package/dist/identity/postgresOperatorIdentity.d.ts +5 -0
- package/dist/identity/postgresOperatorIdentity.js +137 -0
- package/dist/identity/postgresPrivacy.d.ts +7 -0
- package/dist/identity/postgresPrivacy.js +126 -0
- package/dist/identity/privacyHandlers.d.ts +10 -0
- package/dist/identity/privacyHandlers.js +35 -0
- package/dist/identity/privacyPorts.d.ts +72 -0
- package/dist/identity/privacyPorts.js +8 -0
- package/dist/identity/privacyUseCases.d.ts +5 -0
- package/dist/identity/privacyUseCases.js +55 -0
- package/dist/identity/reconcileAccountForPrincipal.d.ts +20 -0
- package/dist/identity/reconcileAccountForPrincipal.js +66 -0
- package/dist/identity/reconcileAccountHandler.d.ts +11 -0
- package/dist/identity/reconcileAccountHandler.js +30 -0
- package/dist/identity/removeAdminUserUseCase.d.ts +31 -0
- package/dist/identity/removeAdminUserUseCase.js +60 -0
- package/dist/identity/response.d.ts +3 -0
- package/dist/identity/response.js +2 -0
- package/dist/identity/sql.d.ts +44 -0
- package/dist/identity/sql.js +10 -0
- package/dist/job-ledger/index.d.ts +5 -0
- package/dist/job-ledger/index.js +3 -0
- package/dist/job-ledger/ports.d.ts +10 -0
- package/dist/job-ledger/ports.js +7 -0
- package/dist/job-ledger/postgres.d.ts +10 -0
- package/dist/job-ledger/postgres.js +101 -0
- package/dist/job-ledger/sql.d.ts +7 -0
- package/dist/job-ledger/sql.js +10 -0
- package/dist/job-ledger/supabase.d.ts +77 -0
- package/dist/job-ledger/supabase.js +202 -0
- package/dist/kernel/index.d.ts +1 -0
- package/dist/kernel/index.js +1 -0
- package/dist/kernel/platformKernel.d.ts +34 -0
- package/dist/kernel/platformKernel.js +115 -0
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.js +1 -0
- package/dist/migrations/postgresRunner.d.ts +50 -0
- package/dist/migrations/postgresRunner.js +176 -0
- package/dist/scheduler/advisoryLock.d.ts +44 -0
- package/dist/scheduler/advisoryLock.js +114 -0
- package/dist/scheduler/cronGuard.d.ts +10 -0
- package/dist/scheduler/cronGuard.js +19 -0
- package/dist/scheduler/cronSecret.d.ts +5 -0
- package/dist/scheduler/cronSecret.js +23 -0
- package/dist/scheduler/handlers.d.ts +9 -0
- package/dist/scheduler/handlers.js +10 -0
- package/dist/scheduler/httpEntry.d.ts +16 -0
- package/dist/scheduler/httpEntry.js +31 -0
- package/dist/scheduler/index.d.ts +9 -0
- package/dist/scheduler/index.js +9 -0
- package/dist/scheduler/jobRegistry.d.ts +21 -0
- package/dist/scheduler/jobRegistry.js +39 -0
- package/dist/scheduler/jobs.d.ts +48 -0
- package/dist/scheduler/jobs.js +92 -0
- package/dist/scheduler/nodeScheduler.d.ts +42 -0
- package/dist/scheduler/nodeScheduler.js +73 -0
- package/dist/scheduler/vercelScheduler.d.ts +29 -0
- package/dist/scheduler/vercelScheduler.js +51 -0
- package/package.json +116 -3
- package/release-gates.json +143 -0
- package/sql/20260809150000_job_run_ledger.sql +301 -0
- package/sql/20260809150001_job_run_ledger_v3.sql +70 -0
- package/sql/20260810180001_self_host_actor_auth.sql +40 -0
- package/sql/20260810204057_customer_identity_read.sql +31 -0
- package/sql/20260814090000_customer_selfservice_recovery.sql +477 -0
- package/sql/20260901143000_admin_membership_authority_foundation.sql +234 -0
- package/sql/20261018120000_identity_operator_invitations.sql +42 -0
- package/sql/20261018180000_identity_operator_challenges.sql +56 -0
- package/sql/20261018190000_identity_privacy_erasures.sql +15 -0
- package/src/bff/dispatch.ts +81 -0
- package/src/bff/envelope.ts +142 -0
- package/src/bff/health.ts +37 -0
- package/src/bff/index.ts +30 -0
- package/src/bff/lazyRoute.ts +28 -0
- package/src/bff/requestContext.ts +48 -0
- package/src/bff/routerRewrite.ts +38 -0
- package/src/blob/binding.ts +55 -0
- package/src/blob/filesystem.ts +86 -0
- package/src/blob/index.ts +17 -0
- package/src/blob/s3.ts +87 -0
- package/src/blob/s3Client.ts +91 -0
- package/src/blob/supabase.ts +88 -0
- package/src/blob/vercel.ts +127 -0
- package/src/composition/composeBundle.ts +109 -0
- package/src/composition/httpBinding.ts +30 -0
- package/src/composition/index.ts +17 -0
- package/src/composition/nodeHost.ts +289 -0
- package/src/data/binding.ts +157 -0
- package/src/data/index.ts +33 -0
- package/src/data/postgresGateway.ts +186 -0
- package/src/data/queryBuilder.ts +300 -0
- package/src/data/queryBuilderSql.ts +183 -0
- package/src/data/supabaseClientFactory.ts +92 -0
- package/src/data/supabaseGateway.ts +76 -0
- package/src/http/deadline.ts +117 -0
- package/src/http/index.ts +36 -0
- package/src/http/requestAdapter.ts +215 -0
- package/src/http/runtime.ts +319 -0
- package/src/http/security.ts +155 -0
- package/src/http/staticAssets.ts +42 -0
- package/src/http/types.ts +31 -0
- package/src/identity/adminAuthBinding.ts +76 -0
- package/src/identity/adminAuthorization.ts +106 -0
- package/src/identity/adminContracts.ts +52 -0
- package/src/identity/adminMagicLinkHandler.ts +132 -0
- package/src/identity/adminMeHandler.ts +64 -0
- package/src/identity/adminPreflight.ts +46 -0
- package/src/identity/adminUserActionPorts.ts +78 -0
- package/src/identity/adminUserHandlers.ts +174 -0
- package/src/identity/authVerifyUrl.ts +56 -0
- package/src/identity/contracts/contracts.ts +15 -0
- package/src/identity/contracts/emailTrust.ts +5 -0
- package/src/identity/contracts/index.ts +8 -0
- package/src/identity/contracts/oidc.ts +10 -0
- package/src/identity/contracts/operatorAuth.ts +61 -0
- package/src/identity/contracts/operatorInvitations.ts +32 -0
- package/src/identity/contracts/ports.ts +25 -0
- package/src/identity/contracts/privacy.ts +86 -0
- package/src/identity/contracts/providerList.ts +13 -0
- package/src/identity/contracts/types.ts +25 -0
- package/src/identity/contribution.ts +29 -0
- package/src/identity/customerAuth.ts +3 -0
- package/src/identity/customerAuthorization.ts +38 -0
- package/src/identity/customerContracts.ts +52 -0
- package/src/identity/customerIdentityBinding.ts +125 -0
- package/src/identity/customerIdentityVerifier.ts +141 -0
- package/src/identity/customerMagicLinkHandler.ts +171 -0
- package/src/identity/customerMeHandler.ts +64 -0
- package/src/identity/customerPorts.ts +2 -0
- package/src/identity/customerSessionBinding.ts +114 -0
- package/src/identity/customerSessionIssuer.ts +71 -0
- package/src/identity/customerVerifyOtpHandler.ts +44 -0
- package/src/identity/directOperatorInvitationStore.ts +178 -0
- package/src/identity/emailTrustPolicy.ts +16 -0
- package/src/identity/identityPersonalDataContributor.ts +48 -0
- package/src/identity/identityReadiness.ts +26 -0
- package/src/identity/index.ts +69 -0
- package/src/identity/inviteAdminUserUseCase.ts +134 -0
- package/src/identity/jwtLibrary.ts +24 -0
- package/src/identity/machineActorWriteFence.ts +19 -0
- package/src/identity/magicLinkPorts.ts +7 -0
- package/src/identity/managedAccountLinkStore.ts +105 -0
- package/src/identity/managedAdminAuthVerifier.ts +40 -0
- package/src/identity/managedAdminUserGateways.ts +164 -0
- package/src/identity/managedClient.ts +58 -0
- package/src/identity/managedClientAuthLink.ts +120 -0
- package/src/identity/managedCustomerMe.ts +52 -0
- package/src/identity/managedEmailTrustPolicy.ts +90 -0
- package/src/identity/managedIdentityVerifier.ts +20 -0
- package/src/identity/managedPrivacy.ts +37 -0
- package/src/identity/messages.ts +41 -0
- package/src/identity/oidcIdentityVerifier.ts +229 -0
- package/src/identity/oidcPrincipal.ts +10 -0
- package/src/identity/operatorAdminAuth.ts +14 -0
- package/src/identity/operatorAuthHandlers.ts +52 -0
- package/src/identity/operatorIdentityVerifier.ts +19 -0
- package/src/identity/operatorInvitationLifecycle.ts +14 -0
- package/src/identity/operatorInvitationPorts.ts +11 -0
- package/src/identity/operatorPorts.ts +11 -0
- package/src/identity/operatorSession.ts +30 -0
- package/src/identity/operatorTokenAdminAuth.ts +79 -0
- package/src/identity/personalDataRegistry.ts +8 -0
- package/src/identity/platformAdminAuth.ts +7 -0
- package/src/identity/ports.ts +158 -0
- package/src/identity/postgresAccountLinkStore.ts +49 -0
- package/src/identity/postgresCustomerMe.ts +33 -0
- package/src/identity/postgresCustomerRecovery.ts +72 -0
- package/src/identity/postgresOperatorIdentity.ts +123 -0
- package/src/identity/postgresPrivacy.ts +117 -0
- package/src/identity/privacyHandlers.ts +33 -0
- package/src/identity/privacyPorts.ts +52 -0
- package/src/identity/privacyUseCases.ts +48 -0
- package/src/identity/reconcileAccountForPrincipal.ts +99 -0
- package/src/identity/reconcileAccountHandler.ts +36 -0
- package/src/identity/removeAdminUserUseCase.ts +85 -0
- package/src/identity/response.ts +3 -0
- package/src/identity/sql.ts +10 -0
- package/src/job-ledger/index.ts +25 -0
- package/src/job-ledger/ports.ts +27 -0
- package/src/job-ledger/postgres.ts +121 -0
- package/src/job-ledger/sql.ts +12 -0
- package/src/job-ledger/supabase.ts +294 -0
- package/src/kernel/index.ts +17 -0
- package/src/kernel/platformKernel.ts +153 -0
- package/src/migrations/index.ts +10 -0
- package/src/migrations/postgresRunner.ts +255 -0
- package/src/scheduler/advisoryLock.ts +145 -0
- package/src/scheduler/cronGuard.ts +30 -0
- package/src/scheduler/cronSecret.ts +26 -0
- package/src/scheduler/handlers.ts +24 -0
- package/src/scheduler/httpEntry.ts +49 -0
- package/src/scheduler/index.ts +47 -0
- package/src/scheduler/jobRegistry.ts +57 -0
- package/src/scheduler/jobs.ts +119 -0
- package/src/scheduler/nodeScheduler.ts +123 -0
- package/src/scheduler/vercelScheduler.ts +71 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Supabase client factory for the DataGatewayPort.
|
|
2
|
+
//
|
|
3
|
+
// ONE canonical definition of the two supabase-js client shapes BFF handlers need:
|
|
4
|
+
// - SERVICE-ROLE client (elevated, RLS-bypassing) — `createClient(url, serviceRoleKey, {auth:{...}})`
|
|
5
|
+
// - ACTOR client (RLS-bound via a Bearer access token) — anon key + `Authorization: Bearer <jwt>`
|
|
6
|
+
// Both use one exact option shape
|
|
7
|
+
// (`auth: { persistSession: false, autoRefreshToken: false }`, plus the actor's global Authorization
|
|
8
|
+
// header). The supabase-js `createClient`
|
|
9
|
+
// is always supplied by the application's composition root, so this package carries no SDK import
|
|
10
|
+
// and the adapter stays unit-testable without network.
|
|
11
|
+
|
|
12
|
+
/** Structural shape of the supabase-js `createClient` we depend on (injection seam for tests). */
|
|
13
|
+
export type SupabaseCreateClient = (
|
|
14
|
+
url: string,
|
|
15
|
+
key: string,
|
|
16
|
+
options?: {
|
|
17
|
+
auth?: { persistSession?: boolean; autoRefreshToken?: boolean };
|
|
18
|
+
global?: { headers?: Record<string, string> };
|
|
19
|
+
},
|
|
20
|
+
) => unknown;
|
|
21
|
+
|
|
22
|
+
export interface SupabaseDataGatewayEnv {
|
|
23
|
+
/** Project URL (SUPABASE_URL / VITE_SUPABASE_URL). */
|
|
24
|
+
url: string;
|
|
25
|
+
/** Anon/publishable key — used for actor (RLS-bound) clients. */
|
|
26
|
+
anonKey: string;
|
|
27
|
+
/** Service-role key — used for elevated (RLS-bypassing) clients. */
|
|
28
|
+
serviceRoleKey: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Read the gateway env using the same precedence the inline sites use today. */
|
|
32
|
+
export function readSupabaseDataGatewayEnv(
|
|
33
|
+
env: Record<string, string | undefined>,
|
|
34
|
+
): SupabaseDataGatewayEnv | null {
|
|
35
|
+
const url = env.SUPABASE_URL ?? env.VITE_SUPABASE_URL;
|
|
36
|
+
const anonKey =
|
|
37
|
+
env.VITE_SUPABASE_PUBLISHABLE_KEY ??
|
|
38
|
+
env.VITE_SUPABASE_ANON_KEY ??
|
|
39
|
+
env.SUPABASE_ANON_KEY;
|
|
40
|
+
const serviceRoleKey = env.SUPABASE_SERVICE_ROLE_KEY;
|
|
41
|
+
// anonKey is only needed for actor clients; service-role work needs only url + serviceRoleKey.
|
|
42
|
+
// We require url + serviceRoleKey (the elevated path the default bundle uses); anonKey may be
|
|
43
|
+
// empty for service-only deployments and is validated lazily when an actor client is requested.
|
|
44
|
+
return url && serviceRoleKey ? { url, anonKey: anonKey ?? "", serviceRoleKey } : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Read env for actor-only gateway consumers. Does not require the elevated service-role key. */
|
|
48
|
+
export function readSupabaseActorDataGatewayEnv(
|
|
49
|
+
env: Record<string, string | undefined>,
|
|
50
|
+
): SupabaseDataGatewayEnv | null {
|
|
51
|
+
const url = env.SUPABASE_URL ?? env.VITE_SUPABASE_URL;
|
|
52
|
+
const anonKey =
|
|
53
|
+
env.VITE_SUPABASE_PUBLISHABLE_KEY ??
|
|
54
|
+
env.VITE_SUPABASE_ANON_KEY ??
|
|
55
|
+
env.SUPABASE_ANON_KEY;
|
|
56
|
+
return url && anonKey ? { url, anonKey, serviceRoleKey: env.SUPABASE_SERVICE_ROLE_KEY ?? "" } : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build an elevated (service-role) supabase client — byte-identical to the inline
|
|
61
|
+
* `createClient(env.url, env.serviceRoleKey, { auth: { persistSession: false, autoRefreshToken: false } })`
|
|
62
|
+
* shape the BFF handlers use today.
|
|
63
|
+
*/
|
|
64
|
+
export function createServiceClient(
|
|
65
|
+
env: SupabaseDataGatewayEnv,
|
|
66
|
+
factory: SupabaseCreateClient,
|
|
67
|
+
): unknown {
|
|
68
|
+
return factory(env.url, env.serviceRoleKey, {
|
|
69
|
+
auth: { persistSession: false, autoRefreshToken: false },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Build an actor (RLS-bound) supabase client — anon key + the actor's Bearer access token, matching
|
|
75
|
+
* the inline `createAdminAuthClient` / `createCustomerClient` shape. When no token is supplied the
|
|
76
|
+
* global Authorization header is omitted (identical to the inline `accessToken ? {...} : {}`).
|
|
77
|
+
*/
|
|
78
|
+
export function createActorClient(
|
|
79
|
+
env: SupabaseDataGatewayEnv,
|
|
80
|
+
accessToken: string | null,
|
|
81
|
+
factory: SupabaseCreateClient,
|
|
82
|
+
): unknown {
|
|
83
|
+
if (!env.anonKey) {
|
|
84
|
+
throw new Error("supabase_data_gateway_actor_requires_anon_key");
|
|
85
|
+
}
|
|
86
|
+
return factory(env.url, env.anonKey, {
|
|
87
|
+
auth: { persistSession: false, autoRefreshToken: false },
|
|
88
|
+
global: {
|
|
89
|
+
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Supabase adapter for the platform-runtime DataGatewayPort.
|
|
2
|
+
//
|
|
3
|
+
// Centralizes the two persistence access modes the BFF handlers build inline today:
|
|
4
|
+
// - asService(work): elevated, RLS-bypassing — a service-role client (the default bundle's path).
|
|
5
|
+
// - asActor(claims, work): RLS-bound — an anon client carrying the actor's Bearer access token, so
|
|
6
|
+
// RLS evaluates the request as that principal (UUID-keep: claims.sub IS the supabase auth uuid).
|
|
7
|
+
// The work callback receives the concrete supabase client (typed `unknown` per the port contract;
|
|
8
|
+
// consumers cast to their narrow read/write surface). Behavior is byte-identical to the inline
|
|
9
|
+
// `createClient(...)` sites this replaces — same option shape, same Authorization header — so the
|
|
10
|
+
// default vercel-supabase bundle stays unchanged (golden-master byte-identical).
|
|
11
|
+
//
|
|
12
|
+
// POOLING SEMANTICS (documented, no behavior change):
|
|
13
|
+
// - Serverless (vercel-supabase): each invocation is short-lived; supabase-js talks to PostgREST
|
|
14
|
+
// over HTTP (PgBouncer in transaction-mode behind the scenes), so there is no long-lived pool to
|
|
15
|
+
// manage — a fresh client per asActor/asService call matches the current per-request creation and
|
|
16
|
+
// is correct for transaction-pooled connections (no session-level state assumed).
|
|
17
|
+
// - Long-running Node (node-supabase): the same supabase-js HTTP path applies; a direct-PG gateway
|
|
18
|
+
// (node-postgres) would instead hold a standard `pg.Pool` and run asActor work inside a
|
|
19
|
+
// `BEGIN; SET LOCAL request.jwt.claims = ...; ... COMMIT` tx-per-request for RLS. That is the
|
|
20
|
+
// MigrationRunner/postgres band's concern; this supabase adapter stays HTTP-pooled either way.
|
|
21
|
+
//
|
|
22
|
+
// asActor honors the access token supplied via `options.resolveAccessToken(claims)` (defaults to the
|
|
23
|
+
// claims' own bearer if present) so RLS is enforced; when no token resolves it falls back to an anon
|
|
24
|
+
// client (RLS still applies, just unauthenticated) rather than silently elevating — fail-safe.
|
|
25
|
+
|
|
26
|
+
import type { DataGatewayPort } from "@openlup/core/platform-runtime";
|
|
27
|
+
import {
|
|
28
|
+
createActorClient,
|
|
29
|
+
createServiceClient,
|
|
30
|
+
type SupabaseCreateClient,
|
|
31
|
+
type SupabaseDataGatewayEnv,
|
|
32
|
+
} from "./supabaseClientFactory.js";
|
|
33
|
+
|
|
34
|
+
type ActorClaims = { sub?: string; role?: string } | null;
|
|
35
|
+
|
|
36
|
+
export interface SupabaseDataGatewayOptions {
|
|
37
|
+
/** The supabase-js `createClient` (tests pass a stub). */
|
|
38
|
+
createClientImpl: SupabaseCreateClient;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve an actor's Bearer access token from their claims. The port contract carries only
|
|
41
|
+
* `{ sub, role }`, but RLS needs the raw JWT; call sites that have the request token bind it here.
|
|
42
|
+
* Returns null → anon (RLS-bound but unauthenticated) client.
|
|
43
|
+
*/
|
|
44
|
+
resolveAccessToken?: (claims: ActorClaims) => string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build a supabase-backed DataGatewayPort from resolved env. Pure: no client is created until a
|
|
49
|
+
* port method runs work (matches the per-request creation the inline sites do today).
|
|
50
|
+
*/
|
|
51
|
+
export function createSupabaseDataGateway(
|
|
52
|
+
env: SupabaseDataGatewayEnv,
|
|
53
|
+
options: SupabaseDataGatewayOptions,
|
|
54
|
+
): DataGatewayPort {
|
|
55
|
+
const resolveAccessToken = options.resolveAccessToken ?? defaultResolveAccessToken;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
async asService<T>(work: (gateway: unknown) => Promise<T>): Promise<T> {
|
|
59
|
+
const client = createServiceClient(env, options.createClientImpl);
|
|
60
|
+
return work(client);
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
async asActor<T>(claims: ActorClaims, work: (gateway: unknown) => Promise<T>): Promise<T> {
|
|
64
|
+
const accessToken = resolveAccessToken(claims);
|
|
65
|
+
const client = createActorClient(env, accessToken, options.createClientImpl);
|
|
66
|
+
return work(client);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Default token resolver: the port's claims shape doesn't carry a raw JWT, so without an injected
|
|
72
|
+
// resolver an actor request maps to an anon (unauthenticated, still RLS-bound) client. Call sites
|
|
73
|
+
// that hold the request bearer inject `resolveAccessToken` to make RLS see the real principal.
|
|
74
|
+
function defaultResolveAccessToken(_claims: ActorClaims): string | null {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Bounded request/response lifecycle for the Node HTTP adapter.
|
|
2
|
+
//
|
|
3
|
+
// This owns transport cancellation only. It gives handlers a cooperative
|
|
4
|
+
// AbortSignal, but cannot roll back downstream work that ignores it.
|
|
5
|
+
|
|
6
|
+
import type { HttpRequest, HttpResponse } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export const NODE_HTTP_TIMEOUT_CODE = "NODE_HTTP_TIMEOUT";
|
|
9
|
+
|
|
10
|
+
type Destroyable = { destroy?: (error?: Error) => void };
|
|
11
|
+
type Evented = { once?: (event: string, listener: () => void) => unknown; removeListener?: (event: string, listener: () => void) => unknown };
|
|
12
|
+
type WritableResponse = HttpResponse & {
|
|
13
|
+
write?: (...args: unknown[]) => boolean;
|
|
14
|
+
writeHead?: (...args: unknown[]) => unknown;
|
|
15
|
+
end?: (...args: unknown[]) => unknown;
|
|
16
|
+
setHeader?: (...args: unknown[]) => unknown;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export interface HttpDeadlineOptions {
|
|
20
|
+
req: HttpRequest;
|
|
21
|
+
res: HttpResponse;
|
|
22
|
+
durationMs: number;
|
|
23
|
+
run: () => Promise<unknown> | unknown;
|
|
24
|
+
onLateError?: (error: unknown) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Run one Node HTTP admission from the instant before lazy loading/adaptation.
|
|
29
|
+
*
|
|
30
|
+
* A timeout before headers commit gets one minimal JSON 504. After headers are
|
|
31
|
+
* committed we cannot change the status safely, so the response is destroyed.
|
|
32
|
+
* The invocation promise remains observed, preventing a late rejection from
|
|
33
|
+
* becoming an unhandled rejection or a second response.
|
|
34
|
+
*/
|
|
35
|
+
export async function runWithHttpDeadline(options: HttpDeadlineOptions): Promise<void> {
|
|
36
|
+
const { req, res, durationMs, run, onLateError } = options;
|
|
37
|
+
if (!Number.isSafeInteger(durationMs) || durationMs <= 0 || durationMs > 2_147_483_647) {
|
|
38
|
+
throw new Error("invalid_node_http_deadline");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
// Node 24 exposes IncomingMessage.signal as a getter without a setter. Keep
|
|
43
|
+
// that host-native property untouched and use the portable adapter seam.
|
|
44
|
+
req.abortSignal = controller.signal;
|
|
45
|
+
const eventedReq = req as HttpRequest & Evented;
|
|
46
|
+
const eventedRes = res as HttpResponse & Evented;
|
|
47
|
+
const abortForClose = () => controller.abort();
|
|
48
|
+
eventedReq.once?.("aborted", abortForClose);
|
|
49
|
+
eventedRes.once?.("close", abortForClose);
|
|
50
|
+
|
|
51
|
+
let expired = false;
|
|
52
|
+
let finishTimeout: (() => void) | undefined;
|
|
53
|
+
const timeout = new Promise<void>((resolve) => { finishTimeout = resolve; });
|
|
54
|
+
const timer = setTimeout(() => {
|
|
55
|
+
expired = true;
|
|
56
|
+
controller.abort();
|
|
57
|
+
if (res.destroyed || res.writableEnded) {
|
|
58
|
+
destroyInput(req);
|
|
59
|
+
return finishTimeout?.();
|
|
60
|
+
}
|
|
61
|
+
if (!res.headersSent && !res.writableEnded) {
|
|
62
|
+
closeInputAfterResponse(req, res);
|
|
63
|
+
res.statusCode = 504;
|
|
64
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
65
|
+
res.end(JSON.stringify({ ok: false, error: { code: NODE_HTTP_TIMEOUT_CODE, message: "Request timed out" } }));
|
|
66
|
+
sealLateWrites(res);
|
|
67
|
+
} else if (!res.writableEnded) {
|
|
68
|
+
sealLateWrites(res);
|
|
69
|
+
(res as HttpResponse & Destroyable).destroy?.();
|
|
70
|
+
destroyInput(req);
|
|
71
|
+
}
|
|
72
|
+
finishTimeout?.();
|
|
73
|
+
}, durationMs);
|
|
74
|
+
|
|
75
|
+
const invocation = Promise.resolve().then(run);
|
|
76
|
+
// Keep a late completion/rejection observed after timeout. It must never get a
|
|
77
|
+
// chance to write a second envelope, and its raw value is not client output.
|
|
78
|
+
void invocation.catch((error) => {
|
|
79
|
+
if (expired) onLateError?.(error);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await Promise.race([invocation.then(() => undefined), timeout]);
|
|
84
|
+
} finally {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
eventedReq.removeListener?.("aborted", abortForClose);
|
|
87
|
+
eventedRes.removeListener?.("close", abortForClose);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Send a response before closing its request input, so the status is deliverable. */
|
|
92
|
+
export function closeInputAfterResponse(req: HttpRequest, res: HttpResponse): void {
|
|
93
|
+
const evented = res as HttpResponse & Evented;
|
|
94
|
+
let closed = false;
|
|
95
|
+
const destroy = () => {
|
|
96
|
+
if (closed) return;
|
|
97
|
+
closed = true;
|
|
98
|
+
evented.removeListener?.("finish", destroy);
|
|
99
|
+
evented.removeListener?.("close", destroy);
|
|
100
|
+
destroyInput(req);
|
|
101
|
+
};
|
|
102
|
+
evented.once?.("finish", destroy);
|
|
103
|
+
evented.once?.("close", destroy);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function destroyInput(req: HttpRequest): void {
|
|
107
|
+
(req as HttpRequest & Destroyable).destroy?.();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Refuse late handler writes after a deadline response has committed. */
|
|
111
|
+
function sealLateWrites(res: HttpResponse): void {
|
|
112
|
+
const mutable = res as WritableResponse;
|
|
113
|
+
mutable.write = () => false;
|
|
114
|
+
mutable.writeHead = () => mutable;
|
|
115
|
+
mutable.end = () => mutable;
|
|
116
|
+
mutable.setHeader = () => mutable;
|
|
117
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type { EntrypointLoader, HttpHandler, HttpRequest, HttpResponse } from "./types.js";
|
|
2
|
+
export {
|
|
3
|
+
DEFAULT_NODE_BUFFERED_BODY_LIMIT_BYTES,
|
|
4
|
+
NodeRequestAbortedError,
|
|
5
|
+
NodeRequestBodyTooLargeError,
|
|
6
|
+
adaptRequest,
|
|
7
|
+
decorateRequest,
|
|
8
|
+
decorateResponse,
|
|
9
|
+
isRawStreamEntrypoint,
|
|
10
|
+
isWebhookPath,
|
|
11
|
+
pathnameOf,
|
|
12
|
+
} from "./requestAdapter.js";
|
|
13
|
+
export {
|
|
14
|
+
NODE_HTTP_TIMEOUT_CODE,
|
|
15
|
+
closeInputAfterResponse,
|
|
16
|
+
runWithHttpDeadline,
|
|
17
|
+
type HttpDeadlineOptions,
|
|
18
|
+
} from "./deadline.js";
|
|
19
|
+
export {
|
|
20
|
+
applySecurityHeaders,
|
|
21
|
+
contentTypeFor,
|
|
22
|
+
looksLikeAsset,
|
|
23
|
+
serveSpaStatic,
|
|
24
|
+
type NodeSecurityHeaderPolicy,
|
|
25
|
+
type StaticAssetReader,
|
|
26
|
+
} from "./security.js";
|
|
27
|
+
export { createDistStaticReader } from "./staticAssets.js";
|
|
28
|
+
export {
|
|
29
|
+
createNodeHttpRuntime,
|
|
30
|
+
createRequestHandler,
|
|
31
|
+
type HttpEntrypointRegistration,
|
|
32
|
+
type HttpPathEntrypoint,
|
|
33
|
+
type NodeCsrFallback,
|
|
34
|
+
type NodeHttpRuntimeOptions,
|
|
35
|
+
type NodeLegacyRedirect,
|
|
36
|
+
} from "./runtime.js";
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// Node request/response adaptation to the hosted-function request shape for the HttpRuntimePort.
|
|
2
|
+
//
|
|
3
|
+
// Owns the RAW-BODY INVARIANT: every buffered request gets the exact bytes re-exposed as an
|
|
4
|
+
// async-iterable (so raw-body signature verification works for ANY route, including ones outside a
|
|
5
|
+
// `/webhooks/` segment) while still setting a parsed JSON req.body; stream-first standalone
|
|
6
|
+
// entrypoints keep their live stream and NO req.body.
|
|
7
|
+
|
|
8
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
9
|
+
|
|
10
|
+
import type { HttpRequest, HttpResponse } from "./types.js";
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_NODE_BUFFERED_BODY_LIMIT_BYTES = 1_048_576;
|
|
13
|
+
|
|
14
|
+
export class NodeRequestBodyTooLargeError extends Error {
|
|
15
|
+
constructor() {
|
|
16
|
+
super("node_request_body_too_large");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class NodeRequestAbortedError extends Error {
|
|
21
|
+
constructor() {
|
|
22
|
+
super("node_request_aborted");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A path is a webhook iff it contains a `/webhooks/` segment. */
|
|
27
|
+
export function isWebhookPath(pathname: string): boolean {
|
|
28
|
+
return pathname.includes("/webhooks/");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The PLATFORM's stream-first standalone entrypoints: they read the raw stream
|
|
33
|
+
* themselves, so the runtime must never pre-parse their body.
|
|
34
|
+
*
|
|
35
|
+
* A deployment's own stream-first endpoint is NOT listed here. It declares
|
|
36
|
+
* `rawStream: true` on its `HttpEntrypointRegistration` and the runtime passes that
|
|
37
|
+
* decision to `adaptRequest` directly — which is what stops this publishable list
|
|
38
|
+
* from having to name a path only that deployment owns.
|
|
39
|
+
*/
|
|
40
|
+
export function isRawStreamEntrypoint(pathname: string): boolean {
|
|
41
|
+
return pathname === "/api/csp-report";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function pathnameOf(req: IncomingMessage): string {
|
|
45
|
+
return new URL(req.url ?? "/", "http://node.local").pathname;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseQuery(req: IncomingMessage): Record<string, string | string[] | undefined> {
|
|
49
|
+
const url = new URL(req.url ?? "/", "http://node.local");
|
|
50
|
+
const query: Record<string, string | string[] | undefined> = {};
|
|
51
|
+
for (const key of url.searchParams.keys()) {
|
|
52
|
+
if (key in query) continue;
|
|
53
|
+
const all = url.searchParams.getAll(key);
|
|
54
|
+
query[key] = all.length > 1 ? all : all[0];
|
|
55
|
+
}
|
|
56
|
+
return query;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseCookies(req: IncomingMessage): Record<string, string> {
|
|
60
|
+
const header = req.headers.cookie;
|
|
61
|
+
if (!header) return {};
|
|
62
|
+
const out: Record<string, string> = {};
|
|
63
|
+
for (const part of header.split(";")) {
|
|
64
|
+
const idx = part.indexOf("=");
|
|
65
|
+
if (idx < 0) continue;
|
|
66
|
+
const key = part.slice(0, idx).trim();
|
|
67
|
+
const value = part.slice(idx + 1).trim();
|
|
68
|
+
if (key) out[key] = decodeURIComponent(value);
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Buffer the request stream once into a single Buffer (GET/HEAD => empty). */
|
|
74
|
+
async function bufferBody(req: HttpRequest, maxBytes: number): Promise<Buffer> {
|
|
75
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
76
|
+
if (method === "GET" || method === "HEAD") return Buffer.alloc(0);
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const chunks: Buffer[] = [];
|
|
79
|
+
let bytes = 0;
|
|
80
|
+
let settled = false;
|
|
81
|
+
const finish = (result: Buffer | Error) => {
|
|
82
|
+
if (settled) return;
|
|
83
|
+
settled = true;
|
|
84
|
+
req.removeListener("data", onData);
|
|
85
|
+
req.removeListener("end", onEnd);
|
|
86
|
+
req.removeListener("error", onError);
|
|
87
|
+
req.abortSignal?.removeEventListener("abort", onAbort);
|
|
88
|
+
if (result instanceof Error) reject(result);
|
|
89
|
+
else resolve(result);
|
|
90
|
+
};
|
|
91
|
+
const onData = (chunk: Buffer | string) => {
|
|
92
|
+
const bytesChunk = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
93
|
+
bytes += bytesChunk.length;
|
|
94
|
+
if (bytes > maxBytes) {
|
|
95
|
+
finish(new NodeRequestBodyTooLargeError());
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
chunks.push(bytesChunk);
|
|
99
|
+
};
|
|
100
|
+
const onEnd = () => finish(Buffer.concat(chunks));
|
|
101
|
+
const onError = (error: Error) => finish(error);
|
|
102
|
+
const onAbort = () => finish(new NodeRequestAbortedError());
|
|
103
|
+
if (req.abortSignal?.aborted) return onAbort();
|
|
104
|
+
req.on("data", onData);
|
|
105
|
+
req.once("end", onEnd);
|
|
106
|
+
req.once("error", onError);
|
|
107
|
+
req.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Re-expose a fixed Buffer as the request's async-iterable so readRawBody sees the EXACT bytes. */
|
|
112
|
+
function reexposeRawStream(req: IncomingMessage, raw: Buffer): void {
|
|
113
|
+
(req as unknown as { [Symbol.asyncIterator]: () => AsyncIterator<Buffer> })[Symbol.asyncIterator] =
|
|
114
|
+
async function* () {
|
|
115
|
+
yield raw;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Best-effort JSON parse of a buffered body for handlers that read `req.body`. */
|
|
120
|
+
function parseJsonBody(raw: Buffer, contentType: string): unknown {
|
|
121
|
+
if (raw.length === 0) return undefined;
|
|
122
|
+
const text = raw.toString("utf8").trim();
|
|
123
|
+
if (!text) return undefined;
|
|
124
|
+
if (contentType.includes("application/json") || text.startsWith("{") || text.startsWith("[")) {
|
|
125
|
+
try {
|
|
126
|
+
return JSON.parse(text);
|
|
127
|
+
} catch {
|
|
128
|
+
return text;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return text;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Decorate a Node response with Vercel's status/json/send/redirect sugar (productionized from the
|
|
136
|
+
* dev shim's decorateResponse — identical semantics).
|
|
137
|
+
*/
|
|
138
|
+
export function decorateResponse(res: ServerResponse): HttpResponse {
|
|
139
|
+
const vres = res as HttpResponse;
|
|
140
|
+
vres.status = function status(code: number): HttpResponse {
|
|
141
|
+
res.statusCode = code;
|
|
142
|
+
return vres;
|
|
143
|
+
};
|
|
144
|
+
vres.json = function json(body: unknown): HttpResponse {
|
|
145
|
+
if (!res.hasHeader("content-type")) {
|
|
146
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
147
|
+
}
|
|
148
|
+
res.end(JSON.stringify(body));
|
|
149
|
+
return vres;
|
|
150
|
+
};
|
|
151
|
+
vres.send = function send(body: string | Buffer | object): HttpResponse {
|
|
152
|
+
if (Buffer.isBuffer(body) || typeof body === "string") {
|
|
153
|
+
res.end(body);
|
|
154
|
+
return vres;
|
|
155
|
+
}
|
|
156
|
+
if (!res.hasHeader("content-type")) {
|
|
157
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
158
|
+
}
|
|
159
|
+
res.end(JSON.stringify(body));
|
|
160
|
+
return vres;
|
|
161
|
+
};
|
|
162
|
+
vres.redirect = function redirect(statusOrUrl: number | string, maybeUrl?: string): HttpResponse {
|
|
163
|
+
const code = typeof statusOrUrl === "number" ? statusOrUrl : 302;
|
|
164
|
+
const location = typeof statusOrUrl === "number" ? maybeUrl ?? "/" : statusOrUrl;
|
|
165
|
+
res.statusCode = code;
|
|
166
|
+
res.setHeader("location", location);
|
|
167
|
+
res.end();
|
|
168
|
+
return vres;
|
|
169
|
+
};
|
|
170
|
+
return vres;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Adapt a Node IncomingMessage into the Vercel request shape, honoring the raw-body invariant:
|
|
175
|
+
* - raw-stream entrypoints (process/csp/feedback): stream left intact, NO req.body;
|
|
176
|
+
* - everything else: raw bytes buffered + re-exposed as async-iterable (so readRawBody sees the
|
|
177
|
+
* EXACT bytes for any signature-verifying route, webhook-segment or not) AND req.body = parsed JSON.
|
|
178
|
+
*/
|
|
179
|
+
export async function adaptRequest(
|
|
180
|
+
req: IncomingMessage,
|
|
181
|
+
pathname: string,
|
|
182
|
+
/** Explicit override for a caller that knows the entrypoint's body contract (see above). */
|
|
183
|
+
rawStream: boolean = isRawStreamEntrypoint(pathname),
|
|
184
|
+
bufferedBodyLimitBytes: number = DEFAULT_NODE_BUFFERED_BODY_LIMIT_BYTES,
|
|
185
|
+
): Promise<HttpRequest> {
|
|
186
|
+
const vreq = req as HttpRequest;
|
|
187
|
+
vreq.query = parseQuery(req);
|
|
188
|
+
vreq.cookies = parseCookies(req);
|
|
189
|
+
|
|
190
|
+
if (rawStream) {
|
|
191
|
+
// Leave the live stream untouched; the handler reads it itself (formidable / custom readBody).
|
|
192
|
+
return vreq;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const raw = await bufferBody(vreq, bufferedBodyLimitBytes);
|
|
196
|
+
const contentType = String(req.headers["content-type"] ?? "");
|
|
197
|
+
|
|
198
|
+
// Re-expose the EXACT bytes as an async-iterable for EVERY buffered request, not only
|
|
199
|
+
// `/webhooks/` paths. Some signature-verifying routes live outside that segment — e.g.
|
|
200
|
+
// `/api/bff/communications/integrations/events` calls readRawBody for HMAC verification.
|
|
201
|
+
// readRawBody is stream-first (isAsyncIterable), so any handler that needs the raw bytes
|
|
202
|
+
// works; handlers that read req.body still get parsed JSON. The re-exposed iterator is
|
|
203
|
+
// simply never consumed for routes that don't call readRawBody, so this is a safe superset
|
|
204
|
+
// of the old `/webhooks/`-only behavior (and not dependent on a fragile path convention).
|
|
205
|
+
reexposeRawStream(req, raw);
|
|
206
|
+
vreq.body = parseJsonBody(raw, contentType);
|
|
207
|
+
return vreq;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Narrow a raw IncomingMessage to HttpRequest at the server boundary (query/cookies set in adapt). */
|
|
211
|
+
export function decorateRequest(req: IncomingMessage): HttpRequest {
|
|
212
|
+
const vreq = req as HttpRequest;
|
|
213
|
+
if (!vreq.query) vreq.query = {};
|
|
214
|
+
return vreq;
|
|
215
|
+
}
|