@nextrush/router 3.0.6 → 4.0.0-beta.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/README.md +250 -492
- package/dist/index.d.ts +142 -185
- package/dist/index.js +816 -665
- 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 +71 -0
- package/src/__tests__/dispatch-deasync.test.ts +312 -0
- package/src/__tests__/find-node-differential.test.ts +220 -0
- package/src/__tests__/fixtures/match-golden.json +556 -0
- package/src/__tests__/helpers/differential-corpus.ts +316 -0
- package/src/__tests__/match-differential.test.ts +46 -0
- package/src/__tests__/match-hotpath-guard.test.ts +71 -0
- package/src/__tests__/match-normalize-fastpath.test.ts +66 -0
- package/src/__tests__/match-safety.test.ts +177 -0
- package/src/__tests__/match-single-alloc.test.ts +84 -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 +59 -0
- package/src/__tests__/route-metadata.test.ts +172 -0
- package/src/__tests__/router-audit.test.ts +326 -0
- package/src/__tests__/router-edge-cases.test.ts +2 -2
- package/src/__tests__/router.test.ts +148 -2
- package/src/__tests__/static-map-reset.test.ts +50 -0
- package/src/composition.ts +75 -0
- package/src/constants.ts +25 -0
- package/src/dispatch.ts +117 -0
- package/src/find-node.ts +125 -0
- package/src/group-router.ts +208 -0
- package/src/index.ts +8 -5
- package/src/match-route.ts +178 -0
- package/src/matching.ts +240 -0
- package/src/middleware-adapter.ts +59 -0
- package/src/redirect.ts +97 -0
- package/src/registration.ts +291 -0
- package/src/route-metadata.ts +68 -0
- package/src/router.ts +150 -881
- package/src/segment-trie.ts +219 -0
- package/src/state.ts +52 -0
- package/src/radix-tree.ts +0 -184
package/dist/index.js
CHANGED
|
@@ -2,9 +2,9 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
4
|
// src/router.ts
|
|
5
|
-
import { HTTP_METHODS } from "@nextrush/types";
|
|
5
|
+
import { HTTP_METHODS as HTTP_METHODS3 } from "@nextrush/types";
|
|
6
6
|
|
|
7
|
-
// src/
|
|
7
|
+
// src/segment-trie.ts
|
|
8
8
|
var NodeType = /* @__PURE__ */ (function(NodeType2) {
|
|
9
9
|
NodeType2[NodeType2["STATIC"] = 0] = "STATIC";
|
|
10
10
|
NodeType2[NodeType2["PARAM"] = 1] = "PARAM";
|
|
@@ -16,43 +16,41 @@ var NOOP_NEXT = /* @__PURE__ */ __name(() => RESOLVED_PROMISE, "NOOP_NEXT");
|
|
|
16
16
|
function compileExecutor(handler, middleware) {
|
|
17
17
|
const len = middleware.length;
|
|
18
18
|
if (len === 0) {
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return async (ctx) => {
|
|
27
|
-
await mw(ctx, async () => {
|
|
28
|
-
await handler(ctx, NOOP_NEXT);
|
|
29
|
-
});
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
if (len === 2) {
|
|
33
|
-
const mw0 = middleware[0];
|
|
34
|
-
const mw1 = middleware[1];
|
|
35
|
-
if (mw0 === void 0 || mw1 === void 0) throw new Error("middleware length mismatch");
|
|
36
|
-
return async (ctx) => {
|
|
37
|
-
await mw0(ctx, async () => {
|
|
38
|
-
await mw1(ctx, async () => {
|
|
39
|
-
await handler(ctx, NOOP_NEXT);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
19
|
+
return (ctx) => {
|
|
20
|
+
if (ctx.setNext) ctx.setNext(NOOP_NEXT);
|
|
21
|
+
try {
|
|
22
|
+
return Promise.resolve(handler(ctx, NOOP_NEXT));
|
|
23
|
+
} catch (err) {
|
|
24
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
25
|
+
}
|
|
42
26
|
};
|
|
43
27
|
}
|
|
44
|
-
return
|
|
45
|
-
let index =
|
|
46
|
-
const dispatch = /* @__PURE__ */ __name(
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
28
|
+
return (ctx) => {
|
|
29
|
+
let index = -1;
|
|
30
|
+
const dispatch = /* @__PURE__ */ __name((i) => {
|
|
31
|
+
if (i <= index) {
|
|
32
|
+
return Promise.reject(new Error("next() called multiple times"));
|
|
33
|
+
}
|
|
34
|
+
index = i;
|
|
35
|
+
if (i < len) {
|
|
36
|
+
const mw = middleware[i];
|
|
37
|
+
if (mw === void 0) return Promise.reject(new Error("middleware length mismatch"));
|
|
38
|
+
const next = /* @__PURE__ */ __name(() => dispatch(i + 1), "next");
|
|
39
|
+
if (ctx.setNext) ctx.setNext(next);
|
|
40
|
+
try {
|
|
41
|
+
return Promise.resolve(mw(ctx, next));
|
|
42
|
+
} catch (err) {
|
|
43
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (ctx.setNext) ctx.setNext(NOOP_NEXT);
|
|
47
|
+
try {
|
|
48
|
+
return Promise.resolve(handler(ctx, NOOP_NEXT));
|
|
49
|
+
} catch (err) {
|
|
50
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
53
51
|
}
|
|
54
52
|
}, "dispatch");
|
|
55
|
-
|
|
53
|
+
return dispatch(0);
|
|
56
54
|
};
|
|
57
55
|
}
|
|
58
56
|
__name(compileExecutor, "compileExecutor");
|
|
@@ -65,6 +63,13 @@ function createNode(segment, type = 0) {
|
|
|
65
63
|
};
|
|
66
64
|
}
|
|
67
65
|
__name(createNode, "createNode");
|
|
66
|
+
function clearNode(node) {
|
|
67
|
+
node.children.clear();
|
|
68
|
+
node.handlers.clear();
|
|
69
|
+
node.paramChild = void 0;
|
|
70
|
+
node.wildcardChild = void 0;
|
|
71
|
+
}
|
|
72
|
+
__name(clearNode, "clearNode");
|
|
68
73
|
function parseSegments(path, caseSensitive = true) {
|
|
69
74
|
const normalized = path.startsWith("/") ? path.slice(1) : path;
|
|
70
75
|
if (normalized === "") return [];
|
|
@@ -95,749 +100,894 @@ function parseSegments(path, caseSensitive = true) {
|
|
|
95
100
|
}
|
|
96
101
|
__name(parseSegments, "parseSegments");
|
|
97
102
|
|
|
98
|
-
// src/router.ts
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
hasParamRoutes = false;
|
|
114
|
-
constructor(options = {}) {
|
|
115
|
-
this.root = createNode("");
|
|
116
|
-
this.opts = {
|
|
117
|
-
prefix: options.prefix ?? "",
|
|
118
|
-
caseSensitive: options.caseSensitive ?? false,
|
|
119
|
-
strict: options.strict ?? false
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Normalize path based on router options
|
|
124
|
-
*/
|
|
125
|
-
normalizePath(path) {
|
|
126
|
-
let prefix = this.opts.prefix;
|
|
127
|
-
if (prefix.endsWith("/") && path.startsWith("/")) {
|
|
128
|
-
prefix = prefix.slice(0, -1);
|
|
129
|
-
}
|
|
130
|
-
let normalized = prefix + path;
|
|
131
|
-
if (normalized.includes("//")) {
|
|
132
|
-
normalized = normalized.replace(/\/+/g, "/");
|
|
103
|
+
// src/group-router.ts
|
|
104
|
+
import { HTTP_METHODS } from "@nextrush/types";
|
|
105
|
+
|
|
106
|
+
// src/redirect.ts
|
|
107
|
+
function compileRedirectTarget(to) {
|
|
108
|
+
const parts = [];
|
|
109
|
+
let pos = 0;
|
|
110
|
+
let found = false;
|
|
111
|
+
while (pos < to.length) {
|
|
112
|
+
let idx = -1;
|
|
113
|
+
for (let i = pos; i < to.length; i++) {
|
|
114
|
+
if (to[i] === ":" && (i === 0 || to[i - 1] === "/") && i + 1 < to.length && to[i + 1] !== "/") {
|
|
115
|
+
idx = i;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
133
118
|
}
|
|
134
|
-
if (
|
|
135
|
-
|
|
119
|
+
if (idx === -1) break;
|
|
120
|
+
found = true;
|
|
121
|
+
parts.push(to.slice(pos, idx));
|
|
122
|
+
const end = to.indexOf("/", idx + 1);
|
|
123
|
+
if (end === -1) {
|
|
124
|
+
parts.push(to.slice(idx + 1));
|
|
125
|
+
pos = to.length;
|
|
126
|
+
} else {
|
|
127
|
+
parts.push(to.slice(idx + 1, end));
|
|
128
|
+
pos = end;
|
|
136
129
|
}
|
|
137
|
-
return normalized.startsWith("/") ? normalized : "/" + normalized;
|
|
138
130
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
node = node.paramChild;
|
|
158
|
-
} else if (seg.type === NodeType.WILDCARD) {
|
|
159
|
-
node.wildcardChild ??= createNode("*", NodeType.WILDCARD);
|
|
160
|
-
node = node.wildcardChild;
|
|
161
|
-
break;
|
|
131
|
+
if (found) {
|
|
132
|
+
parts.push(to.slice(pos));
|
|
133
|
+
return parts;
|
|
134
|
+
}
|
|
135
|
+
return void 0;
|
|
136
|
+
}
|
|
137
|
+
__name(compileRedirectTarget, "compileRedirectTarget");
|
|
138
|
+
function createRedirectHandler(to, status) {
|
|
139
|
+
const compiledParts = compileRedirectTarget(to);
|
|
140
|
+
return (ctx) => {
|
|
141
|
+
let targetPath;
|
|
142
|
+
if (compiledParts) {
|
|
143
|
+
const params = ctx.params;
|
|
144
|
+
const head = compiledParts[0];
|
|
145
|
+
if (head === void 0) {
|
|
146
|
+
targetPath = to;
|
|
162
147
|
} else {
|
|
163
|
-
|
|
164
|
-
let
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
148
|
+
let result = head;
|
|
149
|
+
for (let i = 1; i < compiledParts.length - 1; i += 2) {
|
|
150
|
+
const paramKey = compiledParts[i];
|
|
151
|
+
const tail = compiledParts[i + 1];
|
|
152
|
+
if (paramKey === void 0 || tail === void 0) break;
|
|
153
|
+
result += (params[paramKey] ?? "") + tail;
|
|
168
154
|
}
|
|
169
|
-
|
|
155
|
+
targetPath = result;
|
|
170
156
|
}
|
|
171
|
-
}
|
|
172
|
-
const combinedMiddleware = [
|
|
173
|
-
...middleware
|
|
174
|
-
];
|
|
175
|
-
const finalHandler = handlers[handlers.length - 1];
|
|
176
|
-
if (!finalHandler) {
|
|
177
|
-
throw new Error("At least one handler is required");
|
|
178
|
-
}
|
|
179
|
-
const inlineMiddleware = handlers.slice(0, -1);
|
|
180
|
-
for (const mw of inlineMiddleware) {
|
|
181
|
-
combinedMiddleware.push(mw);
|
|
182
|
-
}
|
|
183
|
-
const executor = compileExecutor(finalHandler, combinedMiddleware);
|
|
184
|
-
const entry = {
|
|
185
|
-
handler: finalHandler,
|
|
186
|
-
middleware: combinedMiddleware,
|
|
187
|
-
executor
|
|
188
|
-
};
|
|
189
|
-
if (node.handlers.has(method)) {
|
|
190
|
-
throw new Error(`Route conflict: ${method} ${normalized} is already registered. Remove the duplicate or use a different path.`);
|
|
191
|
-
}
|
|
192
|
-
node.handlers.set(method, entry);
|
|
193
|
-
const hasParams = segments.some((s) => s.type === NodeType.PARAM || s.type === NodeType.WILDCARD);
|
|
194
|
-
if (hasParams) {
|
|
195
|
-
this.hasParamRoutes = true;
|
|
196
157
|
} else {
|
|
197
|
-
|
|
198
|
-
this.staticRoutes.set(`${method} ${normalizedKey}`, entry);
|
|
158
|
+
targetPath = to;
|
|
199
159
|
}
|
|
160
|
+
ctx.status = status;
|
|
161
|
+
ctx.set("Location", targetPath);
|
|
162
|
+
ctx.body = "";
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
__name(createRedirectHandler, "createRedirectHandler");
|
|
166
|
+
|
|
167
|
+
// src/group-router.ts
|
|
168
|
+
var GroupRouter = class {
|
|
169
|
+
static {
|
|
170
|
+
__name(this, "GroupRouter");
|
|
171
|
+
}
|
|
172
|
+
parent;
|
|
173
|
+
prefix;
|
|
174
|
+
middleware;
|
|
175
|
+
constructor(parent, prefix, middleware) {
|
|
176
|
+
this.parent = parent;
|
|
177
|
+
this.prefix = prefix;
|
|
178
|
+
this.middleware = middleware;
|
|
179
|
+
}
|
|
180
|
+
fullPath(path) {
|
|
181
|
+
if (path === "/" || path === "") {
|
|
182
|
+
return this.prefix;
|
|
183
|
+
}
|
|
184
|
+
const cleanPrefix = this.prefix.endsWith("/") ? this.prefix.slice(0, -1) : this.prefix;
|
|
185
|
+
const cleanPath = path.startsWith("/") ? path : "/" + path;
|
|
186
|
+
return cleanPrefix + cleanPath;
|
|
200
187
|
}
|
|
201
|
-
// ===========================================================================
|
|
202
|
-
// HTTP Method Shortcuts
|
|
203
|
-
// ===========================================================================
|
|
204
188
|
get(path, ...handlers) {
|
|
205
|
-
this.
|
|
189
|
+
this.parent._addGroupRoute("GET", this.fullPath(path), handlers, this.middleware);
|
|
206
190
|
return this;
|
|
207
191
|
}
|
|
208
192
|
post(path, ...handlers) {
|
|
209
|
-
this.
|
|
193
|
+
this.parent._addGroupRoute("POST", this.fullPath(path), handlers, this.middleware);
|
|
210
194
|
return this;
|
|
211
195
|
}
|
|
212
196
|
put(path, ...handlers) {
|
|
213
|
-
this.
|
|
197
|
+
this.parent._addGroupRoute("PUT", this.fullPath(path), handlers, this.middleware);
|
|
214
198
|
return this;
|
|
215
199
|
}
|
|
216
200
|
delete(path, ...handlers) {
|
|
217
|
-
this.
|
|
201
|
+
this.parent._addGroupRoute("DELETE", this.fullPath(path), handlers, this.middleware);
|
|
218
202
|
return this;
|
|
219
203
|
}
|
|
220
204
|
patch(path, ...handlers) {
|
|
221
|
-
this.
|
|
205
|
+
this.parent._addGroupRoute("PATCH", this.fullPath(path), handlers, this.middleware);
|
|
222
206
|
return this;
|
|
223
207
|
}
|
|
224
208
|
head(path, ...handlers) {
|
|
225
|
-
this.
|
|
209
|
+
this.parent._addGroupRoute("HEAD", this.fullPath(path), handlers, this.middleware);
|
|
226
210
|
return this;
|
|
227
211
|
}
|
|
228
212
|
options(path, ...handlers) {
|
|
229
|
-
this.
|
|
213
|
+
this.parent._addGroupRoute("OPTIONS", this.fullPath(path), handlers, this.middleware);
|
|
230
214
|
return this;
|
|
231
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Register a route matching every HTTP method under a single introspection
|
|
218
|
+
* entry, mirroring `Router.all()` (T016) — a group's `.all()` must not
|
|
219
|
+
* regress to the pre-T016 one-row-per-method shape just because
|
|
220
|
+
* registration is routed through `_addGroupRoute` instead of `addRoute`
|
|
221
|
+
* directly.
|
|
222
|
+
*/
|
|
232
223
|
all(path, ...handlers) {
|
|
233
224
|
for (const method of HTTP_METHODS) {
|
|
234
|
-
this.
|
|
225
|
+
this.parent._addGroupRoute(method, this.fullPath(path), handlers, this.middleware, false);
|
|
235
226
|
}
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
route(method, path, ...handlers) {
|
|
239
|
-
this.addRoute(method, path, handlers);
|
|
227
|
+
this.parent._pushAnyMethodRouteDefinition(this.fullPath(path));
|
|
240
228
|
return this;
|
|
241
229
|
}
|
|
242
230
|
/**
|
|
243
|
-
* Register a redirect
|
|
244
|
-
*
|
|
245
|
-
* @param from - Source path to redirect from
|
|
246
|
-
* @param to - Target path or URL to redirect to
|
|
247
|
-
* @param status - HTTP status code (default: 301 permanent redirect)
|
|
248
|
-
* @returns this for chaining
|
|
249
|
-
*
|
|
250
|
-
* @example
|
|
251
|
-
* ```typescript
|
|
252
|
-
* // Permanent redirect (301)
|
|
253
|
-
* router.redirect('/old-page', '/new-page');
|
|
254
|
-
*
|
|
255
|
-
* // Temporary redirect (302)
|
|
256
|
-
* router.redirect('/temp', '/destination', 302);
|
|
257
|
-
*
|
|
258
|
-
* // Redirect to external URL
|
|
259
|
-
* router.redirect('/docs', 'https://docs.example.com');
|
|
260
|
-
*
|
|
261
|
-
* // With parameters - redirects /users/:id to /profiles/:id
|
|
262
|
-
* router.redirect('/users/:id', '/profiles/:id');
|
|
263
|
-
* ```
|
|
231
|
+
* Register a redirect within the group (uses the shared redirect handler,
|
|
232
|
+
* audit RT-4 — no more naive replaceAll param substitution).
|
|
264
233
|
*/
|
|
265
234
|
redirect(from, to, status = 301) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const parts = [];
|
|
269
|
-
let pos = 0;
|
|
270
|
-
let found = false;
|
|
271
|
-
while (pos < to.length) {
|
|
272
|
-
let idx = -1;
|
|
273
|
-
for (let i = pos; i < to.length; i++) {
|
|
274
|
-
if (to[i] === ":" && (i === 0 || to[i - 1] === "/") && i + 1 < to.length && to[i + 1] !== "/") {
|
|
275
|
-
idx = i;
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
if (idx === -1) break;
|
|
280
|
-
found = true;
|
|
281
|
-
parts.push(to.slice(pos, idx));
|
|
282
|
-
const end = to.indexOf("/", idx + 1);
|
|
283
|
-
if (end === -1) {
|
|
284
|
-
parts.push(to.slice(idx + 1));
|
|
285
|
-
pos = to.length;
|
|
286
|
-
} else {
|
|
287
|
-
parts.push(to.slice(idx + 1, end));
|
|
288
|
-
pos = end;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
if (found) {
|
|
292
|
-
parts.push(to.slice(pos));
|
|
293
|
-
compiledParts = parts;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
const redirectHandler = /* @__PURE__ */ __name((ctx) => {
|
|
297
|
-
let targetPath;
|
|
298
|
-
if (compiledParts) {
|
|
299
|
-
const params = ctx.params;
|
|
300
|
-
const parts = compiledParts;
|
|
301
|
-
const head = parts[0];
|
|
302
|
-
if (head === void 0) {
|
|
303
|
-
targetPath = to;
|
|
304
|
-
} else {
|
|
305
|
-
let result = head;
|
|
306
|
-
for (let i = 1; i < parts.length - 1; i += 2) {
|
|
307
|
-
const paramKey = parts[i];
|
|
308
|
-
const tail = parts[i + 1];
|
|
309
|
-
if (paramKey === void 0 || tail === void 0) break;
|
|
310
|
-
result += (params[paramKey] ?? "") + tail;
|
|
311
|
-
}
|
|
312
|
-
targetPath = result;
|
|
313
|
-
}
|
|
314
|
-
} else {
|
|
315
|
-
targetPath = to;
|
|
316
|
-
}
|
|
317
|
-
ctx.status = status;
|
|
318
|
-
ctx.set("Location", targetPath);
|
|
319
|
-
ctx.body = "";
|
|
320
|
-
}, "redirectHandler");
|
|
321
|
-
this.addRoute("GET", from, [
|
|
235
|
+
const redirectHandler = createRedirectHandler(to, status);
|
|
236
|
+
this.parent._addGroupRoute("GET", this.fullPath(from), [
|
|
322
237
|
redirectHandler
|
|
323
|
-
]);
|
|
324
|
-
this.
|
|
238
|
+
], this.middleware);
|
|
239
|
+
this.parent._addGroupRoute("HEAD", this.fullPath(from), [
|
|
325
240
|
redirectHandler
|
|
326
|
-
]);
|
|
327
|
-
if (status === 307 || status === 308) {
|
|
328
|
-
this.addRoute("POST", from, [
|
|
329
|
-
redirectHandler
|
|
330
|
-
]);
|
|
331
|
-
this.addRoute("PUT", from, [
|
|
332
|
-
redirectHandler
|
|
333
|
-
]);
|
|
334
|
-
this.addRoute("PATCH", from, [
|
|
335
|
-
redirectHandler
|
|
336
|
-
]);
|
|
337
|
-
this.addRoute("DELETE", from, [
|
|
338
|
-
redirectHandler
|
|
339
|
-
]);
|
|
340
|
-
}
|
|
341
|
-
return this;
|
|
342
|
-
}
|
|
343
|
-
// ===========================================================================
|
|
344
|
-
// Router Composition
|
|
345
|
-
// ===========================================================================
|
|
346
|
-
use(pathOrMiddleware, routerOrUndefined) {
|
|
347
|
-
if (typeof pathOrMiddleware === "function") {
|
|
348
|
-
this.routerMiddleware.push(pathOrMiddleware);
|
|
349
|
-
} else if (typeof pathOrMiddleware === "string" && routerOrUndefined instanceof _Router) {
|
|
350
|
-
this.mountRouter(pathOrMiddleware, routerOrUndefined);
|
|
351
|
-
} else if (typeof pathOrMiddleware === "string") {
|
|
352
|
-
throw new Error(`router.use('${pathOrMiddleware}', ...) requires a Router instance as the second argument. Use router.group(prefix, callback) for prefix-scoped middleware, or router.use(middlewareFn) to register middleware without a prefix.`);
|
|
353
|
-
} else if (pathOrMiddleware instanceof _Router) {
|
|
354
|
-
this.mountRouter("", pathOrMiddleware);
|
|
355
|
-
}
|
|
241
|
+
], this.middleware);
|
|
356
242
|
return this;
|
|
357
243
|
}
|
|
358
244
|
/**
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
* This is the explicit API for mounting sub-routers.
|
|
362
|
-
* Equivalent to `router.use(path, subRouter)` but more semantic.
|
|
363
|
-
*
|
|
364
|
-
* @param path - Path prefix for the sub-router
|
|
365
|
-
* @param router - Router instance to mount
|
|
366
|
-
* @returns this for chaining
|
|
367
|
-
*
|
|
368
|
-
* @example
|
|
369
|
-
* ```typescript
|
|
370
|
-
* // Create modular routers
|
|
371
|
-
* const users = createRouter();
|
|
372
|
-
* users.get('/', listUsers);
|
|
373
|
-
* users.get('/:id', getUser);
|
|
374
|
-
*
|
|
375
|
-
* const posts = createRouter();
|
|
376
|
-
* posts.get('/', listPosts);
|
|
377
|
-
*
|
|
378
|
-
* // Mount sub-routers
|
|
379
|
-
* const api = createRouter();
|
|
380
|
-
* api.mount('/users', users);
|
|
381
|
-
* api.mount('/posts', posts);
|
|
382
|
-
*
|
|
383
|
-
* // Or use on main router
|
|
384
|
-
* const router = createRouter();
|
|
385
|
-
* router.mount('/api', api);
|
|
386
|
-
*
|
|
387
|
-
* app.use(router.routes());
|
|
388
|
-
* ```
|
|
245
|
+
* Nested group support — combines this group's prefix + middleware with the
|
|
246
|
+
* nested group's.
|
|
389
247
|
*/
|
|
390
|
-
|
|
391
|
-
this.
|
|
248
|
+
group(prefix, middlewareOrCallback, callback) {
|
|
249
|
+
runRouteGroup(this.parent, this.fullPath(prefix), middlewareOrCallback, callback, this.middleware);
|
|
392
250
|
return this;
|
|
393
251
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
252
|
+
};
|
|
253
|
+
function runRouteGroup(host, prefix, middlewareOrCallback, callback, inheritedMiddleware = []) {
|
|
254
|
+
let middleware = [];
|
|
255
|
+
let cb;
|
|
256
|
+
if (Array.isArray(middlewareOrCallback)) {
|
|
257
|
+
middleware = middlewareOrCallback;
|
|
258
|
+
if (!callback) {
|
|
259
|
+
throw new Error("Callback function is required when providing middleware array");
|
|
260
|
+
}
|
|
261
|
+
cb = callback;
|
|
262
|
+
} else {
|
|
263
|
+
cb = middlewareOrCallback;
|
|
264
|
+
}
|
|
265
|
+
const combined = inheritedMiddleware.length > 0 ? [
|
|
266
|
+
...inheritedMiddleware,
|
|
267
|
+
...middleware
|
|
268
|
+
] : middleware;
|
|
269
|
+
cb(new GroupRouter(host, prefix, combined));
|
|
270
|
+
}
|
|
271
|
+
__name(runRouteGroup, "runRouteGroup");
|
|
272
|
+
|
|
273
|
+
// src/constants.ts
|
|
274
|
+
var EMPTY_PARAMS = Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
275
|
+
|
|
276
|
+
// src/matching.ts
|
|
277
|
+
function decodeParam(value, decode) {
|
|
278
|
+
if (!decode || !value.includes("%")) return value;
|
|
279
|
+
try {
|
|
280
|
+
return decodeURIComponent(value);
|
|
281
|
+
} catch {
|
|
282
|
+
return value;
|
|
402
283
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
284
|
+
}
|
|
285
|
+
__name(decodeParam, "decodeParam");
|
|
286
|
+
function segmentAt(path, start) {
|
|
287
|
+
const slashPos = path.indexOf("/", start);
|
|
288
|
+
return slashPos === -1 ? path.slice(start) : path.slice(start, slashPos);
|
|
289
|
+
}
|
|
290
|
+
__name(segmentAt, "segmentAt");
|
|
291
|
+
function isProvablyLowerAscii(path) {
|
|
292
|
+
for (let i = 0; i < path.length; i++) {
|
|
293
|
+
const c = path.charCodeAt(i);
|
|
294
|
+
if (c >= 65 && c <= 90 || c > 127) return false;
|
|
295
|
+
}
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
__name(isProvablyLowerAscii, "isProvablyLowerAscii");
|
|
299
|
+
function collapseAndStrip(path, strict) {
|
|
300
|
+
let normalized = path;
|
|
301
|
+
if (normalized.includes("//")) {
|
|
302
|
+
normalized = normalized.replace(/\/+/g, "/");
|
|
303
|
+
}
|
|
304
|
+
if (!strict && normalized.length > 1 && normalized.endsWith("/")) {
|
|
305
|
+
normalized = normalized.slice(0, -1);
|
|
306
|
+
}
|
|
307
|
+
return normalized;
|
|
308
|
+
}
|
|
309
|
+
__name(collapseAndStrip, "collapseAndStrip");
|
|
310
|
+
function normalizePathForMatch(path, caseSensitive, strict) {
|
|
311
|
+
const folded = caseSensitive || isProvablyLowerAscii(path) ? path : path.toLowerCase();
|
|
312
|
+
return collapseAndStrip(folded, strict);
|
|
313
|
+
}
|
|
314
|
+
__name(normalizePathForMatch, "normalizePathForMatch");
|
|
315
|
+
function matchNodeIndexed(root, path, startPos, bindNames, bindValues, method, decode, originalPath) {
|
|
316
|
+
const stack = [
|
|
317
|
+
{
|
|
318
|
+
node: root,
|
|
319
|
+
pos: startPos,
|
|
320
|
+
stage: 0,
|
|
321
|
+
seg: "",
|
|
322
|
+
next: 0,
|
|
323
|
+
bound: false
|
|
324
|
+
}
|
|
325
|
+
];
|
|
326
|
+
while (stack.length > 0) {
|
|
327
|
+
const frame = stack[stack.length - 1];
|
|
328
|
+
if (frame === void 0) break;
|
|
329
|
+
if (frame.stage === 0) {
|
|
330
|
+
if (frame.pos >= path.length) {
|
|
331
|
+
const handler = frame.node.handlers.get(method);
|
|
332
|
+
if (handler) return handler;
|
|
333
|
+
stack.pop();
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const slashPos = path.indexOf("/", frame.pos);
|
|
337
|
+
if (slashPos === -1) {
|
|
338
|
+
frame.seg = path.slice(frame.pos);
|
|
339
|
+
frame.next = path.length;
|
|
340
|
+
} else {
|
|
341
|
+
frame.seg = path.slice(frame.pos, slashPos);
|
|
342
|
+
frame.next = slashPos + 1;
|
|
343
|
+
}
|
|
344
|
+
if (frame.seg === "") {
|
|
345
|
+
const handler = frame.node.handlers.get(method);
|
|
346
|
+
if (handler) return handler;
|
|
347
|
+
stack.pop();
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
frame.stage = 1;
|
|
351
|
+
const staticChild = frame.node.children.get(frame.seg);
|
|
352
|
+
if (staticChild) {
|
|
353
|
+
stack.push({
|
|
354
|
+
node: staticChild,
|
|
355
|
+
pos: frame.next,
|
|
356
|
+
stage: 0,
|
|
357
|
+
seg: "",
|
|
358
|
+
next: 0,
|
|
359
|
+
bound: false
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (frame.stage === 1) {
|
|
365
|
+
frame.stage = 2;
|
|
366
|
+
const paramChild = frame.node.paramChild;
|
|
367
|
+
if (paramChild) {
|
|
368
|
+
const paramName = paramChild.paramName;
|
|
369
|
+
if (paramName === void 0) return null;
|
|
370
|
+
const value = originalPath !== void 0 ? decodeParam(segmentAt(originalPath, frame.pos), decode) : decodeParam(frame.seg, decode);
|
|
371
|
+
bindNames.push(paramName);
|
|
372
|
+
bindValues.push(value);
|
|
373
|
+
frame.bound = true;
|
|
374
|
+
stack.push({
|
|
375
|
+
node: paramChild,
|
|
376
|
+
pos: frame.next,
|
|
377
|
+
stage: 0,
|
|
378
|
+
seg: "",
|
|
379
|
+
next: 0,
|
|
380
|
+
bound: false
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
continue;
|
|
422
384
|
}
|
|
423
|
-
if (
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
], subRouterMiddleware);
|
|
385
|
+
if (frame.bound) {
|
|
386
|
+
bindNames.pop();
|
|
387
|
+
bindValues.pop();
|
|
388
|
+
frame.bound = false;
|
|
428
389
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
390
|
+
const wildcardChild = frame.node.wildcardChild;
|
|
391
|
+
if (wildcardChild) {
|
|
392
|
+
const src = originalPath ?? path;
|
|
393
|
+
bindNames.push("*");
|
|
394
|
+
bindValues.push(decodeParam(src.slice(frame.pos), decode));
|
|
395
|
+
const handler = wildcardChild.handlers.get(method);
|
|
396
|
+
if (handler) return handler;
|
|
397
|
+
bindNames.pop();
|
|
398
|
+
bindValues.pop();
|
|
434
399
|
}
|
|
400
|
+
stack.pop();
|
|
435
401
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const
|
|
452
|
-
const staticEntry = this.staticRoutes.get(staticKey);
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
__name(matchNodeIndexed, "matchNodeIndexed");
|
|
405
|
+
|
|
406
|
+
// src/match-route.ts
|
|
407
|
+
function matchRoute(method, rawPath, root, staticRoutes, hasParamRoutes, caseSensitive, strict, decode, routerMiddleware) {
|
|
408
|
+
let path = rawPath;
|
|
409
|
+
const queryIdx = path.indexOf("?");
|
|
410
|
+
if (queryIdx !== -1) path = path.slice(0, queryIdx);
|
|
411
|
+
const caseStable = caseSensitive || isProvablyLowerAscii(path);
|
|
412
|
+
const folded = caseStable ? path : path.toLowerCase();
|
|
413
|
+
const normalized = collapseAndStrip(folded, strict);
|
|
414
|
+
const methodMap = staticRoutes.get(method);
|
|
415
|
+
if (methodMap) {
|
|
416
|
+
const staticKey = normalized.length > 1 && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
417
|
+
const staticEntry = methodMap.get(staticKey);
|
|
453
418
|
if (staticEntry) {
|
|
454
419
|
return {
|
|
455
420
|
handler: staticEntry.handler,
|
|
456
421
|
params: EMPTY_PARAMS,
|
|
457
|
-
middleware:
|
|
422
|
+
middleware: routerMiddleware,
|
|
458
423
|
executor: staticEntry.executor
|
|
459
424
|
};
|
|
460
425
|
}
|
|
461
|
-
if (!this.hasParamRoutes) return null;
|
|
462
|
-
const params = {};
|
|
463
|
-
let originalPath;
|
|
464
|
-
if (isCaseInsensitive) {
|
|
465
|
-
originalPath = path;
|
|
466
|
-
if (originalPath.includes("//")) {
|
|
467
|
-
originalPath = originalPath.replace(/\/+/g, "/");
|
|
468
|
-
}
|
|
469
|
-
if (!this.opts.strict && originalPath.length > 1 && originalPath.endsWith("/")) {
|
|
470
|
-
originalPath = originalPath.slice(0, -1);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
const result = this.matchNodeIndexed(this.root, normalized, 1, params, method, originalPath);
|
|
474
|
-
if (!result) return null;
|
|
475
|
-
let hasParams = false;
|
|
476
|
-
for (const key of Object.keys(params)) {
|
|
477
|
-
if (params[key] === void 0) {
|
|
478
|
-
Reflect.deleteProperty(params, key);
|
|
479
|
-
} else {
|
|
480
|
-
hasParams = true;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
return {
|
|
484
|
-
handler: result.handler,
|
|
485
|
-
params: hasParams ? params : EMPTY_PARAMS,
|
|
486
|
-
middleware: this.routerMiddleware,
|
|
487
|
-
executor: result.executor
|
|
488
|
-
};
|
|
489
426
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
427
|
+
if (!hasParamRoutes) return null;
|
|
428
|
+
const originalPath = caseStable ? void 0 : collapseAndStrip(path, strict);
|
|
429
|
+
const bindNames = [];
|
|
430
|
+
const bindValues = [];
|
|
431
|
+
const entry = matchNodeIndexed(root, normalized, 1, bindNames, bindValues, method, decode, originalPath);
|
|
432
|
+
if (!entry) return null;
|
|
433
|
+
const count = bindNames.length;
|
|
434
|
+
let params;
|
|
435
|
+
if (count === 0) {
|
|
436
|
+
params = EMPTY_PARAMS;
|
|
437
|
+
} else {
|
|
438
|
+
params = /* @__PURE__ */ Object.create(null);
|
|
439
|
+
for (let i = 0; i < count; i++) {
|
|
440
|
+
const name = bindNames[i];
|
|
441
|
+
const value = bindValues[i];
|
|
442
|
+
if (name !== void 0 && value !== void 0) params[name] = value;
|
|
501
443
|
}
|
|
502
|
-
return [
|
|
503
|
-
path.slice(start, slashPos),
|
|
504
|
-
slashPos + 1
|
|
505
|
-
];
|
|
506
444
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
445
|
+
return {
|
|
446
|
+
handler: entry.handler,
|
|
447
|
+
params,
|
|
448
|
+
middleware: routerMiddleware,
|
|
449
|
+
executor: entry.executor
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
__name(matchRoute, "matchRoute");
|
|
453
|
+
function resolveMatch(state, hasParamRoutes, method, path) {
|
|
454
|
+
return matchRoute(method, path, state.root, state.staticRoutes, hasParamRoutes, state.caseSensitive, state.strict, state.decode, state.routerMiddleware);
|
|
455
|
+
}
|
|
456
|
+
__name(resolveMatch, "resolveMatch");
|
|
457
|
+
|
|
458
|
+
// src/composition.ts
|
|
459
|
+
function copyRoutes(node, prefix, segments, subRouterMiddleware, addRoute2) {
|
|
460
|
+
for (const [method, entry] of node.handlers) {
|
|
461
|
+
const path = prefix + "/" + segments.join("/");
|
|
462
|
+
const combined = subRouterMiddleware.length > 0 ? [
|
|
463
|
+
...subRouterMiddleware,
|
|
464
|
+
...entry.middleware
|
|
465
|
+
] : entry.middleware;
|
|
466
|
+
addRoute2(method, path || "/", [
|
|
467
|
+
entry.handler
|
|
468
|
+
], combined);
|
|
469
|
+
}
|
|
470
|
+
for (const [, child] of node.children) {
|
|
471
|
+
copyRoutes(child, prefix, [
|
|
472
|
+
...segments,
|
|
473
|
+
child.segment
|
|
474
|
+
], subRouterMiddleware, addRoute2);
|
|
475
|
+
}
|
|
476
|
+
if (node.paramChild) {
|
|
477
|
+
copyRoutes(node.paramChild, prefix, [
|
|
478
|
+
...segments,
|
|
479
|
+
node.paramChild.segment
|
|
480
|
+
], subRouterMiddleware, addRoute2);
|
|
481
|
+
}
|
|
482
|
+
if (node.wildcardChild) {
|
|
483
|
+
copyRoutes(node.wildcardChild, prefix, [
|
|
484
|
+
...segments,
|
|
485
|
+
"*"
|
|
486
|
+
], subRouterMiddleware, addRoute2);
|
|
540
487
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
488
|
+
}
|
|
489
|
+
__name(copyRoutes, "copyRoutes");
|
|
490
|
+
|
|
491
|
+
// src/middleware-adapter.ts
|
|
492
|
+
function sealRouterMiddleware(root, staticRoutes, routerMiddleware) {
|
|
493
|
+
const routerMw = [
|
|
494
|
+
...routerMiddleware
|
|
495
|
+
];
|
|
496
|
+
const walk = /* @__PURE__ */ __name((node) => {
|
|
497
|
+
for (const [method, entry] of node.handlers) {
|
|
498
|
+
const combinedMw = [
|
|
499
|
+
...routerMw,
|
|
500
|
+
...entry.middleware
|
|
501
|
+
];
|
|
502
|
+
entry.executor = compileExecutor(entry.handler, combinedMw);
|
|
503
|
+
node.handlers.set(method, entry);
|
|
557
504
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
await match.executor(ctx);
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
await match.handler(ctx, NOOP_NEXT);
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
/**
|
|
574
|
-
* Re-compile all route executors to include router-level middleware.
|
|
575
|
-
* Called once when routes() is invoked, not per-request.
|
|
576
|
-
*/
|
|
577
|
-
sealRouterMiddleware() {
|
|
578
|
-
const routerMw = [
|
|
579
|
-
...this.routerMiddleware
|
|
580
|
-
];
|
|
581
|
-
const walk = /* @__PURE__ */ __name((node) => {
|
|
582
|
-
for (const [method, entry] of node.handlers) {
|
|
583
|
-
const combinedMw = [
|
|
584
|
-
...routerMw,
|
|
585
|
-
...entry.middleware
|
|
586
|
-
];
|
|
587
|
-
entry.executor = compileExecutor(entry.handler, combinedMw);
|
|
588
|
-
node.handlers.set(method, entry);
|
|
589
|
-
}
|
|
590
|
-
for (const [, child] of node.children) {
|
|
591
|
-
walk(child);
|
|
592
|
-
}
|
|
593
|
-
if (node.paramChild) walk(node.paramChild);
|
|
594
|
-
if (node.wildcardChild) walk(node.wildcardChild);
|
|
595
|
-
}, "walk");
|
|
596
|
-
walk(this.root);
|
|
597
|
-
for (const [key, entry] of this.staticRoutes) {
|
|
505
|
+
for (const [, child] of node.children) {
|
|
506
|
+
walk(child);
|
|
507
|
+
}
|
|
508
|
+
if (node.paramChild) walk(node.paramChild);
|
|
509
|
+
if (node.wildcardChild) walk(node.wildcardChild);
|
|
510
|
+
}, "walk");
|
|
511
|
+
walk(root);
|
|
512
|
+
for (const [, methodMap] of staticRoutes) {
|
|
513
|
+
for (const [key, entry] of methodMap) {
|
|
598
514
|
const combinedMw = [
|
|
599
515
|
...routerMw,
|
|
600
516
|
...entry.middleware
|
|
601
517
|
];
|
|
602
518
|
entry.executor = compileExecutor(entry.handler, combinedMw);
|
|
603
|
-
|
|
519
|
+
methodMap.set(key, entry);
|
|
604
520
|
}
|
|
605
521
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
522
|
+
}
|
|
523
|
+
__name(sealRouterMiddleware, "sealRouterMiddleware");
|
|
524
|
+
|
|
525
|
+
// src/registration.ts
|
|
526
|
+
import { HTTP_METHODS as HTTP_METHODS2 } from "@nextrush/types";
|
|
527
|
+
|
|
528
|
+
// src/route-metadata.ts
|
|
529
|
+
import { ROUTE_METADATA } from "@nextrush/types";
|
|
530
|
+
function endpoint(metadata) {
|
|
531
|
+
return {
|
|
532
|
+
[ROUTE_METADATA]: metadata
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
__name(endpoint, "endpoint");
|
|
536
|
+
function mergeContributions(contributions) {
|
|
537
|
+
if (contributions.length === 0) return void 0;
|
|
538
|
+
const meta = {};
|
|
539
|
+
for (const c of contributions) {
|
|
540
|
+
if (c.summary !== void 0) meta.summary = c.summary;
|
|
541
|
+
if (c.description !== void 0) meta.description = c.description;
|
|
542
|
+
if (c.deprecated !== void 0) meta.deprecated = c.deprecated;
|
|
543
|
+
if (c.visibility !== void 0) meta.visibility = c.visibility;
|
|
544
|
+
if (c.tags !== void 0) meta.tags = c.tags;
|
|
545
|
+
if (c.request) meta.request = {
|
|
546
|
+
...meta.request,
|
|
547
|
+
...c.request
|
|
548
|
+
};
|
|
549
|
+
if (c.responses) meta.responses = {
|
|
550
|
+
...meta.responses,
|
|
551
|
+
...c.responses
|
|
627
552
|
};
|
|
628
553
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const node = this.findNode(this.root, segments, 0);
|
|
643
|
-
if (!node || node.handlers.size === 0) return [];
|
|
644
|
-
return Array.from(node.handlers.keys());
|
|
554
|
+
return meta;
|
|
555
|
+
}
|
|
556
|
+
__name(mergeContributions, "mergeContributions");
|
|
557
|
+
function readContribution(entry) {
|
|
558
|
+
return entry[ROUTE_METADATA];
|
|
559
|
+
}
|
|
560
|
+
__name(readContribution, "readContribution");
|
|
561
|
+
|
|
562
|
+
// src/registration.ts
|
|
563
|
+
function normalizeRegistrationPath(path, prefix, strict) {
|
|
564
|
+
let joinedPrefix = prefix;
|
|
565
|
+
if (joinedPrefix.endsWith("/") && path.startsWith("/")) {
|
|
566
|
+
joinedPrefix = joinedPrefix.slice(0, -1);
|
|
645
567
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
*/
|
|
650
|
-
findNode(node, segments, index) {
|
|
651
|
-
if (index === segments.length) {
|
|
652
|
-
return node;
|
|
653
|
-
}
|
|
654
|
-
const segment = segments[index];
|
|
655
|
-
if (segment === void 0) return null;
|
|
656
|
-
const staticChild = node.children.get(segment);
|
|
657
|
-
if (staticChild) {
|
|
658
|
-
const result = this.findNode(staticChild, segments, index + 1);
|
|
659
|
-
if (result) return result;
|
|
660
|
-
}
|
|
661
|
-
if (node.paramChild) {
|
|
662
|
-
const result = this.findNode(node.paramChild, segments, index + 1);
|
|
663
|
-
if (result) return result;
|
|
664
|
-
}
|
|
665
|
-
if (node.wildcardChild) {
|
|
666
|
-
return node.wildcardChild;
|
|
667
|
-
}
|
|
668
|
-
return null;
|
|
568
|
+
let normalized = joinedPrefix + path;
|
|
569
|
+
if (normalized.includes("//")) {
|
|
570
|
+
normalized = normalized.replace(/\/+/g, "/");
|
|
669
571
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
* r.get('/posts', listPosts);
|
|
687
|
-
* });
|
|
688
|
-
*
|
|
689
|
-
* // Group with middleware
|
|
690
|
-
* router.group('/admin', [authMiddleware], (r) => {
|
|
691
|
-
* r.get('/dashboard', dashboard);
|
|
692
|
-
* r.post('/settings', updateSettings);
|
|
693
|
-
* });
|
|
694
|
-
*
|
|
695
|
-
* // Nested groups
|
|
696
|
-
* router.group('/api/v1', (r) => {
|
|
697
|
-
* r.group('/users', [rateLimit], (ur) => {
|
|
698
|
-
* ur.get('/', listUsers);
|
|
699
|
-
* ur.get('/:id', getUser);
|
|
700
|
-
* });
|
|
701
|
-
* });
|
|
702
|
-
* ```
|
|
703
|
-
*/
|
|
704
|
-
group(prefix, middlewareOrCallback, callback) {
|
|
705
|
-
let middleware = [];
|
|
706
|
-
let cb;
|
|
707
|
-
if (Array.isArray(middlewareOrCallback)) {
|
|
708
|
-
middleware = middlewareOrCallback;
|
|
709
|
-
if (!callback) {
|
|
710
|
-
throw new Error("Callback function is required when providing middleware array");
|
|
572
|
+
if (!strict && normalized.length > 1 && normalized.endsWith("/")) {
|
|
573
|
+
normalized = normalized.slice(0, -1);
|
|
574
|
+
}
|
|
575
|
+
return normalized.startsWith("/") ? normalized : "/" + normalized;
|
|
576
|
+
}
|
|
577
|
+
__name(normalizeRegistrationPath, "normalizeRegistrationPath");
|
|
578
|
+
function addRoute(method, normalized, entries, middleware, state, recordIntrospection = true) {
|
|
579
|
+
const segments = parseSegments(normalized, state.caseSensitive);
|
|
580
|
+
let node = state.root;
|
|
581
|
+
for (const seg of segments) {
|
|
582
|
+
if (seg.type === NodeType.PARAM) {
|
|
583
|
+
if (!node.paramChild) {
|
|
584
|
+
node.paramChild = createNode(seg.segment, NodeType.PARAM);
|
|
585
|
+
node.paramChild.paramName = seg.paramName;
|
|
586
|
+
} else if (node.paramChild.paramName !== seg.paramName) {
|
|
587
|
+
throw new Error(`Route param name conflict at "${normalized}": ":${String(seg.paramName)}" conflicts with existing ":${String(node.paramChild.paramName)}" at the same position. Use the same param name for this segment across all routes.`);
|
|
711
588
|
}
|
|
712
|
-
|
|
589
|
+
node = node.paramChild;
|
|
590
|
+
} else if (seg.type === NodeType.WILDCARD) {
|
|
591
|
+
node.wildcardChild ??= createNode("*", NodeType.WILDCARD);
|
|
592
|
+
node = node.wildcardChild;
|
|
593
|
+
break;
|
|
713
594
|
} else {
|
|
714
|
-
|
|
595
|
+
const key = seg.segment;
|
|
596
|
+
let child = node.children.get(key);
|
|
597
|
+
if (!child) {
|
|
598
|
+
child = createNode(seg.segment, NodeType.STATIC);
|
|
599
|
+
node.children.set(key, child);
|
|
600
|
+
}
|
|
601
|
+
node = child;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
const functions = [];
|
|
605
|
+
const contributions = [];
|
|
606
|
+
for (const routeEntry of entries) {
|
|
607
|
+
const contribution = readContribution(routeEntry);
|
|
608
|
+
if (contribution) contributions.push(contribution);
|
|
609
|
+
if (typeof routeEntry === "function") functions.push(routeEntry);
|
|
610
|
+
}
|
|
611
|
+
const combinedMiddleware = [
|
|
612
|
+
...middleware
|
|
613
|
+
];
|
|
614
|
+
const finalHandler = functions[functions.length - 1];
|
|
615
|
+
if (!finalHandler) {
|
|
616
|
+
throw new Error("At least one handler is required");
|
|
617
|
+
}
|
|
618
|
+
for (let i = 0; i < functions.length - 1; i++) {
|
|
619
|
+
const fn = functions[i];
|
|
620
|
+
if (fn) combinedMiddleware.push(fn);
|
|
621
|
+
}
|
|
622
|
+
const executor = compileExecutor(finalHandler, combinedMiddleware);
|
|
623
|
+
const handlerEntry = {
|
|
624
|
+
handler: finalHandler,
|
|
625
|
+
middleware: combinedMiddleware,
|
|
626
|
+
executor
|
|
627
|
+
};
|
|
628
|
+
if (node.handlers.has(method)) {
|
|
629
|
+
throw new Error(`Route conflict: ${method} ${normalized} is already registered. Remove the duplicate or use a different path.`);
|
|
630
|
+
}
|
|
631
|
+
node.handlers.set(method, handlerEntry);
|
|
632
|
+
const hasParams = segments.some((s) => s.type === NodeType.PARAM || s.type === NodeType.WILDCARD);
|
|
633
|
+
if (!hasParams) {
|
|
634
|
+
const normalizedKey = state.caseSensitive ? normalized : normalized.toLowerCase();
|
|
635
|
+
let methodMap = state.staticRoutes.get(method);
|
|
636
|
+
if (!methodMap) {
|
|
637
|
+
methodMap = /* @__PURE__ */ new Map();
|
|
638
|
+
state.staticRoutes.set(method, methodMap);
|
|
639
|
+
}
|
|
640
|
+
methodMap.set(normalizedKey, handlerEntry);
|
|
641
|
+
}
|
|
642
|
+
if (recordIntrospection) {
|
|
643
|
+
state.routeDefinitions.push({
|
|
644
|
+
key: `${method} ${normalized}`,
|
|
645
|
+
method,
|
|
646
|
+
path: normalized,
|
|
647
|
+
metadata: mergeContributions(contributions)
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
return hasParams;
|
|
651
|
+
}
|
|
652
|
+
__name(addRoute, "addRoute");
|
|
653
|
+
function registerRedirect(from, to, status, addRoute2) {
|
|
654
|
+
const redirectHandler = createRedirectHandler(to, status);
|
|
655
|
+
addRoute2("GET", from, [
|
|
656
|
+
redirectHandler
|
|
657
|
+
]);
|
|
658
|
+
addRoute2("HEAD", from, [
|
|
659
|
+
redirectHandler
|
|
660
|
+
]);
|
|
661
|
+
if (status === 307 || status === 308) {
|
|
662
|
+
addRoute2("POST", from, [
|
|
663
|
+
redirectHandler
|
|
664
|
+
]);
|
|
665
|
+
addRoute2("PUT", from, [
|
|
666
|
+
redirectHandler
|
|
667
|
+
]);
|
|
668
|
+
addRoute2("PATCH", from, [
|
|
669
|
+
redirectHandler
|
|
670
|
+
]);
|
|
671
|
+
addRoute2("DELETE", from, [
|
|
672
|
+
redirectHandler
|
|
673
|
+
]);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
__name(registerRedirect, "registerRedirect");
|
|
677
|
+
function pushAnyMethodDefinition(routeDefinitions, normalized) {
|
|
678
|
+
routeDefinitions.push({
|
|
679
|
+
key: `${HTTP_METHODS2[0]} ${normalized}`,
|
|
680
|
+
method: HTTP_METHODS2[0],
|
|
681
|
+
path: normalized,
|
|
682
|
+
isAnyMethod: true
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
__name(pushAnyMethodDefinition, "pushAnyMethodDefinition");
|
|
686
|
+
|
|
687
|
+
// src/find-node.ts
|
|
688
|
+
function findNode(root, path, startPos) {
|
|
689
|
+
const stack = [
|
|
690
|
+
{
|
|
691
|
+
node: root,
|
|
692
|
+
pos: startPos,
|
|
693
|
+
stage: 0,
|
|
694
|
+
next: 0
|
|
695
|
+
}
|
|
696
|
+
];
|
|
697
|
+
while (stack.length > 0) {
|
|
698
|
+
const frame = stack[stack.length - 1];
|
|
699
|
+
if (frame === void 0) break;
|
|
700
|
+
if (frame.stage === 0) {
|
|
701
|
+
if (frame.pos >= path.length) {
|
|
702
|
+
return frame.node;
|
|
703
|
+
}
|
|
704
|
+
const seg = segmentAt(path, frame.pos);
|
|
705
|
+
if (seg === "") {
|
|
706
|
+
return frame.node;
|
|
707
|
+
}
|
|
708
|
+
const segEnd = frame.pos + seg.length;
|
|
709
|
+
frame.next = segEnd < path.length ? segEnd + 1 : path.length;
|
|
710
|
+
frame.stage = 1;
|
|
711
|
+
const staticChild = frame.node.children.get(seg);
|
|
712
|
+
if (staticChild) {
|
|
713
|
+
stack.push({
|
|
714
|
+
node: staticChild,
|
|
715
|
+
pos: frame.next,
|
|
716
|
+
stage: 0,
|
|
717
|
+
next: 0
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
if (frame.stage === 1) {
|
|
723
|
+
frame.stage = 2;
|
|
724
|
+
if (frame.node.paramChild) {
|
|
725
|
+
stack.push({
|
|
726
|
+
node: frame.node.paramChild,
|
|
727
|
+
pos: frame.next,
|
|
728
|
+
stage: 0,
|
|
729
|
+
next: 0
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
continue;
|
|
715
733
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
734
|
+
if (frame.node.wildcardChild) {
|
|
735
|
+
return frame.node.wildcardChild;
|
|
736
|
+
}
|
|
737
|
+
stack.pop();
|
|
738
|
+
}
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
__name(findNode, "findNode");
|
|
742
|
+
function findAllowedMethods(path, root, caseSensitive, strict) {
|
|
743
|
+
const normalized = normalizePathForMatch(path, caseSensitive, strict);
|
|
744
|
+
const node = findNode(root, normalized, 1);
|
|
745
|
+
if (!node || node.handlers.size === 0) return [];
|
|
746
|
+
return Array.from(node.handlers.keys());
|
|
747
|
+
}
|
|
748
|
+
__name(findAllowedMethods, "findAllowedMethods");
|
|
749
|
+
|
|
750
|
+
// src/dispatch.ts
|
|
751
|
+
var RESOLVED = Promise.resolve();
|
|
752
|
+
function createRoutesMiddleware(match) {
|
|
753
|
+
return (ctx, next) => {
|
|
754
|
+
const routeMatch = match(ctx.method, ctx.path);
|
|
755
|
+
if (!routeMatch) {
|
|
756
|
+
ctx.status = 404;
|
|
757
|
+
return next ? next() : RESOLVED;
|
|
758
|
+
}
|
|
759
|
+
ctx.params = routeMatch.params;
|
|
760
|
+
return routeMatch.executor ? routeMatch.executor(ctx) : (
|
|
761
|
+
// or thenable return still yields a Promise<void> and never a sync throw.
|
|
762
|
+
Promise.resolve(routeMatch.handler(ctx, NOOP_NEXT))
|
|
763
|
+
);
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
__name(createRoutesMiddleware, "createRoutesMiddleware");
|
|
767
|
+
function createAllowedMethodsMiddleware(root, caseSensitive, strict) {
|
|
768
|
+
return async (ctx, next) => {
|
|
769
|
+
if (next) {
|
|
770
|
+
await next();
|
|
771
|
+
}
|
|
772
|
+
if (ctx.status !== 404) return;
|
|
773
|
+
const allowed = findAllowedMethods(ctx.path, root, caseSensitive, strict);
|
|
774
|
+
if (allowed.length === 0) return;
|
|
775
|
+
const allowHeader = allowed.join(", ");
|
|
776
|
+
if (ctx.method === "OPTIONS") {
|
|
777
|
+
ctx.status = 200;
|
|
778
|
+
ctx.set("Allow", allowHeader);
|
|
779
|
+
ctx.body = "";
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
ctx.status = 405;
|
|
783
|
+
ctx.set("Allow", allowHeader);
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
__name(createAllowedMethodsMiddleware, "createAllowedMethodsMiddleware");
|
|
787
|
+
|
|
788
|
+
// src/state.ts
|
|
789
|
+
function resolveRouterOptions(options) {
|
|
790
|
+
return {
|
|
791
|
+
prefix: options.prefix ?? "",
|
|
792
|
+
caseSensitive: options.caseSensitive ?? false,
|
|
793
|
+
strict: options.strict ?? false,
|
|
794
|
+
decode: options.decode ?? true
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
__name(resolveRouterOptions, "resolveRouterOptions");
|
|
798
|
+
function createRouterState(root, opts, staticRoutes, routeDefinitions, routerMiddleware) {
|
|
799
|
+
return {
|
|
800
|
+
root,
|
|
801
|
+
staticRoutes,
|
|
802
|
+
routeDefinitions,
|
|
803
|
+
caseSensitive: opts.caseSensitive,
|
|
804
|
+
strict: opts.strict,
|
|
805
|
+
decode: opts.decode,
|
|
806
|
+
routerMiddleware
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
__name(createRouterState, "createRouterState");
|
|
810
|
+
|
|
811
|
+
// src/router.ts
|
|
812
|
+
var Router = class _Router {
|
|
813
|
+
static {
|
|
814
|
+
__name(this, "Router");
|
|
719
815
|
}
|
|
816
|
+
root;
|
|
817
|
+
opts;
|
|
818
|
+
routerMiddleware = [];
|
|
819
|
+
/** Static-route fast path: method-nested map for O(1) lookup with no per-request key string (HP-9). */
|
|
820
|
+
staticRoutes = /* @__PURE__ */ new Map();
|
|
720
821
|
/**
|
|
721
|
-
*
|
|
722
|
-
*
|
|
822
|
+
* Introspection registry, kept SEPARATE from the hot-path trie/staticRoutes
|
|
823
|
+
* so request dispatch never reads metadata — only getRoutes() touches it.
|
|
723
824
|
*/
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
825
|
+
routeDefinitions = [];
|
|
826
|
+
/** Whether any routes have params or wildcards (disables static-only fast path) */
|
|
827
|
+
hasParamRoutes = false;
|
|
828
|
+
/** Whether router-level middleware has already been sealed into executors (audit RT-7) */
|
|
829
|
+
_sealed = false;
|
|
830
|
+
/** Memoized state the extracted registration/matching functions read (see {@link createRouterState}). */
|
|
831
|
+
state;
|
|
832
|
+
constructor(options = {}) {
|
|
833
|
+
this.root = createNode("");
|
|
834
|
+
this.opts = resolveRouterOptions(options);
|
|
835
|
+
this.state = createRouterState(this.root, this.opts, this.staticRoutes, this.routeDefinitions, this.routerMiddleware);
|
|
732
836
|
}
|
|
733
837
|
/**
|
|
734
|
-
*
|
|
735
|
-
*
|
|
838
|
+
* Validate + normalize a raw path, then delegate trie insertion to the
|
|
839
|
+
* extracted `addRoute` (design.md D2); flips `hasParamRoutes` from its return.
|
|
736
840
|
*/
|
|
737
|
-
|
|
738
|
-
|
|
841
|
+
addRoute(method, path, entries, middleware = [], recordIntrospection = true) {
|
|
842
|
+
const rawPath = path;
|
|
843
|
+
if (typeof rawPath !== "string") {
|
|
844
|
+
throw new TypeError(`Route path must be a string, received ${rawPath === null ? "null" : typeof rawPath}.`);
|
|
845
|
+
}
|
|
846
|
+
const normalized = normalizeRegistrationPath(path, this.opts.prefix, this.opts.strict);
|
|
847
|
+
if (addRoute(method, normalized, entries, middleware, this.state, recordIntrospection)) {
|
|
848
|
+
this.hasParamRoutes = true;
|
|
849
|
+
}
|
|
739
850
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
__name(this, "GroupRouter");
|
|
851
|
+
get(path, ...entries) {
|
|
852
|
+
this.addRoute("GET", path, entries);
|
|
853
|
+
return this;
|
|
744
854
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
constructor(parent, prefix, middleware) {
|
|
749
|
-
this.parent = parent;
|
|
750
|
-
this.prefix = prefix;
|
|
751
|
-
this.middleware = middleware;
|
|
855
|
+
post(path, ...entries) {
|
|
856
|
+
this.addRoute("POST", path, entries);
|
|
857
|
+
return this;
|
|
752
858
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
}
|
|
757
|
-
const cleanPrefix = this.prefix.endsWith("/") ? this.prefix.slice(0, -1) : this.prefix;
|
|
758
|
-
const cleanPath = path.startsWith("/") ? path : "/" + path;
|
|
759
|
-
return cleanPrefix + cleanPath;
|
|
859
|
+
put(path, ...entries) {
|
|
860
|
+
this.addRoute("PUT", path, entries);
|
|
861
|
+
return this;
|
|
760
862
|
}
|
|
761
|
-
|
|
762
|
-
this.
|
|
863
|
+
delete(path, ...entries) {
|
|
864
|
+
this.addRoute("DELETE", path, entries);
|
|
763
865
|
return this;
|
|
764
866
|
}
|
|
765
|
-
|
|
766
|
-
this.
|
|
867
|
+
patch(path, ...entries) {
|
|
868
|
+
this.addRoute("PATCH", path, entries);
|
|
767
869
|
return this;
|
|
768
870
|
}
|
|
769
|
-
|
|
770
|
-
this.
|
|
871
|
+
head(path, ...entries) {
|
|
872
|
+
this.addRoute("HEAD", path, entries);
|
|
771
873
|
return this;
|
|
772
874
|
}
|
|
773
|
-
|
|
774
|
-
this.
|
|
875
|
+
options(path, ...entries) {
|
|
876
|
+
this.addRoute("OPTIONS", path, entries);
|
|
775
877
|
return this;
|
|
776
878
|
}
|
|
777
|
-
|
|
778
|
-
|
|
879
|
+
/**
|
|
880
|
+
* Register a route for every HTTP method under one consolidated `isAnyMethod`
|
|
881
|
+
* introspection row (T016) — matching is unchanged (see {@link pushAnyMethodDefinition}).
|
|
882
|
+
*/
|
|
883
|
+
all(path, ...entries) {
|
|
884
|
+
for (const method of HTTP_METHODS3) {
|
|
885
|
+
this.addRoute(method, path, entries, [], false);
|
|
886
|
+
}
|
|
887
|
+
pushAnyMethodDefinition(this.routeDefinitions, normalizeRegistrationPath(path, this.opts.prefix, this.opts.strict));
|
|
779
888
|
return this;
|
|
780
889
|
}
|
|
781
|
-
|
|
782
|
-
this.
|
|
890
|
+
route(method, path, ...entries) {
|
|
891
|
+
this.addRoute(method, path, entries);
|
|
783
892
|
return this;
|
|
784
893
|
}
|
|
785
|
-
|
|
786
|
-
|
|
894
|
+
/**
|
|
895
|
+
* Every registered route as a read-only list, for renderers (`@nextrush/openapi`,
|
|
896
|
+
* SDK/RPC generators). Doc-generation-time only — never on the request path.
|
|
897
|
+
*/
|
|
898
|
+
getRoutes() {
|
|
899
|
+
return this.routeDefinitions;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Register a redirect from one path to another (301 by default). 307/308
|
|
903
|
+
* additionally register POST/PUT/PATCH/DELETE to preserve the method.
|
|
904
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md#redirects | README: Redirects}
|
|
905
|
+
*/
|
|
906
|
+
redirect(from, to, status = 301) {
|
|
907
|
+
registerRedirect(from, to, status, (method, path, entries) => {
|
|
908
|
+
this.addRoute(method, path, entries);
|
|
909
|
+
});
|
|
787
910
|
return this;
|
|
788
911
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
this.
|
|
912
|
+
use(pathOrMiddleware, routerOrUndefined) {
|
|
913
|
+
if (typeof pathOrMiddleware === "function") {
|
|
914
|
+
this.routerMiddleware.push(pathOrMiddleware);
|
|
915
|
+
} else if (typeof pathOrMiddleware === "string" && routerOrUndefined instanceof _Router) {
|
|
916
|
+
this.mountRouter(pathOrMiddleware, routerOrUndefined);
|
|
917
|
+
} else if (typeof pathOrMiddleware === "string") {
|
|
918
|
+
throw new Error(`router.use('${pathOrMiddleware}', ...) requires a Router instance as the second argument. Use router.group(prefix, callback) for prefix-scoped middleware, or router.use(middlewareFn) to register middleware without a prefix.`);
|
|
919
|
+
} else if (pathOrMiddleware instanceof _Router) {
|
|
920
|
+
this.mountRouter("", pathOrMiddleware);
|
|
792
921
|
}
|
|
793
922
|
return this;
|
|
794
923
|
}
|
|
795
924
|
/**
|
|
796
|
-
*
|
|
925
|
+
* Mount a sub-router at a path prefix (Hono-style) — the explicit, more
|
|
926
|
+
* semantic equivalent of `router.use(path, subRouter)`.
|
|
927
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md#sub-router-mounting | README: Sub-Router Mounting}
|
|
797
928
|
*/
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
let targetPath = to;
|
|
801
|
-
if (targetPath.includes(":")) {
|
|
802
|
-
const entries = Object.entries(ctx.params).sort((a, b) => b[0].length - a[0].length);
|
|
803
|
-
for (const [key, value] of entries) {
|
|
804
|
-
targetPath = targetPath.replaceAll(`:${key}`, value);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
ctx.status = status;
|
|
808
|
-
ctx.set("Location", targetPath);
|
|
809
|
-
ctx.body = "";
|
|
810
|
-
}, "redirectHandler");
|
|
811
|
-
this.parent._addGroupRoute("GET", this.fullPath(from), [
|
|
812
|
-
redirectHandler
|
|
813
|
-
], this.middleware);
|
|
814
|
-
this.parent._addGroupRoute("HEAD", this.fullPath(from), [
|
|
815
|
-
redirectHandler
|
|
816
|
-
], this.middleware);
|
|
929
|
+
mount(path, router) {
|
|
930
|
+
this.mountRouter(path, router);
|
|
817
931
|
return this;
|
|
818
932
|
}
|
|
933
|
+
/** Mount a sub-router, carrying its own `routerMiddleware` onto every copied route. */
|
|
934
|
+
mountRouter(prefix, router) {
|
|
935
|
+
copyRoutes(router.root, prefix, [], router.routerMiddleware, this.addRoute.bind(this));
|
|
936
|
+
}
|
|
937
|
+
/** Match a request to a route — delegates to {@link resolveMatch} (design.md D1). */
|
|
938
|
+
match(method, path) {
|
|
939
|
+
return resolveMatch(this.state, this.hasParamRoutes, method, path);
|
|
940
|
+
}
|
|
819
941
|
/**
|
|
820
|
-
*
|
|
942
|
+
* Return the router's dispatch middleware — mount this on the application.
|
|
943
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md#routerroutes | README: router.routes()}
|
|
821
944
|
*/
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
nestedMiddleware = middlewareOrCallback;
|
|
827
|
-
if (!callback) {
|
|
828
|
-
throw new Error("Callback function is required when providing middleware array");
|
|
829
|
-
}
|
|
830
|
-
cb = callback;
|
|
831
|
-
} else {
|
|
832
|
-
cb = middlewareOrCallback;
|
|
945
|
+
routes() {
|
|
946
|
+
if (this.routerMiddleware.length > 0 && !this._sealed) {
|
|
947
|
+
this._sealed = true;
|
|
948
|
+
sealRouterMiddleware(this.root, this.staticRoutes, this.routerMiddleware);
|
|
833
949
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
950
|
+
return createRoutesMiddleware((method, path) => this.match(method, path));
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Generate allowed-methods middleware. Responds to OPTIONS with an `Allow`
|
|
954
|
+
* header and returns 405 for a known path hit with an unregistered method.
|
|
955
|
+
*/
|
|
956
|
+
allowedMethods() {
|
|
957
|
+
return createAllowedMethodsMiddleware(this.root, this.opts.caseSensitive, this.opts.strict);
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Create a route group with a shared prefix and middleware. The callback
|
|
961
|
+
* receives a {@link RouteGroup} to register routes against.
|
|
962
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md#route-groups | README: Route Groups}
|
|
963
|
+
*/
|
|
964
|
+
group(prefix, middlewareOrCallback, callback) {
|
|
965
|
+
runRouteGroup(this, prefix, middlewareOrCallback, callback);
|
|
839
966
|
return this;
|
|
840
967
|
}
|
|
968
|
+
/**
|
|
969
|
+
* Remove all routes and middleware, resetting the router to its initial
|
|
970
|
+
* state — for test isolation or hot-reload re-registration.
|
|
971
|
+
*/
|
|
972
|
+
reset() {
|
|
973
|
+
clearNode(this.root);
|
|
974
|
+
this.staticRoutes.clear();
|
|
975
|
+
this.routerMiddleware.length = 0;
|
|
976
|
+
this.hasParamRoutes = false;
|
|
977
|
+
this.routeDefinitions.length = 0;
|
|
978
|
+
this._sealed = false;
|
|
979
|
+
}
|
|
980
|
+
/** Register a route on behalf of a {@link RouteGroup} (group context). @internal */
|
|
981
|
+
_addGroupRoute(method, path, handlers, groupMiddleware, recordIntrospection = true) {
|
|
982
|
+
this.addRoute(method, path, handlers, groupMiddleware, recordIntrospection);
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Group-facing entry point to {@link pushAnyMethodDefinition} — a group's `.all()`
|
|
986
|
+
* records its consolidated row here since group routes live on the parent. @internal
|
|
987
|
+
*/
|
|
988
|
+
_pushAnyMethodRouteDefinition(path) {
|
|
989
|
+
pushAnyMethodDefinition(this.routeDefinitions, normalizeRegistrationPath(path, this.opts.prefix, this.opts.strict));
|
|
990
|
+
}
|
|
841
991
|
};
|
|
842
992
|
function createRouter(options) {
|
|
843
993
|
return new Router(options);
|
|
@@ -848,6 +998,7 @@ export {
|
|
|
848
998
|
Router,
|
|
849
999
|
createNode,
|
|
850
1000
|
createRouter,
|
|
1001
|
+
endpoint,
|
|
851
1002
|
parseSegments
|
|
852
1003
|
};
|
|
853
1004
|
//# sourceMappingURL=index.js.map
|