pg-boss 10.0.0-beta8 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/package.json +1 -3
- package/src/attorney.js +16 -22
- package/src/boss.js +0 -28
- package/src/contractor.js +9 -9
- package/src/db.js +9 -9
- package/src/index.js +2 -2
- package/src/manager.js +100 -152
- package/src/plans.js +224 -161
- package/src/timekeeper.js +25 -34
- package/types.d.ts +24 -46
package/src/manager.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
const assert = require('assert')
|
|
2
2
|
const EventEmitter = require('events')
|
|
3
3
|
const { randomUUID } = require('crypto')
|
|
4
|
-
const debounce = require('lodash.debounce')
|
|
5
4
|
const { serializeError: stringify } = require('serialize-error')
|
|
6
|
-
const pMap = require('p-map')
|
|
7
5
|
const { delay } = require('./tools')
|
|
8
6
|
const Attorney = require('./attorney')
|
|
9
7
|
const Worker = require('./worker')
|
|
@@ -14,9 +12,6 @@ const { QUEUE_POLICIES } = plans
|
|
|
14
12
|
|
|
15
13
|
const INTERNAL_QUEUES = Object.values(TIMEKEEPER_QUEUES).reduce((acc, i) => ({ ...acc, [i]: i }), {})
|
|
16
14
|
|
|
17
|
-
const WIP_EVENT_INTERVAL = 2000
|
|
18
|
-
const WIP_DEBOUNCE_OPTIONS = { leading: true, trailing: true, maxWait: WIP_EVENT_INTERVAL }
|
|
19
|
-
|
|
20
15
|
const events = {
|
|
21
16
|
error: 'error',
|
|
22
17
|
wip: 'wip'
|
|
@@ -45,6 +40,7 @@ class Manager extends EventEmitter {
|
|
|
45
40
|
this.db = db
|
|
46
41
|
|
|
47
42
|
this.events = events
|
|
43
|
+
this.wipTs = Date.now()
|
|
48
44
|
this.workers = new Map()
|
|
49
45
|
|
|
50
46
|
this.nextJobCommand = plans.fetchNextJob(config.schema)
|
|
@@ -53,18 +49,27 @@ class Manager extends EventEmitter {
|
|
|
53
49
|
this.completeJobsCommand = plans.completeJobs(config.schema)
|
|
54
50
|
this.cancelJobsCommand = plans.cancelJobs(config.schema)
|
|
55
51
|
this.resumeJobsCommand = plans.resumeJobs(config.schema)
|
|
52
|
+
this.deleteJobsCommand = plans.deleteJobs(config.schema)
|
|
56
53
|
this.failJobsByIdCommand = plans.failJobsById(config.schema)
|
|
57
54
|
this.getJobByIdCommand = plans.getJobById(config.schema)
|
|
58
55
|
this.getArchivedJobByIdCommand = plans.getArchivedJobById(config.schema)
|
|
59
56
|
this.subscribeCommand = plans.subscribe(config.schema)
|
|
60
57
|
this.unsubscribeCommand = plans.unsubscribe(config.schema)
|
|
58
|
+
this.getQueuesCommand = plans.getQueues(config.schema)
|
|
59
|
+
this.getQueueByNameCommand = plans.getQueueByName(config.schema)
|
|
61
60
|
this.getQueuesForEventCommand = plans.getQueuesForEvent(config.schema)
|
|
61
|
+
this.createQueueCommand = plans.createQueue(config.schema)
|
|
62
|
+
this.updateQueueCommand = plans.updateQueue(config.schema)
|
|
63
|
+
this.purgeQueueCommand = plans.purgeQueue(config.schema)
|
|
64
|
+
this.deleteQueueCommand = plans.deleteQueue(config.schema)
|
|
65
|
+
this.clearStorageCommand = plans.clearStorage(config.schema)
|
|
62
66
|
|
|
63
67
|
// exported api to index
|
|
64
68
|
this.functions = [
|
|
65
69
|
this.complete,
|
|
66
70
|
this.cancel,
|
|
67
71
|
this.resume,
|
|
72
|
+
this.deleteJob,
|
|
68
73
|
this.fail,
|
|
69
74
|
this.fetch,
|
|
70
75
|
this.work,
|
|
@@ -80,15 +85,14 @@ class Manager extends EventEmitter {
|
|
|
80
85
|
this.sendAfter,
|
|
81
86
|
this.createQueue,
|
|
82
87
|
this.updateQueue,
|
|
83
|
-
this.getQueue,
|
|
84
88
|
this.deleteQueue,
|
|
85
89
|
this.purgeQueue,
|
|
86
90
|
this.getQueueSize,
|
|
91
|
+
this.getQueue,
|
|
92
|
+
this.getQueues,
|
|
87
93
|
this.clearStorage,
|
|
88
94
|
this.getJobById
|
|
89
95
|
]
|
|
90
|
-
|
|
91
|
-
this.emitWipThrottled = debounce(() => this.emit(events.wip, this.getWipData()), WIP_EVENT_INTERVAL, WIP_DEBOUNCE_OPTIONS)
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
start () {
|
|
@@ -133,7 +137,12 @@ class Manager extends EventEmitter {
|
|
|
133
137
|
|
|
134
138
|
emitWip (name) {
|
|
135
139
|
if (!INTERNAL_QUEUES[name]) {
|
|
136
|
-
|
|
140
|
+
const now = Date.now()
|
|
141
|
+
|
|
142
|
+
if (now - this.wipTs > 2000) {
|
|
143
|
+
this.emit(events.wip, this.getWipData())
|
|
144
|
+
this.wipTs = now
|
|
145
|
+
}
|
|
137
146
|
}
|
|
138
147
|
}
|
|
139
148
|
|
|
@@ -177,73 +186,35 @@ class Manager extends EventEmitter {
|
|
|
177
186
|
}
|
|
178
187
|
|
|
179
188
|
const {
|
|
180
|
-
|
|
189
|
+
pollingInterval: interval = this.config.pollingInterval,
|
|
181
190
|
batchSize,
|
|
182
|
-
teamSize = 1,
|
|
183
|
-
teamConcurrency = 1,
|
|
184
|
-
teamRefill: refill = false,
|
|
185
191
|
includeMetadata = false,
|
|
186
192
|
priority = true
|
|
187
193
|
} = options
|
|
188
194
|
|
|
189
195
|
const id = randomUUID({ disableEntropyCache: true })
|
|
190
196
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
let refillTeamPromise
|
|
194
|
-
let resolveRefillTeam
|
|
195
|
-
|
|
196
|
-
// Setup a promise that onFetch can await for when at least one
|
|
197
|
-
// job is finished and so the team is ready to be topped up
|
|
198
|
-
const createTeamRefillPromise = () => {
|
|
199
|
-
refillTeamPromise = new Promise((resolve) => { resolveRefillTeam = resolve })
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
createTeamRefillPromise()
|
|
203
|
-
|
|
204
|
-
const onRefill = () => {
|
|
205
|
-
queueSize--
|
|
206
|
-
resolveRefillTeam()
|
|
207
|
-
createTeamRefillPromise()
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const fetch = () => this.fetch(name, batchSize || (teamSize - queueSize), { includeMetadata, priority })
|
|
197
|
+
const fetch = () => this.fetch(name, { batchSize, includeMetadata, priority })
|
|
211
198
|
|
|
212
199
|
const onFetch = async (jobs) => {
|
|
200
|
+
if (!jobs.length) {
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
213
204
|
if (this.config.__test__throw_worker) {
|
|
214
205
|
throw new Error('__test__throw_worker')
|
|
215
206
|
}
|
|
216
207
|
|
|
217
208
|
this.emitWip(name)
|
|
218
209
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
queueSize += jobs.length || 1
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const allTeamPromise = pMap(jobs, job =>
|
|
231
|
-
resolveWithinSeconds(callback(job), job.expireInSeconds)
|
|
232
|
-
.then(result => this.complete(name, job.id, result))
|
|
233
|
-
.catch(err => this.fail(name, job.id, err))
|
|
234
|
-
.then(() => refill ? onRefill() : null)
|
|
235
|
-
, { concurrency: teamConcurrency }
|
|
236
|
-
).catch(() => {}) // allow promises & non-promises to live together in harmony
|
|
237
|
-
|
|
238
|
-
if (refill) {
|
|
239
|
-
if (queueSize < teamSize) {
|
|
240
|
-
return
|
|
241
|
-
} else {
|
|
242
|
-
await refillTeamPromise
|
|
243
|
-
}
|
|
244
|
-
} else {
|
|
245
|
-
await allTeamPromise
|
|
246
|
-
}
|
|
210
|
+
const maxExpiration = jobs.reduce((acc, i) => Math.max(acc, i.expireInSeconds), 0)
|
|
211
|
+
const jobIds = jobs.map(job => job.id)
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const result = await resolveWithinSeconds(callback(jobs), maxExpiration)
|
|
215
|
+
this.complete(name, jobIds, jobIds.length === 1 ? result : undefined)
|
|
216
|
+
} catch (err) {
|
|
217
|
+
this.fail(name, jobIds, err)
|
|
247
218
|
}
|
|
248
219
|
|
|
249
220
|
this.emitWip(name)
|
|
@@ -319,7 +290,7 @@ class Manager extends EventEmitter {
|
|
|
319
290
|
|
|
320
291
|
const { rows } = await this.db.executeSql(this.getQueuesForEventCommand, [event])
|
|
321
292
|
|
|
322
|
-
|
|
293
|
+
await Promise.allSettled(rows.map(({ name }) => this.send(name, ...args)))
|
|
323
294
|
}
|
|
324
295
|
|
|
325
296
|
async send (...args) {
|
|
@@ -457,25 +428,20 @@ class Manager extends EventEmitter {
|
|
|
457
428
|
return startAfter
|
|
458
429
|
}
|
|
459
430
|
|
|
460
|
-
async fetch (name,
|
|
461
|
-
|
|
431
|
+
async fetch (name, options = {}) {
|
|
432
|
+
Attorney.checkFetchArgs(name, options)
|
|
462
433
|
const db = options.db || this.db
|
|
463
434
|
const nextJobSql = this.nextJobCommand({ ...options })
|
|
464
|
-
const statementValues = [values.name, batchSize || 1]
|
|
465
435
|
|
|
466
436
|
let result
|
|
467
437
|
|
|
468
438
|
try {
|
|
469
|
-
result = await db.executeSql(nextJobSql,
|
|
439
|
+
result = await db.executeSql(nextJobSql, [name, options.batchSize])
|
|
470
440
|
} catch (err) {
|
|
471
441
|
// errors from fetchquery should only be unique constraint violations
|
|
472
442
|
}
|
|
473
443
|
|
|
474
|
-
|
|
475
|
-
return null
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return result.rows.length === 1 && !batchSize ? result.rows[0] : result.rows
|
|
444
|
+
return result?.rows || []
|
|
479
445
|
}
|
|
480
446
|
|
|
481
447
|
mapCompletionIdArg (id, funcName) {
|
|
@@ -500,48 +466,56 @@ class Manager extends EventEmitter {
|
|
|
500
466
|
return stringify(result)
|
|
501
467
|
}
|
|
502
468
|
|
|
503
|
-
|
|
469
|
+
mapCommandResponse (ids, result) {
|
|
504
470
|
return {
|
|
505
471
|
jobs: ids,
|
|
506
472
|
requested: ids.length,
|
|
507
|
-
|
|
473
|
+
affected: result && result.rows ? parseInt(result.rows[0].count) : 0
|
|
508
474
|
}
|
|
509
475
|
}
|
|
510
476
|
|
|
511
477
|
async complete (name, id, data, options = {}) {
|
|
512
|
-
|
|
478
|
+
Attorney.assertQueueName(name)
|
|
513
479
|
const db = options.db || this.db
|
|
514
480
|
const ids = this.mapCompletionIdArg(id, 'complete')
|
|
515
481
|
const result = await db.executeSql(this.completeJobsCommand, [name, ids, this.mapCompletionDataArg(data)])
|
|
516
|
-
return this.
|
|
482
|
+
return this.mapCommandResponse(ids, result)
|
|
517
483
|
}
|
|
518
484
|
|
|
519
485
|
async fail (name, id, data, options = {}) {
|
|
520
|
-
|
|
486
|
+
Attorney.assertQueueName(name)
|
|
521
487
|
const db = options.db || this.db
|
|
522
488
|
const ids = this.mapCompletionIdArg(id, 'fail')
|
|
523
489
|
const result = await db.executeSql(this.failJobsByIdCommand, [name, ids, this.mapCompletionDataArg(data)])
|
|
524
|
-
return this.
|
|
490
|
+
return this.mapCommandResponse(ids, result)
|
|
525
491
|
}
|
|
526
492
|
|
|
527
493
|
async cancel (name, id, options = {}) {
|
|
528
|
-
|
|
494
|
+
Attorney.assertQueueName(name)
|
|
529
495
|
const db = options.db || this.db
|
|
530
496
|
const ids = this.mapCompletionIdArg(id, 'cancel')
|
|
531
497
|
const result = await db.executeSql(this.cancelJobsCommand, [name, ids])
|
|
532
|
-
return this.
|
|
498
|
+
return this.mapCommandResponse(ids, result)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async deleteJob (name, id, options = {}) {
|
|
502
|
+
Attorney.assertQueueName(name)
|
|
503
|
+
const db = options.db || this.db
|
|
504
|
+
const ids = this.mapCompletionIdArg(id, 'deleteJob')
|
|
505
|
+
const result = await db.executeSql(this.deleteJobsCommand, [name, ids])
|
|
506
|
+
return this.mapCommandResponse(ids, result)
|
|
533
507
|
}
|
|
534
508
|
|
|
535
509
|
async resume (name, id, options = {}) {
|
|
536
|
-
|
|
510
|
+
Attorney.assertQueueName(name)
|
|
537
511
|
const db = options.db || this.db
|
|
538
512
|
const ids = this.mapCompletionIdArg(id, 'resume')
|
|
539
513
|
const result = await db.executeSql(this.resumeJobsCommand, [name, ids])
|
|
540
|
-
return this.
|
|
514
|
+
return this.mapCommandResponse(ids, result)
|
|
541
515
|
}
|
|
542
516
|
|
|
543
517
|
async createQueue (name, options = {}) {
|
|
544
|
-
|
|
518
|
+
name = name || options.name
|
|
545
519
|
|
|
546
520
|
Attorney.assertQueueName(name)
|
|
547
521
|
|
|
@@ -558,14 +532,12 @@ class Manager extends EventEmitter {
|
|
|
558
532
|
deadLetter
|
|
559
533
|
} = Attorney.checkQueueArgs(name, options)
|
|
560
534
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
const sql = plans.createQueue(this.config.schema, name)
|
|
535
|
+
if (deadLetter) {
|
|
536
|
+
Attorney.assertQueueName(deadLetter)
|
|
537
|
+
}
|
|
566
538
|
|
|
567
|
-
|
|
568
|
-
|
|
539
|
+
// todo: pull in defaults from constructor config
|
|
540
|
+
const data = {
|
|
569
541
|
policy,
|
|
570
542
|
retryLimit,
|
|
571
543
|
retryDelay,
|
|
@@ -573,13 +545,22 @@ class Manager extends EventEmitter {
|
|
|
573
545
|
expireInSeconds,
|
|
574
546
|
retentionMinutes,
|
|
575
547
|
deadLetter
|
|
576
|
-
|
|
548
|
+
}
|
|
577
549
|
|
|
578
|
-
await this.db.executeSql(
|
|
550
|
+
await this.db.executeSql(this.createQueueCommand, [name, data])
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async getQueues () {
|
|
554
|
+
const { rows } = await this.db.executeSql(this.getQueuesCommand)
|
|
555
|
+
return rows
|
|
579
556
|
}
|
|
580
557
|
|
|
581
558
|
async updateQueue (name, options = {}) {
|
|
582
|
-
|
|
559
|
+
Attorney.assertQueueName(name)
|
|
560
|
+
|
|
561
|
+
const { policy = QUEUE_POLICIES.standard } = options
|
|
562
|
+
|
|
563
|
+
assert(policy in QUEUE_POLICIES, `${policy} is not a valid queue policy`)
|
|
583
564
|
|
|
584
565
|
const {
|
|
585
566
|
retryLimit,
|
|
@@ -590,10 +571,9 @@ class Manager extends EventEmitter {
|
|
|
590
571
|
deadLetter
|
|
591
572
|
} = Attorney.checkQueueArgs(name, options)
|
|
592
573
|
|
|
593
|
-
const sql = plans.updateQueue(this.config.schema)
|
|
594
|
-
|
|
595
574
|
const params = [
|
|
596
575
|
name,
|
|
576
|
+
policy,
|
|
597
577
|
retryLimit,
|
|
598
578
|
retryDelay,
|
|
599
579
|
retryBackoff,
|
|
@@ -602,93 +582,61 @@ class Manager extends EventEmitter {
|
|
|
602
582
|
deadLetter
|
|
603
583
|
]
|
|
604
584
|
|
|
605
|
-
await this.db.executeSql(
|
|
585
|
+
await this.db.executeSql(this.updateQueueCommand, params)
|
|
606
586
|
}
|
|
607
587
|
|
|
608
588
|
async getQueue (name) {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
const sql = plans.getQueueByName(this.config.schema)
|
|
612
|
-
const result = await this.db.executeSql(sql, [name])
|
|
613
|
-
|
|
614
|
-
if (result.rows.length === 0) {
|
|
615
|
-
return null
|
|
616
|
-
}
|
|
589
|
+
Attorney.assertQueueName(name)
|
|
617
590
|
|
|
618
|
-
const {
|
|
619
|
-
policy,
|
|
620
|
-
retry_limit: retryLimit,
|
|
621
|
-
retry_delay: retryDelay,
|
|
622
|
-
retry_backoff: retryBackoff,
|
|
623
|
-
expire_seconds: expireInSeconds,
|
|
624
|
-
retention_minutes: retentionMinutes,
|
|
625
|
-
dead_letter: deadLetter
|
|
626
|
-
} = result.rows[0]
|
|
591
|
+
const { rows } = await this.db.executeSql(this.getQueueByNameCommand, [name])
|
|
627
592
|
|
|
628
|
-
return
|
|
629
|
-
name,
|
|
630
|
-
policy,
|
|
631
|
-
retryLimit,
|
|
632
|
-
retryDelay,
|
|
633
|
-
retryBackoff,
|
|
634
|
-
expireInSeconds,
|
|
635
|
-
retentionMinutes,
|
|
636
|
-
deadLetter
|
|
637
|
-
}
|
|
593
|
+
return rows[0] || null
|
|
638
594
|
}
|
|
639
595
|
|
|
640
596
|
async deleteQueue (name) {
|
|
641
|
-
|
|
597
|
+
Attorney.assertQueueName(name)
|
|
642
598
|
|
|
643
|
-
const
|
|
644
|
-
const { rows } = await this.db.executeSql(queueSql, [name])
|
|
599
|
+
const { rows } = await this.db.executeSql(this.getQueueByNameCommand, [name])
|
|
645
600
|
|
|
646
|
-
if (rows.length) {
|
|
647
|
-
|
|
648
|
-
const sql = plans.dropPartition(this.config.schema, name)
|
|
649
|
-
await this.db.executeSql(sql)
|
|
601
|
+
if (rows.length === 1) {
|
|
602
|
+
await this.db.executeSql(this.deleteQueueCommand, [name])
|
|
650
603
|
}
|
|
651
|
-
|
|
652
|
-
const sql = plans.deleteQueueRecords(this.config.schema)
|
|
653
|
-
await this.db.executeSql(sql, [name])
|
|
654
604
|
}
|
|
655
605
|
|
|
656
|
-
async purgeQueue (
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
await this.db.executeSql(sql, [queue])
|
|
606
|
+
async purgeQueue (name) {
|
|
607
|
+
Attorney.assertQueueName(name)
|
|
608
|
+
await this.db.executeSql(this.purgeQueueCommand, [name])
|
|
660
609
|
}
|
|
661
610
|
|
|
662
611
|
async clearStorage () {
|
|
663
|
-
|
|
664
|
-
await this.db.executeSql(sql)
|
|
612
|
+
await this.db.executeSql(this.clearStorageCommand)
|
|
665
613
|
}
|
|
666
614
|
|
|
667
|
-
async getQueueSize (
|
|
668
|
-
|
|
615
|
+
async getQueueSize (name, options) {
|
|
616
|
+
Attorney.assertQueueName(name)
|
|
669
617
|
|
|
670
618
|
const sql = plans.getQueueSize(this.config.schema, options)
|
|
671
619
|
|
|
672
|
-
const result = await this.db.executeSql(sql, [
|
|
620
|
+
const result = await this.db.executeSql(sql, [name])
|
|
673
621
|
|
|
674
622
|
return result ? parseFloat(result.rows[0].count) : null
|
|
675
623
|
}
|
|
676
624
|
|
|
677
|
-
async getJobById (
|
|
678
|
-
|
|
679
|
-
const result1 = await db.executeSql(this.getJobByIdCommand, [queue, id])
|
|
625
|
+
async getJobById (name, id, options = {}) {
|
|
626
|
+
Attorney.assertQueueName(name)
|
|
680
627
|
|
|
681
|
-
|
|
682
|
-
return result1.rows[0]
|
|
683
|
-
}
|
|
628
|
+
const db = options.db || this.db
|
|
684
629
|
|
|
685
|
-
const
|
|
630
|
+
const result1 = await db.executeSql(this.getJobByIdCommand, [name, id])
|
|
686
631
|
|
|
687
|
-
if (
|
|
688
|
-
return
|
|
632
|
+
if (result1?.rows?.length === 1) {
|
|
633
|
+
return result1.rows[0]
|
|
634
|
+
} else if (options.includeArchive) {
|
|
635
|
+
const result2 = await db.executeSql(this.getArchivedJobByIdCommand, [name, id])
|
|
636
|
+
return result2?.rows[0] || null
|
|
637
|
+
} else {
|
|
638
|
+
return null
|
|
689
639
|
}
|
|
690
|
-
|
|
691
|
-
return null
|
|
692
640
|
}
|
|
693
641
|
}
|
|
694
642
|
|