Last active
March 19, 2025 11:45
-
-
Save letelete/a3c33252b6451f82889799d22fd2536d to your computer and use it in GitHub Desktop.
Builds a path utility that allows replacing parameters in a given path template.
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 PathParams<T extends string> = | |
T extends `${infer _}:${infer Param}/${infer Rest}` | |
? { [K in keyof PathParams<Rest> | Param]: string } | |
: T extends `${infer _}:${infer Param}` | |
? Record<Param, string> | |
: void; | |
/** | |
* Builds a path utility that allows replacing parameters in a given path template. | |
* | |
* @template T - The path template string. | |
* @param path - The path template containing path parameters prefixed with `:`. | |
* @returns A tuple where the first element is the original path, | |
* and the second element is a function that replaces parameters with provided values. | |
*/ | |
function buildPath<T extends string>(path: T) { | |
/** | |
* Replaces parameters in the path with actual values. | |
* | |
* @param params - The object containing values for path parameters. | |
* @returns The final path with parameters replaced. | |
* @throws If a required parameter is missing. | |
*/ | |
const pathWithParams = (params: PathParams<T>) => { | |
return path.replace(/:([^/]+)/g, (_, key) => { | |
if (typeof params === 'object' && !(key in params)) { | |
throw new Error(`Missing parameter: ${key}`); | |
} | |
return String(params[key as keyof typeof params]); | |
}); | |
}; | |
return [path, pathWithParams] as const; | |
} | |
/** | |
* Generates an object mapping paths to functions that replace parameters. | |
* | |
* @template T - An array of path template strings. | |
* @param paths - An array of path templates. | |
* @returns An object where keys are paths | |
* and values are functions that generate the final URL by replacing parameters. | |
* | |
* @example ```ts | |
* const API_ENDPOINTS = buildPaths([ | |
* '/posts/', | |
* '/posts/:postId/', | |
* '/posts/:postId/comments/:commentId/', | |
* ]); | |
* | |
* console.log(API_ENDPOINTS['/posts/']()); // "/posts/" | |
* console.log(API_ENDPOINTS['/posts/:postId/']({ postId: '123' })); // "/posts/123/" | |
* console.log(API_ENDPOINTS['/posts/:postId/comments/:commentId/']({ postId: '123', commentId: '456' })); // "/posts/123/comments/456/" | |
* ``` | |
*/ | |
function buildPaths<T extends string[]>(paths: T) { | |
return Object.fromEntries(paths.map(buildPath)) as unknown as Readonly<{ | |
[Key in T[number]]: (params: PathParams<Key>) => string; | |
}>; | |
} | |
export { buildPaths }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment