queue-jobs-worker 1.0.5 → 2.0.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 (64) hide show
  1. package/README.md +754 -572
  2. package/dist/index.d.ts +546 -29
  3. package/dist/index.js +1268 -2448
  4. package/dist/scripts/claim-job.lua +117 -0
  5. package/dist/scripts/clear-queue.lua +42 -0
  6. package/dist/scripts/list-jobs.lua +50 -0
  7. package/dist/scripts/remove-job.lua +41 -0
  8. package/dist/scripts/save-job.lua +52 -0
  9. package/dist/scripts/update-job.lua +183 -0
  10. package/package.json +91 -110
  11. package/CHANGELOG.md +0 -133
  12. package/LICENSE +0 -21
  13. package/assets/queue-jobs-worker-demo.gif +0 -0
  14. package/assets/queue-jobs-worker-github.png +0 -0
  15. package/dist/core/backoff.d.ts +0 -24
  16. package/dist/core/backoff.d.ts.map +0 -1
  17. package/dist/core/client.d.ts +0 -95
  18. package/dist/core/client.d.ts.map +0 -1
  19. package/dist/core/id.d.ts +0 -9
  20. package/dist/core/id.d.ts.map +0 -1
  21. package/dist/core/index.d.ts +0 -7
  22. package/dist/core/index.d.ts.map +0 -1
  23. package/dist/core/job.d.ts +0 -75
  24. package/dist/core/job.d.ts.map +0 -1
  25. package/dist/core/queue.d.ts +0 -72
  26. package/dist/core/queue.d.ts.map +0 -1
  27. package/dist/core/worker.d.ts +0 -65
  28. package/dist/core/worker.d.ts.map +0 -1
  29. package/dist/events/emitter.d.ts +0 -24
  30. package/dist/events/emitter.d.ts.map +0 -1
  31. package/dist/index.cjs +0 -2578
  32. package/dist/index.cjs.map +0 -1
  33. package/dist/index.d.ts.map +0 -1
  34. package/dist/index.js.map +0 -1
  35. package/dist/lib/scripts/claim.lua +0 -27
  36. package/dist/lib/scripts/index.d.ts +0 -5
  37. package/dist/lib/scripts/index.d.ts.map +0 -1
  38. package/dist/lib/scripts/rate-limit.lua +0 -36
  39. package/dist/lib/scripts/recover-stalled.lua +0 -40
  40. package/dist/lib/scripts/renew-lock.lua +0 -16
  41. package/dist/storage/in-memory.adapter.d.ts +0 -33
  42. package/dist/storage/in-memory.adapter.d.ts.map +0 -1
  43. package/dist/storage/index.d.ts +0 -5
  44. package/dist/storage/index.d.ts.map +0 -1
  45. package/dist/storage/mysql.adapter.d.ts +0 -38
  46. package/dist/storage/mysql.adapter.d.ts.map +0 -1
  47. package/dist/storage/postgres.adapter.d.ts +0 -38
  48. package/dist/storage/postgres.adapter.d.ts.map +0 -1
  49. package/dist/storage/redis.adapter.d.ts +0 -45
  50. package/dist/storage/redis.adapter.d.ts.map +0 -1
  51. package/dist/types/client.types.d.ts +0 -41
  52. package/dist/types/client.types.d.ts.map +0 -1
  53. package/dist/types/events.types.d.ts +0 -22
  54. package/dist/types/events.types.d.ts.map +0 -1
  55. package/dist/types/index.d.ts +0 -10
  56. package/dist/types/index.d.ts.map +0 -1
  57. package/dist/types/job.types.d.ts +0 -97
  58. package/dist/types/job.types.d.ts.map +0 -1
  59. package/dist/types/queue.types.d.ts +0 -43
  60. package/dist/types/queue.types.d.ts.map +0 -1
  61. package/dist/types/storage.types.d.ts +0 -138
  62. package/dist/types/storage.types.d.ts.map +0 -1
  63. package/dist/types/worker.types.d.ts +0 -25
  64. package/dist/types/worker.types.d.ts.map +0 -1
