@warlock.js/queue 5.41.0 → 5.43.0
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.
- package/CHANGELOG.md +7 -0
- package/cjs/index.cjs +120 -29
- package/cjs/index.cjs.map +1 -1
- package/esm/define-job.d.mts.map +1 -1
- package/esm/define-job.mjs +7 -0
- package/esm/define-job.mjs.map +1 -1
- package/esm/drivers/local.mjs +9 -4
- package/esm/drivers/local.mjs.map +1 -1
- package/esm/errors.d.mts +20 -0
- package/esm/errors.d.mts.map +1 -1
- package/esm/errors.mjs +15 -1
- package/esm/errors.mjs.map +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/index.mjs +3 -3
- package/esm/job-options.d.mts +3 -1
- package/esm/job-options.d.mts.map +1 -1
- package/esm/job-options.mjs +6 -0
- package/esm/job-options.mjs.map +1 -1
- package/esm/job-timeout.mjs +61 -0
- package/esm/job-timeout.mjs.map +1 -0
- package/esm/process-job.mjs +9 -3
- package/esm/process-job.mjs.map +1 -1
- package/esm/types.d.mts +20 -1
- package/esm/types.d.mts.map +1 -1
- package/llms-full.txt +5 -1
- package/llms.txt +1 -1
- package/package.json +3 -3
- package/skills/define-jobs/SKILL.md +5 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.mjs","names":[],"sources":["../../../../../../../../packages/queue/src/drivers/local.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { log } from \"@warlock.js/logger\";\nimport type { DriverDispatch, QueueDriver } from \"../driver\";\nimport { FailedJobNotFoundError, UnrecoverableJobError } from \"../errors\";\nimport { findRegisteredJob } from \"../job-registry\";\nimport { getQueueContext, unwrapPayload } from \"../queue-context\";\nimport type { DispatchedJob, FailedJob, JobContext, JobSnapshot, QueueConfig } from \"../types\";\nimport { jsonCopy, type LocalJob, type LocalStore } from \"./local-store\";\n\ntype ActiveJob = {\n job: LocalJob;\n token: string;\n controller: AbortController;\n heartbeat: NodeJS.Timeout;\n done: Promise<void>;\n};\ntype LocalWorker = { timer?: NodeJS.Timeout; active: Set<ActiveJob> };\n\n/** Shared scheduling and lease semantics for the SQLite and memory stores. */\nexport class LocalDriver implements QueueDriver {\n private workers = new Map<string, LocalWorker>();\n private active = new Set<ActiveJob>();\n private closed = false;\n private stopping = false;\n private leaseDuration: number;\n private pollInterval: number;\n private concurrency: number;\n\n public constructor(\n private store: LocalStore,\n config: QueueConfig,\n ) {\n try {\n this.leaseDuration = positiveInteger(\n config.local?.leaseDuration ?? 30_000,\n \"local.leaseDuration\",\n 30,\n );\n this.pollInterval = positiveInteger(config.local?.pollInterval ?? 100, \"local.pollInterval\");\n this.concurrency = positiveInteger(config.workers?.concurrency ?? 1, \"workers.concurrency\");\n } catch (error) {\n store.close();\n throw error;\n }\n }\n\n public async dispatch(request: DriverDispatch): Promise<DispatchedJob> {\n this.assertOpen();\n validateOptions(request);\n const id = request.options.jobId ?? randomUUID();\n const now = Date.now();\n const job = this.store.transaction(() => {\n const previous = this.store.get(request.queue, id);\n if (previous) return previous;\n const entry: LocalJob = {\n id,\n name: request.name,\n queue: request.queue,\n data: jsonCopy(request.data),\n options: jsonCopy(request.options),\n state: request.options.delay\n ? \"delayed\"\n : request.options.priority\n ? \"prioritized\"\n : \"waiting\",\n createdAt: now,\n readyAt: now + (request.options.delay ?? 0),\n attemptsMade: 0,\n priority: request.options.priority ?? 0,\n progress: 0,\n logs: [],\n stacktrace: [],\n leaseUntil: 0,\n };\n this.store.put(entry);\n return entry;\n });\n return { id: job.id, name: request.name, queue: request.queue };\n }\n\n public async find(queue: string, id: string): Promise<JobSnapshot | undefined> {\n this.assertOpen();\n const job = this.store.get(queue, id);\n return job ? snapshot(job) : undefined;\n }\n\n public async failed(queue: string, start: number, end: number): Promise<FailedJob[]> {\n this.assertOpen();\n const jobs = newestFirst(this.store.list(queue, \"failed\"));\n const last = end < 0 ? jobs.length + end : end;\n return jobs.slice(start, last + 1).map((job) => ({\n id: job.id,\n name: job.name,\n queue,\n payload: unwrapPayload(job.data).payload,\n attemptsMade: job.attemptsMade,\n failedReason: job.failedReason ?? \"\",\n stacktrace: job.stacktrace,\n failedAt: job.finishedAt === undefined ? undefined : new Date(job.finishedAt),\n retry: () => this.retry(queue, job.id),\n }));\n }\n\n public async retry(queue: string, id: string): Promise<void> {\n this.assertOpen();\n this.store.transaction(() => {\n const job = this.store.get(queue, id);\n if (!job || job.state !== \"failed\") throw new FailedJobNotFoundError(id, queue);\n job.state = job.priority ? \"prioritized\" : \"waiting\";\n job.attemptsMade = 0;\n job.readyAt = Date.now();\n delete job.finishedAt;\n delete job.failedReason;\n delete job.result;\n job.stacktrace = [];\n this.store.put(job);\n });\n }\n\n public async start(queue: string): Promise<void> {\n this.assertOpen();\n if (this.stopping) throw new Error(\"Queue workers are shutting down.\");\n if (this.workers.has(queue)) return;\n const worker: LocalWorker = { active: new Set() };\n this.workers.set(queue, worker);\n this.schedule(queue, worker, 0);\n }\n\n public running(): string[] {\n return [...this.workers.keys()];\n }\n\n public async close(timeout: number): Promise<void> {\n if (this.closed) return;\n this.stopping = true;\n for (const worker of this.workers.values()) clearTimeout(worker.timer);\n this.workers.clear();\n let timer: NodeJS.Timeout | undefined;\n const timedOut = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, timeout);\n });\n try {\n await Promise.race([\n Promise.allSettled([...this.active].map((entry) => entry.done)),\n timedOut,\n ]);\n } finally {\n clearTimeout(timer);\n try {\n for (const entry of this.active) {\n clearInterval(entry.heartbeat);\n entry.controller.abort(new Error(\"Queue shutdown timeout elapsed.\"));\n this.mutate(entry.job, entry.token, (job) => {\n job.state = job.priority ? \"prioritized\" : \"waiting\";\n job.readyAt = Date.now();\n job.leaseUntil = 0;\n delete job.token;\n });\n }\n } finally {\n this.closed = true;\n for (const entry of this.active) clearInterval(entry.heartbeat);\n this.store.close();\n }\n }\n }\n\n private schedule(queue: string, worker: LocalWorker, delay: number): void {\n if (this.stopping) return;\n worker.timer = setTimeout(() => {\n try {\n while (!this.stopping && worker.active.size < this.concurrency) {\n const token = randomUUID();\n const job = this.store.transaction(() => {\n const now = Date.now();\n const candidate = this.store.ready(queue, now);\n if (!candidate) return undefined;\n candidate.state = \"active\";\n candidate.token = token;\n candidate.leaseUntil = now + this.leaseDuration;\n this.store.put(candidate);\n return candidate;\n });\n if (!job) break;\n this.run(job, token, worker);\n }\n } catch (error) {\n log.error(\"queue\", \"worker\", error);\n }\n this.schedule(queue, worker, this.pollInterval);\n }, delay);\n }\n\n private run(job: LocalJob, token: string, worker: LocalWorker): void {\n const controller = new AbortController();\n const heartbeat = setInterval(\n () => {\n try {\n if (\n !this.mutate(job, token, (current) => {\n current.leaseUntil = Date.now() + this.leaseDuration;\n })\n ) {\n controller.abort(new Error(\"Queue job lease expired or changed owner.\"));\n clearInterval(heartbeat);\n }\n } catch (error) {\n controller.abort(error);\n clearInterval(heartbeat);\n log.error(\"queue\", \"lease\", error);\n }\n },\n Math.floor(this.leaseDuration / 3),\n );\n const entry: ActiveJob = { job, token, controller, heartbeat, done: Promise.resolve() };\n this.active.add(entry);\n worker.active.add(entry);\n entry.done = this.execute(entry)\n .catch((error: unknown) => {\n log.error(\"queue\", \"job\", error);\n })\n .finally(() => {\n clearInterval(heartbeat);\n this.active.delete(entry);\n worker.active.delete(entry);\n });\n }\n\n private async execute(entry: ActiveJob): Promise<void> {\n const { job, token, controller } = entry;\n try {\n const definition = findRegisteredJob(job.name);\n if (!definition)\n throw new UnrecoverableJobError(\n `No job named \"${job.name}\" is defined in this process. Import its definition in the worker.`,\n );\n const { payload, context: captured } = unwrapPayload(job.data);\n const context: JobContext = {\n id: job.id,\n name: job.name,\n queue: job.queue,\n attempt: job.attemptsMade + 1,\n maxAttempts: job.options.attempts ?? 1,\n context: captured,\n signal: controller.signal,\n progress: async (value) => {\n if (\n !this.mutate(job, token, (current) => {\n current.progress = jsonCopy(value);\n })\n )\n throw new Error(\"Queue job lease lost.\");\n },\n log: async (line) => {\n if (\n !this.mutate(job, token, (current) => {\n current.logs.push(line);\n })\n )\n throw new Error(\"Queue job lease lost.\");\n },\n };\n const run = async () => definition.handle(payload, context);\n const queueContext = getQueueContext();\n const result = jsonCopy(\n captured !== undefined && queueContext\n ? await queueContext.restore(captured, run)\n : await run(),\n );\n this.mutate(\n job,\n token,\n (current) => {\n current.attemptsMade++;\n current.result = result;\n current.state = \"completed\";\n current.finishedAt = Date.now();\n current.leaseUntil = 0;\n delete current.token;\n },\n true,\n );\n } catch (error) {\n this.mutate(\n job,\n token,\n (current) => {\n current.attemptsMade++;\n current.failedReason = error instanceof Error ? error.message : String(error);\n current.stacktrace.push(\n error instanceof Error ? (error.stack ?? error.message) : String(error),\n );\n current.leaseUntil = 0;\n delete current.token;\n if (\n error instanceof UnrecoverableJobError ||\n current.attemptsMade >= (current.options.attempts ?? 1)\n ) {\n current.state = \"failed\";\n current.finishedAt = Date.now();\n } else {\n const backoff = current.options.backoff;\n const base = typeof backoff === \"number\" ? backoff : (backoff?.delay ?? 0);\n const delay =\n typeof backoff === \"object\" && backoff.type === \"exponential\"\n ? base * 2 ** (current.attemptsMade - 1)\n : base;\n current.readyAt = Math.min(Number.MAX_SAFE_INTEGER, Date.now() + delay);\n current.state = delay > 0 ? \"delayed\" : current.priority ? \"prioritized\" : \"waiting\";\n }\n },\n true,\n );\n }\n }\n\n /** Fencing: expired owners cannot renew, finish, or overwrite a new owner's job. */\n private mutate(\n job: LocalJob,\n token: string,\n update: (current: LocalJob) => void,\n retain = false,\n ): boolean {\n if (this.closed) return false;\n return this.store.transaction(() => {\n const current = this.store.get(job.queue, job.id);\n if (!current || !ownsLease(current, token)) return false;\n update(current);\n this.store.put(current);\n if (retain && (current.state === \"completed\" || current.state === \"failed\")) {\n const retention =\n current.state === \"completed\"\n ? current.options.removeOnComplete\n : current.options.removeOnFail;\n if (retention === true) this.store.remove(current.queue, current.id);\n if (typeof retention === \"number\") {\n for (const old of newestFirst(this.store.list(current.queue, current.state)).slice(\n retention,\n ))\n this.store.remove(old.queue, old.id);\n }\n }\n return true;\n });\n }\n\n private assertOpen(): void {\n if (this.closed) throw new Error(\"Queue driver is closed.\");\n }\n}\n\nfunction snapshot(job: LocalJob): JobSnapshot {\n return {\n id: job.id,\n name: job.name,\n queue: job.queue,\n state: job.state,\n payload: unwrapPayload(job.data).payload,\n progress: job.progress,\n attemptsMade: job.attemptsMade,\n result: job.result,\n failedReason: job.failedReason,\n createdAt: new Date(job.createdAt),\n finishedAt: job.finishedAt === undefined ? undefined : new Date(job.finishedAt),\n };\n}\nfunction newestFirst(jobs: LocalJob[]): LocalJob[] {\n return jobs.reverse().sort((a, b) => (b.finishedAt ?? 0) - (a.finishedAt ?? 0));\n}\nfunction positiveInteger(value: number, name: string, minimum = 1): number {\n if (!Number.isSafeInteger(value) || value < minimum || value > 2_147_483_647)\n throw new Error(`${name} must be an integer between ${minimum} and 2147483647.`);\n return value;\n}\nfunction validateOptions({ options }: DriverDispatch): void {\n positiveInteger(options.attempts ?? 1, \"attempts\");\n if (\n options.priority !== undefined &&\n (!Number.isInteger(options.priority) || options.priority < 0 || options.priority > 2_097_152)\n )\n throw new Error(\"priority must be an integer between 0 and 2097152.\");\n const backoff = options.backoff;\n const delay = typeof backoff === \"number\" ? backoff : (backoff?.delay ?? 0);\n if (\n !Number.isFinite(delay) ||\n delay < 0 ||\n (typeof backoff === \"object\" && ![\"fixed\", \"exponential\"].includes(backoff.type))\n )\n throw new Error(\"backoff must have a non-negative finite delay and fixed or exponential type.\");\n if (options.delay !== undefined && (!Number.isFinite(options.delay) || options.delay < 0))\n throw new Error(\"delay must be non-negative and finite.\");\n for (const retention of [options.removeOnComplete, options.removeOnFail]) {\n if (typeof retention === \"number\" && (!Number.isInteger(retention) || retention < 0))\n throw new Error(\"Job retention must be a non-negative integer or boolean.\");\n }\n}\n\nfunction ownsLease(job: LocalJob, token: string): boolean {\n return job.state === \"active\" && job.token === token && job.leaseUntil > Date.now();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local.mjs","names":[],"sources":["../../../../../../../../packages/queue/src/drivers/local.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { log } from \"@warlock.js/logger\";\nimport type { DriverDispatch, QueueDriver } from \"../driver\";\nimport { FailedJobNotFoundError, JobTimeoutError, UnrecoverableJobError } from \"../errors\";\nimport { createJobAbort } from \"../job-timeout\";\nimport { findRegisteredJob } from \"../job-registry\";\nimport { getQueueContext, unwrapPayload } from \"../queue-context\";\nimport type { DispatchedJob, FailedJob, JobContext, JobSnapshot, QueueConfig } from \"../types\";\nimport { jsonCopy, type LocalJob, type LocalStore } from \"./local-store\";\n\ntype ActiveJob = {\n job: LocalJob;\n token: string;\n controller: AbortController;\n heartbeat: NodeJS.Timeout;\n done: Promise<void>;\n};\ntype LocalWorker = { timer?: NodeJS.Timeout; active: Set<ActiveJob> };\n\n/** Shared scheduling and lease semantics for the SQLite and memory stores. */\nexport class LocalDriver implements QueueDriver {\n private workers = new Map<string, LocalWorker>();\n private active = new Set<ActiveJob>();\n private closed = false;\n private stopping = false;\n private leaseDuration: number;\n private pollInterval: number;\n private concurrency: number;\n\n public constructor(\n private store: LocalStore,\n config: QueueConfig,\n ) {\n try {\n this.leaseDuration = positiveInteger(\n config.local?.leaseDuration ?? 30_000,\n \"local.leaseDuration\",\n 30,\n );\n this.pollInterval = positiveInteger(config.local?.pollInterval ?? 100, \"local.pollInterval\");\n this.concurrency = positiveInteger(config.workers?.concurrency ?? 1, \"workers.concurrency\");\n } catch (error) {\n store.close();\n throw error;\n }\n }\n\n public async dispatch(request: DriverDispatch): Promise<DispatchedJob> {\n this.assertOpen();\n validateOptions(request);\n const id = request.options.jobId ?? randomUUID();\n const now = Date.now();\n const job = this.store.transaction(() => {\n const previous = this.store.get(request.queue, id);\n if (previous) return previous;\n const entry: LocalJob = {\n id,\n name: request.name,\n queue: request.queue,\n data: jsonCopy(request.data),\n options: jsonCopy(request.options),\n state: request.options.delay\n ? \"delayed\"\n : request.options.priority\n ? \"prioritized\"\n : \"waiting\",\n createdAt: now,\n readyAt: now + (request.options.delay ?? 0),\n attemptsMade: 0,\n priority: request.options.priority ?? 0,\n progress: 0,\n logs: [],\n stacktrace: [],\n leaseUntil: 0,\n };\n this.store.put(entry);\n return entry;\n });\n return { id: job.id, name: request.name, queue: request.queue };\n }\n\n public async find(queue: string, id: string): Promise<JobSnapshot | undefined> {\n this.assertOpen();\n const job = this.store.get(queue, id);\n return job ? snapshot(job) : undefined;\n }\n\n public async failed(queue: string, start: number, end: number): Promise<FailedJob[]> {\n this.assertOpen();\n const jobs = newestFirst(this.store.list(queue, \"failed\"));\n const last = end < 0 ? jobs.length + end : end;\n return jobs.slice(start, last + 1).map((job) => ({\n id: job.id,\n name: job.name,\n queue,\n payload: unwrapPayload(job.data).payload,\n attemptsMade: job.attemptsMade,\n failedReason: job.failedReason ?? \"\",\n stacktrace: job.stacktrace,\n failedAt: job.finishedAt === undefined ? undefined : new Date(job.finishedAt),\n retry: () => this.retry(queue, job.id),\n }));\n }\n\n public async retry(queue: string, id: string): Promise<void> {\n this.assertOpen();\n this.store.transaction(() => {\n const job = this.store.get(queue, id);\n if (!job || job.state !== \"failed\") throw new FailedJobNotFoundError(id, queue);\n job.state = job.priority ? \"prioritized\" : \"waiting\";\n job.attemptsMade = 0;\n job.readyAt = Date.now();\n delete job.finishedAt;\n delete job.failedReason;\n delete job.result;\n job.stacktrace = [];\n this.store.put(job);\n });\n }\n\n public async start(queue: string): Promise<void> {\n this.assertOpen();\n if (this.stopping) throw new Error(\"Queue workers are shutting down.\");\n if (this.workers.has(queue)) return;\n const worker: LocalWorker = { active: new Set() };\n this.workers.set(queue, worker);\n this.schedule(queue, worker, 0);\n }\n\n public running(): string[] {\n return [...this.workers.keys()];\n }\n\n public async close(timeout: number): Promise<void> {\n if (this.closed) return;\n this.stopping = true;\n for (const worker of this.workers.values()) clearTimeout(worker.timer);\n this.workers.clear();\n let timer: NodeJS.Timeout | undefined;\n const timedOut = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, timeout);\n });\n try {\n await Promise.race([\n Promise.allSettled([...this.active].map((entry) => entry.done)),\n timedOut,\n ]);\n } finally {\n clearTimeout(timer);\n try {\n for (const entry of this.active) {\n clearInterval(entry.heartbeat);\n entry.controller.abort(new Error(\"Queue shutdown timeout elapsed.\"));\n this.mutate(entry.job, entry.token, (job) => {\n job.state = job.priority ? \"prioritized\" : \"waiting\";\n job.readyAt = Date.now();\n job.leaseUntil = 0;\n delete job.token;\n });\n }\n } finally {\n this.closed = true;\n for (const entry of this.active) clearInterval(entry.heartbeat);\n this.store.close();\n }\n }\n }\n\n private schedule(queue: string, worker: LocalWorker, delay: number): void {\n if (this.stopping) return;\n worker.timer = setTimeout(() => {\n try {\n while (!this.stopping && worker.active.size < this.concurrency) {\n const token = randomUUID();\n const job = this.store.transaction(() => {\n const now = Date.now();\n const candidate = this.store.ready(queue, now);\n if (!candidate) return undefined;\n candidate.state = \"active\";\n candidate.token = token;\n candidate.leaseUntil = now + this.leaseDuration;\n this.store.put(candidate);\n return candidate;\n });\n if (!job) break;\n this.run(job, token, worker);\n }\n } catch (error) {\n log.error(\"queue\", \"worker\", error);\n }\n this.schedule(queue, worker, this.pollInterval);\n }, delay);\n }\n\n private run(job: LocalJob, token: string, worker: LocalWorker): void {\n const controller = new AbortController();\n const heartbeat = setInterval(\n () => {\n try {\n if (\n !this.mutate(job, token, (current) => {\n current.leaseUntil = Date.now() + this.leaseDuration;\n })\n ) {\n controller.abort(new Error(\"Queue job lease expired or changed owner.\"));\n clearInterval(heartbeat);\n }\n } catch (error) {\n controller.abort(error);\n clearInterval(heartbeat);\n log.error(\"queue\", \"lease\", error);\n }\n },\n Math.floor(this.leaseDuration / 3),\n );\n const entry: ActiveJob = { job, token, controller, heartbeat, done: Promise.resolve() };\n this.active.add(entry);\n worker.active.add(entry);\n entry.done = this.execute(entry)\n .catch((error: unknown) => {\n log.error(\"queue\", \"job\", error);\n })\n .finally(() => {\n clearInterval(heartbeat);\n this.active.delete(entry);\n worker.active.delete(entry);\n });\n }\n\n private async execute(entry: ActiveJob): Promise<void> {\n const { job, token, controller } = entry;\n const abort = createJobAbort(job.name, job.options, controller.signal);\n try {\n const definition = findRegisteredJob(job.name);\n if (!definition)\n throw new UnrecoverableJobError(\n `No job named \"${job.name}\" is defined in this process. Import its definition in the worker.`,\n );\n const { payload, context: captured } = unwrapPayload(job.data);\n const context: JobContext = {\n id: job.id,\n name: job.name,\n queue: job.queue,\n attempt: job.attemptsMade + 1,\n maxAttempts: job.options.attempts ?? 1,\n context: captured,\n signal: abort.signal,\n progress: async (value) => {\n if (\n !this.mutate(job, token, (current) => {\n current.progress = jsonCopy(value);\n })\n )\n throw new Error(\"Queue job lease lost.\");\n },\n log: async (line) => {\n if (\n !this.mutate(job, token, (current) => {\n current.logs.push(line);\n })\n )\n throw new Error(\"Queue job lease lost.\");\n },\n };\n const run = async () => definition.handle(payload, context);\n const queueContext = getQueueContext();\n const result = jsonCopy(\n await abort.guard(async () =>\n captured !== undefined && queueContext ? queueContext.restore(captured, run) : run(),\n ),\n );\n this.mutate(\n job,\n token,\n (current) => {\n current.attemptsMade++;\n current.result = result;\n current.state = \"completed\";\n current.finishedAt = Date.now();\n current.leaseUntil = 0;\n delete current.token;\n },\n true,\n );\n } catch (error) {\n this.mutate(\n job,\n token,\n (current) => {\n current.attemptsMade++;\n current.failedReason = error instanceof Error ? error.message : String(error);\n current.stacktrace.push(\n error instanceof Error ? (error.stack ?? error.message) : String(error),\n );\n current.leaseUntil = 0;\n delete current.token;\n if (\n error instanceof UnrecoverableJobError ||\n (error instanceof JobTimeoutError && error.failOnTimeout) ||\n current.attemptsMade >= (current.options.attempts ?? 1)\n ) {\n current.state = \"failed\";\n current.finishedAt = Date.now();\n } else {\n const backoff = current.options.backoff;\n const base = typeof backoff === \"number\" ? backoff : (backoff?.delay ?? 0);\n const delay =\n typeof backoff === \"object\" && backoff.type === \"exponential\"\n ? base * 2 ** (current.attemptsMade - 1)\n : base;\n current.readyAt = Math.min(Number.MAX_SAFE_INTEGER, Date.now() + delay);\n current.state = delay > 0 ? \"delayed\" : current.priority ? \"prioritized\" : \"waiting\";\n }\n },\n true,\n );\n } finally {\n abort.dispose();\n }\n }\n\n /** Fencing: expired owners cannot renew, finish, or overwrite a new owner's job. */\n private mutate(\n job: LocalJob,\n token: string,\n update: (current: LocalJob) => void,\n retain = false,\n ): boolean {\n if (this.closed) return false;\n return this.store.transaction(() => {\n const current = this.store.get(job.queue, job.id);\n if (!current || !ownsLease(current, token)) return false;\n update(current);\n this.store.put(current);\n if (retain && (current.state === \"completed\" || current.state === \"failed\")) {\n const retention =\n current.state === \"completed\"\n ? current.options.removeOnComplete\n : current.options.removeOnFail;\n if (retention === true) this.store.remove(current.queue, current.id);\n if (typeof retention === \"number\") {\n for (const old of newestFirst(this.store.list(current.queue, current.state)).slice(\n retention,\n ))\n this.store.remove(old.queue, old.id);\n }\n }\n return true;\n });\n }\n\n private assertOpen(): void {\n if (this.closed) throw new Error(\"Queue driver is closed.\");\n }\n}\n\nfunction snapshot(job: LocalJob): JobSnapshot {\n return {\n id: job.id,\n name: job.name,\n queue: job.queue,\n state: job.state,\n payload: unwrapPayload(job.data).payload,\n progress: job.progress,\n attemptsMade: job.attemptsMade,\n result: job.result,\n failedReason: job.failedReason,\n createdAt: new Date(job.createdAt),\n finishedAt: job.finishedAt === undefined ? undefined : new Date(job.finishedAt),\n };\n}\nfunction newestFirst(jobs: LocalJob[]): LocalJob[] {\n return jobs.reverse().sort((a, b) => (b.finishedAt ?? 0) - (a.finishedAt ?? 0));\n}\nfunction positiveInteger(value: number, name: string, minimum = 1): number {\n if (!Number.isSafeInteger(value) || value < minimum || value > 2_147_483_647)\n throw new Error(`${name} must be an integer between ${minimum} and 2147483647.`);\n return value;\n}\nfunction validateOptions({ options }: DriverDispatch): void {\n positiveInteger(options.attempts ?? 1, \"attempts\");\n if (\n options.priority !== undefined &&\n (!Number.isInteger(options.priority) || options.priority < 0 || options.priority > 2_097_152)\n )\n throw new Error(\"priority must be an integer between 0 and 2097152.\");\n if (options.timeout !== undefined && !(Number.isFinite(options.timeout) && options.timeout > 0))\n throw new Error(\"timeout must be a positive finite number of milliseconds.\");\n const backoff = options.backoff;\n const delay = typeof backoff === \"number\" ? backoff : (backoff?.delay ?? 0);\n if (\n !Number.isFinite(delay) ||\n delay < 0 ||\n (typeof backoff === \"object\" && ![\"fixed\", \"exponential\"].includes(backoff.type))\n )\n throw new Error(\"backoff must have a non-negative finite delay and fixed or exponential type.\");\n if (options.delay !== undefined && (!Number.isFinite(options.delay) || options.delay < 0))\n throw new Error(\"delay must be non-negative and finite.\");\n for (const retention of [options.removeOnComplete, options.removeOnFail]) {\n if (typeof retention === \"number\" && (!Number.isInteger(retention) || retention < 0))\n throw new Error(\"Job retention must be a non-negative integer or boolean.\");\n }\n}\n\nfunction ownsLease(job: LocalJob, token: string): boolean {\n return job.state === \"active\" && job.token === token && job.leaseUntil > Date.now();\n}\n"],"mappings":"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"}
|
package/esm/errors.d.mts
CHANGED
|
@@ -40,5 +40,25 @@ export declare class UnrecoverableJobError extends Error {
|
|
|
40
40
|
cause?: unknown;
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* The failure recorded when a job attempt outlives its `timeout`. It appears
|
|
45
|
+
* as the failed reason of the attempt and as the abort reason of
|
|
46
|
+
* `ctx.signal`. With `failOnTimeout` the job is not retried.
|
|
47
|
+
*/
|
|
48
|
+
export declare class JobTimeoutError extends Error {
|
|
49
|
+
/** The job name. */
|
|
50
|
+
readonly jobName: string;
|
|
51
|
+
/** The limit that was exceeded, in milliseconds. */
|
|
52
|
+
readonly timeout: number;
|
|
53
|
+
/** Whether the job fails without retrying. */
|
|
54
|
+
readonly failOnTimeout: boolean;
|
|
55
|
+
constructor(
|
|
56
|
+
/** The job name. */
|
|
57
|
+
jobName: string,
|
|
58
|
+
/** The limit that was exceeded, in milliseconds. */
|
|
59
|
+
timeout: number,
|
|
60
|
+
/** Whether the job fails without retrying. */
|
|
61
|
+
failOnTimeout?: boolean);
|
|
62
|
+
}
|
|
43
63
|
//#endregion
|
|
44
64
|
//# sourceMappingURL=errors.d.mts.map
|
package/esm/errors.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/errors.ts"],"mappings":";;;;;qBAIa,gCAAgC;EAC3C;;;;;qBAaW,6BAA6B;EACxC,YAAmB;;;;;qBAYR,kCAAkC;EAC7C,YAAmB;;;;;qBASR,+BAA+B;EAC1C,YAAmB,YAAY;;;;;;qBAUpB,sCAAsC;EACjD,YAAmB;;;;;;qBAcR,8BAA8B;EACzC,YAAmB,iBAAiB;IAAY"}
|
|
1
|
+
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/errors.ts"],"mappings":";;;;;qBAIa,gCAAgC;EAC3C;;;;;qBAaW,6BAA6B;EACxC,YAAmB;;;;;qBAYR,kCAAkC;EAC7C,YAAmB;;;;;qBASR,+BAA+B;EAC1C,YAAmB,YAAY;;;;;;qBAUpB,sCAAsC;EACjD,YAAmB;;;;;;qBAcR,8BAA8B;EACzC,YAAmB,iBAAiB;IAAY;;;;;;;;qBAWrC,wBAAwB;;WAGjB;;WAEA;;WAEA;EANlB;;EAEkB;;EAEA;;EAEA"}
|
package/esm/errors.mjs
CHANGED
|
@@ -59,7 +59,21 @@ var UnrecoverableJobError = class extends Error {
|
|
|
59
59
|
this.name = "UnrecoverableJobError";
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* The failure recorded when a job attempt outlives its `timeout`. It appears
|
|
64
|
+
* as the failed reason of the attempt and as the abort reason of
|
|
65
|
+
* `ctx.signal`. With `failOnTimeout` the job is not retried.
|
|
66
|
+
*/
|
|
67
|
+
var JobTimeoutError = class extends Error {
|
|
68
|
+
constructor(jobName, timeout, failOnTimeout = false) {
|
|
69
|
+
super(`Job "${jobName}" timed out after ${timeout}ms.`);
|
|
70
|
+
this.jobName = jobName;
|
|
71
|
+
this.timeout = timeout;
|
|
72
|
+
this.failOnTimeout = failOnTimeout;
|
|
73
|
+
this.name = "JobTimeoutError";
|
|
74
|
+
}
|
|
75
|
+
};
|
|
62
76
|
|
|
63
77
|
//#endregion
|
|
64
|
-
export { FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, QueueDashboardDependencyError, QueueNotConfiguredError, UnrecoverableJobError };
|
|
78
|
+
export { FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, JobTimeoutError, QueueDashboardDependencyError, QueueNotConfiguredError, UnrecoverableJobError };
|
|
65
79
|
//# sourceMappingURL=errors.mjs.map
|
package/esm/errors.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.mjs","names":[],"sources":["../../../../../../../packages/queue/src/errors.ts"],"sourcesContent":["/**\n * Thrown when the queue is used before `setQueueConfig` (or the queue\n * connector) supplied a configuration.\n */\nexport class QueueNotConfiguredError extends Error {\n public constructor() {\n super(\n \"@warlock.js/queue is not configured. Add src/config/queue.ts exporting a QueueConfig \" +\n \"and register queueConnector() in warlock.config.ts > connectors, \" +\n \"or call setQueueConfig() yourself.\",\n );\n this.name = \"QueueNotConfiguredError\";\n }\n}\n\n/**\n * Thrown for a malformed duration such as `\"10 minutes\"`.\n */\nexport class InvalidDurationError extends Error {\n public constructor(value: unknown) {\n super(\n `Invalid duration ${JSON.stringify(value)}: expected milliseconds as a non-negative number ` +\n `or a string like \"500ms\", \"30s\", \"10m\", \"2h\", \"1d\".`,\n );\n this.name = \"InvalidDurationError\";\n }\n}\n\n/**\n * Thrown by `defineJob` for an invalid definition.\n */\nexport class InvalidJobDefinitionError extends Error {\n public constructor(message: string) {\n super(message);\n this.name = \"InvalidJobDefinitionError\";\n }\n}\n\n/**\n * Thrown by `retryFailedJob` when no failed job has the given id.\n */\nexport class FailedJobNotFoundError extends Error {\n public constructor(id: string, queue: string) {\n super(`No failed job with id \"${id}\" on queue \"${queue}\".`);\n this.name = \"FailedJobNotFoundError\";\n }\n}\n\n/**\n * Thrown by `queueDashboard` when an optional bull-board package is not\n * installed.\n */\nexport class QueueDashboardDependencyError extends Error {\n public constructor(missing: string) {\n super(\n `The queue dashboard needs the optional package \"${missing}\", which is not installed.\\n` +\n \"Install both bull-board packages:\\n\\n\" +\n \" npm install @bull-board/api @bull-board/fastify\\n\",\n );\n this.name = \"QueueDashboardDependencyError\";\n }\n}\n\n/**\n * Throw from a job handler (or a queue context's `restore`) to fail the job\n * permanently — it is not retried, whatever `attempts` allows.\n */\nexport class UnrecoverableJobError extends Error {\n public constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"UnrecoverableJobError\";\n }\n}\n"],"mappings":";;;;;AAIA,IAAa,0BAAb,cAA6C,MAAM;CACjD,AAAO,cAAc;EACnB,MACE,0LAGF;EACA,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,AAAO,YAAY,OAAgB;EACjC,MACE,oBAAoB,KAAK,UAAU,KAAK,EAAE,qGAE5C;EACA,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,4BAAb,cAA+C,MAAM;CACnD,AAAO,YAAY,SAAiB;EAClC,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,yBAAb,cAA4C,MAAM;CAChD,AAAO,YAAY,IAAY,OAAe;EAC5C,MAAM,0BAA0B,GAAG,cAAc,MAAM,GAAG;EAC1D,KAAK,OAAO;CACd;AACF;;;;;AAMA,IAAa,gCAAb,cAAmD,MAAM;CACvD,AAAO,YAAY,SAAiB;EAClC,MACE,mDAAmD,QAAQ;;;CAG7D;EACA,KAAK,OAAO;CACd;AACF;;;;;AAMA,IAAa,wBAAb,cAA2C,MAAM;CAC/C,AAAO,YAAY,SAAiB,SAA+B;EACjE,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;CACd;AACF"}
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../../../../../../../packages/queue/src/errors.ts"],"sourcesContent":["/**\n * Thrown when the queue is used before `setQueueConfig` (or the queue\n * connector) supplied a configuration.\n */\nexport class QueueNotConfiguredError extends Error {\n public constructor() {\n super(\n \"@warlock.js/queue is not configured. Add src/config/queue.ts exporting a QueueConfig \" +\n \"and register queueConnector() in warlock.config.ts > connectors, \" +\n \"or call setQueueConfig() yourself.\",\n );\n this.name = \"QueueNotConfiguredError\";\n }\n}\n\n/**\n * Thrown for a malformed duration such as `\"10 minutes\"`.\n */\nexport class InvalidDurationError extends Error {\n public constructor(value: unknown) {\n super(\n `Invalid duration ${JSON.stringify(value)}: expected milliseconds as a non-negative number ` +\n `or a string like \"500ms\", \"30s\", \"10m\", \"2h\", \"1d\".`,\n );\n this.name = \"InvalidDurationError\";\n }\n}\n\n/**\n * Thrown by `defineJob` for an invalid definition.\n */\nexport class InvalidJobDefinitionError extends Error {\n public constructor(message: string) {\n super(message);\n this.name = \"InvalidJobDefinitionError\";\n }\n}\n\n/**\n * Thrown by `retryFailedJob` when no failed job has the given id.\n */\nexport class FailedJobNotFoundError extends Error {\n public constructor(id: string, queue: string) {\n super(`No failed job with id \"${id}\" on queue \"${queue}\".`);\n this.name = \"FailedJobNotFoundError\";\n }\n}\n\n/**\n * Thrown by `queueDashboard` when an optional bull-board package is not\n * installed.\n */\nexport class QueueDashboardDependencyError extends Error {\n public constructor(missing: string) {\n super(\n `The queue dashboard needs the optional package \"${missing}\", which is not installed.\\n` +\n \"Install both bull-board packages:\\n\\n\" +\n \" npm install @bull-board/api @bull-board/fastify\\n\",\n );\n this.name = \"QueueDashboardDependencyError\";\n }\n}\n\n/**\n * Throw from a job handler (or a queue context's `restore`) to fail the job\n * permanently — it is not retried, whatever `attempts` allows.\n */\nexport class UnrecoverableJobError extends Error {\n public constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"UnrecoverableJobError\";\n }\n}\n\n/**\n * The failure recorded when a job attempt outlives its `timeout`. It appears\n * as the failed reason of the attempt and as the abort reason of\n * `ctx.signal`. With `failOnTimeout` the job is not retried.\n */\nexport class JobTimeoutError extends Error {\n public constructor(\n /** The job name. */\n public readonly jobName: string,\n /** The limit that was exceeded, in milliseconds. */\n public readonly timeout: number,\n /** Whether the job fails without retrying. */\n public readonly failOnTimeout = false,\n ) {\n super(`Job \"${jobName}\" timed out after ${timeout}ms.`);\n this.name = \"JobTimeoutError\";\n }\n}\n"],"mappings":";;;;;AAIA,IAAa,0BAAb,cAA6C,MAAM;CACjD,AAAO,cAAc;EACnB,MACE,0LAGF;EACA,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,AAAO,YAAY,OAAgB;EACjC,MACE,oBAAoB,KAAK,UAAU,KAAK,EAAE,qGAE5C;EACA,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,4BAAb,cAA+C,MAAM;CACnD,AAAO,YAAY,SAAiB;EAClC,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,yBAAb,cAA4C,MAAM;CAChD,AAAO,YAAY,IAAY,OAAe;EAC5C,MAAM,0BAA0B,GAAG,cAAc,MAAM,GAAG;EAC1D,KAAK,OAAO;CACd;AACF;;;;;AAMA,IAAa,gCAAb,cAAmD,MAAM;CACvD,AAAO,YAAY,SAAiB;EAClC,MACE,mDAAmD,QAAQ;;;CAG7D;EACA,KAAK,OAAO;CACd;AACF;;;;;AAMA,IAAa,wBAAb,cAA2C,MAAM;CAC/C,AAAO,YAAY,SAAiB,SAA+B;EACjE,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;CACd;AACF;;;;;;AAOA,IAAa,kBAAb,cAAqC,MAAM;CACzC,AAAO,YAEL,AAAgB,SAEhB,AAAgB,SAEhB,AAAgB,gBAAgB,OAChC;EACA,MAAM,QAAQ,QAAQ,oBAAoB,QAAQ,IAAI;EANtC;EAEA;EAEA;EAGhB,KAAK,OAAO;CACd;AACF"}
|
package/esm/index.d.mts
CHANGED
|
@@ -5,7 +5,7 @@ import { DEFAULT_DASHBOARD_PATH, mountQueueDashboard } from "./dashboard-boot.mj
|
|
|
5
5
|
import { defineJob } from "./define-job.mjs";
|
|
6
6
|
import { DispatchInterceptor, getDispatchInterceptor, setDispatchInterceptor } from "./dispatch-interceptor.mjs";
|
|
7
7
|
import { toMilliseconds } from "./duration.mjs";
|
|
8
|
-
import { FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, QueueDashboardDependencyError, QueueNotConfiguredError, UnrecoverableJobError } from "./errors.mjs";
|
|
8
|
+
import { FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, JobTimeoutError, QueueDashboardDependencyError, QueueNotConfiguredError, UnrecoverableJobError } from "./errors.mjs";
|
|
9
9
|
import { FailedJobsOptions, failedJobs, retryFailedJob } from "./failed-jobs.mjs";
|
|
10
10
|
import { RegisteredJob, queueOf, registeredJobs } from "./job-registry.mjs";
|
|
11
11
|
import { QUEUE_CONNECTOR_PRIORITY, QueueConnectorOptions, queueConnector } from "./queue-connector.mjs";
|
|
@@ -14,4 +14,4 @@ import { QueueDashboardUnguardedError } from "./queue-dashboard-unguarded.error.
|
|
|
14
14
|
import { CloseQueueOptions, closeQueue, getQueue, runningWorkers, startWorkers } from "./queue-manager.mjs";
|
|
15
15
|
import { DriverDispatch, QueueDriver } from "./driver.mjs";
|
|
16
16
|
import { getQueueDriver } from "./queue-driver.mjs";
|
|
17
|
-
export { type CloseQueueOptions, DEFAULT_DASHBOARD_PATH, DashboardServer, type DispatchInterceptor, DispatchOptions, DispatchedJob, type DriverDispatch, Duration, DurationUnit, FailedJob, FailedJobNotFoundError, FailedJobsOptions, InvalidDurationError, InvalidJobDefinitionError, JobBackoff, JobContext, JobDefinition, JobOptions, JobProgress, JobRetention, JobSnapshot, JobState, LocalQueueConfig, QUEUE_CONNECTOR_PRIORITY, QueueConfig, QueueConnectorOptions, type QueueContext, QueueDashboardConfig, QueueDashboardDependencyError, QueueDashboardOptions, QueueDashboardUnguardedError, type QueueDriver, QueueJob, QueueNotConfiguredError, QueueWorkersConfig, type RegisteredJob, UnrecoverableJobError, closeQueue, defaultQueueName, defineJob, defineQueueContext, failedJobs, getDispatchInterceptor, getQueue, getQueueConfig, getQueueContext, getQueueDriver, loadBullBoard, mountQueueDashboard, queueConnector, queueDashboard, queueOf, registeredJobs, resetQueueConfig, resetQueueContext, retryFailedJob, runningWorkers, setDispatchInterceptor, setQueueConfig, setQueueContext, startWorkers, toMilliseconds };
|
|
17
|
+
export { type CloseQueueOptions, DEFAULT_DASHBOARD_PATH, DashboardServer, type DispatchInterceptor, DispatchOptions, DispatchedJob, type DriverDispatch, Duration, DurationUnit, FailedJob, FailedJobNotFoundError, FailedJobsOptions, InvalidDurationError, InvalidJobDefinitionError, JobBackoff, JobContext, JobDefinition, JobOptions, JobProgress, JobRetention, JobSnapshot, JobState, JobTimeoutError, LocalQueueConfig, QUEUE_CONNECTOR_PRIORITY, QueueConfig, QueueConnectorOptions, type QueueContext, QueueDashboardConfig, QueueDashboardDependencyError, QueueDashboardOptions, QueueDashboardUnguardedError, type QueueDriver, QueueJob, QueueNotConfiguredError, QueueWorkersConfig, type RegisteredJob, UnrecoverableJobError, closeQueue, defaultQueueName, defineJob, defineQueueContext, failedJobs, getDispatchInterceptor, getQueue, getQueueConfig, getQueueContext, getQueueDriver, loadBullBoard, mountQueueDashboard, queueConnector, queueDashboard, queueOf, registeredJobs, resetQueueConfig, resetQueueContext, retryFailedJob, runningWorkers, setDispatchInterceptor, setQueueConfig, setQueueContext, startWorkers, toMilliseconds };
|
package/esm/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, QueueDashboardDependencyError, QueueNotConfiguredError, UnrecoverableJobError } from "./errors.mjs";
|
|
1
|
+
import { FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, JobTimeoutError, QueueDashboardDependencyError, QueueNotConfiguredError, UnrecoverableJobError } from "./errors.mjs";
|
|
2
2
|
import { defaultQueueName, getQueueConfig, resetQueueConfig, setQueueConfig } from "./config.mjs";
|
|
3
3
|
import { queueOf, registeredJobs } from "./job-registry.mjs";
|
|
4
|
+
import { toMilliseconds } from "./duration.mjs";
|
|
4
5
|
import { defineQueueContext, getQueueContext, resetQueueContext, setQueueContext } from "./queue-context.mjs";
|
|
5
6
|
import { getQueueDriver } from "./queue-driver.mjs";
|
|
6
7
|
import { closeQueue, getQueue, runningWorkers, startWorkers } from "./queue-manager.mjs";
|
|
@@ -8,9 +9,8 @@ import { loadBullBoard, queueDashboard } from "./dashboard.mjs";
|
|
|
8
9
|
import { QueueDashboardUnguardedError } from "./queue-dashboard-unguarded.error.mjs";
|
|
9
10
|
import { DEFAULT_DASHBOARD_PATH, mountQueueDashboard } from "./dashboard-boot.mjs";
|
|
10
11
|
import { getDispatchInterceptor, setDispatchInterceptor } from "./dispatch-interceptor.mjs";
|
|
11
|
-
import { toMilliseconds } from "./duration.mjs";
|
|
12
12
|
import { defineJob } from "./define-job.mjs";
|
|
13
13
|
import { failedJobs, retryFailedJob } from "./failed-jobs.mjs";
|
|
14
14
|
import { QUEUE_CONNECTOR_PRIORITY, queueConnector } from "./queue-connector.mjs";
|
|
15
15
|
|
|
16
|
-
export { DEFAULT_DASHBOARD_PATH, FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, QUEUE_CONNECTOR_PRIORITY, QueueDashboardDependencyError, QueueDashboardUnguardedError, QueueNotConfiguredError, UnrecoverableJobError, closeQueue, defaultQueueName, defineJob, defineQueueContext, failedJobs, getDispatchInterceptor, getQueue, getQueueConfig, getQueueContext, getQueueDriver, loadBullBoard, mountQueueDashboard, queueConnector, queueDashboard, queueOf, registeredJobs, resetQueueConfig, resetQueueContext, retryFailedJob, runningWorkers, setDispatchInterceptor, setQueueConfig, setQueueContext, startWorkers, toMilliseconds };
|
|
16
|
+
export { DEFAULT_DASHBOARD_PATH, FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, JobTimeoutError, QUEUE_CONNECTOR_PRIORITY, QueueDashboardDependencyError, QueueDashboardUnguardedError, QueueNotConfiguredError, UnrecoverableJobError, closeQueue, defaultQueueName, defineJob, defineQueueContext, failedJobs, getDispatchInterceptor, getQueue, getQueueConfig, getQueueContext, getQueueDriver, loadBullBoard, mountQueueDashboard, queueConnector, queueDashboard, queueOf, registeredJobs, resetQueueConfig, resetQueueContext, retryFailedJob, runningWorkers, setDispatchInterceptor, setQueueConfig, setQueueContext, startWorkers, toMilliseconds };
|
package/esm/job-options.d.mts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { JobOptions } from "./types.mjs";
|
|
2
2
|
//#region ../../packages/queue/src/job-options.d.ts
|
|
3
|
-
export type ResolvedJobOptions = JobOptions & {
|
|
3
|
+
export type ResolvedJobOptions = Omit<JobOptions, "timeout"> & {
|
|
4
4
|
delay?: number;
|
|
5
5
|
priority?: number;
|
|
6
6
|
jobId?: string;
|
|
7
|
+
/** Per-attempt limit in milliseconds. */
|
|
8
|
+
timeout?: number;
|
|
7
9
|
};
|
|
8
10
|
//#endregion
|
|
9
11
|
//# sourceMappingURL=job-options.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"job-options.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/job-options.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"job-options.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/job-options.ts"],"mappings":";;YAKY,qBAAqB,KAAK;EACpC;EACA;EACA;;EAEA"}
|
package/esm/job-options.mjs
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { getQueueConfig } from "./config.mjs";
|
|
2
2
|
import { toMilliseconds } from "./duration.mjs";
|
|
3
|
+
import { assertFailOnTimeout, resolveTimeout } from "./job-timeout.mjs";
|
|
3
4
|
|
|
4
5
|
//#region ../../packages/queue/src/job-options.ts
|
|
5
6
|
function resolveJobOptions(definition, options) {
|
|
6
7
|
const defaults = getQueueConfig().defaultJobOptions ?? {};
|
|
8
|
+
const timeout = options.timeout ?? definition.timeout ?? defaults.timeout;
|
|
9
|
+
const failOnTimeout = options.failOnTimeout ?? definition.failOnTimeout ?? defaults.failOnTimeout;
|
|
10
|
+
assertFailOnTimeout(failOnTimeout);
|
|
7
11
|
return {
|
|
8
12
|
attempts: options.attempts ?? definition.attempts ?? defaults.attempts,
|
|
9
13
|
backoff: options.backoff ?? definition.backoff ?? defaults.backoff,
|
|
10
14
|
removeOnComplete: definition.removeOnComplete ?? defaults.removeOnComplete,
|
|
11
15
|
removeOnFail: definition.removeOnFail ?? defaults.removeOnFail,
|
|
16
|
+
timeout: timeout === void 0 ? void 0 : resolveTimeout(timeout),
|
|
17
|
+
failOnTimeout,
|
|
12
18
|
delay: options.delay === void 0 ? void 0 : toMilliseconds(options.delay),
|
|
13
19
|
priority: options.priority,
|
|
14
20
|
jobId: options.jobId
|
package/esm/job-options.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"job-options.mjs","names":[],"sources":["../../../../../../../packages/queue/src/job-options.ts"],"sourcesContent":["import { getQueueConfig } from \"./config\";\nimport { toMilliseconds } from \"./duration\";\nimport type { DispatchOptions, JobOptions } from \"./types\";\n\nexport type ResolvedJobOptions = JobOptions & {\n delay?: number;\n priority?: number;\n jobId?: string;\n};\n\nexport function resolveJobOptions(\n definition: JobOptions,\n options: DispatchOptions,\n): ResolvedJobOptions {\n const defaults = getQueueConfig().defaultJobOptions ?? {};\n return {\n attempts: options.attempts ?? definition.attempts ?? defaults.attempts,\n backoff: options.backoff ?? definition.backoff ?? defaults.backoff,\n removeOnComplete: definition.removeOnComplete ?? defaults.removeOnComplete,\n removeOnFail: definition.removeOnFail ?? defaults.removeOnFail,\n delay: options.delay === undefined ? undefined : toMilliseconds(options.delay),\n priority: options.priority,\n jobId: options.jobId,\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"job-options.mjs","names":[],"sources":["../../../../../../../packages/queue/src/job-options.ts"],"sourcesContent":["import { getQueueConfig } from \"./config\";\nimport { toMilliseconds } from \"./duration\";\nimport { assertFailOnTimeout, resolveTimeout } from \"./job-timeout\";\nimport type { DispatchOptions, JobOptions } from \"./types\";\n\nexport type ResolvedJobOptions = Omit<JobOptions, \"timeout\"> & {\n delay?: number;\n priority?: number;\n jobId?: string;\n /** Per-attempt limit in milliseconds. */\n timeout?: number;\n};\n\nexport function resolveJobOptions(\n definition: JobOptions,\n options: DispatchOptions,\n): ResolvedJobOptions {\n const defaults = getQueueConfig().defaultJobOptions ?? {};\n const timeout = options.timeout ?? definition.timeout ?? defaults.timeout;\n const failOnTimeout = options.failOnTimeout ?? definition.failOnTimeout ?? defaults.failOnTimeout;\n assertFailOnTimeout(failOnTimeout);\n\n return {\n attempts: options.attempts ?? definition.attempts ?? defaults.attempts,\n backoff: options.backoff ?? definition.backoff ?? defaults.backoff,\n removeOnComplete: definition.removeOnComplete ?? defaults.removeOnComplete,\n removeOnFail: definition.removeOnFail ?? defaults.removeOnFail,\n timeout: timeout === undefined ? undefined : resolveTimeout(timeout),\n failOnTimeout,\n delay: options.delay === undefined ? undefined : toMilliseconds(options.delay),\n priority: options.priority,\n jobId: options.jobId,\n };\n}\n"],"mappings":";;;;;AAaA,SAAgB,kBACd,YACA,SACoB;CACpB,MAAM,WAAW,eAAe,CAAC,CAAC,qBAAqB,CAAC;CACxD,MAAM,UAAU,QAAQ,WAAW,WAAW,WAAW,SAAS;CAClE,MAAM,gBAAgB,QAAQ,iBAAiB,WAAW,iBAAiB,SAAS;CACpF,oBAAoB,aAAa;CAEjC,OAAO;EACL,UAAU,QAAQ,YAAY,WAAW,YAAY,SAAS;EAC9D,SAAS,QAAQ,WAAW,WAAW,WAAW,SAAS;EAC3D,kBAAkB,WAAW,oBAAoB,SAAS;EAC1D,cAAc,WAAW,gBAAgB,SAAS;EAClD,SAAS,YAAY,SAAY,SAAY,eAAe,OAAO;EACnE;EACA,OAAO,QAAQ,UAAU,SAAY,SAAY,eAAe,QAAQ,KAAK;EAC7E,UAAU,QAAQ;EAClB,OAAO,QAAQ;CACjB;AACF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { InvalidJobDefinitionError, JobTimeoutError } from "./errors.mjs";
|
|
2
|
+
import { toMilliseconds } from "./duration.mjs";
|
|
3
|
+
|
|
4
|
+
//#region ../../packages/queue/src/job-timeout.ts
|
|
5
|
+
/**
|
|
6
|
+
* Convert a `timeout` option to milliseconds. Unparsable values throw
|
|
7
|
+
* `InvalidDurationError`; zero and negative limits throw
|
|
8
|
+
* `InvalidJobDefinitionError` — a timeout is never silently repaired or ignored.
|
|
9
|
+
*/
|
|
10
|
+
function resolveTimeout(value, label = "timeout") {
|
|
11
|
+
const milliseconds = toMilliseconds(value);
|
|
12
|
+
if (milliseconds <= 0) throw new InvalidJobDefinitionError(`\`${label}\` must be greater than zero, got ${JSON.stringify(value)}.`);
|
|
13
|
+
return milliseconds;
|
|
14
|
+
}
|
|
15
|
+
/** Throw unless `failOnTimeout` is a boolean (or unset). */
|
|
16
|
+
function assertFailOnTimeout(value, label = "failOnTimeout") {
|
|
17
|
+
if (value !== void 0 && typeof value !== "boolean") throw new InvalidJobDefinitionError(`\`${label}\` must be a boolean, got ${JSON.stringify(value)}.`);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* One attempt's time limit, shared by every driver. Without a `timeout` it
|
|
21
|
+
* passes the parent signal through and adds nothing. On expiry the signal
|
|
22
|
+
* aborts with the `JobTimeoutError` and the attempt fails; a handler that
|
|
23
|
+
* ignores the signal keeps running in the background, because JavaScript
|
|
24
|
+
* cannot stop it.
|
|
25
|
+
*/
|
|
26
|
+
function createJobAbort(name, settings, parent) {
|
|
27
|
+
const { timeout } = settings;
|
|
28
|
+
if (timeout === void 0) return {
|
|
29
|
+
signal: parent,
|
|
30
|
+
guard: (work) => work(),
|
|
31
|
+
dispose() {}
|
|
32
|
+
};
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
const onParentAbort = () => controller.abort(parent?.reason);
|
|
35
|
+
if (parent?.aborted) controller.abort(parent.reason);
|
|
36
|
+
else parent?.addEventListener("abort", onParentAbort, { once: true });
|
|
37
|
+
let timer;
|
|
38
|
+
return {
|
|
39
|
+
signal: controller.signal,
|
|
40
|
+
guard(work) {
|
|
41
|
+
const expired = new Promise((_, reject) => {
|
|
42
|
+
timer = setTimeout(() => {
|
|
43
|
+
const error = new JobTimeoutError(name, timeout, settings.failOnTimeout === true);
|
|
44
|
+
controller.abort(error);
|
|
45
|
+
reject(error);
|
|
46
|
+
}, timeout);
|
|
47
|
+
});
|
|
48
|
+
const running = work();
|
|
49
|
+
running.catch(() => {});
|
|
50
|
+
return Promise.race([running, expired]);
|
|
51
|
+
},
|
|
52
|
+
dispose() {
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
parent?.removeEventListener("abort", onParentAbort);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
export { assertFailOnTimeout, createJobAbort, resolveTimeout };
|
|
61
|
+
//# sourceMappingURL=job-timeout.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-timeout.mjs","names":[],"sources":["../../../../../../../packages/queue/src/job-timeout.ts"],"sourcesContent":["import { InvalidJobDefinitionError, JobTimeoutError } from \"./errors\";\nimport { toMilliseconds } from \"./duration\";\nimport type { Duration } from \"./types\";\n\n/**\n * Convert a `timeout` option to milliseconds. Unparsable values throw\n * `InvalidDurationError`; zero and negative limits throw\n * `InvalidJobDefinitionError` — a timeout is never silently repaired or ignored.\n */\nexport function resolveTimeout(value: Duration | string, label = \"timeout\"): number {\n const milliseconds = toMilliseconds(value);\n\n if (milliseconds <= 0) {\n throw new InvalidJobDefinitionError(\n `\\`${label}\\` must be greater than zero, got ${JSON.stringify(value)}.`,\n );\n }\n\n return milliseconds;\n}\n\n/** Throw unless `failOnTimeout` is a boolean (or unset). */\nexport function assertFailOnTimeout(value: unknown, label = \"failOnTimeout\"): void {\n if (value !== undefined && typeof value !== \"boolean\") {\n throw new InvalidJobDefinitionError(\n `\\`${label}\\` must be a boolean, got ${JSON.stringify(value)}.`,\n );\n }\n}\n\nexport type JobTimeoutSettings = {\n /** Limit per attempt in milliseconds. */\n timeout?: number;\n /** Fail the job without retrying when the limit is hit. */\n failOnTimeout?: boolean;\n};\n\nexport type JobAbort = {\n /** Handed to the handler: aborts on timeout, or when the parent signal aborts. */\n signal: AbortSignal | undefined;\n /** Run the work and reject with `JobTimeoutError` if it outlives the limit. */\n guard<T>(work: () => Promise<T>): Promise<T>;\n /** Clear the timer. Always call when the attempt ends. */\n dispose(): void;\n};\n\n/**\n * One attempt's time limit, shared by every driver. Without a `timeout` it\n * passes the parent signal through and adds nothing. On expiry the signal\n * aborts with the `JobTimeoutError` and the attempt fails; a handler that\n * ignores the signal keeps running in the background, because JavaScript\n * cannot stop it.\n */\nexport function createJobAbort(\n name: string,\n settings: JobTimeoutSettings,\n parent?: AbortSignal,\n): JobAbort {\n const { timeout } = settings;\n\n if (timeout === undefined) {\n return { signal: parent, guard: (work) => work(), dispose() {} };\n }\n\n const controller = new AbortController();\n const onParentAbort = () => controller.abort(parent?.reason);\n\n if (parent?.aborted) {\n controller.abort(parent.reason);\n } else {\n parent?.addEventListener(\"abort\", onParentAbort, { once: true });\n }\n\n let timer: NodeJS.Timeout | undefined;\n\n return {\n signal: controller.signal,\n guard(work) {\n const expired = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n const error = new JobTimeoutError(name, timeout, settings.failOnTimeout === true);\n controller.abort(error);\n reject(error);\n }, timeout);\n });\n const running = work();\n\n // The abandoned handler may still reject later; that must not surface as unhandled.\n running.catch(() => {});\n\n return Promise.race([running, expired]);\n },\n dispose() {\n clearTimeout(timer);\n parent?.removeEventListener(\"abort\", onParentAbort);\n },\n };\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,eAAe,OAA0B,QAAQ,WAAmB;CAClF,MAAM,eAAe,eAAe,KAAK;CAEzC,IAAI,gBAAgB,GAClB,MAAM,IAAI,0BACR,KAAK,MAAM,oCAAoC,KAAK,UAAU,KAAK,EAAE,EACvE;CAGF,OAAO;AACT;;AAGA,SAAgB,oBAAoB,OAAgB,QAAQ,iBAAuB;CACjF,IAAI,UAAU,UAAa,OAAO,UAAU,WAC1C,MAAM,IAAI,0BACR,KAAK,MAAM,4BAA4B,KAAK,UAAU,KAAK,EAAE,EAC/D;AAEJ;;;;;;;;AAyBA,SAAgB,eACd,MACA,UACA,QACU;CACV,MAAM,EAAE,YAAY;CAEpB,IAAI,YAAY,QACd,OAAO;EAAE,QAAQ;EAAQ,QAAQ,SAAS,KAAK;EAAG,UAAU,CAAC;CAAE;CAGjE,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,sBAAsB,WAAW,MAAM,QAAQ,MAAM;CAE3D,IAAI,QAAQ,SACV,WAAW,MAAM,OAAO,MAAM;MAE9B,QAAQ,iBAAiB,SAAS,eAAe,EAAE,MAAM,KAAK,CAAC;CAGjE,IAAI;CAEJ,OAAO;EACL,QAAQ,WAAW;EACnB,MAAM,MAAM;GACV,MAAM,UAAU,IAAI,SAAgB,GAAG,WAAW;IAChD,QAAQ,iBAAiB;KACvB,MAAM,QAAQ,IAAI,gBAAgB,MAAM,SAAS,SAAS,kBAAkB,IAAI;KAChF,WAAW,MAAM,KAAK;KACtB,OAAO,KAAK;IACd,GAAG,OAAO;GACZ,CAAC;GACD,MAAM,UAAU,KAAK;GAGrB,QAAQ,YAAY,CAAC,CAAC;GAEtB,OAAO,QAAQ,KAAK,CAAC,SAAS,OAAO,CAAC;EACxC;EACA,UAAU;GACR,aAAa,KAAK;GAClB,QAAQ,oBAAoB,SAAS,aAAa;EACpD;CACF;AACF"}
|
package/esm/process-job.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { UnrecoverableJobError } from "./errors.mjs";
|
|
1
|
+
import { JobTimeoutError, UnrecoverableJobError } from "./errors.mjs";
|
|
2
2
|
import { findRegisteredJob } from "./job-registry.mjs";
|
|
3
|
+
import { createJobAbort } from "./job-timeout.mjs";
|
|
3
4
|
import { getQueueContext, unwrapPayload } from "./queue-context.mjs";
|
|
4
5
|
import { UnrecoverableError } from "bullmq";
|
|
5
6
|
|
|
@@ -16,6 +17,7 @@ async function processJob(job) {
|
|
|
16
17
|
const definition = findRegisteredJob(job.name);
|
|
17
18
|
if (!definition) throw new UnrecoverableError(`No job named "${job.name}" is defined in this process. Make sure the module that calls defineJob() is imported by the worker process.`);
|
|
18
19
|
const { payload, context: captured } = unwrapPayload(job.data);
|
|
20
|
+
const abort = createJobAbort(job.name, job.opts);
|
|
19
21
|
const context = {
|
|
20
22
|
id: String(job.id),
|
|
21
23
|
name: job.name,
|
|
@@ -26,15 +28,19 @@ async function processJob(job) {
|
|
|
26
28
|
log: async (line) => {
|
|
27
29
|
await job.log(line);
|
|
28
30
|
},
|
|
29
|
-
context: captured
|
|
31
|
+
context: captured,
|
|
32
|
+
signal: abort.signal
|
|
30
33
|
};
|
|
31
34
|
const run = async () => definition.handle(payload, context);
|
|
32
35
|
const queueContext = getQueueContext();
|
|
33
36
|
try {
|
|
34
|
-
return captured !== void 0 && queueContext ?
|
|
37
|
+
return await abort.guard(async () => captured !== void 0 && queueContext ? queueContext.restore(captured, run) : run());
|
|
35
38
|
} catch (error) {
|
|
36
39
|
if (error instanceof UnrecoverableJobError) throw new UnrecoverableError(error.message);
|
|
40
|
+
if (error instanceof JobTimeoutError && error.failOnTimeout) throw new UnrecoverableError(error.message);
|
|
37
41
|
throw error;
|
|
42
|
+
} finally {
|
|
43
|
+
abort.dispose();
|
|
38
44
|
}
|
|
39
45
|
}
|
|
40
46
|
|
package/esm/process-job.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-job.mjs","names":[],"sources":["../../../../../../../packages/queue/src/process-job.ts"],"sourcesContent":["import { type Job, UnrecoverableError } from \"bullmq\";\nimport { UnrecoverableJobError } from \"./errors\";\nimport { findRegisteredJob } from \"./job-registry\";\nimport { getQueueContext, unwrapPayload } from \"./queue-context\";\nimport type { JobContext } from \"./types\";\n\n/**\n * The single BullMQ processor every worker runs: route the job to the\n * handler registered under its name.\n *\n * A name with no handler in this process fails with `UnrecoverableError` —\n * retrying cannot make a missing definition appear, so it must not burn\n * through its attempts.\n */\nexport async function processJob(job: Job): Promise<unknown> {\n const definition = findRegisteredJob(job.name);\n\n if (!definition) {\n throw new UnrecoverableError(\n `No job named \"${job.name}\" is defined in this process. ` +\n \"Make sure the module that calls defineJob() is imported by the worker process.\",\n );\n }\n\n const { payload, context: captured } = unwrapPayload(job.data);\n\n const context: JobContext = {\n id: String(job.id),\n name: job.name,\n queue: job.queueName,\n attempt: job.attemptsMade + 1,\n maxAttempts: job.opts.attempts ?? 1,\n progress: (value) => job.updateProgress(value),\n log: async (line) => {\n await job.log(line);\n },\n context: captured,\n };\n\n const run = async () => definition.handle(payload, context);\n const queueContext = getQueueContext();\n\n try {\n return captured !== undefined && queueContext
|
|
1
|
+
{"version":3,"file":"process-job.mjs","names":[],"sources":["../../../../../../../packages/queue/src/process-job.ts"],"sourcesContent":["import { type Job, UnrecoverableError } from \"bullmq\";\nimport { JobTimeoutError, UnrecoverableJobError } from \"./errors\";\nimport { createJobAbort, type JobTimeoutSettings } from \"./job-timeout\";\nimport { findRegisteredJob } from \"./job-registry\";\nimport { getQueueContext, unwrapPayload } from \"./queue-context\";\nimport type { JobContext } from \"./types\";\n\n/**\n * The single BullMQ processor every worker runs: route the job to the\n * handler registered under its name.\n *\n * A name with no handler in this process fails with `UnrecoverableError` —\n * retrying cannot make a missing definition appear, so it must not burn\n * through its attempts.\n */\nexport async function processJob(job: Job): Promise<unknown> {\n const definition = findRegisteredJob(job.name);\n\n if (!definition) {\n throw new UnrecoverableError(\n `No job named \"${job.name}\" is defined in this process. ` +\n \"Make sure the module that calls defineJob() is imported by the worker process.\",\n );\n }\n\n const { payload, context: captured } = unwrapPayload(job.data);\n\n const abort = createJobAbort(job.name, job.opts as unknown as JobTimeoutSettings);\n\n const context: JobContext = {\n id: String(job.id),\n name: job.name,\n queue: job.queueName,\n attempt: job.attemptsMade + 1,\n maxAttempts: job.opts.attempts ?? 1,\n progress: (value) => job.updateProgress(value),\n log: async (line) => {\n await job.log(line);\n },\n context: captured,\n signal: abort.signal,\n };\n\n const run = async () => definition.handle(payload, context);\n const queueContext = getQueueContext();\n\n try {\n return await abort.guard(async () =>\n captured !== undefined && queueContext ? queueContext.restore(captured, run) : run(),\n );\n } catch (error) {\n if (error instanceof UnrecoverableJobError) {\n throw new UnrecoverableError(error.message);\n }\n\n // Unlike a plain timeout, this one must not burn the remaining attempts.\n if (error instanceof JobTimeoutError && error.failOnTimeout) {\n throw new UnrecoverableError(error.message);\n }\n\n throw error;\n } finally {\n abort.dispose();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,eAAsB,WAAW,KAA4B;CAC3D,MAAM,aAAa,kBAAkB,IAAI,IAAI;CAE7C,IAAI,CAAC,YACH,MAAM,IAAI,mBACR,iBAAiB,IAAI,KAAK,6GAE5B;CAGF,MAAM,EAAE,SAAS,SAAS,aAAa,cAAc,IAAI,IAAI;CAE7D,MAAM,QAAQ,eAAe,IAAI,MAAM,IAAI,IAAqC;CAEhF,MAAM,UAAsB;EAC1B,IAAI,OAAO,IAAI,EAAE;EACjB,MAAM,IAAI;EACV,OAAO,IAAI;EACX,SAAS,IAAI,eAAe;EAC5B,aAAa,IAAI,KAAK,YAAY;EAClC,WAAW,UAAU,IAAI,eAAe,KAAK;EAC7C,KAAK,OAAO,SAAS;GACnB,MAAM,IAAI,IAAI,IAAI;EACpB;EACA,SAAS;EACT,QAAQ,MAAM;CAChB;CAEA,MAAM,MAAM,YAAY,WAAW,OAAO,SAAS,OAAO;CAC1D,MAAM,eAAe,gBAAgB;CAErC,IAAI;EACF,OAAO,MAAM,MAAM,MAAM,YACvB,aAAa,UAAa,eAAe,aAAa,QAAQ,UAAU,GAAG,IAAI,IAAI,CACrF;CACF,SAAS,OAAO;EACd,IAAI,iBAAiB,uBACnB,MAAM,IAAI,mBAAmB,MAAM,OAAO;EAI5C,IAAI,iBAAiB,mBAAmB,MAAM,eAC5C,MAAM,IAAI,mBAAmB,MAAM,OAAO;EAG5C,MAAM;CACR,UAAU;EACR,MAAM,QAAQ;CAChB;AACF"}
|
package/esm/types.d.mts
CHANGED
|
@@ -36,6 +36,17 @@ export type JobOptions = {
|
|
|
36
36
|
removeOnComplete?: JobRetention;
|
|
37
37
|
/** Retention for failed jobs. Default keeps them, so `failedJobs()` can list them. */
|
|
38
38
|
removeOnFail?: JobRetention;
|
|
39
|
+
/**
|
|
40
|
+
* Longest one attempt may run: milliseconds or a string like `"2m"`. An
|
|
41
|
+
* attempt that exceeds it fails with `JobTimeoutError` and `ctx.signal`
|
|
42
|
+
* aborts. Must be greater than zero. Default: no limit.
|
|
43
|
+
*/
|
|
44
|
+
timeout?: Duration;
|
|
45
|
+
/**
|
|
46
|
+
* Fail the job for good when it times out instead of retrying it.
|
|
47
|
+
* Default `false`: a timeout is an ordinary failed attempt.
|
|
48
|
+
*/
|
|
49
|
+
failOnTimeout?: boolean;
|
|
39
50
|
};
|
|
40
51
|
/**
|
|
41
52
|
* In-process worker settings.
|
|
@@ -124,7 +135,11 @@ export type JobContext = {
|
|
|
124
135
|
log(line: string): Promise<void>;
|
|
125
136
|
/** State captured at dispatch by the registered queue context, if any. */
|
|
126
137
|
context: unknown;
|
|
127
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Aborted when the attempt times out and, on local/memory workers, when
|
|
140
|
+
* the worker loses its lease or shutdown times out. Present whenever the
|
|
141
|
+
* job has a `timeout`.
|
|
142
|
+
*/
|
|
128
143
|
signal?: AbortSignal;
|
|
129
144
|
};
|
|
130
145
|
/**
|
|
@@ -160,6 +175,10 @@ export type DispatchOptions = {
|
|
|
160
175
|
attempts?: number;
|
|
161
176
|
/** Override the job's backoff for this dispatch. */
|
|
162
177
|
backoff?: JobBackoff;
|
|
178
|
+
/** Override the job's timeout for this dispatch. */
|
|
179
|
+
timeout?: Duration;
|
|
180
|
+
/** Override the job's `failOnTimeout` for this dispatch. */
|
|
181
|
+
failOnTimeout?: boolean;
|
|
163
182
|
};
|
|
164
183
|
/** What `dispatch` resolves with. */
|
|
165
184
|
export type DispatchedJob = {
|
package/esm/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/types.ts"],"mappings":";;;;YAIY;;;;;YAMA,gCAAgC;;;;;YAMhC;;EAIN;;EAEA;;;;;;YAOM;;;;;YAMA;;EAEV;;EAEA,UAAU;;EAEV,mBAAmB;;EAEnB,eAAe;;;;;
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/types.ts"],"mappings":";;;;YAIY;;;;;YAMA,gCAAgC;;;;;YAMhC;;EAIN;;EAEA;;;;;;YAOM;;;;;YAMA;;EAEV;;EAEA,UAAU;;EAEV,mBAAmB;;EAEnB,eAAe;;;;;;EAMf,UAAU;;;;;EAKV;;;;;YAMU;;;;;EAKV;;EAEA;;;;;EAKA;;;;;;;YAQU;;EAEV;;EAEA;;;;;;EAMA,aAAa;;;YAIH;;EAEV;;EAEA;;EAEA;;;YAIU;;EAEV;;EAEA,QAAQ;;;;;EAKR,aAAa;;EAEb;;EAEA;;EAEA,oBAAoB;;EAEpB,UAAU;;EAEV,YAAY;;;YAIF,uBAAuB;;;;YAKvB;;EAEV;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,SAAS,OAAO,cAAc;;EAE9B,IAAI,eAAe;;EAEnB;;;;;;EAMA,SAAS;;;;;YAMC,cAAc,UAAU,WAAW;;EAE7C;;EAEA;;EAEA;;EAEA,OAAO,SAAS,UAAU,SAAS,aAAa,QAAQ,WAAW;;;;;YAMzD;;EAEV,QAAQ;;;;;EAKR;;;;;EAKA;;EAEA;;EAEA,UAAU;;EAEV,UAAU;;EAEV;;;YAIU;EACV;EACA;EACA;;;YAIU;;YAWA,YAAY,oBAAoB;EAC1C;EACA;EACA;EACA,OAAO;EACP,SAAS;EACT,UAAU;EACV;EACA,SAAS;EACT;EACA,WAAW;EACX,aAAa;;;YAIH,UAAU;EACpB;EACA;EACA;EACA,SAAS;EACT;EACA;EACA;EACA,WAAW;;EAEX,SAAS;;;;;YAMC,SAAS,UAAU;WACpB;WACA;;EAET,SAAS,SAAS,UAAU,UAAU,kBAAkB,QAAQ;;EAEhE,KAAK,aAAa,QAAQ,YAAY,UAAU"}
|
package/llms-full.txt
CHANGED
|
@@ -112,7 +112,7 @@ Every process must use the same `prefix`, or they will not see each other's jobs
|
|
|
112
112
|
|
|
113
113
|
---
|
|
114
114
|
name: define-jobs
|
|
115
|
-
description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `setDispatchInterceptor`, `queueFake`, `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
|
|
115
|
+
description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, timeout?, failOnTimeout?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff, timeout, failOnTimeout })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `setDispatchInterceptor`, `queueFake`, `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
|
|
116
116
|
---
|
|
117
117
|
|
|
118
118
|
# Define and dispatch jobs
|
|
@@ -150,6 +150,10 @@ await sendInvoice.dispatch(
|
|
|
150
150
|
);
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
+
## Timeouts
|
|
154
|
+
|
|
155
|
+
`timeout: "2m"` (ms or duration string, must be > 0) fails an attempt that runs too long with `JobTimeoutError`; `attempts`/`backoff` still apply. `failOnTimeout: true` fails the job without retrying. Works on all drivers; set in `defineJob`, dispatch options or `queue.defaultJobOptions`. `ctx.signal` aborts on timeout: pass it to `fetch`/db calls. A handler that ignores it keeps running in the background (JS cannot kill it).
|
|
156
|
+
|
|
153
157
|
Option precedence: dispatch options > `defineJob` > `queue.defaultJobOptions`.
|
|
154
158
|
|
|
155
159
|
## Read a job
|
package/llms.txt
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
- [Package orientation](@warlock.js/queue/skills/index.md): Background jobs with BullMQ/Redis, local SQLite or memory: `defineJob`, `queueConnector`, `startWorkers`, `closeQueue`, `failedJobs` and `mountQueueDashboard`. Use for "run in the background", "retry a job", "delay work", "survive restarts" or "view the job dashboard". Not this package → @warlock.js/scheduler for recurring schedules; @warlock.js/herald for pub/sub; @warlock.js/core for the in-memory `Queue` batcher.
|
|
10
10
|
- [configure-queue](@warlock.js/queue/skills/configure-queue/SKILL.md): Configure `@warlock.js/queue`: the declarative `src/config/queue.ts` (`QueueConfig` — `connection`, `prefix`, `defaultQueue`, `defaultJobOptions`, `workers: { enabled, concurrency, shutdownTimeout }`), registering `queueConnector()` in `warlock.config.ts > connectors`, running a dispatch-only process, and graceful shutdown. Programmatic `setQueueConfig` / `startWorkers` / `closeQueue` for scripts and tests. Triggers: `QueueConfig`, `queueConnector`, `setQueueConfig`, `startWorkers`, `closeQueue`, `workers.enabled`, `shutdownTimeout`; "configure the queue", "connect BullMQ to Redis", "disable workers in the web process", "graceful shutdown of jobs". Skip: writing jobs — the `define-jobs` topic.
|
|
11
|
-
- [define-jobs](@warlock.js/queue/skills/define-jobs/SKILL.md): Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `setDispatchInterceptor`, `queueFake`, `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.
|
|
11
|
+
- [define-jobs](@warlock.js/queue/skills/define-jobs/SKILL.md): Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, timeout?, failOnTimeout?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff, timeout, failOnTimeout })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `setDispatchInterceptor`, `queueFake`, `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.
|
|
12
12
|
- [local-queues](@warlock.js/queue/skills/local-queues/SKILL.md): Run @warlock.js/queue without Redis: driver: "local" for durable single-host SQLite jobs using node:sqlite on Node >= 22.15, or driver: "memory" for real handler execution in tests. Covers local.path, leases, crash recovery, polling, concurrency, cancellation, graceful shutdown, and at-least-once delivery. Use for local/desktop background jobs, testing workers without Redis, or configuring SQLite queues.
|
|
13
13
|
- [manage-failed-jobs](@warlock.js/queue/skills/manage-failed-jobs/SKILL.md): Inspect and retry failed `@warlock.js/queue` jobs: `failedJobs({ queue, start, end })` returns `FailedJob[]` (`id`, `name`, `payload`, `attemptsMade`, `failedReason`, `stacktrace`, `failedAt`, `retry()`), `retryFailedJob(id, { queue })` (throws `FailedJobNotFoundError`), and the bull-board UI — `warlock add bull-board` (requires: ["queue"], so it installs queue first when missing) writes a `dashboard: { enabled, path, middleware }` block to `src/config/queue.ts`; a guard middleware is required in production or boot throws `QueueDashboardUnguardedError`. Manual `queueDashboard(server, { basePath, queues, middleware })` also available — needs `@bull-board/api` + `@bull-board/fastify`, loaded only on call, missing ones throw `QueueDashboardDependencyError`. Triggers: `failedJobs`, `retryFailedJob`, `queueDashboard`, `bull-board`, `dashboard.enabled`, `QueueDashboardUnguardedError`; "list failed jobs", "retry a failed job", "queue dashboard", "job admin UI". Skip: defining retries — the `define-jobs` topic.
|
|
14
14
|
- [queue-notifications](@warlock.js/queue/skills/queue-notifications/SKILL.md): Send `@warlock.js/notifications` `.queue()` deliveries through BullMQ with `bullmqQueue({ queue?, attempts?, backoff? })` from `@warlock.js/notifications` — put it in the `queue` slot of `src/config/notifications.ts`; the queue workers deliver. Honours `SendOptions.delay` (number = seconds, or "10m"), retries a failing `channel.send`, and fails at once for a channel missing from the worker config. Triggers: `bullmqQueue`, `NotificationConfig.queue`, `QueuePackageNotInstalledError`; "queue notifications with BullMQ", "retry notification delivery", "delayed notification". Skip: the herald backend — the `queue-notifications` topic of the `warlock-js-notifications` skill.
|
package/package.json
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"peerDependencies": {
|
|
12
12
|
"@bull-board/api": "^9.0.0",
|
|
13
13
|
"@bull-board/fastify": "^9.0.0",
|
|
14
|
-
"@warlock.js/core": "5.
|
|
15
|
-
"@warlock.js/logger": "5.
|
|
14
|
+
"@warlock.js/core": "5.43.0",
|
|
15
|
+
"@warlock.js/logger": "5.43.0"
|
|
16
16
|
},
|
|
17
17
|
"peerDependenciesMeta": {
|
|
18
18
|
"@bull-board/api": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
],
|
|
39
39
|
"author": "hassanzohdy",
|
|
40
40
|
"license": "MIT",
|
|
41
|
-
"version": "5.
|
|
41
|
+
"version": "5.43.0",
|
|
42
42
|
"main": "./cjs/index.cjs",
|
|
43
43
|
"module": "./esm/index.mjs",
|
|
44
44
|
"types": "./esm/index.d.mts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: define-jobs
|
|
3
|
-
description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `setDispatchInterceptor`, `queueFake`, `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
|
|
3
|
+
description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, timeout?, failOnTimeout?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff, timeout, failOnTimeout })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `setDispatchInterceptor`, `queueFake`, `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Define and dispatch jobs
|
|
@@ -38,6 +38,10 @@ await sendInvoice.dispatch(
|
|
|
38
38
|
);
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## Timeouts
|
|
42
|
+
|
|
43
|
+
`timeout: "2m"` (ms or duration string, must be > 0) fails an attempt that runs too long with `JobTimeoutError`; `attempts`/`backoff` still apply. `failOnTimeout: true` fails the job without retrying. Works on all drivers; set in `defineJob`, dispatch options or `queue.defaultJobOptions`. `ctx.signal` aborts on timeout: pass it to `fetch`/db calls. A handler that ignores it keeps running in the background (JS cannot kill it).
|
|
44
|
+
|
|
41
45
|
Option precedence: dispatch options > `defineJob` > `queue.defaultJobOptions`.
|
|
42
46
|
|
|
43
47
|
## Read a job
|