@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.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,1159 @@ 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
|
-
|
|
158
|
+
targetPath = to;
|
|
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;
|
|
199
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
|
}
|
|
227
|
+
this.parent._pushAnyMethodRouteDefinition(this.fullPath(path));
|
|
236
228
|
return this;
|
|
237
229
|
}
|
|
238
|
-
|
|
239
|
-
|
|
230
|
+
/**
|
|
231
|
+
* Register a redirect within the group (uses the shared redirect handler,
|
|
232
|
+
* audit RT-4 — no more naive replaceAll param substitution).
|
|
233
|
+
*/
|
|
234
|
+
redirect(from, to, status = 301) {
|
|
235
|
+
const redirectHandler = createRedirectHandler(to, status);
|
|
236
|
+
this.parent._addGroupRoute("GET", this.fullPath(from), [
|
|
237
|
+
redirectHandler
|
|
238
|
+
], this.middleware);
|
|
239
|
+
this.parent._addGroupRoute("HEAD", this.fullPath(from), [
|
|
240
|
+
redirectHandler
|
|
241
|
+
], this.middleware);
|
|
240
242
|
return this;
|
|
241
243
|
}
|
|
242
244
|
/**
|
|
243
|
-
*
|
|
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
|
-
* ```
|
|
245
|
+
* Nested group support — combines this group's prefix + middleware with the
|
|
246
|
+
* nested group's.
|
|
264
247
|
*/
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
248
|
+
group(prefix, middlewareOrCallback, callback) {
|
|
249
|
+
runRouteGroup(this.parent, this.fullPath(prefix), middlewareOrCallback, callback, this.middleware);
|
|
250
|
+
return this;
|
|
251
|
+
}
|
|
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 NULL_PROTO = /* @__PURE__ */ Object.create(null);
|
|
275
|
+
var EMPTY_PARAMS = Object.freeze(Object.create(NULL_PROTO));
|
|
276
|
+
|
|
277
|
+
// src/walk-pool.ts
|
|
278
|
+
function createWalkPool(maxDepth) {
|
|
279
|
+
const frames = [];
|
|
280
|
+
for (let i = 0; i < maxDepth + 1; i++) {
|
|
281
|
+
frames.push({
|
|
282
|
+
node: void 0,
|
|
283
|
+
pos: 0,
|
|
284
|
+
stage: 0,
|
|
285
|
+
seg: "",
|
|
286
|
+
next: 0,
|
|
287
|
+
bound: false
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
frames,
|
|
292
|
+
bindNames: [],
|
|
293
|
+
bindValues: []
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
__name(createWalkPool, "createWalkPool");
|
|
297
|
+
function matchNodeIndexedPooled(root, path, startPos, bindNames, bindValues, method, decode, pool, originalPath) {
|
|
298
|
+
const { frames } = pool;
|
|
299
|
+
let depth = 0;
|
|
300
|
+
const first = frames[0];
|
|
301
|
+
if (first === void 0) return null;
|
|
302
|
+
first.node = root;
|
|
303
|
+
first.pos = startPos;
|
|
304
|
+
first.stage = 0;
|
|
305
|
+
first.seg = "";
|
|
306
|
+
first.next = 0;
|
|
307
|
+
first.bound = false;
|
|
308
|
+
while (depth >= 0) {
|
|
309
|
+
const frame = frames[depth];
|
|
310
|
+
if (frame === void 0) break;
|
|
311
|
+
if (frame.stage === 0) {
|
|
312
|
+
if (frame.pos >= path.length) {
|
|
313
|
+
const handler = frame.node.handlers.get(method);
|
|
314
|
+
if (handler) return handler;
|
|
315
|
+
depth--;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const slashPos = path.indexOf("/", frame.pos);
|
|
319
|
+
if (slashPos === -1) {
|
|
320
|
+
frame.seg = path.slice(frame.pos);
|
|
321
|
+
frame.next = path.length;
|
|
322
|
+
} else {
|
|
323
|
+
frame.seg = path.slice(frame.pos, slashPos);
|
|
324
|
+
frame.next = slashPos + 1;
|
|
325
|
+
}
|
|
326
|
+
if (frame.seg === "") {
|
|
327
|
+
const handler = frame.node.handlers.get(method);
|
|
328
|
+
if (handler) return handler;
|
|
329
|
+
depth--;
|
|
330
|
+
continue;
|
|
290
331
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
332
|
+
frame.stage = 1;
|
|
333
|
+
const staticChild = frame.node.children.get(frame.seg);
|
|
334
|
+
if (staticChild) {
|
|
335
|
+
depth++;
|
|
336
|
+
const next = frames[depth];
|
|
337
|
+
if (next === void 0) {
|
|
338
|
+
depth--;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
next.node = staticChild;
|
|
342
|
+
next.pos = frame.next;
|
|
343
|
+
next.stage = 0;
|
|
344
|
+
next.seg = "";
|
|
345
|
+
next.next = 0;
|
|
346
|
+
next.bound = false;
|
|
294
347
|
}
|
|
348
|
+
continue;
|
|
295
349
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
350
|
+
if (frame.stage === 1) {
|
|
351
|
+
frame.stage = 2;
|
|
352
|
+
const paramChild = frame.node.paramChild;
|
|
353
|
+
if (paramChild) {
|
|
354
|
+
const paramName = paramChild.paramName;
|
|
355
|
+
if (paramName === void 0) return null;
|
|
356
|
+
const value = originalPath !== void 0 ? decodeParam(segmentAt(originalPath, frame.pos), decode) : decodeParam(frame.seg, decode);
|
|
357
|
+
bindNames.push(paramName);
|
|
358
|
+
bindValues.push(value);
|
|
359
|
+
frame.bound = true;
|
|
360
|
+
depth++;
|
|
361
|
+
const next = frames[depth];
|
|
362
|
+
if (next === void 0) {
|
|
363
|
+
bindNames.pop();
|
|
364
|
+
bindValues.pop();
|
|
365
|
+
frame.bound = false;
|
|
366
|
+
depth--;
|
|
367
|
+
continue;
|
|
313
368
|
}
|
|
314
|
-
|
|
315
|
-
|
|
369
|
+
next.node = paramChild;
|
|
370
|
+
next.pos = frame.next;
|
|
371
|
+
next.stage = 0;
|
|
372
|
+
next.seg = "";
|
|
373
|
+
next.next = 0;
|
|
374
|
+
next.bound = false;
|
|
316
375
|
}
|
|
317
|
-
|
|
318
|
-
ctx.set("Location", targetPath);
|
|
319
|
-
ctx.body = "";
|
|
320
|
-
}, "redirectHandler");
|
|
321
|
-
this.addRoute("GET", from, [
|
|
322
|
-
redirectHandler
|
|
323
|
-
]);
|
|
324
|
-
this.addRoute("HEAD", from, [
|
|
325
|
-
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
|
-
]);
|
|
376
|
+
continue;
|
|
340
377
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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);
|
|
378
|
+
if (frame.bound) {
|
|
379
|
+
bindNames.pop();
|
|
380
|
+
bindValues.pop();
|
|
381
|
+
frame.bound = false;
|
|
355
382
|
}
|
|
356
|
-
|
|
383
|
+
const wildcardChild = frame.node.wildcardChild;
|
|
384
|
+
if (wildcardChild) {
|
|
385
|
+
const src = originalPath ?? path;
|
|
386
|
+
bindNames.push("*");
|
|
387
|
+
bindValues.push(decodeParam(src.slice(frame.pos), decode));
|
|
388
|
+
const handler = wildcardChild.handlers.get(method);
|
|
389
|
+
if (handler) return handler;
|
|
390
|
+
bindNames.pop();
|
|
391
|
+
bindValues.pop();
|
|
392
|
+
}
|
|
393
|
+
depth--;
|
|
357
394
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
* ```
|
|
389
|
-
*/
|
|
390
|
-
mount(path, router) {
|
|
391
|
-
this.mountRouter(path, router);
|
|
392
|
-
return this;
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
__name(matchNodeIndexedPooled, "matchNodeIndexedPooled");
|
|
398
|
+
|
|
399
|
+
// src/matching.ts
|
|
400
|
+
function decodeParam(value, decode) {
|
|
401
|
+
if (!decode || !value.includes("%")) return value;
|
|
402
|
+
try {
|
|
403
|
+
return decodeURIComponent(value);
|
|
404
|
+
} catch {
|
|
405
|
+
return value;
|
|
393
406
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
407
|
+
}
|
|
408
|
+
__name(decodeParam, "decodeParam");
|
|
409
|
+
function segmentAt(path, start) {
|
|
410
|
+
const slashPos = path.indexOf("/", start);
|
|
411
|
+
return slashPos === -1 ? path.slice(start) : path.slice(start, slashPos);
|
|
412
|
+
}
|
|
413
|
+
__name(segmentAt, "segmentAt");
|
|
414
|
+
function isProvablyLowerAscii(path) {
|
|
415
|
+
for (let i = 0; i < path.length; i++) {
|
|
416
|
+
const c = path.charCodeAt(i);
|
|
417
|
+
if (c >= 65 && c <= 90 || c > 127) return false;
|
|
402
418
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
__name(isProvablyLowerAscii, "isProvablyLowerAscii");
|
|
422
|
+
function collapseAndStrip(path, strict) {
|
|
423
|
+
let normalized = path;
|
|
424
|
+
if (normalized.includes("//")) {
|
|
425
|
+
normalized = normalized.replace(/\/+/g, "/");
|
|
426
|
+
}
|
|
427
|
+
if (!strict && normalized.length > 1 && normalized.endsWith("/")) {
|
|
428
|
+
normalized = normalized.slice(0, -1);
|
|
429
|
+
}
|
|
430
|
+
return normalized;
|
|
431
|
+
}
|
|
432
|
+
__name(collapseAndStrip, "collapseAndStrip");
|
|
433
|
+
function normalizePathForMatch(path, caseSensitive, strict) {
|
|
434
|
+
const folded = caseSensitive || isProvablyLowerAscii(path) ? path : path.toLowerCase();
|
|
435
|
+
return collapseAndStrip(folded, strict);
|
|
436
|
+
}
|
|
437
|
+
__name(normalizePathForMatch, "normalizePathForMatch");
|
|
438
|
+
function matchNodeIndexed(root, path, startPos, bindNames, bindValues, method, decode, originalPath, pool) {
|
|
439
|
+
if (pool) {
|
|
440
|
+
return matchNodeIndexedPooled(root, path, startPos, bindNames, bindValues, method, decode, pool, originalPath);
|
|
441
|
+
}
|
|
442
|
+
const stack = [
|
|
443
|
+
{
|
|
444
|
+
node: root,
|
|
445
|
+
pos: startPos,
|
|
446
|
+
stage: 0,
|
|
447
|
+
seg: "",
|
|
448
|
+
next: 0,
|
|
449
|
+
bound: false
|
|
416
450
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
451
|
+
];
|
|
452
|
+
while (stack.length > 0) {
|
|
453
|
+
const frame = stack[stack.length - 1];
|
|
454
|
+
if (frame === void 0) break;
|
|
455
|
+
if (frame.stage === 0) {
|
|
456
|
+
if (frame.pos >= path.length) {
|
|
457
|
+
const handler = frame.node.handlers.get(method);
|
|
458
|
+
if (handler) return handler;
|
|
459
|
+
stack.pop();
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
const slashPos = path.indexOf("/", frame.pos);
|
|
463
|
+
if (slashPos === -1) {
|
|
464
|
+
frame.seg = path.slice(frame.pos);
|
|
465
|
+
frame.next = path.length;
|
|
466
|
+
} else {
|
|
467
|
+
frame.seg = path.slice(frame.pos, slashPos);
|
|
468
|
+
frame.next = slashPos + 1;
|
|
469
|
+
}
|
|
470
|
+
if (frame.seg === "") {
|
|
471
|
+
const handler = frame.node.handlers.get(method);
|
|
472
|
+
if (handler) return handler;
|
|
473
|
+
stack.pop();
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
frame.stage = 1;
|
|
477
|
+
const staticChild = frame.node.children.get(frame.seg);
|
|
478
|
+
if (staticChild) {
|
|
479
|
+
stack.push({
|
|
480
|
+
node: staticChild,
|
|
481
|
+
pos: frame.next,
|
|
482
|
+
stage: 0,
|
|
483
|
+
seg: "",
|
|
484
|
+
next: 0,
|
|
485
|
+
bound: false
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
continue;
|
|
447
489
|
}
|
|
448
|
-
if (
|
|
449
|
-
|
|
490
|
+
if (frame.stage === 1) {
|
|
491
|
+
frame.stage = 2;
|
|
492
|
+
const paramChild = frame.node.paramChild;
|
|
493
|
+
if (paramChild) {
|
|
494
|
+
const paramName = paramChild.paramName;
|
|
495
|
+
if (paramName === void 0) return null;
|
|
496
|
+
const value = originalPath !== void 0 ? decodeParam(segmentAt(originalPath, frame.pos), decode) : decodeParam(frame.seg, decode);
|
|
497
|
+
bindNames.push(paramName);
|
|
498
|
+
bindValues.push(value);
|
|
499
|
+
frame.bound = true;
|
|
500
|
+
stack.push({
|
|
501
|
+
node: paramChild,
|
|
502
|
+
pos: frame.next,
|
|
503
|
+
stage: 0,
|
|
504
|
+
seg: "",
|
|
505
|
+
next: 0,
|
|
506
|
+
bound: false
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (frame.bound) {
|
|
512
|
+
bindNames.pop();
|
|
513
|
+
bindValues.pop();
|
|
514
|
+
frame.bound = false;
|
|
515
|
+
}
|
|
516
|
+
const wildcardChild = frame.node.wildcardChild;
|
|
517
|
+
if (wildcardChild) {
|
|
518
|
+
const src = originalPath ?? path;
|
|
519
|
+
bindNames.push("*");
|
|
520
|
+
bindValues.push(decodeParam(src.slice(frame.pos), decode));
|
|
521
|
+
const handler = wildcardChild.handlers.get(method);
|
|
522
|
+
if (handler) return handler;
|
|
523
|
+
bindNames.pop();
|
|
524
|
+
bindValues.pop();
|
|
450
525
|
}
|
|
451
|
-
|
|
452
|
-
|
|
526
|
+
stack.pop();
|
|
527
|
+
}
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
__name(matchNodeIndexed, "matchNodeIndexed");
|
|
531
|
+
|
|
532
|
+
// src/match-route.ts
|
|
533
|
+
function matchRoute(method, rawPath, root, staticRoutes, hasParamRoutes, caseSensitive, strict, decode, routerMiddleware, preNormalized = false, walkPool) {
|
|
534
|
+
let path = rawPath;
|
|
535
|
+
if (!preNormalized) {
|
|
536
|
+
const queryIdx = path.indexOf("?");
|
|
537
|
+
if (queryIdx !== -1) path = path.slice(0, queryIdx);
|
|
538
|
+
}
|
|
539
|
+
let normalized;
|
|
540
|
+
let caseStable;
|
|
541
|
+
if (preNormalized) {
|
|
542
|
+
normalized = path;
|
|
543
|
+
caseStable = true;
|
|
544
|
+
} else {
|
|
545
|
+
caseStable = caseSensitive || isProvablyLowerAscii(path);
|
|
546
|
+
const folded = caseStable ? path : path.toLowerCase();
|
|
547
|
+
normalized = collapseAndStrip(folded, strict);
|
|
548
|
+
}
|
|
549
|
+
const methodMap = staticRoutes.get(method);
|
|
550
|
+
if (methodMap) {
|
|
551
|
+
const staticKey = normalized.length > 1 && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
552
|
+
const staticEntry = methodMap.get(staticKey);
|
|
453
553
|
if (staticEntry) {
|
|
454
554
|
return {
|
|
455
555
|
handler: staticEntry.handler,
|
|
456
556
|
params: EMPTY_PARAMS,
|
|
457
|
-
middleware:
|
|
557
|
+
middleware: routerMiddleware,
|
|
458
558
|
executor: staticEntry.executor
|
|
459
559
|
};
|
|
460
560
|
}
|
|
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
561
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
return [
|
|
498
|
-
path.slice(start),
|
|
499
|
-
path.length
|
|
500
|
-
];
|
|
501
|
-
}
|
|
502
|
-
return [
|
|
503
|
-
path.slice(start, slashPos),
|
|
504
|
-
slashPos + 1
|
|
505
|
-
];
|
|
562
|
+
if (!hasParamRoutes) return null;
|
|
563
|
+
const originalPath = caseStable ? void 0 : collapseAndStrip(path, strict);
|
|
564
|
+
const bindNames = walkPool ? walkPool.bindNames : [];
|
|
565
|
+
const bindValues = walkPool ? walkPool.bindValues : [];
|
|
566
|
+
if (walkPool) {
|
|
567
|
+
bindNames.length = 0;
|
|
568
|
+
bindValues.length = 0;
|
|
506
569
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
if (result) return result;
|
|
520
|
-
}
|
|
521
|
-
if (node.paramChild) {
|
|
522
|
-
const paramName = node.paramChild.paramName;
|
|
523
|
-
if (paramName === void 0) return null;
|
|
524
|
-
if (originalPath) {
|
|
525
|
-
const [origSeg] = this.extractSegment(originalPath, pos);
|
|
526
|
-
params[paramName] = origSeg;
|
|
527
|
-
} else {
|
|
528
|
-
params[paramName] = segment;
|
|
529
|
-
}
|
|
530
|
-
const result = this.matchNodeIndexed(node.paramChild, path, nextPos, params, method, originalPath);
|
|
531
|
-
if (result) return result;
|
|
532
|
-
Reflect.deleteProperty(params, paramName);
|
|
533
|
-
}
|
|
534
|
-
if (node.wildcardChild) {
|
|
535
|
-
const src = originalPath ?? path;
|
|
536
|
-
params["*"] = src.slice(pos);
|
|
537
|
-
return node.wildcardChild.handlers.get(method) ?? null;
|
|
570
|
+
const entry = matchNodeIndexed(root, normalized, 1, bindNames, bindValues, method, decode, originalPath, walkPool);
|
|
571
|
+
if (!entry) return null;
|
|
572
|
+
const count = bindNames.length;
|
|
573
|
+
let params;
|
|
574
|
+
if (count === 0) {
|
|
575
|
+
params = EMPTY_PARAMS;
|
|
576
|
+
} else {
|
|
577
|
+
params = Object.create(NULL_PROTO);
|
|
578
|
+
for (let i = 0; i < count; i++) {
|
|
579
|
+
const name = bindNames[i];
|
|
580
|
+
const value = bindValues[i];
|
|
581
|
+
if (name !== void 0 && value !== void 0) params[name] = value;
|
|
538
582
|
}
|
|
539
|
-
return null;
|
|
540
583
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (match.executor) {
|
|
567
|
-
await match.executor(ctx);
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
await match.handler(ctx, NOOP_NEXT);
|
|
571
|
-
};
|
|
584
|
+
return {
|
|
585
|
+
handler: entry.handler,
|
|
586
|
+
params,
|
|
587
|
+
middleware: routerMiddleware,
|
|
588
|
+
executor: entry.executor
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
__name(matchRoute, "matchRoute");
|
|
592
|
+
function resolveMatch(state, hasParamRoutes, method, path, preNormalized = false) {
|
|
593
|
+
return matchRoute(method, path, state.root, state.staticRoutes, hasParamRoutes, state.caseSensitive, state.strict, state.decode, state.routerMiddleware, preNormalized, state.walkPool);
|
|
594
|
+
}
|
|
595
|
+
__name(resolveMatch, "resolveMatch");
|
|
596
|
+
|
|
597
|
+
// src/composition.ts
|
|
598
|
+
function copyRoutes(node, prefix, segments, subRouterMiddleware, addRoute2) {
|
|
599
|
+
for (const [method, entry] of node.handlers) {
|
|
600
|
+
if (entry.autoHead) continue;
|
|
601
|
+
const path = prefix + "/" + segments.join("/");
|
|
602
|
+
const combined = subRouterMiddleware.length > 0 ? [
|
|
603
|
+
...subRouterMiddleware,
|
|
604
|
+
...entry.middleware
|
|
605
|
+
] : entry.middleware;
|
|
606
|
+
addRoute2(method, path || "/", [
|
|
607
|
+
entry.handler
|
|
608
|
+
], combined);
|
|
572
609
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
610
|
+
for (const [, child] of node.children) {
|
|
611
|
+
copyRoutes(child, prefix, [
|
|
612
|
+
...segments,
|
|
613
|
+
child.segment
|
|
614
|
+
], subRouterMiddleware, addRoute2);
|
|
615
|
+
}
|
|
616
|
+
if (node.paramChild) {
|
|
617
|
+
copyRoutes(node.paramChild, prefix, [
|
|
618
|
+
...segments,
|
|
619
|
+
node.paramChild.segment
|
|
620
|
+
], subRouterMiddleware, addRoute2);
|
|
621
|
+
}
|
|
622
|
+
if (node.wildcardChild) {
|
|
623
|
+
copyRoutes(node.wildcardChild, prefix, [
|
|
624
|
+
...segments,
|
|
625
|
+
"*"
|
|
626
|
+
], subRouterMiddleware, addRoute2);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
__name(copyRoutes, "copyRoutes");
|
|
630
|
+
|
|
631
|
+
// src/middleware-adapter.ts
|
|
632
|
+
function sealRouterMiddleware(root, staticRoutes, routerMiddleware) {
|
|
633
|
+
const routerMw = [
|
|
634
|
+
...routerMiddleware
|
|
635
|
+
];
|
|
636
|
+
const walk = /* @__PURE__ */ __name((node) => {
|
|
637
|
+
for (const [method, entry] of node.handlers) {
|
|
598
638
|
const combinedMw = [
|
|
599
639
|
...routerMw,
|
|
600
640
|
...entry.middleware
|
|
601
641
|
];
|
|
602
642
|
entry.executor = compileExecutor(entry.handler, combinedMw);
|
|
603
|
-
|
|
643
|
+
node.handlers.set(method, entry);
|
|
644
|
+
}
|
|
645
|
+
for (const [, child] of node.children) {
|
|
646
|
+
walk(child);
|
|
647
|
+
}
|
|
648
|
+
if (node.paramChild) walk(node.paramChild);
|
|
649
|
+
if (node.wildcardChild) walk(node.wildcardChild);
|
|
650
|
+
}, "walk");
|
|
651
|
+
walk(root);
|
|
652
|
+
for (const [, methodMap] of staticRoutes) {
|
|
653
|
+
for (const [key, entry] of methodMap) {
|
|
654
|
+
const combinedMw = [
|
|
655
|
+
...routerMw,
|
|
656
|
+
...entry.middleware
|
|
657
|
+
];
|
|
658
|
+
entry.executor = compileExecutor(entry.handler, combinedMw);
|
|
659
|
+
methodMap.set(key, entry);
|
|
604
660
|
}
|
|
605
661
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
662
|
+
}
|
|
663
|
+
__name(sealRouterMiddleware, "sealRouterMiddleware");
|
|
664
|
+
|
|
665
|
+
// src/registration.ts
|
|
666
|
+
import { HTTP_METHODS as HTTP_METHODS2 } from "@nextrush/types";
|
|
667
|
+
|
|
668
|
+
// src/route-metadata.ts
|
|
669
|
+
import { ROUTE_METADATA } from "@nextrush/types";
|
|
670
|
+
function endpoint(metadata) {
|
|
671
|
+
return {
|
|
672
|
+
[ROUTE_METADATA]: metadata
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
__name(endpoint, "endpoint");
|
|
676
|
+
function mergeContributions(contributions) {
|
|
677
|
+
if (contributions.length === 0) return void 0;
|
|
678
|
+
const meta = {};
|
|
679
|
+
for (const c of contributions) {
|
|
680
|
+
if (c.summary !== void 0) meta.summary = c.summary;
|
|
681
|
+
if (c.description !== void 0) meta.description = c.description;
|
|
682
|
+
if (c.deprecated !== void 0) meta.deprecated = c.deprecated;
|
|
683
|
+
if (c.visibility !== void 0) meta.visibility = c.visibility;
|
|
684
|
+
if (c.tags !== void 0) meta.tags = c.tags;
|
|
685
|
+
if (c.request) meta.request = {
|
|
686
|
+
...meta.request,
|
|
687
|
+
...c.request
|
|
688
|
+
};
|
|
689
|
+
if (c.responses) meta.responses = {
|
|
690
|
+
...meta.responses,
|
|
691
|
+
...c.responses
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
return meta;
|
|
695
|
+
}
|
|
696
|
+
__name(mergeContributions, "mergeContributions");
|
|
697
|
+
function readContribution(entry) {
|
|
698
|
+
return entry[ROUTE_METADATA];
|
|
699
|
+
}
|
|
700
|
+
__name(readContribution, "readContribution");
|
|
701
|
+
|
|
702
|
+
// src/registration.ts
|
|
703
|
+
function normalizeRegistrationPath(path, prefix, strict) {
|
|
704
|
+
let joinedPrefix = prefix;
|
|
705
|
+
if (joinedPrefix.endsWith("/") && path.startsWith("/")) {
|
|
706
|
+
joinedPrefix = joinedPrefix.slice(0, -1);
|
|
707
|
+
}
|
|
708
|
+
let normalized = joinedPrefix + path;
|
|
709
|
+
if (normalized.includes("//")) {
|
|
710
|
+
normalized = normalized.replace(/\/+/g, "/");
|
|
711
|
+
}
|
|
712
|
+
if (!strict && normalized.length > 1 && normalized.endsWith("/")) {
|
|
713
|
+
normalized = normalized.slice(0, -1);
|
|
714
|
+
}
|
|
715
|
+
return normalized.startsWith("/") ? normalized : "/" + normalized;
|
|
716
|
+
}
|
|
717
|
+
__name(normalizeRegistrationPath, "normalizeRegistrationPath");
|
|
718
|
+
function setStaticEntry(staticRoutes, method, key, entry) {
|
|
719
|
+
let methodMap = staticRoutes.get(method);
|
|
720
|
+
if (!methodMap) {
|
|
721
|
+
methodMap = /* @__PURE__ */ new Map();
|
|
722
|
+
staticRoutes.set(method, methodMap);
|
|
723
|
+
}
|
|
724
|
+
methodMap.set(key, entry);
|
|
725
|
+
}
|
|
726
|
+
__name(setStaticEntry, "setStaticEntry");
|
|
727
|
+
function addRoute(method, normalized, entries, middleware, state, recordIntrospection = true) {
|
|
728
|
+
const segments = parseSegments(normalized, state.caseSensitive);
|
|
729
|
+
if (segments.length > state.maxDepth) {
|
|
730
|
+
state.maxDepth = segments.length;
|
|
731
|
+
}
|
|
732
|
+
let node = state.root;
|
|
733
|
+
for (const seg of segments) {
|
|
734
|
+
if (seg.type === NodeType.PARAM) {
|
|
735
|
+
if (!node.paramChild) {
|
|
736
|
+
node.paramChild = createNode(seg.segment, NodeType.PARAM);
|
|
737
|
+
node.paramChild.paramName = seg.paramName;
|
|
738
|
+
} else if (node.paramChild.paramName !== seg.paramName) {
|
|
739
|
+
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.`);
|
|
614
740
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
741
|
+
node = node.paramChild;
|
|
742
|
+
} else if (seg.type === NodeType.WILDCARD) {
|
|
743
|
+
node.wildcardChild ??= createNode("*", NodeType.WILDCARD);
|
|
744
|
+
node = node.wildcardChild;
|
|
745
|
+
break;
|
|
746
|
+
} else {
|
|
747
|
+
const key = seg.segment;
|
|
748
|
+
let child = node.children.get(key);
|
|
749
|
+
if (!child) {
|
|
750
|
+
child = createNode(seg.segment, NodeType.STATIC);
|
|
751
|
+
node.children.set(key, child);
|
|
624
752
|
}
|
|
625
|
-
|
|
626
|
-
|
|
753
|
+
node = child;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
const functions = [];
|
|
757
|
+
const contributions = [];
|
|
758
|
+
for (const routeEntry of entries) {
|
|
759
|
+
const contribution = readContribution(routeEntry);
|
|
760
|
+
if (contribution) contributions.push(contribution);
|
|
761
|
+
if (typeof routeEntry === "function") functions.push(routeEntry);
|
|
762
|
+
}
|
|
763
|
+
const combinedMiddleware = [
|
|
764
|
+
...middleware
|
|
765
|
+
];
|
|
766
|
+
const finalHandler = functions[functions.length - 1];
|
|
767
|
+
if (!finalHandler) {
|
|
768
|
+
throw new Error("At least one handler is required");
|
|
769
|
+
}
|
|
770
|
+
for (let i = 0; i < functions.length - 1; i++) {
|
|
771
|
+
const fn = functions[i];
|
|
772
|
+
if (fn) combinedMiddleware.push(fn);
|
|
773
|
+
}
|
|
774
|
+
const executor = compileExecutor(finalHandler, combinedMiddleware);
|
|
775
|
+
const handlerEntry = {
|
|
776
|
+
handler: finalHandler,
|
|
777
|
+
middleware: combinedMiddleware,
|
|
778
|
+
executor,
|
|
779
|
+
autoHead: false
|
|
780
|
+
};
|
|
781
|
+
const existing = node.handlers.get(method);
|
|
782
|
+
if (existing && !(method === "HEAD" && existing.autoHead)) {
|
|
783
|
+
throw new Error(`Route conflict: ${method} ${normalized} is already registered. Remove the duplicate or use a different path.`);
|
|
784
|
+
}
|
|
785
|
+
node.handlers.set(method, handlerEntry);
|
|
786
|
+
const hasParams = segments.some((s) => s.type === NodeType.PARAM || s.type === NodeType.WILDCARD);
|
|
787
|
+
const staticKey = hasParams ? void 0 : state.caseSensitive ? normalized : normalized.toLowerCase();
|
|
788
|
+
if (staticKey !== void 0) {
|
|
789
|
+
setStaticEntry(state.staticRoutes, method, staticKey, handlerEntry);
|
|
790
|
+
}
|
|
791
|
+
if (method === "GET" && !node.handlers.has("HEAD")) {
|
|
792
|
+
const derived = {
|
|
793
|
+
handler: finalHandler,
|
|
794
|
+
middleware: combinedMiddleware,
|
|
795
|
+
executor,
|
|
796
|
+
autoHead: true
|
|
627
797
|
};
|
|
798
|
+
node.handlers.set("HEAD", derived);
|
|
799
|
+
if (staticKey !== void 0) {
|
|
800
|
+
setStaticEntry(state.staticRoutes, "HEAD", staticKey, derived);
|
|
801
|
+
}
|
|
628
802
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
803
|
+
if (recordIntrospection) {
|
|
804
|
+
state.routeDefinitions.push({
|
|
805
|
+
key: `${method} ${normalized}`,
|
|
806
|
+
method,
|
|
807
|
+
path: normalized,
|
|
808
|
+
metadata: mergeContributions(contributions)
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
return hasParams;
|
|
812
|
+
}
|
|
813
|
+
__name(addRoute, "addRoute");
|
|
814
|
+
function registerRedirect(from, to, status, addRoute2) {
|
|
815
|
+
const redirectHandler = createRedirectHandler(to, status);
|
|
816
|
+
addRoute2("GET", from, [
|
|
817
|
+
redirectHandler
|
|
818
|
+
]);
|
|
819
|
+
addRoute2("HEAD", from, [
|
|
820
|
+
redirectHandler
|
|
821
|
+
]);
|
|
822
|
+
if (status === 307 || status === 308) {
|
|
823
|
+
addRoute2("POST", from, [
|
|
824
|
+
redirectHandler
|
|
825
|
+
]);
|
|
826
|
+
addRoute2("PUT", from, [
|
|
827
|
+
redirectHandler
|
|
828
|
+
]);
|
|
829
|
+
addRoute2("PATCH", from, [
|
|
830
|
+
redirectHandler
|
|
831
|
+
]);
|
|
832
|
+
addRoute2("DELETE", from, [
|
|
833
|
+
redirectHandler
|
|
834
|
+
]);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
__name(registerRedirect, "registerRedirect");
|
|
838
|
+
function pushAnyMethodDefinition(routeDefinitions, normalized) {
|
|
839
|
+
routeDefinitions.push({
|
|
840
|
+
key: `${HTTP_METHODS2[0]} ${normalized}`,
|
|
841
|
+
method: HTTP_METHODS2[0],
|
|
842
|
+
path: normalized,
|
|
843
|
+
isAnyMethod: true
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
__name(pushAnyMethodDefinition, "pushAnyMethodDefinition");
|
|
847
|
+
|
|
848
|
+
// src/find-node.ts
|
|
849
|
+
function findNode(root, path, startPos) {
|
|
850
|
+
const stack = [
|
|
851
|
+
{
|
|
852
|
+
node: root,
|
|
853
|
+
pos: startPos,
|
|
854
|
+
stage: 0,
|
|
855
|
+
next: 0
|
|
637
856
|
}
|
|
638
|
-
|
|
639
|
-
|
|
857
|
+
];
|
|
858
|
+
while (stack.length > 0) {
|
|
859
|
+
const frame = stack[stack.length - 1];
|
|
860
|
+
if (frame === void 0) break;
|
|
861
|
+
if (frame.stage === 0) {
|
|
862
|
+
if (frame.pos >= path.length) {
|
|
863
|
+
return frame.node;
|
|
864
|
+
}
|
|
865
|
+
const seg = segmentAt(path, frame.pos);
|
|
866
|
+
if (seg === "") {
|
|
867
|
+
return frame.node;
|
|
868
|
+
}
|
|
869
|
+
const segEnd = frame.pos + seg.length;
|
|
870
|
+
frame.next = segEnd < path.length ? segEnd + 1 : path.length;
|
|
871
|
+
frame.stage = 1;
|
|
872
|
+
const staticChild = frame.node.children.get(seg);
|
|
873
|
+
if (staticChild) {
|
|
874
|
+
stack.push({
|
|
875
|
+
node: staticChild,
|
|
876
|
+
pos: frame.next,
|
|
877
|
+
stage: 0,
|
|
878
|
+
next: 0
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
continue;
|
|
640
882
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
883
|
+
if (frame.stage === 1) {
|
|
884
|
+
frame.stage = 2;
|
|
885
|
+
if (frame.node.paramChild) {
|
|
886
|
+
stack.push({
|
|
887
|
+
node: frame.node.paramChild,
|
|
888
|
+
pos: frame.next,
|
|
889
|
+
stage: 0,
|
|
890
|
+
next: 0
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
if (frame.node.wildcardChild) {
|
|
896
|
+
return frame.node.wildcardChild;
|
|
897
|
+
}
|
|
898
|
+
stack.pop();
|
|
645
899
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
__name(findNode, "findNode");
|
|
903
|
+
function findAllowedMethods(path, root, caseSensitive, strict) {
|
|
904
|
+
const normalized = normalizePathForMatch(path, caseSensitive, strict);
|
|
905
|
+
const node = findNode(root, normalized, 1);
|
|
906
|
+
if (!node || node.handlers.size === 0) return [];
|
|
907
|
+
return Array.from(node.handlers.keys());
|
|
908
|
+
}
|
|
909
|
+
__name(findAllowedMethods, "findAllowedMethods");
|
|
910
|
+
|
|
911
|
+
// src/canonicalize.ts
|
|
912
|
+
var DOT = 46;
|
|
913
|
+
var SLASH = 47;
|
|
914
|
+
function hasDotSegment(path) {
|
|
915
|
+
const len = path.length;
|
|
916
|
+
let segStart = 0;
|
|
917
|
+
for (let i = 0; i <= len; i++) {
|
|
918
|
+
const atBoundary = i === len || path.charCodeAt(i) === SLASH;
|
|
919
|
+
if (!atBoundary) continue;
|
|
920
|
+
const segLen = i - segStart;
|
|
921
|
+
if (segLen === 1 && path.charCodeAt(segStart) === DOT) return true;
|
|
922
|
+
if (segLen === 2 && path.charCodeAt(segStart) === DOT && path.charCodeAt(segStart + 1) === DOT) {
|
|
923
|
+
return true;
|
|
653
924
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
925
|
+
segStart = i + 1;
|
|
926
|
+
}
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
__name(hasDotSegment, "hasDotSegment");
|
|
930
|
+
var memoTarget = void 0;
|
|
931
|
+
var memoCaseSensitive = false;
|
|
932
|
+
var memoStrict = false;
|
|
933
|
+
var memoResult = {
|
|
934
|
+
rejected: false,
|
|
935
|
+
path: "/"
|
|
936
|
+
};
|
|
937
|
+
function canonicalizePath(rawTarget, caseSensitive, strict) {
|
|
938
|
+
if (rawTarget === memoTarget && caseSensitive === memoCaseSensitive && strict === memoStrict) {
|
|
939
|
+
return memoResult;
|
|
940
|
+
}
|
|
941
|
+
const queryIdx = rawTarget.indexOf("?");
|
|
942
|
+
const path = queryIdx === -1 ? rawTarget : rawTarget.slice(0, queryIdx);
|
|
943
|
+
const result = hasDotSegment(path) ? {
|
|
944
|
+
rejected: true,
|
|
945
|
+
path
|
|
946
|
+
} : {
|
|
947
|
+
rejected: false,
|
|
948
|
+
path: collapseAndStrip(caseSensitive || isProvablyLowerAscii(path) ? path : path.toLowerCase(), strict)
|
|
949
|
+
};
|
|
950
|
+
memoTarget = rawTarget;
|
|
951
|
+
memoCaseSensitive = caseSensitive;
|
|
952
|
+
memoStrict = strict;
|
|
953
|
+
memoResult = result;
|
|
954
|
+
return result;
|
|
955
|
+
}
|
|
956
|
+
__name(canonicalizePath, "canonicalizePath");
|
|
957
|
+
|
|
958
|
+
// src/dispatch.ts
|
|
959
|
+
var RESOLVED = Promise.resolve();
|
|
960
|
+
function createRoutesMiddleware(match, caseSensitive, strict) {
|
|
961
|
+
return (ctx, next) => {
|
|
962
|
+
const originalPath = ctx.path;
|
|
963
|
+
const canonical = canonicalizePath(originalPath, caseSensitive, strict);
|
|
964
|
+
if (canonical.rejected) {
|
|
965
|
+
ctx.status = 400;
|
|
966
|
+
ctx.originalPath = originalPath;
|
|
967
|
+
return RESOLVED;
|
|
660
968
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
969
|
+
ctx.path = canonical.path;
|
|
970
|
+
ctx.originalPath = originalPath;
|
|
971
|
+
const routeMatch = match(ctx.method, canonical.path);
|
|
972
|
+
if (!routeMatch) {
|
|
973
|
+
ctx.status = 404;
|
|
974
|
+
return next ? next() : RESOLVED;
|
|
664
975
|
}
|
|
665
|
-
|
|
666
|
-
|
|
976
|
+
ctx.params = routeMatch.params;
|
|
977
|
+
return routeMatch.executor ? routeMatch.executor(ctx) : (
|
|
978
|
+
// or thenable return still yields a Promise<void> and never a sync throw.
|
|
979
|
+
Promise.resolve(routeMatch.handler(ctx, NOOP_NEXT))
|
|
980
|
+
);
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
__name(createRoutesMiddleware, "createRoutesMiddleware");
|
|
984
|
+
function createAllowedMethodsMiddleware(root, caseSensitive, strict) {
|
|
985
|
+
return async (ctx, next) => {
|
|
986
|
+
if (next) {
|
|
987
|
+
await next();
|
|
667
988
|
}
|
|
668
|
-
return
|
|
989
|
+
if (ctx.status !== 404) return;
|
|
990
|
+
const allowed = findAllowedMethods(ctx.path, root, caseSensitive, strict);
|
|
991
|
+
if (allowed.length === 0) return;
|
|
992
|
+
const allowHeader = allowed.join(", ");
|
|
993
|
+
if (ctx.method === "OPTIONS") {
|
|
994
|
+
ctx.status = 200;
|
|
995
|
+
ctx.set("Allow", allowHeader);
|
|
996
|
+
ctx.body = "";
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
ctx.status = 405;
|
|
1000
|
+
ctx.set("Allow", allowHeader);
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
__name(createAllowedMethodsMiddleware, "createAllowedMethodsMiddleware");
|
|
1004
|
+
|
|
1005
|
+
// src/state.ts
|
|
1006
|
+
function resolveRouterOptions(options) {
|
|
1007
|
+
return {
|
|
1008
|
+
prefix: options.prefix ?? "",
|
|
1009
|
+
caseSensitive: options.caseSensitive ?? false,
|
|
1010
|
+
strict: options.strict ?? false,
|
|
1011
|
+
decode: options.decode ?? true
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
__name(resolveRouterOptions, "resolveRouterOptions");
|
|
1015
|
+
function createRouterState(root, opts, staticRoutes, routeDefinitions, routerMiddleware) {
|
|
1016
|
+
return {
|
|
1017
|
+
root,
|
|
1018
|
+
staticRoutes,
|
|
1019
|
+
routeDefinitions,
|
|
1020
|
+
caseSensitive: opts.caseSensitive,
|
|
1021
|
+
strict: opts.strict,
|
|
1022
|
+
decode: opts.decode,
|
|
1023
|
+
routerMiddleware,
|
|
1024
|
+
maxDepth: 0
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
__name(createRouterState, "createRouterState");
|
|
1028
|
+
|
|
1029
|
+
// src/router.ts
|
|
1030
|
+
var SLASH_CHAR_CODE = 47;
|
|
1031
|
+
var Router = class _Router {
|
|
1032
|
+
static {
|
|
1033
|
+
__name(this, "Router");
|
|
669
1034
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1035
|
+
root;
|
|
1036
|
+
opts;
|
|
1037
|
+
routerMiddleware = [];
|
|
1038
|
+
/** Static-route fast path: method-nested map for O(1) lookup with no per-request key string (HP-9). */
|
|
1039
|
+
staticRoutes = /* @__PURE__ */ new Map();
|
|
673
1040
|
/**
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
* @param prefix - Path prefix for all routes in the group
|
|
677
|
-
* @param middlewareOrCallback - Middleware array or callback function
|
|
678
|
-
* @param callback - Callback function if middleware is provided
|
|
679
|
-
* @returns this for chaining
|
|
680
|
-
*
|
|
681
|
-
* @example
|
|
682
|
-
* ```typescript
|
|
683
|
-
* // Simple group with prefix
|
|
684
|
-
* router.group('/api', (r) => {
|
|
685
|
-
* r.get('/users', listUsers);
|
|
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
|
-
* ```
|
|
1041
|
+
* Introspection registry, kept SEPARATE from the hot-path trie/staticRoutes
|
|
1042
|
+
* so request dispatch never reads metadata — only getRoutes() touches it.
|
|
703
1043
|
*/
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
if (!callback) {
|
|
710
|
-
throw new Error("Callback function is required when providing middleware array");
|
|
711
|
-
}
|
|
712
|
-
cb = callback;
|
|
713
|
-
} else {
|
|
714
|
-
cb = middlewareOrCallback;
|
|
715
|
-
}
|
|
716
|
-
const groupRouter = new GroupRouter(this, prefix, middleware);
|
|
717
|
-
cb(groupRouter);
|
|
718
|
-
return this;
|
|
719
|
-
}
|
|
1044
|
+
routeDefinitions = [];
|
|
1045
|
+
/** Whether any routes have params or wildcards (disables static-only fast path) */
|
|
1046
|
+
hasParamRoutes = false;
|
|
1047
|
+
/** Whether router-level middleware has already been sealed into executors (audit RT-7) */
|
|
1048
|
+
_sealed = false;
|
|
720
1049
|
/**
|
|
721
|
-
*
|
|
722
|
-
*
|
|
1050
|
+
* Single-entry memo for {@link matchesMountPrefix}'s canonicalization of the
|
|
1051
|
+
* mount prefix, which is fixed at registration time. Declared as two fields
|
|
1052
|
+
* rather than an object so a miss stores without allocating.
|
|
723
1053
|
*/
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
this.
|
|
730
|
-
this.
|
|
731
|
-
this.
|
|
1054
|
+
_prefixMemoRaw = void 0;
|
|
1055
|
+
_prefixMemoCanonical = "";
|
|
1056
|
+
/** Memoized state the extracted registration/matching functions read (see {@link createRouterState}). */
|
|
1057
|
+
state;
|
|
1058
|
+
constructor(options = {}) {
|
|
1059
|
+
this.root = createNode("");
|
|
1060
|
+
this.opts = resolveRouterOptions(options);
|
|
1061
|
+
this.state = createRouterState(this.root, this.opts, this.staticRoutes, this.routeDefinitions, this.routerMiddleware);
|
|
732
1062
|
}
|
|
733
1063
|
/**
|
|
734
|
-
*
|
|
735
|
-
*
|
|
1064
|
+
* Validate + normalize a raw path, then delegate trie insertion to the
|
|
1065
|
+
* extracted `addRoute` (design.md D2); flips `hasParamRoutes` from its return.
|
|
736
1066
|
*/
|
|
737
|
-
|
|
738
|
-
|
|
1067
|
+
addRoute(method, path, entries, middleware = [], recordIntrospection = true) {
|
|
1068
|
+
const rawPath = path;
|
|
1069
|
+
if (typeof rawPath !== "string") {
|
|
1070
|
+
throw new TypeError(`Route path must be a string, received ${rawPath === null ? "null" : typeof rawPath}.`);
|
|
1071
|
+
}
|
|
1072
|
+
const normalized = normalizeRegistrationPath(path, this.opts.prefix, this.opts.strict);
|
|
1073
|
+
const depthBefore = this.state.maxDepth;
|
|
1074
|
+
if (addRoute(method, normalized, entries, middleware, this.state, recordIntrospection)) {
|
|
1075
|
+
this.hasParamRoutes = true;
|
|
1076
|
+
}
|
|
1077
|
+
if (this.state.maxDepth > depthBefore) {
|
|
1078
|
+
this.state.walkPool = createWalkPool(this.state.maxDepth);
|
|
1079
|
+
}
|
|
739
1080
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
__name(this, "GroupRouter");
|
|
1081
|
+
get(path, ...entries) {
|
|
1082
|
+
this.addRoute("GET", path, entries);
|
|
1083
|
+
return this;
|
|
744
1084
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
constructor(parent, prefix, middleware) {
|
|
749
|
-
this.parent = parent;
|
|
750
|
-
this.prefix = prefix;
|
|
751
|
-
this.middleware = middleware;
|
|
1085
|
+
post(path, ...entries) {
|
|
1086
|
+
this.addRoute("POST", path, entries);
|
|
1087
|
+
return this;
|
|
752
1088
|
}
|
|
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;
|
|
1089
|
+
put(path, ...entries) {
|
|
1090
|
+
this.addRoute("PUT", path, entries);
|
|
1091
|
+
return this;
|
|
760
1092
|
}
|
|
761
|
-
|
|
762
|
-
this.
|
|
1093
|
+
delete(path, ...entries) {
|
|
1094
|
+
this.addRoute("DELETE", path, entries);
|
|
763
1095
|
return this;
|
|
764
1096
|
}
|
|
765
|
-
|
|
766
|
-
this.
|
|
1097
|
+
patch(path, ...entries) {
|
|
1098
|
+
this.addRoute("PATCH", path, entries);
|
|
767
1099
|
return this;
|
|
768
1100
|
}
|
|
769
|
-
|
|
770
|
-
this.
|
|
1101
|
+
head(path, ...entries) {
|
|
1102
|
+
this.addRoute("HEAD", path, entries);
|
|
771
1103
|
return this;
|
|
772
1104
|
}
|
|
773
|
-
|
|
774
|
-
this.
|
|
1105
|
+
options(path, ...entries) {
|
|
1106
|
+
this.addRoute("OPTIONS", path, entries);
|
|
775
1107
|
return this;
|
|
776
1108
|
}
|
|
777
|
-
|
|
778
|
-
|
|
1109
|
+
/**
|
|
1110
|
+
* Register a route for every HTTP method under one consolidated `isAnyMethod`
|
|
1111
|
+
* introspection row (T016) — matching is unchanged (see {@link pushAnyMethodDefinition}).
|
|
1112
|
+
*/
|
|
1113
|
+
all(path, ...entries) {
|
|
1114
|
+
for (const method of HTTP_METHODS3) {
|
|
1115
|
+
this.addRoute(method, path, entries, [], false);
|
|
1116
|
+
}
|
|
1117
|
+
pushAnyMethodDefinition(this.routeDefinitions, normalizeRegistrationPath(path, this.opts.prefix, this.opts.strict));
|
|
779
1118
|
return this;
|
|
780
1119
|
}
|
|
781
|
-
|
|
782
|
-
this.
|
|
1120
|
+
route(method, path, ...entries) {
|
|
1121
|
+
this.addRoute(method, path, entries);
|
|
783
1122
|
return this;
|
|
784
1123
|
}
|
|
785
|
-
|
|
786
|
-
|
|
1124
|
+
/**
|
|
1125
|
+
* Every registered route as a read-only list, for renderers (`@nextrush/openapi`,
|
|
1126
|
+
* SDK/RPC generators). Doc-generation-time only — never on the request path.
|
|
1127
|
+
*/
|
|
1128
|
+
getRoutes() {
|
|
1129
|
+
return this.routeDefinitions;
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Register a redirect from one path to another (301 by default). 307/308
|
|
1133
|
+
* additionally register POST/PUT/PATCH/DELETE to preserve the method.
|
|
1134
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md#redirects | README: Redirects}
|
|
1135
|
+
*/
|
|
1136
|
+
redirect(from, to, status = 301) {
|
|
1137
|
+
registerRedirect(from, to, status, (method, path, entries) => {
|
|
1138
|
+
this.addRoute(method, path, entries);
|
|
1139
|
+
});
|
|
787
1140
|
return this;
|
|
788
1141
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
this.
|
|
1142
|
+
use(pathOrMiddleware, routerOrUndefined) {
|
|
1143
|
+
if (typeof pathOrMiddleware === "function") {
|
|
1144
|
+
this.routerMiddleware.push(pathOrMiddleware);
|
|
1145
|
+
} else if (typeof pathOrMiddleware === "string" && routerOrUndefined instanceof _Router) {
|
|
1146
|
+
this.mountRouter(pathOrMiddleware, routerOrUndefined);
|
|
1147
|
+
} else if (typeof pathOrMiddleware === "string") {
|
|
1148
|
+
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.`);
|
|
1149
|
+
} else if (pathOrMiddleware instanceof _Router) {
|
|
1150
|
+
this.mountRouter("", pathOrMiddleware);
|
|
792
1151
|
}
|
|
793
1152
|
return this;
|
|
794
1153
|
}
|
|
795
1154
|
/**
|
|
796
|
-
*
|
|
1155
|
+
* Mount a sub-router at a path prefix (Hono-style) — the explicit, more
|
|
1156
|
+
* semantic equivalent of `router.use(path, subRouter)`.
|
|
1157
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md#sub-router-mounting | README: Sub-Router Mounting}
|
|
797
1158
|
*/
|
|
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);
|
|
1159
|
+
mount(path, router) {
|
|
1160
|
+
this.mountRouter(path, router);
|
|
817
1161
|
return this;
|
|
818
1162
|
}
|
|
1163
|
+
/** Mount a sub-router, carrying its own `routerMiddleware` onto every copied route. */
|
|
1164
|
+
mountRouter(prefix, router) {
|
|
1165
|
+
copyRoutes(router.root, prefix, [], router.routerMiddleware, this.addRoute.bind(this));
|
|
1166
|
+
}
|
|
1167
|
+
/** Match a request to a route — delegates to {@link resolveMatch} (design.md D1). */
|
|
1168
|
+
match(method, path) {
|
|
1169
|
+
return resolveMatch(this.state, this.hasParamRoutes, method, path);
|
|
1170
|
+
}
|
|
819
1171
|
/**
|
|
820
|
-
*
|
|
1172
|
+
* Test whether `path` falls under `prefix` using this router's OWN
|
|
1173
|
+
* canonicalization (case folding per `caseSensitive`, structural
|
|
1174
|
+
* normalization) — the mount-boundary counterpart to {@link match}, so a
|
|
1175
|
+
* router mounted via `Application.route()` is tested with the identical
|
|
1176
|
+
* rule it dispatches with (RFC-029, task 3.8). Implements the optional
|
|
1177
|
+
* `Routable.matchesMountPrefix` contract from `@nextrush/core`.
|
|
1178
|
+
*
|
|
1179
|
+
* @param path - The full request path being tested for this mount.
|
|
1180
|
+
* @param prefix - The normalized mount prefix (leading `/`, no trailing `/`).
|
|
1181
|
+
* @returns The path's remainder past the prefix (e.g. `/users` for a
|
|
1182
|
+
* `/ADMIN/Users` request mounted at `/admin`), or `undefined` when `path`
|
|
1183
|
+
* is not under `prefix` per this router's canonicalization.
|
|
821
1184
|
*/
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
throw new Error("Callback function is required when providing middleware array");
|
|
829
|
-
}
|
|
830
|
-
cb = callback;
|
|
1185
|
+
matchesMountPrefix(path, prefix) {
|
|
1186
|
+
const canonical = canonicalizePath(path, this.opts.caseSensitive, this.opts.strict);
|
|
1187
|
+
if (canonical.rejected) return void 0;
|
|
1188
|
+
let canonicalPrefix;
|
|
1189
|
+
if (this._prefixMemoRaw === prefix) {
|
|
1190
|
+
canonicalPrefix = this._prefixMemoCanonical;
|
|
831
1191
|
} else {
|
|
832
|
-
|
|
1192
|
+
canonicalPrefix = canonicalizePath(prefix, this.opts.caseSensitive, this.opts.strict).path;
|
|
1193
|
+
this._prefixMemoRaw = prefix;
|
|
1194
|
+
this._prefixMemoCanonical = canonicalPrefix;
|
|
833
1195
|
}
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
1196
|
+
const prefixLen = canonicalPrefix.length;
|
|
1197
|
+
if (!canonical.path.startsWith(canonicalPrefix)) return void 0;
|
|
1198
|
+
const hasCharAfterPrefix = prefixLen < canonical.path.length;
|
|
1199
|
+
if (hasCharAfterPrefix && canonical.path.charCodeAt(prefixLen) !== SLASH_CHAR_CODE) {
|
|
1200
|
+
return void 0;
|
|
1201
|
+
}
|
|
1202
|
+
return canonical.path.slice(prefixLen) || "/";
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Return the router's dispatch middleware — mount this on the application.
|
|
1206
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md#routerroutes | README: router.routes()}
|
|
1207
|
+
*/
|
|
1208
|
+
routes() {
|
|
1209
|
+
if (this.routerMiddleware.length > 0 && !this._sealed) {
|
|
1210
|
+
this._sealed = true;
|
|
1211
|
+
sealRouterMiddleware(this.root, this.staticRoutes, this.routerMiddleware);
|
|
1212
|
+
}
|
|
1213
|
+
return createRoutesMiddleware((method, path) => resolveMatch(this.state, this.hasParamRoutes, method, path, true), this.opts.caseSensitive, this.opts.strict);
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Generate allowed-methods middleware. Responds to OPTIONS with an `Allow`
|
|
1217
|
+
* header and returns 405 for a known path hit with an unregistered method.
|
|
1218
|
+
*/
|
|
1219
|
+
allowedMethods() {
|
|
1220
|
+
return createAllowedMethodsMiddleware(this.root, this.opts.caseSensitive, this.opts.strict);
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Create a route group with a shared prefix and middleware. The callback
|
|
1224
|
+
* receives a {@link RouteGroup} to register routes against.
|
|
1225
|
+
* @see {@link https://github.com/0xTanzim/nextRush/blob/main/packages/router/README.md#route-groups | README: Route Groups}
|
|
1226
|
+
*/
|
|
1227
|
+
group(prefix, middlewareOrCallback, callback) {
|
|
1228
|
+
runRouteGroup(this, prefix, middlewareOrCallback, callback);
|
|
839
1229
|
return this;
|
|
840
1230
|
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Remove all routes and middleware, resetting the router to its initial
|
|
1233
|
+
* state — for test isolation or hot-reload re-registration.
|
|
1234
|
+
*/
|
|
1235
|
+
reset() {
|
|
1236
|
+
clearNode(this.root);
|
|
1237
|
+
this.staticRoutes.clear();
|
|
1238
|
+
this.routerMiddleware.length = 0;
|
|
1239
|
+
this.hasParamRoutes = false;
|
|
1240
|
+
this.routeDefinitions.length = 0;
|
|
1241
|
+
this.state.maxDepth = 0;
|
|
1242
|
+
this.state.walkPool = void 0;
|
|
1243
|
+
this._sealed = false;
|
|
1244
|
+
}
|
|
1245
|
+
/** Register a route on behalf of a {@link RouteGroup} (group context). @internal */
|
|
1246
|
+
_addGroupRoute(method, path, handlers, groupMiddleware, recordIntrospection = true) {
|
|
1247
|
+
this.addRoute(method, path, handlers, groupMiddleware, recordIntrospection);
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Group-facing entry point to {@link pushAnyMethodDefinition} — a group's `.all()`
|
|
1251
|
+
* records its consolidated row here since group routes live on the parent. @internal
|
|
1252
|
+
*/
|
|
1253
|
+
_pushAnyMethodRouteDefinition(path) {
|
|
1254
|
+
pushAnyMethodDefinition(this.routeDefinitions, normalizeRegistrationPath(path, this.opts.prefix, this.opts.strict));
|
|
1255
|
+
}
|
|
841
1256
|
};
|
|
842
1257
|
function createRouter(options) {
|
|
843
1258
|
return new Router(options);
|
|
@@ -846,8 +1261,11 @@ __name(createRouter, "createRouter");
|
|
|
846
1261
|
export {
|
|
847
1262
|
NodeType,
|
|
848
1263
|
Router,
|
|
1264
|
+
canonicalizePath,
|
|
849
1265
|
createNode,
|
|
850
1266
|
createRouter,
|
|
1267
|
+
endpoint,
|
|
1268
|
+
hasDotSegment,
|
|
851
1269
|
parseSegments
|
|
852
1270
|
};
|
|
853
1271
|
//# sourceMappingURL=index.js.map
|