@quave/agenda 1.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/LICENSE.md +25 -0
- package/README.md +380 -0
- package/dist/Job.d.ts +190 -0
- package/dist/Job.js +689 -0
- package/dist/Job.js.map +1 -0
- package/dist/JobProcessingQueue.d.ts +47 -0
- package/dist/JobProcessingQueue.js +107 -0
- package/dist/JobProcessingQueue.js.map +1 -0
- package/dist/JobProcessor.d.ts +72 -0
- package/dist/JobProcessor.js +512 -0
- package/dist/JobProcessor.js.map +1 -0
- package/dist/backends/index.d.ts +18 -0
- package/dist/backends/index.js +20 -0
- package/dist/backends/index.js.map +1 -0
- package/dist/cjs/Job.d.ts +190 -0
- package/dist/cjs/Job.js +696 -0
- package/dist/cjs/Job.js.map +1 -0
- package/dist/cjs/JobProcessingQueue.d.ts +47 -0
- package/dist/cjs/JobProcessingQueue.js +111 -0
- package/dist/cjs/JobProcessingQueue.js.map +1 -0
- package/dist/cjs/JobProcessor.d.ts +72 -0
- package/dist/cjs/JobProcessor.js +519 -0
- package/dist/cjs/JobProcessor.js.map +1 -0
- package/dist/cjs/backends/index.d.ts +17 -0
- package/dist/cjs/backends/index.js +20 -0
- package/dist/cjs/backends/index.js.map +1 -0
- package/dist/cjs/decorators/Define.d.ts +33 -0
- package/dist/cjs/decorators/Define.js +47 -0
- package/dist/cjs/decorators/Define.js.map +1 -0
- package/dist/cjs/decorators/Every.d.ts +43 -0
- package/dist/cjs/decorators/Every.js +58 -0
- package/dist/cjs/decorators/Every.js.map +1 -0
- package/dist/cjs/decorators/JobsController.d.ts +43 -0
- package/dist/cjs/decorators/JobsController.js +40 -0
- package/dist/cjs/decorators/JobsController.js.map +1 -0
- package/dist/cjs/decorators/index.d.ts +41 -0
- package/dist/cjs/decorators/index.js +57 -0
- package/dist/cjs/decorators/index.js.map +1 -0
- package/dist/cjs/decorators/metadata.d.ts +50 -0
- package/dist/cjs/decorators/metadata.js +41 -0
- package/dist/cjs/decorators/metadata.js.map +1 -0
- package/dist/cjs/decorators/register.d.ts +52 -0
- package/dist/cjs/decorators/register.js +145 -0
- package/dist/cjs/decorators/register.js.map +1 -0
- package/dist/cjs/index.d.ts +498 -0
- package/dist/cjs/index.js +776 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/notifications/BaseNotificationChannel.d.ts +47 -0
- package/dist/cjs/notifications/BaseNotificationChannel.js +112 -0
- package/dist/cjs/notifications/BaseNotificationChannel.js.map +1 -0
- package/dist/cjs/notifications/InMemoryNotificationChannel.d.ts +13 -0
- package/dist/cjs/notifications/InMemoryNotificationChannel.js +37 -0
- package/dist/cjs/notifications/InMemoryNotificationChannel.js.map +1 -0
- package/dist/cjs/notifications/index.d.ts +2 -0
- package/dist/cjs/notifications/index.js +8 -0
- package/dist/cjs/notifications/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/types/AgendaBackend.d.ts +61 -0
- package/dist/cjs/types/AgendaBackend.js +3 -0
- package/dist/cjs/types/AgendaBackend.js.map +1 -0
- package/dist/cjs/types/AgendaConfig.d.ts +10 -0
- package/dist/cjs/types/AgendaConfig.js +3 -0
- package/dist/cjs/types/AgendaConfig.js.map +1 -0
- package/dist/cjs/types/AgendaStatus.d.ts +32 -0
- package/dist/cjs/types/AgendaStatus.js +3 -0
- package/dist/cjs/types/AgendaStatus.js.map +1 -0
- package/dist/cjs/types/DbOptions.d.ts +4 -0
- package/dist/cjs/types/DbOptions.js +3 -0
- package/dist/cjs/types/DbOptions.js.map +1 -0
- package/dist/cjs/types/DrainOptions.d.ts +37 -0
- package/dist/cjs/types/DrainOptions.js +3 -0
- package/dist/cjs/types/DrainOptions.js.map +1 -0
- package/dist/cjs/types/JobDefinition.d.ts +49 -0
- package/dist/cjs/types/JobDefinition.js +3 -0
- package/dist/cjs/types/JobDefinition.js.map +1 -0
- package/dist/cjs/types/JobLogger.d.ts +111 -0
- package/dist/cjs/types/JobLogger.js +3 -0
- package/dist/cjs/types/JobLogger.js.map +1 -0
- package/dist/cjs/types/JobParameters.d.ts +106 -0
- package/dist/cjs/types/JobParameters.js +21 -0
- package/dist/cjs/types/JobParameters.js.map +1 -0
- package/dist/cjs/types/JobQuery.d.ts +79 -0
- package/dist/cjs/types/JobQuery.js +36 -0
- package/dist/cjs/types/JobQuery.js.map +1 -0
- package/dist/cjs/types/JobRepository.d.ts +101 -0
- package/dist/cjs/types/JobRepository.js +3 -0
- package/dist/cjs/types/JobRepository.js.map +1 -0
- package/dist/cjs/types/NotificationChannel.d.ts +120 -0
- package/dist/cjs/types/NotificationChannel.js +3 -0
- package/dist/cjs/types/NotificationChannel.js.map +1 -0
- package/dist/cjs/utils/backoff.d.ts +152 -0
- package/dist/cjs/utils/backoff.js +175 -0
- package/dist/cjs/utils/backoff.js.map +1 -0
- package/dist/cjs/utils/dateConstraints.d.ts +48 -0
- package/dist/cjs/utils/dateConstraints.js +127 -0
- package/dist/cjs/utils/dateConstraints.js.map +1 -0
- package/dist/cjs/utils/isValidDate.d.ts +1 -0
- package/dist/cjs/utils/isValidDate.js +8 -0
- package/dist/cjs/utils/isValidDate.js.map +1 -0
- package/dist/cjs/utils/nextRunAt.d.ts +10 -0
- package/dist/cjs/utils/nextRunAt.js +119 -0
- package/dist/cjs/utils/nextRunAt.js.map +1 -0
- package/dist/cjs/utils/priority.d.ts +13 -0
- package/dist/cjs/utils/priority.js +23 -0
- package/dist/cjs/utils/priority.js.map +1 -0
- package/dist/cjs/utils/processEvery.d.ts +1 -0
- package/dist/cjs/utils/processEvery.js +13 -0
- package/dist/cjs/utils/processEvery.js.map +1 -0
- package/dist/cjs/utils/stack.d.ts +1 -0
- package/dist/cjs/utils/stack.js +21 -0
- package/dist/cjs/utils/stack.js.map +1 -0
- package/dist/cjs/version.d.ts +9 -0
- package/dist/cjs/version.js +13 -0
- package/dist/cjs/version.js.map +1 -0
- package/dist/decorators/Define.d.ts +33 -0
- package/dist/decorators/Define.js +44 -0
- package/dist/decorators/Define.js.map +1 -0
- package/dist/decorators/Every.d.ts +43 -0
- package/dist/decorators/Every.js +55 -0
- package/dist/decorators/Every.js.map +1 -0
- package/dist/decorators/JobsController.d.ts +43 -0
- package/dist/decorators/JobsController.js +37 -0
- package/dist/decorators/JobsController.js.map +1 -0
- package/dist/decorators/index.d.ts +41 -0
- package/dist/decorators/index.js +46 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/metadata.d.ts +50 -0
- package/dist/decorators/metadata.js +35 -0
- package/dist/decorators/metadata.js.map +1 -0
- package/dist/decorators/register.d.ts +52 -0
- package/dist/decorators/register.js +139 -0
- package/dist/decorators/register.js.map +1 -0
- package/dist/index.d.ts +498 -0
- package/dist/index.js +744 -0
- package/dist/index.js.map +1 -0
- package/dist/notifications/BaseNotificationChannel.d.ts +47 -0
- package/dist/notifications/BaseNotificationChannel.js +108 -0
- package/dist/notifications/BaseNotificationChannel.js.map +1 -0
- package/dist/notifications/InMemoryNotificationChannel.d.ts +13 -0
- package/dist/notifications/InMemoryNotificationChannel.js +33 -0
- package/dist/notifications/InMemoryNotificationChannel.js.map +1 -0
- package/dist/notifications/index.d.ts +2 -0
- package/dist/notifications/index.js +3 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/types/AgendaBackend.d.ts +61 -0
- package/dist/types/AgendaBackend.js +2 -0
- package/dist/types/AgendaBackend.js.map +1 -0
- package/dist/types/AgendaConfig.d.ts +10 -0
- package/dist/types/AgendaConfig.js +2 -0
- package/dist/types/AgendaConfig.js.map +1 -0
- package/dist/types/AgendaStatus.d.ts +32 -0
- package/dist/types/AgendaStatus.js +2 -0
- package/dist/types/AgendaStatus.js.map +1 -0
- package/dist/types/DbOptions.d.ts +4 -0
- package/dist/types/DbOptions.js +2 -0
- package/dist/types/DbOptions.js.map +1 -0
- package/dist/types/DrainOptions.d.ts +37 -0
- package/dist/types/DrainOptions.js +2 -0
- package/dist/types/DrainOptions.js.map +1 -0
- package/dist/types/JobDefinition.d.ts +49 -0
- package/dist/types/JobDefinition.js +2 -0
- package/dist/types/JobDefinition.js.map +1 -0
- package/dist/types/JobLogger.d.ts +111 -0
- package/dist/types/JobLogger.js +2 -0
- package/dist/types/JobLogger.js.map +1 -0
- package/dist/types/JobParameters.d.ts +106 -0
- package/dist/types/JobParameters.js +17 -0
- package/dist/types/JobParameters.js.map +1 -0
- package/dist/types/JobQuery.d.ts +79 -0
- package/dist/types/JobQuery.js +33 -0
- package/dist/types/JobQuery.js.map +1 -0
- package/dist/types/JobRepository.d.ts +101 -0
- package/dist/types/JobRepository.js +2 -0
- package/dist/types/JobRepository.js.map +1 -0
- package/dist/types/NotificationChannel.d.ts +120 -0
- package/dist/types/NotificationChannel.js +2 -0
- package/dist/types/NotificationChannel.js.map +1 -0
- package/dist/utils/backoff.d.ts +152 -0
- package/dist/utils/backoff.js +167 -0
- package/dist/utils/backoff.js.map +1 -0
- package/dist/utils/dateConstraints.d.ts +48 -0
- package/dist/utils/dateConstraints.js +117 -0
- package/dist/utils/dateConstraints.js.map +1 -0
- package/dist/utils/isValidDate.d.ts +1 -0
- package/dist/utils/isValidDate.js +5 -0
- package/dist/utils/isValidDate.js.map +1 -0
- package/dist/utils/nextRunAt.d.ts +10 -0
- package/dist/utils/nextRunAt.js +110 -0
- package/dist/utils/nextRunAt.js.map +1 -0
- package/dist/utils/priority.d.ts +13 -0
- package/dist/utils/priority.js +20 -0
- package/dist/utils/priority.js.map +1 -0
- package/dist/utils/processEvery.d.ts +1 -0
- package/dist/utils/processEvery.js +7 -0
- package/dist/utils/processEvery.js.map +1 -0
- package/dist/utils/stack.d.ts +1 -0
- package/dist/utils/stack.js +18 -0
- package/dist/utils/stack.js.map +1 -0
- package/dist/version.d.ts +9 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/package.json +79 -0
- package/test/fixtures/forkHelper.js +40 -0
- package/test/fixtures/someJobDefinition.ts +12 -0
- package/test/shared/agenda-test-suite.ts +1876 -0
- package/test/shared/backoff-test-suite.ts +408 -0
- package/test/shared/debounce-test-suite.ts +377 -0
- package/test/shared/full-test-suite.ts +161 -0
- package/test/shared/index.ts +50 -0
- package/test/shared/joblogger-test-suite.ts +305 -0
- package/test/shared/jobprocessor-test-suite.ts +266 -0
- package/test/shared/notification-channel-test-suite.ts +471 -0
- package/test/shared/removeoncomplete-test-suite.ts +228 -0
- package/test/shared/repository-test-suite.ts +708 -0
- package/test/shared/retry-test-suite.ts +174 -0
- package/test/shared/test-utils.ts +100 -0
package/dist/Job.js
ADDED
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
import date from 'date.js';
|
|
2
|
+
import debug from 'debug';
|
|
3
|
+
import { fork } from 'child_process';
|
|
4
|
+
import { datefields } from './types/JobParameters.js';
|
|
5
|
+
import { parsePriority } from './utils/priority.js';
|
|
6
|
+
import { computeFromInterval, computeFromRepeatAt } from './utils/nextRunAt.js';
|
|
7
|
+
import { applyAllDateConstraints } from './utils/dateConstraints.js';
|
|
8
|
+
const log = debug('agenda:job');
|
|
9
|
+
/**
|
|
10
|
+
* @class
|
|
11
|
+
*/
|
|
12
|
+
export class Job {
|
|
13
|
+
getCanceledMessage() {
|
|
14
|
+
return typeof this.canceled === 'object'
|
|
15
|
+
? this.canceled?.message || this.canceled
|
|
16
|
+
: this.canceled;
|
|
17
|
+
}
|
|
18
|
+
cancel(error) {
|
|
19
|
+
this.canceled = error || true;
|
|
20
|
+
if (this.forkedChild) {
|
|
21
|
+
try {
|
|
22
|
+
this.forkedChild.send('cancel');
|
|
23
|
+
log('canceled child', this.attrs.name, this.attrs._id);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
log('cannot send cancel to child');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
constructor(agenda, args, byJobProcessor = false) {
|
|
31
|
+
this.agenda = agenda;
|
|
32
|
+
this.byJobProcessor = byJobProcessor;
|
|
33
|
+
this.attrs = {
|
|
34
|
+
...args,
|
|
35
|
+
// Set defaults if undefined
|
|
36
|
+
nextRunAt: args.nextRunAt === undefined ? new Date() : args.nextRunAt,
|
|
37
|
+
priority: parsePriority(args.priority),
|
|
38
|
+
type: args.type
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Given a job, turn it into an JobParameters object
|
|
43
|
+
* @param excludeProcessorFields - If true, excludes fields managed by the job processor
|
|
44
|
+
*/
|
|
45
|
+
toJson(excludeProcessorFields = false) {
|
|
46
|
+
const result = {};
|
|
47
|
+
const attrs = this.attrs;
|
|
48
|
+
for (const key of Object.keys(attrs)) {
|
|
49
|
+
if (Object.hasOwnProperty.call(attrs, key)) {
|
|
50
|
+
// Skip processor-managed fields if requested
|
|
51
|
+
if (excludeProcessorFields && Job.PROCESSOR_MANAGED_FIELDS.has(key)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
result[key] =
|
|
55
|
+
datefields.includes(key) && attrs[key]
|
|
56
|
+
? new Date(attrs[key])
|
|
57
|
+
: attrs[key];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Sets a job to repeat every X amount of time
|
|
64
|
+
* @param interval
|
|
65
|
+
* @param options
|
|
66
|
+
*/
|
|
67
|
+
repeatEvery(interval, options = {}) {
|
|
68
|
+
this.attrs.repeatInterval = interval;
|
|
69
|
+
this.attrs.repeatTimezone = options.timezone;
|
|
70
|
+
if (options.skipImmediate) {
|
|
71
|
+
// Set the lastRunAt time to the nextRunAt so that the new nextRunAt will be computed in reference to the current value.
|
|
72
|
+
this.attrs.lastRunAt = this.attrs.nextRunAt || new Date();
|
|
73
|
+
this.computeNextRunAt();
|
|
74
|
+
this.attrs.lastRunAt = undefined;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
this.computeNextRunAt();
|
|
78
|
+
}
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Sets a job to repeat at a specific time
|
|
83
|
+
* @param time
|
|
84
|
+
*/
|
|
85
|
+
repeatAt(time) {
|
|
86
|
+
this.attrs.repeatAt = time;
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Sets the start date for the job.
|
|
91
|
+
* The job will not run before this date.
|
|
92
|
+
* @param date - The start date (Date object or string parseable by Date constructor)
|
|
93
|
+
*/
|
|
94
|
+
startDate(dateInput) {
|
|
95
|
+
const d = new Date(dateInput);
|
|
96
|
+
if (Number.isNaN(d.getTime())) {
|
|
97
|
+
throw new Error(`Invalid start date: ${dateInput}`);
|
|
98
|
+
}
|
|
99
|
+
this.attrs.startDate = d;
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Sets the end date for the job.
|
|
104
|
+
* The job will not run after this date (nextRunAt will be set to null).
|
|
105
|
+
* @param date - The end date (Date object or string parseable by Date constructor)
|
|
106
|
+
*/
|
|
107
|
+
endDate(dateInput) {
|
|
108
|
+
const d = new Date(dateInput);
|
|
109
|
+
if (Number.isNaN(d.getTime())) {
|
|
110
|
+
throw new Error(`Invalid end date: ${dateInput}`);
|
|
111
|
+
}
|
|
112
|
+
this.attrs.endDate = d;
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Sets the days of the week to skip.
|
|
117
|
+
* The job will not run on these days.
|
|
118
|
+
* @param days - Array of days to skip (0 = Sunday, 1 = Monday, ..., 6 = Saturday)
|
|
119
|
+
*/
|
|
120
|
+
skipDays(days) {
|
|
121
|
+
// Validate days
|
|
122
|
+
for (const day of days) {
|
|
123
|
+
if (day < 0 || day > 6 || !Number.isInteger(day)) {
|
|
124
|
+
throw new Error(`Invalid skip day: ${day}. Must be an integer 0-6 (0=Sunday, 6=Saturday)`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
this.attrs.skipDays = [...new Set(days)]; // Remove duplicates
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* if set, a job is forked via node child process and runs in a seperate/own
|
|
132
|
+
* thread
|
|
133
|
+
* @param enableForkMode
|
|
134
|
+
*/
|
|
135
|
+
forkMode(enableForkMode) {
|
|
136
|
+
this.attrs.fork = enableForkMode;
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Prevents the job from running
|
|
141
|
+
*/
|
|
142
|
+
disable() {
|
|
143
|
+
this.attrs.disabled = true;
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Allows job to run
|
|
148
|
+
*/
|
|
149
|
+
enable() {
|
|
150
|
+
this.attrs.disabled = false;
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Data to ensure is unique for job to be created
|
|
155
|
+
* @param unique
|
|
156
|
+
* @param opts
|
|
157
|
+
*/
|
|
158
|
+
unique(unique, opts) {
|
|
159
|
+
this.attrs.unique = unique;
|
|
160
|
+
this.attrs.uniqueOpts = opts;
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Configure debounce behavior for this job.
|
|
165
|
+
* Debouncing delays job execution and resets the timer on subsequent saves,
|
|
166
|
+
* ensuring the job only runs once after a quiet period.
|
|
167
|
+
*
|
|
168
|
+
* IMPORTANT: Requires a unique constraint to be set via `.unique()`.
|
|
169
|
+
* The unique constraint identifies which jobs should be debounced together.
|
|
170
|
+
*
|
|
171
|
+
* @param delay - Debounce window in milliseconds
|
|
172
|
+
* @param options - Optional configuration (maxWait, strategy)
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* // Basic trailing debounce - execute 2s after last save
|
|
177
|
+
* await agenda.create('updateIndex', { entityId: 123 })
|
|
178
|
+
* .unique({ 'data.entityId': 123 })
|
|
179
|
+
* .debounce(2000)
|
|
180
|
+
* .save();
|
|
181
|
+
*
|
|
182
|
+
* // With maxWait - guarantee execution within 30s
|
|
183
|
+
* await agenda.create('syncUser', { userId: 456 })
|
|
184
|
+
* .unique({ 'data.userId': 456 })
|
|
185
|
+
* .debounce(5000, { maxWait: 30000 })
|
|
186
|
+
* .save();
|
|
187
|
+
*
|
|
188
|
+
* // Leading strategy - execute immediately, ignore subsequent calls
|
|
189
|
+
* await agenda.create('notify', { channel: '#alerts' })
|
|
190
|
+
* .unique({ 'data.channel': '#alerts' })
|
|
191
|
+
* .debounce(60000, { strategy: 'leading' })
|
|
192
|
+
* .save();
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
debounce(delay, options) {
|
|
196
|
+
const debounceOpts = {
|
|
197
|
+
delay,
|
|
198
|
+
maxWait: options?.maxWait,
|
|
199
|
+
strategy: options?.strategy ?? 'trailing'
|
|
200
|
+
};
|
|
201
|
+
this.attrs.uniqueOpts = {
|
|
202
|
+
...this.attrs.uniqueOpts,
|
|
203
|
+
debounce: debounceOpts
|
|
204
|
+
};
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Schedules a job to run at specified time.
|
|
209
|
+
* Date constraints (startDate, endDate, skipDays) are applied if set.
|
|
210
|
+
* @param time
|
|
211
|
+
*/
|
|
212
|
+
schedule(time) {
|
|
213
|
+
const d = new Date(time);
|
|
214
|
+
let nextRunAt = Number.isNaN(d.getTime()) ? date(time) : d;
|
|
215
|
+
// Apply date constraints if any are set
|
|
216
|
+
if (this.attrs.startDate || this.attrs.endDate || this.attrs.skipDays) {
|
|
217
|
+
nextRunAt = applyAllDateConstraints(nextRunAt, {
|
|
218
|
+
startDate: this.attrs.startDate,
|
|
219
|
+
endDate: this.attrs.endDate,
|
|
220
|
+
skipDays: this.attrs.skipDays,
|
|
221
|
+
timezone: this.attrs.repeatTimezone
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
this.attrs.nextRunAt = nextRunAt;
|
|
225
|
+
return this;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Sets priority of the job
|
|
229
|
+
* @param priority priority of when job should be queued
|
|
230
|
+
*/
|
|
231
|
+
priority(priority) {
|
|
232
|
+
this.attrs.priority = parsePriority(priority);
|
|
233
|
+
return this;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Fails the job with a reason (error) specified
|
|
237
|
+
*
|
|
238
|
+
* @param reason
|
|
239
|
+
*/
|
|
240
|
+
fail(reason) {
|
|
241
|
+
// Fork-mode children reject with plain serialized payloads (objects or
|
|
242
|
+
// strings), so normalize anything that is not an Error/string to keep
|
|
243
|
+
// failReason a string as its type promises.
|
|
244
|
+
let failReason;
|
|
245
|
+
if (reason instanceof Error) {
|
|
246
|
+
failReason = reason.message;
|
|
247
|
+
}
|
|
248
|
+
else if (typeof reason === 'string') {
|
|
249
|
+
failReason = reason;
|
|
250
|
+
}
|
|
251
|
+
else if (typeof reason === 'object' &&
|
|
252
|
+
reason !== null &&
|
|
253
|
+
typeof reason.message === 'string') {
|
|
254
|
+
failReason = reason.message;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
try {
|
|
258
|
+
failReason = JSON.stringify(reason) ?? String(reason);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
failReason = String(reason);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
this.attrs.failReason = failReason;
|
|
265
|
+
this.attrs.failCount = (this.attrs.failCount || 0) + 1;
|
|
266
|
+
const now = new Date();
|
|
267
|
+
this.attrs.failedAt = now;
|
|
268
|
+
this.attrs.lastFinishedAt = now;
|
|
269
|
+
log('[%s:%s] fail() called [%d] times so far', this.attrs.name, this.attrs._id, this.attrs.failCount);
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
async fetchStatus() {
|
|
273
|
+
const result = await this.agenda.db.queryJobs({ id: this.attrs._id?.toString() });
|
|
274
|
+
if (!result.jobs || result.jobs.length === 0) {
|
|
275
|
+
// @todo: should we just return false instead? a finished job could have been removed from database,
|
|
276
|
+
// and then this would throw...
|
|
277
|
+
throw new Error(`job with id ${this.attrs._id} not found in database`);
|
|
278
|
+
}
|
|
279
|
+
this.attrs.lastRunAt = result.jobs[0].lastRunAt;
|
|
280
|
+
this.attrs.lockedAt = result.jobs[0].lockedAt;
|
|
281
|
+
this.attrs.lastFinishedAt = result.jobs[0].lastFinishedAt;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* A job is running if:
|
|
285
|
+
* (lastRunAt exists AND lastFinishedAt does not exist)
|
|
286
|
+
* OR
|
|
287
|
+
* (lastRunAt exists AND lastFinishedAt exists but the lastRunAt is newer [in time] than lastFinishedAt)
|
|
288
|
+
* @returns Whether or not job is running at the moment (true for running)
|
|
289
|
+
*/
|
|
290
|
+
async isRunning() {
|
|
291
|
+
if (!this.byJobProcessor || this.attrs.fork) {
|
|
292
|
+
// we have no job definition, therfore we are not the job processor, but a client call
|
|
293
|
+
// so we get the real state from database
|
|
294
|
+
await this.fetchStatus();
|
|
295
|
+
}
|
|
296
|
+
if (!this.attrs.lastRunAt) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
if (!this.attrs.lastFinishedAt) {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
if (this.attrs.lockedAt &&
|
|
303
|
+
this.attrs.lastRunAt.getTime() > this.attrs.lastFinishedAt.getTime()) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Saves a job to database
|
|
310
|
+
*/
|
|
311
|
+
async save() {
|
|
312
|
+
if (this.agenda.forkedWorker) {
|
|
313
|
+
const warning = new Error('calling save() on a Job during a forkedWorker has no effect!');
|
|
314
|
+
log('WARNING: %s %s', warning.message, warning.stack);
|
|
315
|
+
return this;
|
|
316
|
+
}
|
|
317
|
+
// ensure db connection is ready
|
|
318
|
+
await this.agenda.ready;
|
|
319
|
+
// save() is only called by user code (the job processor uses saveJobState() directly),
|
|
320
|
+
// so we always exclude processor-managed fields to prevent race conditions
|
|
321
|
+
const result = await this.agenda.db.saveJob(this.toJson(true), {
|
|
322
|
+
lastModifiedBy: this.agenda.attrs.name || undefined
|
|
323
|
+
});
|
|
324
|
+
// Update attrs from result
|
|
325
|
+
this.attrs._id = result._id;
|
|
326
|
+
this.attrs.nextRunAt = result.nextRunAt;
|
|
327
|
+
this.attrs.debounceStartedAt = result.debounceStartedAt;
|
|
328
|
+
// Publish notification for real-time processing if channel is configured
|
|
329
|
+
if (this.agenda.hasNotificationChannel()) {
|
|
330
|
+
await this.agenda.publishJobNotification(this);
|
|
331
|
+
}
|
|
332
|
+
return this;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Remove the job from database
|
|
336
|
+
*/
|
|
337
|
+
remove() {
|
|
338
|
+
return this.agenda.cancel({ id: this.attrs._id });
|
|
339
|
+
}
|
|
340
|
+
async isDead() {
|
|
341
|
+
return this.isExpired();
|
|
342
|
+
}
|
|
343
|
+
async isExpired() {
|
|
344
|
+
if (!this.byJobProcessor || this.attrs.fork) {
|
|
345
|
+
// we have no job definition, therfore we are not the job processor, but a client call
|
|
346
|
+
// so we get the real state from database
|
|
347
|
+
await this.fetchStatus();
|
|
348
|
+
}
|
|
349
|
+
const definition = this.agenda.definitions[this.attrs.name];
|
|
350
|
+
if (!definition) {
|
|
351
|
+
throw new Error(`Undefined job ${this.attrs.name}`);
|
|
352
|
+
}
|
|
353
|
+
const lockDeadline = new Date(Date.now() - definition.lockLifetime);
|
|
354
|
+
// This means a job has "expired", as in it has not been "touched" within the lockoutTime
|
|
355
|
+
// Remove from local lock
|
|
356
|
+
if (this.attrs.lockedAt && this.attrs.lockedAt < lockDeadline) {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Updates "lockedAt" time so the job does not get picked up again
|
|
363
|
+
* @param progress 0 to 100
|
|
364
|
+
*/
|
|
365
|
+
async touch(progress) {
|
|
366
|
+
if (this.canceled) {
|
|
367
|
+
throw new Error(`job ${this.attrs.name} got canceled already: ${this.canceled}!`);
|
|
368
|
+
}
|
|
369
|
+
this.attrs.lockedAt = new Date();
|
|
370
|
+
this.attrs.progress = progress;
|
|
371
|
+
await this.agenda.db.saveJobState(this.attrs, {
|
|
372
|
+
lastModifiedBy: this.agenda.attrs.name || undefined
|
|
373
|
+
});
|
|
374
|
+
// Publish progress state notification if progress was provided (fire-and-forget)
|
|
375
|
+
if (progress !== undefined) {
|
|
376
|
+
this.agenda.publishJobStateNotification(this, 'progress', {
|
|
377
|
+
progress
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
computeNextRunAt() {
|
|
382
|
+
try {
|
|
383
|
+
if (this.attrs.repeatInterval) {
|
|
384
|
+
const nextRunAt = computeFromInterval(this.attrs);
|
|
385
|
+
this.attrs.nextRunAt = nextRunAt;
|
|
386
|
+
if (nextRunAt) {
|
|
387
|
+
log('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, nextRunAt.toISOString());
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
log('[%s:%s] nextRunAt set to null (date constraints)', this.attrs.name, this.attrs._id);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else if (this.attrs.repeatAt) {
|
|
394
|
+
const nextRunAt = computeFromRepeatAt(this.attrs);
|
|
395
|
+
this.attrs.nextRunAt = nextRunAt;
|
|
396
|
+
if (nextRunAt) {
|
|
397
|
+
log('[%s:%s] nextRunAt set to [%s]', this.attrs.name, this.attrs._id, nextRunAt.toISOString());
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
log('[%s:%s] nextRunAt set to null (date constraints)', this.attrs.name, this.attrs._id);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
this.attrs.nextRunAt = null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
this.attrs.nextRunAt = null;
|
|
409
|
+
this.fail(error);
|
|
410
|
+
}
|
|
411
|
+
return this;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Handle automatic retry with backoff strategy
|
|
415
|
+
* @param error - The error that caused the failure
|
|
416
|
+
*/
|
|
417
|
+
async handleRetry(error) {
|
|
418
|
+
const definition = this.agenda.definitions[this.attrs.name];
|
|
419
|
+
// No backoff strategy configured - skip auto-retry
|
|
420
|
+
if (!definition?.backoff) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const context = {
|
|
424
|
+
attempt: this.attrs.failCount || 1,
|
|
425
|
+
error,
|
|
426
|
+
jobName: this.attrs.name,
|
|
427
|
+
jobData: this.attrs.data
|
|
428
|
+
};
|
|
429
|
+
const retryDelay = definition.backoff(context);
|
|
430
|
+
if (retryDelay === null) {
|
|
431
|
+
// No more retries - emit exhausted event
|
|
432
|
+
log('[%s:%s] retry attempts exhausted after %d failures', this.attrs.name, this.attrs._id, this.attrs.failCount);
|
|
433
|
+
this.agenda.logJobEvent({
|
|
434
|
+
level: 'warn',
|
|
435
|
+
event: 'retry:exhausted',
|
|
436
|
+
jobId: this.attrs._id?.toString(),
|
|
437
|
+
jobName: this.attrs.name,
|
|
438
|
+
message: `retry exhausted after ${this.attrs.failCount} failures: ${error.message}`,
|
|
439
|
+
error: error.message,
|
|
440
|
+
failCount: this.attrs.failCount
|
|
441
|
+
});
|
|
442
|
+
this.agenda.emit('retry exhausted', error, this);
|
|
443
|
+
this.agenda.emit(`retry exhausted:${this.attrs.name}`, error, this);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
// Schedule retry
|
|
447
|
+
const nextRunAt = new Date(Date.now() + retryDelay);
|
|
448
|
+
this.attrs.nextRunAt = nextRunAt;
|
|
449
|
+
log('[%s:%s] scheduling retry #%d in %dms (at %s)', this.attrs.name, this.attrs._id, this.attrs.failCount, retryDelay, nextRunAt.toISOString());
|
|
450
|
+
this.agenda.logJobEvent({
|
|
451
|
+
level: 'info',
|
|
452
|
+
event: 'retry',
|
|
453
|
+
jobId: this.attrs._id?.toString(),
|
|
454
|
+
jobName: this.attrs.name,
|
|
455
|
+
message: `retry #${this.attrs.failCount} scheduled in ${retryDelay}ms`,
|
|
456
|
+
retryDelay,
|
|
457
|
+
retryAttempt: this.attrs.failCount ?? undefined,
|
|
458
|
+
error: error.message
|
|
459
|
+
});
|
|
460
|
+
this.agenda.emit('retry', this, {
|
|
461
|
+
attempt: this.attrs.failCount,
|
|
462
|
+
delay: retryDelay,
|
|
463
|
+
nextRunAt,
|
|
464
|
+
error
|
|
465
|
+
});
|
|
466
|
+
this.agenda.emit(`retry:${this.attrs.name}`, this, {
|
|
467
|
+
attempt: this.attrs.failCount,
|
|
468
|
+
delay: retryDelay,
|
|
469
|
+
nextRunAt,
|
|
470
|
+
error
|
|
471
|
+
});
|
|
472
|
+
// Publish retry state notification for cross-process event propagation (fire-and-forget)
|
|
473
|
+
this.agenda.publishJobStateNotification(this, 'retry', {
|
|
474
|
+
retryAt: nextRunAt,
|
|
475
|
+
retryAttempt: this.attrs.failCount,
|
|
476
|
+
error: error.message
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
async run() {
|
|
480
|
+
this.attrs.lastRunAt = new Date();
|
|
481
|
+
log('[%s:%s] setting lastRunAt to: %s', this.attrs.name, this.attrs._id, this.attrs.lastRunAt.toISOString());
|
|
482
|
+
this.computeNextRunAt();
|
|
483
|
+
await this.agenda.db.saveJobState(this.attrs, {
|
|
484
|
+
lastModifiedBy: this.agenda.attrs.name || undefined
|
|
485
|
+
});
|
|
486
|
+
let succeeded = false;
|
|
487
|
+
try {
|
|
488
|
+
this.agenda.emit('start', this);
|
|
489
|
+
this.agenda.emit(`start:${this.attrs.name}`, this);
|
|
490
|
+
log('[%s:%s] starting job', this.attrs.name, this.attrs._id);
|
|
491
|
+
this.agenda.logJobEvent({
|
|
492
|
+
level: 'info',
|
|
493
|
+
event: 'start',
|
|
494
|
+
jobId: this.attrs._id?.toString(),
|
|
495
|
+
jobName: this.attrs.name,
|
|
496
|
+
message: `job started`
|
|
497
|
+
});
|
|
498
|
+
// Publish start state notification for cross-process event propagation (fire-and-forget)
|
|
499
|
+
this.agenda.publishJobStateNotification(this, 'start', {
|
|
500
|
+
lastRunAt: this.attrs.lastRunAt
|
|
501
|
+
});
|
|
502
|
+
if (this.attrs.fork) {
|
|
503
|
+
if (!this.agenda.forkHelper) {
|
|
504
|
+
throw new Error('no forkHelper specified, you need to set a path to a helper script');
|
|
505
|
+
}
|
|
506
|
+
const { forkHelper } = this.agenda;
|
|
507
|
+
await new Promise((resolve, reject) => {
|
|
508
|
+
this.forkedChild = fork(forkHelper.path, [
|
|
509
|
+
this.attrs.name,
|
|
510
|
+
this.attrs._id.toString(),
|
|
511
|
+
this.agenda.definitions[this.attrs.name].filePath || ''
|
|
512
|
+
], forkHelper.options);
|
|
513
|
+
let childError;
|
|
514
|
+
this.forkedChild.on('close', code => {
|
|
515
|
+
if (code) {
|
|
516
|
+
log('fork parameters: %O name=%s id=%s filePath=%s', forkHelper, this.attrs.name, this.attrs._id, this.agenda.definitions[this.attrs.name].filePath);
|
|
517
|
+
const error = new Error(`child process exited with code: ${code}`);
|
|
518
|
+
log('fork child error: %s %O', error.message, childError || this.canceled);
|
|
519
|
+
reject(childError || this.canceled || error);
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
resolve();
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
this.forkedChild.on('message', message => {
|
|
526
|
+
// console.log(`Message from child.js: ${message}`, JSON.stringify(message));
|
|
527
|
+
if (typeof message === 'string') {
|
|
528
|
+
try {
|
|
529
|
+
childError = JSON.parse(message);
|
|
530
|
+
}
|
|
531
|
+
catch {
|
|
532
|
+
childError = message;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
childError = message;
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
await this.runJob();
|
|
543
|
+
}
|
|
544
|
+
this.attrs.lastFinishedAt = new Date();
|
|
545
|
+
this.agenda.emit('success', this);
|
|
546
|
+
this.agenda.emit(`success:${this.attrs.name}`, this);
|
|
547
|
+
log('[%s:%s] has succeeded', this.attrs.name, this.attrs._id);
|
|
548
|
+
const successDuration = this.attrs.lastFinishedAt.getTime() - (this.attrs.lastRunAt?.getTime() || 0);
|
|
549
|
+
this.agenda.logJobEvent({
|
|
550
|
+
level: 'info',
|
|
551
|
+
event: 'success',
|
|
552
|
+
jobId: this.attrs._id?.toString(),
|
|
553
|
+
jobName: this.attrs.name,
|
|
554
|
+
message: `job succeeded`,
|
|
555
|
+
duration: successDuration
|
|
556
|
+
});
|
|
557
|
+
succeeded = true;
|
|
558
|
+
// Publish success state notification for cross-process event propagation (fire-and-forget)
|
|
559
|
+
this.agenda.publishJobStateNotification(this, 'success', {
|
|
560
|
+
lastRunAt: this.attrs.lastRunAt,
|
|
561
|
+
lastFinishedAt: this.attrs.lastFinishedAt,
|
|
562
|
+
duration: this.attrs.lastFinishedAt.getTime() - (this.attrs.lastRunAt?.getTime() || 0)
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
log('[%s:%s] unknown error occurred', this.attrs.name, this.attrs._id);
|
|
567
|
+
this.fail(error);
|
|
568
|
+
this.agenda.emit('fail', error, this);
|
|
569
|
+
this.agenda.emit(`fail:${this.attrs.name}`, error, this);
|
|
570
|
+
log('[%s:%s] has failed [%s]', this.attrs.name, this.attrs._id, error.message);
|
|
571
|
+
this.agenda.logJobEvent({
|
|
572
|
+
level: 'error',
|
|
573
|
+
event: 'fail',
|
|
574
|
+
jobId: this.attrs._id?.toString(),
|
|
575
|
+
jobName: this.attrs.name,
|
|
576
|
+
message: `job failed: ${error.message}`,
|
|
577
|
+
error: error.message,
|
|
578
|
+
failCount: this.attrs.failCount
|
|
579
|
+
});
|
|
580
|
+
// Publish fail state notification for cross-process event propagation (fire-and-forget)
|
|
581
|
+
this.agenda.publishJobStateNotification(this, 'fail', {
|
|
582
|
+
error: error.message,
|
|
583
|
+
failCount: this.attrs.failCount
|
|
584
|
+
});
|
|
585
|
+
// Handle automatic retry with backoff strategy
|
|
586
|
+
await this.handleRetry(error);
|
|
587
|
+
}
|
|
588
|
+
finally {
|
|
589
|
+
this.forkedChild = undefined;
|
|
590
|
+
this.attrs.lockedAt = undefined;
|
|
591
|
+
try {
|
|
592
|
+
await this.agenda.db.saveJobState(this.attrs, {
|
|
593
|
+
lastModifiedBy: this.agenda.attrs.name || undefined
|
|
594
|
+
});
|
|
595
|
+
log('[%s:%s] was saved successfully to database', this.attrs.name, this.attrs._id);
|
|
596
|
+
}
|
|
597
|
+
catch (err) {
|
|
598
|
+
// in case this fails, we ignore it
|
|
599
|
+
// this can e.g. happen if the job gets removed during the execution
|
|
600
|
+
log('[%s:%s] was not saved to database', this.attrs.name, this.attrs._id, err);
|
|
601
|
+
}
|
|
602
|
+
this.agenda.emit('complete', this);
|
|
603
|
+
this.agenda.emit(`complete:${this.attrs.name}`, this);
|
|
604
|
+
log('[%s:%s] job finished at [%s] and was unlocked', this.attrs.name, this.attrs._id, this.attrs.lastFinishedAt);
|
|
605
|
+
const completeDuration = this.attrs.lastFinishedAt && this.attrs.lastRunAt
|
|
606
|
+
? this.attrs.lastFinishedAt.getTime() - this.attrs.lastRunAt.getTime()
|
|
607
|
+
: undefined;
|
|
608
|
+
this.agenda.logJobEvent({
|
|
609
|
+
level: 'info',
|
|
610
|
+
event: 'complete',
|
|
611
|
+
jobId: this.attrs._id?.toString(),
|
|
612
|
+
jobName: this.attrs.name,
|
|
613
|
+
message: `job complete`,
|
|
614
|
+
duration: completeDuration
|
|
615
|
+
});
|
|
616
|
+
// Publish complete state notification for cross-process event propagation (fire-and-forget)
|
|
617
|
+
const duration = this.attrs.lastFinishedAt && this.attrs.lastRunAt
|
|
618
|
+
? this.attrs.lastFinishedAt.getTime() - this.attrs.lastRunAt.getTime()
|
|
619
|
+
: undefined;
|
|
620
|
+
this.agenda.publishJobStateNotification(this, 'complete', {
|
|
621
|
+
lastRunAt: this.attrs.lastRunAt,
|
|
622
|
+
lastFinishedAt: this.attrs.lastFinishedAt,
|
|
623
|
+
duration
|
|
624
|
+
});
|
|
625
|
+
// Auto-remove completed one-time jobs
|
|
626
|
+
if (succeeded && !this.attrs.nextRunAt) {
|
|
627
|
+
const definition = this.agenda.definitions[this.attrs.name];
|
|
628
|
+
const shouldRemove = definition?.removeOnComplete ?? this.agenda.attrs.removeOnComplete;
|
|
629
|
+
if (shouldRemove) {
|
|
630
|
+
try {
|
|
631
|
+
await this.remove();
|
|
632
|
+
log('[%s:%s] auto-removed completed one-time job', this.attrs.name, this.attrs._id);
|
|
633
|
+
}
|
|
634
|
+
catch (err) {
|
|
635
|
+
log('[%s:%s] failed to auto-remove job', this.attrs.name, this.attrs._id, err);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
async runJob() {
|
|
642
|
+
const definition = this.agenda.definitions[this.attrs.name];
|
|
643
|
+
if (!definition) {
|
|
644
|
+
log('[%s:%s] has no definition, can not run', this.attrs.name, this.attrs._id);
|
|
645
|
+
throw new Error('Undefined job');
|
|
646
|
+
}
|
|
647
|
+
if (definition.fn.length === 2) {
|
|
648
|
+
log('[%s:%s] process function being called', this.attrs.name, this.attrs._id);
|
|
649
|
+
await new Promise((resolve, reject) => {
|
|
650
|
+
try {
|
|
651
|
+
const result = definition.fn(this, error => {
|
|
652
|
+
if (error) {
|
|
653
|
+
reject(error);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
resolve();
|
|
657
|
+
});
|
|
658
|
+
if (this.isPromise(result)) {
|
|
659
|
+
result.catch((error) => reject(error));
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch (error) {
|
|
663
|
+
reject(error);
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
log('[%s:%s] process function being called', this.attrs.name, this.attrs._id);
|
|
669
|
+
await definition.fn(this);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
isPromise(value) {
|
|
673
|
+
return !!(value && typeof value.then === 'function');
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Fields managed by the job processor that should not be saved by user code.
|
|
678
|
+
* These are excluded in save() to prevent race conditions between user code and the processor.
|
|
679
|
+
*/
|
|
680
|
+
Job.PROCESSOR_MANAGED_FIELDS = new Set([
|
|
681
|
+
'lockedAt',
|
|
682
|
+
'lastRunAt',
|
|
683
|
+
'lastFinishedAt',
|
|
684
|
+
'failedAt',
|
|
685
|
+
'failCount',
|
|
686
|
+
'failReason',
|
|
687
|
+
'progress'
|
|
688
|
+
]);
|
|
689
|
+
//# sourceMappingURL=Job.js.map
|