exstack
Version:
A utility library designed to simplify and enhance Express.js applications.
153 lines (151 loc) • 6.05 kB
JavaScript
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 };