@warlock.js/queue 5.42.0 → 5.44.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/cjs/index.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["UnrecoverableError","Queue","log","Worker","randomUUID","resolve","createRequire","dirname","log"],"sources":["../../../../../../../packages/queue/src/errors.ts","../../../../../../../packages/queue/src/config.ts","../../../../../../../packages/queue/src/dashboard-middleware-adapter.ts","../../../../../../../packages/queue/src/dashboard-guard-plugin.ts","../../../../../../../packages/queue/src/job-registry.ts","../../../../../../../packages/queue/src/queue-context.ts","../../../../../../../packages/queue/src/process-job.ts","../../../../../../../packages/queue/src/drivers/bullmq.ts","../../../../../../../packages/queue/src/drivers/local-store.ts","../../../../../../../packages/queue/src/drivers/local.ts","../../../../../../../packages/queue/src/drivers/memory-store.ts","../../../../../../../packages/queue/src/drivers/sqlite-store.ts","../../../../../../../packages/queue/src/queue-driver.ts","../../../../../../../packages/queue/src/queue-manager.ts","../../../../../../../packages/queue/src/dashboard.ts","../../../../../../../packages/queue/src/queue-dashboard-unguarded.error.ts","../../../../../../../packages/queue/src/dashboard-boot.ts","../../../../../../../packages/queue/src/dispatch-interceptor.ts","../../../../../../../packages/queue/src/duration.ts","../../../../../../../packages/queue/src/job-options.ts","../../../../../../../packages/queue/src/define-job.ts","../../../../../../../packages/queue/src/failed-jobs.ts","../../../../../../../packages/queue/src/queue-connector.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","import { QueueNotConfiguredError } from \"./errors\";\nimport type { QueueConfig } from \"./types\";\n\nlet activeConfig: QueueConfig | undefined;\n\n/**\n * Set the active queue configuration. In a Warlock app the queue connector\n * calls this at boot with `src/config/queue.ts`; scripts and tests may call\n * it directly. Replaces (does not merge) any previous configuration.\n */\nexport function setQueueConfig(config: QueueConfig): void {\n activeConfig = config;\n}\n\n/**\n * The active queue configuration. Throws {@link QueueNotConfiguredError}\n * when none was set.\n */\nexport function getQueueConfig(): QueueConfig {\n if (!activeConfig) {\n throw new QueueNotConfiguredError();\n }\n\n return activeConfig;\n}\n\n/** Forget the active configuration. */\nexport function resetQueueConfig(): void {\n activeConfig = undefined;\n}\n\n/** The queue a job runs on when it names none. */\nexport function defaultQueueName(): string {\n return activeConfig?.defaultQueue ?? \"default\";\n}\n","import type { Middleware } from \"@warlock.js/core\";\nimport type { FastifyReply, FastifyRequest } from \"fastify\";\n\n/**\n * Run a Warlock middleware list against a raw Fastify request/reply pair.\n *\n * Bull-board's `FastifyAdapter` hands back a plain Fastify plugin, not a\n * Warlock route — there is no `Route`, no validation pipeline, and none of\n * `createRequestStore`'s context wiring (CSP, tracing, `@warlock.js/context`\n * stores). Reusing that full pipeline here would pull the whole request\n * machinery into a place it was never meant to run. Instead this builds the\n * minimal `Request`/`Response` pair — enough for guard-style middleware\n * (`authMiddleware`, `ipFilterMiddleware`, a custom check) to read the\n * request and short-circuit with a response, which covers every one of\n * bull-board's routes because they all sit behind the same hook.\n *\n * `@warlock.js/core` is imported dynamically so this module never drags\n * core's runtime graph into a process that never mounts the dashboard.\n *\n * @returns `true` when a middleware sent a response and the caller must not\n * continue (bull-board's handler must not run); `false` to continue.\n */\nexport async function runDashboardMiddleware(\n middlewareList: Middleware[],\n fastifyRequest: FastifyRequest,\n fastifyReply: FastifyReply,\n): Promise<boolean> {\n if (middlewareList.length === 0) {\n return false;\n }\n\n const { Request, Response } = await import(\"@warlock.js/core\");\n\n const request = new Request();\n const response = new Response();\n\n response.setResponse(fastifyReply);\n request.response = response;\n response.request = request;\n request.setRequest(fastifyRequest);\n\n for (const middlewareFunction of middlewareList) {\n const result = await middlewareFunction({ request, response });\n\n if (result) {\n return true;\n }\n }\n\n return false;\n}\n","import type { Middleware } from \"@warlock.js/core\";\nimport type { FastifyInstance, FastifyPluginCallback } from \"fastify\";\nimport { runDashboardMiddleware } from \"./dashboard-middleware-adapter\";\n\n/**\n * Wrap bull-board's Fastify plugin in a scope that runs `middlewareList`\n * on every request before bull-board's own routes see it.\n *\n * Bull-board's `FastifyAdapter.registerPlugin()` returns a plain Fastify\n * plugin with no hook for Warlock middleware to run through, and Fastify\n * only lets an `onRequest` hook be added to a plugin scope — never spliced\n * into a plugin someone else wrote. So this builds ONE plugin that adds the\n * hook to its own scope and then registers bull-board's plugin as a child of\n * that scope; Fastify's encapsulation runs the hook for every route the\n * child registers, which is every dashboard route.\n */\nexport function buildDashboardGuardPlugin(\n middlewareList: Middleware[],\n bullBoardPlugin: unknown,\n): FastifyPluginCallback {\n return function dashboardGuardPlugin(instance: FastifyInstance, _options, done) {\n if (middlewareList.length > 0) {\n instance.addHook(\"onRequest\", async (request, reply) => {\n const handled = await runDashboardMiddleware(middlewareList, request, reply);\n\n if (handled) {\n // Returning the reply is Fastify's own way for an `onRequest` hook to\n // END the lifecycle, so bull-board's handler never runs. Relying\n // instead on Fastify noticing the reply was already sent leaves the\n // outcome to write ORDERING: a guard answering asynchronously can lose\n // that race and let the dashboard render to an unauthenticated caller.\n // The adapter returns this boolean precisely so the decision is\n // explicit rather than emergent.\n return reply;\n }\n });\n }\n\n instance.register(bullBoardPlugin as never);\n done();\n };\n}\n","import { defaultQueueName } from \"./config\";\nimport type { JobDefinition } from \"./types\";\n\n/** A registered definition, payload/result erased for storage. */\nexport type RegisteredJob = JobDefinition<unknown, unknown>;\n\ntype RegistryListener = (job: RegisteredJob) => void;\n\nconst jobs = new Map<string, RegisteredJob>();\nconst listeners = new Set<RegistryListener>();\n\n/**\n * Register a definition under its name.\n *\n * Re-registering a name REPLACES the previous definition: in development a\n * job module is re-evaluated on every reload, and refusing the second\n * evaluation would break the reload. Job names must therefore be unique\n * across the app — two different modules using one name leave only the\n * later handler active.\n */\nexport function registerJob(job: RegisteredJob): void {\n jobs.set(job.name, job);\n\n for (const listener of listeners) {\n listener(job);\n }\n}\n\n/** The definition registered under `name`, if any. */\nexport function findRegisteredJob(name: string): RegisteredJob | undefined {\n return jobs.get(name);\n}\n\n/** Every registered definition. */\nexport function registeredJobs(): RegisteredJob[] {\n return [...jobs.values()];\n}\n\n/** The queue a definition runs on, resolved against the active config. */\nexport function queueOf(job: Pick<RegisteredJob, \"queue\">): string {\n return job.queue ?? defaultQueueName();\n}\n\n/** Be told whenever a job is registered. Returns an unsubscribe function. */\nexport function onJobRegistered(listener: RegistryListener): () => void {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n}\n","/**\n * Carries ambient state (tenant, request id, …) from the code that\n * dispatches a job into the worker that runs it.\n */\nexport type QueueContext<T> = {\n /** Read the ambient state at dispatch time; `undefined` means nothing to carry. */\n capture: () => T | undefined;\n /** Run `run` with the captured state re-established. */\n restore: (captured: T, run: () => Promise<unknown>) => Promise<unknown>;\n};\n\n/**\n * Define a queue context. `restore`'s `captured` is inferred from `capture`.\n *\n * @example\n * setQueueContext(\n * defineQueueContext({\n * capture: () => tenantStorage.getStore(),\n * restore: (tenant, run) => tenantStorage.run(tenant, run),\n * }),\n * );\n */\nexport function defineQueueContext<T>(context: QueueContext<T>): QueueContext<T> {\n return context;\n}\n\nlet activeContext: QueueContext<any> | undefined;\n\n/** Register the queue context used by every job dispatch and run. */\nexport function setQueueContext(context: QueueContext<any>): void {\n activeContext = context;\n}\n\n/** The registered queue context, if any. */\nexport function getQueueContext(): QueueContext<any> | undefined {\n return activeContext;\n}\n\n/** Forget the registered queue context. */\nexport function resetQueueContext(): void {\n activeContext = undefined;\n}\n\n/** The job-data envelope written when a context was captured. */\nexport type QueueEnvelope = {\n __warlock: { v: 1; context?: unknown };\n payload: unknown;\n};\n\n/** Wrap a payload with the captured context; no context leaves it bare. */\nexport function wrapPayload(payload: unknown, context: unknown): unknown {\n if (context === undefined) return payload;\n\n return { __warlock: { v: 1, context }, payload } satisfies QueueEnvelope;\n}\n\n/** Split job data into payload and captured context; bare data passes through. */\nexport function unwrapPayload(data: unknown): { payload: unknown; context: unknown } {\n const envelope = data as Partial<QueueEnvelope> | null;\n\n if (\n envelope !== null &&\n typeof envelope === \"object\" &&\n typeof envelope.__warlock === \"object\" &&\n envelope.__warlock !== null &&\n envelope.__warlock.v === 1 &&\n \"payload\" in envelope\n ) {\n return { payload: envelope.payload, context: envelope.__warlock.context };\n }\n\n return { payload: data, context: undefined };\n}\n","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\n ? await queueContext.restore(captured, run)\n : await run();\n } catch (error) {\n if (error instanceof UnrecoverableJobError) {\n throw new UnrecoverableError(error.message);\n }\n\n throw error;\n }\n}\n","import { log } from \"@warlock.js/logger\";\nimport { Queue, Worker, type Job, type JobsOptions } from \"bullmq\";\nimport type { DriverDispatch, QueueDriver } from \"../driver\";\nimport { FailedJobNotFoundError } from \"../errors\";\nimport { processJob } from \"../process-job\";\nimport { unwrapPayload } from \"../queue-context\";\nimport type { DispatchedJob, FailedJob, JobSnapshot, JobState, QueueConfig } from \"../types\";\n\nexport class BullMQDriver implements QueueDriver {\n private queues = new Map<string, Queue>();\n private workers = new Map<string, Worker>();\n\n public constructor(private config: QueueConfig) {\n if (!config.connection) throw new Error(\"The bullmq queue driver requires `connection`.\");\n }\n\n public getQueue(name: string): Queue {\n let queue = this.queues.get(name);\n if (!queue) {\n const connection = this.config.connection;\n if (!connection) throw new Error(\"The bullmq queue driver requires `connection`.\");\n queue = new Queue(name, { connection, prefix: this.config.prefix ?? \"warlock\" });\n queue.on(\"error\", (error) => log.error(\"queue\", \"connection\", error));\n this.queues.set(name, queue);\n }\n return queue;\n }\n\n public async dispatch(request: DriverDispatch): Promise<DispatchedJob> {\n const { backoff, ...rest } = request.options;\n const options: JobsOptions = {\n ...rest,\n backoff: typeof backoff === \"number\" ? { type: \"fixed\", delay: backoff } : backoff,\n };\n const job = await this.getQueue(request.queue).add(request.name, request.data, options);\n return { id: String(job.id), name: request.name, queue: request.queue };\n }\n\n public async find(queue: string, id: string): Promise<JobSnapshot | undefined> {\n const source = this.getQueue(queue);\n const initial = await source.getJob(id);\n if (!initial) return undefined;\n // Fetch fields after reading state so completion fields match the state.\n const state = (await initial.getState()) as JobState;\n return toSnapshot((await source.getJob(id)) ?? initial, state);\n }\n\n public async failed(queue: string, start: number, end: number): Promise<FailedJob[]> {\n return (await this.getQueue(queue).getFailed(start, end)).map((job) => ({\n id: String(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.finishedOn ? new Date(job.finishedOn) : undefined,\n retry: () => job.retry(\"failed\"),\n }));\n }\n\n public async retry(queue: string, id: string): Promise<void> {\n const job = await this.getQueue(queue).getJob(id);\n if (!job || !(await job.isFailed())) throw new FailedJobNotFoundError(id, queue);\n await job.retry(\"failed\");\n }\n\n public async start(queue: string): Promise<void> {\n let worker = this.workers.get(queue);\n if (!worker) {\n const connection = this.config.connection;\n if (!connection) throw new Error(\"The bullmq queue driver requires `connection`.\");\n worker = new Worker(queue, processJob, {\n connection,\n prefix: this.config.prefix ?? \"warlock\",\n concurrency: this.config.workers?.concurrency ?? 1,\n });\n worker.on(\"error\", (error) => log.error(\"queue\", \"worker\", error));\n worker.on(\"failed\", (job, error) =>\n log.error(\n \"queue\",\n \"job.failed\",\n `${job?.name ?? \"unknown\"} (${job?.id ?? \"?\"}): ${error.message}`,\n ),\n );\n this.workers.set(queue, worker);\n }\n await worker.waitUntilReady();\n }\n\n public running(): string[] {\n return [...this.workers.keys()];\n }\n\n public async close(timeout: number): Promise<void> {\n const workers = [...this.workers.values()];\n this.workers.clear();\n await Promise.all(workers.map((worker) => closeWorker(worker, timeout)));\n const queues = [...this.queues.values()];\n this.queues.clear();\n await Promise.all(queues.map((queue) => queue.close()));\n }\n}\n\nfunction toSnapshot(job: Job, state: JobState): JobSnapshot {\n return {\n id: String(job.id),\n name: job.name,\n queue: job.queueName,\n state,\n payload: unwrapPayload(job.data).payload,\n progress: job.progress as JobSnapshot[\"progress\"],\n attemptsMade: job.attemptsMade,\n result: job.returnvalue as unknown,\n failedReason: job.failedReason || undefined,\n createdAt: new Date(job.timestamp),\n finishedAt: job.finishedOn ? new Date(job.finishedOn) : undefined,\n };\n}\n\nasync function closeWorker(worker: Worker, timeout: number): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n const timedOut = new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), timeout);\n });\n const closing = worker.close().then(() => \"closed\" as const);\n closing.catch(() => undefined);\n try {\n if ((await Promise.race([closing, timedOut])) === \"timeout\") {\n log.warn(\n \"queue\",\n \"shutdown\",\n `Worker for \"${worker.name}\" still had active jobs after ${timeout}ms; disconnecting. ` +\n \"Those jobs are retried once their lock expires.\",\n );\n await worker.disconnect();\n }\n } finally {\n clearTimeout(timer);\n }\n}\n","import type { ResolvedJobOptions } from \"../job-options\";\nimport type { JobProgress, JobState } from \"../types\";\n\nexport type LocalJob = {\n id: string;\n queue: string;\n name: string;\n data: unknown;\n options: ResolvedJobOptions;\n state: JobState;\n createdAt: number;\n readyAt: number;\n attemptsMade: number;\n priority: number;\n progress: JobProgress;\n logs: string[];\n stacktrace: string[];\n failedReason?: string;\n result?: unknown;\n finishedAt?: number;\n token?: string;\n leaseUntil: number;\n};\n\n/** Synchronous transactions contain no user code or await points. */\nexport interface LocalStore {\n transaction<T>(run: () => T): T;\n get(queue: string, id: string): LocalJob | undefined;\n list(queue: string, state?: JobState): LocalJob[];\n /** Earliest eligible job, including active jobs with an expired lease. */\n ready(queue: string, now: number): LocalJob | undefined;\n put(job: LocalJob): void;\n remove(queue: string, id: string): void;\n close(): void;\n}\n\n/** Match JSON transport on every backend, without sharing mutable objects. */\nexport function jsonCopy<T>(value: T): T {\n return (JSON.parse(JSON.stringify({ value })) as { value: T }).value;\n}\n","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","import type { JobState } from \"../types\";\nimport { jsonCopy, type LocalJob, type LocalStore } from \"./local-store\";\n\nexport class MemoryStore implements LocalStore {\n private jobs = new Map<string, LocalJob>();\n public transaction<T>(run: () => T): T {\n const before = new Map(this.jobs);\n try {\n return run();\n } catch (error) {\n this.jobs = before;\n throw error;\n }\n }\n private key(queue: string, id: string): string {\n return JSON.stringify([queue, id]);\n }\n public get(queue: string, id: string): LocalJob | undefined {\n return jsonCopy(this.jobs.get(this.key(queue, id)));\n }\n public list(queue: string, state?: JobState): LocalJob[] {\n return [...this.jobs.values()]\n .filter((job) => job.queue === queue && (!state || job.state === state))\n .map(jsonCopy);\n }\n public ready(queue: string, now: number): LocalJob | undefined {\n let candidate: LocalJob | undefined;\n for (const job of this.list(queue)) {\n const eligible =\n (job.state === \"active\" && job.leaseUntil <= now) ||\n ([\"waiting\", \"delayed\", \"prioritized\"].includes(job.state) && job.readyAt <= now);\n if (eligible && (!candidate || job.priority < candidate.priority)) candidate = job;\n }\n return candidate;\n }\n public put(job: LocalJob): void {\n this.jobs.set(this.key(job.queue, job.id), jsonCopy(job));\n }\n public remove(queue: string, id: string): void {\n this.jobs.delete(this.key(queue, id));\n }\n public close(): void {\n this.jobs.clear();\n }\n}\n","import { mkdirSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, resolve } from \"node:path\";\nimport type { JobState, QueueConfig } from \"../types\";\nimport type { LocalJob, LocalStore } from \"./local-store\";\n\n/**\n * The part of `node:sqlite` this store uses. Declared here so apps type-check the\n * shipped source without depending on a particular `@types/node` release.\n * https://nodejs.org/docs/latest-v22.x/api/sqlite.html\n */\ntype SqliteValue = null | number | bigint | string | Uint8Array;\ntype SqliteRow = Record<string, unknown>;\ntype SqliteStatement = {\n all(...parameters: SqliteValue[]): SqliteRow[];\n get(...parameters: SqliteValue[]): SqliteRow | undefined;\n run(...parameters: SqliteValue[]): unknown;\n};\ntype DatabaseSync = {\n exec(sql: string): void;\n prepare(sql: string): SqliteStatement;\n close(): void;\n};\ntype SqliteModule = { DatabaseSync: new (path: string) => DatabaseSync };\n\n/** node:sqlite is loaded only when the local backend is selected. */\nexport class SqliteStore implements LocalStore {\n private db: DatabaseSync;\n private namespace: string;\n\n public constructor(config: QueueConfig) {\n const path = resolve(config.local?.path ?? \".warlock/queue.sqlite\");\n if (config.local?.path === \":memory:\")\n throw new Error(\"Use driver: 'memory' for non-durable queues.\");\n let sqlite: SqliteModule;\n try {\n sqlite = createRequire(import.meta.url)(\"node:sqlite\") as SqliteModule;\n } catch (cause) {\n throw new Error(\n \"The local queue driver requires Node >= 22.15 with node:sqlite available without flags.\",\n { cause },\n );\n }\n if (typeof sqlite.DatabaseSync !== \"function\")\n throw new Error(\"node:sqlite.DatabaseSync is unavailable.\");\n mkdirSync(dirname(path), { recursive: true });\n this.db = new sqlite.DatabaseSync(path);\n this.namespace = config.prefix ?? \"warlock\";\n try {\n // Use the v22.15 API; constructor timeout options were introduced later.\n this.db.exec(`\n PRAGMA busy_timeout = 5000;\n PRAGMA journal_mode = WAL;\n PRAGMA synchronous = FULL;\n CREATE TABLE IF NOT EXISTS warlock_queue_jobs (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n namespace TEXT NOT NULL, queue TEXT NOT NULL, id TEXT NOT NULL,\n state TEXT NOT NULL, ready_at REAL NOT NULL, lease_until REAL NOT NULL,\n priority INTEGER NOT NULL, body TEXT NOT NULL,\n UNIQUE(namespace, queue, id)\n );\n CREATE INDEX IF NOT EXISTS warlock_queue_ready\n ON warlock_queue_jobs(namespace, queue, state, ready_at, lease_until);\n `);\n } catch (error) {\n this.db.close();\n throw error;\n }\n }\n\n public transaction<T>(run: () => T): T {\n this.db.exec(\"BEGIN IMMEDIATE\");\n try {\n const result = run();\n this.db.exec(\"COMMIT\");\n return result;\n } catch (error) {\n this.db.exec(\"ROLLBACK\");\n throw error;\n }\n }\n\n public get(queue: string, id: string): LocalJob | undefined {\n return decode(\n this.db\n .prepare(\"SELECT body FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? AND id = ?\")\n .get(this.namespace, queue, id),\n );\n }\n public list(queue: string, state?: JobState): LocalJob[] {\n const rows = state\n ? this.db\n .prepare(\n \"SELECT body FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? AND state = ? ORDER BY seq\",\n )\n .all(this.namespace, queue, state)\n : this.db\n .prepare(\n \"SELECT body FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? ORDER BY seq\",\n )\n .all(this.namespace, queue);\n return rows.flatMap((row) => {\n const job = decode(row);\n return job ? [job] : [];\n });\n }\n public ready(queue: string, now: number): LocalJob | undefined {\n return decode(\n this.db\n .prepare(`SELECT body FROM warlock_queue_jobs\n WHERE namespace = ? AND queue = ? AND (\n (state IN ('waiting', 'delayed', 'prioritized') AND ready_at <= ?) OR\n (state = 'active' AND lease_until <= ?)\n ) ORDER BY priority, seq LIMIT 1`)\n .get(this.namespace, queue, now, now),\n );\n }\n public put(job: LocalJob): void {\n this.db\n .prepare(`INSERT INTO warlock_queue_jobs\n (namespace, queue, id, state, ready_at, lease_until, priority, body)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(namespace, queue, id) DO UPDATE SET\n state = excluded.state, ready_at = excluded.ready_at,\n lease_until = excluded.lease_until, priority = excluded.priority, body = excluded.body`)\n .run(\n this.namespace,\n job.queue,\n job.id,\n job.state,\n job.readyAt,\n job.leaseUntil,\n job.priority,\n JSON.stringify(job),\n );\n }\n public remove(queue: string, id: string): void {\n this.db\n .prepare(\"DELETE FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? AND id = ?\")\n .run(this.namespace, queue, id);\n }\n public close(): void {\n this.db.close();\n }\n}\n\nfunction decode(row: Record<string, unknown> | undefined): LocalJob | undefined {\n return typeof row?.body === \"string\" ? (JSON.parse(row.body) as LocalJob) : undefined;\n}\n","import { getQueueConfig } from \"./config\";\nimport type { QueueDriver } from \"./driver\";\nimport { BullMQDriver } from \"./drivers/bullmq\";\nimport { LocalDriver } from \"./drivers/local\";\nimport { MemoryStore } from \"./drivers/memory-store\";\nimport { SqliteStore } from \"./drivers/sqlite-store\";\n\nlet activeDriver: QueueDriver | undefined;\n\n/** The configured backend, created on first use. Close it before changing config. */\nexport function getQueueDriver(): QueueDriver {\n if (!activeDriver) {\n const config = getQueueConfig();\n switch (config.driver ?? \"bullmq\") {\n case \"bullmq\":\n activeDriver = new BullMQDriver(config);\n break;\n case \"local\":\n activeDriver = new LocalDriver(new SqliteStore(config), config);\n break;\n case \"memory\":\n activeDriver = new LocalDriver(new MemoryStore(), config);\n break;\n default:\n throw new Error(`Unknown queue driver: ${String(config.driver)}`);\n }\n }\n return activeDriver;\n}\n\nexport function currentQueueDriver(): QueueDriver | undefined {\n return activeDriver;\n}\n\nexport function forgetQueueDriver(): void {\n activeDriver = undefined;\n}\n","import { log } from \"@warlock.js/logger\";\nimport type { Queue } from \"bullmq\";\nimport { getQueueConfig } from \"./config\";\nimport { BullMQDriver } from \"./drivers/bullmq\";\nimport { onJobRegistered, queueOf, registeredJobs } from \"./job-registry\";\nimport { currentQueueDriver, forgetQueueDriver, getQueueDriver } from \"./queue-driver\";\n\nlet stopListening: (() => void) | undefined;\nlet closing: Promise<void> | undefined;\n\n/** Low-level BullMQ access. For portable operations use defineJob / failedJobs. */\nexport function getQueue(name: string): Queue {\n const driver = getQueueDriver();\n if (!(driver instanceof BullMQDriver))\n throw new Error(\"getQueue() requires the bullmq queue driver.\");\n return driver.getQueue(name);\n}\n\n/** Start workers for registered queues, honouring process roles and workers.enabled. */\nexport async function startWorkers(): Promise<string[]> {\n if (closing) await closing;\n const { Application } = await import(\"@warlock.js/core\");\n if (!Application.hasRole(\"worker\")) {\n log.info(\n \"queue\",\n \"workers\",\n `queue: workers not started (role: ${[...Application.roles].join(\",\")})`,\n );\n return [];\n }\n if (getQueueConfig().workers?.enabled === false) return [];\n const driver = getQueueDriver();\n stopListening ??= onJobRegistered((job) => {\n void driver.start(queueOf(job)).catch((error: unknown) => log.error(\"queue\", \"worker\", error));\n });\n await Promise.all(\n [...new Set(registeredJobs().map(queueOf))].map((queue) => driver.start(queue)),\n );\n return driver.running();\n}\n\n/** Names of queues with workers in this process. */\nexport function runningWorkers(): string[] {\n return currentQueueDriver()?.running() ?? [];\n}\n\nexport type CloseQueueOptions = {\n /** Drain timeout in ms, default workers.shutdownTimeout, else 30000. */\n timeout?: number;\n};\n\n/** Stop taking jobs, drain active handlers, then release leases and close storage. */\nexport function closeQueue(options: CloseQueueOptions = {}): Promise<void> {\n if (closing) return closing;\n stopListening?.();\n stopListening = undefined;\n const driver = currentQueueDriver();\n if (!driver) return Promise.resolve();\n const timeout = options.timeout ?? configuredShutdownTimeout();\n if (!Number.isFinite(timeout) || timeout < 0)\n return Promise.reject(new Error(\"Queue shutdown timeout must be non-negative and finite.\"));\n closing = driver.close(timeout).finally(() => {\n forgetQueueDriver();\n closing = undefined;\n });\n return closing;\n}\n\nfunction configuredShutdownTimeout(): number {\n try {\n return getQueueConfig().workers?.shutdownTimeout ?? 30_000;\n } catch {\n return 30_000;\n }\n}\n","import type { Middleware } from \"@warlock.js/core\";\nimport { defaultQueueName, getQueueConfig } from \"./config\";\nimport { buildDashboardGuardPlugin } from \"./dashboard-guard-plugin\";\nimport { QueueDashboardDependencyError } from \"./errors\";\nimport { queueOf, registeredJobs } from \"./job-registry\";\nimport { getQueue } from \"./queue-manager\";\n\n/**\n * The part of a Fastify instance the dashboard needs. Warlock's HTTP server\n * (`getHttpServer()` from `@warlock.js/core`) satisfies it.\n */\nexport type DashboardServer = {\n register(plugin: never, options: { prefix: string }): unknown;\n};\n\nexport type QueueDashboardOptions = {\n /** URL prefix the dashboard is mounted on. Default `\"/admin/queues\"`. */\n basePath?: string;\n /** Queues to show. Default: every queue with a defined job, plus the default queue. */\n queues?: string[];\n /**\n * Run before every dashboard route. Applied via a wrapping Fastify plugin\n * scope, since bull-board's own plugin has no hook to splice Warlock\n * middleware into — see `dashboard-guard-plugin.ts`.\n */\n middleware?: Middleware[];\n};\n\ntype BullBoardModules = {\n createBullBoard: (options: { queues: unknown[]; serverAdapter: unknown }) => unknown;\n BullMQAdapter: new (queue: unknown) => unknown;\n FastifyAdapter: new () => {\n setBasePath(path: string): unknown;\n registerPlugin(): unknown;\n };\n};\n\n/**\n * Mount the bull-board failed/active/delayed job UI on Warlock's Fastify\n * server. Requires the optional packages `@bull-board/api` and\n * `@bull-board/fastify`; they are loaded only when this is called, and a\n * missing one throws {@link QueueDashboardDependencyError}.\n *\n * Call it before the HTTP server starts listening, and put it behind your\n * own authentication — the dashboard can retry and delete jobs.\n *\n * @example\n * import { getHttpServer } from \"@warlock.js/core\";\n * await queueDashboard(getHttpServer(), { basePath: \"/admin/queues\" });\n */\nexport async function queueDashboard(\n server: DashboardServer,\n options: QueueDashboardOptions = {},\n): Promise<void> {\n if ((getQueueConfig().driver ?? \"bullmq\") !== \"bullmq\") {\n throw new Error(\n \"The bull-board dashboard requires the bullmq queue driver. Use job.find() and failedJobs() with local or memory queues.\",\n );\n }\n const basePath = options.basePath ?? \"/admin/queues\";\n const { createBullBoard, BullMQAdapter, FastifyAdapter } = await loadBullBoard();\n const queueNames = options.queues ?? [\n ...new Set([defaultQueueName(), ...registeredJobs().map((job) => queueOf(job))]),\n ];\n\n const serverAdapter = new FastifyAdapter();\n serverAdapter.setBasePath(basePath);\n\n createBullBoard({\n queues: queueNames.map((name) => new BullMQAdapter(getQueue(name))),\n serverAdapter,\n });\n\n const guardedPlugin = buildDashboardGuardPlugin(\n options.middleware ?? [],\n serverAdapter.registerPlugin(),\n );\n\n await server.register(guardedPlugin as never, { prefix: basePath });\n}\n\n/**\n * Load the optional bull-board packages. Exported for tests of the missing\n * dependency path; `importer` defaults to a real dynamic import.\n */\nexport async function loadBullBoard(\n importer: (specifier: string) => Promise<Record<string, unknown>> = (specifier) =>\n import(specifier),\n): Promise<BullBoardModules> {\n const api = await importOptional(importer, \"@bull-board/api\");\n const adapter = await importOptional(importer, \"@bull-board/api/bullMQAdapter\");\n const fastify = await importOptional(importer, \"@bull-board/fastify\");\n\n return {\n createBullBoard: api.createBullBoard as BullBoardModules[\"createBullBoard\"],\n BullMQAdapter: adapter.BullMQAdapter as BullBoardModules[\"BullMQAdapter\"],\n FastifyAdapter: fastify.FastifyAdapter as BullBoardModules[\"FastifyAdapter\"],\n };\n}\n\nasync function importOptional(\n importer: (specifier: string) => Promise<Record<string, unknown>>,\n specifier: string,\n): Promise<Record<string, unknown>> {\n try {\n return await importer(specifier);\n } catch (error) {\n if (isModuleNotFound(error)) {\n throw new QueueDashboardDependencyError(specifier.split(\"/\").slice(0, 2).join(\"/\"));\n }\n\n throw error;\n }\n}\n\nfunction isModuleNotFound(error: unknown): boolean {\n const code = (error as { code?: unknown } | undefined)?.code;\n\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n","/**\n * Thrown at boot when `queue.dashboard.enabled` is `true` in production with\n * no guard middleware. The dashboard can retry and delete jobs; mounting it\n * on the open internet without a guard is a production incident waiting to\n * happen, so this fails the boot instead of shipping the hole.\n */\nexport class QueueDashboardUnguardedError extends Error {\n public constructor() {\n super(\n \"queue.dashboard.enabled is true in production with no middleware. The dashboard can \" +\n \"retry and delete jobs, so it must be guarded before it is exposed.\\n\\n\" +\n \"Add a guard middleware:\\n\\n\" +\n ' import { middleware } from \"@warlock.js/core\";\\n' +\n ' import { authMiddleware } from \"@warlock.js/auth\";\\n\\n' +\n \" const queueConfig: QueueConfig = {\\n\" +\n \" // ...\\n\" +\n \" dashboard: {\\n\" +\n \" enabled: true,\\n\" +\n ' middleware: [authMiddleware(\"admin\")],\\n' +\n \" },\\n\" +\n \" };\\n\",\n );\n this.name = \"QueueDashboardUnguardedError\";\n }\n}\n","import { log } from \"@warlock.js/logger\";\nimport { type DashboardServer, queueDashboard } from \"./dashboard\";\nimport { QueueDashboardUnguardedError } from \"./queue-dashboard-unguarded.error\";\nimport type { QueueConfig } from \"./types\";\n\n/** Default path the dashboard mounts on when `dashboard.path` is not set. */\nexport const DEFAULT_DASHBOARD_PATH = \"/admin/queues\";\n\n/**\n * Mount the bull-board dashboard for `queue.dashboard` in `config`, applying\n * the safety rule: `enabled` in production with no middleware throws\n * {@link QueueDashboardUnguardedError} instead of booting exposed; outside\n * production with no middleware it logs one warning and mounts anyway.\n *\n * Called by `queueConnector()` at boot, once the HTTP server exists but\n * before it starts listening — see `queue-connector.ts`. Exported so it can\n * be unit-tested without going through the whole connector lifecycle.\n */\nexport async function mountQueueDashboard(\n server: DashboardServer | undefined,\n config: QueueConfig,\n): Promise<void> {\n const dashboard = config.dashboard;\n\n if (!dashboard?.enabled) {\n return;\n }\n\n if ((config.driver ?? \"bullmq\") !== \"bullmq\") {\n throw new Error(\"The bull-board dashboard requires the bullmq queue driver.\");\n }\n\n const middlewareList = dashboard.middleware ?? [];\n\n if (middlewareList.length === 0) {\n if (process.env.NODE_ENV === \"production\") {\n throw new QueueDashboardUnguardedError();\n }\n\n log.warn(\n \"queue\",\n \"dashboard\",\n \"queue.dashboard.enabled is true with no middleware. The dashboard can retry and delete \" +\n \"jobs — add a guard middleware before this ships to production.\",\n );\n }\n\n if (!server) {\n log.warn(\n \"queue\",\n \"dashboard\",\n \"queue.dashboard.enabled is true but no HTTP server was found; the dashboard was not mounted.\",\n );\n\n return;\n }\n\n await queueDashboard(server, {\n basePath: dashboard.path ?? DEFAULT_DASHBOARD_PATH,\n middleware: middlewareList,\n });\n}\n","import type { DispatchOptions, DispatchedJob, JobSnapshot } from \"./types\";\n\n/**\n * A stand-in for the broker, used by test fakes.\n *\n * When one is installed, `job.dispatch()` hands the job to it instead of\n * adding it to BullMQ, and `job.find()` asks it instead of Redis. Nothing\n * connects to Redis while it is installed.\n *\n * `@warlock.js/core/tests` installs one as `queueFake` in the test\n * environment; this package exposes the seam so core needs no dependency on\n * it. The slot lives on `globalThis` so every copy of this package in a\n * process (a Vitest worker can load two) sees the same one.\n */\nexport type DispatchInterceptor = {\n dispatch(job: {\n name: string;\n queue: string;\n payload: unknown;\n options: DispatchOptions;\n }): DispatchedJob | Promise<DispatchedJob>;\n find?(job: {\n name: string;\n queue: string;\n id: string;\n }): JobSnapshot | undefined | Promise<JobSnapshot | undefined>;\n};\n\nconst INTERCEPTOR_KEY = Symbol.for(\"warlock.queue.dispatchInterceptor\");\n\ntype InterceptorHolder = { [INTERCEPTOR_KEY]?: DispatchInterceptor };\n\n/** Install an interceptor, or remove it with `undefined`. */\nexport function setDispatchInterceptor(interceptor: DispatchInterceptor | undefined): void {\n (globalThis as InterceptorHolder)[INTERCEPTOR_KEY] = interceptor;\n}\n\n/** The installed interceptor, if any. */\nexport function getDispatchInterceptor(): DispatchInterceptor | undefined {\n return (globalThis as InterceptorHolder)[INTERCEPTOR_KEY];\n}\n","import { InvalidDurationError } from \"./errors\";\nimport type { Duration } from \"./types\";\n\nconst UNIT_MILLISECONDS: Record<string, number> = {\n ms: 1,\n s: 1_000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n};\n\nconst DURATION_PATTERN = /^(\\d+(?:\\.\\d+)?)(ms|s|m|h|d)$/;\n\n/**\n * Convert a {@link Duration} to milliseconds. Numbers are already\n * milliseconds. Anything else is rejected loudly rather than guessed at.\n */\nexport function toMilliseconds(value: Duration | string): number {\n if (typeof value === \"number\") {\n if (!Number.isFinite(value) || value < 0) {\n throw new InvalidDurationError(value);\n }\n\n return Math.round(value);\n }\n\n const match = typeof value === \"string\" ? DURATION_PATTERN.exec(value.trim()) : null;\n\n if (!match) {\n throw new InvalidDurationError(value);\n }\n\n return Math.round(Number(match[1]) * UNIT_MILLISECONDS[match[2]!]!);\n}\n","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","import { getDispatchInterceptor } from \"./dispatch-interceptor\";\nimport { InvalidJobDefinitionError } from \"./errors\";\nimport { getQueueContext, wrapPayload } from \"./queue-context\";\nimport { queueOf, registerJob, type RegisteredJob } from \"./job-registry\";\nimport { getQueueDriver } from \"./queue-driver\";\nimport { resolveJobOptions } from \"./job-options\";\nimport type { JobDefinition, JobSnapshot, QueueJob } from \"./types\";\n\n/**\n * Define a background job.\n *\n * The definition is registered by name so any worker in the process can run\n * it; the returned object dispatches it with a typed payload.\n *\n * @example\n * export const sendInvoice = defineJob({\n * name: \"invoices.send\",\n * attempts: 5,\n * backoff: { type: \"exponential\", delay: 2000 },\n * async handle(payload: { invoiceId: string }, ctx) {\n * await ctx.progress(50);\n * },\n * });\n *\n * await sendInvoice.dispatch({ invoiceId: \"42\" }, { delay: \"10m\", priority: 1 });\n */\nexport function defineJob<TPayload, TResult = unknown>(\n definition: JobDefinition<TPayload, TResult>,\n): QueueJob<TPayload, TResult> {\n assertValidDefinition(definition);\n registerJob(definition as RegisteredJob);\n\n return {\n name: definition.name,\n get queue() {\n return queueOf(definition);\n },\n async dispatch(payload, options = {}) {\n const queueName = queueOf(definition);\n const interceptor = getDispatchInterceptor();\n\n // A test fake stands in for the broker: record, never touch Redis.\n if (interceptor) {\n return interceptor.dispatch({ name: definition.name, queue: queueName, payload, options });\n }\n\n // Capture synchronously, in the caller's async context.\n const captured = definition.context === false ? undefined : getQueueContext()?.capture();\n return getQueueDriver().dispatch({\n name: definition.name,\n queue: queueName,\n data: wrapPayload(payload, captured),\n options: resolveJobOptions(definition, options),\n });\n },\n async find(id) {\n const interceptor = getDispatchInterceptor();\n\n if (interceptor) {\n const found = await interceptor.find?.({\n name: definition.name,\n queue: queueOf(definition),\n id,\n });\n\n return found as JobSnapshot<TPayload, TResult> | undefined;\n }\n\n const snapshot = await getQueueDriver().find(queueOf(definition), id);\n return snapshot?.name === definition.name\n ? (snapshot as JobSnapshot<TPayload, TResult>)\n : undefined;\n },\n };\n}\n\nfunction assertValidDefinition(definition: JobDefinition<unknown, unknown>): void {\n if (typeof definition.name !== \"string\" || definition.name.trim() === \"\") {\n throw new InvalidJobDefinitionError(\"defineJob() requires a non-empty `name`.\");\n }\n\n if (typeof definition.handle !== \"function\") {\n throw new InvalidJobDefinitionError(\n `defineJob(\"${definition.name}\") requires a \\`handle(payload, ctx)\\` function.`,\n );\n }\n\n if (\n definition.attempts !== undefined &&\n !(Number.isInteger(definition.attempts) && definition.attempts >= 1)\n ) {\n throw new InvalidJobDefinitionError(\n `defineJob(\"${definition.name}\"): \\`attempts\\` must be an integer >= 1, got ${definition.attempts}.`,\n );\n }\n}\n","import { defaultQueueName } from \"./config\";\nimport { getQueueDriver } from \"./queue-driver\";\nimport type { FailedJob } from \"./types\";\n\nexport type FailedJobsOptions = {\n /** Queue to read. Default: the default queue. */\n queue?: string;\n /** First index (newest first). Default `0`. */\n start?: number;\n /** Last index, inclusive. Default `99`. */\n end?: number;\n};\n\n/** List failed jobs, newest first, using the selected driver. */\nexport async function failedJobs(options: FailedJobsOptions = {}): Promise<FailedJob[]> {\n return getQueueDriver().failed(\n options.queue ?? defaultQueueName(),\n options.start ?? 0,\n options.end ?? 99,\n );\n}\n\n/** Move a failed job back to waiting, resetting its attempts. */\nexport async function retryFailedJob(id: string, options: { queue?: string } = {}): Promise<void> {\n await getQueueDriver().retry(options.queue ?? defaultQueueName(), id);\n}\n","/**\n * The queue's connector for `warlock.config.ts > connectors`.\n *\n * Deliberately a plain object with TYPE-ONLY imports from core: the config\n * file that constructs it must not drag core's runtime graph (or BullMQ) in\n * at config-load time. Core is imported lazily inside `start()`, where the\n * app has already loaded it.\n */\nimport type { Connector, ConnectorLifecyclePhase } from \"@warlock.js/core\";\nimport { log } from \"@warlock.js/logger\";\nimport { resetQueueConfig, setQueueConfig } from \"./config\";\nimport { mountQueueDashboard } from \"./dashboard-boot\";\nimport { closeQueue, startWorkers } from \"./queue-manager\";\nimport type { QueueConfig } from \"./types\";\n\n/**\n * Boots after every built-in connector (`ConnectorPriority.AI` is `10`), so\n * the logger is up and anything a job handler needs is already connected;\n * shuts down before them for the same reason.\n */\nexport const QUEUE_CONNECTOR_PRIORITY = 11;\n\nconst WATCHED_FILES = [\"src/config/queue.ts\"];\n\nexport type QueueConnectorOptions = {\n /**\n * Supply the configuration directly instead of reading the `queue` config\n * key (`src/config/queue.ts`).\n */\n config?: QueueConfig;\n};\n\n/**\n * Construct the queue connector.\n *\n * Runs in the `late` lifecycle phase — after app code is imported — so every\n * `defineJob` in the app has registered before workers start. At start it\n * reads the `queue` config and starts in-process workers unless\n * `workers.enabled` is `false`; at shutdown it closes workers (waiting for\n * active jobs, bounded by `workers.shutdownTimeout`) and then the queues.\n *\n * @example\n * // warlock.config.ts\n * import { queueConnector } from \"@warlock.js/queue\";\n *\n * export default defineConfig({ connectors: [queueConnector()] });\n */\nexport function queueConnector(options: QueueConnectorOptions = {}): Connector {\n let active = false;\n\n const connector: Connector = {\n name: \"queue\",\n priority: QUEUE_CONNECTOR_PRIORITY,\n // Core's `ConnectorLifecyclePhase.Late`; the value is spelled out so this\n // module stays free of a runtime import of core.\n lifecyclePhase: \"late\" as ConnectorLifecyclePhase,\n isActive: () => active,\n /**\n * Mounts the dashboard, when configured, here rather than in `start()`:\n * `boot()` runs for every late-phase connector, in priority order, before\n * any of them `start()`s — so by the time this runs, the HTTP connector\n * (priority 5, before queue's 11) has already built its Fastify instance\n * and registered its own plugins, but has not yet called `listen()`.\n * Fastify refuses new plugin registrations after `listen()`, so this is\n * the only point in the boot sequence where mounting is possible.\n */\n async boot() {\n const queueConfig = options.config ?? (await readQueueConfig());\n\n if (!queueConfig?.dashboard?.enabled) {\n return;\n }\n\n // The dashboard resolves queues through the active config, so it has to\n // be registered here rather than only in `start()`, which runs after\n // every late connector has booted. `start()` sets it again; the setter\n // is idempotent for the same object.\n setQueueConfig(queueConfig);\n\n const { getHttpServer } = await import(\"@warlock.js/core\");\n\n await mountQueueDashboard(getHttpServer(), queueConfig);\n },\n async start() {\n const queueConfig = options.config ?? (await readQueueConfig());\n\n if (!queueConfig) {\n log.warn(\n \"queue\",\n \"configured\",\n \"queueConnector() is registered but no `queue` config was found (src/config/queue.ts); queue not started\",\n );\n return;\n }\n\n setQueueConfig(queueConfig);\n const started = await startWorkers();\n active = true;\n\n log.info(\n \"queue\",\n \"configured\",\n started.length > 0\n ? `Queue workers running for: ${started.join(\", \")}`\n : \"Queue configured (no in-process workers)\",\n );\n },\n async restart() {\n await connector.shutdown();\n await connector.start();\n },\n async shutdown() {\n if (!active) {\n return;\n }\n\n await closeQueue();\n resetQueueConfig();\n active = false;\n },\n shouldRestart(changedFiles) {\n return changedFiles.some((file) => {\n const normalized = file.replace(/\\\\/g, \"/\");\n\n return WATCHED_FILES.some(\n (watched) => normalized === watched || normalized.endsWith(`/${watched}`),\n );\n });\n },\n };\n\n return connector;\n}\n\nasync function readQueueConfig(): Promise<QueueConfig | undefined> {\n const { config } = await import(\"@warlock.js/core\");\n\n return config.get<QueueConfig | undefined>(\"queue\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["UnrecoverableError","Queue","log","Worker","randomUUID","resolve","createRequire","dirname","log"],"sources":["../../../../../../../packages/queue/src/errors.ts","../../../../../../../packages/queue/src/config.ts","../../../../../../../packages/queue/src/dashboard-middleware-adapter.ts","../../../../../../../packages/queue/src/dashboard-guard-plugin.ts","../../../../../../../packages/queue/src/job-registry.ts","../../../../../../../packages/queue/src/duration.ts","../../../../../../../packages/queue/src/job-timeout.ts","../../../../../../../packages/queue/src/queue-context.ts","../../../../../../../packages/queue/src/process-job.ts","../../../../../../../packages/queue/src/drivers/bullmq.ts","../../../../../../../packages/queue/src/drivers/local-store.ts","../../../../../../../packages/queue/src/drivers/local.ts","../../../../../../../packages/queue/src/drivers/memory-store.ts","../../../../../../../packages/queue/src/drivers/sqlite-store.ts","../../../../../../../packages/queue/src/queue-driver.ts","../../../../../../../packages/queue/src/queue-manager.ts","../../../../../../../packages/queue/src/dashboard.ts","../../../../../../../packages/queue/src/queue-dashboard-unguarded.error.ts","../../../../../../../packages/queue/src/dashboard-boot.ts","../../../../../../../packages/queue/src/dispatch-interceptor.ts","../../../../../../../packages/queue/src/job-options.ts","../../../../../../../packages/queue/src/define-job.ts","../../../../../../../packages/queue/src/failed-jobs.ts","../../../../../../../packages/queue/src/queue-connector.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","import { QueueNotConfiguredError } from \"./errors\";\nimport type { QueueConfig } from \"./types\";\n\nlet activeConfig: QueueConfig | undefined;\n\n/**\n * Set the active queue configuration. In a Warlock app the queue connector\n * calls this at boot with `src/config/queue.ts`; scripts and tests may call\n * it directly. Replaces (does not merge) any previous configuration.\n */\nexport function setQueueConfig(config: QueueConfig): void {\n activeConfig = config;\n}\n\n/**\n * The active queue configuration. Throws {@link QueueNotConfiguredError}\n * when none was set.\n */\nexport function getQueueConfig(): QueueConfig {\n if (!activeConfig) {\n throw new QueueNotConfiguredError();\n }\n\n return activeConfig;\n}\n\n/** Forget the active configuration. */\nexport function resetQueueConfig(): void {\n activeConfig = undefined;\n}\n\n/** The queue a job runs on when it names none. */\nexport function defaultQueueName(): string {\n return activeConfig?.defaultQueue ?? \"default\";\n}\n","import type { Middleware } from \"@warlock.js/core\";\nimport type { FastifyReply, FastifyRequest } from \"fastify\";\n\n/**\n * Run a Warlock middleware list against a raw Fastify request/reply pair.\n *\n * Bull-board's `FastifyAdapter` hands back a plain Fastify plugin, not a\n * Warlock route — there is no `Route`, no validation pipeline, and none of\n * `createRequestStore`'s context wiring (CSP, tracing, `@warlock.js/context`\n * stores). Reusing that full pipeline here would pull the whole request\n * machinery into a place it was never meant to run. Instead this builds the\n * minimal `Request`/`Response` pair — enough for guard-style middleware\n * (`authMiddleware`, `ipFilterMiddleware`, a custom check) to read the\n * request and short-circuit with a response, which covers every one of\n * bull-board's routes because they all sit behind the same hook.\n *\n * `@warlock.js/core` is imported dynamically so this module never drags\n * core's runtime graph into a process that never mounts the dashboard.\n *\n * @returns `true` when a middleware sent a response and the caller must not\n * continue (bull-board's handler must not run); `false` to continue.\n */\nexport async function runDashboardMiddleware(\n middlewareList: Middleware[],\n fastifyRequest: FastifyRequest,\n fastifyReply: FastifyReply,\n): Promise<boolean> {\n if (middlewareList.length === 0) {\n return false;\n }\n\n const { Request, Response } = await import(\"@warlock.js/core\");\n\n const request = new Request();\n const response = new Response();\n\n response.setResponse(fastifyReply);\n request.response = response;\n response.request = request;\n request.setRequest(fastifyRequest);\n\n for (const middlewareFunction of middlewareList) {\n const result = await middlewareFunction({ request, response });\n\n if (result) {\n return true;\n }\n }\n\n return false;\n}\n","import type { Middleware } from \"@warlock.js/core\";\nimport type { FastifyInstance, FastifyPluginCallback } from \"fastify\";\nimport { runDashboardMiddleware } from \"./dashboard-middleware-adapter\";\n\n/**\n * Wrap bull-board's Fastify plugin in a scope that runs `middlewareList`\n * on every request before bull-board's own routes see it.\n *\n * Bull-board's `FastifyAdapter.registerPlugin()` returns a plain Fastify\n * plugin with no hook for Warlock middleware to run through, and Fastify\n * only lets an `onRequest` hook be added to a plugin scope — never spliced\n * into a plugin someone else wrote. So this builds ONE plugin that adds the\n * hook to its own scope and then registers bull-board's plugin as a child of\n * that scope; Fastify's encapsulation runs the hook for every route the\n * child registers, which is every dashboard route.\n */\nexport function buildDashboardGuardPlugin(\n middlewareList: Middleware[],\n bullBoardPlugin: unknown,\n): FastifyPluginCallback {\n return function dashboardGuardPlugin(instance: FastifyInstance, _options, done) {\n if (middlewareList.length > 0) {\n instance.addHook(\"onRequest\", async (request, reply) => {\n const handled = await runDashboardMiddleware(middlewareList, request, reply);\n\n if (handled) {\n // Returning the reply is Fastify's own way for an `onRequest` hook to\n // END the lifecycle, so bull-board's handler never runs. Relying\n // instead on Fastify noticing the reply was already sent leaves the\n // outcome to write ORDERING: a guard answering asynchronously can lose\n // that race and let the dashboard render to an unauthenticated caller.\n // The adapter returns this boolean precisely so the decision is\n // explicit rather than emergent.\n return reply;\n }\n });\n }\n\n instance.register(bullBoardPlugin as never);\n done();\n };\n}\n","import { defaultQueueName } from \"./config\";\nimport type { JobDefinition } from \"./types\";\n\n/** A registered definition, payload/result erased for storage. */\nexport type RegisteredJob = JobDefinition<unknown, unknown>;\n\ntype RegistryListener = (job: RegisteredJob) => void;\n\nconst jobs = new Map<string, RegisteredJob>();\nconst listeners = new Set<RegistryListener>();\n\n/**\n * Register a definition under its name.\n *\n * Re-registering a name REPLACES the previous definition: in development a\n * job module is re-evaluated on every reload, and refusing the second\n * evaluation would break the reload. Job names must therefore be unique\n * across the app — two different modules using one name leave only the\n * later handler active.\n */\nexport function registerJob(job: RegisteredJob): void {\n jobs.set(job.name, job);\n\n for (const listener of listeners) {\n listener(job);\n }\n}\n\n/** The definition registered under `name`, if any. */\nexport function findRegisteredJob(name: string): RegisteredJob | undefined {\n return jobs.get(name);\n}\n\n/** Every registered definition. */\nexport function registeredJobs(): RegisteredJob[] {\n return [...jobs.values()];\n}\n\n/** The queue a definition runs on, resolved against the active config. */\nexport function queueOf(job: Pick<RegisteredJob, \"queue\">): string {\n return job.queue ?? defaultQueueName();\n}\n\n/** Be told whenever a job is registered. Returns an unsubscribe function. */\nexport function onJobRegistered(listener: RegistryListener): () => void {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n}\n","import { InvalidDurationError } from \"./errors\";\nimport type { Duration } from \"./types\";\n\nconst UNIT_MILLISECONDS: Record<string, number> = {\n ms: 1,\n s: 1_000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n};\n\nconst DURATION_PATTERN = /^(\\d+(?:\\.\\d+)?)(ms|s|m|h|d)$/;\n\n/**\n * Convert a {@link Duration} to milliseconds. Numbers are already\n * milliseconds. Anything else is rejected loudly rather than guessed at.\n */\nexport function toMilliseconds(value: Duration | string): number {\n if (typeof value === \"number\") {\n if (!Number.isFinite(value) || value < 0) {\n throw new InvalidDurationError(value);\n }\n\n return Math.round(value);\n }\n\n const match = typeof value === \"string\" ? DURATION_PATTERN.exec(value.trim()) : null;\n\n if (!match) {\n throw new InvalidDurationError(value);\n }\n\n return Math.round(Number(match[1]) * UNIT_MILLISECONDS[match[2]!]!);\n}\n","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","/**\n * Carries ambient state (tenant, request id, …) from the code that\n * dispatches a job into the worker that runs it.\n */\nexport type QueueContext<T> = {\n /** Read the ambient state at dispatch time; `undefined` means nothing to carry. */\n capture: () => T | undefined;\n /** Run `run` with the captured state re-established. */\n restore: (captured: T, run: () => Promise<unknown>) => Promise<unknown>;\n};\n\n/**\n * Define a queue context. `restore`'s `captured` is inferred from `capture`.\n *\n * @example\n * setQueueContext(\n * defineQueueContext({\n * capture: () => tenantStorage.getStore(),\n * restore: (tenant, run) => tenantStorage.run(tenant, run),\n * }),\n * );\n */\nexport function defineQueueContext<T>(context: QueueContext<T>): QueueContext<T> {\n return context;\n}\n\nlet activeContext: QueueContext<any> | undefined;\n\n/** Register the queue context used by every job dispatch and run. */\nexport function setQueueContext(context: QueueContext<any>): void {\n activeContext = context;\n}\n\n/** The registered queue context, if any. */\nexport function getQueueContext(): QueueContext<any> | undefined {\n return activeContext;\n}\n\n/** Forget the registered queue context. */\nexport function resetQueueContext(): void {\n activeContext = undefined;\n}\n\n/** The job-data envelope written when a context was captured. */\nexport type QueueEnvelope = {\n __warlock: { v: 1; context?: unknown };\n payload: unknown;\n};\n\n/** Wrap a payload with the captured context; no context leaves it bare. */\nexport function wrapPayload(payload: unknown, context: unknown): unknown {\n if (context === undefined) return payload;\n\n return { __warlock: { v: 1, context }, payload } satisfies QueueEnvelope;\n}\n\n/** Split job data into payload and captured context; bare data passes through. */\nexport function unwrapPayload(data: unknown): { payload: unknown; context: unknown } {\n const envelope = data as Partial<QueueEnvelope> | null;\n\n if (\n envelope !== null &&\n typeof envelope === \"object\" &&\n typeof envelope.__warlock === \"object\" &&\n envelope.__warlock !== null &&\n envelope.__warlock.v === 1 &&\n \"payload\" in envelope\n ) {\n return { payload: envelope.payload, context: envelope.__warlock.context };\n }\n\n return { payload: data, context: undefined };\n}\n","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","import { log } from \"@warlock.js/logger\";\nimport { Queue, Worker, type Job, type JobsOptions } from \"bullmq\";\nimport type { DriverDispatch, QueueDriver } from \"../driver\";\nimport { FailedJobNotFoundError } from \"../errors\";\nimport { processJob } from \"../process-job\";\nimport { unwrapPayload } from \"../queue-context\";\nimport type { DispatchedJob, FailedJob, JobSnapshot, JobState, QueueConfig } from \"../types\";\n\nexport class BullMQDriver implements QueueDriver {\n private queues = new Map<string, Queue>();\n private workers = new Map<string, Worker>();\n\n public constructor(private config: QueueConfig) {\n if (!config.connection) throw new Error(\"The bullmq queue driver requires `connection`.\");\n }\n\n public getQueue(name: string): Queue {\n let queue = this.queues.get(name);\n if (!queue) {\n const connection = this.config.connection;\n if (!connection) throw new Error(\"The bullmq queue driver requires `connection`.\");\n queue = new Queue(name, { connection, prefix: this.config.prefix ?? \"warlock\" });\n queue.on(\"error\", (error) => log.error(\"queue\", \"connection\", error));\n this.queues.set(name, queue);\n }\n return queue;\n }\n\n public async dispatch(request: DriverDispatch): Promise<DispatchedJob> {\n const { backoff, ...rest } = request.options;\n const options: JobsOptions = {\n ...rest,\n backoff: typeof backoff === \"number\" ? { type: \"fixed\", delay: backoff } : backoff,\n };\n const job = await this.getQueue(request.queue).add(request.name, request.data, options);\n return { id: String(job.id), name: request.name, queue: request.queue };\n }\n\n public async find(queue: string, id: string): Promise<JobSnapshot | undefined> {\n const source = this.getQueue(queue);\n const initial = await source.getJob(id);\n if (!initial) return undefined;\n // Fetch fields after reading state so completion fields match the state.\n const state = (await initial.getState()) as JobState;\n return toSnapshot((await source.getJob(id)) ?? initial, state);\n }\n\n public async failed(queue: string, start: number, end: number): Promise<FailedJob[]> {\n return (await this.getQueue(queue).getFailed(start, end)).map((job) => ({\n id: String(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.finishedOn ? new Date(job.finishedOn) : undefined,\n retry: () => job.retry(\"failed\"),\n }));\n }\n\n public async retry(queue: string, id: string): Promise<void> {\n const job = await this.getQueue(queue).getJob(id);\n if (!job || !(await job.isFailed())) throw new FailedJobNotFoundError(id, queue);\n await job.retry(\"failed\");\n }\n\n public async start(queue: string): Promise<void> {\n let worker = this.workers.get(queue);\n if (!worker) {\n const connection = this.config.connection;\n if (!connection) throw new Error(\"The bullmq queue driver requires `connection`.\");\n worker = new Worker(queue, processJob, {\n connection,\n prefix: this.config.prefix ?? \"warlock\",\n concurrency: this.config.workers?.concurrency ?? 1,\n });\n worker.on(\"error\", (error) => log.error(\"queue\", \"worker\", error));\n worker.on(\"failed\", (job, error) =>\n log.error(\n \"queue\",\n \"job.failed\",\n `${job?.name ?? \"unknown\"} (${job?.id ?? \"?\"}): ${error.message}`,\n ),\n );\n this.workers.set(queue, worker);\n }\n await worker.waitUntilReady();\n }\n\n public running(): string[] {\n return [...this.workers.keys()];\n }\n\n public async close(timeout: number): Promise<void> {\n const workers = [...this.workers.values()];\n this.workers.clear();\n await Promise.all(workers.map((worker) => closeWorker(worker, timeout)));\n const queues = [...this.queues.values()];\n this.queues.clear();\n await Promise.all(queues.map((queue) => queue.close()));\n }\n}\n\nfunction toSnapshot(job: Job, state: JobState): JobSnapshot {\n return {\n id: String(job.id),\n name: job.name,\n queue: job.queueName,\n state,\n payload: unwrapPayload(job.data).payload,\n progress: job.progress as JobSnapshot[\"progress\"],\n attemptsMade: job.attemptsMade,\n result: job.returnvalue as unknown,\n failedReason: job.failedReason || undefined,\n createdAt: new Date(job.timestamp),\n finishedAt: job.finishedOn ? new Date(job.finishedOn) : undefined,\n };\n}\n\nasync function closeWorker(worker: Worker, timeout: number): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n const timedOut = new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), timeout);\n });\n const closing = worker.close().then(() => \"closed\" as const);\n closing.catch(() => undefined);\n try {\n if ((await Promise.race([closing, timedOut])) === \"timeout\") {\n log.warn(\n \"queue\",\n \"shutdown\",\n `Worker for \"${worker.name}\" still had active jobs after ${timeout}ms; disconnecting. ` +\n \"Those jobs are retried once their lock expires.\",\n );\n await worker.disconnect();\n }\n } finally {\n clearTimeout(timer);\n }\n}\n","import type { ResolvedJobOptions } from \"../job-options\";\nimport type { JobProgress, JobState } from \"../types\";\n\nexport type LocalJob = {\n id: string;\n queue: string;\n name: string;\n data: unknown;\n options: ResolvedJobOptions;\n state: JobState;\n createdAt: number;\n readyAt: number;\n attemptsMade: number;\n priority: number;\n progress: JobProgress;\n logs: string[];\n stacktrace: string[];\n failedReason?: string;\n result?: unknown;\n finishedAt?: number;\n token?: string;\n leaseUntil: number;\n};\n\n/** Synchronous transactions contain no user code or await points. */\nexport interface LocalStore {\n transaction<T>(run: () => T): T;\n get(queue: string, id: string): LocalJob | undefined;\n list(queue: string, state?: JobState): LocalJob[];\n /** Earliest eligible job, including active jobs with an expired lease. */\n ready(queue: string, now: number): LocalJob | undefined;\n put(job: LocalJob): void;\n remove(queue: string, id: string): void;\n close(): void;\n}\n\n/** Match JSON transport on every backend, without sharing mutable objects. */\nexport function jsonCopy<T>(value: T): T {\n return (JSON.parse(JSON.stringify({ value })) as { value: T }).value;\n}\n","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","import type { JobState } from \"../types\";\nimport { jsonCopy, type LocalJob, type LocalStore } from \"./local-store\";\n\nexport class MemoryStore implements LocalStore {\n private jobs = new Map<string, LocalJob>();\n public transaction<T>(run: () => T): T {\n const before = new Map(this.jobs);\n try {\n return run();\n } catch (error) {\n this.jobs = before;\n throw error;\n }\n }\n private key(queue: string, id: string): string {\n return JSON.stringify([queue, id]);\n }\n public get(queue: string, id: string): LocalJob | undefined {\n return jsonCopy(this.jobs.get(this.key(queue, id)));\n }\n public list(queue: string, state?: JobState): LocalJob[] {\n return [...this.jobs.values()]\n .filter((job) => job.queue === queue && (!state || job.state === state))\n .map(jsonCopy);\n }\n public ready(queue: string, now: number): LocalJob | undefined {\n let candidate: LocalJob | undefined;\n for (const job of this.list(queue)) {\n const eligible =\n (job.state === \"active\" && job.leaseUntil <= now) ||\n ([\"waiting\", \"delayed\", \"prioritized\"].includes(job.state) && job.readyAt <= now);\n if (eligible && (!candidate || job.priority < candidate.priority)) candidate = job;\n }\n return candidate;\n }\n public put(job: LocalJob): void {\n this.jobs.set(this.key(job.queue, job.id), jsonCopy(job));\n }\n public remove(queue: string, id: string): void {\n this.jobs.delete(this.key(queue, id));\n }\n public close(): void {\n this.jobs.clear();\n }\n}\n","import { mkdirSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, resolve } from \"node:path\";\nimport type { JobState, QueueConfig } from \"../types\";\nimport type { LocalJob, LocalStore } from \"./local-store\";\n\n/**\n * The part of `node:sqlite` this store uses. Declared here so apps type-check the\n * shipped source without depending on a particular `@types/node` release.\n * https://nodejs.org/docs/latest-v22.x/api/sqlite.html\n */\ntype SqliteValue = null | number | bigint | string | Uint8Array;\ntype SqliteRow = Record<string, unknown>;\ntype SqliteStatement = {\n all(...parameters: SqliteValue[]): SqliteRow[];\n get(...parameters: SqliteValue[]): SqliteRow | undefined;\n run(...parameters: SqliteValue[]): unknown;\n};\ntype DatabaseSync = {\n exec(sql: string): void;\n prepare(sql: string): SqliteStatement;\n close(): void;\n};\ntype SqliteModule = { DatabaseSync: new (path: string) => DatabaseSync };\n\n/** node:sqlite is loaded only when the local backend is selected. */\nexport class SqliteStore implements LocalStore {\n private db: DatabaseSync;\n private namespace: string;\n\n public constructor(config: QueueConfig) {\n const path = resolve(config.local?.path ?? \".warlock/queue.sqlite\");\n if (config.local?.path === \":memory:\")\n throw new Error(\"Use driver: 'memory' for non-durable queues.\");\n let sqlite: SqliteModule;\n try {\n sqlite = createRequire(import.meta.url)(\"node:sqlite\") as SqliteModule;\n } catch (cause) {\n throw new Error(\n \"The local queue driver requires Node >= 22.15 with node:sqlite available without flags.\",\n { cause },\n );\n }\n if (typeof sqlite.DatabaseSync !== \"function\")\n throw new Error(\"node:sqlite.DatabaseSync is unavailable.\");\n mkdirSync(dirname(path), { recursive: true });\n this.db = new sqlite.DatabaseSync(path);\n this.namespace = config.prefix ?? \"warlock\";\n try {\n // Use the v22.15 API; constructor timeout options were introduced later.\n this.db.exec(`\n PRAGMA busy_timeout = 5000;\n PRAGMA journal_mode = WAL;\n PRAGMA synchronous = FULL;\n CREATE TABLE IF NOT EXISTS warlock_queue_jobs (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n namespace TEXT NOT NULL, queue TEXT NOT NULL, id TEXT NOT NULL,\n state TEXT NOT NULL, ready_at REAL NOT NULL, lease_until REAL NOT NULL,\n priority INTEGER NOT NULL, body TEXT NOT NULL,\n UNIQUE(namespace, queue, id)\n );\n CREATE INDEX IF NOT EXISTS warlock_queue_ready\n ON warlock_queue_jobs(namespace, queue, state, ready_at, lease_until);\n `);\n } catch (error) {\n this.db.close();\n throw error;\n }\n }\n\n public transaction<T>(run: () => T): T {\n this.db.exec(\"BEGIN IMMEDIATE\");\n try {\n const result = run();\n this.db.exec(\"COMMIT\");\n return result;\n } catch (error) {\n this.db.exec(\"ROLLBACK\");\n throw error;\n }\n }\n\n public get(queue: string, id: string): LocalJob | undefined {\n return decode(\n this.db\n .prepare(\"SELECT body FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? AND id = ?\")\n .get(this.namespace, queue, id),\n );\n }\n public list(queue: string, state?: JobState): LocalJob[] {\n const rows = state\n ? this.db\n .prepare(\n \"SELECT body FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? AND state = ? ORDER BY seq\",\n )\n .all(this.namespace, queue, state)\n : this.db\n .prepare(\n \"SELECT body FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? ORDER BY seq\",\n )\n .all(this.namespace, queue);\n return rows.flatMap((row) => {\n const job = decode(row);\n return job ? [job] : [];\n });\n }\n public ready(queue: string, now: number): LocalJob | undefined {\n return decode(\n this.db\n .prepare(`SELECT body FROM warlock_queue_jobs\n WHERE namespace = ? AND queue = ? AND (\n (state IN ('waiting', 'delayed', 'prioritized') AND ready_at <= ?) OR\n (state = 'active' AND lease_until <= ?)\n ) ORDER BY priority, seq LIMIT 1`)\n .get(this.namespace, queue, now, now),\n );\n }\n public put(job: LocalJob): void {\n this.db\n .prepare(`INSERT INTO warlock_queue_jobs\n (namespace, queue, id, state, ready_at, lease_until, priority, body)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(namespace, queue, id) DO UPDATE SET\n state = excluded.state, ready_at = excluded.ready_at,\n lease_until = excluded.lease_until, priority = excluded.priority, body = excluded.body`)\n .run(\n this.namespace,\n job.queue,\n job.id,\n job.state,\n job.readyAt,\n job.leaseUntil,\n job.priority,\n JSON.stringify(job),\n );\n }\n public remove(queue: string, id: string): void {\n this.db\n .prepare(\"DELETE FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? AND id = ?\")\n .run(this.namespace, queue, id);\n }\n public close(): void {\n this.db.close();\n }\n}\n\nfunction decode(row: Record<string, unknown> | undefined): LocalJob | undefined {\n return typeof row?.body === \"string\" ? (JSON.parse(row.body) as LocalJob) : undefined;\n}\n","import { getQueueConfig } from \"./config\";\nimport type { QueueDriver } from \"./driver\";\nimport { BullMQDriver } from \"./drivers/bullmq\";\nimport { LocalDriver } from \"./drivers/local\";\nimport { MemoryStore } from \"./drivers/memory-store\";\nimport { SqliteStore } from \"./drivers/sqlite-store\";\n\nlet activeDriver: QueueDriver | undefined;\n\n/** The configured backend, created on first use. Close it before changing config. */\nexport function getQueueDriver(): QueueDriver {\n if (!activeDriver) {\n const config = getQueueConfig();\n switch (config.driver ?? \"bullmq\") {\n case \"bullmq\":\n activeDriver = new BullMQDriver(config);\n break;\n case \"local\":\n activeDriver = new LocalDriver(new SqliteStore(config), config);\n break;\n case \"memory\":\n activeDriver = new LocalDriver(new MemoryStore(), config);\n break;\n default:\n throw new Error(`Unknown queue driver: ${String(config.driver)}`);\n }\n }\n return activeDriver;\n}\n\nexport function currentQueueDriver(): QueueDriver | undefined {\n return activeDriver;\n}\n\nexport function forgetQueueDriver(): void {\n activeDriver = undefined;\n}\n","import { log } from \"@warlock.js/logger\";\nimport type { Queue } from \"bullmq\";\nimport { getQueueConfig } from \"./config\";\nimport { BullMQDriver } from \"./drivers/bullmq\";\nimport { onJobRegistered, queueOf, registeredJobs } from \"./job-registry\";\nimport { currentQueueDriver, forgetQueueDriver, getQueueDriver } from \"./queue-driver\";\n\nlet stopListening: (() => void) | undefined;\nlet closing: Promise<void> | undefined;\n\n/** Low-level BullMQ access. For portable operations use defineJob / failedJobs. */\nexport function getQueue(name: string): Queue {\n const driver = getQueueDriver();\n if (!(driver instanceof BullMQDriver))\n throw new Error(\"getQueue() requires the bullmq queue driver.\");\n return driver.getQueue(name);\n}\n\n/** Start workers for registered queues, honouring process roles and workers.enabled. */\nexport async function startWorkers(): Promise<string[]> {\n if (closing) await closing;\n const { Application } = await import(\"@warlock.js/core\");\n if (!Application.hasRole(\"worker\")) {\n log.info(\n \"queue\",\n \"workers\",\n `queue: workers not started (role: ${[...Application.roles].join(\",\")})`,\n );\n return [];\n }\n if (getQueueConfig().workers?.enabled === false) return [];\n const driver = getQueueDriver();\n stopListening ??= onJobRegistered((job) => {\n void driver.start(queueOf(job)).catch((error: unknown) => log.error(\"queue\", \"worker\", error));\n });\n await Promise.all(\n [...new Set(registeredJobs().map(queueOf))].map((queue) => driver.start(queue)),\n );\n return driver.running();\n}\n\n/** Names of queues with workers in this process. */\nexport function runningWorkers(): string[] {\n return currentQueueDriver()?.running() ?? [];\n}\n\nexport type CloseQueueOptions = {\n /** Drain timeout in ms, default workers.shutdownTimeout, else 30000. */\n timeout?: number;\n};\n\n/** Stop taking jobs, drain active handlers, then release leases and close storage. */\nexport function closeQueue(options: CloseQueueOptions = {}): Promise<void> {\n if (closing) return closing;\n stopListening?.();\n stopListening = undefined;\n const driver = currentQueueDriver();\n if (!driver) return Promise.resolve();\n const timeout = options.timeout ?? configuredShutdownTimeout();\n if (!Number.isFinite(timeout) || timeout < 0)\n return Promise.reject(new Error(\"Queue shutdown timeout must be non-negative and finite.\"));\n closing = driver.close(timeout).finally(() => {\n forgetQueueDriver();\n closing = undefined;\n });\n return closing;\n}\n\nfunction configuredShutdownTimeout(): number {\n try {\n return getQueueConfig().workers?.shutdownTimeout ?? 30_000;\n } catch {\n return 30_000;\n }\n}\n","import type { Middleware } from \"@warlock.js/core\";\nimport { defaultQueueName, getQueueConfig } from \"./config\";\nimport { buildDashboardGuardPlugin } from \"./dashboard-guard-plugin\";\nimport { QueueDashboardDependencyError } from \"./errors\";\nimport { queueOf, registeredJobs } from \"./job-registry\";\nimport { getQueue } from \"./queue-manager\";\n\n/**\n * The part of a Fastify instance the dashboard needs. Warlock's HTTP server\n * (`getHttpServer()` from `@warlock.js/core`) satisfies it.\n */\nexport type DashboardServer = {\n register(plugin: never, options: { prefix: string }): unknown;\n};\n\nexport type QueueDashboardOptions = {\n /** URL prefix the dashboard is mounted on. Default `\"/admin/queues\"`. */\n basePath?: string;\n /** Queues to show. Default: every queue with a defined job, plus the default queue. */\n queues?: string[];\n /**\n * Run before every dashboard route. Applied via a wrapping Fastify plugin\n * scope, since bull-board's own plugin has no hook to splice Warlock\n * middleware into — see `dashboard-guard-plugin.ts`.\n */\n middleware?: Middleware[];\n};\n\ntype BullBoardModules = {\n createBullBoard: (options: { queues: unknown[]; serverAdapter: unknown }) => unknown;\n BullMQAdapter: new (queue: unknown) => unknown;\n FastifyAdapter: new () => {\n setBasePath(path: string): unknown;\n registerPlugin(): unknown;\n };\n};\n\n/**\n * Mount the bull-board failed/active/delayed job UI on Warlock's Fastify\n * server. Requires the optional packages `@bull-board/api` and\n * `@bull-board/fastify`; they are loaded only when this is called, and a\n * missing one throws {@link QueueDashboardDependencyError}.\n *\n * Call it before the HTTP server starts listening, and put it behind your\n * own authentication — the dashboard can retry and delete jobs.\n *\n * @example\n * import { getHttpServer } from \"@warlock.js/core\";\n * await queueDashboard(getHttpServer(), { basePath: \"/admin/queues\" });\n */\nexport async function queueDashboard(\n server: DashboardServer,\n options: QueueDashboardOptions = {},\n): Promise<void> {\n if ((getQueueConfig().driver ?? \"bullmq\") !== \"bullmq\") {\n throw new Error(\n \"The bull-board dashboard requires the bullmq queue driver. Use job.find() and failedJobs() with local or memory queues.\",\n );\n }\n const basePath = options.basePath ?? \"/admin/queues\";\n const { createBullBoard, BullMQAdapter, FastifyAdapter } = await loadBullBoard();\n const queueNames = options.queues ?? [\n ...new Set([defaultQueueName(), ...registeredJobs().map((job) => queueOf(job))]),\n ];\n\n const serverAdapter = new FastifyAdapter();\n serverAdapter.setBasePath(basePath);\n\n createBullBoard({\n queues: queueNames.map((name) => new BullMQAdapter(getQueue(name))),\n serverAdapter,\n });\n\n const guardedPlugin = buildDashboardGuardPlugin(\n options.middleware ?? [],\n serverAdapter.registerPlugin(),\n );\n\n await server.register(guardedPlugin as never, { prefix: basePath });\n}\n\n/**\n * Load the optional bull-board packages. Exported for tests of the missing\n * dependency path; `importer` defaults to a real dynamic import.\n */\nexport async function loadBullBoard(\n importer: (specifier: string) => Promise<Record<string, unknown>> = (specifier) =>\n import(specifier),\n): Promise<BullBoardModules> {\n const api = await importOptional(importer, \"@bull-board/api\");\n const adapter = await importOptional(importer, \"@bull-board/api/bullMQAdapter\");\n const fastify = await importOptional(importer, \"@bull-board/fastify\");\n\n return {\n createBullBoard: api.createBullBoard as BullBoardModules[\"createBullBoard\"],\n BullMQAdapter: adapter.BullMQAdapter as BullBoardModules[\"BullMQAdapter\"],\n FastifyAdapter: fastify.FastifyAdapter as BullBoardModules[\"FastifyAdapter\"],\n };\n}\n\nasync function importOptional(\n importer: (specifier: string) => Promise<Record<string, unknown>>,\n specifier: string,\n): Promise<Record<string, unknown>> {\n try {\n return await importer(specifier);\n } catch (error) {\n if (isModuleNotFound(error)) {\n throw new QueueDashboardDependencyError(specifier.split(\"/\").slice(0, 2).join(\"/\"));\n }\n\n throw error;\n }\n}\n\nfunction isModuleNotFound(error: unknown): boolean {\n const code = (error as { code?: unknown } | undefined)?.code;\n\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n","/**\n * Thrown at boot when `queue.dashboard.enabled` is `true` in production with\n * no guard middleware. The dashboard can retry and delete jobs; mounting it\n * on the open internet without a guard is a production incident waiting to\n * happen, so this fails the boot instead of shipping the hole.\n */\nexport class QueueDashboardUnguardedError extends Error {\n public constructor() {\n super(\n \"queue.dashboard.enabled is true in production with no middleware. The dashboard can \" +\n \"retry and delete jobs, so it must be guarded before it is exposed.\\n\\n\" +\n \"Add a guard middleware:\\n\\n\" +\n ' import { middleware } from \"@warlock.js/core\";\\n' +\n ' import { authMiddleware } from \"@warlock.js/auth\";\\n\\n' +\n \" const queueConfig: QueueConfig = {\\n\" +\n \" // ...\\n\" +\n \" dashboard: {\\n\" +\n \" enabled: true,\\n\" +\n ' middleware: [authMiddleware(\"admin\")],\\n' +\n \" },\\n\" +\n \" };\\n\",\n );\n this.name = \"QueueDashboardUnguardedError\";\n }\n}\n","import { log } from \"@warlock.js/logger\";\nimport { type DashboardServer, queueDashboard } from \"./dashboard\";\nimport { QueueDashboardUnguardedError } from \"./queue-dashboard-unguarded.error\";\nimport type { QueueConfig } from \"./types\";\n\n/** Default path the dashboard mounts on when `dashboard.path` is not set. */\nexport const DEFAULT_DASHBOARD_PATH = \"/admin/queues\";\n\n/**\n * Mount the bull-board dashboard for `queue.dashboard` in `config`, applying\n * the safety rule: `enabled` in production with no middleware throws\n * {@link QueueDashboardUnguardedError} instead of booting exposed; outside\n * production with no middleware it logs one warning and mounts anyway.\n *\n * Called by `queueConnector()` at boot, once the HTTP server exists but\n * before it starts listening — see `queue-connector.ts`. Exported so it can\n * be unit-tested without going through the whole connector lifecycle.\n */\nexport async function mountQueueDashboard(\n server: DashboardServer | undefined,\n config: QueueConfig,\n): Promise<void> {\n const dashboard = config.dashboard;\n\n if (!dashboard?.enabled) {\n return;\n }\n\n if ((config.driver ?? \"bullmq\") !== \"bullmq\") {\n throw new Error(\"The bull-board dashboard requires the bullmq queue driver.\");\n }\n\n const middlewareList = dashboard.middleware ?? [];\n\n if (middlewareList.length === 0) {\n if (process.env.NODE_ENV === \"production\") {\n throw new QueueDashboardUnguardedError();\n }\n\n log.warn(\n \"queue\",\n \"dashboard\",\n \"queue.dashboard.enabled is true with no middleware. The dashboard can retry and delete \" +\n \"jobs — add a guard middleware before this ships to production.\",\n );\n }\n\n if (!server) {\n log.warn(\n \"queue\",\n \"dashboard\",\n \"queue.dashboard.enabled is true but no HTTP server was found; the dashboard was not mounted.\",\n );\n\n return;\n }\n\n await queueDashboard(server, {\n basePath: dashboard.path ?? DEFAULT_DASHBOARD_PATH,\n middleware: middlewareList,\n });\n}\n","import type { DispatchOptions, DispatchedJob, JobSnapshot } from \"./types\";\n\n/**\n * A stand-in for the broker, used by test fakes.\n *\n * When one is installed, `job.dispatch()` hands the job to it instead of\n * adding it to BullMQ, and `job.find()` asks it instead of Redis. Nothing\n * connects to Redis while it is installed.\n *\n * `@warlock.js/core/tests` installs one as `queueFake` in the test\n * environment; this package exposes the seam so core needs no dependency on\n * it. The slot lives on `globalThis` so every copy of this package in a\n * process (a Vitest worker can load two) sees the same one.\n */\nexport type DispatchInterceptor = {\n dispatch(job: {\n name: string;\n queue: string;\n payload: unknown;\n options: DispatchOptions;\n }): DispatchedJob | Promise<DispatchedJob>;\n find?(job: {\n name: string;\n queue: string;\n id: string;\n }): JobSnapshot | undefined | Promise<JobSnapshot | undefined>;\n};\n\nconst INTERCEPTOR_KEY = Symbol.for(\"warlock.queue.dispatchInterceptor\");\n\ntype InterceptorHolder = { [INTERCEPTOR_KEY]?: DispatchInterceptor };\n\n/** Install an interceptor, or remove it with `undefined`. */\nexport function setDispatchInterceptor(interceptor: DispatchInterceptor | undefined): void {\n (globalThis as InterceptorHolder)[INTERCEPTOR_KEY] = interceptor;\n}\n\n/** The installed interceptor, if any. */\nexport function getDispatchInterceptor(): DispatchInterceptor | undefined {\n return (globalThis as InterceptorHolder)[INTERCEPTOR_KEY];\n}\n","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","import { getDispatchInterceptor } from \"./dispatch-interceptor\";\nimport { InvalidJobDefinitionError } from \"./errors\";\nimport { getQueueContext, wrapPayload } from \"./queue-context\";\nimport { queueOf, registerJob, type RegisteredJob } from \"./job-registry\";\nimport { getQueueDriver } from \"./queue-driver\";\nimport { resolveJobOptions } from \"./job-options\";\nimport { assertFailOnTimeout, resolveTimeout } from \"./job-timeout\";\nimport type { JobDefinition, JobSnapshot, QueueJob } from \"./types\";\n\n/**\n * Define a background job.\n *\n * The definition is registered by name so any worker in the process can run\n * it; the returned object dispatches it with a typed payload.\n *\n * @example\n * export const sendInvoice = defineJob({\n * name: \"invoices.send\",\n * attempts: 5,\n * backoff: { type: \"exponential\", delay: 2000 },\n * async handle(payload: { invoiceId: string }, ctx) {\n * await ctx.progress(50);\n * },\n * });\n *\n * await sendInvoice.dispatch({ invoiceId: \"42\" }, { delay: \"10m\", priority: 1 });\n */\nexport function defineJob<TPayload, TResult = unknown>(\n definition: JobDefinition<TPayload, TResult>,\n): QueueJob<TPayload, TResult> {\n assertValidDefinition(definition);\n registerJob(definition as RegisteredJob);\n\n return {\n name: definition.name,\n get queue() {\n return queueOf(definition);\n },\n async dispatch(payload, options = {}) {\n const queueName = queueOf(definition);\n const interceptor = getDispatchInterceptor();\n\n // A test fake stands in for the broker: record, never touch Redis.\n if (interceptor) {\n return interceptor.dispatch({ name: definition.name, queue: queueName, payload, options });\n }\n\n // Capture synchronously, in the caller's async context.\n const captured = definition.context === false ? undefined : getQueueContext()?.capture();\n return getQueueDriver().dispatch({\n name: definition.name,\n queue: queueName,\n data: wrapPayload(payload, captured),\n options: resolveJobOptions(definition, options),\n });\n },\n async find(id) {\n const interceptor = getDispatchInterceptor();\n\n if (interceptor) {\n const found = await interceptor.find?.({\n name: definition.name,\n queue: queueOf(definition),\n id,\n });\n\n return found as JobSnapshot<TPayload, TResult> | undefined;\n }\n\n const snapshot = await getQueueDriver().find(queueOf(definition), id);\n return snapshot?.name === definition.name\n ? (snapshot as JobSnapshot<TPayload, TResult>)\n : undefined;\n },\n };\n}\n\nfunction assertValidDefinition(definition: JobDefinition<unknown, unknown>): void {\n if (typeof definition.name !== \"string\" || definition.name.trim() === \"\") {\n throw new InvalidJobDefinitionError(\"defineJob() requires a non-empty `name`.\");\n }\n\n if (typeof definition.handle !== \"function\") {\n throw new InvalidJobDefinitionError(\n `defineJob(\"${definition.name}\") requires a \\`handle(payload, ctx)\\` function.`,\n );\n }\n\n if (\n definition.attempts !== undefined &&\n !(Number.isInteger(definition.attempts) && definition.attempts >= 1)\n ) {\n throw new InvalidJobDefinitionError(\n `defineJob(\"${definition.name}\"): \\`attempts\\` must be an integer >= 1, got ${definition.attempts}.`,\n );\n }\n\n if (definition.timeout !== undefined) {\n try {\n resolveTimeout(definition.timeout);\n } catch (error) {\n throw new InvalidJobDefinitionError(\n `defineJob(\"${definition.name}\"): ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n assertFailOnTimeout(definition.failOnTimeout);\n}\n","import { defaultQueueName } from \"./config\";\nimport { getQueueDriver } from \"./queue-driver\";\nimport type { FailedJob } from \"./types\";\n\nexport type FailedJobsOptions = {\n /** Queue to read. Default: the default queue. */\n queue?: string;\n /** First index (newest first). Default `0`. */\n start?: number;\n /** Last index, inclusive. Default `99`. */\n end?: number;\n};\n\n/** List failed jobs, newest first, using the selected driver. */\nexport async function failedJobs(options: FailedJobsOptions = {}): Promise<FailedJob[]> {\n return getQueueDriver().failed(\n options.queue ?? defaultQueueName(),\n options.start ?? 0,\n options.end ?? 99,\n );\n}\n\n/** Move a failed job back to waiting, resetting its attempts. */\nexport async function retryFailedJob(id: string, options: { queue?: string } = {}): Promise<void> {\n await getQueueDriver().retry(options.queue ?? defaultQueueName(), id);\n}\n","/**\n * The queue's connector for `warlock.config.ts > connectors`.\n *\n * Deliberately a plain object with TYPE-ONLY imports from core: the config\n * file that constructs it must not drag core's runtime graph (or BullMQ) in\n * at config-load time. Core is imported lazily inside `start()`, where the\n * app has already loaded it.\n */\nimport type { Connector, ConnectorLifecyclePhase } from \"@warlock.js/core\";\nimport { log } from \"@warlock.js/logger\";\nimport { resetQueueConfig, setQueueConfig } from \"./config\";\nimport { mountQueueDashboard } from \"./dashboard-boot\";\nimport { closeQueue, startWorkers } from \"./queue-manager\";\nimport type { QueueConfig } from \"./types\";\n\n/**\n * Boots after every built-in connector (`ConnectorPriority.AI` is `10`), so\n * the logger is up and anything a job handler needs is already connected;\n * shuts down before them for the same reason.\n */\nexport const QUEUE_CONNECTOR_PRIORITY = 11;\n\nconst WATCHED_FILES = [\"src/config/queue.ts\"];\n\nexport type QueueConnectorOptions = {\n /**\n * Supply the configuration directly instead of reading the `queue` config\n * key (`src/config/queue.ts`).\n */\n config?: QueueConfig;\n};\n\n/**\n * Construct the queue connector.\n *\n * Runs in the `late` lifecycle phase — after app code is imported — so every\n * `defineJob` in the app has registered before workers start. At start it\n * reads the `queue` config and starts in-process workers unless\n * `workers.enabled` is `false`; at shutdown it closes workers (waiting for\n * active jobs, bounded by `workers.shutdownTimeout`) and then the queues.\n *\n * @example\n * // warlock.config.ts\n * import { queueConnector } from \"@warlock.js/queue\";\n *\n * export default defineConfig({ connectors: [queueConnector()] });\n */\nexport function queueConnector(options: QueueConnectorOptions = {}): Connector {\n let active = false;\n\n const connector: Connector = {\n name: \"queue\",\n priority: QUEUE_CONNECTOR_PRIORITY,\n // Core's `ConnectorLifecyclePhase.Late`; the value is spelled out so this\n // module stays free of a runtime import of core.\n lifecyclePhase: \"late\" as ConnectorLifecyclePhase,\n isActive: () => active,\n /**\n * Mounts the dashboard, when configured, here rather than in `start()`:\n * `boot()` runs for every late-phase connector, in priority order, before\n * any of them `start()`s — so by the time this runs, the HTTP connector\n * (priority 5, before queue's 11) has already built its Fastify instance\n * and registered its own plugins, but has not yet called `listen()`.\n * Fastify refuses new plugin registrations after `listen()`, so this is\n * the only point in the boot sequence where mounting is possible.\n */\n async boot() {\n const queueConfig = options.config ?? (await readQueueConfig());\n\n if (!queueConfig?.dashboard?.enabled) {\n return;\n }\n\n // The dashboard resolves queues through the active config, so it has to\n // be registered here rather than only in `start()`, which runs after\n // every late connector has booted. `start()` sets it again; the setter\n // is idempotent for the same object.\n setQueueConfig(queueConfig);\n\n const { getHttpServer } = await import(\"@warlock.js/core\");\n\n await mountQueueDashboard(getHttpServer(), queueConfig);\n },\n async start() {\n const queueConfig = options.config ?? (await readQueueConfig());\n\n if (!queueConfig) {\n log.warn(\n \"queue\",\n \"configured\",\n \"queueConnector() is registered but no `queue` config was found (src/config/queue.ts); queue not started\",\n );\n return;\n }\n\n setQueueConfig(queueConfig);\n const started = await startWorkers();\n active = true;\n\n log.info(\n \"queue\",\n \"configured\",\n started.length > 0\n ? `Queue workers running for: ${started.join(\", \")}`\n : \"Queue configured (no in-process workers)\",\n );\n },\n async restart() {\n await connector.shutdown();\n await connector.start();\n },\n async shutdown() {\n if (!active) {\n return;\n }\n\n await closeQueue();\n resetQueueConfig();\n active = false;\n },\n shouldRestart(changedFiles) {\n return changedFiles.some((file) => {\n const normalized = file.replace(/\\\\/g, \"/\");\n\n return WATCHED_FILES.some(\n (watched) => normalized === watched || normalized.endsWith(`/${watched}`),\n );\n });\n },\n };\n\n return connector;\n}\n\nasync function readQueueConfig(): Promise<QueueConfig | undefined> {\n const { config } = await import(\"@warlock.js/core\");\n\n return config.get<QueueConfig | undefined>(\"queue\");\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"define-job.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/define-job.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;wBA0BgB,UAAU,UAAU,mBAClC,YAAY,cAAc,UAAU,WACnC,SAAS,UAAU"}
1
+ {"version":3,"file":"define-job.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/define-job.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;wBA2BgB,UAAU,UAAU,mBAClC,YAAY,cAAc,UAAU,WACnC,SAAS,UAAU"}
@@ -1,5 +1,6 @@
1
1
  import { InvalidJobDefinitionError } from "./errors.mjs";
2
2
  import { queueOf, registerJob } from "./job-registry.mjs";
3
+ import { assertFailOnTimeout, resolveTimeout } from "./job-timeout.mjs";
3
4
  import { getQueueContext, wrapPayload } from "./queue-context.mjs";
4
5
  import { getQueueDriver } from "./queue-driver.mjs";
5
6
  import { getDispatchInterceptor } from "./dispatch-interceptor.mjs";
@@ -65,6 +66,12 @@ function assertValidDefinition(definition) {
65
66
  if (typeof definition.name !== "string" || definition.name.trim() === "") throw new InvalidJobDefinitionError("defineJob() requires a non-empty `name`.");
66
67
  if (typeof definition.handle !== "function") throw new InvalidJobDefinitionError(`defineJob("${definition.name}") requires a \`handle(payload, ctx)\` function.`);
67
68
  if (definition.attempts !== void 0 && !(Number.isInteger(definition.attempts) && definition.attempts >= 1)) throw new InvalidJobDefinitionError(`defineJob("${definition.name}"): \`attempts\` must be an integer >= 1, got ${definition.attempts}.`);
69
+ if (definition.timeout !== void 0) try {
70
+ resolveTimeout(definition.timeout);
71
+ } catch (error) {
72
+ throw new InvalidJobDefinitionError(`defineJob("${definition.name}"): ${error instanceof Error ? error.message : String(error)}`);
73
+ }
74
+ assertFailOnTimeout(definition.failOnTimeout);
68
75
  }
69
76
 
70
77
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"define-job.mjs","names":[],"sources":["../../../../../../../packages/queue/src/define-job.ts"],"sourcesContent":["import { getDispatchInterceptor } from \"./dispatch-interceptor\";\nimport { InvalidJobDefinitionError } from \"./errors\";\nimport { getQueueContext, wrapPayload } from \"./queue-context\";\nimport { queueOf, registerJob, type RegisteredJob } from \"./job-registry\";\nimport { getQueueDriver } from \"./queue-driver\";\nimport { resolveJobOptions } from \"./job-options\";\nimport type { JobDefinition, JobSnapshot, QueueJob } from \"./types\";\n\n/**\n * Define a background job.\n *\n * The definition is registered by name so any worker in the process can run\n * it; the returned object dispatches it with a typed payload.\n *\n * @example\n * export const sendInvoice = defineJob({\n * name: \"invoices.send\",\n * attempts: 5,\n * backoff: { type: \"exponential\", delay: 2000 },\n * async handle(payload: { invoiceId: string }, ctx) {\n * await ctx.progress(50);\n * },\n * });\n *\n * await sendInvoice.dispatch({ invoiceId: \"42\" }, { delay: \"10m\", priority: 1 });\n */\nexport function defineJob<TPayload, TResult = unknown>(\n definition: JobDefinition<TPayload, TResult>,\n): QueueJob<TPayload, TResult> {\n assertValidDefinition(definition);\n registerJob(definition as RegisteredJob);\n\n return {\n name: definition.name,\n get queue() {\n return queueOf(definition);\n },\n async dispatch(payload, options = {}) {\n const queueName = queueOf(definition);\n const interceptor = getDispatchInterceptor();\n\n // A test fake stands in for the broker: record, never touch Redis.\n if (interceptor) {\n return interceptor.dispatch({ name: definition.name, queue: queueName, payload, options });\n }\n\n // Capture synchronously, in the caller's async context.\n const captured = definition.context === false ? undefined : getQueueContext()?.capture();\n return getQueueDriver().dispatch({\n name: definition.name,\n queue: queueName,\n data: wrapPayload(payload, captured),\n options: resolveJobOptions(definition, options),\n });\n },\n async find(id) {\n const interceptor = getDispatchInterceptor();\n\n if (interceptor) {\n const found = await interceptor.find?.({\n name: definition.name,\n queue: queueOf(definition),\n id,\n });\n\n return found as JobSnapshot<TPayload, TResult> | undefined;\n }\n\n const snapshot = await getQueueDriver().find(queueOf(definition), id);\n return snapshot?.name === definition.name\n ? (snapshot as JobSnapshot<TPayload, TResult>)\n : undefined;\n },\n };\n}\n\nfunction assertValidDefinition(definition: JobDefinition<unknown, unknown>): void {\n if (typeof definition.name !== \"string\" || definition.name.trim() === \"\") {\n throw new InvalidJobDefinitionError(\"defineJob() requires a non-empty `name`.\");\n }\n\n if (typeof definition.handle !== \"function\") {\n throw new InvalidJobDefinitionError(\n `defineJob(\"${definition.name}\") requires a \\`handle(payload, ctx)\\` function.`,\n );\n }\n\n if (\n definition.attempts !== undefined &&\n !(Number.isInteger(definition.attempts) && definition.attempts >= 1)\n ) {\n throw new InvalidJobDefinitionError(\n `defineJob(\"${definition.name}\"): \\`attempts\\` must be an integer >= 1, got ${definition.attempts}.`,\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,UACd,YAC6B;CAC7B,sBAAsB,UAAU;CAChC,YAAY,UAA2B;CAEvC,OAAO;EACL,MAAM,WAAW;EACjB,IAAI,QAAQ;GACV,OAAO,QAAQ,UAAU;EAC3B;EACA,MAAM,SAAS,SAAS,UAAU,CAAC,GAAG;GACpC,MAAM,YAAY,QAAQ,UAAU;GACpC,MAAM,cAAc,uBAAuB;GAG3C,IAAI,aACF,OAAO,YAAY,SAAS;IAAE,MAAM,WAAW;IAAM,OAAO;IAAW;IAAS;GAAQ,CAAC;GAI3F,MAAM,WAAW,WAAW,YAAY,QAAQ,SAAY,gBAAgB,CAAC,EAAE,QAAQ;GACvF,OAAO,eAAe,CAAC,CAAC,SAAS;IAC/B,MAAM,WAAW;IACjB,OAAO;IACP,MAAM,YAAY,SAAS,QAAQ;IACnC,SAAS,kBAAkB,YAAY,OAAO;GAChD,CAAC;EACH;EACA,MAAM,KAAK,IAAI;GACb,MAAM,cAAc,uBAAuB;GAE3C,IAAI,aAOF,OAAO,MANa,YAAY,OAAO;IACrC,MAAM,WAAW;IACjB,OAAO,QAAQ,UAAU;IACzB;GACF,CAAC;GAKH,MAAM,WAAW,MAAM,eAAe,CAAC,CAAC,KAAK,QAAQ,UAAU,GAAG,EAAE;GACpE,OAAO,UAAU,SAAS,WAAW,OAChC,WACD;EACN;CACF;AACF;AAEA,SAAS,sBAAsB,YAAmD;CAChF,IAAI,OAAO,WAAW,SAAS,YAAY,WAAW,KAAK,KAAK,MAAM,IACpE,MAAM,IAAI,0BAA0B,0CAA0C;CAGhF,IAAI,OAAO,WAAW,WAAW,YAC/B,MAAM,IAAI,0BACR,cAAc,WAAW,KAAK,iDAChC;CAGF,IACE,WAAW,aAAa,UACxB,EAAE,OAAO,UAAU,WAAW,QAAQ,KAAK,WAAW,YAAY,IAElE,MAAM,IAAI,0BACR,cAAc,WAAW,KAAK,gDAAgD,WAAW,SAAS,EACpG;AAEJ"}
1
+ {"version":3,"file":"define-job.mjs","names":[],"sources":["../../../../../../../packages/queue/src/define-job.ts"],"sourcesContent":["import { getDispatchInterceptor } from \"./dispatch-interceptor\";\nimport { InvalidJobDefinitionError } from \"./errors\";\nimport { getQueueContext, wrapPayload } from \"./queue-context\";\nimport { queueOf, registerJob, type RegisteredJob } from \"./job-registry\";\nimport { getQueueDriver } from \"./queue-driver\";\nimport { resolveJobOptions } from \"./job-options\";\nimport { assertFailOnTimeout, resolveTimeout } from \"./job-timeout\";\nimport type { JobDefinition, JobSnapshot, QueueJob } from \"./types\";\n\n/**\n * Define a background job.\n *\n * The definition is registered by name so any worker in the process can run\n * it; the returned object dispatches it with a typed payload.\n *\n * @example\n * export const sendInvoice = defineJob({\n * name: \"invoices.send\",\n * attempts: 5,\n * backoff: { type: \"exponential\", delay: 2000 },\n * async handle(payload: { invoiceId: string }, ctx) {\n * await ctx.progress(50);\n * },\n * });\n *\n * await sendInvoice.dispatch({ invoiceId: \"42\" }, { delay: \"10m\", priority: 1 });\n */\nexport function defineJob<TPayload, TResult = unknown>(\n definition: JobDefinition<TPayload, TResult>,\n): QueueJob<TPayload, TResult> {\n assertValidDefinition(definition);\n registerJob(definition as RegisteredJob);\n\n return {\n name: definition.name,\n get queue() {\n return queueOf(definition);\n },\n async dispatch(payload, options = {}) {\n const queueName = queueOf(definition);\n const interceptor = getDispatchInterceptor();\n\n // A test fake stands in for the broker: record, never touch Redis.\n if (interceptor) {\n return interceptor.dispatch({ name: definition.name, queue: queueName, payload, options });\n }\n\n // Capture synchronously, in the caller's async context.\n const captured = definition.context === false ? undefined : getQueueContext()?.capture();\n return getQueueDriver().dispatch({\n name: definition.name,\n queue: queueName,\n data: wrapPayload(payload, captured),\n options: resolveJobOptions(definition, options),\n });\n },\n async find(id) {\n const interceptor = getDispatchInterceptor();\n\n if (interceptor) {\n const found = await interceptor.find?.({\n name: definition.name,\n queue: queueOf(definition),\n id,\n });\n\n return found as JobSnapshot<TPayload, TResult> | undefined;\n }\n\n const snapshot = await getQueueDriver().find(queueOf(definition), id);\n return snapshot?.name === definition.name\n ? (snapshot as JobSnapshot<TPayload, TResult>)\n : undefined;\n },\n };\n}\n\nfunction assertValidDefinition(definition: JobDefinition<unknown, unknown>): void {\n if (typeof definition.name !== \"string\" || definition.name.trim() === \"\") {\n throw new InvalidJobDefinitionError(\"defineJob() requires a non-empty `name`.\");\n }\n\n if (typeof definition.handle !== \"function\") {\n throw new InvalidJobDefinitionError(\n `defineJob(\"${definition.name}\") requires a \\`handle(payload, ctx)\\` function.`,\n );\n }\n\n if (\n definition.attempts !== undefined &&\n !(Number.isInteger(definition.attempts) && definition.attempts >= 1)\n ) {\n throw new InvalidJobDefinitionError(\n `defineJob(\"${definition.name}\"): \\`attempts\\` must be an integer >= 1, got ${definition.attempts}.`,\n );\n }\n\n if (definition.timeout !== undefined) {\n try {\n resolveTimeout(definition.timeout);\n } catch (error) {\n throw new InvalidJobDefinitionError(\n `defineJob(\"${definition.name}\"): ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n assertFailOnTimeout(definition.failOnTimeout);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,UACd,YAC6B;CAC7B,sBAAsB,UAAU;CAChC,YAAY,UAA2B;CAEvC,OAAO;EACL,MAAM,WAAW;EACjB,IAAI,QAAQ;GACV,OAAO,QAAQ,UAAU;EAC3B;EACA,MAAM,SAAS,SAAS,UAAU,CAAC,GAAG;GACpC,MAAM,YAAY,QAAQ,UAAU;GACpC,MAAM,cAAc,uBAAuB;GAG3C,IAAI,aACF,OAAO,YAAY,SAAS;IAAE,MAAM,WAAW;IAAM,OAAO;IAAW;IAAS;GAAQ,CAAC;GAI3F,MAAM,WAAW,WAAW,YAAY,QAAQ,SAAY,gBAAgB,CAAC,EAAE,QAAQ;GACvF,OAAO,eAAe,CAAC,CAAC,SAAS;IAC/B,MAAM,WAAW;IACjB,OAAO;IACP,MAAM,YAAY,SAAS,QAAQ;IACnC,SAAS,kBAAkB,YAAY,OAAO;GAChD,CAAC;EACH;EACA,MAAM,KAAK,IAAI;GACb,MAAM,cAAc,uBAAuB;GAE3C,IAAI,aAOF,OAAO,MANa,YAAY,OAAO;IACrC,MAAM,WAAW;IACjB,OAAO,QAAQ,UAAU;IACzB;GACF,CAAC;GAKH,MAAM,WAAW,MAAM,eAAe,CAAC,CAAC,KAAK,QAAQ,UAAU,GAAG,EAAE;GACpE,OAAO,UAAU,SAAS,WAAW,OAChC,WACD;EACN;CACF;AACF;AAEA,SAAS,sBAAsB,YAAmD;CAChF,IAAI,OAAO,WAAW,SAAS,YAAY,WAAW,KAAK,KAAK,MAAM,IACpE,MAAM,IAAI,0BAA0B,0CAA0C;CAGhF,IAAI,OAAO,WAAW,WAAW,YAC/B,MAAM,IAAI,0BACR,cAAc,WAAW,KAAK,iDAChC;CAGF,IACE,WAAW,aAAa,UACxB,EAAE,OAAO,UAAU,WAAW,QAAQ,KAAK,WAAW,YAAY,IAElE,MAAM,IAAI,0BACR,cAAc,WAAW,KAAK,gDAAgD,WAAW,SAAS,EACpG;CAGF,IAAI,WAAW,YAAY,QACzB,IAAI;EACF,eAAe,WAAW,OAAO;CACnC,SAAS,OAAO;EACd,MAAM,IAAI,0BACR,cAAc,WAAW,KAAK,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAC3F;CACF;CAGF,oBAAoB,WAAW,aAAa;AAC9C"}
@@ -1,5 +1,6 @@
1
- import { FailedJobNotFoundError, UnrecoverableJobError } from "../errors.mjs";
1
+ import { FailedJobNotFoundError, 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 { jsonCopy } from "./local-store.mjs";
5
6
  import { log } from "@warlock.js/logger";
@@ -193,6 +194,7 @@ var LocalDriver = class {
193
194
  }
194
195
  async execute(entry) {
195
196
  const { job, token, controller } = entry;
197
+ const abort = createJobAbort(job.name, job.options, controller.signal);
196
198
  try {
197
199
  const definition = findRegisteredJob(job.name);
198
200
  if (!definition) throw new UnrecoverableJobError(`No job named "${job.name}" is defined in this process. Import its definition in the worker.`);
@@ -204,7 +206,7 @@ var LocalDriver = class {
204
206
  attempt: job.attemptsMade + 1,
205
207
  maxAttempts: job.options.attempts ?? 1,
206
208
  context: captured,
207
- signal: controller.signal,
209
+ signal: abort.signal,
208
210
  progress: async (value) => {
209
211
  if (!this.mutate(job, token, (current) => {
210
212
  current.progress = jsonCopy(value);
@@ -218,7 +220,7 @@ var LocalDriver = class {
218
220
  };
219
221
  const run = async () => definition.handle(payload, context);
220
222
  const queueContext = getQueueContext();
221
- const result = jsonCopy(captured !== void 0 && queueContext ? await queueContext.restore(captured, run) : await run());
223
+ const result = jsonCopy(await abort.guard(async () => captured !== void 0 && queueContext ? queueContext.restore(captured, run) : run()));
222
224
  this.mutate(job, token, (current) => {
223
225
  current.attemptsMade++;
224
226
  current.result = result;
@@ -234,7 +236,7 @@ var LocalDriver = class {
234
236
  current.stacktrace.push(error instanceof Error ? error.stack ?? error.message : String(error));
235
237
  current.leaseUntil = 0;
236
238
  delete current.token;
237
- if (error instanceof UnrecoverableJobError || current.attemptsMade >= (current.options.attempts ?? 1)) {
239
+ if (error instanceof UnrecoverableJobError || error instanceof JobTimeoutError && error.failOnTimeout || current.attemptsMade >= (current.options.attempts ?? 1)) {
238
240
  current.state = "failed";
239
241
  current.finishedAt = Date.now();
240
242
  } else {
@@ -245,6 +247,8 @@ var LocalDriver = class {
245
247
  current.state = delay > 0 ? "delayed" : current.priority ? "prioritized" : "waiting";
246
248
  }
247
249
  }, true);
250
+ } finally {
251
+ abort.dispose();
248
252
  }
249
253
  }
250
254
  /** Fencing: expired owners cannot renew, finish, or overwrite a new owner's job. */
@@ -292,6 +296,7 @@ function positiveInteger(value, name, minimum = 1) {
292
296
  function validateOptions({ options }) {
293
297
  positiveInteger(options.attempts ?? 1, "attempts");
294
298
  if (options.priority !== void 0 && (!Number.isInteger(options.priority) || options.priority < 0 || options.priority > 2097152)) throw new Error("priority must be an integer between 0 and 2097152.");
299
+ if (options.timeout !== void 0 && !(Number.isFinite(options.timeout) && options.timeout > 0)) throw new Error("timeout must be a positive finite number of milliseconds.");
295
300
  const backoff = options.backoff;
296
301
  const delay = typeof backoff === "number" ? backoff : backoff?.delay ?? 0;
297
302
  if (!Number.isFinite(delay) || delay < 0 || typeof backoff === "object" && !["fixed", "exponential"].includes(backoff.type)) throw new Error("backoff must have a non-negative finite delay and fixed or exponential type.");