@platform-x/hep-message-broker-client 1.0.2 → 1.1.2
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 +1 -1
- package/dist/src/Util/commonUtil.js.map +1 -0
- package/dist/src/Util/constants.js +8 -0
- package/dist/src/Util/constants.js.map +1 -0
- package/dist/src/Util/logger.js.map +1 -0
- package/dist/src/Util/requestTracer.js.map +1 -0
- package/dist/src/config/ConfigManager.js.map +1 -0
- package/dist/src/config/index.js +1 -1
- package/dist/src/config/index.js.map +1 -0
- package/dist/src/index.js +22 -1
- package/dist/src/index.js.map +1 -0
- package/dist/src/messageBroker/BaseRabbitMQClient.js.map +1 -0
- package/dist/src/messageBroker/ConnectionManager.js.map +1 -0
- package/dist/src/messageBroker/MessageBrokerClient.js.map +1 -0
- package/dist/src/messageBroker/MessageConsumer.js.map +1 -0
- package/dist/src/messageBroker/MessageProducer.js.map +1 -0
- package/dist/src/messageBroker/RabbitMQClient.js.map +1 -0
- package/dist/src/messageBroker/RetryManager.js.map +1 -0
- package/dist/src/messageBroker/interface/ConnectionWrapper.js.map +1 -0
- package/dist/src/messageBroker/interface/IMessageBrokerClient.js.map +1 -0
- package/dist/src/messageBroker/rabbitmq/MessageBrokerClient.js +4 -1
- package/dist/src/messageBroker/rabbitmq/MessageBrokerClient.js.map +1 -0
- package/dist/src/messageBroker/types/ActionType.js.map +1 -0
- package/dist/src/messageBroker/types/PublishMessageInputType.js.map +1 -0
- package/dist/src/models/MessageModel.js.map +1 -0
- package/dist/src/models/NotificationMessageModel.js.map +1 -0
- package/package.json +43 -40
- package/src/Util/commonUtil.ts +41 -0
- package/src/Util/constants.ts +4 -0
- package/src/Util/logger.ts +219 -0
- package/src/Util/requestTracer.ts +28 -0
- package/src/config/ConfigManager.ts +35 -0
- package/src/config/index.ts +31 -0
- package/src/index.ts +73 -0
- package/src/messageBroker/BaseRabbitMQClient.ts +30 -0
- package/src/messageBroker/ConnectionManager.ts +182 -0
- package/src/messageBroker/MessageBrokerClient.ts +88 -0
- package/src/messageBroker/MessageConsumer.ts +85 -0
- package/src/messageBroker/MessageProducer.ts +142 -0
- package/src/messageBroker/RabbitMQClient.ts +47 -0
- package/src/messageBroker/RetryManager.ts +64 -0
- package/src/messageBroker/interface/ConnectionWrapper.ts +7 -0
- package/src/messageBroker/interface/IMessageBrokerClient.ts +11 -0
- package/src/messageBroker/rabbitmq/MessageBrokerClient.ts +680 -0
- package/src/messageBroker/types/ActionType.ts +1 -0
- package/src/messageBroker/types/PublishMessageInputType.ts +8 -0
- package/src/models/MessageModel.ts +14 -0
- package/tsconfig.json +73 -0
- package/dist/src/Util/commonUtil.d.ts +0 -16
- package/dist/src/Util/logger.d.ts +0 -68
- package/dist/src/Util/requestTracer.d.ts +0 -7
- package/dist/src/config/ConfigManager.d.ts +0 -9
- package/dist/src/config/index.d.ts +0 -29
- package/dist/src/index.d.ts +0 -2
- package/dist/src/messageBroker/BaseRabbitMQClient.d.ts +0 -16
- package/dist/src/messageBroker/ConnectionManager.d.ts +0 -60
- package/dist/src/messageBroker/MessageBrokerClient.d.ts +0 -34
- package/dist/src/messageBroker/MessageConsumer.d.ts +0 -26
- package/dist/src/messageBroker/MessageProducer.d.ts +0 -29
- package/dist/src/messageBroker/RabbitMQClient.d.ts +0 -12
- package/dist/src/messageBroker/RetryManager.d.ts +0 -23
- package/dist/src/messageBroker/interface/ConnectionWrapper.d.ts +0 -6
- package/dist/src/messageBroker/interface/IMessageBrokerClient.d.ts +0 -7
- package/dist/src/messageBroker/rabbitmq/MessageBrokerClient.d.ts +0 -122
- package/dist/src/messageBroker/types/ActionType.d.ts +0 -1
- package/dist/src/messageBroker/types/PublishMessageInputType.d.ts +0 -7
- package/dist/src/models/MessageModel.d.ts +0 -5
- /package/{dist/src/models/NotificationMessageModel.d.ts → src/models/NotificationMessageModel.ts} +0 -0
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
// Import the amqplib library for RabbitMQ
|
|
2
|
+
import * as amqp from 'amqplib';
|
|
3
|
+
import config from '../../config';
|
|
4
|
+
import { Logger } from '../../Util/logger';
|
|
5
|
+
import ConnectionWrapper from '../interface/ConnectionWrapper';
|
|
6
|
+
import ConfigManager from '../../config/ConfigManager';
|
|
7
|
+
import { PublishMessageInputType } from '../types/PublishMessageInputType';
|
|
8
|
+
import MessageModel from '../../models/MessageModel';
|
|
9
|
+
import { ActionType } from '../types/ActionType';
|
|
10
|
+
import { SECRET_KEYS } from '../../Util/constants';
|
|
11
|
+
import { getIAMSecrets } from '../..';
|
|
12
|
+
// Remove lodash delay import
|
|
13
|
+
let configManager: ConfigManager = ConfigManager.getInstance();
|
|
14
|
+
// Declare variables for the connection and channel
|
|
15
|
+
let connection: amqp.Connection;
|
|
16
|
+
let channel: amqp.Channel;
|
|
17
|
+
// Declare variable for check connection
|
|
18
|
+
let isConnectionOpen = false;
|
|
19
|
+
|
|
20
|
+
let configData: any = {};
|
|
21
|
+
const RABBITMQ = config?.RABBITMQ;
|
|
22
|
+
let maxRetries: number = RABBITMQ?.MAX_RETRIES;
|
|
23
|
+
let retry_delay: any = RABBITMQ?.TTL?.split("|");
|
|
24
|
+
// let dlx_exchange:string = RABBITMQ.DLX_EXCHANGE;
|
|
25
|
+
let dlx_queue:string = RABBITMQ.DLX_QUEUE;
|
|
26
|
+
// let retry_exchange:string = RABBITMQ.RETRY_EXCHANGE;
|
|
27
|
+
// let retry_queue:string = RABBITMQ.RETRY_QUEUE;
|
|
28
|
+
let connectionRetry:number = 1;
|
|
29
|
+
let retryCountHeader = 'x-retry-count';
|
|
30
|
+
export class MessageBrokerClient {
|
|
31
|
+
|
|
32
|
+
// private readonly correlationId: string;
|
|
33
|
+
// constructor() {
|
|
34
|
+
// this.correlationId = generateCorrelationId();
|
|
35
|
+
// }
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Function to check the status of the connection
|
|
39
|
+
* @returns boolean
|
|
40
|
+
*/
|
|
41
|
+
public async isConnected(): Promise<boolean> {
|
|
42
|
+
return isConnectionOpen;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Function to initialize the connection, channel and setup the queues and exchanges
|
|
48
|
+
* @param reqData
|
|
49
|
+
* @returns
|
|
50
|
+
*/
|
|
51
|
+
public async initialize() {
|
|
52
|
+
Logger.info('Reached to initialize', 'initialize');
|
|
53
|
+
configData = configManager.getConfig();
|
|
54
|
+
// dlx_exchange = configData?.dlx_exchange;
|
|
55
|
+
dlx_queue = configData?.dlx_queue;
|
|
56
|
+
// retry_exchange = configData?.retry_exchange;
|
|
57
|
+
// retry_queue = configData?.retry_queue;
|
|
58
|
+
let retries: number = 0;
|
|
59
|
+
// Retrie times connection
|
|
60
|
+
while (retries < maxRetries) {
|
|
61
|
+
try {
|
|
62
|
+
if(RABBITMQ.CONNECTION_ERROR === 'true' || RABBITMQ.CONNECTION_ERROR === true){
|
|
63
|
+
throw new Error("Error in connection");
|
|
64
|
+
}
|
|
65
|
+
await this.createConnection();
|
|
66
|
+
return { connection: connection, channel: channel };
|
|
67
|
+
} catch (err) {
|
|
68
|
+
retries++;
|
|
69
|
+
Logger.error('initialize:Failed to connect to RabbitMQ', `Attempt ${retries} of ${maxRetries}.`);
|
|
70
|
+
if (retries >= maxRetries) {
|
|
71
|
+
Logger.error('Max retries reached, cannot connect to RabbitMQ.', 'initialize');
|
|
72
|
+
return { connection, channel };
|
|
73
|
+
}
|
|
74
|
+
// Wait before retrying (e.g., 10 seconds)
|
|
75
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Function to publish a message with retry
|
|
84
|
+
* @param request
|
|
85
|
+
* @param classInstance
|
|
86
|
+
* @param messagePublisherErrorHandler
|
|
87
|
+
* @returns
|
|
88
|
+
*/
|
|
89
|
+
public async publishMessage(
|
|
90
|
+
request: PublishMessageInputType
|
|
91
|
+
// , classInstance:any, messagePublisherErrorHandler:string
|
|
92
|
+
){
|
|
93
|
+
Logger.info('Reached:publishMessage', 'publishMessage');
|
|
94
|
+
// Logger.debug('Reached:publishMessageToExchange', 'publishMessageToExchange',{classInstance, messagePublisherErrorHandler});
|
|
95
|
+
try{
|
|
96
|
+
if(isConnectionOpen){
|
|
97
|
+
Logger.info('Check connection open or not', 'publishMessage');
|
|
98
|
+
const queueName: string = request.queueName;
|
|
99
|
+
const data: MessageModel = request.message;
|
|
100
|
+
const messageBuffer = Buffer.from(JSON.stringify(data?.message));
|
|
101
|
+
// let attempt: number = 0;
|
|
102
|
+
// let retries: number = typeof data.message === 'object' && data.message !== null ? (data.message as { retries: number }).retries ?? 0 : 0;
|
|
103
|
+
// while (attempt < maxRetries ) {
|
|
104
|
+
Logger.info('Max retries check', 'publishMessage');
|
|
105
|
+
const isSent = await new Promise<boolean>((resolve, reject) => {
|
|
106
|
+
let sent = channel.sendToQueue(queueName, Buffer.from(messageBuffer), {
|
|
107
|
+
persistent: true, // Ensures the message is persisted
|
|
108
|
+
// correlationId: this.correlationId,
|
|
109
|
+
});
|
|
110
|
+
if (sent) {
|
|
111
|
+
Logger.debug(`Message sent successfully`, 'publishMessage', { data, queueName });
|
|
112
|
+
resolve(true);
|
|
113
|
+
} else {
|
|
114
|
+
Logger.error(`Failed to publish message.`, 'publishMessage', { message: messageBuffer.toString() });
|
|
115
|
+
reject(false);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
if((isSent) && (RABBITMQ.PUBLISHER_ERROR !== 'true' && RABBITMQ.PUBLISHER_ERROR !== true)){
|
|
119
|
+
return isSent;
|
|
120
|
+
}
|
|
121
|
+
// else{
|
|
122
|
+
// let queueName = (request as PublishMessageInputType)?.queueName
|
|
123
|
+
// if (attempt >= maxRetries) {
|
|
124
|
+
// Logger.error('Max retries reached. Failed to publish message.', 'publishMessage', request);
|
|
125
|
+
// // Optionally: Log or persist the failed message to a DB, file, or other storage for manual intervention
|
|
126
|
+
// this.sendMessageToDeadLatter(attempt, data, queueName);
|
|
127
|
+
// this.messagePublisherErrorHandler(request); // we need to comment this line
|
|
128
|
+
// if (typeof classInstance[messagePublisherErrorHandler] === 'function') {
|
|
129
|
+
// classInstance[messagePublisherErrorHandler](data); // Dynamically call the method
|
|
130
|
+
// } else {
|
|
131
|
+
// Logger.info(`Method ${messagePublisherErrorHandler} not found in classInstance.`, 'publishMessage');
|
|
132
|
+
// }
|
|
133
|
+
// return false;
|
|
134
|
+
// }else{
|
|
135
|
+
// await this.publishMessageRetryQueue(request, attempt);
|
|
136
|
+
// attempt++;
|
|
137
|
+
// }
|
|
138
|
+
// }
|
|
139
|
+
// }
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
Logger.error(`Error sending message: ${error}`, 'sendMessage');
|
|
144
|
+
throw new Error("Failed to publish message to queue");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Function to publish a message with retry
|
|
150
|
+
* @param request
|
|
151
|
+
* @param classInstance
|
|
152
|
+
* @param messagePublisherErrorHandler
|
|
153
|
+
* @returns
|
|
154
|
+
*/
|
|
155
|
+
public async publishMessageToExchange(request: PublishMessageInputType): Promise<boolean> {
|
|
156
|
+
Logger.info('Reached:publishMessageToExchange', 'publishMessageToExchange');
|
|
157
|
+
let attempt = 0;
|
|
158
|
+
const exchangeName: string | undefined = request?.exchangeName ?? '';
|
|
159
|
+
const queueName: string = request.queueName;
|
|
160
|
+
const data: MessageModel = request?.message;
|
|
161
|
+
const messageBuffer = Buffer.from(JSON.stringify(data.message));
|
|
162
|
+
try {
|
|
163
|
+
// 1. Ensure connection and channel exists
|
|
164
|
+
await this.checkConnectionAndChannel();
|
|
165
|
+
|
|
166
|
+
// 2. Retry loop
|
|
167
|
+
while (attempt < maxRetries) {
|
|
168
|
+
try {
|
|
169
|
+
Logger.info(`Attempting to publish message`, 'publishMessageToExchange');
|
|
170
|
+
let sent = channel.publish(exchangeName, queueName, messageBuffer, {
|
|
171
|
+
persistent: true,
|
|
172
|
+
});
|
|
173
|
+
// if(attempt <1){ sent = false}else{sent = true} // Simulate failure for testing retry logic
|
|
174
|
+
if (sent && (RABBITMQ.PUBLISHER_ERROR !== 'true' && RABBITMQ.PUBLISHER_ERROR !== true)) {
|
|
175
|
+
Logger.info(`Message published successfully on attempt ${attempt + 1}`, 'publishMessageToExchange');
|
|
176
|
+
Logger.info(`✅ Message sent successfully`, 'publishMessageToExchange');
|
|
177
|
+
break;
|
|
178
|
+
} else {
|
|
179
|
+
throw new Error('Publish returned false');
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
attempt++;
|
|
183
|
+
Logger.error(`Failed attempt ${attempt} to publish message`, 'publishMessageToExchange', error);
|
|
184
|
+
if (attempt < maxRetries) {
|
|
185
|
+
Logger.info(`Retrying in ${retry_delay}ms...`, 'publishMessageToExchange');
|
|
186
|
+
await new Promise(res => setTimeout(res, retry_delay));
|
|
187
|
+
}else {
|
|
188
|
+
Logger.error(`Error in publishMessageToExchange: ${error}`, 'publishMessageToExchange');
|
|
189
|
+
throw new Error('Max retries reached');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
Logger.error('Max retries reached. Sending message to Dead Letter Queue...', 'publishMessageToExchange');
|
|
196
|
+
await this.sendMessageToDeadLatter(attempt, data);
|
|
197
|
+
const customError = new Error('Failed to publish message to exchange');
|
|
198
|
+
(customError as any).originalError = error; // optional
|
|
199
|
+
throw customError;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Funciton to message handler
|
|
204
|
+
*/
|
|
205
|
+
// TO Do rename function name messagePublisherErrorHandler
|
|
206
|
+
public async messagePublisherErrorHandler(request: PublishMessageInputType): Promise<boolean> {
|
|
207
|
+
Logger.info(`${JSON.stringify(request)}`, 'messagePublisherErrorHandler');
|
|
208
|
+
return Promise.resolve(true);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Funvction for initialize consumer queue
|
|
213
|
+
* @param consumerQueues
|
|
214
|
+
*/
|
|
215
|
+
public async initializeConsumers(classInstance:any, consumerHandler:string, consumerErrorHandler:string) {
|
|
216
|
+
try {
|
|
217
|
+
Logger.info('Reached in initialize consumer', `initializeConsumers`);
|
|
218
|
+
let consume_queues = configData?.consume_queues;
|
|
219
|
+
// Consume messages from all queues
|
|
220
|
+
for (let queueName in configData?.consume_queues) {
|
|
221
|
+
if (Object.prototype.hasOwnProperty.call(consume_queues, queueName)) {
|
|
222
|
+
// Call function Consume messages with retry
|
|
223
|
+
await this.consumeMessage(queueName, classInstance, consumerHandler, consumerErrorHandler);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} catch (error: unknown) {
|
|
227
|
+
Logger.error('Error initializing consumers:', 'initializeConsumers', (error as Error).message);
|
|
228
|
+
throw new Error((error as Error).message);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Function for perform action with retry
|
|
234
|
+
* @param actionHandler
|
|
235
|
+
* @param context
|
|
236
|
+
* @param failActionHandler
|
|
237
|
+
* @returns
|
|
238
|
+
*/
|
|
239
|
+
public async performActionWithRetry<T>(actionHandler: ActionType<T>, failActionHandler: ActionType<T>, context: T): Promise<boolean> {
|
|
240
|
+
Logger.info("Reached: in perform action with retry ", "performActionWithRetry")
|
|
241
|
+
let attempt: number = 0;
|
|
242
|
+
while (attempt < maxRetries) {
|
|
243
|
+
try {
|
|
244
|
+
actionHandler(context);
|
|
245
|
+
return true;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
attempt++;
|
|
248
|
+
Logger.error(`Failed to publish message. Attempt ${attempt} of ${maxRetries}. Error:`, 'performActionWithRetry', { message: (error as Error).message });
|
|
249
|
+
let queueName = (context as PublishMessageInputType)?.queueName
|
|
250
|
+
const data: MessageModel = (context as PublishMessageInputType)?.message;
|
|
251
|
+
if (attempt >= maxRetries) {
|
|
252
|
+
Logger.error('Max retries reached. Failed to publish message.', 'performActionWithRetry');
|
|
253
|
+
// Optionally: Log or persist the failed message to a DB, file, or other storage for manual intervention
|
|
254
|
+
this.sendMessageToDeadLatter(attempt, data);
|
|
255
|
+
failActionHandler(context);
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
// Wait before retrying (Exponential Backoff)
|
|
259
|
+
let retryDelay: number;
|
|
260
|
+
retryDelay = Number(retry_delay[attempt- 1]);
|
|
261
|
+
let count:any = retry_delay.length - 1 === attempt?retry_delay.length - 1:retry_delay[attempt - 1];
|
|
262
|
+
retryDelay = Number(count);
|
|
263
|
+
this.sendMessageToRetryQueue(attempt, data, retryDelay, queueName);
|
|
264
|
+
Logger.info(`Retrying in ${retryDelay / 1000} seconds...`, 'performActionWithRetry');
|
|
265
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Function to consume message with retry
|
|
275
|
+
* @param queueName
|
|
276
|
+
*/
|
|
277
|
+
public async consumeMessage(queueName: string, classInstance: any, consumerHandler: string , consumerErrorHandler:string) {
|
|
278
|
+
Logger.info("Reached: Consuming messages from the queue", 'consumeMessage')
|
|
279
|
+
let attempt:number = 0;
|
|
280
|
+
if(isConnectionOpen){
|
|
281
|
+
return await channel.consume(queueName, async (message: any) => {
|
|
282
|
+
if(message){
|
|
283
|
+
let msgData: any = JSON.parse(message.content.toString());
|
|
284
|
+
const headers = message.properties.headers || {};
|
|
285
|
+
attempt = headers[retryCountHeader] || 0;
|
|
286
|
+
const data:any = {body:msgData?.result?.data, queue_name:queueName, language: msgData?.language, correlationId: msgData?.correlationId, retries: msgData?.retries, item_id: msgData?.item_id, metadatakey: msgData?.metadatakey};
|
|
287
|
+
try {
|
|
288
|
+
let success:boolean = false;
|
|
289
|
+
if(RABBITMQ.CONSUMER_ERROR !== 'true' && RABBITMQ.CONSUMER_ERROR !== true){
|
|
290
|
+
Logger.info("Consumer handler call for process the request", 'consumeMessage')
|
|
291
|
+
// success = await this.consumerHandler({ queueName, message: msgData });
|
|
292
|
+
if (typeof classInstance[consumerHandler] === 'function') {
|
|
293
|
+
success = await classInstance[consumerHandler]({ queueName, message: msgData }); // Dynamically call the method
|
|
294
|
+
} else {
|
|
295
|
+
Logger.info(`Method ${consumerHandler} not found in classInstance.`, 'consumerMessage');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!success) {
|
|
300
|
+
const retryCount = message.properties.headers['x-retry-count'] || 0;
|
|
301
|
+
// while (retryCount < maxRetries) {
|
|
302
|
+
attempt = retryCount?retryCount:attempt;
|
|
303
|
+
if (retryCount >= maxRetries) {
|
|
304
|
+
Logger.error('Max retries reached. Failed to publish message.', 'consumeMessage');
|
|
305
|
+
// Optionally: Log or persist the failed message to a DB, file, or other storage for manual intervention
|
|
306
|
+
this.sendMessageToDeadLatter(attempt, data);
|
|
307
|
+
// this.consumerErrorHandler(data);
|
|
308
|
+
if (typeof classInstance[consumerErrorHandler] === 'function') {
|
|
309
|
+
classInstance[consumerErrorHandler](data); // Dynamically call the method
|
|
310
|
+
} else {
|
|
311
|
+
Logger.info(`Method ${consumerErrorHandler} not found in classInstance.`, 'consumerMessage');
|
|
312
|
+
}
|
|
313
|
+
channel.nack(message, false, false); // Move message to DLQ after failure
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
attempt++;
|
|
317
|
+
if (data?.retries >=0) {
|
|
318
|
+
data.retries = attempt;
|
|
319
|
+
}
|
|
320
|
+
// Wait before retrying (Exponential Backoff)
|
|
321
|
+
let retryDelay: number;
|
|
322
|
+
let count:any = retry_delay.length === attempt?retry_delay[retry_delay.length - 1]:retry_delay[attempt-1];
|
|
323
|
+
retryDelay = Number(count);
|
|
324
|
+
await this.sendMessageToRetryQueue(attempt, data, retryDelay, queueName);
|
|
325
|
+
Logger.info(`Retrying in ${retryDelay / 1000} seconds...`, 'consumeMessage');
|
|
326
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
327
|
+
} else {
|
|
328
|
+
Logger.info(`Acknowledge the successful processing`, 'consumeMessage');
|
|
329
|
+
}
|
|
330
|
+
// Acknowledge the successful processing
|
|
331
|
+
channel.ack(message);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
let errorMessage:string = (error as Error).message;
|
|
334
|
+
Logger.error(`Error processing message`, 'consumeMessage', { message: errorMessage});
|
|
335
|
+
this.sendMessageToDeadLatter(attempt, data);
|
|
336
|
+
// this.consumerErrorHandler(data);
|
|
337
|
+
if (typeof classInstance[consumerErrorHandler] === 'function') {
|
|
338
|
+
classInstance[consumerErrorHandler](data); // Dynamically call the method
|
|
339
|
+
} else {
|
|
340
|
+
Logger.info(`Method ${consumerErrorHandler} not found in classInstance.`, 'consumerMessage');
|
|
341
|
+
}
|
|
342
|
+
channel.nack(message, false, false); // Move message to DLQ after failure
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// /**
|
|
350
|
+
// * Function for consumer massage handler
|
|
351
|
+
// * @param request
|
|
352
|
+
// * @returns
|
|
353
|
+
// */
|
|
354
|
+
// public async consumerHandler(request: PublishMessageInputType): Promise<boolean> {
|
|
355
|
+
// Logger.info(`${JSON.stringify(request)}`, 'consumerHandler');
|
|
356
|
+
// return true;
|
|
357
|
+
// }
|
|
358
|
+
|
|
359
|
+
// /**
|
|
360
|
+
// * Function to consumer message error handler
|
|
361
|
+
// * @param request
|
|
362
|
+
// * @returns
|
|
363
|
+
// */
|
|
364
|
+
|
|
365
|
+
// public async consumerErrorHandler(request:any):Promise<boolean> {
|
|
366
|
+
// Logger.info(`${JSON.stringify(request)}`, 'consumerErrorHandler');
|
|
367
|
+
// return true;
|
|
368
|
+
// }
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Function to send message to dead later queue
|
|
373
|
+
* @param attempt
|
|
374
|
+
* @param data
|
|
375
|
+
* @param queueName
|
|
376
|
+
* @retruns
|
|
377
|
+
*/
|
|
378
|
+
|
|
379
|
+
public async sendMessageToDeadLatter(attempt:number, data:any){
|
|
380
|
+
Logger.info("Reached: send message on dead letter queue", 'sendMessageToDeadLater')
|
|
381
|
+
try {
|
|
382
|
+
if(isConnectionOpen){
|
|
383
|
+
Logger.info(`Attempting to send message to DLX queue: ${dlx_queue} (attempt: ${attempt}, retry-count: ${attempt - 1}, size: ${JSON.stringify(data).length} bytes)`, 'sendMessageToDeadLater');
|
|
384
|
+
const sent = channel.sendToQueue(dlx_queue, Buffer.from(JSON.stringify(data)), {
|
|
385
|
+
persistent: true,
|
|
386
|
+
headers: {
|
|
387
|
+
'x-retry-count': attempt-1
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
if (sent) {
|
|
391
|
+
Logger.info(`Message successfully published to DLX queue: ${dlx_queue} (attempt: ${attempt}, retry-count: ${attempt - 1})`, 'sendMessageToDeadLater');
|
|
392
|
+
} else {
|
|
393
|
+
Logger.error(`Failed to publish message to DLX queue: ${dlx_queue} - Channel buffer full (attempt: ${attempt}, retry-count: ${attempt - 1})`, 'sendMessageToDeadLater');
|
|
394
|
+
}
|
|
395
|
+
return sent;
|
|
396
|
+
} else {
|
|
397
|
+
Logger.error(`Cannot send message to DLX - Connection is not open (queue: ${dlx_queue}, attempt: ${attempt})`, 'sendMessageToDeadLater');
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
} catch (error:unknown) {
|
|
401
|
+
Logger.error(`Error sending message to dead letter queue: ${(error as Error).message} (queue: ${dlx_queue}, attempt: ${attempt})`, 'sendMessageToDeadLater');
|
|
402
|
+
throw error;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
/***
|
|
408
|
+
* Function to send message to retry queue
|
|
409
|
+
* @param attempt
|
|
410
|
+
* @param data
|
|
411
|
+
* @param retryDelay
|
|
412
|
+
* @param queueName
|
|
413
|
+
* @returns
|
|
414
|
+
*/
|
|
415
|
+
|
|
416
|
+
public async sendMessageToRetryQueue(attempt:number, data:any, retryDelay:any, queueName:string){
|
|
417
|
+
Logger.info("Reached: send message on retry queue", 'sendMessageToRetryQueue')
|
|
418
|
+
try {
|
|
419
|
+
if(isConnectionOpen){
|
|
420
|
+
const isSent = await new Promise<boolean>((resolve, reject) => {
|
|
421
|
+
const messageBuffer = Buffer.from(JSON.stringify(data));
|
|
422
|
+
let sent = channel.sendToQueue(queueName, messageBuffer, {
|
|
423
|
+
headers: {
|
|
424
|
+
'x-retry-count': attempt + 1,
|
|
425
|
+
'x-message-ttl': retryDelay,
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
if (sent) {
|
|
429
|
+
Logger.debug(`Message sent successfully`, 'publishMessage', { data, queueName });
|
|
430
|
+
resolve(true);
|
|
431
|
+
} else {
|
|
432
|
+
Logger.error(`Failed to publish message.`, 'publishMessage', { message: data.toString() });
|
|
433
|
+
reject(false);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
return isSent;
|
|
437
|
+
}
|
|
438
|
+
} catch (error:unknown) {
|
|
439
|
+
Logger.error("Error in send message on retry queue", 'sendMessageToRetryQueue', {message: (error as Error ).message});
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Function to find the queue, exchange and add binding
|
|
446
|
+
* @param queueName
|
|
447
|
+
* @param exchangeName
|
|
448
|
+
* @returns
|
|
449
|
+
*/
|
|
450
|
+
public async findQueueAndExchange(queueName:string, exchangeName:string){
|
|
451
|
+
Logger.info('Reached to findQueueAndExchange', 'findQueueAndExchange');
|
|
452
|
+
try {
|
|
453
|
+
let exchanges: any = configData?.exchanges;
|
|
454
|
+
let queues: any = configData.queues;
|
|
455
|
+
let bindings: any = configData.bindings;
|
|
456
|
+
// Declare the queues with dlx
|
|
457
|
+
|
|
458
|
+
for (let data of queues) {
|
|
459
|
+
if(data?.name === queueName){
|
|
460
|
+
await this.createQueue(data);
|
|
461
|
+
}
|
|
462
|
+
Logger.info(`${data?.name} queue created `, 'findQueueAndExchange');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Declare the exchange with dlx
|
|
466
|
+
for (let data of exchanges) {
|
|
467
|
+
if(data?.name === exchangeName){
|
|
468
|
+
await this.createExchange(data);
|
|
469
|
+
}
|
|
470
|
+
Logger.info(`${data?.name} exchange created`, 'findQueueAndExchange');
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Declare the bindings for queues and exchanges with routing keys
|
|
474
|
+
for (let obj in bindings) {
|
|
475
|
+
bindings[obj]?.forEach(async (key: any) => {
|
|
476
|
+
if(obj === exchangeName && key?.queue === queueName){
|
|
477
|
+
await this.bindQueueAndExchanges(obj, key?.queue, key?.routingKey);
|
|
478
|
+
}
|
|
479
|
+
Logger.info(`${key?.queue} bind with exchange name ${obj} and routing key ${key?.routingKey}`, 'findQueueAndExchange');
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
} catch (error) {
|
|
483
|
+
Logger.error("Error on setup queues, exchanges and binding", 'findQueueAndExchange', (error as Error).message);
|
|
484
|
+
throw error;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Function to check connection and channel
|
|
490
|
+
*/
|
|
491
|
+
private async checkConnectionAndChannel() {
|
|
492
|
+
Logger.info('Reached: checkConnectionAndChannel', 'checkConnectionAndChannel');
|
|
493
|
+
// 🧪 1. Ensure connection exists
|
|
494
|
+
if (!isConnectionOpen) {
|
|
495
|
+
Logger.warn('No connection found. Creating new connection...', 'publishMessageToExchange');
|
|
496
|
+
await this.createConnection();
|
|
497
|
+
}
|
|
498
|
+
// 🧪 2. Ensure channel is alive
|
|
499
|
+
const channelConnection: any = channel?.connection;
|
|
500
|
+
if (!channel || !channelConnection || channelConnection.stream.destroyed) {
|
|
501
|
+
Logger.warn('Channel seems dead, recreating before publish...', 'publishMessageToExchange');
|
|
502
|
+
await this.recreateChannel();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Function to recreate channel
|
|
508
|
+
*/
|
|
509
|
+
private async recreateChannel() {
|
|
510
|
+
try {
|
|
511
|
+
channel = await connection.createConfirmChannel();
|
|
512
|
+
channel.prefetch(RABBITMQ?.PREFETCH_COUNT)
|
|
513
|
+
Logger.info(' Channel recreated successfully.', 'recreateChannel');
|
|
514
|
+
} catch (err: any) {
|
|
515
|
+
Logger.error(' Failed to recreate channel:', err);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Function to setup the queues and exchanges
|
|
521
|
+
* @params array
|
|
522
|
+
* @rtrun
|
|
523
|
+
*/
|
|
524
|
+
private async setupQueuesAndExchanges(): Promise<void> {
|
|
525
|
+
Logger.info('Reached to setupQueuesAndExchanges', 'setupQueuesAndExchanges');
|
|
526
|
+
try {
|
|
527
|
+
let exchanges: any = configData.exchanges;
|
|
528
|
+
let queues: any = configData.queues;
|
|
529
|
+
let bindings: any = configData.bindings;
|
|
530
|
+
// Declare the exchange with dlx
|
|
531
|
+
for (let data of exchanges) {
|
|
532
|
+
await this.createExchange(data);
|
|
533
|
+
Logger.info(`${data?.name} exchange created`, 'setupQueuesAndExchanges');
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Declare the queues with dlx
|
|
537
|
+
for (let data of queues) {
|
|
538
|
+
await this.createQueue(data);
|
|
539
|
+
Logger.info(`${data?.name} queue created `, 'setupQueuesAndExchanges');
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Declare the bindings for queues and exchanges with routing keys
|
|
543
|
+
for (let obj in bindings) {
|
|
544
|
+
bindings[obj]?.forEach(async (key: any) => {
|
|
545
|
+
await this.bindQueueAndExchanges( obj, key?.queue, key?.routingKey);
|
|
546
|
+
Logger.info(`${key?.queue} bind with exchange name ${obj} and routing key ${key?.routingKey}`, 'setupQueuesAndExchanges');
|
|
547
|
+
})
|
|
548
|
+
}
|
|
549
|
+
} catch (error) {
|
|
550
|
+
Logger.error("Error on setup queues, exchanges and binding", 'setupQueuesAndExchanges', (error as Error).message);
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Function to binding queue and exchange
|
|
557
|
+
* @param exchangeName
|
|
558
|
+
* @param queueName
|
|
559
|
+
* @param routingKey
|
|
560
|
+
*/
|
|
561
|
+
|
|
562
|
+
private async bindQueueAndExchanges(exchangeName:string, queueName:string, routingKey:string):Promise<void> {
|
|
563
|
+
try {
|
|
564
|
+
await channel.bindQueue(queueName, exchangeName, routingKey, { 'toQueue':queueName, 'x-match': 'any' });
|
|
565
|
+
} catch (error:unknown) {
|
|
566
|
+
Logger.error('Error in bind Queue and exchange', 'bindQueueAndExchanges', {message:(error as Error).message});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Function to create the queue
|
|
574
|
+
* @param data
|
|
575
|
+
*/
|
|
576
|
+
|
|
577
|
+
private async createQueue(data:any ):Promise<void> {
|
|
578
|
+
try {
|
|
579
|
+
await channel.assertQueue(data?.name, { durable: data.durable, exclusive: data.exclusive, autoDelete: data.autoDelete, arguments: data.arguments });
|
|
580
|
+
} catch (error:unknown) {
|
|
581
|
+
Logger.error('Error in Create Queue', 'createQueue', {message:(error as Error).message});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Function to create the exchange
|
|
587
|
+
* @param data
|
|
588
|
+
*/
|
|
589
|
+
|
|
590
|
+
private async createExchange(data:any ):Promise<void> {
|
|
591
|
+
try {
|
|
592
|
+
await channel.assertExchange(data?.name, data.type, { durable: data.durable, autoDelete: data.autoDelete });
|
|
593
|
+
} catch (error:unknown) {
|
|
594
|
+
Logger.error('Error in Create exchange', 'createExchange', {message:(error as Error).message});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// private async publishMessageRetryQueue(request: PublishMessageInputType, attempt: number) {
|
|
599
|
+
// try {
|
|
600
|
+
// Logger.error(`Failed to publish message. Attempt ${attempt} of ${maxRetries}. Error:`, 'publishMessageRetryQueue', request);
|
|
601
|
+
// let queueName = (request as PublishMessageInputType)?.queueName
|
|
602
|
+
// const data: MessageModel = (request as PublishMessageInputType)?.message;
|
|
603
|
+
// if (typeof data.message === 'object' && data.message !== null) {
|
|
604
|
+
// (data.message as { retries: number }).retries = attempt;
|
|
605
|
+
// }
|
|
606
|
+
// // Wait before retrying (Exponential Backoff)
|
|
607
|
+
// let retryDelay: number;
|
|
608
|
+
// retryDelay = Number(retry_delay[attempt]);
|
|
609
|
+
// let count:any = retry_delay.length - 1 === attempt?retry_delay[retry_delay.length - 1]:retry_delay[attempt];
|
|
610
|
+
// retryDelay = Number(count);
|
|
611
|
+
// await this.sendMessageToRetryQueue(attempt, data, retryDelay, queueName);
|
|
612
|
+
// Logger.info(`Retrying in ${retryDelay / 1000}, seconds...`, 'publishMessageRetryQueue');
|
|
613
|
+
// await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
614
|
+
// } catch (error) {
|
|
615
|
+
// Logger.error(`Failed to publish message. Attempt ${attempt} of ${maxRetries}. Error:`, 'publishMessageRetryQueue', request);
|
|
616
|
+
// throw error;
|
|
617
|
+
// }
|
|
618
|
+
// }
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Function to connect create to RabbitMQ
|
|
622
|
+
*/
|
|
623
|
+
public async createConnection(): Promise<ConnectionWrapper> {
|
|
624
|
+
try {
|
|
625
|
+
Logger.info('Reached to createConnection', 'createConnection');
|
|
626
|
+
configManager.loadConfig(RABBITMQ?.CONFIG_PATH);
|
|
627
|
+
const secret = await getIAMSecrets();
|
|
628
|
+
let url: string = `amqp://${secret?.[SECRET_KEYS.RABBITMQ_USER]}:${secret?.[SECRET_KEYS.RABBITMQ_PASS]}@${RABBITMQ?.HOST}`;
|
|
629
|
+
// Create a connection to RabbitMQ server
|
|
630
|
+
connection = await amqp.connect(url, { heartbeat: RABBITMQ?.HEARTBEAT });
|
|
631
|
+
isConnectionOpen = true;
|
|
632
|
+
// check the connection status
|
|
633
|
+
this.bindConnectionEventHandler();
|
|
634
|
+
// create channel
|
|
635
|
+
channel = await connection.createConfirmChannel();
|
|
636
|
+
channel.prefetch(RABBITMQ?.PREFETCH_COUNT);
|
|
637
|
+
Logger.info('RabbitMQ connected successfully! ', 'createConnection');
|
|
638
|
+
// create exchange and queue and bind with exchange
|
|
639
|
+
await this.setupQueuesAndExchanges();
|
|
640
|
+
const connectionWrapper: ConnectionWrapper = { connection, channel }
|
|
641
|
+
return connectionWrapper;
|
|
642
|
+
} catch (error) {
|
|
643
|
+
// Log and rethrow any errors that occur during connection
|
|
644
|
+
Logger.error('Failed to connect to RabbitMQ', 'connection', (error as Error)?.message);
|
|
645
|
+
return { connection: null, channel: null } as unknown as ConnectionWrapper;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Function for bind connection event handler on close and error setupQueuesAndExchanges
|
|
651
|
+
* @returns
|
|
652
|
+
*/
|
|
653
|
+
private bindConnectionEventHandler(): boolean {
|
|
654
|
+
try {
|
|
655
|
+
connection.on('close', async(err: any) => {
|
|
656
|
+
Logger.error(`RabbitMQ: bindConnectionEventHandler:close`, `Connection closed`, err);
|
|
657
|
+
isConnectionOpen = false;
|
|
658
|
+
if(connectionRetry<= maxRetries){
|
|
659
|
+
await this.createConnection();
|
|
660
|
+
connectionRetry++;
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
connection.on('error', async(err: any) => {
|
|
664
|
+
Logger.error(`RabbitMQ: bindConnectionEventHandler:error`, `Connection error`, err);
|
|
665
|
+
isConnectionOpen = false;
|
|
666
|
+
if(connectionRetry<= maxRetries){
|
|
667
|
+
await this.createConnection();
|
|
668
|
+
connectionRetry++;
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
return isConnectionOpen;
|
|
672
|
+
} catch (error) {
|
|
673
|
+
isConnectionOpen = false;
|
|
674
|
+
return isConnectionOpen;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
}
|
|
680
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type ActionType<T> = (input: T) => void;
|