@@ -40,6 +40,7 @@ import type {
4040 LazyType ,
4141 NoInfer ,
4242 OptionWithFallback ,
43+ Prettify ,
4344 SignatureStyle ,
4445 VariablesOption ,
4546} from "@apollo/client/utilities/internal" ;
@@ -198,12 +199,76 @@ export declare namespace useQuery {
198199 skip : false ;
199200 }
200201
202+ /**
203+ * The constraint for `TOptions` in the `Modern` signatures.
204+ *
205+ * `variables` is deliberately only `unknown` here - see the `Modern`
206+ * signatures for why the real check cannot live in the constraint. It still
207+ * has to be listed: `Base.Options` is a weak type (every property is
208+ * optional), so `{ variables: ... }` on its own would share no property with
209+ * it and be rejected.
210+ *
211+ * The mapped type reports options that are not part of `useQuery.Options`.
212+ * `TOptions` cannot reject them on its own because it is inferred from the
213+ * very object literal being checked, so every key the user writes becomes part
214+ * of it. Two details matter here:
215+ *
216+ * - The properties are required rather than optional. An optional `never`
217+ * would give a misspelled option a property in common with this weak type,
218+ * which is enough to satisfy the weak type check and let it through.
219+ * - `SkipToken` is excluded from `TOptions` first. A conditionally skipped
220+ * query infers `TOptions` as a union of `SkipToken` and the options object,
221+ * and `keyof` of that union is `never`, which would silently make this a
222+ * no-op.
223+ */
224+ export type ConstraintFor <
225+ TOptions ,
226+ TData ,
227+ TVariables extends OperationVariables ,
228+ > = Base . Options < TData , NoInfer < TVariables > > & {
229+ variables ?: unknown ;
230+ } & ( [
231+ Exclude <
232+ keyof Exclude < TOptions , SkipToken > ,
233+ keyof Options < TData , TVariables >
234+ > ,
235+ ] extends [ never ] ?
236+ unknown
237+ : never ) ;
238+
239+ /**
240+ * The `variables` option as it is required at the parameter position of the
241+ * `Modern` signatures.
242+ *
243+ * `NoInfer` keeps this from becoming an inference site for `TVariables`. The
244+ * mapped type reports variables that are not part of `TVariables`, for the
245+ * same reason `ConstraintFor` has to check the option names.
246+ */
247+ export type OptionsFor <
248+ TOptions ,
249+ TData ,
250+ TVariables extends OperationVariables ,
251+ > = Options < TData , NoInfer < TVariables > > & {
252+ variables ?: Prettify <
253+ TVariables & {
254+ // variables that are not part of `TVariables`
255+ [ K in keyof TOptions [ "variables" & keyof TOptions ] ] : K extends (
256+ keyof TVariables
257+ ) ?
258+ TVariables [ K ]
259+ : never ;
260+ }
261+ > ;
262+ } ;
263+
201264 export type ResultForOptions <
202265 TData ,
203266 TVariables extends OperationVariables ,
204267 TOptions extends
205268 | Record < string , never > // no options
206- | Options < TData , TVariables >
269+ // `Base.Options` instead of `Options` because the `variables` option is
270+ // not part of `TOptions` - see the `Modern` signatures for details.
271+ | Base . Options < TData , TVariables >
207272 | SkipToken ,
208273 > = LazyType <
209274 Result <
@@ -519,6 +584,28 @@ export declare namespace useQuery {
519584 }
520585
521586 /** {@inheritDoc @apollo/client/react!useQuery.DocumentationTypes.useQuery:call(1) } */
587+ // The exact `variables` check lives at the parameter position
588+ // (`ConstraintFor` only says `variables?: unknown`), because a constraint
589+ // cannot do it.
590+ //
591+ // TypeScript widens fresh literals when inferring an object literal into a
592+ // naked type parameter, so `useQuery(query, { variables: { type: "main" } })`
593+ // infers the candidate `{ variables: { type: string } }`. A constraint that
594+ // required the exact `TVariables` (`{ type: "main" }`) would reject that
595+ // candidate, and TypeScript then silently substitutes the constraint for
596+ // `TOptions` - discarding every other inferred option along with it. That is
597+ // what made `returnPartialData: false` come back as `boolean` and put
598+ // `"partial"` into `dataState`.
599+ //
600+ // Widening also means the constraint could not do the check even if it did
601+ // not collapse: once `{ type: "nope" }` has widened to `{ type: string }`
602+ // there is nothing left to distinguish it from a valid value. Only the
603+ // parameter position still sees the literal.
604+ //
605+ // `NoInfer` on `TVariables` there is required. Without it,
606+ // `VariablesOption<TVariables>` becomes an inference site and the widened
607+ // `{ type: string }` candidate wins over the one from the document, which
608+ // stops invalid variable values from being reported at all.
522609 export interface Modern {
523610 /** {@inheritDoc @apollo/client/react!useQuery.DocumentationTypes.useQuery:call(1) } */
524611 <
@@ -538,24 +625,20 @@ export declare namespace useQuery {
538625 TData ,
539626 TVariables extends OperationVariables ,
540627 // this overload should never be manually defined, it should always be inferred
541- TOptions extends useQuery . Options < TData , NoInfer < TVariables > > &
542- VariablesOption <
543- TVariables & {
544- [ K in Exclude <
545- keyof TOptions [ "variables" ] ,
546- keyof TVariables
547- > ] ?: never ;
548- }
549- > ,
628+ TOptions extends useQuery . ConstraintFor < TOptions , TData , TVariables > ,
550629 > (
551630 query : DocumentNode | TypedDocumentNode < TData , TVariables > ,
552631 ...[ options ] : // we generally do not allow for a `TVariables` of `never`
553632 // TODO: check if we need a similar check in other hooks
554633 [ TVariables ] extends [ never ] ? [ options : never ]
555634 : // variables optional
556- { } extends TVariables ? [ options ?: TOptions ]
635+ { } extends TVariables ?
636+ [
637+ options ?: TOptions &
638+ useQuery . OptionsFor < TOptions , TData , TVariables > ,
639+ ]
557640 : // variables required
558- [ options : TOptions ]
641+ [ options : TOptions & useQuery . OptionsFor < TOptions , TData , TVariables > ]
559642 ) : useQuery . ResultForOptions < TData , TVariables , TOptions > ;
560643
561644 /** {@inheritDoc @apollo/client/react!useQuery.DocumentationTypes.useQuery:call(1) } */
@@ -574,24 +657,25 @@ export declare namespace useQuery {
574657 TData ,
575658 TVariables extends OperationVariables ,
576659 // this overload should never be manually defined, it should always be inferred
577- TOptions extends useQuery . Options < TData , NoInfer < TVariables > > &
578- VariablesOption <
579- TVariables & {
580- [ K in Exclude <
581- keyof TOptions [ "variables" ] ,
582- keyof TVariables
583- > ] ?: never ;
584- }
585- > ,
660+ TOptions extends useQuery . ConstraintFor < TOptions , TData , TVariables > ,
586661 > (
587662 query : DocumentNode | TypedDocumentNode < TData , TVariables > ,
588663 ...[ options ] : // we generally do not allow for a `TVariables` of `never`
589664 // TODO: check if we need a similar check in other hooks
590665 [ TVariables ] extends [ never ] ? [ options : never ]
591666 : // variables optional
592- { } extends TVariables ? [ options ?: TOptions | SkipToken ]
667+ { } extends TVariables ?
668+ [
669+ options ?:
670+ | ( TOptions & useQuery . OptionsFor < TOptions , TData , TVariables > )
671+ | SkipToken ,
672+ ]
593673 : // variables required
594- [ options : TOptions | SkipToken ]
674+ [
675+ options :
676+ | ( TOptions & useQuery . OptionsFor < TOptions , TData , TVariables > )
677+ | SkipToken ,
678+ ]
595679 ) : useQuery . ResultForOptions < TData , TVariables , TOptions | SkipToken > ;
596680
597681 ssrDisabledResult : ObservableQuery . Result < any > ;
0 commit comments