agenda 4.4.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +20 -8
  2. package/dist/agenda/create.d.ts +2 -2
  3. package/dist/agenda/create.d.ts.map +1 -1
  4. package/dist/agenda/create.js.map +1 -1
  5. package/dist/agenda/define.d.ts +2 -2
  6. package/dist/agenda/define.d.ts.map +1 -1
  7. package/dist/agenda/define.js.map +1 -1
  8. package/dist/agenda/every.d.ts +2 -1
  9. package/dist/agenda/every.d.ts.map +1 -1
  10. package/dist/agenda/every.js +1 -1
  11. package/dist/agenda/every.js.map +1 -1
  12. package/dist/agenda/find-and-lock-next-job.d.ts.map +1 -1
  13. package/dist/agenda/find-and-lock-next-job.js +3 -4
  14. package/dist/agenda/find-and-lock-next-job.js.map +1 -1
  15. package/dist/agenda/now.d.ts +2 -2
  16. package/dist/agenda/now.d.ts.map +1 -1
  17. package/dist/agenda/now.js.map +1 -1
  18. package/dist/agenda/schedule.d.ts +3 -3
  19. package/dist/agenda/schedule.d.ts.map +1 -1
  20. package/dist/agenda/schedule.js.map +1 -1
  21. package/dist/job/compute-next-run-at.js +2 -2
  22. package/dist/job/compute-next-run-at.js.map +1 -1
  23. package/dist/job/index.d.ts +6 -3
  24. package/dist/job/index.d.ts.map +1 -1
  25. package/dist/job/index.js.map +1 -1
  26. package/lib/agenda/cancel.ts +28 -0
  27. package/lib/agenda/close.ts +37 -0
  28. package/lib/agenda/create.ts +22 -0
  29. package/lib/agenda/database.ts +63 -0
  30. package/lib/agenda/db-init.ts +45 -0
  31. package/lib/agenda/default-concurrency.ts +19 -0
  32. package/lib/agenda/default-lock-lifetime.ts +17 -0
  33. package/lib/agenda/default-lock-limit.ts +17 -0
  34. package/lib/agenda/define.ts +85 -0
  35. package/lib/agenda/disable.ts +28 -0
  36. package/lib/agenda/drain.ts +30 -0
  37. package/lib/agenda/enable.ts +29 -0
  38. package/lib/agenda/every.ts +86 -0
  39. package/lib/agenda/find-and-lock-next-job.ts +78 -0
  40. package/lib/agenda/has-mongo-protocol.ts +8 -0
  41. package/lib/agenda/index.ts +227 -0
  42. package/lib/agenda/job-processing-queue.ts +99 -0
  43. package/lib/agenda/jobs.ts +31 -0
  44. package/lib/agenda/lock-limit.ts +17 -0
  45. package/lib/agenda/max-concurrency.ts +20 -0
  46. package/lib/agenda/mongo.ts +21 -0
  47. package/lib/agenda/name.ts +16 -0
  48. package/lib/agenda/now.ts +31 -0
  49. package/lib/agenda/process-every.ts +18 -0
  50. package/lib/agenda/purge.ts +19 -0
  51. package/lib/agenda/save-job.ts +185 -0
  52. package/lib/agenda/schedule.ts +79 -0
  53. package/lib/agenda/sort.ts +17 -0
  54. package/lib/agenda/start.ts +30 -0
  55. package/lib/agenda/stop.ts +49 -0
  56. package/lib/cjs.ts +5 -0
  57. package/lib/index.ts +11 -0
  58. package/lib/job/compute-next-run-at.ts +191 -0
  59. package/lib/job/disable.ts +11 -0
  60. package/lib/job/enable.ts +11 -0
  61. package/lib/job/fail.ts +29 -0
  62. package/lib/job/index.ts +221 -0
  63. package/lib/job/is-running.ts +29 -0
  64. package/lib/job/priority.ts +11 -0
  65. package/lib/job/remove.ts +10 -0
  66. package/lib/job/repeat-at.ts +12 -0
  67. package/lib/job/repeat-every.ts +40 -0
  68. package/lib/job/run.ts +125 -0
  69. package/lib/job/save.ts +11 -0
  70. package/lib/job/schedule.ts +15 -0
  71. package/lib/job/set-shouldsaveresult.ts +10 -0
  72. package/lib/job/to-json.ts +36 -0
  73. package/lib/job/touch.ts +11 -0
  74. package/lib/job/unique.ts +18 -0
  75. package/lib/utils/create-job.ts +13 -0
  76. package/lib/utils/index.ts +3 -0
  77. package/lib/utils/parse-priority.ts +37 -0
  78. package/lib/utils/process-jobs.ts +379 -0
  79. package/package.json +13 -11
  80. package/tsconfig.json +72 -0
