UNPKG

reminist

Version:

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

341 lines (337 loc) 10.3 kB
// src/utils/nullPrototype.ts var d = /* @__PURE__ */ (() => { let o = function() { }; return o.prototype = /* @__PURE__ */ Object.create(null), Object.freeze(o.prototype), o; })(); // src/types/node.ts var f = /* @__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))(f || {}); // src/controllers/Node.ts var h = 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 d(); /** 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: s, paramName: a } = o.resolveSegmentType(this.name); this.type = s, 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 s = t.charCodeAt(0); if (s === 58) return { type: 1, paramName: t.substring(1) }; if (s === 42) return { type: 4, paramName: "*" }; if (s === 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 N = new d(); function P(o) { let t = N[o]; if (t) return t; let s = 0, a = o.length; o.charCodeAt(0) === 47 && (s = 1), a > 1 && o.charCodeAt(a - 1) === 47 && a--; let i = o.substring(s, a).split("/"); return i.length === 1 && i[0] === "" ? (N[o] = [], []) : (N[o] = i, i); } function A(o) { let t = o.charCodeAt(0); return t === 58 || t === 42 || t === 91; } function K(o, t) { let { type: s } = h.resolveSegmentType(t); switch (s) { 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 w = class { keys = []; rootNodes = new d(); staticNodeIndex = new d(); registeredPaths = new d(); /** * Initializes a new Reminist router instance. * @param options Configuration options including top-level keys. */ constructor(t) { t?.keys && (this.keys = t.keys); for (let s = 0, a = this.keys.length; s < a; s++) { let i = this.keys[s]; this.rootNodes[i] = new h({ name: "/", endpoint: !1 }), this.staticNodeIndex[i] = new d(), this.registeredPaths[i] = new d(); } } /** * 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 h({ 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, s, a) { let i = P(s), e = i.every((r) => !A(r)); if (this.registeredPaths[t] && (this.registeredPaths[t][s] = !0), e) { this.staticNodeIndex[t] ??= new d(); let r = new h({ name: s, endpoint: !0, store: a }); return this.staticNodeIndex[t][s] = r, this; } let n = this.getRoot(t); for (let r = 0, l = i.length; r < l; r++) { let c = i[r], m = n.staticChildren[c]; if (!m && A(c)) { let u = K(n, c); if (u && u.name !== c) { let p = `/${i.slice(0, r).concat(u.name).join("/")}`; throw new Error( `\x1B[33m[Reminist] There are two conflicting routes: /${i.join("/")} and ${p} use different dynamic parameters.\x1B[0m` ); } m = u; } if (m) { n = m; continue; } let C = new h({ name: c, endpoint: !1 }); n.addChild(C), n = C; } return n.endpoint = !0, n.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, s) { let a = this.staticNodeIndex[t]?.[s]; if (a) return { node: a, params: {} }; let i = P(s), e = this.getRoot(t), n; for (let l = 0; l < i.length; l++) { let c = i[l], m = e.staticChildren[c]; if (m) { e = m; continue; } if (e.dynamicChildCount === 0) return { node: null, params: n ?? {} }; let { dynamicChild: C, catchAllChild: u, optionalCatchAllChild: p, wildcardChild: b } = e; if (C) { n ??= new d(), n[C.paramName] = c, e = C; continue; } return u ? (n ??= new d(), n[u.paramName] = i.slice(l).join("/"), { node: u, params: n }) : p ? (n ??= new d(), n[p.paramName] = i.slice(l).join("/"), { node: p, params: n }) : b ? (n ??= new d(), n["*"] = i.slice(l).join("/"), { node: b, params: n }) : { node: null, params: n ?? {} }; } if (e.endpoint) return { node: e, params: n ?? {} }; let { optionalCatchAllChild: r } = e; return r?.endpoint ? (n ??= new d(), n[r.paramName] = "", { node: r, params: n }) : { node: null, params: n ?? {} }; } /** * 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, s) { let { node: a } = this.find(t, s); 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, s) { if (this.registeredPaths[t] && delete this.registeredPaths[t][s], this.staticNodeIndex[t]?.[s]) return delete this.staticNodeIndex[t][s], this; let a = P(s); if (a.length === 0 && s !== "/") return this; let i = [this.getRoot(t)], e = i[0]; for (let n = 0, r = a.length; n < r; n++) { let l = a[n], c = e.staticChildren[l] ?? (e.dynamicChild?.name === l ? e.dynamicChild : void 0) ?? (e.catchAllChild?.name === l ? e.catchAllChild : void 0) ?? (e.optionalCatchAllChild?.name === l ? e.optionalCatchAllChild : void 0) ?? (e.wildcardChild?.name === l ? 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 n = i.length - 1; n > 0; n--) { let r = i[n], l = i[n - 1]; if (!r.endpoint && r.childCount === 0) l.removeChild(r); else break; } return this; } getRoutes(t) { if (t) { let a = this.registeredPaths[t]; return a ? Object.keys(a) : []; } let s = new d(); for (let a = 0, i = this.keys.length; a < i; a++) { let e = this.keys[a], n = this.registeredPaths[e]; s[e] = n ? Object.keys(n) : []; } return s; } }; export { h as Node, f as NodeType, d as NullProtoObj, w as Reminist };