@holo-js/queue-redis 0.3.28 → 0.4.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/LICENSE +21 -0
- package/dist/.holo-build-fingerprint +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.mjs +68 -9
- package/package.json +18 -11
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Holo-JS contributors
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
5e7aef1ed0151465c363f05f2dfa0419c83bce155811e71a7c53694adeadfb47
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ declare class RedisQueueDriver implements QueueAsyncDriver {
|
|
|
31
31
|
readonly name: string;
|
|
32
32
|
readonly driver: "redis";
|
|
33
33
|
readonly mode: "async";
|
|
34
|
+
readonly acceptsJobId = true;
|
|
34
35
|
private readonly connection;
|
|
35
36
|
private readonly bullConnection;
|
|
36
37
|
private readonly managedRedisConnection?;
|
|
@@ -42,6 +43,9 @@ declare class RedisQueueDriver implements QueueAsyncDriver {
|
|
|
42
43
|
constructor(connection: NormalizedQueueRedisConnectionConfig, context: QueueDriverFactoryContext);
|
|
43
44
|
private getQueuePrefix;
|
|
44
45
|
private getQueue;
|
|
46
|
+
private getLockDuration;
|
|
47
|
+
private reportFailure;
|
|
48
|
+
private renewReservation;
|
|
45
49
|
private createWorkerKey;
|
|
46
50
|
private normalizeReserveBlockFor;
|
|
47
51
|
private resolveWorkerDrainDelay;
|
|
@@ -60,6 +64,9 @@ declare class RedisQueueDriver implements QueueAsyncDriver {
|
|
|
60
64
|
acknowledge(job: QueueReservedJob): Promise<void>;
|
|
61
65
|
release(job: QueueReservedJob, options?: QueueReleaseOptions): Promise<void>;
|
|
62
66
|
delete(job: QueueReservedJob): Promise<void>;
|
|
67
|
+
abandon(job: QueueReservedJob): void;
|
|
68
|
+
private countLegacyPausedJobs;
|
|
69
|
+
private countQueuedJobs;
|
|
63
70
|
clear(input?: {
|
|
64
71
|
readonly queueNames?: readonly string[];
|
|
65
72
|
}): Promise<number>;
|
|
@@ -78,4 +85,5 @@ declare const redisQueueDriverInternals: {
|
|
|
78
85
|
wrapRedisError: typeof wrapRedisError;
|
|
79
86
|
};
|
|
80
87
|
|
|
81
|
-
export {
|
|
88
|
+
export { RedisQueueDriver, RedisQueueDriverError, redisQueueDriverFactory, redisQueueDriverInternals };
|
|
89
|
+
export type { QueueDriverFactory };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import { reportError } from "@holo-js/kernel/error-reporting";
|
|
2
3
|
import { randomUUID } from "crypto";
|
|
3
4
|
import {
|
|
4
5
|
Queue as BullQueue,
|
|
6
|
+
UnrecoverableError,
|
|
5
7
|
Worker as BullWorker
|
|
6
8
|
} from "bullmq";
|
|
7
9
|
import Redis from "ioredis";
|
|
@@ -38,7 +40,7 @@ function parseClusterNodeUrl(url, label) {
|
|
|
38
40
|
...parsed.protocol === "rediss:" ? { tls: {} } : {}
|
|
39
41
|
};
|
|
40
42
|
} catch (error) {
|
|
41
|
-
throw new Error(`[Holo Queue] ${label} is invalid: ${error instanceof Error ? error.message : String(error)}
|
|
43
|
+
throw new Error(`[Holo Queue] ${label} is invalid: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
function resolveClusterStartupNodes(connection) {
|
|
@@ -132,9 +134,11 @@ var RedisQueueDriver = class {
|
|
|
132
134
|
this.bullConnection = bullConnection;
|
|
133
135
|
this.managedRedisConnection = bullConnection instanceof Redis || bullConnection instanceof Redis.Cluster ? bullConnection : void 0;
|
|
134
136
|
}
|
|
137
|
+
context;
|
|
135
138
|
name;
|
|
136
139
|
driver = "redis";
|
|
137
140
|
mode = "async";
|
|
141
|
+
acceptsJobId = true;
|
|
138
142
|
connection;
|
|
139
143
|
bullConnection;
|
|
140
144
|
managedRedisConnection;
|
|
@@ -162,6 +166,26 @@ var RedisQueueDriver = class {
|
|
|
162
166
|
this.queues.set(queueName, queue);
|
|
163
167
|
return queue;
|
|
164
168
|
}
|
|
169
|
+
getLockDuration() {
|
|
170
|
+
return this.connection.retryAfter * 1e3;
|
|
171
|
+
}
|
|
172
|
+
reportFailure(action, operation, error) {
|
|
173
|
+
reportError(wrapRedisError(this.name, action, error), { capability: "queue", operation });
|
|
174
|
+
}
|
|
175
|
+
renewReservation(job, token) {
|
|
176
|
+
const lockDuration = this.getLockDuration();
|
|
177
|
+
const renewal = setInterval(() => {
|
|
178
|
+
job.extendLock(token, lockDuration).then((extended) => {
|
|
179
|
+
if (extended === 0) {
|
|
180
|
+
clearInterval(renewal);
|
|
181
|
+
}
|
|
182
|
+
}, (error) => {
|
|
183
|
+
this.reportFailure("renew job reservation", "redis.renewReservation", error);
|
|
184
|
+
});
|
|
185
|
+
}, lockDuration / 2);
|
|
186
|
+
renewal.unref();
|
|
187
|
+
return renewal;
|
|
188
|
+
}
|
|
165
189
|
createWorkerKey(queueName, blockFor) {
|
|
166
190
|
return `${queueName}:${blockFor}`;
|
|
167
191
|
}
|
|
@@ -196,7 +220,9 @@ var RedisQueueDriver = class {
|
|
|
196
220
|
connection: this.bullConnection,
|
|
197
221
|
prefix: this.getQueuePrefix(queueName),
|
|
198
222
|
drainDelay: this.resolveWorkerDrainDelay(blockFor),
|
|
199
|
-
lockDuration: this.
|
|
223
|
+
lockDuration: this.getLockDuration(),
|
|
224
|
+
stalledInterval: this.getLockDuration(),
|
|
225
|
+
maxStalledCount: Number.MAX_SAFE_INTEGER,
|
|
200
226
|
removeOnComplete: { count: 0 },
|
|
201
227
|
removeOnFail: { count: 0 }
|
|
202
228
|
}
|
|
@@ -205,7 +231,13 @@ var RedisQueueDriver = class {
|
|
|
205
231
|
queueName,
|
|
206
232
|
worker
|
|
207
233
|
};
|
|
208
|
-
|
|
234
|
+
worker.on("error", (error) => {
|
|
235
|
+
this.reportFailure("recover abandoned reservations", "redis.recoverAbandonedReservations", error);
|
|
236
|
+
});
|
|
237
|
+
const ready = worker.waitUntilReady().then(async () => {
|
|
238
|
+
await worker.startStalledCheckTimer();
|
|
239
|
+
return workerRecord;
|
|
240
|
+
}).catch(async (error) => {
|
|
209
241
|
if (this.workers.get(workerKey) === workerRecord) {
|
|
210
242
|
this.workers.delete(workerKey);
|
|
211
243
|
}
|
|
@@ -265,7 +297,8 @@ var RedisQueueDriver = class {
|
|
|
265
297
|
});
|
|
266
298
|
this.reservations.set(token, {
|
|
267
299
|
job,
|
|
268
|
-
token
|
|
300
|
+
token,
|
|
301
|
+
renewal: this.renewReservation(job, token)
|
|
269
302
|
});
|
|
270
303
|
return {
|
|
271
304
|
reservationId: token,
|
|
@@ -282,6 +315,7 @@ var RedisQueueDriver = class {
|
|
|
282
315
|
}
|
|
283
316
|
async settleReservation(reserved, action, callback) {
|
|
284
317
|
const reservation = this.getReservation(reserved);
|
|
318
|
+
clearInterval(reservation.renewal);
|
|
285
319
|
try {
|
|
286
320
|
await callback(reservation);
|
|
287
321
|
} catch (error) {
|
|
@@ -353,18 +387,40 @@ var RedisQueueDriver = class {
|
|
|
353
387
|
}
|
|
354
388
|
async delete(job) {
|
|
355
389
|
await this.settleReservation(job, "delete job", async (reservation) => {
|
|
356
|
-
reservation.job.
|
|
357
|
-
await reservation.job.moveToFailed(new Error("[Holo Queue] Job deleted."), reservation.token, false);
|
|
390
|
+
await reservation.job.moveToFailed(new UnrecoverableError("[Holo Queue] Job deleted."), reservation.token, false);
|
|
358
391
|
});
|
|
359
392
|
}
|
|
393
|
+
abandon(job) {
|
|
394
|
+
const reservation = this.reservations.get(job.reservationId);
|
|
395
|
+
if (!reservation) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
clearInterval(reservation.renewal);
|
|
399
|
+
this.reservations.delete(job.reservationId);
|
|
400
|
+
}
|
|
401
|
+
async countLegacyPausedJobs(queueName) {
|
|
402
|
+
const client = await this.getQueue(queueName).getBackend().client;
|
|
403
|
+
const pausedKey = `${this.getQueuePrefix(queueName)}:${queueName}:paused`;
|
|
404
|
+
const count = await client.llen(pausedKey);
|
|
405
|
+
if (count === 0) {
|
|
406
|
+
return 0;
|
|
407
|
+
}
|
|
408
|
+
const [last] = await client.lrange(pausedKey, -1, -1);
|
|
409
|
+
return last?.startsWith("0:") ? count - 1 : count;
|
|
410
|
+
}
|
|
411
|
+
async countQueuedJobs(queueName) {
|
|
412
|
+
const counted = await this.getQueue(queueName).getJobCountByTypes("wait", "prioritized", "delayed");
|
|
413
|
+
return counted + await this.countLegacyPausedJobs(queueName);
|
|
414
|
+
}
|
|
360
415
|
async clear(input) {
|
|
361
416
|
try {
|
|
362
417
|
const queueNames = this.normalizeQueueNames(input?.queueNames);
|
|
363
418
|
let cleared = 0;
|
|
364
419
|
for (const queueName of queueNames) {
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
await
|
|
420
|
+
const before = await this.countQueuedJobs(queueName);
|
|
421
|
+
await this.getQueue(queueName).drain(true);
|
|
422
|
+
const after = await this.countQueuedJobs(queueName);
|
|
423
|
+
cleared += Math.max(0, before - after);
|
|
368
424
|
}
|
|
369
425
|
return cleared;
|
|
370
426
|
} catch (error) {
|
|
@@ -377,6 +433,9 @@ var RedisQueueDriver = class {
|
|
|
377
433
|
...this.queues.values()
|
|
378
434
|
];
|
|
379
435
|
const workerReadyPromises = [...this.workerReadyPromises.values()];
|
|
436
|
+
for (const reservation of this.reservations.values()) {
|
|
437
|
+
clearInterval(reservation.renewal);
|
|
438
|
+
}
|
|
380
439
|
this.reservations.clear();
|
|
381
440
|
this.workerReadyPromises.clear();
|
|
382
441
|
this.workers.clear();
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/queue-redis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Holo-JS Framework - Redis queue driver",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/cobraprojects/holo-js.git",
|
|
10
|
+
"directory": "packages/queue-redis"
|
|
11
|
+
},
|
|
7
12
|
"exports": {
|
|
8
13
|
".": {
|
|
9
14
|
"types": "./dist/index.d.ts",
|
|
@@ -14,7 +19,8 @@
|
|
|
14
19
|
"main": "./dist/index.mjs",
|
|
15
20
|
"types": "./dist/index.d.ts",
|
|
16
21
|
"files": [
|
|
17
|
-
"dist"
|
|
22
|
+
"dist",
|
|
23
|
+
"LICENSE"
|
|
18
24
|
],
|
|
19
25
|
"scripts": {
|
|
20
26
|
"build": "tsup",
|
|
@@ -23,21 +29,22 @@
|
|
|
23
29
|
"test": "vitest --run"
|
|
24
30
|
},
|
|
25
31
|
"peerDependencies": {
|
|
26
|
-
"@holo-js/queue": "^0.
|
|
32
|
+
"@holo-js/queue": "^0.4.1"
|
|
27
33
|
},
|
|
28
34
|
"dependencies": {
|
|
29
|
-
"
|
|
30
|
-
"
|
|
35
|
+
"@holo-js/kernel": "^0.4.1",
|
|
36
|
+
"bullmq": "^6.4.0",
|
|
37
|
+
"ioredis": "^6.0.0",
|
|
31
38
|
"tslib": "^2.8.1"
|
|
32
39
|
},
|
|
33
40
|
"devDependencies": {
|
|
34
|
-
"@holo-js/queue": "^0.
|
|
35
|
-
"@types/node": "^22.
|
|
36
|
-
"tsup": "^
|
|
37
|
-
"typescript": "^
|
|
38
|
-
"vitest": "^
|
|
41
|
+
"@holo-js/queue": "^0.4.1",
|
|
42
|
+
"@types/node": "^22.20.5",
|
|
43
|
+
"tsup": "^9.0.0",
|
|
44
|
+
"typescript": "^6.0.3",
|
|
45
|
+
"vitest": "^5.0.5"
|
|
39
46
|
},
|
|
40
47
|
"engines": {
|
|
41
|
-
"node": "^22.
|
|
48
|
+
"node": "^22.18.0 || ^24.0.0 || ^26.0.0"
|
|
42
49
|
}
|
|
43
50
|
}
|