Skip to content

Commit 39b2f81

Browse files
manudeli2-NOW
andauthored
types: add OmitKeyof to Omit object by object's keys strictly (#7139)
* feat(*): add OmitKeyOf to validate Omitting keys * Update packages/vue-query/src/__tests__/useQueries.types.test.ts * Update packages/vue-query/src/__tests__/useQueries.types.test.ts * chore(*): update OmitKeyof's type Co-authored-by: 2-NOW <kj109888@gmail.com> * test(query-core): add test case for OmitKeyof * chore: update --------- Co-authored-by: 2-NOW <kj109888@gmail.com>
1 parent 2ddf803 commit 39b2f81

File tree

32 files changed

+180
-69
lines changed

32 files changed

+180
-69
lines changed

packages/angular-query-experimental/src/inject-queries.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { injectQueryClient } from './inject-query-client'
55
import type { Injector, Signal } from '@angular/core'
66
import type {
77
DefaultError,
8+
OmitKeyof,
89
QueriesObserverOptions,
910
QueriesPlaceholderDataFunction,
1011
QueryFunction,
@@ -22,7 +23,7 @@ type QueryObserverOptionsForCreateQueries<
2223
TError = DefaultError,
2324
TData = TQueryFnData,
2425
TQueryKey extends QueryKey = QueryKey,
25-
> = Omit<
26+
> = OmitKeyof<
2627
QueryObserverOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>,
2728
'placeholderData'
2829
> & {

packages/angular-query-experimental/src/types.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
MutateFunction,
1010
MutationObserverOptions,
1111
MutationObserverResult,
12+
OmitKeyof,
1213
QueryKey,
1314
QueryObserverOptions,
1415
QueryObserverResult,
@@ -34,7 +35,7 @@ export interface CreateQueryOptions<
3435
TError = DefaultError,
3536
TData = TQueryFnData,
3637
TQueryKey extends QueryKey = QueryKey,
37-
> extends Omit<
38+
> extends OmitKeyof<
3839
CreateBaseQueryOptions<
3940
TQueryFnData,
4041
TError,
@@ -82,7 +83,7 @@ export interface CreateInfiniteQueryOptions<
8283
TQueryData = TQueryFnData,
8384
TQueryKey extends QueryKey = QueryKey,
8485
TPageParam = unknown,
85-
> extends Omit<
86+
> extends OmitKeyof<
8687
InfiniteQueryObserverOptions<
8788
TQueryFnData,
8889
TError,
@@ -99,7 +100,7 @@ export type CreateBaseQueryResult<
99100
TError = DefaultError,
100101
TState = QueryObserverResult<TData, TError>,
101102
> = BaseQueryNarrowing<TData, TError> &
102-
MapToSignals<Omit<TState, keyof BaseQueryNarrowing>>
103+
MapToSignals<OmitKeyof<TState, keyof BaseQueryNarrowing, 'safely'>>
103104

104105
export type CreateQueryResult<
105106
TData = unknown,
@@ -131,7 +132,7 @@ export interface CreateMutationOptions<
131132
TError = DefaultError,
132133
TVariables = void,
133134
TContext = unknown,
134-
> extends Omit<
135+
> extends OmitKeyof<
135136
MutationObserverOptions<TData, TError, TVariables, TContext>,
136137
'_defaulted'
137138
> {}
@@ -250,7 +251,7 @@ export type CreateMutationResult<
250251
TContext
251252
>,
252253
> = BaseMutationNarrowing<TData, TError, TVariables, TContext> &
253-
MapToSignals<Omit<TState, keyof BaseMutationNarrowing>>
254+
MapToSignals<OmitKeyof<TState, keyof BaseMutationNarrowing, 'safely'>>
254255

255256
type Override<TTargetA, TTargetB> = {
256257
[AKey in keyof TTargetA]: AKey extends keyof TTargetB
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { describe, expectTypeOf, it } from 'vitest'
2+
import type { OmitKeyof } from '..'
3+
4+
describe('OmitKeyof', () => {
5+
it("'s type check", () => {
6+
type A = {
7+
x: string
8+
y: number
9+
}
10+
11+
type ExpectedType = {
12+
x: string
13+
}
14+
15+
// Bad point
16+
// 1. original Omit can use 'z' as type parameter with no type error
17+
// 2. original Omit have no auto complete for 2nd type parameter
18+
expectTypeOf<Omit<A, 'z' | 'y'>>().toEqualTypeOf<ExpectedType>()
19+
20+
// Solution
21+
22+
// 1. strictly
23+
expectTypeOf<
24+
OmitKeyof<
25+
A,
26+
// OmitKeyof can't use 'z' as type parameter with type error because A don't have key 'z'
27+
// @ts-expect-error Type does not satisfy the constraint keyof A
28+
'z' | 'y'
29+
>
30+
>().toEqualTypeOf<ExpectedType>
31+
expectTypeOf<
32+
OmitKeyof<
33+
A,
34+
// OmitKeyof can't use 'z' as type parameter with type error because A don't have key 'z'
35+
// @ts-expect-error Type does not satisfy the constraint keyof A
36+
'z' | 'y',
37+
'strictly'
38+
>
39+
>().toEqualTypeOf<ExpectedType>
40+
41+
// 2. safely
42+
expectTypeOf<
43+
OmitKeyof<
44+
A,
45+
// OmitKeyof can't use 'z' as type parameter type error with strictly parameter or default parameter
46+
// @ts-expect-error Type does not satisfy the constraint keyof A
47+
'z' | 'y'
48+
>
49+
>().toEqualTypeOf<ExpectedType>
50+
expectTypeOf<
51+
OmitKeyof<
52+
A,
53+
// With 'safely', OmitKeyof can use 'z' as type parameter like original Omit but This support autocomplete too yet for DX.
54+
'z' | 'y',
55+
'safely'
56+
>
57+
>().toEqualTypeOf<ExpectedType>
58+
})
59+
})

packages/query-core/src/infiniteQueryBehavior.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { QueryBehavior } from './query'
33
import type {
44
InfiniteData,
55
InfiniteQueryPageParamsOptions,
6+
OmitKeyof,
67
QueryFunctionContext,
78
QueryKey,
89
} from './types'
@@ -67,7 +68,7 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
6768
return Promise.resolve(data)
6869
}
6970

70-
const queryFnContext: Omit<
71+
const queryFnContext: OmitKeyof<
7172
QueryFunctionContext<QueryKey, unknown>,
7273
'signal'
7374
> = {

packages/query-core/src/query.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
DefaultError,
88
FetchStatus,
99
InitialDataFunction,
10+
OmitKeyof,
1011
QueryFunctionContext,
1112
QueryKey,
1213
QueryMeta,
@@ -370,7 +371,10 @@ export class Query<
370371
const abortController = new AbortController()
371372

372373
// Create query function context
373-
const queryFnContext: Omit<QueryFunctionContext<TQueryKey>, 'signal'> = {
374+
const queryFnContext: OmitKeyof<
375+
QueryFunctionContext<TQueryKey>,
376+
'signal'
377+
> = {
374378
queryKey: this.queryKey,
375379
meta: this.meta,
376380
}
@@ -421,7 +425,7 @@ export class Query<
421425
}
422426

423427
// Trigger behavior hook
424-
const context: Omit<
428+
const context: OmitKeyof<
425429
FetchContext<TQueryFnData, TError, TData, TQueryKey>,
426430
'signal'
427431
> = {

packages/query-core/src/queryClient.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { focusManager } from './focusManager'
1212
import { onlineManager } from './onlineManager'
1313
import { notifyManager } from './notifyManager'
1414
import { infiniteQueryBehavior } from './infiniteQueryBehavior'
15-
import type { DataTag, NoInfer } from './types'
15+
import type { DataTag, NoInfer, OmitKeyof } from './types'
1616
import type { QueryState } from './query'
1717
import type {
1818
CancelOptions,
@@ -43,7 +43,7 @@ import type { MutationFilters, QueryFilters, Updater } from './utils'
4343

4444
interface QueryDefaults {
4545
queryKey: QueryKey
46-
defaultOptions: Omit<QueryOptions<any, any, any>, 'queryKey'>
46+
defaultOptions: OmitKeyof<QueryOptions<any, any, any>, 'queryKey'>
4747
}
4848

4949
interface MutationDefaults {
@@ -429,7 +429,7 @@ export class QueryClient {
429429
setQueryDefaults(
430430
queryKey: QueryKey,
431431
options: Partial<
432-
Omit<QueryObserverOptions<unknown, any, any, any>, 'queryKey'>
432+
OmitKeyof<QueryObserverOptions<unknown, any, any, any>, 'queryKey'>
433433
>,
434434
): void {
435435
this.#queryDefaults.set(hashKey(queryKey), {
@@ -440,10 +440,10 @@ export class QueryClient {
440440

441441
getQueryDefaults(
442442
queryKey: QueryKey,
443-
): Omit<QueryObserverOptions<any, any, any, any, any>, 'queryKey'> {
443+
): OmitKeyof<QueryObserverOptions<any, any, any, any, any>, 'queryKey'> {
444444
const defaults = [...this.#queryDefaults.values()]
445445

446-
let result: Omit<
446+
let result: OmitKeyof<
447447
QueryObserverOptions<any, any, any, any, any>,
448448
'queryKey'
449449
> = {}
@@ -458,7 +458,10 @@ export class QueryClient {
458458

459459
setMutationDefaults(
460460
mutationKey: MutationKey,
461-
options: Omit<MutationObserverOptions<any, any, any, any>, 'mutationKey'>,
461+
options: OmitKeyof<
462+
MutationObserverOptions<any, any, any, any>,
463+
'mutationKey'
464+
>,
462465
): void {
463466
this.#mutationDefaults.set(hashKey(mutationKey), {
464467
mutationKey,

packages/query-core/src/types.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ import type { QueryFilters, QueryTypeFilter, SkipToken } from './utils'
77
import type { QueryCache } from './queryCache'
88
import type { MutationCache } from './mutationCache'
99

10+
export type OmitKeyof<
11+
TObject,
12+
TKey extends TStrictly extends 'safely'
13+
? keyof TObject | (string & Record<never, never>)
14+
: keyof TObject,
15+
TStrictly extends 'strictly' | 'safely' = 'strictly',
16+
> = Omit<TObject, TKey>
17+
1018
export type NoInfer<T> = [T][T extends any ? 0 : never]
1119

1220
export interface Register {
@@ -341,7 +349,7 @@ export type Optional<TTarget, TKey extends keyof TTarget> = Pick<
341349
Partial<TTarget>,
342350
TKey
343351
> &
344-
Omit<TTarget, TKey>
352+
OmitKeyof<TTarget, TKey>
345353

346354
export type DefaultedQueryObserverOptions<
347355
TQueryFnData = unknown,
@@ -889,7 +897,10 @@ export interface QueryClientConfig {
889897
}
890898

891899
export interface DefaultOptions<TError = DefaultError> {
892-
queries?: Omit<QueryObserverOptions<unknown, TError>, 'suspense' | 'queryKey'>
900+
queries?: OmitKeyof<
901+
QueryObserverOptions<unknown, TError>,
902+
'suspense' | 'queryKey'
903+
>
893904
mutations?: MutationObserverOptions<unknown, TError, unknown, unknown>
894905
}
895906

packages/react-query-persist-client/src/PersistQueryClientProvider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import {
77
} from '@tanstack/query-persist-client-core'
88
import { IsRestoringProvider, QueryClientProvider } from '@tanstack/react-query'
99
import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
10-
import type { QueryClientProviderProps } from '@tanstack/react-query'
10+
import type { OmitKeyof, QueryClientProviderProps } from '@tanstack/react-query'
1111

1212
export type PersistQueryClientProviderProps = QueryClientProviderProps & {
13-
persistOptions: Omit<PersistQueryClientOptions, 'queryClient'>
13+
persistOptions: OmitKeyof<PersistQueryClientOptions, 'queryClient'>
1414
onSuccess?: () => Promise<unknown> | unknown
1515
}
1616

packages/react-query/src/HydrationBoundary.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ import { useQueryClient } from './QueryClientProvider'
66
import type {
77
DehydratedState,
88
HydrateOptions,
9+
OmitKeyof,
910
QueryClient,
1011
} from '@tanstack/query-core'
1112

1213
export interface HydrationBoundaryProps {
1314
state?: unknown
14-
options?: Omit<HydrateOptions, 'defaultOptions'> & {
15-
defaultOptions?: Omit<HydrateOptions['defaultOptions'], 'mutations'>
15+
options?: OmitKeyof<HydrateOptions, 'defaultOptions'> & {
16+
defaultOptions?: OmitKeyof<
17+
Exclude<HydrateOptions['defaultOptions'], undefined>,
18+
'mutations'
19+
>
1620
}
1721
children?: React.ReactNode
1822
queryClient?: QueryClient

packages/react-query/src/__tests__/useQueries.test-d.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, expectTypeOf, it } from 'vitest'
22
import { queryOptions } from '../queryOptions'
33
import { useQueries } from '../useQueries'
4+
import type { OmitKeyof } from '..'
45
import type { UseQueryOptions } from '../types'
56

67
describe('UseQueries config object overload', () => {
@@ -104,7 +105,7 @@ describe('UseQueries config object overload', () => {
104105
type Data = string
105106

106107
const useCustomQueries = (
107-
options?: Omit<UseQueryOptions<Data>, 'queryKey' | 'queryFn'>,
108+
options?: OmitKeyof<UseQueryOptions<Data>, 'queryKey' | 'queryFn'>,
108109
) => {
109110
return useQueries({
110111
queries: [

0 commit comments

Comments
 (0)