agenda 4.4.0 → 5.0.0
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 +20 -8
- package/dist/agenda/create.d.ts +2 -2
- package/dist/agenda/create.d.ts.map +1 -1
- package/dist/agenda/create.js.map +1 -1
- package/dist/agenda/define.d.ts +2 -2
- package/dist/agenda/define.d.ts.map +1 -1
- package/dist/agenda/define.js.map +1 -1
- package/dist/agenda/every.d.ts +2 -1
- package/dist/agenda/every.d.ts.map +1 -1
- package/dist/agenda/every.js +1 -1
- package/dist/agenda/every.js.map +1 -1
- package/dist/agenda/find-and-lock-next-job.d.ts.map +1 -1
- package/dist/agenda/find-and-lock-next-job.js +3 -4
- package/dist/agenda/find-and-lock-next-job.js.map +1 -1
- package/dist/agenda/now.d.ts +2 -2
- package/dist/agenda/now.d.ts.map +1 -1
- package/dist/agenda/now.js.map +1 -1
- package/dist/agenda/schedule.d.ts +3 -3
- package/dist/agenda/schedule.d.ts.map +1 -1
- package/dist/agenda/schedule.js.map +1 -1
- package/dist/job/compute-next-run-at.js +2 -2
- package/dist/job/compute-next-run-at.js.map +1 -1
- package/dist/job/index.d.ts +6 -3
- package/dist/job/index.d.ts.map +1 -1
- package/dist/job/index.js.map +1 -1
- package/lib/agenda/cancel.ts +28 -0
- package/lib/agenda/close.ts +37 -0
- package/lib/agenda/create.ts +22 -0
- package/lib/agenda/database.ts +63 -0
- package/lib/agenda/db-init.ts +45 -0
- package/lib/agenda/default-concurrency.ts +19 -0
- package/lib/agenda/default-lock-lifetime.ts +17 -0
- package/lib/agenda/default-lock-limit.ts +17 -0
- package/lib/agenda/define.ts +85 -0
- package/lib/agenda/disable.ts +28 -0
- package/lib/agenda/drain.ts +30 -0
- package/lib/agenda/enable.ts +29 -0
- package/lib/agenda/every.ts +86 -0
- package/lib/agenda/find-and-lock-next-job.ts +78 -0
- package/lib/agenda/has-mongo-protocol.ts +8 -0
- package/lib/agenda/index.ts +227 -0
- package/lib/agenda/job-processing-queue.ts +99 -0
- package/lib/agenda/jobs.ts +31 -0
- package/lib/agenda/lock-limit.ts +17 -0
- package/lib/agenda/max-concurrency.ts +20 -0
- package/lib/agenda/mongo.ts +21 -0
- package/lib/agenda/name.ts +16 -0
- package/lib/agenda/now.ts +31 -0
- package/lib/agenda/process-every.ts +18 -0
- package/lib/agenda/purge.ts +19 -0
- package/lib/agenda/save-job.ts +185 -0
- package/lib/agenda/schedule.ts +79 -0
- package/lib/agenda/sort.ts +17 -0
- package/lib/agenda/start.ts +30 -0
- package/lib/agenda/stop.ts +49 -0
- package/lib/cjs.ts +5 -0
- package/lib/index.ts +11 -0
- package/lib/job/compute-next-run-at.ts +191 -0
- package/lib/job/disable.ts +11 -0
- package/lib/job/enable.ts +11 -0
- package/lib/job/fail.ts +29 -0
- package/lib/job/index.ts +221 -0
- package/lib/job/is-running.ts +29 -0
- package/lib/job/priority.ts +11 -0
- package/lib/job/remove.ts +10 -0
- package/lib/job/repeat-at.ts +12 -0
- package/lib/job/repeat-every.ts +40 -0
- package/lib/job/run.ts +125 -0
- package/lib/job/save.ts +11 -0
- package/lib/job/schedule.ts +15 -0
- package/lib/job/set-shouldsaveresult.ts +10 -0
- package/lib/job/to-json.ts +36 -0
- package/lib/job/touch.ts +11 -0
- package/lib/job/unique.ts +18 -0
- package/lib/utils/create-job.ts +13 -0
- package/lib/utils/index.ts +3 -0
- package/lib/utils/parse-priority.ts +37 -0
- package/lib/utils/process-jobs.ts +379 -0
- package/package.json +13 -11
- package/tsconfig.json +72 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Job } from ".";
|
|
2
|
+
import * as parser from "cron-parser";
|
|
3
|
+
import humanInterval from "human-interval";
|
|
4
|
+
import createDebugger from "debug";
|
|
5
|
+
import moment from "moment-timezone";
|
|
6
|
+
// @ts-expect-error
|
|
7
|
+
import date from "date.js";
|
|
8
|
+
|
|
9
|
+
const debug = createDebugger("agenda:job");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Internal method used to compute next time a job should run and sets the proper values
|
|
13
|
+
* @name Job#computeNextRunAt
|
|
14
|
+
* @function
|
|
15
|
+
*/
|
|
16
|
+
export const computeNextRunAt = function (this: Job): Job {
|
|
17
|
+
const interval = this.attrs.repeatInterval;
|
|
18
|
+
const timezone = this.attrs.repeatTimezone;
|
|
19
|
+
const { repeatAt } = this.attrs;
|
|
20
|
+
const previousNextRunAt = this.attrs.nextRunAt || new Date();
|
|
21
|
+
this.attrs.nextRunAt = undefined;
|
|
22
|
+
|
|
23
|
+
const dateForTimezone = (date: Date): moment.Moment => {
|
|
24
|
+
const mdate: moment.Moment = moment(date);
|
|
25
|
+
if (timezone) {
|
|
26
|
+
mdate.tz(timezone);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return mdate;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Internal method that computes the interval
|
|
34
|
+
*/
|
|
35
|
+
const computeFromInterval = () => {
|
|
36
|
+
debug(
|
|
37
|
+
"[%s:%s] computing next run via interval [%s]",
|
|
38
|
+
this.attrs.name,
|
|
39
|
+
this.attrs._id,
|
|
40
|
+
interval
|
|
41
|
+
);
|
|
42
|
+
const dateNow = new Date();
|
|
43
|
+
let lastRun: Date = this.attrs.lastRunAt || dateNow;
|
|
44
|
+
let { startDate, endDate, skipDays } = this.attrs;
|
|
45
|
+
lastRun = dateForTimezone(lastRun).toDate();
|
|
46
|
+
const cronOptions: any = { currentDate: lastRun };
|
|
47
|
+
if (timezone) {
|
|
48
|
+
cronOptions.tz = timezone;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
let cronTime = parser.parseExpression(interval!, cronOptions);
|
|
53
|
+
let nextDate: Date | null = cronTime.next().toDate();
|
|
54
|
+
if (
|
|
55
|
+
nextDate.getTime() === lastRun.getTime() ||
|
|
56
|
+
nextDate.getTime() <= previousNextRunAt.getTime()
|
|
57
|
+
) {
|
|
58
|
+
// Handle cronTime giving back the same date for the next run time
|
|
59
|
+
cronOptions.currentDate = new Date(lastRun.getTime() + 1000);
|
|
60
|
+
cronTime = parser.parseExpression(interval!, cronOptions);
|
|
61
|
+
nextDate = cronTime.next().toDate();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// If start date is present, check if the nextDate should be larger or equal to startDate. If not set startDate as nextDate
|
|
65
|
+
if (startDate) {
|
|
66
|
+
startDate = moment
|
|
67
|
+
.tz(moment(startDate).format("YYYY-MM-DD HH:mm"), timezone!)
|
|
68
|
+
.toDate();
|
|
69
|
+
if (startDate > nextDate) {
|
|
70
|
+
cronOptions.currentDate = startDate;
|
|
71
|
+
cronTime = parser.parseExpression(interval!, cronOptions);
|
|
72
|
+
nextDate = cronTime.next().toDate();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// If job has run in the past and skipDays is not null, add skipDays to nextDate
|
|
77
|
+
if (dateNow > lastRun && skipDays !== null) {
|
|
78
|
+
try {
|
|
79
|
+
nextDate = new Date(
|
|
80
|
+
nextDate.getTime() + (humanInterval(skipDays) ?? 0)
|
|
81
|
+
);
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// If endDate is less than the nextDate, set nextDate to null to stop the job from running further
|
|
86
|
+
if (endDate) {
|
|
87
|
+
const endDateDate: Date = moment
|
|
88
|
+
.tz(moment(endDate).format("YYYY-MM-DD HH:mm"), timezone!)
|
|
89
|
+
.toDate();
|
|
90
|
+
if (nextDate > endDateDate) {
|
|
91
|
+
nextDate = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.attrs.nextRunAt = nextDate;
|
|
96
|
+
debug(
|
|
97
|
+
"[%s:%s] nextRunAt set to [%s]",
|
|
98
|
+
this.attrs.name,
|
|
99
|
+
this.attrs._id,
|
|
100
|
+
this.attrs.nextRunAt?.toISOString()
|
|
101
|
+
);
|
|
102
|
+
// Either `xo` linter or Node.js 8 stumble on this line if it isn't just ignored
|
|
103
|
+
} catch {
|
|
104
|
+
debug(
|
|
105
|
+
"[%s:%s] failed nextRunAt based on interval [%s]",
|
|
106
|
+
this.attrs.name,
|
|
107
|
+
this.attrs._id,
|
|
108
|
+
interval
|
|
109
|
+
);
|
|
110
|
+
// Nope, humanInterval then!
|
|
111
|
+
try {
|
|
112
|
+
if (!this.attrs.lastRunAt && humanInterval(interval)) {
|
|
113
|
+
this.attrs.nextRunAt = lastRun;
|
|
114
|
+
debug(
|
|
115
|
+
"[%s:%s] nextRunAt set to [%s]",
|
|
116
|
+
this.attrs.name,
|
|
117
|
+
this.attrs._id,
|
|
118
|
+
this.attrs.nextRunAt.toISOString()
|
|
119
|
+
);
|
|
120
|
+
} else {
|
|
121
|
+
this.attrs.nextRunAt = new Date(
|
|
122
|
+
lastRun.getTime() + (humanInterval(interval) ?? 0)
|
|
123
|
+
);
|
|
124
|
+
debug(
|
|
125
|
+
"[%s:%s] nextRunAt set to [%s]",
|
|
126
|
+
this.attrs.name,
|
|
127
|
+
this.attrs._id,
|
|
128
|
+
this.attrs.nextRunAt.toISOString()
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
// Either `xo` linter or Node.js 8 stumble on this line if it isn't just ignored
|
|
132
|
+
} catch {}
|
|
133
|
+
} finally {
|
|
134
|
+
if (!this.attrs.nextRunAt?.getTime()) {
|
|
135
|
+
this.attrs.nextRunAt = undefined;
|
|
136
|
+
debug(
|
|
137
|
+
"[%s:%s] failed to calculate nextRunAt due to invalid repeat interval",
|
|
138
|
+
this.attrs.name,
|
|
139
|
+
this.attrs._id
|
|
140
|
+
);
|
|
141
|
+
this.fail(
|
|
142
|
+
"failed to calculate nextRunAt due to invalid repeat interval"
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Internal method to compute next run time from the repeat string
|
|
150
|
+
*/
|
|
151
|
+
const computeFromRepeatAt = () => {
|
|
152
|
+
const lastRun = this.attrs.lastRunAt || new Date();
|
|
153
|
+
const nextDate: Date = date(repeatAt);
|
|
154
|
+
|
|
155
|
+
// If you do not specify offset date for below test it will fail for ms
|
|
156
|
+
const offset = Date.now();
|
|
157
|
+
if (offset === date(repeatAt, offset).getTime()) {
|
|
158
|
+
this.attrs.nextRunAt = undefined;
|
|
159
|
+
debug(
|
|
160
|
+
"[%s:%s] failed to calculate repeatAt due to invalid format",
|
|
161
|
+
this.attrs.name,
|
|
162
|
+
this.attrs._id
|
|
163
|
+
);
|
|
164
|
+
this.fail("failed to calculate repeatAt time due to invalid format");
|
|
165
|
+
} else if (nextDate.getTime() === lastRun.getTime()) {
|
|
166
|
+
this.attrs.nextRunAt = date("tomorrow at ", repeatAt);
|
|
167
|
+
debug(
|
|
168
|
+
"[%s:%s] nextRunAt set to [%s]",
|
|
169
|
+
this.attrs.name,
|
|
170
|
+
this.attrs._id,
|
|
171
|
+
this.attrs.nextRunAt?.toISOString()
|
|
172
|
+
);
|
|
173
|
+
} else {
|
|
174
|
+
this.attrs.nextRunAt = date(repeatAt);
|
|
175
|
+
debug(
|
|
176
|
+
"[%s:%s] nextRunAt set to [%s]",
|
|
177
|
+
this.attrs.name,
|
|
178
|
+
this.attrs._id,
|
|
179
|
+
this.attrs.nextRunAt?.toISOString()
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
if (interval) {
|
|
185
|
+
computeFromInterval();
|
|
186
|
+
} else if (repeatAt) {
|
|
187
|
+
computeFromRepeatAt();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return this;
|
|
191
|
+
};
|
package/lib/job/fail.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import { Job } from ".";
|
|
3
|
+
|
|
4
|
+
const debug = createDebugger("agenda:job");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Fails the job with a reason (error) specified
|
|
8
|
+
* @name Job#fail
|
|
9
|
+
* @function
|
|
10
|
+
* @param reason reason job failed
|
|
11
|
+
*/
|
|
12
|
+
export const fail = function (this: Job, reason: string | Error): Job {
|
|
13
|
+
if (reason instanceof Error) {
|
|
14
|
+
reason = reason.message;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.attrs.failReason = reason;
|
|
18
|
+
this.attrs.failCount = (this.attrs.failCount || 0) + 1;
|
|
19
|
+
const now = new Date();
|
|
20
|
+
this.attrs.failedAt = now;
|
|
21
|
+
this.attrs.lastFinishedAt = now;
|
|
22
|
+
debug(
|
|
23
|
+
"[%s:%s] fail() called [%d] times so far",
|
|
24
|
+
this.attrs.name,
|
|
25
|
+
this.attrs._id,
|
|
26
|
+
this.attrs.failCount
|
|
27
|
+
);
|
|
28
|
+
return this;
|
|
29
|
+
};
|
package/lib/job/index.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { toJson } from "./to-json";
|
|
2
|
+
import { computeNextRunAt } from "./compute-next-run-at";
|
|
3
|
+
import { repeatEvery } from "./repeat-every";
|
|
4
|
+
import { repeatAt } from "./repeat-at";
|
|
5
|
+
import { disable } from "./disable";
|
|
6
|
+
import { enable } from "./enable";
|
|
7
|
+
import { unique } from "./unique";
|
|
8
|
+
import { schedule } from "./schedule";
|
|
9
|
+
import { priority } from "./priority";
|
|
10
|
+
import { fail } from "./fail";
|
|
11
|
+
import { run } from "./run";
|
|
12
|
+
import { isRunning } from "./is-running";
|
|
13
|
+
import { save } from "./save";
|
|
14
|
+
import { remove } from "./remove";
|
|
15
|
+
import { touch } from "./touch";
|
|
16
|
+
import { setShouldSaveResult } from "./set-shouldsaveresult";
|
|
17
|
+
import { parsePriority } from "../utils";
|
|
18
|
+
import { Agenda } from "../agenda";
|
|
19
|
+
import { JobPriority } from "../agenda/define";
|
|
20
|
+
import * as mongodb from "mongodb";
|
|
21
|
+
|
|
22
|
+
type Modify<T, R> = Omit<T, keyof R> & R
|
|
23
|
+
|
|
24
|
+
export interface JobAttributesData {
|
|
25
|
+
[key: string]: any;
|
|
26
|
+
}
|
|
27
|
+
export interface JobAttributes<
|
|
28
|
+
T extends JobAttributesData = JobAttributesData
|
|
29
|
+
> {
|
|
30
|
+
/**
|
|
31
|
+
* The record identity.
|
|
32
|
+
*/
|
|
33
|
+
_id: mongodb.ObjectId;
|
|
34
|
+
|
|
35
|
+
agenda: Agenda;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The type of the job (single|normal).
|
|
39
|
+
*/
|
|
40
|
+
type: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The name of the job.
|
|
44
|
+
*/
|
|
45
|
+
name: string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Job's state
|
|
49
|
+
*/
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Date/time the job will run next.
|
|
54
|
+
*/
|
|
55
|
+
nextRunAt?: Date | null;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Date/time the job was locked.
|
|
59
|
+
*/
|
|
60
|
+
lockedAt?: Date | null;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The priority of the job.
|
|
64
|
+
*/
|
|
65
|
+
priority: number | string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The job details.
|
|
69
|
+
*/
|
|
70
|
+
data: T;
|
|
71
|
+
|
|
72
|
+
unique?: any;
|
|
73
|
+
uniqueOpts?: {
|
|
74
|
+
insertOnly: boolean;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* How often the job is repeated using a human-readable or cron format.
|
|
79
|
+
*/
|
|
80
|
+
repeatInterval?: string;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The timezone that conforms to [moment-timezone](http://momentjs.com/timezone/).
|
|
84
|
+
*/
|
|
85
|
+
repeatTimezone?: string | null;
|
|
86
|
+
|
|
87
|
+
repeatAt?: string;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Date/time the job was last run.
|
|
91
|
+
*/
|
|
92
|
+
lastRunAt?: Date;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Date/time the job last finished running.
|
|
96
|
+
*/
|
|
97
|
+
lastFinishedAt?: Date;
|
|
98
|
+
|
|
99
|
+
startDate?: Date | number | null;
|
|
100
|
+
endDate?: Date | number | null;
|
|
101
|
+
skipDays?: string | null;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The reason the job failed.
|
|
105
|
+
*/
|
|
106
|
+
failReason?: string;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The number of times the job has failed.
|
|
110
|
+
*/
|
|
111
|
+
failCount?: number;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The date/time the job last failed.
|
|
115
|
+
*/
|
|
116
|
+
failedAt?: Date;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Date/time the job was last modified.
|
|
120
|
+
*/
|
|
121
|
+
lastModifiedBy?: string;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Should the return value of the job be persisted.
|
|
125
|
+
*/
|
|
126
|
+
shouldSaveResult?: boolean;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Result of the finished job.
|
|
130
|
+
*/
|
|
131
|
+
result?: unknown;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @class
|
|
136
|
+
* @param {Object} args - Job Options
|
|
137
|
+
* @property {Object} agenda - The Agenda instance
|
|
138
|
+
* @property {Object} attrs
|
|
139
|
+
*/
|
|
140
|
+
class Job<T extends JobAttributesData = JobAttributesData> {
|
|
141
|
+
/**
|
|
142
|
+
* The agenda that created the job.
|
|
143
|
+
*/
|
|
144
|
+
agenda: Agenda;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The database record associated with the job.
|
|
148
|
+
*/
|
|
149
|
+
attrs: JobAttributes<T>;
|
|
150
|
+
|
|
151
|
+
toJSON!: typeof toJson;
|
|
152
|
+
computeNextRunAt!: typeof computeNextRunAt;
|
|
153
|
+
repeatEvery!: typeof repeatEvery;
|
|
154
|
+
repeatAt!: typeof repeatAt;
|
|
155
|
+
disable!: typeof disable;
|
|
156
|
+
enable!: typeof enable;
|
|
157
|
+
unique!: typeof unique;
|
|
158
|
+
schedule!: typeof schedule;
|
|
159
|
+
priority!: typeof priority;
|
|
160
|
+
fail!: typeof fail;
|
|
161
|
+
run!: typeof run;
|
|
162
|
+
isRunning!: typeof isRunning;
|
|
163
|
+
save!: typeof save;
|
|
164
|
+
remove!: typeof remove;
|
|
165
|
+
touch!: typeof touch;
|
|
166
|
+
setShouldSaveResult!: typeof setShouldSaveResult;
|
|
167
|
+
|
|
168
|
+
constructor(options: Modify<JobAttributes<T>, { _id?: mongodb.ObjectId; }>) {
|
|
169
|
+
const { agenda, type, nextRunAt, ...args } = options ?? {};
|
|
170
|
+
|
|
171
|
+
// Save Agenda instance
|
|
172
|
+
this.agenda = agenda;
|
|
173
|
+
|
|
174
|
+
// Set priority
|
|
175
|
+
args.priority =
|
|
176
|
+
args.priority === undefined
|
|
177
|
+
? JobPriority.normal
|
|
178
|
+
: parsePriority(args.priority);
|
|
179
|
+
|
|
180
|
+
// Set shouldSaveResult option
|
|
181
|
+
args.shouldSaveResult = args.shouldSaveResult || false
|
|
182
|
+
|
|
183
|
+
// Set attrs to args
|
|
184
|
+
const attrs: any = {};
|
|
185
|
+
for (const key in args) {
|
|
186
|
+
if ({}.hasOwnProperty.call(args, key)) {
|
|
187
|
+
// @ts-expect-error
|
|
188
|
+
attrs[key] = args[key];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Set defaults if undefined
|
|
193
|
+
this.attrs = {
|
|
194
|
+
...attrs,
|
|
195
|
+
// NOTE: What is the difference between 'once' here and 'single' in agenda/index.js?
|
|
196
|
+
name: attrs.name || "",
|
|
197
|
+
priority: attrs.priority,
|
|
198
|
+
type: type || "once",
|
|
199
|
+
nextRunAt: nextRunAt || new Date(),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
Job.prototype.toJSON = toJson;
|
|
205
|
+
Job.prototype.computeNextRunAt = computeNextRunAt;
|
|
206
|
+
Job.prototype.repeatEvery = repeatEvery;
|
|
207
|
+
Job.prototype.repeatAt = repeatAt;
|
|
208
|
+
Job.prototype.disable = disable;
|
|
209
|
+
Job.prototype.enable = enable;
|
|
210
|
+
Job.prototype.unique = unique;
|
|
211
|
+
Job.prototype.schedule = schedule;
|
|
212
|
+
Job.prototype.priority = priority;
|
|
213
|
+
Job.prototype.fail = fail;
|
|
214
|
+
Job.prototype.run = run;
|
|
215
|
+
Job.prototype.isRunning = isRunning;
|
|
216
|
+
Job.prototype.save = save;
|
|
217
|
+
Job.prototype.remove = remove;
|
|
218
|
+
Job.prototype.touch = touch;
|
|
219
|
+
Job.prototype.setShouldSaveResult = setShouldSaveResult;
|
|
220
|
+
|
|
221
|
+
export { Job };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Job } from ".";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A job is running if:
|
|
5
|
+
* (lastRunAt exists AND lastFinishedAt does not exist)
|
|
6
|
+
* OR
|
|
7
|
+
* (lastRunAt exists AND lastFinishedAt exists but the lastRunAt is newer [in time] than lastFinishedAt)
|
|
8
|
+
* @name Job#isRunning
|
|
9
|
+
* @function
|
|
10
|
+
* @returns Whether or not job is running at the moment (true for running)
|
|
11
|
+
*/
|
|
12
|
+
export const isRunning = function (this: Job): boolean {
|
|
13
|
+
if (!this.attrs.lastRunAt) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!this.attrs.lastFinishedAt) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (
|
|
22
|
+
this.attrs.lockedAt &&
|
|
23
|
+
this.attrs.lastRunAt.getTime() > this.attrs.lastFinishedAt.getTime()
|
|
24
|
+
) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return false;
|
|
29
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Job } from ".";
|
|
2
|
+
import { parsePriority } from "../utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sets priority of the job
|
|
6
|
+
* @param priority priority of when job should be queued
|
|
7
|
+
*/
|
|
8
|
+
export const priority = function (this: Job, priority: string): Job {
|
|
9
|
+
this.attrs.priority = parsePriority(priority);
|
|
10
|
+
return this;
|
|
11
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Job } from ".";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sets a job to repeat at a specific time
|
|
5
|
+
* @name Job#repeatAt
|
|
6
|
+
* @function
|
|
7
|
+
* @param time time to repeat job at (human readable or number)
|
|
8
|
+
*/
|
|
9
|
+
export const repeatAt = function (this: Job, time: string): Job {
|
|
10
|
+
this.attrs.repeatAt = time;
|
|
11
|
+
return this;
|
|
12
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Job } from ".";
|
|
2
|
+
|
|
3
|
+
export interface JobOptions {
|
|
4
|
+
timezone?: string;
|
|
5
|
+
startDate?: Date | number;
|
|
6
|
+
endDate?: Date | number;
|
|
7
|
+
skipDays?: string;
|
|
8
|
+
skipImmediate?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Sets a job to repeat every X amount of time
|
|
13
|
+
* @name Job#repeatEvery
|
|
14
|
+
* @function
|
|
15
|
+
* @param interval repeat every X
|
|
16
|
+
* @param options options to use for job
|
|
17
|
+
*/
|
|
18
|
+
export const repeatEvery = function (
|
|
19
|
+
this: Job,
|
|
20
|
+
interval: string,
|
|
21
|
+
options: JobOptions = {}
|
|
22
|
+
): Job {
|
|
23
|
+
this.attrs.repeatInterval = interval;
|
|
24
|
+
this.attrs.repeatTimezone = options.timezone ? options.timezone : null;
|
|
25
|
+
// Following options are added to handle start day
|
|
26
|
+
// and cases like run job every x days (skip some days)
|
|
27
|
+
this.attrs.startDate = options.startDate ?? null;
|
|
28
|
+
this.attrs.endDate = options.endDate ?? null;
|
|
29
|
+
this.attrs.skipDays = options.skipDays ?? null;
|
|
30
|
+
if (options.skipImmediate) {
|
|
31
|
+
// Set the lastRunAt time to the nextRunAt so that the new nextRunAt will be computed in reference to the current value.
|
|
32
|
+
this.attrs.lastRunAt = this.attrs.nextRunAt || new Date();
|
|
33
|
+
this.computeNextRunAt();
|
|
34
|
+
this.attrs.lastRunAt = undefined;
|
|
35
|
+
} else {
|
|
36
|
+
this.computeNextRunAt();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return this;
|
|
40
|
+
};
|
package/lib/job/run.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import { Job } from ".";
|
|
3
|
+
|
|
4
|
+
const debug = createDebugger("agenda:job");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal method (RUN)
|
|
8
|
+
* @name Job#run
|
|
9
|
+
* @function
|
|
10
|
+
*/
|
|
11
|
+
export const run = async function (this: Job): Promise<Job> {
|
|
12
|
+
const { agenda } = this;
|
|
13
|
+
const definition = agenda._definitions[this.attrs.name];
|
|
14
|
+
|
|
15
|
+
// @TODO: this lint issue should be looked into: https://eslint.org/docs/rules/no-async-promise-executor
|
|
16
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
17
|
+
return new Promise(async (resolve, reject) => {
|
|
18
|
+
this.attrs.lastRunAt = new Date();
|
|
19
|
+
debug(
|
|
20
|
+
"[%s:%s] setting lastRunAt to: %s",
|
|
21
|
+
this.attrs.name,
|
|
22
|
+
this.attrs._id,
|
|
23
|
+
this.attrs.lastRunAt.toISOString()
|
|
24
|
+
);
|
|
25
|
+
this.computeNextRunAt();
|
|
26
|
+
await this.save();
|
|
27
|
+
|
|
28
|
+
let finished = false;
|
|
29
|
+
const jobCallback = async (error?: Error, result?: unknown) => {
|
|
30
|
+
// We don't want to complete the job multiple times
|
|
31
|
+
if (finished) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
finished = true;
|
|
36
|
+
|
|
37
|
+
if (error) {
|
|
38
|
+
this.fail(error);
|
|
39
|
+
} else {
|
|
40
|
+
this.attrs.lastFinishedAt = new Date();
|
|
41
|
+
|
|
42
|
+
if(this.attrs.shouldSaveResult && result) {
|
|
43
|
+
this.attrs.result = result;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.attrs.lockedAt = null;
|
|
48
|
+
|
|
49
|
+
await this.save().catch((error: Error) => {
|
|
50
|
+
debug(
|
|
51
|
+
"[%s:%s] failed to be saved to MongoDB",
|
|
52
|
+
this.attrs.name,
|
|
53
|
+
this.attrs._id
|
|
54
|
+
);
|
|
55
|
+
reject(error);
|
|
56
|
+
});
|
|
57
|
+
debug(
|
|
58
|
+
"[%s:%s] was saved successfully to MongoDB",
|
|
59
|
+
this.attrs.name,
|
|
60
|
+
this.attrs._id
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (error) {
|
|
64
|
+
agenda.emit("fail", error, this);
|
|
65
|
+
agenda.emit("fail:" + this.attrs.name, error, this);
|
|
66
|
+
debug(
|
|
67
|
+
"[%s:%s] has failed [%s]",
|
|
68
|
+
this.attrs.name,
|
|
69
|
+
this.attrs._id,
|
|
70
|
+
error.message
|
|
71
|
+
);
|
|
72
|
+
} else {
|
|
73
|
+
agenda.emit("success", this);
|
|
74
|
+
agenda.emit("success:" + this.attrs.name, this);
|
|
75
|
+
debug("[%s:%s] has succeeded", this.attrs.name, this.attrs._id);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
agenda.emit("complete", this);
|
|
79
|
+
agenda.emit("complete:" + this.attrs.name, this);
|
|
80
|
+
debug(
|
|
81
|
+
"[%s:%s] job finished at [%s] and was unlocked",
|
|
82
|
+
this.attrs.name,
|
|
83
|
+
this.attrs._id,
|
|
84
|
+
this.attrs.lastFinishedAt
|
|
85
|
+
);
|
|
86
|
+
// Curiously, we still resolve successfully if the job processor failed.
|
|
87
|
+
// Agenda is not equipped to handle errors originating in user code, so, we leave them to inspect the side-effects of job.fail()
|
|
88
|
+
resolve(this);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
agenda.emit("start", this);
|
|
93
|
+
agenda.emit("start:" + this.attrs.name, this);
|
|
94
|
+
debug("[%s:%s] starting job", this.attrs.name, this.attrs._id);
|
|
95
|
+
if (!definition) {
|
|
96
|
+
debug(
|
|
97
|
+
"[%s:%s] has no definition, can not run",
|
|
98
|
+
this.attrs.name,
|
|
99
|
+
this.attrs._id
|
|
100
|
+
);
|
|
101
|
+
throw new Error("Undefined job");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (definition.fn.length === 2) {
|
|
105
|
+
debug(
|
|
106
|
+
"[%s:%s] process function being called",
|
|
107
|
+
this.attrs.name,
|
|
108
|
+
this.attrs._id
|
|
109
|
+
);
|
|
110
|
+
await definition.fn(this, jobCallback);
|
|
111
|
+
} else {
|
|
112
|
+
debug(
|
|
113
|
+
"[%s:%s] process function being called",
|
|
114
|
+
this.attrs.name,
|
|
115
|
+
this.attrs._id
|
|
116
|
+
);
|
|
117
|
+
const result = await definition.fn(this);
|
|
118
|
+
await jobCallback(undefined, result);
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
debug("[%s:%s] unknown error occurred", this.attrs.name, this.attrs._id);
|
|
122
|
+
await jobCallback(error as Error);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
};
|