Code Monkey home page Code Monkey logo

graphql-sequelize's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql-sequelize's Issues

generateIncludes leaves non-nullable fields out

I plan on investigating further and submitting a pull-request for this tomorrow, but just wanted to document the issue before I sleep.

When using the new map option, I mapped id to _id and it said there was a null in that field when it wasn't nullable. So I think generateIncludes is only including fields that are in the request, but it also needs to include any fields that are required by the GraphQL schema.

Can't access belongsTo/hasOne/any non connection associated off of an association

Here is my sample query:

query getStories {
    viewer {
    stories(limit: 15) {
        title,
    storyteller {
        firstName,
        lastName,
        stories(first: 15) {
         edges {
            node {
              title,
              user {
                id,
              },
            },
          },
        },
      },
    },
  },
}

Once I access the user id off of the story model, I receive the GraphQL error "column storyteller.stories.user does not exist". I've tried several variations on this setup, and it seems that when I have a connection, related models that are not in connections off of a node in said collection is queried for incorrectly. I can see the log in my terminal as:

Executing (default): SELECT "Story".*, "storyteller"."firstName" AS "storyteller.firstName", "storyteller"."lastName" AS "storyteller.lastName", "storyteller"."id" AS "storyteller.id", "storyteller.stories"."id" AS "storyteller.stories.id", "storyteller.stories"."title" AS "storyteller.stories.title", "storyteller.stories"."user" AS "storyteller.stories.user" FROM (SELECT "Story"."title", "Story"."id", "Story"."storytellerId" FROM "Stories" AS "Story" LIMIT 15) AS "Story" LEFT OUTER JOIN "Storytellers" AS "storyteller" ON "Story"."storytellerId" = "storyteller"."id" LEFT OUTER JOIN "Stories" AS "storyteller.stories" ON "storyteller"."id" = "storyteller.stories"."storytellerId";

It seems as though graphql-sequelize doesn't recognize related models off of a connection node unless it is also a connection. Strangely, I can see in lib/relay.js that associations are handled differently when not a connection, so I'm not sure what the problem actually is.

ACL on node fetches

We support the relay node lookup ala:

{
  node(id: "someGlobalId") {
    ... on Object {
      fields
   }
  }
}

This internally goes through our node type mapper and does a findById. We currently have no way of hooking ACL into this process other than wrapping around findById or using a beforeFind hook, but that might interfer with other systems that might not necessarily have the same ACL constraints.

Anyone have any good way of solving this? Personally i need to only allow certain node lookups to certain people.

/cc @brad-decker @aweary @idris

Allow for connections on Viewer / Root node (Relay).

First, I think the work you've done with this library is great, it's proven to be very helpful! I am, however, having an issue with the Relay portion of the code. As of right now, connections can only be made from models that have associations:

./src/resolver.js line 28

    if (association && source.get(association.as) !== undefined) {
      if (isConnection(info.returnType)) {
        return handleConnection(source[info.fieldName], args);
      }
      return source.get(association.as);

and
./src/resolver.js line 71

    if (association) {
      return source[association.accessors.get](findOptions).then(function (result) {
        if (isConnection(info.returnType)) {
          result = handleConnection(result, args);
        }
        return options.after(result, args, root, {
          ast: simpleAST,
          type: type
        });
      });
    }

The issue is that this constraint forces us to define the initial connection on the root viewer node as a GraphQL list (which I see done in the tests for relay.js in this library). Though this workaround works, it's inconsistent with how similar data is handled throughout the lib. Could you allow for connections to be attached directly to the root node? Thank you!

Add debug support

How would you feel about adding support for https://github.com/visionmedia/debug?

I've found myself going in and ad-hoc adding console.log statements here and there to debug something and it'd be must easier if I could just run my app with DEBUG=graphql:sequelize or something and see whats happening.

idFetcher only supports sequelize models

So looking at idFetcher

export function idFetcher(sequelize) {
  return globalId => {
    let {type, id} = fromGlobalId(globalId);
    const models = Object.keys(sequelize.models);
    type = type.toLowerCase();
    if (models.some(model => model === type)) {
      return sequelize.models[type].findById(id);
    }
    return null;
  };
}

I've found that there's no way to fetch a GraphQL type that doesn't have an associated sequelize model. This is bad since Relay apps often use a pattern where a root viewer field points to all the other actual fields, making it a sort of virtual field that just acts as an access point.

attributeFields, defaultArgs and defaultListArgs input type getting error

I been looking every where for a solution for this, and couldn't find any. Can anyone help me with this?

All I did was trying to test out this few functions
attributeFields and defaultArgs and defaultListArgs.

as below

let taskType = new GraphQLObjectType({
  name: 'Task',
  description: 'A task',
  fields: _.assign(attributeFields(Task))
});

let userType = new GraphQLObjectType({
  name: 'User',
  description: 'A user',
  fields: _.assign(attributeFields(User))
});

and also

let schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      user: {
        type: userType,
        // args will automatically be mapped to `where`
        args: _.assign(defaultArgs(User), {
          firstName: {type: GraphQLString}
        }),
        resolve: resolver(User)
      },
      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: _.assign(defaultListArgs()),
        resolve: resolver(User)
      }
    }
  })
});

