@b9g/router 0.1.7 → 0.1.8
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/package.json +2 -1
- package/src/index.d.ts +24 -0
- package/src/index.js +61 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/router",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Universal request router for ServiceWorker applications. Built on web standards with cache-aware routing and generator-based middleware.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"router",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"shovel"
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
|
+
"@b9g/http-errors": "^0.1.5",
|
|
16
17
|
"@b9g/match-pattern": "^0.1.7"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
package/src/index.d.ts
CHANGED
|
@@ -189,4 +189,28 @@ export declare class Router {
|
|
|
189
189
|
compiled: boolean;
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Mode for trailing slash normalization
|
|
194
|
+
* - "strip": Redirect /path/ → /path (removes trailing slash)
|
|
195
|
+
* - "add": Redirect /path → /path/ (adds trailing slash)
|
|
196
|
+
*/
|
|
197
|
+
export type TrailingSlashMode = "strip" | "add";
|
|
198
|
+
/**
|
|
199
|
+
* Middleware that normalizes trailing slashes via 301 redirect
|
|
200
|
+
*
|
|
201
|
+
* @param mode - "strip" removes trailing slash, "add" adds trailing slash
|
|
202
|
+
* @returns Function middleware that redirects non-canonical URLs
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```typescript
|
|
206
|
+
* import { Router, trailingSlash } from "@b9g/router";
|
|
207
|
+
*
|
|
208
|
+
* const router = new Router();
|
|
209
|
+
* router.use(trailingSlash("strip")); // Redirect /path/ → /path
|
|
210
|
+
*
|
|
211
|
+
* // Can also be scoped to specific paths
|
|
212
|
+
* router.use("/api", trailingSlash("strip"));
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
export declare function trailingSlash(mode: TrailingSlashMode): FunctionMiddleware;
|
|
192
216
|
export {};
|
package/src/index.js
CHANGED
|
@@ -5,7 +5,11 @@ import {
|
|
|
5
5
|
isSimplePattern,
|
|
6
6
|
compilePathname
|
|
7
7
|
} from "@b9g/match-pattern";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
InternalServerError,
|
|
10
|
+
NotFound,
|
|
11
|
+
isHTTPError
|
|
12
|
+
} from "@b9g/http-errors";
|
|
9
13
|
var RadixNode = class {
|
|
10
14
|
children;
|
|
11
15
|
// char -> RadixNode
|
|
@@ -276,7 +280,7 @@ var Router = class {
|
|
|
276
280
|
);
|
|
277
281
|
} else {
|
|
278
282
|
const notFoundHandler = async () => {
|
|
279
|
-
|
|
283
|
+
throw new NotFound();
|
|
280
284
|
};
|
|
281
285
|
const mutableRequest = this.#createMutableRequest(request);
|
|
282
286
|
return await this.#executeMiddlewareStack(
|
|
@@ -361,18 +365,28 @@ var Router = class {
|
|
|
361
365
|
handler = matchResult.handler;
|
|
362
366
|
context = matchResult.context;
|
|
363
367
|
} else {
|
|
364
|
-
handler = async () =>
|
|
368
|
+
handler = async () => {
|
|
369
|
+
throw new NotFound();
|
|
370
|
+
};
|
|
365
371
|
context = { params: {} };
|
|
366
372
|
}
|
|
367
|
-
let response
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
373
|
+
let response;
|
|
374
|
+
try {
|
|
375
|
+
response = await this.#executeMiddlewareStack(
|
|
376
|
+
this.#middlewares,
|
|
377
|
+
mutableRequest,
|
|
378
|
+
context,
|
|
379
|
+
handler,
|
|
380
|
+
originalURL,
|
|
381
|
+
this.#executor
|
|
382
|
+
// Pass executor for re-routing
|
|
383
|
+
);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
if (!matchResult && isHTTPError(error) && error.status === 404) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
376
390
|
if (!matchResult && response?.status === 404) {
|
|
377
391
|
return null;
|
|
378
392
|
}
|
|
@@ -553,10 +567,18 @@ var Router = class {
|
|
|
553
567
|
handlerError = error;
|
|
554
568
|
}
|
|
555
569
|
if (handlerError) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
570
|
+
if (request.url !== originalURL) {
|
|
571
|
+
currentResponse = this.#handleAutomaticRedirect(
|
|
572
|
+
originalURL,
|
|
573
|
+
request.url,
|
|
574
|
+
request.method
|
|
575
|
+
);
|
|
576
|
+
} else {
|
|
577
|
+
currentResponse = await this.#handleErrorThroughGenerators(
|
|
578
|
+
handlerError,
|
|
579
|
+
runningGenerators
|
|
580
|
+
);
|
|
581
|
+
}
|
|
560
582
|
}
|
|
561
583
|
}
|
|
562
584
|
if (request.url !== originalURL && currentResponse) {
|
|
@@ -667,6 +689,28 @@ var Router = class {
|
|
|
667
689
|
return httpError.toResponse(isDev);
|
|
668
690
|
}
|
|
669
691
|
};
|
|
692
|
+
function trailingSlash(mode) {
|
|
693
|
+
return (request, _context) => {
|
|
694
|
+
const url = new URL(request.url);
|
|
695
|
+
const pathname = url.pathname;
|
|
696
|
+
if (pathname === "/")
|
|
697
|
+
return;
|
|
698
|
+
let newPathname = null;
|
|
699
|
+
if (mode === "strip" && pathname.endsWith("/")) {
|
|
700
|
+
newPathname = pathname.slice(0, -1);
|
|
701
|
+
} else if (mode === "add" && !pathname.endsWith("/")) {
|
|
702
|
+
newPathname = pathname + "/";
|
|
703
|
+
}
|
|
704
|
+
if (newPathname) {
|
|
705
|
+
url.pathname = newPathname;
|
|
706
|
+
return new Response(null, {
|
|
707
|
+
status: 301,
|
|
708
|
+
headers: { Location: url.toString() }
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
}
|
|
670
713
|
export {
|
|
671
|
-
Router
|
|
714
|
+
Router,
|
|
715
|
+
trailingSlash
|
|
672
716
|
};
|