@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
package/src/moro.ts CHANGED
@@ -5,35 +5,39 @@ import { Moro as MoroCore } from './core/framework';
5
5
  import { HttpRequest, HttpResponse, middleware } from './core/http';
6
6
  import { ModuleConfig, InternalRouteDefinition } from './types/module';
7
7
  import { MoroOptions } from './types/core';
8
- import { ModuleDefaultsConfig } from './types/config';
9
8
  import { MoroEventBus } from './core/events';
10
- import { createFrameworkLogger, applyLoggingConfiguration } from './core/logger';
11
- import { Logger } from './types/logger';
9
+ import {
10
+ createFrameworkLogger,
11
+ logger as globalLogger,
12
+ applyLoggingConfiguration,
13
+ } from './core/logger';
12
14
  import { MiddlewareManager } from './core/middleware';
13
15
  import { IntelligentRoutingManager } from './core/routing/app-integration';
14
16
  import { RouteBuilder, RouteSchema, CompiledRoute } from './core/routing';
15
17
  import { AppDocumentationManager, DocsConfig } from './core/docs';
18
+ import { readdirSync, statSync } from 'fs';
19
+ import { join } from 'path';
16
20
  import { EventEmitter } from 'events';
17
21
  // Configuration System Integration
18
- import { initializeConfig, type AppConfig } from './core/config';
22
+ import { initializeConfig, getGlobalConfig, type AppConfig } from './core/config';
19
23
  // Runtime System Integration
20
- import { RuntimeAdapter, RuntimeType, createRuntimeAdapter } from './core/runtime';
24
+ import {
25
+ RuntimeAdapter,
26
+ RuntimeType,
27
+ createRuntimeAdapter,
28
+ NodeRuntimeAdapter,
29
+ } from './core/runtime';
21
30
 
