mickhansen / graphql-sequelize Goto Github PK
View Code? Open in Web Editor NEWGraphQL & Relay for MySQL & Postgres via Sequelize
License: MIT License
GraphQL & Relay for MySQL & Postgres via Sequelize
License: MIT License
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.
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.
Blocked by sequelize/sequelize#4690
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.
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!
Enum Value object names can't contain spaces, special characters etc. Already have a fix ready to push but logging the error for version history etc.
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.
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.
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?
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?
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?
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.
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:
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
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?
plan on releasing this soon via npm ? or should I patch the package locally ?
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!
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.
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`;
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.
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?
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.
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.
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'})
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})
}
}
});
posts(first: 3) {
edges {
node {
id
title
content
description
tags {
name
}
slug
categories {
name
}
createdAt
author {
firstName
lastName
role
}
}
}
}
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;
{
"data": {
"viewer": {
"posts": null
}
},
"errors": [
{
"message": "column reference \"createdAt\" is ambiguous",
"locations": [
{
"line": 1,
"column": 9
}
]
}
]
}
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 🔮 .
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
.
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..
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?
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)
Would be helpful on deciding to approve pull requests on our projects that are upgrading this module.
The resulting SQL calls were still using all the target attributes, so I dug into the code and saw that filterAttributes was being set to false all the time.
relevant line here - https://github.com/mickhansen/graphql-sequelize/blob/master/src/resolver.js#L27
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
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.
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
);
});
});
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.
I believe AST.selectionSet has been removed or renamed.
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.
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}`;
})
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,
});
I would like to add some special case GraphQLObjectType
s 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:
attributeFields
to include aggregate functions.Any thoughts, or am I doing this the wrong way?
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.
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
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.