@onlineapps/mq-client-core 1.0.34 → 1.0.36

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/mq-client-core",
3
- "version": "1.0.34",
3
+ "version": "1.0.36",
4
4
  "description": "Core MQ client library for RabbitMQ - shared by infrastructure services and connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -42,6 +42,21 @@ class RabbitMQClient extends EventEmitter {
42
42
  // Auto-reconnect flags
43
43
  this._reconnecting = false;
44
44
  this._reconnectAttempts = 0;
45
+
46
+ // Channel close hooks - called when channel closes with detailed information
47
+ this._channelCloseHooks = []; // Array of { type: 'publisher'|'queue'|'consumer', callback: (details) => void }
48
+
49
+ // Health monitoring
50
+ this._healthCheckInterval = null;
51
+ this._healthCheckIntervalMs = this._config.healthCheckInterval || 30000; // Default 30s
52
+ this._healthCheckEnabled = this._config.healthCheckEnabled !== false;
53
+
54
+ // Health reporting callbacks
55
+ this._healthReportCallback = this._config.healthReportCallback || null; // (health) => Promise<void>
56
+ this._healthCriticalCallback = this._config.healthCriticalCallback || null; // (health) => Promise<void>
57
+ this._criticalHealthShutdown = this._config.criticalHealthShutdown !== false; // Default: true - shutdown on critical health
58
+ this._criticalHealthShutdownDelay = this._config.criticalHealthShutdownDelay || 60000; // Default: 60s delay before shutdown
59
+ this._criticalHealthStartTime = null; // Track when critical health started
45
60
  }
46
61
 
47
62
  /**
@@ -66,6 +81,50 @@ class RabbitMQClient extends EventEmitter {
66
81
  get queueChannel() {
67
82
  return this._queueChannel || this._channel; // Fallback to main channel if queueChannel not available
68
83
  }
84
+
85
+ /**
86
+ * Register a hook to be called when a channel closes
87
+ * @param {string} type - Channel type: 'publisher', 'queue', 'consumer', or 'all'
88
+ * @param {Function} callback - Callback function receiving close details: { type, channel, error, reason, timestamp, stack }
89
+ */
90
+ onChannelClose(type, callback) {
91
+ this._channelCloseHooks.push({ type, callback });
92
+ }
93
+
94
+ /**
95
+ * Call all registered channel close hooks
96
+ * @private
97
+ * @param {string} channelType - 'publisher', 'queue', or 'consumer'
98
+ * @param {Object} channel - The closed channel
99
+ * @param {Error} error - Error that caused closure (if any)
100
+ * @param {string} reason - Human-readable reason
101
+ */
102
+ _callChannelCloseHooks(channelType, channel, error, reason) {
103
+ const details = {
104
+ type: channelType,
105
+ channel: channel,
106
+ error: error,
107
+ reason: reason,
108
+ timestamp: new Date().toISOString(),
109
+ stack: error?.stack || new Error().stack,
110
+ connectionState: this._connection ? (this._connection.closed ? 'closed' : 'open') : 'null',
111
+ channelCreatedAt: channel?._createdAt || 'unknown',
112
+ lastOperation: channel?._lastOperation || 'unknown'
113
+ };
114
+
115
+ for (const hook of this._channelCloseHooks) {
116
+ if (hook.type === 'all' || hook.type === channelType) {
117
+ try {
118
+ hook.callback(details);
119
+ } catch (hookErr) {
120
+ console.error(`[RabbitMQClient] [mq-client-core] Channel close hook error:`, hookErr.message);
121
+ }
122
+ }
123
+ }
124
+
125
+ // Emit event for external listeners
126
+ this.emit('channel:close', details);
127
+ }
69
128
 
