@@ -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,69 @@ export declare namespace useQuery {
198199 skip : false ;
199200 }
200201
202+ /**
203+ * The options as they are checked at the parameter position of the `Modern`
204+ * signatures, intersected with the inferred `TOptions`.
205+ *
206+ * `NoInfer` keeps this from becoming an inference site for `TVariables`.
207+ *
208+ * The two mapped types report variables that are not part of `TVariables` and
209+ * options that are not part of `useQuery.Options`. `TOptions` cannot reject
210+ * either on its own, because it is inferred from the very object literal being
211+ * checked - every key the user writes becomes part of it. Two details matter
212+ * for the option names:
213+ *
214+ * - `SkipToken` is excluded from `TOptions` first. A conditionally skipped
215+ * query infers `TOptions` as a union of `SkipToken` and the options object,
216+ * and `keyof` of that union is `never`, which would silently make the check
217+ * a no-op.
218+ * - The conditional is what keeps the passing case at `unknown` instead of an
219+ * empty mapped type. An empty object type is not a weak type, and an
220+ * intersection is only weak if every member is, so intersecting one in would
221+ * turn the weak type check off for the whole parameter. That check is not
222+ * only about rejecting options objects without a single known option - it
223+ * also affects inference. Without it, an options argument of the type
224+ * `SkipToken | { returnPartialData: false }` infers a `TVariables` that has
225+ * lost the optionality of its properties.
226+ */
227+ export type OptionsFor <
228+ TData ,
229+ TVariables extends OperationVariables ,
230+ TOptions ,
231+ > = Options < TData , NoInfer < TVariables > > & {
232+ variables ?: Prettify <
233+ TVariables & {
234+ // variables that are not part of `TVariables`
235+ [ K in keyof TOptions [ "variables" & keyof TOptions ] ] : K extends (
236+ keyof TVariables
237+ ) ?
238+ TVariables [ K ]
239+ : never ;
240+ }
241+ > ;
242+ } & ( [
243+ Exclude <
244+ keyof Exclude < TOptions , SkipToken > ,
245+ keyof Options < TData , TVariables >
246+ > ,
247+ ] extends [ never ] ?
248+ unknown
249+ : {
250+ // options that are not part of `useQuery.Options`
251+ [ K in Exclude <
252+ keyof Exclude < TOptions , SkipToken > ,
253+ keyof Options < TData , TVariables >
254+ > ] : never ;
255+ } ) ;
256+
201257 export type ResultForOptions <
202258 TData ,
203259 TVariables extends OperationVariables ,
204260 TOptions extends
205261 | Record < string , never > // no options
206- | Options < TData , TVariables >
262+ // `Base.Options` instead of `Options` because the `variables` option is
263+ // not part of `TOptions` - see the `Modern` signatures for details.
264+ | Base . Options < TData , TVariables >
207265 | SkipToken ,
208266 > = LazyType <
209267 Result <
@@ -519,6 +577,28 @@ export declare namespace useQuery {
519577 }
520578
521579 /** {@inheritDoc @apollo/client/react!useQuery.DocumentationTypes.useQuery:call(1) } */
580+ // Every check on the options lives at the parameter position, in
581+ // `OptionsFor`. The constraint only describes the shape of an options
582+ // object, because a constraint cannot do the checking.
583+ //
584+ // TypeScript widens fresh literals when inferring an object literal into a
585+ // naked type parameter, so `useQuery(query, { variables: { type: "main" } })`
586+ // infers the candidate `{ variables: { type: string } }`. A constraint that
587+ // required the exact `TVariables` (`{ type: "main" }`) would reject that
588+ // candidate, and TypeScript then silently substitutes the constraint for
589+ // `TOptions` - discarding every other inferred option along with it. That is
590+ // what made `returnPartialData: false` come back as `boolean` and put
591+ // `"partial"` into `dataState`.
592+ //
593+ // Widening also means the constraint could not do the check even if it did
594+ // not collapse: once `{ type: "nope" }` has widened to `{ type: string }`
595+ // there is nothing left to distinguish it from a valid value. Only the
596+ // parameter position still sees the literal.
597+ //
598+ // `NoInfer` on `TVariables` there is required. Without it,
599+ // `VariablesOption<TVariables>` becomes an inference site and the widened
600+ // `{ type: string }` candidate wins over the one from the document, which
601+ // stops invalid variable values from being reported at all.
522602 export interface Modern {
523603 /** {@inheritDoc @apollo/client/react!useQuery.DocumentationTypes.useQuery:call(1) } */
524604 <
@@ -538,24 +618,22 @@ export declare namespace useQuery {
538618 TData ,
539619 TVariables extends OperationVariables ,
540620 // 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- > ,
621+ TOptions extends Base . Options < TData , NoInfer < TVariables > > & {
622+ variables ?: unknown ;
623+ } ,
550624 > (
551625 query : DocumentNode | TypedDocumentNode < TData , TVariables > ,
552626 ...[ options ] : // we generally do not allow for a `TVariables` of `never`
553627 // TODO: check if we need a similar check in other hooks
554628 [ TVariables ] extends [ never ] ? [ options : never ]
555629 : // variables optional
556- { } extends TVariables ? [ options ?: TOptions ]
630+ { } extends TVariables ?
631+ [
632+ options ?: TOptions &
633+ useQuery . OptionsFor < TData , TVariables , TOptions > ,
634+ ]
557635 : // variables required
558- [ options : TOptions ]
636+ [ options : TOptions & useQuery . OptionsFor < TData , TVariables , TOptions > ]
559637 ) : useQuery . ResultForOptions < TData , TVariables , TOptions > ;
560638
561639 /** {@inheritDoc @apollo/client/react!useQuery.DocumentationTypes.useQuery:call(1) } */
@@ -574,24 +652,27 @@ export declare namespace useQuery {
574652 TData ,
575653 TVariables extends OperationVariables ,
576654 // 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- > ,
655+ TOptions extends Base . Options < TData , NoInfer < TVariables > > & {
656+ variables ?: unknown ;
657+ } ,
586658 > (
587659 query : DocumentNode | TypedDocumentNode < TData , TVariables > ,
588660 ...[ options ] : // we generally do not allow for a `TVariables` of `never`
589661 // TODO: check if we need a similar check in other hooks
590662 [ TVariables ] extends [ never ] ? [ options : never ]
591663 : // variables optional
592- { } extends TVariables ? [ options ?: TOptions | SkipToken ]
664+ { } extends TVariables ?
665+ [
666+ options ?:
667+ | ( TOptions & useQuery . OptionsFor < TData , TVariables , TOptions > )
668+ | SkipToken ,
669+ ]
593670 : // variables required
594- [ options : TOptions | SkipToken ]
671+ [
672+ options :
673+ | ( TOptions & useQuery . OptionsFor < TData , TVariables , TOptions > )
674+ | SkipToken ,
675+ ]
595676 ) : useQuery . ResultForOptions < TData , TVariables , TOptions | SkipToken > ;
596677
597678 ssrDisabledResult : ObservableQuery . Result < any > ;
0 commit comments