qhttpx 1.8.12 → 1.9.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 (206) hide show
  1. package/package.json +26 -4
  2. package/prebuilds/darwin-arm64/qhttpx.node +0 -0
  3. package/prebuilds/linux-x64/qhttpx.node +0 -0
  4. package/prebuilds/win32-x64/qhttpx.node +0 -0
  5. package/src/native/index.ts +104 -24
  6. package/src/native/picohttpparser.h +5 -0
  7. package/src/native/server.cc +2 -0
  8. package/dist/examples/api-server.d.ts +0 -1
  9. package/dist/examples/api-server.js +0 -77
  10. package/dist/examples/basic.d.ts +0 -1
  11. package/dist/examples/basic.js +0 -10
  12. package/dist/examples/compression.d.ts +0 -1
  13. package/dist/examples/compression.js +0 -17
  14. package/dist/examples/cors.d.ts +0 -1
  15. package/dist/examples/cors.js +0 -19
  16. package/dist/examples/errors.d.ts +0 -1
  17. package/dist/examples/errors.js +0 -25
  18. package/dist/examples/file-upload.d.ts +0 -1
  19. package/dist/examples/file-upload.js +0 -24
  20. package/dist/examples/fusion.d.ts +0 -1
  21. package/dist/examples/fusion.js +0 -21
  22. package/dist/examples/rate-limiting.d.ts +0 -1
  23. package/dist/examples/rate-limiting.js +0 -17
  24. package/dist/examples/validation.d.ts +0 -1
  25. package/dist/examples/validation.js +0 -23
  26. package/dist/examples/websockets.d.ts +0 -1
  27. package/dist/examples/websockets.js +0 -20
  28. package/dist/package.json +0 -101
  29. package/dist/src/benchmarks/quantam-users.d.ts +0 -1
  30. package/dist/src/benchmarks/quantam-users.js +0 -56
  31. package/dist/src/benchmarks/simple-json.d.ts +0 -1
  32. package/dist/src/benchmarks/simple-json.js +0 -60
  33. package/dist/src/benchmarks/ultra-mode.d.ts +0 -1
  34. package/dist/src/benchmarks/ultra-mode.js +0 -94
  35. package/dist/src/cli/index.d.ts +0 -2
  36. package/dist/src/cli/index.js +0 -222
  37. package/dist/src/client/index.d.ts +0 -17
  38. package/dist/src/client/index.js +0 -72
  39. package/dist/src/core/batch.d.ts +0 -24
  40. package/dist/src/core/batch.js +0 -97
  41. package/dist/src/core/body-parser.d.ts +0 -15
  42. package/dist/src/core/body-parser.js +0 -121
  43. package/dist/src/core/buffer-pool.d.ts +0 -41
  44. package/dist/src/core/buffer-pool.js +0 -70
  45. package/dist/src/core/config.d.ts +0 -7
  46. package/dist/src/core/config.js +0 -50
  47. package/dist/src/core/errors.d.ts +0 -34
  48. package/dist/src/core/errors.js +0 -70
  49. package/dist/src/core/fusion.d.ts +0 -14
  50. package/dist/src/core/fusion.js +0 -183
  51. package/dist/src/core/logger.d.ts +0 -22
  52. package/dist/src/core/logger.js +0 -49
  53. package/dist/src/core/metrics.d.ts +0 -45
  54. package/dist/src/core/metrics.js +0 -111
  55. package/dist/src/core/native-adapter.d.ts +0 -11
  56. package/dist/src/core/native-adapter.js +0 -211
  57. package/dist/src/core/resources.d.ts +0 -9
  58. package/dist/src/core/resources.js +0 -25
  59. package/dist/src/core/scheduler.d.ts +0 -34
  60. package/dist/src/core/scheduler.js +0 -85
  61. package/dist/src/core/scope.d.ts +0 -26
  62. package/dist/src/core/scope.js +0 -68
  63. package/dist/src/core/serializer.d.ts +0 -10
  64. package/dist/src/core/serializer.js +0 -44
  65. package/dist/src/core/server.d.ts +0 -138
  66. package/dist/src/core/server.js +0 -1082
  67. package/dist/src/core/stream.d.ts +0 -15
  68. package/dist/src/core/stream.js +0 -71
  69. package/dist/src/core/tasks.d.ts +0 -29
  70. package/dist/src/core/tasks.js +0 -87
  71. package/dist/src/core/types.d.ts +0 -173
  72. package/dist/src/core/types.js +0 -19
  73. package/dist/src/core/websocket.d.ts +0 -25
  74. package/dist/src/core/websocket.js +0 -86
  75. package/dist/src/core/worker-queue.d.ts +0 -41
  76. package/dist/src/core/worker-queue.js +0 -73
  77. package/dist/src/database/adapters/memory.d.ts +0 -21
  78. package/dist/src/database/adapters/memory.js +0 -90
  79. package/dist/src/database/adapters/mongo.d.ts +0 -11
  80. package/dist/src/database/adapters/mongo.js +0 -141
  81. package/dist/src/database/adapters/postgres.d.ts +0 -10
  82. package/dist/src/database/adapters/postgres.js +0 -111
  83. package/dist/src/database/adapters/sqlite.d.ts +0 -10
  84. package/dist/src/database/adapters/sqlite.js +0 -42
  85. package/dist/src/database/coalescer.d.ts +0 -14
  86. package/dist/src/database/coalescer.js +0 -134
  87. package/dist/src/database/manager.d.ts +0 -35
  88. package/dist/src/database/manager.js +0 -87
  89. package/dist/src/database/types.d.ts +0 -20
  90. package/dist/src/database/types.js +0 -2
  91. package/dist/src/index.d.ts +0 -50
  92. package/dist/src/index.js +0 -91
  93. package/dist/src/middleware/compression.d.ts +0 -2
  94. package/dist/src/middleware/compression.js +0 -133
  95. package/dist/src/middleware/cors.d.ts +0 -2
  96. package/dist/src/middleware/cors.js +0 -66
  97. package/dist/src/middleware/presets.d.ts +0 -16
  98. package/dist/src/middleware/presets.js +0 -52
  99. package/dist/src/middleware/rate-limit.d.ts +0 -14
  100. package/dist/src/middleware/rate-limit.js +0 -83
  101. package/dist/src/middleware/security.d.ts +0 -21
  102. package/dist/src/middleware/security.js +0 -69
  103. package/dist/src/middleware/static.d.ts +0 -11
  104. package/dist/src/middleware/static.js +0 -191
  105. package/dist/src/native/index.d.ts +0 -29
  106. package/dist/src/native/index.js +0 -64
  107. package/dist/src/openapi/generator.d.ts +0 -19
  108. package/dist/src/openapi/generator.js +0 -149
  109. package/dist/src/router/radix-router.d.ts +0 -18
  110. package/dist/src/router/radix-router.js +0 -89
  111. package/dist/src/router/radix-tree.d.ts +0 -18
  112. package/dist/src/router/radix-tree.js +0 -131
  113. package/dist/src/router/router.d.ts +0 -34
  114. package/dist/src/router/router.js +0 -186
  115. package/dist/src/testing/index.d.ts +0 -25
  116. package/dist/src/testing/index.js +0 -84
  117. package/dist/src/utils/cookies.d.ts +0 -3
  118. package/dist/src/utils/cookies.js +0 -59
  119. package/dist/src/utils/logger.d.ts +0 -12
  120. package/dist/src/utils/logger.js +0 -45
  121. package/dist/src/utils/signals.d.ts +0 -6
  122. package/dist/src/utils/signals.js +0 -31
  123. package/dist/src/utils/sse.d.ts +0 -6
  124. package/dist/src/utils/sse.js +0 -32
  125. package/dist/src/validation/index.d.ts +0 -3
  126. package/dist/src/validation/index.js +0 -19
  127. package/dist/src/validation/simple.d.ts +0 -5
  128. package/dist/src/validation/simple.js +0 -102
  129. package/dist/src/validation/types.d.ts +0 -32
  130. package/dist/src/validation/types.js +0 -12
  131. package/dist/src/validation/zod.d.ts +0 -4
  132. package/dist/src/validation/zod.js +0 -18
  133. package/dist/src/views/index.d.ts +0 -1
  134. package/dist/src/views/index.js +0 -17
  135. package/dist/src/views/types.d.ts +0 -3
  136. package/dist/src/views/types.js +0 -2
  137. package/dist/tests/adapters.test.d.ts +0 -1
  138. package/dist/tests/adapters.test.js +0 -106
  139. package/dist/tests/batch.test.d.ts +0 -1
  140. package/dist/tests/batch.test.js +0 -117
  141. package/dist/tests/body-parser.test.d.ts +0 -1
  142. package/dist/tests/body-parser.test.js +0 -52
  143. package/dist/tests/compression-sse.test.d.ts +0 -1
  144. package/dist/tests/compression-sse.test.js +0 -87
  145. package/dist/tests/cookies.test.d.ts +0 -1
  146. package/dist/tests/cookies.test.js +0 -63
  147. package/dist/tests/cors.test.d.ts +0 -1
  148. package/dist/tests/cors.test.js +0 -55
  149. package/dist/tests/database.test.d.ts +0 -1
  150. package/dist/tests/database.test.js +0 -80
  151. package/dist/tests/dx.test.d.ts +0 -1
  152. package/dist/tests/dx.test.js +0 -114
  153. package/dist/tests/ecosystem.test.d.ts +0 -1
  154. package/dist/tests/ecosystem.test.js +0 -133
  155. package/dist/tests/features.test.d.ts +0 -1
  156. package/dist/tests/features.test.js +0 -47
  157. package/dist/tests/fusion.test.d.ts +0 -1
  158. package/dist/tests/fusion.test.js +0 -92
  159. package/dist/tests/http-basic.test.d.ts +0 -1
  160. package/dist/tests/http-basic.test.js +0 -124
  161. package/dist/tests/logger.test.d.ts +0 -1
  162. package/dist/tests/logger.test.js +0 -33
  163. package/dist/tests/middleware.test.d.ts +0 -1
  164. package/dist/tests/middleware.test.js +0 -109
  165. package/dist/tests/native-adapter.test.d.ts +0 -1
  166. package/dist/tests/native-adapter.test.js +0 -71
  167. package/dist/tests/observability.test.d.ts +0 -1
  168. package/dist/tests/observability.test.js +0 -59
  169. package/dist/tests/openapi.test.d.ts +0 -1
  170. package/dist/tests/openapi.test.js +0 -64
  171. package/dist/tests/plugin.test.d.ts +0 -1
  172. package/dist/tests/plugin.test.js +0 -65
  173. package/dist/tests/plugins.test.d.ts +0 -1
  174. package/dist/tests/plugins.test.js +0 -71
  175. package/dist/tests/rate-limit.test.d.ts +0 -1
  176. package/dist/tests/rate-limit.test.js +0 -77
  177. package/dist/tests/resources.test.d.ts +0 -1
  178. package/dist/tests/resources.test.js +0 -47
  179. package/dist/tests/scheduler.test.d.ts +0 -1
  180. package/dist/tests/scheduler.test.js +0 -46
  181. package/dist/tests/schema-routes.test.d.ts +0 -1
  182. package/dist/tests/schema-routes.test.js +0 -77
  183. package/dist/tests/security.test.d.ts +0 -1
  184. package/dist/tests/security.test.js +0 -83
  185. package/dist/tests/server-db.test.d.ts +0 -1
  186. package/dist/tests/server-db.test.js +0 -72
  187. package/dist/tests/smoke.test.d.ts +0 -1
  188. package/dist/tests/smoke.test.js +0 -10
  189. package/dist/tests/sqlite-fusion.test.d.ts +0 -1
  190. package/dist/tests/sqlite-fusion.test.js +0 -92
  191. package/dist/tests/static.test.d.ts +0 -1
  192. package/dist/tests/static.test.js +0 -102
  193. package/dist/tests/stream.test.d.ts +0 -1
  194. package/dist/tests/stream.test.js +0 -44
  195. package/dist/tests/task-metrics.test.d.ts +0 -1
  196. package/dist/tests/task-metrics.test.js +0 -53
  197. package/dist/tests/tasks.test.d.ts +0 -1
  198. package/dist/tests/tasks.test.js +0 -62
  199. package/dist/tests/testing.test.d.ts +0 -1
  200. package/dist/tests/testing.test.js +0 -47
  201. package/dist/tests/validation.test.d.ts +0 -1
  202. package/dist/tests/validation.test.js +0 -107
  203. package/dist/tests/websocket.test.d.ts +0 -1
  204. package/dist/tests/websocket.test.js +0 -146
  205. package/dist/vitest.config.d.ts +0 -2
  206. package/dist/vitest.config.js +0 -9
