@nextrush/router 3.0.7 → 4.0.0-beta.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/README.md +250 -492
- package/dist/index.d.ts +227 -178
- package/dist/index.js +1075 -657
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/__tests__/allowed-methods.test.ts +144 -0
- package/src/__tests__/audit-fixes.test.ts +59 -0
- package/src/__tests__/canonical-path-security.test.ts +198 -0
- package/src/__tests__/canonicalize-memo.test.ts +108 -0
- package/src/__tests__/dispatch-deasync.test.ts +292 -0
- package/src/__tests__/find-node-differential.test.ts +220 -0
- package/src/__tests__/fixtures/match-golden.json +556 -0
- package/src/__tests__/head-auto-registration.test.ts +197 -0
- package/src/__tests__/helpers/differential-corpus.ts +314 -0
- package/src/__tests__/match-differential.test.ts +46 -0
- package/src/__tests__/match-hotpath-guard.test.ts +71 -0
- package/src/__tests__/match-node-indexed-unpooled.test.ts +83 -0
- package/src/__tests__/match-normalize-fastpath.test.ts +64 -0
- package/src/__tests__/match-prenormalized.test.ts +95 -0
- package/src/__tests__/match-safety.test.ts +223 -0
- package/src/__tests__/match-single-alloc.test.ts +82 -0
- package/src/__tests__/match-walk-pool-safety.test.ts +103 -0
- package/src/__tests__/middleware-pipeline.test.ts +306 -0
- package/src/__tests__/param-decoding.test.ts +78 -0
- package/src/__tests__/public-surface.test.ts +72 -0
- package/src/__tests__/registration-max-depth.test.ts +56 -0
- package/src/__tests__/route-metadata.test.ts +172 -0
- package/src/__tests__/router-audit.test.ts +315 -0
- package/src/__tests__/router-edge-cases.test.ts +2 -7
- package/src/__tests__/router.test.ts +148 -7
- package/src/__tests__/static-map-reset.test.ts +48 -0
- package/src/__tests__/walk-pool-sizing.test.ts +72 -0
- package/src/__tests__/walk-pool-undersized-guard.test.ts +59 -0
- package/src/canonicalize.ts +137 -0
- package/src/composition.ts +79 -0
- package/src/constants.ts +43 -0
- package/src/dispatch.ts +145 -0
- package/src/find-node.ts +125 -0
- package/src/group-router.ts +208 -0
- package/src/index.ts +14 -5
- package/src/match-route.ts +241 -0
- package/src/matching.ts +246 -0
- package/src/middleware-adapter.ts +59 -0
- package/src/redirect.ts +97 -0
- package/src/registration.ts +343 -0
- package/src/route-metadata.ts +68 -0
- package/src/router.ts +219 -872
- package/src/segment-trie.ts +227 -0
- package/src/state.ts +53 -0
- package/src/walk-pool.ts +208 -0
- package/src/radix-tree.ts +0 -184
package/dist/index.d.ts
CHANGED
|
@@ -1,238 +1,279 @@
|
|
|
1
|
-
import { RouterOptions,
|
|
1
|
+
import { RouteHandler, Middleware, MetadataContribution, RouteMetaMarker, RouterOptions, RouteEntry, HttpMethod, RouteDefinition, RouteMatch, Context } from '@nextrush/types';
|
|
2
2
|
export { HttpMethod, Middleware, Route, RouteHandler, RouteMatch, Router as RouterInterface, RouterOptions } from '@nextrush/types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @nextrush/router -
|
|
5
|
+
* @nextrush/router - Redirect Handler Compilation
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Single, shared redirect implementation used by both `Router.redirect()` and
|
|
8
|
+
* route groups (audit RT-4). Previously the group variant used a naive
|
|
9
|
+
* `replaceAll(':key', value)` that could mis-substitute overlapping param names
|
|
10
|
+
* and re-scanned the string per request; this precompiles the target template
|
|
11
|
+
* once and only substitutes route-style `:param` slots (a `:` at position 0 or
|
|
12
|
+
* preceded by `/`), so `https://` and other literal colons are never touched.
|
|
11
13
|
*
|
|
12
14
|
* @packageDocumentation
|
|
13
15
|
*/
|
|
14
16
|
|
|
17
|
+
/** HTTP redirect status codes supported by `redirect()`. */
|
|
18
|
+
type RedirectStatus = 301 | 302 | 303 | 307 | 308;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @nextrush/router - Route Groups
|
|
22
|
+
*
|
|
23
|
+
* A route group applies a shared path prefix and middleware to a set of routes
|
|
24
|
+
* registered inside a callback. Extracted from `router.ts` (audit RT-3) and
|
|
25
|
+
* given a real public type (audit RT-6): `router.group()` callbacks receive a
|
|
26
|
+
* {@link RouteGroup}, not a mis-cast `Router`.
|
|
27
|
+
*
|
|
28
|
+
* @packageDocumentation
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The object passed to a `router.group(prefix, callback)` callback.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* Exposes only the route-registration surface that is valid inside a group —
|
|
36
|
+
* intentionally NOT the full {@link Router} (no `mount`/`use`/`reset`), which is
|
|
37
|
+
* why the previous `as unknown as Router` cast was a lie (audit RT-6).
|
|
38
|
+
*/
|
|
39
|
+
interface RouteGroup {
|
|
40
|
+
get(path: string, ...handlers: RouteHandler[]): this;
|
|
41
|
+
post(path: string, ...handlers: RouteHandler[]): this;
|
|
42
|
+
put(path: string, ...handlers: RouteHandler[]): this;
|
|
43
|
+
delete(path: string, ...handlers: RouteHandler[]): this;
|
|
44
|
+
patch(path: string, ...handlers: RouteHandler[]): this;
|
|
45
|
+
head(path: string, ...handlers: RouteHandler[]): this;
|
|
46
|
+
options(path: string, ...handlers: RouteHandler[]): this;
|
|
47
|
+
all(path: string, ...handlers: RouteHandler[]): this;
|
|
48
|
+
redirect(from: string, to: string, status?: RedirectStatus): this;
|
|
49
|
+
group(prefix: string, middlewareOrCallback: Middleware[] | ((router: RouteGroup) => void), callback?: (router: RouteGroup) => void): this;
|
|
50
|
+
}
|
|
51
|
+
|
|
15
52
|
/**
|
|
16
|
-
*
|
|
53
|
+
* @nextrush/router - Route Metadata Contributions
|
|
54
|
+
*
|
|
55
|
+
* Inline route metadata (`endpoint()`) and the registration-time merge of
|
|
56
|
+
* contributions from `validate()`, `endpoint()`, etc. Extracted from `router.ts`
|
|
57
|
+
* (audit RT-3). None of this is on the request hot path — it is read only at
|
|
58
|
+
* registration and by `getRoutes()` at doc-generation time.
|
|
17
59
|
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
60
|
+
* @packageDocumentation
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Declare route metadata inline in a route's argument list. Returns a pure data
|
|
65
|
+
* marker (not a middleware) — the router reads its metadata at registration and
|
|
66
|
+
* never executes it per request.
|
|
21
67
|
*
|
|
22
68
|
* @example
|
|
23
69
|
* ```typescript
|
|
24
|
-
*
|
|
70
|
+
* router.post('/users',
|
|
71
|
+
* validate(User),
|
|
72
|
+
* endpoint({ summary: 'Create a user', responses: { 201: UserResponse } }),
|
|
73
|
+
* handler,
|
|
74
|
+
* );
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
declare function endpoint(metadata: MetadataContribution): RouteMetaMarker;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @nextrush/router - Router Implementation
|
|
25
81
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
82
|
+
* The public `Router` shell: a thin, chainable facade delegating registration,
|
|
83
|
+
* matching, dispatch, composition, and grouping to focused sibling modules
|
|
84
|
+
* (design.md D1/D2). Segment trie keyed by whole path segments, not a radix tree.
|
|
29
85
|
*
|
|
30
|
-
*
|
|
31
|
-
|
|
86
|
+
* @packageDocumentation
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* High-performance segment-trie router: O(d) lookup by path segment, with a
|
|
91
|
+
* static-route hash map for an O(1) fast path.
|
|
92
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md | @nextrush/router README}
|
|
32
93
|
*/
|
|
33
94
|
declare class Router {
|
|
34
95
|
private readonly root;
|
|
35
96
|
private readonly opts;
|
|
36
97
|
private readonly routerMiddleware;
|
|
98
|
+
/** Static-route fast path: method-nested map for O(1) lookup with no per-request key string (HP-9). */
|
|
99
|
+
private readonly staticRoutes;
|
|
37
100
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
101
|
+
* Introspection registry, kept SEPARATE from the hot-path trie/staticRoutes
|
|
102
|
+
* so request dispatch never reads metadata — only getRoutes() touches it.
|
|
40
103
|
*/
|
|
41
|
-
private readonly
|
|
104
|
+
private readonly routeDefinitions;
|
|
42
105
|
/** Whether any routes have params or wildcards (disables static-only fast path) */
|
|
43
106
|
private hasParamRoutes;
|
|
44
|
-
|
|
107
|
+
/** Whether router-level middleware has already been sealed into executors (audit RT-7) */
|
|
108
|
+
private _sealed;
|
|
45
109
|
/**
|
|
46
|
-
*
|
|
110
|
+
* Single-entry memo for {@link matchesMountPrefix}'s canonicalization of the
|
|
111
|
+
* mount prefix, which is fixed at registration time. Declared as two fields
|
|
112
|
+
* rather than an object so a miss stores without allocating.
|
|
47
113
|
*/
|
|
48
|
-
private
|
|
114
|
+
private _prefixMemoRaw;
|
|
115
|
+
private _prefixMemoCanonical;
|
|
116
|
+
/** Memoized state the extracted registration/matching functions read (see {@link createRouterState}). */
|
|
117
|
+
private readonly state;
|
|
118
|
+
constructor(options?: RouterOptions);
|
|
49
119
|
/**
|
|
50
|
-
*
|
|
120
|
+
* Validate + normalize a raw path, then delegate trie insertion to the
|
|
121
|
+
* extracted `addRoute` (design.md D2); flips `hasParamRoutes` from its return.
|
|
51
122
|
*/
|
|
52
123
|
private addRoute;
|
|
53
|
-
get(path: string, ...
|
|
54
|
-
post(path: string, ...
|
|
55
|
-
put(path: string, ...
|
|
56
|
-
delete(path: string, ...
|
|
57
|
-
patch(path: string, ...
|
|
58
|
-
head(path: string, ...
|
|
59
|
-
options(path: string, ...
|
|
60
|
-
all(path: string, ...handlers: RouteHandler[]): this;
|
|
61
|
-
route(method: HttpMethod, path: string, ...handlers: RouteHandler[]): this;
|
|
124
|
+
get(path: string, ...entries: RouteEntry[]): this;
|
|
125
|
+
post(path: string, ...entries: RouteEntry[]): this;
|
|
126
|
+
put(path: string, ...entries: RouteEntry[]): this;
|
|
127
|
+
delete(path: string, ...entries: RouteEntry[]): this;
|
|
128
|
+
patch(path: string, ...entries: RouteEntry[]): this;
|
|
129
|
+
head(path: string, ...entries: RouteEntry[]): this;
|
|
130
|
+
options(path: string, ...entries: RouteEntry[]): this;
|
|
62
131
|
/**
|
|
63
|
-
* Register a
|
|
64
|
-
*
|
|
65
|
-
* @param from - Source path to redirect from
|
|
66
|
-
* @param to - Target path or URL to redirect to
|
|
67
|
-
* @param status - HTTP status code (default: 301 permanent redirect)
|
|
68
|
-
* @returns this for chaining
|
|
69
|
-
*
|
|
70
|
-
* @example
|
|
71
|
-
* ```typescript
|
|
72
|
-
* // Permanent redirect (301)
|
|
73
|
-
* router.redirect('/old-page', '/new-page');
|
|
74
|
-
*
|
|
75
|
-
* // Temporary redirect (302)
|
|
76
|
-
* router.redirect('/temp', '/destination', 302);
|
|
77
|
-
*
|
|
78
|
-
* // Redirect to external URL
|
|
79
|
-
* router.redirect('/docs', 'https://docs.example.com');
|
|
80
|
-
*
|
|
81
|
-
* // With parameters - redirects /users/:id to /profiles/:id
|
|
82
|
-
* router.redirect('/users/:id', '/profiles/:id');
|
|
83
|
-
* ```
|
|
132
|
+
* Register a route for every HTTP method under one consolidated `isAnyMethod`
|
|
133
|
+
* introspection row (T016) — matching is unchanged (see {@link pushAnyMethodDefinition}).
|
|
84
134
|
*/
|
|
85
|
-
|
|
86
|
-
|
|
135
|
+
all(path: string, ...entries: RouteEntry[]): this;
|
|
136
|
+
route(method: HttpMethod, path: string, ...entries: RouteEntry[]): this;
|
|
87
137
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* This is the explicit API for mounting sub-routers.
|
|
91
|
-
* Equivalent to `router.use(path, subRouter)` but more semantic.
|
|
92
|
-
*
|
|
93
|
-
* @param path - Path prefix for the sub-router
|
|
94
|
-
* @param router - Router instance to mount
|
|
95
|
-
* @returns this for chaining
|
|
96
|
-
*
|
|
97
|
-
* @example
|
|
98
|
-
* ```typescript
|
|
99
|
-
* // Create modular routers
|
|
100
|
-
* const users = createRouter();
|
|
101
|
-
* users.get('/', listUsers);
|
|
102
|
-
* users.get('/:id', getUser);
|
|
103
|
-
*
|
|
104
|
-
* const posts = createRouter();
|
|
105
|
-
* posts.get('/', listPosts);
|
|
106
|
-
*
|
|
107
|
-
* // Mount sub-routers
|
|
108
|
-
* const api = createRouter();
|
|
109
|
-
* api.mount('/users', users);
|
|
110
|
-
* api.mount('/posts', posts);
|
|
111
|
-
*
|
|
112
|
-
* // Or use on main router
|
|
113
|
-
* const router = createRouter();
|
|
114
|
-
* router.mount('/api', api);
|
|
115
|
-
*
|
|
116
|
-
* app.use(router.routes());
|
|
117
|
-
* ```
|
|
138
|
+
* Every registered route as a read-only list, for renderers (`@nextrush/openapi`,
|
|
139
|
+
* SDK/RPC generators). Doc-generation-time only — never on the request path.
|
|
118
140
|
*/
|
|
119
|
-
|
|
141
|
+
getRoutes(): readonly RouteDefinition[];
|
|
120
142
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* `subrouter.use(mw)` middleware applies to every copied route.
|
|
143
|
+
* Register a redirect from one path to another (301 by default). 307/308
|
|
144
|
+
* additionally register POST/PUT/PATCH/DELETE to preserve the method.
|
|
145
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md#redirects | README: Redirects}
|
|
125
146
|
*/
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
* Recursively copy routes from another router
|
|
129
|
-
*/
|
|
130
|
-
private copyRoutes;
|
|
147
|
+
redirect(from: string, to: string, status?: RedirectStatus): this;
|
|
148
|
+
use(pathOrMiddleware: string | Middleware | Router, routerOrUndefined?: Router): this;
|
|
131
149
|
/**
|
|
132
|
-
*
|
|
150
|
+
* Mount a sub-router at a path prefix (Hono-style) — the explicit, more
|
|
151
|
+
* semantic equivalent of `router.use(path, subRouter)`.
|
|
152
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md#sub-router-mounting | README: Sub-Router Mounting}
|
|
133
153
|
*/
|
|
154
|
+
mount(path: string, router: Router): this;
|
|
155
|
+
/** Mount a sub-router, carrying its own `routerMiddleware` onto every copied route. */
|
|
156
|
+
private mountRouter;
|
|
157
|
+
/** Match a request to a route — delegates to {@link resolveMatch} (design.md D1). */
|
|
134
158
|
match(method: HttpMethod, path: string): RouteMatch | null;
|
|
135
159
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
*
|
|
142
|
-
*/
|
|
143
|
-
private matchNodeIndexed;
|
|
144
|
-
/**
|
|
145
|
-
* Get routes middleware function
|
|
146
|
-
* Mount this on the application
|
|
160
|
+
* Test whether `path` falls under `prefix` using this router's OWN
|
|
161
|
+
* canonicalization (case folding per `caseSensitive`, structural
|
|
162
|
+
* normalization) — the mount-boundary counterpart to {@link match}, so a
|
|
163
|
+
* router mounted via `Application.route()` is tested with the identical
|
|
164
|
+
* rule it dispatches with (RFC-029, task 3.8). Implements the optional
|
|
165
|
+
* `Routable.matchesMountPrefix` contract from `@nextrush/core`.
|
|
147
166
|
*
|
|
148
|
-
* @
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
167
|
+
* @param path - The full request path being tested for this mount.
|
|
168
|
+
* @param prefix - The normalized mount prefix (leading `/`, no trailing `/`).
|
|
169
|
+
* @returns The path's remainder past the prefix (e.g. `/users` for a
|
|
170
|
+
* `/ADMIN/Users` request mounted at `/admin`), or `undefined` when `path`
|
|
171
|
+
* is not under `prefix` per this router's canonicalization.
|
|
152
172
|
*/
|
|
153
|
-
|
|
173
|
+
matchesMountPrefix(path: string, prefix: string): string | undefined;
|
|
154
174
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
175
|
+
* Return the router's dispatch middleware — mount this on the application.
|
|
176
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md#routerroutes | README: router.routes()}
|
|
157
177
|
*/
|
|
158
|
-
|
|
178
|
+
routes(): Middleware;
|
|
159
179
|
/**
|
|
160
|
-
* Generate allowed
|
|
161
|
-
*
|
|
180
|
+
* Generate allowed-methods middleware. Responds to OPTIONS with an `Allow`
|
|
181
|
+
* header and returns 405 for a known path hit with an unregistered method.
|
|
162
182
|
*/
|
|
163
183
|
allowedMethods(): Middleware;
|
|
164
184
|
/**
|
|
165
|
-
*
|
|
166
|
-
* @
|
|
185
|
+
* Create a route group with a shared prefix and middleware. The callback
|
|
186
|
+
* receives a {@link RouteGroup} to register routes against.
|
|
187
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md#route-groups | README: Route Groups}
|
|
167
188
|
*/
|
|
168
|
-
|
|
189
|
+
group(prefix: string, middlewareOrCallback: Middleware[] | ((router: RouteGroup) => void), callback?: (router: RouteGroup) => void): this;
|
|
169
190
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
191
|
+
* Remove all routes and middleware, resetting the router to its initial
|
|
192
|
+
* state — for test isolation or hot-reload re-registration.
|
|
172
193
|
*/
|
|
173
|
-
|
|
194
|
+
reset(): void;
|
|
195
|
+
/** Register a route on behalf of a {@link RouteGroup} (group context). @internal */
|
|
196
|
+
_addGroupRoute(method: HttpMethod, path: string, handlers: RouteHandler[], groupMiddleware: Middleware[], recordIntrospection?: boolean): void;
|
|
174
197
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* @param prefix - Path prefix for all routes in the group
|
|
178
|
-
* @param middlewareOrCallback - Middleware array or callback function
|
|
179
|
-
* @param callback - Callback function if middleware is provided
|
|
180
|
-
* @returns this for chaining
|
|
181
|
-
*
|
|
182
|
-
* @example
|
|
183
|
-
* ```typescript
|
|
184
|
-
* // Simple group with prefix
|
|
185
|
-
* router.group('/api', (r) => {
|
|
186
|
-
* r.get('/users', listUsers);
|
|
187
|
-
* r.get('/posts', listPosts);
|
|
188
|
-
* });
|
|
189
|
-
*
|
|
190
|
-
* // Group with middleware
|
|
191
|
-
* router.group('/admin', [authMiddleware], (r) => {
|
|
192
|
-
* r.get('/dashboard', dashboard);
|
|
193
|
-
* r.post('/settings', updateSettings);
|
|
194
|
-
* });
|
|
195
|
-
*
|
|
196
|
-
* // Nested groups
|
|
197
|
-
* router.group('/api/v1', (r) => {
|
|
198
|
-
* r.group('/users', [rateLimit], (ur) => {
|
|
199
|
-
* ur.get('/', listUsers);
|
|
200
|
-
* ur.get('/:id', getUser);
|
|
201
|
-
* });
|
|
202
|
-
* });
|
|
203
|
-
* ```
|
|
198
|
+
* Group-facing entry point to {@link pushAnyMethodDefinition} — a group's `.all()`
|
|
199
|
+
* records its consolidated row here since group routes live on the parent. @internal
|
|
204
200
|
*/
|
|
205
|
-
|
|
201
|
+
_pushAnyMethodRouteDefinition(path: string): void;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Create a new {@link Router} instance.
|
|
205
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md#quick-start | README: Quick Start}
|
|
206
|
+
*/
|
|
207
|
+
declare function createRouter(options?: RouterOptions): Router;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @nextrush/router - Canonical Request Path
|
|
211
|
+
*
|
|
212
|
+
* The single normalization owner (RFC-029): every consumer that needs to know
|
|
213
|
+
* "what path does the router treat this request as" — the router's own match,
|
|
214
|
+
* a mounted-router prefix test, a CSRF exclude-path match, an adapter's
|
|
215
|
+
* published `ctx.path` — calls {@link canonicalizePath} instead of hand-rolling
|
|
216
|
+
* its own fold/collapse/strip, so there is exactly one definition of "the same
|
|
217
|
+
* path" across the framework (SEC-02, SEC-09, SEC-15).
|
|
218
|
+
*
|
|
219
|
+
* @see docs/RFC/request-data/029-canonical-request-path.md
|
|
220
|
+
* @packageDocumentation
|
|
221
|
+
*/
|
|
222
|
+
/** Result of {@link canonicalizePath}. */
|
|
223
|
+
interface CanonicalPathResult {
|
|
206
224
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
225
|
+
* `true` when the path contains a `.`/`..` path segment. A dot segment is
|
|
226
|
+
* rejected outright (400), never resolved — resolving it locally would
|
|
227
|
+
* diverge from how a front-end proxy already resolved (or didn't resolve)
|
|
228
|
+
* the same segment before forwarding, reopening the desync this RFC closes.
|
|
209
229
|
*/
|
|
210
|
-
|
|
230
|
+
readonly rejected: boolean;
|
|
211
231
|
/**
|
|
212
|
-
*
|
|
213
|
-
*
|
|
232
|
+
* The canonical path: query-stripped, dot-segment-free, case-folded (unless
|
|
233
|
+
* `caseSensitive`), slash-collapsed, trailing-slash-stripped per `strict`.
|
|
234
|
+
* Meaningless when {@link rejected} is `true`.
|
|
214
235
|
*/
|
|
215
|
-
|
|
236
|
+
readonly path: string;
|
|
216
237
|
}
|
|
217
238
|
/**
|
|
218
|
-
*
|
|
239
|
+
* True when `path` (already query-stripped) contains a `.` or `..` segment —
|
|
240
|
+
* a component that is exactly `.` or `..` between slash boundaries (or at the
|
|
241
|
+
* start/end of the string). A single linear scan, no backtracking regex
|
|
242
|
+
* (task 3.5): each character is visited at most once, so a pathological input
|
|
243
|
+
* cannot degrade to worse than O(n).
|
|
219
244
|
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
245
|
+
* A dot as filename content (`archive.tar.gz`, `..hidden.txt`) is NOT a dot
|
|
246
|
+
* segment — only a component whose entire text between slashes is `.` or `..`
|
|
247
|
+
* counts.
|
|
248
|
+
*/
|
|
249
|
+
declare function hasDotSegment(path: string): boolean;
|
|
250
|
+
/**
|
|
251
|
+
* Canonicalize a raw request target into the one path value every consumer
|
|
252
|
+
* (router match, mount-prefix test, CSRF exclude match, published `ctx.path`)
|
|
253
|
+
* treats as "this request's path" (RFC-029).
|
|
222
254
|
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
255
|
+
* Order: strip the query string, reject a dot segment before any further
|
|
256
|
+
* normalization (a dot segment is a request-shape violation, not something to
|
|
257
|
+
* fold case on first), then fold case (unless `caseSensitive`) and collapse
|
|
258
|
+
* structure exactly as {@link import('./matching').normalizePathForMatch} does
|
|
259
|
+
* — this function IS that normalization, extended with dot-segment rejection
|
|
260
|
+
* and made a public, adapter-facing entry point.
|
|
261
|
+
*
|
|
262
|
+
* The most recent result is memoized; see the note above the memo fields for
|
|
263
|
+
* why that is sound. Treat the returned object as immutable.
|
|
264
|
+
*
|
|
265
|
+
* @param rawTarget - The raw request target, may include a query string.
|
|
266
|
+
* @param caseSensitive - Router case-sensitivity option.
|
|
267
|
+
* @param strict - Router strict-trailing-slash option.
|
|
228
268
|
*/
|
|
229
|
-
declare function
|
|
269
|
+
declare function canonicalizePath(rawTarget: string, caseSensitive: boolean, strict: boolean): CanonicalPathResult;
|
|
230
270
|
|
|
231
271
|
/**
|
|
232
272
|
* @nextrush/router - Segment Trie Node
|
|
233
273
|
*
|
|
234
274
|
* Internal segment trie implementation for high-performance route matching.
|
|
235
|
-
*
|
|
275
|
+
* Segments are split by `/` and matched one trie level per segment for O(k)
|
|
276
|
+
* lookups where k is path length.
|
|
236
277
|
*
|
|
237
278
|
* @packageDocumentation
|
|
238
279
|
* @internal
|
|
@@ -250,23 +291,23 @@ declare const enum NodeType {
|
|
|
250
291
|
WILDCARD = 2
|
|
251
292
|
}
|
|
252
293
|
/**
|
|
253
|
-
*
|
|
294
|
+
* Segment trie node
|
|
254
295
|
*/
|
|
255
|
-
interface
|
|
296
|
+
interface TrieNode {
|
|
256
297
|
/** Path segment for this node */
|
|
257
298
|
segment: string;
|
|
258
299
|
/** Node type */
|
|
259
300
|
type: NodeType;
|
|
260
|
-
/**
|
|
261
|
-
children: Map<string,
|
|
301
|
+
/** Static child nodes keyed by whole path segment (e.g. `users`), not the first character */
|
|
302
|
+
children: Map<string, TrieNode>;
|
|
262
303
|
/** Parameter name if this is a param node */
|
|
263
304
|
paramName?: string;
|
|
264
305
|
/** Handlers keyed by HTTP method */
|
|
265
306
|
handlers: Map<HttpMethod, HandlerEntry>;
|
|
266
307
|
/** Wildcard child if any */
|
|
267
|
-
wildcardChild?:
|
|
308
|
+
wildcardChild?: TrieNode;
|
|
268
309
|
/** Parameter child if any */
|
|
269
|
-
paramChild?:
|
|
310
|
+
paramChild?: TrieNode;
|
|
270
311
|
}
|
|
271
312
|
/**
|
|
272
313
|
* Handler entry with middleware and pre-compiled executor
|
|
@@ -276,11 +317,19 @@ interface HandlerEntry {
|
|
|
276
317
|
middleware: Middleware[];
|
|
277
318
|
/** Pre-compiled executor for fast dispatch (no closure per request) */
|
|
278
319
|
executor?: (ctx: Context) => Promise<void>;
|
|
320
|
+
/**
|
|
321
|
+
* `true` only for a `HEAD` entry derived from a `GET` registration
|
|
322
|
+
* (RFC 9110 §9.3.2). A derived entry is replaced by an explicit `HEAD`
|
|
323
|
+
* registration instead of reporting a route conflict, is absent from
|
|
324
|
+
* `getRoutes()`, and is skipped when copying routes into a parent router —
|
|
325
|
+
* which re-derives it from the `GET` it copies.
|
|
326
|
+
*/
|
|
327
|
+
autoHead: boolean;
|
|
279
328
|
}
|
|
280
329
|
/**
|
|
281
|
-
* Create a new
|
|
330
|
+
* Create a new segment trie node
|
|
282
331
|
*/
|
|
283
|
-
declare function createNode(segment: string, type?: NodeType):
|
|
332
|
+
declare function createNode(segment: string, type?: NodeType): TrieNode;
|
|
284
333
|
/**
|
|
285
334
|
* Parse path segments
|
|
286
335
|
* Splits path into segments and identifies param/wildcard types
|
|
@@ -298,4 +347,4 @@ interface ParsedSegment {
|
|
|
298
347
|
paramName?: string;
|
|
299
348
|
}
|
|
300
349
|
|
|
301
|
-
export { type HandlerEntry, NodeType, type ParsedSegment, type
|
|
350
|
+
export { type CanonicalPathResult, type HandlerEntry, NodeType, type ParsedSegment, type RouteGroup, Router, type TrieNode, canonicalizePath, createNode, createRouter, endpoint, hasDotSegment, parseSegments };
|