bun-router 0.5.4 → 0.5.8

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/examples/basic.ts CHANGED
@@ -1,18 +1,18 @@
1
- import { router, html, json } from '..';
1
+ import { router, http } from '..';
2
2
 
3
3
  const r = router();
4
4
 
5
- r.add('/', 'GET', () => json('ok'));
5
+ r.add('/', 'GET', () => http.json('ok'));
6
6
 
7
7
  r.add('/user/:name', 'GET', (ctx) => {
8
8
  const name = ctx.params.get('name');
9
- return json(name);
9
+ return http.json(name);
10
10
  });
11
11
 
12
12
  r.add('/user/:name/:id', 'GET', (ctx) => {
13
13
  const name = ctx.params.get('name');
14
14
  const id = ctx.params.get('id');
15
- return json({name: name, id: id});
15
+ return http.json({name: name, id: id});
16
16
  });
17
17
 
18
18
  r.serve();
@@ -1,22 +1,22 @@
1
- import { router, html } from '..';
1
+ import { router, http } from '..';
2
2
  import { Context } from '../lib/router/router.d';
3
3
 
4
4
  const handler = (ctx: Context) => {
5
5
  const name = ctx.params.get('name');
6
- if (typeof name === 'undefined' || name === '') return html('<h6 style="color: red">User Undefined</h6>');
7
- return html(`<h4>Hello, ${name}!</h4>`);
6
+ if (typeof name === 'undefined' || name === '') return http.html('<h6 style="color: red">User Undefined</h6>');
7
+ return http.html(`<h4>Hello, ${name}!</h4>`);
8
8
  }
9
9
 
10
10
  const space = (ctx: Context) => {
11
11
  const name = ctx.params.get('name');
12
- if (typeof name === 'undefined' || name === '') return html(`<h6 style="color: red">Space [${name}] Not Found</h6>`);
13
- return html(`<h4>Welcome to ${name}!`)
12
+ if (typeof name === 'undefined' || name === '') return http.html(`<h6 style="color: red">Space [${name}] Not Found</h6>`);
13
+ return http.html(`<h4>Welcome to ${name}!`)
14
14
  }
15
15
 
16
16
  const handleSettings = (ctx: Context) => {
17
17
  const name = ctx.params.get('name');
18
- if (typeof name === 'undefined' || name === '') return html(`<h6 style="color: red">User Not Found</h6>`);
19
- return html(`<h4>Settings for ${name}</h4>`)
18
+ if (typeof name === 'undefined' || name === '') return http.html(`<h6 style="color: red">User Not Found</h6>`);
19
+ return http.html(`<h4>Settings for ${name}</h4>`)
20
20
  }
21
21
 
22
22
  const r = router();
@@ -1,4 +1,4 @@
1
- import { router, logger, json, html } from '..';
1
+ import { router, logger, http } from '..';
2
2
 
3
3
  const r = router();
4
4
  const log = logger();
@@ -8,9 +8,9 @@ r.add('/:foo', 'GET', (ctx) => {
8
8
  const foo = ctx.params.get('foo');
9
9
  if (!foo) {
10
10
  log.error(500, url.pathname, ctx.request.method, new Error('undefined'));
11
- return json({status: 500, text: 'Foo is undefined'});
11
+ return http.json({status: 500, text: 'Foo is undefined'});
12
12
  }
13
- return html(`<h4 style='font-family: sans-serif;'>Oh hello, ${foo}</h4>`)
13
+ return http.html(`<h4 style='font-family: sans-serif;'>Oh hello, ${foo}</h4>`)
14
14
  });
15
15
 
16
16
  r.serve();
@@ -1,4 +1,4 @@
1
- import { router, json } from '..';
1
+ import { router, http } from '..';
2
2
 
3
3
  const r = router(3000, {db: './examples/dbs/test.db'});
4
4
 
@@ -8,7 +8,7 @@ r.add('/u/new/:name', 'GET', (ctx) => {
8
8
 
9
9
  ctx.db.run(`INSERT INTO test VALUES(${rando}, "${name}")`);
10
10
 
11
- return json({message: 'ok'});
11
+ return http.json({message: 'ok'});
12
12
  });
13
13
 
