pg-boss 12.21.2 → 12.23.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.
@@ -0,0 +1,141 @@
1
+ import EventEmitter from 'node:events';
2
+ import * as plans from "./plans.js";
3
+ import { delay } from "./tools.js";
4
+ import * as types from "./types.js";
5
+ const events = {
6
+ error: 'error',
7
+ flow: 'flow'
8
+ };
9
+ // Cap audit batches per resolve pass so a large backlog can't monopolize the loop; whatever is
10
+ // left over is picked up on the next poll.
11
+ const MAX_BATCHES_PER_PASS = 100;
12
+ // Background flow resolver. Completion is kept on a join-free hot path (see issue #824); the
13
+ // dependency bookkeeping that used to run inline now happens here, out of band. Modeled on the
14
+ // Bam poller: on each tick it claims the cluster-wide cadence gate (version.flow_on) and, if it
15
+ // wins, audits for completed "blocking" parents via the job_i9 partial index, decrements their
16
+ // children, unblocks those reaching zero, and clears the parents' blocking flag so they are not
17
+ // reprocessed. The Guild Navigator that keeps the spice flowing.
18
+ class Navigator extends EventEmitter {
19
+ #stopped;
20
+ #stopping;
21
+ #working;
22
+ #pollInterval;
23
+ #db;
24
+ #manager;
25
+ #config;
26
+ events = events;
27
+ constructor(db, manager, config) {
28
+ super();
29
+ this.#db = db;
30
+ this.#manager = manager;
31
+ this.#config = config;
32
+ this.#stopped = true;
33
+ this.#stopping = false;
34
+ this.#working = false;
35
+ }
36
+ get working() {
37
+ return this.#working;
38
+ }
39
+ async start() {
40
+ if (!this.#stopped)
41
+ return;
42
+ this.#stopped = false;
43
+ this.#stopping = false;
44
+ setImmediate(() => this.#onPoll());
45
+ this.#pollInterval = setInterval(() => this.#onPoll(), this.#config.flowIntervalSeconds * 1000);
46
+ }
47
+ async stop() {
48
+ if (this.#stopped)
49
+ return;
50
+ this.#stopping = true;
51
+ this.#stopped = true;
52
+ if (this.#pollInterval) {
53
+ clearInterval(this.#pollInterval);
54
+ this.#pollInterval = undefined;
55
+ }
56
+ while (this.#working) {
57
+ await delay(10);
58
+ }
59
+ }
60
+ async #onPoll() {
61
+ if (this.#stopped || this.#working)
62
+ return;
63
+ this.#working = true;
64
+ try {
65
+ if (this.#config.__test__throw_flow) {
66
+ throw new Error(this.#config.__test__throw_flow);
67
+ }
68
+ if (this.#config.__test__delay_flow_ms) {
69
+ await delay(this.#config.__test__delay_flow_ms);
70
+ }
71
+ const gate = plans.trySetFlowTime(this.#config.schema, this.#config.flowIntervalSeconds);
72
+ const { rows } = await this.#db.executeSql(gate);
73
+ if (rows.length === 1) {
74
+ await this.#resolve();
75
+ }
76
+ }
77
+ catch (err) {
78
+ this.emit(events.error, err);
79
+ }
80
+ finally {
81
+ this.#working = false;
82
+ }
83
+ }
84
+ // On-demand, ungated resolution pass. Like boss.supervise(), it is callable whether or not the
85
+ // background poll is running, so tests and apps can resolve flows deterministically. It skips
86
+ // the version-table cadence gate but still serializes against an in-flight poll via #working.
87
+ async resolveNow() {
88
+ while (this.#working) {
89
+ await delay(10);
90
+ }
91
+ if (this.#stopping)
92
+ return;
93
+ this.#working = true;
94
+ try {
95
+ await this.#resolve();
96
+ }
97
+ finally {
98
+ this.#working = false;
99
+ }
100
+ }
101
+ async #resolve() {
102
+ const queues = await this.#manager.getQueues();
103
+ // Group queues by partition table so each audit statement targets a single table and prunes to
104
+ // the chunk's queue names (mirrors boss.supervise()'s grouping).
105
+ const queueGroups = queues.reduce((acc, q) => {
106
+ acc[q.table] = acc[q.table] || { table: q.table, names: [] };
107
+ acc[q.table].names.push(q.name);
108
+ return acc;
109
+ }, {});
110
+ for (const group of Object.values(queueGroups)) {
111
+ if (this.#stopping)
112
+ return;
113
+ const { table } = group;
114
+ const names = [...group.names];
115
+ while (names.length) {
116
+ if (this.#stopping)
117
+ return;
118
+ const chunk = names.splice(0, 100);
119
+ let batches = 0;
120
+ let resolved = 0;
121
+ do {
122
+ if (this.#stopping)
123
+ return;
124
+ resolved = this.#config.noMultiMutationCte
125
+ ? await this.#manager.resolveFlowJobsDistributed(table, chunk)
126
+ : await this.#resolveStandard(table, chunk);
127
+ if (resolved > 0) {
128
+ this.emit(events.flow, { table, resolved });
129
+ }
130
+ } while (resolved >= plans.FLOW_BATCH_SIZE && ++batches < MAX_BATCHES_PER_PASS && !this.#stopping);
131
+ }
132
+ }
133
+ }
134
+ async #resolveStandard(table, names) {
135
+ const query = plans.resolveFlowJobs(this.#config.schema, table, names);
136
+ const { rows } = await this.#db.executeSql(query.text, query.values);
137
+ // CockroachDB returns integer columns as strings; coerce so the drain-loop comparison is numeric.
138
+ return Number(rows[0]?.resolved ?? 0);
139
+ }
140
+ }
141
+ export default Navigator;
package/dist/plans.d.ts CHANGED
@@ -45,6 +45,7 @@ export declare function trySetQueueMonitorTime(schema: string, queues: string[],
45
45
  export declare function trySetQueueDeletionTime(schema: string, queues: string[], seconds: number): SqlQuery;
46
46
  export declare function trySetCronTime(schema: string, seconds: number): string;
47
47
  export declare function trySetBamTime(schema: string, seconds: number): string;
48
+ export declare function trySetFlowTime(schema: string, seconds: number): string;
48
49
  export declare function updateQueue(schema: string, { deadLetter }?: UpdateQueueOptions): string;
49
50
  export declare function getQueues(schema: string, names?: string[]): SqlQuery;
50
51
  export declare function deleteJobsById(schema: string, table: string): string;
@@ -136,8 +137,13 @@ export declare function selectJobsToFailByHeartbeat(schema: string, table: strin
136
137
  export declare function deleteJobsByIds(schema: string, table: string): SqlQuery;
137
138
  export declare function completeJobsDistributed(schema: string, table: string, includeQueued?: boolean): string;
138
139
  export declare function decrementDependents(schema: string): string;
140
+ export declare const FLOW_BATCH_SIZE = 1000;
141
+ export declare function resolveFlowJobs(schema: string, table: string, names: string[]): SqlQuery;
142
+ export declare function selectBlockingParents(schema: string, table: string, names: string[], noSkipLocked?: boolean): SqlQuery;
143
+ export declare function clearBlocking(schema: string): string;
139
144
  export declare function insertRetryJob(schema: string, table: string): string;
140
145
  export declare function insertDeadLetterJob(schema: string): string;
146
+ export declare function redriveJobs(schema: string, table: string): string;
141
147
  export declare function deletion(schema: string, table: string, queues: string[], noAdvisoryLocks?: boolean): string;
142
148
  export declare function retryJobs(schema: string, table: string): string;
143
149
  export declare function getQueueStats(schema: string, table: string, queues: string[]): SqlQuery;
@@ -1 +1 @@
1
- {"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,EAAE,CAAA;CAClB;AAED,eAAO,MAAM,QAAQ;;CAEpB,CAAA;AAED,eAAO,MAAM,cAAc,WAAW,CAAA;AACtC,eAAO,MAAM,oBAAoB,qBAAqB,CAAA;AACtD,eAAO,MAAM,mBAAmB,mBAAmB,CAAA;AAMnD,eAAO,MAAM,UAAU;;;;;;;EAOrB,CAAA;AAEF,eAAO,MAAM,cAAc;;;;;;;EAOzB,CAAA;AAeF,UAAU,aAAa;IACrB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,wBAAgB,MAAM,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,UAyC/E;AA+GD,wBAAgB,kBAAkB,CAAE,MAAM,EAAE,MAAM,UAUjD;AAED,wBAAgB,kBAAkB,CAAE,MAAM,EAAE,MAAM,UAEjD;AAED,wBAAgB,wBAAwB,CAAE,MAAM,EAAE,MAAM,UAUvD;AAED,wBAAgB,8BAA8B,CAAE,MAAM,EAAE,MAAM,UAE7D;AA8WD,wBAAgB,WAAW,CAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,CAAC,EAAE,OAAO,UAGrG;AAcD,wBAAgB,gBAAgB,CAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAExD;AAID,wBAAgB,WAAW,CAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,wBAAgB,WAAW,CAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,OAAO,UAGnF;AAuDD,wBAAgB,sBAAsB,CAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAEnG;AAED,wBAAgB,uBAAuB,CAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAEpG;AAED,wBAAgB,cAAc,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAE9D;AAED,wBAAgB,aAAa,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAE7D;AAwBD,wBAAgB,WAAW,CAAE,MAAM,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,GAAE,kBAAuB,UA2BnF;AAED,wBAAgB,SAAS,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,QAAQ,CAkCrE;AAED,wBAAgB,cAAc,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAU5D;AAED,wBAAgB,gBAAgB,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAE9D;AAED,wBAAgB,gBAAgB,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAE9D;AAED,wBAAgB,aAAa,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAE3D;AAED,wBAAgB,aAAa,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAE3D;AAED,wBAAgB,YAAY,CAAE,MAAM,EAAE,MAAM,UAE3C;AAED,wBAAgB,mBAAmB,CAAE,MAAM,EAAE,MAAM,UAElD;AAED,wBAAgB,QAAQ,CAAE,MAAM,EAAE,MAAM,UAWvC;AAED,wBAAgB,UAAU,CAAE,MAAM,EAAE,MAAM,UAMzC;AAED,wBAAgB,SAAS,CAAE,MAAM,EAAE,MAAM,UASxC;AAED,wBAAgB,WAAW,CAAE,MAAM,EAAE,MAAM,UAK1C;AAED,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,MAAM,UAKhD;AAED,wBAAgB,OAAO,WAEtB;AAED,wBAAgB,aAAa,CAAE,MAAM,EAAE,MAAM,UAK5C;AAED,wBAAgB,WAAW,CAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAanD;AAED,wBAAgB,gBAAgB,CAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKvE;AAED,wBAAgB,UAAU,CAAE,MAAM,EAAE,MAAM,UAEzC;AAED,wBAAgB,UAAU,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAE1D;AAED,wBAAgB,kBAAkB,CAAE,MAAM,EAAE,MAAM,UAEjD;AAED,wBAAgB,yBAAyB,CAAE,MAAM,EAAE,MAAM,UAExD;AAED,wBAAgB,aAAa,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAE7D;AAED,UAAU,sBAAsB;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B;AAED,UAAU,eAAe;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,gBAAgB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IACjC,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IAC9B,gBAAgB,CAAC,EAAE,MAAM,GAAG,sBAAsB,CAAA;IAClD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAwED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAE,OAAO,EAAE,eAAe,EAAE,YAAY,UAAQ,GAAG,QAAQ,CAoItF;AA2CD,wBAAgB,YAAY,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,OAAO,UAqBnF;AAKD,wBAAgB,uBAAuB,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UA+BrE;AAKD,wBAAgB,kCAAkC,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAehF;AAED,wBAAgB,UAAU,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAaxD;AAED,wBAAgB,UAAU,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAaxD;AAED,wBAAgB,WAAW,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UASzD;AAED,UAAU,iBAAiB;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,wBAAgB,UAAU,CAAE,MAAM,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAe,EAAE,MAAc,EAAE,EAAE,iBAAiB,UAgH9G;AAOD,wBAAgB,cAAc,CAAE,MAAM,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAUzH;AAED,wBAAgB,YAAY,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAK1D;AAED,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,GAAG,MAAM,CAQrH;AAED,wBAAgB,mBAAmB,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,GAAG,MAAM,CASvH;AAED,wBAAgB,SAAS,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAYvD;AAmMD,wBAAgB,uBAAuB,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAcrE;AAID,wBAAgB,6BAA6B,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAW3E;AAGD,wBAAgB,oBAAoB,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,CAK7E;AAED,wBAAgB,gBAAgB,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,CAKzE;AAMD,wBAAgB,yBAAyB,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAQpG;AAED,wBAAgB,2BAA2B,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAStG;AAED,wBAAgB,eAAe,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,CAKxE;AAMD,wBAAgB,uBAAuB,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAKvG;AAMD,wBAAgB,mBAAmB,CAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAY3D;AAED,wBAAgB,cAAc,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAcrE;AAED,wBAAgB,mBAAmB,CAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAM3D;AAED,wBAAgB,QAAQ,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,GAAG,MAAM,CAa5G;AAED,wBAAgB,SAAS,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAavD;AAED,wBAAgB,aAAa,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CA4BxF;AAED,wBAAgB,eAAe,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,GAAG,MAAM,CA4BnH;AAGD,wBAAgB,mBAAmB,CAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAG7D;AAGD,wBAAgB,kBAAkB,CAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAE1D;AAED,wBAAgB,WAAW,CAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,CAU7D;AAED,wBAAgB,MAAM,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,OAAO,GAAG,MAAM,CAGjH;AAQD,wBAAgB,eAAe,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAG/D;AAED,wBAAgB,QAAQ,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,UA+BpI;AAED,wBAAgB,UAAU,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAOxD;AAID,wBAAgB,kBAAkB,CAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,UAcnE;AAED,wBAAgB,eAAe,CAAE,MAAM,EAAE,MAAM,UAM9C;AAED,wBAAgB,aAAa,CAAE,MAAM,EAAE,MAAM,UAM5C;AAED,wBAAgB,mBAAmB,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,GAAG,MAAM,CAgBvH;AAED,wBAAgB,cAAc,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAQ5D;AAED,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,MAAM,UAchD;AAED,wBAAgB,eAAe,CAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,UAM1D;AAED,wBAAgB,YAAY,CAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,UAOtE;AAED,wBAAgB,YAAY,CAAE,MAAM,EAAE,MAAM,UAM3C;AAED,wBAAgB,aAAa,CAAE,MAAM,EAAE,MAAM,UAO5C"}
1
+ {"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"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"}
package/dist/plans.js CHANGED
@@ -90,7 +90,8 @@ function createTableVersion(schema) {
90
90
  CREATE TABLE ${schema}.version (
91
91
  version int primary key,
92
92
  cron_on timestamp with time zone,
93
- bam_on timestamp with time zone
93
+ bam_on timestamp with time zone,
94
+ flow_on timestamp with time zone
94
95
  )
95
96
  `;
96
97
  }
@@ -320,7 +321,11 @@ function createTableJob(schema, noPartitioning = false) {
320
321
  heartbeat_seconds int,
321
322
  blocked boolean not null default false,
322
323
  blocking boolean not null default false,
323
- pending_dependencies int not null default 0
324
+ pending_dependencies int not null default 0,
325
+ source_name text,
326
+ source_id uuid,
327
+ source_created_on timestamp with time zone,
328
+ source_retry_count int
324
329
  ) ${partitionClause}
325
330
  `;
326
331
  }
@@ -347,7 +352,11 @@ const JOB_COLUMNS_ALL = `${JOB_COLUMNS_MIN},
347
352
  blocked,
348
353
  blocking,
349
354
  pending_dependencies as "pendingDependencies",
350
- output
355
+ output,
356
+ source_name as "sourceName",
357
+ source_id as "sourceId",
358
+ source_created_on as "sourceCreatedOn",
359
+ source_retry_count as "sourceRetryCount"
351
360
  `;
352
361
  function createTableJobCommon(schema) {
353
362
  return `
@@ -365,6 +374,7 @@ function createTableJobCommon(schema) {
365
374
  SELECT ${schema}.job_table_run($cmd$${createIndexJobThrottle(schema)}$cmd$, '${COMMON_JOB_TABLE}');
366
375
  SELECT ${schema}.job_table_run($cmd$${createIndexJobFetch(schema)}$cmd$, '${COMMON_JOB_TABLE}');
367
376
  SELECT ${schema}.job_table_run($cmd$${createIndexJobGroupConcurrency(schema)}$cmd$, '${COMMON_JOB_TABLE}');
377
+ SELECT ${schema}.job_table_run($cmd$${createIndexJobBlocking(schema)}$cmd$, '${COMMON_JOB_TABLE}');
368
378
 
369
379
  ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.${COMMON_JOB_TABLE} DEFAULT;
370
380
  `;
@@ -383,6 +393,7 @@ function createTableJobIndexes(schema, noDeferrableConstraints = false, noCoveri
383
393
  ${createIndexJobThrottle(schema)};
384
394
  ${createIndexJobFetch(schema, noCoveringIndex)};
385
395
  ${createIndexJobGroupConcurrency(schema)};
396
+ ${createIndexJobBlocking(schema)};
386
397
  `;
387
398
  }
388
399
  function createQueueFunction(schema, noPartitioning = false) {
@@ -496,6 +507,7 @@ function createQueueFunction(schema, noPartitioning = false) {
496
507
  EXECUTE ${schema}.job_table_format($cmd$${createIndexJobFetch(schema)}$cmd$, tablename);
497
508
  EXECUTE ${schema}.job_table_format($cmd$${createIndexJobThrottle(schema)}$cmd$, tablename);
498
509
  EXECUTE ${schema}.job_table_format($cmd$${createIndexJobGroupConcurrency(schema)}$cmd$, tablename);
510
+ EXECUTE ${schema}.job_table_format($cmd$${createIndexJobBlocking(schema)}$cmd$, tablename);
499
511
 
500
512
  IF options->>'policy' = 'short' THEN
501
513
  EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicyShort(schema)}$cmd$, tablename);
@@ -602,10 +614,12 @@ function createIndexJobThrottle(schema) {
602
614
  return `CREATE UNIQUE INDEX job_i4 ON ${schema}.job (name, singleton_on, COALESCE(singleton_key, '')) WHERE state <> '${JOB_STATES.cancelled}' AND singleton_on IS NOT NULL`;
603
615
  }
604
616
  function createIndexJobFetch(schema, noCoveringIndex = false) {
605
- // CockroachDB implicitly includes primary key columns (id) in all indexes,
606
- // so we can't explicitly INCLUDE them - it causes "already contains column" error
607
- const includeClause = noCoveringIndex ? '' : 'INCLUDE (priority, created_on, id) ';
608
- return `CREATE INDEX job_i5 ON ${schema}.job (name, start_after) ${includeClause}WHERE state < '${JOB_STATES.active}' AND NOT blocked`;
617
+ // No covering INCLUDE: the fetch locks candidate rows with FOR UPDATE ... SKIP LOCKED, which
618
+ // forces heap access, so an index-only scan is impossible and a covering payload would never be
619
+ // read from the index. Confirmed dead weight via EXPLAIN ANALYZE (see examples/index-perf);
620
+ // dropping it shrinks job_i5 on the hot insert path at no read-side cost.
621
+ // noCoveringIndex (the CockroachDB profile flag that stripped the old INCLUDE) is now moot here.
622
+ return `CREATE INDEX job_i5 ON ${schema}.job (name, start_after) WHERE state < '${JOB_STATES.active}' AND NOT blocked`;
609
623
  }
610
624
  function createIndexJobPolicyExclusive(schema) {
611
625
  return `CREATE UNIQUE INDEX job_i6 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state <= '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.exclusive}'`;
@@ -619,6 +633,13 @@ function createCheckConstraintKeyStrictFifo(schema) {
619
633
  function createIndexJobGroupConcurrency(schema) {
620
634
  return `CREATE INDEX job_i7 ON ${schema}.job (name, group_id) WHERE state = '${JOB_STATES.active}' AND group_id IS NOT NULL`;
621
635
  }
636
+ // Partial index supporting the background flow resolver (Navigator): lets it find completed
637
+ // blocking parents with an index scan instead of a partition-wide scan. The `state = completed`
638
+ // predicate keeps still-running and permanently-failed blocking parents out of the index, so
639
+ // non-flow queues (and high-partition-count deployments) carry an empty index that costs nothing.
640
+ function createIndexJobBlocking(schema) {
641
+ return `CREATE INDEX job_i9 ON ${schema}.job (name, id) WHERE blocking AND state = '${JOB_STATES.completed}'`;
642
+ }
622
643
  export function trySetQueueMonitorTime(schema, queues, seconds) {
623
644
  return trySetQueueTimestamp(schema, queues, 'monitor_on', seconds);
624
645
  }
@@ -631,6 +652,9 @@ export function trySetCronTime(schema, seconds) {
631
652
  export function trySetBamTime(schema, seconds) {
632
653
  return trySetTimestamp(schema, 'bam_on', seconds);
633
654
  }
655
+ export function trySetFlowTime(schema, seconds) {
656
+ return trySetTimestamp(schema, 'flow_on', seconds);
657
+ }
634
658
  function trySetTimestamp(schema, column, seconds) {
635
659
  return `
636
660
  UPDATE ${schema}.version
@@ -717,7 +741,7 @@ export function deleteJobsById(schema, table) {
717
741
  WITH results as (
718
742
  DELETE FROM ${schema}.${table}
719
743
  WHERE name = $1
720
- AND id IN (SELECT UNNEST($2::uuid[]))
744
+ AND id = ANY($2::uuid[])
721
745
  RETURNING 1
722
746
  )
723
747
  SELECT COUNT(*) from results
@@ -1031,7 +1055,7 @@ function completeJobsUpdate(schema, table, includeQueued) {
1031
1055
  blocked = ${includeQueued ? 'false' : 'blocked'},
1032
1056
  pending_dependencies = ${includeQueued ? '0' : 'pending_dependencies'}
1033
1057
  WHERE name = $1
1034
- AND id IN (SELECT UNNEST($2::uuid[]))
1058
+ AND id = ANY($2::uuid[])
1035
1059
  AND ${includeQueued
1036
1060
  ? `state < '${JOB_STATES.completed}'`
1037
1061
  : `state = '${JOB_STATES.active}'`}`;
@@ -1058,26 +1082,17 @@ function unblockChildrenUpdate(schema) {
1058
1082
  WHERE j.name = lc.name
1059
1083
  AND j.id = lc.id`;
1060
1084
  }
1085
+ // Dependency unblocking is intentionally NOT done here. Completion is the hot path; chasing
1086
+ // dependents inline (joining job_dependency and the partitioned job table) made completion
1087
+ // scale with partition count (see issue #824). The background resolver (Navigator) handles
1088
+ // unblocking out of band, driven by the job_i9 partial index.
1061
1089
  export function completeJobs(schema, table, includeQueued) {
1062
1090
  return `
1063
- WITH completed AS (
1091
+ WITH results AS (
1064
1092
  ${completeJobsUpdate(schema, table, includeQueued)}
1065
- RETURNING name, id, blocking
1066
- ),
1067
- decremented AS (
1068
- SELECT d.child_name, d.child_id, COUNT(*)::int AS n
1069
- FROM ${schema}.job_dependency d
1070
- JOIN completed c ON c.blocking
1071
- AND d.parent_name = c.name
1072
- AND d.parent_id = c.id
1073
- GROUP BY d.child_name, d.child_id
1074
- ),
1075
- ${lockedChildrenCte(schema)},
1076
- unblocked AS (
1077
- ${unblockChildrenUpdate(schema)}
1078
1093
  RETURNING 1
1079
1094
  )
1080
- SELECT COUNT(*) FROM completed
1095
+ SELECT COUNT(*) FROM results
1081
1096
  `;
1082
1097
  }
1083
1098
  // Per-job-output completion: each job's output is supplied via a JSON recordset ($2) and applied by
@@ -1088,7 +1103,7 @@ export function completeJobsWithOutputs(schema, table) {
1088
1103
  WITH input AS (
1089
1104
  SELECT * FROM json_to_recordset($2::json) AS x (id uuid, output jsonb)
1090
1105
  ),
1091
- completed AS (
1106
+ results AS (
1092
1107
  UPDATE ${schema}.${table} j
1093
1108
  SET completed_on = now(),
1094
1109
  state = '${JOB_STATES.completed}',
@@ -1097,27 +1112,14 @@ export function completeJobsWithOutputs(schema, table) {
1097
1112
  WHERE j.name = $1
1098
1113
  AND j.id = i.id
1099
1114
  AND j.state = '${JOB_STATES.active}'
1100
- RETURNING j.name, j.id, j.blocking
1101
- ),
1102
- decremented AS (
1103
- SELECT d.child_name, d.child_id, COUNT(*)::int AS n
1104
- FROM ${schema}.job_dependency d
1105
- JOIN completed c ON c.blocking
1106
- AND d.parent_name = c.name
1107
- AND d.parent_id = c.id
1108
- GROUP BY d.child_name, d.child_id
1109
- ),
1110
- ${lockedChildrenCte(schema)},
1111
- unblocked AS (
1112
- ${unblockChildrenUpdate(schema)}
1113
1115
  RETURNING 1
1114
1116
  )
1115
- SELECT COUNT(*) FROM completed
1117
+ SELECT COUNT(*) FROM results
1116
1118
  `;
1117
1119
  }
1118
1120
  // Distributed equivalent of completeJobsWithOutputs: a single mutation that returns the completed
1119
- // ids and whether each was blocking, so the caller can decrement dependents in a separate statement
1120
- // (CockroachDB rejects multi-mutation CTEs). Pairs with decrementDependents().
1121
+ // ids. Dependency unblocking is handled out of band by the background resolver (Navigator), so
1122
+ // completion does no dependency work on any backend.
1121
1123
  export function completeJobsWithOutputsDistributed(schema, table) {
1122
1124
  return `
1123
1125
  WITH input AS (
@@ -1131,7 +1133,7 @@ export function completeJobsWithOutputsDistributed(schema, table) {
1131
1133
  WHERE j.name = $1
1132
1134
  AND j.id = i.id
1133
1135
  AND j.state = '${JOB_STATES.active}'
1134
- RETURNING j.id, j.blocking
1136
+ RETURNING j.id
1135
1137
  `;
1136
1138
  }
1137
1139
  export function cancelJobs(schema, table) {
@@ -1141,7 +1143,7 @@ export function cancelJobs(schema, table) {
1141
1143
  SET completed_on = now(),
1142
1144
  state = '${JOB_STATES.cancelled}'
1143
1145
  WHERE name = $1
1144
- AND id IN (SELECT UNNEST($2::uuid[]))
1146
+ AND id = ANY($2::uuid[])
1145
1147
  AND state < '${JOB_STATES.completed}'
1146
1148
  RETURNING 1
1147
1149
  )
@@ -1155,7 +1157,7 @@ export function resumeJobs(schema, table) {
1155
1157
  SET completed_on = NULL,
1156
1158
  state = '${JOB_STATES.created}'
1157
1159
  WHERE name = $1
1158
- AND id IN (SELECT UNNEST($2::uuid[]))
1160
+ AND id = ANY($2::uuid[])
1159
1161
  AND state = '${JOB_STATES.cancelled}'
1160
1162
  RETURNING 1
1161
1163
  )
@@ -1169,7 +1171,7 @@ export function restoreJobs(schema, table) {
1169
1171
  started_on = NULL,
1170
1172
  heartbeat_on = NULL
1171
1173
  WHERE name = $1
1172
- AND id IN (SELECT UNNEST($2::uuid[]))
1174
+ AND id = ANY($2::uuid[])
1173
1175
  `;
1174
1176
  }
1175
1177
  export function insertJobs(schema, { table, name, returnId = true, notify = false }) {
@@ -1297,7 +1299,7 @@ export function insertFlowJobs(schema, { table, name }, jobs) {
1297
1299
  `;
1298
1300
  }
1299
1301
  export function failJobsById(schema, table) {
1300
- const where = `name = $1 AND id IN (SELECT UNNEST($2::uuid[])) AND state < '${JOB_STATES.completed}'`;
1302
+ const where = `name = $1 AND id = ANY($2::uuid[]) AND state < '${JOB_STATES.completed}'`;
1301
1303
  const output = '$3::jsonb';
1302
1304
  return failJobs(schema, table, where, output);
1303
1305
  }
@@ -1322,7 +1324,7 @@ export function touchJobs(schema, table) {
1322
1324
  UPDATE ${schema}.${table}
1323
1325
  SET heartbeat_on = now()
1324
1326
  WHERE name = $1
1325
- AND id IN (SELECT UNNEST($2::uuid[]))
1327
+ AND id = ANY($2::uuid[])
1326
1328
  AND state = '${JOB_STATES.active}'
1327
1329
  RETURNING 1
1328
1330
  )
@@ -1504,16 +1506,21 @@ function failJobsBody(schema, table, where, output, forceTerminal = false) {
1504
1506
  SELECT * FROM failed_jobs
1505
1507
  ),
1506
1508
  dlq_jobs as (
1507
- INSERT INTO ${schema}.job (name, data, output, retry_limit, retry_backoff, retry_delay, keep_until, deletion_seconds)
1509
+ INSERT INTO ${schema}.job (name, data, output, retry_limit, retry_backoff, retry_delay, keep_until, deletion_seconds,
1510
+ source_name, source_id, source_created_on, source_retry_count)
1508
1511
  SELECT
1509
1512
  r.dead_letter,
1510
- data,
1511
- output,
1513
+ r.data,
1514
+ r.output,
1512
1515
  q.retry_limit,
1513
1516
  q.retry_backoff,
1514
1517
  q.retry_delay,
1515
1518
  now() + q.retention_seconds * interval '1s',
1516
- q.deletion_seconds
1519
+ q.deletion_seconds,
1520
+ r.name,
1521
+ r.id,
1522
+ r.created_on,
1523
+ r.retry_count
1517
1524
  FROM results r
1518
1525
  JOIN ${schema}.queue q ON q.name = r.dead_letter
1519
1526
  WHERE state = '${JOB_STATES.failed}'
@@ -1549,13 +1556,13 @@ export function deadLetterJobsByIdWithOutputs(schema, table) {
1549
1556
  // Distributed mode: separate queries to avoid CockroachDB's multi-mutation CTE limitation
1550
1557
  export function selectJobsToFailById(schema, table) {
1551
1558
  return {
1552
- text: `SELECT * FROM ${schema}.${table} WHERE name = $1 AND id IN (SELECT UNNEST($2::uuid[])) AND state < '${JOB_STATES.completed}'`,
1559
+ text: `SELECT * FROM ${schema}.${table} WHERE name = $1 AND id = ANY($2::uuid[]) AND state < '${JOB_STATES.completed}'`,
1553
1560
  values: []
1554
1561
  };
1555
1562
  }
1556
1563
  export function deleteJobsToFail(schema, table) {
1557
1564
  return {
1558
- text: `DELETE FROM ${schema}.${table} WHERE name = $1 AND id IN (SELECT UNNEST($2::uuid[]))`,
1565
+ text: `DELETE FROM ${schema}.${table} WHERE name = $1 AND id = ANY($2::uuid[])`,
1559
1566
  values: []
1560
1567
  };
1561
1568
  }
@@ -1584,37 +1591,112 @@ export function selectJobsToFailByHeartbeat(schema, table, queues) {
1584
1591
  }
1585
1592
  export function deleteJobsByIds(schema, table) {
1586
1593
  return {
1587
- text: `DELETE FROM ${schema}.${table} WHERE id IN (SELECT UNNEST($1::uuid[]))`,
1594
+ text: `DELETE FROM ${schema}.${table} WHERE id = ANY($1::uuid[])`,
1588
1595
  values: []
1589
1596
  };
1590
1597
  }
1591
- // Distributed mode: complete jobs without the dependency-unblocking CTE so the
1592
- // statement only mutates a single table once. CockroachDB rejects the standard
1593
- // completeJobs() because it updates both the job table and job (for unblocking)
1594
- // within one statement. The unblocking is handled separately by decrementDependents().
1598
+ // Distributed mode: complete jobs as a single-table mutation. Dependency unblocking is handled
1599
+ // out of band by the background resolver (Navigator), so completion does no dependency work.
1595
1600
  export function completeJobsDistributed(schema, table, includeQueued) {
1596
1601
  return `
1597
1602
  ${completeJobsUpdate(schema, table, includeQueued)}
1598
- RETURNING id, blocking
1603
+ RETURNING id
1599
1604
  `;
1600
1605
  }
1601
- // Distributed mode: decrement pending_dependencies for children of the given
1602
- // completed parent jobs. Only the final UPDATE mutates job, so this is a single
1603
- // mutation acceptable to CockroachDB. $2 is the list of completed parent ids that
1604
- // were marked blocking.
1606
+ // Decrement pending_dependencies for children of the given completed parent jobs, unblocking
1607
+ // any that reach zero. Only the final UPDATE mutates job, so this is a single mutation acceptable
1608
+ // to CockroachDB. Used by the distributed flow resolver path. $1 is the parent queue name, $2 the
1609
+ // list of resolved parent ids for that queue.
1605
1610
  export function decrementDependents(schema) {
1606
1611
  return `
1607
1612
  WITH decremented AS (
1608
1613
  SELECT d.child_name, d.child_id, COUNT(*)::int AS n
1609
1614
  FROM ${schema}.job_dependency d
1610
1615
  WHERE d.parent_name = $1
1611
- AND d.parent_id IN (SELECT UNNEST($2::uuid[]))
1616
+ AND d.parent_id = ANY($2::uuid[])
1612
1617
  GROUP BY d.child_name, d.child_id
1613
1618
  ),
1614
1619
  ${lockedChildrenCte(schema)}
1615
1620
  ${unblockChildrenUpdate(schema)}
1616
1621
  `;
1617
1622
  }
1623
+ // Background flow resolver (Navigator) batch size: the max number of completed blocking parents
1624
+ // locked per audit statement. The resolver loops until a batch drains, so this only bounds the
1625
+ // lock footprint and per-statement cost.
1626
+ export const FLOW_BATCH_SIZE = 1000;
1627
+ // Standard (multi-mutation CTE) flow audit. Locks a batch of completed blocking parents in the
1628
+ // given partition table, decrements their children's pending_dependencies (reusing the shared
1629
+ // unblock fragments, which reach across partitions via the parent job table), unblocks children
1630
+ // that reach zero, and clears `blocking` on the resolved parents so they leave the job_i9 index
1631
+ // and are never reprocessed. $1 is the chunk of queue names (for partition pruning). Returns the
1632
+ // number of parents resolved so the caller can loop until a batch drains.
1633
+ export function resolveFlowJobs(schema, table, names) {
1634
+ return {
1635
+ text: `
1636
+ WITH locked_parents AS (
1637
+ SELECT j.name, j.id
1638
+ FROM ${schema}.${table} j
1639
+ WHERE j.blocking
1640
+ AND j.state = '${JOB_STATES.completed}'
1641
+ AND j.name = ANY($1::text[])
1642
+ ORDER BY j.name, j.id
1643
+ FOR UPDATE OF j SKIP LOCKED
1644
+ LIMIT ${FLOW_BATCH_SIZE}
1645
+ ),
1646
+ decremented AS (
1647
+ SELECT d.child_name, d.child_id, COUNT(*)::int AS n
1648
+ FROM ${schema}.job_dependency d
1649
+ JOIN locked_parents p ON d.parent_name = p.name
1650
+ AND d.parent_id = p.id
1651
+ GROUP BY d.child_name, d.child_id
1652
+ ),
1653
+ ${lockedChildrenCte(schema)},
1654
+ unblocked AS (
1655
+ ${unblockChildrenUpdate(schema)}
1656
+ RETURNING 1
1657
+ ),
1658
+ cleared AS (
1659
+ UPDATE ${schema}.${table} j
1660
+ SET blocking = false
1661
+ FROM locked_parents p
1662
+ WHERE j.name = p.name
1663
+ AND j.id = p.id
1664
+ RETURNING 1
1665
+ )
1666
+ SELECT COUNT(*)::int AS resolved FROM cleared
1667
+ `,
1668
+ values: [names]
1669
+ };
1670
+ }
1671
+ // Distributed flow audit (CockroachDB / noMultiMutationCte). Locks a batch of completed blocking
1672
+ // parents without mutating, so the caller can run the single-mutation decrementDependents() and
1673
+ // clearBlocking() separately within one transaction. $1 is the chunk of queue names; SKIP LOCKED
1674
+ // is omitted under noSkipLocked.
1675
+ export function selectBlockingParents(schema, table, names, noSkipLocked) {
1676
+ return {
1677
+ text: `
1678
+ SELECT name, id
1679
+ FROM ${schema}.${table}
1680
+ WHERE blocking
1681
+ AND state = '${JOB_STATES.completed}'
1682
+ AND name = ANY($1::text[])
1683
+ ORDER BY name, id
1684
+ FOR UPDATE${noSkipLocked ? '' : ' SKIP LOCKED'}
1685
+ LIMIT ${FLOW_BATCH_SIZE}
1686
+ `,
1687
+ values: [names]
1688
+ };
1689
+ }
1690
+ // Distributed flow audit: clear `blocking` on resolved parents (single mutation). $1 is the parent
1691
+ // queue name, $2 the list of resolved parent ids for that queue.
1692
+ export function clearBlocking(schema) {
1693
+ return `
1694
+ UPDATE ${schema}.job
1695
+ SET blocking = false
1696
+ WHERE name = $1
1697
+ AND id = ANY($2::uuid[])
1698
+ `;
1699
+ }
1618
1700
  export function insertRetryJob(schema, table) {
1619
1701
  return `
1620
1702
  INSERT INTO ${schema}.${table} (
@@ -1632,11 +1714,50 @@ export function insertRetryJob(schema, table) {
1632
1714
  }
1633
1715
  export function insertDeadLetterJob(schema) {
1634
1716
  return `
1635
- INSERT INTO ${schema}.job (name, data, output, retry_limit, retry_backoff, retry_delay, keep_until, deletion_seconds)
1636
- SELECT $1, $2, $3, q.retry_limit, q.retry_backoff, q.retry_delay, now() + q.retention_seconds * interval '1s', q.deletion_seconds
1717
+ INSERT INTO ${schema}.job (name, data, output, retry_limit, retry_backoff, retry_delay, keep_until, deletion_seconds,
1718
+ source_name, source_id, source_created_on, source_retry_count)
1719
+ SELECT $1, $2, $3, q.retry_limit, q.retry_backoff, q.retry_delay, now() + q.retention_seconds * interval '1s', q.deletion_seconds,
1720
+ $4, $5, $6, $7
1637
1721
  FROM ${schema}.queue q WHERE q.name = $1
1638
1722
  `;
1639
1723
  }
1724
+ // Dead-letter redrive. Moves un-started jobs out of a dead-letter queue and
1725
+ // re-creates them as fresh jobs on their original source queue (or $2 destination override),
1726
+ // oldest-first, capped at $4. The JOIN in `candidates` only matches jobs whose destination queue
1727
+ // exists, so legacy/orphaned jobs (NULL source_name, no override) are never deleted — they stay
1728
+ // in the DLQ rather than being lost. Re-created jobs get a new id, `created` state, retry_count 0,
1729
+ // cleared output, NULL source_*, and the destination queue's current retry/retention/policy config.
1730
+ export function redriveJobs(schema, table) {
1731
+ return `
1732
+ WITH candidates AS (
1733
+ SELECT j.id
1734
+ FROM ${schema}.${table} j
1735
+ JOIN ${schema}.queue q ON q.name = COALESCE($2, j.source_name)
1736
+ WHERE j.name = $1
1737
+ AND j.state < '${JOB_STATES.active}'
1738
+ AND ($3::text IS NULL OR j.source_name = $3)
1739
+ ORDER BY j.created_on
1740
+ LIMIT $4
1741
+ FOR UPDATE OF j SKIP LOCKED
1742
+ ),
1743
+ moved AS (
1744
+ DELETE FROM ${schema}.${table}
1745
+ WHERE id IN (SELECT id FROM candidates)
1746
+ RETURNING *
1747
+ ),
1748
+ ins AS (
1749
+ INSERT INTO ${schema}.job
1750
+ (name, data, priority, retry_limit, retry_backoff, retry_delay, retry_delay_max,
1751
+ expire_seconds, keep_until, deletion_seconds, policy)
1752
+ SELECT COALESCE($2, m.source_name), m.data, m.priority, q.retry_limit, q.retry_backoff,
1753
+ q.retry_delay, q.retry_delay_max, q.expire_seconds,
1754
+ now() + q.retention_seconds * interval '1s', q.deletion_seconds, q.policy
1755
+ FROM moved m JOIN ${schema}.queue q ON q.name = COALESCE($2, m.source_name)
1756
+ RETURNING 1
1757
+ )
1758
+ SELECT count(*)::int AS moved FROM ins
1759
+ `;
1760
+ }
1640
1761
  export function deletion(schema, table, queues, noAdvisoryLocks) {
1641
1762
  const sql = `
1642
1763
  DELETE FROM ${schema}.${table}
@@ -1657,7 +1778,7 @@ export function retryJobs(schema, table) {
1657
1778
  SET state = '${JOB_STATES.retry}',
1658
1779
  retry_limit = retry_limit + 1
1659
1780
  WHERE name = $1
1660
- AND id IN (SELECT UNNEST($2::uuid[]))
1781
+ AND id = ANY($2::uuid[])
1661
1782
  AND state = '${JOB_STATES.failed}'
1662
1783
  RETURNING 1
1663
1784
  )