qhttpx 2.1.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -21
- package/README.md +117 -221
- package/dist/chunk-QW72SEAS.mjs +98 -0
- package/dist/{src/cli/index.d.ts → cli.d.mts} +0 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +287 -0
- package/dist/cli.mjs +209 -0
- package/dist/index.d.mts +433 -0
- package/dist/index.d.ts +433 -0
- package/dist/index.js +1955 -0
- package/dist/index.mjs +1863 -0
- package/dist/qhttpx-core-new.linux-x64-gnu.node +0 -0
- package/dist/qhttpx-core-new.node +0 -0
- package/dist/qhttpx-core-new.win32-x64-msvc.node +0 -0
- package/examples/benchmark.ts +67 -0
- package/examples/body_demo.ts +32 -0
- package/examples/chat_client.ts +36 -0
- package/examples/chat_demo.ts +41 -0
- package/examples/cluster_demo.ts +26 -0
- package/examples/cors_demo.ts +25 -0
- package/examples/db_auth_demo.ts +66 -0
- package/examples/demo.ts +52 -0
- package/examples/headers_demo.ts +24 -0
- package/examples/hello.ts +7 -0
- package/examples/http2_demo.ts +52 -0
- package/examples/magic-dev.ts +15 -0
- package/examples/middleware_demo.ts +34 -0
- package/examples/mongo_demo.ts +40 -0
- package/examples/native_policy_demo.ts +33 -0
- package/examples/observability_demo.ts +24 -0
- package/examples/public/1mb.dat +1 -0
- package/examples/query_demo.ts +29 -0
- package/examples/response_demo.ts +21 -0
- package/examples/routing_demo.ts +24 -0
- package/examples/server.ts +51 -0
- package/examples/static_demo.ts +29 -0
- package/examples/test_middleware_client.ts +33 -0
- package/examples/test_query_client.ts +34 -0
- package/examples/test_response_client.ts +30 -0
- package/examples/tls_demo.ts +32 -0
- package/examples/upload_demo.ts +43 -0
- package/examples/uploads/test.txt +1 -0
- package/examples/verify_upload.ts +69 -0
- package/examples/ws_client.ts +26 -0
- package/examples/ws_demo.ts +21 -0
- package/package.json +65 -81
- package/CHANGELOG.md +0 -285
- package/dist/examples/api-server.d.ts +0 -1
- package/dist/examples/api-server.js +0 -80
- package/dist/examples/basic.d.ts +0 -1
- package/dist/examples/basic.js +0 -9
- package/dist/examples/compression.d.ts +0 -1
- package/dist/examples/compression.js +0 -15
- package/dist/examples/cors.d.ts +0 -1
- package/dist/examples/cors.js +0 -18
- package/dist/examples/errors.d.ts +0 -1
- package/dist/examples/errors.js +0 -26
- package/dist/examples/file-upload.d.ts +0 -1
- package/dist/examples/file-upload.js +0 -22
- package/dist/examples/fusion.d.ts +0 -1
- package/dist/examples/fusion.js +0 -21
- package/dist/examples/rate-limiting.d.ts +0 -1
- package/dist/examples/rate-limiting.js +0 -17
- package/dist/examples/validation.d.ts +0 -1
- package/dist/examples/validation.js +0 -22
- package/dist/examples/websockets.d.ts +0 -1
- package/dist/examples/websockets.js +0 -19
- package/dist/package.json +0 -107
- package/dist/src/benchmarks/quantam-users.d.ts +0 -1
- package/dist/src/benchmarks/quantam-users.js +0 -56
- package/dist/src/benchmarks/quick-bench.d.ts +0 -1
- package/dist/src/benchmarks/quick-bench.js +0 -57
- package/dist/src/benchmarks/simple-json.d.ts +0 -1
- package/dist/src/benchmarks/simple-json.js +0 -171
- package/dist/src/benchmarks/ultra-mode.d.ts +0 -1
- package/dist/src/benchmarks/ultra-mode.js +0 -64
- package/dist/src/cli/index.js +0 -222
- package/dist/src/client/index.d.ts +0 -17
- package/dist/src/client/index.js +0 -72
- package/dist/src/core/batch.d.ts +0 -24
- package/dist/src/core/batch.js +0 -97
- package/dist/src/core/body-parser.d.ts +0 -15
- package/dist/src/core/body-parser.js +0 -121
- package/dist/src/core/buffer-pool.d.ts +0 -41
- package/dist/src/core/buffer-pool.js +0 -70
- package/dist/src/core/config.d.ts +0 -7
- package/dist/src/core/config.js +0 -50
- package/dist/src/core/context-pool.d.ts +0 -12
- package/dist/src/core/context-pool.js +0 -34
- package/dist/src/core/errors.d.ts +0 -34
- package/dist/src/core/errors.js +0 -70
- package/dist/src/core/fusion.d.ts +0 -20
- package/dist/src/core/fusion.js +0 -191
- package/dist/src/core/logger.d.ts +0 -22
- package/dist/src/core/logger.js +0 -49
- package/dist/src/core/metrics.d.ts +0 -50
- package/dist/src/core/metrics.js +0 -123
- package/dist/src/core/resources.d.ts +0 -9
- package/dist/src/core/resources.js +0 -25
- package/dist/src/core/scheduler.d.ts +0 -38
- package/dist/src/core/scheduler.js +0 -126
- package/dist/src/core/scope.d.ts +0 -41
- package/dist/src/core/scope.js +0 -107
- package/dist/src/core/serializer.d.ts +0 -10
- package/dist/src/core/serializer.js +0 -82
- package/dist/src/core/server.d.ts +0 -179
- package/dist/src/core/server.js +0 -1511
- package/dist/src/core/stream.d.ts +0 -15
- package/dist/src/core/stream.js +0 -71
- package/dist/src/core/tasks.d.ts +0 -29
- package/dist/src/core/tasks.js +0 -87
- package/dist/src/core/timer.d.ts +0 -11
- package/dist/src/core/timer.js +0 -29
- package/dist/src/core/types.d.ts +0 -225
- package/dist/src/core/types.js +0 -19
- package/dist/src/core/websocket.d.ts +0 -25
- package/dist/src/core/websocket.js +0 -86
- package/dist/src/core/worker-queue.d.ts +0 -41
- package/dist/src/core/worker-queue.js +0 -73
- package/dist/src/database/adapters/memory.d.ts +0 -21
- package/dist/src/database/adapters/memory.js +0 -90
- package/dist/src/database/adapters/mongo.d.ts +0 -11
- package/dist/src/database/adapters/mongo.js +0 -141
- package/dist/src/database/adapters/postgres.d.ts +0 -10
- package/dist/src/database/adapters/postgres.js +0 -111
- package/dist/src/database/adapters/sqlite.d.ts +0 -10
- package/dist/src/database/adapters/sqlite.js +0 -42
- package/dist/src/database/coalescer.d.ts +0 -14
- package/dist/src/database/coalescer.js +0 -134
- package/dist/src/database/manager.d.ts +0 -35
- package/dist/src/database/manager.js +0 -87
- package/dist/src/database/types.d.ts +0 -20
- package/dist/src/database/types.js +0 -2
- package/dist/src/index.d.ts +0 -52
- package/dist/src/index.js +0 -92
- package/dist/src/middleware/compression.d.ts +0 -2
- package/dist/src/middleware/compression.js +0 -133
- package/dist/src/middleware/cors.d.ts +0 -2
- package/dist/src/middleware/cors.js +0 -66
- package/dist/src/middleware/presets.d.ts +0 -15
- package/dist/src/middleware/presets.js +0 -52
- package/dist/src/middleware/rate-limit.d.ts +0 -14
- package/dist/src/middleware/rate-limit.js +0 -83
- package/dist/src/middleware/security.d.ts +0 -10
- package/dist/src/middleware/security.js +0 -74
- package/dist/src/middleware/static.d.ts +0 -11
- package/dist/src/middleware/static.js +0 -191
- package/dist/src/openapi/generator.d.ts +0 -19
- package/dist/src/openapi/generator.js +0 -149
- package/dist/src/router/radix-router.d.ts +0 -18
- package/dist/src/router/radix-router.js +0 -89
- package/dist/src/router/radix-tree.d.ts +0 -21
- package/dist/src/router/radix-tree.js +0 -175
- package/dist/src/router/router.d.ts +0 -37
- package/dist/src/router/router.js +0 -203
- package/dist/src/testing/index.d.ts +0 -25
- package/dist/src/testing/index.js +0 -84
- package/dist/src/utils/cookies.d.ts +0 -3
- package/dist/src/utils/cookies.js +0 -59
- package/dist/src/utils/logger.d.ts +0 -2
- package/dist/src/utils/logger.js +0 -45
- package/dist/src/utils/signals.d.ts +0 -6
- package/dist/src/utils/signals.js +0 -31
- package/dist/src/utils/sse.d.ts +0 -6
- package/dist/src/utils/sse.js +0 -32
- package/dist/src/validation/index.d.ts +0 -3
- package/dist/src/validation/index.js +0 -19
- package/dist/src/validation/simple.d.ts +0 -5
- package/dist/src/validation/simple.js +0 -102
- package/dist/src/validation/types.d.ts +0 -32
- package/dist/src/validation/types.js +0 -12
- package/dist/src/validation/zod.d.ts +0 -4
- package/dist/src/validation/zod.js +0 -18
- package/dist/src/views/index.d.ts +0 -1
- package/dist/src/views/index.js +0 -17
- package/dist/src/views/types.d.ts +0 -3
- package/dist/src/views/types.js +0 -2
- package/dist/tests/adapters.test.d.ts +0 -1
- package/dist/tests/adapters.test.js +0 -106
- package/dist/tests/batch.test.d.ts +0 -1
- package/dist/tests/batch.test.js +0 -117
- package/dist/tests/body-parser.test.d.ts +0 -1
- package/dist/tests/body-parser.test.js +0 -52
- package/dist/tests/compression-sse.test.d.ts +0 -1
- package/dist/tests/compression-sse.test.js +0 -87
- package/dist/tests/cookies.test.d.ts +0 -1
- package/dist/tests/cookies.test.js +0 -63
- package/dist/tests/cors.test.d.ts +0 -1
- package/dist/tests/cors.test.js +0 -55
- package/dist/tests/database.test.d.ts +0 -1
- package/dist/tests/database.test.js +0 -80
- package/dist/tests/dx.test.d.ts +0 -1
- package/dist/tests/dx.test.js +0 -114
- package/dist/tests/ecosystem.test.d.ts +0 -1
- package/dist/tests/ecosystem.test.js +0 -133
- package/dist/tests/features.test.d.ts +0 -1
- package/dist/tests/features.test.js +0 -47
- package/dist/tests/fusion.test.d.ts +0 -1
- package/dist/tests/fusion.test.js +0 -92
- package/dist/tests/http-basic.test.d.ts +0 -1
- package/dist/tests/http-basic.test.js +0 -124
- package/dist/tests/logger.test.d.ts +0 -1
- package/dist/tests/logger.test.js +0 -33
- package/dist/tests/middleware.test.d.ts +0 -1
- package/dist/tests/middleware.test.js +0 -109
- package/dist/tests/observability.test.d.ts +0 -1
- package/dist/tests/observability.test.js +0 -59
- package/dist/tests/openapi.test.d.ts +0 -1
- package/dist/tests/openapi.test.js +0 -64
- package/dist/tests/plugin.test.d.ts +0 -1
- package/dist/tests/plugin.test.js +0 -65
- package/dist/tests/plugins.test.d.ts +0 -1
- package/dist/tests/plugins.test.js +0 -71
- package/dist/tests/rate-limit.test.d.ts +0 -1
- package/dist/tests/rate-limit.test.js +0 -77
- package/dist/tests/resources.test.d.ts +0 -1
- package/dist/tests/resources.test.js +0 -47
- package/dist/tests/scheduler.test.d.ts +0 -1
- package/dist/tests/scheduler.test.js +0 -46
- package/dist/tests/schema-routes.test.d.ts +0 -1
- package/dist/tests/schema-routes.test.js +0 -79
- package/dist/tests/security.test.d.ts +0 -1
- package/dist/tests/security.test.js +0 -83
- package/dist/tests/server-db.test.d.ts +0 -1
- package/dist/tests/server-db.test.js +0 -72
- package/dist/tests/smoke.test.d.ts +0 -1
- package/dist/tests/smoke.test.js +0 -10
- package/dist/tests/sqlite-fusion.test.d.ts +0 -1
- package/dist/tests/sqlite-fusion.test.js +0 -92
- package/dist/tests/static.test.d.ts +0 -1
- package/dist/tests/static.test.js +0 -102
- package/dist/tests/stream.test.d.ts +0 -1
- package/dist/tests/stream.test.js +0 -44
- package/dist/tests/task-metrics.test.d.ts +0 -1
- package/dist/tests/task-metrics.test.js +0 -53
- package/dist/tests/tasks.test.d.ts +0 -1
- package/dist/tests/tasks.test.js +0 -62
- package/dist/tests/testing.test.d.ts +0 -1
- package/dist/tests/testing.test.js +0 -47
- package/dist/tests/validation.test.d.ts +0 -1
- package/dist/tests/validation.test.js +0 -107
- package/dist/tests/websocket.test.d.ts +0 -1
- package/dist/tests/websocket.test.js +0 -146
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -9
- package/docs/AEGIS.md +0 -91
- package/docs/API_REFERENCE.md +0 -749
- package/docs/BENCHMARKS.md +0 -39
- package/docs/CAPABILITIES.md +0 -70
- package/docs/CLI.md +0 -43
- package/docs/DATABASE.md +0 -142
- package/docs/ECOSYSTEM.md +0 -146
- package/docs/ERRORS.md +0 -112
- package/docs/FUSION.md +0 -87
- package/docs/MIDDLEWARE.md +0 -65
- package/docs/MIGRATION_1.9_TO_2.0.md +0 -495
- package/docs/NEXT_STEPS.md +0 -99
- package/docs/OPENAPI.md +0 -99
- package/docs/PLUGINS.md +0 -59
- package/docs/PRODUCTION_DEPLOYMENT.md +0 -798
- package/docs/REAL_WORLD_EXAMPLES.md +0 -109
- package/docs/ROADMAP.md +0 -366
- package/docs/ROUTING.md +0 -78
- package/docs/SECURITY.md +0 -876
- package/docs/STATIC.md +0 -61
- package/docs/VALIDATION.md +0 -114
- package/docs/WEBSOCKETS.md +0 -76
package/dist/src/core/server.js
DELETED
|
@@ -1,1511 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.QHTTPX = exports.QHTTPXContextImpl = void 0;
|
|
40
|
-
const http_1 = __importDefault(require("http"));
|
|
41
|
-
const path_1 = __importDefault(require("path"));
|
|
42
|
-
const url_1 = require("url");
|
|
43
|
-
const querystring_1 = require("querystring");
|
|
44
|
-
const package_json_1 = __importDefault(require("../../package.json"));
|
|
45
|
-
const router_1 = require("../router/router");
|
|
46
|
-
const scheduler_1 = require("./scheduler");
|
|
47
|
-
const tasks_1 = require("./tasks");
|
|
48
|
-
const resources_1 = require("./resources");
|
|
49
|
-
const metrics_1 = require("./metrics");
|
|
50
|
-
const body_parser_1 = require("./body-parser");
|
|
51
|
-
const buffer_pool_1 = require("./buffer-pool");
|
|
52
|
-
const websocket_1 = require("./websocket");
|
|
53
|
-
const serializer_1 = require("./serializer");
|
|
54
|
-
const batch_1 = require("./batch");
|
|
55
|
-
const fusion_1 = require("./fusion");
|
|
56
|
-
const simple_1 = require("../validation/simple");
|
|
57
|
-
const zod_1 = require("../validation/zod");
|
|
58
|
-
const generator_1 = require("../openapi/generator");
|
|
59
|
-
const cors_1 = require("../middleware/cors");
|
|
60
|
-
const rate_limit_1 = require("../middleware/rate-limit");
|
|
61
|
-
const logger_1 = require("../utils/logger");
|
|
62
|
-
const security_1 = require("../middleware/security");
|
|
63
|
-
const compression_1 = require("../middleware/compression");
|
|
64
|
-
const types_1 = require("./types");
|
|
65
|
-
const cookies_1 = require("../utils/cookies");
|
|
66
|
-
const context_pool_1 = require("./context-pool");
|
|
67
|
-
const scope_1 = require("./scope");
|
|
68
|
-
const logger_2 = require("./logger");
|
|
69
|
-
const timer_1 = require("./timer");
|
|
70
|
-
const serializer_2 = require("./serializer");
|
|
71
|
-
const EMPTY_QUERY = Object.freeze({});
|
|
72
|
-
const CONTENT_TYPE = {
|
|
73
|
-
JSON: 'application/json; charset=utf-8',
|
|
74
|
-
HTML: 'text/html; charset=utf-8',
|
|
75
|
-
PLAIN: 'text/plain; charset=utf-8',
|
|
76
|
-
};
|
|
77
|
-
class QHTTPXContextImpl {
|
|
78
|
-
get params() { return this._params; }
|
|
79
|
-
set params(v) { this._params = v; this._dirty |= 0b0010; }
|
|
80
|
-
get body() { return this._body; }
|
|
81
|
-
set body(v) { this._body = v; this._dirty |= 0b0001; }
|
|
82
|
-
get files() { return this._files; }
|
|
83
|
-
set files(v) { this._files = v; this._dirty |= 0b10000; }
|
|
84
|
-
// Ultra-Simple API
|
|
85
|
-
httpError(status, message, details) {
|
|
86
|
-
return new types_1.HttpError(status, message, { details });
|
|
87
|
-
}
|
|
88
|
-
constructor(app) {
|
|
89
|
-
this._url = null;
|
|
90
|
-
// Dirty tracking for optimization
|
|
91
|
-
this._generation = 0;
|
|
92
|
-
this._dirty = 0;
|
|
93
|
-
this._customHeaders = null;
|
|
94
|
-
this._app = app;
|
|
95
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
96
|
-
this._appJsonSerializer = app.options.jsonSerializer;
|
|
97
|
-
this.json = this.json.bind(this);
|
|
98
|
-
this.send = this.send.bind(this);
|
|
99
|
-
this.html = this.html.bind(this);
|
|
100
|
-
this.redirect = this.redirect.bind(this);
|
|
101
|
-
this.setCookie = this.setCookie.bind(this);
|
|
102
|
-
this.render = this.render.bind(this);
|
|
103
|
-
this.validate = this.validate.bind(this);
|
|
104
|
-
this.httpError = this.httpError.bind(this);
|
|
105
|
-
}
|
|
106
|
-
get cookies() {
|
|
107
|
-
if (this._dirty & 0b1000)
|
|
108
|
-
return this._cookies;
|
|
109
|
-
if (!this._cookies)
|
|
110
|
-
this._cookies = Object.create(null);
|
|
111
|
-
const header = this.req.headers.cookie;
|
|
112
|
-
if (header) {
|
|
113
|
-
this._parseCookies(header);
|
|
114
|
-
}
|
|
115
|
-
this._dirty |= 0b1000;
|
|
116
|
-
return this._cookies;
|
|
117
|
-
}
|
|
118
|
-
set cookies(v) {
|
|
119
|
-
this._cookies = v;
|
|
120
|
-
this._dirty |= 0b1000;
|
|
121
|
-
}
|
|
122
|
-
get query() {
|
|
123
|
-
if (this._dirty & 0b0100)
|
|
124
|
-
return this._query;
|
|
125
|
-
const url = this.req.url || '/';
|
|
126
|
-
const queryIndex = url.indexOf('?');
|
|
127
|
-
if (queryIndex !== -1) {
|
|
128
|
-
const queryString = url.slice(queryIndex + 1);
|
|
129
|
-
if (queryString.length > 0) {
|
|
130
|
-
this._parseQuery(queryString);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
if (!this._query)
|
|
134
|
-
this._query = Object.create(null);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
if (!this._query)
|
|
139
|
-
this._query = Object.create(null);
|
|
140
|
-
}
|
|
141
|
-
this._dirty |= 0b0100;
|
|
142
|
-
return this._query;
|
|
143
|
-
}
|
|
144
|
-
set query(v) {
|
|
145
|
-
this._query = v;
|
|
146
|
-
this._dirty |= 0b0100;
|
|
147
|
-
}
|
|
148
|
-
_parseQuery(queryString) {
|
|
149
|
-
if (!this._query)
|
|
150
|
-
this._query = Object.create(null);
|
|
151
|
-
const query = this._query;
|
|
152
|
-
let i = 0;
|
|
153
|
-
const len = queryString.length;
|
|
154
|
-
while (i < len) {
|
|
155
|
-
let keyStart = i;
|
|
156
|
-
let keyEnd = -1;
|
|
157
|
-
let valStart = -1;
|
|
158
|
-
let valEnd = -1;
|
|
159
|
-
// Find key
|
|
160
|
-
while (i < len) {
|
|
161
|
-
const c = queryString.charCodeAt(i);
|
|
162
|
-
if (c === 61) { // =
|
|
163
|
-
keyEnd = i;
|
|
164
|
-
valStart = i + 1;
|
|
165
|
-
i++;
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
if (c === 38) { // &
|
|
169
|
-
keyEnd = i;
|
|
170
|
-
valEnd = i; // empty value
|
|
171
|
-
i++;
|
|
172
|
-
break;
|
|
173
|
-
}
|
|
174
|
-
i++;
|
|
175
|
-
}
|
|
176
|
-
if (keyEnd === -1) {
|
|
177
|
-
keyEnd = i;
|
|
178
|
-
valEnd = i; // empty value
|
|
179
|
-
}
|
|
180
|
-
// Find value
|
|
181
|
-
if (valStart !== -1) {
|
|
182
|
-
while (i < len) {
|
|
183
|
-
if (queryString.charCodeAt(i) === 38) { // &
|
|
184
|
-
valEnd = i;
|
|
185
|
-
i++;
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
i++;
|
|
189
|
-
}
|
|
190
|
-
if (valEnd === -1)
|
|
191
|
-
valEnd = i;
|
|
192
|
-
}
|
|
193
|
-
const key = decodeURIComponent(queryString.slice(keyStart, keyEnd));
|
|
194
|
-
const val = valStart !== -1 ? decodeURIComponent(queryString.slice(valStart, valEnd)) : '';
|
|
195
|
-
if (query[key] === undefined) {
|
|
196
|
-
query[key] = val;
|
|
197
|
-
}
|
|
198
|
-
else if (Array.isArray(query[key])) {
|
|
199
|
-
query[key].push(val);
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
query[key] = [query[key], val];
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
_parseCookies(header) {
|
|
207
|
-
const list = this._cookies;
|
|
208
|
-
let start = 0;
|
|
209
|
-
let end = header.length;
|
|
210
|
-
// Inline cookie parsing for speed
|
|
211
|
-
while (start < end) {
|
|
212
|
-
let eqIdx = header.indexOf('=', start);
|
|
213
|
-
if (eqIdx === -1)
|
|
214
|
-
break;
|
|
215
|
-
let semiIdx = header.indexOf(';', start);
|
|
216
|
-
if (semiIdx === -1)
|
|
217
|
-
semiIdx = end;
|
|
218
|
-
if (eqIdx < semiIdx) {
|
|
219
|
-
const key = header.slice(start, eqIdx).trim();
|
|
220
|
-
let val = header.slice(eqIdx + 1, semiIdx).trim();
|
|
221
|
-
// Decode if needed, simple check first
|
|
222
|
-
if (val.indexOf('%') !== -1) {
|
|
223
|
-
try {
|
|
224
|
-
val = decodeURIComponent(val);
|
|
225
|
-
}
|
|
226
|
-
catch { /* ignore */ }
|
|
227
|
-
}
|
|
228
|
-
if (!list[key]) {
|
|
229
|
-
list[key] = val;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
start = semiIdx + 1;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
get bufferPool() {
|
|
236
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
237
|
-
return this._app.bufferPool;
|
|
238
|
-
}
|
|
239
|
-
get db() {
|
|
240
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
241
|
-
return this._app.options.database;
|
|
242
|
-
}
|
|
243
|
-
get state() {
|
|
244
|
-
if (!this._state) {
|
|
245
|
-
this._state = {};
|
|
246
|
-
this._dirty |= 0b100000;
|
|
247
|
-
}
|
|
248
|
-
return this._state;
|
|
249
|
-
}
|
|
250
|
-
set state(v) {
|
|
251
|
-
this._state = v;
|
|
252
|
-
this._dirty |= 0b100000;
|
|
253
|
-
}
|
|
254
|
-
get ip() {
|
|
255
|
-
if (this._ip)
|
|
256
|
-
return this._ip;
|
|
257
|
-
this._ip = this.req.socket.remoteAddress || '';
|
|
258
|
-
return this._ip;
|
|
259
|
-
}
|
|
260
|
-
// Setter for manual override if needed (though usually read-only from socket)
|
|
261
|
-
set ip(v) {
|
|
262
|
-
this._ip = v;
|
|
263
|
-
}
|
|
264
|
-
get url() {
|
|
265
|
-
if (this._url)
|
|
266
|
-
return this._url;
|
|
267
|
-
const host = this.headers.host || 'localhost';
|
|
268
|
-
this._url = new url_1.URL(this.req.url || '/', `http://${host}`);
|
|
269
|
-
return this._url;
|
|
270
|
-
}
|
|
271
|
-
set url(v) {
|
|
272
|
-
this._url = v;
|
|
273
|
-
}
|
|
274
|
-
json(payload, status = 200) {
|
|
275
|
-
const res = this.res;
|
|
276
|
-
let body;
|
|
277
|
-
if (this.serializer) {
|
|
278
|
-
body = this.serializer(payload);
|
|
279
|
-
}
|
|
280
|
-
else if (this._appJsonSerializer) {
|
|
281
|
-
body = this._appJsonSerializer(payload);
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
// Use optimized fast path serializer
|
|
285
|
-
body = (0, serializer_2.fastJsonStringify)(payload);
|
|
286
|
-
}
|
|
287
|
-
if (!res.headersSent) {
|
|
288
|
-
res.statusCode = status;
|
|
289
|
-
res.setHeader('content-type', CONTENT_TYPE.JSON);
|
|
290
|
-
}
|
|
291
|
-
res.end(body);
|
|
292
|
-
}
|
|
293
|
-
send(payload, status = 200) {
|
|
294
|
-
const res = this.res;
|
|
295
|
-
if (!res.headersSent) {
|
|
296
|
-
res.statusCode = status;
|
|
297
|
-
}
|
|
298
|
-
res.end(payload);
|
|
299
|
-
}
|
|
300
|
-
html(payload, status = 200) {
|
|
301
|
-
const res = this.res;
|
|
302
|
-
if (!res.headersSent) {
|
|
303
|
-
res.statusCode = status;
|
|
304
|
-
res.setHeader('content-type', CONTENT_TYPE.HTML);
|
|
305
|
-
}
|
|
306
|
-
res.end(payload);
|
|
307
|
-
}
|
|
308
|
-
redirect(url, status = 302) {
|
|
309
|
-
const res = this.res;
|
|
310
|
-
if (!res.headersSent) {
|
|
311
|
-
res.statusCode = status;
|
|
312
|
-
res.setHeader('Location', url);
|
|
313
|
-
}
|
|
314
|
-
res.end();
|
|
315
|
-
}
|
|
316
|
-
setCookie(name, value, options) {
|
|
317
|
-
const res = this.res;
|
|
318
|
-
const serialized = (0, cookies_1.serializeCookie)(name, value, options);
|
|
319
|
-
let existing = res.getHeader('Set-Cookie');
|
|
320
|
-
if (Array.isArray(existing)) {
|
|
321
|
-
existing.push(serialized);
|
|
322
|
-
res.setHeader('Set-Cookie', existing);
|
|
323
|
-
}
|
|
324
|
-
else if (existing) {
|
|
325
|
-
res.setHeader('Set-Cookie', [existing, serialized]);
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
res.setHeader('Set-Cookie', serialized);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
async render(view, locals) {
|
|
332
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
333
|
-
const engine = this._app.options.viewEngine;
|
|
334
|
-
if (!engine) {
|
|
335
|
-
throw new Error('No view engine registered');
|
|
336
|
-
}
|
|
337
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
338
|
-
const viewsPath = this._app.options.viewsPath || process.cwd();
|
|
339
|
-
const fullPath = path_1.default.resolve(viewsPath, view);
|
|
340
|
-
const html = await engine.render(fullPath, locals || {});
|
|
341
|
-
const res = this.res;
|
|
342
|
-
if (!res.headersSent) {
|
|
343
|
-
res.statusCode = 200;
|
|
344
|
-
res.setHeader('content-type', CONTENT_TYPE.HTML);
|
|
345
|
-
}
|
|
346
|
-
res.end(html);
|
|
347
|
-
}
|
|
348
|
-
async validate(schema, data) {
|
|
349
|
-
const target = data ?? this.body;
|
|
350
|
-
const result = await this._app.validator.validate(schema, target);
|
|
351
|
-
if (result.success) {
|
|
352
|
-
return result.data;
|
|
353
|
-
}
|
|
354
|
-
throw new types_1.HttpError(400, 'Validation Error', {
|
|
355
|
-
code: 'VALIDATION_ERROR',
|
|
356
|
-
details: result.error,
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
reset() {
|
|
360
|
-
// Tiered reset based on what was actually used
|
|
361
|
-
if (this._dirty & 0b0001)
|
|
362
|
-
this._body = undefined;
|
|
363
|
-
if (this._dirty & 0b0010)
|
|
364
|
-
this._params = null;
|
|
365
|
-
// Recycle query object
|
|
366
|
-
if (this._dirty & 0b0100) {
|
|
367
|
-
if (this._query) {
|
|
368
|
-
for (const key in this._query)
|
|
369
|
-
delete this._query[key];
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
// Recycle cookies object
|
|
373
|
-
if (this._dirty & 0b1000) {
|
|
374
|
-
if (this._cookies) {
|
|
375
|
-
for (const key in this._cookies)
|
|
376
|
-
delete this._cookies[key];
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
if (this._dirty & 0b10000)
|
|
380
|
-
this._files = undefined;
|
|
381
|
-
if (this._dirty & 0b100000)
|
|
382
|
-
this._state = undefined;
|
|
383
|
-
this._dirty = 0;
|
|
384
|
-
this.req = null;
|
|
385
|
-
this.res = null;
|
|
386
|
-
this.headers = null;
|
|
387
|
-
this._url = null;
|
|
388
|
-
this.method = null;
|
|
389
|
-
this._ip = undefined;
|
|
390
|
-
this.requestId = undefined;
|
|
391
|
-
this.requestStart = undefined;
|
|
392
|
-
this.serializer = undefined;
|
|
393
|
-
this.path = '';
|
|
394
|
-
this.disableAutoEnd = false;
|
|
395
|
-
this.error = undefined;
|
|
396
|
-
this.next = undefined;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
exports.QHTTPXContextImpl = QHTTPXContextImpl;
|
|
400
|
-
class QHTTPX {
|
|
401
|
-
constructor(options = {}) {
|
|
402
|
-
this.middlewares = [];
|
|
403
|
-
this.pipelineRunner = null;
|
|
404
|
-
this.onStartHooks = [];
|
|
405
|
-
this.onBeforeShutdownHooks = [];
|
|
406
|
-
this.onShutdownHooks = [];
|
|
407
|
-
this.nextRequestId = 1;
|
|
408
|
-
this.lastDateNow = Date.now();
|
|
409
|
-
this.lastDateString = this.lastDateNow.toString(36);
|
|
410
|
-
this.requestCounter = 0;
|
|
411
|
-
this.options = options;
|
|
412
|
-
this.logger = new logger_2.Logger({
|
|
413
|
-
name: options.name,
|
|
414
|
-
level: 'info'
|
|
415
|
-
});
|
|
416
|
-
this.errorHandler = options.errorHandler;
|
|
417
|
-
this.notFoundHandler = options.notFoundHandler;
|
|
418
|
-
this.methodNotAllowedHandler = options.methodNotAllowedHandler;
|
|
419
|
-
this.tracer = options.tracer;
|
|
420
|
-
this.workerCount = (0, resources_1.calculateWorkerCount)(options.workers ?? 'auto');
|
|
421
|
-
this.router = new router_1.Router();
|
|
422
|
-
this.wsManager = new websocket_1.WebSocketManager(this.generateRequestId.bind(this));
|
|
423
|
-
this.bufferPool = new buffer_pool_1.BufferPool(options.bufferPoolConfig);
|
|
424
|
-
const maxConcurrency = options.maxConcurrency ?? 1024;
|
|
425
|
-
this.poolLimit = maxConcurrency * 2;
|
|
426
|
-
this.scheduler = new scheduler_1.Scheduler({
|
|
427
|
-
maxConcurrency,
|
|
428
|
-
});
|
|
429
|
-
this.tasks = new tasks_1.TaskEngine(this.scheduler);
|
|
430
|
-
if (options.enableBatching) {
|
|
431
|
-
this.batchExecutor = new batch_1.BatchExecutor(options.database);
|
|
432
|
-
}
|
|
433
|
-
if (options.enableRequestFusion) {
|
|
434
|
-
this._fusion = new fusion_1.RequestFusion(options.enableRequestFusion);
|
|
435
|
-
}
|
|
436
|
-
this.validator = options.validator ?? new simple_1.SimpleValidator();
|
|
437
|
-
this._metrics = new metrics_1.Metrics(this.scheduler, {
|
|
438
|
-
enabled: this.options.metricsEnabled ?? true,
|
|
439
|
-
}, this.tasks, this._fusion);
|
|
440
|
-
this.contextPool = new context_pool_1.ContextPool(() => this.createContext(), (ctx) => ctx.reset(), maxConcurrency, this.poolLimit);
|
|
441
|
-
this.registerInternalRoutes();
|
|
442
|
-
this.compileMiddlewarePipeline();
|
|
443
|
-
this.server = http_1.default.createServer(this.handleRequest.bind(this));
|
|
444
|
-
if (this.options.keepAliveTimeoutMs !== undefined) {
|
|
445
|
-
this.server.keepAliveTimeout = this.options.keepAliveTimeoutMs;
|
|
446
|
-
}
|
|
447
|
-
if (this.options.headersTimeoutMs !== undefined) {
|
|
448
|
-
this.server.headersTimeout = this.options.headersTimeoutMs;
|
|
449
|
-
}
|
|
450
|
-
// Keep-alive and timeout settings handled by options above
|
|
451
|
-
this.server.on('upgrade', (req, socket, head) => {
|
|
452
|
-
void this.handleUpgrade(req, socket, head);
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
get serverInstance() {
|
|
456
|
-
return this.server;
|
|
457
|
-
}
|
|
458
|
-
get websocket() {
|
|
459
|
-
return this.wsManager;
|
|
460
|
-
}
|
|
461
|
-
setErrorHandler(handler) {
|
|
462
|
-
this.errorHandler = handler;
|
|
463
|
-
}
|
|
464
|
-
setNotFoundHandler(handler) {
|
|
465
|
-
this.notFoundHandler = handler;
|
|
466
|
-
}
|
|
467
|
-
setMethodNotAllowedHandler(handler) {
|
|
468
|
-
this.methodNotAllowedHandler = handler;
|
|
469
|
-
}
|
|
470
|
-
set404Handler(handler) {
|
|
471
|
-
this.setNotFoundHandler(handler);
|
|
472
|
-
}
|
|
473
|
-
set405Handler(handler) {
|
|
474
|
-
this.setMethodNotAllowedHandler(handler);
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Alias for setErrorHandler
|
|
478
|
-
*/
|
|
479
|
-
onError(handler) {
|
|
480
|
-
this.setErrorHandler(handler);
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Alias for setNotFoundHandler
|
|
484
|
-
*/
|
|
485
|
-
notFound(handler) {
|
|
486
|
-
this.setNotFoundHandler(handler);
|
|
487
|
-
}
|
|
488
|
-
onStart(hook) {
|
|
489
|
-
this.onStartHooks.push(hook);
|
|
490
|
-
}
|
|
491
|
-
onBeforeShutdown(hook) {
|
|
492
|
-
this.onBeforeShutdownHooks.push(hook);
|
|
493
|
-
}
|
|
494
|
-
onShutdown(hook) {
|
|
495
|
-
this.onShutdownHooks.push(hook);
|
|
496
|
-
}
|
|
497
|
-
upgrade(path, handler) {
|
|
498
|
-
this.wsManager.register(path, handler);
|
|
499
|
-
}
|
|
500
|
-
use(middleware) {
|
|
501
|
-
this.middlewares.push(middleware);
|
|
502
|
-
// Recompile pipeline after middleware is added
|
|
503
|
-
this.compileMiddlewarePipeline();
|
|
504
|
-
}
|
|
505
|
-
compileMiddlewarePipeline() {
|
|
506
|
-
const middlewares = this.middlewares;
|
|
507
|
-
let runner;
|
|
508
|
-
if (middlewares.length === 0) {
|
|
509
|
-
runner = async (ctx, handler) => {
|
|
510
|
-
const result = handler(ctx);
|
|
511
|
-
// Handle return values
|
|
512
|
-
if (result instanceof Promise) {
|
|
513
|
-
const val = await result;
|
|
514
|
-
if (val !== undefined && !ctx.res.headersSent) {
|
|
515
|
-
if (ctx.method === 'POST' && ctx.res.statusCode === 200)
|
|
516
|
-
ctx.res.statusCode = 201;
|
|
517
|
-
if (typeof val === 'object')
|
|
518
|
-
ctx.json(val);
|
|
519
|
-
else
|
|
520
|
-
ctx.send(String(val));
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
else if (result !== undefined && !ctx.res.headersSent) {
|
|
524
|
-
if (ctx.method === 'POST' && ctx.res.statusCode === 200)
|
|
525
|
-
ctx.res.statusCode = 201;
|
|
526
|
-
if (typeof result === 'object')
|
|
527
|
-
ctx.json(result);
|
|
528
|
-
else
|
|
529
|
-
ctx.send(String(result));
|
|
530
|
-
}
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
else {
|
|
534
|
-
// Flatten middleware pipeline into a single function chain
|
|
535
|
-
// This eliminates Promise nesting, recursive dispatch overhead, and microtask backlogs
|
|
536
|
-
// Each middleware executes directly without closure allocation overhead
|
|
537
|
-
runner = async (ctx, handler) => {
|
|
538
|
-
let index = 0;
|
|
539
|
-
const executeNext = async () => {
|
|
540
|
-
if (index >= middlewares.length) {
|
|
541
|
-
// All middlewares done, execute handler
|
|
542
|
-
const result = handler(ctx);
|
|
543
|
-
// Handle return values (Auto-Response)
|
|
544
|
-
if (result instanceof Promise) {
|
|
545
|
-
const val = await result;
|
|
546
|
-
if (val !== undefined && !ctx.res.headersSent) {
|
|
547
|
-
if (typeof val === 'object')
|
|
548
|
-
ctx.json(val);
|
|
549
|
-
else
|
|
550
|
-
ctx.send(String(val));
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
else if (result !== undefined && !ctx.res.headersSent) {
|
|
554
|
-
if (typeof result === 'object')
|
|
555
|
-
ctx.json(result);
|
|
556
|
-
else
|
|
557
|
-
ctx.send(String(result));
|
|
558
|
-
}
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
const currentIndex = index;
|
|
562
|
-
index += 1;
|
|
563
|
-
ctx.next = executeNext;
|
|
564
|
-
const result = middlewares[currentIndex](ctx, executeNext);
|
|
565
|
-
if (result && typeof result.then === 'function') {
|
|
566
|
-
await result;
|
|
567
|
-
}
|
|
568
|
-
};
|
|
569
|
-
await executeNext();
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
if (this._fusion) {
|
|
573
|
-
// Deprecated: Global fusion wrapping moved to per-route compilation
|
|
574
|
-
// to ensure body parsing and middleware execution happens before key generation.
|
|
575
|
-
// this.pipelineRunner = async (ctx, handler) => {
|
|
576
|
-
// await this._fusion!.coalesce(ctx, async (c) => {
|
|
577
|
-
// await runner(c, handler);
|
|
578
|
-
// });
|
|
579
|
-
// };
|
|
580
|
-
this.pipelineRunner = runner;
|
|
581
|
-
}
|
|
582
|
-
else {
|
|
583
|
-
this.pipelineRunner = runner;
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
compileRoutePipeline(handler,
|
|
587
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
588
|
-
schema) {
|
|
589
|
-
const middlewares = this.middlewares;
|
|
590
|
-
// Optimization: If no middleware and no schema and no fusion, return handler directly
|
|
591
|
-
// This avoids wrapping for simple routes
|
|
592
|
-
if (middlewares.length === 0 && !schema && !this._fusion) {
|
|
593
|
-
return handler;
|
|
594
|
-
}
|
|
595
|
-
// Heuristic: Is it a structured RouteSchema or a legacy ResponseSchema?
|
|
596
|
-
let responseSchema;
|
|
597
|
-
let requestSchema;
|
|
598
|
-
if (schema) {
|
|
599
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
600
|
-
const s = schema;
|
|
601
|
-
if (s.body || s.query || s.params || s.headers || s.response) {
|
|
602
|
-
// It is a RouteSchema
|
|
603
|
-
requestSchema = s;
|
|
604
|
-
responseSchema = s.response;
|
|
605
|
-
}
|
|
606
|
-
else {
|
|
607
|
-
// It is a legacy ResponseSchema
|
|
608
|
-
responseSchema = s;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
const stringifier = responseSchema ? (0, serializer_1.getStringifier)(responseSchema) : undefined;
|
|
612
|
-
// Build middleware chain
|
|
613
|
-
let pipeline = handler;
|
|
614
|
-
// Wrap with validation if needed (before handler, after global middleware)
|
|
615
|
-
if (requestSchema) {
|
|
616
|
-
const inner = pipeline;
|
|
617
|
-
pipeline = async (ctx) => {
|
|
618
|
-
// Validate Body
|
|
619
|
-
if (requestSchema.body) {
|
|
620
|
-
const result = await this.validator.validate(requestSchema.body, ctx.body);
|
|
621
|
-
if (!result.success) {
|
|
622
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
623
|
-
const err = result.error;
|
|
624
|
-
ctx.json({ error: 'Validation Error', details: err.details || err.message || err }, 400);
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
ctx.body = result.data;
|
|
628
|
-
}
|
|
629
|
-
// Validate Query
|
|
630
|
-
if (requestSchema.query) {
|
|
631
|
-
const result = await this.validator.validate(requestSchema.query, ctx.query);
|
|
632
|
-
if (!result.success) {
|
|
633
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
634
|
-
const err = result.error;
|
|
635
|
-
ctx.json({ error: 'Validation Error', details: err.details || err.message || err }, 400);
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
639
|
-
ctx.query = result.data;
|
|
640
|
-
}
|
|
641
|
-
// Validate Params
|
|
642
|
-
if (requestSchema.params) {
|
|
643
|
-
const result = await this.validator.validate(requestSchema.params, ctx.params);
|
|
644
|
-
if (!result.success) {
|
|
645
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
646
|
-
const err = result.error;
|
|
647
|
-
ctx.json({ error: 'Validation Error', details: err.details || err.message || err }, 400);
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
651
|
-
ctx.params = result.data;
|
|
652
|
-
}
|
|
653
|
-
return inner(ctx);
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
// Wrap with Request Fusion if enabled
|
|
657
|
-
if (this._fusion) {
|
|
658
|
-
const inner = pipeline;
|
|
659
|
-
pipeline = (ctx) => this._fusion.coalesce(ctx, inner);
|
|
660
|
-
}
|
|
661
|
-
// Optimization: Pre-compile middleware chain for this specific route
|
|
662
|
-
// This avoids closure allocation per-request and recursive dispatch overhead
|
|
663
|
-
// "Route-Specific Middleware Compilation"
|
|
664
|
-
if (middlewares.length > 0) {
|
|
665
|
-
const innerHandler = pipeline;
|
|
666
|
-
pipeline = async (ctx) => {
|
|
667
|
-
let index = 0;
|
|
668
|
-
const executeNext = async () => {
|
|
669
|
-
if (index >= middlewares.length) {
|
|
670
|
-
// All middlewares done, execute handler
|
|
671
|
-
const result = innerHandler(ctx);
|
|
672
|
-
if (result && typeof result.then === 'function') {
|
|
673
|
-
await result;
|
|
674
|
-
}
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
const currentIndex = index;
|
|
678
|
-
index += 1;
|
|
679
|
-
ctx.next = executeNext;
|
|
680
|
-
const result = middlewares[currentIndex](ctx, executeNext);
|
|
681
|
-
if (result && typeof result.then === 'function') {
|
|
682
|
-
await result;
|
|
683
|
-
}
|
|
684
|
-
};
|
|
685
|
-
await executeNext();
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
if (stringifier) {
|
|
689
|
-
const inner = pipeline;
|
|
690
|
-
return (ctx) => {
|
|
691
|
-
ctx.serializer = stringifier;
|
|
692
|
-
return inner(ctx);
|
|
693
|
-
};
|
|
694
|
-
}
|
|
695
|
-
return pipeline;
|
|
696
|
-
}
|
|
697
|
-
// Internal registration to be accessed by Scopes
|
|
698
|
-
_registerRoute(method, path, handlerOrOptions, handlerIfOptions) {
|
|
699
|
-
this.registerRoute(method, path, handlerOrOptions, handlerIfOptions);
|
|
700
|
-
}
|
|
701
|
-
async register(plugin, options) {
|
|
702
|
-
const scope = new scope_1.QHTTPXScope(this, options?.prefix);
|
|
703
|
-
await plugin(scope, options);
|
|
704
|
-
}
|
|
705
|
-
registerRoute(method, path, handlerOrOptions, handlerIfOptions) {
|
|
706
|
-
let handler;
|
|
707
|
-
let schema;
|
|
708
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
709
|
-
let staticResponse;
|
|
710
|
-
const options = {};
|
|
711
|
-
if (typeof handlerOrOptions === 'function') {
|
|
712
|
-
handler = handlerOrOptions;
|
|
713
|
-
}
|
|
714
|
-
else if (handlerIfOptions) {
|
|
715
|
-
handler = handlerIfOptions;
|
|
716
|
-
const config = handlerOrOptions;
|
|
717
|
-
// Extract schema from flattened options or use explicit schema
|
|
718
|
-
if (config.schema) {
|
|
719
|
-
schema = config.schema;
|
|
720
|
-
}
|
|
721
|
-
else if (config.body || config.params || config.query || config.headers || config.response) {
|
|
722
|
-
schema = {
|
|
723
|
-
body: config.body,
|
|
724
|
-
params: config.params,
|
|
725
|
-
query: config.query,
|
|
726
|
-
headers: config.headers,
|
|
727
|
-
response: config.response
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
options.priority = config.priority;
|
|
731
|
-
staticResponse = config.staticResponse;
|
|
732
|
-
}
|
|
733
|
-
else {
|
|
734
|
-
const opts = handlerOrOptions;
|
|
735
|
-
if (opts.handler) {
|
|
736
|
-
handler = opts.handler;
|
|
737
|
-
}
|
|
738
|
-
else {
|
|
739
|
-
throw new Error(`Handler is required for route ${method} ${path}`);
|
|
740
|
-
}
|
|
741
|
-
// Extract schema from flattened options or use explicit schema
|
|
742
|
-
if (opts.schema) {
|
|
743
|
-
schema = opts.schema;
|
|
744
|
-
}
|
|
745
|
-
else if (opts.body || opts.params || opts.query || opts.headers || opts.response) {
|
|
746
|
-
schema = {
|
|
747
|
-
body: opts.body,
|
|
748
|
-
params: opts.params,
|
|
749
|
-
query: opts.query,
|
|
750
|
-
headers: opts.headers,
|
|
751
|
-
response: opts.response
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
options.priority = opts.priority;
|
|
755
|
-
staticResponse = opts.staticResponse;
|
|
756
|
-
}
|
|
757
|
-
// Nuclear Optimization: Static Response Pre-building
|
|
758
|
-
if (staticResponse !== undefined) {
|
|
759
|
-
const jsonStr = JSON.stringify(staticResponse);
|
|
760
|
-
const buffer = Buffer.from(jsonStr);
|
|
761
|
-
const length = buffer.length;
|
|
762
|
-
// Replace handler with optimized one that bypasses serialization
|
|
763
|
-
handler = (ctx) => {
|
|
764
|
-
ctx.res.writeHead(200, {
|
|
765
|
-
'Content-Type': 'application/json',
|
|
766
|
-
'Content-Length': String(length),
|
|
767
|
-
});
|
|
768
|
-
ctx.res.end(buffer);
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
const compiled = this.compileRoutePipeline(handler, schema);
|
|
772
|
-
// Pass the compiled pipeline as metadata to the router
|
|
773
|
-
// This allows match results to contain the full execution chain directly
|
|
774
|
-
// The router will still perform matching based on path, but the resulting
|
|
775
|
-
// RouteMatch will have this pipeline readily available
|
|
776
|
-
options.metadata = {
|
|
777
|
-
needsQuery: false, // Calculated by Router.detectMetadata if not provided
|
|
778
|
-
needsCookies: false,
|
|
779
|
-
needsBody: false,
|
|
780
|
-
pipeline: compiled
|
|
781
|
-
};
|
|
782
|
-
this.router.register(method, path, compiled, { ...options, schema });
|
|
783
|
-
}
|
|
784
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
785
|
-
get(path, arg1, arg2) {
|
|
786
|
-
this.registerRoute('GET', path, arg1, arg2);
|
|
787
|
-
}
|
|
788
|
-
post(path, handlerOrOptions, handlerOrConfig) {
|
|
789
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
790
|
-
this.registerRoute('POST', path, handlerOrOptions, handlerOrConfig);
|
|
791
|
-
}
|
|
792
|
-
put(path, handlerOrOptions, handlerOrConfig) {
|
|
793
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
794
|
-
this.registerRoute('PUT', path, handlerOrOptions, handlerOrConfig);
|
|
795
|
-
}
|
|
796
|
-
delete(path, handlerOrOptions, handlerOrConfig) {
|
|
797
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
798
|
-
this.registerRoute('DELETE', path, handlerOrOptions, handlerOrConfig);
|
|
799
|
-
}
|
|
800
|
-
patch(path, handlerOrOptions, handlerOrConfig) {
|
|
801
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
802
|
-
this.registerRoute('PATCH', path, handlerOrOptions, handlerOrConfig);
|
|
803
|
-
}
|
|
804
|
-
route(path) {
|
|
805
|
-
const register = (method, handler) => {
|
|
806
|
-
this.registerRoute(method, path, handler);
|
|
807
|
-
};
|
|
808
|
-
const builder = {
|
|
809
|
-
get(handler) {
|
|
810
|
-
register('GET', handler);
|
|
811
|
-
return this;
|
|
812
|
-
},
|
|
813
|
-
post(handler) {
|
|
814
|
-
register('POST', handler);
|
|
815
|
-
return this;
|
|
816
|
-
},
|
|
817
|
-
put(handler) {
|
|
818
|
-
register('PUT', handler);
|
|
819
|
-
return this;
|
|
820
|
-
},
|
|
821
|
-
delete(handler) {
|
|
822
|
-
register('DELETE', handler);
|
|
823
|
-
return this;
|
|
824
|
-
},
|
|
825
|
-
};
|
|
826
|
-
return builder;
|
|
827
|
-
}
|
|
828
|
-
task(name, handler, options) {
|
|
829
|
-
this.tasks.register(name, handler, options);
|
|
830
|
-
}
|
|
831
|
-
enqueue(name, payload) {
|
|
832
|
-
return this.tasks.enqueue(name, payload);
|
|
833
|
-
}
|
|
834
|
-
op(name, handler) {
|
|
835
|
-
if (!this.batchExecutor) {
|
|
836
|
-
// Auto-enable batch executor if op is called?
|
|
837
|
-
// Or throw?
|
|
838
|
-
// Better to throw or warn if not enabled.
|
|
839
|
-
// But for ease of use, let's create it if missing (but we miss DB context if done this way without options)
|
|
840
|
-
// The constructor handles options. If enableBatching is false, we shouldn't be here.
|
|
841
|
-
throw new Error('Batching is not enabled. Pass enableBatching: true to QHTTPX options.');
|
|
842
|
-
}
|
|
843
|
-
this.batchExecutor.register(name, handler);
|
|
844
|
-
}
|
|
845
|
-
registerInternalRoutes() {
|
|
846
|
-
this.router.register('GET', '/__qhttpx/health', ({ json }) => {
|
|
847
|
-
const version = typeof package_json_1.default.version === 'string' ? package_json_1.default.version : '';
|
|
848
|
-
const name = typeof package_json_1.default.name === 'string' ? package_json_1.default.name : '';
|
|
849
|
-
json({
|
|
850
|
-
status: 'ok',
|
|
851
|
-
name,
|
|
852
|
-
version,
|
|
853
|
-
workers: this.workerCount,
|
|
854
|
-
});
|
|
855
|
-
});
|
|
856
|
-
this.router.register('GET', '/__qhttpx/metrics', ({ json }) => {
|
|
857
|
-
const snapshot = this._metrics.snapshot();
|
|
858
|
-
json({
|
|
859
|
-
...snapshot,
|
|
860
|
-
workers: this.workerCount,
|
|
861
|
-
});
|
|
862
|
-
});
|
|
863
|
-
this.router.register('GET', '/__qhttpx/runtime', ({ json }) => {
|
|
864
|
-
const schedulerStats = this.scheduler.getStats();
|
|
865
|
-
json({
|
|
866
|
-
workers: this.workerCount,
|
|
867
|
-
router: {
|
|
868
|
-
frozen: this.router.isFrozenRouter(),
|
|
869
|
-
},
|
|
870
|
-
scheduler: schedulerStats,
|
|
871
|
-
});
|
|
872
|
-
});
|
|
873
|
-
if (this.options.enableBatching && this.batchExecutor) {
|
|
874
|
-
const endpoint = typeof this.options.enableBatching === 'object'
|
|
875
|
-
? this.options.enableBatching.endpoint
|
|
876
|
-
: '/qhttpx';
|
|
877
|
-
this.router.register('POST', endpoint, async (ctx) => {
|
|
878
|
-
// Keep full ctx here as we pass it to handleBatch
|
|
879
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
880
|
-
const body = ctx.body;
|
|
881
|
-
if (!body || !Array.isArray(body.batch)) {
|
|
882
|
-
ctx.json({ error: 'Invalid batch format' }, 400);
|
|
883
|
-
return;
|
|
884
|
-
}
|
|
885
|
-
try {
|
|
886
|
-
const result = await this.batchExecutor.handleBatch(ctx, body.batch);
|
|
887
|
-
ctx.json(result);
|
|
888
|
-
}
|
|
889
|
-
catch (err) {
|
|
890
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
891
|
-
ctx.json({ error: err.message }, 500);
|
|
892
|
-
}
|
|
893
|
-
});
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
// Sugar Methods for Fluent API
|
|
897
|
-
database(manager) {
|
|
898
|
-
this.options.database = manager;
|
|
899
|
-
// Auto-connect?
|
|
900
|
-
// manager.connect() is async.
|
|
901
|
-
// We can't await here.
|
|
902
|
-
// Maybe we queue it?
|
|
903
|
-
// But QHTTPX doesn't have a queue for DB connection.
|
|
904
|
-
// The user might need to await app.start().
|
|
905
|
-
// We can hook into onStart.
|
|
906
|
-
this.onStart(async () => {
|
|
907
|
-
await manager.connect();
|
|
908
|
-
});
|
|
909
|
-
return this;
|
|
910
|
-
}
|
|
911
|
-
security(options) {
|
|
912
|
-
// 1. CORS
|
|
913
|
-
let corsOpts = options?.cors;
|
|
914
|
-
if (corsOpts === undefined)
|
|
915
|
-
corsOpts = true;
|
|
916
|
-
if (corsOpts !== false) {
|
|
917
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
918
|
-
const opts = corsOpts === true ? {} : corsOpts;
|
|
919
|
-
this.use((0, cors_1.createCorsMiddleware)(opts));
|
|
920
|
-
}
|
|
921
|
-
// 2. Security Headers
|
|
922
|
-
this.use((0, security_1.createSecurityHeadersMiddleware)(options?.securityHeaders));
|
|
923
|
-
// 3. Rate Limit (Default 100 req/15min)
|
|
924
|
-
const rateLimitOpts = options?.rateLimit || {};
|
|
925
|
-
this.use((0, rate_limit_1.rateLimit)({
|
|
926
|
-
windowMs: 15 * 60 * 1000,
|
|
927
|
-
max: 100,
|
|
928
|
-
...rateLimitOpts
|
|
929
|
-
}));
|
|
930
|
-
return this;
|
|
931
|
-
}
|
|
932
|
-
log(options) {
|
|
933
|
-
const loggerOptions = typeof options === 'object' ? options : {};
|
|
934
|
-
this.use((0, logger_1.createLoggerMiddleware)(loggerOptions));
|
|
935
|
-
this.options.logging = options || true;
|
|
936
|
-
return this;
|
|
937
|
-
}
|
|
938
|
-
validate(validator) {
|
|
939
|
-
if (validator) {
|
|
940
|
-
this.options.validator = validator;
|
|
941
|
-
}
|
|
942
|
-
else {
|
|
943
|
-
// Default to ZodValidator
|
|
944
|
-
this.options.validator = new zod_1.ZodValidator();
|
|
945
|
-
}
|
|
946
|
-
return this;
|
|
947
|
-
}
|
|
948
|
-
production() {
|
|
949
|
-
this.options.performanceMode = 'default';
|
|
950
|
-
this.use((0, compression_1.createCompressionMiddleware)());
|
|
951
|
-
// Auto-Cluster
|
|
952
|
-
// Note: Clustering usually needs to be at the process entry point.
|
|
953
|
-
// But we can check if we are primary and fork.
|
|
954
|
-
// For now, let's just enable optimizations.
|
|
955
|
-
// We can add a helper or documentation that 'app.start()' handles clustering if production() was called?
|
|
956
|
-
// Or better, app.start() can check options.performanceMode.
|
|
957
|
-
return this;
|
|
958
|
-
}
|
|
959
|
-
routes(prefix, plugin) {
|
|
960
|
-
this.register(plugin, { prefix });
|
|
961
|
-
return this;
|
|
962
|
-
}
|
|
963
|
-
// Ultra-Simple API: Fluent Rate Limit
|
|
964
|
-
rateLimit(option, interval) {
|
|
965
|
-
if (typeof option === 'string') {
|
|
966
|
-
// Preset
|
|
967
|
-
const presets = {
|
|
968
|
-
'strict': { windowMs: 15 * 60 * 1000, max: 10 },
|
|
969
|
-
'standard': { windowMs: 15 * 60 * 1000, max: 100 },
|
|
970
|
-
'relaxed': { windowMs: 60 * 60 * 1000, max: 1000 }
|
|
971
|
-
};
|
|
972
|
-
const config = presets[option] || presets['standard'];
|
|
973
|
-
this.use((0, rate_limit_1.rateLimit)(config));
|
|
974
|
-
}
|
|
975
|
-
else if (typeof option === 'number') {
|
|
976
|
-
// app.rateLimit(100, "1m");
|
|
977
|
-
let ms = 60 * 1000;
|
|
978
|
-
if (interval) {
|
|
979
|
-
if (interval === 'minute' || interval === '1m')
|
|
980
|
-
ms = 60 * 1000;
|
|
981
|
-
if (interval === 'hour' || interval === '1h')
|
|
982
|
-
ms = 60 * 60 * 1000;
|
|
983
|
-
if (interval === 'second' || interval === '1s')
|
|
984
|
-
ms = 1000;
|
|
985
|
-
}
|
|
986
|
-
this.use((0, rate_limit_1.rateLimit)({
|
|
987
|
-
max: option,
|
|
988
|
-
windowMs: ms
|
|
989
|
-
}));
|
|
990
|
-
}
|
|
991
|
-
return this;
|
|
992
|
-
}
|
|
993
|
-
allow(count) {
|
|
994
|
-
return {
|
|
995
|
-
per: (interval) => {
|
|
996
|
-
let ms = 60 * 1000;
|
|
997
|
-
if (interval === 'minute' || interval === '1m')
|
|
998
|
-
ms = 60 * 1000;
|
|
999
|
-
if (interval === 'hour' || interval === '1h')
|
|
1000
|
-
ms = 60 * 60 * 1000;
|
|
1001
|
-
if (interval === 'second' || interval === '1s')
|
|
1002
|
-
ms = 1000;
|
|
1003
|
-
this.use((0, rate_limit_1.rateLimit)({
|
|
1004
|
-
max: count,
|
|
1005
|
-
windowMs: ms
|
|
1006
|
-
}));
|
|
1007
|
-
return this;
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
fusion(enable = true) {
|
|
1012
|
-
if (enable === false) {
|
|
1013
|
-
// We can't easily remove it if it's there, but we can disable it in logic if we had a flag.
|
|
1014
|
-
// But for now, let's assume this is for enabling.
|
|
1015
|
-
return this;
|
|
1016
|
-
}
|
|
1017
|
-
if (!this._fusion) {
|
|
1018
|
-
// Lazy load the class to avoid circular dep issues if any (though imported at top)
|
|
1019
|
-
// But we already import RequestFusion.
|
|
1020
|
-
// We need to set it to this.fusion (which is readonly in TS, but we can cast or ignore)
|
|
1021
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1022
|
-
this._fusion = new fusion_1.RequestFusion(enable);
|
|
1023
|
-
// Re-compile pipeline to include fusion wrapper if needed
|
|
1024
|
-
// Actually, fusion is applied per-route in compileRoutePipeline.
|
|
1025
|
-
// But we might need to re-compile existing routes?
|
|
1026
|
-
// Usually configuration happens before routes.
|
|
1027
|
-
}
|
|
1028
|
-
return this;
|
|
1029
|
-
}
|
|
1030
|
-
bodyLimit(bytes) {
|
|
1031
|
-
this.options.maxBodyBytes = bytes;
|
|
1032
|
-
return this;
|
|
1033
|
-
}
|
|
1034
|
-
metrics(enable = true) {
|
|
1035
|
-
this.options.metricsEnabled = enable;
|
|
1036
|
-
this._metrics.setEnabled(enable);
|
|
1037
|
-
return this;
|
|
1038
|
-
}
|
|
1039
|
-
error(status, message, details) {
|
|
1040
|
-
return new types_1.HttpError(status, message, { details });
|
|
1041
|
-
}
|
|
1042
|
-
start(port) {
|
|
1043
|
-
return this.listen(port);
|
|
1044
|
-
}
|
|
1045
|
-
getOpenAPI(options) {
|
|
1046
|
-
const generator = new generator_1.OpenAPIGenerator(this.router, options);
|
|
1047
|
-
return generator.generate();
|
|
1048
|
-
}
|
|
1049
|
-
async listen(port, hostnameOrCallback, callback) {
|
|
1050
|
-
// Ultra-Simple API: Clustering support in Production
|
|
1051
|
-
if (this.options.performanceMode === 'default' && !process.env.QHTTPX_NO_CLUSTER) {
|
|
1052
|
-
const cluster = await Promise.resolve().then(() => __importStar(require('cluster')));
|
|
1053
|
-
const os = await Promise.resolve().then(() => __importStar(require('os')));
|
|
1054
|
-
if (cluster.default.isPrimary) {
|
|
1055
|
-
const numCPUs = os.cpus().length;
|
|
1056
|
-
console.log(`Primary ${process.pid} is running. Forking ${numCPUs} workers...`);
|
|
1057
|
-
for (let i = 0; i < numCPUs; i++) {
|
|
1058
|
-
cluster.default.fork();
|
|
1059
|
-
}
|
|
1060
|
-
cluster.default.on('exit', (worker) => {
|
|
1061
|
-
console.log(`worker ${worker.process.pid} died`);
|
|
1062
|
-
});
|
|
1063
|
-
// Return a dummy promise for primary, it keeps running
|
|
1064
|
-
return new Promise(() => { });
|
|
1065
|
-
}
|
|
1066
|
-
else {
|
|
1067
|
-
// Worker process falls through to normal listen
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
let hostname;
|
|
1071
|
-
let cb;
|
|
1072
|
-
if (typeof hostnameOrCallback === 'function') {
|
|
1073
|
-
cb = hostnameOrCallback;
|
|
1074
|
-
hostname = undefined;
|
|
1075
|
-
}
|
|
1076
|
-
else {
|
|
1077
|
-
hostname = hostnameOrCallback;
|
|
1078
|
-
cb = callback;
|
|
1079
|
-
}
|
|
1080
|
-
if (this.options.database) {
|
|
1081
|
-
await this.options.database.connect();
|
|
1082
|
-
}
|
|
1083
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1084
|
-
if (this.options.plugins) {
|
|
1085
|
-
// Plugins are registered synchronously in constructor, but their async init happens now?
|
|
1086
|
-
// Actually, plugins are registered via app.register() which is async.
|
|
1087
|
-
// So they should be ready.
|
|
1088
|
-
}
|
|
1089
|
-
return new Promise((resolve, reject) => {
|
|
1090
|
-
const onError = (error) => {
|
|
1091
|
-
this.server.off('error', onError);
|
|
1092
|
-
reject(error);
|
|
1093
|
-
};
|
|
1094
|
-
this.server.once('error', onError);
|
|
1095
|
-
this.server.listen(port, hostname, () => {
|
|
1096
|
-
this.server.off('error', onError);
|
|
1097
|
-
// Freeze router after server starts to prevent further registrations
|
|
1098
|
-
this.router.freeze();
|
|
1099
|
-
void this.runLifecycleHooks(this.onStartHooks);
|
|
1100
|
-
const address = this.server.address();
|
|
1101
|
-
if (cb)
|
|
1102
|
-
cb();
|
|
1103
|
-
if (address && typeof address === 'object') {
|
|
1104
|
-
resolve({ port: address.port });
|
|
1105
|
-
}
|
|
1106
|
-
else {
|
|
1107
|
-
resolve({ port });
|
|
1108
|
-
}
|
|
1109
|
-
});
|
|
1110
|
-
});
|
|
1111
|
-
}
|
|
1112
|
-
close() {
|
|
1113
|
-
return new Promise((resolve, reject) => {
|
|
1114
|
-
this.server.close((err) => {
|
|
1115
|
-
if (err) {
|
|
1116
|
-
reject(err);
|
|
1117
|
-
return;
|
|
1118
|
-
}
|
|
1119
|
-
resolve();
|
|
1120
|
-
});
|
|
1121
|
-
});
|
|
1122
|
-
}
|
|
1123
|
-
async shutdown() {
|
|
1124
|
-
await this.runLifecycleHooks(this.onBeforeShutdownHooks);
|
|
1125
|
-
await this.close();
|
|
1126
|
-
await this.runLifecycleHooks(this.onShutdownHooks);
|
|
1127
|
-
}
|
|
1128
|
-
createContext() {
|
|
1129
|
-
return new QHTTPXContextImpl(this);
|
|
1130
|
-
}
|
|
1131
|
-
acquireContext(req, res, urlOrPath, params, query, requestId, body,
|
|
1132
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1133
|
-
files, cookies) {
|
|
1134
|
-
const ctx = this.contextPool.acquire();
|
|
1135
|
-
// Reset and populate properties
|
|
1136
|
-
// We use type assertions to write to readonly/managed properties for performance
|
|
1137
|
-
const mutableCtx = ctx;
|
|
1138
|
-
mutableCtx.req = req;
|
|
1139
|
-
mutableCtx.res = res;
|
|
1140
|
-
mutableCtx.headers = req.headers;
|
|
1141
|
-
if (typeof urlOrPath === 'string') {
|
|
1142
|
-
// Ultra mode or fast path: Lazy URL instantiation
|
|
1143
|
-
mutableCtx.path = urlOrPath;
|
|
1144
|
-
// Class-based getter handles URL parsing lazily using this.req
|
|
1145
|
-
}
|
|
1146
|
-
else {
|
|
1147
|
-
mutableCtx.url = urlOrPath;
|
|
1148
|
-
mutableCtx.path = urlOrPath.pathname;
|
|
1149
|
-
}
|
|
1150
|
-
mutableCtx.method = req.method;
|
|
1151
|
-
mutableCtx.params = params;
|
|
1152
|
-
if (query) {
|
|
1153
|
-
mutableCtx.query = query;
|
|
1154
|
-
}
|
|
1155
|
-
mutableCtx.body = body;
|
|
1156
|
-
mutableCtx.files = files;
|
|
1157
|
-
if (cookies) {
|
|
1158
|
-
mutableCtx.cookies = cookies;
|
|
1159
|
-
}
|
|
1160
|
-
mutableCtx.requestId = requestId;
|
|
1161
|
-
mutableCtx.serializer = undefined;
|
|
1162
|
-
// In ultra mode, skip cookie parsing to save overhead
|
|
1163
|
-
// Cookies are now parsed lazily via getter in QHTTPXContextImpl
|
|
1164
|
-
mutableCtx.disableAutoEnd = false;
|
|
1165
|
-
return mutableCtx;
|
|
1166
|
-
}
|
|
1167
|
-
releaseContext(ctx) {
|
|
1168
|
-
this.contextPool.release(ctx);
|
|
1169
|
-
}
|
|
1170
|
-
async handleNoMatch(ctx, allowedMethods) {
|
|
1171
|
-
const res = ctx.res;
|
|
1172
|
-
const hasAnyMethod = allowedMethods.length > 0;
|
|
1173
|
-
if (hasAnyMethod && this.methodNotAllowedHandler) {
|
|
1174
|
-
const result = this.methodNotAllowedHandler(ctx, allowedMethods);
|
|
1175
|
-
if (result && typeof result.then === 'function') {
|
|
1176
|
-
await result;
|
|
1177
|
-
}
|
|
1178
|
-
if (!res.writableEnded && !res.headersSent) {
|
|
1179
|
-
res.statusCode = 405;
|
|
1180
|
-
res.setHeader('content-type', CONTENT_TYPE.PLAIN);
|
|
1181
|
-
res.end('Method Not Allowed');
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
else if (!hasAnyMethod && this.notFoundHandler) {
|
|
1185
|
-
const result = this.notFoundHandler(ctx);
|
|
1186
|
-
if (result && typeof result.then === 'function') {
|
|
1187
|
-
await result;
|
|
1188
|
-
}
|
|
1189
|
-
if (!res.writableEnded && !res.headersSent) {
|
|
1190
|
-
res.statusCode = 404;
|
|
1191
|
-
res.setHeader('content-type', CONTENT_TYPE.PLAIN);
|
|
1192
|
-
res.end('Not Found');
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
else if (hasAnyMethod) {
|
|
1196
|
-
if (!res.headersSent) {
|
|
1197
|
-
res.statusCode = 405;
|
|
1198
|
-
res.setHeader('content-type', CONTENT_TYPE.PLAIN);
|
|
1199
|
-
}
|
|
1200
|
-
res.end('Method Not Allowed');
|
|
1201
|
-
}
|
|
1202
|
-
else {
|
|
1203
|
-
if (!res.headersSent) {
|
|
1204
|
-
res.statusCode = 404;
|
|
1205
|
-
res.setHeader('content-type', CONTENT_TYPE.PLAIN);
|
|
1206
|
-
}
|
|
1207
|
-
res.end('Not Found');
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
handleRequest(req, res) {
|
|
1211
|
-
// ⚡ Optimized request handling with fast routing
|
|
1212
|
-
const rawUrl = req.url || '/';
|
|
1213
|
-
const method = (req.method || 'GET');
|
|
1214
|
-
// Single-pass pathname extraction
|
|
1215
|
-
let pathname;
|
|
1216
|
-
const qIndex = rawUrl.indexOf('?');
|
|
1217
|
-
pathname = qIndex === -1 ? rawUrl : rawUrl.substring(0, qIndex);
|
|
1218
|
-
// Fast route matching with O(1) static and optimized dynamic routes
|
|
1219
|
-
let match = this.router.match(method, pathname);
|
|
1220
|
-
// Request ID generation (from x-request-id header or generated)
|
|
1221
|
-
let requestId;
|
|
1222
|
-
const incomingRequestIdHeader = req.headers['x-request-id'];
|
|
1223
|
-
if (typeof incomingRequestIdHeader === 'string') {
|
|
1224
|
-
requestId = incomingRequestIdHeader;
|
|
1225
|
-
}
|
|
1226
|
-
else if (Array.isArray(incomingRequestIdHeader)) {
|
|
1227
|
-
requestId = incomingRequestIdHeader[0];
|
|
1228
|
-
}
|
|
1229
|
-
if (!requestId) {
|
|
1230
|
-
requestId = this.generateRequestId();
|
|
1231
|
-
}
|
|
1232
|
-
if (!res.headersSent && requestId) {
|
|
1233
|
-
res.setHeader('x-request-id', requestId);
|
|
1234
|
-
}
|
|
1235
|
-
const hasRoute = !!match;
|
|
1236
|
-
if (!match) {
|
|
1237
|
-
match = QHTTPX.EMPTY_MATCH;
|
|
1238
|
-
}
|
|
1239
|
-
// Optimization: Predictive Query Parsing
|
|
1240
|
-
// 1. If no query string exists, set empty query to avoid getter scan
|
|
1241
|
-
// 2. If query string exists AND route needs it, parse eagerly
|
|
1242
|
-
let query;
|
|
1243
|
-
if (qIndex === -1) {
|
|
1244
|
-
query = EMPTY_QUERY;
|
|
1245
|
-
}
|
|
1246
|
-
else if (match.metadata?.needsQuery) {
|
|
1247
|
-
query = (0, querystring_1.parse)(rawUrl.slice(qIndex + 1));
|
|
1248
|
-
}
|
|
1249
|
-
// Optimization: Predictive Cookie Parsing
|
|
1250
|
-
let cookies;
|
|
1251
|
-
if (match.metadata?.needsCookies) {
|
|
1252
|
-
cookies = (0, cookies_1.parseCookies)(req.headers.cookie);
|
|
1253
|
-
}
|
|
1254
|
-
// Optimization: Only calculate allowed methods if no route match found
|
|
1255
|
-
const allowedMethods = hasRoute ? [] : this.router.getAllowedMethods(pathname);
|
|
1256
|
-
if (!hasRoute && !res.headersSent) {
|
|
1257
|
-
const hasAnyMethod = allowedMethods.length > 0;
|
|
1258
|
-
res.statusCode = hasAnyMethod ? 405 : 404;
|
|
1259
|
-
}
|
|
1260
|
-
// Memory Checks (throttled to every 100 requests for performance)
|
|
1261
|
-
if (this.options.maxMemoryBytes !== undefined && ++this.requestCounter % 100 === 0) {
|
|
1262
|
-
const sampleRss = process.memoryUsage().rss;
|
|
1263
|
-
const overloadedByMemory = (0, resources_1.isResourceOverloaded)({ rssBytes: sampleRss }, { maxRssBytes: this.options.maxMemoryBytes });
|
|
1264
|
-
if (overloadedByMemory) {
|
|
1265
|
-
const overloaded = () => {
|
|
1266
|
-
if (res.writableEnded) {
|
|
1267
|
-
return;
|
|
1268
|
-
}
|
|
1269
|
-
if (!res.headersSent) {
|
|
1270
|
-
res.statusCode = 503;
|
|
1271
|
-
res.setHeader('content-type', CONTENT_TYPE.PLAIN);
|
|
1272
|
-
}
|
|
1273
|
-
res.end('Server overloaded');
|
|
1274
|
-
};
|
|
1275
|
-
overloaded();
|
|
1276
|
-
return;
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
else {
|
|
1280
|
-
++this.requestCounter;
|
|
1281
|
-
}
|
|
1282
|
-
if (method !== 'GET' && method !== 'HEAD') {
|
|
1283
|
-
body_parser_1.BodyParser.parse(req, {
|
|
1284
|
-
maxBodyBytes: this.options.maxBodyBytes,
|
|
1285
|
-
}).then((parsed) => {
|
|
1286
|
-
this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, parsed.body, parsed.files, cookies);
|
|
1287
|
-
}).catch((err) => {
|
|
1288
|
-
if (err instanceof Error && err.message === 'QHTTPX_INVALID_JSON') {
|
|
1289
|
-
if (!res.headersSent) {
|
|
1290
|
-
res.statusCode = 400;
|
|
1291
|
-
res.setHeader('content-type', CONTENT_TYPE.PLAIN);
|
|
1292
|
-
}
|
|
1293
|
-
res.end('Invalid JSON');
|
|
1294
|
-
return;
|
|
1295
|
-
}
|
|
1296
|
-
if (err instanceof Error && err.message === 'QHTTPX_BODY_TOO_LARGE') {
|
|
1297
|
-
if (!res.headersSent) {
|
|
1298
|
-
res.statusCode = 413;
|
|
1299
|
-
res.setHeader('content-type', CONTENT_TYPE.PLAIN);
|
|
1300
|
-
}
|
|
1301
|
-
res.end('Payload Too Large');
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
if (!res.headersSent) {
|
|
1305
|
-
res.statusCode = 500;
|
|
1306
|
-
res.setHeader('content-type', CONTENT_TYPE.PLAIN);
|
|
1307
|
-
}
|
|
1308
|
-
res.end('Internal Server Error');
|
|
1309
|
-
});
|
|
1310
|
-
return;
|
|
1311
|
-
}
|
|
1312
|
-
this.dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, undefined, undefined, cookies);
|
|
1313
|
-
}
|
|
1314
|
-
async dispatch(req, res, method, pathname, query, requestId, match, hasRoute, allowedMethods, body, files, cookies) {
|
|
1315
|
-
const ctx = this.acquireContext(req, res, pathname, match.params, query, requestId, body, files, cookies);
|
|
1316
|
-
// Balanced Mode Logic
|
|
1317
|
-
const overloaded = () => {
|
|
1318
|
-
if (res.writableEnded) {
|
|
1319
|
-
return;
|
|
1320
|
-
}
|
|
1321
|
-
if (!res.headersSent) {
|
|
1322
|
-
res.statusCode = 503;
|
|
1323
|
-
res.setHeader('content-type', CONTENT_TYPE.PLAIN);
|
|
1324
|
-
}
|
|
1325
|
-
res.end('Server overloaded');
|
|
1326
|
-
};
|
|
1327
|
-
let timedOut = false;
|
|
1328
|
-
const onTimeout = () => {
|
|
1329
|
-
timedOut = true;
|
|
1330
|
-
if (res.writableEnded) {
|
|
1331
|
-
return;
|
|
1332
|
-
}
|
|
1333
|
-
if (!res.headersSent) {
|
|
1334
|
-
res.statusCode = 504;
|
|
1335
|
-
res.setHeader('content-type', CONTENT_TYPE.PLAIN);
|
|
1336
|
-
}
|
|
1337
|
-
res.end('Request timed out');
|
|
1338
|
-
};
|
|
1339
|
-
let start = 0;
|
|
1340
|
-
const metricsEnabled = this._metrics.isEnabled;
|
|
1341
|
-
if (metricsEnabled || this.tracer) {
|
|
1342
|
-
start = timer_1.globalTimer.preciseNow();
|
|
1343
|
-
ctx.requestStart = start;
|
|
1344
|
-
if (metricsEnabled) {
|
|
1345
|
-
this._metrics.onRequestStart();
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
if (this.tracer) {
|
|
1349
|
-
const event = {
|
|
1350
|
-
type: 'request_start',
|
|
1351
|
-
method,
|
|
1352
|
-
path: pathname,
|
|
1353
|
-
requestId,
|
|
1354
|
-
};
|
|
1355
|
-
const result = this.tracer(event);
|
|
1356
|
-
if (result && typeof result.then === 'function') {
|
|
1357
|
-
void result;
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
const finish = () => {
|
|
1361
|
-
let duration = 0;
|
|
1362
|
-
if (metricsEnabled || this.tracer) {
|
|
1363
|
-
duration = timer_1.globalTimer.preciseNow() - start;
|
|
1364
|
-
if (metricsEnabled) {
|
|
1365
|
-
this._metrics.onRequestEnd(duration, res.statusCode);
|
|
1366
|
-
if (timedOut) {
|
|
1367
|
-
this._metrics.onTimeout();
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
if (this.tracer) {
|
|
1372
|
-
const event = {
|
|
1373
|
-
type: 'request_end',
|
|
1374
|
-
method,
|
|
1375
|
-
path: pathname,
|
|
1376
|
-
statusCode: res.statusCode,
|
|
1377
|
-
durationMs: duration,
|
|
1378
|
-
requestId,
|
|
1379
|
-
};
|
|
1380
|
-
const result = this.tracer(event);
|
|
1381
|
-
if (result && typeof result.then === 'function') {
|
|
1382
|
-
void result;
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
this.releaseContext(ctx);
|
|
1386
|
-
};
|
|
1387
|
-
const handle = async () => {
|
|
1388
|
-
const handler = match.handler;
|
|
1389
|
-
try {
|
|
1390
|
-
if (hasRoute) {
|
|
1391
|
-
const result = handler(ctx);
|
|
1392
|
-
if (result && typeof result.then === 'function') {
|
|
1393
|
-
await result;
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
else if (this.pipelineRunner) {
|
|
1397
|
-
await this.pipelineRunner(ctx, handler);
|
|
1398
|
-
}
|
|
1399
|
-
if (!res.writableEnded) {
|
|
1400
|
-
if (!hasRoute) {
|
|
1401
|
-
await this.handleNoMatch(ctx, allowedMethods);
|
|
1402
|
-
}
|
|
1403
|
-
else if (!ctx.disableAutoEnd) {
|
|
1404
|
-
res.end();
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
catch (err) {
|
|
1409
|
-
await this.handleError(err, ctx);
|
|
1410
|
-
}
|
|
1411
|
-
};
|
|
1412
|
-
// Use scheduler for request handling
|
|
1413
|
-
this.scheduler.run(handle, {
|
|
1414
|
-
priority: match.priority,
|
|
1415
|
-
onOverloaded: overloaded,
|
|
1416
|
-
timeoutMs: this.options.requestTimeoutMs,
|
|
1417
|
-
onTimeout,
|
|
1418
|
-
}).then(finish).catch((err) => {
|
|
1419
|
-
console.error('Scheduler error:', err);
|
|
1420
|
-
finish();
|
|
1421
|
-
});
|
|
1422
|
-
}
|
|
1423
|
-
async handleUpgrade(req, socket, head) {
|
|
1424
|
-
await this.wsManager.handleUpgrade(req, socket, head);
|
|
1425
|
-
}
|
|
1426
|
-
async runLifecycleHooks(hooks) {
|
|
1427
|
-
if (hooks.length === 0) {
|
|
1428
|
-
return;
|
|
1429
|
-
}
|
|
1430
|
-
for (const hook of hooks) {
|
|
1431
|
-
try {
|
|
1432
|
-
const result = hook();
|
|
1433
|
-
if (result && typeof result.then === 'function') {
|
|
1434
|
-
await result;
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
catch {
|
|
1438
|
-
// Ignore hook errors to avoid impacting core server flow
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
handleError(err, ctx) {
|
|
1443
|
-
const res = ctx.res;
|
|
1444
|
-
if (res.writableEnded) {
|
|
1445
|
-
return;
|
|
1446
|
-
}
|
|
1447
|
-
if (this.errorHandler) {
|
|
1448
|
-
try {
|
|
1449
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1450
|
-
const errorContext = ctx;
|
|
1451
|
-
errorContext.error = err;
|
|
1452
|
-
const result = this.errorHandler(errorContext);
|
|
1453
|
-
if (result && typeof result.then === 'function') {
|
|
1454
|
-
return result.then(() => {
|
|
1455
|
-
// Ensure response is sent if handler didn't
|
|
1456
|
-
});
|
|
1457
|
-
}
|
|
1458
|
-
if (res.writableEnded) {
|
|
1459
|
-
return;
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
catch (handlerErr) {
|
|
1463
|
-
// Fall through to default error handling below
|
|
1464
|
-
console.error('Error in error handler:', handlerErr);
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
if (res.writableEnded) {
|
|
1468
|
-
return;
|
|
1469
|
-
}
|
|
1470
|
-
if (err instanceof types_1.HttpError) {
|
|
1471
|
-
if (!res.headersSent) {
|
|
1472
|
-
res.statusCode = err.status;
|
|
1473
|
-
res.setHeader('content-type', CONTENT_TYPE.JSON);
|
|
1474
|
-
}
|
|
1475
|
-
const payload = {
|
|
1476
|
-
error: {
|
|
1477
|
-
message: err.message,
|
|
1478
|
-
code: err.code ?? 'HTTP_ERROR',
|
|
1479
|
-
details: err.details,
|
|
1480
|
-
},
|
|
1481
|
-
};
|
|
1482
|
-
const serializer = this.options.jsonSerializer;
|
|
1483
|
-
const body = serializer !== undefined ? serializer(payload) : JSON.stringify(payload);
|
|
1484
|
-
res.end(body);
|
|
1485
|
-
return;
|
|
1486
|
-
}
|
|
1487
|
-
if (!res.headersSent) {
|
|
1488
|
-
res.statusCode = 500;
|
|
1489
|
-
res.setHeader('content-type', CONTENT_TYPE.PLAIN);
|
|
1490
|
-
}
|
|
1491
|
-
res.end('Internal Server Error');
|
|
1492
|
-
}
|
|
1493
|
-
generateRequestId() {
|
|
1494
|
-
const now = timer_1.globalTimer.now();
|
|
1495
|
-
if (now !== this.lastDateNow) {
|
|
1496
|
-
this.lastDateNow = now;
|
|
1497
|
-
this.lastDateString = now.toString(36);
|
|
1498
|
-
}
|
|
1499
|
-
const id = this.nextRequestId;
|
|
1500
|
-
this.nextRequestId += 1;
|
|
1501
|
-
return `${this.lastDateString}-${id.toString(36)}`;
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
exports.QHTTPX = QHTTPX;
|
|
1505
|
-
QHTTPX.EMPTY_PARAMS = Object.freeze({});
|
|
1506
|
-
QHTTPX.EMPTY_MATCH = Object.freeze({
|
|
1507
|
-
handler: () => { },
|
|
1508
|
-
params: QHTTPX.EMPTY_PARAMS,
|
|
1509
|
-
priority: types_1.RoutePriority.STANDARD,
|
|
1510
|
-
metadata: { needsQuery: false, needsCookies: false, needsBody: false, pipeline: undefined },
|
|
1511
|
-
});
|