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.
Files changed (80) hide show
  1. package/README.md +20 -8
  2. package/dist/agenda/create.d.ts +2 -2
  3. package/dist/agenda/create.d.ts.map +1 -1
  4. package/dist/agenda/create.js.map +1 -1
  5. package/dist/agenda/define.d.ts +2 -2
  6. package/dist/agenda/define.d.ts.map +1 -1
  7. package/dist/agenda/define.js.map +1 -1
  8. package/dist/agenda/every.d.ts +2 -1
  9. package/dist/agenda/every.d.ts.map +1 -1
  10. package/dist/agenda/every.js +1 -1
  11. package/dist/agenda/every.js.map +1 -1
  12. package/dist/agenda/find-and-lock-next-job.d.ts.map +1 -1
  13. package/dist/agenda/find-and-lock-next-job.js +3 -4
  14. package/dist/agenda/find-and-lock-next-job.js.map +1 -1
  15. package/dist/agenda/now.d.ts +2 -2
  16. package/dist/agenda/now.d.ts.map +1 -1
  17. package/dist/agenda/now.js.map +1 -1
  18. package/dist/agenda/schedule.d.ts +3 -3
  19. package/dist/agenda/schedule.d.ts.map +1 -1
  20. package/dist/agenda/schedule.js.map +1 -1
  21. package/dist/job/compute-next-run-at.js +2 -2
  22. package/dist/job/compute-next-run-at.js.map +1 -1
  23. package/dist/job/index.d.ts +6 -3
  24. package/dist/job/index.d.ts.map +1 -1
  25. package/dist/job/index.js.map +1 -1
  26. package/lib/agenda/cancel.ts +28 -0
  27. package/lib/agenda/close.ts +37 -0
  28. package/lib/agenda/create.ts +22 -0
  29. package/lib/agenda/database.ts +63 -0
  30. package/lib/agenda/db-init.ts +45 -0
  31. package/lib/agenda/default-concurrency.ts +19 -0
  32. package/lib/agenda/default-lock-lifetime.ts +17 -0
  33. package/lib/agenda/default-lock-limit.ts +17 -0
  34. package/lib/agenda/define.ts +85 -0
  35. package/lib/agenda/disable.ts +28 -0
  36. package/lib/agenda/drain.ts +30 -0
  37. package/lib/agenda/enable.ts +29 -0
  38. package/lib/agenda/every.ts +86 -0
  39. package/lib/agenda/find-and-lock-next-job.ts +78 -0
  40. package/lib/agenda/has-mongo-protocol.ts +8 -0
  41. package/lib/agenda/index.ts +227 -0
  42. package/lib/agenda/job-processing-queue.ts +99 -0
  43. package/lib/agenda/jobs.ts +31 -0
  44. package/lib/agenda/lock-limit.ts +17 -0
  45. package/lib/agenda/max-concurrency.ts +20 -0
  46. package/lib/agenda/mongo.ts +21 -0
  47. package/lib/agenda/name.ts +16 -0
  48. package/lib/agenda/now.ts +31 -0
  49. package/lib/agenda/process-every.ts +18 -0
  50. package/lib/agenda/purge.ts +19 -0
  51. package/lib/agenda/save-job.ts +185 -0
  52. package/lib/agenda/schedule.ts +79 -0
  53. package/lib/agenda/sort.ts +17 -0
  54. package/lib/agenda/start.ts +30 -0
  55. package/lib/agenda/stop.ts +49 -0
  56. package/lib/cjs.ts +5 -0
  57. package/lib/index.ts +11 -0
  58. package/lib/job/compute-next-run-at.ts +191 -0
  59. package/lib/job/disable.ts +11 -0
  60. package/lib/job/enable.ts +11 -0
  61. package/lib/job/fail.ts +29 -0
  62. package/lib/job/index.ts +221 -0
  63. package/lib/job/is-running.ts +29 -0
  64. package/lib/job/priority.ts +11 -0
  65. package/lib/job/remove.ts +10 -0
  66. package/lib/job/repeat-at.ts +12 -0
  67. package/lib/job/repeat-every.ts +40 -0
  68. package/lib/job/run.ts +125 -0
  69. package/lib/job/save.ts +11 -0
  70. package/lib/job/schedule.ts +15 -0
  71. package/lib/job/set-shouldsaveresult.ts +10 -0
  72. package/lib/job/to-json.ts +36 -0
  73. package/lib/job/touch.ts +11 -0
  74. package/lib/job/unique.ts +18 -0
  75. package/lib/utils/create-job.ts +13 -0
  76. package/lib/utils/index.ts +3 -0
  77. package/lib/utils/parse-priority.ts +37 -0
  78. package/lib/utils/process-jobs.ts +379 -0
  79. package/package.json +13 -11
  80. 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
+ };
@@ -0,0 +1,11 @@
1
+ import { Job } from ".";
2
+
3
+ /**
4
+ * Prevents the job type from running
5
+ * @name Job#disable
6
+ * @function
7
+ */
8
+ export const disable = function (this: Job): Job {
9
+ this.attrs.disabled = true;
10
+ return this;
11
+ };
@@ -0,0 +1,11 @@
1
+ import { Job } from ".";
2
+
3
+ /**
4
+ * Allows job type to run
5
+ * @name Job#enable
6
+ * @function
7
+ */
8
+ export const enable = function (this: Job): Job {
9
+ this.attrs.disabled = false;
10
+ return this;
11
+ };
@@ -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
+ };
@@ -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,10 @@
1
+ import { Job } from ".";
2
+
3
+ /**
4
+ * Remove the job from MongoDB
5
+ * @name Job#remove
6
+ * @function
7
+ */
8
+ export const remove = async function (this: Job): Promise<number | undefined> {
9
+ return this.agenda.cancel({ _id: this.attrs._id });
10
+ };
@@ -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
+ };