@boringnode/queue 0.0.1-alpha → 0.0.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/job.ts","../src/worker.ts","../src/queue_manager.ts","../src/strategies/backoff_strategy.ts"],"sourcesContent":["import { JobDispatcher } from '#src/job_dispatcher'\nimport type { JobOptions } from '#types/main'\n\nexport abstract class Job<Payload = any> {\n readonly #payload: Payload\n\n static options: JobOptions = {}\n\n get payload(): Payload {\n return this.#payload\n }\n\n constructor(payload: Payload) {\n this.#payload = payload\n }\n\n static dispatch<T extends Job>(\n this: new (payload: any) => T,\n payload: T extends Job<infer P> ? P : never\n ): JobDispatcher<T extends Job<infer P> ? P : never> {\n const dispatcher = new JobDispatcher<T extends Job<infer P> ? P : never>(\n (this as any).jobName,\n payload\n )\n\n if ((this as any).options.queue) {\n dispatcher.toQueue((this as any).options.queue)\n }\n\n if ((this as any).options.adapter) {\n dispatcher.with((this as any).options.adapter)\n }\n\n if ((this as any).options.priority !== undefined) {\n dispatcher.priority((this as any).options.priority)\n }\n\n return dispatcher\n }\n\n abstract execute(): Promise<void>\n\n failed?(error: Error): Promise<void>\n}\n","import { randomUUID } from 'node:crypto'\nimport { setTimeout } from 'node:timers/promises'\nimport debug from '#src/debug'\nimport { parse } from '#src/utils'\nimport * as errors from '#src/exceptions'\nimport { QueueManager } from '#src/queue_manager'\nimport type { Adapter } from '#contracts/adapter'\nimport type { LeaseManager } from '#contracts/lease_manager'\nimport type { AcquiredJob, JobData, QueueManagerConfig, WorkerCycle } from '#types/main'\nimport { Locator } from '#src/locator'\nimport type { JobOptions } from '#types/main'\n\nexport class Worker {\n readonly #id: string\n readonly #config: QueueManagerConfig\n #adapter!: Adapter\n #leaseManager!: LeaseManager\n #running = false\n #initialized = false\n #generator?: AsyncGenerator<WorkerCycle, void, unknown>\n\n get id() {\n return this.#id\n }\n\n constructor(config: QueueManagerConfig) {\n this.#config = config\n this.#id = randomUUID()\n\n debug('created worker with id %s and config %O', this.#id, config)\n }\n\n async init() {\n if (this.#initialized) {\n return\n }\n\n debug('initializing worker %s', this.#id)\n\n await QueueManager.init(this.#config)\n\n this.#adapter = QueueManager.use()\n this.#leaseManager = this.#adapter.createLeaseManager({\n workerId: this.#id,\n leaseTimeout: parse(this.#config.worker?.leaseTimeout || '5m'),\n renewalInterval: parse(this.#config.worker?.renewalInterval || '5m'),\n })\n\n this.#initialized = true\n\n debug('worker %s initialized', this.#id)\n }\n\n async start(queues: string[] = ['default']): Promise<void> {\n await this.init()\n\n if (this.#running) {\n debug('worker %s is already running', this.#id)\n return\n }\n\n this.#running = true\n\n debug('starting worker %s on queues: %O', this.#id, queues)\n\n await this.#setupGracefulShutdown()\n\n for await (const cycle of this.process(queues)) {\n if (['started', 'completed'].includes(cycle.type)) {\n continue\n }\n\n if (['idle', 'error'].includes(cycle.type)) {\n // @ts-expect-error - we know suggestedDelay exists for these types\n const delay = parse(cycle.suggestedDelay)\n\n if (cycle.type === 'error') {\n debug('worker %s encountered an error: %O', this.#id, cycle.error)\n } else {\n debug('worker %s is idle, waiting for %dms', this.#id, delay)\n }\n\n await setTimeout(delay)\n }\n }\n }\n\n async stop() {\n debug('stopping worker %s', this.#id)\n\n this.#running = false\n\n if (this.#leaseManager) {\n await this.#leaseManager.destroy()\n }\n\n if (this.#adapter) {\n await this.#adapter.destroy()\n }\n }\n\n async processCycle(queues: string[]): Promise<WorkerCycle | null> {\n await this.init()\n\n this.#running = true\n\n if (!this.#generator) {\n this.#generator = this.process(queues)\n }\n\n const result = await this.#generator.next()\n\n if (result.done) {\n this.#generator = undefined\n return null\n }\n\n return result.value\n }\n\n async *process(queues: string[]): AsyncGenerator<WorkerCycle, void, unknown> {\n const concurrency = this.#config.worker?.concurrency || 1\n\n runningLoop: while (this.#running) {\n try {\n for (const queue of queues) {\n const jobs = await this.#acquireJobs(queue, concurrency)\n\n if (jobs.length > 0) {\n // Yield started events for all jobs\n for (const job of jobs) {\n yield { type: 'started', queue, job }\n }\n\n // Execute all jobs in parallel\n const results = await Promise.allSettled(jobs.map((job) => this.#execute(job, queue)))\n\n // Yield completed events for all jobs\n for (const job of jobs) {\n yield { type: 'completed', queue, job: job }\n }\n\n // Check if any job failed\n const hasError = results.some((r) => r.status === 'rejected')\n if (hasError) {\n const error = results.find((r) => r.status === 'rejected') as PromiseRejectedResult\n yield { type: 'error', error: error.reason, suggestedDelay: parse('5s') }\n }\n\n continue runningLoop\n }\n }\n\n const pollingInterval = parse(this.#config.worker?.pollingInterval || '2s')\n yield { type: 'idle', suggestedDelay: pollingInterval }\n } catch (error) {\n yield { type: 'error', error: error as Error, suggestedDelay: parse('5s') }\n }\n }\n }\n\n async #execute(job: AcquiredJob, queue: string): Promise<void> {\n const startTime = performance.now()\n\n debug('worker %s: executing job %s (%s)', this.#id, job.id, job.name)\n\n let JobClass: any\n\n try {\n JobClass = Locator.getOrThrow(job.name)\n } catch (error) {\n debug('worker %s: job class %s not found for job %s', this.#id, job.name, job.id)\n throw error\n }\n\n const instance = new JobClass(job.payload)\n const options: JobOptions = JobClass.options || {}\n\n try {\n await instance.execute()\n await job._lease.commit()\n\n const duration = (performance.now() - startTime).toFixed(2)\n debug('worker %s: successfully executed job %s in %dms', this.#id, job.id, duration)\n } catch (e) {\n const mergedConfig = QueueManager.getMergedRetryConfig(queue, options.retry)\n\n if (typeof mergedConfig.maxRetries === 'undefined' || mergedConfig.maxRetries <= 0) {\n debug('worker %s: job %s has no retries configured, marking as failed', this.#id, job.id)\n\n await instance.failed(e as Error)\n return\n }\n\n if (job.attempts >= mergedConfig.maxRetries!) {\n debug(\n 'worker %s: job %s has exceeded max retries (%d), marking as failed',\n this.#id,\n job.id,\n mergedConfig.maxRetries\n )\n\n const exception = new errors.E_JOB_MAX_ATTEMPTS_REACHED([job.name])\n await instance.failed(exception)\n\n return\n }\n\n if (mergedConfig.backoff) {\n const strategy = mergedConfig.backoff()\n const nextRetryAt = strategy.getNextRetryAt(job.attempts + 1)\n\n debug('worker %s: job %s will retry at %s', this.#id, job.id, nextRetryAt.toISOString())\n\n await this.#rollbackJobWithBackoff(job, queue, nextRetryAt)\n\n return\n }\n\n await this.#rollbackJob(job, queue)\n }\n }\n\n async #acquireJobs(queue: string, count: number): Promise<AcquiredJob[]> {\n const jobs: AcquiredJob[] = []\n\n // Try to acquire up to `count` jobs\n for (let i = 0; i < count; i++) {\n const job = await this.#adapter.popFrom(queue)\n\n if (!job) {\n break\n }\n\n debug('worker %s: attempting to acquire lease for job %s', this.#id, job.id)\n\n try {\n const acquired = await this.#leaseManager.acquire(job.id)\n\n if (!acquired) {\n debug('worker %s: failed to acquire lease for job %s', this.#id, job.id)\n\n await this.#adapter.pushOn(queue, job)\n continue\n }\n\n debug('worker %s: acquired lease for job %s', this.#id, job.id)\n\n jobs.push({\n ...job,\n _lease: {\n commit: () => this.#commitJob(job.id),\n rollback: () => this.#rollbackJob(job, queue),\n },\n })\n } catch (error) {\n console.log(error)\n throw error\n }\n }\n\n return jobs\n }\n\n #commitJob(jobId: string) {\n debug('worker %s: committing job %s', this.#id, jobId)\n return this.#leaseManager.release(jobId)\n }\n\n async #rollbackJob(job: JobData, queue: string) {\n debug('worker %s: rolling back job %s', this.#id, job.id)\n\n const updatedJob = {\n ...job,\n attempts: (job.attempts || 0) + 1,\n }\n\n await Promise.all([this.#leaseManager.release(job.id), this.#adapter.pushOn(queue, updatedJob)])\n }\n\n async #rollbackJobWithBackoff(job: JobData, queue: string, nextRetryAt: Date) {\n debug('worker %s: rolling back job %s with backoff', this.#id, job.id)\n\n const updatedJob = {\n ...job,\n attempts: (job.attempts || 0) + 1,\n nextRetryAt,\n }\n\n await this.#leaseManager.release(job.id)\n\n const delay = nextRetryAt.getTime() - Date.now()\n\n if (delay > 0) {\n await this.#adapter.pushLaterOn(queue, updatedJob, delay)\n } else {\n await this.#adapter.pushOn(queue, updatedJob)\n }\n }\n\n async #setupGracefulShutdown() {\n const shutdown = async () => {\n debug('received shutdown signal, stopping worker...')\n await this.stop()\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n }\n}\n","import * as errors from '#src/exceptions'\nimport debug from '#src/debug'\nimport { Locator } from '#src/locator'\nimport type { Adapter } from '#contracts/adapter'\nimport type { AdapterFactory, QueueConfig, QueueManagerConfig, RetryConfig } from '#types/main'\n\nclass QueueManagerSingleton {\n #defaultAdapter!: string\n #adapters: Record<string, AdapterFactory> = {}\n #globalRetryConfig?: RetryConfig\n #queueConfigs: Map<string, QueueConfig> = new Map()\n\n async init(config: QueueManagerConfig) {\n debug('initializing queue manager with config: %O', config)\n\n this.#validateConfig(config)\n\n this.#defaultAdapter = config.default\n this.#adapters = config.adapters\n this.#globalRetryConfig = config.retry\n\n if (config.queues) {\n for (const [queue, queueConfig] of Object.entries(config.queues)) {\n this.#queueConfigs.set(queue, queueConfig as QueueConfig)\n }\n }\n\n await Locator.registerFromGlob(config.locations)\n\n return this\n }\n\n use(adapter?: string): Adapter {\n if (!adapter) {\n adapter = this.#defaultAdapter\n }\n\n const adapterInstance = this.#adapters[adapter]\n\n if (!adapterInstance) {\n throw new errors.E_CONFIGURATION_ERROR([`Adapter \"${adapter}\" is not registered`])\n }\n\n debug('using adapter \"%s\"', adapter)\n\n try {\n return adapterInstance()\n } catch (error) {\n // TODO: Improve error handling\n throw new Error()\n // throw new errors.E_ADAPTER_ERROR(`Failed to initialize adapter \"${adapter}\"`, error as Error)\n }\n }\n\n /**\n * Priority: job > queue > global\n */\n getMergedRetryConfig(queue: string, jobRetryConfig?: RetryConfig): RetryConfig {\n const queueConfig = this.#queueConfigs.get(queue)\n const queueRetryConfig = queueConfig?.retry || {}\n\n let maxRetries =\n jobRetryConfig?.maxRetries ||\n queueRetryConfig.maxRetries ||\n this.#globalRetryConfig?.maxRetries ||\n 0\n\n let backoff =\n jobRetryConfig?.backoff || queueRetryConfig.backoff || this.#globalRetryConfig?.backoff\n\n return { maxRetries, backoff }\n }\n\n #validateConfig(config: QueueManagerConfig): void {\n if (!config.adapters || Object.keys(config.adapters).length === 0) {\n throw new errors.E_CONFIGURATION_ERROR(['At least one adapter must be configured'])\n }\n\n if (!config.default) {\n throw new errors.E_CONFIGURATION_ERROR(['Default adapter must be specified'])\n }\n\n if (!config.locations || config.locations.length === 0) {\n throw new errors.E_CONFIGURATION_ERROR(['Job locations must be specified'])\n }\n\n if (!config.adapters[config.default]) {\n throw new errors.E_CONFIGURATION_ERROR([\n `Default adapter \"${config.default}\" not found in adapters configuration`,\n ])\n }\n\n for (const [name, factory] of Object.entries(config.adapters)) {\n if (typeof factory !== 'function') {\n throw new errors.E_CONFIGURATION_ERROR([`Adapter \"${name}\" must be a factory function`])\n }\n }\n }\n\n async destroy() {\n for (const adapterName in this.#adapters) {\n const adapter = this.#adapters[adapterName]()\n await adapter.destroy()\n }\n }\n}\n\nexport const QueueManager = new QueueManagerSingleton()\n","import type { BackoffConfig, Duration } from '#types/main'\nimport * as errors from '#src/exceptions'\nimport { parse } from '#src/utils'\nimport { RuntimeException } from '@poppinss/utils'\nimport { assertUnreachable } from '@poppinss/utils/assert'\n\nexport class BackoffStrategy {\n readonly #config: BackoffConfig\n\n constructor(config: BackoffConfig) {\n this.#config = config\n this.#validateConfig()\n }\n\n calculateDelay(attempt: number): number {\n if (attempt < 1) {\n throw new RuntimeException('Attempt number must be >= 1')\n }\n\n const baseDelayMs = parse(this.#config.baseDelay)\n const maxDelayMs = this.#config.maxDelay ? parse(this.#config.maxDelay) : Infinity\n const multiplier = this.#config.multiplier ?? 2\n\n let delay: number\n\n switch (this.#config.strategy) {\n case 'exponential':\n delay = baseDelayMs * Math.pow(multiplier, attempt - 1)\n break\n case 'linear':\n delay = baseDelayMs * attempt\n break\n case 'fixed':\n delay = baseDelayMs\n break\n default:\n assertUnreachable(this.#config.strategy)\n }\n\n // Apply max delay limit\n delay = Math.min(delay, maxDelayMs)\n\n if (this.#config.jitter) {\n delay = this.#applyJitter(delay)\n }\n\n return Math.floor(delay)\n }\n\n getNextRetryAt(attempt: number): Date {\n const delay = this.calculateDelay(attempt)\n return new Date(Date.now() + delay)\n }\n\n getConfig(): Readonly<BackoffConfig> {\n return Object.freeze({ ...this.#config })\n }\n\n #validateConfig() {\n const baseDelayMs = parse(this.#config.baseDelay)\n\n if (baseDelayMs <= 0) {\n throw new errors.E_INVALID_BASE_DELAY([\n 'Base delay must be a positive integer greater than zero',\n ])\n }\n\n if (this.#config.maxDelay) {\n const maxDelayMs = parse(this.#config.maxDelay)\n\n if (maxDelayMs <= 0) {\n throw new errors.E_INVALID_MAX_DELAY([\n 'Max delay must be a positive integer greater than zero',\n ])\n }\n\n if (maxDelayMs <= baseDelayMs) {\n throw new errors.E_INVALID_MAX_DELAY(['Max delay should be greater than base delay'])\n }\n }\n\n if (this.#config.multiplier !== undefined) {\n if (this.#config.multiplier <= 0) {\n throw new errors.E_INVALID_MULTIPLIER([\n 'Multiplier must be a positive number greater than zero',\n ])\n }\n\n if (this.#config.strategy === 'exponential' && this.#config.multiplier < 1) {\n throw new errors.E_INVALID_MULTIPLIER(['Exponential strategy multiplier should be >= 1'])\n }\n }\n }\n\n #applyJitter(delay: number): number {\n const jitterRange = delay * 0.25\n const jitter = (Math.random() - 0.5) * 2 * jitterRange\n\n return Math.max(0, delay + jitter)\n }\n}\n\nexport function exponentialBackoff(config?: Partial<Omit<BackoffConfig, 'strategy'>>) {\n return () =>\n new BackoffStrategy({\n strategy: 'exponential',\n baseDelay: '1s',\n maxDelay: '5m',\n multiplier: 2,\n jitter: true,\n ...config,\n })\n}\n\nexport function linearBackoff(config?: Partial<Omit<BackoffConfig, 'strategy'>>) {\n return () =>\n new BackoffStrategy({\n strategy: 'linear',\n baseDelay: '5s',\n maxDelay: '2m',\n ...config,\n })\n}\n\nexport function fixedBackoff(delay: Duration = '10s') {\n return () =>\n new BackoffStrategy({\n strategy: 'fixed',\n baseDelay: delay,\n })\n}\n\nexport function customBackoff(config: BackoffConfig) {\n return () => new BackoffStrategy(config)\n}\n"],"mappings":"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","names":["errors","debug","Locator","QueueManager","errors","parse"]}
1
+ {"version":3,"sources":["../src/job.ts","../src/worker.ts","../src/queue_manager.ts","../src/strategies/backoff_strategy.ts"],"sourcesContent":["import { JobDispatcher } from '#src/job_dispatcher'\nimport type { JobOptions } from '#types/main'\n\nexport abstract class Job<Payload = any> {\n readonly #payload: Payload\n\n static options: JobOptions = {}\n\n get payload(): Payload {\n return this.#payload\n }\n\n constructor(payload: Payload) {\n this.#payload = payload\n }\n\n static dispatch<T extends Job>(\n this: new (payload: any) => T,\n payload: T extends Job<infer P> ? P : never\n ): JobDispatcher<T extends Job<infer P> ? P : never> {\n const dispatcher = new JobDispatcher<T extends Job<infer P> ? P : never>(\n (this as any).jobName,\n payload\n )\n\n if ((this as any).options.queue) {\n dispatcher.toQueue((this as any).options.queue)\n }\n\n if ((this as any).options.adapter) {\n dispatcher.with((this as any).options.adapter)\n }\n\n if ((this as any).options.priority !== undefined) {\n dispatcher.priority((this as any).options.priority)\n }\n\n return dispatcher\n }\n\n abstract execute(signal?: AbortSignal): Promise<void>\n\n failed?(error: Error): Promise<void>\n}\n","import { randomUUID } from 'node:crypto'\nimport { setTimeout } from 'node:timers/promises'\nimport debug from '#src/debug'\nimport { parse } from '#src/utils'\nimport * as errors from '#src/exceptions'\nimport { QueueManager } from '#src/queue_manager'\nimport { JobPool } from '#src/job_pool'\nimport type { Adapter, AcquiredJob } from '#contracts/adapter'\nimport type { QueueManagerConfig, WorkerCycle } from '#types/main'\nimport { Locator } from '#src/locator'\nimport type { JobOptions } from '#types/main'\nimport type { Job } from '#src/job'\n\nexport class Worker {\n readonly #id: string\n readonly #config: QueueManagerConfig\n #adapter!: Adapter\n #running = false\n #initialized = false\n #generator?: AsyncGenerator<WorkerCycle, void, unknown>\n #pool?: JobPool\n\n get id() {\n return this.#id\n }\n\n constructor(config: QueueManagerConfig) {\n this.#config = config\n this.#id = randomUUID()\n\n debug('created worker with id %s and config %O', this.#id, config)\n }\n\n async init() {\n if (this.#initialized) {\n return\n }\n\n debug('initializing worker %s', this.#id)\n\n await QueueManager.init(this.#config)\n\n this.#adapter = QueueManager.use()\n this.#adapter.setWorkerId(this.#id)\n\n this.#initialized = true\n\n debug('worker %s initialized', this.#id)\n }\n\n async start(queues: string[] = ['default']): Promise<void> {\n await this.init()\n\n if (this.#running) {\n debug('worker %s is already running', this.#id)\n return\n }\n\n this.#running = true\n\n debug('starting worker %s on queues: %O', this.#id, queues)\n\n await this.#setupGracefulShutdown()\n\n for await (const cycle of this.process(queues)) {\n if (['started', 'completed'].includes(cycle.type)) {\n continue\n }\n\n if (['idle', 'error'].includes(cycle.type)) {\n // @ts-expect-error - we know suggestedDelay exists for these types\n const delay = parse(cycle.suggestedDelay)\n\n if (cycle.type === 'error') {\n debug('worker %s encountered an error: %O', this.#id, cycle.error)\n } else {\n debug('worker %s is idle, waiting for %dms', this.#id, delay)\n }\n\n await setTimeout(delay)\n }\n }\n }\n\n async stop() {\n debug('stopping worker %s', this.#id)\n\n this.#running = false\n\n if (this.#pool) {\n debug('worker %s: waiting for %d running jobs to complete', this.#id, this.#pool.size)\n await this.#pool.drain()\n }\n\n if (this.#adapter) {\n await this.#adapter.destroy()\n }\n }\n\n async processCycle(queues: string[]): Promise<WorkerCycle | null> {\n await this.init()\n\n this.#running = true\n\n if (!this.#generator) {\n this.#generator = this.process(queues)\n }\n\n const result = await this.#generator.next()\n\n if (result.done) {\n this.#generator = undefined\n return null\n }\n\n return result.value\n }\n\n async *process(queues: string[]): AsyncGenerator<WorkerCycle, void, unknown> {\n const pollingInterval = parse(this.#config.worker?.pollingInterval || '2s')\n this.#pool = new JobPool()\n\n while (this.#running) {\n try {\n yield* this.#fillPool(queues)\n\n if (this.#pool.isEmpty()) {\n yield { type: 'idle', suggestedDelay: pollingInterval }\n continue\n }\n\n const completed = await this.#pool.waitForNextCompletion()\n yield { type: 'completed', queue: completed.queue, job: completed.job }\n } catch (error) {\n yield { type: 'error', error: error as Error, suggestedDelay: parse('5s') }\n }\n }\n }\n\n async *#fillPool(queues: string[]): AsyncGenerator<WorkerCycle, void, unknown> {\n const concurrency = this.#config.worker?.concurrency || 1\n const slotsAvailable = concurrency - this.#pool!.size\n\n if (slotsAvailable <= 0) return\n\n const popPromises = Array.from({ length: slotsAvailable }, () => this.#acquireNextJob(queues))\n\n const results = await Promise.all(popPromises)\n\n for (const result of results) {\n if (!result) continue\n\n const { job, queue } = result\n const promise = this.#execute(job, queue)\n this.#pool!.add(job, queue, promise)\n\n yield { type: 'started', queue, job }\n }\n }\n\n async #execute(job: AcquiredJob, queue: string): Promise<void> {\n const startTime = performance.now()\n\n debug('worker %s: executing job %s (%s)', this.#id, job.id, job.name)\n\n const { instance, options, timeout } = await this.#initJob(job, queue)\n\n try {\n await this.#executeWithTimeout(instance, timeout)\n await this.#adapter.completeJob(job.id, queue)\n\n const duration = (performance.now() - startTime).toFixed(2)\n debug('worker %s: successfully executed job %s in %dms', this.#id, job.id, duration)\n } catch (e) {\n const isTimeout = e instanceof errors.E_JOB_TIMEOUT\n\n if (isTimeout && options.failOnTimeout) {\n debug('worker %s: job %s timed out and failOnTimeout is set', this.#id, job.id)\n await this.#adapter.failJob(job.id, queue, e as Error)\n await instance.failed?.(e as Error)\n return\n }\n\n const mergedConfig = QueueManager.getMergedRetryConfig(queue, options.retry)\n\n if (typeof mergedConfig.maxRetries === 'undefined' || mergedConfig.maxRetries <= 0) {\n debug('worker %s: job %s has no retries configured, marking as failed', this.#id, job.id)\n await this.#adapter.failJob(job.id, queue, e as Error)\n await instance.failed?.(e as Error)\n return\n }\n\n if (job.attempts >= mergedConfig.maxRetries!) {\n debug(\n 'worker %s: job %s has exceeded max retries (%d), marking as failed',\n this.#id,\n job.id,\n mergedConfig.maxRetries\n )\n await this.#adapter.failJob(job.id, queue, e as Error)\n const exception = new errors.E_JOB_MAX_ATTEMPTS_REACHED([job.name])\n await instance.failed?.(exception)\n\n return\n }\n\n if (mergedConfig.backoff) {\n const strategy = mergedConfig.backoff()\n const nextRetryAt = strategy.getNextRetryAt(job.attempts + 1)\n\n debug('worker %s: job %s will retry at %s', this.#id, job.id, nextRetryAt.toISOString())\n\n await this.#adapter.retryJob(job.id, queue, nextRetryAt)\n return\n }\n\n await this.#adapter.retryJob(job.id, queue)\n }\n }\n\n async #initJob(\n job: AcquiredJob,\n queue: string\n ): Promise<{ instance: Job; options: JobOptions; timeout: number | undefined }> {\n try {\n const JobClass = Locator.getOrThrow(job.name)\n const instance = new JobClass(job.payload)\n const options = JobClass.options || {}\n const timeout = this.#getJobTimeout(options)\n\n return { instance, options, timeout }\n } catch (error) {\n debug('worker %s: failed to initialize job %s (%s)', this.#id, job.id, job.name)\n await this.#adapter.failJob(job.id, queue, error as Error)\n throw error\n }\n }\n\n #getJobTimeout(options: JobOptions): number | undefined {\n if (options.timeout !== undefined) {\n return parse(options.timeout)\n }\n\n if (this.#config.worker?.timeout !== undefined) {\n return parse(this.#config.worker.timeout)\n }\n\n return undefined\n }\n\n async #executeWithTimeout(instance: Job, timeout?: number): Promise<void> {\n if (!timeout) {\n return instance.execute()\n }\n\n const signal = AbortSignal.timeout(timeout)\n\n const abortPromise = new Promise<never>((_, reject) => {\n signal.addEventListener('abort', () => {\n reject(new errors.E_JOB_TIMEOUT([instance.constructor.name, timeout]))\n })\n })\n\n await Promise.race([instance.execute(signal), abortPromise])\n }\n\n async #acquireNextJob(queues: string[]): Promise<{ job: AcquiredJob; queue: string } | null> {\n for (const queue of queues) {\n const job = await this.#adapter.popFrom(queue)\n\n if (!job) {\n continue\n }\n\n debug('worker %s: acquired job %s', this.#id, job.id)\n return { job, queue }\n }\n\n return null\n }\n\n async #setupGracefulShutdown() {\n const shutdown = async () => {\n debug('received shutdown signal, stopping worker...')\n await this.stop()\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n }\n}\n","import * as errors from '#src/exceptions'\nimport debug from '#src/debug'\nimport { Locator } from '#src/locator'\nimport type { Adapter } from '#contracts/adapter'\nimport type { AdapterFactory, QueueConfig, QueueManagerConfig, RetryConfig } from '#types/main'\n\nclass QueueManagerSingleton {\n #defaultAdapter!: string\n #adapters: Record<string, AdapterFactory> = {}\n #adapterInstances: Map<string, Adapter> = new Map()\n #globalRetryConfig?: RetryConfig\n #queueConfigs: Map<string, QueueConfig> = new Map()\n\n async init(config: QueueManagerConfig) {\n debug('initializing queue manager with config: %O', config)\n\n this.#validateConfig(config)\n\n this.#adapterInstances.clear()\n\n this.#defaultAdapter = config.default\n this.#adapters = config.adapters\n this.#globalRetryConfig = config.retry\n\n if (config.queues) {\n for (const [queue, queueConfig] of Object.entries(config.queues)) {\n this.#queueConfigs.set(queue, queueConfig as QueueConfig)\n }\n }\n\n await Locator.registerFromGlob(config.locations)\n\n return this\n }\n\n use(adapter?: string): Adapter {\n if (!adapter) {\n adapter = this.#defaultAdapter\n }\n\n // Return cached instance if exists\n const cached = this.#adapterInstances.get(adapter)\n if (cached) {\n return cached\n }\n\n const adapterFactory = this.#adapters[adapter]\n\n if (!adapterFactory) {\n throw new errors.E_CONFIGURATION_ERROR([`Adapter \"${adapter}\" is not registered`])\n }\n\n debug('using adapter \"%s\"', adapter)\n\n try {\n const instance = adapterFactory()\n this.#adapterInstances.set(adapter, instance)\n return instance\n } catch (error) {\n // TODO: Improve error handling\n throw new Error()\n // throw new errors.E_ADAPTER_ERROR(`Failed to initialize adapter \"${adapter}\"`, error as Error)\n }\n }\n\n /**\n * Priority: job > queue > global\n */\n getMergedRetryConfig(queue: string, jobRetryConfig?: RetryConfig): RetryConfig {\n const queueConfig = this.#queueConfigs.get(queue)\n const queueRetryConfig = queueConfig?.retry || {}\n\n let maxRetries =\n jobRetryConfig?.maxRetries ||\n queueRetryConfig.maxRetries ||\n this.#globalRetryConfig?.maxRetries ||\n 0\n\n let backoff =\n jobRetryConfig?.backoff || queueRetryConfig.backoff || this.#globalRetryConfig?.backoff\n\n return { maxRetries, backoff }\n }\n\n #validateConfig(config: QueueManagerConfig): void {\n if (!config.adapters || Object.keys(config.adapters).length === 0) {\n throw new errors.E_CONFIGURATION_ERROR(['At least one adapter must be configured'])\n }\n\n if (!config.default) {\n throw new errors.E_CONFIGURATION_ERROR(['Default adapter must be specified'])\n }\n\n if (!config.locations || config.locations.length === 0) {\n throw new errors.E_CONFIGURATION_ERROR(['Job locations must be specified'])\n }\n\n if (!config.adapters[config.default]) {\n throw new errors.E_CONFIGURATION_ERROR([\n `Default adapter \"${config.default}\" not found in adapters configuration`,\n ])\n }\n\n for (const [name, factory] of Object.entries(config.adapters)) {\n if (typeof factory !== 'function') {\n throw new errors.E_CONFIGURATION_ERROR([`Adapter \"${name}\" must be a factory function`])\n }\n }\n }\n\n async destroy() {\n for (const [name, adapter] of this.#adapterInstances) {\n debug('destroying adapter \"%s\"', name)\n await adapter.destroy()\n }\n this.#adapterInstances.clear()\n }\n}\n\nexport const QueueManager = new QueueManagerSingleton()\n","import type { BackoffConfig, Duration } from '#types/main'\nimport * as errors from '#src/exceptions'\nimport { parse } from '#src/utils'\nimport { RuntimeException } from '@poppinss/utils'\nimport { assertUnreachable } from '@poppinss/utils/assert'\n\nexport class BackoffStrategy {\n readonly #config: BackoffConfig\n\n constructor(config: BackoffConfig) {\n this.#config = config\n this.#validateConfig()\n }\n\n calculateDelay(attempt: number): number {\n if (attempt < 1) {\n throw new RuntimeException('Attempt number must be >= 1')\n }\n\n const baseDelayMs = parse(this.#config.baseDelay)\n const maxDelayMs = this.#config.maxDelay ? parse(this.#config.maxDelay) : Infinity\n const multiplier = this.#config.multiplier ?? 2\n\n let delay: number\n\n switch (this.#config.strategy) {\n case 'exponential':\n delay = baseDelayMs * Math.pow(multiplier, attempt - 1)\n break\n case 'linear':\n delay = baseDelayMs * attempt\n break\n case 'fixed':\n delay = baseDelayMs\n break\n default:\n assertUnreachable(this.#config.strategy)\n }\n\n // Apply max delay limit\n delay = Math.min(delay, maxDelayMs)\n\n if (this.#config.jitter) {\n delay = this.#applyJitter(delay)\n }\n\n return Math.floor(delay)\n }\n\n getNextRetryAt(attempt: number): Date {\n const delay = this.calculateDelay(attempt)\n return new Date(Date.now() + delay)\n }\n\n getConfig(): Readonly<BackoffConfig> {\n return Object.freeze({ ...this.#config })\n }\n\n #validateConfig() {\n const baseDelayMs = parse(this.#config.baseDelay)\n\n if (baseDelayMs <= 0) {\n throw new errors.E_INVALID_BASE_DELAY([\n 'Base delay must be a positive integer greater than zero',\n ])\n }\n\n if (this.#config.maxDelay) {\n const maxDelayMs = parse(this.#config.maxDelay)\n\n if (maxDelayMs <= 0) {\n throw new errors.E_INVALID_MAX_DELAY([\n 'Max delay must be a positive integer greater than zero',\n ])\n }\n\n if (maxDelayMs <= baseDelayMs) {\n throw new errors.E_INVALID_MAX_DELAY(['Max delay should be greater than base delay'])\n }\n }\n\n if (this.#config.multiplier !== undefined) {\n if (this.#config.multiplier <= 0) {\n throw new errors.E_INVALID_MULTIPLIER([\n 'Multiplier must be a positive number greater than zero',\n ])\n }\n\n if (this.#config.strategy === 'exponential' && this.#config.multiplier < 1) {\n throw new errors.E_INVALID_MULTIPLIER(['Exponential strategy multiplier should be >= 1'])\n }\n }\n }\n\n #applyJitter(delay: number): number {\n const jitterRange = delay * 0.25\n const jitter = (Math.random() - 0.5) * 2 * jitterRange\n\n return Math.max(0, delay + jitter)\n }\n}\n\nexport function exponentialBackoff(config?: Partial<Omit<BackoffConfig, 'strategy'>>) {\n return () =>\n new BackoffStrategy({\n strategy: 'exponential',\n baseDelay: '1s',\n maxDelay: '5m',\n multiplier: 2,\n jitter: true,\n ...config,\n })\n}\n\nexport function linearBackoff(config?: Partial<Omit<BackoffConfig, 'strategy'>>) {\n return () =>\n new BackoffStrategy({\n strategy: 'linear',\n baseDelay: '5s',\n maxDelay: '2m',\n ...config,\n })\n}\n\nexport function fixedBackoff(delay: Duration = '10s') {\n return () =>\n new BackoffStrategy({\n strategy: 'fixed',\n baseDelay: delay,\n })\n}\n\nexport function customBackoff(config: BackoffConfig) {\n return () => new BackoffStrategy(config)\n}\n"],"mappings":";AAAA,SAAS,qBAAqB;AAGvB,IAAe,MAAf,MAAkC;AAAA,EAC9B;AAAA,EAET,OAAO,UAAsB,CAAC;AAAA,EAE9B,IAAI,UAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,YAAY,SAAkB;AAC5B,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,OAAO,SAEL,SACmD;AACnD,UAAM,aAAa,IAAI;AAAA,MACpB,KAAa;AAAA,MACd;AAAA,IACF;AAEA,QAAK,KAAa,QAAQ,OAAO;AAC/B,iBAAW,QAAS,KAAa,QAAQ,KAAK;AAAA,IAChD;AAEA,QAAK,KAAa,QAAQ,SAAS;AACjC,iBAAW,KAAM,KAAa,QAAQ,OAAO;AAAA,IAC/C;AAEA,QAAK,KAAa,QAAQ,aAAa,QAAW;AAChD,iBAAW,SAAU,KAAa,QAAQ,QAAQ;AAAA,IACpD;AAEA,WAAO;AAAA,EACT;AAKF;;;AC3CA,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,OAAO,WAAW;AAClB,SAAS,aAAa;AACtB,YAAY,YAAY;AACxB,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AAGxB,SAAS,eAAe;AAIjB,IAAM,SAAN,MAAa;AAAA,EACT;AAAA,EACA;AAAA,EACT;AAAA,EACA,WAAW;AAAA,EACX,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EAEA,IAAI,KAAK;AACP,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,YAAY,QAA4B;AACtC,SAAK,UAAU;AACf,SAAK,MAAM,WAAW;AAEtB,UAAM,2CAA2C,KAAK,KAAK,MAAM;AAAA,EACnE;AAAA,EAEA,MAAM,OAAO;AACX,QAAI,KAAK,cAAc;AACrB;AAAA,IACF;AAEA,UAAM,0BAA0B,KAAK,GAAG;AAExC,UAAM,aAAa,KAAK,KAAK,OAAO;AAEpC,SAAK,WAAW,aAAa,IAAI;AACjC,SAAK,SAAS,YAAY,KAAK,GAAG;AAElC,SAAK,eAAe;AAEpB,UAAM,yBAAyB,KAAK,GAAG;AAAA,EACzC;AAAA,EAEA,MAAM,MAAM,SAAmB,CAAC,SAAS,GAAkB;AACzD,UAAM,KAAK,KAAK;AAEhB,QAAI,KAAK,UAAU;AACjB,YAAM,gCAAgC,KAAK,GAAG;AAC9C;AAAA,IACF;AAEA,SAAK,WAAW;AAEhB,UAAM,oCAAoC,KAAK,KAAK,MAAM;AAE1D,UAAM,KAAK,uBAAuB;AAElC,qBAAiB,SAAS,KAAK,QAAQ,MAAM,GAAG;AAC9C,UAAI,CAAC,WAAW,WAAW,EAAE,SAAS,MAAM,IAAI,GAAG;AACjD;AAAA,MACF;AAEA,UAAI,CAAC,QAAQ,OAAO,EAAE,SAAS,MAAM,IAAI,GAAG;AAE1C,cAAM,QAAQ,MAAM,MAAM,cAAc;AAExC,YAAI,MAAM,SAAS,SAAS;AAC1B,gBAAM,sCAAsC,KAAK,KAAK,MAAM,KAAK;AAAA,QACnE,OAAO;AACL,gBAAM,uCAAuC,KAAK,KAAK,KAAK;AAAA,QAC9D;AAEA,cAAM,WAAW,KAAK;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,OAAO;AACX,UAAM,sBAAsB,KAAK,GAAG;AAEpC,SAAK,WAAW;AAEhB,QAAI,KAAK,OAAO;AACd,YAAM,sDAAsD,KAAK,KAAK,KAAK,MAAM,IAAI;AACrF,YAAM,KAAK,MAAM,MAAM;AAAA,IACzB;AAEA,QAAI,KAAK,UAAU;AACjB,YAAM,KAAK,SAAS,QAAQ;AAAA,IAC9B;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,QAA+C;AAChE,UAAM,KAAK,KAAK;AAEhB,SAAK,WAAW;AAEhB,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,aAAa,KAAK,QAAQ,MAAM;AAAA,IACvC;AAEA,UAAM,SAAS,MAAM,KAAK,WAAW,KAAK;AAE1C,QAAI,OAAO,MAAM;AACf,WAAK,aAAa;AAClB,aAAO;AAAA,IACT;AAEA,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,OAAO,QAAQ,QAA8D;AAC3E,UAAM,kBAAkB,MAAM,KAAK,QAAQ,QAAQ,mBAAmB,IAAI;AAC1E,SAAK,QAAQ,IAAI,QAAQ;AAEzB,WAAO,KAAK,UAAU;AACpB,UAAI;AACF,eAAO,KAAK,UAAU,MAAM;AAE5B,YAAI,KAAK,MAAM,QAAQ,GAAG;AACxB,gBAAM,EAAE,MAAM,QAAQ,gBAAgB,gBAAgB;AACtD;AAAA,QACF;AAEA,cAAM,YAAY,MAAM,KAAK,MAAM,sBAAsB;AACzD,cAAM,EAAE,MAAM,aAAa,OAAO,UAAU,OAAO,KAAK,UAAU,IAAI;AAAA,MACxE,SAAS,OAAO;AACd,cAAM,EAAE,MAAM,SAAS,OAAuB,gBAAgB,MAAM,IAAI,EAAE;AAAA,MAC5E;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO,UAAU,QAA8D;AAC7E,UAAM,cAAc,KAAK,QAAQ,QAAQ,eAAe;AACxD,UAAM,iBAAiB,cAAc,KAAK,MAAO;AAEjD,QAAI,kBAAkB,EAAG;AAEzB,UAAM,cAAc,MAAM,KAAK,EAAE,QAAQ,eAAe,GAAG,MAAM,KAAK,gBAAgB,MAAM,CAAC;AAE7F,UAAM,UAAU,MAAM,QAAQ,IAAI,WAAW;AAE7C,eAAW,UAAU,SAAS;AAC5B,UAAI,CAAC,OAAQ;AAEb,YAAM,EAAE,KAAK,MAAM,IAAI;AACvB,YAAM,UAAU,KAAK,SAAS,KAAK,KAAK;AACxC,WAAK,MAAO,IAAI,KAAK,OAAO,OAAO;AAEnC,YAAM,EAAE,MAAM,WAAW,OAAO,IAAI;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,KAAkB,OAA8B;AAC7D,UAAM,YAAY,YAAY,IAAI;AAElC,UAAM,oCAAoC,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI;AAEpE,UAAM,EAAE,UAAU,SAAS,QAAQ,IAAI,MAAM,KAAK,SAAS,KAAK,KAAK;AAErE,QAAI;AACF,YAAM,KAAK,oBAAoB,UAAU,OAAO;AAChD,YAAM,KAAK,SAAS,YAAY,IAAI,IAAI,KAAK;AAE7C,YAAM,YAAY,YAAY,IAAI,IAAI,WAAW,QAAQ,CAAC;AAC1D,YAAM,mDAAmD,KAAK,KAAK,IAAI,IAAI,QAAQ;AAAA,IACrF,SAAS,GAAG;AACV,YAAM,YAAY,aAAoB;AAEtC,UAAI,aAAa,QAAQ,eAAe;AACtC,cAAM,wDAAwD,KAAK,KAAK,IAAI,EAAE;AAC9E,cAAM,KAAK,SAAS,QAAQ,IAAI,IAAI,OAAO,CAAU;AACrD,cAAM,SAAS,SAAS,CAAU;AAClC;AAAA,MACF;AAEA,YAAM,eAAe,aAAa,qBAAqB,OAAO,QAAQ,KAAK;AAE3E,UAAI,OAAO,aAAa,eAAe,eAAe,aAAa,cAAc,GAAG;AAClF,cAAM,kEAAkE,KAAK,KAAK,IAAI,EAAE;AACxF,cAAM,KAAK,SAAS,QAAQ,IAAI,IAAI,OAAO,CAAU;AACrD,cAAM,SAAS,SAAS,CAAU;AAClC;AAAA,MACF;AAEA,UAAI,IAAI,YAAY,aAAa,YAAa;AAC5C;AAAA,UACE;AAAA,UACA,KAAK;AAAA,UACL,IAAI;AAAA,UACJ,aAAa;AAAA,QACf;AACA,cAAM,KAAK,SAAS,QAAQ,IAAI,IAAI,OAAO,CAAU;AACrD,cAAM,YAAY,IAAW,kCAA2B,CAAC,IAAI,IAAI,CAAC;AAClE,cAAM,SAAS,SAAS,SAAS;AAEjC;AAAA,MACF;AAEA,UAAI,aAAa,SAAS;AACxB,cAAM,WAAW,aAAa,QAAQ;AACtC,cAAM,cAAc,SAAS,eAAe,IAAI,WAAW,CAAC;AAE5D,cAAM,sCAAsC,KAAK,KAAK,IAAI,IAAI,YAAY,YAAY,CAAC;AAEvF,cAAM,KAAK,SAAS,SAAS,IAAI,IAAI,OAAO,WAAW;AACvD;AAAA,MACF;AAEA,YAAM,KAAK,SAAS,SAAS,IAAI,IAAI,KAAK;AAAA,IAC5C;AAAA,EACF;AAAA,EAEA,MAAM,SACJ,KACA,OAC8E;AAC9E,QAAI;AACF,YAAM,WAAW,QAAQ,WAAW,IAAI,IAAI;AAC5C,YAAM,WAAW,IAAI,SAAS,IAAI,OAAO;AACzC,YAAM,UAAU,SAAS,WAAW,CAAC;AACrC,YAAM,UAAU,KAAK,eAAe,OAAO;AAE3C,aAAO,EAAE,UAAU,SAAS,QAAQ;AAAA,IACtC,SAAS,OAAO;AACd,YAAM,+CAA+C,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI;AAC/E,YAAM,KAAK,SAAS,QAAQ,IAAI,IAAI,OAAO,KAAc;AACzD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,eAAe,SAAyC;AACtD,QAAI,QAAQ,YAAY,QAAW;AACjC,aAAO,MAAM,QAAQ,OAAO;AAAA,IAC9B;AAEA,QAAI,KAAK,QAAQ,QAAQ,YAAY,QAAW;AAC9C,aAAO,MAAM,KAAK,QAAQ,OAAO,OAAO;AAAA,IAC1C;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,oBAAoB,UAAe,SAAiC;AACxE,QAAI,CAAC,SAAS;AACZ,aAAO,SAAS,QAAQ;AAAA,IAC1B;AAEA,UAAM,SAAS,YAAY,QAAQ,OAAO;AAE1C,UAAM,eAAe,IAAI,QAAe,CAAC,GAAG,WAAW;AACrD,aAAO,iBAAiB,SAAS,MAAM;AACrC,eAAO,IAAW,qBAAc,CAAC,SAAS,YAAY,MAAM,OAAO,CAAC,CAAC;AAAA,MACvE,CAAC;AAAA,IACH,CAAC;AAED,UAAM,QAAQ,KAAK,CAAC,SAAS,QAAQ,MAAM,GAAG,YAAY,CAAC;AAAA,EAC7D;AAAA,EAEA,MAAM,gBAAgB,QAAuE;AAC3F,eAAW,SAAS,QAAQ;AAC1B,YAAM,MAAM,MAAM,KAAK,SAAS,QAAQ,KAAK;AAE7C,UAAI,CAAC,KAAK;AACR;AAAA,MACF;AAEA,YAAM,8BAA8B,KAAK,KAAK,IAAI,EAAE;AACpD,aAAO,EAAE,KAAK,MAAM;AAAA,IACtB;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,yBAAyB;AAC7B,UAAM,WAAW,YAAY;AAC3B,YAAM,8CAA8C;AACpD,YAAM,KAAK,KAAK;AAChB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,GAAG,UAAU,QAAQ;AAC7B,YAAQ,GAAG,WAAW,QAAQ;AAAA,EAChC;AACF;;;ACnSA,YAAYA,aAAY;AACxB,OAAOC,YAAW;AAClB,SAAS,WAAAC,gBAAe;AAIxB,IAAM,wBAAN,MAA4B;AAAA,EAC1B;AAAA,EACA,YAA4C,CAAC;AAAA,EAC7C,oBAA0C,oBAAI,IAAI;AAAA,EAClD;AAAA,EACA,gBAA0C,oBAAI,IAAI;AAAA,EAElD,MAAM,KAAK,QAA4B;AACrC,IAAAD,OAAM,8CAA8C,MAAM;AAE1D,SAAK,gBAAgB,MAAM;AAE3B,SAAK,kBAAkB,MAAM;AAE7B,SAAK,kBAAkB,OAAO;AAC9B,SAAK,YAAY,OAAO;AACxB,SAAK,qBAAqB,OAAO;AAEjC,QAAI,OAAO,QAAQ;AACjB,iBAAW,CAAC,OAAO,WAAW,KAAK,OAAO,QAAQ,OAAO,MAAM,GAAG;AAChE,aAAK,cAAc,IAAI,OAAO,WAA0B;AAAA,MAC1D;AAAA,IACF;AAEA,UAAMC,SAAQ,iBAAiB,OAAO,SAAS;AAE/C,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,SAA2B;AAC7B,QAAI,CAAC,SAAS;AACZ,gBAAU,KAAK;AAAA,IACjB;AAGA,UAAM,SAAS,KAAK,kBAAkB,IAAI,OAAO;AACjD,QAAI,QAAQ;AACV,aAAO;AAAA,IACT;AAEA,UAAM,iBAAiB,KAAK,UAAU,OAAO;AAE7C,QAAI,CAAC,gBAAgB;AACnB,YAAM,IAAW,8BAAsB,CAAC,YAAY,OAAO,qBAAqB,CAAC;AAAA,IACnF;AAEA,IAAAD,OAAM,sBAAsB,OAAO;AAEnC,QAAI;AACF,YAAM,WAAW,eAAe;AAChC,WAAK,kBAAkB,IAAI,SAAS,QAAQ;AAC5C,aAAO;AAAA,IACT,SAAS,OAAO;AAEd,YAAM,IAAI,MAAM;AAAA,IAElB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAqB,OAAe,gBAA2C;AAC7E,UAAM,cAAc,KAAK,cAAc,IAAI,KAAK;AAChD,UAAM,mBAAmB,aAAa,SAAS,CAAC;AAEhD,QAAI,aACF,gBAAgB,cAChB,iBAAiB,cACjB,KAAK,oBAAoB,cACzB;AAEF,QAAI,UACF,gBAAgB,WAAW,iBAAiB,WAAW,KAAK,oBAAoB;AAElF,WAAO,EAAE,YAAY,QAAQ;AAAA,EAC/B;AAAA,EAEA,gBAAgB,QAAkC;AAChD,QAAI,CAAC,OAAO,YAAY,OAAO,KAAK,OAAO,QAAQ,EAAE,WAAW,GAAG;AACjE,YAAM,IAAW,8BAAsB,CAAC,yCAAyC,CAAC;AAAA,IACpF;AAEA,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAW,8BAAsB,CAAC,mCAAmC,CAAC;AAAA,IAC9E;AAEA,QAAI,CAAC,OAAO,aAAa,OAAO,UAAU,WAAW,GAAG;AACtD,YAAM,IAAW,8BAAsB,CAAC,iCAAiC,CAAC;AAAA,IAC5E;AAEA,QAAI,CAAC,OAAO,SAAS,OAAO,OAAO,GAAG;AACpC,YAAM,IAAW,8BAAsB;AAAA,QACrC,oBAAoB,OAAO,OAAO;AAAA,MACpC,CAAC;AAAA,IACH;AAEA,eAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,OAAO,QAAQ,GAAG;AAC7D,UAAI,OAAO,YAAY,YAAY;AACjC,cAAM,IAAW,8BAAsB,CAAC,YAAY,IAAI,8BAA8B,CAAC;AAAA,MACzF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,UAAU;AACd,eAAW,CAAC,MAAM,OAAO,KAAK,KAAK,mBAAmB;AACpD,MAAAA,OAAM,2BAA2B,IAAI;AACrC,YAAM,QAAQ,QAAQ;AAAA,IACxB;AACA,SAAK,kBAAkB,MAAM;AAAA,EAC/B;AACF;AAEO,IAAME,gBAAe,IAAI,sBAAsB;;;ACtHtD,YAAYC,aAAY;AACxB,SAAS,SAAAC,cAAa;AACtB,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAE3B,IAAM,kBAAN,MAAsB;AAAA,EAClB;AAAA,EAET,YAAY,QAAuB;AACjC,SAAK,UAAU;AACf,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,eAAe,SAAyB;AACtC,QAAI,UAAU,GAAG;AACf,YAAM,IAAI,iBAAiB,6BAA6B;AAAA,IAC1D;AAEA,UAAM,cAAcA,OAAM,KAAK,QAAQ,SAAS;AAChD,UAAM,aAAa,KAAK,QAAQ,WAAWA,OAAM,KAAK,QAAQ,QAAQ,IAAI;AAC1E,UAAM,aAAa,KAAK,QAAQ,cAAc;AAE9C,QAAI;AAEJ,YAAQ,KAAK,QAAQ,UAAU;AAAA,MAC7B,KAAK;AACH,gBAAQ,cAAc,KAAK,IAAI,YAAY,UAAU,CAAC;AACtD;AAAA,MACF,KAAK;AACH,gBAAQ,cAAc;AACtB;AAAA,MACF,KAAK;AACH,gBAAQ;AACR;AAAA,MACF;AACE,0BAAkB,KAAK,QAAQ,QAAQ;AAAA,IAC3C;AAGA,YAAQ,KAAK,IAAI,OAAO,UAAU;AAElC,QAAI,KAAK,QAAQ,QAAQ;AACvB,cAAQ,KAAK,aAAa,KAAK;AAAA,IACjC;AAEA,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB;AAAA,EAEA,eAAe,SAAuB;AACpC,UAAM,QAAQ,KAAK,eAAe,OAAO;AACzC,WAAO,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK;AAAA,EACpC;AAAA,EAEA,YAAqC;AACnC,WAAO,OAAO,OAAO,EAAE,GAAG,KAAK,QAAQ,CAAC;AAAA,EAC1C;AAAA,EAEA,kBAAkB;AAChB,UAAM,cAAcA,OAAM,KAAK,QAAQ,SAAS;AAEhD,QAAI,eAAe,GAAG;AACpB,YAAM,IAAW,6BAAqB;AAAA,QACpC;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,KAAK,QAAQ,UAAU;AACzB,YAAM,aAAaA,OAAM,KAAK,QAAQ,QAAQ;AAE9C,UAAI,cAAc,GAAG;AACnB,cAAM,IAAW,4BAAoB;AAAA,UACnC;AAAA,QACF,CAAC;AAAA,MACH;AAEA,UAAI,cAAc,aAAa;AAC7B,cAAM,IAAW,4BAAoB,CAAC,6CAA6C,CAAC;AAAA,MACtF;AAAA,IACF;AAEA,QAAI,KAAK,QAAQ,eAAe,QAAW;AACzC,UAAI,KAAK,QAAQ,cAAc,GAAG;AAChC,cAAM,IAAW,6BAAqB;AAAA,UACpC;AAAA,QACF,CAAC;AAAA,MACH;AAEA,UAAI,KAAK,QAAQ,aAAa,iBAAiB,KAAK,QAAQ,aAAa,GAAG;AAC1E,cAAM,IAAW,6BAAqB,CAAC,gDAAgD,CAAC;AAAA,MAC1F;AAAA,IACF;AAAA,EACF;AAAA,EAEA,aAAa,OAAuB;AAClC,UAAM,cAAc,QAAQ;AAC5B,UAAM,UAAU,KAAK,OAAO,IAAI,OAAO,IAAI;AAE3C,WAAO,KAAK,IAAI,GAAG,QAAQ,MAAM;AAAA,EACnC;AACF;AAEO,SAAS,mBAAmB,QAAmD;AACpF,SAAO,MACL,IAAI,gBAAgB;AAAA,IAClB,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,GAAG;AAAA,EACL,CAAC;AACL;AAEO,SAAS,cAAc,QAAmD;AAC/E,SAAO,MACL,IAAI,gBAAgB;AAAA,IAClB,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV,GAAG;AAAA,EACL,CAAC;AACL;AAEO,SAAS,aAAa,QAAkB,OAAO;AACpD,SAAO,MACL,IAAI,gBAAgB;AAAA,IAClB,UAAU;AAAA,IACV,WAAW;AAAA,EACb,CAAC;AACL;AAEO,SAAS,cAAc,QAAuB;AACnD,SAAO,MAAM,IAAI,gBAAgB,MAAM;AACzC;","names":["errors","debug","Locator","QueueManager","errors","parse"]}
@@ -1,5 +1,3 @@
1
- import { LeaseManager } from './src/contracts/lease_manager.js';
2
-
3
1
  declare class BackoffStrategy$1 {
4
2
  #private;
5
3
  constructor(config: BackoffConfig);
@@ -13,12 +11,6 @@ declare function fixedBackoff(delay?: Duration): () => BackoffStrategy$1;
13
11
  declare function customBackoff(config: BackoffConfig): () => BackoffStrategy$1;
14
12
 
15
13
  type Duration = number | string;
16
- interface AcquiredJob extends JobData {
17
- _lease: {
18
- commit: () => Promise<void>;
19
- rollback: () => Promise<void>;
20
- };
21
- }
22
14
  interface JobData {
23
15
  id: string;
24
16
  name: string;
@@ -33,8 +25,12 @@ interface JobOptions {
33
25
  maxRetries?: number;
34
26
  priority?: number;
35
27
  retry?: RetryConfig;
28
+ timeout?: Duration;
29
+ failOnTimeout?: boolean;
36
30
  }
37
- type JobClass<T extends Job = Job> = new (payload: any) => T;
31
+ type JobClass<T extends Job = Job> = (new (payload: any) => T) & {
32
+ options?: JobOptions;
33
+ };
38
34
  interface RetryConfig {
39
35
  maxRetries?: number;
40
36
  backoff?: () => BackoffStrategy$1;
@@ -47,11 +43,6 @@ interface BackoffConfig {
47
43
  multiplier?: number;
48
44
  jitter?: boolean;
49
45
  }
50
- interface LeaseConfig {
51
- workerId: string;
52
- leaseTimeout: Duration;
53
- renewalInterval: Duration;
54
- }
55
46
  interface QueueConfig {
56
47
  adapter?: string;
57
48
  retry?: any;
@@ -61,6 +52,7 @@ interface WorkerConfig {
61
52
  pollingInterval?: Duration;
62
53
  leaseTimeout?: Duration;
63
54
  renewalInterval?: Duration;
55
+ timeout?: Duration;
64
56
  }
65
57
  type WorkerCycle = {
66
58
  type: 'started';
@@ -88,16 +80,48 @@ interface QueueManagerConfig {
88
80
  locations: string[];
89
81
  }
90
82
 
83
+ interface AcquiredJob extends JobData {
84
+ acquiredAt: number;
85
+ }
91
86
  interface Adapter {
92
- createLeaseManager(config: LeaseConfig): LeaseManager;
93
- size(): Promise<number>;
94
- sizeOf(queue: string): Promise<number>;
87
+ /**
88
+ * Set the worker ID for this adapter instance.
89
+ * Required before calling pop methods when consuming jobs.
90
+ */
91
+ setWorkerId(workerId: string): void;
92
+ /**
93
+ * Pop the next available job from the default queue.
94
+ * The driver handles locking internally.
95
+ */
96
+ pop(): Promise<AcquiredJob | null>;
97
+ /**
98
+ * Pop the next available job from a specific queue.
99
+ * The driver handles locking internally.
100
+ */
101
+ popFrom(queue: string): Promise<AcquiredJob | null>;
102
+ /**
103
+ * Blocking pop that waits for a job to be available.
104
+ * Supported by Redis adapter.
105
+ */
106
+ popAndWait?(queue: string, timeout: number): Promise<AcquiredJob | null>;
107
+ /**
108
+ * Mark a job as completed and remove it from active set.
109
+ */
110
+ completeJob(jobId: string, queue: string): Promise<void>;
111
+ /**
112
+ * Mark a job as failed permanently.
113
+ */
114
+ failJob(jobId: string, queue: string, error?: Error): Promise<void>;
115
+ /**
116
+ * Retry a job - move back to pending queue with incremented attempts.
117
+ */
118
+ retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void>;
95
119
  push(jobData: JobData): Promise<void>;
96
120
  pushOn(queue: string, jobData: JobData): Promise<void>;
97
121
  pushLater(jobData: JobData, delay: number): Promise<void>;
98
122
  pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void>;
99
- pop(): Promise<JobData | null>;
100
- popFrom(queue: string): Promise<JobData | null>;
123
+ size(): Promise<number>;
124
+ sizeOf(queue: string): Promise<number>;
101
125
  destroy(): Promise<void>;
102
126
  }
103
127
 
@@ -118,8 +142,8 @@ declare abstract class Job<Payload = any> {
118
142
  get payload(): Payload;
119
143
  constructor(payload: Payload);
120
144
  static dispatch<T extends Job>(this: new (payload: any) => T, payload: T extends Job<infer P> ? P : never): JobDispatcher<T extends Job<infer P> ? P : never>;
121
- abstract execute(): Promise<void>;
145
+ abstract execute(signal?: AbortSignal): Promise<void>;
122
146
  failed?(error: Error): Promise<void>;
123
147
  }
124
148
 
125
- export { type Adapter as A, type BackoffStrategy as B, type Duration as D, Job as J, type LeaseConfig as L, type QueueManagerConfig as Q, type RetryConfig as R, type WorkerCycle as W, type JobData as a, type AcquiredJob as b, customBackoff as c, type JobOptions as d, exponentialBackoff as e, fixedBackoff as f, type JobClass as g, type BackoffConfig as h, type QueueConfig as i, type WorkerConfig as j, type AdapterFactory as k, linearBackoff as l };
149
+ export { type Adapter as A, type BackoffStrategy as B, type Duration as D, Job as J, type QueueManagerConfig as Q, type RetryConfig as R, type WorkerCycle as W, type AcquiredJob as a, type JobData as b, customBackoff as c, type JobOptions as d, exponentialBackoff as e, fixedBackoff as f, type JobClass as g, type BackoffConfig as h, type QueueConfig as i, type WorkerConfig as j, type AdapterFactory as k, linearBackoff as l };
@@ -1,2 +1 @@
1
- import './lease_manager.js';
2
- export { A as Adapter } from '../../job-CcAUWe8j.js';
1
+ export { a as AcquiredJob, A as Adapter } from '../../job-Bd_c2lFK.js';
@@ -0,0 +1,43 @@
1
+ import { Knex } from 'knex';
2
+ import { A as Adapter, a as AcquiredJob, b as JobData } from '../../job-Bd_c2lFK.js';
3
+
4
+ interface KnexAdapterOptions {
5
+ connection: Knex;
6
+ tableName?: string;
7
+ ownsConnection?: boolean;
8
+ }
9
+ type KnexConfig = Knex | Knex.Config;
10
+ /**
11
+ * Create a new Knex adapter factory.
12
+ * Accepts either a Knex instance or a Knex configuration object.
13
+ *
14
+ * When passing a config object, the adapter will create and manage
15
+ * the connection lifecycle (closing it on destroy).
16
+ *
17
+ * When passing a Knex instance, the caller is responsible for
18
+ * managing the connection lifecycle.
19
+ */
20
+ declare function knex(config: KnexConfig, tableName?: string): () => KnexAdapter;
21
+ /**
22
+ * Knex adapter for the queue system.
23
+ * Stores jobs in a SQL database using Knex.
24
+ */
25
+ declare class KnexAdapter implements Adapter {
26
+ #private;
27
+ constructor(config: KnexAdapterOptions);
28
+ setWorkerId(workerId: string): void;
29
+ destroy(): Promise<void>;
30
+ pop(): Promise<AcquiredJob | null>;
31
+ popFrom(queue: string): Promise<AcquiredJob | null>;
32
+ completeJob(jobId: string, queue: string): Promise<void>;
33
+ failJob(jobId: string, queue: string, _error?: Error): Promise<void>;
34
+ retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void>;
35
+ push(jobData: JobData): Promise<void>;
36
+ pushOn(queue: string, jobData: JobData): Promise<void>;
37
+ pushLater(jobData: JobData, delay: number): Promise<void>;
38
+ pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void>;
39
+ size(): Promise<number>;
40
+ sizeOf(queue: string): Promise<number>;
41
+ }
42
+
43
+ export { KnexAdapter, type KnexAdapterOptions, knex };
@@ -0,0 +1,176 @@
1
+ // src/drivers/knex_adapter.ts
2
+ import KnexPkg from "knex";
3
+ function knex(config, tableName) {
4
+ return () => {
5
+ const isKnexInstance = typeof config === "function";
6
+ const connection = isKnexInstance ? config : KnexPkg(config);
7
+ return new KnexAdapter({ connection, tableName, ownsConnection: !isKnexInstance });
8
+ };
9
+ }
10
+ var KnexAdapter = class {
11
+ #connection;
12
+ #tableName;
13
+ #ownsConnection;
14
+ #workerId = "";
15
+ #initialized = false;
16
+ constructor(config) {
17
+ this.#connection = config.connection;
18
+ this.#tableName = config.tableName ?? "queue_jobs";
19
+ this.#ownsConnection = config.ownsConnection ?? false;
20
+ }
21
+ setWorkerId(workerId) {
22
+ this.#workerId = workerId;
23
+ }
24
+ /**
25
+ * Ensure the jobs table exists.
26
+ * Creates it if not exists, handles race conditions.
27
+ */
28
+ async #ensureTableExists() {
29
+ if (this.#initialized) return;
30
+ try {
31
+ await this.#connection.schema.createTable(this.#tableName, (table) => {
32
+ table.string("id", 255).notNullable();
33
+ table.string("queue", 255).notNullable();
34
+ table.enu("status", ["pending", "active", "delayed"]).notNullable();
35
+ table.text("data").notNullable();
36
+ table.bigint("score").unsigned().nullable();
37
+ table.string("worker_id", 255).nullable();
38
+ table.bigint("acquired_at").unsigned().nullable();
39
+ table.bigint("execute_at").unsigned().nullable();
40
+ table.primary(["id", "queue"]);
41
+ table.index(["queue", "status", "score"]);
42
+ table.index(["queue", "status", "execute_at"]);
43
+ });
44
+ } catch {
45
+ const hasTable = await this.#connection.schema.hasTable(this.#tableName);
46
+ if (!hasTable) {
47
+ throw new Error(`Failed to create table "${this.#tableName}"`);
48
+ }
49
+ }
50
+ this.#initialized = true;
51
+ }
52
+ async destroy() {
53
+ if (this.#ownsConnection) {
54
+ await this.#connection.destroy();
55
+ }
56
+ }
57
+ async pop() {
58
+ return this.popFrom("default");
59
+ }
60
+ async popFrom(queue) {
61
+ await this.#ensureTableExists();
62
+ const now = Date.now();
63
+ await this.#processDelayedJobs(queue, now);
64
+ return this.#connection.transaction(async (trx) => {
65
+ const job = await trx(this.#tableName).where("queue", queue).where("status", "pending").orderBy("score", "asc").first();
66
+ if (!job) {
67
+ return null;
68
+ }
69
+ await trx(this.#tableName).where("id", job.id).where("queue", queue).update({
70
+ status: "active",
71
+ worker_id: this.#workerId,
72
+ acquired_at: now
73
+ });
74
+ const jobData = JSON.parse(job.data);
75
+ return {
76
+ ...jobData,
77
+ acquiredAt: now
78
+ };
79
+ });
80
+ }
81
+ async #processDelayedJobs(queue, now) {
82
+ const delayedJobs = await this.#connection(this.#tableName).where("queue", queue).where("status", "delayed").where("execute_at", "<=", now).select("id", "data");
83
+ if (delayedJobs.length === 0) return;
84
+ for (const job of delayedJobs) {
85
+ const jobData = JSON.parse(job.data);
86
+ const priority = jobData.priority ?? 5;
87
+ const score = priority * 1e13 + now;
88
+ await this.#connection(this.#tableName).where("id", job.id).where("queue", queue).update({
89
+ status: "pending",
90
+ score,
91
+ execute_at: null
92
+ });
93
+ }
94
+ }
95
+ async completeJob(jobId, queue) {
96
+ await this.#ensureTableExists();
97
+ await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).delete();
98
+ }
99
+ async failJob(jobId, queue, _error) {
100
+ await this.#ensureTableExists();
101
+ await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).delete();
102
+ }
103
+ async retryJob(jobId, queue, retryAt) {
104
+ await this.#ensureTableExists();
105
+ const now = Date.now();
106
+ const activeJob = await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).where("status", "active").first();
107
+ if (!activeJob) return;
108
+ const jobData = JSON.parse(activeJob.data);
109
+ jobData.attempts = (jobData.attempts || 0) + 1;
110
+ const updatedData = JSON.stringify(jobData);
111
+ if (retryAt && retryAt.getTime() > now) {
112
+ await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).update({
113
+ status: "delayed",
114
+ data: updatedData,
115
+ worker_id: null,
116
+ acquired_at: null,
117
+ score: null,
118
+ execute_at: retryAt.getTime()
119
+ });
120
+ } else {
121
+ const priority = jobData.priority ?? 5;
122
+ const score = priority * 1e13 + now;
123
+ await this.#connection(this.#tableName).where("id", jobId).where("queue", queue).update({
124
+ status: "pending",
125
+ data: updatedData,
126
+ worker_id: null,
127
+ acquired_at: null,
128
+ score,
129
+ execute_at: null
130
+ });
131
+ }
132
+ }
133
+ async push(jobData) {
134
+ return this.pushOn("default", jobData);
135
+ }
136
+ async pushOn(queue, jobData) {
137
+ await this.#ensureTableExists();
138
+ const priority = jobData.priority ?? 5;
139
+ const timestamp = Date.now();
140
+ const score = priority * 1e13 + timestamp;
141
+ await this.#connection(this.#tableName).insert({
142
+ id: jobData.id,
143
+ queue,
144
+ status: "pending",
145
+ data: JSON.stringify(jobData),
146
+ score
147
+ });
148
+ }
149
+ async pushLater(jobData, delay) {
150
+ return this.pushLaterOn("default", jobData, delay);
151
+ }
152
+ async pushLaterOn(queue, jobData, delay) {
153
+ await this.#ensureTableExists();
154
+ const executeAt = Date.now() + delay;
155
+ await this.#connection(this.#tableName).insert({
156
+ id: jobData.id,
157
+ queue,
158
+ status: "delayed",
159
+ data: JSON.stringify(jobData),
160
+ execute_at: executeAt
161
+ });
162
+ }
163
+ async size() {
164
+ return this.sizeOf("default");
165
+ }
166
+ async sizeOf(queue) {
167
+ await this.#ensureTableExists();
168
+ const result = await this.#connection(this.#tableName).where("queue", queue).where("status", "pending").count("* as count").first();
169
+ return Number(result?.count ?? 0);
170
+ }
171
+ };
172
+ export {
173
+ KnexAdapter,
174
+ knex
175
+ };
176
+ //# sourceMappingURL=knex_adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/drivers/knex_adapter.ts"],"sourcesContent":["import KnexPkg from 'knex'\nimport type { Knex } from 'knex'\nimport type { Adapter, AcquiredJob } from '#contracts/adapter'\nimport type { JobData } from '#types/main'\n\nexport interface KnexAdapterOptions {\n connection: Knex\n tableName?: string\n ownsConnection?: boolean\n}\n\ntype KnexConfig = Knex | Knex.Config\n\n/**\n * Create a new Knex adapter factory.\n * Accepts either a Knex instance or a Knex configuration object.\n *\n * When passing a config object, the adapter will create and manage\n * the connection lifecycle (closing it on destroy).\n *\n * When passing a Knex instance, the caller is responsible for\n * managing the connection lifecycle.\n */\nexport function knex(config: KnexConfig, tableName?: string) {\n return () => {\n const isKnexInstance = typeof config === 'function'\n const connection = isKnexInstance ? config : KnexPkg(config)\n return new KnexAdapter({ connection, tableName, ownsConnection: !isKnexInstance })\n }\n}\n\n/**\n * Knex adapter for the queue system.\n * Stores jobs in a SQL database using Knex.\n */\nexport class KnexAdapter implements Adapter {\n readonly #connection: Knex\n readonly #tableName: string\n readonly #ownsConnection: boolean\n #workerId: string = ''\n #initialized: boolean = false\n\n constructor(config: KnexAdapterOptions) {\n this.#connection = config.connection\n this.#tableName = config.tableName ?? 'queue_jobs'\n this.#ownsConnection = config.ownsConnection ?? false\n }\n\n setWorkerId(workerId: string): void {\n this.#workerId = workerId\n }\n\n /**\n * Ensure the jobs table exists.\n * Creates it if not exists, handles race conditions.\n */\n async #ensureTableExists(): Promise<void> {\n if (this.#initialized) return\n\n try {\n await this.#connection.schema.createTable(this.#tableName, (table) => {\n table.string('id', 255).notNullable()\n table.string('queue', 255).notNullable()\n table.enu('status', ['pending', 'active', 'delayed']).notNullable()\n table.text('data').notNullable()\n table.bigint('score').unsigned().nullable()\n table.string('worker_id', 255).nullable()\n table.bigint('acquired_at').unsigned().nullable()\n table.bigint('execute_at').unsigned().nullable()\n table.primary(['id', 'queue'])\n table.index(['queue', 'status', 'score'])\n table.index(['queue', 'status', 'execute_at'])\n })\n } catch {\n /**\n * If table creation fails, verify the table actually exists.\n * This handles race conditions where multiple instances try to create\n * the table simultaneously.\n */\n const hasTable = await this.#connection.schema.hasTable(this.#tableName)\n if (!hasTable) {\n throw new Error(`Failed to create table \"${this.#tableName}\"`)\n }\n }\n\n this.#initialized = true\n }\n\n async destroy(): Promise<void> {\n if (this.#ownsConnection) {\n await this.#connection.destroy()\n }\n }\n\n async pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n async popFrom(queue: string): Promise<AcquiredJob | null> {\n await this.#ensureTableExists()\n\n const now = Date.now()\n\n // First, move ready delayed jobs to pending\n await this.#processDelayedJobs(queue, now)\n\n // Use a transaction to atomically pop a job\n return this.#connection.transaction(async (trx) => {\n // Select the highest priority job (lowest score)\n const job = await trx(this.#tableName)\n .where('queue', queue)\n .where('status', 'pending')\n .orderBy('score', 'asc')\n .first()\n\n if (!job) {\n return null\n }\n\n // Update job to active status\n await trx(this.#tableName)\n .where('id', job.id)\n .where('queue', queue)\n .update({\n status: 'active',\n worker_id: this.#workerId,\n acquired_at: now,\n })\n\n const jobData: JobData = JSON.parse(job.data)\n\n return {\n ...jobData,\n acquiredAt: now,\n }\n })\n }\n\n async #processDelayedJobs(queue: string, now: number): Promise<void> {\n // Get all ready delayed jobs\n const delayedJobs = await this.#connection(this.#tableName)\n .where('queue', queue)\n .where('status', 'delayed')\n .where('execute_at', '<=', now)\n .select('id', 'data')\n\n if (delayedJobs.length === 0) return\n\n // Move them to pending\n for (const job of delayedJobs) {\n const jobData: JobData = JSON.parse(job.data)\n const priority = jobData.priority ?? 5\n const score = priority * 1e13 + now\n\n await this.#connection(this.#tableName)\n .where('id', job.id)\n .where('queue', queue)\n .update({\n status: 'pending',\n score,\n execute_at: null,\n })\n }\n }\n\n async completeJob(jobId: string, queue: string): Promise<void> {\n await this.#ensureTableExists()\n\n await this.#connection(this.#tableName)\n .where('id', jobId)\n .where('queue', queue)\n .delete()\n }\n\n async failJob(jobId: string, queue: string, _error?: Error): Promise<void> {\n await this.#ensureTableExists()\n\n await this.#connection(this.#tableName)\n .where('id', jobId)\n .where('queue', queue)\n .delete()\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n await this.#ensureTableExists()\n\n const now = Date.now()\n\n // Get the active job\n const activeJob = await this.#connection(this.#tableName)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .first()\n\n if (!activeJob) return\n\n const jobData: JobData = JSON.parse(activeJob.data)\n jobData.attempts = (jobData.attempts || 0) + 1\n\n const updatedData = JSON.stringify(jobData)\n\n if (retryAt && retryAt.getTime() > now) {\n // Move to delayed\n await this.#connection(this.#tableName)\n .where('id', jobId)\n .where('queue', queue)\n .update({\n status: 'delayed',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score: null,\n execute_at: retryAt.getTime(),\n })\n } else {\n // Move back to pending\n const priority = jobData.priority ?? 5\n const score = priority * 1e13 + now\n\n await this.#connection(this.#tableName)\n .where('id', jobId)\n .where('queue', queue)\n .update({\n status: 'pending',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score,\n execute_at: null,\n })\n }\n }\n\n async push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n async pushOn(queue: string, jobData: JobData): Promise<void> {\n await this.#ensureTableExists()\n\n const priority = jobData.priority ?? 5\n const timestamp = Date.now()\n const score = priority * 1e13 + timestamp\n\n await this.#connection(this.#tableName).insert({\n id: jobData.id,\n queue,\n status: 'pending',\n data: JSON.stringify(jobData),\n score,\n })\n }\n\n async pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n async pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n await this.#ensureTableExists()\n\n const executeAt = Date.now() + delay\n\n await this.#connection(this.#tableName).insert({\n id: jobData.id,\n queue,\n status: 'delayed',\n data: JSON.stringify(jobData),\n execute_at: executeAt,\n })\n }\n\n async size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n async sizeOf(queue: string): Promise<number> {\n await this.#ensureTableExists()\n\n const result = await this.#connection(this.#tableName)\n .where('queue', queue)\n .where('status', 'pending')\n .count('* as count')\n .first()\n\n return Number(result?.count ?? 0)\n }\n}\n"],"mappings":";AAAA,OAAO,aAAa;AAuBb,SAAS,KAAK,QAAoB,WAAoB;AAC3D,SAAO,MAAM;AACX,UAAM,iBAAiB,OAAO,WAAW;AACzC,UAAM,aAAa,iBAAiB,SAAS,QAAQ,MAAM;AAC3D,WAAO,IAAI,YAAY,EAAE,YAAY,WAAW,gBAAgB,CAAC,eAAe,CAAC;AAAA,EACnF;AACF;AAMO,IAAM,cAAN,MAAqC;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAoB;AAAA,EACpB,eAAwB;AAAA,EAExB,YAAY,QAA4B;AACtC,SAAK,cAAc,OAAO;AAC1B,SAAK,aAAa,OAAO,aAAa;AACtC,SAAK,kBAAkB,OAAO,kBAAkB;AAAA,EAClD;AAAA,EAEA,YAAY,UAAwB;AAClC,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,qBAAoC;AACxC,QAAI,KAAK,aAAc;AAEvB,QAAI;AACF,YAAM,KAAK,YAAY,OAAO,YAAY,KAAK,YAAY,CAAC,UAAU;AACpE,cAAM,OAAO,MAAM,GAAG,EAAE,YAAY;AACpC,cAAM,OAAO,SAAS,GAAG,EAAE,YAAY;AACvC,cAAM,IAAI,UAAU,CAAC,WAAW,UAAU,SAAS,CAAC,EAAE,YAAY;AAClE,cAAM,KAAK,MAAM,EAAE,YAAY;AAC/B,cAAM,OAAO,OAAO,EAAE,SAAS,EAAE,SAAS;AAC1C,cAAM,OAAO,aAAa,GAAG,EAAE,SAAS;AACxC,cAAM,OAAO,aAAa,EAAE,SAAS,EAAE,SAAS;AAChD,cAAM,OAAO,YAAY,EAAE,SAAS,EAAE,SAAS;AAC/C,cAAM,QAAQ,CAAC,MAAM,OAAO,CAAC;AAC7B,cAAM,MAAM,CAAC,SAAS,UAAU,OAAO,CAAC;AACxC,cAAM,MAAM,CAAC,SAAS,UAAU,YAAY,CAAC;AAAA,MAC/C,CAAC;AAAA,IACH,QAAQ;AAMN,YAAM,WAAW,MAAM,KAAK,YAAY,OAAO,SAAS,KAAK,UAAU;AACvE,UAAI,CAAC,UAAU;AACb,cAAM,IAAI,MAAM,2BAA2B,KAAK,UAAU,GAAG;AAAA,MAC/D;AAAA,IACF;AAEA,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,iBAAiB;AACxB,YAAM,KAAK,YAAY,QAAQ;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,MAAM,MAAmC;AACvC,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC/B;AAAA,EAEA,MAAM,QAAQ,OAA4C;AACxD,UAAM,KAAK,mBAAmB;AAE9B,UAAM,MAAM,KAAK,IAAI;AAGrB,UAAM,KAAK,oBAAoB,OAAO,GAAG;AAGzC,WAAO,KAAK,YAAY,YAAY,OAAO,QAAQ;AAEjD,YAAM,MAAM,MAAM,IAAI,KAAK,UAAU,EAClC,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,SAAS,EACzB,QAAQ,SAAS,KAAK,EACtB,MAAM;AAET,UAAI,CAAC,KAAK;AACR,eAAO;AAAA,MACT;AAGA,YAAM,IAAI,KAAK,UAAU,EACtB,MAAM,MAAM,IAAI,EAAE,EAClB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,QACN,QAAQ;AAAA,QACR,WAAW,KAAK;AAAA,QAChB,aAAa;AAAA,MACf,CAAC;AAEH,YAAM,UAAmB,KAAK,MAAM,IAAI,IAAI;AAE5C,aAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,oBAAoB,OAAe,KAA4B;AAEnE,UAAM,cAAc,MAAM,KAAK,YAAY,KAAK,UAAU,EACvD,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,SAAS,EACzB,MAAM,cAAc,MAAM,GAAG,EAC7B,OAAO,MAAM,MAAM;AAEtB,QAAI,YAAY,WAAW,EAAG;AAG9B,eAAW,OAAO,aAAa;AAC7B,YAAM,UAAmB,KAAK,MAAM,IAAI,IAAI;AAC5C,YAAM,WAAW,QAAQ,YAAY;AACrC,YAAM,QAAQ,WAAW,OAAO;AAEhC,YAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,IAAI,EAAE,EAClB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,QACN,QAAQ;AAAA,QACR;AAAA,QACA,YAAY;AAAA,MACd,CAAC;AAAA,IACL;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,OAAe,OAA8B;AAC7D,UAAM,KAAK,mBAAmB;AAE9B,UAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,EACZ;AAAA,EAEA,MAAM,QAAQ,OAAe,OAAe,QAA+B;AACzE,UAAM,KAAK,mBAAmB;AAE9B,UAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,EACZ;AAAA,EAEA,MAAM,SAAS,OAAe,OAAe,SAA+B;AAC1E,UAAM,KAAK,mBAAmB;AAE9B,UAAM,MAAM,KAAK,IAAI;AAGrB,UAAM,YAAY,MAAM,KAAK,YAAY,KAAK,UAAU,EACrD,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,QAAQ,EACxB,MAAM;AAET,QAAI,CAAC,UAAW;AAEhB,UAAM,UAAmB,KAAK,MAAM,UAAU,IAAI;AAClD,YAAQ,YAAY,QAAQ,YAAY,KAAK;AAE7C,UAAM,cAAc,KAAK,UAAU,OAAO;AAE1C,QAAI,WAAW,QAAQ,QAAQ,IAAI,KAAK;AAEtC,YAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,QACN,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,WAAW;AAAA,QACX,aAAa;AAAA,QACb,OAAO;AAAA,QACP,YAAY,QAAQ,QAAQ;AAAA,MAC9B,CAAC;AAAA,IACL,OAAO;AAEL,YAAM,WAAW,QAAQ,YAAY;AACrC,YAAM,QAAQ,WAAW,OAAO;AAEhC,YAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,QACN,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,WAAW;AAAA,QACX,aAAa;AAAA,QACb;AAAA,QACA,YAAY;AAAA,MACd,CAAC;AAAA,IACL;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,SAAiC;AAC1C,WAAO,KAAK,OAAO,WAAW,OAAO;AAAA,EACvC;AAAA,EAEA,MAAM,OAAO,OAAe,SAAiC;AAC3D,UAAM,KAAK,mBAAmB;AAE9B,UAAM,WAAW,QAAQ,YAAY;AACrC,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,QAAQ,WAAW,OAAO;AAEhC,UAAM,KAAK,YAAY,KAAK,UAAU,EAAE,OAAO;AAAA,MAC7C,IAAI,QAAQ;AAAA,MACZ;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,OAAO;AAAA,MAC5B;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAU,SAAkB,OAA8B;AAC9D,WAAO,KAAK,YAAY,WAAW,SAAS,KAAK;AAAA,EACnD;AAAA,EAEA,MAAM,YAAY,OAAe,SAAkB,OAA8B;AAC/E,UAAM,KAAK,mBAAmB;AAE9B,UAAM,YAAY,KAAK,IAAI,IAAI;AAE/B,UAAM,KAAK,YAAY,KAAK,UAAU,EAAE,OAAO;AAAA,MAC7C,IAAI,QAAQ;AAAA,MACZ;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,OAAO;AAAA,MAC5B,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAwB;AAC5B,WAAO,KAAK,OAAO,SAAS;AAAA,EAC9B;AAAA,EAEA,MAAM,OAAO,OAAgC;AAC3C,UAAM,KAAK,mBAAmB;AAE9B,UAAM,SAAS,MAAM,KAAK,YAAY,KAAK,UAAU,EAClD,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,SAAS,EACzB,MAAM,YAAY,EAClB,MAAM;AAET,WAAO,OAAO,QAAQ,SAAS,CAAC;AAAA,EAClC;AACF;","names":[]}
@@ -1,16 +1,29 @@
1
1
  import { Redis, RedisOptions } from 'ioredis';
