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