dotansimha / graphql-code-generator

A tool for generating code based on a GraphQL schema and GraphQL operations (query/mutation/subscription), with flexible support for custom plugins.
https://the-guild.dev/graphql/codegen/
MIT License
10.78k stars 1.31k forks source link

GraphQL Code Generator v5 Roadmap #8296

Open theguild-bot opened 2 years ago

theguild-bot commented 2 years ago

This page is synced automatically from The Guild's Notion Notion page URL: https://www.notion.so/GraphQL-Code-Generator-v4-Roadmap-91923bfb2dee48eaa0d6a77666429968

At The Guild, we’ve decided to work as much as possible in public; that’s why we are opening the roadmaps for all of our projects.

The goals for this are:

  1. So you will know what we are working on, what we see as a higher priority, and know what to expect from our projects in the future
  2. So you can share your opinions and thoughts about what we do and influence our decisions
  3. So you can join us and contribute to our efforts!

Before laying down the roadmap of GraphQL Code Generator v3, we would like to thank all of you for being so many who use codegen daily and for contributing to making it such a complete project! 🚀

While some people judge that GraphQL is difficult, GraphQL Code Generator v3 aims to change that perspective by providing a unified configuration along with a smaller and simpler generated code.

By providing a unified package and configuration for all client-side use cases, all existing and future plugin alternatives will be moved to community repos.

Let’s now cover these changes in detail.

A unified configuration and package for all GraphQL clients

Most of the existing client-side plugins (typescript-react-apollo, typescript-react-query, etc) rely on the generation of hooks or SDKs that wrap the underlying GraphQL Client in a type-safe way.

However, the generation of hooks or SDK code brings many downsides:

To make GraphQL code generation great and simple, the v3 version will introduce two major changes:

Here is how you can already configure codegen for all GraphQL Clients:

import { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
  schema: 'http://localhost:4000/graphql',
  documents: ['src/**/*.tsx'],
  generates: {
    './src/gql/': {
      preset: 'client',
      plugins: []
    }
  }
}

export default config

The client preset comes with a simple opinionated configuration and a lightweight types-only generation.

To try the new client preset, please install the following dependencies:

yarn add graphql
yarn add -D typescript
yarn add -D @graphql-codegen/cli
yarn add -D @graphql-codegen/client-preset

First, start GraphQL Code Generator in watch mode:

yarn graphql-codegen --watch

Using GraphQL Code Generator will type your GraphQL Query and Mutations as you write them ⚡️

Now, each query or mutation written with the generated graphql() function will be automatically typed!

For example, with Apollo Client (React):

import React from 'react';
import { useQuery } from '@apollo/client';
import { graphql } from './gql/gql';

import Film from './Film';

// here, `allFilmsWithVariablesQueryDocument` is fully typed!
const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ `
  query allFilmsWithVariablesQuery($first: Int!) {
    allFilms(first: $first) {
      edges {
        node {
          ...FilmItem
        }
      }
    }
  }
`);

function App() {
    // Most GraphQL Clients know how to deal with typed GraphQL documents,
    //   providing typed data and typed variables
  const { data } = useQuery(allFilmsWithVariablesQueryDocument, { variables: { first: 10 } });
  return (
    <div className="App">
      {data && <ul>{data.allFilms?.edges?.map((e, i) => e?.node && <Film film={e?.node} key={`film-${i}`} />)}</ul>}
    </div>
  );
}

export default App;

Thanks to work made to integrate TypeDocumentNode (the underlying plugin used by preset: client) with most of the popular GraphQL clients, you no longer need hooks or SDK, simple GraphQL documents works!

We believe that the preset: client approach is the way to get the best of TypeScript and GraphQL by:

Finally, this new preset: client has been properly tested on all popular GraphQL clients across most frameworks:

You will find demos and code examples for each of them in the examples/front-end/ folder of the codegen repository.

You will also find a complete guide for React and Vue in codegen documentation.

We aim for GraphQL Code Generator 3.0’s client preset to become the official way to generate GraphQL Types for front-end use cases, replacing all existing hook and SDK-based plugins.

For this reason, we encourage you to already give a try at the codegen v3 client preset (@graphql-codegen/client-presec) and provide feedback on this issue.

The v3 stable release will be shipped once sufficient feedback is posted.

Finally, while the GraphQL Code Generator 3.0 milestone aims to provide a unified front-end experience through the preset: client, the 3.x versions aim to fully rewrite the core packages of codegen.

Some core parts of codegen are more than 6 years old and need to be rewritten (optimized, simplified, and more).

We plan to incorporate the pending issues related to the core packages in this gradual 3.x milestones.

Introduction of the “community plugins”

Historically, all plugins were pushed to the https://github.com/dotansimha/graphql-code-generator repository, making it hard for us to review all contributions in a reasonable timeframe and to enforce consistency across all the options introduced in the different packages.

We believe that the best way to keep codegen extensible and improve the contribution experience at scale is to introduce the concept of community plugins.

A community plugin offers a feature-set that diverges from the preset: client or a plugin created by the community.

Soon, all the existing plugins part of the list below and all the future plugins created by the community will live in their dedicated repository:

