5

I'm using graphql-codegen to generate typescript types from graphql schema. I'm trying to create a fragment with dynamic fields.

schema.ts

This is the type generated by graphql-codegen.

/** User Type */
export type UserType = {
  __typename?: 'UserType';
  id: Scalars['ID'];
  avatar: Scalars['String'];
  email: Scalars['String'];
  name: Scalars['String'];
  showPostsInFeed: Scalars['Boolean'];
  username: Scalars['String'];
};

user.model.ts

I'm using these interfaces throughout the entire application for validations and consistency.

export interface IUserBase {
  id: string;
  avatar: string;
  name: string;
  username: string;
}

export interface IUserPost extends IUserBase {
  showPostsInFeed: boolean;
}

export interface IUserProfile extends IUserBase, IUserPost {
  email: string;
}

show.ts

This is the file used for generation. Here I want to make a fragment with dynamic fields using my existent IUserPost and IUserProfile interfaces, in order to reuse those fields and avoid duplication repeating them inside the fragment one by one.

import gql from 'graphql-tag';
import { keys } from 'ts-transformer-keys';

import { IUserProfile, IUserPost } from '../../user.model';

const keysUserPofile = keys<IUserProfile>();  //Get all interface keys
const keysUserPost = keys<IUserPost>();  //Get all interface keys

//Fragments
export const fragments = {
  userProfile: gql`
    fragment UserProfile on UserType {
      ${keysUserPofile.join('\n')}    //Interpolation
    }
  `,
  userPost: gql`
    fragment UserPost on UserType {
      ${keysUserPost.join('\n')}    //Interpolation
    }
  `
};

//Queries
export const userProfileQuery = gql`
    query UserProfileQuery($id: String!) {
      showUser(id: $id) {
        ...UserProfile
      }
    }
`;

export const userPostQuery = gql`
    query UserPostQuery($id: String!) {
      showUser(id: $id) {
        ...UserPost
      }
    }
`;

When I try to pass these fields using interpolation, I get this error:

$ npm run generate

> gogofans-ui@0.0.0 generate C:\Development\GogoFans\gogofans-ui
> graphql-codegen --config codegen.yml

  √ Parse configuration
  > Generate outputs
    > Generate src/app/core/graphql/schema.ts
      √ Load GraphQL schemas
      × Load GraphQL documents
        → Syntax Error: Expected Name, found "}".
        Generate

Found 1 error

× C:/Development/GogoFans/gogofans-ui/src/app/users/graphql/fragments/show.ts
GraphQLError: Syntax Error: Expected Name, found "}".

at syntaxError (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\error\syntaxError.js:15:10)
at Parser.expectToken (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:1423:40)
at Parser.parseName (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:92:22)
at Parser.parseField (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:289:28)
at Parser.parseSelection (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:278:81)
at Parser.many (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:1537:26)
at Parser.parseSelectionSet (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:265:24)
at Parser.parseFragmentDefinition (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:410:26)
at Parser.parseDefinition (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:134:23)
at Parser.many (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:1537:26)
at Parser.parseDocument (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:109:25)
at Object.parse (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:36:17)
at Object.parseGraphQLSDL (C:\Development\GogoFans\gogofans-ui\node_modules@graphql-tools\utils\index.cjs.js:601:28)
at parseSDL (C:\Development\GogoFans\gogofans-ui\node_modules@graphql-tools\code-file-loader\index.cjs.js:239:18)
at CodeFileLoader.load (C:\Development\GogoFans\gogofans-ui\node_modules@graphql-tools\code-file-loader\index.cjs.js:173:28)
at async loadFile (C:\Development\GogoFans\gogofans-ui\node_modules@graphql-tools\load\index.cjs.js:48:24)

GraphQLError: Syntax Error: Expected Name, found "}".

at syntaxError (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\error\syntaxError.js:15:10)
at Parser.expectToken (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:1423:40)
at Parser.parseName (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:92:22)
at Parser.parseField (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:289:28)
at Parser.parseSelection (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:278:81)
at Parser.many (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:1537:26)
at Parser.parseSelectionSet (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:265:24)
at Parser.parseFragmentDefinition (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:410:26)
at Parser.parseDefinition (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:134:23)
at Parser.many (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:1537:26)
at Parser.parseDocument (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:109:25)
at Object.parse (C:\Development\GogoFans\gogofans-ui\node_modules\graphql\language\parser.js:36:17)
at Object.parseGraphQLSDL (C:\Development\GogoFans\gogofans-ui\node_modules@graphql-tools\utils\index.cjs.js:601:28)
at parseSDL (C:\Development\GogoFans\gogofans-ui\node_modules@graphql-tools\code-file-loader\index.cjs.js:239:18)
at CodeFileLoader.load (C:\Development\GogoFans\gogofans-ui\node_modules@graphql-tools\code-file-loader\index.cjs.js:173:28)
at async loadFile (C:\Development\GogoFans\gogofans-ui\node_modules@graphql-tools\load\index.cjs.js:48:24)

Something went wrong

npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! gogofans-ui@0.0.0 generate: graphql-codegen --config codegen.yml
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the gogofans-ui@0.0.0 generate script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\Fidel\AppData\Roaming\npm-cache_logs\2020-07-06T07_01_25_424Z-debug.log

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
in3pi2
  • 877
  • 1
  • 11
  • 22

2 Answers2

2

Because you schema is static, fields are not going to change. That means, that you can add all possible fields to your selection set, and later filter it by using @skip or @include directives.

GraphQL Codegen and the tooling it uses doesn't support string interpolation, and it's not going to add support for it, because in order to get the final string, we'll need to load, compile and look for that exported identifier. Doing that will make the setup of codegen much more complex - because of the many flavours in the JS ecosystem.

GraphQL schema and operations could be defined as static strings, and you should be able to control the selection set dynamically with directives or similar manipulations.

Here's an example for using the built-in @skip and @include directives, over a fragment spread:

query UserProfileQuery($id: String!, $loadA: Boolean, $loadB: Boolean) {
      showUser(id: $id) {
        ...UserFieldsA @include(if: $loadA)
        ...UserFieldsB @include(if: $loadB)
      }
    }

fragment UserFieldsA {
  id
  a
}

fragment UserFieldsB {
  id
  b
}

This way, you can control it dynamically, by setting the variables $loadA or $loadB from your JS code, during runtime.

These directives are defined like that in GraphQL:

directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

That means you can use it over a field, fragment spread or an inline fragment - so it should give you enough flexibility to manage complex and dynamic selection sets.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Dotan Simha
  • 742
  • 1
  • 5
  • 12
1

GraphQL codegen is using code AST to look for operations (using graphql-tag-pluck). It doesn't do string interpolations, because in some cases it's something that is being determined only at runtime of your application, or it might be an internal variable that codegen can't really access.

In order to run the string interpolation - we need to compile and run your code file, and it's not something that codegen can really do.

Why do you need to do string interpolation? can you please explain your use case? Because if it's for separating the list of fields - then use GraphQL fragments. If it's for dynamically change the fields set - use the built-in @skip and @include directives. If you still need more control over the way codegen loads your documents, use custom document loader: https://graphql-code-generator.com/docs/getting-started/documents-field#custom-document-loader

In my opinion, it's simpler to use .graphql files for your GraphQL operations, and let codegen generate a ready-to-use code with typescript, typescript-operations and typescript-apollo-angular plugins - this way you are getting Angular Services, based on your operations, and it's fully type-safe, and the GraphQL operation is already defined inside.

Dotan Simha
  • 742
  • 1
  • 5
  • 12
  • 2
    I edited my question, but basically I would like to use interpolation to add dynamically the fields from my interfaces, in that way avoid the fields duplication. – in3pi2 Jul 06 '20 at 19:12