I'm replacing my fetch calls with wretch and I want to type the resolver so that I can migrate cleanly and ensure type safety for my api calls.
I've discovered two ways to do it but neither are ideal.
At the top layer
import wretch from "wretch";
import FormDataAddon from "wretch/addons/formData";
import QueryStringAddon from "wretch/addons/queryString";
export const apiUntyped = wretch("api-url")
.errorType("json")
.addon(FormDataAddon)
.addon(QueryStringAddon); // no .resolve() helper
Usage
export const getUsers = async (): Promise<User[]> =>
await apiUntyped.get("/users").json<User[]>(); // <---- add the type arg here
This works but then I have to call .json<T>()
for every api call which I was hoping to avoid by using the .resolve()
helper. However, that creates other problems.
At the top layer
import wretch, { Wretch } from "wretch";
import FormData, { FormDataAddon } from "wretch/addons/formData";
import QueryString, { QueryStringAddon } from "wretch/addons/queryString";
export function apiTyped<T>(): QueryStringAddon &
FormDataAddon &
Wretch<FormDataAddon & QueryStringAddon, unknown, Promise<Awaited<T>>> {
return wretch("api-url")
.errorType("json")
.resolve((r) => r.json<T>()) // <--- add the type arg here
.addon(FormData)
.addon(QueryString);
}
Usage
export const getUsers = async (): Promise<User[]> =>
await apiTyped<User[]>().get("/users"); // no .json() call
Now I don't have to call the .json()
helper but as you can see the explicit return type for the apiTyped
factory is horrendous and gets worse the more addons are used. Obviously I could simply delete the explicit return type and it'll work but I'm not sure that's necessarily any better :)
import wretch from "wretch";
import FormDataAddon from "wretch/addons/formData";
import QueryStringAddon from "wretch/addons/queryString";
export const apiUntypedWithResolver = wretch("api-url")
.errorType("json")
.resolve((r) => r.json())
.addon(FormDataAddon)
.addon(QueryStringAddon);
This won't work
export const getUsers = async (): Promise<User[]> =>
await apiUntypedWithResolver.get("/users");
"unknown is not assignable to type User[]"
And this won't work
export const getUsers = async (): Promise<User[]> =>
await apiUntypedWithResolver.get("/users").json<User[]>();
"Expected 0 type arguments but got 1".
When I was using fetch I didn't explicitly type the fetch()
call and typescript simply accepted the explicit typing of Promise<User[]>
. Then when I was developing downstream Typescript treated the value as a User[].
So is this how wretch was intended to be used? This is my first time using wretch so I might just be missing something. I searched the web but couldn't find any typescript examples and zero threads on this topic. The typescript documentation for wretch is a vey bare bones reference so I wasn't able to glean anything from that.
Aside from the syntax, is ensuring the static type safety here overkill? Should I add a separate step after these functions return to narrow the types so they are easier to work with downstream?
Thanks for taking a look and for any help or advice.
For anyone in the future who is curious, I ended up doing the type inference as part of parsing the response (with zod in this case).
export async function getUsers(): Promise<User[]> {
const res = await api.get("/users");
const parsed = userSchema.array().safeParse(res);
if (!parsed.success) {
throw new Error(`Users "${res}" is not valid.`);
}
return parsed.data;
}
Happy coding!