All the above plugins will be eligible for repository ownership transfer based on relevant past contributions.

Of course, such a change will come with help from our side:

What about server-side plugins?

The 3.x milestones include some work on server-side plugins such as typescript-resolvers (ex: improving Federation support).


Milestones

Below are the details of the aforementioned plans for the 3.0 and 3.x milestones.

3.0

3.x

preset: client improvements

Future of codegen CLI

Back-end code generation issues

We will go over the following typescript-resolvers and graphql-modules pending plugins issues:

huv1k commented 2 years ago

Will there be support for module augmentation still? 🤔 I really enjoy this feature, but there are some shortcomings like some frameworks don't provide for example useFragment etc.

charlypoly commented 2 years ago

Will there be support for module augmentation still? 🤔 I really enjoy this feature, but there are some shortcomings like some frameworks don't provide, for example, useFragment etc.

Hi @huv1k! We are planning to remove the support for module augmentation since it is not working without the associated babel plugin properly set up. Since not all front-end setups can provide custom babel configuration, we were planning to remove the augmentedModuleName options and also to simplify the installation flow.

However, this being an RFC, we will take your feedback into account! 👀

franky47 commented 2 years ago

Based on your code example, does that mean that the preferred way to consume GraphQL operations is co-location within TS/JS files?

I just migrated a large codebase from Apollo CLI to graphql-codegen v2 and refactored it to move all GraphQL operation definitions to .graphql files (importing only typed documents to pass to Apollo Client), I wouldn't want to have to undo that all over again..

charlypoly commented 2 years ago

Based on your code example, does that mean that the preferred way to consume GraphQL operations is co-location within TS/JS files?

I just migrated a large codebase from Apollo CLI to graphql-codegen v2 and refactored it to move all GraphQL operation definitions to .graphql files (importing only typed documents to pass to Apollo Client), I wouldn't want to have to undo that all over again..

Hi @franky47,

We are exploring different migration path options; providing a codemod could be one; however, we have to look at the different setups to migrate from. What plugins are you currently using?

franky47 commented 2 years ago

Hi @charlypoly, here's my codegen.yml:

overwrite: true
schema: "../backend/schema.graphql"
documents: "src/**/*.graphql"
config: # The following configuration will adjust output to the defaults of Apollo-Codegen, and should be compatible with most use-cases.
  preResolveTypes: true # Simplifies the generated types
  namingConvention: keep # Keeps naming as-is
  avoidOptionals: # Avoids optionals on the level of the field
    field: true
  nonOptionalTypename: true # Forces `__typename` on all selection sets
  skipTypeNameForRoot: true # Don't generate __typename for root types
generates:
  src/modules/graphql.types.ts:
    plugins:
      - typescript
      - typescript-operations
      - typed-document-node
    hooks:
      afterOneFileWrite:
        - prettier --write

I have GraphQL code into .graphql files, which are parsed by the generator to give me the full schema types (I use them to type props in a React/Next.js app), and typed schemas to pass to Apollo Client, which provides type inference (variables & output), so that TypeScript has my back when matching the data & view layers.

I migrated from a setup where the Apollo CLI generated the TS types from gql tags co-located with their components in .ts(x) files, hence my remark that having to go back would be quite annoying.

I really like the typed document plugin, it removed the need to explicitly pass generic arguments to useQuery and useMutation, so most of my query/mutation hooks are now one-liners. The only thing that required a bit of refactoring to use those was that their naming convention does not seem to be configurable, so I had to rename all FOO_{QUERY|MUTATION} to FooDocument, not a big deal but the name can be confusing sometimes (especially when dealing with domain objects also named "Document").

Hope this helps you see a bit better about my particular use-case.

charlypoly commented 2 years ago

@franky47, thank you for sharing it!

We are also exploring options to still support .graphql files, we will keep you updated here.

nandita1 commented 2 years ago

I think there are some problems with this:

  1. The generation time has gone way up. It has doubled up infact.
  2. And I see that there have been duplicate types being generated. The size of the file(s) have also gone way up.
productdevbook commented 2 years ago

vue 3 and vue graphql v4 problem

image

but it works when these lines are added

ilijaNL commented 1 year ago

If it is going full TypeDocumentNode way (which I like), is it not better to go with a builder pattern approach. The builder can be full type safe and be generated from the introspection query. Additionally you need to generate the builder only initially and on schema change and not when adding new documents. Some open source projects which apply this principle:

In my opinion this provides best of the worlds. Small bundle size, no watchers necessary on frontend and much faster dev cycle. Additionally the builder can be scoped per context (think about hasura roles or public/private introspection results)

hrougier commented 1 year ago

Will there be support for module augmentation still? 🤔 I really enjoy this feature, but there are some shortcomings like some frameworks don't provide, for example, useFragment etc.

Hi @huv1k! We are planning to remove the support for module augmentation since it is not working without the associated babel plugin properly set up. Since not all front-end setups can provide custom babel configuration, we were planning to remove the augmentedModuleName options and also to simplify the installation flow.

However, this being an RFC, we will take your feedback into account! 👀

Augmented module is a nice feature for people who don't like having their own source code polluted by/relying on generated code, why not keeping it for those willing to make advanced configuration? (babel plugin is just a one line configuration).

