Skip to main content
Module

x/oak/router.ts

A middleware framework for handling HTTP with Deno 🐿️ 🦕
Extremely Popular
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929
/** * Adapted directly from @koa/router at * https://github.com/koajs/router/ which is licensed as: * * The MIT License (MIT) * * Copyright (c) 2015 Alexander C. Mingoia * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */
import { State } from "./application.ts";import { Context } from "./context.ts";import { assert, compile, Key, ParseOptions, pathParse, pathToRegexp, Status, TokensToRegexpOptions,} from "./deps.ts";import { httpErrors } from "./httpError.ts";import { Middleware, compose } from "./middleware.ts";import { HTTPMethods, RedirectStatus } from "./types.d.ts";import { decodeComponent } from "./util.ts";
interface Matches { path: Layer[]; pathAndMethod: Layer[]; route: boolean;}
export interface RouterAllowedMethodsOptions { /** Use the value returned from this function instead of an HTTP error * `MethodNotAllowed`. */ methodNotAllowed?(): any;
/** Use the value returned from this function instead of an HTTP error * `NotImplemented`. */ notImplemented?(): any;
/** When dealing with a non-implemented method or a method not allowed, throw * an error instead of setting the status and header for the response. */ throw?: boolean;}
export interface Route< P extends RouteParams = RouteParams, S extends State = Record<string, any>,> { /** The HTTP methods that this route handles. */ methods: HTTPMethods[];
/** The middleware that will be applied to this route. */ middleware: RouterMiddleware<P, S>[];
/** An optional name for the route. */ name?: string;
/** Options that were used to create the route. */ options: LayerOptions;
/** The parameters that are identified in the route that will be parsed out * on matched requests. */ paramNames: (keyof P)[];
/** The path that this route manages. */ path: string;
/** The regular expression used for matching and parsing parameters for the * route. */ regexp: RegExp;}
/** The context passed router middleware. */export interface RouterContext< P extends RouteParams = RouteParams, S extends State = Record<string, any>,> extends Context<S> { /** When matching the route, an array of the capturing groups from the regular * expression. */ captures: string[];
/** The routes that were matched for this request. */ matched?: Layer<P, S>[];
/** Any parameters parsed from the route when matched. */ params: P;
/** A reference to the router instance. */ router: Router;
/** If the matched route has a `name`, the matched route name is provided * here. */ routeName?: string;
/** Overrides the matched path for future route middleware, when a * `routerPath` option is not defined on the `Router` options. */ routerPath?: string;}
export interface RouterMiddleware< P extends RouteParams = RouteParams, S extends State = Record<string, any>,> { (context: RouterContext<P, S>, next: () => Promise<void>): | Promise<void> | void; /** For route parameter middleware, the `param` key for this parameter will * be set. */ param?: keyof P;}
export interface RouterOptions { /** Override the default set of methods supported by the router. */ methods?: HTTPMethods[];
/** Only handle routes where the requested path starts with the prefix. */ prefix?: string;
/** Override the `request.url.pathname` when matching middleware to run. */ routerPath?: string;
/** Determines if routes are matched in a case sensitive way. Defaults to * `false`. */ sensitive?: boolean;
/** Determines if routes are matched strictly, where the trailing `/` is not * optional. Defaults to `false`. */ strict?: boolean;}
/** Middleware that will be called by the router when handling a specific * parameter, which the middleware will be called when a request matches the * route parameter. */export interface RouterParamMiddleware< P extends RouteParams = RouteParams, S extends State = Record<string, any>,> { ( param: string, context: RouterContext<P, S>, next: () => Promise<void>, ): Promise<void> | void;}
export type RouteParams = Record<string | number, string | undefined>;
type LayerOptions = TokensToRegexpOptions & ParseOptions & { ignoreCaptures?: boolean; name?: string;};
type UrlOptions = TokensToRegexpOptions & ParseOptions & { /** When generating a URL from a route, add the query to the URL. If an * object */ query?: URLSearchParams | Record<string, string> | string;};
/** Generate a URL from a string, potentially replace route params with * values. */function toUrl(url: string, params: RouteParams = {}, options?: UrlOptions) { const tokens = pathParse(url); let replace: RouteParams = {};
if (tokens.some((token) => typeof token === "object")) { replace = params; } else { options = params; }
const toPath = compile(url, options); let replaced = toPath(replace);
if (options && options.query) { const url = new URL(replaced, "http://oak"); if (typeof options.query === "string") { url.search = options.query; } else { url.search = String( options.query instanceof URLSearchParams ? options.query : new URLSearchParams(options.query), ); } return `${url.pathname}${url.search}${url.hash}`; } return replaced;}
class Layer< P extends RouteParams = RouteParams, S extends State = Record<string, any>,> { #opts: LayerOptions; #paramNames: Key[] = []; #regexp: RegExp;
methods: HTTPMethods[]; name?: string; path: string; stack: RouterMiddleware<P, S>[];
constructor( path: string, methods: HTTPMethods[], middleware: RouterMiddleware<P, S> | RouterMiddleware<P, S>[], { name, ...opts }: LayerOptions = {}, ) { this.#opts = opts; this.name = name; this.methods = [...methods]; if (this.methods.includes("GET")) { this.methods.unshift("HEAD"); } this.stack = Array.isArray(middleware) ? middleware : [middleware]; this.path = path; this.#regexp = pathToRegexp(path, this.#paramNames, this.#opts); }
match(path: string): boolean { return this.#regexp.test(path); }
params( captures: string[], existingParams: RouteParams = {}, ): RouteParams { const params = existingParams; for (let i = 0; i < captures.length; i++) { if (this.#paramNames[i]) { const c = captures[i]; params[this.#paramNames[i].name] = c ? decodeComponent(c) : c; } } return params; }
captures(path: string): string[] { if (this.#opts.ignoreCaptures) { return []; } return path.match(this.#regexp)?.slice(1) ?? []; }
url( params: RouteParams = {}, options?: UrlOptions, ): string { const url = this.path.replace(/\(\.\*\)/g, ""); return toUrl(url, params, options); }
param( param: string, fn: RouterParamMiddleware<any, any>, ) { const stack = this.stack; const params = this.#paramNames; const middleware: RouterMiddleware = function ( this: Router, ctx, next, ): Promise<void> | void { const p = ctx.params[param]; assert(p); return fn.call(this, p, ctx, next); }; middleware.param = param;
const names = params.map((p) => p.name);
const x = names.indexOf(param); if (x >= 0) { for (let i = 0; i < stack.length; i++) { const fn = stack[i]; if (!fn.param || names.indexOf(fn.param as (string | number)) > x) { stack.splice(i, 0, middleware); break; } } } return this; }
setPrefix(prefix: string): this { if (this.path) { this.path = this.path !== "/" || this.#opts.strict === true ? `${prefix}${this.path}` : prefix; this.#paramNames = []; this.#regexp = pathToRegexp(this.path, this.#paramNames, this.#opts); } return this; }
toJSON(): Route<any, any> { return { methods: [...this.methods], middleware: [...this.stack], paramNames: this.#paramNames.map((key) => key.name), path: this.path, regexp: this.#regexp, options: { ...this.#opts }, }; }}
/** An interface for registering middleware that will run when certain HTTP * methods and paths are requested, as well as provides a way to parameterize * parts of the requested path. */export class Router< RP extends RouteParams = RouteParams, RS extends State = Record<string, any>,> { #opts: RouterOptions; #methods: HTTPMethods[]; #params: Record<string, RouterParamMiddleware<any, any>> = {}; #stack: Layer[] = [];
#match = (path: string, method: HTTPMethods): Matches => { const matches: Matches = { path: [], pathAndMethod: [], route: false, };
for (const route of this.#stack) { if (route.match(path)) { matches.path.push(route); if (route.methods.length === 0 || route.methods.includes(method)) { matches.pathAndMethod.push(route); if (route.methods.length) { matches.route = true; } } } }
return matches; };
#register = ( path: string | string[], middleware: RouterMiddleware[], methods: HTTPMethods[], options: LayerOptions = {}, ): void => { if (Array.isArray(path)) { for (const p of path) { this.#register(p, middleware, methods, options); } return; }
const { end, name, sensitive, strict, ignoreCaptures } = options; const route = new Layer(path, methods, middleware, { end: end === false ? end : true, name, sensitive: sensitive ?? this.#opts.sensitive ?? false, strict: strict ?? this.#opts.strict ?? false, ignoreCaptures, });
if (this.#opts.prefix) { route.setPrefix(this.#opts.prefix); }
for (const [param, mw] of Object.entries(this.#params)) { route.param(param, mw); }
this.#stack.push(route); };
#route = (name: string): Layer | undefined => { for (const route of this.#stack) { if (route.name === name) { return route; } } };
#useVerb = ( nameOrPath: string, pathOrMiddleware: string | RouterMiddleware, middleware: RouterMiddleware[], methods: HTTPMethods[], ): void => { let name: string | undefined = undefined; let path: string; if (typeof pathOrMiddleware === "string") { name = nameOrPath; path = pathOrMiddleware; } else { path = nameOrPath; middleware.unshift(pathOrMiddleware); }
this.#register(path, middleware, methods, { name }); };
constructor(opts: RouterOptions = {}) { this.#opts = opts; this.#methods = opts.methods ?? [ "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", ]; }
/** Register named middleware for the specified routes when the `DELETE`, * `GET`, `POST`, or `PUT` method is requested. */ all<P extends RouteParams = RP, S extends State = RS>( name: string, path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; /** Register middleware for the specified routes when the `DELETE`, * `GET`, `POST`, or `PUT` method is requested. */ all<P extends RouteParams = RP, S extends State = RS>( path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; all<P extends RouteParams = RP, S extends State = RS>( nameOrPath: string, pathOrMiddleware: string | RouterMiddleware<P, S>, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)> { this.#useVerb( nameOrPath, pathOrMiddleware as (string | RouterMiddleware), middleware as RouterMiddleware[], ["DELETE", "GET", "POST", "PUT"], ); return this as Router<any, any>; }
/** Middleware that handles requests for HTTP methods registered with the * router. If none of the routes handle a method, then "not allowed" logic * will be used. If a method is supported by some routes, but not the * particular matched router, then "not implemented" will be returned. * * The middleware will also automatically handle the `OPTIONS` method, * responding with a `200 OK` when the `Allowed` header sent to the allowed * methods for a given route. * * By default, a "not allowed" request will respond with a `405 Not Allowed` * and a "not implemented" will respond with a `501 Not Implemented`. Setting * the option `.throw` to `true` will cause the middleware to throw an * `HTTPError` instead of setting the response status. The error can be * overridden by providing a `.notImplemented` or `.notAllowed` method in the * options, of which the value will be returned will be thrown instead of the * HTTP error. */ allowedMethods( options: RouterAllowedMethodsOptions = {}, ): Middleware { const implemented = this.#methods;
const allowedMethods: Middleware = async (context, next) => { const ctx = context as RouterContext; await next(); if (!ctx.response.status || ctx.response.status === Status.NotFound) { assert(ctx.matched); const allowed = new Set<HTTPMethods>(); for (const route of ctx.matched) { for (const method of route.methods) { allowed.add(method); } }
const allowedStr = [...allowed].join(", "); if (!implemented.includes(ctx.request.method)) { if (options.throw) { throw options.notImplemented ? options.notImplemented() : new httpErrors.NotImplemented(); } else { ctx.response.status = Status.NotImplemented; ctx.response.headers.set("Allowed", allowedStr); } } else if (allowed.size) { if (ctx.request.method === "OPTIONS") { ctx.response.status = Status.OK; ctx.response.headers.set("Allowed", allowedStr); } else if (!allowed.has(ctx.request.method)) { if (options.throw) { throw options.methodNotAllowed ? options.methodNotAllowed() : new httpErrors.MethodNotAllowed(); } else { ctx.response.status = Status.MethodNotAllowed; ctx.response.headers.set("Allowed", allowedStr); } } } } };
return allowedMethods; }
/** Register named middleware for the specified routes when the `DELETE`, * method is requested. */ delete<P extends RouteParams = RP, S extends State = RS>( name: string, path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; /** Register middleware for the specified routes when the `DELETE`, * method is requested. */ delete<P extends RouteParams = RP, S extends State = RS>( path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; delete<P extends RouteParams = RP, S extends State = RS>( nameOrPath: string, pathOrMiddleware: string | RouterMiddleware<P, S>, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)> { this.#useVerb( nameOrPath, pathOrMiddleware as (string | RouterMiddleware), middleware as RouterMiddleware[], ["DELETE"], ); return this as Router<any, any>; }
/** Iterate over the routes currently added to the router. To be compatible * with the iterable interfaces, both the key and value are set to the value * of the route. */ *entries(): IterableIterator<[Route, Route]> { for (const route of this.#stack) { const value = route.toJSON(); yield [value, value]; } }
/** Iterate over the routes currently added to the router, calling the * `callback` function for each value. */ forEach( callback: (value1: Route, value2: Route, router: this) => void, thisArg: any = null, ): void { for (const route of this.#stack) { const value = route.toJSON(); callback.call(thisArg, value, value, this); } }
/** Register named middleware for the specified routes when the `GET`, * method is requested. */ get<P extends RouteParams = RP, S extends State = RS>( name: string, path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; /** Register middleware for the specified routes when the `GET`, * method is requested. */ get<P extends RouteParams = RP, S extends State = RS>( path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; get<P extends RouteParams = RP, S extends State = RS>( nameOrPath: string, pathOrMiddleware: string | RouterMiddleware<P, S>, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)> { this.#useVerb( nameOrPath, pathOrMiddleware as (string | RouterMiddleware), middleware as RouterMiddleware[], ["GET"], ); return this as Router<any, any>; }
/** Register named middleware for the specified routes when the `HEAD`, * method is requested. */ head<P extends RouteParams = RP, S extends State = RS>( name: string, path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; /** Register middleware for the specified routes when the `HEAD`, * method is requested. */ head<P extends RouteParams = RP, S extends State = RS>( path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; head<P extends RouteParams = RP, S extends State = RS>( nameOrPath: string, pathOrMiddleware: string | RouterMiddleware<P, S>, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)> { this.#useVerb( nameOrPath, pathOrMiddleware as (string | RouterMiddleware), middleware as RouterMiddleware[], ["HEAD"], ); return this as Router<any, any>; }
/** Iterate over the routes currently added to the router. To be compatible * with the iterable interfaces, the key is set to the value of the route. */ *keys(): IterableIterator<Route> { for (const route of this.#stack) { yield route.toJSON(); } }
/** Register named middleware for the specified routes when the `OPTIONS`, * method is requested. */ options<P extends RouteParams = RP, S extends State = RS>( name: string, path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; /** Register middleware for the specified routes when the `OPTIONS`, * method is requested. */ options<P extends RouteParams = RP, S extends State = RS>( path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; options<P extends RouteParams = RP, S extends State = RS>( nameOrPath: string, pathOrMiddleware: string | RouterMiddleware<P, S>, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)> { this.#useVerb( nameOrPath, pathOrMiddleware as (string | RouterMiddleware), middleware as RouterMiddleware[], ["OPTIONS"], ); return this as Router<any, any>; }
/** Register param middleware, which will be called when the particular param * is parsed from the route. */ param<S extends State = RS>( param: keyof RP, middleware: RouterParamMiddleware<RP, S>, ): Router<RP, S> { this.#params[param as string] = middleware; for (const route of this.#stack) { route.param(param as string, middleware); } return this; }
/** Register named middleware for the specified routes when the `PATCH`, * method is requested. */ patch<P extends RouteParams = RP, S extends State = RS>( name: string, path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; /** Register middleware for the specified routes when the `PATCH`, * method is requested. */ patch<P extends RouteParams = RP, S extends State = RS>( path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; patch<P extends RouteParams = RP, S extends State = RS>( nameOrPath: string, pathOrMiddleware: string | RouterMiddleware<P, S>, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)> { this.#useVerb( nameOrPath, pathOrMiddleware as (string | RouterMiddleware), middleware as RouterMiddleware[], ["PATCH"], ); return this as Router<any, any>; }
/** Register named middleware for the specified routes when the `POST`, * method is requested. */ post<P extends RouteParams = RP, S extends State = RS>( name: string, path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; /** Register middleware for the specified routes when the `POST`, * method is requested. */ post<P extends RouteParams = RP, S extends State = RS>( path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; post<P extends RouteParams = RP, S extends State = RS>( nameOrPath: string, pathOrMiddleware: string | RouterMiddleware<P, S>, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)> { this.#useVerb( nameOrPath, pathOrMiddleware as (string | RouterMiddleware), middleware as RouterMiddleware[], ["POST"], ); return this as Router<any, any>; }
/** Set the router prefix for this router. */ prefix(prefix: string): this { prefix = prefix.replace(/\/$/, ""); this.#opts.prefix = prefix; for (const route of this.#stack) { route.setPrefix(prefix); } return this; }
/** Register named middleware for the specified routes when the `PUT` * method is requested. */ put<P extends RouteParams = RP, S extends State = RS>( name: string, path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; /** Register middleware for the specified routes when the `PUT` * method is requested. */ put<P extends RouteParams = RP, S extends State = RS>( path: string, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; put<P extends RouteParams = RP, S extends State = RS>( nameOrPath: string, pathOrMiddleware: string | RouterMiddleware<P, S>, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)> { this.#useVerb( nameOrPath, pathOrMiddleware as (string | RouterMiddleware), middleware as RouterMiddleware[], ["PUT"], ); return this as Router<any, any>; }
/** Register a direction middleware, where when the `source` path is matched * the router will redirect the request to the `destination` path. A `status` * of `302 Found` will be set by default. * * The `source` and `destination` can be named routes. */ redirect( source: string, destination: string, status: RedirectStatus = Status.Found, ): this { if (source[0] !== "/") { const s = this.url(source); if (!s) { throw new RangeError(`Could not resolve named route: "${source}"`); } source = s; } if (destination[0] !== "/") { const d = this.url(destination); if (!d) { throw new RangeError(`Could not resolve named route: "${source}"`); } destination = d; }
this.all(source, (ctx) => { ctx.response.redirect(destination); ctx.response.status = status; }); return this; }
/** Return middleware that will do all the route processing that the router * has been configured to handle. Typical usage would be something like this: * * ```ts * import { Application, Router } from "https://deno.land/x/oak/mod.ts"; * * const app = new Application(); * const router = new Router(); * * // register routes * * app.use(router.routes()); * app.use(router.allowedMethods()); * await app.listen({ port: 80 }); * ``` */ routes(): Middleware { const dispatch = ( context: Context, next: () => Promise<void>, ): Promise<void> => { const ctx = context as RouterContext; const { url: { pathname }, method } = ctx.request; const path = this.#opts.routerPath ?? ctx.routerPath ?? decodeURIComponent(pathname); const matches = this.#match(path, method);
if (ctx.matched) { ctx.matched.push(...matches.path); } else { ctx.matched = [...matches.path]; }
ctx.router = this as Router<any, any>;
if (!matches.route) return next();
const { pathAndMethod: matchedRoutes } = matches;
const chain = matchedRoutes.reduce( (prev, route) => [ ...prev, (ctx: RouterContext, next: () => Promise<void>): Promise<void> => { ctx.captures = route.captures(path); ctx.params = route.params(ctx.captures, ctx.params); ctx.routeName = route.name; return next(); }, ...route.stack, ], [] as RouterMiddleware[], ); return compose(chain)(ctx, next); }; dispatch.router = this; return dispatch; }
/** Generate a URL pathname for a named route, interpolating the optional * params provided. Also accepts an optional set of options. */ url<P extends RouteParams = RP>( name: string, params?: P, options?: UrlOptions, ): string | undefined { const route = this.#route(name);
if (route) { return route.url(params, options); } }
/** Register middleware to be used on every matched route. */ use<P extends RouteParams = RP, S extends State = RS>( ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; /** Register middleware to be used on every route that matches the supplied * `path`. */ use<P extends RouteParams = RP, S extends State = RS>( path: string | string[], ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)>; use<P extends RouteParams = RP, S extends State = RS>( pathOrMiddleware: string | string[] | RouterMiddleware<P, S>, ...middleware: RouterMiddleware<P, S>[] ): Router<P extends RP ? P : (P & RP), S extends RS ? S : (S & RS)> { let path: string | string[] | undefined; if ( typeof pathOrMiddleware === "string" || Array.isArray(pathOrMiddleware) ) { path = pathOrMiddleware; } else { middleware.unshift(pathOrMiddleware); }
this.#register( path ?? "(.*)", middleware as RouterMiddleware[], [], { end: false, ignoreCaptures: !path }, );
return this as Router<any, any>; }
/** Iterate over the routes currently added to the router. */ *values(): IterableIterator<Route<RP, RS>> { for (const route of this.#stack) { yield route.toJSON(); } }
/** Provide an iterator interface that iterates over the routes registered * with the router. */ *[Symbol.iterator](): IterableIterator<Route<RP, RS>> { for (const route of this.#stack) { yield route.toJSON(); } }
/** Generate a URL pathname based on the provided path, interpolating the * optional params provided. Also accepts an optional set of options. */ static url( path: string, params?: RouteParams, options?: UrlOptions, ): string { return toUrl(path, params, options); }}