@hile/redis-stream-queue 3.0.2 → 3.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/dist/queue.d.ts +4 -0
- package/dist/queue.js +42 -12
- package/dist/types.d.ts +5 -0
- package/package.json +2 -2
package/dist/queue.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare class RedisStreamQueue {
|
|
|
9
9
|
readDeadLetters<TData>(definition: QueueDefinition<TData>, options?: ReadDeadLettersOptions): Promise<Array<QueueDeadLetter<TData>>>;
|
|
10
10
|
runWorkerOnce<TData>(definition: QueueDefinition<TData>, handler: QueueWorkerHandler<TData>, options: RequiredWorkerOptions): Promise<number>;
|
|
11
11
|
private processEntry;
|
|
12
|
+
private acknowledge;
|
|
12
13
|
private handleFailure;
|
|
13
14
|
private createJob;
|
|
14
15
|
private promoteDelayed;
|
|
@@ -41,7 +42,10 @@ type RequiredWorkerOptions = {
|
|
|
41
42
|
concurrency: number;
|
|
42
43
|
block: number;
|
|
43
44
|
pollInterval: number;
|
|
45
|
+
errorRetryInterval: number;
|
|
44
46
|
claimIdle: number;
|
|
45
47
|
claimCount: number;
|
|
48
|
+
removeOnAck: boolean;
|
|
49
|
+
onError?: (err: unknown) => void | Promise<void>;
|
|
46
50
|
};
|
|
47
51
|
export {};
|
package/dist/queue.js
CHANGED
|
@@ -6,7 +6,16 @@ const DEFAULT_MAX_ATTEMPTS = 1;
|
|
|
6
6
|
const DEFAULT_CONCURRENCY = 1;
|
|
7
7
|
const DEFAULT_CLAIM_IDLE = 60_000;
|
|
8
8
|
const DEFAULT_POLL_INTERVAL = 1_000;
|
|
9
|
+
const DEFAULT_ERROR_RETRY_INTERVAL = 1_000;
|
|
9
10
|
const DEFAULT_READ_BLOCK = 0;
|
|
11
|
+
const PROMOTE_DELAYED_SCRIPT = `
|
|
12
|
+
if redis.call('ZSCORE', KEYS[1], ARGV[1]) == false then
|
|
13
|
+
return 0
|
|
14
|
+
end
|
|
15
|
+
local streamId = redis.call('XADD', KEYS[2], '*', 'job', ARGV[1])
|
|
16
|
+
redis.call('ZREM', KEYS[1], ARGV[1])
|
|
17
|
+
return streamId
|
|
18
|
+
`;
|
|
10
19
|
export class RedisStreamQueue {
|
|
11
20
|
redis;
|
|
12
21
|
prefix;
|
|
@@ -128,13 +137,20 @@ export class RedisStreamQueue {
|
|
|
128
137
|
else {
|
|
129
138
|
await run();
|
|
130
139
|
}
|
|
131
|
-
await this.redis.xack(this.streamKey(definition), options.group, streamId);
|
|
132
140
|
}
|
|
133
141
|
catch (err) {
|
|
134
|
-
await this.handleFailure(definition, stored, streamId, attempt, options
|
|
142
|
+
await this.handleFailure(definition, stored, streamId, attempt, options, err);
|
|
143
|
+
return;
|
|
135
144
|
}
|
|
145
|
+
await this.acknowledge(definition, options, streamId);
|
|
136
146
|
}
|
|
137
|
-
async
|
|
147
|
+
async acknowledge(definition, options, streamId) {
|
|
148
|
+
await this.redis.xack(this.streamKey(definition), options.group, streamId);
|
|
149
|
+
if (options.removeOnAck) {
|
|
150
|
+
await this.redis.xdel(this.streamKey(definition), streamId);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async handleFailure(definition, stored, streamId, attempt, options, err) {
|
|
138
154
|
const reason = errorReason(err);
|
|
139
155
|
const failed = {
|
|
140
156
|
...stored,
|
|
@@ -150,7 +166,7 @@ export class RedisStreamQueue {
|
|
|
150
166
|
else {
|
|
151
167
|
await this.redis.xadd(this.deadLetterKey(definition), '*', 'job', this.encodeJob(definition, failed));
|
|
152
168
|
}
|
|
153
|
-
await this.
|
|
169
|
+
await this.acknowledge(definition, options, streamId);
|
|
154
170
|
}
|
|
155
171
|
createJob(definition, stored, streamId, attempt) {
|
|
156
172
|
return {
|
|
@@ -170,11 +186,9 @@ export class RedisStreamQueue {
|
|
|
170
186
|
const members = await this.redis.zrangebyscore(this.delayedKey(definition), '-inf', this.now(), 'LIMIT', 0, limit);
|
|
171
187
|
let promoted = 0;
|
|
172
188
|
for (const member of members) {
|
|
173
|
-
const
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
await this.redis.xadd(this.streamKey(definition), '*', 'job', member);
|
|
177
|
-
promoted++;
|
|
189
|
+
const streamId = await this.redis.eval(PROMOTE_DELAYED_SCRIPT, 2, this.delayedKey(definition), this.streamKey(definition), member);
|
|
190
|
+
if (streamId !== 0)
|
|
191
|
+
promoted++;
|
|
178
192
|
}
|
|
179
193
|
return promoted;
|
|
180
194
|
}
|
|
@@ -276,9 +290,22 @@ export class RedisStreamQueueWorker {
|
|
|
276
290
|
}
|
|
277
291
|
async runLoop() {
|
|
278
292
|
while (this.running) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
293
|
+
try {
|
|
294
|
+
const processed = await this.runOnce();
|
|
295
|
+
if (processed === 0) {
|
|
296
|
+
await sleep(this.options.pollInterval);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
try {
|
|
301
|
+
await this.options.onError?.(err);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
// Error observers must not stop a worker that is already recovering.
|
|
305
|
+
}
|
|
306
|
+
if (this.running) {
|
|
307
|
+
await sleep(this.options.errorRetryInterval);
|
|
308
|
+
}
|
|
282
309
|
}
|
|
283
310
|
}
|
|
284
311
|
}
|
|
@@ -291,8 +318,11 @@ function normalizeWorkerOptions(definition, options) {
|
|
|
291
318
|
concurrency,
|
|
292
319
|
block: assertNonNegativeInteger(options.block ?? DEFAULT_READ_BLOCK, 'block'),
|
|
293
320
|
pollInterval: assertNonNegativeInteger(options.pollInterval ?? DEFAULT_POLL_INTERVAL, 'pollInterval'),
|
|
321
|
+
errorRetryInterval: assertNonNegativeInteger(options.errorRetryInterval ?? DEFAULT_ERROR_RETRY_INTERVAL, 'errorRetryInterval'),
|
|
294
322
|
claimIdle: assertNonNegativeInteger(options.claimIdle ?? DEFAULT_CLAIM_IDLE, 'claimIdle'),
|
|
295
323
|
claimCount: assertPositiveInteger(options.claimCount ?? concurrency, 'claimCount'),
|
|
324
|
+
removeOnAck: options.removeOnAck ?? false,
|
|
325
|
+
onError: options.onError,
|
|
296
326
|
};
|
|
297
327
|
}
|
|
298
328
|
function normalizeBackoff(backoff) {
|
package/dist/types.d.ts
CHANGED
|
@@ -66,8 +66,11 @@ export type QueueWorkerOptions = {
|
|
|
66
66
|
concurrency?: number;
|
|
67
67
|
block?: number;
|
|
68
68
|
pollInterval?: number;
|
|
69
|
+
errorRetryInterval?: number;
|
|
69
70
|
claimIdle?: number;
|
|
70
71
|
claimCount?: number;
|
|
72
|
+
removeOnAck?: boolean;
|
|
73
|
+
onError?: (err: unknown) => void | Promise<void>;
|
|
71
74
|
};
|
|
72
75
|
export type RedisStreamEntry = [id: string, fields: string[]];
|
|
73
76
|
export type RedisStreamReadResult = Array<[stream: string, entries: RedisStreamEntry[]]> | null;
|
|
@@ -79,10 +82,12 @@ export interface RedisStreamQueueLike {
|
|
|
79
82
|
xpending(...args: any[]): Promise<any>;
|
|
80
83
|
xclaim(...args: any[]): Promise<any>;
|
|
81
84
|
xack(...args: any[]): Promise<any>;
|
|
85
|
+
xdel(...args: any[]): Promise<any>;
|
|
82
86
|
xrange(...args: any[]): Promise<any>;
|
|
83
87
|
zadd(...args: any[]): Promise<any>;
|
|
84
88
|
zrangebyscore(...args: any[]): Promise<any>;
|
|
85
89
|
zrem(...args: any[]): Promise<any>;
|
|
90
|
+
eval(...args: any[]): Promise<any>;
|
|
86
91
|
set(...args: any[]): Promise<any>;
|
|
87
92
|
get(...args: any[]): Promise<any>;
|
|
88
93
|
del(...args: any[]): Promise<any>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hile/redis-stream-queue",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "Redis Streams backed durable job queue primitives for Hile applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@hile/context": "^3.0.2"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "f5d5970c964440047dda973d3f4eda92116ca809"
|
|
30
30
|
}
|