@onlineapps/service-wrapper 2.0.49 → 2.0.51

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/ServiceWrapper.js +126 -47
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/service-wrapper",
3
- "version": "2.0.49",
3
+ "version": "2.0.51",
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,119 @@ class ServiceWrapper {
557
562
  }
558
563
  }
559
564
 
565
+ /**
566
+ * Run health check before workflow step (debug mode only)
567
+ * @private
568
+ * @param {Object} message - Workflow message
569
+ */
570
+ async _runHealthCheckBeforeWorkflowStep(message) {
571
+ if (!this.mqClient || !this.mqClient._transport) {
572
+ return;
573
+ }
574
+
575
+ const transport = this.mqClient._transport;
576
+
577
+ // Check if transport has performHealthCheck method (public API)
578
+ if (typeof transport.performHealthCheck === 'function') {
579
+ try {
580
+ const health = await transport.performHealthCheck();
581
+
582
+ this.logger?.debug('[ServiceWrapper] [DEBUG] Health check before workflow step', {
583
+ workflow_id: message.workflow_id || message.workflowId,
584
+ current_step: message.current_step,
585
+ health: {
586
+ healthy: health.healthy,
587
+ issues: health.issues,
588
+ consumers: {
589
+ active: health.consumers.active,
590
+ tracked: health.consumers.tracked
591
+ },
592
+ queues: {
593
+ checked: health.queues.checked,
594
+ missing: health.queues.missing,
595
+ noConsumer: health.queues.noConsumer
596
+ },
597
+ channels: health.channels
598
+ }
599
+ });
600
+
601
+ if (!health.healthy) {
602
+ this.logger?.warn('[ServiceWrapper] [DEBUG] Health check FAILED before workflow step', {
603
+ workflow_id: message.workflow_id || message.workflowId,
604
+ issues: health.issues
605
+ });
606
+ }
607
+ } catch (err) {
608
+ this.logger?.warn('[ServiceWrapper] [DEBUG] Health check error before workflow step', {
609
+ error: err.message,
610
+ workflow_id: message.workflow_id || message.workflowId
611
+ });
612
+ }
613
+ }
614
+ }
615
+
616
+ /**
617
+ * Register MQ channel close hooks and health monitoring
618
+ * Must be called AFTER monitoring is initialized
619
+ * @private
620
+ */
621
+ async _registerMQHooks() {
622
+ if (!this.mqClient || !this.mqClient._transport) {
623
+ return;
624
+ }
625
+
626
+ // Access the underlying transport (RabbitMQClient) to register hooks
627
+ const transport = this.mqClient._transport;
628
+
629
+ if (typeof transport.onChannelClose === 'function') {
630
+ // Register hook for all channel types
631
+ transport.onChannelClose('all', (details) => {
632
+ const logData = {
633
+ type: details.type,
634
+ reason: details.reason,
635
+ error: details.error ? {
636
+ message: details.error.message,
637
+ code: details.error.code,
638
+ stack: details.error.stack
639
+ } : null,
640
+ timestamp: details.timestamp,
641
+ connectionState: details.connectionState,
642
+ channelCreatedAt: details.channelCreatedAt,
643
+ lastOperation: details.lastOperation
644
+ };
645
+
646
+ if (this.monitoring && this.monitoring.error) {
647
+ this.monitoring.error(`MQ Channel Closed: ${details.type}`, logData);
648
+ }
649
+ console.error(`[ServiceWrapper] [MQ] Channel closed: ${details.type} - ${details.reason}`);
650
+ });
651
+
652
+ this.logger?.info('MQ channel close hooks registered');
653
+ }
654
+
655
+ // Register health shutdown handler
656
+ if (typeof transport.on === 'function') {
657
+ transport.on('health:shutdown', (health) => {
658
+ console.error('[ServiceWrapper] [MQ] Health shutdown triggered - shutting down service');
659
+ if (this.monitoring && this.monitoring.error) {
660
+ this.monitoring.error('MQ Health Shutdown', {
661
+ issues: health.issues,
662
+ timestamp: health.timestamp
663
+ });
664
+ }
665
+ // Graceful shutdown
666
+ this.shutdown().then(() => {
667
+ process.exit(1);
668
+ }).catch((err) => {
669
+ console.error('[ServiceWrapper] Shutdown error:', err);
670
+ process.exit(1);
671
+ });
672
+ });
673
+
674
+ this.logger?.info('MQ health shutdown handler registered');
675
+ }
676
+ }
677
+
560
678
  /**
561
679
  * Initialize MQ connection
562
680
  * @private
@@ -570,9 +688,10 @@ class ServiceWrapper {
570
688
  const serviceName = this.config.service?.name || 'unnamed-service';
571
689
 
572
690
  // Health monitoring and channel close hooks configuration
691
+ // NOTE: Health monitoring is NOT run periodically - it's triggered manually before workflow steps in debug mode
573
692
  const healthCheckConfig = {
574
- healthCheckInterval: this.config.wrapper?.mq?.healthCheckInterval || 30000, // 30s
575
- healthCheckEnabled: this.config.wrapper?.mq?.healthCheckEnabled !== false, // Default: true
693
+ healthCheckInterval: this.config.wrapper?.mq?.healthCheckInterval || 30000, // Not used when healthCheckEnabled=false
694
+ healthCheckEnabled: false, // Disable periodic checks - we'll run manually before workflow steps
576
695
  criticalHealthShutdown: this.config.wrapper?.mq?.criticalHealthShutdown !== false, // Default: true
577
696
  criticalHealthShutdownDelay: this.config.wrapper?.mq?.criticalHealthShutdownDelay || 60000, // 60s
578
697
  // Health reporting callbacks
@@ -624,51 +743,6 @@ class ServiceWrapper {
624
743
  await this.mqClient.connect();
625
744
  this.logger?.info('MQ connector initialized');
626
745
 
627
- // Register channel close hooks after connection is established
628
- // Access the underlying transport (RabbitMQClient) via _transport
629
- if (this.mqClient._transport && typeof this.mqClient._transport.onChannelClose === 'function') {
630
- // Register hook for all channel types
631
- this.mqClient._transport.onChannelClose('all', (details) => {
632
- const logData = {
633
- type: details.type,
634
- reason: details.reason,
635
- error: details.error ? {
636
- message: details.error.message,
637
- code: details.error.code,
638
- stack: details.error.stack
639
- } : null,
640
- timestamp: details.timestamp,
641
- connectionState: details.connectionState,
642
- channelCreatedAt: details.channelCreatedAt,
643
- lastOperation: details.lastOperation
644
- };
645
-
646
- if (this.monitoring && this.monitoring.error) {
647
- this.monitoring.error(`MQ Channel Closed: ${details.type}`, logData);
648
- }
649
- console.error(`[ServiceWrapper] [MQ] Channel closed: ${details.type} - ${details.reason}`);
650
- });
651
-
652
- // Register health shutdown handler
653
- this.mqClient._transport.on('health:shutdown', (health) => {
654
- console.error('[ServiceWrapper] [MQ] Health shutdown triggered - shutting down service');
655
- if (this.monitoring && this.monitoring.error) {
656
- this.monitoring.error('MQ Health Shutdown', {
657
- issues: health.issues,
658
- timestamp: health.timestamp
659
- });
660
- }
661
- // Graceful shutdown
662
- this.shutdown().then(() => {
663
- process.exit(1);
664
- }).catch((err) => {
665
- console.error('[ServiceWrapper] Shutdown error:', err);
666
- process.exit(1);
667
- });
668
- });
669
-
670
- this.logger?.info('MQ channel close hooks and health monitoring registered');
671
- }
672
746
  } catch (error) {
673
747
  const message = `[MQConnector] Unable to connect to RabbitMQ at ${mqUrl}. ` +
674
748
  'Verify that api_services_queuer is running and accessible. ' +
@@ -1175,6 +1249,11 @@ class ServiceWrapper {
1175
1249
  throw new Error('Workflow message must have current_step field or cookbook with at least one step');
1176
1250
  }
1177
1251
 
1252
+ // DEBUG MODE: Run health check before workflow step
1253
+ if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
1254
+ await this._runHealthCheckBeforeWorkflowStep(message);
1255
+ }
1256
+
1178
1257
  // Normalize message format: Gateway sends workflowId, orchestrator expects workflow_id
1179
1258
  const normalizedMessage = {
1180
1259
  ...message,