@stacksjs/bun-router 0.0.16 → 0.0.17
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/dist/auth.d.ts +22 -8
- package/dist/chunk-1ahs68ys.js +18 -0
- package/dist/{chunk-0145g1me.js → chunk-cgptvjdf.js} +147 -75
- package/dist/chunk-g3ybefhg.js +1923 -0
- package/dist/cli.js +3 -2
- package/dist/container/container.d.ts +1 -0
- package/dist/container/contextual-binding.d.ts +0 -1
- package/dist/container/decorators.d.ts +2 -5
- package/dist/container/index.d.ts +16 -0
- package/dist/container/index.js +322 -0
- package/dist/file-serving/static-files.d.ts +16 -0
- package/dist/index.d.ts +17 -1
- package/dist/index.js +1159 -1380
- package/dist/middleware/cors.d.ts +9 -0
- package/dist/middleware/csrf.d.ts +7 -0
- package/dist/middleware/ddos_protection.d.ts +16 -1
- package/dist/middleware/rate_limit.d.ts +7 -1
- package/dist/middleware/request_tracer.d.ts +15 -9
- package/dist/middleware/response_cache.d.ts +2 -1
- package/dist/request/context.d.ts +12 -1
- package/dist/request/macros.d.ts +40 -2
- package/dist/response/macros.d.ts +3 -3
- package/dist/router/fluent-routing.d.ts +50 -13
- package/dist/router/handler-resolver.d.ts +8 -0
- package/dist/router/index.d.ts +2 -0
- package/dist/router/route-compiler.d.ts +8 -10
- package/dist/router/route-trie.d.ts +7 -1
- package/dist/router/router.d.ts +13 -2
- package/dist/router/validation-integration.d.ts +10 -4
- package/dist/types/middleware-types.d.ts +1 -1
- package/dist/types/route-inference.d.ts +9 -2
- package/dist/types.d.ts +62 -9
- package/dist/utils.d.ts +1 -1
- package/package.json +11 -1
- package/src/auth.ts +32 -12
- package/src/container/container.ts +58 -12
- package/src/container/contextual-binding.ts +4 -1
- package/src/container/decorators.ts +16 -7
- package/src/container/index.ts +49 -0
- package/src/errors/circuit-breaker.ts +3 -2
- package/src/errors/graceful-degradation.ts +2 -0
- package/src/file-serving/static-files.ts +144 -7
- package/src/index.ts +53 -1
- package/src/middleware/cors.ts +76 -8
- package/src/middleware/csrf.ts +70 -22
- package/src/middleware/ddos_protection.ts +27 -3
- package/src/middleware/input_validation.ts +10 -6
- package/src/middleware/performance_alerting.ts +2 -0
- package/src/middleware/performance_monitor.ts +2 -0
- package/src/middleware/rate_limit.ts +7 -1
- package/src/middleware/request_signing.ts +2 -0
- package/src/middleware/request_tracer.ts +34 -17
- package/src/middleware/response_cache.ts +48 -13
- package/src/middleware/session.ts +3 -3
- package/src/observability/metrics.ts +1 -1
- package/src/request/context.ts +50 -3
- package/src/request/enhanced-request.ts +20 -1
- package/src/request/macros.ts +183 -49
- package/src/response/macros.ts +3 -3
- package/src/router/file-based-routing.ts +17 -10
- package/src/router/fluent-routing.ts +117 -48
- package/src/router/group-organization.ts +17 -1
- package/src/router/handler-resolver.ts +36 -0
- package/src/router/http-methods.ts +33 -3
- package/src/router/index.ts +6 -0
- package/src/router/middleware.ts +3 -0
- package/src/router/optimized-route-matching.ts +58 -9
- package/src/router/route-compiler.ts +62 -73
- package/src/router/route-matching.ts +19 -0
- package/src/router/route-trie.ts +34 -48
- package/src/router/router.ts +72 -60
- package/src/router/server.ts +361 -139
- package/src/router/validation-integration.ts +11 -5
- package/src/testing/auth-testing.ts +6 -4
- package/src/types/middleware-types.ts +7 -4
- package/src/types/route-inference.ts +29 -14
- package/src/types.ts +63 -7
- package/src/url.ts +7 -2
- package/src/utils.ts +9 -6
- package/src/validation/validator.ts +7 -4
- package/src/websocket/clustering.ts +8 -1
- package/dist/router/fluent-router.d.ts +0 -315
- package/src/router/fluent-router.ts +0 -927
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { EnhancedRequest, NextFunction } from '../types';
|
|
2
2
|
export default class Cors {
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the `Access-Control-Allow-Origin` value for a request.
|
|
5
|
+
*
|
|
6
|
+
* Supports a string origin (`'*'` or an explicit origin) or an array of
|
|
7
|
+
* allowed origins. For an allowlist, the request `Origin` is reflected back
|
|
8
|
+
* only when it is present in the list. When credentials are enabled a literal
|
|
9
|
+
* `*` is never returned (it would be both spec-violating and dangerous).
|
|
10
|
+
*/
|
|
11
|
+
private resolveAllowOrigin;
|
|
3
12
|
private getCorsHeaders;
|
|
4
13
|
handle(req: EnhancedRequest, next: NextFunction): Promise<Response>;
|
|
5
14
|
}
|
|
@@ -2,6 +2,13 @@ import type { EnhancedRequest, NextFunction } from '../types';
|
|
|
2
2
|
export default class Csrf {
|
|
3
3
|
private static tokens;
|
|
4
4
|
handle(req: EnhancedRequest, next: NextFunction): Promise<Response>;
|
|
5
|
+
/**
|
|
6
|
+
* Constant-time string comparison — a plain `!==` leaks how many leading
|
|
7
|
+
* characters of the token were correct through response timing.
|
|
8
|
+
*/
|
|
9
|
+
private static safeCompare;
|
|
10
|
+
private static storeToken;
|
|
11
|
+
private static isTokenValid;
|
|
5
12
|
private generateToken;
|
|
6
13
|
private parseCookies;
|
|
7
14
|
}
|
|
@@ -13,13 +13,27 @@ export interface DDoSProtectionOptions {
|
|
|
13
13
|
skipSuccessfulRequests?: boolean;
|
|
14
14
|
skipFailedRequests?: boolean;
|
|
15
15
|
keyGenerator?: (req: EnhancedRequest) => string;
|
|
16
|
-
onLimitReached?: (req: EnhancedRequest, rateLimitInfo:
|
|
16
|
+
onLimitReached?: (req: EnhancedRequest, rateLimitInfo: DDoSRateLimitInfo) => Response | Promise<Response>;
|
|
17
17
|
store?: 'memory' | 'redis';
|
|
18
18
|
redis?: {
|
|
19
19
|
url: string;
|
|
20
20
|
prefix?: string;
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
+
interface RequestInfo {
|
|
24
|
+
count: number;
|
|
25
|
+
firstRequest: number;
|
|
26
|
+
lastRequest: number;
|
|
27
|
+
blocked: boolean;
|
|
28
|
+
blockExpires?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Rate-limit details passed to `onLimitReached` when a client is blocked.
|
|
32
|
+
*/
|
|
33
|
+
export interface DDoSRateLimitInfo extends RequestInfo {
|
|
34
|
+
/** Seconds until the block lifts (mirrors the Retry-After header) */
|
|
35
|
+
retryAfter: number;
|
|
36
|
+
}
|
|
23
37
|
export default class DDoSProtection {
|
|
24
38
|
private options;
|
|
25
39
|
private requestStore;
|
|
@@ -37,3 +51,4 @@ export default class DDoSProtection {
|
|
|
37
51
|
destroy(): void;
|
|
38
52
|
}
|
|
39
53
|
export declare function ddosProtection(options?: DDoSProtectionOptions): (req: EnhancedRequest, next: NextFunction) => Promise<Response>;
|
|
54
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StorageProvider } from 'ts-rate-limiter';
|
|
1
2
|
import type { EnhancedRequest, Middleware, MiddlewareHandler, NextFunction } from '../types';
|
|
2
3
|
export interface RateLimitOptions {
|
|
3
4
|
windowMs?: number;
|
|
@@ -12,7 +13,12 @@ export interface RateLimitOptions {
|
|
|
12
13
|
resetTime: number;
|
|
13
14
|
}) => Response | Promise<Response>;
|
|
14
15
|
skip?: (request: Request) => boolean | Promise<boolean>;
|
|
15
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Custom storage backend (ts-rate-limiter `StorageProvider`), or the
|
|
18
|
+
* string `'redis'` to use the built-in Redis store configured via
|
|
19
|
+
* the `redis` option.
|
|
20
|
+
*/
|
|
21
|
+
storage?: StorageProvider | 'redis';
|
|
16
22
|
algorithm?: 'fixed-window' | 'sliding-window' | 'token-bucket';
|
|
17
23
|
draftMode?: boolean;
|
|
18
24
|
redis?: {
|
|
@@ -7,17 +7,28 @@ export interface TraceSpan {
|
|
|
7
7
|
startTime: number;
|
|
8
8
|
endTime?: number;
|
|
9
9
|
duration?: number;
|
|
10
|
-
tags: Record<string,
|
|
10
|
+
tags: Record<string, unknown>;
|
|
11
11
|
logs: Array<{
|
|
12
12
|
timestamp: number;
|
|
13
13
|
level: 'debug' | 'info' | 'warn' | 'error';
|
|
14
14
|
message: string;
|
|
15
|
-
fields?: Record<string,
|
|
15
|
+
fields?: Record<string, unknown>;
|
|
16
16
|
}>;
|
|
17
17
|
status: 'ok' | 'error' | 'timeout';
|
|
18
18
|
error?: string;
|
|
19
19
|
stackTrace?: string[];
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Destination for exported spans. `endpoint`/`headers` configure the
|
|
23
|
+
* HTTP-based exporters (jaeger/zipkin/otlp); `customExporter` receives
|
|
24
|
+
* the spans directly.
|
|
25
|
+
*/
|
|
26
|
+
export interface TraceExporterConfig {
|
|
27
|
+
type: 'console' | 'jaeger' | 'zipkin' | 'otlp' | 'custom';
|
|
28
|
+
endpoint?: string;
|
|
29
|
+
headers?: Record<string, string>;
|
|
30
|
+
customExporter?: (spans: TraceSpan[]) => Promise<void>;
|
|
31
|
+
}
|
|
21
32
|
export interface TracingOptions {
|
|
22
33
|
enabled?: boolean;
|
|
23
34
|
sampleRate?: number;
|
|
@@ -27,12 +38,7 @@ export interface TracingOptions {
|
|
|
27
38
|
includeRequestBody?: boolean;
|
|
28
39
|
includeResponseBody?: boolean;
|
|
29
40
|
maxBodySize?: number;
|
|
30
|
-
exporters?:
|
|
31
|
-
type: 'console' | 'jaeger' | 'zipkin' | 'otlp' | 'custom';
|
|
32
|
-
endpoint?: string;
|
|
33
|
-
headers?: Record<string, string>;
|
|
34
|
-
customExporter?: (spans: TraceSpan[]) => Promise<void>;
|
|
35
|
-
}>;
|
|
41
|
+
exporters?: TraceExporterConfig[];
|
|
36
42
|
propagation?: {
|
|
37
43
|
enabled?: boolean;
|
|
38
44
|
headers?: string[];
|
|
@@ -63,7 +69,7 @@ export default class RequestTracer {
|
|
|
63
69
|
getActiveSpans(): TraceSpan[];
|
|
64
70
|
getSpanById(spanId: string): TraceSpan | undefined;
|
|
65
71
|
createChildSpan(parentSpanId: string, operationName: string): string | null;
|
|
66
|
-
addTag(spanId: string, key: string, value:
|
|
72
|
+
addTag(spanId: string, key: string, value: unknown): void;
|
|
67
73
|
addLog(spanId: string, level: 'debug' | 'info' | 'warn' | 'error', message: string, fields?: Record<string, any>): void;
|
|
68
74
|
finish(spanId: string, status?: 'ok' | 'error' | 'timeout', error?: string): void;
|
|
69
75
|
flush(): Promise<void>;
|
|
@@ -5,7 +5,7 @@ export interface CacheEntry {
|
|
|
5
5
|
status: number;
|
|
6
6
|
statusText: string;
|
|
7
7
|
headers: Record<string, string>;
|
|
8
|
-
body: number[];
|
|
8
|
+
body: Uint8Array | number[];
|
|
9
9
|
contentType: string;
|
|
10
10
|
};
|
|
11
11
|
createdAt: number;
|
|
@@ -54,6 +54,7 @@ export declare class ResponseCache implements Middleware {
|
|
|
54
54
|
private memoryCache;
|
|
55
55
|
private cacheStats;
|
|
56
56
|
private cleanupInterval?;
|
|
57
|
+
private compiledInvalidationPatterns;
|
|
57
58
|
constructor(options?: ResponseCacheOptions);
|
|
58
59
|
private initializeStorage;
|
|
59
60
|
private startCleanupInterval;
|
|
@@ -9,7 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { EnhancedRequest } from '../types';
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Force AsyncLocalStorage-backed request context on for all subsequent
|
|
13
|
+
* requests. Calling `request()`/`getCurrentRequest()` enables it
|
|
14
|
+
* automatically — use this when the very first request must already be
|
|
15
|
+
* fully isolated under concurrent load.
|
|
16
|
+
*/
|
|
17
|
+
export declare function enableRequestContext(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Run `fn` inside a request-context scope where `request()` and
|
|
13
20
|
* `getCurrentRequest()` resolve to `initial`. The Router calls this around
|
|
14
21
|
* each request automatically.
|
|
15
22
|
*/
|
|
@@ -23,6 +30,10 @@ export declare function setCurrentRequest(req: EnhancedRequest): void;
|
|
|
23
30
|
/**
|
|
24
31
|
* Returns the active request, or `undefined` when called outside a request
|
|
25
32
|
* scope. Prefer `request()` when the request is required.
|
|
33
|
+
*
|
|
34
|
+
* First use switches the router to AsyncLocalStorage-backed context for
|
|
35
|
+
* all subsequent requests; this call itself resolves via a synchronous
|
|
36
|
+
* fallback (see `enableRequestContext` for eager opt-in).
|
|
26
37
|
*/
|
|
27
38
|
export declare function getCurrentRequest(): EnhancedRequest | undefined;
|
|
28
39
|
/**
|
package/dist/request/macros.d.ts
CHANGED
|
@@ -7,16 +7,25 @@ import type { EnhancedRequest } from '../types';
|
|
|
7
7
|
export interface RequestMacro {
|
|
8
8
|
name: string;
|
|
9
9
|
handler: (this: EnhancedRequest, ...args: any[]) => any;
|
|
10
|
+
/**
|
|
11
|
+
* Accessor macros are installed as getters on the macro prototype —
|
|
12
|
+
* `req.<name>` invokes the handler instead of returning a function.
|
|
13
|
+
* Assignment shadows the accessor with an own property.
|
|
14
|
+
*/
|
|
15
|
+
accessor?: boolean;
|
|
10
16
|
}
|
|
17
|
+
export declare function getParsedURL(req: EnhancedRequest): URL;
|
|
18
|
+
export declare function getParsedCookies(req: EnhancedRequest): Record<string, string>;
|
|
11
19
|
/**
|
|
12
20
|
* Request macro registry
|
|
13
21
|
*/
|
|
14
22
|
declare class RequestMacroRegistry {
|
|
15
23
|
private macros;
|
|
24
|
+
private epoch;
|
|
16
25
|
/**
|
|
17
26
|
* Register a request macro
|
|
18
27
|
*/
|
|
19
|
-
register(name: string, handler: (this: EnhancedRequest, ...args: any[]) => any): void;
|
|
28
|
+
register(name: string, handler: (this: EnhancedRequest, ...args: any[]) => any, accessor?: boolean): void;
|
|
20
29
|
/**
|
|
21
30
|
* Get a registered macro
|
|
22
31
|
*/
|
|
@@ -37,6 +46,10 @@ declare class RequestMacroRegistry {
|
|
|
37
46
|
* Clear all macros
|
|
38
47
|
*/
|
|
39
48
|
clear(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Monotonic registry version (see `RequestWithMacros.applyMacros`)
|
|
51
|
+
*/
|
|
52
|
+
getEpoch(): number;
|
|
40
53
|
}
|
|
41
54
|
/**
|
|
42
55
|
* Global request macro registry
|
|
@@ -51,7 +64,21 @@ export declare class RequestWithMacros {
|
|
|
51
64
|
*/
|
|
52
65
|
static macro(name: string, handler: (this: EnhancedRequest, ...args: any[]) => any): void;
|
|
53
66
|
/**
|
|
54
|
-
*
|
|
67
|
+
* Register an accessor macro: `req.<name>` evaluates the getter
|
|
68
|
+
* (instead of exposing a callable). Assignment to the property
|
|
69
|
+
* shadows the accessor with an own value, so consumers that set
|
|
70
|
+
* `req.<name> = ...` keep working.
|
|
71
|
+
*/
|
|
72
|
+
static macroAccessor(name: string, getter: (this: EnhancedRequest) => any): void;
|
|
73
|
+
/**
|
|
74
|
+
* Apply macros to a request object.
|
|
75
|
+
*
|
|
76
|
+
* Macros are attached via a shared prototype inserted between the
|
|
77
|
+
* request and its original prototype — one `setPrototypeOf` per
|
|
78
|
+
* request. The previous implementation looped over every macro and
|
|
79
|
+
* `bind`-assigned it, costing ~40 property writes and closures per
|
|
80
|
+
* request on the dispatch hot path. Macro handlers receive the
|
|
81
|
+
* request as `this`, so no per-request binding is needed.
|
|
55
82
|
*/
|
|
56
83
|
static applyMacros(request: EnhancedRequest): EnhancedRequest;
|
|
57
84
|
/**
|
|
@@ -119,6 +146,17 @@ export declare const BuiltInRequestMacros: {
|
|
|
119
146
|
* Get authorization header
|
|
120
147
|
*/
|
|
121
148
|
bearerToken(this: EnhancedRequest): string | null;
|
|
149
|
+
/**
|
|
150
|
+
* Get the raw, unparsed request body as a string.
|
|
151
|
+
*
|
|
152
|
+
* The body stream can only be consumed once, so this caches the result on
|
|
153
|
+
* `_rawBody`. Signature-verifying callbacks (Stripe/GitHub/Slack webhooks)
|
|
154
|
+
* need the exact bytes the client sent, before any JSON parsing — a parsed
|
|
155
|
+
* `jsonBody` re-serialized is NOT byte-identical and will fail HMAC checks.
|
|
156
|
+
* A framework body parser that has already read the body may populate
|
|
157
|
+
* `_rawBody` up front so this returns without re-reading.
|
|
158
|
+
*/
|
|
159
|
+
rawBody(this: EnhancedRequest): Promise<string>;
|
|
122
160
|
/**
|
|
123
161
|
* Get basic auth credentials
|
|
124
162
|
*/
|
|
@@ -7,16 +7,16 @@ export interface ResponseMacro {
|
|
|
7
7
|
name: string;
|
|
8
8
|
handler: (...args: any[]) => Response;
|
|
9
9
|
}
|
|
10
|
-
export interface ApiResponse<T =
|
|
10
|
+
export interface ApiResponse<T = unknown> {
|
|
11
11
|
data?: T;
|
|
12
12
|
message?: string;
|
|
13
13
|
error?: string;
|
|
14
14
|
errors?: Record<string, string[]>;
|
|
15
|
-
meta?: Record<string,
|
|
15
|
+
meta?: Record<string, unknown>;
|
|
16
16
|
links?: Record<string, string>;
|
|
17
17
|
timestamp?: string;
|
|
18
18
|
}
|
|
19
|
-
export interface PaginatedResponse<T =
|
|
19
|
+
export interface PaginatedResponse<T = unknown> extends ApiResponse<T[]> {
|
|
20
20
|
meta: {
|
|
21
21
|
current_page: number;
|
|
22
22
|
per_page: number;
|
|
@@ -4,6 +4,30 @@ import type { ThrottleConfig } from '../routing/route-throttling';
|
|
|
4
4
|
import type { EnhancedRequest, MiddlewareHandler, RouteHandler, ThrottlePattern } from '../types';
|
|
5
5
|
import { RouteCacheFactory } from '../routing/route-caching';
|
|
6
6
|
import { ThrottleFactory } from '../routing/route-throttling';
|
|
7
|
+
/**
|
|
8
|
+
* A route registered on a {@link FluentRouter}
|
|
9
|
+
*/
|
|
10
|
+
export interface RegisteredFluentRoute {
|
|
11
|
+
method: string;
|
|
12
|
+
path: string;
|
|
13
|
+
handler: RouteHandler;
|
|
14
|
+
middleware: MiddlewareHandler[];
|
|
15
|
+
name?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Laravel-style resource controller shape accepted by
|
|
19
|
+
* {@link FluentRouter.resource}. Every action is optional — only the
|
|
20
|
+
* actions present (and allowed by `only`/`except`) are registered.
|
|
21
|
+
*/
|
|
22
|
+
export interface FluentResourceController {
|
|
23
|
+
index?: RouteHandler;
|
|
24
|
+
create?: RouteHandler;
|
|
25
|
+
store?: RouteHandler;
|
|
26
|
+
show?: RouteHandler;
|
|
27
|
+
edit?: RouteHandler;
|
|
28
|
+
update?: RouteHandler;
|
|
29
|
+
destroy?: RouteHandler;
|
|
30
|
+
}
|
|
7
31
|
/**
|
|
8
32
|
* Fluent route builder with chainable API
|
|
9
33
|
*/
|
|
@@ -122,6 +146,16 @@ export declare class FluentRouter {
|
|
|
122
146
|
* Conditional middleware execution
|
|
123
147
|
*/
|
|
124
148
|
when(condition: MiddlewareCondition): FluentConditionalBuilder;
|
|
149
|
+
/**
|
|
150
|
+
* Look up a named middleware factory.
|
|
151
|
+
* @internal Used by the fluent builders — avoids `as any` reach-ins.
|
|
152
|
+
*/
|
|
153
|
+
resolveNamedMiddleware(name: string): ((params?: string) => MiddlewareHandler) | undefined;
|
|
154
|
+
/**
|
|
155
|
+
* Register conditional middleware, evaluated per request in {@link handle}.
|
|
156
|
+
* @internal Used by the fluent builders.
|
|
157
|
+
*/
|
|
158
|
+
addConditionalMiddleware(conditional: ConditionalMiddleware): void;
|
|
125
159
|
/**
|
|
126
160
|
* Apply middleware with parameters
|
|
127
161
|
*/
|
|
@@ -192,7 +226,7 @@ export declare class FluentRouter {
|
|
|
192
226
|
/**
|
|
193
227
|
* Create resource routes (Laravel-style)
|
|
194
228
|
*/
|
|
195
|
-
resource(name: string, controller:
|
|
229
|
+
resource(name: string, controller: FluentResourceController, options?: {
|
|
196
230
|
only?: string[];
|
|
197
231
|
except?: string[];
|
|
198
232
|
model?: BunQueryBuilderModel;
|
|
@@ -200,23 +234,24 @@ export declare class FluentRouter {
|
|
|
200
234
|
/**
|
|
201
235
|
* Get all registered routes
|
|
202
236
|
*/
|
|
203
|
-
getRoutes():
|
|
204
|
-
method: string;
|
|
205
|
-
path: string;
|
|
206
|
-
handler: RouteHandler;
|
|
207
|
-
middleware: MiddlewareHandler[];
|
|
208
|
-
name?: string;
|
|
209
|
-
}>;
|
|
237
|
+
getRoutes(): RegisteredFluentRoute[];
|
|
210
238
|
/**
|
|
211
239
|
* Handle incoming request
|
|
212
240
|
*/
|
|
213
241
|
handle(request: EnhancedRequest): Promise<Response | null>;
|
|
214
242
|
/**
|
|
215
|
-
*
|
|
243
|
+
* Match a route against the request, extracting path parameters.
|
|
244
|
+
*
|
|
245
|
+
* Delegates to the same `matchPath` used by the main `Router`, so the
|
|
246
|
+
* fluent API has identical semantics for `{param}`, optional `{param?}`,
|
|
247
|
+
* and wildcard segments (the previous ad-hoc regex neither escaped
|
|
248
|
+
* static text nor extracted params at all).
|
|
249
|
+
*
|
|
250
|
+
* @returns the extracted params, or `null` when the route doesn't match
|
|
216
251
|
*/
|
|
217
|
-
private
|
|
252
|
+
private matchRouteParams;
|
|
218
253
|
/**
|
|
219
|
-
* Execute route with middleware
|
|
254
|
+
* Execute route with middleware (global → conditional → route-specific)
|
|
220
255
|
*/
|
|
221
256
|
private executeRoute;
|
|
222
257
|
}
|
|
@@ -249,7 +284,9 @@ export declare const RouteFactory: {
|
|
|
249
284
|
*/
|
|
250
285
|
export declare const RouterUtils: {
|
|
251
286
|
/**
|
|
252
|
-
* Generate
|
|
287
|
+
* Generate a URL for a named route. Resolves the path from the shared
|
|
288
|
+
* named-route registry (the same one `router.route()`/`url()` use);
|
|
289
|
+
* falls back to `/<name>` when the name was never registered.
|
|
253
290
|
*/
|
|
254
291
|
route: (name: string, params?: Record<string, string>, query?: Record<string, string>) => string;
|
|
255
292
|
/**
|
|
@@ -259,7 +296,7 @@ export declare const RouterUtils: {
|
|
|
259
296
|
/**
|
|
260
297
|
* JSON response
|
|
261
298
|
*/
|
|
262
|
-
json: (data:
|
|
299
|
+
json: (data: unknown, status?: number) => Response;
|
|
263
300
|
};
|
|
264
301
|
/**
|
|
265
302
|
* Global router instance
|
|
@@ -22,3 +22,11 @@ export declare function resolveHandler(handler: unknown, req: EnhancedRequest, c
|
|
|
22
22
|
* Creates a handler resolver bound to a specific config
|
|
23
23
|
*/
|
|
24
24
|
export declare function createHandlerResolver(config: RouterConfig): (handler: unknown, req: EnhancedRequest) => Promise<Response>;
|
|
25
|
+
/**
|
|
26
|
+
* Precompile the dispatch branch for a handler whose shape never changes
|
|
27
|
+
* (a registered route's handler). `resolveHandler` re-sniffs the handler
|
|
28
|
+
* type — instanceof / typeof / prototype checks — on every request; this
|
|
29
|
+
* resolves the branch once and returns a specialized invoker for the
|
|
30
|
+
* per-request hot path.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createHandlerInvoker(handler: unknown, config: RouterConfig): (req: EnhancedRequest) => Promise<Response>;
|
package/dist/router/index.d.ts
CHANGED
|
@@ -82,6 +82,7 @@ declare module './router' {
|
|
|
82
82
|
status?: number;
|
|
83
83
|
headers?: Record<string, string>;
|
|
84
84
|
}) => Router;
|
|
85
|
+
withoutNativeDispatch: () => Router;
|
|
85
86
|
where: ((_param: string, _pattern: string | RegExp) => Router) & ((constraints: Record<string, string | RegExp>) => Router);
|
|
86
87
|
whereNumber: (param: string) => Router;
|
|
87
88
|
whereAlpha: (param: string) => Router;
|
|
@@ -98,6 +99,7 @@ declare module './router' {
|
|
|
98
99
|
create?: ActionHandler;
|
|
99
100
|
edit?: ActionHandler;
|
|
100
101
|
}) => Router;
|
|
102
|
+
head: (path: string, handler: ActionHandler, type?: 'api' | 'web', name?: string, middleware?: any[]) => Router;
|
|
101
103
|
onError: (handler: (error: Error) => Response | Promise<Response>) => Router;
|
|
102
104
|
redirect: (url: string, status?: 301 | 302 | 303 | 307 | 308) => Response;
|
|
103
105
|
permanentRedirect: (url: string) => Response;
|
|
@@ -9,6 +9,12 @@ export interface RouteCompilerOptions {
|
|
|
9
9
|
enablePriorityOptimization: boolean;
|
|
10
10
|
cacheSize: number;
|
|
11
11
|
precompilePatterns: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Record per-match timing via `performance.now()`. Off by default —
|
|
14
|
+
* timing every request costs two clock reads per match on the hot path.
|
|
15
|
+
* Match/hit/miss counters are always collected (they're just integers).
|
|
16
|
+
*/
|
|
17
|
+
enableProfiling: boolean;
|
|
12
18
|
}
|
|
13
19
|
/**
|
|
14
20
|
* Route matching statistics for performance monitoring
|
|
@@ -27,7 +33,7 @@ export interface RouteMatchStats {
|
|
|
27
33
|
export declare class RouteCompiler {
|
|
28
34
|
private trie;
|
|
29
35
|
private matchCache;
|
|
30
|
-
private
|
|
36
|
+
private routeKeys;
|
|
31
37
|
private stats;
|
|
32
38
|
private options;
|
|
33
39
|
constructor(options?: Partial<RouteCompilerOptions>);
|
|
@@ -36,10 +42,6 @@ export declare class RouteCompiler {
|
|
|
36
42
|
* @returns true if route was added, false if it's a duplicate
|
|
37
43
|
*/
|
|
38
44
|
addRoute(route: Route): boolean;
|
|
39
|
-
/**
|
|
40
|
-
* Check if a route is an exact duplicate of an existing route
|
|
41
|
-
*/
|
|
42
|
-
private checkForDuplicateRoute;
|
|
43
45
|
/**
|
|
44
46
|
* Pre-compile route patterns for faster matching
|
|
45
47
|
*/
|
|
@@ -67,11 +69,7 @@ export declare class RouteCompiler {
|
|
|
67
69
|
*/
|
|
68
70
|
private addToCache;
|
|
69
71
|
/**
|
|
70
|
-
* Update
|
|
71
|
-
*/
|
|
72
|
-
private updateCacheLRU;
|
|
73
|
-
/**
|
|
74
|
-
* Update average match time statistics
|
|
72
|
+
* Update average match time statistics (only when profiling is enabled)
|
|
75
73
|
*/
|
|
76
74
|
private updateMatchTime;
|
|
77
75
|
/**
|
|
@@ -83,7 +83,13 @@ export declare class RouteTrie {
|
|
|
83
83
|
*/
|
|
84
84
|
match(path: string, method: HTTPMethod): RouteMatch | null;
|
|
85
85
|
/**
|
|
86
|
-
* Recursively match path segments
|
|
86
|
+
* Recursively match path segments.
|
|
87
|
+
*
|
|
88
|
+
* Candidates are tried in specificity order — static, then parameter,
|
|
89
|
+
* then wildcard — and the first full match wins. This both fixes the
|
|
90
|
+
* old behavior (where a wildcard sibling could shadow a static route)
|
|
91
|
+
* and avoids allocating candidate arrays and params copies per segment:
|
|
92
|
+
* `params` is mutated in place and rolled back on backtrack.
|
|
87
93
|
*/
|
|
88
94
|
private matchSegments;
|
|
89
95
|
/**
|
package/dist/router/router.d.ts
CHANGED
|
@@ -47,6 +47,8 @@ export declare class Router {
|
|
|
47
47
|
private namedMiddleware;
|
|
48
48
|
private conditionalMiddleware;
|
|
49
49
|
_middlewarePipeline?: MiddlewarePipeline;
|
|
50
|
+
_mwEpoch: number;
|
|
51
|
+
_allowedMethodsCache: Map<string, string[]>;
|
|
50
52
|
config: RouterConfig;
|
|
51
53
|
constructor(config?: Partial<RouterConfig>);
|
|
52
54
|
/**
|
|
@@ -229,12 +231,21 @@ export declare class Router {
|
|
|
229
231
|
*/
|
|
230
232
|
use(...middleware: (string | MiddlewareHandler)[]): Router;
|
|
231
233
|
/**
|
|
232
|
-
* Create a route group with prefix and middleware
|
|
234
|
+
* Create a route group with prefix and middleware.
|
|
235
|
+
*
|
|
236
|
+
* Synchronous callbacks return the router for chaining. Asynchronous
|
|
237
|
+
* callbacks return a promise that resolves once the callback (and any
|
|
238
|
+
* routes it registers) has finished — await it, or routes registered
|
|
239
|
+
* after an `await` inside the callback would lose the group prefix.
|
|
233
240
|
*/
|
|
234
241
|
group(options: {
|
|
235
242
|
prefix?: string;
|
|
236
243
|
middleware?: (string | MiddlewareHandler)[];
|
|
237
|
-
}, callback: () =>
|
|
244
|
+
}, callback: () => Promise<void>): Promise<Router>;
|
|
245
|
+
group(options: {
|
|
246
|
+
prefix?: string;
|
|
247
|
+
middleware?: (string | MiddlewareHandler)[];
|
|
248
|
+
}, callback: () => void): Router;
|
|
238
249
|
/**
|
|
239
250
|
* Add route to the router
|
|
240
251
|
*/
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Router Validation Integration
|
|
3
3
|
*
|
|
4
4
|
* Integration layer for validation and macros with the router.
|
|
5
|
-
* Provides RouteBuilder and
|
|
5
|
+
* Provides RouteBuilder and ValidationFluentRouter for building routes with validation support.
|
|
6
6
|
*/
|
|
7
7
|
import type { EnhancedRequest, MiddlewareHandler, RouteHandler } from '../types';
|
|
8
8
|
import type { ValidationRules, ValidatorConfig } from '../validation/validator';
|
|
@@ -31,9 +31,15 @@ export declare class RouteBuilder {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* Minimal route collector with validation support.
|
|
35
|
+
*
|
|
36
|
+
* Renamed from `FluentRouter`: this module used to declare a third class
|
|
37
|
+
* with that name. It was shadowed by the canonical `FluentRouter` exported
|
|
38
|
+
* from `fluent-routing.ts` (explicit exports win over `export *`), so
|
|
39
|
+
* `createFluentRouter()` silently returned a different class than the
|
|
40
|
+
* public `FluentRouter` symbol. The distinct name removes the ambiguity.
|
|
35
41
|
*/
|
|
36
|
-
export declare class
|
|
42
|
+
export declare class ValidationFluentRouter {
|
|
37
43
|
private routes;
|
|
38
44
|
/**
|
|
39
45
|
* Create GET route
|
|
@@ -136,7 +142,7 @@ export declare const RouteHelpers: {
|
|
|
136
142
|
/**
|
|
137
143
|
* Fluent router factory
|
|
138
144
|
*/
|
|
139
|
-
export declare function createFluentRouter():
|
|
145
|
+
export declare function createFluentRouter(): ValidationFluentRouter;
|
|
140
146
|
/**
|
|
141
147
|
* Middleware composition helpers
|
|
142
148
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Advanced TypeScript utilities for type-safe middleware with generic constraints
|
|
5
5
|
*/
|
|
6
6
|
import type { RouteHandler, TypedRequest } from './route-inference';
|
|
7
|
-
export interface TypedMiddleware<TInput =
|
|
7
|
+
export interface TypedMiddleware<TInput = Request, TOutput = Response, _TContext = object, TNext = () => Promise<TOutput>> {
|
|
8
8
|
(request: TInput, next: TNext): Promise<TOutput> | TOutput;
|
|
9
9
|
}
|
|
10
10
|
export type AugmentContext<TBase, TAddition> = TBase & TAddition;
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Advanced TypeScript utilities for inferring route parameter types from URL patterns
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
/** Strip an inline constraint from a brace parameter: `id:[0-9]+` → `id` */
|
|
7
|
+
type StripInlineConstraint<T extends string> = T extends `${infer Name}:${string}` ? Name : T;
|
|
8
|
+
export type ExtractRouteParams<T extends string> = T extends `${string}{${infer Param}}${infer Rest}` ? (Param extends `${infer Inner}?` ? {
|
|
9
|
+
[K in StripInlineConstraint<Inner>]?: string;
|
|
10
|
+
} : {
|
|
11
|
+
[K in StripInlineConstraint<Param>]: string;
|
|
12
|
+
}) & ExtractRouteParams<Rest> : T extends `${infer _Start}:${infer Param}/${infer Rest}` ? {
|
|
7
13
|
[K in Param]: string;
|
|
8
14
|
} & ExtractRouteParams<`/${Rest}`> : T extends `${infer _Start}:${infer Param}?${infer Rest}` ? {
|
|
9
15
|
[K in Param]?: string;
|
|
@@ -100,7 +106,7 @@ export interface RouteGroup<TPrefix extends string = '', TContext = object> {
|
|
|
100
106
|
middleware?: TypedMiddleware<any, any, any, TContext>[];
|
|
101
107
|
routes: TypedRoute<any, any, any, TContext>[];
|
|
102
108
|
}
|
|
103
|
-
export interface TypedMiddleware<TRequest =
|
|
109
|
+
export interface TypedMiddleware<TRequest = Request, TResponse = Response, TNext = () => Promise<TResponse>, _TContext = Record<string, never>> {
|
|
104
110
|
(request: TRequest, next: TNext): Promise<TResponse> | TResponse;
|
|
105
111
|
}
|
|
106
112
|
export interface RouteBuilder<TContext = object> {
|
|
@@ -166,3 +172,4 @@ export interface GenerateOpenAPISchema<T extends TypedRoute<any, any, any, any>>
|
|
|
166
172
|
export type AssertEqual<T, U> = T extends U ? U extends T ? true : false : false;
|
|
167
173
|
export type AssertExtends<T, U> = T extends U ? true : false;
|
|
168
174
|
export type AssertNotEqual<T, U> = AssertEqual<T, U> extends true ? false : true;
|
|
175
|
+
export {};
|