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

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.
@@ -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
+