@monque/core 1.5.0 → 1.5.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @monque/core
2
2
 
3
+ ## 1.5.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#227](https://github.com/ueberBrot/monque/pull/227) [`e9208ca`](https://github.com/ueberBrot/monque/commit/e9208ca5c985d84d023161560d5d3ba195394fe1) Thanks [@ueberBrot](https://github.com/ueberBrot)! - Prevent change stream reconnection attempts from running after the scheduler stops. This clears pending reconnect timers during shutdown and adds coverage for the stop-during-backoff scenario.
8
+
9
+ ## 1.5.1
10
+
11
+ ### Patch Changes
12
+
13
+ - [#211](https://github.com/ueberBrot/monque/pull/211) [`7181215`](https://github.com/ueberBrot/monque/commit/7181215abac2b5cd231c63f10bf718f0314cc09f) Thanks [@ueberBrot](https://github.com/ueberBrot)! - Close shutdown race condition window by stopping timers before setting isRunning flag
14
+
3
15
  ## 1.5.0
4
16
 
5
17
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -1,9 +1,8 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let mongodb = require("mongodb");
3
3
  let cron_parser = require("cron-parser");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_events = require("node:events");
6
-
7
6
  //#region src/jobs/document-to-persisted-job.ts
8
7
  /**
9
8
  * Convert a raw MongoDB document to a strongly-typed {@link PersistedJob}.
@@ -36,7 +35,6 @@ function documentToPersistedJob(doc) {
36
35
  if (doc["uniqueKey"] !== void 0) job.uniqueKey = doc["uniqueKey"];
37
36
  return job;
38
37
  }
39
-
40
38
  //#endregion
41
39
  //#region src/jobs/types.ts
42
40
  /**
@@ -75,7 +73,6 @@ const CursorDirection = {
75
73
  FORWARD: "forward",
76
74
  BACKWARD: "backward"
77
75
  };
78
-
79
76
  //#endregion
80
77
  //#region src/jobs/guards.ts
81
78
  /**
@@ -288,7 +285,6 @@ function isCancelledJob(job) {
288
285
  function isRecurringJob(job) {
289
286
  return job.repeatInterval !== void 0 && job.repeatInterval !== null;
290
287
  }
291
-
292
288
  //#endregion
293
289
  //#region src/shared/errors.ts
294
290
  /**
@@ -506,7 +502,6 @@ var PayloadTooLargeError = class PayloadTooLargeError extends MonqueError {
506
502
  if (Error.captureStackTrace) Error.captureStackTrace(this, PayloadTooLargeError);
507
503
  }
508
504
  };
509
-
510
505
  //#endregion
511
506
  //#region src/shared/utils/backoff.ts
512
507
  /**
@@ -548,7 +543,7 @@ const DEFAULT_MAX_BACKOFF_DELAY = 1440 * 60 * 1e3;
548
543
  * ```
549
544
  */
550
545
  function calculateBackoff(failCount, baseInterval = DEFAULT_BASE_INTERVAL, maxDelay) {
551
- const effectiveMaxDelay = maxDelay ?? DEFAULT_MAX_BACKOFF_DELAY;
546
+ const effectiveMaxDelay = maxDelay ?? 864e5;
552
547
  let delay = 2 ** failCount * baseInterval;
553
548
  if (delay > effectiveMaxDelay) delay = effectiveMaxDelay;
554
549
  return new Date(Date.now() + delay);
@@ -562,12 +557,11 @@ function calculateBackoff(failCount, baseInterval = DEFAULT_BASE_INTERVAL, maxDe
562
557
  * @returns The delay in milliseconds
563
558
  */
564
559
  function calculateBackoffDelay(failCount, baseInterval = DEFAULT_BASE_INTERVAL, maxDelay) {
565
- const effectiveMaxDelay = maxDelay ?? DEFAULT_MAX_BACKOFF_DELAY;
560
+ const effectiveMaxDelay = maxDelay ?? 864e5;
566
561
  let delay = 2 ** failCount * baseInterval;
567
562
  if (delay > effectiveMaxDelay) delay = effectiveMaxDelay;
568
563
  return delay;
569
564
  }
570
-
571
565
  //#endregion
572
566
  //#region src/shared/utils/cron.ts
573
567
  /**
@@ -621,7 +615,6 @@ function validateCronExpression(expression) {
621
615
  function handleCronParseError(expression, error) {
622
616
  throw new InvalidCronError(expression, `Invalid cron expression "${expression}": ${error instanceof Error ? error.message : "Unknown parsing error"}. Expected 5-field format: "minute hour day-of-month month day-of-week" or predefined expression (e.g. @daily). Example: "0 9 * * 1" (every Monday at 9am)`);
623
617
  }
624
-
625
618
  //#endregion
626
619
  //#region src/shared/utils/error.ts
627
620
  /**
@@ -654,7 +647,6 @@ function toError(value) {
654
647
  return /* @__PURE__ */ new Error(`Unserializable value (${detail})`);
655
648
  }
656
649
  }
657
-
658
650
  //#endregion
659
651
  //#region src/scheduler/helpers.ts
660
652
  /**
@@ -720,7 +712,6 @@ function decodeCursor(cursor) {
720
712
  throw new InvalidCursorError("Invalid cursor payload");
721
713
  }
722
714
  }
723
-
724
715
  //#endregion
725
716
  //#region src/scheduler/services/change-stream-handler.ts
726
717
  /**
@@ -764,6 +755,7 @@ var ChangeStreamHandler = class {
764
755
  */
765
756
  setup() {
766
757
  if (!this.ctx.isRunning()) return;
758
+ this.clearReconnectTimer();
767
759
  try {
768
760
  this.changeStream = this.ctx.collection.watch([{ $match: { $or: [{ operationType: "insert" }, {
769
761
  operationType: "update",
@@ -823,30 +815,35 @@ var ChangeStreamHandler = class {
823
815
  this.reconnectAttempts++;
824
816
  if (this.reconnectAttempts > this.maxReconnectAttempts) {
825
817
  this.usingChangeStreams = false;
826
- if (this.reconnectTimer) {
827
- clearTimeout(this.reconnectTimer);
828
- this.reconnectTimer = null;
829
- }
830
- if (this.changeStream) {
831
- this.changeStream.close().catch(() => {});
832
- this.changeStream = null;
833
- }
818
+ this.clearReconnectTimer();
819
+ this.closeChangeStream();
834
820
  this.ctx.emit("changestream:fallback", { reason: `Exhausted ${this.maxReconnectAttempts} reconnection attempts: ${error.message}` });
835
821
  return;
836
822
  }
837
823
  const delay = 2 ** (this.reconnectAttempts - 1) * 1e3;
838
- if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
824
+ this.clearReconnectTimer();
825
+ if (!this.ctx.isRunning()) return;
839
826
  this.reconnectTimer = setTimeout(() => {
840
- this.reconnectTimer = null;
841
- if (this.ctx.isRunning()) {
842
- if (this.changeStream) {
843
- this.changeStream.close().catch(() => {});
844
- this.changeStream = null;
845
- }
846
- this.setup();
847
- }
827
+ this.clearReconnectTimer();
828
+ this.reconnect();
848
829
  }, delay);
849
830
  }
831
+ reconnect() {
832
+ if (!this.ctx.isRunning()) return;
833
+ this.closeChangeStream();
834
+ if (!this.ctx.isRunning()) return;
835
+ this.setup();
836
+ }
837
+ clearReconnectTimer() {
838
+ if (!this.reconnectTimer) return;
839
+ clearTimeout(this.reconnectTimer);
840
+ this.reconnectTimer = null;
841
+ }
842
+ closeChangeStream() {
843
+ if (!this.changeStream) return;
844
+ this.changeStream.close().catch(() => {});
845
+ this.changeStream = null;
846
+ }
850
847
  /**
851
848
  * Close the change stream cursor and emit closed event.
852
849
  */
@@ -855,10 +852,7 @@ var ChangeStreamHandler = class {
855
852
  clearTimeout(this.debounceTimer);
856
853
  this.debounceTimer = null;
857
854
  }
858
- if (this.reconnectTimer) {
859
- clearTimeout(this.reconnectTimer);
860
- this.reconnectTimer = null;
861
- }
855
+ this.clearReconnectTimer();
862
856
  if (this.changeStream) {
863
857
  try {
864
858
  await this.changeStream.close();
@@ -876,7 +870,6 @@ var ChangeStreamHandler = class {
876
870
  return this.usingChangeStreams;
877
871
  }
878
872
  };
879
-
880
873
  //#endregion
881
874
  //#region src/scheduler/services/job-manager.ts
882
875
  /**
@@ -1169,7 +1162,6 @@ var JobManager = class {
1169
1162
  }
1170
1163
  }
1171
1164
  };
1172
-
1173
1165
  //#endregion
1174
1166
  //#region src/scheduler/services/job-processor.ts
1175
1167
  /**
@@ -1474,7 +1466,6 @@ var JobProcessor = class {
1474
1466
  } });
1475
1467
  }
1476
1468
  };
1477
-
1478
1469
  //#endregion
1479
1470
  //#region src/scheduler/services/job-query.ts
1480
1471
  /**
@@ -1778,7 +1769,6 @@ var JobQueryService = class JobQueryService {
1778
1769
  }
1779
1770
  }
1780
1771
  };
1781
-
1782
1772
  //#endregion
1783
1773
  //#region src/scheduler/services/job-scheduler.ts
1784
1774
  /**
@@ -2009,7 +1999,6 @@ var JobScheduler = class {
2009
1999
  }
2010
2000
  }
2011
2001
  };
2012
-
2013
2002
  //#endregion
2014
2003
  //#region src/scheduler/services/lifecycle-manager.ts
2015
2004
  /**
@@ -2117,7 +2106,6 @@ var LifecycleManager = class {
2117
2106
  if (deletions.length > 0) await Promise.all(deletions);
2118
2107
  }
2119
2108
  };
2120
-
2121
2109
  //#endregion
2122
2110
  //#region src/scheduler/monque.ts
2123
2111
  /**
@@ -3039,12 +3027,12 @@ var Monque = class extends node_events.EventEmitter {
3039
3027
  */
3040
3028
  async stop() {
3041
3029
  if (!this.isRunning) return;
3030
+ this.lifecycleManager.stopTimers();
3042
3031
  this.isRunning = false;
3043
3032
  this._query?.clearStatsCache();
3044
3033
  try {
3045
3034
  await this.changeStreamHandler.close();
3046
3035
  } catch {}
3047
- this.lifecycleManager.stopTimers();
3048
3036
  if (this.getActiveJobs().length === 0) return;
3049
3037
  let checkInterval;
3050
3038
  const waitForJobs = new Promise((resolve) => {
@@ -3166,7 +3154,6 @@ var Monque = class extends node_events.EventEmitter {
3166
3154
  return super.off(event, listener);
3167
3155
  }
3168
3156
  };
3169
-
3170
3157
  //#endregion
3171
3158
  exports.AggregationTimeoutError = AggregationTimeoutError;
3172
3159
  exports.ConnectionError = ConnectionError;
@@ -3194,4 +3181,5 @@ exports.isProcessingJob = isProcessingJob;
3194
3181
  exports.isRecurringJob = isRecurringJob;
3195
3182
  exports.isValidJobStatus = isValidJobStatus;
3196
3183
  exports.validateCronExpression = validateCronExpression;
3184
+
3197
3185
  //# sourceMappingURL=index.cjs.map