bun-router 0.7.4-experimental.8 → 0.7.4-experimental.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,6 +2,6 @@ import { Router } from '..';
2
2
 
3
3
  const r = Router(3001);
4
4
 
5
- r.static('/page', './pages');
5
+ r.static('/', './examples/pages');
6
6
 
7
7
  r.serve();
@@ -1,66 +1,66 @@
1
1
  const httpStatusCodes: { [key: number]: string } = {
2
- 100: 'Continue',
3
- 101: 'Switching Protocols',
4
- 102: 'Processing',
5
- 103: 'Early Hints',
6
- 200: 'OK',
7
- 201: 'Created',
8
- 202: 'Accepted',
9
- 203: 'Non-Authoritative Information',
10
- 204: 'No Content',
11
- 205: 'Reset Content',
12
- 206: 'Partial Content',
13
- 207: 'Multi-Status',
14
- 208: 'Already Reported',
15
- 226: 'IM Used',
16
- 300: 'Multiple Choices',
17
- 301: 'Moved Permanently',
18
- 302: 'Found',
19
- 303: 'See Other',
20
- 304: 'Not Modified',
21
- 305: 'Use Proxy',
22
- 307: 'Temporary Redirect',
23
- 308: 'Permanent Redirect',
24
- 400: 'Bad Request',
25
- 401: 'Unauthorized',
26
- 402: 'Payment Required',
27
- 403: 'Forbidden',
28
- 404: 'Not Found',
29
- 405: 'Method Not Allowed',
30
- 406: 'Not Acceptable',
31
- 407: 'Proxy Authentication Required',
32
- 408: 'Request Timeout',
33
- 409: 'Conflict',
34
- 410: 'Gone',
35
- 411: 'Length Required',
36
- 412: 'Precondition Failed',
37
- 413: 'Payload Too Large',
38
- 414: 'URI Too Long',
39
- 415: 'Unsupported Media Type',
40
- 416: 'Range Not Satisfiable',
41
- 417: 'Expectation Failed',
42
- 418: "I'm a Teapot",
43
- 421: 'Misdirected Request',
44
- 422: 'Unprocessable Entity',
45
- 423: 'Locked',
46
- 424: 'Failed Dependency',
47
- 425: 'Too Early',
48
- 426: 'Upgrade Required',
49
- 428: 'Precondition Required',
50
- 429: 'Too Many Requests',
51
- 431: 'Request Header Fields Too Large',
52
- 451: 'Unavailable For Legal Reasons',
53
- 500: 'Internal Server Error',
54
- 501: 'Not Implemented',
55
- 502: 'Bad Gateway',
56
- 503: 'Service Unavailable',
57
- 504: 'Gateway Timeout',
58
- 505: 'HTTP Version Not Supported',
59
- 506: 'Variant Also Negotiates',
60
- 507: 'Insufficient Storage',
61
- 508: 'Loop Detected',
62
- 510: 'Not Extended',
63
- 511: 'Network Authentication Required',
64
- };
2
+ 100: 'Continue',
3
+ 101: 'Switching Protocols',
4
+ 102: 'Processing',
5
+ 103: 'Early Hints',
6
+ 200: 'OK',
7
+ 201: 'Created',
8
+ 202: 'Accepted',
9
+ 203: 'Non-Authoritative Information',
10
+ 204: 'No Content',
11
+ 205: 'Reset Content',
12
+ 206: 'Partial Content',
13
+ 207: 'Multi-Status',
14
+ 208: 'Already Reported',
15
+ 226: 'IM Used',
16
+ 300: 'Multiple Choices',
17
+ 301: 'Moved Permanently',
18
+ 302: 'Found',
19
+ 303: 'See Other',
20
+ 304: 'Not Modified',
21
+ 305: 'Use Proxy',
22
+ 307: 'Temporary Redirect',
23
+ 308: 'Permanent Redirect',
24
+ 400: 'Bad Request',
25
+ 401: 'Unauthorized',
26
+ 402: 'Payment Required',
27
+ 403: 'Forbidden',
28
+ 404: 'Not Found',
29
+ 405: 'Method Not Allowed',
30
+ 406: 'Not Acceptable',
31
+ 407: 'Proxy Authentication Required',
32
+ 408: 'Request Timeout',
33
+ 409: 'Conflict',
34
+ 410: 'Gone',
35
+ 411: 'Length Required',
36
+ 412: 'Precondition Failed',
37
+ 413: 'Payload Too Large',
38
+ 414: 'URI Too Long',
39
+ 415: 'Unsupported Media Type',
40
+ 416: 'Range Not Satisfiable',
41
+ 417: 'Expectation Failed',
42
+ 418: 'I\'m a Teapot',
43
+ 421: 'Misdirected Request',
44
+ 422: 'Unprocessable Entity',
45
+ 423: 'Locked',
46
+ 424: 'Failed Dependency',
47
+ 425: 'Too Early',
48
+ 426: 'Upgrade Required',
49
+ 428: 'Precondition Required',
50
+ 429: 'Too Many Requests',
51
+ 431: 'Request Header Fields Too Large',
52
+ 451: 'Unavailable For Legal Reasons',
53
+ 500: 'Internal Server Error',
54
+ 501: 'Not Implemented',
55
+ 502: 'Bad Gateway',
56
+ 503: 'Service Unavailable',
57
+ 504: 'Gateway Timeout',
58
+ 505: 'HTTP Version Not Supported',
59
+ 506: 'Variant Also Negotiates',
60
+ 507: 'Insufficient Storage',
61
+ 508: 'Loop Detected',
62
+ 510: 'Not Extended',
63
+ 511: 'Network Authentication Required',
64
+ };
65
65
 