22
31
  export class Moro extends EventEmitter {
23
32
  private coreFramework: MoroCore;
24
33
  private routes: InternalRouteDefinition[] = [];
25
34
  private moduleCounter = 0;
26
35
  private loadedModules = new Set<string>();
27
- private lazyModules = new Map<string, ModuleConfig>();
28
36
  private routeHandlers: Record<string, Function> = {};
29
- private moduleDiscovery?: any; // Store for cleanup
30
- private autoDiscoveryOptions: MoroOptions | null = null;
31
- private autoDiscoveryInitialized = false;
32
- private autoDiscoveryPromise: Promise<void> | null = null;
33
37
  // Enterprise event system integration
34
38
  private eventBus: MoroEventBus;
35
39
  // Application logger
36
- private logger!: Logger;
40
+ private logger = createFrameworkLogger('App');
37
41
  // Intelligent routing system
38
42
  private intelligentRouting = new IntelligentRoutingManager();
39
43
  // Documentation system
@@ -49,33 +53,72 @@ export class Moro extends EventEmitter {
49
53
  constructor(options: MoroOptions = {}) {
50
54
  super(); // Call EventEmitter constructor
51
55
 
52
- // Apply logging configuration BEFORE config loading to avoid DEBUG spam
53
- // 1. Environment variables (base level)
56
+ // Configure logger from environment variables BEFORE config system initialization
57
+ // This ensures the config loading process respects the log level
54
58
  const envLogLevel = process.env.LOG_LEVEL || process.env.MORO_LOG_LEVEL;
55
59
  if (envLogLevel) {
56
60
  applyLoggingConfiguration({ level: envLogLevel }, undefined);
57
61
  }
58
62
 
59
- // 2. createApp logger options (highest precedence)
63
+ // Initialize configuration system - create a deep copy for this instance
64
+ this.config = JSON.parse(JSON.stringify(initializeConfig()));
65
+
66
+ // Apply logging configuration from the loaded config (this happens after config file processing)
67
+ if (this.config.logging) {
68
+ applyLoggingConfiguration(this.config.logging, undefined);
69
+ }
70
+
71
+ // Apply additional logging configuration from createApp options (takes precedence)
60
72
  if (options.logger !== undefined) {
61
73
  applyLoggingConfiguration(undefined, options.logger);
62
74
  }
63
75
 
64
- // Create logger AFTER initial configuration
65
- this.logger = createFrameworkLogger('App');
66
-
67
- // Use simplified global configuration system
68
- this.config = initializeConfig(options);
76
+ // Apply performance configuration from createApp options (takes precedence)
77
+ if (options.performance) {
78
+ if (options.performance.clustering) {
79
+ this.config.performance.clustering = {
80
+ ...this.config.performance.clustering,
81
+ ...options.performance.clustering,
82
+ };
83
+ }
84
+ if (options.performance.compression) {
85
+ this.config.performance.compression = {
86
+ ...this.config.performance.compression,
87
+ ...options.performance.compression,
88
+ };
89
+ }
90
+ if (options.performance.circuitBreaker) {
91
+ this.config.performance.circuitBreaker = {
92
+ ...this.config.performance.circuitBreaker,
93
+ ...options.performance.circuitBreaker,
94
+ };
95
+ }
96
+ }
69
97
 
70
- // Apply config file logging if it exists (may override createApp options if needed)
71
- if (this.config.logging && !options.logger) {
72
- applyLoggingConfiguration(this.config.logging, undefined);
73
- // Recreate logger with updated config
74
- this.logger = createFrameworkLogger('App');
98
+ // Apply modules configuration from createApp options (takes precedence)
99
+ if (options.modules) {
100
+ if (options.modules.cache) {
101
+ this.config.modules.cache = {
102
+ ...this.config.modules.cache,
103
+ ...options.modules.cache,
104
+ };
105
+ }
106
+ if (options.modules.rateLimit) {
107
+ this.config.modules.rateLimit = {
108
+ ...this.config.modules.rateLimit,
109
+ ...options.modules.rateLimit,
110
+ };
111
+ }
112
+ if (options.modules.validation) {
113
+ this.config.modules.validation = {
114
+ ...this.config.modules.validation,
115
+ ...options.modules.validation,
116
+ };
117
+ }
75
118
  }
76
119
 
77
120
  this.logger.info(
78
- `Configuration system initialized: ${process.env.NODE_ENV || 'development'}:${this.config.server.port}`
121
+ `Configuration system initialized: ${this.config.server.environment}:${this.config.server.port}`
79
122
  );
80
123
 
81
124
  // Initialize runtime system
@@ -84,12 +127,10 @@ export class Moro extends EventEmitter {
84
127
 
85
128
  this.logger.info(`Runtime system initialized: ${this.runtimeType}`, 'Runtime');
86
129
 
87
- // Pass configuration from config to framework
130
+ // Pass logging configuration from config to framework
88
131
  const frameworkOptions: any = {
89
132
  ...options,
90
133
  logger: this.config.logging,
91
- websocket: this.config.websocket.enabled ? options.websocket || {} : false,
92
- config: this.config,
93
134
  };
94
135
 
95
136
  this.coreFramework = new MoroCore(frameworkOptions);
@@ -121,10 +162,10 @@ export class Moro extends EventEmitter {
121
162
  ...options,
122
163
  });
123
164
 
124
- // Store auto-discovery options for later initialization
125
- // IMPORTANT: Auto-discovery is deferred to ensure user middleware (like auth)
126
- // is registered before module middleware that might bypass it
127
- this.autoDiscoveryOptions = options.autoDiscover !== false ? options : null;
165
+ // Auto-discover modules if enabled
166
+ if (options.autoDiscover !== false) {
167
+ this.autoDiscoverModules(options.modulesPath || './modules');
168
+ }
128
169
 
129
170
  // Emit initialization event through enterprise event bus
130
171
  this.eventBus.emit('framework:initialized', {
@@ -353,13 +394,6 @@ export class Moro extends EventEmitter {
353
394
  version: moduleOrPath.version || '1.0.0',
354
395
  });
355
396
  }
356
-
357
- // IMPORTANT: If modules are loaded manually after auto-discovery,
358
- // ensure the final module handler is set up to maintain middleware order
359
- if (this.autoDiscoveryInitialized) {
360
- this.coreFramework.setupFinalModuleHandler();
361
- }
362
-
363
397
  return this;
364
398
  }
365
399
 
@@ -501,162 +535,34 @@ export class Moro extends EventEmitter {
501
535
  this.registerDirectRoutes();
502
536
  }
503
537
 
504
- const startServer = () => {
505
- const actualCallback = () => {
506
- const displayHost = host || 'localhost';
507
- this.logger.info('Moro Server Started', 'Server');
508
- this.logger.info(`Runtime: ${this.runtimeType}`, 'Server');
509
- this.logger.info(`HTTP API: http://${displayHost}:${port}`, 'Server');
510
- if (this.config.websocket.enabled) {
511
- this.logger.info(`WebSocket: ws://${displayHost}:${port}`, 'Server');
512
- }
513
- this.logger.info('Learn more at https://morojs.com', 'Server');
514
-
515
- // Log intelligent routes info
516
- const intelligentRoutes = this.intelligentRouting.getIntelligentRoutes();
517
- if (intelligentRoutes.length > 0) {
518
- this.logger.info(`Intelligent Routes: ${intelligentRoutes.length} registered`, 'Server');
519
- }
520
-
521
- this.eventBus.emit('server:started', { port, runtime: this.runtimeType });
522
- if (callback) callback();
523
- };
524
-
525
- if (host && typeof host === 'string') {
526
- this.coreFramework.listen(port, host, actualCallback);
527
- } else {
528
- this.coreFramework.listen(port, actualCallback);
538
+ const actualCallback = () => {
539
+ const displayHost = host || 'localhost';
540
+ this.logger.info('Moro Server Started', 'Server');
541
+ this.logger.info(`Runtime: ${this.runtimeType}`, 'Server');
542
+ this.logger.info(`HTTP API: http://${displayHost}:${port}`, 'Server');
543
+ this.logger.info(`WebSocket: ws://${displayHost}:${port}`, 'Server');
544
+ this.logger.info('Native Node.js HTTP • Zero Dependencies • Maximum Performance', 'Server');
545
+ this.logger.info('Learn more at https://morojs.com', 'Server');
546
+
547
+ // Log intelligent routes info
548
+ const intelligentRoutes = this.intelligentRouting.getIntelligentRoutes();
549
+ if (intelligentRoutes.length > 0) {
550
+ this.logger.info(`Intelligent Routes: ${intelligentRoutes.length} registered`, 'Server');
529
551
  }
530
- };
531
-
532
- // Ensure auto-discovery is complete before starting server
533
- this.ensureAutoDiscoveryComplete()
534
- .then(() => {
535
- startServer();
536
- })
537
- .catch(error => {
538
- this.logger.error('Auto-discovery initialization failed during server start', 'Framework', {
539
- error: error instanceof Error ? error.message : String(error),
540
- });
541
- // Start server anyway - auto-discovery failure shouldn't prevent startup
542
- startServer();
543
- });
544
- }
545
-
546
- // Public method to manually initialize auto-discovery
547
- // Useful for ensuring auth middleware is registered before auto-discovery
548
- async initializeAutoDiscoveryNow(): Promise<void> {
549
- return this.ensureAutoDiscoveryComplete();
550
- }
551
552
 
552
- // Public API: Initialize modules explicitly after middleware setup
553
- // This provides users with explicit control over module loading timing
554
- // IMPORTANT: This forces module loading even if autoDiscovery.enabled is false
555
- // Usage: app.initModules() or app.initModules({ paths: ['./my-modules'] })
556
- initModules(options?: {
557
- paths?: string[];
558
- patterns?: string[];
559
- recursive?: boolean;
560
- loadingStrategy?: 'eager' | 'lazy' | 'conditional';
561
- watchForChanges?: boolean;
562
- ignorePatterns?: string[];
563
- loadOrder?: 'alphabetical' | 'dependency' | 'custom';
564
- failOnError?: boolean;
565
- maxDepth?: number;
566
- }): void {
567
- this.logger.info('User-requested module initialization', 'ModuleSystem');
568
-
569
- // If already initialized, do nothing
570
- if (this.autoDiscoveryInitialized) {
571
- this.logger.debug('Auto-discovery already completed, skipping', 'ModuleSystem');
572
- return;
573
- }
574
-
575
- // Store the options and mark that we want to force initialization
576
- this.autoDiscoveryOptions = {
577
- autoDiscover: {
578
- enabled: true, // Force enabled regardless of original config
579
- paths: options?.paths || ['./modules', './src/modules'],
580
- patterns: options?.patterns || [
581
- '**/*.module.{ts,js}',
582
- '**/index.{ts,js}',
583
- '**/*.config.{ts,js}',
584
- ],
585
- recursive: options?.recursive ?? true,
586
- loadingStrategy: options?.loadingStrategy || ('eager' as const),
587
- watchForChanges: options?.watchForChanges ?? false,
588
- ignorePatterns: options?.ignorePatterns || [
589
- '**/*.test.{ts,js}',
590
- '**/*.spec.{ts,js}',
591
- '**/node_modules/**',
592
- ],
593
- loadOrder: options?.loadOrder || ('dependency' as const),
594
- failOnError: options?.failOnError ?? false,
595
- maxDepth: options?.maxDepth ?? 5,
596
- },
553
+ this.eventBus.emit('server:started', { port, runtime: this.runtimeType });
554
+ if (callback) callback();
597
555
  };
598
556
 
599
- this.logger.debug(
600
- 'Module initialization options stored, will execute on next listen/getHandler call',
601
- 'ModuleSystem'
602
- );
603
- }
604
-
605
- // Robust method to ensure auto-discovery is complete, handling race conditions
606
- private async ensureAutoDiscoveryComplete(): Promise<void> {
607
- // If already initialized, nothing to do
608
- if (this.autoDiscoveryInitialized) {
609
- return;
610
- }
611
-
612
- // If auto-discovery is disabled, mark as initialized
613
- if (!this.autoDiscoveryOptions) {
614
- this.autoDiscoveryInitialized = true;
615
- return;
616
- }
617
-
618
- // If already in progress, wait for it to complete
619
- if (this.autoDiscoveryPromise) {
620
- return this.autoDiscoveryPromise;
621
- }
622
-
623
- // Start auto-discovery
624
- this.autoDiscoveryPromise = this.performAutoDiscovery();
625
-
626
- try {
627
- await this.autoDiscoveryPromise;
628
- this.autoDiscoveryInitialized = true;
629
- } catch (error) {
630
- // Reset promise on error so it can be retried
631
- this.autoDiscoveryPromise = null;
632
- throw error;
633
- } finally {
634
- this.autoDiscoveryOptions = null; // Clear after attempt
557
+ if (host && typeof host === 'string') {
558
+ this.coreFramework.listen(port, host, actualCallback);
559
+ } else {
560
+ this.coreFramework.listen(port, actualCallback);
635
561
  }
636
562
  }
637
563
 
638
- // Perform the actual auto-discovery work
639
- private async performAutoDiscovery(optionsOverride?: MoroOptions): Promise<void> {
640
- const optionsToUse = optionsOverride || this.autoDiscoveryOptions;
641
- if (!optionsToUse) return;
642
-
643
- this.logger.debug('Starting auto-discovery initialization', 'AutoDiscovery');
644
-
645
- await this.initializeAutoDiscovery(optionsToUse);
646
-
647
- this.logger.debug('Auto-discovery initialization completed', 'AutoDiscovery');
648
- }
649
-
650
564
  // Get handler for non-Node.js runtimes
651
565
  getHandler() {
652
- // Ensure auto-discovery is complete for non-Node.js runtimes
653
- // This handles the case where users call getHandler() immediately after createApp()
654
- this.ensureAutoDiscoveryComplete().catch(error => {
655
- this.logger.error('Auto-discovery initialization failed for runtime handler', 'Framework', {
656
- error: error instanceof Error ? error.message : String(error),
657
- });
658
- });
659
-
660
566
  // Create a unified request handler that works with the runtime adapter
661
567
  const handler = async (req: HttpRequest, res: HttpResponse) => {
662
568
  // Add documentation middleware first (if enabled)
@@ -980,33 +886,20 @@ export class Moro extends EventEmitter {
980
886
  }
981
887
 
982
888
  private setupDefaultMiddleware(options: MoroOptions) {
983
- // CORS - check config enabled property OR options.security.cors.enabled === true
984
- if (this.config.security.cors.enabled || options.security?.cors?.enabled === true) {
985
- const corsOptions =
986
- typeof options.cors === 'object'
987
- ? options.cors
988
- : this.config.security.cors
989
- ? this.config.security.cors
990
- : {};
889
+ // CORS
890
+ if (options.cors !== false) {
891
+ const corsOptions = typeof options.cors === 'object' ? options.cors : {};
991
892
  this.use(middleware.cors(corsOptions));
992
893
  }
993
894
 
994
- // Helmet - check config enabled property OR options.security.helmet.enabled === true
995
- if (this.config.security.helmet.enabled || options.security?.helmet?.enabled === true) {
895
+ // Helmet
896
+ if (options.helmet !== false) {
996
897
  this.use(middleware.helmet());
997
898
  }
998
899
 
999
- // Compression - check config enabled property OR options.performance.compression.enabled === true
1000
- if (
1001
- this.config.performance.compression.enabled ||
1002
- options.performance?.compression?.enabled === true
1003
- ) {
1004
- const compressionOptions =
1005
- typeof options.compression === 'object'
1006
- ? options.compression
1007
- : this.config.performance.compression
1008
- ? this.config.performance.compression
1009
- : {};
900
+ // Compression
901
+ if (options.compression !== false) {
902
+ const compressionOptions = typeof options.compression === 'object' ? options.compression : {};
1010
903
  this.use(middleware.compression(compressionOptions));
1011
904
  }
1012
905
 
@@ -1014,216 +907,27 @@ export class Moro extends EventEmitter {
1014
907
  this.use(middleware.bodySize({ limit: '10mb' }));
1015
908
  }
1016
909
 
1017
- // Enhanced auto-discovery initialization
1018
- private async initializeAutoDiscovery(options: MoroOptions): Promise<void> {
1019
- const { ModuleDiscovery } = await import('./core/modules/auto-discovery');
1020
-
1021
- // Merge auto-discovery configuration
1022
- const autoDiscoveryConfig = this.mergeAutoDiscoveryConfig(options);
1023
-
1024
- if (!autoDiscoveryConfig.enabled) {
1025
- return;
1026
- }
1027
-
1028
- this.moduleDiscovery = new ModuleDiscovery(process.cwd());
1029
-
910
+ private autoDiscoverModules(modulesPath: string) {
1030
911
  try {
1031
- // Discover modules based on configuration
1032
- const modules = await this.moduleDiscovery.discoverModulesAdvanced(autoDiscoveryConfig);
1033
-
1034
- // Load modules based on strategy
1035
- await this.loadDiscoveredModules(modules, autoDiscoveryConfig);
1036
-
1037
- // Setup final module handler to run after user middleware (like auth)
1038
- this.coreFramework.setupFinalModuleHandler();
1039
-
1040
- // Setup file watching if enabled
1041
- if (autoDiscoveryConfig.watchForChanges) {
1042
- this.moduleDiscovery.watchModulesAdvanced(
1043
- autoDiscoveryConfig,
1044
- async (updatedModules: ModuleConfig[]) => {
1045
- await this.handleModuleChanges(updatedModules);
912
+ if (!statSync(modulesPath).isDirectory()) return;
913
+
914
+ const items = readdirSync(modulesPath);
915
+ items.forEach(item => {
916
+ const fullPath = join(modulesPath, item);
917
+ if (statSync(fullPath).isDirectory()) {
918
+ const indexPath = join(fullPath, 'index.ts');
919
+ try {
920
+ statSync(indexPath);
921
+ // Module directory found, will be loaded later
922
+ this.logger.debug(`Discovered module: ${item}`, 'ModuleDiscovery');
923
+ } catch {
924
+ // No index.ts, skip
1046
925
  }
1047
- );
1048
- }
1049
-
1050
- this.logger.info(
1051
- `Auto-discovery completed: ${modules.length} modules loaded`,
1052
- 'ModuleDiscovery'
1053
- );
1054
- } catch (error) {
1055
- const errorMsg = error instanceof Error ? error.message : String(error);
1056
-
1057
- if (autoDiscoveryConfig.failOnError) {
1058
- throw new Error(`Module auto-discovery failed: ${errorMsg}`);
1059
- } else {
1060
- this.logger.warn(`Module auto-discovery failed: ${errorMsg}`, 'ModuleDiscovery');
1061
- }
1062
- }
1063
- }
1064
-
1065
- // Merge auto-discovery configuration from multiple sources
1066
- private mergeAutoDiscoveryConfig(options: MoroOptions) {
1067
- const defaultConfig = this.config.modules.autoDiscovery;
1068
-
1069
- // Handle legacy modulesPath option
1070
- if (options.modulesPath && !options.autoDiscover) {
1071
- return {
1072
- ...defaultConfig,
1073
- paths: [options.modulesPath],
1074
- };
1075
- }
1076
-
1077
- // Handle boolean autoDiscover option
1078
- if (typeof options.autoDiscover === 'boolean') {
1079
- return {
1080
- ...defaultConfig,
1081
- enabled: options.autoDiscover,
1082
- };
1083
- }
1084
-
1085
- // Handle object autoDiscover option
1086
- if (typeof options.autoDiscover === 'object') {
1087
- return {
1088
- ...defaultConfig,
1089
- ...options.autoDiscover,
1090
- };
1091
- }
1092
-
1093
- return defaultConfig;
1094
- }
1095
-
1096
- // Load discovered modules based on strategy
1097
- private async loadDiscoveredModules(
1098
- modules: ModuleConfig[],
1099
- config: ModuleDefaultsConfig['autoDiscovery']
1100
- ): Promise<void> {
1101
- switch (config.loadingStrategy) {
1102
- case 'eager':
1103
- // Load all modules immediately
1104
- for (const module of modules) {
1105
- await this.loadModule(module);
1106
- }
1107
- break;
1108
-
1109
- case 'lazy':
1110
- // Register modules for lazy loading
1111
- this.registerLazyModules(modules);
1112
- break;
1113
-
1114
- case 'conditional':
1115
- // Load modules based on conditions
1116
- await this.loadConditionalModules(modules);
1117
- break;
1118
-
1119
- default:
1120
- // Default to eager loading
1121
- for (const module of modules) {
1122
- await this.loadModule(module);
1123
- }
1124
- }
1125
- }
1126
-
1127
- // Register modules for lazy loading
1128
- private registerLazyModules(modules: ModuleConfig[]): void {
1129
- modules.forEach(module => {
1130
- // Store module for lazy loading when first route is accessed
1131
- this.lazyModules.set(module.name, module);
1132
-
1133
- // Register placeholder routes that trigger lazy loading
1134
- if (module.routes) {
1135
- module.routes.forEach(route => {
1136
- const basePath = `/api/v${module.version}/${module.name}`;
1137
- const fullPath = `${basePath}${route.path}`;
1138
-
1139
- // Note: Lazy loading will be implemented when route is accessed
1140
- // For now, we'll store the module for later loading
1141
- this.logger.debug(
1142
- `Registered lazy route: ${route.method} ${fullPath}`,
1143
- 'ModuleDiscovery'
1144
- );
1145
- });
1146
- }
1147
- });
1148
-
1149
- this.logger.info(`Registered ${modules.length} modules for lazy loading`, 'ModuleDiscovery');
1150
- }
1151
-
1152
- // Load modules conditionally based on environment or configuration
1153
- private async loadConditionalModules(modules: ModuleConfig[]): Promise<void> {
1154
- for (const module of modules) {
1155
- const shouldLoad = this.shouldLoadModule(module);
1156
-
1157
- if (shouldLoad) {
1158
- await this.loadModule(module);
1159
- } else {
1160
- this.logger.debug(`Skipping module ${module.name} due to conditions`, 'ModuleDiscovery');
1161
- }
1162
- }
1163
- }
1164
-
1165
- // Determine if a module should be loaded based on conditions
1166
- private shouldLoadModule(module: ModuleConfig): boolean {
1167
- const moduleConfig = module.config as any;
1168
-
1169
- // Check environment conditions
1170
- if (moduleConfig?.conditions?.environment) {
1171
- const requiredEnv = moduleConfig.conditions.environment;
1172
- const currentEnv = process.env.NODE_ENV || 'development';
1173
-
1174
- if (Array.isArray(requiredEnv)) {
1175
- if (!requiredEnv.includes(currentEnv)) {
1176
- return false;
1177
- }
1178
- } else if (requiredEnv !== currentEnv) {
1179
- return false;
1180
- }
1181
- }
1182
-
1183
- // Check feature flags
1184
- if (moduleConfig?.conditions?.features) {
1185
- const requiredFeatures = moduleConfig.conditions.features;
1186
-
1187
- for (const feature of requiredFeatures) {
1188
- if (!process.env[`FEATURE_${feature.toUpperCase()}`]) {
1189
- return false;
1190
926
  }
1191
- }
1192
- }
1193
-
1194
- // Check custom conditions
1195
- if (moduleConfig?.conditions?.custom) {
1196
- const customCondition = moduleConfig.conditions.custom;
1197
-
1198
- if (typeof customCondition === 'function') {
1199
- return customCondition();
1200
- }
927
+ });
928
+ } catch {
929
+ // Modules directory doesn't exist, that's fine
1201
930
  }
1202
-
1203
- return true;
1204
- }
1205
-
1206
- // Handle module changes during development
1207
- private async handleModuleChanges(modules: ModuleConfig[]): Promise<void> {
1208
- this.logger.info('Module changes detected, reloading...', 'ModuleDiscovery');
1209
-
1210
- // Unload existing modules (if supported)
1211
- // For now, just log the change
1212
- this.eventBus.emit('modules:changed', {
1213
- modules: modules.map(m => ({ name: m.name, version: m.version })),
1214
- timestamp: new Date(),
1215
- });
1216
- }
1217
-
1218
- // Legacy method for backward compatibility
1219
- private autoDiscoverModules(modulesPath: string) {
1220
- // Redirect to new system
1221
- this.initializeAutoDiscovery({
1222
- autoDiscover: {
1223
- enabled: true,
1224
- paths: [modulesPath],
1225
- },
1226
- });
1227
931
  }
1228
932
 
1229
933
  private async importModule(modulePath: string): Promise<ModuleConfig> {
@@ -1231,84 +935,55 @@ export class Moro extends EventEmitter {
1231
935
  return module.default || module;
1232
936
  }
1233
937
 
1234
- /**
1235
- * Node.js Clustering Implementation
1236
- * This clustering algorithm is based on published research and Node.js best practices.
1237
- *
1238
- * IPC (Inter-Process Communication) Considerations:
1239
- * - Excessive workers create IPC bottlenecks (Source: BetterStack Node.js Guide)
1240
- * - Round-robin scheduling provides better load distribution (Node.js Documentation)
1241
- * - Message passing overhead increases significantly with worker count
1242
- *
1243
- * Memory Management:
1244
- * - ~2GB per worker prevents memory pressure and GC overhead
1245
- * - Conservative heap limits reduce memory fragmentation
1246
- *
1247
- * References:
1248
- * - Node.js Cluster Documentation: https://nodejs.org/api/cluster.html
1249
- * - BetterStack Node.js Clustering: https://betterstack.com/community/guides/scaling-nodejs/node-clustering/
1250
- */
938
+ // Clustering support for massive performance gains with proper cleanup
1251
939
  private clusterWorkers = new Map<number, any>();
1252
-
1253
940
  private startWithClustering(port: number, host?: string, callback?: () => void): void {
1254
941
  const cluster = require('cluster');
1255
942
  const os = require('os');
1256
943
 
1257
- // Worker count calculation - respect user choice
944
+ // Smart worker count calculation based on actual bottlenecks
1258
945
  let workerCount = this.config.performance?.clustering?.workers || os.cpus().length;
1259
946
 
1260
- // Only auto-optimize if user hasn't specified a number or set it to 'auto'
1261
- if (workerCount === 'auto') {
947
+ // Auto-optimize worker count based on system characteristics
948
+ if (workerCount === 'auto' || workerCount > 8) {
949
+ // For high-core machines, limit workers to prevent IPC/memory bottlenecks
1262
950
  const cpuCount = os.cpus().length;
1263
951
  const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
1264
952
 
1265
- // Get memory per worker from config - if not set by user, calculate dynamically
1266
- let memoryPerWorkerGB = this.config.performance?.clustering?.memoryPerWorkerGB;
1267
-
1268
- if (!memoryPerWorkerGB) {
1269
- // Dynamic calculation: (Total RAM - 4GB headroom) / CPU cores
1270
- const headroomGB = 4;
1271
- memoryPerWorkerGB = Math.max(0.5, Math.floor((totalMemoryGB - headroomGB) / cpuCount));
953
+ // Optimal worker count formula based on research
954
+ if (cpuCount >= 16) {
955
+ // High-core machines: focus on memory/IPC efficiency
956
+ workerCount = Math.min(Math.ceil(totalMemoryGB / 2), 4); // 2GB per worker max, cap at 4
957
+ } else if (cpuCount >= 8) {
958
+ // Mid-range machines: balanced approach
959
+ workerCount = Math.min(cpuCount / 2, 4);
960
+ } else {
961
+ // Low-core machines: use all cores
962
+ workerCount = cpuCount;
1272
963
  }
1273
964
 
1274
- // Conservative formula based on general guidelines:
1275
- // - Don't exceed CPU cores
1276
- // - Respect user's memory allocation preference
1277
- // - Let the system resources determine the limit
1278
- workerCount = Math.min(
1279
- cpuCount, // Don't exceed CPU cores
1280
- Math.floor(totalMemoryGB / memoryPerWorkerGB) // User-configurable memory per worker
1281
- );
1282
-
1283
965
  this.logger.info(
1284
- `Auto-calculated worker count: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB, ${memoryPerWorkerGB}GB per worker)`,
966
+ `Auto-optimized workers: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB)`,
1285
967
  'Cluster'
1286
968
  );
1287
- } else if (typeof workerCount === 'number') {
1288
- // User specified a number - respect their choice
1289
- this.logger.info(`Using user-specified worker count: ${workerCount}`, 'Cluster');
1290
969
  }
1291
970
 
1292
971
  if (cluster.isPrimary) {
1293
- this.logger.info(`Starting ${workerCount} workers`, 'Cluster');
972
+ this.logger.info(`🚀 Starting ${workerCount} workers for maximum performance`, 'Cluster');
1294
973
 
1295
974
  // Optimize cluster scheduling for high concurrency
1296
- // Round-robin is the default on all platforms except Windows (Node.js docs)
1297
- // Provides better load distribution than shared socket approach
1298
- cluster.schedulingPolicy = cluster.SCHED_RR;
975
+ cluster.schedulingPolicy = cluster.SCHED_RR; // Round-robin scheduling
1299
976
 
1300
977
  // Set cluster settings for better performance
1301
978
  cluster.setupMaster({
1302
- exec: process.argv[1] || process.execPath,
979
+ exec: process.argv[1],
1303
980
  args: process.argv.slice(2),
1304
981
  silent: false,
1305
982
  });
1306
983
 
1307
- // IPC Optimization: Reduce communication overhead between master and workers
1308
- // Research shows excessive IPC can create bottlenecks in clustered applications
1309
- // (Source: BetterStack - Node.js Clustering Guide)
984
+ // Optimize IPC to reduce communication overhead
1310
985
  process.env.NODE_CLUSTER_SCHED_POLICY = 'rr'; // Ensure round-robin
1311
- process.env.NODE_DISABLE_COLORS = '1'; // Reduce IPC message size by disabling color codes
986
+ process.env.NODE_DISABLE_COLORS = '1'; // Reduce IPC message size
1312
987
 
1313
988
  // Graceful shutdown handler
1314
989
  const gracefulShutdown = () => {
@@ -1329,32 +1004,37 @@ export class Moro extends EventEmitter {
1329
1004
  process.on('SIGINT', gracefulShutdown);
1330
1005
  process.on('SIGTERM', gracefulShutdown);
1331
1006
 
1332
- // Fork workers with basic tracking
1007
+ // Fork workers with proper tracking and CPU affinity
1333
1008
  for (let i = 0; i < workerCount; i++) {
1334
- const worker = cluster.fork();
1009
+ const worker = cluster.fork({
1010
+ WORKER_ID: i,
1011
+ WORKER_CPU_AFFINITY: i % os.cpus().length, // Distribute workers across CPUs
1012
+ });
1335
1013
  this.clusterWorkers.set(worker.process.pid!, worker);
1336
- this.logger.info(`Worker ${worker.process.pid} started`, 'Cluster');
1014
+ this.logger.info(
1015
+ `Worker ${worker.process.pid} started (CPU ${i % os.cpus().length})`,
1016
+ 'Cluster'
1017
+ );
1337
1018
 
1338
- // Handle individual worker messages
1019
+ // Handle individual worker messages (reuse handler)
1339
1020
  worker.on('message', this.handleWorkerMessage.bind(this));
1340
1021
  }
1341
1022
 
1342
- // Simple worker exit handling
1023
+ // Handle worker exits with cleanup
1343
1024
  cluster.on('exit', (worker: any, code: number, signal: string) => {
1344
- const pid = worker.process.pid;
1345
- this.clusterWorkers.delete(pid);
1346
-
1347
- if (code !== 0 && !worker.exitedAfterDisconnect) {
1348
- this.logger.warn(
1349
- `Worker ${pid} died unexpectedly (${signal || code}). Restarting...`,
1350
- 'Cluster'
1351
- );
1352
-
1353
- // Simple restart
1354
- const newWorker = cluster.fork();
1355
- this.clusterWorkers.set(newWorker.process.pid!, newWorker);
1356
- this.logger.info(`Worker ${newWorker.process.pid} restarted`, 'Cluster');
1357
- }
1025
+ // Clean up worker tracking
1026
+ this.clusterWorkers.delete(worker.process.pid);
1027
+
1028
+ this.logger.warn(
1029
+ `Worker ${worker.process.pid} died (${signal || code}). Restarting...`,
1030
+ 'Cluster'
1031
+ );
1032
+
1033
+ // Restart worker with proper tracking
1034
+ const newWorker = cluster.fork();
1035
+ this.clusterWorkers.set(newWorker.process.pid!, newWorker);
1036
+ newWorker.on('message', this.handleWorkerMessage.bind(this));
1037
+ this.logger.info(`Worker ${newWorker.process.pid} started`, 'Cluster');
1358
1038
  });
1359
1039
 
1360
1040
  // Master process callback
@@ -1367,29 +1047,13 @@ export class Moro extends EventEmitter {
1367
1047
  process.env.UV_THREADPOOL_SIZE = '64';
1368
1048
 
1369
1049
  // Reduce logging contention in workers (major bottleneck)
1370
- // Multiple workers writing to same log files creates I/O contention
1371
1050
  if (this.config.logging) {
1372
1051
  // Workers log less frequently to reduce I/O contention
1373
- applyLoggingConfiguration(undefined, { level: 'warn' }); // Only warnings and errors
1052
+ this.config.logging.level = 'warn'; // Only warnings and errors
1374
1053
  }
1375
1054
 
1376
- // Research-based memory optimization for workers
1377
- const os = require('os');
1378
- const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
1379
- const workerCount = Object.keys(require('cluster').workers || {}).length || 1;
1380
-
1381
- // Conservative memory allocation
1382
- const heapSizePerWorkerMB = Math.min(
1383
- Math.floor(((totalMemoryGB * 1024) / workerCount) * 0.8), // 80% of available memory
1384
- 1536 // Cap at 1.5GB (GC efficiency threshold from research)
1385
- );
1386
-
1387
- process.env.NODE_OPTIONS = `--max-old-space-size=${heapSizePerWorkerMB}`;
1388
-
1389
- this.logger.debug(
1390
- `Worker memory allocated: ${heapSizePerWorkerMB}MB heap (${workerCount} workers, ${totalMemoryGB.toFixed(1)}GB total)`,
1391
- 'Worker'
1392
- );
1055
+ // Memory optimization for workers
1056
+ process.env.NODE_OPTIONS = '--max-old-space-size=1024'; // Limit memory per worker
1393
1057
 
1394
1058
  // Optimize V8 flags for better performance (Rust-level optimizations)
1395
1059
  if (process.env.NODE_ENV === 'production') {
@@ -1485,7 +1149,7 @@ export class Moro extends EventEmitter {
1485
1149
  }
1486
1150
  }
1487
1151
 
1488
- // Simple worker message handler
1152
+ // Reusable worker message handler (avoids creating new functions)
1489
1153
  private handleWorkerMessage(message: any): void {
1490
1154
  // Handle inter-worker communication if needed
1491
1155
  if (message.type === 'health-check') {
@@ -1496,58 +1160,6 @@ export class Moro extends EventEmitter {
1496
1160
  // Log other worker messages
1497
1161
  this.logger.debug(`Worker message: ${JSON.stringify(message)}`, 'Cluster');
1498
1162
  }
1499
-
1500
- /**
1501
- * Gracefully close the application and clean up resources
1502
- * This should be called in tests and during shutdown
1503
- */
1504
- async close(): Promise<void> {
1505
- this.logger.debug('Closing Moro application...');
1506
-
1507
- // Flush logger buffer before shutdown
1508
- try {
1509
- // Use flushBuffer for immediate synchronous flush
1510
- this.logger.flushBuffer();
1511
- } catch (error) {
1512
- // Ignore flush errors during shutdown
1513
- }
1514
-
1515
- // Close the core framework with timeout
1516
- if (this.coreFramework && (this.coreFramework as any).httpServer) {
1517
- try {
1518
- await Promise.race([
1519
- new Promise<void>(resolve => {
1520
- (this.coreFramework as any).httpServer.close(() => {
1521
- resolve();
1522
- });
1523
- }),
1524
- new Promise<void>(resolve => setTimeout(resolve, 2000)), // 2 second timeout
1525
- ]);
1526
- } catch (error) {
1527
- // Force close if graceful close fails
1528
- this.logger.warn('Force closing HTTP server due to timeout');
1529
- }
1530
- }
1531
-
1532
- // Clean up module discovery watchers
1533
- if (this.moduleDiscovery && typeof this.moduleDiscovery.cleanup === 'function') {
1534
- try {
1535
- this.moduleDiscovery.cleanup();
1536
- } catch (error) {
1537
- // Ignore cleanup errors
1538
- }
1539
- }
1540
-
1541
- // Clean up event listeners
1542
- try {
1543
- this.eventBus.removeAllListeners();
1544
- this.removeAllListeners();
1545
- } catch (error) {
1546
- // Ignore cleanup errors
1547
- }
1548
-
1549
- this.logger.debug('Moro application closed successfully');
1550
- }
1551
1163
  }
1552
1164
 
1553
1165
  // Export convenience function