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