Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include optional TError on DataTag so that getQueryState can infer the error type correctly #8361

Merged
merged 11 commits into from
Nov 29, 2024
30 changes: 30 additions & 0 deletions packages/query-core/src/__tests__/queryClient.test-d.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expectTypeOf, it } from 'vitest'
import { QueryClient } from '../queryClient'
import type { QueryState } from '../query'
import type { DataTag, InfiniteData, QueryKey } from '../types'

describe('getQueryData', () => {
Expand Down Expand Up @@ -101,6 +102,35 @@ describe('setQueryData', () => {
})
})

describe('getQueryState', () => {
it('should be loose typed without tag', () => {
const queryKey = ['key'] as const
const queryClient = new QueryClient()
const data = queryClient.getQueryState(queryKey)

expectTypeOf(data).toEqualTypeOf<QueryState<unknown, Error> | undefined>()
})

it('should be typed if key is tagged', () => {
const queryKey = ['key'] as DataTag<Array<string>, number>
const queryClient = new QueryClient()
const data = queryClient.getQueryState(queryKey)

expectTypeOf(data).toEqualTypeOf<QueryState<number, Error> | undefined>()
})

it('should be typed including error if key is tagged', () => {
type CustomError = Error & { customError: string }
const queryKey = ['key'] as DataTag<Array<string>, number, CustomError>
const queryClient = new QueryClient()
const data = queryClient.getQueryState(queryKey)

expectTypeOf(data).toEqualTypeOf<
QueryState<number, CustomError> | undefined
>()
})
})

describe('fetchInfiniteQuery', () => {
it('should allow passing pages', async () => {
const data = await new QueryClient().fetchInfiniteQuery({
Expand Down
26 changes: 23 additions & 3 deletions packages/query-core/src/__tests__/utils.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import type { DataTag } from '../types'
describe('QueryFilters', () => {
it('should be typed if generics are passed', () => {
type TData = { a: number; b: string }
type TError = { message: string }

const a: QueryFilters<TData, TError> = {
const a: QueryFilters<TData> = {
predicate(query) {
expectTypeOf(query.setData({ a: 1, b: '1' })).toEqualTypeOf<TData>()
return true
Expand All @@ -22,7 +21,28 @@ describe('QueryFilters', () => {
expectTypeOf(data).toEqualTypeOf<TData | undefined>()

const error = queryClient.getQueryState(a.queryKey!)?.error
expectTypeOf(error).toEqualTypeOf<Error | null | undefined>() // maybe one day this can return TError
expectTypeOf(error).toEqualTypeOf<Error | null | undefined>()
})

it('should be typed if generics are passed including an error type', () => {
type TData = { a: number; b: string }
type TError = Error & { message: string }

const a: QueryFilters<TData, TError> = {
predicate(query) {
expectTypeOf(query.setData({ a: 1, b: '1' })).toEqualTypeOf<TData>()
return true
},
queryKey: ['key'] as DataTag<undefined, TData, TError>,
}

const queryClient = new QueryClient()

const data = queryClient.getQueryData(a.queryKey!)
expectTypeOf(data).toEqualTypeOf<TData | undefined>()

const error = queryClient.getQueryState(a.queryKey!)?.error
expectTypeOf(error).toEqualTypeOf<TError | null | undefined>()
})

it('should be loose typed if generics are defaults', () => {
Expand Down
26 changes: 20 additions & 6 deletions packages/query-core/src/queryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import type {
RefetchQueryFilters,
ResetOptions,
SetDataOptions,
UnsetMarker,
} from './types'
import type { QueryState } from './query'
import type { MutationFilters, QueryFilters, Updater } from './utils'
Expand Down Expand Up @@ -119,7 +120,8 @@ export class QueryClient {
TTaggedQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = TTaggedQueryKey extends DataTag<
unknown,
infer TaggedValue
infer TaggedValue,
unknown
>
? TaggedValue
: TQueryFnData,
Expand Down Expand Up @@ -169,7 +171,8 @@ export class QueryClient {
TTaggedQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = TTaggedQueryKey extends DataTag<
unknown,
infer TaggedValue
infer TaggedValue,
unknown
>
? TaggedValue
: TQueryFnData,
Expand Down Expand Up @@ -225,16 +228,27 @@ export class QueryClient {
TTaggedQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = TTaggedQueryKey extends DataTag<
unknown,
infer TaggedValue
infer TaggedValue,
unknown
>
? TaggedValue
: TQueryFnData,
TInferredError = TTaggedQueryKey extends DataTag<
unknown,
unknown,
infer TaggedError
>
? TaggedError extends UnsetMarker
? TError
: TaggedError
: TError,
>(
queryKey: TTaggedQueryKey,
): QueryState<TInferredQueryFnData, TError> | undefined {
): QueryState<TInferredQueryFnData, TInferredError> | undefined {
const options = this.defaultQueryOptions({ queryKey })
return this.#queryCache.get<TInferredQueryFnData, TError>(options.queryHash)
?.state
return this.#queryCache.get<TInferredQueryFnData, TInferredError>(
options.queryHash,
)?.state
}

removeQueries(filters?: QueryFilters): void {
Expand Down
10 changes: 7 additions & 3 deletions packages/query-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ export type DefaultError = Register extends {
export type QueryKey = ReadonlyArray<unknown>

export declare const dataTagSymbol: unique symbol
export type DataTag<TType, TValue> = TType & {
export declare const dataTagErrorSymbol: unique symbol
export declare const unsetMarker: unique symbol
export type UnsetMarker = typeof unsetMarker
export type DataTag<TType, TValue, TError = UnsetMarker> = TType & {
[dataTagSymbol]: TValue
[dataTagErrorSymbol]: TError
}

export type QueryFunction<
Expand Down Expand Up @@ -536,7 +540,7 @@ export interface RefetchOptions extends ResultOptions {

export interface InvalidateQueryFilters<
TQueryFnData = unknown,
TError = Error,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> extends QueryFilters<TQueryFnData, TError, TData, TQueryKey> {
Expand All @@ -545,7 +549,7 @@ export interface InvalidateQueryFilters<

export interface RefetchQueryFilters<
TQueryFnData = unknown,
TError = Error,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> extends QueryFilters<TQueryFnData, TError, TData, TQueryKey> {}
Expand Down
4 changes: 2 additions & 2 deletions packages/query-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type { FetchOptions, Query } from './query'

export interface QueryFilters<
TQueryFnData = unknown,
TError = Error,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> {
Expand All @@ -38,7 +38,7 @@ export interface QueryFilters<
*/
queryKey?: unknown extends TQueryFnData
? QueryKey
: QueryKey & DataTag<unknown, TQueryFnData>
: QueryKey & DataTag<unknown, TQueryFnData, TError>
/**
* Include or exclude stale queries
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/vue-query/src/infiniteQueryOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function infiniteQueryOptions<
TQueryKey,
TPageParam
> & {
queryKey: DataTag<TQueryKey, InfiniteData<TQueryFnData>>
queryKey: DataTag<TQueryKey, InfiniteData<TQueryFnData>, TError>
}

export function infiniteQueryOptions<
Expand All @@ -89,7 +89,7 @@ export function infiniteQueryOptions<
TQueryKey,
TPageParam
> & {
queryKey: DataTag<TQueryKey, InfiniteData<TQueryFnData>>
queryKey: DataTag<TQueryKey, InfiniteData<TQueryFnData>, TError>
}

export function infiniteQueryOptions(options: unknown) {
Expand Down
4 changes: 2 additions & 2 deletions packages/vue-query/src/queryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class QueryClient extends QC {
getQueryData<TData = unknown, TTaggedQueryKey extends QueryKey = QueryKey>(
queryKey: TTaggedQueryKey,
):
| (TTaggedQueryKey extends DataTag<unknown, infer TaggedValue>
| (TTaggedQueryKey extends DataTag<unknown, infer TaggedValue, unknown>
? TaggedValue
: TData)
| undefined
Expand Down Expand Up @@ -110,7 +110,7 @@ export class QueryClient extends QC {
setQueryData<
TQueryFnData,
TTaggedQueryKey extends QueryKey,
TData = TTaggedQueryKey extends DataTag<unknown, infer TaggedValue>
TData = TTaggedQueryKey extends DataTag<unknown, infer TaggedValue, unknown>
? TaggedValue
: TQueryFnData,
>(
Expand Down
4 changes: 2 additions & 2 deletions packages/vue-query/src/queryOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function queryOptions<
>(
options: UndefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
): UndefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
queryKey: DataTag<TQueryKey, TQueryFnData>
queryKey: DataTag<TQueryKey, TQueryFnData, TError>
}

export function queryOptions<
Expand All @@ -23,7 +23,7 @@ export function queryOptions<
>(
options: DefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
): DefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
queryKey: DataTag<TQueryKey, TQueryFnData>
queryKey: DataTag<TQueryKey, TQueryFnData, TError>
}

export function queryOptions(options: unknown) {
Expand Down