pg-boss 9.0.3 → 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/src/manager.js CHANGED
@@ -1,21 +1,18 @@
1
1
  const assert = require('assert')
2
2
  const EventEmitter = require('events')
3
- const delay = require('delay')
4
- const uuid = require('uuid')
3
+ const { randomUUID } = require('crypto')
5
4
  const debounce = require('lodash.debounce')
6
5
  const { serializeError: stringify } = require('serialize-error')
6
+ const pMap = require('p-map')
7
+ const { delay } = require('./tools')
7
8
  const Attorney = require('./attorney')
8
9
  const Worker = require('./worker')
9
- const Db = require('./db')
10
- const pMap = require('p-map')
10
+ const plans = require('./plans')
11
11
 
12
- const { QUEUES: BOSS_QUEUES } = require('./boss')
13
12
  const { QUEUES: TIMEKEEPER_QUEUES } = require('./timekeeper')
13
+ const { QUEUE_POLICY } = plans
14
14
 
15
- const INTERNAL_QUEUES = Object.values(BOSS_QUEUES).concat(Object.values(TIMEKEEPER_QUEUES)).reduce((acc, i) => ({ ...acc, [i]: i }), {})
16
-
17
- const plans = require('./plans')
18
- const { COMPLETION_JOB_PREFIX, SINGLETON_QUEUE_KEY } = plans
15
+ const INTERNAL_QUEUES = Object.values(TIMEKEEPER_QUEUES).reduce((acc, i) => ({ ...acc, [i]: i }), {})
19
16
 
20
17
  const WIP_EVENT_INTERVAL = 2000
21
18
  const WIP_DEBOUNCE_OPTIONS = { leading: true, trailing: true, maxWait: WIP_EVENT_INTERVAL }
