I am struggling with typescript overload resolution.
I am using googleapis library with typescript and trying to fetch all tagmanager accounts records. Since googleapis list function requires pagination if response body contains nextPageToken, I would like to create function that paginates all and grabs all records.
My idea is to create a curry function like this. It takes list function as an argument, and it keeps calling the list function with nextPageToken until nextPageToken is not included in returned data from the list function.
// ex. 1)
const allAccounts = await paginateAll(tagmanager.accounts.list)({
// specify tagmanager.accounts.list parameter
});
// ex. 2)
const allContainers = await paginateAll(tagmanager.accounts.containers.list)({
// specify tagmanager.accounts.containers.list parameter
});
I created a paginateAll
signature like below, but it seems like typescript doesn't resolve proper overload.
export const paginateAll = <P1, P2, R>(
list: (params?: P1, options?: P2) => Promise<R>,
): ((arg1: P1, arg2: P2) => Promise<Array<R>>) => async (a, b) => {
// some procedure...
};
const fetchAllAccounts = paginateAll(tagmanager.accounts.list)
^^^
=== ERROR ===
Argument of type '{ (params: Params$Resource$Accounts$List, options: StreamMethodOptions): GaxiosPromise<Readable>; (params?: Params$Resource$Accounts$List | undefined, options?: MethodOptions | undefined): GaxiosPromise<...>; (params: Params$Resource$Accounts$List, options: StreamMethodOptions | BodyResponseCallback<...>, callback: ...' is not assignable to parameter of type '(params?: BodyResponseCallback<Schema$ListAccountsResponse> | undefined, options?: unknown) => Promise<unknown>'.
Types of parameters 'params' and 'params' are incompatible.
Type 'BodyResponseCallback<Schema$ListAccountsResponse> | undefined' is not assignable to type 'Params$Resource$Accounts$List'.
Type 'undefined' is not assignable to type 'Params$Resource$Accounts$List'.
googleapis list
function has 6 overloads like below. I expect paginateAll
to pick the 2nd signature.
1. list(params: Params$Resource$Accounts$List, options: StreamMethodOptions): GaxiosPromise<Readable>;
2. list(params?: Params$Resource$Accounts$List, options?: MethodOptions): GaxiosPromise<Schema$ListAccountsResponse>;
3. list(params: Params$Resource$Accounts$List, options: StreamMethodOptions | BodyResponseCallback<Readable>, callback: BodyResponseCallback<Readable>): void;
4. list(params: Params$Resource$Accounts$List, options: MethodOptions | BodyResponseCallback<Schema$ListAccountsResponse>, callback: BodyResponseCallback<Schema$ListAccountsResponse>): void;
5. list(params: Params$Resource$Accounts$List, callback: BodyResponseCallback<Schema$ListAccountsResponse>): void;
6. list(callback: BodyResponseCallback<Schema$ListAccountsResponse>): void;
I would really appreciate if you guys tell me why this is happening...
==== UPDATE ====
I reproduced the error in my question with TypeScript Playground. (I made it not curry to make it simpler)
type Params = {
value1: string;
value2: string;
}
type Options1 = {
option1Value: string;
};
type Options2 = {
option2Value: string;
};
type Resonse1 = {
response1Value: string;
}
type Response2 = {
response2Value: string;
}
type Callback<T> = () => T
declare function func(params: Params, options: Options1): Promise<Resonse1>;
declare function func(params?: Params, options?: Options2): Promise<Response2>;
declare function func(params: Params, options: Options1 | Callback<Resonse1>, callback: Callback<Resonse1>): void;
declare function func(params: Params, options: Options2 | Callback<Response2>, callback: Callback<Response2>): void;
declare function func(params: Params, callback: Callback<Response2>): void;
declare function func(callback: Callback<Response2>): void;
const anotherFunc = async <P1, P2, R>(
fn: (params?: P1, options?: P2) => Promise<R>,
): Promise<R> => {
return fn();
}
const test = anotherFunc(func);
The compiler is not able to select an overload unless you actually call the overloaded function directly with inputs. If you pass the overloaded function to another function and try to use generic type inference, the compiler will not perform overload resolution the way you want. It just picks an overload, usually the first or the last one. This is a design limitation of TypeScript, see microsoft/TypeScript#30369 for more info.
The workaround here is for you to pick the signature you want to use. Here's one way:
const f: (params?: Params, options?: Options2) => Promise<Response2> = func;
const test = anotherFunc(f);
Here we assign func
to a variable f
of the type corresponding to a function which only has the call signature we wanted to infer. This assignment is allowed. Since f
is not overloaded, the call anotherFunc(f)
works as desired.