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.
Files changed (89) hide show
  1. package/History.md +12 -3
  2. package/README.md +38 -9
  3. package/dist/agenda/create.d.ts +2 -2
  4. package/dist/agenda/create.d.ts.map +1 -1
  5. package/dist/agenda/create.js.map +1 -1
  6. package/dist/agenda/define.d.ts +2 -2
  7. package/dist/agenda/define.d.ts.map +1 -1
  8. package/dist/agenda/define.js.map +1 -1
  9. package/dist/agenda/drain.d.ts +9 -0
  10. package/dist/agenda/drain.d.ts.map +1 -0
  11. package/dist/agenda/drain.js +46 -0
  12. package/dist/agenda/drain.js.map +1 -0
  13. package/dist/agenda/every.d.ts +2 -1
  14. package/dist/agenda/every.d.ts.map +1 -1
  15. package/dist/agenda/every.js +1 -1
  16. package/dist/agenda/every.js.map +1 -1
  17. package/dist/agenda/find-and-lock-next-job.d.ts.map +1 -1
  18. package/dist/agenda/find-and-lock-next-job.js +3 -4
  19. package/dist/agenda/find-and-lock-next-job.js.map +1 -1
  20. package/dist/agenda/index.d.ts +2 -0
  21. package/dist/agenda/index.d.ts.map +1 -1
  22. package/dist/agenda/index.js +2 -0
  23. package/dist/agenda/index.js.map +1 -1
  24. package/dist/agenda/now.d.ts +2 -2
  25. package/dist/agenda/now.d.ts.map +1 -1
  26. package/dist/agenda/now.js.map +1 -1
  27. package/dist/agenda/schedule.d.ts +3 -3
  28. package/dist/agenda/schedule.d.ts.map +1 -1
  29. package/dist/agenda/schedule.js.map +1 -1
  30. package/dist/job/compute-next-run-at.js +2 -2
  31. package/dist/job/compute-next-run-at.js.map +1 -1
  32. package/dist/job/index.d.ts +6 -3
  33. package/dist/job/index.d.ts.map +1 -1
  34. package/dist/job/index.js.map +1 -1
  35. package/lib/agenda/cancel.ts +28 -0
  36. package/lib/agenda/close.ts +37 -0
  37. package/lib/agenda/create.ts +22 -0
  38. package/lib/agenda/database.ts +63 -0
  39. package/lib/agenda/db-init.ts +45 -0
  40. package/lib/agenda/default-concurrency.ts +19 -0
  41. package/lib/agenda/default-lock-lifetime.ts +17 -0
  42. package/lib/agenda/default-lock-limit.ts +17 -0
  43. package/lib/agenda/define.ts +85 -0
  44. package/lib/agenda/disable.ts +28 -0
  45. package/lib/agenda/drain.ts +30 -0
  46. package/lib/agenda/enable.ts +29 -0
  47. package/lib/agenda/every.ts +86 -0
  48. package/lib/agenda/find-and-lock-next-job.ts +78 -0
  49. package/lib/agenda/has-mongo-protocol.ts +8 -0
  50. package/lib/agenda/index.ts +227 -0
  51. package/lib/agenda/job-processing-queue.ts +99 -0
  52. package/lib/agenda/jobs.ts +31 -0
  53. package/lib/agenda/lock-limit.ts +17 -0
  54. package/lib/agenda/max-concurrency.ts +20 -0
  55. package/lib/agenda/mongo.ts +21 -0
  56. package/lib/agenda/name.ts +16 -0
  57. package/lib/agenda/now.ts +31 -0
  58. package/lib/agenda/process-every.ts +18 -0
  59. package/lib/agenda/purge.ts +19 -0
  60. package/lib/agenda/save-job.ts +185 -0
  61. package/lib/agenda/schedule.ts +79 -0
  62. package/lib/agenda/sort.ts +17 -0
  63. package/lib/agenda/start.ts +30 -0
  64. package/lib/agenda/stop.ts +49 -0
  65. package/lib/cjs.ts +5 -0
  66. package/lib/index.ts +11 -0
  67. package/lib/job/compute-next-run-at.ts +191 -0
  68. package/lib/job/disable.ts +11 -0
  69. package/lib/job/enable.ts +11 -0
  70. package/lib/job/fail.ts +29 -0
  71. package/lib/job/index.ts +221 -0
  72. package/lib/job/is-running.ts +29 -0
  73. package/lib/job/priority.ts +11 -0
  74. package/lib/job/remove.ts +10 -0
  75. package/lib/job/repeat-at.ts +12 -0
  76. package/lib/job/repeat-every.ts +40 -0
  77. package/lib/job/run.ts +125 -0
  78. package/lib/job/save.ts +11 -0
  79. package/lib/job/schedule.ts +15 -0
  80. package/lib/job/set-shouldsaveresult.ts +10 -0
  81. package/lib/job/to-json.ts +36 -0
  82. package/lib/job/touch.ts +11 -0
  83. package/lib/job/unique.ts +18 -0
  84. package/lib/utils/create-job.ts +13 -0
  85. package/lib/utils/index.ts +3 -0
  86. package/lib/utils/parse-priority.ts +37 -0
  87. package/lib/utils/process-jobs.ts +379 -0
  88. package/package.json +14 -12
  89. package/tsconfig.json +72 -0
