@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,119 @@
|
|
|
1
|
+
// Transport-neutral scheduled jobs.
|
|
2
|
+
//
|
|
3
|
+
// A job is `{ id, schedule, lease?, run(context) }`. Domains and packages contribute jobs to a
|
|
4
|
+
// registry; a host runs them on its own timer or through an authenticated HTTP cron entry, and both
|
|
5
|
+
// call the same `run`. The in-process path carries no HTTP secret. When a job declares a lease, the
|
|
6
|
+
// runner claims it through the deployment's job-lease port first and skips the run when another
|
|
7
|
+
// instance holds it; a job that fences its own work (for example the outbox) declares no lease.
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
JobLeasePort,
|
|
11
|
+
PlatformJobFinishSummary,
|
|
12
|
+
PlatformJobInvocation,
|
|
13
|
+
} from "@openlup/core/platform-runtime";
|
|
14
|
+
|
|
15
|
+
export interface ScheduledJobContext {
|
|
16
|
+
/** Who triggered this run; evidence only, never an authorization control. */
|
|
17
|
+
readonly invocation: PlatformJobInvocation;
|
|
18
|
+
/** The lease's run id when the job declares a lease. */
|
|
19
|
+
readonly runId?: string;
|
|
20
|
+
readonly signal?: AbortSignal;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ScheduledJob {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
/** Crontab expression (UTC). */
|
|
26
|
+
readonly schedule: string;
|
|
27
|
+
/** Claim a job-run lease of this many seconds before running. */
|
|
28
|
+
readonly lease?: { readonly seconds: number };
|
|
29
|
+
run(context: ScheduledJobContext): Promise<PlatformJobFinishSummary | void> | PlatformJobFinishSummary | void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type ScheduledJobRunResult =
|
|
33
|
+
| { readonly status: "completed"; readonly summary: PlatformJobFinishSummary | null }
|
|
34
|
+
| { readonly status: "skipped"; readonly reason: string };
|
|
35
|
+
|
|
36
|
+
const EMPTY_SUMMARY: PlatformJobFinishSummary = { checked: 0, updated: 0, failures: 0, skipped: false };
|
|
37
|
+
|
|
38
|
+
/** Merge contributed jobs into one registry; refuses a duplicate id or an empty id or schedule. */
|
|
39
|
+
export function createScheduledJobRegistry(
|
|
40
|
+
...contributions: ReadonlyArray<readonly ScheduledJob[]>
|
|
41
|
+
): readonly ScheduledJob[] {
|
|
42
|
+
const seen = new Set<string>();
|
|
43
|
+
const jobs: ScheduledJob[] = [];
|
|
44
|
+
for (const job of contributions.flat()) {
|
|
45
|
+
if (!job.id?.trim()) throw new Error("scheduled_job_id_required");
|
|
46
|
+
if (!job.schedule?.trim()) throw new Error(`scheduled_job_schedule_required:${job.id}`);
|
|
47
|
+
if (seen.has(job.id)) throw new Error(`scheduled_job_duplicate:${job.id}`);
|
|
48
|
+
seen.add(job.id);
|
|
49
|
+
jobs.push(job);
|
|
50
|
+
}
|
|
51
|
+
return Object.freeze(jobs);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The cron expression for a fixed cadence: a whole number of minutes that divides an hour, or a
|
|
56
|
+
* whole number of hours that divides a day. Any other cadence is refused rather than approximated.
|
|
57
|
+
*/
|
|
58
|
+
export function cronFromCadenceSeconds(cadenceSeconds: number): string {
|
|
59
|
+
if (Number.isInteger(cadenceSeconds) && cadenceSeconds > 0 && cadenceSeconds % 60 === 0) {
|
|
60
|
+
const minutes = cadenceSeconds / 60;
|
|
61
|
+
if (minutes < 60 && 60 % minutes === 0) return minutes === 1 ? "* * * * *" : `*/${minutes} * * * *`;
|
|
62
|
+
if (minutes % 60 === 0) {
|
|
63
|
+
const hours = minutes / 60;
|
|
64
|
+
if (hours === 24) return "0 0 * * *";
|
|
65
|
+
if (hours < 24 && 24 % hours === 0) return hours === 1 ? "0 * * * *" : `0 */${hours} * * *`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`scheduled_job_cadence_unsupported:${cadenceSeconds}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Jobs for a package contribution's cadence schedules; each run passes the invocation through. A
|
|
73
|
+
* run that reports `{ outcome: "failed" }` instead of throwing fails the job, so neither transport
|
|
74
|
+
* reports it as completed.
|
|
75
|
+
*/
|
|
76
|
+
export function scheduledJobsFromContribution(
|
|
77
|
+
schedules: ReadonlyArray<{ id: string; cadenceSeconds: number; run: (invocation: PlatformJobInvocation) => unknown }>,
|
|
78
|
+
): ScheduledJob[] {
|
|
79
|
+
return schedules.map((schedule) => ({
|
|
80
|
+
id: schedule.id,
|
|
81
|
+
schedule: cronFromCadenceSeconds(schedule.cadenceSeconds),
|
|
82
|
+
async run({ invocation }) {
|
|
83
|
+
const result = await schedule.run(invocation);
|
|
84
|
+
if (typeof result === "object" && result !== null && (result as { outcome?: unknown }).outcome === "failed") {
|
|
85
|
+
throw new Error(`scheduled_job_outcome_failed:${schedule.id}`);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Run one job once, under its lease when it declares one. */
|
|
92
|
+
export async function runScheduledJob(
|
|
93
|
+
job: ScheduledJob,
|
|
94
|
+
options: { invocation: PlatformJobInvocation; lease?: JobLeasePort; signal?: AbortSignal },
|
|
95
|
+
): Promise<ScheduledJobRunResult> {
|
|
96
|
+
if (!job.lease) {
|
|
97
|
+
const summary = await job.run({ invocation: options.invocation, signal: options.signal });
|
|
98
|
+
return { status: "completed", summary: summary ?? null };
|
|
99
|
+
}
|
|
100
|
+
if (!options.lease) throw new Error(`scheduled_job_lease_port_required:${job.id}`);
|
|
101
|
+
const claim = await options.lease.claimJobRun(job.id, options.invocation, job.lease.seconds);
|
|
102
|
+
if (!claim.acquired) return { status: "skipped", reason: claim.reason };
|
|
103
|
+
if (!claim.runId) throw new Error(`scheduled_job_lease_without_run_id:${job.id}`);
|
|
104
|
+
let summary: PlatformJobFinishSummary | void;
|
|
105
|
+
try {
|
|
106
|
+
summary = await job.run({ invocation: options.invocation, runId: claim.runId, signal: options.signal });
|
|
107
|
+
} catch (error) {
|
|
108
|
+
try {
|
|
109
|
+
await options.lease.finishJobRun(job.id, claim.runId, options.invocation, "failed", { ...EMPTY_SUMMARY, failures: 1 });
|
|
110
|
+
} catch (finishError) {
|
|
111
|
+
throw new AggregateError([error, finishError], `scheduled_job_failed_unfinished:${job.id}`);
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
const finished = await options.lease.finishJobRun(job.id, claim.runId, options.invocation, "success", summary ?? EMPTY_SUMMARY);
|
|
116
|
+
// A refused finish means the lease no longer belongs to this run; its result cannot be recorded.
|
|
117
|
+
if (!finished) throw new Error(`scheduled_job_finish_refused:${job.id}`);
|
|
118
|
+
return { status: "completed", summary: summary ?? null };
|
|
119
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Node (self-hosted) SchedulerPort adapter.
|
|
2
|
+
//
|
|
3
|
+
// Unlike a managed host with an adopter-owned declarative cron manifest, a self-hosted Node bundle owns the timers itself.
|
|
4
|
+
// This adapter:
|
|
5
|
+
// 1. Takes its job set from the platform-runtime configuration `crons` (the SAME single source
|
|
6
|
+
// the managed-host adapter also reads), never a hand-copied list.
|
|
7
|
+
// 2. Registers each job with node-cron (injected lazily so `pg`/`node-cron` aren't required to
|
|
8
|
+
// typecheck or to run the default vercel-supabase path).
|
|
9
|
+
// 3. Wraps every job body in a PER-JOB advisory lock: multi-instance Node must not
|
|
10
|
+
// double-run a cron. Only the instance that acquires `pg_try_advisory_lock(<job>)` runs; others
|
|
11
|
+
// skip. The lock is released in a finally, even if the body throws.
|
|
12
|
+
|
|
13
|
+
import type { SchedulerPort } from "@openlup/core/platform-runtime";
|
|
14
|
+
import type { HttpRequest } from "../http/index.js";
|
|
15
|
+
import type { ScheduledJobDeclaration } from "./jobRegistry.js";
|
|
16
|
+
import { verifyCronAuth } from "./vercelScheduler.js";
|
|
17
|
+
import { createNoopAdvisoryLock, type AdvisoryLock } from "./advisoryLock.js";
|
|
18
|
+
|
|
19
|
+
type CronEnv = Record<string, string | undefined> & { CRON_SECRET?: string };
|
|
20
|
+
|
|
21
|
+
/** Minimal node-cron surface we depend on (keeps the package optional at type level). */
|
|
22
|
+
export interface CronScheduler {
|
|
23
|
+
schedule(
|
|
24
|
+
expression: string,
|
|
25
|
+
handler: () => void | Promise<void>,
|
|
26
|
+
options?: { timezone?: string },
|
|
27
|
+
): { stop: () => void };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface RegisteredTimer {
|
|
31
|
+
jobId: string;
|
|
32
|
+
stop: () => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface NodeSchedulerAdapter extends SchedulerPort {
|
|
36
|
+
readonly jobs: readonly ScheduledJobDeclaration[];
|
|
37
|
+
/** Stop all registered node-cron timers (graceful shutdown). */
|
|
38
|
+
stopAll(): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CreateNodeSchedulerOptions {
|
|
42
|
+
/** node-cron instance (inject `import("node-cron")` at the host boot site). */
|
|
43
|
+
cron: CronScheduler;
|
|
44
|
+
/** Per-job advisory lock; defaults to a no-op (single-instance) — supply pg-backed in prod. */
|
|
45
|
+
lock?: AdvisoryLock;
|
|
46
|
+
/** Environment whose CRON_SECRET verifies an invocation. */
|
|
47
|
+
env: CronEnv;
|
|
48
|
+
/** The deployment's declared jobs (see `loadJobRegistry`). */
|
|
49
|
+
jobs: readonly ScheduledJobDeclaration[];
|
|
50
|
+
/** crontab timezone; jobs are authored in UTC to match Vercel. */
|
|
51
|
+
timezone?: string;
|
|
52
|
+
/** Structured error sink for failed job bodies (defaults to console.error). */
|
|
53
|
+
onError?: (jobId: string, error: unknown) => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Wrap a job body with the per-job advisory lock. Returns a guarded runner that:
|
|
58
|
+
* - tries to acquire the lock; if not acquired (another instance is running it) -> skip silently;
|
|
59
|
+
* - runs the body; always releases the lock in finally.
|
|
60
|
+
*/
|
|
61
|
+
export function withAdvisoryLock(
|
|
62
|
+
jobId: string,
|
|
63
|
+
lock: AdvisoryLock,
|
|
64
|
+
body: () => Promise<void> | void,
|
|
65
|
+
onError: (jobId: string, error: unknown) => void,
|
|
66
|
+
): () => Promise<void> {
|
|
67
|
+
return async () => {
|
|
68
|
+
const acquired = await lock.tryAcquire(jobId);
|
|
69
|
+
if (!acquired) {
|
|
70
|
+
// Another instance holds the lock for this minute — skip (no double-run).
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await body();
|
|
75
|
+
} catch (error) {
|
|
76
|
+
onError(jobId, error);
|
|
77
|
+
} finally {
|
|
78
|
+
await lock.release(jobId);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Build the Node scheduler adapter backed by node-cron + per-job advisory locks. */
|
|
84
|
+
export function createNodeScheduler(options: CreateNodeSchedulerOptions): NodeSchedulerAdapter {
|
|
85
|
+
const env = options.env;
|
|
86
|
+
const jobs = options.jobs;
|
|
87
|
+
const lock = options.lock ?? createNoopAdvisoryLock();
|
|
88
|
+
const onError =
|
|
89
|
+
options.onError ??
|
|
90
|
+
((jobId: string, error: unknown) => {
|
|
91
|
+
console.error(`[node-scheduler] job "${jobId}" failed`, error);
|
|
92
|
+
});
|
|
93
|
+
const scheduleById = new Map(jobs.map((job) => [job.jobId, job.schedule]));
|
|
94
|
+
const timers: RegisteredTimer[] = [];
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
jobs,
|
|
98
|
+
register(input) {
|
|
99
|
+
// Prefer an explicit schedule; fall back to the single-source registry when the caller
|
|
100
|
+
// omits one (empty string or undefined both fall back).
|
|
101
|
+
const explicit = input.schedule?.trim();
|
|
102
|
+
const schedule = explicit && explicit.length > 0 ? explicit : scheduleById.get(input.jobId);
|
|
103
|
+
if (!schedule) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`node-scheduler: job "${input.jobId}" is not declared in config/platform-runtime.json crons`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
const guarded = withAdvisoryLock(input.jobId, lock, input.handler, onError);
|
|
109
|
+
const timer = options.cron.schedule(schedule, guarded, { timezone: options.timezone });
|
|
110
|
+
timers.push({ jobId: input.jobId, stop: timer.stop });
|
|
111
|
+
},
|
|
112
|
+
verifyInvocation(req: HttpRequest) {
|
|
113
|
+
// Self-hosted invocations still present the shared CRON_SECRET; reuse the canonical check.
|
|
114
|
+
return verifyCronAuth(req, env);
|
|
115
|
+
},
|
|
116
|
+
stopAll() {
|
|
117
|
+
for (const timer of timers) {
|
|
118
|
+
timer.stop();
|
|
119
|
+
}
|
|
120
|
+
timers.length = 0;
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Vercel cron SchedulerPort adapter.
|
|
2
|
+
//
|
|
3
|
+
// On Vercel, scheduling is declarative: an adopter-owned `crons` manifest generated from the
|
|
4
|
+
// platform-runtime configuration (the same single source as `jobs`) tells Vercel to GET
|
|
5
|
+
// each `/api/cron/*` endpoint on its schedule. So `register()` is a no-op verification step here —
|
|
6
|
+
// the platform owns the timer. What this adapter OWNS is invocation authenticity: every cron
|
|
7
|
+
// endpoint must verify `Authorization: Bearer ${CRON_SECRET}`, fail-closed when the secret is unset.
|
|
8
|
+
//
|
|
9
|
+
// `verifyCronAuth(req, env)` is the reusable form of a bearer check against CRON_SECRET; cron
|
|
10
|
+
// handlers and the Node host call it instead of re-implementing the comparison.
|
|
11
|
+
|
|
12
|
+
import type { PlatformHttpRequest, SchedulerPort } from "@openlup/core/platform-runtime";
|
|
13
|
+
import type { ScheduledJobDeclaration } from "./jobRegistry.js";
|
|
14
|
+
import { bearerToken, cronSecretMatches } from "./cronSecret.js";
|
|
15
|
+
|
|
16
|
+
type CronEnv = Record<string, string | undefined> & { CRON_SECRET?: string };
|
|
17
|
+
|
|
18
|
+
export { bearerToken } from "./cronSecret.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Verify a cron invocation is authentic. Fail-CLOSED:
|
|
22
|
+
* - CRON_SECRET unset -> false (never authorize when no secret configured)
|
|
23
|
+
* - presented Bearer token != CRON_SECRET -> false
|
|
24
|
+
* - presented Bearer token == CRON_SECRET -> true
|
|
25
|
+
* This is the single canonical check that replaces the per-handler inline comparisons.
|
|
26
|
+
*/
|
|
27
|
+
export function verifyCronAuth(req: PlatformHttpRequest, env: CronEnv): boolean {
|
|
28
|
+
const expected = env.CRON_SECRET;
|
|
29
|
+
if (!expected) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return cronSecretMatches(bearerToken(req), expected);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface VercelSchedulerAdapter extends SchedulerPort {
|
|
36
|
+
/** The jobs this scheduler knows about, derived from the platform-runtime single source. */
|
|
37
|
+
readonly jobs: readonly ScheduledJobDeclaration[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build the Vercel cron scheduler adapter.
|
|
42
|
+
*
|
|
43
|
+
* `register()` validates the job is part of the declared registry (so callers can't silently
|
|
44
|
+
* register a job Vercel will never fire) but does NOT start a timer — Vercel's cron does that.
|
|
45
|
+
*/
|
|
46
|
+
export function createVercelScheduler(options: {
|
|
47
|
+
env: CronEnv;
|
|
48
|
+
/** The deployment's declared jobs (see `loadJobRegistry`). */
|
|
49
|
+
jobs: readonly ScheduledJobDeclaration[];
|
|
50
|
+
}): VercelSchedulerAdapter {
|
|
51
|
+
const env = options.env;
|
|
52
|
+
const jobs = options.jobs;
|
|
53
|
+
const knownIds = new Set(jobs.map((job) => job.jobId));
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
jobs,
|
|
57
|
+
register(input) {
|
|
58
|
+
// Vercel owns the timer through the adopter manifest. We only guard that the job is declared,
|
|
59
|
+
// surfacing drift between code and the single-source registry at registration time.
|
|
60
|
+
if (!knownIds.has(input.jobId)) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`vercel-scheduler: job "${input.jobId}" is not declared in config/platform-runtime.json crons`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
// No-op timer: the handler at the cron path is invoked by Vercel directly.
|
|
66
|
+
},
|
|
67
|
+
verifyInvocation(req) {
|
|
68
|
+
return verifyCronAuth(req, env);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|