@usehenri/jobs 0.0.0 → 1.2.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/CHANGELOG.md +311 -0
- package/LICENSE +21 -0
- package/README.md +8 -1
- package/index.js +45 -0
- package/module.js +8 -0
- package/package.json +50 -10
- package/src/batch.js +379 -0
- package/src/config.js +186 -0
- package/src/cron.js +237 -0
- package/src/definitions.js +236 -0
- package/src/duration.js +112 -0
- package/src/errors.js +115 -0
- package/src/jobs.js +1839 -0
- package/src/keys.js +65 -0
- package/src/module.js +442 -0
- package/src/runner.js +918 -0
- package/src/serialize.js +177 -0
- package/src/store/index.js +37 -0
- package/src/store/mongo.js +1334 -0
- package/src/store/schema.js +499 -0
- package/src/store/sql.js +1744 -0
package/src/keys.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unique keys the queue writes for itself.
|
|
3
|
+
*
|
|
4
|
+
* An application's `unique` key belongs to a job only while it is waiting or
|
|
5
|
+
* running: once the job is done, or once it has died, the key is freed and
|
|
6
|
+
* the same work may be enqueued again. That is what people expect of a
|
|
7
|
+
* unique job, and it is what the guide says.
|
|
8
|
+
*
|
|
9
|
+
* The keys the queue writes for itself are the exception, and they have
|
|
10
|
+
* to be. A recurring slot is enqueued *before* its schedule moves on, so that
|
|
11
|
+
* an enqueue that fails leaves the schedule due and the next tick tries again;
|
|
12
|
+
* the row already in the queue is what stops a second runner enqueueing the
|
|
13
|
+
* same slot. If that key were freed the moment the job finished, a slot
|
|
14
|
+
* whose job ran to completion in the gap between the enqueue and the
|
|
15
|
+
* schedule moving on would be enqueued a second time. So a key of this
|
|
16
|
+
* shape is kept for the life of the row, and the row is pruned like any
|
|
17
|
+
* other -- by which time that slot, a specific millisecond, can never be due
|
|
18
|
+
* again.
|
|
19
|
+
*
|
|
20
|
+
* A batch's callback is the same argument, word for word. It is enqueued
|
|
21
|
+
* *before* the batch is stamped finished, so that a process dying in between
|
|
22
|
+
* leaves the batch unfinished and the next sweep settles it again; the row
|
|
23
|
+
* already in the queue is what makes that second settle answer the same job
|
|
24
|
+
* instead of calling the callback twice. A key freed on completion would let
|
|
25
|
+
* a callback that ran quickly be enqueued a second time by that sweep -- so
|
|
26
|
+
* the key belongs to the row for its life, and the batch is finished long
|
|
27
|
+
* before either is pruned.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** What the recurring scheduler prefixes its slots with */
|
|
31
|
+
const RECURRING = 'recurring:';
|
|
32
|
+
|
|
33
|
+
/** What a batch's callback is enqueued under */
|
|
34
|
+
const BATCH = 'batch:';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The unique key a finished job should keep, if any
|
|
38
|
+
*
|
|
39
|
+
* @param {?string} key The key the job holds
|
|
40
|
+
* @returns {?string} The key to keep, or null to free it
|
|
41
|
+
*/
|
|
42
|
+
const keep = (key) =>
|
|
43
|
+
typeof key === 'string' &&
|
|
44
|
+
(key.startsWith(RECURRING) || key.startsWith(BATCH))
|
|
45
|
+
? key
|
|
46
|
+
: null;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The unique key of one occurrence of a recurring schedule
|
|
50
|
+
*
|
|
51
|
+
* @param {string} name The schedule name
|
|
52
|
+
* @param {number} due The moment the occurrence was due
|
|
53
|
+
* @returns {string} The key
|
|
54
|
+
*/
|
|
55
|
+
const slot = (name, due) => `${RECURRING}${name}:${due}`;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The unique key of a batch's callback
|
|
59
|
+
*
|
|
60
|
+
* @param {string} id The batch id
|
|
61
|
+
* @returns {string} The key
|
|
62
|
+
*/
|
|
63
|
+
const callback = (id) => `${BATCH}${id}`;
|
|
64
|
+
|
|
65
|
+
module.exports = { BATCH, RECURRING, callback, keep, slot };
|
package/src/module.js
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
const BaseModule = require('@usehenri/core/module');
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const debug = require('debug')('henri:jobs');
|
|
6
|
+
|
|
7
|
+
const { Jobs: Queue, MAIL_JOB } = require('./jobs');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Background jobs: the henri module this package ships.
|
|
11
|
+
*
|
|
12
|
+
* `package.json` points at it with `"henri": { "module": "./module.js" }`,
|
|
13
|
+
* so an application that depends on `@usehenri/jobs` has it in the boot as
|
|
14
|
+
* `henri.jobs`, with nothing else to write. One that does not has no such
|
|
15
|
+
* module, and core carries no queue of its own.
|
|
16
|
+
*
|
|
17
|
+
* Installing the package is not the same as using it: an application that
|
|
18
|
+
* has neither `app/jobs` nor a `jobs` block in its configuration keeps the
|
|
19
|
+
* module inert -- `henri.jobs.enabled` is false, no table is created and
|
|
20
|
+
* every call says what to do.
|
|
21
|
+
*
|
|
22
|
+
* It needs the models: the queue reaches its own tables through the store
|
|
23
|
+
* adapter. It runs after the mailers when there are any, so
|
|
24
|
+
* `deliverLater()` goes through the queue. Its slot is 4, not 5: `henri
|
|
25
|
+
* jobs` boots to that level so a runner never binds an HTTP port.
|
|
26
|
+
*
|
|
27
|
+
* @class JobsModule
|
|
28
|
+
* @extends {BaseModule}
|
|
29
|
+
*/
|
|
30
|
+
class JobsModule extends BaseModule {
|
|
31
|
+
/**
|
|
32
|
+
* Creates an instance of JobsModule.
|
|
33
|
+
*
|
|
34
|
+
* @param {object} [henri=null] A henri instance
|
|
35
|
+
* @memberof JobsModule
|
|
36
|
+
*/
|
|
37
|
+
constructor(henri = null) {
|
|
38
|
+
super();
|
|
39
|
+
|
|
40
|
+
this.reloadable = true;
|
|
41
|
+
this.needs = ['config', 'model'];
|
|
42
|
+
this.after = ['mailers'];
|
|
43
|
+
this.runlevel = 4;
|
|
44
|
+
this.name = 'jobs';
|
|
45
|
+
this.henri = henri;
|
|
46
|
+
|
|
47
|
+
this.queue = null;
|
|
48
|
+
this.enabled = false;
|
|
49
|
+
|
|
50
|
+
/** The dead letter queue, see @usehenri/jobs */
|
|
51
|
+
this.dead = {
|
|
52
|
+
count: () => this.ready().dead.count(),
|
|
53
|
+
discard: (id) => this.ready().dead.discard(id),
|
|
54
|
+
discardAll: (filter) => this.ready().dead.discardAll(filter),
|
|
55
|
+
get: (id) => this.ready().dead.get(id),
|
|
56
|
+
list: (filter) => this.ready().dead.list(filter),
|
|
57
|
+
retry: (id, options) => this.ready().dead.retry(id, options),
|
|
58
|
+
retryAll: (filter, options) =>
|
|
59
|
+
this.ready().dead.retryAll(filter, options),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Reading the batches back, see @usehenri/jobs */
|
|
63
|
+
this.batches = {
|
|
64
|
+
discard: (id) => this.ready().batches.discard(id),
|
|
65
|
+
get: (id) => this.ready().batches.get(id),
|
|
66
|
+
jobs: (id, filter) => this.ready().batches.jobs(id, filter),
|
|
67
|
+
list: (filter) => this.ready().batches.list(filter),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
this.init = this.init.bind(this);
|
|
71
|
+
this.stop = this.stop.bind(this);
|
|
72
|
+
this.reload = this.reload.bind(this);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Whether this application asked for a queue
|
|
77
|
+
*
|
|
78
|
+
* @returns {boolean} true when app/jobs holds a file, or the configuration
|
|
79
|
+
* has a `jobs` block
|
|
80
|
+
* @memberof JobsModule
|
|
81
|
+
*/
|
|
82
|
+
wanted() {
|
|
83
|
+
const { config } = this.henri;
|
|
84
|
+
|
|
85
|
+
if (config && config.has && config.has('jobs')) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const location = path.join(this.henri.cwd(), 'app', 'jobs');
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
return fs
|
|
93
|
+
.readdirSync(location, { recursive: true })
|
|
94
|
+
.some((entry) => String(entry).endsWith('.js'));
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Module initialization
|
|
102
|
+
* Called after being loaded by Modules
|
|
103
|
+
*
|
|
104
|
+
* @async
|
|
105
|
+
* @returns {Promise<string>} The name of the module
|
|
106
|
+
* @throws when the queue cannot start, or a job file is not a job
|
|
107
|
+
* @memberof JobsModule
|
|
108
|
+
*/
|
|
109
|
+
async init() {
|
|
110
|
+
const { config, pen } = this.henri;
|
|
111
|
+
|
|
112
|
+
if (!this.wanted()) {
|
|
113
|
+
debug('no app/jobs and no jobs configuration: staying out of the way');
|
|
114
|
+
|
|
115
|
+
return this.name;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const settings =
|
|
119
|
+
config && config.has && config.has('jobs') ? config.get('jobs') : {};
|
|
120
|
+
|
|
121
|
+
this.queue = new Queue(this.henri, {
|
|
122
|
+
config: settings || {},
|
|
123
|
+
cwd: this.henri.cwd(),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
await this.queue.start();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
pen.error('jobs', 'unable to start the queue', error.message);
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.enabled = true;
|
|
134
|
+
this.deliverMail();
|
|
135
|
+
this.instrument();
|
|
136
|
+
|
|
137
|
+
const names = this.queue.names();
|
|
138
|
+
|
|
139
|
+
pen.info(
|
|
140
|
+
'jobs',
|
|
141
|
+
`${names.length} job(s)`,
|
|
142
|
+
names.length > 0 ? names.join(', ') : 'none in app/jobs'
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return this.name;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* How deep the queue is, when henri is collecting metrics.
|
|
150
|
+
*
|
|
151
|
+
* The one number about a queue that a log line cannot carry: a depth is
|
|
152
|
+
* true of a moment, not of an event, and the thing worth alerting on is
|
|
153
|
+
* that it stopped coming down. An **observable** gauge, so nothing is
|
|
154
|
+
* measured while a job runs -- but it does cost one `SELECT ... GROUP BY`
|
|
155
|
+
* per collection, which is what `telemetry.metrics: false` turns off.
|
|
156
|
+
*
|
|
157
|
+
* @returns {boolean} Whether the instrument was registered
|
|
158
|
+
* @memberof JobsModule
|
|
159
|
+
*/
|
|
160
|
+
instrument() {
|
|
161
|
+
const { telemetry } = this.henri;
|
|
162
|
+
|
|
163
|
+
if (!telemetry || !telemetry.enabled) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return telemetry.observe(
|
|
168
|
+
'henri.jobs.queue.depth',
|
|
169
|
+
{
|
|
170
|
+
description: 'How many jobs the queue holds, by queue and state',
|
|
171
|
+
kind: 'gauge',
|
|
172
|
+
unit: '{job}',
|
|
173
|
+
},
|
|
174
|
+
async (observe) => {
|
|
175
|
+
for (const entry of await this.queue.storeOrDie().counts()) {
|
|
176
|
+
observe(entry.total, {
|
|
177
|
+
'henri.job.queue': entry.queue,
|
|
178
|
+
'henri.job.state': entry.state,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Send the mails `henri.mailers.deliverLater()` renders through the queue
|
|
187
|
+
*
|
|
188
|
+
* The mailers module hands over a rendered nodemailer payload and the
|
|
189
|
+
* options of the call (`wait`, `at`, `queue`, `priority`), which are the
|
|
190
|
+
* options of `perform()`: nothing else has to be mapped. Without the queue
|
|
191
|
+
* the mailers send out of band, which the mail guide is explicit about not
|
|
192
|
+
* being a queue.
|
|
193
|
+
*
|
|
194
|
+
* @returns {boolean} Whether the handler was registered
|
|
195
|
+
* @memberof JobsModule
|
|
196
|
+
*/
|
|
197
|
+
deliverMail() {
|
|
198
|
+
const { mailers } = this.henri;
|
|
199
|
+
|
|
200
|
+
if (!mailers || typeof mailers.onDeliverLater !== 'function') {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return mailers.onDeliverLater((message, options) =>
|
|
205
|
+
this.queue.perform(MAIL_JOB, message, options || {})
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Stops the runners this process started
|
|
211
|
+
*
|
|
212
|
+
* @async
|
|
213
|
+
* @returns {Promise<boolean>} true when there was something to stop
|
|
214
|
+
* @memberof JobsModule
|
|
215
|
+
*/
|
|
216
|
+
async stop() {
|
|
217
|
+
if (!this.queue) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const { mailers } = this.henri;
|
|
222
|
+
|
|
223
|
+
if (mailers && typeof mailers.onDeliverLater === 'function') {
|
|
224
|
+
mailers.onDeliverLater(null);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
await this.queue.stop();
|
|
228
|
+
this.enabled = false;
|
|
229
|
+
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Reloads the module: `app/jobs` is read again
|
|
235
|
+
*
|
|
236
|
+
* @async
|
|
237
|
+
* @returns {Promise<string>} Module name
|
|
238
|
+
* @memberof JobsModule
|
|
239
|
+
*/
|
|
240
|
+
async reload() {
|
|
241
|
+
await this.stop();
|
|
242
|
+
|
|
243
|
+
this.queue = null;
|
|
244
|
+
|
|
245
|
+
return this.init();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The queue, or a readable error
|
|
250
|
+
*
|
|
251
|
+
* @returns {object} The queue
|
|
252
|
+
* @throws when the application has no queue
|
|
253
|
+
* @memberof JobsModule
|
|
254
|
+
*/
|
|
255
|
+
ready() {
|
|
256
|
+
if (!this.queue) {
|
|
257
|
+
throw this.henri.pen.fatal(
|
|
258
|
+
'jobs',
|
|
259
|
+
`
|
|
260
|
+
This application has no job queue: it has neither app/jobs nor a jobs
|
|
261
|
+
block in its configuration. Write a job with: henri generate job <name>`,
|
|
262
|
+
null,
|
|
263
|
+
null,
|
|
264
|
+
'HENRI_JOB_QUEUE_UNAVAILABLE'
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return this.queue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Adds a recurring schedule a framework module asks for.
|
|
273
|
+
*
|
|
274
|
+
* `henri.retention` is the one that does: an application with the queue
|
|
275
|
+
* gets its retention sweep on a schedule with nothing to write, and an
|
|
276
|
+
* entry of its own under the same name still wins.
|
|
277
|
+
*
|
|
278
|
+
* @param {string} name The name of the schedule
|
|
279
|
+
* @param {object} entry `cron` or `every`, plus `job`, `args`, `queue`
|
|
280
|
+
* @returns {boolean} false when nothing was added
|
|
281
|
+
* @memberof JobsModule
|
|
282
|
+
*/
|
|
283
|
+
recur(name, entry) {
|
|
284
|
+
return this.queue ? this.queue.recur(name, entry) : false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Adds a job the application did not write
|
|
289
|
+
*
|
|
290
|
+
* The way a package ships work of its own: `@usehenri/webhooks` registers
|
|
291
|
+
* its delivery job here at boot, so every process that has the queue --
|
|
292
|
+
* the runner included -- can perform it. A file of `app/jobs` with the
|
|
293
|
+
* same name wins, and this answers false.
|
|
294
|
+
*
|
|
295
|
+
* @param {string} name The job name
|
|
296
|
+
* @param {object} definition `perform(args, context)` plus the options
|
|
297
|
+
* @returns {boolean} Whether it was registered
|
|
298
|
+
* @memberof JobsModule
|
|
299
|
+
*/
|
|
300
|
+
define(name, definition) {
|
|
301
|
+
return this.ready().define(name, definition);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Enqueues a job
|
|
306
|
+
*
|
|
307
|
+
* @param {string} name The job name (its file under app/jobs)
|
|
308
|
+
* @param {*} [args] What perform() receives
|
|
309
|
+
* @param {object} [options] `wait`, `at`, `queue`, `priority`,
|
|
310
|
+
* `maxAttempts`, `timeout`, `unique`, `batch`, `tenant`
|
|
311
|
+
* @returns {Promise<object>} The enqueued job
|
|
312
|
+
* @memberof JobsModule
|
|
313
|
+
*/
|
|
314
|
+
perform(name, args, options) {
|
|
315
|
+
return this.ready().perform(name, args, options);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Enqueues a job; the name `henri.mailers.onDeliverLater()` expects
|
|
320
|
+
*
|
|
321
|
+
* @param {string} name The job name
|
|
322
|
+
* @param {*} [args] What perform() receives
|
|
323
|
+
* @param {object} [options] The options of perform()
|
|
324
|
+
* @returns {Promise<object>} The enqueued job
|
|
325
|
+
* @memberof JobsModule
|
|
326
|
+
*/
|
|
327
|
+
enqueue(name, args, options) {
|
|
328
|
+
return this.ready().perform(name, args, options);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Enqueues a job to run later
|
|
333
|
+
*
|
|
334
|
+
* @param {(number|string)} wait How long to wait (`'5m'`)
|
|
335
|
+
* @param {string} name The job name
|
|
336
|
+
* @param {*} [args] What perform() receives
|
|
337
|
+
* @param {object} [options] The options of perform()
|
|
338
|
+
* @returns {Promise<object>} The enqueued job
|
|
339
|
+
* @memberof JobsModule
|
|
340
|
+
*/
|
|
341
|
+
performIn(wait, name, args, options) {
|
|
342
|
+
return this.ready().performIn(wait, name, args, options);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Enqueues a job to run at a given moment
|
|
347
|
+
*
|
|
348
|
+
* @param {(Date|string|number)} when The moment
|
|
349
|
+
* @param {string} name The job name
|
|
350
|
+
* @param {*} [args] What perform() receives
|
|
351
|
+
* @param {object} [options] The options of perform()
|
|
352
|
+
* @returns {Promise<object>} The enqueued job
|
|
353
|
+
* @memberof JobsModule
|
|
354
|
+
*/
|
|
355
|
+
performAt(when, name, args, options) {
|
|
356
|
+
return this.ready().performAt(when, name, args, options);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Performs a job here and now, without the queue
|
|
361
|
+
*
|
|
362
|
+
* @param {string} name The job name
|
|
363
|
+
* @param {*} [args] What perform() receives
|
|
364
|
+
* @returns {Promise<*>} What perform() returned
|
|
365
|
+
* @memberof JobsModule
|
|
366
|
+
*/
|
|
367
|
+
performNow(name, args) {
|
|
368
|
+
return this.ready().performNow(name, args);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Makes a batch: these jobs, and one that runs when they are all done
|
|
373
|
+
*
|
|
374
|
+
* The callback runs once every job has reached a terminal state, `dead`
|
|
375
|
+
* included, and is handed the counts under `batch`.
|
|
376
|
+
*
|
|
377
|
+
* @param {object} [options] `callback`, `args`, `name`, `jobs`, plus the
|
|
378
|
+
* callback's own `queue`, `priority`, `maxAttempts`, `timeout`, `wait`
|
|
379
|
+
* and `at`
|
|
380
|
+
* @param {function} [build] Adds the jobs itself; the batch is sealed
|
|
381
|
+
* when it resolves
|
|
382
|
+
* @returns {Promise<object>} The batch
|
|
383
|
+
* @memberof JobsModule
|
|
384
|
+
*/
|
|
385
|
+
batch(options, build) {
|
|
386
|
+
return this.ready().batch(options, build);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* One job
|
|
391
|
+
*
|
|
392
|
+
* @param {string} id The job id
|
|
393
|
+
* @returns {Promise<?object>} The job, or null
|
|
394
|
+
* @memberof JobsModule
|
|
395
|
+
*/
|
|
396
|
+
get(id) {
|
|
397
|
+
return this.ready().get(id);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* The jobs of the queue
|
|
402
|
+
*
|
|
403
|
+
* @param {object} [filter] `state`, `queue`, `name`, `limit`, `offset`
|
|
404
|
+
* @returns {Promise<Array<object>>} The jobs
|
|
405
|
+
* @memberof JobsModule
|
|
406
|
+
*/
|
|
407
|
+
list(filter) {
|
|
408
|
+
return this.ready().list(filter);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* What the queue holds
|
|
413
|
+
*
|
|
414
|
+
* @returns {Promise<object>} Counts, timings and waits
|
|
415
|
+
* @memberof JobsModule
|
|
416
|
+
*/
|
|
417
|
+
stats() {
|
|
418
|
+
return this.ready().stats();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* The concurrency limits of the application, and the slots being held
|
|
423
|
+
*
|
|
424
|
+
* @returns {Promise<object>} `{ declared, held }`
|
|
425
|
+
* @memberof JobsModule
|
|
426
|
+
*/
|
|
427
|
+
limits() {
|
|
428
|
+
return this.ready().limits();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* The job names of the application
|
|
433
|
+
*
|
|
434
|
+
* @returns {Array<string>} The names
|
|
435
|
+
* @memberof JobsModule
|
|
436
|
+
*/
|
|
437
|
+
names() {
|
|
438
|
+
return this.queue ? this.queue.names() : [];
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
module.exports = JobsModule;
|