@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.
Files changed (68) hide show
  1. package/README.md +1 -1
  2. package/dist/src/Util/commonUtil.js.map +1 -0
  3. package/dist/src/Util/constants.js +8 -0
  4. package/dist/src/Util/constants.js.map +1 -0
  5. package/dist/src/Util/logger.js.map +1 -0
  6. package/dist/src/Util/requestTracer.js.map +1 -0
  7. package/dist/src/config/ConfigManager.js.map +1 -0
  8. package/dist/src/config/index.js +1 -1
  9. package/dist/src/config/index.js.map +1 -0
  10. package/dist/src/index.js +22 -1
  11. package/dist/src/index.js.map +1 -0
  12. package/dist/src/messageBroker/BaseRabbitMQClient.js.map +1 -0
  13. package/dist/src/messageBroker/ConnectionManager.js.map +1 -0
  14. package/dist/src/messageBroker/MessageBrokerClient.js.map +1 -0
  15. package/dist/src/messageBroker/MessageConsumer.js.map +1 -0
  16. package/dist/src/messageBroker/MessageProducer.js.map +1 -0
  17. package/dist/src/messageBroker/RabbitMQClient.js.map +1 -0
  18. package/dist/src/messageBroker/RetryManager.js.map +1 -0
  19. package/dist/src/messageBroker/interface/ConnectionWrapper.js.map +1 -0
  20. package/dist/src/messageBroker/interface/IMessageBrokerClient.js.map +1 -0
  21. package/dist/src/messageBroker/rabbitmq/MessageBrokerClient.js +4 -1
  22. package/dist/src/messageBroker/rabbitmq/MessageBrokerClient.js.map +1 -0
  23. package/dist/src/messageBroker/types/ActionType.js.map +1 -0
  24. package/dist/src/messageBroker/types/PublishMessageInputType.js.map +1 -0
  25. package/dist/src/models/MessageModel.js.map +1 -0
  26. package/dist/src/models/NotificationMessageModel.js.map +1 -0
  27. package/package.json +43 -40
  28. package/src/Util/commonUtil.ts +41 -0
  29. package/src/Util/constants.ts +4 -0
  30. package/src/Util/logger.ts +219 -0
  31. package/src/Util/requestTracer.ts +28 -0
  32. package/src/config/ConfigManager.ts +35 -0
  33. package/src/config/index.ts +31 -0
  34. package/src/index.ts +73 -0
  35. package/src/messageBroker/BaseRabbitMQClient.ts +30 -0
  36. package/src/messageBroker/ConnectionManager.ts +182 -0
  37. package/src/messageBroker/MessageBrokerClient.ts +88 -0
  38. package/src/messageBroker/MessageConsumer.ts +85 -0
  39. package/src/messageBroker/MessageProducer.ts +142 -0
  40. package/src/messageBroker/RabbitMQClient.ts +47 -0
  41. package/src/messageBroker/RetryManager.ts +64 -0
  42. package/src/messageBroker/interface/ConnectionWrapper.ts +7 -0
  43. package/src/messageBroker/interface/IMessageBrokerClient.ts +11 -0
  44. package/src/messageBroker/rabbitmq/MessageBrokerClient.ts +680 -0
  45. package/src/messageBroker/types/ActionType.ts +1 -0
  46. package/src/messageBroker/types/PublishMessageInputType.ts +8 -0
  47. package/src/models/MessageModel.ts +14 -0
  48. package/tsconfig.json +73 -0
  49. package/dist/src/Util/commonUtil.d.ts +0 -16
  50. package/dist/src/Util/logger.d.ts +0 -68
  51. package/dist/src/Util/requestTracer.d.ts +0 -7
  52. package/dist/src/config/ConfigManager.d.ts +0 -9
  53. package/dist/src/config/index.d.ts +0 -29
  54. package/dist/src/index.d.ts +0 -2
  55. package/dist/src/messageBroker/BaseRabbitMQClient.d.ts +0 -16
  56. package/dist/src/messageBroker/ConnectionManager.d.ts +0 -60
  57. package/dist/src/messageBroker/MessageBrokerClient.d.ts +0 -34
  58. package/dist/src/messageBroker/MessageConsumer.d.ts +0 -26
  59. package/dist/src/messageBroker/MessageProducer.d.ts +0 -29
  60. package/dist/src/messageBroker/RabbitMQClient.d.ts +0 -12
  61. package/dist/src/messageBroker/RetryManager.d.ts +0 -23
  62. package/dist/src/messageBroker/interface/ConnectionWrapper.d.ts +0 -6
  63. package/dist/src/messageBroker/interface/IMessageBrokerClient.d.ts +0 -7
  64. package/dist/src/messageBroker/rabbitmq/MessageBrokerClient.d.ts +0 -122
  65. package/dist/src/messageBroker/types/ActionType.d.ts +0 -1
  66. package/dist/src/messageBroker/types/PublishMessageInputType.d.ts +0 -7
  67. package/dist/src/models/MessageModel.d.ts +0 -5
  68. /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;
@@ -0,0 +1,8 @@
1
+ import MessageModel from "../../models/MessageModel"
2
+
3
+ export type PublishMessageInputType = {
4
+ queueName:string,
5
+ message:MessageModel,
6
+ exchangeName?: string,
7
+ options?:object | null
8
+ }