queue-jobs-worker 1.0.3 → 1.0.4

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
@@ -4,8 +4,7 @@ All notable changes to **queue-jobs-worker** will be documented in this file.
4
4
 
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
  This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [1.0.3] — 2026-09-09
7
+ ## [1.0.4] — 2026-09-13
9
8
 
10
9
  ### Core
11
10
 
@@ -22,56 +21,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
22
21
  - When job execution times out, the worker aborts the `AbortSignal` with a timeout error before rejecting the wrapper promise.
23
22
  - User processors can monitor `signal.aborted` or pass `signal` to async operations (e.g. `fetch`, database queries, timers) for cooperative cancellation.
24
23
 
25
- ### Package
26
-
27
- ### Fixed
28
-
29
- - **`package.json` — Added `assets` to npm package `files` distribution**
30
-
31
- Added `"assets"` to the `"files"` list in `package.json` so header banner graphics in `README.md` display properly on npmjs.com.
32
-
33
- ---
34
-
35
- ## [1.0.2] — 2026-09-05
36
-
37
- ### Core
38
-
39
- ### Fixed
40
-
41
- - **`Worker` — croner added as a required dependency; invalid expressions no longer fall back to a 1-minute interval** ([#5](https://github.com/rafidahmed870/queue-jobs-worker/issues/5))
42
-
43
- `enqueueCronNext()` previously attempted a dynamic `import("croner")` inside
44
- a try/catch. If the import failed — or if the resolved `Cron` class was not a
45
- function — the code silently fell back to `Date.now() + 60_000`, scheduling
46
- the next run 60 seconds later regardless of the configured cron expression.
47
- The same silent fallback was also triggered for invalid cron expressions that
48
- caused the `Cron` constructor to throw.
49
-
50
- After the fix:
51
-
52
- - `croner` is now declared as a proper `dependency` in `package.json`
53
- (`^10.0.1`) and imported statically, so it is always available without any
54
- dynamic-import dance.
55
- - If the `Cron` constructor throws (invalid expression), a descriptive
56
- `worker:error` event is emitted and re-enqueue is skipped. The worker
57
- remains running.
58
- - If `cronInstance.nextRun()` returns `null` (the schedule has no future
59
- occurrences), a `worker:error` is emitted and re-enqueue is skipped. Again,
60
- the worker keeps running.
61
- - The 1-minute fallback path has been removed entirely — there is no silent
62
- fallback under any failure condition.
63
-
64
- - **`Worker` — rate-limit quota no longer consumed on empty-queue polls** ([#4](https://github.com/rafidahmed870/queue-jobs-worker/issues/4))
65
-
66
- `claimNext()` previously called `checkAndIncrementRateLimit()` before
67
- attempting to claim a job. This meant every poll cycle against an empty queue
68
- burned a quota slot, potentially exhausting the configured window budget
69
- before any real work was done. After the fix, the storage `claim()` call
70
- happens first; the rate-limit counter is only incremented when a job is
71
- actually claimed for processing. If the rate limit is reached at that point
72
- the lock is immediately released via `releaseLock()` so the job remains
73
- reclaimable on the next window.
74
-
75
24
  ---
76
25
 
77
26
  ### Events
@@ -121,18 +70,72 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
121
70
 
122
71
  ---
123
72
 
124
- ## [1.0.1] — 2026-08-31
73
+ ## [1.0.3] — 2026-09-09
125
74
 
126
75
  ### Core
127
76
 
128
77
  ### Fixed
129
78
 
130
- - **`Worker.stop()` — clarified `releaseLock()` behavior in shutdown comment** ([#1](https://github.com/rafidahmed870/queue-jobs-worker/issues/1))
79
+ - **`Worker` — Job timeout cooperative cancellation via `AbortSignal`** ([#12](https://github.com/rafidahmed870/queue-jobs-worker/issues/12))
131
80
 
132
- The inline comment in `worker.ts` now correctly explains that `releaseLock()`
133
- sets `lockExpiresAt` to an already-expired timestamp (not null/empty), so
134
- `recoverStalledJobs()` on any worker will immediately reclaim the job on the
135
- next stall-check cycle.
81
+ Previously, when a job attempt reached its configured `timeout`, the worker rejected the internal execution promise and marked the attempt as failed (or scheduled a retry), but the underlying processor `Promise` continued running in the background. This could lead to duplicate side effects when retries overlapped with timed-out attempts.
82
+
83
+ After the fix:
84
+
85
+ - `Processor` type signature is updated: `type Processor<TPayload = unknown> = (job: Job<TPayload>, signal: AbortSignal) => Promise<void>`.
86
+ - An `AbortController` is created for each job attempt.
87
+ - When job execution times out, the worker aborts the `AbortSignal` with a timeout error before rejecting the wrapper promise.
88
+ - User processors can monitor `signal.aborted` or pass `signal` to async operations (e.g. `fetch`, database queries, timers) for cooperative cancellation.
89
+
90
+ ### Package
91
+
92
+ ### Fixed
93
+
94
+ - **`package.json` — Added `assets` to npm package `files` distribution**
95
+
96
+ Added `"assets"` to the `"files"` list in `package.json` so header banner graphics in `README.md` display properly on npmjs.com.
97
+
98
+ ---
99
+
100
+ ## [1.0.2] — 2026-09-05
101
+
102
+ ### Core
103
+
104
+ ### Fixed
105
+
106
+ - **`Worker` — croner added as a required dependency; invalid expressions no longer fall back to a 1-minute interval** ([#5](https://github.com/rafidahmed870/queue-jobs-worker/issues/5))
107
+
108
+ `enqueueCronNext()` previously attempted a dynamic `import("croner")` inside
109
+ a try/catch. If the import failed — or if the resolved `Cron` class was not a
110
+ function — the code silently fell back to `Date.now() + 60_000`, scheduling
111
+ the next run 60 seconds later regardless of the configured cron expression.
112
+ The same silent fallback was also triggered for invalid cron expressions that
113
+ caused the `Cron` constructor to throw.
114
+
115
+ After the fix:
116
+
117
+ - `croner` is now declared as a proper `dependency` in `package.json`
118
+ (`^10.0.1`) and imported statically, so it is always available without any
119
+ dynamic-import dance.
120
+ - If the `Cron` constructor throws (invalid expression), a descriptive
121
+ `worker:error` event is emitted and re-enqueue is skipped. The worker
122
+ remains running.
123
+ - If `cronInstance.nextRun()` returns `null` (the schedule has no future
124
+ occurrences), a `worker:error` is emitted and re-enqueue is skipped. Again,
125
+ the worker keeps running.
126
+ - The 1-minute fallback path has been removed entirely — there is no silent
127
+ fallback under any failure condition.
128
+
129
+ - **`Worker` — rate-limit quota no longer consumed on empty-queue polls** ([#4](https://github.com/rafidahmed870/queue-jobs-worker/issues/4))
130
+
131
+ `claimNext()` previously called `checkAndIncrementRateLimit()` before
132
+ attempting to claim a job. This meant every poll cycle against an empty queue
133
+ burned a quota slot, potentially exhausting the configured window budget
134
+ before any real work was done. After the fix, the storage `claim()` call
135
+ happens first; the rate-limit counter is only incremented when a job is
136
+ actually claimed for processing. If the rate limit is reached at that point
137
+ the lock is immediately released via `releaseLock()` so the job remains
138
+ reclaimable on the next window.
136
139
 
137
140
  ---
138
141
 
@@ -148,6 +151,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
148
151
 
149
152
  <!-- Links -->
150
153
 
154
+ [1.0.4]: https://github.com/rafidahmed870/queue-jobs-worker/compare/v1.0.0...v1.0.4
155
+ [1.0.0]: https://github.com/rafidahmed870/queue-jobs-worker/releases/tag/v1.0.0
151
156
  [1.0.3]: https://github.com/rafidahmed870/queue-jobs-worker/compare/v1.0.2...v1.0.3
152
157
  [1.0.2]: https://github.com/rafidahmed870/queue-jobs-worker/compare/v1.0.1...v1.0.2
153
158
  [1.0.0]: https://github.com/rafidahmed870/queue-jobs-worker/releases/tag/v1.0.0
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # queue-jobs-worker
4
4
 
5
- A durable, TypeScript-first job queue for Node.js built for asynchronous work, retries, scheduling, and recovery.
5
+ A durable, TypeScript-first job queue for Node.js built for asynchronous work, retries, scheduling, and recovery. It based on multiple storage adapter with postgresql, mysql, redis also in-memory support for dev/testing.
6
6
 
7
7
  <p align="center">
8
8
  <a href="https://www.npmjs.com/package/queue-jobs-worker">
@@ -86,85 +86,40 @@ If you are using TypeScript, you can optionally make the queue payload type-safe
86
86
 
87
87
  ## Supported Backends
88
88
 
89
- ### Memory
90
-
91
- Use the in-memory backend for local development and tests. Data is not persisted across restarts.
89
+ Pass `dialect` and `connectionString` to `QueueClient`. Call `await client.init()` to establish database connections and create required schema tables.
92
90
 
93
91
  ```js
94
- const client = new QueueClient();
95
- // or explicitly:
92
+ // 1. In-Memory (Default for local development & tests, no persistence)
96
93
  const client = new QueueClient({ dialect: "memory" });
97
- ```
98
-
99
- `init()` is effectively a no-op for this backend.
100
-
101
- ### Redis
102
-
103
- ```js
104
- const { QueueClient } = require("queue-jobs-worker");
105
-
106
- const client = new QueueClient({
107
- dialect: "redis",
108
- connectionString: "redis://localhost:6379",
109
- });
110
-
111
- await client.init();
112
- const jobs = client.createQueue("jobs");
113
- ```
114
-
115
- With authentication:
116
94
 
117
- ```js
95
+ // 2. Redis (Supports standard redis://, TLS rediss://, and authentication)
118
96
  const client = new QueueClient({
119
97
  dialect: "redis",
120
- connectionString: "redis://:yourpassword@redis-host:6379/0",
98
+ connectionString: process.env.REDIS_URL || "redis://localhost:6379",
121
99
  });
122
100
 
123
- await client.init();
124
- ```
125
-
126
- With TLS:
127
-
128
- ```js
129
- const client = new QueueClient({
130
- dialect: "redis",
131
- connectionString: "rediss://user:password@host:6380",
132
- });
133
-
134
- await client.init();
135
- ```
136
-
137
- `init()` creates the Redis client, connects to the server, sends `PING`, and verifies the response is `PONG`.
138
-
139
- ### PostgreSQL
140
-
141
- ```js
142
- const { QueueClient } = require("queue-jobs-worker");
143
-
101
+ // 3. PostgreSQL (Auto-creates required queue tables on init)
144
102
  const client = new QueueClient({
145
103
  dialect: "postgres",
146
- connectionString: "postgresql://user:password@localhost:5432/mydb",
104
+ connectionString: process.env.POSTGRES_URL || "postgresql://user:password@localhost:5432/mydb",
147
105
  });
148
106
 
149
- await client.init();
150
- ```
151
-
152
- `init()` verifies connectivity with `SELECT 1` and creates the queue tables if they do not already exist.
153
-
154
- ### MySQL
155
-
156
- ```js
157
- const { QueueClient } = require("queue-jobs-worker");
158
-
107
+ // 4. MySQL (Auto-creates required queue tables on init)
159
108
  const client = new QueueClient({
160
109
  dialect: "mysql",
161
- connectionString: "mysql://user:password@localhost:3306/mydb",
110
+ connectionString: process.env.MYSQL_URL || "mysql://user:password@localhost:3306/mydb",
162
111
  });
163
112
 
113
+ // Initialize backend connection (Required for Redis, PostgreSQL, MySQL)
164
114
  await client.init();
165
115
  ```
166
116
 
167
- `init()` validates the connection and creates the required tables in the database.
117
+ | Dialect | Connection Format | `client.init()` Behavior |
118
+ |---|---|---|
119
+ | `memory` | N/A | No-op (transient memory store) |
120
+ | `redis` | `redis://...`, `rediss://...` (TLS), Auth URL | Connects & verifies with `PING` |
121
+ | `postgres` | `postgresql://user:pass@host:5432/dbname` | `SELECT 1` check & creates schema |
122
+ | `mysql` | `mysql://user:pass@host:3306/dbname` | Connection check & creates schema |
168
123
 
169
124
  ---
170
125
 
@@ -36,6 +36,8 @@ export declare class QueueClient {
36
36
  private readonly queues;
37
37
  private initialised;
38
38
  private closed;
39
+ /** True when the adapter was supplied via {@link withAdapter}; init() skips resolveAdapter() in that case. */
40
+ private _customAdapter;
39
41
  constructor(options?: QueueClientOptions);
40
42
  private buildMemoryOrEagerAdapter;
41
43
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/core/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAkB,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAwBnC,qBAAa,WAAW;IACtB,0DAA0D;IAC1D,QAAQ,EAAE,cAAc,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA2B;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;IAC5D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,GAAE,kBAAuB;IAe5C,OAAO,CAAC,yBAAyB;IAajC;;;;;;;;;;;;;OAaG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAU3B,gCAAgC;IAC1B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAInB,cAAc;IAkC5B;;;;;OAKG;IACH,WAAW,CAAC,QAAQ,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC;IAgBtF,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,SAAS;IAIvE,wDAAwD;IACxD,YAAY,CAAC,QAAQ,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC;IAQ/D,qDAAqD;IACrD,IAAI,UAAU,IAAI,MAAM,EAAE,CAEzB;IAED,wDAAwD;IACxD,IAAI,aAAa,IAAI,OAAO,CAE3B;IAMD,EAAE,CAAC,CAAC,SAAS,MAAM,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAK5F,IAAI,CAAC,CAAC,SAAS,MAAM,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAK9F,GAAG,CAAC,CAAC,SAAS,MAAM,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAS7F;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B;;;;;;;;OAQG;IACH,MAAM,CAAC,WAAW,CAChB,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,kBAAkB,CAAM,GACrE,WAAW;CAKf"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/core/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAkB,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAwBnC,qBAAa,WAAW;IACtB,0DAA0D;IAC1D,QAAQ,EAAE,cAAc,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA2B;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;IAC5D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,MAAM,CAAS;IACvB,8GAA8G;IAC9G,OAAO,CAAC,cAAc,CAAS;gBAEnB,OAAO,GAAE,kBAAuB;IAe5C,OAAO,CAAC,yBAAyB;IAajC;;;;;;;;;;;;;OAaG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,gCAAgC;IAC1B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAInB,cAAc;IAkC5B;;;;;OAKG;IACH,WAAW,CAAC,QAAQ,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC;IAgBtF,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,SAAS;IAIvE,wDAAwD;IACxD,YAAY,CAAC,QAAQ,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC;IAQ/D,qDAAqD;IACrD,IAAI,UAAU,IAAI,MAAM,EAAE,CAEzB;IAED,wDAAwD;IACxD,IAAI,aAAa,IAAI,OAAO,CAE3B;IAMD,EAAE,CAAC,CAAC,SAAS,MAAM,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAK5F,IAAI,CAAC,CAAC,SAAS,MAAM,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAK9F,GAAG,CAAC,CAAC,SAAS,MAAM,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAS7F;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B;;;;;;;;OAQG;IACH,MAAM,CAAC,WAAW,CAChB,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,kBAAkB,CAAM,GACrE,WAAW;CAMf"}
package/dist/index.cjs CHANGED
@@ -2350,6 +2350,8 @@ var QueueClient = class _QueueClient {
2350
2350
  queues = /* @__PURE__ */ new Map();
2351
2351
  initialised = false;
2352
2352
  closed = false;
2353
+ /** True when the adapter was supplied via {@link withAdapter}; init() skips resolveAdapter() in that case. */
2354
+ _customAdapter = false;
2353
2355
  constructor(options = {}) {
2354
2356
  this.defaults = { ...HARD_DEFAULTS, ...options.defaults };
2355
2357
  this.dialect = options.dialect ?? "memory";
@@ -2385,7 +2387,9 @@ var QueueClient = class _QueueClient {
2385
2387
  */
2386
2388
  async init() {
2387
2389
  if (this.initialised) return;
2388
- this._storage = await this.resolveAdapter();
2390
+ if (!this._customAdapter) {
2391
+ this._storage = await this.resolveAdapter();
2392
+ }
2389
2393
  await this._storage.initialize();
2390
2394
  this.initialised = true;
2391
2395
  }
@@ -2502,6 +2506,7 @@ var QueueClient = class _QueueClient {
2502
2506
  static withAdapter(adapter, options = {}) {
2503
2507
  const client = new _QueueClient(options);
2504
2508
  client._storage = adapter;
2509
+ client._customAdapter = true;
2505
2510
  return client;
2506
2511
  }
2507
2512
  };