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.
Files changed (324) hide show
  1. package/README.md +108 -76
  2. package/dist/application/background/dlq.js +2 -0
  3. package/dist/application/contextFactory.js +5 -0
  4. package/dist/application/dlqManager.d.ts +10 -0
  5. package/dist/application/dlqManager.js +47 -9
  6. package/dist/application/dlqRetry.js +59 -38
  7. package/dist/application/eventsManager.d.ts +1 -1
  8. package/dist/application/eventsManager.js +30 -26
  9. package/dist/application/operations/jobManagement.d.ts +4 -0
  10. package/dist/application/operations/jobMoveOperations.js +9 -4
  11. package/dist/application/postgres-queue-manager/batchSnapshot.d.ts +16 -0
  12. package/dist/application/postgres-queue-manager/batchSnapshot.js +17 -0
  13. package/dist/application/postgres-queue-manager/cloud.d.ts +30 -0
  14. package/dist/application/postgres-queue-manager/cloud.js +55 -0
  15. package/dist/application/postgres-queue-manager/config.d.ts +5 -0
  16. package/dist/application/postgres-queue-manager/config.js +1 -0
  17. package/dist/application/postgres-queue-manager/control.d.ts +26 -0
  18. package/dist/application/postgres-queue-manager/control.js +186 -0
  19. package/dist/application/postgres-queue-manager/deferredWrites.d.ts +13 -0
  20. package/dist/application/postgres-queue-manager/deferredWrites.js +60 -0
  21. package/dist/application/postgres-queue-manager/delivery.d.ts +19 -0
  22. package/dist/application/postgres-queue-manager/delivery.js +80 -0
  23. package/dist/application/postgres-queue-manager/lease.d.ts +13 -0
  24. package/dist/application/postgres-queue-manager/lease.js +88 -0
  25. package/dist/application/postgres-queue-manager/lifetimeMetrics.d.ts +32 -0
  26. package/dist/application/postgres-queue-manager/lifetimeMetrics.js +53 -0
  27. package/dist/application/postgres-queue-manager/maintenance.d.ts +14 -0
  28. package/dist/application/postgres-queue-manager/maintenance.js +100 -0
  29. package/dist/application/postgres-queue-manager/operationGate.d.ts +26 -0
  30. package/dist/application/postgres-queue-manager/operationGate.js +96 -0
  31. package/dist/application/postgres-queue-manager/operations.d.ts +25 -0
  32. package/dist/application/postgres-queue-manager/operations.js +194 -0
  33. package/dist/application/postgres-queue-manager/projectionRefreshes.d.ts +43 -0
  34. package/dist/application/postgres-queue-manager/projectionRefreshes.js +170 -0
  35. package/dist/application/postgres-queue-manager/queries.d.ts +142 -0
  36. package/dist/application/postgres-queue-manager/queries.js +231 -0
  37. package/dist/application/postgres-queue-manager/relationships.d.ts +10 -0
  38. package/dist/application/postgres-queue-manager/relationships.js +45 -0
  39. package/dist/application/postgres-queue-manager/services.d.ts +31 -0
  40. package/dist/application/postgres-queue-manager/services.js +149 -0
  41. package/dist/application/postgres-queue-manager/snapshot.d.ts +51 -0
  42. package/dist/application/postgres-queue-manager/snapshot.js +236 -0
  43. package/dist/application/postgres-queue-manager/snapshotViews.d.ts +15 -0
  44. package/dist/application/postgres-queue-manager/snapshotViews.js +100 -0
  45. package/dist/application/postgres-queue-manager/startupEventBuffer.d.ts +12 -0
  46. package/dist/application/postgres-queue-manager/startupEventBuffer.js +36 -0
  47. package/dist/application/postgres-queue-manager/state.d.ts +47 -0
  48. package/dist/application/postgres-queue-manager/state.js +273 -0
  49. package/dist/application/postgres-queue-manager/terminalDelivery.d.ts +20 -0
  50. package/dist/application/postgres-queue-manager/terminalDelivery.js +100 -0
  51. package/dist/application/postgresQueueManager.d.ts +2 -0
  52. package/dist/application/postgresQueueManager.js +1 -0
  53. package/dist/application/queue-manager/flow-failures.js +4 -0
  54. package/dist/application/queue-manager/limits.d.ts +2 -0
  55. package/dist/application/queue-manager/limits.js +4 -0
  56. package/dist/application/queue-manager/observability.js +15 -3
  57. package/dist/application/queue-manager/queries.d.ts +7 -0
  58. package/dist/application/queue-manager/queries.js +9 -0
  59. package/dist/application/queue-manager/services.d.ts +1 -1
  60. package/dist/application/queue-manager/services.js +4 -2
  61. package/dist/application/queue-manager/state.js +16 -1
  62. package/dist/cli/commands/server.js +1 -1
  63. package/dist/client/forwarder.d.ts +6 -4
  64. package/dist/client/forwarder.js +5 -3
  65. package/dist/client/queue/dlq.d.ts +2 -0
  66. package/dist/client/queue/dlq.js +14 -0
  67. package/dist/client/queue/dlqOps.d.ts +2 -0
  68. package/dist/client/queue/dlqOps.js +4 -0
  69. package/dist/client/queue/helpers.js +2 -0
  70. package/dist/client/queue/runtime/configuration.d.ts +4 -0
  71. package/dist/client/queue/runtime/configuration.js +8 -0
  72. package/dist/client/worker/runtime/execution.js +3 -8
  73. package/dist/client/worker/runtime/polling.d.ts +1 -0
  74. package/dist/client/worker/runtime/polling.js +9 -0
  75. package/dist/config/resolve.d.ts +14 -0
  76. package/dist/config/resolve.js +50 -5
  77. package/dist/config/types.d.ts +18 -0
  78. package/dist/domain/queue/dlqShard.d.ts +2 -0
  79. package/dist/domain/queue/dlqShard.js +16 -0
  80. package/dist/domain/queue/shard/dlq.d.ts +1 -0
  81. package/dist/domain/queue/shard/dlq.js +3 -0
  82. package/dist/domain/types/commands/dlq.d.ts +5 -0
  83. package/dist/domain/types/commands/union.d.ts +1 -1
  84. package/dist/domain/types/cron.d.ts +5 -0
  85. package/dist/domain/types/cron.js +14 -3
  86. package/dist/domain/types/dlq.d.ts +1 -1
  87. package/dist/domain/types/dlq.js +1 -2
  88. package/dist/domain/types/jobs/model.d.ts +1 -1
  89. package/dist/infrastructure/cloud/commandHandler.js +5 -3
  90. package/dist/infrastructure/cloud/commands/integrations.js +11 -11
  91. package/dist/infrastructure/cloud/commands/jobs.js +48 -40
  92. package/dist/infrastructure/cloud/commands/queues.js +61 -75
  93. package/dist/infrastructure/cloud/queueAdapter/local.d.ts +41 -0
  94. package/dist/infrastructure/cloud/queueAdapter/local.js +187 -0
  95. package/dist/infrastructure/cloud/queueAdapter/postgres.d.ts +42 -0
  96. package/dist/infrastructure/cloud/queueAdapter/postgres.js +188 -0
  97. package/dist/infrastructure/cloud/queueAdapter/registry.d.ts +4 -0
  98. package/dist/infrastructure/cloud/queueAdapter/registry.js +15 -0
  99. package/dist/infrastructure/cloud/queueAdapter/types.d.ts +87 -0
  100. package/dist/infrastructure/cloud/queueAdapter/types.js +1 -0
  101. package/dist/infrastructure/cloud/snapshot/analytics.d.ts +2 -0
  102. package/dist/infrastructure/cloud/snapshot/analytics.js +7 -0
  103. package/dist/infrastructure/cloud/snapshot/collector.d.ts +4 -0
  104. package/dist/infrastructure/cloud/snapshot/collector.js +39 -1
  105. package/dist/infrastructure/cloud/snapshot/config.d.ts +3 -0
  106. package/dist/infrastructure/cloud/snapshot/config.js +43 -0
  107. package/dist/infrastructure/cloud/snapshot/jobs.d.ts +3 -0
  108. package/dist/infrastructure/cloud/snapshot/jobs.js +32 -1
  109. package/dist/infrastructure/cloud/snapshotCollector.js +20 -35
  110. package/dist/infrastructure/cloud/snapshotHelpers.d.ts +3 -3
  111. package/dist/infrastructure/cloud/snapshotHelpers.js +3 -3
  112. package/dist/infrastructure/cloud/statsRefresh.js +2 -1
  113. package/dist/infrastructure/cloud/types/command.d.ts +2 -2
  114. package/dist/infrastructure/persistence/postgres/admission.d.ts +18 -0
  115. package/dist/infrastructure/persistence/postgres/admission.js +193 -0
  116. package/dist/infrastructure/persistence/postgres/admissionParent.d.ts +5 -0
  117. package/dist/infrastructure/persistence/postgres/admissionParent.js +35 -0
  118. package/dist/infrastructure/persistence/postgres/admissionResult.d.ts +9 -0
  119. package/dist/infrastructure/persistence/postgres/admissionResult.js +1 -0
  120. package/dist/infrastructure/persistence/postgres/admissionStore.d.ts +12 -0
  121. package/dist/infrastructure/persistence/postgres/admissionStore.js +101 -0
  122. package/dist/infrastructure/persistence/postgres/advisoryLocks.d.ts +2 -0
  123. package/dist/infrastructure/persistence/postgres/advisoryLocks.js +13 -0
  124. package/dist/infrastructure/persistence/postgres/batchAdmission.d.ts +11 -0
  125. package/dist/infrastructure/persistence/postgres/batchAdmission.js +110 -0
  126. package/dist/infrastructure/persistence/postgres/batchCompletion.d.ts +4 -0
  127. package/dist/infrastructure/persistence/postgres/batchCompletion.js +174 -0
  128. package/dist/infrastructure/persistence/postgres/batchEvents.d.ts +14 -0
  129. package/dist/infrastructure/persistence/postgres/batchEvents.js +72 -0
  130. package/dist/infrastructure/persistence/postgres/brokerSessions.d.ts +14 -0
  131. package/dist/infrastructure/persistence/postgres/brokerSessions.js +94 -0
  132. package/dist/infrastructure/persistence/postgres/claimBatch.d.ts +11 -0
  133. package/dist/infrastructure/persistence/postgres/claimBatch.js +56 -0
  134. package/dist/infrastructure/persistence/postgres/claimSelection.d.ts +4 -0
  135. package/dist/infrastructure/persistence/postgres/claimSelection.js +198 -0
  136. package/dist/infrastructure/persistence/postgres/claims.d.ts +3 -0
  137. package/dist/infrastructure/persistence/postgres/claims.js +137 -0
  138. package/dist/infrastructure/persistence/postgres/cloudReadModel.d.ts +26 -0
  139. package/dist/infrastructure/persistence/postgres/cloudReadModel.js +123 -0
  140. package/dist/infrastructure/persistence/postgres/codec.d.ts +30 -0
  141. package/dist/infrastructure/persistence/postgres/codec.js +82 -0
  142. package/dist/infrastructure/persistence/postgres/completionEvents.d.ts +11 -0
  143. package/dist/infrastructure/persistence/postgres/completionEvents.js +21 -0
  144. package/dist/infrastructure/persistence/postgres/completionLifecycle.d.ts +11 -0
  145. package/dist/infrastructure/persistence/postgres/completionLifecycle.js +145 -0
  146. package/dist/infrastructure/persistence/postgres/completionOutcome.d.ts +4 -0
  147. package/dist/infrastructure/persistence/postgres/completionOutcome.js +112 -0
  148. package/dist/infrastructure/persistence/postgres/completionQueries.d.ts +10 -0
  149. package/dist/infrastructure/persistence/postgres/completionQueries.js +87 -0
  150. package/dist/infrastructure/persistence/postgres/connection.d.ts +3 -0
  151. package/dist/infrastructure/persistence/postgres/connection.js +15 -0
  152. package/dist/infrastructure/persistence/postgres/context.d.ts +28 -0
  153. package/dist/infrastructure/persistence/postgres/context.js +74 -0
  154. package/dist/infrastructure/persistence/postgres/control.d.ts +23 -0
  155. package/dist/infrastructure/persistence/postgres/control.js +105 -0
  156. package/dist/infrastructure/persistence/postgres/crons.d.ts +18 -0
  157. package/dist/infrastructure/persistence/postgres/crons.js +205 -0
  158. package/dist/infrastructure/persistence/postgres/deduplication.d.ts +9 -0
  159. package/dist/infrastructure/persistence/postgres/deduplication.js +47 -0
  160. package/dist/infrastructure/persistence/postgres/dependencyDestruction.d.ts +14 -0
  161. package/dist/infrastructure/persistence/postgres/dependencyDestruction.js +74 -0
  162. package/dist/infrastructure/persistence/postgres/dependencyPromotion.d.ts +29 -0
  163. package/dist/infrastructure/persistence/postgres/dependencyPromotion.js +212 -0
  164. package/dist/infrastructure/persistence/postgres/destructiveMutations.d.ts +4 -0
  165. package/dist/infrastructure/persistence/postgres/destructiveMutations.js +111 -0
  166. package/dist/infrastructure/persistence/postgres/dlqLifecycle.d.ts +13 -0
  167. package/dist/infrastructure/persistence/postgres/dlqLifecycle.js +184 -0
  168. package/dist/infrastructure/persistence/postgres/dlqMaintenance.d.ts +6 -0
  169. package/dist/infrastructure/persistence/postgres/dlqMaintenance.js +36 -0
  170. package/dist/infrastructure/persistence/postgres/dlqRetryPlan.d.ts +21 -0
  171. package/dist/infrastructure/persistence/postgres/dlqRetryPlan.js +92 -0
  172. package/dist/infrastructure/persistence/postgres/eventCommitGc.d.ts +27 -0
  173. package/dist/infrastructure/persistence/postgres/eventCommitGc.js +62 -0
  174. package/dist/infrastructure/persistence/postgres/eventJournal.d.ts +18 -0
  175. package/dist/infrastructure/persistence/postgres/eventJournal.js +76 -0
  176. package/dist/infrastructure/persistence/postgres/eventJournalSchema.d.ts +5 -0
  177. package/dist/infrastructure/persistence/postgres/eventJournalSchema.js +253 -0
  178. package/dist/infrastructure/persistence/postgres/eventPruneWatermarks.d.ts +24 -0
  179. package/dist/infrastructure/persistence/postgres/eventPruneWatermarks.js +111 -0
  180. package/dist/infrastructure/persistence/postgres/eventRetention.d.ts +18 -0
  181. package/dist/infrastructure/persistence/postgres/eventRetention.js +76 -0
  182. package/dist/infrastructure/persistence/postgres/events.d.ts +45 -0
  183. package/dist/infrastructure/persistence/postgres/events.js +249 -0
  184. package/dist/infrastructure/persistence/postgres/expiry.d.ts +3 -0
  185. package/dist/infrastructure/persistence/postgres/expiry.js +74 -0
  186. package/dist/infrastructure/persistence/postgres/flowFailures.d.ts +17 -0
  187. package/dist/infrastructure/persistence/postgres/flowFailures.js +199 -0
  188. package/dist/infrastructure/persistence/postgres/jobData.d.ts +3 -0
  189. package/dist/infrastructure/persistence/postgres/jobData.js +32 -0
  190. package/dist/infrastructure/persistence/postgres/leaseRelease.d.ts +9 -0
  191. package/dist/infrastructure/persistence/postgres/leaseRelease.js +120 -0
  192. package/dist/infrastructure/persistence/postgres/leaseRenewal.d.ts +4 -0
  193. package/dist/infrastructure/persistence/postgres/leaseRenewal.js +35 -0
  194. package/dist/infrastructure/persistence/postgres/lifetimeMetrics.d.ts +15 -0
  195. package/dist/infrastructure/persistence/postgres/lifetimeMetrics.js +46 -0
  196. package/dist/infrastructure/persistence/postgres/lifetimeMetricsFinalizer.d.ts +4 -0
  197. package/dist/infrastructure/persistence/postgres/lifetimeMetricsFinalizer.js +13 -0
  198. package/dist/infrastructure/persistence/postgres/logs.d.ts +6 -0
  199. package/dist/infrastructure/persistence/postgres/logs.js +70 -0
  200. package/dist/infrastructure/persistence/postgres/maintenance.d.ts +6 -0
  201. package/dist/infrastructure/persistence/postgres/maintenance.js +138 -0
  202. package/dist/infrastructure/persistence/postgres/maintenanceFlights.d.ts +14 -0
  203. package/dist/infrastructure/persistence/postgres/maintenanceFlights.js +66 -0
  204. package/dist/infrastructure/persistence/postgres/metricWrites.d.ts +9 -0
  205. package/dist/infrastructure/persistence/postgres/metricWrites.js +94 -0
  206. package/dist/infrastructure/persistence/postgres/mutations.d.ts +9 -0
  207. package/dist/infrastructure/persistence/postgres/mutations.js +171 -0
  208. package/dist/infrastructure/persistence/postgres/outcomes.d.ts +4 -0
  209. package/dist/infrastructure/persistence/postgres/outcomes.js +194 -0
  210. package/dist/infrastructure/persistence/postgres/policies.d.ts +11 -0
  211. package/dist/infrastructure/persistence/postgres/policies.js +18 -0
  212. package/dist/infrastructure/persistence/postgres/postCommitMaintenance.d.ts +17 -0
  213. package/dist/infrastructure/persistence/postgres/postCommitMaintenance.js +117 -0
  214. package/dist/infrastructure/persistence/postgres/promotion.d.ts +3 -0
  215. package/dist/infrastructure/persistence/postgres/promotion.js +43 -0
  216. package/dist/infrastructure/persistence/postgres/queries.d.ts +22 -0
  217. package/dist/infrastructure/persistence/postgres/queries.js +152 -0
  218. package/dist/infrastructure/persistence/postgres/queueDestruction.d.ts +3 -0
  219. package/dist/infrastructure/persistence/postgres/queueDestruction.js +154 -0
  220. package/dist/infrastructure/persistence/postgres/queueLifecycle.d.ts +10 -0
  221. package/dist/infrastructure/persistence/postgres/queueLifecycle.js +60 -0
  222. package/dist/infrastructure/persistence/postgres/rateLimit.d.ts +6 -0
  223. package/dist/infrastructure/persistence/postgres/rateLimit.js +10 -0
  224. package/dist/infrastructure/persistence/postgres/readModels.d.ts +32 -0
  225. package/dist/infrastructure/persistence/postgres/readModels.js +72 -0
  226. package/dist/infrastructure/persistence/postgres/recovery.d.ts +3 -0
  227. package/dist/infrastructure/persistence/postgres/recovery.js +178 -0
  228. package/dist/infrastructure/persistence/postgres/relationships.d.ts +9 -0
  229. package/dist/infrastructure/persistence/postgres/relationships.js +201 -0
  230. package/dist/infrastructure/persistence/postgres/removeUnprocessedChildren.d.ts +4 -0
  231. package/dist/infrastructure/persistence/postgres/removeUnprocessedChildren.js +241 -0
  232. package/dist/infrastructure/persistence/postgres/repeatMutations.d.ts +5 -0
  233. package/dist/infrastructure/persistence/postgres/repeatMutations.js +37 -0
  234. package/dist/infrastructure/persistence/postgres/runtime.d.ts +46 -0
  235. package/dist/infrastructure/persistence/postgres/runtime.js +236 -0
  236. package/dist/infrastructure/persistence/postgres/runtimeConfig.d.ts +21 -0
  237. package/dist/infrastructure/persistence/postgres/runtimeConfig.js +39 -0
  238. package/dist/infrastructure/persistence/postgres/schema.d.ts +4 -0
  239. package/dist/infrastructure/persistence/postgres/schema.js +257 -0
  240. package/dist/infrastructure/persistence/postgres/schemaInitialization.d.ts +3 -0
  241. package/dist/infrastructure/persistence/postgres/schemaInitialization.js +221 -0
  242. package/dist/infrastructure/persistence/postgres/serialAdmission.d.ts +6 -0
  243. package/dist/infrastructure/persistence/postgres/serialAdmission.js +96 -0
  244. package/dist/infrastructure/persistence/postgres/snapshotBudget.d.ts +3 -0
  245. package/dist/infrastructure/persistence/postgres/snapshotBudget.js +83 -0
  246. package/dist/infrastructure/persistence/postgres/storageHealth.d.ts +10 -0
  247. package/dist/infrastructure/persistence/postgres/storageHealth.js +29 -0
  248. package/dist/infrastructure/persistence/postgres/store.d.ts +90 -0
  249. package/dist/infrastructure/persistence/postgres/store.js +136 -0
  250. package/dist/infrastructure/persistence/postgres/telemetry.d.ts +6 -0
  251. package/dist/infrastructure/persistence/postgres/telemetry.js +88 -0
  252. package/dist/infrastructure/persistence/postgres/terminal.d.ts +8 -0
  253. package/dist/infrastructure/persistence/postgres/terminal.js +137 -0
  254. package/dist/infrastructure/persistence/postgres/transactionRetry.d.ts +6 -0
  255. package/dist/infrastructure/persistence/postgres/transactionRetry.js +42 -0
  256. package/dist/infrastructure/persistence/postgres/types.d.ts +175 -0
  257. package/dist/infrastructure/persistence/postgres/types.js +1 -0
  258. package/dist/infrastructure/persistence/postgres/workers.d.ts +15 -0
  259. package/dist/infrastructure/persistence/postgres/workers.js +126 -0
  260. package/dist/infrastructure/persistence/postgres.d.ts +2 -0
  261. package/dist/infrastructure/persistence/postgres.js +1 -0
  262. package/dist/infrastructure/persistence/sqlite/jobs.js +2 -0
  263. package/dist/infrastructure/scheduler/cron/persisted.d.ts +5 -0
  264. package/dist/infrastructure/scheduler/cron/persisted.js +27 -0
  265. package/dist/infrastructure/scheduler/cron/runtime.js +5 -10
  266. package/dist/infrastructure/scheduler/cron/validation.d.ts +3 -0
  267. package/dist/infrastructure/scheduler/cron/validation.js +11 -0
  268. package/dist/infrastructure/scheduler/cronParser.d.ts +1 -1
  269. package/dist/infrastructure/scheduler/cronParser.js +144 -11
  270. package/dist/infrastructure/server/bootstrap.d.ts +2 -3
  271. package/dist/infrastructure/server/bootstrap.js +33 -59
  272. package/dist/infrastructure/server/errors.d.ts +4 -0
  273. package/dist/infrastructure/server/errors.js +54 -0
  274. package/dist/infrastructure/server/handler-routes/control.d.ts +5 -5
  275. package/dist/infrastructure/server/handler-routes/control.js +3 -1
  276. package/dist/infrastructure/server/handler-routes/monitoring.d.ts +2 -2
  277. package/dist/infrastructure/server/handler.js +9 -11
  278. package/dist/infrastructure/server/handlers/advanced/dependencies.js +7 -6
  279. package/dist/infrastructure/server/handlers/advanced/jobs.js +19 -6
  280. package/dist/infrastructure/server/handlers/advanced/queue.d.ts +9 -9
  281. package/dist/infrastructure/server/handlers/advanced/queue.js +91 -27
  282. package/dist/infrastructure/server/handlers/core.js +32 -31
  283. package/dist/infrastructure/server/handlers/cron.d.ts +4 -4
  284. package/dist/infrastructure/server/handlers/cron.js +60 -59
  285. package/dist/infrastructure/server/handlers/dashboard.d.ts +1 -1
  286. package/dist/infrastructure/server/handlers/dashboard.js +21 -8
  287. package/dist/infrastructure/server/handlers/dlq.d.ts +6 -3
  288. package/dist/infrastructure/server/handlers/dlq.js +35 -9
  289. package/dist/infrastructure/server/handlers/flow.js +2 -1
  290. package/dist/infrastructure/server/handlers/management.d.ts +5 -5
  291. package/dist/infrastructure/server/handlers/management.js +36 -9
  292. package/dist/infrastructure/server/handlers/monitoring/health.d.ts +5 -5
  293. package/dist/infrastructure/server/handlers/monitoring/health.js +45 -16
  294. package/dist/infrastructure/server/handlers/monitoring/operations.d.ts +1 -1
  295. package/dist/infrastructure/server/handlers/monitoring/operations.js +8 -1
  296. package/dist/infrastructure/server/handlers/monitoring/workers.d.ts +3 -3
  297. package/dist/infrastructure/server/handlers/monitoring/workers.js +31 -7
  298. package/dist/infrastructure/server/handlers/pushBatchValidation.d.ts +3 -1
  299. package/dist/infrastructure/server/handlers/pushBatchValidation.js +52 -7
  300. package/dist/infrastructure/server/handlers/query.d.ts +1 -1
  301. package/dist/infrastructure/server/handlers/query.js +5 -4
  302. package/dist/infrastructure/server/http.js +2 -2
  303. package/dist/infrastructure/server/httpDashboardEndpoints.d.ts +1 -1
  304. package/dist/infrastructure/server/httpDashboardEndpoints.js +19 -6
  305. package/dist/infrastructure/server/httpEndpoints.js +18 -7
  306. package/dist/infrastructure/server/httpRouteQueues.js +11 -1
  307. package/dist/infrastructure/server/shutdownCoordinator.d.ts +24 -0
  308. package/dist/infrastructure/server/shutdownCoordinator.js +106 -0
  309. package/dist/infrastructure/server/sseHandler.js +8 -3
  310. package/dist/infrastructure/server/storageAdapter.d.ts +29 -0
  311. package/dist/infrastructure/server/storageAdapter.js +61 -0
  312. package/dist/infrastructure/server/storageManager.d.ts +12 -0
  313. package/dist/infrastructure/server/storageManager.js +102 -0
  314. package/dist/infrastructure/server/tcp.js +2 -3
  315. package/dist/infrastructure/server/ws/snapshots.d.ts +1 -1
  316. package/dist/infrastructure/server/ws/snapshots.js +19 -4
  317. package/dist/infrastructure/server/wsHandler.js +10 -5
  318. package/dist/main.js +1 -1
  319. package/dist/mcp/backend/embedded/services.js +2 -1
  320. package/dist/mcp/index.d.ts +2 -2
  321. package/dist/mcp/index.js +2 -2
  322. package/dist/shared/storageHealth.d.ts +9 -0
  323. package/dist/shared/storageHealth.js +12 -0
  324. package/package.json +74 -65
