The following Paths
type represents a number of url paths broken up by path segment.
type PathSegment = {
path: string;
children: Paths;
}
type Paths = Record<string, PathSegment | string>;
The following is an example object matching this type:
const paths: Paths = {
home: "/",
profile: "/profile",
settings: {
path: "/settings",
children: {
general: "/general",
account: "/account",
}
}
}
Is it possible to create a function that merges path segments together in a type-safe way by providing the keys of the paths that should be merged? For example, given the paths
object above, I could do something like:
const accountSettingsPath = mergePathSegments(paths, ["settings", "account"]);
console.log(accountSettingsPath); // outputs "/settings/account"
The keys passed in via the array should be known at compile-time so that a typo can't be made. And the array can't be bigger than the number of keys that are available. Is something like this possible?
You can build types for both input and output path using recursion:
type PathSegment = {
path: string;
children: Paths;
}
type Paths = Record<string, PathSegment | string>;
const paths = {
home: "/",
profile: "/profile",
settings: {
path: "/settings",
children: {
general: "/general",
account: "/account",
}
}
} as const satisfies Paths;
type Breadcrumbs<T extends Paths> =
{[K in keyof T]: T[K] extends string ?
[K] :
T[K] extends PathSegment ?
[K, ... Breadcrumbs<T[K]['children']>] :
never
}[keyof T];
type MakePath<T extends Paths, B extends Breadcrumbs<T>> =
{[K in B[0]]: T[K] extends string ?
T[K] :
T[K] extends PathSegment ?
`${T[K]['path']}${B extends [string, ...infer C] ? C extends Breadcrumbs<T[K]['children']> ? MakePath<T[K]['children'], C> : '' : ''}` :
never
}[B[0]];
declare function mergePathSegments<T extends Paths, const B extends Breadcrumbs<T>>(paths: T, breadcrumbs: B): MakePath<T, B> extends `${infer A}` ? A : string
const accountSettingsPath = mergePathSegments(paths, ["settings", "account"]); // "/settings/account"
console.log(accountSettingsPath); // outputs "/settings/account"