From 17886b3d81bb56ef827a59ea87d3f32ea130f2ba Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sun, 8 Oct 2023 17:25:05 +0200 Subject: [PATCH 01/14] feat: tag the queryKey returned from queryOptions so that it knows about types of the queryFn --- packages/react-query/src/queryOptions.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/react-query/src/queryOptions.ts b/packages/react-query/src/queryOptions.ts index 11e3c4c7eb..b701a402bc 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< @@ -31,7 +35,7 @@ export function queryOptions< >( options: UndefinedInitialDataOptions, ): UndefinedInitialDataOptions & { - queryKey: DataTag + queryKey: TypedQueryKey } export function queryOptions< @@ -42,7 +46,7 @@ export function queryOptions< >( options: DefinedInitialDataOptions, ): DefinedInitialDataOptions & { - queryKey: DataTag + queryKey: TypedQueryKey } export function queryOptions(options: unknown) { From 1d6e0bf7cda31cfff1ccdffb3dc13bb461342f6d Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sun, 8 Oct 2023 18:42:10 +0200 Subject: [PATCH 02/14] feat: overloads for getQueryData / setQueryData to infer types from queryOptions --- packages/query-core/src/queryClient.ts | 45 +++++++++---------- packages/query-core/src/types.ts | 6 +-- .../src/__tests__/queryOptions.types.test.tsx | 10 ++--- packages/react-query/src/queryOptions.ts | 35 +++++++++++++-- 4 files changed, 60 insertions(+), 36 deletions(-) diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 77e7c1e975..6a0269451b 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -11,7 +11,7 @@ import { focusManager } from './focusManager' import { onlineManager } from './onlineManager' import { notifyManager } from './notifyManager' import { infiniteQueryBehavior } from './infiniteQueryBehavior' -import type { DataTag, NoInfer } from './types' +import type { TaggedQueryKey, queryKeySymbol } from './types' import type { QueryState } from './query' import type { CancelOptions, @@ -108,16 +108,12 @@ 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: TaggedKey, + ): TaggedKey[typeof queryKeySymbol] | undefined + getQueryData( + queryKey: QueryKey, + ): TQueryFnData | undefined getQueryData(queryKey: QueryKey) { return this.#queryCache.find({ queryKey })?.state.data } @@ -146,22 +142,23 @@ export class QueryClient { }) } - setQueryData< - TQueryFnData = unknown, - TaggedQueryKey extends QueryKey = QueryKey, - TInferredQueryFnData = TaggedQueryKey extends DataTag< - unknown, - infer TaggedValue - > - ? TaggedValue - : TQueryFnData, - >( - queryKey: TaggedQueryKey, + setQueryData>( + queryKey: TaggedKey, updater: Updater< - NoInfer | undefined, - NoInfer | undefined + TaggedKey[typeof queryKeySymbol] | undefined, + TaggedKey[typeof queryKeySymbol] | undefined >, options?: SetDataOptions, + ): TaggedKey[typeof queryKeySymbol] + setQueryData( + queryKey: QueryKey, + updater: Updater, + options?: SetDataOptions, + ): TQueryFnData | undefined + setQueryData( + queryKey: QueryKey, + updater: Updater, + options?: SetDataOptions, ): TInferredQueryFnData | undefined { const query = this.#queryCache.find({ queryKey }) const prevData = query?.state.data diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 57a164e2b7..2bbbd4b56f 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -23,9 +23,9 @@ export type DefaultError = Register extends { export type QueryKey = ReadonlyArray -export declare const dataTagSymbol: unique symbol -export type DataTag = Type & { - [dataTagSymbol]: Value +export declare const queryKeySymbol: unique symbol +export type TaggedQueryKey = Type & { + [queryKeySymbol]: Value } export type QueryFunction< diff --git a/packages/react-query/src/__tests__/queryOptions.types.test.tsx b/packages/react-query/src/__tests__/queryOptions.types.test.tsx index 2955780f48..714b677d59 100644 --- a/packages/react-query/src/__tests__/queryOptions.types.test.tsx +++ b/packages/react-query/src/__tests__/queryOptions.types.test.tsx @@ -5,7 +5,7 @@ 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 { queryKeySymbol } from '@tanstack/query-core' import type { Equal, Expect } from './utils' describe('queryOptions', () => { @@ -94,7 +94,7 @@ describe('queryOptions', () => { }) const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], number> + Equal<(typeof queryKey)[typeof queryKeySymbol], number> > = true return result }) @@ -107,7 +107,7 @@ describe('queryOptions', () => { }) const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], number> + Equal<(typeof queryKey)[typeof queryKeySymbol], number> > = true return result }) @@ -120,7 +120,7 @@ describe('queryOptions', () => { }) const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], unknown> + Equal<(typeof queryKey)[typeof queryKeySymbol], unknown> > = true return result }) @@ -141,7 +141,7 @@ describe('queryOptions', () => { }) }) - it('should properly type updaterFn when passed to setQueryData', () => { + it('should properly type when passed to setQueryData', () => { doNotExecute(() => { const { queryKey } = queryOptions({ queryKey: ['key'], diff --git a/packages/react-query/src/queryOptions.ts b/packages/react-query/src/queryOptions.ts index b701a402bc..0ff11bd939 100644 --- a/packages/react-query/src/queryOptions.ts +++ b/packages/react-query/src/queryOptions.ts @@ -1,7 +1,7 @@ import type { DefaultError, QueryKey, - TypedQueryKey, + TaggedQueryKey, } from '@tanstack/query-core' import type { UseQueryOptions } from './types' @@ -44,9 +44,36 @@ export function queryOptions< TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >( - options: DefinedInitialDataOptions, -): DefinedInitialDataOptions & { - queryKey: TypedQueryKey + options: ValidateQueryOptions, +): Omit & { + queryKey: TaggedQueryKey< + TOptions['queryKey'], + TOptions['queryFn'] extends () => any + ? Awaited>> + : unknown + > +} + +export function queryOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TOptions extends DefinedInitialDataOptions< + TQueryFnData, + TError, + TData, + TQueryKey + > = DefinedInitialDataOptions, +>( + options: ValidateQueryOptions, +): Omit & { + queryKey: TaggedQueryKey< + TOptions['queryKey'], + TOptions['queryFn'] extends () => any + ? Awaited>> + : unknown + > } export function queryOptions(options: unknown) { From 7c4683e8907bc446f4fbc0bb160be085c38fd65f Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sun, 8 Oct 2023 18:46:35 +0200 Subject: [PATCH 03/14] test: type-tests for queryClient to avoid regressions --- packages/query-core/src/tests/queryClient.types.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/query-core/src/tests/queryClient.types.test.tsx b/packages/query-core/src/tests/queryClient.types.test.tsx index b9990a2acf..2e3ef9298b 100644 --- a/packages/query-core/src/tests/queryClient.types.test.tsx +++ b/packages/query-core/src/tests/queryClient.types.test.tsx @@ -2,12 +2,12 @@ import { describe, it } from 'vitest' import { QueryClient } from '../queryClient' import { doNotExecute } from './utils' import type { Equal, Expect } from './utils' -import type { DataTag, InfiniteData } from '../types' +import type { TaggedQueryKey } from '../types' describe('getQueryData', () => { it('should be typed if key is tagged', () => { doNotExecute(() => { - const queryKey = ['key'] as DataTag, number> + const queryKey = ['key'] as TaggedQueryKey, number> const queryClient = new QueryClient() const data = queryClient.getQueryData(queryKey) @@ -58,7 +58,7 @@ describe('setQueryData', () => { return result ? prev : 1 }) - const result: Expect> = true + const result: Expect> = true return result }) }) From e8a7b2303c41803cfeb7d50e5f1f029656cdbde4 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sun, 8 Oct 2023 18:48:43 +0200 Subject: [PATCH 04/14] refactor: rename --- packages/query-core/src/queryClient.ts | 18 +++++++++--------- .../src/tests/queryClient.types.test.tsx | 4 ++-- packages/query-core/src/types.ts | 6 +++--- .../src/__tests__/queryOptions.types.test.tsx | 8 ++++---- packages/react-query/src/queryOptions.ts | 10 +++------- 5 files changed, 21 insertions(+), 25 deletions(-) diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 6a0269451b..d176b9a832 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -11,7 +11,7 @@ import { focusManager } from './focusManager' import { onlineManager } from './onlineManager' import { notifyManager } from './notifyManager' import { infiniteQueryBehavior } from './infiniteQueryBehavior' -import type { TaggedQueryKey, queryKeySymbol } from './types' +import type { DataTag, dataTagSymbol } from './types' import type { QueryState } from './query' import type { CancelOptions, @@ -108,9 +108,9 @@ export class QueryClient { return this.#mutationCache.findAll({ ...filters, status: 'pending' }).length } - getQueryData>( - queryKey: TaggedKey, - ): TaggedKey[typeof queryKeySymbol] | undefined + getQueryData>( + queryKey: TaggedQueryKey, + ): TaggedQueryKey[typeof dataTagSymbol] | undefined getQueryData( queryKey: QueryKey, ): TQueryFnData | undefined @@ -142,14 +142,14 @@ export class QueryClient { }) } - setQueryData>( - queryKey: TaggedKey, + setQueryData>( + queryKey: TaggedQueryKey, updater: Updater< - TaggedKey[typeof queryKeySymbol] | undefined, - TaggedKey[typeof queryKeySymbol] | undefined + TaggedQueryKey[typeof dataTagSymbol] | undefined, + TaggedQueryKey[typeof dataTagSymbol] | undefined >, options?: SetDataOptions, - ): TaggedKey[typeof queryKeySymbol] + ): TaggedQueryKey[typeof dataTagSymbol] setQueryData( queryKey: QueryKey, updater: Updater, diff --git a/packages/query-core/src/tests/queryClient.types.test.tsx b/packages/query-core/src/tests/queryClient.types.test.tsx index 2e3ef9298b..c1efc081f6 100644 --- a/packages/query-core/src/tests/queryClient.types.test.tsx +++ b/packages/query-core/src/tests/queryClient.types.test.tsx @@ -2,12 +2,12 @@ import { describe, it } from 'vitest' import { QueryClient } from '../queryClient' import { doNotExecute } from './utils' import type { Equal, Expect } from './utils' -import type { TaggedQueryKey } from '../types' +import type { DataTag } from '../types' describe('getQueryData', () => { it('should be typed if key is tagged', () => { doNotExecute(() => { - const queryKey = ['key'] as TaggedQueryKey, number> + const queryKey = ['key'] as DataTag, number> const queryClient = new QueryClient() const data = queryClient.getQueryData(queryKey) diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 2bbbd4b56f..57a164e2b7 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -23,9 +23,9 @@ export type DefaultError = Register extends { export type QueryKey = ReadonlyArray -export declare const queryKeySymbol: unique symbol -export type TaggedQueryKey = Type & { - [queryKeySymbol]: Value +export declare const dataTagSymbol: unique symbol +export type DataTag = Type & { + [dataTagSymbol]: Value } export type QueryFunction< diff --git a/packages/react-query/src/__tests__/queryOptions.types.test.tsx b/packages/react-query/src/__tests__/queryOptions.types.test.tsx index 714b677d59..4e06153422 100644 --- a/packages/react-query/src/__tests__/queryOptions.types.test.tsx +++ b/packages/react-query/src/__tests__/queryOptions.types.test.tsx @@ -5,7 +5,7 @@ import { useQuery } from '../useQuery' import { useQueries } from '../useQueries' import { useSuspenseQuery } from '../useSuspenseQuery' import { doNotExecute } from './utils' -import type { queryKeySymbol } from '@tanstack/query-core' +import type { dataTagSymbol } from '@tanstack/query-core' import type { Equal, Expect } from './utils' describe('queryOptions', () => { @@ -94,7 +94,7 @@ describe('queryOptions', () => { }) const result: Expect< - Equal<(typeof queryKey)[typeof queryKeySymbol], number> + Equal<(typeof queryKey)[typeof dataTagSymbol], number> > = true return result }) @@ -107,7 +107,7 @@ describe('queryOptions', () => { }) const result: Expect< - Equal<(typeof queryKey)[typeof queryKeySymbol], number> + Equal<(typeof queryKey)[typeof dataTagSymbol], number> > = true return result }) @@ -120,7 +120,7 @@ describe('queryOptions', () => { }) const result: Expect< - Equal<(typeof queryKey)[typeof queryKeySymbol], unknown> + Equal<(typeof queryKey)[typeof dataTagSymbol], unknown> > = true return result }) diff --git a/packages/react-query/src/queryOptions.ts b/packages/react-query/src/queryOptions.ts index 0ff11bd939..ff3b1b0122 100644 --- a/packages/react-query/src/queryOptions.ts +++ b/packages/react-query/src/queryOptions.ts @@ -1,8 +1,4 @@ -import type { - DefaultError, - QueryKey, - TaggedQueryKey, -} from '@tanstack/query-core' +import type { DataTag, DefaultError, QueryKey } from '@tanstack/query-core' import type { UseQueryOptions } from './types' export type UndefinedInitialDataOptions< @@ -46,7 +42,7 @@ export function queryOptions< >( options: ValidateQueryOptions, ): Omit & { - queryKey: TaggedQueryKey< + queryKey: DataTag< TOptions['queryKey'], TOptions['queryFn'] extends () => any ? Awaited>> @@ -68,7 +64,7 @@ export function queryOptions< >( options: ValidateQueryOptions, ): Omit & { - queryKey: TaggedQueryKey< + queryKey: DataTag< TOptions['queryKey'], TOptions['queryFn'] extends () => any ? Awaited>> From 3744907086b6d413c1cca0fa63f36db5795e73ed Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sun, 8 Oct 2023 19:28:18 +0200 Subject: [PATCH 05/14] refactor(types): simplify options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit interestingly, this also solves other types issues we had 🤯 --- packages/react-query/src/queryOptions.ts | 32 ++++++++---------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/packages/react-query/src/queryOptions.ts b/packages/react-query/src/queryOptions.ts index ff3b1b0122..6d6e771011 100644 --- a/packages/react-query/src/queryOptions.ts +++ b/packages/react-query/src/queryOptions.ts @@ -40,14 +40,11 @@ export function queryOptions< TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >( - options: ValidateQueryOptions, -): Omit & { - queryKey: DataTag< - TOptions['queryKey'], - TOptions['queryFn'] extends () => any - ? Awaited>> - : unknown - > + options: ValidateQueryOptions< + UndefinedInitialDataOptions + >, +): UndefinedInitialDataOptions & { + queryKey: DataTag } export function queryOptions< @@ -55,21 +52,12 @@ export function queryOptions< TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, - TOptions extends DefinedInitialDataOptions< - TQueryFnData, - TError, - TData, - TQueryKey - > = DefinedInitialDataOptions, >( - options: ValidateQueryOptions, -): Omit & { - queryKey: DataTag< - TOptions['queryKey'], - TOptions['queryFn'] extends () => any - ? Awaited>> - : unknown - > + options: ValidateQueryOptions< + DefinedInitialDataOptions + >, +): DefinedInitialDataOptions & { + queryKey: DataTag } export function queryOptions(options: unknown) { From dc856680f7fd5eafb763d37c7e2e461a8d0d0e31 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Mon, 9 Oct 2023 08:20:21 +0200 Subject: [PATCH 06/14] fix(types): fetchInfiniteQuery should work when getNextPageParam is passed even if pages isn't --- .../src/tests/queryClient.types.test.tsx | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/packages/query-core/src/tests/queryClient.types.test.tsx b/packages/query-core/src/tests/queryClient.types.test.tsx index c1efc081f6..e6d2d80fc7 100644 --- a/packages/query-core/src/tests/queryClient.types.test.tsx +++ b/packages/query-core/src/tests/queryClient.types.test.tsx @@ -2,7 +2,7 @@ import { describe, it } from 'vitest' import { QueryClient } from '../queryClient' import { doNotExecute } from './utils' import type { Equal, Expect } from './utils' -import type { DataTag } from '../types' +import type { DataTag, InfiniteData } from '../types' describe('getQueryData', () => { it('should be typed if key is tagged', () => { @@ -172,3 +172,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, + }) + }) + }) +}) From 9cf9961563e5385bc71852c9d6b6303648db5d08 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Mon, 9 Oct 2023 08:24:48 +0200 Subject: [PATCH 07/14] feat(types): infiniteQueryOptions --- .../infiniteQueryOptions.types.test.tsx | 2 +- .../react-query/src/infiniteQueryOptions.ts | 32 ++++++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx b/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx index 9735f93705..4739bec4bb 100644 --- a/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx +++ b/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx @@ -155,7 +155,7 @@ describe('queryOptions', () => { }) const result: Expect< - Equal | undefined> + Equal> > = true return result }) diff --git a/packages/react-query/src/infiniteQueryOptions.ts b/packages/react-query/src/infiniteQueryOptions.ts index f91b51ed38..3f16f51e7c 100644 --- a/packages/react-query/src/infiniteQueryOptions.ts +++ b/packages/react-query/src/infiniteQueryOptions.ts @@ -41,6 +41,10 @@ export type DefinedInitialDataInfiniteOptions< | (() => NonUndefinedGuard>) } +type ValidateInfiniteQueryOptions = { + [K in keyof T]: K extends keyof UseInfiniteQueryOptions ? T[K] : never +} + export function infiniteQueryOptions< TQueryFnData, TError = DefaultError, @@ -48,12 +52,14 @@ export function infiniteQueryOptions< TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, >( - options: UndefinedInitialDataInfiniteOptions< - TQueryFnData, - TError, - TData, - TQueryKey, - TPageParam + options: ValidateInfiniteQueryOptions< + UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > >, ): UndefinedInitialDataInfiniteOptions< TQueryFnData, @@ -72,12 +78,14 @@ export function infiniteQueryOptions< TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, >( - options: DefinedInitialDataInfiniteOptions< - TQueryFnData, - TError, - TData, - TQueryKey, - TPageParam + options: ValidateInfiniteQueryOptions< + DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > >, ): DefinedInitialDataInfiniteOptions< TQueryFnData, From 5fbe2bbd6350631a37793d9d2789b46cb633d459 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Mon, 9 Oct 2023 10:49:39 +0200 Subject: [PATCH 08/14] test: this should work --- .../src/__tests__/queryOptions.types.test.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/react-query/src/__tests__/queryOptions.types.test.tsx b/packages/react-query/src/__tests__/queryOptions.types.test.tsx index 4e06153422..237e69d503 100644 --- a/packages/react-query/src/__tests__/queryOptions.types.test.tsx +++ b/packages/react-query/src/__tests__/queryOptions.types.test.tsx @@ -141,7 +141,7 @@ describe('queryOptions', () => { }) }) - it('should properly type when passed to setQueryData', () => { + it('should properly type updaterFn when passed to setQueryData', () => { doNotExecute(() => { const { queryKey } = queryOptions({ queryKey: ['key'], @@ -179,5 +179,24 @@ 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') + + const data = queryClient.setQueryData(queryKey, 5) + + const result: Expect> = true + return result + }) + }) }) }) From f72e736747403c749ad864185477350189a3b419 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Mon, 9 Oct 2023 14:21:20 +0200 Subject: [PATCH 09/14] fix(types): fix typing for setQueryData when only a value is passed by making sure TS doesn't infer types from there also, move from overloads to conditional return types --- packages/query-core/src/queryClient.ts | 49 ++++++++++--------- .../src/tests/queryClient.types.test.tsx | 2 +- .../src/__tests__/queryOptions.types.test.tsx | 4 +- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index d176b9a832..d43aae7bd7 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -11,7 +11,7 @@ import { focusManager } from './focusManager' import { onlineManager } from './onlineManager' import { notifyManager } from './notifyManager' import { infiniteQueryBehavior } from './infiniteQueryBehavior' -import type { DataTag, dataTagSymbol } from './types' +import type { DataTag, NoInfer } from './types' import type { QueryState } from './query' import type { CancelOptions, @@ -108,12 +108,16 @@ export class QueryClient { return this.#mutationCache.findAll({ ...filters, status: 'pending' }).length } - getQueryData>( - queryKey: TaggedQueryKey, - ): TaggedQueryKey[typeof dataTagSymbol] | undefined - getQueryData( - queryKey: QueryKey, - ): TQueryFnData | undefined + getQueryData< + TQueryFnData = unknown, + TaggedQueryKey extends QueryKey = QueryKey, + TInferredQueryFnData = TaggedQueryKey extends DataTag< + unknown, + infer TaggedValue + > + ? TaggedValue + : TQueryFnData, + >(queryKey: TaggedQueryKey): TInferredQueryFnData | undefined getQueryData(queryKey: QueryKey) { return this.#queryCache.find({ queryKey })?.state.data } @@ -142,22 +146,23 @@ export class QueryClient { }) } - setQueryData>( + setQueryData< + TQueryFnData = unknown, + TaggedQueryKey extends QueryKey = QueryKey, + TInferredQueryFnData = TaggedQueryKey extends DataTag< + unknown, + infer TaggedValue + > + ? TaggedValue + : TQueryFnData, + >( queryKey: TaggedQueryKey, - updater: Updater< - TaggedQueryKey[typeof dataTagSymbol] | undefined, - TaggedQueryKey[typeof dataTagSymbol] | undefined - >, - options?: SetDataOptions, - ): TaggedQueryKey[typeof dataTagSymbol] - setQueryData( - queryKey: QueryKey, - updater: Updater, - options?: SetDataOptions, - ): TQueryFnData | undefined - setQueryData( - queryKey: QueryKey, - updater: Updater, + updater: + | NoInfer + | undefined + | (( + prev: TInferredQueryFnData | undefined, + ) => NoInfer | undefined), options?: SetDataOptions, ): TInferredQueryFnData | undefined { const query = this.#queryCache.find({ queryKey }) diff --git a/packages/query-core/src/tests/queryClient.types.test.tsx b/packages/query-core/src/tests/queryClient.types.test.tsx index e6d2d80fc7..bcd6277772 100644 --- a/packages/query-core/src/tests/queryClient.types.test.tsx +++ b/packages/query-core/src/tests/queryClient.types.test.tsx @@ -58,7 +58,7 @@ describe('setQueryData', () => { return result ? prev : 1 }) - const result: Expect> = true + const result: Expect> = 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 237e69d503..3af823694d 100644 --- a/packages/react-query/src/__tests__/queryOptions.types.test.tsx +++ b/packages/react-query/src/__tests__/queryOptions.types.test.tsx @@ -191,10 +191,12 @@ describe('queryOptions', () => { // @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 + const result: Expect> = true return result }) }) From 179be5d5b2e3ea0c779d286576df6b2c5d51f5d9 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Mon, 9 Oct 2023 14:23:11 +0200 Subject: [PATCH 10/14] refactor: re-use Updater type --- packages/query-core/src/queryClient.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index d43aae7bd7..77e7c1e975 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -157,12 +157,10 @@ export class QueryClient { : TQueryFnData, >( queryKey: TaggedQueryKey, - updater: - | NoInfer - | undefined - | (( - prev: TInferredQueryFnData | undefined, - ) => NoInfer | undefined), + updater: Updater< + NoInfer | undefined, + NoInfer | undefined + >, options?: SetDataOptions, ): TInferredQueryFnData | undefined { const query = this.#queryCache.find({ queryKey }) From 1f80eb32511953e61a33380771e46ac6c6dc39af Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Mon, 9 Oct 2023 14:38:58 +0200 Subject: [PATCH 11/14] chore: fix tests --- .../src/__tests__/infiniteQueryOptions.types.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx b/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx index 4739bec4bb..9735f93705 100644 --- a/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx +++ b/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx @@ -155,7 +155,7 @@ describe('queryOptions', () => { }) const result: Expect< - Equal> + Equal | undefined> > = true return result }) From 9e3a7e063419708f2df8a8659ab6c624a740c1ff Mon Sep 17 00:00:00 2001 From: LABAT Robin Date: Mon, 9 Oct 2023 17:38:08 +0200 Subject: [PATCH 12/14] update to Vue Style type --- packages/query-core/src/queryClient.ts | 37 ++++------------ .../src/tests/queryClient.types.test.tsx | 6 +-- packages/query-core/src/types.ts | 5 +-- .../infiniteQueryOptions.types.test.tsx | 6 +-- .../src/__tests__/queryOptions.types.test.tsx | 16 +++---- .../react-query/src/infiniteQueryOptions.ts | 43 ++++++++++--------- packages/react-query/src/queryOptions.ts | 26 +++++++---- 7 files changed, 62 insertions(+), 77 deletions(-) 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 bcd6277772..12b90e8859 100644 --- a/packages/query-core/src/tests/queryClient.types.test.tsx +++ b/packages/query-core/src/tests/queryClient.types.test.tsx @@ -2,12 +2,12 @@ import { describe, it } from 'vitest' 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) @@ -51,7 +51,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 diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 57a164e2b7..2f6d1aa642 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 9735f93705..f78a96297c 100644 --- a/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx +++ b/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx @@ -4,7 +4,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', () => { @@ -97,7 +97,7 @@ describe('queryOptions', () => { }) const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], InfiniteData> + Equal>> > = true return result }) @@ -112,7 +112,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 3af823694d..5d1c91fd36 100644 --- a/packages/react-query/src/__tests__/queryOptions.types.test.tsx +++ b/packages/react-query/src/__tests__/queryOptions.types.test.tsx @@ -5,8 +5,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', () => { @@ -93,9 +93,7 @@ describe('queryOptions', () => { queryFn: () => Promise.resolve(5), }) - const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], number> - > = true + const result: Expect>> = true return result }) @@ -106,9 +104,8 @@ describe('queryOptions', () => { queryFn: () => 5, }) - const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], number> - > = true + const result: Expect>> = + true return result }) }) @@ -119,9 +116,8 @@ describe('queryOptions', () => { queryKey: ['key'], }) - const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], unknown> - > = true + const result: Expect>> = + true return result }) }) diff --git a/packages/react-query/src/infiniteQueryOptions.ts b/packages/react-query/src/infiniteQueryOptions.ts index 3f16f51e7c..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' @@ -49,7 +48,6 @@ export function infiniteQueryOptions< TQueryFnData, TError = DefaultError, TData = InfiniteData, - TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, >( options: ValidateInfiniteQueryOptions< @@ -57,25 +55,27 @@ export function infiniteQueryOptions< TQueryFnData, TError, TData, - TQueryKey, + QueryKey, TPageParam > >, -): UndefinedInitialDataInfiniteOptions< - TQueryFnData, - TError, - TData, - TQueryKey, - TPageParam +): Omit< + UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + QueryKey, + TPageParam + >, + 'queryKey' > & { - queryKey: DataTag + queryKey: TypedQueryKey | undefined> } export function infiniteQueryOptions< TQueryFnData, TError = DefaultError, TData = InfiniteData, - TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, >( options: ValidateInfiniteQueryOptions< @@ -83,18 +83,21 @@ export function infiniteQueryOptions< TQueryFnData, TError, TData, - TQueryKey, + QueryKey, TPageParam > >, -): DefinedInitialDataInfiniteOptions< - TQueryFnData, - TError, - TData, - TQueryKey, - TPageParam +): Omit< + DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + QueryKey, + 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 6d6e771011..c3451865a9 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< @@ -38,26 +42,30 @@ export function queryOptions< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, - TQueryKey extends QueryKey = QueryKey, >( options: ValidateQueryOptions< - UndefinedInitialDataOptions + UndefinedInitialDataOptions >, -): UndefinedInitialDataOptions & { - queryKey: DataTag +): Omit< + UndefinedInitialDataOptions, + 'queryKey' +> & { + queryKey: TypedQueryKey } export function queryOptions< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, - TQueryKey extends QueryKey = QueryKey, >( options: ValidateQueryOptions< - DefinedInitialDataOptions + DefinedInitialDataOptions >, -): DefinedInitialDataOptions & { - queryKey: DataTag +): Omit< + DefinedInitialDataOptions, + 'queryKey' +> & { + queryKey: TypedQueryKey } export function queryOptions(options: unknown) { From 3347a4a1611ab03e9f001a6fb084ccf96651fb51 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Mon, 9 Oct 2023 18:09:46 +0200 Subject: [PATCH 13/14] fix: tests --- packages/query-core/src/tests/queryClient.types.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/query-core/src/tests/queryClient.types.test.tsx b/packages/query-core/src/tests/queryClient.types.test.tsx index 12b90e8859..5a4cd45616 100644 --- a/packages/query-core/src/tests/queryClient.types.test.tsx +++ b/packages/query-core/src/tests/queryClient.types.test.tsx @@ -65,7 +65,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 @@ -95,13 +95,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 }) }) From 3a95c0a53c88dfca858ad97968c0eecc679cb749 Mon Sep 17 00:00:00 2001 From: LABAT Robin Date: Tue, 10 Oct 2023 11:46:21 +0200 Subject: [PATCH 14/14] fix issues from rebase --- packages/react-query/src/queryOptions.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/react-query/src/queryOptions.ts b/packages/react-query/src/queryOptions.ts index c3451865a9..720f4cc5c3 100644 --- a/packages/react-query/src/queryOptions.ts +++ b/packages/react-query/src/queryOptions.ts @@ -27,15 +27,8 @@ export type DefinedInitialDataOptions< | (() => NonUndefinedGuard) } -export function queryOptions< - TQueryFnData = unknown, - TError = DefaultError, - TData = TQueryFnData, - TQueryKey extends QueryKey = QueryKey, ->( - options: UndefinedInitialDataOptions, -): UndefinedInitialDataOptions & { - queryKey: TypedQueryKey +type ValidateQueryOptions = { + [K in keyof T]: K extends keyof UseQueryOptions ? T[K] : never } export function queryOptions<