Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

custom schemaDirectives not working in custom plugin #2108

Closed
konsumer opened this issue Jul 4, 2019 · 9 comments
Closed

custom schemaDirectives not working in custom plugin #2108

konsumer opened this issue Jul 4, 2019 · 9 comments
Assignees
Labels
core Related to codegen core/cli waiting-for-release Fixed/resolved, and waiting for the next stable release

Comments

@konsumer
Copy link

konsumer commented Jul 4, 2019

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
  `
}
@dotansimha
Copy link
Owner

Hi @konsumer !
I think I managed to fix this in #2120.
Can you please try the latest alpha version? (1.3.1-alpha-991b9d78.75)

@dotansimha dotansimha added the waiting-for-release Fixed/resolved, and waiting for the next stable release label Jul 7, 2019
@konsumer
Copy link
Author

konsumer commented Jul 8, 2019

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.

@dotansimha
Copy link
Owner

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 ^).

konsumer added a commit to konsumer/graphql-codegen-knex-migration that referenced this issue Jul 8, 2019
@konsumer
Copy link
Author

konsumer commented Jul 8, 2019

(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/cli@1.3.1-alpha-991b9d78.75. 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]!
}

@konsumer
Copy link
Author

konsumer commented Jul 8, 2019

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.

@dotansimha
Copy link
Owner

dotansimha commented Jul 8, 2019

@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.

@konsumer
Copy link
Author

konsumer commented Jul 8, 2019

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

@dotansimha
Copy link
Owner

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.

@dotansimha
Copy link
Owner

Fixed in 1.4.0 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Related to codegen core/cli waiting-for-release Fixed/resolved, and waiting for the next stable release
Projects
None yet
Development

No branches or pull requests

2 participants