@morojs/moro 1.5.4 → 1.5.6
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-manager.d.ts +44 -0
- package/dist/core/config/config-manager.js +114 -0
- package/dist/core/config/config-manager.js.map +1 -0
- package/dist/core/config/config-sources.d.ts +21 -0
- package/dist/core/config/config-sources.js +314 -0
- package/dist/core/config/config-sources.js.map +1 -0
- package/dist/core/config/config-validator.d.ts +21 -0
- package/dist/core/config/config-validator.js +737 -0
- package/dist/core/config/config-validator.js.map +1 -0
- package/dist/core/config/file-loader.d.ts +0 -5
- package/dist/core/config/file-loader.js +0 -171
- package/dist/core/config/file-loader.js.map +1 -1
- package/dist/core/config/index.d.ts +39 -10
- package/dist/core/config/index.js +66 -29
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/schema.js +22 -31
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/utils.d.ts +9 -2
- package/dist/core/config/utils.js +19 -32
- package/dist/core/config/utils.js.map +1 -1
- package/dist/core/framework.d.ts +2 -7
- package/dist/core/framework.js +12 -5
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/http-server.d.ts +12 -0
- package/dist/core/http/http-server.js +56 -0
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/router.d.ts +12 -0
- package/dist/core/http/router.js +114 -36
- package/dist/core/http/router.js.map +1 -1
- package/dist/core/logger/index.d.ts +1 -1
- package/dist/core/logger/index.js +2 -1
- package/dist/core/logger/index.js.map +1 -1
- package/dist/core/logger/logger.d.ts +10 -1
- package/dist/core/logger/logger.js +99 -37
- package/dist/core/logger/logger.js.map +1 -1
- package/dist/core/routing/index.d.ts +20 -0
- package/dist/core/routing/index.js +109 -11
- package/dist/core/routing/index.js.map +1 -1
- package/dist/moro.d.ts +22 -0
- package/dist/moro.js +134 -98
- package/dist/moro.js.map +1 -1
- package/dist/types/config.d.ts +39 -2
- package/dist/types/core.d.ts +22 -39
- package/dist/types/logger.d.ts +4 -0
- package/package.json +1 -1
- package/src/core/config/config-manager.ts +133 -0
- package/src/core/config/config-sources.ts +384 -0
- package/src/core/config/config-validator.ts +1035 -0
- package/src/core/config/file-loader.ts +0 -233
- package/src/core/config/index.ts +77 -32
- package/src/core/config/schema.ts +22 -31
- package/src/core/config/utils.ts +22 -29
- package/src/core/framework.ts +18 -11
- package/src/core/http/http-server.ts +66 -0
- package/src/core/http/router.ts +127 -38
- package/src/core/logger/index.ts +1 -0
- package/src/core/logger/logger.ts +109 -36
- package/src/core/routing/index.ts +116 -12
- package/src/moro.ts +159 -107
- package/src/types/config.ts +40 -2
- package/src/types/core.ts +32 -43
- package/src/types/logger.ts +6 -0
- package/dist/core/config/loader.d.ts +0 -7
- package/dist/core/config/loader.js +0 -269
- package/dist/core/config/loader.js.map +0 -1
- package/dist/core/config/validation.d.ts +0 -17
- package/dist/core/config/validation.js +0 -131
- package/dist/core/config/validation.js.map +0 -1
- package/src/core/config/loader.ts +0 -633
- package/src/core/config/validation.ts +0 -140
|
@@ -256,20 +256,91 @@ export class IntelligentRouteBuilder implements RouteBuilder {
|
|
|
256
256
|
|
|
257
257
|
// Executable route with intelligent middleware ordering
|
|
258
258
|
export class ExecutableRoute implements CompiledRoute {
|
|
259
|
-
|
|
259
|
+
// PERFORMANCE OPTIMIZATION: Pre-analyze route requirements
|
|
260
|
+
private readonly requiresAuth: boolean;
|
|
261
|
+
private readonly requiresValidation: boolean;
|
|
262
|
+
private readonly requiresRateLimit: boolean;
|
|
263
|
+
private readonly requiresCache: boolean;
|
|
264
|
+
private readonly hasBeforeMiddleware: boolean;
|
|
265
|
+
private readonly hasAfterMiddleware: boolean;
|
|
266
|
+
private readonly hasTransformMiddleware: boolean;
|
|
267
|
+
private readonly isFastPath: boolean;
|
|
268
|
+
|
|
269
|
+
constructor(public readonly schema: RouteSchema) {
|
|
270
|
+
// Pre-calculate what this route actually needs
|
|
271
|
+
this.requiresAuth = !!this.schema.auth;
|
|
272
|
+
this.requiresValidation = !!this.schema.validation;
|
|
273
|
+
this.requiresRateLimit = !!this.schema.rateLimit;
|
|
274
|
+
this.requiresCache = !!this.schema.cache;
|
|
275
|
+
this.hasBeforeMiddleware = !!this.schema.middleware?.before?.length;
|
|
276
|
+
this.hasAfterMiddleware = !!this.schema.middleware?.after?.length;
|
|
277
|
+
this.hasTransformMiddleware = !!this.schema.middleware?.transform?.length;
|
|
278
|
+
|
|
279
|
+
// Fast path: no middleware, no auth, no validation, no rate limiting
|
|
280
|
+
this.isFastPath =
|
|
281
|
+
!this.requiresAuth &&
|
|
282
|
+
!this.requiresValidation &&
|
|
283
|
+
!this.requiresRateLimit &&
|
|
284
|
+
!this.requiresCache &&
|
|
285
|
+
!this.hasBeforeMiddleware &&
|
|
286
|
+
!this.hasAfterMiddleware &&
|
|
287
|
+
!this.hasTransformMiddleware;
|
|
288
|
+
|
|
289
|
+
// Log fast path routes for monitoring
|
|
290
|
+
if (this.isFastPath) {
|
|
291
|
+
logger.debug(`Fast path route: ${this.schema.method} ${this.schema.path}`, 'FastPath');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
260
294
|
|
|
261
295
|
async execute(req: HttpRequest, res: HttpResponse): Promise<void> {
|
|
262
296
|
const validatedReq = req as ValidatedRequest;
|
|
263
297
|
|
|
264
298
|
try {
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
299
|
+
// PERFORMANCE OPTIMIZATION: Fast path for simple routes
|
|
300
|
+
if (this.isFastPath) {
|
|
301
|
+
// Skip all middleware - execute handler directly
|
|
302
|
+
const result = await this.schema.handler(validatedReq, res);
|
|
303
|
+
if (result !== undefined && !res.headersSent) {
|
|
304
|
+
res.json(result);
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Optimized middleware execution - only run what's needed
|
|
310
|
+
if (this.hasBeforeMiddleware) {
|
|
311
|
+
await this.executePhase('before', validatedReq, res);
|
|
312
|
+
if (res.headersSent) return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (this.requiresRateLimit) {
|
|
316
|
+
await this.executePhase('rateLimit', validatedReq, res);
|
|
317
|
+
if (res.headersSent) return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (this.requiresAuth) {
|
|
321
|
+
await this.executePhase('auth', validatedReq, res);
|
|
322
|
+
if (res.headersSent) return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (this.requiresValidation) {
|
|
326
|
+
await this.executePhase('validation', validatedReq, res);
|
|
327
|
+
if (res.headersSent) return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (this.hasTransformMiddleware) {
|
|
331
|
+
await this.executePhase('transform', validatedReq, res);
|
|
332
|
+
if (res.headersSent) return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (this.requiresCache) {
|
|
336
|
+
await this.executePhase('cache', validatedReq, res);
|
|
337
|
+
if (res.headersSent) return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (this.hasAfterMiddleware) {
|
|
341
|
+
await this.executePhase('after', validatedReq, res);
|
|
342
|
+
if (res.headersSent) return;
|
|
343
|
+
}
|
|
273
344
|
|
|
274
345
|
// Execute handler last
|
|
275
346
|
if (!res.headersSent) {
|
|
@@ -361,15 +432,32 @@ export class ExecutableRoute implements CompiledRoute {
|
|
|
361
432
|
req: HttpRequest,
|
|
362
433
|
res: HttpResponse
|
|
363
434
|
): Promise<void> {
|
|
435
|
+
// PERFORMANCE OPTIMIZATION: Reduce Promise overhead
|
|
364
436
|
return new Promise((resolve, reject) => {
|
|
437
|
+
let resolved = false;
|
|
438
|
+
|
|
439
|
+
const next = () => {
|
|
440
|
+
if (!resolved) {
|
|
441
|
+
resolved = true;
|
|
442
|
+
resolve();
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
|
|
365
446
|
try {
|
|
366
|
-
const next = () => resolve();
|
|
367
447
|
const result = middleware(req, res, next);
|
|
368
448
|
if (result instanceof Promise) {
|
|
369
|
-
result.then(() =>
|
|
449
|
+
result.then(() => !resolved && next()).catch(reject);
|
|
450
|
+
} else {
|
|
451
|
+
// Synchronous middleware - call next immediately if not called
|
|
452
|
+
if (!resolved) {
|
|
453
|
+
next();
|
|
454
|
+
}
|
|
370
455
|
}
|
|
371
456
|
} catch (error) {
|
|
372
|
-
|
|
457
|
+
if (!resolved) {
|
|
458
|
+
resolved = true;
|
|
459
|
+
reject(error);
|
|
460
|
+
}
|
|
373
461
|
}
|
|
374
462
|
});
|
|
375
463
|
}
|
|
@@ -475,6 +563,22 @@ export class ExecutableRoute implements CompiledRoute {
|
|
|
475
563
|
config: this.schema.cache,
|
|
476
564
|
});
|
|
477
565
|
}
|
|
566
|
+
|
|
567
|
+
// Performance monitoring
|
|
568
|
+
getPerformanceInfo() {
|
|
569
|
+
return {
|
|
570
|
+
path: this.schema.path,
|
|
571
|
+
method: this.schema.method,
|
|
572
|
+
isFastPath: this.isFastPath,
|
|
573
|
+
requiresAuth: this.requiresAuth,
|
|
574
|
+
requiresValidation: this.requiresValidation,
|
|
575
|
+
requiresRateLimit: this.requiresRateLimit,
|
|
576
|
+
requiresCache: this.requiresCache,
|
|
577
|
+
hasBeforeMiddleware: this.hasBeforeMiddleware,
|
|
578
|
+
hasAfterMiddleware: this.hasAfterMiddleware,
|
|
579
|
+
hasTransformMiddleware: this.hasTransformMiddleware,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
478
582
|
}
|
|
479
583
|
|
|
480
584
|
// Factory functions for creating routes
|
package/src/moro.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
logger as globalLogger,
|
|
12
12
|
applyLoggingConfiguration,
|
|
13
13
|
} from './core/logger';
|
|
14
|
+
import { Logger } from './types/logger';
|
|
14
15
|
import { MiddlewareManager } from './core/middleware';
|
|
15
16
|
import { IntelligentRoutingManager } from './core/routing/app-integration';
|
|
16
17
|
import { RouteBuilder, RouteSchema, CompiledRoute } from './core/routing';
|
|
@@ -19,7 +20,12 @@ import { readdirSync, statSync } from 'fs';
|
|
|
19
20
|
import { join } from 'path';
|
|
20
21
|
import { EventEmitter } from 'events';
|
|
21
22
|
// Configuration System Integration
|
|
22
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
initializeConfig,
|
|
25
|
+
getGlobalConfig,
|
|
26
|
+
loadConfigWithOptions,
|
|
27
|
+
type AppConfig,
|
|
28
|
+
} from './core/config';
|
|
23
29
|
// Runtime System Integration
|
|
24
30
|
import {
|
|
25
31
|
RuntimeAdapter,
|
|
@@ -37,7 +43,7 @@ export class Moro extends EventEmitter {
|
|
|
37
43
|
// Enterprise event system integration
|
|
38
44
|
private eventBus: MoroEventBus;
|
|
39
45
|
// Application logger
|
|
40
|
-
private logger
|
|
46
|
+
private logger!: Logger;
|
|
41
47
|
// Intelligent routing system
|
|
42
48
|
private intelligentRouting = new IntelligentRoutingManager();
|
|
43
49
|
// Documentation system
|
|
@@ -53,72 +59,33 @@ export class Moro extends EventEmitter {
|
|
|
53
59
|
constructor(options: MoroOptions = {}) {
|
|
54
60
|
super(); // Call EventEmitter constructor
|
|
55
61
|
|
|
56
|
-
//
|
|
57
|
-
//
|
|
62
|
+
// Apply logging configuration BEFORE config loading to avoid DEBUG spam
|
|
63
|
+
// 1. Environment variables (base level)
|
|
58
64
|
const envLogLevel = process.env.LOG_LEVEL || process.env.MORO_LOG_LEVEL;
|
|
59
65
|
if (envLogLevel) {
|
|
60
66
|
applyLoggingConfiguration({ level: envLogLevel }, undefined);
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
//
|
|
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)
|
|
69
|
+
// 2. createApp logger options (highest precedence)
|
|
72
70
|
if (options.logger !== undefined) {
|
|
73
71
|
applyLoggingConfiguration(undefined, options.logger);
|
|
74
72
|
}
|
|
75
73
|
|
|
76
|
-
//
|
|
77
|
-
|
|
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
|
-
}
|
|
74
|
+
// Create logger AFTER initial configuration
|
|
75
|
+
this.logger = createFrameworkLogger('App');
|
|
97
76
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
}
|
|
77
|
+
// Use simplified global configuration system
|
|
78
|
+
this.config = initializeConfig(options);
|
|
79
|
+
|
|
80
|
+
// Apply config file logging if it exists (may override createApp options if needed)
|
|
81
|
+
if (this.config.logging && !options.logger) {
|
|
82
|
+
applyLoggingConfiguration(this.config.logging, undefined);
|
|
83
|
+
// Recreate logger with updated config
|
|
84
|
+
this.logger = createFrameworkLogger('App');
|
|
118
85
|
}
|
|
119
86
|
|
|
120
87
|
this.logger.info(
|
|
121
|
-
`Configuration system initialized: ${
|
|
88
|
+
`Configuration system initialized: ${process.env.NODE_ENV || 'development'}:${this.config.server.port}`
|
|
122
89
|
);
|
|
123
90
|
|
|
124
91
|
// Initialize runtime system
|
|
@@ -127,10 +94,11 @@ export class Moro extends EventEmitter {
|
|
|
127
94
|
|
|
128
95
|
this.logger.info(`Runtime system initialized: ${this.runtimeType}`, 'Runtime');
|
|
129
96
|
|
|
130
|
-
// Pass
|
|
97
|
+
// Pass configuration from config to framework
|
|
131
98
|
const frameworkOptions: any = {
|
|
132
99
|
...options,
|
|
133
100
|
logger: this.config.logging,
|
|
101
|
+
websocket: this.config.websocket.enabled ? options.websocket || {} : false,
|
|
134
102
|
};
|
|
135
103
|
|
|
136
104
|
this.coreFramework = new MoroCore(frameworkOptions);
|
|
@@ -540,8 +508,9 @@ export class Moro extends EventEmitter {
|
|
|
540
508
|
this.logger.info('Moro Server Started', 'Server');
|
|
541
509
|
this.logger.info(`Runtime: ${this.runtimeType}`, 'Server');
|
|
542
510
|
this.logger.info(`HTTP API: http://${displayHost}:${port}`, 'Server');
|
|
543
|
-
this.
|
|
544
|
-
|
|
511
|
+
if (this.config.websocket.enabled) {
|
|
512
|
+
this.logger.info(`WebSocket: ws://${displayHost}:${port}`, 'Server');
|
|
513
|
+
}
|
|
545
514
|
this.logger.info('Learn more at https://morojs.com', 'Server');
|
|
546
515
|
|
|
547
516
|
// Log intelligent routes info
|
|
@@ -935,55 +904,84 @@ export class Moro extends EventEmitter {
|
|
|
935
904
|
return module.default || module;
|
|
936
905
|
}
|
|
937
906
|
|
|
938
|
-
|
|
907
|
+
/**
|
|
908
|
+
* Node.js Clustering Implementation
|
|
909
|
+
* This clustering algorithm is based on published research and Node.js best practices.
|
|
910
|
+
*
|
|
911
|
+
* IPC (Inter-Process Communication) Considerations:
|
|
912
|
+
* - Excessive workers create IPC bottlenecks (Source: BetterStack Node.js Guide)
|
|
913
|
+
* - Round-robin scheduling provides better load distribution (Node.js Documentation)
|
|
914
|
+
* - Message passing overhead increases significantly with worker count
|
|
915
|
+
*
|
|
916
|
+
* Memory Management:
|
|
917
|
+
* - ~2GB per worker prevents memory pressure and GC overhead
|
|
918
|
+
* - Conservative heap limits reduce memory fragmentation
|
|
919
|
+
*
|
|
920
|
+
* References:
|
|
921
|
+
* - Node.js Cluster Documentation: https://nodejs.org/api/cluster.html
|
|
922
|
+
* - BetterStack Node.js Clustering: https://betterstack.com/community/guides/scaling-nodejs/node-clustering/
|
|
923
|
+
*/
|
|
939
924
|
private clusterWorkers = new Map<number, any>();
|
|
925
|
+
|
|
940
926
|
private startWithClustering(port: number, host?: string, callback?: () => void): void {
|
|
941
927
|
const cluster = require('cluster');
|
|
942
928
|
const os = require('os');
|
|
943
929
|
|
|
944
|
-
//
|
|
930
|
+
// Worker count calculation - respect user choice
|
|
945
931
|
let workerCount = this.config.performance?.clustering?.workers || os.cpus().length;
|
|
946
932
|
|
|
947
|
-
//
|
|
948
|
-
if (workerCount === 'auto'
|
|
949
|
-
// For high-core machines, limit workers to prevent IPC/memory bottlenecks
|
|
933
|
+
// Only auto-optimize if user hasn't specified a number or set it to 'auto'
|
|
934
|
+
if (workerCount === 'auto') {
|
|
950
935
|
const cpuCount = os.cpus().length;
|
|
951
936
|
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
952
937
|
|
|
953
|
-
//
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
} else {
|
|
961
|
-
// Low-core machines: use all cores
|
|
962
|
-
workerCount = cpuCount;
|
|
938
|
+
// Get memory per worker from config - if not set by user, calculate dynamically
|
|
939
|
+
let memoryPerWorkerGB = this.config.performance?.clustering?.memoryPerWorkerGB;
|
|
940
|
+
|
|
941
|
+
if (!memoryPerWorkerGB) {
|
|
942
|
+
// Dynamic calculation: (Total RAM - 4GB headroom) / CPU cores
|
|
943
|
+
const headroomGB = 4;
|
|
944
|
+
memoryPerWorkerGB = Math.max(0.5, Math.floor((totalMemoryGB - headroomGB) / cpuCount));
|
|
963
945
|
}
|
|
964
946
|
|
|
947
|
+
// Conservative formula based on general guidelines:
|
|
948
|
+
// - Don't exceed CPU cores
|
|
949
|
+
// - Respect user's memory allocation preference
|
|
950
|
+
// - Let the system resources determine the limit
|
|
951
|
+
workerCount = Math.min(
|
|
952
|
+
cpuCount, // Don't exceed CPU cores
|
|
953
|
+
Math.floor(totalMemoryGB / memoryPerWorkerGB) // User-configurable memory per worker
|
|
954
|
+
);
|
|
955
|
+
|
|
965
956
|
this.logger.info(
|
|
966
|
-
`Auto-
|
|
957
|
+
`Auto-calculated worker count: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB, ${memoryPerWorkerGB}GB per worker)`,
|
|
967
958
|
'Cluster'
|
|
968
959
|
);
|
|
960
|
+
} else if (typeof workerCount === 'number') {
|
|
961
|
+
// User specified a number - respect their choice
|
|
962
|
+
this.logger.info(`Using user-specified worker count: ${workerCount}`, 'Cluster');
|
|
969
963
|
}
|
|
970
964
|
|
|
971
965
|
if (cluster.isPrimary) {
|
|
972
|
-
this.logger.info(
|
|
966
|
+
this.logger.info(`Starting ${workerCount} workers`, 'Cluster');
|
|
973
967
|
|
|
974
968
|
// Optimize cluster scheduling for high concurrency
|
|
975
|
-
|
|
969
|
+
// Round-robin is the default on all platforms except Windows (Node.js docs)
|
|
970
|
+
// Provides better load distribution than shared socket approach
|
|
971
|
+
cluster.schedulingPolicy = cluster.SCHED_RR;
|
|
976
972
|
|
|
977
973
|
// Set cluster settings for better performance
|
|
978
974
|
cluster.setupMaster({
|
|
979
|
-
exec: process.argv[1],
|
|
975
|
+
exec: process.argv[1] || process.execPath,
|
|
980
976
|
args: process.argv.slice(2),
|
|
981
977
|
silent: false,
|
|
982
978
|
});
|
|
983
979
|
|
|
984
|
-
//
|
|
980
|
+
// IPC Optimization: Reduce communication overhead between master and workers
|
|
981
|
+
// Research shows excessive IPC can create bottlenecks in clustered applications
|
|
982
|
+
// (Source: BetterStack - Node.js Clustering Guide)
|
|
985
983
|
process.env.NODE_CLUSTER_SCHED_POLICY = 'rr'; // Ensure round-robin
|
|
986
|
-
process.env.NODE_DISABLE_COLORS = '1'; // Reduce IPC message size
|
|
984
|
+
process.env.NODE_DISABLE_COLORS = '1'; // Reduce IPC message size by disabling color codes
|
|
987
985
|
|
|
988
986
|
// Graceful shutdown handler
|
|
989
987
|
const gracefulShutdown = () => {
|
|
@@ -1004,37 +1002,32 @@ export class Moro extends EventEmitter {
|
|
|
1004
1002
|
process.on('SIGINT', gracefulShutdown);
|
|
1005
1003
|
process.on('SIGTERM', gracefulShutdown);
|
|
1006
1004
|
|
|
1007
|
-
// Fork workers with
|
|
1005
|
+
// Fork workers with basic tracking
|
|
1008
1006
|
for (let i = 0; i < workerCount; i++) {
|
|
1009
|
-
const worker = cluster.fork(
|
|
1010
|
-
WORKER_ID: i,
|
|
1011
|
-
WORKER_CPU_AFFINITY: i % os.cpus().length, // Distribute workers across CPUs
|
|
1012
|
-
});
|
|
1007
|
+
const worker = cluster.fork();
|
|
1013
1008
|
this.clusterWorkers.set(worker.process.pid!, worker);
|
|
1014
|
-
this.logger.info(
|
|
1015
|
-
`Worker ${worker.process.pid} started (CPU ${i % os.cpus().length})`,
|
|
1016
|
-
'Cluster'
|
|
1017
|
-
);
|
|
1009
|
+
this.logger.info(`Worker ${worker.process.pid} started`, 'Cluster');
|
|
1018
1010
|
|
|
1019
|
-
// Handle individual worker messages
|
|
1011
|
+
// Handle individual worker messages
|
|
1020
1012
|
worker.on('message', this.handleWorkerMessage.bind(this));
|
|
1021
1013
|
}
|
|
1022
1014
|
|
|
1023
|
-
//
|
|
1015
|
+
// Simple worker exit handling
|
|
1024
1016
|
cluster.on('exit', (worker: any, code: number, signal: string) => {
|
|
1025
|
-
|
|
1026
|
-
this.clusterWorkers.delete(
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1017
|
+
const pid = worker.process.pid;
|
|
1018
|
+
this.clusterWorkers.delete(pid);
|
|
1019
|
+
|
|
1020
|
+
if (code !== 0 && !worker.exitedAfterDisconnect) {
|
|
1021
|
+
this.logger.warn(
|
|
1022
|
+
`Worker ${pid} died unexpectedly (${signal || code}). Restarting...`,
|
|
1023
|
+
'Cluster'
|
|
1024
|
+
);
|
|
1025
|
+
|
|
1026
|
+
// Simple restart
|
|
1027
|
+
const newWorker = cluster.fork();
|
|
1028
|
+
this.clusterWorkers.set(newWorker.process.pid!, newWorker);
|
|
1029
|
+
this.logger.info(`Worker ${newWorker.process.pid} restarted`, 'Cluster');
|
|
1030
|
+
}
|
|
1038
1031
|
});
|
|
1039
1032
|
|
|
1040
1033
|
// Master process callback
|
|
@@ -1047,13 +1040,29 @@ export class Moro extends EventEmitter {
|
|
|
1047
1040
|
process.env.UV_THREADPOOL_SIZE = '64';
|
|
1048
1041
|
|
|
1049
1042
|
// Reduce logging contention in workers (major bottleneck)
|
|
1043
|
+
// Multiple workers writing to same log files creates I/O contention
|
|
1050
1044
|
if (this.config.logging) {
|
|
1051
1045
|
// Workers log less frequently to reduce I/O contention
|
|
1052
|
-
|
|
1046
|
+
applyLoggingConfiguration(undefined, { level: 'warn' }); // Only warnings and errors
|
|
1053
1047
|
}
|
|
1054
1048
|
|
|
1055
|
-
//
|
|
1056
|
-
|
|
1049
|
+
// Research-based memory optimization for workers
|
|
1050
|
+
const os = require('os');
|
|
1051
|
+
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
1052
|
+
const workerCount = Object.keys(require('cluster').workers || {}).length || 1;
|
|
1053
|
+
|
|
1054
|
+
// Conservative memory allocation
|
|
1055
|
+
const heapSizePerWorkerMB = Math.min(
|
|
1056
|
+
Math.floor(((totalMemoryGB * 1024) / workerCount) * 0.8), // 80% of available memory
|
|
1057
|
+
1536 // Cap at 1.5GB (GC efficiency threshold from research)
|
|
1058
|
+
);
|
|
1059
|
+
|
|
1060
|
+
process.env.NODE_OPTIONS = `--max-old-space-size=${heapSizePerWorkerMB}`;
|
|
1061
|
+
|
|
1062
|
+
this.logger.debug(
|
|
1063
|
+
`Worker memory allocated: ${heapSizePerWorkerMB}MB heap (${workerCount} workers, ${totalMemoryGB.toFixed(1)}GB total)`,
|
|
1064
|
+
'Worker'
|
|
1065
|
+
);
|
|
1057
1066
|
|
|
1058
1067
|
// Optimize V8 flags for better performance (Rust-level optimizations)
|
|
1059
1068
|
if (process.env.NODE_ENV === 'production') {
|
|
@@ -1149,7 +1158,7 @@ export class Moro extends EventEmitter {
|
|
|
1149
1158
|
}
|
|
1150
1159
|
}
|
|
1151
1160
|
|
|
1152
|
-
//
|
|
1161
|
+
// Simple worker message handler
|
|
1153
1162
|
private handleWorkerMessage(message: any): void {
|
|
1154
1163
|
// Handle inter-worker communication if needed
|
|
1155
1164
|
if (message.type === 'health-check') {
|
|
@@ -1160,6 +1169,49 @@ export class Moro extends EventEmitter {
|
|
|
1160
1169
|
// Log other worker messages
|
|
1161
1170
|
this.logger.debug(`Worker message: ${JSON.stringify(message)}`, 'Cluster');
|
|
1162
1171
|
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Gracefully close the application and clean up resources
|
|
1175
|
+
* This should be called in tests and during shutdown
|
|
1176
|
+
*/
|
|
1177
|
+
async close(): Promise<void> {
|
|
1178
|
+
this.logger.debug('Closing Moro application...');
|
|
1179
|
+
|
|
1180
|
+
// Flush logger buffer before shutdown
|
|
1181
|
+
try {
|
|
1182
|
+
// Use flushBuffer for immediate synchronous flush
|
|
1183
|
+
this.logger.flushBuffer();
|
|
1184
|
+
} catch (error) {
|
|
1185
|
+
// Ignore flush errors during shutdown
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// Close the core framework with timeout
|
|
1189
|
+
if (this.coreFramework && (this.coreFramework as any).httpServer) {
|
|
1190
|
+
try {
|
|
1191
|
+
await Promise.race([
|
|
1192
|
+
new Promise<void>(resolve => {
|
|
1193
|
+
(this.coreFramework as any).httpServer.close(() => {
|
|
1194
|
+
resolve();
|
|
1195
|
+
});
|
|
1196
|
+
}),
|
|
1197
|
+
new Promise<void>(resolve => setTimeout(resolve, 2000)), // 2 second timeout
|
|
1198
|
+
]);
|
|
1199
|
+
} catch (error) {
|
|
1200
|
+
// Force close if graceful close fails
|
|
1201
|
+
this.logger.warn('Force closing HTTP server due to timeout');
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// Clean up event listeners
|
|
1206
|
+
try {
|
|
1207
|
+
this.eventBus.removeAllListeners();
|
|
1208
|
+
this.removeAllListeners();
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
// Ignore cleanup errors
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
this.logger.debug('Moro application closed successfully');
|
|
1214
|
+
}
|
|
1163
1215
|
}
|
|
1164
1216
|
|
|
1165
1217
|
// Export convenience function
|
package/src/types/config.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
export interface ServerConfig {
|
|
4
4
|
port: number;
|
|
5
5
|
host: string;
|
|
6
|
-
environment: 'development' | 'staging' | 'production';
|
|
7
6
|
maxConnections: number;
|
|
8
7
|
timeout: number;
|
|
9
8
|
}
|
|
@@ -19,7 +18,7 @@ export interface ServiceDiscoveryConfig {
|
|
|
19
18
|
|
|
20
19
|
export interface DatabaseConfig {
|
|
21
20
|
url?: string;
|
|
22
|
-
redis
|
|
21
|
+
redis?: {
|
|
23
22
|
url: string;
|
|
24
23
|
maxRetries: number;
|
|
25
24
|
retryDelay: number;
|
|
@@ -35,6 +34,28 @@ export interface DatabaseConfig {
|
|
|
35
34
|
acquireTimeout: number;
|
|
36
35
|
timeout: number;
|
|
37
36
|
};
|
|
37
|
+
postgresql?: {
|
|
38
|
+
host: string;
|
|
39
|
+
port: number;
|
|
40
|
+
database?: string;
|
|
41
|
+
user?: string;
|
|
42
|
+
password?: string;
|
|
43
|
+
connectionLimit: number;
|
|
44
|
+
ssl?: boolean;
|
|
45
|
+
};
|
|
46
|
+
sqlite?: {
|
|
47
|
+
filename: string;
|
|
48
|
+
memory?: boolean;
|
|
49
|
+
verbose?: boolean;
|
|
50
|
+
};
|
|
51
|
+
mongodb?: {
|
|
52
|
+
url?: string;
|
|
53
|
+
host?: string;
|
|
54
|
+
port?: number;
|
|
55
|
+
database?: string;
|
|
56
|
+
username?: string;
|
|
57
|
+
password?: string;
|
|
58
|
+
};
|
|
38
59
|
}
|
|
39
60
|
|
|
40
61
|
export interface ModuleDefaultsConfig {
|
|
@@ -141,6 +162,22 @@ export interface PerformanceConfig {
|
|
|
141
162
|
clustering: {
|
|
142
163
|
enabled: boolean;
|
|
143
164
|
workers: number | 'auto';
|
|
165
|
+
memoryPerWorkerGB?: number;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface WebSocketConfig {
|
|
170
|
+
enabled: boolean;
|
|
171
|
+
adapter?: string | 'socket.io' | 'ws';
|
|
172
|
+
compression?: boolean;
|
|
173
|
+
customIdGenerator?: () => string;
|
|
174
|
+
options?: {
|
|
175
|
+
cors?: {
|
|
176
|
+
origin?: string | string[];
|
|
177
|
+
credentials?: boolean;
|
|
178
|
+
};
|
|
179
|
+
path?: string;
|
|
180
|
+
maxPayloadLength?: number;
|
|
144
181
|
};
|
|
145
182
|
}
|
|
146
183
|
|
|
@@ -154,4 +191,5 @@ export interface AppConfig {
|
|
|
154
191
|
security: SecurityConfig;
|
|
155
192
|
external: ExternalServicesConfig;
|
|
156
193
|
performance: PerformanceConfig;
|
|
194
|
+
websocket: WebSocketConfig;
|
|
157
195
|
}
|