but I am getting error as below.

    throw new Error(message);
          ^
Error: RootQueryType.user(id:) argument type must be Input Type but got: Int!.
    at invariant (C:\Users\HomePC\Documents\JSWorkspace\orm-testing\node_modules\graphql\jsutils\invariant.js:20:11)
    at C:\Users\HomePC\Documents\JSWorkspace\orm-testing\node_modules\graphql\type\definition.js:307:43
    at Array.map (native)
    at C:\Users\HomePC\Documents\JSWorkspace\orm-testing\node_modules\graphql\type\definition.js:304:45
    at Array.forEach (native)
    at defineFieldMap (C:\Users\HomePC\Documents\JSWorkspace\orm-testing\node_modules\graphql\type\definition.js:293:14)
    at GraphQLObjectType.getFields (C:\Users\HomePC\Documents\JSWorkspace\orm-testing\node_modules\graphql\type\definition.js:247:46)
    at typeMapReducer (C:\Users\HomePC\Documents\JSWorkspace\orm-testing\node_modules\graphql\type\schema.js:173:27)
    at Array.reduce (native)
    at new GraphQLSchema (C:\Users\HomePC\Documents\JSWorkspace\orm-testing\node_modules\graphql\type\schema.js:81:120)
    at Object.<anonymous> (C:\Users\HomePC\Documents\JSWorkspace\orm-testing\sequelize.js:103:14)
    at Module._compile (module.js:435:26)
    at Object.Module._extensions..js (C:\Users\HomePC\AppData\Roaming\npm\node_modules\traceur-runner\node_modules\traceur\src\node\require.js:68:21)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:313:12)
    at Module.require (module.js:366:17)
17 Jan 00:48:04 - [nodemon] app crashed - waiting for file changes before starting...

It work fine, if I were to replace it to call directly on the GraphQL Type.

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.',
    },
    firstName: {
      type: GraphQLString,
      description: 'The name of the user.',
    },
    lastName: {
      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: {
      user: {
        type: userType,
        // args will automatically be mapped to `where`
        args: {
          id: {
            description: 'id of the user',
            type: new GraphQLNonNull(GraphQLInt)
          }
        },
        resolve: resolver(User)
      },
      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)
      }
    }
  })
});

Any idea how to fix this?

Support for connections

I have the following connection setup on the user object,

miners: {
      type: minerConnection,
      description: 'A users miners',
      args: connectionArgs,
      resolve:  (_, args) => resolver(models.Miner, args),
    },

this gives the the following error

Server request for query `AppHomeRoute` failed for the following reasons:

1. Cannot return null for non-nullable field MinerConnection.pageInfo.
   de{id,name},cursor},pageInfo{hasNextPage,hasPreviousPage}},i

Is this designed to work with the :first and :find and :limit args that are needed for the graphql edge connection spec?

attributeFields does not support DECIMAL type

Thanks for your work on this library! It was a big reason on why I chose sequalize for my graphql experiments, and I look forward to using it.

I happened to stumble upon this case, don't know if it's a bug or a feature: I tried to supply a Sequelize model with a DECIMAL type to attributeFields but it doesn't support it. All I get is

Unable to convert DECIMAL to a GraphQL type

I'm not quite sure what type should a decimal be converted to automatically. Maybe to a string, like dates are converted?

Support GraphQL fragments

Currently graphql-sequelize will fail if a query contains a fragment. For example:

query RootQueryType { post(id: 1) { title, ...contentFrag }} fragment contentFrag on Post { content }

Will initially fail with an error:

TypeError: Cannot read property 'reduce' of undefined
    at selections.reduce.fields (/Users/[redacted]/API/node_modules/graphql-sequelize/lib/simplifyAST.js:44:53)
    at Array.reduce (native)
    at simplyAST (/Users/[redacted]/node_modules/graphql-sequelize/lib/simplifyAST.js:30:21)
    at resolver (/Users/[redacted]/node_modules/graphql-sequelize/lib/resolver.js:63:50)
    at resolveField (/Users/[redacted]/node_modules/graphql/execution/execute.js:399:18)

Because:

  simpleAST.fields[key].args = selection.arguments.reduce(function (args, arg) {
      args[arg.name.value] = arg.value.value;
      return args;
    }, {});

Assumes that a field will have an arguments property, while fragmentSpread will not.

Couldnt find anything that works with graphql-relay

Your library seems to work with graphql but not graphql-relay, at least not with the examples I saw.

I was able to write up something pretty light weight that works well with graphql-relay, out of the box.

I could not figure out how to use your library, I kept getting errors whenever I tried to use it with graphql-relay connectionArgs, and connectionFromArray, etc. resolver does not seem to work.

So after spending a couple hours hitting a wall I figured I'd just make something.

**
 * Returns a promise that will resolve into an array by using the Model class
 * static findAll method.
 */
export const getModelsByClass = (model) => {
  return model.findAll();
};


/**
 * Returns a new promise containing a new array with only consumable data.
 */
export const getArrayData = (models) => {
  return new Promise((resolve, reject) => {
    try {
      resolve(resolveArrayData(models));
    } catch (error) {
      reject(error);
    }
  });
};

/**
 * Returns a promise that will resolve into a relay-compatible array of the
 * given Model Class.
 */
export const getArrayByClass = (model) => {
  return new Promise((resolve, reject) => {
    try {
      resolve(getArrayData(getModelsByClass(model)));
    } catch (error) {
      reject(error);
    }
  });
};


/**
 * Returns an array of Models by using the Model class static findAll method.
 */
export const resolveModelsByClass = async (model) => {
  return await model.findAll();
};


/**
 * Returns a new array containing only consumable data from a model array.
 * @param models Array<Model>
 * @returns Array<Object>
 */
export const resolveArrayData = (models) => {
  let array = models.map(model => {
    return Object.assign({}, {
        type: model.type
      }, {
        ...model.dataValues
      }
    );
  });
  return [].concat(...array);
};


/**
 * Returns a new array containing only consumable data from a model Class.
 * @param model
 * @returns Array<Object>
 */
export const resolveArrayByClass = async (model) => {

  let models = await resolveModelsByClass(model);
  return resolveArrayData(models);

};

You can now use connectionArgs nodeConnections etc

      people: {
        decription: 'People',
        type: personConnection,
        args: connectionArgs,
        resolve: (root, args) =>
          connectionFromPromisedArray(resolveArrayByClass(Person), args)
      },

I even have some unit test that prove it:

Imgur

I have a separate repo here, https://github.com/MattMcFarland/sequelize-relay - it does not work with mutations yet, at least its not set up to. I am thinking it might be worth it to maybe modify graphql-sequelize instead

How to support querying for null?

GraphQL doesn't have a null literal, it relies on the defaultValue for the args input field.
Is there a recommended literal value in graphql-sequelize that is used to interpret as null by the resolver function? What's the best way to support querying for null?

Help with n-to-m through association schema mapping

Hi. I'm trying to build a schema over a model with an n-to-m relation through an association model. Basically, Person travels in sequence to many Destinations. I condensed an example of it at a gist.

When I query through the associated object, I'm able to get the Destinations in sequence, but that means I have an extra level on the query, and the relation with hasMany and belongsTo in Sequelize has to be mapped instead of the n:m association:

{
  travel {
    people(name: "Jane") {
      name
      personDestinations(order: "sequence") {
        destination {
          id
          name
        }
      }
    }
  }
}
PersonModel.PersonDestinations = PersonModel.hasMany(PersonDestinationModel, {
  foreignKey: 'personId', as: 'Destinations'
});
PersonDestinationModel.Destination = PersonDestinationModel.belongsTo(DestinationModel, {
  foreignKey: 'destinationId', as: 'Destination'
});

When using n:m directly the query has one less level, I can mappings the relation with belongsToMany, but I couldn't figure out a way to use the association attributes to order it:

{
  travel {
    people(name: "Jane") {
      name
      destinations {
        id
        name
      }
    }
  }
}
PersonModel.Destinations = PersonModel.belongsToMany(DestinationModel, {
  through: PersonDestinationModel, foreignKey: 'personId', as: 'Destinations'
});

Would you suggest a more elegant way to build the schema for this relation? Thank you in advance. Bye!

support multiple selections for the same field

A query like:

{ 
  user(id: ${user.id}) {
    name
    tasks {
      title
    }
    tasks {
      id
    }
  }
}

