@platform-x/hep-message-broker-client 1.1.28 → 1.1.29

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 (50) hide show
  1. package/dist/src/config/index.js +9 -8
  2. package/dist/src/messageBroker/rabbitmq/MessageBroker.js +76 -10
  3. package/dist/src/messageBroker/rabbitmq/MessageBrokerClient.js +18 -187
  4. package/package.json +1 -1
  5. package/rabbitMQConfig.json +344 -0
  6. package/dist/src/Util/commonUtil.js.map +0 -1
  7. package/dist/src/Util/constants.js.map +0 -1
  8. package/dist/src/Util/logger.js.map +0 -1
  9. package/dist/src/Util/requestTracer.js.map +0 -1
  10. package/dist/src/config/ConfigManager.js.map +0 -1
  11. package/dist/src/config/index.js.map +0 -1
  12. package/dist/src/index.js.map +0 -1
  13. package/dist/src/messageBroker/BaseRabbitMQClient.js.map +0 -1
  14. package/dist/src/messageBroker/ConnectionManager.js.map +0 -1
  15. package/dist/src/messageBroker/MessageBrokerClient.js.map +0 -1
  16. package/dist/src/messageBroker/MessageConsumer.js.map +0 -1
  17. package/dist/src/messageBroker/MessageProducer.js.map +0 -1
  18. package/dist/src/messageBroker/RabbitMQClient.js.map +0 -1
  19. package/dist/src/messageBroker/RetryManager.js.map +0 -1
  20. package/dist/src/messageBroker/interface/ConnectionWrapper.js.map +0 -1
  21. package/dist/src/messageBroker/interface/IMessageBrokerClient.js.map +0 -1
  22. package/dist/src/messageBroker/rabbitmq/MessageBroker.js.map +0 -1
  23. package/dist/src/messageBroker/rabbitmq/MessageBrokerClient.js.map +0 -1
  24. package/dist/src/messageBroker/types/ActionType.js.map +0 -1
  25. package/dist/src/messageBroker/types/PublishMessageInputType.js.map +0 -1
  26. package/dist/src/models/MessageModel.js.map +0 -1
  27. package/dist/src/models/NotificationMessageModel.js.map +0 -1
  28. package/src/Util/commonUtil.ts +0 -41
  29. package/src/Util/constants.ts +0 -9
  30. package/src/Util/logger.ts +0 -219
  31. package/src/Util/requestTracer.ts +0 -28
  32. package/src/config/ConfigManager.ts +0 -35
  33. package/src/config/index.ts +0 -38
  34. package/src/index.ts +0 -74
  35. package/src/messageBroker/BaseRabbitMQClient.ts +0 -30
  36. package/src/messageBroker/ConnectionManager.ts +0 -182
  37. package/src/messageBroker/MessageBrokerClient.ts +0 -88
  38. package/src/messageBroker/MessageConsumer.ts +0 -85
  39. package/src/messageBroker/MessageProducer.ts +0 -142
  40. package/src/messageBroker/RabbitMQClient.ts +0 -47
  41. package/src/messageBroker/RetryManager.ts +0 -64
  42. package/src/messageBroker/interface/ConnectionWrapper.ts +0 -7
  43. package/src/messageBroker/interface/IMessageBrokerClient.ts +0 -11
  44. package/src/messageBroker/rabbitmq/MessageBroker.ts +0 -547
  45. package/src/messageBroker/rabbitmq/MessageBrokerClient.ts +0 -855
  46. package/src/messageBroker/types/ActionType.ts +0 -1
  47. package/src/messageBroker/types/PublishMessageInputType.ts +0 -8
  48. package/src/models/MessageModel.ts +0 -14
  49. package/src/models/NotificationMessageModel.ts +0 -0
  50. package/tsconfig.json +0 -73