package/README.md CHANGED
@@ -13,8 +13,8 @@
13
13
  </p>
14
14
 
15
15
  <p align="center">
16
- High-performance job queue for Bun. Built for AI agents and automation.<br/>
17
- Zero external infrastructure. MCP-native. TypeScript-first.
16
+ High-performance job queue for Bun. Memory or one-file SQLite; PostgreSQL 15–18 multi-broker when you scale.<br/>
17
+ DLQ, cron, SQLite S3 backups, and native MCP. Built for AI agents and automation. No Redis.
18
18
  </p>
19
19
 
20
20
  <p align="center">
@@ -48,14 +48,13 @@ await app.add('send', { to: 'alice@example.com' });
48
48
  ```
49
49
 
50
50
  That's it. Queue + Worker in one object, persisted to a single SQLite file.
51
- No Redis, no config, no setup. The install is 5.5 MB, 7 packages, 2 runtime
52
- dependencies (`croner` + `msgpackr`) — SQLite, S3, HTTP and WebSocket are Bun
53
- built-ins.
51
+ No Redis, no config, no setup. `msgpackr` is the only runtime dependency;
52
+ cron, SQLite, S3, HTTP and WebSocket use Bun's built-ins.
54
53
 
55
54
  ### Not on Bun? Run the server, connect from anywhere
56
55
 
57
- The queue also runs as a standalone server one command, nothing else to
58
- operate:
56
+ The queue also runs as a standalone server. Memory is the zero-configuration
57
+ default; SQLite is the zero-infrastructure persistent option:
59
58
 
60
59
  ```bash