@@ -0,0 +1,22 @@
1
+ import createDebugger from "debug";
2
+ import { Job, JobAttributesData } from "../job";
3
+ import { Agenda } from ".";
4
+
5
+ const debug = createDebugger("agenda:create");
6
+
7
+ /**
8
+ * Given a name and some data, create a new job
9
+ * @name Agenda#create
10
+ * @function
11
+ * @param name name of job
12
+ * @param data data to set for job
13
+ */
14
+ export const create = function<T extends JobAttributesData> (this: Agenda, name: string, data: T): Job {
15
+ debug("Agenda.create(%s, [Object])", name);
16
+ const priority = this._definitions[name]
17
+ ? this._definitions[name].priority
18
+ : 0;
19
+ const shouldSaveResult = this._definitions[name] ? this._definitions[name].shouldSaveResult || false : false
20
+ const job = new Job({ name, data, type: "normal", priority, shouldSaveResult, agenda: this });
21
+ return job;
22
+ };
@@ -0,0 +1,63 @@
1
+ import createDebugger from "debug";
2
+ import { AnyError, Collection, MongoClient, MongoClientOptions } from "mongodb";
3
+ import { Agenda } from ".";
4
+ import { hasMongoProtocol } from "./has-mongo-protocol";
5
+
6
+ const debug = createDebugger("agenda:database");
7
+
8
+ /**
9
+ * Connect to the spec'd MongoDB server and database.
10
+ *
11
+ * NOTE:
12
+ * If `url` includes auth details then `options` must specify: { 'uri_decode_auth': true }. This does Auth on
13
+ * the specified database, not the Admin database. If you are using Auth on the Admin DB and not on the Agenda DB,
14
+ * then you need to authenticate against the Admin DB and then pass the MongoDB instance into the constructor
15
+ * or use Agenda.mongo(). If your app already has a MongoDB connection then use that. ie. specify config.mongo in
16
+ * the constructor or use Agenda.mongo().
17
+ * @name Agenda#database
18
+ * @function
19
+ * @param url MongoDB server URI
20
+ * @param [collection] name of collection to use. Defaults to `agendaJobs`
21
+ * @param [options] options for connecting
22
+ * @param [cb] callback of MongoDB connection
23
+ */
24
+ export const database = function (
25
+ this: Agenda,
26
+ url: string,
27
+ collection?: string,
28
+ options: MongoClientOptions = {},
29
+ cb?: (error: AnyError | undefined, collection: Collection<any> | null) => void
30
+ ): Agenda | void {
31
+ if (!hasMongoProtocol(url)) {
32
+ url = "mongodb://" + url;
33
+ }
34
+
35
+ collection = collection || "agendaJobs";
36
+
37
+ MongoClient.connect(url, options, (error, client) => {
38
+ if (error) {
39
+ debug("error connecting to MongoDB using collection: [%s]", collection);
40
+ if (cb) {
41
+ cb(error, null);
42
+ } else {
43
+ throw error;
44
+ }
45
+
46
+ return;
47
+ }
48
+
49
+ debug(
50
+ "successful connection to MongoDB using collection: [%s]",
51
+ collection
52
+ );
53
+
54
+ if (client) {
55
+ this._db = client;
56
+ this._mdb = client.db();
57
+ this.db_init(collection, cb);
58
+ } else {
59
+ throw new Error("Mongo Client is undefined");
60
+ }
61
+ });
62
+ return this;
63
+ };
@@ -0,0 +1,45 @@
1
+ import createDebugger from "debug";
2
+ import { AnyError, Collection } from "mongodb";
3
+ import { Agenda } from ".";
4
+
5
+ const debug = createDebugger("agenda:db_init");
6
+
7
+ /**
8
+ * Setup and initialize the collection used to manage Jobs.
9
+ * @name Agenda#dbInit
10
+ * @function
11
+ * @param collection name or undefined for default 'agendaJobs'
12
+ * @param [cb] called when the db is initialized
13
+ */
14
+ export const dbInit = function (
15
+ this: Agenda,
16
+ collection = "agendaJobs",
17
+ cb?: (error: AnyError | undefined, collection: Collection<any> | null) => void
18
+ ): void {
19
+ debug("init database collection using name [%s]", collection);
20
+ this._collection = this._mdb.collection(collection);
21
+ if (this._disableAutoIndex) {
22
+ debug("skipping auto index creation");
23
+ this.emit("ready");
24
+ return;
25
+ }
26
+
27
+ debug("attempting index creation");
28
+ this._collection.createIndex(
29
+ this._indices,
30
+ { name: "findAndLockNextJobIndex" },
31
+ (error) => {
32
+ if (error) {
33
+ debug("index creation failed");
34
+ this.emit("error", error);
35
+ } else {
36
+ debug("index creation success");
37
+ this.emit("ready");
38
+ }
39
+
40
+ if (cb) {
41
+ cb(error, this._collection);
42
+ }
43
+ }
44
+ );
45
+ };
@@ -0,0 +1,19 @@
1
+ import { Agenda } from ".";
2
+ import createDebugger from "debug";
3
+
4
+ const debug = createDebugger("agenda:defaultConcurrency");
5
+
6
+ /**
7
+ * Set the default concurrency for each job
8
+ * @name Agenda#defaultConcurrency
9
+ * @function
10
+ * @param concurrency default concurrency
11
+ */
12
+ export const defaultConcurrency = function (
13
+ this: Agenda,
14
+ concurrency: number
15
+ ): Agenda {
16
+ debug("Agenda.defaultConcurrency(%d)", concurrency);
17
+ this._defaultConcurrency = concurrency;
18
+ return this;
19
+ };
@@ -0,0 +1,17 @@
1
+ import { Agenda } from ".";
2
+ import createDebugger from "debug";
3
+
4
+ const debug = createDebugger("agenda:defaultLockLifetime");
5
+
6
+ /**
7
+ * Set the default lock time (in ms)
8
+ * Default is 10 * 60 * 1000 ms (10 minutes)
9
+ * @name Agenda#defaultLockLifetime
10
+ * @function
11
+ * @param {Number} ms time in ms to set default lock
12
+ */
13
+ export const defaultLockLifetime = function (this: Agenda, ms: number): Agenda {
14
+ debug("Agenda.defaultLockLifetime(%d)", ms);
15
+ this._defaultLockLifetime = ms;
16
+ return this;
17
+ };
@@ -0,0 +1,17 @@
1
+ import { Agenda } from ".";
2
+ import createDebugger from "debug";
3
+
4
+ const debug = createDebugger("agenda:defaultLockLimit");
5
+
6
+ /**
7
+ * Set default lock limit per job type
8
+ * @name Agenda#defaultLockLimit
9
+ * @function
10
+ * @param {Number} num Lock limit per job
11
+ * @returns {Agenda} agenda instance
12
+ */
13
+ export const defaultLockLimit = function (this: Agenda, times: number): Agenda {
14
+ debug("Agenda.defaultLockLimit(%d)", times);
15
+ this._defaultLockLimit = times;
16
+ return this;
17
+ };
@@ -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
+ };