pg-boss 12.33.6 → 12.33.7

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
@@ -1961,8 +1961,8 @@ export function insertJobs(schema, { table, name, returnId = true, notify = fals
1961
1961
  // When notify is enabled we always RETURN start_after so the wrapper below can gate
1962
1962
  // the NOTIFY on immediate availability, regardless of whether the caller wants ids.
1963
1963
  const returning = notify ? 'RETURNING id, start_after' : returnId ? 'RETURNING id' : '';
1964
- // A caller that knows the slot names it outright: the cron pass files a rule occurrence in the
1965
- // slot the occurrence falls in, and an offset off now() cannot pin that, since now() here is
1964
+ // A caller that knows the slot names it outright: the cron pass files an occurrence in the slot
1965
+ // the occurrence falls in, and an offset off now() cannot pin that, since now() here is
1966
1966
  // insert time. Only in the statement the pass asks for, because insert() stringifies caller
1967
1967
  // objects straight into the recordset below, so a column declared for everyone would be a live,
1968
1968
  // undeclared and unvalidated option on the public path, where a bad value surfaces as a raw
@@ -127,9 +127,13 @@ declare class Timekeeper extends EventEmitter implements types.EventsMixin {
127
127
  * job is what keeps the passes that follow from sending it a second time.
128
128
  *
129
129
  * The window rather than its most recent point, since a rule can put two occurrences inside it
130
- * and a read that answers with one of them drops the other. A cron expression cannot: its finest
131
- * resolution is a second, and consecutive occurrences a second apart share a throttle slot, so
132
- * only the most recent one can produce a job.
130
+ * and a read that answers with one of them drops the other. A cron expression needs only the
131
+ * most recent one, although two of its occurrences either side of a minute fall in slots of
132
+ * their own: the minutes an expression matches are a minute apart at the closest, and passes run
133
+ * closer together than the window is wide, so one lands while a matching minute's first
134
+ * occurrence is still due and before any later minute has come round. It reads an occurrence of
135
+ * that minute and files the job under it. An occurrence this read passes over falls in a minute
136
+ * a pass before it answered for.
133
137
  */
134
138
  private readOccurrences;
135
139
  private reportSendItError;
@@ -1 +1 @@
1
- {"version":3,"file":"timekeeper.d.ts","sourceRoot":"","sources":["../src/timekeeper.ts"],"names":[],"mappings":"AAEA,OAAO,YAAY,MAAM,aAAa,CAAA;AAItC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAKvC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAGnC,eAAO,MAAM,MAAM;;CAElB,CAAA;AAwBD,kGAAkG;AAClG,eAAO,MAAM,iBAAiB,OAAO,CAAA;AA6MrC,cAAM,UAAW,SAAQ,YAAa,YAAW,KAAK,CAAC,WAAW;IAChE,EAAE,EAAE,KAAK,CAAC,SAAS,CAAA;IACnB,MAAM,EAAE,KAAK,CAAC,0BAA0B,CAAA;IACxC,OAAO,EAAE,OAAO,CAAA;IAEhB,OAAO,CAAC,OAAO,CAAO;IACtB,OAAO,CAAC,gBAAgB,CAA+B;IACvD,OAAO,CAAC,gBAAgB,CAAqC;IAC7D,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,aAAa,CAAQ;IAS7B,OAAO,CAAC,eAAe,CAAoB;IAE3C,SAAS,SAAI;IACb,MAAM;;;;MAAS;gBAEF,EAAE,EAAE,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,0BAA0B;IAQ5F,IAAI,YAAY,IAAK,OAAO,CAE3B;IAMD,OAAO,KAAK,YAAY,GAEvB;IAED,OAAO,KAAK,cAAc,GASzB;IAEK,KAAK;IA4BL,IAAI;IAmCV,OAAO,CAAC,mBAAmB;IAUrB,cAAc;IA8Cd,MAAM;IAgDZ;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,eAAe;IAQvB;;;;;OAKG;IACG,IAAI,CAAE,WAAW,GAAE,OAAc;IA0IvC,YAAY,CAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,GAAE,KAAK,CAAC,YAAwC;IAIlG;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,OAAO,CAAC,gBAAgB;IAgBxB;;;;;;;OAOG;IACH,OAAO,CAAC,sBAAsB;IAgB9B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,iBAAiB;YAQX,QAAQ;YAsDR,aAAa;IAerB,YAAY,CAAE,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;IAiBrE,WAAW,CAAE,IAAI,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;IAe1E;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAYxB;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,KAAK,CAAC,sBAA2B,GAAG,IAAI,EAAE;IA0C5E,QAAQ,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,KAAK,CAAC,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IA6CzG,UAAU,CAAE,IAAI,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,OAAO,CAAC,IAAI,CAAC;CAIzD;AAED,eAAe,UAAU,CAAA"}
1
+ {"version":3,"file":"timekeeper.d.ts","sourceRoot":"","sources":["../src/timekeeper.ts"],"names":[],"mappings":"AAEA,OAAO,YAAY,MAAM,aAAa,CAAA;AAItC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAKvC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAGnC,eAAO,MAAM,MAAM;;CAElB,CAAA;AAwBD,kGAAkG;AAClG,eAAO,MAAM,iBAAiB,OAAO,CAAA;AA8MrC,cAAM,UAAW,SAAQ,YAAa,YAAW,KAAK,CAAC,WAAW;IAChE,EAAE,EAAE,KAAK,CAAC,SAAS,CAAA;IACnB,MAAM,EAAE,KAAK,CAAC,0BAA0B,CAAA;IACxC,OAAO,EAAE,OAAO,CAAA;IAEhB,OAAO,CAAC,OAAO,CAAO;IACtB,OAAO,CAAC,gBAAgB,CAA+B;IACvD,OAAO,CAAC,gBAAgB,CAAqC;IAC7D,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,aAAa,CAAQ;IAS7B,OAAO,CAAC,eAAe,CAAoB;IAE3C,SAAS,SAAI;IACb,MAAM;;;;MAAS;gBAEF,EAAE,EAAE,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,0BAA0B;IAQ5F,IAAI,YAAY,IAAK,OAAO,CAE3B;IAMD,OAAO,KAAK,YAAY,GAEvB;IAED,OAAO,KAAK,cAAc,GASzB;IAEK,KAAK;IA4BL,IAAI;IAmCV,OAAO,CAAC,mBAAmB;IAUrB,cAAc;IA8Cd,MAAM;IAgDZ;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,eAAe;IAQvB;;;;;OAKG;IACG,IAAI,CAAE,WAAW,GAAE,OAAc;IAoIvC,YAAY,CAAE,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,GAAE,KAAK,CAAC,YAAwC;IAIlG;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,OAAO,CAAC,gBAAgB;IAgBxB;;;;;;;OAOG;IACH,OAAO,CAAC,sBAAsB;IAgB9B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,iBAAiB;YAQX,QAAQ;YAsDR,aAAa;IAerB,YAAY,CAAE,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;IAiBrE,WAAW,CAAE,IAAI,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;IAe1E;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAYxB;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,KAAK,CAAC,sBAA2B,GAAG,IAAI,EAAE;IA0C5E,QAAQ,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,KAAK,CAAC,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IA6CzG,UAAU,CAAE,IAAI,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,OAAO,CAAC,IAAI,CAAC;CAIzD;AAED,eAAe,UAAU,CAAA"}
@@ -469,46 +469,41 @@ class Timekeeper extends EventEmitter {
469
469
  // The payload carries the schedule's key beside its queue name, so the send-it handler knows
470
470
  // which row an occurrence came from and can record the job it produced.
471
471
  const forwarded = { data: { name, key, data, options }, singletonKey: occurrenceKey(name, key) };
472
- // A recurrence rule can put an occurrence anywhere in the minute, and a slot measured from
473
- // insert time would then straddle it: two passes on either side of a slot boundary both find
474
- // the occurrence inside the window and file it in a slot of their own, sending it twice. So a
475
- // rule occurrence names the slot it falls in outright. An offset from the insert's own now()
476
- // would not pin it: everything between reading the clock here and the insert committing
477
- // counts towards the shifted instant, which lands in the next slot whenever that adds up to a
478
- // boundary crossing.
472
+ // An occurrence can fall anywhere in the minute, a rule wherever it says and a cron
473
+ // expression on the second its sixth placeholder names, and a slot measured from insert
474
+ // time would then straddle it: two passes on either side of a slot boundary both find the occurrence
475
+ // inside the window and file it in a slot of their own, sending it twice. So an occurrence
476
+ // names the slot it falls in outright. An offset from the insert's own now() would not pin
477
+ // it: everything between reading the clock here and the insert committing counts towards
478
+ // the shifted instant, which lands in the next slot whenever that adds up to a boundary
479
+ // crossing.
480
+ //
481
+ // An instance on a release that files a cron occurrence from insert time agrees with this
482
+ // slot wherever that insert lands in the occurrence's own minute, and disagrees only where
483
+ // it would already have sent the occurrence twice, so a rolling upgrade sends no more jobs
484
+ // than the older release does alone.
479
485
  //
480
486
  // A missed occurrence names its slot for that reason and one more: it is older than the
481
487
  // window, so a slot off insert time would file it in the slot the pass runs in, where it
482
- // would collide with the cron job filed below and be dropped. The slot it names is older
483
- // than the one insert time computes, so it cannot.
488
+ // would collide with the job of the occurrence due now and be dropped. The slot it names is
489
+ // older than the one insert time computes, so it cannot.
484
490
  //
485
491
  // One job per slot rather than one per occurrence, which is the resolution the docs promise:
486
- // a rule finer than a slot sends a job a slot, and two occurrences inside one window that
487
- // fall in slots of their own each send.
492
+ // an expression finer than a slot sends a job a slot, and two occurrences inside one window
493
+ // that fall in slots of their own each send.
488
494
  const slots = new Set();
489
495
  if (missed !== null) {
490
496
  slots.add(throttleSlot(missed));
491
497
  }
492
- if (due.kind === plans.SCHEDULE_KINDS.rrule) {
493
- // Through the set the missed occurrences went through, since the window's lower bound falls
494
- // inside a slot rather than on one: an occurrence on the bound is missed, one a millisecond
495
- // later is due, and both belong to the same slot and so to the same job.
496
- for (const occurrence of due.occurrences) {
497
- slots.add(throttleSlot(occurrence));
498
- }
498
+ // Through the set the missed occurrence went through, since the window's lower bound falls
499
+ // inside a slot rather than on one: an occurrence on the bound is missed, one a millisecond
500
+ // later is due, and both belong to the same slot and so to the same job.
501
+ for (const occurrence of due.occurrences) {
502
+ slots.add(throttleSlot(occurrence));
499
503
  }
500
504
  for (const slot of slots) {
501
505
  scheduled.push({ ...forwarded, data: { ...forwarded.data, slot }, __singletonSlot: slot });
502
506
  }
503
- // Anything not read as a rule is read as cron, which is what a row carrying no kind at all
504
- // means: the column defaults to cron, and a reader that cannot see it reads the row the way
505
- // every release before the column did.
506
- if (due.kind !== plans.SCHEDULE_KINDS.rrule && due.occurrences.length > 0) {
507
- // A cron occurrence keeps the slot every release has always filed it in, since an instance
508
- // still running an older one during a rolling upgrade computes that slot and nothing else,
509
- // and a slot the two disagree on collapses nothing.
510
- scheduled.push({ ...forwarded, singletonSeconds: OCCURRENCE_WINDOW_SECONDS });
511
- }
512
507
  }
513
508
  this.warnedSchedules = stillBroken;
514
509
  if (scheduled.length > 0 && !this.stopped) {
@@ -624,9 +619,13 @@ class Timekeeper extends EventEmitter {
624
619
  * job is what keeps the passes that follow from sending it a second time.
625
620
  *
626
621
  * The window rather than its most recent point, since a rule can put two occurrences inside it
627
- * and a read that answers with one of them drops the other. A cron expression cannot: its finest
628
- * resolution is a second, and consecutive occurrences a second apart share a throttle slot, so
629
- * only the most recent one can produce a job.
622
+ * and a read that answers with one of them drops the other. A cron expression needs only the
623
+ * most recent one, although two of its occurrences either side of a minute fall in slots of
624
+ * their own: the minutes an expression matches are a minute apart at the closest, and passes run
625
+ * closer together than the window is wide, so one lands while a matching minute's first
626
+ * occurrence is still due and before any later minute has come round. It reads an occurrence of
627
+ * that minute and files the job under it. An occurrence this read passes over falls in a minute
628
+ * a pass before it answered for.
630
629
  */
631
630
  readOccurrences(expression, kind, tz, databaseTime) {
632
631
  const window = new Date(databaseTime - OCCURRENCE_WINDOW_SECONDS * 1000);
@@ -684,9 +683,9 @@ class Timekeeper extends EventEmitter {
684
683
  continue;
685
684
  }
686
685
  const id = JSON.stringify([name, key]);
687
- // A cron occurrence in the due window names no slot, since the insert files it from its own
688
- // clock. That slot is the one the pass is running in, which is later than every slot a
689
- // catch-up occurrence can name, so the current one stands in for it.
686
+ // A cron occurrence in the due window written by an older instance names no slot, since that
687
+ // insert filed it from its own clock. That slot is the one the pass was running in, which is
688
+ // later than every slot a catch-up occurrence can name, so the current one stands in for it.
690
689
  const filed = slot ?? throttleSlot(new Date(this.databaseTime));
691
690
  const latest = fired.get(id);
692
691
  if (latest === undefined || latest.slot <= filed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pg-boss",
3
- "version": "12.33.6",
3
+ "version": "12.33.7",
4
4
  "description": "Queueing jobs in Postgres from Node.js like a boss",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",