Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

TypeScript – Pass Dynamic Property Name to Children

I’m working on a routing library with nested routing where I’m trying to define a child handler function which infers the parent paths. The reason being is I have another type which can infer dynamic path parameters from a string (e.g., users/:id to { id: string }); so, I want to be able to pass down the inferred path parameters from parent routes to any and all child routes in each child’s route handler function.

Here’s what I have so far:

type RawRouterDefn<Namespace extends string = ""> = {
    [K: string]: Handler<Namespace, typeof K> | RawRouterDefn<`${Namespace}/${typeof K}`>;
};

type Handler<DataPath extends string, Path extends string> = ((args: { dataPath: DataPath, path: Path }) => unknown)

type Router<Routes extends RawRouterDefn, DataPath extends string = ""> = {
    [K in keyof Routes & string]: Routes[K] extends CallableFunction
        ? Handler<DataPath, K>
        : Router<Routes[K], `${DataPath}/${K}`>;
};

function createRouter<T extends RawRouterDefn>(rawDefn: T): Router<T, ""> {
    // Implementation not important right now
    return {} as unknown as Router<T, "">
}

const router = createRouter({
    some: {
        deeply: {
            nested: {
                /**
                 * Here, dataPath should be inferred as `some/deeply/nested`,
                 * path should be `route`
                 */
                route: ({ dataPath, path }) => {}
            },
        },
    },
});

const route = router.some.deeply.nested.route;
// Correctly inferred **after** function applied, I need it within the router definition
type Params = Parameters<typeof route>[0]

(And the link to the playground)

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

I’m almost there, except dataPath should be inferred as /some/deeply/nested and not /${string}/${string}/${string} and path should be inferred as /route and not /${string}. This suggests to me there’s something I’m missing in the RawRouterDefn type.

So it seems that I can correctly narrow the shape of the string, but no further with my current approach.

Again, I need it to be inferred when used as function arguments, not after.

>Solution :

Quite close, just needed to do a little magic with the generics:

function createRouter<T extends RawRouterDefn, R extends Router<T, "">>(rawDefn: R): R {

And now it works: https://tsplay.dev/NDRE1W

You need another generic to tell TypeScript that what you get in the function is exactly what you return. Removing T extends RawRouterDefn and using R extends Router<RawRouterDefn, ""> breaks the inference, so uh, don’t do it.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading