qhttpx 2.1.0 → 2.3.1

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 (267) hide show
  1. package/LICENSE +201 -21
  2. package/README.md +117 -221
  3. package/dist/chunk-QW72SEAS.mjs +98 -0
  4. package/dist/{src/cli/index.d.ts → cli.d.mts} +0 -1
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +287 -0
  7. package/dist/cli.mjs +209 -0
  8. package/dist/index.d.mts +433 -0
  9. package/dist/index.d.ts +433 -0
  10. package/dist/index.js +1955 -0
  11. package/dist/index.mjs +1863 -0
  12. package/dist/qhttpx-core-new.linux-x64-gnu.node +0 -0
  13. package/dist/qhttpx-core-new.node +0 -0
  14. package/dist/qhttpx-core-new.win32-x64-msvc.node +0 -0
  15. package/examples/benchmark.ts +67 -0
  16. package/examples/body_demo.ts +32 -0
  17. package/examples/chat_client.ts +36 -0
  18. package/examples/chat_demo.ts +41 -0
  19. package/examples/cluster_demo.ts +26 -0
  20. package/examples/cors_demo.ts +25 -0
  21. package/examples/db_auth_demo.ts +66 -0
  22. package/examples/demo.ts +52 -0
  23. package/examples/headers_demo.ts +24 -0
  24. package/examples/hello.ts +7 -0
  25. package/examples/http2_demo.ts +52 -0
  26. package/examples/magic-dev.ts +15 -0
  27. package/examples/middleware_demo.ts +34 -0
  28. package/examples/mongo_demo.ts +40 -0
  29. package/examples/native_policy_demo.ts +33 -0
  30. package/examples/observability_demo.ts +24 -0
  31. package/examples/public/1mb.dat +1 -0
  32. package/examples/query_demo.ts +29 -0
  33. package/examples/response_demo.ts +21 -0
  34. package/examples/routing_demo.ts +24 -0
  35. package/examples/server.ts +51 -0
  36. package/examples/static_demo.ts +29 -0
  37. package/examples/test_middleware_client.ts +33 -0
  38. package/examples/test_query_client.ts +34 -0
  39. package/examples/test_response_client.ts +30 -0
  40. package/examples/tls_demo.ts +32 -0
  41. package/examples/upload_demo.ts +43 -0
  42. package/examples/uploads/test.txt +1 -0
  43. package/examples/verify_upload.ts +69 -0
  44. package/examples/ws_client.ts +26 -0
  45. package/examples/ws_demo.ts +21 -0
  46. package/package.json +65 -81
  47. package/CHANGELOG.md +0 -285
  48. package/dist/examples/api-server.d.ts +0 -1
  49. package/dist/examples/api-server.js +0 -80
  50. package/dist/examples/basic.d.ts +0 -1
  51. package/dist/examples/basic.js +0 -9
  52. package/dist/examples/compression.d.ts +0 -1
  53. package/dist/examples/compression.js +0 -15
  54. package/dist/examples/cors.d.ts +0 -1
  55. package/dist/examples/cors.js +0 -18
  56. package/dist/examples/errors.d.ts +0 -1
  57. package/dist/examples/errors.js +0 -26
  58. package/dist/examples/file-upload.d.ts +0 -1
  59. package/dist/examples/file-upload.js +0 -22
  60. package/dist/examples/fusion.d.ts +0 -1
  61. package/dist/examples/fusion.js +0 -21
  62. package/dist/examples/rate-limiting.d.ts +0 -1
  63. package/dist/examples/rate-limiting.js +0 -17
  64. package/dist/examples/validation.d.ts +0 -1
  65. package/dist/examples/validation.js +0 -22
  66. package/dist/examples/websockets.d.ts +0 -1
  67. package/dist/examples/websockets.js +0 -19
  68. package/dist/package.json +0 -107
  69. package/dist/src/benchmarks/quantam-users.d.ts +0 -1
  70. package/dist/src/benchmarks/quantam-users.js +0 -56
  71. package/dist/src/benchmarks/quick-bench.d.ts +0 -1
  72. package/dist/src/benchmarks/quick-bench.js +0 -57
  73. package/dist/src/benchmarks/simple-json.d.ts +0 -1
  74. package/dist/src/benchmarks/simple-json.js +0 -171
  75. package/dist/src/benchmarks/ultra-mode.d.ts +0 -1
  76. package/dist/src/benchmarks/ultra-mode.js +0 -64
  77. package/dist/src/cli/index.js +0 -222
  78. package/dist/src/client/index.d.ts +0 -17
  79. package/dist/src/client/index.js +0 -72
  80. package/dist/src/core/batch.d.ts +0 -24
  81. package/dist/src/core/batch.js +0 -97
  82. package/dist/src/core/body-parser.d.ts +0 -15
  83. package/dist/src/core/body-parser.js +0 -121
  84. package/dist/src/core/buffer-pool.d.ts +0 -41
  85. package/dist/src/core/buffer-pool.js +0 -70
  86. package/dist/src/core/config.d.ts +0 -7
  87. package/dist/src/core/config.js +0 -50
  88. package/dist/src/core/context-pool.d.ts +0 -12
  89. package/dist/src/core/context-pool.js +0 -34
  90. package/dist/src/core/errors.d.ts +0 -34
  91. package/dist/src/core/errors.js +0 -70
  92. package/dist/src/core/fusion.d.ts +0 -20
  93. package/dist/src/core/fusion.js +0 -191
  94. package/dist/src/core/logger.d.ts +0 -22
  95. package/dist/src/core/logger.js +0 -49
  96. package/dist/src/core/metrics.d.ts +0 -50
  97. package/dist/src/core/metrics.js +0 -123
  98. package/dist/src/core/resources.d.ts +0 -9
  99. package/dist/src/core/resources.js +0 -25
  100. package/dist/src/core/scheduler.d.ts +0 -38
  101. package/dist/src/core/scheduler.js +0 -126
  102. package/dist/src/core/scope.d.ts +0 -41
  103. package/dist/src/core/scope.js +0 -107
  104. package/dist/src/core/serializer.d.ts +0 -10
  105. package/dist/src/core/serializer.js +0 -82
  106. package/dist/src/core/server.d.ts +0 -179
  107. package/dist/src/core/server.js +0 -1511
  108. package/dist/src/core/stream.d.ts +0 -15
  109. package/dist/src/core/stream.js +0 -71
  110. package/dist/src/core/tasks.d.ts +0 -29
  111. package/dist/src/core/tasks.js +0 -87
  112. package/dist/src/core/timer.d.ts +0 -11
  113. package/dist/src/core/timer.js +0 -29
  114. package/dist/src/core/types.d.ts +0 -225
  115. package/dist/src/core/types.js +0 -19
  116. package/dist/src/core/websocket.d.ts +0 -25
  117. package/dist/src/core/websocket.js +0 -86
  118. package/dist/src/core/worker-queue.d.ts +0 -41
  119. package/dist/src/core/worker-queue.js +0 -73
  120. package/dist/src/database/adapters/memory.d.ts +0 -21
  121. package/dist/src/database/adapters/memory.js +0 -90
  122. package/dist/src/database/adapters/mongo.d.ts +0 -11
  123. package/dist/src/database/adapters/mongo.js +0 -141
  124. package/dist/src/database/adapters/postgres.d.ts +0 -10
  125. package/dist/src/database/adapters/postgres.js +0 -111
  126. package/dist/src/database/adapters/sqlite.d.ts +0 -10
  127. package/dist/src/database/adapters/sqlite.js +0 -42
  128. package/dist/src/database/coalescer.d.ts +0 -14
  129. package/dist/src/database/coalescer.js +0 -134
  130. package/dist/src/database/manager.d.ts +0 -35
  131. package/dist/src/database/manager.js +0 -87
  132. package/dist/src/database/types.d.ts +0 -20
  133. package/dist/src/database/types.js +0 -2
  134. package/dist/src/index.d.ts +0 -52
  135. package/dist/src/index.js +0 -92
  136. package/dist/src/middleware/compression.d.ts +0 -2
  137. package/dist/src/middleware/compression.js +0 -133
  138. package/dist/src/middleware/cors.d.ts +0 -2
  139. package/dist/src/middleware/cors.js +0 -66
  140. package/dist/src/middleware/presets.d.ts +0 -15
  141. package/dist/src/middleware/presets.js +0 -52
  142. package/dist/src/middleware/rate-limit.d.ts +0 -14
  143. package/dist/src/middleware/rate-limit.js +0 -83
  144. package/dist/src/middleware/security.d.ts +0 -10
  145. package/dist/src/middleware/security.js +0 -74
  146. package/dist/src/middleware/static.d.ts +0 -11
  147. package/dist/src/middleware/static.js +0 -191
  148. package/dist/src/openapi/generator.d.ts +0 -19
  149. package/dist/src/openapi/generator.js +0 -149
  150. package/dist/src/router/radix-router.d.ts +0 -18
  151. package/dist/src/router/radix-router.js +0 -89
  152. package/dist/src/router/radix-tree.d.ts +0 -21
  153. package/dist/src/router/radix-tree.js +0 -175
  154. package/dist/src/router/router.d.ts +0 -37
  155. package/dist/src/router/router.js +0 -203
  156. package/dist/src/testing/index.d.ts +0 -25
  157. package/dist/src/testing/index.js +0 -84
  158. package/dist/src/utils/cookies.d.ts +0 -3
  159. package/dist/src/utils/cookies.js +0 -59
  160. package/dist/src/utils/logger.d.ts +0 -2
  161. package/dist/src/utils/logger.js +0 -45
  162. package/dist/src/utils/signals.d.ts +0 -6
  163. package/dist/src/utils/signals.js +0 -31
  164. package/dist/src/utils/sse.d.ts +0 -6
  165. package/dist/src/utils/sse.js +0 -32
  166. package/dist/src/validation/index.d.ts +0 -3
  167. package/dist/src/validation/index.js +0 -19
  168. package/dist/src/validation/simple.d.ts +0 -5
  169. package/dist/src/validation/simple.js +0 -102
  170. package/dist/src/validation/types.d.ts +0 -32
  171. package/dist/src/validation/types.js +0 -12
  172. package/dist/src/validation/zod.d.ts +0 -4
  173. package/dist/src/validation/zod.js +0 -18
  174. package/dist/src/views/index.d.ts +0 -1
  175. package/dist/src/views/index.js +0 -17
  176. package/dist/src/views/types.d.ts +0 -3
  177. package/dist/src/views/types.js +0 -2
  178. package/dist/tests/adapters.test.d.ts +0 -1
  179. package/dist/tests/adapters.test.js +0 -106
  180. package/dist/tests/batch.test.d.ts +0 -1
  181. package/dist/tests/batch.test.js +0 -117
  182. package/dist/tests/body-parser.test.d.ts +0 -1
  183. package/dist/tests/body-parser.test.js +0 -52
  184. package/dist/tests/compression-sse.test.d.ts +0 -1
  185. package/dist/tests/compression-sse.test.js +0 -87
  186. package/dist/tests/cookies.test.d.ts +0 -1
  187. package/dist/tests/cookies.test.js +0 -63
  188. package/dist/tests/cors.test.d.ts +0 -1
  189. package/dist/tests/cors.test.js +0 -55
  190. package/dist/tests/database.test.d.ts +0 -1
  191. package/dist/tests/database.test.js +0 -80
  192. package/dist/tests/dx.test.d.ts +0 -1
  193. package/dist/tests/dx.test.js +0 -114
  194. package/dist/tests/ecosystem.test.d.ts +0 -1
  195. package/dist/tests/ecosystem.test.js +0 -133
  196. package/dist/tests/features.test.d.ts +0 -1
  197. package/dist/tests/features.test.js +0 -47
  198. package/dist/tests/fusion.test.d.ts +0 -1
  199. package/dist/tests/fusion.test.js +0 -92
  200. package/dist/tests/http-basic.test.d.ts +0 -1
  201. package/dist/tests/http-basic.test.js +0 -124
  202. package/dist/tests/logger.test.d.ts +0 -1
  203. package/dist/tests/logger.test.js +0 -33
  204. package/dist/tests/middleware.test.d.ts +0 -1
  205. package/dist/tests/middleware.test.js +0 -109
  206. package/dist/tests/observability.test.d.ts +0 -1
  207. package/dist/tests/observability.test.js +0 -59
  208. package/dist/tests/openapi.test.d.ts +0 -1
  209. package/dist/tests/openapi.test.js +0 -64
  210. package/dist/tests/plugin.test.d.ts +0 -1
  211. package/dist/tests/plugin.test.js +0 -65
  212. package/dist/tests/plugins.test.d.ts +0 -1
  213. package/dist/tests/plugins.test.js +0 -71
  214. package/dist/tests/rate-limit.test.d.ts +0 -1
  215. package/dist/tests/rate-limit.test.js +0 -77
  216. package/dist/tests/resources.test.d.ts +0 -1
  217. package/dist/tests/resources.test.js +0 -47
  218. package/dist/tests/scheduler.test.d.ts +0 -1
  219. package/dist/tests/scheduler.test.js +0 -46
  220. package/dist/tests/schema-routes.test.d.ts +0 -1
  221. package/dist/tests/schema-routes.test.js +0 -79
  222. package/dist/tests/security.test.d.ts +0 -1
  223. package/dist/tests/security.test.js +0 -83
  224. package/dist/tests/server-db.test.d.ts +0 -1
  225. package/dist/tests/server-db.test.js +0 -72
  226. package/dist/tests/smoke.test.d.ts +0 -1
  227. package/dist/tests/smoke.test.js +0 -10
  228. package/dist/tests/sqlite-fusion.test.d.ts +0 -1
  229. package/dist/tests/sqlite-fusion.test.js +0 -92
  230. package/dist/tests/static.test.d.ts +0 -1
  231. package/dist/tests/static.test.js +0 -102
  232. package/dist/tests/stream.test.d.ts +0 -1
  233. package/dist/tests/stream.test.js +0 -44
  234. package/dist/tests/task-metrics.test.d.ts +0 -1
  235. package/dist/tests/task-metrics.test.js +0 -53
  236. package/dist/tests/tasks.test.d.ts +0 -1
  237. package/dist/tests/tasks.test.js +0 -62
  238. package/dist/tests/testing.test.d.ts +0 -1
  239. package/dist/tests/testing.test.js +0 -47
  240. package/dist/tests/validation.test.d.ts +0 -1
  241. package/dist/tests/validation.test.js +0 -107
  242. package/dist/tests/websocket.test.d.ts +0 -1
  243. package/dist/tests/websocket.test.js +0 -146
  244. package/dist/vitest.config.d.ts +0 -2
  245. package/dist/vitest.config.js +0 -9
  246. package/docs/AEGIS.md +0 -91
  247. package/docs/API_REFERENCE.md +0 -749
  248. package/docs/BENCHMARKS.md +0 -39
  249. package/docs/CAPABILITIES.md +0 -70
  250. package/docs/CLI.md +0 -43
  251. package/docs/DATABASE.md +0 -142
  252. package/docs/ECOSYSTEM.md +0 -146
  253. package/docs/ERRORS.md +0 -112
  254. package/docs/FUSION.md +0 -87
  255. package/docs/MIDDLEWARE.md +0 -65
  256. package/docs/MIGRATION_1.9_TO_2.0.md +0 -495
  257. package/docs/NEXT_STEPS.md +0 -99
  258. package/docs/OPENAPI.md +0 -99
  259. package/docs/PLUGINS.md +0 -59
  260. package/docs/PRODUCTION_DEPLOYMENT.md +0 -798
  261. package/docs/REAL_WORLD_EXAMPLES.md +0 -109
  262. package/docs/ROADMAP.md +0 -366
  263. package/docs/ROUTING.md +0 -78
  264. package/docs/SECURITY.md +0 -876
  265. package/docs/STATIC.md +0 -61
  266. package/docs/VALIDATION.md +0 -114
  267. package/docs/WEBSOCKETS.md +0 -76
