agenda 4.3.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/History.md +12 -3
- package/README.md +38 -9
- 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/drain.d.ts +9 -0
- package/dist/agenda/drain.d.ts.map +1 -0
- package/dist/agenda/drain.js +46 -0
- package/dist/agenda/drain.js.map +1 -0
- 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/index.d.ts +2 -0
- package/dist/agenda/index.d.ts.map +1 -1
- package/dist/agenda/index.js +2 -0
- package/dist/agenda/index.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 +14 -12
- package/tsconfig.json +72 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import humanInterval from "human-interval";
|
|
3
|
+
import {
|
|
4
|
+
AnyError,
|
|
5
|
+
Collection,
|
|
6
|
+
Db as MongoDb,
|
|
7
|
+
MongoClient,
|
|
8
|
+
MongoClientOptions,
|
|
9
|
+
} from "mongodb";
|
|
10
|
+
import { Job } from "../job";
|
|
11
|
+
import { cancel } from "./cancel";
|
|
12
|
+
import { close } from "./close";
|
|
13
|
+
import { create } from "./create";
|
|
14
|
+
import { database } from "./database";
|
|
15
|
+
import { dbInit } from "./db-init";
|
|
16
|
+
import { defaultConcurrency } from "./default-concurrency";
|
|
17
|
+
import { defaultLockLifetime } from "./default-lock-lifetime";
|
|
18
|
+
import { defaultLockLimit } from "./default-lock-limit";
|
|
19
|
+
import { define } from "./define";
|
|
20
|
+
import { disable } from "./disable";
|
|
21
|
+
import { enable } from "./enable";
|
|
22
|
+
import { every } from "./every";
|
|
23
|
+
import { findAndLockNextJob } from "./find-and-lock-next-job";
|
|
24
|
+
import { JobProcessingQueue } from "./job-processing-queue";
|
|
25
|
+
import { jobs } from "./jobs";
|
|
26
|
+
import { lockLimit } from "./lock-limit";
|
|
27
|
+
import { maxConcurrency } from "./max-concurrency";
|
|
28
|
+
import { mongo } from "./mongo";
|
|
29
|
+
import { name } from "./name";
|
|
30
|
+
import { now } from "./now";
|
|
31
|
+
import { processEvery } from "./process-every";
|
|
32
|
+
import { purge } from "./purge";
|
|
33
|
+
import { saveJob } from "./save-job";
|
|
34
|
+
import { schedule } from "./schedule";
|
|
35
|
+
import { sort } from "./sort";
|
|
36
|
+
import { start } from "./start";
|
|
37
|
+
import { stop } from "./stop";
|
|
38
|
+
import { drain } from './drain';
|
|
39
|
+
|
|
40
|
+
export interface AgendaConfig {
|
|
41
|
+
name?: string;
|
|
42
|
+
processEvery?: string;
|
|
43
|
+
maxConcurrency?: number;
|
|
44
|
+
defaultConcurrency?: number;
|
|
45
|
+
lockLimit?: number;
|
|
46
|
+
defaultLockLimit?: number;
|
|
47
|
+
defaultLockLifetime?: number;
|
|
48
|
+
sort?: any;
|
|
49
|
+
mongo?: MongoDb;
|
|
50
|
+
db?: {
|
|
51
|
+
address: string;
|
|
52
|
+
collection?: string;
|
|
53
|
+
options?: MongoClientOptions;
|
|
54
|
+
};
|
|
55
|
+
disableAutoIndex?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @class Agenda
|
|
60
|
+
* @param {Object} config - Agenda Config
|
|
61
|
+
* @param {Function} cb - Callback after Agenda has started and connected to mongo
|
|
62
|
+
* @property {Object} _name - Name of the current Agenda queue
|
|
63
|
+
* @property {Number} _processEvery
|
|
64
|
+
* @property {Number} _defaultConcurrency
|
|
65
|
+
* @property {Number} _maxConcurrency
|
|
66
|
+
* @property {Number} _defaultLockLimit
|
|
67
|
+
* @property {Number} _lockLimit
|
|
68
|
+
* @property {Object} _definitions
|
|
69
|
+
* @property {Object} _runningJobs
|
|
70
|
+
* @property {Object} _lockedJobs
|
|
71
|
+
* @property {Object} _jobQueue
|
|
72
|
+
* @property {Number} _defaultLockLifetime
|
|
73
|
+
* @property {Object} _sort
|
|
74
|
+
* @property {Object} _indices
|
|
75
|
+
* @property {Boolean} _isLockingOnTheFly - true if 'lockingOnTheFly' is currently running. Prevent concurrent execution of this method.
|
|
76
|
+
* @property {Map} _isJobQueueFilling - A map of jobQueues and if the 'jobQueueFilling' method is currently running for a given map. 'lockingOnTheFly' and 'jobQueueFilling' should not run concurrently for the same jobQueue. It can cause that lock limits aren't honored.
|
|
77
|
+
* @property {Array} _jobsToLock
|
|
78
|
+
*/
|
|
79
|
+
class Agenda extends EventEmitter {
|
|
80
|
+
_defaultConcurrency: any;
|
|
81
|
+
_defaultLockLifetime: any;
|
|
82
|
+
_defaultLockLimit: any;
|
|
83
|
+
_definitions: any;
|
|
84
|
+
_findAndLockNextJob = findAndLockNextJob;
|
|
85
|
+
_indices: any;
|
|
86
|
+
_disableAutoIndex: boolean;
|
|
87
|
+
_isLockingOnTheFly: boolean;
|
|
88
|
+
_isJobQueueFilling: Map<string, boolean>;
|
|
89
|
+
_jobQueue: JobProcessingQueue;
|
|
90
|
+
_jobsToLock: Job[];
|
|
91
|
+
_lockedJobs: Job[];
|
|
92
|
+
_runningJobs: Job[];
|
|
93
|
+
_lockLimit: any;
|
|
94
|
+
_maxConcurrency: any;
|
|
95
|
+
_mongoUseUnifiedTopology?: boolean;
|
|
96
|
+
_name: any;
|
|
97
|
+
_processEvery: number;
|
|
98
|
+
_ready: Promise<unknown>;
|
|
99
|
+
_sort: any;
|
|
100
|
+
_db!: MongoClient;
|
|
101
|
+
_mdb!: MongoDb;
|
|
102
|
+
_collection!: Collection;
|
|
103
|
+
_nextScanAt: any;
|
|
104
|
+
_processInterval: any;
|
|
105
|
+
|
|
106
|
+
cancel!: typeof cancel;
|
|
107
|
+
close!: typeof close;
|
|
108
|
+
create!: typeof create;
|
|
109
|
+
database!: typeof database;
|
|
110
|
+
db_init!: typeof dbInit;
|
|
111
|
+
defaultConcurrency!: typeof defaultConcurrency;
|
|
112
|
+
defaultLockLifetime!: typeof defaultLockLifetime;
|
|
113
|
+
defaultLockLimit!: typeof defaultLockLimit;
|
|
114
|
+
define!: typeof define;
|
|
115
|
+
disable!: typeof disable;
|
|
116
|
+
enable!: typeof enable;
|
|
117
|
+
every!: typeof every;
|
|
118
|
+
jobs!: typeof jobs;
|
|
119
|
+
lockLimit!: typeof lockLimit;
|
|
120
|
+
maxConcurrency!: typeof maxConcurrency;
|
|
121
|
+
mongo!: typeof mongo;
|
|
122
|
+
name!: typeof name;
|
|
123
|
+
now!: typeof now;
|
|
124
|
+
processEvery!: typeof processEvery;
|
|
125
|
+
purge!: typeof purge;
|
|
126
|
+
saveJob!: typeof saveJob;
|
|
127
|
+
schedule!: typeof schedule;
|
|
128
|
+
sort!: typeof sort;
|
|
129
|
+
start!: typeof start;
|
|
130
|
+
stop!: typeof stop;
|
|
131
|
+
drain!: typeof drain;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Constructs a new Agenda object.
|
|
135
|
+
* @param config Optional configuration to initialize the Agenda.
|
|
136
|
+
* @param cb Optional callback called with the MongoDB collection.
|
|
137
|
+
*/
|
|
138
|
+
constructor(
|
|
139
|
+
config: AgendaConfig = {},
|
|
140
|
+
cb?: (
|
|
141
|
+
error: AnyError | undefined,
|
|
142
|
+
collection: Collection<any> | null
|
|
143
|
+
) => void
|
|
144
|
+
) {
|
|
145
|
+
super();
|
|
146
|
+
|
|
147
|
+
this._name = config.name;
|
|
148
|
+
this._processEvery = (humanInterval(config.processEvery) ??
|
|
149
|
+
humanInterval("5 seconds")) as number; // eslint-disable-line @typescript-eslint/non-nullable-type-assertion-style
|
|
150
|
+
this._defaultConcurrency = config.defaultConcurrency || 5;
|
|
151
|
+
this._maxConcurrency = config.maxConcurrency || 20;
|
|
152
|
+
this._defaultLockLimit = config.defaultLockLimit || 0;
|
|
153
|
+
this._lockLimit = config.lockLimit || 0;
|
|
154
|
+
this._definitions = {};
|
|
155
|
+
this._runningJobs = [];
|
|
156
|
+
this._lockedJobs = [];
|
|
157
|
+
this._jobQueue = new JobProcessingQueue();
|
|
158
|
+
this._defaultLockLifetime = config.defaultLockLifetime || 10 * 60 * 1000; // 10 minute default lockLifetime
|
|
159
|
+
this._sort = config.sort || { nextRunAt: 1, priority: -1 };
|
|
160
|
+
this._indices = {
|
|
161
|
+
name: 1,
|
|
162
|
+
...this._sort,
|
|
163
|
+
priority: -1,
|
|
164
|
+
lockedAt: 1,
|
|
165
|
+
nextRunAt: 1,
|
|
166
|
+
disabled: 1,
|
|
167
|
+
};
|
|
168
|
+
this._disableAutoIndex = config.disableAutoIndex === true;
|
|
169
|
+
|
|
170
|
+
this._isLockingOnTheFly = false;
|
|
171
|
+
this._isJobQueueFilling = new Map<string, boolean>();
|
|
172
|
+
this._jobsToLock = [];
|
|
173
|
+
this._ready = new Promise((resolve) => {
|
|
174
|
+
this.once("ready", resolve);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (config.mongo) {
|
|
178
|
+
this.mongo(
|
|
179
|
+
config.mongo,
|
|
180
|
+
config.db ? config.db.collection : undefined,
|
|
181
|
+
cb
|
|
182
|
+
); // @ts-expect-error // the documentation shows it should be correct: http://mongodb.github.io/node-mongodb-native/3.6/api/Db.html
|
|
183
|
+
if (config.mongo.s && config.mongo.topology && config.mongo.topology.s) {
|
|
184
|
+
this._mongoUseUnifiedTopology = Boolean(
|
|
185
|
+
// @ts-expect-error
|
|
186
|
+
config.mongo.topology.s.options.useUnifiedTopology
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
} else if (config.db) {
|
|
190
|
+
this.database(
|
|
191
|
+
config.db.address,
|
|
192
|
+
config.db.collection,
|
|
193
|
+
config.db.options,
|
|
194
|
+
cb
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
Agenda.prototype.cancel = cancel;
|
|
201
|
+
Agenda.prototype.close = close;
|
|
202
|
+
Agenda.prototype.create = create;
|
|
203
|
+
Agenda.prototype.database = database;
|
|
204
|
+
Agenda.prototype.db_init = dbInit;
|
|
205
|
+
Agenda.prototype.defaultConcurrency = defaultConcurrency;
|
|
206
|
+
Agenda.prototype.defaultLockLifetime = defaultLockLifetime;
|
|
207
|
+
Agenda.prototype.defaultLockLimit = defaultLockLimit;
|
|
208
|
+
Agenda.prototype.define = define;
|
|
209
|
+
Agenda.prototype.disable = disable;
|
|
210
|
+
Agenda.prototype.enable = enable;
|
|
211
|
+
Agenda.prototype.every = every;
|
|
212
|
+
Agenda.prototype.jobs = jobs;
|
|
213
|
+
Agenda.prototype.lockLimit = lockLimit;
|
|
214
|
+
Agenda.prototype.maxConcurrency = maxConcurrency;
|
|
215
|
+
Agenda.prototype.mongo = mongo;
|
|
216
|
+
Agenda.prototype.name = name;
|
|
217
|
+
Agenda.prototype.now = now;
|
|
218
|
+
Agenda.prototype.processEvery = processEvery;
|
|
219
|
+
Agenda.prototype.purge = purge;
|
|
220
|
+
Agenda.prototype.saveJob = saveJob;
|
|
221
|
+
Agenda.prototype.schedule = schedule;
|
|
222
|
+
Agenda.prototype.sort = sort;
|
|
223
|
+
Agenda.prototype.start = start;
|
|
224
|
+
Agenda.prototype.stop = stop;
|
|
225
|
+
Agenda.prototype.drain = drain;
|
|
226
|
+
|
|
227
|
+
export { Agenda };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Job } from "../job";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @class
|
|
5
|
+
* @param {Object} args - Job Options
|
|
6
|
+
* @property {Object} agenda - The Agenda instance
|
|
7
|
+
* @property {Object} attrs
|
|
8
|
+
*/
|
|
9
|
+
class JobProcessingQueue {
|
|
10
|
+
pop!: () => Job | undefined;
|
|
11
|
+
push!: (job: Job) => void;
|
|
12
|
+
insert!: (job: Job) => void;
|
|
13
|
+
returnNextConcurrencyFreeJob!: (agendaDefinitions: any) => Job;
|
|
14
|
+
|
|
15
|
+
protected _queue: Job[];
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
this._queue = [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get length(): number {
|
|
22
|
+
return this._queue.length;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Pops and returns last queue element (next job to be processed) without checking concurrency.
|
|
28
|
+
* @returns Next Job to be processed
|
|
29
|
+
*/
|
|
30
|
+
JobProcessingQueue.prototype.pop = function (this: JobProcessingQueue) {
|
|
31
|
+
return this._queue.pop();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Inserts job in first queue position
|
|
36
|
+
* @param job job to add to queue
|
|
37
|
+
*/
|
|
38
|
+
JobProcessingQueue.prototype.push = function (
|
|
39
|
+
this: JobProcessingQueue,
|
|
40
|
+
job: Job
|
|
41
|
+
) {
|
|
42
|
+
this._queue.push(job);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Inserts job in queue where it will be order from left to right in decreasing
|
|
47
|
+
* order of nextRunAt and priority (in case of same nextRunAt), if all values
|
|
48
|
+
* are even the first jobs to be introduced will have priority
|
|
49
|
+
* @param job job to add to queue
|
|
50
|
+
*/
|
|
51
|
+
JobProcessingQueue.prototype.insert = function (
|
|
52
|
+
this: JobProcessingQueue,
|
|
53
|
+
job: Job
|
|
54
|
+
) {
|
|
55
|
+
const matchIndex = this._queue.findIndex((element) => {
|
|
56
|
+
if (element.attrs.nextRunAt!.getTime() <= job.attrs.nextRunAt!.getTime()) {
|
|
57
|
+
if (
|
|
58
|
+
element.attrs.nextRunAt!.getTime() === job.attrs.nextRunAt!.getTime()
|
|
59
|
+
) {
|
|
60
|
+
if (element.attrs.priority >= job.attrs.priority) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return false;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (matchIndex === -1) {
|
|
72
|
+
this._queue.push(job);
|
|
73
|
+
} else {
|
|
74
|
+
this._queue.splice(matchIndex, 0, job);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns (does not pop, element remains in queue) first element (always from the right)
|
|
80
|
+
* that can be processed (not blocked by concurrency execution)
|
|
81
|
+
* @param agendaDefinitions job to add to queue
|
|
82
|
+
* @returns Next Job to be processed
|
|
83
|
+
*/
|
|
84
|
+
JobProcessingQueue.prototype.returnNextConcurrencyFreeJob = function (
|
|
85
|
+
this: JobProcessingQueue,
|
|
86
|
+
agendaDefinitions: any
|
|
87
|
+
) {
|
|
88
|
+
let next;
|
|
89
|
+
for (next = this._queue.length - 1; next > 0; next -= 1) {
|
|
90
|
+
const def = agendaDefinitions[this._queue[next].attrs.name];
|
|
91
|
+
if (def.concurrency > def.running) {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return this._queue[next];
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export { JobProcessingQueue };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Filter } from "mongodb";
|
|
2
|
+
import { Agenda } from ".";
|
|
3
|
+
import { Job } from "../job";
|
|
4
|
+
import { createJob } from "../utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Finds all jobs matching 'query'
|
|
8
|
+
* @name Agenda#jobs
|
|
9
|
+
* @function
|
|
10
|
+
* @param [query] object for MongoDB
|
|
11
|
+
* @param [sort] object for MongoDB
|
|
12
|
+
* @param [limit] number of documents to return from MongoDB
|
|
13
|
+
* @param [number] of documents to skip in MongoDB
|
|
14
|
+
* @returns resolves when fails or passes
|
|
15
|
+
*/
|
|
16
|
+
export const jobs = async function (
|
|
17
|
+
this: Agenda,
|
|
18
|
+
query: Filter<any> = {},
|
|
19
|
+
sort = {},
|
|
20
|
+
limit = 0,
|
|
21
|
+
skip = 0
|
|
22
|
+
): Promise<Job[]> {
|
|
23
|
+
const result = await this._collection
|
|
24
|
+
.find(query) // eslint-disable-line
|
|
25
|
+
.sort(sort)
|
|
26
|
+
.limit(limit)
|
|
27
|
+
.skip(skip)
|
|
28
|
+
.toArray();
|
|
29
|
+
|
|
30
|
+
return result.map((job: any) => createJob(this, job));
|
|
31
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import { Agenda } from ".";
|
|
3
|
+
|
|
4
|
+
const debug = createDebugger("agenda:locklimit");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Set the default amount jobs that are allowed to be locked at one time (GLOBAL)
|
|
8
|
+
* @name Agenda#locklimit
|
|
9
|
+
* @function
|
|
10
|
+
* @param limit num Lock limit
|
|
11
|
+
*/
|
|
12
|
+
export const lockLimit = function (this: Agenda, limit: number): Agenda {
|
|
13
|
+
// @NOTE: Is this different than max concurrency?
|
|
14
|
+
debug("Agenda.lockLimit(%d)", limit);
|
|
15
|
+
this._lockLimit = limit;
|
|
16
|
+
return this;
|
|
17
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import { Agenda } from ".";
|
|
3
|
+
|
|
4
|
+
const debug = createDebugger("agenda:maxConcurrency");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Set the concurrency for jobs (globally), type does not matter
|
|
8
|
+
* @name Agenda#maxConcurrency
|
|
9
|
+
* @function
|
|
10
|
+
* @param concurrency max concurrency value
|
|
11
|
+
* @returns agenda instance
|
|
12
|
+
*/
|
|
13
|
+
export const maxConcurrency = function (
|
|
14
|
+
this: Agenda,
|
|
15
|
+
concurrency: number
|
|
16
|
+
): Agenda {
|
|
17
|
+
debug("Agenda.maxConcurrency(%d)", concurrency);
|
|
18
|
+
this._maxConcurrency = concurrency;
|
|
19
|
+
return this;
|
|
20
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AnyError, Collection, Db } from "mongodb";
|
|
2
|
+
import { Agenda } from ".";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build method used to add MongoDB connection details
|
|
6
|
+
* @name Agenda#mongo
|
|
7
|
+
* @function
|
|
8
|
+
* @param mdb instance of MongoClient to use
|
|
9
|
+
* @param [collection] name collection we want to use ('agendaJobs')
|
|
10
|
+
* @param [cb] called when MongoDB connection fails or passes
|
|
11
|
+
*/
|
|
12
|
+
export const mongo = function (
|
|
13
|
+
this: Agenda,
|
|
14
|
+
mdb: Db,
|
|
15
|
+
collection?: string,
|
|
16
|
+
cb?: (error: AnyError | undefined, collection: Collection<any> | null) => void
|
|
17
|
+
): Agenda {
|
|
18
|
+
this._mdb = mdb;
|
|
19
|
+
this.db_init(collection, cb);
|
|
20
|
+
return this;
|
|
21
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import { Agenda } from ".";
|
|
3
|
+
|
|
4
|
+
const debug = createDebugger("agenda:name");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Set name of queue
|
|
8
|
+
* @name Agenda#name
|
|
9
|
+
* @function
|
|
10
|
+
* @param name name of agenda instance
|
|
11
|
+
*/
|
|
12
|
+
export const name = function (this: Agenda, name: string): Agenda {
|
|
13
|
+
debug("Agenda.name(%s)", name);
|
|
14
|
+
this._name = name;
|
|
15
|
+
return this;
|
|
16
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import { Agenda } from ".";
|
|
3
|
+
import { Job, JobAttributesData } from "../job";
|
|
4
|
+
|
|
5
|
+
const debug = createDebugger("agenda:now");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create a job for this exact moment
|
|
9
|
+
* @name Agenda#now
|
|
10
|
+
* @function
|
|
11
|
+
* @param name name of job to schedule
|
|
12
|
+
* @param data data to pass to job
|
|
13
|
+
*/
|
|
14
|
+
export const now = async function<T extends JobAttributesData> (
|
|
15
|
+
this: Agenda,
|
|
16
|
+
name: string,
|
|
17
|
+
data: T
|
|
18
|
+
): Promise<Job> {
|
|
19
|
+
debug("Agenda.now(%s, [Object])", name);
|
|
20
|
+
try {
|
|
21
|
+
const job = this.create(name, data);
|
|
22
|
+
|
|
23
|
+
job.schedule(new Date());
|
|
24
|
+
await job.save();
|
|
25
|
+
|
|
26
|
+
return job;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
debug("error trying to create a job for this exact moment");
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import humanInterval from "human-interval";
|
|
3
|
+
import { Agenda } from ".";
|
|
4
|
+
|
|
5
|
+
const debug = createDebugger("agenda:processEvery");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Set the default process interval
|
|
9
|
+
* @name Agenda#processEvery
|
|
10
|
+
* @function
|
|
11
|
+
* @param time - time to process, expressed in human interval
|
|
12
|
+
*/
|
|
13
|
+
export const processEvery = function (this: Agenda, time: string): Agenda {
|
|
14
|
+
debug("Agenda.processEvery(%d)", time);
|
|
15
|
+
// @ts-expect-error
|
|
16
|
+
this._processEvery = humanInterval(time);
|
|
17
|
+
return this;
|
|
18
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import { Agenda } from ".";
|
|
3
|
+
|
|
4
|
+
const debug = createDebugger("agenda:purge");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Removes all jobs from queue
|
|
8
|
+
* @name Agenda#purge
|
|
9
|
+
* @function
|
|
10
|
+
* @returns resolved when job cancelling fails or passes
|
|
11
|
+
*/
|
|
12
|
+
export const purge = async function (
|
|
13
|
+
this: Agenda
|
|
14
|
+
): Promise<number | undefined> {
|
|
15
|
+
// @NOTE: Only use after defining your jobs
|
|
16
|
+
const definedNames = Object.keys(this._definitions);
|
|
17
|
+
debug("Agenda.purge(%o)", definedNames);
|
|
18
|
+
return this.cancel({ name: { $not: { $in: definedNames } } });
|
|
19
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import { ObjectId } from "mongodb";
|
|
3
|
+
import { Agenda } from ".";
|
|
4
|
+
import { Job } from "../job";
|
|
5
|
+
import { processJobs } from "../utils";
|
|
6
|
+
|
|
7
|
+
const debug = createDebugger("agenda:saveJob");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Given a result for findOneAndUpdate() or insert() above, determine whether to process
|
|
11
|
+
* the job immediately or to let the processJobs() interval pick it up later
|
|
12
|
+
* @param job job instance
|
|
13
|
+
* @param result the data returned from the findOneAndUpdate() call or insertOne() call
|
|
14
|
+
* @access private
|
|
15
|
+
*/
|
|
16
|
+
const processDbResult = async function (this: Agenda, job: Job, result: any) {
|
|
17
|
+
debug(
|
|
18
|
+
"processDbResult() called with success, checking whether to process job immediately or not"
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
// We have a result from the above calls
|
|
22
|
+
// findOneAndUpdate() returns different results than insertOne() so check for that
|
|
23
|
+
let resultValue = result.insertedId ? result.insertedId : result.value;
|
|
24
|
+
|
|
25
|
+
if (resultValue) {
|
|
26
|
+
let _id: ObjectId;
|
|
27
|
+
let nextRunAt: Date | null | undefined;
|
|
28
|
+
|
|
29
|
+
if (result.insertedId) {
|
|
30
|
+
_id = result.insertedId;
|
|
31
|
+
// find the doc using _id
|
|
32
|
+
const _job = await this._collection.findOne({ _id });
|
|
33
|
+
|
|
34
|
+
if (_job) {
|
|
35
|
+
nextRunAt = _job.nextRunAt;
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
// If it is an array, grab the first job
|
|
39
|
+
if (Array.isArray(resultValue)) {
|
|
40
|
+
resultValue = resultValue[0];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_id = resultValue._id;
|
|
44
|
+
nextRunAt = resultValue.nextRunAt;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Grab ID and nextRunAt from MongoDB and store it as an attribute on Job
|
|
48
|
+
job.attrs._id = _id;
|
|
49
|
+
job.attrs.nextRunAt = nextRunAt;
|
|
50
|
+
|
|
51
|
+
// If the current job would have been processed in an older scan, process the job immediately
|
|
52
|
+
if (job.attrs.nextRunAt && job.attrs.nextRunAt < this._nextScanAt) {
|
|
53
|
+
debug(
|
|
54
|
+
"[%s:%s] job would have ran by nextScanAt, processing the job immediately",
|
|
55
|
+
job.attrs.name,
|
|
56
|
+
resultValue._id
|
|
57
|
+
);
|
|
58
|
+
await processJobs.call(this, job);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Return the Job instance
|
|
63
|
+
return job;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Save the properties on a job to MongoDB
|
|
68
|
+
* @name Agenda#saveJob
|
|
69
|
+
* @function
|
|
70
|
+
* @param job job to save into MongoDB
|
|
71
|
+
* @returns resolves when job is saved or errors
|
|
72
|
+
*/
|
|
73
|
+
export const saveJob = async function (this: Agenda, job: Job): Promise<Job> {
|
|
74
|
+
try {
|
|
75
|
+
debug("attempting to save a job into Agenda instance");
|
|
76
|
+
|
|
77
|
+
// Grab information needed to save job but that we don't want to persist in MongoDB
|
|
78
|
+
const id = job.attrs._id;
|
|
79
|
+
const { unique, uniqueOpts } = job.attrs;
|
|
80
|
+
|
|
81
|
+
// Store job as JSON and remove props we don't want to store from object
|
|
82
|
+
const props = job.toJSON();
|
|
83
|
+
delete props._id;
|
|
84
|
+
delete props.unique;
|
|
85
|
+
delete props.uniqueOpts;
|
|
86
|
+
|
|
87
|
+
// Store name of agenda queue as last modifier in job data
|
|
88
|
+
props.lastModifiedBy = this._name;
|
|
89
|
+
debug("[job %s] set job props: \n%O", id, props);
|
|
90
|
+
|
|
91
|
+
// Grab current time and set default query options for MongoDB
|
|
92
|
+
const now = new Date();
|
|
93
|
+
const protect = {};
|
|
94
|
+
let update = { $set: props };
|
|
95
|
+
debug("current time stored as %s", now.toISOString());
|
|
96
|
+
|
|
97
|
+
// If the job already had an ID, then update the properties of the job
|
|
98
|
+
// i.e, who last modified it, etc
|
|
99
|
+
if (id) {
|
|
100
|
+
// Update the job and process the resulting data'
|
|
101
|
+
debug(
|
|
102
|
+
"job already has _id, calling findOneAndUpdate() using _id as query"
|
|
103
|
+
);
|
|
104
|
+
const result = await this._collection.findOneAndUpdate(
|
|
105
|
+
{ _id: id },
|
|
106
|
+
update,
|
|
107
|
+
{ returnDocument: "after" }
|
|
108
|
+
);
|
|
109
|
+
return await processDbResult.call(this, job, result);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (props.type === "single") {
|
|
113
|
+
// Job type set to 'single' so...
|
|
114
|
+
// NOTE: Again, not sure about difference between 'single' here and 'once' in job.js
|
|
115
|
+
debug('job with type of "single" found');
|
|
116
|
+
|
|
117
|
+
// If the nextRunAt time is older than the current time, "protect" that property, meaning, don't change
|
|
118
|
+
// a scheduled job's next run time!
|
|
119
|
+
if (props.nextRunAt && props.nextRunAt <= now) {
|
|
120
|
+
debug(
|
|
121
|
+
"job has a scheduled nextRunAt time, protecting that field from upsert"
|
|
122
|
+
);
|
|
123
|
+
// @ts-expect-error
|
|
124
|
+
protect.nextRunAt = props.nextRunAt;
|
|
125
|
+
delete props.nextRunAt;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// If we have things to protect, set them in MongoDB using $setOnInsert
|
|
129
|
+
if (Object.keys(protect).length > 0) {
|
|
130
|
+
// @ts-expect-error
|
|
131
|
+
update.$setOnInsert = protect;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Try an upsert
|
|
135
|
+
// NOTE: 'single' again, not exactly sure what it means
|
|
136
|
+
debug(
|
|
137
|
+
'calling findOneAndUpdate() with job name and type of "single" as query'
|
|
138
|
+
);
|
|
139
|
+
const result = await this._collection.findOneAndUpdate(
|
|
140
|
+
{
|
|
141
|
+
name: props.name,
|
|
142
|
+
type: "single",
|
|
143
|
+
},
|
|
144
|
+
update,
|
|
145
|
+
{
|
|
146
|
+
upsert: true,
|
|
147
|
+
returnDocument: "after",
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
return await processDbResult.call(this, job, result);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (unique) {
|
|
154
|
+
// If we want the job to be unique, then we can upsert based on the 'unique' query object that was passed in
|
|
155
|
+
const query = job.attrs.unique;
|
|
156
|
+
query.name = props.name;
|
|
157
|
+
if (uniqueOpts?.insertOnly) {
|
|
158
|
+
// @ts-expect-error
|
|
159
|
+
update = { $setOnInsert: props };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Use the 'unique' query object to find an existing job or create a new one
|
|
163
|
+
debug(
|
|
164
|
+
"calling findOneAndUpdate() with unique object as query: \n%O",
|
|
165
|
+
query
|
|
166
|
+
);
|
|
167
|
+
const result = await this._collection.findOneAndUpdate(query, update, {
|
|
168
|
+
upsert: true,
|
|
169
|
+
returnDocument: "after",
|
|
170
|
+
});
|
|
171
|
+
return await processDbResult.call(this, job, result);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// If all else fails, the job does not exist yet so we just insert it into MongoDB
|
|
175
|
+
debug(
|
|
176
|
+
"using default behavior, inserting new job via insertOne() with props that were set: \n%O",
|
|
177
|
+
props
|
|
178
|
+
);
|
|
179
|
+
const result = await this._collection.insertOne(props);
|
|
180
|
+
return await processDbResult.call(this, job, result);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
debug("processDbResult() received an error, job was not updated/created");
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
};
|