From 9311c3f9d8af10682fb4b2764fe3fa34d0e55761 Mon Sep 17 00:00:00 2001 From: Kim-Jaemin420 Date: Wed, 1 Apr 2026 18:16:51 +0900 Subject: [PATCH 1/2] feat(vue-query): add prefetch composables - Add usePrefetchQuery and usePrefetchInfiniteQuery to vue-query. - Includes runtime and type tests, plus a changeset --- .changeset/puny-poems-tell.md | 5 + .../usePrefetchInfiniteQuery.test-d.ts | 96 +++++++++++ .../usePrefetchInfiniteQuery.test.ts | 157 ++++++++++++++++++ .../src/__tests__/usePrefetchQuery.test-d.ts | 72 ++++++++ .../src/__tests__/usePrefetchQuery.test.ts | 122 ++++++++++++++ packages/vue-query/src/index.ts | 4 + .../vue-query/src/usePrefetchInfiniteQuery.ts | 113 +++++++++++++ packages/vue-query/src/usePrefetchQuery.ts | 69 ++++++++ 8 files changed, 638 insertions(+) create mode 100644 .changeset/puny-poems-tell.md create mode 100644 packages/vue-query/src/__tests__/usePrefetchInfiniteQuery.test-d.ts create mode 100644 packages/vue-query/src/__tests__/usePrefetchInfiniteQuery.test.ts create mode 100644 packages/vue-query/src/__tests__/usePrefetchQuery.test-d.ts create mode 100644 packages/vue-query/src/__tests__/usePrefetchQuery.test.ts create mode 100644 packages/vue-query/src/usePrefetchInfiniteQuery.ts create mode 100644 packages/vue-query/src/usePrefetchQuery.ts diff --git a/.changeset/puny-poems-tell.md b/.changeset/puny-poems-tell.md new file mode 100644 index 00000000000..a677e7d5a64 --- /dev/null +++ b/.changeset/puny-poems-tell.md @@ -0,0 +1,5 @@ +--- +'@tanstack/vue-query': minor +--- + +Add usePrefetchQuery and usePrefetchInfiniteQuery to vue-query. diff --git a/packages/vue-query/src/__tests__/usePrefetchInfiniteQuery.test-d.ts b/packages/vue-query/src/__tests__/usePrefetchInfiniteQuery.test-d.ts new file mode 100644 index 00000000000..99dbb8db4ae --- /dev/null +++ b/packages/vue-query/src/__tests__/usePrefetchInfiniteQuery.test-d.ts @@ -0,0 +1,96 @@ +import { assertType, describe, expectTypeOf, it } from 'vitest' +import { ref } from 'vue-demi' +import { skipToken } from '@tanstack/query-core' +import { usePrefetchInfiniteQuery } from '..' + +describe('usePrefetchInfiniteQuery', () => { + it('should return nothing', () => { + const result = usePrefetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + initialPageParam: 1, + getNextPageParam: () => 1, + }) + + expectTypeOf(result).toEqualTypeOf() + }) + + it('should require initialPageParam and getNextPageParam', () => { + assertType( + // @ts-expect-error TS2345 + usePrefetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }), + ) + }) + + it('should not allow refetchInterval, enabled or throwOnError options', () => { + assertType( + usePrefetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + initialPageParam: 1, + getNextPageParam: () => 1, + // @ts-expect-error TS2353 + refetchInterval: 1000, + }), + ) + + assertType( + usePrefetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + initialPageParam: 1, + getNextPageParam: () => 1, + // @ts-expect-error TS2353 + enabled: true, + }), + ) + + assertType( + usePrefetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + initialPageParam: 1, + getNextPageParam: () => 1, + // @ts-expect-error TS2353 + throwOnError: true, + }), + ) + }) + + it('should accept refs in infinite query options', () => { + assertType( + usePrefetchInfiniteQuery({ + queryKey: ['key', ref('id')], + queryFn: () => Promise.resolve(5), + initialPageParam: ref(1), + getNextPageParam: () => 1, + staleTime: ref(1000), + }), + ) + }) + + it('should not allow skipToken in queryFn', () => { + assertType( + usePrefetchInfiniteQuery({ + queryKey: ['key'], + initialPageParam: 1, + getNextPageParam: () => 1, + // @ts-expect-error + queryFn: skipToken, + }), + ) + + assertType( + usePrefetchInfiniteQuery({ + queryKey: ['key'], + initialPageParam: 1, + getNextPageParam: () => 1, + // @ts-expect-error + queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5), + }), + ) + }) +}) diff --git a/packages/vue-query/src/__tests__/usePrefetchInfiniteQuery.test.ts b/packages/vue-query/src/__tests__/usePrefetchInfiniteQuery.test.ts new file mode 100644 index 00000000000..63a4486f39a --- /dev/null +++ b/packages/vue-query/src/__tests__/usePrefetchInfiniteQuery.test.ts @@ -0,0 +1,157 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' +import { nextTick, ref } from 'vue-demi' +import { QueryClient } from '../queryClient' +import { usePrefetchInfiniteQuery } from '../usePrefetchInfiniteQuery' + +describe('usePrefetchInfiniteQuery', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + test('should prefetch infinite query if query state does not exist', () => { + const queryClient = new QueryClient() + const prefetchInfiniteQuerySpy = vi.spyOn( + queryClient, + 'prefetchInfiniteQuery', + ) + const queryFn = vi.fn(() => + Promise.resolve({ data: 'prefetched', currentPage: 1 }), + ) + + usePrefetchInfiniteQuery( + { + queryKey: ['prefetch-infinite-query'], + queryFn, + initialPageParam: 1, + getNextPageParam: () => undefined, + }, + queryClient, + ) + + expect(prefetchInfiniteQuerySpy).toHaveBeenCalledTimes(1) + expect(prefetchInfiniteQuerySpy).toHaveBeenCalledWith({ + queryKey: ['prefetch-infinite-query'], + queryFn, + initialPageParam: 1, + getNextPageParam: expect.any(Function), + }) + }) + + test('should not prefetch infinite query if query state exists', () => { + const queryClient = new QueryClient() + const prefetchInfiniteQuerySpy = vi.spyOn( + queryClient, + 'prefetchInfiniteQuery', + ) + const queryFn = vi.fn(() => + Promise.resolve({ data: 'prefetched', currentPage: 1 }), + ) + + queryClient.setQueryData(['prefetch-infinite-query-existing'], { + pages: [{ data: 'existing', currentPage: 1 }], + pageParams: [1], + }) + + usePrefetchInfiniteQuery( + { + queryKey: ['prefetch-infinite-query-existing'], + queryFn, + initialPageParam: 1, + getNextPageParam: () => undefined, + }, + queryClient, + ) + + expect(prefetchInfiniteQuerySpy).not.toHaveBeenCalled() + }) + + test('should unwrap refs in infinite query options', () => { + const queryClient = new QueryClient() + const prefetchInfiniteQuerySpy = vi.spyOn( + queryClient, + 'prefetchInfiniteQuery', + ) + const nestedRef = ref('value') + + usePrefetchInfiniteQuery( + { + queryKey: ['prefetch-infinite-query-ref', nestedRef], + queryFn: () => Promise.resolve({ data: 'prefetched', currentPage: 1 }), + initialPageParam: 1, + getNextPageParam: () => undefined, + }, + queryClient, + ) + + expect(prefetchInfiniteQuerySpy).toHaveBeenCalledWith( + expect.objectContaining({ + queryKey: ['prefetch-infinite-query-ref', 'value'], + }), + ) + }) + + test('should prefetch infinite query again when query key changes reactively', async () => { + const queryClient = new QueryClient() + const prefetchInfiniteQuerySpy = vi.spyOn( + queryClient, + 'prefetchInfiniteQuery', + ) + const keyRef = ref('first') + + usePrefetchInfiniteQuery( + () => ({ + queryKey: ['prefetch-infinite-query-reactive', keyRef.value], + queryFn: () => Promise.resolve({ data: keyRef.value, currentPage: 1 }), + initialPageParam: 1, + getNextPageParam: () => undefined, + }), + queryClient, + ) + + expect(prefetchInfiniteQuerySpy).toHaveBeenCalledTimes(1) + expect(prefetchInfiniteQuerySpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + queryKey: ['prefetch-infinite-query-reactive', 'first'], + }), + ) + + keyRef.value = 'second' + await nextTick() + + expect(prefetchInfiniteQuerySpy).toHaveBeenCalledTimes(2) + expect(prefetchInfiniteQuerySpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + queryKey: ['prefetch-infinite-query-reactive', 'second'], + }), + ) + }) + + test('should warn when used outside of setup function in development mode', () => { + vi.stubEnv('NODE_ENV', 'development') + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + + try { + usePrefetchInfiniteQuery( + { + queryKey: ['outside-scope-prefetch-infinite-query'], + queryFn: () => + Promise.resolve({ data: 'prefetched', currentPage: 1 }), + initialPageParam: 1, + getNextPageParam: () => undefined, + }, + new QueryClient(), + ) + + expect(warnSpy).toHaveBeenCalledWith( + 'vue-query composable like "useQuery()" should only be used inside a "setup()" function or a running effect scope. They might otherwise lead to memory leaks.', + ) + } finally { + warnSpy.mockRestore() + vi.unstubAllEnvs() + } + }) +}) diff --git a/packages/vue-query/src/__tests__/usePrefetchQuery.test-d.ts b/packages/vue-query/src/__tests__/usePrefetchQuery.test-d.ts new file mode 100644 index 00000000000..fd2f4cda1eb --- /dev/null +++ b/packages/vue-query/src/__tests__/usePrefetchQuery.test-d.ts @@ -0,0 +1,72 @@ +import { assertType, describe, expectTypeOf, it } from 'vitest' +import { ref } from 'vue-demi' +import { skipToken } from '@tanstack/query-core' +import { usePrefetchQuery } from '..' + +describe('usePrefetchQuery', () => { + it('should return nothing', () => { + const result = usePrefetchQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + expectTypeOf(result).toEqualTypeOf() + }) + + it('should not allow refetchInterval, enabled or throwOnError options', () => { + assertType( + usePrefetchQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + // @ts-expect-error TS2353 + refetchInterval: 1000, + }), + ) + + assertType( + usePrefetchQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + // @ts-expect-error TS2353 + enabled: true, + }), + ) + + assertType( + usePrefetchQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + // @ts-expect-error TS2353 + throwOnError: true, + }), + ) + }) + + it('should accept refs in query options', () => { + assertType( + usePrefetchQuery({ + queryKey: ['key', ref('id')], + queryFn: () => Promise.resolve(5), + staleTime: ref(1000), + }), + ) + }) + + it('should not allow skipToken in queryFn', () => { + assertType( + usePrefetchQuery({ + queryKey: ['key'], + // @ts-expect-error + queryFn: skipToken, + }), + ) + + assertType( + usePrefetchQuery({ + queryKey: ['key'], + // @ts-expect-error + queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5), + }), + ) + }) +}) diff --git a/packages/vue-query/src/__tests__/usePrefetchQuery.test.ts b/packages/vue-query/src/__tests__/usePrefetchQuery.test.ts new file mode 100644 index 00000000000..996d853ef64 --- /dev/null +++ b/packages/vue-query/src/__tests__/usePrefetchQuery.test.ts @@ -0,0 +1,122 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' +import { nextTick, ref } from 'vue-demi' +import { QueryClient } from '../queryClient' +import { usePrefetchQuery } from '../usePrefetchQuery' + +describe('usePrefetchQuery', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + test('should prefetch query if query state does not exist', () => { + const queryClient = new QueryClient() + const prefetchQuerySpy = vi.spyOn(queryClient, 'prefetchQuery') + const queryFn = vi.fn(() => Promise.resolve('prefetched')) + + usePrefetchQuery( + { + queryKey: ['prefetch-query'], + queryFn, + }, + queryClient, + ) + + expect(prefetchQuerySpy).toHaveBeenCalledTimes(1) + expect(prefetchQuerySpy).toHaveBeenCalledWith({ + queryKey: ['prefetch-query'], + queryFn, + }) + }) + + test('should not prefetch query if query state exists', () => { + const queryClient = new QueryClient() + const prefetchQuerySpy = vi.spyOn(queryClient, 'prefetchQuery') + const queryFn = vi.fn(() => Promise.resolve('prefetched')) + queryClient.setQueryData(['prefetch-query-existing'], 'existing') + + usePrefetchQuery( + { + queryKey: ['prefetch-query-existing'], + queryFn, + }, + queryClient, + ) + + expect(prefetchQuerySpy).not.toHaveBeenCalled() + }) + + test('should unwrap refs in query options', () => { + const queryClient = new QueryClient() + const prefetchQuerySpy = vi.spyOn(queryClient, 'prefetchQuery') + const nestedRef = ref('value') + + usePrefetchQuery( + { + queryKey: ['prefetch-query-ref', nestedRef], + queryFn: () => Promise.resolve('prefetched'), + }, + queryClient, + ) + + expect(prefetchQuerySpy).toHaveBeenCalledWith( + expect.objectContaining({ + queryKey: ['prefetch-query-ref', 'value'], + }), + ) + }) + + test('should prefetch again when query key changes reactively', async () => { + const queryClient = new QueryClient() + const prefetchQuerySpy = vi.spyOn(queryClient, 'prefetchQuery') + const keyRef = ref('first') + + usePrefetchQuery( + () => ({ + queryKey: ['prefetch-query-reactive', keyRef.value], + queryFn: () => Promise.resolve(keyRef.value), + }), + queryClient, + ) + + expect(prefetchQuerySpy).toHaveBeenCalledTimes(1) + expect(prefetchQuerySpy).toHaveBeenLastCalledWith({ + queryKey: ['prefetch-query-reactive', 'first'], + queryFn: expect.any(Function), + }) + + keyRef.value = 'second' + await nextTick() + + expect(prefetchQuerySpy).toHaveBeenCalledTimes(2) + expect(prefetchQuerySpy).toHaveBeenLastCalledWith({ + queryKey: ['prefetch-query-reactive', 'second'], + queryFn: expect.any(Function), + }) + }) + + test('should warn when used outside of setup function in development mode', () => { + vi.stubEnv('NODE_ENV', 'development') + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + + try { + usePrefetchQuery( + { + queryKey: ['outside-scope-prefetch-query'], + queryFn: () => Promise.resolve('prefetched'), + }, + new QueryClient(), + ) + + expect(warnSpy).toHaveBeenCalledWith( + 'vue-query composable like "useQuery()" should only be used inside a "setup()" function or a running effect scope. They might otherwise lead to memory leaks.', + ) + } finally { + warnSpy.mockRestore() + vi.unstubAllEnvs() + } + }) +}) diff --git a/packages/vue-query/src/index.ts b/packages/vue-query/src/index.ts index 5ea6e26f836..61eb3c4452e 100644 --- a/packages/vue-query/src/index.ts +++ b/packages/vue-query/src/index.ts @@ -15,11 +15,15 @@ export { MutationCache } from './mutationCache' export { useQuery } from './useQuery' export { useQueries } from './useQueries' export { useInfiniteQuery } from './useInfiniteQuery' +export { usePrefetchQuery } from './usePrefetchQuery' +export { usePrefetchInfiniteQuery } from './usePrefetchInfiniteQuery' export { useMutation } from './useMutation' export { useIsFetching } from './useIsFetching' export { useIsMutating, useMutationState } from './useMutationState' export { VUE_QUERY_CLIENT } from './utils' +export type { UsePrefetchQueryOptions } from './usePrefetchQuery' +export type { UsePrefetchInfiniteQueryOptions } from './usePrefetchInfiniteQuery' export type { UseQueryOptions, UseQueryReturnType, diff --git a/packages/vue-query/src/usePrefetchInfiniteQuery.ts b/packages/vue-query/src/usePrefetchInfiniteQuery.ts new file mode 100644 index 00000000000..ff6ff23c0aa --- /dev/null +++ b/packages/vue-query/src/usePrefetchInfiniteQuery.ts @@ -0,0 +1,113 @@ +import { getCurrentScope, unref, watchEffect } from 'vue-demi' +import { useQueryClient } from './useQueryClient' +import { cloneDeepUnref } from './utils' +import type { + DefaultError, + FetchInfiniteQueryOptions, + FetchQueryOptions, + GetNextPageParamFunction, + InfiniteData, + InitialPageParam, + OmitKeyof, + QueryKey, + SkipToken, +} from '@tanstack/query-core' +import type { QueryClient } from './queryClient' +import type { MaybeRefDeep, MaybeRefOrGetter } from './types' + +type PrefetchInfinitePages = + | { + pages?: never + getNextPageParam?: GetNextPageParamFunction + } + | { + pages: number + getNextPageParam: GetNextPageParamFunction + } + +export type UsePrefetchInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey extends QueryKey, + TPageParam, +> = OmitKeyof< + FetchQueryOptions< + TQueryFnData, + TError, + InfiniteData, + TQueryKey, + TPageParam + >, + 'queryFn' | 'initialPageParam' +> & + InitialPageParam & { + queryFn?: Exclude< + FetchQueryOptions< + TQueryFnData, + TError, + InfiniteData, + TQueryKey, + TPageParam + >['queryFn'], + SkipToken + > + } & PrefetchInfinitePages + +function isGetter(value: MaybeRefOrGetter): value is () => T { + return typeof value === 'function' +} + +export function usePrefetchInfiniteQuery< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: MaybeRefOrGetter< + MaybeRefDeep< + UsePrefetchInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > + > + >, + queryClient?: QueryClient, +): void { + if (process.env.NODE_ENV === 'development') { + if (!getCurrentScope()) { + console.warn( + 'vue-query composable like "useQuery()" should only be used inside a "setup()" function or a running effect scope. They might otherwise lead to memory leaks.', + ) + } + } + + const client = queryClient || useQueryClient() + + watchEffect(() => { + const resolvedOptions = isGetter(options) ? options() : unref(options) + const clonedOptions: UsePrefetchInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > = cloneDeepUnref(resolvedOptions) + + if (!client.getQueryState(clonedOptions.queryKey)) { + void client.prefetchInfiniteQuery( + clonedOptions as FetchInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, + ) + } + }) +} diff --git a/packages/vue-query/src/usePrefetchQuery.ts b/packages/vue-query/src/usePrefetchQuery.ts new file mode 100644 index 00000000000..85f239c54b4 --- /dev/null +++ b/packages/vue-query/src/usePrefetchQuery.ts @@ -0,0 +1,69 @@ +import { getCurrentScope, unref, watchEffect } from 'vue-demi' +import { useQueryClient } from './useQueryClient' +import { cloneDeepUnref } from './utils' +import type { + DefaultError, + FetchQueryOptions, + OmitKeyof, + QueryKey, + SkipToken, +} from '@tanstack/query-core' +import type { QueryClient } from './queryClient' +import type { MaybeRefDeep, MaybeRefOrGetter } from './types' + +export type UsePrefetchQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey extends QueryKey, +> = OmitKeyof< + FetchQueryOptions, + 'queryFn' +> & { + queryFn?: Exclude< + FetchQueryOptions['queryFn'], + SkipToken + > +} + +function isGetter(value: MaybeRefOrGetter): value is () => T { + return typeof value === 'function' +} + +export function usePrefetchQuery< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + options: MaybeRefOrGetter< + MaybeRefDeep< + UsePrefetchQueryOptions + > + >, + queryClient?: QueryClient, +): void { + if (process.env.NODE_ENV === 'development') { + if (!getCurrentScope()) { + console.warn( + 'vue-query composable like "useQuery()" should only be used inside a "setup()" function or a running effect scope. They might otherwise lead to memory leaks.', + ) + } + } + + const client = queryClient || useQueryClient() + + watchEffect(() => { + const resolvedOptions = isGetter(options) ? options() : unref(options) + const clonedOptions: UsePrefetchQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey + > = cloneDeepUnref(resolvedOptions) + + if (!client.getQueryState(clonedOptions.queryKey)) { + void client.prefetchQuery(clonedOptions) + } + }) +} From a72cdb524521ed208a03eae54bcb95590d8c3e90 Mon Sep 17 00:00:00 2001 From: Kim-Jaemin420 Date: Wed, 1 Apr 2026 18:17:34 +0900 Subject: [PATCH 2/2] docs(vue-query): document prefetch composables --- docs/config.json | 12 ++++++++---- .../vue/reference/usePrefetchInfiniteQuery.md | 6 ++++++ docs/framework/vue/reference/usePrefetchQuery.md | 6 ++++++ 3 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 docs/framework/vue/reference/usePrefetchInfiniteQuery.md create mode 100644 docs/framework/vue/reference/usePrefetchQuery.md diff --git a/docs/config.json b/docs/config.json index 41200ceaed2..d3d78bcd3a5 100644 --- a/docs/config.json +++ b/docs/config.json @@ -1055,6 +1055,14 @@ "label": "infiniteQueryOptions", "to": "framework/vue/reference/infiniteQueryOptions" }, + { + "label": "usePrefetchQuery", + "to": "framework/vue/reference/usePrefetchQuery" + }, + { + "label": "usePrefetchInfiniteQuery", + "to": "framework/vue/reference/usePrefetchInfiniteQuery" + }, { "label": "hydration", "to": "framework/vue/reference/hydration" @@ -1293,10 +1301,6 @@ { "label": "Mutation Property Order", "to": "eslint/mutation-property-order" - }, - { - "label": "Prefer Query Options", - "to": "eslint/prefer-query-options" } ] }, diff --git a/docs/framework/vue/reference/usePrefetchInfiniteQuery.md b/docs/framework/vue/reference/usePrefetchInfiniteQuery.md new file mode 100644 index 00000000000..c7af728d36d --- /dev/null +++ b/docs/framework/vue/reference/usePrefetchInfiniteQuery.md @@ -0,0 +1,6 @@ +--- +id: usePrefetchInfiniteQuery +title: usePrefetchInfiniteQuery +ref: docs/framework/react/reference/usePrefetchInfiniteQuery.md +replace: { '@tanstack/react-query': '@tanstack/vue-query' } +--- diff --git a/docs/framework/vue/reference/usePrefetchQuery.md b/docs/framework/vue/reference/usePrefetchQuery.md new file mode 100644 index 00000000000..e518a62ef84 --- /dev/null +++ b/docs/framework/vue/reference/usePrefetchQuery.md @@ -0,0 +1,6 @@ +--- +id: usePrefetchQuery +title: usePrefetchQuery +ref: docs/framework/react/reference/usePrefetchQuery.md +replace: { '@tanstack/react-query': '@tanstack/vue-query' } +---