bun-router 0.2.8 → 0.3.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/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 +70 -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,94 @@ 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
|
+
console.log((pattern[0] != '/'))
|
118
|
+
if (pattern[0] !== '/') pattern = '/' + pattern;
|
119
|
+
|
120
|
+
let patternPath = pattern + base;
|
121
|
+
|
122
|
+
if (base === 'index') patternPath = pattern;
|
123
|
+
|
124
|
+
console.log(patternPath);
|
125
|
+
|
126
|
+
const route: Route = {
|
127
|
+
pattern: patternPath,
|
128
|
+
method: 'GET',
|
129
|
+
callback: async () => await file(pure),
|
130
|
+
};
|
131
|
+
routes.push(route);
|
132
|
+
});
|
133
|
+
console.log(routes.length);
|
134
|
+
|
135
|
+
},
|
89
136
|
serve: () => {
|
90
|
-
console.log(`[bun-router]: Listening on port -> :${
|
137
|
+
console.log(`[bun-router]: Listening on port -> :${port ?? 3000}`)
|
91
138
|
Bun.serve({
|
92
139
|
port: port ?? 3000,
|
93
140
|
...options,
|
94
141
|
fetch(req) {
|
95
|
-
const url = new URL(req.url);
|
96
142
|
for (const route of routes) {
|
97
|
-
const
|
143
|
+
const ctx: Context = {
|
98
144
|
request: req,
|
99
145
|
params: new Map(),
|
146
|
+
fs: new Map(),
|
100
147
|
};
|
101
148
|
|
102
|
-
|
103
|
-
|
104
|
-
if (match(route, httpRequest))
|
105
|
-
return route.callback(httpRequest);
|
149
|
+
const extractor = extract(route, ctx);
|
106
150
|
|
151
|
+
extractor?.params();
|
107
152
|
|
108
|
-
if (match(route,
|
153
|
+
if (match(route, ctx))
|
154
|
+
return route.callback(ctx);
|
109
155
|
}
|
110
156
|
return new Response('not found');
|
111
157
|
}
|
@@ -114,4 +160,4 @@ const router: Router = (port?: number | string, options?: Options) => {
|
|
114
160
|
}
|
115
161
|
}
|
116
162
|
|
117
|
-
export { router, json, file,
|
163
|
+
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
|
-
|