bun-router 0.5.0 → 0.5.4

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