@morojs/moro 1.5.17 → 1.6.1

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.
Files changed (138) hide show
  1. package/README.md +48 -65
  2. package/dist/core/auth/morojs-adapter.js +12 -16
  3. package/dist/core/auth/morojs-adapter.js.map +1 -1
  4. package/dist/core/config/file-loader.d.ts +5 -0
  5. package/dist/core/config/file-loader.js +171 -0
  6. package/dist/core/config/file-loader.js.map +1 -1
  7. package/dist/core/config/index.d.ts +10 -39
  8. package/dist/core/config/index.js +29 -66
  9. package/dist/core/config/index.js.map +1 -1
  10. package/dist/core/config/loader.d.ts +7 -0
  11. package/dist/core/config/loader.js +269 -0
  12. package/dist/core/config/loader.js.map +1 -0
  13. package/dist/core/config/schema.js +31 -41
  14. package/dist/core/config/schema.js.map +1 -1
  15. package/dist/core/config/utils.d.ts +2 -9
  16. package/dist/core/config/utils.js +32 -19
  17. package/dist/core/config/utils.js.map +1 -1
  18. package/dist/core/config/validation.d.ts +17 -0
  19. package/dist/core/config/validation.js +131 -0
  20. package/dist/core/config/validation.js.map +1 -0
  21. package/dist/core/database/adapters/mongodb.d.ts +0 -10
  22. package/dist/core/database/adapters/mongodb.js +2 -23
  23. package/dist/core/database/adapters/mongodb.js.map +1 -1
  24. package/dist/core/database/adapters/mysql.d.ts +0 -11
  25. package/dist/core/database/adapters/mysql.js +0 -1
  26. package/dist/core/database/adapters/mysql.js.map +1 -1
  27. package/dist/core/database/adapters/postgresql.d.ts +1 -9
  28. package/dist/core/database/adapters/postgresql.js +1 -1
  29. package/dist/core/database/adapters/postgresql.js.map +1 -1
  30. package/dist/core/database/adapters/redis.d.ts +0 -9
  31. package/dist/core/database/adapters/redis.js +4 -14
  32. package/dist/core/database/adapters/redis.js.map +1 -1
  33. package/dist/core/framework.d.ts +7 -6
  34. package/dist/core/framework.js +16 -131
  35. package/dist/core/framework.js.map +1 -1
  36. package/dist/core/http/http-server.d.ts +0 -12
  37. package/dist/core/http/http-server.js +23 -151
  38. package/dist/core/http/http-server.js.map +1 -1
  39. package/dist/core/http/router.d.ts +0 -12
  40. package/dist/core/http/router.js +36 -114
  41. package/dist/core/http/router.js.map +1 -1
  42. package/dist/core/logger/filters.js +4 -12
  43. package/dist/core/logger/filters.js.map +1 -1
  44. package/dist/core/logger/index.d.ts +1 -1
  45. package/dist/core/logger/index.js +1 -2
  46. package/dist/core/logger/index.js.map +1 -1
  47. package/dist/core/logger/logger.d.ts +13 -29
  48. package/dist/core/logger/logger.js +203 -380
  49. package/dist/core/logger/logger.js.map +1 -1
  50. package/dist/core/logger/outputs.js +2 -0
  51. package/dist/core/logger/outputs.js.map +1 -1
  52. package/dist/core/middleware/built-in/auth.js +17 -88
  53. package/dist/core/middleware/built-in/auth.js.map +1 -1
  54. package/dist/core/middleware/built-in/cache.js +1 -3
  55. package/dist/core/middleware/built-in/cache.js.map +1 -1
  56. package/dist/core/middleware/built-in/index.d.ts +0 -1
  57. package/dist/core/middleware/built-in/index.js +1 -6
  58. package/dist/core/middleware/built-in/index.js.map +1 -1
  59. package/dist/core/middleware/built-in/request-logger.js +2 -3
  60. package/dist/core/middleware/built-in/request-logger.js.map +1 -1
  61. package/dist/core/middleware/built-in/sse.js +7 -9
  62. package/dist/core/middleware/built-in/sse.js.map +1 -1
  63. package/dist/core/modules/auto-discovery.d.ts +0 -17
  64. package/dist/core/modules/auto-discovery.js +12 -367
  65. package/dist/core/modules/auto-discovery.js.map +1 -1
  66. package/dist/core/modules/modules.js +2 -12
  67. package/dist/core/modules/modules.js.map +1 -1
  68. package/dist/core/networking/adapters/ws-adapter.d.ts +1 -1
  69. package/dist/core/networking/adapters/ws-adapter.js +2 -2
  70. package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
  71. package/dist/core/networking/service-discovery.js +7 -7
  72. package/dist/core/networking/service-discovery.js.map +1 -1
  73. package/dist/core/routing/index.d.ts +0 -20
  74. package/dist/core/routing/index.js +13 -178
  75. package/dist/core/routing/index.js.map +1 -1
  76. package/dist/core/runtime/node-adapter.js +6 -12
  77. package/dist/core/runtime/node-adapter.js.map +1 -1
  78. package/dist/moro.d.ts +0 -48
  79. package/dist/moro.js +148 -456
  80. package/dist/moro.js.map +1 -1
  81. package/dist/types/config.d.ts +2 -58
  82. package/dist/types/core.d.ts +40 -34
  83. package/dist/types/http.d.ts +1 -16
  84. package/dist/types/logger.d.ts +0 -7
  85. package/dist/types/module.d.ts +0 -11
  86. package/package.json +2 -2
  87. package/src/core/auth/morojs-adapter.ts +13 -18
  88. package/src/core/config/file-loader.ts +233 -0
  89. package/src/core/config/index.ts +32 -77
  90. package/src/core/config/loader.ts +633 -0
  91. package/src/core/config/schema.ts +31 -41
  92. package/src/core/config/utils.ts +29 -22
  93. package/src/core/config/validation.ts +140 -0
  94. package/src/core/database/README.md +16 -26
  95. package/src/core/database/adapters/mongodb.ts +2 -30
  96. package/src/core/database/adapters/mysql.ts +0 -14
  97. package/src/core/database/adapters/postgresql.ts +2 -12
  98. package/src/core/database/adapters/redis.ts +4 -27
  99. package/src/core/framework.ts +23 -163
  100. package/src/core/http/http-server.ts +36 -176
  101. package/src/core/http/router.ts +38 -127
  102. package/src/core/logger/filters.ts +4 -12
  103. package/src/core/logger/index.ts +0 -1
  104. package/src/core/logger/logger.ts +216 -427
  105. package/src/core/logger/outputs.ts +2 -0
  106. package/src/core/middleware/built-in/auth.ts +17 -98
  107. package/src/core/middleware/built-in/cache.ts +1 -3
  108. package/src/core/middleware/built-in/index.ts +0 -8
  109. package/src/core/middleware/built-in/request-logger.ts +1 -3
  110. package/src/core/middleware/built-in/sse.ts +7 -9
  111. package/src/core/modules/auto-discovery.ts +13 -476
  112. package/src/core/modules/modules.ts +9 -20
  113. package/src/core/networking/adapters/ws-adapter.ts +5 -2
  114. package/src/core/networking/service-discovery.ts +7 -6
  115. package/src/core/routing/index.ts +14 -198
  116. package/src/core/runtime/node-adapter.ts +6 -12
  117. package/src/moro.ts +166 -554
  118. package/src/types/config.ts +2 -59
  119. package/src/types/core.ts +45 -47
  120. package/src/types/http.ts +1 -23
  121. package/src/types/logger.ts +0 -9
  122. package/src/types/module.ts +0 -12
  123. package/dist/core/config/config-manager.d.ts +0 -44
  124. package/dist/core/config/config-manager.js +0 -114
  125. package/dist/core/config/config-manager.js.map +0 -1
  126. package/dist/core/config/config-sources.d.ts +0 -21
  127. package/dist/core/config/config-sources.js +0 -502
  128. package/dist/core/config/config-sources.js.map +0 -1
  129. package/dist/core/config/config-validator.d.ts +0 -21
  130. package/dist/core/config/config-validator.js +0 -765
  131. package/dist/core/config/config-validator.js.map +0 -1
  132. package/dist/core/middleware/built-in/jwt-helpers.d.ts +0 -118
  133. package/dist/core/middleware/built-in/jwt-helpers.js +0 -221
  134. package/dist/core/middleware/built-in/jwt-helpers.js.map +0 -1
  135. package/src/core/config/config-manager.ts +0 -133
  136. package/src/core/config/config-sources.ts +0 -596
  137. package/src/core/config/config-validator.ts +0 -1078
  138. package/src/core/middleware/built-in/jwt-helpers.ts +0 -240
