@warlock.js/queue 5.13.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +121 -0
  4. package/cjs/define-job-DideGKQK.cjs +468 -0
  5. package/cjs/define-job-DideGKQK.cjs.map +1 -0
  6. package/cjs/index.cjs +180 -0
  7. package/cjs/index.cjs.map +1 -0
  8. package/cjs/notifications/index.cjs +68 -0
  9. package/cjs/notifications/index.cjs.map +1 -0
  10. package/esm/config.d.mts +21 -0
  11. package/esm/config.d.mts.map +1 -0
  12. package/esm/config.mjs +32 -0
  13. package/esm/config.mjs.map +1 -0
  14. package/esm/dashboard.d.mts +47 -0
  15. package/esm/dashboard.d.mts.map +1 -0
  16. package/esm/dashboard.mjs +61 -0
  17. package/esm/dashboard.mjs.map +1 -0
  18. package/esm/define-job.d.mts +24 -0
  19. package/esm/define-job.d.mts.map +1 -0
  20. package/esm/define-job.mjs +102 -0
  21. package/esm/define-job.mjs.map +1 -0
  22. package/esm/duration.d.mts +11 -0
  23. package/esm/duration.d.mts.map +1 -0
  24. package/esm/duration.mjs +28 -0
  25. package/esm/duration.mjs.map +1 -0
  26. package/esm/errors.d.mts +36 -0
  27. package/esm/errors.d.mts.map +1 -0
  28. package/esm/errors.mjs +55 -0
  29. package/esm/errors.mjs.map +1 -0
  30. package/esm/failed-jobs.d.mts +23 -0
  31. package/esm/failed-jobs.d.mts.map +1 -0
  32. package/esm/failed-jobs.mjs +39 -0
  33. package/esm/failed-jobs.mjs.map +1 -0
  34. package/esm/index.d.mts +10 -0
  35. package/esm/index.mjs +10 -0
  36. package/esm/job-registry.mjs +39 -0
  37. package/esm/job-registry.mjs.map +1 -0
  38. package/esm/notifications/index.d.mts +2 -0
  39. package/esm/notifications/index.mjs +3 -0
  40. package/esm/notifications/queue-notification-dispatcher.d.mts +34 -0
  41. package/esm/notifications/queue-notification-dispatcher.d.mts.map +1 -0
  42. package/esm/notifications/queue-notification-dispatcher.mjs +67 -0
  43. package/esm/notifications/queue-notification-dispatcher.mjs.map +1 -0
  44. package/esm/process-job.mjs +32 -0
  45. package/esm/process-job.mjs.map +1 -0
  46. package/esm/queue-connector.d.mts +36 -0
  47. package/esm/queue-connector.d.mts.map +1 -0
  48. package/esm/queue-connector.mjs +73 -0
  49. package/esm/queue-connector.mjs.map +1 -0
  50. package/esm/queue-manager.d.mts +36 -0
  51. package/esm/queue-manager.d.mts.map +1 -0
  52. package/esm/queue-manager.mjs +109 -0
  53. package/esm/queue-manager.mjs.map +1 -0
  54. package/esm/types.d.mts +149 -0
  55. package/esm/types.d.mts.map +1 -0
  56. package/llms-full.txt +229 -0
  57. package/llms.txt +13 -0
  58. package/package.json +70 -0
  59. package/skills/configure-queue/SKILL.md +55 -0
  60. package/skills/define-jobs/SKILL.md +51 -0
  61. package/skills/manage-failed-jobs/SKILL.md +38 -0
  62. package/skills/overview/SKILL.md +26 -0
  63. package/skills/queue-notifications/SKILL.md +33 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog — @warlock.js/queue
