@@ -198,12 +198,73 @@ export declare namespace useQuery {
198198 skip : false ;
199199 }
200200
201+ /**
202+ * The constraint for `TOptions` in the `Modern` signatures.
203+ *
204+ * `variables` is deliberately only `unknown` here - see the `Modern`
205+ * signatures for why the real check cannot live in the constraint. It still
206+ * has to be listed: `Base.Options` is a weak type (every property is
207+ * optional), so `{ variables: ... }` on its own would share no property with
208+ * it and be rejected.
209+ *
210+ * The mapped type reports options that are not part of `useQuery.Options`.
211+ * `TOptions` cannot reject them on its own because it is inferred from the
212+ * very object literal being checked, so every key the user writes becomes part
213+ * of it. Two details matter here:
214+ *
215+ * - The properties are required rather than optional. An optional `never`
216+ * would give a misspelled option a property in common with this weak type,
217+ * which is enough to satisfy the weak type check and let it through.
218+ * - `SkipToken` is excluded from `TOptions` first. A conditionally skipped
219+ * query infers `TOptions` as a union of `SkipToken` and the options object,
220+ * and `keyof` of that union is `never`, which would silently make this a
221+ * no-op.
222+ */
223+ export type ConstraintFor <
224+ TOptions ,
225+ TData ,
226+ TVariables extends OperationVariables ,
227+ > = Base . Options < TData , NoInfer < TVariables > > & {
228+ variables ?: unknown ;
229+ } & ( [
230+ Exclude <
231+ keyof Exclude < TOptions , SkipToken > ,
232+ keyof Options < TData , TVariables >
233+ > ,
234+ ] extends [ never ] ?
235+ unknown
236+ : never ) ;
237+
238+ /**
239+ * The `variables` option as it is required at the parameter position of the
240+ * `Modern` signatures.
241+ *
242+ * `NoInfer` keeps this from becoming an inference site for `TVariables`. The
243+ * mapped type reports variables that are not part of `TVariables`, for the
244+ * same reason `ConstraintFor` has to check the option names.
245+ */
246+ export type OptionsFor <
247+ TOptions ,
248+ TData ,
249+ TVariables extends OperationVariables ,
250+ > = Options < TData , NoInfer < TVariables > > & {
251+ variables ?: {
252+ // variables that are not part of `TVariables`
253+ [ K in Exclude <
254+ keyof TOptions [ "variables" & keyof TOptions ] ,
255+ keyof TVariables
256+ > ] ?: never ;
257+ } ;
258+ } ;
259+
201260 export type ResultForOptions <
202261 TData ,
203262 TVariables extends OperationVariables ,
204263 TOptions extends
205264 | Record < string , never > // no options
206- | Options < TData , TVariables >
265+ // `Base.Options` instead of `Options` because the `variables` option is
266+ // not part of `TOptions` - see the `Modern` signatures for details.
267+ | Base . Options < TData , TVariables >
207268 | SkipToken ,
208269 > = LazyType <
209270 Result <
@@ -519,6 +580,28 @@ export declare namespace useQuery {
519580 }
520581
521582 /** {@inheritDoc @apollo/client/react!useQuery.DocumentationTypes.useQuery:call(1) } */
583+ // The exact `variables` check lives at the parameter position
584+ // (`ConstraintFor` only says `variables?: unknown`), because a constraint
585+ // cannot do it.
586+ //
587+ // TypeScript widens fresh literals when inferring an object literal into a
588+ // naked type parameter, so `useQuery(query, { variables: { type: "main" } })`
589+ // infers the candidate `{ variables: { type: string } }`. A constraint that
590+ // required the exact `TVariables` (`{ type: "main" }`) would reject that
591+ // candidate, and TypeScript then silently substitutes the constraint for
592+ // `TOptions` - discarding every other inferred option along with it. That is
593+ // what made `returnPartialData: false` come back as `boolean` and put
594+ // `"partial"` into `dataState`.
595+ //
596+ // Widening also means the constraint could not do the check even if it did
597+ // not collapse: once `{ type: "nope" }` has widened to `{ type: string }`
598+ // there is nothing left to distinguish it from a valid value. Only the
599+ // parameter position still sees the literal.
600+ //
601+ // `NoInfer` on `TVariables` there is required. Without it,
602+ // `VariablesOption<TVariables>` becomes an inference site and the widened
603+ // `{ type: string }` candidate wins over the one from the document, which
604+ // stops invalid variable values from being reported at all.
522605 export interface Modern {
523606 /** {@inheritDoc @apollo/client/react!useQuery.DocumentationTypes.useQuery:call(1) } */
524607 <
@@ -538,24 +621,20 @@ export declare namespace useQuery {
538621 TData ,
539622 TVariables extends OperationVariables ,
540623 // 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- > ,
624+ TOptions extends useQuery . ConstraintFor < TOptions , TData , TVariables > ,
550625 > (
551626 query : DocumentNode | TypedDocumentNode < TData , TVariables > ,
552627 ...[ options ] : // we generally do not allow for a `TVariables` of `never`
553628 // TODO: check if we need a similar check in other hooks
554629 [ TVariables ] extends [ never ] ? [ options : never ]
555630 : // variables optional
556- { } extends TVariables ? [ options ?: TOptions ]
631+ { } extends TVariables ?
632+ [
633+ options ?: TOptions &
634+ useQuery . OptionsFor < TOptions , TData , TVariables > ,
635+ ]
557636 : // variables required
558- [ options : TOptions ]
637+ [ options : TOptions & useQuery . OptionsFor < TOptions , TData , TVariables > ]
559638 ) : useQuery . ResultForOptions < TData , TVariables , TOptions > ;
560639
561640 /** {@inheritDoc @apollo/client/react!useQuery.DocumentationTypes.useQuery:call(1) } */
@@ -574,24 +653,25 @@ export declare namespace useQuery {
574653 TData ,
575654 TVariables extends OperationVariables ,
576655 // 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- > ,
656+ TOptions extends useQuery . ConstraintFor < TOptions , TData , TVariables > ,
586657 > (
587658 query : DocumentNode | TypedDocumentNode < TData , TVariables > ,
588659 ...[ options ] : // we generally do not allow for a `TVariables` of `never`
589660 // TODO: check if we need a similar check in other hooks
590661 [ TVariables ] extends [ never ] ? [ options : never ]
591662 : // variables optional
592- { } extends TVariables ? [ options ?: TOptions | SkipToken ]
663+ { } extends TVariables ?
664+ [
665+ options ?:
666+ | ( TOptions & useQuery . OptionsFor < TOptions , TData , TVariables > )
667+ | SkipToken ,
668+ ]
593669 : // variables required
594- [ options : TOptions | SkipToken ]
670+ [
671+ options :
672+ | ( TOptions & useQuery . OptionsFor < TOptions , TData , TVariables > )
673+ | SkipToken ,
674+ ]
595675 ) : useQuery . ResultForOptions < TData , TVariables , TOptions | SkipToken > ;
596676
597677 ssrDisabledResult : ObservableQuery . Result < any > ;
0 commit comments