bun-router 0.7.1 → 0.7.3-experimental.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -8,7 +8,8 @@ or
8
8
  `bun i bun-router`
9
9
 
10
10
 
11
- #### Example
11
+ #### Examples
12
+ ##### URL Parameters
12
13
  ```ts
13
14
  import { Router, http } from 'bun-router';
14
15
 
@@ -38,3 +39,41 @@ router.static('/assets', 'static');
38
39
  router.serve();
39
40
  ```
40
41
 
42
+ ##### SQLite
43
+ ```ts
44
+ import { Router } from 'bun-router'
45
+
46
+ const router = Router(3000, { db: 'test.db'});
47
+
48
+ router.post('/register', ctx => {
49
+ const query = ctx.db.query("select 'Hello' as message;");
50
+
51
+ return http.ok(query.get());
52
+ });
53
+
54
+ ```
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
+ ```
79
+
package/bun.lockb CHANGED
Binary file
package/examples/todo.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Router, http } from '..';
2
- import { Context } from '../lib/router/router.d';
3
2
 
4
3
  const Todo = () => {
5
4
  const list: Record<string, string> = {};
package/lib/fs/fsys.ts CHANGED
@@ -5,22 +5,25 @@ import path from 'path';
5
5
  // check if the file path is a directory
6
6
  const isDir = async (fp: string): Promise<boolean> => (await fs.lstat(fp)).isDirectory();
7
7
 
8
- // read a directory recursively and apply the callback to each one
9
- const readDir = async (dirpath: string, handler: (filepath: string, entry: BunFile) => void) => {
10
- const files = await fs.readdir(dirpath);
8
+ async function readDir(dirpath: string, handler: (filepath: string, entry: BunFile) => void) {
9
+ const files = await fs.readdir(dirpath);
11
10
 
12
- for (const file of files) {
13
- const bunFile = Bun.file(file);
11
+ for (const file of files) {
12
+ const bunFile = Bun.file(file);
14
13
 
15
- if (typeof bunFile.name === 'undefined') return
14
+ if (typeof bunFile.name === 'undefined') return
16
15
 
17
- const fp = path.join(dirpath, bunFile.name);
18
- const isdir = await isDir(fp);
16
+ const fp = path.join(dirpath, bunFile.name);
17
+ const isdir = await isDir(fp);
19
18
 
20
- if (isdir) await readDir(fp, handler);
21
- else handler(fp, bunFile);
22
- }
19
+ if (isdir) await readDir(fp, handler);
20
+ else handler(fp, bunFile);
21
+ }
22
+ }
23
+
24
+ function ext(path: string): string {
25
+ return path.split('.').pop() || '';
23
26
  }
24
27
 
25
28
 
26
- 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.3';
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,49 +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
- function extractParams(path: string, route: Route): Map<string, string> {
6
- const params: Map<string, string> = new Map();
7
- const pathSegments = path.split('/');
8
- const routeSegments = route.path.split('/');
8
+ async function createContext(path: string, route: Route, request: Request): Promise<Context> {
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
+ });
22
+ }
9
23
 
10
- if (pathSegments.length !== routeSegments.length) return params;
24
+ function extractParams(path: string, route: Route): Map<string, string> {
25
+ const params: Map<string, string> = new Map();
26
+ const pathSegments = path.split('/');
27
+ const routeSegments = route.path.split('/');
11
28
 
12
- for (let i = 0; i < pathSegments.length; i++) {
13
- if (routeSegments[i][0] === ':') {
14
- const key = routeSegments[i].replace(':', '');
15
- const value = pathSegments[i];
16
- params.set(key, value);
17
- }
18
- }
29
+ if (pathSegments.length !== routeSegments.length) return params;
19
30
 
20
- return params;
21
- }
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
+ }
22
38
 
23
- async function createContext(path: string, route: Route, request: Request): Promise<Context> {
24
- const params = extractParams(path, route);
25
- const query = new URLSearchParams(path);
26
- const formData = isMultiPartForm(request.headers) ? await request.formData() : new FormData();
27
-
28
- return Promise.resolve({
29
- params,
30
- request,
31
- query,
32
- formData,
33
- logger: Logger(),
34
- json: (statusCode: number, data: any) => http.json(statusCode, data),
35
- });
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');
51
+ }
52
+
53
+ async function renderStream(children: ReactNode) {
54
+ const stream = await renderToReadableStream(children);
55
+ return new Response(stream, { headers: { 'Content-Type': 'text/html' } });
47
56
  }
48
57
 
49
- 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,7 +29,7 @@ type Context = {
29
29
  params: Map<string, string>;
30
30
  query: URLSearchParams;
31
31
  request: Request;
32
- token?: string;
32
+ render: (component: React.ReactNode) => Response | Promise<Response>;
33
33
  };
34
34
 
35
35
  type HttpHandler = (ctx: Context) => Response | Promise<Response>
@@ -44,4 +44,4 @@ type RouterOptions<Options> = ServeOptions
44
44
  | TLSWebSocketServeOptions<Options>
45
45
  | undefined
46
46
 
47
- export { Context , Route, BunRouter, RouterOptions, Options, HttpHandler }
47
+ export { Context , Route, BunRouter, RouterOptions, Options, HttpHandler };
@@ -1,100 +1,100 @@
1
1
  import path from 'path';
2
2
  import { Database } from 'bun:sqlite';
3
- import { Route, BunRouter, Context, RouterOptions, Options, HttpHandler } from './router.d';
3
+ import { Route, BunRouter, RouterOptions, Options, HttpHandler } from './router.d';
4
4
  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, Context, 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.1"
17
+ "version": "0.7.3-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
  }