@onlineapps/service-wrapper 2.1.42 → 2.1.43

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.
@@ -11,6 +11,16 @@
11
11
  "enabled": true,
12
12
  "heartbeatInterval": 30000
13
13
  },
14
+ "infrastructureGate": {
15
+ "maxWaitMs": 300000,
16
+ "checkIntervalMs": 5000
17
+ },
18
+ "infrastructureVerify": {
19
+ "maxRetries": 12,
20
+ "baseDelayMs": 5000,
21
+ "maxDelayMs": 30000,
22
+ "queueCheckTimeoutMs": 5000
23
+ },
14
24
  "monitoring": {
15
25
  "enabled": true,
16
26
  "metrics": ["requests", "errors", "duration"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/service-wrapper",
3
- "version": "2.1.42",
3
+ "version": "2.1.43",
4
4
  "description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -67,7 +67,14 @@ class ConfigLoader {
67
67
  const varName = match[1];
68
68
  const defaultValue = match[2];
69
69
 
70
- return process.env[varName] || defaultValue || '';
70
+ const envValue = process.env[varName];
71
+ if (envValue !== undefined) {
72
+ return envValue;
73
+ }
74
+ if (defaultValue !== undefined) {
75
+ return defaultValue;
76
+ }
77
+ throw new Error(`[ConfigLoader] Missing environment variable - ${varName} is required (config placeholder ${match[0]})`);
71
78
  }
72
79
 
73
80
  /**
@@ -296,17 +303,28 @@ class ConfigLoader {
296
303
  const operations = this.loadOperations(basePath);
297
304
  const validationProof = this.loadValidationProof(basePath);
298
305
 
306
+ // Include any custom top-level sections from config.json (e.g., storage, smtp, etc.)
307
+ // Keep service-wrapper owned sections (service, wrapper) explicit and stable.
308
+ const customSections = {};
309
+ for (const [k, v] of Object.entries(serviceConfig || {})) {
310
+ if (k === 'service' || k === 'wrapper') {
311
+ continue;
312
+ }
313
+ customSections[k] = v;
314
+ }
315
+
299
316
  return {
300
317
  service: {
301
318
  name: serviceConfig.service.name,
302
319
  version: serviceConfig.service.version,
303
320
  port: parseInt(serviceConfig.service.port, 10),
304
321
  url: serviceConfig.service.url,
305
- env: process.env.NODE_ENV || 'development'
322
+ env: process.env.NODE_ENV
306
323
  },
307
324
  wrapper: serviceConfig.wrapper || {},
308
325
  operations,
309
- validationProof
326
+ validationProof,
327
+ ...customSections
310
328
  };
311
329
  }
312
330
 
@@ -156,8 +156,15 @@ class ServiceWrapper {
156
156
  const { waitForInfrastructureReady } = require('@onlineapps/service-common');
157
157
  const { requireEnv } = require('@onlineapps/service-common');
158
158
  const redisUrl = this.config.wrapper?.cache?.url || requireEnv('REDIS_URL', 'Redis connection URL');
159
- const maxWait = parseInt(process.env.INFRASTRUCTURE_HEALTH_WAIT_MAX_TIME) || 300000;
160
- const checkInterval = parseInt(process.env.INFRASTRUCTURE_HEALTH_WAIT_CHECK_INTERVAL) || 5000;
159
+ const maxWait = this.config.wrapper?.infrastructureGate?.maxWaitMs;
160
+ const checkInterval = this.config.wrapper?.infrastructureGate?.checkIntervalMs;
161
+
162
+ if (typeof maxWait !== 'number' || Number.isNaN(maxWait) || maxWait <= 0) {
163
+ throw new Error(`[InfrastructureGate] Invalid configuration - wrapper.infrastructureGate.maxWaitMs must be a positive number, got: ${maxWait}`);
164
+ }
165
+ if (typeof checkInterval !== 'number' || Number.isNaN(checkInterval) || checkInterval <= 0) {
166
+ throw new Error(`[InfrastructureGate] Invalid configuration - wrapper.infrastructureGate.checkIntervalMs must be a positive number, got: ${checkInterval}`);
167
+ }
161
168
 
162
169
  const logWithPrefix = (level, message) => {
163
170
  const prefixed = `[InfrastructureGate:${context}] ${message}`;
@@ -238,9 +245,8 @@ class ServiceWrapper {
238
245
  return defaultValue;
239
246
  }
240
247
 
241
- // Log warning and return empty string
242
- console.warn(`[ServiceWrapper] Missing env variable: ${varName}`);
243
- return '';
248
+ // Fail-fast: placeholder without default requires ENV to be set
249
+ throw new Error(`[ServiceWrapper] Missing environment variable - ${varName} is required (config placeholder ${match})`);
244
250
  });
245
251
  }
246
252
  return value;
@@ -794,9 +800,25 @@ class ServiceWrapper {
794
800
  throw new Error('Registry URL is required when registry is enabled. Set wrapper.registry.url in config');
795
801
  }
796
802
 
797
- const serviceName = this.config.service?.name || 'unnamed-service';
798
- const servicePort = this.config.service?.port || process.env.PORT || 3000;
799
- const mqUrl = this.config.wrapper?.mq?.url || '';
803
+ const serviceName = this.config.service?.name;
804
+ if (!serviceName) {
805
+ throw new Error('[ServiceWrapper] Missing configuration - service.name is required');
806
+ }
807
+
808
+ const servicePort = this.config.service?.port;
809
+ if (!servicePort) {
810
+ throw new Error('[ServiceWrapper] Missing configuration - service.port is required');
811
+ }
812
+
813
+ const serviceUrl = this.config.service?.url;
814
+ if (!serviceUrl) {
815
+ throw new Error('[ServiceWrapper] Missing configuration - service.url is required');
816
+ }
817
+
818
+ const mqUrl = this.config.wrapper?.mq?.url;
819
+ if (!mqUrl) {
820
+ throw new Error('[ServiceWrapper] Missing configuration - wrapper.mq.url is required');
821
+ }
800
822
 
801
823
  // Infrastructure health check was already done in Fáze 0.4
802
824
  // FÁZE 0.5 a 0.6: Vytvoření front a spuštění konzumerů se provádí v registryClient.init()
@@ -805,7 +827,7 @@ class ServiceWrapper {
805
827
  this.registryClient = new RegistryConnector.ServiceRegistryClient({
806
828
  amqpUrl: mqUrl,
807
829
  serviceName: serviceName,
808
- version: this.config.service?.version || '1.0.0',
830
+ version: this.config.service?.version,
809
831
  registryQueue: 'registry.register', // Use correct queue name
810
832
  registryUrl: registryUrl,
811
833
  logger: this.logger || console,
@@ -819,10 +841,10 @@ class ServiceWrapper {
819
841
  // Register service
820
842
  const serviceInfo = {
821
843
  name: serviceName,
822
- url: `http://localhost:${servicePort}`,
844
+ url: serviceUrl,
823
845
  operations: this.operations?.operations || {},
824
846
  metadata: {
825
- version: this.config.service?.version || '1.0.0',
847
+ version: this.config.service?.version,
826
848
  description: this.config.service?.description || ''
827
849
  }
828
850
  };
@@ -888,10 +910,10 @@ class ServiceWrapper {
888
910
  }
889
911
 
890
912
  // Start heartbeat
891
- // Priority: ENV variable > config > default (10s)
892
- const heartbeatInterval = parseInt(process.env.HEARTBEAT_INTERVAL) ||
893
- this.config.wrapper?.registry?.heartbeatInterval ||
894
- 10000;
913
+ const heartbeatInterval = this.config.wrapper?.registry?.heartbeatInterval;
914
+ if (typeof heartbeatInterval !== 'number' || Number.isNaN(heartbeatInterval) || heartbeatInterval <= 0) {
915
+ throw new Error(`[ServiceWrapper] Invalid configuration - wrapper.registry.heartbeatInterval must be a positive number, got: ${heartbeatInterval}`);
916
+ }
895
917
  this.logger?.info(`[ServiceWrapper] Heartbeat interval set to ${heartbeatInterval}ms`);
896
918
 
897
919
  this.heartbeatTimer = setInterval(async () => {
@@ -920,9 +942,23 @@ class ServiceWrapper {
920
942
  * @private
921
943
  */
922
944
  async _verifyInfrastructureReady(serviceName) {
923
- const maxRetries = parseInt(process.env.INFRASTRUCTURE_VERIFY_MAX_RETRIES) || 12; // 12 * 5s = 60s max
924
- const baseDelay = parseInt(process.env.INFRASTRUCTURE_VERIFY_BASE_DELAY) || 5000; // 5 seconds
925
- const maxDelay = parseInt(process.env.INFRASTRUCTURE_VERIFY_MAX_DELAY) || 30000; // 30 seconds max
945
+ const maxRetries = this.config.wrapper?.infrastructureVerify?.maxRetries;
946
+ const baseDelay = this.config.wrapper?.infrastructureVerify?.baseDelayMs;
947
+ const maxDelay = this.config.wrapper?.infrastructureVerify?.maxDelayMs;
948
+ const queueCheckTimeoutMs = this.config.wrapper?.infrastructureVerify?.queueCheckTimeoutMs;
949
+
950
+ if (typeof maxRetries !== 'number' || Number.isNaN(maxRetries) || maxRetries <= 0) {
951
+ throw new Error(`[InfrastructureVerify] Invalid configuration - wrapper.infrastructureVerify.maxRetries must be a positive number, got: ${maxRetries}`);
952
+ }
953
+ if (typeof baseDelay !== 'number' || Number.isNaN(baseDelay) || baseDelay <= 0) {
954
+ throw new Error(`[InfrastructureVerify] Invalid configuration - wrapper.infrastructureVerify.baseDelayMs must be a positive number, got: ${baseDelay}`);
955
+ }
956
+ if (typeof maxDelay !== 'number' || Number.isNaN(maxDelay) || maxDelay <= 0) {
957
+ throw new Error(`[InfrastructureVerify] Invalid configuration - wrapper.infrastructureVerify.maxDelayMs must be a positive number, got: ${maxDelay}`);
958
+ }
959
+ if (typeof queueCheckTimeoutMs !== 'number' || Number.isNaN(queueCheckTimeoutMs) || queueCheckTimeoutMs <= 0) {
960
+ throw new Error(`[InfrastructureVerify] Invalid configuration - wrapper.infrastructureVerify.queueCheckTimeoutMs must be a positive number, got: ${queueCheckTimeoutMs}`);
961
+ }
926
962
 
927
963
  // Required infrastructure queues that must exist before business queues can be created
928
964
  const requiredInfrastructureQueues = [
@@ -962,15 +998,14 @@ class ServiceWrapper {
962
998
  }
963
999
 
964
1000
  const missingQueues = [];
965
- const QUEUE_CHECK_TIMEOUT = 5000; // 5 seconds per queue check
966
1001
  for (const queueName of requiredInfrastructureQueues) {
967
1002
  try {
968
1003
  // Add timeout to prevent hanging on checkQueue
969
1004
  const checkPromise = channel.checkQueue(queueName);
970
1005
  const checkTimeoutPromise = new Promise((_, reject) => {
971
1006
  setTimeout(() => {
972
- reject(new Error(`checkQueue timeout after ${QUEUE_CHECK_TIMEOUT}ms`));
973
- }, QUEUE_CHECK_TIMEOUT);
1007
+ reject(new Error(`checkQueue timeout after ${queueCheckTimeoutMs}ms`));
1008
+ }, queueCheckTimeoutMs);
974
1009
  });
975
1010
 
976
1011
  await Promise.race([checkPromise, checkTimeoutPromise]);
@@ -1004,7 +1039,7 @@ class ServiceWrapper {
1004
1039
  try {
1005
1040
  const checkPromise = channel.checkQueue(queueName);
1006
1041
  const checkTimeoutPromise = new Promise((_, reject) => {
1007
- setTimeout(() => reject(new Error('timeout')), QUEUE_CHECK_TIMEOUT);
1042
+ setTimeout(() => reject(new Error('timeout')), queueCheckTimeoutMs);
1008
1043
  });
1009
1044
  await Promise.race([checkPromise, checkTimeoutPromise]);
1010
1045
  this.logger?.info(`[InfrastructureVerify] ✓ Optional queue exists: ${queueName}`);
@@ -1102,14 +1137,29 @@ class ServiceWrapper {
1102
1137
  * @private
1103
1138
  */
1104
1139
  async _initializeOrchestrator() {
1105
- const servicePort = this.config.service?.port || process.env.PORT || 3000;
1106
- const serviceUrl = `http://localhost:${servicePort}`;
1140
+ const serviceName = this.config.service?.name;
1141
+ const serviceVersion = this.config.service?.version;
1142
+ const environment = this.config.service?.env;
1143
+ const serviceUrl = this.config.service?.url;
1144
+
1145
+ if (!serviceName) {
1146
+ throw new Error('[ServiceWrapper] Missing configuration - service.name is required');
1147
+ }
1148
+ if (!serviceVersion) {
1149
+ throw new Error('[ServiceWrapper] Missing configuration - service.version is required');
1150
+ }
1151
+ if (!environment) {
1152
+ throw new Error('[ServiceWrapper] Missing configuration - service.env (NODE_ENV) is required');
1153
+ }
1154
+ if (!serviceUrl) {
1155
+ throw new Error('[ServiceWrapper] Missing configuration - service.url is required');
1156
+ }
1107
1157
 
1108
1158
  // Create error handler with monitoring instance
1109
1159
  const errorHandler = new ErrorHandlerConnector({
1110
- serviceName: this.config.service?.name || 'unnamed-service',
1111
- serviceVersion: this.config.service?.version || '0.0.0',
1112
- environment: process.env.NODE_ENV || 'development',
1160
+ serviceName,
1161
+ serviceVersion,
1162
+ environment,
1113
1163
  monitoring: this.monitoring, // Use monitoring instance from conn-base-monitoring
1114
1164
  handling: {
1115
1165
  maxRetries: this.config.wrapper?.errorHandling?.maxRetries || 3,
@@ -1374,8 +1424,12 @@ class ServiceWrapper {
1374
1424
  throw new Error(`Unknown operation: ${operationName}`);
1375
1425
  }
1376
1426
 
1377
- const servicePort = this.config.service?.port || process.env.PORT || 3000;
1378
- const url = `http://localhost:${servicePort}${operation.endpoint}`;
1427
+ const serviceUrl = this.config.service?.url;
1428
+ if (!serviceUrl) {
1429
+ throw new Error('[ServiceWrapper] Missing configuration - service.url is required');
1430
+ }
1431
+
1432
+ const url = new URL(operation.endpoint, serviceUrl).toString();
1379
1433
  const method = operation.method || 'POST';
1380
1434
 
1381
1435
  this.logger?.debug(`Executing operation ${operationName} via ${method} ${url}`);
@@ -1386,10 +1440,15 @@ class ServiceWrapper {
1386
1440
  return new Promise((resolve, reject) => {
1387
1441
  const postData = JSON.stringify(input);
1388
1442
 
1443
+ const target = new URL(url);
1444
+ const targetPort = target.port
1445
+ ? parseInt(target.port, 10)
1446
+ : (target.protocol === 'https:' ? 443 : 80);
1447
+
1389
1448
  const options = {
1390
- hostname: 'localhost',
1391
- port: servicePort,
1392
- path: operation.endpoint,
1449
+ hostname: target.hostname,
1450
+ port: targetPort,
1451
+ path: `${target.pathname}${target.search}`,
1393
1452
  method: method,
1394
1453
  headers: {
1395
1454
  'Content-Type': 'application/json',
package/src/index.js CHANGED
@@ -63,7 +63,7 @@ async function bootstrap(serviceRoot, options = {}) {
63
63
  const PORT = config.service.port;
64
64
  const server = app.listen(PORT, () => {
65
65
  console.log(`✓ HTTP server listening on port ${PORT}`);
66
- console.log(`✓ Health check: http://localhost:${PORT}/health`);
66
+ console.log(`✓ Health check: /health`);
67
67
  });
68
68
 
69
69
  // 2. Initialize Service Wrapper (MQ, Registry, Monitoring, etc.)
@@ -76,7 +76,7 @@ async function bootstrap(serviceRoot, options = {}) {
76
76
  name: config.service.name,
77
77
  version: config.service.version,
78
78
  port: config.service.port,
79
- url: process.env.SERVICE_URL || `http://localhost:${PORT}`
79
+ url: config.service.url
80
80
  },
81
81
  wrapper: config.wrapper
82
82
  },