@@ -1,855 +0,0 @@
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
-
31
- // Registry of active consumers so they can be re-subscribed after any
32
- // (re)connect. Without this, a connection blip brings the socket back up but
33
- // leaves every queue with no consumer attached (the primary "consumer
34
- // frequently detaches" bug). Keyed by queueName to avoid duplicate
35
- // subscriptions on the same queue.
36
- interface RegisteredConsumer {
37
- queueName: string;
38
- classInstance: any;
39
- consumerHandler: string;
40
- consumerErrorHandler: string;
41
- }
42
- let registeredConsumers: Map<string, RegisteredConsumer> = new Map();
43
- // Guards handleReconnect() so overlapping close/error events don't spawn
44
- // multiple concurrent reconnect loops.
45
- let isReconnecting = false;
46
-
47
- export class MessageBrokerClient {
48
-
49
- // private readonly correlationId: string;
50
- // constructor() {
51
- // this.correlationId = generateCorrelationId();
52
- // }
53
-
54
- /**
55
- * Function to check the status of the connection
56
- * @returns boolean
57
- */
58
- public async isConnected(): Promise<boolean> {
59
- return isConnectionOpen;
60
- };
61
-
62
-
63
- /**
64
- * Function to initialize the connection, channel and setup the queues and exchanges
65
- * @param reqData
66
- * @returns
67
- */
68
- public async initialize() {
69
- Logger.info('Reached to initialize', 'initialize');
70
- configData = configManager.getConfig();
71
- // dlx_exchange = configData?.dlx_exchange;
72
- dlx_queue = configData?.dlx_queue;
73
- // retry_exchange = configData?.retry_exchange;
74
- // retry_queue = configData?.retry_queue;
75
- let retries: number = 0;
76
- // Retrie times connection
77
- while (retries < maxRetries) {
78
- try {
79
- if(RABBITMQ.CONNECTION_ERROR === 'true' || RABBITMQ.CONNECTION_ERROR === true){
80
- throw new Error("Error in connection");
81
- }
82
- await this.createConnection();
83
- return { connection: connection, channel: channel };
84
- } catch (err) {
85
- retries++;
86
- Logger.error('initialize:Failed to connect to RabbitMQ', `Attempt ${retries} of ${maxRetries}.`);
87
- if (retries >= maxRetries) {
88
- Logger.error('Max retries reached, cannot connect to RabbitMQ.', 'initialize');
89
- return { connection, channel };
90
- }
91
- // Wait before retrying (e.g., 10 seconds)
92
- await new Promise(resolve => setTimeout(resolve, 10000));
93
- }
94
- }
95
- };
96
-
97
-
98
-
99
- /**
100
- * Function to publish a message with retry
101
- * @param request
102
- * @param classInstance
103
- * @param messagePublisherErrorHandler
104
- * @returns
105
- */
106
- public async publishMessage(
107
- request: PublishMessageInputType
108
- // , classInstance:any, messagePublisherErrorHandler:string
109
- ){
110
- Logger.info('Reached:publishMessage', 'publishMessage');
111
- // Logger.debug('Reached:publishMessageToExchange', 'publishMessageToExchange',{classInstance, messagePublisherErrorHandler});
112
- try{
113
- if(isConnectionOpen){
114
- Logger.info('Check connection open or not', 'publishMessage');
115
- const queueName: string = request.queueName;
116
- const data: MessageModel = request.message;
117
- const messageBuffer = Buffer.from(JSON.stringify(data?.message));
118
- // let attempt: number = 0;
119
- // let retries: number = typeof data.message === 'object' && data.message !== null ? (data.message as { retries: number }).retries ?? 0 : 0;
120
- // while (attempt < maxRetries ) {
121
- Logger.info('Max retries check', 'publishMessage');
122
- const isSent = await new Promise<boolean>((resolve, reject) => {
123
- let sent = channel.sendToQueue(queueName, Buffer.from(messageBuffer), {
124
- persistent: true, // Ensures the message is persisted
125
- // correlationId: this.correlationId,
126
- });
127
- if (sent) {
128
- Logger.debug(`Message sent successfully`, 'publishMessage', { data, queueName });
129
- resolve(true);
130
- } else {
131
- Logger.error(`Failed to publish message.`, 'publishMessage', { message: messageBuffer.toString() });
132
- reject(false);
133
- }
134
- });
135
- if((isSent) && (RABBITMQ.PUBLISHER_ERROR !== 'true' && RABBITMQ.PUBLISHER_ERROR !== true)){
136
- return isSent;
137
- }
138
- // else{
139
- // let queueName = (request as PublishMessageInputType)?.queueName
140
- // if (attempt >= maxRetries) {
141
- // Logger.error('Max retries reached. Failed to publish message.', 'publishMessage', request);
142
- // // Optionally: Log or persist the failed message to a DB, file, or other storage for manual intervention
143
- // this.sendMessageToDeadLatter(attempt, data, queueName);
144
- // this.messagePublisherErrorHandler(request); // we need to comment this line
145
- // if (typeof classInstance[messagePublisherErrorHandler] === 'function') {
146
- // classInstance[messagePublisherErrorHandler](data); // Dynamically call the method
147
- // } else {
148
- // Logger.info(`Method ${messagePublisherErrorHandler} not found in classInstance.`, 'publishMessage');
149
- // }
150
- // return false;
151
- // }else{
152
- // await this.publishMessageRetryQueue(request, attempt);
153
- // attempt++;
154
- // }
155
- // }
156
- // }
157
-
158
- }
159
- } catch (error) {
160
- Logger.error(`Error sending message: ${error}`, 'sendMessage');
161
- throw new Error("Failed to publish message to queue");
162
- }
163
- }
164
-
165
- /**
166
- * Function to publish a message with retry
167
- * @param request
168
- * @param classInstance
169
- * @param messagePublisherErrorHandler
170
- * @returns
171
- */
172
- public async publishMessageToExchange(request: PublishMessageInputType): Promise<boolean> {
173
- Logger.info('Reached:publishMessageToExchange', 'publishMessageToExchange');
174
- let attempt = 0;
175
- const exchangeName: string | undefined = request?.exchangeName ?? '';
176
- const queueName: string = request.queueName;
177
- const data: MessageModel = request?.message;
178
- const messageBuffer = Buffer.from(JSON.stringify(data.message));
179
- try {
180
- // 1. Ensure connection and channel exists
181
- await this.checkConnectionAndChannel();
182
-
183
- // 2. Retry loop
184
- while (attempt < maxRetries) {
185
- try {
186
- Logger.info(`Attempting to publish message`, 'publishMessageToExchange');
187
- let sent = channel.publish(exchangeName, queueName, messageBuffer, {
188
- persistent: true,
189
- });
190
- // if(attempt <1){ sent = false}else{sent = true} // Simulate failure for testing retry logic
191
- if (sent && (RABBITMQ.PUBLISHER_ERROR !== 'true' && RABBITMQ.PUBLISHER_ERROR !== true)) {
192
- Logger.info(`Message published successfully on attempt ${attempt + 1}`, 'publishMessageToExchange');
193
- Logger.info(`✅ Message sent successfully`, 'publishMessageToExchange');
194
- break;
195
- } else {
196
- throw new Error('Publish returned false');
197
- }
198
- } catch (error) {
199
- attempt++;
200
- Logger.error(`Failed attempt ${attempt} to publish message`, 'publishMessageToExchange', error);
201
- if (attempt < maxRetries) {
202
- Logger.info(`Retrying in ${retry_delay}ms...`, 'publishMessageToExchange');
203
- await new Promise(res => setTimeout(res, retry_delay));
204
- }else {
205
- Logger.error(`Error in publishMessageToExchange: ${error}`, 'publishMessageToExchange');
206
- throw new Error('Max retries reached');
207
- }
208
- }
209
- }
210
- return true;
211
- } catch (error) {
212
- Logger.error('Max retries reached. Sending message to Dead Letter Queue...', 'publishMessageToExchange');
213
- await this.sendMessageToDeadLatter(attempt, data);
214
- const customError = new Error('Failed to publish message to exchange');
215
- (customError as any).originalError = error; // optional
216
- throw customError;
217
- }
218
- }
219
- /**
220
- * Funciton to message handler
221
- */
222
- // TO Do rename function name messagePublisherErrorHandler
223
- public async messagePublisherErrorHandler(request: PublishMessageInputType): Promise<boolean> {
224
- Logger.info(`${JSON.stringify(request)}`, 'messagePublisherErrorHandler');
225
- return Promise.resolve(true);
226
- }
227
-
228
- /**
229
- * Funvction for initialize consumer queue
230
- * @param consumerQueues
231
- */
232
- public async initializeConsumers(classInstance:any, consumerHandler:string, consumerErrorHandler:string,serviceConsumerQueues?: {}) {
233
- try {
234
- Logger.info('Reached in initialize consumer', `initializeConsumers`);
235
- Logger.debug('Consumer queues serviceConsumerQueues : ', 'initializeConsumers', { serviceConsumerQueues });
236
-
237
- let consume_queues = serviceConsumerQueues ?? configData?.consume_queues;
238
- // Consume messages from all queues
239
- for (let queueName in consume_queues) {
240
- if (Object.prototype.hasOwnProperty.call(consume_queues, queueName)) {
241
- // Call function Consume messages with retry
242
- await this.consumeMessage(queueName, classInstance, consumerHandler, consumerErrorHandler,);
243
- }
244
- }
245
- } catch (error: unknown) {
246
- Logger.error('Error initializing consumers:', 'initializeConsumers', (error as Error).message);
247
- throw new Error((error as Error).message);
248
- }
249
- }
250
-
251
- /**
252
- * Function for perform action with retry
253
- * @param actionHandler
254
- * @param context
255
- * @param failActionHandler
256
- * @returns
257
- */
258
- public async performActionWithRetry<T>(actionHandler: ActionType<T>, failActionHandler: ActionType<T>, context: T): Promise<boolean> {
259
- Logger.info("Reached: in perform action with retry ", "performActionWithRetry")
260
- let attempt: number = 0;
261
- while (attempt < maxRetries) {
262
- try {
263
- actionHandler(context);
264
- return true;
265
- } catch (error) {
266
- attempt++;
267
- Logger.error(`Failed to publish message. Attempt ${attempt} of ${maxRetries}. Error:`, 'performActionWithRetry', { message: (error as Error).message });
268
- let queueName = (context as PublishMessageInputType)?.queueName
269
- const data: MessageModel = (context as PublishMessageInputType)?.message;
270
- if (attempt >= maxRetries) {
271
- Logger.error('Max retries reached. Failed to publish message.', 'performActionWithRetry');
272
- // Optionally: Log or persist the failed message to a DB, file, or other storage for manual intervention
273
- this.sendMessageToDeadLatter(attempt, data);
274
- failActionHandler(context);
275
- return false;
276
- }
277
- // Wait before retrying (Exponential Backoff)
278
- let retryDelay: number;
279
- retryDelay = Number(retry_delay[attempt- 1]);
280
- let count:any = retry_delay.length - 1 === attempt?retry_delay.length - 1:retry_delay[attempt - 1];
281
- retryDelay = Number(count);
282
- this.sendMessageToRetryQueue(attempt, data, retryDelay, queueName);
283
- Logger.info(`Retrying in ${retryDelay / 1000} seconds...`, 'performActionWithRetry');
284
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
285
- }
286
- }
287
- return false;
288
- }
289
-
290
-
291
-
292
- /**
293
- * Function to consume message with retry
294
- * @param queueName
295
- */
296
- public async consumeMessage(queueName: string, classInstance: any, consumerHandler: string , consumerErrorHandler:string) {
297
- Logger.info("Reached: Consuming messages from the queue", 'consumeMessage')
298
- // Remember this consumer so it can be re-attached after any reconnect or
299
- // channel recreation. Keyed by queueName so repeated calls don't stack.
300
- registeredConsumers.set(queueName, { queueName, classInstance, consumerHandler, consumerErrorHandler });
301
- return await this.subscribeConsumer(queueName, classInstance, consumerHandler, consumerErrorHandler);
302
- };
303
-
304
- /**
305
- * Function to attach a single consumer to a queue. Performs the actual
306
- * channel.consume() and owns the ack/retry/DLQ logic. Kept separate from
307
- * consumeMessage() so re-subscription (on reconnect) does not re-register
308
- * the consumer in the registry, which would grow it unboundedly.
309
- * @param queueName
310
- * @param classInstance
311
- * @param consumerHandler
312
- * @param consumerErrorHandler
313
- */
314
- private async subscribeConsumer(queueName: string, classInstance: any, consumerHandler: string, consumerErrorHandler: string) {
315
- let attempt:number = 0;
316
- if(isConnectionOpen){
317
- // Capture the channel this consumer is registered on. acks/nacks for
318
- // messages delivered here MUST go back to this same channel object —
319
- // if the module-level `channel` is later reassigned (recreateChannel /
320
- // createConnection), acking against the new channel raises
321
- // PRECONDITION_FAILED ("unknown delivery tag") and closes it, taking
322
- // every consumer down with it.
323
- const ch: amqp.Channel = channel;
324
- return await ch.consume(queueName, async (message: any) => {
325
- if(message){
326
- Logger.info(`Message received from queue ${queueName}: ${JSON.stringify(message)}`, 'consumeMessage');
327
- let msgData: any = JSON.parse(message.content.toString());
328
- Logger.info(`Received message from queue ${queueName}: ${JSON.stringify(msgData)}`, 'consumeMessage');
329
- const headers = message.properties.headers || {};
330
- attempt = headers[retryCountHeader] || 0;
331
- 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};
332
- try {
333
- let success:any = {};
334
- if(RABBITMQ.CONSUMER_ERROR !== 'true' && RABBITMQ.CONSUMER_ERROR !== true){
335
- Logger.info("Consumer handler call for process the request", 'consumeMessage')
336
- // success = await this.consumerHandler({ queueName, message: msgData });
337
- if (typeof classInstance[consumerHandler] === 'function') {
338
- success = await classInstance[consumerHandler]({ queueName, message: msgData }); // Dynamically call the method
339
- } else {
340
- Logger.info(`Method ${consumerHandler} not found in classInstance.`, 'consumerMessage');
341
- }
342
- }
343
- if(success?.isMatch === true){
344
- if (!success?.status) {
345
- const retryCount = message.properties.headers['x-retry-count'] || 0;
346
- // while (retryCount < maxRetries) {
347
- attempt = retryCount?retryCount:attempt;
348
- if (retryCount >= maxRetries) {
349
- Logger.error('Max retries reached. Failed to publish message.', 'consumeMessage');
350
- // Optionally: Log or persist the failed message to a DB, file, or other storage for manual intervention
351
- this.sendMessageToDeadLatter(attempt, data);
352
- // this.consumerErrorHandler(data);
353
- if (typeof classInstance[consumerErrorHandler] === 'function') {
354
- classInstance[consumerErrorHandler](data); // Dynamically call the method
355
- } else {
356
- Logger.info(`Method ${consumerErrorHandler} not found in classInstance.`, 'consumerMessage');
357
- }
358
- this.safeNack(ch, message); // Move message to DLQ after failure
359
- return false;
360
- }
361
- attempt++;
362
- if (data?.retries >=0) {
363
- data.retries = attempt;
364
- }
365
- // Wait before retrying (Exponential Backoff)
366
- let retryDelay: number;
367
- let count:any = retry_delay.length === attempt?retry_delay[retry_delay.length - 1]:retry_delay[attempt-1];
368
- retryDelay = Number(count);
369
- await this.sendMessageToRetryQueue(attempt, data, retryDelay, queueName);
370
- Logger.info(`Retrying in ${retryDelay / 1000} seconds...`, 'consumeMessage');
371
- // Ack immediately once the message is safely on the retry
372
- // queue. Do NOT block the delivery for the full TTL — the
373
- // delay is enforced by the retry queue's own expiration.
374
- // Holding it here risks breaching RabbitMQ's
375
- // consumer_timeout and getting the channel cancelled.
376
- this.safeAck(ch, message);
377
- return;
378
- } else {
379
- Logger.info(`Acknowledge the successful processing`, 'consumeMessage');
380
- }
381
- // Acknowledge the successful processing
382
- this.safeAck(ch, message);
383
- }
384
- } catch (error) {
385
- let errorMessage:string = (error as Error).message;
386
- Logger.error(`Error processing message`, 'consumeMessage', { message: errorMessage});
387
- this.sendMessageToDeadLatter(attempt, data);
388
- // this.consumerErrorHandler(data);
389
- if (typeof classInstance[consumerErrorHandler] === 'function') {
390
- classInstance[consumerErrorHandler](data); // Dynamically call the method
391
- } else {
392
- Logger.info(`Method ${consumerErrorHandler} not found in classInstance.`, 'consumerMessage');
393
- }
394
- this.safeNack(ch, message); // Move message to DLQ after failure
395
- }
396
- }
397
- });
398
- }
399
- };
400
-
401
- /**
402
- * Function to re-attach every registered consumer. Called after each
403
- * successful (re)connect and after a channel is recreated, so queues never
404
- * end up with the connection alive but no consumer attached.
405
- */
406
- private async reregisterConsumers(): Promise<void> {
407
- if (registeredConsumers.size === 0) {
408
- return;
409
- }
410
- Logger.info(`Re-subscribing ${registeredConsumers.size} consumer(s) after (re)connect`, 'reregisterConsumers');
411
- for (const c of registeredConsumers.values()) {
412
- try {
413
- await this.subscribeConsumer(c.queueName, c.classInstance, c.consumerHandler, c.consumerErrorHandler);
414
- Logger.info(`Consumer re-subscribed on queue ${c.queueName}`, 'reregisterConsumers');
415
- } catch (error) {
416
- Logger.error(`Failed to re-subscribe consumer on queue ${c.queueName}`, 'reregisterConsumers', (error as Error).message);
417
- }
418
- }
419
- }
420
-
421
- /**
422
- * Function to ack a message against the channel it was delivered on, guarded
423
- * so a stale delivery tag (channel already replaced) cannot throw and take
424
- * down the current channel.
425
- * @param ch the channel the message was consumed on
426
- * @param message
427
- */
428
- private safeAck(ch: amqp.Channel, message: amqp.Message): void {
429
- try {
430
- ch.ack(message);
431
- } catch (error) {
432
- Logger.error('Failed to ack message (stale channel?)', 'safeAck', (error as Error).message);
433
- }
434
- }
435
-
436
- /**
437
- * Function to nack a message against the channel it was delivered on, guarded
438
- * against stale delivery tags. Never requeues (routes to DLQ).
439
- * @param ch the channel the message was consumed on
440
- * @param message
441
- */
442
- private safeNack(ch: amqp.Channel, message: amqp.Message): void {
443
- try {
444
- ch.nack(message, false, false);
445
- } catch (error) {
446
- Logger.error('Failed to nack message (stale channel?)', 'safeNack', (error as Error).message);
447
- }
448
- }
449
-
450
- // /**
451
- // * Function for consumer massage handler
452
- // * @param request
453
- // * @returns
454
- // */
455
- // public async consumerHandler(request: PublishMessageInputType): Promise<boolean> {
456
- // Logger.info(`${JSON.stringify(request)}`, 'consumerHandler');
457
- // return true;
458
- // }
459
-
460
- // /**
461
- // * Function to consumer message error handler
462
- // * @param request
463
- // * @returns
464
- // */
465
-
466
- // public async consumerErrorHandler(request:any):Promise<boolean> {
467
- // Logger.info(`${JSON.stringify(request)}`, 'consumerErrorHandler');
468
- // return true;
469
- // }
470
-
471
-
472
- /**
473
- * Function to send message to dead later queue
474
- * @param attempt
475
- * @param data
476
- * @param queueName
477
- * @retruns
478
- */
479
-
480
- public async sendMessageToDeadLatter(attempt:number, data:any){
481
- Logger.info("Reached: send message on dead letter queue", 'sendMessageToDeadLater')
482
- try {
483
- if(isConnectionOpen){
484
- Logger.info(`Attempting to send message to DLX queue: ${dlx_queue} (attempt: ${attempt}, retry-count: ${attempt - 1}, size: ${JSON.stringify(data).length} bytes)`, 'sendMessageToDeadLater');
485
- const sent = channel.sendToQueue(dlx_queue, Buffer.from(JSON.stringify(data)), {
486
- persistent: true,
487
- headers: {
488
- 'x-retry-count': attempt-1
489
- },
490
- });
491
- if (sent) {
492
- Logger.info(`Message successfully published to DLX queue: ${dlx_queue} (attempt: ${attempt}, retry-count: ${attempt - 1})`, 'sendMessageToDeadLater');
493
- } else {
494
- Logger.error(`Failed to publish message to DLX queue: ${dlx_queue} - Channel buffer full (attempt: ${attempt}, retry-count: ${attempt - 1})`, 'sendMessageToDeadLater');
495
- }
496
- return sent;
497
- } else {
498
- Logger.error(`Cannot send message to DLX - Connection is not open (queue: ${dlx_queue}, attempt: ${attempt})`, 'sendMessageToDeadLater');
499
- return false;
500
- }
501
- } catch (error:unknown) {
502
- Logger.error(`Error sending message to dead letter queue: ${(error as Error).message} (queue: ${dlx_queue}, attempt: ${attempt})`, 'sendMessageToDeadLater');
503
- throw error;
504
- }
505
- }
506
-
507
-
508
- /***
509
- * Function to send message to retry queue
510
- * @param attempt
511
- * @param data
512
- * @param retryDelay
513
- * @param queueName
514
- * @returns
515
- */
516
-
517
- public async sendMessageToRetryQueue(attempt:number, data:any, retryDelay:any, queueName:string){
518
- Logger.info("Reached: send message on retry queue", 'sendMessageToRetryQueue')
519
- try {
520
- if(isConnectionOpen){
521
- const isSent = await new Promise<boolean>((resolve, reject) => {
522
- const messageBuffer = Buffer.from(JSON.stringify(data));
523
- let sent = channel.sendToQueue(queueName, messageBuffer, {
524
- headers: {
525
- 'x-retry-count': attempt + 1,
526
- 'x-message-ttl': retryDelay,
527
- }
528
- });
529
- if (sent) {
530
- Logger.debug(`Message sent successfully`, 'publishMessage', { data, queueName });
531
- resolve(true);
532
- } else {
533
- Logger.error(`Failed to publish message.`, 'publishMessage', { message: data.toString() });
534
- reject(false);
535
- }
536
- });
537
- return isSent;
538
- }
539
- } catch (error:unknown) {
540
- Logger.error("Error in send message on retry queue", 'sendMessageToRetryQueue', {message: (error as Error ).message});
541
- throw error;
542
- }
543
- }
544
-
545
- /**
546
- * Function to find the queue, exchange and add binding
547
- * @param queueName
548
- * @param exchangeName
549
- * @returns
550
- */
551
- public async findQueueAndExchange(queueName:string, exchangeName:string){
552
- Logger.info('Reached to findQueueAndExchange', 'findQueueAndExchange');
553
- try {
554
- let exchanges: any = configData?.exchanges;
555
- let queues: any = configData.queues;
556
- let bindings: any = configData.bindings;
557
- // Declare the queues with dlx
558
-
559
- for (let data of queues) {
560
- if(data?.name === queueName){
561
- await this.createQueue(data);
562
- }
563
- Logger.info(`${data?.name} queue created `, 'findQueueAndExchange');
564
- }
565
-
566
- // Declare the exchange with dlx
567
- for (let data of exchanges) {
568
- if(data?.name === exchangeName){
569
- await this.createExchange(data);
570
- }
571
- Logger.info(`${data?.name} exchange created`, 'findQueueAndExchange');
572
- }
573
-
574
- // Declare the bindings for queues and exchanges with routing keys
575
- for (let obj in bindings) {
576
- bindings[obj]?.forEach(async (key: any) => {
577
- if(obj === exchangeName && key?.queue === queueName){
578
- await this.bindQueueAndExchanges(obj, key?.queue, key?.routingKey);
579
- }
580
- Logger.info(`${key?.queue} bind with exchange name ${obj} and routing key ${key?.routingKey}`, 'findQueueAndExchange');
581
- })
582
- }
583
- } catch (error) {
584
- Logger.error("Error on setup queues, exchanges and binding", 'findQueueAndExchange', (error as Error).message);
585
- throw error;
586
- }
587
- }
588
-
589
- /**
590
- * Function to check connection and channel
591
- */
592
- private async checkConnectionAndChannel() {
593
- Logger.info('Reached: checkConnectionAndChannel', 'checkConnectionAndChannel');
594
- // 🧪 1. Ensure connection exists
595
- if (!isConnectionOpen) {
596
- Logger.warn('No connection found. Creating new connection...', 'publishMessageToExchange');
597
- await this.createConnection();
598
- }
599
- // 🧪 2. Ensure channel is alive
600
- const channelConnection: any = channel?.connection;
601
- if (!channel || !channelConnection || channelConnection.stream.destroyed) {
602
- Logger.warn('Channel seems dead, recreating before publish...', 'publishMessageToExchange');
603
- await this.recreateChannel();
604
- }
605
- }
606
-
607
- /**
608
- * Function to recreate channel
609
- */
610
- private async recreateChannel() {
611
- try {
612
- channel = await connection.createConfirmChannel();
613
- channel.prefetch(RABBITMQ?.PREFETCH_COUNT)
614
- Logger.info(' Channel recreated successfully.', 'recreateChannel');
615
- } catch (err: any) {
616
- Logger.error(' Failed to recreate channel:', err);
617
- }
618
- }
619
-
620
- /**
621
- * Function to setup the queues and exchanges
622
- * @params array
623
- * @rtrun
624
- */
625
- private async setupQueuesAndExchanges(): Promise<void> {
626
- Logger.info('Reached to setupQueuesAndExchanges', 'setupQueuesAndExchanges');
627
- try {
628
- let exchanges: any = configData.exchanges;
629
- let queues: any = configData.queues;
630
- let bindings: any = configData.bindings;
631
- // Declare the exchange with dlx
632
- for (let data of exchanges) {
633
- await this.createExchange(data);
634
- Logger.info(`${data?.name} exchange created`, 'setupQueuesAndExchanges');
635
- }
636
-
637
- // Declare the queues with dlx
638
- for (let data of queues) {
639
- await this.createQueue(data);
640
- Logger.info(`${data?.name} queue created `, 'setupQueuesAndExchanges');
641
- }
642
-
643
- // Declare the bindings for queues and exchanges with routing keys
644
- for (let obj in bindings) {
645
- bindings[obj]?.forEach(async (key: any) => {
646
- await this.bindQueueAndExchanges( obj, key?.queue, key?.routingKey);
647
- Logger.info(`${key?.queue} bind with exchange name ${obj} and routing key ${key?.routingKey}`, 'setupQueuesAndExchanges');
648
- })
649
- }
650
- } catch (error) {
651
- Logger.error("Error on setup queues, exchanges and binding", 'setupQueuesAndExchanges', (error as Error).message);
652
- }
653
- };
654
-
655
-
656
- /**
657
- * Function to binding queue and exchange
658
- * @param exchangeName
659
- * @param queueName
660
- * @param routingKey
661
- */
662
-
663
- private async bindQueueAndExchanges(exchangeName:string, queueName:string, routingKey:string):Promise<void> {
664
- try {
665
- await channel.bindQueue(queueName, exchangeName, routingKey, { 'toQueue':queueName, 'x-match': 'any' });
666
- } catch (error:unknown) {
667
- Logger.error('Error in bind Queue and exchange', 'bindQueueAndExchanges', {message:(error as Error).message});
668
- }
669
-
670
- }
671
-
672
-
673
- /**
674
- * Function to create the queue
675
- * @param data
676
- */
677
-
678
- private async createQueue(data:any ):Promise<void> {
679
- try {
680
- await channel.assertQueue(data?.name, { durable: data.durable, exclusive: data.exclusive, autoDelete: data.autoDelete, arguments: data.arguments });
681
- } catch (error:unknown) {
682
- Logger.error('Error in Create Queue', 'createQueue', {message:(error as Error).message});
683
- }
684
- }
685
-
686
- /**
687
- * Function to create the exchange
688
- * @param data
689
- */
690
-
691
- private async createExchange(data:any ):Promise<void> {
692
- try {
693
- await channel.assertExchange(data?.name, data.type, { durable: data.durable, autoDelete: data.autoDelete });
694
- } catch (error:unknown) {
695
- Logger.error('Error in Create exchange', 'createExchange', {message:(error as Error).message});
696
- }
697
- }
698
-
699
- // private async publishMessageRetryQueue(request: PublishMessageInputType, attempt: number) {
700
- // try {
701
- // Logger.error(`Failed to publish message. Attempt ${attempt} of ${maxRetries}. Error:`, 'publishMessageRetryQueue', request);
702
- // let queueName = (request as PublishMessageInputType)?.queueName
703
- // const data: MessageModel = (request as PublishMessageInputType)?.message;
704
- // if (typeof data.message === 'object' && data.message !== null) {
705
- // (data.message as { retries: number }).retries = attempt;
706
- // }
707
- // // Wait before retrying (Exponential Backoff)
708
- // let retryDelay: number;
709
- // retryDelay = Number(retry_delay[attempt]);
710
- // let count:any = retry_delay.length - 1 === attempt?retry_delay[retry_delay.length - 1]:retry_delay[attempt];
711
- // retryDelay = Number(count);
712
- // await this.sendMessageToRetryQueue(attempt, data, retryDelay, queueName);
713
- // Logger.info(`Retrying in ${retryDelay / 1000}, seconds...`, 'publishMessageRetryQueue');
714
- // await new Promise((resolve) => setTimeout(resolve, retryDelay));
715
- // } catch (error) {
716
- // Logger.error(`Failed to publish message. Attempt ${attempt} of ${maxRetries}. Error:`, 'publishMessageRetryQueue', request);
717
- // throw error;
718
- // }
719
- // }
720
-
721
- /**
722
- * Function to connect create to RabbitMQ
723
- */
724
- public async createConnection(): Promise<ConnectionWrapper> {
725
- try {
726
- Logger.info('Reached to createConnection', 'createConnection');
727
- configManager.loadConfig(RABBITMQ?.CONFIG_PATH);
728
- const secret = await getIAMSecrets();
729
- let url: string = `amqp://${secret?.[SECRET_KEYS.RABBITMQ_USER]}:${secret?.[SECRET_KEYS.RABBITMQ_PASS]}@${RABBITMQ?.HOST}`;
730
- // Create a connection to RabbitMQ server
731
- connection = await amqp.connect(url, { heartbeat: RABBITMQ?.HEARTBEAT });
732
- isConnectionOpen = true;
733
- // check the connection status
734
- this.bindConnectionEventHandler();
735
- // create channel
736
- channel = await connection.createConfirmChannel();
737
- channel.prefetch(RABBITMQ?.PREFETCH_COUNT);
738
- // Watch the channel itself: a channel-level fault (e.g. PRECONDITION_FAILED
739
- // from a stale ack) closes the channel and cancels its consumers without
740
- // closing the connection, so the connection 'close' path never fires.
741
- this.bindChannelEventHandler();
742
- Logger.info('RabbitMQ connected successfully! ', 'createConnection');
743
- // create exchange and queue and bind with exchange
744
- await this.setupQueuesAndExchanges();
745
- // Re-attach any consumers registered before this (re)connect. Without
746
- // this, after a reconnect the connection is healthy but no consumer is
747
- // bound to the queues — the root cause of consumers "disappearing".
748
- await this.reregisterConsumers();
749
- const connectionWrapper: ConnectionWrapper = { connection, channel }
750
- return connectionWrapper;
751
- } catch (error) {
752
- // Log and rethrow any errors that occur during connection
753
- Logger.error('Failed to connect to RabbitMQ', 'connection', (error as Error)?.message);
754
- return { connection: null, channel: null } as unknown as ConnectionWrapper;
755
- }
756
- }
757
-
758
- /**
759
- * Function for bind connection event handler on close and error setupQueuesAndExchanges
760
- * @returns
761
- */
762
- private bindConnectionEventHandler(): boolean {
763
- try {
764
- // 'error' always precedes 'close' from amqplib, so only 'close' drives the
765
- // reconnect to avoid triggering it twice.
766
- connection.on('error', (err: any) => {
767
- Logger.error(`RabbitMQ: bindConnectionEventHandler:error`, `Connection error`, err?.message ?? err);
768
- });
769
- connection.on('close', async(err: any) => {
770
- Logger.error(`RabbitMQ: bindConnectionEventHandler:close`, `Connection closed`, err?.message ?? err);
771
- isConnectionOpen = false;
772
- await this.handleReconnect();
773
- });
774
- return isConnectionOpen;
775
- } catch (error) {
776
- isConnectionOpen = false;
777
- return isConnectionOpen;
778
- }
779
-
780
- }
781
-
782
- /**
783
- * Function to bind channel-level event handlers. A channel can be cancelled
784
- * (e.g. PRECONDITION_FAILED, consumer_timeout) while the connection stays up;
785
- * in that case the connection 'close' handler never fires, so the channel is
786
- * recreated and consumers re-attached here.
787
- */
788
- private bindChannelEventHandler(): void {
789
- try {
790
- channel.on('error', (err: any) => {
791
- Logger.error(`RabbitMQ: bindChannelEventHandler:error`, `Channel error`, err?.message ?? err);
792
- });
793
- channel.on('close', async () => {
794
- Logger.error(`RabbitMQ: bindChannelEventHandler:close`, `Channel closed`);
795
- // Only self-heal the channel while the connection is still open. If the
796
- // connection is down, handleReconnect() owns recovery (and will recreate
797
- // the channel + consumers), so doing it here too would double-subscribe.
798
- if (isConnectionOpen && !isReconnecting) {
799
- try {
800
- await this.recreateChannel();
801
- this.bindChannelEventHandler();
802
- await this.reregisterConsumers();
803
- Logger.info('Channel and consumers recovered after channel close', 'bindChannelEventHandler');
804
- } catch (error) {
805
- Logger.error('Failed to recover channel after close', 'bindChannelEventHandler', (error as Error).message);
806
- }
807
- }
808
- });
809
- } catch (error) {
810
- Logger.error('Error in bindChannelEventHandler', 'bindChannelEventHandler', (error as Error).message);
811
- }
812
- }
813
-
814
- /**
815
- * Function to reconnect to RabbitMQ with capped exponential backoff. Unlike
816
- * the old logic — which capped total lifetime reconnects at MAX_RETRIES and
817
- * never reset the counter — this retries indefinitely and resets on success,
818
- * so the service self-heals from every disconnect. createConnection()
819
- * re-attaches all registered consumers on success.
820
- */
821
- private async handleReconnect(): Promise<void> {
822
- if (isReconnecting) {
823
- Logger.info('Reconnect already in progress, skipping duplicate trigger', 'handleReconnect');
824
- return;
825
- }
826
- isReconnecting = true;
827
- connectionRetry = 1;
828
- // Backoff schedule derived from the TTL config, capped at its last value.
829
- const backoffs: number[] = (retry_delay ?? []).map(Number).filter((n: number) => !Number.isNaN(n) && n > 0);
830
- const maxBackoff = backoffs.length ? backoffs[backoffs.length - 1] : 30000;
831
- try {
832
- while (!isConnectionOpen) {
833
- const delay = backoffs[connectionRetry - 1] ?? maxBackoff;
834
- Logger.info(`Reconnect attempt ${connectionRetry} in ${delay / 1000}s...`, 'handleReconnect');
835
- await new Promise(resolve => setTimeout(resolve, delay));
836
- try {
837
- await this.createConnection();
838
- if (isConnectionOpen) {
839
- connectionRetry = 1;
840
- Logger.info('Reconnected to RabbitMQ successfully', 'handleReconnect');
841
- return;
842
- }
843
- throw new Error('createConnection did not establish an open connection');
844
- } catch (error) {
845
- Logger.error(`Reconnect attempt ${connectionRetry} failed`, 'handleReconnect', (error as Error).message);
846
- connectionRetry++;
847
- }
848
- }
849
- } finally {
850
- isReconnecting = false;
851
- }
852
- }
853
-
854
- }
855
-