UNPKG

next-connect

Version:

The method routing and middleware layer for Next.js (and many others)

117 lines (116 loc) 4.34 kB
/** * Agnostic router class * Adapted from lukeed/trouter library: * https://github.com/lukeed/trouter/blob/master/index.mjs */ import { parse } from "regexparam"; export class Router { constructor(base = "/", routes = []) { this.base = base; this.routes = routes; } add(method, route, ...fns) { if (typeof route === "function") { fns.unshift(route); route = ""; } if (route === "") this.routes.push({ matchAll: true, method, fns, isMiddle: false }); else { const { keys, pattern } = parse(route); this.routes.push({ keys, pattern, method, fns, isMiddle: false }); } return this; } use(base, ...fns) { if (typeof base === "function" || base instanceof Router) { fns.unshift(base); base = "/"; } // mount subrouter fns = fns.map((fn) => { if (fn instanceof Router) { if (typeof base === "string") return fn.clone(base); throw new Error("Mounting a router to RegExp base is not supported"); } return fn; }); const { keys, pattern } = parse(base, true); this.routes.push({ keys, pattern, method: "", fns, isMiddle: true }); return this; } clone(base) { return new Router(base, Array.from(this.routes)); } static async exec(fns, ...args) { let i = 0; const next = () => fns[++i](...args, next); return fns[i](...args, next); } find(method, pathname) { let middleOnly = true; const fns = []; const params = {}; const isHead = method === "HEAD"; for (const route of this.routes) { if (route.method !== method && // matches any method route.method !== "" && // The HEAD method requests that the target resource transfer a representation of its state, as for a GET request... !(isHead && route.method === "GET")) { continue; } let matched = false; if ("matchAll" in route) { matched = true; } else { if (route.keys === false) { // routes.key is RegExp: https://github.com/lukeed/regexparam/blob/master/src/index.js#L2 const matches = route.pattern.exec(pathname); if (matches === null) continue; if (matches.groups !== void 0) for (const k in matches.groups) params[k] = matches.groups[k]; matched = true; } else if (route.keys.length > 0) { const matches = route.pattern.exec(pathname); if (matches === null) continue; for (let j = 0; j < route.keys.length;) params[route.keys[j]] = matches[++j]; matched = true; } else if (route.pattern.test(pathname)) { matched = true; } // else not a match } if (matched) { fns.push(...route.fns .map((fn) => { if (fn instanceof Router) { const base = fn.base; let stripPathname = pathname.substring(base.length); // fix stripped pathname, not sure why this happens if (stripPathname[0] != "/") stripPathname = `/${stripPathname}`; const result = fn.find(method, stripPathname); if (!result.middleOnly) middleOnly = false; // merge params Object.assign(params, result.params); return result.fns; } return fn; }) .flat()); if (!route.isMiddle) middleOnly = false; } } return { fns, params, middleOnly }; } }