Skip to content

Instantly share code, notes, and snippets.

@letelete
Last active March 19, 2025 11:45
Show Gist options
  • Save letelete/a3c33252b6451f82889799d22fd2536d to your computer and use it in GitHub Desktop.
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.
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