Files
simple-mail-cleaner/backend/node_modules/bullmq/dist/cjs/classes/scripts.js
2026-01-22 15:49:12 +01:00

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