mongo-job-scheduler 0.1.17 → 1.0.1

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
@@ -23,6 +23,10 @@ A production-grade MongoDB-backed job scheduler for Node.js with distributed loc
23
23
 
24
24
  ---
25
25
 
26
+ > 🚀 **Ready to start?** Check out the [Complete Example Repository](https://github.com/darshanpatel14/mongo-job-scheduler-example) which demonstrates all features (Priority, Retries, Cron, UI) in a production-ready Express app with Docker.
27
+
28
+ ---
29
+
26
30
  ## Quick Start
27
31
 
28
32
  ### Requirements
@@ -43,6 +47,7 @@ For a visual web dashboard to manage and monitor your jobs, check out:
43
47
 
44
48
  - **NPM**: [`mongo-scheduler-ui`](https://www.npmjs.com/package/mongo-scheduler-ui)
45
49
  - **GitHub**: [mongo-scheduler-ui](https://github.com/darshanpatel14/mongo-job-scheduler-ui)
50
+ - **Full Example**: [mongo-job-scheduler-example](https://github.com/darshanpatel14/mongo-job-scheduler-example) (Backend + UI + Docker)
46
51
  - **API Server**: [mongo-job-scheduler-api](https://github.com/darshanpatel14/mongo-job-scheduler-api)
47
52
 
48
53
  ### Basic Usage
@@ -12,7 +12,7 @@ export declare class InMemoryJobStore implements JobStore {
12
12
  workerId: string;
13
13
  lockTimeoutMs: number;
14
14
  }): Promise<Job | null>;
15
- markCompleted(jobId: unknown): Promise<void>;
15
+ markCompleted(jobId: unknown, workerId: string): Promise<void>;
16
16
  markFailed(jobId: unknown, error: string): Promise<void>;
17
17
  reschedule(jobId: unknown, nextRunAt: Date, updates?: {
18
18
  attempts?: number;
@@ -78,13 +78,19 @@ class InMemoryJobStore {
78
78
  release();
79
79
  }
80
80
  }
81
- async markCompleted(jobId) {
81
+ async markCompleted(jobId, workerId) {
82
82
  const job = this.jobs.get(String(jobId));
83
83
  if (!job)
84
84
  throw new store_errors_1.JobNotFoundError();
85
+ if (job.lockedBy !== workerId || job.status !== "running") {
86
+ throw new store_errors_1.JobOwnershipError(`Cannot complete job ${jobId}: ownership lost (expected workerId: ${workerId})`);
87
+ }
85
88
  job.status = "completed";
86
89
  job.lastRunAt = new Date();
87
90
  job.updatedAt = new Date();
91
+ job.lockedAt = undefined;
92
+ job.lockedBy = undefined;
93
+ job.lockUntil = undefined;
88
94
  }
89
95
  async markFailed(jobId, error) {
90
96
  const job = this.jobs.get(String(jobId));
@@ -19,9 +19,9 @@ export interface JobStore {
19
19
  lockTimeoutMs: number;
20
20
  }): Promise<Job | null>;
21
21
  /**
22
- * Mark job as completed
22
+ * Mark job as completed (requires ownership verification)
23
23
  */
24
- markCompleted(jobId: unknown): Promise<void>;
24
+ markCompleted(jobId: unknown, workerId: string): Promise<void>;
25
25
  /**
26
26
  * Mark job as failed
27
27
  */
@@ -18,7 +18,7 @@ export declare class MongoJobStore implements JobStore {
18
18
  workerId: string;
19
19
  lockTimeoutMs: number;
20
20
  }): Promise<Job | null>;
21
- markCompleted(id: ObjectId): Promise<void>;
21
+ markCompleted(id: ObjectId, workerId: string): Promise<void>;
22
22
  markFailed(id: ObjectId, error: string): Promise<void>;
23
23
  reschedule(id: ObjectId, nextRunAt: Date, updates?: {
24
24
  attempts?: number;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MongoJobStore = void 0;
4
+ const store_errors_1 = require("../store-errors");
4
5
  class MongoJobStore {
5
6
  constructor(db, options = {}) {
6
7
  this.collection = db.collection(options.collectionName ?? "scheduler_jobs");
@@ -210,8 +211,8 @@ class MongoJobStore {
210
211
  }
211
212
  return null;
212
213
  }
213
- async markCompleted(id) {
214
- await this.collection.updateOne({ _id: id }, {
214
+ async markCompleted(id, workerId) {
215
+ const result = await this.collection.updateOne({ _id: id, lockedBy: workerId, status: "running" }, {
215
216
  $set: {
216
217
  status: "completed",
217
218
  updatedAt: new Date(),
@@ -222,6 +223,9 @@ class MongoJobStore {
222
223
  lockUntil: "",
223
224
  },
224
225
  });
226
+ if (result.matchedCount === 0) {
227
+ throw new store_errors_1.JobOwnershipError(`Cannot complete job ${id}: ownership lost (expected workerId: ${workerId})`);
228
+ }
225
229
  }
226
230
  async markFailed(id, error) {
227
231
  await this.collection.updateOne({ _id: id }, {
@@ -4,3 +4,6 @@ export declare class JobNotFoundError extends Error {
4
4
  export declare class JobLockError extends Error {
5
5
  constructor(message?: string);
6
6
  }
7
+ export declare class JobOwnershipError extends Error {
8
+ constructor(message?: string);
9
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.JobLockError = exports.JobNotFoundError = void 0;
3
+ exports.JobOwnershipError = exports.JobLockError = exports.JobNotFoundError = void 0;
4
4
  class JobNotFoundError extends Error {
5
5
  constructor(message = "Job not found") {
6
6
  super(message);
@@ -15,3 +15,10 @@ class JobLockError extends Error {
15
15
  }
16
16
  }
17
17
  exports.JobLockError = JobLockError;
18
+ class JobOwnershipError extends Error {
19
+ constructor(message = "Job ownership lost") {
20
+ super(message);
21
+ this.name = "JobOwnershipError";
22
+ }
23
+ }
24
+ exports.JobOwnershipError = JobOwnershipError;
@@ -127,7 +127,7 @@ class Worker {
127
127
  await this.store.reschedule(job._id, next);
128
128
  }
129
129
  if (!job.repeat) {
130
- await this.store.markCompleted(job._id);
130
+ await this.store.markCompleted(job._id, this.workerId);
131
131
  this.emitter.emitSafe("job:success", job);
132
132
  }
133
133
  this.emitter.emitSafe("job:complete", job);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mongo-job-scheduler",
3
- "version": "0.1.17",
3
+ "version": "1.0.1",
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",