@discover-cloud/shared 1.4.0 → 1.5.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.
|
@@ -34,7 +34,7 @@ class PermissionCacheService {
|
|
|
34
34
|
*/
|
|
35
35
|
constructor() {
|
|
36
36
|
this.client = (0, redis_1.createClient)({
|
|
37
|
-
url: (0, env_util_1.
|
|
37
|
+
url: (0, env_util_1.getEnv)('REDIS_URL'),
|
|
38
38
|
});
|
|
39
39
|
this.client.on('error', (err) => {
|
|
40
40
|
console.error('[PermissionCache] Redis error', err);
|
|
@@ -2,14 +2,24 @@ import { ILogger } from '../utils/logger.util';
|
|
|
2
2
|
export declare class RabbitMQClient {
|
|
3
3
|
private connection;
|
|
4
4
|
private channel;
|
|
5
|
-
private url;
|
|
6
|
-
private exchangeName;
|
|
7
|
-
private logger;
|
|
5
|
+
private readonly url;
|
|
6
|
+
private readonly exchangeName;
|
|
7
|
+
private readonly logger;
|
|
8
|
+
private reconnecting;
|
|
8
9
|
constructor(url: string, logger?: ILogger);
|
|
10
|
+
/**
|
|
11
|
+
* Establishes a connection to RabbitMQ.
|
|
12
|
+
* Blocks until a connection is successfully established.
|
|
13
|
+
*/
|
|
9
14
|
connect(): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Background reconnect loop.
|
|
17
|
+
*/
|
|
18
|
+
private reconnect;
|
|
10
19
|
publishEvent(routingKey: string, payload: unknown): Promise<void>;
|
|
11
20
|
subscribe(queueName: string, routingKey: string, handler: (payload: unknown) => Promise<void>, options?: {
|
|
12
21
|
prefetch?: number;
|
|
13
22
|
}): Promise<void>;
|
|
14
23
|
close(): Promise<void>;
|
|
24
|
+
private delay;
|
|
15
25
|
}
|
|
@@ -41,35 +41,60 @@ class RabbitMQClient {
|
|
|
41
41
|
this.connection = null;
|
|
42
42
|
this.channel = null;
|
|
43
43
|
this.exchangeName = 'discovercloud.topic';
|
|
44
|
+
this.reconnecting = false;
|
|
44
45
|
this.url = url;
|
|
45
46
|
this.logger = logger;
|
|
46
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Establishes a connection to RabbitMQ.
|
|
50
|
+
* Blocks until a connection is successfully established.
|
|
51
|
+
*/
|
|
47
52
|
async connect() {
|
|
48
53
|
if (this.connection && this.channel) {
|
|
49
54
|
return;
|
|
50
55
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
this.connection
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
while (!this.channel) {
|
|
57
|
+
try {
|
|
58
|
+
this.logger.info('Connecting to RabbitMQ...');
|
|
59
|
+
this.connection = await amqplib.connect(this.url);
|
|
60
|
+
this.connection.on('error', (err) => {
|
|
61
|
+
this.logger.error(err, 'RabbitMQ Connection Error');
|
|
62
|
+
});
|
|
63
|
+
this.connection.on('close', () => {
|
|
64
|
+
this.logger.warn('RabbitMQ Connection Closed');
|
|
65
|
+
this.connection = null;
|
|
66
|
+
this.channel = null;
|
|
67
|
+
if (!this.reconnecting) {
|
|
68
|
+
void this.reconnect();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
this.channel = await this.connection.createChannel();
|
|
72
|
+
await this.channel.assertExchange(this.exchangeName, 'topic', {
|
|
73
|
+
durable: true,
|
|
74
|
+
});
|
|
75
|
+
this.logger.info('✅ RabbitMQ connected');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
this.logger.error(err, 'RabbitMQ unavailable. Retrying in 5 seconds...');
|
|
80
|
+
await this.delay(5000);
|
|
81
|
+
}
|
|
68
82
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Background reconnect loop.
|
|
86
|
+
*/
|
|
87
|
+
async reconnect() {
|
|
88
|
+
this.reconnecting = true;
|
|
89
|
+
while (!this.channel) {
|
|
90
|
+
try {
|
|
91
|
+
await this.connect();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
await this.delay(5000);
|
|
95
|
+
}
|
|
72
96
|
}
|
|
97
|
+
this.reconnecting = false;
|
|
73
98
|
}
|
|
74
99
|
async publishEvent(routingKey, payload) {
|
|
75
100
|
if (!this.channel) {
|
|
@@ -89,45 +114,61 @@ class RabbitMQClient {
|
|
|
89
114
|
if (options.prefetch) {
|
|
90
115
|
await this.channel.prefetch(options.prefetch);
|
|
91
116
|
}
|
|
92
|
-
// Dead Letter configuration
|
|
93
117
|
const dlxName = `${this.exchangeName}.dlx`;
|
|
94
118
|
const dlqName = `${queueName}.dlq`;
|
|
95
|
-
await this.channel.assertExchange(dlxName, 'topic', {
|
|
96
|
-
|
|
119
|
+
await this.channel.assertExchange(dlxName, 'topic', {
|
|
120
|
+
durable: true,
|
|
121
|
+
});
|
|
122
|
+
await this.channel.assertQueue(dlqName, {
|
|
123
|
+
durable: true,
|
|
124
|
+
});
|
|
97
125
|
await this.channel.bindQueue(dlqName, dlxName, routingKey);
|
|
98
|
-
// Assert main queue with DLX bindings
|
|
99
126
|
await this.channel.assertQueue(queueName, {
|
|
100
127
|
durable: true,
|
|
101
128
|
deadLetterExchange: dlxName,
|
|
102
129
|
deadLetterRoutingKey: routingKey,
|
|
103
130
|
});
|
|
104
131
|
await this.channel.bindQueue(queueName, this.exchangeName, routingKey);
|
|
105
|
-
this.channel.consume(queueName, async (msg) => {
|
|
106
|
-
if (!msg)
|
|
132
|
+
await this.channel.consume(queueName, async (msg) => {
|
|
133
|
+
if (!msg) {
|
|
107
134
|
return;
|
|
135
|
+
}
|
|
108
136
|
try {
|
|
109
137
|
const payload = JSON.parse(msg.content.toString());
|
|
110
138
|
await handler(payload);
|
|
111
|
-
// Fix: Use optional chaining instead of non-null assertion (!)
|
|
112
139
|
this.channel?.ack(msg);
|
|
113
140
|
}
|
|
114
|
-
catch (
|
|
115
|
-
this.logger.error(
|
|
116
|
-
// Fix: Use optional chaining instead of non-null assertion (!)
|
|
117
|
-
// Nack the message and don't requeue, so it goes to the DLX
|
|
141
|
+
catch (err) {
|
|
142
|
+
this.logger.error(err, `Error processing ${queueName}`);
|
|
118
143
|
this.channel?.nack(msg, false, false);
|
|
119
144
|
}
|
|
120
145
|
});
|
|
121
|
-
this.logger.info(`📡 Subscribed to
|
|
146
|
+
this.logger.info(`📡 Subscribed to ${queueName} (${routingKey})`);
|
|
122
147
|
}
|
|
123
148
|
async close() {
|
|
124
|
-
|
|
125
|
-
|
|
149
|
+
try {
|
|
150
|
+
this.reconnecting = true; // Prevents triggering reconnect during manual close
|
|
151
|
+
if (this.connection) {
|
|
152
|
+
this.connection.removeAllListeners('close');
|
|
153
|
+
}
|
|
154
|
+
if (this.channel) {
|
|
155
|
+
await this.channel.close();
|
|
156
|
+
}
|
|
157
|
+
if (this.connection) {
|
|
158
|
+
await this.connection.close();
|
|
159
|
+
}
|
|
126
160
|
}
|
|
127
|
-
|
|
128
|
-
|
|
161
|
+
finally {
|
|
162
|
+
this.channel = null;
|
|
163
|
+
this.connection = null;
|
|
164
|
+
this.reconnecting = false;
|
|
129
165
|
}
|
|
130
|
-
this.logger.info('RabbitMQ
|
|
166
|
+
this.logger.info('RabbitMQ disconnected');
|
|
167
|
+
}
|
|
168
|
+
delay(ms) {
|
|
169
|
+
return new Promise((resolve) => {
|
|
170
|
+
setTimeout(resolve, ms);
|
|
171
|
+
});
|
|
131
172
|
}
|
|
132
173
|
}
|
|
133
174
|
exports.RabbitMQClient = RabbitMQClient;
|