pg-boss 11.0.7 → 11.1.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 CHANGED
@@ -82,10 +82,10 @@ To run the test suite, linter and code coverage:
82
82
  npm run cover
83
83
  ```
84
84
 
85
- The test suite will try and create a new database named pgboss. The [config.json](test/config.json) file has the default credentials to connect to postgres.
85
+ The test suite will try and create a new database named pgboss. The [config.json](https://github.com/timgit/pg-boss/test/config.json) file has the default credentials to connect to postgres.
86
86
 
87
- The [Docker Compose](docker-compose.yaml) file can be used to start a local postgres instance for testing:
87
+ The [Docker Compose](https://github.com/timgit/pg-boss/docker-compose.yaml) file can be used to start a local postgres instance for testing:
88
88
 
89
89
  ```bash
90
- docker-compose up -d
90
+ docker compose up
91
91
  ```
@@ -1,10 +1,10 @@
1
1
  services:
2
2
  db:
3
- image: postgres:17
3
+ image: postgres:18
4
4
  ports:
5
5
  - 5432:5432
6
6
  volumes:
7
- - db_volume:/var/lib/postgresql/data
7
+ - db_volume:/var/lib/postgresql/data18
8
8
  environment:
9
9
  - POSTGRES_DB=pgboss
10
10
  - POSTGRES_NAME=pgboss
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pg-boss",
3
- "version": "11.0.7",
3
+ "version": "11.1.0",
4
4
  "description": "Queueing jobs in Postgres from Node.js like a boss",
5
5
  "main": "./src/index.js",
6
6
  "type": "commonjs",
@@ -62,6 +62,6 @@
62
62
  "bugs": {
63
63
  "url": "https://github.com/timgit/pg-boss/issues"
64
64
  },
65
- "homepage": "https://github.com/timgit/pg-boss#readme",
65
+ "homepage": "https://timgit.github.io/pg-boss",
66
66
  "types": "./types.d.ts"
67
67
  }
package/src/manager.js CHANGED
@@ -585,7 +585,11 @@ class Manager extends EventEmitter {
585
585
  assert(Object.keys(options).length > 0, 'no properties found to update')
586
586
 
587
587
  if ('policy' in options) {
588
- assert(options.policy in QUEUE_POLICIES, `${options.policy} is not a valid queue policy`)
588
+ throw new Error('queue policy cannot be changed after creation')
589
+ }
590
+
591
+ if ('partition' in options) {
592
+ throw new Error('queue partitioning cannot be changed after creation')
589
593
  }
590
594
 
591
595
  Attorney.validateQueueArgs(options)
@@ -64,5 +64,92 @@ function migrate (value, version, migrations) {
64
64
 
65
65
  function getAll (schema) {
66
66
  return [
67
- ]
67
+ {
68
+ release: '11.1.0',
69
+ version: 26,
70
+ previous: 25,
71
+ install: [
72
+ `
73
+ CREATE OR REPLACE FUNCTION ${schema}.create_queue(queue_name text, options jsonb)
74
+ RETURNS VOID AS
75
+ $$
76
+ DECLARE
77
+ tablename varchar := CASE WHEN options->>'partition' = 'true'
78
+ THEN 'j' || encode(sha224(queue_name::bytea), 'hex')
79
+ ELSE 'job_common'
80
+ END;
81
+ queue_created_on timestamptz;
82
+ BEGIN
83
+
84
+ WITH q as (
85
+ INSERT INTO ${schema}.queue (
86
+ name,
87
+ policy,
88
+ retry_limit,
89
+ retry_delay,
90
+ retry_backoff,
91
+ retry_delay_max,
92
+ expire_seconds,
93
+ retention_seconds,
94
+ deletion_seconds,
95
+ warning_queued,
96
+ dead_letter,
97
+ partition,
98
+ table_name
99
+ )
100
+ VALUES (
101
+ queue_name,
102
+ options->>'policy',
103
+ COALESCE((options->>'retryLimit')::int, 2),
104
+ COALESCE((options->>'retryDelay')::int, 0),
105
+ COALESCE((options->>'retryBackoff')::bool, false),
106
+ (options->>'retryDelayMax')::int,
107
+ COALESCE((options->>'expireInSeconds')::int, 900),
108
+ COALESCE((options->>'retentionSeconds')::int, 1209600),
109
+ COALESCE((options->>'deleteAfterSeconds')::int, 604800),
110
+ COALESCE((options->>'warningQueueSize')::int, 0),
111
+ options->>'deadLetter',
112
+ COALESCE((options->>'partition')::bool, false),
113
+ tablename
114
+ )
115
+ ON CONFLICT DO NOTHING
116
+ RETURNING created_on
117
+ )
118
+ SELECT created_on into queue_created_on from q;
119
+
120
+ IF queue_created_on IS NULL OR options->>'partition' IS DISTINCT FROM 'true' THEN
121
+ RETURN;
122
+ END IF;
123
+
124
+ EXECUTE format('CREATE TABLE ${schema}.%I (LIKE ${schema}.job INCLUDING DEFAULTS)', tablename);
125
+
126
+ EXECUTE format('ALTER TABLE ${schema}.%1$I ADD PRIMARY KEY (name, id)', tablename);
127
+ EXECUTE format('ALTER TABLE ${schema}.%1$I ADD CONSTRAINT q_fkey FOREIGN KEY (name) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED', tablename);
128
+ EXECUTE format('ALTER TABLE ${schema}.%1$I ADD CONSTRAINT dlq_fkey FOREIGN KEY (dead_letter) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED', tablename);
129
+
130
+ EXECUTE format('CREATE INDEX %1$s_i5 ON ${schema}.%1$I (name, start_after) INCLUDE (priority, created_on, id) WHERE state < ''active''', tablename);
131
+ EXECUTE format('CREATE UNIQUE INDEX %1$s_i4 ON ${schema}.%1$I (name, singleton_on, COALESCE(singleton_key, '''')) WHERE state <> ''cancelled'' AND singleton_on IS NOT NULL', tablename);
132
+
133
+ IF options->>'policy' = 'short' THEN
134
+ EXECUTE format('CREATE UNIQUE INDEX %1$s_i1 ON ${schema}.%1$I (name, COALESCE(singleton_key, '''')) WHERE state = ''created'' AND policy = ''short''', tablename);
135
+ ELSIF options->>'policy' = 'singleton' THEN
136
+ EXECUTE format('CREATE UNIQUE INDEX %1$s_i2 ON ${schema}.%1$I (name, COALESCE(singleton_key, '''')) WHERE state = ''active'' AND policy = ''singleton''', tablename);
137
+ ELSIF options->>'policy' = 'stately' THEN
138
+ EXECUTE format('CREATE UNIQUE INDEX %1$s_i3 ON ${schema}.%1$I (name, state, COALESCE(singleton_key, '''')) WHERE state <= ''active'' AND policy = ''stately''', tablename);
139
+ ELSIF options->>'policy' = 'exclusive' THEN
140
+ EXECUTE format('CREATE UNIQUE INDEX %1$s_i6 ON ${schema}.%1$I (name, COALESCE(singleton_key, '''')) WHERE state <= ''active'' AND policy = ''exclusive''', tablename);
141
+ END IF;
142
+
143
+ EXECUTE format('ALTER TABLE ${schema}.%I ADD CONSTRAINT cjc CHECK (name=%L)', tablename, queue_name);
144
+ EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.%I FOR VALUES IN (%L)', tablename, queue_name);
145
+ END;
146
+ $$
147
+ LANGUAGE plpgsql;
148
+ `,
149
+ `CREATE UNIQUE INDEX job_i6 ON ${schema}.job_common (name, COALESCE(singleton_key, '')) WHERE state <= 'active' AND policy = 'exclusive'`
150
+ ],
151
+ uninstall: [
152
+ `DROP INDEX ${schema}.job_i6`
153
+ ]
154
+ },]
68
155
  }
package/src/plans.js CHANGED
@@ -1,6 +1,9 @@
1
1
  const DEFAULT_SCHEMA = 'pgboss'
2
2
  const MIGRATE_RACE_MESSAGE = 'division by zero'
3
3
  const CREATE_RACE_MESSAGE = 'already exists'
4
+ const FIFTEEN_MINUTES = 60 * 15
5
+ const FORTEEN_DAYS = 60 * 60 * 24 * 14
6
+ const SEVEN_DAYS = 60 * 60 * 24 * 7
4
7
 
5
8
  const JOB_STATES = Object.freeze({
6
9
  created: 'created',
@@ -15,9 +18,23 @@ const QUEUE_POLICIES = Object.freeze({
15
18
  standard: 'standard',
16
19
  short: 'short',
17
20
  singleton: 'singleton',
18
- stately: 'stately'
21
+ stately: 'stately',
22
+ exclusive: 'exclusive'
19
23
  })
20
24
 
25
+ const QUEUE_DEFAULTS = {
26
+ expire_seconds: FIFTEEN_MINUTES,
27
+ retention_seconds: FORTEEN_DAYS,
28
+ deletion_seconds: SEVEN_DAYS,
29
+ retry_limit: 2,
30
+ retry_delay: 0,
31
+ warning_queued: 0,
32
+ retry_backoff: false,
33
+ partition: false
34
+ }
35
+
36
+ const COMMON_JOB_TABLE = 'job_common'
37
+
21
38
  module.exports = {
22
39
  create,
23
40
  insertVersion,
@@ -62,24 +79,7 @@ module.exports = {
62
79
  JOB_STATES,
63
80
  MIGRATE_RACE_MESSAGE,
64
81
  CREATE_RACE_MESSAGE,
65
- DEFAULT_SCHEMA
66
- }
67
-
68
- const COMMON_JOB_TABLE = 'job_common'
69
-
70
- const FIFTEEN_MINUTES = 60 * 15
71
- const FORTEEN_DAYS = 60 * 60 * 24 * 14
72
- const SEVEN_DAYS = 60 * 60 * 24 * 7
73
-
74
- const QUEUE_DEFAULTS = {
75
- expire_seconds: FIFTEEN_MINUTES,
76
- retention_seconds: FORTEEN_DAYS,
77
- deletion_seconds: SEVEN_DAYS,
78
- retry_limit: 2,
79
- retry_delay: 0,
80
- warning_queued: 0,
81
- retry_backoff: false,
82
- partition: false
82
+ DEFAULT_SCHEMA,
83
83
  }
84
84
 
85
85
  function create (schema, version) {
@@ -253,6 +253,7 @@ function createTableJobCommon (schema, table) {
253
253
  ${format(createIndexJobPolicyShort(schema))}
254
254
  ${format(createIndexJobPolicySingleton(schema))}
255
255
  ${format(createIndexJobPolicyStately(schema))}
256
+ ${format(createIndexJobPolicyExclusive(schema))}
256
257
  ${format(createIndexJobThrottle(schema))}
257
258
  ${format(createIndexJobFetch(schema))}
258
259
 
@@ -318,11 +319,19 @@ function createQueueFunction (schema) {
318
319
  EXECUTE format('${formatPartitionCommand(createPrimaryKeyJob(schema))}', tablename);
319
320
  EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJob(schema))}', tablename);
320
321
  EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJobDeadLetter(schema))}', tablename);
321
- EXECUTE format('${formatPartitionCommand(createIndexJobPolicyShort(schema))}', tablename);
322
- EXECUTE format('${formatPartitionCommand(createIndexJobPolicySingleton(schema))}', tablename);
323
- EXECUTE format('${formatPartitionCommand(createIndexJobPolicyStately(schema))}', tablename);
324
- EXECUTE format('${formatPartitionCommand(createIndexJobThrottle(schema))}', tablename);
322
+
325
323
  EXECUTE format('${formatPartitionCommand(createIndexJobFetch(schema))}', tablename);
324
+ EXECUTE format('${formatPartitionCommand(createIndexJobThrottle(schema))}', tablename);
325
+
326
+ IF options->>'policy' = 'short' THEN
327
+ EXECUTE format('${formatPartitionCommand(createIndexJobPolicyShort(schema))}', tablename);
328
+ ELSIF options->>'policy' = 'singleton' THEN
329
+ EXECUTE format('${formatPartitionCommand(createIndexJobPolicySingleton(schema))}', tablename);
330
+ ELSIF options->>'policy' = 'stately' THEN
331
+ EXECUTE format('${formatPartitionCommand(createIndexJobPolicyStately(schema))}', tablename);
332
+ ELSIF options->>'policy' = 'exclusive' THEN
333
+ EXECUTE format('${formatPartitionCommand(createIndexJobPolicyExclusive(schema))}', tablename);
334
+ END IF;
326
335
 
327
336
  EXECUTE format('ALTER TABLE ${schema}.%I ADD CONSTRAINT cjc CHECK (name=%L)', tablename, queue_name);
328
337
  EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.%I FOR VALUES IN (%L)', tablename, queue_name);
@@ -408,6 +417,10 @@ function createIndexJobFetch (schema) {
408
417
  return `CREATE INDEX job_i5 ON ${schema}.job (name, start_after) INCLUDE (priority, created_on, id) WHERE state < '${JOB_STATES.active}'`
409
418
  }
410
419
 
420
+ function createIndexJobPolicyExclusive (schema) {
421
+ return `CREATE UNIQUE INDEX job_i6 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state <= '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.exclusive}'`
422
+ }
423
+
411
424
  function trySetQueueMonitorTime (schema, queues, seconds) {
412
425
  return trySetQueueTimestamp(schema, queues, 'monitor_on', seconds)
413
426
  }
@@ -443,7 +456,6 @@ function updateQueue (schema, { deadLetter } = {}) {
443
456
  return `
