{"version":3,"file":"index.cjs","names":["BaseAdapter","BaseAdapter","safeJsonParse","status: string | undefined","valid: Array<Status>","enabled: boolean","name: string","path","fs","err: unknown","delayMs: number | undefined","JobEntity","data: T","jobs: Array<JobEntity<T>>","out: Array<ReservedJob<T>>","idSets: string[]","pipe","rows","uniqueIds: string[]","sleep","nextRunTime: number","CronParser","defaultBackoff: BackoffStrategy","job","job: ReservedJob<T> | void","inProgressItem: { job: ReservedJob<T>; ts: number }","hbTimer: NodeJS.Timeout | undefined","heartbeatDelayTimer: NodeJS.Timeout | undefined"],"sources":["../node_modules/@bull-board/api/dist/queueAdapters/base.js","../src/adapters/groupmq-bullboard-adapter.ts","../src/helpers.ts","../src/job.ts","../src/logger.ts","../src/lua/loader.ts","../src/queue.ts","../src/async-fifo-queue.ts","../src/worker.ts"],"sourcesContent":["\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.BaseAdapter = void 0;\nclass BaseAdapter {\n    constructor(type, options = {}) {\n        this.formatters = new Map();\n        this._visibilityGuard = () => true;\n        this.readOnlyMode = options.readOnlyMode === true;\n        this.allowRetries = this.readOnlyMode ? false : options.allowRetries !== false;\n        this.allowCompletedRetries = this.allowRetries && options.allowCompletedRetries !== false;\n        this.prefix = options.prefix || '';\n        this.delimiter = options.delimiter || '';\n        this.description = options.description || '';\n        this.displayName = options.displayName || '';\n        this.type = type;\n        this.externalJobUrl = options.externalJobUrl;\n    }\n    getDescription() {\n        return this.description;\n    }\n    getDisplayName() {\n        return this.displayName;\n    }\n    setFormatter(field, formatter) {\n        this.formatters.set(field, formatter);\n    }\n    format(field, data, defaultValue = data) {\n        const fieldFormatter = this.formatters.get(field);\n        return typeof fieldFormatter === 'function' ? fieldFormatter(data) : defaultValue;\n    }\n    setVisibilityGuard(guard) {\n        this._visibilityGuard = guard;\n    }\n    isVisible(request) {\n        return this._visibilityGuard(request);\n    }\n}\nexports.BaseAdapter = BaseAdapter;\n//# sourceMappingURL=base.js.map","import { BaseAdapter } from '@bull-board/api/dist/queueAdapters/base.js';\nimport type {\n  JobCounts as BullBoardJobCounts,\n  JobStatus as BullBoardJobStatus,\n  QueueJob as BullBoardQueueJob,\n  Status as BullBoardStatus,\n  QueueType,\n} from '@bull-board/api/typings/app';\nimport type { Job } from '../job';\nimport type { Queue } from '../queue';\n\nexport type GroupMQBullBoardAdapterOptions = {\n  readOnlyMode?: boolean;\n  prefix?: string;\n  delimiter?: string;\n  description?: string;\n  displayName?: string;\n};\n\nexport class BullBoardGroupMQAdapter<T = any> extends BaseAdapter {\n  private queue: Queue<T>;\n  private options: GroupMQBullBoardAdapterOptions;\n\n  constructor(queue: Queue<T>, options: GroupMQBullBoardAdapterOptions = {}) {\n    const libName = queue.namespace;\n    super(libName as QueueType, options);\n    this.queue = queue;\n    this.options = options;\n  }\n\n  // ------------ Metadata ------------\n  public getDescription(): string {\n    return this.options.description || '';\n  }\n\n  public getDisplayName(): string {\n    return this.options.displayName || '';\n  }\n\n  public getName(): string {\n    const prefix = this.options.prefix || '';\n    const delimiter = this.options.delimiter || '';\n    return `${prefix}${delimiter}${this.queue.rawNamespace}`.replace(\n      /(^[\\s:]+)|([\\s:]+$)/g,\n      '',\n    );\n  }\n\n  public async getRedisInfo(): Promise<string> {\n    return this.queue.redis.info();\n  }\n\n  // ------------ Getters ------------\n  public async getJob(\n    id: string,\n  ): Promise<BullBoardQueueJob | undefined | null> {\n    return (await this.queue.getJob(id)) as unknown as BullBoardQueueJob;\n  }\n\n  public async getJobs(\n    jobStatuses: BullBoardJobStatus[],\n    start?: number,\n    end?: number,\n  ): Promise<BullBoardQueueJob[]> {\n    const jobs = await this.queue.getJobsByStatus(\n      jobStatuses as any,\n      start,\n      end,\n    );\n    // QueueJob's update and updateData methods mismatch Record<string, any> vs T\n    return jobs as Omit<Job<T>, 'update' | 'updateData'>[];\n  }\n\n  public async getJobCounts(): Promise<BullBoardJobCounts> {\n    const base = await this.queue.getJobCounts();\n    const counts: BullBoardJobCounts = {\n      latest: 0,\n      active: base.active,\n      waiting: base.waiting,\n      'waiting-children': base['waiting-children'],\n      prioritized: base.prioritized,\n      completed: base.completed,\n      failed: base.failed,\n      delayed: base.delayed,\n      paused: base.paused,\n    } as BullBoardJobCounts;\n    return counts;\n  }\n\n  public async getJobLogs(_id: string): Promise<string[]> {\n    return [];\n  }\n\n  public getStatuses(): BullBoardStatus[] {\n    return [\n      'latest',\n      'active',\n      'waiting',\n      'waiting-children',\n      'prioritized',\n      'completed',\n      'failed',\n      'delayed',\n      'paused',\n    ];\n  }\n\n  public getJobStatuses(): BullBoardJobStatus[] {\n    return [\n      'active',\n      'waiting',\n      'waiting-children',\n      'prioritized',\n      'completed',\n      'failed',\n      'delayed',\n      'paused',\n    ];\n  }\n\n  // ------------ Mutations (read-only stubs) ------------\n  private assertWritable(): void {\n    if (this.options.readOnlyMode) {\n      throw new Error(\n        'This adapter is in read-only mode. Mutations are disabled.',\n      );\n    }\n  }\n\n  public async clean(jobStatus: any, graceTimeMs: number): Promise<void> {\n    this.assertWritable();\n    // Align with BullMQ adapter: delegate to queue.clean\n    if (\n      jobStatus !== 'completed' &&\n      jobStatus !== 'failed' &&\n      jobStatus !== 'delayed'\n    )\n      return;\n    await this.queue.clean(graceTimeMs, Number.MAX_SAFE_INTEGER, jobStatus);\n  }\n\n  public async addJob(\n    _name: string,\n    data: any,\n    options: any,\n  ): Promise<BullBoardQueueJob> {\n    this.assertWritable();\n    const job = await this.queue.add({\n      groupId: options.groupId ?? Math.random().toString(36).substring(2, 15),\n      data: data,\n      ...options,\n    });\n    // QueueJob's update and updateData methods mismatch Record<string, any> vs T\n    return job as Omit<Job<T>, 'update' | 'updateData'>;\n  }\n\n  public async isPaused(): Promise<boolean> {\n    return this.queue.isPaused();\n  }\n\n  public async pause(): Promise<void> {\n    this.assertWritable();\n    await this.queue.pause();\n  }\n\n  public async resume(): Promise<void> {\n    this.assertWritable();\n    await this.queue.resume();\n  }\n\n  public async empty(): Promise<void> {\n    this.assertWritable();\n    throw new Error('Not implemented');\n  }\n\n  public async promoteAll(): Promise<void> {\n    this.assertWritable();\n    throw new Error('Not implemented');\n  }\n}\n","import type { Queue } from './queue';\nimport type { Worker } from './worker';\n\n/**\n * Wait for a queue to become empty\n * @param queue The queue to monitor\n * @param timeoutMs Maximum time to wait (default: 60 seconds)\n * @returns Promise that resolves when queue is empty or timeout is reached\n */\nexport async function waitForQueueToEmpty(\n  queue: Queue,\n  timeoutMs = 60_000,\n): Promise<boolean> {\n  return queue.waitForEmpty(timeoutMs);\n}\n\n/**\n * Get status of all workers\n */\nexport function getWorkersStatus<T = any>(\n  workers: Worker<T>[],\n): {\n  total: number;\n  processing: number;\n  idle: number;\n  workers: Array<{\n    index: number;\n    isProcessing: boolean;\n    currentJob?: {\n      jobId: string;\n      groupId: string;\n      processingTimeMs: number;\n    };\n  }>;\n} {\n  const workersStatus = workers.map((worker, index) => {\n    const currentJob = worker.getCurrentJob();\n    return {\n      index,\n      isProcessing: worker.isProcessing(),\n      currentJob: currentJob\n        ? {\n            jobId: currentJob.job.id,\n            groupId: currentJob.job.groupId,\n            processingTimeMs: currentJob.processingTimeMs,\n          }\n        : undefined,\n    };\n  });\n\n  const processing = workersStatus.filter((w) => w.isProcessing).length;\n  const idle = workersStatus.length - processing;\n\n  return {\n    total: workers.length,\n    processing,\n    idle,\n    workers: workersStatus,\n  };\n}\n","import type { Queue, ReservedJob } from './queue';\nimport type { Status } from './status';\n\nexport class Job<T = any> {\n  public readonly queue: Queue<T>;\n  public readonly id: string;\n  public readonly name: string;\n  public readonly data: T;\n  public readonly groupId: string;\n  public readonly attemptsMade: number;\n  public readonly opts: { attempts: number; delay?: number };\n  public readonly processedOn?: number;\n  public readonly finishedOn?: number;\n  public readonly failedReason: string;\n  public readonly stacktrace?: string;\n  public readonly returnvalue?: any;\n  public readonly timestamp: number; // ms\n  public readonly orderMs?: number;\n  public readonly status: Status | 'unknown';\n\n  constructor(args: {\n    queue: Queue<T>;\n    id: string;\n    name?: string;\n    data: T;\n    groupId: string;\n    attemptsMade: number;\n    opts: { attempts: number; delay?: number };\n    processedOn?: number;\n    finishedOn?: number;\n    failedReason?: string;\n    stacktrace?: string;\n    returnvalue?: any;\n    timestamp: number;\n    orderMs?: number;\n    status?: Status | 'unknown';\n  }) {\n    this.queue = args.queue;\n    this.id = args.id;\n    this.name = args.name ?? 'groupmq';\n    this.data = args.data;\n    this.groupId = args.groupId;\n    this.attemptsMade = args.attemptsMade;\n    this.opts = args.opts;\n    this.processedOn = args.processedOn;\n    this.finishedOn = args.finishedOn;\n    this.failedReason = args.failedReason as any;\n    this.stacktrace = args.stacktrace as any;\n    this.returnvalue = args.returnvalue;\n    this.timestamp = args.timestamp;\n    this.orderMs = args.orderMs;\n    this.status = args.status ?? 'unknown';\n  }\n\n  async getState(): Promise<\n    Status | 'stuck' | 'waiting-children' | 'prioritized' | 'unknown'\n  > {\n    return this.status ?? 'unknown';\n  }\n\n  toJSON() {\n    return {\n      id: this.id,\n      name: this.name,\n      data: this.data,\n      groupId: this.groupId,\n      attemptsMade: this.attemptsMade,\n      opts: this.opts,\n      processedOn: this.processedOn,\n      finishedOn: this.finishedOn,\n      failedReason: this.failedReason,\n      stacktrace: this.stacktrace ? [this.stacktrace] : null,\n      returnvalue: this.returnvalue,\n      timestamp: this.timestamp,\n      orderMs: this.orderMs,\n      status: this.status,\n      progress: 0, // Default progress value\n    };\n  }\n\n  changeDelay(newDelay: number): Promise<boolean> {\n    return this.queue.changeDelay(this.id, newDelay);\n  }\n\n  async promote(): Promise<void> {\n    await this.queue.promote(this.id);\n  }\n\n  async remove(): Promise<void> {\n    await this.queue.remove(this.id);\n  }\n\n  async retry(_state?: Extract<Status, 'completed' | 'failed'>): Promise<void> {\n    await this.queue.retry(this.id);\n  }\n\n  async updateData(jobData: T): Promise<void> {\n    await this.queue.updateData(this.id, jobData);\n  }\n\n  async update(jobData: T): Promise<void> {\n    await this.updateData(jobData);\n  }\n\n  static fromReserved<T = any>(\n    queue: Queue<T>,\n    reserved: ReservedJob<T>,\n    meta?: {\n      processedOn?: number;\n      finishedOn?: number;\n      failedReason?: string;\n      stacktrace?: string;\n      returnvalue?: any;\n      status?: Status | string;\n      delayMs?: number;\n    },\n  ): Job<T> {\n    return new Job<T>({\n      queue,\n      id: reserved.id,\n      name: 'groupmq',\n      data: reserved.data as T,\n      groupId: reserved.groupId,\n      attemptsMade: reserved.attempts,\n      opts: {\n        attempts: reserved.maxAttempts,\n        delay: meta?.delayMs,\n      },\n      processedOn: meta?.processedOn,\n      finishedOn: meta?.finishedOn,\n      failedReason: meta?.failedReason,\n      stacktrace: meta?.stacktrace,\n      returnvalue: meta?.returnvalue,\n      timestamp: reserved.timestamp ? reserved.timestamp : Date.now(),\n      orderMs: reserved.orderMs,\n      status: coerceStatus(meta?.status as any),\n    });\n  }\n\n  /**\n   * Create a Job from raw Redis hash data with optional known status\n   * This avoids extra Redis lookups when status is already known\n   */\n  static fromRawHash<T = any>(\n    queue: Queue<T>,\n    id: string,\n    raw: Record<string, string>,\n    knownStatus?: Status | 'unknown',\n  ): Job<T> {\n    const groupId = raw.groupId ?? '';\n    const payload = raw.data ? safeJsonParse(raw.data) : null;\n    const attempts = raw.attempts ? parseInt(raw.attempts, 10) : 0;\n    const maxAttempts = raw.maxAttempts\n      ? parseInt(raw.maxAttempts, 10)\n      : queue.maxAttemptsDefault;\n    const timestampMs = raw.timestamp ? parseInt(raw.timestamp, 10) : 0;\n    const orderMs = raw.orderMs ? parseInt(raw.orderMs, 10) : undefined;\n    const delayUntil = raw.delayUntil ? parseInt(raw.delayUntil, 10) : 0;\n    const processedOn = raw.processedOn\n      ? parseInt(raw.processedOn, 10)\n      : undefined;\n    const finishedOn = raw.finishedOn\n      ? parseInt(raw.finishedOn, 10)\n      : undefined;\n    const failedReason =\n      (raw.failedReason ?? raw.lastErrorMessage) || undefined;\n    const stacktrace = (raw.stacktrace ?? raw.lastErrorStack) || undefined;\n    const returnvalue = raw.returnvalue\n      ? safeJsonParse(raw.returnvalue)\n      : undefined;\n\n    return new Job<T>({\n      queue,\n      id,\n      name: 'groupmq',\n      data: payload as T,\n      groupId,\n      attemptsMade: attempts,\n      opts: {\n        attempts: maxAttempts,\n        delay:\n          delayUntil && delayUntil > Date.now()\n            ? delayUntil - Date.now()\n            : undefined,\n      },\n      processedOn,\n      finishedOn,\n      failedReason,\n      stacktrace,\n      returnvalue,\n      timestamp: timestampMs || Date.now(),\n      orderMs,\n      status: knownStatus ?? coerceStatus(raw.status as any),\n    });\n  }\n\n  static async fromStore<T = any>(\n    queue: Queue<T>,\n    id: string,\n  ): Promise<Job<T>> {\n    const jobKey = `${queue.namespace}:job:${id}`;\n    const raw = await queue.redis.hgetall(jobKey);\n    if (!raw || Object.keys(raw).length === 0) {\n      throw new Error(`Job ${id} not found`);\n    }\n\n    const groupId = raw.groupId ?? '';\n    const payload = raw.data ? safeJsonParse(raw.data) : null;\n    const attempts = raw.attempts ? parseInt(raw.attempts, 10) : 0;\n    const maxAttempts = raw.maxAttempts\n      ? parseInt(raw.maxAttempts, 10)\n      : queue.maxAttemptsDefault;\n    const timestampMs = raw.timestamp ? parseInt(raw.timestamp, 10) : 0;\n    const orderMs = raw.orderMs ? parseInt(raw.orderMs, 10) : undefined;\n    const delayUntil = raw.delayUntil ? parseInt(raw.delayUntil, 10) : 0;\n    const processedOn = raw.processedOn\n      ? parseInt(raw.processedOn, 10)\n      : undefined;\n    const finishedOn = raw.finishedOn\n      ? parseInt(raw.finishedOn, 10)\n      : undefined;\n    const failedReason =\n      (raw.failedReason ?? raw.lastErrorMessage) || undefined;\n    const stacktrace = (raw.stacktrace ?? raw.lastErrorStack) || undefined;\n    const returnvalue = raw.returnvalue\n      ? safeJsonParse(raw.returnvalue)\n      : undefined;\n\n    // Determine status\n    const [inProcessing, inDelayed] = await Promise.all([\n      queue.redis.zscore(`${queue.namespace}:processing`, id),\n      queue.redis.zscore(`${queue.namespace}:delayed`, id),\n    ]);\n\n    let status: string | undefined = raw.status;\n    if (inProcessing !== null) status = 'active';\n    else if (inDelayed !== null) status = 'delayed';\n    else if (groupId) {\n      const inGroup = await queue.redis.zscore(\n        `${queue.namespace}:g:${groupId}`,\n        id,\n      );\n      if (inGroup !== null) status = 'waiting';\n    }\n\n    return new Job<T>({\n      queue,\n      id,\n      name: 'groupmq',\n      data: payload as T,\n      groupId,\n      attemptsMade: attempts,\n      opts: {\n        attempts: maxAttempts,\n        delay:\n          delayUntil && delayUntil > Date.now()\n            ? delayUntil - Date.now()\n            : undefined,\n      },\n      processedOn,\n      finishedOn,\n      failedReason,\n      stacktrace,\n      returnvalue,\n      timestamp: timestampMs || Date.now(),\n      orderMs,\n      status: coerceStatus(status as any),\n    });\n  }\n}\n\nfunction safeJsonParse(input: string): any {\n  try {\n    return JSON.parse(input);\n  } catch (_e) {\n    return null;\n  }\n}\n\nfunction coerceStatus(input?: string | Status): Status | 'unknown' {\n  const valid: Array<Status> = [\n    'latest',\n    'active',\n    'waiting',\n    'waiting-children',\n    'prioritized',\n    'completed',\n    'failed',\n    'delayed',\n    'paused',\n  ];\n  if (!input) return 'unknown';\n  if (valid.includes(input as Status)) return input as Status;\n  return 'unknown';\n}\n","// Generic logger interface that works with different logger implementations\nexport interface LoggerInterface {\n  warn(...args: any[]): void;\n  info(...args: any[]): void;\n  error(...args: any[]): void;\n  debug(...args: any[]): void;\n}\n\n// Default logger implementation\nexport class Logger implements LoggerInterface {\n  constructor(\n    private readonly enabled: boolean,\n    private readonly name: string,\n  ) {}\n\n  debug(...args: any[]) {\n    if (this.enabled) {\n      console.debug(`[${this.name}]`, ...args);\n    }\n  }\n\n  info(...args: any[]) {\n    if (this.enabled) {\n      console.log(`[${this.name}]`, ...args);\n    }\n  }\n\n  warn(...args: any[]) {\n    if (this.enabled) {\n      console.warn(`⚠️ [${this.name}]`, ...args);\n    }\n  }\n\n  error(...args: any[]) {\n    if (this.enabled) {\n      console.error(`💥 [${this.name}]`, ...args);\n    }\n  }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type Redis from 'ioredis';\n\nexport type ScriptName =\n  | 'enqueue'\n  | 'enqueue-batch'\n  | 'reserve'\n  | 'reserve-batch'\n  | 'reserve-atomic'\n  | 'complete'\n  | 'complete-and-reserve-next-with-metadata'\n  | 'complete-with-metadata'\n  | 'retry'\n  | 'heartbeat'\n  | 'cleanup'\n  | 'promote-delayed-jobs'\n  | 'promote-delayed-one'\n  | 'promote-staged'\n  | 'change-delay'\n  | 'get-active-count'\n  | 'get-waiting-count'\n  | 'get-delayed-count'\n  | 'get-active-jobs'\n  | 'get-waiting-jobs'\n  | 'get-delayed-jobs'\n  | 'get-unique-groups'\n  | 'get-unique-groups-count'\n  | 'cleanup-poisoned-group'\n  | 'remove'\n  | 'clean-status'\n  | 'is-empty'\n  | 'dead-letter'\n  | 'record-job-result'\n  | 'check-stalled';\n\nconst cacheByClient = new WeakMap<Redis, Map<ScriptName, string>>();\n\nfunction scriptPath(name: ScriptName): string {\n  // Resolve Lua script path in both dev (TS) and prod (dist) builds.\n  const currentDir = path.dirname(fileURLToPath(import.meta.url));\n\n  const candidates = [\n    // Dev: alongside loader.ts (src/lua/<name>.lua)\n    path.join(currentDir, `${name}.lua`),\n    // Prod: dist/lua/<name>.lua adjacent to built bundle directory\n    path.join(currentDir, 'lua', `${name}.lua`),\n  ];\n\n  for (const candidate of candidates) {\n    if (fs.existsSync(candidate)) return candidate;\n  }\n  // Fallback to first candidate; read will throw a helpful error if missing\n  return candidates[0];\n}\n\nexport async function loadScript(\n  client: Redis,\n  name: ScriptName,\n): Promise<string> {\n  let map = cacheByClient.get(client);\n  if (!map) {\n    map = new Map();\n    cacheByClient.set(client, map);\n  }\n  const cached = map.get(name);\n  if (cached) return cached;\n\n  const file = scriptPath(name);\n  const lua = fs.readFileSync(file, 'utf8');\n  const sha = await (client as any).script('load', lua);\n  map.set(name, sha as string);\n  return sha as string;\n}\n\nexport async function evalScript<T = any>(\n  client: Redis,\n  name: ScriptName,\n  argv: Array<string>,\n  numKeys: number,\n): Promise<T> {\n  try {\n    const sha = await loadScript(client, name);\n    return await (client as any).evalsha(sha, numKeys, ...argv);\n  } catch (err: unknown) {\n    const msg =\n      err && typeof err === 'object' && 'message' in err\n        ? String((err as { message: unknown }).message)\n        : String(err);\n    if (!/NOSCRIPT|no matching script/i.test(msg)) throw err;\n    const map = cacheByClient.get(client);\n    if (map) map.delete(name);\n    const sha = await loadScript(client, name);\n    return await (client as any).evalsha(sha, numKeys, ...argv);\n  }\n}\n","import { randomUUID } from 'node:crypto';\nimport CronParser from 'cron-parser';\nimport type Redis from 'ioredis';\nimport { type Job, Job as JobEntity } from './job';\nimport { Logger, type LoggerInterface } from './logger';\nimport { evalScript } from './lua/loader';\nimport type { Status } from './status';\n\n/**\n * Options for configuring a GroupMQ queue\n */\nexport type QueueOptions = {\n  /**\n   * Logger configuration for queue operations and debugging.\n   *\n   * @default false (no logging)\n   * @example true // Enable basic logging\n   * @example customLogger // Use custom logger instance\n   *\n   * **When to enable:**\n   * - Development: For debugging queue operations\n   * - Production monitoring: For operational insights\n   * - Troubleshooting: When investigating performance issues\n   */\n  logger?: LoggerInterface | boolean;\n\n  /**\n   * Redis client instance for queue operations.\n   * Should be a connected ioredis client.\n   *\n   * @example new Redis('redis://localhost:6379')\n   * @example new Redis({ host: 'localhost', port: 6379, db: 0 })\n   */\n  redis: Redis;\n\n  /**\n   * Unique namespace for this queue. Used to separate different queues in the same Redis instance.\n   * Should be unique across your application to avoid conflicts.\n   *\n   * @example 'email-queue'\n   * @example 'user-notifications'\n   * @example 'data-processing'\n   */\n  namespace: string;\n\n  /**\n   * Maximum time in milliseconds a job can run before being considered failed.\n   * Jobs that exceed this timeout will be retried or moved to failed state.\n   *\n   * @default 30000 (30 seconds)\n   * @example 60000 // 1 minute timeout\n   * @example 300000 // 5 minute timeout for long-running jobs\n   *\n   * **When to adjust:**\n   * - Long-running jobs: Increase (5-30 minutes)\n   * - Short jobs: Decrease (5-15 seconds) for faster failure detection\n   * - External API calls: Consider API timeout + buffer\n   * - Database operations: Consider query timeout + buffer\n   */\n  jobTimeoutMs?: number;\n\n  /**\n   * Default maximum number of retry attempts for failed jobs.\n   * Can be overridden per job or per worker.\n   *\n   * @default 3\n   * @example 5 // Retry failed jobs up to 5 times\n   * @example 1 // Fail fast with minimal retries\n   *\n   * **When to override:**\n   * - Critical jobs: Increase retries\n   * - Non-critical jobs: Decrease retries\n   * - Idempotent operations: Can safely retry more\n   * - External API calls: Consider API reliability\n   */\n  maxAttempts?: number;\n\n  /**\n   * Maximum number of groups to scan when looking for available jobs.\n   * Higher values may find more jobs but use more Redis resources.\n   *\n   * @default 20\n   * @example 50 // Scan more groups for better job distribution\n   * @example 10 // Reduce Redis load for simple queues\n   *\n   * **When to adjust:**\n   * - Many groups: Increase (50-100) for better job distribution\n   * - Few groups: Decrease (5-10) to reduce Redis overhead\n   * - High job volume: Increase for better throughput\n   * - Resource constraints: Decrease to reduce Redis load\n   */\n  reserveScanLimit?: number;\n\n  /**\n   * Maximum number of completed jobs to retain for inspection.\n   * Jobs beyond this limit are automatically cleaned up.\n   *\n   * @default 0 (no retention)\n   * @example 100 // Keep last 100 completed jobs\n   * @example 1000 // Keep last 1000 completed jobs for analysis\n   *\n   * **When to adjust:**\n   * - Debugging: Increase to investigate issues\n   * - Memory constraints: Decrease to reduce Redis memory usage\n   * - Compliance: Increase for audit requirements\n   */\n  keepCompleted?: number;\n\n  /**\n   * Maximum number of failed jobs to retain for inspection.\n   * Jobs beyond this limit are automatically cleaned up.\n   *\n   * @default 0 (no retention)\n   * @example 1000 // Keep last 1000 failed jobs for analysis\n   * @example 10000 // Keep more failed jobs for trend analysis\n   *\n   * **When to adjust:**\n   * - Error analysis: Increase to investigate failure patterns\n   * - Memory constraints: Decrease to reduce Redis memory usage\n   * - Compliance: Increase for audit requirements\n   */\n  keepFailed?: number;\n\n  /**\n   * TTL for scheduler lock in milliseconds.\n   * Prevents multiple schedulers from running simultaneously.\n   *\n   * @default 1500\n   * @example 3000 // 3 seconds for slower environments\n   * @example 1000 // 1 second for faster environments\n   */\n  schedulerLockTtlMs?: number;\n\n  /**\n   * Ordering delay in milliseconds. When set, jobs with orderMs will be staged\n   * and promoted only after orderMs + orderingDelayMs to ensure proper ordering\n   * even when producers are out of sync.\n   *\n   * @default 0 (no staging, jobs processed immediately)\n   * @example 200 // Wait 200ms to ensure all jobs arrive in order\n   * @example 1000 // Wait 1 second for strict ordering\n   *\n   * **When to use:**\n   * - Distributed producers with clock drift\n   * - Strict timestamp ordering required\n   * - Network latency between producers\n   *\n   * **Note:** Only applies to jobs with orderMs set. Jobs without orderMs\n   * are never staged.\n   */\n  orderingDelayMs?: number;\n\n  /**\n   * Enable automatic job batching to reduce Redis load.\n   * Jobs are buffered in memory and sent in batches.\n   *\n   * @default undefined (disabled)\n   * @example true // Enable with defaults (size: 10, maxWaitMs: 10)\n   * @example { size: 20, maxWaitMs: 5 } // Custom configuration\n   *\n   * **Trade-offs:**\n   * - ✅ 10x fewer Redis calls (huge performance win)\n   * - ✅ Higher throughput (5-10x improvement)\n   * - ✅ Lower latency per add() call\n   * - ⚠️ Jobs buffered in memory briefly before Redis\n   * - ⚠️ If process crashes during batch window, those jobs are lost\n   *\n   * **When to use:**\n   * - High job volume (>100 jobs/s)\n   * - Using orderingDelayMs (already buffering)\n   * - Network latency is a bottleneck\n   * - Acceptable risk of losing jobs during crash (e.g., non-critical jobs)\n   *\n   * **When NOT to use:**\n   * - Critical jobs that must be persisted immediately\n   * - Very low volume (<10 jobs/s)\n   * - Zero tolerance for data loss\n   *\n   * **Configuration:**\n   * - size: Maximum jobs per batch (default: 10)\n   * - maxWaitMs: Maximum time to wait before flushing (default: 10)\n   *\n   * **Safety:**\n   * - Keep maxWaitMs small (10ms = very low risk)\n   * - Batches are flushed on queue.close()\n   * - Consider graceful shutdown handling\n   */\n  autoBatch?:\n    | boolean\n    | {\n        size?: number;\n        maxWaitMs?: number;\n      };\n};\n\n/**\n * Configuration for repeating jobs\n */\nexport type RepeatOptions =\n  | {\n      /**\n       * Repeat interval in milliseconds. Job will be created every N milliseconds.\n       *\n       * @example 60000 // Every minute\n       * @example 3600000 // Every hour\n       * @example 86400000 // Every day\n       *\n       * When to use:\n       * - Simple intervals: Use for regular, predictable schedules\n       * - High frequency: Good for sub-hour intervals\n       * - Performance: More efficient than cron for simple intervals\n       */\n      every: number;\n    }\n  | {\n      /**\n       * Cron pattern for complex scheduling. Uses standard cron syntax with seconds.\n       * Format: second minute hour day month dayOfWeek\n       *\n       * When to use:\n       * - Complex schedules: Business hours, specific days, etc.\n       * - Low frequency: Good for daily, weekly, monthly schedules\n       * - Business logic: Align with business requirements\n       *\n       * Cron format uses standard syntax with seconds precision.\n       */\n      pattern: string;\n    };\n\n/**\n * Options for adding a job to the queue\n *\n * @template T The type of data to store in the job\n */\nexport type AddOptions<T> = {\n  /**\n   * Group ID for this job. Jobs with the same groupId are processed sequentially (FIFO).\n   * Only one job per group can be processed at a time.\n   *\n   * @example 'user-123' // All jobs for user 123\n   * @example 'email-notifications' // All email jobs\n   * @example 'order-processing' // All order-related jobs\n   *\n   * **Best practices:**\n   * - Use meaningful group IDs (user ID, resource ID, etc.)\n   * - Keep group IDs consistent for related jobs\n   * - Avoid too many unique groups (can impact performance)\n   */\n  groupId: string;\n\n  /**\n   * The data payload for this job. Can be any serializable data.\n   *\n   * @example { userId: 123, email: 'user@example.com' }\n   * @example { orderId: 'order-456', items: [...] }\n   * @example 'simple string data'\n   */\n  data: T;\n\n  /**\n   * Custom ordering timestamp in milliseconds. Jobs are processed in orderMs order within each group.\n   * If not provided, uses current timestamp (Date.now()).\n   *\n   * @default Date.now()\n   * @example Date.now() + 5000 // Process 5 seconds from now\n   * @example 1640995200000 // Specific timestamp\n   *\n   * **When to use:**\n   * - Delayed processing: Set future timestamp\n   * - Priority ordering: Use lower timestamps for higher priority\n   * - Batch processing: Group related jobs with same timestamp\n   */\n  orderMs?: number;\n\n  /**\n   * Maximum number of retry attempts for this specific job.\n   * Overrides the queue's default maxAttempts setting.\n   *\n   * @default queue.maxAttemptsDefault\n   * @example 5 // Retry this job up to 5 times\n   * @example 1 // Fail fast with no retries\n   *\n   * **When to override:**\n   * - Critical jobs: Increase retries\n   * - Non-critical jobs: Decrease retries\n   * - Idempotent operations: Can safely retry more\n   * - External API calls: Consider API reliability\n   */\n  maxAttempts?: number;\n\n  /**\n   * Delay in milliseconds before this job becomes available for processing.\n   * Alternative to using orderMs for simple delays.\n   *\n   * @example 5000 // Process after 5 seconds\n   * @example 300000 // Process after 5 minutes\n   *\n   * **When to use:**\n   * - Simple delays: Use delay instead of orderMs\n   * - Rate limiting: Delay jobs to spread load\n   * - Retry backoff: Delay retry attempts\n   */\n  delay?: number;\n\n  /**\n   * Specific time when this job should be processed.\n   * Can be a Date object or timestamp in milliseconds.\n   *\n   * @example new Date('2024-01-01T12:00:00Z')\n   * @example Date.now() + 3600000 // 1 hour from now\n   *\n   * **When to use:**\n   * - Scheduled processing: Process at specific time\n   * - Business hours: Schedule during working hours\n   * - Maintenance windows: Schedule during low-traffic periods\n   */\n  runAt?: Date | number;\n\n  /**\n   * Configuration for repeating jobs (cron or interval-based).\n   * Creates a repeating job that generates new instances automatically.\n   *\n   * @example { every: 60000 } // Every minute\n   *\n   * When to use:\n   * - Periodic tasks: Regular cleanup, reports, etc.\n   * - Monitoring: Health checks, metrics collection\n   * - Maintenance: Regular database cleanup, cache warming\n   */\n  repeat?: RepeatOptions;\n\n  /**\n   * Custom job ID for idempotence. If a job with this ID already exists,\n   * the new job will be ignored (idempotent behavior).\n   *\n   * @example 'user-123-email-welcome'\n   * @example 'order-456-payment-process'\n   *\n   * **When to use:**\n   * - Idempotent operations: Prevent duplicate processing\n   * - External system integration: Use external IDs\n   * - Retry scenarios: Ensure same job isn't added multiple times\n   * - Deduplication: Prevent duplicate jobs from being created\n   */\n  jobId?: string;\n};\n\nexport type ReservedJob<T = any> = {\n  id: string;\n  groupId: string;\n  data: T;\n  attempts: number;\n  maxAttempts: number;\n  seq: number;\n  timestamp: number; // ms\n  orderMs: number;\n  score: number;\n  deadlineAt: number;\n};\n\nfunction nsKey(ns: string, ...parts: string[]) {\n  return [ns, ...parts].join(':');\n}\n\nfunction safeJsonParse(input: string): any {\n  try {\n    return JSON.parse(input);\n  } catch (_e) {\n    return null;\n  }\n}\n\nexport class Queue<T = any> {\n  private logger: LoggerInterface;\n  private r: Redis;\n  private rawNs: string;\n  private ns: string;\n  private vt: number;\n  private defaultMaxAttempts: number;\n  private scanLimit: number;\n  private keepCompleted: number;\n\n  private keepFailed: number;\n  private schedulerLockTtlMs: number;\n  public orderingDelayMs: number;\n  public name: string;\n\n  // Internal tracking for adaptive behavior\n  private _consecutiveEmptyReserves = 0;\n\n  // Promoter service for staging system\n  private promoterRedis?: Redis;\n  private promoterRunning = false;\n  private promoterLockId?: string;\n  private promoterInterval?: NodeJS.Timeout;\n\n  // Auto-batching for high-throughput scenarios\n  private batchConfig?: { size: number; maxWaitMs: number };\n  private batchBuffer: Array<{\n    groupId: string;\n    data: T | null;\n    jobId: string;\n    maxAttempts: number;\n    delayMs?: number;\n    orderMs?: number;\n    resolve: (job: Job<T>) => void;\n    reject: (err: Error) => void;\n  }> = [];\n  private batchTimer?: NodeJS.Timeout;\n  private flushing = false;\n\n  // Inline defineCommand bindings removed; using external Lua via evalsha\n\n  constructor(opts: QueueOptions) {\n    // Use the provided Redis client for main operations to preserve connection semantics\n    // and a dedicated duplicate for blocking operations.\n    this.r = opts.redis;\n    this.rawNs = opts.namespace;\n    this.name = opts.namespace;\n    this.ns = `groupmq:${this.rawNs}`;\n    const rawVt = opts.jobTimeoutMs ?? 30_000;\n    this.vt = Math.max(1, rawVt); // Minimum 1ms\n    this.defaultMaxAttempts = opts.maxAttempts ?? 3;\n    this.scanLimit = opts.reserveScanLimit ?? 20;\n    this.keepCompleted = Math.max(0, opts.keepCompleted ?? 0);\n    this.keepFailed = Math.max(0, opts.keepFailed ?? 0);\n    this.schedulerLockTtlMs = opts.schedulerLockTtlMs ?? 1500;\n    this.orderingDelayMs = opts.orderingDelayMs ?? 0;\n\n    // Initialize auto-batching if enabled\n    if (opts.autoBatch) {\n      this.batchConfig =\n        typeof opts.autoBatch === 'boolean'\n          ? { size: 10, maxWaitMs: 10 }\n          : {\n              size: opts.autoBatch.size ?? 10,\n              maxWaitMs: opts.autoBatch.maxWaitMs ?? 10,\n            };\n    }\n\n    // Initialize logger first\n    this.logger =\n      typeof opts.logger === 'object'\n        ? opts.logger\n        : new Logger(!!opts.logger, this.namespace);\n\n    this.r.on('error', (err) => {\n      this.logger.error('Redis error (main):', err);\n    });\n  }\n\n  get redis(): Redis {\n    return this.r;\n  }\n\n  get namespace(): string {\n    return this.ns;\n  }\n\n  get rawNamespace(): string {\n    return this.rawNs;\n  }\n\n  get jobTimeoutMs(): number {\n    return this.vt;\n  }\n\n  get maxAttemptsDefault(): number {\n    return this.defaultMaxAttempts;\n  }\n\n  async add(opts: AddOptions<T>): Promise<JobEntity<T>> {\n    const maxAttempts = opts.maxAttempts ?? this.defaultMaxAttempts;\n    const orderMs = opts.orderMs ?? Date.now();\n    const now = Date.now();\n    const jobId = opts.jobId ?? randomUUID();\n\n    if (opts.repeat) {\n      // Keep existing behavior for repeating jobs (returns a repeat key string)\n      return this.addRepeatingJob({ ...opts, orderMs, maxAttempts });\n    }\n\n    // Calculate delay\n    let delayMs: number | undefined;\n    if (opts.delay !== undefined && opts.delay > 0) {\n      delayMs = opts.delay;\n    } else if (opts.runAt !== undefined) {\n      const runAtTimestamp =\n        opts.runAt instanceof Date ? opts.runAt.getTime() : opts.runAt;\n      delayMs = Math.max(0, runAtTimestamp - now);\n    }\n\n    // Handle undefined data by converting to null for consistent JSON serialization\n    const data = opts.data === undefined ? null : (opts.data as T);\n\n    // Use batching if enabled\n    if (this.batchConfig) {\n      return new Promise((resolve, reject) => {\n        this.batchBuffer.push({\n          groupId: opts.groupId,\n          data,\n          jobId,\n          maxAttempts,\n          delayMs,\n          orderMs,\n          resolve,\n          reject,\n        });\n\n        // Flush if batch is full\n        if (this.batchBuffer.length >= this.batchConfig!.size) {\n          this.flushBatch();\n        } else if (!this.batchTimer) {\n          // Start timer for partial batch\n          this.batchTimer = setTimeout(\n            () => this.flushBatch(),\n            this.batchConfig!.maxWaitMs,\n          );\n        }\n      });\n    }\n\n    // Non-batched path (original logic)\n    return this.addSingle({\n      ...opts,\n      data,\n      jobId,\n      maxAttempts,\n      orderMs,\n      delayMs,\n    });\n  }\n\n  private async addSingle(opts: {\n    groupId: string;\n    data: T | null;\n    jobId: string;\n    maxAttempts: number;\n    orderMs: number;\n    delayMs?: number;\n  }): Promise<JobEntity<T>> {\n    const now = Date.now();\n\n    // Calculate delay timestamp\n    let delayUntil = 0;\n    if (opts.delayMs !== undefined && opts.delayMs > 0) {\n      delayUntil = now + opts.delayMs;\n    }\n\n    const serializedPayload = JSON.stringify(opts.data);\n\n    const result = await evalScript<string[] | string>(\n      this.r,\n      'enqueue',\n      [\n        this.ns,\n        opts.groupId,\n        serializedPayload,\n        String(opts.maxAttempts),\n        String(opts.orderMs),\n        String(delayUntil),\n        String(opts.jobId),\n        String(this.keepCompleted),\n        String(now), // Pass client timestamp for accurate timing calculations\n        String(this.orderingDelayMs), // Pass orderingDelayMs for staging logic\n      ],\n      1,\n    );\n\n    // Handle new array format that includes job data (avoids race condition)\n    // Format: [jobId, groupId, data, attempts, maxAttempts, timestamp, orderMs, delayUntil, status]\n    if (Array.isArray(result)) {\n      const [\n        returnedJobId,\n        returnedGroupId,\n        returnedData,\n        attempts,\n        returnedMaxAttempts,\n        timestamp,\n        returnedOrderMs,\n        returnedDelayUntil,\n        status,\n      ] = result;\n\n      return JobEntity.fromRawHash<T>(\n        this,\n        returnedJobId,\n        {\n          id: returnedJobId,\n          groupId: returnedGroupId,\n          data: returnedData,\n          attempts,\n          maxAttempts: returnedMaxAttempts,\n          timestamp,\n          orderMs: returnedOrderMs,\n          delayUntil: returnedDelayUntil,\n          status,\n        },\n        status as any,\n      );\n    }\n\n    // Fallback for old format (just jobId string) - this shouldn't happen with updated Lua script\n    // but kept for backwards compatibility during rollout\n    return this.getJob(result);\n  }\n\n  private async flushBatch(): Promise<void> {\n    // Clear timer\n    if (this.batchTimer) {\n      clearTimeout(this.batchTimer);\n      this.batchTimer = undefined;\n    }\n\n    if (this.batchBuffer.length === 0 || this.flushing) return;\n\n    this.flushing = true;\n    const batch = this.batchBuffer.splice(0); // Take all pending jobs\n\n    try {\n      this.logger.debug(`Flushing batch of ${batch.length} jobs`);\n      const now = Date.now();\n\n      // Prepare batch data for Lua script\n      const jobsData = batch.map((job) => ({\n        jobId: job.jobId,\n        groupId: job.groupId,\n        data: JSON.stringify(job.data),\n        maxAttempts: job.maxAttempts,\n        orderMs: job.orderMs,\n        delayMs: job.delayMs,\n      }));\n\n      // Call batch enqueue Lua script\n      // Returns array of job data arrays: [[jobId, groupId, data, attempts, maxAttempts, timestamp, orderMs, delayUntil, status], ...]\n      const jobDataArrays = await evalScript<string[][]>(\n        this.r,\n        'enqueue-batch',\n        [\n          this.ns,\n          JSON.stringify(jobsData),\n          String(this.keepCompleted),\n          String(now),\n          String(this.orderingDelayMs),\n        ],\n        1,\n      );\n\n      // Resolve all promises with job entities\n      for (let i = 0; i < batch.length; i++) {\n        const job = batch[i];\n        const jobDataArray = jobDataArrays[i];\n\n        try {\n          if (jobDataArray && jobDataArray.length >= 9) {\n            const [\n              returnedJobId,\n              returnedGroupId,\n              returnedData,\n              attempts,\n              returnedMaxAttempts,\n              timestamp,\n              returnedOrderMs,\n              returnedDelayUntil,\n              status,\n            ] = jobDataArray;\n\n            const jobEntity = JobEntity.fromRawHash<T>(\n              this,\n              returnedJobId,\n              {\n                id: returnedJobId,\n                groupId: returnedGroupId,\n                data: returnedData,\n                attempts,\n                maxAttempts: returnedMaxAttempts,\n                timestamp,\n                orderMs: returnedOrderMs,\n                delayUntil: returnedDelayUntil,\n                status,\n              },\n              status as any,\n            );\n            job.resolve(jobEntity);\n          } else {\n            throw new Error('Invalid job data returned from batch enqueue');\n          }\n        } catch (err) {\n          job.reject(err instanceof Error ? err : new Error(String(err)));\n        }\n      }\n    } catch (err) {\n      // Reject all promises on error\n      for (const job of batch) {\n        job.reject(err instanceof Error ? err : new Error(String(err)));\n      }\n    } finally {\n      this.flushing = false;\n\n      // If there are jobs that accumulated during flush, flush them now\n      if (this.batchBuffer.length > 0) {\n        // Use setImmediate to avoid deep recursion\n        setImmediate(() => this.flushBatch());\n      }\n    }\n  }\n\n  async reserve(): Promise<ReservedJob<T> | null> {\n    const now = Date.now();\n\n    const raw = await evalScript<string | null>(\n      this.r,\n      'reserve',\n      [this.ns, String(now), String(this.vt), String(this.scanLimit)],\n      1,\n    );\n\n    if (!raw) return null;\n\n    const parts = raw.split('|||');\n    if (parts.length !== 10) return null;\n\n    let data: T;\n    try {\n      data = JSON.parse(parts[2]);\n    } catch (err) {\n      this.logger.warn(\n        `Failed to parse job data: ${(err as Error).message}, raw: ${parts[2]}`,\n      );\n      data = null as T;\n    }\n\n    const parsedOrderMs = Number.parseInt(parts[7], 10);\n    const job = {\n      id: parts[0],\n      groupId: parts[1],\n      data,\n      attempts: Number.parseInt(parts[3], 10),\n      maxAttempts: Number.parseInt(parts[4], 10),\n      seq: Number.parseInt(parts[5], 10),\n      timestamp: Number.parseInt(parts[6], 10),\n      orderMs: Number.isNaN(parsedOrderMs)\n        ? Number.parseInt(parts[6], 10)\n        : parsedOrderMs, // Fallback to timestamp if orderMs is NaN\n      score: Number(parts[8]),\n      deadlineAt: Number.parseInt(parts[9], 10),\n    } as ReservedJob<T>;\n\n    return job;\n  }\n\n  /**\n   * Check how many jobs are waiting in a specific group\n   */\n  async getGroupJobCount(groupId: string): Promise<number> {\n    const gZ = `${this.ns}:g:${groupId}`;\n    return await this.r.zcard(gZ);\n  }\n\n  /**\n   * Complete a job by removing from processing and unlocking the group.\n   * Note: Job metadata recording is handled separately by recordCompleted().\n   *\n   * @deprecated Use completeWithMetadata() for internal operations. This method\n   * is kept for backward compatibility and testing only.\n   */\n  async complete(job: { id: string; groupId: string }) {\n    await evalScript<number>(\n      this.r,\n      'complete',\n      [this.ns, job.id, job.groupId],\n      1,\n    );\n  }\n\n  /**\n   * Complete a job AND record metadata in a single atomic operation.\n   * This is the efficient internal method used by workers.\n   */\n  public async completeWithMetadata(\n    job: { id: string; groupId: string },\n    result: unknown,\n    meta: {\n      processedOn: number;\n      finishedOn: number;\n      attempts: number;\n      maxAttempts: number;\n    },\n  ): Promise<void> {\n    await evalScript<number>(\n      this.r,\n      'complete-with-metadata',\n      [\n        this.ns,\n        job.id,\n        job.groupId,\n        'completed',\n        String(meta.finishedOn),\n        JSON.stringify(result ?? null),\n        String(this.keepCompleted),\n        String(this.keepFailed),\n        String(meta.processedOn),\n        String(meta.finishedOn),\n        String(meta.attempts),\n        String(meta.maxAttempts),\n      ],\n      1,\n    );\n  }\n\n  /**\n   * Atomically complete a job and try to reserve the next job from the same group\n   * This prevents race conditions where other workers can steal subsequent jobs from the same group\n   */\n\n  /**\n   * Atomically complete a job with metadata and reserve the next job from the same group.\n   */\n  async completeAndReserveNextWithMetadata(\n    completedJobId: string,\n    groupId: string,\n    handlerResult: unknown,\n    meta: {\n      processedOn: number;\n      finishedOn: number;\n      attempts: number;\n      maxAttempts: number;\n    },\n  ): Promise<ReservedJob<T> | null> {\n    const now = Date.now();\n\n    try {\n      const result = await evalScript<string | null>(\n        this.r,\n        'complete-and-reserve-next-with-metadata',\n        [\n          this.ns,\n          completedJobId,\n          groupId,\n          'completed',\n          String(meta.finishedOn),\n          JSON.stringify(handlerResult ?? null),\n          String(this.keepCompleted),\n          String(this.keepFailed),\n          String(meta.processedOn),\n          String(meta.finishedOn),\n          String(meta.attempts),\n          String(meta.maxAttempts),\n          String(now),\n          String(this.jobTimeoutMs),\n        ],\n        1,\n      );\n\n      if (!result) {\n        return null;\n      }\n\n      // Parse the result (same format as reserve methods)\n      const parts = result.split('|||');\n      if (parts.length !== 10) {\n        this.logger.error(\n          'Queue completeAndReserveNextWithMetadata: unexpected result format:',\n          result,\n        );\n        return null;\n      }\n\n      const [\n        id,\n        ,\n        data,\n        attempts,\n        maxAttempts,\n        seq,\n        enq,\n        orderMs,\n        score,\n        deadline,\n      ] = parts;\n\n      return {\n        id,\n        groupId,\n        data: JSON.parse(data),\n        attempts: parseInt(attempts, 10),\n        maxAttempts: parseInt(maxAttempts, 10),\n        seq: parseInt(seq, 10),\n        timestamp: parseInt(enq, 10),\n        orderMs: parseInt(orderMs, 10),\n        score: parseFloat(score),\n        deadlineAt: parseInt(deadline, 10),\n      };\n    } catch (error) {\n      this.logger.error(\n        'Queue completeAndReserveNextWithMetadata error:',\n        error,\n      );\n      return null;\n    }\n  }\n\n  /**\n   * Check if a job is currently in processing state\n   */\n  async isJobProcessing(jobId: string): Promise<boolean> {\n    const score = await this.r.zscore(`${this.ns}:processing`, jobId);\n    return score !== null;\n  }\n\n  async retry(jobId: string, backoffMs = 0) {\n    return evalScript<number>(\n      this.r,\n      'retry',\n      [this.ns, jobId, String(backoffMs)],\n\n      1,\n    );\n  }\n\n  /**\n   * Dead letter a job (remove from group and optionally store in dead letter queue)\n   */\n  async deadLetter(jobId: string, groupId: string) {\n    return evalScript<number>(\n      this.r,\n      'dead-letter',\n      [this.ns, jobId, groupId],\n      1,\n    );\n  }\n\n  /**\n   * Record a successful completion for retention and inspection\n   * Uses consolidated Lua script for atomic operation with retention management\n   */\n  async recordCompleted(\n    job: { id: string; groupId: string },\n    result: unknown,\n    meta: {\n      processedOn?: number;\n      finishedOn?: number;\n      attempts?: number;\n      maxAttempts?: number;\n      data?: unknown; // legacy\n    },\n  ): Promise<void> {\n    const processedOn = meta.processedOn ?? Date.now();\n    const finishedOn = meta.finishedOn ?? Date.now();\n    const attempts = meta.attempts ?? 0;\n    const maxAttempts = meta.maxAttempts ?? this.defaultMaxAttempts;\n\n    try {\n      await evalScript<number>(\n        this.r,\n        'record-job-result',\n        [\n          this.ns,\n          job.id,\n          'completed',\n          String(finishedOn),\n          JSON.stringify(result ?? null),\n          String(this.keepCompleted),\n          String(this.keepFailed),\n          String(processedOn),\n          String(finishedOn),\n          String(attempts),\n          String(maxAttempts),\n        ],\n        1,\n      );\n    } catch (error) {\n      this.logger.error(`Error recording completion for job ${job.id}:`, error);\n      throw error;\n    }\n  }\n\n  /**\n   * Record a failure attempt (non-final), storing last error for visibility\n   */\n  async recordAttemptFailure(\n    job: { id: string; groupId: string },\n    error: { message?: string; name?: string; stack?: string } | string,\n    meta: {\n      processedOn?: number;\n      finishedOn?: number;\n      attempts?: number;\n      maxAttempts?: number;\n    },\n  ): Promise<void> {\n    const jobKey = `${this.ns}:job:${job.id}`;\n    const processedOn = meta.processedOn ?? Date.now();\n    const finishedOn = meta.finishedOn ?? Date.now();\n\n    const message =\n      typeof error === 'string' ? error : (error.message ?? 'Error');\n    const name = typeof error === 'string' ? 'Error' : (error.name ?? 'Error');\n    const stack = typeof error === 'string' ? '' : (error.stack ?? '');\n\n    await this.r.hset(\n      jobKey,\n      'lastErrorMessage',\n      message,\n      'lastErrorName',\n      name,\n      'lastErrorStack',\n      stack,\n      'processedOn',\n      String(processedOn),\n      'finishedOn',\n      String(finishedOn),\n    );\n  }\n\n  /**\n   * Record a final failure (dead-lettered) for retention and inspection\n   * Uses consolidated Lua script for atomic operation\n   */\n  async recordFinalFailure(\n    job: { id: string; groupId: string },\n    error: { message?: string; name?: string; stack?: string } | string,\n    meta: {\n      processedOn?: number;\n      finishedOn?: number;\n      attempts?: number;\n      maxAttempts?: number;\n      data?: unknown;\n    },\n  ): Promise<void> {\n    const processedOn = meta.processedOn ?? Date.now();\n    const finishedOn = meta.finishedOn ?? Date.now();\n    const attempts = meta.attempts ?? 0;\n    const maxAttempts = meta.maxAttempts ?? this.defaultMaxAttempts;\n\n    const message =\n      typeof error === 'string' ? error : (error.message ?? 'Error');\n    const name = typeof error === 'string' ? 'Error' : (error.name ?? 'Error');\n    const stack = typeof error === 'string' ? '' : (error.stack ?? '');\n\n    // Package error info as JSON for Lua script\n    const errorInfo = JSON.stringify({ message, name, stack });\n\n    try {\n      await evalScript<number>(\n        this.r,\n        'record-job-result',\n        [\n          this.ns,\n          job.id,\n          'failed',\n          String(finishedOn),\n          errorInfo,\n          String(this.keepCompleted),\n          String(this.keepFailed),\n          String(processedOn),\n          String(finishedOn),\n          String(attempts),\n          String(maxAttempts),\n        ],\n        1,\n      );\n    } catch (err) {\n      this.logger.error(\n        `Error recording final failure for job ${job.id}:`,\n        err,\n      );\n      throw err;\n    }\n  }\n\n  async getCompleted(limit = this.keepCompleted): Promise<\n    Array<{\n      id: string;\n      groupId: string;\n      data: any;\n      returnvalue: any;\n      processedOn?: number;\n      finishedOn?: number;\n      attempts: number;\n      maxAttempts: number;\n    }>\n  > {\n    const completedKey = `${this.ns}:completed`;\n    const ids = await this.r.zrevrange(completedKey, 0, Math.max(0, limit - 1));\n    if (ids.length === 0) return [];\n    const pipe = this.r.multi();\n    for (const id of ids) {\n      pipe.hmget(\n        `${this.ns}:job:${id}`,\n        'groupId',\n        'data',\n        'returnvalue',\n        'processedOn',\n        'finishedOn',\n        'attempts',\n        'maxAttempts',\n      );\n    }\n    const rows = (await pipe.exec()) ?? [];\n    return ids.map((id, idx) => {\n      const row = rows[idx]?.[1] as Array<string | null>;\n      const [\n        groupId,\n        dataStr,\n        retStr,\n        processedOn,\n        finishedOn,\n        attempts,\n        maxAttempts,\n      ] = row || [];\n      return {\n        id,\n        groupId: groupId || '',\n        data: dataStr ? safeJsonParse(dataStr) : null,\n        returnvalue: retStr ? safeJsonParse(retStr) : null,\n        processedOn: processedOn ? parseInt(processedOn, 10) : undefined,\n        finishedOn: finishedOn ? parseInt(finishedOn, 10) : undefined,\n        attempts: attempts ? parseInt(attempts, 10) : 0,\n        maxAttempts: maxAttempts\n          ? parseInt(maxAttempts, 10)\n          : this.defaultMaxAttempts,\n      };\n    });\n  }\n\n  async getFailed(limit = this.keepFailed): Promise<\n    Array<{\n      id: string;\n      groupId: string;\n      data: any;\n      failedReason: string;\n      stacktrace?: string;\n      processedOn?: number;\n      finishedOn?: number;\n      attempts: number;\n      maxAttempts: number;\n    }>\n  > {\n    const failedKey = `${this.ns}:failed`;\n    const ids = await this.r.zrevrange(failedKey, 0, Math.max(0, limit - 1));\n    if (ids.length === 0) return [];\n    const pipe = this.r.multi();\n    for (const id of ids) {\n      pipe.hmget(\n        `${this.ns}:job:${id}`,\n        'groupId',\n        'data',\n        'failedReason',\n        'stacktrace',\n        'processedOn',\n        'finishedOn',\n        'attempts',\n        'maxAttempts',\n      );\n    }\n    const rows = (await pipe.exec()) ?? [];\n    return ids.map((id, idx) => {\n      const row = rows[idx]?.[1] as Array<string | null>;\n      const [\n        groupId,\n        dataStr,\n        failedReason,\n        stacktrace,\n        processedOn,\n        finishedOn,\n        attempts,\n        maxAttempts,\n      ] = row || [];\n      return {\n        id,\n        groupId: groupId || '',\n        data: dataStr ? safeJsonParse(dataStr) : null,\n        failedReason: failedReason || '',\n        stacktrace: stacktrace || undefined,\n        processedOn: processedOn ? parseInt(processedOn, 10) : undefined,\n        finishedOn: finishedOn ? parseInt(finishedOn, 10) : undefined,\n        attempts: attempts ? parseInt(attempts, 10) : 0,\n        maxAttempts: maxAttempts\n          ? parseInt(maxAttempts, 10)\n          : this.defaultMaxAttempts,\n      };\n    });\n  }\n\n  /**\n   * Convenience: return completed jobs as Job entities (non-breaking, new API)\n   */\n  async getCompletedJobs(\n    limit = this.keepCompleted,\n  ): Promise<Array<JobEntity<T>>> {\n    const completedKey = `${this.ns}:completed`;\n    const ids = await this.r.zrevrange(completedKey, 0, Math.max(0, limit - 1));\n    if (ids.length === 0) return [];\n\n    // Atomically fetch all job hashes in one pipeline\n    const pipe = this.r.multi();\n    for (const id of ids) {\n      pipe.hgetall(`${this.ns}:job:${id}`);\n    }\n    const rows = await pipe.exec();\n\n    // Construct jobs directly from pipeline data (atomic, no race condition)\n    const jobs: Array<JobEntity<T>> = [];\n    for (let i = 0; i < ids.length; i++) {\n      const id = ids[i];\n      const raw = (rows?.[i]?.[1] as Record<string, string>) || {};\n\n      // Skip jobs that were already cleaned up\n      if (!raw || Object.keys(raw).length === 0) {\n        this.logger.warn(\n          `Skipping completed job ${id} - not found (likely cleaned up)`,\n        );\n        continue;\n      }\n\n      const job = JobEntity.fromRawHash<T>(this, id, raw, 'completed');\n      jobs.push(job);\n    }\n    return jobs;\n  }\n\n  /**\n   * Convenience: return failed jobs as Job entities (non-breaking, new API)\n   */\n  async getFailedJobs(limit = this.keepFailed): Promise<Array<JobEntity<T>>> {\n    const failedKey = `${this.ns}:failed`;\n    const ids = await this.r.zrevrange(failedKey, 0, Math.max(0, limit - 1));\n    if (ids.length === 0) return [];\n\n    // Atomically fetch all job hashes in one pipeline\n    const pipe = this.r.multi();\n    for (const id of ids) {\n      pipe.hgetall(`${this.ns}:job:${id}`);\n    }\n    const rows = await pipe.exec();\n\n    // Construct jobs directly from pipeline data (atomic, no race condition)\n    const jobs: Array<JobEntity<T>> = [];\n    for (let i = 0; i < ids.length; i++) {\n      const id = ids[i];\n      const raw = (rows?.[i]?.[1] as Record<string, string>) || {};\n\n      // Skip jobs that were already cleaned up\n      if (!raw || Object.keys(raw).length === 0) {\n        this.logger.warn(\n          `Skipping failed job ${id} - not found (likely cleaned up)`,\n        );\n        continue;\n      }\n\n      const job = JobEntity.fromRawHash<T>(this, id, raw, 'failed');\n      jobs.push(job);\n    }\n    return jobs;\n  }\n\n  async getCompletedCount(): Promise<number> {\n    return this.r.zcard(`${this.ns}:completed`);\n  }\n\n  async getFailedCount(): Promise<number> {\n    return this.r.zcard(`${this.ns}:failed`);\n  }\n  async heartbeat(job: { id: string; groupId: string }, extendMs = this.vt) {\n    return evalScript<number>(\n      this.r,\n      'heartbeat',\n      [this.ns, job.id, job.groupId, String(extendMs)],\n      1,\n    );\n  }\n\n  /**\n   * Clean up expired jobs and stale data.\n   * Uses distributed lock to ensure only one worker runs cleanup at a time,\n   * similar to scheduler lock pattern.\n   */\n  async cleanup(): Promise<number> {\n    // Try to acquire cleanup lock (similar to scheduler lock)\n    const cleanupLockKey = `${this.ns}:cleanup:lock`;\n    const ttlMs = 60000; // 60 seconds - longer than typical cleanup duration\n\n    try {\n      const acquired = await (this.r as any).set(\n        cleanupLockKey,\n        '1',\n        'PX',\n        ttlMs,\n        'NX',\n      );\n\n      if (acquired !== 'OK') {\n        // Another worker is running cleanup\n        return 0;\n      }\n\n      // We have the lock, run cleanup\n      const now = Date.now();\n      return evalScript<number>(this.r, 'cleanup', [this.ns, String(now)], 1);\n    } catch (_e) {\n      return 0;\n    }\n  }\n\n  /**\n   * Calculate adaptive blocking timeout like BullMQ\n   * Returns timeout in seconds\n   *\n   * Inspiration by BullMQ ⭐️\n   */\n  private getBlockTimeout(maxTimeout: number, blockUntil?: number): number {\n    const minimumBlockTimeout = 0.001; // 1ms like BullMQ for fast job pickup\n    const maximumBlockTimeout = 5; // 5s max to reduce idle CPU usage\n\n    // Handle delayed jobs case (when we know exactly when next job should be processed)\n    if (blockUntil) {\n      const blockDelay = blockUntil - Date.now();\n\n      // If we've reached the time to get new jobs\n      if (blockDelay <= 0) {\n        return minimumBlockTimeout; // Process immediately\n      } else if (blockDelay < minimumBlockTimeout * 1000) {\n        return minimumBlockTimeout; // Very short delay, use minimum\n      } else {\n        // Block until the delayed job is ready, but cap at maximum\n        return Math.min(blockDelay / 1000, maximumBlockTimeout);\n      }\n    }\n\n    // Use maxTimeout when draining (similar to BullMQ's drainDelay), but clamp to minimum\n    // This keeps the worker responsive while balancing Redis load\n    return Math.max(\n      minimumBlockTimeout,\n      Math.min(maxTimeout, maximumBlockTimeout),\n    );\n  }\n\n  /**\n   * Check if an error is a Redis connection error (should retry)\n   * Conservative approach: only connection closed and ECONNREFUSED\n   */\n  isConnectionError(err: any): boolean {\n    if (!err) return false;\n\n    const message = `${err.message || ''}`;\n\n    return (\n      message === 'Connection is closed.' || message.includes('ECONNREFUSED')\n    );\n  }\n\nasync reserveBlocking(timeoutSec = 5, blockUntil, blockingClient) {\n\t\tconst startTime = Date.now();\n\t\tif (await this.isPaused()) {\n\t\t\tawait sleep$1(50);\n\t\t\treturn null;\n\t\t}\n\t\tif (!(this._consecutiveEmptyReserves >= 3)) {\n\t\t\tconst immediateJob = await this.reserve();\n\t\t\tif (immediateJob) {\n\t\t\t\tthis.logger.debug(`Immediate reserve successful (${Date.now() - startTime}ms)`);\n\t\t\t\tthis._consecutiveEmptyReserves = 0;\n\t\t\t\treturn immediateJob;\n\t\t\t}\n\t\t}\n\t\tconst adaptiveTimeout = this.getBlockTimeout(timeoutSec, blockUntil);\n\t\tif (this._consecutiveEmptyReserves % 10 === 0) this.logger.debug(`Starting blocking operation (timeout: ${adaptiveTimeout}s, consecutive empty: ${this._consecutiveEmptyReserves})`);\n\t\tconst readyKey = nsKey(this.ns, \"ready\");\n\t\ttry {\n\t\t\tconst bzpopminStart = Date.now();\n\t\t\tconst result = await (blockingClient ?? this.r).bzpopmin(readyKey, adaptiveTimeout);\n\t\t\tconst bzpopminDuration = Date.now() - bzpopminStart;\n\t\t\tif (!result || result.length < 3) {\n\t\t\t\tthis.logger.debug(`Blocking timeout/empty (took ${bzpopminDuration}ms)`);\n\t\t\t\tthis._consecutiveEmptyReserves = this._consecutiveEmptyReserves + 1;\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst [, groupId, score] = result;\n\t\t\tif (this._consecutiveEmptyReserves % 10 === 0) this.logger.debug(`Blocking result: group=${groupId}, score=${score} (took ${bzpopminDuration}ms)`);\n\t\t\tconst reserveStart = Date.now();\n\t\t\tconst job = await this.reserveAtomic(groupId);\n\t\t\tconst reserveDuration = Date.now() - reserveStart;\n\t\t\tif (job) {\n\t\t\t\tthis.logger.debug(`Successful job reserve after blocking: ${job.id} from group ${job.groupId} (reserve took ${reserveDuration}ms)`);\n\t\t\t\tthis._consecutiveEmptyReserves = 0;\n\t\t\t} \n\t\t\telse{\n\t\t\t\t// Group found but reserve failed - ALWAYS check the head job\n\t\t\t\tconst groupKey = `${this.ns}:g:${groupId}`;\n\t\t\t\tconst firstJobIds = await this.r.zrange(groupKey, 0, 0);\n\t\t\t  \tthis.logger.debug(\n\t\t\t\t\t\t`groupKey ${groupKey}`,\n\t\t\t\t\t  );\n\t\t\t\tthis.logger.debug('sssss--->',firstJobIds);\n\t\t\t\tif (firstJobIds.length > 0) {\n\t\t\t\t  const firstJobId = firstJobIds[0];\n\t\t\t\t  const jobKey = `${this.ns}:job:${firstJobId}`;\n\t\t\t\t  const [status, delayUntil, processedOn,lastErrorMessage,attempts] = await this.r.hmget(\n\t\t\t\t\tjobKey, 'status', 'delayUntil', 'processedOn','lastErrorMessage','attempts'\n\t\t\t\t  );\n\t\t\t\t  this.logger.debug('lastErrorMessage--->',lastErrorMessage);\n\t\t\t\t  if(String(lastErrorMessage).trim() !== '' && parseInt(attempts) >= 3){\n\t\t\t\t\t\tthis.logger.debug(`H0 Group ${groupId} head job ${firstJobId} delayed for ${String(lastErrorMessage)}ms. Not restoring.`);\n\t\t\t\t\t\tthis._consecutiveEmptyReserves++;\n\t\t\t\t\t\treturn null;\n\t\t\t\t  }\n\t\t\t  \n\t\t\t\t  // Kiểm tra job delayed\n\t\t\t\t  if (status === 'delayed' && delayUntil) {\n\t\t\t\t\tconst due = parseInt(delayUntil, 10);\n\t\t\t\t\tconst now = Date.now();\n\t\t\t\t\tif (due > now) {\n\t\t\t\t\t  this.logger.debug(\n\t\t\t\t\t\t`E001 Group ${groupId} head job ${firstJobId} delayed for ${due - now}ms. Not restoring.`,\n\t\t\t\t\t  );\n\t\t\t\t\t  this._consecutiveEmptyReserves++;\n\t\t\t\t\t  return null;\n\t\t\t\t\t}\n\t\t\t\t  }\n\t\t\t \t  this.logger.debug( 'E002 ---------------status='+status + '      firstJobId='+firstJobId);\n\t\t\t\t  //THÊM: Kiểm tra job processing nhưng có thể là zombie\n\t\t\t\t  if (status === 'processing') {\n\t\t\t\t\tconst now = Date.now();\n\t\t\t\t\tconst processedTime = processedOn ? parseInt(processedOn, 10) : 0;\n\t\t\t\t\tconst processingDuration = processedTime > 0 ? now - processedTime : 0;\n\t\t\t\t\tconst TEN_MINUTES_MS = 3000_000; // 5 phút\n\t\t\t\t\tthis.logger.debug( 'E006 processingDuration='+processingDuration);\n\t\t\t\t\tif (processingDuration <= TEN_MINUTES_MS) {\n\t\t\t\t\t  // Job zombie - đánh dấu failed và cleanup\n\t\t\t\t\t  this.logger.warn(\n\t\t\t\t\t\t`E003 Group ${groupId} head job ${firstJobId} has been processing for ${Math.round(processingDuration / 1000)}s (>5 min). Marking as failed.`,\n\t\t\t\t\t  );\n\t\t\t\t\t  await this.r.hset(jobKey, 'status', 'failed', 'failedReason', 'Processing timeout (>5 min)');\n\t\t\t\t\t  await this.r.zrem(`${this.ns}:processing`, firstJobId);\n\t\t\t\t\t  await this.r.del(`${this.ns}:processing:${firstJobId}`);\n\t\t\t\t\t  await this.r.del(`${this.ns}:g:${groupId}:active`);\n\t\t\t\t\t  // Không return - để logic phía dưới restore group với job tiếp theo\n\t\t\t\t\t} else {\n\t\t\t\t\t  // Job vẫn đang được xử lý hợp lệ - không restore\n\t\t\t\t\t  this.logger.debug(\n\t\t\t\t\t\t`E004 Group ${groupId} head job ${firstJobId} processing for ${Math.round(processingDuration / 1000)}s (<5 min). Not restoring.`,\n\t\t\t\t\t  );\n\t\t\t\t\t  this._consecutiveEmptyReserves++;\n\t\t\t\t\t  return null;\n\t\t\t\t\t}\n\t\t\t\t  }\n\t\t\t\t}\n\t\t\t  \n\t\t\t\t// Restore group to :ready\n\t\t\t\tconst jobCount = await this.r.zcard(groupKey);\n\t\t\t\tif (jobCount > 0) {\n\t\t\t\t  await this.r.zadd(readyKey, Number(score), groupId);\n\t\t\t\t  this.logger.debug(`E005 -- Restored group ${groupId} add `);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn job;\n\t\t} catch (err) {\n\t\t\tconst errorDuration = Date.now() - startTime;\n\t\t\tthis.logger.error(`Blocking error after ${errorDuration}ms:`, err);\n\t\t\tif (this.isConnectionError(err)) {\n\t\t\t\tthis.logger.error(`Connection error detected - rethrowing`);\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\tthis.logger.warn(`Falling back to regular reserve due to error`);\n\t\t\treturn this.reserve();\n\t\t} finally {\n\t\t\tconst totalDuration = Date.now() - startTime;\n\t\t\tif (totalDuration > 1e3) this.logger.debug(`ReserveBlocking completed in ${totalDuration}ms`);\n\t\t}\n\t}\n\n  /**\n   * Reserve a job from a specific group atomically (eliminates race conditions)\n   * @param groupId - The group to reserve from\n   */\n  async reserveAtomic(groupId: string): Promise<ReservedJob<T> | null> {\n    const now = Date.now();\n\n    const result = await evalScript<string | null>(\n      this.r,\n      'reserve-atomic',\n      [this.ns, String(now), String(this.vt), String(groupId)],\n      1,\n    );\n    if (!result) return null;\n\n    // Parse the delimited string response (same format as regular reserve)\n    const parts = result.split('|||');\n    if (parts.length < 10) return null;\n\n    const [\n      id,\n      groupIdRaw,\n      data,\n      attempts,\n      maxAttempts,\n      seq,\n      timestamp,\n      orderMs,\n      score,\n      deadline,\n    ] = parts;\n\n    const parsedTimestamp = parseInt(timestamp, 10);\n    const parsedOrderMs = parseInt(orderMs, 10);\n    return {\n      id,\n      groupId: groupIdRaw,\n      data: JSON.parse(data),\n      attempts: parseInt(attempts, 10),\n      maxAttempts: parseInt(maxAttempts, 10),\n      seq: parseInt(seq, 10),\n      timestamp: parsedTimestamp,\n      orderMs: Number.isNaN(parsedOrderMs) ? parsedTimestamp : parsedOrderMs, // Fallback to timestamp if orderMs is NaN\n      score: parseFloat(score),\n      deadlineAt: parseInt(deadline, 10),\n    };\n  }\n\n  /**\n   * Reserve up to maxBatch jobs (one per available group) atomically in Lua.\n   */\n  async reserveBatch(maxBatch = 16): Promise<Array<ReservedJob<T>>> {\n    const now = Date.now();\n    const results = await evalScript<Array<string | null>>(\n      this.r,\n      'reserve-batch',\n      [this.ns, String(now), String(this.vt), String(Math.max(1, maxBatch))],\n      1,\n    );\n    const out: Array<ReservedJob<T>> = [];\n    for (const r of results || []) {\n      if (!r) continue;\n      const parts = r.split('|||');\n      if (parts.length !== 10) continue;\n      out.push({\n        id: parts[0],\n        groupId: parts[1],\n        data: safeJsonParse(parts[2]),\n        attempts: parseInt(parts[3], 10),\n        maxAttempts: parseInt(parts[4], 10),\n        seq: parseInt(parts[5], 10),\n        timestamp: parseInt(parts[6], 10),\n        orderMs: parseInt(parts[7], 10),\n        score: parseFloat(parts[8]),\n        deadlineAt: parseInt(parts[9], 10),\n      } as ReservedJob<T>);\n    }\n    return out;\n  }\n\n  /**\n   * Get the number of jobs currently being processed (active jobs)\n   */\n  async getActiveCount(): Promise<number> {\n    return evalScript<number>(this.r, 'get-active-count', [this.ns], 1);\n  }\n\n  /**\n   * Get the number of jobs waiting to be processed\n   */\n  async getWaitingCount(): Promise<number> {\n    return evalScript<number>(this.r, 'get-waiting-count', [this.ns], 1);\n  }\n\n  /**\n   * Get the number of jobs delayed due to backoff\n   */\n  async getDelayedCount(): Promise<number> {\n    return evalScript<number>(this.r, 'get-delayed-count', [this.ns], 1);\n  }\n\n  /**\n   * Get list of active job IDs\n   */\n  async getActiveJobs(): Promise<string[]> {\n    return evalScript<string[]>(this.r, 'get-active-jobs', [this.ns], 1);\n  }\n\n  /**\n   * Get list of waiting job IDs\n   */\n  async getWaitingJobs(): Promise<string[]> {\n    return evalScript<string[]>(this.r, 'get-waiting-jobs', [this.ns], 1);\n  }\n\n  /**\n   * Get list of delayed job IDs\n   */\n  async getDelayedJobs(): Promise<string[]> {\n    return evalScript<string[]>(this.r, 'get-delayed-jobs', [this.ns], 1);\n  }\n\n  /**\n   * Get list of unique group IDs that have jobs\n   */\n  async getUniqueGroups(): Promise<string[]> {\n    return evalScript<string[]>(this.r, 'get-unique-groups', [this.ns], 1);\n  }\n\n  /**\n   * Get count of unique groups that have jobs\n   */\n  async getUniqueGroupsCount(): Promise<number> {\n    return evalScript<number>(this.r, 'get-unique-groups-count', [this.ns], 1);\n  }\n\n  /**\n   * Fetch a single job by ID with enriched fields for UI/inspection.\n   * Attempts to mimic BullMQ's Job shape for fields commonly used by BullBoard.\n   */\n  async getJob(id: string): Promise<JobEntity<T>> {\n    return JobEntity.fromStore<T>(this, id);\n  }\n\n  /**\n   * Fetch jobs by statuses, emulating BullMQ's Queue.getJobs API used by BullBoard.\n   * Only getter functionality; ordering is best-effort.\n   *\n   * Optimized with pagination to reduce Redis load - especially important for BullBoard polling.\n   */\n  async getJobsByStatus(\n    jobStatuses: Array<Status>,\n    start = 0,\n    end = -1,\n  ): Promise<Array<JobEntity<T>>> {\n    // Calculate actual limit to fetch (with some buffer for deduplication)\n    const requestedCount = end >= 0 ? end - start + 1 : 100; // Default to 100 if unbounded\n    const fetchLimit = Math.min(requestedCount * 2, 500); // Cap at 500 to prevent excessive fetches\n\n    // Map to track which status each job belongs to (for known status optimization)\n    const idToStatus = new Map<string, Status>();\n    const idSets: string[] = [];\n\n    // Optimized helper that respects pagination\n    const pushZRange = async (key: string, status: Status, reverse = false) => {\n      try {\n        // Fetch only what we need (with buffer), not everything\n        const ids = reverse\n          ? await this.r.zrevrange(key, 0, fetchLimit - 1)\n          : await this.r.zrange(key, 0, fetchLimit - 1);\n        for (const id of ids) {\n          idToStatus.set(id, status);\n        }\n        idSets.push(...ids);\n      } catch (_e) {\n        // ignore\n      }\n    };\n\n    const statuses = new Set(jobStatuses);\n\n    if (statuses.has('active')) {\n      await pushZRange(`${this.ns}:processing`, 'active');\n    }\n    if (statuses.has('delayed')) {\n      await pushZRange(`${this.ns}:delayed`, 'delayed');\n    }\n    if (statuses.has('completed')) {\n      await pushZRange(`${this.ns}:completed`, 'completed', true);\n    }\n    if (statuses.has('failed')) {\n      await pushZRange(`${this.ns}:failed`, 'failed', true);\n    }\n    if (statuses.has('waiting')) {\n      // Aggregate waiting jobs with limits to prevent scanning all groups\n      try {\n        const groupIds = await this.r.smembers(`${this.ns}:groups`);\n        if (groupIds.length > 0) {\n          // Limit groups to scan (prevent excessive iteration)\n          const groupsToScan = groupIds.slice(\n            0,\n            Math.min(100, groupIds.length),\n          );\n          const pipe = this.r.multi();\n\n          // Fetch only first few jobs from each group (most are at the head anyway)\n          const jobsPerGroup = Math.max(\n            1,\n            Math.ceil(fetchLimit / groupsToScan.length),\n          );\n          for (const gid of groupsToScan) {\n            pipe.zrange(`${this.ns}:g:${gid}`, 0, jobsPerGroup - 1);\n          }\n\n          const rows = await pipe.exec();\n          for (const r of rows || []) {\n            const arr = (r?.[1] as string[]) || [];\n            for (const id of arr) {\n              idToStatus.set(id, 'waiting');\n            }\n            idSets.push(...arr);\n          }\n        }\n      } catch (_e) {\n        // ignore\n      }\n    }\n\n    // paused, waiting-children, prioritized are not supported; return empty\n\n    // De-duplicate keeping first occurrence\n    const seen = new Set<string>();\n    const uniqueIds: string[] = [];\n    for (const id of idSets) {\n      if (!seen.has(id)) {\n        seen.add(id);\n        uniqueIds.push(id);\n      }\n    }\n\n    const slice =\n      end >= 0 ? uniqueIds.slice(start, end + 1) : uniqueIds.slice(start);\n    if (slice.length === 0) return [];\n\n    // Atomically fetch all job hashes in one pipeline\n    const pipe = this.r.multi();\n    for (const id of slice) {\n      pipe.hgetall(`${this.ns}:job:${id}`);\n    }\n    const rows = await pipe.exec();\n\n    // Construct jobs directly from pipeline data (atomic, no race condition)\n    const jobs: Array<JobEntity<T>> = [];\n    for (let i = 0; i < slice.length; i++) {\n      const id = slice[i];\n      const raw = (rows?.[i]?.[1] as Record<string, string>) || {};\n\n      // Skip jobs that were already cleaned up\n      if (!raw || Object.keys(raw).length === 0) {\n        this.logger.warn(\n          `Skipping job ${id} - not found (likely cleaned up by retention)`,\n        );\n        continue;\n      }\n\n      // Use the known status from the index we fetched from\n      const knownStatus = idToStatus.get(id);\n      const job = JobEntity.fromRawHash<T>(this, id, raw, knownStatus);\n      jobs.push(job);\n    }\n    return jobs;\n  }\n\n  /**\n   * Provide counts structured like BullBoard expects.\n   */\n  async getJobCounts(): Promise<\n    Record<\n      | 'active'\n      | 'waiting'\n      | 'delayed'\n      | 'completed'\n      | 'failed'\n      | 'paused'\n      | 'waiting-children'\n      | 'prioritized',\n      number\n    >\n  > {\n    const [active, waiting, delayed, completed, failed] = await Promise.all([\n      this.getActiveCount(),\n      this.getWaitingCount(),\n      this.getDelayedCount(),\n      this.getCompletedCount(),\n      this.getFailedCount(),\n    ]);\n\n    return {\n      active,\n      waiting,\n      delayed,\n      completed,\n      failed,\n      paused: 0,\n      'waiting-children': 0,\n      prioritized: 0,\n    };\n  }\n\n  /**\n   * Scan all groups and recover jobs stuck in active lists after ungraceful shutdown.\n   * Call this BEFORE creating workers on startup to clean up ghost entries.\n   *\n   * For each ghost: removes from active list, removes from processing set,\n   * re-queues with 'waiting' status, and restores the group to the ready set.\n   */\n  async recoverActiveJobs(): Promise<number> {\n    const groupsKey = `${this.ns}:groups`;\n    const readyKey = `${this.ns}:ready`;\n    const processingKey = `${this.ns}:processing`;\n    const allGroups = await this.r.smembers(groupsKey);\n    let recovered = 0;\n\n    for (const groupId of allGroups) {\n      const activeKey = `${this.ns}:g:${groupId}:active`;\n      const activeJobs = await this.r.lrange(activeKey, 0, -1);\n      if (activeJobs.length === 0) continue;\n\n      for (const jobId of activeJobs) {\n        const jobKey = `${this.ns}:job:${jobId}`;\n        const score = await this.r.hget(jobKey, 'score');\n\n        await this.r.lrem(activeKey, 0, jobId);\n        await this.r.zrem(processingKey, jobId);\n        await this.r.del(`${this.ns}:processing:${jobId}`);\n\n        if (score) {\n          const groupKey = `${this.ns}:g:${groupId}`;\n          await this.r.zadd(groupKey, Number(score), jobId);\n          await this.r.hset(jobKey, 'status', 'waiting');\n\n          const head = await this.r.zrange(groupKey, 0, 0, 'WITHSCORES');\n          if (head.length >= 2) {\n            await this.r.zadd(readyKey, Number(head[1]), groupId);\n          }\n\n          recovered++;\n          this.logger.info(\n            `Recovered stale active job ${jobId} from group ${groupId}`,\n          );\n        }\n      }\n    }\n    return recovered;\n  }\n\n  /**\n   * Check for stalled jobs and recover or fail them\n   * Returns array of [jobId, groupId, action] tuples\n   */\n  async checkStalledJobs(\n    now: number,\n    gracePeriod: number,\n    maxStalledCount: number,\n  ): Promise<string[]> {\n    try {\n      const results = await evalScript<string[]>(\n        this.r,\n        'check-stalled',\n        [this.ns, String(now), String(gracePeriod), String(maxStalledCount)],\n        1,\n      );\n      return results || [];\n    } catch (error) {\n      this.logger.error('Error checking stalled jobs:', error);\n      return [];\n    }\n  }\n\n  /**\n   * Start the promoter service for staging system.\n   * Promoter listens to Redis keyspace notifications and promotes staged jobs when ready.\n   * This is idempotent - calling multiple times has no effect if already running.\n   */\n  async startPromoter(): Promise<void> {\n    if (this.promoterRunning || this.orderingDelayMs <= 0) {\n      return; // Already running or not needed\n    }\n\n    this.promoterRunning = true;\n    this.promoterLockId = randomUUID();\n\n    try {\n      // Create duplicate Redis connection for pub/sub\n      this.promoterRedis = this.r.duplicate();\n\n      // Try to enable keyspace notifications\n      try {\n        await this.promoterRedis.config('SET', 'notify-keyspace-events', 'Ex');\n        this.logger.debug(\n          'Enabled Redis keyspace notifications for staging promoter',\n        );\n      } catch (err) {\n        this.logger.warn(\n          'Failed to enable keyspace notifications. Promoter will use polling fallback.',\n          err,\n        );\n      }\n\n      // Get Redis database number for keyspace event channel\n      const db = this.promoterRedis.options.db ?? 0;\n\n      const timerKey = `${this.ns}:stage:timer`;\n      const expiredChannel = `__keyevent@${db}__:expired`;\n\n      // Subscribe to keyspace expiration events\n      await this.promoterRedis.subscribe(expiredChannel, (err) => {\n        if (err) {\n          this.logger.error('Failed to subscribe to keyspace events:', err);\n        } else {\n          this.logger.debug(`Subscribed to ${expiredChannel}`);\n        }\n      });\n\n      // Handle expiration events\n      this.promoterRedis.on('message', async (channel, message) => {\n        if (channel === expiredChannel && message === timerKey) {\n          await this.runPromotion();\n        }\n      });\n\n      // Fallback: polling interval (100ms) in case keyspace notifications fail\n      this.promoterInterval = setInterval(async () => {\n        await this.runPromotion();\n      }, 100);\n\n      // Initial promotion check\n      await this.runPromotion();\n\n      this.logger.debug('Staging promoter started');\n    } catch (err) {\n      this.logger.error('Failed to start promoter:', err);\n      this.promoterRunning = false;\n      await this.stopPromoter();\n    }\n  }\n\n  /**\n   * Run a single promotion cycle with distributed locking\n   */\n  private async runPromotion(): Promise<void> {\n    if (!this.promoterRunning) {\n      return;\n    }\n\n    const lockKey = `${this.ns}:promoter:lock`;\n    const lockTtl = 30000; // 30 seconds\n\n    try {\n      // Try to acquire lock\n      const acquired = await this.r.set(\n        lockKey,\n        this.promoterLockId!,\n        'PX',\n        lockTtl,\n        'NX',\n      );\n\n      if (acquired === 'OK') {\n        try {\n          // Promote staged jobs\n          const promoted = await evalScript<number>(\n            this.r,\n            'promote-staged',\n            [\n              this.ns,\n              String(Date.now()),\n              String(100), // Limit per batch\n            ],\n            1,\n          );\n\n          if (promoted > 0) {\n            this.logger.debug(`Promoted ${promoted} staged jobs`);\n          }\n        } finally {\n          // Release lock (only if it's still ours)\n          const currentLockValue = await this.r.get(lockKey);\n          if (currentLockValue === this.promoterLockId) {\n            await this.r.del(lockKey);\n          }\n        }\n      }\n    } catch (err) {\n      this.logger.error('Error during promotion:', err);\n    }\n  }\n\n  /**\n   * Stop the promoter service\n   */\n  async stopPromoter(): Promise<void> {\n    if (!this.promoterRunning) return;\n\n    this.promoterRunning = false;\n\n    // Clear interval\n    if (this.promoterInterval) {\n      clearInterval(this.promoterInterval);\n      this.promoterInterval = undefined;\n    }\n\n    // Close promoter Redis connection\n    if (this.promoterRedis) {\n      try {\n        await this.promoterRedis.unsubscribe();\n        await this.promoterRedis.quit();\n      } catch (_err) {\n        try {\n          this.promoterRedis.disconnect();\n        } catch (_e) {}\n      }\n      this.promoterRedis = undefined;\n    }\n\n    this.logger.debug('Staging promoter stopped');\n  }\n\n  /**\n   * Close underlying Redis connections\n   */\n  async close(): Promise<void> {\n    // Flush any pending batched jobs before closing\n    if (this.batchConfig && this.batchBuffer.length > 0) {\n      this.logger.debug(\n        `Flushing ${this.batchBuffer.length} pending batched jobs before close`,\n      );\n      await this.flushBatch();\n    }\n\n    // Stop promoter\n    await this.stopPromoter();\n\n    try {\n      await this.r.quit();\n    } catch (_e) {\n      try {\n        this.r.disconnect();\n      } catch (_e2) {}\n    }\n  }\n\n  // --------------------- Pause/Resume ---------------------\n  private get pausedKey(): string {\n    return `${this.ns}:paused`;\n  }\n\n  async pause(): Promise<void> {\n    await this.r.set(this.pausedKey, '1');\n  }\n\n  async resume(): Promise<void> {\n    await this.r.del(this.pausedKey);\n  }\n\n  async isPaused(): Promise<boolean> {\n    const v = await this.r.get(this.pausedKey);\n    return v !== null;\n  }\n\n  /**\n   * Wait for the queue to become empty (no active jobs)\n   * @param timeoutMs Maximum time to wait in milliseconds (default: 60 seconds)\n   * @returns true if queue became empty, false if timeout reached\n   */\n  async waitForEmpty(timeoutMs = 60_000): Promise<boolean> {\n    const startTime = Date.now();\n\n    while (Date.now() - startTime < timeoutMs) {\n      try {\n        // Single atomic Lua script checks all queue structures\n        const isEmpty = await evalScript<number>(\n          this.r,\n          'is-empty',\n          [this.ns],\n          1,\n        );\n\n        if (isEmpty === 1) {\n          await sleep(0);\n          return true;\n        }\n\n        await sleep(200);\n      } catch (err) {\n        // Handle connection errors gracefully - Redis might be temporarily unavailable\n        if (this.isConnectionError(err)) {\n          this.logger.warn(\n            'Redis connection error in waitForEmpty, retrying...',\n          );\n          // Wait longer before retry on connection errors\n          await sleep(1000);\n          continue;\n        }\n        // For non-connection errors, rethrow\n        throw err;\n      }\n    }\n\n    return false; // Timeout reached\n  }\n\n  // Track cleanup calls per group to throttle excessive checking\n  private _groupCleanupTracking = new Map<string, number>();\n\n  /**\n   * Remove problematic groups from ready queue to prevent infinite loops\n   * Handles both poisoned groups (only failed/expired jobs) and locked groups\n   *\n   * Throttled to 1% sampling rate to reduce Redis overhead\n   */\n  private async cleanupPoisonedGroup(groupId: string): Promise<string> {\n    // Throttle: only check 1% of the time to reduce Redis load\n    // This is called frequently when workers compete for groups\n    if (Math.random() > 0.01) {\n      return 'skipped';\n    }\n\n    // Additional throttle: max once per 10 seconds per group\n    const lastCheck = this._groupCleanupTracking.get(groupId) || 0;\n    const now = Date.now();\n    if (now - lastCheck < 10000) {\n      return 'throttled';\n    }\n    this._groupCleanupTracking.set(groupId, now);\n\n    // Periodically clean old tracking entries (keep map bounded)\n    if (this._groupCleanupTracking.size > 1000) {\n      const cutoff = now - 60000; // 1 minute ago\n      for (const [gid, ts] of this._groupCleanupTracking.entries()) {\n        if (ts < cutoff) {\n          this._groupCleanupTracking.delete(gid);\n        }\n      }\n    }\n\n    try {\n      const result = await evalScript<string>(\n        this.r,\n        'cleanup-poisoned-group',\n        [this.ns, groupId, String(now)],\n        1,\n      );\n      if (result === 'poisoned') {\n        this.logger.warn(`Removed poisoned group ${groupId} from ready queue`);\n      } else if (result === 'empty') {\n        this.logger.warn(`Removed empty group ${groupId} from ready queue`);\n      } else if (result === 'locked') {\n        // Only log locked group warnings occasionally\n        if (Math.random() < 0.1) {\n          this.logger.debug(\n            `Detected group ${groupId} is locked by another worker (this is normal with high concurrency)`,\n          );\n        }\n      }\n      return result as string;\n    } catch (error) {\n      this.logger.error(`Error cleaning up group ${groupId}:`, error);\n      return 'error';\n    }\n  }\n\n  /**\n   * Distributed one-shot scheduler: promotes delayed jobs and processes repeating jobs.\n   * Only proceeds if a short-lived scheduler lock can be acquired.\n   */\n  private schedulerLockKey(): string {\n    return `${this.ns}:sched:lock`;\n  }\n\n  async acquireSchedulerLock(ttlMs = 1500): Promise<boolean> {\n    try {\n      const res = (await (this.r as any).set(\n        this.schedulerLockKey(),\n        '1',\n        'PX',\n        ttlMs,\n        'NX',\n      )) as string | null;\n      return res === 'OK';\n    } catch (_e) {\n      return false;\n    }\n  }\n\n  async runSchedulerOnce(now = Date.now()): Promise<void> {\n    const ok = await this.acquireSchedulerLock(this.schedulerLockTtlMs);\n    if (!ok) return;\n    // Reduced limits for faster execution: process a few jobs per tick instead of hundreds\n    await this.promoteDelayedJobsBounded(32, now);\n    await this.processRepeatingJobsBounded(16, now);\n  }\n\n  /**\n   * Promote up to `limit` delayed jobs that are due now. Uses a small Lua to move one item per tick.\n   */\n  async promoteDelayedJobsBounded(\n    limit = 256,\n    now = Date.now(),\n  ): Promise<number> {\n    let moved = 0;\n    for (let i = 0; i < limit; i++) {\n      try {\n        const n = await evalScript<number>(\n          this.r,\n          'promote-delayed-one',\n          [this.ns, String(now)],\n          1,\n        );\n        if (!n || n <= 0) break;\n        moved += n;\n      } catch (_e) {\n        break;\n      }\n    }\n    return moved;\n  }\n\n  /**\n   * Process up to `limit` repeating job ticks.\n   * Intentionally small per-tick work to keep Redis CPU flat.\n   */\n  async processRepeatingJobsBounded(\n    limit = 128,\n    now = Date.now(),\n  ): Promise<number> {\n    const scheduleKey = `${this.ns}:repeat:schedule`;\n    let processed = 0;\n    for (let i = 0; i < limit; i++) {\n      // Get one due entry\n      const due = await this.r.zrangebyscore(\n        scheduleKey,\n        0,\n        now,\n        'LIMIT',\n        0,\n        1,\n      );\n      if (!due || due.length === 0) break;\n      const repeatKey = due[0];\n\n      try {\n        const repeatJobKey = `${this.ns}:repeat:${repeatKey}`;\n        const repeatJobDataStr = await this.r.get(repeatJobKey);\n\n        if (!repeatJobDataStr) {\n          await this.r.zrem(scheduleKey, repeatKey);\n          continue;\n        }\n\n        const repeatJobData = JSON.parse(repeatJobDataStr);\n        if (repeatJobData.removed) {\n          await this.r.zrem(scheduleKey, repeatKey);\n          await this.r.del(repeatJobKey);\n          continue;\n        }\n\n        // Remove from schedule first to prevent duplicates\n        await this.r.zrem(scheduleKey, repeatKey);\n\n        // Compute next run\n        let nextRunTime: number;\n        if ('every' in repeatJobData.repeat) {\n          nextRunTime = now + repeatJobData.repeat.every;\n        } else {\n          nextRunTime = this.getNextCronTime(repeatJobData.repeat.pattern, now);\n        }\n\n        repeatJobData.nextRunTime = nextRunTime;\n        repeatJobData.lastRunTime = now;\n        await this.r.set(repeatJobKey, JSON.stringify(repeatJobData));\n        await this.r.zadd(scheduleKey, nextRunTime, repeatKey);\n\n        // Enqueue the instance\n        await evalScript<string>(\n          this.r,\n          'enqueue',\n          [\n            this.ns,\n            repeatJobData.groupId,\n            JSON.stringify(repeatJobData.data),\n            String(repeatJobData.maxAttempts ?? this.defaultMaxAttempts),\n            String(repeatJobData.orderMs ?? now),\n            String(0),\n            String(randomUUID()),\n            String(this.keepCompleted),\n          ],\n          1,\n        );\n\n        processed++;\n      } catch (error) {\n        this.logger.error(\n          `Error processing repeating job ${repeatKey}:`,\n          error,\n        );\n        await this.r.zrem(scheduleKey, repeatKey);\n      }\n    }\n    return processed;\n  }\n\n  /**\n   * Promote delayed jobs that are now ready to be processed\n   * This should be called periodically to move jobs from delayed set to ready queue\n   */\n  async promoteDelayedJobs(): Promise<number> {\n    try {\n      return await evalScript<number>(\n        this.r,\n        'promote-delayed-jobs',\n        [this.ns, String(Date.now())],\n        1,\n      );\n    } catch (error) {\n      this.logger.error(`Error promoting delayed jobs:`, error);\n      return 0;\n    }\n  }\n\n  /**\n   * Change the delay of a specific job\n   */\n  async changeDelay(jobId: string, newDelay: number): Promise<boolean> {\n    const newDelayUntil = newDelay > 0 ? Date.now() + newDelay : 0;\n\n    try {\n      const result = await evalScript<number>(\n        this.r,\n        'change-delay',\n        [this.ns, jobId, String(newDelayUntil), String(Date.now())],\n        1,\n      );\n      return result === 1;\n    } catch (error) {\n      this.logger.error(`Error changing delay for job ${jobId}:`, error);\n      return false;\n    }\n  }\n\n  /**\n   * Promote a delayed job to be ready immediately\n   */\n  async promote(jobId: string): Promise<boolean> {\n    return this.changeDelay(jobId, 0);\n  }\n\n  /**\n   * Remove a job from the queue regardless of state (waiting, delayed, processing)\n   */\n  async remove(jobId: string): Promise<boolean> {\n    try {\n      const result = await evalScript<number>(\n        this.r,\n        'remove',\n        [this.ns, jobId],\n        1,\n      );\n      return result === 1;\n    } catch (error) {\n      this.logger.error(`Error removing job ${jobId}:`, error);\n      return false;\n    }\n  }\n\n  /**\n   * Clean jobs of a given status older than graceTimeMs\n   * @param graceTimeMs Remove jobs with finishedOn <= now - graceTimeMs (for completed/failed)\n   * @param limit Max number of jobs to clean in one call\n   * @param status Either 'completed' or 'failed'\n   */\n  async clean(\n    graceTimeMs: number,\n    limit: number,\n    status: 'completed' | 'failed' | 'delayed',\n  ): Promise<number> {\n    const graceAt = Date.now() - graceTimeMs;\n    try {\n      const removed = await evalScript<number>(\n        this.r,\n        'clean-status',\n        [\n          this.ns,\n          status,\n          String(graceAt),\n          String(Math.max(0, Math.min(limit, 100000))),\n        ],\n        1,\n      );\n      return removed ?? 0;\n    } catch (error) {\n      console.log('HERE?', error);\n\n      this.logger.error(`Error cleaning ${status} jobs:`, error);\n      return 0;\n    }\n  }\n\n  /**\n   * Update a job's data payload (BullMQ-style)\n   */\n  async updateData(jobId: string, data: T): Promise<void> {\n    const jobKey = `${this.ns}:job:${jobId}`;\n    const exists = await this.r.exists(jobKey);\n    if (!exists) {\n      throw new Error(`Job ${jobId} not found`);\n    }\n    const serialized = JSON.stringify(data === undefined ? null : data);\n    await this.r.hset(jobKey, 'data', serialized);\n  }\n\n  /**\n   * Add a repeating job (cron job)\n   */\n  private async addRepeatingJob(opts: AddOptions<T>): Promise<JobEntity> {\n    if (!opts.repeat) {\n      throw new Error('Repeat options are required for repeating jobs');\n    }\n\n    const now = Date.now();\n    // Make repeatKey unique by including a timestamp and random component\n    const repeatKey = `${opts.groupId}:${JSON.stringify(opts.repeat)}:${now}:${Math.random().toString(36).slice(2)}`;\n\n    // Calculate next run time\n    let nextRunTime: number;\n\n    if ('every' in opts.repeat) {\n      // Simple interval-based repeat\n      nextRunTime = now + opts.repeat.every;\n    } else {\n      // Cron pattern-based repeat\n      nextRunTime = this.getNextCronTime(opts.repeat.pattern, now);\n    }\n\n    // Store repeating job metadata\n    const repeatJobData = {\n      groupId: opts.groupId,\n      data: opts.data === undefined ? null : opts.data,\n      maxAttempts: opts.maxAttempts ?? this.defaultMaxAttempts,\n      orderMs: opts.orderMs,\n      repeat: opts.repeat,\n      nextRunTime,\n      lastRunTime: null as number | null,\n      removed: false, // Track if this repeat job has been removed\n    };\n\n    // Store in Redis (metadata JSON)\n    const repeatJobKey = `${this.ns}:repeat:${repeatKey}`;\n    await this.r.set(repeatJobKey, JSON.stringify(repeatJobData));\n\n    // Add to repeating jobs sorted set for efficient scheduling\n    await this.r.zadd(`${this.ns}:repeat:schedule`, nextRunTime, repeatKey);\n\n    // Create a reverse mapping for easier removal\n    const lookupKey = `${this.ns}:repeat:lookup:${opts.groupId}:${JSON.stringify(opts.repeat)}`;\n    await this.r.set(lookupKey, repeatKey);\n\n    // Persist a synthetic Job entity for this repeating definition so that\n    // Queue.add consistently returns a Job. Use a special repeat id namespace.\n    const repeatId = `repeat:${repeatKey}`;\n    const jobHashKey = `${this.ns}:job:${repeatId}`;\n    try {\n      await this.r.hmset(\n        jobHashKey,\n        'id',\n        repeatId,\n        'groupId',\n        repeatJobData.groupId,\n        'data',\n        JSON.stringify(repeatJobData.data),\n        'attempts',\n        '0',\n        'maxAttempts',\n        String(repeatJobData.maxAttempts),\n        'seq',\n        '0',\n        'timestamp',\n        String(Date.now()),\n        'orderMs',\n        String(repeatJobData.orderMs ?? now),\n        'status',\n        'waiting',\n      );\n    } catch (_e) {\n      // best-effort; even if this fails, the repeat metadata exists\n    }\n\n    // Don't schedule the first job immediately - let the cron processor handle it\n    // Return the persisted Job entity handle for the repeating definition\n    return JobEntity.fromStore<T>(this as any, repeatId);\n  }\n\n  /**\n   * Compute next execution time using cron-parser (BullMQ-style)\n   */\n  private getNextCronTime(pattern: string, fromTime: number): number {\n    try {\n      const interval = CronParser.parseExpression(pattern, {\n        currentDate: new Date(fromTime),\n      });\n      return interval.next().getTime();\n    } catch (_e) {\n      throw new Error(`Invalid cron pattern: ${pattern}`);\n    }\n  }\n\n  /**\n   * Remove a repeating job\n   */\n  async removeRepeatingJob(\n    groupId: string,\n    repeat: RepeatOptions,\n  ): Promise<boolean> {\n    try {\n      // Use the lookup key to find the actual repeatKey\n      const lookupKey = `${this.ns}:repeat:lookup:${groupId}:${JSON.stringify(repeat)}`;\n      const repeatKey = await this.r.get(lookupKey);\n\n      if (!repeatKey) {\n        // No such repeating job exists\n        return false;\n      }\n\n      const repeatJobKey = `${this.ns}:repeat:${repeatKey}`;\n      const scheduleKey = `${this.ns}:repeat:schedule`;\n\n      // Get the repeat job data before modifying\n      const repeatJobDataStr = await this.r.get(repeatJobKey);\n\n      if (!repeatJobDataStr) {\n        // Clean up orphaned lookup\n        await this.r.del(lookupKey);\n        return false;\n      }\n\n      const repeatJobData = JSON.parse(repeatJobDataStr);\n\n      // Mark as removed to prevent future scheduling\n      repeatJobData.removed = true;\n      await this.r.set(repeatJobKey, JSON.stringify(repeatJobData));\n\n      // Remove from future schedule (but keep the metadata for cleanup)\n      await this.r.zrem(scheduleKey, repeatKey);\n\n      // Clean up the lookup key\n      await this.r.del(lookupKey);\n\n      // Note: Cleanup of existing job instances is best-effort and not critical.\n      // Jobs will naturally complete or be cleaned up by the retention policies.\n\n      // Remove the synthetic repeat job hash persisted at creation time\n      try {\n        const repeatId = `repeat:${repeatKey}`;\n        await this.r.del(`${this.ns}:job:${repeatId}`);\n      } catch (_e) {\n        // best-effort cleanup\n      }\n\n      return true;\n    } catch (error) {\n      this.logger.error(`Error removing repeating job:`, error);\n      return false;\n    }\n  }\n}\n\nfunction sleep(ms: number): Promise<void> {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * This file contains code copied from BullMQ (https://github.com/taskforcesh/bullmq)\n *\n * BullMQ is a fantastic library and one of the most popular Redis-based job queue\n * libraries for Node.js. We've copied the AsyncFifoQueue implementation from BullMQ\n * as it's a well-designed component that fits our needs perfectly.\n *\n * Original copyright notice:\n * Copyright (c) Taskforce.sh and contributors\n *\n * This code is used under the MIT License. The original license can be found at:\n * https://github.com/taskforcesh/bullmq/blob/main/LICENSE\n *\n * Modifications may have been made to adapt this code for use in GroupMQ.\n */\n\nclass Node<T> {\n  value: T | undefined = undefined;\n  next: Node<T> | null = null;\n\n  constructor(value: T) {\n    this.value = value;\n  }\n}\n\nclass LinkedList<T> {\n  length = 0;\n  private head: Node<T> | null;\n  private tail: Node<T> | null;\n\n  constructor() {\n    this.head = null;\n    this.tail = null;\n  }\n\n  push(value: T) {\n    const newNode = new Node(value);\n    if (!this.length) {\n      this.head = newNode;\n    } else {\n      this.tail!.next = newNode;\n    }\n\n    this.tail = newNode;\n    this.length += 1;\n    return newNode;\n  }\n\n  shift() {\n    if (!this.length) {\n      return null;\n    }\n    const head = this.head;\n    this.head = this.head!.next;\n    this.length -= 1;\n\n    return head;\n  }\n}\n\n/**\n * AsyncFifoQueue\n *\n * A minimal FIFO queue for asynchronous operations. Allows adding asynchronous operations\n * and consume them in the order they are resolved.\n */\nexport class AsyncFifoQueue<T> {\n  /**\n   * A queue of completed promises. As the pending\n   * promises are resolved, they are added to this queue.\n   */\n  private queue: LinkedList<T> = new LinkedList();\n\n  /**\n   * A set of pending promises.\n   */\n  private pending = new Set<Promise<T>>();\n\n  /**\n   * The next promise to be resolved. As soon as a pending promise\n   * is resolved, this promise is resolved with the result of the\n   * pending promise.\n   */\n  private nextPromise: Promise<T | undefined> | undefined;\n  private resolve: ((value: T | undefined) => void) | undefined;\n  private reject: ((reason?: any) => void) | undefined;\n\n  constructor(private ignoreErrors = false) {\n    this.newPromise();\n  }\n\n  public add(promise: Promise<T>): void {\n    this.pending.add(promise);\n\n    promise\n      .then((data) => {\n        this.pending.delete(promise);\n\n        if (this.queue.length === 0) {\n          this.resolvePromise(data);\n        }\n        this.queue.push(data);\n      })\n      .catch((err) => {\n        this.pending.delete(promise);\n\n        if (this.ignoreErrors) {\n          // Push undefined and resolve instead of rejecting\n          if (this.queue.length === 0) {\n            this.resolvePromise(undefined as T);\n          }\n          this.queue.push(undefined as T);\n        } else {\n          this.rejectPromise(err);\n        }\n      });\n  }\n\n  public async waitAll(): Promise<void> {\n    await Promise.all(this.pending);\n  }\n\n  public numTotal(): number {\n    return this.pending.size + this.queue.length;\n  }\n\n  public numPending(): number {\n    return this.pending.size;\n  }\n\n  public numQueued(): number {\n    return this.queue.length;\n  }\n\n  private resolvePromise(data: T) {\n    this.resolve!(data);\n    this.newPromise();\n  }\n\n  private rejectPromise(err: any) {\n    this.reject!(err);\n    this.newPromise();\n  }\n\n  private newPromise() {\n    this.nextPromise = new Promise<T | undefined>((resolve, reject) => {\n      this.resolve = resolve;\n      this.reject = reject;\n    });\n  }\n\n  private async wait() {\n    return this.nextPromise;\n  }\n\n  public async fetch(): Promise<T | void> {\n    if (this.pending.size === 0 && this.queue.length === 0) {\n      return;\n    }\n    while (this.queue.length === 0) {\n      try {\n        await this.wait();\n      } catch (err) {\n        // Ignore errors\n        if (!this.ignoreErrors) {\n          console.error('Unexpected Error in AsyncFifoQueue', err);\n        }\n      }\n    }\n    return this.queue.shift()?.value;\n  }\n}\n","import { AsyncFifoQueue } from './async-fifo-queue';\nimport { Job } from './job';\nimport { Logger, type LoggerInterface } from './logger';\nimport type { AddOptions, Queue, ReservedJob } from './queue';\n\nexport type BackoffStrategy = (attempt: number) => number; // ms\n\n// Typed event system for Worker\nexport interface WorkerEvents<T = any>\n  extends Record<string, (...args: any[]) => void> {\n  error: (error: Error) => void;\n  closed: () => void;\n  ready: () => void;\n  failed: (job: Job<T>) => void;\n  completed: (job: Job<T>) => void;\n  'ioredis:close': () => void;\n  'graceful-timeout': (job: Job<T>) => void;\n  stalled: (jobId: string, groupId: string) => void;\n}\n\nclass TypedEventEmitter<\n  TEvents extends Record<string, (...args: any[]) => void>,\n> {\n  private listeners = new Map<keyof TEvents, Array<TEvents[keyof TEvents]>>();\n\n  on<K extends keyof TEvents>(event: K, listener: TEvents[K]): this {\n    if (!this.listeners.has(event)) {\n      this.listeners.set(event, []);\n    }\n    this.listeners.get(event)!.push(listener);\n    return this;\n  }\n\n  off<K extends keyof TEvents>(event: K, listener: TEvents[K]): this {\n    const eventListeners = this.listeners.get(event);\n    if (eventListeners) {\n      const index = eventListeners.indexOf(listener);\n      if (index !== -1) {\n        eventListeners.splice(index, 1);\n      }\n    }\n    return this;\n  }\n\n  emit<K extends keyof TEvents>(\n    event: K,\n    ...args: Parameters<TEvents[K]>\n  ): boolean {\n    const eventListeners = this.listeners.get(event);\n    if (eventListeners && eventListeners.length > 0) {\n      for (const listener of eventListeners) {\n        try {\n          listener(...args);\n        } catch (error) {\n          // Don't let listener errors break the emit\n          console.error(\n            `Error in event listener for '${String(event)}':`,\n            error,\n          );\n        }\n      }\n      return true;\n    }\n    return false;\n  }\n\n  removeAllListeners<K extends keyof TEvents>(event?: K): this {\n    if (event) {\n      this.listeners.delete(event);\n    } else {\n      this.listeners.clear();\n    }\n    return this;\n  }\n}\n\n/**\n * Configuration options for a GroupMQ Worker\n *\n * @template T The type of data stored in jobs\n */\nexport type WorkerOptions<T> = {\n  /** The queue instance this worker will process jobs from */\n  queue: Queue<T>;\n\n  /**\n   * Optional worker name for logging and identification\n   * @default queue.name\n   */\n  name?: string;\n\n  /**\n   * The function that processes jobs. Must be async and handle job failures gracefully.\n   * @param job The reserved job to process\n   * @returns Promise that resolves when job is complete\n   */\n  handler: (job: ReservedJob<T>) => Promise<unknown>;\n\n  /**\n   * Heartbeat interval in milliseconds to keep jobs alive during processing.\n   * Prevents jobs from timing out during long-running operations.\n   *\n   * @default Math.max(1000, queue.jobTimeoutMs / 3)\n   * @example 5000 // Heartbeat every 5 seconds\n   *\n   * **When to adjust:**\n   * - Long-running jobs: Increase to reduce Redis overhead\n   * - Short jobs: Decrease for faster timeout detection\n   * - High job volume: Increase to reduce Redis commands\n   */\n  heartbeatMs?: number;\n\n  /**\n   * Error handler called when job processing fails or worker encounters errors\n   * @param err The error that occurred\n   * @param job The job that failed (if applicable)\n   */\n  onError?: (err: unknown, job?: ReservedJob<T>) => void;\n\n  /**\n   * Maximum number of retry attempts for failed jobs at the worker level.\n   * This overrides the queue's default maxAttempts setting.\n   *\n   * @default queue.maxAttemptsDefault\n   * @example 5 // Retry failed jobs up to 5 times\n   *\n   * **When to adjust:**\n   * - Critical jobs: Increase for more retries\n   * - Non-critical jobs: Decrease to fail faster\n   * - External API calls: Consider network reliability\n   */\n  maxAttempts?: number;\n\n  /**\n   * Backoff strategy for retrying failed jobs. Determines delay between retries.\n   *\n   * @default Exponential backoff with jitter (500ms, 1s, 2s, 4s, 8s, 16s, 30s max)\n   * @example (attempt) => Math.min(10000, attempt * 1000) // Linear backoff\n   *\n   * **When to adjust:**\n   * - Rate-limited APIs: Use longer delays\n   * - Database timeouts: Use shorter delays\n   * - External services: Consider their retry policies\n   */\n  backoff?: BackoffStrategy;\n\n  /**\n   * Whether to enable automatic cleanup of expired and completed jobs.\n   * Cleanup removes old jobs to prevent Redis memory growth.\n   *\n   * @default true\n   * @example false // Disable if you handle cleanup manually\n   *\n   * **When to disable:**\n   * - Manual cleanup: If you have your own cleanup process\n   * - Job auditing: If you need to keep all job history\n   * - Development: For debugging job states\n   */\n  enableCleanup?: boolean;\n\n  /**\n   * Interval in milliseconds between cleanup operations.\n   * Cleanup removes expired jobs and trims completed/failed job retention.\n   *\n   * @default 300000 (5 minutes)\n   * @example 600000 // Cleanup every 10 minutes\n   *\n   * **When to adjust:**\n   * - High job volume: Increase to reduce Redis overhead\n   * - Low job volume: Decrease for more frequent cleanup\n   * - Memory constraints: Decrease to prevent Redis memory growth\n   * - Job retention needs: Adjust based on keepCompleted/keepFailed settings\n   */\n  cleanupIntervalMs?: number;\n\n  /**\n   * Interval in milliseconds between scheduler operations.\n   * Scheduler promotes delayed jobs and processes cron/repeating jobs.\n   *\n   * @default 5000 (5 seconds)\n   * @example 1000 // For fast cron jobs (every minute or less)\n   * @example 10000 // For slow cron jobs (hourly or daily)\n   *\n   * **When to adjust:**\n   * - Fast cron jobs: Decrease (1000-2000ms) for sub-minute schedules\n   * - Slow cron jobs: Increase (10000-60000ms) to reduce Redis overhead\n   * - No cron jobs: Increase (5000-10000ms) since only delayed jobs are affected\n   */\n  schedulerIntervalMs?: number;\n\n  /**\n   * Maximum time in seconds to wait for new jobs when queue is empty.\n   * Shorter timeouts make workers more responsive but use more Redis resources.\n   *\n   * @default 1\n   * @example 0.5 // Very responsive, higher Redis usage\n   * @example 2 // Less responsive, lower Redis usage\n   *\n   * **When to adjust:**\n   * - High job volume: Use 1s or less for faster job pickup\n   * - Low job volume: Increase (2-3s) to reduce Redis overhead\n   * - Real-time requirements: Decrease to 0.5-1s for lower latency\n   * - Resource constraints: Increase to 2-5s to reduce Redis load\n   *\n   * **Note:** The actual timeout is adaptive and can go as low as 1ms\n   * based on queue activity and delayed job schedules.\n   */\n  blockingTimeoutSec?: number;\n\n  /**\n   * Logger configuration for worker operations and debugging.\n   *\n   * @default false (no logging)\n   * @example true // Enable basic logging\n   * @example customLogger // Use custom logger instance\n   *\n   * **When to enable:**\n   * - Development: For debugging job processing\n   * - Production monitoring: For operational insights\n   * - Troubleshooting: When investigating performance issues\n   */\n  logger?: LoggerInterface | true;\n\n  /**\n   * Number of jobs this worker can process concurrently.\n   * Higher concurrency increases throughput but uses more memory and CPU.\n   *\n   * @default 1\n   * @example 4 // Process 4 jobs simultaneously\n   * @example 8 // For CPU-intensive jobs on multi-core systems\n   *\n   * **When to adjust:**\n   * - CPU-bound jobs: Set to number of CPU cores\n   * - I/O-bound jobs: Set to 2-4x number of CPU cores\n   * - Memory constraints: Lower concurrency to reduce memory usage\n   * - High job volume: Increase for better throughput\n   * - Single-threaded requirements: Keep at 1\n   */\n  concurrency?: number;\n\n  /**\n   * Interval in milliseconds between stalled job checks.\n   * Stalled jobs are those whose worker crashed or lost connection.\n   *\n   * @default 30000 (30 seconds)\n   * @example 60000 // Check every minute for lower overhead\n   * @example 10000 // Check every 10 seconds for faster recovery\n   *\n   * **When to adjust:**\n   * - Fast recovery needed: Decrease (10-20s)\n   * - Lower Redis overhead: Increase (60s+)\n   * - Unreliable workers: Decrease for faster detection\n   */\n  stalledInterval?: number;\n\n  /**\n   * Maximum number of times a job can become stalled before being failed.\n   * A job becomes stalled when its worker crashes or loses connection.\n   *\n   * @default 1\n   * @example 2 // Allow jobs to stall twice before failing\n   * @example 0 // Never fail jobs due to stalling (not recommended)\n   *\n   * **When to adjust:**\n   * - Unreliable infrastructure: Increase to tolerate more failures\n   * - Critical jobs: Increase to allow more recovery attempts\n   * - Quick failure detection: Keep at 1\n   */\n  maxStalledCount?: number;\n\n  /**\n   * Grace period in milliseconds before a job is considered stalled.\n   * Jobs are only marked as stalled if their deadline has passed by this amount.\n   *\n   * @default 0 (no grace period)\n   * @example 5000 // 5 second grace period for clock skew\n   * @example 1000 // 1 second grace for network latency\n   *\n   * **When to adjust:**\n   * - Clock skew between servers: Add 1-5s grace\n   * - Network latency: Add 1-2s grace\n   * - Strict timing: Keep at 0\n   */\n  stalledGracePeriod?: number;\n};\n\nconst defaultBackoff: BackoffStrategy = (attempt) => {\n  const base = Math.min(30_000, 2 ** (attempt - 1) * 500);\n  const jitter = Math.floor(base * 0.25 * Math.random());\n  return base + jitter;\n};\n\nclass _Worker<T = any> extends TypedEventEmitter<WorkerEvents<T>> {\n  private logger: LoggerInterface;\n  public readonly name: string;\n  private q: Queue<T>;\n  private handler: WorkerOptions<T>['handler'];\n  private hbMs: number;\n  private onError?: WorkerOptions<T>['onError'];\n  private stopping = false;\n  private ready = false;\n  private closed = false;\n  private maxAttempts: number;\n  private backoff: BackoffStrategy;\n  private enableCleanup: boolean;\n  private cleanupMs: number;\n  private cleanupTimer?: NodeJS.Timeout;\n  private schedulerTimer?: NodeJS.Timeout;\n  private schedulerMs: number;\n  private blockingTimeoutSec: number;\n  private concurrency: number;\n  private blockingClient: import('ioredis').default | null = null;\n\n  // Stalled job detection\n  private stalledCheckTimer?: NodeJS.Timeout;\n  private stalledInterval: number;\n  private maxStalledCount: number;\n  private stalledGracePeriod: number;\n\n  // Track all jobs in progress (for all concurrency levels)\n  private jobsInProgress = new Set<{ job: ReservedJob<T>; ts: number }>();\n\n  // Blocking detection and monitoring\n  private lastJobPickupTime = Date.now(); // Initialize to now so we start in \"active\" mode\n  private totalJobsProcessed = 0;\n  private blockingStats = {\n    totalBlockingCalls: 0,\n    consecutiveEmptyReserves: 0,\n    lastActivityTime: Date.now(),\n  };\n  private emptyReserveBackoffMs = 0;\n\n  private redisCloseHandler?: () => void;\n  private redisErrorHandler?: (error: Error) => void;\n  private redisReadyHandler?: () => void;\n  private runLoopPromise?: Promise<void>;\n\n  constructor(opts: WorkerOptions<T>) {\n    super();\n\n    if (!opts.handler || typeof opts.handler !== 'function') {\n      throw new Error('Worker handler must be a function');\n    }\n\n    this.q = opts.queue;\n    this.name = opts.name ?? this.q.name;\n    this.logger =\n      typeof opts.logger === 'object'\n        ? opts.logger\n        : new Logger(!!opts.logger, this.name);\n    this.handler = opts.handler;\n    const jobTimeoutMs = this.q.jobTimeoutMs ?? 30_000;\n    this.hbMs =\n      opts.heartbeatMs ?? Math.max(1000, Math.floor(jobTimeoutMs / 3));\n    this.onError = opts.onError;\n    this.maxAttempts = opts.maxAttempts ?? this.q.maxAttemptsDefault ?? 3;\n    this.backoff = opts.backoff ?? defaultBackoff;\n    this.enableCleanup = opts.enableCleanup ?? true;\n    this.cleanupMs = opts.cleanupIntervalMs ?? 60_000; // 1 minutes for high-concurrency production\n\n    // Scheduler interval for delayed jobs and cron jobs\n    const defaultSchedulerMs = 1000; // 1 second for responsive job processing\n    this.schedulerMs = opts.schedulerIntervalMs ?? defaultSchedulerMs;\n\n    this.blockingTimeoutSec = opts.blockingTimeoutSec ?? 5; // 1s default for responsive job pickup (adaptive logic can go lower)\n    // With AsyncFifoQueue, we can safely use atomic completion for all concurrency levels\n    this.concurrency = Math.max(1, opts.concurrency ?? 1);\n\n    // Initialize stalled job detection settings\n    // BullMQ-inspired: More conservative settings for high concurrency\n    this.stalledInterval =\n      opts.stalledInterval ?? (this.concurrency > 50 ? 60000 : 30000); // 60s for high concurrency, 30s otherwise\n    this.maxStalledCount =\n      opts.maxStalledCount ?? (this.concurrency > 50 ? 2 : 1); // Allow 2 stalls for high concurrency\n    // CRITICAL: Grace period must be >= heartbeat startup delay to prevent false positives\n    // Default 5s covers heartbeat startup (2s) + 1 heartbeat interval (2s) + network/load buffer (1s)\n    this.stalledGracePeriod = opts.stalledGracePeriod ?? 5000; // 5s grace for all configurations\n\n    // Set up Redis connection event handlers\n    this.setupRedisEventHandlers();\n\n    // Auto-start promoter if orderingDelayMs is configured\n    if (this.q.orderingDelayMs > 0) {\n      this.q.startPromoter().catch((err) => {\n        this.logger.error('Failed to start staging promoter:', err);\n      });\n    }\n\n    this.run();\n  }\n\n  get isClosed() {\n    return this.closed;\n  }\n\n  /**\n   * Add jitter to prevent thundering herd problems in high-concurrency environments\n   * @param baseInterval The base interval in milliseconds\n   * @param jitterPercent Percentage of jitter to add (0-1, default 0.1 for 10%)\n   * @returns The interval with jitter applied\n   */\n  private addJitter(baseInterval: number, jitterPercent = 0.1): number {\n    const jitter = Math.random() * baseInterval * jitterPercent;\n    return baseInterval + jitter;\n  }\n\n  private setupRedisEventHandlers() {\n    // Get Redis instance from the queue to monitor connection events\n    const redis = this.q.redis;\n    if (redis) {\n      this.redisCloseHandler = () => {\n        this.ready = false;\n        this.emit('ioredis:close');\n      };\n      this.redisErrorHandler = (error: Error) => {\n        this.emit('error', error);\n      };\n      this.redisReadyHandler = () => {\n        if (!this.ready && !this.stopping) {\n          this.ready = true;\n          this.emit('ready');\n        }\n      };\n\n      redis.on('close', this.redisCloseHandler);\n      redis.on('error', this.redisErrorHandler);\n      redis.on('ready', this.redisReadyHandler);\n    }\n  }\n\n  async run(): Promise<void> {\n    if (this.runLoopPromise) {\n      return this.runLoopPromise;\n    }\n\n    // Store the run loop promise so close() can wait for it\n    const runPromise = this._runLoop();\n    this.runLoopPromise = runPromise;\n    return runPromise;\n  }\n\n  private async _runLoop(): Promise<void> {\n    this.logger.info(`🚀 Worker ${this.name} starting...`);\n\n    // Auto-recover ghost active entries left by previous ungraceful shutdowns.\n    // This is idempotent — safe to run from every worker on startup.\n    try {\n      const recovered = await this.q.recoverActiveJobs();\n      if (recovered > 0) {\n        this.logger.info(\n          `Recovered ${recovered} ghost active job(s) from previous crash`,\n        );\n      }\n    } catch (err) {\n      this.logger.warn('Failed to recover active jobs on startup:', err);\n    }\n\n    // Dedicated blocking client per worker with auto-pipelining to reduce contention\n    try {\n      this.blockingClient = this.q.redis.duplicate({\n        enableAutoPipelining: true,\n        // Infinite retries for blocking connections to prevent \"Max retries exceeded\" errors\n        maxRetriesPerRequest: null,\n        // Exponential backoff retry strategy\n        retryStrategy: (times: number) => {\n          return Math.max(Math.min(Math.exp(times) * 1000, 20000), 1000);\n        },\n      });\n\n      // Add reconnection handlers for resilience\n      this.blockingClient.on('error', (err) => {\n        if (!this.q.isConnectionError(err)) {\n          this.logger.error('Blocking client error (non-connection):', err);\n        } else {\n          this.logger.warn('Blocking client connection error:', err.message);\n        }\n        this.emit('error', err instanceof Error ? err : new Error(String(err)));\n      });\n\n      this.blockingClient.on('close', () => {\n        // Only log close if not during shutdown\n        if (!this.stopping && !this.closed) {\n          this.logger.warn(\n            'Blocking client disconnected, will reconnect on next operation',\n          );\n        }\n      });\n\n      this.blockingClient.on('reconnecting', () => {\n        if (!this.stopping && !this.closed) {\n          this.logger.info('Blocking client reconnecting...');\n        }\n      });\n\n      this.blockingClient.on('ready', () => {\n        if (!this.stopping && !this.closed) {\n          this.logger.info('Blocking client ready');\n        }\n      });\n    } catch (err) {\n      this.logger.error('Failed to create blocking client:', err);\n      this.blockingClient = null; // fall back to queue's blocking client\n    }\n\n    // Start cleanup timer if enabled\n    if (this.enableCleanup) {\n      // Cleanup timer: only runs cleanup, not scheduler\n      // Add jitter to prevent all workers from running cleanup simultaneously\n      this.cleanupTimer = setInterval(async () => {\n        try {\n          await this.q.cleanup();\n        } catch (err) {\n          this.onError?.(err);\n        }\n      }, this.addJitter(this.cleanupMs));\n\n      // Scheduler timer: promotes delayed jobs and processes cron jobs\n      // Runs independently in the background, even when worker is blocked on BZPOPMIN\n      // Distributed lock ensures only one worker executes at a time\n      const schedulerInterval = Math.min(this.schedulerMs, this.cleanupMs);\n      this.schedulerTimer = setInterval(async () => {\n        try {\n          await this.q.runSchedulerOnce();\n        } catch (_err) {\n          // Ignore errors, this is best-effort\n        }\n      }, this.addJitter(schedulerInterval));\n    }\n\n    // Start stalled job checker for automatic recovery\n    this.startStalledChecker();\n\n    let connectionRetries = 0;\n    const maxConnectionRetries = 10; // Allow more retries with exponential backoff\n\n    // BullMQ-style async queue for clean promise management\n    const asyncFifoQueue = new AsyncFifoQueue<void | ReservedJob<T> | null>(\n      true,\n    );\n\n    while (!this.stopping || asyncFifoQueue.numTotal() > 0) {\n      try {\n        // Phase 1: Fetch jobs efficiently until we reach concurrency capacity\n        // CRITICAL: Use asyncFifoQueue.numTotal() for concurrency control (matches BullMQ pattern)\n        // The queue tracks all promises (both fetch and processing), so this is accurate\n        while (!this.stopping) {\n          if (asyncFifoQueue.numTotal() >= this.concurrency) {\n            break; // At capacity, exit fetch loop\n          }\n\n          this.blockingStats.totalBlockingCalls++;\n\n          // Prevent overflow: reset counter after 1 billion calls (keeps number manageable)\n          if (this.blockingStats.totalBlockingCalls >= 1_000_000_000) {\n            this.blockingStats.totalBlockingCalls = 0;\n          }\n\n          this.logger.debug(\n            `Fetching job (call #${this.blockingStats.totalBlockingCalls}, processing: ${this.jobsInProgress.size}/${this.concurrency}, queue: ${asyncFifoQueue.numTotal()} (queued: ${asyncFifoQueue.numQueued()}, pending: ${asyncFifoQueue.numPending()}), total: ${asyncFifoQueue.numTotal()}/${this.concurrency})...`,\n          );\n\n          // Try batch reserve first for better efficiency\n          // Use batch reserve even for concurrency=1 since it's more efficient than blocking+atomic\n          // But limit batch size to available concurrency capacity\n          // Only batch reserve when queue is empty (process existing jobs first)\n          const availableCapacity =\n            this.concurrency - asyncFifoQueue.numTotal();\n          if (availableCapacity > 0 && asyncFifoQueue.numTotal() === 0) {\n            const batchSize = Math.min(availableCapacity, 8); // Cap at 8 for efficiency\n            const batchJobs = await this.q.reserveBatch(batchSize);\n\n            if (batchJobs.length > 0) {\n              this.logger.debug(`Batch reserved ${batchJobs.length} jobs`);\n              for (const job of batchJobs) {\n                asyncFifoQueue.add(Promise.resolve(job));\n              }\n              // Reset counters for successful batch\n              connectionRetries = 0;\n              this.lastJobPickupTime = Date.now();\n              this.blockingStats.consecutiveEmptyReserves = 0;\n              this.blockingStats.lastActivityTime = Date.now();\n              this.emptyReserveBackoffMs = 0;\n              continue; // Skip individual reserve\n            }\n          }\n\n          // BullMQ-style: only perform blocking reserve when truly drained\n          // Require 2 consecutive empty reserves before considering queue drained\n          // This prevents false positives from worker competition while staying responsive\n          // Check both queued/pending jobs AND actively processing jobs\n          const allowBlocking =\n            this.blockingStats.consecutiveEmptyReserves >= 2 &&\n            asyncFifoQueue.numTotal() === 0 &&\n            this.jobsInProgress.size === 0;\n\n          // Use a consistent blocking timeout - BullMQ style\n          // Job completion resets consecutiveEmptyReserves to 0, ensuring fast pickup\n          const adaptiveTimeout = this.blockingTimeoutSec;\n\n          const fetchedJob = allowBlocking\n            ? this.q.reserveBlocking(\n              adaptiveTimeout,\n              undefined, // blockUntil removed (was always 0, dead code)\n              this.blockingClient ?? undefined,\n            )\n            : this.q.reserve();\n\n          asyncFifoQueue.add(fetchedJob);\n\n          // Sequential fetching: wait for this fetch before next (prevents thundering herd)\n          const job = await fetchedJob;\n\n          if (job) {\n            // Reset connection retry count and empty reserves\n            connectionRetries = 0;\n            this.lastJobPickupTime = Date.now();\n            this.blockingStats.consecutiveEmptyReserves = 0;\n            this.blockingStats.lastActivityTime = Date.now();\n            this.emptyReserveBackoffMs = 0; // Reset backoff when we get a job\n\n            this.logger.debug(\n              `Fetched job ${job.id} from group ${job.groupId}`,\n            );\n          } else {\n            // No more jobs available - increment counter\n            this.blockingStats.consecutiveEmptyReserves++;\n\n            // Only log every 50th empty reserve to reduce spam\n            if (this.blockingStats.consecutiveEmptyReserves % 50 === 0) {\n              this.logger.debug(\n                `No job available (consecutive empty: ${this.blockingStats.consecutiveEmptyReserves})`,\n              );\n            }\n\n            // Only apply exponential backoff when queue is truly empty (no jobs processing)\n            // This prevents slowdown during the tail end when a few jobs are still processing\n            const backoffThreshold = this.concurrency >= 100 ? 5 : 3;\n            if (\n              this.blockingStats.consecutiveEmptyReserves > backoffThreshold &&\n              asyncFifoQueue.numTotal() === 0 && // No queued or pending jobs\n              this.jobsInProgress.size === 0 // Critical: only backoff when nothing is processing\n            ) {\n              // Adaptive backoff based on concurrency level\n              const maxBackoff = this.concurrency >= 100 ? 2000 : 5000;\n              if (this.emptyReserveBackoffMs === 0) {\n                this.emptyReserveBackoffMs = this.concurrency >= 100 ? 100 : 50;\n              } else {\n                this.emptyReserveBackoffMs = Math.min(\n                  maxBackoff,\n                  Math.max(100, this.emptyReserveBackoffMs * 1.2),\n                );\n              }\n\n              // Only log backoff every 20th time to reduce spam\n              if (this.blockingStats.consecutiveEmptyReserves % 20 === 0) {\n                this.logger.debug(\n                  `Applying backoff: ${Math.round(this.emptyReserveBackoffMs)}ms (consecutive empty: ${this.blockingStats.consecutiveEmptyReserves}, jobs in progress: ${this.jobsInProgress.size})`,\n                );\n              }\n\n              await this.delay(this.emptyReserveBackoffMs);\n            }\n\n            // BullMQ-inspired: Break immediately when no jobs found and queue is idle\n            // This prevents tight polling and allows backoff to work properly\n            if (\n              asyncFifoQueue.numTotal() === 0 &&\n              this.jobsInProgress.size === 0\n            ) {\n              break; // Fully idle - exit fetch loop\n            }\n\n            // If we have jobs queued/pending or processing, break to process them\n            if (asyncFifoQueue.numTotal() > 0 || this.jobsInProgress.size > 0) {\n              break;\n            }\n          }\n        }\n\n        // Phase 2: BullMQ-style - Fetch jobs and process immediately\n        // This is more responsive than batching, especially at high concurrency\n        let job: ReservedJob<T> | void;\n        do {\n          const fetchedJob = await asyncFifoQueue.fetch();\n          job = fetchedJob ?? undefined;\n        } while (!job && asyncFifoQueue.numQueued() > 0);\n\n        if (job && typeof job === 'object' && 'id' in job) {\n          // We fetched an actual job from the queue\n          this.totalJobsProcessed++;\n          this.logger.debug(\n            `Processing job ${job.id} from group ${job.groupId} immediately`,\n          );\n\n          // Add processing promise immediately, don't wait for completion\n          // The promise resolves to void or a chained job\n          // When it resolves to a chained job, that job will be fetched from the queue\n          // and processed by this same worker (maintaining atomic chaining)\n          const processingPromise = this.processJob(\n            job,\n            () => {\n              // Check if we have capacity for atomic chaining\n              // Use asyncFifoQueue.numTotal() to match BullMQ pattern\n              return asyncFifoQueue.numTotal() <= this.concurrency;\n            },\n            this.jobsInProgress,\n          );\n\n          asyncFifoQueue.add(processingPromise);\n        }\n        // Note: No delay here - just loop back to Phase 1 immediately\n        // The adaptive timeout in Phase 1's blocking reserve handles idle efficiently\n      } catch (err) {\n        if (this.stopping) {\n          return;\n        }\n        // Distinguish between connection errors (retry) and other errors (log and continue)\n        const isConnErr = this.q.isConnectionError(err);\n\n        if (isConnErr) {\n          // Connection error - retry with exponential backoff\n          connectionRetries++;\n\n          this.logger.error(\n            `Connection error (retry ${connectionRetries}/${maxConnectionRetries}):`,\n            err,\n          );\n\n          if (connectionRetries >= maxConnectionRetries) {\n            this.logger.error(\n              `⚠️  Max connection retries (${maxConnectionRetries}) exceeded! Worker will continue but may be experiencing persistent Redis issues.`,\n            );\n            this.emit(\n              'error',\n              new Error(\n                `Max connection retries (${maxConnectionRetries}) exceeded - worker continuing with backoff`,\n              ),\n            );\n            // Use maximum backoff delay before continuing\n            await this.delay(20000);\n            connectionRetries = 0; // Reset to continue trying\n          } else {\n            // Exponential backoff with 1s min, 20s max\n            const delayMs = Math.max(\n              Math.min(Math.exp(connectionRetries) * 1000, 20000),\n              1000,\n            );\n            this.logger.debug(\n              `Waiting ${Math.round(delayMs)}ms before retry (exponential backoff)`,\n            );\n            await this.delay(delayMs);\n          }\n        } else {\n          // Non-connection error (programming error, Lua script error, etc.)\n          // Log it, emit it, but don't retry - just continue with next iteration\n          this.logger.error(\n            `Worker loop error (non-connection, continuing):`,\n            err,\n          );\n          this.emit(\n            'error',\n            err instanceof Error ? err : new Error(String(err)),\n          );\n\n          // Reset connection retries since this wasn't a connection issue\n          connectionRetries = 0;\n\n          // Small delay to avoid tight error loops\n          await this.delay(100);\n        }\n\n        this.onError?.(err);\n      }\n    }\n\n    this.logger.info(`Stopped`);\n  }\n\n  private async delay(ms: number): Promise<void> {\n    return new Promise((resolve) => setTimeout(resolve, ms));\n  }\n\n  /**\n   * Process a job and return the next job if atomic completion succeeds\n   * This matches BullMQ's processJob signature\n   */\n  private async processJob(\n    job: ReservedJob<T>,\n    fetchNextCallback: () => boolean,\n    jobsInProgress: Set<{ job: ReservedJob<T>; ts: number }>,\n  ): Promise<void | ReservedJob<T>> {\n    // Check if this job is already tracked (it's a chained job)\n    const existingItem = Array.from(jobsInProgress).find(\n      (item) => item.job.id === job.id,\n    );\n\n    let inProgressItem: { job: ReservedJob<T>; ts: number };\n    if (existingItem) {\n      // Chained job - already tracked, just update timestamp\n      existingItem.ts = Date.now();\n      inProgressItem = existingItem;\n    } else {\n      // New job - add to tracking\n      inProgressItem = { job, ts: Date.now() };\n      jobsInProgress.add(inProgressItem);\n    }\n\n    try {\n      const nextJob = await this.processSingleJob(job, fetchNextCallback);\n\n      // If a chained job is returned from atomic completion:\n      // - It's already reserved in Redis (in processing set)\n      // - We need to track it in jobsInProgress BEFORE removing the original job\n      //   to maintain accurate concurrency tracking\n      // - This ensures totalInFlight calculation is correct\n      if (\n        nextJob &&\n        typeof nextJob === 'object' &&\n        'id' in nextJob &&\n        'groupId' in nextJob\n      ) {\n        // Add chained job to jobsInProgress before removing original\n        // This maintains accurate concurrency count during the transition\n        const chainedItem = { job: nextJob, ts: Date.now() };\n        jobsInProgress.add(chainedItem);\n        // Now remove original job - chained job takes its place\n        jobsInProgress.delete(inProgressItem);\n        return nextJob;\n      }\n\n      // No chained job - original job will be removed in finally block\n      return nextJob;\n    } finally {\n      // Only remove if not already removed (i.e., no chained job replaced it)\n      // Also check if this is still the same item (in case it was a chained job that got replaced)\n      if (jobsInProgress.has(inProgressItem)) {\n        jobsInProgress.delete(inProgressItem);\n      }\n    }\n  }\n\n  /**\n   * Complete a job and try to atomically get next job from same group\n   */\n  private async completeJob(\n    job: ReservedJob<T>,\n    handlerResult: unknown,\n    fetchNextCallback?: () => boolean,\n    processedOn?: number,\n    finishedOn?: number,\n  ): Promise<ReservedJob<T> | undefined> {\n    if (fetchNextCallback?.()) {\n      // Try atomic completion with next job reservation\n      const nextJob = await this.q.completeAndReserveNextWithMetadata(\n        job.id,\n        job.groupId,\n        handlerResult,\n        {\n          processedOn: processedOn || Date.now(),\n          finishedOn: finishedOn || Date.now(),\n          attempts: job.attempts,\n          maxAttempts: job.maxAttempts,\n        },\n      );\n      if (nextJob) {\n        this.logger.debug(\n          `Got next job ${nextJob.id} from same group ${nextJob.groupId} atomically`,\n        );\n        return nextJob;\n      }\n      // Atomic chaining failed - one of these scenarios:\n      // 1. Job was already completed/recovered (early return from Lua script)\n      // 2. Job was completed but no next job to chain (group empty, ordering delay, or not at active list head)\n      // In both cases, the job is properly completed and group is unlocked. No action needed.\n      this.logger.debug(\n        `Atomic chaining returned nil for job ${job.id} - job completed, but no next job chained`,\n      );\n\n      // CRITICAL: For high concurrency, add a small delay to prevent thundering herd\n      // This reduces the chance of multiple workers hitting the same race condition\n      if (Math.random() < 0.1) {\n        // 10% chance\n        await new Promise((resolve) =>\n          setTimeout(resolve, Math.random() * 100),\n        );\n      }\n    } else {\n      // Use completeWithMetadata for atomic completion with metadata\n      await this.q.completeWithMetadata(job, handlerResult, {\n        processedOn: processedOn || Date.now(),\n        finishedOn: finishedOn || Date.now(),\n        attempts: job.attempts,\n        maxAttempts: job.maxAttempts,\n      });\n    }\n\n    return undefined;\n  }\n\n  /**\n   * Start the stalled job checker\n   * Checks periodically for jobs that exceeded their deadline and recovers or fails them\n   */\n  private startStalledChecker(): void {\n    if (this.stalledInterval <= 0) {\n      return; // Disabled\n    }\n\n    this.stalledCheckTimer = setInterval(async () => {\n      try {\n        await this.checkStalled();\n      } catch (err) {\n        this.logger.error('Error in stalled job checker:', err);\n        this.emit('error', err instanceof Error ? err : new Error(String(err)));\n      }\n    }, this.stalledInterval);\n  }\n\n  /**\n   * Check for stalled jobs and recover or fail them\n   * A job is stalled when its worker crashed or lost connection\n   */\n  private async checkStalled(): Promise<void> {\n    if (this.stopping || this.closed) {\n      return;\n    }\n\n    try {\n      const now = Date.now();\n      const results = await this.q.checkStalledJobs(\n        now,\n        this.stalledGracePeriod,\n        this.maxStalledCount,\n      );\n\n      if (results.length > 0) {\n        // Process results in groups of 3: [jobId, groupId, action]\n        for (let i = 0; i < results.length; i += 3) {\n          const jobId = results[i];\n          const groupId = results[i + 1];\n          const action = results[i + 2];\n\n          if (action === 'recovered') {\n            this.logger.info(\n              `Recovered stalled job ${jobId} from group ${groupId}`,\n            );\n            this.emit('stalled', jobId, groupId);\n          } else if (action === 'failed') {\n            this.logger.warn(\n              `Failed stalled job ${jobId} from group ${groupId} (exceeded max stalled count)`,\n            );\n            this.emit('stalled', jobId, groupId);\n          }\n        }\n      }\n    } catch (err) {\n      // Don't throw, just log - stalled checker should be resilient\n      this.logger.error('Error checking stalled jobs:', err);\n    }\n  }\n\n  /**\n   * Get worker performance metrics\n   */\n  getWorkerMetrics() {\n    const now = Date.now();\n    return {\n      name: this.name,\n      totalJobsProcessed: this.totalJobsProcessed,\n      lastJobPickupTime: this.lastJobPickupTime,\n      timeSinceLastJob:\n        this.lastJobPickupTime > 0 ? now - this.lastJobPickupTime : null,\n      blockingStats: { ...this.blockingStats },\n      isProcessing: this.jobsInProgress.size > 0,\n      jobsInProgressCount: this.jobsInProgress.size,\n      jobsInProgress: Array.from(this.jobsInProgress).map((item) => ({\n        jobId: item.job.id,\n        groupId: item.job.groupId,\n        processingTimeMs: now - item.ts,\n      })),\n    };\n  }\n\n  /**\n   * Stop the worker gracefully\n   * @param gracefulTimeoutMs Maximum time to wait for current job to finish (default: 30 seconds)\n   */\n  async close(gracefulTimeoutMs = 30_000): Promise<void> {\n    this.stopping = true;\n    // Give some time if we just received a job\n    // Otherwise jobsInProgress will be 0 and we will exit immediately\n    await this.delay(100);\n\n    if (this.cleanupTimer) {\n      clearInterval(this.cleanupTimer);\n    }\n\n    if (this.schedulerTimer) {\n      clearInterval(this.schedulerTimer);\n    }\n\n    if (this.stalledCheckTimer) {\n      clearInterval(this.stalledCheckTimer);\n    }\n\n    // Wait for jobs to finish first\n    const startTime = Date.now();\n    while (\n      this.jobsInProgress.size > 0 &&\n      Date.now() - startTime < gracefulTimeoutMs\n    ) {\n      await sleep(100);\n    }\n\n    // Close the blocking client to interrupt any blocking operations\n    if (this.blockingClient) {\n      try {\n        if (this.jobsInProgress.size > 0 && gracefulTimeoutMs > 0) {\n          // Graceful: use quit() to allow in-flight commands to complete\n          this.logger.debug('Gracefully closing blocking client (quit)...');\n          await this.blockingClient.quit();\n        } else {\n          // Force or no jobs: use disconnect() for immediate termination\n          this.logger.debug('Force closing blocking client (disconnect)...');\n          this.blockingClient.disconnect();\n        }\n      } catch (err) {\n        // Swallow errors during close\n        this.logger.debug('Error closing blocking client:', err);\n      }\n      this.blockingClient = null;\n    }\n\n    // Now wait for the run loop to fully exit, but with a much shorter timeout\n    // Since we closed the blocking client, the run loop should exit immediately\n    if (this.runLoopPromise) {\n      const runLoopTimeout =\n        this.jobsInProgress.size > 0\n          ? gracefulTimeoutMs // If jobs are still running, use full timeout\n          : 2000; // Run loop should exit in 2 seconds after blocking client is closed\n\n      const timeoutPromise = new Promise<void>((resolve) => {\n        setTimeout(resolve, runLoopTimeout);\n      });\n\n      try {\n        await Promise.race([this.runLoopPromise, timeoutPromise]);\n      } catch (err) {\n        this.logger.warn('Error while waiting for run loop to exit:', err);\n      }\n    }\n\n    if (this.jobsInProgress.size > 0) {\n      this.logger.warn(\n        `Worker stopped with ${this.jobsInProgress.size} jobs still processing after ${gracefulTimeoutMs}ms timeout.`,\n      );\n      // Emit graceful-timeout event for each job still processing\n      const nowWall = Date.now();\n      for (const item of this.jobsInProgress) {\n        this.emit(\n          'graceful-timeout',\n          Job.fromReserved(this.q, item.job, {\n            processedOn: item.ts,\n            finishedOn: nowWall,\n            status: 'active',\n          }),\n        );\n      }\n    }\n\n    // Clear tracking\n    this.jobsInProgress.clear();\n    this.ready = false;\n    this.closed = true;\n\n    // Blocking client was already closed earlier to interrupt blocking operations\n\n    // Remove Redis event listeners to avoid leaks\n    try {\n      const redis = this.q.redis;\n      if (redis) {\n        if (this.redisCloseHandler)\n          redis.off?.('close', this.redisCloseHandler);\n        if (this.redisErrorHandler)\n          redis.off?.('error', this.redisErrorHandler);\n        if (this.redisReadyHandler)\n          redis.off?.('ready', this.redisReadyHandler);\n      }\n    } catch (_e) {\n      // ignore listener cleanup errors\n    }\n\n    // Emit closed event\n    this.emit('closed');\n  }\n\n  /**\n   * Get information about the first currently processing job (if any)\n   * For concurrency > 1, returns the oldest job in progress\n   */\n  getCurrentJob(): { job: ReservedJob<T>; processingTimeMs: number } | null {\n    if (this.jobsInProgress.size === 0) {\n      return null;\n    }\n\n    // Return the oldest job (first one added to the set)\n    const oldest = Array.from(this.jobsInProgress)[0];\n    const now = Date.now();\n    return {\n      job: oldest.job,\n      processingTimeMs: now - oldest.ts,\n    };\n  }\n\n  /**\n   * Get information about all currently processing jobs\n   */\n  getCurrentJobs(): Array<{ job: ReservedJob<T>; processingTimeMs: number }> {\n    const now = Date.now();\n    return Array.from(this.jobsInProgress).map((item) => ({\n      job: item.job,\n      processingTimeMs: now - item.ts,\n    }));\n  }\n\n  /**\n   * Check if the worker is currently processing any jobs\n   */\n  isProcessing(): boolean {\n    return this.jobsInProgress.size > 0;\n  }\n\n  // Proxy to the underlying queue.add with correct data typing inferred from the queue\n  async add(opts: AddOptions<T>) {\n    return this.q.add(opts);\n  }\n\n  private async processSingleJob(\n    job: ReservedJob<T>,\n    fetchNextCallback?: () => boolean,\n  ): Promise<void | ReservedJob<T>> {\n    const jobStartWallTime = Date.now();\n\n    let hbTimer: NodeJS.Timeout | undefined;\n    let heartbeatDelayTimer: NodeJS.Timeout | undefined;\n\n    const startHeartbeat = () => {\n      // BullMQ-inspired: Adaptive heartbeat interval based on concurrency\n      // CRITICAL: Heartbeat must run frequently enough to prevent stalled detection\n      // Run every jobTimeout/3 (with max 10s) to ensure multiple heartbeats before timeout\n      const jobTimeout = this.q.jobTimeoutMs || 30000;\n      const minInterval = Math.min(\n        this.hbMs, // Use the worker's configured heartbeat interval\n        Math.floor(jobTimeout / 3), // At least 3 heartbeats within timeout window\n        10000, // Cap at 10s maximum\n      );\n\n      this.logger.debug(\n        `Starting heartbeat for job ${job.id} (interval: ${minInterval}ms, concurrency: ${this.concurrency})`,\n      );\n\n      hbTimer = setInterval(async () => {\n        try {\n          const result = await this.q.heartbeat(job);\n          if (result === 0) {\n            // Job no longer exists or is not in processing state\n            this.logger.warn(\n              `Heartbeat failed for job ${job.id} - job may have been removed or completed elsewhere`,\n            );\n            // Stop heartbeat since job is gone\n            if (hbTimer) {\n              clearInterval(hbTimer);\n            }\n          }\n        } catch (e) {\n          // Only log heartbeat errors if they're not connection errors during shutdown\n          const isConnErr = this.q.isConnectionError(e);\n          if (!isConnErr || !this.stopping) {\n            this.logger.error(\n              `Heartbeat error for job ${job.id}:`,\n              e instanceof Error ? e.message : String(e),\n            );\n          }\n\n          this.onError?.(e, job);\n\n          // Only emit error if not a connection error during shutdown\n          if (!isConnErr || !this.stopping) {\n            this.emit('error', e instanceof Error ? e : new Error(String(e)));\n          }\n        }\n      }, minInterval);\n    };\n\n    try {\n      // BullMQ-inspired: Smart heartbeat with adaptive timing\n      // CRITICAL FIX: Start heartbeat much earlier to prevent false stalled detection\n      // Under high Redis load, jobs can be marked stalled before heartbeat even starts!\n      const jobTimeout = this.q.jobTimeoutMs || 30000;\n      // Start heartbeat after 10% of timeout OR 2 seconds (whichever is smaller)\n      // This ensures heartbeat is active long before stalled detection can trigger\n      const heartbeatThreshold = Math.min(jobTimeout * 0.1, 2000);\n\n      // Start heartbeat early for potentially long-running jobs\n      heartbeatDelayTimer = setTimeout(() => {\n        startHeartbeat();\n      }, heartbeatThreshold);\n\n      // Execute the user's handler\n      const handlerResult = await this.handler(job);\n\n      // Job finished quickly, cancel delayed heartbeat start\n      if (heartbeatDelayTimer) {\n        clearTimeout(heartbeatDelayTimer);\n      }\n\n      // Clean up heartbeat if it was started\n      if (hbTimer) {\n        clearInterval(hbTimer);\n      }\n\n      // Capture finish time before completing the job\n      const finishedAtWall = Date.now();\n\n      // Complete the job and optionally get next job from same group\n      const nextJob = await this.completeJob(\n        job,\n        handlerResult,\n        fetchNextCallback,\n        jobStartWallTime,\n        finishedAtWall,\n      );\n\n      // Reset adaptive timeout after successful job completion\n      // This ensures the worker uses the low timeout (0.1s) for the next fetch\n      this.blockingStats.consecutiveEmptyReserves = 0;\n      this.emptyReserveBackoffMs = 0;\n\n      // Emit completed event\n      this.emit(\n        'completed',\n        Job.fromReserved(this.q, job, {\n          processedOn: jobStartWallTime,\n          finishedOn: finishedAtWall,\n          returnvalue: handlerResult,\n          status: 'completed',\n        }),\n      );\n\n      // Return chained job if available and we have capacity\n      return nextJob;\n    } catch (err) {\n      // Clean up timers\n      if (heartbeatDelayTimer) {\n        clearTimeout(heartbeatDelayTimer);\n      }\n      if (hbTimer) {\n        clearInterval(hbTimer);\n      }\n      await this.handleJobFailure(err, job, jobStartWallTime);\n    }\n  }\n\n  /**\n   * Handle job failure: emit events, retry or dead-letter\n   */\n  private async handleJobFailure(\n    err: unknown,\n    job: ReservedJob<T>,\n    jobStartWallTime: number,\n  ): Promise<void> {\n    this.onError?.(err, job);\n\n    // Reset adaptive timeout after job failure\n    // This ensures the worker uses the low timeout (0.1s) for the next fetch\n    this.blockingStats.consecutiveEmptyReserves = 0;\n    this.emptyReserveBackoffMs = 0;\n\n    // Safely emit error event\n    try {\n      this.emit('error', err instanceof Error ? err : new Error(String(err)));\n    } catch (_emitError) {\n      // Silently ignore emit errors\n    }\n\n    const failedAt = Date.now();\n\n    // Emit failed event\n    this.emit(\n      'failed',\n      Job.fromReserved(this.q, job, {\n        processedOn: jobStartWallTime,\n        finishedOn: failedAt,\n        failedReason: err instanceof Error ? err.message : String(err),\n        stacktrace:\n          err instanceof Error\n            ? err.stack\n            : typeof err === 'object' && err !== null\n              ? (err as any).stack\n              : undefined,\n        status: 'failed',\n      }),\n    );\n\n    // Calculate next attempt and backoff\n    const nextAttempt = job.attempts + 1;\n    const backoffMs = this.backoff(nextAttempt);\n\n    // Check if we should dead-letter (max attempts reached)\n    if (nextAttempt >= this.maxAttempts) {\n      await this.deadLetterJob(\n        err,\n        job,\n        jobStartWallTime,\n        failedAt,\n        nextAttempt,\n      );\n      return;\n    }\n\n    // Retry the job\n    const retryResult = await this.q.retry(job.id, backoffMs);\n    if (retryResult === -1) {\n      // Queue-level max attempts exceeded\n      await this.deadLetterJob(\n        err,\n        job,\n        jobStartWallTime,\n        failedAt,\n        job.maxAttempts,\n      );\n      return;\n    }\n\n    // Record attempt failure\n    await this.recordFailureAttempt(\n      err,\n      job,\n      jobStartWallTime,\n      failedAt,\n      nextAttempt,\n    );\n  }\n\n  /**\n   * Dead-letter a job that exceeded max attempts\n   */\n  private async deadLetterJob(\n    err: unknown,\n    job: ReservedJob<T>,\n    processedOn: number,\n    finishedOn: number,\n    attempts: number,\n  ): Promise<void> {\n    this.logger.info(\n      `Dead lettering job ${job.id} from group ${job.groupId} (attempts: ${attempts}/${job.maxAttempts})`,\n    );\n\n    const errObj = err instanceof Error ? err : new Error(String(err));\n\n    try {\n      await this.q.recordFinalFailure(\n        { id: job.id, groupId: job.groupId },\n        { name: errObj.name, message: errObj.message, stack: errObj.stack },\n        {\n          processedOn,\n          finishedOn,\n          attempts,\n          maxAttempts: job.maxAttempts,\n          data: job.data,\n        },\n      );\n    } catch (e) {\n      this.logger.warn('Failed to record final failure', e);\n    }\n\n    await this.q.deadLetter(job.id, job.groupId);\n  }\n\n  /**\n   * Record a failed attempt (not final)\n   */\n  private async recordFailureAttempt(\n    err: unknown,\n    job: ReservedJob<T>,\n    processedOn: number,\n    finishedOn: number,\n    attempts: number,\n  ): Promise<void> {\n    const errObj = err instanceof Error ? err : new Error(String(err));\n\n    try {\n      await this.q.recordAttemptFailure(\n        { id: job.id, groupId: job.groupId },\n        { name: errObj.name, message: errObj.message, stack: errObj.stack },\n        {\n          processedOn,\n          finishedOn,\n          attempts,\n          maxAttempts: job.maxAttempts,\n        },\n      );\n    } catch (e) {\n      this.logger.warn('Failed to record attempt failure', e);\n    }\n  }\n}\n\n// Export a value with a generic constructor so T is inferred from opts.queue\nexport type Worker<T = any> = _Worker<T>;\ntype WorkerConstructor = new <T>(opts: WorkerOptions<T>) => _Worker<T>;\nexport const Worker = _Worker as unknown as WorkerConstructor;\n\nfunction sleep(ms: number) {\n  return new Promise((r) => setTimeout(r, ms));\n}\n"],"x_google_ignoreList":[0],"mappings":"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"}