pg-boss 10.0.0-beta2 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pg-boss",
3
- "version": "10.0.0-beta2",
3
+ "version": "10.0.0-beta3",
4
4
  "description": "Queueing jobs in Postgres from Node.js like a boss",
5
5
  "main": "./src/index.js",
6
6
  "engines": {
package/src/attorney.js CHANGED
@@ -8,7 +8,8 @@ module.exports = {
8
8
  checkWorkArgs,
9
9
  checkFetchArgs,
10
10
  warnClockSkew,
11
- assertPostgresObjectName
11
+ assertPostgresObjectName,
12
+ assertQueueName
12
13
  }
13
14
 
14
15
  const MAX_INTERVAL_HOURS = 24
@@ -29,8 +30,6 @@ const WARNINGS = {
29
30
  }
30
31
 
31
32
  function checkQueueArgs (name, options = {}) {
32
- assertPostgresObjectName(name)
33
-
34
33
  assert(!('deadLetter' in options) || (typeof options.deadLetter === 'string'), 'deadLetter must be a string')
35
34
 
36
35
  applyRetryConfig(options)
@@ -178,8 +177,14 @@ function applySchemaConfig (config) {
178
177
  function assertPostgresObjectName (name) {
179
178
  assert(typeof name === 'string', 'Name must be a string')
180
179
  assert(name.length <= 50, 'Name cannot exceed 50 characters')
181
- assert(!/\W/.test(name), 'Name can only contain alphanumeric characters and underscores')
182
- 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')
183
188
  }
184
189
 
185
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 () {
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))
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
@@ -547,6 +547,8 @@ class Manager extends EventEmitter {
547
547
  async createQueue (name, options = {}) {
548
548
  assert(name, 'Missing queue name argument')
549
549
 
550
+ Attorney.assertQueueName(name)
551
+
550
552
  const { policy = QUEUE_POLICY.standard } = options
551
553
 
552
554
  assert(policy in QUEUE_POLICY, `${policy} is not a valid queue policy`)
@@ -560,7 +562,7 @@ class Manager extends EventEmitter {
560
562
  deadLetter
561
563
  } = Attorney.checkQueueArgs(name, options)
562
564
 
563
- const paritionSql = plans.partitionCreateJobName(this.config.schema, name)
565
+ const paritionSql = plans.createPartition(this.config.schema, name)
564
566
 
565
567
  await this.db.executeSql(paritionSql)
566
568
 
@@ -646,8 +648,8 @@ class Manager extends EventEmitter {
646
648
  const result = await this.db.executeSql(queueSql, [name])
647
649
 
648
650
  if (result?.rows?.length) {
649
- Attorney.assertPostgresObjectName(name)
650
- const sql = plans.dropJobTablePartition(this.config.schema, name)
651
+ Attorney.assertQueueName(name)
652
+ const sql = plans.dropPartition(this.config.schema, name)
651
653
  await this.db.executeSql(sql)
652
654
  }
653
655
 
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,
@@ -96,6 +96,10 @@ function create (schema, version) {
96
96
  createTableSchedule(schema),
97
97
  createTableSubscription(schema),
98
98
 
99
+ getPartitionFunction(schema),
100
+ createPartitionFunction(schema),
101
+ dropPartitionFunction(schema),
102
+
99
103
  insertVersion(schema, version)
100
104
  ]
101
105
 
@@ -162,16 +166,53 @@ function createTableJob (schema) {
162
166
  `
163
167
  }
164
168
 
165
- function partitionCreateJobName (schema, name) {
169
+ function createPartition (schema, name) {
170
+ return `SELECT ${schema}.create_partition('${name}');`
171
+ }
172
+
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
+ `
182
+ }
183
+
184
+ function createPartitionFunction (schema) {
185
+ return `
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;
198
+ `
199
+ }
200
+
201
+ function dropPartitionFunction (schema) {
166
202
  return `
167
- CREATE TABLE ${schema}.job_${name} (LIKE ${schema}.job INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
168
- ALTER TABLE ${schema}.job_${name} ADD CONSTRAINT job_check_${name} CHECK (name='${name}');
169
- ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.job_${name} FOR VALUES IN ('${name}');
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;
170
211
  `
171
212
  }
172
213
 
173
- function dropJobTablePartition (schema, name) {
174
- return `DROP TABLE IF EXISTS ${schema}.job_${name}`
214
+ function dropPartition (schema, name) {
215
+ return `SELECT ${schema}.drop_partition('${name}');`
175
216
  }
176
217
 
177
218
  function createPrimaryKeyArchive (schema) {
@@ -723,8 +764,8 @@ function locked (schema, query) {
723
764
  }
724
765
 
725
766
  function advisoryLock (schema, key) {
726
- return `SELECT pg_advisory_xact_lock(
727
- ('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
728
769
  )`
729
770
  }
730
771
 
package/src/timekeeper.js CHANGED
@@ -6,7 +6,7 @@ const pMap = require('p-map')
6
6
 
7
7
  const queues = {
8
8
  CRON: '__pgboss__cron',
9
- SEND_IT: '__pgboss__send_it'
9
+ SEND_IT: '__pgboss__send-it'
10
10
  }
11
11
 
12
12
  const events = {
package/types.d.ts CHANGED
@@ -348,7 +348,7 @@ declare class PgBoss extends EventEmitter {
348
348
  getQueueSize(name: string, options?: object): Promise<number>;
349
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>;