pg-boss 10.0.0-beta1 → 10.0.0-beta3

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-beta3",
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,8 +8,8 @@ module.exports = {
8
8
  checkWorkArgs,
9
9
  checkFetchArgs,
10
10
  warnClockSkew,
11
- queueNameHasPatternMatch,
12
- assertPostgresObjectName
11
+ assertPostgresObjectName,
12
+ assertQueueName
13
13
  }
14
14
 
15
15
  const MAX_INTERVAL_HOURS = 24
@@ -30,8 +30,6 @@ const WARNINGS = {
30
30
  }
31
31
 
32
32
  function checkQueueArgs (name, options = {}) {
33
- assertPostgresObjectName(name)
34
-
35
33
  assert(!('deadLetter' in options) || (typeof options.deadLetter === 'string'), 'deadLetter must be a string')
36
34
 
37
35
  applyRetryConfig(options)
@@ -140,24 +138,12 @@ function checkWorkArgs (name, args, defaults) {
140
138
  function checkFetchArgs (name, batchSize, options) {
141
139
  assert(name, 'missing queue name')
142
140
 
143
- if (queueNameHasPatternMatch(name)) {
144
- name = sanitizeQueueNameForFetch(name)
145
- }
146
-
147
141
  assert(!batchSize || (Number.isInteger(batchSize) && batchSize >= 1), 'batchSize must be an integer > 0')
148
142
  assert(!('includeMetadata' in options) || typeof options.includeMetadata === 'boolean', 'includeMetadata must be a boolean')
149
143
 
150
144
  return { name }
151
145
  }
152
146
 
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
147
  function getConfig (value) {
162
148
  assert(value && (typeof value === 'object' || typeof value === 'string'),
163
149
  'configuration assert: string or config object is required to connect to postgres')
@@ -191,8 +177,14 @@ function applySchemaConfig (config) {
191
177
  function assertPostgresObjectName (name) {
192
178
  assert(typeof name === 'string', 'Name must be a string')
193
179
  assert(name.length <= 50, 'Name cannot exceed 50 characters')
194
- assert(!/\W/.test(name), 'Name can only contain alphanumeric characters and underscores')
195
- assert(!/^d/.test(name), 'Name cannot start with a number')
180
+ assert(!/\W/.test(name), 'Name can only contain alphanumeric characters or underscores')
181
+ assert(!/^\d/.test(name), 'Name cannot start with a number')
182
+ }
183
+
184
+ function assertQueueName (name) {
185
+ assert(typeof name === 'string', 'Name must be a string')
186
+ assert(name.length <= 50, 'Name cannot exceed 50 characters')
187
+ assert(/[\w-]/.test(name), 'Name can only contain alphanumeric characters, underscores, or hyphens')
196
188
  }
197
189
 
198
190
  function applyArchiveConfig (config) {
package/src/contractor.js CHANGED
@@ -21,6 +21,12 @@ class Contractor {
21
21
  this.config = config
22
22
  this.db = db
23
23
  this.migrations = this.config.migrations || migrationStore.getAll(this.config.schema)
24
+
25
+ // exported api to index
26
+ this.functions = [
27
+ this.version,
28
+ this.isInstalled
29
+ ]
24
30
  }
25
31
 
26
32
  async version () {
@@ -40,7 +46,8 @@ class Contractor {
40
46
  const version = await this.version()
41
47
 
42
48
  if (schemaVersion > version) {
43
- await this.migrate(version)
49
+ throw new Error('Migrations are not supported to v10')
50
+ // await this.migrate(version)
44
51
  }
45
52
  } else {
46
53
  await this.create()
package/src/db.js CHANGED
@@ -11,6 +11,10 @@ class Db extends EventEmitter {
11
11
  this.config = config
12
12
  }
13
13
 
14
+ events = {
15
+ error: 'error'
16
+ }
17
+
14
18
  async open () {
15
19
  this.pool = new pg.Pool(this.config)
16
20
  this.pool.on('error', error => this.emit('error', error))
@@ -26,15 +30,15 @@ class Db extends EventEmitter {
26
30
 
27
31
  async executeSql (text, values) {
28
32
  if (this.opened) {
29
- // if (this.config.debug === true) {
30
- // console.log(`${new Date().toISOString()}: DEBUG SQL`)
31
- // console.log(text)
33
+ if (this.config.debug === true) {
34
+ console.log(`${new Date().toISOString()}: DEBUG SQL`)
35
+ console.log(text)
32
36
 
33
- // if (values) {
34
- // console.log(`${new Date().toISOString()}: DEBUG VALUES`)
35
- // console.log(values)
36
- // }
37
- // }
37
+ if (values) {
38
+ console.log(`${new Date().toISOString()}: DEBUG VALUES`)
39
+ console.log(values)
40
+ }
41
+ }
38
42
 
39
43
  return await this.pool.query(text, values)
40
44
  }
@@ -68,14 +72,6 @@ class Db extends EventEmitter {
68
72
 
69
73
  return locker
70
74
  }
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
75
  }
80
76
 
81
77
  module.exports = Db
package/src/index.js CHANGED
@@ -13,6 +13,15 @@ const events = {
13
13
  stopped: 'stopped'
14
14
  }
15
15
  class PgBoss extends EventEmitter {
16
+ #stoppingOn
17
+ #stopped
18
+ #config
19
+ #db
20
+ #boss
21
+ #contractor
22
+ #manager
23
+ #timekeeper
24
+
16
25
  static getConstructionPlans (schema) {
17
26
  return Contractor.constructionPlans(schema)
18
27
  }
@@ -26,60 +35,72 @@ class PgBoss extends EventEmitter {
26
35
  }
27
36
 
28
37
  constructor (value) {
29
- const config = Attorney.getConfig(value)
30
-
31
38
  super()
32
39
 
33
- const db = getDb(config)
40
+ this.#stoppingOn = null
41
+ this.#stopped = true
42
+
43
+ const config = Attorney.getConfig(value)
44
+ this.#config = config
45
+
46
+ const db = this.getDb()
47
+ this.#db = db
34
48
 
35
49
  if (db.isOurs) {
36
- promoteEvent.call(this, db, 'error')
50
+ this.#promoteEvents(db)
37
51
  }
38
52
 
39
- const manager = new Manager(db, config)
40
- Object.keys(manager.events).forEach(event => promoteEvent.call(this, manager, manager.events[event]))
41
- manager.functions.forEach(func => promoteFunction.call(this, manager, func))
53
+ const contractor = new Contractor(db, config)
42
54
 
55
+ const manager = new Manager(db, config)
43
56
  const bossConfig = { ...config, manager }
44
57
 
45
58
  const boss = new Boss(db, bossConfig)
46
- Object.keys(boss.events).forEach(event => promoteEvent.call(this, boss, boss.events[event]))
47
- boss.functions.forEach(func => promoteFunction.call(this, boss, func))
48
59
 
49
60
  const timekeeper = new Timekeeper(db, bossConfig)
50
- Object.keys(timekeeper.events).forEach(event => promoteEvent.call(this, timekeeper, timekeeper.events[event]))
51
- timekeeper.functions.forEach(func => promoteFunction.call(this, timekeeper, func))
52
-
53
61
  manager.timekeeper = timekeeper
54
62
 
55
- this.stoppingOn = null
56
- this.stopped = true
57
- this.config = config
58
- this.db = db
59
- this.boss = boss
60
- this.contractor = new Contractor(db, config)
61
- this.manager = manager
62
- this.timekeeper = timekeeper
63
-
64
- function getDb (config) {
65
- if (config.db) {
66
- return config.db
67
- }
63
+ this.#promoteEvents(manager)
64
+ this.#promoteEvents(boss)
65
+ this.#promoteEvents(timekeeper)
68
66
 
69
- const db = new Db(config)
70
- db.isOurs = true
71
- return db
67
+ this.#promoteFunctions(boss)
68
+ this.#promoteFunctions(contractor)
69
+ this.#promoteFunctions(manager)
70
+ this.#promoteFunctions(timekeeper)
71
+
72
+ this.#boss = boss
73
+ this.#contractor = contractor
74
+ this.#manager = manager
75
+ this.#timekeeper = timekeeper
76
+ }
77
+
78
+ getDb () {
79
+ if (this.#db) {
80
+ return this.#db
72
81
  }
73
82
 
74
- function promoteFunction (obj, func) {
75
- this[func.name] = (...args) => func.apply(obj, args)
83
+ if (this.#config.db) {
84
+ return this.#config.db
76
85
  }
77
86
 
78
- function promoteEvent (emitter, event) {
87
+ const db = new Db(this.#config)
88
+ db.isOurs = true
89
+ return db
90
+ }
91
+
92
+ #promoteEvents (emitter) {
93
+ for (const event of Object.values(emitter?.events)) {
79
94
  emitter.on(event, arg => this.emit(event, arg))
80
95
  }
81
96
  }
82
97
 
98
+ #promoteFunctions (obj) {
99
+ for (const func of obj?.functions) {
100
+ this[func.name] = (...args) => func.apply(obj, args)
101
+ }
102
+ }
103
+
83
104
  async start () {
84
105
  if (this.starting || this.started) {
85
106
  return
@@ -87,39 +108,39 @@ class PgBoss extends EventEmitter {
87
108
 
88
109
  this.starting = true
89
110
 
90
- if (this.db.isOurs && !this.db.opened) {
91
- await this.db.open()
111
+ if (this.#db.isOurs && !this.#db.opened) {
112
+ await this.#db.open()
92
113
  }
93
114
 
94
- if (this.config.migrate) {
95
- await this.contractor.start()
115
+ if (this.#config.migrate) {
116
+ await this.#contractor.start()
96
117
  } else {
97
- await this.contractor.check()
118
+ await this.#contractor.check()
98
119
  }
99
120
 
100
- this.manager.start()
121
+ this.#manager.start()
101
122
 
102
- if (this.config.supervise) {
103
- await this.boss.supervise()
123
+ if (this.#config.supervise) {
124
+ await this.#boss.supervise()
104
125
  }
105
126
 
106
- if (this.config.monitorStateIntervalSeconds) {
107
- await this.boss.monitor()
127
+ if (this.#config.monitorStateIntervalSeconds) {
128
+ await this.#boss.monitor()
108
129
  }
109
130
 
110
- if (this.config.schedule) {
111
- await this.timekeeper.start()
131
+ if (this.#config.schedule) {
132
+ await this.#timekeeper.start()
112
133
  }
113
134
 
114
135
  this.starting = false
115
136
  this.started = true
116
- this.stopped = false
137
+ this.#stopped = false
117
138
 
118
139
  return this
119
140
  }
120
141
 
121
142
  async stop (options = {}) {
122
- if (this.stoppingOn || this.stopped) {
143
+ if (this.#stoppingOn || this.#stopped) {
123
144
  return
124
145
  }
125
146
 
@@ -127,27 +148,27 @@ class PgBoss extends EventEmitter {
127
148
 
128
149
  timeout = Math.max(timeout, 1000)
129
150
 
130
- this.stoppingOn = Date.now()
151
+ this.#stoppingOn = Date.now()
131
152
 
132
- await this.manager.stop()
133
- await this.timekeeper.stop()
134
- await this.boss.stop()
153
+ await this.#manager.stop()
154
+ await this.#timekeeper.stop()
155
+ await this.#boss.stop()
135
156
 
136
157
  await new Promise((resolve, reject) => {
137
158
  const shutdown = async () => {
138
159
  try {
139
- if (this.config.__test__throw_shutdown) {
140
- throw new Error(this.config.__test__throw_shutdown)
160
+ if (this.#config.__test__throw_shutdown) {
161
+ throw new Error(this.#config.__test__throw_shutdown)
141
162
  }
142
163
 
143
- await this.manager.failWip()
164
+ await this.#manager.failWip()
144
165
 
145
- if (this.db.isOurs && this.db.opened && destroy) {
146
- await this.db.close()
166
+ if (this.#db.isOurs && this.#db.opened && destroy) {
167
+ await this.#db.close()
147
168
  }
148
169
 
149
- this.stopped = true
150
- this.stoppingOn = null
170
+ this.#stopped = true
171
+ this.#stoppingOn = null
151
172
  this.started = false
152
173
 
153
174
  this.emit(events.stopped)
@@ -168,13 +189,13 @@ class PgBoss extends EventEmitter {
168
189
 
169
190
  setImmediate(async () => {
170
191
  try {
171
- if (this.config.__test__throw_stop_monitor) {
172
- throw new Error(this.config.__test__throw_stop_monitor)
192
+ if (this.#config.__test__throw_stop_monitor) {
193
+ throw new Error(this.#config.__test__throw_stop_monitor)
173
194
  }
174
195
 
175
- const isWip = () => this.manager.getWipData({ includeInternal: false }).length > 0
196
+ const isWip = () => this.#manager.getWipData({ includeInternal: false }).length > 0
176
197
 
177
- while ((Date.now() - this.stoppingOn) < timeout && isWip()) {
198
+ while ((Date.now() - this.#stoppingOn) < timeout && isWip()) {
178
199
  await delay(500)
179
200
  }
180
201
 
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,37 +512,43 @@ 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
 
560
547
  async createQueue (name, options = {}) {
561
548
  assert(name, 'Missing queue name argument')
562
549
 
550
+ Attorney.assertQueueName(name)
551
+
563
552
  const { policy = QUEUE_POLICY.standard } = options
564
553
 
565
554
  assert(policy in QUEUE_POLICY, `${policy} is not a valid queue policy`)
@@ -573,7 +562,7 @@ class Manager extends EventEmitter {
573
562
  deadLetter
574
563
  } = Attorney.checkQueueArgs(name, options)
575
564
 
576
- const paritionSql = plans.partitionCreateJobName(this.config.schema, name)
565
+ const paritionSql = plans.createPartition(this.config.schema, name)
577
566
 
578
567
  await this.db.executeSql(paritionSql)
579
568
 
@@ -659,8 +648,8 @@ class Manager extends EventEmitter {
659
648
  const result = await this.db.executeSql(queueSql, [name])
660
649
 
661
650
  if (result?.rows?.length) {
662
- Attorney.assertPostgresObjectName(name)
663
- const sql = plans.dropJobTablePartition(this.config.schema, name)
651
+ Attorney.assertQueueName(name)
652
+ const sql = plans.dropPartition(this.config.schema, name)
664
653
  await this.db.executeSql(sql)
665
654
  }
666
655
 
@@ -690,15 +679,15 @@ class Manager extends EventEmitter {
690
679
  return result ? parseFloat(result.rows[0].count) : null
691
680
  }
692
681
 
693
- async getJobById (id, options = {}) {
682
+ async getJobById (queue, id, options = {}) {
694
683
  const db = options.db || this.db
695
- const result1 = await db.executeSql(this.getJobByIdCommand, [id])
684
+ const result1 = await db.executeSql(this.getJobByIdCommand, [queue, id])
696
685
 
697
686
  if (result1 && result1.rows && result1.rows.length === 1) {
698
687
  return result1.rows[0]
699
688
  }
700
689
 
701
- const result2 = await db.executeSql(this.getArchivedJobByIdCommand, [id])
690
+ const result2 = await db.executeSql(this.getArchivedJobByIdCommand, [queue, id])
702
691
 
703
692
  if (result2 && result2.rows && result2.rows.length === 1) {
704
693
  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
@@ -46,8 +46,8 @@ module.exports = {
46
46
  countStates,
47
47
  createQueue,
48
48
  updateQueue,
49
- partitionCreateJobName,
50
- dropJobTablePartition,
49
+ createPartition,
50
+ dropPartition,
51
51
  deleteQueueRecords,
52
52
  getQueueByName,
53
53
  getQueueSize,
@@ -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,13 +90,16 @@ 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),
99
96
  createTableSchedule(schema),
100
97
  createTableSubscription(schema),
101
98
 
99
+ getPartitionFunction(schema),
100
+ createPartitionFunction(schema),
101
+ dropPartitionFunction(schema),
102
+
102
103
  insertVersion(schema, version)
103
104
  ]
104
105
 
@@ -165,24 +166,53 @@ function createTableJob (schema) {
165
166
  `
166
167
  }
167
168
 
168
- function createTableJobDefault (schema) {
169
- return `CREATE TABLE ${schema}.job_default (LIKE ${schema}.job INCLUDING DEFAULTS INCLUDING CONSTRAINTS)`
169
+ function createPartition (schema, name) {
170
+ return `SELECT ${schema}.create_partition('${name}');`
170
171
  }
171
172
 
172
- function attachPartitionJobDefault (schema) {
173
- return `ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.job_default DEFAULT`
173
+ function getPartitionFunction (schema) {
174
+ return `
175
+ CREATE FUNCTION ${schema}.get_partition(queue_name text, out name text) AS
176
+ $$
177
+ SELECT '${schema}.j' || encode(digest(queue_name, 'sha1'), 'hex');
178
+ $$
179
+ LANGUAGE SQL
180
+ IMMUTABLE
181
+ `
174
182
  }
175
183
 
176
- function partitionCreateJobName (schema, name) {
184
+ function createPartitionFunction (schema) {
177
185
  return `
178
- CREATE TABLE ${schema}.job_${name} (LIKE ${schema}.job INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
179
- ALTER TABLE ${schema}.job_${name} ADD CONSTRAINT job_check_${name} CHECK (name='${name}');
180
- ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.job_${name} FOR VALUES IN ('${name}');
186
+ CREATE FUNCTION ${schema}.create_partition(queue_name text)
187
+ RETURNS VOID AS
188
+ $$
189
+ DECLARE
190
+ table_name varchar := ${schema}.get_partition(queue_name);
191
+ BEGIN
192
+ EXECUTE format('CREATE TABLE %I (LIKE ${schema}.job INCLUDING DEFAULTS INCLUDING CONSTRAINTS)', table_name);
193
+ EXECUTE format('ALTER TABLE %I ADD CHECK (name=%L)', table_name, queue_name);
194
+ EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION %I FOR VALUES IN (%L)', table_name, queue_name);
195
+ END;
196
+ $$
197
+ LANGUAGE plpgsql;
181
198
  `
182
199
  }
183
200
 
184
- function dropJobTablePartition (schema, name) {
185
- return `DROP TABLE IF EXISTS ${schema}.job_${name}`
201
+ function dropPartitionFunction (schema) {
202
+ return `
203
+ CREATE FUNCTION ${schema}.drop_partition(queue_name text)
204
+ RETURNS VOID AS
205
+ $$
206
+ BEGIN
207
+ EXECUTE format('DROP TABLE IF EXISTS %I', ${schema}.get_partition(queue_name));
208
+ END;
209
+ $$
210
+ LANGUAGE plpgsql;
211
+ `
212
+ }
213
+
214
+ function dropPartition (schema, name) {
215
+ return `SELECT ${schema}.drop_partition('${name}');`
186
216
  }
187
217
 
188
218
  function createPrimaryKeyArchive (schema) {
@@ -210,21 +240,17 @@ function createIndexJobThrottleKey (schema) {
210
240
  }
211
241
 
212
242
  function createIndexJobName (schema) {
213
- return `CREATE INDEX job_name ON ${schema}.job (name text_pattern_ops)`
243
+ return `CREATE INDEX job_name ON ${schema}.job (name)`
214
244
  }
215
245
 
216
246
  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}'`
247
+ return `CREATE INDEX job_fetch ON ${schema}.job (name, startAfter) INCLUDE (priority, createdOn, id) WHERE state < '${states.active}'`
218
248
  }
219
249
 
220
250
  function createTableArchive (schema) {
221
251
  return `CREATE TABLE ${schema}.archive (LIKE ${schema}.job)`
222
252
  }
223
253
 
224
- function createArchiveBackupTable (schema) {
225
- return `CREATE TABLE ${schema}.archive_backup (LIKE ${schema}.job)`
226
- }
227
-
228
254
  function createColumnArchiveArchivedOn (schema) {
229
255
  return `ALTER TABLE ${schema}.archive ADD archivedOn timestamptz NOT NULL DEFAULT now()`
230
256
  }
@@ -422,12 +448,12 @@ function insertVersion (schema, version) {
422
448
  }
423
449
 
424
450
  function fetchNextJob (schema) {
425
- return ({ includeMetadata, patternMatch, priority = true } = {}) => `
451
+ return ({ includeMetadata, priority = true } = {}) => `
426
452
  WITH next as (
427
453
  SELECT id
428
454
  FROM ${schema}.job
429
- WHERE state < '${states.active}'
430
- AND name ${patternMatch ? 'LIKE' : '='} $1
455
+ WHERE name = $1
456
+ AND state < '${states.active}'
431
457
  AND startAfter < now()
432
458
  ORDER BY ${priority && 'priority desc, '} createdOn, id
433
459
  LIMIT $2
@@ -438,7 +464,7 @@ function fetchNextJob (schema) {
438
464
  startedOn = now(),
439
465
  retryCount = CASE WHEN startedOn IS NOT NULL THEN retryCount + 1 ELSE retryCount END
440
466
  FROM next
441
- WHERE j.id = next.id
467
+ WHERE name = $1 AND j.id = next.id
442
468
  RETURNING ${includeMetadata ? 'j.*' : 'j.id, name, data'},
443
469
  EXTRACT(epoch FROM expireIn) as expire_in_seconds
444
470
  `
@@ -450,8 +476,9 @@ function completeJobs (schema) {
450
476
  UPDATE ${schema}.job
451
477
  SET completedOn = now(),
452
478
  state = '${states.completed}',
453
- output = $2::jsonb
454
- WHERE id IN (SELECT UNNEST($1::uuid[]))
479
+ output = $3::jsonb
480
+ WHERE name = $1
481
+ AND id IN (SELECT UNNEST($2::uuid[]))
455
482
  AND state = '${states.active}'
456
483
  RETURNING *
457
484
  )
@@ -460,8 +487,8 @@ function completeJobs (schema) {
460
487
  }
461
488
 
462
489
  function failJobsById (schema) {
463
- const where = `id IN (SELECT UNNEST($1::uuid[])) AND state < '${states.completed}'`
464
- const output = '$2::jsonb'
490
+ const where = `name = $1 AND id IN (SELECT UNNEST($2::uuid[])) AND state < '${states.completed}'`
491
+ const output = '$3::jsonb'
465
492
 
466
493
  return failJobs(schema, where, output)
467
494
  }
@@ -518,7 +545,8 @@ function cancelJobs (schema) {
518
545
  UPDATE ${schema}.job
519
546
  SET completedOn = now(),
520
547
  state = '${states.cancelled}'
521
- WHERE id IN (SELECT UNNEST($1::uuid[]))
548
+ WHERE name = $1
549
+ AND id IN (SELECT UNNEST($2::uuid[]))
522
550
  AND state < '${states.completed}'
523
551
  RETURNING 1
524
552
  )
@@ -532,7 +560,8 @@ function resumeJobs (schema) {
532
560
  UPDATE ${schema}.job
533
561
  SET completedOn = NULL,
534
562
  state = '${states.created}'
535
- WHERE id IN (SELECT UNNEST($1::uuid[]))
563
+ WHERE name = $1
564
+ AND id IN (SELECT UNNEST($2::uuid[]))
536
565
  RETURNING 1
537
566
  )
538
567
  SELECT COUNT(*) from results
@@ -735,8 +764,8 @@ function locked (schema, query) {
735
764
  }
736
765
 
737
766
  function advisoryLock (schema, key) {
738
- return `SELECT pg_advisory_xact_lock(
739
- ('x' || md5(current_database() || '.pgboss.${schema}${key || ''}'))::bit(64)::bigint
767
+ return `SELECT pg_advisory_xact_lock(
768
+ ('x' || encode(digest(current_database() || '.pgboss.${schema}${key || ''}', 'sha256'), 'hex'))::bit(64)::bigint
740
769
  )`
741
770
  }
742
771
 
@@ -754,5 +783,5 @@ function getArchivedJobById (schema) {
754
783
  }
755
784
 
756
785
  function getJobByTableAndId (schema, table) {
757
- return `SELECT * FROM ${schema}.${table} WHERE id = $1`
786
+ return `SELECT * FROM ${schema}.${table} WHERE name = $1 AND id = $2`
758
787
  }
package/src/timekeeper.js CHANGED
@@ -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,24 +331,24 @@ 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
- createQueue(name: string, policy: 'standard' | 'short' | 'singleton' | 'stately'): Promise<void>;
351
+ createQueue(name: string, options?: { policy: 'standard' | 'short' | 'singleton' | 'stately' }): Promise<void>;
352
352
  deleteQueue(name: string): Promise<void>;
353
353
  purgeQueue(name: string): Promise<void>;
354
354
  clearStorage(): Promise<void>;