1

I have the following generic interface (full playground):

interface Fetcher<Params,Resp> {
  fetch(params: Params): Resp;
};

It's used as a function parameter with extra constraints applied to its generic parameters:

function paginateWithSearch<Params extends (PaginationParams & SearchParams),
  Resp extends SomeItemsResp>(fetcher: Fetcher<Params, Resp>){
    //do something      
}

All constraints are represented with simple objects such as:

type SearchParams = {
    query: string
};

The tricky part is I can't make this constraints actually work in practice:

//sample implementation with SearchParams only
const searchFetcher: Fetcher<SearchParams, SomeItemsResp> = {
  fetch: function(): SomeItemsResp {
    throw new Error("Function not implemented.");
  }
}

//Should throw error but works ok - not extending PaginationParams
paginateWithSearch(searchFetcher);

The only way that I've come up with is to infer generic parameters with conditional types and pass them to the function manually:

//now throws error if inferred manually
paginateWithSearch<FetcherParamsInfer<typeof searchFetcher>, FetcherRespInfer<typeof searchFetcher>>(
  searchFetcher
);

I should be missing something as it seems like a straightforward problem.

1 Answer 1

1

While it might be counter-intuitive, it works as expected.

Remember that a callback is not forced to use all provided arguments or their data:

declare function convertObjToString(obj: {
  toString: () => string;
}): void;

[new Date()].forEach(convertObjToString);
// Fine, even though it receives a Date with many more properties

Therefore it is totally fine passing a searchFetcher that takes only a SearchParams argument, while it will receive a PaginationParams & SearchParams (i.e. an object with more properties than necessary for this searchFetcher).

We could even pass a callback that takes no argument at all:

paginateWithSearch({
  fetch() { // No arg at all: okay
    return {
      items: []
    };
  }
});

Of course, that means this callback does not use any provided data, hence is bound to return unrelated response (here a fixed empty array for example).

In your sample, it would return something only related to search, disregarding pagination params.

It is up to the caller of paginateWithSearch to pass a callback that makes sense for the situation: whether it should be related to both pagination and search, only one of those, or none.

But it cannot use a callback that needs something beyond pagination and search: no sorting for example.

paginateWithSearch({
  // Error: Property 'sorting' is missing in type 'PaginationParams & SearchParams' but required in type '{ sorting: string; }'.
  fetch(params: { sorting: string }) {
    return { items: [] };
  }
})

Playground Link

Sign up to request clarification or add additional context in comments.

1 Comment

So, constraint works as an intersection with an actually provided type, thanks for clarifying it!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.