Capture deeply nested values using generics and mapped types

19 Views Asked by At

I'm trying to capture the value of a record that is nested in a property. However, I seem to only be able to capture the top-level types in the generic not nested ones.

Here's an example that demonstrating what I'm trying to do:

interface RunConfig<
    TContext extends Record<string, any>,
    TChildren extends Record<string, RunConfig<any, any>>
> {
    context: TContext;
    fn: (arg: { context: TContext }) => any;
    children: {
        [child in keyof TChildren]: RunConfig<
            TChildren[child]["context"],
            TChildren[child]["children"]
        >;
    };
}

function run<
    TContext extends Record<string, any>,
    TChildren extends Record<string, RunConfig<any, any>>
>(config: RunConfig<TContext, TChildren>) {
    const { context, fn, children } = config;
    fn({ context });
    for (const config of Object.values(children)) {
        run(config);
    }
}

run({
    context: {
        context: {
            key0: "value0",
        },
    },
    fn({ context }) {
        // Should be `{ key0: string; }`...and it is! Yay...
        console.log("child0", { context });
    },
    children: {
        child1: {
            context: {
                key1: "value1",
            },
            fn({ context }) {
                // Should be `{ key1: string; }`...but it is `any`. Booo...
                console.log("child1", { context });
            },
            children: {
                child11: {
                    context: {
                        key11: "value11",
                    },
                    fn({ context }) {
                        console.log("child11", { context });
                    },
                    children: {},
                },
            },
        },
        child2: {
            context: {
                key2: "value2",
            },
            fn({ context }) {
                console.log("child2", { context });
            },
            children: {},
        },
    },
});

The top level fn correctly infers the context type as { key0: string; } but the child fn inputs are typed as { context: any };

Ultimately, I'd want the children to receive their own context as well as their ancestors' context, but I need to get over this initial hump first.

Playground Link

0

There are 0 best solutions below