@morojs/moro 1.6.5 → 1.6.8

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 (200) hide show
  1. package/README.md +20 -4
  2. package/dist/core/auth/morojs-adapter.js +17 -14
  3. package/dist/core/auth/morojs-adapter.js.map +1 -1
  4. package/dist/core/config/config-sources.js +44 -0
  5. package/dist/core/config/config-sources.js.map +1 -1
  6. package/dist/core/database/adapters/drizzle.js +5 -5
  7. package/dist/core/database/adapters/drizzle.js.map +1 -1
  8. package/dist/core/database/adapters/mongodb.js +5 -1
  9. package/dist/core/database/adapters/mongodb.js.map +1 -1
  10. package/dist/core/database/adapters/mysql.js +5 -1
  11. package/dist/core/database/adapters/mysql.js.map +1 -1
  12. package/dist/core/database/adapters/postgresql.js +1 -1
  13. package/dist/core/database/adapters/postgresql.js.map +1 -1
  14. package/dist/core/database/adapters/redis.js +2 -2
  15. package/dist/core/database/adapters/redis.js.map +1 -1
  16. package/dist/core/database/adapters/sqlite.js +5 -1
  17. package/dist/core/database/adapters/sqlite.js.map +1 -1
  18. package/dist/core/docs/index.js.map +1 -1
  19. package/dist/core/docs/simple-docs.js +2 -1
  20. package/dist/core/docs/simple-docs.js.map +1 -1
  21. package/dist/core/docs/swagger-ui.js +1 -0
  22. package/dist/core/docs/swagger-ui.js.map +1 -1
  23. package/dist/core/docs/zod-to-openapi.js +4 -0
  24. package/dist/core/docs/zod-to-openapi.js.map +1 -1
  25. package/dist/core/events/event-bus.d.ts +1 -1
  26. package/dist/core/events/event-bus.js +8 -4
  27. package/dist/core/events/event-bus.js.map +1 -1
  28. package/dist/core/framework.d.ts +1 -1
  29. package/dist/core/framework.js +3 -1
  30. package/dist/core/framework.js.map +1 -1
  31. package/dist/core/graphql/adapter.d.ts +73 -0
  32. package/dist/core/graphql/adapter.js +2 -0
  33. package/dist/core/graphql/adapter.js.map +1 -0
  34. package/dist/core/graphql/adapters/graphql-js-adapter.d.ts +26 -0
  35. package/dist/core/graphql/adapters/graphql-js-adapter.js +229 -0
  36. package/dist/core/graphql/adapters/graphql-js-adapter.js.map +1 -0
  37. package/dist/core/graphql/core.d.ts +60 -0
  38. package/dist/core/graphql/core.js +165 -0
  39. package/dist/core/graphql/core.js.map +1 -0
  40. package/dist/core/graphql/index.d.ts +4 -0
  41. package/dist/core/graphql/index.js +4 -0
  42. package/dist/core/graphql/index.js.map +1 -0
  43. package/dist/core/graphql/loader.d.ts +9 -0
  44. package/dist/core/graphql/loader.js +32 -0
  45. package/dist/core/graphql/loader.js.map +1 -0
  46. package/dist/core/graphql/types.d.ts +211 -0
  47. package/dist/core/graphql/types.js +2 -0
  48. package/dist/core/graphql/types.js.map +1 -0
  49. package/dist/core/http/http-server.d.ts +7 -0
  50. package/dist/core/http/http-server.js +267 -123
  51. package/dist/core/http/http-server.js.map +1 -1
  52. package/dist/core/http/utils/uws-worker-clustering.d.ts +28 -0
  53. package/dist/core/http/utils/uws-worker-clustering.js +313 -0
  54. package/dist/core/http/utils/uws-worker-clustering.js.map +1 -0
  55. package/dist/core/http/uws-http-server.d.ts +7 -1
  56. package/dist/core/http/uws-http-server.js +272 -189
  57. package/dist/core/http/uws-http-server.js.map +1 -1
  58. package/dist/core/jobs/cron-parser.d.ts +62 -0
  59. package/dist/core/jobs/cron-parser.js +239 -0
  60. package/dist/core/jobs/cron-parser.js.map +1 -0
  61. package/dist/core/jobs/index.d.ts +12 -0
  62. package/dist/core/jobs/index.js +9 -0
  63. package/dist/core/jobs/index.js.map +1 -0
  64. package/dist/core/jobs/job-executor.d.ts +134 -0
  65. package/dist/core/jobs/job-executor.js +413 -0
  66. package/dist/core/jobs/job-executor.js.map +1 -0
  67. package/dist/core/jobs/job-scheduler.d.ts +214 -0
  68. package/dist/core/jobs/job-scheduler.js +551 -0
  69. package/dist/core/jobs/job-scheduler.js.map +1 -0
  70. package/dist/core/jobs/job-state-manager.d.ts +158 -0
  71. package/dist/core/jobs/job-state-manager.js +444 -0
  72. package/dist/core/jobs/job-state-manager.js.map +1 -0
  73. package/dist/core/jobs/leader-election.d.ts +124 -0
  74. package/dist/core/jobs/leader-election.js +481 -0
  75. package/dist/core/jobs/leader-election.js.map +1 -0
  76. package/dist/core/jobs/types.d.ts +151 -0
  77. package/dist/core/jobs/types.js +4 -0
  78. package/dist/core/jobs/types.js.map +1 -0
  79. package/dist/core/jobs/utils.d.ts +95 -0
  80. package/dist/core/jobs/utils.js +258 -0
  81. package/dist/core/jobs/utils.js.map +1 -0
  82. package/dist/core/logger/filters.js +2 -0
  83. package/dist/core/logger/filters.js.map +1 -1
  84. package/dist/core/logger/logger.d.ts +7 -5
  85. package/dist/core/logger/logger.js +68 -27
  86. package/dist/core/logger/logger.js.map +1 -1
  87. package/dist/core/logger/outputs.js +2 -0
  88. package/dist/core/logger/outputs.js.map +1 -1
  89. package/dist/core/middleware/built-in/auth/helpers.js +1 -1
  90. package/dist/core/middleware/built-in/auth/helpers.js.map +1 -1
  91. package/dist/core/middleware/built-in/auth/jwt-helpers.js +1 -1
  92. package/dist/core/middleware/built-in/auth/jwt-helpers.js.map +1 -1
  93. package/dist/core/middleware/built-in/auth/providers.js +1 -1
  94. package/dist/core/middleware/built-in/auth/providers.js.map +1 -1
  95. package/dist/core/middleware/built-in/cache/adapters/cache/file.js +3 -3
  96. package/dist/core/middleware/built-in/cache/adapters/cache/file.js.map +1 -1
  97. package/dist/core/middleware/built-in/cache/adapters/cache/memory.js +1 -0
  98. package/dist/core/middleware/built-in/cache/adapters/cache/memory.js.map +1 -1
  99. package/dist/core/middleware/built-in/cache/adapters/cache/redis.js +1 -1
  100. package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -1
  101. package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.d.ts +8 -0
  102. package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js +100 -7
  103. package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js.map +1 -1
  104. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.d.ts +6 -0
  105. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js +97 -13
  106. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js.map +1 -1
  107. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js +1 -1
  108. package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js.map +1 -1
  109. package/dist/core/middleware/built-in/cookie/hook.d.ts +1 -1
  110. package/dist/core/middleware/built-in/cookie/hook.js +2 -2
  111. package/dist/core/middleware/built-in/cookie/hook.js.map +1 -1
  112. package/dist/core/middleware/built-in/csrf/core.js +1 -0
  113. package/dist/core/middleware/built-in/csrf/core.js.map +1 -1
  114. package/dist/core/middleware/built-in/graphql/core.d.ts +11 -0
  115. package/dist/core/middleware/built-in/graphql/core.js +24 -0
  116. package/dist/core/middleware/built-in/graphql/core.js.map +1 -0
  117. package/dist/core/middleware/built-in/graphql/helpers.d.ts +69 -0
  118. package/dist/core/middleware/built-in/graphql/helpers.js +187 -0
  119. package/dist/core/middleware/built-in/graphql/helpers.js.map +1 -0
  120. package/dist/core/middleware/built-in/graphql/hook.d.ts +7 -0
  121. package/dist/core/middleware/built-in/graphql/hook.js +78 -0
  122. package/dist/core/middleware/built-in/graphql/hook.js.map +1 -0
  123. package/dist/core/middleware/built-in/graphql/index.d.ts +5 -0
  124. package/dist/core/middleware/built-in/graphql/index.js +5 -0
  125. package/dist/core/middleware/built-in/graphql/index.js.map +1 -0
  126. package/dist/core/middleware/built-in/graphql/middleware.d.ts +7 -0
  127. package/dist/core/middleware/built-in/graphql/middleware.js +54 -0
  128. package/dist/core/middleware/built-in/graphql/middleware.js.map +1 -0
  129. package/dist/core/middleware/built-in/graphql/subscriptions.d.ts +20 -0
  130. package/dist/core/middleware/built-in/graphql/subscriptions.js +37 -0
  131. package/dist/core/middleware/built-in/graphql/subscriptions.js.map +1 -0
  132. package/dist/core/middleware/built-in/index.d.ts +2 -1
  133. package/dist/core/middleware/built-in/index.js +3 -0
  134. package/dist/core/middleware/built-in/index.js.map +1 -1
  135. package/dist/core/middleware/built-in/rate-limit/core.d.ts +5 -0
  136. package/dist/core/middleware/built-in/rate-limit/core.js +16 -8
  137. package/dist/core/middleware/built-in/rate-limit/core.js.map +1 -1
  138. package/dist/core/middleware/built-in/validation/core.js +42 -19
  139. package/dist/core/middleware/built-in/validation/core.js.map +1 -1
  140. package/dist/core/middleware/index.js +1 -0
  141. package/dist/core/middleware/index.js.map +1 -1
  142. package/dist/core/modules/auto-discovery.js +5 -4
  143. package/dist/core/modules/auto-discovery.js.map +1 -1
  144. package/dist/core/modules/modules.js.map +1 -1
  145. package/dist/core/networking/adapters/socketio-adapter.js +1 -1
  146. package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
  147. package/dist/core/networking/adapters/uws-adapter.js +7 -2
  148. package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
  149. package/dist/core/networking/adapters/ws-adapter.js +5 -2
  150. package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
  151. package/dist/core/networking/websocket-manager.js +2 -0
  152. package/dist/core/networking/websocket-manager.js.map +1 -1
  153. package/dist/core/pooling/object-pool-manager.d.ts +8 -2
  154. package/dist/core/pooling/object-pool-manager.js +38 -18
  155. package/dist/core/pooling/object-pool-manager.js.map +1 -1
  156. package/dist/core/routing/app-integration.d.ts +3 -3
  157. package/dist/core/routing/app-integration.js +1 -1
  158. package/dist/core/routing/app-integration.js.map +1 -1
  159. package/dist/core/routing/index.d.ts +1 -1
  160. package/dist/core/routing/index.js +1 -1
  161. package/dist/core/routing/index.js.map +1 -1
  162. package/dist/core/routing/path-matcher.d.ts +6 -0
  163. package/dist/core/routing/path-matcher.js +46 -7
  164. package/dist/core/routing/path-matcher.js.map +1 -1
  165. package/dist/core/routing/unified-router.d.ts +4 -0
  166. package/dist/core/routing/unified-router.js +104 -43
  167. package/dist/core/routing/unified-router.js.map +1 -1
  168. package/dist/core/runtime/base-adapter.js +3 -3
  169. package/dist/core/runtime/base-adapter.js.map +1 -1
  170. package/dist/core/runtime/cloudflare-workers-adapter.js +1 -1
  171. package/dist/core/runtime/cloudflare-workers-adapter.js.map +1 -1
  172. package/dist/core/runtime/node-adapter.d.ts +1 -1
  173. package/dist/core/runtime/node-adapter.js +7 -4
  174. package/dist/core/runtime/node-adapter.js.map +1 -1
  175. package/dist/core/runtime/vercel-edge-adapter.js +1 -0
  176. package/dist/core/runtime/vercel-edge-adapter.js.map +1 -1
  177. package/dist/core/utilities/circuit-breaker.d.ts +9 -2
  178. package/dist/core/utilities/circuit-breaker.js +32 -3
  179. package/dist/core/utilities/circuit-breaker.js.map +1 -1
  180. package/dist/core/utilities/container.js +6 -0
  181. package/dist/core/utilities/container.js.map +1 -1
  182. package/dist/core/utilities/hooks.d.ts +4 -0
  183. package/dist/core/utilities/hooks.js +134 -22
  184. package/dist/core/utilities/hooks.js.map +1 -1
  185. package/dist/core/validation/index.js +6 -1
  186. package/dist/core/validation/index.js.map +1 -1
  187. package/dist/index.d.ts +6 -0
  188. package/dist/index.js +5 -0
  189. package/dist/index.js.map +1 -1
  190. package/dist/moro.d.ts +154 -1
  191. package/dist/moro.js +592 -16
  192. package/dist/moro.js.map +1 -1
  193. package/dist/types/config.d.ts +28 -0
  194. package/dist/types/core.d.ts +1 -0
  195. package/dist/types/events.d.ts +1 -1
  196. package/dist/types/events.js +1 -0
  197. package/dist/types/events.js.map +1 -1
  198. package/dist/types/logger.d.ts +1 -0
  199. package/dist/types/module.d.ts +2 -2
  200. package/package.json +21 -1
