qhttpx 1.9.3 → 2.0.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.
Files changed (98) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +17 -12
  3. package/dist/examples/api-server.js +38 -35
  4. package/dist/examples/basic.js +3 -4
  5. package/dist/examples/compression.js +6 -8
  6. package/dist/examples/cors.js +5 -6
  7. package/dist/examples/errors.js +12 -11
  8. package/dist/examples/file-upload.js +4 -6
  9. package/dist/examples/fusion.js +6 -6
  10. package/dist/examples/rate-limiting.js +10 -10
  11. package/dist/examples/validation.js +5 -6
  12. package/dist/examples/websockets.js +3 -4
  13. package/dist/package.json +3 -8
  14. package/dist/src/benchmarks/quantam-users.js +2 -2
  15. package/dist/src/benchmarks/quick-bench.js +57 -0
  16. package/dist/src/benchmarks/simple-json.js +133 -22
  17. package/dist/src/benchmarks/ultra-mode.js +8 -38
  18. package/dist/src/core/context-pool.d.ts +12 -0
  19. package/dist/src/core/context-pool.js +34 -0
  20. package/dist/src/core/fusion.js +0 -2
  21. package/dist/src/core/metrics.d.ts +1 -0
  22. package/dist/src/core/metrics.js +3 -0
  23. package/dist/src/core/scheduler.d.ts +4 -0
  24. package/dist/src/core/scheduler.js +75 -34
  25. package/dist/src/core/scope.d.ts +23 -8
  26. package/dist/src/core/scope.js +53 -14
  27. package/dist/src/core/serializer.d.ts +1 -1
  28. package/dist/src/core/serializer.js +45 -7
  29. package/dist/src/core/server.d.ts +51 -10
  30. package/dist/src/core/server.js +695 -259
  31. package/dist/src/core/timer.d.ts +11 -0
  32. package/dist/src/core/timer.js +29 -0
  33. package/dist/src/core/types.d.ts +64 -12
  34. package/dist/src/core/types.js +6 -6
  35. package/dist/src/index.d.ts +6 -4
  36. package/dist/src/index.js +19 -18
  37. package/dist/src/middleware/presets.d.ts +1 -2
  38. package/dist/src/middleware/presets.js +1 -1
  39. package/dist/src/middleware/security.d.ts +2 -13
  40. package/dist/src/middleware/security.js +6 -1
  41. package/dist/src/router/radix-tree.d.ts +5 -2
  42. package/dist/src/router/radix-tree.js +58 -14
  43. package/dist/src/router/router.d.ts +5 -2
  44. package/dist/src/router/router.js +80 -63
  45. package/dist/src/utils/logger.d.ts +1 -11
  46. package/dist/tests/fusion.test.js +4 -4
  47. package/dist/tests/rate-limit.test.js +2 -2
  48. package/dist/tests/schema-routes.test.js +3 -1
  49. package/docs/AEGIS.md +18 -28
  50. package/docs/BENCHMARKS.md +8 -6
  51. package/docs/DATABASE.md +4 -4
  52. package/docs/MIDDLEWARE.md +3 -3
  53. package/docs/ROUTING.md +21 -13
  54. package/docs/VALIDATION.md +9 -31
  55. package/package.json +3 -8
  56. package/binding.gyp +0 -18
  57. package/dist/src/benchmarks/compare-frameworks.js +0 -119
  58. package/dist/src/benchmarks/compare.js +0 -288
  59. package/dist/src/buffer-pool.js +0 -70
  60. package/dist/src/config.js +0 -50
  61. package/dist/src/cookies.js +0 -59
  62. package/dist/src/core/native-adapter.d.ts +0 -11
  63. package/dist/src/core/native-adapter.js +0 -211
  64. package/dist/src/cors.js +0 -66
  65. package/dist/src/logger.js +0 -45
  66. package/dist/src/metrics.js +0 -111
  67. package/dist/src/native/index.d.ts +0 -32
  68. package/dist/src/native/index.js +0 -141
  69. package/dist/src/presets.js +0 -33
  70. package/dist/src/radix-router.js +0 -89
  71. package/dist/src/radix-tree.js +0 -81
  72. package/dist/src/resources.js +0 -25
  73. package/dist/src/router.js +0 -138
  74. package/dist/src/scheduler.js +0 -85
  75. package/dist/src/security.js +0 -69
  76. package/dist/src/server.js +0 -685
  77. package/dist/src/signals.js +0 -31
  78. package/dist/src/static.js +0 -107
  79. package/dist/src/stream.js +0 -71
  80. package/dist/src/tasks.js +0 -87
  81. package/dist/src/testing.js +0 -40
  82. package/dist/src/types.js +0 -19
  83. package/dist/src/utils/testing.js +0 -40
  84. package/dist/src/worker-queue.js +0 -73
  85. package/dist/tests/native-adapter.test.d.ts +0 -1
  86. package/dist/tests/native-adapter.test.js +0 -71
  87. package/prebuilds/darwin-arm64/qhttpx.node +0 -0
  88. package/prebuilds/linux-x64/qhttpx.node +0 -0
  89. package/prebuilds/win32-x64/qhttpx.node +0 -0
  90. package/scripts/install-native.js +0 -26
  91. package/src/native/README.md +0 -31
  92. package/src/native/addon.cc +0 -8
  93. package/src/native/index.ts +0 -158
  94. package/src/native/picohttpparser.c +0 -608
  95. package/src/native/picohttpparser.h +0 -76
  96. package/src/native/server.cc +0 -264
  97. package/src/native/server.h +0 -30
  98. /package/dist/src/benchmarks/{compare.d.ts → quick-bench.d.ts} +0 -0
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -21,15 +54,43 @@ const serializer_1 = require("./serializer");
21
54
  const batch_1 = require("./batch");
22
55
  const fusion_1 = require("./fusion");
23
56
  const simple_1 = require("../validation/simple");
57
+ const zod_1 = require("../validation/zod");
24
58
  const generator_1 = require("../openapi/generator");
59
+ const cors_1 = require("../middleware/cors");
60
+ const rate_limit_1 = require("../middleware/rate-limit");
61
+ const logger_1 = require("../utils/logger");
62
+ const security_1 = require("../middleware/security");
63
+ const compression_1 = require("../middleware/compression");
25
64
  const types_1 = require("./types");
26
65
  const cookies_1 = require("../utils/cookies");
66
+ const context_pool_1 = require("./context-pool");
27
67
  const scope_1 = require("./scope");
28
- const logger_1 = require("./logger");
68
+ const logger_2 = require("./logger");
69
+ const timer_1 = require("./timer");
70
+ const serializer_2 = require("./serializer");
29
71
  const EMPTY_QUERY = Object.freeze({});
