Skip to content

Commit 24b0a57

Browse files
committed
Merge pull request #18 from ConciergeAuctions/master
Add support for GraphQL-Relay Connections
2 parents c2e2c65 + 57adbf0 commit 24b0a57

File tree

8 files changed

+449
-5
lines changed

8 files changed

+449
-5
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ pids
77
*.pid
88
*.seed
99

10+
.idea
11+
1012
# Directory for instrumented libs generated by jscoverage/JSCover
1113
lib-cov
1214

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
},
4545
"dependencies": {
4646
"babel-runtime": "^5.6.20",
47+
"graphql-relay": "^0.3.2",
4748
"lodash": "^3.10.0"
4849
}
4950
}

src/attributeFields.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as typeMapper from './typeMapper';
2-
import { GraphQLNonNull } from 'graphql';
2+
import { GraphQLNonNull, GraphQLEnumType } from 'graphql';
33

44
module.exports = function (Model, options) {
55
options = options || {};
@@ -14,6 +14,10 @@ module.exports = function (Model, options) {
1414
type: typeMapper.toGraphQL(type, Model.sequelize.constructor)
1515
};
1616

17+
if ( memo[key].type instanceof GraphQLEnumType ) {
18+
memo[key].type.name = `${Model.name}${key}EnumType`;
19+
}
20+
1721
if (attribute.allowNull === false || attribute.primaryKey === true) {
1822
memo[key].type = new GraphQLNonNull(memo[key].type);
1923
}

src/generateIncludes.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import argsToFindOptions from './argsToFindOptions';
2+
import {isConnection} from './relay';
23
import _ from 'lodash';
34

45
function inList(list, attribute) {
@@ -96,5 +97,15 @@ export default function generateIncludes(simpleAST, type, root, options) {
9697
}
9798
});
9899

100+
if (isConnection(type)) {
101+
let node = simpleAST.fields.edges.fields.node;
102+
let fields = [];
103+
_.forIn(node.fields, (field, key) => {
104+
if (!field.fields.hasOwnProperty('edges')) {
105+
fields.push(key);
106+
}
107+
});
108+
result.attributes = result.attributes.concat(fields);
109+
}
99110
return result;
100111
}

src/relay.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import {fromGlobalId, connectionFromArray, nodeDefinitions} from 'graphql-relay';
2+
3+
class NodeTypeMapper {
4+
5+
constructor( sequelize ) {
6+
this.models = Object.keys(sequelize.models);
7+
this.models.forEach(model => {
8+
this[model] = null;
9+
});
10+
}
11+
12+
mapTypes( types ) {
13+
this.models.forEach(model => {
14+
if (types[model]) {
15+
this[model] = types[model];
16+
}
17+
});
18+
}
19+
}
20+
21+
export function idFetcher(sequelize) {
22+
return globalId => {
23+
let {type, id} = fromGlobalId(globalId);
24+
const models = Object.keys(sequelize.models);
25+
type = type.toLowerCase();
26+
if (models.some(model => model === type)) {
27+
return sequelize.models[type].findById(id);
28+
}
29+
return null;
30+
};
31+
}
32+
33+
export function isConnection(type) {
34+
return typeof type.name !== 'undefined' && type.name.endsWith('Connection');
35+
}
36+
37+
export function handleConnection(values, args) {
38+
return connectionFromArray(values, args);
39+
}
40+
41+
export function sequelizeNodeInterface(sequelize) {
42+
let nodeTypeMapper = new NodeTypeMapper(sequelize);
43+
const nodeObjects = nodeDefinitions(idFetcher(sequelize), obj => {
44+
return nodeTypeMapper[obj.Model.options.name.singular];
45+
});
46+
return {
47+
nodeTypeMapper,
48+
...nodeObjects
49+
};
50+
}

src/resolver.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import _ from 'lodash';
33
import simplifyAST from './simplifyAST';
44
import generateIncludes from './generateIncludes';
55
import argsToFindOptions from './argsToFindOptions';
6+
import { isConnection, handleConnection } from './relay';
67