Inspired by what graphql-let does, I personally came up with some kind of hack that generates everything to a fake node_modules/@types of a monorepo:

schema: path/to/schema.json
documents:
  - src/**/*.ts
  - src/**/*.tsx
generates:
  # app operations are generated inside a fake @types module
  ../../node_modules/@types/myProject__graphql-client:
    preset: gql-tag-operations-preset
    presetConfig:
      augmentedModuleName: '@myProject/graphql-client'
      fragmentMasking:
        augmentedModuleName: '@myProject/graphql-client'
hooks:
  # here is the ugliest part of the "hack": generating a package.json
  afterAllFileWrite:
    - "printf '{ \"name\": \"@types/myProject__graphql-client\", \"types\": \"gql.d.ts\" }' > '../../node_modules/@types/myProject__graphql-client/package.json'"

This allows for everything within an app to be transparently imported from a @myProject/graphql-client (a preconfigured apollo-client module in the monorepo) without ever seeing/dealing with any ugly generated code 😅

Big advantage: I can remove codegen at any time without breaking the code, only losing types!

import { gql, useQuery } from '@myProject/graphql-client'

const MyQuery = gql(`
  query MyQuery {
    ...
  }
`)

const MyComponent = () => {
  const { loading, data, error } = useQuery(MyQuery)

  return <>...</>
}

It would be nice to keep this ability to hide the generated code somewhere.

grischaerbe commented 1 year ago

I really like the new approach with preset: client. It seems like a big step towards a simpler toolchain with less dependencies. I guess currently the configuration for a custom scalar types is not considered:

config:

overwrite: true
schema: 'http://localhost:8080/v1/graphql'
documents:
  - './src/**/*.graphql'
  - './src/**/*.svelte'
generates:
  src/lib/graphql/new/:
    preset: client
    config:
      strictScalars: true
      scalars:
        numeric: number
        jsonb: Array<any>
        bigint: number
        uuid: string
        timestamptz: string
        timestamp: string

generated scalars:

export type Scalars = {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
  bigint: any;
  jsonb: any;
  numeric: any;
  timestamp: any;
  timestamptz: any;
  uuid: any;
};
charlypoly commented 1 year ago

I really like the new approach with preset: client. It seems like a big step towards a simpler toolchain with less dependencies. I guess currently the configuration for a custom scalar types is not considered:

Hi @grischaerbe,

We restored some config options in the client preset, from @graphql-codegen/client-preset@1.0.4: more details in the changelog: https://github.com/dotansimha/graphql-code-generator/releases/tag/release-1665062191991

deathemperor commented 1 year ago

I really like the new approach with preset: client. It seems like a big step towards a simpler toolchain with less dependencies. I guess currently the configuration for a custom scalar types is not considered:

Hi @grischaerbe,

We restored some config options in the client preset, from @graphql-codegen/client-preset@1.0.4: more details in the changelog: release-1665062191991 (release)

@charlypoly I use client-preset@1.0.5 with

        presetConfig:
          augmentedModuleName: "@apollo/client"
          namingConvention:
            typeNames: change-case#pascalCase

but the generated still gives Claim_Cases_Bool_Exp instead of ClaimCasesBoolExp

sonatard commented 1 year ago

Hi What is the difference between preset: client and preset: gql-tag-operations-preset ?

https://the-guild.dev/blog/unleash-the-power-of-fragments-with-graphql-codegen https://www.the-guild.dev/graphql/codegen/plugins/presets/gql-tag-operations-preset

dotansimha commented 1 year ago

Hi What is the difference between preset: client and preset: gql-tag-operations-preset ?

the-guild.dev/blog/unleash-the-power-of-fragments-with-graphql-codegen the-guild.dev/graphql/codegen/plugins/presets/gql-tag-operations-preset

Hi @sonatard, The client preset is a wrapper on top of gql-tag-operations-preset. It's going to deprecate gql-tag-operations-preset soon, and introduce better integration with GraphQL clients.

Faithfinder commented 1 year ago

React-query example... Isn't strongly typed. data is any etc.

charlypoly commented 1 year ago

React-query example... Isn't strongly typed. data is any etc.

Hi @Faithfinder,

Could you provide more context about which example you are referring to? The client-preset provides typed GraphQL operations when React Query is used in combination with a supported GraphQL client (ex: graphql-request@^5) or when the fetcher is properly typed (as documented in our guide).

Faithfinder commented 1 year ago

React-query example... Isn't strongly typed. data is any etc.

Hi @Faithfinder,

Could you provide more context about which example you are referring to? The client-preset provides typed GraphQL operations when React Query is used in combination with a supported GraphQL client (ex: graphql-request@^5) or when the fetcher is properly typed (as documented in our guide).

Just checking out an example you've supplied: https://github.com/dotansimha/graphql-code-generator/tree/v3-preset-front-end/examples/front-end/react/tanstack-react-query

Using twoslash here to demostrate that data is any, and on request to demonstrate that yarn install has been run and types are in place, generally. image

joelmukuthu commented 1 year ago