2
+
3
+ All notable changes to `@warlock.js/queue` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
6
+
7
+ ## 5.13.0 - 2026-09-17
8
+
9
+ ### Added
10
+
11
+ - New package: durable background jobs on BullMQ + Redis. Redis is required.
12
+ - `defineJob({ name, queue, attempts, backoff, removeOnComplete, removeOnFail, handle(payload, ctx) })` returns a typed job with `dispatch(payload, { delay, priority, jobId, attempts, backoff })` and `find(id)`.
13
+ - Job context: `id`, `name`, `queue`, `attempt`, `maxAttempts`, `progress(value)`, `log(line)`.
14
+ - `failedJobs({ queue, start, end })` and `retryFailedJob(id, { queue })`.
15
+ - `queueConnector()` for `warlock.config.ts > connectors`: reads the `queue` config key, starts workers in the app process (turn off with `workers.enabled: false`), and on shutdown waits for active jobs up to `workers.shutdownTimeout` before closing.
16
+ - `setQueueConfig`, `startWorkers`, `closeQueue` for scripts, tests and worker-only processes.
17
+ - `@warlock.js/queue/notifications`: `queueNotificationDispatcher()` sends `@warlock.js/notifications` `.queue()` deliveries through BullMQ, with retries and `delay` support.
18
+ - `queueDashboard(server, { basePath })`: mounts bull-board on Warlock's Fastify server. `@bull-board/api` and `@bull-board/fastify` are optional peers, loaded only when it is called; a missing one throws `QueueDashboardDependencyError`.
19
+
20
+ ### Fixed
21
+
22
+ - Portable `typecheck` script: runs against this package's own `typescript` devDependency instead of relying on a hoisted binary from elsewhere in the workspace.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Hassan Zohdy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @warlock.js/queue
2
+
3
+ Durable background jobs for Warlock.js, built on [BullMQ](https://docs.bullmq.io).
4
+
5
+ - `defineJob({ name, handle, attempts, backoff })` → a typed job with `.dispatch(payload, { delay, priority, jobId })`
6
+ - Retries with fixed or exponential backoff, delays, priorities, progress
7
+ - Failed-job listing and retry (`failedJobs()`, `retryFailedJob()`)
8
+ - Workers run inside the app process by default and shut down gracefully (active jobs finish first, with a time limit)
9
+ - A BullMQ backend for `@warlock.js/notifications` `.queue()`
10
+ - An optional bull-board dashboard
11
+
12
+ ## Requirements
13
+
14
+ **Redis is required.** BullMQ stores every job in Redis (5.0 or newer; any Redis-compatible server BullMQ supports, such as Valkey or Dragonfly, also works). This package does not start or bundle Redis.
15
+
16
+ ## Not the same as core's `Queue`
17
+
18
+ `@warlock.js/core` exports a `Queue` class (`core/src/utils/queue.ts`). That one is an **in-memory batcher inside one process**: it collects items and flushes them when a size or time limit is reached. Nothing is stored; items are lost when the process exits; there are no retries.
19
+
20
+ `@warlock.js/queue` is for **durable jobs**: they are stored in Redis, survive restarts, retry on failure, and can be processed by another process.
21
+
22
+ ## Install
23
+
24
+ ```sh
25
+ npm install @warlock.js/queue
26
+ ```
27
+
28
+ ## Configure
29
+
30
+ ```ts title="src/config/queue.ts"
31
+ import type { QueueConfig } from "@warlock.js/queue";
32
+
33
+ const queueConfig: QueueConfig = {
34
+ connection: { host: "127.0.0.1", port: 6379 },
35
+ prefix: "my-app",
36
+ defaultJobOptions: { attempts: 3, backoff: { type: "exponential", delay: 1000 } },
37
+ workers: { enabled: true, concurrency: 5, shutdownTimeout: 30_000 },
38
+ };
39
+
40
+ export default queueConfig;
41
+ ```
42
+
43
+ ```ts title="warlock.config.ts"
44
+ import { defineConfig } from "@warlock.js/core";
45
+ import { queueConnector } from "@warlock.js/queue";
46
+
47
+ export default defineConfig({
48
+ connectors: [queueConnector()],
49
+ });
50
+ ```
51
+
52
+ The connector starts after your app code is loaded, so every `defineJob` is registered before workers start. On shutdown (SIGINT/SIGTERM) it stops the workers, waits up to `shutdownTimeout` ms for running jobs, then closes the Redis connections.
53
+
54
+ Set `workers.enabled: false` in a process that should only dispatch jobs.
55
+
56
+ ## Define and dispatch
57
+
58
+ ```ts
59
+ import { defineJob } from "@warlock.js/queue";
60
+
61
+ export const sendInvoice = defineJob({
62
+ name: "invoices.send",
63
+ attempts: 5,
64
+ backoff: { type: "exponential", delay: 2000 },
65
+ async handle(payload: { invoiceId: string }, ctx) {
66
+ await ctx.progress(10);
67
+ // ... work ...
68
+ await ctx.progress(100);
69
+ return { sent: true };
70
+ },
71
+ });
72
+
73
+ await sendInvoice.dispatch({ invoiceId: "42" });
74
+ await sendInvoice.dispatch({ invoiceId: "43" }, { delay: "10m", priority: 1, jobId: "invoice:43" });
75
+
76
+ const snapshot = await sendInvoice.find("invoice:43"); // state, progress, result, failedReason
77
+ ```
78
+
79
+ ## Failed jobs
80
+
81
+ ```ts
82
+ import { failedJobs, retryFailedJob } from "@warlock.js/queue";
83
+
84
+ for (const job of await failedJobs()) {
85
+ console.log(job.name, job.failedReason, job.attemptsMade);
86
+ }
87
+
88
+ await retryFailedJob("invoice:43");
89
+ ```
90
+
91
+ ## Notifications
92
+
93
+ ```ts title="src/config/notifications.ts"
94
+ import { queueNotificationDispatcher } from "@warlock.js/queue/notifications";
95
+
96
+ const config: NotificationConfig = {
97
+ channels: { mail: mailChannel() },
98
+ queue: queueNotificationDispatcher({ attempts: 3 }),
99
+ };
100
+ ```
101
+
102
+ `.queue()` notifications now go through BullMQ. `SendOptions.delay` is honoured.
103
+
104
+ ## Dashboard (optional)
105
+
106
+ ```sh
107
+ npm install @bull-board/api @bull-board/fastify
108
+ ```
109
+
110
+ ```ts
111
+ import { getHttpServer } from "@warlock.js/core";
112
+ import { queueDashboard } from "@warlock.js/queue";
113
+
114
+ await queueDashboard(getHttpServer(), { basePath: "/admin/queues" });
115
+ ```
116
+
117
+ The bull-board packages are loaded only when `queueDashboard` is called. If they are missing it throws `QueueDashboardDependencyError` with the install command. Protect the route yourself; the dashboard can retry and delete jobs.
118
+
119
+ ## License
120
+
121
+ MIT
@@ -0,0 +1,468 @@
1
+ let _warlock_js_logger = require("@warlock.js/logger");
2
+ let bullmq = require("bullmq");
3
+
4
+ //#region ../queue/src/errors.ts
5
+ /**
6
+ * Thrown when the queue is used before `setQueueConfig` (or the queue
7
+ * connector) supplied a configuration.
8
+ */
9
+ var QueueNotConfiguredError = class extends Error {
10
+ constructor() {
11
+ super("@warlock.js/queue is not configured. Add src/config/queue.ts exporting a QueueConfig and register queueConnector() in warlock.config.ts > connectors, or call setQueueConfig() yourself.");
12
+ this.name = "QueueNotConfiguredError";
13
+ }
14
+ };
15
+ /**
16
+ * Thrown for a malformed duration such as `"10 minutes"`.
17
+ */
18
+ var InvalidDurationError = class extends Error {
19
+ constructor(value) {
20
+ super(`Invalid duration ${JSON.stringify(value)}: expected milliseconds as a non-negative number or a string like "500ms", "30s", "10m", "2h", "1d".`);
21
+ this.name = "InvalidDurationError";
22
+ }
23
+ };
24
+ /**
25
+ * Thrown by `defineJob` for an invalid definition.
26
+ */
27
+ var InvalidJobDefinitionError = class extends Error {
28
+ constructor(message) {
29
+ super(message);
30
+ this.name = "InvalidJobDefinitionError";
31
+ }
32
+ };
33
+ /**
34
+ * Thrown by `retryFailedJob` when no failed job has the given id.
35
+ */
36
+ var FailedJobNotFoundError = class extends Error {
37
+ constructor(id, queue) {
38
+ super(`No failed job with id "${id}" on queue "${queue}".`);
39
+ this.name = "FailedJobNotFoundError";
40
+ }
41
+ };
42
+ /**
43
+ * Thrown by `queueDashboard` when an optional bull-board package is not
44
+ * installed.
45
+ */
46
+ var QueueDashboardDependencyError = class extends Error {
47
+ constructor(missing) {
48
+ super(`The queue dashboard needs the optional package "${missing}", which is not installed.\nInstall both bull-board packages:
49
+
50
+ npm install @bull-board/api @bull-board/fastify
51
+ `);
52
+ this.name = "QueueDashboardDependencyError";
53
+ }
54
+ };
55
+
56
+ //#endregion
57
+ //#region ../queue/src/config.ts
58
+ let activeConfig;
59
+ /**
60
+ * Set the active queue configuration. In a Warlock app the queue connector
61
+ * calls this at boot with `src/config/queue.ts`; scripts and tests may call
62
+ * it directly. Replaces (does not merge) any previous configuration.
63
+ */
64
+ function setQueueConfig(config) {
65
+ activeConfig = config;
66
+ }
67
+ /**
68
+ * The active queue configuration. Throws {@link QueueNotConfiguredError}
69
+ * when none was set.
70
+ */
71
+ function getQueueConfig() {
72
+ if (!activeConfig) throw new QueueNotConfiguredError();
73
+ return activeConfig;
74
+ }
75
+ /** Forget the active configuration. */
76
+ function resetQueueConfig() {
77
+ activeConfig = void 0;
78
+ }
79
+ /** The queue a job runs on when it names none. */
80
+ function defaultQueueName() {
81
+ return activeConfig?.defaultQueue ?? "default";
82
+ }
83
+
84
+ //#endregion
85
+ //#region ../queue/src/job-registry.ts
86
+ const jobs = /* @__PURE__ */ new Map();
87
+ const listeners = /* @__PURE__ */ new Set();
88
+ /**
89
+ * Register a definition under its name.
90
+ *
91
+ * Re-registering a name REPLACES the previous definition: in development a
92
+ * job module is re-evaluated on every reload, and refusing the second
93
+ * evaluation would break the reload. Job names must therefore be unique
94
+ * across the app — two different modules using one name leave only the
95
+ * later handler active.
96
+ */
97
+ function registerJob(job) {
98
+ jobs.set(job.name, job);
99
+ for (const listener of listeners) listener(job);
100
+ }
101
+ /** The definition registered under `name`, if any. */
102
+ function findRegisteredJob(name) {
103
+ return jobs.get(name);
104
+ }
105
+ /** Every registered definition. */
106
+ function registeredJobs() {
107
+ return [...jobs.values()];
108
+ }
109
+ /** The queue a definition runs on, resolved against the active config. */
110
+ function queueOf(job) {
111
+ return job.queue ?? defaultQueueName();
112
+ }
113
+ /** Be told whenever a job is registered. Returns an unsubscribe function. */
114
+ function onJobRegistered(listener) {
115
+ listeners.add(listener);
116
+ return () => listeners.delete(listener);
117
+ }
118
+
119
+ //#endregion
120
+ //#region ../queue/src/process-job.ts
121
+ /**
122
+ * The single BullMQ processor every worker runs: route the job to the
123
+ * handler registered under its name.
124
+ *
125
+ * A name with no handler in this process fails with `UnrecoverableError` —
126
+ * retrying cannot make a missing definition appear, so it must not burn
127
+ * through its attempts.
128
+ */
129
+ async function processJob(job) {
130
+ const definition = findRegisteredJob(job.name);
131
+ if (!definition) throw new bullmq.UnrecoverableError(`No job named "${job.name}" is defined in this process. Make sure the module that calls defineJob() is imported by the worker process.`);
132
+ const context = {
133
+ id: String(job.id),
134
+ name: job.name,
135
+ queue: job.queueName,
136
+ attempt: job.attemptsMade + 1,
137
+ maxAttempts: job.opts.attempts ?? 1,
138
+ progress: (value) => job.updateProgress(value),
139
+ log: async (line) => {
140
+ await job.log(line);
141
+ }
142
+ };
143
+ return definition.handle(job.data, context);
144
+ }
145
+
146
+ //#endregion
147
+ //#region ../queue/src/queue-manager.ts
148
+ const DEFAULT_SHUTDOWN_TIMEOUT = 3e4;
149
+ const queues = /* @__PURE__ */ new Map();
150
+ const workers = /* @__PURE__ */ new Map();
151
+ let stopListening;
152
+ /**
153
+ * The BullMQ queue for `name`, created on first use with the configured
154
+ * connection and prefix.
155
+ */
156
+ function getQueue(name) {
157
+ let queue = queues.get(name);
158
+ if (!queue) {
159
+ const config = getQueueConfig();
160
+ queue = new bullmq.Queue(name, {
161
+ connection: config.connection,
162
+ prefix: config.prefix ?? "warlock"
163
+ });
164
+ queue.on("error", (error) => {
165
+ _warlock_js_logger.log.error("queue", "connection", error);
166
+ });
167
+ queues.set(name, queue);
168
+ }
169
+ return queue;
170
+ }
171
+ /**
172
+ * Start one worker per queue that has a registered job, and keep starting
173
+ * workers for queues whose first job is defined later.
174
+ *
175
+ * A no-op returning `[]` when `workers.enabled` is `false`. Calling it again
176
+ * while workers run starts only the missing ones.
177
+ *
178
+ * @returns the queue names that now have a worker in this process.
179
+ */
180
+ async function startWorkers() {
181
+ if (getQueueConfig().workers?.enabled === false) return [];
182
+ for (const job of registeredJobs()) ensureWorker(queueOf(job));
183
+ stopListening ??= onJobRegistered((job) => {
184
+ ensureWorker(queueOf(job));
185
+ });
186
+ await Promise.all([...workers.values()].map((worker) => worker.waitUntilReady()));
187
+ return [...workers.keys()];
188
+ }
189
+ /** The queue names with a running worker in this process. */
190
+ function runningWorkers() {
191
+ return [...workers.keys()];
192
+ }
193
+ function ensureWorker(queueName) {
194
+ if (workers.has(queueName)) return;
195
+ const config = getQueueConfig();
196
+ const worker = new bullmq.Worker(queueName, processJob, {
197
+ connection: config.connection,
198
+ prefix: config.prefix ?? "warlock",
199
+ concurrency: config.workers?.concurrency ?? 1
200
+ });
201
+ worker.on("error", (error) => {
202
+ _warlock_js_logger.log.error("queue", "worker", error);
203
+ });
204
+ worker.on("failed", (job, error) => {
205
+ _warlock_js_logger.log.error("queue", "job.failed", `${job?.name ?? "unknown"} (${job?.id ?? "?"}): ${error.message}`);
206
+ });
207
+ workers.set(queueName, worker);
208
+ }
209
+ /**
210
+ * Graceful shutdown: stop workers taking new jobs and wait for active ones
211
+ * (bounded by `timeout`, then force-close), then close every queue
212
+ * connection. Safe to call when nothing was started, and more than once.
213
+ */
214
+ async function closeQueue(options = {}) {
215
+ stopListening?.();
216
+ stopListening = void 0;
217
+ const timeout = options.timeout ?? configuredShutdownTimeout();
218
+ const closingWorkers = [...workers.values()];
219
+ workers.clear();
220
+ await Promise.all(closingWorkers.map((worker) => closeWorker(worker, timeout)));
221
+ const closingQueues = [...queues.values()];
222
+ queues.clear();
223
+ await Promise.all(closingQueues.map((queue) => queue.close()));
224
+ }
225
+ function configuredShutdownTimeout() {
226
+ try {
227
+ return getQueueConfig().workers?.shutdownTimeout ?? DEFAULT_SHUTDOWN_TIMEOUT;
228
+ } catch {
229
+ return DEFAULT_SHUTDOWN_TIMEOUT;
230
+ }
231
+ }
232
+ async function closeWorker(worker, timeout) {
233
+ let timer;
234
+ const timedOut = new Promise((resolve) => {
235
+ timer = setTimeout(() => resolve("timeout"), timeout);
236
+ });
237
+ const closing = worker.close().then(() => "closed");
238
+ closing.catch(() => void 0);
239
+ const outcome = await Promise.race([closing, timedOut]);
240
+ clearTimeout(timer);
241
+ if (outcome === "timeout") {
242
+ _warlock_js_logger.log.warn("queue", "shutdown", `Worker for "${worker.name}" still had active jobs after ${timeout}ms; disconnecting. Those jobs are retried once their lock expires.`);
243
+ await worker.disconnect();
244
+ }
245
+ }
246
+
247
+ //#endregion
248
+ //#region ../queue/src/duration.ts
249
+ const UNIT_MILLISECONDS = {
250
+ ms: 1,
251
+ s: 1e3,
252
+ m: 6e4,
253
+ h: 36e5,
254
+ d: 864e5
255
+ };
256
+ const DURATION_PATTERN = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/;
257
+ /**
258
+ * Convert a {@link Duration} to milliseconds. Numbers are already
259
+ * milliseconds. Anything else is rejected loudly rather than guessed at.
260
+ */
261
+ function toMilliseconds(value) {
262
+ if (typeof value === "number") {
263
+ if (!Number.isFinite(value) || value < 0) throw new InvalidDurationError(value);
264
+ return Math.round(value);
265
+ }
266
+ const match = typeof value === "string" ? DURATION_PATTERN.exec(value.trim()) : null;
267
+ if (!match) throw new InvalidDurationError(value);
268
+ return Math.round(Number(match[1]) * UNIT_MILLISECONDS[match[2]]);
269
+ }
270
+
271
+ //#endregion
272
+ //#region ../queue/src/define-job.ts
273
+ /**
274
+ * Define a background job.
275
+ *
276
+ * The definition is registered by name so any worker in the process can run
277
+ * it; the returned object dispatches it with a typed payload.
278
+ *
279
+ * @example
280
+ * export const sendInvoice = defineJob({
281
+ * name: "invoices.send",
282
+ * attempts: 5,
283
+ * backoff: { type: "exponential", delay: 2000 },
284
+ * async handle(payload: { invoiceId: string }, ctx) {
285
+ * await ctx.progress(50);
286
+ * },
287
+ * });
288
+ *
289
+ * await sendInvoice.dispatch({ invoiceId: "42" }, { delay: "10m", priority: 1 });
290
+ */
291
+ function defineJob(definition) {
292
+ assertValidDefinition(definition);
293
+ registerJob(definition);
294
+ return {
295
+ name: definition.name,
296
+ get queue() {
297
+ return queueOf(definition);
298
+ },
299
+ async dispatch(payload, options = {}) {
300
+ const queueName = queueOf(definition);
301
+ const job = await getQueue(queueName).add(definition.name, payload, toBullJobOptions(definition, options));
302
+ return {
303
+ id: String(job.id),
304
+ name: definition.name,
305
+ queue: queueName
306
+ };
307
+ },
308
+ async find(id) {
309
+ const job = await getQueue(queueOf(definition)).getJob(id);
310
+ if (!job || job.name !== definition.name) return;
311
+ return toSnapshot(job);
312
+ }
313
+ };
314
+ }
315
+ function assertValidDefinition(definition) {
316
+ if (typeof definition.name !== "string" || definition.name.trim() === "") throw new InvalidJobDefinitionError("defineJob() requires a non-empty `name`.");
317
+ if (typeof definition.handle !== "function") throw new InvalidJobDefinitionError(`defineJob("${definition.name}") requires a \`handle(payload, ctx)\` function.`);
318
+ 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}.`);
319
+ }
320
+ /**
321
+ * Merge app defaults < job definition < dispatch options into BullMQ's shape.
322
+ */
323
+ function toBullJobOptions(definition, options) {
324
+ const defaults = getQueueConfig().defaultJobOptions ?? {};
325
+ const attempts = options.attempts ?? definition.attempts ?? defaults.attempts;
326
+ const backoff = options.backoff ?? definition.backoff ?? defaults.backoff;
327
+ const removeOnComplete = definition.removeOnComplete ?? defaults.removeOnComplete;
328
+ const removeOnFail = definition.removeOnFail ?? defaults.removeOnFail;
329
+ const bullOptions = {};
330
+ if (attempts !== void 0) bullOptions.attempts = attempts;
331
+ if (backoff !== void 0) bullOptions.backoff = toBullBackoff(backoff);
332
+ if (removeOnComplete !== void 0) bullOptions.removeOnComplete = removeOnComplete;
333
+ if (removeOnFail !== void 0) bullOptions.removeOnFail = removeOnFail;
334
+ if (options.delay !== void 0) bullOptions.delay = toMilliseconds(options.delay);
335
+ if (options.priority !== void 0) bullOptions.priority = options.priority;
336
+ if (options.jobId !== void 0) bullOptions.jobId = options.jobId;
337
+ return bullOptions;
338
+ }
339
+ function toBullBackoff(backoff) {
340
+ return typeof backoff === "number" ? {
341
+ type: "fixed",
342
+ delay: backoff
343
+ } : backoff;
344
+ }
345
+ /**
346
+ * A plain view of a BullMQ job.
347
+ */
348
+ async function toSnapshot(job) {
349
+ const state = await job.getState();
350
+ return {
351
+ id: String(job.id),
352
+ name: job.name,
353
+ queue: job.queueName,
354
+ state,
355
+ payload: job.data,
356
+ progress: job.progress,
357
+ attemptsMade: job.attemptsMade,
358
+ result: job.returnvalue,
359
+ failedReason: job.failedReason || void 0,
360
+ createdAt: new Date(job.timestamp),
361
+ finishedAt: job.finishedOn ? new Date(job.finishedOn) : void 0
362
+ };
363
+ }
364
+
365
+ //#endregion
366
+ Object.defineProperty(exports, 'FailedJobNotFoundError', {
367
+ enumerable: true,
368
+ get: function () {
369
+ return FailedJobNotFoundError;
370
+ }
371
+ });
372
+ Object.defineProperty(exports, 'InvalidDurationError', {
373
+ enumerable: true,
374
+ get: function () {
375
+ return InvalidDurationError;
376
+ }
377
+ });
378
+ Object.defineProperty(exports, 'InvalidJobDefinitionError', {
379
+ enumerable: true,
380
+ get: function () {
381
+ return InvalidJobDefinitionError;
382
+ }
383
+ });
384
+ Object.defineProperty(exports, 'QueueDashboardDependencyError', {
385
+ enumerable: true,
386
+ get: function () {
387
+ return QueueDashboardDependencyError;
388
+ }
389
+ });
390
+ Object.defineProperty(exports, 'QueueNotConfiguredError', {
391
+ enumerable: true,
392
+ get: function () {
393
+ return QueueNotConfiguredError;
394
+ }
395
+ });
396
+ Object.defineProperty(exports, 'closeQueue', {
397
+ enumerable: true,
398
+ get: function () {
399
+ return closeQueue;
400
+ }
401
+ });
402
+ Object.defineProperty(exports, 'defaultQueueName', {
403
+ enumerable: true,
404
+ get: function () {
405
+ return defaultQueueName;
406
+ }
407
+ });
408
+ Object.defineProperty(exports, 'defineJob', {
409
+ enumerable: true,
410
+ get: function () {
411
+ return defineJob;
412
+ }
413
+ });
414
+ Object.defineProperty(exports, 'getQueue', {
415
+ enumerable: true,
416
+ get: function () {
417
+ return getQueue;
418
+ }
419
+ });
420
+ Object.defineProperty(exports, 'getQueueConfig', {
421
+ enumerable: true,
422
+ get: function () {
423
+ return getQueueConfig;
424
+ }
425
+ });
426
+ Object.defineProperty(exports, 'queueOf', {
427
+ enumerable: true,
428
+ get: function () {
429
+ return queueOf;
430
+ }
431
+ });
432
+ Object.defineProperty(exports, 'registeredJobs', {
433
+ enumerable: true,
434
+ get: function () {
435
+ return registeredJobs;
436
+ }
437
+ });
438
+ Object.defineProperty(exports, 'resetQueueConfig', {
439
+ enumerable: true,
440
+ get: function () {
441
+ return resetQueueConfig;
442
+ }
443
+ });
444
+ Object.defineProperty(exports, 'runningWorkers', {
445
+ enumerable: true,
446
+ get: function () {
447
+ return runningWorkers;
448
+ }
449
+ });
450
+ Object.defineProperty(exports, 'setQueueConfig', {
451
+ enumerable: true,
452
+ get: function () {
453
+ return setQueueConfig;
454
+ }
455
+ });
456
+ Object.defineProperty(exports, 'startWorkers', {
457
+ enumerable: true,
458
+ get: function () {
459
+ return startWorkers;
460
+ }
461
+ });
462
+ Object.defineProperty(exports, 'toMilliseconds', {
463
+ enumerable: true,
464
+ get: function () {
465
+ return toMilliseconds;
466
+ }
467
+ });
468
+ //# sourceMappingURL=define-job-DideGKQK.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-job-DideGKQK.cjs","names":["UnrecoverableError","Queue","Worker"],"sources":["../../../../../../queue/src/errors.ts","../../../../../../queue/src/config.ts","../../../../../../queue/src/job-registry.ts","../../../../../../queue/src/process-job.ts","../../../../../../queue/src/queue-manager.ts","../../../../../../queue/src/duration.ts","../../../../../../queue/src/define-job.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","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 { 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 { type Job, UnrecoverableError } from \"bullmq\";\nimport { findRegisteredJob } from \"./job-registry\";\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 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 };\n\n return definition.handle(job.data, context);\n}\n","import { log } from \"@warlock.js/logger\";\nimport { Queue, Worker } from \"bullmq\";\nimport { getQueueConfig } from \"./config\";\nimport { onJobRegistered, queueOf, registeredJobs } from \"./job-registry\";\nimport { processJob } from \"./process-job\";\n\nconst DEFAULT_SHUTDOWN_TIMEOUT = 30_000;\n\nconst queues = new Map<string, Queue>();\nconst workers = new Map<string, Worker>();\nlet stopListening: (() => void) | undefined;\n\n/**\n * The BullMQ queue for `name`, created on first use with the configured\n * connection and prefix.\n */\nexport function getQueue(name: string): Queue {\n let queue = queues.get(name);\n\n if (!queue) {\n const config = getQueueConfig();\n\n queue = new Queue(name, {\n connection: config.connection,\n prefix: config.prefix ?? \"warlock\",\n });\n\n queue.on(\"error\", (error) => {\n log.error(\"queue\", \"connection\", error);\n });\n\n queues.set(name, queue);\n }\n\n return queue;\n}\n\n/**\n * Start one worker per queue that has a registered job, and keep starting\n * workers for queues whose first job is defined later.\n *\n * A no-op returning `[]` when `workers.enabled` is `false`. Calling it again\n * while workers run starts only the missing ones.\n *\n * @returns the queue names that now have a worker in this process.\n */\nexport async function startWorkers(): Promise<string[]> {\n const config = getQueueConfig();\n\n if (config.workers?.enabled === false) {\n return [];\n }\n\n for (const job of registeredJobs()) {\n ensureWorker(queueOf(job));\n }\n\n stopListening ??= onJobRegistered((job) => {\n ensureWorker(queueOf(job));\n });\n\n await Promise.all([...workers.values()].map((worker) => worker.waitUntilReady()));\n\n return [...workers.keys()];\n}\n\n/** The queue names with a running worker in this process. */\nexport function runningWorkers(): string[] {\n return [...workers.keys()];\n}\n\nfunction ensureWorker(queueName: string): void {\n if (workers.has(queueName)) {\n return;\n }\n\n const config = getQueueConfig();\n\n const worker = new Worker(queueName, processJob, {\n connection: config.connection,\n prefix: config.prefix ?? \"warlock\",\n concurrency: config.workers?.concurrency ?? 1,\n });\n\n worker.on(\"error\", (error) => {\n log.error(\"queue\", \"worker\", error);\n });\n\n worker.on(\"failed\", (job, error) => {\n log.error(\"queue\", \"job.failed\", `${job?.name ?? \"unknown\"} (${job?.id ?? \"?\"}): ${error.message}`);\n });\n\n workers.set(queueName, worker);\n}\n\nexport type CloseQueueOptions = {\n /**\n * How long to wait for active jobs before force-closing workers, in\n * milliseconds. Default: `workers.shutdownTimeout`, else `30000`.\n */\n timeout?: number;\n};\n\n/**\n * Graceful shutdown: stop workers taking new jobs and wait for active ones\n * (bounded by `timeout`, then force-close), then close every queue\n * connection. Safe to call when nothing was started, and more than once.\n */\nexport async function closeQueue(options: CloseQueueOptions = {}): Promise<void> {\n stopListening?.();\n stopListening = undefined;\n\n const timeout = options.timeout ?? configuredShutdownTimeout();\n const closingWorkers = [...workers.values()];\n workers.clear();\n\n await Promise.all(closingWorkers.map((worker) => closeWorker(worker, timeout)));\n\n const closingQueues = [...queues.values()];\n queues.clear();\n\n await Promise.all(closingQueues.map((queue) => queue.close()));\n}\n\nfunction configuredShutdownTimeout(): number {\n try {\n return getQueueConfig().workers?.shutdownTimeout ?? DEFAULT_SHUTDOWN_TIMEOUT;\n } catch {\n return DEFAULT_SHUTDOWN_TIMEOUT;\n }\n}\n\nasync function closeWorker(worker: Worker, timeout: number): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n\n const timedOut = new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), timeout);\n });\n\n // A close already in progress cannot be upgraded to a forced one (BullMQ\n // returns the pending promise), so the timeout path drops the connections\n // instead and lets the graceful close settle in the background.\n const closing = worker.close().then(() => \"closed\" as const);\n closing.catch(() => undefined);\n\n const outcome = await Promise.race([closing, timedOut]);\n\n clearTimeout(timer);\n\n if (outcome === \"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\n await worker.disconnect();\n }\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 type { Job, JobsOptions } from \"bullmq\";\nimport { getQueueConfig } from \"./config\";\nimport { toMilliseconds } from \"./duration\";\nimport { InvalidJobDefinitionError } from \"./errors\";\nimport { queueOf, registerJob, type RegisteredJob } from \"./job-registry\";\nimport { getQueue } from \"./queue-manager\";\nimport type {\n DispatchOptions,\n JobBackoff,\n JobDefinition,\n JobOptions,\n JobSnapshot,\n JobState,\n QueueJob,\n} 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 job = await getQueue(queueName).add(\n definition.name,\n payload,\n toBullJobOptions(definition, options),\n );\n\n return { id: String(job.id), name: definition.name, queue: queueName };\n },\n async find(id) {\n const job = await getQueue(queueOf(definition)).getJob(id);\n\n if (!job || job.name !== definition.name) {\n return undefined;\n }\n\n return toSnapshot<TPayload, TResult>(job);\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 (definition.attempts !== undefined && !(Number.isInteger(definition.attempts) && definition.attempts >= 1)) {\n throw new InvalidJobDefinitionError(\n `defineJob(\"${definition.name}\"): \\`attempts\\` must be an integer >= 1, got ${definition.attempts}.`,\n );\n }\n}\n\n/**\n * Merge app defaults < job definition < dispatch options into BullMQ's shape.\n */\nfunction toBullJobOptions(definition: JobOptions, options: DispatchOptions): JobsOptions {\n const defaults = getQueueConfig().defaultJobOptions ?? {};\n const attempts = options.attempts ?? definition.attempts ?? defaults.attempts;\n const backoff = options.backoff ?? definition.backoff ?? defaults.backoff;\n const removeOnComplete = definition.removeOnComplete ?? defaults.removeOnComplete;\n const removeOnFail = definition.removeOnFail ?? defaults.removeOnFail;\n\n const bullOptions: JobsOptions = {};\n\n if (attempts !== undefined) bullOptions.attempts = attempts;\n if (backoff !== undefined) bullOptions.backoff = toBullBackoff(backoff);\n if (removeOnComplete !== undefined) bullOptions.removeOnComplete = removeOnComplete;\n if (removeOnFail !== undefined) bullOptions.removeOnFail = removeOnFail;\n if (options.delay !== undefined) bullOptions.delay = toMilliseconds(options.delay);\n if (options.priority !== undefined) bullOptions.priority = options.priority;\n if (options.jobId !== undefined) bullOptions.jobId = options.jobId;\n\n return bullOptions;\n}\n\nfunction toBullBackoff(backoff: JobBackoff): JobsOptions[\"backoff\"] {\n return typeof backoff === \"number\" ? { type: \"fixed\", delay: backoff } : backoff;\n}\n\n/**\n * A plain view of a BullMQ job.\n */\nexport async function toSnapshot<TPayload, TResult>(\n job: Job,\n): Promise<JobSnapshot<TPayload, TResult>> {\n const state = (await job.getState()) as JobState;\n\n return {\n id: String(job.id),\n name: job.name,\n queue: job.queueName,\n state,\n payload: job.data as TPayload,\n progress: job.progress as JobSnapshot[\"progress\"],\n attemptsMade: job.attemptsMade,\n result: job.returnvalue as TResult | undefined,\n failedReason: job.failedReason || undefined,\n createdAt: new Date(job.timestamp),\n finishedAt: job.finishedOn ? new Date(job.finishedOn) : undefined,\n };\n}\n"],"mappings":"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"}