redis-consumer 1.1.9 → 1.2.0
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/README.md +255 -116
- package/dist/client.d.ts +8 -7
- package/dist/client.js +30 -20
- package/dist/consumer.d.ts +61 -9
- package/dist/consumer.js +273 -163
- package/dist/helpers.d.ts +5 -2
- package/dist/helpers.js +59 -5
- package/dist/index.d.ts +5 -9
- package/dist/index.js +3 -22
- package/dist/producer.d.ts +4 -4
- package/dist/producer.js +2 -6
- package/dist/retry-processor.d.ts +14 -11
- package/dist/retry-processor.js +51 -22
- package/package.json +13 -8
package/dist/index.js
CHANGED
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.RedisClient = void 0;
|
|
18
|
-
__exportStar(require("redis"), exports);
|
|
19
|
-
var client_1 = require("./client");
|
|
20
|
-
Object.defineProperty(exports, "RedisClient", { enumerable: true, get: function () { return client_1.RedisClient; } });
|
|
21
|
-
__exportStar(require("./consumer"), exports);
|
|
22
|
-
__exportStar(require("./producer"), exports);
|
|
1
|
+
export { RedisClient } from './client.js';
|
|
2
|
+
export * from './consumer.js';
|
|
3
|
+
export * from './producer.js';
|
package/dist/producer.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { RedisScripts } from 'redis';
|
|
2
|
-
import { StreamMessageData, StreamMessageId } from '.';
|
|
3
|
-
import { RedisClient } from './client';
|
|
1
|
+
import type { RedisScripts } from 'redis';
|
|
2
|
+
import type { StreamMessageData, StreamMessageId } from './consumer.js';
|
|
3
|
+
import type { RedisClient } from './client.js';
|
|
4
4
|
export declare class RedisProducer<S extends RedisScripts = RedisScripts> {
|
|
5
5
|
private client;
|
|
6
6
|
constructor(client: RedisClient<S>);
|
|
7
|
-
add(stream: StreamMessageId, message: StreamMessageData):
|
|
7
|
+
add(stream: StreamMessageId, message: StreamMessageData): Promise<StreamMessageId>;
|
|
8
8
|
}
|
package/dist/producer.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RedisProducer = void 0;
|
|
4
|
-
class RedisProducer {
|
|
1
|
+
export class RedisProducer {
|
|
5
2
|
client;
|
|
6
3
|
constructor(client) {
|
|
7
4
|
this.client = client;
|
|
8
5
|
}
|
|
9
6
|
add(stream, message) {
|
|
10
|
-
this.client.xAdd(stream, '*', message);
|
|
7
|
+
return this.client.xAdd(stream, '*', message);
|
|
11
8
|
}
|
|
12
9
|
}
|
|
13
|
-
exports.RedisProducer = RedisProducer;
|
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { StreamProcessingFunction, RedisConsumer } from './consumer';
|
|
4
|
-
import { StreamMessageReply } from '@redis/client/dist/lib/commands/generic-transformers';
|
|
5
|
-
import { RedisScripts } from 'redis';
|
|
6
|
-
import { EventEmitter } from 'stream';
|
|
1
|
+
import type { RedisScripts } from 'redis';
|
|
2
|
+
import type { RedisConsumer, StreamExecutable, StreamMessageReply } from './consumer.js';
|
|
7
3
|
interface RetryState {
|
|
8
4
|
lastError: Error;
|
|
9
5
|
timestamps: number[];
|
|
10
6
|
retries: number;
|
|
11
7
|
message: StreamMessageReply;
|
|
12
8
|
stream: string;
|
|
13
|
-
executable:
|
|
9
|
+
executable: StreamExecutable;
|
|
14
10
|
}
|
|
15
11
|
export type RetryFailedMessage = Omit<RetryState, 'executable' | 'lastError'>;
|
|
16
12
|
export type RetryMessage = Omit<RetryFailedMessage, 'timestamps' | 'lastError'> & {
|
|
@@ -18,15 +14,22 @@ export type RetryMessage = Omit<RetryFailedMessage, 'timestamps' | 'lastError'>
|
|
|
18
14
|
};
|
|
19
15
|
interface RetryProcessorOptions {
|
|
20
16
|
maxRetry: number;
|
|
21
|
-
retryTime?: string[];
|
|
17
|
+
retryTime?: string[] | undefined;
|
|
22
18
|
}
|
|
23
|
-
export declare class RetryProcessor<S extends RedisScripts = RedisScripts>
|
|
19
|
+
export declare class RetryProcessor<S extends RedisScripts = RedisScripts> {
|
|
24
20
|
private consumer;
|
|
25
21
|
private state;
|
|
22
|
+
/**
|
|
23
|
+
* Aborts the sleeps between retries. Without it a retry waiting on a long
|
|
24
|
+
* `retryTime` (15m by default) would keep the process alive after `close()`.
|
|
25
|
+
*/
|
|
26
|
+
private abort;
|
|
26
27
|
private retryTime;
|
|
27
|
-
|
|
28
|
+
maxRetry: number;
|
|
28
29
|
constructor(consumer: RedisConsumer<S>, options: RetryProcessorOptions);
|
|
29
|
-
|
|
30
|
+
/** Cancels the pending retries and forgets their state. */
|
|
31
|
+
stop(): void;
|
|
32
|
+
add(error: Error, stream: string, message: StreamMessageReply, executable: StreamExecutable): void;
|
|
30
33
|
private processRetry;
|
|
31
34
|
private calcTimeoutTime;
|
|
32
35
|
private emitRetryFail;
|
package/dist/retry-processor.js
CHANGED
|
@@ -1,30 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { timeout } from './helpers.js';
|
|
2
|
+
/**
|
|
3
|
+
* Message ids are only unique per stream, so retries must be tracked per stream
|
|
4
|
+
* as well - otherwise a message of one stream would shadow one of another.
|
|
5
|
+
*/
|
|
6
|
+
function retryKey(stream, id) {
|
|
7
|
+
return stream + '\u0000' + id;
|
|
8
|
+
}
|
|
9
|
+
export class RetryProcessor {
|
|
7
10
|
consumer;
|
|
8
11
|
state = new Map();
|
|
12
|
+
/**
|
|
13
|
+
* Aborts the sleeps between retries. Without it a retry waiting on a long
|
|
14
|
+
* `retryTime` (15m by default) would keep the process alive after `close()`.
|
|
15
|
+
*/
|
|
16
|
+
abort = new AbortController();
|
|
9
17
|
retryTime;
|
|
10
18
|
maxRetry;
|
|
11
19
|
constructor(consumer, options) {
|
|
12
|
-
super();
|
|
13
20
|
this.consumer = consumer;
|
|
14
21
|
this.retryTime = options.retryTime || ['15s', '1m', '15m'];
|
|
15
22
|
this.maxRetry = options.maxRetry;
|
|
16
23
|
}
|
|
24
|
+
/** Cancels the pending retries and forgets their state. */
|
|
25
|
+
stop() {
|
|
26
|
+
this.abort.abort();
|
|
27
|
+
this.state.clear();
|
|
28
|
+
}
|
|
17
29
|
add(error, stream, message, executable) {
|
|
18
|
-
|
|
19
|
-
|
|
30
|
+
if (this.abort.signal.aborted)
|
|
31
|
+
return;
|
|
32
|
+
const key = retryKey(stream, message.id);
|
|
33
|
+
if (this.state.has(key))
|
|
20
34
|
return;
|
|
21
|
-
this.state.set(
|
|
22
|
-
this.processRetry(
|
|
35
|
+
this.state.set(key, { lastError: error, timestamps: [], retries: 0, message, stream, executable });
|
|
36
|
+
void this.processRetry(key);
|
|
23
37
|
}
|
|
24
|
-
async processRetry(
|
|
25
|
-
const stateObj = this.state.get(
|
|
38
|
+
async processRetry(key) {
|
|
39
|
+
const stateObj = this.state.get(key);
|
|
40
|
+
// Cleared by `stop()`, or already finished.
|
|
41
|
+
if (!stateObj || this.abort.signal.aborted)
|
|
42
|
+
return;
|
|
26
43
|
if (stateObj.retries >= this.maxRetry) {
|
|
27
|
-
this.state.delete(
|
|
44
|
+
this.state.delete(key);
|
|
28
45
|
this.emitRetryFail(stateObj);
|
|
29
46
|
return;
|
|
30
47
|
}
|
|
@@ -32,14 +49,16 @@ class RetryProcessor extends stream_1.EventEmitter {
|
|
|
32
49
|
const timestamp = new Date().getTime();
|
|
33
50
|
stateObj.timestamps.push(timestamp);
|
|
34
51
|
const timeoutTime = this.calcTimeoutTime(stateObj);
|
|
35
|
-
await (
|
|
52
|
+
await timeout(timeoutTime, this.abort.signal);
|
|
53
|
+
if (this.abort.signal.aborted)
|
|
54
|
+
return;
|
|
36
55
|
const fnc = stateObj.executable;
|
|
37
56
|
const message = stateObj.message;
|
|
38
57
|
try {
|
|
39
58
|
this.emitRetry(stateObj);
|
|
40
59
|
await fnc(message, stateObj.stream);
|
|
41
|
-
this.consumer.addAckMessage(stateObj.stream, id);
|
|
42
|
-
this.state.delete(
|
|
60
|
+
this.consumer.addAckMessage(stateObj.stream, message.id);
|
|
61
|
+
this.state.delete(key);
|
|
43
62
|
}
|
|
44
63
|
catch (err) {
|
|
45
64
|
if (err instanceof Error) {
|
|
@@ -50,14 +69,25 @@ class RetryProcessor extends stream_1.EventEmitter {
|
|
|
50
69
|
stateObj.lastError = newErr;
|
|
51
70
|
}
|
|
52
71
|
this.emitProcessFailed(stateObj);
|
|
53
|
-
this.processRetry(
|
|
72
|
+
await this.processRetry(key);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// The read loop is parked in `XREADGROUP`, so without an explicit flush the
|
|
76
|
+
// acknowledgement queued above would only reach Redis once the next message
|
|
77
|
+
// arrives - leaving the retried message in the pending list until then.
|
|
78
|
+
try {
|
|
79
|
+
await this.consumer.flushAcks();
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
// The ids stay queued, so the read loop's next flush retries them.
|
|
83
|
+
this.consumer.client.emit('listen-error', err);
|
|
54
84
|
}
|
|
55
|
-
return;
|
|
56
85
|
}
|
|
57
86
|
calcTimeoutTime(stateObj) {
|
|
58
87
|
const retry = stateObj.retries;
|
|
59
|
-
const index = (retry
|
|
60
|
-
|
|
88
|
+
const index = Math.min(retry, this.retryTime.length) - 1;
|
|
89
|
+
// Falls back to no delay if `retryTime` is empty or shorter than expected.
|
|
90
|
+
const timeString = this.retryTime[index] ?? '0s';
|
|
61
91
|
let hours = +timeString.replace(/(\d+)h/, '$1');
|
|
62
92
|
let minutes = +timeString.replace(/(\d+)m/, '$1');
|
|
63
93
|
let seconds = +timeString.replace(/(\d+)s/, '$1');
|
|
@@ -81,4 +111,3 @@ class RetryProcessor extends stream_1.EventEmitter {
|
|
|
81
111
|
this.consumer.client.emit('process-error', lastError, { stream, message, retries });
|
|
82
112
|
}
|
|
83
113
|
}
|
|
84
|
-
exports.RetryProcessor = RetryProcessor;
|
package/package.json
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redis-consumer",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "NodeJs consumer and producer for Redis Streams",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=24"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
5
9
|
"main": "./dist/index.js",
|
|
6
10
|
"types": "./dist/index.d.ts",
|
|
7
11
|
"files": [
|
|
8
12
|
"dist/"
|
|
9
13
|
],
|
|
10
14
|
"scripts": {
|
|
11
|
-
"build": "tsc"
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"typecheck:example": "tsc -p tsconfig.example.json"
|
|
12
17
|
},
|
|
13
18
|
"repository": {
|
|
14
19
|
"type": "git",
|
|
15
|
-
"url": "git+https://github.com/tomasky/redis-
|
|
20
|
+
"url": "git+https://github.com/tomasky/redis-consumer.git"
|
|
16
21
|
},
|
|
17
22
|
"author": "Sebastiaan Hekner",
|
|
18
23
|
"keywords": [
|
|
@@ -29,14 +34,14 @@
|
|
|
29
34
|
],
|
|
30
35
|
"license": "ISC",
|
|
31
36
|
"bugs": {
|
|
32
|
-
"url": "https://github.com/
|
|
37
|
+
"url": "https://github.com/tomasky/redis-consumer/issues"
|
|
33
38
|
},
|
|
34
|
-
"homepage": "https://github.com/tomasky/redis-
|
|
39
|
+
"homepage": "https://github.com/tomasky/redis-consumer#readme",
|
|
35
40
|
"dependencies": {
|
|
36
|
-
"redis": "^
|
|
41
|
+
"redis": "^6.2.1"
|
|
37
42
|
},
|
|
38
43
|
"devDependencies": {
|
|
39
|
-
"@types/node": "
|
|
44
|
+
"@types/node": "latest",
|
|
40
45
|
"typescript": "latest"
|
|
41
46
|
}
|
|
42
47
|
}
|