2
- import { A as Adapter, L as LeaseConfig, a as JobData } from '../../job-CcAUWe8j.js';
3
- import { LeaseManager } from '../contracts/lease_manager.js';
2
+ import { A as Adapter, a as AcquiredJob, b as JobData } from '../../job-Bd_c2lFK.js';
4
3
 
5
4
  type RedisConfig = Redis | RedisOptions;
5
+ /**
6
+ * Create a new Redis adapter factory.
7
+ * Accepts either a Redis instance or Redis options.
8
+ *
9
+ * When passing options, the adapter will create and manage
10
+ * the connection lifecycle (closing it on destroy).
11
+ *
12
+ * When passing a Redis instance, the caller is responsible for
13
+ * managing the connection lifecycle.
14
+ */
6
15
  declare function redis(config?: RedisConfig): () => RedisAdapter;
7
16
  declare class RedisAdapter implements Adapter {
8
17
  #private;
9
- constructor(connection: Redis);
10
- createLeaseManager(config: LeaseConfig): LeaseManager;
18
+ constructor(connection: Redis, ownsConnection?: boolean);
19
+ setWorkerId(workerId: string): void;
11
20
  destroy(): Promise<void>;
12
- pop(): Promise<JobData | null>;
13
- popFrom(queue: string): Promise<JobData | null>;
21
+ pop(): Promise<AcquiredJob | null>;
22
+ popFrom(queue: string): Promise<AcquiredJob | null>;
23
+ popAndWait(queue: string, timeout: number): Promise<AcquiredJob | null>;
24
+ completeJob(jobId: string, queue: string): Promise<void>;
25
+ failJob(jobId: string, queue: string, _error?: Error): Promise<void>;
26
+ retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void>;
14
27
  push(jobData: JobData): Promise<void>;
15
28
  pushLater(jobData: JobData, delay: number): Promise<void>;
16
29
  pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void>;