@aurelia/route-recognizer
Version:
[](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [ • 19.8 kB
JavaScript
"use strict";
var t = require("@aurelia/kernel");
function isIHandler(e) {
return e != null && typeof e === "object" && t.isArray(e.path);
}
class Parameter {
constructor(t, e, s, n) {
this.name = t;
this.isOptional = e;
this.isStar = s;
this.pattern = n;
}
satisfiesPattern(t) {
if (this.pattern === null) return true;
this.pattern.lastIndex = 0;
return this.pattern.test(t);
}
}
class ConfigurableRoute {
constructor(t, e, s) {
this.path = t;
this.caseSensitive = e;
this.handler = s;
}
}
class Endpoint {
get residualEndpoint() {
return this.t;
}
set residualEndpoint(t) {
if (this.t !== null) throw new Error("Residual endpoint is already set");
this.t = t;
}
constructor(t, e) {
this.route = t;
this.params = e;
this.t = null;
}
equalsOrResidual(t) {
return t != null && this === t || this.t === t;
}
}
class RecognizedRoute {
constructor(t, s, n) {
this.endpoint = t;
this.i = null;
const i = Object.create(null);
for (const t in n) {
const e = n[t];
i[t] = e != null ? decodeURIComponent(e) : e;
}
this.params = Object.freeze(i);
const r = this.params[e];
if ((r?.length ?? 0) > 0 && s.endsWith(r)) {
s = s.slice(0, -r.length);
}
s = s.startsWith("/") ? s.slice(1) : s;
s = s.endsWith("/") ? s.slice(0, -1) : s;
this.path = s;
}
u() {
let t = this.i;
if (t != null) return t;
t = this.path;
if (t.length !== 0) return this.i = t;
const e = this.endpoint.route.handler;
t = isIHandler(e) ? e.path.find(t => t.length > 0) ?? null : null;
if (t !== null) return this.i = t;
throw new Error(`No non-empty path found`);
}
}
class Candidate {
constructor(t, e, s, n) {
this.chars = t;
this.states = e;
this.skippedStates = s;
this.result = n;
this.childRoutes = [];
this.recognizedResult = null;
this.isConstrained = false;
this.satisfiesConstraints = null;
this.head = e[e.length - 1];
this.endpoint = this.head?.endpoint;
}
advance(t) {
const {chars: e, states: s, skippedStates: n, result: i} = this;
let r = null;
let l = 0;
const o = s[s.length - 1];
function $process(u, h) {
if (u.isMatch(t)) {
if (++l === 1) {
r = u;
} else {
i.add(new Candidate(e.concat(t), s.concat(u), h === null ? n : n.concat(h), i));
}
}
if (o.segment === null && u.isOptional && u.nextStates !== null) {
if (u.nextStates.length > 1) {
throw createError(`${u.nextStates.length} nextStates`);
}
const t = u.nextStates[0];
if (!t.isSeparator) {
throw createError(`Not a separator`);
}
if (t.nextStates !== null) {
for (const e of t.nextStates) {
$process(e, u);
}
}
}
}
if (o.isDynamic) {
$process(o, null);
}
if (o.nextStates !== null) {
for (const t of o.nextStates) {
$process(t, null);
}
}
if (r !== null) {
s.push(this.head = r);
e.push(t);
this.isConstrained = this.isConstrained || r.isDynamic && r.segment.isConstrained;
if (r.endpoint !== null) {
this.endpoint = r.endpoint;
}
}
if (l === 0) {
i.remove(this);
}
}
h() {
function collectSkippedStates(t, e) {
const s = e.nextStates;
if (s !== null) {
if (s.length === 1 && s[0].segment === null) {
collectSkippedStates(t, s[0]);
} else {
for (const e of s) {
if (e.isOptional && e.endpoint !== null) {
t.push(e);
if (e.nextStates !== null) {
for (const s of e.nextStates) {
collectSkippedStates(t, s);
}
}
break;
}
}
}
}
}
collectSkippedStates(this.skippedStates, this.head);
if (!this.isConstrained) return true;
this.R();
return this.satisfiesConstraints;
}
R() {
let t = this.recognizedResult;
if (t != null) return t;
const {states: e, chars: s} = this;
this.satisfiesConstraints = true;
const n = [];
let i = null;
for (let t = e.length - 1, r = 0; t >= r; --t) {
const r = e[t];
const l = r.endpoint !== null && (i === null || i.isDifferentEndpoint(r.endpoint) && i.isFulfilled());
if (l) {
if (i !== null) {
n.unshift(i.toRecognizedRoute());
}
i = new EndpointRequirement(r);
}
if (i === null) continue;
this.satisfiesConstraints = this.satisfiesConstraints && i.consume(r, s[t], e[t - 1]);
}
if (i !== null && i.isDifferentRecognizedRoute(n[0])) {
n.unshift(i.toRecognizedRoute());
}
if (n.length > 1 && n[0].path === "") n.shift();
if (this.satisfiesConstraints) {
this.recognizedResult = t = [ n, this.head ];
}
return t;
}
compareTo(t) {
const e = this.states;
const s = t.states;
for (let t = 0, n = 0, i = Math.max(e.length, s.length); t < i; ++t) {
let i = e[t];
if (i === void 0) {
return 1;
}
let r = s[n];
if (r === void 0) {
return -1;
}
let l = i.segment;
let o = r.segment;
if (l === null) {
if (o === null) {
++n;
continue;
}
if ((i = e[++t]) === void 0) {
return 1;
}
l = i.segment;
} else if (o === null) {
if ((r = s[++n]) === void 0) {
return -1;
}
o = r.segment;
}
if (l.kind < o.kind) {
return 1;
}
if (l.kind > o.kind) {
return -1;
}
++n;
}
const n = this.skippedStates;
const i = t.skippedStates;
const r = n.length;
const l = i.length;
if (r < l) {
return 1;
}
if (r > l) {
return -1;
}
for (let t = 0; t < r; ++t) {
const e = n[t];
const s = i[t];
if (e.length < s.length) {
return 1;
}
if (e.length > s.length) {
return -1;
}
}
return 0;
}
}
class EndpointRequirement {
constructor(t) {
this.$ = Object.create(null);
this.C = [];
this.P = "";
if (t.endpoint == null) throw new Error("Invalid state; endpoint is required.");
const e = this.q = t.endpoint;
for (const t of e.params) {
this.$[t.name] = [ void 0, !t.isOptional, false ];
}
for (const t of e.route.path.split("/").filter(t => isNotEmpty(t) && !t.startsWith(":") && !t.startsWith("*"))) {
this.C.push([ t, "", false ]);
}
}
consume(t, e, s) {
this.P = e + this.P;
if (t.isDynamic) {
const n = t.segment;
const i = n.name;
const r = this.$[i];
if (r[0] === void 0) {
r[0] = e;
if (r[1]) r[2] = true;
} else {
r[0] = e + r[0];
}
const l = t.isConstrained && !Object.is(s?.segment, n);
if (!l) return true;
return t.satisfiesConstraint(r[0]);
}
if (this.C.length > 0 && e !== "" && e !== "/") {
const s = this.C.findLast(t => !t[2]);
if (s == null) throw createError(`Unexpected state`);
s[1] = e + s[1];
s[2] = t.segment.caseSensitive ? s[0] === s[1] : s[0].toLowerCase() === s[1].toLowerCase();
}
return true;
}
isFulfilled() {
for (const [, t, e] of Object.values(this.$)) {
if (t && !e) return false;
}
return this.C.length === 0 || this.C[0][2];
}
toRecognizedRoute() {
const t = Object.create(null);
for (const e in this.$) {
t[e] = this.$[e][0];
}
return new RecognizedRoute(this.q, this.P, t);
}
isDifferentRecognizedRoute(t) {
return this.isDifferentEndpoint(t?.endpoint);
}
isDifferentEndpoint(t) {
return t == null || this.q.route.handler !== t.route.handler;
}
}
function hasEndpoint(t) {
return t.head.endpoint !== null;
}
function compareChains(t, e) {
return t.compareTo(e);
}
class RecognizeResult {
get isEmpty() {
return this.candidates.length === 0;
}
constructor(t) {
this.candidates = [];
this.candidates = [ new Candidate([ "" ], [ t ], [], this) ];
}
getSolution() {
const t = this.candidates.filter(t => hasEndpoint(t) && t.h());
if (t.length === 0) {
return null;
}
t.sort(compareChains);
return t[0];
}
add(t) {
this.candidates.push(t);
}
remove(t) {
this.candidates.splice(this.candidates.indexOf(t), 1);
}
advance(t) {
const e = this.candidates.slice();
for (const s of e) {
s.advance(t);
}
}
}
const e = "$$residue";
const s = /^:(?<name>[^?\s{}]+)(?:\{\{(?<constraint>.+)\}\})?(?<optional>\?)?$/g;
class RouteRecognizer {
constructor() {
this.rootState = new State(null, null, "");
this.cache = new Map;
this.endpointLookup = new Map;
}
add(t, s = false, n = null) {
let i;
let r;
if (t instanceof Array) {
for (const l of t) {
r = this.$add(l, false, n);
i = r.params;
if (!s || (i[i.length - 1]?.isStar ?? false)) continue;
r.residualEndpoint = this.$add({
...l,
path: `${l.path}/*${e}`
}, true, n);
}
} else {
r = this.$add(t, false, n);
i = r.params;
if (s && !(i[i.length - 1]?.isStar ?? false)) {
r.residualEndpoint = this.$add({
...t,
path: `${t.path}/*${e}`
}, true, n);
}
}
this.cache.clear();
}
$add(t, n, i) {
const r = i === null ? t.path : `${i}/${t.path}`;
const l = this.endpointLookup;
if (l.has(r)) throw createError(`Cannot add duplicate path '${r}'.`);
const o = new ConfigurableRoute(t.path, t.caseSensitive === true, t.handler);
const u = r === "" ? [ "" ] : r.split("/").filter(isNotEmpty);
const h = [];
let c = this.rootState;
const a = i === null ? 0 : i.split("/").filter(isNotEmpty).length;
const f = u.length;
for (let t = 0; t < f; ++t) {
const i = u[t];
c = c.append(null, "/");
switch (i.charAt(0)) {
case ":":
{
s.lastIndex = 0;
const n = s.exec(i);
const {name: r, optional: l} = n?.groups ?? {};
const o = l === "?";
if (r === e) throw new Error(`Invalid parameter name; usage of the reserved parameter name '${e}' is used.`);
const u = n?.groups?.constraint;
const f = u != null ? new RegExp(u) : null;
if (t >= a) {
h.push(new Parameter(r, o, false, f));
}
c = new DynamicSegment(r, o, f).appendTo(c);
break;
}
case "*":
{
const s = i.slice(1);
let r;
if (s === e) {
if (!n) throw new Error(`Invalid parameter name; usage of the reserved parameter name '${e}' is used.`);
r = 1;
} else {
r = 2;
}
if (t >= a) {
h.push(new Parameter(s, true, true, null));
}
c = new StarSegment(s, r).appendTo(c);
break;
}
default:
{
c = new StaticSegment(i, o.caseSensitive).appendTo(c);
break;
}
}
}
const d = new Endpoint(o, h);
c.setEndpoint(d);
l.set(r, d);
return d;
}
recognize(t, e = null) {
const s = this.cache;
let n;
if (e == null) {
n = s.get(t);
if (n !== void 0) return n?.[0] ?? null;
s.set(t, n = this.$recognize(t, this.rootState));
return n?.[0] ?? null;
}
const i = e.map(t => t.u()).join("/");
const r = combinePaths(i, t);
n = s.get(r);
if (n !== void 0) return n === null ? null : n[0].slice(e.length);
let l = s.get(i);
if (l === null) return null;
if (l === void 0) {
l = this.$recognize(i, this.rootState);
if (l === null) return null;
}
const o = this.$recognize(t, l[1]);
if (o === null) return null;
let u = o[0];
if (u.length > 1 && u[0].path === "") u = u.slice(1);
s.set(r, [ e.concat(u), o[1] ]);
return u;
function combinePaths(t, e) {
if (t === "") return e;
if (e === "") return t;
if (t.endsWith("/")) return e.startsWith("/") ? t + e.slice(1) : t + e;
return e.startsWith("/") ? t + e : `${t}/${e}`;
}
}
$recognize(t, e) {
if (!t.startsWith("/")) {
t = `/${t}`;
}
if (t.length > 1 && t.endsWith("/")) {
t = t.slice(0, -1);
}
const s = new RecognizeResult(e);
for (let e = 0, n = t.length; e < n; ++e) {
const n = t.charAt(e);
s.advance(n);
if (s.isEmpty) {
return null;
}
}
const n = s.getSolution();
if (n === null) {
return null;
}
return n.R();
}
getEndpoint(t) {
return this.endpointLookup.get(t) ?? null;
}
}
class State {
constructor(t, e, s) {
this.prevState = t;
this.segment = e;
this.value = s;
this.nextStates = null;
this.endpoint = null;
this.isConstrained = false;
switch (e?.kind) {
case 3:
this.length = t.length + 1;
this.isSeparator = false;
this.isDynamic = true;
this.isOptional = e.optional;
this.isConstrained = e.isConstrained;
break;
case 2:
case 1:
this.length = t.length + 1;
this.isSeparator = false;
this.isDynamic = true;
this.isOptional = false;
break;
case 4:
this.length = t.length + 1;
this.isSeparator = false;
this.isDynamic = false;
this.isOptional = false;
break;
case undefined:
this.length = t === null ? 0 : t.length;
this.isSeparator = true;
this.isDynamic = false;
this.isOptional = false;
break;
}
}
append(t, e) {
let s;
let n = this.nextStates;
if (n === null) {
s = void 0;
n = this.nextStates = [];
} else if (t === null) {
s = n.find(t => t.value === e);
} else {
s = n.find(e => e.segment?.equals(t));
}
if (s === void 0) {
n.push(s = new State(this, t, e));
}
return s;
}
setEndpoint(t) {
if (this.endpoint !== null) {
throw createError(`Cannot add ambiguous route. The pattern '${t.route.path}' clashes with '${this.endpoint.route.path}'`);
}
this.endpoint = t;
if (this.isOptional) {
this.prevState.setEndpoint(t);
if (this.prevState.isSeparator && this.prevState.prevState !== null) {
this.prevState.prevState.setEndpoint(t);
}
}
}
isMatch(t) {
const e = this.segment;
switch (e?.kind) {
case 3:
return !this.value.includes(t);
case 2:
case 1:
return true;
case 4:
case undefined:
return this.value.includes(t);
}
}
satisfiesConstraint(t) {
return this.isConstrained ? this.segment.satisfiesPattern(t) : true;
}
}
function isNotEmpty(t) {
return t.length > 0;
}
class StaticSegment {
get kind() {
return 4;
}
constructor(t, e) {
this.value = t;
this.caseSensitive = e;
}
appendTo(t) {
const {value: e, value: {length: s}} = this;
if (this.caseSensitive) {
for (let n = 0; n < s; ++n) {
t = t.append(this, e.charAt(n));
}
} else {
for (let n = 0; n < s; ++n) {
const s = e.charAt(n);
t = t.append(this, s.toUpperCase() + s.toLowerCase());
}
}
return t;
}
equals(t) {
return t.kind === 4 && t.caseSensitive === this.caseSensitive && t.value === this.value;
}
}
class DynamicSegment {
get kind() {
return 3;
}
constructor(t, e, s) {
this.name = t;
this.optional = e;
this.pattern = s;
if (s === void 0) throw new Error(`Pattern is undefined`);
this.isConstrained = s !== null;
}
appendTo(t) {
t = t.append(this, "/");
return t;
}
equals(t) {
return t.kind === 3 && t.optional === this.optional && t.name === this.name;
}
satisfiesPattern(t) {
if (this.pattern === null) return true;
this.pattern.lastIndex = 0;
return this.pattern.test(t);
}
}
class StarSegment {
constructor(t, e) {
this.name = t;
this.kind = e;
}
appendTo(t) {
t = t.append(this, "");
return t;
}
equals(t) {
return (t.kind === 2 || t.kind === 1) && t.name === this.name;
}
}
const createError = t => new Error(t);
exports.ConfigurableRoute = ConfigurableRoute;
exports.Endpoint = Endpoint;
exports.Parameter = Parameter;
exports.RESIDUE = e;
exports.RecognizedRoute = RecognizedRoute;
exports.RouteRecognizer = RouteRecognizer;
//# sourceMappingURL=index.cjs.map