diff --git a/packages/solid-query/src/__tests__/createQuery.test-d.tsx b/packages/solid-query/src/__tests__/createQuery.test-d.tsx index 5301d0c379..91717c34d7 100644 --- a/packages/solid-query/src/__tests__/createQuery.test-d.tsx +++ b/packages/solid-query/src/__tests__/createQuery.test-d.tsx @@ -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 }>() }) diff --git a/packages/solid-query/src/__tests__/queryOptions.test-d.tsx b/packages/solid-query/src/__tests__/queryOptions.test-d.tsx new file mode 100644 index 0000000000..080b23e5c3 --- /dev/null +++ b/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() + }, + }) + }) + it('should work when passed to createQuery', () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const { data } = createQuery(() => options) + expectTypeOf(data).toEqualTypeOf() + }) + 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() + }) + 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() + }) + }) + it('should tag the queryKey even if no promise is returned', () => { + const { queryKey } = queryOptions({ + queryKey: ['key'], + queryFn: () => 5, + }) + + expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf() + }) + it('should tag the queryKey with unknown if there is no queryFn', () => { + const { queryKey } = queryOptions({ + queryKey: ['key'], + }) + + expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf() + }) + 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() + }) + 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() + }) + 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() + }) + 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() + return prev + }) + expectTypeOf(data).toEqualTypeOf() + }) + 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() + }) + + 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() + }) + + 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() + }) +}) diff --git a/packages/solid-query/src/createQuery.ts b/packages/solid-query/src/createQuery.ts index 6d9f6261e9..098ccf0a7e 100644 --- a/packages/solid-query/src/createQuery.ts +++ b/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 { @@ -39,26 +39,40 @@ export function queryOptions< TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, - TOptions extends UndefinedInitialDataOptions< - TQueryFnData, - TError, - TData, - TQueryKey - > = UndefinedInitialDataOptions, ->(options: TOptions): TOptions + TOptions extends ReturnType< + UndefinedInitialDataOptions + > = ReturnType< + UndefinedInitialDataOptions + >, +>( + options: ReturnType< + UndefinedInitialDataOptions + >, +): ReturnType< + UndefinedInitialDataOptions +> & { + queryKey: DataTag +} export function queryOptions< TQueryFnData = unknown, TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, - TOptions extends DefinedInitialDataOptions< - TQueryFnData, - TError, - TData, - TQueryKey - > = DefinedInitialDataOptions, ->(options: TOptions): TOptions + TOptions extends ReturnType< + DefinedInitialDataOptions + > = ReturnType< + DefinedInitialDataOptions + >, +>( + options: ReturnType< + DefinedInitialDataOptions + >, +): ReturnType< + DefinedInitialDataOptions +> & { + queryKey: DataTag +} export function queryOptions(options: unknown) { return options