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
`
}
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 beposts: [ 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 ๐