bun-router 0.5.8 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +1 -1
- package/examples/basic.ts +3 -8
- package/examples/cookies.ts +15 -0
- package/examples/dynamic.ts +12 -17
- package/examples/logger.ts +2 -2
- package/examples/todo.ts +3 -3
- package/lib/http/generic-methods.ts +20 -13
- package/lib/logger/color.ts +1 -1
- package/lib/logger/logger.ts +9 -0
- package/lib/router/router.d.ts +23 -17
- package/lib/router/router.ts +43 -86
- package/lib/router/tree.ts +90 -0
- package/package.json +1 -1
- package/tests/router.test.ts +0 -64
- package/examples/sqlite.ts +0 -22
package/README.md
CHANGED
package/examples/basic.ts
CHANGED
@@ -2,17 +2,12 @@ import { router, http } from '..';
|
|
2
2
|
|
3
3
|
const r = router();
|
4
4
|
|
5
|
-
r.add('/', 'GET', () => http.json('ok'));
|
5
|
+
r.add('/', 'GET', () => http.json(200, 'ok'));
|
6
6
|
|
7
7
|
r.add('/user/:name', 'GET', (ctx) => {
|
8
8
|
const name = ctx.params.get('name');
|
9
|
-
return http.json(name);
|
10
|
-
|
11
|
-
|
12
|
-
r.add('/user/:name/:id', 'GET', (ctx) => {
|
13
|
-
const name = ctx.params.get('name');
|
14
|
-
const id = ctx.params.get('id');
|
15
|
-
return http.json({name: name, id: id});
|
9
|
+
if (!name) return http.json(500, 'no name');
|
10
|
+
return http.json(200, name);
|
16
11
|
});
|
17
12
|
|
18
13
|
r.serve();
|
@@ -0,0 +1,15 @@
|
|
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();
|
package/examples/dynamic.ts
CHANGED
@@ -1,29 +1,24 @@
|
|
1
1
|
import { router, http } from '..';
|
2
2
|
import { Context } from '../lib/router/router.d';
|
3
3
|
|
4
|
-
const
|
5
|
-
const name = ctx.params.get('name');
|
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
|
-
}
|
4
|
+
const home = (ctx: Context) => new Response('Welcome Home', { status: 200 });
|
9
5
|
|
10
|
-
const
|
11
|
-
const
|
12
|
-
if (
|
13
|
-
return http.
|
6
|
+
const subreddit = (ctx: Context) => {
|
7
|
+
const sub = ctx.params.get('subreddit');
|
8
|
+
if (!sub) return http.json(400, { error: 'no subreddit provided' });
|
9
|
+
return http.json(200, { subreddit: sub });
|
14
10
|
}
|
15
11
|
|
16
|
-
const
|
17
|
-
const
|
18
|
-
if (
|
19
|
-
return http.
|
12
|
+
const user = (ctx: Context) => {
|
13
|
+
const user = ctx.params.get('user');
|
14
|
+
if (!user) return http.json(400, { error: 'no user provided' });
|
15
|
+
return http.json(200, { user: user });
|
20
16
|
}
|
21
17
|
|
22
18
|
const r = router();
|
23
19
|
|
24
|
-
r.add('/
|
25
|
-
r.add('/
|
26
|
-
r.add('/u/:
|
27
|
-
|
20
|
+
r.add('/', 'GET', home);
|
21
|
+
r.add('/r/:subreddit', 'GET', subreddit);
|
22
|
+
r.add('/u/:user', 'GET', user);
|
28
23
|
|
29
24
|
r.serve();
|
package/examples/logger.ts
CHANGED
@@ -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 http.json(
|
11
|
+
return http.json(500,{text: 'Foo is undefined'});
|
12
12
|
}
|
13
|
-
return http.html(`<h4 style='font-family: sans-serif;'>Oh hello, ${foo}</h4>`)
|
13
|
+
return http.html(200, `<h4 style='font-family: sans-serif;'>Oh hello, ${foo}</h4>`)
|
14
14
|
});
|
15
15
|
|
16
16
|
r.serve();
|
package/examples/todo.ts
CHANGED
@@ -26,7 +26,7 @@ r.add('/api/new', 'POST', ctx => {
|
|
26
26
|
ctx.logger.message(`Adding ${key} with ${content}`);
|
27
27
|
todo.add(key, content);
|
28
28
|
|
29
|
-
return ctx.json({ message: 'ok' });
|
29
|
+
return ctx.json(200, { message: 'ok' });
|
30
30
|
});
|
31
31
|
|
32
32
|
r.add('/api/todo/:key', 'GET', ctx => {
|
@@ -36,11 +36,11 @@ r.add('/api/todo/:key', 'GET', ctx => {
|
|
36
36
|
const content = todo.get(key);
|
37
37
|
if (!content) return http.notFound();
|
38
38
|
|
39
|
-
return ctx.json({key: key, content: content});
|
39
|
+
return ctx.json(200, {key: key, content: content});
|
40
40
|
});
|
41
41
|
|
42
42
|
r.add('/api/get/all', 'GET', ctx => {
|
43
|
-
return ctx.json(todo.export());
|
43
|
+
return ctx.json(200, todo.export());
|
44
44
|
});
|
45
45
|
|
46
46
|
r.serve();
|
@@ -1,23 +1,22 @@
|
|
1
1
|
import { httpStatusCodes } from "./status";
|
2
2
|
|
3
3
|
const http = {
|
4
|
-
json: async (data: any): Promise<Response> => {
|
4
|
+
json: async (statusCode: number, data: any): Promise<Response> => {
|
5
5
|
const jsonString = JSON.stringify(data);
|
6
6
|
return Promise.resolve(new Response(jsonString, {
|
7
|
-
status:
|
8
|
-
statusText: httpStatusCodes[
|
7
|
+
status: statusCode,
|
8
|
+
statusText: httpStatusCodes[statusCode],
|
9
9
|
headers: {'Content-Type': 'application/json'},
|
10
10
|
}));
|
11
11
|
},
|
12
|
-
html: async (content: string): Promise<Response> => {
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
statusText: httpStatusCodes[200],
|
12
|
+
html: async (statusCode: number, content: string): Promise<Response> => {
|
13
|
+
return Promise.resolve(new Response(content, {
|
14
|
+
status: statusCode,
|
15
|
+
statusText: httpStatusCodes[statusCode],
|
17
16
|
headers: {'Content-Type': 'text/html; charset=utf-8'}
|
18
17
|
}));
|
19
18
|
},
|
20
|
-
file: async (fp: string): Promise<Response> => {
|
19
|
+
file: async (statusCode: number, fp: string): Promise<Response> => {
|
21
20
|
const file = Bun.file(fp);
|
22
21
|
const exists = await file.exists();
|
23
22
|
|
@@ -32,8 +31,8 @@ const http = {
|
|
32
31
|
contentType = file.type + '; charset=utf-8';
|
33
32
|
|
34
33
|
return Promise.resolve(new Response(content, {
|
35
|
-
status:
|
36
|
-
statusText: httpStatusCodes[
|
34
|
+
status: statusCode,
|
35
|
+
statusText: httpStatusCodes[statusCode],
|
37
36
|
headers: { 'Content-Type': contentType}
|
38
37
|
}));
|
39
38
|
},
|
@@ -50,6 +49,15 @@ const http = {
|
|
50
49
|
|
51
50
|
return Promise.resolve(response);
|
52
51
|
},
|
52
|
+
methodNotAllowed: async (msg?: string): Promise<Response> => {
|
53
|
+
const response = new Response(msg ?? 'method not allowed', {
|
54
|
+
status: 405,
|
55
|
+
statusText: httpStatusCodes[405],
|
56
|
+
headers: {'Content-Type': 'text/html'},
|
57
|
+
});
|
58
|
+
|
59
|
+
return Promise.resolve(response);
|
60
|
+
},
|
53
61
|
message: async (status: number, msg?: string): Promise<Response> => {
|
54
62
|
const response = new Response(msg ?? '?', {
|
55
63
|
status: status,
|
@@ -57,8 +65,7 @@ const http = {
|
|
57
65
|
headers: {'Content-Type': 'text/html; charset-utf-8'},
|
58
66
|
});
|
59
67
|
return Promise.resolve(response)
|
60
|
-
}
|
61
|
-
|
68
|
+
},
|
62
69
|
}
|
63
70
|
|
64
71
|
export { http }
|
package/lib/logger/color.ts
CHANGED
package/lib/logger/logger.ts
CHANGED
@@ -3,6 +3,14 @@ import {Logger} from './logger.d';
|
|
3
3
|
|
4
4
|
const pad = (n: number) => String(n).padStart(2, '0');
|
5
5
|
|
6
|
+
const TITLE = `
|
7
|
+
_ _
|
8
|
+
| |_ _ _ ___ ___ ___ _ _| |_ ___ ___
|
9
|
+
| . | | | | | _| . | | | _| -_| _|
|
10
|
+
|___|___|_|_| |_| |___|___|_| |___|_|
|
11
|
+
|
12
|
+
`
|
13
|
+
|
6
14
|
const timestamp = (date: Date) => {
|
7
15
|
const month = pad(date.getMonth());
|
8
16
|
const day = pad(date.getDate());
|
@@ -48,6 +56,7 @@ const logger = (): Logger => {
|
|
48
56
|
const portColor = color('green', 'bgBlack', String(port));
|
49
57
|
const msg = `${source}: Starting Server on :${portColor}\n`;
|
50
58
|
|
59
|
+
await Bun.write(Bun.stdout, TITLE);
|
51
60
|
await Bun.write(Bun.stdout, msg);
|
52
61
|
},
|
53
62
|
info: async (statusCode: number, routePath: string, method: string, message?: string) => {
|
package/lib/router/router.d.ts
CHANGED
@@ -2,21 +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 HttpHandler = (ctx: Context) => Response | Promise<Response>
|
5
6
|
|
6
7
|
type Context = {
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
8
|
+
cookies: Map<string, string>;
|
9
|
+
db?: Database;
|
10
|
+
formData: FormData | Promise<FormData> | undefined;
|
11
|
+
json: (statusCode: number, data: any) => Response | Promise<Response>;
|
12
|
+
logger: Logger;
|
13
|
+
params: Map<string, string>;
|
14
|
+
query: URLSearchParams;
|
15
|
+
request: Request;
|
16
|
+
token?: string;
|
17
|
+
};
|
18
|
+
|
15
19
|
|
16
20
|
type Route = {
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
children: Map<string, Route>;
|
22
|
+
path: string;
|
23
|
+
dynamicPath: string;
|
24
|
+
method: string;
|
25
|
+
handler: HttpHandler;
|
26
|
+
isLast: boolean;
|
20
27
|
}
|
21
28
|
|
22
29
|
type Options = {
|
@@ -31,12 +38,11 @@ type RouterOptions<Options> = ServeOptions
|
|
31
38
|
|
32
39
|
|
33
40
|
type Router = (port?: number | string, options?: RouterOptions) => {
|
34
|
-
add: (pattern: string, method: string, callback: (req: Context) => Response | Promise<Response>) => void
|
35
|
-
|
36
|
-
|
37
|
-
static: (pattern: string, root: string) => void,
|
38
|
-
serve: () => void,
|
41
|
+
add: (pattern: string, method: string, callback: (req: Context) => Response | Promise<Response>) => void;
|
42
|
+
static: (pattern: string, root: string) => void;
|
43
|
+
serve: () => void;
|
39
44
|
}
|
40
45
|
|
41
46
|
|
42
|
-
|
47
|
+
|
48
|
+
export { Context , Route, Router, RouterOptions, Options, HttpHandler }
|
package/lib/router/router.ts
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
import path from 'path';
|
2
2
|
import { Database } from 'bun:sqlite';
|
3
|
-
import { Route, Router, Context, RouterOptions, Options } from './router.d';
|
3
|
+
import { Route, Router, Context, RouterOptions, Options, HttpHandler } from './router.d';
|
4
4
|
import { httpStatusCodes } from '../http/status';
|
5
5
|
import { readDir } from '../fs/fsys';
|
6
6
|
import { logger } from '../logger/logger';
|
7
|
-
import { Logger } from '../logger/logger.d';
|
8
7
|
import { http } from '../http/generic-methods';
|
8
|
+
import {Radix, createContext} from './tree';
|
9
9
|
|
10
|
-
|
11
|
-
// if the route pattern is /:foo and the request URL is /bar: {foo: 'bar'}
|
12
|
-
const extract = (route: Route, ctx: Context) => {
|
10
|
+
const extract = (path: string, ctx: Context) => {
|
13
11
|
const url = new URL(ctx.request.url);
|
14
|
-
const pathSegments =
|
12
|
+
const pathSegments = path.split('/');
|
15
13
|
const urlSegments = url.pathname.split('/');
|
16
14
|
|
17
15
|
if (pathSegments.length !== urlSegments.length) return
|
@@ -19,10 +17,10 @@ const extract = (route: Route, ctx: Context) => {
|
|
19
17
|
return {
|
20
18
|
params: () => {
|
21
19
|
for (let i = 0; i < pathSegments.length; i++) {
|
22
|
-
if
|
20
|
+
if((pathSegments[i][0] === ':')) {
|
23
21
|
const k = pathSegments[i].replace(':', '');
|
24
22
|
const v = urlSegments[i];
|
25
|
-
ctx.params.set(k,
|
23
|
+
ctx.params.set(k,v);
|
26
24
|
}
|
27
25
|
}
|
28
26
|
}
|
@@ -30,63 +28,16 @@ const extract = (route: Route, ctx: Context) => {
|
|
30
28
|
|
31
29
|
}
|
32
30
|
|
33
|
-
// ensure the route pattern matches the request URL
|
34
|
-
const match = (route: Route, ctx: Context): boolean => {
|
35
|
-
const url = new URL(ctx.request.url);
|
36
|
-
const patternRegex = new RegExp('^' + route.pattern.replace(/:[^/]+/g, '([^/]+)') + '$');
|
37
|
-
const matches = url.pathname.match(patternRegex);
|
38
|
-
|
39
|
-
if (matches && route.method === ctx.request.method) {
|
40
|
-
const extractor = extract(route, ctx);
|
41
|
-
extractor?.params();
|
42
|
-
|
43
|
-
return true;
|
44
|
-
}
|
45
|
-
|
46
|
-
return false;
|
47
|
-
}
|
48
|
-
|
49
|
-
// set the context for the reuest
|
50
|
-
const setContext = (req: Request, lgr: Logger, opts: Options, route: Route): Context => {
|
51
|
-
return {
|
52
|
-
request: req,
|
53
|
-
params: new Map(),
|
54
|
-
db: new Database(opts.db ?? ':memory:'),
|
55
|
-
logger: lgr,
|
56
|
-
route: route,
|
57
|
-
json: (data: any) => http.json(data),
|
58
|
-
}
|
59
|
-
}
|
60
|
-
|
61
31
|
const router: Router = (port?: number | string, options?: RouterOptions<Options>) => {
|
62
|
-
const
|
32
|
+
const {addRoute, findRoute} = Radix();
|
63
33
|
const lgr = logger();
|
64
|
-
let dbConn = '';
|
65
34
|
|
66
35
|
return {
|
67
|
-
// add a
|
68
|
-
add: (pattern: string, method: string, callback:
|
69
|
-
|
70
|
-
pattern: pattern,
|
71
|
-
method: method,
|
72
|
-
callback: callback,
|
73
|
-
})
|
36
|
+
// add a route to the router tree
|
37
|
+
add: (pattern: string, method: string, callback: HttpHandler) => {
|
38
|
+
addRoute(pattern, method, callback);
|
74
39
|
},
|
75
|
-
|
76
|
-
routes.push({
|
77
|
-
pattern: pattern,
|
78
|
-
method: 'GET',
|
79
|
-
callback: callback,
|
80
|
-
});
|
81
|
-
},
|
82
|
-
POST: (pattern: string, callback: (ctx: Context) => Response | Promise<Response>) => {
|
83
|
-
routes.push({
|
84
|
-
pattern: pattern,
|
85
|
-
method: 'POST',
|
86
|
-
callback: callback,
|
87
|
-
});
|
88
|
-
},
|
89
|
-
// add a route for static files
|
40
|
+
// add a static route to the router tree
|
90
41
|
static: async (pattern: string, root: string) => {
|
91
42
|
await readDir(root, async (fp, _) => {
|
92
43
|
const pure = path.join('.', fp);
|
@@ -103,12 +54,15 @@ const router: Router = (port?: number | string, options?: RouterOptions<Options>
|
|
103
54
|
if (base === 'index') patternPath = pattern;
|
104
55
|
|
105
56
|
const route: Route = {
|
106
|
-
|
57
|
+
children: new Map(),
|
58
|
+
dynamicPath: '',
|
59
|
+
isLast: true,
|
60
|
+
path: patternPath,
|
107
61
|
method: 'GET',
|
108
|
-
|
62
|
+
handler: async () => await http.file(200, pure),
|
109
63
|
};
|
110
64
|
|
111
|
-
|
65
|
+
addRoute(route.path, 'GET', route.handler);
|
112
66
|
});
|
113
67
|
},
|
114
68
|
// start the server
|
@@ -116,41 +70,44 @@ const router: Router = (port?: number | string, options?: RouterOptions<Options>
|
|
116
70
|
lgr.start(port ?? 3000);
|
117
71
|
let opts: Options = { db: ':memory:' };
|
118
72
|
|
73
|
+
// TODO: add support for TLS and WebSockets
|
119
74
|
Bun.serve({
|
120
75
|
port: port ?? 3000,
|
121
76
|
...options,
|
122
77
|
async fetch(req) {
|
123
78
|
const url = new URL(req.url);
|
79
|
+
let path = url.pathname;
|
124
80
|
|
81
|
+
// set the database
|
125
82
|
if (options) {
|
126
83
|
let o = options as Options;
|
127
84
|
opts.db = o.db;
|
128
85
|
}
|
129
86
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
87
|
+
const route = findRoute(path);
|
88
|
+
|
89
|
+
// if the route exists, execute the handler
|
90
|
+
if (route) {
|
91
|
+
if (route.method !== req.method) {
|
92
|
+
lgr.info(405, url.pathname, req.method, httpStatusCodes[405]);
|
93
|
+
return Promise.resolve(http.methodNotAllowed());
|
94
|
+
}
|
95
|
+
|
96
|
+
const context = createContext(path, route, req);
|
97
|
+
context.db = new Database(opts.db);
|
98
|
+
|
99
|
+
const response = await route.handler(context);
|
100
|
+
|
101
|
+
lgr.info(response.status, url.pathname, req.method, httpStatusCodes[response.status]);
|
102
|
+
return Promise.resolve(response);
|
103
|
+
}
|
104
|
+
|
105
|
+
// if no route is found, return 404
|
106
|
+
const response = await http.notFound();
|
107
|
+
|
108
|
+
lgr.info(response.status, url.pathname, req.method, httpStatusCodes[response.status]);
|
109
|
+
return Promise.resolve(http.notFound());
|
151
110
|
|
152
|
-
lgr.info(statusCode, url.pathname, req.method, httpStatusCodes[statusCode]);
|
153
|
-
return Promise.resolve(http.message(statusCode, httpStatusCodes[statusCode]));
|
154
111
|
}
|
155
112
|
});
|
156
113
|
},
|
@@ -0,0 +1,90 @@
|
|
1
|
+
import { HttpHandler, Context, Route } from "./router.d";
|
2
|
+
import { http } from "../http/generic-methods";
|
3
|
+
|
4
|
+
const splitPath = (s: string): string[] => s.split('/').filter(x => x !== '');
|
5
|
+
|
6
|
+
const createRoute = (path: string, method: string, handler: HttpHandler): Route => {
|
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;
|
17
|
+
};
|
18
|
+
|
19
|
+
const extractParams = (path: string, route: Route, params: Map<string, string>) => {
|
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 = () => {
|
48
|
+
let root = createRoute('', 'GET', () => http.notFound());
|
49
|
+
|
50
|
+
const addRoute = (path: string, method: string, handler: HttpHandler) => {
|
51
|
+
const pathParts = splitPath(path);
|
52
|
+
let current = root;
|
53
|
+
|
54
|
+
for (let i = 0; i < pathParts.length; i++) {
|
55
|
+
const part = pathParts[i];
|
56
|
+
if (part.startsWith(':')) {
|
57
|
+
current.dynamicPath = part;
|
58
|
+
}
|
59
|
+
if (!current.children.has(part)) {
|
60
|
+
current.children.set(part, createRoute(part, method, handler));
|
61
|
+
}
|
62
|
+
current = current.children.get(part)!;
|
63
|
+
}
|
64
|
+
|
65
|
+
current.handler = handler;
|
66
|
+
current.isLast = true;
|
67
|
+
current.path = path;
|
68
|
+
};
|
69
|
+
|
70
|
+
const findRoute = (path: string): Route | undefined => {
|
71
|
+
const pathParts = splitPath(path);
|
72
|
+
let current = root;
|
73
|
+
for (let i = 0; i < pathParts.length; i++) {
|
74
|
+
const part = pathParts[i];
|
75
|
+
if (current.children.has(part)) {
|
76
|
+
current = current.children.get(part)!;
|
77
|
+
} else if (current.dynamicPath) {
|
78
|
+
current = current.children.get(current.dynamicPath)!;
|
79
|
+
} else {
|
80
|
+
return;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
return current;
|
84
|
+
}
|
85
|
+
|
86
|
+
return { addRoute, findRoute }
|
87
|
+
|
88
|
+
};
|
89
|
+
|
90
|
+
export { Radix, createContext }
|
package/package.json
CHANGED
package/tests/router.test.ts
CHANGED
@@ -1,68 +1,4 @@
|
|
1
1
|
import { describe, test, expect } from 'bun:test';
|
2
|
-
import { extract } from '..';
|
3
|
-
import { Context, Route } from '../lib/router/router.d';
|
4
|
-
|
5
|
-
describe('URL Params', () => {
|
6
|
-
test('/user/:name', () => {
|
7
|
-
const route: Route = {
|
8
|
-
pattern: '/user/:name',
|
9
|
-
method: 'GET',
|
10
|
-
callback: () => new Response('ok'),
|
11
|
-
};
|
12
|
-
|
13
|
-
const ctx: Context = {
|
14
|
-
request: new Request('http://localhost:3000/user/foo'),
|
15
|
-
params: new Map(),
|
16
|
-
};
|
17
|
-
|
18
|
-
const extractor = extract(route, ctx);
|
19
|
-
|
20
|
-
extractor?.params();
|
21
|
-
|
22
|
-
const name = ctx.params.get('name');
|
23
|
-
expect(name).toBe('foo');
|
24
|
-
});
|
25
|
-
|
26
|
-
test('/user/:name/:id', () => {
|
27
|
-
const route: Route = {
|
28
|
-
pattern: '/user/:name/:id',
|
29
|
-
method: 'GET',
|
30
|
-
callback: () => new Response('ok'),
|
31
|
-
};
|
32
|
-
|
33
|
-
const ctx: Context = {
|
34
|
-
request: new Request('http://localhost:3000/user/foo/123'),
|
35
|
-
params: new Map(),
|
36
|
-
};
|
37
|
-
|
38
|
-
const extractor = extract(route, ctx);
|
39
|
-
|
40
|
-
extractor?.params();
|
41
|
-
|
42
|
-
const name = ctx.params.get('name');
|
43
|
-
const id = ctx.params.get('id');
|
44
|
-
|
45
|
-
expect(name).toBe('foo');
|
46
|
-
expect(id).toBe('123');
|
47
|
-
});
|
48
|
-
|
49
|
-
test('/foo', () => {
|
50
|
-
const route: Route = {
|
51
|
-
pattern: '/foo',
|
52
|
-
method: 'GET',
|
53
|
-
callback: () => new Response('ok'),
|
54
|
-
}
|
55
|
-
|
56
|
-
const ctx: Context = {
|
57
|
-
request: new Request('http://localhost:3000/foo'),
|
58
|
-
params: new Map(),
|
59
|
-
}
|
60
|
-
|
61
|
-
const url = new URL(ctx.request.url);
|
62
|
-
|
63
|
-
expect(url.pathname).toBe(route.pattern);
|
64
|
-
});
|
65
|
-
});
|
66
2
|
|
67
3
|
describe('Router', () => {
|
68
4
|
test('Serve', async () => {
|
package/examples/sqlite.ts
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
import { router, http } 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 http.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 ? http.json(d) : new Response('not found', {status: 404});
|
20
|
-
});
|
21
|
-
|
22
|
-
r.serve();
|