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 +63 -58
- package/README.md +16 -61
- package/assets/queue-jobs-worker-demo.mp4 +0 -0
- package/dist/core/client.d.ts +2 -0
- package/dist/core/client.d.ts.map +1 -1
- package/dist/index.cjs +6 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
73
|
+
## [1.0.3] — 2026-09-09
|
|
125
74
|
|
|
126
75
|
### Core
|
|
127
76
|
|
|
128
77
|
### Fixed
|
|
129
78
|
|
|
130
|
-
- **`Worker
|
|
79
|
+
- **`Worker` — Job timeout cooperative cancellation via `AbortSignal`** ([#12](https://github.com/rafidahmed870/queue-jobs-worker/issues/12))
|
|
131
80
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
95
|
+
// 2. Redis (Supports standard redis://, TLS rediss://, and authentication)
|
|
118
96
|
const client = new QueueClient({
|
|
119
97
|
dialect: "redis",
|
|
120
|
-
connectionString: "redis
|
|
98
|
+
connectionString: process.env.REDIS_URL || "redis://localhost:6379",
|
|
121
99
|
});
|
|
122
100
|
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
Binary file
|
package/dist/core/client.d.ts
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
};
|