reminist
Version:
Blazing fast, zero-dependency, type-safe Radix Tree router for TypeScript/JavaScript.
243 lines (237 loc) • 10.3 kB
TypeScript
/**
* 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 };