package/dist/index.d.ts CHANGED
@@ -1,39 +1,556 @@
1
+ import { EventEmitter } from 'node:events';
2
+
1
3
  /**
2
- * queue-jobs-worker
4
+ * All possible lifecycle states a job can be in.
3
5
  *
4
- * Reliable job queue and background worker system for Node.js.
6
+ * waiting — Job has been added to the queue and is waiting to be picked up.
7
+ * delayed — Job is scheduled to run in the future (delay or cron).
8
+ * active — Job is currently being processed by a worker.
9
+ * completed — Job finished successfully.
10
+ * failed — Job exhausted all retry attempts and is permanently failed.
11
+ * retrying — Job failed once but still has remaining attempts; waiting for next retry.
12
+ */
13
+ type JobStatus = "waiting" | "delayed" | "active" | "completed" | "failed" | "retrying";
14
+ /**
15
+ * Options you can pass when adding a job to the queue.
16
+ *
17
+ * @property attempts - Max retry attempts for this specific job (overrides queue default).
18
+ * @property delay - Milliseconds to wait before the job becomes eligible to run.
19
+ * @property priority - Lower number = higher priority (default: 0).
20
+ * @property jobId - Custom job ID. Auto-generated (crypto UUID) if not provided.
21
+ * @property cron - A cron expression to schedule the job on a recurring schedule.
22
+ * Uses the `croner` library syntax, e.g. "0 * * * *" (every hour).
23
+ * @property removeOnComplete - Auto-remove the job from storage after it completes.
24
+ * @property removeOnFail - Auto-remove the job from storage after it permanently fails.
25
+ */
26
+ interface JobOptions {
27
+ attempts?: number;
28
+ delay?: number;
29
+ priority?: number;
30
+ jobId?: string;
31
+ cron?: string;
32
+ removeOnComplete?: boolean;
33
+ removeOnFail?: boolean;
34
+ }
35
+ /**
36
+ * The full job record stored in the queue.
37
+ *
38
+ * @property id - Unique identifier (UUID v4 via node:crypto).
39
+ * @property name - Logical job name, e.g. "send-welcome-email".
40
+ * @property data - Arbitrary payload passed to the worker handler.
41
+ * @property status - Current lifecycle state.
42
+ * @property opts - Original options this job was created with.
43
+ * @property attempts - Max number of attempts allowed.
44
+ * @property attemptsMade - Number of attempts that have been made so far.
45
+ * @property delay - Milliseconds to wait before first run.
46
+ * @property runAt - Absolute timestamp (ms) when the job becomes eligible.
47
+ * @property priority - Scheduling priority; lower = runs first.
48
+ * @property cron - Cron expression for recurring jobs.
49
+ * @property result - Return value from the handler on success.
50
+ * @property error - Error message from the last failed attempt.
51
+ * @property stacktrace - Error stack trace from the last failed attempt.
52
+ * @property createdAt - Unix timestamp (ms) when the job was created.
53
+ * @property updatedAt - Unix timestamp (ms) of the last status change.
54
+ * @property processedAt - Unix timestamp (ms) when processing started.
55
+ * @property finishedAt - Unix timestamp (ms) when the job completed or permanently failed.
56
+ */
57
+ interface Job<TData = unknown, TResult = unknown> {
58
+ id: string;
59
+ name: string;
60
+ data: TData;
61
+ status: JobStatus;
62
+ opts: JobOptions;
63
+ attempts: number;
64
+ attemptsMade: number;
65
+ delay: number;
66
+ runAt: number;
67
+ priority: number;
68
+ cron?: string;
69
+ result?: TResult;
70
+ error?: string;
71
+ stacktrace?: string;
72
+ createdAt: number;
73
+ updatedAt: number;
74
+ processedAt?: number;
75
+ finishedAt?: number;
76
+ }
77
+
78
+ /**
79
+ * Every storage backend must implement this interface.
80
+ * The Queue and Worker classes talk exclusively to this contract —
81
+ * they never know whether the data is in a Map, Redis, Postgres, or MySQL.
82
+ */
83
+ interface IStorage {
84
+ /**
85
+ * Open the connection / initialise the storage.
86
+ * Called once by QueueClient.init().
87
+ */
88
+ connect(): Promise<void>;
89
+ /**
90
+ * Close the connection and release all resources.
91
+ * Called once by QueueClient.close().
92
+ */
93
+ disconnect(): Promise<void>;
94
+ /** Persist a new job. */
95
+ saveJob<TData = unknown, TResult = unknown>(queueName: string, job: Job<TData, TResult>): Promise<void>;
96
+ /** Fetch a single job by its ID. Returns undefined if not found. */
97
+ getJob<TData = unknown, TResult = unknown>(queueName: string, jobId: string): Promise<Job<TData, TResult> | undefined>;
98
+ /** Update an existing job (partial or full). */
99
+ updateJob<TData = unknown, TResult = unknown>(queueName: string, jobId: string, patch: Partial<Job<TData, TResult>>): Promise<void>;
100
+ /** Permanently remove a job from storage. */
101
+ removeJob(queueName: string, jobId: string): Promise<void>;
102
+ /**
103
+ * Return all jobs for a queue, optionally filtered by status.
104
+ * Results are sorted: lower priority number first, then older createdAt first.
105
+ */
106
+ listJobs<TData = unknown, TResult = unknown>(queueName: string, status?: JobStatus): Promise<Job<TData, TResult>[]>;
107
+ /**
108
+ * Pick the next job that is eligible to run right now.
109
+ *
110
+ * Eligibility rules:
111
+ * - status === "waiting" AND runAt <= Date.now()
112
+ * - OR status === "retrying" AND runAt <= Date.now()
113
+ *
114
+ * Returns undefined when the queue has nothing ready.
115
+ */
116
+ getNextJob<TData = unknown, TResult = unknown>(queueName: string): Promise<Job<TData, TResult> | undefined>;
117
+ /** Remove every job in a queue. */
118
+ clearQueue(queueName: string): Promise<void>;
119
+ /** Return the total number of jobs in a queue (all statuses). */
120
+ countJobs(queueName: string): Promise<number>;
121
+ }
122
+
123
+ /**
124
+ * The storage backend to use.
125
+ *
126
+ * - memory — In-process Map; no external dependencies. Good for dev and testing.
127
+ * - redis — Redis via the `redis` npm package (peer dependency).
128
+ * - postgres — PostgreSQL via the `pg` npm package (peer dependency).
129
+ * - mysql — MySQL/MariaDB via the `mysql2` npm package (peer dependency).
130
+ */
131
+ type StorageDialect = "memory" | "redis" | "mysql" | "postgres";
132
+ /**
133
+ * The retry backoff strategy applied between successive attempts.
134
+ *
135
+ * - fixed — Always wait `retryDelay` ms.
136
+ * - linear — Wait `retryDelay * attemptNumber` ms.
137
+ * - exponential — Wait `retryDelay * 2^(attemptNumber - 1)` ms.
138
+ */
139
+ type BackoffStrategy = "fixed" | "linear" | "exponential";
140
+ /**
141
+ * Global defaults applied to every job unless overridden at the queue or job level.
142
+ *
143
+ * @property attempts - Max retry attempts per job (0 = unlimited). Default: 3.
144
+ * @property retryDelay - Base delay in ms before the first retry. Default: 1000.
145
+ * @property backoff - Backoff strategy. Default: "exponential".
146
+ * @property timeout - Max ms a job can run before being marked failed. Default: 30000.
147
+ */
148
+ interface QueueClientConfigOptions {
149
+ attempts?: number;
150
+ retryDelay?: number;
151
+ backoff?: BackoffStrategy;
152
+ timeout?: number;
153
+ }
154
+ /**
155
+ * Options passed to `new QueueClient(...)`.
156
+ *
157
+ * @property dialect - Which storage backend to use.
158
+ * @property connectionString - DSN/URL for Redis, PostgreSQL, or MySQL.
159
+ * Not required for `memory` dialect.
160
+ * @property debug - When true, the client logs internal operations to console.
161
+ * @property options - Global job execution defaults.
162
+ */
163
+ interface QueueClientOptions {
164
+ dialect: StorageDialect;
165
+ connectionString?: string;
166
+ debug?: boolean;
167
+ options?: QueueClientConfigOptions;
168
+ }
169
+ type storageDialect = StorageDialect;
170
+ type rateLimitOptions = RateLimitOptions;
171
+ /**
172
+ * Rate-limiting configuration for a queue.
173
+ *
174
+ * @property max - Maximum number of jobs to start within the `duration` window.
175
+ * @property duration - Length of the rate-limit window in milliseconds.
176
+ */
177
+ interface RateLimitOptions {
178
+ max: number;
179
+ duration: number;
180
+ }
181
+
182
+ /**
183
+ * QueueClient — the entry point for the entire queue system.
184
+ *
185
+ * Every Queue and Worker you create is tied to a QueueClient instance.
186
+ * One client holds one storage connection and one set of global defaults.
187
+ *
188
+ * Typical usage
189
+ * ─────────────
190
+ * // 1. Create and initialise the client (do this once at app startup)
191
+ * const client = new QueueClient({ dialect: "memory" });
192
+ * await client.init();
5
193
  *
6
- * Quick start:
194
+ * // 2. Create queues and workers … (see Queue / Worker docs)
195
+ *
196
+ * // 3. Gracefully shut everything down (call on app exit / SIGTERM)
197
+ * await client.close();
198
+ *
199
+ * Singleton default client
200
+ * ────────────────────────
201
+ * The first client you call .init() on is automatically stored as the
202
+ * process-wide default. Any Queue or Worker constructed without an explicit
203
+ * client argument will pick it up automatically.
204
+ *
205
+ * You can also set the default explicitly:
206
+ * QueueClient.setDefaultClient(client);
207
+ */
208
+ declare class QueueClient {
209
+ private static _default;
210
+ private readonly _dialect;
211
+ private readonly _connectionString?;
212
+ private readonly _debug;
213
+ private readonly _config;
214
+ private _storage;
215
+ private _initialized;
216
+ private _closed;
217
+ constructor(opts: QueueClientOptions);
218
+ /**
219
+ * Open the storage connection and mark this client as ready.
220
+ *
221
+ * Must be called before creating any Queue or Worker.
222
+ * Calling init() more than once throws to prevent accidental double-init.
223
+ *
224
+ * @returns `this` so you can chain: await new QueueClient(…).init()
225
+ */
226
+ init(): Promise<this>;
227
+ /**
228
+ * Close the storage connection and release all resources.
229
+ *
230
+ * Call this on application shutdown (e.g. SIGTERM handler, NestJS onModuleDestroy,
231
+ * Express app.close()). Any in-flight worker processing will finish its current
232
+ * job before the worker's own close() stops the polling loop.
233
+ */
234
+ close(): Promise<void>;
235
+ /** Returns the process-wide default client, or undefined if none is set. */
236
+ static getDefaultClient(): QueueClient | undefined;
237
+ /**
238
+ * Explicitly set (or replace) the process-wide default client.
239
+ * Useful in tests or multi-client setups.
240
+ */
241
+ static setDefaultClient(client: QueueClient): void;
242
+ /** Clear the default client reference (useful between tests). */
243
+ static clearDefaultClient(): void;
244
+ /** True after init() succeeds and before close() is called. */
245
+ isInitialized(): boolean;
246
+ /** True after close() has been called. */
247
+ isClosed(): boolean;
248
+ /** The merged global job execution defaults. */
249
+ getConfig(): Required<QueueClientConfigOptions>;
250
+ /** The storage dialect this client was created with. */
251
+ getDialect(): StorageDialect;
252
+ /** The connection string (may be undefined for the memory dialect). */
253
+ getConnectionString(): string | undefined;
254
+ /** Whether debug logging is enabled. */
255
+ isDebugMode(): boolean;
256
+ /**
257
+ * The underlying IStorage instance.
258
+ *
259
+ * Exposed so Queue / Worker can call storage methods directly.
260
+ * Throws if the client has not been initialized yet.
261
+ *
262
+ * @internal
263
+ */
264
+ getStorage(): IStorage;
265
+ /**
266
+ * Write a debug-mode log line.
267
+ *
268
+ * FIX: the old signature accepted `...args: unknown[]` which let callers
269
+ * accidentally spread raw objects into the console output, potentially
270
+ * leaking internal state (job payloads, connection strings, stack traces)
271
+ * into log aggregators. The new signature accepts only a pre-formatted
272
+ * string so callers must stringify anything sensitive before passing it.
273
+ *
274
+ * @internal
275
+ */
276
+ _log(message: string): void;
277
+ }
278
+
279
+ /**
280
+ * Options for `new Queue(name, client, options)`.
281
+ *
282
+ * Queue is a pure producer — it only manages job storage.
283
+ * Concurrency, polling, and execution belong to the Worker.
284
+ *
285
+ * @property rateLimit - Optional rate-limit config (max jobs per duration).
286
+ * @property defaultJobOpts - Per-queue job defaults (override client-level defaults).
287
+ */
288
+ interface QueueOptions {
289
+ rateLimit?: RateLimitOptions;
290
+ defaultJobOpts?: {
291
+ attempts?: number;
292
+ delay?: number;
293
+ priority?: number;
294
+ removeOnComplete?: boolean;
295
+ removeOnFail?: boolean;
296
+ };
297
+ }
298
+ /**
299
+ * Options for `new Worker(queue, handler, options)`.
300
+ *
301
+ * Worker is a pure consumer — it only processes jobs from a Queue.
302
+ *
303
+ * @property concurrency - Max parallel jobs. Default: 1.
304
+ * @property pollInterval - Ms between queue polls when idle. Default: 500.
305
+ */
306
+ interface WorkerOptions {
307
+ concurrency?: number;
308
+ pollInterval?: number;
309
+ }
310
+
311
+ /**
312
+ * Queue — pure job producer.
313
+ *
314
+ * Responsible for one thing: managing a named collection of jobs in storage.
315
+ * It does not poll, process, or execute anything. A Worker consumes from it.
316
+ *
317
+ * Usage
318
+ * ─────
319
+ * const client = new QueueClient({ dialect: "redis", connectionString: "…" });
320
+ * await client.init();
321
+ *
322
+ * const queue = new Queue("emails", client);
323
+ *
324
+ * // Add jobs
325
+ * const job = await queue.add("welcome", { to: "user@example.com" });
326
+ * await queue.add("reminder", { userId: 42 }, { delay: 60_000 });
327
+ * await queue.add("report", {}, { cron: "0 9 * * *" });
328
+ *
329
+ * // Inspect
330
+ * const job = await queue.get(id);
331
+ * const all = await queue.list();
332
+ * const n = await queue.count();
333
+ *
334
+ * // Remove
335
+ * await queue.remove(id);
336
+ * await queue.clear();
337
+ */
338
+ declare class Queue<TData = unknown, TResult = unknown> {
339
+ readonly name: string;
340
+ readonly client: QueueClient;
341
+ readonly options: Required<QueueOptions>;
342
+ /** Active croner handles keyed by jobId — used to cancel scheduled jobs. */
343
+ private readonly _cronHandles;
344
+ /** Workers attached to this queue — auto-closed when queue.close() is called. */
345
+ private readonly _workers;
346
+ /** Guards against double-close. */
347
+ private _closed;
348
+ constructor(name: string, client?: QueueClient, options?: QueueOptions);
349
+ private get storage();
350
+ /**
351
+ * Add a job to the queue.
352
+ *
353
+ * The job is immediately persisted to storage with status `"waiting"`.
354
+ * Cron jobs start as `"delayed"` and are moved to `"waiting"` on each tick.
355
+ *
356
+ * @param name - Job type identifier, e.g. `"send-welcome-email"`.
357
+ * @param data - Arbitrary payload forwarded to the Worker handler.
358
+ * @param opts - Per-job overrides for attempts, delay, priority, cron, etc.
359
+ * @returns The fully populated Job record (with auto-generated UUID id).
360
+ */
361
+ add(name: string, data: TData, opts?: JobOptions): Promise<Job<TData, TResult>>;
362
+ /**
363
+ * Fetch a single job by ID.
364
+ * Returns `undefined` when the job does not exist or has been removed.
365
+ */
366
+ get(jobId: string): Promise<Job<TData, TResult> | undefined>;
367
+ /**
368
+ * Permanently remove a job from the queue.
369
+ * If the job has an active cron schedule, it is also cancelled.
370
+ */
371
+ remove(jobId: string): Promise<void>;
372
+ /**
373
+ * List jobs, optionally filtered by status.
374
+ * Results are sorted: priority ASC, createdAt ASC.
375
+ *
376
+ * @param status - `"waiting" | "delayed" | "active" | "completed" | "failed" | "retrying"`
377
+ */
378
+ list(status?: JobStatus): Promise<Job<TData, TResult>[]>;
379
+ /**
380
+ * Remove every job in this queue (all statuses).
381
+ * All active cron schedules for this queue are also cancelled.
382
+ */
383
+ clear(): Promise<void>;
384
+ /**
385
+ * Return the total number of jobs in this queue (all statuses combined).
386
+ */
387
+ count(): Promise<number>;
388
+ /**
389
+ * Gracefully shut down everything tied to this queue.
390
+ *
391
+ * In order:
392
+ * 1. Stop all Workers that were created from this queue (waits for
393
+ * in-flight jobs to finish before each worker resolves).
394
+ * 2. Cancel all active cron schedules.
395
+ *
396
+ * Idempotent — safe to call multiple times.
397
+ * You do NOT need to call worker.close() separately — this handles it.
398
+ */
399
+ close(): Promise<void>;
400
+ /**
401
+ * Register a Worker so it is auto-closed when queue.close() is called.
402
+ * @internal
403
+ */
404
+ _registerWorker(worker: {
405
+ close(): Promise<void>;
406
+ }): void;
407
+ /**
408
+ * Remove a Worker from the auto-close registry (called by worker.close()).
409
+ * @internal
410
+ */
411
+ _unregisterWorker(worker: {
412
+ close(): Promise<void>;
413
+ }): void;
414
+ /**
415
+ * Atomically claim the next eligible job.
416
+ * Eligibility: `status IN ('waiting','retrying') AND runAt <= now`.
417
+ * Returns `undefined` when nothing is ready.
418
+ * @internal
419
+ */
420
+ _nextJob(): Promise<Job<TData, TResult> | undefined>;
421
+ /**
422
+ * Persist a partial status update to a job.
423
+ * @internal
424
+ */
425
+ _updateJob(jobId: string, patch: Partial<Job<TData, TResult>>): Promise<void>;
426
+ private _scheduleCron;
427
+ private _cancelCron;
428
+ }
429
+
430
+ /**
431
+ * The function you write to process a single job.
432
+ *
433
+ * - Return any value — it is stored in `job.result` on success.
434
+ * - Throw any error — the Worker handles retries, backoff, and status updates.
435
+ */
436
+ type WorkerHandler<TData = unknown, TResult = unknown> = (job: Job<TData, TResult>) => TResult | Promise<TResult>;
437
+ /**
438
+ * Typed events emitted by a Worker instance.
439
+ */
440
+ interface WorkerEvents<TData, TResult> {
441
+ /** A job has been picked up and is now processing. */
442
+ active: [job: Job<TData, TResult>];
443
+ /** A job finished successfully. result is the handler's return value. */
444
+ completed: [job: Job<TData, TResult>, result: TResult];
445
+ /** One attempt failed — job may still retry. */
446
+ error: [job: Job<TData, TResult>, error: Error];
447
+ /** All attempts exhausted — job is permanently failed. */
448
+ failed: [job: Job<TData, TResult>, error: Error];
449
+ /** Polling loop started. */
450
+ started: [];
451
+ /** Polling loop stopped and all in-flight jobs drained. */
452
+ stopped: [];
453
+ }
454
+ /**
455
+ * Worker — pure job consumer.
7
456
  *
8
- * import { QueueClient } from "queue-jobs-worker";
457
+ * A Worker takes a Queue and a handler function. It polls the Queue for
458
+ * eligible jobs, executes them through the handler, and manages the full
459
+ * job lifecycle: status transitions, timeouts, retries, backoff, and events.
9
460
  *
10
- * // In-memory (dev / tests)
11
- * const client = new QueueClient();
461
+ * It does NOT add, list, or remove jobs — those operations belong on Queue.
12
462
  *
13
- * // Redis
14
- * const client = new QueueClient({ dialect: "redis", connectionString: "redis://localhost:6379" });
15
- * await client.init();
463
+ * Usage
464
+ * ─────
465
+ * const queue = new Queue("emails", client);
466
+ * const worker = new Worker(queue, async (job) => {
467
+ * await sendEmail(job.data);
468
+ * });
16
469
  *
17
- * // PostgreSQL
18
- * const client = new QueueClient({ dialect: "postgres", connectionString: "postgresql://..." });
19
- * await client.init();
470
+ * worker.start();
20
471
  *
21
- * // MySQL
22
- * const client = new QueueClient({ dialect: "mysql", connectionString: "mysql://..." });
23
- * await client.init();
472
+ * // Lifecycle events
473
+ * worker.on("completed", (job, result) => console.log(job.id, result));
474
+ * worker.on("failed", (job, err) => console.error(job.id, err));
24
475
  *
25
- * @module queue-jobs-worker
476
+ * // Graceful shutdown — waits for in-flight jobs before resolving
477
+ * await worker.close();
26
478
  */
