@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/dist/core/config/config-sources.js +188 -0
- package/dist/core/config/config-sources.js.map +1 -1
- package/dist/core/config/config-validator.js +21 -0
- package/dist/core/config/config-validator.js.map +1 -1
- package/dist/core/config/schema.js +12 -0
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/modules/auto-discovery.d.ts +17 -0
- package/dist/core/modules/auto-discovery.js +366 -11
- package/dist/core/modules/auto-discovery.js.map +1 -1
- package/dist/moro.d.ts +9 -0
- package/dist/moro.js +178 -22
- package/dist/moro.js.map +1 -1
- package/dist/types/config.d.ts +12 -0
- package/dist/types/core.d.ts +12 -1
- package/package.json +1 -1
- package/src/core/config/config-sources.ts +212 -0
- package/src/core/config/config-validator.ts +36 -0
- package/src/core/config/schema.ts +12 -0
- package/src/core/modules/auto-discovery.ts +474 -11
- package/src/moro.ts +224 -37
- package/src/types/config.ts +12 -0
- package/src/types/core.ts +15 -2
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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();
|
package/src/types/config.ts
CHANGED
|
@@ -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?:
|
|
8
|
-
|
|
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
|