1200 lines
43 KiB
JavaScript
1200 lines
43 KiB
JavaScript
/**
|
|
* Includes all the scripts needed by the queue and jobs.
|
|
*/
|
|
/*eslint-env node */
|
|
'use strict';
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.Scripts = void 0;
|
|
exports.raw2NextJobData = raw2NextJobData;
|
|
const msgpackr_1 = require("msgpackr");
|
|
const packer = new msgpackr_1.Packr({
|
|
useRecords: false,
|
|
encodeUndefinedAsNil: true,
|
|
});
|
|
const pack = packer.pack;
|
|
const enums_1 = require("../enums");
|
|
const utils_1 = require("../utils");
|
|
const version_1 = require("../version");
|
|
const errors_1 = require("./errors");
|
|
class Scripts {
|
|
constructor(queue) {
|
|
this.queue = queue;
|
|
this.version = version_1.version;
|
|
const queueKeys = this.queue.keys;
|
|
this.moveToFinishedKeys = [
|
|
queueKeys.wait,
|
|
queueKeys.active,
|
|
queueKeys.prioritized,
|
|
queueKeys.events,
|
|
queueKeys.stalled,
|
|
queueKeys.limiter,
|
|
queueKeys.delayed,
|
|
queueKeys.paused,
|
|
queueKeys.meta,
|
|
queueKeys.pc,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
];
|
|
}
|
|
execCommand(client, commandName, args) {
|
|
const commandNameWithVersion = `${commandName}:${this.version}`;
|
|
return client[commandNameWithVersion](args);
|
|
}
|
|
async isJobInList(listKey, jobId) {
|
|
const client = await this.queue.client;
|
|
let result;
|
|
if ((0, utils_1.isRedisVersionLowerThan)(this.queue.redisVersion, '6.0.6')) {
|
|
result = await this.execCommand(client, 'isJobInList', [listKey, jobId]);
|
|
}
|
|
else {
|
|
result = await client.lpos(listKey, jobId);
|
|
}
|
|
return Number.isInteger(result);
|
|
}
|
|
addDelayedJobArgs(job, encodedOpts, args) {
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [
|
|
queueKeys.marker,
|
|
queueKeys.meta,
|
|
queueKeys.id,
|
|
queueKeys.delayed,
|
|
queueKeys.completed,
|
|
queueKeys.events,
|
|
];
|
|
keys.push(pack(args), job.data, encodedOpts);
|
|
return keys;
|
|
}
|
|
addDelayedJob(client, job, encodedOpts, args) {
|
|
const argsList = this.addDelayedJobArgs(job, encodedOpts, args);
|
|
return this.execCommand(client, 'addDelayedJob', argsList);
|
|
}
|
|
addPrioritizedJobArgs(job, encodedOpts, args) {
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [
|
|
queueKeys.marker,
|
|
queueKeys.meta,
|
|
queueKeys.id,
|
|
queueKeys.prioritized,
|
|
queueKeys.delayed,
|
|
queueKeys.completed,
|
|
queueKeys.active,
|
|
queueKeys.events,
|
|
queueKeys.pc,
|
|
];
|
|
keys.push(pack(args), job.data, encodedOpts);
|
|
return keys;
|
|
}
|
|
addPrioritizedJob(client, job, encodedOpts, args) {
|
|
const argsList = this.addPrioritizedJobArgs(job, encodedOpts, args);
|
|
return this.execCommand(client, 'addPrioritizedJob', argsList);
|
|
}
|
|
addParentJobArgs(job, encodedOpts, args) {
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [
|
|
queueKeys.meta,
|
|
queueKeys.id,
|
|
queueKeys.delayed,
|
|
queueKeys['waiting-children'],
|
|
queueKeys.completed,
|
|
queueKeys.events,
|
|
];
|
|
keys.push(pack(args), job.data, encodedOpts);
|
|
return keys;
|
|
}
|
|
addParentJob(client, job, encodedOpts, args) {
|
|
const argsList = this.addParentJobArgs(job, encodedOpts, args);
|
|
return this.execCommand(client, 'addParentJob', argsList);
|
|
}
|
|
addStandardJobArgs(job, encodedOpts, args) {
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [
|
|
queueKeys.wait,
|
|
queueKeys.paused,
|
|
queueKeys.meta,
|
|
queueKeys.id,
|
|
queueKeys.completed,
|
|
queueKeys.delayed,
|
|
queueKeys.active,
|
|
queueKeys.events,
|
|
queueKeys.marker,
|
|
];
|
|
keys.push(pack(args), job.data, encodedOpts);
|
|
return keys;
|
|
}
|
|
addStandardJob(client, job, encodedOpts, args) {
|
|
const argsList = this.addStandardJobArgs(job, encodedOpts, args);
|
|
return this.execCommand(client, 'addStandardJob', argsList);
|
|
}
|
|
async addJob(client, job, opts, jobId, parentKeyOpts = {}) {
|
|
const queueKeys = this.queue.keys;
|
|
const parent = job.parent;
|
|
const args = [
|
|
queueKeys[''],
|
|
typeof jobId !== 'undefined' ? jobId : '',
|
|
job.name,
|
|
job.timestamp,
|
|
job.parentKey || null,
|
|
parentKeyOpts.parentDependenciesKey || null,
|
|
parent,
|
|
job.repeatJobKey,
|
|
job.deduplicationId ? `${queueKeys.de}:${job.deduplicationId}` : null,
|
|
];
|
|
let encodedOpts;
|
|
if (opts.repeat) {
|
|
const repeat = Object.assign({}, opts.repeat);
|
|
if (repeat.startDate) {
|
|
repeat.startDate = +new Date(repeat.startDate);
|
|
}
|
|
if (repeat.endDate) {
|
|
repeat.endDate = +new Date(repeat.endDate);
|
|
}
|
|
encodedOpts = pack(Object.assign(Object.assign({}, opts), { repeat }));
|
|
}
|
|
else {
|
|
encodedOpts = pack(opts);
|
|
}
|
|
let result;
|
|
if (parentKeyOpts.addToWaitingChildren) {
|
|
result = await this.addParentJob(client, job, encodedOpts, args);
|
|
}
|
|
else if (typeof opts.delay == 'number' && opts.delay > 0) {
|
|
result = await this.addDelayedJob(client, job, encodedOpts, args);
|
|
}
|
|
else if (opts.priority) {
|
|
result = await this.addPrioritizedJob(client, job, encodedOpts, args);
|
|
}
|
|
else {
|
|
result = await this.addStandardJob(client, job, encodedOpts, args);
|
|
}
|
|
if (result < 0) {
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
parentKey: parentKeyOpts.parentKey,
|
|
command: 'addJob',
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
pauseArgs(pause) {
|
|
let src = 'wait', dst = 'paused';
|
|
if (!pause) {
|
|
src = 'paused';
|
|
dst = 'wait';
|
|
}
|
|
const keys = [src, dst, 'meta', 'prioritized'].map((name) => this.queue.toKey(name));
|
|
keys.push(this.queue.keys.events, this.queue.keys.delayed, this.queue.keys.marker);
|
|
const args = [pause ? 'paused' : 'resumed'];
|
|
return keys.concat(args);
|
|
}
|
|
async pause(pause) {
|
|
const client = await this.queue.client;
|
|
const args = this.pauseArgs(pause);
|
|
return this.execCommand(client, 'pause', args);
|
|
}
|
|
addRepeatableJobArgs(customKey, nextMillis, opts, legacyCustomKey) {
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [
|
|
queueKeys.repeat,
|
|
queueKeys.delayed,
|
|
];
|
|
const args = [
|
|
nextMillis,
|
|
pack(opts),
|
|
legacyCustomKey,
|
|
customKey,
|
|
queueKeys[''],
|
|
];
|
|
return keys.concat(args);
|
|
}
|
|
async addRepeatableJob(customKey, nextMillis, opts, legacyCustomKey) {
|
|
const client = await this.queue.client;
|
|
const args = this.addRepeatableJobArgs(customKey, nextMillis, opts, legacyCustomKey);
|
|
return this.execCommand(client, 'addRepeatableJob', args);
|
|
}
|
|
async removeDeduplicationKey(deduplicationId, jobId) {
|
|
const client = await this.queue.client;
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [`${queueKeys.de}:${deduplicationId}`];
|
|
const args = [jobId];
|
|
return this.execCommand(client, 'removeDeduplicationKey', keys.concat(args));
|
|
}
|
|
async addJobScheduler(jobSchedulerId, nextMillis, templateData, templateOpts, opts, delayedJobOpts,
|
|
// The job id of the job that produced this next iteration
|
|
producerId) {
|
|
const client = await this.queue.client;
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [
|
|
queueKeys.repeat,
|
|
queueKeys.delayed,
|
|
queueKeys.wait,
|
|
queueKeys.paused,
|
|
queueKeys.meta,
|
|
queueKeys.prioritized,
|
|
queueKeys.marker,
|
|
queueKeys.id,
|
|
queueKeys.events,
|
|
queueKeys.pc,
|
|
queueKeys.active,
|
|
];
|
|
const args = [
|
|
nextMillis,
|
|
pack(opts),
|
|
jobSchedulerId,
|
|
templateData,
|
|
pack(templateOpts),
|
|
pack(delayedJobOpts),
|
|
Date.now(),
|
|
queueKeys[''],
|
|
producerId ? this.queue.toKey(producerId) : '',
|
|
];
|
|
const result = await this.execCommand(client, 'addJobScheduler', keys.concat(args));
|
|
if (typeof result === 'number' && result < 0) {
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
command: 'addJobScheduler',
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
async updateRepeatableJobMillis(client, customKey, nextMillis, legacyCustomKey) {
|
|
const args = [
|
|
this.queue.keys.repeat,
|
|
nextMillis,
|
|
customKey,
|
|
legacyCustomKey,
|
|
];
|
|
return this.execCommand(client, 'updateRepeatableJobMillis', args);
|
|
}
|
|
async updateJobSchedulerNextMillis(jobSchedulerId, nextMillis, templateData, delayedJobOpts,
|
|
// The job id of the job that produced this next iteration - TODO: remove in next breaking change
|
|
producerId) {
|
|
const client = await this.queue.client;
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [
|
|
queueKeys.repeat,
|
|
queueKeys.delayed,
|
|
queueKeys.wait,
|
|
queueKeys.paused,
|
|
queueKeys.meta,
|
|
queueKeys.prioritized,
|
|
queueKeys.marker,
|
|
queueKeys.id,
|
|
queueKeys.events,
|
|
queueKeys.pc,
|
|
producerId ? this.queue.toKey(producerId) : '',
|
|
queueKeys.active,
|
|
];
|
|
const args = [
|
|
nextMillis,
|
|
jobSchedulerId,
|
|
templateData,
|
|
pack(delayedJobOpts),
|
|
Date.now(),
|
|
queueKeys[''],
|
|
producerId,
|
|
];
|
|
return this.execCommand(client, 'updateJobScheduler', keys.concat(args));
|
|
}
|
|
removeRepeatableArgs(legacyRepeatJobId, repeatConcatOptions, repeatJobKey) {
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [queueKeys.repeat, queueKeys.delayed, queueKeys.events];
|
|
const args = [
|
|
legacyRepeatJobId,
|
|
this.getRepeatConcatOptions(repeatConcatOptions, repeatJobKey),
|
|
repeatJobKey,
|
|
queueKeys[''],
|
|
];
|
|
return keys.concat(args);
|
|
}
|
|
// TODO: remove this check in next breaking change
|
|
getRepeatConcatOptions(repeatConcatOptions, repeatJobKey) {
|
|
if (repeatJobKey && repeatJobKey.split(':').length > 2) {
|
|
return repeatJobKey;
|
|
}
|
|
return repeatConcatOptions;
|
|
}
|
|
async removeRepeatable(legacyRepeatJobId, repeatConcatOptions, repeatJobKey) {
|
|
const client = await this.queue.client;
|
|
const args = this.removeRepeatableArgs(legacyRepeatJobId, repeatConcatOptions, repeatJobKey);
|
|
return this.execCommand(client, 'removeRepeatable', args);
|
|
}
|
|
async removeJobScheduler(jobSchedulerId) {
|
|
const client = await this.queue.client;
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [queueKeys.repeat, queueKeys.delayed, queueKeys.events];
|
|
const args = [jobSchedulerId, queueKeys['']];
|
|
return this.execCommand(client, 'removeJobScheduler', keys.concat(args));
|
|
}
|
|
removeArgs(jobId, removeChildren) {
|
|
const keys = [jobId, 'repeat'].map(name => this.queue.toKey(name));
|
|
const args = [jobId, removeChildren ? 1 : 0, this.queue.toKey('')];
|
|
return keys.concat(args);
|
|
}
|
|
async remove(jobId, removeChildren) {
|
|
const client = await this.queue.client;
|
|
const args = this.removeArgs(jobId, removeChildren);
|
|
const result = await this.execCommand(client, 'removeJob', args);
|
|
if (result < 0) {
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
jobId,
|
|
command: 'removeJob',
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
async removeUnprocessedChildren(jobId) {
|
|
const client = await this.queue.client;
|
|
const args = [
|
|
this.queue.toKey(jobId),
|
|
this.queue.keys.meta,
|
|
this.queue.toKey(''),
|
|
jobId,
|
|
];
|
|
await this.execCommand(client, 'removeUnprocessedChildren', args);
|
|
}
|
|
async extendLock(jobId, token, duration, client) {
|
|
client = client || (await this.queue.client);
|
|
const args = [
|
|
this.queue.toKey(jobId) + ':lock',
|
|
this.queue.keys.stalled,
|
|
token,
|
|
duration,
|
|
jobId,
|
|
];
|
|
return this.execCommand(client, 'extendLock', args);
|
|
}
|
|
async extendLocks(jobIds, tokens, duration) {
|
|
const client = await this.queue.client;
|
|
const args = [
|
|
this.queue.keys.stalled,
|
|
this.queue.toKey(''),
|
|
pack(tokens),
|
|
pack(jobIds),
|
|
duration,
|
|
];
|
|
return this.execCommand(client, 'extendLocks', args);
|
|
}
|
|
async updateData(job, data) {
|
|
const client = await this.queue.client;
|
|
const keys = [this.queue.toKey(job.id)];
|
|
const dataJson = JSON.stringify(data);
|
|
const result = await this.execCommand(client, 'updateData', keys.concat([dataJson]));
|
|
if (result < 0) {
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
jobId: job.id,
|
|
command: 'updateData',
|
|
});
|
|
}
|
|
}
|
|
async updateProgress(jobId, progress) {
|
|
const client = await this.queue.client;
|
|
const keys = [
|
|
this.queue.toKey(jobId),
|
|
this.queue.keys.events,
|
|
this.queue.keys.meta,
|
|
];
|
|
const progressJson = JSON.stringify(progress);
|
|
const result = await this.execCommand(client, 'updateProgress', keys.concat([jobId, progressJson]));
|
|
if (result < 0) {
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
jobId,
|
|
command: 'updateProgress',
|
|
});
|
|
}
|
|
}
|
|
async addLog(jobId, logRow, keepLogs) {
|
|
const client = await this.queue.client;
|
|
const keys = [
|
|
this.queue.toKey(jobId),
|
|
this.queue.toKey(jobId) + ':logs',
|
|
];
|
|
const result = await this.execCommand(client, 'addLog', keys.concat([jobId, logRow, keepLogs ? keepLogs : '']));
|
|
if (result < 0) {
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
jobId,
|
|
command: 'addLog',
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
moveToFinishedArgs(job, val, propVal, shouldRemove, target, token, timestamp, fetchNext = true, fieldsToUpdate) {
|
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
const queueKeys = this.queue.keys;
|
|
const opts = this.queue.opts;
|
|
const workerKeepJobs = target === 'completed' ? opts.removeOnComplete : opts.removeOnFail;
|
|
const metricsKey = this.queue.toKey(`metrics:${target}`);
|
|
const keys = this.moveToFinishedKeys;
|
|
keys[10] = queueKeys[target];
|
|
keys[11] = this.queue.toKey((_a = job.id) !== null && _a !== void 0 ? _a : '');
|
|
keys[12] = metricsKey;
|
|
keys[13] = this.queue.keys.marker;
|
|
const keepJobs = this.getKeepJobs(shouldRemove, workerKeepJobs);
|
|
const args = [
|
|
job.id,
|
|
timestamp,
|
|
propVal,
|
|
typeof val === 'undefined' ? 'null' : val,
|
|
target,
|
|
!fetchNext || this.queue.closing ? 0 : 1,
|
|
queueKeys[''],
|
|
pack({
|
|
token,
|
|
name: opts.name,
|
|
keepJobs,
|
|
limiter: opts.limiter,
|
|
lockDuration: opts.lockDuration,
|
|
attempts: job.opts.attempts,
|
|
maxMetricsSize: ((_b = opts.metrics) === null || _b === void 0 ? void 0 : _b.maxDataPoints)
|
|
? (_c = opts.metrics) === null || _c === void 0 ? void 0 : _c.maxDataPoints
|
|
: '',
|
|
fpof: !!((_d = job.opts) === null || _d === void 0 ? void 0 : _d.failParentOnFailure),
|
|
cpof: !!((_e = job.opts) === null || _e === void 0 ? void 0 : _e.continueParentOnFailure),
|
|
idof: !!((_f = job.opts) === null || _f === void 0 ? void 0 : _f.ignoreDependencyOnFailure),
|
|
rdof: !!((_g = job.opts) === null || _g === void 0 ? void 0 : _g.removeDependencyOnFailure),
|
|
}),
|
|
fieldsToUpdate ? pack((0, utils_1.objectToFlatArray)(fieldsToUpdate)) : void 0,
|
|
];
|
|
return keys.concat(args);
|
|
}
|
|
getKeepJobs(shouldRemove, workerKeepJobs) {
|
|
if (typeof shouldRemove === 'undefined') {
|
|
return workerKeepJobs || { count: shouldRemove ? 0 : -1 };
|
|
}
|
|
return typeof shouldRemove === 'object'
|
|
? shouldRemove
|
|
: typeof shouldRemove === 'number'
|
|
? { count: shouldRemove }
|
|
: { count: shouldRemove ? 0 : -1 };
|
|
}
|
|
async moveToFinished(jobId, args) {
|
|
const client = await this.queue.client;
|
|
const result = await this.execCommand(client, 'moveToFinished', args);
|
|
if (result < 0) {
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
jobId,
|
|
command: 'moveToFinished',
|
|
state: 'active',
|
|
});
|
|
}
|
|
else {
|
|
if (typeof result !== 'undefined') {
|
|
return raw2NextJobData(result);
|
|
}
|
|
}
|
|
}
|
|
drainArgs(delayed) {
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [
|
|
queueKeys.wait,
|
|
queueKeys.paused,
|
|
queueKeys.delayed,
|
|
queueKeys.prioritized,
|
|
queueKeys.repeat,
|
|
];
|
|
const args = [queueKeys[''], delayed ? '1' : '0'];
|
|
return keys.concat(args);
|
|
}
|
|
async drain(delayed) {
|
|
const client = await this.queue.client;
|
|
const args = this.drainArgs(delayed);
|
|
return this.execCommand(client, 'drain', args);
|
|
}
|
|
removeChildDependencyArgs(jobId, parentKey) {
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [queueKeys['']];
|
|
const args = [this.queue.toKey(jobId), parentKey];
|
|
return keys.concat(args);
|
|
}
|
|
async removeChildDependency(jobId, parentKey) {
|
|
const client = await this.queue.client;
|
|
const args = this.removeChildDependencyArgs(jobId, parentKey);
|
|
const result = await this.execCommand(client, 'removeChildDependency', args);
|
|
switch (result) {
|
|
case 0:
|
|
return true;
|
|
case 1:
|
|
return false;
|
|
default:
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
jobId,
|
|
parentKey,
|
|
command: 'removeChildDependency',
|
|
});
|
|
}
|
|
}
|
|
getRangesArgs(types, start, end, asc) {
|
|
const queueKeys = this.queue.keys;
|
|
const transformedTypes = types.map(type => {
|
|
return type === 'waiting' ? 'wait' : type;
|
|
});
|
|
const keys = [queueKeys['']];
|
|
const args = [start, end, asc ? '1' : '0', ...transformedTypes];
|
|
return keys.concat(args);
|
|
}
|
|
async getRanges(types, start = 0, end = 1, asc = false) {
|
|
const client = await this.queue.client;
|
|
const args = this.getRangesArgs(types, start, end, asc);
|
|
return await this.execCommand(client, 'getRanges', args);
|
|
}
|
|
getCountsArgs(types) {
|
|
const queueKeys = this.queue.keys;
|
|
const transformedTypes = types.map(type => {
|
|
return type === 'waiting' ? 'wait' : type;
|
|
});
|
|
const keys = [queueKeys['']];
|
|
const args = [...transformedTypes];
|
|
return keys.concat(args);
|
|
}
|
|
async getCounts(types) {
|
|
const client = await this.queue.client;
|
|
const args = this.getCountsArgs(types);
|
|
return await this.execCommand(client, 'getCounts', args);
|
|
}
|
|
getCountsPerPriorityArgs(priorities) {
|
|
const keys = [
|
|
this.queue.keys.wait,
|
|
this.queue.keys.paused,
|
|
this.queue.keys.meta,
|
|
this.queue.keys.prioritized,
|
|
];
|
|
const args = priorities;
|
|
return keys.concat(args);
|
|
}
|
|
async getCountsPerPriority(priorities) {
|
|
const client = await this.queue.client;
|
|
const args = this.getCountsPerPriorityArgs(priorities);
|
|
return await this.execCommand(client, 'getCountsPerPriority', args);
|
|
}
|
|
getDependencyCountsArgs(jobId, types) {
|
|
const keys = [
|
|
`${jobId}:processed`,
|
|
`${jobId}:dependencies`,
|
|
`${jobId}:failed`,
|
|
`${jobId}:unsuccessful`,
|
|
].map(name => {
|
|
return this.queue.toKey(name);
|
|
});
|
|
const args = types;
|
|
return keys.concat(args);
|
|
}
|
|
async getDependencyCounts(jobId, types) {
|
|
const client = await this.queue.client;
|
|
const args = this.getDependencyCountsArgs(jobId, types);
|
|
return await this.execCommand(client, 'getDependencyCounts', args);
|
|
}
|
|
moveToCompletedArgs(job, returnvalue, removeOnComplete, token, fetchNext = false) {
|
|
const timestamp = Date.now();
|
|
return this.moveToFinishedArgs(job, returnvalue, 'returnvalue', removeOnComplete, 'completed', token, timestamp, fetchNext);
|
|
}
|
|
moveToFailedArgs(job, failedReason, removeOnFailed, token, fetchNext = false, fieldsToUpdate) {
|
|
const timestamp = Date.now();
|
|
return this.moveToFinishedArgs(job, failedReason, 'failedReason', removeOnFailed, 'failed', token, timestamp, fetchNext, fieldsToUpdate);
|
|
}
|
|
async isFinished(jobId, returnValue = false) {
|
|
const client = await this.queue.client;
|
|
const keys = ['completed', 'failed', jobId].map((key) => {
|
|
return this.queue.toKey(key);
|
|
});
|
|
return this.execCommand(client, 'isFinished', keys.concat([jobId, returnValue ? '1' : '']));
|
|
}
|
|
async getState(jobId) {
|
|
const client = await this.queue.client;
|
|
const keys = [
|
|
'completed',
|
|
'failed',
|
|
'delayed',
|
|
'active',
|
|
'wait',
|
|
'paused',
|
|
'waiting-children',
|
|
'prioritized',
|
|
].map((key) => {
|
|
return this.queue.toKey(key);
|
|
});
|
|
if ((0, utils_1.isRedisVersionLowerThan)(this.queue.redisVersion, '6.0.6')) {
|
|
return this.execCommand(client, 'getState', keys.concat([jobId]));
|
|
}
|
|
return this.execCommand(client, 'getStateV2', keys.concat([jobId]));
|
|
}
|
|
/**
|
|
* Change delay of a delayed job.
|
|
*
|
|
* Reschedules a delayed job by setting a new delay from the current time.
|
|
* For example, calling changeDelay(5000) will reschedule the job to execute
|
|
* 5000 milliseconds (5 seconds) from now, regardless of the original delay.
|
|
*
|
|
* @param jobId - the ID of the job to change the delay for.
|
|
* @param delay - milliseconds from now when the job should be processed.
|
|
* @returns delay in milliseconds.
|
|
* @throws JobNotExist
|
|
* This exception is thrown if jobId is missing.
|
|
* @throws JobNotInState
|
|
* This exception is thrown if job is not in delayed state.
|
|
*/
|
|
async changeDelay(jobId, delay) {
|
|
const client = await this.queue.client;
|
|
const args = this.changeDelayArgs(jobId, delay);
|
|
const result = await this.execCommand(client, 'changeDelay', args);
|
|
if (result < 0) {
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
jobId,
|
|
command: 'changeDelay',
|
|
state: 'delayed',
|
|
});
|
|
}
|
|
}
|
|
changeDelayArgs(jobId, delay) {
|
|
const timestamp = Date.now();
|
|
const keys = [
|
|
this.queue.keys.delayed,
|
|
this.queue.keys.meta,
|
|
this.queue.keys.marker,
|
|
this.queue.keys.events,
|
|
];
|
|
return keys.concat([
|
|
delay,
|
|
JSON.stringify(timestamp),
|
|
jobId,
|
|
this.queue.toKey(jobId),
|
|
]);
|
|
}
|
|
async changePriority(jobId, priority = 0, lifo = false) {
|
|
const client = await this.queue.client;
|
|
const args = this.changePriorityArgs(jobId, priority, lifo);
|
|
const result = await this.execCommand(client, 'changePriority', args);
|
|
if (result < 0) {
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
jobId,
|
|
command: 'changePriority',
|
|
});
|
|
}
|
|
}
|
|
changePriorityArgs(jobId, priority = 0, lifo = false) {
|
|
const keys = [
|
|
this.queue.keys.wait,
|
|
this.queue.keys.paused,
|
|
this.queue.keys.meta,
|
|
this.queue.keys.prioritized,
|
|
this.queue.keys.active,
|
|
this.queue.keys.pc,
|
|
this.queue.keys.marker,
|
|
];
|
|
return keys.concat([priority, this.queue.toKey(''), jobId, lifo ? 1 : 0]);
|
|
}
|
|
moveToDelayedArgs(jobId, timestamp, token, delay, opts = {}) {
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [
|
|
queueKeys.marker,
|
|
queueKeys.active,
|
|
queueKeys.prioritized,
|
|
queueKeys.delayed,
|
|
this.queue.toKey(jobId),
|
|
queueKeys.events,
|
|
queueKeys.meta,
|
|
queueKeys.stalled,
|
|
];
|
|
return keys.concat([
|
|
this.queue.keys[''],
|
|
timestamp,
|
|
jobId,
|
|
token,
|
|
delay,
|
|
opts.skipAttempt ? '1' : '0',
|
|
opts.fieldsToUpdate
|
|
? pack((0, utils_1.objectToFlatArray)(opts.fieldsToUpdate))
|
|
: void 0,
|
|
]);
|
|
}
|
|
moveToWaitingChildrenArgs(jobId, token, opts) {
|
|
const timestamp = Date.now();
|
|
const childKey = (0, utils_1.getParentKey)(opts.child);
|
|
const keys = [
|
|
'active',
|
|
'waiting-children',
|
|
jobId,
|
|
`${jobId}:dependencies`,
|
|
`${jobId}:unsuccessful`,
|
|
'stalled',
|
|
'events',
|
|
].map(name => {
|
|
return this.queue.toKey(name);
|
|
});
|
|
return keys.concat([
|
|
token,
|
|
childKey !== null && childKey !== void 0 ? childKey : '',
|
|
JSON.stringify(timestamp),
|
|
jobId,
|
|
this.queue.toKey(''),
|
|
]);
|
|
}
|
|
isMaxedArgs() {
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [queueKeys.meta, queueKeys.active];
|
|
return keys;
|
|
}
|
|
async isMaxed() {
|
|
const client = await this.queue.client;
|
|
const args = this.isMaxedArgs();
|
|
return !!(await this.execCommand(client, 'isMaxed', args));
|
|
}
|
|
async moveToDelayed(jobId, timestamp, delay, token = '0', opts = {}) {
|
|
const client = await this.queue.client;
|
|
const args = this.moveToDelayedArgs(jobId, timestamp, token, delay, opts);
|
|
const result = await this.execCommand(client, 'moveToDelayed', args);
|
|
if (result < 0) {
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
jobId,
|
|
command: 'moveToDelayed',
|
|
state: 'active',
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* Move parent job to waiting-children state.
|
|
*
|
|
* @returns true if job is successfully moved, false if there are pending dependencies.
|
|
* @throws JobNotExist
|
|
* This exception is thrown if jobId is missing.
|
|
* @throws JobLockNotExist
|
|
* This exception is thrown if job lock is missing.
|
|
* @throws JobNotInState
|
|
* This exception is thrown if job is not in active state.
|
|
*/
|
|
async moveToWaitingChildren(jobId, token, opts = {}) {
|
|
const client = await this.queue.client;
|
|
const args = this.moveToWaitingChildrenArgs(jobId, token, opts);
|
|
const result = await this.execCommand(client, 'moveToWaitingChildren', args);
|
|
switch (result) {
|
|
case 0:
|
|
return true;
|
|
case 1:
|
|
return false;
|
|
default:
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
jobId,
|
|
command: 'moveToWaitingChildren',
|
|
state: 'active',
|
|
});
|
|
}
|
|
}
|
|
getRateLimitTtlArgs(maxJobs) {
|
|
const keys = [
|
|
this.queue.keys.limiter,
|
|
this.queue.keys.meta,
|
|
];
|
|
return keys.concat([maxJobs !== null && maxJobs !== void 0 ? maxJobs : '0']);
|
|
}
|
|
async getRateLimitTtl(maxJobs) {
|
|
const client = await this.queue.client;
|
|
const args = this.getRateLimitTtlArgs(maxJobs);
|
|
return this.execCommand(client, 'getRateLimitTtl', args);
|
|
}
|
|
/**
|
|
* Remove jobs in a specific state.
|
|
*
|
|
* @returns Id jobs from the deleted records.
|
|
*/
|
|
async cleanJobsInSet(set, timestamp, limit = 0) {
|
|
const client = await this.queue.client;
|
|
return this.execCommand(client, 'cleanJobsInSet', [
|
|
this.queue.toKey(set),
|
|
this.queue.toKey('events'),
|
|
this.queue.toKey('repeat'),
|
|
this.queue.toKey(''),
|
|
timestamp,
|
|
limit,
|
|
set,
|
|
]);
|
|
}
|
|
getJobSchedulerArgs(id) {
|
|
const keys = [this.queue.keys.repeat];
|
|
return keys.concat([id]);
|
|
}
|
|
async getJobScheduler(id) {
|
|
const client = await this.queue.client;
|
|
const args = this.getJobSchedulerArgs(id);
|
|
return this.execCommand(client, 'getJobScheduler', args);
|
|
}
|
|
retryJobArgs(jobId, lifo, token, opts = {}) {
|
|
const keys = [
|
|
this.queue.keys.active,
|
|
this.queue.keys.wait,
|
|
this.queue.keys.paused,
|
|
this.queue.toKey(jobId),
|
|
this.queue.keys.meta,
|
|
this.queue.keys.events,
|
|
this.queue.keys.delayed,
|
|
this.queue.keys.prioritized,
|
|
this.queue.keys.pc,
|
|
this.queue.keys.marker,
|
|
this.queue.keys.stalled,
|
|
];
|
|
const pushCmd = (lifo ? 'R' : 'L') + 'PUSH';
|
|
return keys.concat([
|
|
this.queue.toKey(''),
|
|
Date.now(),
|
|
pushCmd,
|
|
jobId,
|
|
token,
|
|
opts.fieldsToUpdate
|
|
? pack((0, utils_1.objectToFlatArray)(opts.fieldsToUpdate))
|
|
: void 0,
|
|
]);
|
|
}
|
|
async retryJob(jobId, lifo, token = '0', opts = {}) {
|
|
const client = await this.queue.client;
|
|
const args = this.retryJobArgs(jobId, lifo, token, opts);
|
|
const result = await this.execCommand(client, 'retryJob', args);
|
|
if (result < 0) {
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
jobId,
|
|
command: 'retryJob',
|
|
state: 'active',
|
|
});
|
|
}
|
|
}
|
|
moveJobsToWaitArgs(state, count, timestamp) {
|
|
const keys = [
|
|
this.queue.toKey(''),
|
|
this.queue.keys.events,
|
|
this.queue.toKey(state),
|
|
this.queue.toKey('wait'),
|
|
this.queue.toKey('paused'),
|
|
this.queue.keys.meta,
|
|
this.queue.keys.active,
|
|
this.queue.keys.marker,
|
|
];
|
|
const args = [count, timestamp, state];
|
|
return keys.concat(args);
|
|
}
|
|
async retryJobs(state = 'failed', count = 1000, timestamp = new Date().getTime()) {
|
|
const client = await this.queue.client;
|
|
const args = this.moveJobsToWaitArgs(state, count, timestamp);
|
|
return this.execCommand(client, 'moveJobsToWait', args);
|
|
}
|
|
async promoteJobs(count = 1000) {
|
|
const client = await this.queue.client;
|
|
const args = this.moveJobsToWaitArgs('delayed', count, Number.MAX_VALUE);
|
|
return this.execCommand(client, 'moveJobsToWait', args);
|
|
}
|
|
/**
|
|
* Attempts to reprocess a job
|
|
*
|
|
* @param job - The job to reprocess
|
|
* @param state - The expected job state. If the job is not found
|
|
* on the provided state, then it's not reprocessed. Supported states: 'failed', 'completed'
|
|
*
|
|
* @returns A promise that resolves when the job has been successfully moved to the wait queue.
|
|
* @throws Will throw an error with a code property indicating the failure reason:
|
|
* - code 0: Job does not exist
|
|
* - code -1: Job is currently locked and can't be retried
|
|
* - code -2: Job was not found in the expected set
|
|
*/
|
|
async reprocessJob(job, state, opts = {}) {
|
|
const client = await this.queue.client;
|
|
const keys = [
|
|
this.queue.toKey(job.id),
|
|
this.queue.keys.events,
|
|
this.queue.toKey(state),
|
|
this.queue.keys.wait,
|
|
this.queue.keys.meta,
|
|
this.queue.keys.paused,
|
|
this.queue.keys.active,
|
|
this.queue.keys.marker,
|
|
];
|
|
const args = [
|
|
job.id,
|
|
(job.opts.lifo ? 'R' : 'L') + 'PUSH',
|
|
state === 'failed' ? 'failedReason' : 'returnvalue',
|
|
state,
|
|
opts.resetAttemptsMade ? '1' : '0',
|
|
opts.resetAttemptsStarted ? '1' : '0',
|
|
];
|
|
const result = await this.execCommand(client, 'reprocessJob', keys.concat(args));
|
|
switch (result) {
|
|
case 1:
|
|
return;
|
|
default:
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
jobId: job.id,
|
|
command: 'reprocessJob',
|
|
state,
|
|
});
|
|
}
|
|
}
|
|
async getMetrics(type, start = 0, end = -1) {
|
|
const client = await this.queue.client;
|
|
const keys = [
|
|
this.queue.toKey(`metrics:${type}`),
|
|
this.queue.toKey(`metrics:${type}:data`),
|
|
];
|
|
const args = [start, end];
|
|
const result = await this.execCommand(client, 'getMetrics', keys.concat(args));
|
|
return result;
|
|
}
|
|
async moveToActive(client, token, name) {
|
|
const opts = this.queue.opts;
|
|
const queueKeys = this.queue.keys;
|
|
const keys = [
|
|
queueKeys.wait,
|
|
queueKeys.active,
|
|
queueKeys.prioritized,
|
|
queueKeys.events,
|
|
queueKeys.stalled,
|
|
queueKeys.limiter,
|
|
queueKeys.delayed,
|
|
queueKeys.paused,
|
|
queueKeys.meta,
|
|
queueKeys.pc,
|
|
queueKeys.marker,
|
|
];
|
|
const args = [
|
|
queueKeys[''],
|
|
Date.now(),
|
|
pack({
|
|
token,
|
|
lockDuration: opts.lockDuration,
|
|
limiter: opts.limiter,
|
|
name,
|
|
}),
|
|
];
|
|
const result = await this.execCommand(client, 'moveToActive', keys.concat(args));
|
|
return raw2NextJobData(result);
|
|
}
|
|
async promote(jobId) {
|
|
const client = await this.queue.client;
|
|
const keys = [
|
|
this.queue.keys.delayed,
|
|
this.queue.keys.wait,
|
|
this.queue.keys.paused,
|
|
this.queue.keys.meta,
|
|
this.queue.keys.prioritized,
|
|
this.queue.keys.active,
|
|
this.queue.keys.pc,
|
|
this.queue.keys.events,
|
|
this.queue.keys.marker,
|
|
];
|
|
const args = [this.queue.toKey(''), jobId];
|
|
const code = await this.execCommand(client, 'promote', keys.concat(args));
|
|
if (code < 0) {
|
|
throw this.finishedErrors({
|
|
code,
|
|
jobId,
|
|
command: 'promote',
|
|
state: 'delayed',
|
|
});
|
|
}
|
|
}
|
|
moveStalledJobsToWaitArgs() {
|
|
const opts = this.queue.opts;
|
|
const keys = [
|
|
this.queue.keys.stalled,
|
|
this.queue.keys.wait,
|
|
this.queue.keys.active,
|
|
this.queue.keys['stalled-check'],
|
|
this.queue.keys.meta,
|
|
this.queue.keys.paused,
|
|
this.queue.keys.marker,
|
|
this.queue.keys.events,
|
|
];
|
|
const args = [
|
|
opts.maxStalledCount,
|
|
this.queue.toKey(''),
|
|
Date.now(),
|
|
opts.stalledInterval,
|
|
];
|
|
return keys.concat(args);
|
|
}
|
|
/**
|
|
* Looks for unlocked jobs in the active queue.
|
|
*
|
|
* The job was being worked on, but the worker process died and it failed to renew the lock.
|
|
* We call these jobs 'stalled'. This is the most common case. We resolve these by moving them
|
|
* back to wait to be re-processed. To prevent jobs from cycling endlessly between active and wait,
|
|
* (e.g. if the job handler keeps crashing),
|
|
* we limit the number stalled job recoveries to settings.maxStalledCount.
|
|
*/
|
|
async moveStalledJobsToWait() {
|
|
const client = await this.queue.client;
|
|
const args = this.moveStalledJobsToWaitArgs();
|
|
return this.execCommand(client, 'moveStalledJobsToWait', args);
|
|
}
|
|
/**
|
|
* Moves a job back from Active to Wait.
|
|
* This script is used when a job has been manually rate limited and needs
|
|
* to be moved back to wait from active status.
|
|
*
|
|
* @param client - Redis client
|
|
* @param jobId - Job id
|
|
* @returns
|
|
*/
|
|
async moveJobFromActiveToWait(jobId, token = '0') {
|
|
const client = await this.queue.client;
|
|
const keys = [
|
|
this.queue.keys.active,
|
|
this.queue.keys.wait,
|
|
this.queue.keys.stalled,
|
|
this.queue.keys.paused,
|
|
this.queue.keys.meta,
|
|
this.queue.keys.limiter,
|
|
this.queue.keys.prioritized,
|
|
this.queue.keys.marker,
|
|
this.queue.keys.events,
|
|
];
|
|
const args = [jobId, token, this.queue.toKey(jobId)];
|
|
const result = await this.execCommand(client, 'moveJobFromActiveToWait', keys.concat(args));
|
|
if (result < 0) {
|
|
throw this.finishedErrors({
|
|
code: result,
|
|
jobId,
|
|
command: 'moveJobFromActiveToWait',
|
|
state: 'active',
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
async obliterate(opts) {
|
|
const client = await this.queue.client;
|
|
const keys = [
|
|
this.queue.keys.meta,
|
|
this.queue.toKey(''),
|
|
];
|
|
const args = [opts.count, opts.force ? 'force' : null];
|
|
const result = await this.execCommand(client, 'obliterate', keys.concat(args));
|
|
if (result < 0) {
|
|
switch (result) {
|
|
case -1:
|
|
throw new Error('Cannot obliterate non-paused queue');
|
|
case -2:
|
|
throw new Error('Cannot obliterate queue with active jobs');
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Paginate a set or hash keys.
|
|
* @param opts - options to define the pagination behaviour
|
|
*
|
|
*/
|
|
async paginate(key, opts) {
|
|
const client = await this.queue.client;
|
|
const keys = [key];
|
|
const maxIterations = 5;
|
|
const pageSize = opts.end >= 0 ? opts.end - opts.start + 1 : Infinity;
|
|
let cursor = '0', offset = 0, items, total, rawJobs, page = [], jobs = [];
|
|
do {
|
|
const args = [
|
|
opts.start + page.length,
|
|
opts.end,
|
|
cursor,
|
|
offset,
|
|
maxIterations,
|
|
];
|
|
if (opts.fetchJobs) {
|
|
args.push(1);
|
|
}
|
|
[cursor, offset, items, total, rawJobs] = await this.execCommand(client, 'paginate', keys.concat(args));
|
|
page = page.concat(items);
|
|
if (rawJobs && rawJobs.length) {
|
|
jobs = jobs.concat(rawJobs.map(utils_1.array2obj));
|
|
}
|
|
// Important to keep this coercive inequality (!=) instead of strict inequality (!==)
|
|
} while (cursor != '0' && page.length < pageSize);
|
|
// If we get an array of arrays, it means we are paginating a hash
|
|
if (page.length && Array.isArray(page[0])) {
|
|
const result = [];
|
|
for (let index = 0; index < page.length; index++) {
|
|
const [id, value] = page[index];
|
|
try {
|
|
result.push({ id, v: JSON.parse(value) });
|
|
}
|
|
catch (err) {
|
|
result.push({ id, err: err.message });
|
|
}
|
|
}
|
|
return {
|
|
cursor,
|
|
items: result,
|
|
total,
|
|
jobs,
|
|
};
|
|
}
|
|
else {
|
|
return {
|
|
cursor,
|
|
items: page.map(item => ({ id: item })),
|
|
total,
|
|
jobs,
|
|
};
|
|
}
|
|
}
|
|
finishedErrors({ code, jobId, parentKey, command, state, }) {
|
|
let error;
|
|
switch (code) {
|
|
case enums_1.ErrorCode.JobNotExist:
|
|
error = new Error(`Missing key for job ${jobId}. ${command}`);
|
|
break;
|
|
case enums_1.ErrorCode.JobLockNotExist:
|
|
error = new Error(`Missing lock for job ${jobId}. ${command}`);
|
|
break;
|
|
case enums_1.ErrorCode.JobNotInState:
|
|
error = new Error(`Job ${jobId} is not in the ${state} state. ${command}`);
|
|
break;
|
|
case enums_1.ErrorCode.JobPendingChildren:
|
|
error = new Error(`Job ${jobId} has pending dependencies. ${command}`);
|
|
break;
|
|
case enums_1.ErrorCode.ParentJobNotExist:
|
|
error = new Error(`Missing key for parent job ${parentKey}. ${command}`);
|
|
break;
|
|
case enums_1.ErrorCode.JobLockMismatch:
|
|
error = new Error(`Lock mismatch for job ${jobId}. Cmd ${command} from ${state}`);
|
|
break;
|
|
case enums_1.ErrorCode.ParentJobCannotBeReplaced:
|
|
error = new Error(`The parent job ${parentKey} cannot be replaced. ${command}`);
|
|
break;
|
|
case enums_1.ErrorCode.JobBelongsToJobScheduler:
|
|
error = new Error(`Job ${jobId} belongs to a job scheduler and cannot be removed directly. ${command}`);
|
|
break;
|
|
case enums_1.ErrorCode.JobHasFailedChildren:
|
|
error = new errors_1.UnrecoverableError(`Cannot complete job ${jobId} because it has at least one failed child. ${command}`);
|
|
break;
|
|
case enums_1.ErrorCode.SchedulerJobIdCollision:
|
|
error = new Error(`Cannot create job scheduler iteration - job ID already exists. ${command}`);
|
|
break;
|
|
case enums_1.ErrorCode.SchedulerJobSlotsBusy:
|
|
error = new Error(`Cannot create job scheduler iteration - current and next time slots already have jobs. ${command}`);
|
|
break;
|
|
default:
|
|
error = new Error(`Unknown code ${code} error for ${jobId}. ${command}`);
|
|
}
|
|
// Add the code property to the error object
|
|
error.code = code;
|
|
return error;
|
|
}
|
|
}
|
|
exports.Scripts = Scripts;
|
|
function raw2NextJobData(raw) {
|
|
if (raw) {
|
|
const result = [null, raw[1], raw[2], raw[3]];
|
|
if (raw[0]) {
|
|
result[0] = (0, utils_1.array2obj)(raw[0]);
|
|
}
|
|
return result;
|
|
}
|
|
return [];
|
|
}
|
|
//# sourceMappingURL=scripts.js.map
|