Skip to content

Commit

Permalink
fix(svelte-query): Correct data type when initialData is set (#7769)
Browse files Browse the repository at this point in the history
* Fix createQueries initialData type

* Fix createQuery types
  • Loading branch information
lachlancollins authored Jul 20, 2024
1 parent e4e65be commit b764009
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 67 deletions.
14 changes: 6 additions & 8 deletions packages/react-query/src/__tests__/queryOptions.test-d.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, expectTypeOf, it } from 'vitest'
import { describe, expectTypeOf, it } from 'vitest'
import {
QueriesObserver,
QueryClient,
Expand Down Expand Up @@ -71,14 +71,12 @@ describe('queryOptions', () => {
expectTypeOf(data).toEqualTypeOf<number | undefined>()
})
it('should tag the queryKey with the result type of the QueryFn', () => {
expect(() => {
const { queryKey } = queryOptions({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
})

expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf<number>()
const { queryKey } = queryOptions({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
})

expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf<number>()
})
it('should tag the queryKey even if no promise is returned', () => {
const { queryKey } = queryOptions({
Expand Down
59 changes: 39 additions & 20 deletions packages/svelte-query/src/createQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { Readable } from 'svelte/store'
import type { StoreOrVal } from './types'
import type {
DefaultError,
DefinedQueryObserverResult,
OmitKeyof,
QueriesObserverOptions,
QueriesPlaceholderDataFunction,
Expand All @@ -19,7 +20,7 @@ import type {
} from '@tanstack/query-core'

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

type GetOptions<T> =
type GetQueryObserverOptionsForCreateQueries<T> =
// Part 1: responsible for applying explicit type parameter to function arguments, if object { queryFnData: TQueryFnData, error: TError, data: TData }
T extends {
queryFnData: infer TQueryFnData
Expand Down Expand Up @@ -74,21 +75,38 @@ type GetOptions<T> =
: // Fallback
QueryObserverOptionsForCreateQueries

type GetResults<T> =
// A defined initialData setting should return a DefinedQueryObserverResult rather than CreateQueryResult
type GetDefinedOrUndefinedQueryResult<T, TData, TError = unknown> = T extends {
initialData?: infer TInitialData
}
? unknown extends TInitialData
? QueryObserverResult<TData, TError>
: TInitialData extends TData
? DefinedQueryObserverResult<TData, TError>
: TInitialData extends () => infer TInitialDataResult
? unknown extends TInitialDataResult
? QueryObserverResult<TData, TError>
: TInitialDataResult extends TData
? DefinedQueryObserverResult<TData, TError>
: QueryObserverResult<TData, TError>
: QueryObserverResult<TData, TError>
: QueryObserverResult<TData, TError>

type GetCreateQueryResult<T> =
// Part 1: responsible for mapping explicit type parameter to function result, if object
T extends { queryFnData: any; error?: infer TError; data: infer TData }
? QueryObserverResult<TData, TError>
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
: T extends { queryFnData: infer TQueryFnData; error?: infer TError }
? QueryObserverResult<TQueryFnData, TError>
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>
: T extends { data: infer TData; error?: infer TError }
? QueryObserverResult<TData, TError>
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
: // Part 2: responsible for mapping explicit type parameter to function result, if tuple
T extends [any, infer TError, infer TData]
? QueryObserverResult<TData, TError>
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
: T extends [infer TQueryFnData, infer TError]
? QueryObserverResult<TQueryFnData, TError>
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>
: T extends [infer TQueryFnData]
? QueryObserverResult<TQueryFnData>
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData>
: // Part 3: responsible for mapping inferred type to results, if no explicit parameter was provided
T extends {
queryFn?:
Expand All @@ -97,7 +115,8 @@ type GetResults<T> =
select?: (data: any) => infer TData
throwOnError?: ThrowOnError<any, infer TError, any, any>
}
? QueryObserverResult<
? GetDefinedOrUndefinedQueryResult<
T,
unknown extends TData ? TQueryFnData : TData,
unknown extends TError ? DefaultError : TError
>
Expand All @@ -109,18 +128,18 @@ type GetResults<T> =
*/
export type QueriesOptions<
T extends Array<any>,
TResult extends Array<any> = [],
TResults extends Array<any> = [],
TDepth extends ReadonlyArray<number> = [],
> = TDepth['length'] extends MAXIMUM_DEPTH
? Array<QueryObserverOptionsForCreateQueries>
: T extends []
? []
: T extends [infer Head]
? [...TResult, GetOptions<Head>]
: T extends [infer Head, ...infer Tail]
? [...TResults, GetQueryObserverOptionsForCreateQueries<Head>]
: T extends [infer Head, ...infer Tails]
? QueriesOptions<
[...Tail],
[...TResult, GetOptions<Head>],
[...Tails],
[...TResults, GetQueryObserverOptionsForCreateQueries<Head>],
[...TDepth, 1]
>
: ReadonlyArray<unknown> extends T
Expand Down Expand Up @@ -151,18 +170,18 @@ export type QueriesOptions<
*/
export type QueriesResults<
T extends Array<any>,
TResult extends Array<any> = [],
TResults extends Array<any> = [],
TDepth extends ReadonlyArray<number> = [],
> = TDepth['length'] extends MAXIMUM_DEPTH
? Array<QueryObserverResult>
: T extends []
? []
: T extends [infer Head]
? [...TResult, GetResults<Head>]
: T extends [infer Head, ...infer Tail]
? [...TResults, GetCreateQueryResult<Head>]
: T extends [infer Head, ...infer Tails]
? QueriesResults<
[...Tail],
[...TResult, GetResults<Head>],
[...Tails],
[...TResults, GetCreateQueryResult<Head>],
[...TDepth, 1]
>
: T extends Array<
Expand Down
15 changes: 10 additions & 5 deletions packages/svelte-query/src/createQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export function createQuery<
TQueryKey extends QueryKey = QueryKey,
>(
options: StoreOrVal<
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
>,
queryClient?: QueryClient,
): CreateQueryResult<TData, TError>
): DefinedCreateQueryResult<TData, TError>

export function createQuery<
TQueryFnData = unknown,
Expand All @@ -31,13 +31,13 @@ export function createQuery<
TQueryKey extends QueryKey = QueryKey,
>(
options: StoreOrVal<
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
>,
queryClient?: QueryClient,
): DefinedCreateQueryResult<TData, TError>
): CreateQueryResult<TData, TError>

export function createQuery<
TQueryFnData,
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
Expand All @@ -46,6 +46,11 @@ export function createQuery<
CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey>
>,
queryClient?: QueryClient,
): CreateQueryResult<TData, TError>

export function createQuery(
options: StoreOrVal<CreateQueryOptions>,
queryClient?: QueryClient,
) {
return createBaseQuery(options, QueryObserver, queryClient)
}
8 changes: 4 additions & 4 deletions packages/svelte-query/src/queryOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ export function queryOptions<
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
): UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
): DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
queryKey: DataTag<TQueryKey, TQueryFnData>
}

Expand All @@ -40,8 +40,8 @@ export function queryOptions<
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
): DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
options: UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
): UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
queryKey: DataTag<TQueryKey, TQueryFnData>
}

