I'm aware that there may have been good reasons for not supporting query directives when schema directive support was rolled out. However, I think query directives could be a powerful tool and provide clients additional flexibility. They may also provide solutions for some more common problems. For example, @include and @skip are clunky to use for query building where any combination of fields might be needed -- a @selection directive that lets the client specify an array of field names to include would be more client-friendly. I imagine query directives would allow suitable workarounds for issues like this one too.
Here's an example implementation using a plugin that mirrors the SchemaDirectiveVisitor:
const { ApolloServer, makeExecutableSchema } = require("apollo-server")
const { Kind } = require('graphql')
const { getArgumentValues } = require('graphql/execution/values')
// Simple example schema
const typeDefs = `
directive @selection(set: [String!]!) on FIELD
type Query {
foo(a: Int): Foo
}
type Foo {
a: String
b: String
c: String
}
`
const resolvers = {
Query: {
foo: () => ({ a: 'A', b: 'B', c: 'C' }),
},
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
class QueryDirectiveVisitor {
constructor({ args, schema }) {
this.args = args
this.schema = schema
}
visitFragmentDefinition(fragmentDefinition) { }
visitQuery(operation) { }
visitMutation(operation) { }
visitSubscription(operation) { }
visitVariableDefinition(variableDefinition) { }
visitField(field) { }
visitFragmentSpread(fragmentSpread) { }
visitInlineFragment(inlineFragmentNode) { }
}
const queryDirectives = {
selection: class SelectionDirective extends QueryDirectiveVisitor {
visitField(field) {
field.selectionSet = {
...field.selectionSet,
selections: field.selectionSet.selections.filter(selection => {
return selection.kind !== Kind.FIELD || this.args.set.includes(selection.name.value)
})
}
}
}
}
const applyDirective = (node, directive, variables) => {
const name = directive.name.value
const ClientDirective = queryDirectives[name]
if (ClientDirective) {
const directiveDef = schema.getDirective(name)
const args = getArgumentValues(directiveDef, directive, variables)
const clientDirective = new ClientDirective({ args, schema })
const visitorTarget = node.kind === Kind.OPERATION_DEFINITION
? node.operation.charAt(0).toUpperCase() + node.operation.slice(1)
: node.kind
clientDirective[`visit${visitorTarget}`](node)
}
}
const walkAST = (ast, variables) => {
if (!ast.selectionSet) {
return
}
ast.selectionSet.selections.forEach((selectionNode) => {
selectionNode.directives.forEach(directive => applyDirective(selectionNode, directive, variables))
walkAST(selectionNode, variables)
})
}
const server = new ApolloServer({
schema,
plugins: [
{
requestDidStart: () => ({
didResolveOperation({ operation, request: { variables }, document }) {
// Apply fragment definition directives
document.definitions.forEach(definition => {
if (definition.kind === Kind.FRAGMENT_DEFINITION) {
definition.directives.forEach(directive => applyDirective(definition, directive, variables))
}
})
// Apply operation definition directives
operation.directives.forEach(directive => applyDirective(operation, directive, variables))
// Apply variable definition directives
operation.variableDefinitions.forEach(variableDef => {
variableDef.directives.forEach(directive => applyDirective(variableDef, directive, variables))
})
// Walk the operation AST and apply any remaining directives
walkAST(operation, variables)
},
}),
}
]
})
I'm considering publishing a plugin along these lines, but I also think this might be a good fit for the base library. @martijnwalraven @abernix has there been any more conversation around adding this feature? Would you entertain a PR to this effect?
@danielrearden I'm curious why you closed this? the use case is still compelling
@bennypowers This has been open for 10 months with no response from the maintainers. Like I mentioned above, the functionality can be implemented via a plugin.
Did you publish said plugin 馃槈 ?
Not yet. The overall concept needs to be fleshed out more, with all common use cases covered. I still want to circle back to this, but I'm probably going to first focus on somehow adding that functionality to express-graphql
Most helpful comment
@bennypowers This has been open for 10 months with no response from the maintainers. Like I mentioned above, the functionality can be implemented via a plugin.