@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
@@ -1,596 +0,0 @@
1
- /**
2
- * Configuration Sources - Load from Environment, Files, and Options
3
- *
4
- * This module handles loading configuration from different sources with clear precedence:
5
- * Environment Variables > createApp Options > Config File > Schema Defaults
6
- */
7
-
8
- import { AppConfig } from '../../types/config';
9
- import { MoroOptions } from '../../types/core';
10
- import { DEFAULT_CONFIG } from './schema';
11
- import { loadConfigFileSync } from './file-loader';
12
- import { createFrameworkLogger } from '../logger';
13
- import { validateConfig } from './config-validator';
14
-
15
- const logger = createFrameworkLogger('ConfigSources');
16
-
17
- /**
18
- * Configuration source metadata for debugging
19
- */
20
- export interface ConfigSourceInfo {
21
- source: 'environment' | 'createApp' | 'configFile' | 'default';
22
- path: string;
23
- value: any;
24
- }
25
-
26
- /**
27
- * Load configuration from all sources with proper precedence
28
- * Returns a validated, complete configuration object
29
- */
30
- export function loadConfigFromAllSources(createAppOptions?: MoroOptions): AppConfig {
31
- logger.debug('Loading configuration from all sources');
32
-
33
- // 1. Start with schema defaults
34
- let config = JSON.parse(JSON.stringify(DEFAULT_CONFIG)) as AppConfig;
35
- const sourceMap = new Map<string, ConfigSourceInfo>();
36
-
37
- // Track default values
38
- trackConfigSource(config, sourceMap, 'default', 'schema');
39
-
40
- // 2. Load and merge config file (if exists)
41
- try {
42
- const fileConfig = loadConfigFileSync();
43
- if (fileConfig) {
44
- config = deepMerge(config, fileConfig);
45
- trackConfigSource(fileConfig, sourceMap, 'configFile', 'moro.config.js/ts');
46
- logger.debug('Config file loaded and merged');
47
- }
48
- } catch (error) {
49
- logger.warn('Config file loading failed, continuing without it:', String(error));
50
- }
51
-
52
- // 3. Load and merge environment variables
53
- const envConfig = loadEnvironmentConfig();
54
- config = deepMerge(config, envConfig);
55
- trackConfigSource(envConfig, sourceMap, 'environment', 'process.env');
56
-
57
- // 4. Load and merge createApp options (highest precedence)
58
- if (createAppOptions) {
59
- const normalizedOptions = normalizeCreateAppOptions(createAppOptions);
60
- config = deepMerge(config, normalizedOptions);
61
- trackConfigSource(normalizedOptions, sourceMap, 'createApp', 'createApp()');
62
- logger.debug('createApp options merged');
63
- }
64
-
65
- // 5. Validate the final configuration
66
- const validatedConfig = validateConfig(config);
67
-
68
- // Log configuration sources for debugging
69
- logConfigurationSources(sourceMap);
70
-
71
- return validatedConfig;
72
- }
73
-
74
- /**
75
- * Load configuration from environment variables
76
- * Handles both standard and MORO_ prefixed variables
77
- */
78
- function loadEnvironmentConfig(): Partial<AppConfig> {
79
- const config: Partial<AppConfig> = {};
80
-
81
- // Server configuration
82
- if (process.env.PORT || process.env.MORO_PORT) {
83
- if (!config.server) config.server = {} as any;
84
- config.server!.port = parseInt(process.env.PORT || process.env.MORO_PORT || '3001', 10);
85
- }
86
-
87
- if (process.env.HOST || process.env.MORO_HOST) {
88
- if (!config.server) config.server = {} as any;
89
- config.server!.host = process.env.HOST || process.env.MORO_HOST || 'localhost';
90
- }
91
-
92
- if (process.env.MAX_CONNECTIONS || process.env.MORO_MAX_CONNECTIONS) {
93
- if (!config.server) config.server = {} as any;
94
- config.server!.maxConnections = parseInt(
95
- process.env.MAX_CONNECTIONS || process.env.MORO_MAX_CONNECTIONS || '1000',
96
- 10
97
- );
98
- }
99
-
100
- if (process.env.REQUEST_TIMEOUT || process.env.MORO_TIMEOUT) {
101
- if (!config.server) config.server = {} as any;
102
- config.server!.timeout = parseInt(
103
- process.env.REQUEST_TIMEOUT || process.env.MORO_TIMEOUT || '30000',
104
- 10
105
- );
106
- }
107
-
108
- // Database configuration
109
- if (process.env.DATABASE_URL || process.env.MORO_DATABASE_URL) {
110
- if (!config.database) config.database = {} as any;
111
- config.database!.url = process.env.DATABASE_URL || process.env.MORO_DATABASE_URL;
112
- }
113
-
114
- // Redis configuration
115
- if (process.env.REDIS_URL || process.env.MORO_REDIS_URL) {
116
- if (!config.database) config.database = {} as any;
117
- if (!config.database!.redis) config.database!.redis = {} as any;
118
- config.database!.redis!.url =
119
- process.env.REDIS_URL || process.env.MORO_REDIS_URL || 'redis://localhost:6379';
120
- config.database!.redis!.maxRetries = parseInt(
121
- process.env.REDIS_MAX_RETRIES || process.env.MORO_REDIS_MAX_RETRIES || '3',
122
- 10
123
- );
124
- config.database!.redis!.retryDelay = parseInt(
125
- process.env.REDIS_RETRY_DELAY || process.env.MORO_REDIS_RETRY_DELAY || '1000',
126
- 10
127
- );
128
- config.database!.redis!.keyPrefix =
129
- process.env.REDIS_KEY_PREFIX || process.env.MORO_REDIS_KEY_PREFIX || 'moro:';
130
- }
131
-
132
- // MySQL configuration - only include if MYSQL_HOST is set
133
- if (process.env.MYSQL_HOST || process.env.MORO_MYSQL_HOST) {
134
- if (!config.database) config.database = {} as any;
135
- config.database!.mysql = {
136
- host: process.env.MYSQL_HOST || process.env.MORO_MYSQL_HOST || 'localhost',
137
- port: parseInt(process.env.MYSQL_PORT || process.env.MORO_MYSQL_PORT || '3306', 10),
138
- database: process.env.MYSQL_DATABASE || process.env.MORO_MYSQL_DB,
139
- username: process.env.MYSQL_USERNAME || process.env.MORO_MYSQL_USER,
140
- password: process.env.MYSQL_PASSWORD || process.env.MORO_MYSQL_PASS,
141
- connectionLimit: parseInt(
142
- process.env.MYSQL_CONNECTION_LIMIT || process.env.MORO_MYSQL_CONNECTIONS || '10',
143
- 10
144
- ),
145
- acquireTimeout: parseInt(
146
- process.env.MYSQL_ACQUIRE_TIMEOUT || process.env.MORO_MYSQL_ACQUIRE || '60000',
147
- 10
148
- ),
149
- timeout: parseInt(process.env.MYSQL_TIMEOUT || process.env.MORO_MYSQL_TIMEOUT || '60000', 10),
150
- } as any;
151
- }
152
-
153
- // Logging configuration
154
- if (process.env.LOG_LEVEL || process.env.MORO_LOG_LEVEL) {
155
- const level = process.env.LOG_LEVEL || process.env.MORO_LOG_LEVEL;
156
- if (
157
- level === 'debug' ||
158
- level === 'info' ||
159
- level === 'warn' ||
160
- level === 'error' ||
161
- level === 'fatal'
162
- ) {
163
- if (!config.logging) config.logging = {} as any;
164
- config.logging!.level = level;
165
- }
166
- }
167
-
168
- // External services - only include if configured
169
- const externalConfig: Partial<AppConfig['external']> = {};
170
-
171
- // Stripe
172
- if (process.env.STRIPE_SECRET_KEY || process.env.MORO_STRIPE_SECRET) {
173
- externalConfig.stripe = {
174
- secretKey: process.env.STRIPE_SECRET_KEY || process.env.MORO_STRIPE_SECRET,
175
- publishableKey: process.env.STRIPE_PUBLISHABLE_KEY || process.env.MORO_STRIPE_PUBLIC,
176
- webhookSecret: process.env.STRIPE_WEBHOOK_SECRET || process.env.MORO_STRIPE_WEBHOOK,
177
- apiVersion: process.env.STRIPE_API_VERSION || process.env.MORO_STRIPE_VERSION || '2023-10-16',
178
- };
179
- }
180
-
181
- // PayPal
182
- if (process.env.PAYPAL_CLIENT_ID || process.env.MORO_PAYPAL_CLIENT) {
183
- externalConfig.paypal = {
184
- clientId: process.env.PAYPAL_CLIENT_ID || process.env.MORO_PAYPAL_CLIENT,
185
- clientSecret: process.env.PAYPAL_CLIENT_SECRET || process.env.MORO_PAYPAL_SECRET,
186
- webhookId: process.env.PAYPAL_WEBHOOK_ID || process.env.MORO_PAYPAL_WEBHOOK,
187
- environment:
188
- (process.env.PAYPAL_ENVIRONMENT || process.env.MORO_PAYPAL_ENV) === 'production'
189
- ? 'production'
190
- : 'sandbox',
191
- };
192
- }
193
-
194
- // SMTP
195
- if (process.env.SMTP_HOST || process.env.MORO_SMTP_HOST) {
196
- externalConfig.smtp = {
197
- host: process.env.SMTP_HOST || process.env.MORO_SMTP_HOST,
198
- port: parseInt(process.env.SMTP_PORT || process.env.MORO_SMTP_PORT || '587', 10),
199
- secure: (process.env.SMTP_SECURE || 'false').toLowerCase() === 'true',
200
- username: process.env.SMTP_USERNAME || process.env.MORO_SMTP_USER,
201
- password: process.env.SMTP_PASSWORD || process.env.MORO_SMTP_PASS,
202
- };
203
- }
204
-
205
- if (Object.keys(externalConfig).length > 0) {
206
- config.external = externalConfig;
207
- }
208
-
209
- // Module configuration
210
- const moduleEnvVars = [
211
- // Cache
212
- 'CACHE_ENABLED',
213
- 'MORO_CACHE_ENABLED',
214
- 'DEFAULT_CACHE_TTL',
215
- 'MORO_CACHE_TTL',
216
- 'CACHE_MAX_SIZE',
217
- 'MORO_CACHE_SIZE',
218
- 'CACHE_STRATEGY',
219
- 'MORO_CACHE_STRATEGY',
220
- // Rate Limit
221
- 'RATE_LIMIT_ENABLED',
222
- 'MORO_RATE_LIMIT_ENABLED',
223
- 'DEFAULT_RATE_LIMIT_REQUESTS',
224
- 'MORO_RATE_LIMIT_REQUESTS',
225
- 'DEFAULT_RATE_LIMIT_WINDOW',
226
- 'MORO_RATE_LIMIT_WINDOW',
227
- // Validation
228
- 'VALIDATION_ENABLED',
229
- 'MORO_VALIDATION_ENABLED',
230
- // Auto-Discovery
231
- 'AUTO_DISCOVERY_ENABLED',
232
- 'MORO_AUTO_DISCOVERY_ENABLED',
233
- 'AUTO_DISCOVERY_PATHS',
234
- 'MORO_AUTO_DISCOVERY_PATHS',
235
- 'AUTO_DISCOVERY_PATTERNS',
236
- 'MORO_AUTO_DISCOVERY_PATTERNS',
237
- 'AUTO_DISCOVERY_LOADING_STRATEGY',
238
- 'MORO_AUTO_DISCOVERY_LOADING_STRATEGY',
239
- 'AUTO_DISCOVERY_WATCH_FOR_CHANGES',
240
- 'MORO_AUTO_DISCOVERY_WATCH_FOR_CHANGES',
241
- 'AUTO_DISCOVERY_LOAD_ORDER',
242
- 'MORO_AUTO_DISCOVERY_LOAD_ORDER',
243
- 'AUTO_DISCOVERY_FAIL_ON_ERROR',
244
- 'MORO_AUTO_DISCOVERY_FAIL_ON_ERROR',
245
- 'AUTO_DISCOVERY_MAX_DEPTH',
246
- 'MORO_AUTO_DISCOVERY_MAX_DEPTH',
247
- ];
248
-
249
- if (moduleEnvVars.some(envVar => process.env[envVar])) {
250
- if (!config.modules) config.modules = {} as any;
251
-
252
- // Cache configuration
253
- if (process.env.CACHE_ENABLED || process.env.MORO_CACHE_ENABLED) {
254
- if (!config.modules!.cache) config.modules!.cache = {} as any;
255
- config.modules!.cache!.enabled =
256
- (process.env.CACHE_ENABLED || process.env.MORO_CACHE_ENABLED) === 'true';
257
- }
258
- if (process.env.DEFAULT_CACHE_TTL || process.env.MORO_CACHE_TTL) {
259
- if (!config.modules!.cache) config.modules!.cache = {} as any;
260
- config.modules!.cache!.defaultTtl = parseInt(
261
- process.env.DEFAULT_CACHE_TTL || process.env.MORO_CACHE_TTL || '300',
262
- 10
263
- );
264
- }
265
- if (process.env.CACHE_MAX_SIZE || process.env.MORO_CACHE_SIZE) {
266
- if (!config.modules!.cache) config.modules!.cache = {} as any;
267
- config.modules!.cache!.maxSize = parseInt(
268
- process.env.CACHE_MAX_SIZE || process.env.MORO_CACHE_SIZE || '1000',
269
- 10
270
- );
271
- }
272
- if (process.env.CACHE_STRATEGY || process.env.MORO_CACHE_STRATEGY) {
273
- if (!config.modules!.cache) config.modules!.cache = {} as any;
274
- const strategy = process.env.CACHE_STRATEGY || process.env.MORO_CACHE_STRATEGY;
275
- if (['lru', 'lfu', 'fifo'].includes(strategy || '')) {
276
- config.modules!.cache!.strategy = strategy as 'lru' | 'lfu' | 'fifo';
277
- }
278
- }
279
-
280
- // Rate limit configuration
281
- if (process.env.RATE_LIMIT_ENABLED || process.env.MORO_RATE_LIMIT_ENABLED) {
282
- if (!config.modules!.rateLimit) config.modules!.rateLimit = {} as any;
283
- config.modules!.rateLimit!.enabled =
284
- (process.env.RATE_LIMIT_ENABLED || process.env.MORO_RATE_LIMIT_ENABLED) === 'true';
285
- }
286
- if (process.env.DEFAULT_RATE_LIMIT_REQUESTS || process.env.MORO_RATE_LIMIT_REQUESTS) {
287
- if (!config.modules!.rateLimit) config.modules!.rateLimit = {} as any;
288
- config.modules!.rateLimit!.defaultRequests = parseInt(
289
- process.env.DEFAULT_RATE_LIMIT_REQUESTS || process.env.MORO_RATE_LIMIT_REQUESTS || '100',
290
- 10
291
- );
292
- }
293
- if (process.env.DEFAULT_RATE_LIMIT_WINDOW || process.env.MORO_RATE_LIMIT_WINDOW) {
294
- if (!config.modules!.rateLimit) config.modules!.rateLimit = {} as any;
295
- config.modules!.rateLimit!.defaultWindow = parseInt(
296
- process.env.DEFAULT_RATE_LIMIT_WINDOW || process.env.MORO_RATE_LIMIT_WINDOW || '60000',
297
- 10
298
- );
299
- }
300
-
301
- // Validation configuration
302
- if (process.env.VALIDATION_ENABLED || process.env.MORO_VALIDATION_ENABLED) {
303
- if (!config.modules!.validation) config.modules!.validation = {} as any;
304
- config.modules!.validation!.enabled =
305
- (process.env.VALIDATION_ENABLED || process.env.MORO_VALIDATION_ENABLED) === 'true';
306
- }
307
-
308
- // Auto-Discovery configuration
309
- if (process.env.AUTO_DISCOVERY_ENABLED || process.env.MORO_AUTO_DISCOVERY_ENABLED) {
310
- if (!config.modules!.autoDiscovery) config.modules!.autoDiscovery = {} as any;
311
- config.modules!.autoDiscovery!.enabled =
312
- (process.env.AUTO_DISCOVERY_ENABLED || process.env.MORO_AUTO_DISCOVERY_ENABLED) === 'true';
313
- }
314
- if (process.env.AUTO_DISCOVERY_PATHS || process.env.MORO_AUTO_DISCOVERY_PATHS) {
315
- if (!config.modules!.autoDiscovery) config.modules!.autoDiscovery = {} as any;
316
- const paths = (
317
- process.env.AUTO_DISCOVERY_PATHS ||
318
- process.env.MORO_AUTO_DISCOVERY_PATHS ||
319
- ''
320
- )
321
- .split(',')
322
- .map(p => p.trim())
323
- .filter(Boolean);
324
- if (paths.length > 0) {
325
- config.modules!.autoDiscovery!.paths = paths;
326
- }
327
- }
328
- if (process.env.AUTO_DISCOVERY_PATTERNS || process.env.MORO_AUTO_DISCOVERY_PATTERNS) {
329
- if (!config.modules!.autoDiscovery) config.modules!.autoDiscovery = {} as any;
330
- const patterns = (
331
- process.env.AUTO_DISCOVERY_PATTERNS ||
332
- process.env.MORO_AUTO_DISCOVERY_PATTERNS ||
333
- ''
334
- )
335
- .split(',')
336
- .map(p => p.trim())
337
- .filter(Boolean);
338
- if (patterns.length > 0) {
339
- config.modules!.autoDiscovery!.patterns = patterns;
340
- }
341
- }
342
- if (
343
- process.env.AUTO_DISCOVERY_LOADING_STRATEGY ||
344
- process.env.MORO_AUTO_DISCOVERY_LOADING_STRATEGY
345
- ) {
346
- if (!config.modules!.autoDiscovery) config.modules!.autoDiscovery = {} as any;
347
- const strategy =
348
- process.env.AUTO_DISCOVERY_LOADING_STRATEGY ||
349
- process.env.MORO_AUTO_DISCOVERY_LOADING_STRATEGY;
350
- if (['eager', 'lazy', 'conditional'].includes(strategy || '')) {
351
- config.modules!.autoDiscovery!.loadingStrategy = strategy as
352
- | 'eager'
353
- | 'lazy'
354
- | 'conditional';
355
- }
356
- }
357
- if (
358
- process.env.AUTO_DISCOVERY_WATCH_FOR_CHANGES ||
359
- process.env.MORO_AUTO_DISCOVERY_WATCH_FOR_CHANGES
360
- ) {
361
- if (!config.modules!.autoDiscovery) config.modules!.autoDiscovery = {} as any;
362
- config.modules!.autoDiscovery!.watchForChanges =
363
- (process.env.AUTO_DISCOVERY_WATCH_FOR_CHANGES ||
364
- process.env.MORO_AUTO_DISCOVERY_WATCH_FOR_CHANGES) === 'true';
365
- }
366
- if (process.env.AUTO_DISCOVERY_LOAD_ORDER || process.env.MORO_AUTO_DISCOVERY_LOAD_ORDER) {
367
- if (!config.modules!.autoDiscovery) config.modules!.autoDiscovery = {} as any;
368
- const loadOrder =
369
- process.env.AUTO_DISCOVERY_LOAD_ORDER || process.env.MORO_AUTO_DISCOVERY_LOAD_ORDER;
370
- if (['alphabetical', 'dependency', 'custom'].includes(loadOrder || '')) {
371
- config.modules!.autoDiscovery!.loadOrder = loadOrder as
372
- | 'alphabetical'
373
- | 'dependency'
374
- | 'custom';
375
- }
376
- }
377
- if (process.env.AUTO_DISCOVERY_FAIL_ON_ERROR || process.env.MORO_AUTO_DISCOVERY_FAIL_ON_ERROR) {
378
- if (!config.modules!.autoDiscovery) config.modules!.autoDiscovery = {} as any;
379
- config.modules!.autoDiscovery!.failOnError =
380
- (process.env.AUTO_DISCOVERY_FAIL_ON_ERROR ||
381
- process.env.MORO_AUTO_DISCOVERY_FAIL_ON_ERROR) === 'true';
382
- }
383
- if (process.env.AUTO_DISCOVERY_MAX_DEPTH || process.env.MORO_AUTO_DISCOVERY_MAX_DEPTH) {
384
- if (!config.modules!.autoDiscovery) config.modules!.autoDiscovery = {} as any;
385
- config.modules!.autoDiscovery!.maxDepth = parseInt(
386
- process.env.AUTO_DISCOVERY_MAX_DEPTH || process.env.MORO_AUTO_DISCOVERY_MAX_DEPTH || '5',
387
- 10
388
- );
389
- }
390
- }
391
-
392
- return config;
393
- }
394
-
395
- /**
396
- * Normalize createApp options to match AppConfig structure
397
- * This handles the flexible createApp API while converting to structured config
398
- */
399
- function normalizeCreateAppOptions(options: MoroOptions): Partial<AppConfig> {
400
- const config: Partial<AppConfig> = {};
401
-
402
- // Direct config section overrides - merge with existing config
403
- if (options.server) {
404
- config.server = { ...config.server, ...options.server } as any;
405
- }
406
- if (options.database) {
407
- config.database = { ...config.database, ...options.database } as any;
408
- }
409
- if (options.modules) {
410
- config.modules = { ...config.modules, ...options.modules } as any;
411
- }
412
-
413
- // Handle autoDiscover option (maps to modules.autoDiscovery)
414
- if (options.autoDiscover !== undefined) {
415
- const autoDiscoveryConfig =
416
- typeof options.autoDiscover === 'boolean'
417
- ? { enabled: options.autoDiscover }
418
- : options.autoDiscover;
419
-
420
- config.modules = {
421
- ...config.modules,
422
- autoDiscovery: {
423
- ...DEFAULT_CONFIG.modules.autoDiscovery,
424
- ...autoDiscoveryConfig,
425
- },
426
- } as any;
427
- }
428
-
429
- // Handle legacy modulesPath option (maps to modules.autoDiscovery.paths)
430
- if (options.modulesPath) {
431
- config.modules = {
432
- ...config.modules,
433
- autoDiscovery: {
434
- ...DEFAULT_CONFIG.modules.autoDiscovery,
435
- ...(config.modules as any)?.autoDiscovery,
436
- enabled: true,
437
- paths: [options.modulesPath],
438
- },
439
- } as any;
440
- }
441
- if (options.logging) {
442
- config.logging = { ...config.logging, ...options.logging } as any;
443
- }
444
- if (options.security) {
445
- config.security = { ...config.security, ...options.security } as any;
446
- }
447
- if (options.external) {
448
- config.external = { ...config.external, ...options.external } as any;
449
- }
450
- if (options.performance) {
451
- config.performance = { ...config.performance, ...options.performance } as any;
452
- }
453
-
454
- // Handle shorthand boolean/object options
455
- if (options.cors !== undefined) {
456
- config.security = {
457
- ...config.security,
458
- cors:
459
- typeof options.cors === 'boolean'
460
- ? { ...DEFAULT_CONFIG.security.cors, enabled: options.cors }
461
- : { ...DEFAULT_CONFIG.security.cors, ...options.cors },
462
- } as any;
463
- }
464
-
465
- if (options.compression !== undefined) {
466
- config.performance = {
467
- ...config.performance,
468
- compression:
469
- typeof options.compression === 'boolean'
470
- ? { ...DEFAULT_CONFIG.performance.compression, enabled: options.compression }
471
- : { ...DEFAULT_CONFIG.performance.compression, ...options.compression },
472
- } as any;
473
- }
474
-
475
- if (options.helmet !== undefined) {
476
- config.security = {
477
- ...config.security,
478
- helmet:
479
- typeof options.helmet === 'boolean'
480
- ? { ...DEFAULT_CONFIG.security.helmet, enabled: options.helmet }
481
- : { ...DEFAULT_CONFIG.security.helmet, ...options.helmet },
482
- } as any;
483
- }
484
-
485
- return config;
486
- }
487
-
488
- /**
489
- * Check if a config field contains sensitive information
490
- */
491
- function isSensitiveField(path: string): boolean {
492
- const sensitivePatterns = [
493
- 'password',
494
- 'secret',
495
- 'key',
496
- 'token',
497
- 'auth',
498
- 'stripe',
499
- 'paypal',
500
- 'smtp.password',
501
- 'smtp.username',
502
- 'database.url',
503
- 'redis.url',
504
- 'mysql.password',
505
- ];
506
-
507
- return sensitivePatterns.some(pattern => path.toLowerCase().includes(pattern.toLowerCase()));
508
- }
509
-
510
- /**
511
- * Deep merge two configuration objects
512
- * Later object properties override earlier ones
513
- */
514
- function deepMerge<T>(target: T, source: Partial<T>): T {
515
- const result = { ...target };
516
-
517
- for (const key in source) {
518
- const sourceValue = source[key];
519
- const targetValue = result[key];
520
-
521
- if (
522
- sourceValue &&
523
- typeof sourceValue === 'object' &&
524
- !Array.isArray(sourceValue) &&
525
- targetValue &&
526
- typeof targetValue === 'object' &&
527
- !Array.isArray(targetValue)
528
- ) {
529
- (result as any)[key] = deepMerge(targetValue, sourceValue);
530
- } else if (sourceValue !== undefined) {
531
- (result as any)[key] = sourceValue;
532
- }
533
- }
534
-
535
- return result;
536
- }
537
-
538
- /**
539
- * Track configuration sources for debugging
540
- */
541
- function trackConfigSource(
542
- config: any,
543
- sourceMap: Map<string, ConfigSourceInfo>,
544
- source: ConfigSourceInfo['source'],
545
- path: string
546
- ): void {
547
- function traverse(obj: any, currentPath: string): void {
548
- for (const key in obj) {
549
- const value = obj[key];
550
- const fullPath = currentPath ? `${currentPath}.${key}` : key;
551
-
552
- if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
553
- traverse(value, fullPath);
554
- } else {
555
- sourceMap.set(fullPath, { source, path, value });
556
- }
557
- }
558
- }
559
-
560
- traverse(config, '');
561
- }
562
-
563
- /**
564
- * Log configuration sources for debugging
565
- */
566
- function logConfigurationSources(sourceMap: Map<string, ConfigSourceInfo>): void {
567
- const allSources = Array.from(sourceMap.entries()).sort(([a], [b]) => a.localeCompare(b));
568
- const nonDefaultSources = allSources.filter(([_, info]) => info.source !== 'default');
569
-
570
- if (process.env.NODE_ENV === 'production') {
571
- // In production, only show non-default values with sensitive data obfuscated
572
- if (nonDefaultSources.length > 0) {
573
- logger.debug(`Configuration overrides loaded (${nonDefaultSources.length} total)`);
574
-
575
- nonDefaultSources.forEach(([path, info]) => {
576
- const valueStr = isSensitiveField(path)
577
- ? '***'
578
- : typeof info.value === 'object'
579
- ? JSON.stringify(info.value)
580
- : String(info.value);
581
- logger.debug(` ${path}: ${valueStr} (from ${info.source})`);
582
- });
583
- } else {
584
- logger.debug('Using default configuration (no overrides)');
585
- }
586
- } else {
587
- // In development, show all sources for debugging
588
- logger.debug(`Configuration sources loaded (${allSources.length} total)`);
589
-
590
- allSources.forEach(([path, info]) => {
591
- const valueStr =
592
- typeof info.value === 'object' ? JSON.stringify(info.value) : String(info.value);
593
- logger.debug(` ${path}: ${valueStr} (from ${info.source})`);
594
- });
595
- }
596
- }