@onlineapps/service-wrapper 2.1.55 → 2.1.57

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.55",
3
+ "version": "2.1.57",
4
4
  "description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -28,10 +28,10 @@
28
28
  "@onlineapps/conn-base-monitoring": "1.0.5",
29
29
  "@onlineapps/conn-infra-error-handler": "1.0.5",
30
30
  "@onlineapps/conn-infra-mq": "1.1.57",
31
- "@onlineapps/conn-orch-api-mapper": "1.0.16",
32
- "@onlineapps/conn-orch-cookbook": "2.0.15",
33
- "@onlineapps/conn-orch-orchestrator": "1.0.71",
34
- "@onlineapps/conn-orch-registry": "1.1.31",
31
+ "@onlineapps/conn-orch-api-mapper": "1.0.18",
32
+ "@onlineapps/conn-orch-cookbook": "2.0.16",
33
+ "@onlineapps/conn-orch-orchestrator": "1.0.72",
34
+ "@onlineapps/conn-orch-registry": "1.1.33",
35
35
  "@onlineapps/conn-orch-validator": "2.0.22",
36
36
  "@onlineapps/monitoring-core": "1.0.15",
37
37
  "@onlineapps/service-common": "1.0.12",
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * Usage:
13
13
  * const { ConfigLoader } = require('@onlineapps/service-wrapper');
14
- * const config = ConfigLoader.loadAll();
14
+ * const config = ConfigLoader.loadAll({ env: process.env });
15
15
  *
16
16
  * Design principle: ONE place to define, validate, and load ALL configs.
17
17
  *
@@ -22,7 +22,6 @@
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
  const runtimeCfg = require('./config');
27
26
 
28
27
  /**
@@ -49,63 +48,47 @@ const CONFIG_REGISTRY = {
49
48
 
50
49
  class ConfigLoader {
51
50
  /**
52
- * Resolve environment variable substitution
53
- * Supports format: ${VAR_NAME:default_value} OR ${VAR_NAME}
54
- * @param {string} value - Value to resolve
55
- * @returns {string} Resolved value
51
+ * Ensure env is provided for placeholder resolution.
52
+ * @param {any} env
53
+ * @returns {Object} env object
54
+ * @private
56
55
  */
57
- static resolveEnvVar(value) {
58
- if (typeof value !== 'string') {
59
- return value;
60
- }
61
-
62
- // Match ${VAR_NAME:default} or ${VAR_NAME}
63
- const match = value.match(/^\$\{([^:}]+)(?::([^}]+))?\}$/);
64
- if (!match) {
65
- return value;
66
- }
67
-
68
- const varName = match[1];
69
- const defaultValue = match[2];
70
-
71
- const envValue = process.env[varName];
72
- if (envValue !== undefined) {
73
- return envValue;
56
+ static _requireEnv(env) {
57
+ if (!env || typeof env !== 'object') {
58
+ throw new Error('[ConfigLoader] Missing dependency - env is required (pass { env: process.env } from the service entrypoint)');
74
59
  }
75
- if (defaultValue !== undefined) {
76
- return defaultValue;
77
- }
78
- throw new Error(`[ConfigLoader] Missing environment variable - ${varName} is required (config placeholder ${match[0]})`);
60
+ return env;
79
61
  }
80
62
 
81
63
  /**
82
- * Apply ${VAR} / ${VAR:default} substitutions anywhere within a string.
83
- * Mirrors ServiceWrapper placeholder support but keeps logic centralized in ConfigLoader.
84
- *
85
- * NOTE: This does NOT enforce strict missing-env behavior (that is addressed later).
64
+ * Apply ${VAR} substitutions anywhere within a string.
65
+ * NOTE: ${VAR:default} is NOT allowed (no fallbacks). Use explicit config or set ENV.
86
66
  *
87
67
  * @param {string} value
68
+ * @param {Object} options
69
+ * @param {Object} options.env
88
70
  * @returns {string}
89
71
  */
90
- static resolveEnvPlaceholdersInString(value) {
72
+ static resolveEnvPlaceholdersInString(value, options = {}) {
91
73
  if (typeof value !== 'string') {
92
74
  return value;
93
75
  }
94
76
 
77
+ const env = this._requireEnv(options.env);
78
+
95
79
  return value.replace(/\$\{([^:}]+)(?::([^}]*))?\}/g, (match, varName, defaultValue) => {
96
- const envValue = process.env[varName];
80
+ if (defaultValue !== undefined) {
81
+ throw new Error(`[ConfigLoader] Unsupported placeholder default - "${match}" is not allowed. Fix: set ENV "${varName}" or set an explicit value in config.json`);
82
+ }
83
+
84
+ const envValue = env[varName];
97
85
 
98
86
  // Use env value if exists (even if empty string)
99
87
  if (envValue !== undefined) {
100
88
  return envValue;
101
89
  }
102
90
 
103
- // Use default if provided
104
- if (defaultValue !== undefined) {
105
- return defaultValue;
106
- }
107
-
108
- // Fail-fast: placeholder without default requires ENV to be set
91
+ // Fail-fast: placeholder requires ENV to be set
109
92
  throw new Error(`[ConfigLoader] Missing environment variable - ${varName} is required (config placeholder ${match})`);
110
93
  });
111
94
  }
