You can specify a second generic to encompass the sub elements of the programConfig, in this example I constrained the inner ones to not allow a 3rd level of nesting since supporting arbitrary nesting would be annoying and hopefully not necessary
playground
interface BaseProgramConfig<T extends Record<string, unknown> >{
options?: {
[K in keyof T]: {
validator?: () => T[K]
}
},
handler?: (data: T) => void
}
interface programConfigWithCommands<T extends Record<string, unknown>, Sub extends Record<string, Record<string, unknown>>> extends BaseProgramConfig<T> {
commands?: {[K in keyof Sub]: BaseProgramConfig<Sub[K]>}
}
class Program<T extends Record<string, unknown>, Comms extends Record<string, Record<string, unknown>>> {
constructor(config: programConfigWithCommands<T,Comms>) { }
}
const foo = new Program({
options: {
'fruit': { validator: () => 'asdf' },
'animal': { validator: Number },
},
handler: ({ fruit, animal, thing }) => { // fruit and animal are properly typed based on options above
console.log(fruit, animal)
},
commands: {
foo: {
options: {
'tree': { validator: () => 'asdf' },
'person': {},
},
handler: ({ tree, person, thing }) => { // tree is typed as string, person is typed as unknown
console.log(tree, person)
},
}
}
});
Tif you want it to be the exact same.Tthen all nested instances use the same record as the root instance, which isn't what I want. The example should be more clearcommandsto be generic, but in TypeScript, only type aliases, interfaces, classes, constructors, functions, and methods can be generic, but properties, objects, and values can't. You would essentially needcommands?<U>: { [key: string]: programConfig<U> };See github.com/microsoft/TypeScript/issues/17574