@monque/core 1.1.2 → 1.2.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 CHANGED
@@ -8,12 +8,31 @@
8
8
  <a href="https://www.npmjs.com/package/@monque/core">
9
9
  <img src="https://img.shields.io/npm/v/%40monque%2Fcore?style=for-the-badge&label=%40monque%2Fcore" alt="@monque/core version" />
10
10
  </a>
11
+ <a href="https://github.com/ueberBrot/monque/actions/workflows/ci.yml">
12
+ <img src="https://img.shields.io/github/actions/workflow/status/ueberBrot/monque/ci.yml?branch=main&style=for-the-badge&logo=github" alt="CI Status" />
13
+ </a>
11
14
  <a href="https://codecov.io/gh/ueberBrot/monque">
12
15
  <img src="https://img.shields.io/codecov/c/github/ueberBrot/monque?style=for-the-badge&logo=codecov&logoColor=white" alt="Codecov" />
13
16
  </a>
17
+ <a href="https://opensource.org/licenses/ISC">
18
+ <img src="https://img.shields.io/badge/License-ISC-blue.svg?style=for-the-badge" alt="License: ISC" />
19
+ </a>
20
+ <a href="https://bun.sh">
21
+ <img src="https://img.shields.io/badge/Built%20with-Bun-fbf0df?style=for-the-badge&logo=bun&logoColor=black" alt="Built with Bun" />
22
+ </a>
14
23
  </p>
15
24
 
16
- <p align="center">MongoDB-backed job scheduler with atomic locking, exponential backoff, and cron scheduling.</p>
25
+ A **robust, type-safe MongoDB job queue** for TypeScript with atomic locking, exponential backoff, and cron scheduling.
26
+
27
+ ## Features
28
+
29
+ - 🔒 **Atomic Locking**: Mandatory `findOneAndUpdate` for safe job acquisition in distributed environments.
30
+ - 📈 **Exponential Backoff**: Built-in retry logic with configurable backoff strategies.
31
+ - 📅 **Cron Scheduling**: Native support for recurring jobs using standard cron syntax.
32
+ - 🔍 **Type Safety**: Fully typed job payloads and worker definitions.
33
+ - ⚡ **Event-Driven**: Comprehensive event system for monitoring and logging.
34
+ - 🛠️ **Native Driver**: Uses the native MongoDB driver for maximum performance and compatibility.
35
+ - 🛑 **Graceful Shutdown**: Ensures all in-progress jobs finish or are safely released before stopping.
17
36
 
18
37
  ## Installation
19
38
 
package/dist/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @monque/core
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#112](https://github.com/ueberBrot/monque/pull/112) [`9b7f44f`](https://github.com/ueberBrot/monque/commit/9b7f44f5c1f6b4aa4215e571189cc03cbaa49865) Thanks [@ueberBrot](https://github.com/ueberBrot)! - Add instance-level concurrency throttling and deprecated old naming conventions.
8
+
9
+ - Added `instanceConcurrency` option (formerly `maxConcurrency`) to limit the total number of jobs processed concurrently across all workers on a single Monque instance.
10
+ - Added `workerConcurrency` as a clearer alias for `defaultConcurrency`.
11
+ - Deprecated `maxConcurrency` and `defaultConcurrency` in favor of the new explicit names. They will be removed in the next major version.
12
+
3
13
  ## 1.1.2
4
14
 
5
15
  ### Patch Changes
package/dist/README.md CHANGED
@@ -8,12 +8,31 @@
8
8
  <a href="https://www.npmjs.com/package/@monque/core">
9
9
  <img src="https://img.shields.io/npm/v/%40monque%2Fcore?style=for-the-badge&label=%40monque%2Fcore" alt="@monque/core version" />
10
10
  </a>
11
+ <a href="https://github.com/ueberBrot/monque/actions/workflows/ci.yml">
12
+ <img src="https://img.shields.io/github/actions/workflow/status/ueberBrot/monque/ci.yml?branch=main&style=for-the-badge&logo=github" alt="CI Status" />
13
+ </a>
11
14
  <a href="https://codecov.io/gh/ueberBrot/monque">
