Graphql-code-generator: custom schemaDirectives not working in custom plugin

Created on 4 Jul 2019  ยท  9Comments  ยท  Source: dotansimha/graphql-code-generator

Describe the bug

schemaDirectives don't seem to work with visitor pattern for making a plugin, but I may be using it wrong. Before it even gets to visitor, the directives have been stripped from the schema.

I basically want to generate sql stuff (knex migrations, resolvers & join-monster annotations) with graphql SDL to define config.

To Reproduce
Steps to reproduce the behavior:

type User @db {
  id: ID!
  email: String!
  firstName: String
  lastName: String
  name: String @nodb
  posts: [ Post ]!
}

gets turned into this:

type User {
  id: ID!
  email: String!
  firstName: String
  lastName: String
  name: String
  posts: [Post]!
}

With this plugin-code:

const { printSchema, parse, visit } = require('graphql')

module.exports = {
  plugin: (schema, documents, config) => {
    console.log(printSchema(schema))
  },
  addToSchema: `
    directive @db(table: String) on OBJECT
    directive @nodb on FIELD | FIELD_DEFINITION
  `
}
bug core waiting-for-release

All 9 comments

Hi @konsumer !
I think I managed to fix this in https://github.com/dotansimha/graphql-code-generator/pull/2120.
Can you please try the latest alpha version? (1.3.1-alpha-991b9d78.75)

Hmm, I tried to use the same code I used before to test the plugin and it wouldn't load:

Here is my codegen.yml:

schema: ./example/**/*.graphql
generates:
  migrations/0_generated.js:
    plugins:
      - ./index.js

Here is error:

  โœ– migrations/0_generated.js
    Plugin ./index.js does not export a valid JS object with "plugin" function.

    Make sure your custom plugin is written in the following form:

    module.exports = {
      plugin: (schema, documents, config) => {
        return 'my-custom-plugin-content';
      },
    };

My plugin looks fine, liek this:

const { printSchema } = require('graphql')

module.exports = {
  plugin: (schema, documents, config) => {
    console.log(printSchema(schema))
  }
}

You can see the whole thing here.

I tried it locally, and the error I'm getting is:

โ†’ Syntax Error: Unexpected single quote character ('), did you mean to use a double quote (")?

(comes from posts: [ Post ]! @link('author'), it should be posts: [ Post ]! @link(field: "author")).

It seems like there is a very specific issue with calling your plugin file ./index.js, it tries to load the local file from the codegen compiled code instead of your file.

Change your plugin file to custom-plugin.js and it will work.

Oh, and also, when using alpha versions, make sure to specify the version in package.json without any modifiers (such as ~ or ^).

(comes from posts: [ Post ]! @link('author'), it should be posts: [ Post ]! @link(field: "author")).

Yep, true. I got the error before I made @link but I will make the change.

Oh, and also, when using alpha versions, make sure to specify the version in package.json without any modifiers (such as ~ or ^).

Well, this would be true, if I was worried about the version cascading, but I'm just testing for you until it's published, at which point I will use latest. That is just what gets injected to package.json when you do npm i @graphql-codegen/[email protected]. I made the change anyway, just to keep track if we are going to test with multiple alphas, but it's not necessary if I am installing that version, cleanly.

It seems like there is a very specific issue with calling your plugin file ./index.js, it tries to load the local file from the codegen compiled code instead of your file.

Hmm, this seems like a bug? I moved it to a src dir, and set main in my package. That seemed to be the actual problem, but it still strips the actual usage of the directives:

directive @db(table: String, key: String) on OBJECT

directive @nodb on FIELD | FIELD_DEFINITION

directive @link(field: String) on FIELD | FIELD_DEFINITION

type Post {
  id: ID!
  title: String!
  body: String!
  author: User!
}

type User {
  id: ID!
  email: String!
  firstName: String
  lastName: String
  name: String
  posts: [Post]!
}

I dunno, if I run this in my plugin:

console.log(schema._typeMap.User._fields.posts.astNode.directives[0].arguments.map(a => a.value.value))

I get the arguments to the directive correctly, so I guess this really is an issue of printSchema stripping the usage of the directives. I have tried to use the visitor pattern directly with schema in the past, but it didn't work (I needed to do printSchema then parse.)

If the answer is simply "don't use visitor-pattern, because it has a requirement on a stripped schema" I can totally live with that and do it all very manually with loops. Maybe I could even do a quick lookup of directive args and stuff first, then do a visitor thing after. no big deal.

@konsumer yeah it seems to work.

printSchema strips all directives, you can use printSchemaWithDirectives from graphql-toolkit to solve this.
Then, you can get a valid DocumentNode with all AST inside, and use visit to create custom behaviours.

That is perfect.

When I do this, it also works to get all the directives and their args in a nice lookup table:

module.exports = {
  plugin: (schema, documents, config) => {
    // printSchema strips directives, so build a table of info first
    const tables = Object.keys(schema._typeMap).filter(t => schema._typeMap[t] && schema._typeMap[t].astNode && schema._typeMap[t].astNode.directives.filter(d => d.name.value === 'db').length)
    const fields = {}
    tables.forEach(table => {
      fields[table] = {}
      Object.keys(schema._typeMap[table]._fields).forEach(f => {
        const directives = schema._typeMap[table]._fields[f].astNode.directives.map(d => {
          const args = {}
          d.arguments.forEach(a => {
            args[a.name.value] = a.value.value
          })
          return { name: d.name.value, args }
        })
        fields[table][f] = {
          directives,
          name: f
        }
      })
      console.log(fields)
    })
  },
  addToSchema: `
    directive @db(table: String, key: String) on OBJECT
    directive @nodb on FIELD | FIELD_DEFINITION
    directive @link(field: String) on FIELD | FIELD_DEFINITION
  `
}

I like printSchemaWithDirectives much better, this works great:

const { visit, parse } = require('graphql')
const { printSchemaWithDirectives } = require('graphql-toolkit')

module.exports = {
  plugin: (schema, documents, config) => {
    const printedSchema = printSchemaWithDirectives(schema)
    const astNode = parse(printedSchema)
    const visitor = {
      FieldDefinition: node => {
        if (node.directives && node.directives.length) {
          node.directives.forEach(d => {
            const args = {}
            d.arguments.forEach(a => {
              args[a.name.value] = a.value.value
            })
            console.log(d.name.value, args)
          })
        }
      },
      ObjectTypeDefinition: node => {
        // This function triggered per each type
      }
    }

    const result = visit(astNode, { leave: visitor })
  },
  addToSchema: `
    directive @db(table: String, key: String) on OBJECT
    directive @nodb on FIELD | FIELD_DEFINITION
    directive @link(field: String) on FIELD | FIELD_DEFINITION
  `
}

Yeah, it's much easier to access and check the AST using visitor. Glad that it works for you now ๐Ÿ‘๐Ÿ‘๐Ÿ‘.

Keeping open until we'll release a stable version with the fix.

Fixed in 1.4.0 ๐ŸŽ‰

Was this page helpful?
0 / 5 - 0 ratings