@@ -118,23 +101,23 @@ class ConfigLoader {
118
101
  * @returns {any}
119
102
  */
120
103
  static resolveEnvPlaceholdersDeep(input, options = {}) {
121
- const { jsonPath = '$' } = options;
104
+ const { jsonPath = '$', env } = options;
122
105
 
123
106
  if (Array.isArray(input)) {
124
- return input.map((item, idx) => this.resolveEnvPlaceholdersDeep(item, { jsonPath: `${jsonPath}[${idx}]` }));
107
+ return input.map((item, idx) => this.resolveEnvPlaceholdersDeep(item, { jsonPath: `${jsonPath}[${idx}]`, env }));
125
108
  }
126
109
 
127
110
  if (input && typeof input === 'object') {
128
111
  const out = {};
129
112
  for (const [k, v] of Object.entries(input)) {
130
- out[k] = this.resolveEnvPlaceholdersDeep(v, { jsonPath: `${jsonPath}.${k}` });
113
+ out[k] = this.resolveEnvPlaceholdersDeep(v, { jsonPath: `${jsonPath}.${k}`, env });
131
114
  }
132
115
  return out;
133
116
  }
134
117
 
135
118
  if (typeof input === 'string') {
136
119
  try {
137
- return this.resolveEnvPlaceholdersInString(input);
120
+ return this.resolveEnvPlaceholdersInString(input, { env });
138
121
  } catch (err) {
139
122
  throw new Error(`${err.message} at ${jsonPath}`);
140
123
  }
@@ -200,7 +183,17 @@ class ConfigLoader {
200
183
  * @returns {Object} Service configuration
201
184
  * @throws {Error} If config file not found or invalid JSON
202
185
  */
203
- static loadServiceConfig(basePath = process.cwd()) {
186
+ /**
187
+ * Load service configuration from conn-config/config.json
188
+ * Version is always taken from package.json (Single Source of Truth)
189
+ * @param {Object} options
190
+ * @param {string} [options.basePath] - Base path (defaults to process.cwd())
191
+ * @param {Object} options.env - Environment variables for ${VAR} placeholders
192
+ * @returns {Object} Service configuration
193
+ * @throws {Error} If config file not found or invalid JSON
194
+ */
195
+ static loadServiceConfig(options = {}) {
196
+ const { basePath = process.cwd(), env } = options;
204
197
  const configPath = path.join(basePath, 'conn-config', 'config.json');
205
198
 
206
199
  try {
@@ -215,13 +208,9 @@ class ConfigLoader {
215
208
  // Always use version from package.json (Single Source of Truth)
216
209
  config.service.version = this.loadPackageVersion(basePath);
217
210
 
218
- // Apply repo defaults (non-critical) then allow service config to override
219
- const defaults = RUNTIME_DEFAULTS?.defaults || {};
220
- const merged = this.deepMerge(defaults, config);
221
-
222
211
  // Resolve env placeholders across the whole config (service + wrapper + custom sections)
223
212
  try {
224
- return this.resolveEnvPlaceholdersDeep(merged, { jsonPath: '$' });
213
+ return this.resolveEnvPlaceholdersDeep(config, { jsonPath: '$', env });
225
214
  } catch (err) {
226
215
  throw new Error(`[ConfigLoader] Failed to resolve placeholders in ${configPath}. ${err.message}`);
227
216
  }
@@ -299,8 +288,17 @@ class ConfigLoader {
299
288
  * @param {string} basePath - Base path (defaults to process.cwd())
300
289
  * @returns {Object} Combined configuration object
301
290
  */
302
- static loadAll(basePath = process.cwd()) {
303
- const serviceConfig = this.loadServiceConfig(basePath);
291
+ /**
292
+ * Load all service configurations at once
293
+ * @param {Object} options
294
+ * @param {string} [options.basePath] - Base path (defaults to process.cwd())
295
+ * @param {Object} options.env - Environment variables for ${VAR} placeholders
296
+ * @returns {Object} Combined configuration object
297
+ */
298
+ static loadAll(options = {}) {
299
+ const { basePath = process.cwd(), env } = options;
300
+
301
+ const serviceConfig = this.loadServiceConfig({ basePath, env });
304
302
  const operations = this.loadOperations(basePath);
305
303
  const validationProof = this.loadValidationProof(basePath);
306
304
  const nodeEnv = runtimeCfg.get('nodeEnv');
@@ -1582,7 +1582,11 @@ class ServiceWrapper {
1582
1582
  }
1583
1583
 
1584
1584
  // Build service URL for validation (HTTP calls to running server)
1585
- const serviceUrl = `http://localhost:${servicePort}`;
1585
+ // Fail-fast: do not guess hostnames (no localhost defaults). Service URL must be explicit in ServiceConfig.
1586
+ const serviceUrl = this.config.service?.url;
1587
+ if (!serviceUrl) {
1588
+ throw new Error('[ServiceWrapper] Missing configuration - config.service.url is required for validation (set SERVICE_URL in conn-config/config.json placeholder)');
1589
+ }
1586
1590
 
1587
1591
  this.logger?.info('[ServiceWrapper] Checking validation proof...');
1588
1592
 
package/src/index.js CHANGED
@@ -15,6 +15,7 @@ const path = require('path');
15
15
  const ServiceWrapper = require('./ServiceWrapper');
16
16
  const { ConfigLoader } = require('./ConfigLoader');
17
17
  const runtimeCfg = require('./config');
18
+ const pkg = require('../package.json');
18
19
 
19
20
  // Note: WorkflowProcessor and ApiCaller functionality has been moved to connectors:
20
21
  // - WorkflowProcessor -> @onlineapps/conn-orch-orchestrator
@@ -39,9 +40,6 @@ const runtimeCfg = require('./config');
39
40
  * require('@onlineapps/service-wrapper').bootstrap(__dirname);
40
41
  */
41
42
  async function bootstrap(serviceRoot, options = {}) {
42
- // Load dotenv first
43
- require('dotenv').config({ path: path.join(serviceRoot, '.env') });
44
-
45
43
  // Load app, config, and logger from service
46
44
  const app = options.app || require(path.join(serviceRoot, 'src', 'app'));
47
45
  const config = options.config || require(path.join(serviceRoot, 'src', 'config'));
@@ -118,4 +116,4 @@ module.exports.ServiceWrapper = ServiceWrapper;
118
116
  module.exports.ConfigLoader = ConfigLoader;
119
117
  module.exports.bootstrap = bootstrap;
120
118
  module.exports.default = ServiceWrapper;
121
- module.exports.VERSION = '2.1.11';
119
+ module.exports.VERSION = pkg.version;