bun-router 0.7.3 → 0.7.4-experimental.11
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/.eslintrc.json +35 -0
- package/README.md +28 -0
- package/bun.lockb +0 -0
- package/examples/basic.ts +4 -4
- package/examples/dynamic.ts +8 -8
- package/examples/logger.ts +2 -2
- package/examples/ssr/index.ts +8 -0
- package/examples/ssr/pages/foo.tsx +7 -0
- package/examples/ssr/pages/home.tsx +7 -0
- package/examples/static.ts +1 -0
- package/examples/todo.ts +22 -22
- package/examples/tsx/components/user.tsx +7 -0
- package/examples/tsx/index.ts +20 -0
- package/lib/fs/filetree.ts +75 -0
- package/lib/fs/fsys.ts +25 -11
- package/lib/http/http.ts +81 -67
- package/lib/http/status.ts +64 -64
- package/lib/logger/color.ts +25 -25
- package/lib/logger/logger.d.ts +1 -1
- package/lib/logger/logger.ts +86 -70
- package/lib/router/context.ts +50 -37
- package/lib/router/routeTree.ts +86 -0
- package/lib/router/router.d.ts +6 -4
- package/lib/router/router.ts +122 -89
- package/package.json +13 -2
- package/tests/router.test.ts +21 -20
- package/tsconfig.json +5 -3
- package/examples/db.ts +0 -10
- package/lib/router/tree.ts +0 -63
- package/lib/util/strings.ts +0 -3
    
        package/lib/http/status.ts
    CHANGED
    
    | @@ -1,66 +1,66 @@ | |
