Skip to content

Instantly share code, notes, and snippets.

@arisris
Created November 25, 2022 03:13
Show Gist options
  • Save arisris/eb5ad3f97d883a6d93a8c5c4b34d4d93 to your computer and use it in GitHub Desktop.
Save arisris/eb5ad3f97d883a6d93a8c5c4b34d4d93 to your computer and use it in GitHub Desktop.
Simple Worker Router
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