@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 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.group, err);
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 handleFailure(definition, stored, streamId, attempt, group, err) {
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.redis.xack(this.streamKey(definition), group, streamId);
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 removed = await this.redis.zrem(this.delayedKey(definition), member);
174
- if (removed === 0)
175
- continue;
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
- const processed = await this.runOnce();
280
- if (processed === 0) {
281
- await sleep(this.options.pollInterval);
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.2",
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": "0985b6f8abc1f4de0a36324063585fdc3ac1375b"
29
+ "gitHead": "f5d5970c964440047dda973d3f4eda92116ca809"
30
30
  }