pg-boss 10.0.0-beta1 → 10.0.0-beta2

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
@@ -1,4 +1,4 @@
1
- Queueing jobs in Node.js using PostgreSQL like a boss.
1
+ Queueing jobs in Postgres from Node.js like a boss.
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/pg-boss.svg)](https://badge.fury.io/js/pg-boss)
4
4
  [![Build](https://github.com/timgit/pg-boss/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/timgit/pg-boss/actions/workflows/ci.yml)
@@ -41,15 +41,13 @@ This will likely cater the most to teams already familiar with the simplicity of
41
41
  * Backpressure-compatible polling workers
42
42
  * Cron scheduling
43
43
  * Pub/sub API for fan-out queue relationships
44
- * Priority, deferral, retries (with exponential backoff), rate limiting, debouncing
45
- * Direct table access for bulk loads via COPY or INSERT
44
+ * Priority queues, deferral, retries (with exponential backoff), rate limiting, debouncing
45
+ * Table operations via SQL for bulk loads via COPY or INSERT
46
46
  * Multi-master compatible (for example, in a Kubernetes ReplicaSet)
47
47
  * Dead letter queues
48
- * Automatic creation and migration of storage tables
49
- * Automatic maintenance operations to manage table growth
50
48
 
51
49
  ## Requirements
52
- * Node 18 or higher
50
+ * Node 20 or higher
53
51
  * PostgreSQL 12 or higher
54
52
 
55
53
  ## Installation
@@ -66,7 +64,6 @@ yarn add pg-boss
66
64
  * [Docs](docs/readme.md)
67
65
 
68
66
  ## Contributing
69
-
70
67
  To setup a development environment for this library:
71
68
 
72
69
  ```bash
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "pg-boss",
3
- "version": "10.0.0-beta1",
4
- "description": "Queueing jobs in Node.js using PostgreSQL like a boss",
3
+ "version": "10.0.0-beta2",
4
+ "description": "Queueing jobs in Postgres from Node.js like a boss",
5
5
  "main": "./src/index.js",
6
6
  "engines": {
7
- "node": ">=18"
7
+ "node": ">=20"
8
8
  },
9
9
  "dependencies": {
10
10
  "cron-parser": "^4.0.0",
@@ -17,14 +17,15 @@
17
17
  "@types/node": "^20.3.3",
18
18
  "luxon": "^3.0.1",
19
19
  "mocha": "^10.0.0",
20
- "nyc": "^15.1.0",
20
+ "nyc": "^17.0.0",
21
21
  "standard": "^17.0.0"
22
22
  },
23
23
  "scripts": {
24
24
  "test": "standard && mocha",
25
25
  "cover": "nyc npm test",
26
26
  "tsc": "tsc --noEmit types.d.ts",
27
- "readme": "node ./test/readme.js"
27
+ "readme": "node ./test/readme.js",
28
+ "migrate": "node -e 'console.log(require(\"./src\").getMigrationPlans())'"
28
29
  },
29
30
  "mocha": {
30
31
  "timeout": 10000,
package/src/attorney.js CHANGED
@@ -8,7 +8,6 @@ module.exports = {
8
8
  checkWorkArgs,
9
9
  checkFetchArgs,
10
10
  warnClockSkew,
11
- queueNameHasPatternMatch,
12
11
  assertPostgresObjectName
13
12
  }
14
13
 
@@ -140,24 +139,12 @@ function checkWorkArgs (name, args, defaults) {
140
139
  function checkFetchArgs (name, batchSize, options) {
141
140
  assert(name, 'missing queue name')
142
141
 
143
- if (queueNameHasPatternMatch(name)) {
144
- name = sanitizeQueueNameForFetch(name)
145
- }
146
-
147
142
  assert(!batchSize || (Number.isInteger(batchSize) && batchSize >= 1), 'batchSize must be an integer > 0')
148
143
  assert(!('includeMetadata' in options) || typeof options.includeMetadata === 'boolean', 'includeMetadata must be a boolean')
149
144
 
150
145
  return { name }
151
146
  }
152
147
 
153
- function sanitizeQueueNameForFetch (name) {
154
- return name.replace(/[%_*]/g, match => match === '*' ? '%' : '\\' + match)
155
- }
156
-
157
- function queueNameHasPatternMatch (name) {
158
- return name.includes('*')
159
- }
160
-
161
148
  function getConfig (value) {
162
149
  assert(value && (typeof value === 'object' || typeof value === 'string'),
163
150
  'configuration assert: string or config object is required to connect to postgres')
package/src/contractor.js CHANGED
@@ -40,7 +40,8 @@ class Contractor {
40
40
  const version = await this.version()
41
41
 
42
42
  if (schemaVersion > version) {
43
- await this.migrate(version)
43
+ throw new Error('Migrations are not supported to v10')
44
+ // await this.migrate(version)
44
45
  }
45
46
  } else {
46
47
  await this.create()
package/src/db.js CHANGED
@@ -26,15 +26,15 @@ class Db extends EventEmitter {
26
26
 
27
27
  async executeSql (text, values) {
28
28
  if (this.opened) {
29
- // if (this.config.debug === true) {
30
- // console.log(`${new Date().toISOString()}: DEBUG SQL`)
31
- // console.log(text)
29
+ if (this.config.debug === true) {
30
+ console.log(`${new Date().toISOString()}: DEBUG SQL`)
31
+ console.log(text)
32
32
 
33
- // if (values) {
34
- // console.log(`${new Date().toISOString()}: DEBUG VALUES`)
35
- // console.log(values)
36
- // }
37
- // }
33
+ if (values) {
34
+ console.log(`${new Date().toISOString()}: DEBUG VALUES`)
35
+ console.log(values)
36
+ }
37
+ }
38
38
 
39
39
  return await this.pool.query(text, values)
40
40
  }
@@ -68,14 +68,6 @@ class Db extends EventEmitter {
68
68
 
69
69
  return locker
70
70
  }
71
-
72
- static quotePostgresStr (str) {
73
- const delimeter = '$sanitize$'
74
- if (str.includes(delimeter)) {
75
- throw new Error(`Attempted to quote string that contains reserved Postgres delimeter: ${str}`)
76
- }
77
- return `${delimeter}${str}${delimeter}`
78
- }
79
71
  }
80
72
 
81
73
  module.exports = Db
package/src/manager.js CHANGED
@@ -8,7 +8,6 @@ const { delay } = require('./tools')
8
8
  const Attorney = require('./attorney')
9
9
  const Worker = require('./worker')
10
10
  const plans = require('./plans')
11
- const Db = require('./db')
12
11
 
13
12
  const { QUEUES: TIMEKEEPER_QUEUES } = require('./timekeeper')
14
13
  const { QUEUE_POLICY } = plans
@@ -107,10 +106,11 @@ class Manager extends EventEmitter {
107
106
  }
108
107
 
109
108
  async failWip () {
110
- const jobIds = Array.from(this.workers.values()).flatMap(w => w.jobs.map(j => j.id))
111
-
112
- if (jobIds.length) {
113
- await this.fail(jobIds, 'pg-boss shut down while active')
109
+ for (const worker of this.workers.values()) {
110
+ const jobIds = worker.jobs.map(j => j.id)
111
+ if (jobIds.length) {
112
+ await this.fail(worker.name, jobIds, 'pg-boss shut down while active')
113
+ }
114
114
  }
115
115
  }
116
116
 
@@ -220,8 +220,8 @@ class Manager extends EventEmitter {
220
220
  const maxExpiration = jobs.reduce((acc, i) => Math.max(acc, i.expire_in_seconds), 0)
221
221
 
222
222
  await resolveWithinSeconds(Promise.all([callback(jobs)]), maxExpiration)
223
- .then(() => this.complete(jobs.map(job => job.id)))
224
- .catch(err => this.fail(jobs.map(job => job.id), err))
223
+ .then(() => this.complete(name, jobs.map(job => job.id)))
224
+ .catch(err => this.fail(name, jobs.map(job => job.id), err))
225
225
  } else {
226
226
  if (refill) {
227
227
  queueSize += jobs.length || 1
@@ -229,8 +229,8 @@ class Manager extends EventEmitter {
229
229
 
230
230
  const allTeamPromise = pMap(jobs, job =>
231
231
  resolveWithinSeconds(callback(job), job.expire_in_seconds)
232
- .then(result => this.complete(job.id, result))
233
- .catch(err => this.fail(job.id, err))
232
+ .then(result => this.complete(name, job.id, result))
233
+ .catch(err => this.fail(name, job.id, err))
234
234
  .then(() => refill ? onRefill() : null)
235
235
  , { concurrency: teamConcurrency }
236
236
  ).catch(() => {}) // allow promises & non-promises to live together in harmony
@@ -462,32 +462,15 @@ class Manager extends EventEmitter {
462
462
  }
463
463
 
464
464
  async fetch (name, batchSize, options = {}) {
465
- const patternMatch = Attorney.queueNameHasPatternMatch(name)
466
465
  const values = Attorney.checkFetchArgs(name, batchSize, options)
467
466
  const db = options.db || this.db
468
- const nextJobSql = this.nextJobCommand({ ...options, patternMatch })
467
+ const nextJobSql = this.nextJobCommand({ ...options })
469
468
  const statementValues = [values.name, batchSize || 1]
470
469
 
471
470
  let result
472
471
 
473
472
  try {
474
- if (!options.db) {
475
- // Prepare/format now and send multi-statement transaction
476
- const fetchQuery = nextJobSql
477
- .replace('$1', Db.quotePostgresStr(statementValues[0]))
478
- .replace('$2', statementValues[1].toString())
479
-
480
- // eslint-disable-next-line no-unused-vars
481
- const [_begin, _setLocal, fetchResult, _commit] = await db.executeSql([
482
- 'BEGIN',
483
- 'SET LOCAL jit = OFF', // JIT can slow things down significantly
484
- fetchQuery,
485
- 'COMMIT'
486
- ].join(';\n'))
487
- result = fetchResult
488
- } else {
489
- result = await db.executeSql(nextJobSql, statementValues)
490
- }
473
+ result = await db.executeSql(nextJobSql, statementValues)
491
474
  } catch (err) {
492
475
  // errors from fetchquery should only be unique constraint violations
493
476
  }
@@ -529,31 +512,35 @@ class Manager extends EventEmitter {
529
512
  }
530
513
  }
531
514
 
532
- async complete (id, data, options = {}) {
515
+ async complete (name, id, data, options = {}) {
516
+ assert(name, 'Missing queue name argument')
533
517
  const db = options.db || this.db
534
518
  const ids = this.mapCompletionIdArg(id, 'complete')
535
- const result = await db.executeSql(this.completeJobsCommand, [ids, this.mapCompletionDataArg(data)])
519
+ const result = await db.executeSql(this.completeJobsCommand, [name, ids, this.mapCompletionDataArg(data)])
536
520
  return this.mapCompletionResponse(ids, result)
537
521
  }
538
522
 
539
- async fail (id, data, options = {}) {
523
+ async fail (name, id, data, options = {}) {
524
+ assert(name, 'Missing queue name argument')
540
525
  const db = options.db || this.db
541
526
  const ids = this.mapCompletionIdArg(id, 'fail')
542
- const result = await db.executeSql(this.failJobsByIdCommand, [ids, this.mapCompletionDataArg(data)])
527
+ const result = await db.executeSql(this.failJobsByIdCommand, [name, ids, this.mapCompletionDataArg(data)])
543
528
  return this.mapCompletionResponse(ids, result)
544
529
  }
545
530
 
546
- async cancel (id, options = {}) {
531
+ async cancel (name, id, options = {}) {
532
+ assert(name, 'Missing queue name argument')
547
533
  const db = options.db || this.db
548
534
  const ids = this.mapCompletionIdArg(id, 'cancel')
549
- const result = await db.executeSql(this.cancelJobsCommand, [ids])
535
+ const result = await db.executeSql(this.cancelJobsCommand, [name, ids])
550
536
  return this.mapCompletionResponse(ids, result)
551
537
  }
552
538
 
553
- async resume (id, options = {}) {
539
+ async resume (name, id, options = {}) {
540
+ assert(name, 'Missing queue name argument')
554
541
  const db = options.db || this.db
555
542
  const ids = this.mapCompletionIdArg(id, 'resume')
556
- const result = await db.executeSql(this.resumeJobsCommand, [ids])
543
+ const result = await db.executeSql(this.resumeJobsCommand, [name, ids])
557
544
  return this.mapCompletionResponse(ids, result)
558
545
  }
559
546
 
@@ -690,15 +677,15 @@ class Manager extends EventEmitter {
690
677
  return result ? parseFloat(result.rows[0].count) : null
691
678
  }
692
679
 
693
- async getJobById (id, options = {}) {
680
+ async getJobById (queue, id, options = {}) {
694
681
  const db = options.db || this.db
695
- const result1 = await db.executeSql(this.getJobByIdCommand, [id])
682
+ const result1 = await db.executeSql(this.getJobByIdCommand, [queue, id])
696
683
 
697
684
  if (result1 && result1.rows && result1.rows.length === 1) {
698
685
  return result1.rows[0]
699
686
  }
700
687
 
701
- const result2 = await db.executeSql(this.getArchivedJobByIdCommand, [id])
688
+ const result2 = await db.executeSql(this.getArchivedJobByIdCommand, [queue, id])
702
689
 
703
690
  if (result2 && result2.rows && result2.rows.length === 1) {
704
691
  return result2.rows[0]
@@ -64,166 +64,5 @@ function migrate (value, version, migrations) {
64
64
 
65
65
  function getAll (schema) {
66
66
  return [
67
- {
68
- release: '10.0.0',
69
- version: 21,
70
- previous: 20,
71
- install: [
72
- `DROP INDEX ${schema}.job_singletonKey`,
73
- `DROP INDEX ${schema}.job_singleton_queue`,
74
- `DROP INDEX ${schema}.job_singletonOn`,
75
- `DROP INDEX ${schema}.job_singletonKeyOn`,
76
- `DROP INDEX ${schema}.job_fetch`,
77
- `DROP INDEX ${schema}.job_name`,
78
-
79
- `ALTER TABLE ${schema}.job ADD COLUMN deadletter text`,
80
- `ALTER TABLE ${schema}.job ADD COLUMN policy text`,
81
- `ALTER TABLE ${schema}.job DROP COLUMN on_complete`,
82
-
83
- // update state enum
84
- `ALTER TABLE ${schema}.job ALTER COLUMN state TYPE text`,
85
- `ALTER TABLE ${schema}.job ALTER COLUMN state DROP DEFAULT`,
86
- `ALTER TABLE ${schema}.archive ALTER COLUMN state TYPE text`,
87
-
88
- `DROP TABLE IF EXISTS ${schema}.archive_backup`,
89
- `ALTER TABLE ${schema}.archive RENAME to archive_backup`,
90
- `ALTER INDEX ${schema}.archive_archivedon_idx RENAME to archive_backup_archivedon_idx`,
91
-
92
- `DROP TYPE ${schema}.job_state`,
93
- `CREATE TYPE ${schema}.job_state AS ENUM ('created','retry','active','completed','cancelled','failed')`,
94
-
95
- `ALTER TABLE ${schema}.job ALTER COLUMN state TYPE ${schema}.job_state USING state::${schema}.job_state`,
96
- `ALTER TABLE ${schema}.job ALTER COLUMN state SET DEFAULT 'created'::${schema}.job_state`,
97
-
98
- `DELETE FROM ${schema}.job WHERE name LIKE '__pgboss__%'`,
99
-
100
- // set up job partitioning
101
- `ALTER TABLE ${schema}.job RENAME TO job_default`,
102
- `ALTER TABLE ${schema}.job_default DROP CONSTRAINT IF EXISTS job_pkey`,
103
-
104
- `CREATE TABLE ${schema}.job (
105
- id uuid not null default gen_random_uuid(),
106
- name text not null,
107
- priority integer not null default(0),
108
- data jsonb,
109
- state ${schema}.job_state not null default('created'),
110
- retryLimit integer not null default(0),
111
- retryCount integer not null default(0),
112
- retryDelay integer not null default(0),
113
- retryBackoff boolean not null default false,
114
- startAfter timestamp with time zone not null default now(),
115
- startedOn timestamp with time zone,
116
- singletonKey text,
117
- singletonOn timestamp without time zone,
118
- expireIn interval not null default interval '15 minutes',
119
- createdOn timestamp with time zone not null default now(),
120
- completedOn timestamp with time zone,
121
- keepUntil timestamp with time zone NOT NULL default now() + interval '14 days',
122
- output jsonb,
123
- deadletter text,
124
- policy text,
125
- CONSTRAINT job_pkey PRIMARY KEY (name, id)
126
- ) PARTITION BY LIST (name)`,
127
-
128
- `ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.job_default DEFAULT`,
129
-
130
- `CREATE TABLE ${schema}.archive (LIKE ${schema}.job)`,
131
- `ALTER TABLE ${schema}.archive ADD CONSTRAINT archive_pkey PRIMARY KEY (name, id)`,
132
- `ALTER TABLE ${schema}.archive ADD archivedOn timestamptz NOT NULL DEFAULT now()`,
133
- `CREATE INDEX archive_archivedon_idx ON ${schema}.archive(archivedon)`,
134
- `CREATE INDEX archive_name_idx ON ${schema}.archive(name)`,
135
-
136
- `CREATE INDEX job_fetch ON ${schema}.job (name text_pattern_ops, startAfter) INCLUDE (priority, createdOn, id) WHERE state < 'active'`,
137
- `CREATE INDEX job_name ON ${schema}.job (name text_pattern_ops)`,
138
- `CREATE UNIQUE INDEX job_policy_short ON ${schema}.job (name) WHERE state = 'created' AND policy = 'short'`,
139
- `CREATE UNIQUE INDEX job_policy_singleton ON ${schema}.job (name) WHERE state = 'active' AND policy = 'singleton'`,
140
- `CREATE UNIQUE INDEX job_policy_stately ON ${schema}.job (name, state) WHERE state <= 'active' AND policy = 'stately'`,
141
- `CREATE UNIQUE INDEX job_throttle_key ON ${schema}.job (name, singletonKey) WHERE state <= 'completed' AND singletonOn IS NULL`,
142
- `CREATE UNIQUE INDEX job_throttle_on ON ${schema}.job (name, singletonOn, COALESCE(singletonKey, '')) WHERE state <= 'completed' AND singletonOn IS NOT NULL`,
143
-
144
- `ALTER TABLE ${schema}.version ADD COLUMN monitored_on timestamp with time zone`,
145
-
146
- `CREATE TABLE ${schema}.queue (
147
- name text primary key,
148
- policy text,
149
- retry_limit int,
150
- retry_delay int,
151
- retry_backoff bool,
152
- expire_seconds int,
153
- retention_minutes int,
154
- dead_letter text,
155
- created_on timestamp with time zone not null default now()
156
- )`
157
- ],
158
- uninstall: [
159
- `DROP INDEX ${schema}.job_policy_stately`,
160
- `DROP INDEX ${schema}.job_policy_short`,
161
- `DROP INDEX ${schema}.job_policy_singleton`,
162
- `DROP INDEX ${schema}.job_throttle_on`,
163
- `DROP INDEX ${schema}.job_throttle_key`,
164
- `DROP INDEX ${schema}.job_fetch`,
165
- `DROP INDEX ${schema}.job_name`,
166
- `ALTER TABLE ${schema}.job DETACH PARTITION ${schema}.job_default`,
167
- `DROP TABLE ${schema}.job`,
168
- `ALTER TABLE ${schema}.job_default RENAME TO job`,
169
- `DROP TABLE IF EXISTS ${schema}.archive_backup`,
170
- `DROP INDEX ${schema}.archive_name_idx`,
171
- `ALTER TABLE ${schema}.job DROP COLUMN deadletter`,
172
- `ALTER TABLE ${schema}.job DROP COLUMN policy`,
173
- `ALTER TABLE ${schema}.job ALTER COLUMN state TYPE text`,
174
- `ALTER TABLE ${schema}.job ALTER COLUMN state DROP DEFAULT`,
175
- `ALTER TABLE ${schema}.archive ALTER COLUMN state TYPE text`,
176
- `DROP TYPE ${schema}.job_state`,
177
- `CREATE TYPE ${schema}.job_state AS ENUM ('created','retry','active','completed','expired','cancelled','failed')`,
178
- `ALTER TABLE ${schema}.job ALTER COLUMN state TYPE ${schema}.job_state USING state::${schema}.job_state`,
179
- `ALTER TABLE ${schema}.job ALTER COLUMN state SET DEFAULT 'created'::${schema}.job_state`,
180
- `ALTER TABLE ${schema}.job ADD COLUMN on_complete bool NOT NULL DEFAULT false`,
181
- `ALTER TABLE ${schema}.archive ALTER COLUMN state TYPE ${schema}.job_state USING state::${schema}.job_state`,
182
- `ALTER TABLE ${schema}.archive DROP COLUMN policy`,
183
- `ALTER TABLE ${schema}.archive DROP CONSTRAINT archive_pkey`,
184
- `CREATE INDEX job_fetch ON ${schema}.job (name text_pattern_ops, startAfter) WHERE state < 'active'`,
185
- `CREATE INDEX job_name ON ${schema}.job (name text_pattern_ops)`,
186
- `CREATE UNIQUE INDEX job_singletonOn ON ${schema}.job (name, singletonOn) WHERE state < 'expired' AND singletonKey IS NULL`,
187
- `CREATE UNIQUE INDEX job_singletonKeyOn ON ${schema}.job (name, singletonOn, singletonKey) WHERE state < 'expired'`,
188
- `CREATE UNIQUE INDEX job_singletonKey ON ${schema}.job (name, singletonKey) WHERE state < 'completed' AND singletonOn IS NULL AND NOT singletonKey LIKE '\\_\\_pgboss\\_\\_singleton\\_queue%'`,
189
- `CREATE UNIQUE INDEX job_singleton_queue ON ${schema}.job (name, singletonKey) WHERE state < 'active' AND singletonOn IS NULL AND singletonKey LIKE '\\_\\_pgboss\\_\\_singleton\\_queue%'`,
190
- `DROP TABLE ${schema}.queue`,
191
- `ALTER TABLE ${schema}.version DROP COLUMN monitored_on`
192
- ]
193
- },
194
- {
195
- release: '7.4.0',
196
- version: 20,
197
- previous: 19,
198
- install: [
199
- `DROP INDEX ${schema}.job_singletonKey`,
200
- `DROP INDEX ${schema}.job_singleton_queue`,
201
- `CREATE UNIQUE INDEX job_singletonKey ON ${schema}.job (name, singletonKey) WHERE state < 'completed' AND singletonOn IS NULL AND NOT singletonKey LIKE '\\_\\_pgboss\\_\\_singleton\\_queue%'`,
202
- `CREATE UNIQUE INDEX job_singleton_queue ON ${schema}.job (name, singletonKey) WHERE state < 'active' AND singletonOn IS NULL AND singletonKey LIKE '\\_\\_pgboss\\_\\_singleton\\_queue%'`
203
- ],
204
- uninstall: [
205
- `DROP INDEX ${schema}.job_singletonKey`,
206
- `DROP INDEX ${schema}.job_singleton_queue`,
207
- `CREATE UNIQUE INDEX job_singletonKey ON ${schema}.job (name, singletonKey) WHERE state < 'completed' AND singletonOn IS NULL AND NOT singletonKey = '__pgboss__singleton_queue'`,
208
- `CREATE UNIQUE INDEX job_singleton_queue ON ${schema}.job (name, singletonKey) WHERE state < 'active' AND singletonOn IS NULL AND singletonKey = '__pgboss__singleton_queue'`
209
- ]
210
- },
211
- {
212
- release: '7.0.0',
213
- version: 19,
214
- previous: 18,
215
- install: [
216
- `CREATE TABLE ${schema}.subscription (
217
- event text not null,
218
- name text not null,
219
- created_on timestamp with time zone not null default now(),
220
- updated_on timestamp with time zone not null default now(),
221
- PRIMARY KEY(event, name)
222
- )`
223
- ],
224
- uninstall: [
225
- `DROP TABLE ${schema}.subscription`
226
- ]
227
- }
228
67
  ]
229
68
  }
package/src/plans.js CHANGED
@@ -77,8 +77,6 @@ function create (schema, version) {
77
77
  createEnumJobState(schema),
78
78
 
79
79
  createTableJob(schema),
80
- createTableJobDefault(schema),
81
- attachPartitionJobDefault(schema),
82
80
  createIndexJobName(schema),
83
81
  createIndexJobFetch(schema),
84
82
  createIndexJobPolicyStately(schema),
@@ -92,7 +90,6 @@ function create (schema, version) {
92
90
  createColumnArchiveArchivedOn(schema),
93
91
  createIndexArchiveArchivedOn(schema),
94
92
  createIndexArchiveName(schema),
95
- createArchiveBackupTable(schema),
96
93
 
97
94
  createTableVersion(schema),
98
95
  createTableQueue(schema),
@@ -165,14 +162,6 @@ function createTableJob (schema) {
165
162
  `
166
163
  }
167
164
 
168
- function createTableJobDefault (schema) {
169
- return `CREATE TABLE ${schema}.job_default (LIKE ${schema}.job INCLUDING DEFAULTS INCLUDING CONSTRAINTS)`
170
- }
171
-
172
- function attachPartitionJobDefault (schema) {
173
- return `ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.job_default DEFAULT`
174
- }
175
-
176
165
  function partitionCreateJobName (schema, name) {
177
166
  return `
178
167
  CREATE TABLE ${schema}.job_${name} (LIKE ${schema}.job INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
@@ -210,21 +199,17 @@ function createIndexJobThrottleKey (schema) {
210
199
  }
211
200
 
212
201
  function createIndexJobName (schema) {
213
- return `CREATE INDEX job_name ON ${schema}.job (name text_pattern_ops)`
202
+ return `CREATE INDEX job_name ON ${schema}.job (name)`
214
203
  }
215
204
 
216
205
  function createIndexJobFetch (schema) {
217
- return `CREATE INDEX job_fetch ON ${schema}.job (name text_pattern_ops, startAfter) INCLUDE (priority, createdOn, id) WHERE state < '${states.active}'`
206
+ return `CREATE INDEX job_fetch ON ${schema}.job (name, startAfter) INCLUDE (priority, createdOn, id) WHERE state < '${states.active}'`
218
207
  }
219
208
 
220
209
  function createTableArchive (schema) {
221
210
  return `CREATE TABLE ${schema}.archive (LIKE ${schema}.job)`
222
211
  }
223
212
 
224
- function createArchiveBackupTable (schema) {
225
- return `CREATE TABLE ${schema}.archive_backup (LIKE ${schema}.job)`
226
- }
227
-
228
213
  function createColumnArchiveArchivedOn (schema) {
229
214
  return `ALTER TABLE ${schema}.archive ADD archivedOn timestamptz NOT NULL DEFAULT now()`
230
215
  }
@@ -422,12 +407,12 @@ function insertVersion (schema, version) {
422
407
  }
423
408
 
424
409
  function fetchNextJob (schema) {
425
- return ({ includeMetadata, patternMatch, priority = true } = {}) => `
410
+ return ({ includeMetadata, priority = true } = {}) => `
426
411
  WITH next as (
427
412
  SELECT id
428
413
  FROM ${schema}.job
429
- WHERE state < '${states.active}'
430
- AND name ${patternMatch ? 'LIKE' : '='} $1
414
+ WHERE name = $1
415
+ AND state < '${states.active}'
431
416
  AND startAfter < now()
432
417
  ORDER BY ${priority && 'priority desc, '} createdOn, id
433
418
  LIMIT $2
@@ -438,7 +423,7 @@ function fetchNextJob (schema) {
438
423
  startedOn = now(),
439
424
  retryCount = CASE WHEN startedOn IS NOT NULL THEN retryCount + 1 ELSE retryCount END
440
425
  FROM next
441
- WHERE j.id = next.id
426
+ WHERE name = $1 AND j.id = next.id
442
427
  RETURNING ${includeMetadata ? 'j.*' : 'j.id, name, data'},
443
428
  EXTRACT(epoch FROM expireIn) as expire_in_seconds
444
429
  `
@@ -450,8 +435,9 @@ function completeJobs (schema) {
450
435
  UPDATE ${schema}.job
451
436
  SET completedOn = now(),
452
437
  state = '${states.completed}',
453
- output = $2::jsonb
454
- WHERE id IN (SELECT UNNEST($1::uuid[]))
438
+ output = $3::jsonb
439
+ WHERE name = $1
440
+ AND id IN (SELECT UNNEST($2::uuid[]))
455
441
  AND state = '${states.active}'
456
442
  RETURNING *
457
443
  )
@@ -460,8 +446,8 @@ function completeJobs (schema) {
460
446
  }
461
447
 
462
448
  function failJobsById (schema) {
463
- const where = `id IN (SELECT UNNEST($1::uuid[])) AND state < '${states.completed}'`
464
- const output = '$2::jsonb'
449
+ const where = `name = $1 AND id IN (SELECT UNNEST($2::uuid[])) AND state < '${states.completed}'`
450
+ const output = '$3::jsonb'
465
451
 
466
452
  return failJobs(schema, where, output)
467
453
  }
@@ -518,7 +504,8 @@ function cancelJobs (schema) {
518
504
  UPDATE ${schema}.job
519
505
  SET completedOn = now(),
520
506
  state = '${states.cancelled}'
521
- WHERE id IN (SELECT UNNEST($1::uuid[]))
507
+ WHERE name = $1
508
+ AND id IN (SELECT UNNEST($2::uuid[]))
522
509
  AND state < '${states.completed}'
523
510
  RETURNING 1
524
511
  )
@@ -532,7 +519,8 @@ function resumeJobs (schema) {
532
519
  UPDATE ${schema}.job
533
520
  SET completedOn = NULL,
534
521
  state = '${states.created}'
535
- WHERE id IN (SELECT UNNEST($1::uuid[]))
522
+ WHERE name = $1
523
+ AND id IN (SELECT UNNEST($2::uuid[]))
536
524
  RETURNING 1
537
525
  )
538
526
  SELECT COUNT(*) from results
@@ -754,5 +742,5 @@ function getArchivedJobById (schema) {
754
742
  }
755
743
 
756
744
  function getJobByTableAndId (schema, table) {
757
- return `SELECT * FROM ${schema}.${table} WHERE id = $1`
745
+ return `SELECT * FROM ${schema}.${table} WHERE name = $1 AND id = $2`
758
746
  }
package/src/timekeeper.js CHANGED
@@ -6,7 +6,7 @@ const pMap = require('p-map')
6
6
 
7
7
  const queues = {
8
8
  CRON: '__pgboss__cron',
9
- SEND_IT: '__pgboss__send-it'
9
+ SEND_IT: '__pgboss__send_it'
10
10
  }
11
11
 
12
12
  const events = {
@@ -52,6 +52,9 @@ class Timekeeper extends EventEmitter {
52
52
  // cache the clock skew from the db server
53
53
  await this.cacheClockSkew()
54
54
 
55
+ await this.manager.createQueue(queues.CRON)
56
+ await this.manager.createQueue(queues.SEND_IT)
57
+
55
58
  await this.manager.work(queues.CRON, { newJobCheckIntervalSeconds: this.config.cronWorkerIntervalSeconds }, (job) => this.onCron(job))
56
59
  await this.manager.work(queues.SEND_IT, { newJobCheckIntervalSeconds: this.config.cronWorkerIntervalSeconds, teamSize: 50, teamConcurrency: 5 }, (job) => this.onSendIt(job))
57
60
 
package/src/worker.js CHANGED
@@ -74,7 +74,7 @@ class Worker {
74
74
 
75
75
  this.lastJobDuration = duration
76
76
 
77
- if (!this.stopping && !this.beenNotified && duration < this.interval) {
77
+ if (!this.stopping && !this.beenNotified && (this.interval - duration > 500)) {
78
78
  this.loopDelayPromise = delay(this.interval - duration)
79
79
  await this.loopDelayPromise
80
80
  this.loopDelayPromise = null
package/types.d.ts CHANGED
@@ -331,22 +331,22 @@ declare class PgBoss extends EventEmitter {
331
331
  fetch<T>(name: string, batchSize: number, options: PgBoss.FetchOptions & { includeMetadata: true }): Promise<PgBoss.JobWithMetadata<T>[] | null>;
332
332
  fetch<T>(name: string, batchSize: number, options: PgBoss.FetchOptions): Promise<PgBoss.Job<T>[] | null>;
333
333
 
334
- cancel(id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
335
- cancel(ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
334
+ cancel(name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
335
+ cancel(name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
336
336
 
337
- resume(id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
338
- resume(ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
337
+ resume(name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
338
+ resume(name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
339
339
 
340
- complete(id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
341
- complete(id: string, data: object, options?: PgBoss.ConnectionOptions): Promise<void>;
342
- complete(ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
340
+ complete(name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
341
+ complete(name: string, id: string, data: object, options?: PgBoss.ConnectionOptions): Promise<void>;
342
+ complete(name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
343
343
 
344
- fail(id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
345
- fail(id: string, data: object, options?: PgBoss.ConnectionOptions): Promise<void>;
346
- fail(ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
344
+ fail(name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<void>;
345
+ fail(name: string, id: string, data: object, options?: PgBoss.ConnectionOptions): Promise<void>;
346
+ fail(name: string, ids: string[], options?: PgBoss.ConnectionOptions): Promise<void>;
347
347
 
348
348
  getQueueSize(name: string, options?: object): Promise<number>;
349
- getJobById(id: string, options?: PgBoss.ConnectionOptions): Promise<PgBoss.JobWithMetadata | null>;
349
+ getJobById(name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<PgBoss.JobWithMetadata | null>;
350
350
 
351
351
  createQueue(name: string, policy: 'standard' | 'short' | 'singleton' | 'stately'): Promise<void>;
352
352
  deleteQueue(name: string): Promise<void>;