@morojs/moro 1.5.14 → 1.5.16

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.
@@ -206,6 +206,189 @@ function loadEnvironmentConfig(): Partial<AppConfig> {
206
206
  config.external = externalConfig;
207
207
  }
208
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
+
209
392
  return config;
210
393
  }
211
394
 
@@ -226,6 +409,35 @@ function normalizeCreateAppOptions(options: MoroOptions): Partial<AppConfig> {
226
409
  if (options.modules) {
227
410
  config.modules = { ...config.modules, ...options.modules } as any;
228
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
+ }
229
441
  if (options.logging) {
230
442
  config.logging = { ...config.logging, ...options.logging } as any;
231
443
  }
@@ -338,6 +338,42 @@ function validateModuleDefaultsConfig(config: any, path: string) {
338
338
  cache: validateCacheConfig(config.cache, `${path}.cache`),
339
339
  rateLimit: validateRateLimitConfig(config.rateLimit, `${path}.rateLimit`),
340
340
  validation: validateValidationConfig(config.validation, `${path}.validation`),
341
+ autoDiscovery: validateAutoDiscoveryConfig(config.autoDiscovery, `${path}.autoDiscovery`),
342
+ };
343
+ }
344
+
345
+ /**
346
+ * Validate auto-discovery configuration
347
+ */
348
+ function validateAutoDiscoveryConfig(config: any, path: string) {
349
+ if (!config || typeof config !== 'object') {
350
+ throw new ConfigValidationError(
351
+ path,
352
+ config,
353
+ 'object',
354
+ 'Auto-discovery configuration must be an object'
355
+ );
356
+ }
357
+
358
+ return {
359
+ enabled: validateBoolean(config.enabled, `${path}.enabled`),
360
+ paths: validateStringArray(config.paths, `${path}.paths`),
361
+ patterns: validateStringArray(config.patterns, `${path}.patterns`),
362
+ recursive: validateBoolean(config.recursive, `${path}.recursive`),
363
+ loadingStrategy: validateEnum(
364
+ config.loadingStrategy,
365
+ ['eager', 'lazy', 'conditional'],
366
+ `${path}.loadingStrategy`
367
+ ),
368
+ watchForChanges: validateBoolean(config.watchForChanges, `${path}.watchForChanges`),
369
+ ignorePatterns: validateStringArray(config.ignorePatterns, `${path}.ignorePatterns`),
370
+ loadOrder: validateEnum(
371
+ config.loadOrder,
372
+ ['alphabetical', 'dependency', 'custom'],
373
+ `${path}.loadOrder`
374
+ ),
375
+ failOnError: validateBoolean(config.failOnError, `${path}.failOnError`),
376
+ maxDepth: validateNumber(config.maxDepth, `${path}.maxDepth`),
341
377
  };
342
378
  }
343
379
 
@@ -45,6 +45,18 @@ export const DEFAULT_CONFIG: AppConfig = {
45
45
  stripUnknown: true,
46
46
  abortEarly: false,
47
47
  },
48
+ autoDiscovery: {
49
+ enabled: true, // Enable by default for better DX
50
+ paths: ['./modules', './src/modules'],
51
+ patterns: ['**/*.module.{ts,js}', '**/index.{ts,js}', '**/*.config.{ts,js}'],
52
+ recursive: true,
53
+ loadingStrategy: 'eager',
54
+ watchForChanges: false, // Opt-in for development
55
+ ignorePatterns: ['**/*.test.{ts,js}', '**/*.spec.{ts,js}', '**/node_modules/**'],
56
+ loadOrder: 'dependency',
57
+ failOnError: false, // Graceful degradation
58
+ maxDepth: 5,
59
+ },
48
60
  },
49
61
  logging: {
50
62
  level: 'info',