@morojs/moro 1.2.1 → 1.3.0
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/LICENSE +2 -2
- package/dist/core/config/file-loader.js +31 -25
- package/dist/core/config/file-loader.js.map +1 -1
- package/dist/core/config/schema.d.ts +2 -2
- package/dist/core/config/schema.js +1 -1
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/events/event-bus.js +4 -0
- package/dist/core/events/event-bus.js.map +1 -1
- package/dist/core/http/http-server.d.ts +33 -0
- package/dist/core/http/http-server.js +329 -28
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/utilities/container.d.ts +1 -0
- package/dist/core/utilities/container.js +11 -1
- package/dist/core/utilities/container.js.map +1 -1
- package/dist/moro.d.ts +8 -0
- package/dist/moro.js +331 -12
- package/dist/moro.js.map +1 -1
- package/dist/types/core.d.ts +17 -0
- package/package.json +14 -10
- package/src/core/config/file-loader.ts +34 -25
- package/src/core/config/schema.ts +1 -1
- package/src/core/events/event-bus.ts +5 -0
- package/src/core/http/http-server.ts +377 -28
- package/src/core/utilities/container.ts +14 -1
- package/src/moro.ts +394 -13
- package/src/types/core.ts +18 -0
package/src/moro.ts
CHANGED
|
@@ -73,6 +73,28 @@ export class Moro extends EventEmitter {
|
|
|
73
73
|
applyLoggingConfiguration(undefined, options.logger);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Apply performance configuration from createApp options (takes precedence)
|
|
77
|
+
if (options.performance) {
|
|
78
|
+
if (options.performance.clustering) {
|
|
79
|
+
this.config.performance.clustering = {
|
|
80
|
+
...this.config.performance.clustering,
|
|
81
|
+
...options.performance.clustering,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (options.performance.compression) {
|
|
85
|
+
this.config.performance.compression = {
|
|
86
|
+
...this.config.performance.compression,
|
|
87
|
+
...options.performance.compression,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (options.performance.circuitBreaker) {
|
|
91
|
+
this.config.performance.circuitBreaker = {
|
|
92
|
+
...this.config.performance.circuitBreaker,
|
|
93
|
+
...options.performance.circuitBreaker,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
76
98
|
this.logger.info(
|
|
77
99
|
`Configuration system initialized: ${this.config.server.environment}:${this.config.server.port}`
|
|
78
100
|
);
|
|
@@ -83,7 +105,13 @@ export class Moro extends EventEmitter {
|
|
|
83
105
|
|
|
84
106
|
this.logger.info(`Runtime system initialized: ${this.runtimeType}`, 'Runtime');
|
|
85
107
|
|
|
86
|
-
|
|
108
|
+
// Pass logging configuration from config to framework
|
|
109
|
+
const frameworkOptions: any = {
|
|
110
|
+
...options,
|
|
111
|
+
logger: this.config.logging,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
this.coreFramework = new MoroCore(frameworkOptions);
|
|
87
115
|
|
|
88
116
|
// Initialize middleware system
|
|
89
117
|
this.middlewareManager = new MiddlewareManager();
|
|
@@ -94,6 +122,15 @@ export class Moro extends EventEmitter {
|
|
|
94
122
|
httpServer.setHookManager((this.middlewareManager as any).hooks);
|
|
95
123
|
}
|
|
96
124
|
|
|
125
|
+
// Configure HTTP server performance based on config
|
|
126
|
+
if (httpServer && httpServer.configurePerformance) {
|
|
127
|
+
const performanceConfig = this.config.performance;
|
|
128
|
+
httpServer.configurePerformance({
|
|
129
|
+
compression: performanceConfig?.compression || { enabled: true },
|
|
130
|
+
minimal: performanceConfig?.compression?.enabled === false, // Enable minimal mode if compression disabled
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
97
134
|
// Access enterprise event bus from core framework
|
|
98
135
|
this.eventBus = (this.coreFramework as any).eventBus;
|
|
99
136
|
|
|
@@ -383,9 +420,14 @@ export class Moro extends EventEmitter {
|
|
|
383
420
|
}
|
|
384
421
|
|
|
385
422
|
// Start server with events (Node.js only)
|
|
423
|
+
listen(callback?: () => void): void;
|
|
386
424
|
listen(port: number, callback?: () => void): void;
|
|
387
425
|
listen(port: number, host: string, callback?: () => void): void;
|
|
388
|
-
listen(
|
|
426
|
+
listen(
|
|
427
|
+
portOrCallback?: number | (() => void),
|
|
428
|
+
hostOrCallback?: string | (() => void),
|
|
429
|
+
callback?: () => void
|
|
430
|
+
) {
|
|
389
431
|
// Only available for Node.js runtime
|
|
390
432
|
if (this.runtimeType !== 'node') {
|
|
391
433
|
throw new Error(
|
|
@@ -393,10 +435,46 @@ export class Moro extends EventEmitter {
|
|
|
393
435
|
);
|
|
394
436
|
}
|
|
395
437
|
|
|
396
|
-
// Handle overloaded parameters
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
438
|
+
// Handle overloaded parameters - supports:
|
|
439
|
+
// listen(callback)
|
|
440
|
+
// listen(port, callback)
|
|
441
|
+
// listen(port, host, callback)
|
|
442
|
+
let port: number;
|
|
443
|
+
let host: string | undefined;
|
|
444
|
+
|
|
445
|
+
if (typeof portOrCallback === 'function') {
|
|
446
|
+
// listen(callback) - use port from config
|
|
447
|
+
callback = portOrCallback;
|
|
448
|
+
port = this.config.server.port;
|
|
449
|
+
host = this.config.server.host;
|
|
450
|
+
} else if (typeof portOrCallback === 'number') {
|
|
451
|
+
// listen(port, ...) variants
|
|
452
|
+
port = portOrCallback;
|
|
453
|
+
if (typeof hostOrCallback === 'function') {
|
|
454
|
+
// listen(port, callback)
|
|
455
|
+
callback = hostOrCallback;
|
|
456
|
+
host = undefined;
|
|
457
|
+
} else {
|
|
458
|
+
// listen(port, host, callback)
|
|
459
|
+
host = hostOrCallback;
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
// listen() - use config defaults
|
|
463
|
+
port = this.config.server.port;
|
|
464
|
+
host = this.config.server.host;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Validate that we have a valid port
|
|
468
|
+
if (!port || typeof port !== 'number') {
|
|
469
|
+
throw new Error(
|
|
470
|
+
'Port not specified and not found in configuration. Please provide a port number or configure it in moro.config.js/ts'
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Check if clustering is enabled for massive performance gains
|
|
475
|
+
if (this.config.performance?.clustering?.enabled) {
|
|
476
|
+
this.startWithClustering(port, host as string, callback);
|
|
477
|
+
return;
|
|
400
478
|
}
|
|
401
479
|
this.eventBus.emit('server:starting', { port, runtime: this.runtimeType });
|
|
402
480
|
|
|
@@ -493,7 +571,7 @@ export class Moro extends EventEmitter {
|
|
|
493
571
|
const matches = req.path.match(route.pattern);
|
|
494
572
|
if (matches) {
|
|
495
573
|
req.params = {};
|
|
496
|
-
route.paramNames.forEach((name, index) => {
|
|
574
|
+
route.paramNames.forEach((name: string, index: number) => {
|
|
497
575
|
req.params[name] = matches[index + 1];
|
|
498
576
|
});
|
|
499
577
|
}
|
|
@@ -557,11 +635,38 @@ export class Moro extends EventEmitter {
|
|
|
557
635
|
}
|
|
558
636
|
}
|
|
559
637
|
|
|
560
|
-
//
|
|
638
|
+
// Advanced route matching with caching and optimization
|
|
639
|
+
private routeCache = new Map<string, { pattern: RegExp; paramNames: string[] }>();
|
|
640
|
+
private staticRouteMap = new Map<string, any>();
|
|
641
|
+
private dynamicRoutesBySegments = new Map<number, any[]>();
|
|
642
|
+
|
|
561
643
|
private findMatchingRoute(method: string, path: string) {
|
|
562
|
-
|
|
644
|
+
// Phase 1: O(1) static route lookup
|
|
645
|
+
const staticKey = `${method}:${path}`;
|
|
646
|
+
const staticRoute = this.staticRouteMap.get(staticKey);
|
|
647
|
+
if (staticRoute) {
|
|
648
|
+
return {
|
|
649
|
+
...staticRoute,
|
|
650
|
+
pattern: /^.*$/, // Dummy pattern for static routes
|
|
651
|
+
paramNames: [],
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Phase 2: Optimized dynamic route matching by segment count
|
|
656
|
+
const segments = path.split('/').filter(s => s.length > 0);
|
|
657
|
+
const segmentCount = segments.length;
|
|
658
|
+
const candidateRoutes = this.dynamicRoutesBySegments.get(segmentCount) || [];
|
|
659
|
+
|
|
660
|
+
for (const route of candidateRoutes) {
|
|
563
661
|
if (route.method === method) {
|
|
564
|
-
const
|
|
662
|
+
const cacheKey = `${method}:${route.path}`;
|
|
663
|
+
let pattern = this.routeCache.get(cacheKey);
|
|
664
|
+
|
|
665
|
+
if (!pattern) {
|
|
666
|
+
pattern = this.pathToRegex(route.path);
|
|
667
|
+
this.routeCache.set(cacheKey, pattern);
|
|
668
|
+
}
|
|
669
|
+
|
|
565
670
|
if (pattern.pattern.test(path)) {
|
|
566
671
|
return {
|
|
567
672
|
...route,
|
|
@@ -571,6 +676,7 @@ export class Moro extends EventEmitter {
|
|
|
571
676
|
}
|
|
572
677
|
}
|
|
573
678
|
}
|
|
679
|
+
|
|
574
680
|
return null;
|
|
575
681
|
}
|
|
576
682
|
|
|
@@ -602,7 +708,7 @@ export class Moro extends EventEmitter {
|
|
|
602
708
|
private addRoute(method: string, path: string, handler: Function, options: any = {}) {
|
|
603
709
|
const handlerName = `handler_${this.routes.length}`;
|
|
604
710
|
|
|
605
|
-
|
|
711
|
+
const route = {
|
|
606
712
|
method: method as any,
|
|
607
713
|
path,
|
|
608
714
|
handler: handlerName,
|
|
@@ -610,7 +716,12 @@ export class Moro extends EventEmitter {
|
|
|
610
716
|
rateLimit: options.rateLimit,
|
|
611
717
|
cache: options.cache,
|
|
612
718
|
middleware: options.middleware,
|
|
613
|
-
}
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
this.routes.push(route);
|
|
722
|
+
|
|
723
|
+
// Organize routes for optimal lookup
|
|
724
|
+
this.organizeRouteForLookup(route);
|
|
614
725
|
|
|
615
726
|
// Store handler for later module creation
|
|
616
727
|
this.routeHandlers[handlerName] = handler;
|
|
@@ -618,6 +729,23 @@ export class Moro extends EventEmitter {
|
|
|
618
729
|
return this;
|
|
619
730
|
}
|
|
620
731
|
|
|
732
|
+
private organizeRouteForLookup(route: any): void {
|
|
733
|
+
if (!route.path.includes(':')) {
|
|
734
|
+
// Static route - add to static map for O(1) lookup
|
|
735
|
+
const staticKey = `${route.method}:${route.path}`;
|
|
736
|
+
this.staticRouteMap.set(staticKey, route);
|
|
737
|
+
} else {
|
|
738
|
+
// Dynamic route - organize by segment count
|
|
739
|
+
const segments = route.path.split('/').filter((s: string) => s.length > 0);
|
|
740
|
+
const segmentCount = segments.length;
|
|
741
|
+
|
|
742
|
+
if (!this.dynamicRoutesBySegments.has(segmentCount)) {
|
|
743
|
+
this.dynamicRoutesBySegments.set(segmentCount, []);
|
|
744
|
+
}
|
|
745
|
+
this.dynamicRoutesBySegments.get(segmentCount)!.push(route);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
621
749
|
private registerDirectRoutes() {
|
|
622
750
|
// Register routes directly with the HTTP server for optimal performance
|
|
623
751
|
// This provides the intuitive developer experience users expect
|
|
@@ -766,11 +894,264 @@ export class Moro extends EventEmitter {
|
|
|
766
894
|
const module = await import(modulePath);
|
|
767
895
|
return module.default || module;
|
|
768
896
|
}
|
|
897
|
+
|
|
898
|
+
// Clustering support for massive performance gains with proper cleanup
|
|
899
|
+
private clusterWorkers = new Map<number, any>();
|
|
900
|
+
private startWithClustering(port: number, host?: string, callback?: () => void): void {
|
|
901
|
+
const cluster = require('cluster');
|
|
902
|
+
const os = require('os');
|
|
903
|
+
|
|
904
|
+
// Smart worker count calculation based on actual bottlenecks
|
|
905
|
+
let workerCount = this.config.performance?.clustering?.workers || os.cpus().length;
|
|
906
|
+
|
|
907
|
+
// Auto-optimize worker count based on system characteristics
|
|
908
|
+
if (workerCount === 'auto' || workerCount > 8) {
|
|
909
|
+
// For high-core machines, limit workers to prevent IPC/memory bottlenecks
|
|
910
|
+
const cpuCount = os.cpus().length;
|
|
911
|
+
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
912
|
+
|
|
913
|
+
// Optimal worker count formula based on research
|
|
914
|
+
if (cpuCount >= 16) {
|
|
915
|
+
// High-core machines: focus on memory/IPC efficiency
|
|
916
|
+
workerCount = Math.min(Math.ceil(totalMemoryGB / 2), 4); // 2GB per worker max, cap at 4
|
|
917
|
+
} else if (cpuCount >= 8) {
|
|
918
|
+
// Mid-range machines: balanced approach
|
|
919
|
+
workerCount = Math.min(cpuCount / 2, 4);
|
|
920
|
+
} else {
|
|
921
|
+
// Low-core machines: use all cores
|
|
922
|
+
workerCount = cpuCount;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
this.logger.info(
|
|
926
|
+
`Auto-optimized workers: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB)`,
|
|
927
|
+
'Cluster'
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (cluster.isPrimary) {
|
|
932
|
+
this.logger.info(`🚀 Starting ${workerCount} workers for maximum performance`, 'Cluster');
|
|
933
|
+
|
|
934
|
+
// Optimize cluster scheduling for high concurrency
|
|
935
|
+
cluster.schedulingPolicy = cluster.SCHED_RR; // Round-robin scheduling
|
|
936
|
+
|
|
937
|
+
// Set cluster settings for better performance
|
|
938
|
+
cluster.setupMaster({
|
|
939
|
+
exec: process.argv[1],
|
|
940
|
+
args: process.argv.slice(2),
|
|
941
|
+
silent: false,
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
// Optimize IPC to reduce communication overhead
|
|
945
|
+
process.env.NODE_CLUSTER_SCHED_POLICY = 'rr'; // Ensure round-robin
|
|
946
|
+
process.env.NODE_DISABLE_COLORS = '1'; // Reduce IPC message size
|
|
947
|
+
|
|
948
|
+
// Graceful shutdown handler
|
|
949
|
+
const gracefulShutdown = () => {
|
|
950
|
+
this.logger.info('Gracefully shutting down cluster...', 'Cluster');
|
|
951
|
+
|
|
952
|
+
// Clean up all workers
|
|
953
|
+
for (const [pid, worker] of this.clusterWorkers) {
|
|
954
|
+
worker.removeAllListeners();
|
|
955
|
+
worker.kill('SIGTERM');
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// Clean up cluster listeners
|
|
959
|
+
cluster.removeAllListeners();
|
|
960
|
+
process.exit(0);
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
// Handle process signals for graceful shutdown
|
|
964
|
+
process.on('SIGINT', gracefulShutdown);
|
|
965
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
966
|
+
|
|
967
|
+
// Fork workers with proper tracking and CPU affinity
|
|
968
|
+
for (let i = 0; i < workerCount; i++) {
|
|
969
|
+
const worker = cluster.fork({
|
|
970
|
+
WORKER_ID: i,
|
|
971
|
+
WORKER_CPU_AFFINITY: i % os.cpus().length, // Distribute workers across CPUs
|
|
972
|
+
});
|
|
973
|
+
this.clusterWorkers.set(worker.process.pid!, worker);
|
|
974
|
+
this.logger.info(
|
|
975
|
+
`Worker ${worker.process.pid} started (CPU ${i % os.cpus().length})`,
|
|
976
|
+
'Cluster'
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
// Handle individual worker messages (reuse handler)
|
|
980
|
+
worker.on('message', this.handleWorkerMessage.bind(this));
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Handle worker exits with cleanup
|
|
984
|
+
cluster.on('exit', (worker: any, code: number, signal: string) => {
|
|
985
|
+
// Clean up worker tracking
|
|
986
|
+
this.clusterWorkers.delete(worker.process.pid);
|
|
987
|
+
|
|
988
|
+
this.logger.warn(
|
|
989
|
+
`Worker ${worker.process.pid} died (${signal || code}). Restarting...`,
|
|
990
|
+
'Cluster'
|
|
991
|
+
);
|
|
992
|
+
|
|
993
|
+
// Restart worker with proper tracking
|
|
994
|
+
const newWorker = cluster.fork();
|
|
995
|
+
this.clusterWorkers.set(newWorker.process.pid!, newWorker);
|
|
996
|
+
newWorker.on('message', this.handleWorkerMessage.bind(this));
|
|
997
|
+
this.logger.info(`Worker ${newWorker.process.pid} started`, 'Cluster');
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// Master process callback
|
|
1001
|
+
if (callback) callback();
|
|
1002
|
+
} else {
|
|
1003
|
+
// Worker process - start the actual server with proper cleanup
|
|
1004
|
+
this.logger.info(`Worker ${process.pid} initializing`, 'Worker');
|
|
1005
|
+
|
|
1006
|
+
// Worker-specific optimizations for high concurrency
|
|
1007
|
+
process.env.UV_THREADPOOL_SIZE = '64';
|
|
1008
|
+
|
|
1009
|
+
// Reduce logging contention in workers (major bottleneck)
|
|
1010
|
+
if (this.config.logging) {
|
|
1011
|
+
// Workers log less frequently to reduce I/O contention
|
|
1012
|
+
this.config.logging.level = 'warn'; // Only warnings and errors
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Memory optimization for workers
|
|
1016
|
+
process.env.NODE_OPTIONS = '--max-old-space-size=1024'; // Limit memory per worker
|
|
1017
|
+
|
|
1018
|
+
// Optimize V8 flags for better performance (Rust-level optimizations)
|
|
1019
|
+
if (process.env.NODE_ENV === 'production') {
|
|
1020
|
+
// Ultra-aggressive V8 optimizations for maximum performance
|
|
1021
|
+
const v8Flags = [
|
|
1022
|
+
'--optimize-for-size', // Trade memory for speed
|
|
1023
|
+
'--always-opt', // Always optimize functions
|
|
1024
|
+
'--turbo-fast-api-calls', // Optimize API calls
|
|
1025
|
+
'--turbo-escape-analysis', // Escape analysis optimization
|
|
1026
|
+
'--turbo-inline-api-calls', // Inline API calls
|
|
1027
|
+
'--max-old-space-size=1024', // Limit memory to prevent GC pressure
|
|
1028
|
+
];
|
|
1029
|
+
process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ' ' + v8Flags.join(' ');
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// Optimize garbage collection for workers
|
|
1033
|
+
// eslint-disable-next-line no-undef
|
|
1034
|
+
if ((global as any).gc) {
|
|
1035
|
+
setInterval(() => {
|
|
1036
|
+
// eslint-disable-next-line no-undef
|
|
1037
|
+
if ((global as any).gc) (global as any).gc();
|
|
1038
|
+
}, 60000); // GC every 60 seconds (less frequent)
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// Graceful shutdown for worker
|
|
1042
|
+
const workerShutdown = () => {
|
|
1043
|
+
this.logger.info(`Worker ${process.pid} shutting down gracefully...`, 'Worker');
|
|
1044
|
+
|
|
1045
|
+
// Clean up event listeners
|
|
1046
|
+
this.eventBus.removeAllListeners();
|
|
1047
|
+
this.removeAllListeners();
|
|
1048
|
+
|
|
1049
|
+
// Close server gracefully
|
|
1050
|
+
if (this.coreFramework) {
|
|
1051
|
+
const server = (this.coreFramework as any).server;
|
|
1052
|
+
if (server) {
|
|
1053
|
+
server.close(() => {
|
|
1054
|
+
process.exit(0);
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
// Handle worker shutdown signals
|
|
1061
|
+
process.on('SIGTERM', workerShutdown);
|
|
1062
|
+
process.on('SIGINT', workerShutdown);
|
|
1063
|
+
|
|
1064
|
+
// Continue with normal server startup for this worker
|
|
1065
|
+
this.eventBus.emit('server:starting', {
|
|
1066
|
+
port,
|
|
1067
|
+
runtime: this.runtimeType,
|
|
1068
|
+
worker: process.pid,
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
// Add documentation middleware first (if enabled)
|
|
1072
|
+
try {
|
|
1073
|
+
const docsMiddleware = this.documentation.getDocsMiddleware();
|
|
1074
|
+
this.coreFramework.addMiddleware(docsMiddleware);
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
// Documentation not enabled, that's fine
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// Add intelligent routing middleware
|
|
1080
|
+
this.coreFramework.addMiddleware(
|
|
1081
|
+
async (req: HttpRequest, res: HttpResponse, next: () => void) => {
|
|
1082
|
+
const handled = await this.intelligentRouting.handleIntelligentRoute(req, res);
|
|
1083
|
+
if (!handled) {
|
|
1084
|
+
next();
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
);
|
|
1088
|
+
|
|
1089
|
+
// Register direct routes
|
|
1090
|
+
if (this.routes.length > 0) {
|
|
1091
|
+
this.registerDirectRoutes();
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const workerCallback = () => {
|
|
1095
|
+
const displayHost = host || 'localhost';
|
|
1096
|
+
this.logger.info(`Worker ${process.pid} ready on ${displayHost}:${port}`, 'Worker');
|
|
1097
|
+
this.eventBus.emit('server:started', {
|
|
1098
|
+
port,
|
|
1099
|
+
runtime: this.runtimeType,
|
|
1100
|
+
worker: process.pid,
|
|
1101
|
+
});
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
if (host) {
|
|
1105
|
+
this.coreFramework.listen(port, host, workerCallback);
|
|
1106
|
+
} else {
|
|
1107
|
+
this.coreFramework.listen(port, workerCallback);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// Reusable worker message handler (avoids creating new functions)
|
|
1113
|
+
private handleWorkerMessage(message: any): void {
|
|
1114
|
+
// Handle inter-worker communication if needed
|
|
1115
|
+
if (message.type === 'health-check') {
|
|
1116
|
+
// Worker health check response
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// Log other worker messages
|
|
1121
|
+
this.logger.debug(`Worker message: ${JSON.stringify(message)}`, 'Cluster');
|
|
1122
|
+
}
|
|
769
1123
|
}
|
|
770
1124
|
|
|
771
1125
|
// Export convenience function
|
|
772
1126
|
export function createApp(options?: MoroOptions): Moro {
|
|
773
|
-
|
|
1127
|
+
// Load global config from moro.config.js and merge with passed options
|
|
1128
|
+
const globalConfig = initializeConfig();
|
|
1129
|
+
|
|
1130
|
+
// Convert global config to MoroOptions format for createApp
|
|
1131
|
+
const configAsOptions: Partial<MoroOptions> = {
|
|
1132
|
+
performance: globalConfig.performance,
|
|
1133
|
+
logger: globalConfig.logging
|
|
1134
|
+
? {
|
|
1135
|
+
level: globalConfig.logging.level,
|
|
1136
|
+
enableColors: globalConfig.logging.enableColors,
|
|
1137
|
+
enableTimestamp: globalConfig.logging.enableTimestamp,
|
|
1138
|
+
}
|
|
1139
|
+
: undefined,
|
|
1140
|
+
// Add other relevant config mappings as needed
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
// Merge config file options with passed options (passed options take precedence)
|
|
1144
|
+
const mergedOptions = {
|
|
1145
|
+
...configAsOptions,
|
|
1146
|
+
...options,
|
|
1147
|
+
// Deep merge performance settings
|
|
1148
|
+
performance: {
|
|
1149
|
+
...configAsOptions.performance,
|
|
1150
|
+
...options?.performance,
|
|
1151
|
+
},
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
return new Moro(mergedOptions);
|
|
774
1155
|
}
|
|
775
1156
|
|
|
776
1157
|
// Runtime-specific convenience functions
|
package/src/types/core.ts
CHANGED
|
@@ -14,4 +14,22 @@ export interface MoroOptions {
|
|
|
14
14
|
runtime?: RuntimeConfig;
|
|
15
15
|
// Logger configuration
|
|
16
16
|
logger?: LoggerOptions | boolean;
|
|
17
|
+
// Performance configuration
|
|
18
|
+
performance?: {
|
|
19
|
+
clustering?: {
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
workers?: number | 'auto';
|
|
22
|
+
};
|
|
23
|
+
compression?: {
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
level?: number;
|
|
26
|
+
threshold?: number;
|
|
27
|
+
};
|
|
28
|
+
circuitBreaker?: {
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
failureThreshold?: number;
|
|
31
|
+
resetTimeout?: number;
|
|
32
|
+
monitoringPeriod?: number;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
17
35
|
}
|