Skip to content

Instantly share code, notes, and snippets.

@AspireOne
Last active August 1, 2024 13:57
Show Gist options
  • Save AspireOne/d6727d1898ca3606241bbc206b37a299 to your computer and use it in GitHub Desktop.
Save AspireOne/d6727d1898ca3606241bbc206b37a299 to your computer and use it in GitHub Desktop.
Typesafe react-query wrapper for openapi-fetch
import type {
FetchError,
ResponseType,
MutationVariablesType,
PathsWithDefinedMethods,
} from "@/types/api-mutators";
import { client } from "@/api/common";
import { createApiMutation } from "@/helpers/create-api-mutation.helper";
// NOTE: WILL NOT WORK WITH "PATH" arguments (if the target API endpoint is e.g. "/user/:id").
export function createTypedApiMutation<
TMethod extends "post" | "patch" | "put" | "delete",
TPath extends PathsWithDefinedMethods<TMethod>,
>(method: TMethod, path: TPath) {
type Response = ResponseType<TPath, TMethod>;
type Variables = MutationVariablesType<TMethod, TPath>;
return createApiMutation<Response, Variables, FetchError>({
mutationKey: [path, method],
mutationFn: async (variables) => {
const methodUpper = method.toUpperCase() as Uppercase<TMethod>;
// ? Throw error?
return (client as any)
// TODO(fix): Here, we should also somehow pass in "path" parameter.
[methodUpper](path, { body: variables ?? {} })
.then((response: any) => response.data);
},
});
}
import type { HttpMethod } from "openapi-typescript-helpers";
import type {
FetchError,
PathsWithDefinedMethods,
ResponseType,
VariablesType,
} from "@/types/api-mutators";
import { client } from "@/api/common";
import { createApiQuery } from "@/helpers/create-api-query.helper";
import type { CreateQueryOptions } from "react-query-kit";
type HttpMethodUppercase = Uppercase<HttpMethod>;
export function createTypedApiQuery<
TMethod extends "get" | "delete" | "options" | "head",
TPath extends PathsWithDefinedMethods<TMethod>,
TCustomResponse = ResponseType<TPath, TMethod>,
// prettier-ignore
TOptions = CreateQueryOptions<TCustomResponse, VariablesType<TPath, TMethod>, FetchError>,
>(method: TMethod, path: TPath, options?: TOptions) {
type Response = TCustomResponse;
type Variables = VariablesType<TPath, TMethod>;
const upperMethod = method.toUpperCase() as HttpMethodUppercase;
return createApiQuery<Response, Variables, FetchError>({
queryKey: [path],
...options,
fetcher: async (variables) => {
return (client[upperMethod] as any)(path, { params: variables }).then(
(response: any) => response.data,
);
},
});
}
export type FetchError = {
// For HTTP errors
response: Response;
message: string;
};
export type DefaultStatusCode<M extends HttpMethod> = M extends "post" ? 201 : 200;
export type PathsWithDefinedMethods<M extends HttpMethod> = {
[K in keyof paths]: {
[P in M]: paths[K] extends { [key in P]: infer MethodType }
? MethodType extends never
? never
: K
: never;
}[M];
}[keyof paths];
// Updated ResponseType that allows manual status code specification and includes | undefined
export type ResponseType<
P extends keyof paths,
M extends HttpMethod,
S extends number = DefaultStatusCode<M>,
> = paths[P][M] extends {
responses: { [K in S]: { content: { "application/json": infer R } } };
}
? R | undefined
: undefined;
export type VariablesType<
P extends keyof paths,
M extends HttpMethod,
> = paths[P][M] extends {
parameters: infer Params;
}
? (Params extends { query: infer Q } ? { query: Q } : {}) &
(Params extends { path: infer Path } ? { path: Path } : {})
: never;
export type MutationVariablesType<
M extends HttpMethod,
P extends PathsWithDefinedMethods<M>,
> = paths[P][M] extends {
requestBody: infer Body;
}
? Body extends { content: infer C }
? C extends { "application/json": infer V }
? V
: never
: never
: never;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment