@falcon-client/graphql-sequelize

    0.0.2-alpha.8 • Public • Published

    graphql-sequelize

    NPM Build Status Slack Coverage

    Should be used with dataloader-sequelize to avoid N+1 queries

    Installation

    $ npm install --save graphql-sequelize

    graphql-sequelize assumes you have graphql and sequelize installed.

    Resolve helpers

    import { resolver } from "graphql-sequelize";
    
    resolver(SequelizeModel[, options]);

    A helper for resolving graphql queries targeted at Sequelize models or associations. Please take a look at the tests to best get an idea of implementation.

    Features

    • Automatically converts args to where if arg keys matches model attributes
    • Automatically converts an arg named 'limit' to a sequelize limit
    • Automatically converts an arg named 'order' to a sequelize order

    Relay & Connections

    Relay documentation

    Options

    The resolver function takes a model as its first (required) argument, but also has a second options object argument. The available options are:

    resolver(SequelizeModel, {
      // Whether or not this should return a list. Defaults to whether or not the
      // field type is an instance of `GraphQLList`.
      list: false,
    
      // Whether or not relay connections should be handled. Defaults to `true`.
      handleConnection: true,
    
      // Whether or not Sequelize should be shimmed to use Dataloader. Disable by setting this value to boolean false.
      dataLoader: false,
    
      /**
       * Manipulate the query before it's sent to Sequelize.
       * @param findOptions {object} - Options sent to Seqeulize model's find function
       * @param args {object} - The arguments from the incoming GraphQL query
       * @param context {object} - Resolver context, see more at GraphQL docs below.
       * @returns findOptions or promise that resolves with findOptions
       */
      before: (findOptions, args, context) => {
        findOptions.where = { /* Custom where arguments */ };
        return findOptions;
      },
      /**
       * Manipulate the Sequelize find results before it's sent back to the requester.
       * @param result {object|array} - Result of the query, object or array depending on list or not.
       * @param args {object} - The arguments from the incoming GraphQL query
       * @param context {object} - Resolver context, see more at GraphQL docs below.
       * @returns result(s) or promise that resolves with result(s)
       */
      after: (result, args, context) => {
        result.sort(/* Custom sort function */);
        return result;
      },
    
      /*
       * Transfer fields from the graphql context to the options passed to model calls
       * Inherits from global resolver.contextToOptions
       */
      contextToOptions: {
        a: 'a',
        b: 'c'
      }
    });
    
    resolver.contextToOptions = {}; /* Set contextToOptions globally */

    The args and context parameters are provided by GraphQL. More information about those is available in their resolver docs.

    Examples

    import {resolver} from 'graphql-sequelize';
    
    let User = sequelize.define('user', {
      name: Sequelize.STRING
    });
    
    let Task = sequelize.define('task', {
      title: Sequelize.STRING
    });
    
    User.Tasks = User.hasMany(Task, {as: 'tasks'});
    
    let taskType = new GraphQLObjectType({
      name: 'Task',
      description: 'A task',
      fields: {
        id: {
          type: new GraphQLNonNull(GraphQLInt),
          description: 'The id of the task.',
        },
        title: {
          type: GraphQLString,
          description: 'The title of the task.',
        }
      }
    });
    
    let userType = new GraphQLObjectType({
      name: 'User',
      description: 'A user',
      fields: {
        id: {
          type: new GraphQLNonNull(GraphQLInt),
          description: 'The id of the user.',
        },
        name: {
          type: GraphQLString,
          description: 'The name of the user.',
        },
        tasks: {
          type: new GraphQLList(taskType),
          resolve: resolver(User.Tasks)
        }
      }
    });
    
    let schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'RootQueryType',
        fields: {
          // Field for retrieving a user by ID
          user: {
            type: userType,
            // args will automatically be mapped to `where`
            args: {
              id: {
                description: 'id of the user',
                type: new GraphQLNonNull(GraphQLInt)
              }
            },
            resolve: resolver(User)
          },
    
          // Field for searching for a user by name
          userSearch: {
            type: new GraphQLList(userType),
            args: {
              query: {
                description: "Fuzzy-matched name of user",
                type: new GraphQLNonNull(GraphQLString),
              }
            },
            resolve: resolver(User, {
              // Custom `where` clause that fuzzy-matches user's name and
              // alphabetical sort by username
              before: (findOptions, args) => {
                findOptions.where = {
                  name: { "$like": `%${args.query}%` },
                };
                findOptions.order = [['name', 'ASC']];
                return findOptions;
              },
              // Custom sort override for exact matches first
              after: (results, args) => {
                return results.sort((a, b) => {
                  if (a.name === args.query) {
                    return 1;
                  }
                  else if (b.name === args.query) {
                    return -1;
                  }
    
                  return 0;
                });
              }
            })
          }
        }
      })
    });
    
    let schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'RootQueryType',
        fields: {
          users: {
            // The resolver will use `findOne` or `findAll` depending on whether the field it's used in is a `GraphQLList` or not.
            type: new GraphQLList(userType),
            args: {
              // An arg with the key limit will automatically be converted to a limit on the target
              limit: {
                type: GraphQLInt
              },
              // An arg with the key order will automatically be converted to a order on the target
              order: {
                type: GraphQLString
              }
            },
            resolve: resolver(User)
          }
        }
      })
    });

    field helpers

    field helpers help you automatically define a models attributes as fields for a GraphQL object type.

    var Model = sequelize.define('User', {
      email: {
        type: Sequelize.STRING,
        allowNull: false
      },
      firstName: {
        type: Sequelize.STRING
      },
      lastName: {
        type: Sequelize.STRING
      }
    });
    
    import {attributeFields} from 'graphql-sequelize';
    
    attributeFields(Model, {
      // ... options
      exclude: Array, // array of model attributes to ignore - default: []
      only: Array, // only generate definitions for these model attributes - default: null
      globalId: Boolean, // return an relay global id field - default: false
      map: Object, // rename fields - default: {}
      allowNull: Boolean, // disable wrapping mandatory fields in `GraphQLNonNull` - default: false
      commentToDescription: Boolean, // convert model comment to GraphQL description - default: false
      cache: Object, // Cache enum types to prevent duplicate type name error - default: {}
    });
    
    /*
    {
      id: {
        type: new GraphQLNonNull(GraphQLInt)
      },
      email: {
        type: new GraphQLNonNull(GraphQLString)
      },
      firstName: {
        type: GraphQLString
      },
      lastName: {
        type: GraphQLString
      }
    }
    */
    
    userType = new GraphQLObjectType({
      name: 'User',
      description: 'A user',
      fields: _.assign(attributeFields(Model), {
        // ... extra fields
      })
    });

    Providing custom types

    attributeFields uses the graphql-sequelize typeMapper to map Sequelize types to GraphQL types. You can supply your own mapping function to override this behavior using the mapType export.

    var Model = sequelize.define('User', {
      email: {
        type: Sequelize.STRING,
        allowNull: false
      },
      isValid: {
        type: Sequelize.BOOLEAN,
        allowNull: false
      }
    });
    
    import {attributeFields,typeMapper} from 'graphql-sequelize';
    typeMapper.mapType((type) => {
       //map bools as strings
       if (type instanceof Sequelize.BOOLEAN) {
         return GraphQLString
       }
       //use default for everything else
       return false
    });
    
    //map fields
    attributeFields(Model);
    
    /*
    {
      id: {
        type: new GraphQLNonNull(GraphQLInt)
      },
      email: {
        type: new GraphQLNonNull(GraphQLString)
      },
      isValid: {
          type: new GraphQLNonNull(GraphQLString)
      },
    }
    */

    Renaming generated fields

    attributeFields accepts a map option to customize the way the attribute fields are named. The map option accepts an object or a function that returns a string.

    var Model = sequelize.define('User', {
      email: {
        type: Sequelize.STRING,
        allowNull: false
      },
      firstName: {
        type: Sequelize.STRING
      },
      lastName: {
        type: Sequelize.STRING
      }
    });
    
    attributeFields(Model, {
        map:{
            email:"Email",
            firstName:"FirstName",
            lastName:"LastName"
        }
    });
    
    /*
    {
      id: {
        type: new GraphQLNonNull(GraphQLInt)
      },
      Email: {
        type: new GraphQLNonNull(GraphQLString)
      },
      FirstName: {
        type: GraphQLString
      },
      LastName: {
        type: GraphQLString
      }
    }
    */
    
    attributeFields(Model, {
        map:(k) => k.toLowerCase()
    });
    
    /*
    {
      id: {
        type: new GraphQLNonNull(GraphQLInt)
      },
      email: {
        type: new GraphQLNonNull(GraphQLString)
      },
      firstname: {
        type: GraphQLString
      },
      lastname: {
        type: GraphQLString
      }
    }
    */

    ENUM attributes with non-alphanumeric characters

    GraphQL enum types only support ASCII alphanumeric characters, digits and underscores with leading non-digit. If you have other characters, like a dash (-) in your Sequelize enum types, they will be converted to camelCase. If your enum value starts from a digit, it will be prepended with an underscore.

    For example:

    • foo-bar becomes fooBar

    • 25.8 becomes _258

    VIRTUAL attributes and GraphQL fields

    If you have Sequelize.VIRTUAL attributes on your sequelize model, you need to explicitly set the return type and any field dependencies via new Sequelize.VIRTUAL(returnType, [dependencies ... ]).

    For example, fullName here will not always return valid data when queried via GraphQL:

    firstName: { type: Sequelize.STRING },
    lastName: { type: Sequelize.STRING },
    fullName: {
      type: Sequelize.VIRTUAL,
      get: function() { return `${this.firstName} ${this.lastName}`; },
    },

    To work properly fullName needs to be more fully specified:

    firstName: { type: Sequelize.STRING },
    lastName: { type: Sequelize.STRING },
    fullName: {
      type: new Sequelize.VIRTUAL(Sequelize.STRING, ['firstName', 'lastName']),
      get: function() { return `${this.firstName} ${this.lastName}`; },
    },

    args helpers

    defaultArgs

    defaultArgs(Model) will return an object containing an arg with a key and type matching your models primary key and the "where" argument for passing complex query operations described here

    var Model = sequelize.define('User', {
    
    });
    
    defaultArgs(Model);
    
    /*
    {
      id: {
        type: new GraphQLNonNull(GraphQLInt)
      }
    }
    */
    
    var Model = sequelize.define('Project', {
      project_id: {
        type: Sequelize.UUID
      }
    });
    
    defaultArgs(Model);
    
    /*
    {
      project_id: {
        type: GraphQLString
      },
      where: {
        type: JSONType
      }
    }
    */

    If you would like to pass "where" as a query variable - you should pass it as a JSON string and declare its type as SequelizeJSON:

    /* with GraphiQL */
    // request
    query($where: SequelizeJSON) {
      user(where: $where) {
        name
      }
    }
    
    // query variables
    # JSON doesn't allow single quotes, so you need to use escaped double quotes in your JSON string
    {
      "where": "{\"name\": {\"like\": \"Henry%\"}}"
    }
    

    defaultListArgs

    defaultListArgs will return an object like:

    {
      limit: {
        type: GraphQLInt
      },
      order: {
        type: GraphQLString
      },
      where: {
        type: JSONType
      }
    }

    Which when added to args will let the resolver automatically support limit and ordering in args for graphql queries. Should be used with fields of type GraphQLList.

    import {defaultListArgs} from 'graphql-sequelize'
    
    args: _.assign(defaultListArgs(), {
      // ... additional args
    })

    Install

    npm i @falcon-client/graphql-sequelize

    DownloadsWeekly Downloads

    1

    Version

    0.0.2-alpha.8

    License

    MIT

    Unpacked Size

    221 kB

    Total Files

    57

    Last publish

    Collaborators

    • amilajack