@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,109 @@
|
|
|
1
|
+
// Platform-runtime bundle composition.
|
|
2
|
+
//
|
|
3
|
+
// Resolves the active bundle from the environment and returns its descriptor and port table. Each
|
|
4
|
+
// port slot is filled by one binder, keyed by the bundle's declared capability for that slot. A
|
|
5
|
+
// slot without a binder, or whose binder declines the capability, stays null: callers treat an
|
|
6
|
+
// unbound port as "use the deployment's direct path", never as a silent substitute.
|
|
7
|
+
//
|
|
8
|
+
// The package supplies a default binder for the blob slot. The HTTP, scheduler, data, migrations,
|
|
9
|
+
// analytics and transactional slots carry deployment values (route tables, jobs, the database
|
|
10
|
+
// client factory, migration catalogue, analytics provider, delivery runtime), so the deployment
|
|
11
|
+
// supplies them; `createDataBindings` builds the data binder.
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
AnalyticsPort,
|
|
15
|
+
BlobStoragePort,
|
|
16
|
+
DataGatewayPort,
|
|
17
|
+
HttpRuntimePort,
|
|
18
|
+
MigrationRunnerPort,
|
|
19
|
+
SchedulerPort,
|
|
20
|
+
TransactionalRuntimePort,
|
|
21
|
+
} from "@openlup/core/platform-runtime";
|
|
22
|
+
|
|
23
|
+
import { bindBlobPort, type SupabaseStorageClient } from "../blob/index.js";
|
|
24
|
+
import { createServiceClient, readSupabaseDataGatewayEnv } from "../data/index.js";
|
|
25
|
+
import {
|
|
26
|
+
getBundleDescriptor,
|
|
27
|
+
resolveBlobCapability,
|
|
28
|
+
resolveBundleId,
|
|
29
|
+
type PlatformBundleDescriptor,
|
|
30
|
+
type PlatformBundleId,
|
|
31
|
+
} from "../kernel/index.js";
|
|
32
|
+
|
|
33
|
+
type Env = Record<string, string | undefined>;
|
|
34
|
+
|
|
35
|
+
export interface PlatformBundlePorts {
|
|
36
|
+
http: HttpRuntimePort | null;
|
|
37
|
+
scheduler: SchedulerPort | null;
|
|
38
|
+
blob: BlobStoragePort | null;
|
|
39
|
+
data: DataGatewayPort | null;
|
|
40
|
+
migrations: MigrationRunnerPort | null;
|
|
41
|
+
analytics: AnalyticsPort | null;
|
|
42
|
+
transactional: TransactionalRuntimePort | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface PlatformBundle {
|
|
46
|
+
id: PlatformBundleId;
|
|
47
|
+
descriptor: PlatformBundleDescriptor;
|
|
48
|
+
ports: PlatformBundlePorts;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* One binder per port slot. Each receives the slot's capability and the environment; the blob
|
|
53
|
+
* binder receives the selected blob capability (`PLATFORM_BLOB`, else the bundle's own), and the
|
|
54
|
+
* transactional binder also receives the bundle's data capability.
|
|
55
|
+
*/
|
|
56
|
+
export interface PlatformBundleBinders {
|
|
57
|
+
/**
|
|
58
|
+
* The supabase-js `createClient` used by the default Supabase Storage blob binder. Declared
|
|
59
|
+
* structurally here so the composition entry's declarations reference no data-module type.
|
|
60
|
+
*/
|
|
61
|
+
createSupabaseClient?: (
|
|
62
|
+
url: string,
|
|
63
|
+
key: string,
|
|
64
|
+
options?: { auth?: { persistSession?: boolean; autoRefreshToken?: boolean } },
|
|
65
|
+
) => unknown;
|
|
66
|
+
http?: (capability: string, env: Env) => HttpRuntimePort | null;
|
|
67
|
+
scheduler?: (capability: string, env: Env) => SchedulerPort | null;
|
|
68
|
+
/** Defaults to `bindBlobPort`. */
|
|
69
|
+
blob?: (capability: string, env: Env) => BlobStoragePort | null;
|
|
70
|
+
/** For example `createDataBindings(...).bindDataPort`. */
|
|
71
|
+
data?: (capability: string, env: Env) => DataGatewayPort | null;
|
|
72
|
+
migrations?: (capability: string, env: Env) => MigrationRunnerPort | null;
|
|
73
|
+
analytics?: (capability: string, env: Env) => AnalyticsPort | null;
|
|
74
|
+
transactional?: (capability: string, dataCapability: string, env: Env) => TransactionalRuntimePort | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type ComposeBundle = (env: Env) => PlatformBundle;
|
|
78
|
+
|
|
79
|
+
/** Build the deployment's composition root from its port binders. */
|
|
80
|
+
export function createBundleComposer(binders: PlatformBundleBinders = {}): ComposeBundle {
|
|
81
|
+
const bindBlob = binders.blob ?? ((capability: string, env: Env) => {
|
|
82
|
+
const settings = capability === "supabase" ? readSupabaseDataGatewayEnv({
|
|
83
|
+
...env,
|
|
84
|
+
SUPABASE_URL: env.SUPABASE_URL?.trim() || env.VITE_SUPABASE_URL?.trim(),
|
|
85
|
+
}) : null;
|
|
86
|
+
const createSupabaseClient = binders.createSupabaseClient;
|
|
87
|
+
return bindBlobPort(capability, env, {
|
|
88
|
+
supabaseStorageClient: settings && createSupabaseClient
|
|
89
|
+
? () => createServiceClient(settings, createSupabaseClient) as SupabaseStorageClient
|
|
90
|
+
: undefined,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
return function composeBundle(env: Env): PlatformBundle {
|
|
94
|
+
const id = resolveBundleId(env);
|
|
95
|
+
const descriptor = getBundleDescriptor(id);
|
|
96
|
+
const { capabilities } = descriptor;
|
|
97
|
+
const ports: PlatformBundlePorts = {
|
|
98
|
+
http: binders.http?.(capabilities.http, env) ?? null,
|
|
99
|
+
scheduler: null,
|
|
100
|
+
blob: bindBlob(resolveBlobCapability(descriptor, env), env),
|
|
101
|
+
data: binders.data?.(capabilities.data, env) ?? null,
|
|
102
|
+
migrations: binders.migrations?.(capabilities.migrations, env) ?? null,
|
|
103
|
+
analytics: binders.analytics?.(capabilities.analytics, env) ?? null,
|
|
104
|
+
transactional: binders.transactional?.(capabilities.transactional, capabilities.data, env) ?? null,
|
|
105
|
+
};
|
|
106
|
+
ports.scheduler = binders.scheduler?.(capabilities.scheduler, env) ?? null;
|
|
107
|
+
return { id, descriptor, ports };
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// HTTP slot binding for the Node runtime.
|
|
2
|
+
//
|
|
3
|
+
// Only a bundle whose `http` capability is "node" receives a Node runtime; a hosted bundle keeps
|
|
4
|
+
// `http: null` and serves its route table through its own function entrypoint. The scheduled-route
|
|
5
|
+
// resolver mounts exactly the jobs the deployment declared, so an undeclared `/api/cron/<id>`
|
|
6
|
+
// path stays a 404 and a declared job without an entrypoint fails visibly when called.
|
|
7
|
+
|
|
8
|
+
import type { HttpRuntimePort } from "@openlup/core/platform-runtime";
|
|
9
|
+
|
|
10
|
+
import type { EntrypointLoader } from "../http/index.js";
|
|
11
|
+
import { createNodeHttpRuntime, type NodeHttpRuntimeOptions } from "../http/index.js";
|
|
12
|
+
import type { ScheduledJobDeclaration } from "../scheduler/index.js";
|
|
13
|
+
|
|
14
|
+
/** Mount only the scheduled routes declared by the job registry. */
|
|
15
|
+
export function createScheduledEntrypointResolver(
|
|
16
|
+
jobs: readonly ScheduledJobDeclaration[],
|
|
17
|
+
load: (jobId: string) => ReturnType<EntrypointLoader>,
|
|
18
|
+
): (jobId: string) => EntrypointLoader | null {
|
|
19
|
+
const declared = new Set(jobs.map((job) => job.jobId));
|
|
20
|
+
return (jobId) => declared.has(jobId) ? () => load(jobId) : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Build the http port for a bundle capability ("node" => Node runtime; else null). */
|
|
24
|
+
export function bindNodeHttpPort(
|
|
25
|
+
capability: string,
|
|
26
|
+
options: () => NodeHttpRuntimeOptions,
|
|
27
|
+
): HttpRuntimePort | null {
|
|
28
|
+
if (capability !== "node") return null;
|
|
29
|
+
return createNodeHttpRuntime(options());
|
|
30
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createBundleComposer,
|
|
3
|
+
type ComposeBundle,
|
|
4
|
+
type PlatformBundle,
|
|
5
|
+
type PlatformBundleBinders,
|
|
6
|
+
type PlatformBundlePorts,
|
|
7
|
+
} from "./composeBundle.js";
|
|
8
|
+
export { bindNodeHttpPort, createScheduledEntrypointResolver } from "./httpBinding.js";
|
|
9
|
+
export {
|
|
10
|
+
createNodeHost,
|
|
11
|
+
type NodeHost,
|
|
12
|
+
type NodeHostAdmission,
|
|
13
|
+
type NodeHostDependencies,
|
|
14
|
+
type NodeSchedulerProfile,
|
|
15
|
+
type ServeNodeOptions,
|
|
16
|
+
type ServeNodeResult,
|
|
17
|
+
} from "./nodeHost.js";
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// Node (self-hosted) host bootstrap.
|
|
2
|
+
//
|
|
3
|
+
// Boots a Node platform bundle: prepare the host, check the bundle's readiness, apply the
|
|
4
|
+
// node-postgres migration replay before serving, admit the host, build the HTTP runtime, register
|
|
5
|
+
// the declared jobs with node-cron, and listen. A bundle whose migrations are managed outside the
|
|
6
|
+
// process (node-supabase) is expected to be migrated before the container starts.
|
|
7
|
+
//
|
|
8
|
+
// The deployment supplies its composition root, HTTP runtime options, job registry, direct job
|
|
9
|
+
// bindings and host admission; this module owns the order and the failure modes.
|
|
10
|
+
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
|
|
13
|
+
import type { HttpRuntimePort, JobLeasePort } from "@openlup/core/platform-runtime";
|
|
14
|
+
|
|
15
|
+
import { createDistStaticReader } from "../http/index.js";
|
|
16
|
+
import type { StaticAssetReader } from "../http/index.js";
|
|
17
|
+
import {
|
|
18
|
+
createEnvAdvisoryLock,
|
|
19
|
+
createNodeScheduler,
|
|
20
|
+
mergeScheduledJobHandlers,
|
|
21
|
+
runScheduledJob,
|
|
22
|
+
type AdvisoryLock,
|
|
23
|
+
type CronScheduler,
|
|
24
|
+
type NodeSchedulerAdapter,
|
|
25
|
+
type ScheduledJob,
|
|
26
|
+
type ScheduledJobDeclaration,
|
|
27
|
+
type ScheduledJobHandler,
|
|
28
|
+
type ScheduledJobHandlerRegistry,
|
|
29
|
+
} from "../scheduler/index.js";
|
|
30
|
+
import type { ComposeBundle, PlatformBundlePorts } from "./composeBundle.js";
|
|
31
|
+
|
|
32
|
+
type Env = Record<string, string | undefined>;
|
|
33
|
+
|
|
34
|
+
/** What the host hands its admission check before it serves or schedules anything. */
|
|
35
|
+
export interface NodeHostAdmission {
|
|
36
|
+
kind: "node";
|
|
37
|
+
nodeJobHandlers: ScheduledJobHandlerRegistry;
|
|
38
|
+
/** The path-declared jobs this process will time; empty when it does not run the scheduler. */
|
|
39
|
+
nodeJobs: readonly ScheduledJobDeclaration[];
|
|
40
|
+
/** The registry jobs this process will run directly; empty when it does not run the scheduler. */
|
|
41
|
+
scheduledJobs: readonly ScheduledJob[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface NodeHostDependencies {
|
|
45
|
+
/** The deployment's composition root (see `createBundleComposer`). */
|
|
46
|
+
composeBundle: ComposeBundle;
|
|
47
|
+
/**
|
|
48
|
+
* Build the HTTP runtime this host serves, for the declared jobs and its static reader. The
|
|
49
|
+
* bundle's `http` port only proves that the bundle is a Node bundle at readiness time.
|
|
50
|
+
*/
|
|
51
|
+
createHttpRuntime: (input: {
|
|
52
|
+
schedulerJobs: readonly ScheduledJobDeclaration[];
|
|
53
|
+
staticReader: StaticAssetReader;
|
|
54
|
+
/** The composed ports (blob storage, data gateway and the rest) for routes to use. */
|
|
55
|
+
ports: PlatformBundlePorts;
|
|
56
|
+
}) => HttpRuntimePort;
|
|
57
|
+
/** The deployment's declared jobs, used when the caller passes none. */
|
|
58
|
+
loadSchedulerJobs: () => readonly ScheduledJobDeclaration[];
|
|
59
|
+
/** Transport-neutral jobs contributed to the registry; run in-process with no HTTP secret. */
|
|
60
|
+
scheduledJobs?: (env: Env, ports: PlatformBundlePorts) => readonly ScheduledJob[];
|
|
61
|
+
/** The job-lease port for registry jobs that declare a lease. */
|
|
62
|
+
jobLease?: (env: Env) => JobLeasePort;
|
|
63
|
+
/** Direct bindings the deployment and its domains contribute; caller overrides win. */
|
|
64
|
+
scheduledJobHandlers?: (env: Env) => ScheduledJobHandlerRegistry;
|
|
65
|
+
/** Runs first, before readiness (for example, process-wide configuration checks). */
|
|
66
|
+
prepareHost?: (env: Env) => void;
|
|
67
|
+
/** Admits the host before it serves or schedules; a rejection stops the boot. */
|
|
68
|
+
admitHost?: (env: Env, admission: NodeHostAdmission) => Promise<void>;
|
|
69
|
+
/** Operational log sink (defaults to console.log). */
|
|
70
|
+
log?: (message: string) => void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A deployment's scheduler profile: a fenced projection of the declared jobs with its own
|
|
75
|
+
* validation, applied at the host's boot boundaries. Each hook may throw to stop the boot.
|
|
76
|
+
*/
|
|
77
|
+
export interface NodeSchedulerProfile {
|
|
78
|
+
/** Replaces the declared jobs with this profile's projection. Runs after any migration replay. */
|
|
79
|
+
projectJobs(env: Env, jobs: readonly ScheduledJobDeclaration[]): readonly ScheduledJobDeclaration[];
|
|
80
|
+
/** Direct bindings the profile adds; they replace the deployment's and the caller's for a job. */
|
|
81
|
+
handlers?(env: Env): ScheduledJobHandlerRegistry;
|
|
82
|
+
/** Runs once the advisory lock is resolved and before any job is registered. */
|
|
83
|
+
validateSchedule?(input: {
|
|
84
|
+
lock: AdvisoryLock | undefined;
|
|
85
|
+
jobs: readonly ScheduledJobDeclaration[];
|
|
86
|
+
overrides: ScheduledJobHandlerRegistry | undefined;
|
|
87
|
+
}): void;
|
|
88
|
+
/**
|
|
89
|
+
* Runs for each job, in order, immediately before it is registered, with its direct binding if
|
|
90
|
+
* any. A refusal stops the boot; jobs registered before it keep their timers, as the boot fails.
|
|
91
|
+
*/
|
|
92
|
+
validateJob?(job: ScheduledJobDeclaration, directHandler: ScheduledJobHandler | undefined): void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface ServeNodeOptions {
|
|
96
|
+
/** The process environment of this host. */
|
|
97
|
+
env: Env;
|
|
98
|
+
/** dist/ root for static serving (defaults to <cwd>/dist). */
|
|
99
|
+
distDir?: string;
|
|
100
|
+
/** node-cron instance; injected so the dependency stays optional for typecheck and tests. */
|
|
101
|
+
cron?: CronScheduler;
|
|
102
|
+
/** Direct bindings added over the deployment's own for this boot. */
|
|
103
|
+
scheduledJobHandlers?: ScheduledJobHandlerRegistry;
|
|
104
|
+
/** Explicit scheduler projection; production uses the deployment's job registry. */
|
|
105
|
+
schedulerJobs?: readonly ScheduledJobDeclaration[];
|
|
106
|
+
/** Optional scheduler profile applied to the declared jobs (see `NodeSchedulerProfile`). */
|
|
107
|
+
schedulerProfile?: NodeSchedulerProfile;
|
|
108
|
+
/**
|
|
109
|
+
* Per-job cluster mutex. Defaults to the one the environment's connection string describes, so a
|
|
110
|
+
* host that runs more than one instance stops executing every timer once per instance.
|
|
111
|
+
*/
|
|
112
|
+
advisoryLock?: AdvisoryLock;
|
|
113
|
+
/** Skip binding the network listener (unit tests build the runtime without opening a socket). */
|
|
114
|
+
listen?: boolean;
|
|
115
|
+
port?: number;
|
|
116
|
+
host?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface ServeNodeResult {
|
|
120
|
+
bundleId: string;
|
|
121
|
+
httpRuntime: HttpRuntimePort;
|
|
122
|
+
registeredJobs: string[];
|
|
123
|
+
/** Stop timers and the local listener after a host signal; safe to call repeatedly. */
|
|
124
|
+
stop(): Promise<void>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface NodeHost {
|
|
128
|
+
/** Readiness gate: the active bundle's descriptor decides, and the bundle must bind a Node runtime. */
|
|
129
|
+
assertBundleReady(env: Env): { bundleId: string };
|
|
130
|
+
/** Build (and optionally start) the Node host. */
|
|
131
|
+
serveNode(options: ServeNodeOptions): Promise<ServeNodeResult>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function createNodeHost(deps: NodeHostDependencies): NodeHost {
|
|
135
|
+
const log = deps.log ?? ((message: string) => console.log(message));
|
|
136
|
+
|
|
137
|
+
function assertBundleReady(env: Env): { bundleId: string } {
|
|
138
|
+
const bundle = deps.composeBundle(env);
|
|
139
|
+
const readiness = bundle.descriptor.readReadiness(env);
|
|
140
|
+
if (!readiness.ok) {
|
|
141
|
+
throw new Error(`[serve.node] bundle "${bundle.id}" not ready: ${readiness.error}`);
|
|
142
|
+
}
|
|
143
|
+
if (!bundle.ports.http) {
|
|
144
|
+
throw new Error(`[serve.node] bundle "${bundle.id}" has no http runtime (expected node bundle)`);
|
|
145
|
+
}
|
|
146
|
+
return { bundleId: bundle.id };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function serveNode(options: ServeNodeOptions): Promise<ServeNodeResult> {
|
|
150
|
+
const env = options.env;
|
|
151
|
+
deps.prepareHost?.(env);
|
|
152
|
+
const { bundleId } = assertBundleReady(env);
|
|
153
|
+
|
|
154
|
+
// Deploy parity (migrate -> health -> serve): the node-postgres bundle has no external migration
|
|
155
|
+
// step, so it applies the composed MigrationRunnerPort BEFORE serving. Gated on the data
|
|
156
|
+
// capability so managed-schema bundles are untouched. A listening node-postgres process has no
|
|
157
|
+
// bypass: unit tests that need no database use the non-listening seam.
|
|
158
|
+
const bundle = deps.composeBundle(env);
|
|
159
|
+
const shouldMigrate =
|
|
160
|
+
options.listen !== false &&
|
|
161
|
+
bundle.descriptor.capabilities.data === "postgres" &&
|
|
162
|
+
bundle.ports.migrations !== null;
|
|
163
|
+
if (shouldMigrate) {
|
|
164
|
+
const { applied } = await bundle.ports.migrations!.apply();
|
|
165
|
+
log(`[serve.node] bundle=${bundleId} applied ${applied.length} migration(s) before serve`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const distDir = options.distDir ?? resolve(process.cwd(), "dist");
|
|
169
|
+
const staticReader = createDistStaticReader(distDir);
|
|
170
|
+
const declaredSchedulerJobs = options.schedulerJobs ?? deps.loadSchedulerJobs();
|
|
171
|
+
const profile = options.schedulerProfile;
|
|
172
|
+
const schedulerJobs = profile ? profile.projectJobs(env, declaredSchedulerJobs) : declaredSchedulerJobs;
|
|
173
|
+
|
|
174
|
+
const handlers = mergeScheduledJobHandlers(
|
|
175
|
+
deps.scheduledJobHandlers?.(env),
|
|
176
|
+
options.scheduledJobHandlers,
|
|
177
|
+
profile?.handlers?.(env),
|
|
178
|
+
);
|
|
179
|
+
const registryJobs = deps.scheduledJobs?.(env, bundle.ports) ?? [];
|
|
180
|
+
for (const job of registryJobs) {
|
|
181
|
+
if (schedulerJobs.some((declared) => declared.jobId === job.id)) {
|
|
182
|
+
throw new Error(`scheduled_job_declared_twice:${job.id}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (deps.admitHost && (options.listen !== false || options.cron)) {
|
|
186
|
+
await deps.admitHost(env, {
|
|
187
|
+
kind: "node",
|
|
188
|
+
nodeJobHandlers: handlers,
|
|
189
|
+
nodeJobs: options.cron ? schedulerJobs : [],
|
|
190
|
+
scheduledJobs: options.cron ? registryJobs : [],
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const httpRuntime = deps.createHttpRuntime({ schedulerJobs, staticReader, ports: bundle.ports });
|
|
195
|
+
|
|
196
|
+
// Node cron invokes a direct binding where one exists. Every other job takes the HTTP
|
|
197
|
+
// compatibility bridge to its own entrypoint, which the runtime mounts from the same registry,
|
|
198
|
+
// so a routing miss cannot masquerade as a scheduler failure and a job without an entrypoint
|
|
199
|
+
// fails visibly. The scheduler holds the cluster mutex: without it every instance of a
|
|
200
|
+
// horizontally scaled host fires the same timer in the same minute.
|
|
201
|
+
const registeredJobs: string[] = [];
|
|
202
|
+
let scheduler: NodeSchedulerAdapter | null = null;
|
|
203
|
+
let schedulerLock: AdvisoryLock | undefined;
|
|
204
|
+
if (options.cron) {
|
|
205
|
+
schedulerLock = options.advisoryLock ?? createEnvAdvisoryLock(env) ?? undefined;
|
|
206
|
+
scheduler = createNodeScheduler({
|
|
207
|
+
cron: options.cron,
|
|
208
|
+
env,
|
|
209
|
+
jobs: schedulerJobs,
|
|
210
|
+
lock: schedulerLock,
|
|
211
|
+
});
|
|
212
|
+
profile?.validateSchedule?.({
|
|
213
|
+
lock: schedulerLock,
|
|
214
|
+
jobs: scheduler.jobs,
|
|
215
|
+
overrides: options.scheduledJobHandlers,
|
|
216
|
+
});
|
|
217
|
+
const baseUrl = env.APP_BASE_URL ?? "http://localhost:3000";
|
|
218
|
+
const cronSecret = env.CRON_SECRET ?? "";
|
|
219
|
+
for (const job of scheduler.jobs) {
|
|
220
|
+
const directHandler = handlers[job.jobId];
|
|
221
|
+
profile?.validateJob?.(job, directHandler);
|
|
222
|
+
scheduler.register({
|
|
223
|
+
jobId: job.jobId,
|
|
224
|
+
schedule: job.schedule,
|
|
225
|
+
handler: async () => {
|
|
226
|
+
if (directHandler) {
|
|
227
|
+
await directHandler({ jobId: job.jobId, invocationSource: "node_cron" });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const response = await fetch(new URL(job.path, baseUrl).toString(), {
|
|
231
|
+
method: "GET",
|
|
232
|
+
headers: cronSecret ? { Authorization: `Bearer ${cronSecret}` } : {},
|
|
233
|
+
});
|
|
234
|
+
if (!response.ok) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`node scheduler compatibility bridge for "${job.jobId}" returned HTTP ${response.status}`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
registeredJobs.push(job.jobId);
|
|
242
|
+
}
|
|
243
|
+
const lease = registryJobs.some((job) => job.lease) ? deps.jobLease?.(env) : undefined;
|
|
244
|
+
for (const job of registryJobs) {
|
|
245
|
+
scheduler.register({
|
|
246
|
+
jobId: job.id,
|
|
247
|
+
schedule: job.schedule,
|
|
248
|
+
handler: async () => {
|
|
249
|
+
await runScheduledJob(job, { invocation: { triggerKind: "scheduler", invocationSource: "node_cron" }, lease });
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
registeredJobs.push(job.id);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (options.listen !== false) {
|
|
257
|
+
const port = options.port ?? Number(env.PORT ?? 3000);
|
|
258
|
+
await httpRuntime.listen?.({ port, host: options.host ?? "0.0.0.0" });
|
|
259
|
+
log(`[serve.node] bundle=${bundleId} listening on :${port} (${registeredJobs.length} cron jobs)`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
let stopPromise: Promise<void> | null = null;
|
|
263
|
+
return {
|
|
264
|
+
bundleId,
|
|
265
|
+
httpRuntime,
|
|
266
|
+
registeredJobs,
|
|
267
|
+
stop: () => (stopPromise ??= stopNodeHost(scheduler, schedulerLock, httpRuntime)),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return { assertBundleReady, serveNode };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function stopNodeHost(
|
|
275
|
+
scheduler: NodeSchedulerAdapter | null,
|
|
276
|
+
advisoryLock: AdvisoryLock | undefined,
|
|
277
|
+
httpRuntime: HttpRuntimePort,
|
|
278
|
+
): Promise<void> {
|
|
279
|
+
const timers = Promise.resolve().then(() => scheduler?.stopAll());
|
|
280
|
+
const server = (httpRuntime as HttpRuntimePort & {
|
|
281
|
+
server?: { close(callback: (error?: Error) => void): void } | null;
|
|
282
|
+
}).server;
|
|
283
|
+
const listener = server
|
|
284
|
+
? new Promise<void>((resolveClose, reject) => server.close((error) => error ? reject(error) : resolveClose()))
|
|
285
|
+
: Promise.resolve();
|
|
286
|
+
const results = await Promise.allSettled([timers, advisoryLock?.close(), listener]);
|
|
287
|
+
const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
288
|
+
if (failures.length > 0) throw new AggregateError(failures, "node_host_shutdown_failed");
|
|
289
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// DataGatewayPort bindings for bundle composition.
|
|
2
|
+
//
|
|
3
|
+
// Maps a bundle's declared `data` capability to a concrete DataGatewayPort. The supabase gateway is
|
|
4
|
+
// constructed eagerly (pure factory; no client/network until a port method runs work), so the
|
|
5
|
+
// default vercel-supabase path binds the slot with no secrets needed: env is read with
|
|
6
|
+
// empty-string fallbacks and validated lazily inside asActor/asService. The application supplies
|
|
7
|
+
// the supabase-js `createClient` once, when it creates its bindings.
|
|
8
|
+
//
|
|
9
|
+
// The `postgres` capability (node-postgres) uses the existing direct-PG DataGatewayPort. Its
|
|
10
|
+
// PgGatewayClient exposes the same narrow RPC surface used by Supabase-backed domain ports.
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
getBundleDescriptor,
|
|
14
|
+
resolveBundleId,
|
|
15
|
+
} from "../kernel/index.js";
|
|
16
|
+
import { createSupabaseDataGateway } from "./supabaseGateway.js";
|
|
17
|
+
import {
|
|
18
|
+
createPostgresDataGateway,
|
|
19
|
+
resolvePostgresDataGatewayEnv,
|
|
20
|
+
type PostgresDataGatewayEnv,
|
|
21
|
+
} from "./postgresGateway.js";
|
|
22
|
+
import type { DataGatewayPort } from "@openlup/core/platform-runtime";
|
|
23
|
+
import type { SupabaseCreateClient } from "./supabaseClientFactory.js";
|
|
24
|
+
|
|
25
|
+
type Env = Record<string, string | undefined>;
|
|
26
|
+
type ActorClaims = { sub?: string; role?: string } | null;
|
|
27
|
+
/** Actor-only data access for one request. */
|
|
28
|
+
export type ActorOnlyDataPort = {
|
|
29
|
+
asActor: <T>(
|
|
30
|
+
claims: ActorClaims,
|
|
31
|
+
work: (gateway: unknown) => Promise<T>,
|
|
32
|
+
) => Promise<T>;
|
|
33
|
+
};
|
|
34
|
+
export interface DataPortBindingOptions {
|
|
35
|
+
/** Replaces the bindings' supabase-js `createClient` for this call (tests). */
|
|
36
|
+
createClientImpl?: SupabaseCreateClient;
|
|
37
|
+
resolveAccessToken?: (claims: ActorClaims) => string | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface CachedBundleDataPort {
|
|
41
|
+
cacheKey: string;
|
|
42
|
+
port: DataGatewayPort | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DataBindingDependencies {
|
|
46
|
+
/** The supabase-js `createClient` used by every supabase gateway these bindings build. */
|
|
47
|
+
createSupabaseClient: SupabaseCreateClient;
|
|
48
|
+
/** Builds the direct-PostgreSQL gateway; defaults to `createPostgresDataGateway`. */
|
|
49
|
+
createPostgresGateway?: (env: PostgresDataGatewayEnv) => DataGatewayPort;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface DataBindings {
|
|
53
|
+
/** Resolve the DataGatewayPort for a bundle's declared data capability kind. */
|
|
54
|
+
bindDataPort(dataKind: string, env: Env, requestOptions?: DataPortBindingOptions): DataGatewayPort | null;
|
|
55
|
+
/** Bind one request to actor-only data access. */
|
|
56
|
+
bindRequestActorDataPort(
|
|
57
|
+
accessToken: string,
|
|
58
|
+
env: Env,
|
|
59
|
+
requestOptions?: DataPortBindingOptions,
|
|
60
|
+
): ActorOnlyDataPort | null;
|
|
61
|
+
/** Resolve and bind the active bundle's data gateway, cached per bundle and data configuration. */
|
|
62
|
+
bindBundleDataPort(env: Env): DataGatewayPort | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Create one set of data bindings; the bundle data port cache belongs to this set. */
|
|
66
|
+
export function createDataBindings(deps: DataBindingDependencies): DataBindings {
|
|
67
|
+
let cachedBundleDataPort: CachedBundleDataPort | null = null;
|
|
68
|
+
const createPostgresGateway = deps.createPostgresGateway ?? ((env: PostgresDataGatewayEnv) => createPostgresDataGateway(env));
|
|
69
|
+
|
|
70
|
+
/** Resolve the DataGatewayPort for a bundle's declared data capability kind. */
|
|
71
|
+
function bindDataPort(
|
|
72
|
+
dataKind: string,
|
|
73
|
+
env: Env,
|
|
74
|
+
requestOptions: DataPortBindingOptions = {},
|
|
75
|
+
): DataGatewayPort | null {
|
|
76
|
+
switch (dataKind) {
|
|
77
|
+
case "supabase":
|
|
78
|
+
// Construct from env with empty-string fallbacks; the actual supabase client is created
|
|
79
|
+
// lazily when asActor/asService runs work, so the slot binds even before secrets are present
|
|
80
|
+
// (same as the blob/transactional/analytics slots — keeps composeBundle({}) non-null).
|
|
81
|
+
return createSupabaseDataGateway({
|
|
82
|
+
url: env.SUPABASE_URL ?? env.VITE_SUPABASE_URL ?? "",
|
|
83
|
+
anonKey:
|
|
84
|
+
env.VITE_SUPABASE_PUBLISHABLE_KEY ??
|
|
85
|
+
env.VITE_SUPABASE_ANON_KEY ??
|
|
86
|
+
env.SUPABASE_ANON_KEY ??
|
|
87
|
+
"",
|
|
88
|
+
serviceRoleKey: env.SUPABASE_SERVICE_ROLE_KEY ?? "",
|
|
89
|
+
}, { ...requestOptions, createClientImpl: requestOptions.createClientImpl ?? deps.createSupabaseClient });
|
|
90
|
+
case "postgres":
|
|
91
|
+
// Direct-PG gateway (pg.Pool, tx-per-request RLS via SET LOCAL). Pure factory — no pool
|
|
92
|
+
// until asActor/asService runs work, so the slot binds even before DATABASE_URL is present
|
|
93
|
+
// (matches the supabase case's lazy null-is-no-op convention; keeps composeBundle non-null).
|
|
94
|
+
return createPostgresGateway(resolvePostgresDataGatewayEnv(env));
|
|
95
|
+
default:
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Bind one request to actor-only data access.
|
|
102
|
+
*
|
|
103
|
+
* HTTP-backed bundles receive a fresh gateway so the raw bearer resolver cannot
|
|
104
|
+
* cross request boundaries. The direct database bundle reuses the existing
|
|
105
|
+
* cached port (and therefore its lazy pool); only this actor-only facade is new.
|
|
106
|
+
*/
|
|
107
|
+
function bindRequestActorDataPort(
|
|
108
|
+
accessToken: string,
|
|
109
|
+
env: Env,
|
|
110
|
+
requestOptions: DataPortBindingOptions = {},
|
|
111
|
+
): ActorOnlyDataPort | null {
|
|
112
|
+
const bundleId = resolveBundleId(env);
|
|
113
|
+
const dataKind = getBundleDescriptor(bundleId).capabilities.data;
|
|
114
|
+
const port = dataKind === "postgres"
|
|
115
|
+
? bindBundleDataPort(env)
|
|
116
|
+
: bindDataPort(dataKind, env, {
|
|
117
|
+
...requestOptions,
|
|
118
|
+
resolveAccessToken: () => accessToken,
|
|
119
|
+
});
|
|
120
|
+
if (!port) return null;
|
|
121
|
+
return {
|
|
122
|
+
asActor: (claims, work) => port.asActor(claims, work),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Resolve and bind the active bundle's data gateway without composing the whole runtime. */
|
|
127
|
+
function bindBundleDataPort(env: Env): DataGatewayPort | null {
|
|
128
|
+
const bundleId = resolveBundleId(env);
|
|
129
|
+
const descriptor = getBundleDescriptor(bundleId);
|
|
130
|
+
const cacheKey = bundleDataPortCacheKey(bundleId, descriptor.capabilities.data, env);
|
|
131
|
+
if (cachedBundleDataPort?.cacheKey === cacheKey) {
|
|
132
|
+
return cachedBundleDataPort.port;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const port = bindDataPort(descriptor.capabilities.data, env);
|
|
136
|
+
cachedBundleDataPort = { cacheKey, port };
|
|
137
|
+
return port;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { bindDataPort, bindRequestActorDataPort, bindBundleDataPort };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function bundleDataPortCacheKey(bundleId: string, dataKind: string, env: Env): string {
|
|
144
|
+
const dataConfig = dataKind === "postgres"
|
|
145
|
+
? [env.DATABASE_URL ?? ""]
|
|
146
|
+
: dataKind === "supabase"
|
|
147
|
+
? [
|
|
148
|
+
env.SUPABASE_URL ?? env.VITE_SUPABASE_URL ?? "",
|
|
149
|
+
env.VITE_SUPABASE_PUBLISHABLE_KEY ??
|
|
150
|
+
env.VITE_SUPABASE_ANON_KEY ??
|
|
151
|
+
env.SUPABASE_ANON_KEY ??
|
|
152
|
+
"",
|
|
153
|
+
env.SUPABASE_SERVICE_ROLE_KEY ?? "",
|
|
154
|
+
]
|
|
155
|
+
: [];
|
|
156
|
+
return JSON.stringify([bundleId, dataKind, ...dataConfig]);
|
|
157
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createPostgresDataGateway,
|
|
3
|
+
createPostgresGatewayTypeOverrides,
|
|
4
|
+
createPostgresRoleFreeTransactionLane,
|
|
5
|
+
postgresTimestampTzToRfc3339,
|
|
6
|
+
resolvePostgresDataGatewayEnv,
|
|
7
|
+
type PostgresDataGatewayEnv,
|
|
8
|
+
type PostgresDataGatewayOptions,
|
|
9
|
+
type PostgresRoleFreeTransactionLane,
|
|
10
|
+
} from "./postgresGateway.js";
|
|
11
|
+
export {
|
|
12
|
+
PgGatewayClient,
|
|
13
|
+
PgQueryBuilder,
|
|
14
|
+
type PgQueryExecutor,
|
|
15
|
+
type PgShimError,
|
|
16
|
+
type PgShimResult,
|
|
17
|
+
} from "./queryBuilder.js";
|
|
18
|
+
export {
|
|
19
|
+
createActorClient,
|
|
20
|
+
createServiceClient,
|
|
21
|
+
readSupabaseActorDataGatewayEnv,
|
|
22
|
+
readSupabaseDataGatewayEnv,
|
|
23
|
+
type SupabaseCreateClient,
|
|
24
|
+
type SupabaseDataGatewayEnv,
|
|
25
|
+
} from "./supabaseClientFactory.js";
|
|
26
|
+
export { createSupabaseDataGateway, type SupabaseDataGatewayOptions } from "./supabaseGateway.js";
|
|
27
|
+
export {
|
|
28
|
+
createDataBindings,
|
|
29
|
+
type ActorOnlyDataPort,
|
|
30
|
+
type DataBindingDependencies,
|
|
31
|
+
type DataBindings,
|
|
32
|
+
type DataPortBindingOptions,
|
|
33
|
+
} from "./binding.js";
|