@@ -1,1511 +0,0 @@
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
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.QHTTPX = exports.QHTTPXContextImpl = void 0;
40
- const http_1 = __importDefault(require("http"));
41
- const path_1 = __importDefault(require("path"));
42
- const url_1 = require("url");
43
- const querystring_1 = require("querystring");
44
- const package_json_1 = __importDefault(require("../../package.json"));
45
- const router_1 = require("../router/router");
46
- const scheduler_1 = require("./scheduler");
47
- const tasks_1 = require("./tasks");
48
- const resources_1 = require("./resources");
49
- const metrics_1 = require("./metrics");
50
- const body_parser_1 = require("./body-parser");
51
- const buffer_pool_1 = require("./buffer-pool");
52
- const websocket_1 = require("./websocket");
53
- const serializer_1 = require("./serializer");
54
- const batch_1 = require("./batch");
55
- const fusion_1 = require("./fusion");
56
- const simple_1 = require("../validation/simple");
57
- const zod_1 = require("../validation/zod");
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");
64
- const types_1 = require("./types");
65
- const cookies_1 = require("../utils/cookies");
66
- const context_pool_1 = require("./context-pool");
67
- const scope_1 = require("./scope");
68
- const logger_2 = require("./logger");
69
- const timer_1 = require("./timer");
70
- const serializer_2 = require("./serializer");
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
- };
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
- }
88
- constructor(app) {
89
- this._url = null;
90
- // Dirty tracking for optimization
91
- this._generation = 0;
92
- this._dirty = 0;
93
- this._customHeaders = null;
94
- this._app = app;
95
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
96
- this._appJsonSerializer = app.options.jsonSerializer;
97
- this.json = this.json.bind(this);
98
- this.send = this.send.bind(this);
99
- this.html = this.html.bind(this);
100
- this.redirect = this.redirect.bind(this);
101
- this.setCookie = this.setCookie.bind(this);
102
- this.render = this.render.bind(this);
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
- }
234
- }
235
- get bufferPool() {
236
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
237
- return this._app.bufferPool;
238
- }
239
- get db() {
240
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
241
- return this._app.options.database;
242
- }
243
- get state() {
244
- if (!this._state) {
245
- this._state = {};
246
- this._dirty |= 0b100000;
247
- }
248
- return this._state;
249
- }
250
- set state(v) {
251
- this._state = v;
252
- this._dirty |= 0b100000;
253
- }
254
- get ip() {
255
- if (this._ip)
256
- return this._ip;
257
- this._ip = this.req.socket.remoteAddress || '';
258
- return this._ip;
259
- }
260
- // Setter for manual override if needed (though usually read-only from socket)
261
- set ip(v) {
262
- this._ip = v;
263
- }
264
- get url() {
265
- if (this._url)
266
- return this._url;
267
- const host = this.headers.host || 'localhost';
268
- this._url = new url_1.URL(this.req.url || '/', `http://${host}`);
269
- return this._url;
270
- }
271
- set url(v) {
272
- this._url = v;
273
- }
274
- json(payload, status = 200) {
275
- const res = this.res;
276
- let body;
277
- if (this.serializer) {
278
- body = this.serializer(payload);
279
- }
280
- else if (this._appJsonSerializer) {
281
- body = this._appJsonSerializer(payload);
282
- }
283
- else {
284
- // Use optimized fast path serializer
285
- body = (0, serializer_2.fastJsonStringify)(payload);
286
- }
287
- if (!res.headersSent) {
288
- res.statusCode = status;
289
- res.setHeader('content-type', CONTENT_TYPE.JSON);
290
- }
291
- res.end(body);
292
- }
293
- send(payload, status = 200) {
294
- const res = this.res;
295
- if (!res.headersSent) {
296
- res.statusCode = status;
297
- }
298
- res.end(payload);
299
- }
300
- html(payload, status = 200) {
301
- const res = this.res;
302
- if (!res.headersSent) {
303
- res.statusCode = status;
304
- res.setHeader('content-type', CONTENT_TYPE.HTML);
305
- }
306
- res.end(payload);
307
- }
308
- redirect(url, status = 302) {
309
- const res = this.res;
310
- if (!res.headersSent) {
311
- res.statusCode = status;
312
- res.setHeader('Location', url);
313
- }
314
- res.end();
315
- }
316
- setCookie(name, value, options) {
317
- const res = this.res;
318
- const serialized = (0, cookies_1.serializeCookie)(name, value, options);
319
- let existing = res.getHeader('Set-Cookie');
320
- if (Array.isArray(existing)) {
321
- existing.push(serialized);
322
- res.setHeader('Set-Cookie', existing);
323
- }
324
- else if (existing) {
325
- res.setHeader('Set-Cookie', [existing, serialized]);
326
- }
327
- else {
328
- res.setHeader('Set-Cookie', serialized);
329
- }
330
- }
331
- async render(view, locals) {
332
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
333
- const engine = this._app.options.viewEngine;
334
- if (!engine) {
335
- throw new Error('No view engine registered');
336
- }
337
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
338
- const viewsPath = this._app.options.viewsPath || process.cwd();
339
- const fullPath = path_1.default.resolve(viewsPath, view);
340
- const html = await engine.render(fullPath, locals || {});
341
- const res = this.res;
342
- if (!res.headersSent) {
343
- res.statusCode = 200;
344
- res.setHeader('content-type', CONTENT_TYPE.HTML);
345
- }
346
- res.end(html);
347
- }
348
- async validate(schema, data) {
349
- const target = data ?? this.body;
350
- const result = await this._app.validator.validate(schema, target);
351
- if (result.success) {
352
- return result.data;
353
- }
354
- throw new types_1.HttpError(400, 'Validation Error', {
355
- code: 'VALIDATION_ERROR',
356
- details: result.error,
357
- });
358
- }
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;
384
- this.req = null;
385
- this.res = null;
386
- this.headers = null;
387
- this._url = null;
388
- this.method = null;
389
- this._ip = undefined;
390
- this.requestId = undefined;
391
- this.requestStart = undefined;
392
- this.serializer = undefined;
393
- this.path = '';
394
- this.disableAutoEnd = false;
395
- this.error = undefined;
396
- this.next = undefined;
397
- }
398
- }
399
- exports.QHTTPXContextImpl = QHTTPXContextImpl;
400
- class QHTTPX {
401
- constructor(options = {}) {
402
- this.middlewares = [];
403
- this.pipelineRunner = null;
404
- this.onStartHooks = [];
405
- this.onBeforeShutdownHooks = [];
406
- this.onShutdownHooks = [];
407
- this.nextRequestId = 1;
408
- this.lastDateNow = Date.now();
409
- this.lastDateString = this.lastDateNow.toString(36);
410
- this.requestCounter = 0;
411
- this.options = options;
412
- this.logger = new logger_2.Logger({
413
- name: options.name,
414
- level: 'info'
415
- });
416
- this.errorHandler = options.errorHandler;
417
- this.notFoundHandler = options.notFoundHandler;
418
- this.methodNotAllowedHandler = options.methodNotAllowedHandler;
419
- this.tracer = options.tracer;
420
- this.workerCount = (0, resources_1.calculateWorkerCount)(options.workers ?? 'auto');
421
- this.router = new router_1.Router();
422
- this.wsManager = new websocket_1.WebSocketManager(this.generateRequestId.bind(this));
423
- this.bufferPool = new buffer_pool_1.BufferPool(options.bufferPoolConfig);
424
- const maxConcurrency = options.maxConcurrency ?? 1024;
425
- this.poolLimit = maxConcurrency * 2;
426
- this.scheduler = new scheduler_1.Scheduler({
427
- maxConcurrency,
428
- });
429
- this.tasks = new tasks_1.TaskEngine(this.scheduler);
430
- if (options.enableBatching) {
431
- this.batchExecutor = new batch_1.BatchExecutor(options.database);
432
- }
433
- if (options.enableRequestFusion) {
434
- this._fusion = new fusion_1.RequestFusion(options.enableRequestFusion);
435
- }
436
- this.validator = options.validator ?? new simple_1.SimpleValidator();
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);
441
- this.registerInternalRoutes();
442
- this.compileMiddlewarePipeline();
443
- this.server = http_1.default.createServer(this.handleRequest.bind(this));
444
- if (this.options.keepAliveTimeoutMs !== undefined) {
445
- this.server.keepAliveTimeout = this.options.keepAliveTimeoutMs;
446
- }
447
- if (this.options.headersTimeoutMs !== undefined) {
448
- this.server.headersTimeout = this.options.headersTimeoutMs;
449
- }
450
- // Keep-alive and timeout settings handled by options above
451
- this.server.on('upgrade', (req, socket, head) => {
452
- void this.handleUpgrade(req, socket, head);
453
- });
454
- }
455
- get serverInstance() {
456
- return this.server;
457
- }
458
- get websocket() {
459
- return this.wsManager;
460
- }
461
- setErrorHandler(handler) {
462
- this.errorHandler = handler;
463
- }
464
- setNotFoundHandler(handler) {
465
- this.notFoundHandler = handler;
466
- }
467
- setMethodNotAllowedHandler(handler) {
468
- this.methodNotAllowedHandler = handler;
469
- }
470
- set404Handler(handler) {
471
- this.setNotFoundHandler(handler);
472
- }
473
- set405Handler(handler) {
474
- this.setMethodNotAllowedHandler(handler);
475
- }
476
- /**
477
- * Alias for setErrorHandler
478
- */
479
- onError(handler) {
480
- this.setErrorHandler(handler);
481
- }
482
- /**
483
- * Alias for setNotFoundHandler
484
- */
485
- notFound(handler) {
486
- this.setNotFoundHandler(handler);
487
- }
488
- onStart(hook) {
489
- this.onStartHooks.push(hook);
490
- }
491
- onBeforeShutdown(hook) {
492
- this.onBeforeShutdownHooks.push(hook);
493
- }
494
- onShutdown(hook) {
495
- this.onShutdownHooks.push(hook);
496
- }
497
- upgrade(path, handler) {
498
- this.wsManager.register(path, handler);
499
- }
500
- use(middleware) {
501
- this.middlewares.push(middleware);
502
- // Recompile pipeline after middleware is added
503
- this.compileMiddlewarePipeline();
504
- }
505
- compileMiddlewarePipeline() {
506
- const middlewares = this.middlewares;
507
- let runner;
508
- if (middlewares.length === 0) {
509
- runner = async (ctx, handler) => {
510
- const result = handler(ctx);
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));
530
- }
531
- };
532
- }
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);
565
- if (result && typeof result.then === 'function') {
566
- await result;
567
- }
568
- };
569
- await executeNext();
570
- };
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
- }
585
- }
586
- compileRoutePipeline(handler,
587
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
588
- schema) {
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
- }
595
- // Heuristic: Is it a structured RouteSchema or a legacy ResponseSchema?
596
- let responseSchema;
597
- let requestSchema;
598
- if (schema) {
599
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
600
- const s = schema;
601
- if (s.body || s.query || s.params || s.headers || s.response) {
602
- // It is a RouteSchema
603
- requestSchema = s;
604
- responseSchema = s.response;
605
- }
606
- else {
607
- // It is a legacy ResponseSchema
608
- responseSchema = s;
609
- }
610
- }
611
- const stringifier = responseSchema ? (0, serializer_1.getStringifier)(responseSchema) : undefined;
612
- // Build middleware chain
613
- let pipeline = handler;
614
- // Wrap with validation if needed (before handler, after global middleware)
615
- if (requestSchema) {
616
- const inner = pipeline;
617
- pipeline = async (ctx) => {
618
- // Validate Body
619
- if (requestSchema.body) {
620
- const result = await this.validator.validate(requestSchema.body, ctx.body);
621
- if (!result.success) {
622
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
623
- const err = result.error;
624
- ctx.json({ error: 'Validation Error', details: err.details || err.message || err }, 400);
625
- return;
626
- }
627
- ctx.body = result.data;
628
- }
629
- // Validate Query
630
- if (requestSchema.query) {
631
- const result = await this.validator.validate(requestSchema.query, ctx.query);
632
- if (!result.success) {
633
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
634
- const err = result.error;
635
- ctx.json({ error: 'Validation Error', details: err.details || err.message || err }, 400);
636
- return;
637
- }
638
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
639
- ctx.query = result.data;
640
- }
641
- // Validate Params
642
- if (requestSchema.params) {
643
- const result = await this.validator.validate(requestSchema.params, ctx.params);
644
- if (!result.success) {
645
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
646
- const err = result.error;
647
- ctx.json({ error: 'Validation Error', details: err.details || err.message || err }, 400);
648
- return;
649
- }
650
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
651
- ctx.params = result.data;
652
- }
653
- return inner(ctx);
654
- };
655
- }
656
- // Wrap with Request Fusion if enabled
657
- if (this._fusion) {
658
- const inner = pipeline;
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);
681
- if (result && typeof result.then === 'function') {
682
- await result;
683
- }
684
- };
685
- await executeNext();
686
- };
687
- }
688
- if (stringifier) {
689
- const inner = pipeline;
690
- return (ctx) => {
691
- ctx.serializer = stringifier;
692
- return inner(ctx);
693
- };
694
- }
695
- return pipeline;
696
- }
697
- // Internal registration to be accessed by Scopes
698
- _registerRoute(method, path, handlerOrOptions, handlerIfOptions) {
699
- this.registerRoute(method, path, handlerOrOptions, handlerIfOptions);
700
- }
701
- async register(plugin, options) {
702
- const scope = new scope_1.QHTTPXScope(this, options?.prefix);
703
- await plugin(scope, options);
704
- }
705
- registerRoute(method, path, handlerOrOptions, handlerIfOptions) {
706
- let handler;
707
- let schema;
708
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
709
- let staticResponse;
710
- const options = {};
711
- if (typeof handlerOrOptions === 'function') {
712
- handler = handlerOrOptions;
713
- }
714
- else if (handlerIfOptions) {
715
- handler = handlerIfOptions;
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;
732
- }
733
- else {
734
- const opts = handlerOrOptions;
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
- }
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
- };
770
- }
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
- };
782
- this.router.register(method, path, compiled, { ...options, schema });
783
- }
784
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
785
- get(path, arg1, arg2) {
786
- this.registerRoute('GET', path, arg1, arg2);
787
- }
788
- post(path, handlerOrOptions, handlerOrConfig) {
789
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
790
- this.registerRoute('POST', path, handlerOrOptions, handlerOrConfig);
791
- }
792
- put(path, handlerOrOptions, handlerOrConfig) {
793
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
794
- this.registerRoute('PUT', path, handlerOrOptions, handlerOrConfig);
795
- }
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);
803
- }
804
- route(path) {
805
- const register = (method, handler) => {
806
- this.registerRoute(method, path, handler);
807
- };
808
- const builder = {
809
- get(handler) {
810
- register('GET', handler);
811
- return this;
812
- },
813
- post(handler) {
814
- register('POST', handler);
815
- return this;
816
- },
817
- put(handler) {
818
- register('PUT', handler);
819
- return this;
820
- },
821
- delete(handler) {
822
- register('DELETE', handler);
823
- return this;
824
- },
825
- };
826
- return builder;
827
- }
828
- task(name, handler, options) {
829
- this.tasks.register(name, handler, options);
830
- }
831
- enqueue(name, payload) {
832
- return this.tasks.enqueue(name, payload);
833
- }
834
- op(name, handler) {
835
- if (!this.batchExecutor) {
836
- // Auto-enable batch executor if op is called?
837
- // Or throw?
838
- // Better to throw or warn if not enabled.
839
- // But for ease of use, let's create it if missing (but we miss DB context if done this way without options)
840
- // The constructor handles options. If enableBatching is false, we shouldn't be here.
841
- throw new Error('Batching is not enabled. Pass enableBatching: true to QHTTPX options.');
842
- }
843
- this.batchExecutor.register(name, handler);
844
- }
845
- registerInternalRoutes() {
846
- this.router.register('GET', '/__qhttpx/health', ({ json }) => {
847
- const version = typeof package_json_1.default.version === 'string' ? package_json_1.default.version : '';
848
- const name = typeof package_json_1.default.name === 'string' ? package_json_1.default.name : '';
849
- json({
850
- status: 'ok',
851
- name,
852
- version,
853
- workers: this.workerCount,
854
- });
855
- });
856
- this.router.register('GET', '/__qhttpx/metrics', ({ json }) => {
857
- const snapshot = this._metrics.snapshot();
858
- json({
859
- ...snapshot,
860
- workers: this.workerCount,
861
- });
862
- });
863
- this.router.register('GET', '/__qhttpx/runtime', ({ json }) => {
864
- const schedulerStats = this.scheduler.getStats();
865
- json({
866
- workers: this.workerCount,
867
- router: {
868
- frozen: this.router.isFrozenRouter(),
869
- },
870
- scheduler: schedulerStats,
871
- });
872
- });
873
- if (this.options.enableBatching && this.batchExecutor) {
874
- const endpoint = typeof this.options.enableBatching === 'object'
875
- ? this.options.enableBatching.endpoint
876
- : '/qhttpx';
877
- this.router.register('POST', endpoint, async (ctx) => {
878
- // Keep full ctx here as we pass it to handleBatch
879
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
880
- const body = ctx.body;
881
- if (!body || !Array.isArray(body.batch)) {
882
- ctx.json({ error: 'Invalid batch format' }, 400);
883
- return;
884
- }
885
- try {
886
- const result = await this.batchExecutor.handleBatch(ctx, body.batch);
887
- ctx.json(result);
888
- }
889
- catch (err) {
890
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
891
- ctx.json({ error: err.message }, 500);
892
- }
893
- });
894
- }
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
- this._metrics.setEnabled(enable);
1037
- return this;
1038
- }
1039
- error(status, message, details) {
1040
- return new types_1.HttpError(status, message, { details });
1041
- }
1042
- start(port) {
1043
- return this.listen(port);
1044
- }
1045
- getOpenAPI(options) {
1046
- const generator = new generator_1.OpenAPIGenerator(this.router, options);
1047
- return generator.generate();
1048
- }
1049
- async listen(port, hostnameOrCallback, callback) {
1050
- // Ultra-Simple API: Clustering support in Production
1051
- if (this.options.performanceMode === 'default' && !process.env.QHTTPX_NO_CLUSTER) {
1052
- const cluster = await Promise.resolve().then(() => __importStar(require('cluster')));
1053
- const os = await Promise.resolve().then(() => __importStar(require('os')));
1054
- if (cluster.default.isPrimary) {
1055
- const numCPUs = os.cpus().length;
1056
- console.log(`Primary ${process.pid} is running. Forking ${numCPUs} workers...`);
1057
- for (let i = 0; i < numCPUs; i++) {
1058
- cluster.default.fork();
1059
- }
1060
- cluster.default.on('exit', (worker) => {
1061
- console.log(`worker ${worker.process.pid} died`);
1062
- });
1063
- // Return a dummy promise for primary, it keeps running
1064
- return new Promise(() => { });
1065
- }
1066
- else {
1067
- // Worker process falls through to normal listen
1068
- }
1069
- }
1070
- let hostname;
1071
- let cb;
1072
- if (typeof hostnameOrCallback === 'function') {
1073
- cb = hostnameOrCallback;
1074
- hostname = undefined;
1075
- }
1076
- else {
1077
- hostname = hostnameOrCallback;
1078
- cb = callback;
1079
- }
1080
- if (this.options.database) {
1081
- await this.options.database.connect();
1082
- }
1083
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1084
- if (this.options.plugins) {
1085
- // Plugins are registered synchronously in constructor, but their async init happens now?
1086
- // Actually, plugins are registered via app.register() which is async.
1087
- // So they should be ready.
1088
- }
1089
- return new Promise((resolve, reject) => {
1090
- const onError = (error) => {
1091
- this.server.off('error', onError);
1092
- reject(error);
1093
- };
1094
- this.server.once('error', onError);
1095
- this.server.listen(port, hostname, () => {
1096
- this.server.off('error', onError);
1097
- // Freeze router after server starts to prevent further registrations
1098
- this.router.freeze();
1099
- void this.runLifecycleHooks(this.onStartHooks);
1100
- const address = this.server.address();
1101
- if (cb)
1102
- cb();
1103
- if (address && typeof address === 'object') {
1104
- resolve({ port: address.port });
1105
- }
1106
- else {
1107
- resolve({ port });
1108
- }
1109
- });
1110
- });
1111
- }
1112
- close() {
1113
- return new Promise((resolve, reject) => {
1114
- this.server.close((err) => {
1115
- if (err) {
1116
- reject(err);
1117
- return;
1118
- }
1119
- resolve();
1120
- });
1121
- });
1122
- }
1123
- async shutdown() {
1124
- await this.runLifecycleHooks(this.onBeforeShutdownHooks);
1125
- await this.close();
1126
- await this.runLifecycleHooks(this.onShutdownHooks);
1127
- }
1128
- createContext() {
1129
- return new QHTTPXContextImpl(this);
1130
- }
1131
- acquireContext(req, res, urlOrPath, params, query, requestId, body,
1132
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1133
- files, cookies) {
1134
- const ctx = this.contextPool.acquire();
1135
- // Reset and populate properties
1136
- // We use type assertions to write to readonly/managed properties for performance
1137
- const mutableCtx = ctx;
1138
- mutableCtx.req = req;
1139
- mutableCtx.res = res;
1140
- mutableCtx.headers = req.headers;
1141
- if (typeof urlOrPath === 'string') {
1142
- // Ultra mode or fast path: Lazy URL instantiation
1143
- mutableCtx.path = urlOrPath;
1144
- // Class-based getter handles URL parsing lazily using this.req
1145
- }
1146
- else {
1147
- mutableCtx.url = urlOrPath;
1148
- mutableCtx.path = urlOrPath.pathname;
1149
- }
1150
- mutableCtx.method = req.method;
1151
- mutableCtx.params = params;
1152
- if (query) {
1153
- mutableCtx.query = query;
1154
- }
1155
- mutableCtx.body = body;
1156
- mutableCtx.files = files;
1157
- if (cookies) {
1158
- mutableCtx.cookies = cookies;
1159
- }
1160
- mutableCtx.requestId = requestId;
1161
- mutableCtx.serializer = undefined;
1162
- // In ultra mode, skip cookie parsing to save overhead
1163
- // Cookies are now parsed lazily via getter in QHTTPXContextImpl
1164
- mutableCtx.disableAutoEnd = false;
1165
- return mutableCtx;
1166
- }
1167
- releaseContext(ctx) {
1168
- this.contextPool.release(ctx);
1169
- }
1170
- async handleNoMatch(ctx, allowedMethods) {
1171
- const res = ctx.res;
1172
- const hasAnyMethod = allowedMethods.length > 0;
1173
- if (hasAnyMethod && this.methodNotAllowedHandler) {
1174
- const result = this.methodNotAllowedHandler(ctx, allowedMethods);
1175
- if (result && typeof result.then === 'function') {
1176
- await result;
1177
- }
1178
- if (!res.writableEnded && !res.headersSent) {
1179
- res.statusCode = 405;
1180
- res.setHeader('content-type', CONTENT_TYPE.PLAIN);
1181
- res.end('Method Not Allowed');
1182
- }
1183
- }
1184
- else if (!hasAnyMethod && this.notFoundHandler) {
1185
- const result = this.notFoundHandler(ctx);
1186
- if (result && typeof result.then === 'function') {
1187
- await result;
1188
- }
1189
- if (!res.writableEnded && !res.headersSent) {
1190
- res.statusCode = 404;
1191
- res.setHeader('content-type', CONTENT_TYPE.PLAIN);
1192
- res.end('Not Found');
1193
- }
1194
- }
1195
- else if (hasAnyMethod) {
1196
- if (!res.headersSent) {
1197
- res.statusCode = 405;
1198
- res.setHeader('content-type', CONTENT_TYPE.PLAIN);
1199
- }
1200
- res.end('Method Not Allowed');
1201
- }
1202
- else {
1203
- if (!res.headersSent) {
1204
- res.statusCode = 404;
1205
- res.setHeader('content-type', CONTENT_TYPE.PLAIN);
1206
- }
1207
- res.end('Not Found');
1208
- }
1209
- }
1210
- handleRequest(req, res) {
1211
- // ⚡ Optimized request handling with fast routing
1212
- const rawUrl = req.url || '/';
1213
- const method = (req.method || 'GET');
1214
- // Single-pass pathname extraction
1215
- let pathname;
1216
- const qIndex = rawUrl.indexOf('?');
1217
- pathname = qIndex === -1 ? rawUrl : rawUrl.substring(0, qIndex);
1218
- // Fast route matching with O(1) static and optimized dynamic routes
1219
- let match = this.router.match(method, pathname);
1220
- // Request ID generation (from x-request-id header or generated)
1221
- let requestId;
1222
- const incomingRequestIdHeader = req.headers['x-request-id'];
1223
- if (typeof incomingRequestIdHeader === 'string') {
1224
- requestId = incomingRequestIdHeader;
1225
- }
1226
- else if (Array.isArray(incomingRequestIdHeader)) {
1227
- requestId = incomingRequestIdHeader[0];
1228
- }
1229
- if (!requestId) {
1230
- requestId = this.generateRequestId();
1231
- }
1232
- if (!res.headersSent && requestId) {
1233
- res.setHeader('x-request-id', requestId);
1234
- }
1235
- const hasRoute = !!match;
1236
- if (!match) {
1237
- match = QHTTPX.EMPTY_MATCH;
1238
- }
1239
- // Optimization: Predictive Query Parsing
1240
- // 1. If no query string exists, set empty query to avoid getter scan
1241
- // 2. If query string exists AND route needs it, parse eagerly
1242
- let query;
1243
- if (qIndex === -1) {
1244
- query = EMPTY_QUERY;
1245
- }
1246
- else if (match.metadata?.needsQuery) {
1247
- query = (0, querystring_1.parse)(rawUrl.slice(qIndex + 1));
1248
- }
1249
- // Optimization: Predictive Cookie Parsing
1250
- let cookies;
1251
- if (match.metadata?.needsCookies) {
1252
- cookies = (0, cookies_1.parseCookies)(req.headers.cookie);
1253
- }
1254
- // Optimization: Only calculate allowed methods if no route match found
1255
- const allowedMethods = hasRoute ? [] : this.router.getAllowedMethods(pathname);
1256
- if (!hasRoute && !res.headersSent) {
1257
- const hasAnyMethod = allowedMethods.length > 0;
1258
- res.statusCode = hasAnyMethod ? 405 : 404;
1259
- }
1260
- // Memory Checks (throttled to every 100 requests for performance)
1261
- if (this.options.maxMemoryBytes !== undefined && ++this.requestCounter % 100 === 0) {
1262
- const sampleRss = process.memoryUsage().rss;
1263
- const overloadedByMemory = (0, resources_1.isResourceOverloaded)({ rssBytes: sampleRss }, { maxRssBytes: this.options.maxMemoryBytes });
1264
- if (overloadedByMemory) {
1265
- const overloaded = () => {
1266
- if (res.writableEnded) {
1267
- return;
1268
- }
1269
- if (!res.headersSent) {
1270
- res.statusCode = 503;
1271
- res.setHeader('content-type', CONTENT_TYPE.PLAIN);
1272
- }
1273
- res.end('Server overloaded');
1274
- };
1275
- overloaded();
1276
- return;
1277
- }
1278
- }
1279
- else {
1280
- ++this.requestCounter;
1281
- }
1282
- if (method !== 'GET' && method !== 'HEAD') {
1283
- body_parser_1.BodyParser.parse(req, {
1284
- maxBodyBytes: this.options.maxBodyBytes,
1285
- }).then((parsed) => {
1286
- this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, parsed.body, parsed.files, cookies);
1287
- }).catch((err) => {
1288
- if (err instanceof Error && err.message === 'QHTTPX_INVALID_JSON') {
1289
- if (!res.headersSent) {
1290
- res.statusCode = 400;
1291
- res.setHeader('content-type', CONTENT_TYPE.PLAIN);
1292
- }
1293
- res.end('Invalid JSON');
1294
- return;
1295
- }
1296
- if (err instanceof Error && err.message === 'QHTTPX_BODY_TOO_LARGE') {
1297
- if (!res.headersSent) {
1298
- res.statusCode = 413;
1299
- res.setHeader('content-type', CONTENT_TYPE.PLAIN);
1300
- }
1301
- res.end('Payload Too Large');
1302
- return;
1303
- }
1304
- if (!res.headersSent) {
1305
- res.statusCode = 500;
1306
- res.setHeader('content-type', CONTENT_TYPE.PLAIN);
1307
- }
1308
- res.end('Internal Server Error');
1309
- });
1310
- return;
1311
- }
1312
- this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, undefined, undefined, cookies);
1313
- }
1314
- async dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, body, files, cookies) {
1315
- const ctx = this.acquireContext(req, res, pathname, match.params, query, requestId, body, files, cookies);
1316
- // Balanced Mode Logic
1317
- const overloaded = () => {
1318
- if (res.writableEnded) {
1319
- return;
1320
- }
1321
- if (!res.headersSent) {
1322
- res.statusCode = 503;
1323
- res.setHeader('content-type', CONTENT_TYPE.PLAIN);
1324
- }
1325
- res.end('Server overloaded');
1326
- };
1327
- let timedOut = false;
1328
- const onTimeout = () => {
1329
- timedOut = true;
1330
- if (res.writableEnded) {
1331
- return;
1332
- }
1333
- if (!res.headersSent) {
1334
- res.statusCode = 504;
1335
- res.setHeader('content-type', CONTENT_TYPE.PLAIN);
1336
- }
1337
- res.end('Request timed out');
1338
- };
1339
- let start = 0;
1340
- const metricsEnabled = this._metrics.isEnabled;
1341
- if (metricsEnabled || this.tracer) {
1342
- start = timer_1.globalTimer.preciseNow();
1343
- ctx.requestStart = start;
1344
- if (metricsEnabled) {
1345
- this._metrics.onRequestStart();
1346
- }
1347
- }
1348
- if (this.tracer) {
1349
- const event = {
1350
- type: 'request_start',
1351
- method,
1352
- path: pathname,
1353
- requestId,
1354
- };
1355
- const result = this.tracer(event);
1356
- if (result && typeof result.then === 'function') {
1357
- void result;
1358
- }
1359
- }
1360
- const finish = () => {
1361
- let duration = 0;
1362
- if (metricsEnabled || this.tracer) {
1363
- duration = timer_1.globalTimer.preciseNow() - start;
1364
- if (metricsEnabled) {
1365
- this._metrics.onRequestEnd(duration, res.statusCode);
1366
- if (timedOut) {
1367
- this._metrics.onTimeout();
1368
- }
1369
- }
1370
- }
1371
- if (this.tracer) {
1372
- const event = {
1373
- type: 'request_end',
1374
- method,
1375
- path: pathname,
1376
- statusCode: res.statusCode,
1377
- durationMs: duration,
1378
- requestId,
1379
- };
1380
- const result = this.tracer(event);
1381
- if (result && typeof result.then === 'function') {
1382
- void result;
1383
- }
1384
- }
1385
- this.releaseContext(ctx);
1386
- };
1387
- const handle = async () => {
1388
- const handler = match.handler;
1389
- try {
1390
- if (hasRoute) {
1391
- const result = handler(ctx);
1392
- if (result && typeof result.then === 'function') {
1393
- await result;
1394
- }
1395
- }
1396
- else if (this.pipelineRunner) {
1397
- await this.pipelineRunner(ctx, handler);
1398
- }
1399
- if (!res.writableEnded) {
1400
- if (!hasRoute) {
1401
- await this.handleNoMatch(ctx, allowedMethods);
1402
- }
1403
- else if (!ctx.disableAutoEnd) {
1404
- res.end();
1405
- }
1406
- }
1407
- }
1408
- catch (err) {
1409
- await this.handleError(err, ctx);
1410
- }
1411
- };
1412
- // Use scheduler for request handling
1413
- this.scheduler.run(handle, {
1414
- priority: match.priority,
1415
- onOverloaded: overloaded,
1416
- timeoutMs: this.options.requestTimeoutMs,
1417
- onTimeout,
1418
- }).then(finish).catch((err) => {
1419
- console.error('Scheduler error:', err);
1420
- finish();
1421
- });
1422
- }
1423
- async handleUpgrade(req, socket, head) {
1424
- await this.wsManager.handleUpgrade(req, socket, head);
1425
- }
1426
- async runLifecycleHooks(hooks) {
1427
- if (hooks.length === 0) {
1428
- return;
1429
- }
1430
- for (const hook of hooks) {
1431
- try {
1432
- const result = hook();
1433
- if (result && typeof result.then === 'function') {
1434
- await result;
1435
- }
1436
- }
1437
- catch {
1438
- // Ignore hook errors to avoid impacting core server flow
1439
- }
1440
- }
1441
- }
1442
- handleError(err, ctx) {
1443
- const res = ctx.res;
1444
- if (res.writableEnded) {
1445
- return;
1446
- }
1447
- if (this.errorHandler) {
1448
- try {
1449
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1450
- const errorContext = ctx;
1451
- errorContext.error = err;
1452
- const result = this.errorHandler(errorContext);
1453
- if (result && typeof result.then === 'function') {
1454
- return result.then(() => {
1455
- // Ensure response is sent if handler didn't
1456
- });
1457
- }
1458
- if (res.writableEnded) {
1459
- return;
1460
- }
1461
- }
1462
- catch (handlerErr) {
1463
- // Fall through to default error handling below
1464
- console.error('Error in error handler:', handlerErr);
1465
- }
1466
- }
1467
- if (res.writableEnded) {
1468
- return;
1469
- }
1470
- if (err instanceof types_1.HttpError) {
1471
- if (!res.headersSent) {
1472
- res.statusCode = err.status;
1473
- res.setHeader('content-type', CONTENT_TYPE.JSON);
1474
- }
1475
- const payload = {
1476
- error: {
1477
- message: err.message,
1478
- code: err.code ?? 'HTTP_ERROR',
1479
- details: err.details,
1480
- },
1481
- };
1482
- const serializer = this.options.jsonSerializer;
1483
- const body = serializer !== undefined ? serializer(payload) : JSON.stringify(payload);
1484
- res.end(body);
1485
- return;
1486
- }
1487
- if (!res.headersSent) {
1488
- res.statusCode = 500;
1489
- res.setHeader('content-type', CONTENT_TYPE.PLAIN);
1490
- }
1491
- res.end('Internal Server Error');
1492
- }
1493
- generateRequestId() {
1494
- const now = timer_1.globalTimer.now();
1495
- if (now !== this.lastDateNow) {
1496
- this.lastDateNow = now;
1497
- this.lastDateString = now.toString(36);
1498
- }
1499
- const id = this.nextRequestId;
1500
- this.nextRequestId += 1;
1501
- return `${this.lastDateString}-${id.toString(36)}`;
1502
- }
1503
- }
1504
- exports.QHTTPX = QHTTPX;
1505
- QHTTPX.EMPTY_PARAMS = Object.freeze({});
1506
- QHTTPX.EMPTY_MATCH = Object.freeze({
1507
- handler: () => { },
1508
- params: QHTTPX.EMPTY_PARAMS,
1509
- priority: types_1.RoutePriority.STANDARD,
1510
- metadata: { needsQuery: false, needsCookies: false, needsBody: false, pipeline: undefined },
1511
- });