bun-router 0.5.4 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/basic.ts +4 -4
- package/examples/dynamic.ts +7 -7
- package/examples/logger.ts +3 -3
- package/examples/sqlite.ts +3 -3
- package/examples/todo.ts +49 -0
- package/lib/http/generic-methods.ts +64 -0
- package/lib/router/router.d.ts +2 -0
- package/lib/router/router.ts +26 -128
- package/package.json +1 -1
package/examples/basic.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { router,
|
|
1
|
+
import { router, http } from '..';
|
|
2
2
|
|
|
3
3
|
const r = router();
|
|
4
4
|
|
|
5
|
-
r.add('/', 'GET', () => json('ok'));
|
|
5
|
+
r.add('/', 'GET', () => http.json('ok'));
|
|
6
6
|
|
|
7
7
|
r.add('/user/:name', 'GET', (ctx) => {
|
|
8
8
|
const name = ctx.params.get('name');
|
|
9
|
-
return json(name);
|
|
9
|
+
return http.json(name);
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
r.add('/user/:name/:id', 'GET', (ctx) => {
|
|
13
13
|
const name = ctx.params.get('name');
|
|
14
14
|
const id = ctx.params.get('id');
|
|
15
|
-
return json({name: name, id: id});
|
|
15
|
+
return http.json({name: name, id: id});
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
r.serve();
|
package/examples/dynamic.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { router,
|
|
1
|
+
import { router, http } from '..';
|
|
2
2
|
import { Context } from '../lib/router/router.d';
|
|
3
3
|
|
|
4
4
|
const handler = (ctx: Context) => {
|
|
5
5
|
const name = ctx.params.get('name');
|
|
6
|
-
if (typeof name === 'undefined' || name === '') return html('<h6 style="color: red">User Undefined</h6>');
|
|
7
|
-
return html(`<h4>Hello, ${name}!</h4>`);
|
|
6
|
+
if (typeof name === 'undefined' || name === '') return http.html('<h6 style="color: red">User Undefined</h6>');
|
|
7
|
+
return http.html(`<h4>Hello, ${name}!</h4>`);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const space = (ctx: Context) => {
|
|
11
11
|
const name = ctx.params.get('name');
|
|
12
|
-
if (typeof name === 'undefined' || name === '') return html(`<h6 style="color: red">Space [${name}] Not Found</h6>`);
|
|
13
|
-
return html(`<h4>Welcome to ${name}!`)
|
|
12
|
+
if (typeof name === 'undefined' || name === '') return http.html(`<h6 style="color: red">Space [${name}] Not Found</h6>`);
|
|
13
|
+
return http.html(`<h4>Welcome to ${name}!`)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const handleSettings = (ctx: Context) => {
|
|
17
17
|
const name = ctx.params.get('name');
|
|
18
|
-
if (typeof name === 'undefined' || name === '') return html(`<h6 style="color: red">User Not Found</h6>`);
|
|
19
|
-
return html(`<h4>Settings for ${name}</h4>`)
|
|
18
|
+
if (typeof name === 'undefined' || name === '') return http.html(`<h6 style="color: red">User Not Found</h6>`);
|
|
19
|
+
return http.html(`<h4>Settings for ${name}</h4>`)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const r = router();
|
package/examples/logger.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { router, logger,
|
|
1
|
+
import { router, logger, http } from '..';
|
|
2
2
|
|
|
3
3
|
const r = router();
|
|
4
4
|
const log = logger();
|
|
@@ -8,9 +8,9 @@ r.add('/:foo', 'GET', (ctx) => {
|
|
|
8
8
|
const foo = ctx.params.get('foo');
|
|
9
9
|
if (!foo) {
|
|
10
10
|
log.error(500, url.pathname, ctx.request.method, new Error('undefined'));
|
|
11
|
-
return json({status: 500, text: 'Foo is undefined'});
|
|
11
|
+
return http.json({status: 500, text: 'Foo is undefined'});
|
|
12
12
|
}
|
|
13
|
-
return html(`<h4 style='font-family: sans-serif;'>Oh hello, ${foo}</h4>`)
|
|
13
|
+
return http.html(`<h4 style='font-family: sans-serif;'>Oh hello, ${foo}</h4>`)
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
r.serve();
|
package/examples/sqlite.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { router,
|
|
1
|
+
import { router, http } from '..';
|
|
2
2
|
|
|
3
3
|
const r = router(3000, {db: './examples/dbs/test.db'});
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ r.add('/u/new/:name', 'GET', (ctx) => {
|
|
|
8
8
|
|
|
9
9
|
ctx.db.run(`INSERT INTO test VALUES(${rando}, "${name}")`);
|
|
10
10
|
|
|
11
|
-
return json({message: 'ok'});
|
|
11
|
+
return http.json({message: 'ok'});
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
r.add('/u/:name', 'GET', (ctx) => {
|
|
@@ -16,7 +16,7 @@ r.add('/u/:name', 'GET', (ctx) => {
|
|
|
16
16
|
const data = ctx.db.query(`SELECT * FROM test WHERE name = "${name}";`).get();
|
|
17
17
|
const d = data as {id: number, name: string};
|
|
18
18
|
|
|
19
|
-
return d ? json(d) : new Response('not found', {status: 404});
|
|
19
|
+
return d ? http.json(d) : new Response('not found', {status: 404});
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
r.serve();
|
package/examples/todo.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { router, http } from '..';
|
|
2
|
+
import { Context } from '../lib/router/router.d';
|
|
3
|
+
|
|
4
|
+
const Todo = () => {
|
|
5
|
+
const list: Record<string, string> = {};
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
add: (key: string, value: string) => { list[key] = value },
|
|
9
|
+
get: (key: string) => list[key],
|
|
10
|
+
remove: (key: string) => { delete list[key] },
|
|
11
|
+
size: () => Object.entries(list).length,
|
|
12
|
+
export: () => list,
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const todo = Todo();
|
|
17
|
+
|
|
18
|
+
const r = router();
|
|
19
|
+
|
|
20
|
+
r.add('/api/new', 'POST', ctx => {
|
|
21
|
+
const query = new URL(ctx.request.url).searchParams;
|
|
22
|
+
const key = query.get('key');
|
|
23
|
+
const content = query.get('content');
|
|
24
|
+
|
|
25
|
+
if (!key || !content) return http.message(400, 'invalid query params');
|
|
26
|
+
ctx.logger.message(`Adding ${key} with ${content}`);
|
|
27
|
+
todo.add(key, content);
|
|
28
|
+
|
|
29
|
+
return ctx.json({ message: 'ok' });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
r.add('/api/todo/:key', 'GET', ctx => {
|
|
33
|
+
const key = ctx.params.get('key');
|
|
34
|
+
if (!key) return http.message(400, 'invalid params');
|
|
35
|
+
|
|
36
|
+
const content = todo.get(key);
|
|
37
|
+
if (!content) return http.notFound();
|
|
38
|
+
|
|
39
|
+
return ctx.json({key: key, content: content});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
r.add('/api/get/all', 'GET', ctx => {
|
|
43
|
+
return ctx.json(todo.export());
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
r.serve();
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { httpStatusCodes } from "./status";
|
|
2
|
+
|
|
3
|
+
const http = {
|
|
4
|
+
json: async (data: any): Promise<Response> => {
|
|
5
|
+
const jsonString = JSON.stringify(data);
|
|
6
|
+
return Promise.resolve(new Response(jsonString, {
|
|
7
|
+
status: 200,
|
|
8
|
+
statusText: httpStatusCodes[200],
|
|
9
|
+
headers: {'Content-Type': 'application/json'},
|
|
10
|
+
}));
|
|
11
|
+
},
|
|
12
|
+
html: async (content: string): Promise<Response> => {
|
|
13
|
+
content = Bun.escapeHTML(content);
|
|
14
|
+
return Promise.resolve(new Response(Bun.escapeHTML(content), {
|
|
15
|
+
status: 200,
|
|
16
|
+
statusText: httpStatusCodes[200],
|
|
17
|
+
headers: {'Content-Type': 'text/html; charset=utf-8'}
|
|
18
|
+
}));
|
|
19
|
+
},
|
|
20
|
+
file: async (fp: string): Promise<Response> => {
|
|
21
|
+
const file = Bun.file(fp);
|
|
22
|
+
const exists = await file.exists();
|
|
23
|
+
|
|
24
|
+
if (!exists) return http.notFound(`File not found: ${fp}`);
|
|
25
|
+
|
|
26
|
+
const content = await file.arrayBuffer();
|
|
27
|
+
if (!content) return http.noContent();
|
|
28
|
+
|
|
29
|
+
let contentType = 'text/html; charset=utf-9';
|
|
30
|
+
|
|
31
|
+
if (file.type.includes('image'))
|
|
32
|
+
contentType = file.type + '; charset=utf-8';
|
|
33
|
+
|
|
34
|
+
return Promise.resolve(new Response(content, {
|
|
35
|
+
status: 200,
|
|
36
|
+
statusText: httpStatusCodes[200],
|
|
37
|
+
headers: { 'Content-Type': contentType}
|
|
38
|
+
}));
|
|
39
|
+
},
|
|
40
|
+
noContent: async (): Promise<Response> => Promise.resolve(new Response('no content', {
|
|
41
|
+
status: 204,
|
|
42
|
+
statusText: 'no content',
|
|
43
|
+
})),
|
|
44
|
+
notFound: async(msg?: string): Promise<Response> => {
|
|
45
|
+
const response = new Response(msg ?? 'not found', {
|
|
46
|
+
status: 404,
|
|
47
|
+
statusText: httpStatusCodes[404],
|
|
48
|
+
headers: {'Content-Type': 'text/html'},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return Promise.resolve(response);
|
|
52
|
+
},
|
|
53
|
+
message: async (status: number, msg?: string): Promise<Response> => {
|
|
54
|
+
const response = new Response(msg ?? '?', {
|
|
55
|
+
status: status,
|
|
56
|
+
statusText: httpStatusCodes[status],
|
|
57
|
+
headers: {'Content-Type': 'text/html; charset-utf-8'},
|
|
58
|
+
});
|
|
59
|
+
return Promise.resolve(response)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { http }
|
package/lib/router/router.d.ts
CHANGED
|
@@ -5,10 +5,12 @@ import { Database } from 'bun:sqlite';
|
|
|
5
5
|
|
|
6
6
|
type Context = {
|
|
7
7
|
request: Request,
|
|
8
|
+
route: Route,
|
|
8
9
|
params: Map<string, string>,
|
|
9
10
|
token?: string,
|
|
10
11
|
db: Database,
|
|
11
12
|
logger: Logger,
|
|
13
|
+
json: (data: any) => Response | Promise<Response>,
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
type Route = {
|
package/lib/router/router.ts
CHANGED
|
@@ -1,104 +1,11 @@
|
|
|
1
|
+
import path from 'path';
|
|
1
2
|
import { Database } from 'bun:sqlite';
|
|
2
3
|
import { Route, Router, Context, RouterOptions, Options } from './router.d';
|
|
3
4
|
import { httpStatusCodes } from '../http/status';
|
|
4
5
|
import { readDir } from '../fs/fsys';
|
|
5
6
|
import { logger } from '../logger/logger';
|
|
6
|
-
import path from 'path';
|
|
7
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
|
-
};
|
|
20
|
-
|
|
21
|
-
// a generic 'not found' HTTP response
|
|
22
|
-
const notFound = async (msg?: string): Promise<Response> => {
|
|
23
|
-
const response = new Response(msg ?? 'not found', {
|
|
24
|
-
status: 404,
|
|
25
|
-
statusText: 'not found',
|
|
26
|
-
headers: { 'Content-Type': 'text/html' },
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
return new Promise((resolve) => {
|
|
30
|
-
resolve(response);
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// a generic 'no content' HTTP response
|
|
35
|
-
const noContent = async (): Promise<Response> => {
|
|
36
|
-
const response = new Response('no content', {
|
|
37
|
-
status: 204,
|
|
38
|
-
statusText: 'no content',
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
return new Promise((resolve) => {
|
|
42
|
-
resolve(response);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// IO handling
|
|
47
|
-
const file = async (filepath: string): Promise<Response> => {
|
|
48
|
-
const file = Bun.file(filepath);
|
|
49
|
-
const exists = await file.exists();
|
|
50
|
-
|
|
51
|
-
// check if the file exists, return 'not found' if it doesn't.
|
|
52
|
-
if (!exists)
|
|
53
|
-
return notFound(`File not found: ${filepath}`);
|
|
54
|
-
|
|
55
|
-
// get the content of the file as an ArrayBuffer
|
|
56
|
-
const content = await file.arrayBuffer();
|
|
57
|
-
if (!content)
|
|
58
|
-
return noContent();
|
|
59
|
-
|
|
60
|
-
// default Content-Type + encoding
|
|
61
|
-
let contentType = 'text/html; charset=utf-8';
|
|
62
|
-
|
|
63
|
-
// change the Content-Type if the file type is an image.
|
|
64
|
-
// file.type provides the necessary Content-Type
|
|
65
|
-
if (file.type.includes('image')) {
|
|
66
|
-
contentType = file.type + '; charset=utf-8';
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// create a new response with the necessary criteria
|
|
70
|
-
const response = new Response(content, {
|
|
71
|
-
status: 200,
|
|
72
|
-
statusText: 'ok',
|
|
73
|
-
headers: { 'Content-Type': contentType },
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
return Promise.resolve(response);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// handle strings as HTML
|
|
80
|
-
const html = async (content: string): Promise<Response> => {
|
|
81
|
-
const response = new Response(content, {
|
|
82
|
-
status: 200,
|
|
83
|
-
statusText: 'ok',
|
|
84
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// escape the HTML
|
|
88
|
-
content = Bun.escapeHTML(content);
|
|
89
|
-
|
|
90
|
-
return Promise.resolve(response);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// create a JSON response
|
|
94
|
-
const json = (data: any): Response => {
|
|
95
|
-
const jsonString = JSON.stringify(data);
|
|
96
|
-
|
|
97
|
-
const res = new Response(jsonString);
|
|
98
|
-
res.headers.set('Content-Type', 'application/json');
|
|
99
|
-
|
|
100
|
-
return res
|
|
101
|
-
}
|
|
8
|
+
import { http } from '../http/generic-methods';
|
|
102
9
|
|
|
103
10
|
// extract dynamic URL parameters
|
|
104
11
|
// if the route pattern is /:foo and the request URL is /bar: {foo: 'bar'}
|
|
@@ -139,17 +46,18 @@ const match = (route: Route, ctx: Context): boolean => {
|
|
|
139
46
|
return false;
|
|
140
47
|
}
|
|
141
48
|
|
|
142
|
-
|
|
49
|
+
// set the context for the reuest
|
|
50
|
+
const setContext = (req: Request, lgr: Logger, opts: Options, route: Route): Context => {
|
|
143
51
|
return {
|
|
144
52
|
request: req,
|
|
145
53
|
params: new Map(),
|
|
146
54
|
db: new Database(opts.db ?? ':memory:'),
|
|
147
55
|
logger: lgr,
|
|
56
|
+
route: route,
|
|
57
|
+
json: (data: any) => http.json(data),
|
|
148
58
|
}
|
|
149
59
|
}
|
|
150
60
|
|
|
151
|
-
|
|
152
|
-
|
|
153
61
|
const router: Router = (port?: number | string, options?: RouterOptions<Options>) => {
|
|
154
62
|
const routes: Array<Route> = new Array();
|
|
155
63
|
const lgr = logger();
|
|
@@ -197,8 +105,9 @@ const router: Router = (port?: number | string, options?: RouterOptions<Options>
|
|
|
197
105
|
const route: Route = {
|
|
198
106
|
pattern: patternPath,
|
|
199
107
|
method: 'GET',
|
|
200
|
-
callback: async () => await file(pure),
|
|
108
|
+
callback: async () => await http.file(pure),
|
|
201
109
|
};
|
|
110
|
+
|
|
202
111
|
routes.push(route);
|
|
203
112
|
});
|
|
204
113
|
},
|
|
@@ -218,41 +127,30 @@ const router: Router = (port?: number | string, options?: RouterOptions<Options>
|
|
|
218
127
|
opts.db = o.db;
|
|
219
128
|
}
|
|
220
129
|
|
|
221
|
-
let statusCode = 404;
|
|
130
|
+
let statusCode = 404;
|
|
222
131
|
|
|
223
132
|
for (const route of routes) {
|
|
224
|
-
const ctx = setContext(req, lgr, opts);
|
|
225
|
-
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (match(route, ctx)) {
|
|
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
|
|
133
|
+
const ctx = setContext(req, lgr, opts, route);
|
|
134
|
+
|
|
135
|
+
if (match(route, ctx) || route.pattern === url.pathname) {
|
|
136
|
+
if (route.method === ctx.request.method) {
|
|
137
|
+
const res = await route.callback(ctx);
|
|
138
|
+
statusCode = res.status;
|
|
139
|
+
lgr.info(res.status, route.pattern, req.method, httpStatusCodes[res.status]);
|
|
140
|
+
return Promise.resolve(res);
|
|
241
141
|
} else {
|
|
242
|
-
|
|
243
|
-
|
|
142
|
+
const res = new Response(httpStatusCodes[405], {
|
|
143
|
+
status: 405,
|
|
144
|
+
statusText: httpStatusCodes[305]
|
|
145
|
+
});
|
|
146
|
+
lgr.info(405, route.pattern, req.method, httpStatusCodes[405])
|
|
147
|
+
return Promise.resolve(res);
|
|
244
148
|
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (statusCode === 405) {
|
|
249
|
-
lgr.info(statusCode, url.pathname, req.method, httpStatusCodes[statusCode]);
|
|
250
|
-
return httpMessage(statusCode, httpStatusCodes[statusCode]);
|
|
149
|
+
}
|
|
251
150
|
}
|
|
252
151
|
|
|
253
152
|
lgr.info(statusCode, url.pathname, req.method, httpStatusCodes[statusCode]);
|
|
254
|
-
return
|
|
255
|
-
|
|
153
|
+
return Promise.resolve(http.message(statusCode, httpStatusCodes[statusCode]));
|
|
256
154
|
}
|
|
257
155
|
});
|
|
258
156
|
},
|
|
@@ -260,4 +158,4 @@ const router: Router = (port?: number | string, options?: RouterOptions<Options>
|
|
|
260
158
|
}
|
|
261
159
|
|
|
262
160
|
|
|
263
|
-
export { router,
|
|
161
|
+
export { router, extract, http }
|
package/package.json
CHANGED