bunqueue 2.8.60 → 2.9.2
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 +114 -78
- package/dist/application/background/dlq.js +2 -0
- package/dist/application/background/recovery/active.d.ts +1 -0
- package/dist/application/background/recovery/active.js +12 -0
- package/dist/application/background/recovery/index.js +3 -1
- package/dist/application/background/recovery/pending.js +1 -0
- package/dist/application/cleanupTasks.js +4 -16
- package/dist/application/contextFactory.js +8 -0
- package/dist/application/dlqManager.d.ts +10 -0
- package/dist/application/dlqManager.js +48 -9
- package/dist/application/dlqRetry.js +60 -38
- package/dist/application/eventsManager.d.ts +9 -2
- package/dist/application/eventsManager.js +106 -55
- package/dist/application/operations/ackHelpers.d.ts +1 -4
- package/dist/application/operations/ackHelpers.js +39 -14
- package/dist/application/operations/flowPush.js +1 -0
- package/dist/application/operations/flowValidation.js +4 -4
- package/dist/application/operations/jobManagement.d.ts +4 -0
- package/dist/application/operations/jobMoveOperations.js +9 -4
- package/dist/application/operations/pull.d.ts +7 -2
- package/dist/application/operations/pull.js +15 -57
- package/dist/application/operations/pullFinalization.d.ts +6 -0
- package/dist/application/operations/pullFinalization.js +69 -0
- package/dist/application/operations/pullStateTransition.d.ts +8 -7
- package/dist/application/operations/pullStateTransition.js +25 -18
- package/dist/application/operations/push.js +3 -0
- package/dist/application/operations/pushBatch.js +14 -7
- package/dist/application/operations/pushContext.d.ts +2 -1
- 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 +30 -0
- package/dist/application/postgres-queue-manager/control.js +210 -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 +20 -0
- package/dist/application/postgres-queue-manager/delivery.js +83 -0
- package/dist/application/postgres-queue-manager/groupQueries.d.ts +10 -0
- package/dist/application/postgres-queue-manager/groupQueries.js +40 -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/control.js +1 -0
- package/dist/application/queue-manager/delivery.d.ts +5 -4
- package/dist/application/queue-manager/delivery.js +15 -8
- package/dist/application/queue-manager/flow-failures.js +4 -0
- package/dist/application/queue-manager/limits.d.ts +13 -0
- package/dist/application/queue-manager/limits.js +65 -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 +17 -2
- package/dist/application/queueTelemetryJournal.d.ts +2 -0
- package/dist/application/queueTelemetryJournal.js +23 -0
- package/dist/application/types/ack.d.ts +3 -1
- package/dist/cli/commandRouter.d.ts +1 -1
- package/dist/cli/commands/server.js +1 -1
- package/dist/client/flowPush.js +6 -0
- package/dist/client/forwarder.d.ts +6 -4
- package/dist/client/forwarder.js +5 -3
- package/dist/client/groupId.d.ts +1 -0
- package/dist/client/groupId.js +1 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/jobHelpers.js +1 -0
- 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/bulk.js +4 -4
- package/dist/client/queue/operations/add/payload.d.ts +1 -0
- package/dist/client/queue/operations/add/payload.js +7 -1
- package/dist/client/queue/operations/add/single.js +3 -3
- package/dist/client/queue/operations/groups.d.ts +21 -0
- package/dist/client/queue/operations/groups.js +88 -0
- package/dist/client/queue/runtime/configuration.d.ts +4 -0
- package/dist/client/queue/runtime/configuration.js +8 -0
- package/dist/client/queue/runtime/queries.d.ts +13 -0
- package/dist/client/queue/runtime/queries.js +31 -0
- package/dist/client/queue/runtime/state.d.ts +4 -0
- package/dist/client/queue/runtime/state.js +1 -0
- package/dist/client/types/index.d.ts +2 -2
- package/dist/client/types/options.d.ts +4 -0
- package/dist/client/types/worker.d.ts +8 -0
- package/dist/client/worker/runtime/execution.js +4 -8
- package/dist/client/worker/runtime/options.js +3 -0
- package/dist/client/worker/runtime/polling.d.ts +1 -0
- package/dist/client/worker/runtime/polling.js +9 -0
- package/dist/client/worker/types/options.d.ts +2 -1
- package/dist/client/worker/workerPull.d.ts +2 -0
- package/dist/client/worker/workerPull.js +6 -4
- 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/job/groupFifoOrder.d.ts +9 -0
- package/dist/domain/job/groupFifoOrder.js +26 -0
- package/dist/domain/queue/dlqShard.d.ts +2 -0
- package/dist/domain/queue/dlqShard.js +16 -0
- package/dist/domain/queue/groupLimiterManager.d.ts +23 -0
- package/dist/domain/queue/groupLimiterManager.js +107 -0
- package/dist/domain/queue/groupScheduler.d.ts +31 -0
- package/dist/domain/queue/groupScheduler.js +256 -0
- package/dist/domain/queue/groupSchedulerOrder.d.ts +6 -0
- package/dist/domain/queue/groupSchedulerOrder.js +30 -0
- package/dist/domain/queue/priorityQueue.d.ts +13 -23
- package/dist/domain/queue/priorityQueue.js +53 -42
- package/dist/domain/queue/shard/dlq.d.ts +1 -0
- package/dist/domain/queue/shard/dlq.js +3 -0
- package/dist/domain/queue/shard/keys.d.ts +1 -0
- package/dist/domain/queue/shard/keys.js +25 -2
- package/dist/domain/queue/shard/lifecycle.js +4 -0
- package/dist/domain/queue/shard/limits.d.ts +20 -1
- package/dist/domain/queue/shard/limits.js +56 -0
- package/dist/domain/queue/shard/state.d.ts +5 -0
- package/dist/domain/queue/shard/state.js +9 -1
- package/dist/domain/types/commands/core.d.ts +3 -0
- package/dist/domain/types/commands/dlq.d.ts +5 -0
- package/dist/domain/types/commands/limits.d.ts +41 -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/group.d.ts +21 -0
- package/dist/domain/types/group.js +78 -0
- package/dist/domain/types/jobs/model.d.ts +1 -1
- package/dist/domain/types/priorityQueue.d.ts +1 -0
- 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/jobOptionsBlob.d.ts +1 -0
- package/dist/infrastructure/persistence/jobOptionsBlob.js +10 -2
- package/dist/infrastructure/persistence/migrations.js +11 -0
- package/dist/infrastructure/persistence/postgres/admission.d.ts +18 -0
- package/dist/infrastructure/persistence/postgres/admission.js +196 -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 +128 -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 +14 -0
- package/dist/infrastructure/persistence/postgres/claimBatch.js +69 -0
- package/dist/infrastructure/persistence/postgres/claimSelection.d.ts +11 -0
- package/dist/infrastructure/persistence/postgres/claimSelection.js +140 -0
- package/dist/infrastructure/persistence/postgres/claims.d.ts +10 -0
- package/dist/infrastructure/persistence/postgres/claims.js +153 -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/eventCatchupCursors.d.ts +32 -0
- package/dist/infrastructure/persistence/postgres/eventCatchupCursors.js +66 -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 +267 -0
- package/dist/infrastructure/persistence/postgres/eventPruneWatermarks.d.ts +26 -0
- package/dist/infrastructure/persistence/postgres/eventPruneWatermarks.js +129 -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 +276 -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/groupClaims.d.ts +15 -0
- package/dist/infrastructure/persistence/postgres/groupClaims.js +95 -0
- package/dist/infrastructure/persistence/postgres/groupSchema.d.ts +2 -0
- package/dist/infrastructure/persistence/postgres/groupSchema.js +155 -0
- package/dist/infrastructure/persistence/postgres/groupSchemaFingerprint.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/groupSchemaFingerprint.js +127 -0
- package/dist/infrastructure/persistence/postgres/groupStateRetention.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/groupStateRetention.js +93 -0
- package/dist/infrastructure/persistence/postgres/groups.d.ts +11 -0
- package/dist/infrastructure/persistence/postgres/groups.js +117 -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 +158 -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 +45 -0
- package/dist/infrastructure/persistence/postgres/runtime.js +238 -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 +261 -0
- package/dist/infrastructure/persistence/postgres/schemaInitialization.d.ts +3 -0
- package/dist/infrastructure/persistence/postgres/schemaInitialization.js +226 -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 +100 -0
- package/dist/infrastructure/persistence/postgres/store.js +180 -0
- package/dist/infrastructure/persistence/postgres/telemetry.d.ts +8 -0
- package/dist/infrastructure/persistence/postgres/telemetry.js +100 -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 +176 -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/schema.d.ts +2 -2
- package/dist/infrastructure/persistence/schema.js +12 -1
- package/dist/infrastructure/persistence/sqlite/control.d.ts +14 -0
- package/dist/infrastructure/persistence/sqlite/control.js +69 -0
- package/dist/infrastructure/persistence/sqlite/jobs.d.ts +12 -0
- package/dist/infrastructure/persistence/sqlite/jobs.js +38 -0
- package/dist/infrastructure/persistence/sqlite/telemetry.d.ts +2 -1
- package/dist/infrastructure/persistence/sqlite/telemetry.js +5 -64
- package/dist/infrastructure/persistence/sqlite/telemetryWrites.d.ts +4 -0
- package/dist/infrastructure/persistence/sqlite/telemetryWrites.js +123 -0
- package/dist/infrastructure/persistence/sqliteSerializer.js +2 -0
- 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 +16 -1
- package/dist/infrastructure/server/handler-routes/monitoring.d.ts +2 -2
- package/dist/infrastructure/server/handler.js +9 -11
- 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.d.ts +0 -5
- package/dist/infrastructure/server/handlers/core.js +47 -49
- 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/groups.d.ts +11 -0
- package/dist/infrastructure/server/handlers/groups.js +55 -0
- 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 +56 -8
- 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 +18 -7
- package/dist/infrastructure/server/httpRouteQueues.js +11 -1
- package/dist/infrastructure/server/protocol/validation.d.ts +1 -0
- package/dist/infrastructure/server/protocol/validation.js +1 -0
- package/dist/infrastructure/server/protocol.d.ts +1 -1
- package/dist/infrastructure/server/protocol.js +1 -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 +75 -65
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SQL, TransactionSQL } from 'bun';
|
|
2
|
+
import type { Worker } from '../../../domain/types/worker';
|
|
3
|
+
import type { PostgresContext } from './context';
|
|
4
|
+
export declare function savePostgresWorker(ctx: PostgresContext, worker: Worker): Promise<void>;
|
|
5
|
+
export declare function removePostgresWorker(ctx: PostgresContext, id: string, clientId?: string): Promise<boolean>;
|
|
6
|
+
export declare function heartbeatPostgresWorker(ctx: PostgresContext, id: string, stats?: {
|
|
7
|
+
activeJobs?: number;
|
|
8
|
+
processed?: number;
|
|
9
|
+
failed?: number;
|
|
10
|
+
}, clientId?: string): Promise<boolean>;
|
|
11
|
+
export declare function removePostgresClientWorkers(ctx: PostgresContext, clientId: string): Promise<number>;
|
|
12
|
+
export declare function removePostgresBrokerWorkers(ctx: PostgresContext): Promise<number>;
|
|
13
|
+
export declare function listPostgresWorkers(ctx: PostgresContext): Promise<Worker[]>;
|
|
14
|
+
export declare function hasActivePostgresWorker(sql: SQL | TransactionSQL, ctx: PostgresContext, queue: string, now: number, timeoutMs?: number): Promise<boolean>;
|
|
15
|
+
export declare function purgeStalePostgresWorkers(ctx: PostgresContext, requestedNow?: number, staleMs?: number): Promise<number>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { decodePostgresValue, encodePostgresValue } from './codec';
|
|
2
|
+
import { databaseNow } from './context';
|
|
3
|
+
import { lockPostgresBrokerSession } from './brokerSessions';
|
|
4
|
+
export async function savePostgresWorker(ctx, worker) {
|
|
5
|
+
await ctx.sql.begin(async (tx) => {
|
|
6
|
+
await lockPostgresBrokerSession(tx, ctx);
|
|
7
|
+
worker.lastSeen = await databaseNow(tx);
|
|
8
|
+
await tx `
|
|
9
|
+
INSERT INTO bunqueue_workers
|
|
10
|
+
(namespace, id, broker_id, broker_session_id, client_id, queues, payload, last_seen)
|
|
11
|
+
VALUES
|
|
12
|
+
(${ctx.config.namespace}, ${worker.id}, ${ctx.config.brokerId},
|
|
13
|
+
${ctx.config.brokerSessionId}, ${worker.clientId},
|
|
14
|
+
${tx.array(worker.queues, 'TEXT')}, ${encodePostgresValue(worker)}, ${worker.lastSeen})
|
|
15
|
+
ON CONFLICT (namespace, id) DO UPDATE SET
|
|
16
|
+
broker_id = excluded.broker_id,
|
|
17
|
+
broker_session_id = excluded.broker_session_id,
|
|
18
|
+
client_id = excluded.client_id,
|
|
19
|
+
queues = excluded.queues,
|
|
20
|
+
payload = excluded.payload,
|
|
21
|
+
last_seen = excluded.last_seen
|
|
22
|
+
`;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export async function removePostgresWorker(ctx, id, clientId) {
|
|
26
|
+
const expectedClientId = clientId ?? null;
|
|
27
|
+
const rows = await ctx.sql `
|
|
28
|
+
DELETE FROM bunqueue_workers
|
|
29
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${id}
|
|
30
|
+
AND broker_id = ${ctx.config.brokerId}
|
|
31
|
+
AND broker_session_id = ${ctx.config.brokerSessionId}
|
|
32
|
+
AND (${expectedClientId}::text IS NULL OR client_id IS NOT DISTINCT FROM ${expectedClientId})
|
|
33
|
+
RETURNING id
|
|
34
|
+
`;
|
|
35
|
+
return rows.length > 0;
|
|
36
|
+
}
|
|
37
|
+
export async function heartbeatPostgresWorker(ctx, id, stats, clientId) {
|
|
38
|
+
return await ctx.sql.begin(async (tx) => {
|
|
39
|
+
await lockPostgresBrokerSession(tx, ctx);
|
|
40
|
+
const expectedClientId = clientId ?? null;
|
|
41
|
+
const rows = await tx `
|
|
42
|
+
SELECT id, payload FROM bunqueue_workers
|
|
43
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${id}
|
|
44
|
+
AND broker_id = ${ctx.config.brokerId}
|
|
45
|
+
AND broker_session_id = ${ctx.config.brokerSessionId}
|
|
46
|
+
AND (${expectedClientId}::text IS NULL OR client_id IS NOT DISTINCT FROM ${expectedClientId})
|
|
47
|
+
FOR UPDATE
|
|
48
|
+
`;
|
|
49
|
+
const row = rows[0];
|
|
50
|
+
if (!row)
|
|
51
|
+
return false;
|
|
52
|
+
const worker = decodePostgresValue(row.payload, null, `postgresWorker:${row.id}`);
|
|
53
|
+
if (!worker)
|
|
54
|
+
throw new Error(`Corrupt PostgreSQL worker payload for ${row.id}`);
|
|
55
|
+
worker.lastSeen = await databaseNow(tx);
|
|
56
|
+
if (stats?.activeJobs !== undefined)
|
|
57
|
+
worker.activeJobs = stats.activeJobs;
|
|
58
|
+
if (stats?.processed !== undefined)
|
|
59
|
+
worker.processedJobs = stats.processed;
|
|
60
|
+
if (stats?.failed !== undefined)
|
|
61
|
+
worker.failedJobs = stats.failed;
|
|
62
|
+
if (clientId !== undefined)
|
|
63
|
+
worker.clientId = clientId;
|
|
64
|
+
await tx `
|
|
65
|
+
UPDATE bunqueue_workers
|
|
66
|
+
SET broker_id = ${ctx.config.brokerId},
|
|
67
|
+
broker_session_id = ${ctx.config.brokerSessionId}, client_id = ${worker.clientId},
|
|
68
|
+
payload = ${encodePostgresValue(worker)}, last_seen = ${worker.lastSeen}
|
|
69
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${id}
|
|
70
|
+
AND broker_id = ${ctx.config.brokerId}
|
|
71
|
+
AND broker_session_id = ${ctx.config.brokerSessionId}
|
|
72
|
+
AND (${expectedClientId}::text IS NULL OR client_id IS NOT DISTINCT FROM ${expectedClientId})
|
|
73
|
+
`;
|
|
74
|
+
return true;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export async function removePostgresClientWorkers(ctx, clientId) {
|
|
78
|
+
const rows = await ctx.sql `
|
|
79
|
+
DELETE FROM bunqueue_workers
|
|
80
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
81
|
+
AND broker_id = ${ctx.config.brokerId} AND client_id = ${clientId}
|
|
82
|
+
AND broker_session_id = ${ctx.config.brokerSessionId}
|
|
83
|
+
RETURNING id
|
|
84
|
+
`;
|
|
85
|
+
return rows.length;
|
|
86
|
+
}
|
|
87
|
+
export async function removePostgresBrokerWorkers(ctx) {
|
|
88
|
+
const rows = await ctx.sql `
|
|
89
|
+
DELETE FROM bunqueue_workers
|
|
90
|
+
WHERE namespace = ${ctx.config.namespace} AND broker_id = ${ctx.config.brokerId}
|
|
91
|
+
AND broker_session_id = ${ctx.config.brokerSessionId}
|
|
92
|
+
RETURNING id
|
|
93
|
+
`;
|
|
94
|
+
return rows.length;
|
|
95
|
+
}
|
|
96
|
+
export async function listPostgresWorkers(ctx) {
|
|
97
|
+
const rows = await ctx.sql `
|
|
98
|
+
SELECT id, payload FROM bunqueue_workers
|
|
99
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
100
|
+
ORDER BY id
|
|
101
|
+
`;
|
|
102
|
+
return rows.flatMap((row) => {
|
|
103
|
+
const worker = decodePostgresValue(row.payload, null, `postgresWorker:${row.id}`);
|
|
104
|
+
return worker ? [worker] : [];
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
export async function hasActivePostgresWorker(sql, ctx, queue, now, timeoutMs = 30_000) {
|
|
108
|
+
const rows = await sql `
|
|
109
|
+
SELECT EXISTS (
|
|
110
|
+
SELECT 1 FROM bunqueue_workers
|
|
111
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
112
|
+
AND ${queue} = ANY(queues)
|
|
113
|
+
AND last_seen > ${now - timeoutMs}
|
|
114
|
+
) AS found
|
|
115
|
+
`;
|
|
116
|
+
return rows[0]?.found ?? false;
|
|
117
|
+
}
|
|
118
|
+
export async function purgeStalePostgresWorkers(ctx, requestedNow, staleMs = 90_000) {
|
|
119
|
+
const now = requestedNow ?? (await databaseNow(ctx.sql));
|
|
120
|
+
const rows = await ctx.sql `
|
|
121
|
+
DELETE FROM bunqueue_workers
|
|
122
|
+
WHERE namespace = ${ctx.config.namespace} AND last_seen <= ${now - staleMs}
|
|
123
|
+
RETURNING id
|
|
124
|
+
`;
|
|
125
|
+
return rows.length;
|
|
126
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { PostgresQueueStore } from './postgres/store';
|
|
2
|
+
export type { ClaimedPostgresJob, PostgresCompletionResult, PostgresCounts, PostgresDeliveredStoreEvent, PostgresFailureInput, PostgresQueueState, PostgresStorageConfig, PostgresStorageHealth, PostgresStoreEvent, PostgresStoredJob, PostgresJobState, } from './postgres/types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PostgresQueueStore } from './postgres/store';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** SQLite PRAGMA settings for optimal performance */
|
|
2
2
|
export declare const PRAGMA_SETTINGS = "\nPRAGMA journal_mode = WAL;\nPRAGMA synchronous = NORMAL;\nPRAGMA cache_size = -64000;\nPRAGMA temp_store = MEMORY;\nPRAGMA mmap_size = 268435456;\nPRAGMA page_size = 4096;\nPRAGMA busy_timeout = 5000;\n";
|
|
3
3
|
/** Main schema creation. */
|
|
4
|
-
export declare const SCHEMA = "\n-- Jobs table (using UUIDv7 for job IDs)\n-- Uses BLOB for data fields (MessagePack serialization for ~2-3x faster than JSON)\nCREATE TABLE IF NOT EXISTS jobs (\n id TEXT PRIMARY KEY,\n queue TEXT NOT NULL,\n name TEXT,\n data BLOB NOT NULL,\n priority INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n run_at INTEGER NOT NULL,\n started_at INTEGER,\n completed_at INTEGER,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 3,\n backoff INTEGER NOT NULL DEFAULT 1000,\n ttl INTEGER,\n timeout INTEGER,\n unique_key TEXT,\n custom_id TEXT,\n depends_on BLOB,\n parent_id TEXT,\n children_ids BLOB,\n tags BLOB,\n state TEXT NOT NULL DEFAULT 'waiting',\n lifo INTEGER NOT NULL DEFAULT 0,\n group_id TEXT,\n progress INTEGER DEFAULT 0,\n progress_msg TEXT,\n remove_on_complete INTEGER DEFAULT 0,\n remove_on_fail INTEGER DEFAULT 0,\n fail_parent_on_failure INTEGER NOT NULL DEFAULT 0,\n remove_dependency_on_failure INTEGER NOT NULL DEFAULT 0,\n continue_parent_on_failure INTEGER NOT NULL DEFAULT 0,\n ignore_dependency_on_failure INTEGER NOT NULL DEFAULT 0,\n stall_timeout INTEGER,\n last_heartbeat INTEGER,\n stall_count INTEGER NOT NULL DEFAULT 0,\n timeline BLOB,\n stacktrace BLOB,\n dlq_retry_state BLOB,\n extended_options BLOB\n);\n\n-- Indexes for common queries\nCREATE INDEX IF NOT EXISTS idx_jobs_queue_state\n ON jobs(queue, state);\n-- Stable createdAt/id pagination for unfiltered and logical-state queue views\nCREATE INDEX IF NOT EXISTS idx_jobs_queue_created\n ON jobs(queue, created_at, id);\nCREATE INDEX IF NOT EXISTS idx_jobs_queue_state_created\n ON jobs(queue, state, created_at, id);\nCREATE INDEX IF NOT EXISTS idx_jobs_run_at\n ON jobs(run_at) WHERE state IN ('waiting', 'prioritized', 'waiting-children', 'delayed');\nCREATE INDEX IF NOT EXISTS idx_jobs_unique\n ON jobs(queue, unique_key) WHERE unique_key IS NOT NULL;\nCREATE INDEX IF NOT EXISTS idx_jobs_custom_id\n ON jobs(custom_id) WHERE custom_id IS NOT NULL;\nCREATE INDEX IF NOT EXISTS idx_jobs_parent\n ON jobs(parent_id) WHERE parent_id IS NOT NULL;\n\n-- Durable outbox/state for terminal child failure propagation.\nCREATE TABLE IF NOT EXISTS flow_failures (\n parent_id TEXT NOT NULL,\n child_id TEXT NOT NULL,\n child_queue TEXT NOT NULL,\n mode TEXT NOT NULL,\n error TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n PRIMARY KEY (parent_id, child_id)\n);\nCREATE INDEX IF NOT EXISTS idx_flow_failures_parent ON flow_failures(parent_id);\n\n\nCREATE TABLE IF NOT EXISTS dependency_completions (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n job_id TEXT NOT NULL UNIQUE,\n queue TEXT NOT NULL,\n completed_at INTEGER NOT NULL,\n pinned INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_dependency_completions_queue\n ON dependency_completions(queue);\n\n\n-- Job results storage (BLOB for MessagePack)\nCREATE TABLE IF NOT EXISTS job_results (\n job_id TEXT PRIMARY KEY,\n result BLOB,\n completed_at INTEGER NOT NULL\n);\n\n-- Dead letter queue (BLOB for MessagePack - stores full DlqEntry)\nCREATE TABLE IF NOT EXISTS dlq (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n job_id TEXT NOT NULL,\n queue TEXT NOT NULL,\n entry BLOB NOT NULL,\n entered_at INTEGER NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_dlq_queue ON dlq(queue);\nCREATE INDEX IF NOT EXISTS idx_dlq_job_id ON dlq(job_id);\nCREATE INDEX IF NOT EXISTS idx_dlq_entered_at ON dlq(entered_at);\n\n-- Performance indexes for high-throughput operations\n-- Stall detection: runs every 5s, needs fast lookup of active jobs by started_at\nCREATE INDEX IF NOT EXISTS idx_jobs_state_started\n ON jobs(state, started_at) WHERE state = 'active';\n\n-- Group operations: fast lookup by group_id\nCREATE INDEX IF NOT EXISTS idx_jobs_group_id\n ON jobs(group_id) WHERE group_id IS NOT NULL;\n\n-- Pending jobs: compound index for priority-ordered retrieval\nCREATE INDEX IF NOT EXISTS idx_jobs_pending_priority\n ON jobs(queue, state, priority DESC, run_at ASC) WHERE state IN ('waiting', 'prioritized', 'waiting-children', 'delayed');\n\n-- Completed jobs: index for recovery ordering (issue #84)\nCREATE INDEX IF NOT EXISTS idx_jobs_completed_order\n ON jobs(completed_at DESC) WHERE state = 'completed';\n\n-- Cron jobs (BLOB for MessagePack)\nCREATE TABLE IF NOT EXISTS cron_jobs (\n name TEXT PRIMARY KEY,\n queue TEXT NOT NULL,\n job_name TEXT,\n data BLOB NOT NULL,\n schedule TEXT,\n repeat_every INTEGER,\n priority INTEGER NOT NULL DEFAULT 0,\n next_run INTEGER NOT NULL,\n executions INTEGER NOT NULL DEFAULT 0,\n max_limit INTEGER,\n timezone TEXT,\n unique_key TEXT,\n dedup BLOB,\n skip_missed_on_restart INTEGER NOT NULL DEFAULT 0,\n skip_if_no_worker INTEGER NOT NULL DEFAULT 0,\n prevent_overlap INTEGER NOT NULL DEFAULT 1,\n job_options BLOB\n);\n\n-- Queue state persistence (optional)\nCREATE TABLE IF NOT EXISTS queue_state (\n name TEXT PRIMARY KEY,\n paused INTEGER NOT NULL DEFAULT 0,\n rate_limit INTEGER,\n concurrency_limit INTEGER,\n rate_limit_duration INTEGER,\n rate_limit_expires_at INTEGER,\n stall_enabled INTEGER,\n stall_interval INTEGER,\n max_stalls INTEGER,\n stall_grace_period INTEGER,\n dlq_config BLOB\n);\n\n-- Per-queue lifecycle event journal. Entries are kept newest-first by id and\n-- bounded by QueueManagerConfig.maxQueueEvents when they are appended.\nCREATE TABLE IF NOT EXISTS queue_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n queue TEXT NOT NULL,\n event_type TEXT NOT NULL,\n job_id TEXT NOT NULL,\n occurred_at INTEGER NOT NULL,\n payload BLOB\n);\nCREATE INDEX IF NOT EXISTS idx_queue_events_queue_id\n ON queue_events(queue, id DESC);\n\n-- Cumulative terminal counters are separate from bounded minute buckets so\n-- pruning old data points never resets meta.count.\nCREATE TABLE IF NOT EXISTS queue_metrics_meta (\n queue TEXT NOT NULL,\n type TEXT NOT NULL CHECK (type IN ('completed', 'failed')),\n total_count INTEGER NOT NULL DEFAULT 0,\n prev_ts INTEGER NOT NULL DEFAULT 0,\n prev_count INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (queue, type)\n);\nCREATE TABLE IF NOT EXISTS queue_metric_buckets (\n queue TEXT NOT NULL,\n type TEXT NOT NULL CHECK (type IN ('completed', 'failed')),\n minute INTEGER NOT NULL,\n count INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (queue, type, minute)\n);\nCREATE INDEX IF NOT EXISTS idx_queue_metric_buckets_window\n ON queue_metric_buckets(queue, type, minute DESC);\n";
|
|
4
|
+
export declare const SCHEMA = "\n-- Jobs table (using UUIDv7 for job IDs)\n-- Uses BLOB for data fields (MessagePack serialization for ~2-3x faster than JSON)\nCREATE TABLE IF NOT EXISTS jobs (\n id TEXT PRIMARY KEY,\n queue TEXT NOT NULL,\n name TEXT,\n data BLOB NOT NULL,\n priority INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n run_at INTEGER NOT NULL,\n started_at INTEGER,\n completed_at INTEGER,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 3,\n backoff INTEGER NOT NULL DEFAULT 1000,\n ttl INTEGER,\n timeout INTEGER,\n unique_key TEXT,\n custom_id TEXT,\n depends_on BLOB,\n parent_id TEXT,\n children_ids BLOB,\n tags BLOB,\n state TEXT NOT NULL DEFAULT 'waiting',\n lifo INTEGER NOT NULL DEFAULT 0,\n group_id TEXT,\n progress INTEGER DEFAULT 0,\n progress_msg TEXT,\n remove_on_complete INTEGER DEFAULT 0,\n remove_on_fail INTEGER DEFAULT 0,\n fail_parent_on_failure INTEGER NOT NULL DEFAULT 0,\n remove_dependency_on_failure INTEGER NOT NULL DEFAULT 0,\n continue_parent_on_failure INTEGER NOT NULL DEFAULT 0,\n ignore_dependency_on_failure INTEGER NOT NULL DEFAULT 0,\n stall_timeout INTEGER,\n last_heartbeat INTEGER,\n stall_count INTEGER NOT NULL DEFAULT 0,\n timeline BLOB,\n stacktrace BLOB,\n dlq_retry_state BLOB,\n extended_options BLOB\n);\n\n-- Indexes for common queries\nCREATE INDEX IF NOT EXISTS idx_jobs_queue_state\n ON jobs(queue, state);\n-- Stable createdAt/id pagination for unfiltered and logical-state queue views\nCREATE INDEX IF NOT EXISTS idx_jobs_queue_created\n ON jobs(queue, created_at, id);\nCREATE INDEX IF NOT EXISTS idx_jobs_queue_state_created\n ON jobs(queue, state, created_at, id);\nCREATE INDEX IF NOT EXISTS idx_jobs_run_at\n ON jobs(run_at) WHERE state IN ('waiting', 'prioritized', 'waiting-children', 'delayed');\nCREATE INDEX IF NOT EXISTS idx_jobs_unique\n ON jobs(queue, unique_key) WHERE unique_key IS NOT NULL;\nCREATE INDEX IF NOT EXISTS idx_jobs_custom_id\n ON jobs(custom_id) WHERE custom_id IS NOT NULL;\nCREATE INDEX IF NOT EXISTS idx_jobs_parent\n ON jobs(parent_id) WHERE parent_id IS NOT NULL;\n\n-- Durable outbox/state for terminal child failure propagation.\nCREATE TABLE IF NOT EXISTS flow_failures (\n parent_id TEXT NOT NULL,\n child_id TEXT NOT NULL,\n child_queue TEXT NOT NULL,\n mode TEXT NOT NULL,\n error TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n PRIMARY KEY (parent_id, child_id)\n);\nCREATE INDEX IF NOT EXISTS idx_flow_failures_parent ON flow_failures(parent_id);\n\n\nCREATE TABLE IF NOT EXISTS dependency_completions (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n job_id TEXT NOT NULL UNIQUE,\n queue TEXT NOT NULL,\n completed_at INTEGER NOT NULL,\n pinned INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_dependency_completions_queue\n ON dependency_completions(queue);\n\n\n-- Job results storage (BLOB for MessagePack)\nCREATE TABLE IF NOT EXISTS job_results (\n job_id TEXT PRIMARY KEY,\n result BLOB,\n completed_at INTEGER NOT NULL\n);\n\n-- Dead letter queue (BLOB for MessagePack - stores full DlqEntry)\nCREATE TABLE IF NOT EXISTS dlq (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n job_id TEXT NOT NULL,\n queue TEXT NOT NULL,\n entry BLOB NOT NULL,\n entered_at INTEGER NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_dlq_queue ON dlq(queue);\nCREATE INDEX IF NOT EXISTS idx_dlq_job_id ON dlq(job_id);\nCREATE INDEX IF NOT EXISTS idx_dlq_entered_at ON dlq(entered_at);\n\n-- Performance indexes for high-throughput operations\n-- Stall detection: runs every 5s, needs fast lookup of active jobs by started_at\nCREATE INDEX IF NOT EXISTS idx_jobs_state_started\n ON jobs(state, started_at) WHERE state = 'active';\n\n-- Group operations: fast lookup by group_id\nCREATE INDEX IF NOT EXISTS idx_jobs_group_id\n ON jobs(group_id) WHERE group_id IS NOT NULL;\n\n-- Pending jobs: compound index for priority-ordered retrieval\nCREATE INDEX IF NOT EXISTS idx_jobs_pending_priority\n ON jobs(queue, state, priority DESC, run_at ASC) WHERE state IN ('waiting', 'prioritized', 'waiting-children', 'delayed');\n\n-- Completed jobs: index for recovery ordering (issue #84)\nCREATE INDEX IF NOT EXISTS idx_jobs_completed_order\n ON jobs(completed_at DESC) WHERE state = 'completed';\n\n-- Cron jobs (BLOB for MessagePack)\nCREATE TABLE IF NOT EXISTS cron_jobs (\n name TEXT PRIMARY KEY,\n queue TEXT NOT NULL,\n job_name TEXT,\n data BLOB NOT NULL,\n schedule TEXT,\n repeat_every INTEGER,\n priority INTEGER NOT NULL DEFAULT 0,\n next_run INTEGER NOT NULL,\n executions INTEGER NOT NULL DEFAULT 0,\n max_limit INTEGER,\n timezone TEXT,\n unique_key TEXT,\n dedup BLOB,\n skip_missed_on_restart INTEGER NOT NULL DEFAULT 0,\n skip_if_no_worker INTEGER NOT NULL DEFAULT 0,\n prevent_overlap INTEGER NOT NULL DEFAULT 1,\n job_options BLOB\n);\n\n-- Queue state persistence (optional)\nCREATE TABLE IF NOT EXISTS queue_state (\n name TEXT PRIMARY KEY,\n paused INTEGER NOT NULL DEFAULT 0,\n rate_limit INTEGER,\n concurrency_limit INTEGER,\n rate_limit_duration INTEGER,\n rate_limit_expires_at INTEGER,\n stall_enabled INTEGER,\n stall_interval INTEGER,\n max_stalls INTEGER,\n stall_grace_period INTEGER,\n dlq_config BLOB\n);\n\n-- Durable per-group overrides. Runtime rate windows remain in-memory in the\n-- embedded single-broker engine, matching the existing queue-level limiter.\nCREATE TABLE IF NOT EXISTS group_state (\n queue TEXT NOT NULL,\n group_id TEXT NOT NULL,\n rate_limit INTEGER,\n rate_duration INTEGER,\n concurrency_limit INTEGER,\n PRIMARY KEY (queue, group_id)\n);\n\n-- Per-queue lifecycle event journal. Entries are kept newest-first by id and\n-- bounded by QueueManagerConfig.maxQueueEvents when they are appended.\nCREATE TABLE IF NOT EXISTS queue_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n queue TEXT NOT NULL,\n event_type TEXT NOT NULL,\n job_id TEXT NOT NULL,\n occurred_at INTEGER NOT NULL,\n payload BLOB\n);\nCREATE INDEX IF NOT EXISTS idx_queue_events_queue_id\n ON queue_events(queue, id DESC);\n\n-- Cumulative terminal counters are separate from bounded minute buckets so\n-- pruning old data points never resets meta.count.\nCREATE TABLE IF NOT EXISTS queue_metrics_meta (\n queue TEXT NOT NULL,\n type TEXT NOT NULL CHECK (type IN ('completed', 'failed')),\n total_count INTEGER NOT NULL DEFAULT 0,\n prev_ts INTEGER NOT NULL DEFAULT 0,\n prev_count INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (queue, type)\n);\nCREATE TABLE IF NOT EXISTS queue_metric_buckets (\n queue TEXT NOT NULL,\n type TEXT NOT NULL CHECK (type IN ('completed', 'failed')),\n minute INTEGER NOT NULL,\n count INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (queue, type, minute)\n);\nCREATE INDEX IF NOT EXISTS idx_queue_metric_buckets_window\n ON queue_metric_buckets(queue, type, minute DESC);\n";
|
|
5
5
|
/** Migration version table */
|
|
6
6
|
export declare const MIGRATION_TABLE = "\nCREATE TABLE IF NOT EXISTS migrations (\n version INTEGER PRIMARY KEY,\n applied_at INTEGER NOT NULL\n);\n";
|
|
7
7
|
/** Current schema version */
|
|
8
|
-
export declare const SCHEMA_VERSION =
|
|
8
|
+
export declare const SCHEMA_VERSION = 35;
|
|
@@ -158,6 +158,17 @@ CREATE TABLE IF NOT EXISTS queue_state (
|
|
|
158
158
|
dlq_config BLOB
|
|
159
159
|
);
|
|
160
160
|
|
|
161
|
+
-- Durable per-group overrides. Runtime rate windows remain in-memory in the
|
|
162
|
+
-- embedded single-broker engine, matching the existing queue-level limiter.
|
|
163
|
+
CREATE TABLE IF NOT EXISTS group_state (
|
|
164
|
+
queue TEXT NOT NULL,
|
|
165
|
+
group_id TEXT NOT NULL,
|
|
166
|
+
rate_limit INTEGER,
|
|
167
|
+
rate_duration INTEGER,
|
|
168
|
+
concurrency_limit INTEGER,
|
|
169
|
+
PRIMARY KEY (queue, group_id)
|
|
170
|
+
);
|
|
171
|
+
|
|
161
172
|
-- Per-queue lifecycle event journal. Entries are kept newest-first by id and
|
|
162
173
|
-- bounded by QueueManagerConfig.maxQueueEvents when they are appended.
|
|
163
174
|
CREATE TABLE IF NOT EXISTS queue_events (
|
|
@@ -199,4 +210,4 @@ CREATE TABLE IF NOT EXISTS migrations (
|
|
|
199
210
|
);
|
|
200
211
|
`;
|
|
201
212
|
/** Current schema version */
|
|
202
|
-
export const SCHEMA_VERSION =
|
|
213
|
+
export const SCHEMA_VERSION = 35;
|
|
@@ -12,6 +12,13 @@ export interface PersistedQueueState {
|
|
|
12
12
|
stallConfig: StallConfig | null;
|
|
13
13
|
dlqConfig: DlqConfig | null;
|
|
14
14
|
}
|
|
15
|
+
export interface PersistedGroupState {
|
|
16
|
+
queue: string;
|
|
17
|
+
groupId: string;
|
|
18
|
+
rateLimit: number | null;
|
|
19
|
+
rateDuration: number | null;
|
|
20
|
+
concurrencyLimit: number | null;
|
|
21
|
+
}
|
|
15
22
|
/** Cron schedules and durable queue control-state. */
|
|
16
23
|
export declare abstract class SqliteControl extends SqliteQueries {
|
|
17
24
|
saveCron(cron: CronJob): void;
|
|
@@ -28,5 +35,12 @@ export declare abstract class SqliteControl extends SqliteQueries {
|
|
|
28
35
|
dlqConfig?: DlqConfig | null;
|
|
29
36
|
}): void;
|
|
30
37
|
loadQueueState(): PersistedQueueState[];
|
|
38
|
+
saveGroupRateLimit(queue: string, groupId: string, max: number, duration: number): void;
|
|
39
|
+
removeGroupRateLimit(queue: string, groupId: string): void;
|
|
40
|
+
saveGroupConcurrency(queue: string, groupId: string, concurrency: number): void;
|
|
41
|
+
removeGroupConcurrency(queue: string, groupId: string): void;
|
|
42
|
+
loadGroupState(): PersistedGroupState[];
|
|
43
|
+
deleteGroupState(queue: string): void;
|
|
31
44
|
deleteQueueState(name: string): void;
|
|
45
|
+
private deleteEmptyGroupState;
|
|
32
46
|
}
|
|
@@ -82,9 +82,78 @@ export class SqliteControl extends SqliteQueries {
|
|
|
82
82
|
dlqConfig: unpack(row.dlq_config, null, `loadQueueDlqConfig:${row.name}`),
|
|
83
83
|
}));
|
|
84
84
|
}
|
|
85
|
+
saveGroupRateLimit(queue, groupId, max, duration) {
|
|
86
|
+
this.safeWrite(() => {
|
|
87
|
+
this.db
|
|
88
|
+
.prepare(`INSERT INTO group_state (queue, group_id, rate_limit, rate_duration)
|
|
89
|
+
VALUES (?, ?, ?, ?)
|
|
90
|
+
ON CONFLICT(queue, group_id) DO UPDATE SET
|
|
91
|
+
rate_limit = excluded.rate_limit,
|
|
92
|
+
rate_duration = excluded.rate_duration`)
|
|
93
|
+
.run(queue, groupId, max, duration);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
removeGroupRateLimit(queue, groupId) {
|
|
97
|
+
this.safeWrite(() => {
|
|
98
|
+
const transaction = this.db.transaction(() => {
|
|
99
|
+
this.db
|
|
100
|
+
.prepare(`UPDATE group_state SET rate_limit = NULL, rate_duration = NULL
|
|
101
|
+
WHERE queue = ? AND group_id = ?`)
|
|
102
|
+
.run(queue, groupId);
|
|
103
|
+
this.deleteEmptyGroupState(queue, groupId);
|
|
104
|
+
});
|
|
105
|
+
transaction();
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
saveGroupConcurrency(queue, groupId, concurrency) {
|
|
109
|
+
this.safeWrite(() => {
|
|
110
|
+
this.db
|
|
111
|
+
.prepare(`INSERT INTO group_state (queue, group_id, concurrency_limit)
|
|
112
|
+
VALUES (?, ?, ?)
|
|
113
|
+
ON CONFLICT(queue, group_id) DO UPDATE SET
|
|
114
|
+
concurrency_limit = excluded.concurrency_limit`)
|
|
115
|
+
.run(queue, groupId, concurrency);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
removeGroupConcurrency(queue, groupId) {
|
|
119
|
+
this.safeWrite(() => {
|
|
120
|
+
const transaction = this.db.transaction(() => {
|
|
121
|
+
this.db
|
|
122
|
+
.prepare(`UPDATE group_state SET concurrency_limit = NULL
|
|
123
|
+
WHERE queue = ? AND group_id = ?`)
|
|
124
|
+
.run(queue, groupId);
|
|
125
|
+
this.deleteEmptyGroupState(queue, groupId);
|
|
126
|
+
});
|
|
127
|
+
transaction();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
loadGroupState() {
|
|
131
|
+
return this.db
|
|
132
|
+
.query('SELECT * FROM group_state')
|
|
133
|
+
.all()
|
|
134
|
+
.map((row) => ({
|
|
135
|
+
queue: row.queue,
|
|
136
|
+
groupId: row.group_id,
|
|
137
|
+
rateLimit: row.rate_limit,
|
|
138
|
+
rateDuration: row.rate_duration,
|
|
139
|
+
concurrencyLimit: row.concurrency_limit,
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
deleteGroupState(queue) {
|
|
143
|
+
this.safeWrite(() => {
|
|
144
|
+
this.db.prepare('DELETE FROM group_state WHERE queue = ?').run(queue);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
85
147
|
deleteQueueState(name) {
|
|
86
148
|
this.safeWrite(() => {
|
|
87
149
|
this.statements.get('deleteQueueState').run(name);
|
|
88
150
|
});
|
|
89
151
|
}
|
|
152
|
+
deleteEmptyGroupState(queue, groupId) {
|
|
153
|
+
this.db
|
|
154
|
+
.prepare(`DELETE FROM group_state
|
|
155
|
+
WHERE queue = ? AND group_id = ?
|
|
156
|
+
AND rate_limit IS NULL AND concurrency_limit IS NULL`)
|
|
157
|
+
.run(queue, groupId);
|
|
158
|
+
}
|
|
90
159
|
}
|
|
@@ -3,6 +3,16 @@ import type { FlowFailureRecord } from '../../../domain/types/flow';
|
|
|
3
3
|
import type { Job, JobId, JobTimelineEntry } from '../../../domain/types/job';
|
|
4
4
|
import type { DurableAdmissionMetadata } from '../types/admission';
|
|
5
5
|
import { SqliteAdmission } from './admission';
|
|
6
|
+
export interface ActiveJobWrite {
|
|
7
|
+
jobId: JobId;
|
|
8
|
+
startedAt: number;
|
|
9
|
+
timeline?: JobTimelineEntry[];
|
|
10
|
+
}
|
|
11
|
+
export interface CompletedJobWrite {
|
|
12
|
+
jobId: JobId;
|
|
13
|
+
completedAt: number;
|
|
14
|
+
timeline?: JobTimelineEntry[];
|
|
15
|
+
}
|
|
6
16
|
/** Job insertion, state transitions, and DLQ persistence. */
|
|
7
17
|
export declare abstract class SqliteJobs extends SqliteAdmission {
|
|
8
18
|
insertJob(job: Job, durable?: boolean, admission?: DurableAdmissionMetadata): void;
|
|
@@ -10,8 +20,10 @@ export declare abstract class SqliteJobs extends SqliteAdmission {
|
|
|
10
20
|
protected runInsertJobStmt(job: Job): void;
|
|
11
21
|
protected flushIfBuffered(jobId: JobId): void;
|
|
12
22
|
markActive(jobId: JobId, startedAt: number, timeline?: JobTimelineEntry[]): void;
|
|
23
|
+
markActiveBatch(updates: readonly ActiveJobWrite[]): void;
|
|
13
24
|
markWaitingChildren(jobId: JobId, timeline?: JobTimelineEntry[]): void;
|
|
14
25
|
markCompleted(jobId: JobId, completedAt: number, timeline?: JobTimelineEntry[]): void;
|
|
26
|
+
markCompletedBatch(updates: readonly CompletedJobWrite[]): void;
|
|
15
27
|
markFailed(job: Job, error: string | null): void;
|
|
16
28
|
saveDlqEntry(entry: DlqEntry): void;
|
|
17
29
|
commitFailedJob(jobId: JobId, entry: DlqEntry | null, flowFailure: FlowFailureRecord | null): void;
|
|
@@ -50,6 +50,24 @@ export class SqliteJobs extends SqliteAdmission {
|
|
|
50
50
|
.run('active', startedAt, timeline && timeline.length > 0 ? pack(timeline) : null, jobId);
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
|
+
markActiveBatch(updates) {
|
|
54
|
+
if (updates.length === 0)
|
|
55
|
+
return;
|
|
56
|
+
for (const update of updates)
|
|
57
|
+
this.flushIfBuffered(update.jobId);
|
|
58
|
+
const rows = updates.map((update) => ({
|
|
59
|
+
...update,
|
|
60
|
+
timeline: update.timeline?.length ? pack(update.timeline) : null,
|
|
61
|
+
}));
|
|
62
|
+
this.safeWrite(() => {
|
|
63
|
+
this.db.transaction((batch) => {
|
|
64
|
+
const statement = this.statements.get('updateJobState');
|
|
65
|
+
for (const row of batch) {
|
|
66
|
+
statement.run('active', row.startedAt, row.timeline, row.jobId);
|
|
67
|
+
}
|
|
68
|
+
})(rows);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
53
71
|
markWaitingChildren(jobId, timeline) {
|
|
54
72
|
this.flushIfBuffered(jobId);
|
|
55
73
|
this.safeWrite(() => {
|
|
@@ -66,6 +84,24 @@ export class SqliteJobs extends SqliteAdmission {
|
|
|
66
84
|
.run('completed', completedAt, timeline && timeline.length > 0 ? pack(timeline) : null, jobId);
|
|
67
85
|
});
|
|
68
86
|
}
|
|
87
|
+
markCompletedBatch(updates) {
|
|
88
|
+
if (updates.length === 0)
|
|
89
|
+
return;
|
|
90
|
+
for (const update of updates)
|
|
91
|
+
this.flushIfBuffered(update.jobId);
|
|
92
|
+
const rows = updates.map((update) => ({
|
|
93
|
+
...update,
|
|
94
|
+
timeline: update.timeline?.length ? pack(update.timeline) : null,
|
|
95
|
+
}));
|
|
96
|
+
this.safeWrite(() => {
|
|
97
|
+
this.db.transaction((batch) => {
|
|
98
|
+
const statement = this.statements.get('completeJob');
|
|
99
|
+
for (const row of batch) {
|
|
100
|
+
statement.run('completed', row.completedAt, row.timeline, row.jobId);
|
|
101
|
+
}
|
|
102
|
+
})(rows);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
69
105
|
markFailed(job, error) {
|
|
70
106
|
this.flushIfBuffered(job.id);
|
|
71
107
|
this.safeWrite(() => {
|
|
@@ -132,6 +168,7 @@ export class SqliteJobs extends SqliteAdmission {
|
|
|
132
168
|
const clearDlq = this.statements.get('clearDlqQueue');
|
|
133
169
|
const deleteJob = this.statements.get('deleteJob');
|
|
134
170
|
const deleteResult = this.statements.get('deleteJobResult');
|
|
171
|
+
const deleteFlowFailures = this.db.prepare('DELETE FROM flow_failures WHERE parent_id = ?');
|
|
135
172
|
const transaction = this.db.transaction(() => {
|
|
136
173
|
if (clearQueue)
|
|
137
174
|
clearDlq.run(queue);
|
|
@@ -141,6 +178,7 @@ export class SqliteJobs extends SqliteAdmission {
|
|
|
141
178
|
for (const jobId of terminalJobIds) {
|
|
142
179
|
deleteJob.run(jobId);
|
|
143
180
|
deleteResult.run(jobId);
|
|
181
|
+
deleteFlowFailures.run(jobId);
|
|
144
182
|
}
|
|
145
183
|
});
|
|
146
184
|
transaction();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { QueueMetricsMeta, QueueMetricType } from '../../../domain/types/metrics';
|
|
2
|
-
import {
|
|
2
|
+
import type { JobEvent } from '../../../domain/types/queue';
|
|
3
3
|
import { SqliteControl } from './control';
|
|
4
4
|
export interface PersistedQueueMetrics {
|
|
5
5
|
meta: QueueMetricsMeta;
|
|
@@ -8,6 +8,7 @@ export interface PersistedQueueMetrics {
|
|
|
8
8
|
/** Durable lifecycle journal and minute-bucket metrics. */
|
|
9
9
|
export declare abstract class SqliteTelemetry extends SqliteControl {
|
|
10
10
|
recordQueueEvent(event: JobEvent, maxEvents: number, maxMetricDataPoints: number): void;
|
|
11
|
+
recordQueueEventsBatch(events: readonly JobEvent[], maxEvents: number, maxMetricDataPoints: number): void;
|
|
11
12
|
getQueueMetrics(queue: string, type: QueueMetricType, maxMetricDataPoints: number): PersistedQueueMetrics;
|
|
12
13
|
trimQueueEvents(queue: string, maxLength: number): number;
|
|
13
14
|
countQueueEvents(queue: string): number;
|
|
@@ -1,71 +1,12 @@
|
|
|
1
|
-
import { pack } from '../sqliteSerializer';
|
|
2
1
|
import { SqliteControl } from './control';
|
|
3
|
-
|
|
4
|
-
if (event.eventType === "completed" /* EventType.Completed */)
|
|
5
|
-
return 'completed';
|
|
6
|
-
if (event.eventType === "failed" /* EventType.Failed */ && event.terminal !== false)
|
|
7
|
-
return 'failed';
|
|
8
|
-
return null;
|
|
9
|
-
}
|
|
10
|
-
function eventPayload(event) {
|
|
11
|
-
return pack({
|
|
12
|
-
data: event.data,
|
|
13
|
-
error: event.error,
|
|
14
|
-
progress: event.progress,
|
|
15
|
-
prev: event.prev,
|
|
16
|
-
delay: event.delay,
|
|
17
|
-
terminal: event.terminal,
|
|
18
|
-
});
|
|
19
|
-
}
|
|
2
|
+
import { writeQueueEvents } from './telemetryWrites';
|
|
20
3
|
/** Durable lifecycle journal and minute-bucket metrics. */
|
|
21
4
|
export class SqliteTelemetry extends SqliteControl {
|
|
22
5
|
recordQueueEvent(event, maxEvents, maxMetricDataPoints) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const payload = eventPayload(event);
|
|
28
|
-
this.safeWrite(() => {
|
|
29
|
-
const transaction = this.db.transaction(() => {
|
|
30
|
-
this.db
|
|
31
|
-
.prepare('INSERT INTO queue_events (queue, event_type, job_id, occurred_at, payload) VALUES (?, ?, ?, ?, ?)')
|
|
32
|
-
.run(event.queue, event.eventType, String(event.jobId), event.timestamp, payload);
|
|
33
|
-
this.db
|
|
34
|
-
.prepare('DELETE FROM queue_events WHERE queue = ? AND id IN (SELECT id FROM queue_events WHERE queue = ? ORDER BY id DESC LIMIT -1 OFFSET ?)')
|
|
35
|
-
.run(event.queue, event.queue, eventLimit);
|
|
36
|
-
if (!metricType)
|
|
37
|
-
return;
|
|
38
|
-
this.db
|
|
39
|
-
.prepare(`INSERT INTO queue_metric_buckets (queue, type, minute, count)
|
|
40
|
-
VALUES (?, ?, ?, 1)
|
|
41
|
-
ON CONFLICT(queue, type, minute) DO UPDATE SET count = count + 1`)
|
|
42
|
-
.run(event.queue, metricType, minute);
|
|
43
|
-
const bucketCount = this.db
|
|
44
|
-
.query('SELECT count FROM queue_metric_buckets WHERE queue = ? AND type = ? AND minute = ?')
|
|
45
|
-
.get(event.queue, metricType, minute)?.count ?? 1;
|
|
46
|
-
this.db
|
|
47
|
-
.prepare(`INSERT INTO queue_metrics_meta (queue, type, total_count, prev_ts, prev_count)
|
|
48
|
-
VALUES (?, ?, 1, ?, ?)
|
|
49
|
-
ON CONFLICT(queue, type) DO UPDATE SET
|
|
50
|
-
total_count = total_count + 1,
|
|
51
|
-
prev_ts = excluded.prev_ts,
|
|
52
|
-
prev_count = excluded.prev_count`)
|
|
53
|
-
.run(event.queue, metricType, event.timestamp, bucketCount);
|
|
54
|
-
if (metricLimit === 0) {
|
|
55
|
-
this.db
|
|
56
|
-
.prepare('DELETE FROM queue_metric_buckets WHERE queue = ? AND type = ?')
|
|
57
|
-
.run(event.queue, metricType);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const newest = this.db
|
|
61
|
-
.query('SELECT MAX(minute) AS minute FROM queue_metric_buckets WHERE queue = ? AND type = ?')
|
|
62
|
-
.get(event.queue, metricType)?.minute ?? minute;
|
|
63
|
-
this.db
|
|
64
|
-
.prepare('DELETE FROM queue_metric_buckets WHERE queue = ? AND type = ? AND minute < ?')
|
|
65
|
-
.run(event.queue, metricType, newest - metricLimit + 1);
|
|
66
|
-
});
|
|
67
|
-
transaction();
|
|
68
|
-
});
|
|
6
|
+
this.safeWrite(() => writeQueueEvents(this.db, [event], maxEvents, maxMetricDataPoints));
|
|
7
|
+
}
|
|
8
|
+
recordQueueEventsBatch(events, maxEvents, maxMetricDataPoints) {
|
|
9
|
+
this.safeWrite(() => writeQueueEvents(this.db, events, maxEvents, maxMetricDataPoints));
|
|
69
10
|
}
|
|
70
11
|
getQueueMetrics(queue, type, maxMetricDataPoints) {
|
|
71
12
|
const metaRow = this.db
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
import { type JobEvent } from '../../../domain/types/queue';
|
|
3
|
+
/** Apply events in input order inside one transaction, preserving scalar semantics. */
|
|
4
|
+
export declare function writeQueueEvents(db: Database, events: readonly JobEvent[], maxEvents: number, maxMetricDataPoints: number): void;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { pack } from '../sqliteSerializer';
|
|
2
|
+
function terminalMetricType(event) {
|
|
3
|
+
if (event.eventType === "completed" /* EventType.Completed */)
|
|
4
|
+
return 'completed';
|
|
5
|
+
if (event.eventType === "failed" /* EventType.Failed */ && event.terminal !== false)
|
|
6
|
+
return 'failed';
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
function eventPayload(event) {
|
|
10
|
+
return pack({
|
|
11
|
+
data: event.data,
|
|
12
|
+
error: event.error,
|
|
13
|
+
progress: event.progress,
|
|
14
|
+
prev: event.prev,
|
|
15
|
+
delay: event.delay,
|
|
16
|
+
terminal: event.terminal,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function prepareQueueEvents(events) {
|
|
20
|
+
return events.map((event) => ({
|
|
21
|
+
event,
|
|
22
|
+
metricType: terminalMetricType(event),
|
|
23
|
+
minute: Math.floor(event.timestamp / 60_000),
|
|
24
|
+
payload: eventPayload(event),
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
function groupMetricEvents(events) {
|
|
28
|
+
const byQueue = new Map();
|
|
29
|
+
for (const entry of events) {
|
|
30
|
+
if (!entry.metricType)
|
|
31
|
+
continue;
|
|
32
|
+
let byType = byQueue.get(entry.event.queue);
|
|
33
|
+
if (!byType) {
|
|
34
|
+
byType = new Map();
|
|
35
|
+
byQueue.set(entry.event.queue, byType);
|
|
36
|
+
}
|
|
37
|
+
let group = byType.get(entry.metricType);
|
|
38
|
+
if (!group) {
|
|
39
|
+
group = { queue: entry.event.queue, type: entry.metricType, events: [] };
|
|
40
|
+
byType.set(entry.metricType, group);
|
|
41
|
+
}
|
|
42
|
+
group.events.push({ minute: entry.minute, timestamp: entry.event.timestamp });
|
|
43
|
+
}
|
|
44
|
+
return [...byQueue.values()].flatMap((byType) => [...byType.values()]);
|
|
45
|
+
}
|
|
46
|
+
/** Apply events in input order inside one transaction, preserving scalar semantics. */
|
|
47
|
+
export function writeQueueEvents(db, events, maxEvents, maxMetricDataPoints) {
|
|
48
|
+
if (events.length === 0)
|
|
49
|
+
return;
|
|
50
|
+
const preparedEvents = prepareQueueEvents(events);
|
|
51
|
+
const metricGroups = groupMetricEvents(preparedEvents);
|
|
52
|
+
const eventLimit = Math.max(0, Math.floor(maxEvents));
|
|
53
|
+
const metricLimit = Math.max(0, Math.floor(maxMetricDataPoints));
|
|
54
|
+
const insertEvent = db.prepare('INSERT INTO queue_events (queue, event_type, job_id, occurred_at, payload) VALUES (?, ?, ?, ?, ?)');
|
|
55
|
+
const trimEvents = db.prepare('DELETE FROM queue_events WHERE queue = ? AND id IN (SELECT id FROM queue_events WHERE queue = ? ORDER BY id DESC LIMIT -1 OFFSET ?)');
|
|
56
|
+
const incrementBucket = db.prepare(`INSERT INTO queue_metric_buckets (queue, type, minute, count)
|
|
57
|
+
VALUES (?, ?, ?, ?)
|
|
58
|
+
ON CONFLICT(queue, type, minute) DO UPDATE SET count = count + excluded.count`);
|
|
59
|
+
const readBucket = db.query('SELECT count FROM queue_metric_buckets WHERE queue = ? AND type = ? AND minute = ?');
|
|
60
|
+
const updateMeta = db.prepare(`INSERT INTO queue_metrics_meta (queue, type, total_count, prev_ts, prev_count)
|
|
61
|
+
VALUES (?, ?, ?, ?, ?)
|
|
62
|
+
ON CONFLICT(queue, type) DO UPDATE SET
|
|
63
|
+
total_count = total_count + excluded.total_count,
|
|
64
|
+
prev_ts = excluded.prev_ts,
|
|
65
|
+
prev_count = excluded.prev_count`);
|
|
66
|
+
const clearBuckets = db.prepare('DELETE FROM queue_metric_buckets WHERE queue = ? AND type = ?');
|
|
67
|
+
const readNewest = db.query('SELECT MAX(minute) AS minute FROM queue_metric_buckets WHERE queue = ? AND type = ?');
|
|
68
|
+
const trimBuckets = db.prepare('DELETE FROM queue_metric_buckets WHERE queue = ? AND type = ? AND minute < ?');
|
|
69
|
+
const transaction = db.transaction((batch) => {
|
|
70
|
+
const affectedQueues = new Set();
|
|
71
|
+
for (const { event, payload } of batch) {
|
|
72
|
+
insertEvent.run(event.queue, event.eventType, String(event.jobId), event.timestamp, payload);
|
|
73
|
+
affectedQueues.add(event.queue);
|
|
74
|
+
}
|
|
75
|
+
for (const queue of affectedQueues)
|
|
76
|
+
trimEvents.run(queue, queue, eventLimit);
|
|
77
|
+
for (const group of metricGroups) {
|
|
78
|
+
const eventMinutes = new Set(group.events.map((event) => event.minute));
|
|
79
|
+
const counts = new Map();
|
|
80
|
+
for (const minute of eventMinutes) {
|
|
81
|
+
const count = readBucket.get(group.queue, group.type, minute)?.count;
|
|
82
|
+
if (count !== undefined)
|
|
83
|
+
counts.set(minute, count);
|
|
84
|
+
}
|
|
85
|
+
let newest = readNewest.get(group.queue, group.type)?.minute ?? null;
|
|
86
|
+
const increments = new Map();
|
|
87
|
+
let lastTimestamp = 0;
|
|
88
|
+
let lastCount = 0;
|
|
89
|
+
let cutoff = null;
|
|
90
|
+
for (const event of group.events) {
|
|
91
|
+
lastCount = (counts.get(event.minute) ?? 0) + 1;
|
|
92
|
+
lastTimestamp = event.timestamp;
|
|
93
|
+
counts.set(event.minute, lastCount);
|
|
94
|
+
increments.set(event.minute, (increments.get(event.minute) ?? 0) + 1);
|
|
95
|
+
if (metricLimit === 0) {
|
|
96
|
+
counts.clear();
|
|
97
|
+
increments.clear();
|
|
98
|
+
newest = null;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
newest = newest === null ? event.minute : Math.max(newest, event.minute);
|
|
102
|
+
cutoff = newest - metricLimit + 1;
|
|
103
|
+
for (const minute of counts.keys()) {
|
|
104
|
+
if (minute >= cutoff)
|
|
105
|
+
continue;
|
|
106
|
+
counts.delete(minute);
|
|
107
|
+
increments.delete(minute);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
updateMeta.run(group.queue, group.type, group.events.length, lastTimestamp, lastCount);
|
|
111
|
+
if (metricLimit === 0) {
|
|
112
|
+
clearBuckets.run(group.queue, group.type);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
for (const [minute, count] of increments) {
|
|
116
|
+
incrementBucket.run(group.queue, group.type, minute, count);
|
|
117
|
+
}
|
|
118
|
+
if (cutoff !== null)
|
|
119
|
+
trimBuckets.run(group.queue, group.type, cutoff);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
transaction(preparedEvents);
|
|
123
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { jobId } from '../../domain/types/job';
|
|
7
7
|
import { setDlqRetryState } from '../../domain/types/dlq';
|
|
8
8
|
import { normalizeLegacyJobPayload } from '../../domain/job/payload';
|
|
9
|
+
import { restoreGroupFifoOrder } from '../../domain/job/groupFifoOrder';
|
|
9
10
|
import { decodeJobOptions } from './jobOptionsBlob';
|
|
10
11
|
import { storageLog } from '../../shared/logger';
|
|
11
12
|
import { decodeMessagePack as msgpackDecode, encodeMessagePack as msgpackEncode, } from '../../shared/msgpack';
|
|
@@ -166,6 +167,7 @@ export function rowToJob(row) {
|
|
|
166
167
|
? unpack(row.stacktrace, null, `${jobContext}:stacktrace`)
|
|
167
168
|
: null,
|
|
168
169
|
};
|
|
170
|
+
restoreGroupFifoOrder(job, extended.groupFifoOrder);
|
|
169
171
|
const dlqRetryState = row.dlq_retry_state
|
|
170
172
|
? unpack(row.dlq_retry_state, null, `${jobContext}:dlqRetryState`)
|
|
171
173
|
: null;
|