UNPKG

reminist

Version:

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

243 lines (237 loc) 10.3 kB
/** * A utility type for objects with no prototype (Object.create(null)). * This is useful for high-performance caches and maps to avoid prototype inheritance overhead * and potential property collisions. * * @template T The type of the values stored in the object. */ type NullProtoObj<T = unknown> = InstanceType<new () => Record<PropertyKey, T>>; /** * A class-like constructor that creates objects with a null prototype. * These objects are truly empty and do not inherit from Object.prototype. * * @example * const cache = new NullProtoObj<string[]>(); * // cache.toString is undefined */ declare const NullProtoObj: { new <T>(): NullProtoObj<T>; }; /** * Enum representing the different types of routing nodes. */ declare enum NodeType { /** A literal path segment (e.g., 'users') */ Static = 0, /** A dynamic segment with a parameter (e.g., ':id' or '[id]') */ Dynamic = 1, /** A catch-all segment that matches multiple levels (e.g., '[...slug]') */ CatchAll = 2, /** An optional catch-all segment (e.g., '[[...slug]]') */ OptionalCatchAll = 3, /** A wildcard segment (e.g., '*') */ Wildcard = 4 } /** * Parameters for initializing a Node. * * @template Data The type of data stored in the node. * @template Endpoint Whether this node represents a terminal route. * @template Path The string literal type of the path segment. */ type NodeParams<Data, Endpoint extends boolean, Path extends string> = { name: Path; } & (Endpoint extends true ? { endpoint: true; store: Data; } : { endpoint: false; store?: never; }); /** * Represents a single node in the routing tree. * * @template Data The type of data associated with this node (e.g., a handler). * @template Path The string literal representing the segment name. * @template Endpoint Whether this node is a final route segment. */ declare class Node<Data, Path extends string, Endpoint extends boolean> { /** The name of this path segment */ name: string; /** The data stored in this node if it's an endpoint */ store: Endpoint extends true ? Data : unknown; /** Indicates if this node marks the end of a valid route */ endpoint: Endpoint; /** The classification of this node (Static, Dynamic, CatchAll, etc.) */ type: NodeType; /** The name of the parameter extracted from this segment, if any */ paramName: string; /** Direct child nodes with static names */ staticChildren: NullProtoObj<Node<Data, any, any>>; /** Child node for simple dynamic segments (e.g., ':id') */ dynamicChild: Node<Data, any, any> | null; /** Child node for catch-all segments (e.g., '[...slug]') */ catchAllChild: Node<Data, any, any> | null; /** Child node for optional catch-all segments (e.g., '[[...slug]]') */ optionalCatchAllChild: Node<Data, any, any> | null; /** Child node for wildcard segments (e.g., '*') */ wildcardChild: Node<Data, any, any> | null; /** Total number of direct children */ childCount: number; /** Number of non-static children */ dynamicChildCount: number; /** * Creates a new Node instance. * @param params Initialization parameters including name and endpoint status. */ constructor(params: NodeParams<Data, Endpoint, Path>); /** * 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(segment: string): { type: NodeType; paramName: string; }; /** * Adds a child node to this node. * @param child The node to add as a child. */ addChild(child: Node<Data, string, boolean>): void; /** * Removes a child node from this node. * @param child The node to remove. */ removeChild(child: Node<Data, any, any>): void; } /** * Options for configuring the Reminist router instance. * @template Keys A readonly array of string keys (e.g., HTTP methods). */ type ReministOptions<Keys extends readonly string[]> = { keys: Keys; }; /** * Transforms a route template into its matching type. * @template T The route template string. */ type RouteToType<T extends string> = T extends `${infer Start}:${string}/${infer Rest}` ? `${Start}${string}/${RouteToType<Rest>}` : T extends `${infer Start}:${string}` ? `${Start}${string}` : T extends `${infer Start}[[...${string}]]` ? `${Start}${string}` | Start | (Start extends `${infer S}/` ? S : never) : T extends `${infer Start}[...${string}]` ? Start extends `${string}[` ? never : `${Start}${string}` : T extends `${infer Start}*` ? `${Start}${string}` : T; /** * Transforms a route template into an autocomplete-friendly string. * @template T The route template string. */ type RouteToAutocomplete<T extends string> = T extends `${infer Start}:${infer Param}/${infer Rest}` ? `${Start}<${Param}>/${RouteToAutocomplete<Rest>}` : T extends `${infer Start}:${infer Param}` ? `${Start}<${Param}>` : T extends `${infer Start}[[...${infer Param}]]` ? `${Start}<[[...${Param}]]>` : T extends `${infer Start}[...${infer Param}]` ? Start extends `${string}[` ? never : `${Start}<...${Param}>` : T extends `${infer Start}*` ? `${Start}<*>` : T; /** * Matches a provided path against a set of registered routes. * @template P The path to match. * @template R The registered route template. */ type MatchRoute<P extends string, R extends string> = [ R ] extends [never] ? never : R extends any ? P extends RouteToType<R> ? R : never : never; /** * Extracts parameter types from a route template. * @template T The route template string. */ type ExtractParams<T extends string> = [ T ] extends [never] ? Record<string, string> : T extends `${string}:${infer Param}/${infer Rest}` ? { [K in Param]: string; } & ExtractParams<Rest> : T extends `${string}:${infer Param}` ? { [K in Param]: string; } : T extends `${string}[[...${infer Param}]]` ? { [K in Param]: string; } : T extends `${infer Start}[...${infer Param}]` ? Start extends `${string}[` ? never : { [K in Param]: string; } : T extends `${string}*` ? { '*': string; } : Record<string, string>; /** * Reminist - A high-performance, type-safe router based on a Radix Tree. * * Supports static routes, dynamic parameters, catch-all, and wildcards with * full TypeScript inference for parameters and stored data. * * @template Context A record mapping path templates to their associated data types. * @template Keys A readonly array of keys (e.g., HTTP methods) used to group routes. */ declare class Reminist<const Context extends Record<string, any> = {}, const Keys extends readonly string[] = readonly string[]> { private keys; private rootNodes; private staticNodeIndex; private registeredPaths; /** * Initializes a new Reminist router instance. * @param options Configuration options including top-level keys. */ constructor(options?: ReministOptions<Keys>); /** * 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(key: Keys[number]): Node<Context[keyof Context], string, boolean>; /** * 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<P extends string, S>(key: Keys[number], path: P, store: S): Reminist<Context & { [K in P]: S; }, Keys>; /** * 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<const P extends RouteToAutocomplete<Extract<keyof Context, string>> | (string & {})>(key: Keys[number], path: P): { node: Node<Context[MatchRoute<P extends string ? P : string, Extract<keyof Context, string>>], string, true> | null; params: ExtractParams<MatchRoute<P extends string ? P : string, Extract<keyof Context, string>>>; }; /** * 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<const P extends RouteToAutocomplete<Extract<keyof Context, string>> | (string & {})>(key: Keys[number], path: P): boolean; /** * 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<P extends RouteToAutocomplete<Extract<keyof Context, string>> | (string & {})>(key: Keys[number], path: P): Reminist<Omit<Context, P extends string ? P : string>, Keys>; /** * Retrieves all registered routes for a specific key or all keys. * * @param key Optional key to filter routes. * @returns If key is provided, an array of routes. Otherwise, a record of all keys and their routes. */ getRoutes<K extends Keys[number]>(key: K): string[]; getRoutes(): { [K in Keys[number]]: string[]; }; } export { type ExtractParams, type MatchRoute, Node, type NodeParams, NodeType, NullProtoObj, Reminist, type ReministOptions, type RouteToAutocomplete, type RouteToType };