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.
Files changed (46) hide show
  1. package/README.md +5 -5
  2. package/dist/adapters/bun.d.ts +33 -0
  3. package/dist/adapters/bun.d.ts.map +1 -0
  4. package/dist/adapters/bun.js +86 -0
  5. package/dist/adapters/drizzle.d.ts.map +1 -1
  6. package/dist/adapters/drizzle.js +44 -7
  7. package/dist/adapters/index.d.ts +2 -0
  8. package/dist/adapters/index.d.ts.map +1 -1
  9. package/dist/adapters/index.js +1 -0
  10. package/dist/adapters/placeholders.d.ts +15 -0
  11. package/dist/adapters/placeholders.d.ts.map +1 -1
  12. package/dist/adapters/placeholders.js +28 -0
  13. package/dist/attorney.d.ts +2 -1
  14. package/dist/attorney.d.ts.map +1 -1
  15. package/dist/attorney.js +18 -4
  16. package/dist/boss.d.ts.map +1 -1
  17. package/dist/boss.js +421 -14
  18. package/dist/cli.js +61 -15
  19. package/dist/contractor.d.ts +3 -7
  20. package/dist/contractor.d.ts.map +1 -1
  21. package/dist/contractor.js +35 -6
  22. package/dist/index.d.ts +15 -8
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +18 -5
  25. package/dist/manager.d.ts +3 -1
  26. package/dist/manager.d.ts.map +1 -1
  27. package/dist/manager.js +61 -4
  28. package/dist/migrationStore.d.ts +3 -2
  29. package/dist/migrationStore.d.ts.map +1 -1
  30. package/dist/migrationStore.js +226 -29
  31. package/dist/plans.d.ts +113 -4
  32. package/dist/plans.d.ts.map +1 -1
  33. package/dist/plans.js +622 -82
  34. package/dist/rrule.d.ts +85 -0
  35. package/dist/rrule.d.ts.map +1 -0
  36. package/dist/rrule.js +384 -0
  37. package/dist/schema.json +80 -16
  38. package/dist/timekeeper.d.ts +103 -2
  39. package/dist/timekeeper.d.ts.map +1 -1
  40. package/dist/timekeeper.js +499 -44
  41. package/dist/timezone.d.ts +18 -0
  42. package/dist/timezone.d.ts.map +1 -0
  43. package/dist/timezone.js +34 -0
  44. package/dist/types.d.ts +115 -8
  45. package/dist/types.d.ts.map +1 -1
  46. 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=200&amp;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=200&amp;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=200&amp;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=200&amp;v=4" alt="Ferry Health" title="Ferry Health" width="64" height="64" style="border-radius: 8px;"></a>
88
+ <a href="https://superpower.com/" target="_blank" rel="noopener"><img src="https://avatars.githubusercontent.com/u/114334709?s=256&amp;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&amp;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&amp;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&amp;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,CAa1F"}
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"}
@@ -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 = Object.assign([...parts], { raw: [...parts] });
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
+ }
@@ -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;AAExC,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"}
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"}
@@ -3,3 +3,4 @@ export { fromKysely } from "./kysely.js";
3
3
  export { fromDrizzle } from "./drizzle.js";
4
4
  export { fromPrisma } from "./prisma.js";
5
5
  export { fromPglite } from "./pglite.js";
6
+ export { fromBunSql } from "./bun.js";
@@ -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
+ }
@@ -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
@@ -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;AA+ED,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;AAuLD,OAAO,EACL,SAAS,EACT,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":"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 };
@@ -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;AA0BnC,cAAM,IAAK,SAAQ,YAAa,YAAW,KAAK,CAAC,WAAW;;IAoB1D,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;IAsSvF;;;;OAIG;IACG,kBAAkB,CAAE,OAAO,CAAC,EAAE,KAAK,CAAC,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAwB7E;AAED,eAAe,IAAI,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"}