Skip to content

Commit b764009

Browse files
fix(svelte-query): Correct data type when initialData is set (#7769)
* Fix createQueries initialData type * Fix createQuery types
1 parent e4e65be commit b764009

File tree

9 files changed

+287
-67
lines changed

9 files changed

+287
-67
lines changed

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

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, expect, expectTypeOf, it } from 'vitest'
1+
import { describe, expectTypeOf, it } from 'vitest'
22
import {
33
QueriesObserver,
44
QueryClient,
@@ -71,14 +71,12 @@ describe('queryOptions', () => {
7171
expectTypeOf(data).toEqualTypeOf<number | undefined>()
7272
})
7373
it('should tag the queryKey with the result type of the QueryFn', () => {
74-
expect(() => {
75-
const { queryKey } = queryOptions({
76-
queryKey: ['key'],
77-
queryFn: () => Promise.resolve(5),
78-
})
79-
80-
expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf<number>()
74+
const { queryKey } = queryOptions({
75+
queryKey: ['key'],
76+
queryFn: () => Promise.resolve(5),
8177
})
78+
79+
expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf<number>()
8280
})
8381
it('should tag the queryKey even if no promise is returned', () => {
8482
const { queryKey } = queryOptions({

packages/svelte-query/src/createQueries.ts

+39-20
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { Readable } from 'svelte/store'
77
import type { StoreOrVal } from './types'
88
import type {
99
DefaultError,
10+
DefinedQueryObserverResult,
1011
OmitKeyof,
1112
QueriesObserverOptions,
1213
QueriesPlaceholderDataFunction,
@@ -19,7 +20,7 @@ import type {
1920
} from '@tanstack/query-core'
2021

2122
// This defines the `CreateQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`.
22-
// `placeholderData` function does not have a parameter
23+
// `placeholderData` function always gets undefined passed
2324
type QueryObserverOptionsForCreateQueries<
2425
TQueryFnData = unknown,
2526
TError = DefaultError,
@@ -38,7 +39,7 @@ type MAXIMUM_DEPTH = 20
3839
// Widen the type of the symbol to enable type inference even if skipToken is not immutable.
3940
type SkipTokenForUseQueries = symbol
4041

41-
type GetOptions<T> =
42+
type GetQueryObserverOptionsForCreateQueries<T> =
4243
// Part 1: responsible for applying explicit type parameter to function arguments, if object { queryFnData: TQueryFnData, error: TError, data: TData }
4344
T extends {
4445
queryFnData: infer TQueryFnData
@@ -74,21 +75,38 @@ type GetOptions<T> =
7475
: // Fallback
7576
QueryObserverOptionsForCreateQueries
7677

77-
type GetResults<T> =
78+
// A defined initialData setting should return a DefinedQueryObserverResult rather than CreateQueryResult
79+
type GetDefinedOrUndefinedQueryResult<T, TData, TError = unknown> = T extends {
80+
initialData?: infer TInitialData
81+
}
82+
? unknown extends TInitialData
83+
? QueryObserverResult<TData, TError>
84+
: TInitialData extends TData
85+
? DefinedQueryObserverResult<TData, TError>
86+
: TInitialData extends () => infer TInitialDataResult
87+
? unknown extends TInitialDataResult
88+
? QueryObserverResult<TData, TError>
89+
: TInitialDataResult extends TData
90+
? DefinedQueryObserverResult<TData, TError>
91+
: QueryObserverResult<TData, TError>
92+
: QueryObserverResult<TData, TError>
93+
: QueryObserverResult<TData, TError>
94+
95+
type GetCreateQueryResult<T> =
7896
// Part 1: responsible for mapping explicit type parameter to function result, if object
7997
T extends { queryFnData: any; error?: infer TError; data: infer TData }
80-
? QueryObserverResult<TData, TError>
98+
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
8199
: T extends { queryFnData: infer TQueryFnData; error?: infer TError }
82-
? QueryObserverResult<TQueryFnData, TError>
100+
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>
83101
: T extends { data: infer TData; error?: infer TError }
84-
? QueryObserverResult<TData, TError>
102+
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
85103
: // Part 2: responsible for mapping explicit type parameter to function result, if tuple
86104
T extends [any, infer TError, infer TData]
87-
? QueryObserverResult<TData, TError>
105+
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
88106
: T extends [infer TQueryFnData, infer TError]
89-
? QueryObserverResult<TQueryFnData, TError>
107+
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>
90108
: T extends [infer TQueryFnData]
91-
? QueryObserverResult<TQueryFnData>
109+
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData>
92110
: // Part 3: responsible for mapping inferred type to results, if no explicit parameter was provided
93111
T extends {
94112
queryFn?:
@@ -97,7 +115,8 @@ type GetResults<T> =
97115
select?: (data: any) => infer TData
98116
throwOnError?: ThrowOnError<any, infer TError, any, any>
99117
}
100-
? QueryObserverResult<
118+
? GetDefinedOrUndefinedQueryResult<
119+
T,
101120
unknown extends TData ? TQueryFnData : TData,
102121
unknown extends TError ? DefaultError : TError
103122
>
@@ -109,18 +128,18 @@ type GetResults<T> =
109128
*/
110129
export type QueriesOptions<
111130
T extends Array<any>,
112-
TResult extends Array<any> = [],
131+
TResults extends Array<any> = [],
113132
TDepth extends ReadonlyArray<number> = [],
114133
> = TDepth['length'] extends MAXIMUM_DEPTH
115134
? Array<QueryObserverOptionsForCreateQueries>
116135
: T extends []
117136
? []
118137
: T extends [infer Head]
119-
? [...TResult, GetOptions<Head>]
120-
: T extends [infer Head, ...infer Tail]
138+
? [...TResults, GetQueryObserverOptionsForCreateQueries<Head>]
139+
: T extends [infer Head, ...infer Tails]
121140
? QueriesOptions<
122-
[...Tail],
123-
[...TResult, GetOptions<Head>],
141+
[...Tails],
142+
[...TResults, GetQueryObserverOptionsForCreateQueries<Head>],
124143
[...TDepth, 1]
125144
>
126145
: ReadonlyArray<unknown> extends T
@@ -151,18 +170,18 @@ export type QueriesOptions<
151170
*/
152171
export type QueriesResults<
153172
T extends Array<any>,
154-
TResult extends Array<any> = [],
173+
TResults extends Array<any> = [],
155174
TDepth extends ReadonlyArray<number> = [],
156175
> = TDepth['length'] extends MAXIMUM_DEPTH
157176
? Array<QueryObserverResult>
158177
: T extends []
159178
? []
160179
: T extends [infer Head]
161-
? [...TResult, GetResults<Head>]
162-
: T extends [infer Head, ...infer Tail]
180+
? [...TResults, GetCreateQueryResult<Head>]
181+
: T extends [infer Head, ...infer Tails]
163182
? QueriesResults<
164-
[...Tail],
165-
[...TResult, GetResults<Head>],
183+
[...Tails],
184+
[...TResults, GetCreateQueryResult<Head>],
166185
[...TDepth, 1]
167186
>
168187
: T extends Array<

packages/svelte-query/src/createQuery.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ export function createQuery<
1919
TQueryKey extends QueryKey = QueryKey,
2020
>(
2121
options: StoreOrVal<
22-
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
22+
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
2323
>,
2424
queryClient?: QueryClient,
25-
): CreateQueryResult<TData, TError>
25+
): DefinedCreateQueryResult<TData, TError>
2626

2727
export function createQuery<
2828
TQueryFnData = unknown,
@@ -31,13 +31,13 @@ export function createQuery<
3131
TQueryKey extends QueryKey = QueryKey,
3232
>(
3333
options: StoreOrVal<
34-
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
34+
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
3535
>,
3636
queryClient?: QueryClient,
37-
): DefinedCreateQueryResult<TData, TError>
37+
): CreateQueryResult<TData, TError>
3838

3939
export function createQuery<
40-
TQueryFnData,
40+
TQueryFnData = unknown,
4141
TError = DefaultError,
4242
TData = TQueryFnData,
4343
TQueryKey extends QueryKey = QueryKey,
@@ -46,6 +46,11 @@ export function createQuery<
4646
CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>
4747
>,
4848
queryClient?: QueryClient,
49+
): CreateQueryResult<TData, TError>
50+
51+
export function createQuery(
52+
options: StoreOrVal<CreateQueryOptions>,
53+
queryClient?: QueryClient,
4954
) {
5055
return createBaseQuery(options, QueryObserver, queryClient)
5156
}

packages/svelte-query/src/queryOptions.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ export function queryOptions<
2929
TData = TQueryFnData,
3030
TQueryKey extends QueryKey = QueryKey,
3131
>(
32-
options: UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
33-
): UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
32+
options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
33+
): DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
3434
queryKey: DataTag<TQueryKey, TQueryFnData>
3535
}
3636

@@ -40,8 +40,8 @@ export function queryOptions<
4040
TData = TQueryFnData,
4141
TQueryKey extends QueryKey = QueryKey,
4242
>(
43-
options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
44-
): DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
43+
options: UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
44+
): UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
4545
queryKey: DataTag<TQueryKey, TQueryFnData>
4646
}
4747

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { describe, expectTypeOf, test } from 'vitest'
2+
import { get } from 'svelte/store'
3+
import { skipToken } from '@tanstack/query-core'
4+
import { createQueries, queryOptions } from '../../src/index'
5+
import type { OmitKeyof, QueryObserverResult } from '@tanstack/query-core'
6+
import type { CreateQueryOptions } from '../../src/index'
7+
8+
describe('createQueries', () => {
9+
test('TData should be defined when passed through queryOptions', () => {
10+
const options = queryOptions({
11+
queryKey: ['key'],
12+
queryFn: () => {
13+
return {
14+
wow: true,
15+
}
16+
},
17+
initialData: {
18+
wow: true,
19+
},
20+
})
21+
const queryResults = createQueries({ queries: [options] })
22+
23+
const data = get(queryResults)[0].data
24+
25+
expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>()
26+
})
27+
28+
test('Allow custom hooks using UseQueryOptions', () => {
29+
type Data = string
30+
31+
const useCustomQueries = (
32+
options?: OmitKeyof<CreateQueryOptions<Data>, 'queryKey' | 'queryFn'>,
33+
) => {
34+
return createQueries({
35+
queries: [
36+
{
37+
...options,
38+
queryKey: ['todos-key'],
39+
queryFn: () => Promise.resolve('data'),
40+
},
41+
],
42+
})
43+
}
44+
45+
const query = useCustomQueries()
46+
const data = get(query)[0].data
47+
48+
expectTypeOf(data).toEqualTypeOf<Data | undefined>()
49+
})
50+
51+
test('TData should have correct type when conditional skipToken is passed', () => {
52+
const queryResults = createQueries({
53+
queries: [
54+
{
55+
queryKey: ['withSkipToken'],
56+
queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5),
57+
},
58+
],
59+
})
60+
61+
const firstResult = get(queryResults)[0]
62+
63+
expectTypeOf(firstResult).toEqualTypeOf<
64+
QueryObserverResult<number, Error>
65+
>()
66+
expectTypeOf(firstResult.data).toEqualTypeOf<number | undefined>()
67+
})
68+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { describe, expectTypeOf, test } from 'vitest'
2+
import { get } from 'svelte/store'
3+
import { createQuery, queryOptions } from '../../src/index'
4+
import type { OmitKeyof } from '@tanstack/query-core'
5+
import type { CreateQueryOptions } from '../../src/index'
6+
7+
describe('createQuery', () => {
8+
test('TData should always be defined when initialData is provided as an object', () => {
9+
const query = createQuery({
10+
queryKey: ['key'],
11+
queryFn: () => ({ wow: true }),
12+
initialData: { wow: true },
13+
})
14+
15+
expectTypeOf(get(query).data).toEqualTypeOf<{ wow: boolean }>()
16+
})
17+
18+
test('TData should be defined when passed through queryOptions', () => {
19+
const options = queryOptions({
20+
queryKey: ['key'],
21+
queryFn: () => {
22+
return {
23+
wow: true,
24+
}
25+
},
26+
initialData: {
27+
wow: true,
28+
},
29+
})
30+
const query = createQuery(options)
31+
32+
expectTypeOf(get(query).data).toEqualTypeOf<{ wow: boolean }>()
33+
})
34+
35+
test('TData should have undefined in the union when initialData is NOT provided', () => {
36+
const query = createQuery({
37+
queryKey: ['key'],
38+
queryFn: () => {
39+
return {
40+
wow: true,
41+
}
42+
},
43+
})
44+
45+
expectTypeOf(get(query).data).toEqualTypeOf<{ wow: boolean } | undefined>()
46+
})
47+
48+
test('Allow custom hooks using CreateQueryOptions', () => {
49+
type Data = string
50+
51+
const useCustomQuery = (
52+
options?: OmitKeyof<CreateQueryOptions<Data>, 'queryKey' | 'queryFn'>,
53+
) => {
54+
return createQuery({
55+
...options,
56+
queryKey: ['todos-key'],
57+
queryFn: () => Promise.resolve('data'),
58+
})
59+
}
60+
61+
const query = useCustomQuery()
62+
63+
expectTypeOf(get(query).data).toEqualTypeOf<Data | undefined>()
64+
})
65+
})

0 commit comments

Comments
 (0)