444
457
  WITH options as (SELECT $2::jsonb as data)
445
458
  UPDATE ${schema}.queue SET
446
- policy = COALESCE(o.data->>'policy', policy),
447
459
  retry_limit = COALESCE((o.data->>'retryLimit')::int, retry_limit),
448
460
  retry_delay = COALESCE((o.data->>'retryDelay')::int, retry_delay),
449
461
  retry_backoff = COALESCE((o.data->>'retryBackoff')::bool, retry_backoff),
@@ -943,7 +955,7 @@ function getQueueStats (schema, table, queues) {
943
955
  (count(*) FILTER (WHERE state < '${JOB_STATES.active}'))::int as "queuedCount",
944
956
  (count(*) FILTER (WHERE state = '${JOB_STATES.active}'))::int as "activeCount",
945
957
  count(*)::int as "totalCount",
946
- array_agg(singleton_key) FILTER (WHERE policy IN ('${QUEUE_POLICIES.singleton}','${QUEUE_POLICIES.stately}') AND state IN ('${JOB_STATES.retry}','${JOB_STATES.active}')) as "singletonsActive"
958
+ array_agg(singleton_key) FILTER (WHERE policy IN ('${QUEUE_POLICIES.singleton}','${QUEUE_POLICIES.stately}') AND state = '${JOB_STATES.active}') as "singletonsActive"
947
959
  FROM ${schema}.${table}
948
960
  WHERE name IN (${getQueueInClause(queues)})
949
961
  GROUP BY 1
package/types.d.ts CHANGED
@@ -83,7 +83,7 @@ declare namespace PgBoss {
83
83
 
84
84
  type SendOptions = JobOptions & QueueOptions & ConnectionOptions
85
85
 
86
- type QueuePolicy = 'standard' | 'short' | 'singleton' | 'stately'
86
+ type QueuePolicy = 'standard' | 'short' | 'singleton' | 'stately' | 'exclusive'
87
87
 
88
88
  type Queue = {
89
89
  name: string;
@@ -302,8 +302,8 @@ declare class PgBoss extends EventEmitter {
302
302
 
303
303
  createQueue (name: string, options?: Omit<PgBoss.Queue, 'name'>): Promise<void>
304
304
  createQueue (options: PgBoss.Queue): Promise<void>
305
- updateQueue (name: string, options?: Omit<PgBoss.Queue, 'name'>): Promise<void>
306
- updateQueue (options: PgBoss.Queue): Promise<void>
305
+ updateQueue (name: string, options?: Omit<PgBoss.Queue, 'name', 'partition', 'policy'>): Promise<void>
306
+ updateQueue (options: Omit<PgBoss.Queue, 'partition', 'policy'>): Promise<void>
307
307
  deleteQueue (name: string): Promise<void>
308
308
  getQueues (): Promise<PgBoss.QueueResult[]>
309
309
  getQueue (name: string): Promise<PgBoss.QueueResult | null>
package/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "schema": 25
2
+ "schema": 26
3
3
  }