is not supported right now because of how we work with the AST right now.
Ideally we need to write a function that can better merge selection sets.

Nested hasMany List types, args don't work properly

When you have nested relationships of a List type, the limit, order and offset args don't work. It seems order isn't applied, and limit creates an incorrect SQL query

Given a GraphQL Query like this-

{
  group(group_id: 5017) {
    deleted
    group_activity_logs(limit:10,offset:4) {
      log_date
    }
  }
}

results in a SQL query like this -

Executing (default): SELECT `group_activity_log`.* FROM ((SELECT `log_date`, `group_activity_log_id`,
`group_id` FROM `group_activity_log` AS `group_activity_log` WHERE `group_activity_log`.`group_id` = 
5017 LIMIT '10')) AS `group_activity_log`;

typeMapper returned ENUM type missing name

Every GraphQL object is required to have a unique name iirc, but the returned GraphQLEnumType from the typeMapper doesnt provide one.

The first GraphQLEnumType created with name undefined by the typeMapper will not throw an error, but the second time it'll give you an error saying that a GraphQL object with name undefined already exists.

BigInt columns should be GraphQLFloat

Wanted to get your thoughts on this issue we just ran into.

The graphql spec limits GraphQLInt values to 2^31.

 const num = parseInt(ast.value, 10);
  if (num <= MAX_INT && num >= MIN_INT) {
    return num;
  }

