qhttpx 1.9.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +17 -12
- package/dist/examples/api-server.js +38 -35
- package/dist/examples/basic.js +3 -4
- package/dist/examples/compression.js +6 -8
- package/dist/examples/cors.js +5 -6
- package/dist/examples/errors.js +12 -11
- package/dist/examples/file-upload.js +4 -6
- package/dist/examples/fusion.js +6 -6
- package/dist/examples/rate-limiting.js +10 -10
- package/dist/examples/validation.js +5 -6
- package/dist/examples/websockets.js +3 -4
- package/dist/package.json +3 -8
- package/dist/src/benchmarks/quantam-users.js +2 -2
- package/dist/src/benchmarks/quick-bench.js +57 -0
- package/dist/src/benchmarks/simple-json.js +133 -22
- package/dist/src/benchmarks/ultra-mode.js +8 -38
- package/dist/src/core/context-pool.d.ts +12 -0
- package/dist/src/core/context-pool.js +34 -0
- package/dist/src/core/fusion.js +0 -2
- package/dist/src/core/metrics.d.ts +1 -0
- package/dist/src/core/metrics.js +3 -0
- package/dist/src/core/scheduler.d.ts +4 -0
- package/dist/src/core/scheduler.js +75 -34
- package/dist/src/core/scope.d.ts +23 -8
- package/dist/src/core/scope.js +53 -14
- package/dist/src/core/serializer.d.ts +1 -1
- package/dist/src/core/serializer.js +45 -7
- package/dist/src/core/server.d.ts +51 -10
- package/dist/src/core/server.js +695 -259
- package/dist/src/core/timer.d.ts +11 -0
- package/dist/src/core/timer.js +29 -0
- package/dist/src/core/types.d.ts +41 -13
- package/dist/src/core/types.js +6 -6
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.js +19 -20
- package/dist/src/middleware/security.js +6 -1
- package/dist/src/router/radix-tree.d.ts +5 -2
- package/dist/src/router/radix-tree.js +58 -14
- package/dist/src/router/router.d.ts +5 -2
- package/dist/src/router/router.js +80 -63
- package/dist/tests/fusion.test.js +4 -4
- package/dist/tests/rate-limit.test.js +2 -2
- package/dist/tests/schema-routes.test.js +3 -1
- package/docs/AEGIS.md +18 -28
- package/docs/BENCHMARKS.md +8 -6
- package/docs/DATABASE.md +4 -4
- package/docs/MIDDLEWARE.md +3 -3
- package/docs/ROUTING.md +21 -13
- package/docs/VALIDATION.md +9 -31
- package/package.json +3 -8
- package/binding.gyp +0 -18
- package/dist/src/benchmarks/compare-frameworks.js +0 -119
- package/dist/src/benchmarks/compare.js +0 -288
- package/dist/src/buffer-pool.js +0 -70
- package/dist/src/config.js +0 -50
- package/dist/src/cookies.js +0 -59
- package/dist/src/core/native-adapter.d.ts +0 -11
- package/dist/src/core/native-adapter.js +0 -211
- package/dist/src/cors.js +0 -66
- package/dist/src/logger.js +0 -45
- package/dist/src/metrics.js +0 -111
- package/dist/src/native/index.d.ts +0 -32
- package/dist/src/native/index.js +0 -141
- package/dist/src/presets.js +0 -33
- package/dist/src/radix-router.js +0 -89
- package/dist/src/radix-tree.js +0 -81
- package/dist/src/resources.js +0 -25
- package/dist/src/router.js +0 -138
- package/dist/src/scheduler.js +0 -85
- package/dist/src/security.js +0 -69
- package/dist/src/server.js +0 -685
- package/dist/src/signals.js +0 -31
- package/dist/src/static.js +0 -107
- package/dist/src/stream.js +0 -71
- package/dist/src/tasks.js +0 -87
- package/dist/src/testing.js +0 -40
- package/dist/src/types.js +0 -19
- package/dist/src/utils/testing.js +0 -40
- package/dist/src/worker-queue.js +0 -73
- package/dist/tests/native-adapter.test.d.ts +0 -1
- package/dist/tests/native-adapter.test.js +0 -71
- 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/scripts/install-native.js +0 -26
- package/src/native/README.md +0 -31
- package/src/native/addon.cc +0 -8
- package/src/native/index.ts +0 -158
- package/src/native/picohttpparser.c +0 -608
- package/src/native/picohttpparser.h +0 -76
- package/src/native/server.cc +0 -264
- package/src/native/server.h +0 -30
- /package/dist/src/benchmarks/{compare.d.ts → quick-bench.d.ts} +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class RequestTimer {
|
|
2
|
+
private cachedTime;
|
|
3
|
+
private readonly perfStart;
|
|
4
|
+
private readonly updateInterval;
|
|
5
|
+
private lastUpdate;
|
|
6
|
+
private intervalId;
|
|
7
|
+
constructor();
|
|
8
|
+
now(): number;
|
|
9
|
+
preciseNow(): number;
|
|
10
|
+
}
|
|
11
|
+
export declare const globalTimer: RequestTimer;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.globalTimer = exports.RequestTimer = void 0;
|
|
4
|
+
class RequestTimer {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.cachedTime = Date.now();
|
|
7
|
+
this.perfStart = performance.now();
|
|
8
|
+
this.updateInterval = 10; // Update every 10ms
|
|
9
|
+
this.lastUpdate = this.perfStart;
|
|
10
|
+
// Auto-update timer
|
|
11
|
+
this.intervalId = setInterval(() => {
|
|
12
|
+
this.cachedTime = Date.now();
|
|
13
|
+
this.lastUpdate = performance.now();
|
|
14
|
+
}, this.updateInterval);
|
|
15
|
+
// Ensure the timer doesn't keep the process alive
|
|
16
|
+
this.intervalId.unref();
|
|
17
|
+
}
|
|
18
|
+
now() {
|
|
19
|
+
// Use cached time for most calls
|
|
20
|
+
return this.cachedTime;
|
|
21
|
+
}
|
|
22
|
+
preciseNow() {
|
|
23
|
+
// For metrics that need precision, calculate offset
|
|
24
|
+
const elapsed = performance.now() - this.lastUpdate;
|
|
25
|
+
return this.cachedTime + Math.floor(elapsed);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.RequestTimer = RequestTimer;
|
|
29
|
+
exports.globalTimer = new RequestTimer();
|
package/dist/src/core/types.d.ts
CHANGED
|
@@ -5,14 +5,24 @@ import type { DatabaseManager } from '../database/manager';
|
|
|
5
5
|
import type { RouteSchema, Validator } from '../validation/types';
|
|
6
6
|
import type { ViewEngine } from '../views/types';
|
|
7
7
|
import type { RequestFusionOptions } from './fusion';
|
|
8
|
+
export type { RequestFusionOptions };
|
|
8
9
|
export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
export type RoutePriority = 'critical' | 'standard' | 'best-effort';
|
|
11
|
+
export declare const RoutePriority: {
|
|
12
|
+
CRITICAL: RoutePriority;
|
|
13
|
+
STANDARD: RoutePriority;
|
|
14
|
+
BEST_EFFORT: RoutePriority;
|
|
15
|
+
};
|
|
16
|
+
export type RouteMetadata = {
|
|
17
|
+
needsQuery?: boolean;
|
|
18
|
+
needsCookies?: boolean;
|
|
19
|
+
needsBody?: boolean;
|
|
20
|
+
pipeline?: QHTTPXHandler;
|
|
21
|
+
};
|
|
14
22
|
export type RouteOptions = {
|
|
15
23
|
priority?: RoutePriority;
|
|
24
|
+
metadata?: RouteMetadata;
|
|
25
|
+
staticResponse?: any;
|
|
16
26
|
};
|
|
17
27
|
export declare class HttpError extends Error {
|
|
18
28
|
status: number;
|
|
@@ -39,7 +49,7 @@ export type QHTTPXFile = {
|
|
|
39
49
|
data: Buffer;
|
|
40
50
|
size: number;
|
|
41
51
|
};
|
|
42
|
-
export
|
|
52
|
+
export interface QHTTPXContext {
|
|
43
53
|
readonly req: IncomingMessage;
|
|
44
54
|
readonly res: ServerResponse;
|
|
45
55
|
readonly headers: IncomingHttpHeaders;
|
|
@@ -58,6 +68,7 @@ export type QHTTPXContext = {
|
|
|
58
68
|
serializer?: (value: unknown) => string;
|
|
59
69
|
readonly json: (body: unknown, status?: number) => void;
|
|
60
70
|
readonly send: (body: string | Buffer, status?: number) => void;
|
|
71
|
+
readonly httpError: (status: number, message?: string, details?: unknown) => HttpError;
|
|
61
72
|
readonly html: (html: string, status?: number) => void;
|
|
62
73
|
readonly redirect: (url: string, status?: number) => void;
|
|
63
74
|
readonly setCookie: (name: string, value: string, options?: CookieOptions) => void;
|
|
@@ -68,15 +79,26 @@ export type QHTTPXContext = {
|
|
|
68
79
|
disableAutoEnd?: boolean;
|
|
69
80
|
readonly path: string;
|
|
70
81
|
readonly next?: () => Promise<void>;
|
|
71
|
-
}
|
|
82
|
+
}
|
|
72
83
|
export type QHTTPXOpHandler = (params: any, ctx: QHTTPXContext) => any | Promise<any>;
|
|
73
|
-
export type QHTTPXHandler = (ctx: QHTTPXContext) =>
|
|
84
|
+
export type QHTTPXHandler<Result = any> = (ctx: QHTTPXContext) => Result | Promise<Result>;
|
|
74
85
|
export type QHTTPXRouteOptions = {
|
|
75
86
|
schema?: RouteSchema | Record<string, any>;
|
|
76
|
-
|
|
87
|
+
body?: any;
|
|
88
|
+
params?: any;
|
|
89
|
+
query?: any;
|
|
90
|
+
headers?: any;
|
|
91
|
+
response?: any;
|
|
92
|
+
handler?: QHTTPXHandler;
|
|
77
93
|
priority?: RoutePriority;
|
|
94
|
+
staticResponse?: any;
|
|
78
95
|
};
|
|
79
96
|
export type QHTTPXRouteConfig = Omit<QHTTPXRouteOptions, 'handler'>;
|
|
97
|
+
export interface QHTTPXRouteMethod {
|
|
98
|
+
(path: string, handler: QHTTPXHandler | QHTTPXRouteOptions): void;
|
|
99
|
+
(path: string, handler: QHTTPXHandler, config: QHTTPXRouteConfig): void;
|
|
100
|
+
(path: string, config: QHTTPXRouteConfig, handler: QHTTPXHandler): void;
|
|
101
|
+
}
|
|
80
102
|
export type QHTTPXMiddleware = (ctx: QHTTPXContext, next: () => Promise<void>) => void | Promise<void>;
|
|
81
103
|
export type QHTTPXErrorContext = QHTTPXContext & {
|
|
82
104
|
error: unknown;
|
|
@@ -103,7 +125,7 @@ export type QHTTPXTaskOptions = {
|
|
|
103
125
|
maxRetries?: number;
|
|
104
126
|
backoffMs?: number;
|
|
105
127
|
};
|
|
106
|
-
export type PerformanceMode = '
|
|
128
|
+
export type PerformanceMode = 'default';
|
|
107
129
|
export interface RateLimitStore {
|
|
108
130
|
increment(key: string, windowMs: number): Promise<{
|
|
109
131
|
total: number;
|
|
@@ -123,6 +145,11 @@ export interface RateLimitOptions {
|
|
|
123
145
|
store?: RateLimitStore;
|
|
124
146
|
trustProxy?: boolean;
|
|
125
147
|
}
|
|
148
|
+
export interface RateLimitConfig {
|
|
149
|
+
windowMs: number;
|
|
150
|
+
max: number;
|
|
151
|
+
}
|
|
152
|
+
export type RateLimitPreset = 'strict' | 'relaxed' | 'standard';
|
|
126
153
|
export type QHTTPXOptions = {
|
|
127
154
|
name?: string;
|
|
128
155
|
workers?: 'auto' | number;
|
|
@@ -173,8 +200,9 @@ export type SecurityHeadersOptions = {
|
|
|
173
200
|
strictTransportSecurity?: string | null;
|
|
174
201
|
};
|
|
175
202
|
export type SecureDefaultsOptions = {
|
|
176
|
-
cors?: CorsOptions;
|
|
203
|
+
cors?: CorsOptions | boolean;
|
|
177
204
|
securityHeaders?: SecurityHeadersOptions;
|
|
205
|
+
rateLimit?: RateLimitOptions;
|
|
178
206
|
};
|
|
179
207
|
export type CorsOrigin = string | string[] | ((origin: string | undefined) => string | null | undefined);
|
|
180
208
|
export type CorsOptions = {
|
|
@@ -189,8 +217,8 @@ export type CompressionOptions = {
|
|
|
189
217
|
threshold?: number;
|
|
190
218
|
level?: number;
|
|
191
219
|
};
|
|
192
|
-
|
|
193
|
-
options: Options) => void | Promise<void>;
|
|
220
|
+
import type { QHTTPXScope } from './scope';
|
|
221
|
+
export type QHTTPXPlugin<Options = any> = (app: QHTTPXScope, options: Options) => void | Promise<void>;
|
|
194
222
|
export type QHTTPXPluginOptions = {
|
|
195
223
|
prefix?: string;
|
|
196
224
|
[key: string]: any;
|
package/dist/src/core/types.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HttpError = exports.RoutePriority = void 0;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
})(RoutePriority || (exports.RoutePriority = RoutePriority = {}));
|
|
4
|
+
exports.RoutePriority = {
|
|
5
|
+
CRITICAL: 'critical',
|
|
6
|
+
STANDARD: 'standard',
|
|
7
|
+
BEST_EFFORT: 'best-effort',
|
|
8
|
+
};
|
|
10
9
|
class HttpError extends Error {
|
|
11
10
|
constructor(status, message, options = {}) {
|
|
12
11
|
super(message ?? 'HTTP Error');
|
|
@@ -17,3 +16,4 @@ class HttpError extends Error {
|
|
|
17
16
|
}
|
|
18
17
|
}
|
|
19
18
|
exports.HttpError = HttpError;
|
|
19
|
+
;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { QHTTPX } from './core/server';
|
|
2
|
-
import type { QHTTPXOptions } from './core/types';
|
|
3
|
-
import { NativeAdapter } from './core/native-adapter';
|
|
2
|
+
import type { QHTTPXOptions, QHTTPXPlugin } from './core/types';
|
|
4
3
|
export { QHTTPX } from './core/server';
|
|
4
|
+
export { QHTTPXScope } from './core/scope';
|
|
5
5
|
export * from './core/types';
|
|
6
6
|
export * from './core/errors';
|
|
7
7
|
export * from './middleware/cors';
|
|
@@ -31,9 +31,11 @@ export * from './validation/types';
|
|
|
31
31
|
export * from './validation/simple';
|
|
32
32
|
export * from './openapi/generator';
|
|
33
33
|
export * from './client';
|
|
34
|
-
export { NativeAdapter } from './core/native-adapter';
|
|
35
34
|
export declare function createHttpApp(options?: QHTTPXOptions): QHTTPX;
|
|
36
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Type helper for defining plugins
|
|
37
|
+
*/
|
|
38
|
+
export declare function plugin(fn: QHTTPXPlugin): QHTTPXPlugin;
|
|
37
39
|
/**
|
|
38
40
|
* Singleton instance for quick start
|
|
39
41
|
* @example
|
package/dist/src/index.js
CHANGED
|
@@ -14,14 +14,15 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.app = exports.
|
|
17
|
+
exports.app = exports.getStringifier = exports.fastJsonStringify = exports.BufferPool = exports.createSecureDefaults = exports.createSecurityHeadersMiddleware = exports.QHTTPXScope = exports.QHTTPX = void 0;
|
|
18
18
|
exports.createHttpApp = createHttpApp;
|
|
19
|
-
exports.
|
|
19
|
+
exports.plugin = plugin;
|
|
20
20
|
const server_1 = require("./core/server");
|
|
21
21
|
const presets_1 = require("./middleware/presets");
|
|
22
|
-
const native_adapter_1 = require("./core/native-adapter");
|
|
23
22
|
var server_2 = require("./core/server");
|
|
24
23
|
Object.defineProperty(exports, "QHTTPX", { enumerable: true, get: function () { return server_2.QHTTPX; } });
|
|
24
|
+
var scope_1 = require("./core/scope");
|
|
25
|
+
Object.defineProperty(exports, "QHTTPXScope", { enumerable: true, get: function () { return scope_1.QHTTPXScope; } });
|
|
25
26
|
__exportStar(require("./core/types"), exports);
|
|
26
27
|
__exportStar(require("./core/errors"), exports);
|
|
27
28
|
__exportStar(require("./middleware/cors"), exports);
|
|
@@ -56,26 +57,24 @@ __exportStar(require("./validation/types"), exports);
|
|
|
56
57
|
__exportStar(require("./validation/simple"), exports);
|
|
57
58
|
__exportStar(require("./openapi/generator"), exports);
|
|
58
59
|
__exportStar(require("./client"), exports);
|
|
59
|
-
var native_adapter_2 = require("./core/native-adapter");
|
|
60
|
-
Object.defineProperty(exports, "NativeAdapter", { enumerable: true, get: function () { return native_adapter_2.NativeAdapter; } });
|
|
61
60
|
function createHttpApp(options = {}) {
|
|
62
61
|
const app = new server_1.QHTTPX(options);
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
middlewares.forEach((mw) => app.use(mw));
|
|
73
|
-
}
|
|
62
|
+
// Apply default middleware presets
|
|
63
|
+
const middlewares = (0, presets_1.createApiPreset)({
|
|
64
|
+
rateLimit: options.rateLimit,
|
|
65
|
+
cors: options.cors,
|
|
66
|
+
compression: options.compression,
|
|
67
|
+
logging: options.logging,
|
|
68
|
+
security: options.security,
|
|
69
|
+
});
|
|
70
|
+
middlewares.forEach((mw) => app.use(mw));
|
|
74
71
|
return app;
|
|
75
72
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Type helper for defining plugins
|
|
75
|
+
*/
|
|
76
|
+
function plugin(fn) {
|
|
77
|
+
return fn;
|
|
79
78
|
}
|
|
80
79
|
/**
|
|
81
80
|
* Singleton instance for quick start
|
|
@@ -83,7 +82,7 @@ function createNativeApp(options = {}) {
|
|
|
83
82
|
* import { app } from 'qhttpx';
|
|
84
83
|
* app.get('/', ({ json }) => json({ hello: 'world' }));
|
|
85
84
|
*/
|
|
86
|
-
exports.app =
|
|
85
|
+
exports.app = new server_1.QHTTPX();
|
|
87
86
|
/**
|
|
88
87
|
* Default export for simplified usage
|
|
89
88
|
* @example
|
|
@@ -30,7 +30,12 @@ function createSecurityHeadersMiddleware(options = {}) {
|
|
|
30
30
|
}
|
|
31
31
|
function createSecureDefaults(options = {}) {
|
|
32
32
|
const middlewares = [];
|
|
33
|
-
|
|
33
|
+
const corsOpts = options.cors;
|
|
34
|
+
if (corsOpts !== false) {
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
const opts = (corsOpts === true || corsOpts === undefined) ? {} : corsOpts;
|
|
37
|
+
middlewares.push((0, cors_1.createCorsMiddleware)(opts));
|
|
38
|
+
}
|
|
34
39
|
middlewares.push(createSecurityHeadersMiddleware(options.securityHeaders));
|
|
35
40
|
return middlewares;
|
|
36
41
|
}
|
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import { QHTTPXHandler, RoutePriority } from '../core/types';
|
|
1
|
+
import { QHTTPXHandler, RoutePriority, RouteMetadata } from '../core/types';
|
|
2
2
|
export type RouteData = {
|
|
3
3
|
handler: QHTTPXHandler;
|
|
4
4
|
priority: RoutePriority;
|
|
5
|
+
metadata: RouteMetadata;
|
|
5
6
|
};
|
|
6
7
|
export type MatchResult = {
|
|
7
8
|
handler: QHTTPXHandler;
|
|
8
9
|
priority: RoutePriority;
|
|
9
10
|
params: Record<string, string>;
|
|
11
|
+
metadata: RouteMetadata;
|
|
10
12
|
};
|
|
11
13
|
export declare class RadixTree {
|
|
12
14
|
private root;
|
|
13
|
-
insert(segments: string[], handler: QHTTPXHandler, priority: RoutePriority): void;
|
|
15
|
+
insert(segments: string[], handler: QHTTPXHandler, priority: RoutePriority, metadata: RouteMetadata): void;
|
|
14
16
|
lookup(segments: string[]): MatchResult | null;
|
|
15
17
|
lookupPath(path: string): MatchResult | null;
|
|
18
|
+
private toMatchResult;
|
|
16
19
|
private findPath;
|
|
17
20
|
private find;
|
|
18
21
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RadixTree = void 0;
|
|
4
|
+
const EMPTY_PARAMS = Object.freeze({});
|
|
4
5
|
class Node {
|
|
5
6
|
constructor() {
|
|
6
7
|
// Map segment -> Node
|
|
@@ -18,7 +19,7 @@ class RadixTree {
|
|
|
18
19
|
constructor() {
|
|
19
20
|
this.root = new Node();
|
|
20
21
|
}
|
|
21
|
-
insert(segments, handler, priority) {
|
|
22
|
+
insert(segments, handler, priority, metadata) {
|
|
22
23
|
let node = this.root;
|
|
23
24
|
for (const segment of segments) {
|
|
24
25
|
if (segment.startsWith(':')) {
|
|
@@ -39,11 +40,14 @@ class RadixTree {
|
|
|
39
40
|
node = node.children.get(segment);
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
node.data = { handler, priority };
|
|
43
|
-
node.staticMatch = { handler, priority, params:
|
|
43
|
+
node.data = { handler, priority, metadata };
|
|
44
|
+
node.staticMatch = { handler, priority, params: EMPTY_PARAMS, metadata };
|
|
44
45
|
}
|
|
45
46
|
lookup(segments) {
|
|
46
|
-
|
|
47
|
+
const result = this.find(this.root, segments, 0);
|
|
48
|
+
if (!result)
|
|
49
|
+
return null;
|
|
50
|
+
return this.toMatchResult(result);
|
|
47
51
|
}
|
|
48
52
|
lookupPath(path) {
|
|
49
53
|
if (path === '/' || path === '') {
|
|
@@ -53,13 +57,43 @@ class RadixTree {
|
|
|
53
57
|
return null;
|
|
54
58
|
}
|
|
55
59
|
const start = path.charCodeAt(0) === 47 ? 1 : 0;
|
|
56
|
-
|
|
60
|
+
const result = this.findPath(this.root, path, start);
|
|
61
|
+
if (!result)
|
|
62
|
+
return null;
|
|
63
|
+
if (!result.params) {
|
|
64
|
+
return {
|
|
65
|
+
handler: result.handler,
|
|
66
|
+
priority: result.priority,
|
|
67
|
+
params: EMPTY_PARAMS,
|
|
68
|
+
metadata: result.metadata
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return this.toMatchResult(result);
|
|
72
|
+
}
|
|
73
|
+
toMatchResult(result) {
|
|
74
|
+
const params = {};
|
|
75
|
+
let node = result.params;
|
|
76
|
+
while (node) {
|
|
77
|
+
params[node.key] = node.value;
|
|
78
|
+
node = node.next;
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
handler: result.handler,
|
|
82
|
+
priority: result.priority,
|
|
83
|
+
params,
|
|
84
|
+
metadata: result.metadata
|
|
85
|
+
};
|
|
57
86
|
}
|
|
58
87
|
findPath(node, path, start) {
|
|
59
88
|
// Handle trailing slash or end of path
|
|
60
89
|
if (start >= path.length) {
|
|
61
90
|
if (node.staticMatch) {
|
|
62
|
-
return
|
|
91
|
+
return {
|
|
92
|
+
handler: node.staticMatch.handler,
|
|
93
|
+
priority: node.staticMatch.priority,
|
|
94
|
+
params: null,
|
|
95
|
+
metadata: node.staticMatch.metadata
|
|
96
|
+
};
|
|
63
97
|
}
|
|
64
98
|
return null;
|
|
65
99
|
}
|
|
@@ -84,13 +118,15 @@ class RadixTree {
|
|
|
84
118
|
if (node.paramChild) {
|
|
85
119
|
const result = this.findPath(node.paramChild, path, nextStart);
|
|
86
120
|
if (result) {
|
|
87
|
-
// Clone params to avoid modifying shared staticMatch
|
|
88
|
-
const newParams = { ...result.params };
|
|
89
|
-
newParams[node.paramChild.paramName] = segment;
|
|
90
121
|
return {
|
|
91
122
|
handler: result.handler,
|
|
92
123
|
priority: result.priority,
|
|
93
|
-
|
|
124
|
+
metadata: result.metadata,
|
|
125
|
+
params: {
|
|
126
|
+
key: node.paramChild.paramName,
|
|
127
|
+
value: segment,
|
|
128
|
+
next: result.params
|
|
129
|
+
}
|
|
94
130
|
};
|
|
95
131
|
}
|
|
96
132
|
}
|
|
@@ -102,7 +138,8 @@ class RadixTree {
|
|
|
102
138
|
return {
|
|
103
139
|
handler: node.data.handler,
|
|
104
140
|
priority: node.data.priority,
|
|
105
|
-
|
|
141
|
+
metadata: node.data.metadata,
|
|
142
|
+
params: null,
|
|
106
143
|
};
|
|
107
144
|
}
|
|
108
145
|
return null;
|
|
@@ -120,9 +157,16 @@ class RadixTree {
|
|
|
120
157
|
if (node.paramChild) {
|
|
121
158
|
const result = this.find(node.paramChild, segments, index + 1);
|
|
122
159
|
if (result) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
160
|
+
return {
|
|
161
|
+
handler: result.handler,
|
|
162
|
+
priority: result.priority,
|
|
163
|
+
metadata: result.metadata,
|
|
164
|
+
params: {
|
|
165
|
+
key: node.paramChild.paramName,
|
|
166
|
+
value: segment,
|
|
167
|
+
next: result.params
|
|
168
|
+
}
|
|
169
|
+
};
|
|
126
170
|
}
|
|
127
171
|
}
|
|
128
172
|
return null;
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import { HTTPMethod, QHTTPXHandler, RouteOptions, RoutePriority } from '../core/types';
|
|
1
|
+
import { HTTPMethod, QHTTPXHandler, RouteOptions, RoutePriority, RouteMetadata } from '../core/types';
|
|
2
2
|
import { RouteSchema } from '../validation/types';
|
|
3
3
|
type RouteDefinition = {
|
|
4
4
|
path: string;
|
|
5
5
|
segments: string[];
|
|
6
6
|
handler: QHTTPXHandler;
|
|
7
7
|
priority: RoutePriority;
|
|
8
|
+
metadata: RouteMetadata;
|
|
8
9
|
schema?: RouteSchema | Record<string, unknown>;
|
|
9
10
|
};
|
|
10
11
|
export type RouteMatch = {
|
|
11
12
|
handler: QHTTPXHandler;
|
|
12
13
|
params: Record<string, string>;
|
|
13
14
|
priority: RoutePriority;
|
|
15
|
+
metadata: RouteMetadata;
|
|
14
16
|
};
|
|
15
17
|
export declare class Router {
|
|
16
18
|
private readonly methodBuckets;
|
|
17
|
-
private readonly radixTrees;
|
|
18
19
|
private readonly staticRoutes;
|
|
20
|
+
private readonly dynamicRoutes;
|
|
19
21
|
private isFrozen;
|
|
22
|
+
private detectMetadata;
|
|
20
23
|
register(method: HTTPMethod, path: string, handler: QHTTPXHandler, options?: RouteOptions & {
|
|
21
24
|
schema?: RouteSchema | Record<string, unknown>;
|
|
22
25
|
}): void;
|