pg-boss 12.29.0 → 12.31.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/README.md +5 -5
- package/dist/adapters/bun.d.ts +33 -0
- package/dist/adapters/bun.d.ts.map +1 -0
- package/dist/adapters/bun.js +86 -0
- package/dist/adapters/drizzle.d.ts.map +1 -1
- package/dist/adapters/drizzle.js +44 -7
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/placeholders.d.ts +15 -0
- package/dist/adapters/placeholders.d.ts.map +1 -1
- package/dist/adapters/placeholders.js +28 -0
- package/dist/attorney.d.ts +2 -1
- package/dist/attorney.d.ts.map +1 -1
- package/dist/attorney.js +18 -4
- package/dist/boss.d.ts.map +1 -1
- package/dist/boss.js +421 -14
- package/dist/cli.js +61 -15
- package/dist/contractor.d.ts +3 -7
- package/dist/contractor.d.ts.map +1 -1
- package/dist/contractor.js +35 -6
- package/dist/index.d.ts +15 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -5
- package/dist/manager.d.ts +3 -1
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +61 -4
- package/dist/migrationStore.d.ts +3 -2
- package/dist/migrationStore.d.ts.map +1 -1
- package/dist/migrationStore.js +226 -29
- package/dist/plans.d.ts +113 -4
- package/dist/plans.d.ts.map +1 -1
- package/dist/plans.js +622 -82
- package/dist/rrule.d.ts +85 -0
- package/dist/rrule.d.ts.map +1 -0
- package/dist/rrule.js +384 -0
- package/dist/schema.json +80 -16
- package/dist/timekeeper.d.ts +103 -2
- package/dist/timekeeper.d.ts.map +1 -1
- package/dist/timekeeper.js +499 -44
- package/dist/timezone.d.ts +18 -0
- package/dist/timezone.d.ts.map +1 -0
- package/dist/timezone.js +34 -0
- package/dist/types.d.ts +115 -8
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ This will likely cater the most to teams already familiar with the simplicity of
|
|
|
46
46
|
* Create jobs in an existing db transaction, including adapters for popular ORMs such as Drizzle, Knex, Kysely, Prisma
|
|
47
47
|
* Backpressure-compatible polling workers, including support for LISTEN/NOTIFY low latency delivery
|
|
48
48
|
* Job dependency workflow orchestration
|
|
49
|
-
* Cron scheduling, job deferral
|
|
49
|
+
* Cron and RRULE scheduling, job deferral
|
|
50
50
|
* Queue storage policies to support a variety of rate limiting, debouncing, and concurrency use cases
|
|
51
51
|
* Priority queues, dead letter queues with redrive, automatic retries with exponential backoff
|
|
52
52
|
* Pub/sub API for fan-out queue relationships
|
|
@@ -85,10 +85,10 @@ pg-boss is MIT licensed and always will be. It is maintained by one person, and
|
|
|
85
85
|
|
|
86
86
|
<!-- sponsors:start The logos below are generated from GitHub Sponsors by scripts/sync-sponsors.js. Do not edit them directly. -->
|
|
87
87
|
<div style="display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; margin: 1.5rem 0;">
|
|
88
|
-
<a href="https://superpower.com/" target="_blank" rel="noopener"><img src="https://avatars.githubusercontent.com/u/114334709?s=
|
|
89
|
-
<a href="https://altruistiq.com/" target="_blank" rel="noopener"><img src="https://avatars.githubusercontent.com/u/88195975?s=
|
|
90
|
-
<a href="https://wasp.sh/" target="_blank" rel="noopener"><img src="https://avatars.githubusercontent.com/u/55102317?s=
|
|
91
|
-
<a href="https://github.com/Ferry-Health" target="_blank" rel="noopener"><img src="https://avatars.githubusercontent.com/u/158637456?s=
|
|
88
|
+
<a href="https://superpower.com/" target="_blank" rel="noopener"><img src="https://avatars.githubusercontent.com/u/114334709?s=256&v=4" alt="Superpower" title="Superpower" width="96" height="96" style="border-radius: 8px;"></a>
|
|
89
|
+
<a href="https://altruistiq.com/" target="_blank" rel="noopener"><img src="https://avatars.githubusercontent.com/u/88195975?s=256&v=4" alt="Altruistiq" title="Altruistiq" width="64" height="64" style="border-radius: 8px;"></a>
|
|
90
|
+
<a href="https://wasp.sh/" target="_blank" rel="noopener"><img src="https://avatars.githubusercontent.com/u/55102317?s=256&v=4" alt="wasp-lang" title="wasp-lang" width="64" height="64" style="border-radius: 8px;"></a>
|
|
91
|
+
<a href="https://github.com/Ferry-Health" target="_blank" rel="noopener"><img src="https://avatars.githubusercontent.com/u/158637456?s=256&v=4" alt="Ferry Health" title="Ferry Health" width="64" height="64" style="border-radius: 8px;"></a>
|
|
92
92
|
</div>
|
|
93
93
|
<!-- sponsors:end -->
|
|
94
94
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { IDatabase } from '../types.ts';
|
|
2
|
+
export interface BunSqlLike {
|
|
3
|
+
unsafe(text: string, values?: unknown[]): Promise<unknown>;
|
|
4
|
+
reserve(): Promise<BunReservedSqlLike>;
|
|
5
|
+
listen?(channel: string, onNotification: (payload: string) => void, onSubscribe?: () => void): Promise<BunSubscriptionLike>;
|
|
6
|
+
}
|
|
7
|
+
export interface BunSubscriptionLike {
|
|
8
|
+
unlisten(): Promise<void> | void;
|
|
9
|
+
}
|
|
10
|
+
export interface BunReservedSqlLike {
|
|
11
|
+
unsafe(text: string, values?: unknown[]): Promise<unknown>;
|
|
12
|
+
release(): void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Adapts a `Bun.SQL` client to pg-boss's IDatabase, for running pg-boss on Bun's built-in
|
|
16
|
+
* PostgreSQL driver instead of the bundled `pg` pool.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { SQL } from 'bun'
|
|
21
|
+
* import { PgBoss, fromBunSql } from 'pg-boss'
|
|
22
|
+
*
|
|
23
|
+
* const boss = new PgBoss({ db: fromBunSql(new SQL(connectionString)) })
|
|
24
|
+
* await boss.start()
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* The caller owns the client's lifecycle: pg-boss never calls `end()` on it.
|
|
28
|
+
*
|
|
29
|
+
* `useListenNotify` needs `sql.listen()`, which Bun added in 1.4.0. On an older runtime the adapter
|
|
30
|
+
* exposes no `listen` and pg-boss falls back to polling.
|
|
31
|
+
*/
|
|
32
|
+
export declare function fromBunSql(client: BunSqlLike): IDatabase;
|
|
33
|
+
//# sourceMappingURL=bun.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../../src/adapters/bun.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAM5C,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1D,OAAO,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAEtC,MAAM,CAAC,CACL,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACzC,WAAW,CAAC,EAAE,MAAM,IAAI,GACvB,OAAO,CAAC,mBAAmB,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CACjC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1D,OAAO,IAAI,IAAI,CAAA;CAChB;AAeD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAE,MAAM,EAAE,UAAU,GAAG,SAAS,CAiCzD"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { expandArrayParams } from "./placeholders.js";
|
|
2
|
+
// pg-boss ships schema installation, migration and maintenance as `BEGIN; ... COMMIT;` scripts
|
|
3
|
+
// (plans.transaction / plans.locked). Bun rejects those on a pooled connection with `Only use
|
|
4
|
+
// sql.begin, sql.reserved or max: 1`, because the pool is free to hand the next statement to a
|
|
5
|
+
// different connection. A reserved connection is pinned for its lifetime and takes them as-is.
|
|
6
|
+
const STARTS_TRANSACTION = /^\s*BEGIN\b/i;
|
|
7
|
+
// Bun reports every server error as ERR_POSTGRES_SERVER_ERROR and puts the SQLSTATE in `errno`,
|
|
8
|
+
// where node-postgres puts it in `code`. pg-boss reads `code` to recognise a handful of states it
|
|
9
|
+
// treats as control flow rather than failure - 23505 from a lost fetch race, 22012 from the
|
|
10
|
+
// in-SQL insert guard, 25001 from REINDEX inside a transaction - so an unmapped error turns a
|
|
11
|
+
// tolerated race into a thrown one.
|
|
12
|
+
const BUN_SERVER_ERROR = 'ERR_POSTGRES_SERVER_ERROR';
|
|
13
|
+
/**
|
|
14
|
+
* Adapts a `Bun.SQL` client to pg-boss's IDatabase, for running pg-boss on Bun's built-in
|
|
15
|
+
* PostgreSQL driver instead of the bundled `pg` pool.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { SQL } from 'bun'
|
|
20
|
+
* import { PgBoss, fromBunSql } from 'pg-boss'
|
|
21
|
+
*
|
|
22
|
+
* const boss = new PgBoss({ db: fromBunSql(new SQL(connectionString)) })
|
|
23
|
+
* await boss.start()
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* The caller owns the client's lifecycle: pg-boss never calls `end()` on it.
|
|
27
|
+
*
|
|
28
|
+
* `useListenNotify` needs `sql.listen()`, which Bun added in 1.4.0. On an older runtime the adapter
|
|
29
|
+
* exposes no `listen` and pg-boss falls back to polling.
|
|
30
|
+
*/
|
|
31
|
+
export function fromBunSql(client) {
|
|
32
|
+
const db = {
|
|
33
|
+
async executeSql(text, values) {
|
|
34
|
+
const query = expandArrayParams(text, values);
|
|
35
|
+
// A parameterless call goes through the simple protocol, which is what lets a multi-statement
|
|
36
|
+
// script run at all; pg-boss only ever concatenates statements when there is nothing to bind.
|
|
37
|
+
const run = (target) => query.values.length
|
|
38
|
+
? target.unsafe(query.text, query.values)
|
|
39
|
+
: target.unsafe(query.text);
|
|
40
|
+
try {
|
|
41
|
+
return unwrapBunResult(await (STARTS_TRANSACTION.test(text) ? runReserved(client, run) : run(client)));
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
throw withSqlState(err);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
// Bun's third argument fires on the initial subscribe and again after each reconnect - it
|
|
49
|
+
// re-establishes the connection and re-subscribes every channel on its own - which is exactly
|
|
50
|
+
// what pg-boss's onReconnect is for: a notification sent while the listener was down is gone,
|
|
51
|
+
// so the gap has to be closed by a fetch. Only expose `listen` when the client actually has it
|
|
52
|
+
// (Bun < 1.4.0, or a mock), so the notifier cleanly falls back to polling otherwise.
|
|
53
|
+
if (typeof client.listen === 'function') {
|
|
54
|
+
db.listen = async (channel, onNotification, onReconnect) => {
|
|
55
|
+
const subscription = await client.listen(channel, onNotification, onReconnect);
|
|
56
|
+
return { close: async () => { await subscription.unlisten(); } };
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return db;
|
|
60
|
+
}
|
|
61
|
+
async function runReserved(client, run) {
|
|
62
|
+
const reserved = await client.reserve();
|
|
63
|
+
try {
|
|
64
|
+
return await run(reserved);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
reserved.release();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Bun returns the rows of a single statement as a flat array, and one such array per statement for
|
|
71
|
+
// a multi-statement script. Flatten the latter so a RETURNING in the middle of a transaction block
|
|
72
|
+
// is not lost behind the trailing COMMIT's empty result - the same unwrapping the PGlite adapter
|
|
73
|
+
// does for exec() and pg-boss does for node-postgres (see unwrapSQLResult).
|
|
74
|
+
function unwrapBunResult(result) {
|
|
75
|
+
if (!Array.isArray(result)) {
|
|
76
|
+
return { rows: [] };
|
|
77
|
+
}
|
|
78
|
+
return { rows: result.some(Array.isArray) ? result.flat() : result };
|
|
79
|
+
}
|
|
80
|
+
function withSqlState(err) {
|
|
81
|
+
const error = err;
|
|
82
|
+
if (error?.code === BUN_SERVER_ERROR && error.errno) {
|
|
83
|
+
error.code = error.errno;
|
|
84
|
+
}
|
|
85
|
+
return err;
|
|
86
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drizzle.d.ts","sourceRoot":"","sources":["../../src/adapters/drizzle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAI5C,MAAM,WAAW,sBAAsB;IAGrC,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,EAAE,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,GAAG,EAAE,CAAA;KAAE,EAAE,GAAG,GAAG,EAAE,CAAC,CAAA;CAC9E;AAED,MAAM,WAAW,iBAAiB;IAChC,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;IAC9D,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAA;CAC/B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAE,EAAE,EAAE,sBAAsB,EAAE,GAAG,EAAE,iBAAiB,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"drizzle.d.ts","sourceRoot":"","sources":["../../src/adapters/drizzle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAI5C,MAAM,WAAW,sBAAsB;IAGrC,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,EAAE,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,GAAG,EAAE,CAAA;KAAE,EAAE,GAAG,GAAG,EAAE,CAAC,CAAA;CAC9E;AAED,MAAM,WAAW,iBAAiB;IAChC,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;IAC9D,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAA;CAC/B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAE,EAAE,EAAE,sBAAsB,EAAE,GAAG,EAAE,iBAAiB,GAAG,SAAS,CAQ1F"}
|
package/dist/adapters/drizzle.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parsePlaceholders } from "./placeholders.js";
|
|
1
|
+
import { ARRAY_CAST, parsePlaceholders } from "./placeholders.js";
|
|
2
2
|
import { unwrapSQLResult } from "../tools.js";
|
|
3
3
|
/**
|
|
4
4
|
* Wraps a drizzle-orm transaction as an {@link IDatabase}.
|
|
@@ -21,13 +21,50 @@ export function fromDrizzle(tx, sql) {
|
|
|
21
21
|
return {
|
|
22
22
|
async executeSql(text, values) {
|
|
23
23
|
const { parts, reordered } = parsePlaceholders(text, values);
|
|
24
|
-
const strings =
|
|
25
|
-
// Bind each value through sql.param so drizzle emits exactly one placeholder
|
|
26
|
-
// per value. A bare array would otherwise be expanded into a parameter list
|
|
27
|
-
// ($2, $3, ...) instead of a single array-typed parameter, breaking any query
|
|
28
|
-
// with `= ANY($N::uuid[])` (deleteJob/complete/fail/cancel/resume/retry).
|
|
29
|
-
const params = reordered.map(value => sql.param(value));
|
|
24
|
+
const { strings, params } = buildQuery(parts, reordered, sql);
|
|
30
25
|
return unwrapSQLResult(await tx.execute(sql(strings, ...params)));
|
|
31
26
|
}
|
|
32
27
|
};
|
|
33
28
|
}
|
|
29
|
+
// A parameter cast to an array type (`$N::uuid[]`) is expanded into an inline
|
|
30
|
+
// ARRAY[...] constructor of scalar parameters rather than bound whole.
|
|
31
|
+
//
|
|
32
|
+
// Binding the array whole (sql.param([a, b])) is what postgres wants, and it is
|
|
33
|
+
// what node-postgres and postgres-js do, but Bun's SQL client cannot encode a JS
|
|
34
|
+
// array: it stringifies it to `a,b`, and every `= ANY($N::uuid[])` in pg-boss then
|
|
35
|
+
// fails with `malformed array literal` (oven-sh/bun#18775). Letting drizzle expand
|
|
36
|
+
// the array on its own is no better - it emits a bare `$2, $3` list, which is not
|
|
37
|
+
// valid where the statement expects one array expression.
|
|
38
|
+
//
|
|
39
|
+
// ARRAY[$2, $3] is valid everywhere a single array parameter was, keeps every
|
|
40
|
+
// element a real bind parameter (so no value is ever escaped into SQL text), and
|
|
41
|
+
// carries no driver-specific knowledge.
|
|
42
|
+
//
|
|
43
|
+
// The array cast is what makes the rewrite safe to apply, so it is required rather
|
|
44
|
+
// than assumed (see ARRAY_CAST).
|
|
45
|
+
function buildQuery(parts, values, sql) {
|
|
46
|
+
const literals = [];
|
|
47
|
+
const params = [];
|
|
48
|
+
let pending = parts[0];
|
|
49
|
+
for (let i = 0; i < values.length; i++) {
|
|
50
|
+
const value = values[i];
|
|
51
|
+
if (Array.isArray(value) && ARRAY_CAST.test(parts[i + 1])) {
|
|
52
|
+
// ARRAY[] alone has no element type; the cast that qualified this parameter
|
|
53
|
+
// for the rewrite is what resolves it.
|
|
54
|
+
pending += 'ARRAY[';
|
|
55
|
+
for (let element = 0; element < value.length; element++) {
|
|
56
|
+
literals.push(pending);
|
|
57
|
+
params.push(sql.param(value[element]));
|
|
58
|
+
pending = element === value.length - 1 ? '' : ',';
|
|
59
|
+
}
|
|
60
|
+
pending += ']' + parts[i + 1];
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
literals.push(pending);
|
|
64
|
+
params.push(sql.param(value));
|
|
65
|
+
pending = parts[i + 1];
|
|
66
|
+
}
|
|
67
|
+
literals.push(pending);
|
|
68
|
+
const strings = Object.assign(literals, { raw: [...literals] });
|
|
69
|
+
return { strings, params };
|
|
70
|
+
}
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -3,9 +3,11 @@ export { fromKysely } from './kysely.ts';
|
|
|
3
3
|
export { fromDrizzle } from './drizzle.ts';
|
|
4
4
|
export { fromPrisma } from './prisma.ts';
|
|
5
5
|
export { fromPglite } from './pglite.ts';
|
|
6
|
+
export { fromBunSql } from './bun.ts';
|
|
6
7
|
export type { KnexTransactionLike } from './knex.ts';
|
|
7
8
|
export type { KyselyTransactionLike } from './kysely.ts';
|
|
8
9
|
export type { DrizzleTransactionLike, DrizzleSqlTagLike } from './drizzle.ts';
|
|
9
10
|
export type { PrismaTransactionLike } from './prisma.ts';
|
|
10
11
|
export type { PGliteLike } from './pglite.ts';
|
|
12
|
+
export type { BunSqlLike, BunReservedSqlLike } from './bun.ts';
|
|
11
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAErC,YAAY,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AACpD,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACxD,YAAY,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAC7E,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACxD,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA"}
|
package/dist/adapters/index.js
CHANGED
|
@@ -11,4 +11,19 @@ export declare function parsePlaceholders(text: string, values?: readonly unknow
|
|
|
11
11
|
parts: string[];
|
|
12
12
|
reordered: unknown[];
|
|
13
13
|
};
|
|
14
|
+
export declare const ARRAY_CAST: RegExp;
|
|
15
|
+
/**
|
|
16
|
+
* Rewrites `$N` placeholders into a fresh sequential list, expanding any array-cast parameter
|
|
17
|
+
* into an inline `ARRAY[...]` constructor of scalar parameters.
|
|
18
|
+
*
|
|
19
|
+
* Postgres wants an array bound whole, and node-postgres, postgres-js and PGlite all encode one.
|
|
20
|
+
* Bun's SQL client cannot: it stringifies the array, so `= ANY($N::uuid[])` reaches the server as
|
|
21
|
+
* `a,b` and fails with `malformed array literal` (oven-sh/bun#18775). `ARRAY[$2,$3]` is valid
|
|
22
|
+
* everywhere a single array parameter was, and every element stays a bind parameter, so no value
|
|
23
|
+
* is ever escaped into SQL text.
|
|
24
|
+
*/
|
|
25
|
+
export declare function expandArrayParams(text: string, values?: readonly unknown[]): {
|
|
26
|
+
text: string;
|
|
27
|
+
values: unknown[];
|
|
28
|
+
};
|
|
14
29
|
//# sourceMappingURL=placeholders.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"placeholders.d.ts","sourceRoot":"","sources":["../../src/adapters/placeholders.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG;IAC7E,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,SAAS,EAAE,OAAO,EAAE,CAAA;CACrB,CAcA"}
|
|
1
|
+
{"version":3,"file":"placeholders.d.ts","sourceRoot":"","sources":["../../src/adapters/placeholders.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG;IAC7E,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,SAAS,EAAE,OAAO,EAAE,CAAA;CACrB,CAcA;AAMD,eAAO,MAAM,UAAU,QAA6B,CAAA;AAEpD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG;IAC7E,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,EAAE,CAAA;CAClB,CAgBA"}
|
|
@@ -22,3 +22,31 @@ export function parsePlaceholders(text, values) {
|
|
|
22
22
|
parts.push(text.slice(lastIndex));
|
|
23
23
|
return { parts, reordered };
|
|
24
24
|
}
|
|
25
|
+
// A parameter cast to an array type, as in `$2::uuid[]`. pg-boss binds job ids and queue names
|
|
26
|
+
// this way, and the cast is what tells an adapter the value is a postgres array rather than a
|
|
27
|
+
// JSON one - a JS array bound to a json column is a JSON array, and rewriting that would change
|
|
28
|
+
// what gets stored.
|
|
29
|
+
export const ARRAY_CAST = /^::[a-z_][a-z0-9_]*\[\]/i;
|
|
30
|
+
/**
|
|
31
|
+
* Rewrites `$N` placeholders into a fresh sequential list, expanding any array-cast parameter
|
|
32
|
+
* into an inline `ARRAY[...]` constructor of scalar parameters.
|
|
33
|
+
*
|
|
34
|
+
* Postgres wants an array bound whole, and node-postgres, postgres-js and PGlite all encode one.
|
|
35
|
+
* Bun's SQL client cannot: it stringifies the array, so `= ANY($N::uuid[])` reaches the server as
|
|
36
|
+
* `a,b` and fails with `malformed array literal` (oven-sh/bun#18775). `ARRAY[$2,$3]` is valid
|
|
37
|
+
* everywhere a single array parameter was, and every element stays a bind parameter, so no value
|
|
38
|
+
* is ever escaped into SQL text.
|
|
39
|
+
*/
|
|
40
|
+
export function expandArrayParams(text, values) {
|
|
41
|
+
const { parts, reordered } = parsePlaceholders(text, values);
|
|
42
|
+
const expanded = [];
|
|
43
|
+
let rewritten = parts[0];
|
|
44
|
+
for (let i = 0; i < reordered.length; i++) {
|
|
45
|
+
const value = reordered[i];
|
|
46
|
+
rewritten += Array.isArray(value) && ARRAY_CAST.test(parts[i + 1])
|
|
47
|
+
? `ARRAY[${value.map(element => `$${expanded.push(element)}`).join(',')}]`
|
|
48
|
+
: `$${expanded.push(value)}`;
|
|
49
|
+
rewritten += parts[i + 1];
|
|
50
|
+
}
|
|
51
|
+
return { text: rewritten, values: expanded };
|
|
52
|
+
}
|
package/dist/attorney.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ declare const POLICY: {
|
|
|
4
4
|
MIN_POLLING_INTERVAL_MS: number;
|
|
5
5
|
MAX_RETENTION_DAYS: number;
|
|
6
6
|
};
|
|
7
|
+
declare const COMPATIBILITY_FLAGS: readonly ["noSkipLocked", "noMultiMutationCte", "noTablePartitioning", "noDeferrableConstraints", "noAdvisoryLocks", "noCoveringIndexes", "noAddColumnBackfill", "noListenNotify", "noIndexProgressView", "noReindex", "noMonitorVacuum"];
|
|
7
8
|
declare function validateQueueArgs(config?: any): void;
|
|
8
9
|
declare function pinZonelessDateTime(value: string): string;
|
|
9
10
|
declare function checkSendArgs(args: any): types.Request;
|
|
@@ -21,5 +22,5 @@ declare function getConfig(value: string | types.ConstructorOptions): types.Reso
|
|
|
21
22
|
declare function assertPostgresObjectName(name: string): void;
|
|
22
23
|
declare function assertQueueName(name: string): void;
|
|
23
24
|
declare function assertKey(key: string): void;
|
|
24
|
-
export { assertKey, assertPostgresObjectName, assertQueueName, checkFetchArgs, checkSendArgs, checkUpdateArgs, checkWorkArgs, getConfig, pinZonelessDateTime, POLICY, validateFlowJobs, validateGroupConfig, validateQueueArgs };
|
|
25
|
+
export { assertKey, COMPATIBILITY_FLAGS, assertPostgresObjectName, assertQueueName, checkFetchArgs, checkSendArgs, checkUpdateArgs, checkWorkArgs, getConfig, pinZonelessDateTime, POLICY, validateFlowJobs, validateGroupConfig, validateQueueArgs };
|
|
25
26
|
//# 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":"AAEA,OAAO,KAAK,KAAK,KAAK,MAAM,YAAY,CAAA;AAExC,QAAA,MAAM,MAAM;;;;CAIX,CAAA;
|
|
1
|
+
{"version":3,"file":"attorney.d.ts","sourceRoot":"","sources":["../src/attorney.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,KAAK,MAAM,YAAY,CAAA;AAExC,QAAA,MAAM,MAAM;;;;CAIX,CAAA;AAKD,QAAA,MAAM,mBAAmB,2OAYf,CAAA;AAsEV,iBAAS,iBAAiB,CAAE,MAAM,GAAE,GAAQ,QAc3C;AAgBD,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,CA8BA;AAED,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,CAyB9F;AA4FD,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"}
|
package/dist/attorney.js
CHANGED
|
@@ -15,9 +15,11 @@ const COMPATIBILITY_FLAGS = [
|
|
|
15
15
|
'noDeferrableConstraints',
|
|
16
16
|
'noAdvisoryLocks',
|
|
17
17
|
'noCoveringIndexes',
|
|
18
|
+
'noAddColumnBackfill',
|
|
18
19
|
'noListenNotify',
|
|
19
20
|
'noIndexProgressView',
|
|
20
|
-
'noReindex'
|
|
21
|
+
'noReindex',
|
|
22
|
+
'noMonitorVacuum'
|
|
21
23
|
];
|
|
22
24
|
// The single source of truth for backend presets, mirrored by test/testHelper.ts.
|
|
23
25
|
const BACKEND_PROFILES = {
|
|
@@ -31,6 +33,10 @@ const BACKEND_PROFILES = {
|
|
|
31
33
|
noDeferrableConstraints: true,
|
|
32
34
|
noAdvisoryLocks: true,
|
|
33
35
|
noCoveringIndexes: true,
|
|
36
|
+
// ADD COLUMN runs as a schema-change job, so a transaction cannot write the column it just
|
|
37
|
+
// added: the UPDATE fails with "column is being backfilled". A migration that seeds a new
|
|
38
|
+
// column leaves the seeding statement out here.
|
|
39
|
+
noAddColumnBackfill: true,
|
|
34
40
|
noListenNotify: true,
|
|
35
41
|
// Online DDL runs as a schema-change job, not the PG CONCURRENTLY path, and
|
|
36
42
|
// pg_stat_progress_create_index isn't available — so BAM can't use liveness-based reclaim.
|
|
@@ -39,7 +45,8 @@ const BACKEND_PROFILES = {
|
|
|
39
45
|
// CONCURRENTLY, "unimplemented: this syntax" without — and the bloat check itself cannot run:
|
|
40
46
|
// there is no pg_relation_size(), and reltuples / relpages is an "unsupported binary
|
|
41
47
|
// operator: <float4> / <int4>".
|
|
42
|
-
noReindex: true
|
|
48
|
+
noReindex: true,
|
|
49
|
+
noMonitorVacuum: true
|
|
43
50
|
}
|
|
44
51
|
},
|
|
45
52
|
yugabytedb: {
|
|
@@ -53,7 +60,8 @@ const BACKEND_PROFILES = {
|
|
|
53
60
|
// "REINDEX not supported yet" with or without CONCURRENTLY, and DocDB compaction owns
|
|
54
61
|
// reclamation. The bloat check runs but can never match: relpages and pg_relation_size() are
|
|
55
62
|
// 0 for every relation.
|
|
56
|
-
noReindex: true
|
|
63
|
+
noReindex: true,
|
|
64
|
+
noMonitorVacuum: true
|
|
57
65
|
}
|
|
58
66
|
},
|
|
59
67
|
// No noIndexProgressView: pg-boss keeps its tables coordinator-local (it never calls
|
|
@@ -570,6 +578,7 @@ function applyOpsConfig(config) {
|
|
|
570
578
|
}
|
|
571
579
|
config.queueStatRetentionDays = config.queueStatRetentionDays || 7;
|
|
572
580
|
validateReindexConfig(config);
|
|
581
|
+
validateVacuumMonitorConfig(config);
|
|
573
582
|
}
|
|
574
583
|
function validateReindexConfig(config) {
|
|
575
584
|
assert(!('reindex' in config) || typeof config.reindex === 'boolean' || (typeof config.reindex === 'object' && config.reindex !== null), 'configuration assert: reindex must be a boolean or an options object');
|
|
@@ -587,6 +596,11 @@ function validateReindexConfig(config) {
|
|
|
587
596
|
config.reindexIntervalSeconds = config.reindexIntervalSeconds || POLICY.MAX_EXPIRATION_HOURS * 60 * 60;
|
|
588
597
|
assert(config.reindexIntervalSeconds / 60 / 60 <= POLICY.MAX_EXPIRATION_HOURS, `configuration assert: reindexIntervalSeconds cannot exceed ${POLICY.MAX_EXPIRATION_HOURS} hours`);
|
|
589
598
|
}
|
|
599
|
+
function validateVacuumMonitorConfig(config) {
|
|
600
|
+
// A switch, not a threshold: the check derives its trigger from the server's own autovacuum
|
|
601
|
+
// settings and from measured dead tuples, so there is no number here for a caller to get wrong.
|
|
602
|
+
assert(!('monitorVacuum' in config) || typeof config.monitorVacuum === 'boolean', 'configuration assert: monitorVacuum must be a boolean');
|
|
603
|
+
}
|
|
590
604
|
function validateDeletionConfig(config) {
|
|
591
605
|
assert(!('deleteAfterSeconds' in config) || config.deleteAfterSeconds >= 0, 'configuration assert: deleteAfterSeconds must be at least 0 (0 disables deletion)');
|
|
592
606
|
}
|
|
@@ -608,4 +622,4 @@ function applyFlowConfig(config) {
|
|
|
608
622
|
assert(!('flowIntervalSeconds' in config) || config.flowIntervalSeconds >= minInterval, `configuration assert: flowIntervalSeconds must be at least ${minInterval} seconds`);
|
|
609
623
|
config.flowIntervalSeconds = config.flowIntervalSeconds || 5;
|
|
610
624
|
}
|
|
611
|
-
export { assertKey, assertPostgresObjectName, assertQueueName, checkFetchArgs, checkSendArgs, checkUpdateArgs, checkWorkArgs, getConfig, pinZonelessDateTime, POLICY, validateFlowJobs, validateGroupConfig, validateQueueArgs };
|
|
625
|
+
export { assertKey, COMPATIBILITY_FLAGS, assertPostgresObjectName, assertQueueName, checkFetchArgs, checkSendArgs, checkUpdateArgs, checkWorkArgs, getConfig, pinZonelessDateTime, POLICY, validateFlowJobs, validateGroupConfig, validateQueueArgs };
|
package/dist/boss.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boss.d.ts","sourceRoot":"","sources":["../src/boss.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,aAAa,CAAA;AACtC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAGvC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"boss.d.ts","sourceRoot":"","sources":["../src/boss.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,aAAa,CAAA;AACtC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAGvC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAgHnC,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;IAEK,KAAK;IAWL,IAAI;IA8FJ,SAAS,CAAE,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,gBAAgB;IAwqBvF;;;;OAIG;IACG,kBAAkB,CAAE,OAAO,CAAC,EAAE,KAAK,CAAC,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAwB7E;AAED,eAAe,IAAI,CAAA"}
|