From 562030d93925994c51fc9f9dabc4bb10f79a0c6f Mon Sep 17 00:00:00 2001 From: Ousama Ben Younes Date: Sat, 25 Apr 2026 10:51:36 +0000 Subject: [PATCH] fix(svelte-query): wrap TData in NoInfer on query return types (#7673) The original report (#7673) showed `select`'s `data` parameter typed as `any` when wrapping options in a Svelte 4 `derived` store. Svelte 5 removed that store-based API and the basic case now infers correctly, but `createQuery` / `createInfiniteQuery` still let TypeScript widen `TData` from the result-type annotation, unlike `react-query`, `preact-query`, and `vue-query` which all wrap `TData` in `NoInfer`. Aligning svelte-query with the rest of the ecosystem ensures `TData` comes from the input options only, locking in the inferred `select` result and preventing silent widening when the call site is annotated. Adds three type tests covering: select inference from queryFn return, select inference when spreading queryOptions, and the negative case where a wrong result-type annotation must not widen TData. Generated by Claude Code Vibe coded by ousamabenyounes Co-Authored-By: Claude --- .changeset/svelte-query-no-infer-tdata.md | 5 +++ .../svelte-query/src/createInfiniteQuery.ts | 3 +- packages/svelte-query/src/createQuery.ts | 13 +++++-- .../tests/createQuery/createQuery.test-d.ts | 39 +++++++++++++++++++ 4 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 .changeset/svelte-query-no-infer-tdata.md diff --git a/.changeset/svelte-query-no-infer-tdata.md b/.changeset/svelte-query-no-infer-tdata.md new file mode 100644 index 00000000000..338f8fed392 --- /dev/null +++ b/.changeset/svelte-query-no-infer-tdata.md @@ -0,0 +1,5 @@ +--- +'@tanstack/svelte-query': patch +--- + +fix(svelte-query): wrap `TData` in `NoInfer` on `createQuery` and `createInfiniteQuery` return types so `TData` is inferred from the input options only (matching `react-query`, `preact-query`, and `vue-query`). Prevents the result-type annotation from silently widening `TData` and improves `select` inference (#7673). diff --git a/packages/svelte-query/src/createInfiniteQuery.ts b/packages/svelte-query/src/createInfiniteQuery.ts index e8fe7948909..f909ccc3c74 100644 --- a/packages/svelte-query/src/createInfiniteQuery.ts +++ b/packages/svelte-query/src/createInfiniteQuery.ts @@ -3,6 +3,7 @@ import { createBaseQuery } from './createBaseQuery.svelte.js' import type { DefaultError, InfiniteData, + NoInfer, QueryClient, QueryKey, QueryObserver, @@ -30,7 +31,7 @@ export function createInfiniteQuery< > >, queryClient?: Accessor, -): CreateInfiniteQueryResult { +): CreateInfiniteQueryResult, TError> { return createBaseQuery( options, InfiniteQueryObserver as typeof QueryObserver, diff --git a/packages/svelte-query/src/createQuery.ts b/packages/svelte-query/src/createQuery.ts index bf7efe81a74..cfe2f7dc14b 100644 --- a/packages/svelte-query/src/createQuery.ts +++ b/packages/svelte-query/src/createQuery.ts @@ -1,6 +1,11 @@ import { QueryObserver } from '@tanstack/query-core' import { createBaseQuery } from './createBaseQuery.svelte.js' -import type { DefaultError, QueryClient, QueryKey } from '@tanstack/query-core' +import type { + DefaultError, + NoInfer, + QueryClient, + QueryKey, +} from '@tanstack/query-core' import type { Accessor, CreateQueryOptions, @@ -22,7 +27,7 @@ export function createQuery< UndefinedInitialDataOptions >, queryClient?: Accessor, -): CreateQueryResult +): CreateQueryResult, TError> export function createQuery< TQueryFnData = unknown, @@ -34,7 +39,7 @@ export function createQuery< DefinedInitialDataOptions >, queryClient?: Accessor, -): DefinedCreateQueryResult +): DefinedCreateQueryResult, TError> export function createQuery< TQueryFnData, @@ -44,7 +49,7 @@ export function createQuery< >( options: Accessor>, queryClient?: Accessor, -): CreateQueryResult +): CreateQueryResult, TError> export function createQuery( options: Accessor, diff --git a/packages/svelte-query/tests/createQuery/createQuery.test-d.ts b/packages/svelte-query/tests/createQuery/createQuery.test-d.ts index 8b6590b3347..1b7b1398be8 100644 --- a/packages/svelte-query/tests/createQuery/createQuery.test-d.ts +++ b/packages/svelte-query/tests/createQuery/createQuery.test-d.ts @@ -1,8 +1,47 @@ import { describe, expectTypeOf, it } from 'vitest' import { queryKey } from '@tanstack/query-test-utils' import { createQuery, queryOptions } from '../../src/index.js' +import type { CreateQueryResult } from '../../src/index.js' describe('createQuery', () => { + describe('select', () => { + it('should infer select data type from queryFn return (issue #7673)', () => { + const key = queryKey() + const { data } = createQuery(() => ({ + queryKey: key, + queryFn: () => Promise.resolve({ a: { b: { c: 'hello' } } }), + select: (input) => input.a.b.c, + })) + + expectTypeOf(data).toEqualTypeOf() + }) + + it('should infer select data type when options come from queryOptions', () => { + const key = queryKey() + const options = queryOptions({ + queryKey: key, + queryFn: () => Promise.resolve(1), + }) + const { data } = createQuery(() => ({ + ...options, + select: (input) => input > 1, + })) + + expectTypeOf(data).toEqualTypeOf() + }) + + // eslint-disable-next-line vitest/expect-expect + it('TData should depend on arguments only, not on the result type annotation', () => { + // @ts-expect-error TData inferred from queryFn ({ wow: boolean }), not from result + const result: CreateQueryResult<{ wow: string }> = createQuery(() => ({ + queryKey: queryKey(), + queryFn: () => ({ wow: true }), + })) + + void result + }) + }) + describe('initialData', () => { describe('Config object overload', () => { it('TData should always be defined when initialData is provided as an object', () => {