agenda 4.4.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -8
- package/dist/agenda/create.d.ts +2 -2
- package/dist/agenda/create.d.ts.map +1 -1
- package/dist/agenda/create.js.map +1 -1
- package/dist/agenda/define.d.ts +2 -2
- package/dist/agenda/define.d.ts.map +1 -1
- package/dist/agenda/define.js.map +1 -1
- package/dist/agenda/every.d.ts +2 -1
- package/dist/agenda/every.d.ts.map +1 -1
- package/dist/agenda/every.js +1 -1
- package/dist/agenda/every.js.map +1 -1
- package/dist/agenda/find-and-lock-next-job.d.ts.map +1 -1
- package/dist/agenda/find-and-lock-next-job.js +3 -4
- package/dist/agenda/find-and-lock-next-job.js.map +1 -1
- package/dist/agenda/now.d.ts +2 -2
- package/dist/agenda/now.d.ts.map +1 -1
- package/dist/agenda/now.js.map +1 -1
- package/dist/agenda/schedule.d.ts +3 -3
- package/dist/agenda/schedule.d.ts.map +1 -1
- package/dist/agenda/schedule.js.map +1 -1
- package/dist/job/compute-next-run-at.js +2 -2
- package/dist/job/compute-next-run-at.js.map +1 -1
- package/dist/job/index.d.ts +6 -3
- package/dist/job/index.d.ts.map +1 -1
- package/dist/job/index.js.map +1 -1
- package/lib/agenda/cancel.ts +28 -0
- package/lib/agenda/close.ts +37 -0
- package/lib/agenda/create.ts +22 -0
- package/lib/agenda/database.ts +63 -0
- package/lib/agenda/db-init.ts +45 -0
- package/lib/agenda/default-concurrency.ts +19 -0
- package/lib/agenda/default-lock-lifetime.ts +17 -0
- package/lib/agenda/default-lock-limit.ts +17 -0
- package/lib/agenda/define.ts +85 -0
- package/lib/agenda/disable.ts +28 -0
- package/lib/agenda/drain.ts +30 -0
- package/lib/agenda/enable.ts +29 -0
- package/lib/agenda/every.ts +86 -0
- package/lib/agenda/find-and-lock-next-job.ts +78 -0
- package/lib/agenda/has-mongo-protocol.ts +8 -0
- package/lib/agenda/index.ts +227 -0
- package/lib/agenda/job-processing-queue.ts +99 -0
- package/lib/agenda/jobs.ts +31 -0
- package/lib/agenda/lock-limit.ts +17 -0
- package/lib/agenda/max-concurrency.ts +20 -0
- package/lib/agenda/mongo.ts +21 -0
- package/lib/agenda/name.ts +16 -0
- package/lib/agenda/now.ts +31 -0
- package/lib/agenda/process-every.ts +18 -0
- package/lib/agenda/purge.ts +19 -0
- package/lib/agenda/save-job.ts +185 -0
- package/lib/agenda/schedule.ts +79 -0
- package/lib/agenda/sort.ts +17 -0
- package/lib/agenda/start.ts +30 -0
- package/lib/agenda/stop.ts +49 -0
- package/lib/cjs.ts +5 -0
- package/lib/index.ts +11 -0
- package/lib/job/compute-next-run-at.ts +191 -0
- package/lib/job/disable.ts +11 -0
- package/lib/job/enable.ts +11 -0
- package/lib/job/fail.ts +29 -0
- package/lib/job/index.ts +221 -0
- package/lib/job/is-running.ts +29 -0
- package/lib/job/priority.ts +11 -0
- package/lib/job/remove.ts +10 -0
- package/lib/job/repeat-at.ts +12 -0
- package/lib/job/repeat-every.ts +40 -0
- package/lib/job/run.ts +125 -0
- package/lib/job/save.ts +11 -0
- package/lib/job/schedule.ts +15 -0
- package/lib/job/set-shouldsaveresult.ts +10 -0
- package/lib/job/to-json.ts +36 -0
- package/lib/job/touch.ts +11 -0
- package/lib/job/unique.ts +18 -0
- package/lib/utils/create-job.ts +13 -0
- package/lib/utils/index.ts +3 -0
- package/lib/utils/parse-priority.ts +37 -0
- package/lib/utils/process-jobs.ts +379 -0
- package/package.json +13 -11
- package/tsconfig.json +72 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Agenda } from ".";
|
|
2
|
+
import createDebugger from "debug";
|
|
3
|
+
import { Job } from "../job";
|
|
4
|
+
|
|
5
|
+
const debug = createDebugger("agenda:define");
|
|
6
|
+
|
|
7
|
+
export enum JobPriority {
|
|
8
|
+
highest = 20,
|
|
9
|
+
high = 10,
|
|
10
|
+
normal = 0,
|
|
11
|
+
low = -10,
|
|
12
|
+
lowest = -20,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DefineOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Maximum number of that job that can be running at once (per instance of agenda)
|
|
18
|
+
*/
|
|
19
|
+
concurrency?: number;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Maximum number of that job that can be locked at once (per instance of agenda)
|
|
23
|
+
*/
|
|
24
|
+
lockLimit?: number;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Interval in ms of how long the job stays locked for (see multiple job processors for more info). A job will
|
|
28
|
+
* automatically unlock if done() is called.
|
|
29
|
+
*/
|
|
30
|
+
lockLifetime?: number;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* (lowest|low|normal|high|highest|number) specifies the priority of the job. Higher priority jobs will run
|
|
34
|
+
* first.
|
|
35
|
+
*/
|
|
36
|
+
priority?: JobPriority;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Should the return value of the job be persisted
|
|
40
|
+
*/
|
|
41
|
+
shouldSaveResult?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type Processor<T> =
|
|
45
|
+
| ((job: Job<T>) => Promise<void>)
|
|
46
|
+
| ((job: Job<T>, done: () => void) => void);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Setup definition for job
|
|
50
|
+
* Method is used by consumers of lib to setup their functions
|
|
51
|
+
* @name Agenda#define
|
|
52
|
+
* @function
|
|
53
|
+
* @param name name of job
|
|
54
|
+
* @param options options for job to run
|
|
55
|
+
* @param [processor] function to be called to run actual job
|
|
56
|
+
*/
|
|
57
|
+
export const define = function<T> (
|
|
58
|
+
this: Agenda,
|
|
59
|
+
name: string,
|
|
60
|
+
options: DefineOptions | Processor<T>,
|
|
61
|
+
processor?: Processor<T>
|
|
62
|
+
): void {
|
|
63
|
+
if (processor === undefined) {
|
|
64
|
+
processor = options as Processor<T>;
|
|
65
|
+
options = {};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this._definitions[name] = {
|
|
69
|
+
fn: processor,
|
|
70
|
+
concurrency:
|
|
71
|
+
(options as DefineOptions).concurrency || this._defaultConcurrency, // `null` is per interface definition of DefineOptions not valid
|
|
72
|
+
lockLimit: (options as DefineOptions).lockLimit || this._defaultLockLimit,
|
|
73
|
+
priority: (options as DefineOptions).priority || JobPriority.normal,
|
|
74
|
+
lockLifetime:
|
|
75
|
+
(options as DefineOptions).lockLifetime || this._defaultLockLifetime,
|
|
76
|
+
running: 0,
|
|
77
|
+
locked: 0,
|
|
78
|
+
shouldSaveResult: (options as DefineOptions).shouldSaveResult || false
|
|
79
|
+
};
|
|
80
|
+
debug(
|
|
81
|
+
"job [%s] defined with following options: \n%O",
|
|
82
|
+
name,
|
|
83
|
+
this._definitions[name]
|
|
84
|
+
);
|
|
85
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import { Filter } from "mongodb";
|
|
3
|
+
import { Agenda } from ".";
|
|
4
|
+
const debug = createDebugger("agenda:disable");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Disables any jobs matching the passed MongoDB query by setting the `disabled` flag to `true`
|
|
8
|
+
* @name Agenda#disable
|
|
9
|
+
* @function
|
|
10
|
+
* @param query MongoDB query to use when enabling
|
|
11
|
+
* @returns {Promise<number>} Resolved with the number of disabled job instances.
|
|
12
|
+
*/
|
|
13
|
+
export const disable = async function (
|
|
14
|
+
this: Agenda,
|
|
15
|
+
query: Filter<unknown> = {}
|
|
16
|
+
): Promise<number> {
|
|
17
|
+
debug("attempting to disable all jobs matching query", query);
|
|
18
|
+
try {
|
|
19
|
+
const { modifiedCount } = await this._collection.updateMany(query, {
|
|
20
|
+
$set: { disabled: true },
|
|
21
|
+
});
|
|
22
|
+
debug("%s jobs disabled");
|
|
23
|
+
return modifiedCount;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
debug("error trying to mark jobs as `disabled`");
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import { Agenda } from ".";
|
|
3
|
+
|
|
4
|
+
const debug = createDebugger("agenda:drain");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Clear the interval that processes the jobs
|
|
8
|
+
* @name Agenda#drain
|
|
9
|
+
* @function
|
|
10
|
+
* @returns resolves when all running jobs completes
|
|
11
|
+
*/
|
|
12
|
+
export const drain = async function (this: Agenda): Promise<void> {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
debug("Agenda.drain called, clearing interval for processJobs()");
|
|
15
|
+
clearInterval(this._processInterval);
|
|
16
|
+
this._processInterval = undefined;
|
|
17
|
+
|
|
18
|
+
if (this._runningJobs.length === 0) {
|
|
19
|
+
resolve();
|
|
20
|
+
} else {
|
|
21
|
+
debug("Agenda.drain waiting for jobs to finish");
|
|
22
|
+
this.on('complete', () => {
|
|
23
|
+
// running jobs are removed after the event
|
|
24
|
+
if (this._runningJobs.length === 1) {
|
|
25
|
+
resolve();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import { Filter } from "mongodb";
|
|
3
|
+
import { Agenda } from ".";
|
|
4
|
+
const debug = createDebugger("agenda:enable");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Enables any jobs matching the passed MongoDB query by setting the `disabled` flag to `false`
|
|
8
|
+
* @name Agenda#enable
|
|
9
|
+
* @function
|
|
10
|
+
* @param query MongoDB query to use when enabling
|
|
11
|
+
* @caller client code, Agenda.purge(), Job.remove()
|
|
12
|
+
* @returns {Promise<Number>} A promise that contains the number of removed documents when fulfilled.
|
|
13
|
+
*/
|
|
14
|
+
export const enable = async function (
|
|
15
|
+
this: Agenda,
|
|
16
|
+
query: Filter<unknown> = {}
|
|
17
|
+
): Promise<number> {
|
|
18
|
+
debug("attempting to enable all jobs matching query", query);
|
|
19
|
+
try {
|
|
20
|
+
const { modifiedCount } = await this._collection.updateMany(query, {
|
|
21
|
+
$set: { disabled: false },
|
|
22
|
+
});
|
|
23
|
+
debug("%s jobs enabled", modifiedCount);
|
|
24
|
+
return modifiedCount;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
debug("error trying to mark jobs as `enabled`");
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import { Agenda } from ".";
|
|
3
|
+
import { Job, JobAttributesData } from "../job";
|
|
4
|
+
import { JobOptions } from "../job/repeat-every";
|
|
5
|
+
|
|
6
|
+
const debug = createDebugger("agenda:every");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a scheduled job with given interval and name/names of the job to run
|
|
10
|
+
* @name Agenda#every
|
|
11
|
+
* @function
|
|
12
|
+
* @param interval - run every X interval
|
|
13
|
+
* @param names - String or strings of jobs to schedule
|
|
14
|
+
* @param data - data to run for job
|
|
15
|
+
* @param options - options to run job for
|
|
16
|
+
* @returns Job/s created. Resolves when schedule fails or passes
|
|
17
|
+
*/
|
|
18
|
+
export const every = async function<T extends JobAttributesData> (
|
|
19
|
+
this: Agenda,
|
|
20
|
+
interval: string,
|
|
21
|
+
names: string | string[],
|
|
22
|
+
data?: T,
|
|
23
|
+
options?: JobOptions
|
|
24
|
+
): Promise<any> {
|
|
25
|
+
/**
|
|
26
|
+
* Internal method to setup job that gets run every interval
|
|
27
|
+
* @param interval run every X interval
|
|
28
|
+
* @param name String job to schedule
|
|
29
|
+
* @param [data] data to run for job
|
|
30
|
+
* @param [options] options to run job for
|
|
31
|
+
* @returns instance of job
|
|
32
|
+
*/
|
|
33
|
+
const createJob = async<T extends JobAttributesData> (
|
|
34
|
+
interval: string,
|
|
35
|
+
name: string,
|
|
36
|
+
data?: T,
|
|
37
|
+
options?: JobOptions
|
|
38
|
+
): Promise<Job> => {
|
|
39
|
+
const job = this.create(name, data || {});
|
|
40
|
+
|
|
41
|
+
job.attrs.type = "single";
|
|
42
|
+
job.repeatEvery(interval, options);
|
|
43
|
+
return job.save();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Internal helper method that uses createJob to create jobs for an array of names
|
|
48
|
+
* @param interval run every X interval
|
|
49
|
+
* @param names Strings of jobs to schedule
|
|
50
|
+
* @param [data] data to run for job
|
|
51
|
+
* @param [options] options to run job for
|
|
52
|
+
* @return array of jobs created
|
|
53
|
+
*/
|
|
54
|
+
const createJobs = async (
|
|
55
|
+
interval: string,
|
|
56
|
+
names: string[],
|
|
57
|
+
data?: T,
|
|
58
|
+
options?: JobOptions
|
|
59
|
+
): Promise<Job[] | undefined> => {
|
|
60
|
+
try {
|
|
61
|
+
const jobs: Array<Promise<Job>> = [];
|
|
62
|
+
names.map((name) => jobs.push(createJob(interval, name, data, options)));
|
|
63
|
+
|
|
64
|
+
debug("every() -> all jobs created successfully");
|
|
65
|
+
|
|
66
|
+
return Promise.all(jobs);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
// @TODO: catch - ignore :O
|
|
69
|
+
debug("every() -> error creating one or more of the jobs", error);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (typeof names === "string") {
|
|
74
|
+
debug("Agenda.every(%s, %O, %O)", interval, names, options);
|
|
75
|
+
const jobs = await createJob(interval, names, data, options);
|
|
76
|
+
|
|
77
|
+
return jobs;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (Array.isArray(names)) {
|
|
81
|
+
debug("Agenda.every(%s, %s, %O)", interval, names, options);
|
|
82
|
+
const jobs = await createJobs(interval, names, data, options);
|
|
83
|
+
|
|
84
|
+
return jobs;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import createDebugger from "debug";
|
|
2
|
+
import { createJob } from "../utils";
|
|
3
|
+
import { Agenda } from ".";
|
|
4
|
+
import { Job } from "../job";
|
|
5
|
+
import { ReturnDocument } from "mongodb";
|
|
6
|
+
|
|
7
|
+
const debug = createDebugger("agenda:internal:_findAndLockNextJob");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Find and lock jobs
|
|
11
|
+
* @name Agenda#findAndLockNextJob
|
|
12
|
+
* @function
|
|
13
|
+
* @param jobName name of job to try to lock
|
|
14
|
+
* @param definition definition used to tell how job is run
|
|
15
|
+
* @access protected
|
|
16
|
+
* @caller jobQueueFilling() only
|
|
17
|
+
*/
|
|
18
|
+
export const findAndLockNextJob = async function (
|
|
19
|
+
this: Agenda,
|
|
20
|
+
jobName: string,
|
|
21
|
+
definition: any
|
|
22
|
+
): Promise<Job | undefined> {
|
|
23
|
+
const now = new Date();
|
|
24
|
+
const lockDeadline = new Date(Date.now().valueOf() - definition.lockLifetime);
|
|
25
|
+
debug("_findAndLockNextJob(%s, [Function])", jobName);
|
|
26
|
+
|
|
27
|
+
const JOB_PROCESS_WHERE_QUERY = {
|
|
28
|
+
$and: [
|
|
29
|
+
{
|
|
30
|
+
name: jobName,
|
|
31
|
+
disabled: { $ne: true },
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
$or: [
|
|
35
|
+
{
|
|
36
|
+
lockedAt: { $eq: null },
|
|
37
|
+
nextRunAt: { $lte: this._nextScanAt },
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
lockedAt: { $lte: lockDeadline },
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Query used to set a job as locked
|
|
49
|
+
* @type {{$set: {lockedAt: Date}}}
|
|
50
|
+
*/
|
|
51
|
+
const JOB_PROCESS_SET_QUERY = { $set: { lockedAt: now } };
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Query used to affect what gets returned
|
|
55
|
+
* @type {{returnOriginal: boolean, sort: object}}
|
|
56
|
+
*/
|
|
57
|
+
const JOB_RETURN_QUERY = { returnDocument: ReturnDocument.AFTER, sort: this._sort };
|
|
58
|
+
|
|
59
|
+
// Find ONE and ONLY ONE job and set the 'lockedAt' time so that job begins to be processed
|
|
60
|
+
const result = await this._collection.findOneAndUpdate(
|
|
61
|
+
JOB_PROCESS_WHERE_QUERY,
|
|
62
|
+
JOB_PROCESS_SET_QUERY,
|
|
63
|
+
JOB_RETURN_QUERY
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
let job: Job | undefined = undefined;
|
|
67
|
+
if (result.value) {
|
|
68
|
+
debug(
|
|
69
|
+
"found a job available to lock, creating a new job on Agenda with id [%s]",
|
|
70
|
+
result.value._id
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
job = createJob(this, result.value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return job;
|
|
78
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Given a mongo connection url will check if it contains the mongo
|
|
3
|
+
* @param url URL to be tested
|
|
4
|
+
* @returns whether or not the url is a valid mongo URL
|
|
5
|
+
*/
|
|
6
|
+
export const hasMongoProtocol = function (url: string): boolean {
|
|
7
|
+
return /mongodb(?:\+srv)?:\/\/.*/.exec(url) !== null;
|
|
8
|
+
};
|
|
@@ -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 };
|