@onlineapps/service-wrapper 2.1.41 → 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.
- package/config/runtime-defaults.json +53 -0
- package/package.json +1 -1
- package/src/ConfigLoader.js +125 -10
- package/src/ServiceWrapper.js +91 -32
- package/src/index.js +2 -2
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"description": "Runtime defaults for @onlineapps/service-wrapper (non-critical only). Service configs may override.",
|
|
4
|
+
"defaults": {
|
|
5
|
+
"wrapper": {
|
|
6
|
+
"mq": {
|
|
7
|
+
"prefetch": 10,
|
|
8
|
+
"enabled": true
|
|
9
|
+
},
|
|
10
|
+
"registry": {
|
|
11
|
+
"enabled": true,
|
|
12
|
+
"heartbeatInterval": 30000
|
|
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
|
+
},
|
|
24
|
+
"monitoring": {
|
|
25
|
+
"enabled": true,
|
|
26
|
+
"metrics": ["requests", "errors", "duration"]
|
|
27
|
+
},
|
|
28
|
+
"logging": {
|
|
29
|
+
"enabled": true,
|
|
30
|
+
"level": "info",
|
|
31
|
+
"requests": {
|
|
32
|
+
"enabled": true,
|
|
33
|
+
"bodies": true,
|
|
34
|
+
"headers": false
|
|
35
|
+
},
|
|
36
|
+
"responses": {
|
|
37
|
+
"enabled": true,
|
|
38
|
+
"bodies": true
|
|
39
|
+
},
|
|
40
|
+
"excludePaths": ["/health", "/status", "/metrics", "/specification"]
|
|
41
|
+
},
|
|
42
|
+
"health": {
|
|
43
|
+
"enabled": true,
|
|
44
|
+
"endpoint": "/health"
|
|
45
|
+
},
|
|
46
|
+
"validation": {
|
|
47
|
+
"enabled": true
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
package/package.json
CHANGED
package/src/ConfigLoader.js
CHANGED
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
const fs = require('fs');
|
|
23
23
|
const path = require('path');
|
|
24
24
|
|
|
25
|
+
const RUNTIME_DEFAULTS = require('../config/runtime-defaults.json');
|
|
26
|
+
|
|
25
27
|
/**
|
|
26
28
|
* CENTRAL REGISTRY: All configuration files used by business services
|
|
27
29
|
* If you add a new config file, add it here!
|
|
@@ -47,7 +49,7 @@ const CONFIG_REGISTRY = {
|
|
|
47
49
|
class ConfigLoader {
|
|
48
50
|
/**
|
|
49
51
|
* Resolve environment variable substitution
|
|
50
|
-
* Supports format: ${VAR_NAME:default_value}
|
|
52
|
+
* Supports format: ${VAR_NAME:default_value} OR ${VAR_NAME}
|
|
51
53
|
* @param {string} value - Value to resolve
|
|
52
54
|
* @returns {string} Resolved value
|
|
53
55
|
*/
|
|
@@ -65,7 +67,104 @@ class ConfigLoader {
|
|
|
65
67
|
const varName = match[1];
|
|
66
68
|
const defaultValue = match[2];
|
|
67
69
|
|
|
68
|
-
|
|
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]})`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Apply ${VAR} / ${VAR:default} substitutions anywhere within a string.
|
|
82
|
+
* Mirrors ServiceWrapper placeholder support but keeps logic centralized in ConfigLoader.
|
|
83
|
+
*
|
|
84
|
+
* NOTE: This does NOT enforce strict missing-env behavior (that is addressed later).
|
|
85
|
+
*
|
|
86
|
+
* @param {string} value
|
|
87
|
+
* @returns {string}
|
|
88
|
+
*/
|
|
89
|
+
static resolveEnvPlaceholdersInString(value) {
|
|
90
|
+
if (typeof value !== 'string') {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return value.replace(/\$\{([^:}]+)(?::([^}]*))?\}/g, (match, varName, defaultValue) => {
|
|
95
|
+
const envValue = process.env[varName];
|
|
96
|
+
|
|
97
|
+
// Use env value if exists (even if empty string)
|
|
98
|
+
if (envValue !== undefined) {
|
|
99
|
+
return envValue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Use default if provided
|
|
103
|
+
if (defaultValue !== undefined) {
|
|
104
|
+
return defaultValue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Fail-fast: placeholder without default requires ENV to be set
|
|
108
|
+
throw new Error(`[ConfigLoader] Missing environment variable - ${varName} is required (config placeholder ${match})`);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Deeply resolve env placeholders across an object.
|
|
114
|
+
* @param {any} input
|
|
115
|
+
* @param {Object} [options]
|
|
116
|
+
* @param {string} [options.jsonPath] - JSON path prefix for error context
|
|
117
|
+
* @returns {any}
|
|
118
|
+
*/
|
|
119
|
+
static resolveEnvPlaceholdersDeep(input, options = {}) {
|
|
120
|
+
const { jsonPath = '$' } = options;
|
|
121
|
+
|
|
122
|
+
if (Array.isArray(input)) {
|
|
123
|
+
return input.map((item, idx) => this.resolveEnvPlaceholdersDeep(item, { jsonPath: `${jsonPath}[${idx}]` }));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (input && typeof input === 'object') {
|
|
127
|
+
const out = {};
|
|
128
|
+
for (const [k, v] of Object.entries(input)) {
|
|
129
|
+
out[k] = this.resolveEnvPlaceholdersDeep(v, { jsonPath: `${jsonPath}.${k}` });
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (typeof input === 'string') {
|
|
135
|
+
try {
|
|
136
|
+
return this.resolveEnvPlaceholdersInString(input);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
throw new Error(`${err.message} at ${jsonPath}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return input;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Deep merge (plain objects only). Arrays are replaced (not merged).
|
|
147
|
+
* @param {Object} base
|
|
148
|
+
* @param {Object} override
|
|
149
|
+
* @returns {Object}
|
|
150
|
+
*/
|
|
151
|
+
static deepMerge(base, override) {
|
|
152
|
+
if (!base || typeof base !== 'object' || Array.isArray(base)) {
|
|
153
|
+
return override;
|
|
154
|
+
}
|
|
155
|
+
if (!override || typeof override !== 'object' || Array.isArray(override)) {
|
|
156
|
+
return override === undefined ? base : override;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const out = { ...base };
|
|
160
|
+
for (const [k, v] of Object.entries(override)) {
|
|
161
|
+
if (v && typeof v === 'object' && !Array.isArray(v) && base[k] && typeof base[k] === 'object' && !Array.isArray(base[k])) {
|
|
162
|
+
out[k] = this.deepMerge(base[k], v);
|
|
163
|
+
} else {
|
|
164
|
+
out[k] = v;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
69
168
|
}
|
|
70
169
|
|
|
71
170
|
/**
|
|
@@ -115,7 +214,16 @@ class ConfigLoader {
|
|
|
115
214
|
// Always use version from package.json (Single Source of Truth)
|
|
116
215
|
config.service.version = this.loadPackageVersion(basePath);
|
|
117
216
|
|
|
118
|
-
|
|
217
|
+
// Apply repo defaults (non-critical) then allow service config to override
|
|
218
|
+
const defaults = RUNTIME_DEFAULTS?.defaults || {};
|
|
219
|
+
const merged = this.deepMerge(defaults, config);
|
|
220
|
+
|
|
221
|
+
// Resolve env placeholders across the whole config (service + wrapper + custom sections)
|
|
222
|
+
try {
|
|
223
|
+
return this.resolveEnvPlaceholdersDeep(merged, { jsonPath: '$' });
|
|
224
|
+
} catch (err) {
|
|
225
|
+
throw new Error(`[ConfigLoader] Failed to resolve placeholders in ${configPath}. ${err.message}`);
|
|
226
|
+
}
|
|
119
227
|
} catch (error) {
|
|
120
228
|
if (error.code === 'ENOENT') {
|
|
121
229
|
throw new Error(`Service config not found: ${configPath}`);
|
|
@@ -195,21 +303,28 @@ class ConfigLoader {
|
|
|
195
303
|
const operations = this.loadOperations(basePath);
|
|
196
304
|
const validationProof = this.loadValidationProof(basePath);
|
|
197
305
|
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
const
|
|
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
|
+
}
|
|
201
315
|
|
|
202
316
|
return {
|
|
203
317
|
service: {
|
|
204
318
|
name: serviceConfig.service.name,
|
|
205
319
|
version: serviceConfig.service.version,
|
|
206
|
-
port: parseInt(
|
|
207
|
-
url:
|
|
208
|
-
env: process.env.NODE_ENV
|
|
320
|
+
port: parseInt(serviceConfig.service.port, 10),
|
|
321
|
+
url: serviceConfig.service.url,
|
|
322
|
+
env: process.env.NODE_ENV
|
|
209
323
|
},
|
|
210
324
|
wrapper: serviceConfig.wrapper || {},
|
|
211
325
|
operations,
|
|
212
|
-
validationProof
|
|
326
|
+
validationProof,
|
|
327
|
+
...customSections
|
|
213
328
|
};
|
|
214
329
|
}
|
|
215
330
|
|
package/src/ServiceWrapper.js
CHANGED
|
@@ -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 =
|
|
160
|
-
const checkInterval =
|
|
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
|
-
//
|
|
242
|
-
|
|
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
|
|
798
|
-
|
|
799
|
-
|
|
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
|
|
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:
|
|
844
|
+
url: serviceUrl,
|
|
823
845
|
operations: this.operations?.operations || {},
|
|
824
846
|
metadata: {
|
|
825
|
-
version: this.config.service?.version
|
|
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
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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 =
|
|
924
|
-
const baseDelay =
|
|
925
|
-
const maxDelay =
|
|
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 ${
|
|
973
|
-
},
|
|
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')),
|
|
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
|
|
1106
|
-
const
|
|
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
|
|
1111
|
-
serviceVersion
|
|
1112
|
-
environment
|
|
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
|
|
1378
|
-
|
|
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:
|
|
1391
|
-
port:
|
|
1392
|
-
path:
|
|
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:
|
|
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:
|
|
79
|
+
url: config.service.url
|
|
80
80
|
},
|
|
81
81
|
wrapper: config.wrapper
|
|
82
82
|
},
|