1021 lines
32 KiB
JavaScript
1021 lines
32 KiB
JavaScript
const NullProtoObj = /* @__PURE__ */ (() => {
|
||
const e = function() {};
|
||
return e.prototype = Object.create(null), Object.freeze(e.prototype), e;
|
||
})();
|
||
function createRouter() {
|
||
return {
|
||
root: { key: "" },
|
||
static: new NullProtoObj()
|
||
};
|
||
}
|
||
function scanFirstGroup(path) {
|
||
let i = 0;
|
||
let depth = 0;
|
||
for (; i < path.length; i++) {
|
||
const c = path.charCodeAt(i);
|
||
if (c === 92) i++;
|
||
else if (c === 40) depth++;
|
||
else if (c === 41 && depth > 0) depth--;
|
||
else if (c === 123 && depth === 0) break;
|
||
}
|
||
if (i >= path.length) return;
|
||
let j = i + 1;
|
||
depth = 0;
|
||
for (; j < path.length; j++) {
|
||
const c = path.charCodeAt(j);
|
||
if (c === 92) j++;
|
||
else if (c === 40) depth++;
|
||
else if (c === 41 && depth > 0) depth--;
|
||
else if (c === 125 && depth === 0) break;
|
||
}
|
||
if (j >= path.length) return;
|
||
const mod = path[j + 1];
|
||
const hasMod = mod === "?" || mod === "+" || mod === "*";
|
||
return [
|
||
path.slice(0, i),
|
||
path.slice(i + 1, j),
|
||
path.slice(j + (hasMod ? 2 : 1)),
|
||
hasMod ? mod : void 0
|
||
];
|
||
}
|
||
function expandGroupDelimiters(path) {
|
||
if (!path.includes("{")) return;
|
||
const group = scanFirstGroup(path);
|
||
if (!group) return;
|
||
const [pre, body, suf, mod] = group;
|
||
if (!mod) return [pre + body + suf];
|
||
if (mod === "?") return [pre + body + suf, pre + suf];
|
||
if (body.includes("/")) throw new Error("unsupported group repetition across segments");
|
||
return [`${pre}(?:${body})${mod}${suf}`];
|
||
}
|
||
const UNNAMED_GROUP_PREFIX = "__rou3_unnamed_";
|
||
const ESCAPED_GROUP_PREFIX = "__rou3_esc_";
|
||
function toUnnamedGroupKey(index) {
|
||
return `${UNNAMED_GROUP_PREFIX}${index}`;
|
||
}
|
||
function toGroupName(name) {
|
||
return /^(?!__rou3_|_\d)[A-Za-z_]\w*$/.test(name) ? name : ESCAPED_GROUP_PREFIX + name.replace(/[_-]/g, (c) => c === "_" ? "__" : "_h");
|
||
}
|
||
function fromGroupName(key) {
|
||
if (key.charCodeAt(0) !== 95) return key;
|
||
if (key.startsWith("__rou3_esc_")) return key.slice(11).replace(/__|_h/g, (c) => c === "__" ? "_" : "-");
|
||
return key.startsWith("__rou3_unnamed_") ? key.slice(15) : key;
|
||
}
|
||
function hasSegmentWildcard(segment) {
|
||
let depth = 0;
|
||
for (let i = 0; i < segment.length; i++) {
|
||
const ch = segment.charCodeAt(i);
|
||
if (ch === 92) {
|
||
i++;
|
||
continue;
|
||
}
|
||
if (ch === 40) {
|
||
depth++;
|
||
continue;
|
||
}
|
||
if (ch === 41 && depth > 0) {
|
||
depth--;
|
||
continue;
|
||
}
|
||
if (ch === 42 && depth === 0) return true;
|
||
}
|
||
return false;
|
||
}
|
||
function replaceSegmentWildcards(segment, unnamedStart, toGroupKey = toUnnamedGroupKey) {
|
||
let depth = 0;
|
||
let nextIndex = unnamedStart;
|
||
let replaced = "";
|
||
for (let i = 0; i < segment.length; i++) {
|
||
const ch = segment.charCodeAt(i);
|
||
if (ch === 92) {
|
||
replaced += segment[i];
|
||
if (i + 1 < segment.length) replaced += segment[++i];
|
||
continue;
|
||
}
|
||
if (ch === 40) {
|
||
depth++;
|
||
replaced += segment[i];
|
||
continue;
|
||
}
|
||
if (ch === 41 && depth > 0) {
|
||
depth--;
|
||
replaced += segment[i];
|
||
continue;
|
||
}
|
||
if (ch === 42 && depth === 0) {
|
||
replaced += `(?<${toGroupKey(nextIndex++)}>[^/]*)`;
|
||
continue;
|
||
}
|
||
replaced += segment[i];
|
||
}
|
||
return [replaced, nextIndex];
|
||
}
|
||
function encodeEscapes(path) {
|
||
if (!path.includes("\\")) return path;
|
||
return path.replace(/\\([:(){}])/g, (_, c) => "<22>" + "ABCDE"[":(){}".indexOf(c)]);
|
||
}
|
||
function segmentKey(segment) {
|
||
if (segment.startsWith("**")) return 2;
|
||
if (segment === "*" || segment.includes(":") || segment.includes("(") || hasSegmentWildcard(segment)) return 1;
|
||
if (segment === "\\*") return "*";
|
||
if (segment === "\\*\\*") return "**";
|
||
if (!segment.includes("<22>")) return segment;
|
||
return segment.replace(/\uFFFD([A-E])/g, (_, c) => c === "A" ? ":" : c === "B" ? "(" : c === "C" ? ")" : c === "D" ? "{" : "}");
|
||
}
|
||
function expandModifiers(segments) {
|
||
for (let i = 0; i < segments.length; i++) {
|
||
const last = segments[i].charCodeAt(segments[i].length - 1);
|
||
if (last !== 63 && last !== 43 && last !== 42) continue;
|
||
const m = segments[i].match(/^(.*:[\w-]+(?:\([^)]*\))?)([?+*])$/);
|
||
if (!m) continue;
|
||
const pre = segments.slice(0, i);
|
||
const suf = segments.slice(i + 1);
|
||
if (m[2] === "?") return ["/" + pre.concat(m[1]).concat(suf).join("/"), "/" + pre.concat(suf).join("/")];
|
||
const name = m[1].match(/:([\w-]+)/)?.[1] || "_";
|
||
const wc = "/" + [
|
||
...pre,
|
||
`**:${name}`,
|
||
...suf
|
||
].join("/");
|
||
const without = "/" + [...pre, ...suf].join("/");
|
||
return m[2] === "+" ? [wc] : [wc, without];
|
||
}
|
||
}
|
||
function normalizePath(path) {
|
||
if (!path.includes("/.")) return path;
|
||
const r = [];
|
||
for (const s of path.split("/")) if (s === ".") continue;
|
||
else if (s === ".." && r.length > 1) r.pop();
|
||
else r.push(s);
|
||
return r.join("/") || "/";
|
||
}
|
||
function splitPath(path) {
|
||
const s = path.split("/");
|
||
s.shift();
|
||
if (s[s.length - 1] === "") s.pop();
|
||
return s;
|
||
}
|
||
function splitRoute(path) {
|
||
const s = splitPath(path);
|
||
while (s[s.length - 1] === "") s.pop();
|
||
return s;
|
||
}
|
||
function getMatchParams(segments, paramsMap) {
|
||
const params = new NullProtoObj();
|
||
for (const [index, name] of paramsMap) {
|
||
const segment = index < 0 ? segments.slice(-(index + 1)).join("/") : segments[index];
|
||
if (typeof name === "string") params[name] = segment;
|
||
else {
|
||
const match = segment.match(name);
|
||
if (match) for (const key in match.groups) params[fromGroupName(key)] = match.groups[key];
|
||
}
|
||
}
|
||
return params;
|
||
}
|
||
function addRoute(ctx, method = "", path, data) {
|
||
method = method.toUpperCase();
|
||
if (path.charCodeAt(0) !== 47) path = `/${path}`;
|
||
const groupExpanded = expandGroupDelimiters(path);
|
||
if (groupExpanded) {
|
||
for (const expandedPath of groupExpanded) addRoute(ctx, method, expandedPath, data);
|
||
return;
|
||
}
|
||
path = encodeEscapes(path);
|
||
const segments = splitRoute(path);
|
||
const expanded = expandModifiers(segments);
|
||
if (expanded) {
|
||
for (const p of expanded) addRoute(ctx, method, p, data);
|
||
return;
|
||
}
|
||
let node = ctx.root;
|
||
let _unnamedParamIndex = 0;
|
||
const paramsMap = [];
|
||
const paramsRegexp = [];
|
||
for (let i = 0; i < segments.length; i++) {
|
||
let segment = segments[i];
|
||
const key = segmentKey(segment);
|
||
if (key === 2) {
|
||
if (!node.wildcard) node.wildcard = { key: "**" };
|
||
node = node.wildcard;
|
||
paramsMap.push([
|
||
-(i + 1),
|
||
segment.split(":")[1] || "_",
|
||
segment.length === 2
|
||
]);
|
||
break;
|
||
}
|
||
if (key === 1) {
|
||
if (!node.param) node.param = { key: "*" };
|
||
node = node.param;
|
||
if (segment === "*") paramsMap.push([
|
||
i,
|
||
String(_unnamedParamIndex++),
|
||
true
|
||
]);
|
||
else if (segment.includes("(") || segment.includes(":", 1) || !/^:[\w-]+$/.test(segment)) {
|
||
const [regexp, nextIndex] = getParamRegexp(segment, _unnamedParamIndex);
|
||
_unnamedParamIndex = nextIndex;
|
||
paramsRegexp[i] = regexp;
|
||
node.hasRegexParam = true;
|
||
paramsMap.push([
|
||
i,
|
||
regexp,
|
||
false
|
||
]);
|
||
} else paramsMap.push([
|
||
i,
|
||
segment.slice(1),
|
||
false
|
||
]);
|
||
continue;
|
||
}
|
||
segment = segments[i] = key;
|
||
const child = node.static?.[segment];
|
||
if (child) node = child;
|
||
else {
|
||
const staticNode = { key: segment };
|
||
if (!node.static) node.static = new NullProtoObj();
|
||
node.static[segment] = staticNode;
|
||
node = staticNode;
|
||
}
|
||
}
|
||
const hasParams = paramsMap.length > 0;
|
||
const methods = node.methods ??= new NullProtoObj();
|
||
(methods[method] ??= []).push({
|
||
data: data || null,
|
||
paramsRegexp,
|
||
paramsMap: hasParams ? paramsMap : void 0
|
||
});
|
||
if (!hasParams) ctx.static["/" + segments.join("/")] = node;
|
||
}
|
||
function getParamRegexp(segment, unnamedStart = 0) {
|
||
let _i = unnamedStart;
|
||
let _s = "", _d = 0;
|
||
for (let j = 0; j < segment.length; j++) {
|
||
const c = segment.charCodeAt(j);
|
||
if (c === 40) _d++;
|
||
else if (c === 41 && _d > 0) _d--;
|
||
else if (c === 92 && _d === 0 && j + 1 < segment.length) {
|
||
const n = segment[j + 1];
|
||
if (n !== ":" && n !== "(" && n !== "*" && n !== "\\") {
|
||
_s += "" + n;
|
||
j++;
|
||
continue;
|
||
}
|
||
} else if (c === 46 && _d === 0) {
|
||
_s += "\\.";
|
||
continue;
|
||
}
|
||
_s += segment[j];
|
||
}
|
||
[_s, _i] = replaceSegmentWildcards(_s, _i);
|
||
const regex = _s.replace(/:([\w-]+)(?:\(([^)]*)\))?/g, (_, id, p) => `(?<${toGroupName(id)}>${p || "[^/]+"})`).replace(/\((?![?<])/g, () => `(?<${toUnnamedGroupKey(_i++)}>`).replace(/\uFFFE(.)/g, (_, c) => /[.*+?^${}()|[\]\\]/.test(c) ? `\\${c}` : c);
|
||
return [new RegExp(`^${regex}$`), _i];
|
||
}
|
||
function findRoute(ctx, method = "", path, opts) {
|
||
if (opts?.normalize) path = normalizePath(path);
|
||
if (path.charCodeAt(path.length - 1) === 47) path = path.slice(0, -1);
|
||
const staticNode = ctx.static[path];
|
||
if (staticNode && staticNode.methods) {
|
||
const staticMatch = staticNode.methods[method] || staticNode.methods[""];
|
||
if (staticMatch !== void 0) return staticMatch[0];
|
||
}
|
||
const segments = splitPath(path);
|
||
const match = _lookupTree(ctx.root, method, segments, 0);
|
||
if (match === void 0) return;
|
||
if (opts?.params === false) return match;
|
||
return {
|
||
data: match.data,
|
||
params: match.paramsMap ? getMatchParams(segments, match.paramsMap) : void 0
|
||
};
|
||
}
|
||
function _lookupTree(node, method, segments, index) {
|
||
if (index === segments.length) {
|
||
if (node.methods) {
|
||
const match = _selectMatcher(node.methods, method, segments, node.key === "*", false);
|
||
if (match) return match;
|
||
}
|
||
return node.param?.methods && _selectMatcher(node.param.methods, method, segments, true, true) || node.wildcard?.methods && _selectMatcher(node.wildcard.methods, method, segments, true, true) || void 0;
|
||
}
|
||
const segment = segments[index];
|
||
if (node.static) {
|
||
const staticChild = node.static[segment];
|
||
if (staticChild) {
|
||
const match = _lookupTree(staticChild, method, segments, index + 1);
|
||
if (match) return match;
|
||
}
|
||
}
|
||
if (node.param) {
|
||
const match = _lookupTree(node.param, method, segments, index + 1);
|
||
if (match) return match;
|
||
}
|
||
if (node.wildcard && node.wildcard.methods) return _selectMatcher(node.wildcard.methods, method, segments, true, false);
|
||
}
|
||
function _selectMatcher(methods, method, segments, dynamicTerminal, optionalOnly) {
|
||
const match = methods[method] || methods[""];
|
||
if (!match) return;
|
||
const first = match[0];
|
||
if (match.length === 1 && first.paramsRegexp.length === 0) {
|
||
if (!optionalOnly) return first;
|
||
const pMap = first.paramsMap;
|
||
return pMap?.[pMap.length - 1]?.[2] ? first : void 0;
|
||
}
|
||
let best;
|
||
let bestWeight = -1;
|
||
for (const m of match) {
|
||
const pMap = m.paramsMap;
|
||
const lastOptional = pMap?.[pMap.length - 1]?.[2];
|
||
if (optionalOnly && !lastOptional) continue;
|
||
let weight = dynamicTerminal && pMap && !lastOptional ? 1 : 0;
|
||
const regexps = m.paramsRegexp;
|
||
for (let i = 0; i < regexps.length; i++) if (regexps[i]) {
|
||
if (!regexps[i].test(segments[i])) {
|
||
weight = -1;
|
||
break;
|
||
}
|
||
weight++;
|
||
}
|
||
if (weight > bestWeight) {
|
||
best = m;
|
||
bestWeight = weight;
|
||
}
|
||
}
|
||
return best;
|
||
}
|
||
function removeRoute(ctx, method = "", path) {
|
||
method = method.toUpperCase();
|
||
if (path.charCodeAt(0) !== 47) path = `/${path}`;
|
||
const groupExpanded = expandGroupDelimiters(path);
|
||
if (groupExpanded) {
|
||
for (const expandedPath of groupExpanded) removeRoute(ctx, method, expandedPath);
|
||
return;
|
||
}
|
||
path = encodeEscapes(path);
|
||
const segments = splitRoute(path);
|
||
const modExpanded = expandModifiers(segments);
|
||
if (modExpanded) {
|
||
for (const expandedPath of modExpanded) removeRoute(ctx, method, expandedPath);
|
||
return;
|
||
}
|
||
_remove(ctx.root, method, segments, 0);
|
||
}
|
||
function _remove(node, method, segments, index) {
|
||
if (index === segments.length) {
|
||
if (node.methods && method in node.methods) {
|
||
delete node.methods[method];
|
||
if (Object.keys(node.methods).length === 0) node.methods = void 0;
|
||
}
|
||
return;
|
||
}
|
||
const segment = segments[index];
|
||
const key = segmentKey(segment);
|
||
if (key === 2) {
|
||
if (node.wildcard) {
|
||
_remove(node.wildcard, method, segments, segments.length);
|
||
if (_isEmptyNode(node.wildcard)) node.wildcard = void 0;
|
||
}
|
||
return;
|
||
}
|
||
if (key === 1) {
|
||
if (node.param) {
|
||
_remove(node.param, method, segments, index + 1);
|
||
if (_isEmptyNode(node.param)) node.param = void 0;
|
||
}
|
||
return;
|
||
}
|
||
const childNode = node.static?.[key];
|
||
if (childNode) {
|
||
_remove(childNode, method, segments, index + 1);
|
||
if (_isEmptyNode(childNode)) {
|
||
delete node.static[key];
|
||
if (Object.keys(node.static).length === 0) node.static = void 0;
|
||
}
|
||
}
|
||
}
|
||
function _isEmptyNode(node) {
|
||
return node.methods === void 0 && node.static === void 0 && node.param === void 0 && node.wildcard === void 0;
|
||
}
|
||
function findAllRoutes(ctx, method = "", path, opts) {
|
||
if (opts?.normalize) path = normalizePath(path);
|
||
if (path.charCodeAt(path.length - 1) === 47) path = path.slice(0, -1);
|
||
const segments = splitPath(path);
|
||
const matches = _findAll(ctx.root, method, segments, 0);
|
||
if (opts?.params === false) return matches;
|
||
return matches.map((m) => {
|
||
return {
|
||
data: m.data,
|
||
params: m.paramsMap ? getMatchParams(segments, m.paramsMap) : void 0
|
||
};
|
||
});
|
||
}
|
||
function _findAll(node, method, segments, index, matches = []) {
|
||
const segment = segments[index];
|
||
if (node.wildcard && node.wildcard.methods) {
|
||
const match = node.wildcard.methods[method] || node.wildcard.methods[""];
|
||
if (match) if (index < segments.length) pushSorted(matches, match, true);
|
||
else {
|
||
const optional = [];
|
||
for (const m of match) {
|
||
const pMap = m.paramsMap;
|
||
if (pMap?.[pMap.length - 1]?.[2]) optional.push(m);
|
||
}
|
||
pushSorted(matches, optional, true);
|
||
}
|
||
}
|
||
if (node.param) {
|
||
if (index < segments.length) {
|
||
const start = matches.length;
|
||
_findAll(node.param, method, segments, index + 1, matches);
|
||
if (node.param.hasRegexParam) {
|
||
for (let r = matches.length - 1; r >= start; r--) if (matches[r].paramsRegexp[index]?.test(segment) === false) matches.splice(r, 1);
|
||
}
|
||
} else if (node.param.methods) {
|
||
const match = node.param.methods[method] || node.param.methods[""];
|
||
if (match) {
|
||
const optional = [];
|
||
for (const m of match) {
|
||
const pMap = m.paramsMap;
|
||
if (pMap?.[pMap.length - 1]?.[2]) optional.push(m);
|
||
}
|
||
pushSorted(matches, optional, true);
|
||
}
|
||
}
|
||
}
|
||
if (index < segments.length) {
|
||
const staticChild = node.static?.[segment];
|
||
if (staticChild) _findAll(staticChild, method, segments, index + 1, matches);
|
||
}
|
||
if (index === segments.length && node.methods) {
|
||
const match = node.methods[method] || node.methods[""];
|
||
if (match) pushSorted(matches, match, node.key === "*");
|
||
}
|
||
return matches;
|
||
}
|
||
function pushSorted(matches, match, dynamicTerminal) {
|
||
if (match.length > 1) match = match.map((m) => {
|
||
let w = 0;
|
||
const { paramsRegexp: rx, paramsMap: pm } = m;
|
||
for (let i = 0; i < rx.length; i++) if (rx[i]) w++;
|
||
if (dynamicTerminal && pm && !pm[pm.length - 1][2]) w++;
|
||
return [m, w];
|
||
}).sort((a, b) => a[1] - b[1]).map((e) => e[0]);
|
||
for (const m of match) matches.push(m);
|
||
}
|
||
function shapeSubsumes(a, b) {
|
||
const fa = a.fixed.length;
|
||
const fb = b.fixed.length;
|
||
if (fa + a.tailMin > fb + b.tailMin || fa + a.tailMax < fb + b.tailMax) return false;
|
||
const common = fa < fb ? fa : fb;
|
||
for (let k = 0; k < common; k++) if (!_segmentSubsumes(a.fixed[k], b.fixed[k])) return false;
|
||
for (let k = fb; k < fa; k++) if (a.fixed[k] !== void 0) return false;
|
||
return true;
|
||
}
|
||
function mergeShapes(shapes) {
|
||
for (let i = 0; i < shapes.length; i++) for (let j = i + 1; j < shapes.length; j++) {
|
||
const a = shapes[i];
|
||
const b = shapes[j];
|
||
if (_sameFixed(a.fixed, b.fixed) && a.tailMin <= b.tailMax + 1 && b.tailMin <= a.tailMax + 1) {
|
||
a.tailMin = Math.min(a.tailMin, b.tailMin);
|
||
a.tailMax = Math.max(a.tailMax, b.tailMax);
|
||
shapes.splice(j, 1);
|
||
j = i;
|
||
}
|
||
}
|
||
return shapes;
|
||
}
|
||
function _sameFixed(a, b) {
|
||
if (a.length !== b.length) return false;
|
||
for (let k = 0; k < a.length; k++) if (!_segmentEqual(a[k], b[k])) return false;
|
||
return true;
|
||
}
|
||
function _segmentEqual(x, y) {
|
||
return x === y || x instanceof RegExp && y instanceof RegExp && x.flags === y.flags && _regExpKey(x) === _regExpKey(y);
|
||
}
|
||
function _segmentSubsumes(x, y) {
|
||
if (x === void 0) return true;
|
||
if (typeof x === "string") return x === y;
|
||
if (typeof y === "string") return x.test(y);
|
||
return y instanceof RegExp && x.flags === y.flags && _regExpKey(x) === _regExpKey(y);
|
||
}
|
||
const _regExpKeys = /* @__PURE__ */ new WeakMap();
|
||
function _regExpKey(r) {
|
||
let key = _regExpKeys.get(r);
|
||
if (key === void 0) {
|
||
const s = r.source;
|
||
key = "";
|
||
for (let i = 0; i < s.length; i++) {
|
||
const c = s[i];
|
||
if (c === "\\") key += c + s[++i];
|
||
else if (c === "[") {
|
||
let j = i + 1;
|
||
while (j < s.length && s[j] !== "]") j += s[j] === "\\" ? 2 : 1;
|
||
key += s.slice(i, j + 1);
|
||
i = j;
|
||
} else if (c === "(" && s[i + 1] === "?" && s[i + 2] === "<" && s[i + 3] !== "=" && s[i + 3] !== "!") {
|
||
const end = s.indexOf(">", i + 3);
|
||
key += "(";
|
||
i = end === -1 ? i : end;
|
||
} else key += c;
|
||
}
|
||
_regExpKeys.set(r, key);
|
||
}
|
||
return key;
|
||
}
|
||
function routeToShapes(pattern) {
|
||
let shapes = _patternShapes.get(pattern);
|
||
if (shapes === void 0) {
|
||
const ctx = createRouter();
|
||
addRoute(ctx, "", pattern);
|
||
shapes = [];
|
||
_collectShapes(ctx.root, [], shapes);
|
||
shapes = mergeShapes(shapes);
|
||
if (_patternShapes.size >= 1024) _patternShapes.clear();
|
||
_patternShapes.set(pattern, shapes);
|
||
}
|
||
return shapes;
|
||
}
|
||
function shapeOf(edges, entry) {
|
||
let shape = _shapeCache.get(entry);
|
||
if (!shape) _shapeCache.set(entry, shape = _computeShape(edges, entry));
|
||
return shape;
|
||
}
|
||
function shapesOverlap(a, b) {
|
||
const fa = a.fixed.length;
|
||
const fb = b.fixed.length;
|
||
const common = fa < fb ? fa : fb;
|
||
for (let k = 0; k < common; k++) if (!_segmentsCanOverlap(a.fixed[k], b.fixed[k])) return false;
|
||
return Math.max(fa + a.tailMin, fb + b.tailMin) <= Math.min(fa + a.tailMax, fb + b.tailMax);
|
||
}
|
||
function mayMatchAt(query, depth, key) {
|
||
for (const q of query) if (depth < q.fixed.length) {
|
||
const m = q.fixed[depth];
|
||
if (m === void 0 || (typeof m === "string" ? m === key : m.test(key))) return true;
|
||
} else if (depth - q.fixed.length < q.tailMax) return true;
|
||
return false;
|
||
}
|
||
const _shapeCache = /* @__PURE__ */ new WeakMap();
|
||
const _patternShapes = /* @__PURE__ */ new Map();
|
||
function _collectShapes(node, edges, shapes) {
|
||
if (node.methods) for (const entry of node.methods[""] || []) shapes.push(_computeShape(edges, entry));
|
||
if (node.static) for (const key in node.static) {
|
||
edges.push(key);
|
||
_collectShapes(node.static[key], edges, shapes);
|
||
edges.pop();
|
||
}
|
||
if (node.param) {
|
||
edges.push(0);
|
||
_collectShapes(node.param, edges, shapes);
|
||
edges.pop();
|
||
}
|
||
if (node.wildcard) {
|
||
edges.push(1);
|
||
_collectShapes(node.wildcard, edges, shapes);
|
||
edges.pop();
|
||
}
|
||
}
|
||
function _computeShape(edges, entry) {
|
||
const fixed = [];
|
||
let tailMin = 0;
|
||
let tailMax = 0;
|
||
const pMap = entry.paramsMap;
|
||
for (let d = 0; d < edges.length; d++) {
|
||
const edge = edges[d];
|
||
if (typeof edge === "string") fixed.push(edge);
|
||
else if (edge === 1) {
|
||
tailMin = pMap[pMap.length - 1][2] ? 0 : 1;
|
||
tailMax = Number.POSITIVE_INFINITY;
|
||
} else if (pMap) {
|
||
const p = pMap.find((e) => e[0] === d);
|
||
if (p[1] instanceof RegExp) fixed.push(p[1]);
|
||
else if (p[2] && d === edges.length - 1) tailMax = 1;
|
||
else fixed.push(void 0);
|
||
}
|
||
}
|
||
let f = fixed.length;
|
||
while (f > 0 && fixed[f - 1] === void 0) f--;
|
||
if (f < fixed.length) {
|
||
tailMin += fixed.length - f;
|
||
tailMax += fixed.length - f;
|
||
fixed.length = f;
|
||
}
|
||
return {
|
||
fixed,
|
||
tailMin,
|
||
tailMax
|
||
};
|
||
}
|
||
function _segmentsCanOverlap(x, y) {
|
||
if (typeof x === "string") return typeof y === "string" ? x === y : y instanceof RegExp ? y.test(x) : true;
|
||
if (x instanceof RegExp && typeof y === "string") return x.test(y);
|
||
return true;
|
||
}
|
||
function routesOverlap(patternA, patternB) {
|
||
return _anyOverlap(routeToShapes(patternA), routeToShapes(patternB));
|
||
}
|
||
function compareRoutes(patternA, patternB) {
|
||
const a = routeToShapes(patternA);
|
||
const b = routeToShapes(patternB);
|
||
const aCoversB = _covers(a, b);
|
||
const bCoversA = _covers(b, a);
|
||
if (aCoversB && bCoversA) return "equal";
|
||
if (aCoversB) return "superset";
|
||
if (bCoversA) return "subset";
|
||
return _anyOverlap(a, b) ? "partial" : "disjoint";
|
||
}
|
||
function findOverlappingRoutes(ctx, method = "", pattern) {
|
||
const query = routeToShapes(pattern);
|
||
const matches = [];
|
||
_collectOverlaps(ctx.root, method, query, [], /* @__PURE__ */ new Set(), matches);
|
||
return matches;
|
||
}
|
||
function _anyOverlap(a, b) {
|
||
for (const x of a) for (const y of b) if (shapesOverlap(x, y)) return true;
|
||
return false;
|
||
}
|
||
function _covers(sup, sub) {
|
||
return sub.every((s) => sup.some((x) => shapeSubsumes(x, s)));
|
||
}
|
||
function _collectOverlaps(node, method, query, edges, seen, matches) {
|
||
if (node.wildcard) {
|
||
edges.push(1);
|
||
_collectOverlaps(node.wildcard, method, query, edges, seen, matches);
|
||
edges.pop();
|
||
}
|
||
if (node.param) {
|
||
edges.push(0);
|
||
_collectOverlaps(node.param, method, query, edges, seen, matches);
|
||
edges.pop();
|
||
}
|
||
if (node.static) {
|
||
for (const key in node.static) if (mayMatchAt(query, edges.length, key)) {
|
||
edges.push(key);
|
||
_collectOverlaps(node.static[key], method, query, edges, seen, matches);
|
||
edges.pop();
|
||
}
|
||
}
|
||
if (node.methods) {
|
||
const data = node.methods[method] || node.methods[""];
|
||
if (data) for (const entry of data) {
|
||
const d = entry.data;
|
||
const isRef = d !== null && (typeof d === "object" || typeof d === "function");
|
||
if (isRef && seen.has(d)) continue;
|
||
const shape = shapeOf(edges, entry);
|
||
if (query.some((q) => shapesOverlap(q, shape))) {
|
||
if (isRef) seen.add(d);
|
||
matches.push({ data: d });
|
||
}
|
||
}
|
||
}
|
||
}
|
||
function routeNodeKeys(pattern) {
|
||
let keys = _patternKeys.get(pattern);
|
||
if (keys === void 0) {
|
||
const ctx = createRouter();
|
||
addRoute(ctx, "", pattern);
|
||
keys = [];
|
||
_collectKeys(ctx.root, "", keys);
|
||
if (_patternKeys.size >= 1024) _patternKeys.clear();
|
||
_patternKeys.set(pattern, keys);
|
||
}
|
||
return keys.slice();
|
||
}
|
||
const _patternKeys = /* @__PURE__ */ new Map();
|
||
function _collectKeys(node, prefix, keys) {
|
||
if (node.methods) keys.push(prefix || "/");
|
||
if (node.static) for (const key in node.static) _collectKeys(node.static[key], prefix + "/" + _escapeKey(key), keys);
|
||
if (node.param) _collectKeys(node.param, prefix + "/*", keys);
|
||
if (node.wildcard) _collectKeys(node.wildcard, prefix + "/**", keys);
|
||
}
|
||
function _escapeKey(key) {
|
||
if (key === "*") return "\\*";
|
||
if (key === "**") return "\\*\\*";
|
||
return key.replace(/[:(){}]/g, "\\$&");
|
||
}
|
||
const _P = "";
|
||
function replaceEscapesOutsideGroups(segment) {
|
||
let r = "", d = 0;
|
||
for (let i = 0; i < segment.length; i++) {
|
||
const c = segment.charCodeAt(i);
|
||
if (c === 40) d++;
|
||
else if (c === 41 && d > 0) d--;
|
||
else if (c === 92 && d === 0 && i + 1 < segment.length) {
|
||
const n = segment[i + 1];
|
||
if (n !== ":" && n !== "(" && n !== "*" && n !== "\\") {
|
||
r += _P + n;
|
||
i++;
|
||
continue;
|
||
}
|
||
}
|
||
r += segment[i];
|
||
}
|
||
return r;
|
||
}
|
||
function resolveEscapePlaceholders(str) {
|
||
return str.replace(/\uFFFE(.)/g, (_, c) => /[.*+?^${}()|[\]\\]/.test(c) ? `\\${c}` : c);
|
||
}
|
||
function escapeBareDots(str) {
|
||
let r = "", d = 0;
|
||
for (let i = 0; i < str.length; i++) {
|
||
const c = str.charCodeAt(i);
|
||
if (c === 92 || c === 65534) {
|
||
r += str[i] + (str[i + 1] || "");
|
||
i++;
|
||
} else if (c === 40) {
|
||
d++;
|
||
r += str[i];
|
||
} else if (c === 41) {
|
||
if (d > 0) d--;
|
||
r += str[i];
|
||
} else if (c === 46 && d === 0) r += "\\.";
|
||
else r += str[i];
|
||
}
|
||
return r;
|
||
}
|
||
function routeToRegExp(route = "/") {
|
||
if (route.charCodeAt(0) !== 47) route = `/${route}`;
|
||
const inlineOptional = inlineOptionalGroup(route);
|
||
if (inlineOptional) return inlineOptional;
|
||
const groupExpanded = expandGroupDelimiters(route);
|
||
if (groupExpanded) {
|
||
const sources = groupExpanded.map((expandedRoute) => routeToRegExp(expandedRoute).source.slice(1, -1));
|
||
return new RegExp(`^(?:${sources.join("|")})$`);
|
||
}
|
||
return _routeToRegExp(route);
|
||
}
|
||
function inlineOptionalGroup(route) {
|
||
const group = scanFirstGroup(route);
|
||
if (!group) return;
|
||
const [pre, body, suf, mod] = group;
|
||
if (mod !== "?" || suf !== "" || body === "" || scanFirstGroup(pre) || scanFirstGroup(body)) return;
|
||
const baseSegs = routeToRegExpSegments(pre);
|
||
const fullSegs = routeToRegExpSegments(pre + body);
|
||
const baseLen = baseSegs.length;
|
||
if (baseLen === 0 || fullSegs.length < baseLen) return;
|
||
const midSegment = fullSegs.length === baseLen;
|
||
const sharedLen = midSegment ? baseLen - 1 : baseLen;
|
||
for (let i = 0; i < sharedLen; i++) if (fullSegs[i] !== baseSegs[i]) return;
|
||
if (midSegment) {
|
||
const prefix = baseSegs[baseLen - 1];
|
||
const last = fullSegs[baseLen - 1];
|
||
if (!last.startsWith(prefix)) return;
|
||
if (/(?:\[\^\/\]|\.)[*+]\)?$/.test(prefix)) return;
|
||
const k = prefix.length;
|
||
const inlineSegs = fullSegs.slice(0, baseLen - 1);
|
||
inlineSegs.push(`${last.slice(0, k)}(?:${last.slice(k)})?`);
|
||
return new RegExp(`^/${inlineSegs.join("/")}/?$`);
|
||
}
|
||
const head = fullSegs.slice(0, baseLen).join("/");
|
||
const tail = fullSegs.slice(baseLen).join("/");
|
||
return new RegExp(`^/${head}(?:/${tail})?/?$`);
|
||
}
|
||
function _routeToRegExp(route) {
|
||
return new RegExp(`^/${routeToRegExpSegments(route).join("/")}/?$`);
|
||
}
|
||
function routeToRegExpSegments(route) {
|
||
const reSegments = [];
|
||
let idCtr = 0;
|
||
for (const segment of splitRoute(route)) {
|
||
if (!segment) {
|
||
reSegments.push("");
|
||
continue;
|
||
}
|
||
if (segment === "*") reSegments.push(`(?<${toRegExpUnnamedKey(idCtr++)}>[^/]*)`);
|
||
else if (segment.startsWith("**")) reSegments.push(segment === "**" ? "?(?<_>.*)" : `?(?<${toGroupName(segment.slice(3))}>.+)`);
|
||
else if (segment.includes(":") || /(^|[^\\])\(/.test(segment) || hasSegmentWildcard(segment)) {
|
||
const modMatch = segment.match(/^(.*:[\w-]+(?:\([^)]*\))?)([?+*])$/);
|
||
if (modMatch) {
|
||
const [, base, mod] = modMatch;
|
||
const name = toGroupName(base.match(/:([\w-]+)/)?.[1] || `_${idCtr++}`);
|
||
if (mod === "?") {
|
||
const inner = escapeBareDots(base.replace(/:([\w-]+)(?:\(([^)]*)\))?/g, (_, id, pattern) => `(?<${toGroupName(id)}>${pattern || "[^/]+"})`));
|
||
if (reSegments.length > 0) {
|
||
const prevQ = reSegments.pop();
|
||
reSegments.push(`${prevQ}(?:/${inner})?`);
|
||
} else reSegments.push(`?${inner}?`);
|
||
continue;
|
||
}
|
||
const pattern = base.match(/:([\w-]+)(?:\(([^)]*)\))?/)?.[2];
|
||
if (reSegments.length > 0) {
|
||
const prevMod = reSegments.pop();
|
||
if (pattern) {
|
||
const repeated = `${pattern}(?:/${pattern})*`;
|
||
reSegments.push(mod === "+" ? `${prevMod}/(?<${name}>${repeated})` : `${prevMod}(?:/(?<${name}>${repeated}))?`);
|
||
} else reSegments.push(mod === "+" ? `${prevMod}/(?<${name}>.+)` : `${prevMod}(?:/(?<${name}>.*))?`);
|
||
} else if (pattern) {
|
||
const repeated = `${pattern}(?:/${pattern})*`;
|
||
reSegments.push(mod === "+" ? `?(?<${name}>${repeated})` : `?(?<${name}>${repeated})?`);
|
||
} else reSegments.push(mod === "+" ? `?(?<${name}>.+)` : `?(?<${name}>.*)`);
|
||
continue;
|
||
}
|
||
let dynamicSegment = replaceEscapesOutsideGroups(segment);
|
||
[dynamicSegment, idCtr] = replaceSegmentWildcards(dynamicSegment, idCtr, toRegExpUnnamedKey);
|
||
reSegments.push(resolveEscapePlaceholders(escapeBareDots(dynamicSegment.replace(/:([\w-]+)(?:\(([^)]*)\))?/g, (_, id, pattern) => `(?<${toGroupName(id)}>${pattern || "[^/]+"})`).replace(/(^|[^\\])\((?![?<])/g, (_, p) => `${p}(?<${toRegExpUnnamedKey(idCtr++)}>`))));
|
||
} else reSegments.push(segment.replace(/\\(.)/g, "$1").replace(/[.*+?^${}()|[\]]/g, "\\$&"));
|
||
}
|
||
return reSegments;
|
||
}
|
||
function toRegExpUnnamedKey(index) {
|
||
return `_${index}`;
|
||
}
|
||
const ROUTE_SPECIAL = /* @__PURE__ */ new Set([
|
||
":",
|
||
"(",
|
||
")",
|
||
"{",
|
||
"}",
|
||
"*",
|
||
"\\",
|
||
"?",
|
||
"+",
|
||
"|",
|
||
"^",
|
||
"$",
|
||
"[",
|
||
"]"
|
||
]);
|
||
function regExpToRoute(regexp) {
|
||
if (typeof regexp !== "string" && /[ims]/.test(regexp.flags)) throw new Error(`rou3: cannot represent regexp flag(s) "${regexp.flags}" as a route`);
|
||
let src = typeof regexp === "string" ? regexp : regexp.source;
|
||
if (src.startsWith("^")) src = src.slice(1);
|
||
if (src.endsWith("$")) src = src.slice(0, -1);
|
||
if (src.endsWith("\\/?")) src = src.slice(0, -3);
|
||
if (src === "" || src === "\\/") return "/";
|
||
const segments = [];
|
||
const n = src.length;
|
||
let i = 0;
|
||
while (i < n) {
|
||
if (src.startsWith("(?:", i)) {
|
||
const end = readGroup(src, i);
|
||
if (src[end] !== "?") throw new Error(`rou3: unsupported non-optional group in "${src}"`);
|
||
applyOptional(segments, src.slice(i + 3, end - 1));
|
||
i = end + 1;
|
||
continue;
|
||
}
|
||
if (src.startsWith("\\/?", i)) {
|
||
const g = matchNamedGroup(src, i + 3);
|
||
if (g && g.end === n && (g.body === ".*" || g.body === ".+")) {
|
||
segments.push(g.name === "_" ? "**" : `**:${g.name}`);
|
||
break;
|
||
}
|
||
}
|
||
if (src.startsWith("\\/", i)) {
|
||
const end = segmentEnd(src, i + 2);
|
||
segments.push(reverseSegment(src.slice(i + 2, end)));
|
||
i = end;
|
||
continue;
|
||
}
|
||
throw new Error(`rou3: cannot parse "${src}" at index ${i}`);
|
||
}
|
||
return "/" + segments.join("/");
|
||
}
|
||
function reverseSegment(seg) {
|
||
const whole = matchNamedGroup(seg, 0);
|
||
if (whole && whole.end === seg.length) {
|
||
if (whole.body === ".+") return `:${whole.name}+`;
|
||
const rep = matchRepeat(whole.body);
|
||
if (rep) return `:${whole.name}${constraint(rep)}+`;
|
||
}
|
||
let out = "";
|
||
let i = 0;
|
||
while (i < seg.length) {
|
||
const c = seg[i];
|
||
if (c === "(") {
|
||
const g = matchNamedGroup(seg, i);
|
||
if (g) {
|
||
out += paramToken(g.name, g.body);
|
||
i = g.end;
|
||
continue;
|
||
}
|
||
if (!seg.startsWith("(?", i)) {
|
||
const end = readGroup(seg, i);
|
||
out += paramToken("_0", seg.slice(i + 1, end - 1));
|
||
i = end;
|
||
continue;
|
||
}
|
||
throw new Error(`rou3: unsupported group construct in "${seg}"`);
|
||
}
|
||
if (c === "\\") {
|
||
const next = seg[i + 1];
|
||
if (next === void 0) throw new Error(`rou3: dangling escape in "${seg}"`);
|
||
if (/[a-z0-9]/i.test(next)) throw new Error(`rou3: unsupported escape "\\${next}" in "${seg}"`);
|
||
out += escapeLiteral(next);
|
||
i += 2;
|
||
continue;
|
||
}
|
||
if (BARE_META.has(c)) throw new Error(`rou3: unsupported metacharacter "${c}" in "${seg}"`);
|
||
out += escapeLiteral(c);
|
||
i += 1;
|
||
}
|
||
return out;
|
||
}
|
||
const BARE_META = /* @__PURE__ */ new Set([
|
||
".",
|
||
"^",
|
||
"$",
|
||
"*",
|
||
"+",
|
||
"?",
|
||
"|",
|
||
"[",
|
||
"]",
|
||
"{",
|
||
"}",
|
||
")"
|
||
]);
|
||
function applyOptional(segments, inner) {
|
||
if (inner.startsWith("\\/")) {
|
||
const rest = inner.slice(2);
|
||
const g = matchNamedGroup(rest, 0);
|
||
if (g && g.end === rest.length) {
|
||
segments.push(optionalParam(g.name, g.body));
|
||
return;
|
||
}
|
||
mergeGroup(segments, `/${reverseSegment(rest)}`);
|
||
return;
|
||
}
|
||
mergeGroup(segments, reverseSegment(inner));
|
||
}
|
||
function mergeGroup(segments, body) {
|
||
if (segments.length === 0) throw new Error(`rou3: optional group "{${body}}?" has no preceding segment`);
|
||
segments[segments.length - 1] += `{${body}}?`;
|
||
}
|
||
function paramToken(name, body) {
|
||
const unnamed = /^_\d+$/.test(name);
|
||
if (unnamed && body === "[^/]*") return "*";
|
||
if (!unnamed && body === "[^/]+") return `:${name}`;
|
||
return unnamed ? constraint(body) : `:${name}${constraint(body)}`;
|
||
}
|
||
function optionalParam(name, body) {
|
||
if (body === "[^/]+") return `:${name}?`;
|
||
if (body === ".*") return `:${name}*`;
|
||
const rep = matchRepeat(body);
|
||
if (rep) return `:${name}${constraint(rep)}*`;
|
||
return `:${name}${constraint(body)}?`;
|
||
}
|
||
function matchRepeat(body) {
|
||
const m = body.match(/^(.+)\(\?:\\\/(.+)\)\*$/);
|
||
return m && m[1] === m[2] ? m[1] : void 0;
|
||
}
|
||
function constraint(body) {
|
||
if (body.includes("/")) throw new Error(`rou3: param constraint "(${body})" cannot contain "/"`);
|
||
return `(${body})`;
|
||
}
|
||
function escapeLiteral(ch) {
|
||
return ROUTE_SPECIAL.has(ch) ? `\\${ch}` : ch;
|
||
}
|
||
function matchNamedGroup(src, start) {
|
||
if (!src.startsWith("(?<", start)) return;
|
||
const after = src[start + 3];
|
||
if (after === "=" || after === "!") return;
|
||
const gt = src.indexOf(">", start);
|
||
if (gt === -1) return;
|
||
const end = readGroup(src, start);
|
||
return {
|
||
name: fromGroupName(src.slice(start + 3, gt)),
|
||
body: src.slice(gt + 1, end - 1),
|
||
end
|
||
};
|
||
}
|
||
function readGroup(src, start) {
|
||
let depth = 0;
|
||
let inClass = false;
|
||
for (let i = start; i < src.length; i++) {
|
||
const c = src[i];
|
||
if (inClass) {
|
||
if (c === "\\") i++;
|
||
else if (c === "]") inClass = false;
|
||
continue;
|
||
}
|
||
if (c === "\\") i++;
|
||
else if (c === "[") inClass = true;
|
||
else if (c === "(") depth++;
|
||
else if (c === ")" && --depth === 0) return i + 1;
|
||
}
|
||
throw new Error(`rou3: unbalanced group in "${src}"`);
|
||
}
|
||
function segmentEnd(src, start) {
|
||
let depth = 0;
|
||
let inClass = false;
|
||
let i = start;
|
||
while (i < src.length) {
|
||
if (inClass) {
|
||
if (src[i] === "\\") i++;
|
||
else if (src[i] === "]") inClass = false;
|
||
i++;
|
||
continue;
|
||
}
|
||
if (depth === 0 && (src.startsWith("\\/", i) || src.startsWith("(?:", i))) break;
|
||
const c = src[i];
|
||
if (c === "\\") i += 2;
|
||
else if (c === "[") {
|
||
inClass = true;
|
||
i++;
|
||
} else {
|
||
if (c === "(") depth++;
|
||
else if (c === ")") depth--;
|
||
i++;
|
||
}
|
||
}
|
||
return i;
|
||
}
|
||
export { NullProtoObj, addRoute, compareRoutes, createRouter, findAllRoutes, findOverlappingRoutes, findRoute, regExpToRoute, removeRoute, routeNodeKeys, routeToRegExp, routesOverlap };
|