70
129
  /**
71
130
  * Connects to RabbitMQ server and creates a confirm channel.
@@ -101,17 +160,37 @@ class RabbitMQClient extends EventEmitter {
101
160
 
102
161
  // Use ConfirmChannel to enable publisher confirms for publish operations
103
162
  this._channel = await this._connection.createConfirmChannel();
163
+
164
+ // Track channel metadata for debugging
165
+ this._channel._createdAt = new Date().toISOString();
166
+ this._channel._closeReason = null;
167
+ this._channel._lastOperation = 'Initial creation';
168
+ this._channel._type = 'publisher';
169
+
104
170
  // Enable publisher confirms explicitly
105
171
  this._channel.on('error', async (err) => {
106
- console.error('[RabbitMQClient] [mq-client-core] Publisher channel error:', err.message);
172
+ const reason = `Publisher channel error: ${err.message} (code: ${err.code || 'unknown'})`;
173
+ this._channel._closeReason = reason;
174
+ console.error(`[RabbitMQClient] [mq-client-core] ${reason}`);
175
+
176
+ // Call close hooks with detailed information
177
+ this._callChannelCloseHooks('publisher', this._channel, err, reason);
178
+
107
179
  // Auto-recreate channel if connection is still alive
108
180
  await this._ensurePublisherChannel();
109
181
  this.emit('error', err);
110
182
  });
111
183
  this._channel.on('close', async () => {
112
- console.warn('[RabbitMQClient] [mq-client-core] Publisher channel closed - will auto-recreate on next publish');
184
+ const reason = this._channel._closeReason || 'Publisher channel closed unexpectedly';
185
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason} - will auto-recreate on next publish`);
186
+
187
+ // Call close hooks with detailed information
188
+ this._callChannelCloseHooks('publisher', this._channel, null, reason);
189
+
113
190
  // Mark channel as null - will be recreated on next publish
191
+ const closedChannel = this._channel;
114
192
  this._channel = null;
193
+
115
194
  // Try to recreate immediately if connection is alive
116
195
  await this._ensurePublisherChannel();
117
196
  });
@@ -123,16 +202,35 @@ class RabbitMQClient extends EventEmitter {
123
202
  // Create a separate regular channel for queue operations (assertQueue, checkQueue)
124
203
  // This avoids RPC reply queue issues with ConfirmChannel.assertQueue()
125
204
  this._queueChannel = await this._connection.createChannel();
205
+
206
+ // Track channel metadata for debugging
207
+ this._queueChannel._createdAt = new Date().toISOString();
208
+ this._queueChannel._closeReason = null;
209
+ this._queueChannel._lastOperation = 'Initial creation';
210
+ this._queueChannel._type = 'queue';
211
+
126
212
  this._queueChannel.on('error', async (err) => {
127
- // Log but don't emit - queue channel errors are less critical
128
- console.warn('[RabbitMQClient] Queue channel error:', err.message);
213
+ const reason = `Queue channel error: ${err.message} (code: ${err.code || 'unknown'})`;
214
+ this._queueChannel._closeReason = reason;
215
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason}`);
216
+
217
+ // Call close hooks with detailed information
218
+ this._callChannelCloseHooks('queue', this._queueChannel, err, reason);
219
+
129
220
  // Auto-recreate channel if connection is still alive
130
221
  await this._ensureQueueChannel();
131
222
  });
132
223
  this._queueChannel.on('close', async () => {
133
- console.warn('[RabbitMQClient] Queue channel closed - will auto-recreate on next operation');
224
+ const reason = this._queueChannel._closeReason || 'Queue channel closed unexpectedly';
225
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason} - will auto-recreate on next operation`);
226
+
227
+ // Call close hooks with detailed information
228
+ this._callChannelCloseHooks('queue', this._queueChannel, null, reason);
229
+
134
230
  // Mark channel as null - will be recreated on next queue operation
231
+ const closedChannel = this._queueChannel;
135
232
  this._queueChannel = null;
233
+
136
234
  // Try to recreate immediately if connection is alive
137
235
  await this._ensureQueueChannel();
138
236
  });
@@ -140,19 +238,44 @@ class RabbitMQClient extends EventEmitter {
140
238
  // Create a dedicated channel for consume operations
141
239
  // This prevents channel conflicts between publish (ConfirmChannel) and consume operations
142
240
  this._consumerChannel = await this._connection.createChannel();
241
+
242
+ // Track channel metadata for debugging
243
+ this._consumerChannel._createdAt = new Date().toISOString();
244
+ this._consumerChannel._closeReason = null;
245
+ this._consumerChannel._lastOperation = 'Initial creation';
246
+ this._consumerChannel._type = 'consumer';
247
+
143
248
  this._consumerChannel.on('error', async (err) => {
144
- console.warn('[RabbitMQClient] Consumer channel error:', err.message);
249
+ const reason = `Consumer channel error: ${err.message} (code: ${err.code || 'unknown'})`;
250
+ this._consumerChannel._closeReason = reason;
251
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason}`);
252
+
253
+ // Call close hooks with detailed information
254
+ this._callChannelCloseHooks('consumer', this._consumerChannel, err, reason);
255
+
145
256
  // Auto-recreate channel and re-register consumers
146
257
  await this._ensureConsumerChannel();
147
258
  this.emit('error', err);
148
259
  });
149
260
  this._consumerChannel.on('close', async () => {
150
- console.warn('[RabbitMQClient] Consumer channel closed - will auto-recreate and re-register consumers');
261
+ const reason = this._consumerChannel._closeReason || 'Consumer channel closed unexpectedly';
262
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason} - will auto-recreate and re-register consumers`);
263
+
264
+ // Call close hooks with detailed information
265
+ this._callChannelCloseHooks('consumer', this._consumerChannel, null, reason);
266
+
151
267
  // Mark channel as null - will be recreated and consumers re-registered
268
+ const closedChannel = this._consumerChannel;
152
269
  this._consumerChannel = null;
270
+
153
271
  // Try to recreate and re-register consumers immediately if connection is alive
154
272
  await this._ensureConsumerChannel();
155
273
  });
274
+
275
+ // Start health monitoring if enabled
276
+ if (this._healthCheckEnabled) {
277
+ this._startHealthMonitoring();
278
+ }
156
279
  } catch (err) {
157
280
  // Cleanup partially created resources
158
281
  if (this._connection) {
@@ -194,13 +317,32 @@ class RabbitMQClient extends EventEmitter {
194
317
 
195
318
  // Create new ConfirmChannel
196
319
  this._channel = await this._connection.createConfirmChannel();
320
+
321
+ // Track channel metadata for debugging
322
+ this._channel._createdAt = new Date().toISOString();
323
+ this._channel._closeReason = null;
324
+ this._channel._lastOperation = 'Recreated via _ensurePublisherChannel()';
325
+ this._channel._type = 'publisher';
326
+
197
327
  this._channel.on('error', async (err) => {
198
- console.error('[RabbitMQClient] [mq-client-core] Publisher channel error:', err.message);
328
+ const reason = `Publisher channel error: ${err.message} (code: ${err.code || 'unknown'})`;
329
+ this._channel._closeReason = reason;
330
+ console.error(`[RabbitMQClient] [mq-client-core] ${reason}`);
331
+
332
+ // Call close hooks
333
+ this._callChannelCloseHooks('publisher', this._channel, err, reason);
334
+
199
335
  await this._ensurePublisherChannel();
200
336
  this.emit('error', err);
201
337
  });
202
338
  this._channel.on('close', async () => {
203
- console.warn('[RabbitMQClient] [mq-client-core] Publisher channel closed - will auto-recreate on next publish');
339
+ const reason = this._channel._closeReason || 'Publisher channel closed unexpectedly';
340
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason} - will auto-recreate on next publish`);
341
+
342
+ // Call close hooks
343
+ this._callChannelCloseHooks('publisher', this._channel, null, reason);
344
+
345
+ const closedChannel = this._channel;
204
346
  this._channel = null;
205
347
  await this._ensurePublisherChannel();
206
348
  });
@@ -242,12 +384,31 @@ class RabbitMQClient extends EventEmitter {
242
384
 
243
385
  // Create new regular channel
244
386
  this._queueChannel = await this._connection.createChannel();
387
+
388
+ // Track channel metadata for debugging
389
+ this._queueChannel._createdAt = new Date().toISOString();
390
+ this._queueChannel._closeReason = null;
391
+ this._queueChannel._lastOperation = 'Recreated via _ensureQueueChannel()';
392
+ this._queueChannel._type = 'queue';
393
+
245
394
  this._queueChannel.on('error', async (err) => {
246
- console.warn('[RabbitMQClient] Queue channel error:', err.message);
395
+ const reason = `Queue channel error: ${err.message} (code: ${err.code || 'unknown'})`;
396
+ this._queueChannel._closeReason = reason;
397
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason}`);
398
+
399
+ // Call close hooks
400
+ this._callChannelCloseHooks('queue', this._queueChannel, err, reason);
401
+
247
402
  await this._ensureQueueChannel();
248
403
  });
249
404
  this._queueChannel.on('close', async () => {
250
- console.warn('[RabbitMQClient] Queue channel closed - will auto-recreate on next operation');
405
+ const reason = this._queueChannel._closeReason || 'Queue channel closed unexpectedly';
406
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason} - will auto-recreate on next operation`);
407
+
408
+ // Call close hooks
409
+ this._callChannelCloseHooks('queue', this._queueChannel, null, reason);
410
+
411
+ const closedChannel = this._queueChannel;
251
412
  this._queueChannel = null;
252
413
  await this._ensureQueueChannel();
253
414
  });
@@ -286,13 +447,32 @@ class RabbitMQClient extends EventEmitter {
286
447
 
287
448
  // Create new regular channel
288
449
  this._consumerChannel = await this._connection.createChannel();
450
+
451
+ // Track channel metadata for debugging
452
+ this._consumerChannel._createdAt = new Date().toISOString();
453
+ this._consumerChannel._closeReason = null;
454
+ this._consumerChannel._lastOperation = 'Recreated via _ensureConsumerChannel()';
455
+ this._consumerChannel._type = 'consumer';
456
+
289
457
  this._consumerChannel.on('error', async (err) => {
290
- console.warn('[RabbitMQClient] Consumer channel error:', err.message);
458
+ const reason = `Consumer channel error: ${err.message} (code: ${err.code || 'unknown'})`;
459
+ this._consumerChannel._closeReason = reason;
460
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason}`);
461
+
462
+ // Call close hooks
463
+ this._callChannelCloseHooks('consumer', this._consumerChannel, err, reason);
464
+
291
465
  await this._ensureConsumerChannel();
292
466
  this.emit('error', err);
293
467
  });
294
468
  this._consumerChannel.on('close', async () => {
295
- console.warn('[RabbitMQClient] Consumer channel closed - will auto-recreate and re-register consumers');
469
+ const reason = this._consumerChannel._closeReason || 'Consumer channel closed unexpectedly';
470
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason} - will auto-recreate and re-register consumers`);
471
+
472
+ // Call close hooks
473
+ this._callChannelCloseHooks('consumer', this._consumerChannel, null, reason);
474
+
475
+ const closedChannel = this._consumerChannel;
296
476
  this._consumerChannel = null;
297
477
  await this._ensureConsumerChannel();
298
478
  });
@@ -306,6 +486,7 @@ class RabbitMQClient extends EventEmitter {
306
486
  if (consumerInfo.options.queueOptions) {
307
487
  try {
308
488
  await this._ensureQueueChannel();
489
+ this._trackChannelOperation(this._queueChannel, `assertQueue ${queue} (re-register)`);
309
490
  await this._queueChannel.assertQueue(queue, consumerInfo.options.queueOptions);
310
491
  } catch (assertErr) {
311
492
  if (assertErr.code === 404) {
@@ -349,7 +530,213 @@ class RabbitMQClient extends EventEmitter {
349
530
  * Disconnects: closes channel and connection.
350
531
  * @returns {Promise<void>}
351
532
  */