12
15
  <img src="https://img.shields.io/codecov/c/github/ueberBrot/monque?style=for-the-badge&logo=codecov&logoColor=white" alt="Codecov" />
13
16
  </a>
17
+ <a href="https://opensource.org/licenses/ISC">
18
+ <img src="https://img.shields.io/badge/License-ISC-blue.svg?style=for-the-badge" alt="License: ISC" />
19
+ </a>
20
+ <a href="https://bun.sh">
21
+ <img src="https://img.shields.io/badge/Built%20with-Bun-fbf0df?style=for-the-badge&logo=bun&logoColor=black" alt="Built with Bun" />
22
+ </a>
14
23
  </p>
15
24
 
16
- <p align="center">MongoDB-backed job scheduler with atomic locking, exponential backoff, and cron scheduling.</p>
25
+ A **robust, type-safe MongoDB job queue** for TypeScript with atomic locking, exponential backoff, and cron scheduling.
26
+
27
+ ## Features
28
+
29
+ - 🔒 **Atomic Locking**: Mandatory `findOneAndUpdate` for safe job acquisition in distributed environments.
30
+ - 📈 **Exponential Backoff**: Built-in retry logic with configurable backoff strategies.
31
+ - 📅 **Cron Scheduling**: Native support for recurring jobs using standard cron syntax.
32
+ - 🔍 **Type Safety**: Fully typed job payloads and worker definitions.
33
+ - ⚡ **Event-Driven**: Comprehensive event system for monitoring and logging.
34
+ - 🛠️ **Native Driver**: Uses the native MongoDB driver for maximum performance and compatibility.
35
+ - 🛑 **Graceful Shutdown**: Ensures all in-progress jobs finish or are safely released before stopping.
17
36
 
18
37
  ## Installation
19
38
 
package/dist/index.cjs CHANGED
@@ -1114,27 +1114,57 @@ var JobProcessor = class {
1114
1114
  this.ctx = ctx;
1115
1115
  }
1116
1116
  /**
1117
+ * Get the total number of active jobs across all workers.
1118
+ *
1119
+ * Used for instance-level throttling when `instanceConcurrency` is configured.
1120
+ */
1121
+ getTotalActiveJobs() {
1122
+ let total = 0;
1123
+ for (const worker of this.ctx.workers.values()) total += worker.activeJobs.size;
1124
+ return total;
1125
+ }
1126
+ /**
1127
+ * Get the number of available slots considering the global instanceConcurrency limit.
1128
+ *
1129
+ * @param workerAvailableSlots - Available slots for the specific worker
1130
+ * @returns Number of slots available after applying global limit
1131
+ */
1132
+ getGloballyAvailableSlots(workerAvailableSlots) {
1133
+ const { instanceConcurrency } = this.ctx.options;
1134
+ if (instanceConcurrency === void 0) return workerAvailableSlots;
1135
+ const globalAvailable = instanceConcurrency - this.getTotalActiveJobs();
1136
+ return Math.min(workerAvailableSlots, globalAvailable);
1137
+ }
1138
+ /**
1117
1139
  * Poll for available jobs and process them.
1118
1140
  *
1119
1141
  * Called at regular intervals (configured by `pollInterval`). For each registered worker,
1120
1142
  * attempts to acquire jobs up to the worker's available concurrency slots.
1121
- * Aborts early if the scheduler is stopping (`isRunning` is false).
1143
+ * Aborts early if the scheduler is stopping (`isRunning` is false) or if
1144
+ * the instance-level `instanceConcurrency` limit is reached.
1122
1145
  */