66
- export { httpStatusCodes }
66
+ export { httpStatusCodes };
@@ -9,7 +9,7 @@ _ _
9
9
  |___|___|_|_| |_| |___|___|_| |___|_|
10
10
 
11
11
  `;
12
- const VERSION = '0.7.4-experimental.8';
12
+ const VERSION = '0.7.4-experimental.9';
13
13
  const Logger = (): BunLogger => {
14
14
  return {
15
15
  info: async (statusCode: number, routePath: string, method: string, message?: string) => {
@@ -18,7 +18,7 @@ const createRoute = (path: string, method: string, handler: HttpHandler): Route
18
18
  const RouteTree = () => {
19
19
  const root = createRoute('', 'GET', () => http.notFound());
20
20
 
21
- const addRoute = (pattern: string, method: string, handler: HttpHandler) => {
21
+ function addRoute (pattern: string, method: string, handler: HttpHandler){
22
22
  const pathParts = pattern.split('/');
23
23
  let current = root;
24
24
 
@@ -36,7 +36,7 @@ const RouteTree = () => {
36
36
  current.handler = handler;
37
37
  current.isLast = true;
38
38
  current.path = pattern;
39
- };
39
+ }
40
40
 
41
41
  function findRoute(pathname: string): Route | undefined {
42
42
  const pathParts = pathname.split('/');
@@ -54,7 +54,31 @@ const RouteTree = () => {
54
54
  return current;
55
55
  }
56
56
 
57
- return { addRoute, findRoute };
57
+ function size() {
58
+ let count = 0;
59
+ function traverse(route: Route) {
60
+ count++;
61
+ for (const child of route.children.values()) {
62
+ traverse(child);
63
+ }
64
+ }
65
+ traverse(root);
66
+ return count;
67
+ }
68
+
69
+ function list() {
70
+ const routes: Route[] = [];
71
+ function traverse(route: Route) {
72
+ routes.push(route);
73
+ for (const child of route.children.values()) {
74
+ traverse(child);
75
+ }
76
+ }
77
+ traverse(root);
78
+ return routes;
79
+ }
80
+
81
+ return { addRoute, findRoute, size, list };
58
82
 
59
83
  };
60
84
 
@@ -2,14 +2,14 @@ import path from 'path';
2
2
  import { Database } from 'bun:sqlite';
3
3
  import { Route, BunRouter, RouterOptions, Options, HttpHandler } from './router.d';
4
4
  import { httpStatusCodes } from '../http/status';
5
- import { readDir, exists } from '../fs/fsys';
5
+ import { readDir } from '../fs/fsys';
6
6
  import { Logger, startMessage } from '../logger/logger';
7
7
  import { http } from '../http/http';
8
8
  import { RouteTree } from './routeTree';
9
9
  import { createContext } from './context';
10
10
 
11
11
  const Router: BunRouter = (port?: number | string, options?: RouterOptions<Options>) => {
12
- const { addRoute, findRoute } = RouteTree();
12
+ const { addRoute, findRoute, list } = RouteTree();
13
13
  const logger = Logger();
14
14
 
15
15
  async function loadComponent(root: string, name: string) {
@@ -17,9 +17,9 @@ const Router: BunRouter = (port?: number | string, options?: RouterOptions<Optio
17
17
  return module.default;
18
18
  }
19
19
 
20
- function normalizePath(pattern: string, pathname: string) {
20
+ function extractPathExtBase(pattern: string, pathname: string) {
21
21
  const extension = path.extname(pathname);
22
- let base = path.basename(pathname);
22
+ let base = encodeURIComponent(path.basename(pathname));
23
23
 
24
24
  if (extension === '.html' || extension === '.tsx') base = base.replace(extension, '');
25
25
 
@@ -27,9 +27,14 @@ const Router: BunRouter = (port?: number | string, options?: RouterOptions<Optio
27
27
 
28
28
  if (base === 'index') patternPath = pattern;
29
29
 
30
- return { patternPath, extension, base }
30
+ return { patternPath, extension, base };
31
31
  }
32
32
 
33
+ async function exists(fp: string) {
34
+ const f = Bun.file(fp);
35
+ return await f.exists();
36
+ }
37
+
33
38
  return {
34
39
  // add a route to the router tree
35
40
  add: (pattern, method, callback) => { addRoute(pattern, method, callback); },
@@ -43,15 +48,15 @@ const Router: BunRouter = (port?: number | string, options?: RouterOptions<Optio
43
48
  // all other file extensions are served as files
44
49
  // the root directory is traversed recursively
45
50
  static: async (pattern: string, root: string) => {
46
- if (!exists(root)) console.log(`Cannot find directory ${root}`);
51
+ if (!exists(root)) return console.error(`Directory not found: ${root}`);
47
52
  await readDir(root, async (fp) => {
48
- const { patternPath, extension, base } = normalizePath(pattern, fp);
53
+ const { patternPath, extension, base } = extractPathExtBase(pattern, fp);
49
54
 
50
55
  const route: Route = {
51
56
  children: new Map(),
52
57
  dynamicPath: '',
53
58
  isLast: true,
54
- path: patternPath.slice(1), // remove the leading '/'
59
+ path: patternPath.startsWith('//') ? patternPath.slice(1) : patternPath, // remove the leading '/' if it exists
55
60
  method: 'GET',
56
61
  handler: async () => {
57
62
  if (extension === '.tsx') {
@@ -65,6 +70,9 @@ const Router: BunRouter = (port?: number | string, options?: RouterOptions<Optio
65
70
 
66
71
  addRoute(route.path, 'GET', route.handler);
67
72
  });
73
+
74
+ console.log(list());
75
+
68
76
  },
69
77
  // start the server
70
78
  serve: () => {
@@ -102,11 +110,16 @@ const Router: BunRouter = (port?: number | string, options?: RouterOptions<Optio
102
110
  return Promise.resolve(response);
103
111
  }
104
112
 
105
- // if no route is found, return 404
113
+ // if no route is found, return 404
106
114
  const response = await http.notFound();
107
115
 
108
116
  logger.info(response.status, url.pathname, req.method, httpStatusCodes[response.status]);
109
117
  return Promise.resolve(http.notFound());
118
+ },
119
+ error(error) {
120
+ return new Response(`<pre>${error}\n${error.stack}</pre>`, {
121
+ headers: { 'Content-Type': 'text/html' },
122
+ });
110
123
  }
111
124
  });
112
125
  },
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "peerDependencies": {
15
15
  "typescript": "^5.0.0"
16
16
  },
17
- "version": "0.7.4-experimental.8",
17
+ "version": "0.7.4-experimental.9",
18
18
  "dependencies": {
19
19
  "@types/react": "^18.2.22",
20
20
  "eslint-plugin-react-hooks": "^4.6.0",
@@ -1,35 +1,36 @@
1
1
  import { describe, test, expect } from 'bun:test';
2
2
 
3
3
  describe('Router', () => {
4
- test('Serve', async () => {
5
- const proc = Bun.spawn(['./tests/serve.test.sh'], {
6
- onExit: (_proc, _exitCode, _signalCode , error) => {
7
- if (error) console.error(error);
8
- },
9
- });
4
+ test('Serve', async () => {
5
+ const proc = Bun.spawn(['./tests/serve.test.sh'], {
6
+ onExit: (_proc, _exitCode, _signalCode , error) => {
7
+ if (error) console.error(error);
8
+ },
9
+ });
10
10
 
11
- const text = await new Response(proc.stdout).text();
11
+ const text = await new Response(proc.stdout).text();
12
12
 
13
- const hasFailed = text.includes('Failed');
13
+ const hasFailed = text.includes('Failed');
14
14
 
15
- if (hasFailed) console.log(text);
15
+ if (hasFailed) console.log(text);
16
16
 
17
- expect(hasFailed).toBe(false);
17
+ expect(hasFailed).toBe(false);
18
18
 
19
- proc.kill(0);
20
- })
19
+ proc.kill(0);
20
+ });
21
21
 
22
- test('Static', async() => {
23
- const proc = Bun.spawn(['./tests/static.test.sh']);
22
+ test('Static', async() => {
23
+ const proc = Bun.spawn(['./tests/static.test.sh']);
24
24
 
25
- const text = await new Response(proc.stdout).text();
25
+ const text = await new Response(proc.stdout).text();
26
26
 
27
- const hasFailed = text.includes('Failed');
28
- if (hasFailed) console.log(text);
27
+ const hasFailed = text.includes('Failed');
28
+ if (hasFailed) console.log(text);
29
29
 
30
- expect(hasFailed).toBe(false);
30
+ expect(hasFailed).toBe(false);
31
31
 
32
- proc.kill(0);
33
- });
32
+ proc.kill(0);
33
+ });
34
34
  });
35
35
 
36
+