qhttpx 1.8.5 → 1.8.11

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 (161) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +72 -52
  3. package/binding.gyp +18 -0
  4. package/dist/examples/api-server.js +29 -8
  5. package/dist/examples/basic.d.ts +1 -0
  6. package/dist/examples/basic.js +10 -0
  7. package/dist/examples/compression.d.ts +1 -0
  8. package/dist/examples/compression.js +17 -0
  9. package/dist/examples/cors.d.ts +1 -0
  10. package/dist/examples/cors.js +19 -0
  11. package/dist/examples/errors.d.ts +1 -0
  12. package/dist/examples/errors.js +25 -0
  13. package/dist/examples/file-upload.d.ts +1 -0
  14. package/dist/examples/file-upload.js +24 -0
  15. package/dist/examples/fusion.d.ts +1 -0
  16. package/dist/examples/fusion.js +21 -0
  17. package/dist/examples/rate-limiting.d.ts +1 -0
  18. package/dist/examples/rate-limiting.js +17 -0
  19. package/dist/examples/validation.d.ts +1 -0
  20. package/dist/examples/validation.js +23 -0
  21. package/dist/examples/websockets.d.ts +1 -0
  22. package/dist/examples/websockets.js +20 -0
  23. package/dist/package.json +11 -1
  24. package/dist/src/benchmarks/simple-json.js +6 -4
  25. package/dist/src/cli/index.js +33 -11
  26. package/dist/src/core/errors.d.ts +34 -0
  27. package/dist/src/core/errors.js +70 -0
  28. package/dist/src/core/native-adapter.d.ts +11 -0
  29. package/dist/src/core/native-adapter.js +211 -0
  30. package/dist/src/core/server.d.ts +52 -4
  31. package/dist/src/core/server.js +389 -261
  32. package/dist/src/core/types.d.ts +37 -0
  33. package/dist/src/index.d.ts +6 -1
  34. package/dist/src/index.js +19 -3
  35. package/dist/src/middleware/compression.d.ts +1 -5
  36. package/dist/src/middleware/cors.d.ts +1 -10
  37. package/dist/src/middleware/presets.d.ts +4 -1
  38. package/dist/src/middleware/presets.js +22 -3
  39. package/dist/src/middleware/rate-limit.d.ts +1 -19
  40. package/dist/src/middleware/rate-limit.js +6 -0
  41. package/dist/src/middleware/security.d.ts +1 -2
  42. package/dist/src/native/index.d.ts +29 -0
  43. package/dist/src/native/index.js +64 -0
  44. package/dist/src/router/radix-tree.d.ts +2 -0
  45. package/dist/src/router/radix-tree.js +54 -4
  46. package/dist/src/router/router.d.ts +1 -0
  47. package/dist/src/router/router.js +42 -2
  48. package/dist/tests/native-adapter.test.d.ts +1 -0
  49. package/dist/tests/native-adapter.test.js +71 -0
  50. package/dist/tests/resources.test.js +3 -0
  51. package/dist/tests/security.test.js +2 -2
  52. package/docs/AEGIS.md +34 -9
  53. package/docs/BENCHMARKS.md +8 -7
  54. package/docs/ERRORS.md +112 -0
  55. package/docs/FUSION.md +68 -0
  56. package/docs/MIDDLEWARE.md +65 -0
  57. package/docs/ROUTING.md +70 -0
  58. package/docs/STATIC.md +61 -0
  59. package/docs/WEBSOCKETS.md +76 -0
  60. package/package.json +11 -1
  61. package/src/native/README.md +31 -0
  62. package/src/native/addon.cc +8 -0
  63. package/src/native/index.ts +78 -0
  64. package/src/native/picohttpparser.c +608 -0
  65. package/src/native/picohttpparser.h +71 -0
  66. package/src/native/server.cc +262 -0
  67. package/src/native/server.h +30 -0
  68. package/.eslintrc.json +0 -22
  69. package/.github/workflows/ci.yml +0 -32
  70. package/.github/workflows/npm-publish.yml +0 -37
  71. package/.github/workflows/release.yml +0 -21
  72. package/.prettierrc +0 -7
  73. package/assets/logo.svg +0 -25
  74. package/eslint.config.cjs +0 -26
  75. package/examples/api-server.ts +0 -62
  76. package/src/benchmarks/quantam-users.ts +0 -70
  77. package/src/benchmarks/simple-json.ts +0 -71
  78. package/src/benchmarks/ultra-mode.ts +0 -127
  79. package/src/cli/index.ts +0 -214
  80. package/src/client/index.ts +0 -93
  81. package/src/core/batch.ts +0 -110
  82. package/src/core/body-parser.ts +0 -151
  83. package/src/core/buffer-pool.ts +0 -96
  84. package/src/core/config.ts +0 -60
  85. package/src/core/fusion.ts +0 -210
  86. package/src/core/logger.ts +0 -70
  87. package/src/core/metrics.ts +0 -166
  88. package/src/core/resources.ts +0 -38
  89. package/src/core/scheduler.ts +0 -126
  90. package/src/core/scope.ts +0 -87
  91. package/src/core/serializer.ts +0 -41
  92. package/src/core/server.ts +0 -1234
  93. package/src/core/stream.ts +0 -111
  94. package/src/core/tasks.ts +0 -138
  95. package/src/core/types.ts +0 -192
  96. package/src/core/websocket.ts +0 -112
  97. package/src/core/worker-queue.ts +0 -90
  98. package/src/database/adapters/memory.ts +0 -99
  99. package/src/database/adapters/mongo.ts +0 -116
  100. package/src/database/adapters/postgres.ts +0 -86
  101. package/src/database/adapters/sqlite.ts +0 -44
  102. package/src/database/coalescer.ts +0 -153
  103. package/src/database/manager.ts +0 -97
  104. package/src/database/types.ts +0 -24
  105. package/src/index.ts +0 -58
  106. package/src/middleware/compression.ts +0 -147
  107. package/src/middleware/cors.ts +0 -98
  108. package/src/middleware/presets.ts +0 -50
  109. package/src/middleware/rate-limit.ts +0 -106
  110. package/src/middleware/security.ts +0 -109
  111. package/src/middleware/static.ts +0 -216
  112. package/src/openapi/generator.ts +0 -167
  113. package/src/router/radix-router.ts +0 -119
  114. package/src/router/radix-tree.ts +0 -106
  115. package/src/router/router.ts +0 -190
  116. package/src/testing/index.ts +0 -104
  117. package/src/utils/cookies.ts +0 -67
  118. package/src/utils/logger.ts +0 -59
  119. package/src/utils/signals.ts +0 -45
  120. package/src/utils/sse.ts +0 -41
  121. package/src/validation/index.ts +0 -3
  122. package/src/validation/simple.ts +0 -93
  123. package/src/validation/types.ts +0 -38
  124. package/src/validation/zod.ts +0 -14
  125. package/src/views/index.ts +0 -1
  126. package/src/views/types.ts +0 -4
  127. package/tests/adapters.test.ts +0 -120
  128. package/tests/batch.test.ts +0 -139
  129. package/tests/body-parser.test.ts +0 -83
  130. package/tests/compression-sse.test.ts +0 -98
  131. package/tests/cookies.test.ts +0 -74
  132. package/tests/cors.test.ts +0 -79
  133. package/tests/database.test.ts +0 -90
  134. package/tests/dx.test.ts +0 -130
  135. package/tests/ecosystem.test.ts +0 -156
  136. package/tests/features.test.ts +0 -51
  137. package/tests/fusion.test.ts +0 -121
  138. package/tests/http-basic.test.ts +0 -161
  139. package/tests/logger.test.ts +0 -48
  140. package/tests/middleware.test.ts +0 -137
  141. package/tests/observability.test.ts +0 -91
  142. package/tests/openapi.test.ts +0 -74
  143. package/tests/plugin.test.ts +0 -85
  144. package/tests/plugins.test.ts +0 -93
  145. package/tests/rate-limit.test.ts +0 -97
  146. package/tests/resources.test.ts +0 -64
  147. package/tests/scheduler.test.ts +0 -71
  148. package/tests/schema-routes.test.ts +0 -89
  149. package/tests/security.test.ts +0 -128
  150. package/tests/server-db.test.ts +0 -72
  151. package/tests/smoke.test.ts +0 -9
  152. package/tests/sqlite-fusion.test.ts +0 -106
  153. package/tests/static.test.ts +0 -111
  154. package/tests/stream.test.ts +0 -58
  155. package/tests/task-metrics.test.ts +0 -78
  156. package/tests/tasks.test.ts +0 -90
  157. package/tests/testing.test.ts +0 -53
  158. package/tests/validation.test.ts +0 -126
  159. package/tests/websocket.test.ts +0 -132
  160. package/tsconfig.json +0 -17
  161. package/vitest.config.ts +0 -9
