Skip to content

Commit 1b54251

Browse files
authored
feat(types): typed query filters (#8670)
* 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
1 parent e85df7a commit 1b54251

File tree

5 files changed

+315
-83
lines changed

5 files changed

+315
-83
lines changed

packages/query-core/src/__tests__/queryClient.test-d.tsx

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,3 +497,113 @@ describe('fully typed usage', () => {
497497
queryClient.getMutationDefaults(mutationKey)
498498
})
499499
})
500+
501+
describe('invalidateQueries', () => {
502+
it('shows type error when queryKey is a wrong type in invalidateQueries', () => {
503+
const queryClient = new QueryClient()
504+
505+
queryClient.invalidateQueries()
506+
507+
queryClient.invalidateQueries({
508+
queryKey: ['1'],
509+
})
510+
511+
queryClient.invalidateQueries({
512+
// @ts-expect-error
513+
queryKey: '1',
514+
})
515+
516+
queryClient.invalidateQueries({
517+
// @ts-expect-error
518+
queryKey: {},
519+
})
520+
})
521+
it('needs queryKey to be an array (#8684)', () => {
522+
new QueryClient().invalidateQueries({
523+
// @ts-expect-error key is not an array
524+
queryKey: { foo: true },
525+
})
526+
})
527+
it('predicate should be typed if key is tagged', () => {
528+
const queryKey = ['key'] as DataTag<Array<string>, number>
529+
const queryClient = new QueryClient()
530+
queryClient.invalidateQueries({
531+
queryKey,
532+
predicate: (query) => {
533+
expectTypeOf(query.state.data).toEqualTypeOf<number | undefined>()
534+
expectTypeOf(query.queryKey).toEqualTypeOf<
535+
DataTag<Array<string>, number>
536+
>()
537+
return true
538+
},
539+
})
540+
})
541+
})
542+
543+
describe('cancelQueries', () => {
544+
it('predicate should be typed if key is tagged', () => {
545+
const queryKey = ['key'] as DataTag<Array<string>, number>
546+
const queryClient = new QueryClient()
547+
queryClient.cancelQueries({
548+
queryKey,
549+
predicate: (query) => {
550+
expectTypeOf(query.state.data).toEqualTypeOf<number | undefined>()
551+
expectTypeOf(query.queryKey).toEqualTypeOf<
552+
DataTag<Array<string>, number>
553+
>()
554+
return true
555+
},
556+
})
557+
})
558+
})
559+
560+
describe('removeQueries', () => {
561+
it('predicate should be typed if key is tagged', () => {
562+
const queryKey = ['key'] as DataTag<Array<string>, number>
563+
const queryClient = new QueryClient()
564+
queryClient.removeQueries({
565+
queryKey,
566+
predicate: (query) => {
567+
expectTypeOf(query.state.data).toEqualTypeOf<number | undefined>()
568+
expectTypeOf(query.queryKey).toEqualTypeOf<
569+
DataTag<Array<string>, number>
570+
>()
571+
return true
572+
},
573+
})
574+
})
575+
})
576+
577+
describe('refetchQueries', () => {
578+
it('predicate should be typed if key is tagged', () => {
579+
const queryKey = ['key'] as DataTag<Array<string>, number>
580+
const queryClient = new QueryClient()
581+
queryClient.refetchQueries({
582+
queryKey,
583+
predicate: (query) => {
584+
expectTypeOf(query.state.data).toEqualTypeOf<number | undefined>()
585+
expectTypeOf(query.queryKey).toEqualTypeOf<
586+
DataTag<Array<string>, number>
587+
>()
588+
return true
589+
},
590+
})
591+
})
592+
})
593+
594+
describe('resetQueries', () => {
595+
it('predicate should be typed if key is tagged', () => {
596+
const queryKey = ['key'] as DataTag<Array<string>, number>
597+
const queryClient = new QueryClient()
598+
queryClient.resetQueries({
599+
queryKey,
600+
predicate: (query) => {
601+
expectTypeOf(query.state.data).toEqualTypeOf<number | undefined>()
602+
expectTypeOf(query.queryKey).toEqualTypeOf<
603+
DataTag<Array<string>, number>
604+
>()
605+
return true
606+
},
607+
})
608+
})
609+
})

packages/query-core/src/queryCache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export class QueryCache extends Subscribable<QueryCacheListener> {
190190
) as Query<TQueryFnData, TError, TData> | undefined
191191
}
192192

