@morojs/moro 1.5.4 → 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.
Files changed (70) hide show
  1. package/dist/core/config/config-manager.d.ts +44 -0
  2. package/dist/core/config/config-manager.js +114 -0
  3. package/dist/core/config/config-manager.js.map +1 -0
  4. package/dist/core/config/config-sources.d.ts +21 -0
  5. package/dist/core/config/config-sources.js +314 -0
  6. package/dist/core/config/config-sources.js.map +1 -0
  7. package/dist/core/config/config-validator.d.ts +21 -0
  8. package/dist/core/config/config-validator.js +737 -0
  9. package/dist/core/config/config-validator.js.map +1 -0
  10. package/dist/core/config/file-loader.d.ts +0 -5
  11. package/dist/core/config/file-loader.js +0 -171
  12. package/dist/core/config/file-loader.js.map +1 -1
  13. package/dist/core/config/index.d.ts +39 -10
  14. package/dist/core/config/index.js +66 -29
  15. package/dist/core/config/index.js.map +1 -1
  16. package/dist/core/config/schema.js +22 -31
  17. package/dist/core/config/schema.js.map +1 -1
  18. package/dist/core/config/utils.d.ts +9 -2
  19. package/dist/core/config/utils.js +19 -32
  20. package/dist/core/config/utils.js.map +1 -1
  21. package/dist/core/framework.d.ts +2 -7
  22. package/dist/core/framework.js +12 -5
  23. package/dist/core/framework.js.map +1 -1
  24. package/dist/core/http/http-server.d.ts +12 -0
  25. package/dist/core/http/http-server.js +56 -0
  26. package/dist/core/http/http-server.js.map +1 -1
  27. package/dist/core/http/router.d.ts +12 -0
  28. package/dist/core/http/router.js +114 -36
  29. package/dist/core/http/router.js.map +1 -1
  30. package/dist/core/logger/index.d.ts +1 -1
  31. package/dist/core/logger/index.js +2 -1
  32. package/dist/core/logger/index.js.map +1 -1
  33. package/dist/core/logger/logger.d.ts +10 -1
  34. package/dist/core/logger/logger.js +99 -37
  35. package/dist/core/logger/logger.js.map +1 -1
  36. package/dist/core/routing/index.d.ts +20 -0
  37. package/dist/core/routing/index.js +109 -11
  38. package/dist/core/routing/index.js.map +1 -1
  39. package/dist/moro.d.ts +22 -0
  40. package/dist/moro.js +134 -98
  41. package/dist/moro.js.map +1 -1
  42. package/dist/types/config.d.ts +39 -2
  43. package/dist/types/core.d.ts +22 -39
  44. package/dist/types/logger.d.ts +4 -0
  45. package/package.json +1 -1
  46. package/src/core/config/config-manager.ts +133 -0
  47. package/src/core/config/config-sources.ts +384 -0
  48. package/src/core/config/config-validator.ts +1035 -0
  49. package/src/core/config/file-loader.ts +0 -233
  50. package/src/core/config/index.ts +77 -32
  51. package/src/core/config/schema.ts +22 -31
  52. package/src/core/config/utils.ts +22 -29
  53. package/src/core/framework.ts +18 -11
  54. package/src/core/http/http-server.ts +66 -0
  55. package/src/core/http/router.ts +127 -38
  56. package/src/core/logger/index.ts +1 -0
  57. package/src/core/logger/logger.ts +109 -36
  58. package/src/core/routing/index.ts +116 -12
  59. package/src/moro.ts +159 -107
  60. package/src/types/config.ts +40 -2
  61. package/src/types/core.ts +32 -43
  62. package/src/types/logger.ts +6 -0
  63. package/dist/core/config/loader.d.ts +0 -7
  64. package/dist/core/config/loader.js +0 -269
  65. package/dist/core/config/loader.js.map +0 -1
  66. package/dist/core/config/validation.d.ts +0 -17
  67. package/dist/core/config/validation.js +0 -131
  68. package/dist/core/config/validation.js.map +0 -1
  69. package/src/core/config/loader.ts +0 -633
  70. package/src/core/config/validation.ts +0 -140
