bun-router 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +98 -0
- package/bun.lockb +0 -0
- package/examples/cache.ts +41 -0
- package/examples/file.ts +12 -0
- package/examples/html.ts +9 -0
- package/examples/pages/foobar.html +37 -0
- package/examples/pages/index.html +79 -0
- package/examples/params.ts +24 -0
- package/examples/post.ts +38 -0
- package/index.d.ts +1 -0
- package/index.ts +1 -0
- package/lib/router/router.d.ts +28 -0
- package/lib/router/router.ts +101 -0
- package/package.json +12 -0
- package/tests/router.test.sh +42 -0
- package/tests/router.test.ts +47 -0
- package/tsconfig.json +21 -0
package/README.md
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# Bun Router
|
2
|
+
|
3
|
+
I needed a router for `Bun`, so I made one. It's simple, naive, and hardly anything is abstracted.
|
4
|
+
|
5
|
+
### Usage
|
6
|
+
Import the `router`.
|
7
|
+
```typescript
|
8
|
+
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
|
+
|
32
|
+
const r = router();
|
33
|
+
|
34
|
+
const pets = {
|
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
|
+
});
|
51
|
+
|
52
|
+
r.serve();
|
53
|
+
```
|
54
|
+
|
55
|
+
```typescript
|
56
|
+
import { router, json } from "..";
|
57
|
+
|
58
|
+
const r = router();
|
59
|
+
|
60
|
+
|
61
|
+
const cacher = () => {
|
62
|
+
const cache = new Map();
|
63
|
+
return {
|
64
|
+
set: (key: string, value: string) => {
|
65
|
+
cache.set(key, value);
|
66
|
+
},
|
67
|
+
get: (key: string) => cache.get(key)!,
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
const cache = cacher();
|
72
|
+
|
73
|
+
const rand = (max: number) => Math.floor(Math.random() * max);
|
74
|
+
|
75
|
+
r.add('/set', 'POST', req => {
|
76
|
+
const url = new URL(req.request.url);
|
77
|
+
const query = url.searchParams;
|
78
|
+
|
79
|
+
const name = query.get('name')!;
|
80
|
+
|
81
|
+
cache.set(name, `${rand(1000)}`)
|
82
|
+
|
83
|
+
return new Response('thank you for joining\n');
|
84
|
+
});
|
85
|
+
|
86
|
+
r.add('/get/:key', 'GET', req => {
|
87
|
+
const name = req.params.get('key')
|
88
|
+
const result = cache.get(name ?? '');
|
89
|
+
|
90
|
+
if (!result) return new Response('not found')
|
91
|
+
|
92
|
+
return json(`Welcome user ID: ${result}`)
|
93
|
+
|
94
|
+
});
|
95
|
+
|
96
|
+
r.serve();
|
97
|
+
```
|
98
|
+
|
package/bun.lockb
ADDED
Binary file
|
@@ -0,0 +1,41 @@
|
|
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
ADDED
@@ -0,0 +1,12 @@
|
|
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
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=>, initial-scale=1.0">
|
6
|
+
<title>Document</title>
|
7
|
+
</head>
|
8
|
+
<style>
|
9
|
+
* {
|
10
|
+
margin: 0;
|
11
|
+
padding: 0;
|
12
|
+
box-sizing: border-box;
|
13
|
+
font-family: sans-serif;
|
14
|
+
}
|
15
|
+
|
16
|
+
body {
|
17
|
+
min-width: 100vw;
|
18
|
+
min-height: 100vh;
|
19
|
+
background: white;
|
20
|
+
}
|
21
|
+
|
22
|
+
main {
|
23
|
+
width: 75%;
|
24
|
+
line-height: 2;
|
25
|
+
padding: 0.5em;
|
26
|
+
margin: 0.125em;
|
27
|
+
box-shadow: 2px 2px 5px rgba(0,0,0,0.7);
|
28
|
+
}
|
29
|
+
</style>
|
30
|
+
<body>
|
31
|
+
<main>
|
32
|
+
<h1>Foobar</h1>
|
33
|
+
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Dicta a atque perspiciatis eos ipsa facere ut laborum commodi, quibusdam doloribus quia numquam iusto fugit. Mollitia quam at voluptas ipsam blanditiis.</p>
|
34
|
+
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolor unde tempora illo vitae dignissimos, sed blanditiis velit ipsa aut, veritatis voluptates! Exercitationem excepturi dolorum nesciunt numquam assumenda similique aliquam dicta veritatis magnam tenetur dolor, harum quos? Accusamus autem in culpa velit earum. Error repudiandae dolore pariatur reprehenderit molestiae quam suscipit.</p>
|
35
|
+
</main>
|
36
|
+
</body>
|
37
|
+
</html>
|
@@ -0,0 +1,79 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<meta charset=UTF-8>
|
3
|
+
<meta name=viewport content=width=device-width, initial-scale=1.0>
|
4
|
+
<title>Bun Router</title>
|
5
|
+
|
6
|
+
<style>
|
7
|
+
* {
|
8
|
+
margin: 0;
|
9
|
+
padding: 0;
|
10
|
+
box-sizing: border-box;
|
11
|
+
font-family: sans-serif;
|
12
|
+
}
|
13
|
+
|
14
|
+
|
15
|
+
main div {
|
16
|
+
width: 85%;
|
17
|
+
border-radius: 8px;
|
18
|
+
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.2);
|
19
|
+
padding: 4em;
|
20
|
+
background: #18191a;
|
21
|
+
color: white;
|
22
|
+
}
|
23
|
+
|
24
|
+
main div a {
|
25
|
+
background: none;
|
26
|
+
color: dodgerblue;
|
27
|
+
text-decoration: none;
|
28
|
+
font-weight: 200;
|
29
|
+
}
|
30
|
+
|
31
|
+
|
32
|
+
a:not(div a) {
|
33
|
+
text-decoration: none;
|
34
|
+
min-width: 128px;
|
35
|
+
text-align: center;
|
36
|
+
color: dodgerblue;
|
37
|
+
display: block;
|
38
|
+
padding: 0.85em;
|
39
|
+
margin: 0.25em;
|
40
|
+
background: #18191a;
|
41
|
+
border-radius: 5px;
|
42
|
+
}
|
43
|
+
|
44
|
+
main a:hover {
|
45
|
+
color: skyblue;
|
46
|
+
transition: color 100ms ease;
|
47
|
+
}
|
48
|
+
|
49
|
+
main {
|
50
|
+
display: flex;
|
51
|
+
background: #0d0d0d;
|
52
|
+
flex-flow: column;
|
53
|
+
justify-content: center;
|
54
|
+
align-items: center;
|
55
|
+
width: 100vw;
|
56
|
+
height: 100vh;
|
57
|
+
padding: 1.25em;
|
58
|
+
}
|
59
|
+
|
60
|
+
main section {
|
61
|
+
display: flex;
|
62
|
+
}
|
63
|
+
|
64
|
+
|
65
|
+
</style>
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
<main>
|
70
|
+
<div>
|
71
|
+
<h1>Bun Router</h1>
|
72
|
+
<p>A simple router, for <a href=https://bun.sh>Bun.sh.</a></p>
|
73
|
+
</div>
|
74
|
+
<section>
|
75
|
+
<a href=https://github.com/aboxofsox/bun-router/docs>Docs</a>
|
76
|
+
<a href=https://github.com/aboxofsox/bun-router/examples>Examples</a>
|
77
|
+
<a href=https://google.com>Why</a>
|
78
|
+
</section>
|
79
|
+
</main>
|
@@ -0,0 +1,24 @@
|
|
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
ADDED
@@ -0,0 +1,38 @@
|
|
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/index.d.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export type HttpHandler = (r: Request) => Response
|
package/index.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export * from './lib/router/router';
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { TLSOptions, TLSWebSocketServeOptions, WebSocketServeOptions, ServeOptions, TLSServeOptions } from 'bun';
|
2
|
+
|
3
|
+
|
4
|
+
type HttpRequest = {
|
5
|
+
request: Request,
|
6
|
+
params: Map<string, string>,
|
7
|
+
}
|
8
|
+
|
9
|
+
type Route = {
|
10
|
+
pattern: string,
|
11
|
+
method: string,
|
12
|
+
callback: (req: HttpRequest) => Response | Promise<Response>
|
13
|
+
}
|
14
|
+
|
15
|
+
type Options = ServeOptions
|
16
|
+
| TLSServeOptions
|
17
|
+
| WebSocketServeOptions
|
18
|
+
| TLSWebSocketServeOptions
|
19
|
+
| undefined
|
20
|
+
|
21
|
+
|
22
|
+
type Router = (port?: number | string, options?: Options) => {
|
23
|
+
add: (pattern: string, method: string, callback: (req: HttpRequest) => Response | Promise<Response>) => void,
|
24
|
+
serve: () => void,
|
25
|
+
}
|
26
|
+
|
27
|
+
|
28
|
+
export { HttpRequest, Route, Router, Options }
|
@@ -0,0 +1,101 @@
|
|
1
|
+
import { Route, Router, HttpRequest, Options } from './router.d';
|
2
|
+
|
3
|
+
const notFound = async (): Promise<Response> => {
|
4
|
+
const response = new Response('not found', {
|
5
|
+
status: 404,
|
6
|
+
statusText: 'not found',
|
7
|
+
headers: { 'Content-Type': 'text/html' },
|
8
|
+
});
|
9
|
+
|
10
|
+
return new Promise((resolve) => {
|
11
|
+
resolve(response);
|
12
|
+
});
|
13
|
+
}
|
14
|
+
|
15
|
+
const html = async (filepath: string): Promise<Response> => {
|
16
|
+
const file = Bun.file(filepath);
|
17
|
+
const exists = await file.exists();
|
18
|
+
|
19
|
+
if (!exists)
|
20
|
+
return notFound();
|
21
|
+
|
22
|
+
const content = await file.text();
|
23
|
+
if (content === '')
|
24
|
+
return notFound();
|
25
|
+
|
26
|
+
const response = new Response(content, {
|
27
|
+
status: 200,
|
28
|
+
statusText: 'ok',
|
29
|
+
headers: { 'Content-Type': 'text/html' },
|
30
|
+
});
|
31
|
+
|
32
|
+
return new Promise<Response>((resolve) => {
|
33
|
+
resolve(response);
|
34
|
+
});
|
35
|
+
}
|
36
|
+
|
37
|
+
const json = (data: any): Response => {
|
38
|
+
const jsonString = JSON.stringify(data);
|
39
|
+
|
40
|
+
const res = new Response(jsonString);
|
41
|
+
res.headers.set('Content-Type', 'application/json');
|
42
|
+
|
43
|
+
return res
|
44
|
+
}
|
45
|
+
|
46
|
+
const extractParams = (route: Route, req: HttpRequest) => {
|
47
|
+
const url = new URL(req.request.url);
|
48
|
+
const pathSegments = route.pattern.split('/');
|
49
|
+
const urlSegments = url.pathname.split('/');
|
50
|
+
|
51
|
+
if (pathSegments.length !== urlSegments.length) return
|
52
|
+
|
53
|
+
for (let i = 0; i < pathSegments.length; i++) {
|
54
|
+
if ((pathSegments[i][0] === ':') && (pathSegments[i - 1] === urlSegments[i - 1])) {
|
55
|
+
const k = pathSegments[i].replace(':', '');
|
56
|
+
const v = urlSegments[i];
|
57
|
+
req.params.set(k, v);
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
const match = (route: Route, req: HttpRequest): boolean => {
|
63
|
+
return req.params.size !== 0 && route.method === req.request.method
|
64
|
+
}
|
65
|
+
|
66
|
+
const router: Router = (port?: number | string, options?: Options) => {
|
67
|
+
const routes: Array<Route> = new Array();
|
68
|
+
|
69
|
+
return {
|
70
|
+
add: (pattern: string, method: string, callback: (req: HttpRequest) => Response | Promise<Response>) => {
|
71
|
+
routes.push({
|
72
|
+
pattern: pattern,
|
73
|
+
method: method,
|
74
|
+
callback: callback,
|
75
|
+
})
|
76
|
+
},
|
77
|
+
serve: () => {
|
78
|
+
console.log(`[bun-router]: Listening on port -> :${ port ?? 3000 }`)
|
79
|
+
Bun.serve({
|
80
|
+
port: port ?? 3000,
|
81
|
+
...options,
|
82
|
+
fetch(req) {
|
83
|
+
const url = new URL(req.url);
|
84
|
+
for (const route of routes) {
|
85
|
+
const httpRequest: HttpRequest = {
|
86
|
+
request: req,
|
87
|
+
params: new Map(),
|
88
|
+
};
|
89
|
+
|
90
|
+
extractParams(route, httpRequest);
|
91
|
+
|
92
|
+
if (match(route, httpRequest)) return route.callback(httpRequest);
|
93
|
+
}
|
94
|
+
return new Response('not found');
|
95
|
+
}
|
96
|
+
});
|
97
|
+
},
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
export { router, json, html, extractParams }
|
package/package.json
ADDED
@@ -0,0 +1,42 @@
|
|
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
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
2
|
+
import { html, json, extractParams } from '..';
|
3
|
+
import { HttpRequest, Route } from '../lib/router/router.d';
|
4
|
+
|
5
|
+
describe('Helpers', async () => {
|
6
|
+
test('html', async () => {
|
7
|
+
const fp = './examples/pages/index.html';
|
8
|
+
const res = await html(fp);
|
9
|
+
const contentType = res.headers.get('Content-Type');
|
10
|
+
|
11
|
+
expect(contentType).toBe('text/html');
|
12
|
+
expect(res.status).toBe(200);
|
13
|
+
});
|
14
|
+
|
15
|
+
test('json', async () => {
|
16
|
+
const test = { message: 'ok' }
|
17
|
+
const res = await json(test);
|
18
|
+
const jsn = await res.json();
|
19
|
+
|
20
|
+
expect(jsn).toStrictEqual({ message: 'ok' })
|
21
|
+
|
22
|
+
});
|
23
|
+
|
24
|
+
test('extract params 1', () => {
|
25
|
+
const route: Route = {pattern: '/:name', method: 'GET', callback: (req) => new Response('ok')};
|
26
|
+
const httpRequest: HttpRequest = {
|
27
|
+
request: new Request('http://localhost:3000/foo'),
|
28
|
+
params: new Map(),
|
29
|
+
}
|
30
|
+
|
31
|
+
extractParams(route, httpRequest);
|
32
|
+
const name = httpRequest.params.get('name');
|
33
|
+
expect(name).toBe('foo');
|
34
|
+
});
|
35
|
+
|
36
|
+
test('extract params 2', () =>{
|
37
|
+
const route: Route = {pattern: '/foo/:name', method: 'GET', callback: (req) => new Response('ok')};
|
38
|
+
const httpRequest: HttpRequest = {
|
39
|
+
request: new Request('http://localhost:3000/foo/bar'),
|
40
|
+
params: new Map(),
|
41
|
+
}
|
42
|
+
|
43
|
+
extractParams(route, httpRequest);
|
44
|
+
const name = httpRequest.params.get('name');
|
45
|
+
expect(name).toBe('bar');
|
46
|
+
});
|
47
|
+
});
|
package/tsconfig.json
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
{
|
2
|
+
"compilerOptions": {
|
3
|
+
"lib": ["ESNext"],
|
4
|
+
"module": "esnext",
|
5
|
+
"target": "esnext",
|
6
|
+
"moduleResolution": "bundler",
|
7
|
+
"moduleDetection": "force",
|
8
|
+
"allowImportingTsExtensions": true,
|
9
|
+
"strict": true,
|
10
|
+
"downlevelIteration": true,
|
11
|
+
"skipLibCheck": true,
|
12
|
+
"jsx": "preserve",
|
13
|
+
"allowSyntheticDefaultImports": true,
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
15
|
+
"allowJs": true,
|
16
|
+
"noEmit": true,
|
17
|
+
"types": [
|
18
|
+
"bun-types" // add Bun global
|
19
|
+
]
|
20
|
+
}
|
21
|
+
}
|