bun-router 0.7.3 → 0.7.4-experimental.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/.eslintrc.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "env": {
3
+ "browser": true,
4
+ "es2021": true
5
+ },
6
+ "extends": [
7
+ "eslint:recommended",
8
+ "plugin:@typescript-eslint/recommended",
9
+ "plugin:react/recommended"
10
+ ],
11
+ "parser": "@typescript-eslint/parser",
12
+ "parserOptions": {
13
+ "ecmaVersion": "latest",
14
+ "sourceType": "module"
15
+ },
16
+ "plugins": [
17
+ "@typescript-eslint",
18
+ "react"
19
+ ],
20
+ "rules": {
21
+ "indent": [
22
+ "warn",
23
+ "tab"
24
+ ],
25
+ "quotes": [
26
+ "error",
27
+ "single"
28
+ ],
29
+ "semi": [
30
+ "error",
31
+ "always"
32
+ ]
33
+ }
34
+ }
package/README.md CHANGED
@@ -53,4 +53,27 @@ router.post('/register', ctx => {
53
53
 
54
54
  ```
55
55
 
56
+ ##### JSX
57
+ ```tsx
58
+ // ./pages/home.tsx
59
+ export default const Home = (title: string) => {
60
+ return (
61
+ <main>
62
+ <h1>{ title }</h1>
63
+ </main>
64
+ );
65
+ };
66
+ ```
67
+
68
+ ```ts
69
+ // ./index.ts
70
+ import { Router } from 'bun-router';
71
+ import Home from './pages/home';
72
+
73
+ const router = Router();
74
+
75
+ router.get('/', ctx => ctx.render(Home('Hello World')))
76
+
77
+ router.serve();
78
+ ```
56
79
 
package/bun.lockb CHANGED
Binary file
package/lib/fs/fsys.ts CHANGED
@@ -21,5 +21,9 @@ async function readDir(dirpath: string, handler: (filepath: string, entry: BunFi
21
21
  }
22
22
  }
23
23
 
24
+ function ext(path: string): string {
25
+ return path.split('.').pop() || '';
26
+ }
27
+
24
28
 
25
- export { readDir }
29
+ export { readDir, ext }
package/lib/http/http.ts CHANGED
@@ -1,77 +1,78 @@
1
- import { httpStatusCodes } from "./status";
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { httpStatusCodes } from './status';
2
3
 
3
4
  const http = {
4
- ok: async (msg?: string): Promise<Response> => {
5
- return Promise.resolve(new Response(msg ?? httpStatusCodes[200], {
6
- status: 200,
7
- statusText: httpStatusCodes[200],
8
- }));
9
- },
10
- json: async (statusCode: number, data: any): Promise<Response> => {
11
- const jsonString = JSON.stringify(data);
12
- return Promise.resolve(new Response(jsonString, {
13
- status: statusCode,
14
- statusText: httpStatusCodes[statusCode],
15
- headers: {'Content-Type': 'application/json'},
16
- }));
17
- },
18
- html: async (statusCode: number, content: string): Promise<Response> => {
19
- return Promise.resolve(new Response(content, {
20
- status: statusCode,
21
- statusText: httpStatusCodes[statusCode],
22
- headers: {'Content-Type': 'text/html; charset=utf-8'}
23
- }));
24
- },
25
- file: async (statusCode: number, fp: string): Promise<Response> => {
26
- const file = Bun.file(fp);
27
- const exists = await file.exists();
5
+ ok: async (msg?: string): Promise<Response> => {
6
+ return Promise.resolve(new Response(msg ?? httpStatusCodes[200], {
7
+ status: 200,
8
+ statusText: httpStatusCodes[200],
9
+ }));
10
+ },
11
+ json: async (statusCode: number, data: any): Promise<Response> => {
12
+ const jsonString = JSON.stringify(data);
13
+ return Promise.resolve(new Response(jsonString, {
14
+ status: statusCode,
15
+ statusText: httpStatusCodes[statusCode],
16
+ headers: {'Content-Type': 'application/json'},
17
+ }));
18
+ },
19
+ html: async (statusCode: number, content: string): Promise<Response> => {
20
+ return Promise.resolve(new Response(content, {
21
+ status: statusCode,
22
+ statusText: httpStatusCodes[statusCode],
23
+ headers: {'Content-Type': 'text/html; charset=utf-8'}
24
+ }));
25
+ },
26
+ file: async (statusCode: number, fp: string): Promise<Response> => {
27
+ const file = Bun.file(fp);
28
+ const exists = await file.exists();
28
29
 
29
- if (!exists) return http.notFound(`File not found: ${fp}`);
30
+ if (!exists) return http.notFound(`File not found: ${fp}`);
30
31
 
31
- const content = await file.arrayBuffer();
32
- if (!content) return http.noContent();
32
+ const content = await file.arrayBuffer();
33
+ if (!content) return http.noContent();
33
34
 
34
- let contentType = 'text/html; charset=utf-9';
35
+ let contentType = 'text/html; charset=utf-9';
35
36
 
36
- if (file.type.includes('image'))
37
- contentType = file.type + '; charset=utf-8';
37
+ if (file.type.includes('image'))
38
+ contentType = file.type + '; charset=utf-8';
38
39
 
39
- return Promise.resolve(new Response(content, {
40
- status: statusCode,
41
- statusText: httpStatusCodes[statusCode],
42
- headers: { 'Content-Type': contentType}
43
- }));
44
- },
45
- noContent: async (): Promise<Response> => Promise.resolve(new Response('no content', {
46
- status: 204,
47
- statusText: 'no content',
48
- })),
49
- notFound: async(msg?: string): Promise<Response> => {
50
- const response = new Response(msg ?? 'not found', {
51
- status: 404,
52
- statusText: httpStatusCodes[404],
53
- headers: {'Content-Type': 'text/html'},
54
- });
40
+ return Promise.resolve(new Response(content, {
41
+ status: statusCode,
42
+ statusText: httpStatusCodes[statusCode],
43
+ headers: { 'Content-Type': contentType}
44
+ }));
45
+ },
46
+ noContent: async (): Promise<Response> => Promise.resolve(new Response('no content', {
47
+ status: 204,
48
+ statusText: 'no content',
49
+ })),
50
+ notFound: async(msg?: string): Promise<Response> => {
51
+ const response = new Response(msg ?? 'not found', {
52
+ status: 404,
53
+ statusText: httpStatusCodes[404],
54
+ headers: {'Content-Type': 'text/html'},
55
+ });
55
56
 
56
- return Promise.resolve(response);
57
- },
58
- methodNotAllowed: async (msg?: string): Promise<Response> => {
59
- const response = new Response(msg ?? 'method not allowed', {
60
- status: 405,
61
- statusText: httpStatusCodes[405],
62
- headers: {'Content-Type': 'text/html'},
63
- });
57
+ return Promise.resolve(response);
58
+ },
59
+ methodNotAllowed: async (msg?: string): Promise<Response> => {
60
+ const response = new Response(msg ?? 'method not allowed', {
61
+ status: 405,
62
+ statusText: httpStatusCodes[405],
63
+ headers: {'Content-Type': 'text/html'},
64
+ });
64
65
 
65
- return Promise.resolve(response);
66
- },
67
- message: async (status: number, msg?: string): Promise<Response> => {
68
- const response = new Response(msg ?? '?', {
69
- status: status,
70
- statusText: httpStatusCodes[status],
71
- headers: {'Content-Type': 'text/html; charset-utf-8'},
72
- });
73
- return Promise.resolve(response)
74
- },
75
- }
66
+ return Promise.resolve(response);
67
+ },
68
+ message: async (status: number, msg?: string): Promise<Response> => {
69
+ const response = new Response(msg ?? '?', {
70
+ status: status,
71
+ statusText: httpStatusCodes[status],
72
+ headers: {'Content-Type': 'text/html; charset-utf-8'},
73
+ });
74
+ return Promise.resolve(response);
75
+ },
76
+ };
76
77
 
77
- export { http }
78
+ export { http };
@@ -8,87 +8,87 @@ _ _
8
8
  | . | | | | | _| . | | | _| -_| _|
9
9
  |___|___|_|_| |_| |___|___|_| |___|_|
10
10
 
11
- `
12
- const VERSION = '0.7.1';
11
+ `;
12
+ const VERSION = '0.7.4-experimental';
13
13
  const Logger = (): BunLogger => {
14
- return {
15
- info: async (statusCode: number, routePath: string, method: string, message?: string) => {
16
- const { stamp } = timestamp((new Date(Date.now())));
17
- const source = color('green', 'bgBlack', `[bun-router ${stamp}]`);
18
- const rp = color('white', 'bgBlack', routePath);
14
+ return {
15
+ info: async (statusCode: number, routePath: string, method: string, message?: string) => {
16
+ const { stamp } = timestamp((new Date(Date.now())));
17
+ const source = color('green', 'bgBlack', `[bun-router ${stamp}]`);
18
+ const rp = color('white', 'bgBlack', routePath);
19
19
 
20
- message = `${source}: ${setColor(statusCode)}: ${rp} ${(method === 'GET') ? '->' : '<-'} ${method}${ message ?? ''}\n`
20
+ message = `${source}: ${setColor(statusCode)}: ${rp} ${(method === 'GET') ? ' ->' : ' <-'} ${method} ${ message ?? ''}\n`;
21
21
 
22
- await Bun.write(Bun.stdout, message);
22
+ await Bun.write(Bun.stdout, message);
23
23
 
24
- },
25
- error: async (statusCode: number, routePath: string, method: string, error: Error) => {
26
- const { stamp } = timestamp((new Date(Date.now())));
27
- const source = color('black', 'bgRed', `[error ${stamp}]`);
28
- const rp = color('white', 'bgBlack', routePath);
24
+ },
25
+ error: async (statusCode: number, routePath: string, method: string, error: Error) => {
26
+ const { stamp } = timestamp((new Date(Date.now())));
27
+ const source = color('black', 'bgRed', `[error ${stamp}]`);
28
+ const rp = color('white', 'bgBlack', routePath);
29
29
 
30
- const message = `${source}: ${setColor(statusCode)}: ${rp} ${(method === 'GET') ? '->' : '<-'} ${error.message}\n`
30
+ const message = `${source}: ${setColor(statusCode)}: ${rp} ${(method === 'GET') ? ' -> ' : ' <-'} ${error.message}\n`;
31
31
 
32
- await Bun.write(Bun.stdout, message);
33
- },
34
- warn: async (message: string) => {
35
- const { stamp } = timestamp((new Date(Date.now())));
36
- const source = color('black', 'bgYellow', `[warning ${stamp}]`);
37
- const messageColor = color('yellow', 'bgBlack', message);
32
+ await Bun.write(Bun.stdout, message);
33
+ },
34
+ warn: async (message: string) => {
35
+ const { stamp } = timestamp((new Date(Date.now())));
36
+ const source = color('black', 'bgYellow', `[warning ${stamp}]`);
37
+ const messageColor = color('yellow', 'bgBlack', message);
38
38
 
39
- message = `${source} : ${messageColor}\n`;
39
+ message = `${source} : ${messageColor}\n`;
40
40
 
41
- await Bun.write(Bun.stdout, message);
42
- },
43
- message: async (message: string) => {
44
- const { stamp } = timestamp((new Date(Date.now())));
45
- const source = color('black', 'bgCyan', `[message ${stamp}]`);
46
- const messageColor = color('yellow', 'bgBlack', message);
41
+ await Bun.write(Bun.stdout, message);
42
+ },
43
+ message: async (message: string) => {
44
+ const { stamp } = timestamp((new Date(Date.now())));
45
+ const source = color('black', 'bgCyan', `[message ${stamp}]`);
46
+ const messageColor = color('yellow', 'bgBlack', message);
47
47
 
48
- message = `${source}: ${messageColor}\n`;
48
+ message = `${source}: ${messageColor}\n`;
49
49
 
50
- await Bun.write(Bun.stdout, message);
51
- },
52
- }
53
- }
50
+ await Bun.write(Bun.stdout, message);
51
+ },
52
+ };
53
+ };
54
54
 
55
55
  function timestamp(date: Date) {
56
- const month = pad(date.getMonth());
57
- const day = pad(date.getDate());
58
- const hour = pad(date.getHours());
59
- const minute = pad(date.getMinutes());
60
- const seconds = pad(date.getSeconds());
61
- const stamp = `${hour}:${minute}:${seconds}`;
62
-
63
- return {month, day, hour, minute, stamp};
56
+ const month = pad(date.getMonth());
57
+ const day = pad(date.getDate());
58
+ const hour = pad(date.getHours());
59
+ const minute = pad(date.getMinutes());
60
+ const seconds = pad(date.getSeconds());
61
+ const stamp = `${hour}:${minute}:${seconds}`;
62
+
63
+ return {month, day, hour, minute, stamp};
64
64
  }
65
65
 
66
66
  function setColor(n: number, text?: string){
67
- const s = ` [${String(n)}${text ?? ''}] `;
67
+ const s = ` [${String(n)}${text ?? ''}] `;
68
68
 
69
- if (n < 100) return color('black', 'bgYellow', s);
70
- else if (n >= 100 && n < 200) return color('black', 'bgCyan', s);
71
- else if (n >= 200 && n < 300) return color('black', 'bgGreen', s);
72
- else if (n >= 300 && n < 400) return color('black', 'bgRed', s);
73
- else if (n >= 400 && n < 500) return color('black', 'bgRed', s);
74
- else if (n >= 500) return color('white', 'bgRed', s);
69
+ if (n < 100) return color('black', 'bgYellow', s);
70
+ else if (n >= 100 && n < 200) return color('black', 'bgCyan', s);
71
+ else if (n >= 200 && n < 300) return color('black', 'bgGreen', s);
72
+ else if (n >= 300 && n < 400) return color('black', 'bgRed', s);
73
+ else if (n >= 400 && n < 500) return color('black', 'bgRed', s);
74
+ else if (n >= 500) return color('white', 'bgRed', s);
75
75
 
76
- return color('white', 'bgBlack', `[${s}]`).trim();
76
+ return color('white', 'bgBlack', `[${s}]`).trim();
77
77
  }
78
78
 
79
79
  function startMessage(port: number | string) {
80
- const { stamp } = timestamp((new Date(Date.now())));
81
- const source = color('green', 'bgBlack', `[bun-router ${stamp}]`)
82
- const portColor = color('green', 'bgBlack', String(port));
83
- const msg = `${source}: Starting Server on :${portColor}\n`;
84
- const version = color('red', 'bgBlack', `v${VERSION}\n`);
85
-
86
- Bun.write(Bun.stdout, TITLE + '\n' + version);
87
- Bun.write(Bun.stdout, msg);
80
+ const { stamp } = timestamp((new Date(Date.now())));
81
+ const source = color('green', 'bgBlack', `[bun-router ${stamp}]`);
82
+ const portColor = color('green', 'bgBlack', String(port));
83
+ const msg = `${source}: Starting Server on :${portColor}\n`;
84
+ const version = color('red', 'bgBlack', `v${VERSION}\n`);
85
+
86
+ Bun.write(Bun.stdout, TITLE + '\n' + version);
87
+ Bun.write(Bun.stdout, msg);
88
88
  }
89
89
 
90
90
  function pad(n: number) {
91
- return String(n).padStart(2, '0');
91
+ return String(n).padStart(2, '0');
92
92
  }
93
93
 
94
- export { Logger, startMessage }
94
+ export { Logger, startMessage };
@@ -1,51 +1,58 @@
1
- import { Route, Context } from "./router.d";
2
- import { Logger } from "../..";
3
- import { http } from "./router";
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { Route, Context } from './router.d';
3
+ import { renderToReadableStream } from 'react-dom/server';
4
+ import { Logger } from '../logger/logger';
5
+ import { http } from './router';
6
+ import { ReactNode } from 'react';
4
7
 
5
8
  async function createContext(path: string, route: Route, request: Request): Promise<Context> {
6
- const params = extractParams(path, route);
7
- const query = new URLSearchParams(path);
8
- const formData = isMultiPartForm(request.headers) ? await request.formData() : new FormData();
9
-
10
- return Promise.resolve({
11
- params,
12
- request,
13
- query,
14
- formData,
15
- logger: Logger(),
16
- json: (statusCode: number, data: any) => http.json(statusCode, data),
17
- });
9
+ const params = extractParams(path, route);
10
+ const query = new URLSearchParams(path);
11
+ const formData = isMultiPartForm(request.headers) ? await request.formData() : new FormData();
12
+
13
+ return Promise.resolve({
14
+ params,
15
+ request,
16
+ query,
17
+ formData,
18
+ logger: Logger(),
19
+ json: (statusCode: number, data: any) => http.json(statusCode, data),
20
+ render: async (component: ReactNode) => await renderStream(component),
21
+ });
18
22
  }
19
23
 
20
24
  function extractParams(path: string, route: Route): Map<string, string> {
21
- const params: Map<string, string> = new Map();
22
- const pathSegments = path.split('/');
23
- const routeSegments = route.path.split('/');
25
+ const params: Map<string, string> = new Map();
26
+ const pathSegments = path.split('/');
27
+ const routeSegments = route.path.split('/');
24
28
 
25
- if (pathSegments.length !== routeSegments.length) return params;
29
+ if (pathSegments.length !== routeSegments.length) return params;
26
30
 
27
- for (let i = 0; i < pathSegments.length; i++) {
28
- if (routeSegments[i][0] === ':') {
29
- const key = routeSegments[i].replace(':', '');
30
- const value = pathSegments[i];
31
- params.set(key, value);
32
- }
33
- }
31
+ for (let i = 0; i < pathSegments.length; i++) {
32
+ if (routeSegments[i][0] === ':') {
33
+ const key = routeSegments[i].replace(':', '');
34
+ const value = pathSegments[i];
35
+ params.set(key, value);
36
+ }
37
+ }
34
38
 
35
- return params;
39
+ return params;
36
40
  }
37
41
 
38
42
  function getContentType(headers: Headers): string {
39
- const contentType = headers.get('Content-Type');
40
- if (!contentType) return '';
41
- return contentType;
43
+ const contentType = headers.get('Content-Type');
44
+ if (!contentType) return '';
45
+ return contentType;
42
46
  }
43
47
 
44
48
  function isMultiPartForm(headers: Headers): boolean {
45
- const contentType = getContentType(headers);
46
- return contentType.includes('multipart/form-data');
49
+ const contentType = getContentType(headers);
50
+ return contentType.includes('multipart/form-data');
47
51
  }
48
52
 
53
+ async function renderStream(children: ReactNode) {
54
+ const stream = await renderToReadableStream(children);
55
+ return new Response(stream, { headers: { 'Content-Type': 'text/html' } });
56
+ }
49
57
 
50
-
51
- export { createContext }
58
+ export { createContext };
@@ -1,4 +1,5 @@
1
- import { TLSOptions, TLSWebSocketServeOptions, WebSocketServeOptions, ServeOptions, TLSServeOptions } from 'bun';
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { TLSWebSocketServeOptions, WebSocketServeOptions, ServeOptions, TLSServeOptions } from 'bun';
2
3
  import { Logger } from '../logger/logger';
3
4
  import { Database } from 'bun:sqlite';
4
5
 
@@ -20,7 +21,6 @@ type Route = {
20
21
  handler: HttpHandler;
21
22
  isLast: boolean;
22
23
  }
23
-
24
24
  type Context = {
25
25
  db?: Database;
26
26
  formData: FormData | Promise<FormData>;
@@ -29,6 +29,7 @@ type Context = {
29
29
  params: Map<string, string>;
30
30
  query: URLSearchParams;
31
31
  request: Request;
32
+ render: (component: React.ReactNode) => Response | Promise<Response>;
32
33
  };
33
34
 
34
35
  type HttpHandler = (ctx: Context) => Response | Promise<Response>
@@ -43,4 +44,4 @@ type RouterOptions<Options> = ServeOptions
43
44
  | TLSWebSocketServeOptions<Options>
44
45
  | undefined
45
46
 
46
- export { Context , Route, BunRouter, RouterOptions, Options, HttpHandler }
47
+ export { Context , Route, BunRouter, RouterOptions, Options, HttpHandler };
@@ -5,96 +5,96 @@ import { httpStatusCodes } from '../http/status';
5
5
  import { readDir } from '../fs/fsys';
6
6
  import { Logger, startMessage } from '../logger/logger';
7
7
  import { http } from '../http/http';
8
- import {RouteTree } from './tree';
8
+ import { RouteTree } from './tree';
9
9
  import { createContext } from './context';
10
10
 
11
11
 
12
12
  const Router: BunRouter = (port?: number | string, options?: RouterOptions<Options>) => {
13
- const { addRoute, findRoute } = RouteTree();
14
- const logger = Logger();
15
-
16
- return {
17
- // add a route to the router tree
18
- add: (pattern, method, callback) => { addRoute(pattern, method, callback) },
19
- get: (pattern: string, callback: HttpHandler) => { addRoute(pattern, 'GET', callback) },
20
- post: (pattern, callback) => { addRoute(pattern, 'POST', callback) },
21
- put: (pattern, callback) => { addRoute(pattern, 'PUT', callback)},
22
- delete: (pattern, callback) => { addRoute(pattern, 'DELETE', callback) },
13
+ const { addRoute, findRoute } = RouteTree();
14
+ const logger = Logger();
15
+
16
+ return {
17
+ // add a route to the router tree
18
+ add: (pattern, method, callback) => { addRoute(pattern, method, callback); },
19
+ get: (pattern: string, callback: HttpHandler) => { addRoute(pattern, 'GET', callback); },
20
+ post: (pattern, callback) => { addRoute(pattern, 'POST', callback); },
21
+ put: (pattern, callback) => { addRoute(pattern, 'PUT', callback);},
22
+ delete: (pattern, callback) => { addRoute(pattern, 'DELETE', callback); },
23
23
 
24
- // add a static route to the router tree
25
- static: async (pattern: string, root: string) => {
26
- await readDir(root, async (fp, _) => {
27
- const pure = path.join('.', fp);
28
- const ext = path.extname(pure);
29
-
30
- let base = path.basename(pure);
31
-
32
- if (ext === '.html') base = base.replace(ext, '');
33
-
34
- if (pattern[0] !== '/') pattern = '/' + pattern;
35
-
36
- let patternPath = pattern + base;
37
-
38
- if (base === 'index') patternPath = pattern;
39
-
40
- const route: Route = {
41
- children: new Map(),
42
- dynamicPath: '',
43
- isLast: true,
44
- path: patternPath,
45
- method: 'GET',
46
- handler: async () => await http.file(200, pure),
47
- };
48
-
49
- addRoute(route.path, 'GET', route.handler);
50
- });
51
- },
52
- // start the server
53
- serve: () => {
54
- startMessage(port ?? 3000);
55
- let opts: Options = { db: ':memory:' };
56
-
57
- Bun.serve({
58
- port: port ?? 3000,
59
- ...options,
60
- async fetch(req) {
61
- const url = new URL(req.url);
62
- let path = url.pathname;
63
-
64
- // set the database
65
- if (options) {
66
- let o = options as Options;
67
- opts.db = o.db;
68
- }
69
-
70
- const route = findRoute(path);
71
-
72
- // if the route exists, execute the handler
73
- if (route) {
74
- if (route.method !== req.method) {
75
- logger.info(405, url.pathname, req.method, httpStatusCodes[405]);
76
- return Promise.resolve(http.methodNotAllowed());
77
- }
78
-
79
- const context = await createContext(path, route, req);
80
- context.db = new Database(opts.db);
81
-
82
- const response = await route.handler(context);
83
-
84
- logger.info(response.status, url.pathname, req.method, httpStatusCodes[response.status]);
85
- return Promise.resolve(response);
86
- }
87
-
88
- // if no route is found, return 404
89
- const response = await http.notFound();
24
+ // add a static route to the router tree
25
+ static: async (pattern: string, root: string) => {
26
+ await readDir(root, async (fp) => {
27
+ const pure = path.join('.', fp);
28
+ const ext = path.extname(pure);
29
+
30
+ let base = path.basename(pure);
31
+
32
+ if (ext === '.html') base = base.replace(ext, '');
33
+
34
+ if (pattern[0] !== '/') pattern = '/' + pattern;
35
+
36
+ let patternPath = pattern + base;
37
+
38
+ if (base === 'index') patternPath = pattern;
39
+
40
+ const route: Route = {
41
+ children: new Map(),
42
+ dynamicPath: '',
43
+ isLast: true,
44
+ path: patternPath,
45
+ method: 'GET',
46
+ handler: async () => await http.file(200, pure),
47
+ };
48
+
49
+ addRoute(route.path, 'GET', route.handler);
50
+ });
51
+ },
52
+ // start the server
53
+ serve: () => {
54
+ startMessage(port ?? 3000);
55
+ const opts: Options = { db: ':memory:' };
56
+
57
+ Bun.serve({
58
+ port: port ?? 3000,
59
+ ...options,
60
+ async fetch(req) {
61
+ const url = new URL(req.url);
62
+ const path = url.pathname;
63
+
64
+ // set the database
65
+ if (options) {
66
+ const o = options as Options;
67
+ opts.db = o.db;
68
+ }
69
+
70
+ const route = findRoute(path);
71
+
72
+ // if the route exists, execute the handler
73
+ if (route) {
74
+ if (route.method !== req.method) {
75
+ logger.info(405, url.pathname, req.method, httpStatusCodes[405]);
76
+ return Promise.resolve(http.methodNotAllowed());
77
+ }
78
+
79
+ const context = await createContext(path, route, req);
80
+ context.db = new Database(opts.db);
81
+
82
+ const response = await route.handler(context);
83
+
84
+ logger.info(response.status, url.pathname, req.method, httpStatusCodes[response.status]);
85
+ return Promise.resolve(response);
86
+ }
87
+
88
+ // if no route is found, return 404
89
+ const response = await http.notFound();
90
90
 
91
- logger.info(response.status, url.pathname, req.method, httpStatusCodes[response.status]);
92
- return Promise.resolve(http.notFound());
91
+ logger.info(response.status, url.pathname, req.method, httpStatusCodes[response.status]);
92
+ return Promise.resolve(http.notFound());
93
93
 
94
- }
95
- });
96
- },
97
- }
98
- }
94
+ }
95
+ });
96
+ },
97
+ };
98
+ };
99
99
 
100
- export { Router, http }
100
+ export { Router, http };
@@ -1,63 +1,62 @@
1
- import { HttpHandler, Route } from "./router.d";
2
- import { http } from "../http/http";
1
+ import { HttpHandler, Route } from './router.d';
2
+ import { http } from '../http/http';
3
3
  import { createContext } from './context';
4
-
5
- const splitPath = (s: string): string[] => s.split('/').filter(x => x !== '');
4
+ import { splitPath } from '../util/strings';
6
5
 
7
6
  const createRoute = (path: string, method: string, handler: HttpHandler): Route => {
8
- const route: Route = {
9
- children: new Map(),
10
- path: path,
11
- dynamicPath: '',
12
- method: method,
13
- handler: handler,
14
- isLast: false
15
- };
16
-
17
- return 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;
18
17
  };
19
18
 
20
19
  const RouteTree = () => {
21
- let root = createRoute('', 'GET', () => http.notFound());
22
-
23
- const addRoute = (path: string, method: string, handler: HttpHandler) => {
24
- const pathParts = splitPath(path);
25
- let current = root;
26
-
27
- for (let i = 0; i < pathParts.length; i++) {
28
- const part = pathParts[i];
29
- if (part.startsWith(':')) {
30
- current.dynamicPath = part;
31
- }
32
- if (!current.children.has(part)) {
33
- current.children.set(part, createRoute(part, method, handler));
34
- }
35
- current = current.children.get(part)!;
36
- }
37
-
38
- current.handler = handler;
39
- current.isLast = true;
40
- current.path = path;
41
- };
42
-
43
- const findRoute = (path: string): Route | undefined => {
44
- const pathParts = splitPath(path);
45
- let current = root;
46
- for (let i = 0; i < pathParts.length; i++) {
47
- const part = pathParts[i];
48
- if (current.children.has(part)) {
49
- current = current.children.get(part)!;
50
- } else if (current.dynamicPath) {
51
- current = current.children.get(current.dynamicPath)!;
52
- } else {
53
- return;
54
- }
55
- }
56
- return current;
57
- }
58
-
59
- return { addRoute, findRoute }
20
+ const root = createRoute('', 'GET', () => http.notFound());
21
+
22
+ const addRoute = (path: string, method: string, handler: HttpHandler) => {
23
+ const pathParts = splitPath(path);
24
+ let current = root;
25
+
26
+ for (let i = 0; i < pathParts.length; i++) {
27
+ const part = pathParts[i];
28
+ if (part.startsWith(':')) {
29
+ current.dynamicPath = part;
30
+ }
31
+ if (!current.children.has(part)) {
32
+ current.children.set(part, createRoute(part, method, handler));
33
+ }
34
+ current = current.children.get(part)!;
35
+ }
36
+
37
+ current.handler = handler;
38
+ current.isLast = true;
39
+ current.path = path;
40
+ };
41
+
42
+ function findRoute(path: string): Route | undefined {
43
+ const pathParts = splitPath(path);
44
+ let current = root;
45
+ for (let i = 0; i < pathParts.length; i++) {
46
+ const part = pathParts[i];
47
+ if (current.children.has(part)) {
48
+ current = current.children.get(part)!;
49
+ } else if (current.dynamicPath) {
50
+ current = current.children.get(current.dynamicPath)!;
51
+ } else {
52
+ return;
53
+ }
54
+ }
55
+ return current;
56
+ }
57
+
58
+ return { addRoute, findRoute };
60
59
 
61
60
  };
62
61
 
63
- export { RouteTree, createContext }
62
+ export { RouteTree, createContext as createContext };
package/package.json CHANGED
@@ -3,10 +3,19 @@
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
5
  "devDependencies": {
6
- "bun-types": "latest"
6
+ "@types/react-dom": "^18.2.7",
7
+ "@typescript-eslint/eslint-plugin": "^6.7.0",
8
+ "@typescript-eslint/parser": "^6.7.0",
9
+ "bun-types": "latest",
10
+ "eslint": "^8.49.0",
11
+ "eslint-plugin-react": "^7.33.2",
12
+ "react-dom": "^18.2.0"
7
13
  },
8
14
  "peerDependencies": {
9
15
  "typescript": "^5.0.0"
10
16
  },
11
- "version": "0.7.3"
17
+ "version": "0.7.4-experimental.0",
18
+ "dependencies": {
19
+ "eslint-plugin-react-hooks": "^4.6.0"
20
+ }
12
21
  }
package/tsconfig.json CHANGED
@@ -16,7 +16,6 @@
16
16
  "allowJs": true,
17
17
  "noEmit": true,
18
18
  "types": [
19
- "./index.d.ts",
20
19
  "bun-types" // add Bun global
21
20
  ]
22
21
  }