61
60
  # in-memory without --data-path; pass it to persist jobs to SQLite
@@ -67,8 +66,24 @@ docker run -d -p 6789:6789 -p 6790:6790 \
67
66
  ghcr.io/egeominotti/bunqueue:latest
68
67
  ```
69
68
 
69
+ For multiple active brokers, the repository includes a topology pinned to
70
+ PostgreSQL 18.6:
71
+
72
+ ```bash
73
+ POSTGRES_PASSWORD='replace-me' \
74
+ BUNQUEUE_POSTGRES_URL='postgres://bunqueue:replace-me@postgres:5432/bunqueue' \
75
+ docker compose -f docker-compose.postgres.yml up --build -d
76
+ ```
77
+
78
+ It starts two brokers against one database/namespace. PostgreSQL is server-only;
79
+ embedded mode keeps using memory/SQLite. Supply both Compose values when the
80
+ password changes, percent-encoding reserved characters in the URL only. MySQL
81
+ is not supported. CI validates PostgreSQL 15, 16, 17, and the pinned/recommended
82
+ 18.6 release. See the
83
+ [storage guide](https://bunqueue.dev/guide/databases/).
84
+
70
85
  Release images also carry the exact package version. For reproducible
71
- deployments, pin `ghcr.io/egeominotti/bunqueue:2.8.60`; `latest` points to the
86
+ deployments, pin `ghcr.io/egeominotti/bunqueue:2.9.0`; `latest` points to the
72
87
  same multi-arch image at release time.
73
88
 
74
89
  Then produce and process from the language you already use:
@@ -80,7 +95,7 @@ npm install bunqueue-client # Node.js ≥ 20, Deno ≥ 2, Bun, Cloudflare Wor
80
95
  ```typescript
