bun-router 0.5.0 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,5 @@
1
- import { router, html, Context } from '..';
1
+ import { router, html } from '..';
2
+ import { Context } from '../lib/router/router.d';
2
3
 
3
4
  const handler = (ctx: Context) => {
4
5
  const name = ctx.params.get('name');
@@ -0,0 +1,66 @@
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
+ };
65
+
66
+ export { httpStatusCodes }
@@ -3,6 +3,7 @@ type Logger = {
3
3
  info: (statusCode: number, routePath: string, method: string, message?: string) => void,
4
4
  error: (statusCode: number, routePath: string, method: string, error: Error) => void,
5
5
  warn: (msg: string) => void,
6
+ message: (msg: string) => void,
6
7
  }
7
8
 
8
9
  export { Logger }
@@ -26,6 +26,7 @@ const colorCode = (n: number, text?:string): string => {
26
26
  return color('white', 'bgBlack', `[${s}]`).trim();
27
27
  }
28
28
 
29
+
29
30
  const clean = (s: string) => s.replace(/\x1B\[\d{1,2}(;\d{1,2}){0,2}m/g, '');
30
31
 
31
32
  const format = (statusCode: number, routePath: string, method: string, message?: string): string => {
@@ -53,7 +54,7 @@ const logger = (): Logger => {
53
54
  const { stamp } = timestamp((new Date(Date.now())));
54
55
  const source = color('green', 'bgBlack', `[bun-router ${stamp}]`);
55
56
  const rp = color('white', 'bgBlack', routePath);
56
- const msg = `${source}: ${colorCode(statusCode)}: ${rp} ${(method === 'GET') ? '->' : '<-'} ${method}\n`
57
+ const msg = `${source}: ${colorCode(statusCode)}: ${rp} ${(method === 'GET') ? '->' : '<-'} ${method}${' | ' +message ?? ''}\n`
57
58
 
58
59
  await Bun.write(Bun.stdout, msg);
59
60
 
@@ -75,6 +76,15 @@ const logger = (): Logger => {
75
76
  const msgColor = color('yellow', 'bgBlack', msg);
76
77
  msg = `${source} : ${msgColor}\n`;
77
78
  await Bun.write(Bun.stdout, msg);
79
+ },
80
+ message: async (msg: string) => {
81
+ const { stamp } = timestamp((new Date(Date.now())));
82
+ const source = color('black', 'bgCyan', `[message ${stamp}]`);
83
+ const msgColor = color('yellow', 'bgBlack', msg);
84
+ msg = `${source}: ${msgColor}\n`;
85
+ await Bun.write(Bun.stdout, msg);
86
+
87
+ messages.push(clean(msg));
78
88
  }
79
89
  }
80
90
  }
@@ -1,4 +1,5 @@
1
1
  import { TLSOptions, TLSWebSocketServeOptions, WebSocketServeOptions, ServeOptions, TLSServeOptions } from 'bun';
2
+ import { Logger } from '../logger/logger';
2
3
  import { Database } from 'bun:sqlite';
3
4
 
4
5
 
@@ -7,6 +8,7 @@ type Context = {
7
8
  params: Map<string, string>,
8
9
  token?: string,
9
10
  db: Database,
11
+ logger: Logger,
10
12
  }
11
13
 
12
14
  type Route = {
@@ -28,6 +30,8 @@ type RouterOptions<Options> = ServeOptions
28
30
 
29
31
  type Router = (port?: number | string, options?: RouterOptions) => {
30
32
  add: (pattern: string, method: string, callback: (req: Context) => Response | Promise<Response>) => void,
33
+ GET: (pattern: string, callback: (ctx: Context) => Response | Promise<Response>) => void,
34
+ POST: (pattern: string, callback: (ctx: Context) => Response | Promise<Response>) => void,
31
35
  static: (pattern: string, root: string) => void,
32
36
  serve: () => void,
33
37
  }
@@ -1,15 +1,17 @@
1
1
  import { Database } from 'bun:sqlite';
2
2
  import { Route, Router, Context, RouterOptions, Options } from './router.d';
3
+ import { httpStatusCodes } from '../http/status';
3
4
  import { readDir } from '../fs/fsys';
4
5
  import { logger } from '../logger/logger';
5
6
  import path from 'path';
7
+ import { Logger } from '../logger/logger.d';
6
8
 
7
9
  // create a generic HTTP response
8
10
  const httpMessage = async (status: number, msg?: string): Promise<Response> => {
9
11
  const response = new Response(msg ?? '?', {
10
12
  status: status,
11
13
  statusText: msg ?? '?',
12
- headers: {'Content-Type': 'text/html; charset-uft-8'}
14
+ headers: { 'Content-Type': 'text/html; charset-uft-8' }
13
15
  });
14
16
  return new Promise((resolve) => {
15
17
  resolve(response);
@@ -127,7 +129,7 @@ const match = (route: Route, ctx: Context): boolean => {
127
129
  const patternRegex = new RegExp('^' + route.pattern.replace(/:[^/]+/g, '([^/]+)') + '$');
128
130
  const matches = url.pathname.match(patternRegex);
129
131
 
130
- if (matches) {
132
+ if (matches && route.method === ctx.request.method) {
131
133
  const extractor = extract(route, ctx);
132
134
  extractor?.params();
133
135
 
@@ -137,6 +139,15 @@ const match = (route: Route, ctx: Context): boolean => {
137
139
  return false;
138
140
  }
139
141
 
142
+ const setContext = (req: Request, lgr: Logger, opts: Options): Context => {
143
+ return {
144
+ request: req,
145
+ params: new Map(),
146
+ db: new Database(opts.db ?? ':memory:'),
147
+ logger: lgr,
148
+ }
149
+ }
150
+
140
151
 
141
152
 
142
153
  const router: Router = (port?: number | string, options?: RouterOptions<Options>) => {
@@ -153,6 +164,20 @@ const router: Router = (port?: number | string, options?: RouterOptions<Options>
153
164
  callback: callback,
154
165
  })
155
166
  },
167
+ GET: (pattern: string, callback: (ctx: Context) => Response | Promise<Response>) => {
168
+ routes.push({
169
+ pattern: pattern,
170
+ method: 'GET',
171
+ callback: callback,
172
+ });
173
+ },
174
+ POST: (pattern: string, callback: (ctx: Context) => Response | Promise<Response>) => {
175
+ routes.push({
176
+ pattern: pattern,
177
+ method: 'POST',
178
+ callback: callback,
179
+ });
180
+ },
156
181
  // add a route for static files
157
182
  static: async (pattern: string, root: string) => {
158
183
  await readDir(root, async (fp, _) => {
@@ -180,36 +205,54 @@ const router: Router = (port?: number | string, options?: RouterOptions<Options>
180
205
  // start the server
181
206
  serve: () => {
182
207
  lgr.start(port ?? 3000);
183
- let opts: Options = {db: ':memory:'};
208
+ let opts: Options = { db: ':memory:' };
184
209
 
185
210
  Bun.serve({
186
211
  port: port ?? 3000,
187
212
  ...options,
188
213
  async fetch(req) {
189
214
  const url = new URL(req.url);
190
-
191
- //? ????
215
+
192
216
  if (options) {
193
217
  let o = options as Options;
194
218
  opts.db = o.db;
195
219
  }
220
+
221
+ let statusCode = 404; // Default status code for route not found
222
+
196
223
  for (const route of routes) {
197
- const ctx: Context = {
198
- request: req,
199
- params: new Map(),
200
- db: new Database(opts.db ?? ':memory:'),
201
- };
224
+ const ctx = setContext(req, lgr, opts);
225
+
226
+ if (url.pathname === '/favicon.ico') {
227
+ return noContent();
228
+ }
202
229
 
203
- if (url.pathname === '/favicon.ico') return noContent();
230
+ if (route.method !== req.method) {
231
+ statusCode = 405;
232
+ continue;
233
+ }
204
234
 
205
235
  if (match(route, ctx)) {
206
236
  const res = await route.callback(ctx);
207
- lgr.info(res.status, url.pathname, route.method);
208
- return res;
237
+ if (res) {
238
+ statusCode = 200;
239
+ lgr.info(statusCode, url.pathname, req.method, httpStatusCodes[statusCode]);
240
+ return res
241
+ } else {
242
+ statusCode = 500;
243
+ break;
244
+ }
209
245
  }
210
246
  }
211
- lgr.info(404, url.pathname, req.method, 'not found');
212
- return httpMessage(404, 'not found');
247
+
248
+ if (statusCode === 405) {
249
+ lgr.info(statusCode, url.pathname, req.method, httpStatusCodes[statusCode]);
250
+ return httpMessage(statusCode, httpStatusCodes[statusCode]);
251
+ }
252
+
253
+ lgr.info(statusCode, url.pathname, req.method, httpStatusCodes[statusCode]);
254
+ return httpMessage(statusCode, httpStatusCodes[statusCode]);
255
+
213
256
  }
214
257
  });
215
258
  },
package/package.json CHANGED
@@ -8,5 +8,5 @@
8
8
  "peerDependencies": {
9
9
  "typescript": "^5.0.0"
10
10
  },
11
- "version": "0.5.0"
11
+ "version": "0.5.4"
12
12
  }