@@ -0,0 +1,633 @@
1
+ // Configuration Loader - Environment Variable Mapping and Validation
2
+ import { ConfigSchema, AppConfig, DEFAULT_CONFIG } from './schema';
3
+ import {
4
+ validatePort,
5
+ validateBoolean,
6
+ validateNumber,
7
+ validateString,
8
+ validateUrl,
9
+ validateEnum,
10
+ validateStringArray,
11
+ validateOptional,
12
+ coerceEnvValue,
13
+ ConfigValidationError,
14
+ } from './validation';
15
+ import { createFrameworkLogger } from '../logger';
16
+ import { loadConfigFileSync, applyConfigAsEnvironmentVariables } from './file-loader';
17
+
18
+ const logger = createFrameworkLogger('Config');
19
+
20
+ /**
21
+ * Load and validate configuration from config files and environment variables
22
+ * Priority: Environment Variables > Config File > Schema Defaults
23
+ * @returns Validated and typed application configuration
24
+ */
25
+ export function loadConfig(): AppConfig {
26
+ logger.debug('Loading configuration with TypeScript validation');
27
+
28
+ // First, try to load from config file and apply as environment variables
29
+ try {
30
+ const fileConfig = loadConfigFileSync();
31
+ if (fileConfig) {
32
+ logger.debug('Applying config file values as environment variables');
33
+ applyConfigAsEnvironmentVariables(fileConfig);
34
+ }
35
+ } catch (error) {
36
+ logger.warn(
37
+ 'Config file loading failed, continuing with environment variables only:',
38
+ String(error)
39
+ );
40
+ }
41
+
42
+ try {
43
+ // Build configuration using robust validation functions
44
+ const config: AppConfig = {
45
+ server: {
46
+ port: validatePort(
47
+ coerceEnvValue(process.env.PORT || process.env.MORO_PORT || '') ||
48
+ DEFAULT_CONFIG.server.port,
49
+ 'server.port'
50
+ ),
51
+ host: validateString(
52
+ process.env.HOST || process.env.MORO_HOST || DEFAULT_CONFIG.server.host,
53
+ 'server.host'
54
+ ),
55
+ environment: validateEnum(
56
+ process.env.NODE_ENV || process.env.MORO_ENV || DEFAULT_CONFIG.server.environment,
57
+ ['development', 'staging', 'production'] as const,
58
+ 'server.environment'
59
+ ),
60
+ maxConnections: validateNumber(
61
+ coerceEnvValue(process.env.MAX_CONNECTIONS || process.env.MORO_MAX_CONNECTIONS || '') ||
62
+ DEFAULT_CONFIG.server.maxConnections,
63
+ 'server.maxConnections',
64
+ { min: 1 }
65
+ ),
66
+ timeout: validateNumber(
67
+ coerceEnvValue(process.env.REQUEST_TIMEOUT || process.env.MORO_TIMEOUT || '') ||
68
+ DEFAULT_CONFIG.server.timeout,
69
+ 'server.timeout',
70
+ { min: 1000 }
71
+ ),
72
+ },
73
+
74
+ serviceDiscovery: {
75
+ enabled: validateBoolean(
76
+ coerceEnvValue(
77
+ process.env.SERVICE_DISCOVERY_ENABLED || process.env.MORO_SERVICE_DISCOVERY || ''
78
+ ) ?? DEFAULT_CONFIG.serviceDiscovery.enabled,
79
+ 'serviceDiscovery.enabled'
80
+ ),
81
+ type: validateEnum(
82
+ process.env.DISCOVERY_TYPE ||
83
+ process.env.MORO_DISCOVERY_TYPE ||
84
+ DEFAULT_CONFIG.serviceDiscovery.type,
85
+ ['memory', 'consul', 'kubernetes'] as const,
86
+ 'serviceDiscovery.type'
87
+ ),
88
+ consulUrl: validateUrl(
89
+ process.env.CONSUL_URL ||
90
+ process.env.MORO_CONSUL_URL ||
91
+ DEFAULT_CONFIG.serviceDiscovery.consulUrl,
92
+ 'serviceDiscovery.consulUrl'
93
+ ),
94
+ kubernetesNamespace: validateString(
95
+ process.env.KUBERNETES_NAMESPACE ||
96
+ process.env.MORO_KUBERNETES_NAMESPACE ||
97
+ DEFAULT_CONFIG.serviceDiscovery.kubernetesNamespace,
98
+ 'serviceDiscovery.kubernetesNamespace'
99
+ ),
100
+ healthCheckInterval: validateNumber(
101
+ coerceEnvValue(
102
+ process.env.HEALTH_CHECK_INTERVAL || process.env.MORO_HEALTH_CHECK_INTERVAL || ''
103
+ ) || DEFAULT_CONFIG.serviceDiscovery.healthCheckInterval,
104
+ 'serviceDiscovery.healthCheckInterval',
105
+ { min: 1000 }
106
+ ),
107
+ retryAttempts: validateNumber(
108
+ coerceEnvValue(process.env.RETRY_ATTEMPTS || process.env.MORO_RETRY_ATTEMPTS || '') ||
109
+ DEFAULT_CONFIG.serviceDiscovery.retryAttempts,
110
+ 'serviceDiscovery.retryAttempts',
111
+ { min: 0 }
112
+ ),
113
+ },
114
+
115
+ database: {
116
+ url: validateOptional(process.env.DATABASE_URL, validateString, 'database.url'),
117
+ redis: {
118
+ url: validateString(
119
+ process.env.REDIS_URL ||
120
+ process.env.MORO_REDIS_URL ||
121
+ DEFAULT_CONFIG.database.redis.url,
122
+ 'database.redis.url'
123
+ ),
124
+ maxRetries: validateNumber(
125
+ coerceEnvValue(process.env.REDIS_MAX_RETRIES || process.env.MORO_REDIS_RETRIES || '') ||
126
+ DEFAULT_CONFIG.database.redis.maxRetries,
127
+ 'database.redis.maxRetries',
128
+ { min: 0 }
129
+ ),
130
+ retryDelay: validateNumber(
131
+ coerceEnvValue(process.env.REDIS_RETRY_DELAY || process.env.MORO_REDIS_DELAY || '') ||
132
+ DEFAULT_CONFIG.database.redis.retryDelay,
133
+ 'database.redis.retryDelay',
134
+ { min: 100 }
135
+ ),
136
+ keyPrefix: validateString(
137
+ process.env.REDIS_KEY_PREFIX ||
138
+ process.env.MORO_REDIS_PREFIX ||
139
+ DEFAULT_CONFIG.database.redis.keyPrefix,
140
+ 'database.redis.keyPrefix'
141
+ ),
142
+ },
143
+ mysql: process.env.MYSQL_HOST
144
+ ? {
145
+ host: validateString(process.env.MYSQL_HOST, 'database.mysql.host'),
146
+ port: validatePort(
147
+ process.env.MYSQL_PORT || process.env.MORO_MYSQL_PORT || '3306',
148
+ 'database.mysql.port'
149
+ ),
150
+ database: validateOptional(
151
+ process.env.MYSQL_DATABASE || process.env.MORO_MYSQL_DB,
152
+ validateString,
153
+ 'database.mysql.database'
154
+ ),
155
+ username: validateOptional(
156
+ process.env.MYSQL_USERNAME || process.env.MORO_MYSQL_USER,
157
+ validateString,
158
+ 'database.mysql.username'
159
+ ),
160
+ password: validateOptional(
161
+ process.env.MYSQL_PASSWORD || process.env.MORO_MYSQL_PASS,
162
+ validateString,
163
+ 'database.mysql.password'
164
+ ),
165
+ connectionLimit: validateNumber(
166
+ coerceEnvValue(
167
+ process.env.MYSQL_CONNECTION_LIMIT || process.env.MORO_MYSQL_CONNECTIONS || ''
168
+ ) || 10,
169
+ 'database.mysql.connectionLimit',
170
+ { min: 1 }
171
+ ),
172
+ acquireTimeout: validateNumber(
173
+ coerceEnvValue(
174
+ process.env.MYSQL_ACQUIRE_TIMEOUT || process.env.MORO_MYSQL_ACQUIRE_TIMEOUT || ''
175
+ ) || 60000,
176
+ 'database.mysql.acquireTimeout',
177
+ { min: 1000 }
178
+ ),
179
+ timeout: validateNumber(
180
+ coerceEnvValue(process.env.MYSQL_TIMEOUT || process.env.MORO_MYSQL_TIMEOUT || '') ||
181
+ 60000,
182
+ 'database.mysql.timeout',
183
+ { min: 1000 }
184
+ ),
185
+ }
186
+ : undefined,
187
+ },
188
+
189
+ modules: {
190
+ cache: {
191
+ enabled: validateBoolean(
192
+ coerceEnvValue(process.env.CACHE_ENABLED || process.env.MORO_CACHE_ENABLED || '') ??
193
+ DEFAULT_CONFIG.modules.cache.enabled,
194
+ 'modules.cache.enabled'
195
+ ),
196
+ defaultTtl: validateNumber(
197
+ coerceEnvValue(process.env.CACHE_TTL || process.env.MORO_CACHE_TTL || '') ||
198
+ DEFAULT_CONFIG.modules.cache.defaultTtl,
199
+ 'modules.cache.defaultTtl',
200
+ { min: 0 }
201
+ ),
202
+ maxSize: validateNumber(
203
+ coerceEnvValue(process.env.CACHE_MAX_SIZE || process.env.MORO_CACHE_MAX_SIZE || '') ||
204
+ DEFAULT_CONFIG.modules.cache.maxSize,
205
+ 'modules.cache.maxSize',
206
+ { min: 1 }
207
+ ),
208
+ strategy: validateEnum(
209
+ process.env.CACHE_STRATEGY ||
210
+ process.env.MORO_CACHE_STRATEGY ||
211
+ DEFAULT_CONFIG.modules.cache.strategy,
212
+ ['lru', 'lfu', 'fifo'] as const,
213
+ 'modules.cache.strategy'
214
+ ),
215
+ },
216
+ rateLimit: {
217
+ enabled: validateBoolean(
218
+ coerceEnvValue(
219
+ process.env.RATE_LIMIT_ENABLED || process.env.MORO_RATE_LIMIT_ENABLED || ''
220
+ ) ?? DEFAULT_CONFIG.modules.rateLimit.enabled,
221
+ 'modules.rateLimit.enabled'
222
+ ),
223
+ defaultRequests: validateNumber(
224
+ coerceEnvValue(
225
+ process.env.RATE_LIMIT_REQUESTS || process.env.MORO_RATE_LIMIT_REQUESTS || ''
226
+ ) || DEFAULT_CONFIG.modules.rateLimit.defaultRequests,
227
+ 'modules.rateLimit.defaultRequests',
228
+ { min: 1 }
229
+ ),
230
+ defaultWindow: validateNumber(
231
+ coerceEnvValue(
232
+ process.env.RATE_LIMIT_WINDOW || process.env.MORO_RATE_LIMIT_WINDOW || ''
233
+ ) || DEFAULT_CONFIG.modules.rateLimit.defaultWindow,
234
+ 'modules.rateLimit.defaultWindow',
235
+ { min: 1000 }
236
+ ),
237
+ skipSuccessfulRequests: validateBoolean(
238
+ coerceEnvValue(
239
+ process.env.RATE_LIMIT_SKIP_SUCCESS || process.env.MORO_RATE_LIMIT_SKIP_SUCCESS || ''
240
+ ) ?? DEFAULT_CONFIG.modules.rateLimit.skipSuccessfulRequests,
241
+ 'modules.rateLimit.skipSuccessfulRequests'
242
+ ),
243
+ skipFailedRequests: validateBoolean(
244
+ coerceEnvValue(
245
+ process.env.RATE_LIMIT_SKIP_FAILED || process.env.MORO_RATE_LIMIT_SKIP_FAILED || ''
246
+ ) ?? DEFAULT_CONFIG.modules.rateLimit.skipFailedRequests,
247
+ 'modules.rateLimit.skipFailedRequests'
248
+ ),
249
+ },
250
+ validation: {
251
+ enabled: validateBoolean(
252
+ coerceEnvValue(
253
+ process.env.VALIDATION_ENABLED || process.env.MORO_VALIDATION_ENABLED || ''
254
+ ) ?? DEFAULT_CONFIG.modules.validation.enabled,
255
+ 'modules.validation.enabled'
256
+ ),
257
+ stripUnknown: validateBoolean(
258
+ coerceEnvValue(
259
+ process.env.VALIDATION_STRIP_UNKNOWN ||
260
+ process.env.MORO_VALIDATION_STRIP_UNKNOWN ||
261
+ ''
262
+ ) ?? DEFAULT_CONFIG.modules.validation.stripUnknown,
263
+ 'modules.validation.stripUnknown'
264
+ ),
265
+ abortEarly: validateBoolean(
266
+ coerceEnvValue(
267
+ process.env.VALIDATION_ABORT_EARLY || process.env.MORO_VALIDATION_ABORT_EARLY || ''
268
+ ) ?? DEFAULT_CONFIG.modules.validation.abortEarly,
269
+ 'modules.validation.abortEarly'
270
+ ),
271
+ },
272
+ },
273
+
274
+ logging: {
275
+ level: validateEnum(
276
+ process.env.LOG_LEVEL || process.env.MORO_LOG_LEVEL || DEFAULT_CONFIG.logging.level,
277
+ ['debug', 'info', 'warn', 'error', 'fatal'] as const,
278
+ 'logging.level'
279
+ ),
280
+ format: validateEnum(
281
+ process.env.LOG_FORMAT || process.env.MORO_LOG_FORMAT || DEFAULT_CONFIG.logging.format,
282
+ ['pretty', 'json', 'compact'] as const,
283
+ 'logging.format'
284
+ ),
285
+ enableColors: validateBoolean(
286
+ coerceEnvValue(process.env.LOG_COLORS || process.env.MORO_LOG_COLORS || '') ??
287
+ DEFAULT_CONFIG.logging.enableColors,
288
+ 'logging.enableColors'
289
+ ),
290
+ enableTimestamp: validateBoolean(
291
+ coerceEnvValue(process.env.LOG_TIMESTAMP || process.env.MORO_LOG_TIMESTAMP || '') ??
292
+ DEFAULT_CONFIG.logging.enableTimestamp,
293
+ 'logging.enableTimestamp'
294
+ ),
295
+ enableContext: validateBoolean(
296
+ coerceEnvValue(process.env.LOG_CONTEXT || process.env.MORO_LOG_CONTEXT || '') ??
297
+ DEFAULT_CONFIG.logging.enableContext,
298
+ 'logging.enableContext'
299
+ ),
300
+ outputs: {
301
+ console: validateBoolean(
302
+ coerceEnvValue(process.env.LOG_CONSOLE || process.env.MORO_LOG_CONSOLE || '') ??
303
+ DEFAULT_CONFIG.logging.outputs.console,
304
+ 'logging.outputs.console'
305
+ ),
306
+ file: {
307
+ enabled: validateBoolean(
308
+ coerceEnvValue(
309
+ process.env.LOG_FILE_ENABLED || process.env.MORO_LOG_FILE_ENABLED || ''
310
+ ) ?? DEFAULT_CONFIG.logging.outputs.file.enabled,
311
+ 'logging.outputs.file.enabled'
312
+ ),
313
+ path: validateString(
314
+ process.env.LOG_FILE_PATH ||
315
+ process.env.MORO_LOG_FILE_PATH ||
316
+ DEFAULT_CONFIG.logging.outputs.file.path,
317
+ 'logging.outputs.file.path'
318
+ ),
319
+ maxSize: validateString(
320
+ process.env.LOG_FILE_MAX_SIZE ||
321
+ process.env.MORO_LOG_FILE_MAX_SIZE ||
322
+ DEFAULT_CONFIG.logging.outputs.file.maxSize,
323
+ 'logging.outputs.file.maxSize'
324
+ ),
325
+ maxFiles: validateNumber(
326
+ coerceEnvValue(
327
+ process.env.LOG_FILE_MAX_FILES || process.env.MORO_LOG_FILE_MAX_FILES || ''
328
+ ) || DEFAULT_CONFIG.logging.outputs.file.maxFiles,
329
+ 'logging.outputs.file.maxFiles',
330
+ { min: 1 }
331
+ ),
332
+ },
333
+ webhook: {
334
+ enabled: validateBoolean(
335
+ coerceEnvValue(
336
+ process.env.LOG_WEBHOOK_ENABLED || process.env.MORO_LOG_WEBHOOK_ENABLED || ''
337
+ ) ?? DEFAULT_CONFIG.logging.outputs.webhook.enabled,
338
+ 'logging.outputs.webhook.enabled'
339
+ ),
340
+ url: validateOptional(
341
+ process.env.LOG_WEBHOOK_URL || process.env.MORO_LOG_WEBHOOK_URL,
342
+ validateUrl,
343
+ 'logging.outputs.webhook.url'
344
+ ),
345
+ headers: DEFAULT_CONFIG.logging.outputs.webhook.headers,
346
+ },
347
+ },
348
+ },
349
+
350
+ security: {
351
+ cors: {
352
+ enabled: validateBoolean(
353
+ coerceEnvValue(process.env.CORS_ENABLED || process.env.MORO_CORS_ENABLED || '') ??
354
+ DEFAULT_CONFIG.security.cors.enabled,
355
+ 'security.cors.enabled'
356
+ ),
357
+ origin:
358
+ process.env.CORS_ORIGIN ||
359
+ process.env.MORO_CORS_ORIGIN ||
360
+ DEFAULT_CONFIG.security.cors.origin,
361
+ methods: validateStringArray(
362
+ process.env.CORS_METHODS ||
363
+ process.env.MORO_CORS_METHODS ||
364
+ DEFAULT_CONFIG.security.cors.methods,
365
+ 'security.cors.methods'
366
+ ),
367
+ allowedHeaders: validateStringArray(
368
+ process.env.CORS_HEADERS ||
369
+ process.env.MORO_CORS_HEADERS ||
370
+ DEFAULT_CONFIG.security.cors.allowedHeaders,
371
+ 'security.cors.allowedHeaders'
372
+ ),
373
+ credentials: validateBoolean(
374
+ coerceEnvValue(
375
+ process.env.CORS_CREDENTIALS || process.env.MORO_CORS_CREDENTIALS || ''
376
+ ) ?? DEFAULT_CONFIG.security.cors.credentials,
377
+ 'security.cors.credentials'
378
+ ),
379
+ },
380
+ helmet: {
381
+ enabled: validateBoolean(
382
+ coerceEnvValue(process.env.HELMET_ENABLED || process.env.MORO_HELMET_ENABLED || '') ??
383
+ DEFAULT_CONFIG.security.helmet.enabled,
384
+ 'security.helmet.enabled'
385
+ ),
386
+ contentSecurityPolicy: validateBoolean(
387
+ coerceEnvValue(process.env.HELMET_CSP || process.env.MORO_HELMET_CSP || '') ??
388
+ DEFAULT_CONFIG.security.helmet.contentSecurityPolicy,
389
+ 'security.helmet.contentSecurityPolicy'
390
+ ),
391
+ hsts: validateBoolean(
392
+ coerceEnvValue(process.env.HELMET_HSTS || process.env.MORO_HELMET_HSTS || '') ??
393
+ DEFAULT_CONFIG.security.helmet.hsts,
394
+ 'security.helmet.hsts'
395
+ ),
396
+ noSniff: validateBoolean(
397
+ coerceEnvValue(process.env.HELMET_NO_SNIFF || process.env.MORO_HELMET_NO_SNIFF || '') ??
398
+ DEFAULT_CONFIG.security.helmet.noSniff,
399
+ 'security.helmet.noSniff'
400
+ ),
401
+ frameguard: validateBoolean(
402
+ coerceEnvValue(
403
+ process.env.HELMET_FRAMEGUARD || process.env.MORO_HELMET_FRAMEGUARD || ''
404
+ ) ?? DEFAULT_CONFIG.security.helmet.frameguard,
405
+ 'security.helmet.frameguard'
406
+ ),
407
+ },
408
+ rateLimit: {
409
+ global: {
410
+ enabled: validateBoolean(
411
+ coerceEnvValue(
412
+ process.env.GLOBAL_RATE_LIMIT_ENABLED ||
413
+ process.env.MORO_GLOBAL_RATE_LIMIT_ENABLED ||
414
+ ''
415
+ ) ?? DEFAULT_CONFIG.security.rateLimit.global.enabled,
416
+ 'security.rateLimit.global.enabled'
417
+ ),
418
+ requests: validateNumber(
419
+ coerceEnvValue(
420
+ process.env.GLOBAL_RATE_LIMIT_REQUESTS ||
421
+ process.env.MORO_GLOBAL_RATE_LIMIT_REQUESTS ||
422
+ ''
423
+ ) || DEFAULT_CONFIG.security.rateLimit.global.requests,
424
+ 'security.rateLimit.global.requests',
425
+ { min: 1 }
426
+ ),
427
+ window: validateNumber(
428
+ coerceEnvValue(
429
+ process.env.GLOBAL_RATE_LIMIT_WINDOW ||
430
+ process.env.MORO_GLOBAL_RATE_LIMIT_WINDOW ||
431
+ ''
432
+ ) || DEFAULT_CONFIG.security.rateLimit.global.window,
433
+ 'security.rateLimit.global.window',
434
+ { min: 1000 }
435
+ ),
436
+ },
437
+ },
438
+ },
439
+
440
+ external: {
441
+ stripe: {
442
+ secretKey: validateOptional(
443
+ process.env.STRIPE_SECRET_KEY,
444
+ validateString,
445
+ 'external.stripe.secretKey'
446
+ ),
447
+ publishableKey: validateOptional(
448
+ process.env.STRIPE_PUBLISHABLE_KEY,
449
+ validateString,
450
+ 'external.stripe.publishableKey'
451
+ ),
452
+ webhookSecret: validateOptional(
453
+ process.env.STRIPE_WEBHOOK_SECRET,
454
+ validateString,
455
+ 'external.stripe.webhookSecret'
456
+ ),
457
+ apiVersion: validateString(
458
+ process.env.STRIPE_API_VERSION ||
459
+ DEFAULT_CONFIG.external.stripe?.apiVersion ||
460
+ '2023-10-16',
461
+ 'external.stripe.apiVersion'
462
+ ),
463
+ },
464
+ paypal: {
465
+ clientId: validateOptional(
466
+ process.env.PAYPAL_CLIENT_ID,
467
+ validateString,
468
+ 'external.paypal.clientId'
469
+ ),
470
+ clientSecret: validateOptional(
471
+ process.env.PAYPAL_CLIENT_SECRET,
472
+ validateString,
473
+ 'external.paypal.clientSecret'
474
+ ),
475
+ webhookId: validateOptional(
476
+ process.env.PAYPAL_WEBHOOK_ID,
477
+ validateString,
478
+ 'external.paypal.webhookId'
479
+ ),
480
+ environment: validateEnum(
481
+ process.env.PAYPAL_ENVIRONMENT ||
482
+ DEFAULT_CONFIG.external.paypal?.environment ||
483
+ 'sandbox',
484
+ ['sandbox', 'production'] as const,
485
+ 'external.paypal.environment'
486
+ ),
487
+ },
488
+ smtp: {
489
+ host: validateOptional(process.env.SMTP_HOST, validateString, 'external.smtp.host'),
490
+ port: validatePort(
491
+ process.env.SMTP_PORT || (DEFAULT_CONFIG.external.smtp?.port || 587).toString(),
492
+ 'external.smtp.port'
493
+ ),
494
+ secure: validateBoolean(
495
+ coerceEnvValue(process.env.SMTP_SECURE || '') ??
496
+ (DEFAULT_CONFIG.external.smtp?.secure || false),
497
+ 'external.smtp.secure'
498
+ ),
499
+ username: validateOptional(
500
+ process.env.SMTP_USERNAME,
501
+ validateString,
502
+ 'external.smtp.username'
503
+ ),
504
+ password: validateOptional(
505
+ process.env.SMTP_PASSWORD,
506
+ validateString,
507
+ 'external.smtp.password'
508
+ ),
509
+ },
510
+ },
511
+
512
+ performance: {
513
+ compression: {
514
+ enabled: validateBoolean(
515
+ coerceEnvValue(
516
+ process.env.COMPRESSION_ENABLED || process.env.MORO_COMPRESSION_ENABLED || ''
517
+ ) ?? DEFAULT_CONFIG.performance.compression.enabled,
518
+ 'performance.compression.enabled'
519
+ ),
520
+ level: validateNumber(
521
+ coerceEnvValue(
522
+ process.env.COMPRESSION_LEVEL || process.env.MORO_COMPRESSION_LEVEL || ''
523
+ ) || DEFAULT_CONFIG.performance.compression.level,
524
+ 'performance.compression.level',
525
+ { min: 1, max: 9 }
526
+ ),
527
+ threshold: validateNumber(
528
+ coerceEnvValue(
529
+ process.env.COMPRESSION_THRESHOLD || process.env.MORO_COMPRESSION_THRESHOLD || ''
530
+ ) || DEFAULT_CONFIG.performance.compression.threshold,
531
+ 'performance.compression.threshold',
532
+ { min: 0 }
533
+ ),
534
+ },
535
+ circuitBreaker: {
536
+ enabled: validateBoolean(
537
+ coerceEnvValue(
538
+ process.env.CIRCUIT_BREAKER_ENABLED || process.env.MORO_CIRCUIT_BREAKER_ENABLED || ''
539
+ ) ?? DEFAULT_CONFIG.performance.circuitBreaker.enabled,
540
+ 'performance.circuitBreaker.enabled'
541
+ ),
542
+ failureThreshold: validateNumber(
543
+ coerceEnvValue(
544
+ process.env.CIRCUIT_BREAKER_THRESHOLD ||
545
+ process.env.MORO_CIRCUIT_BREAKER_THRESHOLD ||
546
+ ''
547
+ ) || DEFAULT_CONFIG.performance.circuitBreaker.failureThreshold,
548
+ 'performance.circuitBreaker.failureThreshold',
549
+ { min: 1 }
550
+ ),
551
+ resetTimeout: validateNumber(
552
+ coerceEnvValue(
553
+ process.env.CIRCUIT_BREAKER_RESET || process.env.MORO_CIRCUIT_BREAKER_RESET || ''
554
+ ) || DEFAULT_CONFIG.performance.circuitBreaker.resetTimeout,
555
+ 'performance.circuitBreaker.resetTimeout',
556
+ { min: 1000 }
557
+ ),
558
+ monitoringPeriod: validateNumber(
559
+ coerceEnvValue(
560
+ process.env.CIRCUIT_BREAKER_MONITOR || process.env.MORO_CIRCUIT_BREAKER_MONITOR || ''
561
+ ) || DEFAULT_CONFIG.performance.circuitBreaker.monitoringPeriod,
562
+ 'performance.circuitBreaker.monitoringPeriod',
563
+ { min: 1000 }
564
+ ),
565
+ },
566
+ clustering: {
567
+ enabled: validateBoolean(
568
+ coerceEnvValue(
569
+ process.env.CLUSTERING_ENABLED || process.env.MORO_CLUSTERING_ENABLED || ''
570
+ ) ?? DEFAULT_CONFIG.performance.clustering.enabled,
571
+ 'performance.clustering.enabled'
572
+ ),
573
+ workers:
574
+ process.env.CLUSTER_WORKERS === 'auto' || process.env.MORO_CLUSTER_WORKERS === 'auto'
575
+ ? 'auto'
576
+ : validateNumber(
577
+ coerceEnvValue(
578
+ process.env.CLUSTER_WORKERS || process.env.MORO_CLUSTER_WORKERS || ''
579
+ ) || DEFAULT_CONFIG.performance.clustering.workers,
580
+ 'performance.clustering.workers',
581
+ { min: 1 }
582
+ ),
583
+ },
584
+ },
585
+ };
586
+
587
+ logger.info('Configuration loaded and validated successfully with TypeScript');
588
+ logger.debug(
589
+ 'Configuration summary:',
590
+ JSON.stringify({
591
+ server: { port: config.server.port, environment: config.server.environment },
592
+ serviceDiscovery: {
593
+ enabled: config.serviceDiscovery.enabled,
594
+ type: config.serviceDiscovery.type,
595
+ },
596
+ modules: {
597
+ cache: config.modules.cache.enabled,
598
+ rateLimit: config.modules.rateLimit.enabled,
599
+ validation: config.modules.validation.enabled,
600
+ },
601
+ })
602
+ );
603
+
604
+ return config;
605
+ } catch (error) {
606
+ logger.error('❌ Configuration validation failed');
607
+
608
+ if (error instanceof ConfigValidationError) {
609
+ logger.error(`Configuration error in '${error.field}': ${error.message}`);
610
+ logger.error(` Value: ${JSON.stringify(error.value)}`);
611
+
612
+ // Provide helpful hints
613
+ if (error.field.includes('port')) {
614
+ logger.error(' Hint: Ports must be numbers between 1 and 65535');
615
+ }
616
+ if (error.field.includes('url')) {
617
+ logger.error(' Hint: URLs must include protocol (http:// or https://)');
618
+ }
619
+ if (error.field.includes('environment')) {
620
+ logger.error(' Hint: NODE_ENV must be one of: development, staging, production');
621
+ }
622
+ } else {
623
+ logger.error('Unexpected configuration error:', String(error));
624
+ }
625
+
626
+ logger.error('\nConfiguration Help:');
627
+ logger.error(' - Use MORO_* prefixed environment variables for framework-specific config');
628
+ logger.error(' - Check .env.example for available configuration options');
629
+ logger.error(' - See documentation for detailed configuration guide');
630
+
631
+ process.exit(1);
632
+ }
633
+ }