Skip to content

Commit

Permalink
fix(svelte-query): infer query data type in queryOptions (#7537)
Browse files Browse the repository at this point in the history
* fix(svelte-query): infer query data type in queryOptions

* test(svelte-query): add tests for queryOptions type inference
  • Loading branch information
sossost committed Jun 8, 2024
1 parent 281f386 commit b8cdb89
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 3 deletions.
146 changes: 146 additions & 0 deletions packages/svelte-query/src/__tests__/queryOptions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { QueryClient, dataTagSymbol, skipToken } from '@tanstack/query-core'
import { describe, expectTypeOf, it } from 'vitest'
import { queryOptions } from '../queryOptions'

describe('queryOptions', () => {
it('should not allow excess properties', () => {
queryOptions({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
// @ts-expect-error this is a good error, because stallTime does not exist!
stallTime: 1000,
})
})

it('should infer types for callbacks', () => {
queryOptions({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
staleTime: 1000,
select: (data) => {
expectTypeOf(data).toEqualTypeOf<number>()
},
})
})

it('should work when passed to fetchQuery', async () => {
const options = queryOptions({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
})

const data = await new QueryClient().fetchQuery(options)
expectTypeOf(data).toEqualTypeOf<number>()
})

it('should tag the queryKey with the result type of the QueryFn', () => {
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({
queryKey: ['key'],
queryFn: () => 5,
})

expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf<number>()
})

it('should tag the queryKey with unknown if there is no queryFn', () => {
const { queryKey } = queryOptions({
queryKey: ['key'],
})

expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf<unknown>()
})

it('should tag the queryKey with the result type of the QueryFn if select is used', () => {
const { queryKey } = queryOptions({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
select: (data) => data.toString(),
})

expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf<number>()
})

it('should return the proper type when passed to getQueryData', () => {
const { queryKey } = queryOptions({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
})

const queryClient = new QueryClient()
const data = queryClient.getQueryData(queryKey)
expectTypeOf(data).toEqualTypeOf<number | undefined>()
})

it('should return the proper type when passed to getQueryState', () => {
const { queryKey } = queryOptions({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
})

const queryClient = new QueryClient()
const state = queryClient.getQueryState(queryKey)
expectTypeOf(state?.data).toEqualTypeOf<number | undefined>()
})

it('should properly type updaterFn when passed to setQueryData', () => {
const { queryKey } = queryOptions({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
})

const queryClient = new QueryClient()
const data = queryClient.setQueryData(queryKey, (prev) => {
expectTypeOf(prev).toEqualTypeOf<number | undefined>()
return prev
})
expectTypeOf(data).toEqualTypeOf<number | undefined>()
})

it('should properly type value when passed to setQueryData', () => {
const { queryKey } = queryOptions({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
})

const queryClient = new QueryClient()

// @ts-expect-error value should be a number
queryClient.setQueryData(queryKey, '5')
// @ts-expect-error value should be a number
queryClient.setQueryData(queryKey, () => '5')

const data = queryClient.setQueryData(queryKey, 5)
expectTypeOf(data).toEqualTypeOf<number | undefined>()
})

it('should infer even if there is a conditional skipToken', () => {
const options = queryOptions({
queryKey: ['key'],
queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5),
})

const queryClient = new QueryClient()
const data = queryClient.getQueryData(options.queryKey)
expectTypeOf(data).toEqualTypeOf<number | undefined>()
})

it('should infer to unknown if we disable a query with just a skipToken', () => {
const options = queryOptions({
queryKey: ['key'],
queryFn: skipToken,
})

const queryClient = new QueryClient()
const data = queryClient.getQueryData(options.queryKey)
expectTypeOf(data).toEqualTypeOf<unknown>()
})
})
10 changes: 7 additions & 3 deletions packages/svelte-query/src/queryOptions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DefaultError, QueryKey } from '@tanstack/query-core'
import type { DataTag, DefaultError, QueryKey } from '@tanstack/query-core'
import type { CreateQueryOptions } from './types'

export type UndefinedInitialDataOptions<
Expand Down Expand Up @@ -30,7 +30,9 @@ export function queryOptions<
TQueryKey extends QueryKey = QueryKey,
>(
options: UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
): UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
): UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
queryKey: DataTag<TQueryKey, TQueryFnData>
}

export function queryOptions<
TQueryFnData = unknown,
Expand All @@ -39,7 +41,9 @@ export function queryOptions<
TQueryKey extends QueryKey = QueryKey,
>(
options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
): DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
): DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
queryKey: DataTag<TQueryKey, TQueryFnData>
}

export function queryOptions(options: unknown) {
return options
Expand Down

0 comments on commit b8cdb89

Please sign in to comment.