bun-router 0.4.0 → 0.5.3
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 +28 -0
- package/examples/dbs/test.db +0 -0
- package/examples/dynamic.ts +2 -1
- package/examples/sqlite.ts +22 -0
- package/index.ts +0 -2
- package/lib/fs/fsys.ts +7 -5
- package/lib/http/status.ts +66 -0
- package/lib/logger/logger.d.ts +1 -0
- package/lib/logger/logger.ts +25 -9
- package/lib/router/router.d.ts +16 -7
- package/lib/router/router.ts +109 -30
- package/package.json +1 -1
- package/tests/router.test.ts +3 -7
package/README.md
CHANGED
|
@@ -52,4 +52,32 @@ r.add('/user/:id', 'GET', (ctx) => {
|
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
r.serve();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**SQLite**
|
|
58
|
+
```ts
|
|
59
|
+
import { router, json } from '..';
|
|
60
|
+
import { Database } from 'bun:sqlite';
|
|
61
|
+
|
|
62
|
+
const r = router(3000, {db: './examples/dbs/test.db'});
|
|
63
|
+
|
|
64
|
+
r.add('/u/new/:name', 'GET', (ctx) => {
|
|
65
|
+
const name = ctx.params.get('name');
|
|
66
|
+
const rando = Math.floor(Math.random()*1000);
|
|
67
|
+
|
|
68
|
+
ctx.db.run(`INSERT INTO test VALUES(${rando}, "${name}")`);
|
|
69
|
+
|
|
70
|
+
return json({message: 'ok'});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
r.add('/u/:name', 'GET', (ctx) => {
|
|
74
|
+
const name = ctx.params.get('name');
|
|
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();
|
|
82
|
+
|
|
55
83
|
```
|
|
Binary file
|
package/examples/dynamic.ts
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { router, json } from '..';
|
|
2
|
+
|
|
3
|
+
const r = router(3000, {db: './examples/dbs/test.db'});
|
|
4
|
+
|
|
5
|
+
r.add('/u/new/:name', 'GET', (ctx) => {
|
|
6
|
+
const name = ctx.params.get('name');
|
|
7
|
+
const rando = Math.floor(Math.random()*1000);
|
|
8
|
+
|
|
9
|
+
ctx.db.run(`INSERT INTO test VALUES(${rando}, "${name}")`);
|
|
10
|
+
|
|
11
|
+
return json({message: 'ok'});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
r.add('/u/:name', 'GET', (ctx) => {
|
|
15
|
+
const name = ctx.params.get('name');
|
|
16
|
+
const data = ctx.db.query(`SELECT * FROM test WHERE name = "${name}";`).get();
|
|
17
|
+
const d = data as {id: number, name: string};
|
|
18
|
+
|
|
19
|
+
return d ? json(d) : new Response('not found', {status: 404});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
r.serve();
|
package/index.ts
CHANGED
package/lib/fs/fsys.ts
CHANGED
|
@@ -2,23 +2,25 @@ import { BunFile } from "bun";
|
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
|
|
5
|
+
// check if the file path is a directory
|
|
5
6
|
const isDir = async (fp: string): Promise<boolean> => (await fs.lstat(fp)).isDirectory();
|
|
6
7
|
|
|
8
|
+
// read a directory recursively and apply the callback to each one
|
|
7
9
|
const readDir = async (dirpath: string, handler: (filepath: string, entry: BunFile) => void) => {
|
|
8
|
-
try {
|
|
9
10
|
const files = await fs.readdir(dirpath);
|
|
10
11
|
|
|
11
12
|
for (const file of files) {
|
|
12
13
|
const bunFile = Bun.file(file);
|
|
13
|
-
|
|
14
|
+
|
|
15
|
+
if (typeof bunFile.name === 'undefined') return
|
|
16
|
+
|
|
17
|
+
const fp = path.join(dirpath, bunFile.name);
|
|
14
18
|
const isdir = await isDir(fp);
|
|
15
19
|
|
|
16
20
|
if (isdir) await readDir(fp, handler);
|
|
17
21
|
else handler(fp, bunFile);
|
|
18
22
|
}
|
|
19
|
-
|
|
20
|
-
console.error(err);
|
|
21
|
-
}
|
|
23
|
+
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export { readDir }
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const httpStatusCodes: { [key: number]: string } = {
|
|
2
|
+
100: 'Continue',
|
|
3
|
+
101: 'Switching Protocols',
|
|
4
|
+
102: 'Processing',
|
|
5
|
+
103: 'Early Hints',
|
|
6
|
+
200: 'OK',
|
|
7
|
+
201: 'Created',
|
|
8
|
+
202: 'Accepted',
|
|
9
|
+
203: 'Non-Authoritative Information',
|
|
10
|
+
204: 'No Content',
|
|
11
|
+
205: 'Reset Content',
|
|
12
|
+
206: 'Partial Content',
|
|
13
|
+
207: 'Multi-Status',
|
|
14
|
+
208: 'Already Reported',
|
|
15
|
+
226: 'IM Used',
|
|
16
|
+
300: 'Multiple Choices',
|
|
17
|
+
301: 'Moved Permanently',
|
|
18
|
+
302: 'Found',
|
|
19
|
+
303: 'See Other',
|
|
20
|
+
304: 'Not Modified',
|
|
21
|
+
305: 'Use Proxy',
|
|
22
|
+
307: 'Temporary Redirect',
|
|
23
|
+
308: 'Permanent Redirect',
|
|
24
|
+
400: 'Bad Request',
|
|
25
|
+
401: 'Unauthorized',
|
|
26
|
+
402: 'Payment Required',
|
|
27
|
+
403: 'Forbidden',
|
|
28
|
+
404: 'Not Found',
|
|
29
|
+
405: 'Method Not Allowed',
|
|
30
|
+
406: 'Not Acceptable',
|
|
31
|
+
407: 'Proxy Authentication Required',
|
|
32
|
+
408: 'Request Timeout',
|
|
33
|
+
409: 'Conflict',
|
|
34
|
+
410: 'Gone',
|
|
35
|
+
411: 'Length Required',
|
|
36
|
+
412: 'Precondition Failed',
|
|
37
|
+
413: 'Payload Too Large',
|
|
38
|
+
414: 'URI Too Long',
|
|
39
|
+
415: 'Unsupported Media Type',
|
|
40
|
+
416: 'Range Not Satisfiable',
|
|
41
|
+
417: 'Expectation Failed',
|
|
42
|
+
418: "I'm a Teapot",
|
|
43
|
+
421: 'Misdirected Request',
|
|
44
|
+
422: 'Unprocessable Entity',
|
|
45
|
+
423: 'Locked',
|
|
46
|
+
424: 'Failed Dependency',
|
|
47
|
+
425: 'Too Early',
|
|
48
|
+
426: 'Upgrade Required',
|
|
49
|
+
428: 'Precondition Required',
|
|
50
|
+
429: 'Too Many Requests',
|
|
51
|
+
431: 'Request Header Fields Too Large',
|
|
52
|
+
451: 'Unavailable For Legal Reasons',
|
|
53
|
+
500: 'Internal Server Error',
|
|
54
|
+
501: 'Not Implemented',
|
|
55
|
+
502: 'Bad Gateway',
|
|
56
|
+
503: 'Service Unavailable',
|
|
57
|
+
504: 'Gateway Timeout',
|
|
58
|
+
505: 'HTTP Version Not Supported',
|
|
59
|
+
506: 'Variant Also Negotiates',
|
|
60
|
+
507: 'Insufficient Storage',
|
|
61
|
+
508: 'Loop Detected',
|
|
62
|
+
510: 'Not Extended',
|
|
63
|
+
511: 'Network Authentication Required',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export { httpStatusCodes }
|
package/lib/logger/logger.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ type Logger = {
|
|
|
3
3
|
info: (statusCode: number, routePath: string, method: string, message?: string) => void,
|
|
4
4
|
error: (statusCode: number, routePath: string, method: string, error: Error) => void,
|
|
5
5
|
warn: (msg: string) => void,
|
|
6
|
+
message: (msg: string) => void,
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export { Logger }
|
package/lib/logger/logger.ts
CHANGED
|
@@ -14,17 +14,23 @@ const timestamp = (date: Date) => {
|
|
|
14
14
|
return {month, day, hour, minute, stamp};
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// append ANSI color escape sequences to a string based on the given HTTP status code.
|
|
18
|
+
const colorCode = (n: number, text?: string): string => {
|
|
18
19
|
const s = ` [${String(n)}${text ?? ''}] `;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
else if (n
|
|
24
|
-
else if (n
|
|
25
|
-
|
|
20
|
+
let backgroundColor = 'bgBlack';
|
|
21
|
+
let foregroundColor = 'white';
|
|
22
|
+
|
|
23
|
+
if (n < 100) backgroundColor = 'bgYellow';
|
|
24
|
+
else if (n < 200) backgroundColor = 'bgCyan';
|
|
25
|
+
else if (n < 300) backgroundColor = 'bgGreen';
|
|
26
|
+
else if (n < 400) backgroundColor = 'bgRed';
|
|
27
|
+
else if (n < 500) backgroundColor = 'bgRed';
|
|
28
|
+
else foregroundColor = 'white';
|
|
29
|
+
|
|
30
|
+
return color(foregroundColor, backgroundColor, s).trim();
|
|
26
31
|
}
|
|
27
32
|
|
|
33
|
+
|
|
28
34
|
const clean = (s: string) => s.replace(/\x1B\[\d{1,2}(;\d{1,2}){0,2}m/g, '');
|
|
29
35
|
|
|
30
36
|
const format = (statusCode: number, routePath: string, method: string, message?: string): string => {
|
|
@@ -39,6 +45,7 @@ const logger = (): Logger => {
|
|
|
39
45
|
const messages: string[] = [];
|
|
40
46
|
const errors: string[] = [];
|
|
41
47
|
return {
|
|
48
|
+
// initial log message
|
|
42
49
|
start: async (port: number | string) => {
|
|
43
50
|
const { stamp } = timestamp((new Date(Date.now())));
|
|
44
51
|
const source = color('green', 'bgBlack', `[bun-router ${stamp}]`)
|
|
@@ -51,7 +58,7 @@ const logger = (): Logger => {
|
|
|
51
58
|
const { stamp } = timestamp((new Date(Date.now())));
|
|
52
59
|
const source = color('green', 'bgBlack', `[bun-router ${stamp}]`);
|
|
53
60
|
const rp = color('white', 'bgBlack', routePath);
|
|
54
|
-
const msg = `${source}: ${colorCode(statusCode)}: ${rp} ${(method === 'GET') ? '->' : '<-'} ${method}\n`
|
|
61
|
+
const msg = `${source}: ${colorCode(statusCode)}: ${rp} ${(method === 'GET') ? '->' : '<-'} ${method}${' | ' +message ?? ''}\n`
|
|
55
62
|
|
|
56
63
|
await Bun.write(Bun.stdout, msg);
|
|
57
64
|
|
|
@@ -73,6 +80,15 @@ const logger = (): Logger => {
|
|
|
73
80
|
const msgColor = color('yellow', 'bgBlack', msg);
|
|
74
81
|
msg = `${source} : ${msgColor}\n`;
|
|
75
82
|
await Bun.write(Bun.stdout, msg);
|
|
83
|
+
},
|
|
84
|
+
message: async (msg: string) => {
|
|
85
|
+
const { stamp } = timestamp((new Date(Date.now())));
|
|
86
|
+
const source = color('black', 'bgCyan', `[message ${stamp}]`);
|
|
87
|
+
const msgColor = color('yellow', 'bgBlack', msg);
|
|
88
|
+
msg = `${source}: ${msgColor}\n`;
|
|
89
|
+
await Bun.write(Bun.stdout, msg);
|
|
90
|
+
|
|
91
|
+
messages.push(clean(msg));
|
|
76
92
|
}
|
|
77
93
|
}
|
|
78
94
|
}
|
package/lib/router/router.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { TLSOptions, TLSWebSocketServeOptions, WebSocketServeOptions, ServeOptions, TLSServeOptions } from 'bun';
|
|
2
|
+
import { Logger } from '../logger/logger';
|
|
3
|
+
import { Database } from 'bun:sqlite';
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
type Context = {
|
|
5
7
|
request: Request,
|
|
6
8
|
params: Map<string, string>,
|
|
7
|
-
fs: Map<string, string>,
|
|
8
9
|
token?: string,
|
|
10
|
+
db: Database,
|
|
11
|
+
logger: Logger,
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
type Route = {
|
|
@@ -14,18 +17,24 @@ type Route = {
|
|
|
14
17
|
callback: (req: Context) => Response | Promise<Response>
|
|
15
18
|
}
|
|
16
19
|
|
|
17
|
-
type Options =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
type Options = {
|
|
21
|
+
db: string,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type RouterOptions<Options> = ServeOptions
|
|
25
|
+
| TLSServeOptions<Options>
|
|
26
|
+
| WebSocketServeOptions<Options>
|
|
27
|
+
| TLSWebSocketServeOptions<Options>
|
|
21
28
|
| undefined
|
|
22
29
|
|
|
23
30
|
|
|
24
|
-
type Router = (port?: number | string, options?:
|
|
31
|
+
type Router = (port?: number | string, options?: RouterOptions) => {
|
|
25
32
|
add: (pattern: string, method: string, callback: (req: Context) => Response | Promise<Response>) => void,
|
|
33
|
+
GET: (pattern: string, callback: (ctx: Context) => Response | Promise<Response>) => void,
|
|
34
|
+
POST: (pattern: string, callback: (ctx: Context) => Response | Promise<Response>) => void,
|
|
26
35
|
static: (pattern: string, root: string) => void,
|
|
27
36
|
serve: () => void,
|
|
28
37
|
}
|
|
29
38
|
|
|
30
39
|
|
|
31
|
-
export { Context , Route, Router, Options }
|
|
40
|
+
export { Context , Route, Router, RouterOptions, Options }
|
package/lib/router/router.ts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { Route, Router, Context, RouterOptions, Options } from './router.d';
|
|
3
|
+
import { httpStatusCodes } from '../http/status';
|
|
2
4
|
import { readDir } from '../fs/fsys';
|
|
3
5
|
import { logger } from '../logger/logger';
|
|
4
6
|
import path from 'path';
|
|
7
|
+
import { Logger } from '../logger/logger.d';
|
|
8
|
+
|
|
9
|
+
// create a generic HTTP response
|
|
10
|
+
const httpMessage = async (status: number, msg?: string): Promise<Response> => {
|
|
11
|
+
const response = new Response(msg ?? '?', {
|
|
12
|
+
status: status,
|
|
13
|
+
statusText: msg ?? '?',
|
|
14
|
+
headers: { 'Content-Type': 'text/html; charset-uft-8' }
|
|
15
|
+
});
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
resolve(response);
|
|
18
|
+
});
|
|
19
|
+
};
|
|
5
20
|
|
|
6
|
-
|
|
7
|
-
|
|
21
|
+
// a generic 'not found' HTTP response
|
|
22
|
+
const notFound = async (msg?: string): Promise<Response> => {
|
|
23
|
+
const response = new Response(msg ?? 'not found', {
|
|
8
24
|
status: 404,
|
|
9
25
|
statusText: 'not found',
|
|
10
26
|
headers: { 'Content-Type': 'text/html' },
|
|
@@ -15,6 +31,7 @@ const notFound = async (): Promise<Response> => {
|
|
|
15
31
|
});
|
|
16
32
|
}
|
|
17
33
|
|
|
34
|
+
// a generic 'no content' HTTP response
|
|
18
35
|
const noContent = async (): Promise<Response> => {
|
|
19
36
|
const response = new Response('no content', {
|
|
20
37
|
status: 204,
|
|
@@ -26,47 +43,54 @@ const noContent = async (): Promise<Response> => {
|
|
|
26
43
|
});
|
|
27
44
|
}
|
|
28
45
|
|
|
46
|
+
// IO handling
|
|
29
47
|
const file = async (filepath: string): Promise<Response> => {
|
|
30
48
|
const file = Bun.file(filepath);
|
|
31
49
|
const exists = await file.exists();
|
|
32
50
|
|
|
51
|
+
// check if the file exists, return 'not found' if it doesn't.
|
|
33
52
|
if (!exists)
|
|
34
|
-
return notFound();
|
|
53
|
+
return notFound(`File not found: ${filepath}`);
|
|
35
54
|
|
|
55
|
+
// get the content of the file as an ArrayBuffer
|
|
36
56
|
const content = await file.arrayBuffer();
|
|
37
57
|
if (!content)
|
|
38
|
-
return
|
|
58
|
+
return noContent();
|
|
39
59
|
|
|
60
|
+
// default Content-Type + encoding
|
|
40
61
|
let contentType = 'text/html; charset=utf-8';
|
|
41
62
|
|
|
63
|
+
// change the Content-Type if the file type is an image.
|
|
64
|
+
// file.type provides the necessary Content-Type
|
|
42
65
|
if (file.type.includes('image')) {
|
|
43
66
|
contentType = file.type + '; charset=utf-8';
|
|
44
67
|
}
|
|
45
68
|
|
|
69
|
+
// create a new response with the necessary criteria
|
|
46
70
|
const response = new Response(content, {
|
|
47
71
|
status: 200,
|
|
48
72
|
statusText: 'ok',
|
|
49
73
|
headers: { 'Content-Type': contentType },
|
|
50
74
|
});
|
|
51
75
|
|
|
52
|
-
return
|
|
53
|
-
resolve(response);
|
|
54
|
-
});
|
|
76
|
+
return Promise.resolve(response);
|
|
55
77
|
}
|
|
56
78
|
|
|
79
|
+
// handle strings as HTML
|
|
57
80
|
const html = async (content: string): Promise<Response> => {
|
|
58
81
|
const response = new Response(content, {
|
|
59
82
|
status: 200,
|
|
60
83
|
statusText: 'ok',
|
|
61
84
|
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
62
85
|
});
|
|
86
|
+
|
|
87
|
+
// escape the HTML
|
|
63
88
|
content = Bun.escapeHTML(content);
|
|
64
89
|
|
|
65
|
-
return
|
|
66
|
-
resolve(response);
|
|
67
|
-
});
|
|
90
|
+
return Promise.resolve(response);
|
|
68
91
|
}
|
|
69
92
|
|
|
93
|
+
// create a JSON response
|
|
70
94
|
const json = (data: any): Response => {
|
|
71
95
|
const jsonString = JSON.stringify(data);
|
|
72
96
|
|
|
@@ -76,6 +100,8 @@ const json = (data: any): Response => {
|
|
|
76
100
|
return res
|
|
77
101
|
}
|
|
78
102
|
|
|
103
|
+
// extract dynamic URL parameters
|
|
104
|
+
// if the route pattern is /:foo and the request URL is /bar: {foo: 'bar'}
|
|
79
105
|
const extract = (route: Route, ctx: Context) => {
|
|
80
106
|
const url = new URL(ctx.request.url);
|
|
81
107
|
const pathSegments = route.pattern.split('/');
|
|
@@ -83,7 +109,6 @@ const extract = (route: Route, ctx: Context) => {
|
|
|
83
109
|
|
|
84
110
|
if (pathSegments.length !== urlSegments.length) return
|
|
85
111
|
|
|
86
|
-
|
|
87
112
|
return {
|
|
88
113
|
params: () => {
|
|
89
114
|
for (let i = 0; i < pathSegments.length; i++) {
|
|
@@ -98,12 +123,13 @@ const extract = (route: Route, ctx: Context) => {
|
|
|
98
123
|
|
|
99
124
|
}
|
|
100
125
|
|
|
126
|
+
// ensure the route pattern matches the request URL
|
|
101
127
|
const match = (route: Route, ctx: Context): boolean => {
|
|
102
128
|
const url = new URL(ctx.request.url);
|
|
103
129
|
const patternRegex = new RegExp('^' + route.pattern.replace(/:[^/]+/g, '([^/]+)') + '$');
|
|
104
130
|
const matches = url.pathname.match(patternRegex);
|
|
105
131
|
|
|
106
|
-
if (matches) {
|
|
132
|
+
if (matches && route.method === ctx.request.method) {
|
|
107
133
|
const extractor = extract(route, ctx);
|
|
108
134
|
extractor?.params();
|
|
109
135
|
|
|
@@ -113,12 +139,24 @@ const match = (route: Route, ctx: Context): boolean => {
|
|
|
113
139
|
return false;
|
|
114
140
|
}
|
|
115
141
|
|
|
116
|
-
const
|
|
142
|
+
const setContext = (req: Request, lgr: Logger, opts: Options): Context => {
|
|
143
|
+
return {
|
|
144
|
+
request: req,
|
|
145
|
+
params: new Map(),
|
|
146
|
+
db: new Database(opts.db ?? ':memory:'),
|
|
147
|
+
logger: lgr,
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
const router: Router = (port?: number | string, options?: RouterOptions<Options>) => {
|
|
117
154
|
const routes: Array<Route> = new Array();
|
|
118
|
-
const paths: { [key: string]: string } = {};
|
|
119
155
|
const lgr = logger();
|
|
156
|
+
let dbConn = '';
|
|
120
157
|
|
|
121
158
|
return {
|
|
159
|
+
// add a new route
|
|
122
160
|
add: (pattern: string, method: string, callback: (ctx: Context) => Response | Promise<Response>) => {
|
|
123
161
|
routes.push({
|
|
124
162
|
pattern: pattern,
|
|
@@ -126,6 +164,21 @@ const router: Router = (port?: number | string, options?: Options) => {
|
|
|
126
164
|
callback: callback,
|
|
127
165
|
})
|
|
128
166
|
},
|
|
167
|
+
GET: (pattern: string, callback: (ctx: Context) => Response | Promise<Response>) => {
|
|
168
|
+
routes.push({
|
|
169
|
+
pattern: pattern,
|
|
170
|
+
method: 'GET',
|
|
171
|
+
callback: callback,
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
POST: (pattern: string, callback: (ctx: Context) => Response | Promise<Response>) => {
|
|
175
|
+
routes.push({
|
|
176
|
+
pattern: pattern,
|
|
177
|
+
method: 'POST',
|
|
178
|
+
callback: callback,
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
// add a route for static files
|
|
129
182
|
static: async (pattern: string, root: string) => {
|
|
130
183
|
await readDir(root, async (fp, _) => {
|
|
131
184
|
const pure = path.join('.', fp);
|
|
@@ -133,10 +186,7 @@ const router: Router = (port?: number | string, options?: Options) => {
|
|
|
133
186
|
|
|
134
187
|
let base = path.basename(pure);
|
|
135
188
|
|
|
136
|
-
if (ext === '.html')
|
|
137
|
-
base = base.replace(ext, '');
|
|
138
|
-
|
|
139
|
-
}
|
|
189
|
+
if (ext === '.html') base = base.replace(ext, '');
|
|
140
190
|
|
|
141
191
|
if (pattern[0] !== '/') pattern = '/' + pattern;
|
|
142
192
|
|
|
@@ -152,33 +202,62 @@ const router: Router = (port?: number | string, options?: Options) => {
|
|
|
152
202
|
routes.push(route);
|
|
153
203
|
});
|
|
154
204
|
},
|
|
205
|
+
// start the server
|
|
155
206
|
serve: () => {
|
|
156
207
|
lgr.start(port ?? 3000);
|
|
208
|
+
let opts: Options = { db: ':memory:' };
|
|
209
|
+
|
|
157
210
|
Bun.serve({
|
|
158
211
|
port: port ?? 3000,
|
|
159
212
|
...options,
|
|
160
|
-
fetch(req) {
|
|
213
|
+
async fetch(req) {
|
|
161
214
|
const url = new URL(req.url);
|
|
215
|
+
|
|
216
|
+
if (options) {
|
|
217
|
+
let o = options as Options;
|
|
218
|
+
opts.db = o.db;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let statusCode = 404; // Default status code for route not found
|
|
222
|
+
|
|
162
223
|
for (const route of routes) {
|
|
163
|
-
const ctx
|
|
164
|
-
request: req,
|
|
165
|
-
params: new Map(),
|
|
166
|
-
fs: new Map(),
|
|
167
|
-
};
|
|
224
|
+
const ctx = setContext(req, lgr, opts);
|
|
168
225
|
|
|
169
|
-
if (url.pathname === '/favicon.ico')
|
|
226
|
+
if (url.pathname === '/favicon.ico') {
|
|
227
|
+
return noContent();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (route.method !== req.method) {
|
|
231
|
+
statusCode = 405;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
170
234
|
|
|
171
235
|
if (match(route, ctx)) {
|
|
172
|
-
|
|
173
|
-
|
|
236
|
+
const res = await route.callback(ctx);
|
|
237
|
+
if (res) {
|
|
238
|
+
statusCode = 200;
|
|
239
|
+
lgr.info(statusCode, url.pathname, req.method, httpStatusCodes[statusCode]);
|
|
240
|
+
return res
|
|
241
|
+
} else {
|
|
242
|
+
statusCode = 500;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
174
245
|
}
|
|
175
246
|
}
|
|
176
|
-
|
|
177
|
-
|
|
247
|
+
|
|
248
|
+
if (statusCode === 405) {
|
|
249
|
+
lgr.info(statusCode, url.pathname, req.method, httpStatusCodes[statusCode]);
|
|
250
|
+
return httpMessage(statusCode, httpStatusCodes[statusCode]);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
lgr.info(statusCode, url.pathname, req.method, httpStatusCodes[statusCode]);
|
|
254
|
+
return httpMessage(statusCode, httpStatusCodes[statusCode]);
|
|
255
|
+
|
|
178
256
|
}
|
|
179
257
|
});
|
|
180
258
|
},
|
|
181
259
|
}
|
|
182
260
|
}
|
|
183
261
|
|
|
262
|
+
|
|
184
263
|
export { router, json, file, extract, html }
|
package/package.json
CHANGED
package/tests/router.test.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
import {
|
|
2
|
+
import { extract } from '..';
|
|
3
3
|
import { Context, Route } from '../lib/router/router.d';
|
|
4
|
-
import { logger } from '../lib/logger/logger';
|
|
5
|
-
import { color } from '../lib/logger/color';
|
|
6
4
|
|
|
7
5
|
describe('URL Params', () => {
|
|
8
6
|
test('/user/:name', () => {
|
|
@@ -15,7 +13,6 @@ describe('URL Params', () => {
|
|
|
15
13
|
const ctx: Context = {
|
|
16
14
|
request: new Request('http://localhost:3000/user/foo'),
|
|
17
15
|
params: new Map(),
|
|
18
|
-
fs: new Map(),
|
|
19
16
|
};
|
|
20
17
|
|
|
21
18
|
const extractor = extract(route, ctx);
|
|
@@ -36,7 +33,6 @@ describe('URL Params', () => {
|
|
|
36
33
|
const ctx: Context = {
|
|
37
34
|
request: new Request('http://localhost:3000/user/foo/123'),
|
|
38
35
|
params: new Map(),
|
|
39
|
-
fs: new Map(),
|
|
40
36
|
};
|
|
41
37
|
|
|
42
38
|
const extractor = extract(route, ctx);
|
|
@@ -60,7 +56,6 @@ describe('URL Params', () => {
|
|
|
60
56
|
const ctx: Context = {
|
|
61
57
|
request: new Request('http://localhost:3000/foo'),
|
|
62
58
|
params: new Map(),
|
|
63
|
-
fs: new Map(),
|
|
64
59
|
}
|
|
65
60
|
|
|
66
61
|
const url = new URL(ctx.request.url);
|
|
@@ -72,7 +67,7 @@ describe('URL Params', () => {
|
|
|
72
67
|
describe('Router', () => {
|
|
73
68
|
test('Serve', async () => {
|
|
74
69
|
const proc = Bun.spawn(['./tests/serve.test.sh'], {
|
|
75
|
-
onExit: (
|
|
70
|
+
onExit: (_proc, _exitCode, _signalCode , error) => {
|
|
76
71
|
if (error) console.error(error);
|
|
77
72
|
},
|
|
78
73
|
});
|
|
@@ -101,3 +96,4 @@ describe('Router', () => {
|
|
|
101
96
|
proc.kill(0);
|
|
102
97
|
});
|
|
103
98
|
});
|
|
99
|
+
|