1123
1146
  async poll() {
1124
1147
  if (!this.ctx.isRunning()) return;
1148
+ const { instanceConcurrency } = this.ctx.options;
1149
+ if (instanceConcurrency !== void 0 && this.getTotalActiveJobs() >= instanceConcurrency) return;
1125
1150
  for (const [name, worker] of this.ctx.workers) {
1126
- const availableSlots = worker.concurrency - worker.activeJobs.size;
1127
- if (availableSlots <= 0) continue;
1151
+ const workerAvailableSlots = worker.concurrency - worker.activeJobs.size;
1152
+ if (workerAvailableSlots <= 0) continue;
1153
+ const availableSlots = this.getGloballyAvailableSlots(workerAvailableSlots);
1154
+ if (availableSlots <= 0) return;
1128
1155
  for (let i = 0; i < availableSlots; i++) {
1129
1156
  if (!this.ctx.isRunning()) return;
1157
+ if (instanceConcurrency !== void 0 && this.getTotalActiveJobs() >= instanceConcurrency) return;
1130
1158
  const job = await this.acquireJob(name);
1131
- if (job) this.processJob(job, worker).catch((error) => {
1132
- this.ctx.emit("job:error", {
1133
- error,
1134
- job
1159
+ if (job) {
1160
+ worker.activeJobs.set(job._id.toString(), job);
1161
+ this.processJob(job, worker).catch((error) => {
1162
+ this.ctx.emit("job:error", {
1163
+ error,
1164
+ job
1165
+ });
1135
1166
  });
1136
- });
1137
- else break;
1167
+ } else break;
1138
1168
  }
1139
1169
  }
1140
1170
  }
@@ -1187,7 +1217,6 @@ var JobProcessor = class {
1187
1217
  */
1188
1218
  async processJob(job, worker) {
1189
1219
  const jobId = job._id.toString();
1190
- worker.activeJobs.set(jobId, job);
1191
1220
  const startTime = Date.now();
1192
1221
  this.ctx.emit("job:start", job);
1193
1222
  try {
@@ -1817,7 +1846,7 @@ const DEFAULTS = {
1817
1846
  maxRetries: 10,
1818
1847
  baseRetryInterval: 1e3,
1819
1848
  shutdownTimeout: 3e4,
1820
- defaultConcurrency: 5,
1849
+ workerConcurrency: 5,
1821
1850
  lockTimeout: 18e5,
1822
1851
  recoverStaleJobs: true,
1823
1852
  heartbeatInterval: 3e4,
@@ -1911,10 +1940,11 @@ var Monque = class extends node_events.EventEmitter {
1911
1940
  maxRetries: options.maxRetries ?? DEFAULTS.maxRetries,
1912
1941
  baseRetryInterval: options.baseRetryInterval ?? DEFAULTS.baseRetryInterval,
1913
1942
  shutdownTimeout: options.shutdownTimeout ?? DEFAULTS.shutdownTimeout,
1914
- defaultConcurrency: options.defaultConcurrency ?? DEFAULTS.defaultConcurrency,
1943
+ workerConcurrency: options.workerConcurrency ?? options.defaultConcurrency ?? DEFAULTS.workerConcurrency,
1915
1944
  lockTimeout: options.lockTimeout ?? DEFAULTS.lockTimeout,
1916
1945
  recoverStaleJobs: options.recoverStaleJobs ?? DEFAULTS.recoverStaleJobs,
1917
1946
  maxBackoffDelay: options.maxBackoffDelay,
1947
+ instanceConcurrency: options.instanceConcurrency ?? options.maxConcurrency,
1918
1948
  schedulerInstanceId: options.schedulerInstanceId ?? (0, node_crypto.randomUUID)(),
1919
1949
  heartbeatInterval: options.heartbeatInterval ?? DEFAULTS.heartbeatInterval,
1920
1950
  jobRetention: options.jobRetention
@@ -2577,7 +2607,7 @@ var Monque = class extends node_events.EventEmitter {
2577
2607
  * ```
2578
2608
  */
2579
2609
  register(name, handler, options = {}) {
2580
- const concurrency = options.concurrency ?? this.options.defaultConcurrency;
2610
+ const concurrency = options.concurrency ?? this.options.workerConcurrency;
2581
2611
  if (this.workers.has(name) && options.replace !== true) throw new WorkerRegistrationError(`Worker already registered for job name "${name}". Use { replace: true } to replace.`, name);
2582
2612
  this.workers.set(name, {
2583
2613
  handler,