pg-boss 10.0.0-beta6 → 10.0.0-beta7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pg-boss",
3
- "version": "10.0.0-beta6",
3
+ "version": "10.0.0-beta7",
4
4
  "description": "Queueing jobs in Postgres from Node.js like a boss",
5
5
  "main": "./src/index.js",
6
6
  "engines": {
package/src/boss.js CHANGED
@@ -24,10 +24,8 @@ class Boss extends EventEmitter {
24
24
  this.failJobsByTimeoutCommand = plans.locked(config.schema, plans.failJobsByTimeout(config.schema))
25
25
  this.archiveCommand = plans.locked(config.schema, plans.archive(config.schema, config.archiveInterval, config.archiveFailedInterval))
26
26
  this.dropCommand = plans.locked(config.schema, plans.drop(config.schema, config.deleteAfter))
27
- this.getMaintenanceTimeCommand = plans.getMaintenanceTime(config.schema)
28
- this.setMaintenanceTimeCommand = plans.setMaintenanceTime(config.schema)
29
- this.getMonitorTimeCommand = plans.getMonitorTime(config.schema)
30
- this.setMonitorTimeCommand = plans.setMonitorTime(config.schema)
27
+ this.trySetMaintenanceTimeCommand = plans.trySetMaintenanceTime(config.schema)
28
+ this.trySetMonitorTimeCommand = plans.trySetMonitorTime(config.schema)
31
29
  this.countStatesCommand = plans.countStates(config.schema)
32
30
 
33
31
  this.functions = [
@@ -48,8 +46,6 @@ class Boss extends EventEmitter {
48
46
  }
49
47
 
50
48
  async onMonitor () {
51
- let locker
52
-
53
49
  try {
54
50
  if (this.monitoring) {
55
51
  return
@@ -65,26 +61,24 @@ class Boss extends EventEmitter {
65
61
  throw new Error(this.config.__test__throw_monitor)
66
62
  }
67
63
 
68
- locker = await this.db.lock({ key: 'monitor' })
64
+ if (this.stopped) {
65
+ return
66
+ }
69
67
 
70
- const { secondsAgo } = await this.getMonitorTime()
68
+ const { rows } = await this.db.executeSql(this.trySetMonitorTimeCommand, [this.config.monitorStateIntervalSeconds])
71
69
 
72
- if (secondsAgo > this.monitorStateIntervalSeconds && !this.stopped) {
70
+ if (rows.length === 1 && !this.stopped) {
73
71
  const states = await this.countStates()
74
- this.setMonitorTime()
75
72
  this.emit(events.monitorStates, states)
76
73
  }
77
74
  } catch (err) {
78
75
  this.emit(events.error, err)
79
76
  } finally {
80
- await locker?.unlock()
81
77
  this.monitoring = false
82
78
  }
83
79
  }
84
80
 
85
81
  async onSupervise () {
86
- let locker
87
-
88
82
  try {
89
83
  if (this.maintaining) {
90
84
  return
@@ -105,18 +99,15 @@ class Boss extends EventEmitter {
105
99
  return
106
100
  }
107
101
 
108
- locker = await this.db.lock({ key: 'maintenance' })
109
-
110
- const { secondsAgo } = await this.getMaintenanceTime()
102
+ const { rows } = await this.db.executeSql(this.trySetMaintenanceTimeCommand, [this.config.maintenanceIntervalSeconds])
111
103
 
112
- if (secondsAgo > this.maintenanceIntervalSeconds) {
104
+ if (rows.length === 1 && !this.stopped) {
113
105
  const result = await this.maintain()
114
106
  this.emit(events.maintenance, result)
115
107
  }
116
108
  } catch (err) {
117
109
  this.emit(events.error, err)
118
110
  } finally {
119
- await locker?.unlock()
120
111
  this.maintaining = false
121
112
  }
122
113
  }
@@ -130,8 +121,6 @@ class Boss extends EventEmitter {
130
121
 
131
122
  const ended = Date.now()
132
123
 
133
- await this.setMaintenanceTime()
134
-
135
124
  return { ms: ended - started }
136
125
  }
137
126
 
package/src/contractor.js CHANGED
@@ -24,26 +24,26 @@ class Contractor {
24
24
 
25
25
  // exported api to index
26
26
  this.functions = [
27
- this.version,
27
+ this.schemaVersion,
28
28
  this.isInstalled
29
29
  ]
30
30
  }
31
31
 
32
- async version () {
32
+ async schemaVersion () {
33
33
  const result = await this.db.executeSql(plans.getVersion(this.config.schema))
34
34
  return result.rows.length ? parseInt(result.rows[0].version) : null
35
35
  }
36
36
 
37
37
  async isInstalled () {
38
38
  const result = await this.db.executeSql(plans.versionTableExists(this.config.schema))
39
- return result.rows.length ? result.rows[0].name : null
39
+ return !!result.rows[0].name
40
40
  }
41
41
 
42
42
  async start () {
43
43
  const installed = await this.isInstalled()
44
44
 
45
45
  if (installed) {
46
- const version = await this.version()
46
+ const version = await this.schemaVersion()
47
47
 
48
48
  if (schemaVersion > version) {
49
49
  throw new Error('Migrations are not supported to v10')
@@ -61,7 +61,7 @@ class Contractor {
61
61
  throw new Error('pg-boss is not installed')
62
62
  }
63
63
 
64
- const version = await this.version()
64
+ const version = await this.schemaVersion()
65
65
 
66
66
  if (schemaVersion !== version) {
67
67
  throw new Error('pg-boss database requires migrations')
package/src/db.js CHANGED
@@ -1,6 +1,5 @@
1
1
  const EventEmitter = require('events')
2
2
  const pg = require('pg')
3
- const { advisoryLock } = require('./plans')
4
3
 
5
4
  class Db extends EventEmitter {
6
5
  constructor (config) {
@@ -43,31 +42,6 @@ class Db extends EventEmitter {
43
42
  return await this.pool.query(text, values)
44
43
  }
45
44
  }
46
-
47
- async lock ({ timeout = 30, key } = {}) {
48
- const lockedClient = await this.pool.connect()
49
-
50
- const query = `
51
- BEGIN;
52
- SET LOCAL lock_timeout = '${timeout}s';
53
- SET LOCAL idle_in_transaction_session_timeout = '${timeout}s';
54
- ${advisoryLock(this.config.schema, key)};
55
- `
56
-
57
- await lockedClient.query(query)
58
-
59
- const locker = {
60
- unlock: async function () {
61
- try {
62
- await lockedClient.query('COMMIT')
63
- } finally {
64
- lockedClient.release()
65
- }
66
- }
67
- }
68
-
69
- return locker
70
- }
71
45
  }
72
46
 
73
47
  module.exports = Db
package/src/manager.js CHANGED
@@ -317,13 +317,9 @@ class Manager extends EventEmitter {
317
317
  async publish (event, ...args) {
318
318
  assert(event, 'Missing required argument')
319
319
 
320
- const result = await this.db.executeSql(this.getQueuesForEventCommand, [event])
320
+ const { rows } = await this.db.executeSql(this.getQueuesForEventCommand, [event])
321
321
 
322
- if (!result || result.rowCount === 0) {
323
- return []
324
- }
325
-
326
- return await Promise.all(result.rows.map(({ name }) => this.send(name, ...args)))
322
+ return await Promise.all(rows.map(({ name }) => this.send(name, ...args)))
327
323
  }
328
324
 
329
325
  async send (...args) {
@@ -406,10 +402,10 @@ class Manager extends EventEmitter {
406
402
  ]
407
403
 
408
404
  const db = wrapper || this.db
409
- const result = await db.executeSql(this.insertJobCommand, values)
405
+ const { rows } = await db.executeSql(this.insertJobCommand, values)
410
406
 
411
- if (result && result.rowCount === 1) {
412
- return result.rows[0].id
407
+ if (rows.length === 1) {
408
+ return rows[0].id
413
409
  }
414
410
 
415
411
  if (!options.singletonNextSlot) {
@@ -645,17 +641,16 @@ class Manager extends EventEmitter {
645
641
  assert(name, 'Missing queue name argument')
646
642
 
647
643
  const queueSql = plans.getQueueByName(this.config.schema)
648
- const result = await this.db.executeSql(queueSql, [name])
644
+ const { rows } = await this.db.executeSql(queueSql, [name])
649
645
 
650
- if (result?.rows?.length) {
646
+ if (rows.length) {
651
647
  Attorney.assertQueueName(name)
652
648
  const sql = plans.dropPartition(this.config.schema, name)
653
649
  await this.db.executeSql(sql)
654
650
  }
655
651
 
656
652
  const sql = plans.deleteQueueRecords(this.config.schema)
657
- const result2 = await this.db.executeSql(sql, [name])
658
- return result2?.rowCount || null
653
+ await this.db.executeSql(sql, [name])
659
654
  }
660
655
 
661
656
  async purgeQueue (queue) {
package/src/plans.js CHANGED
@@ -53,14 +53,10 @@ module.exports = {
53
53
  getQueueSize,
54
54
  purgeQueue,
55
55
  clearStorage,
56
- getMaintenanceTime,
57
- setMaintenanceTime,
58
- getMonitorTime,
59
- setMonitorTime,
60
- getCronTime,
61
- setCronTime,
56
+ trySetMaintenanceTime,
57
+ trySetMonitorTime,
58
+ trySetCronTime,
62
59
  locked,
63
- advisoryLock,
64
60
  assertMigration,
65
61
  getArchivedJobById,
66
62
  getJobById,
@@ -77,7 +73,6 @@ function create (schema, version) {
77
73
  createEnumJobState(schema),
78
74
 
79
75
  createTableJob(schema),
80
- createIndexJobName(schema),
81
76
  createIndexJobFetch(schema),
82
77
  createIndexJobPolicyStately(schema),
83
78
  createIndexJobPolicyShort(schema),
@@ -89,7 +84,6 @@ function create (schema, version) {
89
84
  createPrimaryKeyArchive(schema),
90
85
  createColumnArchiveArchivedOn(schema),
91
86
  createIndexArchiveArchivedOn(schema),
92
- createIndexArchiveName(schema),
93
87
 
94
88
  createTableVersion(schema),
95
89
  createTableQueue(schema),
@@ -257,10 +251,6 @@ function createIndexJobThrottleKey (schema) {
257
251
  return `CREATE UNIQUE INDEX job_throttle_key ON ${schema}.job (name, singleton_key) WHERE state <= '${states.completed}' AND singleton_on IS NULL`
258
252
  }
259
253
 
260
- function createIndexJobName (schema) {
261
- return `CREATE INDEX job_name ON ${schema}.job (name)`
262
- }
263
-
264
254
  function createIndexJobFetch (schema) {
265
255
  return `CREATE INDEX job_fetch ON ${schema}.job (name, start_after) INCLUDE (priority, created_on, id) WHERE state < '${states.active}'`
266
256
  }
@@ -277,33 +267,24 @@ function createIndexArchiveArchivedOn (schema) {
277
267
  return `CREATE INDEX archive_archived_on_idx ON ${schema}.archive(archived_on)`
278
268
  }
279
269
 
280
- function createIndexArchiveName (schema) {
281
- return `CREATE INDEX archive_name_idx ON ${schema}.archive(name)`
282
- }
283
-
284
- function getMaintenanceTime (schema) {
285
- return `SELECT maintained_on, EXTRACT( EPOCH FROM (now() - maintained_on) ) seconds_ago FROM ${schema}.version`
286
- }
287
-
288
- function setMaintenanceTime (schema) {
289
- return `UPDATE ${schema}.version SET maintained_on = now()`
290
- }
291
-
292
- function getMonitorTime (schema) {
293
- return `SELECT monitored_on, EXTRACT( EPOCH FROM (now() - monitored_on) ) seconds_ago FROM ${schema}.version`
270
+ function trySetMaintenanceTime (schema) {
271
+ return trySetTimestamp(schema, 'maintained_on')
294
272
  }
295
273
 
296
- function setMonitorTime (schema) {
297
- return `UPDATE ${schema}.version SET monitored_on = now()`
274
+ function trySetMonitorTime (schema) {
275
+ return trySetTimestamp(schema, 'monitored_on')
298
276
  }
299
277
 
300
- function setCronTime (schema, time) {
301
- time = time || 'now()'
302
- return `UPDATE ${schema}.version SET cron_on = ${time}`
278
+ function trySetCronTime (schema) {
279
+ return trySetTimestamp(schema, 'cron_on')
303
280
  }
304
281
 
305
- function getCronTime (schema) {
306
- return `SELECT cron_on, EXTRACT( EPOCH FROM (now() - cron_on) ) seconds_ago FROM ${schema}.version`
282
+ function trySetTimestamp (schema, column) {
283
+ return `
284
+ UPDATE ${schema}.version SET ${column} = now()
285
+ WHERE EXTRACT( EPOCH FROM (now() - COALESCE(${column}, now() - interval '1 week') ) ) > $1
286
+ RETURNING true
287
+ `
307
288
  }
308
289
 
309
290
  function createQueue (schema) {
@@ -777,6 +758,7 @@ function locked (schema, query) {
777
758
  return `
778
759
  BEGIN;
779
760
  SET LOCAL lock_timeout = '30s';
761
+ SET LOCAL idle_in_transaction_session_timeout = '30s';
780
762
  ${advisoryLock(schema)};
781
763
  ${query};
782
764
  COMMIT;
package/src/timekeeper.js CHANGED
@@ -31,8 +31,7 @@ class Timekeeper extends EventEmitter {
31
31
  this.getSchedulesCommand = plans.getSchedules(config.schema)
32
32
  this.scheduleCommand = plans.schedule(config.schema)
33
33
  this.unscheduleCommand = plans.unschedule(config.schema)
34
- this.getCronTimeCommand = plans.getCronTime(config.schema)
35
- this.setCronTimeCommand = plans.setCronTime(config.schema)
34
+ this.trySetCronTimeCommand = plans.trySetCronTime(config.schema)
36
35
 
37
36
  this.functions = [
38
37
  this.schedule,
@@ -44,27 +43,30 @@ class Timekeeper extends EventEmitter {
44
43
  }
45
44
 
46
45
  async start () {
47
- this.stopped = false
48
-
49
46
  // setting the archive config too low breaks the cron 60s debounce interval so don't even try
50
47
  if (this.config.archiveSeconds < 60 || this.config.archiveFailedSeconds < 60) {
51
48
  return
52
49
  }
53
50
 
54
- // cache the clock skew from the db server
51
+ this.stopped = false
52
+
55
53
  await this.cacheClockSkew()
56
54
 
57
55
  try {
58
56
  await this.manager.createQueue(queues.SEND_IT)
59
57
  } catch {}
60
58
 
61
- await this.manager.work(queues.SEND_IT, { newJobCheckIntervalSeconds: this.config.cronWorkerIntervalSeconds, teamSize: 50, teamConcurrency: 5 }, (job) => this.onSendIt(job))
59
+ const options = {
60
+ newJobCheckIntervalSeconds: this.config.cronWorkerIntervalSeconds,
61
+ teamSize: 50,
62
+ teamConcurrency: 5
63
+ }
64
+
65
+ await this.manager.work(queues.SEND_IT, options, (job) => this.onSendIt(job))
62
66
 
63
67
  setImmediate(() => this.onCron())
64
68
 
65
- // create monitoring interval to make sure cron hasn't crashed
66
69
  this.cronMonitorInterval = setInterval(async () => await this.onCron(), this.cronMonitorIntervalMs)
67
- // create monitoring interval to measure and adjust for drift in clock skew
68
70
  this.skewMonitorInterval = setInterval(async () => await this.cacheClockSkew(), this.skewMonitorIntervalMs)
69
71
  }
70
72
 
@@ -117,8 +119,6 @@ class Timekeeper extends EventEmitter {
117
119
  }
118
120
 
119
121
  async onCron () {
120
- let locker
121
-
122
122
  try {
123
123
  if (this.stopped || this.timekeeping) return
124
124
 
@@ -128,19 +128,15 @@ class Timekeeper extends EventEmitter {
128
128
 
129
129
  this.timekeeping = true
130
130
 
131
- locker = await this.db.lock({ key: 'timekeeper' })
132
-
133
- const { secondsAgo } = await this.getCronTime()
131
+ const { rows } = await this.db.executeSql(this.trySetCronTimeCommand, [this.config.cronMonitorIntervalSeconds])
134
132
 
135
- if (secondsAgo > this.config.cronMonitorIntervalSeconds) {
133
+ if (rows.length === 1 && !this.stopped) {
136
134
  await this.cron()
137
- await this.setCronTime()
138
135
  }
139
136
  } catch (err) {
140
137
  this.emit(this.events.error, err)
141
138
  } finally {
142
139
  this.timekeeping = false
143
- await locker?.unlock()
144
140
  }
145
141
  }
146
142
 
@@ -198,28 +194,11 @@ class Timekeeper extends EventEmitter {
198
194
 
199
195
  const values = [name, cron, tz, data, options]
200
196
 
201
- const result = await this.db.executeSql(this.scheduleCommand, values)
202
-
203
- return result ? result.rowCount : null
197
+ await this.db.executeSql(this.scheduleCommand, values)
204
198
  }
205
199
 
206
200
  async unschedule (name) {
207
- const result = await this.db.executeSql(this.unscheduleCommand, [name])
208
- return result ? result.rowCount : null
209
- }
210
-
211
- async setCronTime () {
212
- await this.db.executeSql(this.setCronTimeCommand)
213
- }
214
-
215
- async getCronTime () {
216
- const { rows } = await this.db.executeSql(this.getCronTimeCommand)
217
-
218
- let { cron_on: cronOn, seconds_ago: secondsAgo } = rows[0]
219
-
220
- secondsAgo = secondsAgo !== null ? parseFloat(secondsAgo) : 61
221
-
222
- return { cronOn, secondsAgo }
201
+ await this.db.executeSql(this.unscheduleCommand, [name])
223
202
  }
224
203
  }
225
204
 
package/types.d.ts CHANGED
@@ -2,7 +2,7 @@ import { EventEmitter } from 'events'
2
2
 
3
3
  declare namespace PgBoss {
4
4
  interface Db {
5
- executeSql(text: string, values: any[]): Promise<{ rows: any[]; rowCount: number }>;
5
+ executeSql(text: string, values: any[]): Promise<{ rows: any[] }>;
6
6
  }
7
7
 
8
8
  interface DatabaseOptions {
@@ -91,10 +91,14 @@ declare namespace PgBoss {
91
91
  interface ConnectionOptions {
92
92
  db?: Db;
93
93
  }
94
-
94
+
95
95
  type InsertOptions = ConnectionOptions;
96
-
96
+
97
97
  type SendOptions = JobOptions & ExpirationOptions & RetentionOptions & RetryOptions & ConnectionOptions;
98
+
99
+ type QueuePolicy = 'standard' | 'short' | 'singleton' | 'stately'
100
+ type Queue = ExpirationOptions & RetentionOptions & RetryOptions & { policy: QueuePolicy }
101
+ type QueueUpdateOptions = ExpirationOptions & RetentionOptions & RetryOptions
98
102
 
99
103
  type ScheduleOptions = SendOptions & { tz?: string }
100
104
 
@@ -195,7 +199,7 @@ declare namespace PgBoss {
195
199
  completedOn: Date | null;
196
200
  keepUntil: Date;
197
201
  deadLetter: string,
198
- policy: string,
202
+ policy: QueuePolicy,
199
203
  output: object
200
204
  }
201
205
 
@@ -316,10 +320,6 @@ declare class PgBoss extends EventEmitter {
316
320
  offWork(name: string): Promise<void>;
317
321
  offWork(options: PgBoss.OffWorkOptions): Promise<void>;
318
322
 
319
- /**
320
- * Notify worker that something has changed
321
- * @param workerId
322
- */
323
323
  notifyWorker(workerId: string): void;
324
324
 
325
325
  subscribe(event: string, name: string): Promise<void>;
@@ -350,15 +350,19 @@ declare class PgBoss extends EventEmitter {
350
350
  getQueueSize(name: string, options?: object): Promise<number>;
351
351
  getJobById(name: string, id: string, options?: PgBoss.ConnectionOptions): Promise<PgBoss.JobWithMetadata | null>;
352
352
 
353
- createQueue(name: string, options?: { policy: 'standard' | 'short' | 'singleton' | 'stately' }): Promise<void>;
353
+ createQueue(name: string, options?: PgBoss.Queue): Promise<void>;
354
+ getQueue(name: string): Promise<PgBoss.Queue | null>;
355
+ updateQueue(name: string, options?: PgBoss.QueueUpdateOptions): Promise<void>;
354
356
  deleteQueue(name: string): Promise<void>;
355
357
  purgeQueue(name: string): Promise<void>;
356
- clearStorage(): Promise<void>;
357
358
 
359
+ clearStorage(): Promise<void>;
358
360
  archive(): Promise<void>;
359
361
  purge(): Promise<void>;
360
362
  expire(): Promise<void>;
361
363
  maintain(): Promise<void>;
364
+ isInstalled(): Promise<Boolean>;
365
+ schemaVersion(): Promise<Number>;
362
366
 
363
367
  schedule(name: string, cron: string, data?: object, options?: PgBoss.ScheduleOptions): Promise<void>;
364
368
  unschedule(name: string): Promise<void>;