@morojs/moro 1.5.5 → 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 +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 +97 -192
- 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 +43 -4
- package/src/core/routing/index.ts +116 -12
- package/src/moro.ts +105 -225
- 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
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
|
|
@@ -936,14 +905,8 @@ export class Moro extends EventEmitter {
|
|
|
936
905
|
}
|
|
937
906
|
|
|
938
907
|
/**
|
|
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
|
|
908
|
+
* Node.js Clustering Implementation
|
|
909
|
+
* This clustering algorithm is based on published research and Node.js best practices.
|
|
947
910
|
*
|
|
948
911
|
* IPC (Inter-Process Communication) Considerations:
|
|
949
912
|
* - Excessive workers create IPC bottlenecks (Source: BetterStack Node.js Guide)
|
|
@@ -954,71 +917,53 @@ export class Moro extends EventEmitter {
|
|
|
954
917
|
* - ~2GB per worker prevents memory pressure and GC overhead
|
|
955
918
|
* - Conservative heap limits reduce memory fragmentation
|
|
956
919
|
*
|
|
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
920
|
* References:
|
|
963
921
|
* - Node.js Cluster Documentation: https://nodejs.org/api/cluster.html
|
|
964
922
|
* - BetterStack Node.js Clustering: https://betterstack.com/community/guides/scaling-nodejs/node-clustering/
|
|
965
923
|
*/
|
|
966
924
|
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
925
|
|
|
975
926
|
private startWithClustering(port: number, host?: string, callback?: () => void): void {
|
|
976
927
|
const cluster = require('cluster');
|
|
977
928
|
const os = require('os');
|
|
978
929
|
|
|
979
|
-
//
|
|
980
|
-
// Based on empirical testing and Node.js clustering best practices
|
|
930
|
+
// Worker count calculation - respect user choice
|
|
981
931
|
let workerCount = this.config.performance?.clustering?.workers || os.cpus().length;
|
|
982
932
|
|
|
983
|
-
//
|
|
984
|
-
|
|
985
|
-
// cause IPC overhead that degrades performance (Source: Medium - Clustering in Node.js)
|
|
986
|
-
if (workerCount === 'auto' || workerCount > 8) {
|
|
933
|
+
// Only auto-optimize if user hasn't specified a number or set it to 'auto'
|
|
934
|
+
if (workerCount === 'auto') {
|
|
987
935
|
const cpuCount = os.cpus().length;
|
|
988
936
|
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
989
937
|
|
|
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);
|
|
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));
|
|
1008
945
|
}
|
|
1009
946
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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
|
|
1013
954
|
);
|
|
1014
|
-
|
|
1015
|
-
|
|
955
|
+
|
|
956
|
+
this.logger.info(
|
|
957
|
+
`Auto-calculated worker count: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB, ${memoryPerWorkerGB}GB per worker)`,
|
|
1016
958
|
'Cluster'
|
|
1017
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');
|
|
1018
963
|
}
|
|
1019
964
|
|
|
1020
965
|
if (cluster.isPrimary) {
|
|
1021
|
-
this.logger.info(
|
|
966
|
+
this.logger.info(`Starting ${workerCount} workers`, 'Cluster');
|
|
1022
967
|
|
|
1023
968
|
// Optimize cluster scheduling for high concurrency
|
|
1024
969
|
// Round-robin is the default on all platforms except Windows (Node.js docs)
|
|
@@ -1027,7 +972,7 @@ export class Moro extends EventEmitter {
|
|
|
1027
972
|
|
|
1028
973
|
// Set cluster settings for better performance
|
|
1029
974
|
cluster.setupMaster({
|
|
1030
|
-
exec: process.argv[1],
|
|
975
|
+
exec: process.argv[1] || process.execPath,
|
|
1031
976
|
args: process.argv.slice(2),
|
|
1032
977
|
silent: false,
|
|
1033
978
|
});
|
|
@@ -1057,50 +1002,34 @@ export class Moro extends EventEmitter {
|
|
|
1057
1002
|
process.on('SIGINT', gracefulShutdown);
|
|
1058
1003
|
process.on('SIGTERM', gracefulShutdown);
|
|
1059
1004
|
|
|
1060
|
-
// Fork workers with
|
|
1005
|
+
// Fork workers with basic tracking
|
|
1061
1006
|
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
|
-
});
|
|
1007
|
+
const worker = cluster.fork();
|
|
1066
1008
|
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
|
-
);
|
|
1009
|
+
this.logger.info(`Worker ${worker.process.pid} started`, 'Cluster');
|
|
1071
1010
|
|
|
1072
|
-
// Handle individual worker messages
|
|
1011
|
+
// Handle individual worker messages
|
|
1073
1012
|
worker.on('message', this.handleWorkerMessage.bind(this));
|
|
1074
1013
|
}
|
|
1075
1014
|
|
|
1076
|
-
//
|
|
1015
|
+
// Simple worker exit handling
|
|
1077
1016
|
cluster.on('exit', (worker: any, code: number, signal: string) => {
|
|
1078
1017
|
const pid = worker.process.pid;
|
|
1079
|
-
|
|
1080
|
-
// Clean up worker tracking and stats
|
|
1081
1018
|
this.clusterWorkers.delete(pid);
|
|
1082
|
-
this.workerStats.delete(pid);
|
|
1083
1019
|
|
|
1084
1020
|
if (code !== 0 && !worker.exitedAfterDisconnect) {
|
|
1085
1021
|
this.logger.warn(
|
|
1086
|
-
`Worker ${pid} died unexpectedly (${signal || code}).
|
|
1022
|
+
`Worker ${pid} died unexpectedly (${signal || code}). Restarting...`,
|
|
1087
1023
|
'Cluster'
|
|
1088
1024
|
);
|
|
1089
1025
|
|
|
1090
|
-
//
|
|
1091
|
-
|
|
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');
|
|
1092
1030
|
}
|
|
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
1031
|
});
|
|
1098
1032
|
|
|
1099
|
-
// Start adaptive scaling system
|
|
1100
|
-
if (this.adaptiveScalingEnabled) {
|
|
1101
|
-
this.startAdaptiveScaling();
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
1033
|
// Master process callback
|
|
1105
1034
|
if (callback) callback();
|
|
1106
1035
|
} else {
|
|
@@ -1114,25 +1043,24 @@ export class Moro extends EventEmitter {
|
|
|
1114
1043
|
// Multiple workers writing to same log files creates I/O contention
|
|
1115
1044
|
if (this.config.logging) {
|
|
1116
1045
|
// Workers log less frequently to reduce I/O contention
|
|
1117
|
-
|
|
1046
|
+
applyLoggingConfiguration(undefined, { level: 'warn' }); // Only warnings and errors
|
|
1118
1047
|
}
|
|
1119
1048
|
|
|
1120
|
-
//
|
|
1121
|
-
// Dynamic heap sizing based on available system memory and worker count
|
|
1049
|
+
// Research-based memory optimization for workers
|
|
1122
1050
|
const os = require('os');
|
|
1123
1051
|
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
1124
1052
|
const workerCount = Object.keys(require('cluster').workers || {}).length || 1;
|
|
1125
1053
|
|
|
1126
|
-
//
|
|
1054
|
+
// Conservative memory allocation
|
|
1127
1055
|
const heapSizePerWorkerMB = Math.min(
|
|
1128
|
-
Math.floor((totalMemoryGB * 1024) /
|
|
1129
|
-
1536 // Cap at 1.5GB
|
|
1056
|
+
Math.floor(((totalMemoryGB * 1024) / workerCount) * 0.8), // 80% of available memory
|
|
1057
|
+
1536 // Cap at 1.5GB (GC efficiency threshold from research)
|
|
1130
1058
|
);
|
|
1131
1059
|
|
|
1132
1060
|
process.env.NODE_OPTIONS = `--max-old-space-size=${heapSizePerWorkerMB}`;
|
|
1133
1061
|
|
|
1134
1062
|
this.logger.debug(
|
|
1135
|
-
`Worker memory
|
|
1063
|
+
`Worker memory allocated: ${heapSizePerWorkerMB}MB heap (${workerCount} workers, ${totalMemoryGB.toFixed(1)}GB total)`,
|
|
1136
1064
|
'Worker'
|
|
1137
1065
|
);
|
|
1138
1066
|
|
|
@@ -1230,20 +1158,8 @@ export class Moro extends EventEmitter {
|
|
|
1230
1158
|
}
|
|
1231
1159
|
}
|
|
1232
1160
|
|
|
1233
|
-
//
|
|
1161
|
+
// Simple worker message handler
|
|
1234
1162
|
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
1163
|
// Handle inter-worker communication if needed
|
|
1248
1164
|
if (message.type === 'health-check') {
|
|
1249
1165
|
// Worker health check response
|
|
@@ -1254,83 +1170,47 @@ export class Moro extends EventEmitter {
|
|
|
1254
1170
|
this.logger.debug(`Worker message: ${JSON.stringify(message)}`, 'Cluster');
|
|
1255
1171
|
}
|
|
1256
1172
|
|
|
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
|
-
}
|
|
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...');
|
|
1271
1179
|
|
|
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
|
-
}
|
|
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
|
|
1288
1186
|
}
|
|
1289
1187
|
|
|
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
|
-
);
|
|
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
|
+
}
|
|
1312
1203
|
}
|
|
1313
1204
|
|
|
1314
|
-
//
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
this.
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
);
|
|
1205
|
+
// Clean up event listeners
|
|
1206
|
+
try {
|
|
1207
|
+
this.eventBus.removeAllListeners();
|
|
1208
|
+
this.removeAllListeners();
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
// Ignore cleanup errors
|
|
1321
1211
|
}
|
|
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
1212
|
|
|
1333
|
-
this.logger.
|
|
1213
|
+
this.logger.debug('Moro application closed successfully');
|
|
1334
1214
|
}
|
|
1335
1215
|
}
|
|
1336
1216
|
|
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
|
}
|
package/src/types/core.ts
CHANGED
|
@@ -1,56 +1,45 @@
|
|
|
1
1
|
// Core Framework Types
|
|
2
2
|
import { RuntimeConfig } from './runtime';
|
|
3
3
|
import { LogLevel, LoggerOptions } from './logger';
|
|
4
|
+
import { AppConfig } from './config';
|
|
4
5
|
|
|
5
6
|
export interface MoroOptions {
|
|
6
7
|
autoDiscover?: boolean;
|
|
7
8
|
modulesPath?: string;
|
|
8
9
|
middleware?: any[];
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
// Runtime configuration
|
|
12
|
+
runtime?: RuntimeConfig;
|
|
13
|
+
|
|
14
|
+
// HTTP/WebSocket options
|
|
15
|
+
http2?: boolean;
|
|
16
|
+
https?: {
|
|
17
|
+
key: string | Buffer;
|
|
18
|
+
cert: string | Buffer;
|
|
19
|
+
ca?: string | Buffer;
|
|
20
|
+
};
|
|
21
|
+
websocket?:
|
|
22
|
+
| {
|
|
23
|
+
enabled?: boolean;
|
|
24
|
+
adapter?: any;
|
|
25
|
+
compression?: boolean;
|
|
26
|
+
customIdGenerator?: () => string;
|
|
27
|
+
options?: any;
|
|
28
|
+
}
|
|
29
|
+
| false;
|
|
30
|
+
|
|
31
|
+
// Simplified config options (these map to the full config)
|
|
10
32
|
cors?: boolean | object;
|
|
11
33
|
compression?: boolean | object;
|
|
12
34
|
helmet?: boolean | object;
|
|
13
|
-
// Runtime configuration
|
|
14
|
-
runtime?: RuntimeConfig;
|
|
15
|
-
// Logger configuration
|
|
16
35
|
logger?: LoggerOptions | boolean;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
enabled?: boolean;
|
|
27
|
-
defaultRequests?: number;
|
|
28
|
-
defaultWindow?: number;
|
|
29
|
-
skipSuccessfulRequests?: boolean;
|
|
30
|
-
skipFailedRequests?: boolean;
|
|
31
|
-
};
|
|
32
|
-
validation?: {
|
|
33
|
-
enabled?: boolean;
|
|
34
|
-
stripUnknown?: boolean;
|
|
35
|
-
abortEarly?: boolean;
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
// Performance configuration
|
|
39
|
-
performance?: {
|
|
40
|
-
clustering?: {
|
|
41
|
-
enabled?: boolean;
|
|
42
|
-
workers?: number | 'auto';
|
|
43
|
-
};
|
|
44
|
-
compression?: {
|
|
45
|
-
enabled?: boolean;
|
|
46
|
-
level?: number;
|
|
47
|
-
threshold?: number;
|
|
48
|
-
};
|
|
49
|
-
circuitBreaker?: {
|
|
50
|
-
enabled?: boolean;
|
|
51
|
-
failureThreshold?: number;
|
|
52
|
-
resetTimeout?: number;
|
|
53
|
-
monitoringPeriod?: number;
|
|
54
|
-
};
|
|
55
|
-
};
|
|
36
|
+
|
|
37
|
+
// Direct config overrides (partial)
|
|
38
|
+
server?: Partial<AppConfig['server']>;
|
|
39
|
+
database?: Partial<AppConfig['database']>;
|
|
40
|
+
modules?: Partial<AppConfig['modules']>;
|
|
41
|
+
logging?: Partial<AppConfig['logging']>;
|
|
42
|
+
security?: Partial<AppConfig['security']>;
|
|
43
|
+
external?: Partial<AppConfig['external']>;
|
|
44
|
+
performance?: Partial<AppConfig['performance']>;
|
|
56
45
|
}
|
package/src/types/logger.ts
CHANGED
|
@@ -61,11 +61,17 @@ export interface Logger {
|
|
|
61
61
|
|
|
62
62
|
// Configuration
|
|
63
63
|
setLevel(level: LogLevel): void;
|
|
64
|
+
getLevel(): LogLevel;
|
|
64
65
|
addOutput(output: LogOutput): void;
|
|
65
66
|
removeOutput(name: string): void;
|
|
66
67
|
addFilter(filter: LogFilter): void;
|
|
67
68
|
removeFilter(name: string): void;
|
|
68
69
|
|
|
70
|
+
// Cleanup
|
|
71
|
+
flush(): void;
|
|
72
|
+
flushBuffer(): void;
|
|
73
|
+
destroy(): void;
|
|
74
|
+
|
|
69
75
|
// Metrics and history
|
|
70
76
|
getHistory(count?: number): LogEntry[];
|
|
71
77
|
getMetrics(): LogMetrics;
|