pg-boss 12.37.0 → 12.38.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/activity.d.ts.map +1 -1
- package/dist/activity.js +2 -1
- package/dist/attorney.d.ts +4 -1
- package/dist/attorney.d.ts.map +1 -1
- package/dist/attorney.js +28 -1
- package/dist/bam.d.ts +1 -1
- package/dist/bam.d.ts.map +1 -1
- package/dist/bam.js +154 -15
- package/dist/boss.d.ts.map +1 -1
- package/dist/boss.js +113 -30
- package/dist/cli.js +14 -3
- package/dist/db.d.ts +4 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +43 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -20
- package/dist/manager.d.ts +12 -11
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +294 -57
- package/dist/migrationStore.d.ts.map +1 -1
- package/dist/migrationStore.js +219 -45
- package/dist/notifier.d.ts.map +1 -1
- package/dist/notifier.js +11 -4
- package/dist/plans.d.ts +53 -20
- package/dist/plans.d.ts.map +1 -1
- package/dist/plans.js +758 -208
- package/dist/registrar.d.ts +1 -1
- package/dist/registrar.d.ts.map +1 -1
- package/dist/registrar.js +10 -2
- package/dist/schema.json +84 -2
- package/dist/timekeeper.d.ts +1 -1
- package/dist/timekeeper.d.ts.map +1 -1
- package/dist/timekeeper.js +10 -5
- package/dist/tools.d.ts +2 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +7 -4
- package/dist/types.d.ts +55 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/warning.d.ts +5 -0
- package/dist/warning.d.ts.map +1 -1
- package/dist/warning.js +18 -4
- package/dist/worker.d.ts +3 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +10 -0
- package/package.json +8 -8
package/dist/activity.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../src/activity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAqB,MAAM,YAAY,CAAA;AAI9D;;;GAGG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,SAAS,EAAG,EAAE,EAAE,CAAC,GAAG,CAAC,CAExD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,SAAS,EAAG,EAAE,EAAE,CAAC,GAAG;IAAE,EAAE,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../src/activity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAqB,MAAM,YAAY,CAAA;AAI9D;;;GAGG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,SAAS,EAAG,EAAE,EAAE,CAAC,GAAG,CAAC,CAExD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,SAAS,EAAG,EAAE,EAAE,CAAC,GAAG;IAAE,EAAE,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAoFlG"}
|
package/dist/activity.js
CHANGED
|
@@ -51,7 +51,8 @@ export function trackActivity(db) {
|
|
|
51
51
|
});
|
|
52
52
|
const wrapDb = (inner) => {
|
|
53
53
|
const overrides = {
|
|
54
|
-
|
|
54
|
+
// Every argument, not only text and values: pg-boss's own Db takes BAM's stop signals as a third.
|
|
55
|
+
executeSql: track((...args) => inner.executeSql(...args))
|
|
55
56
|
};
|
|
56
57
|
// Only when present: callers detect the capability with typeof.
|
|
57
58
|
if (typeof inner.beginTransaction === 'function') {
|
package/dist/attorney.d.ts
CHANGED
|
@@ -20,7 +20,10 @@ declare function checkWorkArgs(name: string, args: any[]): {
|
|
|
20
20
|
declare function checkFetchArgs(name: string, options: any): void;
|
|
21
21
|
declare function getConfig(value: string | types.ConstructorOptions): types.ResolvedConstructorOptions;
|
|
22
22
|
declare function assertPostgresObjectName(name: string): void;
|
|
23
|
+
declare function assertListLimit(method: string, limit?: number): number;
|
|
24
|
+
declare function assertListAfter(method: string, after: unknown, id?: boolean): void;
|
|
25
|
+
declare function assertListAfterRow(method: string, after: unknown, fields: [string, string], id?: string): void;
|
|
23
26
|
declare function assertQueueName(name: string): void;
|
|
24
27
|
declare function assertKey(key: string): void;
|
|
25
|
-
export { assertKey, COMPATIBILITY_FLAGS, assertPostgresObjectName, assertQueueName, checkFetchArgs, checkSendArgs, checkUpdateArgs, checkWorkArgs, getConfig, pinZonelessDateTime, POLICY, validateFlowJobs, validateGroupConfig, validateQueueArgs };
|
|
28
|
+
export { assertKey, assertListAfter, assertListAfterRow, assertListLimit, COMPATIBILITY_FLAGS, assertPostgresObjectName, assertQueueName, checkFetchArgs, checkSendArgs, checkUpdateArgs, checkWorkArgs, getConfig, pinZonelessDateTime, POLICY, validateFlowJobs, validateGroupConfig, validateQueueArgs };
|
|
26
29
|
//# sourceMappingURL=attorney.d.ts.map
|
package/dist/attorney.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attorney.d.ts","sourceRoot":"","sources":["../src/attorney.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,KAAK,MAAM,YAAY,CAAA;AAExC,QAAA,MAAM,MAAM;;;;CAIX,CAAA;AAKD,QAAA,MAAM,mBAAmB,6RAcf,CAAA;AA+EV,iBAAS,iBAAiB,CAAE,MAAM,GAAE,GAAQ,QAc3C;AAiBD,iBAAS,mBAAmB,CAAE,KAAK,EAAE,MAAM,UAE1C;AAED,iBAAS,aAAa,CAAE,IAAI,EAAE,GAAG,GAAG,KAAK,CAAC,OAAO,CAgDhD;AAOD,iBAAS,eAAe,CAAE,IAAI,EAAE,GAAG,EAAE,EAAE,MAAc,EAAE;;CAAK,GAAG,KAAK,CAAC,OAAO,CA4D3E;AAED,iBAAS,mBAAmB,CAAE,MAAM,EAAE,GAAG,QAOxC;AAED,iBAAS,gBAAgB,CAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,QA2D/C;AA2GD,iBAAS,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG;IAClD,OAAO,EAAE,KAAK,CAAC,mBAAmB,CAAA;IAClC,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;CACjC,CAgCA;AAqBD,iBAAS,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,QAQlD;AAED,iBAAS,SAAS,CAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,kBAAkB,GAAG,KAAK,CAAC,0BAA0B,CA4B9F;AA4JD,iBAAS,wBAAwB,CAAE,IAAI,EAAE,MAAM,QAiC9C;AAED,iBAAS,eAAe,CAAE,IAAI,EAAE,MAAM,QAIrC;AAED,iBAAS,SAAS,CAAE,GAAG,EAAE,MAAM,QAI9B;AA+LD,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,wBAAwB,EACxB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,aAAa,EACb,SAAS,EACT,mBAAmB,EACnB,MAAM,EACN,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EAClB,CAAA"}
|
|
1
|
+
{"version":3,"file":"attorney.d.ts","sourceRoot":"","sources":["../src/attorney.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,KAAK,MAAM,YAAY,CAAA;AAExC,QAAA,MAAM,MAAM;;;;CAIX,CAAA;AAKD,QAAA,MAAM,mBAAmB,6RAcf,CAAA;AA+EV,iBAAS,iBAAiB,CAAE,MAAM,GAAE,GAAQ,QAc3C;AAiBD,iBAAS,mBAAmB,CAAE,KAAK,EAAE,MAAM,UAE1C;AAED,iBAAS,aAAa,CAAE,IAAI,EAAE,GAAG,GAAG,KAAK,CAAC,OAAO,CAgDhD;AAOD,iBAAS,eAAe,CAAE,IAAI,EAAE,GAAG,EAAE,EAAE,MAAc,EAAE;;CAAK,GAAG,KAAK,CAAC,OAAO,CA4D3E;AAED,iBAAS,mBAAmB,CAAE,MAAM,EAAE,GAAG,QAOxC;AAED,iBAAS,gBAAgB,CAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,QA2D/C;AA2GD,iBAAS,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG;IAClD,OAAO,EAAE,KAAK,CAAC,mBAAmB,CAAA;IAClC,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;CACjC,CAgCA;AAqBD,iBAAS,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,QAQlD;AAED,iBAAS,SAAS,CAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,kBAAkB,GAAG,KAAK,CAAC,0BAA0B,CA4B9F;AA4JD,iBAAS,wBAAwB,CAAE,IAAI,EAAE,MAAM,QAiC9C;AAOD,iBAAS,eAAe,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,MAA2B,GAAG,MAAM,CAIpF;AAYD,iBAAS,eAAe,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,UAAQ,GAAG,IAAI,CAI1E;AAID,iBAAS,kBAAkB,CAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAKxG;AAED,iBAAS,eAAe,CAAE,IAAI,EAAE,MAAM,QAIrC;AAED,iBAAS,SAAS,CAAE,GAAG,EAAE,MAAM,QAI9B;AA+LD,OAAO,EACL,SAAS,EACT,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,aAAa,EACb,SAAS,EACT,mBAAmB,EACnB,MAAM,EACN,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EAClB,CAAA"}
|
package/dist/attorney.js
CHANGED
|
@@ -572,6 +572,33 @@ function assertPostgresObjectName(name) {
|
|
|
572
572
|
assert(!/\W/.test(name), `Schema name ${JSON.stringify(name)} can only contain alphanumeric characters or underscores when unquoted.${remedy}`);
|
|
573
573
|
assert(!/^\d/.test(name), `Schema name ${JSON.stringify(name)} cannot start with a number when unquoted.${remedy}`);
|
|
574
574
|
}
|
|
575
|
+
const LIST_LIMIT_DEFAULT = 1000;
|
|
576
|
+
const LIST_LIMIT_MAX = 100_000;
|
|
577
|
+
// The limit of a public list read: an integer from 1 to LIST_LIMIT_MAX, LIST_LIMIT_DEFAULT when
|
|
578
|
+
// left out. Internal callers that need every row call below the public method instead.
|
|
579
|
+
function assertListLimit(method, limit = LIST_LIMIT_DEFAULT) {
|
|
580
|
+
assert(Number.isInteger(limit) && limit >= 1 && limit <= LIST_LIMIT_MAX, `${method}: limit must be an integer between 1 and ${LIST_LIMIT_MAX}`);
|
|
581
|
+
return limit;
|
|
582
|
+
}
|
|
583
|
+
// Any spelling the uuid type accepts, in either case: 32 hex digits, a hyphen allowed after any group
|
|
584
|
+
// of four but the last, and the whole optionally inside one pair of braces. A read that pages by id
|
|
585
|
+
// checks it here, so a malformed one is a usage error rather than the database's.
|
|
586
|
+
const UUID_INPUT = /^(?:\{[0-9a-fA-F]{4}(?:-?[0-9a-fA-F]{4}){7}\}|[0-9a-fA-F]{4}(?:-?[0-9a-fA-F]{4}){7})$/;
|
|
587
|
+
function isUuid(value) {
|
|
588
|
+
return UUID_INPUT.test(value);
|
|
589
|
+
}
|
|
590
|
+
// The `after` of a list read that pages by one value: a queue name, a key or, with `id`, an id.
|
|
591
|
+
function assertListAfter(method, after, id = false) {
|
|
592
|
+
assert(after === undefined || (typeof after === 'string' && after.length > 0), `${method}: after must be a non-empty string`);
|
|
593
|
+
assert(!id || after === undefined || isUuid(after), `${method}: after must be a uuid`);
|
|
594
|
+
}
|
|
595
|
+
// The `after` of a list read that pages by a row: the previous page's last row, of which only the
|
|
596
|
+
// two named fields are read. With `id`, that field must be a uuid.
|
|
597
|
+
function assertListAfterRow(method, after, fields, id) {
|
|
598
|
+
assert(after === undefined || (typeof after === 'object' && after !== null &&
|
|
599
|
+
fields.every(field => typeof after[field] === 'string')), `${method}: after must be an object with string ${fields.join(' and ')}`);
|
|
600
|
+
assert(!id || after === undefined || isUuid(after[id]), `${method}: after.${id} must be a uuid`);
|
|
601
|
+
}
|
|
575
602
|
function assertQueueName(name) {
|
|
576
603
|
assert(name, 'Name is required');
|
|
577
604
|
assert(typeof name === 'string', 'Name must be a string');
|
|
@@ -694,4 +721,4 @@ function applyFlowConfig(config) {
|
|
|
694
721
|
assert(!('flowIntervalSeconds' in config) || config.flowIntervalSeconds >= minInterval, `configuration assert: flowIntervalSeconds must be at least ${minInterval} seconds`);
|
|
695
722
|
config.flowIntervalSeconds = config.flowIntervalSeconds || 5;
|
|
696
723
|
}
|
|
697
|
-
export { assertKey, COMPATIBILITY_FLAGS, assertPostgresObjectName, assertQueueName, checkFetchArgs, checkSendArgs, checkUpdateArgs, checkWorkArgs, getConfig, pinZonelessDateTime, POLICY, validateFlowJobs, validateGroupConfig, validateQueueArgs };
|
|
724
|
+
export { assertKey, assertListAfter, assertListAfterRow, assertListLimit, COMPATIBILITY_FLAGS, assertPostgresObjectName, assertQueueName, checkFetchArgs, checkSendArgs, checkUpdateArgs, checkWorkArgs, getConfig, pinZonelessDateTime, POLICY, validateFlowJobs, validateGroupConfig, validateQueueArgs };
|
package/dist/bam.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ declare class Bam extends EventEmitter implements types.EventsMixin {
|
|
|
9
9
|
constructor(db: types.IDatabase, config: types.ResolvedConstructorOptions);
|
|
10
10
|
get working(): boolean;
|
|
11
11
|
start(): Promise<void>;
|
|
12
|
-
stop(): Promise<void>;
|
|
12
|
+
stop(deadline?: Promise<void>): Promise<void>;
|
|
13
13
|
}
|
|
14
14
|
export default Bam;
|
|
15
15
|
//# sourceMappingURL=bam.d.ts.map
|
package/dist/bam.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bam.d.ts","sourceRoot":"","sources":["../src/bam.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bam.d.ts","sourceRoot":"","sources":["../src/bam.ts"],"names":[],"mappings":"AACA,OAAO,YAAY,MAAM,aAAa,CAAA;AAKtC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAmDnC,cAAM,GAAI,SAAQ,YAAa,YAAW,KAAK,CAAC,WAAW;;IAQzD,MAAM;;;MAAS;gBAGb,EAAE,EAAE,KAAK,CAAC,SAAS,EACnB,MAAM,EAAE,KAAK,CAAC,0BAA0B;IAU1C,IAAI,OAAO,IAAK,OAAO,CAEtB;IAEK,KAAK;IAiBL,IAAI,CAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC;CA+UrC;AAED,eAAe,GAAG,CAAA"}
|
package/dist/bam.js
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
1
2
|
import EventEmitter from 'node:events';
|
|
2
3
|
import { ClaimTimer } from "./claimTimer.js";
|
|
4
|
+
import Db from "./db.js";
|
|
3
5
|
import * as plans from "./plans.js";
|
|
4
|
-
import { delay } from "./tools.js";
|
|
6
|
+
import { delay, unwrapSQLResult } from "./tools.js";
|
|
5
7
|
import * as types from "./types.js";
|
|
6
8
|
const events = {
|
|
7
9
|
error: 'error',
|
|
8
10
|
bam: 'bam'
|
|
9
11
|
};
|
|
12
|
+
// How often stop() repeats a cancel that found nothing to cancel. It can land between two statements
|
|
13
|
+
// of one row, or before the statement reaches the server. It also finds nothing when the role cannot
|
|
14
|
+
// see the statement in pg_stat_activity, and then stop() keeps trying until the statement ends.
|
|
15
|
+
const CANCEL_RETRY_MS = 1000;
|
|
10
16
|
class Bam extends EventEmitter {
|
|
11
17
|
#stopped;
|
|
12
18
|
#working;
|
|
13
19
|
#pollTimer;
|
|
20
|
+
#running;
|
|
14
21
|
#db;
|
|
15
22
|
#config;
|
|
16
23
|
events = events;
|
|
@@ -32,16 +39,95 @@ class Bam extends EventEmitter {
|
|
|
32
39
|
setImmediate(() => this.#onPoll());
|
|
33
40
|
this.#pollTimer.start();
|
|
34
41
|
}
|
|
35
|
-
|
|
42
|
+
// Waits for a running command until deadline resolves, then ends it (see Ending) and waits for
|
|
43
|
+
// that to land, so the row is recorded before index.ts closes the pool. Without a deadline it waits
|
|
44
|
+
// for the command to finish.
|
|
45
|
+
async stop(deadline) {
|
|
36
46
|
if (this.#stopped)
|
|
37
47
|
return;
|
|
38
48
|
this.#stopped = true;
|
|
39
49
|
this.#pollTimer.stop();
|
|
40
50
|
this.#pollTimer = undefined;
|
|
51
|
+
let expired = false;
|
|
52
|
+
deadline?.then(() => { expired = true; });
|
|
53
|
+
let endedAt = 0;
|
|
41
54
|
while (this.#working) {
|
|
55
|
+
const running = this.#running;
|
|
56
|
+
if (expired && running) {
|
|
57
|
+
running.stopped = true;
|
|
58
|
+
if (!running.ended && running.ending !== 'wait' && Date.now() - endedAt >= CANCEL_RETRY_MS) {
|
|
59
|
+
endedAt = Date.now();
|
|
60
|
+
await this.#end(running);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
42
63
|
await delay(10);
|
|
43
64
|
}
|
|
44
65
|
}
|
|
66
|
+
async #end(running) {
|
|
67
|
+
if (running.ending === 'abandon' || running.ending === 'drop') {
|
|
68
|
+
running.abort.abort();
|
|
69
|
+
running.ended = true;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Before the cancel reads pg_stat_activity, so a statement that ends after the read closes its
|
|
73
|
+
// connection rather than returning it to the pool with the cancel still on its way.
|
|
74
|
+
running.abort.abort();
|
|
75
|
+
try {
|
|
76
|
+
const { rows } = await this.#db.executeSql(plans.cancelBamCommand(running.id));
|
|
77
|
+
// Only ever set here: a statement that stop() kept from starting may have set it while the
|
|
78
|
+
// cancel was in flight, and the cancel then finds nothing.
|
|
79
|
+
if (rows.some(row => row.cancelled))
|
|
80
|
+
running.ended = true;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
// Usually a role that can see the statement but not signal its backend, which a retry will not
|
|
84
|
+
// change. Report it once and wait for the statement.
|
|
85
|
+
running.ending = 'wait';
|
|
86
|
+
this.emit(events.error, err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Runs one of a claimed row's statements so stop() can end it. See Ending.
|
|
90
|
+
async #execute(running, sql) {
|
|
91
|
+
if (running.stopped) {
|
|
92
|
+
this.#notStarted(running);
|
|
93
|
+
}
|
|
94
|
+
const cockroach = this.#config.backend === 'cockroachdb';
|
|
95
|
+
if (!(this.#db instanceof Db)) {
|
|
96
|
+
running.ending = cockroach ? 'wait' : 'cancel';
|
|
97
|
+
}
|
|
98
|
+
else if (cockroach) {
|
|
99
|
+
running.ending = 'drop';
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
running.ending = !this.#config.noIndexProgressView && plans.isConcurrentIndexCommand(sql) ? 'abandon' : 'cancel';
|
|
103
|
+
}
|
|
104
|
+
// CockroachDB drops comments from the statement text it reports, so the tag only serves Postgres.
|
|
105
|
+
const text = cockroach ? sql : `${plans.bamCommandTag(running.id)}\n${sql}`;
|
|
106
|
+
try {
|
|
107
|
+
const result = !(this.#db instanceof Db)
|
|
108
|
+
? await this.#db.executeSql(text)
|
|
109
|
+
: await this.#db.executeSql(text, undefined, running.ending === 'cancel'
|
|
110
|
+
? { retire: running.abort.signal }
|
|
111
|
+
: { signal: running.abort.signal });
|
|
112
|
+
// The statement is over. Until the next one starts, which checks stopped first, stop() has
|
|
113
|
+
// nothing to end, and a failure of the row's other statements is not the stop's doing.
|
|
114
|
+
running.ending = 'wait';
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
// Db throws the abort reason when stop() aborted before the statement reached a connection.
|
|
119
|
+
if (running.abort.signal.aborted && err === running.abort.signal.reason) {
|
|
120
|
+
this.#notStarted(running);
|
|
121
|
+
}
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
#notStarted(running) {
|
|
126
|
+
// Nothing is left running for the row to wait on, whatever the previous statement was.
|
|
127
|
+
running.ending = 'wait';
|
|
128
|
+
running.ended = true;
|
|
129
|
+
throw new Error('stop() ended the command before it ran');
|
|
130
|
+
}
|
|
45
131
|
async #onPoll() {
|
|
46
132
|
if (this.#stopped || this.#working || !this.#config.migrate)
|
|
47
133
|
return;
|
|
@@ -69,12 +155,20 @@ class Bam extends EventEmitter {
|
|
|
69
155
|
this.#working = false;
|
|
70
156
|
}
|
|
71
157
|
}
|
|
158
|
+
// Runs commands back to back once a poll finds one, rather than one per bamIntervalSeconds, so a
|
|
159
|
+
// migration's builds land one after another. The first claim may retry a failed or stale row; the
|
|
160
|
+
// rest take pending rows only, and the burst ends when none is left or the runner stops.
|
|
72
161
|
async #processCommands() {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
162
|
+
let pendingOnly = false;
|
|
163
|
+
while (!this.#stopped && await this.#processNextCommand(pendingOnly)) {
|
|
164
|
+
pendingOnly = true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Claims and runs one command. Returns whether a command was claimed, whatever its outcome.
|
|
168
|
+
async #processNextCommand(pendingOnly) {
|
|
169
|
+
const entry = await this.#getNextCommand(pendingOnly);
|
|
76
170
|
if (!entry)
|
|
77
|
-
return;
|
|
171
|
+
return false;
|
|
78
172
|
if (this.#config.__test__delay_bam_claim_ms) {
|
|
79
173
|
await delay(this.#config.__test__delay_bam_claim_ms);
|
|
80
174
|
}
|
|
@@ -90,8 +184,10 @@ class Bam extends EventEmitter {
|
|
|
90
184
|
catch (err) {
|
|
91
185
|
this.emit(events.error, err);
|
|
92
186
|
}
|
|
93
|
-
return;
|
|
187
|
+
return false;
|
|
94
188
|
}
|
|
189
|
+
const running = { id: entry.id, stopped: false, ended: false, ending: 'wait', abort: new AbortController() };
|
|
190
|
+
this.#running = running;
|
|
95
191
|
this.emit(events.bam, {
|
|
96
192
|
id: entry.id,
|
|
97
193
|
name: entry.name,
|
|
@@ -99,14 +195,19 @@ class Bam extends EventEmitter {
|
|
|
99
195
|
queue: entry.queue,
|
|
100
196
|
table: entry.table
|
|
101
197
|
});
|
|
198
|
+
// Set when a CockroachDB build returns without its index because an earlier build of it is still
|
|
199
|
+
// running as a schema change job. The row fails until that job ends, which is not an error.
|
|
200
|
+
let buildPending = false;
|
|
102
201
|
try {
|
|
103
202
|
let alreadyBuilt = false;
|
|
203
|
+
// Non-null for a CONCURRENTLY index build, which is checked against the catalog before a
|
|
204
|
+
// re-attempt and after the build returns.
|
|
205
|
+
const probeSql = plans.bamHealProbe(this.#config.schema, entry.command);
|
|
104
206
|
// A re-attempted command (a stale in_progress reclaim, or a retry of a prior 'failed', including
|
|
105
207
|
// failed rows left by older releases) needs the catalog consulted before the command is re-run,
|
|
106
208
|
// because the row keeps the text it was enqueued with and that text may not be idempotent.
|
|
107
209
|
// Probe indisvalid on every backend: pg_index is readable everywhere, and both outcomes matter.
|
|
108
210
|
if (entry.reattempt) {
|
|
109
|
-
const probeSql = plans.bamHealProbe(this.#config.schema, entry.command);
|
|
110
211
|
if (probeSql) {
|
|
111
212
|
const { rows } = await this.#db.executeSql(probeSql);
|
|
112
213
|
if (rows[0]?.invalid) {
|
|
@@ -117,7 +218,7 @@ class Bam extends EventEmitter {
|
|
|
117
218
|
// DROP ... CONCURRENTLY isn't their model.
|
|
118
219
|
if (!this.#config.noIndexProgressView) {
|
|
119
220
|
// Non-null wherever the probe was: both recognise the same CONCURRENTLY commands.
|
|
120
|
-
await this.#
|
|
221
|
+
await this.#execute(running, plans.bamHealDrop(this.#config.schema, entry.command));
|
|
121
222
|
}
|
|
122
223
|
}
|
|
123
224
|
else if (rows[0]) {
|
|
@@ -134,7 +235,25 @@ class Bam extends EventEmitter {
|
|
|
134
235
|
}
|
|
135
236
|
}
|
|
136
237
|
if (!alreadyBuilt) {
|
|
137
|
-
await this.#
|
|
238
|
+
await this.#execute(running, entry.command);
|
|
239
|
+
// A build can return without leaving a valid index. On CockroachDB, a retry of a build whose
|
|
240
|
+
// connection stop() dropped returns at once while the first build still runs as a schema
|
|
241
|
+
// change job, because IF NOT EXISTS matches the index it is adding, which pg_index does not
|
|
242
|
+
// list yet. On Postgres, IF NOT EXISTS skips over an INVALID index of the same name. Marking
|
|
243
|
+
// the row completed then would release the commands behind it, and nothing would build the
|
|
244
|
+
// index again if that job failed. Failing the row has a later attempt check again, where the
|
|
245
|
+
// probe completes it or heals the INVALID index. Without liveness there is no heal, so an
|
|
246
|
+
// INVALID index would fail every retry, and the row completes as before.
|
|
247
|
+
if (probeSql) {
|
|
248
|
+
const { rows } = await this.#db.executeSql(probeSql);
|
|
249
|
+
if (!rows[0] || (rows[0].invalid && !this.#config.noIndexProgressView)) {
|
|
250
|
+
buildPending = !rows[0] && this.#config.backend === 'cockroachdb';
|
|
251
|
+
const name = plans.bamCommandIndexName(entry.command);
|
|
252
|
+
throw new Error(rows[0]
|
|
253
|
+
? `index ${name} is invalid after the build`
|
|
254
|
+
: `index ${name} does not exist after the build. An earlier build of it may still be running.`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
138
257
|
}
|
|
139
258
|
// Record the outcome even when a stop landed while the command was running. stop() waits out
|
|
140
259
|
// #working and index.ts closes the pool only after #bam.stop() resolves, so the UPDATE is safe
|
|
@@ -154,6 +273,11 @@ class Bam extends EventEmitter {
|
|
|
154
273
|
});
|
|
155
274
|
}
|
|
156
275
|
catch (err) {
|
|
276
|
+
// The one failure that leaves the row in_progress: stop() dropped the connection under a
|
|
277
|
+
// concurrent index statement that the server is still running. See Ending.
|
|
278
|
+
if (running.ended && running.ending === 'abandon') {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
157
281
|
// Same reasoning as the completed path: a stop must not leave the row in_progress. This write
|
|
158
282
|
// is guarded because it commonly runs on the connection that just failed - a terminated backend
|
|
159
283
|
// or dropped connection fails the command AND the UPDATE that would record it. Letting that
|
|
@@ -165,7 +289,11 @@ class Bam extends EventEmitter {
|
|
|
165
289
|
catch (markErr) {
|
|
166
290
|
this.emit(events.error, markErr);
|
|
167
291
|
}
|
|
168
|
-
|
|
292
|
+
// A failure stop() caused is not an error. The row still records it, so the next attempt runs
|
|
293
|
+
// as a reattempt and heals what the cancel left.
|
|
294
|
+
if (!running.ended && !buildPending) {
|
|
295
|
+
this.emit(events.error, err);
|
|
296
|
+
}
|
|
169
297
|
this.emit(events.bam, {
|
|
170
298
|
id: entry.id,
|
|
171
299
|
name: entry.name,
|
|
@@ -175,11 +303,22 @@ class Bam extends EventEmitter {
|
|
|
175
303
|
error: String(err)
|
|
176
304
|
});
|
|
177
305
|
}
|
|
306
|
+
finally {
|
|
307
|
+
this.#running = undefined;
|
|
308
|
+
}
|
|
309
|
+
return true;
|
|
178
310
|
}
|
|
179
|
-
async #getNextCommand() {
|
|
180
|
-
const sql = plans.
|
|
181
|
-
|
|
182
|
-
|
|
311
|
+
async #getNextCommand(pendingOnly) {
|
|
312
|
+
const sql = plans.claimBamCommand(this.#config.schema, {
|
|
313
|
+
useLiveness: !this.#config.noIndexProgressView,
|
|
314
|
+
pendingOnly,
|
|
315
|
+
noAdvisoryLocks: this.#config.noAdvisoryLocks
|
|
316
|
+
});
|
|
317
|
+
const { rows } = unwrapSQLResult(await this.#db.executeSql(sql));
|
|
318
|
+
// The lock's own row comes first, so the claim is told apart by a column only it returns. Without
|
|
319
|
+
// the lock's row the db returned only the last statement, COMMIT, and a claim it made is lost.
|
|
320
|
+
assert(this.#config.noAdvisoryLocks || rows.some(row => row && 'pg_advisory_xact_lock' in row), 'bam claim returned no lock row: a custom db must return the rows of every statement in a multi-statement query');
|
|
321
|
+
return rows.find(row => row?.claimedStartedOn !== undefined) ?? null;
|
|
183
322
|
}
|
|
184
323
|
async #releaseCommand(entry) {
|
|
185
324
|
const sql = plans.releaseBamCommand(this.#config.schema, entry.id, entry.priorStatus, entry.priorStartedOn ?? null, entry.claimedStartedOn);
|
package/dist/boss.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boss.d.ts","sourceRoot":"","sources":["../src/boss.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"boss.d.ts","sourceRoot":"","sources":["../src/boss.ts"],"names":[],"mappings":"AACA,OAAO,YAAY,MAAM,aAAa,CAAA;AAEtC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAGvC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAkInC,cAAM,IAAK,SAAQ,YAAa,YAAW,KAAK,CAAC,WAAW;;IAsC1D,MAAM;;;MAAS;gBAGb,EAAE,EAAE,KAAK,CAAC,SAAS,EACnB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,KAAK,CAAC,0BAA0B;IAa1C,IAAI,WAAW,IAAK,OAAO,CAE1B;IAIK,KAAK;IAiBL,IAAI;IAwHJ,SAAS,CAAE,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,gBAAgB;IAizBvF;;;;OAIG;IACG,kBAAkB,CAAE,OAAO,CAAC,EAAE,KAAK,CAAC,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAwB7E;AAED,eAAe,IAAI,CAAA"}
|
package/dist/boss.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
1
2
|
import EventEmitter from 'node:events';
|
|
2
3
|
import { ClaimTimer } from "./claimTimer.js";
|
|
3
4
|
import * as plans from "./plans.js";
|
|
@@ -242,10 +243,14 @@ class Boss extends EventEmitter {
|
|
|
242
243
|
if (!this.#config.persistQueueStats || !this.#config.queueStatRetentionDays) {
|
|
243
244
|
return;
|
|
244
245
|
}
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
246
|
+
const { schema, queueStatRetentionDays: days } = this.#config;
|
|
247
|
+
if (this.#config.noTablePartitioning) {
|
|
248
|
+
const batchSize = this.#config.__test__walk_batch_size;
|
|
249
|
+
await this.#walk((after) => plans.deleteOldQueueStats(schema, days, { after, batchSize }));
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
await this.#executeQuery(plans.dropOldQueueStatsPartitions(schema, days));
|
|
253
|
+
}
|
|
249
254
|
}
|
|
250
255
|
// Waits for a pass in flight rather than skipping like #onSupervise, since the caller asked for
|
|
251
256
|
// this one. Holding #maintaining keeps it from overlapping the background pass and makes stop()
|
|
@@ -392,38 +397,117 @@ class Boss extends EventEmitter {
|
|
|
392
397
|
return;
|
|
393
398
|
// CockroachDB rejects the multi-mutation failJobs() CTE these use, so under noMultiMutationCte
|
|
394
399
|
// route expiry through the manager's split select/delete/re-insert variants instead.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const sql = plans.failJobsByTimeout(this.#config.schema, table, queues, this.#config.noAdvisoryLocks);
|
|
400
|
-
await this.#executeQuery(sql);
|
|
401
|
-
}
|
|
400
|
+
const limit = this.#config.__test__expiry_batch_size ?? plans.EXPIRY_BATCH_SIZE;
|
|
401
|
+
await this.#repeatWhileFull(limit, () => this.#config.noMultiMutationCte
|
|
402
|
+
? this.#manager.failJobsByTimeoutDistributed(table, queues, limit)
|
|
403
|
+
: this.#expiryBatch(plans.failJobsByTimeout(this.#config.schema, table, queues, this.#config.noAdvisoryLocks, limit)));
|
|
402
404
|
if (this.#stopping)
|
|
403
405
|
return;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
else {
|
|
408
|
-
const heartbeatSql = plans.failJobsByHeartbeat(this.#config.schema, table, queues, this.#config.noAdvisoryLocks);
|
|
409
|
-
await this.#executeQuery(heartbeatSql);
|
|
410
|
-
}
|
|
406
|
+
await this.#repeatWhileFull(limit, () => this.#config.noMultiMutationCte
|
|
407
|
+
? this.#manager.failJobsByHeartbeatDistributed(table, queues, limit)
|
|
408
|
+
: this.#expiryBatch(plans.failJobsByHeartbeat(this.#config.schema, table, queues, this.#config.noAdvisoryLocks, limit)));
|
|
411
409
|
}
|
|
412
410
|
}
|
|
411
|
+
// Runs a maintenance sweep a batch at a time until a batch picks fewer than `limit` rows. A stop
|
|
412
|
+
// between batches leaves the rest for the next pass.
|
|
413
|
+
async #repeatWhileFull(limit, batch) {
|
|
414
|
+
while (!this.#stopping) {
|
|
415
|
+
if (await batch() < limit)
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// CockroachDB returns the count as a string.
|
|
420
|
+
async #expiryBatch(sql) {
|
|
421
|
+
const { rows } = await this.#executeQuery(sql);
|
|
422
|
+
return Number(rows.find((row) => row.expiryPicked !== undefined)?.expiryPicked ?? 0);
|
|
423
|
+
}
|
|
413
424
|
async #maintain(table, names) {
|
|
414
425
|
if (this.#stopping)
|
|
415
426
|
return;
|
|
416
|
-
const
|
|
427
|
+
const skipLocked = plans.queueClaimSkipLocked(this.#config.backend, this.#config.noSkipLocked);
|
|
428
|
+
const command = plans.trySetQueueDeletionTime(this.#config.schema, names, this.#config.maintenanceIntervalSeconds, skipLocked);
|
|
417
429
|
const { rows } = await this.#executeQuery(command);
|
|
430
|
+
const queues = rows.map((q) => q.name);
|
|
431
|
+
// One statement stamps every queue it claims with the same time.
|
|
432
|
+
const claimedOn = rows[0]?.claimedOn;
|
|
433
|
+
let finished = 0;
|
|
434
|
+
try {
|
|
435
|
+
while (finished < queues.length && await this.#maintainQueue(table, queues[finished])) {
|
|
436
|
+
finished++;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
finally {
|
|
440
|
+
// The claim covers every queue up front, so a pass that a stop or a failure ends early hands
|
|
441
|
+
// back the ones it did not finish. Otherwise they would wait a whole maintenance interval.
|
|
442
|
+
if (finished < queues.length) {
|
|
443
|
+
try {
|
|
444
|
+
await this.#executeQuery(plans.releaseQueueDeletionTime(this.#config.schema, queues.slice(finished), claimedOn, skipLocked));
|
|
445
|
+
}
|
|
446
|
+
catch (err) {
|
|
447
|
+
this.emit(events.error, err);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// The retention sweep and the dependency cleanup for one queue. Returns whether every walk covered
|
|
453
|
+
// the whole queue.
|
|
454
|
+
async #maintainQueue(table, name) {
|
|
418
455
|
if (this.#stopping)
|
|
419
|
-
return;
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
456
|
+
return false;
|
|
457
|
+
const { schema, noAdvisoryLocks, backend } = this.#config;
|
|
458
|
+
const batchSize = this.#config.__test__walk_batch_size;
|
|
459
|
+
// The walk counts the completed and cancelled jobs that finished by DELTA_LAG before it
|
|
460
|
+
// started, so a completion committed late is still ahead of the watermark, and the monitor
|
|
461
|
+
// counts the rest after that (see plans.createTableQueue).
|
|
462
|
+
const { rows: [{ time }] } = await this.#executeQuery(plans.getTime(schema));
|
|
463
|
+
const retainedBefore = Number(time) - plans.DELTA_LAG_SECONDS * 1000;
|
|
464
|
+
const retention = { retained: 0, deleted: 0, deletedRetained: 0 };
|
|
465
|
+
let done = false;
|
|
466
|
+
try {
|
|
467
|
+
done = await this.#walk((after) => plans.deletion(schema, table, name, { after, batchSize, backend, noAdvisoryLocks, retainedBefore }), retention);
|
|
426
468
|
}
|
|
469
|
+
finally {
|
|
470
|
+
// A walk that a stop or a failed batch cut short has not counted what it kept, so it only
|
|
471
|
+
// takes what it deleted off the counts, as a bulk delete does. A failed update is reported so
|
|
472
|
+
// that it does not hide the batch's own error, and the next full walk sets the count.
|
|
473
|
+
if (!done && retention.deleted) {
|
|
474
|
+
try {
|
|
475
|
+
await this.#executeQuery({ text: plans.reduceRetainedCount(schema), values: [name, retention.deletedRetained, retention.deleted] });
|
|
476
|
+
}
|
|
477
|
+
catch (err) {
|
|
478
|
+
this.emit(events.error, err);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// Only a walk that covered the whole queue has counted all of what it kept.
|
|
483
|
+
if (done) {
|
|
484
|
+
await this.#executeQuery({ text: plans.setRetainedCount(schema, table), values: [name, retention.retained, retainedBefore] });
|
|
485
|
+
}
|
|
486
|
+
const children = await this.#walk((after) => plans.cleanupDependencies(schema, table, name, 'child', { after, batchSize, backend, noAdvisoryLocks }));
|
|
487
|
+
const parents = await this.#walk((after) => plans.cleanupDependencies(schema, table, name, 'parent', { after, batchSize, backend, noAdvisoryLocks }));
|
|
488
|
+
return done && children && parents;
|
|
489
|
+
}
|
|
490
|
+
// Walks one queue's keys a batch at a time (see plans.deletion), carrying each batch's cursor into
|
|
491
|
+
// the next until a batch comes back without one, and returns whether it got there. A stop between
|
|
492
|
+
// batches leaves the rest for the next pass. `tally` totals the batches' counts as they commit, so
|
|
493
|
+
// it holds them even when a batch throws. CockroachDB returns counts as strings. A batch with no
|
|
494
|
+
// result row fails the walk rather than ending it, since a walk taken as done records its count.
|
|
495
|
+
async #walk(batch, tally) {
|
|
496
|
+
let after;
|
|
497
|
+
while (!this.#stopping) {
|
|
498
|
+
const { rows } = await this.#executeQuery(batch(after));
|
|
499
|
+
const row = rows.find((row) => row.walkCursor !== undefined);
|
|
500
|
+
assert(row, 'maintenance batch returned no result row: a custom db must return the rows of every statement in a multi-statement query');
|
|
501
|
+
if (tally) {
|
|
502
|
+
tally.retained += Number(row.retained ?? 0);
|
|
503
|
+
tally.deleted += Number(row.deleted ?? 0);
|
|
504
|
+
tally.deletedRetained += Number(row.deletedRetained ?? 0);
|
|
505
|
+
}
|
|
506
|
+
if (!row.walkCursor)
|
|
507
|
+
return true;
|
|
508
|
+
after = row.walkCursor;
|
|
509
|
+
}
|
|
510
|
+
return false;
|
|
427
511
|
}
|
|
428
512
|
/**
|
|
429
513
|
* Defer the next queue-stats aggregate when this pass spent long enough inside it to threaten
|
|
@@ -712,11 +796,10 @@ class Boss extends EventEmitter {
|
|
|
712
796
|
// because "no rows" from an unreadable view must not read as "horizon is healthy".
|
|
713
797
|
const readable = await this.#narrowXminHorizonSources(attempted, lastVacuum);
|
|
714
798
|
this.#xminHorizonSources = readable;
|
|
799
|
+
// Persisted like the horizon warnings themselves, so a table with none of them does not read
|
|
800
|
+
// as a healthy horizon on an instance that cannot check it.
|
|
715
801
|
if (!readable.length) {
|
|
716
|
-
this.
|
|
717
|
-
message: 'Unable to read the transaction horizon; the xmin_horizon check is disabled for this instance.',
|
|
718
|
-
data: { type: WARNING_TYPES.XMIN_HORIZON, error: err?.message }
|
|
719
|
-
});
|
|
802
|
+
await emitAndPersistWarning(this.#warningContext, WARNING_TYPES.XMIN_HORIZON, 'Unable to read the transaction horizon; the xmin_horizon check is disabled for this instance.', { type: WARNING_TYPES.XMIN_HORIZON, instance: this.#manager.instanceId, error: err?.message });
|
|
720
803
|
}
|
|
721
804
|
return null;
|
|
722
805
|
}
|
package/dist/cli.js
CHANGED
|
@@ -331,9 +331,20 @@ async function cmdMigrate(args) {
|
|
|
331
331
|
const partitionTables = await getPartitionTables(db, schema);
|
|
332
332
|
const { sql, concurrent } = migrationStore.migrateCommands(schema, version, migrationStore.getAllForConfig(config), config.noAdvisoryLocks, { inlineAsync: true, partitionTables });
|
|
333
333
|
await db.executeSql(sql);
|
|
334
|
-
// CONCURRENTLY index builds must run outside the migration transaction, one at a time.
|
|
335
|
-
for
|
|
336
|
-
|
|
334
|
+
// CONCURRENTLY index builds must run outside the migration transaction, one at a time. The
|
|
335
|
+
// version is already committed by then and nothing enqueues them for BAM, so a failed build
|
|
336
|
+
// would never be retried: hand the rest to the operator instead.
|
|
337
|
+
for (const [i, statement] of concurrent.entries()) {
|
|
338
|
+
try {
|
|
339
|
+
await db.executeSql(statement);
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
console.error(`pg-boss schema "${schema}" is at version ${schemaVersion}, but an index build failed: ${err.message}`);
|
|
343
|
+
console.error('Drop any invalid index the failed build left, then run these statements one at a time:\n');
|
|
344
|
+
console.error(concurrent.slice(i).map(rest => `${rest};`).join('\n\n'));
|
|
345
|
+
process.exitCode = 1;
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
337
348
|
}
|
|
338
349
|
console.log(`Successfully migrated pg-boss schema "${schema}" to version ${schemaVersion}`);
|
|
339
350
|
}
|
package/dist/db.d.ts
CHANGED
|
@@ -17,7 +17,10 @@ declare class Db extends EventEmitter implements types.IDatabase, types.EventsMi
|
|
|
17
17
|
close(): Promise<void>;
|
|
18
18
|
poolCounts(): types.PoolCounts | null;
|
|
19
19
|
setSessionStatements(statements: string[]): Promise<void>;
|
|
20
|
-
executeSql(text: string, values?: unknown[]
|
|
20
|
+
executeSql(text: string, values?: unknown[], { signal, retire }?: {
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
retire?: AbortSignal;
|
|
23
|
+
}): Promise<import("pg").QueryResult<any>>;
|
|
21
24
|
listen(channel: string, onNotification: (payload: string) => void, onReconnect: () => void): Promise<types.ListenHandle>;
|
|
22
25
|
beginTransaction(): Promise<types.TransactionHandle>;
|
|
23
26
|
withTransaction<T>(fn: (db: types.IDatabase) => Promise<T>): Promise<T>;
|
package/dist/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,aAAa,CAAA;AAItC,OAAO,KAAK,KAAK,KAAK,MAAM,YAAY,CAAA;AAaxC,eAAO,MAAM,+BAA+B,OAAO,CAAA;AAEnD,cAAM,EAAG,SAAQ,YAAa,YAAW,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW;IACzE,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,KAAK,CAAa;IAG1B,OAAO,CAAC,iBAAiB,CAAe;IAGxC,MAAM,EAAE,OAAO,CAAA;gBAEF,MAAM,EAAE,KAAK,CAAC,eAAe,GAAG;QAAE,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,CAAA;KAAE;IAcpE,MAAM;;MAEL;IAEK,IAAI;IAgBJ,KAAK;IAQX,UAAU,IAAK,KAAK,CAAC,UAAU,GAAG,IAAI;IAUhC,oBAAoB,CAAE,UAAU,EAAE,MAAM,EAAE;
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,aAAa,CAAA;AAItC,OAAO,KAAK,KAAK,KAAK,MAAM,YAAY,CAAA;AAaxC,eAAO,MAAM,+BAA+B,OAAO,CAAA;AAEnD,cAAM,EAAG,SAAQ,YAAa,YAAW,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW;IACzE,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,KAAK,CAAa;IAG1B,OAAO,CAAC,iBAAiB,CAAe;IAGxC,MAAM,EAAE,OAAO,CAAA;gBAEF,MAAM,EAAE,KAAK,CAAC,eAAe,GAAG;QAAE,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,CAAA;KAAE;IAcpE,MAAM;;MAEL;IAEK,IAAI;IAgBJ,KAAK;IAQX,UAAU,IAAK,KAAK,CAAC,UAAU,GAAG,IAAI;IAUhC,oBAAoB,CAAE,UAAU,EAAE,MAAM,EAAE;IA2C1C,UAAU,CAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO;IA4DrH,MAAM,CACV,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACzC,WAAW,EAAE,MAAM,IAAI,GACtB,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC;IA2JxB,gBAAgB,IAAK,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC;IAsFrD,eAAe,CAAC,CAAC,EAAG,EAAE,EAAE,CAAC,EAAE,EAAE,KAAK,CAAC,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAY/E;AAED,eAAe,EAAE,CAAA"}
|