Hi @charlypoly / @dotansimha. I'm trying out the client preset and so far it's going well but I'm missing some config options that I could previously set: useTypeImports, arrayInputCoercion, immutableTypes, nonOptionalTypename. I understand that the preset is being strict about which options are passed along but I'm wondering why that is? Are plugin configs also deliberately disabled?

charlypoly commented 1 year ago

React-query example... Isn't strongly typed. data is any etc.

Hi @Faithfinder, Could you provide more context about which example you are referring to? The client-preset provides typed GraphQL operations when React Query is used in combination with a supported GraphQL client (ex: graphql-request@^5) or when the fetcher is properly typed (as documented in our guide).

Just checking out an example you've supplied: v3-preset-front-end/examples/front-end/react/tanstack-react-query

Using twoslash here to demostrate that data is any, and on request to demonstrate that yarn install has been run and types are in place, generally. image

Hi @Faithfinder,

I've moved your discussion to a dedicated bug issue: https://github.com/dotansimha/graphql-code-generator/issues/8516 I couldn't reproduce your issue; let's move forward on a reproduction here.

charlypoly commented 1 year ago

Hi @charlypoly / @dotansimha. I'm trying out the client preset and so far it's going well but I'm missing some config options that I could previously set: useTypeImports, arrayInputCoercion, immutableTypes, nonOptionalTypename. I understand that the preset is being strict about which options are passed along but I'm wondering why that is? Are plugin configs also deliberately disabled?

Hi @joelmukuthu,

useTypeImports, arrayInputCoercion are still available to use, as documented here: https://www.the-guild.dev/graphql/codegen/docs/guides/react-vue#config-api

The new preset: "client" reduces the number of available options to recommend best practices of codegen configuration. We will work this week on refining the list of available options and also, the default values of some.

awinograd commented 1 year ago

Love the idea of how the client-preset works in terms of TypedDocumentNode. I'm running into issues integrating into my codegen due to the lack of plugin support along with the preset. I use the add plugin to import my scalars from a different file (as well as to add a comment to my generated files).

      - add:
          content: "// This file is automatically generated. See gqlcodegen.yml for details"
      - add:
          content: "import * as CustomScalars from \"@src/types/scalars\";"
config:
  scalars:
    Any: CustomScalars.GQLAny
    JSONObject: CustomScalars.GQLJSONObject

with the preset i get the following error:

