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 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
@@ -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
- for (const job of this.jobs.values()) {
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) {
@@ -71,4 +71,5 @@ export interface JobUpdates {
71
71
  repeat?: RepeatOptions;
72
72
  status?: JobStatus;
73
73
  attempts?: number;
74
+ priority?: number;
74
75
  }
@@ -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) {
@@ -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
  }
@@ -20,4 +20,9 @@ export interface ScheduleOptions<T = unknown> {
20
20
  * Idempotency key to prevent duplicate jobs
21
21
  */
22
22
  dedupeKey?: string;
23
+ /**
24
+ * Job priority (1-10). Lower values = higher priority.
25
+ * Default: 5
26
+ */
27
+ priority?: number;
23
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mongo-job-scheduler",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Production-grade MongoDB-backed job scheduler with retries, cron, timezone support, and crash recovery",
5
5
  "license": "MIT",
6
6
  "author": "Darshan Bhut",