@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.
package/src/moro.ts CHANGED
@@ -5,41 +5,28 @@ 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';
8
9
  import { MoroEventBus } from './core/events';
9
- import {
10
- createFrameworkLogger,
11
- logger as globalLogger,
12
- applyLoggingConfiguration,
13
- } from './core/logger';
10
+ import { createFrameworkLogger, applyLoggingConfiguration } from './core/logger';
14
11
  import { Logger } from './types/logger';
15
12
  import { MiddlewareManager } from './core/middleware';
16
13
  import { IntelligentRoutingManager } from './core/routing/app-integration';
17
14
  import { RouteBuilder, RouteSchema, CompiledRoute } from './core/routing';
18
15
  import { AppDocumentationManager, DocsConfig } from './core/docs';
19
- import { readdirSync, statSync } from 'fs';
20
- import { join } from 'path';
21
16
  import { EventEmitter } from 'events';
22
17
  // Configuration System Integration
23
- import {
24
- initializeConfig,
25
- getGlobalConfig,
26
- loadConfigWithOptions,
27
- type AppConfig,
28
- } from './core/config';
18
+ import { initializeConfig, type AppConfig } from './core/config';
29
19
  // Runtime System Integration
30
- import {
31
- RuntimeAdapter,
32
- RuntimeType,
33
- createRuntimeAdapter,
34
- NodeRuntimeAdapter,
35
- } from './core/runtime';
20
+ import { RuntimeAdapter, RuntimeType, createRuntimeAdapter } from './core/runtime';
36
21
 
37
22
  export class Moro extends EventEmitter {
38
23
  private coreFramework: MoroCore;
39
24
  private routes: InternalRouteDefinition[] = [];
40
25
  private moduleCounter = 0;
41
26
  private loadedModules = new Set<string>();
27
+ private lazyModules = new Map<string, ModuleConfig>();
42
28
  private routeHandlers: Record<string, Function> = {};
29
+ private moduleDiscovery?: any; // Store for cleanup
43
30
  // Enterprise event system integration
44
31
  private eventBus: MoroEventBus;
45
32
  // Application logger
@@ -133,7 +120,12 @@ export class Moro extends EventEmitter {
133
120
 
134
121
  // Auto-discover modules if enabled
135
122
  if (options.autoDiscover !== false) {
136
- this.autoDiscoverModules(options.modulesPath || './modules');
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
+ });
137
129
  }
138
130
 
139
131
  // Emit initialization event through enterprise event bus
@@ -890,29 +882,215 @@ export class Moro extends EventEmitter {
890
882
  this.use(middleware.bodySize({ limit: '10mb' }));
891
883
  }
892
884
 
