@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/dist/moro.js
CHANGED
|
@@ -48,6 +48,8 @@ const logger_1 = require("./core/logger");
|
|
|
48
48
|
const middleware_1 = require("./core/middleware");
|
|
49
49
|
const app_integration_1 = require("./core/routing/app-integration");
|
|
50
50
|
const docs_1 = require("./core/docs");
|
|
51
|
+
const fs_1 = require("fs");
|
|
52
|
+
const path_1 = require("path");
|
|
51
53
|
const events_1 = require("events");
|
|
52
54
|
// Configuration System Integration
|
|
53
55
|
const config_1 = require("./core/config");
|
|
@@ -58,16 +60,11 @@ class Moro extends events_1.EventEmitter {
|
|
|
58
60
|
routes = [];
|
|
59
61
|
moduleCounter = 0;
|
|
60
62
|
loadedModules = new Set();
|
|
61
|
-
lazyModules = new Map();
|
|
62
63
|
routeHandlers = {};
|
|
63
|
-
moduleDiscovery; // Store for cleanup
|
|
64
|
-
autoDiscoveryOptions = null;
|
|
65
|
-
autoDiscoveryInitialized = false;
|
|
66
|
-
autoDiscoveryPromise = null;
|
|
67
64
|
// Enterprise event system integration
|
|
68
65
|
eventBus;
|
|
69
66
|
// Application logger
|
|
70
|
-
logger;
|
|
67
|
+
logger = (0, logger_1.createFrameworkLogger)('App');
|
|
71
68
|
// Intelligent routing system
|
|
72
69
|
intelligentRouting = new app_integration_1.IntelligentRoutingManager();
|
|
73
70
|
// Documentation system
|
|
@@ -81,37 +78,73 @@ class Moro extends events_1.EventEmitter {
|
|
|
81
78
|
middlewareManager;
|
|
82
79
|
constructor(options = {}) {
|
|
83
80
|
super(); // Call EventEmitter constructor
|
|
84
|
-
//
|
|
85
|
-
//
|
|
81
|
+
// Configure logger from environment variables BEFORE config system initialization
|
|
82
|
+
// This ensures the config loading process respects the log level
|
|
86
83
|
const envLogLevel = process.env.LOG_LEVEL || process.env.MORO_LOG_LEVEL;
|
|
87
84
|
if (envLogLevel) {
|
|
88
85
|
(0, logger_1.applyLoggingConfiguration)({ level: envLogLevel }, undefined);
|
|
89
86
|
}
|
|
90
|
-
//
|
|
87
|
+
// Initialize configuration system - create a deep copy for this instance
|
|
88
|
+
this.config = JSON.parse(JSON.stringify((0, config_1.initializeConfig)()));
|
|
89
|
+
// Apply logging configuration from the loaded config (this happens after config file processing)
|
|
90
|
+
if (this.config.logging) {
|
|
91
|
+
(0, logger_1.applyLoggingConfiguration)(this.config.logging, undefined);
|
|
92
|
+
}
|
|
93
|
+
// Apply additional logging configuration from createApp options (takes precedence)
|
|
91
94
|
if (options.logger !== undefined) {
|
|
92
95
|
(0, logger_1.applyLoggingConfiguration)(undefined, options.logger);
|
|
93
96
|
}
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
// Apply performance configuration from createApp options (takes precedence)
|
|
98
|
+
if (options.performance) {
|
|
99
|
+
if (options.performance.clustering) {
|
|
100
|
+
this.config.performance.clustering = {
|
|
101
|
+
...this.config.performance.clustering,
|
|
102
|
+
...options.performance.clustering,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (options.performance.compression) {
|
|
106
|
+
this.config.performance.compression = {
|
|
107
|
+
...this.config.performance.compression,
|
|
108
|
+
...options.performance.compression,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (options.performance.circuitBreaker) {
|
|
112
|
+
this.config.performance.circuitBreaker = {
|
|
113
|
+
...this.config.performance.circuitBreaker,
|
|
114
|
+
...options.performance.circuitBreaker,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Apply modules configuration from createApp options (takes precedence)
|
|
119
|
+
if (options.modules) {
|
|
120
|
+
if (options.modules.cache) {
|
|
121
|
+
this.config.modules.cache = {
|
|
122
|
+
...this.config.modules.cache,
|
|
123
|
+
...options.modules.cache,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (options.modules.rateLimit) {
|
|
127
|
+
this.config.modules.rateLimit = {
|
|
128
|
+
...this.config.modules.rateLimit,
|
|
129
|
+
...options.modules.rateLimit,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (options.modules.validation) {
|
|
133
|
+
this.config.modules.validation = {
|
|
134
|
+
...this.config.modules.validation,
|
|
135
|
+
...options.modules.validation,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
103
138
|
}
|
|
104
|
-
this.logger.info(`Configuration system initialized: ${
|
|
139
|
+
this.logger.info(`Configuration system initialized: ${this.config.server.environment}:${this.config.server.port}`);
|
|
105
140
|
// Initialize runtime system
|
|
106
141
|
this.runtimeType = options.runtime?.type || 'node';
|
|
107
142
|
this.runtimeAdapter = options.runtime?.adapter || (0, runtime_1.createRuntimeAdapter)(this.runtimeType);
|
|
108
143
|
this.logger.info(`Runtime system initialized: ${this.runtimeType}`, 'Runtime');
|
|
109
|
-
// Pass configuration from config to framework
|
|
144
|
+
// Pass logging configuration from config to framework
|
|
110
145
|
const frameworkOptions = {
|
|
111
146
|
...options,
|
|
112
147
|
logger: this.config.logging,
|
|
113
|
-
websocket: this.config.websocket.enabled ? options.websocket || {} : false,
|
|
114
|
-
config: this.config,
|
|
115
148
|
};
|
|
116
149
|
this.coreFramework = new framework_1.Moro(frameworkOptions);
|
|
117
150
|
// Initialize middleware system
|
|
@@ -136,10 +169,10 @@ class Moro extends events_1.EventEmitter {
|
|
|
136
169
|
...this.getDefaultOptionsFromConfig(),
|
|
137
170
|
...options,
|
|
138
171
|
});
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
172
|
+
// Auto-discover modules if enabled
|
|
173
|
+
if (options.autoDiscover !== false) {
|
|
174
|
+
this.autoDiscoverModules(options.modulesPath || './modules');
|
|
175
|
+
}
|
|
143
176
|
// Emit initialization event through enterprise event bus
|
|
144
177
|
this.eventBus.emit('framework:initialized', {
|
|
145
178
|
options,
|
|
@@ -305,11 +338,6 @@ class Moro extends events_1.EventEmitter {
|
|
|
305
338
|
version: moduleOrPath.version || '1.0.0',
|
|
306
339
|
});
|
|
307
340
|
}
|
|
308
|
-
// IMPORTANT: If modules are loaded manually after auto-discovery,
|
|
309
|
-
// ensure the final module handler is set up to maintain middleware order
|
|
310
|
-
if (this.autoDiscoveryInitialized) {
|
|
311
|
-
this.coreFramework.setupFinalModuleHandler();
|
|
312
|
-
}
|
|
313
341
|
return this;
|
|
314
342
|
}
|
|
315
343
|
// Database helper with events
|
|
@@ -426,134 +454,32 @@ class Moro extends events_1.EventEmitter {
|
|
|
426
454
|
if (this.routes.length > 0) {
|
|
427
455
|
this.registerDirectRoutes();
|
|
428
456
|
}
|
|
429
|
-
const
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (intelligentRoutes.length > 0) {
|
|
442
|
-
this.logger.info(`Intelligent Routes: ${intelligentRoutes.length} registered`, 'Server');
|
|
443
|
-
}
|
|
444
|
-
this.eventBus.emit('server:started', { port, runtime: this.runtimeType });
|
|
445
|
-
if (callback)
|
|
446
|
-
callback();
|
|
447
|
-
};
|
|
448
|
-
if (host && typeof host === 'string') {
|
|
449
|
-
this.coreFramework.listen(port, host, actualCallback);
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
this.coreFramework.listen(port, actualCallback);
|
|
457
|
+
const actualCallback = () => {
|
|
458
|
+
const displayHost = host || 'localhost';
|
|
459
|
+
this.logger.info('Moro Server Started', 'Server');
|
|
460
|
+
this.logger.info(`Runtime: ${this.runtimeType}`, 'Server');
|
|
461
|
+
this.logger.info(`HTTP API: http://${displayHost}:${port}`, 'Server');
|
|
462
|
+
this.logger.info(`WebSocket: ws://${displayHost}:${port}`, 'Server');
|
|
463
|
+
this.logger.info('Native Node.js HTTP • Zero Dependencies • Maximum Performance', 'Server');
|
|
464
|
+
this.logger.info('Learn more at https://morojs.com', 'Server');
|
|
465
|
+
// Log intelligent routes info
|
|
466
|
+
const intelligentRoutes = this.intelligentRouting.getIntelligentRoutes();
|
|
467
|
+
if (intelligentRoutes.length > 0) {
|
|
468
|
+
this.logger.info(`Intelligent Routes: ${intelligentRoutes.length} registered`, 'Server');
|
|
453
469
|
}
|
|
470
|
+
this.eventBus.emit('server:started', { port, runtime: this.runtimeType });
|
|
471
|
+
if (callback)
|
|
472
|
+
callback();
|
|
454
473
|
};
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
.then(() => {
|
|
458
|
-
startServer();
|
|
459
|
-
})
|
|
460
|
-
.catch(error => {
|
|
461
|
-
this.logger.error('Auto-discovery initialization failed during server start', 'Framework', {
|
|
462
|
-
error: error instanceof Error ? error.message : String(error),
|
|
463
|
-
});
|
|
464
|
-
// Start server anyway - auto-discovery failure shouldn't prevent startup
|
|
465
|
-
startServer();
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
// Public method to manually initialize auto-discovery
|
|
469
|
-
// Useful for ensuring auth middleware is registered before auto-discovery
|
|
470
|
-
async initializeAutoDiscoveryNow() {
|
|
471
|
-
return this.ensureAutoDiscoveryComplete();
|
|
472
|
-
}
|
|
473
|
-
// Public API: Initialize modules explicitly after middleware setup
|
|
474
|
-
// This provides users with explicit control over module loading timing
|
|
475
|
-
// IMPORTANT: This forces module loading even if autoDiscovery.enabled is false
|
|
476
|
-
// Usage: app.initModules() or app.initModules({ paths: ['./my-modules'] })
|
|
477
|
-
initModules(options) {
|
|
478
|
-
this.logger.info('User-requested module initialization', 'ModuleSystem');
|
|
479
|
-
// If already initialized, do nothing
|
|
480
|
-
if (this.autoDiscoveryInitialized) {
|
|
481
|
-
this.logger.debug('Auto-discovery already completed, skipping', 'ModuleSystem');
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
// Store the options and mark that we want to force initialization
|
|
485
|
-
this.autoDiscoveryOptions = {
|
|
486
|
-
autoDiscover: {
|
|
487
|
-
enabled: true, // Force enabled regardless of original config
|
|
488
|
-
paths: options?.paths || ['./modules', './src/modules'],
|
|
489
|
-
patterns: options?.patterns || [
|
|
490
|
-
'**/*.module.{ts,js}',
|
|
491
|
-
'**/index.{ts,js}',
|
|
492
|
-
'**/*.config.{ts,js}',
|
|
493
|
-
],
|
|
494
|
-
recursive: options?.recursive ?? true,
|
|
495
|
-
loadingStrategy: options?.loadingStrategy || 'eager',
|
|
496
|
-
watchForChanges: options?.watchForChanges ?? false,
|
|
497
|
-
ignorePatterns: options?.ignorePatterns || [
|
|
498
|
-
'**/*.test.{ts,js}',
|
|
499
|
-
'**/*.spec.{ts,js}',
|
|
500
|
-
'**/node_modules/**',
|
|
501
|
-
],
|
|
502
|
-
loadOrder: options?.loadOrder || 'dependency',
|
|
503
|
-
failOnError: options?.failOnError ?? false,
|
|
504
|
-
maxDepth: options?.maxDepth ?? 5,
|
|
505
|
-
},
|
|
506
|
-
};
|
|
507
|
-
this.logger.debug('Module initialization options stored, will execute on next listen/getHandler call', 'ModuleSystem');
|
|
508
|
-
}
|
|
509
|
-
// Robust method to ensure auto-discovery is complete, handling race conditions
|
|
510
|
-
async ensureAutoDiscoveryComplete() {
|
|
511
|
-
// If already initialized, nothing to do
|
|
512
|
-
if (this.autoDiscoveryInitialized) {
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
// If auto-discovery is disabled, mark as initialized
|
|
516
|
-
if (!this.autoDiscoveryOptions) {
|
|
517
|
-
this.autoDiscoveryInitialized = true;
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
// If already in progress, wait for it to complete
|
|
521
|
-
if (this.autoDiscoveryPromise) {
|
|
522
|
-
return this.autoDiscoveryPromise;
|
|
523
|
-
}
|
|
524
|
-
// Start auto-discovery
|
|
525
|
-
this.autoDiscoveryPromise = this.performAutoDiscovery();
|
|
526
|
-
try {
|
|
527
|
-
await this.autoDiscoveryPromise;
|
|
528
|
-
this.autoDiscoveryInitialized = true;
|
|
474
|
+
if (host && typeof host === 'string') {
|
|
475
|
+
this.coreFramework.listen(port, host, actualCallback);
|
|
529
476
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
this.autoDiscoveryPromise = null;
|
|
533
|
-
throw error;
|
|
534
|
-
}
|
|
535
|
-
finally {
|
|
536
|
-
this.autoDiscoveryOptions = null; // Clear after attempt
|
|
477
|
+
else {
|
|
478
|
+
this.coreFramework.listen(port, actualCallback);
|
|
537
479
|
}
|
|
538
480
|
}
|
|
539
|
-
// Perform the actual auto-discovery work
|
|
540
|
-
async performAutoDiscovery(optionsOverride) {
|
|
541
|
-
const optionsToUse = optionsOverride || this.autoDiscoveryOptions;
|
|
542
|
-
if (!optionsToUse)
|
|
543
|
-
return;
|
|
544
|
-
this.logger.debug('Starting auto-discovery initialization', 'AutoDiscovery');
|
|
545
|
-
await this.initializeAutoDiscovery(optionsToUse);
|
|
546
|
-
this.logger.debug('Auto-discovery initialization completed', 'AutoDiscovery');
|
|
547
|
-
}
|
|
548
481
|
// Get handler for non-Node.js runtimes
|
|
549
482
|
getHandler() {
|
|
550
|
-
// Ensure auto-discovery is complete for non-Node.js runtimes
|
|
551
|
-
// This handles the case where users call getHandler() immediately after createApp()
|
|
552
|
-
this.ensureAutoDiscoveryComplete().catch(error => {
|
|
553
|
-
this.logger.error('Auto-discovery initialization failed for runtime handler', 'Framework', {
|
|
554
|
-
error: error instanceof Error ? error.message : String(error),
|
|
555
|
-
});
|
|
556
|
-
});
|
|
557
483
|
// Create a unified request handler that works with the runtime adapter
|
|
558
484
|
const handler = async (req, res) => {
|
|
559
485
|
// Add documentation middleware first (if enabled)
|
|
@@ -841,268 +767,91 @@ class Moro extends events_1.EventEmitter {
|
|
|
841
767
|
return true;
|
|
842
768
|
}
|
|
843
769
|
setupDefaultMiddleware(options) {
|
|
844
|
-
// CORS
|
|
845
|
-
if (
|
|
846
|
-
const corsOptions = typeof options.cors === 'object'
|
|
847
|
-
? options.cors
|
|
848
|
-
: this.config.security.cors
|
|
849
|
-
? this.config.security.cors
|
|
850
|
-
: {};
|
|
770
|
+
// CORS
|
|
771
|
+
if (options.cors !== false) {
|
|
772
|
+
const corsOptions = typeof options.cors === 'object' ? options.cors : {};
|
|
851
773
|
this.use(http_1.middleware.cors(corsOptions));
|
|
852
774
|
}
|
|
853
|
-
// Helmet
|
|
854
|
-
if (
|
|
775
|
+
// Helmet
|
|
776
|
+
if (options.helmet !== false) {
|
|
855
777
|
this.use(http_1.middleware.helmet());
|
|
856
778
|
}
|
|
857
|
-
// Compression
|
|
858
|
-
if (
|
|
859
|
-
options.
|
|
860
|
-
const compressionOptions = typeof options.compression === 'object'
|
|
861
|
-
? options.compression
|
|
862
|
-
: this.config.performance.compression
|
|
863
|
-
? this.config.performance.compression
|
|
864
|
-
: {};
|
|
779
|
+
// Compression
|
|
780
|
+
if (options.compression !== false) {
|
|
781
|
+
const compressionOptions = typeof options.compression === 'object' ? options.compression : {};
|
|
865
782
|
this.use(http_1.middleware.compression(compressionOptions));
|
|
866
783
|
}
|
|
867
784
|
// Body size limiting
|
|
868
785
|
this.use(http_1.middleware.bodySize({ limit: '10mb' }));
|
|
869
786
|
}
|
|
870
|
-
|
|
871
|
-
async initializeAutoDiscovery(options) {
|
|
872
|
-
const { ModuleDiscovery } = await Promise.resolve().then(() => __importStar(require('./core/modules/auto-discovery')));
|
|
873
|
-
// Merge auto-discovery configuration
|
|
874
|
-
const autoDiscoveryConfig = this.mergeAutoDiscoveryConfig(options);
|
|
875
|
-
if (!autoDiscoveryConfig.enabled) {
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
this.moduleDiscovery = new ModuleDiscovery(process.cwd());
|
|
787
|
+
autoDiscoverModules(modulesPath) {
|
|
879
788
|
try {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
896
|
-
if (autoDiscoveryConfig.failOnError) {
|
|
897
|
-
throw new Error(`Module auto-discovery failed: ${errorMsg}`);
|
|
898
|
-
}
|
|
899
|
-
else {
|
|
900
|
-
this.logger.warn(`Module auto-discovery failed: ${errorMsg}`, 'ModuleDiscovery');
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
// Merge auto-discovery configuration from multiple sources
|
|
905
|
-
mergeAutoDiscoveryConfig(options) {
|
|
906
|
-
const defaultConfig = this.config.modules.autoDiscovery;
|
|
907
|
-
// Handle legacy modulesPath option
|
|
908
|
-
if (options.modulesPath && !options.autoDiscover) {
|
|
909
|
-
return {
|
|
910
|
-
...defaultConfig,
|
|
911
|
-
paths: [options.modulesPath],
|
|
912
|
-
};
|
|
913
|
-
}
|
|
914
|
-
// Handle boolean autoDiscover option
|
|
915
|
-
if (typeof options.autoDiscover === 'boolean') {
|
|
916
|
-
return {
|
|
917
|
-
...defaultConfig,
|
|
918
|
-
enabled: options.autoDiscover,
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
// Handle object autoDiscover option
|
|
922
|
-
if (typeof options.autoDiscover === 'object') {
|
|
923
|
-
return {
|
|
924
|
-
...defaultConfig,
|
|
925
|
-
...options.autoDiscover,
|
|
926
|
-
};
|
|
927
|
-
}
|
|
928
|
-
return defaultConfig;
|
|
929
|
-
}
|
|
930
|
-
// Load discovered modules based on strategy
|
|
931
|
-
async loadDiscoveredModules(modules, config) {
|
|
932
|
-
switch (config.loadingStrategy) {
|
|
933
|
-
case 'eager':
|
|
934
|
-
// Load all modules immediately
|
|
935
|
-
for (const module of modules) {
|
|
936
|
-
await this.loadModule(module);
|
|
937
|
-
}
|
|
938
|
-
break;
|
|
939
|
-
case 'lazy':
|
|
940
|
-
// Register modules for lazy loading
|
|
941
|
-
this.registerLazyModules(modules);
|
|
942
|
-
break;
|
|
943
|
-
case 'conditional':
|
|
944
|
-
// Load modules based on conditions
|
|
945
|
-
await this.loadConditionalModules(modules);
|
|
946
|
-
break;
|
|
947
|
-
default:
|
|
948
|
-
// Default to eager loading
|
|
949
|
-
for (const module of modules) {
|
|
950
|
-
await this.loadModule(module);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
// Register modules for lazy loading
|
|
955
|
-
registerLazyModules(modules) {
|
|
956
|
-
modules.forEach(module => {
|
|
957
|
-
// Store module for lazy loading when first route is accessed
|
|
958
|
-
this.lazyModules.set(module.name, module);
|
|
959
|
-
// Register placeholder routes that trigger lazy loading
|
|
960
|
-
if (module.routes) {
|
|
961
|
-
module.routes.forEach(route => {
|
|
962
|
-
const basePath = `/api/v${module.version}/${module.name}`;
|
|
963
|
-
const fullPath = `${basePath}${route.path}`;
|
|
964
|
-
// Note: Lazy loading will be implemented when route is accessed
|
|
965
|
-
// For now, we'll store the module for later loading
|
|
966
|
-
this.logger.debug(`Registered lazy route: ${route.method} ${fullPath}`, 'ModuleDiscovery');
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
|
-
});
|
|
970
|
-
this.logger.info(`Registered ${modules.length} modules for lazy loading`, 'ModuleDiscovery');
|
|
971
|
-
}
|
|
972
|
-
// Load modules conditionally based on environment or configuration
|
|
973
|
-
async loadConditionalModules(modules) {
|
|
974
|
-
for (const module of modules) {
|
|
975
|
-
const shouldLoad = this.shouldLoadModule(module);
|
|
976
|
-
if (shouldLoad) {
|
|
977
|
-
await this.loadModule(module);
|
|
978
|
-
}
|
|
979
|
-
else {
|
|
980
|
-
this.logger.debug(`Skipping module ${module.name} due to conditions`, 'ModuleDiscovery');
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
// Determine if a module should be loaded based on conditions
|
|
985
|
-
shouldLoadModule(module) {
|
|
986
|
-
const moduleConfig = module.config;
|
|
987
|
-
// Check environment conditions
|
|
988
|
-
if (moduleConfig?.conditions?.environment) {
|
|
989
|
-
const requiredEnv = moduleConfig.conditions.environment;
|
|
990
|
-
const currentEnv = process.env.NODE_ENV || 'development';
|
|
991
|
-
if (Array.isArray(requiredEnv)) {
|
|
992
|
-
if (!requiredEnv.includes(currentEnv)) {
|
|
993
|
-
return false;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
else if (requiredEnv !== currentEnv) {
|
|
997
|
-
return false;
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
// Check feature flags
|
|
1001
|
-
if (moduleConfig?.conditions?.features) {
|
|
1002
|
-
const requiredFeatures = moduleConfig.conditions.features;
|
|
1003
|
-
for (const feature of requiredFeatures) {
|
|
1004
|
-
if (!process.env[`FEATURE_${feature.toUpperCase()}`]) {
|
|
1005
|
-
return false;
|
|
789
|
+
if (!(0, fs_1.statSync)(modulesPath).isDirectory())
|
|
790
|
+
return;
|
|
791
|
+
const items = (0, fs_1.readdirSync)(modulesPath);
|
|
792
|
+
items.forEach(item => {
|
|
793
|
+
const fullPath = (0, path_1.join)(modulesPath, item);
|
|
794
|
+
if ((0, fs_1.statSync)(fullPath).isDirectory()) {
|
|
795
|
+
const indexPath = (0, path_1.join)(fullPath, 'index.ts');
|
|
796
|
+
try {
|
|
797
|
+
(0, fs_1.statSync)(indexPath);
|
|
798
|
+
// Module directory found, will be loaded later
|
|
799
|
+
this.logger.debug(`Discovered module: ${item}`, 'ModuleDiscovery');
|
|
800
|
+
}
|
|
801
|
+
catch {
|
|
802
|
+
// No index.ts, skip
|
|
803
|
+
}
|
|
1006
804
|
}
|
|
1007
|
-
}
|
|
805
|
+
});
|
|
1008
806
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
const customCondition = moduleConfig.conditions.custom;
|
|
1012
|
-
if (typeof customCondition === 'function') {
|
|
1013
|
-
return customCondition();
|
|
1014
|
-
}
|
|
807
|
+
catch {
|
|
808
|
+
// Modules directory doesn't exist, that's fine
|
|
1015
809
|
}
|
|
1016
|
-
return true;
|
|
1017
|
-
}
|
|
1018
|
-
// Handle module changes during development
|
|
1019
|
-
async handleModuleChanges(modules) {
|
|
1020
|
-
this.logger.info('Module changes detected, reloading...', 'ModuleDiscovery');
|
|
1021
|
-
// Unload existing modules (if supported)
|
|
1022
|
-
// For now, just log the change
|
|
1023
|
-
this.eventBus.emit('modules:changed', {
|
|
1024
|
-
modules: modules.map(m => ({ name: m.name, version: m.version })),
|
|
1025
|
-
timestamp: new Date(),
|
|
1026
|
-
});
|
|
1027
|
-
}
|
|
1028
|
-
// Legacy method for backward compatibility
|
|
1029
|
-
autoDiscoverModules(modulesPath) {
|
|
1030
|
-
// Redirect to new system
|
|
1031
|
-
this.initializeAutoDiscovery({
|
|
1032
|
-
autoDiscover: {
|
|
1033
|
-
enabled: true,
|
|
1034
|
-
paths: [modulesPath],
|
|
1035
|
-
},
|
|
1036
|
-
});
|
|
1037
810
|
}
|
|
1038
811
|
async importModule(modulePath) {
|
|
1039
812
|
const module = await Promise.resolve(`${modulePath}`).then(s => __importStar(require(s)));
|
|
1040
813
|
return module.default || module;
|
|
1041
814
|
}
|
|
1042
|
-
|
|
1043
|
-
* Node.js Clustering Implementation
|
|
1044
|
-
* This clustering algorithm is based on published research and Node.js best practices.
|
|
1045
|
-
*
|
|
1046
|
-
* IPC (Inter-Process Communication) Considerations:
|
|
1047
|
-
* - Excessive workers create IPC bottlenecks (Source: BetterStack Node.js Guide)
|
|
1048
|
-
* - Round-robin scheduling provides better load distribution (Node.js Documentation)
|
|
1049
|
-
* - Message passing overhead increases significantly with worker count
|
|
1050
|
-
*
|
|
1051
|
-
* Memory Management:
|
|
1052
|
-
* - ~2GB per worker prevents memory pressure and GC overhead
|
|
1053
|
-
* - Conservative heap limits reduce memory fragmentation
|
|
1054
|
-
*
|
|
1055
|
-
* References:
|
|
1056
|
-
* - Node.js Cluster Documentation: https://nodejs.org/api/cluster.html
|
|
1057
|
-
* - BetterStack Node.js Clustering: https://betterstack.com/community/guides/scaling-nodejs/node-clustering/
|
|
1058
|
-
*/
|
|
815
|
+
// Clustering support for massive performance gains with proper cleanup
|
|
1059
816
|
clusterWorkers = new Map();
|
|
1060
817
|
startWithClustering(port, host, callback) {
|
|
1061
818
|
const cluster = require('cluster');
|
|
1062
819
|
const os = require('os');
|
|
1063
|
-
//
|
|
820
|
+
// Smart worker count calculation based on actual bottlenecks
|
|
1064
821
|
let workerCount = this.config.performance?.clustering?.workers || os.cpus().length;
|
|
1065
|
-
//
|
|
1066
|
-
if (workerCount === 'auto') {
|
|
822
|
+
// Auto-optimize worker count based on system characteristics
|
|
823
|
+
if (workerCount === 'auto' || workerCount > 8) {
|
|
824
|
+
// For high-core machines, limit workers to prevent IPC/memory bottlenecks
|
|
1067
825
|
const cpuCount = os.cpus().length;
|
|
1068
826
|
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
1069
|
-
//
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
const headroomGB = 4;
|
|
1074
|
-
memoryPerWorkerGB = Math.max(0.5, Math.floor((totalMemoryGB - headroomGB) / cpuCount));
|
|
827
|
+
// Optimal worker count formula based on research
|
|
828
|
+
if (cpuCount >= 16) {
|
|
829
|
+
// High-core machines: focus on memory/IPC efficiency
|
|
830
|
+
workerCount = Math.min(Math.ceil(totalMemoryGB / 2), 4); // 2GB per worker max, cap at 4
|
|
1075
831
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
else if (typeof workerCount === 'number') {
|
|
1086
|
-
// User specified a number - respect their choice
|
|
1087
|
-
this.logger.info(`Using user-specified worker count: ${workerCount}`, 'Cluster');
|
|
832
|
+
else if (cpuCount >= 8) {
|
|
833
|
+
// Mid-range machines: balanced approach
|
|
834
|
+
workerCount = Math.min(cpuCount / 2, 4);
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
// Low-core machines: use all cores
|
|
838
|
+
workerCount = cpuCount;
|
|
839
|
+
}
|
|
840
|
+
this.logger.info(`Auto-optimized workers: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB)`, 'Cluster');
|
|
1088
841
|
}
|
|
1089
842
|
if (cluster.isPrimary) {
|
|
1090
|
-
this.logger.info(
|
|
843
|
+
this.logger.info(`🚀 Starting ${workerCount} workers for maximum performance`, 'Cluster');
|
|
1091
844
|
// Optimize cluster scheduling for high concurrency
|
|
1092
|
-
// Round-robin
|
|
1093
|
-
// Provides better load distribution than shared socket approach
|
|
1094
|
-
cluster.schedulingPolicy = cluster.SCHED_RR;
|
|
845
|
+
cluster.schedulingPolicy = cluster.SCHED_RR; // Round-robin scheduling
|
|
1095
846
|
// Set cluster settings for better performance
|
|
1096
847
|
cluster.setupMaster({
|
|
1097
|
-
exec: process.argv[1]
|
|
848
|
+
exec: process.argv[1],
|
|
1098
849
|
args: process.argv.slice(2),
|
|
1099
850
|
silent: false,
|
|
1100
851
|
});
|
|
1101
|
-
// IPC
|
|
1102
|
-
// Research shows excessive IPC can create bottlenecks in clustered applications
|
|
1103
|
-
// (Source: BetterStack - Node.js Clustering Guide)
|
|
852
|
+
// Optimize IPC to reduce communication overhead
|
|
1104
853
|
process.env.NODE_CLUSTER_SCHED_POLICY = 'rr'; // Ensure round-robin
|
|
1105
|
-
process.env.NODE_DISABLE_COLORS = '1'; // Reduce IPC message size
|
|
854
|
+
process.env.NODE_DISABLE_COLORS = '1'; // Reduce IPC message size
|
|
1106
855
|
// Graceful shutdown handler
|
|
1107
856
|
const gracefulShutdown = () => {
|
|
1108
857
|
this.logger.info('Gracefully shutting down cluster...', 'Cluster');
|
|
@@ -1118,25 +867,27 @@ class Moro extends events_1.EventEmitter {
|
|
|
1118
867
|
// Handle process signals for graceful shutdown
|
|
1119
868
|
process.on('SIGINT', gracefulShutdown);
|
|
1120
869
|
process.on('SIGTERM', gracefulShutdown);
|
|
1121
|
-
// Fork workers with
|
|
870
|
+
// Fork workers with proper tracking and CPU affinity
|
|
1122
871
|
for (let i = 0; i < workerCount; i++) {
|
|
1123
|
-
const worker = cluster.fork(
|
|
872
|
+
const worker = cluster.fork({
|
|
873
|
+
WORKER_ID: i,
|
|
874
|
+
WORKER_CPU_AFFINITY: i % os.cpus().length, // Distribute workers across CPUs
|
|
875
|
+
});
|
|
1124
876
|
this.clusterWorkers.set(worker.process.pid, worker);
|
|
1125
|
-
this.logger.info(`Worker ${worker.process.pid} started`, 'Cluster');
|
|
1126
|
-
// Handle individual worker messages
|
|
877
|
+
this.logger.info(`Worker ${worker.process.pid} started (CPU ${i % os.cpus().length})`, 'Cluster');
|
|
878
|
+
// Handle individual worker messages (reuse handler)
|
|
1127
879
|
worker.on('message', this.handleWorkerMessage.bind(this));
|
|
1128
880
|
}
|
|
1129
|
-
//
|
|
881
|
+
// Handle worker exits with cleanup
|
|
1130
882
|
cluster.on('exit', (worker, code, signal) => {
|
|
1131
|
-
|
|
1132
|
-
this.clusterWorkers.delete(pid);
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
}
|
|
883
|
+
// Clean up worker tracking
|
|
884
|
+
this.clusterWorkers.delete(worker.process.pid);
|
|
885
|
+
this.logger.warn(`Worker ${worker.process.pid} died (${signal || code}). Restarting...`, 'Cluster');
|
|
886
|
+
// Restart worker with proper tracking
|
|
887
|
+
const newWorker = cluster.fork();
|
|
888
|
+
this.clusterWorkers.set(newWorker.process.pid, newWorker);
|
|
889
|
+
newWorker.on('message', this.handleWorkerMessage.bind(this));
|
|
890
|
+
this.logger.info(`Worker ${newWorker.process.pid} started`, 'Cluster');
|
|
1140
891
|
});
|
|
1141
892
|
// Master process callback
|
|
1142
893
|
if (callback)
|
|
@@ -1148,21 +899,12 @@ class Moro extends events_1.EventEmitter {
|
|
|
1148
899
|
// Worker-specific optimizations for high concurrency
|
|
1149
900
|
process.env.UV_THREADPOOL_SIZE = '64';
|
|
1150
901
|
// Reduce logging contention in workers (major bottleneck)
|
|
1151
|
-
// Multiple workers writing to same log files creates I/O contention
|
|
1152
902
|
if (this.config.logging) {
|
|
1153
903
|
// Workers log less frequently to reduce I/O contention
|
|
1154
|
-
|
|
904
|
+
this.config.logging.level = 'warn'; // Only warnings and errors
|
|
1155
905
|
}
|
|
1156
|
-
//
|
|
1157
|
-
|
|
1158
|
-
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
1159
|
-
const workerCount = Object.keys(require('cluster').workers || {}).length || 1;
|
|
1160
|
-
// Conservative memory allocation
|
|
1161
|
-
const heapSizePerWorkerMB = Math.min(Math.floor(((totalMemoryGB * 1024) / workerCount) * 0.8), // 80% of available memory
|
|
1162
|
-
1536 // Cap at 1.5GB (GC efficiency threshold from research)
|
|
1163
|
-
);
|
|
1164
|
-
process.env.NODE_OPTIONS = `--max-old-space-size=${heapSizePerWorkerMB}`;
|
|
1165
|
-
this.logger.debug(`Worker memory allocated: ${heapSizePerWorkerMB}MB heap (${workerCount} workers, ${totalMemoryGB.toFixed(1)}GB total)`, 'Worker');
|
|
906
|
+
// Memory optimization for workers
|
|
907
|
+
process.env.NODE_OPTIONS = '--max-old-space-size=1024'; // Limit memory per worker
|
|
1166
908
|
// Optimize V8 flags for better performance (Rust-level optimizations)
|
|
1167
909
|
if (process.env.NODE_ENV === 'production') {
|
|
1168
910
|
// Ultra-aggressive V8 optimizations for maximum performance
|
|
@@ -1246,7 +988,7 @@ class Moro extends events_1.EventEmitter {
|
|
|
1246
988
|
}
|
|
1247
989
|
}
|
|
1248
990
|
}
|
|
1249
|
-
//
|
|
991
|
+
// Reusable worker message handler (avoids creating new functions)
|
|
1250
992
|
handleWorkerMessage(message) {
|
|
1251
993
|
// Handle inter-worker communication if needed
|
|
1252
994
|
if (message.type === 'health-check') {
|
|
@@ -1256,56 +998,6 @@ class Moro extends events_1.EventEmitter {
|
|
|
1256
998
|
// Log other worker messages
|
|
1257
999
|
this.logger.debug(`Worker message: ${JSON.stringify(message)}`, 'Cluster');
|
|
1258
1000
|
}
|
|
1259
|
-
/**
|
|
1260
|
-
* Gracefully close the application and clean up resources
|
|
1261
|
-
* This should be called in tests and during shutdown
|
|
1262
|
-
*/
|
|
1263
|
-
async close() {
|
|
1264
|
-
this.logger.debug('Closing Moro application...');
|
|
1265
|
-
// Flush logger buffer before shutdown
|
|
1266
|
-
try {
|
|
1267
|
-
// Use flushBuffer for immediate synchronous flush
|
|
1268
|
-
this.logger.flushBuffer();
|
|
1269
|
-
}
|
|
1270
|
-
catch (error) {
|
|
1271
|
-
// Ignore flush errors during shutdown
|
|
1272
|
-
}
|
|
1273
|
-
// Close the core framework with timeout
|
|
1274
|
-
if (this.coreFramework && this.coreFramework.httpServer) {
|
|
1275
|
-
try {
|
|
1276
|
-
await Promise.race([
|
|
1277
|
-
new Promise(resolve => {
|
|
1278
|
-
this.coreFramework.httpServer.close(() => {
|
|
1279
|
-
resolve();
|
|
1280
|
-
});
|
|
1281
|
-
}),
|
|
1282
|
-
new Promise(resolve => setTimeout(resolve, 2000)), // 2 second timeout
|
|
1283
|
-
]);
|
|
1284
|
-
}
|
|
1285
|
-
catch (error) {
|
|
1286
|
-
// Force close if graceful close fails
|
|
1287
|
-
this.logger.warn('Force closing HTTP server due to timeout');
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
// Clean up module discovery watchers
|
|
1291
|
-
if (this.moduleDiscovery && typeof this.moduleDiscovery.cleanup === 'function') {
|
|
1292
|
-
try {
|
|
1293
|
-
this.moduleDiscovery.cleanup();
|
|
1294
|
-
}
|
|
1295
|
-
catch (error) {
|
|
1296
|
-
// Ignore cleanup errors
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
// Clean up event listeners
|
|
1300
|
-
try {
|
|
1301
|
-
this.eventBus.removeAllListeners();
|
|
1302
|
-
this.removeAllListeners();
|
|
1303
|
-
}
|
|
1304
|
-
catch (error) {
|
|
1305
|
-
// Ignore cleanup errors
|
|
1306
|
-
}
|
|
1307
|
-
this.logger.debug('Moro application closed successfully');
|
|
1308
|
-
}
|
|
1309
1001
|
}
|
|
1310
1002
|
exports.Moro = Moro;
|
|
1311
1003
|
// Export convenience function
|