bun-router 0.5.0 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- package/examples/dynamic.ts +2 -1
- package/lib/http/status.ts +66 -0
- package/lib/logger/logger.d.ts +1 -0
- package/lib/logger/logger.ts +23 -9
- package/lib/router/router.d.ts +4 -0
- package/lib/router/router.ts +58 -15
- package/package.json +1 -1
package/examples/dynamic.ts
CHANGED
@@ -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 }
|
package/lib/logger/logger.d.ts
CHANGED
@@ -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 }
|
package/lib/logger/logger.ts
CHANGED
@@ -15,17 +15,22 @@ const timestamp = (date: Date) => {
|
|
15
15
|
}
|
16
16
|
|
17
17
|
// append ANSI color escape sequences to a string based on the given HTTP status code.
|
18
|
-
const colorCode = (n: number, text?:string): string => {
|
18
|
+
const colorCode = (n: number, text?: string): string => {
|
19
19
|
const s = ` [${String(n)}${text ?? ''}] `;
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
else if (n
|
25
|
-
else if (n
|
26
|
-
|
20
|
+
let backgroundColor = 'bgBlack';
|
21
|
+
let foregroundColor = 'white';
|
22
|
+
|
23
|
+
if (n < 100) backgroundColor = 'bgYellow';
|
24
|
+
else if (n < 200) backgroundColor = 'bgCyan';
|
25
|
+
else if (n < 300) backgroundColor = 'bgGreen';
|
26
|
+
else if (n < 400) backgroundColor = 'bgRed';
|
27
|
+
else if (n < 500) backgroundColor = 'bgRed';
|
28
|
+
else foregroundColor = 'white';
|
29
|
+
|
30
|
+
return color(foregroundColor, backgroundColor, s).trim();
|
27
31
|
}
|
28
32
|
|
33
|
+
|
29
34
|
const clean = (s: string) => s.replace(/\x1B\[\d{1,2}(;\d{1,2}){0,2}m/g, '');
|
30
35
|
|
31
36
|
const format = (statusCode: number, routePath: string, method: string, message?: string): string => {
|
@@ -53,7 +58,7 @@ const logger = (): Logger => {
|
|
53
58
|
const { stamp } = timestamp((new Date(Date.now())));
|
54
59
|
const source = color('green', 'bgBlack', `[bun-router ${stamp}]`);
|
55
60
|
const rp = color('white', 'bgBlack', routePath);
|
56
|
-
const msg = `${source}: ${colorCode(statusCode)}: ${rp} ${(method === 'GET') ? '->' : '<-'} ${method}\n`
|
61
|
+
const msg = `${source}: ${colorCode(statusCode)}: ${rp} ${(method === 'GET') ? '->' : '<-'} ${method}${' | ' +message ?? ''}\n`
|
57
62
|
|
58
63
|
await Bun.write(Bun.stdout, msg);
|
59
64
|
|
@@ -75,6 +80,15 @@ const logger = (): Logger => {
|
|
75
80
|
const msgColor = color('yellow', 'bgBlack', msg);
|
76
81
|
msg = `${source} : ${msgColor}\n`;
|
77
82
|
await Bun.write(Bun.stdout, msg);
|
83
|
+
},
|
84
|
+
message: async (msg: string) => {
|
85
|
+
const { stamp } = timestamp((new Date(Date.now())));
|
86
|
+
const source = color('black', 'bgCyan', `[message ${stamp}]`);
|
87
|
+
const msgColor = color('yellow', 'bgBlack', msg);
|
88
|
+
msg = `${source}: ${msgColor}\n`;
|
89
|
+
await Bun.write(Bun.stdout, msg);
|
90
|
+
|
91
|
+
messages.push(clean(msg));
|
78
92
|
}
|
79
93
|
}
|
80
94
|
}
|
package/lib/router/router.d.ts
CHANGED
@@ -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
|
}
|
package/lib/router/router.ts
CHANGED
@@ -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
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
}
|
224
|
+
const ctx = setContext(req, lgr, opts);
|
225
|
+
|
226
|
+
if (url.pathname === '/favicon.ico') {
|
227
|
+
return noContent();
|
228
|
+
}
|
202
229
|
|
203
|
-
if (
|
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
|
-
|
208
|
-
|
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
|
-
|
212
|
-
|
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