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 0ca534c34..a2949c10e 100644 --- a/packages/openapi-react-query/src/index.ts +++ b/packages/openapi-react-query/src/index.ts @@ -31,13 +31,18 @@ type InferSelectReturnType = TSelect extends (data: TData) => in type InitWithUnknowns = Init & { [key: string]: unknown }; export type QueryKey< + Prefix, Paths extends Record>, Method extends HttpMethod, Path extends PathsWithMethod, Init = MaybeOptionalInit, -> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init]; +> = readonly [Prefix, Method, Path, ...(Init extends undefined ? [] : [Init])]; -export type QueryOptionsFunction>, Media extends MediaType> = < +export type QueryOptionsFunction< + Paths extends Record>, + Media extends MediaType, + Prefix = unknown, +> = < Method extends HttpMethod, Path extends PathsWithMethod, Init extends MaybeOptionalInit, @@ -47,7 +52,7 @@ export type QueryOptionsFunction, - QueryKey + QueryKey >, "queryKey" | "queryFn" >, @@ -63,7 +68,7 @@ export type QueryOptionsFunction, - QueryKey + QueryKey >, "queryFn" > & { @@ -72,14 +77,18 @@ export type QueryOptionsFunction, - QueryKey + QueryKey >["queryFn"], SkipToken | undefined >; } >; -export type UseQueryMethod>, Media extends MediaType> = < +export type UseQueryMethod< + Paths extends Record>, + Media extends MediaType, + Prefix = unknown, +> = < Method extends HttpMethod, Path extends PathsWithMethod, Init extends MaybeOptionalInit, @@ -89,7 +98,7 @@ export type UseQueryMethod>, Response["data"], Response["error"], InferSelectReturnType, - QueryKey + QueryKey >, "queryKey" | "queryFn" >, @@ -101,7 +110,11 @@ export type UseQueryMethod>, : [InitWithUnknowns, Options?, QueryClient?] ) => UseQueryResult, Response["error"]>; -export type UseInfiniteQueryMethod>, Media extends MediaType> = < +export type UseInfiniteQueryMethod< + Paths extends Record>, + Media extends MediaType, + Prefix = unknown, +> = < Method extends HttpMethod, Path extends PathsWithMethod, Init extends MaybeOptionalInit, @@ -112,7 +125,7 @@ export type UseInfiniteQueryMethod, Options["select"]>, Response["data"], - QueryKey, + QueryKey, unknown >, "queryKey" | "queryFn" @@ -130,7 +143,11 @@ export type UseInfiniteQueryMethod; -export type UseSuspenseQueryMethod>, Media extends MediaType> = < +export type UseSuspenseQueryMethod< + Paths extends Record>, + Media extends MediaType, + Prefix = unknown, +> = < Method extends HttpMethod, Path extends PathsWithMethod, Init extends MaybeOptionalInit, @@ -140,7 +157,7 @@ export type UseSuspenseQueryMethod, - QueryKey + QueryKey >, "queryKey" | "queryFn" >, @@ -165,11 +182,11 @@ export type UseMutationMethod UseMutationResult; -export interface OpenapiQueryClient { - queryOptions: QueryOptionsFunction; - useQuery: UseQueryMethod; - useSuspenseQuery: UseSuspenseQueryMethod; - useInfiniteQuery: UseInfiniteQueryMethod; +export interface OpenapiQueryClient { + queryOptions: QueryOptionsFunction; + useQuery: UseQueryMethod; + useSuspenseQuery: UseSuspenseQueryMethod; + useInfiniteQuery: UseInfiniteQueryMethod; useMutation: UseMutationMethod; } @@ -185,13 +202,14 @@ export type MethodResponse< : never; // TODO: Add the ability to bring queryClient as argument -export default function createClient( +export default function createClient( client: FetchClient, -): OpenapiQueryClient { + { prefixQueryKey }: { prefixQueryKey?: Prefix } = {}, +): 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, response } = await fn(path, { signal, ...(init as any) }); // TODO: find a way to avoid as any @@ -205,8 +223,9 @@ export default function createClient = (method, path, ...[init, options]) => ({ - queryKey: (init === undefined ? ([method, path] as const) : ([method, path, init] as const)) as QueryKey< + const queryOptions: QueryOptionsFunction = (method, path, ...[init, options]) => ({ + queryKey: [prefixQueryKey, method, path, ...(init === undefined ? [] : [init])] as const as QueryKey< + Prefix, Paths, typeof method, typeof path @@ -227,7 +246,7 @@ export default function createClient { + queryFn: async ({ queryKey: [, method, path, init], pageParam = 0, signal }) => { const mth = method.toUpperCase() as Uppercase; const fn = client[mth] as ClientMethod; const mergedInit = { diff --git a/packages/openapi-react-query/test/index.test.tsx b/packages/openapi-react-query/test/index.test.tsx index 4e2801a15..7bf9554ba 100644 --- a/packages/openapi-react-query/test/index.test.tsx +++ b/packages/openapi-react-query/test/index.test.tsx @@ -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({ 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); + expect(client2.queryOptions("get", "/foo").queryKey).toEqual([["cache2"], "get", "/foo"]); }); });