qhttpx 1.9.2 → 1.9.4

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 (232) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +79 -17
  3. package/dist/examples/api-server.d.ts +1 -0
  4. package/dist/examples/api-server.js +77 -0
  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 +112 -0
  24. package/dist/src/benchmarks/compare-frameworks.js +119 -0
  25. package/dist/src/benchmarks/compare.d.ts +1 -0
  26. package/dist/src/benchmarks/compare.js +288 -0
  27. package/dist/src/benchmarks/quantam-users.d.ts +1 -0
  28. package/dist/src/benchmarks/quantam-users.js +56 -0
  29. package/dist/src/benchmarks/simple-json.d.ts +1 -0
  30. package/dist/src/benchmarks/simple-json.js +60 -0
  31. package/dist/src/benchmarks/ultra-mode.d.ts +1 -0
  32. package/dist/src/benchmarks/ultra-mode.js +94 -0
  33. package/dist/src/buffer-pool.js +70 -0
  34. package/dist/src/cli/index.d.ts +2 -0
  35. package/dist/src/cli/index.js +222 -0
  36. package/dist/src/client/index.d.ts +17 -0
  37. package/dist/src/client/index.js +72 -0
  38. package/dist/src/config.js +50 -0
  39. package/dist/src/cookies.js +59 -0
  40. package/dist/src/core/batch.d.ts +24 -0
  41. package/dist/src/core/batch.js +97 -0
  42. package/dist/src/core/body-parser.d.ts +15 -0
  43. package/dist/src/core/body-parser.js +121 -0
  44. package/dist/src/core/buffer-pool.d.ts +41 -0
  45. package/dist/src/core/buffer-pool.js +70 -0
  46. package/dist/src/core/config.d.ts +7 -0
  47. package/dist/src/core/config.js +50 -0
  48. package/dist/src/core/errors.d.ts +34 -0
  49. package/dist/src/core/errors.js +70 -0
  50. package/dist/src/core/fusion.d.ts +20 -0
  51. package/dist/src/core/fusion.js +193 -0
  52. package/dist/src/core/logger.d.ts +22 -0
  53. package/dist/src/core/logger.js +49 -0
  54. package/dist/src/core/metrics.d.ts +48 -0
  55. package/dist/src/core/metrics.js +117 -0
  56. package/dist/src/core/native-adapter.d.ts +11 -0
  57. package/dist/src/core/native-adapter.js +211 -0
  58. package/dist/src/core/resources.d.ts +9 -0
  59. package/dist/src/core/resources.js +25 -0
  60. package/dist/src/core/scheduler.d.ts +34 -0
  61. package/dist/src/core/scheduler.js +85 -0
  62. package/dist/src/core/scope.d.ts +26 -0
  63. package/dist/src/core/scope.js +68 -0
  64. package/dist/src/core/serializer.d.ts +10 -0
  65. package/dist/src/core/serializer.js +44 -0
  66. package/dist/src/core/server.d.ts +138 -0
  67. package/dist/src/core/server.js +1082 -0
  68. package/dist/src/core/stream.d.ts +15 -0
  69. package/dist/src/core/stream.js +71 -0
  70. package/dist/src/core/tasks.d.ts +29 -0
  71. package/dist/src/core/tasks.js +87 -0
  72. package/dist/src/core/types.d.ts +197 -0
  73. package/dist/src/core/types.js +19 -0
  74. package/dist/src/core/websocket.d.ts +25 -0
  75. package/dist/src/core/websocket.js +86 -0
  76. package/dist/src/core/worker-queue.d.ts +41 -0
  77. package/dist/src/core/worker-queue.js +73 -0
  78. package/dist/src/cors.js +66 -0
  79. package/dist/src/database/adapters/memory.d.ts +21 -0
  80. package/dist/src/database/adapters/memory.js +90 -0
  81. package/dist/src/database/adapters/mongo.d.ts +11 -0
  82. package/dist/src/database/adapters/mongo.js +141 -0
  83. package/dist/src/database/adapters/postgres.d.ts +10 -0
  84. package/dist/src/database/adapters/postgres.js +111 -0
  85. package/dist/src/database/adapters/sqlite.d.ts +10 -0
  86. package/dist/src/database/adapters/sqlite.js +42 -0
  87. package/dist/src/database/coalescer.d.ts +14 -0
  88. package/dist/src/database/coalescer.js +134 -0
  89. package/dist/src/database/manager.d.ts +35 -0
  90. package/dist/src/database/manager.js +87 -0
  91. package/dist/src/database/types.d.ts +20 -0
  92. package/dist/src/database/types.js +2 -0
  93. package/dist/src/index.d.ts +50 -0
  94. package/dist/src/index.js +93 -0
  95. package/dist/src/logger.js +45 -0
  96. package/dist/src/metrics.js +111 -0
  97. package/dist/src/middleware/compression.d.ts +2 -0
  98. package/dist/src/middleware/compression.js +133 -0
  99. package/dist/src/middleware/cors.d.ts +2 -0
  100. package/dist/src/middleware/cors.js +66 -0
  101. package/dist/src/middleware/presets.d.ts +15 -0
  102. package/dist/src/middleware/presets.js +52 -0
  103. package/dist/src/middleware/rate-limit.d.ts +14 -0
  104. package/dist/src/middleware/rate-limit.js +83 -0
  105. package/dist/src/middleware/security.d.ts +10 -0
  106. package/dist/src/middleware/security.js +69 -0
  107. package/dist/src/middleware/static.d.ts +11 -0
  108. package/dist/src/middleware/static.js +191 -0
  109. package/dist/src/native/index.d.ts +32 -0
  110. package/dist/src/native/index.js +141 -0
  111. package/dist/src/openapi/generator.d.ts +19 -0
  112. package/dist/src/openapi/generator.js +149 -0
  113. package/dist/src/presets.js +33 -0
  114. package/dist/src/radix-router.js +89 -0
  115. package/dist/src/radix-tree.js +81 -0
  116. package/dist/src/resources.js +25 -0
  117. package/dist/src/router/radix-router.d.ts +18 -0
  118. package/dist/src/router/radix-router.js +89 -0
  119. package/dist/src/router/radix-tree.d.ts +18 -0
  120. package/dist/src/router/radix-tree.js +131 -0
  121. package/dist/src/router/router.d.ts +34 -0
  122. package/dist/src/router/router.js +186 -0
  123. package/dist/src/router.js +138 -0
  124. package/dist/src/scheduler.js +85 -0
  125. package/dist/src/security.js +69 -0
  126. package/dist/src/server.js +685 -0
  127. package/dist/src/signals.js +31 -0
  128. package/dist/src/static.js +107 -0
  129. package/dist/src/stream.js +71 -0
  130. package/dist/src/tasks.js +87 -0
  131. package/dist/src/testing/index.d.ts +25 -0
  132. package/dist/src/testing/index.js +84 -0
  133. package/dist/src/testing.js +40 -0
  134. package/dist/src/types.js +19 -0
  135. package/dist/src/utils/cookies.d.ts +3 -0
  136. package/dist/src/utils/cookies.js +59 -0
  137. package/dist/src/utils/logger.d.ts +2 -0
  138. package/dist/src/utils/logger.js +45 -0
  139. package/dist/src/utils/signals.d.ts +6 -0
  140. package/dist/src/utils/signals.js +31 -0
  141. package/dist/src/utils/sse.d.ts +6 -0
  142. package/dist/src/utils/sse.js +32 -0
  143. package/dist/src/utils/testing.js +40 -0
  144. package/dist/src/validation/index.d.ts +3 -0
  145. package/dist/src/validation/index.js +19 -0
  146. package/dist/src/validation/simple.d.ts +5 -0
  147. package/dist/src/validation/simple.js +102 -0
  148. package/dist/src/validation/types.d.ts +32 -0
  149. package/dist/src/validation/types.js +12 -0
  150. package/dist/src/validation/zod.d.ts +4 -0
  151. package/dist/src/validation/zod.js +18 -0
  152. package/dist/src/views/index.d.ts +1 -0
  153. package/dist/src/views/index.js +17 -0
  154. package/dist/src/views/types.d.ts +3 -0
  155. package/dist/src/views/types.js +2 -0
  156. package/dist/src/worker-queue.js +73 -0
  157. package/dist/tests/adapters.test.d.ts +1 -0
  158. package/dist/tests/adapters.test.js +106 -0
  159. package/dist/tests/batch.test.d.ts +1 -0
  160. package/dist/tests/batch.test.js +117 -0
  161. package/dist/tests/body-parser.test.d.ts +1 -0
  162. package/dist/tests/body-parser.test.js +52 -0
  163. package/dist/tests/compression-sse.test.d.ts +1 -0
  164. package/dist/tests/compression-sse.test.js +87 -0
  165. package/dist/tests/cookies.test.d.ts +1 -0
  166. package/dist/tests/cookies.test.js +63 -0
  167. package/dist/tests/cors.test.d.ts +1 -0
  168. package/dist/tests/cors.test.js +55 -0
  169. package/dist/tests/database.test.d.ts +1 -0
  170. package/dist/tests/database.test.js +80 -0
  171. package/dist/tests/dx.test.d.ts +1 -0
  172. package/dist/tests/dx.test.js +114 -0
  173. package/dist/tests/ecosystem.test.d.ts +1 -0
  174. package/dist/tests/ecosystem.test.js +133 -0
  175. package/dist/tests/features.test.d.ts +1 -0
  176. package/dist/tests/features.test.js +47 -0
  177. package/dist/tests/fusion.test.d.ts +1 -0
  178. package/dist/tests/fusion.test.js +92 -0
  179. package/dist/tests/http-basic.test.d.ts +1 -0
  180. package/dist/tests/http-basic.test.js +124 -0
  181. package/dist/tests/logger.test.d.ts +1 -0
  182. package/dist/tests/logger.test.js +33 -0
  183. package/dist/tests/middleware.test.d.ts +1 -0
  184. package/dist/tests/middleware.test.js +109 -0
  185. package/dist/tests/native-adapter.test.d.ts +1 -0
  186. package/dist/tests/native-adapter.test.js +71 -0
  187. package/dist/tests/observability.test.d.ts +1 -0
  188. package/dist/tests/observability.test.js +59 -0
  189. package/dist/tests/openapi.test.d.ts +1 -0
  190. package/dist/tests/openapi.test.js +64 -0
  191. package/dist/tests/plugin.test.d.ts +1 -0
  192. package/dist/tests/plugin.test.js +65 -0
  193. package/dist/tests/plugins.test.d.ts +1 -0
  194. package/dist/tests/plugins.test.js +71 -0
  195. package/dist/tests/rate-limit.test.d.ts +1 -0
  196. package/dist/tests/rate-limit.test.js +77 -0
  197. package/dist/tests/resources.test.d.ts +1 -0
  198. package/dist/tests/resources.test.js +47 -0
  199. package/dist/tests/scheduler.test.d.ts +1 -0
  200. package/dist/tests/scheduler.test.js +46 -0
  201. package/dist/tests/schema-routes.test.d.ts +1 -0
  202. package/dist/tests/schema-routes.test.js +77 -0
  203. package/dist/tests/security.test.d.ts +1 -0
  204. package/dist/tests/security.test.js +83 -0
  205. package/dist/tests/server-db.test.d.ts +1 -0
  206. package/dist/tests/server-db.test.js +72 -0
  207. package/dist/tests/smoke.test.d.ts +1 -0
  208. package/dist/tests/smoke.test.js +10 -0
  209. package/dist/tests/sqlite-fusion.test.d.ts +1 -0
  210. package/dist/tests/sqlite-fusion.test.js +92 -0
  211. package/dist/tests/static.test.d.ts +1 -0
  212. package/dist/tests/static.test.js +102 -0
  213. package/dist/tests/stream.test.d.ts +1 -0
  214. package/dist/tests/stream.test.js +44 -0
  215. package/dist/tests/task-metrics.test.d.ts +1 -0
  216. package/dist/tests/task-metrics.test.js +53 -0
  217. package/dist/tests/tasks.test.d.ts +1 -0
  218. package/dist/tests/tasks.test.js +62 -0
  219. package/dist/tests/testing.test.d.ts +1 -0
  220. package/dist/tests/testing.test.js +47 -0
  221. package/dist/tests/validation.test.d.ts +1 -0
  222. package/dist/tests/validation.test.js +107 -0
  223. package/dist/tests/websocket.test.d.ts +1 -0
  224. package/dist/tests/websocket.test.js +146 -0
  225. package/dist/vitest.config.d.ts +2 -0
  226. package/dist/vitest.config.js +9 -0
  227. package/docs/FUSION.md +19 -0
  228. package/package.json +2 -1
  229. package/prebuilds/darwin-arm64/qhttpx.node +0 -0
  230. package/prebuilds/linux-x64/qhttpx.node +0 -0
  231. package/prebuilds/win32-x64/qhttpx.node +0 -0
  232. package/scripts/install-native.js +26 -0
