I have x number of modules that are used in my AWS lambda functions such as a UserModule, NotificationsModule, CompanyModule, etc. I created an interface that describes the structure of the modules as the following:
interface Modules {
company: ICompanyModule
user: {
base: IUserModule
}
notifications: {
settings: INotificationSettingsModule
details: INotificationDetailsModule
}
}
Let's say I need the company and user base module in a function I would have to import both manually and instantiate them. If I had just a handful of functions that would work but that is not the case. To decrease the amount of imports and manual work I thought that I would create a factory method that would return an object with only the modules that the function needs. For example:
// Single module
const module = factory('company') // Type would be ICompanyModule
// Multiple Modules
const modules = factory(['company', 'user.base']) // Type would be { company: ICompanyModule, 'user.base': IUserModule }
The input to the factory method would be an array of keys in the Modules interface.
I'm about to get to a "solution" but I can't seem to fix a Type instantiation is excessively deep and possibly infinite error. Below is what I currently have:
// This gets all the keys and nested keys of my Modules interface
type Path<T, Key extends keyof T = keyof T> = Key extends string
? T[Key] extends Record<string, unknown>
?
| `${Key}.${Path<T[Key], Exclude<keyof T[Key], keyof Array<unknown>>> & string}`
| `${Key}.${Exclude<keyof T[Key], keyof Array<unknown>> & string}`
| Key
: Key
: never
// This gets the value for the Path wether it be nested or not
type PathValue<T, P extends Path<T>> = P extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? Rest extends Path<T[Key]>
? PathValue<T[Key], Rest>
: never
: never
: P extends keyof T
? T[P]
: never
// This is my ReturnType that takes in the a single key or array of keys and builds the object type
type ReturnObject<T extends Object, P extends Path<T> | Array<Path<T>>> = P extends Array<Path<T>>
? {
[Key in P[number]]: Key extends keyof T
? T[Key] extends Object
? Key extends Path<T[Key]>
? ReturnObject<T[Key], Key>
: T[Key]
: never
: Key extends `${infer K}.${infer Rest}`
? K extends keyof T
? T[K] extends Object
? Rest extends Path<T[K]>
? ReturnObject<T[K], Rest>
: never
: never
: never
: never
}
: P extends keyof T
? T[P]
: P extends `${infer K}.${infer Rest}`
? K extends keyof T
? T[K] extends Object
? Rest extends Path<T[K]>
? ReturnObject<T[K], Rest>
: never
: T[K]
: never
: never
// This is the factory function declaration
declare function factory<T extends ModuleFactory, P extends Path<T> | Array<Path<T>>>(
modules: P
): ReturnObject<T, P>
Now this works. When I create call factory with any of the given keys I get the correct object structure back but Typescript is throwing the Type instantiation is excessively deep and possibly infinite error in the ReturnType at { [Key in P[number]]: ... }
After a few days of messing around with this my best guess is that the issue is that P can extend Array<Path<T>> and Typescript is trying to handle the case the the array passed in could have a huge length and in turn cause almost infinite recursion.
You may use non-fallible path getter
Getto obtain the value and generic type validatorconformsto allow only proper pathshttps://tsplay.dev/mqyVZm