@morojs/moro 1.2.0 → 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/src/moro.ts CHANGED
@@ -63,11 +63,38 @@ export class Moro extends EventEmitter {
63
63
  // Initialize configuration system
64
64
  this.config = initializeConfig();
65
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
+
66
71
  // Apply additional logging configuration from createApp options (takes precedence)
67
72
  if (options.logger !== undefined) {
68
73
  applyLoggingConfiguration(undefined, options.logger);
69
74
  }
70
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
+
71
98
  this.logger.info(
72
99
  `Configuration system initialized: ${this.config.server.environment}:${this.config.server.port}`
73
100
  );
@@ -78,7 +105,13 @@ export class Moro extends EventEmitter {
78
105
 
79
106
  this.logger.info(`Runtime system initialized: ${this.runtimeType}`, 'Runtime');
80
107
 
81
- this.coreFramework = new MoroCore();
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);
82
115
 
83
116
  // Initialize middleware system
84
117
  this.middlewareManager = new MiddlewareManager();
@@ -89,6 +122,15 @@ export class Moro extends EventEmitter {
89
122
  httpServer.setHookManager((this.middlewareManager as any).hooks);
90
123
  }
91
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
+
92
134
  // Access enterprise event bus from core framework
93
135
  this.eventBus = (this.coreFramework as any).eventBus;
94
136
 
@@ -378,9 +420,14 @@ export class Moro extends EventEmitter {
378
420
  }
379
421
 
380
422
  // Start server with events (Node.js only)
423
+ listen(callback?: () => void): void;
381
424
  listen(port: number, callback?: () => void): void;
382
425
  listen(port: number, host: string, callback?: () => void): void;
383
- listen(port: number, host?: string | (() => void), callback?: () => void) {
426
+ listen(
427
+ portOrCallback?: number | (() => void),
428
+ hostOrCallback?: string | (() => void),
429
+ callback?: () => void
430
+ ) {
384
431
  // Only available for Node.js runtime
385
432
  if (this.runtimeType !== 'node') {
386
433
  throw new Error(
@@ -388,10 +435,46 @@ export class Moro extends EventEmitter {
388
435
  );
389
436
  }
390
437
 
391
- // Handle overloaded parameters (port, callback) or (port, host, callback)
392
- if (typeof host === 'function') {
393
- callback = host;
394
- host = undefined;
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;
395
478
  }
396
479
  this.eventBus.emit('server:starting', { port, runtime: this.runtimeType });
397
480
 
@@ -488,7 +571,7 @@ export class Moro extends EventEmitter {
488
571
  const matches = req.path.match(route.pattern);
489
572
  if (matches) {
490
573
  req.params = {};
491
- route.paramNames.forEach((name, index) => {
574
+ route.paramNames.forEach((name: string, index: number) => {
492
575
  req.params[name] = matches[index + 1];
493
576
  });
494
577
  }
@@ -552,11 +635,38 @@ export class Moro extends EventEmitter {
552
635
  }
553
636
  }
554
637
 
555
- // Find matching route
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
+
556
643
  private findMatchingRoute(method: string, path: string) {
557
- for (const route of this.routes) {
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) {
558
661
  if (route.method === method) {
559
- const pattern = this.pathToRegex(route.path);
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
+
560
670
  if (pattern.pattern.test(path)) {
561
671
  return {
562
672
  ...route,
@@ -566,6 +676,7 @@ export class Moro extends EventEmitter {
566
676
  }
567
677
  }
568
678
  }
679
+
569
680
  return null;
570
681
  }
571
682
 
@@ -597,7 +708,7 @@ export class Moro extends EventEmitter {
597
708
  private addRoute(method: string, path: string, handler: Function, options: any = {}) {
598
709
  const handlerName = `handler_${this.routes.length}`;
599
710
 
600
- this.routes.push({
711
+ const route = {
601
712
  method: method as any,
602
713
  path,
603
714
  handler: handlerName,
@@ -605,7 +716,12 @@ export class Moro extends EventEmitter {
605
716
  rateLimit: options.rateLimit,
606
717
  cache: options.cache,
607
718
  middleware: options.middleware,
608
- });
719
+ };
720
+
721
+ this.routes.push(route);
722
+
723
+ // Organize routes for optimal lookup
724
+ this.organizeRouteForLookup(route);
609
725
 
610
726
  // Store handler for later module creation
611
727
  this.routeHandlers[handlerName] = handler;
@@ -613,6 +729,23 @@ export class Moro extends EventEmitter {
613
729
  return this;
614
730
  }
615
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
+
616
749
  private registerDirectRoutes() {
617
750
  // Register routes directly with the HTTP server for optimal performance
618
751
  // This provides the intuitive developer experience users expect
@@ -761,11 +894,264 @@ export class Moro extends EventEmitter {
761
894
  const module = await import(modulePath);
762
895
  return module.default || module;
763
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
+ }
764
1123
  }
765
1124
 
766
1125
  // Export convenience function
767
1126
  export function createApp(options?: MoroOptions): Moro {
768
- return new Moro(options);
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);
769
1155
  }
770
1156
 
771
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
  }