UNPKG

exstack

Version:

A utility library designed to simplify and enhance Express.js applications.

153 lines (151 loc) 6.05 kB
import { buildWildcardRegExp, checkOptionalParameter, clearWildcardRegExpCache } from "../utils.js"; import { MESSAGE_MATCHER_IS_ALREADY_BUILT, UnsupportedPathError } from "../errors.js"; import { METHOD_NAME_ALL } from "../../types.js"; import { PATH_ERROR, Trie } from "./node.js"; //#region src/router/reg-exp/index.ts const nullMatcher = [ /^$/, [], Object.create(null) ]; const emptyParam = []; /** * Utility function: find middleware handlers that match a given path. * Checks for longest matching wildcard pattern (e.g. `/api/*`). */ const findMiddleware = (middleware, path) => { if (!middleware) return void 0; for (const key of Object.keys(middleware).sort((a, b) => b.length - a.length)) if (buildWildcardRegExp(key).test(path)) return [...middleware[key]]; }; /** * Build matcher from preprocessed route data. * This is where trie-based RegExp generation happens. */ const buildMatcherFromPreprocessedRoutes = (routes) => { const trie = new Trie(); const handlerData = []; if (routes.length === 0) return nullMatcher; const routesWithStaticPathFlag = routes.map((route) => [!/\*|\/:/.test(route[0]), ...route]).sort(([isStaticA, pathA], [isStaticB, pathB]) => isStaticA ? 1 : isStaticB ? -1 : pathA.length - pathB.length); const staticMap = Object.create(null); for (let i = 0, j = -1, len = routesWithStaticPathFlag.length; i < len; i++) { const [pathErrorCheckOnly, path, handlers] = routesWithStaticPathFlag[i]; if (pathErrorCheckOnly) staticMap[path] = [handlers.map(([h]) => [h, Object.create(null)]), emptyParam]; else j++; let paramAssoc; try { paramAssoc = trie.insert(path, j, pathErrorCheckOnly); } catch (e) { throw e === PATH_ERROR ? new UnsupportedPathError(path) : e; } if (pathErrorCheckOnly) continue; handlerData[j] = handlers.map(([h, paramCount]) => { const paramIndexMap = Object.create(null); paramCount -= 1; for (; paramCount >= 0; paramCount--) { const [key, value] = paramAssoc[paramCount]; paramIndexMap[key] = value; } return [h, paramIndexMap]; }); } const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp(); for (let i = 0, len = handlerData.length; i < len; i++) for (let j = 0, len$1 = handlerData[i].length; j < len$1; j++) { const map = handlerData[i][j]?.[1]; if (!map) continue; const keys = Object.keys(map); for (let k = 0, len$2 = keys.length; k < len$2; k++) map[keys[k]] = paramReplacementMap[map[keys[k]]]; } const handlerMap = []; for (const i in indexReplacementMap) handlerMap[i] = handlerData[indexReplacementMap[i]]; return [ regexp, handlerMap, staticMap ]; }; var RegExpRouter = class { name = "RegExpRouter"; #middleware; #routes; constructor() { this.#middleware = { [METHOD_NAME_ALL]: Object.create(null) }; this.#routes = { [METHOD_NAME_ALL]: Object.create(null) }; } add(method, path, handler) { const middleware = this.#middleware; const routes = this.#routes; if (!middleware || !routes) throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT); if (!middleware[method]) [middleware, routes].forEach((handlerMap) => { handlerMap[method] = Object.create(null); Object.keys(handlerMap[METHOD_NAME_ALL]).forEach((p) => { handlerMap[method][p] = [...handlerMap[METHOD_NAME_ALL][p]]; }); }); if (path === "/*") path = "*"; const paramCount = (path.match(/\/:/g) || []).length; if (/\*$/.test(path)) { const re = buildWildcardRegExp(path); if (method === METHOD_NAME_ALL) Object.keys(middleware).forEach((m) => { middleware[m][path] ||= findMiddleware(middleware[m], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || []; }); else middleware[method][path] ||= findMiddleware(middleware[method], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || []; Object.keys(middleware).forEach((m) => { if (method === METHOD_NAME_ALL || method === m) Object.keys(middleware[m]).forEach((p) => { re.test(p) && middleware[m][p].push([handler, paramCount]); }); }); Object.keys(routes).forEach((m) => { if (method === METHOD_NAME_ALL || method === m) Object.keys(routes[m]).forEach((p) => re.test(p) && routes[m][p].push([handler, paramCount])); }); return; } const paths = checkOptionalParameter(path) || [path]; for (let i = 0, len = paths.length; i < len; i++) { const path$1 = paths[i]; Object.keys(routes).forEach((m) => { if (method === METHOD_NAME_ALL || method === m) { routes[m][path$1] ||= [...findMiddleware(middleware[m], path$1) || findMiddleware(middleware[METHOD_NAME_ALL], path$1) || []]; routes[m][path$1].push([handler, paramCount - len + i + 1]); } }); } } match(method, path) { const matchers = this.#buildAllMatchers(); const match = ((method$1, path$1) => { const matcher = matchers[method$1] || matchers[METHOD_NAME_ALL]; const staticMatch = matcher[2][path$1]; if (staticMatch) return staticMatch; const m = path$1.match(matcher[0]); if (!m) return [[], emptyParam]; const index = m.indexOf("", 1); return [matcher[1][index], m]; }); this.match = match; return match(method, path); } #buildAllMatchers() { const matchers = Object.create(null); Object.keys(this.#routes).concat(Object.keys(this.#middleware)).forEach((method) => { matchers[method] ||= this.#buildMatcher(method); }); this.#middleware = this.#routes = void 0; clearWildcardRegExpCache(); return matchers; } #buildMatcher(method) { const routes = []; let hasOwnRoute = method === METHOD_NAME_ALL; [this.#middleware, this.#routes].forEach((r) => { const ownRoute = r[method] ? Object.keys(r[method]).map((path) => [path, r[method][path]]) : []; if (ownRoute.length !== 0) { hasOwnRoute ||= true; routes.push(...ownRoute); } else if (method !== METHOD_NAME_ALL) routes.push(...Object.keys(r[METHOD_NAME_ALL]).map((path) => [path, r[METHOD_NAME_ALL][path]])); }); if (!hasOwnRoute) return null; else return buildMatcherFromPreprocessedRoutes(routes); } }; //#endregion export { RegExpRouter };