Skip to content

Commit

Permalink
feat(types): typed query filters (#8670)
Browse files Browse the repository at this point in the history
* feat: typed query filters

* refactor: use util types to extract from dataTag

* chore: try to fix vue types with overloads

* chore: vue types

* test: from #8691

* fix: make options a MaybeRefDeep

* feat: types for filters on other queryClient methods

* fix: add a test for 8684
  • Loading branch information
TkDodo authored Mar 3, 2025
1 parent e85df7a commit 1b54251
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 83 deletions.
110 changes: 110 additions & 0 deletions packages/query-core/src/__tests__/queryClient.test-d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,113 @@ describe('fully typed usage', () => {
queryClient.getMutationDefaults(mutationKey)
})
})

describe('invalidateQueries', () => {
it('shows type error when queryKey is a wrong type in invalidateQueries', () => {
const queryClient = new QueryClient()

queryClient.invalidateQueries()

queryClient.invalidateQueries({
queryKey: ['1'],
})

queryClient.invalidateQueries({
// @ts-expect-error
queryKey: '1',
})

queryClient.invalidateQueries({
// @ts-expect-error
queryKey: {},
})
})
it('needs queryKey to be an array (#8684)', () => {
new QueryClient().invalidateQueries({
// @ts-expect-error key is not an array
queryKey: { foo: true },
})
})
it('predicate should be typed if key is tagged', () => {
const queryKey = ['key'] as DataTag<Array<string>, number>
const queryClient = new QueryClient()
queryClient.invalidateQueries({
queryKey,
predicate: (query) => {
expectTypeOf(query.state.data).toEqualTypeOf<number | undefined>()
expectTypeOf(query.queryKey).toEqualTypeOf<
DataTag<Array<string>, number>
>()
return true
},
})
})
})

describe('cancelQueries', () => {
it('predicate should be typed if key is tagged', () => {
const queryKey = ['key'] as DataTag<Array<string>, number>
const queryClient = new QueryClient()
queryClient.cancelQueries({
queryKey,
predicate: (query) => {
expectTypeOf(query.state.data).toEqualTypeOf<number | undefined>()
expectTypeOf(query.queryKey).toEqualTypeOf<
DataTag<Array<string>, number>
>()
return true
},
})
})
})

describe('removeQueries', () => {
it('predicate should be typed if key is tagged', () => {
const queryKey = ['key'] as DataTag<Array<string>, number>
const queryClient = new QueryClient()
queryClient.removeQueries({
queryKey,
predicate: (query) => {
expectTypeOf(query.state.data).toEqualTypeOf<number | undefined>()
expectTypeOf(query.queryKey).toEqualTypeOf<
DataTag<Array<string>, number>
>()
return true
},
})
})
})

describe('refetchQueries', () => {
it('predicate should be typed if key is tagged', () => {
const queryKey = ['key'] as DataTag<Array<string>, number>
const queryClient = new QueryClient()
queryClient.refetchQueries({
queryKey,
predicate: (query) => {
expectTypeOf(query.state.data).toEqualTypeOf<number | undefined>()
expectTypeOf(query.queryKey).toEqualTypeOf<
DataTag<Array<string>, number>
>()
return true
},
})
})
})

describe('resetQueries', () => {
it('predicate should be typed if key is tagged', () => {
const queryKey = ['key'] as DataTag<Array<string>, number>
const queryClient = new QueryClient()
queryClient.resetQueries({
queryKey,
predicate: (query) => {
expectTypeOf(query.state.data).toEqualTypeOf<number | undefined>()
expectTypeOf(query.queryKey).toEqualTypeOf<
DataTag<Array<string>, number>
>()
return true
},
})
})
})
2 changes: 1 addition & 1 deletion packages/query-core/src/queryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export class QueryCache extends Subscribable<QueryCacheListener> {
) as Query<TQueryFnData, TError, TData> | undefined
}

findAll(filters: QueryFilters = {}): Array<Query> {
findAll(filters: QueryFilters<any, any, any, any> = {}): Array<Query> {
const queries = this.getAll()
return Object.keys(filters).length > 0
? queries.filter((query) => matchQuery(filters, query))
Expand Down
146 changes: 83 additions & 63 deletions packages/query-core/src/queryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ import { notifyManager } from './notifyManager'
import { infiniteQueryBehavior } from './infiniteQueryBehavior'
import type {
CancelOptions,
DataTag,
DefaultError,
DefaultOptions,
DefaultedQueryObserverOptions,
EnsureInfiniteQueryDataOptions,
EnsureQueryDataOptions,
FetchInfiniteQueryOptions,
FetchQueryOptions,
InferDataFromTag,
InferErrorFromTag,
InfiniteData,
InvalidateOptions,
InvalidateQueryFilters,
Expand All @@ -39,7 +40,6 @@ import type {
RefetchQueryFilters,
ResetOptions,
SetDataOptions,
UnsetMarker,
} from './types'
import type { QueryState } from './query'
import type { MutationFilters, QueryFilters, Updater } from './utils'
Expand Down Expand Up @@ -122,13 +122,7 @@ export class QueryClient {
getQueryData<
TQueryFnData = unknown,
TTaggedQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = TTaggedQueryKey extends DataTag<
unknown,
infer TaggedValue,
unknown
>
? TaggedValue
: TQueryFnData,
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
>(queryKey: TTaggedQueryKey): TInferredQueryFnData | undefined {
const options = this.defaultQueryOptions({ queryKey })

Expand Down Expand Up @@ -191,13 +185,7 @@ export class QueryClient {
setQueryData<
TQueryFnData = unknown,
TTaggedQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = TTaggedQueryKey extends DataTag<
unknown,
infer TaggedValue,
unknown
>
? TaggedValue
: TQueryFnData,
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
>(
queryKey: TTaggedQueryKey,
updater: Updater<
Expand Down Expand Up @@ -267,22 +255,8 @@ export class QueryClient {
TQueryFnData = unknown,
TError = DefaultError,
TTaggedQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = TTaggedQueryKey extends DataTag<
unknown,
infer TaggedValue,
unknown
>
? TaggedValue
: TQueryFnData,
TInferredError = TTaggedQueryKey extends DataTag<
unknown,
unknown,
infer TaggedError
>
? TaggedError extends UnsetMarker
? TError
: TaggedError
: TError,
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
TInferredError = InferErrorFromTag<TError, TTaggedQueryKey>,
>(
queryKey: TTaggedQueryKey,
): QueryState<TInferredQueryFnData, TInferredError> | undefined {
Expand All @@ -293,8 +267,19 @@ export class QueryClient {
}

removeQueries<
TQueryFilters extends QueryFilters<any, any, any, any> = QueryFilters,
>(filters?: TQueryFilters): void {
TQueryFnData = unknown,
TError = DefaultError,
TTaggedQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
TInferredError = InferErrorFromTag<TError, TTaggedQueryKey>,
>(
filters?: QueryFilters<
TInferredQueryFnData,
TInferredError,
TInferredQueryFnData,
TTaggedQueryKey
>,
): void {
const queryCache = this.#queryCache
notifyManager.batch(() => {
queryCache.findAll(filters).forEach((query) => {
Expand All @@ -304,26 +289,51 @@ export class QueryClient {
}

resetQueries<
TQueryFilters extends QueryFilters<any, any, any, any> = QueryFilters,
>(filters?: TQueryFilters, options?: ResetOptions): Promise<void> {
TQueryFnData = unknown,
TError = DefaultError,
TTaggedQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
TInferredError = InferErrorFromTag<TError, TTaggedQueryKey>,
>(
filters?: QueryFilters<
TInferredQueryFnData,
TInferredError,
TInferredQueryFnData,
TTaggedQueryKey
>,
options?: ResetOptions,
): Promise<void> {
const queryCache = this.#queryCache

const refetchFilters: RefetchQueryFilters = {
type: 'active',
...filters,
}

return notifyManager.batch(() => {
queryCache.findAll(filters).forEach((query) => {
query.reset()
})
return this.refetchQueries(refetchFilters, options)
return this.refetchQueries(
{
type: 'active',
...filters,
},
options,
)
})
}

cancelQueries<
TQueryFilters extends QueryFilters<any, any, any, any> = QueryFilters,
>(filters?: TQueryFilters, cancelOptions: CancelOptions = {}): Promise<void> {
TQueryFnData = unknown,
TError = DefaultError,
TTaggedQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
TInferredError = InferErrorFromTag<TError, TTaggedQueryKey>,
>(
filters?: QueryFilters<
TInferredQueryFnData,
TInferredError,
TInferredQueryFnData,
TTaggedQueryKey
>,
cancelOptions: CancelOptions = {},
): Promise<void> {
const defaultedCancelOptions = { revert: true, ...cancelOptions }

const promises = notifyManager.batch(() =>
Expand All @@ -336,14 +346,18 @@ export class QueryClient {
}

invalidateQueries<
TInvalidateQueryFilters extends InvalidateQueryFilters<
any,
any,
any,
any
> = InvalidateQueryFilters,
TQueryFnData = unknown,
TError = DefaultError,
TTaggedQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
TInferredError = InferErrorFromTag<TError, TTaggedQueryKey>,
>(
filters?: TInvalidateQueryFilters,
filters?: InvalidateQueryFilters<
TInferredQueryFnData,
TInferredError,
TInferredQueryFnData,
TTaggedQueryKey
>,
options: InvalidateOptions = {},
): Promise<void> {
return notifyManager.batch(() => {
Expand All @@ -354,23 +368,29 @@ export class QueryClient {
if (filters?.refetchType === 'none') {
return Promise.resolve()
}
const refetchFilters: RefetchQueryFilters = {
...filters,
type: filters?.refetchType ?? filters?.type ?? 'active',
}
return this.refetchQueries(refetchFilters, options)
return this.refetchQueries(
{
...filters,
type: filters?.refetchType ?? filters?.type ?? 'active',
},
options,
)
})
}

refetchQueries<
TRefetchQueryFilters extends RefetchQueryFilters<
any,
any,
any,
any
> = RefetchQueryFilters,
TQueryFnData = unknown,
TError = DefaultError,
TTaggedQueryKey extends QueryKey = QueryKey,
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
TInferredError = InferErrorFromTag<TError, TTaggedQueryKey>,
>(
filters?: TRefetchQueryFilters,
filters?: RefetchQueryFilters<
TInferredQueryFnData,
TInferredError,
TInferredQueryFnData,
TTaggedQueryKey
>,
options: RefetchOptions = {},
): Promise<void> {
const fetchOptions = {
Expand Down
12 changes: 12 additions & 0 deletions packages/query-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ export type DataTag<
[dataTagErrorSymbol]: TError
}

export type InferDataFromTag<TQueryFnData, TTaggedQueryKey extends QueryKey> =
TTaggedQueryKey extends DataTag<unknown, infer TaggedValue, unknown>
? TaggedValue
: TQueryFnData

export type InferErrorFromTag<TError, TTaggedQueryKey extends QueryKey> =
TTaggedQueryKey extends DataTag<unknown, unknown, infer TaggedError>
? TaggedError extends UnsetMarker
? TError
: TaggedError
: TError

export type QueryFunction<
T = unknown,
TQueryKey extends QueryKey = QueryKey,
Expand Down
Loading

0 comments on commit 1b54251

Please sign in to comment.