✖ [client-preset] providing plugins with `preset: "client" leads to duplicated generated types

Is there a workaround here? I understand wanting to disable plugins due to the one-size-fits-all nature of the preset, but an escape hatch would be nice

charlypoly commented 1 year ago

Is there a workaround here? I understand wanting to disable plugins due to the one-size-fits-all nature of the preset, but an escape hatch would be nice

Hi @awinograd,

Thank you for bringing this up! I'll remove the add from the disallow plugin list.

charlypoly commented 1 year ago

Hi @awinograd,

Thank you for bringing this up! I'll remove the add from the disallow plugin list.

@aradalvand is has been fixed in @graphql-codegen/client-preset@1.1.1 📦

aradalvand commented 1 year ago

@charlypoly I think you meant to mention @awinograd and not me :)

marco2216 commented 1 year ago

Hi @charlypoly I think it's great with the type inference from the query string so that we don't have to import the type def. I have always not liked the importing of hooks with custom names, so I think this is a great way forwards. I've run into a few problems while testing this out in our codebase.

  1. Sometimes you are just using a fragment to reduce duplicated definitions, not to pass data to a child component. In some cases this fragment lives in the same string as the parent query/fragment. But the generated types masks the selected data from this fragment. I would expect it to be available without using the hook.
  2. Sometimes, we have a component where we used the Fragment type for a prop, but we use it in a place where the data doesn't come from graphql. What is the suggested solution to pass the data? Currently I just did a type cast to the FragmentType which works but seems a bit hacky.
  3. If I postfix fragment names with Fragment (like in the docs) the type names don't seem to be deduplicated by default (they will have FragmentFragment as postfix). That caused an issue because I had added dedupeOperationSuffix: true to my config (just copy pasted from prev. config), because the gql.ts file was deduped but not the graphql.ts file. Also when importing the fragment type, if I want to use it somewhere, I would prefer that it's deduped.

These issues leads me to think that it might be useful to have an option to disable the query masking. Masking seems a bit unnecessary to me when you are using TypeScript anyways, as it doesn't force you to remove data when you don't use it anymore. It basically just checks that you have selected the data in the components fragment. But is there actually any danger in using data that's selected by parent/child components? TypeScript would catch it if we were to remove one of those components and we no longer query the data.

charlypoly commented 1 year ago

Hi @marco2216,

Thank you for your feedback; we appreciate it! Let me answer your questions.

  • Sometimes you are just using a fragment to reduce duplicated definitions, not to pass data to a child component. In some cases this fragment lives in the same string as the parent query/fragment. But the generated types masks the selected data from this fragment. I would expect it to be available without using the hook.

You can disable Fragment Masking by passing the following presetConfig:

import { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
  schema: 'https://swapi-graphql.netlify.app/.netlify/functions/index',
  documents: ['src/**/*.tsx'],
  ignoreNoDocuments: true, // for better experience with the watcher
  generates: {
    './src/gql/': {
      preset: 'client',
      presetConfig: {
            fragmentMasking: false,
      },
      plugins: []
    }
  }
}

export default config

2. Sometimes, we have a component where we used the Fragment type for a prop, but we use it in a place where the data doesn't come from graphql. What is the suggested solution to pass the data? Currently I just did a type cast to the FragmentType which works but seems a bit hacky.

Could you provide an example? Isn't FragmentType<typeof fragmentDocument> solving this issue?

3. If I postfix fragment names with Fragment (like in the docs) the type names don't seem to be deduplicated by default (they will have FragmentFragment as postfix). That caused an issue because I had added dedupeOperationSuffix: true to my config (just copy pasted from prev. config), because the gql.ts file was deduped but not the graphql.ts file. Also when importing the fragment type, if I want to use it somewhere, I would prefer that it's deduped.

The dedupeOperationSuffix: true should not affect the output since it is not part of the config API option supported by the client-preset. Could you share your configuration?

marco2216 commented 1 year ago

@charlypoly

  1. Good to know, thanks. But what if I want masking enabled in general but just not for fragments that are defined in the same fragment/query string? Maybe it's possible to parse and check that if it's local to the component, don't mask? Or maybe with a special directive or comment?
  2. I can cast it to FragmentType<typeof fragmentDocument>, but then I lose the type safety on the data that's passed in. I can type cast an empty object to FragmentType.
  3. Used client preset v1.1.1 with this config image
jahglow commented 1 year ago

First off thanks for keeping it up, guys! We have a large codebase with files in .graphql so that support would be great! Looking at generated files I can say this: it is expensive to have an operation string as a key in documents object in gql.ts. Moreover the generated document is suboptimal:

/* eslint-disable */
import * as types from './graphql';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';

const documents = {
    "\n  fragment WorkplaceReg on ManufactureEmployeeReg {\n    id\n    workplace {\n      id\n      name\n      quant {\n        id\n        picking\n        name\n      }\n    }\n    checkIn {\n      id\n      createdAt\n    }\n  }\n": types.WorkplaceRegFragmentDoc,
    "\n  query employeeQuants($id: ID!) {\n    employee(id: $id) {\n      id\n      workplaceRegs {\n        id\n        workplace {\n          id\n          name\n          quant {\n            id\n            name\n          }\n        }\n        ...WorkplaceReg\n      }\n      storageWorkerClasses {\n        id\n      }\n    }\n  }\n": types.EmployeeQuantsDocument,
};

export function graphql(source: "\n  fragment WorkplaceReg on ManufactureEmployeeReg {\n    id\n    workplace {\n      id\n      name\n      quant {\n        id\n        picking\n        name\n      }\n    }\n    checkIn {\n      id\n      createdAt\n    }\n  }\n"): (typeof documents)["\n  fragment WorkplaceReg on ManufactureEmployeeReg {\n    id\n    workplace {\n      id\n      name\n      quant {\n        id\n        picking\n        name\n      }\n    }\n    checkIn {\n      id\n      createdAt\n    }\n  }\n"];
export function graphql(source: "\n  query employeeQuants($id: ID!) {\n    employee(id: $id) {\n      id\n      workplaceRegs {\n        id\n        workplace {\n          id\n          name\n          quant {\n            id\n            name\n          }\n        }\n        ...WorkplaceReg\n      }\n      storageWorkerClasses {\n        id\n      }\n    }\n  }\n"): (typeof documents)["\n  query employeeQuants($id: ID!) {\n    employee(id: $id) {\n      id\n      workplaceRegs {\n        id\n        workplace {\n          id\n          name\n          quant {\n            id\n            name\n          }\n        }\n        ...WorkplaceReg\n      }\n      storageWorkerClasses {\n        id\n      }\n    }\n  }\n"];

export function graphql(source: string): unknown;
export function graphql(source: string) {
  return (documents as any)[source] ?? {};
}

export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode<  infer TType,  any>  ? TType  : never;

What can be improved: every source is triplicated (repeated in 3 places): in document key and graphql(source: __here__)(typeof documents):[__here__]

Assign it to a constant with the name of the Fragment of Query etc and you get a reduced file (bundle) size.

You may easily extract the query/mutation/fragment name and use it as an inferred type and the key in documents, like so without any need of overloading graphql function type:

import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';

/* eslint-disable */
import * as types from './graphql';

const documents = {
  'WorkplaceReg': types.WorkplaceRegFragmentDoc,
  'employeeQuants': types.EmployeeQuantsDocument,
};

type DocumentKey = keyof typeof documents;
type OperationType = 'query' | 'fragment' | 'mutation' | 'subscription';
type GenericDeclaration<T extends string, V extends OperationType> = V extends 'fragment'? `\n  ${V} ${T} on ${string}`:`\n  ${V} ${T}${'(' | ' on' | ' {'}${string}`;

export function graphql<T extends string, V extends OperationType>(
  source: GenericDeclaration<T, V>,
): T extends DocumentKey ? typeof documents[T] : never {
// use a different approach (regex?) as to extract the name that corresponds to T 
const extractedName:T = pseudoFn()
  return (documents)[extractedName] ?? {};
}

export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode<
  infer TType,
  any
>
  ? TType
  : never;

Secondly you stated that this approach is better than plugins with sdk generators. Well, it is not! you have the documents constants which is no different than what the typescript-generic-sdk plugin we wrote does - it stores all document nodes json in one big object which cannot be tree shaken. If I'm wrong, I'm happy to be corrected. Actually if you look up the plugin in your repo - we took the approach of using operation names instead of operation string code. It's up to the dev to make sure his methods are unique in naming. We have a huge graphql scheme and didn't have any naming collisions so far.

jahglow commented 1 year ago

Yeah and lastly fragmentMasking: false, is not listed in the link you provided above

jahglow commented 1 year ago

An one more thing: +1 to generating the types for the scheme and importing them into the graphql.ts thus making the external. In a superapp (in a monorepo) that imports micro-frontends and they all reflect against the same backend-served schema it's better to have enums and everything else deduped

marco2216 commented 1 year ago

I am getting some duplicate fragment errors from the apollo-server backend with the new graphql tag that I wasn't getting with the tag from graphql-tag. I am using these fragments in multiple different fragments, but I would expect them to get deduplicated or something? image

joelmukuthu commented 1 year ago

Hi @charlypoly, thanks for responding to my earlier question. While working with fragments, types don't work with something like this:

const userFragment = graphql(`
  fragment User_user on User {
    firstName
    lastName
  }
