UNPKG

@wroud/navigation

Version:

A flexible, pattern-matching navigation system for JavaScript applications with built-in routing, browser integration, and navigation state management

186 lines (162 loc) 5.37 kB
/** * Types for the Trie-based pattern matching system */ import type { RouteParams } from "../IRouteMatcher.js"; import type { IRouteState } from "../IRouteState.js"; /** * Extracts parameter names from a pattern string using template literal types */ // Helper to split pattern into segments type Split< S extends string, D extends string, > = S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : [S]; // Helper to remove leading and trailing slashes from a pattern type TrimSlashes<S extends string> = S extends `/${infer T}` ? T extends `${infer U}/` ? TrimSlashes<U> : T : S extends `${infer U}/` ? TrimSlashes<U> : S; // Split pattern into path and query parts type PatternPath<S extends string> = S extends `${infer Path}?${string}` ? Path : S; type PatternQuery<S extends string> = S extends `${string}?${infer Query}` ? Query : ""; // Extract query parameter segments from "key=:param<type>&key2=:param2" type SplitAmpersand<S extends string> = S extends `${infer First}&${infer Rest}` ? [First, ...SplitAmpersand<Rest>] : S extends "" ? [] : [S]; // Extract the param part from "key=:param<type>" type QueryParamSegment<S extends string> = S extends `${string}=:${infer Param}` ? `:${Param}` : never; type StripWildcard<S extends string> = S extends `${infer R}*` ? R : S; type StripParam<S extends string> = S extends `:${infer R}` ? R : S; type StripType<S extends string> = S extends `${infer N}<${string}>${string}` ? N : S; type StripRequired<S extends string> = S extends `${infer R}!` ? R : S; type ExtractName<S extends string> = StripRequired< StripType<StripWildcard<StripParam<S>>> >; type ExtractType<S extends string> = S extends `${string}<${infer T}>${string}` ? T : "string"; type IsWildcard<S extends string> = S extends `${string}*` ? true : false; type ParamInfo<S extends string> = { name: ExtractName<S>; type: ExtractType<S>; wildcard: IsWildcard<S>; }; type PrimitiveFromType<T extends string> = T extends "number" ? number : T extends "boolean" ? boolean : T extends "date" ? Date : T extends "json" ? object : string; type ParamToValue<P extends { type: string; wildcard: boolean }> = P["wildcard"] extends true ? PrimitiveFromType<P["type"]>[] : PrimitiveFromType<P["type"]>; // Helper to simplify intersected types into a single flat object type Simplify<T> = { [K in keyof T]: T[K] }; // Distribute ParamInfo over a union of segment strings type ToParamInfo<S> = S extends string ? ParamInfo<S> : never; // Path params (always required) type PathParams<Pattern extends string> = ToParamInfo< Extract<Split<TrimSlashes<PatternPath<Pattern>>, "/">[number], `:${string}`> >; // Query param segments split by required (!) vs optional type QuerySegments<Pattern extends string> = QueryParamSegment< SplitAmpersand<PatternQuery<Pattern>>[number] >; type RequiredQueryParams<Pattern extends string> = ToParamInfo< Extract<QuerySegments<Pattern>, `${string}!`> >; type OptionalQueryParams<Pattern extends string> = ToParamInfo< Exclude<QuerySegments<Pattern>, `${string}!`> >; /** * Extract route parameters from a pattern string. * Path params and query params with `!` suffix are required. * Query params without `!` are optional. * * @template Pattern - The URL pattern to extract parameters from * @example * type Params = ExtractRouteParams<"/user/:id">; // { id: string } * type QueryParams = ExtractRouteParams<"/search?q=:q!&page=:page<number>">; // { q: string; page?: number } * type FileParams = ExtractRouteParams<"/files/:path*">; // { path: string[] } */ export type ExtractRouteParams<Pattern extends string> = Pattern extends | "/" | "" ? {} : Simplify< { [P in | PathParams<Pattern> | RequiredQueryParams<Pattern> as P["name"]]: ParamToValue<P>; } & { [P in OptionalQueryParams<Pattern> as P["name"]]?: ParamToValue<P>; } >; /** * Type-safe TriePatternMatching methods */ // Interface that provides type safety for pattern matching operations export interface TypedPatternMatcher { /** * Decode URL parameters based on a pattern */ decode<Pattern extends string>( pattern: Pattern, url: string, ): ExtractRouteParams<Pattern> | null; /** * Encode parameters into a URL based on a pattern */ encode<Pattern extends string>( pattern: Pattern, params: ExtractRouteParams<Pattern>, ): string; /** * Match a URL against the patterns in the trie */ match<Pattern extends string>( url: string, ): IPatternRouteState<Pattern> | null; } export interface IPatternRouteState<Pattern extends string> extends IRouteState { id: Pattern; params: ExtractRouteParams<this["id"]>; } /** * Types of nodes in the trie structure * - static: regular path segment that matches exactly (e.g., "user", "posts") * - param: named parameter segment (e.g., ":id", ":action") * - wildcard: captures multiple segments (e.g., ":path*") */ export type NodeType = "static" | "param" | "wildcard"; /** * Result of a matching operation */ export interface MatchResult { matched: boolean; params: RouteParams; } /** * Extended match result with the matched pattern */ export interface ExtendedMatchResult extends MatchResult { pattern?: string; }