site-panzai/node_modules/rou3/dist/index.mjs

1022 lines
32 KiB
JavaScript
Raw Normal View History

2026-11-13 13:57:04 +01:00
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 };