Skip to main content
Module

x/composium/mod.ts

Powered by functional composition and the URL Pattern API, composium has become the most flexible routing framework in the world.
Go to Latest
File
// deno-lint-ignore-file no-unsafe-finally no-cond-assignimport { compose, composeSync } from "./util.ts";import { ConnInfo, Handler, serve, ServeInit, serveTls, ServeTlsInit,} from "./deps.ts";
type CtxHandler<C extends Context> = (ctx: C) => C | Promise<C>;type Method = | "ALL" | "CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE";
/** * The extendable `Context` is passed as only argument to your `CtxHandler`s. * You can optionally extend the default `Context` object. * ```ts * class Ctx extends Context { * url = new URL(this.request.url); * state: any = {}; * } * ``` */export class Context { error: Error | null = null; params: URLPatternResult = {} as URLPatternResult; response: Response = new Response("Not Found", { status: 404 }); constructor(readonly request: Request, readonly connInfo: ConnInfo) { }}
/** * A curried function which takes HTTP `Method`s, a `URLPatternInput` and * `CtxHandler`s and returns in the end a composed route function. * ```ts * createRoute("GET")({ pathname: "*" })(ctxHandler) * ``` */export function createRoute(...methods: Method[]) { return (urlPatternInput: URLPatternInput) => { const urlPattern = new URLPattern(urlPatternInput); return <C extends Context>(...handlers: CtxHandler<C>[]) => async (ctx: C): Promise<C> => { if ( methods.includes("ALL") || methods.includes(ctx.request.method as Method) ) { if (ctx.params = urlPattern.exec(ctx.request.url)!) { return await (compose<C | Promise<C>>(...handlers))(ctx); } } return ctx; }; };}
/** * A curried function which takes `Context` class, `mainHandlers`, `catchHandlers` * and `finallyHandlers` and returns in the end a `Handler` which can be passed * to `listen`. * ```ts * createHandler(Ctx)(routeGet)(catchHandler)(finallyHandler) * ``` */export function createHandler<C extends Context>( contextClass: new (request: Request, connInfo: ConnInfo) => C,) { return (...mainHandler: CtxHandler<C>[]) => (...catchHandler: CtxHandler<C>[]) => (...finallyHandler: CtxHandler<C>[]) => async (request: Request, connInfo: ConnInfo): Promise<Response> => { const ctx = new contextClass(request, connInfo); try { await (compose(...mainHandler)(ctx)); } catch (caught) { if (caught instanceof Response) { ctx.response = caught; } else { ctx.error = caught instanceof Error ? caught : new Error("[non-error thrown]"); await (compose(...catchHandler)(ctx)); } } finally { await (compose(...finallyHandler)(ctx)); return ctx.response; } };}
/** * A curried function which constructs a server, creates a listener on the given * address, accepts incoming connections, upgrades them to TLS, and handles * requests. * ```ts * await app.listen({ port: 8080 })(handler) * ``` */export function listen(options: ServeInit | ServeTlsInit) { return async (handler: Handler) => { return "certFile" in options || "keyFile" in options ? await serveTls(handler, options) : await serve(handler, options); };}
export { compose, composeSync };