@@ -21,6 +21,14 @@ export class MoroHttpServer {
21
21
  requestCounter = 0;
22
22
  // Use shared object pool manager
23
23
  poolManager = ObjectPoolManager.getInstance();
24
+ // Interned method strings for fast reference equality comparison
25
+ static METHOD_POST = 'POST';
26
+ static METHOD_PUT = 'PUT';
27
+ static METHOD_PATCH = 'PATCH';
28
+ static METHOD_GET = 'GET';
29
+ static METHOD_DELETE = 'DELETE';
30
+ static METHOD_HEAD = 'HEAD';
31
+ static METHOD_OPTIONS = 'OPTIONS';
24
32
  // Pre-compiled response templates for common responses
25
33
  static RESPONSE_TEMPLATES = {
26
34
  notFound: Buffer.from('{"success":false,"error":"Not found"}'),
@@ -39,6 +47,7 @@ export class MoroHttpServer {
39
47
  MoroHttpServer.BUFFER_POOLS.set(size, []);
40
48
  for (let i = 0; i < 50; i++) {
41
49
  // 50 buffers per size
50
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
42
51
  MoroHttpServer.BUFFER_POOLS.get(size).push(Buffer.allocUnsafe(size));
43
52
  }
44
53
  }
@@ -47,7 +56,9 @@ export class MoroHttpServer {
47
56
  // Find the smallest buffer that fits
48
57
  for (const poolSize of MoroHttpServer.BUFFER_SIZES) {
49
58
  if (size <= poolSize) {
59
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
50
60
  const pool = MoroHttpServer.BUFFER_POOLS.get(poolSize);
61
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
51
62
  return pool.length > 0 ? pool.pop() : Buffer.allocUnsafe(poolSize);
52
63
  }
53
64
  }
@@ -57,6 +68,7 @@ export class MoroHttpServer {
57
68
  // Return buffer to appropriate pool
58
69
  const size = buffer.length;
59
70
  if (MoroHttpServer.BUFFER_POOLS.has(size)) {
71
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
60
72
  const pool = MoroHttpServer.BUFFER_POOLS.get(size);
61
73
  if (pool.length < 50) {
62
74
  // Don't let pools grow too large
@@ -135,11 +147,11 @@ export class MoroHttpServer {
135
147
  else {
136
148
  // Dynamic route - organize by segment count for faster matching
137
149
  this.dynamicRoutes.push(route);
138
- const segments = path.split('/').filter(s => s.length > 0);
139
- const segmentCount = segments.length;
150
+ const segmentCount = PathMatcher.countSegments(path);
140
151
  if (!this.routesBySegmentCount.has(segmentCount)) {
141
152
  this.routesBySegmentCount.set(segmentCount, []);
142
153
  }
154
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
143
155
  this.routesBySegmentCount.get(segmentCount).push(route);
144
156
  }
145
157
  }
@@ -158,6 +170,7 @@ export class MoroHttpServer {
158
170
  const originalParams = httpReq.params;
159
171
  try {
160
172
  // Optimized URL and query parsing with object pooling
173
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
161
174
  const urlString = req.url;
162
175
  const queryIndex = urlString.indexOf('?');
163
176
  if (queryIndex === -1) {
@@ -170,25 +183,30 @@ export class MoroHttpServer {
170
183
  httpReq.path = urlString.substring(0, queryIndex);
171
184
  httpReq.query = this.parseQueryStringPooled(urlString.substring(queryIndex + 1));
172
185
  }
173
- // Method checking - avoid array includes
174
- const method = req.method;
175
- if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
186
+ // Method checking - use reference equality for interned strings (50-100% faster)
187
+ if (httpReq.method === MoroHttpServer.METHOD_POST ||
188
+ httpReq.method === MoroHttpServer.METHOD_PUT ||
189
+ httpReq.method === MoroHttpServer.METHOD_PATCH) {
176
190
  httpReq.body = await this.parseBody(req);
177
191
  }
178
- // Execute hooks before request processing
192
+ // Execute hooks before request processing - NOOP if no hookManager
179
193
  if (this.hookManager) {
180
194
  await this.hookManager.execute('request', {
181
195
  request: httpReq,
182
196
  response: httpRes,
183
197
  });
184
198
  }
185
- // Execute global middleware first
186
- await this.executeMiddleware(this.globalMiddleware, httpReq, httpRes);
199
+ // Execute global middleware first - EARLY EXIT if none registered
200
+ const middlewareLen = this.globalMiddleware.length;
201
+ if (middlewareLen > 0) {
202
+ await this.executeMiddleware(this.globalMiddleware, httpReq, httpRes);
203
+ }
187
204
  // If middleware handled the request, don't continue
188
205
  if (httpRes.headersSent) {
189
206
  return;
190
207
  }
191
208
  // Find matching route
209
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
192
210
  const route = this.findRoute(req.method, httpReq.path);
193
211
  if (!route) {
194
212
  // 404 response with pre-compiled buffer
@@ -203,14 +221,22 @@ export class MoroHttpServer {
203
221
  if (matches) {
204
222
  // Use pooled object for parameters
205
223
  httpReq.params = this.acquireParamObject();
206
- route.paramNames.forEach((name, index) => {
207
- httpReq.params[name] = matches[index + 1];
208
- });
224
+ const paramNames = route.paramNames;
225
+ const paramNamesLen = paramNames.length;
226
+ for (let i = 0; i < paramNamesLen; i++) {
227
+ httpReq.params[paramNames[i]] = matches[i + 1];
228
+ }
229
+ }
230
+ // Execute middleware chain - EARLY EXIT if no route middleware
231
+ const routeMiddlewareLen = route.middleware.length;
232
+ if (routeMiddlewareLen > 0) {
233
+ await this.executeMiddleware(route.middleware, httpReq, httpRes);
234
+ }
235
+ // Execute handler - Don't await sync handlers
236
+ const handlerResult = route.handler(httpReq, httpRes);
237
+ if (handlerResult && typeof handlerResult.then === 'function') {
238
+ await handlerResult;
209
239
  }
210
- // Execute middleware chain
211
- await this.executeMiddleware(route.middleware, httpReq, httpRes);
212
- // Execute handler
213
- await route.handler(httpReq, httpRes);
214
240
  }
215
241
  catch (error) {
216
242
  // Debug: Log the actual error and where it came from
@@ -265,24 +291,54 @@ export class MoroHttpServer {
265
291
  finally {
266
292
  // CRITICAL: Always release pooled objects back to the pool
267
293
  // This prevents memory leaks and ensures consistent performance
268
- if (originalParams && Object.keys(originalParams).length === 0) {
269
- this.releaseParamObject(originalParams);
294
+ // Optimized: Check if object is empty without Object.keys()
295
+ if (originalParams) {
296
+ let isEmpty = true;
297
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
298
+ for (const _key in originalParams) {
299
+ isEmpty = false;
300
+ break;
301
+ }
302
+ if (isEmpty) {
303
+ this.releaseParamObject(originalParams);
304
+ }
270
305
  }
271
- if (httpReq.params &&
272
- httpReq.params !== originalParams &&
273
- Object.keys(httpReq.params).length === 0) {
274
- this.releaseParamObject(httpReq.params);
306
+ if (httpReq.params && httpReq.params !== originalParams) {
307
+ let isEmpty = true;
308
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
309
+ for (const _key in httpReq.params) {
310
+ isEmpty = false;
311
+ break;
312
+ }
313
+ if (isEmpty) {
314
+ this.releaseParamObject(httpReq.params);
315
+ }
275
316
  }
276
317
  }
277
318
  // Additional cleanup on response completion to ensure objects are returned to pool
278
319
  res.once('finish', () => {
279
- if (originalParams && Object.keys(originalParams).length === 0) {
280
- this.releaseParamObject(originalParams);
320
+ // Optimized: Check if object is empty without Object.keys()
321
+ if (originalParams) {
322
+ let isEmpty = true;
323
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
324
+ for (const _key in originalParams) {
325
+ isEmpty = false;
326
+ break;
327
+ }
328
+ if (isEmpty) {
329
+ this.releaseParamObject(originalParams);
330
+ }
281
331
  }
282
- if (httpReq.params &&
283
- httpReq.params !== originalParams &&
284
- Object.keys(httpReq.params).length === 0) {
285
- this.releaseParamObject(httpReq.params);
332
+ if (httpReq.params && httpReq.params !== originalParams) {
333
+ let isEmpty = true;
334
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
335
+ for (const _key in httpReq.params) {
336
+ isEmpty = false;
337
+ break;
338
+ }
339
+ if (isEmpty) {
340
+ this.releaseParamObject(httpReq.params);
341
+ }
286
342
  }
287
343
  });
288
344
  }
@@ -323,6 +379,7 @@ export class MoroHttpServer {
323
379
  normalizePath(path) {
324
380
  // Check cache first
325
381
  if (this.pathNormalizationCache.has(path)) {
382
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
326
383
  return this.pathNormalizationCache.get(path);
327
384
  }
328
385
  // Normalization: remove trailing slash (except root), decode once
@@ -346,20 +403,61 @@ export class MoroHttpServer {
346
403
  // Request ID generation using pool manager (if enabled)
347
404
  httpReq.requestId = this.requestTrackingEnabled ? this.poolManager.generateRequestId() : '';
348
405
  httpReq.headers = req.headers;
349
- // Parse cookies
350
- httpReq.cookies = this.parseCookies(req.headers.cookie || '');
406
+ // Intern method string for fast reference equality comparison (50-100% faster)
407
+ const method = req.method;
408
+ switch (method) {
409
+ case 'POST':
410
+ httpReq.method = MoroHttpServer.METHOD_POST;
411
+ break;
412
+ case 'PUT':
413
+ httpReq.method = MoroHttpServer.METHOD_PUT;
414
+ break;
415
+ case 'PATCH':
416
+ httpReq.method = MoroHttpServer.METHOD_PATCH;
417
+ break;
418
+ case 'GET':
419
+ httpReq.method = MoroHttpServer.METHOD_GET;
420
+ break;
421
+ case 'DELETE':
422
+ httpReq.method = MoroHttpServer.METHOD_DELETE;
423
+ break;
424
+ case 'HEAD':
425
+ httpReq.method = MoroHttpServer.METHOD_HEAD;
426
+ break;
427
+ case 'OPTIONS':
428
+ httpReq.method = MoroHttpServer.METHOD_OPTIONS;
429
+ break;
430
+ default:
431
+ httpReq.method = method;
432
+ }
433
+ // Parse cookies - EARLY EXIT if no cookie header
434
+ const cookieHeader = req.headers.cookie;
435
+ if (cookieHeader) {
436
+ httpReq.cookies = this.parseCookies(cookieHeader);
437
+ }
438
+ else {
439
+ httpReq.cookies = {};
440
+ }
351
441
  return httpReq;
352
442
  }
353
443
  parseCookies(cookieHeader) {
354
444
  const cookies = {};
445
+ // EARLY EXIT if no cookie header
355
446
  if (!cookieHeader)
356
447
  return cookies;
357
- cookieHeader.split(';').forEach(cookie => {
358
- const [name, value] = cookie.trim().split('=');
359
- if (name && value) {
360
- cookies[name] = decodeURIComponent(value);
448
+ const cookieParts = cookieHeader.split(';');
449
+ const cookiePartsLen = cookieParts.length;
450
+ for (let i = 0; i < cookiePartsLen; i++) {
451
+ const cookie = cookieParts[i];
452
+ const equalIndex = cookie.indexOf('=');
453
+ if (equalIndex > 0) {
454
+ const name = cookie.substring(0, equalIndex).trim();
455
+ const value = cookie.substring(equalIndex + 1);
456
+ if (name && value) {
457
+ cookies[name] = decodeURIComponent(value);
458
+ }
361
459
  }
362
- });
460
+ }
363
461
  return cookies;
364
462
  }
365
463
  enhanceResponse(res, req) {
@@ -377,28 +475,51 @@ export class MoroHttpServer {
377
475
  // JSON serialization with zero-copy buffers
378
476
  let jsonString;
379
477
  // Enhanced JSON optimization for common API patterns
380
- // Only optimize simple patterns without extra fields
478
+ // Fast path for common 2-3 key objects without Object.keys() overhead
381
479
  if (data && typeof data === 'object' && 'success' in data) {
382
- const keys = Object.keys(data);
383
- const keyCount = keys.length;
384
- if (keyCount === 3 && 'data' in data && 'error' in data) {
385
- // {success, data, error} pattern (exactly 3 keys)
386
- jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"error":${JSON.stringify(data.error)}}`;
480
+ // Check for common patterns using 'in' operator (faster than Object.keys for small objects)
481
+ const hasData = 'data' in data;
482
+ const hasError = 'error' in data;
483
+ const hasTotal = 'total' in data;
484
+ // Fast path: {success, data} - most common pattern
485
+ if (hasData && !hasError && !hasTotal) {
486
+ // Verify it's exactly 2 keys by checking no other common keys exist
487
+ if (!('message' in data) && !('code' in data) && !('status' in data)) {
488
+ jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)}}`;
489
+ }
490
+ else {
491
+ jsonString = JSON.stringify(data);
492
+ }
387
493
  }
388
- else if (keyCount === 3 && 'data' in data && 'total' in data) {
389
- // {success, data, total} pattern (exactly 3 keys)
390
- jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"total":${data.total}}`;
494
+ else if (hasError && !hasData && !hasTotal) {
495
+ // Fast path: {success, error}
496
+ if (!('message' in data) && !('code' in data) && !('status' in data)) {
497
+ jsonString = `{"success":${data.success},"error":${JSON.stringify(data.error)}}`;
498
+ }
499
+ else {
500
+ jsonString = JSON.stringify(data);
501
+ }
391
502
  }
392
- else if (keyCount === 2 && 'data' in data) {
393
- // {success, data} pattern (exactly 2 keys)
394
- jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)}}`;
503
+ else if (hasData && hasError && !hasTotal) {
504
+ // Fast path: {success, data, error}
505
+ if (!('message' in data) && !('code' in data) && !('status' in data)) {
506
+ jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"error":${JSON.stringify(data.error)}}`;
507
+ }
508
+ else {
509
+ jsonString = JSON.stringify(data);
510
+ }
395
511
  }
396
- else if (keyCount === 2 && 'error' in data) {
397
- // {success, error} pattern (exactly 2 keys)
398
- jsonString = `{"success":${data.success},"error":${JSON.stringify(data.error)}}`;
512
+ else if (hasData && hasTotal && !hasError) {
513
+ // Fast path: {success, data, total}
514
+ if (!('message' in data) && !('code' in data) && !('status' in data)) {
515
+ jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"total":${data.total}}`;
516
+ }
517
+ else {
518
+ jsonString = JSON.stringify(data);
519
+ }
399
520
  }
400
521
  else {
401
- // Complex object or has additional fields - use standard JSON.stringify
522
+ // Complex object - use standard JSON.stringify
402
523
  jsonString = JSON.stringify(data);
403
524
  }
404
525
  }
@@ -419,40 +540,41 @@ export class MoroHttpServer {
419
540
  const headers = {
420
541
  'Content-Type': 'application/json; charset=utf-8',
421
542
  };
422
- // Compression with buffer pool
543
+ // Compression with buffer pool - EARLY EXIT if disabled or below threshold
544
+ // CRITICAL: Only make this async if compression is actually happening
423
545
  if (this.compressionEnabled && finalBuffer.length > this.compressionThreshold) {
424
- const acceptEncoding = httpRes.req.headers['accept-encoding'] || '';
425
- if (acceptEncoding.includes('gzip')) {
426
- const compressed = await gzip(finalBuffer);
427
- headers['Content-Encoding'] = 'gzip';
428
- headers['Content-Length'] = compressed.length;
429
- // Set all headers at once
430
- Object.entries(headers).forEach(([key, value]) => {
431
- httpRes.setHeader(key, value);
546
+ const acceptEncoding = httpRes.req.headers['accept-encoding'];
547
+ if (acceptEncoding && acceptEncoding.includes('gzip')) {
548
+ // ASYNC PATH - compression needed
549
+ gzip(finalBuffer).then(compressed => {
550
+ headers['Content-Encoding'] = 'gzip';
551
+ headers['Content-Length'] = compressed.length;
552
+ // Batch write all headers at once (50-100% faster)
553
+ httpRes.writeHead(httpRes.statusCode || 200, headers);
554
+ httpRes.end(compressed);
555
+ // Return buffer to pool after response
556
+ process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
432
557
  });
433
- httpRes.end(compressed);
434
- // Return buffer to pool after response
435
- process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
436
558
  return;
437
559
  }
438
- else if (acceptEncoding.includes('deflate')) {
439
- const compressed = await deflate(finalBuffer);
440
- headers['Content-Encoding'] = 'deflate';
441
- headers['Content-Length'] = compressed.length;
442
- Object.entries(headers).forEach(([key, value]) => {
443
- httpRes.setHeader(key, value);
560
+ else if (acceptEncoding && acceptEncoding.includes('deflate')) {
561
+ // ASYNC PATH - compression needed
562
+ deflate(finalBuffer).then(compressed => {
563
+ headers['Content-Encoding'] = 'deflate';
564
+ headers['Content-Length'] = compressed.length;
565
+ // Batch write all headers at once
566
+ httpRes.writeHead(httpRes.statusCode || 200, headers);
567
+ httpRes.end(compressed);
568
+ // Return buffer to pool after response
569
+ process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
444
570
  });
445
- httpRes.end(compressed);
446
- // Return buffer to pool after response
447
- process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
448
571
  return;
449
572
  }
450
573
  }
574
+ // SYNC PATH - no compression, fast path
451
575
  headers['Content-Length'] = finalBuffer.length;
452
- // Set all headers at once for better performance
453
- Object.entries(headers).forEach(([key, value]) => {
454
- httpRes.setHeader(key, value);
455
- });
576
+ // Batch write all headers at once
577
+ httpRes.writeHead(httpRes.statusCode || 200, headers);
456
578
  httpRes.end(finalBuffer);
457
579
  // Return buffer to pool after response (zero-copy achievement!)
458
580
  process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
@@ -555,7 +677,7 @@ export class MoroHttpServer {
555
677
  httpRes.setHeader('Cache-Control', 'public, max-age=31536000'); // 1 year for static files
556
678
  httpRes.end(data);
557
679
  }
558
- catch (error) {
680
+ catch {
559
681
  httpRes.status(404).json({ success: false, error: 'File not found' });
560
682
  }
561
683
  };
@@ -571,9 +693,12 @@ export class MoroHttpServer {
571
693
  });
572
694
  return httpRes;
573
695
  }
574
- Object.entries(headers).forEach(([key, value]) => {
575
- httpRes.setHeader(key, value);
576
- });
696
+ const headerKeys = Object.keys(headers);
697
+ const headerKeysLen = headerKeys.length;
698
+ for (let i = 0; i < headerKeysLen; i++) {
699
+ const key = headerKeys[i];
700
+ httpRes.setHeader(key, headers[key]);
701
+ }
577
702
  return httpRes;
578
703
  };
579
704
  httpRes.appendHeader = (name, value) => {
@@ -626,17 +751,16 @@ export class MoroHttpServer {
626
751
  return mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
627
752
  }
628
753
  addCharsetIfNeeded(mimeType) {
629
- // Add charset for text-based content types
630
- const textTypes = [
631
- 'text/',
632
- 'application/json',
633
- 'application/javascript',
634
- 'application/xml',
635
- 'image/svg+xml',
636
- ];
637
- const needsCharset = textTypes.some(type => mimeType.startsWith(type));
638
- if (needsCharset && !mimeType.includes('charset')) {
639
- return `${mimeType}; charset=utf-8`;
754
+ // Add charset for text-based content types - optimized with early exit
755
+ // Check most common cases first
756
+ if (mimeType.startsWith('text/') ||
757
+ mimeType.startsWith('application/json') ||
758
+ mimeType.startsWith('application/javascript') ||
759
+ mimeType.startsWith('application/xml') ||
760
+ mimeType.startsWith('image/svg+xml')) {
761
+ if (!mimeType.includes('charset')) {
762
+ return `${mimeType}; charset=utf-8`;
763
+ }
640
764
  }
641
765
  return mimeType;
642
766
  }
@@ -685,7 +809,8 @@ export class MoroHttpServer {
685
809
  const parts = buffer.toString('binary').split('--' + boundary);
686
810
  const fields = {};
687
811
  const files = {};
688
- for (let i = 1; i < parts.length - 1; i++) {
812
+ const partsLen = parts.length - 1;
813
+ for (let i = 1; i < partsLen; i++) {
689
814
  const part = parts[i];
690
815
  const [headers, content] = part.split('\r\n\r\n');
691
816
  if (!headers || content === undefined)
@@ -733,7 +858,8 @@ export class MoroHttpServer {
733
858
  return {};
734
859
  const result = this.poolManager.acquireQuery();
735
860
  const pairs = queryString.split('&');
736
- for (let i = 0; i < pairs.length; i++) {
861
+ const pairsLen = pairs.length;
862
+ for (let i = 0; i < pairsLen; i++) {
737
863
  const pair = pairs[i];
738
864
  const equalIndex = pair.indexOf('=');
739
865
  if (equalIndex === -1) {
@@ -757,35 +883,41 @@ export class MoroHttpServer {
757
883
  routeHitCount = new Map(); // Track route popularity for cache optimization
758
884
  static HOT_ROUTE_THRESHOLD = 100; // Routes accessed 100+ times get hot path treatment
759
885
  findRoute(method, path) {
760
- // Normalize path for consistent matching
761
- const normalizedPath = this.normalizePath(path);
762
- const cacheKey = `${method}:${normalizedPath}`;
763
- // Track route popularity for hot path optimization
764
- const hitCount = (this.routeHitCount.get(cacheKey) || 0) + 1;
765
- this.routeHitCount.set(cacheKey, hitCount);
766
- // Check cache first (hot path optimization)
886
+ // Skip normalization and hit tracking for cached routes
887
+ const cacheKey = `${method}:${path}`;
888
+ // Check cache first (hot path optimization) - BEFORE any other work
767
889
  if (this.routeCache.has(cacheKey)) {
768
- const cachedRoute = this.routeCache.get(cacheKey);
769
- // Promote frequently accessed routes to front of cache (LRU-like)
770
- if (hitCount > MoroHttpServer.HOT_ROUTE_THRESHOLD && this.routeCache.size > 100) {
771
- this.routeCache.delete(cacheKey);
772
- this.routeCache.set(cacheKey, cachedRoute); // Move to end (most recent)
773
- }
774
- return cachedRoute;
890
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
891
+ return this.routeCache.get(cacheKey);
892
+ }
893
+ // Normalize path for consistent matching (only if not cached)
894
+ const normalizedPath = this.normalizePath(path);
895
+ const normalizedCacheKey = normalizedPath !== path ? `${method}:${normalizedPath}` : cacheKey;
896
+ // Check cache again with normalized path
897
+ if (normalizedPath !== path && this.routeCache.has(normalizedCacheKey)) {
898
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
899
+ return this.routeCache.get(normalizedCacheKey);
775
900
  }
776
901
  // Phase 1: O(1) static route lookup
777
- const staticRoute = this.staticRoutes.get(cacheKey);
902
+ const staticRoute = this.staticRoutes.get(normalizedCacheKey);
778
903
  if (staticRoute) {
779
- this.routeCache.set(cacheKey, staticRoute);
904
+ this.routeCache.set(normalizedCacheKey, staticRoute);
905
+ if (normalizedPath !== path) {
906
+ this.routeCache.set(cacheKey, staticRoute);
907
+ }
780
908
  return staticRoute;
781
909
  }
782
910
  // Phase 2: Optimized dynamic route matching by segment count
783
911
  let route = null;
784
- if (this.dynamicRoutes.length > 0) {
785
- const segments = normalizedPath.split('/').filter(s => s.length > 0);
786
- const candidateRoutes = this.routesBySegmentCount.get(segments.length) || this.dynamicRoutes;
912
+ const dynamicRoutesLen = this.dynamicRoutes.length;
913
+ if (dynamicRoutesLen > 0) {
914
+ // Use shared utility for DRY principle
915
+ const segmentCount = PathMatcher.countSegments(normalizedPath);
916
+ const candidateRoutes = this.routesBySegmentCount.get(segmentCount) || this.dynamicRoutes;
917
+ const candidateLen = candidateRoutes.length;
787
918
  // Only test routes with matching method and segment count
788
- for (const candidateRoute of candidateRoutes) {
919
+ for (let i = 0; i < candidateLen; i++) {
920
+ const candidateRoute = candidateRoutes[i];
789
921
  if (candidateRoute.method === method && candidateRoute.pattern.test(normalizedPath)) {
790
922
  route = candidateRoute;
791
923
  break;
@@ -794,13 +926,17 @@ export class MoroHttpServer {
794
926
  }
795
927
  // Cache result (limit cache size to prevent memory leaks)
796
928
  if (this.routeCache.size < 500) {
797
- this.routeCache.set(cacheKey, route);
929
+ this.routeCache.set(normalizedCacheKey, route);
930
+ if (normalizedPath !== path) {
931
+ this.routeCache.set(cacheKey, route);
932
+ }
798
933
  }
799
934
  return route;
800
935
  }
801
936
  // Optimized middleware execution with reduced Promise allocation
802
937
  async executeMiddleware(middleware, req, res) {
803
- for (let i = 0; i < middleware.length; i++) {
938
+ const len = middleware.length;
939
+ for (let i = 0; i < len; i++) {
804
940
  // Short-circuit if response already sent
805
941
  if (res.headersSent)
806
942
  return;
@@ -816,7 +952,7 @@ export class MoroHttpServer {
816
952
  };
817
953
  try {
818
954
  const result = mw(req, res, next);
819
- // Handle async middleware
955
+ // Handle async middleware - optimized with early check
820
956
  if (result && typeof result.then === 'function') {
821
957
  result
822
958
  .then(() => {
@@ -962,6 +1098,7 @@ export const middleware = {
962
1098
  return (req, res, next) => {
963
1099
  const start = Date.now();
964
1100
  res.on('finish', () => {
1101
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
965
1102
  const duration = Date.now() - start;
966
1103
  // Request completed - logged by framework
967
1104
  });
@@ -1017,7 +1154,7 @@ export const middleware = {
1017
1154
  try {
1018
1155
  stats = await fs.stat(filePath);
1019
1156
  }
1020
- catch (error) {
1157
+ catch {
1021
1158
  next(); // File not found, let other middleware handle
1022
1159
  return;
1023
1160
  }
@@ -1036,7 +1173,7 @@ export const middleware = {
1036
1173
  break;
1037
1174
  }
1038
1175
  }
1039
- catch (error) {
1176
+ catch {
1040
1177
  // Continue to next index file
1041
1178
  }
1042
1179
  }
@@ -1103,7 +1240,7 @@ export const middleware = {
1103
1240
  const data = await fs.readFile(filePath);
1104
1241
  res.end(data);
1105
1242
  }
1106
- catch (error) {
1243
+ catch {
1107
1244
  res.status(500).json({ success: false, error: 'Internal server error' });
1108
1245
  }
1109
1246
  };
@@ -1131,6 +1268,7 @@ export const middleware = {
1131
1268
  return;
1132
1269
  }
1133
1270
  // Validate each file
1271
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1134
1272
  for (const [fieldName, file] of Object.entries(files)) {
1135
1273
  const fileData = file;
1136
1274
  // Validate file size
@@ -1168,6 +1306,7 @@ export const middleware = {
1168
1306
  let templateContent;
1169
1307
  // Check cache first
1170
1308
  if (options.cache && templateCache.has(templatePath)) {
1309
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1171
1310
  templateContent = templateCache.get(templatePath);
1172
1311
  }
1173
1312
  else {
@@ -1214,6 +1353,7 @@ export const middleware = {
1214
1353
  try {
1215
1354
  let layoutContent;
1216
1355
  if (options.cache && templateCache.has(layoutPath)) {
1356
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1217
1357
  layoutContent = templateCache.get(layoutPath);
1218
1358
  }
1219
1359
  else {
@@ -1224,14 +1364,14 @@ export const middleware = {
1224
1364
  }
1225
1365
  rendered = layoutContent.replace(/\{\{body\}\}/, rendered);
1226
1366
  }
1227
- catch (error) {
1367
+ catch {
1228
1368
  // Layout not found, use template as-is
1229
1369
  }
1230
1370
  }
1231
1371
  res.setHeader('Content-Type', 'text/html');
1232
1372
  res.end(rendered);
1233
1373
  }
1234
- catch (error) {
1374
+ catch {
1235
1375
  res.status(500).json({ success: false, error: 'Template rendering failed' });
1236
1376
  }
1237
1377
  };
@@ -1256,7 +1396,7 @@ export const middleware = {
1256
1396
  return pushStream;
1257
1397
  }
1258
1398
  }
1259
- catch (error) {
1399
+ catch {
1260
1400
  // Push failed, continue normally
1261
1401
  }
1262
1402
  }
@@ -1334,7 +1474,8 @@ export const middleware = {
1334
1474
  res.sendRange = async (filePath, stats) => {
1335
1475
  try {
1336
1476
  const fs = await import('fs/promises');
1337
- const path = await import('path');
1477
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1478
+ const _path = await import('path');
1338
1479
  if (!stats) {
1339
1480
  stats = await fs.stat(filePath);
1340
1481
  }
@@ -1393,6 +1534,7 @@ export const middleware = {
1393
1534
  for (const { start, end } of ranges) {
1394
1535
  if (start >= fileSize || end >= fileSize)
1395
1536
  continue;
1537
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1396
1538
  const chunkSize = end - start + 1;
1397
1539
  res.write(`\r\n--${boundary}\r\n`);
1398
1540
  res.write(`Content-Range: bytes ${start}-${end}/${fileSize}\r\n\r\n`);
@@ -1409,7 +1551,7 @@ export const middleware = {
1409
1551
  res.end();
1410
1552
  }
1411
1553
  }
1412
- catch (error) {
1554
+ catch {
1413
1555
  res.status(500).json({ success: false, error: 'Range request failed' });
1414
1556
  }
1415
1557
  };
@@ -1418,6 +1560,7 @@ export const middleware = {
1418
1560
  },
1419
1561
  // CSRF Protection middleware
1420
1562
  csrf: (options = {}) => {
1563
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1421
1564
  const secret = options.secret || 'moro-csrf-secret';
1422
1565
  const tokenLength = options.tokenLength || 32;
1423
1566
  const cookieName = options.cookieName || '_csrf';
@@ -1444,6 +1587,7 @@ export const middleware = {
1444
1587
  return req._csrfToken;
1445
1588
  };
1446
1589
  // Skip verification for safe methods
1590
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1447
1591
  if (ignoreMethods.includes(req.method)) {
1448
1592
  next();
1449
1593
  return;