Created
November 25, 2022 03:13
-
-
Save arisris/eb5ad3f97d883a6d93a8c5c4b34d4d93 to your computer and use it in GitHub Desktop.
Simple Worker Router
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface ICtx { | |
readonly req: Request; | |
readonly params: Record<string, any>; | |
readonly env: Record<string, any>; | |
readonly executionContext: Record<string, any>; | |
} | |
type TExecResult = { | |
groups: Record<string, string>; | |
}; | |
type THandler = ( | |
ctx: ICtx | |
) => Response | void | Promise<Response> | Promise<void>; | |
type TRouter = { | |
readonly routes: [ | |
string[], // methods | |
(p: string) => TExecResult | undefined, // executor | |
THandler[] // handlers | |
][]; | |
map(pathname: string, ...handlers: THandler[]): TRouter; | |
fetch( | |
req: Request, | |
env?: Record<string, any>, | |
excecutionContext?: any | |
): Response | Promise<Response>; | |
}; | |
export function createRouter(base = "/") { | |
const routes: TRouter["routes"] = [], | |
map = | |
(receiver: any): TRouter["map"] => | |
(pathname, ...handlers) => { | |
let path = pathname.trim(), | |
methods = ["*"]; | |
if (pathname.indexOf(" ") !== -1) { | |
const [a, b] = pathname.split(" ", 2).map((i) => i.trim()); | |
(path = b), | |
(methods = a.split("|").map((x) => x.trim().toUpperCase())); | |
} | |
path = path.startsWith(base) ? path : base + path; | |
const pattern = new URLPattern({ pathname: path }), | |
exec = (p: string) => pattern.exec({ pathname: p })?.pathname; | |
routes.push([methods, exec, handlers]); | |
return receiver; | |
}, | |
handler: TRouter["fetch"] = async ( | |
req, | |
env = {}, | |
executionContext = {} | |
): Promise<Response> => { | |
let { pathname } = new URL(req.url), | |
index = 0, | |
patternResult: TExecResult | undefined, | |
res: ReturnType<THandler>; | |
while (index < routes.length) { | |
const [methods, exec, handlers] = routes[index]; | |
if ( | |
(methods.includes(req.method) || methods.includes("*")) && | |
(patternResult = exec(pathname)) | |
) { | |
for (let x of handlers) { | |
try { | |
res = await x({ | |
req, | |
params: patternResult?.groups, | |
env, | |
executionContext, | |
}); | |
if (x instanceof Response) break; | |
} catch (e) { | |
if (e instanceof Error) { | |
res = new Response(`Error: ${e.message}`, { status: 500 }); | |
break; | |
} | |
} | |
} | |
} | |
index++; | |
} | |
return res instanceof Response | |
? res | |
: new Response("Not Found", { status: 404 }); | |
}, | |
get = (obj: Record<any, any>, prop: string, receiver: any) => | |
prop in obj ? obj[prop] : prop === "map" ? map(receiver) : receiver; | |
return new Proxy( | |
{ fetch: handler, routes: [...routes] }, | |
{ get } | |
) as never as TRouter; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment