pg-boss 12.25.0 → 12.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/knex.d.ts.map +1 -1
- package/dist/adapters/knex.js +6 -1
- package/dist/attorney.d.ts.map +1 -1
- package/dist/attorney.js +31 -9
- package/dist/bam.d.ts.map +1 -1
- package/dist/bam.js +23 -1
- package/dist/boss.d.ts.map +1 -1
- package/dist/boss.js +15 -20
- package/dist/cli.js +147 -3
- package/dist/contractor.d.ts +1 -0
- package/dist/contractor.d.ts.map +1 -1
- package/dist/contractor.js +97 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +9 -1
- package/dist/drifter.d.ts +84 -0
- package/dist/drifter.d.ts.map +1 -0
- package/dist/drifter.js +423 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +98 -55
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +68 -22
- package/dist/migrationStore.d.ts +2 -1
- package/dist/migrationStore.d.ts.map +1 -1
- package/dist/migrationStore.js +77 -4
- package/dist/plans.d.ts +24 -2
- package/dist/plans.d.ts.map +1 -1
- package/dist/plans.js +375 -71
- package/dist/schema.json +2069 -0
- package/dist/timekeeper.d.ts.map +1 -1
- package/dist/timekeeper.js +10 -4
- package/dist/types.d.ts +139 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -7
package/dist/index.js
CHANGED
|
@@ -32,8 +32,9 @@ export function getRollbackPlans(schema, version) {
|
|
|
32
32
|
export class PgBoss extends EventEmitter {
|
|
33
33
|
#stoppingOn;
|
|
34
34
|
#stopped;
|
|
35
|
-
#starting;
|
|
36
35
|
#started;
|
|
36
|
+
#startingPromise = null;
|
|
37
|
+
#stoppingPromise = null;
|
|
37
38
|
#config;
|
|
38
39
|
#db;
|
|
39
40
|
#boss;
|
|
@@ -83,43 +84,58 @@ export class PgBoss extends EventEmitter {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
async start() {
|
|
86
|
-
|
|
87
|
+
// A stop() already in flight must finish (clearing any resources it's tearing down) before a
|
|
88
|
+
// fresh start() begins, otherwise the two race over the same intervals/pool.
|
|
89
|
+
if (this.#stoppingPromise) {
|
|
90
|
+
await this.#stoppingPromise.catch(() => { });
|
|
91
|
+
}
|
|
92
|
+
// Return the SAME in-flight promise to a concurrent caller instead of a fresh `this` — a
|
|
93
|
+
// second caller must observe the actual outcome (including a rejection), not silently no-op
|
|
94
|
+
// while the first call is still mid-flight.
|
|
95
|
+
if (this.#startingPromise) {
|
|
96
|
+
return this.#startingPromise;
|
|
97
|
+
}
|
|
98
|
+
if (this.#started) {
|
|
87
99
|
return this;
|
|
88
100
|
}
|
|
89
|
-
|
|
101
|
+
// Cleared to false before any subsystem is started (not just on success): if #doStart throws
|
|
102
|
+
// partway through, subsystems already started (e.g. manager's queueCacheInterval/wipInterval)
|
|
103
|
+
// must still be reachable by stop() for cleanup, and stop() no-ops whenever #stopped is true.
|
|
104
|
+
this.#stopped = false;
|
|
105
|
+
this.#startingPromise = this.#doStart();
|
|
90
106
|
try {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
if (this.#config.migrate) {
|
|
113
|
-
await this.#bam.start();
|
|
114
|
-
}
|
|
107
|
+
return await this.#startingPromise;
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
this.#startingPromise = null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async #doStart() {
|
|
114
|
+
if (this.#db._pgbdb && !this.#db.opened) {
|
|
115
|
+
await this.#db.open();
|
|
116
|
+
}
|
|
117
|
+
await this.#warnIfDistributedMisconfigured();
|
|
118
|
+
if (this.#config.migrate) {
|
|
119
|
+
await this.#contractor.start();
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
await this.#contractor.check();
|
|
123
|
+
}
|
|
124
|
+
await this.#manager.start();
|
|
125
|
+
if (this.#config.useListenNotify) {
|
|
126
|
+
await this.#notifier.start();
|
|
115
127
|
}
|
|
116
|
-
|
|
117
|
-
this.#
|
|
118
|
-
|
|
128
|
+
if (this.#config.supervise) {
|
|
129
|
+
await this.#boss.start();
|
|
130
|
+
await this.#navigator.start();
|
|
131
|
+
}
|
|
132
|
+
if (this.#config.schedule) {
|
|
133
|
+
await this.#timekeeper.start();
|
|
134
|
+
}
|
|
135
|
+
if (this.#config.migrate) {
|
|
136
|
+
await this.#bam.start();
|
|
119
137
|
}
|
|
120
|
-
this.#starting = false;
|
|
121
138
|
this.#started = true;
|
|
122
|
-
this.#stopped = false;
|
|
123
139
|
return this;
|
|
124
140
|
}
|
|
125
141
|
// YugabyteDB needs the yugabytedb backend profile (no table partitioning + no advisory locks;
|
|
@@ -144,37 +160,61 @@ export class PgBoss extends EventEmitter {
|
|
|
144
160
|
}
|
|
145
161
|
}
|
|
146
162
|
async stop(options = {}) {
|
|
147
|
-
|
|
163
|
+
// A start() already in flight must finish (or fail) before stop() evaluates state, otherwise
|
|
164
|
+
// stop() reads #stopped mid-start and silently no-ops while start() keeps running.
|
|
165
|
+
if (this.#startingPromise) {
|
|
166
|
+
await this.#startingPromise.catch(() => { });
|
|
167
|
+
}
|
|
168
|
+
if (this.#stoppingPromise) {
|
|
169
|
+
return this.#stoppingPromise;
|
|
170
|
+
}
|
|
171
|
+
if (this.#stopped) {
|
|
148
172
|
return;
|
|
149
173
|
}
|
|
150
174
|
let { close = true, graceful = true, timeout = 30000 } = options;
|
|
151
175
|
timeout = Math.max(timeout, 1000);
|
|
152
176
|
this.#stoppingOn = Date.now();
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
177
|
+
this.#stoppingPromise = this.#doStop(close, graceful, timeout);
|
|
178
|
+
try {
|
|
179
|
+
return await this.#stoppingPromise;
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
this.#stoppingPromise = null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async #doStop(close, graceful, timeout) {
|
|
186
|
+
try {
|
|
187
|
+
await this.#notifier.stop();
|
|
188
|
+
await this.#manager.stop();
|
|
189
|
+
await this.#timekeeper.stop();
|
|
190
|
+
await this.#boss.stop();
|
|
191
|
+
await this.#navigator.stop();
|
|
192
|
+
await this.#bam.stop();
|
|
193
|
+
const shutdown = async () => {
|
|
194
|
+
await this.#manager.failWip();
|
|
195
|
+
if (this.#db._pgbdb && this.#db.opened && close) {
|
|
196
|
+
await this.#db.close();
|
|
197
|
+
// Give event loop time to process socket closes
|
|
198
|
+
await delay(10);
|
|
199
|
+
}
|
|
200
|
+
this.#stopped = true;
|
|
201
|
+
this.#started = false;
|
|
202
|
+
this.emit(events.stopped);
|
|
203
|
+
};
|
|
204
|
+
if (!graceful) {
|
|
205
|
+
await shutdown();
|
|
206
|
+
return;
|
|
165
207
|
}
|
|
166
|
-
this.#
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
};
|
|
171
|
-
if (!graceful) {
|
|
172
|
-
return await shutdown();
|
|
208
|
+
while ((Date.now() - this.#stoppingOn) < timeout && this.#manager.hasPendingCleanups()) {
|
|
209
|
+
await delay(500);
|
|
210
|
+
}
|
|
211
|
+
await shutdown();
|
|
173
212
|
}
|
|
174
|
-
|
|
175
|
-
|
|
213
|
+
finally {
|
|
214
|
+
// Reset unconditionally (success or throw) so a stop() that fails partway can be retried
|
|
215
|
+
// instead of every future stop()/start() call silently no-op-ing forever on the stale marker.
|
|
216
|
+
this.#stoppingOn = null;
|
|
176
217
|
}
|
|
177
|
-
await shutdown();
|
|
178
218
|
}
|
|
179
219
|
async send(...args) {
|
|
180
220
|
return await this.#manager.send(...args);
|
|
@@ -325,6 +365,9 @@ export class PgBoss extends EventEmitter {
|
|
|
325
365
|
schemaVersion() {
|
|
326
366
|
return this.#contractor.schemaVersion();
|
|
327
367
|
}
|
|
368
|
+
detectSchemaDrift() {
|
|
369
|
+
return this.#contractor.detectDrift();
|
|
370
|
+
}
|
|
328
371
|
schedule(name, cron, data, options) {
|
|
329
372
|
return this.#timekeeper.schedule(name, cron, data, options);
|
|
330
373
|
}
|
package/dist/manager.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"AAEA,OAAO,YAAY,MAAM,aAAa,CAAA;AAGtC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAG7C,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,MAAM,MAAM,aAAa,CAAA;AAChC,OAAO,EAAU,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAwEvD,cAAM,OAAQ,SAAQ,YAAa,YAAW,KAAK,CAAC,WAAW;;IAC7D,MAAM;;;MAAS;IACf,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,GAAG;QAAE,MAAM,CAAC,EAAE,KAAK,CAAA;KAAE,CAAC,GAAG,EAAE,CAAA;IAC/C,MAAM,EAAE,KAAK,CAAC,0BAA0B,CAAA;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,OAAO,EAAE,OAAO,GAAG,SAAS,CAAA;IAC5B,kBAAkB,EAAE,MAAM,CAAC,OAAO,GAAG,SAAS,CAAA;IAC9C,WAAW,EAAE,MAAM,CAAC,OAAO,GAAG,SAAS,CAAA;IACvC,UAAU,EAAE,UAAU,GAAG,SAAS,CAAA;IAClC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,GAAG,IAAI,CAAA;IAChD,sBAAsB,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;gBAM5B,EAAE,EAAE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,0BAA0B;IAe1E,MAAM,CAAC,CAAC,GAAG,MAAM,EAAG,IAAI,EAAE,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC;IAYrD,UAAU,IAAK,IAAI;
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"AAEA,OAAO,YAAY,MAAM,aAAa,CAAA;AAGtC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAG7C,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,MAAM,MAAM,aAAa,CAAA;AAChC,OAAO,EAAU,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAwEvD,cAAM,OAAQ,SAAQ,YAAa,YAAW,KAAK,CAAC,WAAW;;IAC7D,MAAM;;;MAAS;IACf,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,GAAG;QAAE,MAAM,CAAC,EAAE,KAAK,CAAA;KAAE,CAAC,GAAG,EAAE,CAAA;IAC/C,MAAM,EAAE,KAAK,CAAC,0BAA0B,CAAA;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,OAAO,EAAE,OAAO,GAAG,SAAS,CAAA;IAC5B,kBAAkB,EAAE,MAAM,CAAC,OAAO,GAAG,SAAS,CAAA;IAC9C,WAAW,EAAE,MAAM,CAAC,OAAO,GAAG,SAAS,CAAA;IACvC,UAAU,EAAE,UAAU,GAAG,SAAS,CAAA;IAClC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,GAAG,IAAI,CAAA;IAChD,sBAAsB,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;gBAM5B,EAAE,EAAE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,0BAA0B;IAe1E,MAAM,CAAC,CAAC,GAAG,MAAM,EAAG,IAAI,EAAE,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC;IAYrD,UAAU,IAAK,IAAI;IAyYb,KAAK;IAkBL,aAAa,CAAE,EAAE,IAAY,EAAE;;KAAK;IAUpC,aAAa,CAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC;IAwBxD,IAAI;IAkBJ,OAAO;IAUb,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IACjF,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,SAAS,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAuH1J,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,OAAO;IAWf,UAAU,CAAE,OAAO,GAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAO;IAUvD,kBAAkB,IAAK,OAAO;IAIxB,OAAO,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,KAAK,CAAC,cAA+B,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC3F,YAAY,CAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAarC,WAAW,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAUhC,mBAAmB,IAAK,IAAI;IAQtB,SAAS,CAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtD,WAAW,CAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9D,OAAO,CAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IASlF,IAAI,CAAE,OAAO,EAAE,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IACrD,IAAI,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAO/F,SAAS,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,WAAW,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IASvI,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,WAAW,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAW3I,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,WAAW,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAwD3I,SAAS,CAAE,OAAO,EAAE,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAgFhE,MAAM,CAAE,OAAO,EAAE,KAAK,CAAC,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC;IACpE,MAAM,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC;IA0BpH,MAAM,CAAE,OAAO,EAAE,KAAK,CAAC,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC;IACpE,MAAM,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC;IA6D9G,MAAM,CACV,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,KAAK,CAAC,SAAS,EAAE,EACvB,OAAO,GAAE,KAAK,CAAC,aAAkB;IA2E7B,IAAI,CAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,OAAO,GAAE,KAAK,CAAC,iBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IA0G1G,qBAAqB,CAAE,gBAAgB,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;IAiBpE,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/C,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,YAAY,GAAG;QAAE,eAAe,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;IACpH,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAiD5E,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,kBAAkB;IAQpB,QAAQ,CAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE,KAAK,CAAC,eAAoB;YAsBhG,iBAAiB;YAQjB,mBAAmB;IAQ3B,IAAI,CAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,GAAE,KAAK,CAAC,iBAAsB;YAmBpF,eAAe;IA2BvB,4BAA4B,CAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAK/E,8BAA8B,CAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IASjF,0BAA0B,CAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;YA6BpE,qBAAqB;YAmBrB,kBAAkB;IA6E1B,SAAS,CAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,GAAE,KAAK,CAAC,iBAAsB;IAUrF,OAAO,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,KAAK,CAAC,cAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;IAsB3E,MAAM,CAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,GAAE,KAAK,CAAC,iBAAsB;IAUlF,MAAM,CAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,GAAE,KAAK,CAAC,iBAAsB;IAUlF,OAAO,CAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,GAAE,KAAK,CAAC,iBAAsB;IASnF,KAAK,CAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,GAAE,KAAK,CAAC,iBAAsB;IAUjF,KAAK,CAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,GAAE,KAAK,CAAC,iBAAsB,GAAG,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC;IAUlH,WAAW,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO;IAsBtF,cAAc,CAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAehD,SAAS,CAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;IAuBnE,WAAW,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,KAAK,CAAC,kBAAuB;IA2BjE,QAAQ,CAAE,IAAI,EAAE,MAAM;IAMtB,WAAW,CAAE,IAAI,EAAE,MAAM;IAiBzB,gBAAgB,CAAE,IAAI,EAAE,MAAM;IAO9B,gBAAgB,CAAE,IAAI,EAAE,MAAM;IAO9B,aAAa,CAAE,IAAI,CAAC,EAAE,MAAM;IA4B5B,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,KAAK,CAAC,iBAAsB,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;IAgGhG,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,KAAK,CAAC,iBAAsB,GAAG,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IA4BxH,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,KAAK,CAAC,eAAoB,GAAG,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;IA0BnG,eAAe,CAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,KAAK,CAAC,iBAAsB,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;IAQjH,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,KAAK,CAAC,iBAAsB,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;IAQrH,OAAO,CAAC,QAAQ;CAWjB;AAED,eAAe,OAAO,CAAA"}
|
package/dist/manager.js
CHANGED
|
@@ -252,7 +252,7 @@ class Manager extends EventEmitter {
|
|
|
252
252
|
// Fail the whole batch so the mistake surfaces and the jobs are retried.
|
|
253
253
|
const err = new Error('perJobResults handler must resolve with an array of job results');
|
|
254
254
|
await this.fail(name, jobs.map(job => job.id), err);
|
|
255
|
-
this.#trackJobsFailed(name, jobs, err);
|
|
255
|
+
await this.#trackJobsFailed(name, jobs, err);
|
|
256
256
|
return;
|
|
257
257
|
}
|
|
258
258
|
// Index the handler's dispositions by job id, keeping only valid entries that reference a job
|
|
@@ -393,7 +393,12 @@ class Manager extends EventEmitter {
|
|
|
393
393
|
async #processJobs(name, jobs, callback, worker, heartbeatRefreshSeconds, perJobResults = false) {
|
|
394
394
|
const jobIds = jobs.map(job => job.id);
|
|
395
395
|
const maxExpiration = jobs.reduce((acc, i) => Math.max(acc, i.expireInSeconds), 0);
|
|
396
|
-
|
|
396
|
+
// Minimum, not maximum: heartbeatSeconds is per-job, and failJobsByHeartbeat fails a job once
|
|
397
|
+
// its OWN heartbeat_on is stale by ITS OWN heartbeat_seconds. A refresh cadence derived from
|
|
398
|
+
// the batch max would let a small-heartbeat job in a mixed batch go stale and get failed out
|
|
399
|
+
// from under a still-running handler before the shared timer ever touches it.
|
|
400
|
+
const heartbeatCandidates = jobs.map(j => j.heartbeatSeconds || 0).filter(s => s > 0);
|
|
401
|
+
const heartbeatSeconds = heartbeatCandidates.length ? Math.min(...heartbeatCandidates) : 0;
|
|
397
402
|
const ac = new AbortController();
|
|
398
403
|
jobs.forEach(job => { job.signal = ac.signal; });
|
|
399
404
|
// Store AbortController on worker so it can be aborted after graceful shutdown
|
|
@@ -578,18 +583,18 @@ class Manager extends EventEmitter {
|
|
|
578
583
|
}
|
|
579
584
|
else {
|
|
580
585
|
const { allowed, excess, groupedJobs } = this.#trackLocalGroupStart(name, jobs);
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if (allowed.length > 0) {
|
|
586
|
-
try {
|
|
587
|
-
await this.#processJobs(name, allowed, callback, worker, heartbeatRefreshSeconds, perJobResults);
|
|
586
|
+
try {
|
|
587
|
+
if (excess.length > 0) {
|
|
588
|
+
const excessIds = excess.map(job => job.id);
|
|
589
|
+
await this.restore(name, excessIds);
|
|
588
590
|
}
|
|
589
|
-
|
|
590
|
-
this.#
|
|
591
|
+
if (allowed.length > 0) {
|
|
592
|
+
await this.#processJobs(name, allowed, callback, worker, heartbeatRefreshSeconds, perJobResults);
|
|
591
593
|
}
|
|
592
594
|
}
|
|
595
|
+
finally {
|
|
596
|
+
this.#trackLocalGroupEnd(name, groupedJobs);
|
|
597
|
+
}
|
|
593
598
|
}
|
|
594
599
|
this.emitWip(name);
|
|
595
600
|
};
|
|
@@ -638,7 +643,13 @@ class Manager extends EventEmitter {
|
|
|
638
643
|
async offWork(name, options = { wait: true }) {
|
|
639
644
|
assert(name, 'queue name is required');
|
|
640
645
|
assert(typeof name === 'string', 'queue name must be a string');
|
|
641
|
-
|
|
646
|
+
// work() returns only the first spawned worker's id (shared as `workId` across every worker
|
|
647
|
+
// it spawned under localConcurrency), so { id } must match on workId too — otherwise only
|
|
648
|
+
// worker 0 of a localConcurrency > 1 call ever stops, and the rest poll forever with no other
|
|
649
|
+
// way to reach them. i.id is still checked so a specific worker id from getWipData() still
|
|
650
|
+
// targets just that one worker. name is always required so a stray/mismatched id can't stop
|
|
651
|
+
// a worker on a different queue.
|
|
652
|
+
const query = (i) => i.name === name && (options?.id ? (i.id === options.id || i.workId === options.id) : true);
|
|
642
653
|
const workers = this.getWorkers().filter(i => query(i) && !i.stopping && !i.stopped);
|
|
643
654
|
if (workers.length === 0) {
|
|
644
655
|
return;
|
|
@@ -886,6 +897,15 @@ class Manager extends EventEmitter {
|
|
|
886
897
|
}
|
|
887
898
|
async insert(name, jobs, options = {}) {
|
|
888
899
|
assert(Array.isArray(jobs), 'jobs argument should be an array');
|
|
900
|
+
const seenIds = new Set();
|
|
901
|
+
for (const job of jobs) {
|
|
902
|
+
if (job.id != null) {
|
|
903
|
+
if (seenIds.has(job.id)) {
|
|
904
|
+
throw new Error(`duplicate job id in insert batch: ${job.id}`);
|
|
905
|
+
}
|
|
906
|
+
seenIds.add(job.id);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
889
909
|
const { table, policy, notify } = await this.getQueueCache(name);
|
|
890
910
|
if (policy === plans.QUEUE_POLICIES.key_strict_fifo) {
|
|
891
911
|
for (const job of jobs) {
|
|
@@ -894,20 +914,35 @@ class Manager extends EventEmitter {
|
|
|
894
914
|
}
|
|
895
915
|
}
|
|
896
916
|
}
|
|
917
|
+
const spy = this.config.__test__enableSpies ? this.#spies.get(name) : undefined;
|
|
918
|
+
// insertJobs ends in ON CONFLICT DO NOTHING, so skipped rows shift the returned rows out of
|
|
919
|
+
// alignment with the input jobs — a positional rows[i] <-> jobs[i] pairing attributes the wrong
|
|
920
|
+
// data to the wrong id. When a spy is watching, assign every job an explicit id up front (the
|
|
921
|
+
// insert COALESCEs id, so this is equivalent to letting the DB generate one) and index data by
|
|
922
|
+
// id, so returned rows can be matched back to their job regardless of any conflicts.
|
|
923
|
+
const dataById = spy ? new Map() : undefined;
|
|
897
924
|
const insertPayload = jobs.map(j => {
|
|
898
925
|
const { blocked, blocking, pendingDependencies, ...rest } = j;
|
|
926
|
+
if (dataById) {
|
|
927
|
+
// Best-effort spy bookkeeping, only reached when __test__enableSpies is set (a test-intended
|
|
928
|
+
// opt-in, off by default). The id we assign here is exactly what the DB would otherwise
|
|
929
|
+
// COALESCE in, so generating it client-side is harmless — and if randomUUID ever fell short,
|
|
930
|
+
// only spy attribution would degrade, never the insert itself.
|
|
931
|
+
rest.id ??= randomUUID();
|
|
932
|
+
dataById.set(rest.id, j.data ?? {});
|
|
933
|
+
}
|
|
899
934
|
return rest;
|
|
900
935
|
});
|
|
901
936
|
const db = this.assertDb(options);
|
|
902
|
-
const spy = this.config.__test__enableSpies ? this.#spies.get(name) : undefined;
|
|
903
937
|
// Return IDs if spy is active for this queue (needed for job tracking)
|
|
904
938
|
const returnId = !!spy || !!options.returnId;
|
|
905
939
|
const sql = plans.insertJobs(this.config.schema, { table, name, returnId, notify: this.#notifyEnabled(notify) });
|
|
906
940
|
const { rows } = await db.executeSql(sql, [JSON.stringify(insertPayload)]);
|
|
907
941
|
if (rows.length) {
|
|
908
942
|
if (spy) {
|
|
909
|
-
|
|
910
|
-
|
|
943
|
+
// dataById is populated for every job when a spy is active
|
|
944
|
+
for (const row of rows) {
|
|
945
|
+
spy.addJob(row.id, name, dataById.get(row.id), 'created');
|
|
911
946
|
}
|
|
912
947
|
}
|
|
913
948
|
return rows.map((i) => i.id);
|
|
@@ -1035,7 +1070,13 @@ class Manager extends EventEmitter {
|
|
|
1035
1070
|
result = await db.executeSql(query.text, query.values);
|
|
1036
1071
|
}
|
|
1037
1072
|
catch (err) {
|
|
1038
|
-
//
|
|
1073
|
+
// The only fetch error we tolerate is a unique-constraint violation (SQLSTATE 23505) from a
|
|
1074
|
+
// policy/singleton index when a concurrent fetch won the same slot — treat that as an empty
|
|
1075
|
+
// fetch. Anything else (a DB outage, a malformed query) must surface: swallowing it turned
|
|
1076
|
+
// every failed fetch into a silent [] with no error event, indistinguishable from an empty
|
|
1077
|
+
// queue. Rethrowing routes it to the worker's onError (emits `error`) or to a direct caller.
|
|
1078
|
+
if (err?.code !== '23505')
|
|
1079
|
+
throw err;
|
|
1039
1080
|
}
|
|
1040
1081
|
const rows = result?.rows || [];
|
|
1041
1082
|
// CockroachDB returns integer columns as strings; normalize them. Even a minimal fetch
|
|
@@ -1220,7 +1261,7 @@ class Manager extends EventEmitter {
|
|
|
1220
1261
|
}
|
|
1221
1262
|
else {
|
|
1222
1263
|
const exp = Math.min(16, retryCount + 1);
|
|
1223
|
-
const delay = retryDelay * (Math.pow(2, exp) / 2 + Math.pow(2, exp) / 2 * Math.random());
|
|
1264
|
+
const delay = Math.max(retryDelay, 1) * (Math.pow(2, exp) / 2 + Math.pow(2, exp) / 2 * Math.random());
|
|
1224
1265
|
// Match the canonical failJobs() SQL: LEAST(retry_delay_max, delay) caps the backoff,
|
|
1225
1266
|
// treating NULL as "no cap" and 0 as a real cap. (`?:` would wrongly treat 0 as no cap.)
|
|
1226
1267
|
const cappedDelay = retryDelayMax != null ? Math.min(retryDelayMax, delay) : delay;
|
|
@@ -1253,7 +1294,7 @@ class Manager extends EventEmitter {
|
|
|
1253
1294
|
]);
|
|
1254
1295
|
// Insert to dead letter queue if failed and has dead_letter configured
|
|
1255
1296
|
if (job.dead_letter) {
|
|
1256
|
-
await tx.executeSql(dlqSql, [job.dead_letter, job.data, jobOutput, job.name, job.id, job.created_on, job.retry_count]);
|
|
1297
|
+
await tx.executeSql(dlqSql, [job.dead_letter, job.data, jobOutput, job.name, job.id, job.created_on, job.retry_count, job.singleton_key, job.heartbeat_seconds]);
|
|
1257
1298
|
}
|
|
1258
1299
|
}
|
|
1259
1300
|
count++;
|
|
@@ -1399,13 +1440,18 @@ class Manager extends EventEmitter {
|
|
|
1399
1440
|
}
|
|
1400
1441
|
async deleteQueue(name) {
|
|
1401
1442
|
Attorney.assertQueueName(name);
|
|
1443
|
+
// Scope the catch to the cache lookup only: a queue that doesn't exist is a no-op. The DELETE
|
|
1444
|
+
// and cache eviction must NOT be swallowed — a transient connection error there previously
|
|
1445
|
+
// resolved as success while the queue (and its stale cache entry) survived.
|
|
1402
1446
|
try {
|
|
1403
1447
|
await this.getQueueCache(name);
|
|
1404
|
-
const sql = plans.deleteQueue(this.config.schema, name, this.config.noAdvisoryLocks);
|
|
1405
|
-
await this.db.executeSql(sql);
|
|
1406
|
-
this.#evictQueueCache(name);
|
|
1407
1448
|
}
|
|
1408
|
-
catch {
|
|
1449
|
+
catch {
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
const sql = plans.deleteQueue(this.config.schema, name, this.config.noAdvisoryLocks);
|
|
1453
|
+
await this.db.executeSql(sql);
|
|
1454
|
+
this.#evictQueueCache(name);
|
|
1409
1455
|
}
|
|
1410
1456
|
async deleteQueuedJobs(name) {
|
|
1411
1457
|
Attorney.assertQueueName(name);
|
package/dist/migrationStore.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ declare function rollback(schema: string, version: number, migrations?: types.Mi
|
|
|
11
11
|
declare function next(schema: string, version: number, migrations?: types.Migration[], noAdvisoryLocks?: boolean): string;
|
|
12
12
|
declare function migrateCommands(schema: string, version: number, migrations?: types.Migration[], noAdvisoryLocks?: boolean, options?: MigrateOptions): MigrationCommands;
|
|
13
13
|
declare function migrate(schema: string, version: number, migrations?: types.Migration[], noAdvisoryLocks?: boolean, options?: MigrateOptions): string;
|
|
14
|
+
declare function getMinVersion(schema: string): number;
|
|
14
15
|
declare function getAll(schema: string, noPartitioning?: boolean, noCovering?: boolean): types.Migration[];
|
|
15
|
-
export { rollback, next, migrate, migrateCommands, getAll, };
|
|
16
|
+
export { rollback, next, migrate, migrateCommands, getAll, getMinVersion, };
|
|
16
17
|
//# sourceMappingURL=migrationStore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrationStore.d.ts","sourceRoot":"","sources":["../src/migrationStore.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAKnC,UAAU,cAAc;IACtB,WAAW,CAAC,EAAE,OAAO,CAAA;IAIrB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;
|
|
1
|
+
{"version":3,"file":"migrationStore.d.ts","sourceRoot":"","sources":["../src/migrationStore.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAKnC,UAAU,cAAc;IACtB,WAAW,CAAC,EAAE,OAAO,CAAA;IAIrB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAgDD,UAAU,iBAAiB;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AASD,iBAAS,QAAQ,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,UAQ5G;AAED,iBAAS,IAAI,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,UAQxG;AAKD,iBAAS,eAAe,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,cAAmB,GAAG,iBAAiB,CA+CrK;AAKD,iBAAS,OAAO,CAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,cAAmB,UAIzI;AAyrBD,iBAAS,aAAa,CAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,iBAAS,MAAM,CAAE,MAAM,EAAE,MAAM,EAAE,cAAc,UAAQ,EAAE,UAAU,UAAQ,GAAG,KAAK,CAAC,SAAS,EAAE,CAoa9F;AAED,OAAO,EACL,QAAQ,EACR,IAAI,EACJ,OAAO,EACP,eAAe,EACf,MAAM,EACN,aAAa,GACd,CAAA"}
|
package/dist/migrationStore.js
CHANGED
|
@@ -4,9 +4,13 @@ import * as types from "./types.js";
|
|
|
4
4
|
// Mirrors the SQL job_table_format() function (src/plans.ts): rewrites a command targeting
|
|
5
5
|
// the base `job` table to target a specific partition table.
|
|
6
6
|
function formatJobTable(command, table) {
|
|
7
|
+
// Anchor both rewrites so a schema name that itself contains these substrings (e.g. `job_intake`)
|
|
8
|
+
// isn't mangled: `.job\b` only matches the base table reference (`schema.job`, not `schema.job_i5`
|
|
9
|
+
// whose `job` is followed by `_`), and `job_iN` only matches the bare index-name tokens (job_i1..9),
|
|
10
|
+
// never the `job_i` inside an arbitrary schema name.
|
|
7
11
|
return command
|
|
8
|
-
.
|
|
9
|
-
.
|
|
12
|
+
.replace(/\.job\b/g, `.${table}`)
|
|
13
|
+
.replace(/\bjob_i(\d+)/g, `${table}_i$1`);
|
|
10
14
|
}
|
|
11
15
|
// Derives the direct index DDL that a job_table_run_async() command would eventually run
|
|
12
16
|
// via BAM, one statement per target table, each prefixed with a provenance comment. The
|
|
@@ -53,6 +57,19 @@ function next(schema, version, migrations, noAdvisoryLocks) {
|
|
|
53
57
|
// programmatically must run `concurrent` statements individually, outside a transaction.
|
|
54
58
|
function migrateCommands(schema, version, migrations, noAdvisoryLocks, options = {}) {
|
|
55
59
|
migrations = migrations || getAll(schema);
|
|
60
|
+
// Refuse to migrate from a real DB version older than the oldest migration can start from.
|
|
61
|
+
// Without this floor, `filter(i => i.previous >= version)` happily selects the whole chain for any
|
|
62
|
+
// version below the minimum `previous`, applying migrations over missing intermediate steps — a
|
|
63
|
+
// cryptic mid-transaction failure, or worse a "success" that stamps the latest version onto an
|
|
64
|
+
// incomplete schema. Version 0 is the sentinel for a full "from scratch" export (getMigrationPlans)
|
|
65
|
+
// and is intentionally exempt.
|
|
66
|
+
// Only floor a valid numeric version; a non-numeric/garbage version falls through to the
|
|
67
|
+
// "Version X not found" assert below. Version 0 is the full-export sentinel and is exempt.
|
|
68
|
+
if (Number.isInteger(version) && version !== 0) {
|
|
69
|
+
const minPrevious = Math.min(...migrations.map(i => i.previous));
|
|
70
|
+
assert(version >= minPrevious, `Cannot migrate pg-boss schema from version ${version}: the oldest supported starting version is ${minPrevious}. ` +
|
|
71
|
+
'Upgrade to a schema at or above that version using an older pg-boss release first.');
|
|
72
|
+
}
|
|
56
73
|
const concurrent = [];
|
|
57
74
|
const result = migrations
|
|
58
75
|
.filter(i => i.previous >= version)
|
|
@@ -732,6 +749,45 @@ const ensureQueueStatsPartitionsFn = {
|
|
|
732
749
|
$$
|
|
733
750
|
`
|
|
734
751
|
};
|
|
752
|
+
// Frozen job_table_format() bodies, one per schema version, so the v37 migration is an immutable
|
|
753
|
+
// snapshot even if plans.jobTableFormatFunction drifts later. 37 is the anchored regexp_replace
|
|
754
|
+
// fix (matches only the base table reference and bare job_iN tokens); 36 is the prior naive
|
|
755
|
+
// replace(), kept solely so v37's rollback restores the exact previous definition.
|
|
756
|
+
const jobTableFormatFn = {
|
|
757
|
+
36: (schema) => `
|
|
758
|
+
CREATE OR REPLACE FUNCTION ${schema}.job_table_format(command text, table_name text)
|
|
759
|
+
RETURNS text AS
|
|
760
|
+
$$
|
|
761
|
+
SELECT format(
|
|
762
|
+
replace(
|
|
763
|
+
replace(command, '.job', '.%1$I'),
|
|
764
|
+
'job_i', '%1$s_i'
|
|
765
|
+
),
|
|
766
|
+
table_name
|
|
767
|
+
);
|
|
768
|
+
$$
|
|
769
|
+
LANGUAGE sql IMMUTABLE;
|
|
770
|
+
`,
|
|
771
|
+
37: (schema) => `
|
|
772
|
+
CREATE OR REPLACE FUNCTION ${schema}.job_table_format(command text, table_name text)
|
|
773
|
+
RETURNS text AS
|
|
774
|
+
$$
|
|
775
|
+
SELECT format(
|
|
776
|
+
regexp_replace(
|
|
777
|
+
regexp_replace(command, '\\.job\\y', '.%1$I', 'g'),
|
|
778
|
+
'\\yjob_i(\\d+)', '%1$s_i\\1', 'g'
|
|
779
|
+
),
|
|
780
|
+
table_name
|
|
781
|
+
);
|
|
782
|
+
$$
|
|
783
|
+
LANGUAGE sql IMMUTABLE;
|
|
784
|
+
`
|
|
785
|
+
};
|
|
786
|
+
// Lowest schema version a migration can start from (the smallest `previous` in the set). Below
|
|
787
|
+
// this there is no chain to apply; callers use it as an honest floor / offline fallback.
|
|
788
|
+
function getMinVersion(schema) {
|
|
789
|
+
return Math.min(...getAll(schema).map(i => i.previous));
|
|
790
|
+
}
|
|
735
791
|
function getAll(schema, noPartitioning = false, noCovering = false) {
|
|
736
792
|
return [
|
|
737
793
|
{
|
|
@@ -877,7 +933,7 @@ function getAll(schema, noPartitioning = false, noCovering = false) {
|
|
|
877
933
|
`ALTER INDEX ${schema}.job_common_i3 RENAME TO job_i3`,
|
|
878
934
|
`ALTER INDEX ${schema}.job_common_i2 RENAME TO job_i2`,
|
|
879
935
|
`ALTER INDEX ${schema}.job_common_i1 RENAME TO job_i1`,
|
|
880
|
-
`SELECT ${schema}.job_table_run('DROP INDEX ${schema}.job_i7')`,
|
|
936
|
+
`SELECT ${schema}.job_table_run('DROP INDEX IF EXISTS ${schema}.job_i7')`,
|
|
881
937
|
createQueueFn[26](schema),
|
|
882
938
|
`DROP FUNCTION ${schema}.job_table_run(text, text, text)`,
|
|
883
939
|
`DROP FUNCTION ${schema}.job_table_run_async(text, int, text, text, text)`,
|
|
@@ -1133,7 +1189,24 @@ function getAll(schema, noPartitioning = false, noCovering = false) {
|
|
|
1133
1189
|
],
|
|
1134
1190
|
// The default change is forward-compatible and harmless to keep, so rollback leaves it in place.
|
|
1135
1191
|
uninstall: []
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
release: '12.26.0',
|
|
1195
|
+
version: 37,
|
|
1196
|
+
previous: 36,
|
|
1197
|
+
// Fix job_table_format(): the naive replace() mangled schema names containing `.job` or
|
|
1198
|
+
// `job_i` (e.g. `job_intake`), rewriting index builds to a nonexistent schema. The anchored
|
|
1199
|
+
// regexp_replace version matches only the base table reference and bare job_iN index tokens.
|
|
1200
|
+
// Only installed where partitioning is enabled — the function is created by plans.create()
|
|
1201
|
+
// solely in that case (plans.ts), so a noPartitioning database has none to replace.
|
|
1202
|
+
install: noPartitioning
|
|
1203
|
+
? []
|
|
1204
|
+
: [jobTableFormatFn[37](schema)],
|
|
1205
|
+
// Restore the prior (naive) definition on rollback so the schema matches v36 exactly.
|
|
1206
|
+
uninstall: noPartitioning
|
|
1207
|
+
? []
|
|
1208
|
+
: [jobTableFormatFn[36](schema)]
|
|
1136
1209
|
}
|
|
1137
1210
|
];
|
|
1138
1211
|
}
|
|
1139
|
-
export { rollback, next, migrate, migrateCommands, getAll, };
|
|
1212
|
+
export { rollback, next, migrate, migrateCommands, getAll, getMinVersion, };
|
package/dist/plans.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { JobMatchStrategy, UpdateQueueOptions } from './types.ts';
|
|
1
|
+
import type { JobMatchStrategy, UpdateQueueOptions, ManagedIndex, ManagedFunction } from './types.ts';
|
|
2
|
+
import type { ExpectedColumns, ExpectedConstraints } from './drifter.ts';
|
|
2
3
|
export interface SqlQuery {
|
|
3
4
|
text: string;
|
|
4
5
|
values: unknown[];
|
|
@@ -9,6 +10,7 @@ export declare const PG_ERROR: {
|
|
|
9
10
|
export declare const DEFAULT_SCHEMA = "pgboss";
|
|
10
11
|
export declare const MIGRATE_RACE_MESSAGE = "division by zero";
|
|
11
12
|
export declare const CREATE_RACE_MESSAGE = "already exists";
|
|
13
|
+
export declare const SINGLE_QUOTE_REGEX: RegExp;
|
|
12
14
|
export declare const JOB_STATES: Readonly<{
|
|
13
15
|
created: "created";
|
|
14
16
|
retry: "retry";
|
|
@@ -25,6 +27,7 @@ export declare const QUEUE_POLICIES: Readonly<{
|
|
|
25
27
|
exclusive: "exclusive";
|
|
26
28
|
key_strict_fifo: "key_strict_fifo";
|
|
27
29
|
}>;
|
|
30
|
+
export declare const COMMON_JOB_TABLE = "job_common";
|
|
28
31
|
interface CreateOptions {
|
|
29
32
|
createSchema?: boolean;
|
|
30
33
|
noTablePartitioning?: boolean;
|
|
@@ -37,6 +40,7 @@ export declare function createTableWarning(schema: string): string;
|
|
|
37
40
|
export declare function createIndexWarning(schema: string): string;
|
|
38
41
|
export declare function createTableJobDependency(schema: string): string;
|
|
39
42
|
export declare function createIndexJobDependencyParent(schema: string): string;
|
|
43
|
+
export declare function jobTableFormatFunction(schema: string): string;
|
|
40
44
|
export declare function createQueue(schema: string, name: string, options: unknown, noAdvisoryLocks?: boolean): string;
|
|
41
45
|
export declare function notifyChannelSql(schema: string): string;
|
|
42
46
|
export declare function notifyQueue(schema: string, name: string): string;
|
|
@@ -178,10 +182,28 @@ export declare function getDependencies(schema: string): string;
|
|
|
178
182
|
export declare function getDependents(schema: string): string;
|
|
179
183
|
export declare function cleanupDependencies(schema: string, table: string, queues: string[], noAdvisoryLocks?: boolean): string;
|
|
180
184
|
export declare function getBlockedKeys(schema: string, table: string): string;
|
|
181
|
-
export declare function getNextBamCommand(schema: string
|
|
185
|
+
export declare function getNextBamCommand(schema: string, { useLiveness }?: {
|
|
186
|
+
useLiveness?: boolean;
|
|
187
|
+
}): string;
|
|
188
|
+
export declare function bamHealDrop(schema: string, command: string): string | null;
|
|
189
|
+
export declare function bamHealProbe(schema: string, command: string): string | null;
|
|
182
190
|
export declare function setBamCompleted(schema: string, id: string): string;
|
|
183
191
|
export declare function setBamFailed(schema: string, id: string, error: string): string;
|
|
184
192
|
export declare function getBamStatus(schema: string): string;
|
|
185
193
|
export declare function getBamEntries(schema: string): string;
|
|
194
|
+
export declare function jobCommonExists(schema: string): string;
|
|
195
|
+
export declare function getManagedQueuePartitions(schema: string): string;
|
|
196
|
+
export declare function getIncompleteBamCommands(schema: string): string;
|
|
197
|
+
export declare function bamCommandIndexName(command: string): string | null;
|
|
198
|
+
interface QueuePartition {
|
|
199
|
+
table: string;
|
|
200
|
+
policy?: string | null;
|
|
201
|
+
}
|
|
202
|
+
export declare const EXPECTED_JOB_STATES: readonly string[];
|
|
203
|
+
export declare function expectedManagedTables(schema: string, partitioned: boolean, partitions?: QueuePartition[]): string[];
|
|
204
|
+
export declare function expectedManagedColumns(schema: string, partitioned: boolean, partitions?: QueuePartition[]): ExpectedColumns[];
|
|
205
|
+
export declare function expectedManagedConstraints(schema: string, partitioned: boolean): ExpectedConstraints[];
|
|
206
|
+
export declare function expectedManagedFunctions(schema: string, partitioned: boolean): ManagedFunction[];
|
|
207
|
+
export declare function expectedManagedIndexes(schema: string, partitioned: boolean, partitions?: QueuePartition[]): ManagedIndex[];
|
|
186
208
|
export {};
|
|
187
209
|
//# sourceMappingURL=plans.d.ts.map
|