typescript - Issues with generic type narrowing on merged records - Stack Overflow

admin2025-04-30  0

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

转载请注明原文地址:http://anycun.com/QandA/1746027357a91538.html