@morojs/moro 1.5.5 → 1.5.7
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 +744 -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 +29 -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 +4 -7
- package/dist/core/framework.js +38 -12
- 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 +9 -1
- package/dist/core/logger/logger.js +36 -3
- 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 +7 -20
- package/dist/moro.js +115 -200
- package/dist/moro.js.map +1 -1
- package/dist/types/config.d.ts +46 -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 +1042 -0
- package/src/core/config/file-loader.ts +0 -233
- package/src/core/config/index.ts +77 -32
- package/src/core/config/schema.ts +29 -31
- package/src/core/config/utils.ts +22 -29
- package/src/core/framework.ts +51 -18
- 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 +43 -4
- package/src/core/routing/index.ts +116 -12
- package/src/moro.ts +127 -233
- package/src/types/config.ts +47 -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
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,12 @@ 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,
|
|
102
|
+
config: this.config,
|
|
134
103
|
};
|
|
135
104
|
|
|
136
105
|
this.coreFramework = new MoroCore(frameworkOptions);
|
|
@@ -540,8 +509,9 @@ export class Moro extends EventEmitter {
|
|
|
540
509
|
this.logger.info('Moro Server Started', 'Server');
|
|
541
510
|
this.logger.info(`Runtime: ${this.runtimeType}`, 'Server');
|
|
542
511
|
this.logger.info(`HTTP API: http://${displayHost}:${port}`, 'Server');
|
|
543
|
-
this.
|
|
544
|
-
|
|
512
|
+
if (this.config.websocket.enabled) {
|
|
513
|
+
this.logger.info(`WebSocket: ws://${displayHost}:${port}`, 'Server');
|
|
514
|
+
}
|
|
545
515
|
this.logger.info('Learn more at https://morojs.com', 'Server');
|
|
546
516
|
|
|
547
517
|
// Log intelligent routes info
|
|
@@ -886,20 +856,33 @@ export class Moro extends EventEmitter {
|
|
|
886
856
|
}
|
|
887
857
|
|
|
888
858
|
private setupDefaultMiddleware(options: MoroOptions) {
|
|
889
|
-
// CORS
|
|
890
|
-
if (options.cors
|
|
891
|
-
const corsOptions =
|
|
859
|
+
// CORS - check config enabled property OR options.security.cors.enabled === true
|
|
860
|
+
if (this.config.security.cors.enabled || options.security?.cors?.enabled === true) {
|
|
861
|
+
const corsOptions =
|
|
862
|
+
typeof options.cors === 'object'
|
|
863
|
+
? options.cors
|
|
864
|
+
: this.config.security.cors
|
|
865
|
+
? this.config.security.cors
|
|
866
|
+
: {};
|
|
892
867
|
this.use(middleware.cors(corsOptions));
|
|
893
868
|
}
|
|
894
869
|
|
|
895
|
-
// Helmet
|
|
896
|
-
if (options.helmet
|
|
870
|
+
// Helmet - check config enabled property OR options.security.helmet.enabled === true
|
|
871
|
+
if (this.config.security.helmet.enabled || options.security?.helmet?.enabled === true) {
|
|
897
872
|
this.use(middleware.helmet());
|
|
898
873
|
}
|
|
899
874
|
|
|
900
|
-
// Compression
|
|
901
|
-
if (
|
|
902
|
-
|
|
875
|
+
// Compression - check config enabled property OR options.performance.compression.enabled === true
|
|
876
|
+
if (
|
|
877
|
+
this.config.performance.compression.enabled ||
|
|
878
|
+
options.performance?.compression?.enabled === true
|
|
879
|
+
) {
|
|
880
|
+
const compressionOptions =
|
|
881
|
+
typeof options.compression === 'object'
|
|
882
|
+
? options.compression
|
|
883
|
+
: this.config.performance.compression
|
|
884
|
+
? this.config.performance.compression
|
|
885
|
+
: {};
|
|
903
886
|
this.use(middleware.compression(compressionOptions));
|
|
904
887
|
}
|
|
905
888
|
|
|
@@ -936,14 +919,8 @@ export class Moro extends EventEmitter {
|
|
|
936
919
|
}
|
|
937
920
|
|
|
938
921
|
/**
|
|
939
|
-
* Node.js Clustering Implementation
|
|
940
|
-
*
|
|
941
|
-
* This clustering algorithm is based on empirical testing and Node.js best practices.
|
|
942
|
-
* Key findings from research and testing:
|
|
943
|
-
*
|
|
944
|
-
* Performance Benefits:
|
|
945
|
-
* - Clustering can improve performance by up to 66% (Source: Medium - Danish Siddiq)
|
|
946
|
-
* - Enables utilization of multiple CPU cores in Node.js applications
|
|
922
|
+
* Node.js Clustering Implementation
|
|
923
|
+
* This clustering algorithm is based on published research and Node.js best practices.
|
|
947
924
|
*
|
|
948
925
|
* IPC (Inter-Process Communication) Considerations:
|
|
949
926
|
* - Excessive workers create IPC bottlenecks (Source: BetterStack Node.js Guide)
|
|
@@ -954,71 +931,53 @@ export class Moro extends EventEmitter {
|
|
|
954
931
|
* - ~2GB per worker prevents memory pressure and GC overhead
|
|
955
932
|
* - Conservative heap limits reduce memory fragmentation
|
|
956
933
|
*
|
|
957
|
-
* Empirical Findings (MoroJS Testing):
|
|
958
|
-
* - 4-worker cap provides optimal performance regardless of core count
|
|
959
|
-
* - IPC becomes the primary bottleneck on high-core machines (16+ cores)
|
|
960
|
-
* - Memory allocation per worker more important than CPU utilization
|
|
961
|
-
*
|
|
962
934
|
* References:
|
|
963
935
|
* - Node.js Cluster Documentation: https://nodejs.org/api/cluster.html
|
|
964
936
|
* - BetterStack Node.js Clustering: https://betterstack.com/community/guides/scaling-nodejs/node-clustering/
|
|
965
937
|
*/
|
|
966
938
|
private clusterWorkers = new Map<number, any>();
|
|
967
|
-
private workerStats = new Map<
|
|
968
|
-
number,
|
|
969
|
-
{ cpu: number; memory: number; requests: number; lastCheck: number }
|
|
970
|
-
>();
|
|
971
|
-
private adaptiveScalingEnabled = true;
|
|
972
|
-
private lastScalingCheck = 0;
|
|
973
|
-
private readonly SCALING_INTERVAL = 30000; // 30 seconds
|
|
974
939
|
|
|
975
940
|
private startWithClustering(port: number, host?: string, callback?: () => void): void {
|
|
976
941
|
const cluster = require('cluster');
|
|
977
942
|
const os = require('os');
|
|
978
943
|
|
|
979
|
-
//
|
|
980
|
-
// Based on empirical testing and Node.js clustering best practices
|
|
944
|
+
// Worker count calculation - respect user choice
|
|
981
945
|
let workerCount = this.config.performance?.clustering?.workers || os.cpus().length;
|
|
982
946
|
|
|
983
|
-
//
|
|
984
|
-
|
|
985
|
-
// cause IPC overhead that degrades performance (Source: Medium - Clustering in Node.js)
|
|
986
|
-
if (workerCount === 'auto' || workerCount > 8) {
|
|
947
|
+
// Only auto-optimize if user hasn't specified a number or set it to 'auto'
|
|
948
|
+
if (workerCount === 'auto') {
|
|
987
949
|
const cpuCount = os.cpus().length;
|
|
988
950
|
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
989
951
|
|
|
990
|
-
//
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
workerCount = Math.min(maxWorkersFromMemory, 4);
|
|
998
|
-
} else if (cpuCount >= 8) {
|
|
999
|
-
// Mid-range machines: optimal ratio found to be CPU/3 for IPC efficiency
|
|
1000
|
-
// Avoids context switching overhead while maintaining throughput
|
|
1001
|
-
workerCount = Math.min(Math.ceil(cpuCount / 3), maxWorkersFromMemory, 6);
|
|
1002
|
-
} else if (cpuCount >= 4) {
|
|
1003
|
-
// Standard machines: use 3/4 of cores to leave room for OS processes
|
|
1004
|
-
workerCount = Math.min(Math.ceil(cpuCount * 0.75), maxWorkersFromMemory, 4);
|
|
1005
|
-
} else {
|
|
1006
|
-
// Low-core machines: use all cores but cap for memory safety
|
|
1007
|
-
workerCount = Math.min(cpuCount, maxWorkersFromMemory, 2);
|
|
952
|
+
// Get memory per worker from config - if not set by user, calculate dynamically
|
|
953
|
+
let memoryPerWorkerGB = this.config.performance?.clustering?.memoryPerWorkerGB;
|
|
954
|
+
|
|
955
|
+
if (!memoryPerWorkerGB) {
|
|
956
|
+
// Dynamic calculation: (Total RAM - 4GB headroom) / CPU cores
|
|
957
|
+
const headroomGB = 4;
|
|
958
|
+
memoryPerWorkerGB = Math.max(0.5, Math.floor((totalMemoryGB - headroomGB) / cpuCount));
|
|
1008
959
|
}
|
|
1009
960
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
961
|
+
// Conservative formula based on general guidelines:
|
|
962
|
+
// - Don't exceed CPU cores
|
|
963
|
+
// - Respect user's memory allocation preference
|
|
964
|
+
// - Let the system resources determine the limit
|
|
965
|
+
workerCount = Math.min(
|
|
966
|
+
cpuCount, // Don't exceed CPU cores
|
|
967
|
+
Math.floor(totalMemoryGB / memoryPerWorkerGB) // User-configurable memory per worker
|
|
1013
968
|
);
|
|
1014
|
-
|
|
1015
|
-
|
|
969
|
+
|
|
970
|
+
this.logger.info(
|
|
971
|
+
`Auto-calculated worker count: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB, ${memoryPerWorkerGB}GB per worker)`,
|
|
1016
972
|
'Cluster'
|
|
1017
973
|
);
|
|
974
|
+
} else if (typeof workerCount === 'number') {
|
|
975
|
+
// User specified a number - respect their choice
|
|
976
|
+
this.logger.info(`Using user-specified worker count: ${workerCount}`, 'Cluster');
|
|
1018
977
|
}
|
|
1019
978
|
|
|
1020
979
|
if (cluster.isPrimary) {
|
|
1021
|
-
this.logger.info(
|
|
980
|
+
this.logger.info(`Starting ${workerCount} workers`, 'Cluster');
|
|
1022
981
|
|
|
1023
982
|
// Optimize cluster scheduling for high concurrency
|
|
1024
983
|
// Round-robin is the default on all platforms except Windows (Node.js docs)
|
|
@@ -1027,7 +986,7 @@ export class Moro extends EventEmitter {
|
|
|
1027
986
|
|
|
1028
987
|
// Set cluster settings for better performance
|
|
1029
988
|
cluster.setupMaster({
|
|
1030
|
-
exec: process.argv[1],
|
|
989
|
+
exec: process.argv[1] || process.execPath,
|
|
1031
990
|
args: process.argv.slice(2),
|
|
1032
991
|
silent: false,
|
|
1033
992
|
});
|
|
@@ -1057,50 +1016,34 @@ export class Moro extends EventEmitter {
|
|
|
1057
1016
|
process.on('SIGINT', gracefulShutdown);
|
|
1058
1017
|
process.on('SIGTERM', gracefulShutdown);
|
|
1059
1018
|
|
|
1060
|
-
// Fork workers with
|
|
1019
|
+
// Fork workers with basic tracking
|
|
1061
1020
|
for (let i = 0; i < workerCount; i++) {
|
|
1062
|
-
const worker = cluster.fork(
|
|
1063
|
-
WORKER_ID: i,
|
|
1064
|
-
WORKER_CPU_AFFINITY: i % os.cpus().length, // Distribute workers across CPUs
|
|
1065
|
-
});
|
|
1021
|
+
const worker = cluster.fork();
|
|
1066
1022
|
this.clusterWorkers.set(worker.process.pid!, worker);
|
|
1067
|
-
this.logger.info(
|
|
1068
|
-
`Worker ${worker.process.pid} started (CPU ${i % os.cpus().length})`,
|
|
1069
|
-
'Cluster'
|
|
1070
|
-
);
|
|
1023
|
+
this.logger.info(`Worker ${worker.process.pid} started`, 'Cluster');
|
|
1071
1024
|
|
|
1072
|
-
// Handle individual worker messages
|
|
1025
|
+
// Handle individual worker messages
|
|
1073
1026
|
worker.on('message', this.handleWorkerMessage.bind(this));
|
|
1074
1027
|
}
|
|
1075
1028
|
|
|
1076
|
-
//
|
|
1029
|
+
// Simple worker exit handling
|
|
1077
1030
|
cluster.on('exit', (worker: any, code: number, signal: string) => {
|
|
1078
1031
|
const pid = worker.process.pid;
|
|
1079
|
-
|
|
1080
|
-
// Clean up worker tracking and stats
|
|
1081
1032
|
this.clusterWorkers.delete(pid);
|
|
1082
|
-
this.workerStats.delete(pid);
|
|
1083
1033
|
|
|
1084
1034
|
if (code !== 0 && !worker.exitedAfterDisconnect) {
|
|
1085
1035
|
this.logger.warn(
|
|
1086
|
-
`Worker ${pid} died unexpectedly (${signal || code}).
|
|
1036
|
+
`Worker ${pid} died unexpectedly (${signal || code}). Restarting...`,
|
|
1087
1037
|
'Cluster'
|
|
1088
1038
|
);
|
|
1089
1039
|
|
|
1090
|
-
//
|
|
1091
|
-
|
|
1040
|
+
// Simple restart
|
|
1041
|
+
const newWorker = cluster.fork();
|
|
1042
|
+
this.clusterWorkers.set(newWorker.process.pid!, newWorker);
|
|
1043
|
+
this.logger.info(`Worker ${newWorker.process.pid} restarted`, 'Cluster');
|
|
1092
1044
|
}
|
|
1093
|
-
|
|
1094
|
-
// Restart worker with enhanced tracking
|
|
1095
|
-
const newWorker = this.forkWorkerWithMonitoring();
|
|
1096
|
-
this.logger.info(`Worker ${newWorker.process.pid} started with monitoring`, 'Cluster');
|
|
1097
1045
|
});
|
|
1098
1046
|
|
|
1099
|
-
// Start adaptive scaling system
|
|
1100
|
-
if (this.adaptiveScalingEnabled) {
|
|
1101
|
-
this.startAdaptiveScaling();
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
1047
|
// Master process callback
|
|
1105
1048
|
if (callback) callback();
|
|
1106
1049
|
} else {
|
|
@@ -1114,25 +1057,24 @@ export class Moro extends EventEmitter {
|
|
|
1114
1057
|
// Multiple workers writing to same log files creates I/O contention
|
|
1115
1058
|
if (this.config.logging) {
|
|
1116
1059
|
// Workers log less frequently to reduce I/O contention
|
|
1117
|
-
|
|
1060
|
+
applyLoggingConfiguration(undefined, { level: 'warn' }); // Only warnings and errors
|
|
1118
1061
|
}
|
|
1119
1062
|
|
|
1120
|
-
//
|
|
1121
|
-
// Dynamic heap sizing based on available system memory and worker count
|
|
1063
|
+
// Research-based memory optimization for workers
|
|
1122
1064
|
const os = require('os');
|
|
1123
1065
|
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
1124
1066
|
const workerCount = Object.keys(require('cluster').workers || {}).length || 1;
|
|
1125
1067
|
|
|
1126
|
-
//
|
|
1068
|
+
// Conservative memory allocation
|
|
1127
1069
|
const heapSizePerWorkerMB = Math.min(
|
|
1128
|
-
Math.floor((totalMemoryGB * 1024) /
|
|
1129
|
-
1536 // Cap at 1.5GB
|
|
1070
|
+
Math.floor(((totalMemoryGB * 1024) / workerCount) * 0.8), // 80% of available memory
|
|
1071
|
+
1536 // Cap at 1.5GB (GC efficiency threshold from research)
|
|
1130
1072
|
);
|
|
1131
1073
|
|
|
1132
1074
|
process.env.NODE_OPTIONS = `--max-old-space-size=${heapSizePerWorkerMB}`;
|
|
1133
1075
|
|
|
1134
1076
|
this.logger.debug(
|
|
1135
|
-
`Worker memory
|
|
1077
|
+
`Worker memory allocated: ${heapSizePerWorkerMB}MB heap (${workerCount} workers, ${totalMemoryGB.toFixed(1)}GB total)`,
|
|
1136
1078
|
'Worker'
|
|
1137
1079
|
);
|
|
1138
1080
|
|
|
@@ -1230,20 +1172,8 @@ export class Moro extends EventEmitter {
|
|
|
1230
1172
|
}
|
|
1231
1173
|
}
|
|
1232
1174
|
|
|
1233
|
-
//
|
|
1175
|
+
// Simple worker message handler
|
|
1234
1176
|
private handleWorkerMessage(message: any): void {
|
|
1235
|
-
// Handle performance monitoring messages
|
|
1236
|
-
if (message.type === 'performance') {
|
|
1237
|
-
const pid = message.pid;
|
|
1238
|
-
this.workerStats.set(pid, {
|
|
1239
|
-
cpu: message.cpu || 0,
|
|
1240
|
-
memory: message.memory || 0,
|
|
1241
|
-
requests: message.requests || 0,
|
|
1242
|
-
lastCheck: Date.now(),
|
|
1243
|
-
});
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
1177
|
// Handle inter-worker communication if needed
|
|
1248
1178
|
if (message.type === 'health-check') {
|
|
1249
1179
|
// Worker health check response
|
|
@@ -1254,83 +1184,47 @@ export class Moro extends EventEmitter {
|
|
|
1254
1184
|
this.logger.debug(`Worker message: ${JSON.stringify(message)}`, 'Cluster');
|
|
1255
1185
|
}
|
|
1256
1186
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
WORKER_CPU_AFFINITY: this.clusterWorkers.size % os.cpus().length,
|
|
1264
|
-
});
|
|
1265
|
-
|
|
1266
|
-
this.clusterWorkers.set(worker.process.pid!, worker);
|
|
1267
|
-
worker.on('message', this.handleWorkerMessage.bind(this));
|
|
1268
|
-
|
|
1269
|
-
return worker;
|
|
1270
|
-
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Gracefully close the application and clean up resources
|
|
1189
|
+
* This should be called in tests and during shutdown
|
|
1190
|
+
*/
|
|
1191
|
+
async close(): Promise<void> {
|
|
1192
|
+
this.logger.debug('Closing Moro application...');
|
|
1271
1193
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
let totalMemory = 0;
|
|
1279
|
-
let activeWorkers = 0;
|
|
1280
|
-
|
|
1281
|
-
for (const [pid, stats] of this.workerStats) {
|
|
1282
|
-
if (now - stats.lastCheck < 60000) {
|
|
1283
|
-
// Data less than 1 minute old
|
|
1284
|
-
totalCpu += stats.cpu;
|
|
1285
|
-
totalMemory += stats.memory;
|
|
1286
|
-
activeWorkers++;
|
|
1287
|
-
}
|
|
1194
|
+
// Flush logger buffer before shutdown
|
|
1195
|
+
try {
|
|
1196
|
+
// Use flushBuffer for immediate synchronous flush
|
|
1197
|
+
this.logger.flushBuffer();
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
// Ignore flush errors during shutdown
|
|
1288
1200
|
}
|
|
1289
1201
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
'Cluster'
|
|
1306
|
-
);
|
|
1307
|
-
} else if (avgCpu < 25 && currentWorkerCount > 2) {
|
|
1308
|
-
this.logger.info(
|
|
1309
|
-
'Low CPU utilization detected, excessive workers may be causing IPC overhead',
|
|
1310
|
-
'Cluster'
|
|
1311
|
-
);
|
|
1202
|
+
// Close the core framework with timeout
|
|
1203
|
+
if (this.coreFramework && (this.coreFramework as any).httpServer) {
|
|
1204
|
+
try {
|
|
1205
|
+
await Promise.race([
|
|
1206
|
+
new Promise<void>(resolve => {
|
|
1207
|
+
(this.coreFramework as any).httpServer.close(() => {
|
|
1208
|
+
resolve();
|
|
1209
|
+
});
|
|
1210
|
+
}),
|
|
1211
|
+
new Promise<void>(resolve => setTimeout(resolve, 2000)), // 2 second timeout
|
|
1212
|
+
]);
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
// Force close if graceful close fails
|
|
1215
|
+
this.logger.warn('Force closing HTTP server due to timeout');
|
|
1216
|
+
}
|
|
1312
1217
|
}
|
|
1313
1218
|
|
|
1314
|
-
//
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
this.
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
);
|
|
1219
|
+
// Clean up event listeners
|
|
1220
|
+
try {
|
|
1221
|
+
this.eventBus.removeAllListeners();
|
|
1222
|
+
this.removeAllListeners();
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
// Ignore cleanup errors
|
|
1321
1225
|
}
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
private startAdaptiveScaling(): void {
|
|
1325
|
-
setInterval(() => {
|
|
1326
|
-
const now = Date.now();
|
|
1327
|
-
if (now - this.lastScalingCheck > this.SCALING_INTERVAL) {
|
|
1328
|
-
this.evaluateWorkerPerformance();
|
|
1329
|
-
this.lastScalingCheck = now;
|
|
1330
|
-
}
|
|
1331
|
-
}, this.SCALING_INTERVAL);
|
|
1332
1226
|
|
|
1333
|
-
this.logger.
|
|
1227
|
+
this.logger.debug('Moro application closed successfully');
|
|
1334
1228
|
}
|
|
1335
1229
|
}
|
|
1336
1230
|
|
package/src/types/config.ts
CHANGED
|
@@ -3,9 +3,15 @@
|
|
|
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;
|
|
8
|
+
bodySizeLimit: string;
|
|
9
|
+
requestTracking: {
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
};
|
|
12
|
+
errorBoundary: {
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
};
|
|
9
15
|
}
|
|
10
16
|
|
|
11
17
|
export interface ServiceDiscoveryConfig {
|
|
@@ -19,7 +25,7 @@ export interface ServiceDiscoveryConfig {
|
|
|
19
25
|
|
|
20
26
|
export interface DatabaseConfig {
|
|
21
27
|
url?: string;
|
|
22
|
-
redis
|
|
28
|
+
redis?: {
|
|
23
29
|
url: string;
|
|
24
30
|
maxRetries: number;
|
|
25
31
|
retryDelay: number;
|
|
@@ -35,6 +41,28 @@ export interface DatabaseConfig {
|
|
|
35
41
|
acquireTimeout: number;
|
|
36
42
|
timeout: number;
|
|
37
43
|
};
|
|
44
|
+
postgresql?: {
|
|
45
|
+
host: string;
|
|
46
|
+
port: number;
|
|
47
|
+
database?: string;
|
|
48
|
+
user?: string;
|
|
49
|
+
password?: string;
|
|
50
|
+
connectionLimit: number;
|
|
51
|
+
ssl?: boolean;
|
|
52
|
+
};
|
|
53
|
+
sqlite?: {
|
|
54
|
+
filename: string;
|
|
55
|
+
memory?: boolean;
|
|
56
|
+
verbose?: boolean;
|
|
57
|
+
};
|
|
58
|
+
mongodb?: {
|
|
59
|
+
url?: string;
|
|
60
|
+
host?: string;
|
|
61
|
+
port?: number;
|
|
62
|
+
database?: string;
|
|
63
|
+
username?: string;
|
|
64
|
+
password?: string;
|
|
65
|
+
};
|
|
38
66
|
}
|
|
39
67
|
|
|
40
68
|
export interface ModuleDefaultsConfig {
|
|
@@ -141,6 +169,22 @@ export interface PerformanceConfig {
|
|
|
141
169
|
clustering: {
|
|
142
170
|
enabled: boolean;
|
|
143
171
|
workers: number | 'auto';
|
|
172
|
+
memoryPerWorkerGB?: number;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface WebSocketConfig {
|
|
177
|
+
enabled: boolean;
|
|
178
|
+
adapter?: string | 'socket.io' | 'ws';
|
|
179
|
+
compression?: boolean;
|
|
180
|
+
customIdGenerator?: () => string;
|
|
181
|
+
options?: {
|
|
182
|
+
cors?: {
|
|
183
|
+
origin?: string | string[];
|
|
184
|
+
credentials?: boolean;
|
|
185
|
+
};
|
|
186
|
+
path?: string;
|
|
187
|
+
maxPayloadLength?: number;
|
|
144
188
|
};
|
|
145
189
|
}
|
|
146
190
|
|
|
@@ -154,4 +198,5 @@ export interface AppConfig {
|
|
|
154
198
|
security: SecurityConfig;
|
|
155
199
|
external: ExternalServicesConfig;
|
|
156
200
|
performance: PerformanceConfig;
|
|
201
|
+
websocket: WebSocketConfig;
|
|
157
202
|
}
|