However, we are converting BigInt columns to GraphQLInt, which silently returns those over 2^31 as null. Changing this type mapping to GraphQLFloat (which feels slightly wrong, since they are not floating point numbers) allows the numbers to be not-null (at least up to the JS limit of 2^53. Thoughts?

Support for relay global ids

Because attributeFields defines the id field with a non-null Int (or whatever the primary key is), I always have do this in my GraphQLObjectTypes to get it to play nice with Relay:

  fields: _.assign(attributeFields(MyModel), {
    id: globalIdField('MyModel'),
    _id: {
      type: GraphQLInt,
      resolve: model => model.id,
    },
  }),

This is repeated code, and I'd like to get rid of it. If there's no current way to do this, I'd be willing to make a pull request with a proposed solution.

Ambigious column referenced on viewer level connection

I have a viewer level connection defined and a query that is giving me a SQL error. I can see the issue - "Post"."createdAt" is being defined twice by this query. I just have no idea what to do to fix it. If i remove tags + categories from the search there is no problem. removing Author subquery doesn't do anything if both of those are still there. Let me know if you need my model definitions. I'll summarize them here.

Model summary

Post: (defined used a package that has decorators)

@belongsTo('User', {as: 'author', foreignKey: 'authorId' })
@belongsTo('Media', {as: 'featuredPostImage', foreignKey: 'featuredPostImageId' })
@belongsToMany('Tag', {through: 'postTags', as: 'tags'})
@belongsToMany('Category', {through: 'postCategories', as: 'categories'})
Sequelize Connection Definition
  const whereFn = (key, value) => ({[key]: value});
  const viewerPostsConnection = sequelizeConnection({
    name: 'viewerPosts',
    nodeType: postType,
    target: Post,
    orderBy: new GraphQLEnumType({
      name: 'viewerPostsOrderBy',
      values: {
        AGE: {value: ['id', 'DESC']}
      }
    }),
    where: whereFn,
    connectionFields: {
      total: {
        type: GraphQLInt,
        resolve: ({where}) => Post.count({where})
      }
    }
  });
Relay Query
posts(first: 3) {
          edges {
            node {
              id
              title
              content
              description
              tags {
                name
              }
              slug
              categories {
                name
              }
              createdAt
              author {
                firstName
                lastName
                role
              }
            }
          }
        }
Generated SQL
SELECT "Post".*, 
"tags"."id" AS "tags.id", "tags"."name" AS "tags.name", "tags"."slug" AS "tags.slug", 
"tags"."description" AS "tags.description", "tags"."createdAt" AS "tags.createdAt", 
"tags"."updatedAt" AS "tags.updatedAt", "tags.postTags"."createdAt" AS "tags.postTags.createdAt",
"tags.postTags"."updatedAt" AS "tags.postTags.updatedAt", 
"tags.postTags"."TagId" AS "tags.postTags.TagId", "tags.postTags"."PostId" AS "tags.postTags.PostId",
"categories"."id" AS "categories.id", "categories"."name" AS "categories.name",
"categories"."slug" AS "categories.slug", "categories"."description" AS "categories.description",
"categories"."createdAt" AS "categories.createdAt", "categories"."updatedAt" AS "categories.updatedAt",
"categories.postCategories"."createdAt" AS "categories.postCategories.createdAt",
"categories.postCategories"."updatedAt" AS "categories.postCategories.updatedAt",
"categories.postCategories"."CategoryId" AS "categories.postCategories.CategoryId",
"categories.postCategories"."PostId" AS "categories.postCategories.PostId", "author"."id" AS "author.id",
"author"."wordpressId" AS "author.wordpressId", "author"."wpUpdatedAt" AS "author.wpUpdatedAt",
"author"."email" AS "author.email", "author"."emailVerified" AS "author.emailVerified",
"author"."gravatar" AS "author.gravatar", "author"."firstName" AS "author.firstName",
"author"."lastName" AS "author.lastName", "author"."type" AS "author.type",
"author"."accessLevel" AS "author.accessLevel", "author"."role" AS "author.role",
"author"."staffData" AS "author.staffData", "author"."lastLogin" AS "author.lastLogin",
"author"."authZeroIds" AS "author.authZeroIds", "author"."sfid" AS "author.sfid",
"author"."sfType" AS "author.sfType", "author"."hubspotId" AS "author.hubspotId",
"author"."lastIp" AS "author.lastIp", "author"."loginCount" AS "author.loginCount",
"author"."locale" AS "author.locale", "author"."createdAt" AS "author.createdAt",
"author"."updatedAt" AS "author.updatedAt",
"author"."staffFunnyPhotoId" AS "author.staffFunnyPhotoId",
"author"."staffPhotoId" AS "author.staffPhotoId",
"author"."profileImageId" AS "author.profileImageId"
FROM (
    SELECT "Post"."id", "Post"."wordpressId", "Post"."wpUpdatedAt", "Post"."pageTitle", "Post"."slug",
    "Post"."description", "Post"."metatags", "Post"."title", "Post"."content", "Post"."publish", "Post"."status",
    "Post"."createdAt", "Post"."updatedAt", "Post"."featuredPostImageId", "Post"."authorId",
    "Post"."createdAt", COUNT(*) OVER() AS "full_count"
     FROM "Posts" AS "Post"
     ORDER BY "Post"."createdAt" DESC, "Post"."id" ASC 
     LIMIT 3
) AS "Post"
LEFT OUTER JOIN (
    "postTags" AS "tags.postTags"
    INNER JOIN "Tags" AS "tags"
    ON "tags"."id" = "tags.postTags"."TagId"
) ON "Post"."id" = "tags.postTags"."PostId"
LEFT OUTER JOIN (
    "postCategories" AS "categories.postCategories"
     INNER JOIN "Categories" AS "categories"
     ON "categories"."id" = "categories.postCategories"."CategoryId"
) ON "Post"."id" = "categories.postCategories"."PostId"
LEFT OUTER JOIN "Users" AS "author"
ON "Post"."authorId" = "author"."id"
ORDER BY "Post"."createdAt" DESC, "Post"."id" ASC;
Error response
{
  "data": {
    "viewer": {
      "posts": null
    }
  },
  "errors": [
    {
      "message": "column reference \"createdAt\" is ambiguous",
      "locations": [
        {
          "line": 1,
          "column": 9
        }
      ]
    }
  ]
}

Sequelize.VIRTUAL fields are incorrect unless dependent data is explicitly fetched

Sequelize.VIRTUAL fields don't seem to work correctly and data is undefined unless explicitly fetched.

Given a model like this:

sequelize.define('User', {
  firstName: {
    type: Sequelize.STRING,
    allowNull: false,
  },
  lastName: {
    type: Sequelize.STRING,
    allowNull: false,
  },
  fullName: {
    type: Sequelize.VIRTUAL,
    get: function(): ?string { return `${this.firstName} ${this.lastName}`; },
  },
}, {});

👉 And a row in the database with firstName = Anonymous and lastName = Anonymous.

With a GraphQL type of:

var userType = new GraphQLObjectType({
  name: 'User',
  description: 'A person who uses the system.',
  fields: () => Object.assign(
    attributeFields(User),
    {
      id: globalIdField('User'),
    },
  ),
  interfaces: [nodeInterface],
});

With a GraphQL query schema like this:

users: {
  type: new GraphQLList(userType),
  resolve: resolver(User),
},

Performing the following query:

{ users {
   id
   firstName,
   fullName,
}}

🪲🪲🪲 Incorrectly gives me: 🪲🪲🪲

 {
    "data": {
      "users": [
        {
          "id": "VXNlcjox",
          "firstName": "Anonymous",
          "fullName": "Anonymous undefined"
        },
    ]
  }
}

If I add lastName to the query fields:

{ users {
   id
   firstName,
   lastName,
   fullName,
}}

It works correctly:

 {
    "data": {
      "users": [
        {
          "id": "VXNlcjox",
          "firstName": "Anonymous",
          "lastName": "Anonymous",
          "fullName": "Anonymous Anonymous"
        },
    ]
  }
}

This seems like a pretty bad "bug" as you need to know the implementation of all VIRTUAL methods in order to make sure they return a valid value 🔮 .

resolver should support proxy resolver functions

If the resolver for a nested field has a $proxy element (like we currently add $association) ourselves, it should recursively follow $proxy to find $association for prefetching.

We probably need to ensure we also call the $proxy to get the proper args.

defaultArgs additions

It would be great to have defaultArgs have the ability have a general "where" clause included for more advanced filtering. Something like-

{
  users(where: {
    user_id:1,
    lockedEndDate:
      {
        $gte:"1/1/2016"
      }
    })
}

Not sure if it can be a GraphQLObject, or if it will have to be a string..

root query field (viewer) as connection

Hi,

I am not much familiar sequelize and not sure how to define the schema to achieve what is described below.

I have a rootQuery, where the fields are all connections. So, in relay.test.js, it is like

schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'RootQueryType',
        fields: {
          user: {
           ......
          },
          users: {
            type: userConnection.connectionType,
            args: connectionArgs,
            resolve: resolver(User)
          },
          project: {
           ....
          },
          node: nodeField
        }
      })
    });

  });