`);
const query = graphql(`
  {
    user(id: 5) {
      ...User_user
    }
  }
  ${userFragment}
`);

And that's because the string that the generated code expects is something like this:

const generatedQueryString = `
  {
    user(id: 5) {
      ...User_user
    }
  }
  # empty line here
`;

While it's possible to avoid the empty line by formatting the query a different way, I think this is still a case that should be handled by the code generator. Alternatively, using operation names instead of the query/mutation/fragment strings (as suggested by jahglow) could also work around this.

ardatan commented 1 year ago

I think it is better if you can create new issues for your bug reports or feature requests :) This issue belongs to the roadmap.

charlypoly commented 1 year ago

I am getting some duplicate fragment errors from the apollo-server backend with the new graphql tag that I wasn't getting with the tag from graphql-tag. I am using these fragments in multiple different fragments, but I would expect them to get deduplicated or something?

Hi @marco2216, Please refer to the dedicated issue where a solution has been suggested: https://github.com/dotansimha/graphql-code-generator/issues/8103

charlypoly commented 1 year ago

While working with fragments, types don't work with something like this:

Hi @joelmukuthu,

Fragment documents should not be included in the Query string, you will find more details here: https://stackoverflow.com/questions/74068078/the-query-is-always-of-type-unknown-when-created-with-graphql-codegen

charlypoly commented 1 year ago
  1. If I postfix fragment names with Fragment (like in the docs) the type names don't seem to be deduplicated by default (they will have FragmentFragment as postfix). That caused an issue because I had added dedupeOperationSuffix: true to my config (just copy pasted from prev. config), because the gql.ts file was deduped but not the graphql.ts file. Also when importing the fragment type, if I want to use it somewhere, I would prefer that it's deduped.

The dedupeOperationSuffix: true should not affect the output since it is not part of the config API option supported by the client-preset.

@marco2216 let's continue the discussion here: https://github.com/dotansimha/graphql-code-generator/discussions/8554#discussioncomment-4026197

charlypoly commented 1 year ago

Hi all!

As the client-preset is getting more usage, we are looking for feedback regarding its config options and defaults: https://github.com/dotansimha/graphql-code-generator/issues/8562

kazekyo commented 1 year ago

Hi. I am a developer of a preset. https://github.com/kazekyo/nau https://www.naugraphql.com/docs/introduction

This preset is like relay-operation-optimizer, enabling the use of Relay directives such as @argumentDefinitions, @refetchable, and @appendNode, etc.

I want to allow the users who are currently using my preset to use the new client preset naturally in v3, so I would like to provide my features as a plugin instead of the preset. But there are still things that I cannot provide within the current limitation of plugin. 😢

I need a feature to change documents before executing plugins. It is similar to addToSchema. It is necessary for the implementation of @arguments and @argumentDefinitions, etc. I also want to use the original documents to execute my plugin, meaning I want to write the following plugin:

module.exports = {
  plugin(schema, documents, config, context) {
    return generateMyCode(schema, documents, context.myPlugin.originalDocuments);
  },
  changeDocuments(schema, documents, config) {
    const newDocuments = myChangeDocumentsFunction(documents);
    return { documents: newDocuments, context: { myPlugin: { originalDocuments: documents } } };
  },
};

Can you please consider extending the plugin's design to solve these problems? I would be happy to provide ideas or help with implementation if you can consider it. 🙌

revero-doug commented 1 year ago

unless I'm missing something, you cannot currently write something like this using the client preset:

import { gql } from '@apollo/client/core'
import { graphql } from './generated'

const myQuery = graphql(gql`
query MyQuery {
  me {
    name
  }
}
`)

IMO this is the most idiomatic way people define graphql documents inline with code, and it's a bit... optimistic to consider any example documentation complete when it was done by avoiding using gql tag and instead using /* GraphQL */ followed by a regular template string. As it currently stands, you cannot use Apollo GraphQL VS Code features such as intellisense while writing your inline document.

This issue seems to indicate Typescript will not support this kind of interface

charlypoly commented 1 year ago

Hi. I am a developer of a preset. kazekyo/nau naugraphql.com/docs/introduction

This preset is like relay-operation-optimizer, enabling the use of Relay directives such as @argumentDefinitions, @refetchable, and @appendNode, etc.

I want to allow the users who are currently using my preset to use the new client preset naturally in v3, so I would like to provide my features as a plugin instead of the preset. But there are still things that I cannot provide within the current limitation of plugin. 😢

I need a feature to change documents before executing plugins. It is similar to addToSchema. It is necessary for the implementation of @arguments and @argumentDefinitions, etc. I also want to use the original documents to execute my plugin, meaning I want to write the following plugin:

module.exports = {
  plugin(schema, documents, config, context) {
    return generateMyCode(schema, documents, context.myPlugin.originalDocuments);
  },
  changeDocuments(schema, documents, config) {
    const newDocuments = myChangeDocumentsFunction(documents);
    return { documents: newDocuments, context: { myPlugin: { originalDocuments: documents } } };
  },
};

Can you please consider extending the plugin's design to solve these problems? I would be happy to provide ideas or help with implementation if you can consider it. 🙌

Hi @kazekyo,

It's a really nice preset! 🚀

Regarding your core API change request, we end up adding a similar API for the new client-preset:

https://github.com/dotansimha/graphql-code-generator/blob/0ee912231c67b96f2e0bf3a573fcccabaa21cea5/packages/presets/client/src/index.ts#L70

Would this match your needs?

charlypoly commented 1 year ago

IMO this is the most idiomatic way people define graphql documents inline with code, and it's a bit... optimistic to consider any example documentation complete when it was done by avoiding using gql tag and instead using /* GraphQL */ followed by a regular template string. As it currently stands, you cannot use Apollo GraphQL VS Code features such as intellisense while writing your inline document.

This topic is currently being discussed on the following discussion: https://github.com/dotansimha/graphql-code-generator/discussions/8584

For now, due to TypeScript limitations, client-preset cannot support the gql template literal syntax.

revero-doug commented 1 year ago

IMO this is the most idiomatic way people define graphql documents inline with code, and it's a bit... optimistic to consider any example documentation complete when it was done by avoiding using gql tag and instead using /* GraphQL */ followed by a regular template string. As it currently stands, you cannot use Apollo GraphQL VS Code features such as intellisense while writing your inline document.

This topic is currently being discussed on the following discussion: #8584

For now, due to TypeScript limitations, client-preset cannot support the gql template literal syntax.

Understood @charlypoly -- see #8591 for an alternative proposal that is possible with Typescript and only a slight difference from currently available behavior when using plugins instead of client preset

kazekyo commented 1 year ago

Hi. I am a developer of a preset. kazekyo/nau naugraphql.com/docs/introduction This preset is like relay-operation-optimizer, enabling the use of Relay directives such as @argumentDefinitions, @refetchable, and @appendNode, etc. I want to allow the users who are currently using my preset to use the new client preset naturally in v3, so I would like to provide my features as a plugin instead of the preset. But there are still things that I cannot provide within the current limitation of plugin. 😢 I need a feature to change documents before executing plugins. It is similar to addToSchema. It is necessary for the implementation of @arguments and @argumentDefinitions, etc. I also want to use the original documents to execute my plugin, meaning I want to write the following plugin:

module.exports = {
  plugin(schema, documents, config, context) {
    return generateMyCode(schema, documents, context.myPlugin.originalDocuments);
  },
  changeDocuments(schema, documents, config) {
    const newDocuments = myChangeDocumentsFunction(documents);
    return { documents: newDocuments, context: { myPlugin: { originalDocuments: documents } } };
  },
};

Can you please consider extending the plugin's design to solve these problems? I would be happy to provide ideas or help with implementation if you can consider it. 🙌

Hi @kazekyo,

It's a really nice preset! 🚀

Regarding your core API change request, we end up adding a similar API for the new client-preset:

https://github.com/dotansimha/graphql-code-generator/blob/0ee912231c67b96f2e0bf3a573fcccabaa21cea5/packages/presets/client/src/index.ts#L70

Would this match your needs?

@charlypoly This API may be different from what I need. I think we need some more discussion. 🙏 I have created a discussion. https://github.com/dotansimha/graphql-code-generator/discussions/8593 I would be happy to discuss it there. 😃

Anyway, I'm really looking forward to v3! 🚀

frehner commented 1 year ago

Hello team, great work, love it! Question:

Create a TypedDocumentNode string alternative (TypedString) that does not require GraphQL AST on the Client (should be easily configurable within the preset)

Does this mean that it would be a goal to output the original GraphQL string instead of a document when generating things?

If so, that would be wonderful. (Is it currently possible but just not part of the client preset yet? Or just not possible at all right now?)

We've had issues in the past because of the GraphQL AST parser slowing down our websites, so we've had to pass on using GraphQL code generator for the time being. However, if we could get all the automatically-typed goodness, but without graphql-tag / GraphQL AST parsing at runtime, that would be great. (For context, we're using Remix, which uses ESBuild, so we can't use any of the listed preprocessing tricks either)

Or maybe I'm missing something and this is all possible already? Forgive me if that's true.

Thanks!

n1ru4l commented 1 year ago

Hello team, great work, love it! Question:

Create a TypedDocumentNode string alternative (TypedString) that does not require GraphQL AST on the Client (should be easily configurable within the preset)

Does this mean that it would be a goal to output the original GraphQL string instead of a document when generating things?

If so, that would be wonderful. (Is it currently possible but just not part of the client preset yet? Or just not possible at all right now?)

We've had issues in the past because of the GraphQL AST parser slowing down our websites, so we've had to pass on using GraphQL code generator for the time being. However, if we could get all the automatically-typed goodness, but without graphql-tag / GraphQL AST parsing at runtime, that would be great. (For context, we're using Remix, which uses ESBuild, so we can't use any of the listed preprocessing tricks either)

Or maybe I'm missing something and this is all possible already? Forgive me if that's true.

Thanks!

@frehner exactly, basically we can bake in the type into anything. Here is a quick example.

import {DocumentNode} from "graphql"

type WithSignature<TResult, TVariables> = {["    __baked_in_types"]?: (v: TVariables) => TResult }

type ThingWithBakedInType<T, TResult = Record<string, unknown>, TVariables = Record<string, never>> = T & WithSignature<TResult, TVariables>

type TypedString<TResult, TVariables> = ThingWithBakedInType<string, TResult, TVariables>

declare function fetch<TResult, TVariables>(foo: ThingWithBakedInType<unknown, TResult, TVariables>): TResult

declare let foo: TypedString<{a: 1}, {b: 2}>
declare let foo2: ThingWithBakedInType<DocumentNode, {c: 3}>

fetch(foo).a

fetch(foo2).c

https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbwCIQMYFcQFMB2MByEAJlgL5wBmUEIcARAOZQCGYAFgI4A2dAULzACeYLHADqwGGwDKwBjmYx0ULAB4AKgCUsAZ3RcYAGjjqAasyjBmAIy66AfHAC8iANp04nuAH1v15gDWWETewDjeQiI6dAC6APwAXHAAFABuSWYWVra6AJTOjlq6+vCk-JGi6mxhDBJSAEKBwQCSOOrCaurGRXoGznDaqNBEqjowljgMxug4ATgQAO449t3mljZ2Ov2Dw6PjNcY4WKlYUPaOLupwAGTikjJyCkoqGtq9RiZr2Zv25R0mHSI0n2k1exQMqyyGwc-SqNTqbEaQSIrXaIj2EymJjeJUh6xyOl+vBIqC4FlEFBmqBgwAgOEoWBgqDYYPeeO+DmSFAgEAy1UmCKRLTaHVUMzmixw3RxEM+UIJ9lyGRlMH4xKwpPJcDs8G5vIBIiBIIYqgQzCSAEZSMYENYkgAmUi-ElklTaxmUHn2vnw+5ClEi9EoDDYPCEEg21BJADMTv4FEZzK5PNyADpmPHE2xkxB7WnULwgA


Links

aaronadamsCA commented 1 year ago

With fragmentMasking: false, I thought we were supposed to be using the generated fragment types; but it seems these are in graphql.ts and not re-exported from index.ts. Is this just an oversight, or is there some other way we should be getting back our fragment types? (Obviously we can deep import, but I am assuming index.ts is supposed to be our interface to the generated code.)

I also hope .graphql file support is still being considered; the const someName = graphql(/* GraphQL */`query SomeOtherName { ... }`) pattern is a real eyebrow-raiser that we're frankly not interested in adopting. We would rather keep our GraphQL in GraphQL files and avoid this confusing syntax if possible.

jstejada commented 1 year ago

thanks for building this! i'm glad i can use some of the same patterns available in relay with graphql-codegen now

@charlypoly do you have any thoughts on supporting generating multiple files vs single gql.ts/graphql.ts files that contains the runtime nodes for all document definitions?

in a project with a large amount of definitions i would like to be able to not load all of the runtime document nodes upfront unless they're actually being used, and let code-splitting and the bundler just include the ones that are needed.

I noticed that the implementation of the generated graphql function relies on having the documents map to be able to return the corresponding runtime node for a document, but maybe this could work similarly to the relay compiler where the graphql call gets transpiled into an import/require call that imports the corresponding generated file for that document, which would contain the appropriate document node?

alternatively, is there a recommended way to customize the output of the client-preset, maybe to do a custom implementation of this sort of capability?

charlypoly commented 1 year ago

in a project with a large amount of definitions i would like to be able to not load all of the runtime document nodes upfront unless they're actually being used, and let code-splitting and the bundler just include the ones that are needed.

Hi @jstejada,

The client-preset comes with a Babel plugin to address this concern. It is not documented yet but you can find a working example here: https://github.com/dotansimha/graphql-code-generator/tree/master/examples/front-end/advanced/babel-optimized

jstejada commented 1 year ago

@charlypoly oh that’s perfect, thank you!