27
- export { QueueClient } from "./core/client.js";
28
- export { Queue } from "./core/queue.js";
29
- export { Job } from "./core/job.js";
30
- export { Worker } from "./core/worker.js";
31
- export { InMemoryStorageAdapter } from "./storage/in-memory.adapter.js";
32
- export { RedisStorageAdapter } from "./storage/redis.adapter.js";
33
- export { PostgreSQLStorageAdapter } from "./storage/postgres.adapter.js";
34
- export { MySQLStorageAdapter } from "./storage/mysql.adapter.js";
35
- export { QueueEventEmitter } from "./events/emitter.js";
36
- export { calculateBackoff, nextRunAt } from "./core/backoff.js";
37
- export { generateJobId } from "./core/id.js";
38
- export type { JobStatus, JobAttempt, JobSchedule, JobOptions, BackoffStrategy, JobData, RateLimitOptions, QueueOptions, Processor, WorkerOptions, WorkerStatus, StorageAdapter, EnqueueInput, ClaimInput, ClaimResult, RequeueInput, MoveToDlqInput, GetJobsFilter, StorageDialect, ClientDefaults, QueueClientOptions, QueueEvents, } from "./types/index.js";
39
- //# sourceMappingURL=index.d.ts.map
479
+ declare class Worker<TData = unknown, TResult = unknown> extends EventEmitter {
480
+ /** The Queue this worker consumes from. Read-only after construction. */
481
+ readonly queue: Queue<TData, TResult>;
482
+ private readonly handler;
483
+ private readonly concurrency;
484
+ private readonly pollInterval;
485
+ private running;
486
+ private _closed;
487
+ private loopTimer;
488
+ private activeCount;
489
+ /**
490
+ * @param queue - The Queue to consume jobs from.
491
+ * @param handler - Async function that processes one job at a time.
492
+ * @param options - `concurrency` (default 1) and `pollInterval` ms (default 500).
493
+ */
494
+ constructor(queue: Queue<TData, TResult>, handler: WorkerHandler<TData, TResult>, options?: WorkerOptions);
495
+ /**
496
+ * Start the polling loop.
497
+ * Calling start() on an already-running worker is a no-op.
498
+ * Calling start() after close() throws — create a new Worker instead.
499
+ */
500
+ start(): this;
501
+ /**
502
+ * Gracefully stop the worker.
503
+ *
504
+ * - No new jobs are picked up after this call.
505
+ * - Already in-flight jobs are allowed to finish.
506
+ * - Resolves once the poll loop has stopped and active count reaches 0.
507
+ *
508
+ * Safe to call even if start() was never called.
509
+ */
510
+ close(): Promise<void>;
511
+ /** `true` while the polling loop is active. */
512
+ isRunning(): boolean;
513
+ /** `true` after close() has been called. */
514
+ isClosed(): boolean;
515
+ on<K extends keyof WorkerEvents<TData, TResult>>(event: K, listener: (...args: WorkerEvents<TData, TResult>[K]) => void): this;
516
+ once<K extends keyof WorkerEvents<TData, TResult>>(event: K, listener: (...args: WorkerEvents<TData, TResult>[K]) => void): this;
517
+ /**
518
+ * FIX: Override emit() to guard against ERR_UNHANDLED_ERROR only when no
519
+ * real "error" listener is registered. This avoids the blank-listener
520
+ * anti-pattern that would have swallowed errors in tests / callers that DO
521
+ * register their own listener (since all listeners are always called).
522
+ */
523
+ emit<K extends keyof WorkerEvents<TData, TResult>>(event: K, ...args: WorkerEvents<TData, TResult>[K]): boolean;
524
+ private scheduleLoop;
525
+ /**
526
+ * One iteration of the poll loop.
527
+ * Starts as many jobs as concurrency allows, then returns.
528
+ */
529
+ private tick;
530
+ private processJob;
531
+ /**
532
+ * Race the handler against a hard timeout.
533
+ * FIX: clear the deadline timer when the handler wins the race — without
534
+ * this, the setTimeout handle keeps the event loop alive and Node.js will
535
+ * not exit cleanly in tests or short-lived scripts.
536
+ */
537
+ private runWithTimeout;
538
+ /** Decide between a retry and a permanent failure. */
539
+ private handleFailure;
540
+ /**
541
+ * Calculate the delay before the next retry.
542
+ *
543
+ * fixed → base
544
+ * linear → base × attemptsMade
545
+ * exponential → base × 2^(attemptsMade − 1) capped at 30 min
546
+ */
547
+ private calcBackoff;
548
+ /** Poll every 50 ms until no jobs are actively processing, with a 30 s safety timeout. */
549
+ private drainActive;
550
+ private get client();
551
+ private _log;
552
+ }
553
+
554
+ declare const VERSION = "2.0.0";
555
+
556
+ export { type BackoffStrategy, type IStorage, type Job, type JobOptions, type JobStatus, Queue, QueueClient, type QueueClientConfigOptions, type QueueClientOptions, type QueueOptions, type RateLimitOptions, type StorageDialect, VERSION, Worker, type WorkerEvents, type WorkerHandler, type WorkerOptions, type rateLimitOptions, type storageDialect };