queue-jobs-worker 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,76 +1,122 @@
1
- # Changelog
2
-
3
- All notable changes to **queue-jobs-worker** will be documented in this file.
4
-
5
- The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
- This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ---
9
-
10
- ## [1.0.0] — 2026-08-29
11
-
12
- ### Added
13
-
14
- **Core**
15
-
16
- - `QueueClient` — primary entry point; configures storage, exposes queues, and coordinates lifecycle.
17
- - `Queue` independent job stream with per-queue configuration overrides.
18
- - `Job` rich wrapper around the raw job data record; passed to user processors.
19
- - `Worker` claims and executes jobs with configurable concurrency and graceful shutdown.
20
- - `QueueEventEmitter` — strongly-typed lifecycle event bus shared across all components.
21
-
22
- **Job processing**
23
-
24
- - At-least-once delivery guarantee.
25
- - Stable job identity preserved across all retries (no duplicate job IDs).
26
- - Full attempt history stored per job.
27
- - Per-job processor timeout enforcement.
28
- - Priority-ordered job claiming (higher priority = claimed first).
29
- - Delayed and scheduled job support via `schedule.delay` and `schedule.runAt`.
30
- - Recurring job support via `schedule.cron` field (cron expression stored; recurrence integration point provided).
31
-
32
- **Reliability**
33
-
34
- - Configurable retry with `attempts`, `retryDelay`, and `backoff` strategy.
35
- - Three backoff strategies: `fixed`, `linear`, `exponential` (capped at 10 minutes).
36
- - Dead Letter Queue (DLQ): jobs moved to `dead` status after exhausting all attempts.
37
- - Stalled-job recovery: expired locks on `active` jobs are detected and re-queued automatically.
38
- - Configurable lock duration (`lockDuration`) per queue.
39
-
40
- **Concurrency**
41
-
42
- - Per-worker concurrency limit (`concurrency`).
43
- - Atomic job claiming inside `InMemoryStorageAdapter` (single event-loop tick).
44
-
45
- **Rate limiting**
46
-
47
- - Sliding-window rate limiter configurable per queue (`rateLimit.max` / `rateLimit.duration`).
48
-
49
- **Storage**
50
-
51
- - `StorageAdapter` interface clean abstraction; all core logic talks through this interface.
52
- - `InMemoryStorageAdapter` — full-featured in-process adapter for development and testing.
53
-
54
- **Configuration**
55
-
56
- - Layered configuration: Client defaults → Queue options → Worker options → Job options.
57
- - `QueueClient.withAdapter()` static factory for supplying a custom storage adapter.
58
-
59
- **TypeScript**
60
-
61
- - Full strict TypeScript types with `exactOptionalPropertyTypes` and `noUncheckedIndexedAccess`.
62
- - All public types exported from the top-level `index.ts`.
63
-
64
- **Build**
65
-
66
- - Dual ESM + CJS output via `tsup`.
67
- - Declaration files (`.d.ts` + `.d.ts.map`) generated via `tsc`.
68
-
69
- **Tests**
70
-
71
- - 31 tests across backoff strategies, storage adapter, queue client, and worker behaviour.
72
- - Coverage: job lifecycle, retry, DLQ, concurrency, stalled recovery, rate limiting, graceful shutdown.
73
-
74
- ---
75
-
76
- [1.0.0]: https://github.com/rafidahmed870/queue-jobs-worker/releases/tag/v1.0.0
1
+ # Changelog
2
+
3
+ All notable changes to **queue-jobs-worker** will be documented in this file.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
+ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+ ## [1.0.1] — 2026-08-31
10
+
11
+ ### Core
12
+
13
+ ### Fixed
14
+
15
+ - **`Worker.stop()` — clarified `releaseLock()` behavior in shutdown comment** ([#1](https://github.com/rafidahmed870/queue-jobs-worker/issues/1))
16
+
17
+ The inline comment in `worker.ts` now correctly explains that `releaseLock()`
18
+ sets `lockExpiresAt` to an already-expired timestamp (not null/empty), so
19
+ `recoverStalledJobs()` on any worker will immediately reclaim the job on the
20
+ next stall-check cycle.
21
+
22
+ ---
23
+
24
+ ### Events
25
+
26
+ ### Added
27
+
28
+ - `QueueEventEmitter` — strongly-typed lifecycle event bus shared across all components.
29
+ - Emits events for the full job lifecycle: enqueued, started, completed, failed, retrying, dead, stalled.
30
+ - All event payloads fully typed via `events.types.ts`.
31
+
32
+ ---
33
+
34
+ <!-- Links -->
35
+ [1.0.0]: https://github.com/rafidahmed870/queue-jobs-worker/releases/tag/v1.0.0
36
+
37
+ ### Storage
38
+
39
+ ### Fixed
40
+
41
+ - **`releaseLock()` leaves jobs permanently stuck in `active` status** ([#1](https://github.com/rafidahmed870/queue-jobs-worker/issues/1))
42
+
43
+ All four adapters were clearing `lockExpiresAt` to `null` / empty string
44
+ while keeping `status` as `"active"`. Because `recoverStalledJobs()` requires
45
+ a non-null, already-expired `lockExpiresAt` to match a stalled job, those
46
+ jobs were silently skipped and could never be reclaimed or retried.
47
+
48
+ - `InMemoryStorageAdapter.releaseLock()` — `lockExpiresAt` now set to `new Date().toISOString()` instead of `null`.
49
+ - `RedisStorageAdapter.releaseLock()` — `lockExpiresAt` hash field now set to the current ISO timestamp instead of `""`.
50
+ - `PostgreSQLStorageAdapter.releaseLock()` — `lock_expires_at` column now set to `NOW()` instead of `NULL`.
51
+ - `MySQLStorageAdapter.releaseLock()` — `lock_expires_at` column now set to `NOW(3)` instead of `NULL`.
52
+
53
+ ---
54
+
55
+ ## [1.0.0] — 2026-08-29
56
+
57
+ ### Added
58
+
59
+ **Core**
60
+
61
+ - `QueueClient` primary entry point; configures storage, exposes queues, and coordinates lifecycle.
62
+ - `Queue` independent job stream with per-queue configuration overrides.
63
+ - `Job` — rich wrapper around the raw job data record; passed to user processors.
64
+ - `Worker` — claims and executes jobs with configurable concurrency and graceful shutdown.
65
+ - `QueueEventEmitter` — strongly-typed lifecycle event bus shared across all components.
66
+
67
+ **Job processing**
68
+
69
+ - At-least-once delivery guarantee.
70
+ - Stable job identity preserved across all retries (no duplicate job IDs).
71
+ - Full attempt history stored per job.
72
+ - Per-job processor timeout enforcement.
73
+ - Priority-ordered job claiming (higher priority = claimed first).
74
+ - Delayed and scheduled job support via `schedule.delay` and `schedule.runAt`.
75
+ - Recurring job support via `schedule.cron` field (cron expression stored; recurrence integration point provided).
76
+
77
+ **Reliability**
78
+
79
+ - Configurable retry with `attempts`, `retryDelay`, and `backoff` strategy.
80
+ - Three backoff strategies: `fixed`, `linear`, `exponential` (capped at 10 minutes).
81
+ - Dead Letter Queue (DLQ): jobs moved to `dead` status after exhausting all attempts.
82
+ - Stalled-job recovery: expired locks on `active` jobs are detected and re-queued automatically.
83
+ - Configurable lock duration (`lockDuration`) per queue.
84
+
85
+ **Concurrency**
86
+
87
+ - Per-worker concurrency limit (`concurrency`).
88
+ - Atomic job claiming inside `InMemoryStorageAdapter` (single event-loop tick).
89
+
90
+ **Rate limiting**
91
+
92
+ - Sliding-window rate limiter configurable per queue (`rateLimit.max` / `rateLimit.duration`).
93
+
94
+ **Storage**
95
+
96
+ - `StorageAdapter` interface — clean abstraction; all core logic talks through this interface.
97
+ - `InMemoryStorageAdapter` — full-featured in-process adapter for development and testing.
98
+
99
+ **Configuration**
100
+
101
+ - Layered configuration: Client defaults → Queue options → Worker options → Job options.
102
+ - `QueueClient.withAdapter()` static factory for supplying a custom storage adapter.
103
+
104
+ **TypeScript**
105
+
106
+ - Full strict TypeScript types with `exactOptionalPropertyTypes` and `noUncheckedIndexedAccess`.
107
+ - All public types exported from the top-level `index.ts`.
108
+
109
+ **Build**
110
+
111
+ - Dual ESM + CJS output via `tsup`.
112
+ - Declaration files (`.d.ts` + `.d.ts.map`) generated via `tsc`.
113
+
114
+ **Tests**
115
+
116
+ - 31 tests across backoff strategies, storage adapter, queue client, and worker behaviour.
117
+ - Coverage: job lifecycle, retry, DLQ, concurrency, stalled recovery, rate limiting, graceful shutdown.
118
+
119
+ ---
120
+
121
+ [1.0.1]: https://github.com/rafidahmed870/queue-jobs-worker/compare/v1.0.0...v1.0.1
122
+ [1.0.0]: https://github.com/rafidahmed870/queue-jobs-worker/releases/tag/v1.0.0
package/README.md CHANGED
@@ -818,4 +818,10 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md).
818
818
 
819
819
  ## License
820
820
 
821
- MIT — [LICENSE](./LICENSE)
821
+ MIT — [LICENSE](./LICENSE)
822
+
823
+ # Donations
824
+
825
+ ## Buy me a coffee!
826
+
827
+ BTC — ``12dxgVQ3sRFhc4g7M6oydsN2tTMMthJJqS``
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/core/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAI/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,KAAK,gBAAgB,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;AAyBjD,qBAAa,MAAM;IACjB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAE1D,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,WAAW,CAAK;IAExB;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAElD,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,YAAY,CAA+B;IAEnD,4DAA4D;IAC5D,OAAO,CAAC,YAAY,CAA6B;gBAG/C,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,iBAAiB,EAC1B,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,EAC3C,aAAa,EAAE,aAAa,EAC5B,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,gBAAgB;IAc5B,IAAI,MAAM,IAAI,YAAY,CAEzB;IAED,8BAA8B;IAC9B,KAAK,IAAI,IAAI;IAab;;;;;;;;OAQG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiD3B,OAAO,CAAC,YAAY;YAQN,IAAI;YAsBJ,SAAS;YAiCT,UAAU;YAwDV,eAAe;YAwDf,aAAa;IA0C3B,OAAO,CAAC,kBAAkB;YAQZ,kBAAkB;CAkBjC"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/core/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAI/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,KAAK,gBAAgB,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;AAyBjD,qBAAa,MAAM;IACjB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAE1D,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,WAAW,CAAK;IAExB;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAElD,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,YAAY,CAA+B;IAEnD,4DAA4D;IAC5D,OAAO,CAAC,YAAY,CAA6B;gBAG/C,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,iBAAiB,EAC1B,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,EAC3C,aAAa,EAAE,aAAa,EAC5B,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,gBAAgB;IAc5B,IAAI,MAAM,IAAI,YAAY,CAEzB;IAED,8BAA8B;IAC9B,KAAK,IAAI,IAAI;IAab;;;;;;;;OAQG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAqD3B,OAAO,CAAC,YAAY;YAQN,IAAI;YAsBJ,SAAS;YAiCT,UAAU;YAwDV,eAAe;YAwDf,aAAa;IA0C3B,OAAO,CAAC,kBAAkB;YAQZ,kBAAkB;CAkBjC"}
package/dist/index.cjs CHANGED
@@ -294,10 +294,11 @@ return job_id
294
294
  // Release lock
295
295
  // -------------------------------------------------------------------------
296
296
  async releaseLock(jobId) {
297
+ const now = (/* @__PURE__ */ new Date()).toISOString();
297
298
  await this.client.hSet(k.job(jobId), {
298
299
  lockId: "",
299
- lockExpiresAt: "",
300
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
300
+ lockExpiresAt: now,
301
+ updatedAt: now
301
302
  });
302
303
  }
303
304
  // -------------------------------------------------------------------------
@@ -678,7 +679,7 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
678
679
  async releaseLock(jobId) {
679
680
  await this.pool.query(
680
681
  `UPDATE qjw_jobs
681
- SET lock_id = NULL, lock_expires_at = NULL, updated_at = NOW()
682
+ SET lock_id = NULL, lock_expires_at = NOW(), updated_at = NOW()
682
683
  WHERE id = $1`,
683
684
  [jobId]
684
685
  );
@@ -1029,7 +1030,7 @@ CREATE TABLE IF NOT EXISTS qjw_rate_limits (
1029
1030
  async releaseLock(jobId) {
1030
1031
  await this.pool.query(
1031
1032
  `UPDATE qjw_jobs
1032
- SET lock_id = NULL, lock_expires_at = NULL, updated_at = NOW(3)
1033
+ SET lock_id = NULL, lock_expires_at = NOW(3), updated_at = NOW(3)
1033
1034
  WHERE id = ?`,
1034
1035
  [jobId]
1035
1036
  );
@@ -1904,9 +1905,10 @@ var InMemoryStorageAdapter = class {
1904
1905
  async releaseLock(jobId) {
1905
1906
  const job = this.jobs.get(jobId);
1906
1907
  if (!job) return;
1908
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1907
1909
  job.lockId = null;
1908
- job.lockExpiresAt = null;
1909
- job.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1910
+ job.lockExpiresAt = now;
1911
+ job.updatedAt = now;
1910
1912
  }
1911
1913
  // -------------------------------------------------------------------------
1912
1914
  // Recover stalled jobs