@onlineapps/service-wrapper 2.0.48 → 2.0.50

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/service-wrapper",
3
- "version": "2.0.48",
3
+ "version": "2.0.50",
4
4
  "description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -485,6 +485,11 @@ class ServiceWrapper {
485
485
  console.warn('Monitoring initialization failed (non-critical):', error.message);
486
486
  }
487
487
  }
488
+
489
+ // Register MQ channel close hooks and health monitoring AFTER monitoring is initialized
490
+ if (this.mqClient) {
491
+ await this._registerMQHooks();
492
+ }
488
493
 
489
494
  // Initialize cache if configured
490
495
  if (this.config.wrapper?.cache?.enabled === true) {
@@ -557,6 +562,68 @@ class ServiceWrapper {
557
562
  }
558
563
  }
559
564
 
565
+ /**
566
+ * Register MQ channel close hooks and health monitoring
567
+ * Must be called AFTER monitoring is initialized
568
+ * @private
569
+ */
570
+ async _registerMQHooks() {
571
+ if (!this.mqClient || !this.mqClient._transport) {
572
+ return;
573
+ }
574
+
575
+ // Access the underlying transport (RabbitMQClient) to register hooks
576
+ const transport = this.mqClient._transport;
577
+
578
+ if (typeof transport.onChannelClose === 'function') {
579
+ // Register hook for all channel types
580
+ transport.onChannelClose('all', (details) => {
581
+ const logData = {
582
+ type: details.type,
583
+ reason: details.reason,
584
+ error: details.error ? {
585
+ message: details.error.message,
586
+ code: details.error.code,
587
+ stack: details.error.stack
588
+ } : null,
589
+ timestamp: details.timestamp,
590
+ connectionState: details.connectionState,
591
+ channelCreatedAt: details.channelCreatedAt,
592
+ lastOperation: details.lastOperation
593
+ };
594
+
595
+ if (this.monitoring && this.monitoring.error) {
596
+ this.monitoring.error(`MQ Channel Closed: ${details.type}`, logData);
597
+ }
598
+ console.error(`[ServiceWrapper] [MQ] Channel closed: ${details.type} - ${details.reason}`);
599
+ });
600
+
601
+ this.logger?.info('MQ channel close hooks registered');
602
+ }
603
+
604
+ // Register health shutdown handler
605
+ if (typeof transport.on === 'function') {
606
+ transport.on('health:shutdown', (health) => {
607
+ console.error('[ServiceWrapper] [MQ] Health shutdown triggered - shutting down service');
608
+ if (this.monitoring && this.monitoring.error) {
609
+ this.monitoring.error('MQ Health Shutdown', {
610
+ issues: health.issues,
611
+ timestamp: health.timestamp
612
+ });
613
+ }
614
+ // Graceful shutdown
615
+ this.shutdown().then(() => {
616
+ process.exit(1);
617
+ }).catch((err) => {
618
+ console.error('[ServiceWrapper] Shutdown error:', err);
619
+ process.exit(1);
620
+ });
621
+ });
622
+
623
+ this.logger?.info('MQ health shutdown handler registered');
624
+ }
625
+ }
626
+
560
627
  /**
561
628
  * Initialize MQ connection
562
629
  * @private
@@ -569,6 +636,39 @@ class ServiceWrapper {
569
636
 
570
637
  const serviceName = this.config.service?.name || 'unnamed-service';
571
638
 
639
+ // Health monitoring and channel close hooks configuration
640
+ const healthCheckConfig = {
641
+ healthCheckInterval: this.config.wrapper?.mq?.healthCheckInterval || 30000, // 30s
642
+ healthCheckEnabled: this.config.wrapper?.mq?.healthCheckEnabled !== false, // Default: true
643
+ criticalHealthShutdown: this.config.wrapper?.mq?.criticalHealthShutdown !== false, // Default: true
644
+ criticalHealthShutdownDelay: this.config.wrapper?.mq?.criticalHealthShutdownDelay || 60000, // 60s
645
+ // Health reporting callbacks
646
+ healthReportCallback: async (health) => {
647
+ if (this.monitoring && this.monitoring.info) {
648
+ this.monitoring.info('MQ Health Check', {
649
+ healthy: health.healthy,
650
+ issues: health.issues,
651
+ consumers: health.consumers,
652
+ queues: health.queues,
653
+ channels: health.channels
654
+ });
655
+ }
656
+ },
657
+ healthCriticalCallback: async (health) => {
658
+ if (this.monitoring && this.monitoring.error) {
659
+ this.monitoring.error('MQ CRITICAL HEALTH', {
660
+ issues: health.issues,
661
+ consumers: health.consumers,
662
+ queues: health.queues,
663
+ channels: health.channels,
664
+ timestamp: health.timestamp
665
+ });
666
+ }
667
+ // Also log to console for visibility
668
+ console.error('[ServiceWrapper] [MQ] CRITICAL HEALTH:', health.issues);
669
+ }
670
+ };
671
+
572
672
  this.mqClient = new MQConnector({
573
673
  type: 'rabbitmq',
574
674
  host: mqUrl,
@@ -578,12 +678,19 @@ class ServiceWrapper {
578
678
  prefetch: this.config.wrapper?.mq?.prefetch || 10,
579
679
  durable: true,
580
680
  noAck: false,
581
- logger: this.logger || console
681
+ logger: this.logger || console,
682
+ ...healthCheckConfig
582
683
  });
583
684
 
685
+ // Register channel close hooks AFTER mqClient is created but BEFORE connect
686
+ // Access the underlying transport (RabbitMQClient) to register hooks
687
+ // Note: transport is created during connect(), so we need to register hooks after connect
688
+ // We'll do this in a separate method called after connect
689
+
584
690
  try {
585
691
  await this.mqClient.connect();
586
692
  this.logger?.info('MQ connector initialized');
693
+
587
694
  } catch (error) {
588
695
  const message = `[MQConnector] Unable to connect to RabbitMQ at ${mqUrl}. ` +
589
696
  'Verify that api_services_queuer is running and accessible. ' +