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,111 @@
|
|
|
1
|
+
import { MAX_TIMELINE_ENTRIES } from '../../../domain/types/job';
|
|
2
|
+
import { decodePostgresJob, encodePostgresValue } from './codec';
|
|
3
|
+
import { databaseNow, recordPostgresEvent } from './context';
|
|
4
|
+
import { lockPostgresDestructionCandidates, partitionPostgresDestructionCandidates, } from './dependencyDestruction';
|
|
5
|
+
async function lockJob(tx, ctx, id) {
|
|
6
|
+
const rows = await tx `
|
|
7
|
+
SELECT * FROM bunqueue_jobs
|
|
8
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${String(id)}
|
|
9
|
+
FOR UPDATE
|
|
10
|
+
`;
|
|
11
|
+
return rows[0] ?? null;
|
|
12
|
+
}
|
|
13
|
+
function pendingState(job, now) {
|
|
14
|
+
if (job.dependsOn.length > 0)
|
|
15
|
+
return 'waiting-children';
|
|
16
|
+
if (job.runAt > now)
|
|
17
|
+
return 'delayed';
|
|
18
|
+
return job.priority > 0 ? 'prioritized' : 'waiting';
|
|
19
|
+
}
|
|
20
|
+
export async function removePostgresJob(ctx, id, token) {
|
|
21
|
+
return await ctx.sql.begin(async (tx) => {
|
|
22
|
+
await lockPostgresDestructionCandidates(tx, ctx, [id]);
|
|
23
|
+
const row = await lockJob(tx, ctx, id);
|
|
24
|
+
if (!row || (row.state === 'active' && row.lease_token !== token))
|
|
25
|
+
return false;
|
|
26
|
+
const { protectedIds } = await partitionPostgresDestructionCandidates(tx, ctx, [id], [id]);
|
|
27
|
+
if (protectedIds.length > 0)
|
|
28
|
+
return false;
|
|
29
|
+
const job = decodePostgresJob(row).job;
|
|
30
|
+
const now = await databaseNow(tx);
|
|
31
|
+
await tx `
|
|
32
|
+
DELETE FROM bunqueue_job_logs
|
|
33
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ${String(id)}
|
|
34
|
+
`;
|
|
35
|
+
await tx `
|
|
36
|
+
DELETE FROM bunqueue_flow_failures
|
|
37
|
+
WHERE namespace = ${ctx.config.namespace} AND parent_id = ${String(id)}
|
|
38
|
+
`;
|
|
39
|
+
await tx `
|
|
40
|
+
DELETE FROM bunqueue_dependencies
|
|
41
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ${String(id)}
|
|
42
|
+
`;
|
|
43
|
+
await tx `
|
|
44
|
+
DELETE FROM bunqueue_completions
|
|
45
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ${String(id)}
|
|
46
|
+
`;
|
|
47
|
+
await tx `
|
|
48
|
+
DELETE FROM bunqueue_jobs
|
|
49
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${String(id)}
|
|
50
|
+
`;
|
|
51
|
+
await recordPostgresEvent(tx, ctx, {
|
|
52
|
+
queue: job.queue,
|
|
53
|
+
type: 'removed',
|
|
54
|
+
jobId: job.id,
|
|
55
|
+
occurredAt: now,
|
|
56
|
+
job,
|
|
57
|
+
state: row.state,
|
|
58
|
+
});
|
|
59
|
+
return true;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export async function retryPostgresTerminalJob(ctx, id, requestedTimestamp) {
|
|
63
|
+
return await ctx.sql.begin(async (tx) => {
|
|
64
|
+
await lockPostgresDestructionCandidates(tx, ctx, [id]);
|
|
65
|
+
const timestamp = requestedTimestamp ?? (await databaseNow(tx));
|
|
66
|
+
const row = await lockJob(tx, ctx, id);
|
|
67
|
+
if (!row || (row.state !== 'failed' && row.state !== 'completed'))
|
|
68
|
+
return false;
|
|
69
|
+
if (row.state === 'completed') {
|
|
70
|
+
const { protectedIds } = await partitionPostgresDestructionCandidates(tx, ctx, [id], [id]);
|
|
71
|
+
if (protectedIds.length > 0)
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const job = decodePostgresJob(row).job;
|
|
75
|
+
job.attempts = 0;
|
|
76
|
+
job.stallCount = 0;
|
|
77
|
+
job.completedAt = null;
|
|
78
|
+
job.startedAt = null;
|
|
79
|
+
job.runAt = timestamp;
|
|
80
|
+
job.progress = 0;
|
|
81
|
+
job.progressMessage = null;
|
|
82
|
+
job.lastHeartbeat = timestamp;
|
|
83
|
+
if (job.timeline.length < MAX_TIMELINE_ENTRIES) {
|
|
84
|
+
job.timeline.push({ state: 'waiting', timestamp });
|
|
85
|
+
}
|
|
86
|
+
const state = pendingState(job, timestamp);
|
|
87
|
+
await tx `
|
|
88
|
+
DELETE FROM bunqueue_completions
|
|
89
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ${String(id)}
|
|
90
|
+
`;
|
|
91
|
+
await tx `
|
|
92
|
+
UPDATE bunqueue_jobs
|
|
93
|
+
SET payload = ${encodePostgresValue(job)}, state = ${state}, run_at = ${timestamp},
|
|
94
|
+
started_at = NULL, completed_at = NULL, result = NULL, dlq_entry = NULL,
|
|
95
|
+
error = NULL, failure_reason = NULL, lease_owner = NULL, lease_token = NULL,
|
|
96
|
+
lease_broker_id = NULL, lease_broker_session_id = NULL,
|
|
97
|
+
lease_until = NULL, unique_key = NULL,
|
|
98
|
+
unique_expires_at = NULL, dlq_retry_state = NULL, version = version + 1
|
|
99
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${String(id)}
|
|
100
|
+
`;
|
|
101
|
+
await recordPostgresEvent(tx, ctx, {
|
|
102
|
+
queue: job.queue,
|
|
103
|
+
type: 'retried',
|
|
104
|
+
jobId: job.id,
|
|
105
|
+
occurredAt: timestamp,
|
|
106
|
+
job,
|
|
107
|
+
state,
|
|
108
|
+
});
|
|
109
|
+
return true;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TransactionSQL } from 'bun';
|
|
2
|
+
import { type JobId } from '../../../domain/types/job';
|
|
3
|
+
import { type PostgresContext } from './context';
|
|
4
|
+
export declare function recordPostgresDlqInvalidation(tx: TransactionSQL, ctx: PostgresContext, queue: string, occurredAt: number): Promise<void>;
|
|
5
|
+
/** Enforce the same oldest-first bounded DLQ policy as the SQLite engine. */
|
|
6
|
+
export declare function enforcePostgresDlqLimit(ctx: PostgresContext, queue: string, maxEntries: number): Promise<JobId[]>;
|
|
7
|
+
export declare function enforcePostgresDlqLimitInTransaction(tx: TransactionSQL, ctx: PostgresContext, queue: string, maxEntries: number): Promise<JobId[]>;
|
|
8
|
+
export interface PostgresDlqMaintenanceResult {
|
|
9
|
+
readonly retried: number;
|
|
10
|
+
readonly purged: number;
|
|
11
|
+
}
|
|
12
|
+
/** Claim due DLQ work with row locks so concurrent brokers execute it once. */
|
|
13
|
+
export declare function maintainPostgresDlq(ctx: PostgresContext, limit?: number): Promise<PostgresDlqMaintenanceResult>;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { canAutoRetry, getDlqRetryState, isDlqEntryExpired, retainDlqRetryState, scheduleNextRetry, } from '../../../domain/types/dlq';
|
|
2
|
+
import { MAX_TIMELINE_ENTRIES, jobId } from '../../../domain/types/job';
|
|
3
|
+
import { decodePostgresJob, encodePostgresValue } from './codec';
|
|
4
|
+
import { databaseNow, recordPostgresEvent } from './context';
|
|
5
|
+
import { lockPostgresDestructionCandidates, partitionPostgresDestructionCandidates, } from './dependencyDestruction';
|
|
6
|
+
import { postgresDependenciesSatisfied } from './dependencyPromotion';
|
|
7
|
+
import { discoverPostgresDlqRetryPlan, lockPostgresDlqRetryBatch } from './dlqRetryPlan';
|
|
8
|
+
export async function recordPostgresDlqInvalidation(tx, ctx, queue, occurredAt) {
|
|
9
|
+
await recordPostgresEvent(tx, ctx, {
|
|
10
|
+
queue,
|
|
11
|
+
type: 'queue-dlq-pruned',
|
|
12
|
+
jobId: jobId(`__queue__:${queue}`),
|
|
13
|
+
occurredAt,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
async function deleteTerminalRows(tx, ctx, rows, occurredAt) {
|
|
17
|
+
if (rows.length === 0)
|
|
18
|
+
return;
|
|
19
|
+
const ids = rows.map((row) => row.id);
|
|
20
|
+
const values = tx.array([...ids], 'TEXT');
|
|
21
|
+
await tx `
|
|
22
|
+
DELETE FROM bunqueue_job_logs
|
|
23
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ANY(${values})
|
|
24
|
+
`;
|
|
25
|
+
await tx `
|
|
26
|
+
DELETE FROM bunqueue_flow_failures
|
|
27
|
+
WHERE namespace = ${ctx.config.namespace} AND parent_id = ANY(${values})
|
|
28
|
+
`;
|
|
29
|
+
await tx `
|
|
30
|
+
DELETE FROM bunqueue_dependencies
|
|
31
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ANY(${values})
|
|
32
|
+
`;
|
|
33
|
+
await tx `
|
|
34
|
+
DELETE FROM bunqueue_jobs
|
|
35
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ANY(${values})
|
|
36
|
+
`;
|
|
37
|
+
const queues = [...new Set(rows.map((row) => row.queue))].sort();
|
|
38
|
+
for (const queue of queues) {
|
|
39
|
+
await recordPostgresDlqInvalidation(tx, ctx, queue, occurredAt);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Enforce the same oldest-first bounded DLQ policy as the SQLite engine. */
|
|
43
|
+
export async function enforcePostgresDlqLimit(ctx, queue, maxEntries) {
|
|
44
|
+
return await ctx.sql.begin((tx) => enforcePostgresDlqLimitInTransaction(tx, ctx, queue, maxEntries));
|
|
45
|
+
}
|
|
46
|
+
export async function enforcePostgresDlqLimitInTransaction(tx, ctx, queue, maxEntries) {
|
|
47
|
+
const retained = Math.max(1, maxEntries);
|
|
48
|
+
const candidates = await tx `
|
|
49
|
+
SELECT id FROM bunqueue_jobs
|
|
50
|
+
WHERE namespace = ${ctx.config.namespace} AND queue = ${queue} AND state = 'failed'
|
|
51
|
+
ORDER BY completed_at DESC NULLS LAST, id DESC
|
|
52
|
+
OFFSET ${retained}
|
|
53
|
+
`;
|
|
54
|
+
const candidateIds = candidates.map((row) => jobId(row.id));
|
|
55
|
+
if (candidateIds.length === 0)
|
|
56
|
+
return [];
|
|
57
|
+
await lockPostgresDestructionCandidates(tx, ctx, candidateIds);
|
|
58
|
+
const rows = await tx `
|
|
59
|
+
WITH ranked AS (
|
|
60
|
+
SELECT id, queue,
|
|
61
|
+
ROW_NUMBER() OVER (ORDER BY completed_at DESC NULLS LAST, id DESC) AS position
|
|
62
|
+
FROM bunqueue_jobs
|
|
63
|
+
WHERE namespace = ${ctx.config.namespace} AND queue = ${queue} AND state = 'failed'
|
|
64
|
+
)
|
|
65
|
+
SELECT id, queue FROM ranked
|
|
66
|
+
WHERE position > ${retained}
|
|
67
|
+
AND id = ANY(${tx.array(candidateIds.map(String), 'TEXT')})
|
|
68
|
+
ORDER BY id
|
|
69
|
+
`;
|
|
70
|
+
const plannedIds = rows.map((row) => jobId(row.id));
|
|
71
|
+
const { deletableIds } = await partitionPostgresDestructionCandidates(tx, ctx, plannedIds, plannedIds);
|
|
72
|
+
const deletable = new Set(deletableIds.map(String));
|
|
73
|
+
const removed = rows.filter((row) => deletable.has(row.id));
|
|
74
|
+
await deleteTerminalRows(tx, ctx, removed, await databaseNow(tx));
|
|
75
|
+
return removed.map((row) => jobId(row.id));
|
|
76
|
+
}
|
|
77
|
+
function pendingState(job, resolved, now) {
|
|
78
|
+
if (!resolved)
|
|
79
|
+
return 'waiting-children';
|
|
80
|
+
if (job.runAt > now)
|
|
81
|
+
return 'delayed';
|
|
82
|
+
return job.priority > 0 ? 'prioritized' : 'waiting';
|
|
83
|
+
}
|
|
84
|
+
async function retryEntry(tx, ctx, row, options) {
|
|
85
|
+
const { config, now, dependencyIds } = options;
|
|
86
|
+
const stored = decodePostgresJob(row);
|
|
87
|
+
const entry = stored.dlqEntry;
|
|
88
|
+
if (!entry)
|
|
89
|
+
return;
|
|
90
|
+
scheduleNextRetry(entry, config);
|
|
91
|
+
const job = stored.job;
|
|
92
|
+
retainDlqRetryState(job, entry);
|
|
93
|
+
job.attempts = 0;
|
|
94
|
+
job.runAt = now;
|
|
95
|
+
job.startedAt = null;
|
|
96
|
+
job.completedAt = null;
|
|
97
|
+
job.stallCount = 0;
|
|
98
|
+
job.lastHeartbeat = now;
|
|
99
|
+
if (job.timeline.length < MAX_TIMELINE_ENTRIES) {
|
|
100
|
+
job.timeline.push({ state: 'waiting', timestamp: now });
|
|
101
|
+
}
|
|
102
|
+
const state = pendingState(job, await postgresDependenciesSatisfied(tx, ctx, dependencyIds), now);
|
|
103
|
+
await tx `
|
|
104
|
+
UPDATE bunqueue_jobs
|
|
105
|
+
SET payload = ${encodePostgresValue(job)}, state = ${state}, attempts = 0,
|
|
106
|
+
run_at = ${now}, started_at = NULL, completed_at = NULL, result = NULL,
|
|
107
|
+
dlq_entry = NULL, dlq_retry_state = ${encodePostgresValue(getDlqRetryState(job))},
|
|
108
|
+
error = NULL, failure_reason = NULL, lease_owner = NULL, lease_broker_id = NULL,
|
|
109
|
+
lease_broker_session_id = NULL,
|
|
110
|
+
lease_token = NULL, lease_until = NULL, unique_key = NULL,
|
|
111
|
+
unique_expires_at = NULL, version = version + 1
|
|
112
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${row.id}
|
|
113
|
+
`;
|
|
114
|
+
await recordPostgresEvent(tx, ctx, {
|
|
115
|
+
queue: job.queue,
|
|
116
|
+
type: 'retried',
|
|
117
|
+
jobId: job.id,
|
|
118
|
+
occurredAt: now,
|
|
119
|
+
job,
|
|
120
|
+
state,
|
|
121
|
+
dlqEntry: null,
|
|
122
|
+
dlqRetryState: getDlqRetryState(job),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async function purgeExpiredEntries(ctx, ids, requestedNow) {
|
|
126
|
+
if (ids.length === 0)
|
|
127
|
+
return 0;
|
|
128
|
+
return await ctx.sql.begin(async (tx) => {
|
|
129
|
+
await lockPostgresDestructionCandidates(tx, ctx, ids);
|
|
130
|
+
const rows = await tx `
|
|
131
|
+
SELECT * FROM bunqueue_jobs
|
|
132
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
133
|
+
AND state = 'failed' AND dlq_entry IS NOT NULL
|
|
134
|
+
AND id = ANY(${tx.array(ids.map(String), 'TEXT')})
|
|
135
|
+
ORDER BY id
|
|
136
|
+
`;
|
|
137
|
+
const expired = rows.filter((row) => {
|
|
138
|
+
const entry = decodePostgresJob(row).dlqEntry;
|
|
139
|
+
return entry !== null && isDlqEntryExpired(entry, requestedNow);
|
|
140
|
+
});
|
|
141
|
+
const plannedIds = expired.map((row) => jobId(row.id));
|
|
142
|
+
const { deletableIds } = await partitionPostgresDestructionCandidates(tx, ctx, plannedIds, plannedIds);
|
|
143
|
+
const deletable = new Set(deletableIds.map(String));
|
|
144
|
+
const removed = expired.filter((row) => deletable.has(row.id));
|
|
145
|
+
await deleteTerminalRows(tx, ctx, removed, requestedNow);
|
|
146
|
+
return removed.length;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/** Claim due DLQ work with row locks so concurrent brokers execute it once. */
|
|
150
|
+
export async function maintainPostgresDlq(ctx, limit = 1000) {
|
|
151
|
+
const plan = await discoverPostgresDlqRetryPlan(ctx, limit);
|
|
152
|
+
const phase = await ctx.sql.begin(async (tx) => {
|
|
153
|
+
const batch = await lockPostgresDlqRetryBatch(tx, ctx, plan);
|
|
154
|
+
const now = await databaseNow(tx);
|
|
155
|
+
const expired = [];
|
|
156
|
+
let retried = 0;
|
|
157
|
+
for (const row of batch.rows) {
|
|
158
|
+
const stored = decodePostgresJob(row);
|
|
159
|
+
const entry = stored.dlqEntry;
|
|
160
|
+
if (!entry)
|
|
161
|
+
continue;
|
|
162
|
+
if (isDlqEntryExpired(entry, now)) {
|
|
163
|
+
expired.push(jobId(row.id));
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const dlq = batch.policies.get(row.queue);
|
|
167
|
+
if (!dlq)
|
|
168
|
+
continue;
|
|
169
|
+
if (!canAutoRetry(entry, dlq, now))
|
|
170
|
+
continue;
|
|
171
|
+
await retryEntry(tx, ctx, row, {
|
|
172
|
+
config: dlq,
|
|
173
|
+
now,
|
|
174
|
+
dependencyIds: batch.dependencies.get(row.id) ?? [],
|
|
175
|
+
});
|
|
176
|
+
retried++;
|
|
177
|
+
}
|
|
178
|
+
return { retried, expired, now };
|
|
179
|
+
});
|
|
180
|
+
return {
|
|
181
|
+
retried: phase.retried,
|
|
182
|
+
purged: await purgeExpiredEntries(ctx, phase.expired, phase.now),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PostgresContext } from './context';
|
|
2
|
+
import { type PostgresDlqMaintenanceResult } from './dlqLifecycle';
|
|
3
|
+
/** Reconcile persisted queue limits after interrupted post-commit maintenance. */
|
|
4
|
+
export declare function repairPostgresDlq(ctx: PostgresContext): Promise<number>;
|
|
5
|
+
/** Periodic DLQ work also repairs any retention skipped by a stopped broker. */
|
|
6
|
+
export declare function maintainPostgresDlq(ctx: PostgresContext, limit?: number): Promise<PostgresDlqMaintenanceResult>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { enforcePostgresDlqLimitInTransaction, maintainPostgresDlq as maintainPostgresDlqLifecycle, } from './dlqLifecycle';
|
|
2
|
+
import { decodePostgresDlqConfig } from './policies';
|
|
3
|
+
/** Reconcile persisted queue limits after interrupted post-commit maintenance. */
|
|
4
|
+
export async function repairPostgresDlq(ctx) {
|
|
5
|
+
const candidates = await ctx.sql `
|
|
6
|
+
SELECT jobs.queue, state.dlq_config, COUNT(*)::int AS failed_count
|
|
7
|
+
FROM bunqueue_jobs AS jobs
|
|
8
|
+
JOIN bunqueue_queue_state AS state
|
|
9
|
+
ON state.namespace = jobs.namespace AND state.queue = jobs.queue
|
|
10
|
+
WHERE jobs.namespace = ${ctx.config.namespace} AND jobs.state = 'failed'
|
|
11
|
+
GROUP BY jobs.queue, state.dlq_config
|
|
12
|
+
ORDER BY jobs.queue
|
|
13
|
+
`;
|
|
14
|
+
let removed = 0;
|
|
15
|
+
for (const candidate of candidates) {
|
|
16
|
+
const snapshotConfig = decodePostgresDlqConfig(candidate.queue, candidate.dlq_config);
|
|
17
|
+
if (candidate.failed_count <= snapshotConfig.maxEntries)
|
|
18
|
+
continue;
|
|
19
|
+
removed += await ctx.sql.begin(async (tx) => {
|
|
20
|
+
const [state] = await tx `
|
|
21
|
+
SELECT dlq_config FROM bunqueue_queue_state
|
|
22
|
+
WHERE namespace = ${ctx.config.namespace} AND queue = ${candidate.queue}
|
|
23
|
+
FOR SHARE
|
|
24
|
+
`;
|
|
25
|
+
const config = decodePostgresDlqConfig(candidate.queue, state?.dlq_config ?? null);
|
|
26
|
+
return (await enforcePostgresDlqLimitInTransaction(tx, ctx, candidate.queue, config.maxEntries)).length;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return removed;
|
|
30
|
+
}
|
|
31
|
+
/** Periodic DLQ work also repairs any retention skipped by a stopped broker. */
|
|
32
|
+
export async function maintainPostgresDlq(ctx, limit = 1000) {
|
|
33
|
+
const result = await maintainPostgresDlqLifecycle(ctx, limit);
|
|
34
|
+
await repairPostgresDlq(ctx);
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TransactionSQL } from 'bun';
|
|
2
|
+
import type { DlqConfig } from '../../../domain/types/dlq';
|
|
3
|
+
import { type JobId } from '../../../domain/types/job';
|
|
4
|
+
import type { PostgresContext } from './context';
|
|
5
|
+
import type { PostgresJobRow } from './types';
|
|
6
|
+
export interface PostgresDlqRetryPlan {
|
|
7
|
+
readonly candidates: ReadonlyArray<{
|
|
8
|
+
id: JobId;
|
|
9
|
+
queue: string;
|
|
10
|
+
}>;
|
|
11
|
+
readonly identityIds: readonly JobId[];
|
|
12
|
+
}
|
|
13
|
+
export interface LockedPostgresDlqRetryBatch {
|
|
14
|
+
readonly rows: readonly PostgresJobRow[];
|
|
15
|
+
readonly policies: ReadonlyMap<string, DlqConfig>;
|
|
16
|
+
readonly dependencies: ReadonlyMap<string, readonly JobId[]>;
|
|
17
|
+
}
|
|
18
|
+
/** Discover identities before taking any row lock so advisory locks stay first. */
|
|
19
|
+
export declare function discoverPostgresDlqRetryPlan(ctx: PostgresContext, limit: number): Promise<PostgresDlqRetryPlan>;
|
|
20
|
+
/** Lock policies and identities before rows, then reject any late dependency edge. */
|
|
21
|
+
export declare function lockPostgresDlqRetryBatch(tx: TransactionSQL, ctx: PostgresContext, plan: PostgresDlqRetryPlan): Promise<LockedPostgresDlqRetryBatch>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { jobId } from '../../../domain/types/job';
|
|
2
|
+
import { lockPostgresDependencyCompletions } from './dependencyPromotion';
|
|
3
|
+
import { decodePostgresDlqConfig } from './policies';
|
|
4
|
+
/** Discover identities before taking any row lock so advisory locks stay first. */
|
|
5
|
+
export async function discoverPostgresDlqRetryPlan(ctx, limit) {
|
|
6
|
+
const rows = await ctx.sql `
|
|
7
|
+
WITH candidates AS MATERIALIZED (
|
|
8
|
+
SELECT id, queue, completed_at
|
|
9
|
+
FROM bunqueue_jobs
|
|
10
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
11
|
+
AND state = 'failed' AND dlq_entry IS NOT NULL
|
|
12
|
+
ORDER BY completed_at, id
|
|
13
|
+
LIMIT ${Math.max(1, Math.floor(limit))}
|
|
14
|
+
)
|
|
15
|
+
SELECT candidate.id, candidate.queue, dependency.dependency_id
|
|
16
|
+
FROM candidates AS candidate
|
|
17
|
+
LEFT JOIN bunqueue_dependencies AS dependency
|
|
18
|
+
ON dependency.namespace = ${ctx.config.namespace}
|
|
19
|
+
AND dependency.job_id = candidate.id
|
|
20
|
+
ORDER BY candidate.completed_at, candidate.id, dependency.dependency_id
|
|
21
|
+
`;
|
|
22
|
+
const candidates = new Map();
|
|
23
|
+
const identities = new Set();
|
|
24
|
+
for (const row of rows) {
|
|
25
|
+
if (!candidates.has(row.id)) {
|
|
26
|
+
candidates.set(row.id, { id: jobId(row.id), queue: row.queue });
|
|
27
|
+
identities.add(row.id);
|
|
28
|
+
}
|
|
29
|
+
if (row.dependency_id !== null)
|
|
30
|
+
identities.add(row.dependency_id);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
candidates: [...candidates.values()],
|
|
34
|
+
identityIds: [...identities].sort().map(jobId),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async function lockPolicies(tx, ctx, queues) {
|
|
38
|
+
const uniqueQueues = [...new Set(queues)].sort();
|
|
39
|
+
if (uniqueQueues.length === 0)
|
|
40
|
+
return new Map();
|
|
41
|
+
const rows = await tx `
|
|
42
|
+
SELECT queue, dlq_config
|
|
43
|
+
FROM bunqueue_queue_state
|
|
44
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
45
|
+
AND queue = ANY(${tx.array(uniqueQueues, 'TEXT')})
|
|
46
|
+
ORDER BY queue
|
|
47
|
+
FOR SHARE
|
|
48
|
+
`;
|
|
49
|
+
const encoded = new Map(rows.map((row) => [row.queue, row.dlq_config]));
|
|
50
|
+
return new Map(uniqueQueues.map((queue) => [queue, decodePostgresDlqConfig(queue, encoded.get(queue) ?? null)]));
|
|
51
|
+
}
|
|
52
|
+
async function currentDependencies(tx, ctx, ids) {
|
|
53
|
+
if (ids.length === 0)
|
|
54
|
+
return new Map();
|
|
55
|
+
const rows = await tx `
|
|
56
|
+
SELECT job_id, dependency_id
|
|
57
|
+
FROM bunqueue_dependencies
|
|
58
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
59
|
+
AND job_id = ANY(${tx.array([...ids], 'TEXT')})
|
|
60
|
+
ORDER BY job_id, dependency_id
|
|
61
|
+
`;
|
|
62
|
+
const result = new Map();
|
|
63
|
+
for (const row of rows) {
|
|
64
|
+
const values = result.get(row.job_id) ?? [];
|
|
65
|
+
values.push(jobId(row.dependency_id));
|
|
66
|
+
result.set(row.job_id, values);
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
/** Lock policies and identities before rows, then reject any late dependency edge. */
|
|
71
|
+
export async function lockPostgresDlqRetryBatch(tx, ctx, plan) {
|
|
72
|
+
const policies = await lockPolicies(tx, ctx, plan.candidates.map((candidate) => candidate.queue));
|
|
73
|
+
await lockPostgresDependencyCompletions(tx, ctx, plan.identityIds);
|
|
74
|
+
const ids = plan.candidates.map((candidate) => String(candidate.id));
|
|
75
|
+
if (ids.length === 0)
|
|
76
|
+
return { rows: [], policies, dependencies: new Map() };
|
|
77
|
+
const plannedQueues = new Map(plan.candidates.map((candidate) => [String(candidate.id), candidate.queue]));
|
|
78
|
+
const rows = await tx `
|
|
79
|
+
SELECT job.*
|
|
80
|
+
FROM bunqueue_jobs AS job
|
|
81
|
+
WHERE job.namespace = ${ctx.config.namespace}
|
|
82
|
+
AND job.id = ANY(${tx.array(ids, 'TEXT')})
|
|
83
|
+
AND job.state = 'failed' AND job.dlq_entry IS NOT NULL
|
|
84
|
+
ORDER BY array_position(${tx.array(ids, 'TEXT')}, job.id)
|
|
85
|
+
FOR UPDATE OF job SKIP LOCKED
|
|
86
|
+
`;
|
|
87
|
+
const dependencies = await currentDependencies(tx, ctx, rows.map((row) => row.id));
|
|
88
|
+
const locked = new Set(plan.identityIds.map(String));
|
|
89
|
+
const revalidated = rows.filter((row) => plannedQueues.get(row.id) === row.queue &&
|
|
90
|
+
(dependencies.get(row.id) ?? []).every((dependencyId) => locked.has(String(dependencyId))));
|
|
91
|
+
return { rows: revalidated, policies, dependencies };
|
|
92
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type EventCommitPruner = (batchSize: number, maxBatches: number) => Promise<number>;
|
|
2
|
+
type MaintenanceReporter = (subsystem: string, error: unknown) => void;
|
|
3
|
+
export interface PostgresEventCommitGcOptions {
|
|
4
|
+
readonly batchSize?: number;
|
|
5
|
+
readonly maxBatches?: number;
|
|
6
|
+
readonly idleDelayMs?: number;
|
|
7
|
+
readonly backlogDelayMs?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Adapt journal GC frequency to backlog while keeping each database turn bounded. */
|
|
10
|
+
export declare class PostgresEventCommitGc {
|
|
11
|
+
private readonly prune;
|
|
12
|
+
private readonly report;
|
|
13
|
+
private timer;
|
|
14
|
+
private active;
|
|
15
|
+
private closed;
|
|
16
|
+
private readonly batchSize;
|
|
17
|
+
private readonly maxBatches;
|
|
18
|
+
private readonly idleDelayMs;
|
|
19
|
+
private readonly backlogDelayMs;
|
|
20
|
+
constructor(prune: EventCommitPruner, report: MaintenanceReporter, options?: PostgresEventCommitGcOptions);
|
|
21
|
+
start(): void;
|
|
22
|
+
close(): void;
|
|
23
|
+
drain(): Promise<void>;
|
|
24
|
+
private schedule;
|
|
25
|
+
private runOnce;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const DEFAULT_BATCH_SIZE = 10_000;
|
|
2
|
+
const DEFAULT_MAX_BATCHES = 8;
|
|
3
|
+
const DEFAULT_IDLE_DELAY_MS = 60_000;
|
|
4
|
+
const DEFAULT_BACKLOG_DELAY_MS = 25;
|
|
5
|
+
/** Adapt journal GC frequency to backlog while keeping each database turn bounded. */
|
|
6
|
+
export class PostgresEventCommitGc {
|
|
7
|
+
prune;
|
|
8
|
+
report;
|
|
9
|
+
timer = null;
|
|
10
|
+
active = null;
|
|
11
|
+
closed = false;
|
|
12
|
+
batchSize;
|
|
13
|
+
maxBatches;
|
|
14
|
+
idleDelayMs;
|
|
15
|
+
backlogDelayMs;
|
|
16
|
+
constructor(prune, report, options = {}) {
|
|
17
|
+
this.prune = prune;
|
|
18
|
+
this.report = report;
|
|
19
|
+
this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
20
|
+
this.maxBatches = options.maxBatches ?? DEFAULT_MAX_BATCHES;
|
|
21
|
+
this.idleDelayMs = options.idleDelayMs ?? DEFAULT_IDLE_DELAY_MS;
|
|
22
|
+
this.backlogDelayMs = options.backlogDelayMs ?? DEFAULT_BACKLOG_DELAY_MS;
|
|
23
|
+
}
|
|
24
|
+
start() {
|
|
25
|
+
this.schedule(this.idleDelayMs);
|
|
26
|
+
}
|
|
27
|
+
close() {
|
|
28
|
+
this.closed = true;
|
|
29
|
+
if (this.timer)
|
|
30
|
+
clearTimeout(this.timer);
|
|
31
|
+
this.timer = null;
|
|
32
|
+
}
|
|
33
|
+
async drain() {
|
|
34
|
+
await this.active;
|
|
35
|
+
}
|
|
36
|
+
schedule(delayMs) {
|
|
37
|
+
if (this.closed || this.timer)
|
|
38
|
+
return;
|
|
39
|
+
this.timer = setTimeout(() => {
|
|
40
|
+
this.timer = null;
|
|
41
|
+
this.active = this.runOnce().finally(() => {
|
|
42
|
+
this.active = null;
|
|
43
|
+
});
|
|
44
|
+
}, delayMs);
|
|
45
|
+
}
|
|
46
|
+
async runOnce() {
|
|
47
|
+
let nextDelay = this.idleDelayMs;
|
|
48
|
+
try {
|
|
49
|
+
const deleted = await this.prune(this.batchSize, this.maxBatches);
|
|
50
|
+
if (deleted >= this.batchSize * this.maxBatches)
|
|
51
|
+
nextDelay = this.backlogDelayMs;
|
|
52
|
+
this.report('event-commit-gc', null);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
nextDelay = this.backlogDelayMs;
|
|
56
|
+
this.report('event-commit-gc', error);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
this.schedule(nextDelay);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PostgresContext } from './context';
|
|
2
|
+
import type { PostgresStoreEvent } from './types';
|
|
3
|
+
export interface PostgresEventJournalCursor {
|
|
4
|
+
readonly commitSeq: number;
|
|
5
|
+
readonly eventId: number;
|
|
6
|
+
}
|
|
7
|
+
export interface PostgresJournalEvent {
|
|
8
|
+
readonly commitSeq: number;
|
|
9
|
+
readonly event: PostgresStoreEvent;
|
|
10
|
+
}
|
|
11
|
+
export declare function latestPostgresEventJournalBaseline(ctx: PostgresContext): Promise<{
|
|
12
|
+
commitSeq: number;
|
|
13
|
+
latestEventId: number;
|
|
14
|
+
}>;
|
|
15
|
+
/** Load events in commit order, independent of physical BIGSERIAL allocation. */
|
|
16
|
+
export declare function loadPostgresJournalEventsAfter(ctx: PostgresContext, cursor: PostgresEventJournalCursor, limit?: number): Promise<PostgresJournalEvent[]>;
|
|
17
|
+
/** Remove commit envelopes after every referenced event and watermark is gone. */
|
|
18
|
+
export declare function prunePostgresEventCommits(ctx: PostgresContext, limit?: number, maxBatches?: number): Promise<number>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { decodePostgresEvent, numeric } from './codec';
|
|
2
|
+
export async function latestPostgresEventJournalBaseline(ctx) {
|
|
3
|
+
const [row] = await ctx.sql `
|
|
4
|
+
SELECT
|
|
5
|
+
(
|
|
6
|
+
SELECT MAX(commit_seq) FROM bunqueue_event_commits
|
|
7
|
+
WHERE namespace = ${ctx.config.namespace} AND commit_seq IS NOT NULL
|
|
8
|
+
) AS commit_seq,
|
|
9
|
+
(
|
|
10
|
+
SELECT MAX(id) FROM bunqueue_events
|
|
11
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
12
|
+
) AS latest_event_id
|
|
13
|
+
`;
|
|
14
|
+
return {
|
|
15
|
+
commitSeq: numeric(row.commit_seq) ?? 0,
|
|
16
|
+
latestEventId: numeric(row.latest_event_id) ?? 0,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Load events in commit order, independent of physical BIGSERIAL allocation. */
|
|
20
|
+
export async function loadPostgresJournalEventsAfter(ctx, cursor, limit = 1000) {
|
|
21
|
+
const rows = await ctx.sql `
|
|
22
|
+
SELECT event.id, event.queue, event.event_type, event.job_id,
|
|
23
|
+
event.occurred_at, event.payload, journal.commit_seq
|
|
24
|
+
FROM bunqueue_event_commits AS journal
|
|
25
|
+
JOIN bunqueue_events AS event
|
|
26
|
+
ON event.namespace = journal.namespace
|
|
27
|
+
AND event.transaction_id = journal.transaction_id
|
|
28
|
+
WHERE journal.namespace = ${ctx.config.namespace}
|
|
29
|
+
AND journal.commit_seq IS NOT NULL
|
|
30
|
+
AND (journal.commit_seq, event.id) > (${cursor.commitSeq}, ${cursor.eventId})
|
|
31
|
+
ORDER BY journal.commit_seq, event.id
|
|
32
|
+
LIMIT ${Math.max(1, limit)}
|
|
33
|
+
`;
|
|
34
|
+
return rows.map((row) => ({
|
|
35
|
+
commitSeq: numeric(row.commit_seq) ?? 0,
|
|
36
|
+
event: decodePostgresEvent(row),
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
/** Remove commit envelopes after every referenced event and watermark is gone. */
|
|
40
|
+
export async function prunePostgresEventCommits(ctx, limit = 10_000, maxBatches = 8) {
|
|
41
|
+
const batchSize = Math.max(1, limit);
|
|
42
|
+
const batchLimit = Math.max(1, maxBatches);
|
|
43
|
+
let total = 0;
|
|
44
|
+
for (let batch = 0; batch < batchLimit; batch++) {
|
|
45
|
+
const deleted = await ctx.sql `
|
|
46
|
+
WITH candidates AS MATERIALIZED (
|
|
47
|
+
SELECT journal.namespace, journal.transaction_id
|
|
48
|
+
FROM bunqueue_event_commits AS journal
|
|
49
|
+
WHERE journal.namespace = ${ctx.config.namespace}
|
|
50
|
+
AND journal.commit_seq IS NOT NULL
|
|
51
|
+
AND NOT EXISTS (
|
|
52
|
+
SELECT 1 FROM bunqueue_events AS event
|
|
53
|
+
WHERE event.namespace = journal.namespace
|
|
54
|
+
AND event.transaction_id = journal.transaction_id
|
|
55
|
+
)
|
|
56
|
+
AND NOT EXISTS (
|
|
57
|
+
SELECT 1 FROM bunqueue_event_prune_watermarks AS watermark
|
|
58
|
+
WHERE watermark.namespace = journal.namespace
|
|
59
|
+
AND watermark.transaction_id = journal.transaction_id
|
|
60
|
+
)
|
|
61
|
+
ORDER BY journal.commit_seq
|
|
62
|
+
LIMIT ${batchSize}
|
|
63
|
+
FOR UPDATE SKIP LOCKED
|
|
64
|
+
)
|
|
65
|
+
DELETE FROM bunqueue_event_commits AS journal
|
|
66
|
+
USING candidates
|
|
67
|
+
WHERE journal.namespace = candidates.namespace
|
|
68
|
+
AND journal.transaction_id = candidates.transaction_id
|
|
69
|
+
RETURNING journal.transaction_id
|
|
70
|
+
`;
|
|
71
|
+
total += deleted.length;
|
|
72
|
+
if (deleted.length < batchSize)
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
return total;
|
|
76
|
+
}
|