so I modify from List to Connection to support pagination.

but the test fails

it.only('connections', function () {
    var users = this.users;

    return graphql(schema, `
      {
        users {
          edges {
            node {
              name
            }
          }
        }
      }
    `).then(function (result) {
      console.log(result.data.users);

      if (result.errors) throw new Error(result.errors[0].stack);

      expect(result.data.users.edges.length).to.equal(users.length);
      result.data.users.edges.forEach(function (edge) {
        var user = edge.node;
        expect(user.tasks.edges).to.have.length.above(0);
      });


    });
  });

the console log says


  relay
{ edges: null }
    1) connections


  0 passing (229ms)
  1 failing

  1) relay connections:
     TypeError: Cannot read property 'length' of null
      at test/relay.test.js:482:37

What can I do to support the root query (typically viewer) has some fields which are connection?

Reverse ordering

Is there currently any way to do reverse ordering using the order attribute?

If not, what about something like order: "reverse_fieldname" to order DESC?
(got that idea from Facebook's Graph API)

Create a changelog.md

Would be helpful on deciding to approve pull requests on our projects that are upgrading this module.

Not sure if an issue or maybe something I'm not getting:

module.exports = (sequelize, DataTypes) => {
  let Speaker = sequelize.define('Speaker', { // eslint-disable-line
    name: { type: DataTypes.STRING(128), allowNull: false }, // eslint-disable-line
    description: DataTypes.STRING,
    position: DataTypes.STRING(128), // eslint-disable-line
    company: DataTypes.STRING(128), // eslint-disable-line
  }, {
    classMethods: {
      associate: (models) => {
        Speaker.Presentations = Speaker.hasMany(models.Presentation, { as: 'presentations' });
      },
    },
  });

  return Speaker;
};
module.exports = (sequelize, DataTypes) => {
  const Presentation = sequelize.define('Presentation', {
    title: { type: DataTypes.STRING(128), allowNull: false }, // eslint-disable-line
    description: DataTypes.STRING,
  }, {
    classMethods: {
      associate: (models) => {
        Presentation.Speaker = Presentation.belongsTo(models.Speaker, {
          as: 'speaker',
          foreignKey: 'speakerId',
          onDelete: 'CASCADE',
        });
      },
    },
  });

  return Presentation;
};
'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const sequelize = new Sequelize('xxxxx', 'xxxxx', 'xxxxx', {
  host: 'localhost',
  dialect: 'mariadb',
});

let db = {};

fs
  .readdirSync(__dirname)
  .filter((file) => (file.indexOf('.') !== 0) && (file !== 'index.js'))
  .forEach((file) => {
    const model = sequelize.import(path.join(__dirname, file));
    db[model.name] = model;
  });

