interface RouterContext { url: URL; params: Record; state: Record; pattern: URLPattern; request: Request; } type Handler = (ctx: RouterContext) => Promise | Response; type Middleware = (ctx: RouterContext, next: () => Promise) => Promise; interface RouteConfig { pattern: URLPattern; handlers: { [method: string]: Handler }; } interface MiddlewareConfig { pattern: URLPattern; handler: Middleware; } export class Router { private routes: RouteConfig[] = []; private middleware: MiddlewareConfig[] = []; route(path: string) { path = path.startsWith('/') ? path : `/${path}`; const pattern = new URLPattern({ pathname: path }); const routeConfig: RouteConfig = { pattern, handlers: {} }; this.routes.push(routeConfig); return { get: (handler: Handler) => { routeConfig.handlers['GET'] = handler; return this; }, post: (handler: Handler) => { routeConfig.handlers['POST'] = handler; return this; }, put: (handler: Handler) => { routeConfig.handlers['PUT'] = handler; return this; }, delete: (handler: Handler) => { routeConfig.handlers['DELETE'] = handler; return this; }, patch: (handler: Handler) => { routeConfig.handlers['PATCH'] = handler; return this; }, options: (handler: Handler) => { routeConfig.handlers['OPTIONS'] = handler; return this; } }; } use(pathOrMiddleware: string | Middleware, middlewareOrRouter?: Middleware | Router) { // Handle the case where only middleware is provided if (typeof pathOrMiddleware === 'function') { const pattern = new URLPattern({ pathname: '/*' }); this.middleware.push({ pattern, handler: pathOrMiddleware }); return this; } const path = pathOrMiddleware; const middleware = middlewareOrRouter; if (!middleware) { throw new Error('Middleware or Router is required'); } // Normalize the path const normalizedPath = path.startsWith('/') ? path : `/${path}`; // Only add wildcard if there isn't already a parameter or pattern at the end const wildcardPath = normalizedPath.includes(':') || normalizedPath.includes('*') ? normalizedPath : `${normalizedPath.replace(/\/+$/, '')}/*`; if (middleware instanceof Router) { // Merge the nested router's routes for (const nestedRoute of middleware.routes) { const combinedPath = this.combinePaths(path, nestedRoute.pattern.pathname); const newPattern = new URLPattern({ pathname: combinedPath }); this.routes.push({ pattern: newPattern, handlers: { ...nestedRoute.handlers } }); } // Merge the nested router's middleware for (const nestedMiddleware of middleware.middleware) { const combinedPath = this.combinePaths(path, nestedMiddleware.pattern.pathname); const newPattern = new URLPattern({ pathname: combinedPath }); this.middleware.push({ pattern: newPattern, handler: nestedMiddleware.handler }); } } else { // Handle regular middleware const pattern = new URLPattern({ pathname: wildcardPath }); this.middleware.push({ pattern, handler: middleware }); } return this; } handle = async (req: Request): Promise => { const url = new URL(req.url); const method = req.method; // Find the matching route const matchedRoute = this.findMatchingRoute(url); if (!matchedRoute) { return new Response('Not Found', { status: 404 }); } const handler = matchedRoute.config.handlers[method]; if (!handler) { return new Response('Method Not Allowed', { status: 405 }); } // Get matching middleware with their parameters const matchingMiddleware = this.getMatchingMiddleware(url); // Create the base context object const baseCtx: RouterContext = { url, params: {}, state: {}, pattern: matchedRoute.config.pattern, request: req }; // Combine route parameters with the base context baseCtx.params = { ...baseCtx.params, ...matchedRoute.params }; // Execute middleware chain let index = 0; const executeMiddleware = async (): Promise => { if (index < matchingMiddleware.length) { const { middleware, params } = matchingMiddleware[index++]; // Create a new context for each middleware with its specific parameters const middlewareCtx: RouterContext = { ...baseCtx, params: { ...baseCtx.params, ...params }, pattern: middleware.pattern }; return await middleware.handler(middlewareCtx, executeMiddleware); } // Final handler gets the accumulated parameters return await handler(baseCtx); }; try { return await executeMiddleware(); } catch (error) { console.error('Error handling request:', error); return new Response('Internal Server Error', { status: 500 }); } } private findMatchingRoute(url: URL): { config: RouteConfig; params: Record } | null { for (const route of this.routes) { const result = route.pattern.exec(url); if (result) { return { config: route, params: result.pathname.groups }; } } return null; } private getMatchingMiddleware(url: URL): Array<{ middleware: MiddlewareConfig; params: Record }> { return this.middleware .map(mw => { const result = mw.pattern.exec(url); if (result) { return { middleware: mw, params: result.pathname.groups }; } return null; }) .filter((item): item is { middleware: MiddlewareConfig; params: Record } => item !== null); } private combinePaths(basePath: string, routePath: string): string { const normalizedBase = basePath.replace(/\/+$/, '').replace(/^\/*/, '/'); const normalizedRoute = routePath.replace(/^\/*/, ''); return `${normalizedBase}/${normalizedRoute}`; } } export default Router;