bunqueue 2.8.60 → 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 +108 -76
- package/dist/application/background/dlq.js +2 -0
- package/dist/application/contextFactory.js +5 -0
- package/dist/application/dlqManager.d.ts +10 -0
- package/dist/application/dlqManager.js +47 -9
- 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/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/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/cli/commands/server.js +1 -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/runtime/configuration.d.ts +4 -0
- package/dist/client/queue/runtime/configuration.js +8 -0
- package/dist/client/worker/runtime/execution.js +3 -8
- package/dist/client/worker/runtime/polling.d.ts +1 -0
- package/dist/client/worker/runtime/polling.js +9 -0
- 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/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/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.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 +18 -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 +74 -65
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { createCronJob } from '../../../domain/types/cron';
|
|
2
|
+
import { createJob, generateJobId } from '../../../domain/types/job';
|
|
3
|
+
import { expandCronShortcut, getNextCronRun, getNextIntervalRun } from '../../scheduler/cronParser';
|
|
4
|
+
import { assertPersistedCronSupported } from '../../scheduler/cron/persisted';
|
|
5
|
+
import { assertValidCronInput } from '../../scheduler/cron/validation';
|
|
6
|
+
import { postgresAdvisoryLockName } from './advisoryLocks';
|
|
7
|
+
import { admitPostgresJob } from './admission';
|
|
8
|
+
import { decodePostgresValue, encodePostgresValue } from './codec';
|
|
9
|
+
import { databaseNow } from './context';
|
|
10
|
+
import { hasActivePostgresWorker } from './workers';
|
|
11
|
+
import { postgresBrokerStaleMs } from './brokerSessions';
|
|
12
|
+
export function decodePostgresCron(row) {
|
|
13
|
+
const cron = decodePostgresValue(row.payload, null, `postgresCron:${row.name}`);
|
|
14
|
+
if (!cron)
|
|
15
|
+
throw new Error(`Corrupt PostgreSQL cron payload for ${row.name}`);
|
|
16
|
+
cron.nextRun = Number(row.next_run);
|
|
17
|
+
cron.executions = row.executions;
|
|
18
|
+
assertPersistedCronSupported(cron);
|
|
19
|
+
return cron;
|
|
20
|
+
}
|
|
21
|
+
function validateInput(input) {
|
|
22
|
+
assertValidCronInput(input);
|
|
23
|
+
}
|
|
24
|
+
function nextRun(input, now) {
|
|
25
|
+
if (input.schedule) {
|
|
26
|
+
return getNextCronRun(expandCronShortcut(input.schedule), now, input.timezone);
|
|
27
|
+
}
|
|
28
|
+
return getNextIntervalRun(input.repeatEvery, now);
|
|
29
|
+
}
|
|
30
|
+
export async function upsertPostgresCron(ctx, input) {
|
|
31
|
+
validateInput(input);
|
|
32
|
+
return await ctx.sql.begin(async (tx) => {
|
|
33
|
+
const existing = await tx `
|
|
34
|
+
SELECT name, payload, next_run, executions, max_limit
|
|
35
|
+
FROM bunqueue_crons
|
|
36
|
+
WHERE namespace = ${ctx.config.namespace} AND name = ${input.name}
|
|
37
|
+
FOR UPDATE
|
|
38
|
+
`;
|
|
39
|
+
const now = await databaseNow(tx);
|
|
40
|
+
const cron = createCronJob(input, input.immediately && !existing[0] ? now : nextRun(input, now));
|
|
41
|
+
cron.executions = existing[0]?.executions ?? 0;
|
|
42
|
+
await tx `
|
|
43
|
+
INSERT INTO bunqueue_crons
|
|
44
|
+
(namespace, name, payload, next_run, executions, max_limit, updated_at)
|
|
45
|
+
VALUES
|
|
46
|
+
(${ctx.config.namespace}, ${cron.name}, ${encodePostgresValue(cron)}, ${cron.nextRun},
|
|
47
|
+
${cron.executions}, ${cron.maxLimit}, ${now})
|
|
48
|
+
ON CONFLICT (namespace, name) DO UPDATE SET
|
|
49
|
+
payload = excluded.payload,
|
|
50
|
+
next_run = excluded.next_run,
|
|
51
|
+
executions = excluded.executions,
|
|
52
|
+
max_limit = excluded.max_limit,
|
|
53
|
+
updated_at = excluded.updated_at
|
|
54
|
+
`;
|
|
55
|
+
return cron;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export async function getPostgresCron(ctx, name) {
|
|
59
|
+
const rows = await ctx.sql `
|
|
60
|
+
SELECT name, payload, next_run, executions, max_limit
|
|
61
|
+
FROM bunqueue_crons
|
|
62
|
+
WHERE namespace = ${ctx.config.namespace} AND name = ${name}
|
|
63
|
+
`;
|
|
64
|
+
return rows[0] ? decodePostgresCron(rows[0]) : undefined;
|
|
65
|
+
}
|
|
66
|
+
export async function listPostgresCrons(ctx, sql = ctx.sql) {
|
|
67
|
+
const rows = await sql `
|
|
68
|
+
SELECT name, payload, next_run, executions, max_limit
|
|
69
|
+
FROM bunqueue_crons
|
|
70
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
71
|
+
ORDER BY name
|
|
72
|
+
`;
|
|
73
|
+
return rows.map(decodePostgresCron);
|
|
74
|
+
}
|
|
75
|
+
export async function removePostgresCron(ctx, name) {
|
|
76
|
+
const rows = await ctx.sql `
|
|
77
|
+
DELETE FROM bunqueue_crons
|
|
78
|
+
WHERE namespace = ${ctx.config.namespace} AND name = ${name}
|
|
79
|
+
RETURNING name
|
|
80
|
+
`;
|
|
81
|
+
return rows.length > 0;
|
|
82
|
+
}
|
|
83
|
+
function spawnedInput(cron) {
|
|
84
|
+
const uniqueKey = cron.uniqueKey ?? (cron.preventOverlap ? `cron:${cron.name}` : undefined);
|
|
85
|
+
const options = cron.jobOptions;
|
|
86
|
+
return {
|
|
87
|
+
name: cron.jobName,
|
|
88
|
+
data: cron.data,
|
|
89
|
+
priority: cron.priority,
|
|
90
|
+
uniqueKey,
|
|
91
|
+
dedup: cron.dedup ?? undefined,
|
|
92
|
+
maxAttempts: options?.maxAttempts,
|
|
93
|
+
backoff: options?.backoff,
|
|
94
|
+
timeout: options?.timeout,
|
|
95
|
+
delay: options?.delay,
|
|
96
|
+
stallTimeout: options?.stallTimeout,
|
|
97
|
+
removeOnComplete: options?.removeOnComplete,
|
|
98
|
+
removeOnFail: options?.removeOnFail,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function followingRun(cron, now) {
|
|
102
|
+
if (cron.schedule) {
|
|
103
|
+
return getNextCronRun(expandCronShortcut(cron.schedule), now, cron.timezone ?? undefined);
|
|
104
|
+
}
|
|
105
|
+
const interval = cron.repeatEvery;
|
|
106
|
+
const scheduled = getNextIntervalRun(interval, cron.nextRun);
|
|
107
|
+
return scheduled <= now ? getNextIntervalRun(interval, now) : scheduled;
|
|
108
|
+
}
|
|
109
|
+
/** Recalculate missed rows only on the oldest live broker for this namespace. */
|
|
110
|
+
export async function reconcilePostgresCronsOnStartup(ctx) {
|
|
111
|
+
const lockName = postgresAdvisoryLockName('cron-startup', ctx.config.namespace);
|
|
112
|
+
return await ctx.sql.begin(async (tx) => {
|
|
113
|
+
await tx `
|
|
114
|
+
SELECT pg_advisory_xact_lock(hashtextextended(${lockName}, 0))
|
|
115
|
+
`;
|
|
116
|
+
const now = await databaseNow(tx);
|
|
117
|
+
const [leader] = await tx `
|
|
118
|
+
SELECT
|
|
119
|
+
broker_id = ${ctx.config.brokerId}
|
|
120
|
+
AND session_id IS NOT DISTINCT FROM ${ctx.config.brokerSessionId} AS elected
|
|
121
|
+
FROM bunqueue_brokers
|
|
122
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
123
|
+
AND heartbeat_at > ${now - postgresBrokerStaleMs(ctx)}
|
|
124
|
+
ORDER BY started_at, broker_id, session_id NULLS FIRST
|
|
125
|
+
LIMIT 1
|
|
126
|
+
`;
|
|
127
|
+
if (!leader?.elected)
|
|
128
|
+
return 0;
|
|
129
|
+
const rows = await tx `
|
|
130
|
+
SELECT name, payload, next_run, executions, max_limit
|
|
131
|
+
FROM bunqueue_crons
|
|
132
|
+
WHERE namespace = ${ctx.config.namespace} AND next_run < ${now}
|
|
133
|
+
ORDER BY next_run, name
|
|
134
|
+
FOR UPDATE
|
|
135
|
+
`;
|
|
136
|
+
let reconciled = 0;
|
|
137
|
+
for (const row of rows) {
|
|
138
|
+
const cron = decodePostgresCron(row);
|
|
139
|
+
if (!cron.skipMissedOnRestart && !cron.skipIfNoWorker)
|
|
140
|
+
continue;
|
|
141
|
+
cron.nextRun = cron.schedule
|
|
142
|
+
? getNextCronRun(expandCronShortcut(cron.schedule), now, cron.timezone ?? undefined)
|
|
143
|
+
: getNextIntervalRun(cron.repeatEvery, now);
|
|
144
|
+
await tx `
|
|
145
|
+
UPDATE bunqueue_crons
|
|
146
|
+
SET payload = ${encodePostgresValue(cron)}, next_run = ${cron.nextRun}, updated_at = ${now}
|
|
147
|
+
WHERE namespace = ${ctx.config.namespace} AND name = ${cron.name}
|
|
148
|
+
`;
|
|
149
|
+
reconciled++;
|
|
150
|
+
}
|
|
151
|
+
return reconciled;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async function fireCron(tx, ctx, row, now) {
|
|
155
|
+
const cron = decodePostgresCron(row);
|
|
156
|
+
const admitted = await admitPostgresJob(tx, ctx, createJob(generateJobId(), cron.queue, spawnedInput(cron), now), { linkParent: false });
|
|
157
|
+
if (!admitted.inserted && cron.preventOverlap) {
|
|
158
|
+
await skipCron(tx, ctx, cron, now);
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
cron.executions++;
|
|
162
|
+
cron.nextRun = followingRun(cron, now);
|
|
163
|
+
await tx `
|
|
164
|
+
UPDATE bunqueue_crons
|
|
165
|
+
SET payload = ${encodePostgresValue(cron)}, next_run = ${cron.nextRun},
|
|
166
|
+
executions = ${cron.executions}, updated_at = ${now}
|
|
167
|
+
WHERE namespace = ${ctx.config.namespace} AND name = ${cron.name}
|
|
168
|
+
`;
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
async function skipCron(tx, ctx, cron, now) {
|
|
172
|
+
cron.nextRun = followingRun(cron, now);
|
|
173
|
+
await tx `
|
|
174
|
+
UPDATE bunqueue_crons
|
|
175
|
+
SET payload = ${encodePostgresValue(cron)}, next_run = ${cron.nextRun}, updated_at = ${now}
|
|
176
|
+
WHERE namespace = ${ctx.config.namespace} AND name = ${cron.name}
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
/** Advance and enqueue each due cron once under PostgreSQL row locks. */
|
|
180
|
+
export async function processPostgresCrons(ctx, limit = 100) {
|
|
181
|
+
return await ctx.sql.begin(async (tx) => {
|
|
182
|
+
const now = await databaseNow(tx);
|
|
183
|
+
const rows = await tx `
|
|
184
|
+
SELECT name, payload, next_run, executions, max_limit
|
|
185
|
+
FROM bunqueue_crons
|
|
186
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
187
|
+
AND next_run <= ${now}
|
|
188
|
+
AND (max_limit IS NULL OR executions < max_limit)
|
|
189
|
+
ORDER BY next_run, name
|
|
190
|
+
LIMIT ${Math.max(1, limit)}
|
|
191
|
+
FOR UPDATE SKIP LOCKED
|
|
192
|
+
`;
|
|
193
|
+
let fired = 0;
|
|
194
|
+
for (const row of rows) {
|
|
195
|
+
const cron = decodePostgresCron(row);
|
|
196
|
+
if (cron.skipIfNoWorker && !(await hasActivePostgresWorker(tx, ctx, cron.queue, now))) {
|
|
197
|
+
await skipCron(tx, ctx, cron, now);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (await fireCron(tx, ctx, row, now))
|
|
201
|
+
fired++;
|
|
202
|
+
}
|
|
203
|
+
return fired;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TransactionSQL } from 'bun';
|
|
2
|
+
import { type PostgresContext } from './context';
|
|
3
|
+
import type { PostgresJobRow, PostgresStoredJob } from './types';
|
|
4
|
+
/** Clear a live deduplication owner and publish the new authoritative payload. */
|
|
5
|
+
export declare function clearPostgresDeduplication(tx: TransactionSQL, ctx: PostgresContext, row: PostgresJobRow, now: number): Promise<PostgresStoredJob>;
|
|
6
|
+
/** Persist a newer extend window so every broker observes the same deduplication metadata. */
|
|
7
|
+
export declare function extendPostgresDeduplication(tx: TransactionSQL, ctx: PostgresContext, row: PostgresJobRow, ttl: number, now: number): Promise<PostgresStoredJob>;
|
|
8
|
+
/** Publish deletion of a pending generation replaced within the same transaction. */
|
|
9
|
+
export declare function recordPostgresDeduplicationRemoval(tx: TransactionSQL, ctx: PostgresContext, row: PostgresJobRow, now: number): Promise<void>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { decodePostgresJob, encodePostgresValue } from './codec';
|
|
2
|
+
import { recordPostgresEvent } from './context';
|
|
3
|
+
/** Clear a live deduplication owner and publish the new authoritative payload. */
|
|
4
|
+
export async function clearPostgresDeduplication(tx, ctx, row, now) {
|
|
5
|
+
const stored = decodePostgresJob(row);
|
|
6
|
+
const updatedJob = { ...stored.job, uniqueKey: null };
|
|
7
|
+
await tx `
|
|
8
|
+
UPDATE bunqueue_jobs
|
|
9
|
+
SET payload = ${encodePostgresValue(updatedJob)}, unique_key = NULL,
|
|
10
|
+
unique_expires_at = NULL, version = version + 1
|
|
11
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${row.id}
|
|
12
|
+
`;
|
|
13
|
+
await recordPostgresEvent(tx, ctx, {
|
|
14
|
+
queue: updatedJob.queue,
|
|
15
|
+
type: 'updated',
|
|
16
|
+
jobId: updatedJob.id,
|
|
17
|
+
occurredAt: now,
|
|
18
|
+
job: updatedJob,
|
|
19
|
+
state: row.state,
|
|
20
|
+
});
|
|
21
|
+
return { ...stored, job: updatedJob, version: stored.version + 1 };
|
|
22
|
+
}
|
|
23
|
+
/** Persist a newer extend window so every broker observes the same deduplication metadata. */
|
|
24
|
+
export async function extendPostgresDeduplication(tx, ctx, row, ttl, now) {
|
|
25
|
+
const stored = decodePostgresJob(row);
|
|
26
|
+
const updatedJob = { ...stored.job, deduplicationTtl: ttl };
|
|
27
|
+
await tx `
|
|
28
|
+
UPDATE bunqueue_jobs
|
|
29
|
+
SET payload = ${encodePostgresValue(updatedJob)}, unique_expires_at = ${now + ttl},
|
|
30
|
+
version = version + 1
|
|
31
|
+
WHERE namespace = ${ctx.config.namespace} AND id = ${row.id}
|
|
32
|
+
`;
|
|
33
|
+
return { ...stored, job: updatedJob, version: stored.version + 1 };
|
|
34
|
+
}
|
|
35
|
+
/** Publish deletion of a pending generation replaced within the same transaction. */
|
|
36
|
+
export async function recordPostgresDeduplicationRemoval(tx, ctx, row, now) {
|
|
37
|
+
const stored = decodePostgresJob(row);
|
|
38
|
+
await recordPostgresEvent(tx, ctx, {
|
|
39
|
+
queue: stored.job.queue,
|
|
40
|
+
type: 'removed',
|
|
41
|
+
jobId: stored.job.id,
|
|
42
|
+
occurredAt: now,
|
|
43
|
+
job: stored.job,
|
|
44
|
+
state: stored.state,
|
|
45
|
+
removed: true,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TransactionSQL } from 'bun';
|
|
2
|
+
import { type JobId } from '../../../domain/types/job';
|
|
3
|
+
import type { PostgresContext } from './context';
|
|
4
|
+
/** Acquire candidate identity locks in the same order as dependency admission. */
|
|
5
|
+
export declare function lockPostgresDestructionIdentities(tx: TransactionSQL, ctx: PostgresContext, ids: readonly JobId[]): Promise<void>;
|
|
6
|
+
/** Lock candidate rows and every live consumer row in deterministic order. */
|
|
7
|
+
export declare function lockPostgresDestructionRows(tx: TransactionSQL, ctx: PostgresContext, ids: readonly JobId[]): Promise<void>;
|
|
8
|
+
/** Acquire identity locks before any target or consumer row lock. */
|
|
9
|
+
export declare function lockPostgresDestructionCandidates(tx: TransactionSQL, ctx: PostgresContext, ids: readonly JobId[]): Promise<void>;
|
|
10
|
+
/** Split a revalidated delete set without orphaning consumers that survive the command. */
|
|
11
|
+
export declare function partitionPostgresDestructionCandidates(tx: TransactionSQL, ctx: PostgresContext, ids: readonly JobId[], deletingConsumerIds?: readonly JobId[]): Promise<{
|
|
12
|
+
deletableIds: JobId[];
|
|
13
|
+
protectedIds: JobId[];
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jobId } from '../../../domain/types/job';
|
|
2
|
+
import { lockPostgresDependencyCompletions } from './dependencyPromotion';
|
|
3
|
+
const LIVE_CONSUMER_STATES = [
|
|
4
|
+
'waiting',
|
|
5
|
+
'prioritized',
|
|
6
|
+
'delayed',
|
|
7
|
+
'waiting-children',
|
|
8
|
+
'active',
|
|
9
|
+
];
|
|
10
|
+
function uniqueIds(ids) {
|
|
11
|
+
return [...new Set(ids.map(String))].sort();
|
|
12
|
+
}
|
|
13
|
+
/** Acquire candidate identity locks in the same order as dependency admission. */
|
|
14
|
+
export async function lockPostgresDestructionIdentities(tx, ctx, ids) {
|
|
15
|
+
const candidates = uniqueIds(ids);
|
|
16
|
+
if (candidates.length === 0)
|
|
17
|
+
return;
|
|
18
|
+
await lockPostgresDependencyCompletions(tx, ctx, candidates.map(jobId));
|
|
19
|
+
}
|
|
20
|
+
/** Lock candidate rows and every live consumer row in deterministic order. */
|
|
21
|
+
export async function lockPostgresDestructionRows(tx, ctx, ids) {
|
|
22
|
+
const candidates = uniqueIds(ids);
|
|
23
|
+
if (candidates.length === 0)
|
|
24
|
+
return;
|
|
25
|
+
const consumers = await tx `
|
|
26
|
+
SELECT DISTINCT consumer.id
|
|
27
|
+
FROM bunqueue_dependencies AS dependency
|
|
28
|
+
JOIN bunqueue_jobs AS consumer
|
|
29
|
+
ON consumer.namespace = dependency.namespace AND consumer.id = dependency.job_id
|
|
30
|
+
WHERE dependency.namespace = ${ctx.config.namespace}
|
|
31
|
+
AND dependency.dependency_id = ANY(${tx.array(candidates, 'TEXT')})
|
|
32
|
+
AND consumer.state = ANY(${tx.array([...LIVE_CONSUMER_STATES], 'TEXT')})
|
|
33
|
+
ORDER BY consumer.id
|
|
34
|
+
`;
|
|
35
|
+
const rowIds = [...new Set([...candidates, ...consumers.map((row) => row.id)])].sort();
|
|
36
|
+
await tx `
|
|
37
|
+
SELECT id FROM bunqueue_jobs
|
|
38
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
39
|
+
AND id = ANY(${tx.array(rowIds, 'TEXT')})
|
|
40
|
+
ORDER BY id
|
|
41
|
+
FOR UPDATE
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
/** Acquire identity locks before any target or consumer row lock. */
|
|
45
|
+
export async function lockPostgresDestructionCandidates(tx, ctx, ids) {
|
|
46
|
+
await lockPostgresDestructionIdentities(tx, ctx, ids);
|
|
47
|
+
await lockPostgresDestructionRows(tx, ctx, ids);
|
|
48
|
+
}
|
|
49
|
+
/** Split a revalidated delete set without orphaning consumers that survive the command. */
|
|
50
|
+
export async function partitionPostgresDestructionCandidates(tx, ctx, ids, deletingConsumerIds = ids) {
|
|
51
|
+
const candidates = uniqueIds(ids);
|
|
52
|
+
if (candidates.length === 0)
|
|
53
|
+
return { deletableIds: [], protectedIds: [] };
|
|
54
|
+
const deleting = uniqueIds(deletingConsumerIds);
|
|
55
|
+
const rows = await tx `
|
|
56
|
+
SELECT DISTINCT dependency.dependency_id
|
|
57
|
+
FROM bunqueue_dependencies AS dependency
|
|
58
|
+
JOIN bunqueue_jobs AS consumer
|
|
59
|
+
ON consumer.namespace = dependency.namespace AND consumer.id = dependency.job_id
|
|
60
|
+
WHERE dependency.namespace = ${ctx.config.namespace}
|
|
61
|
+
AND dependency.dependency_id = ANY(${tx.array(candidates, 'TEXT')})
|
|
62
|
+
AND consumer.state = ANY(${tx.array([...LIVE_CONSUMER_STATES], 'TEXT')})
|
|
63
|
+
AND (
|
|
64
|
+
${deleting.length} = 0
|
|
65
|
+
OR consumer.id <> ALL(${tx.array(deleting, 'TEXT')})
|
|
66
|
+
)
|
|
67
|
+
ORDER BY dependency.dependency_id
|
|
68
|
+
`;
|
|
69
|
+
const protectedSet = new Set(rows.map((row) => row.dependency_id));
|
|
70
|
+
return {
|
|
71
|
+
deletableIds: candidates.filter((id) => !protectedSet.has(id)).map(jobId),
|
|
72
|
+
protectedIds: candidates.filter((id) => protectedSet.has(id)).map(jobId),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TransactionSQL } from 'bun';
|
|
2
|
+
import { type Job, type JobId } from '../../../domain/types/job';
|
|
3
|
+
import type { PostgresContext } from './context';
|
|
4
|
+
import type { PostgresJobState } from './types';
|
|
5
|
+
type DependencyReadyState = Extract<PostgresJobState, 'waiting' | 'prioritized' | 'delayed'>;
|
|
6
|
+
/** Serialize completion evidence readers and writers for the supplied generations. */
|
|
7
|
+
export declare function lockPostgresDependencyCompletions(tx: TransactionSQL, ctx: PostgresContext, dependencyIds: readonly JobId[]): Promise<void>;
|
|
8
|
+
/** Safely probe a late-discovered identity without inverting row/advisory lock order. */
|
|
9
|
+
export declare function tryLockPostgresDependencyCompletions(tx: TransactionSQL, ctx: PostgresContext, dependencyIds: readonly JobId[]): Promise<boolean>;
|
|
10
|
+
/** Reject dependency references that have no job row or durable completion evidence. */
|
|
11
|
+
export declare function assertPostgresDependenciesExist(tx: TransactionSQL, ctx: PostgresContext, dependencyIds: readonly JobId[]): Promise<void>;
|
|
12
|
+
/** Read dependency evidence while the caller holds every corresponding completion lock. */
|
|
13
|
+
export declare function postgresDependenciesSatisfied(tx: TransactionSQL, ctx: PostgresContext, dependencyIds: readonly JobId[]): Promise<boolean>;
|
|
14
|
+
/** Pure scheduling policy shared by completion-time promotion and pull-time repair. */
|
|
15
|
+
export declare function postgresDependencyReadyState(job: Job, now: number): DependencyReadyState;
|
|
16
|
+
/**
|
|
17
|
+
* Discover and serialize every dependency consumer before completion locks child rows.
|
|
18
|
+
* The returned immutable plan is safe to execute after completion evidence is stored.
|
|
19
|
+
*/
|
|
20
|
+
export declare function lockPostgresCompletionConsumers(tx: TransactionSQL, ctx: PostgresContext, dependencyIds: readonly JobId[]): Promise<readonly JobId[]>;
|
|
21
|
+
/** Build a completion-scoped command after acquiring its parent locks. */
|
|
22
|
+
export declare function planPostgresDependencyCompletion(tx: TransactionSQL, ctx: PostgresContext, dependencyIds: readonly JobId[]): Promise<{
|
|
23
|
+
promote(now: number): Promise<JobId[]>;
|
|
24
|
+
}>;
|
|
25
|
+
/** Promote all resolved members of a previously locked completion-consumer plan. */
|
|
26
|
+
export declare function promotePostgresCompletionConsumers(tx: TransactionSQL, ctx: PostgresContext, parentIds: readonly JobId[], now: number): Promise<JobId[]>;
|
|
27
|
+
/** Pull-time convergence path for rows left behind by interrupted or legacy writers. */
|
|
28
|
+
export declare function repairResolvedPostgresDependencyConsumers(tx: TransactionSQL, ctx: PostgresContext, queue: string, now: number): Promise<JobId[]>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { jobId, MAX_TIMELINE_ENTRIES } from '../../../domain/types/job';
|
|
2
|
+
import { postgresAdvisoryLockName } from './advisoryLocks';
|
|
3
|
+
import { recordPostgresJobEvents } from './batchEvents';
|
|
4
|
+
import { decodePostgresJob, encodePostgresValue, postgresByteaBase64 } from './codec';
|
|
5
|
+
import { lockPostgresFlowParents } from './flowFailures';
|
|
6
|
+
function uniqueIds(ids) {
|
|
7
|
+
return [...new Set(ids.map(String))].sort();
|
|
8
|
+
}
|
|
9
|
+
/** Serialize completion evidence readers and writers for the supplied generations. */
|
|
10
|
+
export async function lockPostgresDependencyCompletions(tx, ctx, dependencyIds) {
|
|
11
|
+
const lockNames = uniqueIds(dependencyIds).map((id) => postgresAdvisoryLockName('dependency-completion', ctx.config.namespace, id));
|
|
12
|
+
if (lockNames.length === 0)
|
|
13
|
+
return;
|
|
14
|
+
await tx `
|
|
15
|
+
WITH ordered_ids AS MATERIALIZED (
|
|
16
|
+
SELECT DISTINCT hashtextextended(lock_name, 0) AS lock_key
|
|
17
|
+
FROM unnest(${tx.array(lockNames, 'TEXT')}) AS dependency(lock_name)
|
|
18
|
+
ORDER BY lock_key
|
|
19
|
+
), locked_ids AS MATERIALIZED (
|
|
20
|
+
SELECT lock_key, pg_advisory_xact_lock(lock_key) AS acquired
|
|
21
|
+
FROM ordered_ids
|
|
22
|
+
ORDER BY lock_key
|
|
23
|
+
)
|
|
24
|
+
SELECT COUNT(*) FROM locked_ids
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
/** Safely probe a late-discovered identity without inverting row/advisory lock order. */
|
|
28
|
+
export async function tryLockPostgresDependencyCompletions(tx, ctx, dependencyIds) {
|
|
29
|
+
const lockNames = uniqueIds(dependencyIds).map((id) => postgresAdvisoryLockName('dependency-completion', ctx.config.namespace, id));
|
|
30
|
+
if (lockNames.length === 0)
|
|
31
|
+
return true;
|
|
32
|
+
const [row] = await tx `
|
|
33
|
+
WITH ordered_ids AS MATERIALIZED (
|
|
34
|
+
SELECT DISTINCT hashtextextended(lock_name, 0) AS lock_key
|
|
35
|
+
FROM unnest(${tx.array(lockNames, 'TEXT')}) AS dependency(lock_name)
|
|
36
|
+
ORDER BY lock_key
|
|
37
|
+
), locked_ids AS MATERIALIZED (
|
|
38
|
+
SELECT lock_key, pg_try_advisory_xact_lock(lock_key) AS acquired
|
|
39
|
+
FROM ordered_ids
|
|
40
|
+
ORDER BY lock_key
|
|
41
|
+
)
|
|
42
|
+
SELECT COALESCE(BOOL_AND(acquired), TRUE) AS acquired FROM locked_ids
|
|
43
|
+
`;
|
|
44
|
+
return row.acquired;
|
|
45
|
+
}
|
|
46
|
+
/** Reject dependency references that have no job row or durable completion evidence. */
|
|
47
|
+
export async function assertPostgresDependenciesExist(tx, ctx, dependencyIds) {
|
|
48
|
+
const ids = [...new Set(dependencyIds.map(String))];
|
|
49
|
+
if (ids.length === 0)
|
|
50
|
+
return;
|
|
51
|
+
const rows = await tx `
|
|
52
|
+
WITH requested AS MATERIALIZED (
|
|
53
|
+
SELECT dependency_id, ordinal
|
|
54
|
+
FROM unnest(${tx.array(ids, 'TEXT')}) WITH ORDINALITY
|
|
55
|
+
AS dependency(dependency_id, ordinal)
|
|
56
|
+
)
|
|
57
|
+
SELECT requested.dependency_id
|
|
58
|
+
FROM requested
|
|
59
|
+
WHERE NOT EXISTS (
|
|
60
|
+
SELECT 1 FROM bunqueue_jobs AS job
|
|
61
|
+
WHERE job.namespace = ${ctx.config.namespace} AND job.id = requested.dependency_id
|
|
62
|
+
) AND NOT EXISTS (
|
|
63
|
+
SELECT 1 FROM bunqueue_completions AS completion
|
|
64
|
+
WHERE completion.namespace = ${ctx.config.namespace}
|
|
65
|
+
AND completion.job_id = requested.dependency_id
|
|
66
|
+
)
|
|
67
|
+
ORDER BY requested.ordinal
|
|
68
|
+
LIMIT 1
|
|
69
|
+
`;
|
|
70
|
+
if (rows[0])
|
|
71
|
+
throw new Error(`Dependency job not found: ${rows[0].dependency_id}`);
|
|
72
|
+
}
|
|
73
|
+
/** Read dependency evidence while the caller holds every corresponding completion lock. */
|
|
74
|
+
export async function postgresDependenciesSatisfied(tx, ctx, dependencyIds) {
|
|
75
|
+
const ids = uniqueIds(dependencyIds);
|
|
76
|
+
if (ids.length === 0)
|
|
77
|
+
return true;
|
|
78
|
+
const [row] = await tx `
|
|
79
|
+
SELECT COUNT(DISTINCT dependency_id)::bigint AS count
|
|
80
|
+
FROM (
|
|
81
|
+
SELECT job_id AS dependency_id
|
|
82
|
+
FROM bunqueue_completions
|
|
83
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
84
|
+
AND job_id = ANY(${tx.array(ids, 'TEXT')})
|
|
85
|
+
UNION
|
|
86
|
+
SELECT id AS dependency_id
|
|
87
|
+
FROM bunqueue_jobs
|
|
88
|
+
WHERE namespace = ${ctx.config.namespace}
|
|
89
|
+
AND state = 'completed'
|
|
90
|
+
AND id = ANY(${tx.array(ids, 'TEXT')})
|
|
91
|
+
) completed_dependencies
|
|
92
|
+
`;
|
|
93
|
+
return Number(row.count) === ids.length;
|
|
94
|
+
}
|
|
95
|
+
/** Pure scheduling policy shared by completion-time promotion and pull-time repair. */
|
|
96
|
+
export function postgresDependencyReadyState(job, now) {
|
|
97
|
+
if (job.runAt > now)
|
|
98
|
+
return 'delayed';
|
|
99
|
+
return job.priority > 0 ? 'prioritized' : 'waiting';
|
|
100
|
+
}
|
|
101
|
+
function preparePromotion(row, now) {
|
|
102
|
+
const job = decodePostgresJob(row).job;
|
|
103
|
+
const state = postgresDependencyReadyState(job, now);
|
|
104
|
+
if (job.timeline.at(-1)?.state !== state && job.timeline.length < MAX_TIMELINE_ENTRIES) {
|
|
105
|
+
job.timeline.push({ state, timestamp: now });
|
|
106
|
+
}
|
|
107
|
+
return { job, state, payload: encodePostgresValue(job) };
|
|
108
|
+
}
|
|
109
|
+
async function consumerIdsForDependencies(tx, ctx, dependencyIds) {
|
|
110
|
+
const ids = uniqueIds(dependencyIds);
|
|
111
|
+
if (ids.length === 0)
|
|
112
|
+
return [];
|
|
113
|
+
const rows = await tx `
|
|
114
|
+
SELECT DISTINCT dependency.job_id AS parent_id
|
|
115
|
+
FROM bunqueue_dependencies AS dependency
|
|
116
|
+
JOIN bunqueue_jobs AS parent
|
|
117
|
+
ON parent.namespace = dependency.namespace AND parent.id = dependency.job_id
|
|
118
|
+
WHERE dependency.namespace = ${ctx.config.namespace}
|
|
119
|
+
AND dependency.dependency_id = ANY(${tx.array(ids, 'TEXT')})
|
|
120
|
+
AND parent.state = 'waiting-children'
|
|
121
|
+
ORDER BY dependency.job_id
|
|
122
|
+
`;
|
|
123
|
+
return rows.map((row) => jobId(row.parent_id));
|
|
124
|
+
}
|
|
125
|
+
async function resolvedRows(tx, ctx, parentIds) {
|
|
126
|
+
const ids = uniqueIds(parentIds);
|
|
127
|
+
if (ids.length === 0)
|
|
128
|
+
return [];
|
|
129
|
+
return await tx `
|
|
130
|
+
SELECT parent.*
|
|
131
|
+
FROM bunqueue_jobs AS parent
|
|
132
|
+
WHERE parent.namespace = ${ctx.config.namespace}
|
|
133
|
+
AND parent.id = ANY(${tx.array(ids, 'TEXT')})
|
|
134
|
+
AND parent.state = 'waiting-children'
|
|
135
|
+
AND NOT EXISTS (
|
|
136
|
+
SELECT 1
|
|
137
|
+
FROM bunqueue_dependencies AS dependency
|
|
138
|
+
WHERE dependency.namespace = parent.namespace
|
|
139
|
+
AND dependency.job_id = parent.id
|
|
140
|
+
AND NOT EXISTS (
|
|
141
|
+
SELECT 1 FROM bunqueue_completions AS completion
|
|
142
|
+
WHERE completion.namespace = dependency.namespace
|
|
143
|
+
AND completion.job_id = dependency.dependency_id
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
ORDER BY parent.id
|
|
147
|
+
FOR UPDATE OF parent
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
async function persistPromotions(tx, ctx, prepared) {
|
|
151
|
+
if (prepared.length === 0)
|
|
152
|
+
return;
|
|
153
|
+
await tx `
|
|
154
|
+
UPDATE bunqueue_jobs AS parent
|
|
155
|
+
SET payload = decode(batch.payload_base64, 'base64'),
|
|
156
|
+
state = batch.state,
|
|
157
|
+
version = parent.version + 1
|
|
158
|
+
FROM unnest(
|
|
159
|
+
${tx.array(prepared.map(({ job }) => String(job.id)), 'TEXT')},
|
|
160
|
+
${tx.array(prepared.map(({ payload }) => postgresByteaBase64(payload)), 'TEXT')},
|
|
161
|
+
${tx.array(prepared.map(({ state }) => state), 'TEXT')}
|
|
162
|
+
) AS batch(id, payload_base64, state)
|
|
163
|
+
WHERE parent.namespace = ${ctx.config.namespace} AND parent.id = batch.id
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Discover and serialize every dependency consumer before completion locks child rows.
|
|
168
|
+
* The returned immutable plan is safe to execute after completion evidence is stored.
|
|
169
|
+
*/
|
|
170
|
+
export async function lockPostgresCompletionConsumers(tx, ctx, dependencyIds) {
|
|
171
|
+
const parentIds = await consumerIdsForDependencies(tx, ctx, dependencyIds);
|
|
172
|
+
await lockPostgresFlowParents(tx, ctx, parentIds);
|
|
173
|
+
return parentIds;
|
|
174
|
+
}
|
|
175
|
+
/** Build a completion-scoped command after acquiring its parent locks. */
|
|
176
|
+
export async function planPostgresDependencyCompletion(tx, ctx, dependencyIds) {
|
|
177
|
+
await lockPostgresDependencyCompletions(tx, ctx, dependencyIds);
|
|
178
|
+
const parentIds = await lockPostgresCompletionConsumers(tx, ctx, dependencyIds);
|
|
179
|
+
return {
|
|
180
|
+
promote: (now) => promotePostgresCompletionConsumers(tx, ctx, parentIds, now),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/** Promote all resolved members of a previously locked completion-consumer plan. */
|
|
184
|
+
export async function promotePostgresCompletionConsumers(tx, ctx, parentIds, now) {
|
|
185
|
+
const prepared = (await resolvedRows(tx, ctx, parentIds)).map((row) => preparePromotion(row, now));
|
|
186
|
+
await persistPromotions(tx, ctx, prepared);
|
|
187
|
+
await recordPostgresJobEvents(tx, ctx, prepared.map(({ job, state }) => ({ job, type: 'retried', state })), now);
|
|
188
|
+
return prepared.map(({ job }) => job.id);
|
|
189
|
+
}
|
|
190
|
+
/** Pull-time convergence path for rows left behind by interrupted or legacy writers. */
|
|
191
|
+
export async function repairResolvedPostgresDependencyConsumers(tx, ctx, queue, now) {
|
|
192
|
+
const rows = await tx `
|
|
193
|
+
SELECT parent.id AS parent_id
|
|
194
|
+
FROM bunqueue_jobs AS parent
|
|
195
|
+
WHERE parent.namespace = ${ctx.config.namespace}
|
|
196
|
+
AND parent.queue = ${queue}
|
|
197
|
+
AND parent.state = 'waiting-children'
|
|
198
|
+
AND NOT EXISTS (
|
|
199
|
+
SELECT 1 FROM bunqueue_dependencies AS dependency
|
|
200
|
+
WHERE dependency.namespace = parent.namespace AND dependency.job_id = parent.id
|
|
201
|
+
AND NOT EXISTS (
|
|
202
|
+
SELECT 1 FROM bunqueue_completions AS completion
|
|
203
|
+
WHERE completion.namespace = dependency.namespace
|
|
204
|
+
AND completion.job_id = dependency.dependency_id
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
ORDER BY parent.id
|
|
208
|
+
`;
|
|
209
|
+
const parentIds = rows.map((row) => jobId(row.parent_id));
|
|
210
|
+
await lockPostgresFlowParents(tx, ctx, parentIds);
|
|
211
|
+
return await promotePostgresCompletionConsumers(tx, ctx, parentIds, now);
|
|
212
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type JobId } from '../../../domain/types/job';
|
|
2
|
+
import { type PostgresContext } from './context';
|
|
3
|
+
export declare function removePostgresJob(ctx: PostgresContext, id: JobId, token?: string): Promise<boolean>;
|
|
4
|
+
export declare function retryPostgresTerminalJob(ctx: PostgresContext, id: JobId, requestedTimestamp?: number): Promise<boolean>;
|