Search code examples
typescripttypescript-typingstypescript-generics

TypeScript does not infer type


I have the following function:

type DefaultEntity = {
  id: string;
  createdBy: string;
  [fieldName: string]: unknown;
};

abstract find<T, Schema extends string | (new () => T)>(
    schema: Schema,
    id: string,
): Promise<(Schema extends string ? DefaultEntity : T) | null>;

When I call it with a string as schema the return type is DefaultEntity. However if I use MyClass for the schema argument the return type is unknown not MyClass. It should be MyClass because T becomes MyClass. Did I miss something or is this something TypeScript just cannot do?


Solution

  • To infer the type of Schema you don't need an additional T type parameter. You just have to infer the instance-type of the class either by using the infer T keyword or using the built in helper InstanceType<Class>. Like this:

    type DefaultEntity = {
      id: string;
      createdBy: string;
      [fieldName: string]: unknown;
    };
    
    abstract class Base {
        // The first way
        abstract find<Schema extends string | (new () => any)>(
            schema: Schema,
            id: string,
        ): Promise<(Schema extends (new () => infer T) ? T : DefaultEntity) | null>;
    
        // this also works
        abstract find2<Schema extends string | (new () => any)>(
            schema: Schema,
            id: string,
        ): Promise<(Schema extends (new () => any) ? InstanceType<Schema> : DefaultEntity) | null>;
    }
    
    class UserEntity {
        id: string = ''
        createdBy: string = ''
        name: string = ''
    }
    
    class BaseImpl extends Base {
        async a () {
            (await this.find('asdfqer' , 'asdfasd')) satisfies DefaultEntity | null;
            (await this.find(UserEntity, 'asdf') ) satisfies UserEntity | null;
        }
    }
    

    Note: it's better to use overloads or even separate methods, i.e findByName(string, string) and findByEntity(Schema, string), it would be much easier to maintain and read in the future.