bun-router 0.7.3 → 0.7.4-experimental.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +34 -0
- package/README.md +23 -0
- package/bun.lockb +0 -0
- package/lib/fs/fsys.ts +5 -1
- package/lib/http/http.ts +68 -67
- package/lib/logger/logger.ts +59 -59
- package/lib/router/context.ts +41 -34
- package/lib/router/router.d.ts +4 -3
- package/lib/router/router.ts +85 -85
- package/lib/router/tree.ts +53 -54
- package/package.json +11 -2
- package/tsconfig.json +0 -1
package/.eslintrc.json
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
{
|
2
|
+
"env": {
|
3
|
+
"browser": true,
|
4
|
+
"es2021": true
|
5
|
+
},
|
6
|
+
"extends": [
|
7
|
+
"eslint:recommended",
|
8
|
+
"plugin:@typescript-eslint/recommended",
|
9
|
+
"plugin:react/recommended"
|
10
|
+
],
|
11
|
+
"parser": "@typescript-eslint/parser",
|
12
|
+
"parserOptions": {
|
13
|
+
"ecmaVersion": "latest",
|
14
|
+
"sourceType": "module"
|
15
|
+
},
|
16
|
+
"plugins": [
|
17
|
+
"@typescript-eslint",
|
18
|
+
"react"
|
19
|
+
],
|
20
|
+
"rules": {
|
21
|
+
"indent": [
|
22
|
+
"warn",
|
23
|
+
"tab"
|
24
|
+
],
|
25
|
+
"quotes": [
|
26
|
+
"error",
|
27
|
+
"single"
|
28
|
+
],
|
29
|
+
"semi": [
|
30
|
+
"error",
|
31
|
+
"always"
|
32
|
+
]
|
33
|
+
}
|
34
|
+
}
|
package/README.md
CHANGED
@@ -53,4 +53,27 @@ router.post('/register', ctx => {
|
|
53
53
|
|
54
54
|
```
|
55
55
|
|
56
|
+
##### JSX
|
57
|
+
```tsx
|
58
|
+
// ./pages/home.tsx
|
59
|
+
export default const Home = (title: string) => {
|
60
|
+
return (
|
61
|
+
<main>
|
62
|
+
<h1>{ title }</h1>
|
63
|
+
</main>
|
64
|
+
);
|
65
|
+
};
|
66
|
+
```
|
67
|
+
|
68
|
+
```ts
|
69
|
+
// ./index.ts
|
70
|
+
import { Router } from 'bun-router';
|
71
|
+
import Home from './pages/home';
|
72
|
+
|
73
|
+
const router = Router();
|
74
|
+
|
75
|
+
router.get('/', ctx => ctx.render(Home('Hello World')))
|
76
|
+
|
77
|
+
router.serve();
|
78
|
+
```
|
56
79
|
|
package/bun.lockb
CHANGED
Binary file
|
package/lib/fs/fsys.ts
CHANGED
package/lib/http/http.ts
CHANGED
@@ -1,77 +1,78 @@
|
|
1
|
-
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
|
+
import { httpStatusCodes } from './status';
|
2
3
|
|
3
4
|
const http = {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
5
|
+
ok: async (msg?: string): Promise<Response> => {
|
6
|
+
return Promise.resolve(new Response(msg ?? httpStatusCodes[200], {
|
7
|
+
status: 200,
|
8
|
+
statusText: httpStatusCodes[200],
|
9
|
+
}));
|
10
|
+
},
|
11
|
+
json: async (statusCode: number, data: any): Promise<Response> => {
|
12
|
+
const jsonString = JSON.stringify(data);
|
13
|
+
return Promise.resolve(new Response(jsonString, {
|
14
|
+
status: statusCode,
|
15
|
+
statusText: httpStatusCodes[statusCode],
|
16
|
+
headers: {'Content-Type': 'application/json'},
|
17
|
+
}));
|
18
|
+
},
|
19
|
+
html: async (statusCode: number, content: string): Promise<Response> => {
|
20
|
+
return Promise.resolve(new Response(content, {
|
21
|
+
status: statusCode,
|
22
|
+
statusText: httpStatusCodes[statusCode],
|
23
|
+
headers: {'Content-Type': 'text/html; charset=utf-8'}
|
24
|
+
}));
|
25
|
+
},
|
26
|
+
file: async (statusCode: number, fp: string): Promise<Response> => {
|
27
|
+
const file = Bun.file(fp);
|
28
|
+
const exists = await file.exists();
|
28
29
|
|
29
|
-
|
30
|
+
if (!exists) return http.notFound(`File not found: ${fp}`);
|
30
31
|
|
31
|
-
|
32
|
-
|
32
|
+
const content = await file.arrayBuffer();
|
33
|
+
if (!content) return http.noContent();
|
33
34
|
|
34
|
-
|
35
|
+
let contentType = 'text/html; charset=utf-9';
|
35
36
|
|
36
|
-
|
37
|
-
|
37
|
+
if (file.type.includes('image'))
|
38
|
+
contentType = file.type + '; charset=utf-8';
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
40
|
+
return Promise.resolve(new Response(content, {
|
41
|
+
status: statusCode,
|
42
|
+
statusText: httpStatusCodes[statusCode],
|
43
|
+
headers: { 'Content-Type': contentType}
|
44
|
+
}));
|
45
|
+
},
|
46
|
+
noContent: async (): Promise<Response> => Promise.resolve(new Response('no content', {
|
47
|
+
status: 204,
|
48
|
+
statusText: 'no content',
|
49
|
+
})),
|
50
|
+
notFound: async(msg?: string): Promise<Response> => {
|
51
|
+
const response = new Response(msg ?? 'not found', {
|
52
|
+
status: 404,
|
53
|
+
statusText: httpStatusCodes[404],
|
54
|
+
headers: {'Content-Type': 'text/html'},
|
55
|
+
});
|
55
56
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
57
|
+
return Promise.resolve(response);
|
58
|
+
},
|
59
|
+
methodNotAllowed: async (msg?: string): Promise<Response> => {
|
60
|
+
const response = new Response(msg ?? 'method not allowed', {
|
61
|
+
status: 405,
|
62
|
+
statusText: httpStatusCodes[405],
|
63
|
+
headers: {'Content-Type': 'text/html'},
|
64
|
+
});
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
}
|
66
|
+
return Promise.resolve(response);
|
67
|
+
},
|
68
|
+
message: async (status: number, msg?: string): Promise<Response> => {
|
69
|
+
const response = new Response(msg ?? '?', {
|
70
|
+
status: status,
|
71
|
+
statusText: httpStatusCodes[status],
|
72
|
+
headers: {'Content-Type': 'text/html; charset-utf-8'},
|
73
|
+
});
|
74
|
+
return Promise.resolve(response);
|
75
|
+
},
|
76
|
+
};
|
76
77
|
|
77
|
-
export { http }
|
78
|
+
export { http };
|
package/lib/logger/logger.ts
CHANGED
@@ -8,87 +8,87 @@ _ _
|
|
8
8
|
| . | | | | | _| . | | | _| -_| _|
|
9
9
|
|___|___|_|_| |_| |___|___|_| |___|_|
|
10
10
|
|
11
|
-
|
12
|
-
const VERSION = '0.7.
|
11
|
+
`;
|
12
|
+
const VERSION = '0.7.4-experimental';
|
13
13
|
const Logger = (): BunLogger => {
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
19
|
|
20
|
-
|
20
|
+
message = `${source}: ${setColor(statusCode)}: ${rp} ${(method === 'GET') ? ' ->' : ' <-'} ${method} ${ message ?? ''}\n`;
|
21
21
|
|
22
|
-
|
22
|
+
await Bun.write(Bun.stdout, message);
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
29
|
|
30
|
-
|
30
|
+
const message = `${source}: ${setColor(statusCode)}: ${rp} ${(method === 'GET') ? ' -> ' : ' <-'} ${error.message}\n`;
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
38
|
|
39
|
-
|
39
|
+
message = `${source} : ${messageColor}\n`;
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
47
|
|
48
|
-
|
48
|
+
message = `${source}: ${messageColor}\n`;
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
}
|
50
|
+
await Bun.write(Bun.stdout, message);
|
51
|
+
},
|
52
|
+
};
|
53
|
+
};
|
54
54
|
|
55
55
|
function timestamp(date: Date) {
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
56
|
+
const month = pad(date.getMonth());
|
57
|
+
const day = pad(date.getDate());
|
58
|
+
const hour = pad(date.getHours());
|
59
|
+
const minute = pad(date.getMinutes());
|
60
|
+
const seconds = pad(date.getSeconds());
|
61
|
+
const stamp = `${hour}:${minute}:${seconds}`;
|
62
|
+
|
63
|
+
return {month, day, hour, minute, stamp};
|
64
64
|
}
|
65
65
|
|
66
66
|
function setColor(n: number, text?: string){
|
67
|
-
|
67
|
+
const s = ` [${String(n)}${text ?? ''}] `;
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
69
|
+
if (n < 100) return color('black', 'bgYellow', s);
|
70
|
+
else if (n >= 100 && n < 200) return color('black', 'bgCyan', s);
|
71
|
+
else if (n >= 200 && n < 300) return color('black', 'bgGreen', s);
|
72
|
+
else if (n >= 300 && n < 400) return color('black', 'bgRed', s);
|
73
|
+
else if (n >= 400 && n < 500) return color('black', 'bgRed', s);
|
74
|
+
else if (n >= 500) return color('white', 'bgRed', s);
|
75
75
|
|
76
|
-
|
76
|
+
return color('white', 'bgBlack', `[${s}]`).trim();
|
77
77
|
}
|
78
78
|
|
79
79
|
function startMessage(port: number | string) {
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
80
|
+
const { stamp } = timestamp((new Date(Date.now())));
|
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`);
|
85
|
+
|
86
|
+
Bun.write(Bun.stdout, TITLE + '\n' + version);
|
87
|
+
Bun.write(Bun.stdout, msg);
|
88
88
|
}
|
89
89
|
|
90
90
|
function pad(n: number) {
|
91
|
-
|
91
|
+
return String(n).padStart(2, '0');
|
92
92
|
}
|
93
93
|
|
94
|
-
export { Logger, startMessage }
|
94
|
+
export { Logger, startMessage };
|
package/lib/router/context.ts
CHANGED
@@ -1,51 +1,58 @@
|
|
1
|
-
|
2
|
-
import {
|
3
|
-
import {
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
|
+
import { Route, Context } from './router.d';
|
3
|
+
import { renderToReadableStream } from 'react-dom/server';
|
4
|
+
import { Logger } from '../logger/logger';
|
5
|
+
import { http } from './router';
|
6
|
+
import { ReactNode } from 'react';
|
4
7
|
|
5
8
|
async function createContext(path: string, route: Route, request: Request): Promise<Context> {
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
const params = extractParams(path, route);
|
10
|
+
const query = new URLSearchParams(path);
|
11
|
+
const formData = isMultiPartForm(request.headers) ? await request.formData() : new FormData();
|
12
|
+
|
13
|
+
return Promise.resolve({
|
14
|
+
params,
|
15
|
+
request,
|
16
|
+
query,
|
17
|
+
formData,
|
18
|
+
logger: Logger(),
|
19
|
+
json: (statusCode: number, data: any) => http.json(statusCode, data),
|
20
|
+
render: async (component: ReactNode) => await renderStream(component),
|
21
|
+
});
|
18
22
|
}
|
19
23
|
|
20
24
|
function extractParams(path: string, route: Route): Map<string, string> {
|
21
|
-
|
22
|
-
|
23
|
-
|
25
|
+
const params: Map<string, string> = new Map();
|
26
|
+
const pathSegments = path.split('/');
|
27
|
+
const routeSegments = route.path.split('/');
|
24
28
|
|
25
|
-
|
29
|
+
if (pathSegments.length !== routeSegments.length) return params;
|
26
30
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
for (let i = 0; i < pathSegments.length; i++) {
|
32
|
+
if (routeSegments[i][0] === ':') {
|
33
|
+
const key = routeSegments[i].replace(':', '');
|
34
|
+
const value = pathSegments[i];
|
35
|
+
params.set(key, value);
|
36
|
+
}
|
37
|
+
}
|
34
38
|
|
35
|
-
|
39
|
+
return params;
|
36
40
|
}
|
37
41
|
|
38
42
|
function getContentType(headers: Headers): string {
|
39
|
-
|
40
|
-
|
41
|
-
|
43
|
+
const contentType = headers.get('Content-Type');
|
44
|
+
if (!contentType) return '';
|
45
|
+
return contentType;
|
42
46
|
}
|
43
47
|
|
44
48
|
function isMultiPartForm(headers: Headers): boolean {
|
45
|
-
|
46
|
-
|
49
|
+
const contentType = getContentType(headers);
|
50
|
+
return contentType.includes('multipart/form-data');
|
47
51
|
}
|
48
52
|
|
53
|
+
async function renderStream(children: ReactNode) {
|
54
|
+
const stream = await renderToReadableStream(children);
|
55
|
+
return new Response(stream, { headers: { 'Content-Type': 'text/html' } });
|
56
|
+
}
|
49
57
|
|
50
|
-
|
51
|
-
export { createContext }
|
58
|
+
export { createContext };
|
package/lib/router/router.d.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
|
+
import { TLSWebSocketServeOptions, WebSocketServeOptions, ServeOptions, TLSServeOptions } from 'bun';
|
2
3
|
import { Logger } from '../logger/logger';
|
3
4
|
import { Database } from 'bun:sqlite';
|
4
5
|
|
@@ -20,7 +21,6 @@ type Route = {
|
|
20
21
|
handler: HttpHandler;
|
21
22
|
isLast: boolean;
|
22
23
|
}
|
23
|
-
|
24
24
|
type Context = {
|
25
25
|
db?: Database;
|
26
26
|
formData: FormData | Promise<FormData>;
|
@@ -29,6 +29,7 @@ type Context = {
|
|
29
29
|
params: Map<string, string>;
|
30
30
|
query: URLSearchParams;
|
31
31
|
request: Request;
|
32
|
+
render: (component: React.ReactNode) => Response | Promise<Response>;
|
32
33
|
};
|
33
34
|
|
34
35
|
type HttpHandler = (ctx: Context) => Response | Promise<Response>
|
@@ -43,4 +44,4 @@ type RouterOptions<Options> = ServeOptions
|
|
43
44
|
| TLSWebSocketServeOptions<Options>
|
44
45
|
| undefined
|
45
46
|
|
46
|
-
export { Context , Route, BunRouter, RouterOptions, Options, HttpHandler }
|
47
|
+
export { Context , Route, BunRouter, RouterOptions, Options, HttpHandler };
|
package/lib/router/router.ts
CHANGED
@@ -5,96 +5,96 @@ import { httpStatusCodes } from '../http/status';
|
|
5
5
|
import { readDir } from '../fs/fsys';
|
6
6
|
import { Logger, startMessage } from '../logger/logger';
|
7
7
|
import { http } from '../http/http';
|
8
|
-
import {RouteTree } from './tree';
|
8
|
+
import { RouteTree } from './tree';
|
9
9
|
import { createContext } from './context';
|
10
10
|
|
11
11
|
|
12
12
|
const Router: BunRouter = (port?: number | string, options?: RouterOptions<Options>) => {
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
13
|
+
const { addRoute, findRoute } = RouteTree();
|
14
|
+
const logger = Logger();
|
15
|
+
|
16
|
+
return {
|
17
|
+
// add a route to the router tree
|
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
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
24
|
+
// add a static route to the router tree
|
25
|
+
static: async (pattern: string, root: string) => {
|
26
|
+
await readDir(root, async (fp) => {
|
27
|
+
const pure = path.join('.', fp);
|
28
|
+
const ext = path.extname(pure);
|
29
|
+
|
30
|
+
let base = path.basename(pure);
|
31
|
+
|
32
|
+
if (ext === '.html') base = base.replace(ext, '');
|
33
|
+
|
34
|
+
if (pattern[0] !== '/') pattern = '/' + pattern;
|
35
|
+
|
36
|
+
let patternPath = pattern + base;
|
37
|
+
|
38
|
+
if (base === 'index') patternPath = pattern;
|
39
|
+
|
40
|
+
const route: Route = {
|
41
|
+
children: new Map(),
|
42
|
+
dynamicPath: '',
|
43
|
+
isLast: true,
|
44
|
+
path: patternPath,
|
45
|
+
method: 'GET',
|
46
|
+
handler: async () => await http.file(200, pure),
|
47
|
+
};
|
48
|
+
|
49
|
+
addRoute(route.path, 'GET', route.handler);
|
50
|
+
});
|
51
|
+
},
|
52
|
+
// start the server
|
53
|
+
serve: () => {
|
54
|
+
startMessage(port ?? 3000);
|
55
|
+
const opts: Options = { db: ':memory:' };
|
56
|
+
|
57
|
+
Bun.serve({
|
58
|
+
port: port ?? 3000,
|
59
|
+
...options,
|
60
|
+
async fetch(req) {
|
61
|
+
const url = new URL(req.url);
|
62
|
+
const path = url.pathname;
|
63
|
+
|
64
|
+
// set the database
|
65
|
+
if (options) {
|
66
|
+
const o = options as Options;
|
67
|
+
opts.db = o.db;
|
68
|
+
}
|
69
|
+
|
70
|
+
const route = findRoute(path);
|
71
|
+
|
72
|
+
// if the route exists, execute the handler
|
73
|
+
if (route) {
|
74
|
+
if (route.method !== req.method) {
|
75
|
+
logger.info(405, url.pathname, req.method, httpStatusCodes[405]);
|
76
|
+
return Promise.resolve(http.methodNotAllowed());
|
77
|
+
}
|
78
|
+
|
79
|
+
const context = await createContext(path, route, req);
|
80
|
+
context.db = new Database(opts.db);
|
81
|
+
|
82
|
+
const response = await route.handler(context);
|
83
|
+
|
84
|
+
logger.info(response.status, url.pathname, req.method, httpStatusCodes[response.status]);
|
85
|
+
return Promise.resolve(response);
|
86
|
+
}
|
87
|
+
|
88
|
+
// if no route is found, return 404
|
89
|
+
const response = await http.notFound();
|
90
90
|
|
91
|
-
|
92
|
-
|
91
|
+
logger.info(response.status, url.pathname, req.method, httpStatusCodes[response.status]);
|
92
|
+
return Promise.resolve(http.notFound());
|
93
93
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
}
|
94
|
+
}
|
95
|
+
});
|
96
|
+
},
|
97
|
+
};
|
98
|
+
};
|
99
99
|
|
100
|
-
export { Router, http }
|
100
|
+
export { Router, http };
|
package/lib/router/tree.ts
CHANGED
@@ -1,63 +1,62 @@
|
|
1
|
-
import { HttpHandler, Route } from
|
2
|
-
import { http } from
|
1
|
+
import { HttpHandler, Route } from './router.d';
|
2
|
+
import { http } from '../http/http';
|
3
3
|
import { createContext } from './context';
|
4
|
-
|
5
|
-
const splitPath = (s: string): string[] => s.split('/').filter(x => x !== '');
|
4
|
+
import { splitPath } from '../util/strings';
|
6
5
|
|
7
6
|
const createRoute = (path: string, method: string, handler: HttpHandler): Route => {
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
7
|
+
const route: Route = {
|
8
|
+
children: new Map(),
|
9
|
+
path: path,
|
10
|
+
dynamicPath: '',
|
11
|
+
method: method,
|
12
|
+
handler: handler,
|
13
|
+
isLast: false
|
14
|
+
};
|
15
|
+
|
16
|
+
return route;
|
18
17
|
};
|
19
18
|
|
20
19
|
const RouteTree = () => {
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
20
|
+
const root = createRoute('', 'GET', () => http.notFound());
|
21
|
+
|
22
|
+
const addRoute = (path: string, method: string, handler: HttpHandler) => {
|
23
|
+
const pathParts = splitPath(path);
|
24
|
+
let current = root;
|
25
|
+
|
26
|
+
for (let i = 0; i < pathParts.length; i++) {
|
27
|
+
const part = pathParts[i];
|
28
|
+
if (part.startsWith(':')) {
|
29
|
+
current.dynamicPath = part;
|
30
|
+
}
|
31
|
+
if (!current.children.has(part)) {
|
32
|
+
current.children.set(part, createRoute(part, method, handler));
|
33
|
+
}
|
34
|
+
current = current.children.get(part)!;
|
35
|
+
}
|
36
|
+
|
37
|
+
current.handler = handler;
|
38
|
+
current.isLast = true;
|
39
|
+
current.path = path;
|
40
|
+
};
|
41
|
+
|
42
|
+
function findRoute(path: string): Route | undefined {
|
43
|
+
const pathParts = splitPath(path);
|
44
|
+
let current = root;
|
45
|
+
for (let i = 0; i < pathParts.length; i++) {
|
46
|
+
const part = pathParts[i];
|
47
|
+
if (current.children.has(part)) {
|
48
|
+
current = current.children.get(part)!;
|
49
|
+
} else if (current.dynamicPath) {
|
50
|
+
current = current.children.get(current.dynamicPath)!;
|
51
|
+
} else {
|
52
|
+
return;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
return current;
|
56
|
+
}
|
57
|
+
|
58
|
+
return { addRoute, findRoute };
|
60
59
|
|
61
60
|
};
|
62
61
|
|
63
|
-
export { RouteTree, createContext }
|
62
|
+
export { RouteTree, createContext as createContext };
|
package/package.json
CHANGED
@@ -3,10 +3,19 @@
|
|
3
3
|
"module": "index.ts",
|
4
4
|
"type": "module",
|
5
5
|
"devDependencies": {
|
6
|
-
"
|
6
|
+
"@types/react-dom": "^18.2.7",
|
7
|
+
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
8
|
+
"@typescript-eslint/parser": "^6.7.0",
|
9
|
+
"bun-types": "latest",
|
10
|
+
"eslint": "^8.49.0",
|
11
|
+
"eslint-plugin-react": "^7.33.2",
|
12
|
+
"react-dom": "^18.2.0"
|
7
13
|
},
|
8
14
|
"peerDependencies": {
|
9
15
|
"typescript": "^5.0.0"
|
10
16
|
},
|
11
|
-
"version": "0.7.
|
17
|
+
"version": "0.7.4-experimental.0",
|
18
|
+
"dependencies": {
|
19
|
+
"eslint-plugin-react-hooks": "^4.6.0"
|
20
|
+
}
|
12
21
|
}
|