Code Monkey home page Code Monkey logo

graphql-decorators's Introduction

Idea + draft specification for GraphQL Schema Decorators

Decorators can be used to augment GraphQL schemas. They are similar to directives which modify the way a GraphQL server executes a query, but they apply only to schemas.

At the highest level of abstraction, decorators are just a structured way to add metadata to a GraphQL schema, and a proposed rule for processing that metadata in a standard way.

The intent of schema decorators is to make GraphQL schemas more malleable without the need for modifying the underlying GraphQL engine for every new decorator.

Decorators can be used for a variety of purposes:

  • Adding metadata to the schema
  • Authorization
  • Argument validation
  • Filtering of results
  • Logging & profiling
  • Error handling
  • Backend connectors

GraphQL schema decorators use the + sign to distinguish them from directives, which use the @ sign and come after the thing they're modifying. They could both share the @ sign, but then decorators would have to come after the thing they decorate, which looks a bit awkward.

Here is an example of decorators on a schema specified with GraphQL schema language:

+connector(storage: "mongoDB")
+id(fields: ["uuid"])
type Person {
  uuid: String!
  name: String!

  +deprecated(reason: "Use the 'name' field instead")
  longName: String
  friends: [Person]
}

type RootQuery {
  +description(text: "find a person by name")
  findPerson(name: String!)

  +adminOnly
  +log(type: "adminAccess")
  allPersons(
    page: Int = 0

    +validateRange(min: 1, max: 10)
    numPages: Int = 1
  ): [Person]
}

type RootMutation {
  +requiresAuthentication
  +log(type: "userAccess")
  addPerson(
    +maxLen(100)
    name: String!
  ): Int

  +adminOnly
  +log(type: "adminAccess")
  removePerson(id: Int!): Boolean
}

schema {
  query: RootQuery
  mutation: RootMutation
}

In GraphQL schema language, arguments to decorators follow the same spec as arguments to fields.

What decorators do:

Decorators can be selectively applied to:

  • The schema
  • A specific type (object type, union, interface, input, scalar)
  • A specific field
  • An argument

Decorators can modify the behavior of the parts of the schema they are applied to. Sometimes that requires modifying other parts of the schema. For instance, the +validateRange decorator modifies the behavior of the containing field's resolve function.

In general, decorators either add, remove or modify an attribute of the thing they wrap. The most common type of decorator (e.g. +adminOnly, +log, +connector) will wrap one or more field's resolve functions to alter the execution behavior of the GraphQL schema, but other decorators (e.g. +description) may add attributes to a type, field or argument. It is also possible for a type decorator to add a field to the type (e.g. +id(fields: ["uuid"]) can add the __id field).

Schema decorator API

All decorators must extend the SchemaDecorator class and implement the following interfaces:

class SampleFieldDecorator extends SchemaDecorator {
  const defaultTag = 'sample'; // matches +sample in GraphQL schema language
  const locations = ['field', 'type', 'interface', 'union']; // where this decorator can be applied

  // the arguments declaration can be used to check whether a decorator's arguments are valid.
  const arguments = {
    type: GraphQLString,
    min: GraphQLInt,
    max: GraphQLInt
  };

  // the constructor is used to configure things once per server, such as database credentials.
  // if the same decorator class is to be used with different configurations, then two instances
  // with different prefixes have to be created.
  constructor(config){
    this.config = config;
    this.tag = config.tag || defaultTag;
  }

  get tag(){
    return this.tag;
  }

  isWellPlaced(locationName){
    return locations.indexOf(locationName) >= 0;
  }

  get arguments(){
    return arguments;
  }

  // apply returns a function which gets applied to the decorated thing.
  apply(){

    // context says what scope this decorator is being applied to, i.e. 'type', 'schema', 'field' etc.
    return (wrappedThing, { schema, type, field, context }) => {
      // use this.config ...
      // use args
      // modify wrappedThing's properties, resolve functions, etc.
    }
  }
}

Another possibility would be to make schema decorators plain objects and pass them through a createDecorator function, which could run some checks to make sure that the object has the proper format for a decorator.

When constructing a GraphQL schema from GraphQL schema language with decorators, all decorators need to be specified and given to the schema generation function, otherwise an error will be thrown:

import { Description, Deprecated, Validator } from 'graphql-decorators';
const shorthandSchema = gql` ... schema here `;

const availableDecorators = [ new Description(), new Deprecated(), new Validator()];

// fictional example, not the actual function signature:
const schema = makeExecutableSchema({
  schema: shorthandSchema,
  decorators: availableDecorators
});

Applying decorators to a GraphQL-JS schema

The use of schema decorators is most immediately obvious in GraphQL schema language, but they can also be applied to a GraphQL-JS schema. You might want to do this to get portable components that can be used across many schemas. Here is an example:

import { Description, DeprecationReason, Validator } from 'graphql-decorators';
// ... more imports ...
const deprecationReason = new DeprecationReason();
const description = new Description();
const validator = new Validator();

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQuery',
    decorators: [ description.apply({ text: 'This is the root query' }) ],
    fields: () => {
      getString: {
        type: GraphQLString,
        decorators: [ deprecationReason.apply({ text: 'This field never did anything useful' })],
        resolve(root, {str}){ return str; },
        args: {
          str: {
            type: GraphQLString,
            decorators: [ validator.apply({ type: 'length', min: 1, max: 1000 }) ]
          },
        },
      },
    },
  }),
});

To apply these decorators, the function applySchemaDecorators(schema) has to be called like so:

import { applySchemaDecorators } from 'graphql-tools';

const schema = new GraphQLSchema({
  // schema definition here
});

applySchemaDecorators(schema); // applies the decorators to the schema in place.

Many decorators can be used on the server as well as the client, which means they have to be part of the information returned by the introspection query. However, only the tag and the arguments should be shared with the client, not the configuration. The client will most likely need different configuration.

Some decorators may need to be server-only, in which case they should not be introspectable by the client.

Decorators can be used to add metadata to a GraphQL schema in a way that is portable across different servers and clients. As long as the semantics of a decorator are well-specified, there could be GraphQL-JS, Graphene, Apollo-client, Relay, Sangria, etc. implementations for the same decorator, which given the same decorator tag and arguments will do the same thing on all these different implementations. For example, they could be used to provide optimistic UI in apollo-client and relay with zero additional code: The server version of the decorator modifies a mutation so it updates a specific store, the client version (eg. apollo-client) updates the client cache instead.

graphql-decorators's People

Contributors

helfer avatar

Watchers

Adam Stankiewicz avatar James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.