// ============================================================================= // Radix Router - Efficient URL pattern matching // ============================================================================= /** * Result of a successful route match. * @template T The type of data associated with the route (e.g., handler and pipeline). */ export interface RouteMatch { /** The data payload stored for the matched route. */ data: T; /** Extracted path parameters. */ params: Record; } /** * Internal tree node for the Radix router. * @template T The type of data stored at the node. */ class RouterNode { children: Map> = new Map(); paramChild: RouterNode | null = null; wildcardChild: RouterNode | null = null; paramName: string | null = null; handlers: Map = new Map(); /** * Inserts a route into the node tree. */ insert(segments: string[], method: string, data: T) { let current: RouterNode = this; for (const segment of segments) { if (segment.startsWith(":")) { if (!current.paramChild) { current.paramChild = new RouterNode(); current.paramName = segment.slice(1); } current = current.paramChild; } else if (segment === "*") { if (!current.wildcardChild) { current.wildcardChild = new RouterNode(); } current = current.wildcardChild; break; } else { if (!current.children.has(segment)) { current.children.set(segment, new RouterNode()); } current = current.children.get(segment)!; } } current.handlers.set(method.toUpperCase(), data); } /** * Searches for a matching route in the node tree. */ search(segments: string[], method: string, params: Record): T | null { let current: RouterNode = this; for (const segment of segments) { const next = current.children.get(segment); if (next) { current = next; } else if (current.paramChild) { if (current.paramName) { params[current.paramName] = segment; } current = current.paramChild; } else if (current.wildcardChild) { current = current.wildcardChild; return current.handlers.get(method.toUpperCase()) || null; } else { return null; } } return current.handlers.get(method.toUpperCase()) || null; } } /** * Radix tree based router for efficient URL pattern matching. * @template T The type of data associated with each route. */ export class RadixRouter { private root = new RouterNode(); /** * Adds a new route to the router. * @param method HTTP method. * @param path URL path pattern. * @param data Data payload to store with the route. */ add(method: string, path: string, data: T) { const segments = this.splitPath(path); this.root.insert(segments, method, data); } /** * Finds a matching route for the given method and path. * @param method HTTP method. * @param path URL path to match. * @returns RouteMatch containing the data and extracted parameters, or null if not found. */ find(method: string, path: string): RouteMatch | null { const segments = this.splitPath(path); const params: Record = {}; const data = this.root.search(segments, method, params); if (data === null) { return null; } return { data, params }; } /** Helper to split a path into normalized segments. */ private splitPath(path: string): string[] { return path.split("/").filter((s) => s.length > 0); } }