14
14
  r.add('/u/:name', 'GET', (ctx) => {
@@ -16,7 +16,7 @@ r.add('/u/:name', 'GET', (ctx) => {
16
16
  const data = ctx.db.query(`SELECT * FROM test WHERE name = "${name}";`).get();
17
17
  const d = data as {id: number, name: string};
18
18
 
19
- return d ? json(d) : new Response('not found', {status: 404});
19
+ return d ? http.json(d) : new Response('not found', {status: 404});
20
20
  });
21
21
 
22
22
  r.serve();
@@ -0,0 +1,49 @@
1
+ import { router, http } from '..';
2
+ import { Context } from '../lib/router/router.d';
3
+
4
+ const Todo = () => {
5
+ const list: Record<string, string> = {};
6
+
7
+ return {
8
+ add: (key: string, value: string) => { list[key] = value },
9
+ get: (key: string) => list[key],
10
+ remove: (key: string) => { delete list[key] },
11
+ size: () => Object.entries(list).length,
12
+ export: () => list,
13
+ }
14
+ }
15
+
16
+ const todo = Todo();
17
+
18
+ const r = router();
19
+
20
+ r.add('/api/new', 'POST', ctx => {
21
+ const query = new URL(ctx.request.url).searchParams;
22
+ const key = query.get('key');
23
+ const content = query.get('content');
24
+
25
+ if (!key || !content) return http.message(400, 'invalid query params');
26
+ ctx.logger.message(`Adding ${key} with ${content}`);
27
+ todo.add(key, content);
28
+
29
+ return ctx.json({ message: 'ok' });
30
+ });
31
+
32
+ r.add('/api/todo/:key', 'GET', ctx => {
33
+ const key = ctx.params.get('key');
34
+ if (!key) return http.message(400, 'invalid params');
35
+
36
+ const content = todo.get(key);
37
+ if (!content) return http.notFound();
38
+
39
+ return ctx.json({key: key, content: content});
40
+ });
41
+
42
+ r.add('/api/get/all', 'GET', ctx => {
43
+ return ctx.json(todo.export());
44
+ });
45
+
46
+ r.serve();
47
+
48
+
49
+
@@ -0,0 +1,64 @@
1
+ import { httpStatusCodes } from "./status";
2
+
3
+ const http = {
4
+ json: async (data: any): Promise<Response> => {
5
+ const jsonString = JSON.stringify(data);
6
+ return Promise.resolve(new Response(jsonString, {
7
+ status: 200,
8
+ statusText: httpStatusCodes[200],
9
+ headers: {'Content-Type': 'application/json'},
10
+ }));
11
+ },
12
+ html: async (content: string): Promise<Response> => {
13
+ content = Bun.escapeHTML(content);
14
+ return Promise.resolve(new Response(Bun.escapeHTML(content), {
15
+ status: 200,
16
+ statusText: httpStatusCodes[200],
17
+ headers: {'Content-Type': 'text/html; charset=utf-8'}
18
+ }));
19
+ },
20
+ file: async (fp: string): Promise<Response> => {
21
+ const file = Bun.file(fp);
22
+ const exists = await file.exists();
23
+
24
+ if (!exists) return http.notFound(`File not found: ${fp}`);
25
+
26
+ const content = await file.arrayBuffer();
27
+ if (!content) return http.noContent();
28
+
29
+ let contentType = 'text/html; charset=utf-9';
30
+
31
+ if (file.type.includes('image'))
32
+ contentType = file.type + '; charset=utf-8';
33
+
34
+ return Promise.resolve(new Response(content, {
35
+ status: 200,
36
+ statusText: httpStatusCodes[200],
37
+ headers: { 'Content-Type': contentType}
38
+ }));
39
+ },
40
+ noContent: async (): Promise<Response> => Promise.resolve(new Response('no content', {
41
+ status: 204,
42
+ statusText: 'no content',
43
+ })),
44
+ notFound: async(msg?: string): Promise<Response> => {
45
+ const response = new Response(msg ?? 'not found', {
46
+ status: 404,
47
+ statusText: httpStatusCodes[404],
48
+ headers: {'Content-Type': 'text/html'},
49
+ });
50
+
51
+ return Promise.resolve(response);
52
+ },
53
+ message: async (status: number, msg?: string): Promise<Response> => {
54
+ const response = new Response(msg ?? '?', {
55
+ status: status,
56
+ statusText: httpStatusCodes[status],
57
+ headers: {'Content-Type': 'text/html; charset-utf-8'},
58
+ });
59
+ return Promise.resolve(response)
60
+ }
61
+
62
+ }
63
+
64
+ export { http }
@@ -5,10 +5,12 @@ import { Database } from 'bun:sqlite';
5
5
 
6
6
  type Context = {
7
7
  request: Request,
8
+ route: Route,
8
9
  params: Map<string, string>,
9
10
  token?: string,
10
11
  db: Database,
11
12
  logger: Logger,
13
+ json: (data: any) => Response | Promise<Response>,
12
14
  }
13
15
 
14
16
  type Route = {
@@ -1,104 +1,11 @@
1
+ import path from 'path';
1
2
  import { Database } from 'bun:sqlite';
2
3
  import { Route, Router, Context, RouterOptions, Options } from './router.d';
3
4
  import { httpStatusCodes } from '../http/status';
4
5
  import { readDir } from '../fs/fsys';
5
6
  import { logger } from '../logger/logger';
6
- import path from 'path';
7
7
  import { Logger } from '../logger/logger.d';
8
-
9
- // create a generic HTTP response
10
- const httpMessage = async (status: number, msg?: string): Promise<Response> => {
11
- const response = new Response(msg ?? '?', {
12
- status: status,
13
- statusText: msg ?? '?',
14
- headers: { 'Content-Type': 'text/html; charset-uft-8' }
15
- });
16
- return new Promise((resolve) => {
17
- resolve(response);
18
- });
19
- };
20
-
21
- // a generic 'not found' HTTP response
22
- const notFound = async (msg?: string): Promise<Response> => {
23
- const response = new Response(msg ?? 'not found', {
24
- status: 404,
25
- statusText: 'not found',
26
- headers: { 'Content-Type': 'text/html' },
27
- });
28
-
29
- return new Promise((resolve) => {
30
- resolve(response);
31
- });
32
- }
33
-
34
- // a generic 'no content' HTTP response
35
- const noContent = async (): Promise<Response> => {
36
- const response = new Response('no content', {
37
- status: 204,
38
- statusText: 'no content',
39
- });
40
-
41
- return new Promise((resolve) => {
42
- resolve(response);
43
- });
44
- }
45
-
46
- // IO handling
47
- const file = async (filepath: string): Promise<Response> => {
48
- const file = Bun.file(filepath);
49
- const exists = await file.exists();
50
-
51
- // check if the file exists, return 'not found' if it doesn't.
52
- if (!exists)
53
- return notFound(`File not found: ${filepath}`);
54
-
55
- // get the content of the file as an ArrayBuffer
56
- const content = await file.arrayBuffer();
57
- if (!content)
58
- return noContent();
59
-
60
- // default Content-Type + encoding
61
- let contentType = 'text/html; charset=utf-8';
62
-
63
- // change the Content-Type if the file type is an image.
64
- // file.type provides the necessary Content-Type
65
- if (file.type.includes('image')) {
66
- contentType = file.type + '; charset=utf-8';
67
- }
68
-
69
- // create a new response with the necessary criteria
70
- const response = new Response(content, {
71
- status: 200,
72
- statusText: 'ok',
73
- headers: { 'Content-Type': contentType },
74
- });
75
-
76
- return Promise.resolve(response);
77
- }
78
-
79
- // handle strings as HTML
80
- const html = async (content: string): Promise<Response> => {
81
- const response = new Response(content, {
82
- status: 200,
83
- statusText: 'ok',
84
- headers: { 'Content-Type': 'text/html; charset=utf-8' },
85
- });
86
-
87
- // escape the HTML
88
- content = Bun.escapeHTML(content);
89
-
90
- return Promise.resolve(response);
91
- }
92
-
93
- // create a JSON response
94
- const json = (data: any): Response => {
95
- const jsonString = JSON.stringify(data);
96
-
97
- const res = new Response(jsonString);
98
- res.headers.set('Content-Type', 'application/json');
99
-
100
- return res
101
- }
8
+ import { http } from '../http/generic-methods';
102
9
 
103
10
  // extract dynamic URL parameters
104
11
  // if the route pattern is /:foo and the request URL is /bar: {foo: 'bar'}
@@ -139,17 +46,18 @@ const match = (route: Route, ctx: Context): boolean => {
139
46
  return false;
140
47
  }
141
48
 
142
- const setContext = (req: Request, lgr: Logger, opts: Options): Context => {
49
+ // set the context for the reuest
50
+ const setContext = (req: Request, lgr: Logger, opts: Options, route: Route): Context => {
143
51
  return {
144
52
  request: req,
145
53
  params: new Map(),
146
54
  db: new Database(opts.db ?? ':memory:'),
147
55
  logger: lgr,
56
+ route: route,
57
+ json: (data: any) => http.json(data),
148
58
  }
149
59
  }
150
60
 
151
-
152
-
153
61
  const router: Router = (port?: number | string, options?: RouterOptions<Options>) => {
154
62
  const routes: Array<Route> = new Array();
155
63
  const lgr = logger();
@@ -197,8 +105,9 @@ const router: Router = (port?: number | string, options?: RouterOptions<Options>
197
105
  const route: Route = {
198
106
  pattern: patternPath,
199
107
  method: 'GET',
200
- callback: async () => await file(pure),
108
+ callback: async () => await http.file(pure),
201
109
  };
110
+
202
111
  routes.push(route);
203
112
  });
204
113
  },
@@ -218,41 +127,30 @@ const router: Router = (port?: number | string, options?: RouterOptions<Options>
218
127
  opts.db = o.db;
219
128
  }
220
129
 
221
- let statusCode = 404; // Default status code for route not found
130
+ let statusCode = 404;
222
131
 
223
132
  for (const route of routes) {
224
- const ctx = setContext(req, lgr, opts);
225
-
226
- if (url.pathname === '/favicon.ico') {
227
- return noContent();
228
- }
229
-
230
- if (route.method !== req.method) {
231
- statusCode = 405;
232
- continue;
233
- }
234
-
235
- if (match(route, ctx)) {
236
- const res = await route.callback(ctx);
237
- if (res) {
238
- statusCode = 200;
239
- lgr.info(statusCode, url.pathname, req.method, httpStatusCodes[statusCode]);
240
- return res
133
+ const ctx = setContext(req, lgr, opts, route);
134
+
135
+ if (match(route, ctx) || route.pattern === url.pathname) {
136
+ if (route.method === ctx.request.method) {
137
+ const res = await route.callback(ctx);
138
+ statusCode = res.status;
139
+ lgr.info(res.status, route.pattern, req.method, httpStatusCodes[res.status]);
140
+ return Promise.resolve(res);
241
141
  } else {
242
- statusCode = 500;
243
- break;
142
+ const res = new Response(httpStatusCodes[405], {
143
+ status: 405,
144
+ statusText: httpStatusCodes[305]
145
+ });
146
+ lgr.info(405, route.pattern, req.method, httpStatusCodes[405])
147
+ return Promise.resolve(res);
244
148
  }
245
- }
246
- }
247
-
248
- if (statusCode === 405) {
249
- lgr.info(statusCode, url.pathname, req.method, httpStatusCodes[statusCode]);
250
- return httpMessage(statusCode, httpStatusCodes[statusCode]);
149
+ }
251
150
  }
252
151
 
253
152
  lgr.info(statusCode, url.pathname, req.method, httpStatusCodes[statusCode]);
254
- return httpMessage(statusCode, httpStatusCodes[statusCode]);
255
-
153
+ return Promise.resolve(http.message(statusCode, httpStatusCodes[statusCode]));
256
154
  }
257
155
  });
258
156
  },
@@ -260,4 +158,4 @@ const router: Router = (port?: number | string, options?: RouterOptions<Options>
260
158
  }
261
159
 
262
160
 
263
- export { router, json, file, extract, html }
161
+ export { router, extract, http }
package/package.json CHANGED
@@ -8,5 +8,5 @@
8
8
  "peerDependencies": {
9
9
  "typescript": "^5.0.0"
10
10
  },
11
- "version": "0.5.4"
11
+ "version": "0.5.8"
12
12
  }