Object.keys(db).forEach((modelName) => {
  if ('associate' in db[modelName]) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;
import { resolver } from 'graphql-sequelize';
import {
  GraphQLObjectType,
  GraphQLNonNull,
  GraphQLString,
  GraphQLInt,
} from 'graphql';

import { Presentation } from '../models';
import speakerType from './speakerType';


const presentationType = new GraphQLObjectType({
  name: 'Presentation',
  description: 'A Presentation',
  fields: {
    id: {
      type: new GraphQLNonNull(GraphQLInt),
      description: 'Presentation Id',
    },
    title: {
      type: new GraphQLNonNull(GraphQLString),
      description: 'Presentation Title',
    },
    description: {
      type: GraphQLString,
      description: 'Presentation Description',
    },
    speaker: {
      type: speakerType,
      resolve: resolver(Presentation.Speaker),
    },
  },
});

export default presentationType;

error:

Error: Presentation.speaker field type must be Output Type but got: undefined.

Can't seem to get references to work

newEdge mutation helper

When doing a mutation that results in an edge it would be great to have a sequelize helper to do the heavy lifting in terms of formatting it to the connection.

Ordering uses attribute name as field name

Hi. I noticed that when ordering by an attribute that has a different field name the resulting SQL query does not use the field name but the given attribute. For example, the query:

{
  travel {
    people(order: "name") {
      id
      name
    }
  }
}

for the example server below gives the error:

{
  "data": {
    "travel": {
      "people": null
    }
  },
  "errors": [
    {
      "message": "Invalid column name 'name'.",
      "locations": [
        {
          "line": 3,
          "column": 5
        }
      ]
    }
  ]
}

and logs the SQL query (on SQL Server):

SELECT [id], [fullName] AS [name] FROM [LegacyPeople] AS [Person] ORDER BY [Person].[name];

While the query:

{
  travel {
    people(order: "fullName") {
      id
      name
    }
  }
}

works fine.

This is the example server I was using.

import _ from 'underscore';
import Promise from 'bluebird';
import {
  GraphQLInt,
  GraphQLList,
  GraphQLObjectType,
  GraphQLSchema,
  GraphQLString
} from 'graphql';
import {
  resolver,
  attributeFields,
  defaultArgs,
  defaultListArgs
} from 'graphql-sequelize';
import Sequelize from 'sequelize';
import express from 'express';
import graphqlHTTP from 'express-graphql';
import cors from 'cors';

const peopleData = [
  {id: 1, name: 'John'},
  {id: 2, name: 'Jane'},
  {id: 3, name: 'Jill'}
];

const sequelize = new Sequelize(process.env.DB);

const PersonModel = sequelize.define('Person', {
  name: {type: Sequelize.STRING, field: 'fullName'}
}, {
  tableName: 'LegacyPeople'
});

const PersonType = new GraphQLObjectType({
  name: 'Person',
  fields: () => (_.assign(attributeFields(PersonModel)))
});

const travelType = new GraphQLObjectType({
  name: 'Travels',
  fields: {
    people: {
      type: new GraphQLList(PersonType),
      resolve: resolver(PersonModel),
      args: _.assign(defaultListArgs(), {
        name: {type: GraphQLString}
      })
    }
  }
});

const rootSchema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      travel: {
        type: travelType,
        resolve: () => (true)
      }
    }
  })
});

const app = express();
app.use(cors());
app.use('/graphql', graphqlHTTP({
  schema: rootSchema,
  pretty: true,
  graphiql: true
}));

// exploded models drop and sync, and data insert for clarity

Promise.each(
  [
    PersonModel
  ],
  (Model) => Model.drop()
).then(() => (
  Promise.each(
    [
      {Model: PersonModel, data: peopleData},
    ],
    (pair) => {
      const {Model, data} = pair;
      return Model.sync().then(() => (
        Promise.each(data, (entry) => (
          Model.create(entry)
        ))
      ));
    }
  )
)).then(() => {
  const server = app.listen(process.env.PORT || 3000, () => {
    console.log(
      'Example app listening at http://%s:%s',
      server.address().address,
      server.address().port
    );
  });
});

Add support for the rest of sequelizeTypes

We've run into an error where the getAttributes method fails if any of our attributes are ENUM or JSONB, for example.

typeMapper doesn't seem to check for those types. Do you think it'd be much work to get those supported as well? I'd be happy to give it a crack myself.

build lib to root and move publishing to travis