| 1 1 | 
             
            const httpStatusCodes: { [key: number]: string } = {
         | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 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 65 |  | 
| 66 | 
            -
             | 
| 66 | 
            +
            export { httpStatusCodes };
         | 
    
        package/lib/logger/color.ts
    CHANGED
    
    | @@ -1,36 +1,36 @@ | |
| 1 1 | 
             
            const Colors: Record<string,string> = {
         | 
| 2 | 
            -
             | 
| 2 | 
            +
            	reset: '\x1b[0m',
         | 
| 3 3 |  | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 4 | 
            +
            	// foreground
         | 
| 5 | 
            +
            	black: '\x1b[30m',
         | 
| 6 | 
            +
            	red: '\x1b[31m',
         | 
| 7 | 
            +
            	green: '\x1b[32m',
         | 
| 8 | 
            +
            	yellow: '\x1b[33m',
         | 
| 9 | 
            +
            	blue: '\x1b[34m',
         | 
| 10 | 
            +
            	magenta: '\x1b[35m',
         | 
| 11 | 
            +
            	cyan: '\x1b[36m',
         | 
| 12 | 
            +
            	white: '\x1b[37m',
         | 
| 13 13 |  | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 14 | 
            +
            	// background
         | 
| 15 | 
            +
            	bgBlack: '\x1b[40m',
         | 
| 16 | 
            +
            	bgRed: '\x1b[41m',
         | 
| 17 | 
            +
            	bgGreen: '\x1b[42m',
         | 
| 18 | 
            +
            	bgYellow: '\x1b[43m',
         | 
| 19 | 
            +
            	bgBlue: '\x1b[44m',
         | 
| 20 | 
            +
            	bgMagenta: '\x1b[45m',
         | 
| 21 | 
            +
            	bgCyan: '\x1b[46m',
         | 
| 22 | 
            +
            	bgWhite: '\x1b[47m',
         | 
| 23 | 
            +
            } as const;
         | 
| 24 24 |  | 
| 25 25 |  | 
| 26 26 |  | 
| 27 27 | 
             
            function color(foreground: string, background: string, message: string) {
         | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 28 | 
            +
            	const _foreground = Colors[foreground];
         | 
| 29 | 
            +
            	const _background = Colors[background];
         | 
| 30 | 
            +
            	const reset = Colors.reset;
         | 
| 31 | 
            +
            	return `${_foreground}${_background}${message}${reset}`;
         | 
| 32 32 | 
             
            }
         | 
| 33 33 |  | 
| 34 34 |  | 
| 35 35 |  | 
| 36 | 
            -
            export { color }
         | 
| 36 | 
            +
            export { color };
         | 
    
        package/lib/logger/logger.d.ts
    CHANGED
    
    
    
        package/lib/logger/logger.ts
    CHANGED
    
    | @@ -1,5 +1,4 @@ | |
| 1 1 | 
             
            import { color } from './color';
         | 
| 2 | 
            -
            import { BunLogger } from './logger.d';
         | 
| 3 2 |  | 
| 4 3 |  | 
| 5 4 | 
             
            const TITLE = `
         | 
| @@ -8,87 +7,104 @@ _                          _ | |
| 8 7 | 
             
            | . | | |   |  |  _| . | | |  _| -_|  _|
         | 
| 9 8 | 
             
            |___|___|_|_|  |_| |___|___|_| |___|_|  
         | 
| 10 9 |  | 
| 11 | 
            -
             | 
| 12 | 
            -
            const VERSION = '0.7. | 
| 13 | 
            -
            const Logger = ( | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 10 | 
            +
            `;
         | 
| 11 | 
            +
            const VERSION = '0.7.4-experimental.11';
         | 
| 12 | 
            +
            const Logger = (enableFileLogging: boolean) => {
         | 
| 13 | 
            +
            	const file = Bun.file('bun-router.log');
         | 
| 14 | 
            +
            	const writer = enableFileLogging ? file.writer() : null;
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            	function stripAnsi(str: string) {
         | 
| 17 | 
            +
            		const ansiRegex = /\u001B\[(?:[0-9]{1,3}(?:;[0-9]{1,3})*)?[m|K]/g;
         | 
| 18 | 
            +
            		return str.replace(ansiRegex, '');
         | 
| 19 | 
            +
            	}
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            	async function write(message: string) {
         | 
| 22 | 
            +
            		await Bun.write(Bun.stdout, message);
         | 
| 23 | 
            +
            		if (writer) {
         | 
| 24 | 
            +
            			writer.write(stripAnsi(message));
         | 
| 25 | 
            +
            			writer.flush();
         | 
| 26 | 
            +
            		}
         | 
| 27 | 
            +
            	}
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            	return {
         | 
| 30 | 
            +
            		info: async (statusCode: number, routePath: string, method: string, message?: string) => {
         | 
| 31 | 
            +
            			const { stamp } = timestamp((new Date(Date.now())));
         | 
| 32 | 
            +
            			const source = color('green', 'bgBlack', `[bun-router ${stamp}]`);
         | 
| 33 | 
            +
            			const rp = color('white', 'bgBlack', routePath);
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            			message = `${source}: ${setColor(statusCode)}: ${rp} ${(method === 'GET') ? ' ->' : ' <-'} ${method} ${ message ?? ''}\n`;
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            			await write(message);
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            		},
         | 
| 40 | 
            +
            		error: async (statusCode: number, routePath: string, method: string, error: Error) => {
         | 
| 41 | 
            +
            			const { stamp } = timestamp((new Date(Date.now())));
         | 
| 42 | 
            +
            			const source = color('black', 'bgRed', `[error ${stamp}]`);
         | 
| 43 | 
            +
            			const rp = color('white', 'bgBlack', routePath);
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            			const message = `${source}: ${setColor(statusCode)}: ${rp} ${(method === 'GET') ? ' -> ' : ' <-'} ${error.message}\n`;
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            			await write(message);
         | 
| 48 | 
            +
            		},
         | 
| 49 | 
            +
            		warn: async (message: string) => {
         | 
| 50 | 
            +
            			const { stamp } = timestamp((new Date(Date.now())));
         | 
| 51 | 
            +
            			const source = color('black', 'bgYellow', `[warning ${stamp}]`);
         | 
| 52 | 
            +
            			const messageColor = color('yellow', 'bgBlack', message);
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            			message = `${source} : ${messageColor}\n`;
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            			await write(message);
         | 
| 57 | 
            +
            		},
         | 
| 58 | 
            +
            		message: async (message: string) => {
         | 
| 59 | 
            +
            			const { stamp } = timestamp((new Date(Date.now())));
         | 
| 60 | 
            +
            			const source = color('black', 'bgCyan', `[message ${stamp}]`);
         | 
| 61 | 
            +
            			const messageColor = color('yellow', 'bgBlack', message);
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            			message = `${source}: ${messageColor}\n`;
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            			await write(message);
         | 
| 66 | 
            +
            		},
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            	};
         | 
| 69 | 
            +
            };
         | 
| 54 70 |  | 
| 55 71 | 
             
            function timestamp(date: Date) {
         | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 72 | 
            +
            	const month = pad(date.getMonth());
         | 
| 73 | 
            +
            	const day = pad(date.getDate());
         | 
| 74 | 
            +
            	const hour = pad(date.getHours());
         | 
| 75 | 
            +
            	const minute = pad(date.getMinutes());
         | 
| 76 | 
            +
            	const seconds = pad(date.getSeconds());
         | 
| 77 | 
            +
            	const stamp = `${hour}:${minute}:${seconds}`;
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            	return {month, day, hour, minute, stamp};
         | 
| 64 80 | 
             
            }
         | 
| 65 81 |  | 
| 66 82 | 
             
            function setColor(n: number, text?: string){
         | 
| 67 | 
            -
             | 
| 83 | 
            +
            	const s = ` [${String(n)}${text ?? ''}] `;
         | 
| 68 84 |  | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 85 | 
            +
            	if (n < 100) return color('black', 'bgYellow', s);
         | 
| 86 | 
            +
            	else if (n >= 100 && n < 200) return color('black', 'bgCyan', s);
         | 
| 87 | 
            +
            	else if (n >= 200 && n < 300) return color('black', 'bgGreen', s);
         | 
| 88 | 
            +
            	else if (n >= 300 && n < 400) return color('black', 'bgRed', s);
         | 
| 89 | 
            +
            	else if (n >= 400 && n < 500) return color('black', 'bgRed', s);
         | 
| 90 | 
            +
            	else if (n >= 500) return color('white', 'bgRed', s);
         | 
| 75 91 |  | 
| 76 | 
            -
             | 
| 92 | 
            +
            	return color('white', 'bgBlack', `[${s}]`).trim();
         | 
| 77 93 | 
             
            }
         | 
| 78 94 |  | 
| 79 95 | 
             
            function startMessage(port: number | string) {
         | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 96 | 
            +
            	const { stamp } = timestamp((new Date(Date.now())));
         | 
| 97 | 
            +
            	const source = color('green', 'bgBlack', `[bun-router ${stamp}]`);
         | 
| 98 | 
            +
            	const portColor = color('green', 'bgBlack', String(port));
         | 
| 99 | 
            +
            	const msg = `${source}: Starting Server on :${portColor}\n`;
         | 
| 100 | 
            +
            	const version = color('red', 'bgBlack', `v${VERSION}\n`);
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            	Bun.write(Bun.stdout, TITLE + '\n' + version);
         | 
| 103 | 
            +
            	Bun.write(Bun.stdout, msg);
         | 
| 88 104 | 
             
            }
         | 
| 89 105 |  | 
| 90 106 | 
             
            function pad(n: number) {
         | 
| 91 | 
            -
             | 
| 107 | 
            +
            	return String(n).padStart(2, '0');
         | 
| 92 108 | 
             
            }
         | 
| 93 109 |  | 
| 94 | 
            -
            export { Logger, startMessage }
         | 
| 110 | 
            +
            export { Logger, startMessage };
         | 
    
        package/lib/router/context.ts
    CHANGED
    
    | @@ -1,51 +1,64 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            import {  | 
| 3 | 
            -
            import {  | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 1 | 
            +
            /* eslint-disable  @typescript-eslint/no-explicit-any */
         | 
| 2 | 
            +
            import { Route, Context } from './router.d';
         | 
| 3 | 
            +
            import { renderToReadableStream } from 'react-dom/server';
         | 
| 4 | 
            +
            import { Logger } from '../logger/logger';
         | 
| 5 | 
            +
            import { http } from './router';
         | 
| 6 | 
            +
            import { ReactNode } from 'react';
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            // createContext creates a context object
         | 
| 9 | 
            +
            async function createContext(path: string, route: Route, request: Request, enableFileLogging: boolean): Promise<Context> {
         | 
| 10 | 
            +
            	const query = new URLSearchParams(path);
         | 
| 11 | 
            +
            	const params = extractParams(path, route);
         | 
| 12 | 
            +
            	const formData = isMultiPartForm(request.headers) ? await request.formData() : new FormData();
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            	return Promise.resolve({
         | 
| 15 | 
            +
            		params,
         | 
| 16 | 
            +
            		request,
         | 
| 17 | 
            +
            		query,
         | 
| 18 | 
            +
            		formData,
         | 
| 19 | 
            +
            		logger: Logger(enableFileLogging),
         | 
| 20 | 
            +
            		json: (statusCode: number, data: any) => http.json(statusCode, data),
         | 
| 21 | 
            +
            		render: async (component: ReactNode) => await renderStream(component),
         | 
| 22 | 
            +
            	});
         | 
| 18 23 | 
             
            }
         | 
| 19 24 |  | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 25 | 
            +
            // extractParams extracts the parameters from the path
         | 
| 26 | 
            +
            // and returns a map of key/value pairs
         | 
| 27 | 
            +
            // e.g. /users/:id => /users/123 => { id: 123 }
         | 
| 28 | 
            +
            function extractParams(pattern: string, route: Route): Map<string, string> {
         | 
| 29 | 
            +
            	const params: Map<string, string> = new Map();
         | 
| 30 | 
            +
            	const pathSegments = pattern.split('/');
         | 
| 31 | 
            +
            	const routeSegments = route.path.split('/');
         | 
| 24 32 |  | 
| 25 | 
            -
             | 
| 33 | 
            +
            	if (pathSegments.length !== routeSegments.length) return params;
         | 
| 26 34 |  | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 35 | 
            +
            	for (let i = 0; i < pathSegments.length; i++) {
         | 
| 36 | 
            +
            		if (routeSegments[i][0] === ':') {
         | 
| 37 | 
            +
            			const key = routeSegments[i].slice(1);
         | 
| 38 | 
            +
            			const value = pathSegments[i];
         | 
| 39 | 
            +
            			params.set(key, value);
         | 
| 40 | 
            +
            		}
         | 
| 41 | 
            +
            	}
         | 
| 34 42 |  | 
| 35 | 
            -
             | 
| 43 | 
            +
            	return params;
         | 
| 36 44 | 
             
            }
         | 
| 37 45 |  | 
| 46 | 
            +
            // getContentType returns the content type from the headers
         | 
| 38 47 | 
             
            function getContentType(headers: Headers): string {
         | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
                return contentType;
         | 
| 48 | 
            +
            	const contentType = headers.get('Content-Type');
         | 
| 49 | 
            +
            	return contentType ?? '';
         | 
| 42 50 | 
             
            }
         | 
| 43 51 |  | 
| 52 | 
            +
            // isMultiPartForm returns true if the content type is multipart/form-data
         | 
| 44 53 | 
             
            function isMultiPartForm(headers: Headers): boolean {
         | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 54 | 
            +
            	const contentType = getContentType(headers);
         | 
| 55 | 
            +
            	return contentType.includes('multipart/form-data');
         | 
| 47 56 | 
             
            }
         | 
| 48 57 |  | 
| 58 | 
            +
            // renderStream renders the component to a readable stream
         | 
| 59 | 
            +
            async function renderStream(children: ReactNode) {
         | 
| 60 | 
            +
            	const stream = await renderToReadableStream(children);
         | 
| 61 | 
            +
            	return new Response(stream, { headers: { 'Content-Type': 'text/html' } });
         | 
| 62 | 
            +
            }
         | 
| 49 63 |  | 
| 50 | 
            -
             | 
| 51 | 
            -
            export { createContext }
         | 
| 64 | 
            +
            export { createContext };
         | 
| @@ -0,0 +1,86 @@ | |
| 1 | 
            +
            import { HttpHandler, Route } from './router.d';
         | 
| 2 | 
            +
            import { http } from '../http/http';
         | 
| 3 | 
            +
            import { createContext } from './context';
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            const createRoute = (path: string, method: string, handler: HttpHandler): Route => {
         | 
| 6 | 
            +
            	const route: Route = {
         | 
| 7 | 
            +
            		children: new Map(),
         | 
| 8 | 
            +
            		path: path,
         | 
| 9 | 
            +
            		dynamicPath: '',
         | 
| 10 | 
            +
            		method: method,
         | 
| 11 | 
            +
            		handler: handler,
         | 
| 12 | 
            +
            		isLast: false
         | 
| 13 | 
            +
            	};
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            	return route;
         | 
| 16 | 
            +
            };
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            const RouteTree = () => {
         | 
| 19 | 
            +
            	const root = createRoute('', 'GET', () => http.notFound());
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            	function addRoute (pattern: string, method: string, handler: HttpHandler){
         | 
| 22 | 
            +
            		console.log(pattern);
         | 
| 23 | 
            +
            		const pathParts = pattern.split('/');
         | 
| 24 | 
            +
            		let current = root;
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            		for (let i = 0; i < pathParts.length; i++) {
         | 
| 27 | 
            +
            			const part = pathParts[i];
         | 
| 28 | 
            +
            			if (part.startsWith(':')) {
         | 
| 29 | 
            +
            				current.dynamicPath = part;
         | 
| 30 | 
            +
            			}
         | 
| 31 | 
            +
            			if (!current.children.has(part)) {
         | 
| 32 | 
            +
            				current.children.set(part, createRoute(part, method, handler));
         | 
| 33 | 
            +
            			}
         | 
| 34 | 
            +
            			current = current.children.get(part)!;
         | 
| 35 | 
            +
            		}
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            		current.handler = handler;
         | 
| 38 | 
            +
            		current.isLast = true;
         | 
| 39 | 
            +
            		current.path = pattern;
         | 
| 40 | 
            +
            	}
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            	function findRoute(pathname: string): Route | undefined {
         | 
| 43 | 
            +
            		const pathParts = pathname.split('/');
         | 
| 44 | 
            +
            		let current = root;
         | 
| 45 | 
            +
            		for (let i = 0; i < pathParts.length; i++) {
         | 
| 46 | 
            +
            			const part = pathParts[i];
         | 
| 47 | 
            +
            			if (current.children.has(part)) {
         | 
| 48 | 
            +
            				current = current.children.get(part)!;
         | 
| 49 | 
            +
            			} else if (current.dynamicPath) {
         | 
| 50 | 
            +
            				current = current.children.get(current.dynamicPath)!;
         | 
| 51 | 
            +
            			} else {
         | 
| 52 | 
            +
            				return;
         | 
| 53 | 
            +
            			}
         | 
| 54 | 
            +
            		}
         | 
| 55 | 
            +
            		return current;
         | 
| 56 | 
            +
            	}
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            	function size() {
         | 
| 59 | 
            +
            		let count = 0;
         | 
| 60 | 
            +
            		function traverse(route: Route) {
         | 
| 61 | 
            +
            			count++;
         | 
| 62 | 
            +
            			for (const child of route.children.values()) {
         | 
| 63 | 
            +
            				traverse(child);
         | 
| 64 | 
            +
            			}
         | 
| 65 | 
            +
            		}
         | 
| 66 | 
            +
            		traverse(root);
         | 
| 67 | 
            +
            		return count;
         | 
| 68 | 
            +
            	}
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            	function list() {
         | 
| 71 | 
            +
            		const routes: Route[] = [];
         | 
| 72 | 
            +
            		function traverse(route: Route) {
         | 
| 73 | 
            +
            			routes.push(route);
         | 
| 74 | 
            +
            			for (const child of route.children.values()) {
         | 
| 75 | 
            +
            				traverse(child);
         | 
| 76 | 
            +
            			}
         | 
| 77 | 
            +
            		}
         | 
| 78 | 
            +
            		traverse(root);
         | 
| 79 | 
            +
            		return routes;
         | 
| 80 | 
            +
            	}
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            	return { addRoute, findRoute, size, list };
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            };
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            export { RouteTree, createContext };
         | 
    
        package/lib/router/router.d.ts
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            /* eslint-disable  @typescript-eslint/no-explicit-any */
         | 
| 2 | 
            +
            import { TLSWebSocketServeOptions, WebSocketServeOptions, ServeOptions, TLSServeOptions } from 'bun';
         | 
| 2 3 | 
             
            import { Logger } from '../logger/logger';
         | 
| 3 4 | 
             
            import { Database } from 'bun:sqlite';
         | 
| 4 5 |  | 
| @@ -20,7 +21,6 @@ type Route = { | |
| 20 21 | 
             
                handler: HttpHandler;
         | 
| 21 22 | 
             
                isLast: boolean;
         | 
| 22 23 | 
             
            }
         | 
| 23 | 
            -
             | 
| 24 24 | 
             
            type Context = {
         | 
| 25 25 | 
             
                db?: Database;
         | 
| 26 26 | 
             
                formData: FormData | Promise<FormData>;
         | 
| @@ -29,12 +29,14 @@ type Context = { | |
| 29 29 | 
             
                params: Map<string, string>;
         | 
| 30 30 | 
             
                query: URLSearchParams;
         | 
| 31 31 | 
             
                request: Request;
         | 
| 32 | 
            +
                render: (component: React.ReactNode) => Response | Promise<Response>;
         | 
| 32 33 | 
             
            };
         | 
| 33 34 |  | 
| 34 35 | 
             
            type HttpHandler = (ctx: Context) => Response | Promise<Response>
         | 
| 35 36 |  | 
| 36 37 | 
             
            type Options = {
         | 
| 37 | 
            -
                db: string | 
| 38 | 
            +
                db: string;
         | 
| 39 | 
            +
                enableFileLogging: boolean;
         | 
| 38 40 | 
             
            }
         | 
| 39 41 |  | 
| 40 42 | 
             
            type RouterOptions<Options> = ServeOptions
         | 
| @@ -43,4 +45,4 @@ type RouterOptions<Options> = ServeOptions | |
| 43 45 | 
             
            | TLSWebSocketServeOptions<Options>
         | 
| 44 46 | 
             
            | undefined
         | 
| 45 47 |  | 
| 46 | 
            -
            export { Context , Route, BunRouter, RouterOptions, Options, HttpHandler }
         | 
| 48 | 
            +
            export { Context , Route, BunRouter, RouterOptions, Options, HttpHandler };
         |