forked from nestjs/nest
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclient-redis.ts
More file actions
134 lines (121 loc) · 3.78 KB
/
Copy pathclient-redis.ts
File metadata and controls
134 lines (121 loc) · 3.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import * as redis from 'redis';
import { ClientProxy } from './client-proxy';
import { Logger } from '@nestjs/common/services/logger.service';
import { ClientOptions } from '../interfaces/client-metadata.interface';
import {
REDIS_DEFAULT_URL,
MESSAGE_EVENT,
ERROR_EVENT,
CONNECT_EVENT,
SUBSCRIBE,
} from './../constants';
import { WritePacket } from './../interfaces';
import { ReadPacket, PacketId } from './../interfaces';
export class ClientRedis extends ClientProxy {
private readonly logger = new Logger(ClientProxy.name);
private readonly url: string;
private pubClient: redis.RedisClient;
private subClient: redis.RedisClient;
private isExplicitlyTerminated = false;
constructor(private readonly options: ClientOptions) {
super();
this.url = options.url || REDIS_DEFAULT_URL;
}
protected async publish(
partialPacket: ReadPacket,
callback: (packet: WritePacket) => any,
) {
if (!this.pubClient || !this.subClient) {
this.init(callback);
}
const packet = this.assignPacketId(partialPacket);
const pattern = JSON.stringify(partialPacket.pattern);
const responseChannel = this.getResPatternName(pattern, packet.id);
const responseCallback = (channel: string, buffer: string) => {
if (responseChannel !== channel) {
return void 0;
}
const { err, response, isDisposed } = JSON.parse(buffer) as WritePacket &
PacketId;
if (isDisposed || err) {
callback({
err,
response: null,
isDisposed: true,
});
this.subClient.unsubscribe(channel);
this.subClient.removeListener(MESSAGE_EVENT, responseCallback);
return;
}
callback({
err,
response,
});
};
this.subClient.on(MESSAGE_EVENT, responseCallback);
this.subClient.subscribe(responseChannel);
await new Promise(resolve =>
this.subClient.on(
SUBSCRIBE,
channel => channel === responseChannel && resolve(),
),
);
this.pubClient.publish(
this.getAckPatternName(pattern),
JSON.stringify(packet),
);
return responseCallback;
}
public getAckPatternName(pattern: string): string {
return `${pattern}_ack`;
}
public getResPatternName(pattern: string, id: string): string {
return `${pattern}_${id}_res`;
}
public close() {
this.pubClient && this.pubClient.quit();
this.subClient && this.subClient.quit();
this.pubClient = this.subClient = null;
}
public init(callback: (...args) => any) {
this.pubClient = this.createClient();
this.subClient = this.createClient();
this.handleError(this.pubClient, callback);
this.handleError(this.subClient, callback);
}
public createClient(): redis.RedisClient {
return redis.createClient({ ...this.getClientOptions(), url: this.url });
}
public handleError(client: redis.RedisClient, callback: (...args) => any) {
const errorCallback = err => {
if (err.code === 'ECONNREFUSED') {
callback(err, null);
this.pubClient = this.subClient = null;
}
this.logger.error(err);
};
client.addListener(ERROR_EVENT, errorCallback);
client.on(CONNECT_EVENT, () => {
client.removeListener(ERROR_EVENT, errorCallback);
client.addListener(ERROR_EVENT, err => this.logger.error(err));
});
}
public getClientOptions(): Partial<redis.ClientOpts> {
const retry_strategy = options => this.createRetryStrategy(options);
return {
retry_strategy,
};
}
public createRetryStrategy(
options: redis.RetryStrategyOptions,
): undefined | number {
if (
this.isExplicitlyTerminated ||
!this.options.retryAttempts ||
options.attempt > this.options.retryAttempts
) {
return undefined;
}
return this.options.retryDelay || 0;
}
}