reminist
Version:
Blazing fast, zero-dependency, type-safe Radix Tree router for TypeScript/JavaScript.
368 lines (362 loc) • 11.1 kB
JavaScript
"use strict";
var g = Object.defineProperty;
var K = Object.getOwnPropertyDescriptor;
var O = Object.getOwnPropertyNames;
var S = Object.prototype.hasOwnProperty;
var D = (o, t) => {
for (var n in t)
g(o, n, { get: t[n], enumerable: !0 });
}, E = (o, t, n, a) => {
if (t && typeof t == "object" || typeof t == "function")
for (let i of O(t))
!S.call(o, i) && i !== n && g(o, i, { get: () => t[i], enumerable: !(a = K(t, i)) || a.enumerable });
return o;
};
var j = (o) => E(g({}, "__esModule", { value: !0 }), o);
// src/index.ts
var T = {};
D(T, {
Node: () => u,
NodeType: () => y,
NullProtoObj: () => l,
Reminist: () => R
});
module.exports = j(T);
// src/utils/nullPrototype.ts
var l = /* @__PURE__ */ (() => {
let o = function() {
};
return o.prototype = /* @__PURE__ */ Object.create(null), Object.freeze(o.prototype), o;
})();
// src/types/node.ts
var y = /* @__PURE__ */ ((e) => (e[e.Static = 0] = "Static", e[e.Dynamic = 1] = "Dynamic", e[e.CatchAll = 2] = "CatchAll", e[e.OptionalCatchAll = 3] = "OptionalCatchAll", e[e.Wildcard = 4] = "Wildcard", e))(y || {});
// src/controllers/Node.ts
var u = class o {
/** The name of this path segment */
name;
/** The data stored in this node if it's an endpoint */
store;
/** Indicates if this node marks the end of a valid route */
endpoint;
/** The classification of this node (Static, Dynamic, CatchAll, etc.) */
type;
/** The name of the parameter extracted from this segment, if any */
paramName;
/** Direct child nodes with static names */
staticChildren = new l();
/** Child node for simple dynamic segments (e.g., ':id') */
dynamicChild = null;
/** Child node for catch-all segments (e.g., '[...slug]') */
catchAllChild = null;
/** Child node for optional catch-all segments (e.g., '[[...slug]]') */
optionalCatchAllChild = null;
/** Child node for wildcard segments (e.g., '*') */
wildcardChild = null;
/** Total number of direct children */
childCount = 0;
/** Number of non-static children */
dynamicChildCount = 0;
/**
* Creates a new Node instance.
* @param params Initialization parameters including name and endpoint status.
*/
constructor(t) {
this.name = t.name, this.endpoint = t.endpoint, this.store = t.store;
let { type: n, paramName: a } = o.resolveSegmentType(this.name);
this.type = n, this.paramName = a;
}
/**
* Derives the NodeType and extracted parameter name from a raw path segment.
*
* Supported segment syntaxes:
* - `:id` → Dynamic (paramName: 'id')
* - `*` → Wildcard (paramName: '*')
* - `[id]` → Dynamic (paramName: 'id')
* - `[...id]` → CatchAll (paramName: 'id')
* - `[[...id]]` → OptionalCatchAll (paramName: 'id')
* - Otherwise → Static (paramName: '')
*
* @param segment The raw segment string to analyze.
* @returns An object containing the derived type and parameter name.
*/
static resolveSegmentType(t) {
let n = t.charCodeAt(0);
if (n === 58)
return {
type: 1,
paramName: t.substring(1)
};
if (n === 42)
return {
type: 4,
paramName: "*"
};
if (n === 91) {
let a = t.charCodeAt(1);
return a === 91 ? {
type: 3,
paramName: t.substring(5, t.length - 2)
} : a === 46 ? {
type: 2,
paramName: t.substring(4, t.length - 1)
} : {
type: 1,
paramName: t.substring(1, t.length - 1)
};
}
return {
type: 0,
paramName: ""
};
}
/**
* Adds a child node to this node.
* @param child The node to add as a child.
*/
addChild(t) {
switch (this.childCount++, t.type !== 0 && this.dynamicChildCount++, t.type) {
case 0:
this.staticChildren[t.name] = t;
break;
case 1:
this.dynamicChild = t;
break;
case 2:
this.catchAllChild = t;
break;
case 3:
this.optionalCatchAllChild = t;
break;
case 4:
this.wildcardChild = t;
break;
}
}
/**
* Removes a child node from this node.
* @param child The node to remove.
*/
removeChild(t) {
switch (this.childCount--, t.type !== 0 && this.dynamicChildCount--, t.type) {
case 0:
this.staticChildren && delete this.staticChildren[t.name];
break;
case 1:
this.dynamicChild = null;
break;
case 2:
this.catchAllChild = null;
break;
case 3:
this.optionalCatchAllChild = null;
break;
case 4:
this.wildcardChild = null;
break;
}
}
};
// src/controllers/Reminist.ts
var P = new l();
function b(o) {
let t = P[o];
if (t) return t;
let n = 0, a = o.length;
o.charCodeAt(0) === 47 && (n = 1), a > 1 && o.charCodeAt(a - 1) === 47 && a--;
let i = o.substring(n, a).split("/");
return i.length === 1 && i[0] === "" ? (P[o] = [], []) : (P[o] = i, i);
}
function k(o) {
let t = o.charCodeAt(0);
return t === 58 || t === 42 || t === 91;
}
function _(o, t) {
let { type: n } = u.resolveSegmentType(t);
switch (n) {
case 1:
return o.dynamicChild ?? void 0;
case 2:
return o.catchAllChild ?? void 0;
case 3:
return o.optionalCatchAllChild ?? void 0;
case 4:
return o.wildcardChild ?? void 0;
default:
return;
}
}
var R = class {
keys = [];
rootNodes = new l();
staticNodeIndex = new l();
registeredPaths = new l();
/**
* Initializes a new Reminist router instance.
* @param options Configuration options including top-level keys.
*/
constructor(t) {
t?.keys && (this.keys = t.keys);
for (let n = 0, a = this.keys.length; n < a; n++) {
let i = this.keys[n];
this.rootNodes[i] = new u({ name: "/", endpoint: !1 }), this.staticNodeIndex[i] = new l(), this.registeredPaths[i] = new l();
}
}
/**
* Gets or initializes the root node for a specific key.
* @param key The key (e.g., 'GET') to retrieve the root for.
* @returns The root Node for the given key.
*/
getRoot(t) {
return this.rootNodes[t] ??= new u({ name: "/", endpoint: !1 });
}
/**
* Registers a new route in the router.
*
* @template P The path template string.
* @template S The type of data to store with this route.
* @param key The key group for this route (e.g., 'GET').
* @param path The route path template (e.g., '/users/:id').
* @param store The data/handler to associate with this route.
* @returns A new Reminist instance with updated context types.
*/
add(t, n, a) {
let i = b(n), e = i.every((r) => !k(r));
if (this.registeredPaths[t] && (this.registeredPaths[t][n] = !0), e) {
this.staticNodeIndex[t] ??= new l();
let r = new u({ name: n, endpoint: !0, store: a });
return this.staticNodeIndex[t][n] = r, this;
}
let s = this.getRoot(t);
for (let r = 0, d = i.length; r < d; r++) {
let c = i[r], m = s.staticChildren[c];
if (!m && k(c)) {
let h = _(s, c);
if (h && h.name !== c) {
let p = `/${i.slice(0, r).concat(h.name).join("/")}`;
throw new Error(
`\x1B[33m[Reminist] There are two conflicting routes: /${i.join("/")} and ${p} use different dynamic parameters.\x1B[0m`
);
}
m = h;
}
if (m) {
s = m;
continue;
}
let C = new u({
name: c,
endpoint: !1
});
s.addChild(C), s = C;
}
return s.endpoint = !0, s.store = a, this;
}
/**
* Finds a registered route matching the given path.
*
* @template P The literal path string to search for.
* @param key The key group to search within (e.g., 'GET').
* @param path The actual path to match.
* @returns An object containing the matched node (if any) and extracted parameters.
*/
find(t, n) {
let a = this.staticNodeIndex[t]?.[n];
if (a)
return { node: a, params: {} };
let i = b(n), e = this.getRoot(t), s;
for (let d = 0; d < i.length; d++) {
let c = i[d], m = e.staticChildren[c];
if (m) {
e = m;
continue;
}
if (e.dynamicChildCount === 0)
return { node: null, params: s ?? {} };
let { dynamicChild: C, catchAllChild: h, optionalCatchAllChild: p, wildcardChild: A } = e;
if (C) {
s ??= new l(), s[C.paramName] = c, e = C;
continue;
}
return h ? (s ??= new l(), s[h.paramName] = i.slice(d).join("/"), {
node: h,
params: s
}) : p ? (s ??= new l(), s[p.paramName] = i.slice(d).join("/"), {
node: p,
params: s
}) : A ? (s ??= new l(), s["*"] = i.slice(d).join("/"), {
node: A,
params: s
}) : { node: null, params: s ?? {} };
}
if (e.endpoint)
return {
node: e,
params: s ?? {}
};
let { optionalCatchAllChild: r } = e;
return r?.endpoint ? (s ??= new l(), s[r.paramName] = "", {
node: r,
params: s
}) : { node: null, params: s ?? {} };
}
/**
* Checks if a route exists for the given path.
*
* @param key The key group to check.
* @param path The path to check for.
* @returns True if the path is registered.
*/
has(t, n) {
let { node: a } = this.find(t, n);
return a !== null && a.endpoint;
}
/**
* Deletes a registered route.
*
* @template P The route path to delete.
* @param key The key group containing the route.
* @param path The path template to remove.
* @returns The Reminist instance with updated context types.
*/
delete(t, n) {
if (this.registeredPaths[t] && delete this.registeredPaths[t][n], this.staticNodeIndex[t]?.[n])
return delete this.staticNodeIndex[t][n], this;
let a = b(n);
if (a.length === 0 && n !== "/") return this;
let i = [this.getRoot(t)], e = i[0];
for (let s = 0, r = a.length; s < r; s++) {
let d = a[s], c = e.staticChildren[d] ?? (e.dynamicChild?.name === d ? e.dynamicChild : void 0) ?? (e.catchAllChild?.name === d ? e.catchAllChild : void 0) ?? (e.optionalCatchAllChild?.name === d ? e.optionalCatchAllChild : void 0) ?? (e.wildcardChild?.name === d ? e.wildcardChild : void 0);
if (!c) return this;
e = c, i.push(e);
}
if (!e.endpoint) return this;
e.endpoint = !1, e.store = void 0;
for (let s = i.length - 1; s > 0; s--) {
let r = i[s], d = i[s - 1];
if (!r.endpoint && r.childCount === 0)
d.removeChild(r);
else
break;
}
return this;
}
getRoutes(t) {
if (t) {
let a = this.registeredPaths[t];
return a ? Object.keys(a) : [];
}
let n = new l();
for (let a = 0, i = this.keys.length; a < i; a++) {
let e = this.keys[a], s = this.registeredPaths[e];
n[e] = s ? Object.keys(s) : [];
}
return n;
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Node,
NodeType,
NullProtoObj,
Reminist
});