@morojs/moro 1.5.16 → 1.5.17

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.
package/src/moro.ts CHANGED
@@ -27,6 +27,9 @@ export class Moro extends EventEmitter {
27
27
  private lazyModules = new Map<string, ModuleConfig>();
28
28
  private routeHandlers: Record<string, Function> = {};
29
29
  private moduleDiscovery?: any; // Store for cleanup
30
+ private autoDiscoveryOptions: MoroOptions | null = null;
31
+ private autoDiscoveryInitialized = false;
32
+ private autoDiscoveryPromise: Promise<void> | null = null;
30
33
  // Enterprise event system integration
31
34
  private eventBus: MoroEventBus;
32
35
  // Application logger
@@ -118,15 +121,10 @@ export class Moro extends EventEmitter {
118
121
  ...options,
119
122
  });
120
123
 
121
- // Auto-discover modules if enabled
122
- if (options.autoDiscover !== false) {
123
- // Initialize auto-discovery asynchronously
124
- this.initializeAutoDiscovery(options).catch(error => {
125
- this.logger.error('Auto-discovery initialization failed', 'Framework', {
126
- error: error instanceof Error ? error.message : String(error),
127
- });
128
- });
129
- }
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;
130
128
 
131
129
  // Emit initialization event through enterprise event bus
132
130
  this.eventBus.emit('framework:initialized', {
@@ -355,6 +353,13 @@ export class Moro extends EventEmitter {
355
353
  version: moduleOrPath.version || '1.0.0',
356
354
  });
357
355
  }
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
+
358
363
  return this;
359
364
  }
360
365
 
@@ -496,35 +501,162 @@ export class Moro extends EventEmitter {
496
501
  this.registerDirectRoutes();
497
502
  }
498
503
 
499
- const actualCallback = () => {
500
- const displayHost = host || 'localhost';
501
- this.logger.info('Moro Server Started', 'Server');
502
- this.logger.info(`Runtime: ${this.runtimeType}`, 'Server');
503
- this.logger.info(`HTTP API: http://${displayHost}:${port}`, 'Server');
504
- if (this.config.websocket.enabled) {
505
- this.logger.info(`WebSocket: ws://${displayHost}:${port}`, 'Server');
506
- }
507
- this.logger.info('Learn more at https://morojs.com', 'Server');
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
+ };
508
524
 
509
- // Log intelligent routes info
510
- const intelligentRoutes = this.intelligentRouting.getIntelligentRoutes();
511
- if (intelligentRoutes.length > 0) {
512
- this.logger.info(`Intelligent Routes: ${intelligentRoutes.length} registered`, 'Server');
525
+ if (host && typeof host === 'string') {
526
+ this.coreFramework.listen(port, host, actualCallback);
527
+ } else {
528
+ this.coreFramework.listen(port, actualCallback);
513
529
  }
530
+ };
514
531
 
515
- this.eventBus.emit('server:started', { port, runtime: this.runtimeType });
516
- if (callback) callback();
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
+ // 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
+ },
517
597
  };
518
598
 
519
- if (host && typeof host === 'string') {
520
- this.coreFramework.listen(port, host, actualCallback);
521
- } else {
522
- this.coreFramework.listen(port, actualCallback);
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
523
635
  }
524
636
  }
525
637
 
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
+
526
650
  // Get handler for non-Node.js runtimes
527
651
  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
+
528
660
  // Create a unified request handler that works with the runtime adapter
529
661
  const handler = async (req: HttpRequest, res: HttpResponse) => {
530
662
  // Add documentation middleware first (if enabled)
@@ -902,6 +1034,9 @@ export class Moro extends EventEmitter {
902
1034
  // Load modules based on strategy
903
1035
  await this.loadDiscoveredModules(modules, autoDiscoveryConfig);
904
1036
 
1037
+ // Setup final module handler to run after user middleware (like auth)
1038
+ this.coreFramework.setupFinalModuleHandler();
1039
+
905
1040
  // Setup file watching if enabled
906
1041
  if (autoDiscoveryConfig.watchForChanges) {
907
1042
  this.moduleDiscovery.watchModulesAdvanced(