@@ -26,6 +26,11 @@ export class MoroHttpServer {
26
26
  // Request handler pooling to avoid function creation overhead
27
27
  private middlewareExecutionCache = new Map<string, Function>();
28
28
 
29
+ // Response caching for ultra-fast common responses
30
+ private responseCache = new Map<string, Buffer>();
31
+ private responseCacheHits = 0;
32
+ private responseCacheMisses = 0;
33
+
29
34
  // String interning for common values (massive memory savings)
30
35
  private static readonly INTERNED_METHODS = new Map([
31
36
  ['GET', 'GET'],
@@ -492,6 +497,20 @@ export class MoroHttpServer {
492
497
  httpRes.json = async (data: any) => {
493
498
  if (httpRes.headersSent) return;
494
499
 
500
+ // PERFORMANCE OPTIMIZATION: Check response cache for common patterns
501
+ const cacheKey = this.getResponseCacheKey(data);
502
+ if (cacheKey) {
503
+ const cachedBuffer = this.responseCache.get(cacheKey);
504
+ if (cachedBuffer) {
505
+ this.responseCacheHits++;
506
+ httpRes.setHeader('Content-Type', 'application/json; charset=utf-8');
507
+ httpRes.setHeader('Content-Length', cachedBuffer.length);
508
+ httpRes.end(cachedBuffer);
509
+ return;
510
+ }
511
+ this.responseCacheMisses++;
512
+ }
513
+
495
514
  // Ultra-fast JSON serialization with zero-copy buffers
496
515
  let jsonString: string;
497
516
 
@@ -578,6 +597,12 @@ export class MoroHttpServer {
578
597
  });
579
598
 
580
599
  httpRes.end(finalBuffer);
600
+
601
+ // PERFORMANCE OPTIMIZATION: Cache small, common responses
602
+ if (cacheKey && finalBuffer.length < 1024 && this.responseCache.size < 100) {
603
+ this.responseCache.set(cacheKey, Buffer.from(finalBuffer));
604
+ }
605
+
581
606
  // Return buffer to pool after response (zero-copy achievement!)
582
607
  process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
583
608
  };
@@ -953,6 +978,47 @@ export class MoroHttpServer {
953
978
  getServer(): Server {
954
979
  return this.server;
955
980
  }
981
+
982
+ // PERFORMANCE OPTIMIZATION: Generate cache key for common response patterns
983
+ private getResponseCacheKey(data: any): string | null {
984
+ // Only cache simple, common responses
985
+ if (!data || typeof data !== 'object') {
986
+ // Simple primitives or strings - generate key
987
+ const key = JSON.stringify(data);
988
+ return key.length < 100 ? key : null;
989
+ }
990
+
991
+ // Common API response patterns
992
+ if ('hello' in data && Object.keys(data).length <= 2) {
993
+ // Hello world type responses
994
+ return `hello:${JSON.stringify(data)}`;
995
+ }
996
+
997
+ if ('status' in data && Object.keys(data).length <= 3) {
998
+ // Status responses like {status: "ok", version: "1.0.0"}
999
+ return `status:${JSON.stringify(data)}`;
1000
+ }
1001
+
1002
+ if ('success' in data && 'message' in data && Object.keys(data).length <= 3) {
1003
+ // Simple success/error responses
1004
+ return `msg:${data.success}:${data.message}`;
1005
+ }
1006
+
1007
+ // Don't cache complex objects
1008
+ return null;
1009
+ }
1010
+
1011
+ // Performance statistics
1012
+ getPerformanceStats() {
1013
+ return {
1014
+ responseCacheHits: this.responseCacheHits,
1015
+ responseCacheMisses: this.responseCacheMisses,
1016
+ responseCacheSize: this.responseCache.size,
1017
+ paramObjectPoolSize: this.paramObjectPool.length,
1018
+ bufferPoolSize: this.bufferPool.length,
1019
+ middlewareExecutionCacheSize: this.middlewareExecutionCache.size,
1020
+ };
1021
+ }
956
1022
  }
957
1023
 
958
1024
  // Built-in middleware
@@ -12,6 +12,14 @@ export class Router {
12
12
  private routes: RouteDefinition[] = [];
13
13
  private logger = createFrameworkLogger('Router');
14
14
 
15
+ // Performance optimizations - O(1) static route lookup
16
+ private staticRoutes = new Map<string, RouteDefinition>(); // "GET:/api/users" -> route
17
+ private dynamicRoutes: RouteDefinition[] = []; // Routes with parameters
18
+
19
+ // Object pooling for parameters to reduce GC pressure
20
+ private paramObjectPool: Record<string, string>[] = [];
21
+ private readonly maxPoolSize = 50;
22
+
15
23
  get(path: string, ...handlers: (Middleware | HttpHandler)[]): void {
16
24
  this.addRoute('GET', path, handlers);
17
25
  }
@@ -37,14 +45,37 @@ export class Router {
37
45
  const handler = handlers.pop() as HttpHandler;
38
46
  const middleware = handlers as Middleware[];
39
47
 
40
- this.routes.push({
48
+ const route: RouteDefinition = {
41
49
  method,
42
50
  path,
43
51
  pattern,
44
52
  paramNames,
45
53
  handler,
46
54
  middleware,
47
- });
55
+ };
56
+
57
+ // Add to routes array (maintain compatibility)
58
+ this.routes.push(route);
59
+
60
+ // Performance optimization: separate static and dynamic routes
61
+ const isStatic = !path.includes(':') && !path.includes('*');
62
+ if (isStatic && middleware.length === 0) {
63
+ // Static route with no middleware - use O(1) lookup
64
+ const routeKey = `${method}:${path}`;
65
+ this.staticRoutes.set(routeKey, route);
66
+ this.logger.debug(`Added static route: ${routeKey}`, 'FastRoute');
67
+ } else {
68
+ // Dynamic route or has middleware - needs regex matching
69
+ this.dynamicRoutes.push(route);
70
+ this.logger.debug(`Added dynamic route: ${method} ${path}`, 'DynamicRoute');
71
+ }
72
+
73
+ // Initialize object pool on first route
74
+ if (this.paramObjectPool.length === 0) {
75
+ for (let i = 0; i < this.maxPoolSize; i++) {
76
+ this.paramObjectPool.push({});
77
+ }
78
+ }
48
79
  }
49
80
 
50
81
  private pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {
@@ -77,10 +108,30 @@ export class Router {
77
108
  'Processing'
78
109
  );
79
110
 
80
- const route = this.routes.find(r => r.method === req.method && r.pattern.test(path));
111
+ // PERFORMANCE OPTIMIZATION: Fast path - O(1) static route lookup first
112
+ const routeKey = `${req.method}:${path}`;
113
+ const staticRoute = this.staticRoutes.get(routeKey);
114
+
115
+ if (staticRoute) {
116
+ this.logger.debug(`Fast route match: ${routeKey}`, 'FastRoute');
117
+
118
+ // Static route with no middleware - execute handler directly
119
+ req.params = {}; // No params for static routes
120
+ const result = await staticRoute.handler(req, res);
121
+
122
+ // If handler returns data and response hasn't been sent, send it
123
+ if (result !== undefined && result !== null && !res.headersSent) {
124
+ res.json(result);
125
+ }
126
+
127
+ return true;
128
+ }
129
+
130
+ // Fallback: Dynamic route matching (with middleware support)
131
+ const route = this.dynamicRoutes.find(r => r.method === req.method && r.pattern.test(path));
81
132
 
82
133
  this.logger.debug(
83
- `Found route: ${!!route}${route ? ` ${route.method} ${route.path}` : ' none'}`,
134
+ `Found dynamic route: ${!!route}${route ? ` ${route.method} ${route.path}` : ' none'}`,
84
135
  'RouteMatch'
85
136
  );
86
137
 
@@ -88,54 +139,92 @@ export class Router {
88
139
  return false; // Route not found
89
140
  }
90
141
 
91
- // Extract path parameters
142
+ // Extract path parameters using object pooling
92
143
  const matches = path.match(route.pattern);
93
144
  if (matches) {
94
- req.params = {};
145
+ req.params = this.acquireParamObject();
95
146
  route.paramNames.forEach((name, index) => {
96
147
  req.params[name] = matches[index + 1];
97
148
  });
98
149
  }
99
150
 
100
- // Execute middleware
101
- for (const mw of route.middleware) {
102
- await new Promise<void>((resolve, reject) => {
103
- let nextCalled = false;
104
-
105
- const next = () => {
106
- if (nextCalled) return;
107
- nextCalled = true;
108
- resolve();
109
- };
110
-
111
- try {
112
- const result = mw(req, res, next);
113
-
114
- if (result instanceof Promise) {
115
- result
116
- .then(() => {
117
- if (!nextCalled) next();
118
- })
119
- .catch(reject);
151
+ try {
152
+ // Execute middleware
153
+ for (const mw of route.middleware) {
154
+ await new Promise<void>((resolve, reject) => {
155
+ let nextCalled = false;
156
+
157
+ const next = () => {
158
+ if (nextCalled) return;
159
+ nextCalled = true;
160
+ resolve();
161
+ };
162
+
163
+ try {
164
+ const result = mw(req, res, next);
165
+
166
+ if (result instanceof Promise) {
167
+ result
168
+ .then(() => {
169
+ if (!nextCalled) next();
170
+ })
171
+ .catch(reject);
172
+ }
173
+ } catch (error) {
174
+ reject(error);
120
175
  }
121
- } catch (error) {
122
- reject(error);
123
- }
124
- });
125
- }
176
+ });
126
177
 
127
- // Execute handler
128
- const result = await route.handler(req, res);
178
+ if (res.headersSent) break; // Early exit if response sent
179
+ }
129
180
 
130
- // If handler returns data and response hasn't been sent, send it
131
- if (result !== undefined && result !== null && !res.headersSent) {
132
- res.json(result);
133
- }
181
+ // Execute handler
182
+ const result = await route.handler(req, res);
134
183
 
135
- return true;
184
+ // If handler returns data and response hasn't been sent, send it
185
+ if (result !== undefined && result !== null && !res.headersSent) {
186
+ res.json(result);
187
+ }
188
+
189
+ return true;
190
+ } finally {
191
+ // Release parameter object back to pool
192
+ if (req.params && matches) {
193
+ this.releaseParamObject(req.params);
194
+ }
195
+ }
136
196
  }
137
197
 
138
198
  getRoutes(): RouteDefinition[] {
139
199
  return [...this.routes];
140
200
  }
201
+
202
+ // Object pooling methods for performance optimization
203
+ private acquireParamObject(): Record<string, string> {
204
+ const obj = this.paramObjectPool.pop();
205
+ if (obj) {
206
+ // Clear the object
207
+ for (const key in obj) {
208
+ delete obj[key];
209
+ }
210
+ return obj;
211
+ }
212
+ return {};
213
+ }
214
+
215
+ private releaseParamObject(obj: Record<string, string>): void {
216
+ if (this.paramObjectPool.length < this.maxPoolSize) {
217
+ this.paramObjectPool.push(obj);
218
+ }
219
+ }
220
+
221
+ // Performance statistics for monitoring
222
+ getPerformanceStats() {
223
+ return {
224
+ totalRoutes: this.routes.length,
225
+ staticRoutes: this.staticRoutes.size,
226
+ dynamicRoutes: this.dynamicRoutes.length,
227
+ paramObjectPoolSize: this.paramObjectPool.length,
228
+ };
229
+ }
141
230
  }
@@ -5,6 +5,7 @@ export {
5
5
  createFrameworkLogger,
6
6
  configureGlobalLogger,
7
7
  applyLoggingConfiguration,
8
+ destroyGlobalLogger,
8
9
  } from './logger';
9
10
  export * from './filters';
10
11
 
@@ -60,6 +60,7 @@ export class MoroLogger implements Logger {
60
60
  // Buffer overflow protection
61
61
  private bufferOverflowThreshold: number;
62
62
  private emergencyFlushInProgress = false;
63
+ private isDestroyed = false;
63
64
 
64
65
  // High-performance output methods
65
66
 
@@ -226,7 +227,9 @@ export class MoroLogger implements Logger {
226
227
  }
227
228
 
228
229
  child(context: string, metadata?: Record<string, any>): Logger {
229
- const childLogger = new MoroLogger(this.options);
230
+ // Create child logger with current parent level (not original options level)
231
+ const childOptions = { ...this.options, level: this.level };
232
+ const childLogger = new MoroLogger(childOptions);
230
233
  childLogger.contextPrefix = this.contextPrefix ? `${this.contextPrefix}:${context}` : context;
231
234
  childLogger.contextMetadata = { ...this.contextMetadata, ...metadata };
232
235
  childLogger.outputs = this.outputs;
@@ -242,6 +245,10 @@ export class MoroLogger implements Logger {
242
245
  this.level = level;
243
246
  }
244
247
 
248
+ getLevel(): LogLevel {
249
+ return this.level;
250
+ }
251
+
245
252
  addOutput(output: LogOutput): void {
246
253
  this.outputs.set(output.name, output);
247
254
  }
@@ -344,24 +351,13 @@ export class MoroLogger implements Logger {
344
351
 
345
352
  // Absolute minimal path for simple logs - pure speed
346
353
  if (!metadata && !context && !this.contextPrefix && !this.contextMetadata) {
347
- const logStr = level.toUpperCase() + ' ' + message + '\n';
348
- if (level === 'error' || level === 'fatal') {
349
- process.stderr.write(logStr);
350
- } else {
351
- process.stdout.write(logStr);
352
- }
354
+ this.writeSimpleLog(level, message);
353
355
  return;
354
356
  }
355
357
 
356
358
  // Minimal path for logs with context but no metadata
357
359
  if (!metadata && !this.contextMetadata) {
358
- const contextStr = context ? '[' + context + '] ' : '';
359
- const logStr = level.toUpperCase() + ' ' + contextStr + message + '\n';
360
- if (level === 'error' || level === 'fatal') {
361
- process.stderr.write(logStr);
362
- } else {
363
- process.stdout.write(logStr);
364
- }
360
+ this.writeSimpleLog(level, message, context);
365
361
  return;
366
362
  }
367
363
 
@@ -427,33 +423,78 @@ export class MoroLogger implements Logger {
427
423
  context?: string,
428
424
  metadata?: Record<string, any>
429
425
  ): void {
430
- // Build string directly - no array, no try-catch, no metrics
431
- let logStr = this.getFastCachedTimestamp() + ' ' + level.toUpperCase().padEnd(5);
426
+ // Use object pooling for LogEntry (Pino's technique)
427
+ const entry = MoroLogger.getPooledEntry();
428
+ const now = Date.now();
432
429
 
433
- if (context) {
434
- logStr += '[' + context + '] ';
435
- } else if (this.contextPrefix) {
436
- logStr += '[' + this.contextPrefix + '] ';
437
- }
430
+ entry.timestamp = new Date(now);
431
+ entry.level = level;
432
+ entry.message = message;
433
+ entry.context = this.contextPrefix
434
+ ? context
435
+ ? `${this.contextPrefix}:${context}`
436
+ : this.contextPrefix
437
+ : context;
438
+ entry.metadata = this.createMetadata(metadata);
439
+ entry.performance = this.options.enablePerformance ? this.getPerformanceData(now) : undefined;
438
440
 
439
- logStr += message;
441
+ // Write to outputs with batched processing
442
+ this.writeToOutputs(entry, level);
440
443
 
441
- if (metadata && Object.keys(metadata).length > 0) {
442
- logStr += ' ' + this.safeStringify(metadata);
443
- }
444
+ // Return entry to pool
445
+ MoroLogger.returnPooledEntry(entry);
446
+ }
444
447
 
445
- if (this.contextMetadata && Object.keys(this.contextMetadata).length > 0) {
446
- logStr += ' ' + this.safeStringify(this.contextMetadata);
447
- }
448
+ // Simple log writer with colors for minimal overhead cases
449
+ private writeSimpleLog(level: LogLevel, message: string, context?: string): void {
450
+ const colors = this.options.enableColors !== false;
451
+ const levelReset = colors ? MoroLogger.RESET : '';
448
452
 
449
- logStr += '\n';
453
+ MoroLogger.resetStringBuilder();
450
454
 
451
- // Direct write - no error handling, no buffering
452
- if (level === 'error' || level === 'fatal') {
453
- process.stderr.write(logStr);
455
+ // Timestamp with caching optimization
456
+ if (this.options.enableTimestamp !== false) {
457
+ const timestamp = this.getFastCachedTimestamp();
458
+ if (colors) {
459
+ MoroLogger.appendToBuilder(MoroLogger.COLORS.timestamp);
460
+ MoroLogger.appendToBuilder(timestamp);
461
+ MoroLogger.appendToBuilder(levelReset);
462
+ } else {
463
+ MoroLogger.appendToBuilder(timestamp);
464
+ }
465
+ MoroLogger.appendToBuilder(' ');
466
+ }
467
+
468
+ // Level with pre-allocated strings
469
+ const levelStr = MoroLogger.LEVEL_STRINGS[level];
470
+ if (colors) {
471
+ MoroLogger.appendToBuilder(MoroLogger.COLORS[level]);
472
+ MoroLogger.appendToBuilder(MoroLogger.BOLD);
473
+ MoroLogger.appendToBuilder(levelStr);
474
+ MoroLogger.appendToBuilder(levelReset);
454
475
  } else {
455
- process.stdout.write(logStr);
476
+ MoroLogger.appendToBuilder(levelStr);
477
+ }
478
+
479
+ // Context
480
+ if (context && this.options.enableContext !== false) {
481
+ MoroLogger.appendToBuilder(' ');
482
+ if (colors) {
483
+ MoroLogger.appendToBuilder(MoroLogger.COLORS.context);
484
+ MoroLogger.appendToBuilder(`[${context}]`);
485
+ MoroLogger.appendToBuilder(levelReset);
486
+ } else {
487
+ MoroLogger.appendToBuilder(`[${context}]`);
488
+ }
456
489
  }
490
+
491
+ // Message
492
+ MoroLogger.appendToBuilder(' ');
493
+ MoroLogger.appendToBuilder(message);
494
+
495
+ // Output main log line with high-performance method
496
+ const finalMessage = MoroLogger.buildString();
497
+ this.output(`${finalMessage}\n`, level);
457
498
  }
458
499
 
459
500
  private updateMetrics(entry: LogEntry): void {
@@ -691,8 +732,8 @@ export class MoroLogger implements Logger {
691
732
  }
692
733
 
693
734
  private scheduleFlush(): void {
694
- if (this.flushTimeout) {
695
- return; // Already scheduled
735
+ if (this.flushTimeout || this.isDestroyed) {
736
+ return; // Already scheduled or destroyed
696
737
  }
697
738
 
698
739
  this.flushTimeout = setTimeout(() => {
@@ -700,7 +741,7 @@ export class MoroLogger implements Logger {
700
741
  }, this.flushInterval);
701
742
  }
702
743
 
703
- private flushBuffer(): void {
744
+ public flushBuffer(): void {
704
745
  if (this.outputBuffer.length === 0) {
705
746
  return;
706
747
  }
@@ -882,6 +923,30 @@ export class MoroLogger implements Logger {
882
923
  // Ignore flush errors
883
924
  }
884
925
  }
926
+
927
+ // Destroy logger and clean up all resources (for testing)
928
+ public destroy(): void {
929
+ // Mark as destroyed to prevent new timeouts
930
+ this.isDestroyed = true;
931
+
932
+ // Clear any remaining timeouts
933
+ if (this.flushTimeout) {
934
+ clearTimeout(this.flushTimeout);
935
+ this.flushTimeout = null;
936
+ }
937
+
938
+ // Flush any remaining buffer
939
+ this.flushBuffer();
940
+
941
+ // Clear outputs and filters
942
+ this.outputs.clear();
943
+ this.filters.clear();
944
+
945
+ // Clear history
946
+ this.history.length = 0;
947
+ this.historyIndex = 0;
948
+ this.historySize = 0;
949
+ }
885
950
  }
886
951
 
887
952
  // Global logger instance
@@ -908,6 +973,14 @@ export function configureGlobalLogger(options: Partial<LoggerOptions>): void {
908
973
  // For now, focusing on level which is the most critical
909
974
  }
910
975
 
976
+ /**
977
+ * Destroy the global logger and clean up resources (for testing)
978
+ * @internal
979
+ */
980
+ export function destroyGlobalLogger(): void {
981
+ logger.destroy();
982
+ }
983
+
911
984
  /**
912
985
  * Apply logging configuration from the config system and/or createApp options
913
986
  */