@onlineapps/service-wrapper 2.1.40 → 2.1.42
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 +43 -0
- package/package.json +1 -1
- package/src/ConfigLoader.js +105 -8
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
"monitoring": {
|
|
15
|
+
"enabled": true,
|
|
16
|
+
"metrics": ["requests", "errors", "duration"]
|
|
17
|
+
},
|
|
18
|
+
"logging": {
|
|
19
|
+
"enabled": true,
|
|
20
|
+
"level": "info",
|
|
21
|
+
"requests": {
|
|
22
|
+
"enabled": true,
|
|
23
|
+
"bodies": true,
|
|
24
|
+
"headers": false
|
|
25
|
+
},
|
|
26
|
+
"responses": {
|
|
27
|
+
"enabled": true,
|
|
28
|
+
"bodies": true
|
|
29
|
+
},
|
|
30
|
+
"excludePaths": ["/health", "/status", "/metrics", "/specification"]
|
|
31
|
+
},
|
|
32
|
+
"health": {
|
|
33
|
+
"enabled": true,
|
|
34
|
+
"endpoint": "/health"
|
|
35
|
+
},
|
|
36
|
+
"validation": {
|
|
37
|
+
"enabled": true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
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
|
*/
|
|
@@ -68,6 +70,96 @@ class ConfigLoader {
|
|
|
68
70
|
return process.env[varName] || defaultValue || '';
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Apply ${VAR} / ${VAR:default} substitutions anywhere within a string.
|
|
75
|
+
* Mirrors ServiceWrapper placeholder support but keeps logic centralized in ConfigLoader.
|
|
76
|
+
*
|
|
77
|
+
* NOTE: This does NOT enforce strict missing-env behavior (that is addressed later).
|
|
78
|
+
*
|
|
79
|
+
* @param {string} value
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
static resolveEnvPlaceholdersInString(value) {
|
|
83
|
+
if (typeof value !== 'string') {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return value.replace(/\$\{([^:}]+)(?::([^}]*))?\}/g, (match, varName, defaultValue) => {
|
|
88
|
+
const envValue = process.env[varName];
|
|
89
|
+
|
|
90
|
+
// Use env value if exists (even if empty string)
|
|
91
|
+
if (envValue !== undefined) {
|
|
92
|
+
return envValue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Use default if provided
|
|
96
|
+
if (defaultValue !== undefined) {
|
|
97
|
+
return defaultValue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Fail-fast: placeholder without default requires ENV to be set
|
|
101
|
+
throw new Error(`[ConfigLoader] Missing environment variable - ${varName} is required (config placeholder ${match})`);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Deeply resolve env placeholders across an object.
|
|
107
|
+
* @param {any} input
|
|
108
|
+
* @param {Object} [options]
|
|
109
|
+
* @param {string} [options.jsonPath] - JSON path prefix for error context
|
|
110
|
+
* @returns {any}
|
|
111
|
+
*/
|
|
112
|
+
static resolveEnvPlaceholdersDeep(input, options = {}) {
|
|
113
|
+
const { jsonPath = '$' } = options;
|
|
114
|
+
|
|
115
|
+
if (Array.isArray(input)) {
|
|
116
|
+
return input.map((item, idx) => this.resolveEnvPlaceholdersDeep(item, { jsonPath: `${jsonPath}[${idx}]` }));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (input && typeof input === 'object') {
|
|
120
|
+
const out = {};
|
|
121
|
+
for (const [k, v] of Object.entries(input)) {
|
|
122
|
+
out[k] = this.resolveEnvPlaceholdersDeep(v, { jsonPath: `${jsonPath}.${k}` });
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (typeof input === 'string') {
|
|
128
|
+
try {
|
|
129
|
+
return this.resolveEnvPlaceholdersInString(input);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
throw new Error(`${err.message} at ${jsonPath}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return input;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Deep merge (plain objects only). Arrays are replaced (not merged).
|
|
140
|
+
* @param {Object} base
|
|
141
|
+
* @param {Object} override
|
|
142
|
+
* @returns {Object}
|
|
143
|
+
*/
|
|
144
|
+
static deepMerge(base, override) {
|
|
145
|
+
if (!base || typeof base !== 'object' || Array.isArray(base)) {
|
|
146
|
+
return override;
|
|
147
|
+
}
|
|
148
|
+
if (!override || typeof override !== 'object' || Array.isArray(override)) {
|
|
149
|
+
return override === undefined ? base : override;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const out = { ...base };
|
|
153
|
+
for (const [k, v] of Object.entries(override)) {
|
|
154
|
+
if (v && typeof v === 'object' && !Array.isArray(v) && base[k] && typeof base[k] === 'object' && !Array.isArray(base[k])) {
|
|
155
|
+
out[k] = this.deepMerge(base[k], v);
|
|
156
|
+
} else {
|
|
157
|
+
out[k] = v;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
|
|
71
163
|
/**
|
|
72
164
|
* Get list of all configuration files
|
|
73
165
|
* @returns {Object} Configuration registry
|
|
@@ -115,7 +207,16 @@ class ConfigLoader {
|
|
|
115
207
|
// Always use version from package.json (Single Source of Truth)
|
|
116
208
|
config.service.version = this.loadPackageVersion(basePath);
|
|
117
209
|
|
|
118
|
-
|
|
210
|
+
// Apply repo defaults (non-critical) then allow service config to override
|
|
211
|
+
const defaults = RUNTIME_DEFAULTS?.defaults || {};
|
|
212
|
+
const merged = this.deepMerge(defaults, config);
|
|
213
|
+
|
|
214
|
+
// Resolve env placeholders across the whole config (service + wrapper + custom sections)
|
|
215
|
+
try {
|
|
216
|
+
return this.resolveEnvPlaceholdersDeep(merged, { jsonPath: '$' });
|
|
217
|
+
} catch (err) {
|
|
218
|
+
throw new Error(`[ConfigLoader] Failed to resolve placeholders in ${configPath}. ${err.message}`);
|
|
219
|
+
}
|
|
119
220
|
} catch (error) {
|
|
120
221
|
if (error.code === 'ENOENT') {
|
|
121
222
|
throw new Error(`Service config not found: ${configPath}`);
|
|
@@ -195,16 +296,12 @@ class ConfigLoader {
|
|
|
195
296
|
const operations = this.loadOperations(basePath);
|
|
196
297
|
const validationProof = this.loadValidationProof(basePath);
|
|
197
298
|
|
|
198
|
-
// Resolve environment variables in port and url
|
|
199
|
-
const resolvedPort = this.resolveEnvVar(serviceConfig.service.port);
|
|
200
|
-
const resolvedUrl = this.resolveEnvVar(serviceConfig.service.url);
|
|
201
|
-
|
|
202
299
|
return {
|
|
203
300
|
service: {
|
|
204
301
|
name: serviceConfig.service.name,
|
|
205
302
|
version: serviceConfig.service.version,
|
|
206
|
-
port: parseInt(
|
|
207
|
-
url:
|
|
303
|
+
port: parseInt(serviceConfig.service.port, 10),
|
|
304
|
+
url: serviceConfig.service.url,
|
|
208
305
|
env: process.env.NODE_ENV || 'development'
|
|
209
306
|
},
|
|
210
307
|
wrapper: serviceConfig.wrapper || {},
|