@morojs/moro 1.2.1 → 1.4.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/README.md +61 -7
- 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/config/types.d.ts +147 -0
- package/dist/core/config/types.js +124 -0
- package/dist/core/config/types.js.map +1 -0
- package/dist/core/config/typescript-loader.d.ts +6 -0
- package/dist/core/config/typescript-loader.js +268 -0
- package/dist/core/config/typescript-loader.js.map +1 -0
- package/dist/core/config/validation.d.ts +18 -0
- package/dist/core/config/validation.js +134 -0
- package/dist/core/config/validation.js.map +1 -0
- package/dist/core/docs/openapi-generator.js +6 -6
- package/dist/core/docs/openapi-generator.js.map +1 -1
- package/dist/core/docs/schema-to-openapi.d.ts +7 -0
- package/dist/core/docs/schema-to-openapi.js +124 -0
- package/dist/core/docs/schema-to-openapi.js.map +1 -0
- package/dist/core/docs/zod-to-openapi.d.ts +2 -0
- package/dist/core/docs/zod-to-openapi.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/framework.d.ts +29 -6
- package/dist/core/framework.js +117 -18
- package/dist/core/framework.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/networking/adapters/index.d.ts +3 -0
- package/dist/core/networking/adapters/index.js +10 -0
- package/dist/core/networking/adapters/index.js.map +1 -0
- package/dist/core/networking/adapters/socketio-adapter.d.ts +16 -0
- package/dist/core/networking/adapters/socketio-adapter.js +244 -0
- package/dist/core/networking/adapters/socketio-adapter.js.map +1 -0
- package/dist/core/networking/adapters/ws-adapter.d.ts +54 -0
- package/dist/core/networking/adapters/ws-adapter.js +383 -0
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -0
- package/dist/core/networking/websocket-adapter.d.ts +171 -0
- package/dist/core/networking/websocket-adapter.js +5 -0
- package/dist/core/networking/websocket-adapter.js.map +1 -0
- package/dist/core/networking/websocket-manager.d.ts +53 -17
- package/dist/core/networking/websocket-manager.js +166 -108
- package/dist/core/networking/websocket-manager.js.map +1 -1
- package/dist/core/routing/index.d.ts +13 -13
- package/dist/core/routing/index.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/core/validation/adapters.d.ts +51 -0
- package/dist/core/validation/adapters.js +135 -0
- package/dist/core/validation/adapters.js.map +1 -0
- package/dist/core/validation/index.d.ts +14 -11
- package/dist/core/validation/index.js +37 -26
- package/dist/core/validation/index.js.map +1 -1
- package/dist/core/validation/schema-interface.d.ts +36 -0
- package/dist/core/validation/schema-interface.js +68 -0
- package/dist/core/validation/schema-interface.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/moro.d.ts +8 -0
- package/dist/moro.js +339 -14
- package/dist/moro.js.map +1 -1
- package/dist/types/core.d.ts +17 -0
- package/package.json +42 -14
- package/src/core/config/file-loader.ts +34 -25
- package/src/core/config/schema.ts +1 -1
- package/src/core/config/types.ts +277 -0
- package/src/core/config/typescript-loader.ts +571 -0
- package/src/core/config/validation.ts +145 -0
- package/src/core/docs/openapi-generator.ts +7 -6
- package/src/core/docs/schema-to-openapi.ts +148 -0
- package/src/core/docs/zod-to-openapi.ts +2 -0
- package/src/core/events/event-bus.ts +5 -0
- package/src/core/framework.ts +121 -28
- package/src/core/http/http-server.ts +377 -28
- package/src/core/networking/adapters/index.ts +16 -0
- package/src/core/networking/adapters/socketio-adapter.ts +252 -0
- package/src/core/networking/adapters/ws-adapter.ts +425 -0
- package/src/core/networking/websocket-adapter.ts +217 -0
- package/src/core/networking/websocket-manager.ts +185 -127
- package/src/core/routing/index.ts +13 -13
- package/src/core/utilities/container.ts +14 -1
- package/src/core/validation/adapters.ts +147 -0
- package/src/core/validation/index.ts +60 -38
- package/src/core/validation/schema-interface.ts +100 -0
- package/src/index.ts +25 -2
- package/src/moro.ts +405 -15
- 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
|
|
|
@@ -350,10 +387,19 @@ export class Moro extends EventEmitter {
|
|
|
350
387
|
|
|
351
388
|
// WebSocket helper with events
|
|
352
389
|
websocket(namespace: string, handlers: Record<string, Function>) {
|
|
390
|
+
const adapter = this.coreFramework.getWebSocketAdapter();
|
|
391
|
+
if (!adapter) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
'WebSocket features require a WebSocket adapter. Install socket.io or configure an adapter:\n' +
|
|
394
|
+
'npm install socket.io\n' +
|
|
395
|
+
'or\n' +
|
|
396
|
+
'new Moro({ websocket: { adapter: new SocketIOAdapter() } })'
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
353
400
|
this.emit('websocket:registering', { namespace, handlers });
|
|
354
401
|
|
|
355
|
-
const
|
|
356
|
-
const ns = io.of(namespace);
|
|
402
|
+
const ns = adapter.createNamespace(namespace);
|
|
357
403
|
|
|
358
404
|
Object.entries(handlers).forEach(([event, handler]) => {
|
|
359
405
|
ns.on('connection', socket => {
|
|
@@ -383,9 +429,14 @@ export class Moro extends EventEmitter {
|
|
|
383
429
|
}
|
|
384
430
|
|
|
385
431
|
// Start server with events (Node.js only)
|
|
432
|
+
listen(callback?: () => void): void;
|
|
386
433
|
listen(port: number, callback?: () => void): void;
|
|
387
434
|
listen(port: number, host: string, callback?: () => void): void;
|
|
388
|
-
listen(
|
|
435
|
+
listen(
|
|
436
|
+
portOrCallback?: number | (() => void),
|
|
437
|
+
hostOrCallback?: string | (() => void),
|
|
438
|
+
callback?: () => void
|
|
439
|
+
) {
|
|
389
440
|
// Only available for Node.js runtime
|
|
390
441
|
if (this.runtimeType !== 'node') {
|
|
391
442
|
throw new Error(
|
|
@@ -393,10 +444,46 @@ export class Moro extends EventEmitter {
|
|
|
393
444
|
);
|
|
394
445
|
}
|
|
395
446
|
|
|
396
|
-
// Handle overloaded parameters
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
447
|
+
// Handle overloaded parameters - supports:
|
|
448
|
+
// listen(callback)
|
|
449
|
+
// listen(port, callback)
|
|
450
|
+
// listen(port, host, callback)
|
|
451
|
+
let port: number;
|
|
452
|
+
let host: string | undefined;
|
|
453
|
+
|
|
454
|
+
if (typeof portOrCallback === 'function') {
|
|
455
|
+
// listen(callback) - use port from config
|
|
456
|
+
callback = portOrCallback;
|
|
457
|
+
port = this.config.server.port;
|
|
458
|
+
host = this.config.server.host;
|
|
459
|
+
} else if (typeof portOrCallback === 'number') {
|
|
460
|
+
// listen(port, ...) variants
|
|
461
|
+
port = portOrCallback;
|
|
462
|
+
if (typeof hostOrCallback === 'function') {
|
|
463
|
+
// listen(port, callback)
|
|
464
|
+
callback = hostOrCallback;
|
|
465
|
+
host = undefined;
|
|
466
|
+
} else {
|
|
467
|
+
// listen(port, host, callback)
|
|
468
|
+
host = hostOrCallback;
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
// listen() - use config defaults
|
|
472
|
+
port = this.config.server.port;
|
|
473
|
+
host = this.config.server.host;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Validate that we have a valid port
|
|
477
|
+
if (!port || typeof port !== 'number') {
|
|
478
|
+
throw new Error(
|
|
479
|
+
'Port not specified and not found in configuration. Please provide a port number or configure it in moro.config.js/ts'
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Check if clustering is enabled for massive performance gains
|
|
484
|
+
if (this.config.performance?.clustering?.enabled) {
|
|
485
|
+
this.startWithClustering(port, host as string, callback);
|
|
486
|
+
return;
|
|
400
487
|
}
|
|
401
488
|
this.eventBus.emit('server:starting', { port, runtime: this.runtimeType });
|
|
402
489
|
|
|
@@ -493,7 +580,7 @@ export class Moro extends EventEmitter {
|
|
|
493
580
|
const matches = req.path.match(route.pattern);
|
|
494
581
|
if (matches) {
|
|
495
582
|
req.params = {};
|
|
496
|
-
route.paramNames.forEach((name, index) => {
|
|
583
|
+
route.paramNames.forEach((name: string, index: number) => {
|
|
497
584
|
req.params[name] = matches[index + 1];
|
|
498
585
|
});
|
|
499
586
|
}
|
|
@@ -557,11 +644,38 @@ export class Moro extends EventEmitter {
|
|
|
557
644
|
}
|
|
558
645
|
}
|
|
559
646
|
|
|
560
|
-
//
|
|
647
|
+
// Advanced route matching with caching and optimization
|
|
648
|
+
private routeCache = new Map<string, { pattern: RegExp; paramNames: string[] }>();
|
|
649
|
+
private staticRouteMap = new Map<string, any>();
|
|
650
|
+
private dynamicRoutesBySegments = new Map<number, any[]>();
|
|
651
|
+
|
|
561
652
|
private findMatchingRoute(method: string, path: string) {
|
|
562
|
-
|
|
653
|
+
// Phase 1: O(1) static route lookup
|
|
654
|
+
const staticKey = `${method}:${path}`;
|
|
655
|
+
const staticRoute = this.staticRouteMap.get(staticKey);
|
|
656
|
+
if (staticRoute) {
|
|
657
|
+
return {
|
|
658
|
+
...staticRoute,
|
|
659
|
+
pattern: /^.*$/, // Dummy pattern for static routes
|
|
660
|
+
paramNames: [],
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Phase 2: Optimized dynamic route matching by segment count
|
|
665
|
+
const segments = path.split('/').filter(s => s.length > 0);
|
|
666
|
+
const segmentCount = segments.length;
|
|
667
|
+
const candidateRoutes = this.dynamicRoutesBySegments.get(segmentCount) || [];
|
|
668
|
+
|
|
669
|
+
for (const route of candidateRoutes) {
|
|
563
670
|
if (route.method === method) {
|
|
564
|
-
const
|
|
671
|
+
const cacheKey = `${method}:${route.path}`;
|
|
672
|
+
let pattern = this.routeCache.get(cacheKey);
|
|
673
|
+
|
|
674
|
+
if (!pattern) {
|
|
675
|
+
pattern = this.pathToRegex(route.path);
|
|
676
|
+
this.routeCache.set(cacheKey, pattern);
|
|
677
|
+
}
|
|
678
|
+
|
|
565
679
|
if (pattern.pattern.test(path)) {
|
|
566
680
|
return {
|
|
567
681
|
...route,
|
|
@@ -571,6 +685,7 @@ export class Moro extends EventEmitter {
|
|
|
571
685
|
}
|
|
572
686
|
}
|
|
573
687
|
}
|
|
688
|
+
|
|
574
689
|
return null;
|
|
575
690
|
}
|
|
576
691
|
|
|
@@ -602,7 +717,7 @@ export class Moro extends EventEmitter {
|
|
|
602
717
|
private addRoute(method: string, path: string, handler: Function, options: any = {}) {
|
|
603
718
|
const handlerName = `handler_${this.routes.length}`;
|
|
604
719
|
|
|
605
|
-
|
|
720
|
+
const route = {
|
|
606
721
|
method: method as any,
|
|
607
722
|
path,
|
|
608
723
|
handler: handlerName,
|
|
@@ -610,7 +725,12 @@ export class Moro extends EventEmitter {
|
|
|
610
725
|
rateLimit: options.rateLimit,
|
|
611
726
|
cache: options.cache,
|
|
612
727
|
middleware: options.middleware,
|
|
613
|
-
}
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
this.routes.push(route);
|
|
731
|
+
|
|
732
|
+
// Organize routes for optimal lookup
|
|
733
|
+
this.organizeRouteForLookup(route);
|
|
614
734
|
|
|
615
735
|
// Store handler for later module creation
|
|
616
736
|
this.routeHandlers[handlerName] = handler;
|
|
@@ -618,6 +738,23 @@ export class Moro extends EventEmitter {
|
|
|
618
738
|
return this;
|
|
619
739
|
}
|
|
620
740
|
|
|
741
|
+
private organizeRouteForLookup(route: any): void {
|
|
742
|
+
if (!route.path.includes(':')) {
|
|
743
|
+
// Static route - add to static map for O(1) lookup
|
|
744
|
+
const staticKey = `${route.method}:${route.path}`;
|
|
745
|
+
this.staticRouteMap.set(staticKey, route);
|
|
746
|
+
} else {
|
|
747
|
+
// Dynamic route - organize by segment count
|
|
748
|
+
const segments = route.path.split('/').filter((s: string) => s.length > 0);
|
|
749
|
+
const segmentCount = segments.length;
|
|
750
|
+
|
|
751
|
+
if (!this.dynamicRoutesBySegments.has(segmentCount)) {
|
|
752
|
+
this.dynamicRoutesBySegments.set(segmentCount, []);
|
|
753
|
+
}
|
|
754
|
+
this.dynamicRoutesBySegments.get(segmentCount)!.push(route);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
621
758
|
private registerDirectRoutes() {
|
|
622
759
|
// Register routes directly with the HTTP server for optimal performance
|
|
623
760
|
// This provides the intuitive developer experience users expect
|
|
@@ -766,11 +903,264 @@ export class Moro extends EventEmitter {
|
|
|
766
903
|
const module = await import(modulePath);
|
|
767
904
|
return module.default || module;
|
|
768
905
|
}
|
|
906
|
+
|
|
907
|
+
// Clustering support for massive performance gains with proper cleanup
|
|
908
|
+
private clusterWorkers = new Map<number, any>();
|
|
909
|
+
private startWithClustering(port: number, host?: string, callback?: () => void): void {
|
|
910
|
+
const cluster = require('cluster');
|
|
911
|
+
const os = require('os');
|
|
912
|
+
|
|
913
|
+
// Smart worker count calculation based on actual bottlenecks
|
|
914
|
+
let workerCount = this.config.performance?.clustering?.workers || os.cpus().length;
|
|
915
|
+
|
|
916
|
+
// Auto-optimize worker count based on system characteristics
|
|
917
|
+
if (workerCount === 'auto' || workerCount > 8) {
|
|
918
|
+
// For high-core machines, limit workers to prevent IPC/memory bottlenecks
|
|
919
|
+
const cpuCount = os.cpus().length;
|
|
920
|
+
const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);
|
|
921
|
+
|
|
922
|
+
// Optimal worker count formula based on research
|
|
923
|
+
if (cpuCount >= 16) {
|
|
924
|
+
// High-core machines: focus on memory/IPC efficiency
|
|
925
|
+
workerCount = Math.min(Math.ceil(totalMemoryGB / 2), 4); // 2GB per worker max, cap at 4
|
|
926
|
+
} else if (cpuCount >= 8) {
|
|
927
|
+
// Mid-range machines: balanced approach
|
|
928
|
+
workerCount = Math.min(cpuCount / 2, 4);
|
|
929
|
+
} else {
|
|
930
|
+
// Low-core machines: use all cores
|
|
931
|
+
workerCount = cpuCount;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
this.logger.info(
|
|
935
|
+
`Auto-optimized workers: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB)`,
|
|
936
|
+
'Cluster'
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
if (cluster.isPrimary) {
|
|
941
|
+
this.logger.info(`🚀 Starting ${workerCount} workers for maximum performance`, 'Cluster');
|
|
942
|
+
|
|
943
|
+
// Optimize cluster scheduling for high concurrency
|
|
944
|
+
cluster.schedulingPolicy = cluster.SCHED_RR; // Round-robin scheduling
|
|
945
|
+
|
|
946
|
+
// Set cluster settings for better performance
|
|
947
|
+
cluster.setupMaster({
|
|
948
|
+
exec: process.argv[1],
|
|
949
|
+
args: process.argv.slice(2),
|
|
950
|
+
silent: false,
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
// Optimize IPC to reduce communication overhead
|
|
954
|
+
process.env.NODE_CLUSTER_SCHED_POLICY = 'rr'; // Ensure round-robin
|
|
955
|
+
process.env.NODE_DISABLE_COLORS = '1'; // Reduce IPC message size
|
|
956
|
+
|
|
957
|
+
// Graceful shutdown handler
|
|
958
|
+
const gracefulShutdown = () => {
|
|
959
|
+
this.logger.info('Gracefully shutting down cluster...', 'Cluster');
|
|
960
|
+
|
|
961
|
+
// Clean up all workers
|
|
962
|
+
for (const [pid, worker] of this.clusterWorkers) {
|
|
963
|
+
worker.removeAllListeners();
|
|
964
|
+
worker.kill('SIGTERM');
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// Clean up cluster listeners
|
|
968
|
+
cluster.removeAllListeners();
|
|
969
|
+
process.exit(0);
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
// Handle process signals for graceful shutdown
|
|
973
|
+
process.on('SIGINT', gracefulShutdown);
|
|
974
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
975
|
+
|
|
976
|
+
// Fork workers with proper tracking and CPU affinity
|
|
977
|
+
for (let i = 0; i < workerCount; i++) {
|
|
978
|
+
const worker = cluster.fork({
|
|
979
|
+
WORKER_ID: i,
|
|
980
|
+
WORKER_CPU_AFFINITY: i % os.cpus().length, // Distribute workers across CPUs
|
|
981
|
+
});
|
|
982
|
+
this.clusterWorkers.set(worker.process.pid!, worker);
|
|
983
|
+
this.logger.info(
|
|
984
|
+
`Worker ${worker.process.pid} started (CPU ${i % os.cpus().length})`,
|
|
985
|
+
'Cluster'
|
|
986
|
+
);
|
|
987
|
+
|
|
988
|
+
// Handle individual worker messages (reuse handler)
|
|
989
|
+
worker.on('message', this.handleWorkerMessage.bind(this));
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// Handle worker exits with cleanup
|
|
993
|
+
cluster.on('exit', (worker: any, code: number, signal: string) => {
|
|
994
|
+
// Clean up worker tracking
|
|
995
|
+
this.clusterWorkers.delete(worker.process.pid);
|
|
996
|
+
|
|
997
|
+
this.logger.warn(
|
|
998
|
+
`Worker ${worker.process.pid} died (${signal || code}). Restarting...`,
|
|
999
|
+
'Cluster'
|
|
1000
|
+
);
|
|
1001
|
+
|
|
1002
|
+
// Restart worker with proper tracking
|
|
1003
|
+
const newWorker = cluster.fork();
|
|
1004
|
+
this.clusterWorkers.set(newWorker.process.pid!, newWorker);
|
|
1005
|
+
newWorker.on('message', this.handleWorkerMessage.bind(this));
|
|
1006
|
+
this.logger.info(`Worker ${newWorker.process.pid} started`, 'Cluster');
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
// Master process callback
|
|
1010
|
+
if (callback) callback();
|
|
1011
|
+
} else {
|
|
1012
|
+
// Worker process - start the actual server with proper cleanup
|
|
1013
|
+
this.logger.info(`Worker ${process.pid} initializing`, 'Worker');
|
|
1014
|
+
|
|
1015
|
+
// Worker-specific optimizations for high concurrency
|
|
1016
|
+
process.env.UV_THREADPOOL_SIZE = '64';
|
|
1017
|
+
|
|
1018
|
+
// Reduce logging contention in workers (major bottleneck)
|
|
1019
|
+
if (this.config.logging) {
|
|
1020
|
+
// Workers log less frequently to reduce I/O contention
|
|
1021
|
+
this.config.logging.level = 'warn'; // Only warnings and errors
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// Memory optimization for workers
|
|
1025
|
+
process.env.NODE_OPTIONS = '--max-old-space-size=1024'; // Limit memory per worker
|
|
1026
|
+
|
|
1027
|
+
// Optimize V8 flags for better performance (Rust-level optimizations)
|
|
1028
|
+
if (process.env.NODE_ENV === 'production') {
|
|
1029
|
+
// Ultra-aggressive V8 optimizations for maximum performance
|
|
1030
|
+
const v8Flags = [
|
|
1031
|
+
'--optimize-for-size', // Trade memory for speed
|
|
1032
|
+
'--always-opt', // Always optimize functions
|
|
1033
|
+
'--turbo-fast-api-calls', // Optimize API calls
|
|
1034
|
+
'--turbo-escape-analysis', // Escape analysis optimization
|
|
1035
|
+
'--turbo-inline-api-calls', // Inline API calls
|
|
1036
|
+
'--max-old-space-size=1024', // Limit memory to prevent GC pressure
|
|
1037
|
+
];
|
|
1038
|
+
process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ' ' + v8Flags.join(' ');
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// Optimize garbage collection for workers
|
|
1042
|
+
// eslint-disable-next-line no-undef
|
|
1043
|
+
if ((global as any).gc) {
|
|
1044
|
+
setInterval(() => {
|
|
1045
|
+
// eslint-disable-next-line no-undef
|
|
1046
|
+
if ((global as any).gc) (global as any).gc();
|
|
1047
|
+
}, 60000); // GC every 60 seconds (less frequent)
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Graceful shutdown for worker
|
|
1051
|
+
const workerShutdown = () => {
|
|
1052
|
+
this.logger.info(`Worker ${process.pid} shutting down gracefully...`, 'Worker');
|
|
1053
|
+
|
|
1054
|
+
// Clean up event listeners
|
|
1055
|
+
this.eventBus.removeAllListeners();
|
|
1056
|
+
this.removeAllListeners();
|
|
1057
|
+
|
|
1058
|
+
// Close server gracefully
|
|
1059
|
+
if (this.coreFramework) {
|
|
1060
|
+
const server = (this.coreFramework as any).server;
|
|
1061
|
+
if (server) {
|
|
1062
|
+
server.close(() => {
|
|
1063
|
+
process.exit(0);
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
// Handle worker shutdown signals
|
|
1070
|
+
process.on('SIGTERM', workerShutdown);
|
|
1071
|
+
process.on('SIGINT', workerShutdown);
|
|
1072
|
+
|
|
1073
|
+
// Continue with normal server startup for this worker
|
|
1074
|
+
this.eventBus.emit('server:starting', {
|
|
1075
|
+
port,
|
|
1076
|
+
runtime: this.runtimeType,
|
|
1077
|
+
worker: process.pid,
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
// Add documentation middleware first (if enabled)
|
|
1081
|
+
try {
|
|
1082
|
+
const docsMiddleware = this.documentation.getDocsMiddleware();
|
|
1083
|
+
this.coreFramework.addMiddleware(docsMiddleware);
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
// Documentation not enabled, that's fine
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Add intelligent routing middleware
|
|
1089
|
+
this.coreFramework.addMiddleware(
|
|
1090
|
+
async (req: HttpRequest, res: HttpResponse, next: () => void) => {
|
|
1091
|
+
const handled = await this.intelligentRouting.handleIntelligentRoute(req, res);
|
|
1092
|
+
if (!handled) {
|
|
1093
|
+
next();
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
);
|
|
1097
|
+
|
|
1098
|
+
// Register direct routes
|
|
1099
|
+
if (this.routes.length > 0) {
|
|
1100
|
+
this.registerDirectRoutes();
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
const workerCallback = () => {
|
|
1104
|
+
const displayHost = host || 'localhost';
|
|
1105
|
+
this.logger.info(`Worker ${process.pid} ready on ${displayHost}:${port}`, 'Worker');
|
|
1106
|
+
this.eventBus.emit('server:started', {
|
|
1107
|
+
port,
|
|
1108
|
+
runtime: this.runtimeType,
|
|
1109
|
+
worker: process.pid,
|
|
1110
|
+
});
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
if (host) {
|
|
1114
|
+
this.coreFramework.listen(port, host, workerCallback);
|
|
1115
|
+
} else {
|
|
1116
|
+
this.coreFramework.listen(port, workerCallback);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Reusable worker message handler (avoids creating new functions)
|
|
1122
|
+
private handleWorkerMessage(message: any): void {
|
|
1123
|
+
// Handle inter-worker communication if needed
|
|
1124
|
+
if (message.type === 'health-check') {
|
|
1125
|
+
// Worker health check response
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Log other worker messages
|
|
1130
|
+
this.logger.debug(`Worker message: ${JSON.stringify(message)}`, 'Cluster');
|
|
1131
|
+
}
|
|
769
1132
|
}
|
|
770
1133
|
|
|
771
1134
|
// Export convenience function
|
|
772
1135
|
export function createApp(options?: MoroOptions): Moro {
|
|
773
|
-
|
|
1136
|
+
// Load global config from moro.config.js and merge with passed options
|
|
1137
|
+
const globalConfig = initializeConfig();
|
|
1138
|
+
|
|
1139
|
+
// Convert global config to MoroOptions format for createApp
|
|
1140
|
+
const configAsOptions: Partial<MoroOptions> = {
|
|
1141
|
+
performance: globalConfig.performance,
|
|
1142
|
+
logger: globalConfig.logging
|
|
1143
|
+
? {
|
|
1144
|
+
level: globalConfig.logging.level,
|
|
1145
|
+
enableColors: globalConfig.logging.enableColors,
|
|
1146
|
+
enableTimestamp: globalConfig.logging.enableTimestamp,
|
|
1147
|
+
}
|
|
1148
|
+
: undefined,
|
|
1149
|
+
// Add other relevant config mappings as needed
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
// Merge config file options with passed options (passed options take precedence)
|
|
1153
|
+
const mergedOptions = {
|
|
1154
|
+
...configAsOptions,
|
|
1155
|
+
...options,
|
|
1156
|
+
// Deep merge performance settings
|
|
1157
|
+
performance: {
|
|
1158
|
+
...configAsOptions.performance,
|
|
1159
|
+
...options?.performance,
|
|
1160
|
+
},
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
return new Moro(mergedOptions);
|
|
774
1164
|
}
|
|
775
1165
|
|
|
776
1166
|
// 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
|
}
|