pg-boss 12.4.0 → 12.4.1

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/dist/index.mjs DELETED
@@ -1,2468 +0,0 @@
1
- import EventEmitter from "node:events";
2
- import assert, { notStrictEqual } from "node:assert";
3
- import { randomUUID } from "node:crypto";
4
- import { CronExpressionParser } from "cron-parser";
5
- import { setTimeout } from "node:timers/promises";
6
- import pg from "pg";
7
-
8
- //#region src/plans.ts
9
- const DEFAULT_SCHEMA = "pgboss";
10
- const MIGRATE_RACE_MESSAGE = "division by zero";
11
- const CREATE_RACE_MESSAGE = "already exists";
12
- const FIFTEEN_MINUTES = 900;
13
- const FORTEEN_DAYS = 3600 * 24 * 14;
14
- const SEVEN_DAYS = 3600 * 24 * 7;
15
- const JOB_STATES = Object.freeze({
16
- created: "created",
17
- retry: "retry",
18
- active: "active",
19
- completed: "completed",
20
- cancelled: "cancelled",
21
- failed: "failed"
22
- });
23
- const QUEUE_POLICIES = Object.freeze({
24
- standard: "standard",
25
- short: "short",
26
- singleton: "singleton",
27
- stately: "stately",
28
- exclusive: "exclusive"
29
- });
30
- const QUEUE_DEFAULTS = {
31
- expire_seconds: FIFTEEN_MINUTES,
32
- retention_seconds: FORTEEN_DAYS,
33
- deletion_seconds: SEVEN_DAYS,
34
- retry_limit: 2,
35
- retry_delay: 0,
36
- warning_queued: 0,
37
- retry_backoff: false,
38
- partition: false
39
- };
40
- const COMMON_JOB_TABLE = "job_common";
41
- function create(schema, version, options) {
42
- return locked(schema, [
43
- options?.createSchema ? createSchema(schema) : "",
44
- createEnumJobState(schema),
45
- createTableVersion(schema),
46
- createTableQueue(schema),
47
- createTableSchedule(schema),
48
- createTableSubscription(schema),
49
- createTableJob(schema),
50
- createPrimaryKeyJob(schema),
51
- createTableJobCommon(schema, COMMON_JOB_TABLE),
52
- createQueueFunction(schema),
53
- deleteQueueFunction(schema),
54
- insertVersion(schema, version)
55
- ]);
56
- }
57
- function createSchema(schema) {
58
- return `CREATE SCHEMA IF NOT EXISTS ${schema}`;
59
- }
60
- function createEnumJobState(schema) {
61
- return `
62
- CREATE TYPE ${schema}.job_state AS ENUM (
63
- '${JOB_STATES.created}',
64
- '${JOB_STATES.retry}',
65
- '${JOB_STATES.active}',
66
- '${JOB_STATES.completed}',
67
- '${JOB_STATES.cancelled}',
68
- '${JOB_STATES.failed}'
69
- )
70
- `;
71
- }
72
- function createTableVersion(schema) {
73
- return `
74
- CREATE TABLE ${schema}.version (
75
- version int primary key,
76
- cron_on timestamp with time zone
77
- )
78
- `;
79
- }
80
- function createTableQueue(schema) {
81
- return `
82
- CREATE TABLE ${schema}.queue (
83
- name text NOT NULL,
84
- policy text NOT NULL,
85
- retry_limit int NOT NULL,
86
- retry_delay int NOT NULL,
87
- retry_backoff bool NOT NULL,
88
- retry_delay_max int,
89
- expire_seconds int NOT NULL,
90
- retention_seconds int NOT NULL,
91
- deletion_seconds int NOT NULL,
92
- dead_letter text REFERENCES ${schema}.queue (name) CHECK (dead_letter IS DISTINCT FROM name),
93
- partition bool NOT NULL,
94
- table_name text NOT NULL,
95
- deferred_count int NOT NULL default 0,
96
- queued_count int NOT NULL default 0,
97
- warning_queued int NOT NULL default 0,
98
- active_count int NOT NULL default 0,
99
- total_count int NOT NULL default 0,
100
- singletons_active text[],
101
- monitor_on timestamp with time zone,
102
- maintain_on timestamp with time zone,
103
- created_on timestamp with time zone not null default now(),
104
- updated_on timestamp with time zone not null default now(),
105
- PRIMARY KEY (name)
106
- )
107
- `;
108
- }
109
- function createTableSchedule(schema) {
110
- return `
111
- CREATE TABLE ${schema}.schedule (
112
- name text REFERENCES ${schema}.queue ON DELETE CASCADE,
113
- key text not null DEFAULT '',
114
- cron text not null,
115
- timezone text,
116
- data jsonb,
117
- options jsonb,
118
- created_on timestamp with time zone not null default now(),
119
- updated_on timestamp with time zone not null default now(),
120
- PRIMARY KEY (name, key)
121
- )
122
- `;
123
- }
124
- function createTableSubscription(schema) {
125
- return `
126
- CREATE TABLE ${schema}.subscription (
127
- event text not null,
128
- name text not null REFERENCES ${schema}.queue ON DELETE CASCADE,
129
- created_on timestamp with time zone not null default now(),
130
- updated_on timestamp with time zone not null default now(),
131
- PRIMARY KEY(event, name)
132
- )
133
- `;
134
- }
135
- function createTableJob(schema) {
136
- return `
137
- CREATE TABLE ${schema}.job (
138
- id uuid not null default gen_random_uuid(),
139
- name text not null,
140
- priority integer not null default(0),
141
- data jsonb,
142
- state ${schema}.job_state not null default '${JOB_STATES.created}',
143
- retry_limit integer not null default ${QUEUE_DEFAULTS.retry_limit},
144
- retry_count integer not null default 0,
145
- retry_delay integer not null default ${QUEUE_DEFAULTS.retry_delay},
146
- retry_backoff boolean not null default ${QUEUE_DEFAULTS.retry_backoff},
147
- retry_delay_max integer,
148
- expire_seconds int not null default ${QUEUE_DEFAULTS.expire_seconds},
149
- deletion_seconds int not null default ${QUEUE_DEFAULTS.deletion_seconds},
150
- singleton_key text,
151
- singleton_on timestamp without time zone,
152
- start_after timestamp with time zone not null default now(),
153
- created_on timestamp with time zone not null default now(),
154
- started_on timestamp with time zone,
155
- completed_on timestamp with time zone,
156
- keep_until timestamp with time zone NOT NULL default now() + interval '${QUEUE_DEFAULTS.retention_seconds}',
157
- output jsonb,
158
- dead_letter text,
159
- policy text
160
- ) PARTITION BY LIST (name)
161
- `;
162
- }
163
- const JOB_COLUMNS_MIN = "id, name, data, expire_seconds as \"expireInSeconds\"";
164
- const JOB_COLUMNS_ALL = `${JOB_COLUMNS_MIN},
165
- policy,
166
- state,
167
- priority,
168
- retry_limit as "retryLimit",
169
- retry_count as "retryCount",
170
- retry_delay as "retryDelay",
171
- retry_backoff as "retryBackoff",
172
- retry_delay_max as "retryDelayMax",
173
- start_after as "startAfter",
174
- started_on as "startedOn",
175
- singleton_key as "singletonKey",
176
- singleton_on as "singletonOn",
177
- deletion_seconds as "deleteAfterSeconds",
178
- created_on as "createdOn",
179
- completed_on as "completedOn",
180
- keep_until as "keepUntil",
181
- dead_letter as "deadLetter",
182
- output
183
- `;
184
- function createTableJobCommon(schema, table) {
185
- const format = (command) => command.replaceAll(".job", `.${table}`) + ";";
186
- return `
187
- CREATE TABLE ${schema}.${table} (LIKE ${schema}.job INCLUDING GENERATED INCLUDING DEFAULTS);
188
- ${format(createPrimaryKeyJob(schema))}
189
- ${format(createQueueForeignKeyJob(schema))}
190
- ${format(createQueueForeignKeyJobDeadLetter(schema))}
191
- ${format(createIndexJobPolicyShort(schema))}
192
- ${format(createIndexJobPolicySingleton(schema))}
193
- ${format(createIndexJobPolicyStately(schema))}
194
- ${format(createIndexJobPolicyExclusive(schema))}
195
- ${format(createIndexJobThrottle(schema))}
196
- ${format(createIndexJobFetch(schema))}
197
-
198
- ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.${table} DEFAULT;
199
- `;
200
- }
201
- function createQueueFunction(schema) {
202
- return `
203
- CREATE FUNCTION ${schema}.create_queue(queue_name text, options jsonb)
204
- RETURNS VOID AS
205
- $$
206
- DECLARE
207
- tablename varchar := CASE WHEN options->>'partition' = 'true'
208
- THEN 'j' || encode(sha224(queue_name::bytea), 'hex')
209
- ELSE '${COMMON_JOB_TABLE}'
210
- END;
211
- queue_created_on timestamptz;
212
- BEGIN
213
-
214
- WITH q as (
215
- INSERT INTO ${schema}.queue (
216
- name,
217
- policy,
218
- retry_limit,
219
- retry_delay,
220
- retry_backoff,
221
- retry_delay_max,
222
- expire_seconds,
223
- retention_seconds,
224
- deletion_seconds,
225
- warning_queued,
226
- dead_letter,
227
- partition,
228
- table_name
229
- )
230
- VALUES (
231
- queue_name,
232
- options->>'policy',
233
- COALESCE((options->>'retryLimit')::int, ${QUEUE_DEFAULTS.retry_limit}),
234
- COALESCE((options->>'retryDelay')::int, ${QUEUE_DEFAULTS.retry_delay}),
235
- COALESCE((options->>'retryBackoff')::bool, ${QUEUE_DEFAULTS.retry_backoff}),
236
- (options->>'retryDelayMax')::int,
237
- COALESCE((options->>'expireInSeconds')::int, ${QUEUE_DEFAULTS.expire_seconds}),
238
- COALESCE((options->>'retentionSeconds')::int, ${QUEUE_DEFAULTS.retention_seconds}),
239
- COALESCE((options->>'deleteAfterSeconds')::int, ${QUEUE_DEFAULTS.deletion_seconds}),
240
- COALESCE((options->>'warningQueueSize')::int, ${QUEUE_DEFAULTS.warning_queued}),
241
- options->>'deadLetter',
242
- COALESCE((options->>'partition')::bool, ${QUEUE_DEFAULTS.partition}),
243
- tablename
244
- )
245
- ON CONFLICT DO NOTHING
246
- RETURNING created_on
247
- )
248
- SELECT created_on into queue_created_on from q;
249
-
250
- IF queue_created_on IS NULL OR options->>'partition' IS DISTINCT FROM 'true' THEN
251
- RETURN;
252
- END IF;
253
-
254
- EXECUTE format('CREATE TABLE ${schema}.%I (LIKE ${schema}.job INCLUDING DEFAULTS)', tablename);
255
-
256
- EXECUTE format('${formatPartitionCommand(createPrimaryKeyJob(schema))}', tablename);
257
- EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJob(schema))}', tablename);
258
- EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJobDeadLetter(schema))}', tablename);
259
-
260
- EXECUTE format('${formatPartitionCommand(createIndexJobFetch(schema))}', tablename);
261
- EXECUTE format('${formatPartitionCommand(createIndexJobThrottle(schema))}', tablename);
262
-
263
- IF options->>'policy' = 'short' THEN
264
- EXECUTE format('${formatPartitionCommand(createIndexJobPolicyShort(schema))}', tablename);
265
- ELSIF options->>'policy' = 'singleton' THEN
266
- EXECUTE format('${formatPartitionCommand(createIndexJobPolicySingleton(schema))}', tablename);
267
- ELSIF options->>'policy' = 'stately' THEN
268
- EXECUTE format('${formatPartitionCommand(createIndexJobPolicyStately(schema))}', tablename);
269
- ELSIF options->>'policy' = 'exclusive' THEN
270
- EXECUTE format('${formatPartitionCommand(createIndexJobPolicyExclusive(schema))}', tablename);
271
- END IF;
272
-
273
- EXECUTE format('ALTER TABLE ${schema}.%I ADD CONSTRAINT cjc CHECK (name=%L)', tablename, queue_name);
274
- EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.%I FOR VALUES IN (%L)', tablename, queue_name);
275
- END;
276
- $$
277
- LANGUAGE plpgsql;
278
- `;
279
- }
280
- function formatPartitionCommand(command) {
281
- return command.replace(".job", ".%1$I").replace("job_i", "%1$s_i").replaceAll("'", "''");
282
- }
283
- function deleteQueueFunction(schema) {
284
- return `
285
- CREATE FUNCTION ${schema}.delete_queue(queue_name text)
286
- RETURNS VOID AS
287
- $$
288
- DECLARE
289
- v_table varchar;
290
- v_partition bool;
291
- BEGIN
292
- SELECT table_name, partition
293
- FROM ${schema}.queue
294
- WHERE name = queue_name
295
- INTO v_table, v_partition;
296
-
297
- IF v_partition THEN
298
- EXECUTE format('DROP TABLE IF EXISTS ${schema}.%I', v_table);
299
- ELSE
300
- EXECUTE format('DELETE FROM ${schema}.%I WHERE name = %L', v_table, queue_name);
301
- END IF;
302
-
303
- DELETE FROM ${schema}.queue WHERE name = queue_name;
304
- END;
305
- $$
306
- LANGUAGE plpgsql;
307
- `;
308
- }
309
- function createQueue(schema, name, options) {
310
- return locked(schema, `SELECT ${schema}.create_queue('${name}', '${JSON.stringify(options)}'::jsonb)`, "create-queue");
311
- }
312
- function deleteQueue(schema, name) {
313
- return locked(schema, `SELECT ${schema}.delete_queue('${name}')`, "delete-queue");
314
- }
315
- function createPrimaryKeyJob(schema) {
316
- return `ALTER TABLE ${schema}.job ADD PRIMARY KEY (name, id)`;
317
- }
318
- function createQueueForeignKeyJob(schema) {
319
- return `ALTER TABLE ${schema}.job ADD CONSTRAINT q_fkey FOREIGN KEY (name) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED`;
320
- }
321
- function createQueueForeignKeyJobDeadLetter(schema) {
322
- return `ALTER TABLE ${schema}.job ADD CONSTRAINT dlq_fkey FOREIGN KEY (dead_letter) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED`;
323
- }
324
- function createIndexJobPolicyShort(schema) {
325
- return `CREATE UNIQUE INDEX job_i1 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state = '${JOB_STATES.created}' AND policy = '${QUEUE_POLICIES.short}'`;
326
- }
327
- function createIndexJobPolicySingleton(schema) {
328
- return `CREATE UNIQUE INDEX job_i2 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state = '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.singleton}'`;
329
- }
330
- function createIndexJobPolicyStately(schema) {
331
- return `CREATE UNIQUE INDEX job_i3 ON ${schema}.job (name, state, COALESCE(singleton_key, '')) WHERE state <= '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.stately}'`;
332
- }
333
- function createIndexJobThrottle(schema) {
334
- return `CREATE UNIQUE INDEX job_i4 ON ${schema}.job (name, singleton_on, COALESCE(singleton_key, '')) WHERE state <> '${JOB_STATES.cancelled}' AND singleton_on IS NOT NULL`;
335
- }
336
- function createIndexJobFetch(schema) {
337
- return `CREATE INDEX job_i5 ON ${schema}.job (name, start_after) INCLUDE (priority, created_on, id) WHERE state < '${JOB_STATES.active}'`;
338
- }
339
- function createIndexJobPolicyExclusive(schema) {
340
- return `CREATE UNIQUE INDEX job_i6 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state <= '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.exclusive}'`;
341
- }
342
- function trySetQueueMonitorTime(schema, queues, seconds) {
343
- return trySetQueueTimestamp(schema, queues, "monitor_on", seconds);
344
- }
345
- function trySetQueueDeletionTime(schema, queues, seconds) {
346
- return trySetQueueTimestamp(schema, queues, "maintain_on", seconds);
347
- }
348
- function trySetCronTime(schema, seconds) {
349
- return trySetTimestamp(schema, "cron_on", seconds);
350
- }
351
- function trySetTimestamp(schema, column, seconds) {
352
- return `
353
- UPDATE ${schema}.version
354
- SET ${column} = now()
355
- WHERE EXTRACT( EPOCH FROM (now() - COALESCE(${column}, now() - interval '1 week') ) ) > ${seconds}
356
- RETURNING true
357
- `;
358
- }
359
- function trySetQueueTimestamp(schema, queues, column, seconds) {
360
- return `
361
- UPDATE ${schema}.queue
362
- SET ${column} = now()
363
- WHERE name IN(${getQueueInClause(queues)})
364
- AND EXTRACT( EPOCH FROM (now() - COALESCE(${column}, now() - interval '1 week') ) ) > ${seconds}
365
- RETURNING name
366
- `;
367
- }
368
- function updateQueue(schema, { deadLetter } = {}) {
369
- return `
370
- WITH options as (SELECT $2::jsonb as data)
371
- UPDATE ${schema}.queue SET
372
- retry_limit = COALESCE((o.data->>'retryLimit')::int, retry_limit),
373
- retry_delay = COALESCE((o.data->>'retryDelay')::int, retry_delay),
374
- retry_backoff = COALESCE((o.data->>'retryBackoff')::bool, retry_backoff),
375
- retry_delay_max = CASE WHEN o.data ? 'retryDelayMax'
376
- THEN (o.data->>'retryDelayMax')::int
377
- ELSE retry_delay_max END,
378
- expire_seconds = COALESCE((o.data->>'expireInSeconds')::int, expire_seconds),
379
- retention_seconds = COALESCE((o.data->>'retentionSeconds')::int, retention_seconds),
380
- deletion_seconds = COALESCE((o.data->>'deleteAfterSeconds')::int, deletion_seconds),
381
- warning_queued = COALESCE((o.data->>'warningQueueSize')::int, warning_queued),
382
- ${deadLetter === void 0 ? "" : `dead_letter = CASE WHEN '${deadLetter}' IS DISTINCT FROM dead_letter THEN '${deadLetter}' ELSE dead_letter END,`}
383
- updated_on = now()
384
- FROM options o
385
- WHERE name = $1
386
- `;
387
- }
388
- function getQueues(schema, names) {
389
- return `
390
- SELECT
391
- q.name,
392
- q.policy,
393
- q.retry_limit as "retryLimit",
394
- q.retry_delay as "retryDelay",
395
- q.retry_backoff as "retryBackoff",
396
- q.retry_delay_max as "retryDelayMax",
397
- q.expire_seconds as "expireInSeconds",
398
- q.retention_seconds as "retentionSeconds",
399
- q.deletion_seconds as "deleteAfterSeconds",
400
- q.partition,
401
- q.dead_letter as "deadLetter",
402
- q.deferred_count as "deferredCount",
403
- q.warning_queued as "warningQueueSize",
404
- q.queued_count as "queuedCount",
405
- q.active_count as "activeCount",
406
- q.total_count as "totalCount",
407
- q.singletons_active as "singletonsActive",
408
- q.table_name as "table",
409
- q.created_on as "createdOn",
410
- q.updated_on as "updatedOn"
411
- FROM ${schema}.queue q
412
- ${names ? `WHERE q.name IN (${names.map((i) => `'${i}'`)})` : ""}
413
- `;
414
- }
415
- function deleteJobsById(schema, table) {
416
- return `
417
- WITH results as (
418
- DELETE FROM ${schema}.${table}
419
- WHERE name = $1
420
- AND id IN (SELECT UNNEST($2::uuid[]))
421
- RETURNING 1
422
- )
423
- SELECT COUNT(*) from results
424
- `;
425
- }
426
- function deleteQueuedJobs(schema, table) {
427
- return `DELETE from ${schema}.${table} WHERE name = $1 and state < '${JOB_STATES.active}'`;
428
- }
429
- function deleteStoredJobs(schema, table) {
430
- return `DELETE from ${schema}.${table} WHERE name = $1 and state > '${JOB_STATES.active}'`;
431
- }
432
- function truncateTable(schema, table) {
433
- return `TRUNCATE ${schema}.${table}`;
434
- }
435
- function deleteAllJobs(schema, table) {
436
- return `DELETE from ${schema}.${table} WHERE name = $1`;
437
- }
438
- function getSchedules(schema) {
439
- return `SELECT * FROM ${schema}.schedule`;
440
- }
441
- function getSchedulesByQueue(schema) {
442
- return `SELECT * FROM ${schema}.schedule WHERE name = $1 AND COALESCE(key, '') = $2`;
443
- }
444
- function schedule(schema) {
445
- return `
446
- INSERT INTO ${schema}.schedule (name, key, cron, timezone, data, options)
447
- VALUES ($1, $2, $3, $4, $5, $6)
448
- ON CONFLICT (name, key) DO UPDATE SET
449
- cron = EXCLUDED.cron,
450
- timezone = EXCLUDED.timezone,
451
- data = EXCLUDED.data,
452
- options = EXCLUDED.options,
453
- updated_on = now()
454
- `;
455
- }
456
- function unschedule(schema) {
457
- return `
458
- DELETE FROM ${schema}.schedule
459
- WHERE name = $1
460
- AND COALESCE(key, '') = $2
461
- `;
462
- }
463
- function subscribe(schema) {
464
- return `
465
- INSERT INTO ${schema}.subscription (event, name)
466
- VALUES ($1, $2)
467
- ON CONFLICT (event, name) DO UPDATE SET
468
- event = EXCLUDED.event,
469
- name = EXCLUDED.name,
470
- updated_on = now()
471
- `;
472
- }
473
- function unsubscribe(schema) {
474
- return `
475
- DELETE FROM ${schema}.subscription
476
- WHERE event = $1 and name = $2
477
- `;
478
- }
479
- function getQueuesForEvent(schema) {
480
- return `
481
- SELECT name FROM ${schema}.subscription
482
- WHERE event = $1
483
- `;
484
- }
485
- function getTime() {
486
- return "SELECT round(date_part('epoch', now()) * 1000) as time";
487
- }
488
- function getVersion(schema) {
489
- return `SELECT version from ${schema}.version`;
490
- }
491
- function setVersion(schema, version) {
492
- return `UPDATE ${schema}.version SET version = '${version}'`;
493
- }
494
- function versionTableExists(schema) {
495
- return `SELECT to_regclass('${schema}.version') as name`;
496
- }
497
- function insertVersion(schema, version) {
498
- return `INSERT INTO ${schema}.version(version) VALUES ('${version}')`;
499
- }
500
- function fetchNextJob({ schema, table, name, policy, limit, includeMetadata, priority = true, ignoreStartAfter = false, ignoreSingletons = null }) {
501
- const singletonFetch = limit > 1 && (policy === QUEUE_POLICIES.singleton || policy === QUEUE_POLICIES.stately);
502
- const cte = singletonFetch ? "grouped" : "next";
503
- return `
504
- WITH next as (
505
- SELECT id ${singletonFetch ? ", singleton_key" : ""}
506
- FROM ${schema}.${table}
507
- WHERE name = '${name}'
508
- AND state < '${JOB_STATES.active}'
509
- ${ignoreStartAfter ? "" : "AND start_after < now()"}
510
- ${ignoreSingletons != null && ignoreSingletons?.length > 0 ? `AND singleton_key NOT IN (${ignoreSingletons.map((i) => `'${i}'`).join()})` : ""}
511
- ORDER BY ${priority ? "priority desc, " : ""}created_on, id
512
- LIMIT ${limit}
513
- FOR UPDATE SKIP LOCKED
514
- )
515
- ${singletonFetch ? ", grouped as ( SELECT id, row_number() OVER (PARTITION BY singleton_key) FROM next)" : ""}
516
- UPDATE ${schema}.${table} j SET
517
- state = '${JOB_STATES.active}',
518
- started_on = now(),
519
- retry_count = CASE WHEN started_on IS NOT NULL THEN retry_count + 1 ELSE retry_count END
520
- FROM ${cte}
521
- WHERE name = '${name}' AND j.id = ${cte}.id
522
- ${singletonFetch ? ` AND ${cte}.row_number = 1` : ""}
523
- RETURNING j.${includeMetadata ? JOB_COLUMNS_ALL : JOB_COLUMNS_MIN}
524
- `;
525
- }
526
- function completeJobs(schema, table) {
527
- return `
528
- WITH results AS (
529
- UPDATE ${schema}.${table}
530
- SET completed_on = now(),
531
- state = '${JOB_STATES.completed}',
532
- output = $3::jsonb
533
- WHERE name = $1
534
- AND id IN (SELECT UNNEST($2::uuid[]))
535
- AND state = '${JOB_STATES.active}'
536
- RETURNING *
537
- )
538
- SELECT COUNT(*) FROM results
539
- `;
540
- }
541
- function cancelJobs(schema, table) {
542
- return `
543
- WITH results as (
544
- UPDATE ${schema}.${table}
545
- SET completed_on = now(),
546
- state = '${JOB_STATES.cancelled}'
547
- WHERE name = $1
548
- AND id IN (SELECT UNNEST($2::uuid[]))
549
- AND state < '${JOB_STATES.completed}'
550
- RETURNING 1
551
- )
552
- SELECT COUNT(*) from results
553
- `;
554
- }
555
- function resumeJobs(schema, table) {
556
- return `
557
- WITH results as (
558
- UPDATE ${schema}.${table}
559
- SET completed_on = NULL,
560
- state = '${JOB_STATES.created}'
561
- WHERE name = $1
562
- AND id IN (SELECT UNNEST($2::uuid[]))
563
- AND state = '${JOB_STATES.cancelled}'
564
- RETURNING 1
565
- )
566
- SELECT COUNT(*) from results
567
- `;
568
- }
569
- function insertJobs(schema, { table, name, returnId = true }) {
570
- return `
571
- INSERT INTO ${schema}.${table} (
572
- id,
573
- name,
574
- data,
575
- priority,
576
- start_after,
577
- singleton_key,
578
- singleton_on,
579
- expire_seconds,
580
- deletion_seconds,
581
- keep_until,
582
- retry_limit,
583
- retry_delay,
584
- retry_backoff,
585
- retry_delay_max,
586
- policy,
587
- dead_letter
588
- )
589
- SELECT
590
- COALESCE(id, gen_random_uuid()) as id,
591
- '${name}' as name,
592
- data,
593
- COALESCE(priority, 0) as priority,
594
- j.start_after,
595
- "singletonKey",
596
- CASE
597
- WHEN "singletonSeconds" IS NOT NULL THEN 'epoch'::timestamp + '1s'::interval * ("singletonSeconds" * floor(( date_part('epoch', now()) + COALESCE("singletonOffset",0)) / "singletonSeconds" ))
598
- ELSE NULL
599
- END as singleton_on,
600
- COALESCE("expireInSeconds", q.expire_seconds) as expire_seconds,
601
- COALESCE("deleteAfterSeconds", q.deletion_seconds) as deletion_seconds,
602
- j.start_after + (COALESCE("retentionSeconds", q.retention_seconds) * interval '1s') as keep_until,
603
- COALESCE("retryLimit", q.retry_limit) as retry_limit,
604
- COALESCE("retryDelay", q.retry_delay) as retry_delay,
605
- COALESCE("retryBackoff", q.retry_backoff, false) as retry_backoff,
606
- COALESCE("retryDelayMax", q.retry_delay_max) as retry_delay_max,
607
- q.policy,
608
- q.dead_letter
609
- FROM (
610
- SELECT *,
611
- CASE
612
- WHEN right("startAfter", 1) = 'Z' THEN CAST("startAfter" as timestamp with time zone)
613
- ELSE now() + CAST(COALESCE("startAfter",'0') as interval)
614
- END as start_after
615
- FROM json_to_recordset($1::json) as x (
616
- id uuid,
617
- priority integer,
618
- data jsonb,
619
- "startAfter" text,
620
- "retryLimit" integer,
621
- "retryDelay" integer,
622
- "retryDelayMax" integer,
623
- "retryBackoff" boolean,
624
- "singletonKey" text,
625
- "singletonSeconds" integer,
626
- "singletonOffset" integer,
627
- "expireInSeconds" integer,
628
- "deleteAfterSeconds" integer,
629
- "retentionSeconds" integer
630
- )
631
- ) j
632
- JOIN ${schema}.queue q ON q.name = '${name}'
633
- ON CONFLICT DO NOTHING
634
- ${returnId ? "RETURNING id" : ""}
635
- `;
636
- }
637
- function failJobsById(schema, table) {
638
- return failJobs(schema, table, `name = $1 AND id IN (SELECT UNNEST($2::uuid[])) AND state < '${JOB_STATES.completed}'`, "$3::jsonb");
639
- }
640
- function failJobsByTimeout(schema, table, queues) {
641
- return locked(schema, failJobs(schema, table, `state = '${JOB_STATES.active}'
642
- AND (started_on + expire_seconds * interval '1s') < now()
643
- AND name IN (${getQueueInClause(queues)})`, "'{ \"value\": { \"message\": \"job timed out\" } }'::jsonb"), table + "failJobsByTimeout");
644
- }
645
- function failJobs(schema, table, where, output) {
646
- return `
647
- WITH deleted_jobs AS (
648
- DELETE FROM ${schema}.${table}
649
- WHERE ${where}
650
- RETURNING *
651
- ),
652
- retried_jobs AS (
653
- INSERT INTO ${schema}.${table} (
654
- id,
655
- name,
656
- priority,
657
- data,
658
- state,
659
- retry_limit,
660
- retry_count,
661
- retry_delay,
662
- retry_backoff,
663
- retry_delay_max,
664
- start_after,
665
- started_on,
666
- singleton_key,
667
- singleton_on,
668
- expire_seconds,
669
- deletion_seconds,
670
- created_on,
671
- completed_on,
672
- keep_until,
673
- policy,
674
- output,
675
- dead_letter
676
- )
677
- SELECT
678
- id,
679
- name,
680
- priority,
681
- data,
682
- CASE
683
- WHEN retry_count < retry_limit THEN '${JOB_STATES.retry}'::${schema}.job_state
684
- ELSE '${JOB_STATES.failed}'::${schema}.job_state
685
- END as state,
686
- retry_limit,
687
- retry_count,
688
- retry_delay,
689
- retry_backoff,
690
- retry_delay_max,
691
- CASE WHEN retry_count = retry_limit THEN start_after
692
- WHEN NOT retry_backoff THEN now() + retry_delay * interval '1'
693
- ELSE now() + LEAST(
694
- retry_delay_max,
695
- retry_delay + (
696
- 2 ^ LEAST(16, retry_count + 1) / 2 +
697
- 2 ^ LEAST(16, retry_count + 1) / 2 * random()
698
- )
699
- ) * interval '1s'
700
- END as start_after,
701
- started_on,
702
- singleton_key,
703
- singleton_on,
704
- expire_seconds,
705
- deletion_seconds,
706
- created_on,
707
- CASE WHEN retry_count < retry_limit THEN NULL ELSE now() END as completed_on,
708
- keep_until,
709
- policy,
710
- ${output},
711
- dead_letter
712
- FROM deleted_jobs
713
- ON CONFLICT DO NOTHING
714
- RETURNING *
715
- ),
716
- failed_jobs as (
717
- INSERT INTO ${schema}.${table} (
718
- id,
719
- name,
720
- priority,
721
- data,
722
- state,
723
- retry_limit,
724
- retry_count,
725
- retry_delay,
726
- retry_backoff,
727
- retry_delay_max,
728
- start_after,
729
- started_on,
730
- singleton_key,
731
- singleton_on,
732
- expire_seconds,
733
- deletion_seconds,
734
- created_on,
735
- completed_on,
736
- keep_until,
737
- policy,
738
- output,
739
- dead_letter
740
- )
741
- SELECT
742
- id,
743
- name,
744
- priority,
745
- data,
746
- '${JOB_STATES.failed}'::${schema}.job_state as state,
747
- retry_limit,
748
- retry_count,
749
- retry_delay,
750
- retry_backoff,
751
- retry_delay_max,
752
- start_after,
753
- started_on,
754
- singleton_key,
755
- singleton_on,
756
- expire_seconds,
757
- deletion_seconds,
758
- created_on,
759
- now() as completed_on,
760
- keep_until,
761
- policy,
762
- ${output},
763
- dead_letter
764
- FROM deleted_jobs
765
- WHERE id NOT IN (SELECT id from retried_jobs)
766
- RETURNING *
767
- ),
768
- results as (
769
- SELECT * FROM retried_jobs
770
- UNION ALL
771
- SELECT * FROM failed_jobs
772
- ),
773
- dlq_jobs as (
774
- INSERT INTO ${schema}.job (name, data, output, retry_limit, retry_backoff, retry_delay, keep_until, deletion_seconds)
775
- SELECT
776
- r.dead_letter,
777
- data,
778
- output,
779
- q.retry_limit,
780
- q.retry_backoff,
781
- q.retry_delay,
782
- now() + q.retention_seconds * interval '1s',
783
- q.deletion_seconds
784
- FROM results r
785
- JOIN ${schema}.queue q ON q.name = r.dead_letter
786
- WHERE state = '${JOB_STATES.failed}'
787
- )
788
- SELECT COUNT(*) FROM results
789
- `;
790
- }
791
- function deletion(schema, table, queues) {
792
- return locked(schema, `
793
- DELETE FROM ${schema}.${table}
794
- WHERE name IN (${getQueueInClause(queues)})
795
- AND
796
- (
797
- completed_on + deletion_seconds * interval '1s' < now()
798
- OR
799
- (state < '${JOB_STATES.active}' AND keep_until < now())
800
- )
801
- `, table + "deletion");
802
- }
803
- function retryJobs(schema, table) {
804
- return `
805
- WITH results as (
806
- UPDATE ${schema}.job
807
- SET state = '${JOB_STATES.retry}',
808
- retry_limit = retry_limit + 1
809
- WHERE name = $1
810
- AND id IN (SELECT UNNEST($2::uuid[]))
811
- AND state = '${JOB_STATES.failed}'
812
- RETURNING 1
813
- )
814
- SELECT COUNT(*) from results
815
- `;
816
- }
817
- function getQueueStats(schema, table, queues) {
818
- return `
819
- SELECT
820
- name,
821
- (count(*) FILTER (WHERE start_after > now()))::int as "deferredCount",
822
- (count(*) FILTER (WHERE state < '${JOB_STATES.active}'))::int as "queuedCount",
823
- (count(*) FILTER (WHERE state = '${JOB_STATES.active}'))::int as "activeCount",
824
- count(*)::int as "totalCount",
825
- array_agg(singleton_key) FILTER (WHERE policy IN ('${QUEUE_POLICIES.singleton}','${QUEUE_POLICIES.stately}') AND state = '${JOB_STATES.active}') as "singletonsActive"
826
- FROM ${schema}.${table}
827
- WHERE name IN (${getQueueInClause(queues)})
828
- GROUP BY 1
829
- `;
830
- }
831
- function cacheQueueStats(schema, table, queues) {
832
- return locked(schema, `
833
- WITH stats AS (${getQueueStats(schema, table, queues)})
834
- UPDATE ${schema}.queue SET
835
- deferred_count = "deferredCount",
836
- queued_count = "queuedCount",
837
- active_count = "activeCount",
838
- total_count = "totalCount",
839
- singletons_active = "singletonsActive"
840
- FROM stats
841
- WHERE queue.name = stats.name
842
- RETURNING
843
- queue.name,
844
- "queuedCount",
845
- warning_queued as "warningQueueSize"
846
- `, "queue-stats");
847
- }
848
- function locked(schema, query, key) {
849
- if (Array.isArray(query)) query = query.join(";\n");
850
- return `
851
- BEGIN;
852
- SET LOCAL lock_timeout = 30000;
853
- SET LOCAL idle_in_transaction_session_timeout = 30000;
854
- ${advisoryLock(schema, key)};
855
- ${query};
856
- COMMIT;
857
- `;
858
- }
859
- function advisoryLock(schema, key) {
860
- return `SELECT pg_advisory_xact_lock(
861
- ('x' || encode(sha224((current_database() || '.pgboss.${schema}${key || ""}')::bytea), 'hex'))::bit(64)::bigint
862
- )`;
863
- }
864
- function assertMigration(schema, version) {
865
- return `SELECT version::int/(version::int-${version}) from ${schema}.version`;
866
- }
867
- function getJobById(schema, table) {
868
- return `SELECT ${JOB_COLUMNS_ALL} FROM ${schema}.${table} WHERE name = $1 AND id = $2`;
869
- }
870
- function getQueueInClause(queues) {
871
- return queues.map((i) => `'${i}'`).join(",");
872
- }
873
-
874
- //#endregion
875
- //#region src/attorney.ts
876
- const POLICY = {
877
- MAX_EXPIRATION_HOURS: 24,
878
- MIN_POLLING_INTERVAL_MS: 500,
879
- MAX_RETENTION_DAYS: 365
880
- };
881
- function assertObjectName(value, name = "Name") {
882
- assert(/^[\w.-]+$/.test(value), `${name} can only contain alphanumeric characters, underscores, hyphens, or periods`);
883
- }
884
- function validateQueueArgs(config = {}) {
885
- assert(!("deadLetter" in config) || config.deadLetter === null || typeof config.deadLetter === "string", "deadLetter must be a string");
886
- if (config.deadLetter) assertObjectName(config.deadLetter, "deadLetter");
887
- validateRetryConfig(config);
888
- validateExpirationConfig(config);
889
- validateRetentionConfig(config);
890
- validateDeletionConfig(config);
891
- }
892
- function checkSendArgs(args) {
893
- let name, data, options;
894
- if (typeof args[0] === "string") {
895
- name = args[0];
896
- data = args[1];
897
- assert(typeof data !== "function", "send() cannot accept a function as the payload. Did you intend to use work()?");
898
- options = args[2];
899
- } else if (typeof args[0] === "object") {
900
- assert(args.length === 1, "send object API only accepts 1 argument");
901
- const job = args[0];
902
- assert(job, "boss requires all jobs to have a name");
903
- name = job.name;
904
- data = job.data;
905
- options = job.options;
906
- }
907
- options = options || {};
908
- assert(name, "boss requires all jobs to have a queue name");
909
- assert(typeof options === "object", "options should be an object");
910
- options = { ...options };
911
- assert(!("priority" in options) || Number.isInteger(options.priority), "priority must be an integer");
912
- options.priority = options.priority || 0;
913
- options.startAfter = options.startAfter instanceof Date && typeof options.startAfter.toISOString === "function" ? options.startAfter.toISOString() : +options.startAfter > 0 ? "" + options.startAfter : typeof options.startAfter === "string" ? options.startAfter : void 0;
914
- validateRetryConfig(options);
915
- validateExpirationConfig(options);
916
- validateRetentionConfig(options);
917
- validateDeletionConfig(options);
918
- return {
919
- name,
920
- data,
921
- options
922
- };
923
- }
924
- function checkWorkArgs(name, args) {
925
- let options, callback;
926
- assert(name, "queue name is required");
927
- if (args.length === 1) {
928
- callback = args[0];
929
- options = {};
930
- } else if (args.length > 1) {
931
- options = args[0] || {};
932
- callback = args[1];
933
- }
934
- assert(typeof callback === "function", "expected callback to be a function");
935
- assert(typeof options === "object", "expected config to be an object");
936
- options = { ...options };
937
- applyPollingInterval(options);
938
- assert(!("batchSize" in options) || Number.isInteger(options.batchSize) && options.batchSize >= 1, "batchSize must be an integer > 0");
939
- assert(!("includeMetadata" in options) || typeof options.includeMetadata === "boolean", "includeMetadata must be a boolean");
940
- assert(!("priority" in options) || typeof options.priority === "boolean", "priority must be a boolean");
941
- return {
942
- options,
943
- callback
944
- };
945
- }
946
- function checkFetchArgs(name, options) {
947
- assert(name, "missing queue name");
948
- assert(!("batchSize" in options) || Number.isInteger(options.batchSize) && options.batchSize >= 1, "batchSize must be an integer > 0");
949
- assert(!("includeMetadata" in options) || typeof options.includeMetadata === "boolean", "includeMetadata must be a boolean");
950
- assert(!("priority" in options) || typeof options.priority === "boolean", "priority must be a boolean");
951
- assert(!("ignoreStartAfter" in options) || typeof options.ignoreStartAfter === "boolean", "ignoreStartAfter must be a boolean");
952
- }
953
- function getConfig(value) {
954
- assert(value && (typeof value === "object" || typeof value === "string"), "configuration assert: string or config object is required to connect to postgres");
955
- const config = typeof value === "string" ? { connectionString: value } : { ...value };
956
- config.schedule = "schedule" in config ? config.schedule : true;
957
- config.supervise = "supervise" in config ? config.supervise : true;
958
- config.migrate = "migrate" in config ? config.migrate : true;
959
- config.createSchema = "createSchema" in config ? config.createSchema : true;
960
- applySchemaConfig(config);
961
- applyOpsConfig(config);
962
- applyScheduleConfig(config);
963
- validateWarningConfig(config);
964
- return config;
965
- }
966
- function applySchemaConfig(config) {
967
- if (config.schema) assertPostgresObjectName(config.schema);
968
- config.schema = config.schema || DEFAULT_SCHEMA;
969
- }
970
- function validateWarningConfig(config) {
971
- assert(!("warningQueueSize" in config) || config.warningQueueSize >= 1, "configuration assert: warningQueueSize must be at least 1");
972
- assert(!("warningSlowQuerySeconds" in config) || config.warningSlowQuerySeconds >= 1, "configuration assert: warningSlowQuerySeconds must be at least 1");
973
- }
974
- function assertPostgresObjectName(name) {
975
- assert(typeof name === "string", "Name must be a string");
976
- assert(name.length <= 50, "Name cannot exceed 50 characters");
977
- assert(!/\W/.test(name), "Name can only contain alphanumeric characters or underscores");
978
- assert(!/^\d/.test(name), "Name cannot start with a number");
979
- }
980
- function assertQueueName(name) {
981
- assert(name, "Name is required");
982
- assert(typeof name === "string", "Name must be a string");
983
- assertObjectName(name);
984
- }
985
- function assertKey(key) {
986
- if (!key) return;
987
- assert(typeof key === "string", "Key must be a string");
988
- assertObjectName(key, "Key");
989
- }
990
- function validateRetentionConfig(config) {
991
- assert(!("retentionSeconds" in config) || config.retentionSeconds >= 1, "configuration assert: retentionSeconds must be at least every second");
992
- }
993
- function validateExpirationConfig(config) {
994
- assert(!("expireInSeconds" in config) || config.expireInSeconds >= 1, "configuration assert: expireInSeconds must be at least every second");
995
- assert(!config.expireInSeconds || config.expireInSeconds / 60 / 60 < POLICY.MAX_EXPIRATION_HOURS, `configuration assert: expiration cannot exceed ${POLICY.MAX_EXPIRATION_HOURS} hours`);
996
- }
997
- function validateRetryConfig(config) {
998
- assert(!("retryDelay" in config) || Number.isInteger(config.retryDelay) && config.retryDelay >= 0, "retryDelay must be an integer >= 0");
999
- assert(!("retryLimit" in config) || Number.isInteger(config.retryLimit) && config.retryLimit >= 0, "retryLimit must be an integer >= 0");
1000
- assert(!("retryBackoff" in config) || config.retryBackoff === true || config.retryBackoff === false, "retryBackoff must be either true or false");
1001
- assert(!("retryDelayMax" in config) || config.retryDelayMax === null || config.retryBackoff === true, "retryDelayMax can only be set if retryBackoff is true");
1002
- assert(!("retryDelayMax" in config) || config.retryDelayMax === null || Number.isInteger(config.retryDelayMax) && config.retryDelayMax >= 0, "retryDelayMax must be an integer >= 0");
1003
- }
1004
- function applyPollingInterval(config) {
1005
- assert(!("pollingIntervalSeconds" in config) || config.pollingIntervalSeconds >= POLICY.MIN_POLLING_INTERVAL_MS / 1e3, `configuration assert: pollingIntervalSeconds must be at least every ${POLICY.MIN_POLLING_INTERVAL_MS}ms`);
1006
- config.pollingInterval = "pollingIntervalSeconds" in config ? config.pollingIntervalSeconds * 1e3 : 2e3;
1007
- }
1008
- function applyOpsConfig(config) {
1009
- assert(!("superviseIntervalSeconds" in config) || config.superviseIntervalSeconds >= 1, "configuration assert: superviseIntervalSeconds must be at least every second");
1010
- config.superviseIntervalSeconds = config.superviseIntervalSeconds || 60;
1011
- assert(config.superviseIntervalSeconds / 60 / 60 <= POLICY.MAX_EXPIRATION_HOURS, `configuration assert: superviseIntervalSeconds cannot exceed ${POLICY.MAX_EXPIRATION_HOURS} hours`);
1012
- assert(!("maintenanceIntervalSeconds" in config) || config.maintenanceIntervalSeconds >= 1, "configuration assert: maintenanceIntervalSeconds must be at least every second");
1013
- config.maintenanceIntervalSeconds = config.maintenanceIntervalSeconds || POLICY.MAX_EXPIRATION_HOURS * 60 * 60;
1014
- assert(config.maintenanceIntervalSeconds / 60 / 60 <= POLICY.MAX_EXPIRATION_HOURS, `configuration assert: maintenanceIntervalSeconds cannot exceed ${POLICY.MAX_EXPIRATION_HOURS} hours`);
1015
- assert(!("monitorIntervalSeconds" in config) || config.monitorIntervalSeconds >= 1, "configuration assert: monitorIntervalSeconds must be at least every second");
1016
- config.monitorIntervalSeconds = config.monitorIntervalSeconds || 60;
1017
- assert(config.monitorIntervalSeconds / 60 / 60 <= POLICY.MAX_EXPIRATION_HOURS, `configuration assert: monitorIntervalSeconds cannot exceed ${POLICY.MAX_EXPIRATION_HOURS} hours`);
1018
- assert(!("queueCacheIntervalSeconds" in config) || config.queueCacheIntervalSeconds >= 1, "configuration assert: queueCacheIntervalSeconds must be at least every second");
1019
- config.queueCacheIntervalSeconds = config.queueCacheIntervalSeconds || 60;
1020
- assert(config.queueCacheIntervalSeconds / 60 / 60 <= POLICY.MAX_EXPIRATION_HOURS, `configuration assert: queueCacheIntervalSeconds cannot exceed ${POLICY.MAX_EXPIRATION_HOURS} hours`);
1021
- }
1022
- function validateDeletionConfig(config) {
1023
- assert(!("deleteAfterSeconds" in config) || config.deleteAfterSeconds >= 1, "configuration assert: deleteAfterSeconds must be at least every second");
1024
- }
1025
- function applyScheduleConfig(config) {
1026
- assert(!("clockMonitorIntervalSeconds" in config) || config.clockMonitorIntervalSeconds >= 1 && config.clockMonitorIntervalSeconds <= 600, "configuration assert: clockMonitorIntervalSeconds must be between 1 second and 10 minutes");
1027
- config.clockMonitorIntervalSeconds = config.clockMonitorIntervalSeconds || 600;
1028
- assert(!("cronMonitorIntervalSeconds" in config) || config.cronMonitorIntervalSeconds >= 1 && config.cronMonitorIntervalSeconds <= 45, "configuration assert: cronMonitorIntervalSeconds must be between 1 and 45 seconds");
1029
- config.cronMonitorIntervalSeconds = config.cronMonitorIntervalSeconds || 30;
1030
- assert(!("cronWorkerIntervalSeconds" in config) || config.cronWorkerIntervalSeconds >= 1 && config.cronWorkerIntervalSeconds <= 45, "configuration assert: cronWorkerIntervalSeconds must be between 1 and 45 seconds");
1031
- config.cronWorkerIntervalSeconds = config.cronWorkerIntervalSeconds || 5;
1032
- }
1033
-
1034
- //#endregion
1035
- //#region src/migrationStore.ts
1036
- function flatten(schema, commands, version) {
1037
- commands.unshift(assertMigration(schema, version));
1038
- commands.push(setVersion(schema, version));
1039
- return locked(schema, commands);
1040
- }
1041
- function rollback(schema, version, migrations) {
1042
- migrations = migrations || getAll(schema);
1043
- const result = migrations.find((i) => i.version === version);
1044
- assert(result, `Version ${version} not found.`);
1045
- return flatten(schema, result.uninstall || [], result.previous);
1046
- }
1047
- function next(schema, version, migrations) {
1048
- migrations = migrations || getAll(schema);
1049
- const result = migrations.find((i) => i.previous === version);
1050
- assert(result, `Version ${version} not found.`);
1051
- return flatten(schema, result.install, result.version);
1052
- }
1053
- function migrate(schema, version, migrations) {
1054
- migrations = migrations || getAll(schema);
1055
- const result = migrations.filter((i) => i.previous >= version).sort((a, b) => a.version - b.version).reduce((acc, i) => {
1056
- acc.install = acc.install.concat(i.install);
1057
- acc.version = i.version;
1058
- return acc;
1059
- }, {
1060
- install: [],
1061
- version
1062
- });
1063
- assert(result.install.length > 0, `Version ${version} not found.`);
1064
- return flatten(schema, result.install, result.version);
1065
- }
1066
- function getAll(schema) {
1067
- return [{
1068
- release: "11.1.0",
1069
- version: 26,
1070
- previous: 25,
1071
- install: [`
1072
- CREATE OR REPLACE FUNCTION ${schema}.create_queue(queue_name text, options jsonb)
1073
- RETURNS VOID AS
1074
- $$
1075
- DECLARE
1076
- tablename varchar := CASE WHEN options->>'partition' = 'true'
1077
- THEN 'j' || encode(sha224(queue_name::bytea), 'hex')
1078
- ELSE 'job_common'
1079
- END;
1080
- queue_created_on timestamptz;
1081
- BEGIN
1082
-
1083
- WITH q as (
1084
- INSERT INTO ${schema}.queue (
1085
- name,
1086
- policy,
1087
- retry_limit,
1088
- retry_delay,
1089
- retry_backoff,
1090
- retry_delay_max,
1091
- expire_seconds,
1092
- retention_seconds,
1093
- deletion_seconds,
1094
- warning_queued,
1095
- dead_letter,
1096
- partition,
1097
- table_name
1098
- )
1099
- VALUES (
1100
- queue_name,
1101
- options->>'policy',
1102
- COALESCE((options->>'retryLimit')::int, 2),
1103
- COALESCE((options->>'retryDelay')::int, 0),
1104
- COALESCE((options->>'retryBackoff')::bool, false),
1105
- (options->>'retryDelayMax')::int,
1106
- COALESCE((options->>'expireInSeconds')::int, 900),
1107
- COALESCE((options->>'retentionSeconds')::int, 1209600),
1108
- COALESCE((options->>'deleteAfterSeconds')::int, 604800),
1109
- COALESCE((options->>'warningQueueSize')::int, 0),
1110
- options->>'deadLetter',
1111
- COALESCE((options->>'partition')::bool, false),
1112
- tablename
1113
- )
1114
- ON CONFLICT DO NOTHING
1115
- RETURNING created_on
1116
- )
1117
- SELECT created_on into queue_created_on from q;
1118
-
1119
- IF queue_created_on IS NULL OR options->>'partition' IS DISTINCT FROM 'true' THEN
1120
- RETURN;
1121
- END IF;
1122
-
1123
- EXECUTE format('CREATE TABLE ${schema}.%I (LIKE ${schema}.job INCLUDING DEFAULTS)', tablename);
1124
-
1125
- EXECUTE format('ALTER TABLE ${schema}.%1$I ADD PRIMARY KEY (name, id)', tablename);
1126
- EXECUTE format('ALTER TABLE ${schema}.%1$I ADD CONSTRAINT q_fkey FOREIGN KEY (name) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED', tablename);
1127
- EXECUTE format('ALTER TABLE ${schema}.%1$I ADD CONSTRAINT dlq_fkey FOREIGN KEY (dead_letter) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED', tablename);
1128
-
1129
- EXECUTE format('CREATE INDEX %1$s_i5 ON ${schema}.%1$I (name, start_after) INCLUDE (priority, created_on, id) WHERE state < ''active''', tablename);
1130
- EXECUTE format('CREATE UNIQUE INDEX %1$s_i4 ON ${schema}.%1$I (name, singleton_on, COALESCE(singleton_key, '''')) WHERE state <> ''cancelled'' AND singleton_on IS NOT NULL', tablename);
1131
-
1132
- IF options->>'policy' = 'short' THEN
1133
- EXECUTE format('CREATE UNIQUE INDEX %1$s_i1 ON ${schema}.%1$I (name, COALESCE(singleton_key, '''')) WHERE state = ''created'' AND policy = ''short''', tablename);
1134
- ELSIF options->>'policy' = 'singleton' THEN
1135
- EXECUTE format('CREATE UNIQUE INDEX %1$s_i2 ON ${schema}.%1$I (name, COALESCE(singleton_key, '''')) WHERE state = ''active'' AND policy = ''singleton''', tablename);
1136
- ELSIF options->>'policy' = 'stately' THEN
1137
- EXECUTE format('CREATE UNIQUE INDEX %1$s_i3 ON ${schema}.%1$I (name, state, COALESCE(singleton_key, '''')) WHERE state <= ''active'' AND policy = ''stately''', tablename);
1138
- ELSIF options->>'policy' = 'exclusive' THEN
1139
- EXECUTE format('CREATE UNIQUE INDEX %1$s_i6 ON ${schema}.%1$I (name, COALESCE(singleton_key, '''')) WHERE state <= ''active'' AND policy = ''exclusive''', tablename);
1140
- END IF;
1141
-
1142
- EXECUTE format('ALTER TABLE ${schema}.%I ADD CONSTRAINT cjc CHECK (name=%L)', tablename, queue_name);
1143
- EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.%I FOR VALUES IN (%L)', tablename, queue_name);
1144
- END;
1145
- $$
1146
- LANGUAGE plpgsql;
1147
- `, `CREATE UNIQUE INDEX job_i6 ON ${schema}.job_common (name, COALESCE(singleton_key, '')) WHERE state <= 'active' AND policy = 'exclusive'`],
1148
- uninstall: [`DROP INDEX ${schema}.job_i6`]
1149
- }];
1150
- }
1151
-
1152
- //#endregion
1153
- //#region package.json
1154
- var pgboss = { "schema": 26 };
1155
-
1156
- //#endregion
1157
- //#region src/contractor.ts
1158
- const schemaVersion = pgboss.schema;
1159
- var Contractor = class {
1160
- static constructionPlans(schema = DEFAULT_SCHEMA, options = { createSchema: true }) {
1161
- return create(schema, schemaVersion, options);
1162
- }
1163
- static migrationPlans(schema = DEFAULT_SCHEMA, version = schemaVersion - 1) {
1164
- return migrate(schema, version);
1165
- }
1166
- static rollbackPlans(schema = DEFAULT_SCHEMA, version = schemaVersion) {
1167
- return rollback(schema, version);
1168
- }
1169
- config;
1170
- db;
1171
- migrations;
1172
- constructor(db, config) {
1173
- this.config = config;
1174
- this.db = db;
1175
- this.migrations = this.config.migrations || getAll(this.config.schema);
1176
- }
1177
- async schemaVersion() {
1178
- const result = await this.db.executeSql(getVersion(this.config.schema));
1179
- return result.rows.length ? parseInt(result.rows[0].version) : null;
1180
- }
1181
- async isInstalled() {
1182
- return !!(await this.db.executeSql(versionTableExists(this.config.schema))).rows[0].name;
1183
- }
1184
- async start() {
1185
- if (await this.isInstalled()) {
1186
- const version = await this.schemaVersion();
1187
- if (version !== null && schemaVersion > version) await this.migrate(version);
1188
- } else await this.create();
1189
- }
1190
- async check() {
1191
- if (!await this.isInstalled()) throw new Error("pg-boss is not installed");
1192
- if (schemaVersion !== await this.schemaVersion()) throw new Error("pg-boss database requires migrations");
1193
- }
1194
- async create() {
1195
- try {
1196
- const commands = create(this.config.schema, schemaVersion, this.config);
1197
- await this.db.executeSql(commands);
1198
- } catch (err) {
1199
- assert(err.message.includes(CREATE_RACE_MESSAGE), err);
1200
- }
1201
- }
1202
- async migrate(version) {
1203
- try {
1204
- const commands = migrate(this.config.schema, version, this.migrations);
1205
- await this.db.executeSql(commands);
1206
- } catch (err) {
1207
- assert(err.message.includes(MIGRATE_RACE_MESSAGE), err);
1208
- }
1209
- }
1210
- async next(version) {
1211
- const commands = next(this.config.schema, version, this.migrations);
1212
- await this.db.executeSql(commands);
1213
- }
1214
- async rollback(version) {
1215
- const commands = rollback(this.config.schema, version, this.migrations);
1216
- await this.db.executeSql(commands);
1217
- }
1218
- };
1219
- var contractor_default = Contractor;
1220
-
1221
- //#endregion
1222
- //#region node_modules/serialize-error/error-constructors.js
1223
- const list = [
1224
- Error,
1225
- EvalError,
1226
- RangeError,
1227
- ReferenceError,
1228
- SyntaxError,
1229
- TypeError,
1230
- URIError,
1231
- AggregateError,
1232
- globalThis.DOMException,
1233
- globalThis.AssertionError,
1234
- globalThis.SystemError
1235
- ].filter(Boolean).map((constructor) => [constructor.name, constructor]);
1236
- const errorConstructors = new Map(list);
1237
-
1238
- //#endregion
1239
- //#region node_modules/serialize-error/index.js
1240
- const errorProperties = [
1241
- {
1242
- property: "name",
1243
- enumerable: false
1244
- },
1245
- {
1246
- property: "message",
1247
- enumerable: false
1248
- },
1249
- {
1250
- property: "stack",
1251
- enumerable: false
1252
- },
1253
- {
1254
- property: "code",
1255
- enumerable: true
1256
- },
1257
- {
1258
- property: "cause",
1259
- enumerable: false
1260
- },
1261
- {
1262
- property: "errors",
1263
- enumerable: false
1264
- }
1265
- ];
1266
- const toJsonWasCalled = /* @__PURE__ */ new WeakSet();
1267
- const toJSON = (from) => {
1268
- toJsonWasCalled.add(from);
1269
- const json = from.toJSON();
1270
- toJsonWasCalled.delete(from);
1271
- return json;
1272
- };
1273
- const newError = (name) => {
1274
- const ErrorConstructor = errorConstructors.get(name) ?? Error;
1275
- return ErrorConstructor === AggregateError ? new ErrorConstructor([]) : new ErrorConstructor();
1276
- };
1277
- const destroyCircular = ({ from, seen, to, forceEnumerable, maxDepth, depth, useToJSON, serialize }) => {
1278
- if (!to) if (Array.isArray(from)) to = [];
1279
- else if (!serialize && isErrorLike(from)) to = newError(from.name);
1280
- else to = {};
1281
- seen.push(from);
1282
- if (depth >= maxDepth) return to;
1283
- if (useToJSON && typeof from.toJSON === "function" && !toJsonWasCalled.has(from)) return toJSON(from);
1284
- const continueDestroyCircular = (value) => destroyCircular({
1285
- from: value,
1286
- seen: [...seen],
1287
- forceEnumerable,
1288
- maxDepth,
1289
- depth,
1290
- useToJSON,
1291
- serialize
1292
- });
1293
- for (const [key, value] of Object.entries(from)) {
1294
- if (value && value instanceof Uint8Array && value.constructor.name === "Buffer") {
1295
- to[key] = "[object Buffer]";
1296
- continue;
1297
- }
1298
- if (value !== null && typeof value === "object" && typeof value.pipe === "function") {
1299
- to[key] = "[object Stream]";
1300
- continue;
1301
- }
1302
- if (typeof value === "function") continue;
1303
- if (!value || typeof value !== "object") {
1304
- try {
1305
- to[key] = value;
1306
- } catch {}
1307
- continue;
1308
- }
1309
- if (!seen.includes(from[key])) {
1310
- depth++;
1311
- to[key] = continueDestroyCircular(from[key]);
1312
- continue;
1313
- }
1314
- to[key] = "[Circular]";
1315
- }
1316
- if (serialize || to instanceof Error) {
1317
- for (const { property, enumerable } of errorProperties) if (from[property] !== void 0 && from[property] !== null) Object.defineProperty(to, property, {
1318
- value: isErrorLike(from[property]) || Array.isArray(from[property]) ? continueDestroyCircular(from[property]) : from[property],
1319
- enumerable: forceEnumerable ? true : enumerable,
1320
- configurable: true,
1321
- writable: true
1322
- });
1323
- }
1324
- return to;
1325
- };
1326
- function serializeError(value, options = {}) {
1327
- const { maxDepth = Number.POSITIVE_INFINITY, useToJSON = true } = options;
1328
- if (typeof value === "object" && value !== null) return destroyCircular({
1329
- from: value,
1330
- seen: [],
1331
- forceEnumerable: true,
1332
- maxDepth,
1333
- depth: 0,
1334
- useToJSON,
1335
- serialize: true
1336
- });
1337
- if (typeof value === "function") return `[Function: ${value.name || "anonymous"}]`;
1338
- return value;
1339
- }
1340
- function isErrorLike(value) {
1341
- return Boolean(value) && typeof value === "object" && typeof value.name === "string" && typeof value.message === "string" && typeof value.stack === "string";
1342
- }
1343
-
1344
- //#endregion
1345
- //#region src/timekeeper.ts
1346
- const QUEUES = { SEND_IT: "__pgboss__send-it" };
1347
- const EVENTS = {
1348
- error: "error",
1349
- schedule: "schedule",
1350
- warning: "warning"
1351
- };
1352
- const WARNINGS$1 = { CLOCK_SKEW: { message: "Warning: Clock skew between this instance and the database server. This will not break scheduling, but is emitted any time the skew exceeds 60 seconds." } };
1353
- var Timekeeper = class extends EventEmitter {
1354
- db;
1355
- config;
1356
- manager;
1357
- stopped = true;
1358
- cronMonitorInterval;
1359
- skewMonitorInterval;
1360
- timekeeping;
1361
- clockSkew = 0;
1362
- events = EVENTS;
1363
- constructor(db, manager, config) {
1364
- super();
1365
- this.db = db;
1366
- this.config = config;
1367
- this.manager = manager;
1368
- }
1369
- async start() {
1370
- this.stopped = false;
1371
- await this.cacheClockSkew();
1372
- await this.manager.createQueue(QUEUES.SEND_IT);
1373
- const options = {
1374
- pollingIntervalSeconds: this.config.cronWorkerIntervalSeconds,
1375
- batchSize: 50
1376
- };
1377
- await this.manager.work(QUEUES.SEND_IT, options, (jobs) => this.onSendIt(jobs));
1378
- setImmediate(() => this.onCron());
1379
- this.cronMonitorInterval = setInterval(async () => await this.onCron(), this.config.cronMonitorIntervalSeconds * 1e3);
1380
- this.skewMonitorInterval = setInterval(async () => await this.cacheClockSkew(), this.config.clockMonitorIntervalSeconds * 1e3);
1381
- }
1382
- async stop() {
1383
- if (this.stopped) return;
1384
- this.stopped = true;
1385
- await this.manager.offWork(QUEUES.SEND_IT, { wait: true });
1386
- if (this.skewMonitorInterval) {
1387
- clearInterval(this.skewMonitorInterval);
1388
- this.skewMonitorInterval = null;
1389
- }
1390
- if (this.cronMonitorInterval) {
1391
- clearInterval(this.cronMonitorInterval);
1392
- this.cronMonitorInterval = null;
1393
- }
1394
- }
1395
- async cacheClockSkew() {
1396
- let skew = 0;
1397
- try {
1398
- if (this.config.__test__force_clock_monitoring_error) throw new Error(this.config.__test__force_clock_monitoring_error);
1399
- const { rows } = await this.db.executeSql(getTime());
1400
- const local = Date.now();
1401
- skew = parseFloat(rows[0].time) - local;
1402
- const skewSeconds = Math.abs(skew) / 1e3;
1403
- if (skewSeconds >= 60 || this.config.__test__force_clock_skew_warning) this.emit(this.events.warning, {
1404
- message: WARNINGS$1.CLOCK_SKEW.message,
1405
- data: {
1406
- seconds: skewSeconds,
1407
- direction: skew > 0 ? "slower" : "faster"
1408
- }
1409
- });
1410
- } catch (err) {
1411
- this.emit(this.events.error, err);
1412
- } finally {
1413
- this.clockSkew = skew;
1414
- }
1415
- }
1416
- async onCron() {
1417
- try {
1418
- if (this.stopped || this.timekeeping) return;
1419
- if (this.config.__test__force_cron_monitoring_error) throw new Error(this.config.__test__force_cron_monitoring_error);
1420
- this.timekeeping = true;
1421
- const sql = trySetCronTime(this.config.schema, this.config.cronMonitorIntervalSeconds);
1422
- if (!this.stopped) {
1423
- const { rows } = await this.db.executeSql(sql);
1424
- if (!this.stopped && rows.length === 1) await this.cron();
1425
- }
1426
- } catch (err) {
1427
- this.emit(this.events.error, err);
1428
- } finally {
1429
- this.timekeeping = false;
1430
- }
1431
- }
1432
- async cron() {
1433
- const scheduled = (await this.getSchedules()).filter((i) => this.shouldSendIt(i.cron, i.timezone)).map(({ name, key, data, options }) => ({
1434
- data: {
1435
- name,
1436
- data,
1437
- options
1438
- },
1439
- singletonKey: `${name}__${key}`,
1440
- singletonSeconds: 60
1441
- }));
1442
- if (scheduled.length > 0 && !this.stopped) await this.manager.insert(QUEUES.SEND_IT, scheduled);
1443
- }
1444
- shouldSendIt(cron, tz) {
1445
- const prevTime = CronExpressionParser.parse(cron, {
1446
- tz,
1447
- strict: false
1448
- }).prev();
1449
- return (Date.now() + this.clockSkew - prevTime.getTime()) / 1e3 < 60;
1450
- }
1451
- async onSendIt(jobs) {
1452
- await Promise.allSettled(jobs.map(({ data }) => this.manager.send(data)));
1453
- }
1454
- async getSchedules(name, key = "") {
1455
- let sql = getSchedules(this.config.schema);
1456
- let params = [];
1457
- if (name) {
1458
- sql = getSchedulesByQueue(this.config.schema);
1459
- params = [name, key];
1460
- }
1461
- const { rows } = await this.db.executeSql(sql, params);
1462
- return rows;
1463
- }
1464
- async schedule(name, cron, data, options = {}) {
1465
- const { tz = "UTC", key = "", ...rest } = options;
1466
- CronExpressionParser.parse(cron, {
1467
- tz,
1468
- strict: false
1469
- });
1470
- checkSendArgs([
1471
- name,
1472
- data,
1473
- { ...rest }
1474
- ]);
1475
- assertKey(key);
1476
- try {
1477
- const sql = schedule(this.config.schema);
1478
- await this.db.executeSql(sql, [
1479
- name,
1480
- key,
1481
- cron,
1482
- tz,
1483
- data,
1484
- options
1485
- ]);
1486
- } catch (err) {
1487
- if (err.message.includes("foreign key")) err.message = `Queue ${name} not found`;
1488
- throw err;
1489
- }
1490
- }
1491
- async unschedule(name, key = "") {
1492
- const sql = unschedule(this.config.schema);
1493
- await this.db.executeSql(sql, [name, key]);
1494
- }
1495
- };
1496
- var timekeeper_default = Timekeeper;
1497
-
1498
- //#endregion
1499
- //#region src/tools.ts
1500
- /**
1501
- * When sql contains multiple queries, result is an array of objects with rows property
1502
- * This function unwraps the result into a single object with rows property
1503
- */
1504
- function unwrapSQLResult(result) {
1505
- if (Array.isArray(result)) return { rows: result.flatMap((i) => i.rows) };
1506
- return result;
1507
- }
1508
- function delay(ms, error, abortController) {
1509
- const ac = abortController || new AbortController();
1510
- const promise = new Promise((resolve, reject) => {
1511
- setTimeout(ms, null, { signal: ac.signal }).then(() => {
1512
- if (error) reject(new Error(error));
1513
- else resolve();
1514
- }).catch(resolve);
1515
- });
1516
- promise.abort = () => {
1517
- if (!ac.signal.aborted) ac.abort();
1518
- };
1519
- return promise;
1520
- }
1521
- async function resolveWithinSeconds(promise, seconds, message, abortController) {
1522
- const reject = delay(Math.max(1, seconds) * 1e3, message, abortController);
1523
- let result;
1524
- try {
1525
- result = await Promise.race([promise, reject]);
1526
- } finally {
1527
- reject.abort();
1528
- }
1529
- return result;
1530
- }
1531
-
1532
- //#endregion
1533
- //#region src/worker.ts
1534
- const WORKER_STATES = {
1535
- created: "created",
1536
- active: "active",
1537
- stopping: "stopping",
1538
- stopped: "stopped"
1539
- };
1540
- var Worker = class {
1541
- id;
1542
- name;
1543
- options;
1544
- fetch;
1545
- onFetch;
1546
- onError;
1547
- interval;
1548
- jobs = [];
1549
- createdOn = Date.now();
1550
- state = WORKER_STATES.created;
1551
- lastFetchedOn = null;
1552
- lastJobStartedOn = null;
1553
- lastJobEndedOn = null;
1554
- lastJobDuration = null;
1555
- lastError = null;
1556
- lastErrorOn = null;
1557
- stopping = false;
1558
- stopped = false;
1559
- loopDelayPromise = null;
1560
- beenNotified = false;
1561
- constructor({ id, name, options, interval, fetch, onFetch, onError }) {
1562
- this.id = id;
1563
- this.name = name;
1564
- this.options = options;
1565
- this.fetch = fetch;
1566
- this.onFetch = onFetch;
1567
- this.onError = onError;
1568
- this.interval = interval;
1569
- }
1570
- notify() {
1571
- this.beenNotified = true;
1572
- if (this.loopDelayPromise) this.loopDelayPromise.abort();
1573
- }
1574
- async start() {
1575
- this.state = WORKER_STATES.active;
1576
- while (!this.stopping) {
1577
- const started = Date.now();
1578
- try {
1579
- this.beenNotified = false;
1580
- const jobs = await this.fetch();
1581
- this.lastFetchedOn = Date.now();
1582
- if (jobs) {
1583
- this.jobs = jobs;
1584
- this.lastJobStartedOn = this.lastFetchedOn;
1585
- await this.onFetch(jobs);
1586
- this.lastJobEndedOn = Date.now();
1587
- this.jobs = [];
1588
- }
1589
- } catch (err) {
1590
- this.lastErrorOn = Date.now();
1591
- this.lastError = err;
1592
- err.message = `${err.message} (Queue: ${this.name}, Worker: ${this.id})`;
1593
- this.onError(err);
1594
- }
1595
- const duration = Date.now() - started;
1596
- this.lastJobDuration = duration;
1597
- if (!this.stopping && !this.beenNotified && this.interval - duration > 100) {
1598
- this.loopDelayPromise = delay(this.interval - duration);
1599
- await this.loopDelayPromise;
1600
- this.loopDelayPromise = null;
1601
- }
1602
- }
1603
- this.stopping = false;
1604
- this.stopped = true;
1605
- this.state = WORKER_STATES.stopped;
1606
- }
1607
- stop() {
1608
- this.stopping = true;
1609
- this.state = WORKER_STATES.stopping;
1610
- if (this.loopDelayPromise) this.loopDelayPromise.abort();
1611
- }
1612
- toWipData() {
1613
- return {
1614
- id: this.id,
1615
- name: this.name,
1616
- options: this.options,
1617
- state: this.state,
1618
- count: this.jobs.length,
1619
- createdOn: this.createdOn,
1620
- lastFetchedOn: this.lastFetchedOn,
1621
- lastJobStartedOn: this.lastJobStartedOn,
1622
- lastJobEndedOn: this.lastJobEndedOn,
1623
- lastError: this.lastError,
1624
- lastErrorOn: this.lastErrorOn,
1625
- lastJobDuration: this.lastJobDuration
1626
- };
1627
- }
1628
- };
1629
- var worker_default = Worker;
1630
-
1631
- //#endregion
1632
- //#region src/manager.ts
1633
- const INTERNAL_QUEUES = Object.values(QUEUES).reduce((acc, i) => ({
1634
- ...acc,
1635
- [i]: i
1636
- }), {});
1637
- const events$2 = {
1638
- error: "error",
1639
- wip: "wip"
1640
- };
1641
- var Manager = class extends EventEmitter {
1642
- events = events$2;
1643
- db;
1644
- config;
1645
- wipTs;
1646
- workers;
1647
- stopped;
1648
- queueCacheInterval;
1649
- timekeeper;
1650
- queues;
1651
- pendingOffWorkCleanups;
1652
- constructor(db, config) {
1653
- super();
1654
- this.config = config;
1655
- this.db = db;
1656
- this.wipTs = Date.now();
1657
- this.workers = /* @__PURE__ */ new Map();
1658
- this.queues = null;
1659
- this.pendingOffWorkCleanups = /* @__PURE__ */ new Set();
1660
- }
1661
- async start() {
1662
- this.stopped = false;
1663
- this.queueCacheInterval = setInterval(() => this.onCacheQueues({ emit: true }), this.config.queueCacheIntervalSeconds * 1e3);
1664
- await this.onCacheQueues();
1665
- }
1666
- async onCacheQueues({ emit = false } = {}) {
1667
- try {
1668
- assert(!this.config.__test__throw_queueCache, "test error");
1669
- this.queues = (await this.getQueues()).reduce((acc, i) => {
1670
- acc[i.name] = i;
1671
- return acc;
1672
- }, {});
1673
- } catch (error) {
1674
- emit && this.emit(events$2.error, {
1675
- ...error,
1676
- message: error.message,
1677
- stack: error.stack
1678
- });
1679
- }
1680
- }
1681
- async getQueueCache(name) {
1682
- assert(this.queues, "Queue cache is not initialized");
1683
- let queue = this.queues[name];
1684
- if (queue) return queue;
1685
- queue = await this.getQueue(name);
1686
- if (!queue) throw new Error(`Queue ${name} does not exist`);
1687
- this.queues[name] = queue;
1688
- return queue;
1689
- }
1690
- async stop() {
1691
- this.stopped = true;
1692
- clearInterval(this.queueCacheInterval);
1693
- await Promise.allSettled([...this.workers.values()].filter((worker) => !INTERNAL_QUEUES[worker.name]).map(async (worker) => await this.offWork(worker.name, { wait: false })));
1694
- }
1695
- async failWip() {
1696
- for (const worker of this.workers.values()) {
1697
- const jobIds = worker.jobs.map((j) => j.id);
1698
- if (jobIds.length) await this.fail(worker.name, jobIds, "pg-boss shut down while active");
1699
- }
1700
- }
1701
- async work(name, ...args) {
1702
- const { options, callback } = checkWorkArgs(name, args);
1703
- if (this.stopped) throw new Error("Workers are disabled. pg-boss is stopped");
1704
- const { pollingInterval: interval, batchSize = 1, includeMetadata = false, priority = true } = options;
1705
- const id = randomUUID({ disableEntropyCache: true });
1706
- const fetch = () => this.fetch(name, {
1707
- batchSize,
1708
- includeMetadata,
1709
- priority
1710
- });
1711
- const onFetch = async (jobs) => {
1712
- if (!jobs.length) return;
1713
- if (this.config.__test__throw_worker) throw new Error("__test__throw_worker");
1714
- this.emitWip(name);
1715
- const maxExpiration = jobs.reduce((acc, i) => Math.max(acc, i.expireInSeconds), 0);
1716
- const jobIds = jobs.map((job) => job.id);
1717
- const ac = new AbortController();
1718
- jobs.forEach((job) => {
1719
- job.signal = ac.signal;
1720
- });
1721
- try {
1722
- const result = await resolveWithinSeconds(callback(jobs), maxExpiration, `handler execution exceeded ${maxExpiration}s`, ac);
1723
- await this.complete(name, jobIds, jobIds.length === 1 ? result : void 0);
1724
- } catch (err) {
1725
- await this.fail(name, jobIds, err);
1726
- }
1727
- this.emitWip(name);
1728
- };
1729
- const onError = (error) => {
1730
- this.emit(events$2.error, {
1731
- ...error,
1732
- message: error.message,
1733
- stack: error.stack,
1734
- queue: name,
1735
- worker: id
1736
- });
1737
- };
1738
- const worker = new worker_default({
1739
- id,
1740
- name,
1741
- options,
1742
- interval,
1743
- fetch,
1744
- onFetch,
1745
- onError
1746
- });
1747
- this.addWorker(worker);
1748
- worker.start();
1749
- return id;
1750
- }
1751
- addWorker(worker) {
1752
- this.workers.set(worker.id, worker);
1753
- }
1754
- removeWorker(worker) {
1755
- this.workers.delete(worker.id);
1756
- }
1757
- getWorkers() {
1758
- return Array.from(this.workers.values());
1759
- }
1760
- emitWip(name) {
1761
- if (!INTERNAL_QUEUES[name]) {
1762
- const now = Date.now();
1763
- if (now - this.wipTs > 2e3) {
1764
- this.emit(events$2.wip, this.getWipData());
1765
- this.wipTs = now;
1766
- }
1767
- }
1768
- }
1769
- getWipData(options = {}) {
1770
- const { includeInternal = false } = options;
1771
- return this.getWorkers().map((i) => i.toWipData()).filter((i) => i.state !== "stopped" && (!INTERNAL_QUEUES[i.name] || includeInternal));
1772
- }
1773
- hasPendingCleanups() {
1774
- return this.pendingOffWorkCleanups.size > 0;
1775
- }
1776
- async offWork(name, options = { wait: true }) {
1777
- assert(name, "queue name is required");
1778
- assert(typeof name === "string", "queue name must be a string");
1779
- const query = (i) => options?.id ? i.id === options.id : i.name === name;
1780
- const workers = this.getWorkers().filter((i) => query(i) && !i.stopping && !i.stopped);
1781
- if (workers.length === 0) return;
1782
- for (const worker of workers) worker.stop();
1783
- const cleanupPromise = (async () => {
1784
- while (!workers.every((w) => w.stopped)) await delay(1e3);
1785
- for (const worker of workers) this.removeWorker(worker);
1786
- })();
1787
- if (options.wait) await cleanupPromise;
1788
- else {
1789
- this.pendingOffWorkCleanups.add(cleanupPromise);
1790
- cleanupPromise.finally(() => this.pendingOffWorkCleanups.delete(cleanupPromise));
1791
- }
1792
- }
1793
- notifyWorker(workerId) {
1794
- this.workers.get(workerId)?.notify();
1795
- }
1796
- async subscribe(event, name) {
1797
- assert(event, "Missing required argument");
1798
- assert(name, "Missing required argument");
1799
- const sql = subscribe(this.config.schema);
1800
- await this.db.executeSql(sql, [event, name]);
1801
- }
1802
- async unsubscribe(event, name) {
1803
- assert(event, "Missing required argument");
1804
- assert(name, "Missing required argument");
1805
- const sql = unsubscribe(this.config.schema);
1806
- await this.db.executeSql(sql, [event, name]);
1807
- }
1808
- async publish(event, data, options) {
1809
- assert(event, "Missing required argument");
1810
- const sql = getQueuesForEvent(this.config.schema);
1811
- const { rows } = await this.db.executeSql(sql, [event]);
1812
- await Promise.allSettled(rows.map(({ name }) => this.send(name, data, options)));
1813
- }
1814
- async send(...args) {
1815
- const result = checkSendArgs(args);
1816
- return await this.createJob(result);
1817
- }
1818
- async sendAfter(name, data, options, after) {
1819
- options = options ? { ...options } : {};
1820
- options.startAfter = after;
1821
- const result = checkSendArgs([
1822
- name,
1823
- data,
1824
- options
1825
- ]);
1826
- return await this.createJob(result);
1827
- }
1828
- async sendThrottled(name, data, options, seconds, key) {
1829
- options = options ? { ...options } : {};
1830
- options.singletonSeconds = seconds;
1831
- options.singletonNextSlot = false;
1832
- options.singletonKey = key;
1833
- const result = checkSendArgs([
1834
- name,
1835
- data,
1836
- options
1837
- ]);
1838
- return await this.createJob(result);
1839
- }
1840
- async sendDebounced(name, data, options, seconds, key) {
1841
- options = options ? { ...options } : {};
1842
- options.singletonSeconds = seconds;
1843
- options.singletonNextSlot = true;
1844
- options.singletonKey = key;
1845
- const result = checkSendArgs([
1846
- name,
1847
- data,
1848
- options
1849
- ]);
1850
- return await this.createJob(result);
1851
- }
1852
- async createJob(request) {
1853
- const { name, data = null, options = {} } = request;
1854
- const { id = null, db: wrapper, priority, startAfter, singletonKey = null, singletonSeconds, singletonNextSlot, expireInSeconds, deleteAfterSeconds, retentionSeconds, keepUntil, retryLimit, retryDelay, retryBackoff, retryDelayMax } = options;
1855
- const job = {
1856
- id,
1857
- name,
1858
- data,
1859
- priority,
1860
- startAfter,
1861
- singletonKey,
1862
- singletonSeconds,
1863
- singletonOffset: 0,
1864
- expireInSeconds,
1865
- deleteAfterSeconds,
1866
- retentionSeconds,
1867
- keepUntil,
1868
- retryLimit,
1869
- retryDelay,
1870
- retryBackoff,
1871
- retryDelayMax
1872
- };
1873
- const db = wrapper || this.db;
1874
- const { table } = await this.getQueueCache(name);
1875
- const sql = insertJobs(this.config.schema, {
1876
- table,
1877
- name,
1878
- returnId: true
1879
- });
1880
- const { rows: try1 } = await db.executeSql(sql, [JSON.stringify([job])]);
1881
- if (try1.length === 1) return try1[0].id;
1882
- if (singletonNextSlot) {
1883
- job.startAfter = this.getDebounceStartAfter(singletonSeconds, this.timekeeper.clockSkew);
1884
- job.singletonOffset = singletonSeconds;
1885
- const { rows: try2 } = await db.executeSql(sql, [JSON.stringify([job])]);
1886
- if (try2.length === 1) return try2[0].id;
1887
- }
1888
- return null;
1889
- }
1890
- async insert(name, jobs, options = {}) {
1891
- assert(Array.isArray(jobs), "jobs argument should be an array");
1892
- const { table } = await this.getQueueCache(name);
1893
- const db = this.assertDb(options);
1894
- const sql = insertJobs(this.config.schema, {
1895
- table,
1896
- name,
1897
- returnId: false
1898
- });
1899
- const { rows } = await db.executeSql(sql, [JSON.stringify(jobs)]);
1900
- return rows.length ? rows.map((i) => i.id) : null;
1901
- }
1902
- getDebounceStartAfter(singletonSeconds, clockOffset) {
1903
- const debounceInterval = singletonSeconds * 1e3;
1904
- const now = Date.now() + clockOffset;
1905
- const slot = Math.floor(now / debounceInterval) * debounceInterval;
1906
- let startAfter = singletonSeconds - Math.floor((now - slot) / 1e3) || 1;
1907
- if (singletonSeconds > 1) startAfter++;
1908
- return startAfter;
1909
- }
1910
- async fetch(name, options = {}) {
1911
- checkFetchArgs(name, options);
1912
- const db = this.assertDb(options);
1913
- const { table, policy, singletonsActive } = await this.getQueueCache(name);
1914
- const fetchOptions = {
1915
- ...options,
1916
- schema: this.config.schema,
1917
- table,
1918
- name,
1919
- policy,
1920
- limit: options.batchSize || 1,
1921
- ignoreSingletons: singletonsActive
1922
- };
1923
- const sql = fetchNextJob(fetchOptions);
1924
- let result;
1925
- try {
1926
- result = await db.executeSql(sql);
1927
- } catch (err) {}
1928
- return result?.rows || [];
1929
- }
1930
- mapCompletionIdArg(id, funcName) {
1931
- const errorMessage = `${funcName}() requires an id`;
1932
- assert(id, errorMessage);
1933
- const ids = Array.isArray(id) ? id : [id];
1934
- assert(ids.length, errorMessage);
1935
- return ids;
1936
- }
1937
- mapCompletionDataArg(data) {
1938
- if (data === null || typeof data === "undefined" || typeof data === "function") return null;
1939
- return serializeError(typeof data === "object" && !Array.isArray(data) ? data : { value: data });
1940
- }
1941
- mapCommandResponse(ids, result) {
1942
- return {
1943
- jobs: ids,
1944
- requested: ids.length,
1945
- affected: result && result.rows ? parseInt(result.rows[0].count) : 0
1946
- };
1947
- }
1948
- async complete(name, id, data, options = {}) {
1949
- assertQueueName(name);
1950
- const db = this.assertDb(options);
1951
- const ids = this.mapCompletionIdArg(id, "complete");
1952
- const { table } = await this.getQueueCache(name);
1953
- const sql = completeJobs(this.config.schema, table);
1954
- const result = await db.executeSql(sql, [
1955
- name,
1956
- ids,
1957
- this.mapCompletionDataArg(data)
1958
- ]);
1959
- return this.mapCommandResponse(ids, result);
1960
- }
1961
- async fail(name, id, data, options = {}) {
1962
- assertQueueName(name);
1963
- const db = this.assertDb(options);
1964
- const ids = this.mapCompletionIdArg(id, "fail");
1965
- const { table } = await this.getQueueCache(name);
1966
- const sql = failJobsById(this.config.schema, table);
1967
- const result = await db.executeSql(sql, [
1968
- name,
1969
- ids,
1970
- this.mapCompletionDataArg(data)
1971
- ]);
1972
- return this.mapCommandResponse(ids, result);
1973
- }
1974
- async cancel(name, id, options = {}) {
1975
- assertQueueName(name);
1976
- const db = this.assertDb(options);
1977
- const ids = this.mapCompletionIdArg(id, "cancel");
1978
- const { table } = await this.getQueueCache(name);
1979
- const sql = cancelJobs(this.config.schema, table);
1980
- const result = await db.executeSql(sql, [name, ids]);
1981
- return this.mapCommandResponse(ids, result);
1982
- }
1983
- async deleteJob(name, id, options = {}) {
1984
- assertQueueName(name);
1985
- const db = this.assertDb(options);
1986
- const ids = this.mapCompletionIdArg(id, "deleteJob");
1987
- const { table } = await this.getQueueCache(name);
1988
- const sql = deleteJobsById(this.config.schema, table);
1989
- const result = await db.executeSql(sql, [name, ids]);
1990
- return this.mapCommandResponse(ids, result);
1991
- }
1992
- async resume(name, id, options = {}) {
1993
- assertQueueName(name);
1994
- const db = this.assertDb(options);
1995
- const ids = this.mapCompletionIdArg(id, "resume");
1996
- const { table } = await this.getQueueCache(name);
1997
- const sql = resumeJobs(this.config.schema, table);
1998
- const result = await db.executeSql(sql, [name, ids]);
1999
- return this.mapCommandResponse(ids, result);
2000
- }
2001
- async retry(name, id, options = {}) {
2002
- assertQueueName(name);
2003
- const db = options.db || this.db;
2004
- const ids = this.mapCompletionIdArg(id, "retry");
2005
- const { table } = await this.getQueueCache(name);
2006
- const sql = retryJobs(this.config.schema, table);
2007
- const result = await db.executeSql(sql, [name, ids]);
2008
- return this.mapCommandResponse(ids, result);
2009
- }
2010
- async createQueue(name, options = {}) {
2011
- name = name || options.name;
2012
- assertQueueName(name);
2013
- const policy = options.policy || QUEUE_POLICIES.standard;
2014
- assert(policy in QUEUE_POLICIES, `${policy} is not a valid queue policy`);
2015
- validateQueueArgs(options);
2016
- if (options.deadLetter) {
2017
- assertQueueName(options.deadLetter);
2018
- notStrictEqual(name, options.deadLetter, "deadLetter cannot be itself");
2019
- await this.getQueueCache(options.deadLetter);
2020
- }
2021
- const sql = createQueue(this.config.schema, name, {
2022
- ...options,
2023
- policy
2024
- });
2025
- await this.db.executeSql(sql);
2026
- }
2027
- async getQueues(names) {
2028
- names = Array.isArray(names) ? names : typeof names === "string" ? [names] : void 0;
2029
- if (names) for (const name of names) assertQueueName(name);
2030
- const sql = getQueues(this.config.schema, names);
2031
- const { rows } = await this.db.executeSql(sql);
2032
- return rows;
2033
- }
2034
- async updateQueue(name, options = {}) {
2035
- assertQueueName(name);
2036
- assert(Object.keys(options).length > 0, "no properties found to update");
2037
- if ("policy" in options) throw new Error("queue policy cannot be changed after creation");
2038
- if ("partition" in options) throw new Error("queue partitioning cannot be changed after creation");
2039
- validateQueueArgs(options);
2040
- const { deadLetter } = options;
2041
- if (deadLetter) {
2042
- assertQueueName(deadLetter);
2043
- notStrictEqual(name, deadLetter, "deadLetter cannot be itself");
2044
- }
2045
- const sql = updateQueue(this.config.schema, { deadLetter });
2046
- await this.db.executeSql(sql, [name, options]);
2047
- }
2048
- async getQueue(name) {
2049
- assertQueueName(name);
2050
- const sql = getQueues(this.config.schema, [name]);
2051
- const { rows } = await this.db.executeSql(sql);
2052
- return rows[0] || null;
2053
- }
2054
- async deleteQueue(name) {
2055
- assertQueueName(name);
2056
- try {
2057
- await this.getQueueCache(name);
2058
- const sql = deleteQueue(this.config.schema, name);
2059
- await this.db.executeSql(sql);
2060
- } catch {}
2061
- }
2062
- async deleteQueuedJobs(name) {
2063
- assertQueueName(name);
2064
- const { table } = await this.getQueueCache(name);
2065
- const sql = deleteQueuedJobs(this.config.schema, table);
2066
- await this.db.executeSql(sql, [name]);
2067
- }
2068
- async deleteStoredJobs(name) {
2069
- assertQueueName(name);
2070
- const { table } = await this.getQueueCache(name);
2071
- const sql = deleteStoredJobs(this.config.schema, table);
2072
- await this.db.executeSql(sql, [name]);
2073
- }
2074
- async deleteAllJobs(name) {
2075
- assertQueueName(name);
2076
- const { table, partition } = await this.getQueueCache(name);
2077
- if (partition) {
2078
- const sql = truncateTable(this.config.schema, table);
2079
- await this.db.executeSql(sql);
2080
- } else {
2081
- const sql = deleteAllJobs(this.config.schema, table);
2082
- await this.db.executeSql(sql, [name]);
2083
- }
2084
- }
2085
- async getQueueStats(name) {
2086
- assertQueueName(name);
2087
- const queue = await this.getQueueCache(name);
2088
- const sql = getQueueStats(this.config.schema, queue.table, [name]);
2089
- const { rows } = await this.db.executeSql(sql);
2090
- return Object.assign(queue, rows.at(0) || {});
2091
- }
2092
- async getJobById(name, id, options = {}) {
2093
- assertQueueName(name);
2094
- const db = this.assertDb(options);
2095
- const { table } = await this.getQueueCache(name);
2096
- const sql = getJobById(this.config.schema, table);
2097
- const result1 = await db.executeSql(sql, [name, id]);
2098
- if (result1?.rows?.length === 1) return result1.rows[0];
2099
- else return null;
2100
- }
2101
- assertDb(options) {
2102
- if (options.db) return options.db;
2103
- if (this.db._pgbdb) assert(this.db.opened, "Database connection is not opened");
2104
- return this.db;
2105
- }
2106
- };
2107
- var manager_default = Manager;
2108
-
2109
- //#endregion
2110
- //#region src/boss.ts
2111
- const events$1 = {
2112
- error: "error",
2113
- warning: "warning"
2114
- };
2115
- const WARNINGS = {
2116
- SLOW_QUERY: {
2117
- seconds: 30,
2118
- message: "Warning: slow query. Your queues and/or database server should be reviewed"
2119
- },
2120
- LARGE_QUEUE: {
2121
- size: 1e4,
2122
- message: "Warning: large queue backlog. Your queue should be reviewed"
2123
- }
2124
- };
2125
- var Boss = class extends EventEmitter {
2126
- #stopped;
2127
- #maintaining;
2128
- #superviseInterval;
2129
- #db;
2130
- #config;
2131
- #manager;
2132
- events = events$1;
2133
- constructor(db, manager, config) {
2134
- super();
2135
- this.#db = db;
2136
- this.#config = config;
2137
- this.#manager = manager;
2138
- this.#stopped = true;
2139
- if (config.warningSlowQuerySeconds) WARNINGS.SLOW_QUERY.seconds = config.warningSlowQuerySeconds;
2140
- if (config.warningQueueSize) WARNINGS.LARGE_QUEUE.size = config.warningQueueSize;
2141
- }
2142
- async start() {
2143
- if (this.#stopped) {
2144
- this.#superviseInterval = setInterval(() => this.#onSupervise(), this.#config.superviseIntervalSeconds * 1e3);
2145
- this.#stopped = false;
2146
- }
2147
- }
2148
- async stop() {
2149
- if (!this.#stopped) {
2150
- if (this.#superviseInterval) clearInterval(this.#superviseInterval);
2151
- this.#stopped = true;
2152
- }
2153
- }
2154
- async #executeSql(sql, values) {
2155
- const started = Date.now();
2156
- const result = unwrapSQLResult(await this.#db.executeSql(sql, values));
2157
- const elapsed = (Date.now() - started) / 1e3;
2158
- if (elapsed > WARNINGS.SLOW_QUERY.seconds || this.#config.__test__warn_slow_query) this.emit(events$1.warning, {
2159
- message: WARNINGS.SLOW_QUERY.message,
2160
- data: {
2161
- elapsed,
2162
- sql,
2163
- values
2164
- }
2165
- });
2166
- return result;
2167
- }
2168
- async #onSupervise() {
2169
- try {
2170
- if (this.#stopped) return;
2171
- if (this.#maintaining) return;
2172
- if (this.#config.__test__throw_maint) throw new Error(this.#config.__test__throw_maint);
2173
- this.#maintaining = true;
2174
- const queues = await this.#manager.getQueues();
2175
- for (const queue of queues) !this.#stopped && await this.supervise(queue);
2176
- } catch (err) {
2177
- this.emit(events$1.error, err);
2178
- } finally {
2179
- this.#maintaining = false;
2180
- }
2181
- }
2182
- async supervise(value) {
2183
- let queues;
2184
- if (typeof value === "object") queues = [value];
2185
- else queues = await this.#manager.getQueues(value);
2186
- const queueGroups = queues.reduce((acc, q) => {
2187
- const { table } = q;
2188
- acc[table] = acc[table] || {
2189
- table,
2190
- queues: []
2191
- };
2192
- acc[table].queues.push(q);
2193
- return acc;
2194
- }, {});
2195
- for (const queueGroup of Object.values(queueGroups)) {
2196
- const { table, queues: queues$1 } = queueGroup;
2197
- const names = queues$1.map((i) => i.name);
2198
- while (names.length) {
2199
- const chunk = names.splice(0, 100);
2200
- await this.#monitor(table, chunk);
2201
- await this.#maintain(table, chunk);
2202
- }
2203
- }
2204
- }
2205
- async #monitor(table, names) {
2206
- const command = trySetQueueMonitorTime(this.#config.schema, names, this.#config.monitorIntervalSeconds);
2207
- const { rows } = await this.#executeSql(command);
2208
- if (rows.length) {
2209
- const queues = rows.map((q) => q.name);
2210
- const cacheStatsSql = cacheQueueStats(this.#config.schema, table, queues);
2211
- const { rows: rowsCacheStats } = await this.#executeSql(cacheStatsSql);
2212
- const warnings = rowsCacheStats.filter((i) => i.queuedCount > (i.warningQueueSize || WARNINGS.LARGE_QUEUE.size));
2213
- for (const warning of warnings) this.emit(events$1.warning, {
2214
- message: WARNINGS.LARGE_QUEUE.message,
2215
- data: warning
2216
- });
2217
- const sql = failJobsByTimeout(this.#config.schema, table, queues);
2218
- await this.#executeSql(sql);
2219
- }
2220
- }
2221
- async #maintain(table, names) {
2222
- const command = trySetQueueDeletionTime(this.#config.schema, names, this.#config.maintenanceIntervalSeconds);
2223
- const { rows } = await this.#executeSql(command);
2224
- if (rows.length) {
2225
- const queues = rows.map((q) => q.name);
2226
- const sql = deletion(this.#config.schema, table, queues);
2227
- await this.#executeSql(sql);
2228
- }
2229
- }
2230
- };
2231
- var boss_default = Boss;
2232
-
2233
- //#endregion
2234
- //#region src/db.ts
2235
- var Db = class extends EventEmitter {
2236
- pool;
2237
- config;
2238
- /** @internal */
2239
- _pgbdb;
2240
- opened;
2241
- constructor(config) {
2242
- super();
2243
- config.application_name = config.application_name || "pgboss";
2244
- config.connectionTimeoutMillis = config.connectionTimeoutMillis || 1e4;
2245
- this.config = config;
2246
- this._pgbdb = true;
2247
- this.opened = false;
2248
- }
2249
- events = { error: "error" };
2250
- async open() {
2251
- this.pool = new pg.Pool(this.config);
2252
- this.pool.on("error", (error) => this.emit("error", error));
2253
- this.opened = true;
2254
- }
2255
- async close() {
2256
- if (!this.pool.ending) {
2257
- this.opened = false;
2258
- await this.pool.end();
2259
- }
2260
- }
2261
- async executeSql(text, values) {
2262
- assert(this.opened, "Database not opened. Call open() before executing SQL.");
2263
- return await this.pool.query(text, values);
2264
- }
2265
- };
2266
- var db_default = Db;
2267
-
2268
- //#endregion
2269
- //#region src/index.ts
2270
- const events = Object.freeze({
2271
- error: "error",
2272
- warning: "warning",
2273
- wip: "wip",
2274
- stopped: "stopped"
2275
- });
2276
- function getConstructionPlans(schema) {
2277
- return contractor_default.constructionPlans(schema);
2278
- }
2279
- function getMigrationPlans(schema, version) {
2280
- return contractor_default.migrationPlans(schema, version);
2281
- }
2282
- function getRollbackPlans(schema, version) {
2283
- return contractor_default.rollbackPlans(schema, version);
2284
- }
2285
- var PgBoss = class extends EventEmitter {
2286
- #stoppingOn;
2287
- #stopped;
2288
- #starting;
2289
- #started;
2290
- #config;
2291
- #db;
2292
- #boss;
2293
- #contractor;
2294
- #manager;
2295
- #timekeeper;
2296
- constructor(value) {
2297
- super();
2298
- this.#stoppingOn = null;
2299
- this.#stopped = true;
2300
- const config = getConfig(value);
2301
- this.#config = config;
2302
- const db = this.getDb();
2303
- this.#db = db;
2304
- if ("_pgbdb" in this.#db && this.#db._pgbdb) this.#promoteEvents(this.#db);
2305
- const contractor = new contractor_default(db, config);
2306
- const manager = new manager_default(db, config);
2307
- const boss = new boss_default(db, manager, config);
2308
- const timekeeper = new timekeeper_default(db, manager, config);
2309
- manager.timekeeper = timekeeper;
2310
- this.#promoteEvents(manager);
2311
- this.#promoteEvents(boss);
2312
- this.#promoteEvents(timekeeper);
2313
- this.#boss = boss;
2314
- this.#contractor = contractor;
2315
- this.#manager = manager;
2316
- this.#timekeeper = timekeeper;
2317
- }
2318
- #promoteEvents(emitter) {
2319
- for (const event of Object.values(emitter?.events)) emitter.on(event, (arg) => this.emit(event, arg));
2320
- }
2321
- async start() {
2322
- if (this.#starting || this.#started) return this;
2323
- this.#starting = true;
2324
- if (this.#db._pgbdb && !this.#db.opened) await this.#db.open();
2325
- if (this.#config.migrate) await this.#contractor.start();
2326
- else await this.#contractor.check();
2327
- await this.#manager.start();
2328
- if (this.#config.supervise) await this.#boss.start();
2329
- if (this.#config.schedule) await this.#timekeeper.start();
2330
- this.#starting = false;
2331
- this.#started = true;
2332
- this.#stopped = false;
2333
- return this;
2334
- }
2335
- async stop(options = {}) {
2336
- if (this.#stoppingOn || this.#stopped) return;
2337
- let { close = true, graceful = true, timeout = 3e4 } = options;
2338
- timeout = Math.max(timeout, 1e3);
2339
- this.#stoppingOn = Date.now();
2340
- await this.#manager.stop();
2341
- await this.#timekeeper.stop();
2342
- await this.#boss.stop();
2343
- const shutdown = async () => {
2344
- await this.#manager.failWip();
2345
- if (this.#db._pgbdb && this.#db.opened && close) {
2346
- await this.#db.close();
2347
- await delay(10);
2348
- }
2349
- this.#stopped = true;
2350
- this.#stoppingOn = null;
2351
- this.#started = false;
2352
- this.emit(events.stopped);
2353
- };
2354
- if (!graceful) return await shutdown();
2355
- while (Date.now() - this.#stoppingOn < timeout && this.#manager.hasPendingCleanups()) await delay(500);
2356
- await shutdown();
2357
- }
2358
- async send(...args) {
2359
- return await this.#manager.send(...args);
2360
- }
2361
- async sendAfter(name, data, options, after) {
2362
- return this.#manager.sendAfter(name, data, options, after);
2363
- }
2364
- sendThrottled(name, data, options, seconds, key) {
2365
- return this.#manager.sendThrottled(name, data, options, seconds, key);
2366
- }
2367
- sendDebounced(name, data, options, seconds, key) {
2368
- return this.#manager.sendDebounced(name, data, options, seconds, key);
2369
- }
2370
- insert(name, jobs, options) {
2371
- return this.#manager.insert(name, jobs, options);
2372
- }
2373
- fetch(name, options = {}) {
2374
- return this.#manager.fetch(name, options);
2375
- }
2376
- work(...args) {
2377
- return this.#manager.work(...args);
2378
- }
2379
- offWork(name, options) {
2380
- return this.#manager.offWork(name, options);
2381
- }
2382
- notifyWorker(workerId) {
2383
- return this.#manager.notifyWorker(workerId);
2384
- }
2385
- subscribe(event, name) {
2386
- return this.#manager.subscribe(event, name);
2387
- }
2388
- unsubscribe(event, name) {
2389
- return this.#manager.unsubscribe(event, name);
2390
- }
2391
- publish(event, data, options) {
2392
- return this.#manager.publish(event, data, options);
2393
- }
2394
- cancel(name, id, options) {
2395
- return this.#manager.cancel(name, id, options);
2396
- }
2397
- resume(name, id, options) {
2398
- return this.#manager.resume(name, id, options);
2399
- }
2400
- retry(name, id, options) {
2401
- return this.#manager.retry(name, id, options);
2402
- }
2403
- deleteJob(name, id, options) {
2404
- return this.#manager.deleteJob(name, id, options);
2405
- }
2406
- deleteQueuedJobs(name) {
2407
- return this.#manager.deleteQueuedJobs(name);
2408
- }
2409
- deleteStoredJobs(name) {
2410
- return this.#manager.deleteStoredJobs(name);
2411
- }
2412
- deleteAllJobs(name) {
2413
- return this.#manager.deleteAllJobs(name);
2414
- }
2415
- complete(name, id, data, options) {
2416
- return this.#manager.complete(name, id, data, options);
2417
- }
2418
- fail(name, id, data, options) {
2419
- return this.#manager.fail(name, id, data, options);
2420
- }
2421
- getJobById(name, id, options) {
2422
- return this.#manager.getJobById(name, id, options);
2423
- }
2424
- createQueue(name, options) {
2425
- return this.#manager.createQueue(name, options);
2426
- }
2427
- updateQueue(name, options) {
2428
- return this.#manager.updateQueue(name, options);
2429
- }
2430
- deleteQueue(name) {
2431
- return this.#manager.deleteQueue(name);
2432
- }
2433
- getQueues(names) {
2434
- return this.#manager.getQueues();
2435
- }
2436
- getQueue(name) {
2437
- return this.#manager.getQueue(name);
2438
- }
2439
- getQueueStats(name) {
2440
- return this.#manager.getQueueStats(name);
2441
- }
2442
- supervise(name) {
2443
- return this.#boss.supervise(name);
2444
- }
2445
- isInstalled() {
2446
- return this.#contractor.isInstalled();
2447
- }
2448
- schemaVersion() {
2449
- return this.#contractor.schemaVersion();
2450
- }
2451
- schedule(name, cron, data, options) {
2452
- return this.#timekeeper.schedule(name, cron, data, options);
2453
- }
2454
- unschedule(name, key) {
2455
- return this.#timekeeper.unschedule(name, key);
2456
- }
2457
- getSchedules(name, key) {
2458
- return this.#timekeeper.getSchedules(name, key);
2459
- }
2460
- getDb() {
2461
- if (this.#db) return this.#db;
2462
- if (this.#config.db) return this.#config.db;
2463
- return new db_default(this.#config);
2464
- }
2465
- };
2466
-
2467
- //#endregion
2468
- export { PgBoss, events, getConstructionPlans, getMigrationPlans, getRollbackPlans, QUEUE_POLICIES as policies, JOB_STATES as states };