@@ -27,16 +24,14 @@ const events = {
27
24
 
28
25
  const resolveWithinSeconds = async (promise, seconds) => {
29
26
  const timeout = Math.max(1, seconds) * 1000
30
- const reject = delay.reject(timeout, { value: new Error(`handler execution exceeded ${timeout}ms`) })
27
+ const reject = delay(timeout, `handler execution exceeded ${timeout}ms`)
31
28
 
32
29
  let result
33
30
 
34
31
  try {
35
32
  result = await Promise.race([promise, reject])
36
33
  } finally {
37
- try {
38
- reject.clear()
39
- } catch {}
34
+ reject.abort()
40
35
  }
41
36
 
42
37
  return result
@@ -58,7 +53,7 @@ class Manager extends EventEmitter {
58
53
  this.completeJobsCommand = plans.completeJobs(config.schema)
59
54
  this.cancelJobsCommand = plans.cancelJobs(config.schema)
60
55
  this.resumeJobsCommand = plans.resumeJobs(config.schema)
61
- this.failJobsCommand = plans.failJobs(config.schema)
56
+ this.failJobsByIdCommand = plans.failJobsById(config.schema)
62
57
  this.getJobByIdCommand = plans.getJobById(config.schema)
63
58
  this.getArchivedJobByIdCommand = plans.getArchivedJobById(config.schema)
64
59
  this.subscribeCommand = plans.subscribe(config.schema)
@@ -72,12 +67,9 @@ class Manager extends EventEmitter {
72
67
  this.resume,
73
68
  this.fail,
74
69
  this.fetch,
75
- this.fetchCompleted,
76
70
  this.work,
77
71
  this.offWork,
78
72
  this.notifyWorker,
79
- this.onComplete,
80
- this.offComplete,
81
73
  this.publish,
82
74
  this.subscribe,
83
75
  this.unsubscribe,
@@ -85,13 +77,14 @@ class Manager extends EventEmitter {
85
77
  this.send,
86
78
  this.sendDebounced,
87
79
  this.sendThrottled,
88
- this.sendOnce,
89
80
  this.sendAfter,
90
- this.sendSingleton,
81
+ this.createQueue,
82
+ this.updateQueue,
83
+ this.getQueue,
91
84
  this.deleteQueue,
92
- this.deleteAllQueues,
93
- this.clearStorage,
85
+ this.purgeQueue,
94
86
  this.getQueueSize,
87
+ this.clearStorage,
95
88
  this.getJobById
96
89
  ]
97
90
 
@@ -99,27 +92,31 @@ class Manager extends EventEmitter {
99
92
  }
100
93
 
101
94
  start () {
102
- this.stopping = false
95
+ this.stopped = false
103
96
  }
104
97
 
105
98
  async stop () {
106
- this.stopping = true
99
+ this.stopped = true
107
100
 
108
- for (const sub of this.workers.values()) {
109
- if (!INTERNAL_QUEUES[sub.name]) {
110
- await this.offWork(sub.name)
101
+ for (const worker of this.workers.values()) {
102
+ if (!INTERNAL_QUEUES[worker.name]) {
103
+ await this.offWork(worker.name)
111
104
  }
112
105
  }
113
106
  }
114
107
 
115
- async work (name, ...args) {
116
- const { options, callback } = Attorney.checkWorkArgs(name, args, this.config)
117
- return await this.watch(name, options, callback)
108
+ async failWip () {
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
+ }
118
115
  }
119
116
 
120
- async onComplete (name, ...args) {
117
+ async work (name, ...args) {
121
118
  const { options, callback } = Attorney.checkWorkArgs(name, args, this.config)
122
- return await this.watch(COMPLETION_JOB_PREFIX + name, options, callback)
119
+ return await this.watch(name, options, callback)
123
120
  }
124
121
 
125
122
  addWorker (worker) {
@@ -175,8 +172,8 @@ class Manager extends EventEmitter {
175
172
  }
176
173
 
177
174
  async watch (name, options, callback) {
178
- if (this.stopping) {
179
- throw new Error('Workers are disabled. pg-boss is stopping.')
175
+ if (this.stopped) {
176
+ throw new Error('Workers are disabled. pg-boss is stopped')
180
177
  }
181
178
 
182
179
  const {
@@ -186,10 +183,10 @@ class Manager extends EventEmitter {
186
183
  teamConcurrency = 1,
187
184
  teamRefill: refill = false,
188
185
  includeMetadata = false,
189
- enforceSingletonQueueActiveLimit = false
186
+ priority = true
190
187
  } = options
191
188
 
192
- const id = uuid.v4()
189
+ const id = randomUUID({ disableEntropyCache: true })
193
190
 
194
191
  let queueSize = 0
195
192
 
@@ -210,7 +207,7 @@ class Manager extends EventEmitter {
210
207
  createTeamRefillPromise()
211
208
  }
212
209
 
213
- const fetch = () => this.fetch(name, batchSize || (teamSize - queueSize), { includeMetadata, enforceSingletonQueueActiveLimit })
210
+ const fetch = () => this.fetch(name, batchSize || (teamSize - queueSize), { includeMetadata, priority })
214
211
 
215
212
  const onFetch = async (jobs) => {
216
213
  if (this.config.__test__throw_worker) {
@@ -223,8 +220,8 @@ class Manager extends EventEmitter {
223
220
  const maxExpiration = jobs.reduce((acc, i) => Math.max(acc, i.expire_in_seconds), 0)
224
221
 
225
222
  await resolveWithinSeconds(Promise.all([callback(jobs)]), maxExpiration)
226
- .then(() => this.complete(jobs.map(job => job.id)))
227
- .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))
228
225
  } else {
229
226
  if (refill) {
230
227
  queueSize += jobs.length || 1
@@ -232,8 +229,8 @@ class Manager extends EventEmitter {
232
229
 
233
230
  const allTeamPromise = pMap(jobs, job =>
234
231
  resolveWithinSeconds(callback(job), job.expire_in_seconds)
235
- .then(result => this.complete(job.id, result))
236
- .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))
237
234
  .then(() => refill ? onRefill() : null)
238
235
  , { concurrency: teamConcurrency }
239
236
  ).catch(() => {}) // allow promises & non-promises to live together in harmony
@@ -329,39 +326,11 @@ class Manager extends EventEmitter {
329
326
  return await Promise.all(result.rows.map(({ name }) => this.send(name, ...args)))
330
327
  }
331
328
 
332
- async offComplete (value) {
333
- if (typeof value === 'string') {
334
- value = COMPLETION_JOB_PREFIX + value
335
- }
336
-
337
- return await this.offWork(value)
338
- }
339
-
340
329
  async send (...args) {
341
330
  const { name, data, options } = Attorney.checkSendArgs(args, this.config)
342
331
  return await this.createJob(name, data, options)
343
332
  }
344
333
 
345
- async sendOnce (name, data, options, key) {
346
- options = options ? { ...options } : {}
347
-
348
- options.singletonKey = key || name
349
-
350
- const result = Attorney.checkSendArgs([name, data, options], this.config)
351
-
352
- return await this.createJob(result.name, result.data, result.options)
353
- }
354
-
355
- async sendSingleton (name, data, options) {
356
- options = options ? { ...options } : {}
357
-
358
- options.singletonKey = SINGLETON_QUEUE_KEY
359
-
360
- const result = Attorney.checkSendArgs([name, data, options], this.config)
361
-
362
- return await this.createJob(result.name, result.data, result.options)
363
- }
364
-
365
334
  async sendAfter (name, data, options, after) {
366
335
  options = options ? { ...options } : {}
367
336
  options.startAfter = after
@@ -395,37 +364,47 @@ class Manager extends EventEmitter {
395
364
 
396
365
  async createJob (name, data, options, singletonOffset = 0) {
397
366
  const {
367
+ id = null,
398
368
  db: wrapper,
399
- expireIn,
400
369
  priority,
401
370
  startAfter,
402
- keepUntil,
403
371
  singletonKey = null,
404
372
  singletonSeconds,
405
- retryBackoff,
373
+ deadLetter = null,
374
+ expireIn,
375
+ expireInDefault,
376
+ keepUntil,
377
+ keepUntilDefault,
406
378
  retryLimit,
379
+ retryLimitDefault,
407
380
  retryDelay,
408
- onComplete
381
+ retryDelayDefault,
382
+ retryBackoff,
383
+ retryBackoffDefault
409
384
  } = options
410
385
 
411
- const id = uuid[this.config.uuid]()
412
-
413
386
  const values = [
414
387
  id, // 1
415
388
  name, // 2
416
- priority, // 3
417
- retryLimit, // 4
389
+ data, // 3
390
+ priority, // 4
418
391
  startAfter, // 5
419
- expireIn, // 6
420
- data, // 7
421
- singletonKey, // 8
422
- singletonSeconds, // 9
423
- singletonOffset, // 10
424
- retryDelay, // 11
425
- retryBackoff, // 12
426
- keepUntil, // 13
427
- onComplete // 14
392
+ singletonKey, // 6
393
+ singletonSeconds, // 7
394
+ singletonOffset, // 8
395
+ deadLetter, // 9
396
+ expireIn, // 10
397
+ expireInDefault, // 11
398
+ keepUntil, // 12
399
+ keepUntilDefault, // 13
400
+ retryLimit, // 14
401
+ retryLimitDefault, // 15
402
+ retryDelay, // 16
403
+ retryDelayDefault, // 17
404
+ retryBackoff, // 18
405
+ retryBackoffDefault // 19
428
406
  ]
407
+
429
408
  const db = wrapper || this.db
430
409
  const result = await db.executeSql(this.insertJobCommand, values)
431
410
 
@@ -449,11 +428,20 @@ class Manager extends EventEmitter {
449
428
  }
450
429
 
451
430
  async insert (jobs, options = {}) {
452
- const { db: wrapper } = options
453
- const db = wrapper || this.db
454
- const checkedJobs = Attorney.checkInsertArgs(jobs)
455
- const data = JSON.stringify(checkedJobs)
456
- return await db.executeSql(this.insertJobsCommand, [data])
431
+ assert(Array.isArray(jobs), 'jobs argument should be an array')
432
+
433
+ const db = options.db || this.db
434
+
435
+ const params = [
436
+ JSON.stringify(jobs), // 1
437
+ this.config.expireIn, // 2
438
+ this.config.keepUntil, // 3
439
+ this.config.retryLimit, // 4
440
+ this.config.retryDelay, // 5
441
+ this.config.retryBackoff // 6
442
+ ]
443
+
444
+ return await db.executeSql(this.insertJobsCommand, params)
457
445
  }
458
446
 
459
447
  getDebounceStartAfter (singletonSeconds, clockOffset) {
@@ -476,25 +464,15 @@ class Manager extends EventEmitter {
476
464
  async fetch (name, batchSize, options = {}) {
477
465
  const values = Attorney.checkFetchArgs(name, batchSize, options)
478
466
  const db = options.db || this.db
479
- const preparedStatement = this.nextJobCommand(options.includeMetadata || false, options.enforceSingletonQueueActiveLimit || false)
467
+ const nextJobSql = this.nextJobCommand({ ...options })
480
468
  const statementValues = [values.name, batchSize || 1]
481
469
 
482
470
  let result
483
- if (options.enforceSingletonQueueActiveLimit && !options.db) {
484
- // Prepare/format now and send multi-statement transaction
485
- const fetchQuery = preparedStatement
486
- .replace('$1', Db.quotePostgresStr(statementValues[0]))
487
- .replace('$2', statementValues[1].toString())
488
- // eslint-disable-next-line no-unused-vars
489
- const [_begin, _setLocal, fetchResult, _commit] = await db.executeSql([
490
- 'BEGIN',
491
- 'SET LOCAL jit = OFF', // JIT can slow things down significantly
492
- fetchQuery,
493
- 'COMMIT'
494
- ].join(';\n'))
495
- result = fetchResult
496
- } else {
497
- result = await db.executeSql(preparedStatement, statementValues)
471
+
472
+ try {
473
+ result = await db.executeSql(nextJobSql, statementValues)
474
+ } catch (err) {
475
+ // errors from fetchquery should only be unique constraint violations
498
476
  }
499
477
 
500
478
  if (!result || result.rows.length === 0) {
@@ -504,10 +482,6 @@ class Manager extends EventEmitter {
504
482
  return result.rows.length === 1 && !batchSize ? result.rows[0] : result.rows
505
483
  }
506
484
 
507
- async fetchCompleted (name, batchSize, options = {}) {
508
- return await this.fetch(COMPLETION_JOB_PREFIX + name, batchSize, options)
509
- }
510
-
511
485
  mapCompletionIdArg (id, funcName) {
512
486
  const errorMessage = `${funcName}() requires an id`
513
487
 
@@ -538,45 +512,154 @@ class Manager extends EventEmitter {
538
512
  }
539
513
  }
540
514
 
541
- async complete (id, data, options = {}) {
515
+ async complete (name, id, data, options = {}) {
516
+ assert(name, 'Missing queue name argument')
542
517
  const db = options.db || this.db
543
518
  const ids = this.mapCompletionIdArg(id, 'complete')
544
- const result = await db.executeSql(this.completeJobsCommand, [ids, this.mapCompletionDataArg(data)])
519
+ const result = await db.executeSql(this.completeJobsCommand, [name, ids, this.mapCompletionDataArg(data)])
545
520
  return this.mapCompletionResponse(ids, result)
546
521
  }
547
522
 
548
- async fail (id, data, options = {}) {
523
+ async fail (name, id, data, options = {}) {
524
+ assert(name, 'Missing queue name argument')
549
525
  const db = options.db || this.db
550
526
  const ids = this.mapCompletionIdArg(id, 'fail')
551
- const result = await db.executeSql(this.failJobsCommand, [ids, this.mapCompletionDataArg(data)])
527
+ const result = await db.executeSql(this.failJobsByIdCommand, [name, ids, this.mapCompletionDataArg(data)])
552
528
  return this.mapCompletionResponse(ids, result)
553
529
  }
554
530
 
555
- async cancel (id, options = {}) {
531
+ async cancel (name, id, options = {}) {
532
+ assert(name, 'Missing queue name argument')
556
533
  const db = options.db || this.db
557
534
  const ids = this.mapCompletionIdArg(id, 'cancel')
558
- const result = await db.executeSql(this.cancelJobsCommand, [ids])
535
+ const result = await db.executeSql(this.cancelJobsCommand, [name, ids])
559
536
  return this.mapCompletionResponse(ids, result)
560
537
  }
561
538
 
562
- async resume (id, options = {}) {
539
+ async resume (name, id, options = {}) {
540
+ assert(name, 'Missing queue name argument')
563
541
  const db = options.db || this.db
564
542
  const ids = this.mapCompletionIdArg(id, 'resume')
565
- const result = await db.executeSql(this.resumeJobsCommand, [ids])
543
+ const result = await db.executeSql(this.resumeJobsCommand, [name, ids])
566
544
  return this.mapCompletionResponse(ids, result)
567
545
  }
568
546
 
569
- async deleteQueue (queue, options) {
570
- assert(queue, 'Missing queue name argument')
571
- const sql = plans.deleteQueue(this.config.schema, options)
572
- const result = await this.db.executeSql(sql, [queue])
573
- return result ? result.rowCount : null
547
+ async createQueue (name, options = {}) {
548
+ assert(name, 'Missing queue name argument')
549
+
550
+ const { policy = QUEUE_POLICY.standard } = options
551
+
552
+ assert(policy in QUEUE_POLICY, `${policy} is not a valid queue policy`)
553
+
554
+ const {
555
+ retryLimit,
556
+ retryDelay,
557
+ retryBackoff,
558
+ expireInSeconds,
559
+ retentionMinutes,
560
+ deadLetter
561
+ } = Attorney.checkQueueArgs(name, options)
562
+
563
+ const paritionSql = plans.partitionCreateJobName(this.config.schema, name)
564
+
565
+ await this.db.executeSql(paritionSql)
566
+
567
+ const sql = plans.createQueue(this.config.schema, name)
568
+
569
+ const params = [
570
+ name,
571
+ policy,
572
+ retryLimit,
573
+ retryDelay,
574
+ retryBackoff,
575
+ expireInSeconds,
576
+ retentionMinutes,
577
+ deadLetter
578
+ ]
579
+
580
+ await this.db.executeSql(sql, params)
574
581
  }
575
582
 
576
- async deleteAllQueues (options) {
577
- const sql = plans.deleteAllQueues(this.config.schema, options)
578
- const result = await this.db.executeSql(sql)
579
- return result ? result.rowCount : null
583
+ async updateQueue (name, options = {}) {
584
+ assert(name, 'Missing queue name argument')
585
+
586
+ const {
587
+ retryLimit,
588
+ retryDelay,
589
+ retryBackoff,
590
+ expireInSeconds,
591
+ retentionMinutes,
592
+ deadLetter
593
+ } = Attorney.checkQueueArgs(name, options)
594
+
595
+ const sql = plans.updateQueue(this.config.schema)
596
+
597
+ const params = [
598
+ name,
599
+ retryLimit,
600
+ retryDelay,
601
+ retryBackoff,
602
+ expireInSeconds,
603
+ retentionMinutes,
604
+ deadLetter
605
+ ]
606
+
607
+ await this.db.executeSql(sql, params)
608
+ }
609
+
610
+ async getQueue (name) {
611
+ assert(name, 'Missing queue name argument')
612
+
613
+ const sql = plans.getQueueByName(this.config.schema)
614
+ const result = await this.db.executeSql(sql, [name])
615
+
616
+ if (result.rows.length === 0) {
617
+ return null
618
+ }
619
+
620
+ const {
621
+ policy,
622
+ retry_limit: retryLimit,
623
+ retry_delay: retryDelay,
624
+ retry_backoff: retryBackoff,
625
+ expire_seconds: expireInSeconds,
626
+ retention_minutes: retentionMinutes,
627
+ dead_letter: deadLetter
628
+ } = result.rows[0]
629
+
630
+ return {
631
+ name,
632
+ policy,
633
+ retryLimit,
634
+ retryDelay,
635
+ retryBackoff,
636
+ expireInSeconds,
637
+ retentionMinutes,
638
+ deadLetter
639
+ }
640
+ }
641
+
642
+ async deleteQueue (name) {
643
+ assert(name, 'Missing queue name argument')
644
+
645
+ const queueSql = plans.getQueueByName(this.config.schema)
646
+ const result = await this.db.executeSql(queueSql, [name])
647
+
648
+ if (result?.rows?.length) {
649
+ Attorney.assertPostgresObjectName(name)
650
+ const sql = plans.dropJobTablePartition(this.config.schema, name)
651
+ await this.db.executeSql(sql)
652
+ }
653
+
654
+ const sql = plans.deleteQueueRecords(this.config.schema)
655
+ const result2 = await this.db.executeSql(sql, [name])
656
+ return result2?.rowCount || null
657
+ }
658
+
659
+ async purgeQueue (queue) {
660
+ assert(queue, 'Missing queue name argument')
661
+ const sql = plans.purgeQueue(this.config.schema)
662
+ await this.db.executeSql(sql, [queue])
580
663
  }
581
664
 
582
665
  async clearStorage () {
@@ -594,15 +677,15 @@ class Manager extends EventEmitter {
594
677
  return result ? parseFloat(result.rows[0].count) : null
595
678
  }
596
679
 
597
- async getJobById (id, options = {}) {
680
+ async getJobById (queue, id, options = {}) {
598
681
  const db = options.db || this.db
599
- const result1 = await db.executeSql(this.getJobByIdCommand, [id])
682
+ const result1 = await db.executeSql(this.getJobByIdCommand, [queue, id])
600
683
 
601
684
  if (result1 && result1.rows && result1.rows.length === 1) {
602
685
  return result1.rows[0]
603
686
  }
604
687
 
605
- const result2 = await db.executeSql(this.getArchivedJobByIdCommand, [id])
688
+ const result2 = await db.executeSql(this.getArchivedJobByIdCommand, [queue, id])
606
689
 
607
690
  if (result2 && result2.rows && result2.rows.length === 1) {
608
691
  return result2.rows[0]
@@ -64,110 +64,5 @@ function migrate (value, version, migrations) {
64
64
 
65
65
  function getAll (schema) {
66
66
  return [
67
- {
68
- release: '7.4.0',
69
- version: 20,
70
- previous: 19,
71
- install: [
72
- `DROP INDEX ${schema}.job_singletonKey`,
73
- `DROP INDEX ${schema}.job_singleton_queue`,
74
- `CREATE UNIQUE INDEX job_singletonKey ON ${schema}.job (name, singletonKey) WHERE state < 'completed' AND singletonOn IS NULL AND NOT singletonKey LIKE '\\_\\_pgboss\\_\\_singleton\\_queue%'`,
75
- `CREATE UNIQUE INDEX job_singleton_queue ON ${schema}.job (name, singletonKey) WHERE state < 'active' AND singletonOn IS NULL AND singletonKey LIKE '\\_\\_pgboss\\_\\_singleton\\_queue%'`
76
- ],
77
- uninstall: [
78
- `DROP INDEX ${schema}.job_singletonKey`,
79
- `DROP INDEX ${schema}.job_singleton_queue`,
80
- `CREATE UNIQUE INDEX job_singletonKey ON ${schema}.job (name, singletonKey) WHERE state < 'completed' AND singletonOn IS NULL AND NOT singletonKey = '__pgboss__singleton_queue'`,
81
- `CREATE UNIQUE INDEX job_singleton_queue ON ${schema}.job (name, singletonKey) WHERE state < 'active' AND singletonOn IS NULL AND singletonKey = '__pgboss__singleton_queue'`
82
- ]
83
- },
84
- {
85
- release: '7.0.0',
86
- version: 19,
87
- previous: 18,
88
- install: [
89
- `CREATE TABLE ${schema}.subscription (
90
- event text not null,
91
- name text not null,
92
- created_on timestamp with time zone not null default now(),
93
- updated_on timestamp with time zone not null default now(),
94
- PRIMARY KEY(event, name)
95
- )`
96
- ],
97
- uninstall: [
98
- `DROP TABLE ${schema}.subscription`
99
- ]
100
- },
101
- {
102
- release: '6.1.1',
103
- version: 18,
104
- previous: 17,
105
- install: [
106
- `ALTER TABLE ${schema}.job ALTER COLUMN on_complete SET DEFAULT false`
107
- ]
108
- },
109
- {
110
- release: '6.0.0',
111
- version: 17,
112
- previous: 16,
113
- install: [
114
- `DROP INDEX ${schema}.job_singletonKey`,
115
- `CREATE UNIQUE INDEX job_singletonKey ON ${schema}.job (name, singletonKey) WHERE state < 'completed' AND singletonOn IS NULL AND NOT singletonKey = '__pgboss__singleton_queue'`,
116
- `CREATE UNIQUE INDEX job_singleton_queue ON ${schema}.job (name, singletonKey) WHERE state < 'active' AND singletonOn IS NULL AND singletonKey = '__pgboss__singleton_queue'`,
117
- `CREATE INDEX IF NOT EXISTS job_fetch ON ${schema}.job (name text_pattern_ops, startAfter) WHERE state < 'active'`,
118
- `ALTER TABLE ${schema}.job ADD output jsonb`,
119
- `ALTER TABLE ${schema}.archive ADD output jsonb`,
120
- `ALTER TABLE ${schema}.job ALTER COLUMN on_complete SET DEFAULT false`,
121
- `ALTER TABLE ${schema}.job ALTER COLUMN keepuntil SET DEFAULT now() + interval '14 days'`
122
- ],
123
- uninstall: [
124
- `DROP INDEX ${schema}.job_fetch`,
125
- `DROP INDEX ${schema}.job_singleton_queue`,
126
- `DROP INDEX ${schema}.job_singletonKey`,
127
- `CREATE UNIQUE INDEX job_singletonKey ON ${schema}.job (name, singletonKey) WHERE state < 'completed' AND singletonOn IS NULL`,
128
- `ALTER TABLE ${schema}.job DROP COLUMN output`,
129
- `ALTER TABLE ${schema}.archive DROP COLUMN output`,
130
- `ALTER TABLE ${schema}.job ALTER COLUMN on_complete SET DEFAULT true`,
131
- `ALTER TABLE ${schema}.job ALTER COLUMN keepuntil SET DEFAULT now() + interval '30 days'`
132
- ]
133
- },
134
- {
135
- release: '5.2.0',
136
- version: 16,
137
- previous: 15,
138
- install: [
139
- `ALTER TABLE ${schema}.job ADD on_complete boolean`,
140
- `UPDATE ${schema}.job SET on_complete = true`,
141
- `ALTER TABLE ${schema}.job ALTER COLUMN on_complete SET DEFAULT true`,
142
- `ALTER TABLE ${schema}.job ALTER COLUMN on_complete SET NOT NULL`,
143
- `ALTER TABLE ${schema}.archive ADD on_complete boolean`
144
- ],
145
- uninstall: [
146
- `ALTER TABLE ${schema}.job DROP COLUMN on_complete`,
147
- `ALTER TABLE ${schema}.archive DROP COLUMN on_complete`
148
- ]
149
- },
150
- {
151
- release: '5.0.6',
152
- version: 15,
153
- previous: 14,
154
- install: [
155
- `ALTER TABLE ${schema}.version ADD cron_on timestamp with time zone`
156
- ],
157
- uninstall: [
158
- `ALTER TABLE ${schema}.version DROP COLUMN cron_on`
159
- ]
160
- },
161
- {
162
- release: '5.0.0',
163
- version: 14,
164
- previous: 13,
165
- install: [
166
- `ALTER TABLE ${schema}.version ADD maintained_on timestamp with time zone`
167
- ],
168
- uninstall: [
169
- `ALTER TABLE ${schema}.version DROP COLUMN maintained_on`
170
- ]
171
- }
172
67
  ]
173
68
  }