@morojs/moro 1.3.0 → 1.4.0

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 (70) hide show
  1. package/README.md +61 -7
  2. package/dist/core/config/types.d.ts +147 -0
  3. package/dist/core/config/types.js +124 -0
  4. package/dist/core/config/types.js.map +1 -0
  5. package/dist/core/config/typescript-loader.d.ts +6 -0
  6. package/dist/core/config/typescript-loader.js +268 -0
  7. package/dist/core/config/typescript-loader.js.map +1 -0
  8. package/dist/core/config/validation.d.ts +18 -0
  9. package/dist/core/config/validation.js +134 -0
  10. package/dist/core/config/validation.js.map +1 -0
  11. package/dist/core/docs/openapi-generator.js +6 -6
  12. package/dist/core/docs/openapi-generator.js.map +1 -1
  13. package/dist/core/docs/schema-to-openapi.d.ts +7 -0
  14. package/dist/core/docs/schema-to-openapi.js +124 -0
  15. package/dist/core/docs/schema-to-openapi.js.map +1 -0
  16. package/dist/core/docs/zod-to-openapi.d.ts +2 -0
  17. package/dist/core/docs/zod-to-openapi.js.map +1 -1
  18. package/dist/core/framework.d.ts +29 -6
  19. package/dist/core/framework.js +117 -18
  20. package/dist/core/framework.js.map +1 -1
  21. package/dist/core/networking/adapters/index.d.ts +3 -0
  22. package/dist/core/networking/adapters/index.js +10 -0
  23. package/dist/core/networking/adapters/index.js.map +1 -0
  24. package/dist/core/networking/adapters/socketio-adapter.d.ts +16 -0
  25. package/dist/core/networking/adapters/socketio-adapter.js +244 -0
  26. package/dist/core/networking/adapters/socketio-adapter.js.map +1 -0
  27. package/dist/core/networking/adapters/ws-adapter.d.ts +54 -0
  28. package/dist/core/networking/adapters/ws-adapter.js +383 -0
  29. package/dist/core/networking/adapters/ws-adapter.js.map +1 -0
  30. package/dist/core/networking/websocket-adapter.d.ts +171 -0
  31. package/dist/core/networking/websocket-adapter.js +5 -0
  32. package/dist/core/networking/websocket-adapter.js.map +1 -0
  33. package/dist/core/networking/websocket-manager.d.ts +53 -17
  34. package/dist/core/networking/websocket-manager.js +166 -108
  35. package/dist/core/networking/websocket-manager.js.map +1 -1
  36. package/dist/core/routing/index.d.ts +13 -13
  37. package/dist/core/routing/index.js.map +1 -1
  38. package/dist/core/validation/adapters.d.ts +51 -0
  39. package/dist/core/validation/adapters.js +135 -0
  40. package/dist/core/validation/adapters.js.map +1 -0
  41. package/dist/core/validation/index.d.ts +14 -11
  42. package/dist/core/validation/index.js +37 -26
  43. package/dist/core/validation/index.js.map +1 -1
  44. package/dist/core/validation/schema-interface.d.ts +36 -0
  45. package/dist/core/validation/schema-interface.js +68 -0
  46. package/dist/core/validation/schema-interface.js.map +1 -0
  47. package/dist/index.d.ts +6 -1
  48. package/dist/index.js +14 -3
  49. package/dist/index.js.map +1 -1
  50. package/dist/moro.js +8 -2
  51. package/dist/moro.js.map +1 -1
  52. package/package.json +31 -7
  53. package/src/core/config/types.ts +277 -0
  54. package/src/core/config/typescript-loader.ts +571 -0
  55. package/src/core/config/validation.ts +145 -0
  56. package/src/core/docs/openapi-generator.ts +7 -6
  57. package/src/core/docs/schema-to-openapi.ts +148 -0
  58. package/src/core/docs/zod-to-openapi.ts +2 -0
  59. package/src/core/framework.ts +121 -28
  60. package/src/core/networking/adapters/index.ts +16 -0
  61. package/src/core/networking/adapters/socketio-adapter.ts +252 -0
  62. package/src/core/networking/adapters/ws-adapter.ts +425 -0
  63. package/src/core/networking/websocket-adapter.ts +217 -0
  64. package/src/core/networking/websocket-manager.ts +185 -127
  65. package/src/core/routing/index.ts +13 -13
  66. package/src/core/validation/adapters.ts +147 -0
  67. package/src/core/validation/index.ts +60 -38
  68. package/src/core/validation/schema-interface.ts +100 -0
  69. package/src/index.ts +25 -2
  70. package/src/moro.ts +11 -2
