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,178 @@
|
|
|
1
|
+
import { calculateBackoff, MAX_TIMELINE_ENTRIES } from '../../../domain/types/job';
|
|
2
|
+
import { createDlqEntry, getDlqRetryState, recordJobFailureAttempt, } from '../../../domain/types/dlq';
|
|
3
|
+
import { decodePostgresJob, encodePostgresValue } from './codec';
|
|
4
|
+
import { databaseNow, recordPostgresEvent } from './context';
|
|
5
|
+
import { applyPostgresFlowFailure, clearPostgresParentFlowFailures, lockPostgresFlowParents, } from './flowFailures';
|
|
6
|
+
import { getPostgresQueuePolicies } from './policies';
|
|
7
|
+
import { enforcePostgresDlqLimit } from './dlqLifecycle';
|
|
8
|
+
import { discardPostgresProtectedCronLease } from './leaseRelease';
|
|
9
|
+
import { lockPostgresDependencyCompletions } from './dependencyPromotion';
|
|
10
|
+
import { partitionPostgresDestructionCandidates } from './dependencyDestruction';
|
|
11
|
+
async function retryExpiredLease(tx, ctx, input) {
|
|
12
|
+
const { job, now, reason, error } = input;
|
|
13
|
+
recordJobFailureAttempt(job, reason, error, now);
|
|
14
|
+
job.runAt = now + calculateBackoff(job);
|
|
15
|
+
job.startedAt = null;
|
|
16
|
+
const state = job.runAt > now ? 'delayed' : job.priority > 0 ? 'prioritized' : 'waiting';
|
|
17
|
+
if (job.timeline.length < MAX_TIMELINE_ENTRIES) {
|
|
18
|
+
job.timeline.push({ state, timestamp: now, attempt: job.attempts + 1 });
|
|
19
|
+
}
|
|
20
|
+
await tx `
|
|
21
|
+
UPDATE bunqueue_jobs
|
|
22
|
+
SET payload = ${encodePostgresValue(job)}, state = ${state}, attempts = ${job.attempts},
|
|
23
|
+
run_at = ${job.runAt}, started_at = NULL, lease_owner = NULL, lease_token = NULL,
|
|
24
|
+
lease_broker_id = NULL, lease_broker_session_id = NULL,
|
|
25
|
+
lease_until = NULL, error = NULL, failure_reason = NULL,
|
|
26
|
+
dlq_entry = NULL,
|
|
27
|
+
dlq_retry_state = ${encodePostgresValue(getDlqRetryState(job))},
|
|
28
|
+
version = version + 1
|
|
29
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${String(job.id)}
|
|
30
|
+
`;
|
|
31
|
+
await recordPostgresEvent(tx, ctx, {
|
|
32
|
+
queue: job.queue,
|
|
33
|
+
type: reason === "stalled" /* FailureReason.Stalled */ ? 'stalled' : 'failed',
|
|
34
|
+
jobId: job.id,
|
|
35
|
+
occurredAt: now,
|
|
36
|
+
job,
|
|
37
|
+
state,
|
|
38
|
+
error,
|
|
39
|
+
dlqEntry: null,
|
|
40
|
+
dlqRetryState: getDlqRetryState(job),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async function failExpiredLease(tx, ctx, input) {
|
|
44
|
+
const { job, now, reason, error } = input;
|
|
45
|
+
job.completedAt = now;
|
|
46
|
+
const { dlq } = await getPostgresQueuePolicies(tx, ctx, job.queue);
|
|
47
|
+
const entry = createDlqEntry(job, reason, error, dlq, now);
|
|
48
|
+
const { protectedIds } = job.removeOnFail
|
|
49
|
+
? await partitionPostgresDestructionCandidates(tx, ctx, [job.id], [job.id])
|
|
50
|
+
: { protectedIds: [] };
|
|
51
|
+
const removed = job.removeOnFail && protectedIds.length === 0;
|
|
52
|
+
if (removed) {
|
|
53
|
+
await tx `
|
|
54
|
+
DELETE FROM bunqueue_job_logs
|
|
55
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ${String(job.id)}
|
|
56
|
+
`;
|
|
57
|
+
await tx `
|
|
58
|
+
DELETE FROM bunqueue_dependencies
|
|
59
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ${String(job.id)}
|
|
60
|
+
`;
|
|
61
|
+
await tx `
|
|
62
|
+
DELETE FROM bunqueue_jobs
|
|
63
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${String(job.id)}
|
|
64
|
+
`;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
await tx `
|
|
68
|
+
UPDATE bunqueue_jobs
|
|
69
|
+
SET payload = ${encodePostgresValue(job)}, state = 'failed', attempts = ${job.attempts},
|
|
70
|
+
completed_at = ${now}, lease_owner = NULL, lease_token = NULL, lease_until = NULL,
|
|
71
|
+
lease_broker_id = NULL, lease_broker_session_id = NULL,
|
|
72
|
+
error = ${error}, failure_reason = ${reason},
|
|
73
|
+
dlq_entry = ${encodePostgresValue(entry)}, dlq_retry_state = NULL,
|
|
74
|
+
version = version + 1
|
|
75
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${String(job.id)}
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
await recordPostgresEvent(tx, ctx, {
|
|
79
|
+
queue: job.queue,
|
|
80
|
+
type: 'failed',
|
|
81
|
+
jobId: job.id,
|
|
82
|
+
occurredAt: now,
|
|
83
|
+
job,
|
|
84
|
+
state: 'failed',
|
|
85
|
+
error,
|
|
86
|
+
removed,
|
|
87
|
+
dlqEntry: removed ? null : entry,
|
|
88
|
+
dlqRetryState: null,
|
|
89
|
+
});
|
|
90
|
+
return removed ? null : dlq.maxEntries;
|
|
91
|
+
}
|
|
92
|
+
/** Reclaim expired active leases. Row locks make the sweep safe across many brokers. */
|
|
93
|
+
export async function recoverExpiredPostgresLeases(ctx, limit = 100) {
|
|
94
|
+
const result = await ctx.sql.begin(async (tx) => {
|
|
95
|
+
const now = await databaseNow(tx);
|
|
96
|
+
const initialCandidates = await tx `
|
|
97
|
+
SELECT id FROM bunqueue_jobs
|
|
98
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
99
|
+
AND state = 'active'
|
|
100
|
+
AND lease_until <= ${now}
|
|
101
|
+
ORDER BY lease_until, id
|
|
102
|
+
LIMIT ${Math.max(1, limit)}
|
|
103
|
+
`;
|
|
104
|
+
if (initialCandidates.length === 0) {
|
|
105
|
+
return { count: 0, dlqLimits: new Map() };
|
|
106
|
+
}
|
|
107
|
+
await lockPostgresDependencyCompletions(tx, ctx, initialCandidates.map(({ id }) => id));
|
|
108
|
+
const candidates = await tx `
|
|
109
|
+
SELECT id, parent_id FROM bunqueue_jobs
|
|
110
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
111
|
+
AND id = ANY(${tx.array(initialCandidates.map(({ id }) => id), 'TEXT')})
|
|
112
|
+
AND state = 'active'
|
|
113
|
+
AND lease_until <= ${now}
|
|
114
|
+
ORDER BY lease_until, id
|
|
115
|
+
`;
|
|
116
|
+
const parentIds = [
|
|
117
|
+
...new Set(candidates.flatMap((row) => (row.parent_id ? [row.parent_id] : []))),
|
|
118
|
+
].sort();
|
|
119
|
+
await lockPostgresFlowParents(tx, ctx, parentIds.map((id) => id));
|
|
120
|
+
if (candidates.length === 0) {
|
|
121
|
+
return { count: 0, dlqLimits: new Map() };
|
|
122
|
+
}
|
|
123
|
+
const rows = await tx `
|
|
124
|
+
SELECT * FROM bunqueue_jobs
|
|
125
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
126
|
+
AND id = ANY(${tx.array(candidates.map((row) => row.id), 'TEXT')})
|
|
127
|
+
AND state = 'active'
|
|
128
|
+
AND lease_until <= ${now}
|
|
129
|
+
ORDER BY lease_until, id
|
|
130
|
+
LIMIT ${Math.max(1, limit)}
|
|
131
|
+
FOR UPDATE SKIP LOCKED
|
|
132
|
+
`;
|
|
133
|
+
const dlqLimits = new Map();
|
|
134
|
+
for (const row of rows) {
|
|
135
|
+
if (await discardPostgresProtectedCronLease(tx, ctx, row, now))
|
|
136
|
+
continue;
|
|
137
|
+
const job = decodePostgresJob(row).job;
|
|
138
|
+
const timedOut = job.timeout !== null && job.startedAt !== null && job.startedAt + job.timeout <= now;
|
|
139
|
+
const { stall } = await getPostgresQueuePolicies(tx, ctx, job.queue);
|
|
140
|
+
job.attempts++;
|
|
141
|
+
if (!timedOut)
|
|
142
|
+
job.stallCount++;
|
|
143
|
+
const attemptsExhausted = job.attempts >= job.maxAttempts;
|
|
144
|
+
const reason = timedOut
|
|
145
|
+
? "timeout" /* FailureReason.Timeout */
|
|
146
|
+
: attemptsExhausted
|
|
147
|
+
? "max_attempts_exceeded" /* FailureReason.MaxAttemptsExceeded */
|
|
148
|
+
: "stalled" /* FailureReason.Stalled */;
|
|
149
|
+
const error = timedOut ? 'Job processing timed out' : 'PostgreSQL lease expired';
|
|
150
|
+
if (job.timeline.length < MAX_TIMELINE_ENTRIES) {
|
|
151
|
+
job.timeline.push({
|
|
152
|
+
state: 'failed',
|
|
153
|
+
timestamp: now,
|
|
154
|
+
error,
|
|
155
|
+
attempt: job.attempts,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const failure = { job, now, reason, error };
|
|
159
|
+
const stallsExhausted = !timedOut && stall.maxStalls > 0 && job.stallCount >= stall.maxStalls;
|
|
160
|
+
if (!attemptsExhausted && !stallsExhausted)
|
|
161
|
+
await retryExpiredLease(tx, ctx, failure);
|
|
162
|
+
else {
|
|
163
|
+
const maxEntries = await failExpiredLease(tx, ctx, failure);
|
|
164
|
+
await clearPostgresParentFlowFailures(tx, ctx, job.id);
|
|
165
|
+
const request = await applyPostgresFlowFailure(tx, ctx, job, error, now);
|
|
166
|
+
if (maxEntries !== null)
|
|
167
|
+
dlqLimits.set(job.queue, maxEntries);
|
|
168
|
+
if (request)
|
|
169
|
+
dlqLimits.set(request.queue, request.maxEntries);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return { count: rows.length, dlqLimits };
|
|
173
|
+
});
|
|
174
|
+
for (const [queue, maxEntries] of [...result.dlqLimits].sort(([left], [right]) => left.localeCompare(right))) {
|
|
175
|
+
await enforcePostgresDlqLimit(ctx, queue, maxEntries);
|
|
176
|
+
}
|
|
177
|
+
return result.count;
|
|
178
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TransactionSQL } from 'bun';
|
|
2
|
+
import type { JobId } from '../../../domain/types/job';
|
|
3
|
+
import { type PostgresContext } from './context';
|
|
4
|
+
import { type PostgresDlqLimitRequest } from './flowFailures';
|
|
5
|
+
/** Atomically attach an existing child to a linkable parent. */
|
|
6
|
+
export declare function updatePostgresJobParentInTransaction(tx: TransactionSQL, ctx: PostgresContext, childId: JobId, parentId: JobId, dependencyLockHeld?: boolean): Promise<PostgresDlqLimitRequest | null>;
|
|
7
|
+
export declare function updatePostgresJobParent(ctx: PostgresContext, childId: JobId, parentId: JobId): Promise<void>;
|
|
8
|
+
/** Detach a child and promote the parent when no unresolved edge remains. */
|
|
9
|
+
export declare function removePostgresChildDependency(ctx: PostgresContext, childId: JobId): Promise<boolean>;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { decodePostgresJob, encodePostgresValue } from './codec';
|
|
2
|
+
import { databaseNow, recordPostgresEvent } from './context';
|
|
3
|
+
import { enforcePostgresDlqLimit } from './dlqLifecycle';
|
|
4
|
+
import { applyPostgresFlowFailure, lockPostgresFlowParent, } from './flowFailures';
|
|
5
|
+
import { lockPostgresDependencyCompletions } from './dependencyPromotion';
|
|
6
|
+
function recordData(value) {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
8
|
+
? { ...value }
|
|
9
|
+
: {};
|
|
10
|
+
}
|
|
11
|
+
function attachChild(child, parent) {
|
|
12
|
+
return {
|
|
13
|
+
...child,
|
|
14
|
+
parentId: parent.id,
|
|
15
|
+
data: { ...recordData(child.data), __parentId: String(parent.id), __parentQueue: parent.queue },
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function detachChild(child) {
|
|
19
|
+
const data = recordData(child.data);
|
|
20
|
+
delete data.__parentId;
|
|
21
|
+
delete data.__parentQueue;
|
|
22
|
+
return { ...child, parentId: null, data };
|
|
23
|
+
}
|
|
24
|
+
function withChildren(parent, childrenIds, dependsOn) {
|
|
25
|
+
const data = recordData(parent.data);
|
|
26
|
+
if (childrenIds.length > 0)
|
|
27
|
+
data.__childrenIds = childrenIds.map(String);
|
|
28
|
+
else
|
|
29
|
+
delete data.__childrenIds;
|
|
30
|
+
return { ...parent, childrenIds, dependsOn, data };
|
|
31
|
+
}
|
|
32
|
+
async function lockJobs(tx, ctx, ids) {
|
|
33
|
+
const values = [...new Set(ids.map(String))].sort();
|
|
34
|
+
const rows = await tx `
|
|
35
|
+
SELECT * FROM bunqueue_jobs
|
|
36
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ANY(${tx.array(values, 'TEXT')})
|
|
37
|
+
ORDER BY id
|
|
38
|
+
FOR UPDATE
|
|
39
|
+
`;
|
|
40
|
+
return new Map(rows.map((row) => [row.id, row]));
|
|
41
|
+
}
|
|
42
|
+
async function hasDependencyCycle(tx, ctx, childId, parentId) {
|
|
43
|
+
const rows = await tx `
|
|
44
|
+
WITH RECURSIVE reachable(id) AS (
|
|
45
|
+
SELECT dependency_id
|
|
46
|
+
FROM bunqueue_dependencies
|
|
47
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ${String(childId)}
|
|
48
|
+
UNION
|
|
49
|
+
SELECT dependency.dependency_id
|
|
50
|
+
FROM bunqueue_dependencies AS dependency
|
|
51
|
+
JOIN reachable ON reachable.id = dependency.job_id
|
|
52
|
+
WHERE dependency.namespace = ${ctx.config.namespace}
|
|
53
|
+
)
|
|
54
|
+
SELECT 1 AS found FROM reachable WHERE id = ${String(parentId)} LIMIT 1
|
|
55
|
+
`;
|
|
56
|
+
return rows.length > 0;
|
|
57
|
+
}
|
|
58
|
+
async function unresolvedCount(tx, ctx, parentId) {
|
|
59
|
+
const [row] = await tx `
|
|
60
|
+
SELECT COUNT(*)::bigint AS count
|
|
61
|
+
FROM bunqueue_dependencies AS dependency
|
|
62
|
+
WHERE dependency.namespace = ${ctx.config.namespace}
|
|
63
|
+
AND dependency.job_id = ${String(parentId)}
|
|
64
|
+
AND NOT EXISTS (
|
|
65
|
+
SELECT 1 FROM bunqueue_completions AS completion
|
|
66
|
+
WHERE completion.namespace = dependency.namespace
|
|
67
|
+
AND completion.job_id = dependency.dependency_id
|
|
68
|
+
)
|
|
69
|
+
AND NOT EXISTS (
|
|
70
|
+
SELECT 1 FROM bunqueue_jobs AS completed
|
|
71
|
+
WHERE completed.namespace = dependency.namespace
|
|
72
|
+
AND completed.id = dependency.dependency_id AND completed.state = 'completed'
|
|
73
|
+
)
|
|
74
|
+
`;
|
|
75
|
+
return Number(row.count);
|
|
76
|
+
}
|
|
77
|
+
function readyState(job, now) {
|
|
78
|
+
if (job.runAt > now)
|
|
79
|
+
return 'delayed';
|
|
80
|
+
return job.priority > 0 ? 'prioritized' : 'waiting';
|
|
81
|
+
}
|
|
82
|
+
/** Atomically attach an existing child to a linkable parent. */
|
|
83
|
+
export async function updatePostgresJobParentInTransaction(tx, ctx, childId, parentId, dependencyLockHeld = false) {
|
|
84
|
+
if (childId === parentId)
|
|
85
|
+
throw new Error('A flow job cannot be its own parent');
|
|
86
|
+
if (!dependencyLockHeld)
|
|
87
|
+
await lockPostgresDependencyCompletions(tx, ctx, [childId]);
|
|
88
|
+
await lockPostgresFlowParent(tx, ctx, parentId);
|
|
89
|
+
const rows = await lockJobs(tx, ctx, [childId, parentId]);
|
|
90
|
+
const childRow = rows.get(String(childId));
|
|
91
|
+
const parentRow = rows.get(String(parentId));
|
|
92
|
+
if (!childRow)
|
|
93
|
+
throw new Error(`Child job not found: ${String(childId)}`);
|
|
94
|
+
if (!parentRow)
|
|
95
|
+
throw new Error(`Parent job not found: ${String(parentId)}`);
|
|
96
|
+
if (['active', 'completed', 'failed'].includes(parentRow.state)) {
|
|
97
|
+
throw new Error(`Parent job ${String(parentId)} is not linkable`);
|
|
98
|
+
}
|
|
99
|
+
const child = decodePostgresJob(childRow).job;
|
|
100
|
+
const parent = decodePostgresJob(parentRow).job;
|
|
101
|
+
if (child.parentId && child.parentId !== parentId) {
|
|
102
|
+
throw new Error(`Child job ${String(childId)} already belongs to another parent`);
|
|
103
|
+
}
|
|
104
|
+
if (await hasDependencyCycle(tx, ctx, childId, parentId)) {
|
|
105
|
+
throw new Error('Flow dependency cycle detected');
|
|
106
|
+
}
|
|
107
|
+
const linkedChild = attachChild(child, parent);
|
|
108
|
+
const linkedParent = withChildren(parent, [...new Set([...parent.childrenIds, childId])], [...new Set([...parent.dependsOn, childId])]);
|
|
109
|
+
await tx `
|
|
110
|
+
INSERT INTO bunqueue_dependencies (namespace, job_id, dependency_id)
|
|
111
|
+
VALUES (${ctx.config.namespace}, ${String(parentId)}, ${String(childId)})
|
|
112
|
+
ON CONFLICT DO NOTHING
|
|
113
|
+
`;
|
|
114
|
+
const now = await databaseNow(tx);
|
|
115
|
+
const state = (await unresolvedCount(tx, ctx, parentId)) > 0
|
|
116
|
+
? 'waiting-children'
|
|
117
|
+
: readyState(linkedParent, now);
|
|
118
|
+
await tx `
|
|
119
|
+
UPDATE bunqueue_jobs
|
|
120
|
+
SET payload = ${encodePostgresValue(linkedChild)}, parent_id = ${String(parentId)},
|
|
121
|
+
version = version + 1
|
|
122
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${String(childId)}
|
|
123
|
+
`;
|
|
124
|
+
await tx `
|
|
125
|
+
UPDATE bunqueue_jobs
|
|
126
|
+
SET payload = ${encodePostgresValue(linkedParent)}, state = ${state}, version = version + 1
|
|
127
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${String(parentId)}
|
|
128
|
+
`;
|
|
129
|
+
await recordPostgresEvent(tx, ctx, {
|
|
130
|
+
queue: parent.queue,
|
|
131
|
+
type: state === 'waiting-children' ? 'waiting-children' : 'retried',
|
|
132
|
+
jobId: parentId,
|
|
133
|
+
occurredAt: now,
|
|
134
|
+
job: linkedParent,
|
|
135
|
+
state,
|
|
136
|
+
});
|
|
137
|
+
if (childRow.state === 'failed') {
|
|
138
|
+
return await applyPostgresFlowFailure(tx, ctx, linkedChild, childRow.error ?? undefined, now);
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
export async function updatePostgresJobParent(ctx, childId, parentId) {
|
|
143
|
+
const request = await ctx.sql.begin((tx) => updatePostgresJobParentInTransaction(tx, ctx, childId, parentId));
|
|
144
|
+
if (request)
|
|
145
|
+
await enforcePostgresDlqLimit(ctx, request.queue, request.maxEntries);
|
|
146
|
+
}
|
|
147
|
+
/** Detach a child and promote the parent when no unresolved edge remains. */
|
|
148
|
+
export async function removePostgresChildDependency(ctx, childId) {
|
|
149
|
+
return await ctx.sql.begin(async (tx) => {
|
|
150
|
+
await lockPostgresDependencyCompletions(tx, ctx, [childId]);
|
|
151
|
+
const [relation] = await tx `
|
|
152
|
+
SELECT parent_id FROM bunqueue_jobs
|
|
153
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${String(childId)}
|
|
154
|
+
`;
|
|
155
|
+
if (!relation)
|
|
156
|
+
throw new Error(`Job not found: ${String(childId)}`);
|
|
157
|
+
if (!relation.parent_id)
|
|
158
|
+
throw new Error(`Job ${String(childId)} has no parent`);
|
|
159
|
+
const parentId = relation.parent_id;
|
|
160
|
+
await lockPostgresFlowParent(tx, ctx, parentId);
|
|
161
|
+
const rows = await lockJobs(tx, ctx, [childId, parentId]);
|
|
162
|
+
const childRow = rows.get(String(childId));
|
|
163
|
+
const parentRow = rows.get(String(parentId));
|
|
164
|
+
if (!childRow || !parentRow)
|
|
165
|
+
return false;
|
|
166
|
+
const child = decodePostgresJob(childRow).job;
|
|
167
|
+
const parent = decodePostgresJob(parentRow).job;
|
|
168
|
+
if (child.parentId !== parentId || !parent.dependsOn.includes(childId))
|
|
169
|
+
return false;
|
|
170
|
+
const detachedChild = detachChild(child);
|
|
171
|
+
const detachedParent = withChildren(parent, parent.childrenIds.filter((id) => id !== childId), parent.dependsOn.filter((id) => id !== childId));
|
|
172
|
+
await tx `
|
|
173
|
+
DELETE FROM bunqueue_dependencies
|
|
174
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ${String(parentId)}
|
|
175
|
+
AND dependency_id = ${String(childId)}
|
|
176
|
+
`;
|
|
177
|
+
const now = await databaseNow(tx);
|
|
178
|
+
const state = (await unresolvedCount(tx, ctx, parentId)) > 0
|
|
179
|
+
? 'waiting-children'
|
|
180
|
+
: readyState(detachedParent, now);
|
|
181
|
+
await tx `
|
|
182
|
+
UPDATE bunqueue_jobs
|
|
183
|
+
SET payload = ${encodePostgresValue(detachedChild)}, parent_id = NULL, version = version + 1
|
|
184
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${String(childId)}
|
|
185
|
+
`;
|
|
186
|
+
await tx `
|
|
187
|
+
UPDATE bunqueue_jobs
|
|
188
|
+
SET payload = ${encodePostgresValue(detachedParent)}, state = ${state}, version = version + 1
|
|
189
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${String(parentId)}
|
|
190
|
+
`;
|
|
191
|
+
await recordPostgresEvent(tx, ctx, {
|
|
192
|
+
queue: parent.queue,
|
|
193
|
+
type: state === 'waiting-children' ? 'waiting-children' : 'retried',
|
|
194
|
+
jobId: parentId,
|
|
195
|
+
occurredAt: now,
|
|
196
|
+
job: detachedParent,
|
|
197
|
+
state,
|
|
198
|
+
});
|
|
199
|
+
return true;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type JobId } from '../../../domain/types/job';
|
|
2
|
+
import { type PostgresContext } from './context';
|
|
3
|
+
/** Atomically remove only direct, pending children without orphaning live consumers. */
|
|
4
|
+
export declare function removePostgresUnprocessedChildren(ctx: PostgresContext, parentId: JobId): Promise<JobId[]>;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { jobId, MAX_TIMELINE_ENTRIES } from '../../../domain/types/job';
|
|
2
|
+
import { recordPostgresJobEvents } from './batchEvents';
|
|
3
|
+
import { decodePostgresJob, encodePostgresValue, postgresByteaBase64 } from './codec';
|
|
4
|
+
import { databaseNow } from './context';
|
|
5
|
+
import { lockPostgresDependencyCompletions } from './dependencyPromotion';
|
|
6
|
+
import { lockPostgresFlowParent } from './flowFailures';
|
|
7
|
+
const REMOVABLE_STATES = new Set([
|
|
8
|
+
'waiting',
|
|
9
|
+
'prioritized',
|
|
10
|
+
'delayed',
|
|
11
|
+
'waiting-children',
|
|
12
|
+
]);
|
|
13
|
+
const LIVE_CONSUMER_STATES = [
|
|
14
|
+
'waiting',
|
|
15
|
+
'prioritized',
|
|
16
|
+
'delayed',
|
|
17
|
+
'waiting-children',
|
|
18
|
+
'active',
|
|
19
|
+
];
|
|
20
|
+
function recordData(value) {
|
|
21
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
22
|
+
? { ...value }
|
|
23
|
+
: {};
|
|
24
|
+
}
|
|
25
|
+
function uniqueIds(ids) {
|
|
26
|
+
return [...new Set(ids.map(String))].sort();
|
|
27
|
+
}
|
|
28
|
+
function detachChild(job) {
|
|
29
|
+
const data = recordData(job.data);
|
|
30
|
+
delete data.__parentId;
|
|
31
|
+
delete data.__parentQueue;
|
|
32
|
+
return { ...job, parentId: null, data };
|
|
33
|
+
}
|
|
34
|
+
function updateParent(job, removedIds) {
|
|
35
|
+
const childrenIds = job.childrenIds.filter((id) => !removedIds.has(String(id)));
|
|
36
|
+
const dependsOn = job.dependsOn.filter((id) => !removedIds.has(String(id)));
|
|
37
|
+
const data = recordData(job.data);
|
|
38
|
+
if (childrenIds.length > 0)
|
|
39
|
+
data.__childrenIds = childrenIds.map(String);
|
|
40
|
+
else
|
|
41
|
+
delete data.__childrenIds;
|
|
42
|
+
return { ...job, childrenIds, dependsOn, data };
|
|
43
|
+
}
|
|
44
|
+
function readyState(job, now) {
|
|
45
|
+
if (job.runAt > now)
|
|
46
|
+
return 'delayed';
|
|
47
|
+
return job.priority > 0 ? 'prioritized' : 'waiting';
|
|
48
|
+
}
|
|
49
|
+
async function discoverChildren(tx, ctx, parentId) {
|
|
50
|
+
const [parent] = await tx `
|
|
51
|
+
SELECT * FROM bunqueue_jobs
|
|
52
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${String(parentId)}
|
|
53
|
+
`;
|
|
54
|
+
if (!parent)
|
|
55
|
+
return [];
|
|
56
|
+
const linked = await tx `
|
|
57
|
+
SELECT id AS child_id
|
|
58
|
+
FROM bunqueue_jobs
|
|
59
|
+
WHERE namespace = ${ctx.config.namespace} AND parent_id = ${String(parentId)}
|
|
60
|
+
UNION
|
|
61
|
+
SELECT dependency_id AS child_id
|
|
62
|
+
FROM bunqueue_dependencies
|
|
63
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ${String(parentId)}
|
|
64
|
+
`;
|
|
65
|
+
return uniqueIds([
|
|
66
|
+
...decodePostgresJob(parent).job.childrenIds,
|
|
67
|
+
...linked.map((row) => row.child_id),
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
async function lockRows(tx, ctx, parentId, candidates) {
|
|
71
|
+
const consumers = await tx `
|
|
72
|
+
SELECT DISTINCT consumer.id
|
|
73
|
+
FROM bunqueue_dependencies AS dependency
|
|
74
|
+
JOIN bunqueue_jobs AS consumer
|
|
75
|
+
ON consumer.namespace = dependency.namespace AND consumer.id = dependency.job_id
|
|
76
|
+
WHERE dependency.namespace = ${ctx.config.namespace}
|
|
77
|
+
AND dependency.dependency_id = ANY(${tx.array([...candidates], 'TEXT')})
|
|
78
|
+
AND consumer.state = ANY(${tx.array([...LIVE_CONSUMER_STATES], 'TEXT')})
|
|
79
|
+
ORDER BY consumer.id
|
|
80
|
+
`;
|
|
81
|
+
const ids = uniqueIds([parentId, ...candidates, ...consumers.map((row) => row.id)]);
|
|
82
|
+
const rows = await tx `
|
|
83
|
+
SELECT * FROM bunqueue_jobs
|
|
84
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ANY(${tx.array(ids, 'TEXT')})
|
|
85
|
+
ORDER BY id
|
|
86
|
+
FOR UPDATE
|
|
87
|
+
`;
|
|
88
|
+
return new Map(rows.map((row) => [row.id, row]));
|
|
89
|
+
}
|
|
90
|
+
async function protectedCandidates(tx, ctx, parentId, removable) {
|
|
91
|
+
if (removable.size === 0)
|
|
92
|
+
return new Set();
|
|
93
|
+
const edges = await tx `
|
|
94
|
+
SELECT dependency.job_id AS consumer_id, dependency.dependency_id
|
|
95
|
+
FROM bunqueue_dependencies AS dependency
|
|
96
|
+
JOIN bunqueue_jobs AS consumer
|
|
97
|
+
ON consumer.namespace = dependency.namespace AND consumer.id = dependency.job_id
|
|
98
|
+
WHERE dependency.namespace = ${ctx.config.namespace}
|
|
99
|
+
AND dependency.dependency_id = ANY(${tx.array([...removable], 'TEXT')})
|
|
100
|
+
AND consumer.state = ANY(${tx.array([...LIVE_CONSUMER_STATES], 'TEXT')})
|
|
101
|
+
ORDER BY dependency.job_id, dependency.dependency_id
|
|
102
|
+
`;
|
|
103
|
+
const protectedIds = new Set();
|
|
104
|
+
for (const edge of edges) {
|
|
105
|
+
if (edge.consumer_id !== String(parentId) && !removable.has(edge.consumer_id)) {
|
|
106
|
+
protectedIds.add(edge.dependency_id);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
let changed = true;
|
|
110
|
+
while (changed) {
|
|
111
|
+
changed = false;
|
|
112
|
+
for (const edge of edges) {
|
|
113
|
+
if (!protectedIds.has(edge.consumer_id) || protectedIds.has(edge.dependency_id))
|
|
114
|
+
continue;
|
|
115
|
+
protectedIds.add(edge.dependency_id);
|
|
116
|
+
changed = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return protectedIds;
|
|
120
|
+
}
|
|
121
|
+
async function deleteJobs(tx, ctx, ids) {
|
|
122
|
+
if (ids.length === 0)
|
|
123
|
+
return;
|
|
124
|
+
await tx `
|
|
125
|
+
DELETE FROM bunqueue_dependencies
|
|
126
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ANY(${tx.array([...ids], 'TEXT')})
|
|
127
|
+
`;
|
|
128
|
+
await tx `
|
|
129
|
+
DELETE FROM bunqueue_job_logs
|
|
130
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ANY(${tx.array([...ids], 'TEXT')})
|
|
131
|
+
`;
|
|
132
|
+
await tx `
|
|
133
|
+
DELETE FROM bunqueue_flow_failures
|
|
134
|
+
WHERE namespace = ${ctx.config.namespace} AND parent_id = ANY(${tx.array([...ids], 'TEXT')})
|
|
135
|
+
`;
|
|
136
|
+
await tx `
|
|
137
|
+
DELETE FROM bunqueue_jobs
|
|
138
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ANY(${tx.array([...ids], 'TEXT')})
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
async function detachJobs(tx, ctx, jobs) {
|
|
142
|
+
if (jobs.length === 0)
|
|
143
|
+
return;
|
|
144
|
+
await tx `
|
|
145
|
+
UPDATE bunqueue_jobs AS child
|
|
146
|
+
SET payload = decode(batch.payload_base64, 'base64'), parent_id = NULL,
|
|
147
|
+
version = child.version + 1
|
|
148
|
+
FROM unnest(
|
|
149
|
+
${tx.array(jobs.map((job) => String(job.id)), 'TEXT')},
|
|
150
|
+
${tx.array(jobs.map((job) => postgresByteaBase64(encodePostgresValue(job))), 'TEXT')}
|
|
151
|
+
) AS batch(id, payload_base64)
|
|
152
|
+
WHERE child.namespace = ${ctx.config.namespace} AND child.id = batch.id
|
|
153
|
+
`;
|
|
154
|
+
}
|
|
155
|
+
async function unresolvedParentDependencies(tx, ctx, parentId) {
|
|
156
|
+
const [row] = await tx `
|
|
157
|
+
SELECT EXISTS (
|
|
158
|
+
SELECT 1 FROM bunqueue_dependencies AS dependency
|
|
159
|
+
WHERE dependency.namespace = ${ctx.config.namespace}
|
|
160
|
+
AND dependency.job_id = ${String(parentId)}
|
|
161
|
+
AND NOT EXISTS (
|
|
162
|
+
SELECT 1 FROM bunqueue_completions AS completion
|
|
163
|
+
WHERE completion.namespace = dependency.namespace
|
|
164
|
+
AND completion.job_id = dependency.dependency_id
|
|
165
|
+
)
|
|
166
|
+
AND NOT EXISTS (
|
|
167
|
+
SELECT 1 FROM bunqueue_jobs AS completed
|
|
168
|
+
WHERE completed.namespace = dependency.namespace
|
|
169
|
+
AND completed.id = dependency.dependency_id AND completed.state = 'completed'
|
|
170
|
+
)
|
|
171
|
+
) AS unresolved
|
|
172
|
+
`;
|
|
173
|
+
return row.unresolved;
|
|
174
|
+
}
|
|
175
|
+
/** Atomically remove only direct, pending children without orphaning live consumers. */
|
|
176
|
+
export async function removePostgresUnprocessedChildren(ctx, parentId) {
|
|
177
|
+
return await ctx.sql.begin(async (tx) => {
|
|
178
|
+
const candidates = await discoverChildren(tx, ctx, parentId);
|
|
179
|
+
if (candidates.length === 0)
|
|
180
|
+
return [];
|
|
181
|
+
await lockPostgresDependencyCompletions(tx, ctx, candidates.map(jobId));
|
|
182
|
+
await lockPostgresFlowParent(tx, ctx, parentId);
|
|
183
|
+
const rows = await lockRows(tx, ctx, parentId, candidates);
|
|
184
|
+
const parentRow = rows.get(String(parentId));
|
|
185
|
+
if (!parentRow)
|
|
186
|
+
return [];
|
|
187
|
+
const removable = new Set(candidates.filter((id) => {
|
|
188
|
+
const row = rows.get(id);
|
|
189
|
+
return row?.parent_id === String(parentId) && REMOVABLE_STATES.has(row.state);
|
|
190
|
+
}));
|
|
191
|
+
if (removable.size === 0)
|
|
192
|
+
return [];
|
|
193
|
+
const protectedIds = await protectedCandidates(tx, ctx, parentId, removable);
|
|
194
|
+
const deletedIds = [...removable].filter((id) => !protectedIds.has(id));
|
|
195
|
+
const detachedRows = [...protectedIds]
|
|
196
|
+
.map((id) => rows.get(id))
|
|
197
|
+
.filter((row) => row !== undefined);
|
|
198
|
+
const deletedRows = deletedIds
|
|
199
|
+
.map((id) => rows.get(id))
|
|
200
|
+
.filter((row) => row !== undefined);
|
|
201
|
+
const detachedJobs = detachedRows.map((row) => detachChild(decodePostgresJob(row).job));
|
|
202
|
+
const removed = new Set(removable);
|
|
203
|
+
await tx `
|
|
204
|
+
DELETE FROM bunqueue_dependencies
|
|
205
|
+
WHERE namespace = ${ctx.config.namespace} AND job_id = ${String(parentId)}
|
|
206
|
+
AND dependency_id = ANY(${tx.array([...removed], 'TEXT')})
|
|
207
|
+
`;
|
|
208
|
+
await deleteJobs(tx, ctx, deletedIds);
|
|
209
|
+
await detachJobs(tx, ctx, detachedJobs);
|
|
210
|
+
const now = await databaseNow(tx);
|
|
211
|
+
const parent = updateParent(decodePostgresJob(parentRow).job, removed);
|
|
212
|
+
const promote = parentRow.state === 'waiting-children' &&
|
|
213
|
+
!(await unresolvedParentDependencies(tx, ctx, parentId));
|
|
214
|
+
const parentState = promote ? readyState(parent, now) : parentRow.state;
|
|
215
|
+
if (promote && parent.timeline.at(-1)?.state !== parentState) {
|
|
216
|
+
if (parent.timeline.length < MAX_TIMELINE_ENTRIES) {
|
|
217
|
+
parent.timeline.push({ state: parentState, timestamp: now });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
await tx `
|
|
221
|
+
UPDATE bunqueue_jobs
|
|
222
|
+
SET payload = ${encodePostgresValue(parent)}, state = ${parentState}, version = version + 1
|
|
223
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${String(parentId)}
|
|
224
|
+
`;
|
|
225
|
+
await recordPostgresJobEvents(tx, ctx, [
|
|
226
|
+
...deletedRows.map((row) => ({
|
|
227
|
+
job: decodePostgresJob(row).job,
|
|
228
|
+
type: 'removed',
|
|
229
|
+
state: row.state,
|
|
230
|
+
removed: true,
|
|
231
|
+
})),
|
|
232
|
+
...detachedRows.map((row, index) => ({
|
|
233
|
+
job: detachedJobs[index],
|
|
234
|
+
type: 'updated',
|
|
235
|
+
state: row.state,
|
|
236
|
+
})),
|
|
237
|
+
{ job: parent, type: promote ? 'retried' : 'updated', state: parentState },
|
|
238
|
+
], now);
|
|
239
|
+
return [...removed].map(jobId);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TransactionSQL } from 'bun';
|
|
2
|
+
import type { JobId } from '../../../domain/types/job';
|
|
3
|
+
import { type PostgresContext } from './context';
|
|
4
|
+
/** Update the pending successor addressed through a completed repeat generation. */
|
|
5
|
+
export declare function updatePostgresRepeatSuccessorData(tx: TransactionSQL, ctx: PostgresContext, originalId: JobId, data: unknown): Promise<boolean>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { decodePostgresJob, encodePostgresValue } from './codec';
|
|
2
|
+
import { databaseNow, recordPostgresEvent } from './context';
|
|
3
|
+
import { updatedPostgresJobData } from './jobData';
|
|
4
|
+
/** Update the pending successor addressed through a completed repeat generation. */
|
|
5
|
+
export async function updatePostgresRepeatSuccessorData(tx, ctx, originalId, data) {
|
|
6
|
+
const links = await tx `
|
|
7
|
+
SELECT successor_id FROM bunqueue_repeat_links
|
|
8
|
+
WHERE namespace = ${ctx.config.namespace} AND original_id = ${String(originalId)}
|
|
9
|
+
`;
|
|
10
|
+
if (!links[0])
|
|
11
|
+
return false;
|
|
12
|
+
const rows = await tx `
|
|
13
|
+
SELECT * FROM bunqueue_jobs
|
|
14
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${links[0].successor_id}
|
|
15
|
+
FOR UPDATE
|
|
16
|
+
`;
|
|
17
|
+
const row = rows[0];
|
|
18
|
+
if (!row || ['active', 'completed', 'failed'].includes(row.state))
|
|
19
|
+
return false;
|
|
20
|
+
const job = decodePostgresJob(row).job;
|
|
21
|
+
const updated = { ...job, data: updatedPostgresJobData(job, data) };
|
|
22
|
+
const now = await databaseNow(tx);
|
|
23
|
+
await tx `
|
|
24
|
+
UPDATE bunqueue_jobs
|
|
25
|
+
SET payload = ${encodePostgresValue(updated)}, version = version + 1
|
|
26
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${row.id}
|
|
27
|
+
`;
|
|
28
|
+
await recordPostgresEvent(tx, ctx, {
|
|
29
|
+
queue: job.queue,
|
|
30
|
+
type: 'updated',
|
|
31
|
+
jobId: job.id,
|
|
32
|
+
occurredAt: now,
|
|
33
|
+
job: updated,
|
|
34
|
+
state: row.state,
|
|
35
|
+
});
|
|
36
|
+
return true;
|
|
37
|
+
}
|