Created
August 29, 2025 12:01
-
-
Save terrysahaidak/9b2ff50034e6018a401f07b13d559157 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Type-safe route parameter parser library with pattern-based type inference | |
| */ | |
| // Template literal type utilities for extracting parameter names from route patterns | |
| type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` | |
| ? {[K in Param | keyof ExtractRouteParams<`/${Rest}`>]: string} | |
| : T extends `${infer _Start}:${infer Param}` | |
| ? {[K in Param]: string} | |
| : Record<string, never>; | |
| // Helper type to ensure we get clean parameter extraction | |
| type RouteParams<T extends string> = string extends T | |
| ? Record<string, string> // Fallback for non-literal strings | |
| : ExtractRouteParams<T>; | |
| export interface RouteMatch<T extends string = string> { | |
| params: RouteParams<T>; | |
| matched: boolean; | |
| } | |
| /** | |
| * Type-safe route parser that infers parameter types from the pattern | |
| * @param pattern Route pattern like "/users/:userId/posts/:postId" | |
| * @param path Actual URL path like "/users/123/posts/456" | |
| * @returns Typed object containing matched parameters | |
| */ | |
| export function parseRoute<T extends string>(pattern: T, path: string): RouteMatch<T> { | |
| // Normalize paths by removing trailing slashes (except root) | |
| const normalizeUrl = (url: string) => (url === '/' ? '/' : url.replace(/\/$/, '')); | |
| const normalizedPattern = normalizeUrl(pattern); | |
| const normalizedPath = normalizeUrl(path); | |
| // Split into segments | |
| const patternSegments = normalizedPattern.split('/').filter(Boolean); | |
| const pathSegments = normalizedPath.split('/').filter(Boolean); | |
| // Must have same number of segments | |
| if (patternSegments.length !== pathSegments.length) { | |
| return {params: {} as RouteParams<T>, matched: false}; | |
| } | |
| const params: Record<string, string> = {}; | |
| // Check each segment | |
| for (let i = 0; i < patternSegments.length; i++) { | |
| const patternSegment = patternSegments[i]; | |
| const pathSegment = pathSegments[i]; | |
| if (patternSegment.startsWith(':')) { | |
| // Parameter segment - extract parameter name and value | |
| const paramName = patternSegment.slice(1); | |
| params[paramName] = decodeURIComponent(pathSegment); | |
| } else if (patternSegment !== pathSegment) { | |
| // Static segment must match exactly | |
| return {params: {} as RouteParams<T>, matched: false}; | |
| } | |
| } | |
| return {params: params as RouteParams<T>, matched: true}; | |
| } | |
| /** | |
| * Type-safe router class with pattern-based type inference | |
| */ | |
| export class Router { | |
| private routes: Array<{ | |
| pattern: string; | |
| handler: (params: Record<string, string>) => any; | |
| }> = []; | |
| /** | |
| * Register a route pattern with a type-safe handler | |
| */ | |
| route<T extends string>(pattern: T, handler: (params: RouteParams<T>) => any): void { | |
| this.routes.push({ | |
| pattern, | |
| handler: handler as (params: Record<string, string>) => any, | |
| }); | |
| } | |
| /** | |
| * Match a path against registered routes and call the first matching handler | |
| */ | |
| match(path: string): any { | |
| for (const {pattern, handler} of this.routes) { | |
| const result = parseRoute(pattern, path); | |
| if (result.matched) { | |
| return handler(result.params); | |
| } | |
| } | |
| return null; | |
| } | |
| /** | |
| * Get all matching routes (useful for middleware scenarios) | |
| */ | |
| matchAll(path: string): Array<{ | |
| params: Record<string, string>; | |
| handler: (params: Record<string, string>) => any; | |
| }> { | |
| const matches = []; | |
| for (const {pattern, handler} of this.routes) { | |
| const result = parseRoute(pattern, path); | |
| if (result.matched) { | |
| matches.push({params: result.params, handler}); | |
| } | |
| } | |
| return matches; | |
| } | |
| } | |
| /** | |
| * Utility type for creating typed route handlers outside of the Router class | |
| */ | |
| export type RouteHandler<T extends string> = (params: RouteParams<T>) => any; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment