ufiber
Version:
Next-gen webserver for node-js developer
160 lines (158 loc) • 6.65 kB
JavaScript
const require_consts = require('../../consts.cjs');
const require_url = require('../../utils/url.cjs');
const require_error = require('../error.cjs');
const require_node = require('./node.cjs');
//#region src/router/reg-exp/index.ts
const nullMatcher = [
/^$/,
[],
Object.create(null)
];
const emptyParam = [];
let wildcardRegExpCache = Object.create(null);
const buildWildcardRegExp = (path) => {
return wildcardRegExpCache[path] ??= /* @__PURE__ */ new RegExp(path === "*" ? "" : `^${path.replace(/\/\*$|([.\\+*[^\]$()])/g, (_, metaChar) => metaChar ? `\\${metaChar}` : "(?:|/.*)")}$`);
};
const clearWildcardRegExpCache = () => {
wildcardRegExpCache = Object.create(null);
};
/**
* 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 require_node.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 === require_node.PATH_ERROR ? new require_error.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 = { [require_consts.METHOD_NAME_ALL]: Object.create(null) };
this.#routes = { [require_consts.METHOD_NAME_ALL]: Object.create(null) };
}
add(method, path, handler) {
const middleware = this.#middleware;
const routes = this.#routes;
if (!middleware || !routes) throw new Error(require_consts.MESSAGE_MATCHER_IS_ALREADY_BUILT);
if (!middleware[method]) [middleware, routes].forEach((handlerMap) => {
handlerMap[method] = Object.create(null);
Object.keys(handlerMap[require_consts.METHOD_NAME_ALL]).forEach((p) => {
handlerMap[method][p] = [...handlerMap[require_consts.METHOD_NAME_ALL][p]];
});
});
if (path === "/*") path = "*";
const paramCount = (path.match(/\/:/g) || []).length;
if (/\*$/.test(path)) {
const re = buildWildcardRegExp(path);
if (method === require_consts.METHOD_NAME_ALL) Object.keys(middleware).forEach((m) => {
middleware[m][path] ||= findMiddleware(middleware[m], path) || findMiddleware(middleware[require_consts.METHOD_NAME_ALL], path) || [];
});
else middleware[method][path] ||= findMiddleware(middleware[method], path) || findMiddleware(middleware[require_consts.METHOD_NAME_ALL], path) || [];
Object.keys(middleware).forEach((m) => {
if (method === require_consts.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 === require_consts.METHOD_NAME_ALL || method === m) Object.keys(routes[m]).forEach((p) => re.test(p) && routes[m][p].push([handler, paramCount]));
});
return;
}
const paths = require_url.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 === require_consts.METHOD_NAME_ALL || method === m) {
routes[m][path$1] ||= [...findMiddleware(middleware[m], path$1) || findMiddleware(middleware[require_consts.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[require_consts.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 === require_consts.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 !== require_consts.METHOD_NAME_ALL) routes.push(...Object.keys(r[require_consts.METHOD_NAME_ALL]).map((path) => [path, r[require_consts.METHOD_NAME_ALL][path]]));
});
if (!hasOwnRoute) return null;
else return buildMatcherFromPreprocessedRoutes(routes);
}
};
//#endregion
exports.RegExpRouter = RegExpRouter;