/** * Includes all the scripts needed by the queue and jobs. */ import { EventEmitter } from 'events'; import { DependenciesOpts, IQueueBackend, JobJson, MinimalJob, MoveToWaitingChildrenOpts, ParentKeyOpts, RedisClient, MoveToDelayedOpts, RepeatableOptions, RetryJobOpts, RetryOptions, ScriptQueueContext, StreamReadRaw } from '../interfaces'; import { JobsOptions, JobState, JobType, FinishedStatus, FinishedPropValAttribute, KeepJobs, JobProgress } from '../types'; import { IRedisTransaction } from '../interfaces'; import { QueueBaseOptions } from '../interfaces'; import { KeysMap } from './queue-keys'; import { RedisConnection } from './redis-connection'; export type JobData = [JobJson | number, string?]; export declare class RedisQueueBackend extends EventEmitter implements IQueueBackend { connection: RedisConnection; protected readonly name: string; blockingConnection?: RedisConnection; protected ownsConnection: boolean; protected version: string; moveToFinishedKeys: (string | undefined)[]; /** * Resolves once a close has been initiated. Owned by the backend (it owns the * underlying connection(s)). */ closing: Promise | undefined; /** * Internal Redis access context (client, version, keys, …). Built from the * owned connection(s); kept private to this adapter. */ protected queue: ScriptQueueContext; /** * The resolved key prefix (defaults to `bull`). A Redis-specific concept used * to namespace this queue's keys, qualified name and client name. */ protected readonly redisPrefix: string; constructor(connection: RedisConnection, name: string, keys: KeysMap, toKey: (type: string) => string, opts: QueueBaseOptions, blockingConnection?: RedisConnection, ownsConnection?: boolean); /** * Returns a sibling backend bound to a different queue that shares this * backend's connection(s). Used by {@link FlowProducer} to operate on the * many queues that a flow may span over a single connection. The sibling * does not own the connection, so its `close`/`disconnect` are no-ops. */ forQueue(queueName: string, prefix?: string): IQueueBackend; /** * The queue's fully-qualified name (`":"`). This is the * cross-backend logical identifier (e.g. used as a flow parent reference). */ get qualifiedName(): string; /** * The concrete Redis keys for this queue (wait, active, events, …). */ get keys(): KeysMap; /** * Builds a namespaced Redis sub-key of the given `type` * (`"::"`). */ toKey(type: string): string; /** * Parses a Redis flow child key (`"::"`) into its * components. Inverse of {@link toKey}. */ parseNodeKey(key: string): { prefix: string; queueName: string; id: string; }; /** * Builds the Redis client name (`":"`), used * for `CLIENT SETNAME` and worker/queue discovery via `CLIENT LIST`. */ clientName(suffix?: string): string; /** * Normalizes the events of the owned connection(s) into the backend's own * `'ready' | 'error' | 'close'` events. */ private forwardConnectionEvents; /** * Resolves once the backend's underlying connection(s) are ready. */ waitUntilReady(): Promise; /** * Closes the backend and its underlying connection(s). * * The dedicated blocking connection (if any) is closed first so that an * in-flight blocking command (e.g. `bzpopmin`) is interrupted before the * main connection is closed. */ close(force?: boolean): Promise; /** * Forcibly disconnects the backend's underlying connection(s). */ disconnect(): Promise; /** * Sets a human-readable name on the underlying connection (CLIENT SETNAME). * Unsupported-command and shutdown errors are swallowed. */ setName(name: string): Promise; /** * The raw Redis client. Redis-specific escape hatch (used e.g. by * `Queue.client`); not part of {@link IQueueBackend}. */ get client(): Promise; /** * The raw blocking Redis client (a dedicated connection used for the * blocking `waitForJob` primitive), if this backend was created with one. * Redis-specific escape hatch; not part of {@link IQueueBackend}. */ get blockingClient(): Promise | undefined; /** * The detected Redis server version. Redis-specific escape hatch; not part * of {@link IQueueBackend}. */ get redisVersion(): string; /** * The detected datastore flavour (`redis`, `dragonfly`, `valkey`, …). * Redis-specific escape hatch; not part of {@link IQueueBackend}. */ get databaseType(): string; /** * Smallest meaningful block timeout (seconds) given the blocking * connection's capabilities. */ get minimumBlockTimeout(): number; /** * Interrupts the in-flight blocking wait by disconnecting the dedicated * blocking connection. No-op if there is none. */ disconnectBlocking(wait?: boolean): Promise; /** * Re-establishes the dedicated blocking connection after an interrupt. */ reconnectBlocking(): Promise; /** * Executes a registered Lua script on the given Redis client, resolving the * versioned command name (e.g. `addJob:`) so the script * belonging to the current BullMQ version is invoked. * * @param client - The Redis client or pipeline/transaction on which to run the command. * @param commandName - The base name of the Lua script (without version suffix). * @param args - Positional arguments forwarded to the Lua script (keys followed by argv). * @returns The raw result produced by the Lua script. * * @private */ execCommand(client: RedisClient | IRedisTransaction, commandName: string, args: any[]): any; /** * Checks whether a job with the given id is present in the provided queue * state. */ isJobInState(state: string, jobId: string): Promise; protected addDelayedJobArgs(job: JobJson, encodedOpts: any, args: (string | number | Record)[], keysMap?: KeysMap): (string | Buffer)[]; protected addDelayedJob(client: RedisClient | IRedisTransaction, job: JobJson, encodedOpts: any, args: (string | number | Record)[], keys?: KeysMap): Promise; protected addPrioritizedJobArgs(job: JobJson, encodedOpts: any, args: (string | number | Record)[], keysMap?: KeysMap): (string | Buffer)[]; protected addPrioritizedJob(client: RedisClient | IRedisTransaction, job: JobJson, encodedOpts: any, args: (string | number | Record)[], keys?: KeysMap): Promise; protected addParentJobArgs(job: JobJson, encodedOpts: any, args: (string | number | Record)[], keysMap?: KeysMap): (string | Buffer)[]; protected addParentJob(client: RedisClient | IRedisTransaction, job: JobJson, encodedOpts: any, args: (string | number | Record)[], keys?: KeysMap): Promise; protected addStandardJobArgs(job: JobJson, encodedOpts: any, args: (string | number | Record)[], keysMap?: KeysMap): (string | Buffer)[]; protected addStandardJob(client: RedisClient | IRedisTransaction, job: JobJson, encodedOpts: any, args: (string | number | Record)[], keys?: KeysMap): Promise; /** * Low-level Redis adapter helper: queues/executes a single job insert on the * provided client or transaction (pipeline/multi). This is the only place * that needs a connection handle; the public {@link addJob} / {@link addJobs} * operations obtain it from the backend itself. * * Kept public (but outside {@link IQueueBackend}) so that flow producers can * batch inserts across queues onto a shared transaction. */ addJobToTransaction(client: RedisClient | IRedisTransaction, job: JobJson, jobId: string, parentKeyOpts?: ParentKeyOpts, keys?: KeysMap): Promise; addJob(job: JobJson, jobId: string, parentKeyOpts?: ParentKeyOpts): Promise; addJobs(entries: { job: JobJson; jobId: string; parentKeyOpts?: ParentKeyOpts; }[]): Promise; /** * Atomically inserts a whole flow (tree) of jobs that may span multiple * queues, returning one `[error, idOrCode]` tuple per entry in the same * order they were provided. For the Redis adapter this is a single `MULTI` * transaction; another backend would use a single SQL transaction. * * Each entry is self-describing (it carries its own queue `prefix` and * `queueName`), so the operation does not need to be bound to a single * queue's key map. */ addFlow(entries: { jobData: JobJson; jobId: string; parentKeyOpts: ParentKeyOpts; prefix: string; queueName: string; }[]): Promise<[Error | null, string | number][]>; protected pauseArgs(pause: boolean, emitEvent?: boolean): (string | number)[]; pause(pause: boolean): Promise; /** * Removes a deduplication key from Redis so that a new job with the same * deduplication id can be enqueued again. The key is only removed if it * currently maps to the provided `jobId`, preventing races between * producers and finishing jobs. * * @param deduplicationId - The deduplication id whose key should be cleared. * @param jobId - The id of the job that currently owns the dedup key. * @returns `1` if the key was removed, `0` otherwise. * * @private */ removeDeduplicationKey(deduplicationId: string, jobId: string): Promise; /** * Registers a job scheduler and enqueues its next delayed iteration. * The scheduler stores the template data/options so subsequent iterations * can be produced automatically based on the repeat options. * * @param jobSchedulerId - The id that uniquely identifies this scheduler. * @param nextMillis - Timestamp (ms since epoch) for the next iteration. * @param templateData - Serialized template data reused for every iteration. * @param templateOpts - Redis-encoded job options applied to every iteration. * @param opts - Repeat options describing the scheduling pattern. * @param delayedJobOpts - Options applied to the next delayed job that is produced. * @param producerId - Optional id of the job that produced this iteration, used to prevent duplicates. * @returns A tuple of `[jobId, delay]`, where `delay` is the computed delay in milliseconds * for the next iteration. When `delay` is `0`, the job is enqueued immediately. * @throws An error resolved from `finishedErrors` when the Lua script returns a negative status code. * * @private */ addJobScheduler(jobSchedulerId: string, nextMillis: number, templateData: string, templateOpts: JobsOptions, opts: RepeatableOptions, delayedJobOpts: JobsOptions, producerId?: string): Promise<[string, number]>; updateJobSchedulerNextMillis(jobSchedulerId: string, nextMillis: number, templateData: string, delayedJobOpts: JobsOptions, producerId?: string): Promise; removeJobScheduler(jobSchedulerId: string): Promise; protected removeArgs(jobId: string, removeChildren: boolean): (string | number)[]; remove(jobId: string, removeChildren: boolean): Promise; removeUnprocessedChildren(jobId: string): Promise; extendLock(jobId: string, token: string, duration: number, client?: RedisClient | IRedisTransaction): Promise; extendLocks(jobIds: string[], tokens: string[], duration: number): Promise; updateData(job: MinimalJob, data: T): Promise; updateProgress(jobId: string, progress: JobProgress): Promise; addLog(jobId: string, logRow: string, keepLogs?: number): Promise; protected moveToFinishedArgs(job: MinimalJob, val: any, propVal: FinishedPropValAttribute, shouldRemove: undefined | boolean | number | KeepJobs, target: FinishedStatus, token: string, timestamp: number, fetchNext?: boolean, fieldsToUpdate?: Record): (string | number | boolean | Buffer)[]; protected getKeepJobs(shouldRemove: undefined | boolean | number | KeepJobs, workerKeepJobs: undefined | KeepJobs): KeepJobs; moveToFinished(jobId: string, args: (string | number | boolean | Buffer)[]): Promise; private drainArgs; drain(delayed: boolean): Promise; private removeChildDependencyArgs; removeChildDependency(jobId: string, parentKey: string): Promise; private getRangesArgs; getRanges(types: JobType[], start?: number, end?: number, asc?: boolean): Promise<[string][]>; private getJobsArgs; /** * Fetches job ids and their job hashes for the provided states in a single * script, skipping ids whose job hash is missing (for example the deprecated * wait list marker or jobs removed after their id was read). Each returned * entry is a `[jobId, jobHashFields]` tuple grouped per requested type. */ getJobs(types: JobType[], start?: number, end?: number, asc?: boolean): Promise<[string, string[]][][]>; private getCountsArgs; getCounts(types: JobType[]): Promise; protected getCountsPerPriorityArgs(priorities: number[]): (string | number)[]; getCountsPerPriority(priorities: number[]): Promise; protected getDependencyCountsArgs(jobId: string, types: string[]): (string | number)[]; getDependencyCounts(jobId: string, types: string[]): Promise; moveToCompletedArgs(job: MinimalJob, returnvalue: R, removeOnComplete: boolean | number | KeepJobs, token: string, fetchNext?: boolean): (string | number | boolean | Buffer)[]; moveToFailedArgs(job: MinimalJob, failedReason: string, removeOnFailed: boolean | number | KeepJobs, token: string, fetchNext?: boolean, fieldsToUpdate?: Record): (string | number | boolean | Buffer)[]; isFinished(jobId: string, returnValue?: boolean): Promise; getState(jobId: string): Promise; /** * 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. */ changeDelay(jobId: string, delay: number): Promise; private changeDelayArgs; changePriority(jobId: string, priority?: number, lifo?: boolean): Promise; protected changePriorityArgs(jobId: string, priority?: number, lifo?: boolean): (string | number)[]; moveToDelayedArgs(jobId: string, timestamp: number, token: string, delay: number, opts?: MoveToDelayedOpts): (string | number | Buffer)[]; moveToWaitingChildrenArgs(jobId: string, token: string, opts?: MoveToWaitingChildrenOpts): (string | number)[]; isMaxedArgs(): string[]; isMaxed(): Promise; moveToDelayed(jobId: string, timestamp: number, delay: number, token?: string, opts?: MoveToDelayedOpts): Promise; /** * 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. */ moveToWaitingChildren(jobId: string, token: string, opts?: MoveToWaitingChildrenOpts): Promise; getRateLimitTtlArgs(maxJobs?: number): (string | number)[]; getRateLimitTtl(maxJobs?: number): Promise; /** * Remove jobs in a specific state. * * @returns Id jobs from the deleted records. */ cleanJobsByState(state: string, timestamp: number, limit?: number): Promise; getJobSchedulerArgs(id: string): string[]; getJobScheduler(id: string): Promise<[any, string | null]>; isJobScheduler(id: string): Promise; getJobSchedulerData(key: string): Promise>; getJobSchedulersRange(start: number, end: number, asc: boolean): Promise; getJobSchedulersCount(): Promise; retryJobArgs(jobId: string, lifo: boolean, token: string, opts?: MoveToDelayedOpts): (string | number | Buffer)[]; retryJob(jobId: string, lifo: boolean, token?: string, opts?: RetryJobOpts): Promise; protected moveJobsToWaitArgs(state: FinishedStatus | 'delayed', count: number, timestamp: number): (string | number)[]; retryFinishedJobs(state?: FinishedStatus, count?: number, timestamp?: number): Promise; promoteJobs(count?: number): Promise; /** * 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 */ retryFinishedJob(job: MinimalJob, state: 'failed' | 'completed', opts?: RetryOptions): Promise; getMetrics(type: 'completed' | 'failed', start?: number, end?: number): Promise<[string[], string[], number]>; getClientList(): Promise; moveToActive(token: string, name?: string): Promise; promote(jobId: string): Promise; protected moveStalledJobsToWaitArgs(): (string | number)[]; /** * 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. */ moveStalledJobsToWait(): Promise; /** * 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 */ moveJobFromActiveToWait(jobId: string, token?: string): Promise; obliterate(opts: { force: boolean; count: number; }): Promise; /** * Paginate a set or hash keys. * @param opts - options to define the pagination behaviour * */ paginate(key: string, opts: { start: number; end: number; fetchJobs?: boolean; }): Promise<{ cursor: string; items: { id: string; v?: any; err?: string; }[]; total: number; jobs?: JobJson[]; }>; finishedErrors({ code, jobId, parentKey, command, state, }: { code: number; jobId?: string; parentKey?: string; command: string; state?: string; }): Error; /** * Low-level Redis adapter helper: atomically check-and-delete a single batch * of candidate orphaned jobs. Driven by {@link removeOrphanedJobs}. */ protected removeOrphanedJobsBatch(candidateJobIds: string[], stateKeySuffixes: string[], jobSubKeySuffixes: string[]): Promise; removeOrphanedJobs(count?: number, limit?: number): Promise; moveToCompleted(job: MinimalJob, returnValue: R, removeOnComplete: boolean | number | KeepJobs, token: string, fetchNext: boolean): Promise<{ result: void | any[]; finishedOn: number; }>; moveToFailed(job: MinimalJob, failedReason: string, removeOnFail: boolean | number | KeepJobs, token: string, fetchNext: boolean, fieldsToUpdate?: Record): Promise<{ result: void | any[]; finishedOn: number; }>; getJobData(jobId: string): Promise; getDeduplicationJobId(deduplicationId: string): Promise; getJobLogs(jobId: string, start: number, end: number, asc: boolean): Promise<{ logs: string[]; count: number; }>; clearLogs(jobId: string, keepLogs?: number): Promise; getProcessedChildrenValues(jobId: string): Promise>; getIgnoredChildrenFailures(jobId: string): Promise>; getDependencies(jobId: string, opts?: DependenciesOpts): Promise<{ nextFailedCursor?: number; failed?: string[]; nextIgnoredCursor?: number; ignored?: Record; nextProcessedCursor?: number; processed?: Record; nextUnprocessedCursor?: number; unprocessed?: string[]; }>; setQueueMeta(values: Record): Promise; getQueueMetaField(field: string): Promise; getQueueMetaFields(fields: string[]): Promise<(string | null)[]>; getQueueMeta(): Promise>; removeQueueMetaFields(fields: string[]): Promise; hasQueueMetaField(field: string): Promise; setRateLimit(expireTimeMs: number): Promise; removeRateLimitKey(): Promise; removeDeprecatedPriorityKey(): Promise; deleteDeduplicationKey(deduplicationId: string): Promise; trimEvents(maxLength: number): Promise; waitForJob(blockTimeout: number): Promise<{ member: string; score: number; } | null>; publishEvent(fields: Record, maxEvents: number): Promise; readEvents(id: string, blockTimeout: number): Promise; } export declare function raw2NextJobData(raw: any[]): any[];