I’m building a Vue project with urql and graphql-codegen.
Urql has the ability to take Vue reactive variables when using useQuery()
to enable useQuery to be reactive and update when the variables do.
But graphql-codegen is creating the type for the variables parameter to require scalars (ex. string) and so typescript is throwing an error when I try to use (for ex.) a Ref instead.
This is my codegen.ts:
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: 'https://localhost:5001/graphql',
documents: ['src/**/*.vue', 'src/**/*.ts'],
ignoreNoDocuments: true, // for better experience with the watcher
generates: {
'./src/gql/': {
preset: 'client',
config: {
useTypeImports: true,
scalars: {
CustomDate: 'string',
ObjectID: 'string',
},
},
plugins: [],
},
},
};
export default config;
A sample variable type that’s created looks like:
export type Scalars = {
String: string;
ObjectID: string;
};
export type GetItemQueryVariables = Exact<{
_id: Scalars['ObjectID'];
}>;
And then a sample call might be:
const id = ref('123');
const queryResult = useQuery({
query: queryGQL, // which is the graphql for the get item query
variables: { _id: id }, // this line complains that type Ref<string is not assignable to type string
});
I can cast queryGQL to be something more generic so that it will take any variables, but that defeats the point.
Is there a setting on graphql-codegen to fix this? Or alternately, some way I could automatically patch the generated typescript definitions to make it work?
2 Answers
I had the same issue. I’m using @graphql-codegen
and urql for the first time. If anyone knows a better solution, let me know. 😁
Configuration
I followed the official documentation from the codegen
website.
The problem
URQL
The urql
allows us to pass ref objects as variables. It works great, especially if you write conditional queries. You can pause the query and pass in ref
variable.
const exampleQuery = useQuery({
query: GqlQueryString,
variables: {
someVariable, // <- it's a reference
},
pause: isQueryPaused,
});
Codegen
When codegen generates types, it makes variables of a simple type. So, in the code, you will see a type error since variable can not be of type Ref<...>
.
The solution
After Googling, I couldn’t find any solution quickly. Documentation of codegen
is pure and doesn’t have any good examples for this case.
We need to set scalars property so it accepts both Ref
and type.
config: {
useTypeImports: true,
scalars: {
String: {
input: "string | Ref<string | undefined>",
output: "string",
},
Boolean: {
input: "boolean | Ref<boolean | undefined>",
output: "boolean",
},
Int: {
input: "number | Ref<number | undefined>",
output: "number",
},
Float: {
input: "number | Ref<number | undefined>",
output: "number",
},
},
},
But having set that will not work. Because Ref
is not imported in the generated file.
To import the required type, I had to write a simple plugin:
// codegen-import-plugin.cjs
module.exports = {
plugin(schema, documents, config) {
return {
prepend: config.imports,
content: "",
};
},
validate(schema, documents, config) {
if (!Array.isArray(config.imports) || config.imports.length === 0) {
throw new Error("codegen-import-plugin requires imports to be set");
}
},
};
Add the plugin to the config:
plugins: [
{
"codegen-import-plugin.cjs": {
imports: ["import type { Ref } from 'vue';"],
},
},
],
As a result we’ll have this content in generated file:
import type { Ref } from 'vue';
import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: { input: string; output: string; }
String: { input: string | Ref<string | undefined>; output: string; }
Boolean: { input: boolean | Ref<boolean | undefined>; output: boolean; }
Int: { input: number | Ref<number | undefined>; output: number; }
Float: { input: number; output: number; }
};