pg-boss 12.18.2 → 12.19.0
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/README.md +2 -2
- package/dist/adapters/drizzle.d.ts +3 -1
- package/dist/adapters/drizzle.d.ts.map +1 -1
- package/dist/adapters/drizzle.js +2 -1
- package/dist/adapters/knex.d.ts +3 -1
- package/dist/adapters/knex.d.ts.map +1 -1
- package/dist/adapters/knex.js +2 -2
- package/dist/attorney.d.ts +2 -1
- package/dist/attorney.d.ts.map +1 -1
- package/dist/attorney.js +91 -1
- package/dist/boss.d.ts.map +1 -1
- package/dist/boss.js +2 -0
- package/dist/db.d.ts +1 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +20 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/manager.d.ts +4 -0
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +136 -1
- package/dist/migrationStore.d.ts.map +1 -1
- package/dist/migrationStore.js +197 -0
- package/dist/plans.d.ts +74 -63
- package/dist/plans.d.ts.map +1 -1
- package/dist/plans.js +203 -81
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -10
package/dist/plans.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export const PG_ERROR = {
|
|
2
|
+
divisionByZero: '22012'
|
|
3
|
+
};
|
|
4
|
+
export const DEFAULT_SCHEMA = 'pgboss';
|
|
5
|
+
export const MIGRATE_RACE_MESSAGE = 'division by zero';
|
|
6
|
+
export const CREATE_RACE_MESSAGE = 'already exists';
|
|
4
7
|
const SINGLE_QUOTE_REGEX = /'/g;
|
|
5
8
|
const FIFTEEN_MINUTES = 60 * 15;
|
|
6
9
|
const FORTEEN_DAYS = 60 * 60 * 24 * 14;
|
|
7
10
|
const SEVEN_DAYS = 60 * 60 * 24 * 7;
|
|
8
|
-
const JOB_STATES = Object.freeze({
|
|
11
|
+
export const JOB_STATES = Object.freeze({
|
|
9
12
|
created: 'created',
|
|
10
13
|
retry: 'retry',
|
|
11
14
|
active: 'active',
|
|
@@ -13,7 +16,7 @@ const JOB_STATES = Object.freeze({
|
|
|
13
16
|
cancelled: 'cancelled',
|
|
14
17
|
failed: 'failed'
|
|
15
18
|
});
|
|
16
|
-
const QUEUE_POLICIES = Object.freeze({
|
|
19
|
+
export const QUEUE_POLICIES = Object.freeze({
|
|
17
20
|
standard: 'standard',
|
|
18
21
|
short: 'short',
|
|
19
22
|
singleton: 'singleton',
|
|
@@ -32,7 +35,7 @@ const QUEUE_DEFAULTS = {
|
|
|
32
35
|
partition: false
|
|
33
36
|
};
|
|
34
37
|
const COMMON_JOB_TABLE = 'job_common';
|
|
35
|
-
function create(schema, version, options) {
|
|
38
|
+
export function create(schema, version, options) {
|
|
36
39
|
const commands = [
|
|
37
40
|
options?.createSchema ? createSchema(schema) : '',
|
|
38
41
|
createEnumJobState(schema),
|
|
@@ -49,6 +52,8 @@ function create(schema, version, options) {
|
|
|
49
52
|
createTableJobCommon(schema),
|
|
50
53
|
createTableWarning(schema),
|
|
51
54
|
createIndexWarning(schema),
|
|
55
|
+
createTableJobDependency(schema),
|
|
56
|
+
createIndexJobDependencyParent(schema),
|
|
52
57
|
createQueueFunction(schema),
|
|
53
58
|
deleteQueueFunction(schema),
|
|
54
59
|
insertVersion(schema, version)
|
|
@@ -154,7 +159,7 @@ function createTableBam(schema) {
|
|
|
154
159
|
)
|
|
155
160
|
`;
|
|
156
161
|
}
|
|
157
|
-
function createTableWarning(schema) {
|
|
162
|
+
export function createTableWarning(schema) {
|
|
158
163
|
return `
|
|
159
164
|
CREATE TABLE ${schema}.warning (
|
|
160
165
|
id uuid PRIMARY KEY default gen_random_uuid(),
|
|
@@ -165,9 +170,23 @@ function createTableWarning(schema) {
|
|
|
165
170
|
)
|
|
166
171
|
`;
|
|
167
172
|
}
|
|
168
|
-
function createIndexWarning(schema) {
|
|
173
|
+
export function createIndexWarning(schema) {
|
|
169
174
|
return `CREATE INDEX warning_i1 ON ${schema}.warning (created_on DESC)`;
|
|
170
175
|
}
|
|
176
|
+
export function createTableJobDependency(schema) {
|
|
177
|
+
return `
|
|
178
|
+
CREATE TABLE ${schema}.job_dependency (
|
|
179
|
+
child_name text NOT NULL,
|
|
180
|
+
child_id uuid NOT NULL,
|
|
181
|
+
parent_name text NOT NULL,
|
|
182
|
+
parent_id uuid NOT NULL,
|
|
183
|
+
PRIMARY KEY (child_name, child_id, parent_name, parent_id)
|
|
184
|
+
)
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
export function createIndexJobDependencyParent(schema) {
|
|
188
|
+
return `CREATE INDEX IF NOT EXISTS job_dep_parent_idx ON ${schema}.job_dependency (parent_name, parent_id)`;
|
|
189
|
+
}
|
|
171
190
|
function jobTableFormatFunction(schema) {
|
|
172
191
|
return `
|
|
173
192
|
CREATE FUNCTION ${schema}.job_table_format(command text, table_name text)
|
|
@@ -286,7 +305,10 @@ function createTableJob(schema) {
|
|
|
286
305
|
dead_letter text,
|
|
287
306
|
policy text,
|
|
288
307
|
heartbeat_on timestamp with time zone,
|
|
289
|
-
heartbeat_seconds int
|
|
308
|
+
heartbeat_seconds int,
|
|
309
|
+
blocked boolean not null default false,
|
|
310
|
+
blocking boolean not null default false,
|
|
311
|
+
pending_dependencies int not null default 0
|
|
290
312
|
) PARTITION BY LIST (name)
|
|
291
313
|
`;
|
|
292
314
|
}
|
|
@@ -310,6 +332,9 @@ const JOB_COLUMNS_ALL = `${JOB_COLUMNS_MIN},
|
|
|
310
332
|
completed_on as "completedOn",
|
|
311
333
|
keep_until as "keepUntil",
|
|
312
334
|
dead_letter as "deadLetter",
|
|
335
|
+
blocked,
|
|
336
|
+
blocking,
|
|
337
|
+
pending_dependencies as "pendingDependencies",
|
|
313
338
|
output
|
|
314
339
|
`;
|
|
315
340
|
function createTableJobCommon(schema) {
|
|
@@ -443,11 +468,11 @@ function deleteQueueFunction(schema) {
|
|
|
443
468
|
LANGUAGE plpgsql;
|
|
444
469
|
`;
|
|
445
470
|
}
|
|
446
|
-
function createQueue(schema, name, options) {
|
|
471
|
+
export function createQueue(schema, name, options) {
|
|
447
472
|
const sql = `SELECT ${schema}.create_queue('${name}', '${JSON.stringify(options)}'::jsonb)`;
|
|
448
473
|
return locked(schema, sql, 'create-queue');
|
|
449
474
|
}
|
|
450
|
-
function deleteQueue(schema, name) {
|
|
475
|
+
export function deleteQueue(schema, name) {
|
|
451
476
|
const sql = `SELECT ${schema}.delete_queue('${name}')`;
|
|
452
477
|
return locked(schema, sql, 'delete-queue');
|
|
453
478
|
}
|
|
@@ -473,7 +498,7 @@ function createIndexJobThrottle(schema) {
|
|
|
473
498
|
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`;
|
|
474
499
|
}
|
|
475
500
|
function createIndexJobFetch(schema) {
|
|
476
|
-
return `CREATE INDEX job_i5 ON ${schema}.job (name, start_after) INCLUDE (priority, created_on, id) WHERE state < '${JOB_STATES.active}'`;
|
|
501
|
+
return `CREATE INDEX job_i5 ON ${schema}.job (name, start_after) INCLUDE (priority, created_on, id) WHERE state < '${JOB_STATES.active}' AND NOT blocked`;
|
|
477
502
|
}
|
|
478
503
|
function createIndexJobPolicyExclusive(schema) {
|
|
479
504
|
return `CREATE UNIQUE INDEX job_i6 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state <= '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.exclusive}'`;
|
|
@@ -487,16 +512,16 @@ function createCheckConstraintKeyStrictFifo(schema) {
|
|
|
487
512
|
function createIndexJobGroupConcurrency(schema) {
|
|
488
513
|
return `CREATE INDEX job_i7 ON ${schema}.job (name, group_id) WHERE state = '${JOB_STATES.active}' AND group_id IS NOT NULL`;
|
|
489
514
|
}
|
|
490
|
-
function trySetQueueMonitorTime(schema, queues, seconds) {
|
|
515
|
+
export function trySetQueueMonitorTime(schema, queues, seconds) {
|
|
491
516
|
return trySetQueueTimestamp(schema, queues, 'monitor_on', seconds);
|
|
492
517
|
}
|
|
493
|
-
function trySetQueueDeletionTime(schema, queues, seconds) {
|
|
518
|
+
export function trySetQueueDeletionTime(schema, queues, seconds) {
|
|
494
519
|
return trySetQueueTimestamp(schema, queues, 'maintain_on', seconds);
|
|
495
520
|
}
|
|
496
|
-
function trySetCronTime(schema, seconds) {
|
|
521
|
+
export function trySetCronTime(schema, seconds) {
|
|
497
522
|
return trySetTimestamp(schema, 'cron_on', seconds);
|
|
498
523
|
}
|
|
499
|
-
function trySetBamTime(schema, seconds) {
|
|
524
|
+
export function trySetBamTime(schema, seconds) {
|
|
500
525
|
return trySetTimestamp(schema, 'bam_on', seconds);
|
|
501
526
|
}
|
|
502
527
|
function trySetTimestamp(schema, column, seconds) {
|
|
@@ -519,7 +544,7 @@ function trySetQueueTimestamp(schema, queues, column, seconds) {
|
|
|
519
544
|
values: [queues]
|
|
520
545
|
};
|
|
521
546
|
}
|
|
522
|
-
function updateQueue(schema, { deadLetter } = {}) {
|
|
547
|
+
export function updateQueue(schema, { deadLetter } = {}) {
|
|
523
548
|
return `
|
|
524
549
|
WITH options as (SELECT $2::jsonb as data)
|
|
525
550
|
UPDATE ${schema}.queue SET
|
|
@@ -544,7 +569,7 @@ function updateQueue(schema, { deadLetter } = {}) {
|
|
|
544
569
|
WHERE name = $1
|
|
545
570
|
`;
|
|
546
571
|
}
|
|
547
|
-
function getQueues(schema, names) {
|
|
572
|
+
export function getQueues(schema, names) {
|
|
548
573
|
const hasNames = names && names.length > 0;
|
|
549
574
|
return {
|
|
550
575
|
text: `
|
|
@@ -576,7 +601,7 @@ function getQueues(schema, names) {
|
|
|
576
601
|
values: hasNames ? [names] : []
|
|
577
602
|
};
|
|
578
603
|
}
|
|
579
|
-
function deleteJobsById(schema, table) {
|
|
604
|
+
export function deleteJobsById(schema, table) {
|
|
580
605
|
return `
|
|
581
606
|
WITH results as (
|
|
582
607
|
DELETE FROM ${schema}.${table}
|
|
@@ -587,25 +612,25 @@ function deleteJobsById(schema, table) {
|
|
|
587
612
|
SELECT COUNT(*) from results
|
|
588
613
|
`;
|
|
589
614
|
}
|
|
590
|
-
function deleteQueuedJobs(schema, table) {
|
|
615
|
+
export function deleteQueuedJobs(schema, table) {
|
|
591
616
|
return `DELETE from ${schema}.${table} WHERE name = $1 and state < '${JOB_STATES.active}'`;
|
|
592
617
|
}
|
|
593
|
-
function deleteStoredJobs(schema, table) {
|
|
618
|
+
export function deleteStoredJobs(schema, table) {
|
|
594
619
|
return `DELETE from ${schema}.${table} WHERE name = $1 and state > '${JOB_STATES.active}'`;
|
|
595
620
|
}
|
|
596
|
-
function truncateTable(schema, table) {
|
|
621
|
+
export function truncateTable(schema, table) {
|
|
597
622
|
return `TRUNCATE ${schema}.${table}`;
|
|
598
623
|
}
|
|
599
|
-
function deleteAllJobs(schema, table) {
|
|
624
|
+
export function deleteAllJobs(schema, table) {
|
|
600
625
|
return `DELETE from ${schema}.${table} WHERE name = $1`;
|
|
601
626
|
}
|
|
602
|
-
function getSchedules(schema) {
|
|
627
|
+
export function getSchedules(schema) {
|
|
603
628
|
return `SELECT * FROM ${schema}.schedule ORDER BY name, key`;
|
|
604
629
|
}
|
|
605
|
-
function getSchedulesByQueue(schema) {
|
|
630
|
+
export function getSchedulesByQueue(schema) {
|
|
606
631
|
return `SELECT * FROM ${schema}.schedule WHERE name = $1 AND COALESCE(key, '') = $2`;
|
|
607
632
|
}
|
|
608
|
-
function schedule(schema) {
|
|
633
|
+
export function schedule(schema) {
|
|
609
634
|
return `
|
|
610
635
|
INSERT INTO ${schema}.schedule (name, key, cron, timezone, data, options)
|
|
611
636
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
@@ -617,14 +642,14 @@ function schedule(schema) {
|
|
|
617
642
|
updated_on = now()
|
|
618
643
|
`;
|
|
619
644
|
}
|
|
620
|
-
function unschedule(schema) {
|
|
645
|
+
export function unschedule(schema) {
|
|
621
646
|
return `
|
|
622
647
|
DELETE FROM ${schema}.schedule
|
|
623
648
|
WHERE name = $1
|
|
624
649
|
AND COALESCE(key, '') = $2
|
|
625
650
|
`;
|
|
626
651
|
}
|
|
627
|
-
function subscribe(schema) {
|
|
652
|
+
export function subscribe(schema) {
|
|
628
653
|
return `
|
|
629
654
|
INSERT INTO ${schema}.subscription (event, name)
|
|
630
655
|
VALUES ($1, $2)
|
|
@@ -634,28 +659,28 @@ function subscribe(schema) {
|
|
|
634
659
|
updated_on = now()
|
|
635
660
|
`;
|
|
636
661
|
}
|
|
637
|
-
function unsubscribe(schema) {
|
|
662
|
+
export function unsubscribe(schema) {
|
|
638
663
|
return `
|
|
639
664
|
DELETE FROM ${schema}.subscription
|
|
640
665
|
WHERE event = $1 and name = $2
|
|
641
666
|
`;
|
|
642
667
|
}
|
|
643
|
-
function getQueuesForEvent(schema) {
|
|
668
|
+
export function getQueuesForEvent(schema) {
|
|
644
669
|
return `
|
|
645
670
|
SELECT name FROM ${schema}.subscription
|
|
646
671
|
WHERE event = $1
|
|
647
672
|
`;
|
|
648
673
|
}
|
|
649
|
-
function getTime() {
|
|
674
|
+
export function getTime() {
|
|
650
675
|
return "SELECT round(date_part('epoch', now()) * 1000) as time";
|
|
651
676
|
}
|
|
652
|
-
function insertWarning(schema) {
|
|
677
|
+
export function insertWarning(schema) {
|
|
653
678
|
return `
|
|
654
679
|
INSERT INTO ${schema}.warning (type, message, data)
|
|
655
680
|
VALUES ($1, $2, $3)
|
|
656
681
|
`;
|
|
657
682
|
}
|
|
658
|
-
function getWarnings(schema) {
|
|
683
|
+
export function getWarnings(schema) {
|
|
659
684
|
return `
|
|
660
685
|
SELECT
|
|
661
686
|
id,
|
|
@@ -669,29 +694,29 @@ function getWarnings(schema) {
|
|
|
669
694
|
LIMIT $2 OFFSET $3
|
|
670
695
|
`;
|
|
671
696
|
}
|
|
672
|
-
function getWarningsCount(schema) {
|
|
697
|
+
export function getWarningsCount(schema) {
|
|
673
698
|
return `
|
|
674
699
|
SELECT COUNT(*)::int as count
|
|
675
700
|
FROM ${schema}.warning
|
|
676
701
|
WHERE ($1::text IS NULL OR type = $1)
|
|
677
702
|
`;
|
|
678
703
|
}
|
|
679
|
-
function deleteOldWarnings(schema, days) {
|
|
704
|
+
export function deleteOldWarnings(schema, days) {
|
|
680
705
|
return `
|
|
681
706
|
DELETE FROM ${schema}.warning
|
|
682
707
|
WHERE created_on < now() - interval '${days} days'
|
|
683
708
|
`;
|
|
684
709
|
}
|
|
685
|
-
function getVersion(schema) {
|
|
710
|
+
export function getVersion(schema) {
|
|
686
711
|
return `SELECT version from ${schema}.version`;
|
|
687
712
|
}
|
|
688
|
-
function setVersion(schema, version) {
|
|
713
|
+
export function setVersion(schema, version) {
|
|
689
714
|
return `UPDATE ${schema}.version SET version = '${version}'`;
|
|
690
715
|
}
|
|
691
|
-
function versionTableExists(schema) {
|
|
716
|
+
export function versionTableExists(schema) {
|
|
692
717
|
return `SELECT to_regclass('${schema}.version') as name`;
|
|
693
718
|
}
|
|
694
|
-
function insertVersion(schema, version) {
|
|
719
|
+
export function insertVersion(schema, version) {
|
|
695
720
|
return `INSERT INTO ${schema}.version(version) VALUES ('${version}')`;
|
|
696
721
|
}
|
|
697
722
|
function buildFetchParams(options) {
|
|
@@ -745,7 +770,7 @@ function buildFetchParams(options) {
|
|
|
745
770
|
}
|
|
746
771
|
return { values, ignoreSingletonsParam, ignoreGroupsParam, defaultGroupLimitParam, tiersParam, minPriorityParam, maxPriorityParam };
|
|
747
772
|
}
|
|
748
|
-
function fetchNextJob(options) {
|
|
773
|
+
export function fetchNextJob(options) {
|
|
749
774
|
const { schema, table, name, policy, limit, includeMetadata, priority = true, orderByCreatedOn = true, ignoreStartAfter = false, groupConcurrency, minPriority, maxPriority } = options;
|
|
750
775
|
const singletonFetch = limit > 1 && (policy === QUEUE_POLICIES.singleton || policy === QUEUE_POLICIES.stately);
|
|
751
776
|
const hasIgnoreSingletons = options.ignoreSingletons != null && options.ignoreSingletons.length > 0;
|
|
@@ -782,6 +807,7 @@ function fetchNextJob(options) {
|
|
|
782
807
|
const whereConditions = [
|
|
783
808
|
`j.name = '${name}'`,
|
|
784
809
|
`j.state < '${JOB_STATES.active}'`,
|
|
810
|
+
'NOT j.blocked',
|
|
785
811
|
!ignoreStartAfter ? 'j.start_after < now()' : '',
|
|
786
812
|
hasIgnoreSingletons ? `j.singleton_key <> ALL(${params.ignoreSingletonsParam})` : '',
|
|
787
813
|
hasIgnoreGroups ? `(j.group_id IS NULL OR j.group_id <> ALL(${params.ignoreGroupsParam}))` : '',
|
|
@@ -858,25 +884,52 @@ function fetchNextJob(options) {
|
|
|
858
884
|
values: params.values
|
|
859
885
|
};
|
|
860
886
|
}
|
|
861
|
-
function completeJobs(schema, table, includeQueued) {
|
|
862
|
-
const stateFilter = includeQueued
|
|
863
|
-
? `state < '${JOB_STATES.completed}'`
|
|
864
|
-
: `state = '${JOB_STATES.active}'`;
|
|
887
|
+
export function completeJobs(schema, table, includeQueued) {
|
|
865
888
|
return `
|
|
866
|
-
WITH
|
|
889
|
+
WITH completed AS (
|
|
867
890
|
UPDATE ${schema}.${table}
|
|
868
891
|
SET completed_on = now(),
|
|
869
892
|
state = '${JOB_STATES.completed}',
|
|
870
|
-
output = $3::jsonb
|
|
893
|
+
output = $3::jsonb,
|
|
894
|
+
blocked = ${includeQueued ? 'false' : 'blocked'},
|
|
895
|
+
pending_dependencies = ${includeQueued ? '0' : 'pending_dependencies'}
|
|
871
896
|
WHERE name = $1
|
|
872
897
|
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
873
|
-
AND ${
|
|
874
|
-
|
|
898
|
+
AND ${includeQueued
|
|
899
|
+
? `state < '${JOB_STATES.completed}'`
|
|
900
|
+
: `state = '${JOB_STATES.active}'`}
|
|
901
|
+
RETURNING name, id, blocking
|
|
902
|
+
),
|
|
903
|
+
decremented AS (
|
|
904
|
+
SELECT d.child_name, d.child_id, COUNT(*)::int AS n
|
|
905
|
+
FROM ${schema}.job_dependency d
|
|
906
|
+
JOIN completed c ON c.blocking
|
|
907
|
+
AND d.parent_name = c.name
|
|
908
|
+
AND d.parent_id = c.id
|
|
909
|
+
GROUP BY d.child_name, d.child_id
|
|
910
|
+
),
|
|
911
|
+
locked_children AS (
|
|
912
|
+
SELECT j.name, j.id, d.n
|
|
913
|
+
FROM ${schema}.job j
|
|
914
|
+
JOIN decremented d ON d.child_name = j.name
|
|
915
|
+
AND d.child_id = j.id
|
|
916
|
+
WHERE j.blocked
|
|
917
|
+
ORDER BY j.name, j.id
|
|
918
|
+
FOR UPDATE OF j
|
|
919
|
+
),
|
|
920
|
+
unblocked AS (
|
|
921
|
+
UPDATE ${schema}.job j
|
|
922
|
+
SET pending_dependencies = GREATEST(j.pending_dependencies - lc.n, 0),
|
|
923
|
+
blocked = GREATEST(j.pending_dependencies - lc.n, 0) > 0
|
|
924
|
+
FROM locked_children lc
|
|
925
|
+
WHERE j.name = lc.name
|
|
926
|
+
AND j.id = lc.id
|
|
927
|
+
RETURNING 1
|
|
875
928
|
)
|
|
876
|
-
SELECT COUNT(*) FROM
|
|
929
|
+
SELECT COUNT(*) FROM completed
|
|
877
930
|
`;
|
|
878
931
|
}
|
|
879
|
-
function cancelJobs(schema, table) {
|
|
932
|
+
export function cancelJobs(schema, table) {
|
|
880
933
|
return `
|
|
881
934
|
WITH results as (
|
|
882
935
|
UPDATE ${schema}.${table}
|
|
@@ -890,7 +943,7 @@ function cancelJobs(schema, table) {
|
|
|
890
943
|
SELECT COUNT(*) from results
|
|
891
944
|
`;
|
|
892
945
|
}
|
|
893
|
-
function resumeJobs(schema, table) {
|
|
946
|
+
export function resumeJobs(schema, table) {
|
|
894
947
|
return `
|
|
895
948
|
WITH results as (
|
|
896
949
|
UPDATE ${schema}.${table}
|
|
@@ -904,7 +957,7 @@ function resumeJobs(schema, table) {
|
|
|
904
957
|
SELECT COUNT(*) from results
|
|
905
958
|
`;
|
|
906
959
|
}
|
|
907
|
-
function restoreJobs(schema, table) {
|
|
960
|
+
export function restoreJobs(schema, table) {
|
|
908
961
|
return `
|
|
909
962
|
UPDATE ${schema}.${table}
|
|
910
963
|
SET state = '${JOB_STATES.created}',
|
|
@@ -914,7 +967,7 @@ function restoreJobs(schema, table) {
|
|
|
914
967
|
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
915
968
|
`;
|
|
916
969
|
}
|
|
917
|
-
function insertJobs(schema, { table, name, returnId = true }) {
|
|
970
|
+
export function insertJobs(schema, { table, name, returnId = true }) {
|
|
918
971
|
const sql = `
|
|
919
972
|
INSERT INTO ${schema}.${table} (
|
|
920
973
|
id,
|
|
@@ -935,7 +988,10 @@ function insertJobs(schema, { table, name, returnId = true }) {
|
|
|
935
988
|
retry_delay_max,
|
|
936
989
|
policy,
|
|
937
990
|
dead_letter,
|
|
938
|
-
heartbeat_seconds
|
|
991
|
+
heartbeat_seconds,
|
|
992
|
+
blocked,
|
|
993
|
+
blocking,
|
|
994
|
+
pending_dependencies
|
|
939
995
|
)
|
|
940
996
|
SELECT
|
|
941
997
|
COALESCE(id, gen_random_uuid()) as id,
|
|
@@ -959,7 +1015,10 @@ function insertJobs(schema, { table, name, returnId = true }) {
|
|
|
959
1015
|
COALESCE("retryDelayMax", q.retry_delay_max) as retry_delay_max,
|
|
960
1016
|
q.policy,
|
|
961
1017
|
COALESCE("deadLetter", q.dead_letter) as dead_letter,
|
|
962
|
-
COALESCE("heartbeatSeconds", q.heartbeat_seconds) as heartbeat_seconds
|
|
1018
|
+
COALESCE("heartbeatSeconds", q.heartbeat_seconds) as heartbeat_seconds,
|
|
1019
|
+
COALESCE(blocked, false) as blocked,
|
|
1020
|
+
COALESCE(blocking, false) as blocking,
|
|
1021
|
+
COALESCE("pendingDependencies", 0) as pending_dependencies
|
|
963
1022
|
FROM (
|
|
964
1023
|
SELECT *,
|
|
965
1024
|
CASE
|
|
@@ -984,7 +1043,10 @@ function insertJobs(schema, { table, name, returnId = true }) {
|
|
|
984
1043
|
"deleteAfterSeconds" integer,
|
|
985
1044
|
"retentionSeconds" integer,
|
|
986
1045
|
"deadLetter" text,
|
|
987
|
-
"heartbeatSeconds" integer
|
|
1046
|
+
"heartbeatSeconds" integer,
|
|
1047
|
+
blocked boolean,
|
|
1048
|
+
blocking boolean,
|
|
1049
|
+
"pendingDependencies" integer
|
|
988
1050
|
)
|
|
989
1051
|
) j
|
|
990
1052
|
JOIN ${schema}.queue q ON q.name = '${name}'
|
|
@@ -993,19 +1055,19 @@ function insertJobs(schema, { table, name, returnId = true }) {
|
|
|
993
1055
|
`;
|
|
994
1056
|
return sql;
|
|
995
1057
|
}
|
|
996
|
-
function failJobsById(schema, table) {
|
|
1058
|
+
export function failJobsById(schema, table) {
|
|
997
1059
|
const where = `name = $1 AND id IN (SELECT UNNEST($2::uuid[])) AND state < '${JOB_STATES.completed}'`;
|
|
998
1060
|
const output = '$3::jsonb';
|
|
999
1061
|
return failJobs(schema, table, where, output);
|
|
1000
1062
|
}
|
|
1001
|
-
function failJobsByTimeout(schema, table, queues) {
|
|
1063
|
+
export function failJobsByTimeout(schema, table, queues) {
|
|
1002
1064
|
const where = `state = '${JOB_STATES.active}'
|
|
1003
1065
|
AND (started_on + expire_seconds * interval '1s') < now()
|
|
1004
1066
|
AND name = ANY(${serializeArrayParam(queues)})`;
|
|
1005
1067
|
const output = '\'{ "value": { "message": "job timed out" } }\'::jsonb';
|
|
1006
1068
|
return locked(schema, failJobs(schema, table, where, output), table + 'failJobsByTimeout');
|
|
1007
1069
|
}
|
|
1008
|
-
function failJobsByHeartbeat(schema, table, queues) {
|
|
1070
|
+
export function failJobsByHeartbeat(schema, table, queues) {
|
|
1009
1071
|
const where = `state = '${JOB_STATES.active}'
|
|
1010
1072
|
AND heartbeat_seconds IS NOT NULL
|
|
1011
1073
|
AND (heartbeat_on + heartbeat_seconds * interval '1s') < now()
|
|
@@ -1013,7 +1075,7 @@ function failJobsByHeartbeat(schema, table, queues) {
|
|
|
1013
1075
|
const output = '\'{ "value": { "message": "job heartbeat timeout" } }\'::jsonb';
|
|
1014
1076
|
return locked(schema, failJobs(schema, table, where, output), table + 'failJobsByHeartbeat');
|
|
1015
1077
|
}
|
|
1016
|
-
function touchJobs(schema, table) {
|
|
1078
|
+
export function touchJobs(schema, table) {
|
|
1017
1079
|
return `
|
|
1018
1080
|
WITH results AS (
|
|
1019
1081
|
UPDATE ${schema}.${table}
|
|
@@ -1060,7 +1122,10 @@ function failJobs(schema, table, where, output) {
|
|
|
1060
1122
|
output,
|
|
1061
1123
|
dead_letter,
|
|
1062
1124
|
heartbeat_on,
|
|
1063
|
-
heartbeat_seconds
|
|
1125
|
+
heartbeat_seconds,
|
|
1126
|
+
blocked,
|
|
1127
|
+
blocking,
|
|
1128
|
+
pending_dependencies
|
|
1064
1129
|
)
|
|
1065
1130
|
SELECT
|
|
1066
1131
|
id,
|
|
@@ -1100,7 +1165,10 @@ function failJobs(schema, table, where, output) {
|
|
|
1100
1165
|
${output},
|
|
1101
1166
|
dead_letter,
|
|
1102
1167
|
NULL as heartbeat_on,
|
|
1103
|
-
heartbeat_seconds
|
|
1168
|
+
heartbeat_seconds,
|
|
1169
|
+
blocked,
|
|
1170
|
+
blocking,
|
|
1171
|
+
pending_dependencies
|
|
1104
1172
|
FROM deleted_jobs
|
|
1105
1173
|
ON CONFLICT DO NOTHING
|
|
1106
1174
|
RETURNING *
|
|
@@ -1132,7 +1200,10 @@ function failJobs(schema, table, where, output) {
|
|
|
1132
1200
|
output,
|
|
1133
1201
|
dead_letter,
|
|
1134
1202
|
heartbeat_on,
|
|
1135
|
-
heartbeat_seconds
|
|
1203
|
+
heartbeat_seconds,
|
|
1204
|
+
blocked,
|
|
1205
|
+
blocking,
|
|
1206
|
+
pending_dependencies
|
|
1136
1207
|
)
|
|
1137
1208
|
SELECT
|
|
1138
1209
|
id,
|
|
@@ -1160,7 +1231,10 @@ function failJobs(schema, table, where, output) {
|
|
|
1160
1231
|
${output},
|
|
1161
1232
|
dead_letter,
|
|
1162
1233
|
NULL as heartbeat_on,
|
|
1163
|
-
heartbeat_seconds
|
|
1234
|
+
heartbeat_seconds,
|
|
1235
|
+
blocked,
|
|
1236
|
+
blocking,
|
|
1237
|
+
pending_dependencies
|
|
1164
1238
|
FROM deleted_jobs
|
|
1165
1239
|
WHERE id NOT IN (SELECT id from retried_jobs)
|
|
1166
1240
|
RETURNING *
|
|
@@ -1188,7 +1262,7 @@ function failJobs(schema, table, where, output) {
|
|
|
1188
1262
|
SELECT COUNT(*) FROM results
|
|
1189
1263
|
`;
|
|
1190
1264
|
}
|
|
1191
|
-
function deletion(schema, table, queues) {
|
|
1265
|
+
export function deletion(schema, table, queues) {
|
|
1192
1266
|
const sql = `
|
|
1193
1267
|
DELETE FROM ${schema}.${table}
|
|
1194
1268
|
WHERE name = ANY(${serializeArrayParam(queues)})
|
|
@@ -1201,7 +1275,7 @@ function deletion(schema, table, queues) {
|
|
|
1201
1275
|
`;
|
|
1202
1276
|
return locked(schema, sql, table + 'deletion');
|
|
1203
1277
|
}
|
|
1204
|
-
function retryJobs(schema, table) {
|
|
1278
|
+
export function retryJobs(schema, table) {
|
|
1205
1279
|
return `
|
|
1206
1280
|
WITH results as (
|
|
1207
1281
|
UPDATE ${schema}.job
|
|
@@ -1215,7 +1289,7 @@ function retryJobs(schema, table) {
|
|
|
1215
1289
|
SELECT COUNT(*) from results
|
|
1216
1290
|
`;
|
|
1217
1291
|
}
|
|
1218
|
-
function getQueueStats(schema, table, queues) {
|
|
1292
|
+
export function getQueueStats(schema, table, queues) {
|
|
1219
1293
|
return {
|
|
1220
1294
|
text: `
|
|
1221
1295
|
SELECT
|
|
@@ -1232,7 +1306,7 @@ function getQueueStats(schema, table, queues) {
|
|
|
1232
1306
|
values: [queues]
|
|
1233
1307
|
};
|
|
1234
1308
|
}
|
|
1235
|
-
function cacheQueueStats(schema, table, queues) {
|
|
1309
|
+
export function cacheQueueStats(schema, table, queues) {
|
|
1236
1310
|
const statsQuery = getQueueStats(schema, table, queues);
|
|
1237
1311
|
// Serialize the $1 parameter for use in locked() multi-statement query
|
|
1238
1312
|
const statsText = statsQuery.text.replace('$1::text[]', serializeArrayParam(queues));
|
|
@@ -1258,31 +1332,37 @@ function cacheQueueStats(schema, table, queues) {
|
|
|
1258
1332
|
return locked(schema, sql, 'queue-stats');
|
|
1259
1333
|
}
|
|
1260
1334
|
// Serialize a string array for embedding directly in SQL as PostgreSQL array literal
|
|
1261
|
-
function serializeArrayParam(values) {
|
|
1335
|
+
export function serializeArrayParam(values) {
|
|
1262
1336
|
const escaped = values.map(v => `'${v.replace(SINGLE_QUOTE_REGEX, "''")}'`);
|
|
1263
1337
|
return `ARRAY[${escaped.join(',')}]::text[]`;
|
|
1264
1338
|
}
|
|
1265
|
-
|
|
1339
|
+
// Serialize a JSON-serializable value for embedding directly in SQL as a quoted literal
|
|
1340
|
+
export function serializeJsonParam(value) {
|
|
1341
|
+
return `'${JSON.stringify(value).replace(SINGLE_QUOTE_REGEX, "''")}'`;
|
|
1342
|
+
}
|
|
1343
|
+
export function transaction(query) {
|
|
1266
1344
|
const sql = Array.isArray(query) ? query.join(';\n') : query;
|
|
1267
1345
|
return `
|
|
1268
1346
|
BEGIN;
|
|
1269
1347
|
SET LOCAL lock_timeout = 30000;
|
|
1270
1348
|
SET LOCAL idle_in_transaction_session_timeout = 30000;
|
|
1271
|
-
${advisoryLock(schema, key)};
|
|
1272
1349
|
${sql};
|
|
1273
1350
|
COMMIT;
|
|
1274
1351
|
`;
|
|
1275
1352
|
}
|
|
1353
|
+
export function locked(schema, query, key) {
|
|
1354
|
+
return transaction([advisoryLock(schema, key), ...(Array.isArray(query) ? query : [query])]);
|
|
1355
|
+
}
|
|
1276
1356
|
function advisoryLock(schema, key) {
|
|
1277
1357
|
return `SELECT pg_advisory_xact_lock(
|
|
1278
1358
|
('x' || encode(sha224((current_database() || '.pgboss.${schema}${key || ''}')::bytea), 'hex'))::bit(64)::bigint
|
|
1279
1359
|
)`;
|
|
1280
1360
|
}
|
|
1281
|
-
function assertMigration(schema, version) {
|
|
1361
|
+
export function assertMigration(schema, version) {
|
|
1282
1362
|
// raises 'division by zero' if already on desired schema version
|
|
1283
1363
|
return `SELECT version::int/(version::int-${version}) from ${schema}.version`;
|
|
1284
1364
|
}
|
|
1285
|
-
function findJobs(schema, table, options) {
|
|
1365
|
+
export function findJobs(schema, table, options) {
|
|
1286
1366
|
const { queued, byKey, byData, byId } = options;
|
|
1287
1367
|
let paramIndex = 1;
|
|
1288
1368
|
const whereConditions = [];
|
|
@@ -1308,7 +1388,7 @@ function findJobs(schema, table, options) {
|
|
|
1308
1388
|
${whereConditions.join('\n ')}
|
|
1309
1389
|
`;
|
|
1310
1390
|
}
|
|
1311
|
-
function getJobById(schema, table) {
|
|
1391
|
+
export function getJobById(schema, table) {
|
|
1312
1392
|
return `
|
|
1313
1393
|
SELECT ${JOB_COLUMNS_ALL}
|
|
1314
1394
|
FROM ${schema}.${table}
|
|
@@ -1316,7 +1396,50 @@ function getJobById(schema, table) {
|
|
|
1316
1396
|
AND id = $2
|
|
1317
1397
|
`;
|
|
1318
1398
|
}
|
|
1319
|
-
function
|
|
1399
|
+
export function insertDependencies(schema) {
|
|
1400
|
+
return `
|
|
1401
|
+
INSERT INTO ${schema}.job_dependency (child_name, child_id, parent_name, parent_id)
|
|
1402
|
+
SELECT child_name, child_id, parent_name, parent_id
|
|
1403
|
+
FROM json_to_recordset($1::json) AS x (
|
|
1404
|
+
child_name text,
|
|
1405
|
+
child_id uuid,
|
|
1406
|
+
parent_name text,
|
|
1407
|
+
parent_id uuid
|
|
1408
|
+
)
|
|
1409
|
+
ON CONFLICT DO NOTHING
|
|
1410
|
+
`;
|
|
1411
|
+
}
|
|
1412
|
+
export function getDependencies(schema) {
|
|
1413
|
+
return `
|
|
1414
|
+
SELECT parent_name as "parentName", parent_id as "parentId"
|
|
1415
|
+
FROM ${schema}.job_dependency
|
|
1416
|
+
WHERE child_name = $1 AND child_id = $2
|
|
1417
|
+
`;
|
|
1418
|
+
}
|
|
1419
|
+
export function getDependents(schema) {
|
|
1420
|
+
return `
|
|
1421
|
+
SELECT child_name as "childName", child_id as "childId"
|
|
1422
|
+
FROM ${schema}.job_dependency
|
|
1423
|
+
WHERE parent_name = $1 AND parent_id = $2
|
|
1424
|
+
`;
|
|
1425
|
+
}
|
|
1426
|
+
export function cleanupDependencies(schema, table, queues) {
|
|
1427
|
+
const sql = `
|
|
1428
|
+
DELETE FROM ${schema}.job_dependency
|
|
1429
|
+
WHERE (child_name = ANY(${serializeArrayParam(queues)})
|
|
1430
|
+
AND NOT EXISTS (
|
|
1431
|
+
SELECT 1 FROM ${schema}.${table} j
|
|
1432
|
+
WHERE j.name = child_name AND j.id = child_id
|
|
1433
|
+
))
|
|
1434
|
+
OR (parent_name = ANY(${serializeArrayParam(queues)})
|
|
1435
|
+
AND NOT EXISTS (
|
|
1436
|
+
SELECT 1 FROM ${schema}.${table} j
|
|
1437
|
+
WHERE j.name = parent_name AND j.id = parent_id
|
|
1438
|
+
))
|
|
1439
|
+
`;
|
|
1440
|
+
return locked(schema, sql, table + 'cleanupDependencies');
|
|
1441
|
+
}
|
|
1442
|
+
export function getBlockedKeys(schema, table) {
|
|
1320
1443
|
return `
|
|
1321
1444
|
SELECT DISTINCT singleton_key as "singletonKey"
|
|
1322
1445
|
FROM ${schema}.${table}
|
|
@@ -1325,7 +1448,7 @@ function getBlockedKeys(schema, table) {
|
|
|
1325
1448
|
AND policy = '${QUEUE_POLICIES.key_strict_fifo}'
|
|
1326
1449
|
`;
|
|
1327
1450
|
}
|
|
1328
|
-
function getNextBamCommand(schema) {
|
|
1451
|
+
export function getNextBamCommand(schema) {
|
|
1329
1452
|
return `
|
|
1330
1453
|
UPDATE ${schema}.bam
|
|
1331
1454
|
SET status = 'in_progress', started_on = now()
|
|
@@ -1340,14 +1463,14 @@ function getNextBamCommand(schema) {
|
|
|
1340
1463
|
created_on as "createdOn", started_on as "startedOn", completed_on as "completedOn"
|
|
1341
1464
|
`;
|
|
1342
1465
|
}
|
|
1343
|
-
function setBamCompleted(schema, id) {
|
|
1466
|
+
export function setBamCompleted(schema, id) {
|
|
1344
1467
|
return `
|
|
1345
1468
|
UPDATE ${schema}.bam
|
|
1346
1469
|
SET status = 'completed', completed_on = now()
|
|
1347
1470
|
WHERE id = '${id}'
|
|
1348
1471
|
`;
|
|
1349
1472
|
}
|
|
1350
|
-
function setBamFailed(schema, id, error) {
|
|
1473
|
+
export function setBamFailed(schema, id, error) {
|
|
1351
1474
|
const escapedError = error.replace(/'/g, "''");
|
|
1352
1475
|
return `
|
|
1353
1476
|
UPDATE ${schema}.bam
|
|
@@ -1355,14 +1478,14 @@ function setBamFailed(schema, id, error) {
|
|
|
1355
1478
|
WHERE id = '${id}'
|
|
1356
1479
|
`;
|
|
1357
1480
|
}
|
|
1358
|
-
function getBamStatus(schema) {
|
|
1481
|
+
export function getBamStatus(schema) {
|
|
1359
1482
|
return `
|
|
1360
1483
|
SELECT status, count(*)::int as count, max(created_on) as "lastCreatedOn"
|
|
1361
1484
|
FROM ${schema}.bam
|
|
1362
1485
|
GROUP BY status
|
|
1363
1486
|
`;
|
|
1364
1487
|
}
|
|
1365
|
-
function getBamEntries(schema) {
|
|
1488
|
+
export function getBamEntries(schema) {
|
|
1366
1489
|
return `
|
|
1367
1490
|
SELECT id, name, version, status, queue, table_name as "table", command, error,
|
|
1368
1491
|
created_on as "createdOn", started_on as "startedOn", completed_on as "completedOn"
|
|
@@ -1370,4 +1493,3 @@ function getBamEntries(schema) {
|
|
|
1370
1493
|
ORDER BY version, created_on
|
|
1371
1494
|
`;
|
|
1372
1495
|
}
|
|
1373
|
-
export { create, insertVersion, getVersion, setVersion, versionTableExists, fetchNextJob, completeJobs, cancelJobs, resumeJobs, restoreJobs, retryJobs, findJobs, deleteJobsById, deleteAllJobs, deleteQueuedJobs, deleteStoredJobs, truncateTable, failJobsById, failJobsByTimeout, failJobsByHeartbeat, touchJobs, insertJobs, getTime, getSchedules, getSchedulesByQueue, schedule, unschedule, subscribe, unsubscribe, getQueuesForEvent, deletion, cacheQueueStats, updateQueue, createQueue, deleteQueue, getQueues, getQueueStats, trySetQueueMonitorTime, trySetQueueDeletionTime, trySetCronTime, trySetBamTime, locked, assertMigration, getJobById, insertWarning, getWarnings, getWarningsCount, deleteOldWarnings, createTableWarning, createIndexWarning, getBlockedKeys, getNextBamCommand, setBamCompleted, setBamFailed, getBamStatus, getBamEntries, serializeArrayParam, QUEUE_POLICIES, JOB_STATES, MIGRATE_RACE_MESSAGE, CREATE_RACE_MESSAGE, DEFAULT_SCHEMA, };
|