mongo-job-scheduler 0.1.14 → 0.1.15
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 +30 -3
- package/dist/core/scheduler.js +18 -0
- package/dist/store/in-memory-job-store.js +13 -1
- package/dist/store/job-store.d.ts +1 -0
- package/dist/store/mongo/mongo-job-store.js +7 -3
- package/dist/types/job.d.ts +5 -0
- package/dist/types/schedule.d.ts +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ A production-grade MongoDB-backed job scheduler for Node.js with distributed loc
|
|
|
10
10
|
|
|
11
11
|
- ✅ **Distributed locking** — safe for multiple instances
|
|
12
12
|
- ✅ **Atomic job execution** — no double processing
|
|
13
|
+
- ✅ **Job priority** — process important jobs first
|
|
13
14
|
- ✅ **Automatic retries** — with configurable backoff
|
|
14
15
|
- ✅ **Cron scheduling** — timezone-aware, non-drifting
|
|
15
16
|
- ✅ **Interval jobs** — repeated execution
|
|
@@ -178,6 +179,34 @@ await scheduler.cancel(jobId);
|
|
|
178
179
|
|
|
179
180
|
## Advanced Features
|
|
180
181
|
|
|
182
|
+
### Job Priority
|
|
183
|
+
|
|
184
|
+
Process important jobs first using priority levels (1-10, where 1 is highest priority):
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// High priority job - runs first
|
|
188
|
+
await scheduler.schedule({
|
|
189
|
+
name: "urgent-alert",
|
|
190
|
+
priority: 1,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Normal priority (default is 5)
|
|
194
|
+
await scheduler.schedule({
|
|
195
|
+
name: "regular-task",
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Low priority job - runs last
|
|
199
|
+
await scheduler.schedule({
|
|
200
|
+
name: "background-cleanup",
|
|
201
|
+
priority: 10,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Update priority of existing job
|
|
205
|
+
await scheduler.updateJob(jobId, { priority: 2 });
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
> **Priority Scale**: 1 (highest) → 10 (lowest). Jobs with equal priority run in FIFO order by `nextRunAt`.
|
|
209
|
+
|
|
181
210
|
### Retries with Backoff
|
|
182
211
|
|
|
183
212
|
```typescript
|
|
@@ -246,7 +275,7 @@ await scheduler.stop({
|
|
|
246
275
|
|
|
247
276
|
The library creates three indexes in background mode:
|
|
248
277
|
|
|
249
|
-
- `{ status: 1, nextRunAt: 1 }` — for job polling (critical)
|
|
278
|
+
- `{ status: 1, priority: 1, nextRunAt: 1 }` — for priority-based job polling (critical)
|
|
250
279
|
- `{ dedupeKey: 1 }` — for deduplication (unique)
|
|
251
280
|
- `{ lockedAt: 1 }` — for stale lock recovery
|
|
252
281
|
|
|
@@ -260,8 +289,6 @@ Run **multiple scheduler instances** (different servers, pods, or processes) con
|
|
|
260
289
|
- **Concurrency Control** — only one worker executes a job instance
|
|
261
290
|
- **Horizontally Scalable** — supports MongoDB sharding
|
|
262
291
|
|
|
263
|
-
- **Horizontally Scalable** — supports MongoDB sharding
|
|
264
|
-
|
|
265
292
|
---
|
|
266
293
|
|
|
267
294
|
## Documentation
|
package/dist/core/scheduler.js
CHANGED
|
@@ -34,6 +34,14 @@ class Scheduler {
|
|
|
34
34
|
if (options.repeat?.cron && options.repeat?.every != null) {
|
|
35
35
|
throw new Error("Use either cron or every, not both");
|
|
36
36
|
}
|
|
37
|
+
// Priority validation
|
|
38
|
+
if (options.priority !== undefined) {
|
|
39
|
+
if (!Number.isInteger(options.priority) ||
|
|
40
|
+
options.priority < 1 ||
|
|
41
|
+
options.priority > 10) {
|
|
42
|
+
throw new Error("Priority must be an integer between 1 and 10");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
37
45
|
// ------------------------
|
|
38
46
|
// Normalize run time
|
|
39
47
|
// ------------------------
|
|
@@ -50,6 +58,7 @@ class Scheduler {
|
|
|
50
58
|
retry: options.retry,
|
|
51
59
|
repeat: options.repeat,
|
|
52
60
|
dedupeKey: options.dedupeKey,
|
|
61
|
+
priority: options.priority,
|
|
53
62
|
createdAt: now,
|
|
54
63
|
updatedAt: now,
|
|
55
64
|
};
|
|
@@ -71,6 +80,14 @@ class Scheduler {
|
|
|
71
80
|
if (options.repeat?.cron && options.repeat.every) {
|
|
72
81
|
throw new Error("Cannot specify both cron and every");
|
|
73
82
|
}
|
|
83
|
+
// Priority validation
|
|
84
|
+
if (options.priority !== undefined) {
|
|
85
|
+
if (!Number.isInteger(options.priority) ||
|
|
86
|
+
options.priority < 1 ||
|
|
87
|
+
options.priority > 10) {
|
|
88
|
+
throw new Error("Priority must be an integer between 1 and 10");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
74
91
|
const job = {
|
|
75
92
|
name: options.name,
|
|
76
93
|
data: options.data,
|
|
@@ -79,6 +96,7 @@ class Scheduler {
|
|
|
79
96
|
repeat: options.repeat,
|
|
80
97
|
retry: options.retry,
|
|
81
98
|
dedupeKey: options.dedupeKey,
|
|
99
|
+
priority: options.priority,
|
|
82
100
|
};
|
|
83
101
|
if (isNaN(job.nextRunAt.getTime())) {
|
|
84
102
|
throw new Error("Invalid Date provided for runAt");
|
|
@@ -23,6 +23,7 @@ class InMemoryJobStore {
|
|
|
23
23
|
const stored = {
|
|
24
24
|
...job,
|
|
25
25
|
_id: id,
|
|
26
|
+
priority: job.priority ?? 5,
|
|
26
27
|
createdAt: job.createdAt ?? new Date(),
|
|
27
28
|
updatedAt: job.updatedAt ?? new Date(),
|
|
28
29
|
};
|
|
@@ -35,7 +36,15 @@ class InMemoryJobStore {
|
|
|
35
36
|
async findAndLockNext({ now, workerId, lockTimeoutMs, }) {
|
|
36
37
|
const release = await this.mutex.acquire();
|
|
37
38
|
try {
|
|
38
|
-
|
|
39
|
+
// Sort jobs by priority (ascending), then nextRunAt (ascending)
|
|
40
|
+
const sortedJobs = Array.from(this.jobs.values()).sort((a, b) => {
|
|
41
|
+
const priorityA = a.priority ?? 5;
|
|
42
|
+
const priorityB = b.priority ?? 5;
|
|
43
|
+
if (priorityA !== priorityB)
|
|
44
|
+
return priorityA - priorityB;
|
|
45
|
+
return a.nextRunAt.getTime() - b.nextRunAt.getTime();
|
|
46
|
+
});
|
|
47
|
+
for (const job of sortedJobs) {
|
|
39
48
|
if (job.status !== "pending")
|
|
40
49
|
continue;
|
|
41
50
|
if (job.nextRunAt > now)
|
|
@@ -153,6 +162,9 @@ class InMemoryJobStore {
|
|
|
153
162
|
if (updates.attempts !== undefined) {
|
|
154
163
|
job.attempts = updates.attempts;
|
|
155
164
|
}
|
|
165
|
+
if (updates.priority !== undefined) {
|
|
166
|
+
job.priority = updates.priority;
|
|
167
|
+
}
|
|
156
168
|
job.updatedAt = new Date();
|
|
157
169
|
}
|
|
158
170
|
async findAll(query) {
|
|
@@ -15,8 +15,8 @@ class MongoJobStore {
|
|
|
15
15
|
*/
|
|
16
16
|
async ensureIndexes() {
|
|
17
17
|
await Promise.all([
|
|
18
|
-
// Primary index for job polling (findAndLockNext)
|
|
19
|
-
this.collection.createIndex({ status: 1, nextRunAt: 1 }, { background: true }),
|
|
18
|
+
// Primary index for job polling (findAndLockNext) with priority
|
|
19
|
+
this.collection.createIndex({ status: 1, priority: 1, nextRunAt: 1 }, { background: true }),
|
|
20
20
|
// Index for deduplication
|
|
21
21
|
this.collection.createIndex({ dedupeKey: 1 }, { unique: true, sparse: true, background: true }),
|
|
22
22
|
// Index for stale lock recovery
|
|
@@ -34,6 +34,7 @@ class MongoJobStore {
|
|
|
34
34
|
...jobWithoutId,
|
|
35
35
|
status: job.status ?? "pending",
|
|
36
36
|
attempts: job.attempts ?? 0,
|
|
37
|
+
priority: job.priority ?? 5,
|
|
37
38
|
createdAt: now,
|
|
38
39
|
updatedAt: now,
|
|
39
40
|
};
|
|
@@ -57,6 +58,7 @@ class MongoJobStore {
|
|
|
57
58
|
...jobWithoutId,
|
|
58
59
|
status: job.status ?? "pending",
|
|
59
60
|
attempts: job.attempts ?? 0,
|
|
61
|
+
priority: job.priority ?? 5,
|
|
60
62
|
createdAt: now,
|
|
61
63
|
updatedAt: now,
|
|
62
64
|
};
|
|
@@ -95,7 +97,7 @@ class MongoJobStore {
|
|
|
95
97
|
updatedAt: now,
|
|
96
98
|
},
|
|
97
99
|
}, {
|
|
98
|
-
sort: { nextRunAt: 1 },
|
|
100
|
+
sort: { priority: 1, nextRunAt: 1 },
|
|
99
101
|
returnDocument: "after",
|
|
100
102
|
});
|
|
101
103
|
return result;
|
|
@@ -221,6 +223,8 @@ class MongoJobStore {
|
|
|
221
223
|
$set.status = updates.status;
|
|
222
224
|
if (updates.attempts !== undefined)
|
|
223
225
|
$set.attempts = updates.attempts;
|
|
226
|
+
if (updates.priority !== undefined)
|
|
227
|
+
$set.priority = updates.priority;
|
|
224
228
|
await this.collection.updateOne({ _id: id }, { $set });
|
|
225
229
|
}
|
|
226
230
|
async findAll(query) {
|
package/dist/types/job.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export interface Job<Data = unknown> {
|
|
|
16
16
|
retry?: RetryOptions | number;
|
|
17
17
|
repeat?: RepeatOptions;
|
|
18
18
|
dedupeKey?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Job priority (1-10). Lower values = higher priority.
|
|
21
|
+
* Default: 5
|
|
22
|
+
*/
|
|
23
|
+
priority?: number;
|
|
19
24
|
createdAt: Date;
|
|
20
25
|
updatedAt: Date;
|
|
21
26
|
}
|
package/dist/types/schedule.d.ts
CHANGED
package/package.json
CHANGED