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 +5 -0
- package/dist/store/in-memory-job-store.d.ts +1 -1
- package/dist/store/in-memory-job-store.js +7 -1
- package/dist/store/job-store.d.ts +2 -2
- package/dist/store/mongo/mongo-job-store.d.ts +1 -1
- package/dist/store/mongo/mongo-job-store.js +6 -2
- package/dist/store/store-errors.d.ts +3 -0
- package/dist/store/store-errors.js +8 -1
- package/dist/worker/worker.js +1 -1
- package/package.json +1 -1
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 }, {
|
|
@@ -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;
|
package/dist/worker/worker.js
CHANGED
|
@@ -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