0
votes

I am trying to implement a custom GraphQL directive. My understanding is that if my SchemaDirectiveVisitor subclass implements static getDirectiveDeclaration(directiveName, schema) then I don't have to manually declare the directive in my SDL (Schema Definition Language).

Because AuthDirective implements getDirectiveDeclaration, it’s no longer necessary for the schema author to include the directive @auth ... declaration explicitly in the schema. The returned GraphQLDirective object will be used to enforce the argument types and default values, as well as enabling tools like GraphiQL to discover the directive using schema introspection. Additionally, if the AuthDirective class fails to implement visitObject or visitFieldDefinition, a helpful error will be thrown.

Source: https://blog.apollographql.com/reusable-graphql-schema-directives-131fb3a177d1

and

However, if you’re implementing a reusable SchemaDirectiveVisitor for public consumption, you will probably not be the person writing the SDL syntax, so you may not have control over which directives the schema author decides to declare, and how. That’s why a well-implemented, reusable SchemaDirectiveVisitor should consider overriding the getDirectiveDeclaration method

Source: https://www.apollographql.com/docs/apollo-server/features/creating-directives.html

In my code, despite having implemented static getDirectiveDeclaration(directiveName, schema) I still have to declare the directive in SDL.

Shouldn't it work without manually declaring in SDL?

Full Example Code:

const { ApolloServer, gql, SchemaDirectiveVisitor } = require('apollo-server');
const { DirectiveLocation, GraphQLDirective, defaultFieldResolver } = require("graphql");

class UpperCaseDirective extends SchemaDirectiveVisitor {
  static getDirectiveDeclaration(directiveName, schema) {
    console.log("inside getDirectiveDeclaration", directiveName)
    return new GraphQLDirective({
      name: directiveName,
      locations: [
        DirectiveLocation.FIELD_DEFINITION,
      ],
      args: {}
    });
  }

  visitFieldDefinition(field) {
    console.log("inside visitFieldDefinition")
    const { resolve = defaultFieldResolver } = field;
    field.resolve = async function (...args) {
      const result = await resolve.apply(this, args);
      if (typeof result === 'string') {
        return result.toUpperCase();
      }
      return result;
    };
  }
}

const books = [
  {
    title: 'Harry Potter and the Chamber of Secrets',
    author: 'J.K. Rowling',
  },
  {
    title: 'Jurassic Park',
    author: 'Michael Crichton',
  },
];

const typeDefs = gql`

  #########################################
  # ONLY WORKS WITH THIS LINE UNCOMMENTED #
  #########################################
  directive @upper on FIELD_DEFINITION

  type Book {
    title: String
    author: String @upper
  }

  type Query {
    books: [Book]
  }
`;

const resolvers = {
  Query: {
    books: () => books,
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  schemaDirectives: {
    upper: UpperCaseDirective
  }
});

server.listen().then(({ url }) => {
  console.log(`????  Server ready at ${url}`);
});
1

1 Answers

1
votes

I have the same problem and was able to find this comment from graphql-tools issue #957.

From the changelog:

NOTE: graphql 14 includes breaking changes. We're bumping the major version of graphql-tools to accommodate those breaking changes. If you're planning on using graphql 14 with graphql-tools 4.0.0, please make sure you've reviewed the graphql breaking changes list.

This is likely caused by the fact that graphql-js now requires you to define your directives in your schema, before you attempt to use them. For example:

directive @upper on FIELD_DEFINITION

type TestObject {
  hello: String @upper
}

You can likely work around this by pre-defining your directives in your schema, but I'd like to confirm this. If this works, we'll need to update the docs.