urql icon indicating copy to clipboard operation
urql copied to clipboard

QueryArgs `variables` type seems to be broken with TypedDocumentNode

Open jaschaephraim opened this issue 2 years ago • 1 comments

Describe the bug

There is a Typescript error thrown by the variables argument in the following minimal example:

function example<Result, Variables>(
  query: TypedDocumentNode<Result, Variables>,
  variables: Variables,
) {
  return useQuery({ query, variables });
}

The reproduction provided shows the same error with React, Vue, and Svelte implementations (press "Run" in the REPL to see the tsc output).

I would submit a PR but I'm not sure of the use cases you're trying to cover with the type definition. Apologies if this is a misunderstanding on my end!

Reproduction

https://replit.com/@jaschaephraim/Urql-Variables-Example#index.ts

Urql version

urql v3.0.2 @urql/vue v1.0.2 @urql/svelte v3.0.1

Validations

  • [X] I can confirm that this is a bug report, and not a feature request, RFC, question, or discussion, for which GitHub Discussions should be used
  • [X] Read the docs.
  • [X] Follow our Code of Conduct

jaschaephraim avatar Sep 04 '22 00:09 jaschaephraim

The linked reproduction in question does not compile as per the stated error message because the generics you're passing are not compatible anymore in the new major.

The exact generic is shown here for instance: https://github.com/FormidableLabs/urql/blob/0582f9b138ddb66c5164b00a978aac90e865c0dc/packages/core/src/client.ts#L101-L105

This means that your generic has to be bound now by AnyVariables, e.g. <Variables extends AnyVariables>. Afterwards, the generic will match but some bindings will then have problems interpreting the type to be matching. That's because depending on what kind of generic is actually passed the argument becomes "empty" or not. In other words, because some queries allow for optional or no variables sometimes undefined or {} is allowed and sometimes not, but you can basically at that point just cast your options, e.g.:

export function react<Result, Variables extends AnyVariables>(
  query: TypedDocumentNode<Result, Variables>,
  variables: Variables,
) {
  return useQueryReact(
    { query, variables } as UseQueryArgs<Result, Variables>
  );
}

kitten avatar Sep 21 '22 13:09 kitten

I think the reason that worked is because the REPL was using an old version of TypeScript (v4.4.4). Starting with the following version the casting no longer works. Here's an updated example using the current version of TypeScript (v4.8.4):

https://replit.com/@jaschaephraim/Urql-Variables-Example-Latest-TS#index.ts

jaschaephraim avatar Sep 30 '22 22:09 jaschaephraim

Hiya :wave: I'm planning to still look into this before we release the next set of patches. I'm sure there should be a way around this or an escape hatch we can make more obvious around these new typings.

kitten avatar Oct 13 '22 01:10 kitten

I believe I have the same issue.

I have a function that wraps queryStore from @urql/svelte. I cannot find a generic type that will satisfy the new definition of variables in QueryArgs. It works just fine when passing a concrete type, but when I attempt to accept AnyVariables in my function signature, just like queryStore does, I get an error:

Type '{ query: string | DocumentNode | TypedDocumentNode<Data, Variables>; client: Client; requestPolicy: RequestPolicy | undefined; }' is not assignable to type 'QueryArgs<Data, Variables>'. Type '{ query: string | DocumentNode | TypedDocumentNode<Data, Variables>; client: Client; requestPolicy: RequestPolicy | undefined; }' is not assignable to type 'Variables extends void ? { variables?: Variables | undefined; } : Variables extends { [P in keyof Variables]: Variables[P] | null; } ? { ...; } : { ...; }'.ts(2322)

Here is my function:

export async function storePromise<Data = any, Variables extends AnyVariables = AnyVariables>(query: string | DocumentNode | TypedDocumentNode<Data, Variables>, variables: Variables, client: Client, requestPolicy: RequestPolicy|undefined): Promise<ReturnType<typeof queryStore<Data, Variables>>> {
	return new Promise((resolve, reject) => {
		// Next line errors:
		const qargs: QueryArgs<Data, Variables> = {query, client, requestPolicy}
		const s = queryStore<Data, Variables>(qargs)

		const unsubscribe = s.subscribe(value => {
			if (!value.fetching || value.error) {
				resolve(s)
			}
		})
		// Some other code...
	})
}

freb avatar Jan 05 '23 01:01 freb

Hiya, sorry to drop the ball here for a bit! :wave: It took me a little to realise and identify when this would occur and which cases we forgot. @jaschaephraim: Thanks for the reproduction! By the way, UseQueryArgsReact<Result, Variables> should read UseQueryArgsReact<Variables, Result> instead there. That's because the generics there are swapped to not break backwards compatibility.

Anyway! #3022 should be resolving these problems.

@freb: This should also start working; That said, this is an extremely convoluted way to do this, if that's how you're trying to get to a promise, since this is a thing 😅 https://formidable.com/open-source/urql/docs/basics/core/#one-off-queries-and-mutations

kitten avatar Mar 09 '23 04:03 kitten