1

TypeScript: Construct a typed interface based on a generic one, with the same methods but excluding the first parameter from all the functions.

Having a generic interface like:

interface GenericRepository {
  insertOne<E>(entity: Type<E>, body: E): Promise<string>;

  find<E>(entity: Type<E>, qm: Query<E>): Promise<E[]>;
}

interface Type<T> extends Function {
  new (...args: any[]): T;
}

type Query<E> = {
  [K in keyof E]: E[K];
};

class User {
  firstName?: string;
}

class Company {
  name?: string;
}

I'd like to be able to dynamically "produce" specific interfaces/types like:

interface UserRepository {
  insertOne(body: User): Promise<string>;

  find(qm: Query<User>): Promise<User[]>;
}

interface CompanyRepository {
  insertOne(body: Company): Promise<string>;

  find(qm: Query<Company>): Promise<Company[]>;
}

Below is what I've till now but it is not working because the entity type is not properly inferred (for the specific interfaces):

type SpecificRepository<E> = {
  [K in keyof GenericRepository]: Fn<GenericRepository[K], E>;
};

type Fn<A extends (...args: any[]) => any, E> = (...a: Params<Parameters<A>>) => ReturnType<A>;

type Params<T extends any[]> = T extends [infer F, ...infer L] ? L : never;


const userRepository = {} as SpecificRepository<User>;
const companyRepository = {} as SpecificRepository<Company>;

If I do (for testing the "produced" types):

async function someMethod() {
  const foundUsers = await userRepository.find({});
}

Then foundUsers is of type unknown[] but it should be User[].

How to make this properly?

4
  • 3
    Please consider modifying the code in this question so as to constitute a minimal reproducible example which, when dropped into a standalone IDE like The TypeScript Playground (link to code), clearly demonstrates the issue you are facing without unrelated errors (what is Type? what is Query? what is User? what is Company?). This will allow those who want to help you to immediately get to work solving the problem without first needing to re-create it. And it will make it so that any answer you get is testable against a well-defined use case. Commented May 16, 2021 at 14:28
  • 1
    The main issue here is that you want the compiler to convert a generic function type into a nongeneric function type automatically, by specifying the generic type parameter somehow; this is not possible programmatically at the type level; you need to drop to value level, which means you need to write redundant code like this. See this question for the general problem. If this does not accurately describe/address your issue, let me know. Commented May 16, 2021 at 14:52
  • @jcalz thank you for the comments. I updated the comment to show the complete code. I know it is possible at the value level, but I wondered if/how it would be at the type level. Thanks again! PD: so it is not possible because a typed (generic) parameter can not be obtained from a generic one (via types), right? Wondering if I can submit a request to the TS team for this feature. Commented May 17, 2021 at 13:34
  • 1
    Right, not possible now. ms/TS#1213 and ms/TS#17574 and maybe even ms/TS#14466 are related issues but I don't know if any of them specifically address "I want to specify the generic type parameter in a generic function from the outside" this way. You might want to open your own feature request, but don't be surprised if it's closed as a duplicate of something else. Commented May 17, 2021 at 15:11

1 Answer 1

0

you're nearly there! To help TypeScript's inference mechanics, you should make the interface generic, rather than just its member function. This way you won't need to subclass either:

interface GenericRepository<E> {
  insertOne(entity: Type<E>, body: E): Promise<string>;

  find(entity: Type<E>, qm: Query<E>): Promise<E[]>;
}

interface Type<T> extends Function {
  new (...args: any[]): T;
}

type Query<E> = {
  [K in keyof E]: E[K];
};

class User {
  firstName?: string;
}

class Company {
  name?: string;
}

const userRepository = {} as GenericRepository<User>;
const companyRepository = {} as GenericRepository<Company>;

async function someMethod() {
  const foundUsers = await userRepository.find({});
}
Sign up to request clarification or add additional context in comments.

1 Comment

that won't work because find and insertOne would still need the entity parameter (that is the one I want to remove). Please notice that the main point is: GenericRepository interface does already exists, and I want to automatically get a typed (via types/interfaces) repository interface from it but excluding the entity parameter from the methods. Thanks.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.