bunqueue 2.8.59 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -77
- package/dist/application/background/dlq.js +2 -0
- package/dist/application/contextFactory.js +5 -0
- package/dist/application/dlqManager.d.ts +11 -1
- package/dist/application/dlqManager.js +48 -10
- package/dist/application/dlqRetry.js +59 -38
- package/dist/application/eventsManager.d.ts +1 -1
- package/dist/application/eventsManager.js +30 -26
- package/dist/application/lockManager.js +1 -1
- package/dist/application/operations/jobManagement.d.ts +4 -0
- package/dist/application/operations/jobMoveOperations.js +9 -4
- package/dist/application/postgres-queue-manager/batchSnapshot.d.ts +16 -0
- package/dist/application/postgres-queue-manager/batchSnapshot.js +17 -0
- package/dist/application/postgres-queue-manager/cloud.d.ts +30 -0
- package/dist/application/postgres-queue-manager/cloud.js +55 -0
- package/dist/application/postgres-queue-manager/config.d.ts +5 -0
- package/dist/application/postgres-queue-manager/config.js +1 -0
- package/dist/application/postgres-queue-manager/control.d.ts +26 -0
- package/dist/application/postgres-queue-manager/control.js +186 -0
- package/dist/application/postgres-queue-manager/deferredWrites.d.ts +13 -0
- package/dist/application/postgres-queue-manager/deferredWrites.js +60 -0
- package/dist/application/postgres-queue-manager/delivery.d.ts +19 -0
- package/dist/application/postgres-queue-manager/delivery.js +80 -0
- package/dist/application/postgres-queue-manager/lease.d.ts +13 -0
- package/dist/application/postgres-queue-manager/lease.js +88 -0
- package/dist/application/postgres-queue-manager/lifetimeMetrics.d.ts +32 -0
- package/dist/application/postgres-queue-manager/lifetimeMetrics.js +53 -0
- package/dist/application/postgres-queue-manager/maintenance.d.ts +14 -0
- package/dist/application/postgres-queue-manager/maintenance.js +100 -0
- package/dist/application/postgres-queue-manager/operationGate.d.ts +26 -0
- package/dist/application/postgres-queue-manager/operationGate.js +96 -0
- package/dist/application/postgres-queue-manager/operations.d.ts +25 -0
- package/dist/application/postgres-queue-manager/operations.js +194 -0
- package/dist/application/postgres-queue-manager/projectionRefreshes.d.ts +43 -0
- package/dist/application/postgres-queue-manager/projectionRefreshes.js +170 -0
- package/dist/application/postgres-queue-manager/queries.d.ts +142 -0
- package/dist/application/postgres-queue-manager/queries.js +231 -0
- package/dist/application/postgres-queue-manager/relationships.d.ts +10 -0
- package/dist/application/postgres-queue-manager/relationships.js +45 -0
- package/dist/application/postgres-queue-manager/services.d.ts +31 -0
- package/dist/application/postgres-queue-manager/services.js +149 -0
- package/dist/application/postgres-queue-manager/snapshot.d.ts +51 -0
- package/dist/application/postgres-queue-manager/snapshot.js +236 -0
- package/dist/application/postgres-queue-manager/snapshotViews.d.ts +15 -0
- package/dist/application/postgres-queue-manager/snapshotViews.js +100 -0
- package/dist/application/postgres-queue-manager/startupEventBuffer.d.ts +12 -0
- package/dist/application/postgres-queue-manager/startupEventBuffer.js +36 -0
- package/dist/application/postgres-queue-manager/state.d.ts +47 -0
- package/dist/application/postgres-queue-manager/state.js +273 -0
- package/dist/application/postgres-queue-manager/terminalDelivery.d.ts +20 -0
- package/dist/application/postgres-queue-manager/terminalDelivery.js +100 -0
- package/dist/application/postgresQueueManager.d.ts +2 -0
- package/dist/application/postgresQueueManager.js +1 -0
- package/dist/application/queue-manager/ack.js +1 -1
- package/dist/application/queue-manager/delivery.js +1 -1
- package/dist/application/queue-manager/flow-failures.js +4 -0
- package/dist/application/queue-manager/limits.d.ts +2 -0
- package/dist/application/queue-manager/limits.js +4 -0
- package/dist/application/queue-manager/observability.js +15 -3
- package/dist/application/queue-manager/queries.d.ts +7 -0
- package/dist/application/queue-manager/queries.js +9 -0
- package/dist/application/queue-manager/services.d.ts +1 -1
- package/dist/application/queue-manager/services.js +4 -2
- package/dist/application/queue-manager/state.js +16 -1
- package/dist/application/statsManager.d.ts +1 -1
- package/dist/application/statsManager.js +1 -1
- package/dist/cli/commands/server.js +1 -1
- package/dist/client/events.js +2 -2
- package/dist/client/flow.js +0 -1
- package/dist/client/forwarder.d.ts +6 -4
- package/dist/client/forwarder.js +5 -3
- package/dist/client/queue/dlq.d.ts +2 -0
- package/dist/client/queue/dlq.js +14 -0
- package/dist/client/queue/dlqOps.d.ts +2 -0
- package/dist/client/queue/dlqOps.js +4 -0
- package/dist/client/queue/helpers.js +2 -0
- package/dist/client/queue/operations/add/single.js +1 -1
- package/dist/client/queue/runtime/configuration.d.ts +4 -0
- package/dist/client/queue/runtime/configuration.js +8 -0
- package/dist/client/sandboxed/runtime/state.js +2 -2
- package/dist/client/tcp/connection.d.ts +1 -1
- package/dist/client/tcp/connection.js +1 -1
- package/dist/client/tcp/runtime/state.js +2 -2
- package/dist/client/types/metrics.d.ts +1 -1
- package/dist/client/worker/runtime/control.js +0 -1
- package/dist/client/worker/runtime/execution.js +3 -8
- package/dist/client/worker/runtime/lifecycle.js +0 -1
- package/dist/client/worker/runtime/polling.d.ts +1 -0
- package/dist/client/worker/runtime/polling.js +9 -0
- package/dist/client/worker/runtime/state.js +3 -3
- package/dist/client/workflow/compensationChild.d.ts +2 -0
- package/dist/client/workflow/compensationChild.js +3 -1
- package/dist/client/workflow/compensationPass.d.ts +1 -0
- package/dist/client/workflow/compensationPass.js +18 -2
- package/dist/client/workflow/compensator.d.ts +2 -1
- package/dist/client/workflow/compensator.js +11 -3
- package/dist/client/workflow/engine.js +1 -1
- package/dist/client/workflow/executionFence.d.ts +15 -0
- package/dist/client/workflow/executionFence.js +29 -0
- package/dist/client/workflow/executor.d.ts +5 -11
- package/dist/client/workflow/executor.js +48 -47
- package/dist/client/workflow/executorLifecycle.d.ts +1 -0
- package/dist/client/workflow/executorLifecycle.js +41 -10
- package/dist/client/workflow/executorNodes.d.ts +1 -0
- package/dist/client/workflow/executorNodes.js +45 -16
- package/dist/client/workflow/executorQueue.d.ts +9 -0
- package/dist/client/workflow/executorQueue.js +15 -0
- package/dist/client/workflow/forEachRunner.d.ts +4 -0
- package/dist/client/workflow/forEachRunner.js +70 -0
- package/dist/client/workflow/loops.d.ts +4 -5
- package/dist/client/workflow/loops.js +27 -93
- package/dist/client/workflow/mapRunner.d.ts +1 -1
- package/dist/client/workflow/mapRunner.js +11 -2
- package/dist/client/workflow/recovery.d.ts +1 -0
- package/dist/client/workflow/recovery.js +24 -18
- package/dist/client/workflow/rollbackControl.d.ts +1 -0
- package/dist/client/workflow/rollbackControl.js +5 -2
- package/dist/client/workflow/runner.d.ts +3 -1
- package/dist/client/workflow/runner.js +26 -3
- package/dist/client/workflow/stepTypes.js +1 -1
- package/dist/client/workflow/subWorkflowRunner.d.ts +1 -0
- package/dist/client/workflow/subWorkflowRunner.js +7 -0
- package/dist/client/workflow/waitFor.d.ts +4 -0
- package/dist/client/workflow/waitFor.js +23 -11
- package/dist/client/workflow/workflowDecisions.d.ts +1 -1
- package/dist/client/workflow/workflowDecisions.js +3 -1
- package/dist/config/resolve.d.ts +14 -0
- package/dist/config/resolve.js +50 -5
- package/dist/config/types.d.ts +18 -0
- package/dist/domain/queue/dlqShard.d.ts +2 -0
- package/dist/domain/queue/dlqShard.js +16 -0
- package/dist/domain/queue/shard/dlq.d.ts +1 -0
- package/dist/domain/queue/shard/dlq.js +3 -0
- package/dist/domain/types/commands/dlq.d.ts +5 -0
- package/dist/domain/types/commands/union.d.ts +1 -1
- package/dist/domain/types/cron.d.ts +5 -0
- package/dist/domain/types/cron.js +14 -3
- package/dist/domain/types/dlq.d.ts +1 -1
- package/dist/domain/types/dlq.js +1 -2
- package/dist/domain/types/jobs/model.d.ts +1 -1
- package/dist/infrastructure/cloud/commandHandler.js +5 -3
- package/dist/infrastructure/cloud/commands/integrations.js +11 -11
- package/dist/infrastructure/cloud/commands/jobs.js +48 -40
- package/dist/infrastructure/cloud/commands/queues.js +61 -75
- package/dist/infrastructure/cloud/queueAdapter/local.d.ts +41 -0
- package/dist/infrastructure/cloud/queueAdapter/local.js +187 -0
- package/dist/infrastructure/cloud/queueAdapter/postgres.d.ts +42 -0
- package/dist/infrastructure/cloud/queueAdapter/postgres.js +188 -0
- package/dist/infrastructure/cloud/queueAdapter/registry.d.ts +4 -0
- package/dist/infrastructure/cloud/queueAdapter/registry.js +15 -0
- package/dist/infrastructure/cloud/queueAdapter/types.d.ts +87 -0
- package/dist/infrastructure/cloud/queueAdapter/types.js +1 -0
- package/dist/infrastructure/cloud/snapshot/analytics.d.ts +2 -0
- package/dist/infrastructure/cloud/snapshot/analytics.js +7 -0
- package/dist/infrastructure/cloud/snapshot/collector.d.ts +4 -0
- package/dist/infrastructure/cloud/snapshot/collector.js +39 -1
- package/dist/infrastructure/cloud/snapshot/config.d.ts +3 -0
- package/dist/infrastructure/cloud/snapshot/config.js +43 -0
- package/dist/infrastructure/cloud/snapshot/jobs.d.ts +3 -0
- package/dist/infrastructure/cloud/snapshot/jobs.js +32 -1
- package/dist/infrastructure/cloud/snapshotCollector.js +20 -35
- package/dist/infrastructure/cloud/snapshotHelpers.d.ts +3 -3
- package/dist/infrastructure/cloud/snapshotHelpers.js +3 -3
- package/dist/infrastructure/cloud/statsRefresh.js +2 -1
- package/dist/infrastructure/cloud/types/command.d.ts +2 -2
- package/dist/infrastructure/persistence/postgres/admission.d.ts +18 -0
- package/dist/infrastructure/persistence/postgres/admission.js +193 -0
- package/dist/infrastructure/persistence/postgres/admissionParent.d.ts +5 -0
- package/dist/infrastructure/persistence/postgres/admissionParent.js +35 -0
- package/dist/infrastructure/persistence/postgres/admissionResult.d.ts +9 -0
- package/dist/infrastructure/persistence/postgres/admissionResult.js +1 -0
- package/dist/infrastructure/persistence/postgres/admissionStore.d.ts +12 -0
- package/dist/infrastructure/persistence/postgres/admissionStore.js +101 -0
- package/dist/infrastructure/persistence/postgres/advisoryLocks.d.ts +2 -0
- package/dist/infrastructure/persistence/postgres/advisoryLocks.js +13 -0
- package/dist/infrastructure/persistence/postgres/batchAdmission.d.ts +11 -0
- package/dist/infrastructure/persistence/postgres/batchAdmission.js +110 -0
- package/dist/infrastructure/persistence/postgres/batchCompletion.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/batchCompletion.js +174 -0
- package/dist/infrastructure/persistence/postgres/batchEvents.d.ts +14 -0
- package/dist/infrastructure/persistence/postgres/batchEvents.js +72 -0
- package/dist/infrastructure/persistence/postgres/brokerSessions.d.ts +14 -0
- package/dist/infrastructure/persistence/postgres/brokerSessions.js +94 -0
- package/dist/infrastructure/persistence/postgres/claimBatch.d.ts +11 -0
- package/dist/infrastructure/persistence/postgres/claimBatch.js +56 -0
- package/dist/infrastructure/persistence/postgres/claimSelection.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/claimSelection.js +198 -0
- package/dist/infrastructure/persistence/postgres/claims.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/claims.js +137 -0
- package/dist/infrastructure/persistence/postgres/cloudReadModel.d.ts +26 -0
- package/dist/infrastructure/persistence/postgres/cloudReadModel.js +123 -0
- package/dist/infrastructure/persistence/postgres/codec.d.ts +30 -0
- package/dist/infrastructure/persistence/postgres/codec.js +82 -0
- package/dist/infrastructure/persistence/postgres/completionEvents.d.ts +11 -0
- package/dist/infrastructure/persistence/postgres/completionEvents.js +21 -0
- package/dist/infrastructure/persistence/postgres/completionLifecycle.d.ts +11 -0
- package/dist/infrastructure/persistence/postgres/completionLifecycle.js +145 -0
- package/dist/infrastructure/persistence/postgres/completionOutcome.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/completionOutcome.js +112 -0
- package/dist/infrastructure/persistence/postgres/completionQueries.d.ts +10 -0
- package/dist/infrastructure/persistence/postgres/completionQueries.js +87 -0
- package/dist/infrastructure/persistence/postgres/connection.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/connection.js +15 -0
- package/dist/infrastructure/persistence/postgres/context.d.ts +28 -0
- package/dist/infrastructure/persistence/postgres/context.js +74 -0
- package/dist/infrastructure/persistence/postgres/control.d.ts +23 -0
- package/dist/infrastructure/persistence/postgres/control.js +105 -0
- package/dist/infrastructure/persistence/postgres/crons.d.ts +18 -0
- package/dist/infrastructure/persistence/postgres/crons.js +205 -0
- package/dist/infrastructure/persistence/postgres/deduplication.d.ts +9 -0
- package/dist/infrastructure/persistence/postgres/deduplication.js +47 -0
- package/dist/infrastructure/persistence/postgres/dependencyDestruction.d.ts +14 -0
- package/dist/infrastructure/persistence/postgres/dependencyDestruction.js +74 -0
- package/dist/infrastructure/persistence/postgres/dependencyPromotion.d.ts +29 -0
- package/dist/infrastructure/persistence/postgres/dependencyPromotion.js +212 -0
- package/dist/infrastructure/persistence/postgres/destructiveMutations.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/destructiveMutations.js +111 -0
- package/dist/infrastructure/persistence/postgres/dlqLifecycle.d.ts +13 -0
- package/dist/infrastructure/persistence/postgres/dlqLifecycle.js +184 -0
- package/dist/infrastructure/persistence/postgres/dlqMaintenance.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/dlqMaintenance.js +36 -0
- package/dist/infrastructure/persistence/postgres/dlqRetryPlan.d.ts +21 -0
- package/dist/infrastructure/persistence/postgres/dlqRetryPlan.js +92 -0
- package/dist/infrastructure/persistence/postgres/eventCommitGc.d.ts +27 -0
- package/dist/infrastructure/persistence/postgres/eventCommitGc.js +62 -0
- package/dist/infrastructure/persistence/postgres/eventJournal.d.ts +18 -0
- package/dist/infrastructure/persistence/postgres/eventJournal.js +76 -0
- package/dist/infrastructure/persistence/postgres/eventJournalSchema.d.ts +5 -0
- package/dist/infrastructure/persistence/postgres/eventJournalSchema.js +253 -0
- package/dist/infrastructure/persistence/postgres/eventPruneWatermarks.d.ts +24 -0
- package/dist/infrastructure/persistence/postgres/eventPruneWatermarks.js +111 -0
- package/dist/infrastructure/persistence/postgres/eventRetention.d.ts +18 -0
- package/dist/infrastructure/persistence/postgres/eventRetention.js +76 -0
- package/dist/infrastructure/persistence/postgres/events.d.ts +45 -0
- package/dist/infrastructure/persistence/postgres/events.js +249 -0
- package/dist/infrastructure/persistence/postgres/expiry.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/expiry.js +74 -0
- package/dist/infrastructure/persistence/postgres/flowFailures.d.ts +17 -0
- package/dist/infrastructure/persistence/postgres/flowFailures.js +199 -0
- package/dist/infrastructure/persistence/postgres/jobData.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/jobData.js +32 -0
- package/dist/infrastructure/persistence/postgres/leaseRelease.d.ts +9 -0
- package/dist/infrastructure/persistence/postgres/leaseRelease.js +120 -0
- package/dist/infrastructure/persistence/postgres/leaseRenewal.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/leaseRenewal.js +35 -0
- package/dist/infrastructure/persistence/postgres/lifetimeMetrics.d.ts +15 -0
- package/dist/infrastructure/persistence/postgres/lifetimeMetrics.js +46 -0
- package/dist/infrastructure/persistence/postgres/lifetimeMetricsFinalizer.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/lifetimeMetricsFinalizer.js +13 -0
- package/dist/infrastructure/persistence/postgres/logs.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/logs.js +70 -0
- package/dist/infrastructure/persistence/postgres/maintenance.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/maintenance.js +138 -0
- package/dist/infrastructure/persistence/postgres/maintenanceFlights.d.ts +14 -0
- package/dist/infrastructure/persistence/postgres/maintenanceFlights.js +66 -0
- package/dist/infrastructure/persistence/postgres/metricWrites.d.ts +9 -0
- package/dist/infrastructure/persistence/postgres/metricWrites.js +94 -0
- package/dist/infrastructure/persistence/postgres/mutations.d.ts +9 -0
- package/dist/infrastructure/persistence/postgres/mutations.js +171 -0
- package/dist/infrastructure/persistence/postgres/outcomes.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/outcomes.js +194 -0
- package/dist/infrastructure/persistence/postgres/policies.d.ts +11 -0
- package/dist/infrastructure/persistence/postgres/policies.js +18 -0
- package/dist/infrastructure/persistence/postgres/postCommitMaintenance.d.ts +17 -0
- package/dist/infrastructure/persistence/postgres/postCommitMaintenance.js +117 -0
- package/dist/infrastructure/persistence/postgres/promotion.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/promotion.js +43 -0
- package/dist/infrastructure/persistence/postgres/queries.d.ts +22 -0
- package/dist/infrastructure/persistence/postgres/queries.js +152 -0
- package/dist/infrastructure/persistence/postgres/queueDestruction.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/queueDestruction.js +154 -0
- package/dist/infrastructure/persistence/postgres/queueLifecycle.d.ts +10 -0
- package/dist/infrastructure/persistence/postgres/queueLifecycle.js +60 -0
- package/dist/infrastructure/persistence/postgres/rateLimit.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/rateLimit.js +10 -0
- package/dist/infrastructure/persistence/postgres/readModels.d.ts +32 -0
- package/dist/infrastructure/persistence/postgres/readModels.js +72 -0
- package/dist/infrastructure/persistence/postgres/recovery.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/recovery.js +178 -0
- package/dist/infrastructure/persistence/postgres/relationships.d.ts +9 -0
- package/dist/infrastructure/persistence/postgres/relationships.js +201 -0
- package/dist/infrastructure/persistence/postgres/removeUnprocessedChildren.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/removeUnprocessedChildren.js +241 -0
- package/dist/infrastructure/persistence/postgres/repeatMutations.d.ts +5 -0
- package/dist/infrastructure/persistence/postgres/repeatMutations.js +37 -0
- package/dist/infrastructure/persistence/postgres/runtime.d.ts +46 -0
- package/dist/infrastructure/persistence/postgres/runtime.js +236 -0
- package/dist/infrastructure/persistence/postgres/runtimeConfig.d.ts +21 -0
- package/dist/infrastructure/persistence/postgres/runtimeConfig.js +39 -0
- package/dist/infrastructure/persistence/postgres/schema.d.ts +4 -0
- package/dist/infrastructure/persistence/postgres/schema.js +257 -0
- package/dist/infrastructure/persistence/postgres/schemaInitialization.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/schemaInitialization.js +221 -0
- package/dist/infrastructure/persistence/postgres/serialAdmission.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/serialAdmission.js +96 -0
- package/dist/infrastructure/persistence/postgres/snapshotBudget.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/snapshotBudget.js +83 -0
- package/dist/infrastructure/persistence/postgres/storageHealth.d.ts +10 -0
- package/dist/infrastructure/persistence/postgres/storageHealth.js +29 -0
- package/dist/infrastructure/persistence/postgres/store.d.ts +90 -0
- package/dist/infrastructure/persistence/postgres/store.js +136 -0
- package/dist/infrastructure/persistence/postgres/telemetry.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/telemetry.js +88 -0
- package/dist/infrastructure/persistence/postgres/terminal.d.ts +8 -0
- package/dist/infrastructure/persistence/postgres/terminal.js +137 -0
- package/dist/infrastructure/persistence/postgres/transactionRetry.d.ts +6 -0
- package/dist/infrastructure/persistence/postgres/transactionRetry.js +42 -0
- package/dist/infrastructure/persistence/postgres/types.d.ts +175 -0
- package/dist/infrastructure/persistence/postgres/types.js +1 -0
- package/dist/infrastructure/persistence/postgres/workers.d.ts +15 -0
- package/dist/infrastructure/persistence/postgres/workers.js +126 -0
- package/dist/infrastructure/persistence/postgres.d.ts +2 -0
- package/dist/infrastructure/persistence/postgres.js +1 -0
- package/dist/infrastructure/persistence/sqlite/jobs.js +2 -0
- package/dist/infrastructure/persistence/sqlite.d.ts +1 -1
- package/dist/infrastructure/scheduler/cron/persisted.d.ts +5 -0
- package/dist/infrastructure/scheduler/cron/persisted.js +27 -0
- package/dist/infrastructure/scheduler/cron/runtime.js +5 -10
- package/dist/infrastructure/scheduler/cron/validation.d.ts +3 -0
- package/dist/infrastructure/scheduler/cron/validation.js +11 -0
- package/dist/infrastructure/scheduler/cronParser.d.ts +1 -1
- package/dist/infrastructure/scheduler/cronParser.js +144 -11
- package/dist/infrastructure/server/bootstrap.d.ts +2 -3
- package/dist/infrastructure/server/bootstrap.js +33 -59
- package/dist/infrastructure/server/errors.d.ts +4 -0
- package/dist/infrastructure/server/errors.js +54 -0
- package/dist/infrastructure/server/handler-routes/control.d.ts +5 -5
- package/dist/infrastructure/server/handler-routes/control.js +3 -1
- package/dist/infrastructure/server/handler-routes/jobs.js +0 -3
- package/dist/infrastructure/server/handler-routes/monitoring.d.ts +2 -2
- package/dist/infrastructure/server/handler.js +9 -11
- package/dist/infrastructure/server/handlerRoutes.d.ts +2 -2
- package/dist/infrastructure/server/handlerRoutes.js +2 -2
- package/dist/infrastructure/server/handlers/advanced/dependencies.js +7 -6
- package/dist/infrastructure/server/handlers/advanced/jobs.js +19 -6
- package/dist/infrastructure/server/handlers/advanced/queue.d.ts +9 -9
- package/dist/infrastructure/server/handlers/advanced/queue.js +91 -27
- package/dist/infrastructure/server/handlers/core.js +32 -31
- package/dist/infrastructure/server/handlers/cron.d.ts +4 -4
- package/dist/infrastructure/server/handlers/cron.js +60 -59
- package/dist/infrastructure/server/handlers/dashboard.d.ts +1 -1
- package/dist/infrastructure/server/handlers/dashboard.js +21 -8
- package/dist/infrastructure/server/handlers/dlq.d.ts +6 -3
- package/dist/infrastructure/server/handlers/dlq.js +35 -9
- package/dist/infrastructure/server/handlers/flow.js +2 -1
- package/dist/infrastructure/server/handlers/management.d.ts +5 -5
- package/dist/infrastructure/server/handlers/management.js +36 -9
- package/dist/infrastructure/server/handlers/monitoring/health.d.ts +5 -5
- package/dist/infrastructure/server/handlers/monitoring/health.js +45 -16
- package/dist/infrastructure/server/handlers/monitoring/operations.d.ts +1 -1
- package/dist/infrastructure/server/handlers/monitoring/operations.js +8 -1
- package/dist/infrastructure/server/handlers/monitoring/workers.d.ts +3 -3
- package/dist/infrastructure/server/handlers/monitoring/workers.js +31 -7
- package/dist/infrastructure/server/handlers/pushBatchValidation.d.ts +3 -1
- package/dist/infrastructure/server/handlers/pushBatchValidation.js +52 -7
- package/dist/infrastructure/server/handlers/query.d.ts +1 -1
- package/dist/infrastructure/server/handlers/query.js +5 -4
- package/dist/infrastructure/server/http.js +2 -2
- package/dist/infrastructure/server/httpDashboardEndpoints.d.ts +1 -1
- package/dist/infrastructure/server/httpDashboardEndpoints.js +19 -6
- package/dist/infrastructure/server/httpEndpoints.js +20 -7
- package/dist/infrastructure/server/httpRouteQueues.js +11 -1
- package/dist/infrastructure/server/shutdownCoordinator.d.ts +24 -0
- package/dist/infrastructure/server/shutdownCoordinator.js +106 -0
- package/dist/infrastructure/server/sseHandler.js +8 -3
- package/dist/infrastructure/server/storageAdapter.d.ts +29 -0
- package/dist/infrastructure/server/storageAdapter.js +61 -0
- package/dist/infrastructure/server/storageManager.d.ts +12 -0
- package/dist/infrastructure/server/storageManager.js +102 -0
- package/dist/infrastructure/server/tcp.js +2 -3
- package/dist/infrastructure/server/ws/snapshots.d.ts +1 -1
- package/dist/infrastructure/server/ws/snapshots.js +19 -4
- package/dist/infrastructure/server/wsHandler.js +10 -5
- package/dist/main.js +1 -1
- package/dist/mcp/backend/embedded/services.js +2 -1
- package/dist/mcp/index.d.ts +2 -2
- package/dist/mcp/index.js +2 -2
- package/dist/shared/storageHealth.d.ts +9 -0
- package/dist/shared/storageHealth.js +12 -0
- package/package.json +85 -74
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { createJob, generateJobId, MAX_TIMELINE_ENTRIES } from '../../../domain/types/job';
|
|
2
|
+
import { buildRepeatSuccessor } from '../../../application/repeatJobs';
|
|
3
|
+
import { decodePostgresJob, encodePostgresValue, postgresByteaBase64 } from './codec';
|
|
4
|
+
import { databaseNow } from './context';
|
|
5
|
+
import { admitPostgresJob } from './admission';
|
|
6
|
+
import { recordPostgresCompletionEvents } from './completionEvents';
|
|
7
|
+
import { planPostgresDependencyCompletion } from './dependencyPromotion';
|
|
8
|
+
import { runPostgresTransactionWithRetry } from './transactionRetry';
|
|
9
|
+
import { tryLockPostgresRepeatQueues } from './queueLifecycle';
|
|
10
|
+
function invalidToken(id) {
|
|
11
|
+
return new Error(`Invalid or expired lock token for job ${id}`);
|
|
12
|
+
}
|
|
13
|
+
function prepareCompletions(rows, inputs, now) {
|
|
14
|
+
const byId = new Map(rows.map((row) => [row.id, row]));
|
|
15
|
+
const applied = [];
|
|
16
|
+
const results = inputs.map((input) => {
|
|
17
|
+
const row = byId.get(String(input.id));
|
|
18
|
+
if (!row)
|
|
19
|
+
throw invalidToken(String(input.id));
|
|
20
|
+
if (row.state === 'completed' || row.state === 'failed') {
|
|
21
|
+
return {
|
|
22
|
+
applied: false,
|
|
23
|
+
alreadyFinalized: true,
|
|
24
|
+
job: decodePostgresJob(row).job,
|
|
25
|
+
state: row.state,
|
|
26
|
+
removed: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (row.state !== 'active' ||
|
|
30
|
+
row.lease_token !== input.token ||
|
|
31
|
+
Number(row.lease_until ?? 0) <= now) {
|
|
32
|
+
throw invalidToken(String(input.id));
|
|
33
|
+
}
|
|
34
|
+
const job = decodePostgresJob(row).job;
|
|
35
|
+
job.completedAt = now;
|
|
36
|
+
job.progress = 100;
|
|
37
|
+
if (job.timeline.length < MAX_TIMELINE_ENTRIES) {
|
|
38
|
+
job.timeline.push({ state: 'completed', timestamp: now });
|
|
39
|
+
}
|
|
40
|
+
const removed = job.removeOnComplete || input.removeOnComplete === true;
|
|
41
|
+
applied.push({
|
|
42
|
+
input,
|
|
43
|
+
job,
|
|
44
|
+
payload: encodePostgresValue(job),
|
|
45
|
+
resultPayload: encodePostgresValue(input.result),
|
|
46
|
+
removed,
|
|
47
|
+
});
|
|
48
|
+
return { applied: true, alreadyFinalized: false, job, state: 'completed', removed };
|
|
49
|
+
});
|
|
50
|
+
return { applied, results };
|
|
51
|
+
}
|
|
52
|
+
async function lockRows(tx, ctx, inputs) {
|
|
53
|
+
const ids = inputs.map((input) => String(input.id)).sort();
|
|
54
|
+
return await tx `
|
|
55
|
+
SELECT * FROM bunqueue_jobs
|
|
56
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
57
|
+
AND id = ANY(${tx.array(ids, 'TEXT')})
|
|
58
|
+
ORDER BY id
|
|
59
|
+
FOR UPDATE
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
async function storeResults(tx, ctx, applied, now) {
|
|
63
|
+
await tx `
|
|
64
|
+
INSERT INTO bunqueue_completions (namespace, job_id, queue, completed_at, result)
|
|
65
|
+
SELECT
|
|
66
|
+
${ctx.config.namespace}, batch.id, batch.queue, ${now},
|
|
67
|
+
decode(batch.result_base64, 'base64')
|
|
68
|
+
FROM unnest(
|
|
69
|
+
${tx.array(applied.map(({ job }) => String(job.id)), 'TEXT')},
|
|
70
|
+
${tx.array(applied.map(({ job }) => job.queue), 'TEXT')},
|
|
71
|
+
${tx.array(applied.map(({ resultPayload }) => postgresByteaBase64(resultPayload)), 'TEXT')}
|
|
72
|
+
) AS batch(id, queue, result_base64)
|
|
73
|
+
ON CONFLICT (namespace, job_id) DO UPDATE SET
|
|
74
|
+
queue = excluded.queue,
|
|
75
|
+
completed_at = excluded.completed_at,
|
|
76
|
+
result = excluded.result
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
async function updateRetainedJobs(tx, ctx, applied, now) {
|
|
80
|
+
const retained = applied.filter((item) => !item.removed);
|
|
81
|
+
if (retained.length === 0)
|
|
82
|
+
return;
|
|
83
|
+
await tx `
|
|
84
|
+
UPDATE bunqueue_jobs AS job
|
|
85
|
+
SET payload = decode(batch.payload_base64, 'base64'),
|
|
86
|
+
state = 'completed',
|
|
87
|
+
completed_at = ${now},
|
|
88
|
+
result = decode(batch.result_base64, 'base64'),
|
|
89
|
+
lease_owner = NULL,
|
|
90
|
+
lease_token = NULL,
|
|
91
|
+
lease_broker_id = NULL, lease_broker_session_id = NULL,
|
|
92
|
+
lease_until = NULL,
|
|
93
|
+
dlq_retry_state = NULL,
|
|
94
|
+
version = job.version + 1
|
|
95
|
+
FROM unnest(
|
|
96
|
+
${tx.array(retained.map(({ job }) => String(job.id)), 'TEXT')},
|
|
97
|
+
${tx.array(retained.map(({ payload }) => postgresByteaBase64(payload)), 'TEXT')},
|
|
98
|
+
${tx.array(retained.map(({ resultPayload }) => postgresByteaBase64(resultPayload)), 'TEXT')}
|
|
99
|
+
) AS batch(id, payload_base64, result_base64)
|
|
100
|
+
WHERE job.namespace = ${ctx.config.namespace} AND job.id = batch.id
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
async function deleteRemovedJobs(tx, ctx, applied) {
|
|
104
|
+
const ids = applied.filter((item) => item.removed).map(({ job }) => String(job.id));
|
|
105
|
+
if (ids.length === 0)
|
|
106
|
+
return;
|
|
107
|
+
await tx `
|
|
108
|
+
DELETE FROM bunqueue_job_logs
|
|
109
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ANY(${tx.array(ids, 'TEXT')})
|
|
110
|
+
`;
|
|
111
|
+
await tx `
|
|
112
|
+
DELETE FROM bunqueue_dependencies
|
|
113
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ANY(${tx.array(ids, 'TEXT')})
|
|
114
|
+
`;
|
|
115
|
+
await tx `
|
|
116
|
+
DELETE FROM bunqueue_jobs
|
|
117
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ANY(${tx.array(ids, 'TEXT')})
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
async function createRepeatSuccessors(tx, ctx, applied, now) {
|
|
121
|
+
for (const { job } of applied) {
|
|
122
|
+
const shouldRepeat = job.repeat !== null &&
|
|
123
|
+
(job.repeat.limit === undefined || job.repeat.count < job.repeat.limit);
|
|
124
|
+
const successorInput = shouldRepeat ? buildRepeatSuccessor(job, now) : null;
|
|
125
|
+
if (!successorInput)
|
|
126
|
+
continue;
|
|
127
|
+
const successor = createJob(generateJobId(), job.queue, successorInput, now);
|
|
128
|
+
const admitted = await admitPostgresJob(tx, ctx, successor, {
|
|
129
|
+
linkParent: false,
|
|
130
|
+
queueLifecycleLocksHeld: true,
|
|
131
|
+
});
|
|
132
|
+
await tx `
|
|
133
|
+
INSERT INTO bunqueue_repeat_links (namespace, original_id, successor_id)
|
|
134
|
+
VALUES (${ctx.config.namespace}, ${String(job.id)}, ${String(admitted.job.id)})
|
|
135
|
+
ON CONFLICT (namespace, original_id) DO UPDATE SET successor_id = excluded.successor_id
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Complete a unique ACK batch atomically with set-based persistence. */
|
|
140
|
+
export async function completePostgresJobs(ctx, inputs) {
|
|
141
|
+
if (inputs.length === 0)
|
|
142
|
+
return [];
|
|
143
|
+
if (new Set(inputs.map((input) => String(input.id))).size !== inputs.length) {
|
|
144
|
+
throw new Error('PostgreSQL batch completion requires unique job IDs');
|
|
145
|
+
}
|
|
146
|
+
return await runPostgresTransactionWithRetry(ctx, async (tx) => {
|
|
147
|
+
const now = await databaseNow(tx);
|
|
148
|
+
const dependencyCompletion = await planPostgresDependencyCompletion(tx, ctx, inputs.map(({ id }) => id));
|
|
149
|
+
const rows = await lockRows(tx, ctx, inputs);
|
|
150
|
+
const { applied, results } = prepareCompletions(rows, inputs, now);
|
|
151
|
+
if (applied.length === 0)
|
|
152
|
+
return results;
|
|
153
|
+
const repeatQueues = applied.flatMap(({ job }) => {
|
|
154
|
+
const repeats = job.repeat !== null &&
|
|
155
|
+
(job.repeat.limit === undefined || job.repeat.count < job.repeat.limit);
|
|
156
|
+
return repeats ? [job.queue] : [];
|
|
157
|
+
});
|
|
158
|
+
if (!(await tryLockPostgresRepeatQueues(tx, ctx, repeatQueues))) {
|
|
159
|
+
throw new Error('A repeat queue is being obliterated; retry batch completion');
|
|
160
|
+
}
|
|
161
|
+
const ids = applied.map(({ job }) => String(job.id));
|
|
162
|
+
await tx `
|
|
163
|
+
DELETE FROM bunqueue_flow_failures
|
|
164
|
+
WHERE namespace = ${ctx.config.namespace} AND parent_id = ANY(${tx.array(ids, 'TEXT')})
|
|
165
|
+
`;
|
|
166
|
+
await storeResults(tx, ctx, applied, now);
|
|
167
|
+
await updateRetainedJobs(tx, ctx, applied, now);
|
|
168
|
+
await deleteRemovedJobs(tx, ctx, applied);
|
|
169
|
+
await recordPostgresCompletionEvents(tx, ctx, applied.map(({ job, input, removed }) => ({ job, result: input.result, removed })), now);
|
|
170
|
+
await dependencyCompletion.promote(now);
|
|
171
|
+
await createRepeatSuccessors(tx, ctx, applied, now);
|
|
172
|
+
return results;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TransactionSQL } from 'bun';
|
|
2
|
+
import type { Job } from '../../../domain/types/job';
|
|
3
|
+
import type { PostgresContext } from './context';
|
|
4
|
+
import type { PostgresJobState } from './types';
|
|
5
|
+
export interface PostgresBatchJobEventInput {
|
|
6
|
+
readonly job: Job;
|
|
7
|
+
readonly type: string;
|
|
8
|
+
readonly state?: PostgresJobState;
|
|
9
|
+
readonly result?: unknown;
|
|
10
|
+
readonly error?: string;
|
|
11
|
+
readonly removed?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** Persist job events while amortizing retention and wakeups per affected queue. */
|
|
14
|
+
export declare function recordPostgresJobEvents(tx: TransactionSQL, ctx: PostgresContext, inputs: readonly PostgresBatchJobEventInput[], now: number): Promise<void>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { eventPayload, postgresByteaBase64 } from './codec';
|
|
2
|
+
import { recordPostgresEventPruneWatermarks, summarizePostgresPrunedEvents, } from './eventPruneWatermarks';
|
|
3
|
+
import { prunePostgresQueueEvents, tryLockPostgresEventRetention } from './eventRetention';
|
|
4
|
+
import { POSTGRES_EVENT_CHANNEL } from './schema';
|
|
5
|
+
function payloadForEvent(input) {
|
|
6
|
+
return eventPayload({
|
|
7
|
+
job: input.job,
|
|
8
|
+
...(input.state && { state: input.state }),
|
|
9
|
+
...(Object.hasOwn(input, 'result') && { result: input.result }),
|
|
10
|
+
...(input.error !== undefined && { error: input.error }),
|
|
11
|
+
...(input.removed !== undefined && { removed: input.removed }),
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
async function insertEvents(tx, ctx, inputs, now) {
|
|
15
|
+
const inserted = await tx `
|
|
16
|
+
INSERT INTO bunqueue_events
|
|
17
|
+
(namespace, queue, event_type, job_id, occurred_at, payload)
|
|
18
|
+
SELECT
|
|
19
|
+
${ctx.config.namespace}, batch.queue, batch.event_type, batch.job_id, ${now},
|
|
20
|
+
decode(batch.payload_base64, 'base64')
|
|
21
|
+
FROM unnest(
|
|
22
|
+
${tx.array(inputs.map((input) => input.job.queue), 'TEXT')},
|
|
23
|
+
${tx.array(inputs.map((input) => input.type), 'TEXT')},
|
|
24
|
+
${tx.array(inputs.map((input) => String(input.job.id)), 'TEXT')},
|
|
25
|
+
${tx.array(inputs.map((input) => postgresByteaBase64(payloadForEvent(input))), 'TEXT')}
|
|
26
|
+
) WITH ORDINALITY AS batch(queue, event_type, job_id, payload_base64, position)
|
|
27
|
+
ORDER BY batch.position
|
|
28
|
+
RETURNING id
|
|
29
|
+
`;
|
|
30
|
+
const ids = inserted.map((row) => Number(row.id));
|
|
31
|
+
return { startId: Math.min(...ids), endId: Math.max(...ids) };
|
|
32
|
+
}
|
|
33
|
+
async function trimEvents(tx, ctx, queues, sourceEventId) {
|
|
34
|
+
const watermarks = [];
|
|
35
|
+
const skippedQueues = new Set();
|
|
36
|
+
for (const queue of [...queues].sort()) {
|
|
37
|
+
if (!(await tryLockPostgresEventRetention(tx, ctx, queue))) {
|
|
38
|
+
skippedQueues.add(queue);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const pruned = await prunePostgresQueueEvents(tx, ctx, queue, ctx.config.maxQueueEvents);
|
|
42
|
+
const prune = summarizePostgresPrunedEvents(pruned);
|
|
43
|
+
if (prune.prunedThrough > 0)
|
|
44
|
+
watermarks.push({ queue, sourceEventId, ...prune });
|
|
45
|
+
}
|
|
46
|
+
await recordPostgresEventPruneWatermarks(tx, ctx, watermarks);
|
|
47
|
+
return {
|
|
48
|
+
watermarks: new Map(watermarks.map(({ queue, prunedThrough }) => [queue, prunedThrough])),
|
|
49
|
+
skippedQueues,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Persist job events while amortizing retention and wakeups per affected queue. */
|
|
53
|
+
export async function recordPostgresJobEvents(tx, ctx, inputs, now) {
|
|
54
|
+
if (inputs.length === 0)
|
|
55
|
+
return;
|
|
56
|
+
const queues = [...new Set(inputs.map((input) => input.job.queue))];
|
|
57
|
+
const eventRange = await insertEvents(tx, ctx, inputs, now);
|
|
58
|
+
const { watermarks, skippedQueues } = await trimEvents(tx, ctx, queues, eventRange.endId);
|
|
59
|
+
for (const queue of queues) {
|
|
60
|
+
const prunedThrough = watermarks.get(queue);
|
|
61
|
+
await tx.notify(POSTGRES_EVENT_CHANNEL, JSON.stringify({
|
|
62
|
+
namespace: ctx.config.namespace,
|
|
63
|
+
queue,
|
|
64
|
+
brokerId: ctx.config.brokerId,
|
|
65
|
+
eventStartId: eventRange.startId,
|
|
66
|
+
eventEndId: eventRange.endId,
|
|
67
|
+
...(prunedThrough !== undefined && { prunedThrough }),
|
|
68
|
+
...(skippedQueues.has(queue) && { retentionRequested: true }),
|
|
69
|
+
...(watermarks.size > 0 && { scanPruneWatermarks: true }),
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TransactionSQL } from 'bun';
|
|
2
|
+
import { type PostgresContext } from './context';
|
|
3
|
+
export declare class PostgresBrokerIdentityInUseError extends Error {
|
|
4
|
+
constructor(brokerId: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class PostgresBrokerSessionFencedError extends Error {
|
|
7
|
+
constructor(brokerId: string);
|
|
8
|
+
}
|
|
9
|
+
export declare function postgresBrokerStaleMs(ctx: PostgresContext): number;
|
|
10
|
+
export declare function registerPostgresBroker(ctx: PostgresContext): Promise<void>;
|
|
11
|
+
export declare function lockPostgresBrokerSession(tx: TransactionSQL, ctx: PostgresContext): Promise<void>;
|
|
12
|
+
export declare function heartbeatPostgresBroker(ctx: PostgresContext): Promise<void>;
|
|
13
|
+
export declare function unregisterPostgresBroker(ctx: PostgresContext): Promise<void>;
|
|
14
|
+
export declare function purgeStalePostgresBrokers(ctx: PostgresContext): Promise<number>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { databaseNow } from './context';
|
|
2
|
+
export class PostgresBrokerIdentityInUseError extends Error {
|
|
3
|
+
constructor(brokerId) {
|
|
4
|
+
super(`PostgreSQL brokerId "${brokerId}" is already active`);
|
|
5
|
+
this.name = 'PostgresBrokerIdentityInUseError';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class PostgresBrokerSessionFencedError extends Error {
|
|
9
|
+
constructor(brokerId) {
|
|
10
|
+
super(`PostgreSQL broker session for "${brokerId}" has been fenced`);
|
|
11
|
+
this.name = 'PostgresBrokerSessionFencedError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function postgresBrokerStaleMs(ctx) {
|
|
15
|
+
const heartbeatMs = Math.max(1000, Math.floor(ctx.config.leaseDurationMs / 3));
|
|
16
|
+
return Math.max(ctx.config.leaseDurationMs, heartbeatMs * 3);
|
|
17
|
+
}
|
|
18
|
+
export async function registerPostgresBroker(ctx) {
|
|
19
|
+
const now = await databaseNow(ctx.sql);
|
|
20
|
+
const staleBefore = now - postgresBrokerStaleMs(ctx);
|
|
21
|
+
const [existing] = await ctx.sql `
|
|
22
|
+
SELECT broker_id, session_id, heartbeat_at FROM bunqueue_brokers
|
|
23
|
+
WHERE namespace = ${ctx.config.namespace} AND broker_id = ${ctx.config.brokerId}
|
|
24
|
+
`;
|
|
25
|
+
if (existing &&
|
|
26
|
+
existing.session_id !== ctx.config.brokerSessionId &&
|
|
27
|
+
Number(existing.heartbeat_at) > staleBefore) {
|
|
28
|
+
throw new PostgresBrokerIdentityInUseError(ctx.config.brokerId);
|
|
29
|
+
}
|
|
30
|
+
const rows = await ctx.sql.begin(async (tx) => {
|
|
31
|
+
const transactionNow = await databaseNow(tx);
|
|
32
|
+
return await tx `
|
|
33
|
+
INSERT INTO bunqueue_brokers AS broker
|
|
34
|
+
(namespace, broker_id, session_id, started_at, heartbeat_at)
|
|
35
|
+
VALUES
|
|
36
|
+
(${ctx.config.namespace}, ${ctx.config.brokerId}, ${ctx.config.brokerSessionId},
|
|
37
|
+
${transactionNow}, ${transactionNow})
|
|
38
|
+
ON CONFLICT (namespace, broker_id) DO UPDATE SET
|
|
39
|
+
session_id = excluded.session_id,
|
|
40
|
+
started_at = excluded.started_at,
|
|
41
|
+
heartbeat_at = excluded.heartbeat_at
|
|
42
|
+
WHERE broker.session_id IS NOT DISTINCT FROM excluded.session_id
|
|
43
|
+
OR broker.heartbeat_at <= ${transactionNow - postgresBrokerStaleMs(ctx)}
|
|
44
|
+
RETURNING broker_id
|
|
45
|
+
`;
|
|
46
|
+
});
|
|
47
|
+
if (rows.length === 0)
|
|
48
|
+
throw new PostgresBrokerIdentityInUseError(ctx.config.brokerId);
|
|
49
|
+
}
|
|
50
|
+
export async function lockPostgresBrokerSession(tx, ctx) {
|
|
51
|
+
const rows = await tx `
|
|
52
|
+
SELECT broker_id FROM bunqueue_brokers
|
|
53
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
54
|
+
AND broker_id = ${ctx.config.brokerId}
|
|
55
|
+
AND session_id = ${ctx.config.brokerSessionId}
|
|
56
|
+
FOR SHARE
|
|
57
|
+
`;
|
|
58
|
+
if (rows.length === 0)
|
|
59
|
+
throw new PostgresBrokerSessionFencedError(ctx.config.brokerId);
|
|
60
|
+
}
|
|
61
|
+
export async function heartbeatPostgresBroker(ctx) {
|
|
62
|
+
const now = await databaseNow(ctx.sql);
|
|
63
|
+
const rows = await ctx.sql `
|
|
64
|
+
UPDATE bunqueue_brokers SET heartbeat_at = ${now}
|
|
65
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
66
|
+
AND broker_id = ${ctx.config.brokerId}
|
|
67
|
+
AND session_id = ${ctx.config.brokerSessionId}
|
|
68
|
+
RETURNING broker_id
|
|
69
|
+
`;
|
|
70
|
+
if (rows.length === 0)
|
|
71
|
+
throw new PostgresBrokerSessionFencedError(ctx.config.brokerId);
|
|
72
|
+
}
|
|
73
|
+
export async function unregisterPostgresBroker(ctx) {
|
|
74
|
+
await ctx.sql `
|
|
75
|
+
DELETE FROM bunqueue_brokers
|
|
76
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
77
|
+
AND broker_id = ${ctx.config.brokerId}
|
|
78
|
+
AND session_id = ${ctx.config.brokerSessionId}
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
export async function purgeStalePostgresBrokers(ctx) {
|
|
82
|
+
const now = await databaseNow(ctx.sql);
|
|
83
|
+
const rows = await ctx.sql `
|
|
84
|
+
DELETE FROM bunqueue_brokers
|
|
85
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
86
|
+
AND heartbeat_at <= ${now - postgresBrokerStaleMs(ctx)}
|
|
87
|
+
AND NOT (
|
|
88
|
+
broker_id = ${ctx.config.brokerId}
|
|
89
|
+
AND session_id IS NOT DISTINCT FROM ${ctx.config.brokerSessionId}
|
|
90
|
+
)
|
|
91
|
+
RETURNING broker_id
|
|
92
|
+
`;
|
|
93
|
+
return rows.length;
|
|
94
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TransactionSQL } from 'bun';
|
|
2
|
+
import type { PostgresContext } from './context';
|
|
3
|
+
import type { ClaimedPostgresJob } from './types';
|
|
4
|
+
/** Claim one queue batch after its queue-state row has been locked. */
|
|
5
|
+
export declare function claimPostgresCandidates(tx: TransactionSQL, ctx: PostgresContext, input: {
|
|
6
|
+
queue: string;
|
|
7
|
+
capacity: number;
|
|
8
|
+
owner: string;
|
|
9
|
+
requestedLeaseMs: number;
|
|
10
|
+
now: number;
|
|
11
|
+
}): Promise<ClaimedPostgresJob[]>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { generateLockToken, MAX_TIMELINE_ENTRIES } from '../../../domain/types/job';
|
|
2
|
+
import { recordPostgresJobEvents } from './batchEvents';
|
|
3
|
+
import { selectPostgresClaimCandidates } from './claimSelection';
|
|
4
|
+
import { decodePostgresJob, encodePostgresValue, postgresByteaBase64 } from './codec';
|
|
5
|
+
function prepareClaims(rows, input) {
|
|
6
|
+
const { owner, requestedLeaseMs, now, brokerId } = input;
|
|
7
|
+
return rows.map((row) => {
|
|
8
|
+
const job = decodePostgresJob(row).job;
|
|
9
|
+
const token = String(generateLockToken());
|
|
10
|
+
const leaseDuration = Math.max(1, Math.min(requestedLeaseMs, job.timeout === null ? requestedLeaseMs : job.timeout, job.stallTimeout === null ? requestedLeaseMs : job.stallTimeout));
|
|
11
|
+
const leaseUntil = now + leaseDuration;
|
|
12
|
+
job.startedAt = now;
|
|
13
|
+
job.lastHeartbeat = now;
|
|
14
|
+
if (job.timeline.length < MAX_TIMELINE_ENTRIES) {
|
|
15
|
+
job.timeline.push({ state: 'active', timestamp: now, worker: owner });
|
|
16
|
+
}
|
|
17
|
+
return { job, token, owner, brokerId, leaseUntil };
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
async function persistClaims(tx, ctx, claimed, now) {
|
|
21
|
+
await tx `
|
|
22
|
+
UPDATE bunqueue_jobs AS job
|
|
23
|
+
SET state = 'active',
|
|
24
|
+
payload = decode(batch.payload_base64, 'base64'),
|
|
25
|
+
started_at = ${now},
|
|
26
|
+
lease_owner = ${claimed[0].owner},
|
|
27
|
+
lease_broker_id = ${ctx.config.brokerId},
|
|
28
|
+
lease_broker_session_id = ${ctx.config.brokerSessionId},
|
|
29
|
+
lease_token = batch.token,
|
|
30
|
+
lease_until = batch.lease_until,
|
|
31
|
+
lease_renewals = 0,
|
|
32
|
+
version = job.version + 1
|
|
33
|
+
FROM unnest(
|
|
34
|
+
${tx.array(claimed.map(({ job }) => String(job.id)), 'TEXT')},
|
|
35
|
+
${tx.array(claimed.map(({ job }) => postgresByteaBase64(encodePostgresValue(job))), 'TEXT')},
|
|
36
|
+
${tx.array(claimed.map(({ token }) => token), 'TEXT')},
|
|
37
|
+
${tx.array(claimed.map(({ leaseUntil }) => leaseUntil), 'BIGINT')}
|
|
38
|
+
) AS batch(id, payload_base64, token, lease_until)
|
|
39
|
+
WHERE job.namespace = ${ctx.config.namespace} AND job.id = batch.id
|
|
40
|
+
`;
|
|
41
|
+
await recordPostgresJobEvents(tx, ctx, claimed.map(({ job }) => ({ job, type: 'pulled', state: 'active' })), now);
|
|
42
|
+
}
|
|
43
|
+
/** Claim one queue batch after its queue-state row has been locked. */
|
|
44
|
+
export async function claimPostgresCandidates(tx, ctx, input) {
|
|
45
|
+
const { queue, capacity, owner, requestedLeaseMs, now } = input;
|
|
46
|
+
const rows = await selectPostgresClaimCandidates(tx, ctx, queue, now, capacity);
|
|
47
|
+
const claimed = prepareClaims(rows, {
|
|
48
|
+
owner,
|
|
49
|
+
requestedLeaseMs,
|
|
50
|
+
now,
|
|
51
|
+
brokerId: ctx.config.brokerId,
|
|
52
|
+
});
|
|
53
|
+
if (claimed.length > 0)
|
|
54
|
+
await persistClaims(tx, ctx, claimed, now);
|
|
55
|
+
return claimed;
|
|
56
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TransactionSQL } from 'bun';
|
|
2
|
+
import type { PostgresContext } from './context';
|
|
3
|
+
import type { PostgresJobRow } from './types';
|
|
4
|
+
export declare function selectPostgresClaimCandidates(tx: TransactionSQL, ctx: PostgresContext, queue: string, now: number, capacity: number): Promise<PostgresJobRow[]>;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
async function hasClaimableGroupedJob(tx, ctx, queue, now) {
|
|
2
|
+
const [row] = await tx `
|
|
3
|
+
SELECT EXISTS (
|
|
4
|
+
SELECT 1
|
|
5
|
+
FROM bunqueue_jobs AS job
|
|
6
|
+
WHERE job.namespace = ${ctx.config.namespace}
|
|
7
|
+
AND job.queue = ${queue}
|
|
8
|
+
AND job.group_id IS NOT NULL
|
|
9
|
+
AND job.state IN ('waiting', 'prioritized', 'delayed')
|
|
10
|
+
AND job.run_at <= ${now}
|
|
11
|
+
AND (job.ttl IS NULL OR job.created_at + job.ttl >= ${now})
|
|
12
|
+
AND NOT EXISTS (
|
|
13
|
+
SELECT 1
|
|
14
|
+
FROM bunqueue_dependencies AS dependency
|
|
15
|
+
WHERE dependency.namespace = job.namespace
|
|
16
|
+
AND dependency.job_id = job.id
|
|
17
|
+
AND NOT EXISTS (
|
|
18
|
+
SELECT 1 FROM bunqueue_completions AS completion
|
|
19
|
+
WHERE completion.namespace = dependency.namespace
|
|
20
|
+
AND completion.job_id = dependency.dependency_id
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
AND NOT EXISTS (
|
|
24
|
+
SELECT 1 FROM bunqueue_jobs AS active_group
|
|
25
|
+
WHERE active_group.namespace = job.namespace
|
|
26
|
+
AND active_group.queue = job.queue
|
|
27
|
+
AND active_group.group_id = job.group_id
|
|
28
|
+
AND active_group.state = 'active'
|
|
29
|
+
AND active_group.lease_until > ${now}
|
|
30
|
+
)
|
|
31
|
+
) AS exists
|
|
32
|
+
`;
|
|
33
|
+
return row.exists;
|
|
34
|
+
}
|
|
35
|
+
async function hasClaimableLifoJob(tx, ctx, queue, now) {
|
|
36
|
+
const [row] = await tx `
|
|
37
|
+
SELECT EXISTS (
|
|
38
|
+
SELECT 1 FROM bunqueue_jobs AS job
|
|
39
|
+
WHERE job.namespace = ${ctx.config.namespace}
|
|
40
|
+
AND job.queue = ${queue}
|
|
41
|
+
AND job.group_id IS NULL
|
|
42
|
+
AND job.lifo
|
|
43
|
+
AND job.state IN ('waiting', 'prioritized', 'delayed')
|
|
44
|
+
AND job.run_at <= ${now}
|
|
45
|
+
AND (job.ttl IS NULL OR job.created_at + job.ttl >= ${now})
|
|
46
|
+
) AS exists
|
|
47
|
+
`;
|
|
48
|
+
return row.exists;
|
|
49
|
+
}
|
|
50
|
+
async function selectFifoCandidates(tx, ctx, queue, now, capacity) {
|
|
51
|
+
return await tx `
|
|
52
|
+
WITH selected AS (
|
|
53
|
+
SELECT job.id, job.priority, job.run_at
|
|
54
|
+
FROM bunqueue_jobs AS job
|
|
55
|
+
WHERE job.namespace = ${ctx.config.namespace}
|
|
56
|
+
AND job.queue = ${queue}
|
|
57
|
+
AND job.group_id IS NULL
|
|
58
|
+
AND NOT job.lifo
|
|
59
|
+
AND job.state IN ('waiting', 'prioritized', 'delayed')
|
|
60
|
+
AND job.run_at <= ${now}
|
|
61
|
+
AND (job.ttl IS NULL OR job.created_at + job.ttl >= ${now})
|
|
62
|
+
AND NOT EXISTS (
|
|
63
|
+
SELECT 1 FROM bunqueue_dependencies AS dependency
|
|
64
|
+
WHERE dependency.namespace = job.namespace
|
|
65
|
+
AND dependency.job_id = job.id
|
|
66
|
+
AND NOT EXISTS (
|
|
67
|
+
SELECT 1 FROM bunqueue_completions AS completion
|
|
68
|
+
WHERE completion.namespace = dependency.namespace
|
|
69
|
+
AND completion.job_id = dependency.dependency_id
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
ORDER BY job.priority DESC, job.run_at ASC, job.id ASC
|
|
73
|
+
LIMIT ${capacity}
|
|
74
|
+
FOR UPDATE OF job SKIP LOCKED
|
|
75
|
+
)
|
|
76
|
+
SELECT job.*
|
|
77
|
+
FROM bunqueue_jobs AS job
|
|
78
|
+
JOIN selected ON selected.id = job.id
|
|
79
|
+
WHERE job.namespace = ${ctx.config.namespace}
|
|
80
|
+
ORDER BY selected.priority DESC, selected.run_at ASC, selected.id ASC
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
async function selectUngroupedCandidates(tx, ctx, queue, now, capacity) {
|
|
84
|
+
return await tx `
|
|
85
|
+
WITH selected AS (
|
|
86
|
+
SELECT job.id, job.priority, job.lifo, job.run_at
|
|
87
|
+
FROM bunqueue_jobs AS job
|
|
88
|
+
WHERE job.namespace = ${ctx.config.namespace}
|
|
89
|
+
AND job.queue = ${queue}
|
|
90
|
+
AND job.group_id IS NULL
|
|
91
|
+
AND job.state IN ('waiting', 'prioritized', 'delayed')
|
|
92
|
+
AND job.run_at <= ${now}
|
|
93
|
+
AND (job.ttl IS NULL OR job.created_at + job.ttl >= ${now})
|
|
94
|
+
AND NOT EXISTS (
|
|
95
|
+
SELECT 1 FROM bunqueue_dependencies AS dependency
|
|
96
|
+
WHERE dependency.namespace = job.namespace
|
|
97
|
+
AND dependency.job_id = job.id
|
|
98
|
+
AND NOT EXISTS (
|
|
99
|
+
SELECT 1 FROM bunqueue_completions AS completion
|
|
100
|
+
WHERE completion.namespace = dependency.namespace
|
|
101
|
+
AND completion.job_id = dependency.dependency_id
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
ORDER BY
|
|
105
|
+
job.priority DESC,
|
|
106
|
+
CASE WHEN job.lifo THEN 0 ELSE 1 END ASC,
|
|
107
|
+
CASE WHEN job.lifo THEN job.id END DESC,
|
|
108
|
+
CASE WHEN NOT job.lifo THEN job.run_at END ASC,
|
|
109
|
+
job.id ASC
|
|
110
|
+
LIMIT ${capacity}
|
|
111
|
+
FOR UPDATE OF job SKIP LOCKED
|
|
112
|
+
)
|
|
113
|
+
SELECT job.*
|
|
114
|
+
FROM bunqueue_jobs AS job
|
|
115
|
+
JOIN selected ON selected.id = job.id
|
|
116
|
+
WHERE job.namespace = ${ctx.config.namespace}
|
|
117
|
+
ORDER BY
|
|
118
|
+
selected.priority DESC,
|
|
119
|
+
CASE WHEN selected.lifo THEN 0 ELSE 1 END ASC,
|
|
120
|
+
CASE WHEN selected.lifo THEN selected.id END DESC,
|
|
121
|
+
CASE WHEN NOT selected.lifo THEN selected.run_at END ASC,
|
|
122
|
+
selected.id ASC
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
async function selectGroupedCandidates(tx, ctx, queue, now, capacity) {
|
|
126
|
+
return await tx `
|
|
127
|
+
WITH eligible AS (
|
|
128
|
+
SELECT job.id, job.priority, job.lifo, job.run_at,
|
|
129
|
+
row_number() OVER (
|
|
130
|
+
PARTITION BY CASE
|
|
131
|
+
WHEN job.group_id IS NULL THEN 'job:' || job.id ELSE 'group:' || job.group_id
|
|
132
|
+
END
|
|
133
|
+
ORDER BY job.priority DESC,
|
|
134
|
+
CASE WHEN job.lifo THEN 0 ELSE 1 END ASC,
|
|
135
|
+
CASE WHEN job.lifo THEN job.id END DESC,
|
|
136
|
+
CASE WHEN NOT job.lifo THEN job.run_at END ASC, job.id ASC
|
|
137
|
+
) AS group_position
|
|
138
|
+
FROM bunqueue_jobs AS job
|
|
139
|
+
WHERE job.namespace = ${ctx.config.namespace}
|
|
140
|
+
AND job.queue = ${queue}
|
|
141
|
+
AND job.state IN ('waiting', 'prioritized', 'delayed')
|
|
142
|
+
AND job.run_at <= ${now}
|
|
143
|
+
AND (job.ttl IS NULL OR job.created_at + job.ttl >= ${now})
|
|
144
|
+
AND NOT EXISTS (
|
|
145
|
+
SELECT 1 FROM bunqueue_dependencies AS dependency
|
|
146
|
+
WHERE dependency.namespace = job.namespace AND dependency.job_id = job.id
|
|
147
|
+
AND NOT EXISTS (
|
|
148
|
+
SELECT 1 FROM bunqueue_completions AS completion
|
|
149
|
+
WHERE completion.namespace = dependency.namespace
|
|
150
|
+
AND completion.job_id = dependency.dependency_id
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
AND (job.group_id IS NULL OR NOT EXISTS (
|
|
154
|
+
SELECT 1 FROM bunqueue_jobs AS active_group
|
|
155
|
+
WHERE active_group.namespace = job.namespace AND active_group.queue = job.queue
|
|
156
|
+
AND active_group.group_id = job.group_id AND active_group.state = 'active'
|
|
157
|
+
AND active_group.lease_until > ${now}
|
|
158
|
+
))
|
|
159
|
+
)
|
|
160
|
+
SELECT job.*
|
|
161
|
+
FROM bunqueue_jobs AS job
|
|
162
|
+
JOIN eligible ON eligible.id = job.id
|
|
163
|
+
WHERE job.namespace = ${ctx.config.namespace}
|
|
164
|
+
AND eligible.group_position = 1
|
|
165
|
+
AND job.state IN ('waiting', 'prioritized', 'delayed')
|
|
166
|
+
AND job.run_at <= ${now}
|
|
167
|
+
AND (job.ttl IS NULL OR job.created_at + job.ttl >= ${now})
|
|
168
|
+
AND NOT EXISTS (
|
|
169
|
+
SELECT 1 FROM bunqueue_dependencies AS dependency
|
|
170
|
+
WHERE dependency.namespace = job.namespace AND dependency.job_id = job.id
|
|
171
|
+
AND NOT EXISTS (
|
|
172
|
+
SELECT 1 FROM bunqueue_completions AS completion
|
|
173
|
+
WHERE completion.namespace = dependency.namespace
|
|
174
|
+
AND completion.job_id = dependency.dependency_id
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
AND (job.group_id IS NULL OR NOT EXISTS (
|
|
178
|
+
SELECT 1 FROM bunqueue_jobs AS active_group
|
|
179
|
+
WHERE active_group.namespace = job.namespace AND active_group.queue = job.queue
|
|
180
|
+
AND active_group.group_id = job.group_id AND active_group.state = 'active'
|
|
181
|
+
AND active_group.lease_until > ${now}
|
|
182
|
+
))
|
|
183
|
+
ORDER BY job.priority DESC,
|
|
184
|
+
CASE WHEN job.lifo THEN 0 ELSE 1 END ASC,
|
|
185
|
+
CASE WHEN job.lifo THEN job.id END DESC,
|
|
186
|
+
CASE WHEN NOT job.lifo THEN job.run_at END ASC, job.id ASC
|
|
187
|
+
LIMIT ${capacity}
|
|
188
|
+
FOR UPDATE OF job SKIP LOCKED
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
export async function selectPostgresClaimCandidates(tx, ctx, queue, now, capacity) {
|
|
192
|
+
if (await hasClaimableGroupedJob(tx, ctx, queue, now)) {
|
|
193
|
+
return await selectGroupedCandidates(tx, ctx, queue, now, capacity);
|
|
194
|
+
}
|
|
195
|
+
return (await hasClaimableLifoJob(tx, ctx, queue, now))
|
|
196
|
+
? await selectUngroupedCandidates(tx, ctx, queue, now, capacity)
|
|
197
|
+
: await selectFifoCandidates(tx, ctx, queue, now, capacity);
|
|
198
|
+
}
|