prep for publish
This commit is contained in:
parent
b73af68989
commit
0c8d1865ff
59
README.md
59
README.md
@ -7,24 +7,26 @@ A simple router for Deno.
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Basics
|
### Basics
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import Router from '@bearmetal/router';
|
import Router from "@bearmetal/router";
|
||||||
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
router
|
router
|
||||||
.route('/users')
|
.route("/users")
|
||||||
.get((ctx) => {
|
.get((req, ctx) => {
|
||||||
return new Response('GET /users');
|
return new Response("GET /users");
|
||||||
})
|
})
|
||||||
.post((ctx) => {
|
.post((req, ctx) => {
|
||||||
return new Response('POST /users');
|
return new Response("POST /users");
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.serve(router.handle)
|
Deno.serve(router.handle);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Middleware
|
### Middleware
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
...
|
...
|
||||||
|
|
||||||
@ -37,6 +39,7 @@ router.use('/users', async (ctx, next) => {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Nested Routers
|
### Nested Routers
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
...
|
...
|
||||||
|
|
||||||
@ -56,3 +59,45 @@ router.use('/users', nestedRouter);
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Static Files
|
||||||
|
|
||||||
|
```ts
|
||||||
|
...
|
||||||
|
router.serveDirectory('dirname', '/url-root') // files from 'dirname' directory will be available at '/url-root/filename'
|
||||||
|
|
||||||
|
// To automatically locate index.html pages:
|
||||||
|
router.serveDirectory('dirWithIndexHtml', '/indexes', {showIndex: true});
|
||||||
|
// Will also generate an index if there is no index.html present in the directory
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### File-based Routing
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { FileRouter } from "@bearmetal/router";
|
||||||
|
|
||||||
|
const router = new FileRouter("dirname");
|
||||||
|
Deno.listen(router.handle);
|
||||||
|
|
||||||
|
// dirname/index.ts - will be accessible at '/'
|
||||||
|
export default function (req, ctx) {
|
||||||
|
return new Response("Hello, world!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// dirname/methods.ts - will be accessible at '/methods'
|
||||||
|
export const handlers = {
|
||||||
|
get() {
|
||||||
|
return new Response("Hello, world");
|
||||||
|
},
|
||||||
|
post(req, ctx) {
|
||||||
|
const data = doDataOp(req.body);
|
||||||
|
return new Response(data);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// dirname/nestedRouter.ts - will be accessible at '/nestedRouter'
|
||||||
|
import { router } from "@bearmetal/router";
|
||||||
|
|
||||||
|
const router = new Router();
|
||||||
|
export default router;
|
||||||
|
```
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@bearmetal/router",
|
"name": "@bearmetal/router",
|
||||||
"description": "A simple router for Deno",
|
"description": "A simple router for Deno",
|
||||||
"version": "0.1.3",
|
"version": "0.2.0",
|
||||||
"stable": true,
|
"stable": true,
|
||||||
"repository": "https://github.com/emmaos/bearmetal",
|
|
||||||
"files": [
|
"files": [
|
||||||
"mod.ts",
|
"mod.ts",
|
||||||
"README.md",
|
"README.md",
|
||||||
@ -13,8 +12,11 @@
|
|||||||
".": "./mod.ts",
|
".": "./mod.ts",
|
||||||
"./types": "./types.ts"
|
"./types": "./types.ts"
|
||||||
},
|
},
|
||||||
|
"exclude": [
|
||||||
|
".vscode/"
|
||||||
|
],
|
||||||
"imports": {
|
"imports": {
|
||||||
"@std/assert": "jsr:@std/assert@^1.0.7",
|
"@std/assert": "jsr:@std/assert@^1.0.7",
|
||||||
"@std/testing": "jsr:@std/testing@^1.0.4"
|
"@std/testing": "jsr:@std/testing@^1.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
329
file_router.ts
329
file_router.ts
@ -1,6 +1,5 @@
|
|||||||
import Router from "./router.ts";
|
import Router from "./router.ts";
|
||||||
import type { Handler, RouteConfigurator } from "./types.ts";
|
import type { Handler, RouteConfigurator } from "./types.ts";
|
||||||
import { NotFound } from "./util/response.ts";
|
|
||||||
|
|
||||||
function crawl(dir: string, callback: (path: string) => void) {
|
function crawl(dir: string, callback: (path: string) => void) {
|
||||||
for (const entry of Deno.readDirSync(dir)) {
|
for (const entry of Deno.readDirSync(dir)) {
|
||||||
@ -15,6 +14,15 @@ function crawl(dir: string, callback: (path: string) => void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* import {FileRouter} from "@bearmetal/router"
|
||||||
|
*
|
||||||
|
* const router = new FileRouter("dirName");
|
||||||
|
* Deno.listen(router.handle);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export class FileRouter extends Router {
|
export class FileRouter extends Router {
|
||||||
constructor(root: string) {
|
constructor(root: string) {
|
||||||
super();
|
super();
|
||||||
@ -22,16 +30,16 @@ export class FileRouter extends Router {
|
|||||||
let relativePath = path.replace(root, "");
|
let relativePath = path.replace(root, "");
|
||||||
if (path.endsWith(".ts") || path.endsWith(".js")) {
|
if (path.endsWith(".ts") || path.endsWith(".js")) {
|
||||||
relativePath = relativePath.replace(/\.[tj]s/, "");
|
relativePath = relativePath.replace(/\.[tj]s/, "");
|
||||||
const asdf = await import(path);
|
const handlers = await import(path);
|
||||||
|
|
||||||
if (asdf.default) {
|
if (handlers.default) {
|
||||||
asdf.default instanceof Router
|
handlers.default instanceof Router
|
||||||
? this.use(relativePath, asdf.default)
|
? this.use(relativePath, handlers.default)
|
||||||
: this.route(relativePath).get(asdf.default);
|
: this.route(relativePath).get(handlers.default);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (asdf.handlers) {
|
if (handlers.handlers) {
|
||||||
for (const [method, handler] of Object.entries(asdf.handlers)) {
|
for (const [method, handler] of Object.entries(handlers.handlers)) {
|
||||||
this.route(relativePath)[method as keyof RouteConfigurator](
|
this.route(relativePath)[method as keyof RouteConfigurator](
|
||||||
handler as Handler,
|
handler as Handler,
|
||||||
);
|
);
|
||||||
@ -48,314 +56,11 @@ export class FileRouter extends Router {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
serveDirectory(dir: string, root: string, opts?: { showIndex: boolean }) {
|
|
||||||
this.route(root + "*").get(async (_req, ctx) => {
|
|
||||||
const { showIndex } = opts ?? { showIndex: false };
|
|
||||||
|
|
||||||
let normalizedPath = (dir + "/" +
|
|
||||||
ctx.url.pathname.replace(new RegExp("^" + root), "")).trim().replace(
|
|
||||||
"//",
|
|
||||||
"/",
|
|
||||||
);
|
|
||||||
|
|
||||||
normalizedPath = normalizedPath.replace(
|
|
||||||
/\/\s?$/,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
|
|
||||||
let fileInfo: Deno.FileInfo;
|
|
||||||
|
|
||||||
try {
|
|
||||||
fileInfo = await Deno.stat(normalizedPath);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Deno.errors.NotFound) {
|
|
||||||
return NotFound();
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileInfo.isDirectory) {
|
|
||||||
if (!showIndex) return NotFound();
|
|
||||||
normalizedPath += "/index.html";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const file = await Deno.readFile(normalizedPath);
|
|
||||||
return new Response(file);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Deno.errors.NotFound) {
|
|
||||||
return showIndex ? generateIndex(normalizedPath) : NotFound();
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateIndex(dir: string) {
|
|
||||||
dir = dir.replace(/index\.html$/, "");
|
|
||||||
const items: Deno.DirEntry[] = [];
|
|
||||||
|
|
||||||
for await (const entry of Deno.readDir(dir)) {
|
|
||||||
items.push(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileIcon = `
|
|
||||||
<svg
|
|
||||||
width="48"
|
|
||||||
height="48"
|
|
||||||
viewBox="0 0 12.7 12.7"
|
|
||||||
version="1.1"
|
|
||||||
id="svg1"
|
|
||||||
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
|
|
||||||
sodipodi:docname="file icon.svg"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg">
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="namedview1"
|
|
||||||
pagecolor="#505050"
|
|
||||||
bordercolor="#eeeeee"
|
|
||||||
borderopacity="1"
|
|
||||||
inkscape:showpageshadow="0"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#505050"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
inkscape:zoom="17.318182"
|
|
||||||
inkscape:cx="10.018373"
|
|
||||||
inkscape:cy="28.207349"
|
|
||||||
inkscape:window-width="3440"
|
|
||||||
inkscape:window-height="1417"
|
|
||||||
inkscape:window-x="-8"
|
|
||||||
inkscape:window-y="1432"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="layer1" />
|
|
||||||
<defs
|
|
||||||
id="defs1" />
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1">
|
|
||||||
<path
|
|
||||||
d="m 3.5196818,1.7373617 c -0.3351528,0 -0.605131,0.2694614 -0.605131,0.6046142 v 8.0160481 c 0,0.335153 0.2699782,0.604614 0.605131,0.604614 h 5.6606364 c 0.3351528,0 0.605131,-0.269461 0.605131,-0.604614 V 3.7372396 L 7.7850545,1.7373617 Z"
|
|
||||||
style="fill:#4d1f6d;stroke-width:0.264583"
|
|
||||||
id="path4" />
|
|
||||||
<path
|
|
||||||
d="M 9.6529679,3.8697209 7.6525732,1.869843 v 1.3952637 c 0,0.3351528 0.2699782,0.6046142 0.605131,0.6046142 z"
|
|
||||||
style="fill:#918b93;fill-opacity:1;stroke:#4d1f6d;stroke-width:0.265;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
|
||||||
id="path5" />
|
|
||||||
<rect
|
|
||||||
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.285269;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
|
||||||
id="rect5"
|
|
||||||
width="5.5833006"
|
|
||||||
height="0.5505569"
|
|
||||||
x="3.5583498"
|
|
||||||
y="4.0022206" />
|
|
||||||
<rect
|
|
||||||
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.272493;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
|
||||||
id="rect6"
|
|
||||||
width="5.0944114"
|
|
||||||
height="0.5505569"
|
|
||||||
x="3.5583498"
|
|
||||||
y="4.8455539" />
|
|
||||||
<rect
|
|
||||||
style="fill:#463e4b;fill-opacity:1;stroke:none;stroke-width:0.279751;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
|
||||||
id="rect7"
|
|
||||||
width="5.3694115"
|
|
||||||
height="0.5505569"
|
|
||||||
x="3.5583498"
|
|
||||||
y="5.682776" />
|
|
||||||
<rect
|
|
||||||
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.279751;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
|
||||||
id="rect8"
|
|
||||||
width="5.3694115"
|
|
||||||
height="0.5505569"
|
|
||||||
x="3.5583498"
|
|
||||||
y="5.6888871" />
|
|
||||||
<rect
|
|
||||||
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.285269;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
|
||||||
id="rect9"
|
|
||||||
width="5.5833006"
|
|
||||||
height="0.5505569"
|
|
||||||
x="3.5583498"
|
|
||||||
y="6.5322208" />
|
|
||||||
<rect
|
|
||||||
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.272493;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
|
||||||
id="rect10"
|
|
||||||
width="5.0944114"
|
|
||||||
height="0.5505569"
|
|
||||||
x="3.5583498"
|
|
||||||
y="7.3755541" />
|
|
||||||
<rect
|
|
||||||
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.279751;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
|
||||||
id="rect11"
|
|
||||||
width="5.3694115"
|
|
||||||
height="0.5505569"
|
|
||||||
x="3.5583498"
|
|
||||||
y="8.2188873" />
|
|
||||||
<rect
|
|
||||||
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.217416;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
|
||||||
id="rect12"
|
|
||||||
width="3.2431545"
|
|
||||||
height="0.5505569"
|
|
||||||
x="5.8984962"
|
|
||||||
y="9.7313871" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
`;
|
|
||||||
const folderIcon = `
|
|
||||||
<svg
|
|
||||||
width="48"
|
|
||||||
height="48"
|
|
||||||
viewBox="0 0 12.7 12.7"
|
|
||||||
version="1.1"
|
|
||||||
id="svg1"
|
|
||||||
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
|
|
||||||
sodipodi:docname="folder icon.svg"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg">
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="namedview1"
|
|
||||||
pagecolor="#505050"
|
|
||||||
bordercolor="#eeeeee"
|
|
||||||
borderopacity="1"
|
|
||||||
inkscape:showpageshadow="0"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#505050"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
inkscape:zoom="12.245804"
|
|
||||||
inkscape:cx="17.924507"
|
|
||||||
inkscape:cy="30.990207"
|
|
||||||
inkscape:window-width="3440"
|
|
||||||
inkscape:window-height="1417"
|
|
||||||
inkscape:window-x="-8"
|
|
||||||
inkscape:window-y="1432"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="layer1" />
|
|
||||||
<defs
|
|
||||||
id="defs1" />
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1">
|
|
||||||
<rect
|
|
||||||
style="fill:#463e4b;fill-opacity:1;stroke-width:0.264583"
|
|
||||||
id="rect3"
|
|
||||||
width="10.867838"
|
|
||||||
height="7.4972959"
|
|
||||||
x="0.91608107"
|
|
||||||
y="2.1476252"
|
|
||||||
ry="1.1883322" />
|
|
||||||
<rect
|
|
||||||
style="fill:#4d1f6d;fill-opacity:1;stroke-width:0.264583"
|
|
||||||
id="rect1"
|
|
||||||
width="10.867838"
|
|
||||||
height="7.4972959"
|
|
||||||
x="0.91608107"
|
|
||||||
y="2.601352"
|
|
||||||
ry="1.1883322" />
|
|
||||||
<rect
|
|
||||||
style="fill:#4d1f6d;fill-opacity:1;stroke-width:0.264583"
|
|
||||||
id="rect2"
|
|
||||||
width="4.2779961"
|
|
||||||
height="4.0619354"
|
|
||||||
x="0.91608107"
|
|
||||||
y="1.5426561"
|
|
||||||
ry="1.1883322" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const template = `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>${dir}</title>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
list-style: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 4px 2px;
|
|
||||||
|
|
||||||
&:nth-child(odd) {
|
|
||||||
background-color: rgba(0,0,0, .2)
|
|
||||||
}
|
|
||||||
|
|
||||||
& svg {
|
|
||||||
height: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
background-color: #1b1220;
|
|
||||||
color: #dcdcdc
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
background-color: rgba(255,255,255,.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>${dir}</h1>
|
|
||||||
<ul>
|
|
||||||
${
|
|
||||||
items
|
|
||||||
.sort((a, b) => {
|
|
||||||
if (a.isDirectory && b.isDirectory) return a.name > b.name ? 1 : -1;
|
|
||||||
|
|
||||||
if (a.isDirectory) return -1;
|
|
||||||
|
|
||||||
if (b.isDirectory) return 1;
|
|
||||||
|
|
||||||
return a.name > b.name ? 1 : -1;
|
|
||||||
})
|
|
||||||
.map((e) =>
|
|
||||||
`<li>${e.isFile ? fileIcon : folderIcon}<a href="${
|
|
||||||
"/" + dir.replace(/^\//, "") + e.name
|
|
||||||
}">${e.name}</a></li>`
|
|
||||||
)
|
|
||||||
.join("\n") || "<li>Directory is empty</li>"
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return new Response(template, {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/html",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (import.meta.main) {
|
if (import.meta.main) {
|
||||||
const router = new FileRouter("fileRouterTest");
|
const router = new FileRouter("fileRouterTest");
|
||||||
router.serveDirectory("things", "/things", { showIndex: true });
|
router.serveDirectory("docs", "/", { showIndex: true });
|
||||||
Deno.serve({
|
Deno.serve({
|
||||||
port: 8000,
|
port: 8000,
|
||||||
handler: router.handle,
|
handler: router.handle,
|
||||||
|
3
mod.ts
3
mod.ts
@ -4,3 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export { Router } from "./router.ts";
|
export { Router } from "./router.ts";
|
||||||
|
export { FileRouter } from "./file_router.ts";
|
||||||
|
|
||||||
|
export type { Handler, Middleware } from "./types.ts";
|
||||||
|
315
router.ts
315
router.ts
@ -6,6 +6,7 @@ import type {
|
|||||||
RouteConfigurator,
|
RouteConfigurator,
|
||||||
RouterContext,
|
RouterContext,
|
||||||
} from "./types.ts";
|
} from "./types.ts";
|
||||||
|
import { NotFound } from "./util/response.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple router for Deno
|
* A simple router for Deno
|
||||||
@ -187,7 +188,6 @@ export class Router {
|
|||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
const method = req.method;
|
const method = req.method;
|
||||||
|
|
||||||
// Find the matching route
|
|
||||||
const matchedRoute = this.findMatchingRoute(url);
|
const matchedRoute = this.findMatchingRoute(url);
|
||||||
if (!matchedRoute) {
|
if (!matchedRoute) {
|
||||||
return new Response("Not Found", { status: 404 });
|
return new Response("Not Found", { status: 404 });
|
||||||
@ -198,10 +198,8 @@ export class Router {
|
|||||||
return new Response("Method Not Allowed", { status: 405 });
|
return new Response("Method Not Allowed", { status: 405 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get matching middleware and sort by path specificity
|
|
||||||
const matchingMiddleware = this.getMatchingMiddleware(url);
|
const matchingMiddleware = this.getMatchingMiddleware(url);
|
||||||
|
|
||||||
// Create the base context object
|
|
||||||
const baseCtx: RouterContext = {
|
const baseCtx: RouterContext = {
|
||||||
url,
|
url,
|
||||||
params: {},
|
params: {},
|
||||||
@ -210,26 +208,22 @@ export class Router {
|
|||||||
request: req,
|
request: req,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Combine route parameters with the base context
|
|
||||||
baseCtx.params = {
|
baseCtx.params = {
|
||||||
...baseCtx.params,
|
...baseCtx.params,
|
||||||
...matchedRoute.params,
|
...matchedRoute.params,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Execute middleware chain
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
const executeMiddleware = async (): Promise<Response> => {
|
const executeMiddleware = async (): Promise<Response> => {
|
||||||
if (index < matchingMiddleware.length) {
|
if (index < matchingMiddleware.length) {
|
||||||
const { middleware, params } = matchingMiddleware[index++];
|
const { middleware, params } = matchingMiddleware[index++];
|
||||||
// Create a new context for each middleware with its specific parameters
|
|
||||||
const middlewareCtx: RouterContext = {
|
const middlewareCtx: RouterContext = {
|
||||||
...baseCtx,
|
...baseCtx,
|
||||||
params: { ...baseCtx.params, ...params },
|
params: { ...baseCtx.params, ...params },
|
||||||
pattern: middleware.pattern,
|
pattern: middleware.pattern,
|
||||||
};
|
};
|
||||||
return await middleware.handler(middlewareCtx, executeMiddleware);
|
return await middleware.handler(req, middlewareCtx, executeMiddleware);
|
||||||
}
|
}
|
||||||
// Final handler gets the accumulated parameters
|
|
||||||
return await handler(req, baseCtx);
|
return await handler(req, baseCtx);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -308,6 +302,309 @@ export class Router {
|
|||||||
? `/${normalizedRoute}`
|
? `/${normalizedRoute}`
|
||||||
: `${normalizedBase}/${normalizedRoute}`;
|
: `${normalizedBase}/${normalizedRoute}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serveDirectory(dir: string, root: string, opts?: { showIndex: boolean }) {
|
||||||
|
this.route(root + "*").get(async (_req, ctx) => {
|
||||||
|
const { showIndex } = opts ?? { showIndex: false };
|
||||||
|
|
||||||
|
let normalizedPath = (dir + "/" +
|
||||||
|
ctx.url.pathname.replace(new RegExp("^" + root), "")).trim().replace(
|
||||||
|
"//",
|
||||||
|
"/",
|
||||||
|
);
|
||||||
|
|
||||||
|
normalizedPath = normalizedPath.replace(
|
||||||
|
/\/\s?$/,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
let fileInfo: Deno.FileInfo;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fileInfo = await Deno.stat(normalizedPath);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Deno.errors.NotFound) {
|
||||||
|
return NotFound();
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileInfo.isDirectory) {
|
||||||
|
if (!showIndex) return NotFound();
|
||||||
|
normalizedPath += "/index.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const file = await Deno.readFile(normalizedPath);
|
||||||
|
return new Response(file);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Deno.errors.NotFound) {
|
||||||
|
return showIndex ? generateIndex(normalizedPath) : NotFound();
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateIndex(dir: string) {
|
||||||
|
dir = dir.replace(/index\.html$/, "");
|
||||||
|
const items: Deno.DirEntry[] = [];
|
||||||
|
|
||||||
|
for await (const entry of Deno.readDir(dir)) {
|
||||||
|
items.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileIcon = `
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 12.7 12.7"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
|
||||||
|
sodipodi:docname="file icon.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#505050"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="17.318182"
|
||||||
|
inkscape:cx="10.018373"
|
||||||
|
inkscape:cy="28.207349"
|
||||||
|
inkscape:window-width="3440"
|
||||||
|
inkscape:window-height="1417"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="1432"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
d="m 3.5196818,1.7373617 c -0.3351528,0 -0.605131,0.2694614 -0.605131,0.6046142 v 8.0160481 c 0,0.335153 0.2699782,0.604614 0.605131,0.604614 h 5.6606364 c 0.3351528,0 0.605131,-0.269461 0.605131,-0.604614 V 3.7372396 L 7.7850545,1.7373617 Z"
|
||||||
|
style="fill:#4d1f6d;stroke-width:0.264583"
|
||||||
|
id="path4" />
|
||||||
|
<path
|
||||||
|
d="M 9.6529679,3.8697209 7.6525732,1.869843 v 1.3952637 c 0,0.3351528 0.2699782,0.6046142 0.605131,0.6046142 z"
|
||||||
|
style="fill:#918b93;fill-opacity:1;stroke:#4d1f6d;stroke-width:0.265;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="path5" />
|
||||||
|
<rect
|
||||||
|
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.285269;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect5"
|
||||||
|
width="5.5833006"
|
||||||
|
height="0.5505569"
|
||||||
|
x="3.5583498"
|
||||||
|
y="4.0022206" />
|
||||||
|
<rect
|
||||||
|
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.272493;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect6"
|
||||||
|
width="5.0944114"
|
||||||
|
height="0.5505569"
|
||||||
|
x="3.5583498"
|
||||||
|
y="4.8455539" />
|
||||||
|
<rect
|
||||||
|
style="fill:#463e4b;fill-opacity:1;stroke:none;stroke-width:0.279751;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect7"
|
||||||
|
width="5.3694115"
|
||||||
|
height="0.5505569"
|
||||||
|
x="3.5583498"
|
||||||
|
y="5.682776" />
|
||||||
|
<rect
|
||||||
|
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.279751;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect8"
|
||||||
|
width="5.3694115"
|
||||||
|
height="0.5505569"
|
||||||
|
x="3.5583498"
|
||||||
|
y="5.6888871" />
|
||||||
|
<rect
|
||||||
|
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.285269;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect9"
|
||||||
|
width="5.5833006"
|
||||||
|
height="0.5505569"
|
||||||
|
x="3.5583498"
|
||||||
|
y="6.5322208" />
|
||||||
|
<rect
|
||||||
|
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.272493;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect10"
|
||||||
|
width="5.0944114"
|
||||||
|
height="0.5505569"
|
||||||
|
x="3.5583498"
|
||||||
|
y="7.3755541" />
|
||||||
|
<rect
|
||||||
|
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.279751;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect11"
|
||||||
|
width="5.3694115"
|
||||||
|
height="0.5505569"
|
||||||
|
x="3.5583498"
|
||||||
|
y="8.2188873" />
|
||||||
|
<rect
|
||||||
|
style="fill:#918b93;fill-opacity:1;stroke:none;stroke-width:0.217416;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||||
|
id="rect12"
|
||||||
|
width="3.2431545"
|
||||||
|
height="0.5505569"
|
||||||
|
x="5.8984962"
|
||||||
|
y="9.7313871" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
const folderIcon = `
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 12.7 12.7"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
|
||||||
|
sodipodi:docname="folder icon.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#505050"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="12.245804"
|
||||||
|
inkscape:cx="17.924507"
|
||||||
|
inkscape:cy="30.990207"
|
||||||
|
inkscape:window-width="3440"
|
||||||
|
inkscape:window-height="1417"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="1432"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:#463e4b;fill-opacity:1;stroke-width:0.264583"
|
||||||
|
id="rect3"
|
||||||
|
width="10.867838"
|
||||||
|
height="7.4972959"
|
||||||
|
x="0.91608107"
|
||||||
|
y="2.1476252"
|
||||||
|
ry="1.1883322" />
|
||||||
|
<rect
|
||||||
|
style="fill:#4d1f6d;fill-opacity:1;stroke-width:0.264583"
|
||||||
|
id="rect1"
|
||||||
|
width="10.867838"
|
||||||
|
height="7.4972959"
|
||||||
|
x="0.91608107"
|
||||||
|
y="2.601352"
|
||||||
|
ry="1.1883322" />
|
||||||
|
<rect
|
||||||
|
style="fill:#4d1f6d;fill-opacity:1;stroke-width:0.264583"
|
||||||
|
id="rect2"
|
||||||
|
width="4.2779961"
|
||||||
|
height="4.0619354"
|
||||||
|
x="0.91608107"
|
||||||
|
y="1.5426561"
|
||||||
|
ry="1.1883322" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const template = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>${dir}</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 4px 2px;
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
background-color: rgba(0,0,0, .2)
|
||||||
|
}
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
background-color: #1b1220;
|
||||||
|
color: #dcdcdc
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
background-color: rgba(255,255,255,.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>${dir}</h1>
|
||||||
|
<ul>
|
||||||
|
${
|
||||||
|
items
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.isDirectory && b.isDirectory) return a.name > b.name ? 1 : -1;
|
||||||
|
|
||||||
|
if (a.isDirectory) return -1;
|
||||||
|
|
||||||
|
if (b.isDirectory) return 1;
|
||||||
|
|
||||||
|
return a.name > b.name ? 1 : -1;
|
||||||
|
})
|
||||||
|
.map((e) =>
|
||||||
|
`<li>${e.isFile ? fileIcon : folderIcon}<a href="${
|
||||||
|
"/" + dir.replace(/^\//, "") + e.name
|
||||||
|
}">${e.name}</a></li>`
|
||||||
|
)
|
||||||
|
.join("\n") || "<li>Directory is empty</li>"
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return new Response(template, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "text/html",
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Router;
|
export default Router;
|
||||||
@ -361,7 +658,7 @@ if (import.meta.main) {
|
|||||||
// return new Response('POST /*');
|
// return new Response('POST /*');
|
||||||
// });
|
// });
|
||||||
|
|
||||||
router.use("/users", async (_, next) => {
|
router.use("/users", async (_req, _ctx, next) => {
|
||||||
console.log("Using middleware");
|
console.log("Using middleware");
|
||||||
return await next();
|
return await next();
|
||||||
});
|
});
|
||||||
|
1
types.ts
1
types.ts
@ -16,6 +16,7 @@ export type Handler = (
|
|||||||
ctx: RouterContext,
|
ctx: RouterContext,
|
||||||
) => Promise<Response> | Response;
|
) => Promise<Response> | Response;
|
||||||
export type Middleware = (
|
export type Middleware = (
|
||||||
|
req: Request,
|
||||||
ctx: RouterContext,
|
ctx: RouterContext,
|
||||||
next: () => Promise<Response>,
|
next: () => Promise<Response>,
|
||||||
) => Promise<Response>;
|
) => Promise<Response>;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user