pg-boss 12.18.3 → 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/dist/plans.js CHANGED
@@ -1,11 +1,14 @@
1
- const DEFAULT_SCHEMA = 'pgboss';
2
- const MIGRATE_RACE_MESSAGE = 'division by zero';
3
- const CREATE_RACE_MESSAGE = 'already exists';
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 results AS (
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 ${stateFilter}
874
- RETURNING *
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 results
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
- function locked(schema, query, key) {
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 getBlockedKeys(schema, table) {
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, };