533
+ /**
534
+ * Start health monitoring - periodically checks channels, queues, and consumers
535
+ * @private
536
+ */
537
+ _startHealthMonitoring() {
538
+ if (this._healthCheckInterval) {
539
+ return; // Already started
540
+ }
541
+
542
+ this._healthCheckInterval = setInterval(async () => {
543
+ try {
544
+ await this._performHealthCheck();
545
+ } catch (err) {
546
+ console.error(`[RabbitMQClient] [mq-client-core] Health check error:`, err.message);
547
+ // Emit event for external monitoring
548
+ this.emit('health:check:error', err);
549
+ }
550
+ }, this._healthCheckIntervalMs);
551
+
552
+ console.log(`[RabbitMQClient] [mq-client-core] Health monitoring started (interval: ${this._healthCheckIntervalMs}ms)`);
553
+ }
554
+
555
+ /**
556
+ * Stop health monitoring
557
+ * @private
558
+ */
559
+ _stopHealthMonitoring() {
560
+ if (this._healthCheckInterval) {
561
+ clearInterval(this._healthCheckInterval);
562
+ this._healthCheckInterval = null;
563
+ console.log('[RabbitMQClient] [mq-client-core] Health monitoring stopped');
564
+ }
565
+ }
566
+
567
+ /**
568
+ * Perform health check - verify channels, queues, and consumers
569
+ * Can be called manually (e.g., before workflow steps in debug mode)
570
+ * @public (exposed for manual calls)
571
+ * @returns {Promise<Object>} Health check results
572
+ */
573
+ async performHealthCheck() {
574
+ return this._performHealthCheck();
575
+ }
576
+
577
+ /**
578
+ * Perform health check - verify channels, queues, and consumers
579
+ * @private
580
+ * @returns {Promise<Object>} Health check results
581
+ */
582
+ async _performHealthCheck() {
583
+ const health = {
584
+ timestamp: new Date().toISOString(),
585
+ connection: {
586
+ exists: !!this._connection,
587
+ closed: this._connection ? this._connection.closed : true
588
+ },
589
+ channels: {
590
+ publisher: {
591
+ exists: !!this._channel,
592
+ closed: this._channel ? this._channel.closed : true
593
+ },
594
+ queue: {
595
+ exists: !!this._queueChannel,
596
+ closed: this._queueChannel ? this._queueChannel.closed : true
597
+ },
598
+ consumer: {
599
+ exists: !!this._consumerChannel,
600
+ closed: this._consumerChannel ? this._consumerChannel.closed : true
601
+ }
602
+ },
603
+ consumers: {
604
+ tracked: this._activeConsumers.size,
605
+ active: 0,
606
+ inactive: []
607
+ },
608
+ queues: {
609
+ checked: 0,
610
+ missing: [],
611
+ noConsumer: []
612
+ },
613
+ healthy: true,
614
+ issues: []
615
+ };
616
+
617
+ // Check connection
618
+ if (!health.connection.exists || health.connection.closed) {
619
+ health.healthy = false;
620
+ health.issues.push('Connection is closed or missing');
621
+ }
622
+
623
+ // Check channels
624
+ if (health.channels.publisher.closed || !health.channels.publisher.exists) {
625
+ health.healthy = false;
626
+ health.issues.push('Publisher channel is closed or missing');
627
+ }
628
+ if (health.channels.queue.closed || !health.channels.queue.exists) {
629
+ health.healthy = false;
630
+ health.issues.push('Queue channel is closed or missing');
631
+ }
632
+ if (health.channels.consumer.closed || !health.channels.consumer.exists) {
633
+ health.healthy = false;
634
+ health.issues.push('Consumer channel is closed or missing');
635
+ }
636
+
637
+ // Check tracked consumers - verify queues exist and have active consumers
638
+ if (this._connection && !this._connection.closed && this._queueChannel && !this._queueChannel.closed) {
639
+ for (const [queue, consumerInfo] of this._activeConsumers.entries()) {
640
+ health.queues.checked++;
641
+ try {
642
+ // Check if queue exists
643
+ this._trackChannelOperation(this._queueChannel, `checkQueue ${queue}`);
644
+ const queueInfo = await this._queueChannel.checkQueue(queue);
645
+ if (queueInfo) {
646
+ // Queue exists - check if consumer is active
647
+ // Note: RabbitMQ doesn't provide direct API to check if consumer is active
648
+ // We can only verify that consumerTag is set
649
+ if (consumerInfo.consumerTag) {
650
+ health.consumers.active++;
651
+ } else {
652
+ health.consumers.inactive.push(queue);
653
+ health.queues.noConsumer.push(queue);
654
+ health.healthy = false;
655
+ health.issues.push(`Queue ${queue} has no active consumer (consumerTag missing)`);
656
+ }
657
+ } else {
658
+ health.queues.missing.push(queue);
659
+ health.healthy = false;
660
+ health.issues.push(`Queue ${queue} does not exist`);
661
+ }
662
+ } catch (err) {
663
+ if (err.code === 404) {
664
+ health.queues.missing.push(queue);
665
+ health.healthy = false;
666
+ health.issues.push(`Queue ${queue} does not exist (404)`);
667
+ } else {
668
+ health.healthy = false;
669
+ health.issues.push(`Error checking queue ${queue}: ${err.message}`);
670
+ }
671
+ }
672
+ }
673
+ }
674
+
675
+ // Emit health check result
676
+ this.emit('health:check', health);
677
+
678
+ // If unhealthy, log and potentially trigger shutdown
679
+ if (!health.healthy) {
680
+ console.error(`[RabbitMQClient] [mq-client-core] Health check FAILED:`, health.issues);
681
+ console.error(`[RabbitMQClient] [mq-client-core] Health details:`, JSON.stringify(health, null, 2));
682
+
683
+ // Track critical health start time
684
+ if (!this._criticalHealthStartTime) {
685
+ this._criticalHealthStartTime = Date.now();
686
+ console.error(`[RabbitMQClient] [mq-client-core] Critical health detected at ${new Date(this._criticalHealthStartTime).toISOString()}`);
687
+ }
688
+
689
+ // Report to monitoring if callback provided
690
+ if (this._healthReportCallback) {
691
+ try {
692
+ await this._healthReportCallback(health);
693
+ } catch (reportErr) {
694
+ console.error(`[RabbitMQClient] [mq-client-core] Health report callback failed:`, reportErr.message);
695
+ }
696
+ }
697
+
698
+ // Call critical health callback if provided
699
+ if (this._healthCriticalCallback) {
700
+ try {
701
+ await this._healthCriticalCallback(health);
702
+ } catch (criticalErr) {
703
+ console.error(`[RabbitMQClient] [mq-client-core] Critical health callback failed:`, criticalErr.message);
704
+ }
705
+ }
706
+
707
+ // Emit critical health event
708
+ this.emit('health:critical', health);
709
+
710
+ // Check if we should shutdown after delay
711
+ if (this._criticalHealthShutdown) {
712
+ const timeSinceCritical = Date.now() - this._criticalHealthStartTime;
713
+ if (timeSinceCritical >= this._criticalHealthShutdownDelay) {
714
+ console.error(`[RabbitMQClient] [mq-client-core] Critical health persisted for ${timeSinceCritical}ms (threshold: ${this._criticalHealthShutdownDelay}ms) - triggering shutdown`);
715
+ this.emit('health:shutdown', health);
716
+ // Note: Actual shutdown should be handled by the application using this event
717
+ } else {
718
+ const remaining = this._criticalHealthShutdownDelay - timeSinceCritical;
719
+ console.warn(`[RabbitMQClient] [mq-client-core] Critical health will trigger shutdown in ${remaining}ms if not resolved`);
720
+ }
721
+ }
722
+ } else {
723
+ // Health is OK - reset critical health tracking
724
+ if (this._criticalHealthStartTime) {
725
+ const duration = Date.now() - this._criticalHealthStartTime;
726
+ console.log(`[RabbitMQClient] [mq-client-core] Health recovered after ${duration}ms of critical state`);
727
+ this._criticalHealthStartTime = null;
728
+ }
729
+
730
+ console.log(`[RabbitMQClient] [mq-client-core] Health check OK: ${health.consumers.active}/${health.consumers.tracked} consumers active, ${health.queues.checked} queues checked`);
731
+ }
732
+
733
+ return health;
734
+ }
735
+
352
736
  async disconnect() {
737
+ // Stop health monitoring
738
+ this._stopHealthMonitoring();
739
+
353
740
  // Clear active consumers tracking
354
741
  this._activeConsumers.clear();
355
742
 
@@ -400,6 +787,9 @@ class RabbitMQClient extends EventEmitter {
400
787
  // Ensure publisher channel exists and is open (auto-recreates if closed)
401
788
  await this._ensurePublisherChannel();
402
789
 
790
+ // Track operation for debugging
791
+ this._trackChannelOperation(this._channel, `publish to ${queue}`);
792
+
403
793
  // Log publish attempt for debugging
404
794
  console.log(`[RabbitMQClient] [mq-client-core] [PUBLISH] Attempting to publish to queue "${queue}"`);
405
795
 
@@ -418,6 +808,7 @@ class RabbitMQClient extends EventEmitter {
418
808
  try {
419
809
  // Ensure queue channel exists and is open (auto-recreates if closed)
420
810
  await this._ensureQueueChannel();
811
+ this._trackChannelOperation(this._queueChannel, `checkQueue ${queue}`);
421
812
  await this._queueChannel.checkQueue(queue);
422
813
  // Queue exists - proceed to publish
423
814
  } catch (checkErr) {
@@ -445,6 +836,7 @@ class RabbitMQClient extends EventEmitter {
445
836
  // For non-infrastructure queues, allow auto-creation with default options
446
837
  const queueOptions = options.queueOptions || { durable: this._config.durable };
447
838
  console.warn(`[RabbitMQClient] [mq-client-core] [PUBLISH] Auto-creating non-infrastructure queue ${queue} with default options (no TTL). This should be avoided for production.`);
839
+ this._trackChannelOperation(this._queueChannel, `assertQueue ${queue}`);
448
840
  await this._queueChannel.assertQueue(queue, queueOptions);
449
841
  } else {
450
842
  // Other error - rethrow
@@ -519,6 +911,9 @@ class RabbitMQClient extends EventEmitter {
519
911
  async consume(queue, onMessage, options = {}) {
520
912
  // Ensure consumer channel exists and is open (auto-recreates if closed and re-registers consumers)
521
913
  await this._ensureConsumerChannel();
914
+
915
+ // Track operation for debugging
916
+ this._trackChannelOperation(this._consumerChannel, `consume from ${queue}`);
522
917
 
523
918
  const durable = options.durable !== undefined ? options.durable : this._config.durable;
524
919
  const prefetch = options.prefetch !== undefined ? options.prefetch : this._config.prefetch;
@@ -597,6 +992,7 @@ class RabbitMQClient extends EventEmitter {
597
992
 
598
993
  try {
599
994
  console.log(`[RabbitMQClient] [mq-client-core] [CONSUMER] About to call assertQueue(${queue}, ${JSON.stringify(queueOptions)})`);
995
+ this._trackChannelOperation(this._queueChannel, `assertQueue ${queue}`);
600
996
  await this._queueChannel.assertQueue(queue, queueOptions);
601
997
  console.log(`[RabbitMQClient] [mq-client-core] [CONSUMER] ✓ Queue ${queue} asserted successfully`);
602
998
  } catch (assertErr) {