Expand Down
68 changes: 68 additions & 0 deletions packages/svelte-query/tests/createQueries/createQueries.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { describe, expectTypeOf, test } from 'vitest'
import { get } from 'svelte/store'
import { skipToken } from '@tanstack/query-core'
import { createQueries, queryOptions } from '../../src/index'
import type { OmitKeyof, QueryObserverResult } from '@tanstack/query-core'
import type { CreateQueryOptions } from '../../src/index'

describe('createQueries', () => {
test('TData should be defined when passed through queryOptions', () => {
const options = queryOptions({
queryKey: ['key'],
queryFn: () => {
return {
wow: true,
}
},
initialData: {
wow: true,
},
})
const queryResults = createQueries({ queries: [options] })

const data = get(queryResults)[0].data

expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>()
})

test('Allow custom hooks using UseQueryOptions', () => {
type Data = string

const useCustomQueries = (
options?: OmitKeyof<CreateQueryOptions<Data>, 'queryKey' | 'queryFn'>,
) => {
return createQueries({
queries: [
{
...options,
queryKey: ['todos-key'],
queryFn: () => Promise.resolve('data'),
},
],
})
}

const query = useCustomQueries()
const data = get(query)[0].data

expectTypeOf(data).toEqualTypeOf<Data | undefined>()
})

test('TData should have correct type when conditional skipToken is passed', () => {
const queryResults = createQueries({
queries: [
{
queryKey: ['withSkipToken'],
queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5),
},
],
})

const firstResult = get(queryResults)[0]

expectTypeOf(firstResult).toEqualTypeOf<
QueryObserverResult<number, Error>
>()
expectTypeOf(firstResult.data).toEqualTypeOf<number | undefined>()
})
})
65 changes: 65 additions & 0 deletions packages/svelte-query/tests/createQuery/createQuery.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, expectTypeOf, test } from 'vitest'
import { get } from 'svelte/store'
import { createQuery, queryOptions } from '../../src/index'
import type { OmitKeyof } from '@tanstack/query-core'
import type { CreateQueryOptions } from '../../src/index'

describe('createQuery', () => {
test('TData should always be defined when initialData is provided as an object', () => {
const query = createQuery({
queryKey: ['key'],
queryFn: () => ({ wow: true }),
initialData: { wow: true },
})

expectTypeOf(get(query).data).toEqualTypeOf<{ wow: boolean }>()
})

test('TData should be defined when passed through queryOptions', () => {
const options = queryOptions({
queryKey: ['key'],
queryFn: () => {
return {
wow: true,
}
},
initialData: {
wow: true,
},
})
const query = createQuery(options)

expectTypeOf(get(query).data).toEqualTypeOf<{ wow: boolean }>()
})

test('TData should have undefined in the union when initialData is NOT provided', () => {
const query = createQuery({
queryKey: ['key'],
queryFn: () => {
return {
wow: true,
}
},
})

expectTypeOf(get(query).data).toEqualTypeOf<{ wow: boolean } | undefined>()
})

test('Allow custom hooks using CreateQueryOptions', () => {
type Data = string

const useCustomQuery = (
options?: OmitKeyof<CreateQueryOptions<Data>, 'queryKey' | 'queryFn'>,
) => {
return createQuery({
...options,
queryKey: ['todos-key'],
queryFn: () => Promise.resolve('data'),
})
}

const query = useCustomQuery()

expectTypeOf(get(query).data).toEqualTypeOf<Data | undefined>()
})
})
Loading

0 comments on commit b764009

Please sign in to comment.