@@ -1,1082 +0,0 @@
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 = exports.QHTTPXContextImpl = void 0;
7
- const http_1 = __importDefault(require("http"));
8
- const path_1 = __importDefault(require("path"));
9
- const url_1 = require("url");
10
- const querystring_1 = require("querystring");
11
- const package_json_1 = __importDefault(require("../../package.json"));
12
- const router_1 = require("../router/router");
13
- const scheduler_1 = require("./scheduler");
14
- const tasks_1 = require("./tasks");
15
- const resources_1 = require("./resources");
16
- const metrics_1 = require("./metrics");
17
- const body_parser_1 = require("./body-parser");
18
- const buffer_pool_1 = require("./buffer-pool");
19
- const websocket_1 = require("./websocket");
20
- const serializer_1 = require("./serializer");
21
- const batch_1 = require("./batch");
22
- const fusion_1 = require("./fusion");
23
- const simple_1 = require("../validation/simple");
24
- const generator_1 = require("../openapi/generator");
25
- const types_1 = require("./types");
26
- const cookies_1 = require("../utils/cookies");
27
- const scope_1 = require("./scope");
28
- const logger_1 = require("./logger");
29
- const EMPTY_QUERY = Object.freeze({});
30
- class QHTTPXContextImpl {
31
- constructor(app) {
32
- this._url = null;
33
- this._app = app;
34
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
- this._appJsonSerializer = app.options.jsonSerializer;
36
- this.json = this.json.bind(this);
37
- this.send = this.send.bind(this);
38
- this.html = this.html.bind(this);
39
- this.redirect = this.redirect.bind(this);
40
- this.setCookie = this.setCookie.bind(this);
41
- this.render = this.render.bind(this);
42
- this.validate = this.validate.bind(this);
43
- }
44
- get bufferPool() {
45
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
- return this._app.bufferPool;
47
- }
48
- get db() {
49
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
- return this._app.options.database;
51
- }
52
- get state() {
53
- if (!this._state) {
54
- this._state = {};
55
- }
56
- return this._state;
57
- }
58
- set state(v) {
59
- this._state = v;
60
- }
61
- get ip() {
62
- if (this._ip)
63
- return this._ip;
64
- this._ip = this.req.socket.remoteAddress || '';
65
- return this._ip;
66
- }
67
- // Setter for manual override if needed (though usually read-only from socket)
68
- set ip(v) {
69
- this._ip = v;
70
- }
71
- get url() {
72
- if (this._url)
73
- return this._url;
74
- const host = this.headers.host || 'localhost';
75
- this._url = new url_1.URL(this.req.url || '/', `http://${host}`);
76
- return this._url;
77
- }
78
- set url(v) {
79
- this._url = v;
80
- }
81
- json(payload, status = 200) {
82
- const res = this.res;
83
- let body;
84
- if (this.serializer) {
85
- body = this.serializer(payload);
86
- }
87
- else if (this._appJsonSerializer) {
88
- body = this._appJsonSerializer(payload);
89
- }
90
- else {
91
- // Native JSON.stringify is faster for schema-less objects
92
- body = JSON.stringify(payload);
93
- }
94
- if (!res.headersSent) {
95
- res.statusCode = status;
96
- res.setHeader('content-type', 'application/json; charset=utf-8');
97
- }
98
- res.end(body);
99
- }
100
- send(payload, status = 200) {
101
- const res = this.res;
102
- if (!res.headersSent) {
103
- res.statusCode = status;
104
- }
105
- res.end(payload);
106
- }
107
- html(payload, status = 200) {
108
- const res = this.res;
109
- if (!res.headersSent) {
110
- res.statusCode = status;
111
- res.setHeader('content-type', 'text/html; charset=utf-8');
112
- }
113
- res.end(payload);
114
- }
115
- redirect(url, status = 302) {
116
- const res = this.res;
117
- if (!res.headersSent) {
118
- res.statusCode = status;
119
- res.setHeader('Location', url);
120
- }
121
- res.end();
122
- }
123
- setCookie(name, value, options) {
124
- const res = this.res;
125
- const serialized = (0, cookies_1.serializeCookie)(name, value, options);
126
- let existing = res.getHeader('Set-Cookie');
127
- if (Array.isArray(existing)) {
128
- existing.push(serialized);
129
- res.setHeader('Set-Cookie', existing);
130
- }
131
- else if (existing) {
132
- res.setHeader('Set-Cookie', [existing, serialized]);
133
- }
134
- else {
135
- res.setHeader('Set-Cookie', serialized);
136
- }
137
- }
138
- async render(view, locals) {
139
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
- const engine = this._app.options.viewEngine;
141
- if (!engine) {
142
- throw new Error('No view engine registered');
143
- }
144
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
145
- const viewsPath = this._app.options.viewsPath || process.cwd();
146
- const fullPath = path_1.default.resolve(viewsPath, view);
147
- const html = await engine.render(fullPath, locals || {});
148
- const res = this.res;
149
- if (!res.headersSent) {
150
- res.statusCode = 200;
151
- res.setHeader('content-type', 'text/html; charset=utf-8');
152
- }
153
- res.end(html);
154
- }
155
- async validate(schema, data) {
156
- const target = data ?? this.body;
157
- const result = await this._app.validator.validate(schema, target);
158
- if (result.success) {
159
- return result.data;
160
- }
161
- throw new types_1.HttpError(400, 'Validation Error', {
162
- code: 'VALIDATION_ERROR',
163
- details: result.error,
164
- });
165
- }
166
- reset() {
167
- this.req = null;
168
- this.res = null;
169
- this.headers = null;
170
- this._url = null;
171
- this.method = null;
172
- this._ip = undefined;
173
- this.params = null;
174
- this.query = null;
175
- this.body = undefined;
176
- this.files = undefined;
177
- this.cookies = null;
178
- this._state = undefined;
179
- this.requestId = undefined;
180
- this.requestStart = undefined;
181
- this.serializer = undefined;
182
- this.path = '';
183
- this.disableAutoEnd = false;
184
- this.error = undefined;
185
- }
186
- }
187
- exports.QHTTPXContextImpl = QHTTPXContextImpl;
188
- class QHTTPX {
189
- constructor(options = {}) {
190
- this.middlewares = [];
191
- this.contextPool = [];
192
- this.pipelineRunner = null;
193
- this.onStartHooks = [];
194
- this.onBeforeShutdownHooks = [];
195
- this.onShutdownHooks = [];
196
- this.nextRequestId = 1;
197
- this.requestCounter = 0;
198
- this.options = options;
199
- this.logger = new logger_1.Logger({
200
- name: options.name,
201
- level: options.performanceMode === 'ultra' ? 'error' : 'info'
202
- });
203
- this.ultraMode = options.performanceMode === 'ultra';
204
- this.errorHandler = this.ultraMode ? undefined : options.errorHandler;
205
- this.notFoundHandler = this.ultraMode ? undefined : options.notFoundHandler;
206
- this.methodNotAllowedHandler = this.ultraMode ? undefined : options.methodNotAllowedHandler;
207
- this.tracer = this.ultraMode ? undefined : options.tracer;
208
- this.workerCount = (0, resources_1.calculateWorkerCount)(options.workers ?? 'auto');
209
- this.router = new router_1.Router();
210
- this.wsManager = new websocket_1.WebSocketManager(this.generateRequestId.bind(this));
211
- this.bufferPool = new buffer_pool_1.BufferPool(options.bufferPoolConfig);
212
- const maxConcurrency = options.maxConcurrency ?? 1024;
213
- this.poolLimit = maxConcurrency * 2;
214
- this.scheduler = new scheduler_1.Scheduler({
215
- maxConcurrency,
216
- });
217
- this.tasks = new tasks_1.TaskEngine(this.scheduler);
218
- if (options.enableBatching) {
219
- this.batchExecutor = new batch_1.BatchExecutor(options.database);
220
- }
221
- if (options.enableRequestFusion) {
222
- this.fusion = new fusion_1.RequestFusion(options.enableRequestFusion);
223
- }
224
- this.validator = options.validator ?? new simple_1.SimpleValidator();
225
- this.metrics = new metrics_1.Metrics(this.scheduler, {
226
- enabled: !this.ultraMode && (this.options.metricsEnabled ?? true),
227
- }, this.tasks);
228
- for (let i = 0; i < maxConcurrency; i += 1) {
229
- this.contextPool.push(this.createContext());
230
- }
231
- this.registerInternalRoutes();
232
- this.compileMiddlewarePipeline();
233
- this.server = http_1.default.createServer(this.handleRequest.bind(this));
234
- if (this.options.keepAliveTimeoutMs !== undefined) {
235
- this.server.keepAliveTimeout = this.options.keepAliveTimeoutMs;
236
- }
237
- else if (this.ultraMode) {
238
- this.server.keepAliveTimeout = 0;
239
- }
240
- if (this.options.headersTimeoutMs !== undefined) {
241
- this.server.headersTimeout = this.options.headersTimeoutMs;
242
- }
243
- else if (this.ultraMode) {
244
- this.server.headersTimeout = 0;
245
- }
246
- if (this.ultraMode) {
247
- this.server.requestTimeout = 0;
248
- this.server.maxHeadersCount = 0;
249
- this.server.timeout = 0;
250
- }
251
- this.server.on('upgrade', (req, socket, head) => {
252
- void this.handleUpgrade(req, socket, head);
253
- });
254
- }
255
- get serverInstance() {
256
- return this.server;
257
- }
258
- get websocket() {
259
- return this.wsManager;
260
- }
261
- setErrorHandler(handler) {
262
- this.errorHandler = handler;
263
- }
264
- setNotFoundHandler(handler) {
265
- this.notFoundHandler = handler;
266
- }
267
- setMethodNotAllowedHandler(handler) {
268
- this.methodNotAllowedHandler = handler;
269
- }
270
- set404Handler(handler) {
271
- this.setNotFoundHandler(handler);
272
- }
273
- set405Handler(handler) {
274
- this.setMethodNotAllowedHandler(handler);
275
- }
276
- /**
277
- * Alias for setErrorHandler
278
- */
279
- onError(handler) {
280
- this.setErrorHandler(handler);
281
- }
282
- /**
283
- * Alias for setNotFoundHandler
284
- */
285
- notFound(handler) {
286
- this.setNotFoundHandler(handler);
287
- }
288
- onStart(hook) {
289
- this.onStartHooks.push(hook);
290
- }
291
- onBeforeShutdown(hook) {
292
- this.onBeforeShutdownHooks.push(hook);
293
- }
294
- onShutdown(hook) {
295
- this.onShutdownHooks.push(hook);
296
- }
297
- upgrade(path, handler) {
298
- this.wsManager.register(path, handler);
299
- }
300
- use(middleware) {
301
- this.middlewares.push(middleware);
302
- // Recompile pipeline after middleware is added
303
- this.compileMiddlewarePipeline();
304
- }
305
- compileMiddlewarePipeline() {
306
- const middlewares = this.middlewares;
307
- if (middlewares.length === 0) {
308
- this.pipelineRunner = async (ctx, handler) => {
309
- const result = handler(ctx);
310
- if (result && typeof result.then === 'function') {
311
- await result;
312
- }
313
- };
314
- return;
315
- }
316
- // Flatten middleware pipeline into a single function chain
317
- // This eliminates Promise nesting, recursive dispatch overhead, and microtask backlogs
318
- // Each middleware executes directly without closure allocation overhead
319
- this.pipelineRunner = async (ctx, handler) => {
320
- let index = 0;
321
- const executeNext = async () => {
322
- if (index >= middlewares.length) {
323
- // All middlewares done, execute handler
324
- const result = handler(ctx);
325
- if (result && typeof result.then === 'function') {
326
- await result;
327
- }
328
- return;
329
- }
330
- const currentIndex = index;
331
- index += 1;
332
- ctx.next = executeNext;
333
- const result = middlewares[currentIndex](ctx, executeNext);
334
- if (result && typeof result.then === 'function') {
335
- await result;
336
- }
337
- };
338
- await executeNext();
339
- };
340
- }
341
- compileRoutePipeline(handler,
342
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
343
- schema) {
344
- const middlewares = this.middlewares;
345
- // Heuristic: Is it a structured RouteSchema or a legacy ResponseSchema?
346
- let responseSchema;
347
- let requestSchema;
348
- if (schema) {
349
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
350
- const s = schema;
351
- if (s.body || s.query || s.params || s.headers || s.response) {
352
- // It is a RouteSchema
353
- requestSchema = s;
354
- responseSchema = s.response;
355
- }
356
- else {
357
- // It is a legacy ResponseSchema
358
- responseSchema = s;
359
- }
360
- }
361
- const stringifier = responseSchema ? (0, serializer_1.getStringifier)(responseSchema) : undefined;
362
- // Build middleware chain
363
- let pipeline = handler;
364
- // Wrap with validation if needed (before handler, after global middleware)
365
- if (requestSchema) {
366
- const inner = pipeline;
367
- pipeline = async (ctx) => {
368
- // Validate Body
369
- if (requestSchema.body) {
370
- const result = await this.validator.validate(requestSchema.body, ctx.body);
371
- if (!result.success) {
372
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
373
- const err = result.error;
374
- ctx.json({ error: 'Validation Error', details: err.details || err.message || err }, 400);
375
- return;
376
- }
377
- ctx.body = result.data;
378
- }
379
- // Validate Query
380
- if (requestSchema.query) {
381
- const result = await this.validator.validate(requestSchema.query, ctx.query);
382
- if (!result.success) {
383
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
384
- const err = result.error;
385
- ctx.json({ error: 'Validation Error', details: err.details || err.message || err }, 400);
386
- return;
387
- }
388
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
389
- ctx.query = result.data;
390
- }
391
- // Validate Params
392
- if (requestSchema.params) {
393
- const result = await this.validator.validate(requestSchema.params, ctx.params);
394
- if (!result.success) {
395
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
396
- const err = result.error;
397
- ctx.json({ error: 'Validation Error', details: err.details || err.message || err }, 400);
398
- return;
399
- }
400
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
401
- ctx.params = result.data;
402
- }
403
- return inner(ctx);
404
- };
405
- }
406
- // Wrap with Request Fusion if enabled
407
- if (this.fusion) {
408
- const inner = pipeline;
409
- pipeline = (ctx) => this.fusion.coalesce(ctx, inner);
410
- }
411
- // We iterate backwards to wrap the handler
412
- // pipeline = m[last](ctx, () => pipeline(ctx))
413
- for (let i = middlewares.length - 1; i >= 0; i -= 1) {
414
- const middleware = middlewares[i];
415
- const next = pipeline;
416
- pipeline = (ctx) => {
417
- const nextFn = async () => {
418
- const result = next(ctx);
419
- if (result && typeof result.then === 'function') {
420
- await result;
421
- }
422
- };
423
- // Attach next to ctx for destructuring support
424
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
425
- ctx.next = nextFn;
426
- return middleware(ctx, nextFn);
427
- };
428
- }
429
- if (stringifier) {
430
- const inner = pipeline;
431
- return (ctx) => {
432
- ctx.serializer = stringifier;
433
- return inner(ctx);
434
- };
435
- }
436
- return pipeline;
437
- }
438
- // Internal registration to be accessed by Scopes
439
- _registerRoute(method, path, handlerOrOptions) {
440
- this.registerRoute(method, path, handlerOrOptions);
441
- }
442
- async register(plugin, options) {
443
- const scope = new scope_1.QHTTPXScope(this, options?.prefix);
444
- await plugin(scope, options);
445
- }
446
- registerRoute(method, path, handlerOrOptions, handlerIfOptions) {
447
- let handler;
448
- let schema;
449
- const options = {};
450
- if (typeof handlerOrOptions === 'function') {
451
- handler = handlerOrOptions;
452
- }
453
- else if (handlerIfOptions) {
454
- handler = handlerIfOptions;
455
- schema = handlerOrOptions.schema;
456
- options.priority = handlerOrOptions.priority;
457
- }
458
- else {
459
- const opts = handlerOrOptions;
460
- handler = opts.handler;
461
- schema = opts.schema;
462
- options.priority = opts.priority;
463
- }
464
- const compiled = this.compileRoutePipeline(handler, schema);
465
- this.router.register(method, path, compiled, { ...options, schema });
466
- }
467
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
468
- get(path, arg1, arg2) {
469
- this.registerRoute('GET', path, arg1, arg2);
470
- }
471
- post(path, handlerOrOptions, handler) {
472
- this.registerRoute('POST', path, handlerOrOptions, handler);
473
- }
474
- put(path, handlerOrOptions, handler) {
475
- this.registerRoute('PUT', path, handlerOrOptions, handler);
476
- }
477
- delete(path, handlerOrOptions, handler) {
478
- this.registerRoute('DELETE', path, handlerOrOptions, handler);
479
- }
480
- route(path) {
481
- const register = (method, handler) => {
482
- this.registerRoute(method, path, handler);
483
- };
484
- const builder = {
485
- get(handler) {
486
- register('GET', handler);
487
- return this;
488
- },
489
- post(handler) {
490
- register('POST', handler);
491
- return this;
492
- },
493
- put(handler) {
494
- register('PUT', handler);
495
- return this;
496
- },
497
- delete(handler) {
498
- register('DELETE', handler);
499
- return this;
500
- },
501
- };
502
- return builder;
503
- }
504
- task(name, handler, options) {
505
- this.tasks.register(name, handler, options);
506
- }
507
- enqueue(name, payload) {
508
- return this.tasks.enqueue(name, payload);
509
- }
510
- op(name, handler) {
511
- if (!this.batchExecutor) {
512
- // Auto-enable batch executor if op is called?
513
- // Or throw?
514
- // Better to throw or warn if not enabled.
515
- // But for ease of use, let's create it if missing (but we miss DB context if done this way without options)
516
- // The constructor handles options. If enableBatching is false, we shouldn't be here.
517
- throw new Error('Batching is not enabled. Pass enableBatching: true to QHTTPX options.');
518
- }
519
- this.batchExecutor.register(name, handler);
520
- }
521
- registerInternalRoutes() {
522
- this.router.register('GET', '/__qhttpx/health', (ctx) => {
523
- const version = typeof package_json_1.default.version === 'string' ? package_json_1.default.version : '';
524
- const name = typeof package_json_1.default.name === 'string' ? package_json_1.default.name : '';
525
- ctx.json({
526
- status: 'ok',
527
- name,
528
- version,
529
- workers: this.workerCount,
530
- });
531
- });
532
- this.router.register('GET', '/__qhttpx/metrics', (ctx) => {
533
- const snapshot = this.metrics.snapshot();
534
- ctx.json({
535
- ...snapshot,
536
- workers: this.workerCount,
537
- });
538
- });
539
- this.router.register('GET', '/__qhttpx/runtime', (ctx) => {
540
- const schedulerStats = this.scheduler.getStats();
541
- ctx.json({
542
- workers: this.workerCount,
543
- router: {
544
- frozen: this.router.isFrozenRouter(),
545
- },
546
- scheduler: schedulerStats,
547
- });
548
- });
549
- if (this.options.enableBatching && this.batchExecutor) {
550
- const endpoint = typeof this.options.enableBatching === 'object'
551
- ? this.options.enableBatching.endpoint
552
- : '/qhttpx';
553
- this.router.register('POST', endpoint, async (ctx) => {
554
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
555
- const body = ctx.body;
556
- if (!body || !Array.isArray(body.batch)) {
557
- ctx.json({ error: 'Invalid batch format' }, 400);
558
- return;
559
- }
560
- try {
561
- const result = await this.batchExecutor.handleBatch(ctx, body.batch);
562
- ctx.json(result);
563
- }
564
- catch (err) {
565
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
566
- ctx.json({ error: err.message }, 500);
567
- }
568
- });
569
- }
570
- }
571
- getOpenAPI(options) {
572
- const generator = new generator_1.OpenAPIGenerator(this.router, options);
573
- return generator.generate();
574
- }
575
- async listen(port, hostnameOrCallback, callback) {
576
- let hostname;
577
- let cb;
578
- if (typeof hostnameOrCallback === 'function') {
579
- cb = hostnameOrCallback;
580
- hostname = undefined;
581
- }
582
- else {
583
- hostname = hostnameOrCallback;
584
- cb = callback;
585
- }
586
- if (this.options.database) {
587
- await this.options.database.connect();
588
- }
589
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
590
- if (this.options.plugins) {
591
- // Plugins are registered synchronously in constructor, but their async init happens now?
592
- // Actually, plugins are registered via app.register() which is async.
593
- // So they should be ready.
594
- }
595
- return new Promise((resolve, reject) => {
596
- const onError = (error) => {
597
- this.server.off('error', onError);
598
- reject(error);
599
- };
600
- this.server.once('error', onError);
601
- this.server.listen(port, hostname, () => {
602
- this.server.off('error', onError);
603
- // Freeze router after server starts to prevent further registrations
604
- this.router.freeze();
605
- void this.runLifecycleHooks(this.onStartHooks);
606
- const address = this.server.address();
607
- if (cb)
608
- cb();
609
- if (address && typeof address === 'object') {
610
- resolve({ port: address.port });
611
- }
612
- else {
613
- resolve({ port });
614
- }
615
- });
616
- });
617
- }
618
- close() {
619
- return new Promise((resolve, reject) => {
620
- this.server.close((err) => {
621
- if (err) {
622
- reject(err);
623
- return;
624
- }
625
- resolve();
626
- });
627
- });
628
- }
629
- async shutdown() {
630
- await this.runLifecycleHooks(this.onBeforeShutdownHooks);
631
- await this.close();
632
- await this.runLifecycleHooks(this.onShutdownHooks);
633
- }
634
- createContext() {
635
- return new QHTTPXContextImpl(this);
636
- }
637
- acquireContext(req, res, urlOrPath, params, query, requestId, body,
638
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
639
- files) {
640
- const ctx = this.contextPool.pop() ?? this.createContext();
641
- // Reset and populate properties
642
- // We use type assertions to write to readonly/managed properties for performance
643
- const mutableCtx = ctx;
644
- mutableCtx.req = req;
645
- mutableCtx.res = res;
646
- mutableCtx.headers = req.headers;
647
- if (typeof urlOrPath === 'string') {
648
- // Ultra mode or fast path: Lazy URL instantiation
649
- mutableCtx.path = urlOrPath;
650
- // Class-based getter handles URL parsing lazily using this.req
651
- }
652
- else {
653
- mutableCtx.url = urlOrPath;
654
- mutableCtx.path = urlOrPath.pathname;
655
- }
656
- mutableCtx.method = req.method;
657
- mutableCtx.params = params;
658
- mutableCtx.query = query;
659
- mutableCtx.body = body;
660
- mutableCtx.files = files;
661
- mutableCtx.requestId = requestId;
662
- mutableCtx.serializer = undefined;
663
- // In ultra mode, skip cookie parsing to save overhead
664
- if (!this.ultraMode) {
665
- mutableCtx.cookies = (0, cookies_1.parseCookies)(req.headers.cookie);
666
- }
667
- mutableCtx.disableAutoEnd = false;
668
- return mutableCtx;
669
- }
670
- releaseContext(ctx) {
671
- const mutableCtx = ctx;
672
- mutableCtx.reset();
673
- if (this.contextPool.length < this.poolLimit) {
674
- this.contextPool.push(ctx);
675
- }
676
- }
677
- async handleNoMatch(ctx, allowedMethods) {
678
- const res = ctx.res;
679
- const hasAnyMethod = allowedMethods.length > 0;
680
- if (hasAnyMethod && this.methodNotAllowedHandler) {
681
- const result = this.methodNotAllowedHandler(ctx, allowedMethods);
682
- if (result && typeof result.then === 'function') {
683
- await result;
684
- }
685
- if (!res.writableEnded && !res.headersSent) {
686
- res.statusCode = 405;
687
- res.setHeader('content-type', 'text/plain; charset=utf-8');
688
- res.end('Method Not Allowed');
689
- }
690
- }
691
- else if (!hasAnyMethod && this.notFoundHandler) {
692
- const result = this.notFoundHandler(ctx);
693
- if (result && typeof result.then === 'function') {
694
- await result;
695
- }
696
- if (!res.writableEnded && !res.headersSent) {
697
- res.statusCode = 404;
698
- res.setHeader('content-type', 'text/plain; charset=utf-8');
699
- res.end('Not Found');
700
- }
701
- }
702
- else if (hasAnyMethod) {
703
- if (!res.headersSent) {
704
- res.statusCode = 405;
705
- res.setHeader('content-type', 'text/plain; charset=utf-8');
706
- }
707
- res.end('Method Not Allowed');
708
- }
709
- else {
710
- if (!res.headersSent) {
711
- res.statusCode = 404;
712
- res.setHeader('content-type', 'text/plain; charset=utf-8');
713
- }
714
- res.end('Not Found');
715
- }
716
- }
717
- handleRequest(req, res) {
718
- // ⚡ Ultra Mode Optimization: Flattened & Inlined Logic
719
- if (this.ultraMode) {
720
- const rawUrl = req.url || '/';
721
- const method = (req.method || 'GET');
722
- // Single-pass pathname extraction
723
- let pathname;
724
- const qIndex = rawUrl.indexOf('?');
725
- pathname = qIndex === -1 ? rawUrl : rawUrl.substring(0, qIndex);
726
- const match = this.router.match(method, pathname);
727
- if (match) {
728
- // Acquire Context - reuse pooled object (no allocations)
729
- const ctx = this.contextPool.pop() ?? this.createContext();
730
- const mutableCtx = ctx;
731
- mutableCtx.req = req;
732
- mutableCtx.res = res;
733
- mutableCtx.path = pathname;
734
- mutableCtx.method = method;
735
- mutableCtx.params = match.params || QHTTPX.EMPTY_PARAMS;
736
- mutableCtx.query = EMPTY_QUERY;
737
- mutableCtx.requestId = undefined;
738
- mutableCtx.body = undefined;
739
- mutableCtx.files = undefined;
740
- mutableCtx.disableAutoEnd = false;
741
- // Don't allocate state - leave as undefined
742
- const releaseCtx = () => {
743
- mutableCtx.reset();
744
- if (this.contextPool.length < this.poolLimit) {
745
- this.contextPool.push(ctx);
746
- }
747
- };
748
- try {
749
- const result = match.handler(ctx);
750
- // Optimized promise detection
751
- if (result?.then) {
752
- result.then(() => {
753
- if (!res.writableEnded && !ctx.disableAutoEnd) {
754
- res.end();
755
- }
756
- releaseCtx();
757
- }).catch((err) => {
758
- console.error(err);
759
- if (!res.headersSent) {
760
- res.statusCode = 500;
761
- res.end();
762
- }
763
- releaseCtx();
764
- });
765
- }
766
- else {
767
- if (!res.writableEnded && !ctx.disableAutoEnd) {
768
- res.end();
769
- }
770
- releaseCtx();
771
- }
772
- }
773
- catch (err) {
774
- console.error(err);
775
- if (!res.headersSent) {
776
- res.statusCode = 500;
777
- res.end();
778
- }
779
- releaseCtx();
780
- }
781
- }
782
- else {
783
- // No match - Ultra Mode Fast 404
784
- if (!res.headersSent) {
785
- res.statusCode = 404;
786
- res.end('Not Found');
787
- }
788
- }
789
- return;
790
- }
791
- // === Balanced Mode Logic (Legacy) ===
792
- const rawMethod = (req.method || 'GET').toUpperCase();
793
- const method = rawMethod;
794
- const rawUrl = req.url || '/';
795
- // Fast path: parse URL without new URL() constructor (50-70x faster)
796
- let pathname = rawUrl;
797
- let query = EMPTY_QUERY;
798
- const queryIndex = rawUrl.indexOf('?');
799
- if (queryIndex !== -1) {
800
- // Extract pathname
801
- pathname = rawUrl.slice(0, queryIndex);
802
- // Parse query string only if present
803
- const queryString = rawUrl.slice(queryIndex + 1);
804
- if (queryString.length > 0) {
805
- const parsed = (0, querystring_1.parse)(queryString);
806
- query = parsed;
807
- }
808
- }
809
- else {
810
- // No query string, just extract pathname
811
- pathname = rawUrl;
812
- }
813
- // Request ID generation
814
- let requestId;
815
- if (!this.ultraMode) {
816
- const incomingRequestIdHeader = req.headers['x-request-id'];
817
- if (typeof incomingRequestIdHeader === 'string') {
818
- requestId = incomingRequestIdHeader;
819
- }
820
- else if (Array.isArray(incomingRequestIdHeader)) {
821
- requestId = incomingRequestIdHeader[0];
822
- }
823
- if (!requestId) {
824
- requestId = this.generateRequestId();
825
- }
826
- if (!res.headersSent && requestId) {
827
- res.setHeader('x-request-id', requestId);
828
- }
829
- }
830
- let match = this.router.match(method, pathname);
831
- const hasRoute = !!match;
832
- if (!match) {
833
- match = QHTTPX.EMPTY_MATCH;
834
- }
835
- const allowedMethods = this.router.getAllowedMethods(pathname);
836
- if (!hasRoute && !res.headersSent) {
837
- const hasAnyMethod = allowedMethods.length > 0;
838
- res.statusCode = hasAnyMethod ? 405 : 404;
839
- }
840
- // Memory Checks (throttled to every 100 requests for performance)
841
- if (this.options.maxMemoryBytes !== undefined && ++this.requestCounter % 100 === 0) {
842
- const sampleRss = process.memoryUsage().rss;
843
- const overloadedByMemory = (0, resources_1.isResourceOverloaded)({ rssBytes: sampleRss }, { maxRssBytes: this.options.maxMemoryBytes });
844
- if (overloadedByMemory) {
845
- const overloaded = () => {
846
- if (res.writableEnded) {
847
- return;
848
- }
849
- if (!res.headersSent) {
850
- res.statusCode = 503;
851
- res.setHeader('content-type', 'text/plain; charset=utf-8');
852
- }
853
- res.end('Server overloaded');
854
- };
855
- overloaded();
856
- return;
857
- }
858
- }
859
- else {
860
- ++this.requestCounter;
861
- }
862
- if (method !== 'GET' && method !== 'HEAD') {
863
- body_parser_1.BodyParser.parse(req, {
864
- maxBodyBytes: this.options.maxBodyBytes,
865
- }).then((parsed) => {
866
- this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, parsed.body, parsed.files);
867
- }).catch((err) => {
868
- if (err instanceof Error && err.message === 'QHTTPX_INVALID_JSON') {
869
- if (!res.headersSent) {
870
- res.statusCode = 400;
871
- res.setHeader('content-type', 'text/plain; charset=utf-8');
872
- }
873
- res.end('Invalid JSON');
874
- return;
875
- }
876
- if (err instanceof Error && err.message === 'QHTTPX_BODY_TOO_LARGE') {
877
- if (!res.headersSent) {
878
- res.statusCode = 413;
879
- res.setHeader('content-type', 'text/plain; charset=utf-8');
880
- }
881
- res.end('Payload Too Large');
882
- return;
883
- }
884
- if (!res.headersSent) {
885
- res.statusCode = 500;
886
- res.setHeader('content-type', 'text/plain; charset=utf-8');
887
- }
888
- res.end('Internal Server Error');
889
- });
890
- return;
891
- }
892
- this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, undefined, undefined);
893
- }
894
- dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, body, files) {
895
- const ctx = this.acquireContext(req, res, pathname, match.params, query, requestId, body, files);
896
- // Balanced Mode Logic
897
- const overloaded = () => {
898
- if (res.writableEnded) {
899
- return;
900
- }
901
- if (!res.headersSent) {
902
- res.statusCode = 503;
903
- res.setHeader('content-type', 'text/plain; charset=utf-8');
904
- }
905
- res.end('Server overloaded');
906
- };
907
- let timedOut = false;
908
- const onTimeout = () => {
909
- timedOut = true;
910
- if (res.writableEnded) {
911
- return;
912
- }
913
- if (!res.headersSent) {
914
- res.statusCode = 504;
915
- res.setHeader('content-type', 'text/plain; charset=utf-8');
916
- }
917
- res.end('Request timed out');
918
- };
919
- const start = Date.now();
920
- ctx.requestStart = start;
921
- this.metrics.onRequestStart();
922
- if (this.tracer) {
923
- const event = {
924
- type: 'request_start',
925
- method,
926
- path: pathname,
927
- requestId,
928
- };
929
- const result = this.tracer(event);
930
- if (result && typeof result.then === 'function') {
931
- void result;
932
- }
933
- }
934
- const finish = () => {
935
- const duration = Date.now() - start;
936
- this.metrics.onRequestEnd(duration, res.statusCode);
937
- if (timedOut) {
938
- this.metrics.onTimeout();
939
- }
940
- if (this.tracer) {
941
- const event = {
942
- type: 'request_end',
943
- method,
944
- path: pathname,
945
- statusCode: res.statusCode,
946
- durationMs: duration,
947
- requestId,
948
- };
949
- const result = this.tracer(event);
950
- if (result && typeof result.then === 'function') {
951
- void result;
952
- }
953
- }
954
- this.releaseContext(ctx);
955
- };
956
- const handle = async () => {
957
- const handler = match.handler;
958
- try {
959
- if (hasRoute) {
960
- const result = handler(ctx);
961
- if (result && typeof result.then === 'function') {
962
- await result;
963
- }
964
- }
965
- else if (this.pipelineRunner) {
966
- await this.pipelineRunner(ctx, handler);
967
- }
968
- if (!res.writableEnded) {
969
- if (!hasRoute) {
970
- await this.handleNoMatch(ctx, allowedMethods);
971
- }
972
- else if (!ctx.disableAutoEnd) {
973
- res.end();
974
- }
975
- }
976
- }
977
- catch (err) {
978
- await this.handleError(err, ctx);
979
- }
980
- };
981
- // Ultra mode: skip scheduler for minimal overhead
982
- if (this.ultraMode) {
983
- handle().then(finish).catch((err) => {
984
- console.error('Request handler error:', err);
985
- finish();
986
- });
987
- }
988
- else {
989
- this.scheduler.run(handle, {
990
- priority: match.priority,
991
- onOverloaded: overloaded,
992
- timeoutMs: this.options.requestTimeoutMs,
993
- onTimeout,
994
- }).then(finish).catch((err) => {
995
- console.error('Scheduler error:', err);
996
- finish();
997
- });
998
- }
999
- }
1000
- async handleUpgrade(req, socket, head) {
1001
- await this.wsManager.handleUpgrade(req, socket, head);
1002
- }
1003
- async runLifecycleHooks(hooks) {
1004
- if (hooks.length === 0) {
1005
- return;
1006
- }
1007
- for (const hook of hooks) {
1008
- try {
1009
- const result = hook();
1010
- if (result && typeof result.then === 'function') {
1011
- await result;
1012
- }
1013
- }
1014
- catch {
1015
- // Ignore hook errors to avoid impacting core server flow
1016
- }
1017
- }
1018
- }
1019
- handleError(err, ctx) {
1020
- const res = ctx.res;
1021
- if (res.writableEnded) {
1022
- return;
1023
- }
1024
- if (this.errorHandler) {
1025
- try {
1026
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1027
- const errorContext = ctx;
1028
- errorContext.error = err;
1029
- const result = this.errorHandler(errorContext);
1030
- if (result && typeof result.then === 'function') {
1031
- return result.then(() => {
1032
- // Ensure response is sent if handler didn't
1033
- });
1034
- }
1035
- if (res.writableEnded) {
1036
- return;
1037
- }
1038
- }
1039
- catch (handlerErr) {
1040
- // Fall through to default error handling below
1041
- console.error('Error in error handler:', handlerErr);
1042
- }
1043
- }
1044
- if (res.writableEnded) {
1045
- return;
1046
- }
1047
- if (err instanceof types_1.HttpError) {
1048
- if (!res.headersSent) {
1049
- res.statusCode = err.status;
1050
- res.setHeader('content-type', 'application/json; charset=utf-8');
1051
- }
1052
- const payload = {
1053
- error: {
1054
- message: err.message,
1055
- code: err.code ?? 'HTTP_ERROR',
1056
- details: err.details,
1057
- },
1058
- };
1059
- const serializer = this.options.jsonSerializer;
1060
- const body = serializer !== undefined ? serializer(payload) : JSON.stringify(payload);
1061
- res.end(body);
1062
- return;
1063
- }
1064
- if (!res.headersSent) {
1065
- res.statusCode = 500;
1066
- res.setHeader('content-type', 'text/plain; charset=utf-8');
1067
- }
1068
- res.end('Internal Server Error');
1069
- }
1070
- generateRequestId() {
1071
- const id = this.nextRequestId;
1072
- this.nextRequestId += 1;
1073
- return `${Date.now().toString(36)}-${id.toString(36)}`;
1074
- }
1075
- }
1076
- exports.QHTTPX = QHTTPX;
1077
- QHTTPX.EMPTY_PARAMS = Object.freeze({});
1078
- QHTTPX.EMPTY_MATCH = Object.freeze({
1079
- handler: () => { },
1080
- params: QHTTPX.EMPTY_PARAMS,
1081
- priority: types_1.RoutePriority.STANDARD,
1082
- });