@nextrush/router 3.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/LICENSE +21 -0
- package/README.md +618 -0
- package/dist/index.d.ts +301 -0
- package/dist/index.js +853 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
- package/src/__tests__/router-edge-cases.test.ts +486 -0
- package/src/__tests__/router.test.ts +621 -0
- package/src/index.ts +31 -0
- package/src/radix-tree.ts +184 -0
- package/src/router.ts +1029 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { RouterOptions, RouteHandler, HttpMethod, Middleware, RouteMatch, Context } from '@nextrush/types';
|
|
2
|
+
export { HttpMethod, Middleware, Route, RouteHandler, RouteMatch, Router as RouterInterface, RouterOptions } from '@nextrush/types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @nextrush/router - Router Implementation
|
|
6
|
+
*
|
|
7
|
+
* High-performance router using a segment trie for route matching.
|
|
8
|
+
* Routes are keyed by full path segments (e.g. "users", ":id"), not by
|
|
9
|
+
* individual characters — this is a segment-based trie, not a compressed
|
|
10
|
+
* radix tree. Supports parameters, wildcards, and method-based routing.
|
|
11
|
+
*
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Router class — high-performance segment trie router
|
|
17
|
+
*
|
|
18
|
+
* Routes are indexed by path segment, giving O(d) lookup where d is the
|
|
19
|
+
* number of segments. Static routes are additionally stored in a hash map
|
|
20
|
+
* for O(1) fast-path lookup.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const router = createRouter();
|
|
25
|
+
*
|
|
26
|
+
* router.get('/users', listUsers);
|
|
27
|
+
* router.get('/users/:id', getUser);
|
|
28
|
+
* router.post('/users', createUser);
|
|
29
|
+
*
|
|
30
|
+
* app.use(router.routes());
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
declare class Router {
|
|
34
|
+
private readonly root;
|
|
35
|
+
private readonly opts;
|
|
36
|
+
private readonly routerMiddleware;
|
|
37
|
+
/**
|
|
38
|
+
* Static route hash map for O(1) lookup.
|
|
39
|
+
* Key: "METHOD path" (e.g. "GET /users"), Value: HandlerEntry
|
|
40
|
+
*/
|
|
41
|
+
private readonly staticRoutes;
|
|
42
|
+
/** Whether any routes have params or wildcards (disables static-only fast path) */
|
|
43
|
+
private hasParamRoutes;
|
|
44
|
+
constructor(options?: RouterOptions);
|
|
45
|
+
/**
|
|
46
|
+
* Normalize path based on router options
|
|
47
|
+
*/
|
|
48
|
+
private normalizePath;
|
|
49
|
+
/**
|
|
50
|
+
* Add a route to the radix tree
|
|
51
|
+
*/
|
|
52
|
+
private addRoute;
|
|
53
|
+
get(path: string, ...handlers: RouteHandler[]): this;
|
|
54
|
+
post(path: string, ...handlers: RouteHandler[]): this;
|
|
55
|
+
put(path: string, ...handlers: RouteHandler[]): this;
|
|
56
|
+
delete(path: string, ...handlers: RouteHandler[]): this;
|
|
57
|
+
patch(path: string, ...handlers: RouteHandler[]): this;
|
|
58
|
+
head(path: string, ...handlers: RouteHandler[]): this;
|
|
59
|
+
options(path: string, ...handlers: RouteHandler[]): this;
|
|
60
|
+
all(path: string, ...handlers: RouteHandler[]): this;
|
|
61
|
+
route(method: HttpMethod, path: string, ...handlers: RouteHandler[]): this;
|
|
62
|
+
/**
|
|
63
|
+
* Register a redirect route from one path to another
|
|
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
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
redirect(from: string, to: string, status?: 301 | 302 | 303 | 307 | 308): this;
|
|
86
|
+
use(pathOrMiddleware: string | Middleware | Router, routerOrUndefined?: Router): this;
|
|
87
|
+
/**
|
|
88
|
+
* Mount a sub-router at a path prefix (Hono-style)
|
|
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
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
mount(path: string, router: Router): this;
|
|
120
|
+
/**
|
|
121
|
+
* Mount a sub-router (internal)
|
|
122
|
+
*
|
|
123
|
+
* Carries the sub-router's own `routerMiddleware` forward so that
|
|
124
|
+
* `subrouter.use(mw)` middleware applies to every copied route.
|
|
125
|
+
*/
|
|
126
|
+
private mountRouter;
|
|
127
|
+
/**
|
|
128
|
+
* Recursively copy routes from another router
|
|
129
|
+
*/
|
|
130
|
+
private copyRoutes;
|
|
131
|
+
/**
|
|
132
|
+
* Match a route and return handler + params
|
|
133
|
+
*/
|
|
134
|
+
match(method: HttpMethod, path: string): RouteMatch | null;
|
|
135
|
+
/**
|
|
136
|
+
* Extract the next segment from path at given position without allocating arrays.
|
|
137
|
+
* Returns [segment, nextIndex] where nextIndex is position after the trailing '/'.
|
|
138
|
+
*/
|
|
139
|
+
private extractSegment;
|
|
140
|
+
/**
|
|
141
|
+
* Index-based recursive node matching (avoids array allocation)
|
|
142
|
+
*/
|
|
143
|
+
private matchNodeIndexed;
|
|
144
|
+
/**
|
|
145
|
+
* Get routes middleware function
|
|
146
|
+
* Mount this on the application
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```typescript
|
|
150
|
+
* app.use(router.routes());
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
routes(): Middleware;
|
|
154
|
+
/**
|
|
155
|
+
* Re-compile all route executors to include router-level middleware.
|
|
156
|
+
* Called once when routes() is invoked, not per-request.
|
|
157
|
+
*/
|
|
158
|
+
private sealRouterMiddleware;
|
|
159
|
+
/**
|
|
160
|
+
* Generate allowed methods middleware
|
|
161
|
+
* Responds to OPTIONS and sets Allow header
|
|
162
|
+
*/
|
|
163
|
+
allowedMethods(): Middleware;
|
|
164
|
+
/**
|
|
165
|
+
* Find all HTTP methods registered for a given path via single tree walk
|
|
166
|
+
* @internal
|
|
167
|
+
*/
|
|
168
|
+
private findAllowedMethods;
|
|
169
|
+
/**
|
|
170
|
+
* Walk the tree to find the node matching a path (ignoring HTTP method)
|
|
171
|
+
* @internal
|
|
172
|
+
*/
|
|
173
|
+
private findNode;
|
|
174
|
+
/**
|
|
175
|
+
* Create a route group with shared prefix and middleware
|
|
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
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
group(prefix: string, middlewareOrCallback: Middleware[] | ((router: Router) => void), callback?: (router: Router) => void): this;
|
|
206
|
+
/**
|
|
207
|
+
* Remove all registered routes and middleware, resetting the router to its initial state.
|
|
208
|
+
* Useful for plugin `destroy()` to cleanly un-register routes.
|
|
209
|
+
*/
|
|
210
|
+
reset(): void;
|
|
211
|
+
/**
|
|
212
|
+
* Internal method to add route with group context
|
|
213
|
+
* @internal
|
|
214
|
+
*/
|
|
215
|
+
_addGroupRoute(method: HttpMethod, path: string, handlers: RouteHandler[], groupMiddleware: Middleware[]): void;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Create a new Router instance
|
|
219
|
+
*
|
|
220
|
+
* @param options - Router options
|
|
221
|
+
* @returns New Router instance
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* const router = createRouter();
|
|
226
|
+
* const apiRouter = createRouter({ prefix: '/api/v1' });
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
declare function createRouter(options?: RouterOptions): Router;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @nextrush/router - Segment Trie Node
|
|
233
|
+
*
|
|
234
|
+
* Internal segment trie implementation for high-performance route matching.
|
|
235
|
+
* Uses a compressed trie structure for O(k) lookups where k is path length.
|
|
236
|
+
*
|
|
237
|
+
* @packageDocumentation
|
|
238
|
+
* @internal
|
|
239
|
+
*/
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Node type enumeration
|
|
243
|
+
*/
|
|
244
|
+
declare const enum NodeType {
|
|
245
|
+
/** Static path segment: /users */
|
|
246
|
+
STATIC = 0,
|
|
247
|
+
/** Named parameter: /:id */
|
|
248
|
+
PARAM = 1,
|
|
249
|
+
/** Wildcard: /* */
|
|
250
|
+
WILDCARD = 2
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Radix tree node
|
|
254
|
+
*/
|
|
255
|
+
interface RadixNode {
|
|
256
|
+
/** Path segment for this node */
|
|
257
|
+
segment: string;
|
|
258
|
+
/** Node type */
|
|
259
|
+
type: NodeType;
|
|
260
|
+
/** Children nodes keyed by first character */
|
|
261
|
+
children: Map<string, RadixNode>;
|
|
262
|
+
/** Parameter name if this is a param node */
|
|
263
|
+
paramName?: string;
|
|
264
|
+
/** Handlers keyed by HTTP method */
|
|
265
|
+
handlers: Map<HttpMethod, HandlerEntry>;
|
|
266
|
+
/** Wildcard child if any */
|
|
267
|
+
wildcardChild?: RadixNode;
|
|
268
|
+
/** Parameter child if any */
|
|
269
|
+
paramChild?: RadixNode;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Handler entry with middleware and pre-compiled executor
|
|
273
|
+
*/
|
|
274
|
+
interface HandlerEntry {
|
|
275
|
+
handler: RouteHandler;
|
|
276
|
+
middleware: Middleware[];
|
|
277
|
+
/** Pre-compiled executor for fast dispatch (no closure per request) */
|
|
278
|
+
executor?: (ctx: Context) => Promise<void>;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Create a new radix node
|
|
282
|
+
*/
|
|
283
|
+
declare function createNode(segment: string, type?: NodeType): RadixNode;
|
|
284
|
+
/**
|
|
285
|
+
* Parse path segments
|
|
286
|
+
* Splits path into segments and identifies param/wildcard types
|
|
287
|
+
*
|
|
288
|
+
* @param path - Route path to parse
|
|
289
|
+
* @param caseSensitive - If false, lowercase static segments for case-insensitive matching
|
|
290
|
+
*/
|
|
291
|
+
declare function parseSegments(path: string, caseSensitive?: boolean): ParsedSegment[];
|
|
292
|
+
/**
|
|
293
|
+
* Parsed segment structure
|
|
294
|
+
*/
|
|
295
|
+
interface ParsedSegment {
|
|
296
|
+
segment: string;
|
|
297
|
+
type: NodeType;
|
|
298
|
+
paramName?: string;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export { type HandlerEntry, NodeType, type ParsedSegment, type RadixNode, Router, createNode, createRouter, parseSegments };
|