Skip to content

feat(openapi-react-query): add prefixQueryKey option #2357

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/good-buttons-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openapi-react-query': minor
---

Add `prefixQueryKey` to `createClient` to avoid query key collision between different openapi-fetch clients.
65 changes: 42 additions & 23 deletions packages/openapi-react-query/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,18 @@ type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => in
type InitWithUnknowns<Init> = Init & { [key: string]: unknown };

export type QueryKey<
Prefix,
Paths extends Record<string, Record<HttpMethod, {}>>,
Method extends HttpMethod,
Path extends PathsWithMethod<Paths, Method>,
Init = MaybeOptionalInit<Paths[Path], Method>,
> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];
> = readonly [Prefix, Method, Path, ...(Init extends undefined ? [] : [Init])];

export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
export type QueryOptionsFunction<
Paths extends Record<string, Record<HttpMethod, {}>>,
Media extends MediaType,
Prefix = unknown,
> = <
Method extends HttpMethod,
Path extends PathsWithMethod<Paths, Method>,
Init extends MaybeOptionalInit<Paths[Path], Method>,
Expand All @@ -47,7 +52,7 @@ export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod,
Response["data"],
Response["error"],
InferSelectReturnType<Response["data"], Options["select"]>,
QueryKey<Paths, Method, Path>
QueryKey<Prefix, Paths, Method, Path>
>,
"queryKey" | "queryFn"
>,
Expand All @@ -63,7 +68,7 @@ export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod,
Response["data"],
Response["error"],
InferSelectReturnType<Response["data"], Options["select"]>,
QueryKey<Paths, Method, Path>
QueryKey<Prefix, Paths, Method, Path>
>,
"queryFn"
> & {
Expand All @@ -72,14 +77,18 @@ export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod,
Response["data"],
Response["error"],
InferSelectReturnType<Response["data"], Options["select"]>,
QueryKey<Paths, Method, Path>
QueryKey<Prefix, Paths, Method, Path>
>["queryFn"],
SkipToken | undefined
>;
}
>;

export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
export type UseQueryMethod<
Paths extends Record<string, Record<HttpMethod, {}>>,
Media extends MediaType,
Prefix = unknown,
> = <
Method extends HttpMethod,
Path extends PathsWithMethod<Paths, Method>,
Init extends MaybeOptionalInit<Paths[Path], Method>,
Expand All @@ -89,7 +98,7 @@ export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>,
Response["data"],
Response["error"],
InferSelectReturnType<Response["data"], Options["select"]>,
QueryKey<Paths, Method, Path>
QueryKey<Prefix, Paths, Method, Path>
>,
"queryKey" | "queryFn"
>,
Expand All @@ -101,7 +110,11 @@ export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>,
: [InitWithUnknowns<Init>, Options?, QueryClient?]
) => UseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;

export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
export type UseInfiniteQueryMethod<
Paths extends Record<string, Record<HttpMethod, {}>>,
Media extends MediaType,
Prefix = unknown,
> = <
Method extends HttpMethod,
Path extends PathsWithMethod<Paths, Method>,
Init extends MaybeOptionalInit<Paths[Path], Method>,
Expand All @@ -112,7 +125,7 @@ export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMetho
Response["error"],
InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>,
Response["data"],
QueryKey<Paths, Method, Path>,
QueryKey<Prefix, Paths, Method, Path>,
unknown
>,
"queryKey" | "queryFn"
Expand All @@ -130,7 +143,11 @@ export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMetho
Response["error"]
>;

export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
export type UseSuspenseQueryMethod<
Paths extends Record<string, Record<HttpMethod, {}>>,
Media extends MediaType,
Prefix = unknown,
> = <
Method extends HttpMethod,
Path extends PathsWithMethod<Paths, Method>,
Init extends MaybeOptionalInit<Paths[Path], Method>,
Expand All @@ -140,7 +157,7 @@ export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMetho
Response["data"],
Response["error"],
InferSelectReturnType<Response["data"], Options["select"]>,
QueryKey<Paths, Method, Path>
QueryKey<Prefix, Paths, Method, Path>
>,
"queryKey" | "queryFn"
>,
Expand All @@ -165,11 +182,11 @@ export type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}
queryClient?: QueryClient,
) => UseMutationResult<Response["data"], Response["error"], Init>;

export interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {
queryOptions: QueryOptionsFunction<Paths, Media>;
useQuery: UseQueryMethod<Paths, Media>;
useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media>;
useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media>;
export interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType, Prefix = unknown> {
queryOptions: QueryOptionsFunction<Paths, Media, Prefix>;
useQuery: UseQueryMethod<Paths, Media, Prefix>;
useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media, Prefix>;
useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media, Prefix>;
useMutation: UseMutationMethod<Paths, Media>;
}

Expand All @@ -185,13 +202,14 @@ export type MethodResponse<
: never;

// TODO: Add the ability to bring queryClient as argument
export default function createClient<Paths extends {}, Media extends MediaType = MediaType>(
export default function createClient<Paths extends {}, Media extends MediaType = MediaType, Prefix = unknown>(
client: FetchClient<Paths, Media>,
): OpenapiQueryClient<Paths, Media> {
{ prefixQueryKey }: { prefixQueryKey?: Prefix } = {},
): OpenapiQueryClient<Paths, Media, Prefix> {
const queryFn = async <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>({
queryKey: [method, path, init],
queryKey: [, method, path, init],
signal,
}: QueryFunctionContext<QueryKey<Paths, Method, Path>>) => {
}: QueryFunctionContext<QueryKey<Prefix, Paths, Method, Path>>) => {
const mth = method.toUpperCase() as Uppercase<typeof method>;
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
const { data, error, response } = await fn(path, { signal, ...(init as any) }); // TODO: find a way to avoid as any
Expand All @@ -205,8 +223,9 @@ export default function createClient<Paths extends {}, Media extends MediaType =
return data;
};

const queryOptions: QueryOptionsFunction<Paths, Media> = (method, path, ...[init, options]) => ({
queryKey: (init === undefined ? ([method, path] as const) : ([method, path, init] as const)) as QueryKey<
const queryOptions: QueryOptionsFunction<Paths, Media, Prefix> = (method, path, ...[init, options]) => ({
queryKey: [prefixQueryKey, method, path, ...(init === undefined ? [] : [init])] as const as QueryKey<
Prefix,
Paths,
typeof method,
typeof path
Expand All @@ -227,7 +246,7 @@ export default function createClient<Paths extends {}, Media extends MediaType =
return useInfiniteQuery(
{
queryKey,
queryFn: async ({ queryKey: [method, path, init], pageParam = 0, signal }) => {
queryFn: async ({ queryKey: [, method, path, init], pageParam = 0, signal }) => {
const mth = method.toUpperCase() as Uppercase<typeof method>;
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
const mergedInit = {
Expand Down
30 changes: 29 additions & 1 deletion packages/openapi-react-query/test/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,35 @@ describe("client", () => {
});
const client = createClient(fetchClient);

expect(client.queryOptions("get", "/foo").queryKey.length).toBe(2);
expect(client.queryOptions("get", "/foo").queryKey.length).toBe(3);
});

it("should differentiate queries by prefixQueryKey", async () => {
const fetchClient1 = createFetchClient<minimalGetPaths>({ baseUrl, fetch: fetchInfinite });
const fetchClient2 = createFetchClient<minimalGetPaths>({ baseUrl, fetch: fetchInfinite });
const client1 = createClient(fetchClient1);
const client11 = createClient(fetchClient1);
const client2 = createClient(fetchClient2, { prefixQueryKey: ["cache2"] as const });

renderHook(
() => {
useQueries({
queries: [
client1.queryOptions("get", "/foo"),
client11.queryOptions("get", "/foo"),
client2.queryOptions("get", "/foo"),
],
});
},
{ wrapper },
);

expectTypeOf(client1.queryOptions("get", "/foo").queryKey[0]).toEqualTypeOf<unknown>();
expectTypeOf(client2.queryOptions("get", "/foo").queryKey[0]).toEqualTypeOf<readonly ["cache2"]>();

// client1 and client11 have the same query key, so 3 - 1 = 2
expect(queryClient.isFetching()).toBe(2);
expect(client2.queryOptions("get", "/foo").queryKey).toEqual([["cache2"], "get", "/foo"]);
});
});

Expand Down