Skip to content

Commit

Permalink
feat(solid-query): Revise queryOptions implementation (#7366)
Browse files Browse the repository at this point in the history
  • Loading branch information
ardeora committed May 2, 2024
1 parent 9ff2c2e commit bd8ce7d
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 18 deletions.
6 changes: 3 additions & 3 deletions packages/solid-query/src/__tests__/createQuery.test-d.tsx
Expand Up @@ -14,12 +14,12 @@ describe('initialData', () => {
})

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

expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>()
})
Expand Down
147 changes: 147 additions & 0 deletions packages/solid-query/src/__tests__/queryOptions.test-d.tsx
@@ -0,0 +1,147 @@
import { describe, expect, expectTypeOf, it } from 'vitest'
import { QueryClient, dataTagSymbol, skipToken } from '@tanstack/query-core'
import { createQuery, queryOptions } from '../createQuery'

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 createQuery', () => {
const options = queryOptions({
queryKey: ['key'],
queryFn: () => Promise.resolve(5),
})

const { data } = createQuery(() => options)
expectTypeOf(data).toEqualTypeOf<number | undefined>()
})
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', () => {
expect(() => {
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>()
})
})
44 changes: 29 additions & 15 deletions packages/solid-query/src/createQuery.ts
@@ -1,7 +1,7 @@
import { QueryObserver } from '@tanstack/query-core'
import { createMemo } from 'solid-js'
import { createBaseQuery } from './createBaseQuery'
import type { DefaultError, QueryKey } from '@tanstack/query-core'
import type { DataTag, DefaultError, QueryKey } from '@tanstack/query-core'
import type { QueryClient } from './QueryClient'
import type { Accessor } from 'solid-js'
import type {
Expand Down Expand Up @@ -39,26 +39,40 @@ export function queryOptions<
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
TOptions extends UndefinedInitialDataOptions<
TQueryFnData,
TError,
TData,
TQueryKey
> = UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
>(options: TOptions): TOptions
TOptions extends ReturnType<
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
> = ReturnType<
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
>,
>(
options: ReturnType<
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
>,
): ReturnType<
UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
> & {
queryKey: DataTag<TQueryKey, TQueryFnData>
}

export function queryOptions<
TQueryFnData = unknown,
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
TOptions extends DefinedInitialDataOptions<
TQueryFnData,
TError,
TData,
TQueryKey
> = DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
>(options: TOptions): TOptions
TOptions extends ReturnType<
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
> = ReturnType<
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
>,
>(
options: ReturnType<
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
>,
): ReturnType<
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
> & {
queryKey: DataTag<TQueryKey, TQueryFnData>
}

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

0 comments on commit bd8ce7d

Please sign in to comment.