193-
findAll(filters: QueryFilters = {}): Array<Query> {
193+
findAll(filters: QueryFilters<any, any, any, any> = {}): Array<Query> {
194194
const queries = this.getAll()
195195
return Object.keys(filters).length > 0
196196
? queries.filter((query) => matchQuery(filters, query))

packages/query-core/src/queryClient.ts

Lines changed: 83 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ import { notifyManager } from './notifyManager'
1515
import { infiniteQueryBehavior } from './infiniteQueryBehavior'
1616
import type {
1717
CancelOptions,
18-
DataTag,
1918
DefaultError,
2019
DefaultOptions,
2120
DefaultedQueryObserverOptions,
2221
EnsureInfiniteQueryDataOptions,
2322
EnsureQueryDataOptions,
2423
FetchInfiniteQueryOptions,
2524
FetchQueryOptions,
25+
InferDataFromTag,
26+
InferErrorFromTag,
2627
InfiniteData,
2728
InvalidateOptions,
2829
InvalidateQueryFilters,
@@ -39,7 +40,6 @@ import type {
3940
RefetchQueryFilters,
4041
ResetOptions,
4142
SetDataOptions,
42-
UnsetMarker,
4343
} from './types'
4444
import type { QueryState } from './query'
4545
import type { MutationFilters, QueryFilters, Updater } from './utils'
@@ -122,13 +122,7 @@ export class QueryClient {
122122
getQueryData<
123123
TQueryFnData = unknown,
124124
TTaggedQueryKey extends QueryKey = QueryKey,
125-
TInferredQueryFnData = TTaggedQueryKey extends DataTag<
126-
unknown,
127-
infer TaggedValue,
128-
unknown
129-
>
130-
? TaggedValue
131-
: TQueryFnData,
125+
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
132126
>(queryKey: TTaggedQueryKey): TInferredQueryFnData | undefined {
133127
const options = this.defaultQueryOptions({ queryKey })
134128

@@ -191,13 +185,7 @@ export class QueryClient {
191185
setQueryData<
192186
TQueryFnData = unknown,
193187
TTaggedQueryKey extends QueryKey = QueryKey,
194-
TInferredQueryFnData = TTaggedQueryKey extends DataTag<
195-
unknown,
196-
infer TaggedValue,
197-
unknown
198-
>
199-
? TaggedValue
200-
: TQueryFnData,
188+
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
201189
>(
202190
queryKey: TTaggedQueryKey,
203191
updater: Updater<
@@ -267,22 +255,8 @@ export class QueryClient {
267255
TQueryFnData = unknown,
268256
TError = DefaultError,
269257
TTaggedQueryKey extends QueryKey = QueryKey,
270-
TInferredQueryFnData = TTaggedQueryKey extends DataTag<
271-
unknown,
272-
infer TaggedValue,
273-
unknown
274-
>
275-
? TaggedValue
276-
: TQueryFnData,
277-
TInferredError = TTaggedQueryKey extends DataTag<
278-
unknown,
279-
unknown,
280-
infer TaggedError
281-
>
282-
? TaggedError extends UnsetMarker
283-
? TError
284-
: TaggedError
285-
: TError,
258+
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
259+
TInferredError = InferErrorFromTag<TError, TTaggedQueryKey>,
286260
>(
287261
queryKey: TTaggedQueryKey,
288262
): QueryState<TInferredQueryFnData, TInferredError> | undefined {
@@ -293,8 +267,19 @@ export class QueryClient {
293267
}
294268

295269
removeQueries<
296-
TQueryFilters extends QueryFilters<any, any, any, any> = QueryFilters,
297-
>(filters?: TQueryFilters): void {
270+
TQueryFnData = unknown,
271+
TError = DefaultError,
272+
TTaggedQueryKey extends QueryKey = QueryKey,
273+
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
274+
TInferredError = InferErrorFromTag<TError, TTaggedQueryKey>,
275+
>(
276+
filters?: QueryFilters<
277+
TInferredQueryFnData,
278+
TInferredError,
279+
TInferredQueryFnData,
280+
TTaggedQueryKey
281+
>,
282+
): void {
298283
const queryCache = this.#queryCache
299284
notifyManager.batch(() => {
300285
queryCache.findAll(filters).forEach((query) => {
@@ -304,26 +289,51 @@ export class QueryClient {
304289
}
305290

306291
resetQueries<
307-
TQueryFilters extends QueryFilters<any, any, any, any> = QueryFilters,
308-
>(filters?: TQueryFilters, options?: ResetOptions): Promise<void> {
292+
TQueryFnData = unknown,
293+
TError = DefaultError,
294+
TTaggedQueryKey extends QueryKey = QueryKey,
295+
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
296+
TInferredError = InferErrorFromTag<TError, TTaggedQueryKey>,
297+
>(
298+
filters?: QueryFilters<
299+
TInferredQueryFnData,
300+
TInferredError,
301+
TInferredQueryFnData,
302+
TTaggedQueryKey
303+
>,
304+
options?: ResetOptions,
305+
): Promise<void> {
309306
const queryCache = this.#queryCache
310307

311-
const refetchFilters: RefetchQueryFilters = {
312-
type: 'active',
313-
...filters,
314-
}
315-
316308
return notifyManager.batch(() => {
317309
queryCache.findAll(filters).forEach((query) => {
318310
query.reset()
319311
})
320-
return this.refetchQueries(refetchFilters, options)
312+
return this.refetchQueries(
313+
{
314+
type: 'active',
315+
...filters,
316+
},
317+
options,
318+
)
321319
})
322320
}
323321

324322
cancelQueries<
325-
TQueryFilters extends QueryFilters<any, any, any, any> = QueryFilters,
326-
>(filters?: TQueryFilters, cancelOptions: CancelOptions = {}): Promise<void> {
323+
TQueryFnData = unknown,
324+
TError = DefaultError,
325+
TTaggedQueryKey extends QueryKey = QueryKey,
326+
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
327+
TInferredError = InferErrorFromTag<TError, TTaggedQueryKey>,
328+
>(
329+
filters?: QueryFilters<
330+
TInferredQueryFnData,
331+
TInferredError,
332+
TInferredQueryFnData,
333+
TTaggedQueryKey
334+
>,
335+
cancelOptions: CancelOptions = {},
336+
): Promise<void> {
327337
const defaultedCancelOptions = { revert: true, ...cancelOptions }
328338

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

338348
invalidateQueries<
339-
TInvalidateQueryFilters extends InvalidateQueryFilters<
340-
any,
341-
any,
342-
any,
343-
any
344-
> = InvalidateQueryFilters,
349+
TQueryFnData = unknown,
350+
TError = DefaultError,
351+
TTaggedQueryKey extends QueryKey = QueryKey,
352+
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
353+
TInferredError = InferErrorFromTag<TError, TTaggedQueryKey>,
345354
>(
346-
filters?: TInvalidateQueryFilters,
355+
filters?: InvalidateQueryFilters<
356+
TInferredQueryFnData,
357+
TInferredError,
358+
TInferredQueryFnData,
359+
TTaggedQueryKey
360+
>,
347361
options: InvalidateOptions = {},
348362
): Promise<void> {
349363
return notifyManager.batch(() => {
@@ -354,23 +368,29 @@ export class QueryClient {
354368
if (filters?.refetchType === 'none') {
355369
return Promise.resolve()
356370
}
357-
const refetchFilters: RefetchQueryFilters = {
358-
...filters,
359-
type: filters?.refetchType ?? filters?.type ?? 'active',
360-
}
361-
return this.refetchQueries(refetchFilters, options)
371+
return this.refetchQueries(
372+
{
373+
...filters,
374+
type: filters?.refetchType ?? filters?.type ?? 'active',
375+
},
376+
options,
377+
)
362378
})
363379
}
364380

365381
refetchQueries<
366-
TRefetchQueryFilters extends RefetchQueryFilters<
367-
any,
368-
any,
369-
any,
370-
any
371-
> = RefetchQueryFilters,
382+
TQueryFnData = unknown,
383+
TError = DefaultError,
384+
TTaggedQueryKey extends QueryKey = QueryKey,
385+
TInferredQueryFnData = InferDataFromTag<TQueryFnData, TTaggedQueryKey>,
386+
TInferredError = InferErrorFromTag<TError, TTaggedQueryKey>,
372387
>(
373-
filters?: TRefetchQueryFilters,
388+
filters?: RefetchQueryFilters<
389+
TInferredQueryFnData,
390+
TInferredError,
391+
TInferredQueryFnData,
392+
TTaggedQueryKey
393+
>,
374394
options: RefetchOptions = {},
375395
): Promise<void> {
376396
const fetchOptions = {

packages/query-core/src/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ export type DataTag<
7474
[dataTagErrorSymbol]: TError
7575
}
7676

77+
export type InferDataFromTag<TQueryFnData, TTaggedQueryKey extends QueryKey> =
78+
TTaggedQueryKey extends DataTag<unknown, infer TaggedValue, unknown>
79+
? TaggedValue
80+
: TQueryFnData
81+
82+
export type InferErrorFromTag<TError, TTaggedQueryKey extends QueryKey> =
83+
TTaggedQueryKey extends DataTag<unknown, unknown, infer TaggedError>
84+
? TaggedError extends UnsetMarker
85+
? TError
86+
: TaggedError
87+
: TError
88+
7789
export type QueryFunction<
7890
T = unknown,
7991
TQueryKey extends QueryKey = QueryKey,

0 commit comments

Comments
 (0)