@onlineapps/service-wrapper 2.1.111 → 2.1.113

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.1.111",
3
+ "version": "2.1.113",
4
4
  "description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -25,16 +25,16 @@
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
27
  "@onlineapps/conn-base-cache": "1.0.8",
28
- "@onlineapps/conn-base-monitoring": "1.0.9",
29
- "@onlineapps/conn-infra-error-handler": "1.0.8",
28
+ "@onlineapps/conn-base-monitoring": "1.0.10",
29
+ "@onlineapps/conn-infra-error-handler": "1.0.9",
30
30
  "@onlineapps/conn-infra-mq": "1.1.67",
31
31
  "@onlineapps/conn-orch-api-mapper": "1.0.30",
32
32
  "@onlineapps/conn-orch-cookbook": "2.0.35",
33
- "@onlineapps/conn-orch-orchestrator": "1.0.102",
33
+ "@onlineapps/conn-orch-orchestrator": "1.0.103",
34
34
  "@onlineapps/conn-orch-registry": "1.1.52",
35
35
  "@onlineapps/conn-orch-validator": "2.0.29",
36
- "@onlineapps/monitoring-core": "1.0.20",
37
- "@onlineapps/service-common": "1.0.15",
36
+ "@onlineapps/monitoring-core": "1.0.21",
37
+ "@onlineapps/service-common": "1.0.17",
38
38
  "@onlineapps/runtime-config": "1.0.2"
39
39
  },
40
40
  "devDependencies": {
@@ -973,18 +973,37 @@ class ServiceWrapper {
973
973
  throw new Error(`Failed to register service ${serviceName}: ${error.message}`);
974
974
  }
975
975
 
976
- // Start heartbeat
976
+ // Start heartbeat with fail-fast on persistent MQ failure
977
977
  const heartbeatInterval = this.config.wrapper?.registry?.heartbeatInterval;
978
978
  if (typeof heartbeatInterval !== 'number' || Number.isNaN(heartbeatInterval) || heartbeatInterval <= 0) {
979
979
  throw new Error(`[ServiceWrapper] Invalid configuration - wrapper.registry.heartbeatInterval must be a positive number, got: ${heartbeatInterval}`);
980
980
  }
981
981
  this.logger?.info(`[ServiceWrapper] Heartbeat interval set to ${heartbeatInterval}ms`);
982
982
 
983
+ const MAX_CONSECUTIVE_FAILURES = this.config.wrapper?.registry?.maxHeartbeatFailures || 5;
984
+ let consecutiveFailures = 0;
985
+
983
986
  this.heartbeatTimer = setInterval(async () => {
984
987
  try {
985
988
  await this.registryClient.sendHeartbeat(serviceName);
989
+ consecutiveFailures = 0;
986
990
  } catch (error) {
987
- this.logger?.error('Heartbeat failed', { error: error.message, stack: error.stack });
991
+ consecutiveFailures++;
992
+ this.logger?.error('Heartbeat failed', {
993
+ error: error.message,
994
+ consecutiveFailures,
995
+ maxBeforeExit: MAX_CONSECUTIVE_FAILURES,
996
+ stack: error.stack
997
+ });
998
+
999
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1000
+ this.logger?.error(
1001
+ `[ServiceWrapper] FATAL: ${consecutiveFailures} consecutive heartbeat failures. ` +
1002
+ `MQ channel is dead and not recovering. Exiting for Docker restart.`,
1003
+ { consecutiveFailures, serviceName }
1004
+ );
1005
+ process.exit(1);
1006
+ }
988
1007
  }
989
1008
  }, heartbeatInterval);
990
1009
  }
@@ -1059,20 +1078,32 @@ class ServiceWrapper {
1059
1078
  _setupHealthChecks() {
1060
1079
  const healthEndpoint = this.config.wrapper?.health?.endpoint || '/health';
1061
1080
 
1062
- this.app.get(healthEndpoint, (req, res) => {
1081
+ this.app.get(healthEndpoint, async (req, res) => {
1063
1082
  const health = {
1064
1083
  status: 'healthy',
1065
1084
  service: this.config.service?.name || 'unnamed-service',
1066
1085
  timestamp: new Date().toISOString(),
1067
1086
  components: {
1068
1087
  http: 'healthy',
1069
- mq: this.mqClient?.isConnected() ? 'healthy' : 'unhealthy',
1088
+ mq: 'disabled',
1070
1089
  registry: this.registryClient ? 'healthy' : 'disabled',
1071
1090
  cache: this.cacheConnector ? 'healthy' : 'disabled'
1072
1091
  }
1073
1092
  };
1074
1093
 
1075
- // Determine overall status
1094
+ if (this.mqClient) {
1095
+ try {
1096
+ const mqHealth = await this.mqClient.performHealthCheck();
1097
+ health.components.mq = mqHealth.healthy ? 'healthy' : 'unhealthy';
1098
+ if (!mqHealth.healthy) {
1099
+ health.mqIssues = mqHealth.issues;
1100
+ }
1101
+ } catch (e) {
1102
+ health.components.mq = 'unhealthy';
1103
+ health.mqIssues = [e.message];
1104
+ }
1105
+ }
1106
+
1076
1107
  const statuses = Object.values(health.components);
1077
1108
  if (statuses.includes('unhealthy')) {
1078
1109
  health.status = 'unhealthy';