UNPKG

reminist

Version:

Blazing fast, zero-dependency, type-safe Radix Tree router for TypeScript/JavaScript.

368 lines (362 loc) 11.1 kB
"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 });