diff --git a/.changeset/good-buttons-bake.md b/.changeset/good-buttons-bake.md new file mode 100644 index 000000000..cb0e39e24 --- /dev/null +++ b/.changeset/good-buttons-bake.md @@ -0,0 +1,5 @@ +--- +'openapi-react-query': minor +--- + +Add `prefixQueryKey` to `createClient` to avoid query key collision between different openapi-fetch clients. diff --git a/packages/openapi-react-query/src/index.ts b/packages/openapi-react-query/src/index.ts index cbee066c0..a319ca5c2 100644 --- a/packages/openapi-react-query/src/index.ts +++ b/packages/openapi-react-query/src/index.ts @@ -1,35 +1,34 @@ -import { - type UseMutationOptions, - type UseMutationResult, - type UseQueryOptions, - type UseQueryResult, - type UseSuspenseQueryOptions, - type UseSuspenseQueryResult, - type QueryClient, - type QueryFunctionContext, - type SkipToken, - useMutation, - useQuery, - useSuspenseQuery, +import type { + QueryClient, + QueryFunctionContext, + SkipToken, + UseMutationOptions, + UseMutationResult, + UseQueryOptions, + UseQueryResult, + UseSuspenseQueryOptions, + UseSuspenseQueryResult, } from "@tanstack/react-query"; -import type { ClientMethod, FetchResponse, MaybeOptionalInit, Client as FetchClient } from "openapi-fetch"; +import { useMutation, useQuery, useSuspenseQuery } from "@tanstack/react-query"; +import type { ClientMethod, Client as FetchClient, FetchResponse, MaybeOptionalInit } from "openapi-fetch"; import type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typescript-helpers"; type InitWithUnknowns = Init & { [key: string]: unknown }; export type QueryKey< + Prefix, Paths extends Record>, Method extends HttpMethod, Path extends PathsWithMethod, -> = readonly [Method, Path, MaybeOptionalInit]; +> = readonly [Prefix, Method, Path, MaybeOptionalInit]; -export type QueryOptionsFunction>, Media extends MediaType> = < +export type QueryOptionsFunction>, Media extends MediaType, Prefix = unknown> = < Method extends HttpMethod, Path extends PathsWithMethod, Init extends MaybeOptionalInit, Response extends Required>, // note: Required is used to avoid repeating NonNullable in UseQuery types Options extends Omit< - UseQueryOptions>, + UseQueryOptions>, "queryKey" | "queryFn" >, >( @@ -40,23 +39,23 @@ export type QueryOptionsFunction, Options?] ) => NoInfer< Omit< - UseQueryOptions>, + UseQueryOptions>, "queryFn" > & { queryFn: Exclude< - UseQueryOptions>["queryFn"], + UseQueryOptions>["queryFn"], SkipToken | undefined >; } >; -export type UseQueryMethod>, Media extends MediaType> = < +export type UseQueryMethod>, Media extends MediaType, Prefix = unknown> = < Method extends HttpMethod, Path extends PathsWithMethod, Init extends MaybeOptionalInit, Response extends Required>, // note: Required is used to avoid repeating NonNullable in UseQuery types Options extends Omit< - UseQueryOptions>, + UseQueryOptions>, "queryKey" | "queryFn" >, >( @@ -67,13 +66,13 @@ export type UseQueryMethod>, : [InitWithUnknowns, Options?, QueryClient?] ) => UseQueryResult; -export type UseSuspenseQueryMethod>, Media extends MediaType> = < +export type UseSuspenseQueryMethod>, Media extends MediaType, Prefix = unknown> = < Method extends HttpMethod, Path extends PathsWithMethod, Init extends MaybeOptionalInit, Response extends Required>, // note: Required is used to avoid repeating NonNullable in UseQuery types Options extends Omit< - UseSuspenseQueryOptions>, + UseSuspenseQueryOptions>, "queryKey" | "queryFn" >, >( @@ -97,21 +96,22 @@ export type UseMutationMethod UseMutationResult; -export interface OpenapiQueryClient { - queryOptions: QueryOptionsFunction; - useQuery: UseQueryMethod; - useSuspenseQuery: UseSuspenseQueryMethod; +export interface OpenapiQueryClient { + queryOptions: QueryOptionsFunction; + useQuery: UseQueryMethod; + useSuspenseQuery: UseSuspenseQueryMethod; useMutation: UseMutationMethod; } // TODO: Add the ability to bring queryClient as argument -export default function createClient( +export default function createClient( client: FetchClient, -): OpenapiQueryClient { + { prefixQueryKey }: { prefixQueryKey?: T } = {}, +): OpenapiQueryClient { const queryFn = async >({ - queryKey: [method, path, init], + queryKey: [, method, path, init], signal, - }: QueryFunctionContext>) => { + }: QueryFunctionContext>) => { const mth = method.toUpperCase() as Uppercase; const fn = client[mth] as ClientMethod; const { data, error } = await fn(path, { signal, ...(init as any) }); // TODO: find a way to avoid as any @@ -122,8 +122,8 @@ export default function createClient = (method, path, ...[init, options]) => ({ - queryKey: [method, path, init as InitWithUnknowns] as const, + const queryOptions: QueryOptionsFunction = (method, path, ...[init, options]) => ({ + queryKey: [prefixQueryKey as T, method, path, init as InitWithUnknowns] as const, queryFn, ...options, }); diff --git a/packages/openapi-react-query/test/index.test.tsx b/packages/openapi-react-query/test/index.test.tsx index 294175df9..602626ada 100644 --- a/packages/openapi-react-query/test/index.test.tsx +++ b/packages/openapi-react-query/test/index.test.tsx @@ -231,6 +231,33 @@ describe("client", () => { expectTypeOf(result.current.data).toEqualTypeOf<"select(true)">(); expectTypeOf(result.current.error).toEqualTypeOf(); }); + + it('should differentiate queries by prefixQueryKey', async () => { + const fetchClient1 = createFetchClient({ baseUrl, fetch: fetchInfinite }); + const fetchClient2 = createFetchClient({ 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(); + expectTypeOf(client2.queryOptions('get', '/foo').queryKey[0]).toEqualTypeOf(); + + // client1 and client11 have the same query key, so 3 - 1 = 2 + expect(queryClient.isFetching()).toBe(2); + }); }); describe("useQuery", () => {