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 +3 -3
- package/docker-compose.yaml +2 -2
- package/package.json +2 -2
- package/src/manager.js +5 -1
- package/src/migrationStore.js +88 -1
- package/src/plans.js +37 -25
- package/types.d.ts +3 -3
- package/version.json +1 -1
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
|
|
90
|
+
docker compose up
|
|
91
91
|
```
|
package/docker-compose.yaml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pg-boss",
|
|
3
|
-
"version": "11.0
|
|
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.
|
|
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
|
-
|
|
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)
|
package/src/migrationStore.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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