Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(svelte-query): Correct data type when initialData is set #7769

Merged
merged 2 commits into from
Jul 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading