bun-router 0.7.0 → 0.7.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 +20 -63
- package/examples/basic.ts +5 -5
- package/examples/db.ts +10 -0
- package/examples/dynamic.ts +3 -3
- package/examples/logger.ts +6 -11
- package/examples/static.ts +2 -2
- package/examples/todo.ts +2 -2
- package/lib/fs/fsys.ts +1 -1
- package/lib/http/{generic-methods.ts → http.ts} +7 -1
- package/lib/logger/color.ts +6 -6
- package/lib/logger/logger.d.ts +4 -5
- package/lib/logger/logger.ts +57 -64
- package/lib/router/context.ts +49 -0
- package/lib/router/router.d.ts +21 -22
- package/lib/router/router.ts +20 -38
- package/lib/router/tree.ts +4 -31
- package/lib/util/strings.ts +3 -0
- package/package.json +1 -1
- package/examples/cookies.ts +0 -15
package/README.md
CHANGED
@@ -1,83 +1,40 @@
|
|
1
|
-
# Bun
|
2
|
-
|
3
|
-
I needed a router for `Bun`, so I made one. It's simple, naive, and hardly anything is abstracted.
|
1
|
+
# Bun router
|
4
2
|
|
5
3
|
### Usage
|
6
|
-
|
7
|
-
import { router } from 'bun-router';
|
8
|
-
|
9
|
-
const r = router();
|
10
|
-
|
11
|
-
r.add('/', 'GET', (ctx) => new Response('Hello World'));
|
12
|
-
|
13
|
-
r.serve();
|
14
|
-
```
|
15
|
-
#### Static Files
|
16
|
-
```typescript
|
17
|
-
import { router } from 'bun-router';
|
18
|
-
|
19
|
-
const r = router();
|
20
|
-
|
21
|
-
r.static('/', './pages');
|
22
|
-
|
23
|
-
r.serve();
|
24
|
-
```
|
4
|
+
`npm i -s bun-router`
|
25
5
|
|
26
|
-
|
27
|
-
```typescript
|
28
|
-
import {router, html, json } from 'bun-router';
|
6
|
+
or
|
29
7
|
|
30
|
-
|
8
|
+
`bun i bun-router`
|
31
9
|
|
32
|
-
r.add('/', (ctx) => html('<h1>Hello World</h1>'));
|
33
10
|
|
34
|
-
|
35
|
-
|
36
|
-
|
11
|
+
#### Example
|
12
|
+
```ts
|
13
|
+
import { Router, http } from 'bun-router';
|
37
14
|
|
38
|
-
|
39
|
-
});
|
15
|
+
const router = Router();
|
40
16
|
|
41
|
-
|
17
|
+
router.add('/', 'GET', () => http.ok());
|
42
18
|
|
43
|
-
|
44
|
-
const
|
45
|
-
if (!id) return new Response('user not found', {status: 404});
|
46
|
-
|
47
|
-
const user = store.get(id);
|
19
|
+
router.get('/u/:username', ctx => {
|
20
|
+
const username = ctx.params.get('username');
|
48
21
|
|
49
|
-
if (!
|
22
|
+
if (!username) return http.badRequest();
|
50
23
|
|
51
|
-
return json(
|
24
|
+
return ctx.json({ username: username });
|
52
25
|
});
|
53
26
|
|
54
|
-
|
27
|
+
router.serve();
|
55
28
|
```
|
56
29
|
|
57
|
-
|
30
|
+
##### Static
|
58
31
|
```ts
|
59
|
-
import {
|
60
|
-
import { Database } from 'bun:sqlite';
|
61
|
-
|
62
|
-
const r = router(3000, {db: './examples/dbs/test.db'});
|
32
|
+
import { Router } from 'bun-router';
|
63
33
|
|
64
|
-
|
65
|
-
const name = ctx.params.get('name');
|
66
|
-
const rando = Math.floor(Math.random()*1000);
|
34
|
+
const router = Router();
|
67
35
|
|
68
|
-
|
69
|
-
|
70
|
-
return json({message: 'ok'});
|
71
|
-
});
|
36
|
+
router.static('/assets', 'static');
|
72
37
|
|
73
|
-
|
74
|
-
|
75
|
-
const data = ctx.db.query(`SELECT * FROM test WHERE name = "${name}";`).get();
|
76
|
-
const d = data as {id: number, name: string};
|
77
|
-
|
78
|
-
return d ? json(d) : new Response('not found', {status: 404});
|
79
|
-
});
|
80
|
-
|
81
|
-
r.serve();
|
38
|
+
router.serve();
|
39
|
+
```
|
82
40
|
|
83
|
-
```
|
package/examples/basic.ts
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
import {
|
1
|
+
import { Router, http } from '..';
|
2
2
|
|
3
|
-
const
|
3
|
+
const router = Router();
|
4
4
|
|
5
|
-
|
5
|
+
router.add('/', 'GET', () => http.json(200, 'ok'));
|
6
6
|
|
7
|
-
|
7
|
+
router.add('/user/:name', 'GET', (ctx) => {
|
8
8
|
const name = ctx.params.get('name');
|
9
9
|
if (!name) return http.json(500, 'no name');
|
10
10
|
return http.json(200, name);
|
11
11
|
});
|
12
12
|
|
13
|
-
|
13
|
+
router.serve();
|
package/examples/db.ts
ADDED
package/examples/dynamic.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
import {
|
1
|
+
import { Router, http } from '..';
|
2
2
|
import { Context } from '../lib/router/router.d';
|
3
3
|
|
4
|
-
const home = (
|
4
|
+
const home = () => new Response('Welcome Home', { status: 200 });
|
5
5
|
|
6
6
|
const subreddit = (ctx: Context) => {
|
7
7
|
const sub = ctx.params.get('subreddit');
|
@@ -15,7 +15,7 @@ const user = (ctx: Context) => {
|
|
15
15
|
return http.json(200, { user: user });
|
16
16
|
}
|
17
17
|
|
18
|
-
const r =
|
18
|
+
const r = Router();
|
19
19
|
|
20
20
|
r.add('/', 'GET', home);
|
21
21
|
r.add('/r/:subreddit', 'GET', subreddit);
|
package/examples/logger.ts
CHANGED
@@ -1,16 +1,11 @@
|
|
1
|
-
import {
|
1
|
+
import { Router, http } from '..';
|
2
2
|
|
3
|
-
const r =
|
4
|
-
const log = logger();
|
3
|
+
const r = Router();
|
5
4
|
|
6
|
-
r.
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
log.error(500, url.pathname, ctx.request.method, new Error('undefined'));
|
11
|
-
return http.json(500,{text: 'Foo is undefined'});
|
12
|
-
}
|
13
|
-
return http.html(200, `<h4 style='font-family: sans-serif;'>Oh hello, ${foo}</h4>`)
|
5
|
+
r.get('/', ctx => {
|
6
|
+
ctx.logger.debug('hello from home');
|
7
|
+
|
8
|
+
return http.ok();
|
14
9
|
});
|
15
10
|
|
16
11
|
r.serve();
|
package/examples/static.ts
CHANGED
package/examples/todo.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { Router, http } from '..';
|
2
2
|
import { Context } from '../lib/router/router.d';
|
3
3
|
|
4
4
|
const Todo = () => {
|
@@ -15,7 +15,7 @@ const Todo = () => {
|
|
15
15
|
|
16
16
|
const todo = Todo();
|
17
17
|
|
18
|
-
const r =
|
18
|
+
const r = Router();
|
19
19
|
|
20
20
|
r.add('/api/new', 'POST', ctx => {
|
21
21
|
const query = new URL(ctx.request.url).searchParams;
|
package/lib/fs/fsys.ts
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
import { httpStatusCodes } from "./status";
|
2
2
|
|
3
3
|
const http = {
|
4
|
+
ok: async (msg?: string): Promise<Response> => {
|
5
|
+
return Promise.resolve(new Response(msg ?? httpStatusCodes[200], {
|
6
|
+
status: 200,
|
7
|
+
statusText: httpStatusCodes[200],
|
8
|
+
}));
|
9
|
+
},
|
4
10
|
json: async (statusCode: number, data: any): Promise<Response> => {
|
5
11
|
const jsonString = JSON.stringify(data);
|
6
12
|
return Promise.resolve(new Response(jsonString, {
|
@@ -65,7 +71,7 @@ const http = {
|
|
65
71
|
headers: {'Content-Type': 'text/html; charset-utf-8'},
|
66
72
|
});
|
67
73
|
return Promise.resolve(response)
|
68
|
-
},
|
74
|
+
},
|
69
75
|
}
|
70
76
|
|
71
77
|
export { http }
|
package/lib/logger/color.ts
CHANGED
@@ -23,13 +23,13 @@ const Colors: Record<string,string> = {
|
|
23
23
|
} as const;
|
24
24
|
|
25
25
|
|
26
|
-
const color = (c: string, bkg: string, msg: string) => {
|
27
|
-
const foreground = Colors[c];
|
28
|
-
const background = Colors[bkg];
|
29
|
-
const reset = Colors.reset;
|
30
26
|
|
31
|
-
|
32
|
-
|
27
|
+
function color(foreground: string, background: string, message: string) {
|
28
|
+
const _foreground = Colors[foreground];
|
29
|
+
const _background = Colors[background];
|
30
|
+
const reset = Colors.reset;
|
31
|
+
return `${_foreground}${_background}${message}${reset}`;
|
32
|
+
}
|
33
33
|
|
34
34
|
|
35
35
|
|
package/lib/logger/logger.d.ts
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
type
|
2
|
-
start: (port: number | string) => void,
|
1
|
+
type BunLogger = {
|
3
2
|
info: (statusCode: number, routePath: string, method: string, message?: string) => void,
|
4
3
|
error: (statusCode: number, routePath: string, method: string, error: Error) => void,
|
5
|
-
warn: (
|
6
|
-
message: (
|
4
|
+
warn: (message: string) => void,
|
5
|
+
message: (message: string) => void,
|
7
6
|
}
|
8
7
|
|
9
|
-
export {
|
8
|
+
export { BunLogger }
|
package/lib/logger/logger.ts
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import { color } from './color';
|
2
|
-
import {
|
2
|
+
import { BunLogger } from './logger.d';
|
3
3
|
|
4
|
-
const pad = (n: number) => String(n).padStart(2, '0');
|
5
4
|
|
6
5
|
const TITLE = `
|
7
6
|
_ _
|
@@ -10,8 +9,50 @@ _ _
|
|
10
9
|
|___|___|_|_| |_| |___|___|_| |___|_|
|
11
10
|
|
12
11
|
`
|
12
|
+
const VERSION = '0.7.1';
|
13
|
+
const Logger = (): BunLogger => {
|
14
|
+
return {
|
15
|
+
info: async (statusCode: number, routePath: string, method: string, message?: string) => {
|
16
|
+
const { stamp } = timestamp((new Date(Date.now())));
|
17
|
+
const source = color('green', 'bgBlack', `[bun-router ${stamp}]`);
|
18
|
+
const rp = color('white', 'bgBlack', routePath);
|
19
|
+
|
20
|
+
message = `${source}: ${setColor(statusCode)}: ${rp} ${(method === 'GET') ? '->' : '<-'} ${method}${ message ?? ''}\n`
|
13
21
|
|
14
|
-
|
22
|
+
await Bun.write(Bun.stdout, message);
|
23
|
+
|
24
|
+
},
|
25
|
+
error: async (statusCode: number, routePath: string, method: string, error: Error) => {
|
26
|
+
const { stamp } = timestamp((new Date(Date.now())));
|
27
|
+
const source = color('black', 'bgRed', `[error ${stamp}]`);
|
28
|
+
const rp = color('white', 'bgBlack', routePath);
|
29
|
+
|
30
|
+
const message = `${source}: ${setColor(statusCode)}: ${rp} ${(method === 'GET') ? '->' : '<-'} ${error.message}\n`
|
31
|
+
|
32
|
+
await Bun.write(Bun.stdout, message);
|
33
|
+
},
|
34
|
+
warn: async (message: string) => {
|
35
|
+
const { stamp } = timestamp((new Date(Date.now())));
|
36
|
+
const source = color('black', 'bgYellow', `[warning ${stamp}]`);
|
37
|
+
const messageColor = color('yellow', 'bgBlack', message);
|
38
|
+
|
39
|
+
message = `${source} : ${messageColor}\n`;
|
40
|
+
|
41
|
+
await Bun.write(Bun.stdout, message);
|
42
|
+
},
|
43
|
+
message: async (message: string) => {
|
44
|
+
const { stamp } = timestamp((new Date(Date.now())));
|
45
|
+
const source = color('black', 'bgCyan', `[message ${stamp}]`);
|
46
|
+
const messageColor = color('yellow', 'bgBlack', message);
|
47
|
+
|
48
|
+
message = `${source}: ${messageColor}\n`;
|
49
|
+
|
50
|
+
await Bun.write(Bun.stdout, message);
|
51
|
+
},
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
function timestamp(date: Date) {
|
15
56
|
const month = pad(date.getMonth());
|
16
57
|
const day = pad(date.getDate());
|
17
58
|
const hour = pad(date.getHours());
|
@@ -22,80 +63,32 @@ const timestamp = (date: Date) => {
|
|
22
63
|
return {month, day, hour, minute, stamp};
|
23
64
|
}
|
24
65
|
|
25
|
-
|
26
|
-
const colorCode = (n: number, text?:string): string => {
|
66
|
+
function setColor(n: number, text?: string){
|
27
67
|
const s = ` [${String(n)}${text ?? ''}] `;
|
68
|
+
|
28
69
|
if (n < 100) return color('black', 'bgYellow', s);
|
29
70
|
else if (n >= 100 && n < 200) return color('black', 'bgCyan', s);
|
30
71
|
else if (n >= 200 && n < 300) return color('black', 'bgGreen', s);
|
31
72
|
else if (n >= 300 && n < 400) return color('black', 'bgRed', s);
|
32
73
|
else if (n >= 400 && n < 500) return color('black', 'bgRed', s);
|
33
74
|
else if (n >= 500) return color('white', 'bgRed', s);
|
75
|
+
|
34
76
|
return color('white', 'bgBlack', `[${s}]`).trim();
|
35
77
|
}
|
36
78
|
|
37
|
-
|
38
|
-
const clean = (s: string) => s.replace(/\x1B\[\d{1,2}(;\d{1,2}){0,2}m/g, '');
|
39
|
-
|
40
|
-
const format = (statusCode: number, routePath: string, method: string, message?: string): string => {
|
79
|
+
function startMessage(port: number | string) {
|
41
80
|
const { stamp } = timestamp((new Date(Date.now())));
|
42
|
-
const source = color('green', 'bgBlack', `[bun-router ${stamp}]`)
|
43
|
-
const
|
81
|
+
const source = color('green', 'bgBlack', `[bun-router ${stamp}]`)
|
82
|
+
const portColor = color('green', 'bgBlack', String(port));
|
83
|
+
const msg = `${source}: Starting Server on :${portColor}\n`;
|
84
|
+
const version = color('red', 'bgBlack', `v${VERSION}\n`);
|
44
85
|
|
45
|
-
|
86
|
+
Bun.write(Bun.stdout, TITLE + '\n' + version);
|
87
|
+
Bun.write(Bun.stdout, msg);
|
46
88
|
}
|
47
89
|
|
48
|
-
|
49
|
-
|
50
|
-
const errors: string[] = [];
|
51
|
-
return {
|
52
|
-
// initial log message
|
53
|
-
start: async (port: number | string) => {
|
54
|
-
const { stamp } = timestamp((new Date(Date.now())));
|
55
|
-
const source = color('green', 'bgBlack', `[bun-router ${stamp}]`)
|
56
|
-
const portColor = color('green', 'bgBlack', String(port));
|
57
|
-
const msg = `${source}: Starting Server on :${portColor}\n`;
|
58
|
-
|
59
|
-
await Bun.write(Bun.stdout, TITLE);
|
60
|
-
await Bun.write(Bun.stdout, msg);
|
61
|
-
},
|
62
|
-
info: async (statusCode: number, routePath: string, method: string, message?: string) => {
|
63
|
-
const { stamp } = timestamp((new Date(Date.now())));
|
64
|
-
const source = color('green', 'bgBlack', `[bun-router ${stamp}]`);
|
65
|
-
const rp = color('white', 'bgBlack', routePath);
|
66
|
-
const msg = `${source}: ${colorCode(statusCode)}: ${rp} ${(method === 'GET') ? '->' : '<-'} ${method}${' | ' +message ?? ''}\n`
|
67
|
-
|
68
|
-
await Bun.write(Bun.stdout, msg);
|
69
|
-
|
70
|
-
messages.push(clean(msg));
|
71
|
-
},
|
72
|
-
error: async (statusCode: number, routePath: string, method: string, error: Error) => {
|
73
|
-
const { stamp } = timestamp((new Date(Date.now())));
|
74
|
-
const source = color('black', 'bgRed', `[error ${stamp}]`);
|
75
|
-
const rp = color('white', 'bgBlack', routePath);
|
76
|
-
const msg = `${source}: ${colorCode(statusCode)}: ${rp} ${(method === 'GET') ? '->' : '<-'} ${error.message}\n`;
|
77
|
-
|
78
|
-
await Bun.write(Bun.stdout, msg);
|
79
|
-
|
80
|
-
errors.push(clean(msg));
|
81
|
-
},
|
82
|
-
warn: async (msg: string) => {
|
83
|
-
const { stamp } = timestamp((new Date(Date.now())));
|
84
|
-
const source = color('black', 'bgYellow', `[warning ${stamp}]`);
|
85
|
-
const msgColor = color('yellow', 'bgBlack', msg);
|
86
|
-
msg = `${source} : ${msgColor}\n`;
|
87
|
-
await Bun.write(Bun.stdout, msg);
|
88
|
-
},
|
89
|
-
message: async (msg: string) => {
|
90
|
-
const { stamp } = timestamp((new Date(Date.now())));
|
91
|
-
const source = color('black', 'bgCyan', `[message ${stamp}]`);
|
92
|
-
const msgColor = color('yellow', 'bgBlack', msg);
|
93
|
-
msg = `${source}: ${msgColor}\n`;
|
94
|
-
await Bun.write(Bun.stdout, msg);
|
95
|
-
|
96
|
-
messages.push(clean(msg));
|
97
|
-
}
|
98
|
-
}
|
90
|
+
function pad(n: number) {
|
91
|
+
return String(n).padStart(2, '0');
|
99
92
|
}
|
100
93
|
|
101
|
-
export {
|
94
|
+
export { Logger, startMessage }
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import { Route, Context } from "./router.d";
|
2
|
+
import { Logger } from "../..";
|
3
|
+
import { http } from "./router";
|
4
|
+
|
5
|
+
function extractParams(path: string, route: Route): Map<string, string> {
|
6
|
+
const params: Map<string, string> = new Map();
|
7
|
+
const pathSegments = path.split('/');
|
8
|
+
const routeSegments = route.path.split('/');
|
9
|
+
|
10
|
+
if (pathSegments.length !== routeSegments.length) return params;
|
11
|
+
|
12
|
+
for (let i = 0; i < pathSegments.length; i++) {
|
13
|
+
if (routeSegments[i][0] === ':') {
|
14
|
+
const key = routeSegments[i].replace(':', '');
|
15
|
+
const value = pathSegments[i];
|
16
|
+
params.set(key, value);
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
return params;
|
21
|
+
}
|
22
|
+
|
23
|
+
async function createContext(path: string, route: Route, request: Request): Promise<Context> {
|
24
|
+
const params = extractParams(path, route);
|
25
|
+
const query = new URLSearchParams(path);
|
26
|
+
const formData = isMultiPartForm(request.headers) ? await request.formData() : new FormData();
|
27
|
+
|
28
|
+
return Promise.resolve({
|
29
|
+
params,
|
30
|
+
request,
|
31
|
+
query,
|
32
|
+
formData,
|
33
|
+
logger: Logger(),
|
34
|
+
json: (statusCode: number, data: any) => http.json(statusCode, data),
|
35
|
+
});
|
36
|
+
}
|
37
|
+
|
38
|
+
function getContentType(headers: Headers): string {
|
39
|
+
const contentType = headers.get('Content-Type');
|
40
|
+
if (!contentType) return '';
|
41
|
+
return contentType;
|
42
|
+
}
|
43
|
+
|
44
|
+
function isMultiPartForm(headers: Headers): boolean {
|
45
|
+
const contentType = getContentType(headers);
|
46
|
+
return contentType.includes('multipart/form-data');
|
47
|
+
}
|
48
|
+
|
49
|
+
export { createContext }
|
package/lib/router/router.d.ts
CHANGED
@@ -2,12 +2,28 @@ import { TLSOptions, TLSWebSocketServeOptions, WebSocketServeOptions, ServeOptio
|
|
2
2
|
import { Logger } from '../logger/logger';
|
3
3
|
import { Database } from 'bun:sqlite';
|
4
4
|
|
5
|
-
type
|
5
|
+
type BunRouter = (port?: number | string, options?: RouterOptions) => {
|
6
|
+
add: (pattern: string, method: string, callback: HttpHandler) => void;
|
7
|
+
get: (pattern: string, callback: HttpHandler) => void;
|
8
|
+
post: (pattern: string, callback: HttpHandler) => void;
|
9
|
+
put: (pattern: string, callback: HttpHandler) => void;
|
10
|
+
delete: (pattern: string, callback: HttpHandler) => void;
|
11
|
+
static: (pattern: string, root: string) => void;
|
12
|
+
serve: () => void;
|
13
|
+
}
|
14
|
+
|
15
|
+
type Route = {
|
16
|
+
children: Map<string, Route>;
|
17
|
+
path: string;
|
18
|
+
dynamicPath: string;
|
19
|
+
method: string;
|
20
|
+
handler: HttpHandler;
|
21
|
+
isLast: boolean;
|
22
|
+
}
|
6
23
|
|
7
24
|
type Context = {
|
8
|
-
cookies: Map<string, string>;
|
9
25
|
db?: Database;
|
10
|
-
formData: FormData | Promise<FormData
|
26
|
+
formData: FormData | Promise<FormData>;
|
11
27
|
json: (statusCode: number, data: any) => Response | Promise<Response>;
|
12
28
|
logger: Logger;
|
13
29
|
params: Map<string, string>;
|
@@ -16,15 +32,7 @@ type Context = {
|
|
16
32
|
token?: string;
|
17
33
|
};
|
18
34
|
|
19
|
-
|
20
|
-
type Route = {
|
21
|
-
children: Map<string, Route>;
|
22
|
-
path: string;
|
23
|
-
dynamicPath: string;
|
24
|
-
method: string;
|
25
|
-
handler: HttpHandler;
|
26
|
-
isLast: boolean;
|
27
|
-
}
|
35
|
+
type HttpHandler = (ctx: Context) => Response | Promise<Response>
|
28
36
|
|
29
37
|
type Options = {
|
30
38
|
db: string,
|
@@ -36,13 +44,4 @@ type RouterOptions<Options> = ServeOptions
|
|
36
44
|
| TLSWebSocketServeOptions<Options>
|
37
45
|
| undefined
|
38
46
|
|
39
|
-
|
40
|
-
type Router = (port?: number | string, options?: RouterOptions) => {
|
41
|
-
add: (pattern: string, method: string, callback: (req: Context) => Response | Promise<Response>) => void;
|
42
|
-
static: (pattern: string, root: string) => void;
|
43
|
-
serve: () => void;
|
44
|
-
}
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
export { Context , Route, Router, RouterOptions, Options, HttpHandler }
|
47
|
+
export { Context , Route, BunRouter, RouterOptions, Options, HttpHandler }
|
package/lib/router/router.ts
CHANGED
@@ -1,42 +1,26 @@
|
|
1
1
|
import path from 'path';
|
2
2
|
import { Database } from 'bun:sqlite';
|
3
|
-
import { Route,
|
3
|
+
import { Route, BunRouter, Context, RouterOptions, Options, HttpHandler } from './router.d';
|
4
4
|
import { httpStatusCodes } from '../http/status';
|
5
5
|
import { readDir } from '../fs/fsys';
|
6
|
-
import {
|
7
|
-
import { http } from '../http/
|
8
|
-
import {
|
6
|
+
import { Logger, startMessage } from '../logger/logger';
|
7
|
+
import { http } from '../http/http';
|
8
|
+
import {RouteTree } from './tree';
|
9
|
+
import { createContext } from './context';
|
9
10
|
|
10
|
-
const extract = (path: string, ctx: Context) => {
|
11
|
-
const url = new URL(ctx.request.url);
|
12
|
-
const pathSegments = path.split('/');
|
13
|
-
const urlSegments = url.pathname.split('/');
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
params: () => {
|
19
|
-
for (let i = 0; i < pathSegments.length; i++) {
|
20
|
-
if((pathSegments[i][0] === ':')) {
|
21
|
-
const k = pathSegments[i].replace(':', '');
|
22
|
-
const v = urlSegments[i];
|
23
|
-
ctx.params.set(k,v);
|
24
|
-
}
|
25
|
-
}
|
26
|
-
}
|
27
|
-
}
|
28
|
-
|
29
|
-
}
|
30
|
-
|
31
|
-
const router: Router = (port?: number | string, options?: RouterOptions<Options>) => {
|
32
|
-
const {addRoute, findRoute} = Radix();
|
33
|
-
const lgr = logger();
|
12
|
+
const Router: BunRouter = (port?: number | string, options?: RouterOptions<Options>) => {
|
13
|
+
const { addRoute, findRoute } = RouteTree();
|
14
|
+
const logger = Logger();
|
34
15
|
|
35
16
|
return {
|
36
17
|
// add a route to the router tree
|
37
|
-
add: (pattern
|
38
|
-
|
39
|
-
},
|
18
|
+
add: (pattern, method, callback) => { addRoute(pattern, method, callback) },
|
19
|
+
get: (pattern: string, callback: HttpHandler) => { addRoute(pattern, 'GET', callback) },
|
20
|
+
post: (pattern, callback) => { addRoute(pattern, 'POST', callback) },
|
21
|
+
put: (pattern, callback) => { addRoute(pattern, 'PUT', callback)},
|
22
|
+
delete: (pattern, callback) => { addRoute(pattern, 'DELETE', callback) },
|
23
|
+
|
40
24
|
// add a static route to the router tree
|
41
25
|
static: async (pattern: string, root: string) => {
|
42
26
|
await readDir(root, async (fp, _) => {
|
@@ -67,10 +51,9 @@ const router: Router = (port?: number | string, options?: RouterOptions<Options>
|
|
67
51
|
},
|
68
52
|
// start the server
|
69
53
|
serve: () => {
|
70
|
-
|
54
|
+
startMessage(port ?? 3000);
|
71
55
|
let opts: Options = { db: ':memory:' };
|
72
56
|
|
73
|
-
// TODO: add support for TLS and WebSockets
|
74
57
|
Bun.serve({
|
75
58
|
port: port ?? 3000,
|
76
59
|
...options,
|
@@ -89,23 +72,23 @@ const router: Router = (port?: number | string, options?: RouterOptions<Options>
|
|
89
72
|
// if the route exists, execute the handler
|
90
73
|
if (route) {
|
91
74
|
if (route.method !== req.method) {
|
92
|
-
|
75
|
+
logger.info(405, url.pathname, req.method, httpStatusCodes[405]);
|
93
76
|
return Promise.resolve(http.methodNotAllowed());
|
94
77
|
}
|
95
78
|
|
96
|
-
const context = createContext(path, route, req);
|
79
|
+
const context = await createContext(path, route, req);
|
97
80
|
context.db = new Database(opts.db);
|
98
81
|
|
99
82
|
const response = await route.handler(context);
|
100
83
|
|
101
|
-
|
84
|
+
logger.info(response.status, url.pathname, req.method, httpStatusCodes[response.status]);
|
102
85
|
return Promise.resolve(response);
|
103
86
|
}
|
104
87
|
|
105
88
|
// if no route is found, return 404
|
106
89
|
const response = await http.notFound();
|
107
90
|
|
108
|
-
|
91
|
+
logger.info(response.status, url.pathname, req.method, httpStatusCodes[response.status]);
|
109
92
|
return Promise.resolve(http.notFound());
|
110
93
|
|
111
94
|
}
|
@@ -114,5 +97,4 @@ const router: Router = (port?: number | string, options?: RouterOptions<Options>
|
|
114
97
|
}
|
115
98
|
}
|
116
99
|
|
117
|
-
|
118
|
-
export { router, extract, http }
|
100
|
+
export { Router, http }
|
package/lib/router/tree.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import { HttpHandler, Context, Route } from "./router.d";
|
2
|
-
import { http } from "../http/
|
2
|
+
import { http } from "../http/http";
|
3
|
+
import { createContext } from './context';
|
3
4
|
|
4
5
|
const splitPath = (s: string): string[] => s.split('/').filter(x => x !== '');
|
5
6
|
|
@@ -16,35 +17,7 @@ const createRoute = (path: string, method: string, handler: HttpHandler): Route
|
|
16
17
|
return route;
|
17
18
|
};
|
18
19
|
|
19
|
-
const
|
20
|
-
const pathParts = splitPath(path);
|
21
|
-
const routeParts = splitPath(route.path);
|
22
|
-
|
23
|
-
for (let i = 0; i < routeParts.length; i++) {
|
24
|
-
const part = routeParts[i];
|
25
|
-
if (part.startsWith(':')) {
|
26
|
-
params.set(part.slice(1), pathParts[i]);
|
27
|
-
}
|
28
|
-
}
|
29
|
-
};
|
30
|
-
|
31
|
-
const createContext = (path: string, route: Route, req: Request): Context => {
|
32
|
-
const params: Map<string, string> = new Map();
|
33
|
-
|
34
|
-
if (route) extractParams(path, route, params);
|
35
|
-
|
36
|
-
return {
|
37
|
-
params: params,
|
38
|
-
request: req,
|
39
|
-
query: new URLSearchParams(path),
|
40
|
-
cookies: new Map(),
|
41
|
-
formData: undefined,
|
42
|
-
logger: undefined,
|
43
|
-
json: (statusCode: number, data: any) => http.json(statusCode, data),
|
44
|
-
}
|
45
|
-
};
|
46
|
-
|
47
|
-
const Radix = () => {
|
20
|
+
const RouteTree = () => {
|
48
21
|
let root = createRoute('', 'GET', () => http.notFound());
|
49
22
|
|
50
23
|
const addRoute = (path: string, method: string, handler: HttpHandler) => {
|
@@ -87,4 +60,4 @@ const Radix = () => {
|
|
87
60
|
|
88
61
|
};
|
89
62
|
|
90
|
-
export {
|
63
|
+
export { RouteTree, createContext }
|
package/package.json
CHANGED
package/examples/cookies.ts
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
import { router } from '..';
|
2
|
-
|
3
|
-
const r = router();
|
4
|
-
|
5
|
-
r.add('/set-cookie', 'GET', ctx => {
|
6
|
-
ctx.cookies.set('domain', 'localhost');
|
7
|
-
ctx.cookies.set('path', '/set-cookie');
|
8
|
-
|
9
|
-
ctx.logger.message(ctx.token ?? 'no token provided');
|
10
|
-
|
11
|
-
return ctx.json( 200, {message: 'cookie stored'});
|
12
|
-
});
|
13
|
-
|
14
|
-
|
15
|
-
r.serve();
|