@@ -3,10 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.QHTTPX = void 0;
6
+ exports.QHTTPX = exports.QHTTPXContextImpl = void 0;
7
7
  const http_1 = __importDefault(require("http"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const url_1 = require("url");
10
+ const querystring_1 = require("querystring");
10
11
  const package_json_1 = __importDefault(require("../../package.json"));
11
12
  const router_1 = require("../router/router");
12
13
  const scheduler_1 = require("./scheduler");
@@ -25,6 +26,165 @@ const types_1 = require("./types");
25
26
  const cookies_1 = require("../utils/cookies");
26
27
  const scope_1 = require("./scope");
27
28
  const logger_1 = require("./logger");
29
+ const EMPTY_QUERY = Object.freeze({});
30
+ class QHTTPXContextImpl {
31
+ constructor(app) {
32
+ this._url = null;
33
+ this._app = app;
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ this._appJsonSerializer = app.options.jsonSerializer;
36
+ this.json = this.json.bind(this);
37
+ this.send = this.send.bind(this);
38
+ this.html = this.html.bind(this);
39
+ this.redirect = this.redirect.bind(this);
40
+ this.setCookie = this.setCookie.bind(this);
41
+ this.render = this.render.bind(this);
42
+ this.validate = this.validate.bind(this);
43
+ }
44
+ get bufferPool() {
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ return this._app.bufferPool;
47
+ }
48
+ get db() {
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ return this._app.options.database;
51
+ }
52
+ get state() {
53
+ if (!this._state) {
54
+ this._state = {};
55
+ }
56
+ return this._state;
57
+ }
58
+ set state(v) {
59
+ this._state = v;
60
+ }
61
+ get ip() {
62
+ if (this._ip)
63
+ return this._ip;
64
+ this._ip = this.req.socket.remoteAddress || '';
65
+ return this._ip;
66
+ }
67
+ // Setter for manual override if needed (though usually read-only from socket)
68
+ set ip(v) {
69
+ this._ip = v;
70
+ }
71
+ get url() {
72
+ if (this._url)
73
+ return this._url;
74
+ const host = this.headers.host || 'localhost';
75
+ this._url = new url_1.URL(this.req.url || '/', `http://${host}`);
76
+ return this._url;
77
+ }
78
+ set url(v) {
79
+ this._url = v;
80
+ }
81
+ json(payload, status = 200) {
82
+ const res = this.res;
83
+ let body;
84
+ if (this.serializer) {
85
+ body = this.serializer(payload);
86
+ }
87
+ else if (this._appJsonSerializer) {
88
+ body = this._appJsonSerializer(payload);
89
+ }
90
+ else {
91
+ // Native JSON.stringify is faster for schema-less objects
92
+ body = JSON.stringify(payload);
93
+ }
94
+ if (!res.headersSent) {
95
+ res.statusCode = status;
96
+ res.setHeader('content-type', 'application/json; charset=utf-8');
97
+ }
98
+ res.end(body);
99
+ }
100
+ send(payload, status = 200) {
101
+ const res = this.res;
102
+ if (!res.headersSent) {
103
+ res.statusCode = status;
104
+ }
105
+ res.end(payload);
106
+ }
107
+ html(payload, status = 200) {
108
+ const res = this.res;
109
+ if (!res.headersSent) {
110
+ res.statusCode = status;
111
+ res.setHeader('content-type', 'text/html; charset=utf-8');
112
+ }
113
+ res.end(payload);
114
+ }
115
+ redirect(url, status = 302) {
116
+ const res = this.res;
117
+ if (!res.headersSent) {
118
+ res.statusCode = status;
119
+ res.setHeader('Location', url);
120
+ }
121
+ res.end();
122
+ }
123
+ setCookie(name, value, options) {
124
+ const res = this.res;
125
+ const serialized = (0, cookies_1.serializeCookie)(name, value, options);
126
+ let existing = res.getHeader('Set-Cookie');
127
+ if (Array.isArray(existing)) {
128
+ existing.push(serialized);
129
+ res.setHeader('Set-Cookie', existing);
130
+ }
131
+ else if (existing) {
132
+ res.setHeader('Set-Cookie', [existing, serialized]);
133
+ }
134
+ else {
135
+ res.setHeader('Set-Cookie', serialized);
136
+ }
137
+ }
138
+ async render(view, locals) {
139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
+ const engine = this._app.options.viewEngine;
141
+ if (!engine) {
142
+ throw new Error('No view engine registered');
143
+ }
144
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
145
+ const viewsPath = this._app.options.viewsPath || process.cwd();
146
+ const fullPath = path_1.default.resolve(viewsPath, view);
147
+ const html = await engine.render(fullPath, locals || {});
148
+ const res = this.res;
149
+ if (!res.headersSent) {
150
+ res.statusCode = 200;
151
+ res.setHeader('content-type', 'text/html; charset=utf-8');
152
+ }
153
+ res.end(html);
154
+ }
155
+ async validate(schema, data) {
156
+ const target = data ?? this.body;
157
+ const result = await this._app.validator.validate(schema, target);
158
+ if (result.success) {
159
+ return result.data;
160
+ }
161
+ throw new types_1.HttpError(400, 'Validation Error', {
162
+ code: 'VALIDATION_ERROR',
163
+ details: result.error,
164
+ });
165
+ }
166
+ reset() {
167
+ this.req = null;
168
+ this.res = null;
169
+ this.headers = null;
170
+ this._url = null;
171
+ this.method = null;
172
+ 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
+ this.requestId = undefined;
180
+ this.requestStart = undefined;
181
+ this.serializer = undefined;
182
+ this.path = '';
183
+ this.disableAutoEnd = false;
184
+ this.error = undefined;
185
+ }
186
+ }
187
+ exports.QHTTPXContextImpl = QHTTPXContextImpl;
28
188
  class QHTTPX {
29
189
  constructor(options = {}) {
30
190
  this.middlewares = [];
@@ -34,6 +194,7 @@ class QHTTPX {
34
194
  this.onBeforeShutdownHooks = [];
35
195
  this.onShutdownHooks = [];
36
196
  this.nextRequestId = 1;
197
+ this.requestCounter = 0;
37
198
  this.options = options;
38
199
  this.logger = new logger_1.Logger({
39
200
  name: options.name,
@@ -49,6 +210,7 @@ class QHTTPX {
49
210
  this.wsManager = new websocket_1.WebSocketManager(this.generateRequestId.bind(this));
50
211
  this.bufferPool = new buffer_pool_1.BufferPool(options.bufferPoolConfig);
51
212
  const maxConcurrency = options.maxConcurrency ?? 1024;
213
+ this.poolLimit = maxConcurrency * 2;
52
214
  this.scheduler = new scheduler_1.Scheduler({
53
215
  maxConcurrency,
54
216
  });
@@ -167,6 +329,7 @@ class QHTTPX {
167
329
  }
168
330
  const currentIndex = index;
169
331
  index += 1;
332
+ ctx.next = executeNext;
170
333
  const result = middlewares[currentIndex](ctx, executeNext);
171
334
  if (result && typeof result.then === 'function') {
172
335
  await result;
@@ -301,8 +464,9 @@ class QHTTPX {
301
464
  const compiled = this.compileRoutePipeline(handler, schema);
302
465
  this.router.register(method, path, compiled, { ...options, schema });
303
466
  }
304
- get(path, handlerOrOptions, handler) {
305
- this.registerRoute('GET', path, handlerOrOptions, handler);
467
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
468
+ get(path, arg1, arg2) {
469
+ this.registerRoute('GET', path, arg1, arg2);
306
470
  }
307
471
  post(path, handlerOrOptions, handler) {
308
472
  this.registerRoute('POST', path, handlerOrOptions, handler);
@@ -468,130 +632,28 @@ class QHTTPX {
468
632
  await this.runLifecycleHooks(this.onShutdownHooks);
469
633
  }
470
634
  createContext() {
471
- const jsonSerializer = this.options.jsonSerializer;
472
- const useFastStringify = jsonSerializer === undefined;
473
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
474
- const ctx = {
475
- req: null,
476
- res: null,
477
- headers: null,
478
- url: null,
479
- params: null,
480
- query: null,
481
- body: undefined,
482
- cookies: null,
483
- state: null,
484
- bufferPool: this.bufferPool,
485
- requestId: '',
486
- requestStart: 0,
487
- serializer: null,
488
- path: '',
489
- error: undefined,
490
- db: this.options.database,
491
- };
492
- // Helper to get response object from closure-captured ctx
493
- // We use arrow functions to ensure they don't depend on 'this' context at call site
494
- // enabling destructuring like: ({ json }) => json(...)
495
- ctx.json = (payload, status = 200) => {
496
- const res = ctx.res;
497
- if (!res.headersSent) {
498
- res.statusCode = status;
499
- res.setHeader('content-type', 'application/json; charset=utf-8');
500
- }
501
- let body;
502
- if (ctx.serializer) {
503
- body = ctx.serializer(payload);
504
- }
505
- else if (useFastStringify) {
506
- body = (0, serializer_1.fastJsonStringify)(payload);
507
- }
508
- else if (jsonSerializer) {
509
- body = jsonSerializer(payload);
510
- }
511
- else {
512
- body = JSON.stringify(payload);
513
- }
514
- res.end(body);
515
- };
516
- ctx.send = (payload, status = 200) => {
517
- const res = ctx.res;
518
- if (!res.headersSent) {
519
- res.statusCode = status;
520
- }
521
- res.end(payload);
522
- };
523
- ctx.html = (payload, status = 200) => {
524
- const res = ctx.res;
525
- if (!res.headersSent) {
526
- res.statusCode = status;
527
- res.setHeader('content-type', 'text/html; charset=utf-8');
528
- }
529
- res.end(payload);
530
- };
531
- ctx.redirect = (url, status = 302) => {
532
- const res = ctx.res;
533
- if (!res.headersSent) {
534
- res.statusCode = status;
535
- res.setHeader('Location', url);
536
- }
537
- res.end();
538
- };
539
- ctx.setCookie = (name, value, options) => {
540
- const res = ctx.res;
541
- const serialized = (0, cookies_1.serializeCookie)(name, value, options);
542
- let existing = res.getHeader('Set-Cookie');
543
- if (Array.isArray(existing)) {
544
- existing.push(serialized);
545
- res.setHeader('Set-Cookie', existing);
546
- }
547
- else if (existing) {
548
- res.setHeader('Set-Cookie', [existing, serialized]);
549
- }
550
- else {
551
- res.setHeader('Set-Cookie', serialized);
552
- }
553
- };
554
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
555
- ctx.render = async (view, locals) => {
556
- const engine = this.options.viewEngine;
557
- if (!engine) {
558
- throw new Error('No view engine registered');
559
- }
560
- const viewsPath = this.options.viewsPath || process.cwd();
561
- const fullPath = path_1.default.resolve(viewsPath, view);
562
- const html = await engine.render(fullPath, locals || {});
563
- const res = ctx.res;
564
- if (!res.headersSent) {
565
- res.statusCode = 200;
566
- res.setHeader('content-type', 'text/html; charset=utf-8');
567
- }
568
- res.end(html);
569
- };
570
- ctx.validate = async (schema, data) => {
571
- const target = data ?? ctx.body;
572
- const result = await this.validator.validate(schema, target);
573
- if (result.success) {
574
- return result.data;
575
- }
576
- throw new types_1.HttpError(400, 'Validation Error', {
577
- code: 'VALIDATION_ERROR',
578
- details: result.error,
579
- });
580
- };
581
- return ctx;
635
+ return new QHTTPXContextImpl(this);
582
636
  }
583
- acquireContext(req, res, url, params, query, requestId, body,
637
+ acquireContext(req, res, urlOrPath, params, query, requestId, body,
584
638
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
585
639
  files) {
586
640
  const ctx = this.contextPool.pop() ?? this.createContext();
587
641
  // Reset and populate properties
588
642
  // We use type assertions to write to readonly/managed properties for performance
589
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
590
643
  const mutableCtx = ctx;
591
644
  mutableCtx.req = req;
592
645
  mutableCtx.res = res;
593
646
  mutableCtx.headers = req.headers;
594
- mutableCtx.url = url;
647
+ if (typeof urlOrPath === 'string') {
648
+ // Ultra mode or fast path: Lazy URL instantiation
649
+ mutableCtx.path = urlOrPath;
650
+ // Class-based getter handles URL parsing lazily using this.req
651
+ }
652
+ else {
653
+ mutableCtx.url = urlOrPath;
654
+ mutableCtx.path = urlOrPath.pathname;
655
+ }
656
+ mutableCtx.method = req.method;
595
657
  mutableCtx.params = params;
596
658
  mutableCtx.query = query;
597
659
  mutableCtx.body = body;
@@ -602,88 +664,181 @@ class QHTTPX {
602
664
  if (!this.ultraMode) {
603
665
  mutableCtx.cookies = (0, cookies_1.parseCookies)(req.headers.cookie);
604
666
  }
605
- mutableCtx.state = {};
606
667
  mutableCtx.disableAutoEnd = false;
607
- mutableCtx.path = url.pathname;
608
- return ctx;
668
+ return mutableCtx;
609
669
  }
610
670
  releaseContext(ctx) {
611
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
612
671
  const mutableCtx = ctx;
613
- mutableCtx.req = null;
614
- mutableCtx.res = null;
615
- mutableCtx.headers = null;
616
- mutableCtx.url = null;
617
- mutableCtx.params = null;
618
- mutableCtx.query = null;
619
- mutableCtx.body = undefined;
620
- mutableCtx.files = undefined;
621
- mutableCtx.requestId = '';
622
- mutableCtx.requestStart = 0;
623
- mutableCtx.serializer = null;
624
- mutableCtx.cookies = null;
625
- mutableCtx.state = null;
626
- mutableCtx.path = '';
627
- mutableCtx.error = undefined;
628
- // render method is static per context instance creation (closure over options),
629
- // but good to keep it consistent.
630
- // Wait, 'render' is defined in 'createContext' and depends on 'this.options'.
631
- // It doesn't hold request-specific state other than 'res' which is updated in 'acquireContext'.
632
- // So we don't need to null it out.
633
- if (this.contextPool.length < (this.options.maxConcurrency ?? 1024)) {
672
+ mutableCtx.reset();
673
+ if (this.contextPool.length < this.poolLimit) {
634
674
  this.contextPool.push(ctx);
635
675
  }
636
676
  }
637
- async handleRequest(req, res) {
677
+ async handleNoMatch(ctx, allowedMethods) {
678
+ const res = ctx.res;
679
+ const hasAnyMethod = allowedMethods.length > 0;
680
+ if (hasAnyMethod && this.methodNotAllowedHandler) {
681
+ const result = this.methodNotAllowedHandler(ctx, allowedMethods);
682
+ if (result && typeof result.then === 'function') {
683
+ await result;
684
+ }
685
+ if (!res.writableEnded && !res.headersSent) {
686
+ res.statusCode = 405;
687
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
688
+ res.end('Method Not Allowed');
689
+ }
690
+ }
691
+ else if (!hasAnyMethod && this.notFoundHandler) {
692
+ const result = this.notFoundHandler(ctx);
693
+ if (result && typeof result.then === 'function') {
694
+ await result;
695
+ }
696
+ if (!res.writableEnded && !res.headersSent) {
697
+ res.statusCode = 404;
698
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
699
+ res.end('Not Found');
700
+ }
701
+ }
702
+ else if (hasAnyMethod) {
703
+ if (!res.headersSent) {
704
+ res.statusCode = 405;
705
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
706
+ }
707
+ res.end('Method Not Allowed');
708
+ }
709
+ else {
710
+ if (!res.headersSent) {
711
+ res.statusCode = 404;
712
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
713
+ }
714
+ res.end('Not Found');
715
+ }
716
+ }
717
+ 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) ===
638
792
  const rawMethod = (req.method || 'GET').toUpperCase();
639
793
  const method = rawMethod;
640
794
  const rawUrl = req.url || '/';
641
- const host = req.headers.host || 'localhost';
642
- const url = new url_1.URL(rawUrl, `http://${host}`);
643
- const incomingRequestIdHeader = req.headers['x-request-id'];
644
- let requestId;
645
- if (typeof incomingRequestIdHeader === 'string') {
646
- requestId = incomingRequestIdHeader;
647
- }
648
- else if (Array.isArray(incomingRequestIdHeader)) {
649
- requestId = incomingRequestIdHeader[0];
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
+ }
650
808
  }
651
- if (!requestId) {
652
- requestId = this.generateRequestId();
809
+ else {
810
+ // No query string, just extract pathname
811
+ pathname = rawUrl;
653
812
  }
654
- if (!res.headersSent && requestId) {
655
- res.setHeader('x-request-id', requestId);
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
+ }
656
829
  }
657
- let match = this.router.match(method, url.pathname);
830
+ let match = this.router.match(method, pathname);
658
831
  const hasRoute = !!match;
659
832
  if (!match) {
660
- match = {
661
- handler: () => { },
662
- params: {},
663
- priority: types_1.RoutePriority.STANDARD,
664
- };
833
+ match = QHTTPX.EMPTY_MATCH;
665
834
  }
666
- const allowedMethods = this.router.getAllowedMethods(url.pathname);
835
+ const allowedMethods = this.router.getAllowedMethods(pathname);
667
836
  if (!hasRoute && !res.headersSent) {
668
837
  const hasAnyMethod = allowedMethods.length > 0;
669
838
  res.statusCode = hasAnyMethod ? 405 : 404;
670
839
  }
671
- const query = {};
672
- url.searchParams.forEach((value, key) => {
673
- if (Object.prototype.hasOwnProperty.call(query, key)) {
674
- const existing = query[key];
675
- if (Array.isArray(existing)) {
676
- existing.push(value);
677
- }
678
- else {
679
- query[key] = [existing, value];
680
- }
681
- }
682
- else {
683
- query[key] = value;
684
- }
685
- });
686
- if (this.options.maxMemoryBytes !== undefined) {
840
+ // Memory Checks (throttled to every 100 requests for performance)
841
+ if (this.options.maxMemoryBytes !== undefined && ++this.requestCounter % 100 === 0) {
687
842
  const sampleRss = process.memoryUsage().rss;
688
843
  const overloadedByMemory = (0, resources_1.isResourceOverloaded)({ rssBytes: sampleRss }, { maxRssBytes: this.options.maxMemoryBytes });
689
844
  if (overloadedByMemory) {
@@ -701,17 +856,15 @@ class QHTTPX {
701
856
  return;
702
857
  }
703
858
  }
704
- let body;
705
- let files;
859
+ else {
860
+ ++this.requestCounter;
861
+ }
706
862
  if (method !== 'GET' && method !== 'HEAD') {
707
- try {
708
- const parsed = await body_parser_1.BodyParser.parse(req, {
709
- maxBodyBytes: this.options.maxBodyBytes,
710
- });
711
- body = parsed.body;
712
- files = parsed.files;
713
- }
714
- catch (err) {
863
+ body_parser_1.BodyParser.parse(req, {
864
+ maxBodyBytes: this.options.maxBodyBytes,
865
+ }).then((parsed) => {
866
+ this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, parsed.body, parsed.files);
867
+ }).catch((err) => {
715
868
  if (err instanceof Error && err.message === 'QHTTPX_INVALID_JSON') {
716
869
  if (!res.headersSent) {
717
870
  res.statusCode = 400;
@@ -733,10 +886,14 @@ class QHTTPX {
733
886
  res.setHeader('content-type', 'text/plain; charset=utf-8');
734
887
  }
735
888
  res.end('Internal Server Error');
736
- return;
737
- }
889
+ });
890
+ return;
738
891
  }
739
- const ctx = this.acquireContext(req, res, url, match.params, query, requestId, body, files);
892
+ this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, undefined, undefined);
893
+ }
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);
896
+ // Balanced Mode Logic
740
897
  const overloaded = () => {
741
898
  if (res.writableEnded) {
742
899
  return;
@@ -761,14 +918,12 @@ class QHTTPX {
761
918
  };
762
919
  const start = Date.now();
763
920
  ctx.requestStart = start;
764
- if (!this.ultraMode) {
765
- this.metrics.onRequestStart();
766
- }
921
+ this.metrics.onRequestStart();
767
922
  if (this.tracer) {
768
923
  const event = {
769
924
  type: 'request_start',
770
925
  method,
771
- path: url.pathname,
926
+ path: pathname,
772
927
  requestId,
773
928
  };
774
929
  const result = this.tracer(event);
@@ -776,61 +931,43 @@ class QHTTPX {
776
931
  void result;
777
932
  }
778
933
  }
934
+ const finish = () => {
935
+ const duration = Date.now() - start;
936
+ this.metrics.onRequestEnd(duration, res.statusCode);
937
+ if (timedOut) {
938
+ this.metrics.onTimeout();
939
+ }
940
+ if (this.tracer) {
941
+ const event = {
942
+ type: 'request_end',
943
+ method,
944
+ path: pathname,
945
+ statusCode: res.statusCode,
946
+ durationMs: duration,
947
+ requestId,
948
+ };
949
+ const result = this.tracer(event);
950
+ if (result && typeof result.then === 'function') {
951
+ void result;
952
+ }
953
+ }
954
+ this.releaseContext(ctx);
955
+ };
779
956
  const handle = async () => {
780
957
  const handler = match.handler;
781
958
  try {
782
959
  if (hasRoute) {
783
- // Optimized path: Route handler is already compiled with middlewares
784
960
  const result = handler(ctx);
785
961
  if (result && typeof result.then === 'function') {
786
962
  await result;
787
963
  }
788
964
  }
789
965
  else if (this.pipelineRunner) {
790
- // Slow path: Run global middleware pipeline for 404/405
791
966
  await this.pipelineRunner(ctx, handler);
792
967
  }
793
968
  if (!res.writableEnded) {
794
969
  if (!hasRoute) {
795
- const hasAnyMethod = allowedMethods.length > 0;
796
- if (hasAnyMethod && this.methodNotAllowedHandler) {
797
- const result = this.methodNotAllowedHandler(ctx, allowedMethods);
798
- if (result &&
799
- typeof result.then === 'function') {
800
- await result;
801
- }
802
- if (!res.writableEnded && !res.headersSent) {
803
- res.statusCode = 405;
804
- res.setHeader('content-type', 'text/plain; charset=utf-8');
805
- res.end('Method Not Allowed');
806
- }
807
- }
808
- else if (!hasAnyMethod && this.notFoundHandler) {
809
- const result = this.notFoundHandler(ctx);
810
- if (result &&
811
- typeof result.then === 'function') {
812
- await result;
813
- }
814
- if (!res.writableEnded && !res.headersSent) {
815
- res.statusCode = 404;
816
- res.setHeader('content-type', 'text/plain; charset=utf-8');
817
- res.end('Not Found');
818
- }
819
- }
820
- else if (hasAnyMethod) {
821
- if (!res.headersSent) {
822
- res.statusCode = 405;
823
- res.setHeader('content-type', 'text/plain; charset=utf-8');
824
- }
825
- res.end('Method Not Allowed');
826
- }
827
- else {
828
- if (!res.headersSent) {
829
- res.statusCode = 404;
830
- res.setHeader('content-type', 'text/plain; charset=utf-8');
831
- }
832
- res.end('Not Found');
833
- }
970
+ await this.handleNoMatch(ctx, allowedMethods);
834
971
  }
835
972
  else if (!ctx.disableAutoEnd) {
836
973
  res.end();
@@ -841,39 +978,24 @@ class QHTTPX {
841
978
  await this.handleError(err, ctx);
842
979
  }
843
980
  };
981
+ // Ultra mode: skip scheduler for minimal overhead
844
982
  if (this.ultraMode) {
845
- await handle();
983
+ handle().then(finish).catch((err) => {
984
+ console.error('Request handler error:', err);
985
+ finish();
986
+ });
846
987
  }
847
988
  else {
848
- await this.scheduler.run(handle, {
989
+ this.scheduler.run(handle, {
849
990
  priority: match.priority,
850
991
  onOverloaded: overloaded,
851
992
  timeoutMs: this.options.requestTimeoutMs,
852
993
  onTimeout,
994
+ }).then(finish).catch((err) => {
995
+ console.error('Scheduler error:', err);
996
+ finish();
853
997
  });
854
998
  }
855
- const duration = Date.now() - start;
856
- if (!this.ultraMode) {
857
- this.metrics.onRequestEnd(duration, res.statusCode);
858
- if (timedOut) {
859
- this.metrics.onTimeout();
860
- }
861
- }
862
- if (this.tracer) {
863
- const event = {
864
- type: 'request_end',
865
- method,
866
- path: url.pathname,
867
- statusCode: res.statusCode,
868
- durationMs: duration,
869
- requestId,
870
- };
871
- const result = this.tracer(event);
872
- if (result && typeof result.then === 'function') {
873
- void result;
874
- }
875
- }
876
- this.releaseContext(ctx);
877
999
  }
878
1000
  async handleUpgrade(req, socket, head) {
879
1001
  await this.wsManager.handleUpgrade(req, socket, head);
@@ -952,3 +1074,9 @@ class QHTTPX {
952
1074
  }
953
1075
  }
954
1076
  exports.QHTTPX = QHTTPX;
1077
+ QHTTPX.EMPTY_PARAMS = Object.freeze({});
1078
+ QHTTPX.EMPTY_MATCH = Object.freeze({
1079
+ handler: () => { },
1080
+ params: QHTTPX.EMPTY_PARAMS,
1081
+ priority: types_1.RoutePriority.STANDARD,
1082
+ });