I know that the following example might be a little specific, however, I was not able to generalize it further. I would appreciate if you could help me narrow down the issue and I'd be happy to update the question afterwards.
In my apps, I often create class
wrappers for API data. I would like to be able to make some of these models generic and add more embedded properties to them. For example, there might be a ProductModel
, which extends the base ApiModel
, and then I would like to create a B2BProductModel
, which extends the ProductModel
and has some extra properties.
I pass the embedded property types through a generic:
class Model<TEmbeds extends Record<string, any> = {}> {
and then access them through the following method:
getEmbed<K extends keyof TEmbeds>(key: K): TEmbeds[K];
getEmbed<K extends keyof TEmbeds, M extends ConstructorType<Model>>(
key: K,
model: M
): TEmbeds[K] extends any[]
? InstanceType<M>[]
: TEmbeds[K] extends Record<any, InstanceType<M>>
? Record<keyof TEmbeds[K], InstanceType<M>>
: InstanceType<M> | null
getEmbed<K extends keyof TEmbeds, M extends ConstructorType<Model>>(
key: K,
model?: M
): TEmbeds[K] | InstanceType<M>[] | Record<keyof TEmbeds[K], InstanceType<M>> | InstanceType<M> | null {
The issue I ran into, though, is that when I want to extend the type of TEmbeds
, the getEmbed
method doesn't narrow down the type and instead, returns a union of all the possible types.
interface Embeds {
baz: SomeModel[]
}
type DisallowObjectProps<T extends Record<string, any>> = Partial<{ [key in keyof T]: never }>
type MergeLeft<A extends Record<string, any>, B> = B extends Record<string, any> ? {
[K in keyof A | keyof B]: K extends keyof A ? A[K] : K extends keyof B ? B[K] : never
} : A
class FooModel<
TExtraEmbeds extends Record<string, any> & DisallowObjectProps<Embeds>,
> extends Model<
MergeLeft<Embeds, TExtraEmbeds>
> {
get baz() {
// Issue: the type is not SomeModel[], but rather: SomeModel | SomeModel[] | Record<keyof MergeLeft<Embeds, TExtraEmbeds>["baz"], SomeModel> | null
return this.getEmbed('baz', SomeModel);
}
processBaz() {
return this.baz.map((item) => item.doSomething()); // <----
}
}
How could I fix this, please? Here is the TS Playground link: /mZ3aeW