@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.
- package/README.md +48 -65
- package/dist/core/auth/morojs-adapter.js +12 -16
- package/dist/core/auth/morojs-adapter.js.map +1 -1
- package/dist/core/config/file-loader.d.ts +5 -0
- package/dist/core/config/file-loader.js +171 -0
- package/dist/core/config/file-loader.js.map +1 -1
- package/dist/core/config/index.d.ts +10 -39
- package/dist/core/config/index.js +29 -66
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/loader.d.ts +7 -0
- package/dist/core/config/loader.js +269 -0
- package/dist/core/config/loader.js.map +1 -0
- package/dist/core/config/schema.js +31 -41
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/utils.d.ts +2 -9
- package/dist/core/config/utils.js +32 -19
- package/dist/core/config/utils.js.map +1 -1
- package/dist/core/config/validation.d.ts +17 -0
- package/dist/core/config/validation.js +131 -0
- package/dist/core/config/validation.js.map +1 -0
- package/dist/core/database/adapters/mongodb.d.ts +0 -10
- package/dist/core/database/adapters/mongodb.js +2 -23
- package/dist/core/database/adapters/mongodb.js.map +1 -1
- package/dist/core/database/adapters/mysql.d.ts +0 -11
- package/dist/core/database/adapters/mysql.js +0 -1
- package/dist/core/database/adapters/mysql.js.map +1 -1
- package/dist/core/database/adapters/postgresql.d.ts +1 -9
- package/dist/core/database/adapters/postgresql.js +1 -1
- package/dist/core/database/adapters/postgresql.js.map +1 -1
- package/dist/core/database/adapters/redis.d.ts +0 -9
- package/dist/core/database/adapters/redis.js +4 -14
- package/dist/core/database/adapters/redis.js.map +1 -1
- package/dist/core/framework.d.ts +7 -6
- package/dist/core/framework.js +16 -131
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/http-server.d.ts +0 -12
- package/dist/core/http/http-server.js +23 -151
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/router.d.ts +0 -12
- package/dist/core/http/router.js +36 -114
- package/dist/core/http/router.js.map +1 -1
- package/dist/core/logger/filters.js +4 -12
- package/dist/core/logger/filters.js.map +1 -1
- package/dist/core/logger/index.d.ts +1 -1
- package/dist/core/logger/index.js +1 -2
- package/dist/core/logger/index.js.map +1 -1
- package/dist/core/logger/logger.d.ts +13 -29
- package/dist/core/logger/logger.js +203 -380
- package/dist/core/logger/logger.js.map +1 -1
- package/dist/core/logger/outputs.js +2 -0
- package/dist/core/logger/outputs.js.map +1 -1
- package/dist/core/middleware/built-in/auth.js +17 -88
- package/dist/core/middleware/built-in/auth.js.map +1 -1
- package/dist/core/middleware/built-in/cache.js +1 -3
- package/dist/core/middleware/built-in/cache.js.map +1 -1
- package/dist/core/middleware/built-in/index.d.ts +0 -1
- package/dist/core/middleware/built-in/index.js +1 -6
- package/dist/core/middleware/built-in/index.js.map +1 -1
- package/dist/core/middleware/built-in/request-logger.js +2 -3
- package/dist/core/middleware/built-in/request-logger.js.map +1 -1
- package/dist/core/middleware/built-in/sse.js +7 -9
- package/dist/core/middleware/built-in/sse.js.map +1 -1
- package/dist/core/modules/auto-discovery.d.ts +0 -17
- package/dist/core/modules/auto-discovery.js +12 -367
- package/dist/core/modules/auto-discovery.js.map +1 -1
- package/dist/core/modules/modules.js +2 -12
- package/dist/core/modules/modules.js.map +1 -1
- package/dist/core/networking/adapters/ws-adapter.d.ts +1 -1
- package/dist/core/networking/adapters/ws-adapter.js +2 -2
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
- package/dist/core/networking/service-discovery.js +7 -7
- package/dist/core/networking/service-discovery.js.map +1 -1
- package/dist/core/routing/index.d.ts +0 -20
- package/dist/core/routing/index.js +13 -178
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/runtime/node-adapter.js +6 -12
- package/dist/core/runtime/node-adapter.js.map +1 -1
- package/dist/moro.d.ts +0 -48
- package/dist/moro.js +148 -456
- package/dist/moro.js.map +1 -1
- package/dist/types/config.d.ts +2 -58
- package/dist/types/core.d.ts +40 -34
- package/dist/types/http.d.ts +1 -16
- package/dist/types/logger.d.ts +0 -7
- package/dist/types/module.d.ts +0 -11
- package/package.json +2 -2
- package/src/core/auth/morojs-adapter.ts +13 -18
- package/src/core/config/file-loader.ts +233 -0
- package/src/core/config/index.ts +32 -77
- package/src/core/config/loader.ts +633 -0
- package/src/core/config/schema.ts +31 -41
- package/src/core/config/utils.ts +29 -22
- package/src/core/config/validation.ts +140 -0
- package/src/core/database/README.md +16 -26
- package/src/core/database/adapters/mongodb.ts +2 -30
- package/src/core/database/adapters/mysql.ts +0 -14
- package/src/core/database/adapters/postgresql.ts +2 -12
- package/src/core/database/adapters/redis.ts +4 -27
- package/src/core/framework.ts +23 -163
- package/src/core/http/http-server.ts +36 -176
- package/src/core/http/router.ts +38 -127
- package/src/core/logger/filters.ts +4 -12
- package/src/core/logger/index.ts +0 -1
- package/src/core/logger/logger.ts +216 -427
- package/src/core/logger/outputs.ts +2 -0
- package/src/core/middleware/built-in/auth.ts +17 -98
- package/src/core/middleware/built-in/cache.ts +1 -3
- package/src/core/middleware/built-in/index.ts +0 -8
- package/src/core/middleware/built-in/request-logger.ts +1 -3
- package/src/core/middleware/built-in/sse.ts +7 -9
- package/src/core/modules/auto-discovery.ts +13 -476
- package/src/core/modules/modules.ts +9 -20
- package/src/core/networking/adapters/ws-adapter.ts +5 -2
- package/src/core/networking/service-discovery.ts +7 -6
- package/src/core/routing/index.ts +14 -198
- package/src/core/runtime/node-adapter.ts +6 -12
- package/src/moro.ts +166 -554
- package/src/types/config.ts +2 -59
- package/src/types/core.ts +45 -47
- package/src/types/http.ts +1 -23
- package/src/types/logger.ts +0 -9
- package/src/types/module.ts +0 -12
- package/dist/core/config/config-manager.d.ts +0 -44
- package/dist/core/config/config-manager.js +0 -114
- package/dist/core/config/config-manager.js.map +0 -1
- package/dist/core/config/config-sources.d.ts +0 -21
- package/dist/core/config/config-sources.js +0 -502
- package/dist/core/config/config-sources.js.map +0 -1
- package/dist/core/config/config-validator.d.ts +0 -21
- package/dist/core/config/config-validator.js +0 -765
- package/dist/core/config/config-validator.js.map +0 -1
- package/dist/core/middleware/built-in/jwt-helpers.d.ts +0 -118
- package/dist/core/middleware/built-in/jwt-helpers.js +0 -221
- package/dist/core/middleware/built-in/jwt-helpers.js.map +0 -1
- package/src/core/config/config-manager.ts +0 -133
- package/src/core/config/config-sources.ts +0 -596
- package/src/core/config/config-validator.ts +0 -1078
- 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 {
|
|
11
|
-
|
|
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 {
|
|
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
|
|
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
|
-
//
|
|
53
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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: ${
|
|
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
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
-
|
|
553
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
|
984
|
-
if (
|
|
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
|
|
995
|
-
if (
|
|
895
|
+
// Helmet
|
|
896
|
+
if (options.helmet !== false) {
|
|
996
897
|
this.use(middleware.helmet());
|
|
997
898
|
}
|
|
998
899
|
|
|
999
|
-
// Compression
|
|
1000
|
-
if (
|
|
1001
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
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
|
-
//
|
|
944
|
+
// Smart worker count calculation based on actual bottlenecks
|
|
1258
945
|
let workerCount = this.config.performance?.clustering?.workers || os.cpus().length;
|
|
1259
946
|
|
|
1260
|
-
//
|
|
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
|
-
//
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
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-
|
|
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(
|
|
972
|
+
this.logger.info(`🚀 Starting ${workerCount} workers for maximum performance`, 'Cluster');
|
|
1294
973
|
|
|
1295
974
|
// Optimize cluster scheduling for high concurrency
|
|
1296
|
-
// Round-robin
|
|
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]
|
|
979
|
+
exec: process.argv[1],
|
|
1303
980
|
args: process.argv.slice(2),
|
|
1304
981
|
silent: false,
|
|
1305
982
|
});
|
|
1306
983
|
|
|
1307
|
-
// IPC
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
//
|
|
1023
|
+
// Handle worker exits with cleanup
|
|
1343
1024
|
cluster.on('exit', (worker: any, code: number, signal: string) => {
|
|
1344
|
-
|
|
1345
|
-
this.clusterWorkers.delete(pid);
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
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
|
-
|
|
1052
|
+
this.config.logging.level = 'warn'; // Only warnings and errors
|
|
1374
1053
|
}
|
|
1375
1054
|
|
|
1376
|
-
//
|
|
1377
|
-
|
|
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
|
-
//
|
|
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
|