qhttpx 1.8.12 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +26 -4
- package/prebuilds/darwin-arm64/qhttpx.node +0 -0
- package/prebuilds/linux-x64/qhttpx.node +0 -0
- package/prebuilds/win32-x64/qhttpx.node +0 -0
- package/src/native/index.ts +104 -24
- package/src/native/picohttpparser.h +5 -0
- package/src/native/server.cc +2 -0
- package/dist/examples/api-server.d.ts +0 -1
- package/dist/examples/api-server.js +0 -77
- package/dist/examples/basic.d.ts +0 -1
- package/dist/examples/basic.js +0 -10
- package/dist/examples/compression.d.ts +0 -1
- package/dist/examples/compression.js +0 -17
- package/dist/examples/cors.d.ts +0 -1
- package/dist/examples/cors.js +0 -19
- package/dist/examples/errors.d.ts +0 -1
- package/dist/examples/errors.js +0 -25
- package/dist/examples/file-upload.d.ts +0 -1
- package/dist/examples/file-upload.js +0 -24
- 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 -23
- package/dist/examples/websockets.d.ts +0 -1
- package/dist/examples/websockets.js +0 -20
- package/dist/package.json +0 -101
- package/dist/src/benchmarks/quantam-users.d.ts +0 -1
- package/dist/src/benchmarks/quantam-users.js +0 -56
- package/dist/src/benchmarks/simple-json.d.ts +0 -1
- package/dist/src/benchmarks/simple-json.js +0 -60
- package/dist/src/benchmarks/ultra-mode.d.ts +0 -1
- package/dist/src/benchmarks/ultra-mode.js +0 -94
- package/dist/src/cli/index.d.ts +0 -2
- 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/errors.d.ts +0 -34
- package/dist/src/core/errors.js +0 -70
- package/dist/src/core/fusion.d.ts +0 -14
- package/dist/src/core/fusion.js +0 -183
- 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 -45
- package/dist/src/core/metrics.js +0 -111
- package/dist/src/core/native-adapter.d.ts +0 -11
- package/dist/src/core/native-adapter.js +0 -211
- 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 -34
- package/dist/src/core/scheduler.js +0 -85
- package/dist/src/core/scope.d.ts +0 -26
- package/dist/src/core/scope.js +0 -68
- package/dist/src/core/serializer.d.ts +0 -10
- package/dist/src/core/serializer.js +0 -44
- package/dist/src/core/server.d.ts +0 -138
- package/dist/src/core/server.js +0 -1082
- 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/types.d.ts +0 -173
- 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 -50
- package/dist/src/index.js +0 -91
- 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 -16
- 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 -21
- package/dist/src/middleware/security.js +0 -69
- package/dist/src/middleware/static.d.ts +0 -11
- package/dist/src/middleware/static.js +0 -191
- package/dist/src/native/index.d.ts +0 -29
- package/dist/src/native/index.js +0 -64
- 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 -18
- package/dist/src/router/radix-tree.js +0 -131
- package/dist/src/router/router.d.ts +0 -34
- package/dist/src/router/router.js +0 -186
- 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 -12
- 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/native-adapter.test.d.ts +0 -1
- package/dist/tests/native-adapter.test.js +0 -71
- 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 -77
- 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
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RadixTree = void 0;
|
|
4
|
-
class Node {
|
|
5
|
-
constructor() {
|
|
6
|
-
// Map segment -> Node
|
|
7
|
-
this.children = new Map();
|
|
8
|
-
// Parameter child node (only one allowed per level for simplicity)
|
|
9
|
-
this.paramChild = null;
|
|
10
|
-
this.paramName = null;
|
|
11
|
-
// Data if this node is a route end
|
|
12
|
-
this.data = null;
|
|
13
|
-
// Optimization: Pre-allocated match result for static matches
|
|
14
|
-
this.staticMatch = null;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
class RadixTree {
|
|
18
|
-
constructor() {
|
|
19
|
-
this.root = new Node();
|
|
20
|
-
}
|
|
21
|
-
insert(segments, handler, priority) {
|
|
22
|
-
let node = this.root;
|
|
23
|
-
for (const segment of segments) {
|
|
24
|
-
if (segment.startsWith(':')) {
|
|
25
|
-
const paramName = segment.slice(1);
|
|
26
|
-
if (!node.paramChild) {
|
|
27
|
-
node.paramChild = new Node();
|
|
28
|
-
node.paramChild.paramName = paramName;
|
|
29
|
-
}
|
|
30
|
-
else if (node.paramChild.paramName !== paramName) {
|
|
31
|
-
throw new Error(`Cannot have two different parameter names at the same level: "${node.paramChild.paramName}" and "${paramName}"`);
|
|
32
|
-
}
|
|
33
|
-
node = node.paramChild;
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
if (!node.children.has(segment)) {
|
|
37
|
-
node.children.set(segment, new Node());
|
|
38
|
-
}
|
|
39
|
-
node = node.children.get(segment);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
node.data = { handler, priority };
|
|
43
|
-
node.staticMatch = { handler, priority, params: {} };
|
|
44
|
-
}
|
|
45
|
-
lookup(segments) {
|
|
46
|
-
return this.find(this.root, segments, 0);
|
|
47
|
-
}
|
|
48
|
-
lookupPath(path) {
|
|
49
|
-
if (path === '/' || path === '') {
|
|
50
|
-
if (this.root.staticMatch) {
|
|
51
|
-
return this.root.staticMatch;
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
const start = path.charCodeAt(0) === 47 ? 1 : 0;
|
|
56
|
-
return this.findPath(this.root, path, start);
|
|
57
|
-
}
|
|
58
|
-
findPath(node, path, start) {
|
|
59
|
-
// Handle trailing slash or end of path
|
|
60
|
-
if (start >= path.length) {
|
|
61
|
-
if (node.staticMatch) {
|
|
62
|
-
return node.staticMatch;
|
|
63
|
-
}
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
let end = path.indexOf('/', start);
|
|
67
|
-
if (end === -1) {
|
|
68
|
-
end = path.length;
|
|
69
|
-
}
|
|
70
|
-
const segment = path.substring(start, end);
|
|
71
|
-
const nextStart = end + 1;
|
|
72
|
-
// Skip empty segments (e.g. //) to match normalize behavior
|
|
73
|
-
if (segment.length === 0) {
|
|
74
|
-
return this.findPath(node, path, nextStart);
|
|
75
|
-
}
|
|
76
|
-
// 1. Try exact match
|
|
77
|
-
const child = node.children.get(segment);
|
|
78
|
-
if (child) {
|
|
79
|
-
const result = this.findPath(child, path, nextStart);
|
|
80
|
-
if (result)
|
|
81
|
-
return result;
|
|
82
|
-
}
|
|
83
|
-
// 2. Try param match
|
|
84
|
-
if (node.paramChild) {
|
|
85
|
-
const result = this.findPath(node.paramChild, path, nextStart);
|
|
86
|
-
if (result) {
|
|
87
|
-
// Clone params to avoid modifying shared staticMatch
|
|
88
|
-
const newParams = { ...result.params };
|
|
89
|
-
newParams[node.paramChild.paramName] = segment;
|
|
90
|
-
return {
|
|
91
|
-
handler: result.handler,
|
|
92
|
-
priority: result.priority,
|
|
93
|
-
params: newParams
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
find(node, segments, index) {
|
|
100
|
-
if (index === segments.length) {
|
|
101
|
-
if (node.data) {
|
|
102
|
-
return {
|
|
103
|
-
handler: node.data.handler,
|
|
104
|
-
priority: node.data.priority,
|
|
105
|
-
params: {},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
const segment = segments[index];
|
|
111
|
-
// 1. Try exact match
|
|
112
|
-
const child = node.children.get(segment);
|
|
113
|
-
if (child) {
|
|
114
|
-
const result = this.find(child, segments, index + 1);
|
|
115
|
-
if (result) {
|
|
116
|
-
return result;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// 2. Try param match
|
|
120
|
-
if (node.paramChild) {
|
|
121
|
-
const result = this.find(node.paramChild, segments, index + 1);
|
|
122
|
-
if (result) {
|
|
123
|
-
// If match found down this path, add current param to it
|
|
124
|
-
result.params[node.paramChild.paramName] = segment;
|
|
125
|
-
return result;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
exports.RadixTree = RadixTree;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { HTTPMethod, QHTTPXHandler, RouteOptions, RoutePriority } from '../core/types';
|
|
2
|
-
import { RouteSchema } from '../validation/types';
|
|
3
|
-
type RouteDefinition = {
|
|
4
|
-
path: string;
|
|
5
|
-
segments: string[];
|
|
6
|
-
handler: QHTTPXHandler;
|
|
7
|
-
priority: RoutePriority;
|
|
8
|
-
schema?: RouteSchema | Record<string, unknown>;
|
|
9
|
-
};
|
|
10
|
-
export type RouteMatch = {
|
|
11
|
-
handler: QHTTPXHandler;
|
|
12
|
-
params: Record<string, string>;
|
|
13
|
-
priority: RoutePriority;
|
|
14
|
-
};
|
|
15
|
-
export declare class Router {
|
|
16
|
-
private readonly methodBuckets;
|
|
17
|
-
private readonly radixTrees;
|
|
18
|
-
private readonly staticRoutes;
|
|
19
|
-
private isFrozen;
|
|
20
|
-
register(method: HTTPMethod, path: string, handler: QHTTPXHandler, options?: RouteOptions & {
|
|
21
|
-
schema?: RouteSchema | Record<string, unknown>;
|
|
22
|
-
}): void;
|
|
23
|
-
getRoutes(): Map<HTTPMethod, RouteDefinition[]>;
|
|
24
|
-
match(method: HTTPMethod, path: string): RouteMatch | undefined;
|
|
25
|
-
getAllowedMethods(path: string): HTTPMethod[];
|
|
26
|
-
/**
|
|
27
|
-
* Freeze the router after server starts.
|
|
28
|
-
* Prevents further route registration and builds derived structures for optimized matching.
|
|
29
|
-
*/
|
|
30
|
-
freeze(): void;
|
|
31
|
-
isFrozenRouter(): boolean;
|
|
32
|
-
private normalize;
|
|
33
|
-
}
|
|
34
|
-
export {};
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Router = void 0;
|
|
4
|
-
const radix_tree_1 = require("./radix-tree");
|
|
5
|
-
const types_1 = require("../core/types");
|
|
6
|
-
class Router {
|
|
7
|
-
constructor() {
|
|
8
|
-
// Per-method route buckets
|
|
9
|
-
this.methodBuckets = new Map([
|
|
10
|
-
['GET', []],
|
|
11
|
-
['POST', []],
|
|
12
|
-
['PUT', []],
|
|
13
|
-
['DELETE', []],
|
|
14
|
-
['PATCH', []],
|
|
15
|
-
['HEAD', []],
|
|
16
|
-
['OPTIONS', []],
|
|
17
|
-
]);
|
|
18
|
-
// Derived structures (built at freeze time)
|
|
19
|
-
this.radixTrees = new Map();
|
|
20
|
-
// Fast lookup for static routes (no params)
|
|
21
|
-
this.staticRoutes = new Map([
|
|
22
|
-
['GET', new Map()],
|
|
23
|
-
['POST', new Map()],
|
|
24
|
-
['PUT', new Map()],
|
|
25
|
-
['DELETE', new Map()],
|
|
26
|
-
['PATCH', new Map()],
|
|
27
|
-
['HEAD', new Map()],
|
|
28
|
-
['OPTIONS', new Map()],
|
|
29
|
-
]);
|
|
30
|
-
// Freeze state
|
|
31
|
-
this.isFrozen = false;
|
|
32
|
-
}
|
|
33
|
-
register(method, path, handler, options) {
|
|
34
|
-
if (this.isFrozen) {
|
|
35
|
-
console.warn(`Router is frozen. Late route registration (${method} ${path}) may not be optimized.`);
|
|
36
|
-
}
|
|
37
|
-
const segments = this.normalize(path);
|
|
38
|
-
const bucket = this.methodBuckets.get(method);
|
|
39
|
-
if (bucket) {
|
|
40
|
-
bucket.push({
|
|
41
|
-
path,
|
|
42
|
-
segments,
|
|
43
|
-
handler,
|
|
44
|
-
priority: options?.priority ?? types_1.RoutePriority.STANDARD,
|
|
45
|
-
schema: options?.schema,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
getRoutes() {
|
|
50
|
-
return this.methodBuckets;
|
|
51
|
-
}
|
|
52
|
-
match(method, path) {
|
|
53
|
-
// Fast path for frozen router
|
|
54
|
-
if (this.isFrozen) {
|
|
55
|
-
// 1. Try static match (O(1))
|
|
56
|
-
const staticMap = this.staticRoutes.get(method);
|
|
57
|
-
if (staticMap) {
|
|
58
|
-
const match = staticMap.get(path);
|
|
59
|
-
if (match) {
|
|
60
|
-
return match;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
// 2. Try radix tree match
|
|
64
|
-
const tree = this.radixTrees.get(method);
|
|
65
|
-
if (tree) {
|
|
66
|
-
const match = tree.lookupPath(path);
|
|
67
|
-
if (match) {
|
|
68
|
-
return match;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
// Slow path for unfrozen router (legacy behavior)
|
|
74
|
-
const segments = this.normalize(path);
|
|
75
|
-
const bucket = this.methodBuckets.get(method);
|
|
76
|
-
if (!bucket || bucket.length === 0) {
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
// Try to match against routes
|
|
80
|
-
for (const route of bucket) {
|
|
81
|
-
if (route.segments.length !== segments.length) {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
const params = {};
|
|
85
|
-
let matched = true;
|
|
86
|
-
for (let i = 0; i < route.segments.length; i += 1) {
|
|
87
|
-
const pattern = route.segments[i];
|
|
88
|
-
const value = segments[i];
|
|
89
|
-
if (pattern.startsWith(':')) {
|
|
90
|
-
const key = pattern.slice(1);
|
|
91
|
-
params[key] = value;
|
|
92
|
-
}
|
|
93
|
-
else if (pattern !== value) {
|
|
94
|
-
matched = false;
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (matched) {
|
|
99
|
-
return {
|
|
100
|
-
handler: route.handler,
|
|
101
|
-
params,
|
|
102
|
-
priority: route.priority,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
getAllowedMethods(path) {
|
|
109
|
-
const segments = this.normalize(path);
|
|
110
|
-
const methods = [];
|
|
111
|
-
for (const [method, routes] of this.methodBuckets.entries()) {
|
|
112
|
-
for (const route of routes) {
|
|
113
|
-
if (route.segments.length !== segments.length) {
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
let matched = true;
|
|
117
|
-
for (let i = 0; i < route.segments.length; i += 1) {
|
|
118
|
-
const pattern = route.segments[i];
|
|
119
|
-
const value = segments[i];
|
|
120
|
-
if (pattern.startsWith(':')) {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (pattern !== value) {
|
|
124
|
-
matched = false;
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
if (matched) {
|
|
129
|
-
methods.push(method);
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return methods;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Freeze the router after server starts.
|
|
138
|
-
* Prevents further route registration and builds derived structures for optimized matching.
|
|
139
|
-
*/
|
|
140
|
-
freeze() {
|
|
141
|
-
if (this.isFrozen) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
this.isFrozen = true;
|
|
145
|
-
// Build derived structures for faster matching
|
|
146
|
-
for (const [method, routes] of this.methodBuckets.entries()) {
|
|
147
|
-
const tree = new radix_tree_1.RadixTree();
|
|
148
|
-
const staticMap = this.staticRoutes.get(method);
|
|
149
|
-
for (const route of routes) {
|
|
150
|
-
tree.insert(route.segments, route.handler, route.priority);
|
|
151
|
-
// Check if route is static (no params)
|
|
152
|
-
let isStatic = true;
|
|
153
|
-
for (const segment of route.segments) {
|
|
154
|
-
if (segment.startsWith(':')) {
|
|
155
|
-
isStatic = false;
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (isStatic) {
|
|
160
|
-
// Optimization: Pre-allocate static match result
|
|
161
|
-
// Note: we use route.path directly as key
|
|
162
|
-
staticMap.set(route.path, {
|
|
163
|
-
handler: route.handler,
|
|
164
|
-
params: {},
|
|
165
|
-
priority: route.priority,
|
|
166
|
-
});
|
|
167
|
-
// Also handle trailing slash or no trailing slash?
|
|
168
|
-
// For strict matching, we use exact path.
|
|
169
|
-
// QHTTPX seems to normalize, so /json/ and /json might be different?
|
|
170
|
-
// route.path comes from user.
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
this.radixTrees.set(method, tree);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
isFrozenRouter() {
|
|
177
|
-
return this.isFrozen;
|
|
178
|
-
}
|
|
179
|
-
normalize(path) {
|
|
180
|
-
if (!path || path === '/') {
|
|
181
|
-
return [];
|
|
182
|
-
}
|
|
183
|
-
return path.split('/').filter((segment) => segment.length > 0);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
exports.Router = Router;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { QHTTPX } from '../core/server';
|
|
2
|
-
import { HTTPMethod } from '../core/types';
|
|
3
|
-
export declare class TestClient {
|
|
4
|
-
private app;
|
|
5
|
-
private server;
|
|
6
|
-
private baseURL;
|
|
7
|
-
constructor(app: QHTTPX);
|
|
8
|
-
/**
|
|
9
|
-
* Starts the server on a random port.
|
|
10
|
-
* Automatically called by request methods if not started.
|
|
11
|
-
*/
|
|
12
|
-
start(): Promise<void>;
|
|
13
|
-
stop(): Promise<void>;
|
|
14
|
-
request(method: HTTPMethod, path: string, options?: {
|
|
15
|
-
headers?: Record<string, string>;
|
|
16
|
-
body?: any;
|
|
17
|
-
query?: Record<string, string | number | boolean>;
|
|
18
|
-
}): Promise<Response>;
|
|
19
|
-
get(path: string, options?: Omit<Parameters<TestClient['request']>[2], 'body'>): Promise<Response>;
|
|
20
|
-
post(path: string, body?: any, options?: Omit<Parameters<TestClient['request']>[2], 'body'>): Promise<Response>;
|
|
21
|
-
put(path: string, body?: any, options?: Omit<Parameters<TestClient['request']>[2], 'body'>): Promise<Response>;
|
|
22
|
-
delete(path: string, options?: Omit<Parameters<TestClient['request']>[2], 'body'>): Promise<Response>;
|
|
23
|
-
patch(path: string, body?: any, options?: Omit<Parameters<TestClient['request']>[2], 'body'>): Promise<Response>;
|
|
24
|
-
}
|
|
25
|
-
export declare function createTestClient(app: QHTTPX): TestClient;
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TestClient = void 0;
|
|
4
|
-
exports.createTestClient = createTestClient;
|
|
5
|
-
class TestClient {
|
|
6
|
-
constructor(app) {
|
|
7
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
-
this.server = null;
|
|
9
|
-
this.baseURL = '';
|
|
10
|
-
this.app = app;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Starts the server on a random port.
|
|
14
|
-
* Automatically called by request methods if not started.
|
|
15
|
-
*/
|
|
16
|
-
async start() {
|
|
17
|
-
if (this.server)
|
|
18
|
-
return;
|
|
19
|
-
const { port } = await this.app.listen(0);
|
|
20
|
-
this.server = this.app.serverInstance;
|
|
21
|
-
this.baseURL = `http://127.0.0.1:${port}`;
|
|
22
|
-
}
|
|
23
|
-
async stop() {
|
|
24
|
-
if (this.server) {
|
|
25
|
-
await this.app.close();
|
|
26
|
-
this.server = null;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
async request(method, path, options = {}) {
|
|
30
|
-
if (!this.server) {
|
|
31
|
-
await this.start();
|
|
32
|
-
}
|
|
33
|
-
const url = new URL(path, this.baseURL);
|
|
34
|
-
if (options.query) {
|
|
35
|
-
Object.entries(options.query).forEach(([k, v]) => {
|
|
36
|
-
url.searchParams.append(k, String(v));
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
const headers = options.headers || {};
|
|
40
|
-
let bodyPayload;
|
|
41
|
-
if (options.body) {
|
|
42
|
-
if (typeof options.body === 'object' &&
|
|
43
|
-
!(options.body instanceof Uint8Array) &&
|
|
44
|
-
!(options.body instanceof ArrayBuffer) &&
|
|
45
|
-
!(options.body instanceof FormData) &&
|
|
46
|
-
!(options.body instanceof URLSearchParams)) {
|
|
47
|
-
bodyPayload = JSON.stringify(options.body);
|
|
48
|
-
if (!headers['content-type']) {
|
|
49
|
-
headers['content-type'] = 'application/json';
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
bodyPayload = options.body;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return fetch(url, {
|
|
57
|
-
method,
|
|
58
|
-
headers,
|
|
59
|
-
body: bodyPayload,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
get(path, options) {
|
|
63
|
-
return this.request('GET', path, options);
|
|
64
|
-
}
|
|
65
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
-
post(path, body, options) {
|
|
67
|
-
return this.request('POST', path, { ...options, body });
|
|
68
|
-
}
|
|
69
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
-
put(path, body, options) {
|
|
71
|
-
return this.request('PUT', path, { ...options, body });
|
|
72
|
-
}
|
|
73
|
-
delete(path, options) {
|
|
74
|
-
return this.request('DELETE', path, options);
|
|
75
|
-
}
|
|
76
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
|
-
patch(path, body, options) {
|
|
78
|
-
return this.request('PATCH', path, { ...options, body });
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
exports.TestClient = TestClient;
|
|
82
|
-
function createTestClient(app) {
|
|
83
|
-
return new TestClient(app);
|
|
84
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parseCookies = parseCookies;
|
|
4
|
-
exports.serializeCookie = serializeCookie;
|
|
5
|
-
function parseCookies(header) {
|
|
6
|
-
const list = {};
|
|
7
|
-
if (!header) {
|
|
8
|
-
return list;
|
|
9
|
-
}
|
|
10
|
-
header.split(';').forEach((cookie) => {
|
|
11
|
-
const parts = cookie.split('=');
|
|
12
|
-
const name = parts.shift()?.trim();
|
|
13
|
-
if (name) {
|
|
14
|
-
const value = parts.join('=');
|
|
15
|
-
list[name] = decodeURIComponent(value);
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
return list;
|
|
19
|
-
}
|
|
20
|
-
function serializeCookie(name, value, options = {}) {
|
|
21
|
-
let str = `${name}=${encodeURIComponent(value)}`;
|
|
22
|
-
if (options.maxAge) {
|
|
23
|
-
str += `; Max-Age=${Math.floor(options.maxAge)}`;
|
|
24
|
-
}
|
|
25
|
-
if (options.domain) {
|
|
26
|
-
str += `; Domain=${options.domain}`;
|
|
27
|
-
}
|
|
28
|
-
if (options.path) {
|
|
29
|
-
str += `; Path=${options.path}`;
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
str += '; Path=/';
|
|
33
|
-
}
|
|
34
|
-
if (options.expires) {
|
|
35
|
-
str += `; Expires=${options.expires.toUTCString()}`;
|
|
36
|
-
}
|
|
37
|
-
if (options.httpOnly) {
|
|
38
|
-
str += '; HttpOnly';
|
|
39
|
-
}
|
|
40
|
-
if (options.secure) {
|
|
41
|
-
str += '; Secure';
|
|
42
|
-
}
|
|
43
|
-
if (options.sameSite) {
|
|
44
|
-
switch (options.sameSite) {
|
|
45
|
-
case 'lax':
|
|
46
|
-
str += '; SameSite=Lax';
|
|
47
|
-
break;
|
|
48
|
-
case 'strict':
|
|
49
|
-
str += '; SameSite=Strict';
|
|
50
|
-
break;
|
|
51
|
-
case 'none':
|
|
52
|
-
str += '; SameSite=None';
|
|
53
|
-
break;
|
|
54
|
-
default:
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return str;
|
|
59
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { QHTTPXContext, QHTTPXMiddleware } from '../core/types';
|
|
2
|
-
export type LogEntry = {
|
|
3
|
-
method: string;
|
|
4
|
-
path: string;
|
|
5
|
-
status: number;
|
|
6
|
-
durationMs: number;
|
|
7
|
-
requestId?: string;
|
|
8
|
-
};
|
|
9
|
-
export type LoggerOptions = {
|
|
10
|
-
sink?: (entry: LogEntry, ctx: QHTTPXContext) => void;
|
|
11
|
-
};
|
|
12
|
-
export declare function createLoggerMiddleware(options?: LoggerOptions): QHTTPXMiddleware;
|
package/dist/src/utils/logger.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createLoggerMiddleware = createLoggerMiddleware;
|
|
4
|
-
function createLoggerMiddleware(options = {}) {
|
|
5
|
-
const sink = options.sink ??
|
|
6
|
-
((entry) => {
|
|
7
|
-
const status = entry.status;
|
|
8
|
-
let color = '\x1b[37m';
|
|
9
|
-
if (status >= 200 && status < 300) {
|
|
10
|
-
color = '\x1b[32m';
|
|
11
|
-
}
|
|
12
|
-
else if (status === 404) {
|
|
13
|
-
color = '\x1b[33m';
|
|
14
|
-
}
|
|
15
|
-
else if (status >= 500) {
|
|
16
|
-
color = '\x1b[34m';
|
|
17
|
-
}
|
|
18
|
-
else if (status >= 400) {
|
|
19
|
-
color = '\x1b[35m';
|
|
20
|
-
}
|
|
21
|
-
const reset = '\x1b[0m';
|
|
22
|
-
const prefix = entry.requestId ? `${entry.requestId} ` : '';
|
|
23
|
-
const line = `${prefix}${entry.method} ${entry.path} ${status} ${entry.durationMs}ms`;
|
|
24
|
-
console.log(`${color}${line}${reset}`);
|
|
25
|
-
});
|
|
26
|
-
return async (ctx, next) => {
|
|
27
|
-
const start = ctx.requestStart ?? Date.now();
|
|
28
|
-
try {
|
|
29
|
-
await next();
|
|
30
|
-
}
|
|
31
|
-
finally {
|
|
32
|
-
const durationMs = Math.max(1, Date.now() - start);
|
|
33
|
-
const method = ctx.req.method || 'GET';
|
|
34
|
-
const path = ctx.url.pathname;
|
|
35
|
-
const status = ctx.res.statusCode || 200;
|
|
36
|
-
sink({
|
|
37
|
-
method,
|
|
38
|
-
path,
|
|
39
|
-
status,
|
|
40
|
-
durationMs,
|
|
41
|
-
requestId: ctx.requestId,
|
|
42
|
-
}, ctx);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.attachSignalHandlers = attachSignalHandlers;
|
|
4
|
-
function attachSignalHandlers(app, options = {}) {
|
|
5
|
-
const signals = options.signals ?? ['SIGINT', 'SIGTERM'];
|
|
6
|
-
const timeoutMs = options.timeoutMs ?? 10000;
|
|
7
|
-
let shuttingDown = false;
|
|
8
|
-
const handler = async (signal) => {
|
|
9
|
-
if (shuttingDown) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
shuttingDown = true;
|
|
13
|
-
console.log(`Received ${signal}, shutting down...`);
|
|
14
|
-
const timer = setTimeout(() => {
|
|
15
|
-
console.error('Shutdown timed out, forcing exit. (some connections might be lost)');
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}, timeoutMs);
|
|
18
|
-
try {
|
|
19
|
-
await app.shutdown();
|
|
20
|
-
clearTimeout(timer);
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
23
|
-
catch (err) {
|
|
24
|
-
console.error('Error during shutdown:', err);
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
for (const signal of signals) {
|
|
29
|
-
process.on(signal, handler);
|
|
30
|
-
}
|
|
31
|
-
}
|
package/dist/src/utils/sse.d.ts
DELETED
package/dist/src/utils/sse.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createSSE = createSSE;
|
|
4
|
-
function createSSE(ctx) {
|
|
5
|
-
const res = ctx.res;
|
|
6
|
-
// Disable auto-end so we can keep the connection open
|
|
7
|
-
ctx.disableAutoEnd = true;
|
|
8
|
-
// Only write headers if not already sent
|
|
9
|
-
if (!res.headersSent) {
|
|
10
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
11
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
12
|
-
res.setHeader('Connection', 'keep-alive');
|
|
13
|
-
res.setHeader('X-Accel-Buffering', 'no'); // For Nginx
|
|
14
|
-
// Send initial ping/comment to flush headers and establish connection
|
|
15
|
-
res.write(': connected\n\n');
|
|
16
|
-
}
|
|
17
|
-
const send = (data, event, id) => {
|
|
18
|
-
if (id)
|
|
19
|
-
res.write(`id: ${id}\n`);
|
|
20
|
-
if (event)
|
|
21
|
-
res.write(`event: ${event}\n`);
|
|
22
|
-
const payload = typeof data === 'string' ? data : JSON.stringify(data);
|
|
23
|
-
res.write(`data: ${payload}\n\n`);
|
|
24
|
-
};
|
|
25
|
-
const close = () => {
|
|
26
|
-
res.end();
|
|
27
|
-
};
|
|
28
|
-
ctx.req.on('close', () => {
|
|
29
|
-
// console.log('SSE client disconnected');
|
|
30
|
-
});
|
|
31
|
-
return { send, close };
|
|
32
|
-
}
|