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.
CodePudding user response:
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: [] };
}
})