@@ -0,0 +1,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
+ };
@@ -0,0 +1,79 @@
1
+ import createDebugger from "debug";
2
+ import { Agenda } from ".";
3
+ import { Job, JobAttributesData } from "../job";
4
+
5
+ const debug = createDebugger("agenda:schedule");
6
+
7
+ /**
8
+ * Schedule a job or jobs at a specific time
9
+ * @name Agenda#schedule
10
+ * @function
11
+ * @param when when the job gets run
12
+ * @param names array of job names to run
13
+ * @param data data to send to job
14
+ * @returns job or jobs created
15
+ */
16
+ export function schedule<T extends JobAttributesData>(this: Agenda, when: string | Date, names: string, data: T): Promise<Job>;
17
+ export function schedule<T extends JobAttributesData>(this: Agenda, when: string | Date, names: string[], data: T): Promise<Job[]>;
18
+ export function schedule<T extends JobAttributesData> (
19
+ this: Agenda,
20
+ when: string | Date,
21
+ names: string | string[],
22
+ data: T,
23
+ ): Promise<Job | Job[]> {
24
+ /**
25
+ * Internal method that creates a job with given date
26
+ * @param when when the job gets run
27
+ * @param name of job to run
28
+ * @param data data to send to job
29
+ * @returns instance of new job
30
+ */
31
+ const createJob = async (
32
+ when: string | Date,
33
+ name: string,
34
+ data: any
35
+ ): Promise<Job> => {
36
+ const job = this.create(name, data);
37
+
38
+ await job.schedule(when).save();
39
+
40
+ return job;
41
+ };
42
+
43
+ /**
44
+ * Internal helper method that calls createJob on a names array
45
+ * @param when when the job gets run
46
+ * @param names names of jobs to run
47
+ * @param data data to send to job
48
+ * @returns jobs that were created
49
+ */
50
+ const createJobs = async (
51
+ when: string | Date,
52
+ names: string[],
53
+ data: any
54
+ ): Promise<Job[]> => {
55
+ try {
56
+ const createJobList: Array<Promise<Job>> = [];
57
+ names.map((name) => createJobList.push(createJob(when, name, data)));
58
+ debug("Agenda.schedule()::createJobs() -> all jobs created successfully");
59
+ return Promise.all(createJobList);
60
+ } catch (error) {
61
+ debug(
62
+ "Agenda.schedule()::createJobs() -> error creating one or more of the jobs"
63
+ );
64
+ throw error;
65
+ }
66
+ };
67
+
68
+ if (typeof names === "string") {
69
+ debug("Agenda.schedule(%s, %O, [%O], cb)", when, names);
70
+ return createJob(when, names, data);
71
+ }
72
+
73
+ if (Array.isArray(names)) {
74
+ debug("Agenda.schedule(%s, %O, [%O])", when, names);
75
+ return createJobs(when, names, data);
76
+ }
77
+
78
+ throw new TypeError("Name must be string or array of strings")
79
+ };
@@ -0,0 +1,17 @@
1
+ import createDebugger from "debug";
2
+ import { Agenda } from ".";
3
+
4
+ const debug = createDebugger("agenda:sort");
5
+
6
+ /**
7
+ * Set the sort query for finding next job
8
+ * Default is { nextRunAt: 1, priority: -1 }
9
+ * @name Agenda#sort
10
+ * @function
11
+ * @param query sort query object for MongoDB
12
+ */
13
+ export const sort = function (this: Agenda, query: any): Agenda {
14
+ debug("Agenda.sort([Object])");
15
+ this._sort = query;
16
+ return this;
17
+ };
@@ -0,0 +1,30 @@
1
+ import createDebugger from "debug";
2
+ import { Agenda } from ".";
3
+ import { processJobs } from "../utils";
4
+
5
+ const debug = createDebugger("agenda:start");
6
+
7
+ /**
8
+ * Starts processing jobs using processJobs() methods, storing an interval ID
9
+ * This method will only resolve if a db has been set up beforehand.
10
+ * @name Agenda#start
11
+ * @function
12
+ * @returns resolves if db set beforehand, returns undefined otherwise
13
+ */
14
+ export const start = async function (this: Agenda): Promise<void | unknown> {
15
+ if (this._processInterval) {
16
+ debug("Agenda.start was already called, ignoring");
17
+ return this._ready;
18
+ }
19
+
20
+ await this._ready;
21
+ debug(
22
+ "Agenda.start called, creating interval to call processJobs every [%dms]",
23
+ this._processEvery
24
+ );
25
+ this._processInterval = setInterval(
26
+ processJobs.bind(this),
27
+ this._processEvery
28
+ );
29
+ process.nextTick(processJobs.bind(this));
30
+ };
@@ -0,0 +1,49 @@
1
+ import createDebugger from "debug";
2
+ import { Agenda } from ".";
3
+
4
+ const debug = createDebugger("agenda:stop");
5
+
6
+ /**
7
+ * Clear the interval that processes the jobs
8
+ * @name Agenda#stop
9
+ * @function
10
+ * @returns resolves when job unlocking fails or passes
11
+ */
12
+ export const stop = async function (this: Agenda): Promise<void> {
13
+ /**
14
+ * Internal method to unlock jobs so that they can be re-run
15
+ * NOTE: May need to update what properties get set here, since job unlocking seems to fail
16
+ * @access private
17
+ * @returns resolves when job unlocking fails or passes
18
+ */
19
+ const _unlockJobs = async (): Promise<void> => {
20
+ return new Promise((resolve, reject) => {
21
+ debug("Agenda._unlockJobs()");
22
+ const jobIds = this._lockedJobs.map((job) => job.attrs._id);
23
+
24
+ if (jobIds.length === 0) {
25
+ debug("no jobs to unlock");
26
+ resolve();
27
+ }
28
+
29
+ debug("about to unlock jobs with ids: %O", jobIds);
30
+ this._collection.updateMany(
31
+ { _id: { $in: jobIds } },
32
+ { $set: { lockedAt: null } },
33
+ (error) => {
34
+ if (error) {
35
+ reject(error);
36
+ }
37
+
38
+ this._lockedJobs = [];
39
+ resolve();
40
+ }
41
+ );
42
+ });
43
+ };
44
+
45
+ debug("Agenda.stop called, clearing interval for processJobs()");
46
+ clearInterval(this._processInterval);
47
+ this._processInterval = undefined;
48
+ return _unlockJobs();
49
+ };
package/lib/cjs.ts ADDED
@@ -0,0 +1,5 @@
1
+ // old common js export (see index.ts for module exports)
2
+
3
+ import { Agenda } from "./agenda";
4
+ module.exports = Agenda;
5
+ module.exports.Agenda = Agenda;
package/lib/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ // module export, beware: cjs.ts is exported as main entry point!
2
+ export * from "./agenda";
3
+ export * from "./job";
4
+
5
+ export { DefineOptions, JobPriority, Processor } from "./agenda/define";
6
+ export { JobOptions } from "./job/repeat-every";
7
+
8
+ import { Agenda } from "./agenda";
9
+ export { Agenda };
10
+
11
+ export default Agenda;