78
function inList(list, attribute) {
89
return ~list.indexOf(attribute);
@@ -25,6 +26,9 @@ module.exports = function (target, options) {
2526

2627
resolver = function (source, args, info) {
2728
if (association && source.get(association.as) !== undefined) {
29+
if (isConnection(info.returnType)) {
30+
return handleConnection(source[info.fieldName], args);
31+
}
2832
return source.get(association.as);
2933
}
3034

@@ -66,6 +70,9 @@ module.exports = function (target, options) {
6670

6771
if (association) {
6872
return source[association.accessors.get](findOptions).then(function (result) {
73+
if (isConnection(info.returnType)) {
74+
result = handleConnection(result, args);
75+
}
6976
return options.after(result, args, root, {
7077
ast: simpleAST,
7178
type: type

test/attributeFields.test.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import {
1919

2020
describe('attributeFields', function () {
2121
var Model;
22-
22+
var modelName = Math.random().toString();
2323
before(function () {
24-
Model = sequelize.define(Math.random().toString(), {
24+
Model = sequelize.define(modelName, {
2525
email: {
2626
type: Sequelize.STRING,
2727
allowNull: false
@@ -38,6 +38,9 @@ describe('attributeFields', function () {
3838
enum: {
3939
type: Sequelize.ENUM('first', 'second')
4040
},
41+
enumTwo: {
42+
type: Sequelize.ENUM('first', 'second')
43+
},
4144
list: {
4245
type: Sequelize.ARRAY(Sequelize.STRING)
4346
},
@@ -55,7 +58,7 @@ describe('attributeFields', function () {
5558
it('should return fields for a simple model', function () {
5659
var fields = attributeFields(Model);
5760

58-
expect(Object.keys(fields)).to.deep.equal(['id', 'email', 'firstName', 'lastName', 'float', 'enum', 'list', 'virtualInteger', 'virtualBoolean']);
61+
expect(Object.keys(fields)).to.deep.equal(['id', 'email', 'firstName', 'lastName', 'float', 'enum', 'enumTwo', 'list', 'virtualInteger', 'virtualBoolean']);
5962

6063
expect(fields.id.type).to.be.an.instanceOf(GraphQLNonNull);
6164
expect(fields.id.type.ofType).to.equal(GraphQLInt);
@@ -69,6 +72,8 @@ describe('attributeFields', function () {
6972

7073
expect(fields.enum.type).to.be.an.instanceOf(GraphQLEnumType);
7174

75+
expect(fields.enumTwo.type).to.be.an.instanceOf(GraphQLEnumType);
76+
7277
expect(fields.list.type).to.be.an.instanceOf(GraphQLList);
7378

7479
expect(fields.float.type).to.equal(GraphQLFloat);
@@ -80,9 +85,19 @@ describe('attributeFields', function () {
8085

8186
it('should be possible to exclude fields', function () {
8287
var fields = attributeFields(Model, {
83-
exclude: ['id', 'email', 'float', 'enum', 'list', 'virtualInteger', 'virtualBoolean']
88+
exclude: ['id', 'email', 'float', 'enum', 'enumTwo', 'list', 'virtualInteger', 'virtualBoolean']
8489
});
8590

8691
expect(Object.keys(fields)).to.deep.equal(['firstName', 'lastName']);
8792
});
93+
94+
it('should automatically name enum types', function () {
95+
var fields = attributeFields(Model);
96+
97+
expect(fields.enum.type.name).to.not.be.undefined;
98+
expect(fields.enumTwo.type.name).to.not.be.undefined;
99+
100+
expect(fields.enum.type.name).to.equal(modelName + 'enum' + 'EnumType');
101+
expect(fields.enumTwo.type.name).to.equal(modelName + 'enumTwo' + 'EnumType');
102+
});
88103
});

0 commit comments

Comments
 (0)