@@ -0,0 +1,685 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.QHTTPX = void 0;
7
+ const http_1 = __importDefault(require("http"));
8
+ const url_1 = require("url");
9
+ const package_json_1 = __importDefault(require("../package.json"));
10
+ const router_1 = require("./router");
11
+ const scheduler_1 = require("./scheduler");
12
+ const tasks_1 = require("./tasks");
13
+ const resources_1 = require("./resources");
14
+ const metrics_1 = require("./metrics");
15
+ const buffer_pool_1 = require("./buffer-pool");
16
+ const types_1 = require("./types");
17
+ const cookies_1 = require("./cookies");
18
+ class QHTTPX {
19
+ constructor(options = {}) {
20
+ this.middlewares = [];
21
+ this.contextPool = [];
22
+ this.pipelineRunner = null;
23
+ this.onStartHooks = [];
24
+ this.onBeforeShutdownHooks = [];
25
+ this.onShutdownHooks = [];
26
+ this.nextRequestId = 1;
27
+ this.upgradeHandlers = [];
28
+ this.options = options;
29
+ this.errorHandler = options.errorHandler;
30
+ this.notFoundHandler = options.notFoundHandler;
31
+ this.methodNotAllowedHandler = options.methodNotAllowedHandler;
32
+ this.tracer = options.tracer;
33
+ this.workerCount = (0, resources_1.calculateWorkerCount)(options.workers ?? 'auto');
34
+ this.router = new router_1.Router();
35
+ this.bufferPool = new buffer_pool_1.BufferPool(options.bufferPoolConfig);
36
+ const maxConcurrency = options.maxConcurrency ?? 1024;
37
+ this.scheduler = new scheduler_1.Scheduler({
38
+ maxConcurrency,
39
+ });
40
+ this.tasks = new tasks_1.TaskEngine(this.scheduler);
41
+ this.metrics = new metrics_1.Metrics(this.scheduler, {
42
+ enabled: this.options.metricsEnabled ?? true,
43
+ }, this.tasks);
44
+ for (let i = 0; i < maxConcurrency; i += 1) {
45
+ this.contextPool.push(this.createContext());
46
+ }
47
+ this.registerInternalRoutes();
48
+ this.compileMiddlewarePipeline();
49
+ this.server = http_1.default.createServer(this.handleRequest.bind(this));
50
+ if (this.options.keepAliveTimeoutMs !== undefined) {
51
+ this.server.keepAliveTimeout = this.options.keepAliveTimeoutMs;
52
+ }
53
+ if (this.options.headersTimeoutMs !== undefined) {
54
+ this.server.headersTimeout = this.options.headersTimeoutMs;
55
+ }
56
+ this.server.on('upgrade', (req, socket, head) => {
57
+ void this.handleUpgrade(req, socket, head);
58
+ });
59
+ }
60
+ setErrorHandler(handler) {
61
+ this.errorHandler = handler;
62
+ }
63
+ setNotFoundHandler(handler) {
64
+ this.notFoundHandler = handler;
65
+ }
66
+ setMethodNotAllowedHandler(handler) {
67
+ this.methodNotAllowedHandler = handler;
68
+ }
69
+ set404Handler(handler) {
70
+ this.setNotFoundHandler(handler);
71
+ }
72
+ set405Handler(handler) {
73
+ this.setMethodNotAllowedHandler(handler);
74
+ }
75
+ onStart(hook) {
76
+ this.onStartHooks.push(hook);
77
+ }
78
+ onBeforeShutdown(hook) {
79
+ this.onBeforeShutdownHooks.push(hook);
80
+ }
81
+ onShutdown(hook) {
82
+ this.onShutdownHooks.push(hook);
83
+ }
84
+ upgrade(path, handler) {
85
+ this.upgradeHandlers.push({ path, handler });
86
+ }
87
+ use(middleware) {
88
+ this.middlewares.push(middleware);
89
+ // Recompile pipeline after middleware is added
90
+ this.compileMiddlewarePipeline();
91
+ }
92
+ compileMiddlewarePipeline() {
93
+ const middlewares = this.middlewares;
94
+ if (middlewares.length === 0) {
95
+ this.pipelineRunner = async (ctx, handler) => {
96
+ const result = handler(ctx);
97
+ if (result && typeof result.then === 'function') {
98
+ await result;
99
+ }
100
+ };
101
+ return;
102
+ }
103
+ // Precompile a closure that captures the middleware chain
104
+ this.pipelineRunner = async (ctx, handler) => {
105
+ let index = -1;
106
+ const dispatch = async () => {
107
+ index += 1;
108
+ if (index < middlewares.length) {
109
+ await middlewares[index](ctx, dispatch);
110
+ return;
111
+ }
112
+ const result = handler(ctx);
113
+ if (result && typeof result.then === 'function') {
114
+ await result;
115
+ }
116
+ };
117
+ await dispatch();
118
+ };
119
+ }
120
+ get(path, handler) {
121
+ this.router.register('GET', path, handler);
122
+ }
123
+ post(path, handler) {
124
+ this.router.register('POST', path, handler);
125
+ }
126
+ put(path, handler) {
127
+ this.router.register('PUT', path, handler);
128
+ }
129
+ delete(path, handler) {
130
+ this.router.register('DELETE', path, handler);
131
+ }
132
+ route(path) {
133
+ const register = (method, handler) => {
134
+ this.router.register(method, path, handler);
135
+ };
136
+ const builder = {
137
+ get(handler) {
138
+ register('GET', handler);
139
+ return this;
140
+ },
141
+ post(handler) {
142
+ register('POST', handler);
143
+ return this;
144
+ },
145
+ put(handler) {
146
+ register('PUT', handler);
147
+ return this;
148
+ },
149
+ delete(handler) {
150
+ register('DELETE', handler);
151
+ return this;
152
+ },
153
+ };
154
+ return builder;
155
+ }
156
+ task(name, handler, options) {
157
+ this.tasks.register(name, handler, options);
158
+ }
159
+ enqueue(name, payload) {
160
+ return this.tasks.enqueue(name, payload);
161
+ }
162
+ registerInternalRoutes() {
163
+ this.router.register('GET', '/__qhttpx/health', (ctx) => {
164
+ const version = typeof package_json_1.default.version === 'string' ? package_json_1.default.version : '';
165
+ const name = typeof package_json_1.default.name === 'string' ? package_json_1.default.name : '';
166
+ ctx.json({
167
+ status: 'ok',
168
+ name,
169
+ version,
170
+ workers: this.workerCount,
171
+ });
172
+ });
173
+ this.router.register('GET', '/__qhttpx/metrics', (ctx) => {
174
+ const snapshot = this.metrics.snapshot();
175
+ ctx.json({
176
+ ...snapshot,
177
+ workers: this.workerCount,
178
+ });
179
+ });
180
+ this.router.register('GET', '/__qhttpx/runtime', (ctx) => {
181
+ const schedulerStats = this.scheduler.getStats();
182
+ ctx.json({
183
+ workers: this.workerCount,
184
+ router: {
185
+ frozen: this.router.isFrozenRouter(),
186
+ },
187
+ scheduler: schedulerStats,
188
+ });
189
+ });
190
+ }
191
+ listen(port, hostname) {
192
+ return new Promise((resolve, reject) => {
193
+ const onError = (error) => {
194
+ this.server.off('error', onError);
195
+ reject(error);
196
+ };
197
+ this.server.once('error', onError);
198
+ this.server.listen(port, hostname, () => {
199
+ this.server.off('error', onError);
200
+ // Freeze router after server starts to prevent further registrations
201
+ this.router.freeze();
202
+ void this.runLifecycleHooks(this.onStartHooks);
203
+ const address = this.server.address();
204
+ if (address && typeof address === 'object') {
205
+ resolve({ port: address.port });
206
+ }
207
+ else {
208
+ resolve({ port });
209
+ }
210
+ });
211
+ });
212
+ }
213
+ close() {
214
+ return new Promise((resolve, reject) => {
215
+ this.server.close((err) => {
216
+ if (err) {
217
+ reject(err);
218
+ return;
219
+ }
220
+ resolve();
221
+ });
222
+ });
223
+ }
224
+ async shutdown() {
225
+ await this.runLifecycleHooks(this.onBeforeShutdownHooks);
226
+ await this.close();
227
+ await this.runLifecycleHooks(this.onShutdownHooks);
228
+ }
229
+ createContext() {
230
+ const jsonSerializer = this.options.jsonSerializer;
231
+ const ctx = {
232
+ req: {},
233
+ res: {},
234
+ url: {},
235
+ params: {},
236
+ query: {},
237
+ body: undefined,
238
+ cookies: {},
239
+ state: {},
240
+ bufferPool: this.bufferPool,
241
+ json(payload, status = 200) {
242
+ const res = this.res;
243
+ if (!res.headersSent) {
244
+ res.statusCode = status;
245
+ res.setHeader('content-type', 'application/json; charset=utf-8');
246
+ }
247
+ const serializer = jsonSerializer;
248
+ const body = serializer !== undefined ? serializer(payload) : JSON.stringify(payload);
249
+ res.end(body);
250
+ },
251
+ send(payload, status = 200) {
252
+ const res = this.res;
253
+ if (!res.headersSent) {
254
+ res.statusCode = status;
255
+ }
256
+ res.end(payload);
257
+ },
258
+ html(payload, status = 200) {
259
+ const res = this.res;
260
+ if (!res.headersSent) {
261
+ res.statusCode = status;
262
+ res.setHeader('content-type', 'text/html; charset=utf-8');
263
+ }
264
+ res.end(payload);
265
+ },
266
+ redirect(url, status = 302) {
267
+ const res = this.res;
268
+ if (!res.headersSent) {
269
+ res.statusCode = status;
270
+ res.setHeader('Location', url);
271
+ }
272
+ res.end();
273
+ },
274
+ setCookie(name, value, options) {
275
+ const res = this.res;
276
+ const serialized = (0, cookies_1.serializeCookie)(name, value, options);
277
+ let existing = res.getHeader('Set-Cookie');
278
+ if (Array.isArray(existing)) {
279
+ existing.push(serialized);
280
+ res.setHeader('Set-Cookie', existing);
281
+ }
282
+ else if (existing) {
283
+ res.setHeader('Set-Cookie', [existing, serialized]);
284
+ }
285
+ else {
286
+ res.setHeader('Set-Cookie', serialized);
287
+ }
288
+ },
289
+ };
290
+ return ctx;
291
+ }
292
+ acquireContext(req, res, url, params, query, requestId, body) {
293
+ const ctx = this.contextPool.pop() ?? this.createContext();
294
+ ctx.req = req;
295
+ ctx.res = res;
296
+ ctx.url = url;
297
+ ctx.params = params;
298
+ ctx.query = query;
299
+ ctx.body = body;
300
+ ctx.requestId = requestId;
301
+ return ctx;
302
+ }
303
+ releaseContext(ctx) {
304
+ ctx.req = ctx.req;
305
+ ctx.res = ctx.res;
306
+ ctx.url = ctx.url;
307
+ ctx.params = {};
308
+ ctx.query = {};
309
+ ctx.body = undefined;
310
+ ctx.requestId = undefined;
311
+ ctx.requestStart = undefined;
312
+ if (this.contextPool.length < (this.options.maxConcurrency ?? 1024)) {
313
+ this.contextPool.push(ctx);
314
+ }
315
+ }
316
+ async handleRequest(req, res) {
317
+ const rawMethod = (req.method || 'GET').toUpperCase();
318
+ const method = rawMethod;
319
+ const rawUrl = req.url || '/';
320
+ const host = req.headers.host || 'localhost';
321
+ const url = new url_1.URL(rawUrl, `http://${host}`);
322
+ const incomingRequestIdHeader = req.headers['x-request-id'];
323
+ let requestId;
324
+ if (typeof incomingRequestIdHeader === 'string') {
325
+ requestId = incomingRequestIdHeader;
326
+ }
327
+ else if (Array.isArray(incomingRequestIdHeader)) {
328
+ requestId = incomingRequestIdHeader[0];
329
+ }
330
+ if (!requestId) {
331
+ requestId = this.generateRequestId();
332
+ }
333
+ if (!res.headersSent && requestId) {
334
+ res.setHeader('x-request-id', requestId);
335
+ }
336
+ let match = this.router.match(method, url.pathname);
337
+ const hasRoute = !!match;
338
+ if (!match) {
339
+ match = {
340
+ handler: () => { },
341
+ params: {},
342
+ priority: types_1.RoutePriority.STANDARD,
343
+ };
344
+ }
345
+ const allowedMethods = this.router.getAllowedMethods(url.pathname);
346
+ if (!hasRoute && !res.headersSent) {
347
+ const hasAnyMethod = allowedMethods.length > 0;
348
+ res.statusCode = hasAnyMethod ? 405 : 404;
349
+ }
350
+ const query = {};
351
+ url.searchParams.forEach((value, key) => {
352
+ if (Object.prototype.hasOwnProperty.call(query, key)) {
353
+ const existing = query[key];
354
+ if (Array.isArray(existing)) {
355
+ existing.push(value);
356
+ }
357
+ else {
358
+ query[key] = [existing, value];
359
+ }
360
+ }
361
+ else {
362
+ query[key] = value;
363
+ }
364
+ });
365
+ if (this.options.maxMemoryBytes !== undefined) {
366
+ const sampleRss = process.memoryUsage().rss;
367
+ const overloadedByMemory = (0, resources_1.isResourceOverloaded)({ rssBytes: sampleRss }, { maxRssBytes: this.options.maxMemoryBytes });
368
+ if (overloadedByMemory) {
369
+ const overloaded = () => {
370
+ if (res.writableEnded) {
371
+ return;
372
+ }
373
+ if (!res.headersSent) {
374
+ res.statusCode = 503;
375
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
376
+ }
377
+ res.end('Server overloaded');
378
+ };
379
+ overloaded();
380
+ return;
381
+ }
382
+ }
383
+ let body;
384
+ try {
385
+ body = await this.readBody(req);
386
+ }
387
+ catch (err) {
388
+ if (err instanceof Error && err.message === 'QHTTPX_INVALID_JSON') {
389
+ if (!res.headersSent) {
390
+ res.statusCode = 400;
391
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
392
+ }
393
+ res.end('Invalid JSON');
394
+ return;
395
+ }
396
+ if (err instanceof Error && err.message === 'QHTTPX_BODY_TOO_LARGE') {
397
+ if (!res.headersSent) {
398
+ res.statusCode = 413;
399
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
400
+ }
401
+ res.end('Payload Too Large');
402
+ return;
403
+ }
404
+ if (!res.headersSent) {
405
+ res.statusCode = 500;
406
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
407
+ }
408
+ res.end('Internal Server Error');
409
+ return;
410
+ }
411
+ const ctx = this.acquireContext(req, res, url, match.params, query, requestId, body);
412
+ const overloaded = () => {
413
+ if (res.writableEnded) {
414
+ return;
415
+ }
416
+ if (!res.headersSent) {
417
+ res.statusCode = 503;
418
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
419
+ }
420
+ res.end('Server overloaded');
421
+ };
422
+ let timedOut = false;
423
+ const onTimeout = () => {
424
+ timedOut = true;
425
+ if (res.writableEnded) {
426
+ return;
427
+ }
428
+ if (!res.headersSent) {
429
+ res.statusCode = 504;
430
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
431
+ }
432
+ res.end('Request timed out');
433
+ };
434
+ const start = Date.now();
435
+ ctx.requestStart = start;
436
+ this.metrics.onRequestStart();
437
+ if (this.tracer) {
438
+ const event = {
439
+ type: 'request_start',
440
+ method,
441
+ path: url.pathname,
442
+ requestId,
443
+ };
444
+ const result = this.tracer(event);
445
+ if (result && typeof result.then === 'function') {
446
+ void result;
447
+ }
448
+ }
449
+ await this.scheduler.run(async () => {
450
+ const handler = match.handler;
451
+ try {
452
+ if (this.pipelineRunner) {
453
+ await this.pipelineRunner(ctx, handler);
454
+ }
455
+ if (!res.writableEnded) {
456
+ if (!hasRoute) {
457
+ const hasAnyMethod = allowedMethods.length > 0;
458
+ if (hasAnyMethod && this.methodNotAllowedHandler) {
459
+ const result = this.methodNotAllowedHandler(ctx, allowedMethods);
460
+ if (result &&
461
+ typeof result.then === 'function') {
462
+ await result;
463
+ }
464
+ if (!res.writableEnded && !res.headersSent) {
465
+ res.statusCode = 405;
466
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
467
+ res.end('Method Not Allowed');
468
+ }
469
+ }
470
+ else if (!hasAnyMethod && this.notFoundHandler) {
471
+ const result = this.notFoundHandler(ctx);
472
+ if (result &&
473
+ typeof result.then === 'function') {
474
+ await result;
475
+ }
476
+ if (!res.writableEnded && !res.headersSent) {
477
+ res.statusCode = 404;
478
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
479
+ res.end('Not Found');
480
+ }
481
+ }
482
+ else if (hasAnyMethod) {
483
+ if (!res.headersSent) {
484
+ res.statusCode = 405;
485
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
486
+ }
487
+ res.end('Method Not Allowed');
488
+ }
489
+ else {
490
+ if (!res.headersSent) {
491
+ res.statusCode = 404;
492
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
493
+ }
494
+ res.end('Not Found');
495
+ }
496
+ }
497
+ else {
498
+ res.end();
499
+ }
500
+ }
501
+ }
502
+ catch (err) {
503
+ await this.handleError(err, ctx);
504
+ }
505
+ }, {
506
+ priority: match.priority,
507
+ onOverloaded: overloaded,
508
+ timeoutMs: this.options.requestTimeoutMs,
509
+ onTimeout,
510
+ });
511
+ const duration = Date.now() - start;
512
+ this.metrics.onRequestEnd(duration, res.statusCode);
513
+ if (timedOut) {
514
+ this.metrics.onTimeout();
515
+ }
516
+ if (this.tracer) {
517
+ const event = {
518
+ type: 'request_end',
519
+ method,
520
+ path: url.pathname,
521
+ statusCode: res.statusCode,
522
+ durationMs: duration,
523
+ requestId,
524
+ };
525
+ const result = this.tracer(event);
526
+ if (result && typeof result.then === 'function') {
527
+ void result;
528
+ }
529
+ }
530
+ this.releaseContext(ctx);
531
+ }
532
+ async handleUpgrade(req, socket, head) {
533
+ const rawUrl = req.url || '/';
534
+ const host = req.headers.host || 'localhost';
535
+ const urlObj = new url_1.URL(rawUrl, `http://${host}`);
536
+ const path = urlObj.pathname;
537
+ const incomingRequestIdHeader = req.headers['x-request-id'];
538
+ let requestId;
539
+ if (typeof incomingRequestIdHeader === 'string') {
540
+ requestId = incomingRequestIdHeader;
541
+ }
542
+ else if (Array.isArray(incomingRequestIdHeader)) {
543
+ requestId = incomingRequestIdHeader[0];
544
+ }
545
+ if (!requestId) {
546
+ requestId = this.generateRequestId();
547
+ }
548
+ const start = Date.now();
549
+ const handlerEntry = this.upgradeHandlers.find((entry) => entry.path === rawUrl);
550
+ if (!handlerEntry) {
551
+ const duration = Date.now() - start;
552
+ const prefix = requestId ? `${requestId} ` : '';
553
+ const status = 404;
554
+ const color = '\x1b[33m';
555
+ const reset = '\x1b[0m';
556
+ const line = `${prefix}WS ${path} ${status} ${duration}ms`;
557
+ console.log(`${color}${line}${reset}`);
558
+ socket.destroy();
559
+ return;
560
+ }
561
+ try {
562
+ const result = handlerEntry.handler(req, socket, head);
563
+ if (result && typeof result.then === 'function') {
564
+ await result;
565
+ }
566
+ const duration = Date.now() - start;
567
+ const prefix = requestId ? `${requestId} ` : '';
568
+ const status = 101;
569
+ const color = '\x1b[36m';
570
+ const reset = '\x1b[0m';
571
+ const line = `${prefix}WS ${path} ${status} ${duration}ms`;
572
+ console.log(`${color}${line}${reset}`);
573
+ }
574
+ catch {
575
+ const duration = Date.now() - start;
576
+ const prefix = requestId ? `${requestId} ` : '';
577
+ const status = 500;
578
+ const color = '\x1b[34m';
579
+ const reset = '\x1b[0m';
580
+ const line = `${prefix}WS ${path} ${status} ${duration}ms`;
581
+ console.log(`${color}${line}${reset}`);
582
+ socket.destroy();
583
+ }
584
+ }
585
+ async readBody(req) {
586
+ const method = (req.method || 'GET').toUpperCase();
587
+ if (method === 'GET' || method === 'HEAD') {
588
+ return undefined;
589
+ }
590
+ const chunks = [];
591
+ let totalBytes = 0;
592
+ const maxBodyBytes = this.options.maxBodyBytes;
593
+ for await (const chunk of req) {
594
+ const bufferChunk = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;
595
+ chunks.push(bufferChunk);
596
+ totalBytes += bufferChunk.length;
597
+ if (maxBodyBytes !== undefined && totalBytes > maxBodyBytes) {
598
+ throw new Error('QHTTPX_BODY_TOO_LARGE');
599
+ }
600
+ }
601
+ if (chunks.length === 0) {
602
+ return undefined;
603
+ }
604
+ const buffer = Buffer.concat(chunks);
605
+ const contentType = req.headers['content-type'] || '';
606
+ if (typeof contentType === 'string' &&
607
+ contentType.includes('application/json')) {
608
+ const text = buffer.toString('utf8');
609
+ try {
610
+ return JSON.parse(text);
611
+ }
612
+ catch {
613
+ throw new Error('QHTTPX_INVALID_JSON');
614
+ }
615
+ }
616
+ return buffer;
617
+ }
618
+ async runLifecycleHooks(hooks) {
619
+ if (hooks.length === 0) {
620
+ return;
621
+ }
622
+ for (const hook of hooks) {
623
+ try {
624
+ const result = hook();
625
+ if (result && typeof result.then === 'function') {
626
+ await result;
627
+ }
628
+ }
629
+ catch {
630
+ // Ignore hook errors to avoid impacting core server flow
631
+ }
632
+ }
633
+ }
634
+ async handleError(err, ctx) {
635
+ const res = ctx.res;
636
+ if (res.writableEnded) {
637
+ return;
638
+ }
639
+ if (this.errorHandler) {
640
+ try {
641
+ const result = this.errorHandler(err, ctx);
642
+ if (result && typeof result.then === 'function') {
643
+ await result;
644
+ }
645
+ if (res.writableEnded) {
646
+ return;
647
+ }
648
+ }
649
+ catch {
650
+ // Fall through to default error handling below
651
+ }
652
+ }
653
+ if (res.writableEnded) {
654
+ return;
655
+ }
656
+ if (err instanceof types_1.HttpError) {
657
+ if (!res.headersSent) {
658
+ res.statusCode = err.status;
659
+ res.setHeader('content-type', 'application/json; charset=utf-8');
660
+ }
661
+ const payload = {
662
+ error: {
663
+ message: err.message,
664
+ code: err.code ?? 'HTTP_ERROR',
665
+ details: err.details,
666
+ },
667
+ };
668
+ const serializer = this.options.jsonSerializer;
669
+ const body = serializer !== undefined ? serializer(payload) : JSON.stringify(payload);
670
+ res.end(body);
671
+ return;
672
+ }
673
+ if (!res.headersSent) {
674
+ res.statusCode = 500;
675
+ res.setHeader('content-type', 'text/plain; charset=utf-8');
676
+ }
677
+ res.end('Internal Server Error');
678
+ }
679
+ generateRequestId() {
680
+ const id = this.nextRequestId;
681
+ this.nextRequestId += 1;
682
+ return `${Date.now().toString(36)}-${id.toString(36)}`;
683
+ }
684
+ }
685
+ exports.QHTTPX = QHTTPX;