diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 77e7c1e975..9e2a3885cb 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -11,7 +11,6 @@ import { focusManager } from './focusManager' import { onlineManager } from './onlineManager' import { notifyManager } from './notifyManager' import { infiniteQueryBehavior } from './infiniteQueryBehavior' -import type { DataTag, NoInfer } from './types' import type { QueryState } from './query' import type { CancelOptions, @@ -34,6 +33,7 @@ import type { RefetchQueryFilters, ResetOptions, SetDataOptions, + TypedQueryKey, } from './types' import type { MutationFilters, QueryFilters, Updater } from './utils' @@ -108,16 +108,9 @@ export class QueryClient { return this.#mutationCache.findAll({ ...filters, status: 'pending' }).length } - getQueryData< - TQueryFnData = unknown, - TaggedQueryKey extends QueryKey = QueryKey, - TInferredQueryFnData = TaggedQueryKey extends DataTag< - unknown, - infer TaggedValue - > - ? TaggedValue - : TQueryFnData, - >(queryKey: TaggedQueryKey): TInferredQueryFnData | undefined + getQueryData( + queryKey: TypedQueryKey | QueryKey, + ): TQueryFnData | undefined getQueryData(queryKey: QueryKey) { return this.#queryCache.find({ queryKey })?.state.data } @@ -146,24 +139,12 @@ export class QueryClient { }) } - setQueryData< - TQueryFnData = unknown, - TaggedQueryKey extends QueryKey = QueryKey, - TInferredQueryFnData = TaggedQueryKey extends DataTag< - unknown, - infer TaggedValue - > - ? TaggedValue - : TQueryFnData, - >( - queryKey: TaggedQueryKey, - updater: Updater< - NoInfer | undefined, - NoInfer | undefined - >, + setQueryData( + queryKey: TypedQueryKey | QueryKey, + updater: Updater, options?: SetDataOptions, - ): TInferredQueryFnData | undefined { - const query = this.#queryCache.find({ queryKey }) + ): TQueryFnData | undefined { + const query = this.#queryCache.find({ queryKey }) const prevData = query?.state.data const data = functionalUpdate(updater, prevData) diff --git a/packages/query-core/src/tests/queryClient.types.test.tsx b/packages/query-core/src/tests/queryClient.types.test.tsx index 78c3cedace..3c82239dde 100644 --- a/packages/query-core/src/tests/queryClient.types.test.tsx +++ b/packages/query-core/src/tests/queryClient.types.test.tsx @@ -1,12 +1,12 @@ import { QueryClient } from '../queryClient' import { doNotExecute } from './utils' import type { Equal, Expect } from './utils' -import type { DataTag, InfiniteData } from '../types' +import type { InfiniteData, TypedQueryKey } from '../types' describe('getQueryData', () => { it('should be typed if key is tagged', () => { doNotExecute(() => { - const queryKey = ['key'] as DataTag, number> + const queryKey = ['key'] as TypedQueryKey const queryClient = new QueryClient() const data = queryClient.getQueryData(queryKey) @@ -50,7 +50,7 @@ describe('getQueryData', () => { describe('setQueryData', () => { it('updater should be typed if key is tagged', () => { doNotExecute(() => { - const queryKey = ['key'] as DataTag, number> + const queryKey = ['key'] as TypedQueryKey const queryClient = new QueryClient() const data = queryClient.setQueryData(queryKey, (prev) => { const result: Expect> = true @@ -64,7 +64,7 @@ describe('setQueryData', () => { it('value should be typed if key is tagged', () => { doNotExecute(() => { - const queryKey = ['key'] as DataTag, number> + const queryKey = ['key'] as TypedQueryKey const queryClient = new QueryClient() // @ts-expect-error value should be a number @@ -94,13 +94,13 @@ describe('setQueryData', () => { }) }) - it('should infer unknown for value if key is not tagged', () => { + it('should infer literal value if key is not tagged', () => { doNotExecute(() => { const queryKey = ['key'] as const const queryClient = new QueryClient() const data = queryClient.setQueryData(queryKey, 'foo') - const result: Expect> = true + const result: Expect> = true return result }) }) @@ -171,3 +171,44 @@ describe('fetchInfiniteQuery', () => { }) }) }) + +describe('fetchInfiniteQuery', () => { + it('should allow passing pages', () => { + doNotExecute(async () => { + const data = await new QueryClient().fetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + pages: 5, + }) + + const result: Expect>> = + true + return result + }) + }) + + it('should not allow passing getNextPageParam without pages', () => { + doNotExecute(async () => { + return new QueryClient().fetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + getNextPageParam: () => 1, + }) + }) + }) + + it('should not allow passing pages without getNextPageParam', () => { + doNotExecute(async () => { + // @ts-expect-error Property 'getNextPageParam' is missing + return new QueryClient().fetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + pages: 5, + }) + }) + }) +}) diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 517fb38632..ce11b4facd 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -23,10 +23,7 @@ export type DefaultError = Register extends { export type QueryKey = ReadonlyArray -export declare const dataTagSymbol: unique symbol -export type DataTag = Type & { - [dataTagSymbol]: Value -} +export declare interface TypedQueryKey extends QueryKey {} export type QueryFunction< T = unknown, diff --git a/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx b/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx index 5c94389b4d..472084da05 100644 --- a/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx +++ b/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx @@ -3,7 +3,7 @@ import { infiniteQueryOptions } from '../infiniteQueryOptions' import { useInfiniteQuery } from '../useInfiniteQuery' import { useSuspenseInfiniteQuery } from '../useSuspenseInfiniteQuery' import { doNotExecute } from './utils' -import type { InfiniteData, dataTagSymbol } from '@tanstack/query-core' +import type { InfiniteData, TypedQueryKey } from '@tanstack/query-core' import type { Equal, Expect } from './utils' describe('queryOptions', () => { @@ -96,7 +96,7 @@ describe('queryOptions', () => { }) const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], InfiniteData> + Equal>> > = true return result }) @@ -111,7 +111,7 @@ describe('queryOptions', () => { }) const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], InfiniteData> + Equal>> > = true return result }) diff --git a/packages/react-query/src/__tests__/queryOptions.types.test.tsx b/packages/react-query/src/__tests__/queryOptions.types.test.tsx index 2186330d38..e89ecd7bab 100644 --- a/packages/react-query/src/__tests__/queryOptions.types.test.tsx +++ b/packages/react-query/src/__tests__/queryOptions.types.test.tsx @@ -4,8 +4,8 @@ import { useQuery } from '../useQuery' import { useQueries } from '../useQueries' import { useSuspenseQuery } from '../useSuspenseQuery' import { doNotExecute } from './utils' -import type { dataTagSymbol } from '@tanstack/query-core' import type { Equal, Expect } from './utils' +import type { TypedQueryKey } from '@tanstack/query-core' describe('queryOptions', () => { it('should not allow excess properties', () => { @@ -92,9 +92,7 @@ describe('queryOptions', () => { queryFn: () => Promise.resolve(5), }) - const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], number> - > = true + const result: Expect>> = true return result }) @@ -105,9 +103,8 @@ describe('queryOptions', () => { queryFn: () => 5, }) - const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], number> - > = true + const result: Expect>> = + true return result }) }) @@ -118,9 +115,8 @@ describe('queryOptions', () => { queryKey: ['key'], }) - const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], unknown> - > = true + const result: Expect>> = + true return result }) }) @@ -178,5 +174,26 @@ describe('queryOptions', () => { return result }) }) + + it('should properly type value when passed to setQueryData', () => { + doNotExecute(() => { + const { queryKey } = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const queryClient = new QueryClient() + + // @ts-expect-error value should be a number + queryClient.setQueryData(queryKey, '5') + // @ts-expect-error value should be a number + queryClient.setQueryData(queryKey, () => '5') + + const data = queryClient.setQueryData(queryKey, 5) + + const result: Expect> = true + return result + }) + }) }) }) diff --git a/packages/react-query/src/infiniteQueryOptions.ts b/packages/react-query/src/infiniteQueryOptions.ts index f91b51ed38..b59f975b94 100644 --- a/packages/react-query/src/infiniteQueryOptions.ts +++ b/packages/react-query/src/infiniteQueryOptions.ts @@ -1,5 +1,4 @@ -import type { DataTag } from '@tanstack/query-core' -import type { InfiniteData } from '@tanstack/query-core' +import type { InfiniteData, TypedQueryKey } from '@tanstack/query-core' import type { UseInfiniteQueryOptions } from './types' import type { DefaultError, QueryKey } from '@tanstack/query-core' @@ -41,52 +40,64 @@ export type DefinedInitialDataInfiniteOptions< | (() => NonUndefinedGuard>) } +type ValidateInfiniteQueryOptions = { + [K in keyof T]: K extends keyof UseInfiniteQueryOptions ? T[K] : never +} + export function infiniteQueryOptions< TQueryFnData, TError = DefaultError, TData = InfiniteData, - TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, >( - options: UndefinedInitialDataInfiniteOptions< + options: ValidateInfiniteQueryOptions< + UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + QueryKey, + TPageParam + > + >, +): Omit< + UndefinedInitialDataInfiniteOptions< TQueryFnData, TError, TData, - TQueryKey, + QueryKey, TPageParam >, -): UndefinedInitialDataInfiniteOptions< - TQueryFnData, - TError, - TData, - TQueryKey, - TPageParam + 'queryKey' > & { - queryKey: DataTag + queryKey: TypedQueryKey | undefined> } export function infiniteQueryOptions< TQueryFnData, TError = DefaultError, TData = InfiniteData, - TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, >( - options: DefinedInitialDataInfiniteOptions< + options: ValidateInfiniteQueryOptions< + DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + QueryKey, + TPageParam + > + >, +): Omit< + DefinedInitialDataInfiniteOptions< TQueryFnData, TError, TData, - TQueryKey, + QueryKey, TPageParam >, -): DefinedInitialDataInfiniteOptions< - TQueryFnData, - TError, - TData, - TQueryKey, - TPageParam + 'queryKey' > & { - queryKey: DataTag + queryKey: TypedQueryKey> } export function infiniteQueryOptions(options: unknown) { diff --git a/packages/react-query/src/queryOptions.ts b/packages/react-query/src/queryOptions.ts index 11e3c4c7eb..720f4cc5c3 100644 --- a/packages/react-query/src/queryOptions.ts +++ b/packages/react-query/src/queryOptions.ts @@ -1,4 +1,8 @@ -import type { DataTag, DefaultError, QueryKey } from '@tanstack/query-core' +import type { + DefaultError, + QueryKey, + TypedQueryKey, +} from '@tanstack/query-core' import type { UseQueryOptions } from './types' export type UndefinedInitialDataOptions< @@ -23,26 +27,38 @@ export type DefinedInitialDataOptions< | (() => NonUndefinedGuard) } +type ValidateQueryOptions = { + [K in keyof T]: K extends keyof UseQueryOptions ? T[K] : never +} + export function queryOptions< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, - TQueryKey extends QueryKey = QueryKey, >( - options: UndefinedInitialDataOptions, -): UndefinedInitialDataOptions & { - queryKey: DataTag + options: ValidateQueryOptions< + UndefinedInitialDataOptions + >, +): Omit< + UndefinedInitialDataOptions, + 'queryKey' +> & { + queryKey: TypedQueryKey } export function queryOptions< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, - TQueryKey extends QueryKey = QueryKey, >( - options: DefinedInitialDataOptions, -): DefinedInitialDataOptions & { - queryKey: DataTag + options: ValidateQueryOptions< + DefinedInitialDataOptions + >, +): Omit< + DefinedInitialDataOptions, + 'queryKey' +> & { + queryKey: TypedQueryKey } export function queryOptions(options: unknown) {