bun-router 0.5.4 → 0.5.8

Sign up to get free protection for your applications and to get access to all the features.
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
  }