bun-router 0.2.8 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +25 -66
- package/examples/basic.ts +18 -0
- package/examples/pages/gopher.png +0 -0
- package/examples/pages/vader.jpg +0 -0
- package/examples/static.ts +6 -0
- package/index.ts +2 -1
- package/lib/fs/fsys.ts +24 -0
- package/lib/router/router.d.ts +7 -4
- package/lib/router/router.ts +65 -24
- package/package.json +1 -1
- package/tests/router.test.ts +85 -31
- package/tests/serve.test.sh +35 -0
- package/tests/static.test.sh +55 -0
- package/examples/cache.ts +0 -41
- package/examples/file.ts +0 -12
- package/examples/html.ts +0 -9
- package/examples/params.ts +0 -24
- package/examples/post.ts +0 -38
- package/examples/space.ts +0 -9
- package/tests/router.test.sh +0 -42
package/README.md
CHANGED
@@ -3,96 +3,55 @@
|
|
3
3
|
I needed a router for `Bun`, so I made one. It's simple, naive, and hardly anything is abstracted.
|
4
4
|
|
5
5
|
### Usage
|
6
|
-
Import the `router`.
|
7
6
|
```typescript
|
8
7
|
import { router } from 'bun-router';
|
9
|
-
```
|
10
|
-
|
11
|
-
Create the `router`.
|
12
|
-
```typescript
|
13
|
-
const r = router(3000)
|
14
|
-
```
|
15
|
-
|
16
|
-
Add routes to the `router`.
|
17
|
-
```typescript
|
18
|
-
r.add('/', 'GET', req => new Response('Hello World'));
|
19
|
-
```
|
20
|
-
|
21
|
-
The `req` parameter is of type `HttpRequest` which is just a type that contains both `Response` and `Params` for URL parameters.
|
22
|
-
|
23
|
-
Start the server.
|
24
|
-
```typescript
|
25
|
-
r.serve()
|
26
|
-
```
|
27
|
-
|
28
|
-
Some overly-simple examples:
|
29
|
-
```typescript
|
30
|
-
import { router, json } from '..';
|
31
8
|
|
32
9
|
const r = router();
|
33
10
|
|
34
|
-
|
35
|
-
dogs: ['Joey', 'Benny', 'Max'],
|
36
|
-
cats: ['Charles', 'Arya', 'Binx'],
|
37
|
-
}
|
38
|
-
|
39
|
-
const foods = {
|
40
|
-
apple: '🍎', banana: '🍌', strawberry: '🍓', pear: '🍐',
|
41
|
-
}
|
42
|
-
|
43
|
-
r.add('/pets/:type', 'GET', req => {
|
44
|
-
const petType = req.params.get('type') as keyof typeof pets;
|
45
|
-
return json(pets[petType] ?? 'not found');
|
46
|
-
});
|
47
|
-
r.add('/grocery/:food', 'GET', req => {
|
48
|
-
const food = req.params.get('food') as keyof typeof foods
|
49
|
-
return json(foods[food] ?? 'not found')
|
50
|
-
});
|
11
|
+
r.add('/', 'GET', (ctx) => new Response('Hello World'));
|
51
12
|
|
52
13
|
r.serve();
|
53
14
|
```
|
54
|
-
|
15
|
+
#### Static Files
|
55
16
|
```typescript
|
56
|
-
import { router
|
17
|
+
import { router } from 'bun-router';
|
57
18
|
|
58
19
|
const r = router();
|
59
20
|
|
21
|
+
r.static('/', './pages');
|
60
22
|
|
61
|
-
|
62
|
-
|
63
|
-
return {
|
64
|
-
set: (key: string, value: string) => {
|
65
|
-
cache.set(key, value);
|
66
|
-
},
|
67
|
-
get: (key: string) => cache.get(key)!,
|
68
|
-
}
|
69
|
-
}
|
23
|
+
r.serve();
|
24
|
+
```
|
70
25
|
|
71
|
-
|
26
|
+
##### Example
|
27
|
+
```typescript
|
28
|
+
import {router, html, json } from 'bun-router';
|
72
29
|
|
73
|
-
const
|
30
|
+
const r = router(3001);
|
74
31
|
|
75
|
-
r.
|
76
|
-
const url = new URL(req.request.url);
|
77
|
-
const query = url.searchParams;
|
32
|
+
r.static('/assets', './assets');
|
78
33
|
|
79
|
-
|
34
|
+
r.add('/', (ctx) => html('<h1>Hello World</h1>'));
|
80
35
|
|
81
|
-
|
36
|
+
r.add('/greeting/:name', 'GET', (ctx) => {
|
37
|
+
const name = ctx.params.get('name');
|
38
|
+
if (!name) return new Response('invalid url parameters', {status: 400});
|
82
39
|
|
83
|
-
return
|
40
|
+
return html(`<h4>Greetings, ${name}!</h4>`);
|
84
41
|
});
|
85
42
|
|
86
|
-
|
87
|
-
const name = req.params.get('key')
|
88
|
-
const result = cache.get(name ?? '');
|
43
|
+
const store: Map<string, string> = new Map();
|
89
44
|
|
90
|
-
|
45
|
+
r.add('/user/:id', 'GET', (ctx) => {
|
46
|
+
const id = ctx.params.get('id');
|
47
|
+
if (!id) return new Response('user not found', {status: 404});
|
91
48
|
|
92
|
-
|
49
|
+
const user = store.get(id);
|
93
50
|
|
51
|
+
if (!user) return new Response('user not found', { status: 404 });
|
52
|
+
|
53
|
+
return json(user);
|
94
54
|
});
|
95
55
|
|
96
56
|
r.serve();
|
97
|
-
```
|
98
|
-
|
57
|
+
```
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { router, html, json } from '..';
|
2
|
+
|
3
|
+
const r = router();
|
4
|
+
|
5
|
+
r.add('/', 'GET', () => json('ok'));
|
6
|
+
|
7
|
+
r.add('/user/:name', 'GET', (ctx) => {
|
8
|
+
const name = ctx.params.get('name');
|
9
|
+
return 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 json({name: name, id: id});
|
16
|
+
});
|
17
|
+
|
18
|
+
r.serve();
|
Binary file
|
Binary file
|
package/index.ts
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
export * from './lib/router/router';
|
1
|
+
export * from './lib/router/router';
|
2
|
+
export * from './lib/fs/fsys';
|
package/lib/fs/fsys.ts
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
import { BunFile } from "bun";
|
2
|
+
import fs from 'node:fs/promises';
|
3
|
+
import path from 'path';
|
4
|
+
|
5
|
+
const isDir = async (fp: string): Promise<boolean> => (await fs.lstat(fp)).isDirectory();
|
6
|
+
|
7
|
+
const readDir = async (dirpath: string, handler: (filepath: string, entry: BunFile) => void) => {
|
8
|
+
try {
|
9
|
+
const files = await fs.readdir(dirpath);
|
10
|
+
|
11
|
+
for (const file of files) {
|
12
|
+
const bunFile = Bun.file(file);
|
13
|
+
const fp = path.join(dirpath, bunFile.name!);
|
14
|
+
const isdir = await isDir(fp);
|
15
|
+
|
16
|
+
if (isdir) await readDir(fp, handler);
|
17
|
+
else handler(fp, bunFile);
|
18
|
+
}
|
19
|
+
} catch (err) {
|
20
|
+
console.error(err);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
export { readDir }
|
package/lib/router/router.d.ts
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
import { TLSOptions, TLSWebSocketServeOptions, WebSocketServeOptions, ServeOptions, TLSServeOptions } from 'bun';
|
2
2
|
|
3
3
|
|
4
|
-
type
|
4
|
+
type Context = {
|
5
5
|
request: Request,
|
6
6
|
params: Map<string, string>,
|
7
|
+
fs: Map<string, string>,
|
8
|
+
token?: string,
|
7
9
|
}
|
8
10
|
|
9
11
|
type Route = {
|
10
12
|
pattern: string,
|
11
13
|
method: string,
|
12
|
-
callback: (req:
|
14
|
+
callback: (req: Context) => Response | Promise<Response>
|
13
15
|
}
|
14
16
|
|
15
17
|
type Options = ServeOptions
|
@@ -20,9 +22,10 @@ type Options = ServeOptions
|
|
20
22
|
|
21
23
|
|
22
24
|
type Router = (port?: number | string, options?: Options) => {
|
23
|
-
add: (pattern: string, method: string, callback: (req:
|
25
|
+
add: (pattern: string, method: string, callback: (req: Context) => Response | Promise<Response>) => void,
|
26
|
+
static: (pattern: string, root: string) => void,
|
24
27
|
serve: () => void,
|
25
28
|
}
|
26
29
|
|
27
30
|
|
28
|
-
export {
|
31
|
+
export { Context , Route, Router, Options }
|
package/lib/router/router.ts
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
import { Route, Router,
|
1
|
+
import { Route, Router, Context, Options } from './router.d';
|
2
|
+
import { readDir } from '../fs/fsys';
|
3
|
+
import path from 'path';
|
2
4
|
|
3
5
|
const notFound = async (): Promise<Response> => {
|
4
6
|
const response = new Response('not found', {
|
@@ -19,14 +21,20 @@ const file = async (filepath: string): Promise<Response> => {
|
|
19
21
|
if (!exists)
|
20
22
|
return notFound();
|
21
23
|
|
22
|
-
const content = await file.
|
23
|
-
if (content
|
24
|
+
const content = await file.arrayBuffer();
|
25
|
+
if (!content)
|
24
26
|
return notFound();
|
25
27
|
|
28
|
+
let contentType = 'text/html; charset=utf-8';
|
29
|
+
|
30
|
+
if (file.type.includes('image')) {
|
31
|
+
contentType = file.type + '; charset=utf-8';
|
32
|
+
}
|
33
|
+
|
26
34
|
const response = new Response(content, {
|
27
35
|
status: 200,
|
28
36
|
statusText: 'ok',
|
29
|
-
headers: { 'Content-Type':
|
37
|
+
headers: { 'Content-Type': contentType },
|
30
38
|
});
|
31
39
|
|
32
40
|
return new Promise<Response>((resolve) => {
|
@@ -38,8 +46,9 @@ const html = async (content: string): Promise<Response> => {
|
|
38
46
|
const response = new Response(content, {
|
39
47
|
status: 200,
|
40
48
|
statusText: 'ok',
|
41
|
-
headers: {'Content-Type': 'text/html; charset=utf-8'},
|
49
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
42
50
|
});
|
51
|
+
content = Bun.escapeHTML(content);
|
43
52
|
|
44
53
|
return new Promise<Response>((resolve) => {
|
45
54
|
resolve(response);
|
@@ -55,57 +64,89 @@ const json = (data: any): Response => {
|
|
55
64
|
return res
|
56
65
|
}
|
57
66
|
|
58
|
-
const
|
59
|
-
const url = new URL(
|
67
|
+
const extract = (route: Route, ctx: Context) => {
|
68
|
+
const url = new URL(ctx.request.url);
|
60
69
|
const pathSegments = route.pattern.split('/');
|
61
70
|
const urlSegments = url.pathname.split('/');
|
62
71
|
|
63
72
|
if (pathSegments.length !== urlSegments.length) return
|
64
73
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
74
|
+
|
75
|
+
return {
|
76
|
+
params: () => {
|
77
|
+
for (let i = 0; i < pathSegments.length; i++) {
|
78
|
+
if ((pathSegments[i][0] === ':')) {
|
79
|
+
const k = pathSegments[i].replace(':', '');
|
80
|
+
const v = urlSegments[i];
|
81
|
+
ctx.params.set(k, v);
|
82
|
+
}
|
83
|
+
}
|
70
84
|
}
|
71
85
|
}
|
86
|
+
|
72
87
|
}
|
73
88
|
|
74
|
-
const match = (route: Route,
|
75
|
-
return
|
89
|
+
const match = (route: Route, ctx: Context): boolean => {
|
90
|
+
return ctx.params.size !== 0 || route.pattern === (new URL(ctx.request.url)).pathname
|
76
91
|
}
|
77
92
|
|
78
93
|
const router: Router = (port?: number | string, options?: Options) => {
|
79
94
|
const routes: Array<Route> = new Array();
|
95
|
+
const paths: { [key: string]: string } = {};
|
80
96
|
|
81
97
|
return {
|
82
|
-
add: (pattern: string, method: string, callback: (
|
98
|
+
add: (pattern: string, method: string, callback: (ctx: Context) => Response | Promise<Response>) => {
|
83
99
|
routes.push({
|
84
100
|
pattern: pattern,
|
85
101
|
method: method,
|
86
102
|
callback: callback,
|
87
103
|
})
|
88
104
|
},
|
105
|
+
static: async (pattern: string, root: string) => {
|
106
|
+
await readDir(root, async (fp, _) => {
|
107
|
+
const pure = path.join('.', fp);
|
108
|
+
const ext = path.extname(pure);
|
109
|
+
|
110
|
+
let base = path.basename(pure);
|
111
|
+
|
112
|
+
if (ext === '.html') {
|
113
|
+
base = base.replace(ext, '');
|
114
|
+
|
115
|
+
}
|
116
|
+
|
117
|
+
if (pattern[0] !== '/') pattern = '/' + pattern;
|
118
|
+
|
119
|
+
let patternPath = pattern + base;
|
120
|
+
|
121
|
+
if (base === 'index') patternPath = pattern;
|
122
|
+
|
123
|
+
const route: Route = {
|
124
|
+
pattern: patternPath,
|
125
|
+
method: 'GET',
|
126
|
+
callback: async () => await file(pure),
|
127
|
+
};
|
128
|
+
routes.push(route);
|
129
|
+
});
|
130
|
+
},
|
89
131
|
serve: () => {
|
90
|
-
console.log(`[bun-router]: Listening on port -> :${
|
132
|
+
console.log(`[bun-router]: Listening on port -> :${port ?? 3000}`)
|
91
133
|
Bun.serve({
|
92
134
|
port: port ?? 3000,
|
93
135
|
...options,
|
94
136
|
fetch(req) {
|
95
|
-
const url = new URL(req.url);
|
96
137
|
for (const route of routes) {
|
97
|
-
const
|
138
|
+
const ctx: Context = {
|
98
139
|
request: req,
|
99
140
|
params: new Map(),
|
141
|
+
fs: new Map(),
|
100
142
|
};
|
101
143
|
|
102
|
-
|
103
|
-
|
104
|
-
if (match(route, httpRequest))
|
105
|
-
return route.callback(httpRequest);
|
144
|
+
const extractor = extract(route, ctx);
|
106
145
|
|
146
|
+
extractor?.params();
|
107
147
|
|
108
|
-
if (match(route,
|
148
|
+
if (match(route, ctx))
|
149
|
+
return route.callback(ctx);
|
109
150
|
}
|
110
151
|
return new Response('not found');
|
111
152
|
}
|
@@ -114,4 +155,4 @@ const router: Router = (port?: number | string, options?: Options) => {
|
|
114
155
|
}
|
115
156
|
}
|
116
157
|
|
117
|
-
export { router, json, file,
|
158
|
+
export { router, json, file, extract, html }
|
package/package.json
CHANGED
package/tests/router.test.ts
CHANGED
@@ -1,47 +1,101 @@
|
|
1
1
|
import { describe, test, expect } from 'bun:test';
|
2
|
-
import {
|
3
|
-
import {
|
4
|
-
|
5
|
-
describe('Helpers', async () => {
|
6
|
-
test('html', async () => {
|
7
|
-
const fp = './examples/pages/index.html';
|
8
|
-
const res = await file(fp);
|
9
|
-
const contentType = res.headers.get('Content-Type');
|
10
|
-
|
11
|
-
expect(contentType).toBe('text/html; charset=utf-8');
|
12
|
-
expect(res.status).toBe(200);
|
13
|
-
});
|
2
|
+
import { router, extract } from '..';
|
3
|
+
import { Context, Route } from '../lib/router/router.d';
|
14
4
|
|
15
|
-
|
16
|
-
|
17
|
-
const
|
18
|
-
|
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
|
+
fs: new Map(),
|
17
|
+
};
|
19
18
|
|
20
|
-
|
19
|
+
const extractor = extract(route, ctx);
|
21
20
|
|
21
|
+
extractor?.params();
|
22
|
+
|
23
|
+
const name = ctx.params.get('name');
|
24
|
+
expect(name).toBe('foo');
|
22
25
|
});
|
23
26
|
|
24
|
-
test('
|
25
|
-
const route: Route = {
|
26
|
-
|
27
|
-
|
27
|
+
test('/user/:name/:id', () => {
|
28
|
+
const route: Route = {
|
29
|
+
pattern: '/user/:name/:id',
|
30
|
+
method: 'GET',
|
31
|
+
callback: () => new Response('ok'),
|
32
|
+
};
|
33
|
+
|
34
|
+
const ctx: Context = {
|
35
|
+
request: new Request('http://localhost:3000/user/foo/123'),
|
28
36
|
params: new Map(),
|
29
|
-
|
37
|
+
fs: new Map(),
|
38
|
+
};
|
39
|
+
|
40
|
+
const extractor = extract(route, ctx);
|
41
|
+
|
42
|
+
extractor?.params();
|
43
|
+
|
44
|
+
const name = ctx.params.get('name');
|
45
|
+
const id = ctx.params.get('id');
|
30
46
|
|
31
|
-
extractParams(route, httpRequest);
|
32
|
-
const name = httpRequest.params.get('name');
|
33
47
|
expect(name).toBe('foo');
|
48
|
+
expect(id).toBe('123');
|
34
49
|
});
|
35
50
|
|
36
|
-
test('
|
37
|
-
const route: Route = {
|
38
|
-
|
39
|
-
|
51
|
+
test('/foo', () => {
|
52
|
+
const route: Route = {
|
53
|
+
pattern: '/foo',
|
54
|
+
method: 'GET',
|
55
|
+
callback: () => new Response('ok'),
|
56
|
+
}
|
57
|
+
|
58
|
+
const ctx: Context = {
|
59
|
+
request: new Request('http://localhost:3000/foo'),
|
40
60
|
params: new Map(),
|
61
|
+
fs: new Map(),
|
41
62
|
}
|
42
63
|
|
43
|
-
|
44
|
-
|
45
|
-
expect(
|
64
|
+
const url = new URL(ctx.request.url);
|
65
|
+
|
66
|
+
expect(url.pathname).toBe(route.pattern);
|
67
|
+
});
|
68
|
+
});
|
69
|
+
|
70
|
+
describe('Router', () => {
|
71
|
+
test('Serve', async () => {
|
72
|
+
const proc = Bun.spawn(['./tests/serve.test.sh'], {
|
73
|
+
onExit: (proc, exitCode, signalCode , error) => {
|
74
|
+
if (error) console.error(error);
|
75
|
+
},
|
76
|
+
});
|
77
|
+
|
78
|
+
const text = await new Response(proc.stdout).text();
|
79
|
+
|
80
|
+
const hasFailed = text.includes('Failed');
|
81
|
+
|
82
|
+
if (hasFailed) console.log(text);
|
83
|
+
|
84
|
+
expect(hasFailed).toBe(false);
|
85
|
+
|
86
|
+
proc.kill(0);
|
87
|
+
})
|
88
|
+
|
89
|
+
test('Static', async() => {
|
90
|
+
const proc = Bun.spawn(['./tests/static.test.sh']);
|
91
|
+
|
92
|
+
const text = await new Response(proc.stdout).text();
|
93
|
+
|
94
|
+
const hasFailed = text.includes('Failed');
|
95
|
+
if (hasFailed) console.log(text);
|
96
|
+
|
97
|
+
expect(hasFailed).toBe(false);
|
98
|
+
|
99
|
+
proc.kill(0);
|
46
100
|
});
|
47
101
|
});
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
bun run ./examples/basic.ts &
|
4
|
+
SERVER_PID=$!
|
5
|
+
|
6
|
+
function stop_server {
|
7
|
+
kill $SERVER_PID
|
8
|
+
}
|
9
|
+
|
10
|
+
trap stop_server EXIT
|
11
|
+
|
12
|
+
sleep .1
|
13
|
+
|
14
|
+
declare -A test_cases
|
15
|
+
test_cases['"ok"']="http://localhost:3000/"
|
16
|
+
test_cases['"baz"']="http://localhost:3000/user/baz"
|
17
|
+
|
18
|
+
function run_test() {
|
19
|
+
local expected_response="$1"
|
20
|
+
local url="$2"
|
21
|
+
|
22
|
+
actual_response=$(curl -sS "$url")
|
23
|
+
|
24
|
+
if [[ "$actual_response" == "$expected_response" ]]; then
|
25
|
+
echo "Passed."
|
26
|
+
else
|
27
|
+
echo "Failed: $url returned $actual_response : $expected_response"
|
28
|
+
fi
|
29
|
+
}
|
30
|
+
|
31
|
+
for expected in "${!test_cases[@]}"; do
|
32
|
+
url="${test_cases[$expected]}"
|
33
|
+
run_test "$expected" "$url"
|
34
|
+
done
|
35
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
bun run ./examples/static.ts &
|
4
|
+
SERVER_PID=$!
|
5
|
+
|
6
|
+
function stop_server {
|
7
|
+
kill $SERVER_PID
|
8
|
+
}
|
9
|
+
|
10
|
+
trap stop_server EXIT
|
11
|
+
|
12
|
+
sleep .1
|
13
|
+
|
14
|
+
index=$(<./examples/pages/index.html)
|
15
|
+
vader=$(md5sum ./examples/pages/vader.jpg | awk '{print $1}')
|
16
|
+
gopher=$(md5sum ./examples/pages/gopher.png | awk '{print $1}')
|
17
|
+
|
18
|
+
declare -A test_cases
|
19
|
+
test_cases["$index"]="http://localhost:3001/"
|
20
|
+
test_cases["$vader"]="http://localhost:3001/vader.jpg"
|
21
|
+
test_cases["$gopher"]="http://localhost:3001/gopher.png"
|
22
|
+
|
23
|
+
function does_exist() {
|
24
|
+
if [ ! -f "$1" ]; then
|
25
|
+
echo "Error: File '$1' not found."
|
26
|
+
exit 1
|
27
|
+
fi
|
28
|
+
}
|
29
|
+
|
30
|
+
function run_test() {
|
31
|
+
local expected="$1"
|
32
|
+
local url="$2"
|
33
|
+
local actual=""
|
34
|
+
|
35
|
+
if [[ "$url" == *".jpg"* ]] || [[ "$url" == *".png"* ]]; then
|
36
|
+
name="${url//[^[:alnum:]. ]}"
|
37
|
+
curl -sSo "$name" "$url"
|
38
|
+
actual=$(md5sum "$name" | awk '{print $1}')
|
39
|
+
else
|
40
|
+
actual=$(curl -sS "$url")
|
41
|
+
fi
|
42
|
+
|
43
|
+
if [ "$actual" == "$expected" ]; then
|
44
|
+
echo "Passed."
|
45
|
+
else
|
46
|
+
echo "Failed: $url returned $actual | expected: $expected"
|
47
|
+
fi
|
48
|
+
|
49
|
+
rm "$name"
|
50
|
+
}
|
51
|
+
|
52
|
+
for expected in "${!test_cases[@]}"; do
|
53
|
+
url="${test_cases[$expected]}"
|
54
|
+
run_test "$expected" "$url"
|
55
|
+
done
|
package/examples/cache.ts
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
import { router, json } from "..";
|
2
|
-
|
3
|
-
const r = router();
|
4
|
-
|
5
|
-
|
6
|
-
const cacher = () => {
|
7
|
-
const cache = new Map();
|
8
|
-
return {
|
9
|
-
set: (key: string, value: string) => {
|
10
|
-
cache.set(key, value);
|
11
|
-
},
|
12
|
-
get: (key: string) => cache.get(key)!,
|
13
|
-
}
|
14
|
-
}
|
15
|
-
|
16
|
-
const cache = cacher();
|
17
|
-
|
18
|
-
const rand = (max: number) => Math.floor(Math.random() * max);
|
19
|
-
|
20
|
-
r.add('/set', 'POST', req => {
|
21
|
-
const url = new URL(req.request.url);
|
22
|
-
const query = url.searchParams;
|
23
|
-
|
24
|
-
const name = query.get('name')!;
|
25
|
-
|
26
|
-
cache.set(name, `${rand(1000)}`)
|
27
|
-
|
28
|
-
return new Response('thank you for joining\n');
|
29
|
-
});
|
30
|
-
|
31
|
-
r.add('/get/:key', 'GET', req => {
|
32
|
-
const name = req.params.get('key')
|
33
|
-
const result = cache.get(name ?? '');
|
34
|
-
|
35
|
-
if (!result) return new Response('not found')
|
36
|
-
|
37
|
-
return json(`Welcome user ID: ${result}`)
|
38
|
-
|
39
|
-
});
|
40
|
-
|
41
|
-
r.serve();
|
package/examples/file.ts
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
import { router, html } from '..';
|
2
|
-
import path from 'path';
|
3
|
-
|
4
|
-
const r = router();
|
5
|
-
|
6
|
-
r.add('/page/:name', 'GET', async req => {
|
7
|
-
const pageName = req.params.get('name')!;
|
8
|
-
const fullpath = path.join('.', 'examples', 'pages', pageName+'.html');
|
9
|
-
return html(fullpath);
|
10
|
-
});
|
11
|
-
|
12
|
-
r.serve();
|
package/examples/html.ts
DELETED
package/examples/params.ts
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
import { router, json } from '..';
|
2
|
-
|
3
|
-
const r = router();
|
4
|
-
|
5
|
-
const pets = {
|
6
|
-
dogs: ['Joey', 'Benny', 'Max'],
|
7
|
-
cats: ['Charles', 'Arya', 'Binx'],
|
8
|
-
}
|
9
|
-
|
10
|
-
const foods = {
|
11
|
-
apple: '🍎', banana: '🍌', strawberry: '🍓', pear: '🍐',
|
12
|
-
}
|
13
|
-
|
14
|
-
r.add('/pets/:type', 'GET', req => {
|
15
|
-
const petType = req.params.get('type') as keyof typeof pets;
|
16
|
-
return json(pets[petType] ?? 'not found');
|
17
|
-
});
|
18
|
-
r.add('/grocery/:food', 'GET', req => {
|
19
|
-
const food = req.params.get('food') as keyof typeof foods
|
20
|
-
return json(foods[food] ?? 'not found')
|
21
|
-
});
|
22
|
-
|
23
|
-
r.serve();
|
24
|
-
|
package/examples/post.ts
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
import { router, json, } from '..';
|
2
|
-
|
3
|
-
const r = router(3030);
|
4
|
-
|
5
|
-
const userStore = new Map();
|
6
|
-
const rando = (max: number) => `${Math.floor(Math.random() * max)}`
|
7
|
-
|
8
|
-
r.add('/new', 'POST', req => {
|
9
|
-
const url = new URL(req.request.url);
|
10
|
-
const query = url.searchParams;
|
11
|
-
|
12
|
-
const name = query.get('name');
|
13
|
-
const email = query.get('email');
|
14
|
-
|
15
|
-
if (typeof name === 'undefined' || typeof email === 'undefined')
|
16
|
-
return new Response('invalid query parameters');
|
17
|
-
|
18
|
-
|
19
|
-
const id = rando(2_000_000);
|
20
|
-
userStore.set(id, {name: name, email: email});
|
21
|
-
|
22
|
-
const message = `Thank you, ${name} for registering your email: ${email}.\nYour ID is ${id}`
|
23
|
-
return new Response(message);
|
24
|
-
});
|
25
|
-
|
26
|
-
r.add('/user/:id', 'GET', req => {
|
27
|
-
const id = req.params.get('id');
|
28
|
-
if (typeof id === 'undefined')
|
29
|
-
return new Response('invalid id');
|
30
|
-
|
31
|
-
const user = userStore.get(id);
|
32
|
-
if (typeof user === 'undefined')
|
33
|
-
return new Response('not found');
|
34
|
-
|
35
|
-
return json(user);
|
36
|
-
});
|
37
|
-
|
38
|
-
r.serve();
|
package/examples/space.ts
DELETED
package/tests/router.test.sh
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
#!/bin/bash
|
2
|
-
|
3
|
-
PARAMS_TEST=(
|
4
|
-
"http://localhost:3000/pets/dogs"
|
5
|
-
"http://localhost:3000/grocery/strawberry"
|
6
|
-
)
|
7
|
-
|
8
|
-
echo "Starting test server"
|
9
|
-
bun run ./examples/params.ts &
|
10
|
-
|
11
|
-
SERVER_PID=$!
|
12
|
-
|
13
|
-
function stop_server {
|
14
|
-
echo "Stopping the server $SERVER_PID"
|
15
|
-
kill $SERVER_PID
|
16
|
-
}
|
17
|
-
|
18
|
-
trap stop_server EXIT
|
19
|
-
|
20
|
-
sleep .1
|
21
|
-
|
22
|
-
expected='["Joey","Benny","Max"]'
|
23
|
-
response=$(curl -sS "${PARAMS_TEST[0]}")
|
24
|
-
response_trimmed=$(echo "$response" | xargs -0)
|
25
|
-
expected_trimmed=$(echo "$expected" | xargs -0)
|
26
|
-
|
27
|
-
if [ "$response_trimmed" = "$expected_trimmed" ]; then
|
28
|
-
echo "Passed."
|
29
|
-
else
|
30
|
-
echo "$response_trimmed"
|
31
|
-
echo "$expected_trimmed"
|
32
|
-
echo "Failed."
|
33
|
-
fi
|
34
|
-
|
35
|
-
expected="🍓"
|
36
|
-
response=$(curl -s "${PARAMS_TEST[1]}")
|
37
|
-
if [ "$response" = "$expected" ]; then
|
38
|
-
echo "Failed: ${PARAMS_TEST[1]}"
|
39
|
-
else
|
40
|
-
echo "Passed."
|
41
|
-
fi
|
42
|
-
|