@@ -0,0 +1,571 @@
1
+ // TypeScript-based Configuration Loader
2
+ // Replaces Zod-based configuration system with pure TypeScript + validation
3
+
4
+ import { AppConfig, DEFAULT_CONFIG } from './types';
5
+ import {
6
+ validatePort,
7
+ validateBoolean,
8
+ validateNumber,
9
+ validateString,
10
+ validateUrl,
11
+ validateEnum,
12
+ validateStringArray,
13
+ validateOptional,
14
+ coerceEnvValue,
15
+ ConfigValidationError,
16
+ } from './validation';
17
+ import { createFrameworkLogger } from '../logger';
18
+ import { loadConfigFileSync, applyConfigAsEnvironmentVariables } from './file-loader';
19
+
20
+ const logger = createFrameworkLogger('TypeScriptConfig');
21
+
22
+ /**
23
+ * Load and validate configuration using TypeScript + simple validation
24
+ * No Zod dependency required!
25
+ */
26
+ export function loadConfigWithTypeScript(): AppConfig {
27
+ logger.debug('Loading configuration with TypeScript validation');
28
+
29
+ // Load config file and apply as environment variables
30
+ try {
31
+ const fileConfig = loadConfigFileSync();
32
+ if (fileConfig) {
33
+ logger.debug('Applying config file values as environment variables');
34
+ applyConfigAsEnvironmentVariables(fileConfig);
35
+ }
36
+ } catch (error) {
37
+ logger.warn(
38
+ 'Config file loading failed, continuing with environment variables only:',
39
+ String(error)
40
+ );
41
+ }
42
+
43
+ try {
44
+ // Build configuration from environment variables with type-safe validation
45
+ const config: AppConfig = {
46
+ server: {
47
+ port: validatePort(
48
+ coerceEnvValue(process.env.MORO_SERVER_PORT || '') || DEFAULT_CONFIG.server.port,
49
+ 'server.port'
50
+ ),
51
+ host: validateString(
52
+ process.env.MORO_SERVER_HOST || DEFAULT_CONFIG.server.host,
53
+ 'server.host'
54
+ ),
55
+ environment: validateEnum(
56
+ process.env.NODE_ENV || DEFAULT_CONFIG.server.environment,
57
+ ['development', 'staging', 'production'] as const,
58
+ 'server.environment'
59
+ ),
60
+ maxConnections: validateNumber(
61
+ coerceEnvValue(process.env.MORO_SERVER_MAX_CONNECTIONS || '') ||
62
+ DEFAULT_CONFIG.server.maxConnections,
63
+ 'server.maxConnections',
64
+ { min: 1 }
65
+ ),
66
+ timeout: validateNumber(
67
+ coerceEnvValue(process.env.MORO_SERVER_TIMEOUT || '') || DEFAULT_CONFIG.server.timeout,
68
+ 'server.timeout',
69
+ { min: 1000 }
70
+ ),
71
+ },
72
+
73
+ serviceDiscovery: {
74
+ enabled: validateBoolean(
75
+ coerceEnvValue(process.env.MORO_SERVICE_DISCOVERY_ENABLED || '') ??
76
+ DEFAULT_CONFIG.serviceDiscovery.enabled,
77
+ 'serviceDiscovery.enabled'
78
+ ),
79
+ type: validateEnum(
80
+ process.env.MORO_SERVICE_DISCOVERY_TYPE || DEFAULT_CONFIG.serviceDiscovery.type,
81
+ ['memory', 'consul', 'kubernetes'] as const,
82
+ 'serviceDiscovery.type'
83
+ ),
84
+ consulUrl: validateUrl(
85
+ process.env.MORO_SERVICE_DISCOVERY_CONSUL_URL ||
86
+ DEFAULT_CONFIG.serviceDiscovery.consulUrl,
87
+ 'serviceDiscovery.consulUrl'
88
+ ),
89
+ kubernetesNamespace: validateString(
90
+ process.env.MORO_SERVICE_DISCOVERY_KUBERNETES_NAMESPACE ||
91
+ DEFAULT_CONFIG.serviceDiscovery.kubernetesNamespace,
92
+ 'serviceDiscovery.kubernetesNamespace'
93
+ ),
94
+ healthCheckInterval: validateNumber(
95
+ coerceEnvValue(process.env.MORO_SERVICE_DISCOVERY_HEALTH_CHECK_INTERVAL || '') ||
96
+ DEFAULT_CONFIG.serviceDiscovery.healthCheckInterval,
97
+ 'serviceDiscovery.healthCheckInterval',
98
+ { min: 1000 }
99
+ ),
100
+ retryAttempts: validateNumber(
101
+ coerceEnvValue(process.env.MORO_SERVICE_DISCOVERY_RETRY_ATTEMPTS || '') ||
102
+ DEFAULT_CONFIG.serviceDiscovery.retryAttempts,
103
+ 'serviceDiscovery.retryAttempts',
104
+ { min: 0 }
105
+ ),
106
+ },
107
+
108
+ database: {
109
+ url: validateOptional(process.env.DATABASE_URL, validateString, 'database.url'),
110
+ redis: {
111
+ url: validateString(
112
+ process.env.REDIS_URL || DEFAULT_CONFIG.database.redis.url,
113
+ 'database.redis.url'
114
+ ),
115
+ maxRetries: validateNumber(
116
+ coerceEnvValue(process.env.REDIS_MAX_RETRIES || '') ||
117
+ DEFAULT_CONFIG.database.redis.maxRetries,
118
+ 'database.redis.maxRetries',
119
+ { min: 0 }
120
+ ),
121
+ retryDelay: validateNumber(
122
+ coerceEnvValue(process.env.REDIS_RETRY_DELAY || '') ||
123
+ DEFAULT_CONFIG.database.redis.retryDelay,
124
+ 'database.redis.retryDelay',
125
+ { min: 100 }
126
+ ),
127
+ keyPrefix: validateString(
128
+ process.env.REDIS_KEY_PREFIX || DEFAULT_CONFIG.database.redis.keyPrefix,
129
+ 'database.redis.keyPrefix'
130
+ ),
131
+ },
132
+ mysql: process.env.MYSQL_HOST
133
+ ? {
134
+ host: validateString(process.env.MYSQL_HOST, 'database.mysql.host'),
135
+ port: validatePort(process.env.MYSQL_PORT || '3306', 'database.mysql.port'),
136
+ database: validateOptional(
137
+ process.env.MYSQL_DATABASE,
138
+ validateString,
139
+ 'database.mysql.database'
140
+ ),
141
+ username: validateOptional(
142
+ process.env.MYSQL_USERNAME,
143
+ validateString,
144
+ 'database.mysql.username'
145
+ ),
146
+ password: validateOptional(
147
+ process.env.MYSQL_PASSWORD,
148
+ validateString,
149
+ 'database.mysql.password'
150
+ ),
151
+ connectionLimit: validateNumber(
152
+ coerceEnvValue(process.env.MYSQL_CONNECTION_LIMIT || '') || 10,
153
+ 'database.mysql.connectionLimit',
154
+ { min: 1 }
155
+ ),
156
+ acquireTimeout: validateNumber(
157
+ coerceEnvValue(process.env.MYSQL_ACQUIRE_TIMEOUT || '') || 60000,
158
+ 'database.mysql.acquireTimeout',
159
+ { min: 1000 }
160
+ ),
161
+ timeout: validateNumber(
162
+ coerceEnvValue(process.env.MYSQL_TIMEOUT || '') || 60000,
163
+ 'database.mysql.timeout',
164
+ { min: 1000 }
165
+ ),
166
+ }
167
+ : undefined,
168
+ },
169
+
170
+ modules: {
171
+ cache: {
172
+ enabled: validateBoolean(
173
+ coerceEnvValue(process.env.MORO_MODULES_CACHE_ENABLED || '') ??
174
+ DEFAULT_CONFIG.modules.cache.enabled,
175
+ 'modules.cache.enabled'
176
+ ),
177
+ defaultTtl: validateNumber(
178
+ coerceEnvValue(process.env.MORO_MODULES_CACHE_DEFAULT_TTL || '') ||
179
+ DEFAULT_CONFIG.modules.cache.defaultTtl,
180
+ 'modules.cache.defaultTtl',
181
+ { min: 0 }
182
+ ),
183
+ maxSize: validateNumber(
184
+ coerceEnvValue(process.env.MORO_MODULES_CACHE_MAX_SIZE || '') ||
185
+ DEFAULT_CONFIG.modules.cache.maxSize,
186
+ 'modules.cache.maxSize',
187
+ { min: 1 }
188
+ ),
189
+ strategy: validateEnum(
190
+ process.env.MORO_MODULES_CACHE_STRATEGY || DEFAULT_CONFIG.modules.cache.strategy,
191
+ ['lru', 'lfu', 'fifo'] as const,
192
+ 'modules.cache.strategy'
193
+ ),
194
+ },
195
+ rateLimit: {
196
+ enabled: validateBoolean(
197
+ coerceEnvValue(process.env.MORO_MODULES_RATE_LIMIT_ENABLED || '') ??
198
+ DEFAULT_CONFIG.modules.rateLimit.enabled,
199
+ 'modules.rateLimit.enabled'
200
+ ),
201
+ defaultRequests: validateNumber(
202
+ coerceEnvValue(process.env.MORO_MODULES_RATE_LIMIT_DEFAULT_REQUESTS || '') ||
203
+ DEFAULT_CONFIG.modules.rateLimit.defaultRequests,
204
+ 'modules.rateLimit.defaultRequests',
205
+ { min: 1 }
206
+ ),
207
+ defaultWindow: validateNumber(
208
+ coerceEnvValue(process.env.MORO_MODULES_RATE_LIMIT_DEFAULT_WINDOW || '') ||
209
+ DEFAULT_CONFIG.modules.rateLimit.defaultWindow,
210
+ 'modules.rateLimit.defaultWindow',
211
+ { min: 1000 }
212
+ ),
213
+ skipSuccessfulRequests: validateBoolean(
214
+ coerceEnvValue(process.env.MORO_MODULES_RATE_LIMIT_SKIP_SUCCESSFUL_REQUESTS || '') ??
215
+ DEFAULT_CONFIG.modules.rateLimit.skipSuccessfulRequests,
216
+ 'modules.rateLimit.skipSuccessfulRequests'
217
+ ),
218
+ skipFailedRequests: validateBoolean(
219
+ coerceEnvValue(process.env.MORO_MODULES_RATE_LIMIT_SKIP_FAILED_REQUESTS || '') ??
220
+ DEFAULT_CONFIG.modules.rateLimit.skipFailedRequests,
221
+ 'modules.rateLimit.skipFailedRequests'
222
+ ),
223
+ },
224
+ validation: {
225
+ enabled: validateBoolean(
226
+ coerceEnvValue(process.env.MORO_MODULES_VALIDATION_ENABLED || '') ??
227
+ DEFAULT_CONFIG.modules.validation.enabled,
228
+ 'modules.validation.enabled'
229
+ ),
230
+ stripUnknown: validateBoolean(
231
+ coerceEnvValue(process.env.MORO_MODULES_VALIDATION_STRIP_UNKNOWN || '') ??
232
+ DEFAULT_CONFIG.modules.validation.stripUnknown,
233
+ 'modules.validation.stripUnknown'
234
+ ),
235
+ abortEarly: validateBoolean(
236
+ coerceEnvValue(process.env.MORO_MODULES_VALIDATION_ABORT_EARLY || '') ??
237
+ DEFAULT_CONFIG.modules.validation.abortEarly,
238
+ 'modules.validation.abortEarly'
239
+ ),
240
+ },
241
+ },
242
+
243
+ logging: {
244
+ level: validateEnum(
245
+ process.env.MORO_LOGGING_LEVEL || DEFAULT_CONFIG.logging.level,
246
+ ['debug', 'info', 'warn', 'error', 'fatal'] as const,
247
+ 'logging.level'
248
+ ),
249
+ format: validateEnum(
250
+ process.env.MORO_LOGGING_FORMAT || DEFAULT_CONFIG.logging.format,
251
+ ['pretty', 'json', 'compact'] as const,
252
+ 'logging.format'
253
+ ),
254
+ enableColors: validateBoolean(
255
+ coerceEnvValue(process.env.MORO_LOGGING_ENABLE_COLORS || '') ??
256
+ DEFAULT_CONFIG.logging.enableColors,
257
+ 'logging.enableColors'
258
+ ),
259
+ enableTimestamp: validateBoolean(
260
+ coerceEnvValue(process.env.MORO_LOGGING_ENABLE_TIMESTAMP || '') ??
261
+ DEFAULT_CONFIG.logging.enableTimestamp,
262
+ 'logging.enableTimestamp'
263
+ ),
264
+ enableContext: validateBoolean(
265
+ coerceEnvValue(process.env.MORO_LOGGING_ENABLE_CONTEXT || '') ??
266
+ DEFAULT_CONFIG.logging.enableContext,
267
+ 'logging.enableContext'
268
+ ),
269
+ outputs: {
270
+ console: validateBoolean(
271
+ coerceEnvValue(process.env.MORO_LOGGING_OUTPUTS_CONSOLE || '') ??
272
+ DEFAULT_CONFIG.logging.outputs.console,
273
+ 'logging.outputs.console'
274
+ ),
275
+ file: {
276
+ enabled: validateBoolean(
277
+ coerceEnvValue(process.env.MORO_LOGGING_OUTPUTS_FILE_ENABLED || '') ??
278
+ DEFAULT_CONFIG.logging.outputs.file.enabled,
279
+ 'logging.outputs.file.enabled'
280
+ ),
281
+ path: validateString(
282
+ process.env.MORO_LOGGING_OUTPUTS_FILE_PATH ||
283
+ DEFAULT_CONFIG.logging.outputs.file.path,
284
+ 'logging.outputs.file.path'
285
+ ),
286
+ maxSize: validateString(
287
+ process.env.MORO_LOGGING_OUTPUTS_FILE_MAX_SIZE ||
288
+ DEFAULT_CONFIG.logging.outputs.file.maxSize,
289
+ 'logging.outputs.file.maxSize'
290
+ ),
291
+ maxFiles: validateNumber(
292
+ coerceEnvValue(process.env.MORO_LOGGING_OUTPUTS_FILE_MAX_FILES || '') ||
293
+ DEFAULT_CONFIG.logging.outputs.file.maxFiles,
294
+ 'logging.outputs.file.maxFiles',
295
+ { min: 1 }
296
+ ),
297
+ },
298
+ webhook: {
299
+ enabled: validateBoolean(
300
+ coerceEnvValue(process.env.MORO_LOGGING_OUTPUTS_WEBHOOK_ENABLED || '') ??
301
+ DEFAULT_CONFIG.logging.outputs.webhook.enabled,
302
+ 'logging.outputs.webhook.enabled'
303
+ ),
304
+ url: validateOptional(
305
+ process.env.MORO_LOGGING_OUTPUTS_WEBHOOK_URL,
306
+ validateUrl,
307
+ 'logging.outputs.webhook.url'
308
+ ),
309
+ headers: DEFAULT_CONFIG.logging.outputs.webhook.headers,
310
+ },
311
+ },
312
+ },
313
+
314
+ security: {
315
+ cors: {
316
+ enabled: validateBoolean(
317
+ coerceEnvValue(process.env.MORO_SECURITY_CORS_ENABLED || '') ??
318
+ DEFAULT_CONFIG.security.cors.enabled,
319
+ 'security.cors.enabled'
320
+ ),
321
+ origin: process.env.MORO_SECURITY_CORS_ORIGIN || DEFAULT_CONFIG.security.cors.origin,
322
+ methods: validateStringArray(
323
+ process.env.MORO_SECURITY_CORS_METHODS || DEFAULT_CONFIG.security.cors.methods,
324
+ 'security.cors.methods'
325
+ ),
326
+ allowedHeaders: validateStringArray(
327
+ process.env.MORO_SECURITY_CORS_ALLOWED_HEADERS ||
328
+ DEFAULT_CONFIG.security.cors.allowedHeaders,
329
+ 'security.cors.allowedHeaders'
330
+ ),
331
+ credentials: validateBoolean(
332
+ coerceEnvValue(process.env.MORO_SECURITY_CORS_CREDENTIALS || '') ??
333
+ DEFAULT_CONFIG.security.cors.credentials,
334
+ 'security.cors.credentials'
335
+ ),
336
+ },
337
+ helmet: {
338
+ enabled: validateBoolean(
339
+ coerceEnvValue(process.env.MORO_SECURITY_HELMET_ENABLED || '') ??
340
+ DEFAULT_CONFIG.security.helmet.enabled,
341
+ 'security.helmet.enabled'
342
+ ),
343
+ contentSecurityPolicy: validateBoolean(
344
+ coerceEnvValue(process.env.MORO_SECURITY_HELMET_CSP || '') ??
345
+ DEFAULT_CONFIG.security.helmet.contentSecurityPolicy,
346
+ 'security.helmet.contentSecurityPolicy'
347
+ ),
348
+ hsts: validateBoolean(
349
+ coerceEnvValue(process.env.MORO_SECURITY_HELMET_HSTS || '') ??
350
+ DEFAULT_CONFIG.security.helmet.hsts,
351
+ 'security.helmet.hsts'
352
+ ),
353
+ noSniff: validateBoolean(
354
+ coerceEnvValue(process.env.MORO_SECURITY_HELMET_NO_SNIFF || '') ??
355
+ DEFAULT_CONFIG.security.helmet.noSniff,
356
+ 'security.helmet.noSniff'
357
+ ),
358
+ frameguard: validateBoolean(
359
+ coerceEnvValue(process.env.MORO_SECURITY_HELMET_FRAMEGUARD || '') ??
360
+ DEFAULT_CONFIG.security.helmet.frameguard,
361
+ 'security.helmet.frameguard'
362
+ ),
363
+ },
364
+ rateLimit: {
365
+ global: {
366
+ enabled: validateBoolean(
367
+ coerceEnvValue(process.env.MORO_SECURITY_RATE_LIMIT_GLOBAL_ENABLED || '') ??
368
+ DEFAULT_CONFIG.security.rateLimit.global.enabled,
369
+ 'security.rateLimit.global.enabled'
370
+ ),
371
+ requests: validateNumber(
372
+ coerceEnvValue(process.env.MORO_SECURITY_RATE_LIMIT_GLOBAL_REQUESTS || '') ||
373
+ DEFAULT_CONFIG.security.rateLimit.global.requests,
374
+ 'security.rateLimit.global.requests',
375
+ { min: 1 }
376
+ ),
377
+ window: validateNumber(
378
+ coerceEnvValue(process.env.MORO_SECURITY_RATE_LIMIT_GLOBAL_WINDOW || '') ||
379
+ DEFAULT_CONFIG.security.rateLimit.global.window,
380
+ 'security.rateLimit.global.window',
381
+ { min: 1000 }
382
+ ),
383
+ },
384
+ },
385
+ },
386
+
387
+ external: {
388
+ stripe: process.env.STRIPE_SECRET_KEY
389
+ ? {
390
+ secretKey: validateOptional(
391
+ process.env.STRIPE_SECRET_KEY,
392
+ validateString,
393
+ 'external.stripe.secretKey'
394
+ ),
395
+ publishableKey: validateOptional(
396
+ process.env.STRIPE_PUBLISHABLE_KEY,
397
+ validateString,
398
+ 'external.stripe.publishableKey'
399
+ ),
400
+ webhookSecret: validateOptional(
401
+ process.env.STRIPE_WEBHOOK_SECRET,
402
+ validateString,
403
+ 'external.stripe.webhookSecret'
404
+ ),
405
+ apiVersion: validateString(
406
+ process.env.STRIPE_API_VERSION || DEFAULT_CONFIG.external.stripe!.apiVersion,
407
+ 'external.stripe.apiVersion'
408
+ ),
409
+ }
410
+ : undefined,
411
+ paypal: process.env.PAYPAL_CLIENT_ID
412
+ ? {
413
+ clientId: validateOptional(
414
+ process.env.PAYPAL_CLIENT_ID,
415
+ validateString,
416
+ 'external.paypal.clientId'
417
+ ),
418
+ clientSecret: validateOptional(
419
+ process.env.PAYPAL_CLIENT_SECRET,
420
+ validateString,
421
+ 'external.paypal.clientSecret'
422
+ ),
423
+ webhookId: validateOptional(
424
+ process.env.PAYPAL_WEBHOOK_ID,
425
+ validateString,
426
+ 'external.paypal.webhookId'
427
+ ),
428
+ environment: validateEnum(
429
+ process.env.PAYPAL_ENVIRONMENT || DEFAULT_CONFIG.external.paypal!.environment,
430
+ ['sandbox', 'production'] as const,
431
+ 'external.paypal.environment'
432
+ ),
433
+ }
434
+ : undefined,
435
+ smtp: process.env.SMTP_HOST
436
+ ? {
437
+ host: validateOptional(process.env.SMTP_HOST, validateString, 'external.smtp.host'),
438
+ port: validatePort(
439
+ process.env.SMTP_PORT || DEFAULT_CONFIG.external.smtp!.port.toString(),
440
+ 'external.smtp.port'
441
+ ),
442
+ secure: validateBoolean(
443
+ coerceEnvValue(process.env.SMTP_SECURE || '') ??
444
+ DEFAULT_CONFIG.external.smtp!.secure,
445
+ 'external.smtp.secure'
446
+ ),
447
+ username: validateOptional(
448
+ process.env.SMTP_USERNAME,
449
+ validateString,
450
+ 'external.smtp.username'
451
+ ),
452
+ password: validateOptional(
453
+ process.env.SMTP_PASSWORD,
454
+ validateString,
455
+ 'external.smtp.password'
456
+ ),
457
+ }
458
+ : undefined,
459
+ },
460
+
461
+ performance: {
462
+ compression: {
463
+ enabled: validateBoolean(
464
+ coerceEnvValue(process.env.MORO_PERFORMANCE_COMPRESSION_ENABLED || '') ??
465
+ DEFAULT_CONFIG.performance.compression.enabled,
466
+ 'performance.compression.enabled'
467
+ ),
468
+ level: validateNumber(
469
+ coerceEnvValue(process.env.MORO_PERFORMANCE_COMPRESSION_LEVEL || '') ||
470
+ DEFAULT_CONFIG.performance.compression.level,
471
+ 'performance.compression.level',
472
+ { min: 1, max: 9 }
473
+ ),
474
+ threshold: validateNumber(
475
+ coerceEnvValue(process.env.MORO_PERFORMANCE_COMPRESSION_THRESHOLD || '') ||
476
+ DEFAULT_CONFIG.performance.compression.threshold,
477
+ 'performance.compression.threshold',
478
+ { min: 0 }
479
+ ),
480
+ },
481
+ circuitBreaker: {
482
+ enabled: validateBoolean(
483
+ coerceEnvValue(process.env.MORO_PERFORMANCE_CIRCUIT_BREAKER_ENABLED || '') ??
484
+ DEFAULT_CONFIG.performance.circuitBreaker.enabled,
485
+ 'performance.circuitBreaker.enabled'
486
+ ),
487
+ failureThreshold: validateNumber(
488
+ coerceEnvValue(process.env.MORO_PERFORMANCE_CIRCUIT_BREAKER_FAILURE_THRESHOLD || '') ||
489
+ DEFAULT_CONFIG.performance.circuitBreaker.failureThreshold,
490
+ 'performance.circuitBreaker.failureThreshold',
491
+ { min: 1 }
492
+ ),
493
+ resetTimeout: validateNumber(
494
+ coerceEnvValue(process.env.MORO_PERFORMANCE_CIRCUIT_BREAKER_RESET_TIMEOUT || '') ||
495
+ DEFAULT_CONFIG.performance.circuitBreaker.resetTimeout,
496
+ 'performance.circuitBreaker.resetTimeout',
497
+ { min: 1000 }
498
+ ),
499
+ monitoringPeriod: validateNumber(
500
+ coerceEnvValue(process.env.MORO_PERFORMANCE_CIRCUIT_BREAKER_MONITORING_PERIOD || '') ||
501
+ DEFAULT_CONFIG.performance.circuitBreaker.monitoringPeriod,
502
+ 'performance.circuitBreaker.monitoringPeriod',
503
+ { min: 1000 }
504
+ ),
505
+ },
506
+ clustering: {
507
+ enabled: validateBoolean(
508
+ coerceEnvValue(process.env.MORO_PERFORMANCE_CLUSTERING_ENABLED || '') ??
509
+ DEFAULT_CONFIG.performance.clustering.enabled,
510
+ 'performance.clustering.enabled'
511
+ ),
512
+ workers:
513
+ process.env.MORO_PERFORMANCE_CLUSTERING_WORKERS === 'auto'
514
+ ? 'auto'
515
+ : validateNumber(
516
+ coerceEnvValue(process.env.MORO_PERFORMANCE_CLUSTERING_WORKERS || '') ||
517
+ DEFAULT_CONFIG.performance.clustering.workers,
518
+ 'performance.clustering.workers',
519
+ { min: 1 }
520
+ ),
521
+ },
522
+ },
523
+ };
524
+
525
+ logger.info('Configuration loaded and validated successfully with TypeScript');
526
+ logger.debug(
527
+ 'Configuration summary:',
528
+ JSON.stringify({
529
+ server: { port: config.server.port, environment: config.server.environment },
530
+ serviceDiscovery: {
531
+ enabled: config.serviceDiscovery.enabled,
532
+ type: config.serviceDiscovery.type,
533
+ },
534
+ modules: {
535
+ cache: config.modules.cache.enabled,
536
+ rateLimit: config.modules.rateLimit.enabled,
537
+ validation: config.modules.validation.enabled,
538
+ },
539
+ })
540
+ );
541
+
542
+ return config;
543
+ } catch (error) {
544
+ logger.error('❌ Configuration validation failed');
545
+
546
+ if (error instanceof ConfigValidationError) {
547
+ logger.error(`Configuration error in '${error.field}': ${error.message}`);
548
+ logger.error(` Value: ${JSON.stringify(error.value)}`);
549
+
550
+ // Provide helpful hints
551
+ if (error.field.includes('port')) {
552
+ logger.error(' Hint: Ports must be numbers between 1 and 65535');
553
+ }
554
+ if (error.field.includes('url')) {
555
+ logger.error(' Hint: URLs must include protocol (http:// or https://)');
556
+ }
557
+ if (error.field.includes('environment')) {
558
+ logger.error(' Hint: NODE_ENV must be one of: development, staging, production');
559
+ }
560
+ } else {
561
+ logger.error('Unexpected configuration error:', String(error));
562
+ }
563
+
564
+ logger.error('\nConfiguration Help:');
565
+ logger.error(' - Use MORO_* prefixed environment variables for framework-specific config');
566
+ logger.error(' - Check .env.example for available configuration options');
567
+ logger.error(' - See documentation for detailed configuration guide');
568
+
569
+ process.exit(1);
570
+ }
571
+ }