pg-boss 10.0.0-beta12 → 10.0.0-beta13

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-beta12",
3
+ "version": "10.0.0-beta13",
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
@@ -186,6 +186,7 @@ function assertPostgresObjectName (name) {
186
186
  }
187
187
 
188
188
  function assertQueueName (name) {
189
+ assert(name, 'Name is required')
189
190
  assert(typeof name === 'string', 'Name must be a string')
190
191
  assert(/[\w-]/.test(name), 'Name can only contain alphanumeric characters, underscores, or hyphens')
191
192
  }
package/src/manager.js CHANGED
@@ -511,7 +511,7 @@ class Manager extends EventEmitter {
511
511
  }
512
512
 
513
513
  async complete (name, id, data, options = {}) {
514
- assert(name, 'Missing queue name argument')
514
+ Attorney.assertQueueName(name)
515
515
  const db = options.db || this.db
516
516
  const ids = this.mapCompletionIdArg(id, 'complete')
517
517
  const result = await db.executeSql(this.completeJobsCommand, [name, ids, this.mapCompletionDataArg(data)])
@@ -519,7 +519,7 @@ class Manager extends EventEmitter {
519
519
  }
520
520
 
521
521
  async fail (name, id, data, options = {}) {
522
- assert(name, 'Missing queue name argument')
522
+ Attorney.assertQueueName(name)
523
523
  const db = options.db || this.db
524
524
  const ids = this.mapCompletionIdArg(id, 'fail')
525
525
  const result = await db.executeSql(this.failJobsByIdCommand, [name, ids, this.mapCompletionDataArg(data)])
@@ -527,7 +527,7 @@ class Manager extends EventEmitter {
527
527
  }
528
528
 
529
529
  async cancel (name, id, options = {}) {
530
- assert(name, 'Missing queue name argument')
530
+ Attorney.assertQueueName(name)
531
531
  const db = options.db || this.db
532
532
  const ids = this.mapCompletionIdArg(id, 'cancel')
533
533
  const result = await db.executeSql(this.cancelJobsCommand, [name, ids])
@@ -535,7 +535,7 @@ class Manager extends EventEmitter {
535
535
  }
536
536
 
537
537
  async resume (name, id, options = {}) {
538
- assert(name, 'Missing queue name argument')
538
+ Attorney.assertQueueName(name)
539
539
  const db = options.db || this.db
540
540
  const ids = this.mapCompletionIdArg(id, 'resume')
541
541
  const result = await db.executeSql(this.resumeJobsCommand, [name, ids])
@@ -543,7 +543,7 @@ class Manager extends EventEmitter {
543
543
  }
544
544
 
545
545
  async createQueue (name, options = {}) {
546
- assert(name, 'Missing queue name argument')
546
+ name = name || options.name
547
547
 
548
548
  Attorney.assertQueueName(name)
549
549
 
@@ -560,6 +560,10 @@ class Manager extends EventEmitter {
560
560
  deadLetter
561
561
  } = Attorney.checkQueueArgs(name, options)
562
562
 
563
+ if (deadLetter) {
564
+ Attorney.assertQueueName(deadLetter)
565
+ }
566
+
563
567
  const paritionSql = plans.createPartition(this.config.schema, name)
564
568
 
565
569
  await this.db.executeSql(paritionSql)
@@ -586,7 +590,11 @@ class Manager extends EventEmitter {
586
590
  }
587
591
 
588
592
  async updateQueue (name, options = {}) {
589
- assert(name, 'Missing queue name argument')
593
+ Attorney.assertQueueName(name)
594
+
595
+ const { policy = QUEUE_POLICIES.standard } = options
596
+
597
+ assert(policy in QUEUE_POLICIES, `${policy} is not a valid queue policy`)
590
598
 
591
599
  const {
592
600
  retryLimit,
@@ -601,6 +609,7 @@ class Manager extends EventEmitter {
601
609
 
602
610
  const params = [
603
611
  name,
612
+ policy,
604
613
  retryLimit,
605
614
  retryDelay,
606
615
  retryBackoff,
@@ -613,7 +622,7 @@ class Manager extends EventEmitter {
613
622
  }
614
623
 
615
624
  async getQueue (name) {
616
- assert(name, 'Missing queue name argument')
625
+ Attorney.assertQueueName(name)
617
626
 
618
627
  const sql = plans.getQueueByName(this.config.schema)
619
628
  const result = await this.db.executeSql(sql, [name])
@@ -645,25 +654,21 @@ class Manager extends EventEmitter {
645
654
  }
646
655
 
647
656
  async deleteQueue (name) {
648
- assert(name, 'Missing queue name argument')
657
+ Attorney.assertQueueName(name)
649
658
 
650
659
  const queueSql = plans.getQueueByName(this.config.schema)
651
660
  const { rows } = await this.db.executeSql(queueSql, [name])
652
661
 
653
662
  if (rows.length) {
654
- Attorney.assertQueueName(name)
655
- const sql = plans.dropPartition(this.config.schema, name)
656
- await this.db.executeSql(sql)
663
+ await this.db.executeSql(plans.dropPartition(this.config.schema, name))
664
+ await this.db.executeSql(plans.deleteQueueRecords(this.config.schema), [name])
657
665
  }
658
-
659
- const sql = plans.deleteQueueRecords(this.config.schema)
660
- await this.db.executeSql(sql, [name])
661
666
  }
662
667
 
663
- async purgeQueue (queue) {
664
- assert(queue, 'Missing queue name argument')
668
+ async purgeQueue (name) {
669
+ Attorney.assertQueueName(name)
665
670
  const sql = plans.purgeQueue(this.config.schema)
666
- await this.db.executeSql(sql, [queue])
671
+ await this.db.executeSql(sql, [name])
667
672
  }
668
673
 
669
674
  async clearStorage () {
@@ -671,25 +676,27 @@ class Manager extends EventEmitter {
671
676
  await this.db.executeSql(sql)
672
677
  }
673
678
 
674
- async getQueueSize (queue, options) {
675
- assert(queue, 'Missing queue name argument')
679
+ async getQueueSize (name, options) {
680
+ Attorney.assertQueueName(name)
676
681
 
677
682
  const sql = plans.getQueueSize(this.config.schema, options)
678
683
 
679
- const result = await this.db.executeSql(sql, [queue])
684
+ const result = await this.db.executeSql(sql, [name])
680
685
 
681
686
  return result ? parseFloat(result.rows[0].count) : null
682
687
  }
683
688
 
684
- async getJobById (queue, id, options = {}) {
689
+ async getJobById (name, id, options = {}) {
690
+ Attorney.assertQueueName(name)
691
+
685
692
  const db = options.db || this.db
686
- const result1 = await db.executeSql(this.getJobByIdCommand, [queue, id])
693
+ const result1 = await db.executeSql(this.getJobByIdCommand, [name, id])
687
694
 
688
695
  if (result1 && result1.rows && result1.rows.length === 1) {
689
696
  return result1.rows[0]
690
697
  }
691
698
 
692
- const result2 = await db.executeSql(this.getArchivedJobByIdCommand, [queue, id])
699
+ const result2 = await db.executeSql(this.getArchivedJobByIdCommand, [name, id])
693
700
 
694
701
  if (result2 && result2.rows && result2.rows.length === 1) {
695
702
  return result2.rows[0]
package/src/plans.js CHANGED
@@ -73,24 +73,18 @@ function create (schema, version) {
73
73
  createSchema(schema),
74
74
  createEnumJobState(schema),
75
75
 
76
+ createTableVersion(schema),
77
+ createTableQueue(schema),
78
+ createTableSchedule(schema),
79
+ createTableSubscription(schema),
80
+
76
81
  createTableJob(schema),
77
- createIndexJobFetch(schema),
78
- createIndexJobPolicyStately(schema),
79
- createIndexJobPolicyShort(schema),
80
- createIndexJobPolicySingleton(schema),
81
- createIndexJobThrottleOn(schema),
82
- createIndexJobThrottleKey(schema),
83
82
 
84
83
  createTableArchive(schema),
85
84
  createPrimaryKeyArchive(schema),
86
85
  createColumnArchiveArchivedOn(schema),
87
86
  createIndexArchiveArchivedOn(schema),
88
87
 
89
- createTableVersion(schema),
90
- createTableQueue(schema),
91
- createTableSchedule(schema),
92
- createTableSubscription(schema),
93
-
94
88
  getPartitionFunction(schema),
95
89
  createPartitionFunction(schema),
96
90
  dropPartitionFunction(schema),
@@ -107,6 +101,21 @@ function createSchema (schema) {
107
101
  `
108
102
  }
109
103
 
104
+ function createEnumJobState (schema) {
105
+ // ENUM definition order is important
106
+ // base type is numeric and first values are less than last values
107
+ return `
108
+ CREATE TYPE ${schema}.job_state AS ENUM (
109
+ '${JOB_STATES.created}',
110
+ '${JOB_STATES.retry}',
111
+ '${JOB_STATES.active}',
112
+ '${JOB_STATES.completed}',
113
+ '${JOB_STATES.cancelled}',
114
+ '${JOB_STATES.failed}'
115
+ )
116
+ `
117
+ }
118
+
110
119
  function createTableVersion (schema) {
111
120
  return `
112
121
  CREATE TABLE ${schema}.version (
@@ -118,17 +127,46 @@ function createTableVersion (schema) {
118
127
  `
119
128
  }
120
129
 
121
- function createEnumJobState (schema) {
122
- // ENUM definition order is important
123
- // base type is numeric and first values are less than last values
130
+ function createTableQueue (schema) {
124
131
  return `
125
- CREATE TYPE ${schema}.job_state AS ENUM (
126
- '${JOB_STATES.created}',
127
- '${JOB_STATES.retry}',
128
- '${JOB_STATES.active}',
129
- '${JOB_STATES.completed}',
130
- '${JOB_STATES.cancelled}',
131
- '${JOB_STATES.failed}'
132
+ CREATE TABLE ${schema}.queue (
133
+ name text,
134
+ policy text,
135
+ retry_limit int,
136
+ retry_delay int,
137
+ retry_backoff bool,
138
+ expire_seconds int,
139
+ retention_minutes int,
140
+ dead_letter text,
141
+ created_on timestamp with time zone not null default now(),
142
+ PRIMARY KEY (name)
143
+ )
144
+ `
145
+ }
146
+
147
+ function createTableSchedule (schema) {
148
+ return `
149
+ CREATE TABLE ${schema}.schedule (
150
+ name text REFERENCES ${schema}.queue ON DELETE CASCADE,
151
+ cron text not null,
152
+ timezone text,
153
+ data jsonb,
154
+ options jsonb,
155
+ created_on timestamp with time zone not null default now(),
156
+ updated_on timestamp with time zone not null default now(),
157
+ PRIMARY KEY (name)
158
+ )
159
+ `
160
+ }
161
+
162
+ function createTableSubscription (schema) {
163
+ return `
164
+ CREATE TABLE ${schema}.subscription (
165
+ event text not null,
166
+ name text not null REFERENCES ${schema}.queue ON DELETE CASCADE,
167
+ created_on timestamp with time zone not null default now(),
168
+ updated_on timestamp with time zone not null default now(),
169
+ PRIMARY KEY(event, name)
132
170
  )
133
171
  `
134
172
  }
@@ -155,8 +193,7 @@ function createTableJob (schema) {
155
193
  keep_until timestamp with time zone NOT NULL default now() + interval '14 days',
156
194
  output jsonb,
157
195
  dead_letter text,
158
- policy text,
159
- CONSTRAINT job_pkey PRIMARY KEY (name, id)
196
+ policy text
160
197
  ) PARTITION BY LIST (name)
161
198
  `
162
199
  }
@@ -187,7 +224,7 @@ function getPartitionFunction (schema) {
187
224
  return `
188
225
  CREATE FUNCTION ${schema}.get_partition(queue_name text, out name text) AS
189
226
  $$
190
- SELECT 'job_' || encode(sha224(queue_name::bytea), 'hex');
227
+ SELECT 'j' || left(regexp_replace(queue_name, '\\W', '', 'g'),10) || left(encode(sha224(queue_name::bytea), 'hex'),10);
191
228
  $$
192
229
  LANGUAGE SQL
193
230
  IMMUTABLE
@@ -202,8 +239,18 @@ function createPartitionFunction (schema) {
202
239
  DECLARE
203
240
  table_name varchar := ${schema}.get_partition(queue_name);
204
241
  BEGIN
205
- EXECUTE format('CREATE TABLE ${schema}.%I (LIKE ${schema}.job INCLUDING DEFAULTS INCLUDING CONSTRAINTS)', table_name);
206
- EXECUTE format('ALTER TABLE ${schema}.%I ADD CHECK (name=%L)', table_name, queue_name);
242
+ EXECUTE format('CREATE TABLE ${schema}.%I (LIKE ${schema}.job INCLUDING DEFAULTS)', table_name);
243
+
244
+ EXECUTE format('${formatPartitionCommand(createPrimaryKeyJob(schema))}', table_name);
245
+ EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJob(schema))}', table_name);
246
+ EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJobDeadLetter(schema))}', table_name);
247
+ EXECUTE format('${formatPartitionCommand(createIndexJobPolicyShort(schema))}', table_name);
248
+ EXECUTE format('${formatPartitionCommand(createIndexJobPolicySingleton(schema))}', table_name);
249
+ EXECUTE format('${formatPartitionCommand(createIndexJobPolicyStately(schema))}', table_name);
250
+ EXECUTE format('${formatPartitionCommand(createIndexJobThrottle(schema))}', table_name);
251
+ EXECUTE format('${formatPartitionCommand(createIndexJobFetch(schema))}', table_name);
252
+
253
+ EXECUTE format('ALTER TABLE ${schema}.%I ADD CONSTRAINT cjc CHECK (name=%L)', table_name, queue_name);
207
254
  EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.%I FOR VALUES IN (%L)', table_name, queue_name);
208
255
  END;
209
256
  $$
@@ -211,6 +258,10 @@ function createPartitionFunction (schema) {
211
258
  `
212
259
  }
213
260
 
261
+ function formatPartitionCommand (command) {
262
+ return command.replace('.job', '.%1$I').replace('job_idx', '%1$s_idx').replaceAll('\'', '\'\'')
263
+ }
264
+
214
265
  function dropPartitionFunction (schema) {
215
266
  return `
216
267
  CREATE FUNCTION ${schema}.drop_partition(queue_name text)
@@ -228,34 +279,40 @@ function dropPartition (schema, name) {
228
279
  return `SELECT ${schema}.drop_partition('${name}');`
229
280
  }
230
281
 
282
+ function createPrimaryKeyJob (schema) {
283
+ return `ALTER TABLE ${schema}.job ADD PRIMARY KEY (name, id)`
284
+ }
285
+
286
+ function createQueueForeignKeyJob (schema) {
287
+ return `ALTER TABLE ${schema}.job ADD FOREIGN KEY (name) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT`
288
+ }
289
+
290
+ function createQueueForeignKeyJobDeadLetter (schema) {
291
+ return `ALTER TABLE ${schema}.job ADD FOREIGN KEY (dead_letter) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT`
292
+ }
293
+
231
294
  function createPrimaryKeyArchive (schema) {
232
- return `ALTER TABLE ${schema}.archive ADD CONSTRAINT archive_pkey PRIMARY KEY (name, id)`
295
+ return `ALTER TABLE ${schema}.archive ADD PRIMARY KEY (name, id)`
233
296
  }
234
297
 
235
298
  function createIndexJobPolicyShort (schema) {
236
- // todo: how to combine these policies with singleton key?
237
- return `CREATE UNIQUE INDEX job_policy_short ON ${schema}.job (name) WHERE state = '${JOB_STATES.created}' AND policy = '${QUEUE_POLICIES.short}'`
299
+ return `CREATE UNIQUE INDEX job_idx_psh ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state = '${JOB_STATES.created}' AND policy = '${QUEUE_POLICIES.short}';`
238
300
  }
239
301
 
240
302
  function createIndexJobPolicySingleton (schema) {
241
- return `CREATE UNIQUE INDEX job_policy_singleton ON ${schema}.job (name) WHERE state = '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.singleton}'`
303
+ return `CREATE UNIQUE INDEX job_idx_psi ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state = '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.singleton}'`
242
304
  }
243
305
 
244
306
  function createIndexJobPolicyStately (schema) {
245
- return `CREATE UNIQUE INDEX job_policy_stately ON ${schema}.job (name, state) WHERE state <= '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.stately}'`
246
- }
247
-
248
- function createIndexJobThrottleOn (schema) {
249
- return `CREATE UNIQUE INDEX job_throttle_on ON ${schema}.job (name, singleton_on, COALESCE(singleton_key, '')) WHERE state <= '${JOB_STATES.completed}' AND singleton_on IS NOT NULL`
307
+ return `CREATE UNIQUE INDEX job_idx_pst ON ${schema}.job (name, state, COALESCE(singleton_key, '')) WHERE state <= '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.stately}'`
250
308
  }
251
309
 
252
- function createIndexJobThrottleKey (schema) {
253
- // how useful is this really?
254
- return `CREATE UNIQUE INDEX job_throttle_key ON ${schema}.job (name, singleton_key) WHERE state <= '${JOB_STATES.completed}' AND singleton_on IS NULL`
310
+ function createIndexJobThrottle (schema) {
311
+ return `CREATE UNIQUE INDEX job_idx_to ON ${schema}.job (name, singleton_on, COALESCE(singleton_key, '')) WHERE state <> '${JOB_STATES.cancelled}' AND singleton_on IS NOT NULL`
255
312
  }
256
313
 
257
314
  function createIndexJobFetch (schema) {
258
- return `CREATE INDEX job_fetch ON ${schema}.job (name, start_after) INCLUDE (priority, created_on, id) WHERE state < '${JOB_STATES.active}'`
315
+ return `CREATE INDEX job_idx_f ON ${schema}.job (name, start_after) INCLUDE (priority, created_on, id) WHERE state < '${JOB_STATES.active}'`
259
316
  }
260
317
 
261
318
  function createTableArchive (schema) {
@@ -267,7 +324,7 @@ function createColumnArchiveArchivedOn (schema) {
267
324
  }
268
325
 
269
326
  function createIndexArchiveArchivedOn (schema) {
270
- return `CREATE INDEX archive_archived_on_idx ON ${schema}.archive(archived_on)`
327
+ return `CREATE INDEX archive_idx_ao ON ${schema}.archive(archived_on)`
271
328
  }
272
329
 
273
330
  function trySetMaintenanceTime (schema) {
@@ -300,12 +357,13 @@ function insertQueue (schema) {
300
357
  function updateQueue (schema) {
301
358
  return `
302
359
  UPDATE ${schema}.queue SET
303
- retry_limit = COALESCE($2, retry_limit),
304
- retry_delay = COALESCE($3, retry_delay),
305
- retry_backoff = COALESCE($4, retry_backoff),
306
- expire_seconds = COALESCE($5, expire_seconds),
307
- retention_minutes = COALESCE($6, retention_minutes),
308
- dead_letter = COALESCE($7, dead_letter)
360
+ policy = COALESCE($2, policy),
361
+ retry_limit = COALESCE($3, retry_limit),
362
+ retry_delay = COALESCE($4, retry_delay),
363
+ retry_backoff = COALESCE($5, retry_backoff),
364
+ expire_seconds = COALESCE($6, expire_seconds),
365
+ retention_minutes = COALESCE($7, retention_minutes),
366
+ dead_letter = COALESCE($8, dead_letter)
309
367
  WHERE name = $1
310
368
  `
311
369
  }
@@ -319,13 +377,7 @@ function getQueueByName (schema) {
319
377
  }
320
378
 
321
379
  function deleteQueueRecords (schema) {
322
- return `WITH dq AS (
323
- DELETE FROM ${schema}.queue WHERE name = $1
324
- ), ds AS (
325
- DELETE FROM ${schema}.schedule WHERE name = $1
326
- )
327
- DELETE FROM ${schema}.job WHERE name = $1
328
- `
380
+ return `DELETE FROM ${schema}.queue WHERE name = $1`
329
381
  }
330
382
 
331
383
  function purgeQueue (schema) {
@@ -342,54 +394,8 @@ function getQueueSize (schema, options = {}) {
342
394
  return `SELECT count(*) as count FROM ${schema}.job WHERE name = $1 AND state < '${options.before}'`
343
395
  }
344
396
 
345
- function createTableQueue (schema) {
346
- return `
347
- CREATE TABLE ${schema}.queue (
348
- name text primary key,
349
- policy text,
350
- retry_limit int,
351
- retry_delay int,
352
- retry_backoff bool,
353
- expire_seconds int,
354
- retention_minutes int,
355
- dead_letter text,
356
- created_on timestamp with time zone not null default now()
357
- )
358
- `
359
- }
360
-
361
- function createTableSchedule (schema) {
362
- return `
363
- CREATE TABLE ${schema}.schedule (
364
- name text primary key,
365
- cron text not null,
366
- timezone text,
367
- data jsonb,
368
- options jsonb,
369
- created_on timestamp with time zone not null default now(),
370
- updated_on timestamp with time zone not null default now()
371
- )
372
- `
373
- }
374
-
375
- function createTableSubscription (schema) {
376
- return `
377
- CREATE TABLE ${schema}.subscription (
378
- event text not null,
379
- name text not null,
380
- created_on timestamp with time zone not null default now(),
381
- updated_on timestamp with time zone not null default now(),
382
- PRIMARY KEY(event, name)
383
- )
384
- `
385
- }
386
-
387
397
  function getSchedules (schema) {
388
- return `
389
- SELECT s.*
390
- FROM ${schema}.schedule s
391
- JOIN ${schema}.queue q on s.name = q.name
392
- `
398
+ return `SELECT * FROM ${schema}.schedule`
393
399
  }
394
400
 
395
401
  function schedule (schema) {
package/types.d.ts CHANGED
@@ -113,9 +113,9 @@ declare namespace PgBoss {
113
113
  type SendOptions = JobOptions & ExpirationOptions & RetentionOptions & RetryOptions & ConnectionOptions;
114
114
 
115
115
  type QueuePolicy = 'standard' | 'short' | 'singleton' | 'stately'
116
- type Queue = ExpirationOptions & RetentionOptions & RetryOptions & { policy: QueuePolicy }
117
- type QueueUpdateOptions = ExpirationOptions & RetentionOptions & RetryOptions
118
-
116
+
117
+ type Queue = RetryOptions & ExpirationOptions & RetentionOptions & { name: string, policy: QueuePolicy, deadLetter?: string }
118
+
119
119
  type ScheduleOptions = SendOptions & { tz?: string }
120
120
 
121
121
  interface JobPollingOptions {
@@ -371,7 +371,7 @@ declare class PgBoss extends EventEmitter {
371
371
  createQueue(name: string, options?: PgBoss.Queue): Promise<void>;
372
372
  getQueue(name: string): Promise<PgBoss.Queue | null>;
373
373
  getQueues(): Promise<[PgBoss.Queue]>;
374
- updateQueue(name: string, options?: PgBoss.QueueUpdateOptions): Promise<void>;
374
+ updateQueue(name: string, options?: PgBoss.Queue): Promise<void>;
375
375
  deleteQueue(name: string): Promise<void>;
376
376
  purgeQueue(name: string): Promise<void>;
377
377