pg-boss 9.0.3 → 10.0.0-beta10

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/boss.js CHANGED
@@ -1,12 +1,6 @@
1
1
  const EventEmitter = require('events')
2
2
  const plans = require('./plans')
3
- const { states } = require('./plans')
4
- const { COMPLETION_JOB_PREFIX } = plans
5
-
6
- const queues = {
7
- MAINTENANCE: '__pgboss__maintenance',
8
- MONITOR_STATES: '__pgboss__monitor-states'
9
- }
3
+ const { delay } = require('./tools')
10
4
 
11
5
  const events = {
12
6
  error: 'error',
@@ -23,177 +17,131 @@ class Boss extends EventEmitter {
23
17
  this.manager = config.manager
24
18
 
25
19
  this.maintenanceIntervalSeconds = config.maintenanceIntervalSeconds
26
-
27
- this.monitorStates = config.monitorStateIntervalSeconds !== null
28
-
29
- if (this.monitorStates) {
30
- this.monitorIntervalSeconds = config.monitorStateIntervalSeconds
31
- }
20
+ this.monitorStateIntervalSeconds = config.monitorStateIntervalSeconds
32
21
 
33
22
  this.events = events
34
23
 
35
- this.expireCommand = plans.locked(config.schema, plans.expire(config.schema))
24
+ this.failJobsByTimeoutCommand = plans.locked(config.schema, plans.failJobsByTimeout(config.schema))
36
25
  this.archiveCommand = plans.locked(config.schema, plans.archive(config.schema, config.archiveInterval, config.archiveFailedInterval))
37
- this.purgeCommand = plans.locked(config.schema, plans.purge(config.schema, config.deleteAfter))
38
- this.getMaintenanceTimeCommand = plans.getMaintenanceTime(config.schema)
39
- this.setMaintenanceTimeCommand = plans.setMaintenanceTime(config.schema)
26
+ this.dropCommand = plans.locked(config.schema, plans.drop(config.schema, config.deleteAfter))
27
+ this.trySetMaintenanceTimeCommand = plans.trySetMaintenanceTime(config.schema)
28
+ this.trySetMonitorTimeCommand = plans.trySetMonitorTime(config.schema)
40
29
  this.countStatesCommand = plans.countStates(config.schema)
41
30
 
42
31
  this.functions = [
43
32
  this.expire,
44
33
  this.archive,
45
- this.purge,
34
+ this.drop,
46
35
  this.countStates,
47
- this.getQueueNames
36
+ this.maintain
48
37
  ]
49
38
  }
50
39
 
51
40
  async supervise () {
52
- this.metaMonitor()
53
-
54
- await this.manager.deleteQueue(COMPLETION_JOB_PREFIX + queues.MAINTENANCE)
55
- await this.manager.deleteQueue(queues.MAINTENANCE)
56
-
57
- await this.maintenanceAsync()
58
-
59
- const maintenanceWorkOptions = {
60
- newJobCheckIntervalSeconds: Math.max(1, this.maintenanceIntervalSeconds / 2)
61
- }
41
+ this.maintenanceInterval = setInterval(() => this.onSupervise(), this.maintenanceIntervalSeconds * 1000)
42
+ }
62
43
 
63
- await this.manager.work(queues.MAINTENANCE, maintenanceWorkOptions, (job) => this.onMaintenance(job))
44
+ async monitor () {
45
+ this.monitorInterval = setInterval(() => this.onMonitor(), this.monitorStateIntervalSeconds * 1000)
46
+ }
64
47
 
65
- if (this.monitorStates) {
66
- await this.manager.deleteQueue(COMPLETION_JOB_PREFIX + queues.MONITOR_STATES)
67
- await this.manager.deleteQueue(queues.MONITOR_STATES)
48
+ async onMonitor () {
49
+ try {
50
+ if (this.monitoring) {
51
+ return
52
+ }
68
53
 
69
- await this.monitorStatesAsync()
54
+ this.monitoring = true
70
55
 
71
- const monitorStatesWorkOptions = {
72
- newJobCheckIntervalSeconds: Math.max(1, this.monitorIntervalSeconds / 2)
56
+ if (this.config.__test__delay_monitor) {
57
+ await delay(this.config.__test__delay_monitor)
73
58
  }
74
59
 
75
- await this.manager.work(queues.MONITOR_STATES, monitorStatesWorkOptions, (job) => this.onMonitorStates(job))
76
- }
77
- }
60
+ if (this.config.__test__throw_monitor) {
61
+ throw new Error(this.config.__test__throw_monitor)
62
+ }
78
63
 
79
- metaMonitor () {
80
- this.metaMonitorInterval = setInterval(async () => {
81
- try {
82
- if (this.config.__test__throw_meta_monitor) {
83
- throw new Error(this.config.__test__throw_meta_monitor)
84
- }
85
-
86
- const { secondsAgo } = await this.getMaintenanceTime()
87
-
88
- if (secondsAgo > this.maintenanceIntervalSeconds * 2) {
89
- await this.manager.deleteQueue(queues.MAINTENANCE, { before: states.completed })
90
- await this.maintenanceAsync()
91
- }
92
- } catch (err) {
93
- this.emit(events.error, err)
64
+ if (this.stopped) {
65
+ return
94
66
  }
95
- }, this.maintenanceIntervalSeconds * 2 * 1000)
96
- }
97
67
 
98
- async maintenanceAsync (options = {}) {
99
- const { startAfter } = options
68
+ const { rows } = await this.db.executeSql(this.trySetMonitorTimeCommand, [this.config.monitorStateIntervalSeconds])
100
69
 
101
- options = {
102
- startAfter,
103
- retentionSeconds: this.maintenanceIntervalSeconds * 4,
104
- singletonKey: queues.MAINTENANCE,
105
- onComplete: false
70
+ if (rows.length === 1 && !this.stopped) {
71
+ const states = await this.countStates()
72
+ this.emit(events.monitorStates, states)
73
+ }
74
+ } catch (err) {
75
+ this.emit(events.error, err)
76
+ } finally {
77
+ this.monitoring = false
106
78
  }
107
-
108
- await this.manager.send(queues.MAINTENANCE, null, options)
109
79
  }
110
80
 
111
- async monitorStatesAsync (options = {}) {
112
- const { startAfter } = options
81
+ async onSupervise () {
82
+ try {
83
+ if (this.maintaining) {
84
+ return
85
+ }
113
86
 
114
- options = {
115
- startAfter,
116
- retentionSeconds: this.monitorIntervalSeconds * 4,
117
- singletonKey: queues.MONITOR_STATES,
118
- onComplete: false
119
- }
87
+ this.maintaining = true
120
88
 
121
- await this.manager.send(queues.MONITOR_STATES, null, options)
122
- }
89
+ if (this.config.__test__delay_maintenance && !this.stopped) {
90
+ this.__testDelayPromise = delay(this.config.__test__delay_maintenance)
91
+ await this.__testDelayPromise
92
+ }
123
93
 
124
- async onMaintenance (job) {
125
- try {
126
94
  if (this.config.__test__throw_maint) {
127
95
  throw new Error(this.config.__test__throw_maint)
128
96
  }
129
97
 
130
- const started = Date.now()
131
-
132
- await this.expire()
133
- await this.archive()
134
- await this.purge()
135
-
136
- const ended = Date.now()
137
-
138
- await this.setMaintenanceTime()
98
+ if (this.stopped) {
99
+ return
100
+ }
139
101
 
140
- this.emit('maintenance', { ms: ended - started })
102
+ const { rows } = await this.db.executeSql(this.trySetMaintenanceTimeCommand, [this.config.maintenanceIntervalSeconds])
141
103
 
142
- if (!this.stopped) {
143
- await this.manager.complete(job.id) // pre-complete to bypass throttling
144
- await this.maintenanceAsync({ startAfter: this.maintenanceIntervalSeconds })
104
+ if (rows.length === 1 && !this.stopped) {
105
+ const result = await this.maintain()
106
+ this.emit(events.maintenance, result)
145
107
  }
146
108
  } catch (err) {
147
109
  this.emit(events.error, err)
110
+ } finally {
111
+ this.maintaining = false
148
112
  }
149
113
  }
150
114
 
151
- async onMonitorStates (job) {
152
- try {
153
- if (this.config.__test__throw_monitor) {
154
- throw new Error(this.config.__test__throw_monitor)
155
- }
115
+ async maintain () {
116
+ const started = Date.now()
156
117
 
157
- const states = await this.countStates()
118
+ !this.stopped && await this.expire()
119
+ !this.stopped && await this.archive()
120
+ !this.stopped && await this.drop()
158
121
 
159
- this.emit(events.monitorStates, states)
122
+ const ended = Date.now()
160
123
 
161
- if (!this.stopped && this.monitorStates) {
162
- await this.manager.complete(job.id) // pre-complete to bypass throttling
163
- await this.monitorStatesAsync({ startAfter: this.monitorIntervalSeconds })
164
- }
165
- } catch (err) {
166
- this.emit(events.error, err)
167
- }
124
+ return { ms: ended - started }
168
125
  }
169
126
 
170
127
  async stop () {
171
- if (this.config.__test__throw_stop) {
172
- throw new Error(this.config.__test__throw_stop)
173
- }
174
-
175
128
  if (!this.stopped) {
176
- if (this.metaMonitorInterval) {
177
- clearInterval(this.metaMonitorInterval)
178
- }
179
-
180
- await this.manager.offWork(queues.MAINTENANCE)
181
-
182
- if (this.monitorStates) {
183
- await this.manager.offWork(queues.MONITOR_STATES)
184
- }
129
+ if (this.__testDelayPromise) this.__testDelayPromise.abort()
130
+ if (this.maintenanceInterval) clearInterval(this.maintenanceInterval)
131
+ if (this.monitorInterval) clearInterval(this.monitorInterval)
185
132
 
186
133
  this.stopped = true
187
134
  }
188
135
  }
189
136
 
190
137
  async countStates () {
191
- const stateCountDefault = { ...plans.states }
138
+ const stateCountDefault = { ...plans.JOB_STATES }
192
139
 
193
- Object.keys(stateCountDefault)
194
- .forEach(key => { stateCountDefault[key] = 0 })
140
+ for (const key of Object.keys(stateCountDefault)) {
141
+ stateCountDefault[key] = 0
142
+ }
195
143
 
196
- const counts = await this.executeSql(this.countStatesCommand)
144
+ const counts = await this.db.executeSql(this.countStatesCommand)
197
145
 
198
146
  const states = counts.rows.reduce((acc, item) => {
199
147
  if (item.name) {
@@ -213,43 +161,44 @@ class Boss extends EventEmitter {
213
161
  }
214
162
 
215
163
  async expire () {
216
- await this.executeSql(this.expireCommand)
164
+ await this.db.executeSql(this.failJobsByTimeoutCommand)
217
165
  }
218
166
 
219
167
  async archive () {
220
- await this.executeSql(this.archiveCommand)
168
+ await this.db.executeSql(this.archiveCommand)
221
169
  }
222
170
 
223
- async purge () {
224
- await this.executeSql(this.purgeCommand)
171
+ async drop () {
172
+ await this.db.executeSql(this.dropCommand)
225
173
  }
226
174
 
227
175
  async setMaintenanceTime () {
228
- await this.executeSql(this.setMaintenanceTimeCommand)
176
+ await this.db.executeSql(this.setMaintenanceTimeCommand)
229
177
  }
230
178
 
231
179
  async getMaintenanceTime () {
232
- if (!this.stopped) {
233
- const { rows } = await this.db.executeSql(this.getMaintenanceTimeCommand)
180
+ const { rows } = await this.db.executeSql(this.getMaintenanceTimeCommand)
234
181
 
235
- let { maintained_on: maintainedOn, seconds_ago: secondsAgo } = rows[0]
182
+ let { maintained_on: maintainedOn, seconds_ago: secondsAgo } = rows[0]
236
183
 
237
- secondsAgo = secondsAgo !== null ? parseFloat(secondsAgo) : this.maintenanceIntervalSeconds * 10
184
+ secondsAgo = secondsAgo !== null ? parseFloat(secondsAgo) : 999_999_999
238
185
 
239
- return { maintainedOn, secondsAgo }
240
- }
186
+ return { maintainedOn, secondsAgo }
241
187
  }
242
188
 
243
- getQueueNames () {
244
- return queues
189
+ async setMonitorTime () {
190
+ await this.db.executeSql(this.setMonitorTimeCommand)
245
191
  }
246
192
 
247
- async executeSql (sql, params) {
248
- if (!this.stopped) {
249
- return await this.db.executeSql(sql, params)
250
- }
193
+ async getMonitorTime () {
194
+ const { rows } = await this.db.executeSql(this.getMonitorTimeCommand)
195
+
196
+ let { monitored_on: monitoredOn, seconds_ago: secondsAgo } = rows[0]
197
+
198
+ secondsAgo = secondsAgo !== null ? parseFloat(secondsAgo) : 999_999_999
199
+
200
+ return { monitoredOn, secondsAgo }
251
201
  }
252
202
  }
253
203
 
254
204
  module.exports = Boss
255
- module.exports.QUEUES = queues
package/src/contractor.js CHANGED
@@ -21,32 +21,53 @@ 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.schemaVersion,
28
+ this.isInstalled
29
+ ]
24
30
  }
25
31
 
26
- async version () {
32
+ async schemaVersion () {
27
33
  const result = await this.db.executeSql(plans.getVersion(this.config.schema))
28
34
  return result.rows.length ? parseInt(result.rows[0].version) : null
29
35
  }
30
36
 
31
37
  async isInstalled () {
32
38
  const result = await this.db.executeSql(plans.versionTableExists(this.config.schema))
33
- return result.rows.length ? result.rows[0].name : null
39
+ return !!result.rows[0].name
34
40
  }
35
41
 
36
42
  async start () {
37
43
  const installed = await this.isInstalled()
38
44
 
39
45
  if (installed) {
40
- const version = await this.version()
46
+ const version = await this.schemaVersion()
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()
47
54
  }
48
55
  }
49
56
 
57
+ async check () {
58
+ const installed = await this.isInstalled()
59
+
60
+ if (!installed) {
61
+ throw new Error('pg-boss is not installed')
62
+ }
63
+
64
+ const version = await this.schemaVersion()
65
+
66
+ if (schemaVersion !== version) {
67
+ throw new Error('pg-boss database requires migrations')
68
+ }
69
+ }
70
+
50
71
  async create () {
51
72
  try {
52
73
  const commands = plans.create(this.config.schema, schemaVersion)
package/src/db.js CHANGED
@@ -10,6 +10,10 @@ class Db extends EventEmitter {
10
10
  this.config = config
11
11
  }
12
12
 
13
+ events = {
14
+ error: 'error'
15
+ }
16
+
13
17
  async open () {
14
18
  this.pool = new pg.Pool(this.config)
15
19
  this.pool.on('error', error => this.emit('error', error))
@@ -25,16 +29,18 @@ class Db extends EventEmitter {
25
29
 
26
30
  async executeSql (text, values) {
27
31
  if (this.opened) {
28
- return await this.pool.query(text, values)
29
- }
30
- }
32
+ if (this.config.debug === true) {
33
+ console.log(`${new Date().toISOString()}: DEBUG SQL`)
34
+ console.log(text)
35
+
36
+ if (values) {
37
+ console.log(`${new Date().toISOString()}: DEBUG VALUES`)
38
+ console.log(values)
39
+ }
40
+ }
31
41
 
32
- static quotePostgresStr (str) {
33
- const delimeter = '$sanitize$'
34
- if (str.includes(delimeter)) {
35
- throw new Error(`Attempted to quote string that contains reserved Postgres delimeter: ${str}`)
42
+ return await this.pool.query(text, values)
36
43
  }
37
- return `${delimeter}${str}${delimeter}`
38
44
  }
39
45
  }
40
46