You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PR #10510 (5.100.5) correctly fixed persister inference for TQueryFnData when queryFn takes an explicit parameter. However, removing the NoInfer wrappers on persister has a side effect: for codebases that use the documented Register.queryKey augmentation and define a wrapper whose return re-brands the queryKey with DataTag, the branded return becomes un-assignable in contravariant slots (mockReturnValue, ReturnType<> containers, etc.) — even though no call site changed.
A narrowly-scoped structural tweak (NoInfer<TQueryKey> on persister only) would preserve #10510's benefit while restoring behaviour for augmented-Register users.
Minimal repro
Requires:
Register.queryKey augmented with a narrowed constraint.
A wrapper around queryOptions whose return re-brands queryKey with DataTag (to keep queryClient.getQueryData / setQueryData inferable on that key).
A literal assigned into the wrapper's return type.
// 1. The documented Register augmentation.import'@tanstack/react-query';declare module '@tanstack/react-query'{interfaceRegister{queryKey: readonly['todos'|'users'|'settings', ...unknown[]];}}
// 2. A wrapper that brands the returned queryKey with DataTag.import{typeDataTag,typeQueryKey,queryOptions,typeUndefinedInitialDataOptions,typeUseQueryOptions,}from'@tanstack/react-query';functionappQueryOptions<TData,constTPrefixextendsQueryKey=QueryKey,TKeyextendsQueryKey=[...TPrefix,string],>({name: _n,prefix: _p,
...options}: Omit<UseQueryOptions<TData,Error,TData,TKey>,'queryFn'|'queryKey'>&{prefix: TPrefix;name: string;}): UndefinedInitialDataOptions<TData,Error,TData,TKey>&{queryKey: DataTag<TKey,TData,Error>;}{returnqueryOptions({queryKey: []asunknownasTKey,queryFn: async()=>({})asTData,
...options,})asnever;}
// 3. A literal assigned into the wrapper's return type.// 5.100.1: compiles. 5.100.5: TS2322 on queryKey.const_a: ReturnType<typeofappQueryOptions<unknown[]>>={queryKey: ['todos','list'],queryFn: async()=>[],};
Error on 5.100.5:
Type '["todos", "list"]' is not assignable to type
'readonly ["todos" | "users" | "settings", ...unknown[]]
& { [dataTagSymbol]: unknown[]; [dataTagErrorSymbol]: Error; }'.
Why it changed
Under the old signature, NoInfer<TQueryKey> in persister kept that slot out of TQueryKey inference, so TQueryKey was determined solely from the queryKey field (or the wrapper's own generics). The returned DataTag<TQueryKey, ...> brand then sat on the caller's literal type, which was trivially assignable back to itself.
After #10510, when the caller supplies no persister, TS still considers the persister slot during TQueryKey inference. With Register.queryKey augmented to a non-default constraint, the default TQueryKey extends QueryKey = QueryKey resolves to the augmented type, and the inferred TQueryKey widens to the constraint rather than the literal. The returned DataTag is then branded on the wider type, and a plain literal tuple can no longer satisfy both the augmented constraint and the phantom brand.
This is a side effect of the fix interacting with Register-augmented keys, not the fix itself being wrong.
Consumer-side workaround
For anyone wrapping queryOptions and returning UndefinedInitialDataOptions<..., TKey> & { queryKey: DataTag<TKey, ...> }: Omitpersister from the wrapper's return type. The persister slot is the contravariant position dragging TQueryKey inference around, and most wrappers don't plumb it through anyway:
This preserves DataTag inference for getQueryData / setQueryData without touching any call sites.
Suggested upstream action
Any of:
Structural tweak so persister's generics decouple: persister?: QueryPersister<TQueryFnData, NoInfer<TQueryKey>, TPageParam>. TQueryFnData still participates (as fix(query-core): stop wrapping persister generics in NoInfer #10510 needs), TQueryKey does not. Worth running against queryOptions.test-d.tsx before committing.
Changelog note in 5.100.5 calling out that consumers using Register.queryKey with a non-default constraint may see new errors at DataTag-branded wrapper return sites, with a pointer to the workaround above.
Documentation on the Register.queryKey augmentation page noting the interaction with DataTag-tagged returns.
Summary
PR #10510 (5.100.5) correctly fixed
persisterinference forTQueryFnDatawhenqueryFntakes an explicit parameter. However, removing theNoInferwrappers onpersisterhas a side effect: for codebases that use the documentedRegister.queryKeyaugmentation and define a wrapper whose return re-brands thequeryKeywithDataTag, the branded return becomes un-assignable in contravariant slots (mockReturnValue,ReturnType<>containers, etc.) — even though no call site changed.A narrowly-scoped structural tweak (
NoInfer<TQueryKey>onpersisteronly) would preserve #10510's benefit while restoring behaviour for augmented-Registerusers.Minimal repro
Requires:
Register.queryKeyaugmented with a narrowed constraint.queryOptionswhose return re-brandsqueryKeywithDataTag(to keepqueryClient.getQueryData/setQueryDatainferable on that key).Error on 5.100.5:
Why it changed
Under the old signature,
NoInfer<TQueryKey>inpersisterkept that slot out ofTQueryKeyinference, soTQueryKeywas determined solely from thequeryKeyfield (or the wrapper's own generics). The returnedDataTag<TQueryKey, ...>brand then sat on the caller's literal type, which was trivially assignable back to itself.After #10510, when the caller supplies no
persister, TS still considers thepersisterslot duringTQueryKeyinference. WithRegister.queryKeyaugmented to a non-default constraint, the defaultTQueryKey extends QueryKey = QueryKeyresolves to the augmented type, and the inferredTQueryKeywidens to the constraint rather than the literal. The returnedDataTagis then branded on the wider type, and a plain literal tuple can no longer satisfy both the augmented constraint and the phantom brand.This is a side effect of the fix interacting with
Register-augmented keys, not the fix itself being wrong.Consumer-side workaround
For anyone wrapping
queryOptionsand returningUndefinedInitialDataOptions<..., TKey> & { queryKey: DataTag<TKey, ...> }:Omitpersisterfrom the wrapper's return type. Thepersisterslot is the contravariant position draggingTQueryKeyinference around, and most wrappers don't plumb it through anyway:This preserves
DataTaginference forgetQueryData/setQueryDatawithout touching any call sites.Suggested upstream action
Any of:
persister's generics decouple:persister?: QueryPersister<TQueryFnData, NoInfer<TQueryKey>, TPageParam>.TQueryFnDatastill participates (as fix(query-core): stop wrapping persister generics in NoInfer #10510 needs),TQueryKeydoes not. Worth running againstqueryOptions.test-d.tsxbefore committing.Register.queryKeywith a non-default constraint may see new errors atDataTag-branded wrapper return sites, with a pointer to the workaround above.Register.queryKeyaugmentation page noting the interaction withDataTag-tagged returns.Versions
@tanstack/react-query5.100.5@tanstack/query-core5.100.5I've opened #10601 with a suggested fix.