81
96
  import { Queue, Worker } from 'bunqueue-client';
82
97
 
83
- const queue = new Queue('emails'); // localhost:6789 by default
98
+ const queue = new Queue('emails'); // localhost:6789 by default
84
99
  await queue.add('welcome', { to: 'user@example.com' });
85
100
 
86
101
  new Worker('emails', async (job) => ({ sent: true }), { concurrency: 10 });
@@ -96,14 +111,18 @@ Python, PHP, Go, Rust and Elixir clients speak the same protocol — see
96
111
 
97
112
  ## Why bunqueue?
98
113
 
99
- | Library | Requires | AI-native |
100
- | ------------ | ----------- | --------- |
101
- | BullMQ | Redis | No |
102
- | Agenda | MongoDB | No |
103
- | pg-boss | PostgreSQL | No |
104
- | **bunqueue** | **Nothing** | **Yes** |
105
-
106
- - **Zero external infrastructure** — one process, one SQLite file. `cp` to back up
114
+ | Library | Requires | AI-native |
115
+ | ------------ | ------------------------------------------ | --------- |
116
+ | BullMQ | Redis | No |
117
+ | Agenda | MongoDB | No |
118
+ | pg-boss | PostgreSQL | No |
119
+ | **bunqueue** | **Nothing (memory/SQLite) · PostgreSQL optional** | **Yes** |
120
+
121
+ - **Zero external infrastructure by default** — memory by default; one SQLite
122
+ file when local persistence is configured
123
+ - **PostgreSQL 15–18 multi-broker mode** — PostgreSQL 18.6 is recommended;
124
+ database-authoritative claims, fenced
125
+ leases, shared limits, cron, workers, job-state/lifecycle metrics, and failover state
107
126
  - **BullMQ-compatible API** — same `Queue`, `Worker`, `QueueEvents`; [migrating takes minutes](https://bunqueue.dev/guide/migration/)
108
127
  - **MCP server included** — 73 tools; AI agents get full queue control out of the box
109
128
  - **Everything server-side** — retries with backoff, priorities, cron, rate limits, dead letter queue
@@ -111,24 +130,24 @@ Python, PHP, Go, Rust and Elixir clients speak the same protocol — see
111
130
  in-memory batch push, 186K jobs/sec public on-disk Embedded `addBulk`, and
112
131
  159K jobs/sec TCP `PUSHB`; [methodology and distributions](https://bunqueue.dev/guide/benchmarks/)
113
132
 
114
- **Great for:** single-server deployments, AI agents that need a scheduler,
115
- prototypes and MVPs, embedded use cases (CLI tools, edge, serverless), teams
133
+ **Great for:** embedded and single-server deployments, PostgreSQL-backed broker
134
+ fleets, AI agents that need a scheduler, edge/serverless spooling, and teams
116
135
  that don't want to operate Redis.
117
136
 
118
- **Not ideal for:** multi-region distributed systems requiring HA or automatic
119
- failover today. If you already run Redis and BullMQ works for you, keep it.
137
+ **Not ideal for:** multi-region consensus or deployments that require MySQL as
138
+ the queue store. If you already run Redis and BullMQ works for you, keep it.
120
139
 
121
140
  [When to choose bunqueue →](https://bunqueue.dev/guide/comparison/)
122
141
 
123
142
  ## Two Modes
124
143
 
125
- | | Embedded | Server (TCP) |
126
- | ---------------- | ------------------------------------- | -------------------------------------------- |
127
- | **How it works** | Queue runs inside your process | Standalone server, clients connect via TCP |
128
- | **Setup** | `bun add bunqueue` | `docker run` or `bunqueue start` |
129
- | **Performance** | 186K jobs/sec on-disk `addBulk`; 729K internal in-memory batch | 159K jobs/sec TCP `PUSHB`; 17K jobs/sec worker drain |
130
- | **Best for** | Single-process apps, CLIs, serverless | Multiple workers, separate producer/consumer |
131
- | **Scaling** | Same process only | Multiple clients across machines |
144
+ | | Embedded | Server (TCP) |
145
+ | ---------------- | -------------------------------------------------------------- | -------------------------------------------------------- |
146
+ | **How it works** | Queue runs inside your process | Standalone server, clients connect via TCP |
147
+ | **Setup** | `bun add bunqueue` | `docker run` or `bunqueue start` |
148
+ | **Performance** | 186K jobs/sec on-disk `addBulk`; 729K internal in-memory batch | SQLite: 159K TCP `PUSHB`; 17K jobs/sec worker drain |
149
+ | **Best for** | Single-process apps, CLIs, serverless | Multiple workers, separate producer/consumer |
150
+ | **Scaling** | Same process only | Multiple clients; multiple brokers with PostgreSQL 15–18 |
132
151
 
133
152
  ### Embedded
134
153
 
@@ -180,18 +199,20 @@ await queue.add('process', { data: 'hello' });
180
199
 
181
200
  ## One Queue, Any Language (SDKs)
182
201
 
183
- The server does all the heavy lifting. Official client SDKs speak the native
184
- TCP protocol with full feature parity, so producers and workers can live
185
- anywhere in your stack — add a job from TypeScript, process it from Python:
186
-
187
- | Where your code runs | Install |
188
- | -------------------- | ------- |
189
- | **Node.js 20, Deno ≥ 2, Bun, Cloudflare Workers** | [`npm install bunqueue-client`](https://www.npmjs.com/package/bunqueue-client) |
190
- | **Python ≥ 3.9** | [`pip install bunqueue-client`](https://pypi.org/project/bunqueue-client/) |
191
- | **PHP8.1** | [`composer require bunqueue/client`](https://packagist.org/packages/bunqueue/client) |
192
- | **Go1.26.5** | `go get github.com/egeominotti/bunqueue/sdk/go` |
193
- | **Rust ≥ 1.85** | [`cargo add bunqueue-client`](https://crates.io/crates/bunqueue-client) |
194
- | **Elixir ≥ 1.15** | Hex coming soon — today: use [`sdk/elixir`](./sdk/elixir) as a path dependency |
202
+ The server does all the heavy lifting. Official client SDKs share the
203
+ protocol-conformant core Queue, Worker, and Flow surface, so producers and
204
+ workers can live anywhere in your stack — add a job from TypeScript, process it
205
+ from Python. Language-specific capabilities are tracked in the
206
+ [SDK guide](https://bunqueue.dev/guide/sdks/).
207
+
208
+ | Where your code runs | Install |
209
+ | --------------------------------------------------- | -------------------------------------------------------------------------------------- |
210
+ | **Node.js20, Deno ≥ 2, Bun, Cloudflare Workers** | [`npm install bunqueue-client`](https://www.npmjs.com/package/bunqueue-client) |
211
+ | **Python3.9** | [`pip install bunqueue-client`](https://pypi.org/project/bunqueue-client/) |
212
+ | **PHP8.1** | [`composer require bunqueue/client`](https://packagist.org/packages/bunqueue/client) |
213
+ | **Go ≥ 1.26.5** | `go get github.com/egeominotti/bunqueue/sdk/go` |
214
+ | **Rust ≥ 1.85** | [`cargo add bunqueue-client`](https://crates.io/crates/bunqueue-client) |
215
+ | **Elixir ≥ 1.15** | Hex coming soon — today: use [`sdk/elixir`](./sdk/elixir) as a path dependency |
195
216
 
196
217
  ```typescript
197
218
  // Node.js / Deno / Cloudflare Workers
@@ -237,21 +258,24 @@ const root = await flows.add({
237
258
  ],
238
259
  });
239
260
 
240
- console.log(root.job.id, root.children?.map(({ job }) => job.id));
261
+ console.log(
262
+ root.job.id,
263
+ root.children?.map(({ job }) => job.id)
264
+ );
241
265
  await flows.close();
242
266
  ```
243
267
 
244
268
  The repository records the contracts and the test strategy beside each
245
269
  implementation:
246
270
 
247
- | SDK | Runtime invariants | Generated tests | Mutation engine |
248
- | --- | --- | --- | --- |
249
- | [TypeScript](./sdk/typescript/README.md) | [contract](./sdk/typescript/INVARIANTS.md) | fast-check | none¹ |
250
- | [Python](./sdk/python/README.md) | [contract](./sdk/python/INVARIANTS.md) | Hypothesis | mutmut |
251
- | [PHP](./sdk/php/README.md) | [contract](./sdk/php/INVARIANTS.md) | Eris | Infection |
252
- | [Go](./sdk/go/README.md) | [contract](./sdk/go/INVARIANTS.md) | Rapid | Gremlins |
253
- | [Rust](./sdk/rust/README.md) | [contract](./sdk/rust/INVARIANTS.md) | proptest | cargo-mutants |
254
- | [Elixir](./sdk/elixir/README.md) | [contract](./sdk/elixir/INVARIANTS.md) | StreamData | Muex |
271
+ | SDK | Runtime invariants | Generated tests | Mutation engine |
272
+ | ---------------------------------------- | ------------------------------------------ | --------------- | --------------- |
273
+ | [TypeScript](./sdk/typescript/README.md) | [contract](./sdk/typescript/INVARIANTS.md) | fast-check | none¹ |
274
+ | [Python](./sdk/python/README.md) | [contract](./sdk/python/INVARIANTS.md) | Hypothesis | mutmut |
275
+ | [PHP](./sdk/php/README.md) | [contract](./sdk/php/INVARIANTS.md) | Eris | Infection |
276
+ | [Go](./sdk/go/README.md) | [contract](./sdk/go/INVARIANTS.md) | Rapid | Gremlins |
277
+ | [Rust](./sdk/rust/README.md) | [contract](./sdk/rust/INVARIANTS.md) | proptest | cargo-mutants |
278
+ | [Elixir](./sdk/elixir/README.md) | [contract](./sdk/elixir/INVARIANTS.md) | StreamData | Muex |
255
279
 
256
280
  ¹ The TypeScript SDK has no mutation engine. StrykerJS was removed because its
257
281
  dependency graph produced every advisory the weekly audit had to answer for,
@@ -314,17 +338,25 @@ human-in-the-loop signals — built on bunqueue, no new infrastructure:
314
338
  import { Workflow, Engine } from 'bunqueue/workflow';
315
339
 
316
340
  const orderFlow = new Workflow('order-pipeline')
317
- .step('reserve-stock', async () => {
318
- await inventory.reserve();
319
- return { reserved: true };
320
- }, {
321
- compensate: async () => await inventory.release(), // auto-rollback on failure
322
- })
323
- .step('charge', async () => {
324
- return { txId: await payments.charge() };
325
- }, {
326
- compensate: async () => await payments.refund(),
327
- })
341
+ .step(
342
+ 'reserve-stock',
343
+ async () => {
344
+ await inventory.reserve();
345
+ return { reserved: true };
346
+ },
347
+ {
348
+ compensate: async () => await inventory.release(), // auto-rollback on failure
349
+ }
350
+ )
351
+ .step(
352
+ 'charge',
353
+ async () => {
354
+ return { txId: await payments.charge() };
355
+ },
356
+ {
357
+ compensate: async () => await payments.refund(),
358
+ }
359
+ )
328
360
  .waitFor('manager-approval', { timeout: 86_400_000 }) // human-in-the-loop
329
361
  .step('confirm', async (ctx) => {
330
362
  return { txId: (ctx.steps['charge'] as { txId: string }).txId };
@@ -336,13 +368,13 @@ const run = await engine.start('order-pipeline', { orderId: 'ORD-1' });
336
368
  await engine.signal(run.id, 'manager-approval', { approved: true });
337
369
  ```
338
370
 
339
- | | **bunqueue** | **Temporal** | **Inngest** | **Trigger.dev** |
340
- |---|---|---|---|---|
341
- | **Infrastructure** | None (embedded) | PostgreSQL + 7 services | Cloud-only | Redis + PostgreSQL |
342
- | **Saga compensation** | Built-in | Manual | Manual | Manual |
343
- | **Human-in-the-loop** | `.waitFor()` | Signals API | `step.waitForEvent()` | Waitpoint tokens |
344
- | **Self-hosted** | Zero-config | Complex | No | Complex |
345
- | **Pricing** | Free (MIT) | Free / Cloud $$ | Per-execution | Free tier, then $50/mo+ |
371
+ | | **bunqueue** | **Temporal** | **Inngest** | **Trigger.dev** |
372
+ | --------------------- | --------------- | ----------------------- | --------------------- | ----------------------- |
373
+ | **Infrastructure** | None (embedded) | PostgreSQL + 7 services | Cloud-only | Redis + PostgreSQL |
374
+ | **Saga compensation** | Built-in | Manual | Manual | Manual |
375
+ | **Human-in-the-loop** | `.waitFor()` | Signals API | `step.waitForEvent()` | Waitpoint tokens |
376
+ | **Self-hosted** | Zero-config | Complex | No | Complex |
377
+ | **Pricing** | Free (MIT) | Free / Cloud $$ | Per-execution | Free tier, then $50/mo+ |
346
378
 
347
379
  Also included: nested workflows, `doUntil`/`doWhile` loops, `forEach` over
348
380
  dynamic lists, schema validation (Zod, ArkType, Valibot or any `.parse()`),
@@ -374,8 +406,8 @@ claude mcp add bunqueue -- bunx bunqueue-mcp
374
406
  }
375
407
  ```
376
408
 
377
- Then just ask: *"Schedule a cleanup job every day at 3 AM"* · *"Show me all
378
- failed jobs and retry them"* · *"Set rate limit to 50/sec on api-calls"*.
409
+ Then just ask: _"Schedule a cleanup job every day at 3 AM"_ · _"Show me all
410
+ failed jobs and retry them"_ · _"Set rate limit to 50/sec on api-calls"_.
379
411
 
380
412
  [MCP guide →](https://bunqueue.dev/guide/mcp/)
381
413
 
@@ -399,13 +431,13 @@ https://github.com/user-attachments/assets/e8a8d38e-b4a6-4dc8-8360-876c0f24d116
399
431
 
400
432
  Native Ryzen 9 9950X3D, Bun 1.3.14; medians from repeated fresh processes:
401
433
 
402
- | Workload | Mode | Median | Persistence |
403
- | --- | --- | ---: | --- |
404
- | Internal batched push, 1M jobs | Embedded | 729,395 jobs/sec | In-memory, no `dataPath` |
405
- | Public sustained `addBulk`, 50K cell | Embedded | 186,384 jobs/sec | On-disk buffered SQLite |
406
- | `PUSHB`, fresh 50K sample | TCP | 158,779 jobs/sec | On-disk buffered SQLite |
407
- | No-work worker drain, concurrency 50 | TCP | 17,256 jobs/sec | Full pull/process/ACK |
408
- | Linear Workflow Engine | Embedded / TCP | 2,700 / 3,187 workflows/sec | Workflow SQLite + 3 queue nodes |
434
+ | Workload | Mode | Median | Persistence |
435
+ | ------------------------------------ | -------------- | --------------------------: | ------------------------------- |
436
+ | Internal batched push, 1M jobs | Embedded | 729,395 jobs/sec | In-memory, no `dataPath` |
437
+ | Public sustained `addBulk`, 50K cell | Embedded | 186,384 jobs/sec | On-disk buffered SQLite |
438
+ | `PUSHB`, fresh 50K sample | TCP | 158,779 jobs/sec | On-disk buffered SQLite |
439
+ | No-work worker drain, concurrency 50 | TCP | 17,256 jobs/sec | Full pull/process/ACK |
440
+ | Linear Workflow Engine | Embedded / TCP | 2,700 / 3,187 workflows/sec | Workflow SQLite + 3 queue nodes |
409
441
 
410
442
  These operations do different work; the internal in-memory result is not an
411
443
  SQLite or public-API claim. Run `bun run bench`, `bun run bench:tcp`, or
@@ -5,6 +5,8 @@ export function performDlqMaintenance(ctx) {
5
5
  shards: ctx.shards,
6
6
  jobIndex: ctx.jobIndex,
7
7
  jobResults: ctx.jobResults,
8
+ dependencyResults: ctx.dependencyResults,
9
+ customIdMap: ctx.customIdMap,
8
10
  jobLogs: ctx.jobLogs,
9
11
  storage: ctx.storage,
10
12
  };
@@ -161,6 +161,7 @@ export class ContextFactory {
161
161
  jobLocks: this.deps.jobLocks,
162
162
  clientJobs: this.deps.clientJobs,
163
163
  dependencyResults: this.deps.dependencyResults,
164
+ customIdMap: this.deps.customIdMap,
164
165
  depCompletions: this.deps.depCompletions,
165
166
  maxDependencyCompletions: this.deps.config.maxCompletedJobs,
166
167
  webhookManager: this.deps.webhookManager,
@@ -188,6 +189,8 @@ export class ContextFactory {
188
189
  shards: this.deps.shards,
189
190
  jobIndex: this.deps.jobIndex,
190
191
  jobResults: this.deps.jobResults,
192
+ dependencyResults: this.deps.dependencyResults,
193
+ customIdMap: this.deps.customIdMap,
191
194
  jobLogs: this.deps.jobLogs,
192
195
  storage: this.deps.storage,
193
196
  };
@@ -201,6 +204,8 @@ export class ContextFactory {
201
204
  completedJobs: this.deps.completedJobs,
202
205
  completedJobsData: this.deps.completedJobsData,
203
206
  jobResults: this.deps.jobResults,
207
+ dependencyResults: this.deps.dependencyResults,
208
+ customIdMap: this.deps.customIdMap,
204
209
  };
205
210
  }
206
211
  getLogsContext() {
@@ -14,6 +14,14 @@ export interface DlqContext {
14
14
  jobResults: {
15
15
  delete(id: JobId): boolean;
16
16
  };
17
+ dependencyResults: {
18
+ releaseConsumer(id: JobId): void;
19
+ };
20
+ customIdMap: {
21
+ get(id: string): JobId | undefined;
22
+ set(id: string, jobId: JobId): void;
23
+ delete(id: string): boolean;
24
+ };
17
25
  jobLogs: {
18
26
  delete(id: JobId): boolean;
19
27
  };
@@ -30,6 +38,8 @@ export declare function getDlqStats(queue: string, ctx: DlqContext): DlqStats;
30
38
  export declare function purgeExpiredDlq(queue: string, ctx: DlqContext): number;
31
39
  /** Purge all jobs from DLQ */
32
40
  export declare function purgeDlqJobs(queue: string, ctx: DlqContext): number;
41
+ /** Remove one DLQ entry without re-enqueuing it. */
42
+ export declare function removeDlqJob(queue: string, id: JobId, ctx: DlqContext): boolean;
33
43
  /** Configure DLQ for a queue */
34
44
  export declare function configureDlq(queue: string, ctx: DlqContext, config: Partial<DlqConfig>): void;
35
45
  /** Get DLQ configuration */
@@ -83,6 +83,40 @@ export function purgeDlqJobs(queue, ctx) {
83
83
  shard.clearDlq(queue);
84
84
  return entries.length;
85
85
  }
86
+ /** Remove one DLQ entry without re-enqueuing it. */
87
+ export function removeDlqJob(queue, id, ctx) {
88
+ const shard = ctx.shards[shardIndex(queue)];
89
+ const entries = shard.getDlqEntries(queue).filter((entry) => entry.job.id === id);
90
+ if (entries.length === 0)
91
+ return false;
92
+ const location = ctx.jobIndex.get(id);
93
+ const terminalJobIds = location === undefined || (location.type === 'dlq' && location.queueName === queue) ? [id] : [];
94
+ // Keep the in-memory entry visible until the durable transaction succeeds.
95
+ // This prevents a failed SQLite write from leaving memory and disk out of sync.
96
+ ctx.storage?.purgeDlqEntries(queue, [id], terminalJobIds, false);
97
+ shard.removeAllFromDlq(queue, id);
98
+ cleanupTerminalEntries(queue, entries, terminalJobIds, ctx);
99
+ return true;
100
+ }
101
+ function cleanupTerminalEntries(queue, entries, terminalJobIds, ctx) {
102
+ const terminalIds = new Set(terminalJobIds);
103
+ for (const entry of entries) {
104
+ if (!terminalIds.has(entry.job.id))
105
+ continue;
106
+ const customId = entry.job.customId;
107
+ if (customId && ctx.customIdMap.get(customId) === entry.job.id) {
108
+ ctx.customIdMap.delete(customId);
109
+ }
110
+ }
111
+ for (const jobId of terminalJobIds) {
112
+ const location = ctx.jobIndex.get(jobId);
113
+ if (location?.type === 'dlq' && location.queueName === queue)
114
+ ctx.jobIndex.delete(jobId);
115
+ ctx.jobResults.delete(jobId);
116
+ ctx.jobLogs.delete(jobId);
117
+ ctx.dependencyResults.releaseConsumer(jobId);
118
+ }
119
+ }
86
120
  /** Remove every durable and global reference owned by terminal DLQ entries. */
87
121
  function cleanupPurgedEntries(queue, entries, ctx, clearQueue) {
88
122
  const jobIds = [...new Set(entries.map((entry) => entry.job.id))];
@@ -95,14 +129,7 @@ function cleanupPurgedEntries(queue, entries, ctx, clearQueue) {
95
129
  // A newer live generation can reuse the same custom id; its jobs row and
96
130
  // auxiliary state must survive cleanup of the stale terminal generation.
97
131
  ctx.storage?.purgeDlqEntries(queue, jobIds, terminalJobIds, clearQueue);
98
- for (const jobId of terminalJobIds) {
99
- const location = ctx.jobIndex.get(jobId);
100
- if (location?.type === 'dlq' && location.queueName === queue) {
101
- ctx.jobIndex.delete(jobId);
102
- }
103
- ctx.jobResults.delete(jobId);
104
- ctx.jobLogs.delete(jobId);
105
- }
132
+ cleanupTerminalEntries(queue, entries, terminalJobIds, ctx);
106
133
  }
107
134
  /** Configure DLQ for a queue */
108
135
  export function configureDlq(queue, ctx, config) {
@@ -145,6 +172,13 @@ export function retryCompletedJobs(queue, ctx, jobId, options = {}) {
145
172
  }
146
173
  /** Requeue a single completed job */
147
174
  function requeueCompletedJob(job, ctx) {
175
+ const currentLocation = ctx.jobIndex.get(job.id);
176
+ if (currentLocation?.type !== 'completed' || currentLocation.queueName !== job.queue)
177
+ return 0;
178
+ const shard = ctx.shards[shardIndex(job.queue)];
179
+ const keyOwner = job.uniqueKey ? shard.getUniqueKeyEntry(job.queue, job.uniqueKey) : null;
180
+ if (keyOwner && keyOwner.jobId !== job.id)
181
+ return 0;
148
182
  const now = Date.now();
149
183
  const timeline = [...job.timeline];
150
184
  if (timeline.length < MAX_TIMELINE_ENTRIES) {
@@ -166,7 +200,6 @@ function requeueCompletedJob(job, ctx) {
166
200
  // A SQLite failure therefore leaves the completed generation authoritative.
167
201
  ctx.storage?.requeueCompletedJob(requeued);
168
202
  const idx = shardIndex(requeued.queue);
169
- const shard = ctx.shards[idx];
170
203
  shard.getQueue(requeued.queue).push(requeued);
171
204
  shard.incrementQueued(requeued.id, false, requeued.createdAt, requeued.queue, requeued.runAt);
172
205
  ctx.jobIndex.set(requeued.id, {
@@ -177,6 +210,11 @@ function requeueCompletedJob(job, ctx) {
177
210
  ctx.completedJobs.delete(requeued.id);
178
211
  ctx.completedJobsData.delete(requeued.id);
179
212
  ctx.jobResults.delete(requeued.id);
213
+ if (requeued.customId)
214
+ ctx.customIdMap.set(requeued.customId, requeued.id);
215
+ if (requeued.uniqueKey) {
216
+ shard.registerUniqueKeyWithTtl(requeued.queue, requeued.uniqueKey, requeued.id, requeued.deduplicationTtl ?? undefined);
217
+ }
180
218
  shard.notify(requeued.queue);
181
219
  return 1;
182
220
  }
@@ -3,33 +3,62 @@ import { MAX_TIMELINE_ENTRIES } from '../domain/types/job';
3
3
  import { retainDlqRetryState, scheduleNextRetry, setDlqRetryState } from '../domain/types/dlq';
4
4
  import { shardIndex } from '../shared/hash';
5
5
  function prepareRetry(job, now) {
6
- job.attempts = 0;
7
- job.runAt = now;
8
- job.startedAt = null;
9
- job.stallCount = 0;
10
- job.lastHeartbeat = now;
11
- if (job.timeline.length < MAX_TIMELINE_ENTRIES) {
12
- job.timeline.push({ state: 'waiting', timestamp: now });
13
- }
6
+ const timeline = [...job.timeline];
7
+ if (timeline.length < MAX_TIMELINE_ENTRIES)
8
+ timeline.push({ state: 'waiting', timestamp: now });
9
+ return {
10
+ ...job,
11
+ attempts: 0,
12
+ lastHeartbeat: now,
13
+ runAt: now,
14
+ stallCount: 0,
15
+ startedAt: null,
16
+ timeline,
17
+ };
18
+ }
19
+ function canRetry(queue, job, ctx) {
20
+ const idx = shardIndex(queue);
21
+ const shard = ctx.shards[idx];
22
+ const location = ctx.jobIndex.get(job.id);
23
+ if (location?.type !== 'dlq' || location.queueName !== queue)
24
+ return false;
25
+ const customOwner = job.customId ? ctx.customIdMap.get(job.customId) : undefined;
26
+ if (customOwner && customOwner !== job.id)
27
+ return false;
28
+ const keyOwner = job.uniqueKey ? shard.getUniqueKeyEntry(queue, job.uniqueKey) : null;
29
+ return keyOwner === null || keyOwner.jobId === job.id;
14
30
  }
15
- function enqueueRetry(queue, job, now, ctx) {
31
+ function publishRetry(queue, job, ctx) {
16
32
  const idx = shardIndex(queue);
17
33
  const shard = ctx.shards[idx];
18
- prepareRetry(job, now);
19
34
  shard.getQueue(queue).push(job);
20
- shard.incrementQueued(job.id, job.runAt > now, job.createdAt, queue, job.runAt);
35
+ shard.incrementQueued(job.id, false, job.createdAt, queue, job.runAt);
21
36
  ctx.jobIndex.set(job.id, { type: 'queue', shardIdx: idx, queueName: queue });
37
+ if (job.customId)
38
+ ctx.customIdMap.set(job.customId, job.id);
39
+ if (job.uniqueKey) {
40
+ shard.registerUniqueKeyWithTtl(queue, job.uniqueKey, job.id, job.deduplicationTtl ?? undefined);
41
+ }
42
+ }
43
+ function retryEntry(queue, entry, ctx, now, prepareState) {
44
+ if (!canRetry(queue, entry.job, ctx))
45
+ return null;
46
+ const job = prepareRetry(entry.job, now);
47
+ prepareState(job);
22
48
  ctx.storage?.requeueDlqJob(job);
49
+ const removed = ctx.shards[shardIndex(queue)].removeFromDlq(queue, entry.job.id);
50
+ if (!removed)
51
+ throw new Error('DLQ entry disappeared after its retry committed');
52
+ publishRetry(queue, job, ctx);
53
+ return job;
23
54
  }
24
55
  /** Retry a single job from DLQ and reset the automatic retry generation. */
25
56
  export function retryDlqJob(queue, jobId, ctx) {
26
57
  const shard = ctx.shards[shardIndex(queue)];
27
- const entry = shard.removeFromDlq(queue, jobId);
58
+ const entry = shard.getDlqEntries(queue).find((candidate) => candidate.job.id === jobId);
28
59
  if (!entry)
29
60
  return null;
30
- setDlqRetryState(entry.job, null);
31
- enqueueRetry(queue, entry.job, Date.now(), ctx);
32
- return entry.job;
61
+ return retryEntry(queue, entry, ctx, Date.now(), (job) => setDlqRetryState(job, null));
33
62
  }
34
63
  /** Retry jobs from DLQ, optionally bounded to the oldest `limit` entries. */
35
64
  export function retryDlqJobs(queue, ctx, jobId, limit) {
@@ -49,29 +78,21 @@ export function retryDlqJobs(queue, ctx, jobId, limit) {
49
78
  }
50
79
  return retried;
51
80
  }
52
- const entries = [...shard.getDlqEntries(queue)];
53
- shard.clearDlq(queue);
54
- ctx.storage?.clearDlqQueue(queue);
55
- const now = Date.now();
56
- for (const entry of entries) {
57
- setDlqRetryState(entry.job, null);
58
- enqueueRetry(queue, entry.job, now, ctx);
59
- }
60
- return entries.length;
81
+ const ids = shard.getDlqEntries(queue).map((entry) => entry.job.id);
82
+ let retried = 0;
83
+ for (const id of ids)
84
+ if (retryDlqJob(queue, id, ctx))
85
+ retried++;
86
+ return retried;
61
87
  }
62
88
  /** Retry all DLQ entries selected by a metadata filter. */
63
89
  export function retryDlqByFilter(queue, ctx, filter) {
64
90
  const shard = ctx.shards[shardIndex(queue)];
65
91
  const entries = [...shard.getDlqFiltered(queue, filter)];
66
- const now = Date.now();
67
92
  let retried = 0;
68
93
  for (const entry of entries) {
69
- const removed = shard.removeFromDlq(queue, entry.job.id);
70
- if (!removed)
71
- continue;
72
- setDlqRetryState(removed.job, null);
73
- enqueueRetry(queue, removed.job, now, ctx);
74
- retried++;
94
+ if (retryDlqJob(queue, entry.job.id, ctx))
95
+ retried++;
75
96
  }
76
97
  return retried;
77
98
  }
@@ -85,13 +106,13 @@ export function processAutoRetry(queue, ctx) {
85
106
  const entries = [...shard.getAutoRetryEntries(queue, now)];
86
107
  let retried = 0;
87
108
  for (const entry of entries) {
88
- const removed = shard.removeFromDlq(queue, entry.job.id);
89
- if (!removed)
90
- continue;
91
- scheduleNextRetry(removed, config);
92
- retainDlqRetryState(removed.job, removed);
93
- enqueueRetry(queue, removed.job, now, ctx);
94
- retried++;
109
+ const scheduled = { ...entry, attempts: [...entry.attempts] };
110
+ scheduleNextRetry(scheduled, config);
111
+ const job = retryEntry(queue, entry, ctx, now, (candidate) => {
112
+ retainDlqRetryState(candidate, scheduled);
113
+ });
114
+ if (job)
115
+ retried++;
95
116
  }
96
117
  return retried;
97
118
  }
@@ -27,7 +27,7 @@ export declare class EventsManager {
27
27
  * Wait for a specific job to complete - event-driven, no polling
28
28
  * Returns true if job completed, false if timeout
29
29
  */
30
- waitForJobCompletion(jobId: JobId, timeoutMs: number): Promise<boolean>;
30
+ waitForJobCompletion(jobId: JobId, timeoutMs: number, signal?: AbortSignal): Promise<boolean>;
31
31
  /** Check if broadcast has any listeners - for batch optimizations */
32
32
  needsBroadcast(): boolean;
33
33
  /** Broadcast event to all subscribers - optimized to skip work when no listeners */