893
- private autoDiscoverModules(modulesPath: string) {
885
+ // Enhanced auto-discovery initialization
886
+ private async initializeAutoDiscovery(options: MoroOptions): Promise<void> {
887
+ const { ModuleDiscovery } = await import('./core/modules/auto-discovery');
888
+
889
+ // Merge auto-discovery configuration
890
+ const autoDiscoveryConfig = this.mergeAutoDiscoveryConfig(options);
891
+
892
+ if (!autoDiscoveryConfig.enabled) {
893
+ return;
894
+ }
895
+
896
+ this.moduleDiscovery = new ModuleDiscovery(process.cwd());
897
+
894
898
  try {
895
- if (!statSync(modulesPath).isDirectory()) return;
896
-
897
- const items = readdirSync(modulesPath);
898
- items.forEach(item => {
899
- const fullPath = join(modulesPath, item);
900
- if (statSync(fullPath).isDirectory()) {
901
- const indexPath = join(fullPath, 'index.ts');
902
- try {
903
- statSync(indexPath);
904
- // Module directory found, will be loaded later
905
- this.logger.debug(`Discovered module: ${item}`, 'ModuleDiscovery');
906
- } catch {
907
- // No index.ts, skip
899
+ // Discover modules based on configuration
900
+ const modules = await this.moduleDiscovery.discoverModulesAdvanced(autoDiscoveryConfig);
901
+
902
+ // Load modules based on strategy
903
+ await this.loadDiscoveredModules(modules, autoDiscoveryConfig);
904
+
905
+ // Setup file watching if enabled
906
+ if (autoDiscoveryConfig.watchForChanges) {
907
+ this.moduleDiscovery.watchModulesAdvanced(
908
+ autoDiscoveryConfig,
909
+ async (updatedModules: ModuleConfig[]) => {
910
+ await this.handleModuleChanges(updatedModules);
908
911
  }
912
+ );
913
+ }
914
+
915
+ this.logger.info(
916
+ `Auto-discovery completed: ${modules.length} modules loaded`,
917
+ 'ModuleDiscovery'
918
+ );
919
+ } catch (error) {
920
+ const errorMsg = error instanceof Error ? error.message : String(error);
921
+
922
+ if (autoDiscoveryConfig.failOnError) {
923
+ throw new Error(`Module auto-discovery failed: ${errorMsg}`);
924
+ } else {
925
+ this.logger.warn(`Module auto-discovery failed: ${errorMsg}`, 'ModuleDiscovery');
926
+ }
927
+ }
928
+ }
929
+
930
+ // Merge auto-discovery configuration from multiple sources
931
+ private mergeAutoDiscoveryConfig(options: MoroOptions) {
932
+ const defaultConfig = this.config.modules.autoDiscovery;
933
+
934
+ // Handle legacy modulesPath option
935
+ if (options.modulesPath && !options.autoDiscover) {
936
+ return {
937
+ ...defaultConfig,
938
+ paths: [options.modulesPath],
939
+ };
940
+ }
941
+
942
+ // Handle boolean autoDiscover option
943
+ if (typeof options.autoDiscover === 'boolean') {
944
+ return {
945
+ ...defaultConfig,
946
+ enabled: options.autoDiscover,
947
+ };
948
+ }
949
+
950
+ // Handle object autoDiscover option
951
+ if (typeof options.autoDiscover === 'object') {
952
+ return {
953
+ ...defaultConfig,
954
+ ...options.autoDiscover,
955
+ };
956
+ }
957
+
958
+ return defaultConfig;
959
+ }
960
+
961
+ // Load discovered modules based on strategy
962
+ private async loadDiscoveredModules(
963
+ modules: ModuleConfig[],
964
+ config: ModuleDefaultsConfig['autoDiscovery']
965
+ ): Promise<void> {
966
+ switch (config.loadingStrategy) {
967
+ case 'eager':
968
+ // Load all modules immediately
969
+ for (const module of modules) {
970
+ await this.loadModule(module);
971
+ }
972
+ break;
973
+
974
+ case 'lazy':
975
+ // Register modules for lazy loading
976
+ this.registerLazyModules(modules);
977
+ break;
978
+
979
+ case 'conditional':
980
+ // Load modules based on conditions
981
+ await this.loadConditionalModules(modules);
982
+ break;
983
+
984
+ default:
985
+ // Default to eager loading
986
+ for (const module of modules) {
987
+ await this.loadModule(module);
909
988
  }
910
- });
911
- } catch {
912
- // Modules directory doesn't exist, that's fine
913
989
  }
914
990
  }
915
991
 
992
+ // Register modules for lazy loading
993
+ private registerLazyModules(modules: ModuleConfig[]): void {
994
+ modules.forEach(module => {
995
+ // Store module for lazy loading when first route is accessed
996
+ this.lazyModules.set(module.name, module);
997
+
998
+ // Register placeholder routes that trigger lazy loading
999
+ if (module.routes) {
1000
+ module.routes.forEach(route => {
1001
+ const basePath = `/api/v${module.version}/${module.name}`;
1002
+ const fullPath = `${basePath}${route.path}`;
1003
+
1004
+ // Note: Lazy loading will be implemented when route is accessed
1005
+ // For now, we'll store the module for later loading
1006
+ this.logger.debug(
1007
+ `Registered lazy route: ${route.method} ${fullPath}`,
1008
+ 'ModuleDiscovery'
1009
+ );
1010
+ });
1011
+ }
1012
+ });
1013
+
1014
+ this.logger.info(`Registered ${modules.length} modules for lazy loading`, 'ModuleDiscovery');
1015
+ }
1016
+
1017
+ // Load modules conditionally based on environment or configuration
1018
+ private async loadConditionalModules(modules: ModuleConfig[]): Promise<void> {
1019
+ for (const module of modules) {
1020
+ const shouldLoad = this.shouldLoadModule(module);
1021
+
1022
+ if (shouldLoad) {
1023
+ await this.loadModule(module);
1024
+ } else {
1025
+ this.logger.debug(`Skipping module ${module.name} due to conditions`, 'ModuleDiscovery');
1026
+ }
1027
+ }
1028
+ }
1029
+
1030
+ // Determine if a module should be loaded based on conditions
1031
+ private shouldLoadModule(module: ModuleConfig): boolean {
1032
+ const moduleConfig = module.config as any;
1033
+
1034
+ // Check environment conditions
1035
+ if (moduleConfig?.conditions?.environment) {
1036
+ const requiredEnv = moduleConfig.conditions.environment;
1037
+ const currentEnv = process.env.NODE_ENV || 'development';
1038
+
1039
+ if (Array.isArray(requiredEnv)) {
1040
+ if (!requiredEnv.includes(currentEnv)) {
1041
+ return false;
1042
+ }
1043
+ } else if (requiredEnv !== currentEnv) {
1044
+ return false;
1045
+ }
1046
+ }
1047
+
1048
+ // Check feature flags
1049
+ if (moduleConfig?.conditions?.features) {
1050
+ const requiredFeatures = moduleConfig.conditions.features;
1051
+
1052
+ for (const feature of requiredFeatures) {
1053
+ if (!process.env[`FEATURE_${feature.toUpperCase()}`]) {
1054
+ return false;
1055
+ }
1056
+ }
1057
+ }
1058
+
1059
+ // Check custom conditions
1060
+ if (moduleConfig?.conditions?.custom) {
1061
+ const customCondition = moduleConfig.conditions.custom;
1062
+
1063
+ if (typeof customCondition === 'function') {
1064
+ return customCondition();
1065
+ }
1066
+ }
1067
+
1068
+ return true;
1069
+ }
1070
+
1071
+ // Handle module changes during development
1072
+ private async handleModuleChanges(modules: ModuleConfig[]): Promise<void> {
1073
+ this.logger.info('Module changes detected, reloading...', 'ModuleDiscovery');
1074
+
1075
+ // Unload existing modules (if supported)
1076
+ // For now, just log the change
1077
+ this.eventBus.emit('modules:changed', {
1078
+ modules: modules.map(m => ({ name: m.name, version: m.version })),
1079
+ timestamp: new Date(),
1080
+ });
1081
+ }
1082
+
1083
+ // Legacy method for backward compatibility
1084
+ private autoDiscoverModules(modulesPath: string) {
1085
+ // Redirect to new system
1086
+ this.initializeAutoDiscovery({
1087
+ autoDiscover: {
1088
+ enabled: true,
1089
+ paths: [modulesPath],
1090
+ },
1091
+ });
1092
+ }
1093
+
916
1094
  private async importModule(modulePath: string): Promise<ModuleConfig> {
917
1095
  const module = await import(modulePath);
918
1096
  return module.default || module;
@@ -1216,6 +1394,15 @@ export class Moro extends EventEmitter {
1216
1394
  }
1217
1395
  }
1218
1396
 
1397
+ // Clean up module discovery watchers
1398
+ if (this.moduleDiscovery && typeof this.moduleDiscovery.cleanup === 'function') {
1399
+ try {
1400
+ this.moduleDiscovery.cleanup();
1401
+ } catch (error) {
1402
+ // Ignore cleanup errors
1403
+ }
1404
+ }
1405
+
1219
1406
  // Clean up event listeners
1220
1407
  try {
1221
1408
  this.eventBus.removeAllListeners();
@@ -84,6 +84,18 @@ export interface ModuleDefaultsConfig {
84
84
  stripUnknown: boolean;
85
85
  abortEarly: boolean;
86
86
  };
87
+ autoDiscovery: {
88
+ enabled: boolean;
89
+ paths: string[];
90
+ patterns: string[];
91
+ recursive: boolean;
92
+ loadingStrategy: 'eager' | 'lazy' | 'conditional';
93
+ watchForChanges: boolean;
94
+ ignorePatterns: string[];
95
+ loadOrder: 'alphabetical' | 'dependency' | 'custom';
96
+ failOnError: boolean;
97
+ maxDepth: number;
98
+ };
87
99
  }
88
100
 
89
101
  export interface LoggingConfig {
package/src/types/core.ts CHANGED
@@ -4,8 +4,21 @@ import { LogLevel, LoggerOptions } from './logger';
4
4
  import { AppConfig } from './config';
5
5
 
6
6
  export interface MoroOptions {
7
- autoDiscover?: boolean;
8
- modulesPath?: string;
7
+ autoDiscover?:
8
+ | boolean
9
+ | {
10
+ enabled?: boolean;
11
+ paths?: string[];
12
+ patterns?: string[];
13
+ recursive?: boolean;
14
+ loadingStrategy?: 'eager' | 'lazy' | 'conditional';
15
+ watchForChanges?: boolean;
16
+ ignorePatterns?: string[];
17
+ loadOrder?: 'alphabetical' | 'dependency' | 'custom';
18
+ failOnError?: boolean;
19
+ maxDepth?: number;
20
+ };
21
+ modulesPath?: string; // Deprecated: use autoDiscover.paths instead
9
22
  middleware?: any[];
10
23
 
11
24
  // Runtime configuration