Describing my issue with the title was challenging, but here it is:
I have several objects that follow this structure:
type TUtilityFunction = {[key: string]: <T>(a: T, b: any) => T}
For example:
class UtilityA{
DoSomeWork = function (arg1: SomeCustomType, arg2: string){
// do some work
return arg1;
}
class UtilityB{
DoSomeOtherWork = function (arg1: SomeCustomType, arg2: number){
// do some work
return arg1;
}
}
I want to merge these two classes into one while preserving intellisense with the new consolidated object.
The resulting object would combine functionalities of both previous classes:
{
DoSomeWork: (arg1: SomeCustomType, arg2: string) => SomeCustomType,
DoSomeOtherWork: (arg1: SomeOtherCustomType, arg2: number) => SomeCustomType
}
I attempted a solution similar to this Is it possible to infer return type of functions in mapped types in TypeScript?
but it focused on a single object of functions, whereas I have multiple. My best effort so far looks like this:
export const combineUtilities = function <
TUtilities extends {
[TKey in keyof TUtilities ]: Record<keyof TUtilities [keyof TUtilities ], TUtilityFunction>;
}
>(reducers: TUtilities ): Record<keyof TUtilities [keyof TUtilities ], TUtilityFunction> {
return (Object.keys(reducers) as Array<keyof TUtilities >).reduce(
<K extends keyof TUtilities >(
nextReducer: {[key in keyof TUtilities [keyof TUtilities ]]: TUtilityFunction},
reducerKey: K
) => {
return {...nextReducer, ...reducers[reducerKey]};
},
{} as Record<keyof TUtilities [keyof TUtilities ], TUtilityFunction>
);
};
Although TypeScript allows me to write this code, when I try to use the method:
const result = combineUtitilies({prop1: new UtilityA(), prop2: new UtilityB()});
the resulting type is:
const result: Record<never, TUtilityFunction>
which seems logical, but I'm stuck on how to infer the end result or somehow infer each utility class entering the combine method. The number of utility classes can vary as arguments but will always be at least 2. Is this even achievable? Any advice is greatly appreciated!
Update
The example I provided earlier was simplified to highlight the core problem. As mentioned by motto, simply spreading the two classes into a new object worked. However, I noticed when working with my actual code, I still encountered the "never" type.
This might be due to having a private variable with the same name in both classes. Now that I've resolved that, I need to find a way forward. This private variable is passed through the constructor and acts as a config variable. To elaborate further, imagine the two classes looking like this:
class UtilityA{
private readonly config: TSomeConfigType;
constructor(config: TSomeConfigType) {
this.config = config;
}
DoSomeWork = function (arg1: SomeCustomType, arg2: string){
// do some work
return arg1;
}
class UtilityB{
private readonly config: TSomeConfigType;
constructor(config: TSomeConfigType) {
this.config = config;
}
DoSomeOtherWork = function (arg1: SomeCustomType, arg2: number){
// do some work
return arg1;
}
}
When running:
const result = {...new UtilityA({}), ...new UtilityB({})}
the result is:
const result: {}
Which makes sense because it's combining two instances of config with the same property, as mentioned by motto. Sometimes this config property may be of a different type. So now I'm contemplating the best approach to merge the utilities while keeping each instance of config separate. Perhaps the combine function needs to dynamically rename each config instance to a unique name. But maybe that's excessive.
What would be a good strategy for this?