72
+ const CONTENT_TYPE = {
73
+ JSON: 'application/json; charset=utf-8',
74
+ HTML: 'text/html; charset=utf-8',
75
+ PLAIN: 'text/plain; charset=utf-8',
76
+ };
30
77
  class QHTTPXContextImpl {
78
+ get params() { return this._params; }
79
+ set params(v) { this._params = v; this._dirty |= 0b0010; }
80
+ get body() { return this._body; }
81
+ set body(v) { this._body = v; this._dirty |= 0b0001; }
82
+ get files() { return this._files; }
83
+ set files(v) { this._files = v; this._dirty |= 0b10000; }
84
+ // Ultra-Simple API
85
+ httpError(status, message, details) {
86
+ return new types_1.HttpError(status, message, { details });
87
+ }
31
88
  constructor(app) {
32
89
  this._url = null;
90
+ // Dirty tracking for optimization
91
+ this._generation = 0;
92
+ this._dirty = 0;
93
+ this._customHeaders = null;
33
94
  this._app = app;
34
95
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
96
  this._appJsonSerializer = app.options.jsonSerializer;
@@ -40,6 +101,136 @@ class QHTTPXContextImpl {
40
101
  this.setCookie = this.setCookie.bind(this);
41
102
  this.render = this.render.bind(this);
42
103
  this.validate = this.validate.bind(this);
104
+ this.httpError = this.httpError.bind(this);
105
+ }
106
+ get cookies() {
107
+ if (this._dirty & 0b1000)
108
+ return this._cookies;
109
+ if (!this._cookies)
110
+ this._cookies = Object.create(null);
111
+ const header = this.req.headers.cookie;
112
+ if (header) {
113
+ this._parseCookies(header);
114
+ }
115
+ this._dirty |= 0b1000;
116
+ return this._cookies;
117
+ }
118
+ set cookies(v) {
119
+ this._cookies = v;
120
+ this._dirty |= 0b1000;
121
+ }
122
+ get query() {
123
+ if (this._dirty & 0b0100)
124
+ return this._query;
125
+ const url = this.req.url || '/';
126
+ const queryIndex = url.indexOf('?');
127
+ if (queryIndex !== -1) {
128
+ const queryString = url.slice(queryIndex + 1);
129
+ if (queryString.length > 0) {
130
+ this._parseQuery(queryString);
131
+ }
132
+ else {
133
+ if (!this._query)
134
+ this._query = Object.create(null);
135
+ }
136
+ }
137
+ else {
138
+ if (!this._query)
139
+ this._query = Object.create(null);
140
+ }
141
+ this._dirty |= 0b0100;
142
+ return this._query;
143
+ }
144
+ set query(v) {
145
+ this._query = v;
146
+ this._dirty |= 0b0100;
147
+ }
148
+ _parseQuery(queryString) {
149
+ if (!this._query)
150
+ this._query = Object.create(null);
151
+ const query = this._query;
152
+ let i = 0;
153
+ const len = queryString.length;
154
+ while (i < len) {
155
+ let keyStart = i;
156
+ let keyEnd = -1;
157
+ let valStart = -1;
158
+ let valEnd = -1;
159
+ // Find key
160
+ while (i < len) {
161
+ const c = queryString.charCodeAt(i);
162
+ if (c === 61) { // =
163
+ keyEnd = i;
164
+ valStart = i + 1;
165
+ i++;
166
+ break;
167
+ }
168
+ if (c === 38) { // &
169
+ keyEnd = i;
170
+ valEnd = i; // empty value
171
+ i++;
172
+ break;
173
+ }
174
+ i++;
175
+ }
176
+ if (keyEnd === -1) {
177
+ keyEnd = i;
178
+ valEnd = i; // empty value
179
+ }
180
+ // Find value
181
+ if (valStart !== -1) {
182
+ while (i < len) {
183
+ if (queryString.charCodeAt(i) === 38) { // &
184
+ valEnd = i;
185
+ i++;
186
+ break;
187
+ }
188
+ i++;
189
+ }
190
+ if (valEnd === -1)
191
+ valEnd = i;
192
+ }
193
+ const key = decodeURIComponent(queryString.slice(keyStart, keyEnd));
194
+ const val = valStart !== -1 ? decodeURIComponent(queryString.slice(valStart, valEnd)) : '';
195
+ if (query[key] === undefined) {
196
+ query[key] = val;
197
+ }
198
+ else if (Array.isArray(query[key])) {
199
+ query[key].push(val);
200
+ }
201
+ else {
202
+ query[key] = [query[key], val];
203
+ }
204
+ }
205
+ }
206
+ _parseCookies(header) {
207
+ const list = this._cookies;
208
+ let start = 0;
209
+ let end = header.length;
210
+ // Inline cookie parsing for speed
211
+ while (start < end) {
212
+ let eqIdx = header.indexOf('=', start);
213
+ if (eqIdx === -1)
214
+ break;
215
+ let semiIdx = header.indexOf(';', start);
216
+ if (semiIdx === -1)
217
+ semiIdx = end;
218
+ if (eqIdx < semiIdx) {
219
+ const key = header.slice(start, eqIdx).trim();
220
+ let val = header.slice(eqIdx + 1, semiIdx).trim();
221
+ // Decode if needed, simple check first
222
+ if (val.indexOf('%') !== -1) {
223
+ try {
224
+ val = decodeURIComponent(val);
225
+ }
226
+ catch { /* ignore */ }
227
+ }
228
+ if (!list[key]) {
229
+ list[key] = val;
230
+ }
231
+ }
232
+ start = semiIdx + 1;
233
+ }
43
234
  }
44
235
  get bufferPool() {
45
236
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -52,11 +243,13 @@ class QHTTPXContextImpl {
52
243
  get state() {
53
244
  if (!this._state) {
54
245
  this._state = {};
246
+ this._dirty |= 0b100000;
55
247
  }
56
248
  return this._state;
57
249
  }
58
250
  set state(v) {
59
251
  this._state = v;
252
+ this._dirty |= 0b100000;
60
253
  }
61
254
  get ip() {
62
255
  if (this._ip)
@@ -88,12 +281,12 @@ class QHTTPXContextImpl {
88
281
  body = this._appJsonSerializer(payload);
89
282
  }
90
283
  else {
91
- // Native JSON.stringify is faster for schema-less objects
92
- body = JSON.stringify(payload);
284
+ // Use optimized fast path serializer
285
+ body = (0, serializer_2.fastJsonStringify)(payload);
93
286
  }
94
287
  if (!res.headersSent) {
95
288
  res.statusCode = status;
96
- res.setHeader('content-type', 'application/json; charset=utf-8');
289
+ res.setHeader('content-type', CONTENT_TYPE.JSON);
97
290
  }
98
291
  res.end(body);
99
292
  }
@@ -108,7 +301,7 @@ class QHTTPXContextImpl {
108
301
  const res = this.res;
109
302
  if (!res.headersSent) {
110
303
  res.statusCode = status;
111
- res.setHeader('content-type', 'text/html; charset=utf-8');
304
+ res.setHeader('content-type', CONTENT_TYPE.HTML);
112
305
  }
113
306
  res.end(payload);
114
307
  }
@@ -148,7 +341,7 @@ class QHTTPXContextImpl {
148
341
  const res = this.res;
149
342
  if (!res.headersSent) {
150
343
  res.statusCode = 200;
151
- res.setHeader('content-type', 'text/html; charset=utf-8');
344
+ res.setHeader('content-type', CONTENT_TYPE.HTML);
152
345
  }
153
346
  res.end(html);
154
347
  }
@@ -164,47 +357,66 @@ class QHTTPXContextImpl {
164
357
  });
165
358
  }
166
359
  reset() {
360
+ // Tiered reset based on what was actually used
361
+ if (this._dirty & 0b0001)
362
+ this._body = undefined;
363
+ if (this._dirty & 0b0010)
364
+ this._params = null;
365
+ // Recycle query object
366
+ if (this._dirty & 0b0100) {
367
+ if (this._query) {
368
+ for (const key in this._query)
369
+ delete this._query[key];
370
+ }
371
+ }
372
+ // Recycle cookies object
373
+ if (this._dirty & 0b1000) {
374
+ if (this._cookies) {
375
+ for (const key in this._cookies)
376
+ delete this._cookies[key];
377
+ }
378
+ }
379
+ if (this._dirty & 0b10000)
380
+ this._files = undefined;
381
+ if (this._dirty & 0b100000)
382
+ this._state = undefined;
383
+ this._dirty = 0;
167
384
  this.req = null;
168
385
  this.res = null;
169
386
  this.headers = null;
170
387
  this._url = null;
171
388
  this.method = null;
172
389
  this._ip = undefined;
173
- this.params = null;
174
- this.query = null;
175
- this.body = undefined;
176
- this.files = undefined;
177
- this.cookies = null;
178
- this._state = undefined;
179
390
  this.requestId = undefined;
180
391
  this.requestStart = undefined;
181
392
  this.serializer = undefined;
182
393
  this.path = '';
183
394
  this.disableAutoEnd = false;
184
395
  this.error = undefined;
396
+ this.next = undefined;
185
397
  }
186
398
  }
187
399
  exports.QHTTPXContextImpl = QHTTPXContextImpl;
188
400
  class QHTTPX {
189
401
  constructor(options = {}) {
190
402
  this.middlewares = [];
191
- this.contextPool = [];
192
403
  this.pipelineRunner = null;
193
404
  this.onStartHooks = [];
194
405
  this.onBeforeShutdownHooks = [];
195
406
  this.onShutdownHooks = [];
196
407
  this.nextRequestId = 1;
408
+ this.lastDateNow = Date.now();
409
+ this.lastDateString = this.lastDateNow.toString(36);
197
410
  this.requestCounter = 0;
198
411
  this.options = options;
199
- this.logger = new logger_1.Logger({
412
+ this.logger = new logger_2.Logger({
200
413
  name: options.name,
201
- level: options.performanceMode === 'ultra' ? 'error' : 'info'
414
+ level: 'info'
202
415
  });
203
- this.ultraMode = options.performanceMode === 'ultra';
204
- this.errorHandler = this.ultraMode ? undefined : options.errorHandler;
205
- this.notFoundHandler = this.ultraMode ? undefined : options.notFoundHandler;
206
- this.methodNotAllowedHandler = this.ultraMode ? undefined : options.methodNotAllowedHandler;
207
- this.tracer = this.ultraMode ? undefined : options.tracer;
416
+ this.errorHandler = options.errorHandler;
417
+ this.notFoundHandler = options.notFoundHandler;
418
+ this.methodNotAllowedHandler = options.methodNotAllowedHandler;
419
+ this.tracer = options.tracer;
208
420
  this.workerCount = (0, resources_1.calculateWorkerCount)(options.workers ?? 'auto');
209
421
  this.router = new router_1.Router();
210
422
  this.wsManager = new websocket_1.WebSocketManager(this.generateRequestId.bind(this));
@@ -219,35 +431,23 @@ class QHTTPX {
219
431
  this.batchExecutor = new batch_1.BatchExecutor(options.database);
220
432
  }
221
433
  if (options.enableRequestFusion) {
222
- this.fusion = new fusion_1.RequestFusion(options.enableRequestFusion);
434
+ this._fusion = new fusion_1.RequestFusion(options.enableRequestFusion);
223
435
  }
224
436
  this.validator = options.validator ?? new simple_1.SimpleValidator();
225
- this.metrics = new metrics_1.Metrics(this.scheduler, {
226
- enabled: !this.ultraMode && (this.options.metricsEnabled ?? true),
227
- }, this.tasks, this.fusion);
228
- for (let i = 0; i < maxConcurrency; i += 1) {
229
- this.contextPool.push(this.createContext());
230
- }
437
+ this._metrics = new metrics_1.Metrics(this.scheduler, {
438
+ enabled: this.options.metricsEnabled ?? true,
439
+ }, this.tasks, this._fusion);
440
+ this.contextPool = new context_pool_1.ContextPool(() => this.createContext(), (ctx) => ctx.reset(), maxConcurrency, this.poolLimit);
231
441
  this.registerInternalRoutes();
232
442
  this.compileMiddlewarePipeline();
233
443
  this.server = http_1.default.createServer(this.handleRequest.bind(this));
234
444
  if (this.options.keepAliveTimeoutMs !== undefined) {
235
445
  this.server.keepAliveTimeout = this.options.keepAliveTimeoutMs;
236
446
  }
237
- else if (this.ultraMode) {
238
- this.server.keepAliveTimeout = 0;
239
- }
240
447
  if (this.options.headersTimeoutMs !== undefined) {
241
448
  this.server.headersTimeout = this.options.headersTimeoutMs;
242
449
  }
243
- else if (this.ultraMode) {
244
- this.server.headersTimeout = 0;
245
- }
246
- if (this.ultraMode) {
247
- this.server.requestTimeout = 0;
248
- this.server.maxHeadersCount = 0;
249
- this.server.timeout = 0;
250
- }
450
+ // Keep-alive and timeout settings handled by options above
251
451
  this.server.on('upgrade', (req, socket, head) => {
252
452
  void this.handleUpgrade(req, socket, head);
253
453
  });
@@ -304,44 +504,94 @@ class QHTTPX {
304
504
  }
305
505
  compileMiddlewarePipeline() {
306
506
  const middlewares = this.middlewares;
507
+ let runner;
307
508
  if (middlewares.length === 0) {
308
- this.pipelineRunner = async (ctx, handler) => {
509
+ runner = async (ctx, handler) => {
309
510
  const result = handler(ctx);
310
- if (result && typeof result.then === 'function') {
311
- await result;
511
+ // Handle return values
512
+ if (result instanceof Promise) {
513
+ const val = await result;
514
+ if (val !== undefined && !ctx.res.headersSent) {
515
+ if (ctx.method === 'POST' && ctx.res.statusCode === 200)
516
+ ctx.res.statusCode = 201;
517
+ if (typeof val === 'object')
518
+ ctx.json(val);
519
+ else
520
+ ctx.send(String(val));
521
+ }
522
+ }
523
+ else if (result !== undefined && !ctx.res.headersSent) {
524
+ if (ctx.method === 'POST' && ctx.res.statusCode === 200)
525
+ ctx.res.statusCode = 201;
526
+ if (typeof result === 'object')
527
+ ctx.json(result);
528
+ else
529
+ ctx.send(String(result));
312
530
  }
313
531
  };
314
- return;
315
532
  }
316
- // Flatten middleware pipeline into a single function chain
317
- // This eliminates Promise nesting, recursive dispatch overhead, and microtask backlogs
318
- // Each middleware executes directly without closure allocation overhead
319
- this.pipelineRunner = async (ctx, handler) => {
320
- let index = 0;
321
- const executeNext = async () => {
322
- if (index >= middlewares.length) {
323
- // All middlewares done, execute handler
324
- const result = handler(ctx);
533
+ else {
534
+ // Flatten middleware pipeline into a single function chain
535
+ // This eliminates Promise nesting, recursive dispatch overhead, and microtask backlogs
536
+ // Each middleware executes directly without closure allocation overhead
537
+ runner = async (ctx, handler) => {
538
+ let index = 0;
539
+ const executeNext = async () => {
540
+ if (index >= middlewares.length) {
541
+ // All middlewares done, execute handler
542
+ const result = handler(ctx);
543
+ // Handle return values (Auto-Response)
544
+ if (result instanceof Promise) {
545
+ const val = await result;
546
+ if (val !== undefined && !ctx.res.headersSent) {
547
+ if (typeof val === 'object')
548
+ ctx.json(val);
549
+ else
550
+ ctx.send(String(val));
551
+ }
552
+ }
553
+ else if (result !== undefined && !ctx.res.headersSent) {
554
+ if (typeof result === 'object')
555
+ ctx.json(result);
556
+ else
557
+ ctx.send(String(result));
558
+ }
559
+ return;
560
+ }
561
+ const currentIndex = index;
562
+ index += 1;
563
+ ctx.next = executeNext;
564
+ const result = middlewares[currentIndex](ctx, executeNext);
325
565
  if (result && typeof result.then === 'function') {
326
566
  await result;
327
567
  }
328
- return;
329
- }
330
- const currentIndex = index;
331
- index += 1;
332
- ctx.next = executeNext;
333
- const result = middlewares[currentIndex](ctx, executeNext);
334
- if (result && typeof result.then === 'function') {
335
- await result;
336
- }
568
+ };
569
+ await executeNext();
337
570
  };
338
- await executeNext();
339
- };
571
+ }
572
+ if (this._fusion) {
573
+ // Deprecated: Global fusion wrapping moved to per-route compilation
574
+ // to ensure body parsing and middleware execution happens before key generation.
575
+ // this.pipelineRunner = async (ctx, handler) => {
576
+ // await this._fusion!.coalesce(ctx, async (c) => {
577
+ // await runner(c, handler);
578
+ // });
579
+ // };
580
+ this.pipelineRunner = runner;
581
+ }
582
+ else {
583
+ this.pipelineRunner = runner;
584
+ }
340
585
  }
341
586
  compileRoutePipeline(handler,
342
587
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
343
588
  schema) {
344
589
  const middlewares = this.middlewares;
590
+ // Optimization: If no middleware and no schema and no fusion, return handler directly
591
+ // This avoids wrapping for simple routes
592
+ if (middlewares.length === 0 && !schema && !this._fusion) {
593
+ return handler;
594
+ }
345
595
  // Heuristic: Is it a structured RouteSchema or a legacy ResponseSchema?
346
596
  let responseSchema;
347
597
  let requestSchema;
@@ -404,26 +654,35 @@ class QHTTPX {
404
654
  };
405
655
  }
406
656
  // Wrap with Request Fusion if enabled
407
- if (this.fusion) {
657
+ if (this._fusion) {
408
658
  const inner = pipeline;
409
- pipeline = (ctx) => this.fusion.coalesce(ctx, inner);
410
- }
411
- // We iterate backwards to wrap the handler
412
- // pipeline = m[last](ctx, () => pipeline(ctx))
413
- for (let i = middlewares.length - 1; i >= 0; i -= 1) {
414
- const middleware = middlewares[i];
415
- const next = pipeline;
416
- pipeline = (ctx) => {
417
- const nextFn = async () => {
418
- const result = next(ctx);
659
+ pipeline = (ctx) => this._fusion.coalesce(ctx, inner);
660
+ }
661
+ // Optimization: Pre-compile middleware chain for this specific route
662
+ // This avoids closure allocation per-request and recursive dispatch overhead
663
+ // "Route-Specific Middleware Compilation"
664
+ if (middlewares.length > 0) {
665
+ const innerHandler = pipeline;
666
+ pipeline = async (ctx) => {
667
+ let index = 0;
668
+ const executeNext = async () => {
669
+ if (index >= middlewares.length) {
670
+ // All middlewares done, execute handler
671
+ const result = innerHandler(ctx);
672
+ if (result && typeof result.then === 'function') {
673
+ await result;
674
+ }
675
+ return;
676
+ }
677
+ const currentIndex = index;
678
+ index += 1;
679
+ ctx.next = executeNext;
680
+ const result = middlewares[currentIndex](ctx, executeNext);
419
681
  if (result && typeof result.then === 'function') {
420
682
  await result;
421
683
  }
422
684
  };
423
- // Attach next to ctx for destructuring support
424
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
425
- ctx.next = nextFn;
426
- return middleware(ctx, nextFn);
685
+ await executeNext();
427
686
  };
428
687
  }
429
688
  if (stringifier) {
@@ -436,8 +695,8 @@ class QHTTPX {
436
695
  return pipeline;
437
696
  }
438
697
  // Internal registration to be accessed by Scopes
439
- _registerRoute(method, path, handlerOrOptions) {
440
- this.registerRoute(method, path, handlerOrOptions);
698
+ _registerRoute(method, path, handlerOrOptions, handlerIfOptions) {
699
+ this.registerRoute(method, path, handlerOrOptions, handlerIfOptions);
441
700
  }
442
701
  async register(plugin, options) {
443
702
  const scope = new scope_1.QHTTPXScope(this, options?.prefix);
@@ -446,36 +705,101 @@ class QHTTPX {
446
705
  registerRoute(method, path, handlerOrOptions, handlerIfOptions) {
447
706
  let handler;
448
707
  let schema;
708
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
709
+ let staticResponse;
449
710
  const options = {};
450
711
  if (typeof handlerOrOptions === 'function') {
451
712
  handler = handlerOrOptions;
452
713
  }
453
714
  else if (handlerIfOptions) {
454
715
  handler = handlerIfOptions;
455
- schema = handlerOrOptions.schema;
456
- options.priority = handlerOrOptions.priority;
716
+ const config = handlerOrOptions;
717
+ // Extract schema from flattened options or use explicit schema
718
+ if (config.schema) {
719
+ schema = config.schema;
720
+ }
721
+ else if (config.body || config.params || config.query || config.headers || config.response) {
722
+ schema = {
723
+ body: config.body,
724
+ params: config.params,
725
+ query: config.query,
726
+ headers: config.headers,
727
+ response: config.response
728
+ };
729
+ }
730
+ options.priority = config.priority;
731
+ staticResponse = config.staticResponse;
457
732
  }
458
733
  else {
459
734
  const opts = handlerOrOptions;
460
- handler = opts.handler;
461
- schema = opts.schema;
735
+ if (opts.handler) {
736
+ handler = opts.handler;
737
+ }
738
+ else {
739
+ throw new Error(`Handler is required for route ${method} ${path}`);
740
+ }
741
+ // Extract schema from flattened options or use explicit schema
742
+ if (opts.schema) {
743
+ schema = opts.schema;
744
+ }
745
+ else if (opts.body || opts.params || opts.query || opts.headers || opts.response) {
746
+ schema = {
747
+ body: opts.body,
748
+ params: opts.params,
749
+ query: opts.query,
750
+ headers: opts.headers,
751
+ response: opts.response
752
+ };
753
+ }
462
754
  options.priority = opts.priority;
755
+ staticResponse = opts.staticResponse;
756
+ }
757
+ // Nuclear Optimization: Static Response Pre-building
758
+ if (staticResponse !== undefined) {
759
+ const jsonStr = JSON.stringify(staticResponse);
760
+ const buffer = Buffer.from(jsonStr);
761
+ const length = buffer.length;
762
+ // Replace handler with optimized one that bypasses serialization
763
+ handler = (ctx) => {
764
+ ctx.res.writeHead(200, {
765
+ 'Content-Type': 'application/json',
766
+ 'Content-Length': String(length),
767
+ });
768
+ ctx.res.end(buffer);
769
+ };
463
770
  }
464
771
  const compiled = this.compileRoutePipeline(handler, schema);
772
+ // Pass the compiled pipeline as metadata to the router
773
+ // This allows match results to contain the full execution chain directly
774
+ // The router will still perform matching based on path, but the resulting
775
+ // RouteMatch will have this pipeline readily available
776
+ options.metadata = {
777
+ needsQuery: false, // Calculated by Router.detectMetadata if not provided
778
+ needsCookies: false,
779
+ needsBody: false,
780
+ pipeline: compiled
781
+ };
465
782
  this.router.register(method, path, compiled, { ...options, schema });
466
783
  }
467
784
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
468
785
  get(path, arg1, arg2) {
469
786
  this.registerRoute('GET', path, arg1, arg2);
470
787
  }
471
- post(path, handlerOrOptions, handler) {
472
- this.registerRoute('POST', path, handlerOrOptions, handler);
788
+ post(path, handlerOrOptions, handlerOrConfig) {
789
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
790
+ this.registerRoute('POST', path, handlerOrOptions, handlerOrConfig);
473
791
  }
474
- put(path, handlerOrOptions, handler) {
475
- this.registerRoute('PUT', path, handlerOrOptions, handler);
792
+ put(path, handlerOrOptions, handlerOrConfig) {
793
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
794
+ this.registerRoute('PUT', path, handlerOrOptions, handlerOrConfig);
476
795
  }
477
- delete(path, handlerOrOptions, handler) {
478
- this.registerRoute('DELETE', path, handlerOrOptions, handler);
796
+ delete(path, handlerOrOptions, handlerOrConfig) {
797
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
798
+ this.registerRoute('DELETE', path, handlerOrOptions, handlerOrConfig);
799
+ }
800
+ patch(path, handlerOrOptions, handlerOrConfig) {
801
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
802
+ this.registerRoute('PATCH', path, handlerOrOptions, handlerOrConfig);
479
803
  }
480
804
  route(path) {
481
805
  const register = (method, handler) => {
@@ -519,26 +843,26 @@ class QHTTPX {
519
843
  this.batchExecutor.register(name, handler);
520
844
  }
521
845
  registerInternalRoutes() {
522
- this.router.register('GET', '/__qhttpx/health', (ctx) => {
846
+ this.router.register('GET', '/__qhttpx/health', ({ json }) => {
523
847
  const version = typeof package_json_1.default.version === 'string' ? package_json_1.default.version : '';
524
848
  const name = typeof package_json_1.default.name === 'string' ? package_json_1.default.name : '';
525
- ctx.json({
849
+ json({
526
850
  status: 'ok',
527
851
  name,
528
852
  version,
529
853
  workers: this.workerCount,
530
854
  });
531
855
  });
532
- this.router.register('GET', '/__qhttpx/metrics', (ctx) => {
533
- const snapshot = this.metrics.snapshot();
534
- ctx.json({
856
+ this.router.register('GET', '/__qhttpx/metrics', ({ json }) => {
857
+ const snapshot = this._metrics.snapshot();
858
+ json({
535
859
  ...snapshot,
536
860
  workers: this.workerCount,
537
861
  });
538
862
  });
539
- this.router.register('GET', '/__qhttpx/runtime', (ctx) => {
863
+ this.router.register('GET', '/__qhttpx/runtime', ({ json }) => {
540
864
  const schedulerStats = this.scheduler.getStats();
541
- ctx.json({
865
+ json({
542
866
  workers: this.workerCount,
543
867
  router: {
544
868
  frozen: this.router.isFrozenRouter(),
@@ -551,6 +875,7 @@ class QHTTPX {
551
875
  ? this.options.enableBatching.endpoint
552
876
  : '/qhttpx';
553
877
  this.router.register('POST', endpoint, async (ctx) => {
878
+ // Keep full ctx here as we pass it to handleBatch
554
879
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
555
880
  const body = ctx.body;
556
881
  if (!body || !Array.isArray(body.batch)) {
@@ -568,11 +893,187 @@ class QHTTPX {
568
893
  });
569
894
  }
570
895
  }
896
+ // Sugar Methods for Fluent API
897
+ database(manager) {
898
+ this.options.database = manager;
899
+ // Auto-connect?
900
+ // manager.connect() is async.
901
+ // We can't await here.
902
+ // Maybe we queue it?
903
+ // But QHTTPX doesn't have a queue for DB connection.
904
+ // The user might need to await app.start().
905
+ // We can hook into onStart.
906
+ this.onStart(async () => {
907
+ await manager.connect();
908
+ });
909
+ return this;
910
+ }
911
+ security(options) {
912
+ // 1. CORS
913
+ let corsOpts = options?.cors;
914
+ if (corsOpts === undefined)
915
+ corsOpts = true;
916
+ if (corsOpts !== false) {
917
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
918
+ const opts = corsOpts === true ? {} : corsOpts;
919
+ this.use((0, cors_1.createCorsMiddleware)(opts));
920
+ }
921
+ // 2. Security Headers
922
+ this.use((0, security_1.createSecurityHeadersMiddleware)(options?.securityHeaders));
923
+ // 3. Rate Limit (Default 100 req/15min)
924
+ const rateLimitOpts = options?.rateLimit || {};
925
+ this.use((0, rate_limit_1.rateLimit)({
926
+ windowMs: 15 * 60 * 1000,
927
+ max: 100,
928
+ ...rateLimitOpts
929
+ }));
930
+ return this;
931
+ }
932
+ log(options) {
933
+ const loggerOptions = typeof options === 'object' ? options : {};
934
+ this.use((0, logger_1.createLoggerMiddleware)(loggerOptions));
935
+ this.options.logging = options || true;
936
+ return this;
937
+ }
938
+ validate(validator) {
939
+ if (validator) {
940
+ this.options.validator = validator;
941
+ }
942
+ else {
943
+ // Default to ZodValidator
944
+ this.options.validator = new zod_1.ZodValidator();
945
+ }
946
+ return this;
947
+ }
948
+ production() {
949
+ this.options.performanceMode = 'default';
950
+ this.use((0, compression_1.createCompressionMiddleware)());
951
+ // Auto-Cluster
952
+ // Note: Clustering usually needs to be at the process entry point.
953
+ // But we can check if we are primary and fork.
954
+ // For now, let's just enable optimizations.
955
+ // We can add a helper or documentation that 'app.start()' handles clustering if production() was called?
956
+ // Or better, app.start() can check options.performanceMode.
957
+ return this;
958
+ }
959
+ routes(prefix, plugin) {
960
+ this.register(plugin, { prefix });
961
+ return this;
962
+ }
963
+ // Ultra-Simple API: Fluent Rate Limit
964
+ rateLimit(option, interval) {
965
+ if (typeof option === 'string') {
966
+ // Preset
967
+ const presets = {
968
+ 'strict': { windowMs: 15 * 60 * 1000, max: 10 },
969
+ 'standard': { windowMs: 15 * 60 * 1000, max: 100 },
970
+ 'relaxed': { windowMs: 60 * 60 * 1000, max: 1000 }
971
+ };
972
+ const config = presets[option] || presets['standard'];
973
+ this.use((0, rate_limit_1.rateLimit)(config));
974
+ }
975
+ else if (typeof option === 'number') {
976
+ // app.rateLimit(100, "1m");
977
+ let ms = 60 * 1000;
978
+ if (interval) {
979
+ if (interval === 'minute' || interval === '1m')
980
+ ms = 60 * 1000;
981
+ if (interval === 'hour' || interval === '1h')
982
+ ms = 60 * 60 * 1000;
983
+ if (interval === 'second' || interval === '1s')
984
+ ms = 1000;
985
+ }
986
+ this.use((0, rate_limit_1.rateLimit)({
987
+ max: option,
988
+ windowMs: ms
989
+ }));
990
+ }
991
+ return this;
992
+ }
993
+ allow(count) {
994
+ return {
995
+ per: (interval) => {
996
+ let ms = 60 * 1000;
997
+ if (interval === 'minute' || interval === '1m')
998
+ ms = 60 * 1000;
999
+ if (interval === 'hour' || interval === '1h')
1000
+ ms = 60 * 60 * 1000;
1001
+ if (interval === 'second' || interval === '1s')
1002
+ ms = 1000;
1003
+ this.use((0, rate_limit_1.rateLimit)({
1004
+ max: count,
1005
+ windowMs: ms
1006
+ }));
1007
+ return this;
1008
+ }
1009
+ };
1010
+ }
1011
+ fusion(enable = true) {
1012
+ if (enable === false) {
1013
+ // We can't easily remove it if it's there, but we can disable it in logic if we had a flag.
1014
+ // But for now, let's assume this is for enabling.
1015
+ return this;
1016
+ }
1017
+ if (!this._fusion) {
1018
+ // Lazy load the class to avoid circular dep issues if any (though imported at top)
1019
+ // But we already import RequestFusion.
1020
+ // We need to set it to this.fusion (which is readonly in TS, but we can cast or ignore)
1021
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1022
+ this._fusion = new fusion_1.RequestFusion(enable);
1023
+ // Re-compile pipeline to include fusion wrapper if needed
1024
+ // Actually, fusion is applied per-route in compileRoutePipeline.
1025
+ // But we might need to re-compile existing routes?
1026
+ // Usually configuration happens before routes.
1027
+ }
1028
+ return this;
1029
+ }
1030
+ bodyLimit(bytes) {
1031
+ this.options.maxBodyBytes = bytes;
1032
+ return this;
1033
+ }
1034
+ metrics(enable = true) {
1035
+ this.options.metricsEnabled = enable;
1036
+ // Metrics instance checks this.options.metricsEnabled in some places?
1037
+ // The Metrics class is initialized in constructor.
1038
+ // We might need to update the metrics instance.
1039
+ // But looking at Metrics class, it takes options in constructor.
1040
+ // However, we can probably update the internal state if needed.
1041
+ // For now, let's assume setting options is enough or we might need to expose a method on Metrics.
1042
+ // Re-initializing metrics might be complex.
1043
+ // Let's assume the user calls this before start.
1044
+ return this;
1045
+ }
1046
+ error(status, message, details) {
1047
+ return new types_1.HttpError(status, message, { details });
1048
+ }
1049
+ start(port) {
1050
+ return this.listen(port);
1051
+ }
571
1052
  getOpenAPI(options) {
572
1053
  const generator = new generator_1.OpenAPIGenerator(this.router, options);
573
1054
  return generator.generate();
574
1055
  }
575
1056
  async listen(port, hostnameOrCallback, callback) {
1057
+ // Ultra-Simple API: Clustering support in Production
1058
+ if (this.options.performanceMode === 'default' && !process.env.QHTTPX_NO_CLUSTER) {
1059
+ const cluster = await Promise.resolve().then(() => __importStar(require('cluster')));
1060
+ const os = await Promise.resolve().then(() => __importStar(require('os')));
1061
+ if (cluster.default.isPrimary) {
1062
+ const numCPUs = os.cpus().length;
1063
+ console.log(`Primary ${process.pid} is running. Forking ${numCPUs} workers...`);
1064
+ for (let i = 0; i < numCPUs; i++) {
1065
+ cluster.default.fork();
1066
+ }
1067
+ cluster.default.on('exit', (worker) => {
1068
+ console.log(`worker ${worker.process.pid} died`);
1069
+ });
1070
+ // Return a dummy promise for primary, it keeps running
1071
+ return new Promise(() => { });
1072
+ }
1073
+ else {
1074
+ // Worker process falls through to normal listen
1075
+ }
1076
+ }
576
1077
  let hostname;
577
1078
  let cb;
578
1079
  if (typeof hostnameOrCallback === 'function') {
@@ -636,8 +1137,8 @@ class QHTTPX {
636
1137
  }
637
1138
  acquireContext(req, res, urlOrPath, params, query, requestId, body,
638
1139
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
639
- files) {
640
- const ctx = this.contextPool.pop() ?? this.createContext();
1140
+ files, cookies) {
1141
+ const ctx = this.contextPool.acquire();
641
1142
  // Reset and populate properties
642
1143
  // We use type assertions to write to readonly/managed properties for performance
643
1144
  const mutableCtx = ctx;
@@ -655,24 +1156,23 @@ class QHTTPX {
655
1156
  }
656
1157
  mutableCtx.method = req.method;
657
1158
  mutableCtx.params = params;
658
- mutableCtx.query = query;
1159
+ if (query) {
1160
+ mutableCtx.query = query;
1161
+ }
659
1162
  mutableCtx.body = body;
660
1163
  mutableCtx.files = files;
1164
+ if (cookies) {
1165
+ mutableCtx.cookies = cookies;
1166
+ }
661
1167
  mutableCtx.requestId = requestId;
662
1168
  mutableCtx.serializer = undefined;
663
1169
  // In ultra mode, skip cookie parsing to save overhead
664
- if (!this.ultraMode) {
665
- mutableCtx.cookies = (0, cookies_1.parseCookies)(req.headers.cookie);
666
- }
1170
+ // Cookies are now parsed lazily via getter in QHTTPXContextImpl
667
1171
  mutableCtx.disableAutoEnd = false;
668
1172
  return mutableCtx;
669
1173
  }
670
1174
  releaseContext(ctx) {
671
- const mutableCtx = ctx;
672
- mutableCtx.reset();
673
- if (this.contextPool.length < this.poolLimit) {
674
- this.contextPool.push(ctx);
675
- }
1175
+ this.contextPool.release(ctx);
676
1176
  }
677
1177
  async handleNoMatch(ctx, allowedMethods) {
678
1178
  const res = ctx.res;
@@ -684,7 +1184,7 @@ class QHTTPX {
684
1184
  }
685
1185
  if (!res.writableEnded && !res.headersSent) {
686
1186
  res.statusCode = 405;
687
- res.setHeader('content-type', 'text/plain; charset=utf-8');
1187
+ res.setHeader('content-type', CONTENT_TYPE.PLAIN);
688
1188
  res.end('Method Not Allowed');
689
1189
  }
690
1190
  }
@@ -695,144 +1195,71 @@ class QHTTPX {
695
1195
  }
696
1196
  if (!res.writableEnded && !res.headersSent) {
697
1197
  res.statusCode = 404;
698
- res.setHeader('content-type', 'text/plain; charset=utf-8');
1198
+ res.setHeader('content-type', CONTENT_TYPE.PLAIN);
699
1199
  res.end('Not Found');
700
1200
  }
701
1201
  }
702
1202
  else if (hasAnyMethod) {
703
1203
  if (!res.headersSent) {
704
1204
  res.statusCode = 405;
705
- res.setHeader('content-type', 'text/plain; charset=utf-8');
1205
+ res.setHeader('content-type', CONTENT_TYPE.PLAIN);
706
1206
  }
707
1207
  res.end('Method Not Allowed');
708
1208
  }
709
1209
  else {
710
1210
  if (!res.headersSent) {
711
1211
  res.statusCode = 404;
712
- res.setHeader('content-type', 'text/plain; charset=utf-8');
1212
+ res.setHeader('content-type', CONTENT_TYPE.PLAIN);
713
1213
  }
714
1214
  res.end('Not Found');
715
1215
  }
716
1216
  }
717
1217
  handleRequest(req, res) {
718
- // ⚡ Ultra Mode Optimization: Flattened & Inlined Logic
719
- if (this.ultraMode) {
720
- const rawUrl = req.url || '/';
721
- const method = (req.method || 'GET');
722
- // Single-pass pathname extraction
723
- let pathname;
724
- const qIndex = rawUrl.indexOf('?');
725
- pathname = qIndex === -1 ? rawUrl : rawUrl.substring(0, qIndex);
726
- const match = this.router.match(method, pathname);
727
- if (match) {
728
- // Acquire Context - reuse pooled object (no allocations)
729
- const ctx = this.contextPool.pop() ?? this.createContext();
730
- const mutableCtx = ctx;
731
- mutableCtx.req = req;
732
- mutableCtx.res = res;
733
- mutableCtx.path = pathname;
734
- mutableCtx.method = method;
735
- mutableCtx.params = match.params || QHTTPX.EMPTY_PARAMS;
736
- mutableCtx.query = EMPTY_QUERY;
737
- mutableCtx.requestId = undefined;
738
- mutableCtx.body = undefined;
739
- mutableCtx.files = undefined;
740
- mutableCtx.disableAutoEnd = false;
741
- // Don't allocate state - leave as undefined
742
- const releaseCtx = () => {
743
- mutableCtx.reset();
744
- if (this.contextPool.length < this.poolLimit) {
745
- this.contextPool.push(ctx);
746
- }
747
- };
748
- try {
749
- const result = match.handler(ctx);
750
- // Optimized promise detection
751
- if (result?.then) {
752
- result.then(() => {
753
- if (!res.writableEnded && !ctx.disableAutoEnd) {
754
- res.end();
755
- }
756
- releaseCtx();
757
- }).catch((err) => {
758
- console.error(err);
759
- if (!res.headersSent) {
760
- res.statusCode = 500;
761
- res.end();
762
- }
763
- releaseCtx();
764
- });
765
- }
766
- else {
767
- if (!res.writableEnded && !ctx.disableAutoEnd) {
768
- res.end();
769
- }
770
- releaseCtx();
771
- }
772
- }
773
- catch (err) {
774
- console.error(err);
775
- if (!res.headersSent) {
776
- res.statusCode = 500;
777
- res.end();
778
- }
779
- releaseCtx();
780
- }
781
- }
782
- else {
783
- // No match - Ultra Mode Fast 404
784
- if (!res.headersSent) {
785
- res.statusCode = 404;
786
- res.end('Not Found');
787
- }
788
- }
789
- return;
790
- }
791
- // === Balanced Mode Logic (Legacy) ===
792
- const rawMethod = (req.method || 'GET').toUpperCase();
793
- const method = rawMethod;
1218
+ // ⚡ Optimized request handling with fast routing
794
1219
  const rawUrl = req.url || '/';
795
- // Fast path: parse URL without new URL() constructor (50-70x faster)
796
- let pathname = rawUrl;
797
- let query = EMPTY_QUERY;
798
- const queryIndex = rawUrl.indexOf('?');
799
- if (queryIndex !== -1) {
800
- // Extract pathname
801
- pathname = rawUrl.slice(0, queryIndex);
802
- // Parse query string only if present
803
- const queryString = rawUrl.slice(queryIndex + 1);
804
- if (queryString.length > 0) {
805
- const parsed = (0, querystring_1.parse)(queryString);
806
- query = parsed;
807
- }
1220
+ const method = (req.method || 'GET');
1221
+ // Single-pass pathname extraction
1222
+ let pathname;
1223
+ const qIndex = rawUrl.indexOf('?');
1224
+ pathname = qIndex === -1 ? rawUrl : rawUrl.substring(0, qIndex);
1225
+ // Fast route matching with O(1) static and optimized dynamic routes
1226
+ let match = this.router.match(method, pathname);
1227
+ // Request ID generation (from x-request-id header or generated)
1228
+ let requestId;
1229
+ const incomingRequestIdHeader = req.headers['x-request-id'];
1230
+ if (typeof incomingRequestIdHeader === 'string') {
1231
+ requestId = incomingRequestIdHeader;
808
1232
  }
809
- else {
810
- // No query string, just extract pathname
811
- pathname = rawUrl;
1233
+ else if (Array.isArray(incomingRequestIdHeader)) {
1234
+ requestId = incomingRequestIdHeader[0];
812
1235
  }
813
- // Request ID generation
814
- let requestId;
815
- if (!this.ultraMode) {
816
- const incomingRequestIdHeader = req.headers['x-request-id'];
817
- if (typeof incomingRequestIdHeader === 'string') {
818
- requestId = incomingRequestIdHeader;
819
- }
820
- else if (Array.isArray(incomingRequestIdHeader)) {
821
- requestId = incomingRequestIdHeader[0];
822
- }
823
- if (!requestId) {
824
- requestId = this.generateRequestId();
825
- }
826
- if (!res.headersSent && requestId) {
827
- res.setHeader('x-request-id', requestId);
828
- }
1236
+ if (!requestId) {
1237
+ requestId = this.generateRequestId();
1238
+ }
1239
+ if (!res.headersSent && requestId) {
1240
+ res.setHeader('x-request-id', requestId);
829
1241
  }
830
- let match = this.router.match(method, pathname);
831
1242
  const hasRoute = !!match;
832
1243
  if (!match) {
833
1244
  match = QHTTPX.EMPTY_MATCH;
834
1245
  }
835
- const allowedMethods = this.router.getAllowedMethods(pathname);
1246
+ // Optimization: Predictive Query Parsing
1247
+ // 1. If no query string exists, set empty query to avoid getter scan
1248
+ // 2. If query string exists AND route needs it, parse eagerly
1249
+ let query;
1250
+ if (qIndex === -1) {
1251
+ query = EMPTY_QUERY;
1252
+ }
1253
+ else if (match.metadata?.needsQuery) {
1254
+ query = (0, querystring_1.parse)(rawUrl.slice(qIndex + 1));
1255
+ }
1256
+ // Optimization: Predictive Cookie Parsing
1257
+ let cookies;
1258
+ if (match.metadata?.needsCookies) {
1259
+ cookies = (0, cookies_1.parseCookies)(req.headers.cookie);
1260
+ }
1261
+ // Optimization: Only calculate allowed methods if no route match found
1262
+ const allowedMethods = hasRoute ? [] : this.router.getAllowedMethods(pathname);
836
1263
  if (!hasRoute && !res.headersSent) {
837
1264
  const hasAnyMethod = allowedMethods.length > 0;
838
1265
  res.statusCode = hasAnyMethod ? 405 : 404;
@@ -848,7 +1275,7 @@ class QHTTPX {
848
1275
  }
849
1276
  if (!res.headersSent) {
850
1277
  res.statusCode = 503;
851
- res.setHeader('content-type', 'text/plain; charset=utf-8');
1278
+ res.setHeader('content-type', CONTENT_TYPE.PLAIN);
852
1279
  }
853
1280
  res.end('Server overloaded');
854
1281
  };
@@ -863,12 +1290,12 @@ class QHTTPX {
863
1290
  body_parser_1.BodyParser.parse(req, {
864
1291
  maxBodyBytes: this.options.maxBodyBytes,
865
1292
  }).then((parsed) => {
866
- this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, parsed.body, parsed.files);
1293
+ this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, parsed.body, parsed.files, cookies);
867
1294
  }).catch((err) => {
868
1295
  if (err instanceof Error && err.message === 'QHTTPX_INVALID_JSON') {
869
1296
  if (!res.headersSent) {
870
1297
  res.statusCode = 400;
871
- res.setHeader('content-type', 'text/plain; charset=utf-8');
1298
+ res.setHeader('content-type', CONTENT_TYPE.PLAIN);
872
1299
  }
873
1300
  res.end('Invalid JSON');
874
1301
  return;
@@ -876,23 +1303,23 @@ class QHTTPX {
876
1303
  if (err instanceof Error && err.message === 'QHTTPX_BODY_TOO_LARGE') {
877
1304
  if (!res.headersSent) {
878
1305
  res.statusCode = 413;
879
- res.setHeader('content-type', 'text/plain; charset=utf-8');
1306
+ res.setHeader('content-type', CONTENT_TYPE.PLAIN);
880
1307
  }
881
1308
  res.end('Payload Too Large');
882
1309
  return;
883
1310
  }
884
1311
  if (!res.headersSent) {
885
1312
  res.statusCode = 500;
886
- res.setHeader('content-type', 'text/plain; charset=utf-8');
1313
+ res.setHeader('content-type', CONTENT_TYPE.PLAIN);
887
1314
  }
888
1315
  res.end('Internal Server Error');
889
1316
  });
890
1317
  return;
891
1318
  }
892
- this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, undefined, undefined);
1319
+ this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, undefined, undefined, cookies);
893
1320
  }
894
- dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, body, files) {
895
- const ctx = this.acquireContext(req, res, pathname, match.params, query, requestId, body, files);
1321
+ async dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, body, files, cookies) {
1322
+ const ctx = this.acquireContext(req, res, pathname, match.params, query, requestId, body, files, cookies);
896
1323
  // Balanced Mode Logic
897
1324
  const overloaded = () => {
898
1325
  if (res.writableEnded) {
@@ -900,7 +1327,7 @@ class QHTTPX {
900
1327
  }
901
1328
  if (!res.headersSent) {
902
1329
  res.statusCode = 503;
903
- res.setHeader('content-type', 'text/plain; charset=utf-8');
1330
+ res.setHeader('content-type', CONTENT_TYPE.PLAIN);
904
1331
  }
905
1332
  res.end('Server overloaded');
906
1333
  };
@@ -912,13 +1339,19 @@ class QHTTPX {
912
1339
  }
913
1340
  if (!res.headersSent) {
914
1341
  res.statusCode = 504;
915
- res.setHeader('content-type', 'text/plain; charset=utf-8');
1342
+ res.setHeader('content-type', CONTENT_TYPE.PLAIN);
916
1343
  }
917
1344
  res.end('Request timed out');
918
1345
  };
919
- const start = Date.now();
920
- ctx.requestStart = start;
921
- this.metrics.onRequestStart();
1346
+ let start = 0;
1347
+ const metricsEnabled = this._metrics.isEnabled;
1348
+ if (metricsEnabled || this.tracer) {
1349
+ start = timer_1.globalTimer.preciseNow();
1350
+ ctx.requestStart = start;
1351
+ if (metricsEnabled) {
1352
+ this._metrics.onRequestStart();
1353
+ }
1354
+ }
922
1355
  if (this.tracer) {
923
1356
  const event = {
924
1357
  type: 'request_start',
@@ -932,10 +1365,15 @@ class QHTTPX {
932
1365
  }
933
1366
  }
934
1367
  const finish = () => {
935
- const duration = Date.now() - start;
936
- this.metrics.onRequestEnd(duration, res.statusCode);
937
- if (timedOut) {
938
- this.metrics.onTimeout();
1368
+ let duration = 0;
1369
+ if (metricsEnabled || this.tracer) {
1370
+ duration = timer_1.globalTimer.preciseNow() - start;
1371
+ if (metricsEnabled) {
1372
+ this._metrics.onRequestEnd(duration, res.statusCode);
1373
+ if (timedOut) {
1374
+ this._metrics.onTimeout();
1375
+ }
1376
+ }
939
1377
  }
940
1378
  if (this.tracer) {
941
1379
  const event = {
@@ -978,24 +1416,16 @@ class QHTTPX {
978
1416
  await this.handleError(err, ctx);
979
1417
  }
980
1418
  };
981
- // Ultra mode: skip scheduler for minimal overhead
982
- if (this.ultraMode) {
983
- handle().then(finish).catch((err) => {
984
- console.error('Request handler error:', err);
985
- finish();
986
- });
987
- }
988
- else {
989
- this.scheduler.run(handle, {
990
- priority: match.priority,
991
- onOverloaded: overloaded,
992
- timeoutMs: this.options.requestTimeoutMs,
993
- onTimeout,
994
- }).then(finish).catch((err) => {
995
- console.error('Scheduler error:', err);
996
- finish();
997
- });
998
- }
1419
+ // Use scheduler for request handling
1420
+ this.scheduler.run(handle, {
1421
+ priority: match.priority,
1422
+ onOverloaded: overloaded,
1423
+ timeoutMs: this.options.requestTimeoutMs,
1424
+ onTimeout,
1425
+ }).then(finish).catch((err) => {
1426
+ console.error('Scheduler error:', err);
1427
+ finish();
1428
+ });
999
1429
  }
1000
1430
  async handleUpgrade(req, socket, head) {
1001
1431
  await this.wsManager.handleUpgrade(req, socket, head);
@@ -1047,7 +1477,7 @@ class QHTTPX {
1047
1477
  if (err instanceof types_1.HttpError) {
1048
1478
  if (!res.headersSent) {
1049
1479
  res.statusCode = err.status;
1050
- res.setHeader('content-type', 'application/json; charset=utf-8');
1480
+ res.setHeader('content-type', CONTENT_TYPE.JSON);
1051
1481
  }
1052
1482
  const payload = {
1053
1483
  error: {
@@ -1063,14 +1493,19 @@ class QHTTPX {
1063
1493
  }
1064
1494
  if (!res.headersSent) {
1065
1495
  res.statusCode = 500;
1066
- res.setHeader('content-type', 'text/plain; charset=utf-8');
1496
+ res.setHeader('content-type', CONTENT_TYPE.PLAIN);
1067
1497
  }
1068
1498
  res.end('Internal Server Error');
1069
1499
  }
1070
1500
  generateRequestId() {
1501
+ const now = timer_1.globalTimer.now();
1502
+ if (now !== this.lastDateNow) {
1503
+ this.lastDateNow = now;
1504
+ this.lastDateString = now.toString(36);
1505
+ }
1071
1506
  const id = this.nextRequestId;
1072
1507
  this.nextRequestId += 1;
1073
- return `${Date.now().toString(36)}-${id.toString(36)}`;
1508
+ return `${this.lastDateString}-${id.toString(36)}`;
1074
1509
  }
1075
1510
  }
1076
1511
  exports.QHTTPX = QHTTPX;
@@ -1079,4 +1514,5 @@ QHTTPX.EMPTY_MATCH = Object.freeze({
1079
1514
  handler: () => { },
1080
1515
  params: QHTTPX.EMPTY_PARAMS,
1081
1516
  priority: types_1.RoutePriority.STANDARD,
1517
+ metadata: { needsQuery: false, needsCookies: false, needsBody: false, pipeline: undefined },
1082
1518
  });