babel should publish to project root rather than /lib to allow users to do stuff like `import 'graphql-sequelize/relay';

publishing should be moved to travis.ci so local environment won't be polluted by root files.

ENUM characters ¼ ½ ¾

An empty value is returned when splitting on ¼ ½ ¾ and an error is thrown on https://github.com/mickhansen/graphql-sequelize/blob/master/src/typeMapper.js#L95

const specialChars = /[^a-z\d_]/i;
const value = '¾'

sanitizedValue = value
.split(specialChars)
.reduce((reduced, val, idx) => {
    let newVal = val;
    if (idx > 0) {
        console.log(idx, val)
        newVal = `${val[0].toUpperCase()}${val.slice(1)}`;
    }
    return `${reduced}${newVal}`;
})

https://jsfiddle.net/RichAyotte/x25qm45r/

<table>.get<childtable>() not working

I have the following code.
Person can have many Post
I have already freeze my postgres table name

RwPost ->arPerson:->return arRec.getTbPerson(); [Is working well]
RwPerson->ArPost-> new GraphQLList(RwPost),->arRec.getTbPost(); [it says arRec.getTbPost is undefined]

what is wrong here?

import {
GraphQLObjectType,
GraphQLInt,
GraphQLString,
GraphQLList,
GraphQLSchema,
GraphQLNonNull,
} from 'graphql';

import Db from './db';

const RwPerson = new GraphQLObjectType({
name: 'RwPerson',
description: 'This represent a person',
fields: () => {
return {
id: {
type: GraphQLInt,
resolve(arRec) {
return arRec.id;
}
},
vcFirstName: {
type: GraphQLString,
resolve(arRec) {
return arRec.vcFirstName;
}
},
ArPost: {
type: new GraphQLList(RwPost),
resolve(arRec){
return arRec.getTbPost();
}
},
}
}
});

const RwPost = new GraphQLObjectType({
name: 'RwPost',
description: 'This represent a Post related to a person',
fields: () => {
return {
id: {
type: GraphQLInt,
resolve(arRec) {
return arRec.id;
}
},
vcTitle: {
type: GraphQLString,
resolve(arRec) {
return arRec.vcTitle;
}
},
arPerson:{
type: RwPerson,
resolve(arRec){
return arRec.getTbPerson();
},
},
}
}
});

const Query = new GraphQLObjectType({
name: 'Query',
description: 'this is a root query',
fields: () => {
return {
qrPerson: {
type: new GraphQLList(RwPerson),
args: {
id: {
type: GraphQLInt,
},
vcEmail: {
type: GraphQLString,
},
},
resolve(root, args){
return Db.models.TbPerson.findAll({where: args});
},
},
qrPost: {
type: new GraphQLList(RwPost),
resolve(root, args){
return Db.models.TbPost.findAll({where: args});
}
},
}
},
});

const Schema = new GraphQLSchema({
query: Query,
});

Support for aggregation functions and grouping

I would like to add some special case GraphQLObjectTypes that include aggregate functions and group by. The original sequelize query that I'm trying to model in GraphQL is something like this:

   Customers.findAll({
        attributes: ["Name",[Sequelize.fn('COUNT',Sequelize.col("[Orders].[OrderId]")),"OrderCount"]],
        include:[{ association:customerOrders,attributes:[]}],
        group:["Name"]
);

I could pull down the all the child entities and do the count on the server-side, but that is wasteful and impractical if there are 1000s of rows. So, in graph-sequelize there are 2 or 3 problems with modeling special case entities that include aggregate functions:

  • No way to extend the attributeFields to include aggregate functions.
  • No way to stipulate a group clause in the options
  • There are some assumptions in the code that the attribute list will always include the joined fields for the association. The empty array above removes them, but that seems to break the GraphQL sequelize resolver for associations.

Any thoughts, or am I doing this the wrong way?

Support For Mutations

Need to add support for mutations to the resolver, or build a new resolver for mutations. I plan on working on this feature, but if you have any input on the best way to achieve this @mickhansen I'm open to ideas.

first/last, before/after support

It would be great to support the Relay connection args out of the box for users.
first/last would simply require the user to select what field to order on.

before/after is a little more tricky, we can't simply do a comparison on ID since the order might be on something that doesn't align with the ID.

We might need to do something like [field] > (SELECT [field] FROM [table] WHERE id = [after] LIMIT 1) which might kill performance slightly.

/cc @brad-decker

Map known String attributes in typeMapper

I want to get your thoughts on mapping some column types, that sequelize doesn't have a concrete type for.

For example, on my project we are running into string column types in our models for things like -

'longblob'
'char(72)'

With things like 'longblob', we can exclude that value, but it would be nice to supply a custom typeMapper for things like 'char' that tells graphql-sequelize to make it a string type. Something like -

let autoMapped = attributeFields(Models.SomeLegacyThingWithChar,{
  typeMap:{
    'char(72)': GraphQLString,
    'longblob': GraphQLString
  }
});

I can work on this, if you think it is a useful addition.

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.