How to allow Urql (typescript) to accept Vue reactive variables for queries created with graphql-codegen

How to allow Urql (typescript) to accept Vue reactive variables for queries created with graphql-codegen


2

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
2


1

Use reactive to convert refs to properties:

let r = ref(123)    // Ref<number>
let a = { r }       // { r: Ref<number> }
let b = reactive(a) // { r: number }


0

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; }
};



Leave a Reply

Your email address will not be published. Required fields are marked *