mongo-job-scheduler 0.1.6 → 0.1.8
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 +148 -89
- package/dist/core/scheduler.d.ts +5 -0
- package/dist/core/scheduler.js +10 -1
- package/dist/store/in-memory-job-store.d.ts +2 -0
- package/dist/store/in-memory-job-store.js +26 -0
- package/dist/store/job-store.d.ts +5 -0
- package/dist/store/mongo/mongo-job-store.d.ts +6 -0
- package/dist/store/mongo/mongo-job-store.js +42 -0
- package/dist/types/job.d.ts +1 -1
- package/dist/types/query.d.ts +11 -0
- package/dist/types/query.js +2 -0
- package/dist/types/schedule.d.ts +1 -1
- package/dist/worker/worker.js +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,52 +1,40 @@
|
|
|
1
1
|
# Mongo Job Scheduler
|
|
2
2
|
|
|
3
|
-
A production-grade MongoDB-backed job scheduler for Node.js.
|
|
3
|
+
A production-grade MongoDB-backed job scheduler for Node.js with distributed locking, retries, cron scheduling, and crash recovery.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- reliable background jobs
|
|
8
|
-
- retries with backoff
|
|
9
|
-
- cron & interval scheduling
|
|
10
|
-
- crash recovery
|
|
11
|
-
- MongoDB sharding safety
|
|
5
|
+
[](https://www.npmjs.com/package/mongo-job-scheduler)
|
|
12
6
|
|
|
13
7
|
---
|
|
14
8
|
|
|
15
9
|
## Features
|
|
16
10
|
|
|
17
|
-
- **Distributed locking**
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **Cron
|
|
21
|
-
- **Interval jobs**
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
|
|
27
|
-
## Distributed Systems
|
|
28
|
-
|
|
29
|
-
This library is designed for distributed environments. You can run **multiple scheduler instances** (on different servers, pods, or processes) connected to the same MongoDB.
|
|
30
|
-
|
|
31
|
-
- **Atomic Locking**: Uses `findOneAndUpdate` to safe-guard against race conditions.
|
|
32
|
-
- **Concurrency**: Only one worker will execute a given job instance.
|
|
33
|
-
- **Scalable**: Horizontal scaling is supported via MongoDB sharding.
|
|
11
|
+
- ✅ **Distributed locking** — safe for multiple instances
|
|
12
|
+
- ✅ **Atomic job execution** — no double processing
|
|
13
|
+
- ✅ **Automatic retries** — with configurable backoff
|
|
14
|
+
- ✅ **Cron scheduling** — timezone-aware, non-drifting
|
|
15
|
+
- ✅ **Interval jobs** — repeated execution
|
|
16
|
+
- ✅ **Crash recovery** — resume on restart
|
|
17
|
+
- ✅ **Heartbeats** — automatic lock renewal for long jobs
|
|
18
|
+
- ✅ **Query API** — filter, sort, paginate jobs
|
|
19
|
+
- ✅ **Auto-indexing** — performance optimized out of the box
|
|
20
|
+
- ✅ **Sharding-safe** — designed for MongoDB sharding
|
|
34
21
|
|
|
35
22
|
---
|
|
36
23
|
|
|
37
|
-
##
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Installation
|
|
38
27
|
|
|
39
28
|
```bash
|
|
40
29
|
npm install mongo-job-scheduler
|
|
41
30
|
```
|
|
42
31
|
|
|
43
|
-
|
|
32
|
+
### Basic Usage
|
|
44
33
|
|
|
45
34
|
```typescript
|
|
46
35
|
import { Scheduler, MongoJobStore } from "mongo-job-scheduler";
|
|
47
36
|
import { MongoClient } from "mongodb";
|
|
48
37
|
|
|
49
|
-
// ... connect to mongo ...
|
|
50
38
|
const client = new MongoClient("mongodb://localhost:27017");
|
|
51
39
|
await client.connect();
|
|
52
40
|
const db = client.db("my-app");
|
|
@@ -62,133 +50,204 @@ const scheduler = new Scheduler({
|
|
|
62
50
|
await scheduler.start();
|
|
63
51
|
```
|
|
64
52
|
|
|
65
|
-
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Scheduling Jobs
|
|
56
|
+
|
|
57
|
+
### One-Time Job
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
await scheduler.schedule({
|
|
61
|
+
name: "send-email",
|
|
62
|
+
data: { userId: 123 },
|
|
63
|
+
runAt: new Date(Date.now() + 60000), // run in 1 minute
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Cron Jobs (Timezone-Aware)
|
|
66
68
|
|
|
67
69
|
```typescript
|
|
68
70
|
await scheduler.schedule({
|
|
69
71
|
name: "daily-report",
|
|
70
|
-
// data: { type: "report" }, // optional payload
|
|
71
72
|
repeat: {
|
|
72
|
-
cron: "0 9 * * *",
|
|
73
|
+
cron: "0 9 * * *", // every day at 9 AM
|
|
73
74
|
timezone: "Asia/Kolkata", // default is UTC
|
|
74
75
|
},
|
|
75
76
|
});
|
|
76
77
|
```
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
Run a job repeatedly with a fixed delay between executions (e.g., every 5 minutes).
|
|
79
|
+
### Interval Jobs
|
|
81
80
|
|
|
82
81
|
```typescript
|
|
83
82
|
await scheduler.schedule({
|
|
84
83
|
name: "cleanup-logs",
|
|
85
84
|
data: {},
|
|
86
85
|
repeat: {
|
|
87
|
-
every: 5 * 60 * 1000, // 5 minutes in milliseconds
|
|
86
|
+
every: 5 * 60 * 1000, // every 5 minutes (in milliseconds)
|
|
88
87
|
},
|
|
89
88
|
});
|
|
90
89
|
```
|
|
91
90
|
|
|
92
|
-
|
|
91
|
+
### Bulk Scheduling
|
|
93
92
|
|
|
94
|
-
|
|
93
|
+
For high-performance ingestion:
|
|
95
94
|
|
|
96
95
|
```typescript
|
|
97
|
-
await scheduler.
|
|
98
|
-
name: "email",
|
|
99
|
-
data: { userId:
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
const jobs = await scheduler.scheduleBulk([
|
|
97
|
+
{ name: "email", data: { userId: 1 } },
|
|
98
|
+
{ name: "email", data: { userId: 2 } },
|
|
99
|
+
{ name: "email", data: { userId: 3 } },
|
|
100
|
+
]);
|
|
102
101
|
```
|
|
103
102
|
|
|
104
|
-
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Job Management
|
|
106
|
+
|
|
107
|
+
### Get Job by ID
|
|
105
108
|
|
|
106
109
|
```typescript
|
|
107
|
-
|
|
108
|
-
await scheduler.cancel(jobId);
|
|
110
|
+
const job = await scheduler.getJob(jobId);
|
|
109
111
|
```
|
|
110
112
|
|
|
111
|
-
|
|
113
|
+
### Query Jobs
|
|
114
|
+
|
|
115
|
+
List jobs with filtering, sorting, and pagination:
|
|
112
116
|
|
|
113
117
|
```typescript
|
|
114
|
-
const
|
|
118
|
+
const jobs = await scheduler.getJobs({
|
|
119
|
+
name: "daily-report",
|
|
120
|
+
status: "failed", // or ["failed", "pending"]
|
|
121
|
+
sort: { field: "updatedAt", order: "desc" },
|
|
122
|
+
limit: 10,
|
|
123
|
+
skip: 0,
|
|
124
|
+
});
|
|
115
125
|
```
|
|
116
126
|
|
|
117
|
-
|
|
127
|
+
### Update Job
|
|
118
128
|
|
|
119
|
-
Update job
|
|
129
|
+
Update job data, reschedule, or modify configuration:
|
|
120
130
|
|
|
121
131
|
```typescript
|
|
122
132
|
await scheduler.updateJob(jobId, {
|
|
123
133
|
data: { page: 2 },
|
|
124
134
|
nextRunAt: new Date(Date.now() + 60000), // delay by 1 min
|
|
125
|
-
repeat: { every: 60000 }, //
|
|
135
|
+
repeat: { every: 60000 }, // change to run every minute
|
|
126
136
|
});
|
|
127
137
|
```
|
|
128
138
|
|
|
129
|
-
|
|
139
|
+
### Cancel Job
|
|
130
140
|
|
|
131
|
-
|
|
141
|
+
```typescript
|
|
142
|
+
await scheduler.cancel(jobId);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Advanced Features
|
|
148
|
+
|
|
149
|
+
### Retries with Backoff
|
|
132
150
|
|
|
133
151
|
```typescript
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
152
|
+
// Simple: 3 attempts with instant retry
|
|
153
|
+
await scheduler.schedule({
|
|
154
|
+
name: "webhook",
|
|
155
|
+
retry: 3,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Advanced: custom delay and backoff
|
|
159
|
+
await scheduler.schedule({
|
|
160
|
+
name: "api-call",
|
|
161
|
+
retry: {
|
|
162
|
+
maxAttempts: 5,
|
|
163
|
+
delay: 1000, // 1 second fixed delay
|
|
164
|
+
// or: delay: (attempt) => attempt * 1000 // dynamic backoff
|
|
165
|
+
},
|
|
166
|
+
});
|
|
138
167
|
```
|
|
139
168
|
|
|
140
|
-
|
|
169
|
+
### Job Deduplication
|
|
141
170
|
|
|
142
|
-
|
|
171
|
+
Prevent duplicate jobs using idempotency keys:
|
|
143
172
|
|
|
144
173
|
```typescript
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
174
|
+
await scheduler.schedule({
|
|
175
|
+
name: "email",
|
|
176
|
+
data: { userId: 123 },
|
|
177
|
+
dedupeKey: "email:user:123", // only one job with this key
|
|
178
|
+
});
|
|
179
|
+
```
|
|
151
180
|
|
|
152
|
-
|
|
153
|
-
scheduler.on("worker:start", (workerId) =>
|
|
154
|
-
console.log("Worker started:", workerId)
|
|
155
|
-
);
|
|
156
|
-
scheduler.on("worker:stop", (workerId) =>
|
|
157
|
-
console.log("Worker stopped:", workerId)
|
|
158
|
-
);
|
|
181
|
+
### Event Monitoring
|
|
159
182
|
|
|
160
|
-
|
|
161
|
-
scheduler.on("job:created", (job) => console.log("Job created:", job._id));
|
|
162
|
-
scheduler.on("job:start", (job) => console.log("Job processing:", job._id));
|
|
183
|
+
```typescript
|
|
163
184
|
scheduler.on("job:success", (job) => console.log("Job done:", job._id));
|
|
164
185
|
scheduler.on("job:fail", ({ job, error }) =>
|
|
165
186
|
console.error("Job failed:", job._id, error)
|
|
166
187
|
);
|
|
167
188
|
scheduler.on("job:retry", (job) =>
|
|
168
|
-
console.warn("
|
|
189
|
+
console.warn("Retrying:", job._id, "attempt", job.attempts)
|
|
169
190
|
);
|
|
170
|
-
|
|
191
|
+
|
|
192
|
+
// More events: scheduler:start, scheduler:stop, worker:start,
|
|
193
|
+
// worker:stop, job:created, job:start, job:cancel
|
|
171
194
|
```
|
|
172
195
|
|
|
173
|
-
|
|
196
|
+
### Graceful Shutdown
|
|
174
197
|
|
|
175
|
-
|
|
198
|
+
Wait for in-flight jobs to complete:
|
|
176
199
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
200
|
+
```typescript
|
|
201
|
+
await scheduler.stop({
|
|
202
|
+
graceful: true,
|
|
203
|
+
timeoutMs: 30000,
|
|
204
|
+
});
|
|
205
|
+
```
|
|
182
206
|
|
|
183
|
-
|
|
207
|
+
---
|
|
184
208
|
|
|
185
|
-
|
|
209
|
+
## Performance & Scaling
|
|
186
210
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
211
|
+
### Automatic Indexing
|
|
212
|
+
|
|
213
|
+
**MongoDB indexes are created automatically** when you initialize `MongoJobStore`. No manual setup required.
|
|
214
|
+
|
|
215
|
+
The library creates three indexes in background mode:
|
|
216
|
+
|
|
217
|
+
- `{ status: 1, nextRunAt: 1 }` — for job polling (critical)
|
|
218
|
+
- `{ dedupeKey: 1 }` — for deduplication (unique)
|
|
219
|
+
- `{ lockedAt: 1 }` — for stale lock recovery
|
|
220
|
+
|
|
221
|
+
These indexes prevent query time from degrading from O(log n) to O(n) at scale.
|
|
222
|
+
|
|
223
|
+
### Distributed Systems
|
|
224
|
+
|
|
225
|
+
Run **multiple scheduler instances** (different servers, pods, or processes) connected to the same MongoDB:
|
|
226
|
+
|
|
227
|
+
- **Atomic Locking** — uses `findOneAndUpdate` to prevent race conditions
|
|
228
|
+
- **Concurrency Control** — only one worker executes a job instance
|
|
229
|
+
- **Horizontally Scalable** — supports MongoDB sharding
|
|
230
|
+
|
|
231
|
+
See `architecture.md` for sharding strategy and production guidelines.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Documentation
|
|
236
|
+
|
|
237
|
+
- **`architecture.md`** — Internal design, MongoDB schema, sharding strategy, production checklist
|
|
238
|
+
- **Job lifecycle** — pending → running → completed/failed
|
|
239
|
+
- **Retry & repeat semantics** — at-most-once guarantees
|
|
240
|
+
- **Correctness guarantees** — what we ensure and what we don't
|
|
241
|
+
|
|
242
|
+
---
|
|
190
243
|
|
|
191
244
|
## Status
|
|
192
245
|
|
|
193
|
-
**Early-stage but production-tested.**
|
|
246
|
+
**Early-stage but production-tested.**
|
|
194
247
|
API may evolve before 1.0.0.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## License
|
|
252
|
+
|
|
253
|
+
MIT
|
package/dist/core/scheduler.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { SchedulerEventMap } from "../types/events";
|
|
|
2
2
|
import { JobStore, JobUpdates } from "../store";
|
|
3
3
|
import { Job } from "../types/job";
|
|
4
4
|
import { ScheduleOptions } from "../types/schedule";
|
|
5
|
+
import { JobQuery } from "../types/query";
|
|
5
6
|
export interface SchedulerOptions {
|
|
6
7
|
id?: string;
|
|
7
8
|
store?: JobStore;
|
|
@@ -33,6 +34,10 @@ export declare class Scheduler {
|
|
|
33
34
|
* Get a job by ID
|
|
34
35
|
*/
|
|
35
36
|
getJob(jobId: unknown): Promise<Job | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Query jobs
|
|
39
|
+
*/
|
|
40
|
+
getJobs(query: JobQuery): Promise<Job[]>;
|
|
36
41
|
/**
|
|
37
42
|
* Update job data or schedule
|
|
38
43
|
*/
|
package/dist/core/scheduler.js
CHANGED
|
@@ -13,7 +13,7 @@ class Scheduler {
|
|
|
13
13
|
this.handler = options.handler;
|
|
14
14
|
this.workerCount = options.workers ?? 1;
|
|
15
15
|
this.pollInterval = options.pollIntervalMs ?? 500;
|
|
16
|
-
this.lockTimeout = options.lockTimeoutMs ??
|
|
16
|
+
this.lockTimeout = options.lockTimeoutMs ?? 10 * 60 * 1000; // default 10 minutes
|
|
17
17
|
this.defaultTimezone = options.defaultTimezone;
|
|
18
18
|
}
|
|
19
19
|
on(event, listener) {
|
|
@@ -94,6 +94,15 @@ class Scheduler {
|
|
|
94
94
|
}
|
|
95
95
|
return this.store.findById(jobId);
|
|
96
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Query jobs
|
|
99
|
+
*/
|
|
100
|
+
async getJobs(query) {
|
|
101
|
+
if (!this.store) {
|
|
102
|
+
throw new Error("Scheduler has no JobStore configured");
|
|
103
|
+
}
|
|
104
|
+
return this.store.findAll(query);
|
|
105
|
+
}
|
|
97
106
|
/**
|
|
98
107
|
* Update job data or schedule
|
|
99
108
|
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Job } from "../types/job";
|
|
2
2
|
import { JobStore, JobUpdates } from "./job-store";
|
|
3
|
+
import { JobQuery } from "../types/query";
|
|
3
4
|
export declare class InMemoryJobStore implements JobStore {
|
|
4
5
|
private jobs;
|
|
5
6
|
private mutex;
|
|
@@ -24,4 +25,5 @@ export declare class InMemoryJobStore implements JobStore {
|
|
|
24
25
|
findById(jobId: unknown): Promise<Job | null>;
|
|
25
26
|
renewLock(jobId: unknown, workerId: string): Promise<void>;
|
|
26
27
|
update(jobId: unknown, updates: JobUpdates): Promise<void>;
|
|
28
|
+
findAll(query: JobQuery): Promise<Job[]>;
|
|
27
29
|
}
|
|
@@ -149,5 +149,31 @@ class InMemoryJobStore {
|
|
|
149
149
|
}
|
|
150
150
|
job.updatedAt = new Date();
|
|
151
151
|
}
|
|
152
|
+
async findAll(query) {
|
|
153
|
+
let jobs = Array.from(this.jobs.values());
|
|
154
|
+
// Filter
|
|
155
|
+
if (query.name) {
|
|
156
|
+
jobs = jobs.filter((j) => j.name === query.name);
|
|
157
|
+
}
|
|
158
|
+
if (query.status) {
|
|
159
|
+
const statuses = Array.isArray(query.status)
|
|
160
|
+
? query.status
|
|
161
|
+
: [query.status];
|
|
162
|
+
jobs = jobs.filter((j) => statuses.includes(j.status));
|
|
163
|
+
}
|
|
164
|
+
// Sort
|
|
165
|
+
if (query.sort) {
|
|
166
|
+
const { field, order } = query.sort;
|
|
167
|
+
jobs.sort((a, b) => {
|
|
168
|
+
const valA = a[field].getTime();
|
|
169
|
+
const valB = b[field].getTime();
|
|
170
|
+
return order === "asc" ? valA - valB : valB - valA;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
// Skip/Limit
|
|
174
|
+
const start = query.skip ?? 0;
|
|
175
|
+
const end = query.limit ? start + query.limit : undefined;
|
|
176
|
+
return jobs.slice(start, end);
|
|
177
|
+
}
|
|
152
178
|
}
|
|
153
179
|
exports.InMemoryJobStore = InMemoryJobStore;
|
|
@@ -55,9 +55,14 @@ export interface JobStore {
|
|
|
55
55
|
* Update job properties (data persistence)
|
|
56
56
|
*/
|
|
57
57
|
update(jobId: unknown, updates: JobUpdates): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Find all jobs matching query
|
|
60
|
+
*/
|
|
61
|
+
findAll(query: JobQuery): Promise<Job[]>;
|
|
58
62
|
}
|
|
59
63
|
import { RetryOptions } from "../types/retry";
|
|
60
64
|
import { RepeatOptions } from "../types/repeat";
|
|
65
|
+
import { JobQuery } from "../types/query";
|
|
61
66
|
export interface JobUpdates {
|
|
62
67
|
data?: unknown;
|
|
63
68
|
nextRunAt?: Date;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Db, ObjectId } from "mongodb";
|
|
2
2
|
import { JobStore, JobUpdates } from "../job-store";
|
|
3
3
|
import { Job } from "../../types/job";
|
|
4
|
+
import { JobQuery } from "../../types/query";
|
|
4
5
|
export interface MongoJobStoreOptions {
|
|
5
6
|
collectionName?: string;
|
|
6
7
|
lockTimeoutMs?: number;
|
|
@@ -9,6 +10,10 @@ export declare class MongoJobStore implements JobStore {
|
|
|
9
10
|
private readonly collection;
|
|
10
11
|
private readonly defaultLockTimeoutMs;
|
|
11
12
|
constructor(db: Db, options?: MongoJobStoreOptions);
|
|
13
|
+
/**
|
|
14
|
+
* Create necessary indexes for optimal query performance
|
|
15
|
+
*/
|
|
16
|
+
private ensureIndexes;
|
|
12
17
|
create(job: Job): Promise<Job>;
|
|
13
18
|
createBulk(jobs: Job[]): Promise<Job[]>;
|
|
14
19
|
findAndLockNext(options: {
|
|
@@ -30,4 +35,5 @@ export declare class MongoJobStore implements JobStore {
|
|
|
30
35
|
}): Promise<number>;
|
|
31
36
|
renewLock(id: ObjectId, workerId: string): Promise<void>;
|
|
32
37
|
update(id: ObjectId, updates: JobUpdates): Promise<void>;
|
|
38
|
+
findAll(query: JobQuery): Promise<Job[]>;
|
|
33
39
|
}
|
|
@@ -5,6 +5,23 @@ class MongoJobStore {
|
|
|
5
5
|
constructor(db, options = {}) {
|
|
6
6
|
this.collection = db.collection(options.collectionName ?? "scheduler_jobs");
|
|
7
7
|
this.defaultLockTimeoutMs = options.lockTimeoutMs ?? 30000;
|
|
8
|
+
// Auto-create indexes for performance
|
|
9
|
+
this.ensureIndexes().catch((err) => {
|
|
10
|
+
console.error("Failed to create indexes:", err);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create necessary indexes for optimal query performance
|
|
15
|
+
*/
|
|
16
|
+
async ensureIndexes() {
|
|
17
|
+
await Promise.all([
|
|
18
|
+
// Primary index for job polling (findAndLockNext)
|
|
19
|
+
this.collection.createIndex({ status: 1, nextRunAt: 1 }, { background: true }),
|
|
20
|
+
// Index for deduplication
|
|
21
|
+
this.collection.createIndex({ dedupeKey: 1 }, { unique: true, sparse: true, background: true }),
|
|
22
|
+
// Index for stale lock recovery
|
|
23
|
+
this.collection.createIndex({ lockedAt: 1 }, { sparse: true, background: true }),
|
|
24
|
+
]);
|
|
8
25
|
}
|
|
9
26
|
// --------------------------------------------------
|
|
10
27
|
// CREATE
|
|
@@ -195,5 +212,30 @@ class MongoJobStore {
|
|
|
195
212
|
$set.repeat = updates.repeat;
|
|
196
213
|
await this.collection.updateOne({ _id: id }, { $set });
|
|
197
214
|
}
|
|
215
|
+
async findAll(query) {
|
|
216
|
+
const filter = {};
|
|
217
|
+
if (query.name) {
|
|
218
|
+
filter.name = query.name;
|
|
219
|
+
}
|
|
220
|
+
if (query.status) {
|
|
221
|
+
filter.status = Array.isArray(query.status)
|
|
222
|
+
? { $in: query.status }
|
|
223
|
+
: query.status;
|
|
224
|
+
}
|
|
225
|
+
let cursor = this.collection.find(filter);
|
|
226
|
+
if (query.sort) {
|
|
227
|
+
cursor = cursor.sort({
|
|
228
|
+
[query.sort.field]: query.sort.order === "asc" ? 1 : -1,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (query.skip) {
|
|
232
|
+
cursor = cursor.skip(query.skip);
|
|
233
|
+
}
|
|
234
|
+
if (query.limit) {
|
|
235
|
+
cursor = cursor.limit(query.limit);
|
|
236
|
+
}
|
|
237
|
+
const docs = await cursor.toArray();
|
|
238
|
+
return docs;
|
|
239
|
+
}
|
|
198
240
|
}
|
|
199
241
|
exports.MongoJobStore = MongoJobStore;
|
package/dist/types/job.d.ts
CHANGED
package/dist/types/schedule.d.ts
CHANGED
package/dist/worker/worker.js
CHANGED
|
@@ -10,7 +10,7 @@ class Worker {
|
|
|
10
10
|
this.handler = handler;
|
|
11
11
|
this.running = false;
|
|
12
12
|
this.pollInterval = options.pollIntervalMs ?? 500;
|
|
13
|
-
this.lockTimeout = options.lockTimeoutMs ??
|
|
13
|
+
this.lockTimeout = options.lockTimeoutMs ?? 10 * 60 * 1000; // default 10 minutes
|
|
14
14
|
this.workerId =
|
|
15
15
|
options.workerId ?? `worker-${Math.random().toString(36).slice(2)}`;
|
|
16
16
|
this.defaultTimezone = options.defaultTimezone;
|
|
@@ -128,7 +128,10 @@ class Worker {
|
|
|
128
128
|
catch (err) {
|
|
129
129
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
130
130
|
const attempts = (job.attempts ?? 0) + 1;
|
|
131
|
-
|
|
131
|
+
let retry = job.retry;
|
|
132
|
+
if (typeof retry === "number") {
|
|
133
|
+
retry = { maxAttempts: retry, delay: 0 };
|
|
134
|
+
}
|
|
132
135
|
if (retry && attempts < retry.maxAttempts) {
|
|
133
136
|
const nextRunAt = new Date(Date.now() + (0, retry_1.getRetryDelay)(retry, attempts));
|
|
134
137
|
await this.store.reschedule(job._id, nextRunAt, { attempts });
|
package/package.json
CHANGED