@monque/core 1.1.2 → 1.3.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 +20 -1
- package/dist/CHANGELOG.md +20 -0
- package/dist/README.md +20 -1
- package/dist/index.cjs +58 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +40 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +57 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -12
- package/src/scheduler/monque.ts +5 -3
- package/src/scheduler/services/job-processor.ts +77 -6
- package/src/scheduler/services/types.ts +15 -3
- package/src/scheduler/types.ts +43 -1
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
|
-
|
|
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,25 @@
|
|
|
1
1
|
# @monque/core
|
|
2
2
|
|
|
3
|
+
## 1.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#158](https://github.com/ueberBrot/monque/pull/158) [`2f83396`](https://github.com/ueberBrot/monque/commit/2f833966d7798307deaa7a1e655e0623cfb42a3e) Thanks [@renovate](https://github.com/apps/renovate)! - mongodb (^7.0.0 → ^7.1.0)
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#160](https://github.com/ueberBrot/monque/pull/160) [`b5fcaf8`](https://github.com/ueberBrot/monque/commit/b5fcaf8be2a49fb1ba97b8d3d9f28f00850f77a1) Thanks [@ueberBrot](https://github.com/ueberBrot)! - Fix race condition where concurrent poll cycles could exceed workerConcurrency limit. Added a guard to prevent overlapping poll() execution from setInterval and change stream triggers.
|
|
12
|
+
|
|
13
|
+
## 1.2.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- [#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.
|
|
18
|
+
|
|
19
|
+
- Added `instanceConcurrency` option (formerly `maxConcurrency`) to limit the total number of jobs processed concurrently across all workers on a single Monque instance.
|
|
20
|
+
- Added `workerConcurrency` as a clearer alias for `defaultConcurrency`.
|
|
21
|
+
- Deprecated `maxConcurrency` and `defaultConcurrency` in favor of the new explicit names. They will be removed in the next major version.
|
|
22
|
+
|
|
3
23
|
## 1.1.2
|
|
4
24
|
|
|
5
25
|
### 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
|
-
|
|
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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
1
2
|
let mongodb = require("mongodb");
|
|
2
3
|
let cron_parser = require("cron-parser");
|
|
3
4
|
let node_crypto = require("node:crypto");
|
|
@@ -1110,31 +1111,74 @@ var JobManager = class {
|
|
|
1110
1111
|
* @internal Not part of public API.
|
|
1111
1112
|
*/
|
|
1112
1113
|
var JobProcessor = class {
|
|
1114
|
+
/** Guard flag to prevent concurrent poll() execution */
|
|
1115
|
+
_isPolling = false;
|
|
1113
1116
|
constructor(ctx) {
|
|
1114
1117
|
this.ctx = ctx;
|
|
1115
1118
|
}
|
|
1116
1119
|
/**
|
|
1120
|
+
* Get the total number of active jobs across all workers.
|
|
1121
|
+
*
|
|
1122
|
+
* Used for instance-level throttling when `instanceConcurrency` is configured.
|
|
1123
|
+
*/
|
|
1124
|
+
getTotalActiveJobs() {
|
|
1125
|
+
let total = 0;
|
|
1126
|
+
for (const worker of this.ctx.workers.values()) total += worker.activeJobs.size;
|
|
1127
|
+
return total;
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Get the number of available slots considering the global instanceConcurrency limit.
|
|
1131
|
+
*
|
|
1132
|
+
* @param workerAvailableSlots - Available slots for the specific worker
|
|
1133
|
+
* @returns Number of slots available after applying global limit
|
|
1134
|
+
*/
|
|
1135
|
+
getGloballyAvailableSlots(workerAvailableSlots) {
|
|
1136
|
+
const { instanceConcurrency } = this.ctx.options;
|
|
1137
|
+
if (instanceConcurrency === void 0) return workerAvailableSlots;
|
|
1138
|
+
const globalAvailable = instanceConcurrency - this.getTotalActiveJobs();
|
|
1139
|
+
return Math.min(workerAvailableSlots, globalAvailable);
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1117
1142
|
* Poll for available jobs and process them.
|
|
1118
1143
|
*
|
|
1119
1144
|
* Called at regular intervals (configured by `pollInterval`). For each registered worker,
|
|
1120
1145
|
* attempts to acquire jobs up to the worker's available concurrency slots.
|
|
1121
|
-
* Aborts early if the scheduler is stopping (`isRunning` is false)
|
|
1146
|
+
* Aborts early if the scheduler is stopping (`isRunning` is false) or if
|
|
1147
|
+
* the instance-level `instanceConcurrency` limit is reached.
|
|
1122
1148
|
*/
|
|
1123
1149
|
async poll() {
|
|
1124
|
-
if (!this.ctx.isRunning()) return;
|
|
1150
|
+
if (!this.ctx.isRunning() || this._isPolling) return;
|
|
1151
|
+
this._isPolling = true;
|
|
1152
|
+
try {
|
|
1153
|
+
await this._doPoll();
|
|
1154
|
+
} finally {
|
|
1155
|
+
this._isPolling = false;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Internal poll implementation.
|
|
1160
|
+
*/
|
|
1161
|
+
async _doPoll() {
|
|
1162
|
+
const { instanceConcurrency } = this.ctx.options;
|
|
1163
|
+
if (instanceConcurrency !== void 0 && this.getTotalActiveJobs() >= instanceConcurrency) return;
|
|
1125
1164
|
for (const [name, worker] of this.ctx.workers) {
|
|
1126
|
-
const
|
|
1127
|
-
if (
|
|
1165
|
+
const workerAvailableSlots = worker.concurrency - worker.activeJobs.size;
|
|
1166
|
+
if (workerAvailableSlots <= 0) continue;
|
|
1167
|
+
const availableSlots = this.getGloballyAvailableSlots(workerAvailableSlots);
|
|
1168
|
+
if (availableSlots <= 0) return;
|
|
1128
1169
|
for (let i = 0; i < availableSlots; i++) {
|
|
1129
1170
|
if (!this.ctx.isRunning()) return;
|
|
1171
|
+
if (instanceConcurrency !== void 0 && this.getTotalActiveJobs() >= instanceConcurrency) return;
|
|
1130
1172
|
const job = await this.acquireJob(name);
|
|
1131
|
-
if (job)
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
job
|
|
1173
|
+
if (job) {
|
|
1174
|
+
worker.activeJobs.set(job._id.toString(), job);
|
|
1175
|
+
this.processJob(job, worker).catch((error) => {
|
|
1176
|
+
this.ctx.emit("job:error", {
|
|
1177
|
+
error,
|
|
1178
|
+
job
|
|
1179
|
+
});
|
|
1135
1180
|
});
|
|
1136
|
-
}
|
|
1137
|
-
else break;
|
|
1181
|
+
} else break;
|
|
1138
1182
|
}
|
|
1139
1183
|
}
|
|
1140
1184
|
}
|
|
@@ -1187,7 +1231,6 @@ var JobProcessor = class {
|
|
|
1187
1231
|
*/
|
|
1188
1232
|
async processJob(job, worker) {
|
|
1189
1233
|
const jobId = job._id.toString();
|
|
1190
|
-
worker.activeJobs.set(jobId, job);
|
|
1191
1234
|
const startTime = Date.now();
|
|
1192
1235
|
this.ctx.emit("job:start", job);
|
|
1193
1236
|
try {
|
|
@@ -1817,7 +1860,7 @@ const DEFAULTS = {
|
|
|
1817
1860
|
maxRetries: 10,
|
|
1818
1861
|
baseRetryInterval: 1e3,
|
|
1819
1862
|
shutdownTimeout: 3e4,
|
|
1820
|
-
|
|
1863
|
+
workerConcurrency: 5,
|
|
1821
1864
|
lockTimeout: 18e5,
|
|
1822
1865
|
recoverStaleJobs: true,
|
|
1823
1866
|
heartbeatInterval: 3e4,
|
|
@@ -1911,10 +1954,11 @@ var Monque = class extends node_events.EventEmitter {
|
|
|
1911
1954
|
maxRetries: options.maxRetries ?? DEFAULTS.maxRetries,
|
|
1912
1955
|
baseRetryInterval: options.baseRetryInterval ?? DEFAULTS.baseRetryInterval,
|
|
1913
1956
|
shutdownTimeout: options.shutdownTimeout ?? DEFAULTS.shutdownTimeout,
|
|
1914
|
-
|
|
1957
|
+
workerConcurrency: options.workerConcurrency ?? options.defaultConcurrency ?? DEFAULTS.workerConcurrency,
|
|
1915
1958
|
lockTimeout: options.lockTimeout ?? DEFAULTS.lockTimeout,
|
|
1916
1959
|
recoverStaleJobs: options.recoverStaleJobs ?? DEFAULTS.recoverStaleJobs,
|
|
1917
1960
|
maxBackoffDelay: options.maxBackoffDelay,
|
|
1961
|
+
instanceConcurrency: options.instanceConcurrency ?? options.maxConcurrency,
|
|
1918
1962
|
schedulerInstanceId: options.schedulerInstanceId ?? (0, node_crypto.randomUUID)(),
|
|
1919
1963
|
heartbeatInterval: options.heartbeatInterval ?? DEFAULTS.heartbeatInterval,
|
|
1920
1964
|
jobRetention: options.jobRetention
|
|
@@ -2577,7 +2621,7 @@ var Monque = class extends node_events.EventEmitter {
|
|
|
2577
2621
|
* ```
|
|
2578
2622
|
*/
|
|
2579
2623
|
register(name, handler, options = {}) {
|
|
2580
|
-
const concurrency = options.concurrency ?? this.options.
|
|
2624
|
+
const concurrency = options.concurrency ?? this.options.workerConcurrency;
|
|
2581
2625
|
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
2626
|
this.workers.set(name, {
|
|
2583
2627
|
handler,
|