init
This commit is contained in:
parent
d3041d789d
commit
7368533e3a
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"deno.enable": true
|
||||||
|
}
|
12
deno.json
Normal file
12
deno.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "BearMetal Router",
|
||||||
|
"description": "A simple router for Deno",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"stable": true,
|
||||||
|
"repository": "https://github.com/emmaos/bearmetal",
|
||||||
|
"files": [
|
||||||
|
"mod.ts",
|
||||||
|
"README.md",
|
||||||
|
"LICENSE"
|
||||||
|
]
|
||||||
|
}
|
222
mod.ts
Normal file
222
mod.ts
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
interface RouterContext {
|
||||||
|
url: URL;
|
||||||
|
params: Record<string, string | undefined>;
|
||||||
|
state: Record<string, unknown>;
|
||||||
|
pattern: URLPattern;
|
||||||
|
request: Request;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handler = (ctx: RouterContext) => Promise<Response> | Response;
|
||||||
|
type Middleware = (ctx: RouterContext, next: () => Promise<Response>) => Promise<Response>;
|
||||||
|
|
||||||
|
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<Response> => {
|
||||||
|
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<Response> => {
|
||||||
|
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<string, string | undefined> } | 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<string, string>
|
||||||
|
}> {
|
||||||
|
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<string, string>
|
||||||
|
} => item !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private combinePaths(basePath: string, routePath: string): string {
|
||||||
|
const normalizedBase = basePath.replace(/\/+$/, '').replace(/^\/*/, '/');
|
||||||
|
const normalizedRoute = routePath.replace(/^\/*/, '');
|
||||||
|
return `${normalizedBase}/${normalizedRoute}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Router;
|
Loading…
x
Reference in New Issue
Block a user