├── docs ├── guides │ └── guides.md ├── graphql │ ├── graphql.jpg │ ├── graphql-relay.png │ ├── graphql.md │ ├── relay.svg │ ├── using_nodedefinitions.md │ └── using_connections.md ├── sequelize │ ├── sequelize.png │ ├── sequelizemd.md │ └── quick_setup.md ├── connecting_to_react_md.md ├── creating_graphql_schema_md.md ├── setup.md ├── References │ ├── summarymd.md │ ├── module_imports.md │ ├── connection_patterns.md │ └── nodedefinitions.md ├── connecting_graphql_and_relay_md.md ├── creating_a_database.md └── methods │ ├── SUMMARY.md │ ├── resolveArrayByClass.md │ ├── getModelsByClass.md │ ├── resolveModelsByClass.md │ ├── resolveArrayData.md │ └── getArrayData.md ├── cover.jpg ├── .babelrc ├── sequelize ├── db.fixture.sqlite ├── README.md ├── config │ └── config.json ├── index.js ├── models │ ├── Article.js │ ├── index.js │ └── Person.js ├── seed.js ├── __tests__ │ └── database.js ├── rawPeople.json └── schema.js ├── scripts ├── travis.sh ├── babelRelayPlugin.js ├── mocha-bootload.js ├── updateSchema.js ├── watch-seed.js └── watch.js ├── .flowconfig ├── src ├── index.js └── data │ ├── methods.js │ └── __tests__ │ └── connections.js ├── .travis.yml ├── book.json ├── GLOSSARY.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── SUMMARY.md ├── PATENT ├── LICENSE ├── package.json ├── INTRO.md ├── README.md ├── .eslintrc └── .gitignore /docs/guides/guides.md: -------------------------------------------------------------------------------- 1 | # Guides 2 | 3 | -------------------------------------------------------------------------------- /cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattMcFarland/sequelize-relay/HEAD/cover.jpg -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"], 3 | "plugins": ["transform-flow-strip-types"] 4 | } -------------------------------------------------------------------------------- /docs/graphql/graphql.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattMcFarland/sequelize-relay/HEAD/docs/graphql/graphql.jpg -------------------------------------------------------------------------------- /docs/sequelize/sequelize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattMcFarland/sequelize-relay/HEAD/docs/sequelize/sequelize.png -------------------------------------------------------------------------------- /sequelize/db.fixture.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattMcFarland/sequelize-relay/HEAD/sequelize/db.fixture.sqlite -------------------------------------------------------------------------------- /docs/graphql/graphql-relay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MattMcFarland/sequelize-relay/HEAD/docs/graphql/graphql-relay.png -------------------------------------------------------------------------------- /scripts/travis.sh: -------------------------------------------------------------------------------- 1 | (if [[ "$TRAVIS_JOB_NUMBER" == *.1 ]]; 2 | then npm run cover:lcov; 3 | else npm test; 4 | fi) 5 | -------------------------------------------------------------------------------- /sequelize/README.md: -------------------------------------------------------------------------------- 1 | The Sequelize files here are part of the sequelize-cli pattern structure, so these files are not tested. 2 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/coverage/.* 3 | .*/scripts/.* 4 | .*/node_modules/.* 5 | .*/public/.* 6 | .*/dist/.* 7 | 8 | [include] 9 | 10 | [libs] 11 | 12 | [options] 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 2 | export { 3 | getArrayData, 4 | getModelsByClass, 5 | resolveArrayByClass, 6 | resolveArrayData, 7 | resolveModelsByClass 8 | } from './data/methods'; 9 | -------------------------------------------------------------------------------- /docs/graphql/graphql.md: -------------------------------------------------------------------------------- 1 | # GraphQL-Relay 2 | ![GraphQL Logo](graphql.jpg) 3 | 4 | - [Using nodeDefinitions](using_nodedefinitions.md) 5 | - [Using connections](using_connections.md) 6 | -------------------------------------------------------------------------------- /scripts/babelRelayPlugin.js: -------------------------------------------------------------------------------- 1 | import getBabelRelayPlugin from "babel-relay-plugin"; 2 | 3 | import schema from "../data/schema.json"; 4 | 5 | export default getBabelRelayPlugin(schema.data); 6 | -------------------------------------------------------------------------------- /sequelize/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "dialect": "sqlite", 4 | "storage": "./db.development.sqlite", 5 | "logging": false 6 | }, 7 | "test": { 8 | "dialect": "sqlite", 9 | "storage": "./db.test.sqlite", 10 | "logging": false 11 | } 12 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "6" 5 | - "7" 6 | 7 | notifications: 8 | email: false 9 | 10 | script: 11 | - npm run travis 12 | 13 | after_success: 14 | - if [[ "$TRAVIS_JOB_NUMBER" == *.1 ]]; then cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js; fi 15 | -------------------------------------------------------------------------------- /docs/connecting_to_react_md.md: -------------------------------------------------------------------------------- 1 | # Connecting to React 2 | 3 | 4 | This chapter is not setup yet, you may find the other chapters useful. 5 | 6 | 7 | This section will connect a react application and relay. The final step will have the users using relay on the clientside and all of the serverside will be working properly. -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "structure": { 3 | "readme": "INTRO.md" 4 | }, 5 | "plugins": [ 6 | "editlink" 7 | ], 8 | "pluginsConfig": { 9 | "editlink": { 10 | "base": "https://github.com/MattMcFarland/sequelize-relay/edit/master/", 11 | "label": "Edit This Page", 12 | "multilingual": false 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /docs/sequelize/sequelizemd.md: -------------------------------------------------------------------------------- 1 | # Sequelize 2 | ![Sequelize Logo](sequelize.png) 3 | 4 | [Sequelize](http://docs.sequelizejs.com/en/latest/) is a promise-based ORM for Node.js and io.js. It supports the dialects PostgreSQL, MySQL, MariaDB, SQLite and MSSQL and features solid transaction support, relations, read replication and more. 5 | 6 | [Quick Setup](quick_setup.md) 7 | -------------------------------------------------------------------------------- /docs/creating_graphql_schema_md.md: -------------------------------------------------------------------------------- 1 | # Creating GraphQL Schema 2 | 3 | This chapter is not complete (not started yet) 4 | 5 | In the mean time, please review: 6 | [GraphQL-Relay](graphql/graphql.md) 7 | 8 | 9 | This chapter will go over the addition of GraphiQL, we will then create a simple graphQL schema file that allows us to see data in various graphql shapes. 10 | -------------------------------------------------------------------------------- /sequelize/index.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | 3 | export const models = require('./models/index'); 4 | 5 | export const connect = () => { 6 | return new Promise((resolve, reject) => { 7 | try { 8 | models.sequelize.sync().then(() => { 9 | resolve(models); 10 | }); 11 | } catch (error) { 12 | reject(error); 13 | } 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /docs/setup.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | This Chapter will guide users through setting up a blank package with nodejs. We'll cover installing dependencies, installing Express JS, installing graphql, etc. We'll end this chapter with a confirmed hello world and wide eyes.... 4 | 5 | 1. Setup a new npm project 6 | 2. Run `npm install graphql graphql-relay sequelize sequelize-relay --save-dev` 7 | 8 | Of course, this is WIP. I hope to finish this within the next week. 9 | -------------------------------------------------------------------------------- /docs/References/summarymd.md: -------------------------------------------------------------------------------- 1 | # References 2 | 3 | The following documentation may help you get up to speed. 4 | 5 | - [Module Imports](module_imports.md) - A list of common modules to import into a graphql schema file. 6 | - [nodeDefinitions](nodedefinitions.md) - A quick reference of how to work with nodeDefinitions 7 | - [Connection Patterns](connection_patterns.md) - Common Connection Patterns, used with relationships and nodes. 8 | - [API Methods](../methods/SUMMARY.md) - sequelize-relay methods -------------------------------------------------------------------------------- /GLOSSARY.md: -------------------------------------------------------------------------------- 1 | # Attributes 2 | In A flat object literal containing key/value pairs. 3 | 4 | # SequelizeModel 5 | An instance of SequelizeClass that contains a lot of functionality (like getters and setters) and database methods. 6 | 7 | # SequelizeClass 8 | A Javascript Class that contains static methods that help with connectivity with a sequelized database.. 9 | 10 | # sequelize-relay 11 | The library this gitbook is about. 12 | 13 | # ORM 14 | A library written that encapsulates the code needed to manipulate relational data. 15 | 16 | -------------------------------------------------------------------------------- /docs/connecting_graphql_and_relay_md.md: -------------------------------------------------------------------------------- 1 | # Connecting GraphQL and Relay 2 | 3 | This chapter is not setup yet, you may find the other chapters useful. 4 | 5 | 6 | This section will cover the usage of graphql-relay-js in regards to adding nodes, edges, etc. It will explain the base64 stuff as well as share information regarding relay specification. 7 | 8 | This section will also cover some of the babel transforms that create graphql schema files etc. 9 | 10 | This will end with the ability to do all of the basic things needed in the specification so we can finally connect a react app. -------------------------------------------------------------------------------- /scripts/mocha-bootload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | require('babel-polyfill'); 10 | require('babel-core/register'); 11 | 12 | // require('babel-register')(); 13 | 14 | var chai = require('chai'); 15 | 16 | var chaiAsPromised = require('chai-as-promised'); 17 | chai.use(chaiAsPromised); -------------------------------------------------------------------------------- /docs/creating_a_database.md: -------------------------------------------------------------------------------- 1 | # Creating a Database 2 | 3 | [Sequelize](http://docs.sequelizejs.com/en/latest/) is a promise-based ORM for Node.js and io.js. It supports the dialects PostgreSQL, MySQL, MariaDB, SQLite and MSSQL and features solid transaction support, relations, read replication and more. 4 | 5 | [Quick Setup](quick_setup.md) 6 | 7 | 8 | This chapter will walk users through a quick installation and test of a sequelize-express server. We'll also create a couple models, and we'll seed our database. We will confirm the database connection and end with a working database connection. We'll cover basic database actions as well. -------------------------------------------------------------------------------- /scripts/updateSchema.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env babel-node --optional es7.asyncFunctions 2 | 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import {graphql} from 'graphql'; 6 | import {introspectionQuery, printSchema} from 'graphql/utilities'; 7 | 8 | import Schema from '../src/__tests__/schema.js'; 9 | 10 | (async () => { 11 | var result = await (graphql(Schema, introspectionQuery)); 12 | if(result.errors) { 13 | console.error( 14 | 'ERROR introspecting schema: ', 15 | JSON.stringify(result.errors, null, 2) 16 | ); 17 | } else { 18 | fs.writeFileSync( 19 | path.join(__dirname, '../data/schema.json'), 20 | JSON.stringify(result, null, 2) 21 | ); 22 | } 23 | })(); 24 | 25 | fs.writeFileSync( 26 | path.join(__dirname, '../data/schema.graphql'), 27 | printSchema(Schema) 28 | ); 29 | -------------------------------------------------------------------------------- /docs/graphql/relay.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/methods/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # API Methods 2 | 3 | * [getArrayData](getArrayData.md) - 4 | Converts an `Array` of SequelizeModel instances to an `Array` of Attributes 5 | objects. 6 | 7 | * [getModelsByClass](getModelsByClass.md) - 8 | Returns an `Array` of SequelizeModel instances that are of the 9 | passed-in SequelizeClass. 10 | 11 | * [resolveArrayByClass](resolveArrayByClass.md) - 12 | First, it internally resolves an an `Array` of SequelizeModel instances 13 | that are of the passed-in SequelizeClass. Then it converts the array 14 | into a **promised** `Array` of objects. 15 | 16 | * [resolveArrayData](resolveArrayData.md) - Converts a **promised** `Array` 17 | of SequelizeModel instances into a **promised** `Array` of Attributes objects. 18 | 19 | * [resolveModelsByClass](resolveModelsByClass.md) - Returns a **promised** 20 | `Array` of SequelizeModel objects by `SequelizeClass`. -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Changelog 2 | 3 | #### v1.0.0 4 | - Thu Nov 17 2016 14:02:15 GMT-0500 (EST) 5 | - Update dependencies (long time coming!) 6 | 7 | #### v0.1.0 8 | - Add optional query object to resolvers. 9 | 10 | #### v0.0.7 11 | - remove peerDeps from travis, move to devDeps. 12 | 13 | #### v0.0.6 14 | - Add peerDependencies to travis yml 15 | 16 | #### v0.0.5 17 | 18 | - fixed docs 19 | - fixed lib folder issue (added to gitignore) 20 | 21 | #### v0.0.4 22 | 23 | - removed devdeps from deps 24 | - removed deps, replaced with peerDeps 25 | 26 | #### v0.0.3 27 | 28 | - Added methods docs 29 | 30 | TODO: Add Guides 31 | 32 | #### v0.0.2 33 | 34 | - Added MIT LICENSE info. 35 | - Added Facebook LICENSE info 36 | - Added intro documentation 37 | - Added contributing info 38 | 39 | #### v0.0.1 40 | 41 | - Added Travis CI, Coveralls, and npm version info 42 | 43 | #### v0.0.0 44 | 45 | - Hooray! Initial Release! -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | After cloning this repo, ensure dependencies are installed by running: 4 | 5 | ```sh 6 | npm install 7 | ``` 8 | 9 | This library is written in ES6 and uses [Babel](http://babeljs.io/) for ES5 10 | transpilation and [Flow](http://flowtype.org/) for type safety. Widely 11 | consumable JavaScript can be produced by running: 12 | 13 | ```sh 14 | npm run build 15 | ``` 16 | 17 | Once `npm run build` has run, you may `import` or `require()` directly from 18 | node. 19 | 20 | After developing, the full test suite can be evaluated by running: 21 | 22 | ```sh 23 | npm test 24 | ``` 25 | 26 | While actively developing, we recommend running 27 | 28 | ```sh 29 | npm run watch 30 | ``` 31 | 32 | in a terminal. This will watch the file system run lint, tests, and type 33 | checking automatically whenever you save a js file. 34 | 35 | To lint the JS files and run type interface checks run `npm run lint`. 36 | -------------------------------------------------------------------------------- /sequelize/models/Article.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (sequelize: Sequelize, DataTypes) { 3 | var Article = sequelize.define('Article', { 4 | type: { 5 | type: new DataTypes.VIRTUAL(DataTypes.STRING), 6 | get() { 7 | return 'articleType'; 8 | } 9 | }, 10 | articleBody: { 11 | type: DataTypes.TEXT, 12 | description: 'The actual body of the article.' 13 | }, 14 | articleSection: { 15 | type: DataTypes.STRING, 16 | description: 'Articles may belong to one or more "sections" in a ' + 17 | 'magazine or newspaper, such as Sports, Lifestyle, etc.' 18 | }, 19 | headline: { 20 | type: DataTypes.STRING, 21 | description: 'Headline of the article.' 22 | }, 23 | thumbnailUrl: { 24 | type: DataTypes.STRING, 25 | description: 'A URL path to the thumbnail image relevant to the Article.' 26 | } 27 | }, { 28 | classMethods: { 29 | associate: (models) => { 30 | Article.belongsTo(models.Person, {as: 'Author'}); 31 | } 32 | } 33 | }); 34 | return Article; 35 | }; 36 | -------------------------------------------------------------------------------- /docs/References/module_imports.md: -------------------------------------------------------------------------------- 1 | # Module Imports 2 | 3 | Some boilerplate for importing all the modules into a graphql schema.js file. I typically comment out the ones I'm not using so I don't have to look them up again. 4 | 5 | ```javascript 6 | 7 | /** 8 | * GraphQL Library Modules 9 | */ 10 | import { 11 | GraphQLSchema, 12 | GraphQLObjectType, 13 | GraphQLInterfaceType, 14 | GraphQLEnumType, 15 | GraphQLList, 16 | GraphQLNonNull, 17 | GraphQLBoolean, 18 | GraphQLInt, 19 | GraphQLFloat, 20 | GraphQLString, 21 | GraphQLID 22 | } from 'graphql'; 23 | 24 | /** 25 | * GraphQL-Relay Modules 26 | */ 27 | import { 28 | nodeDefinitions, 29 | fromGlobalId, 30 | globalIdField, 31 | connectionFromArray, 32 | connectionFromPromisedArray, 33 | connectionArgs, 34 | connectionDefinitions, 35 | mutationWithClientMutationId 36 | } from 'graphql-relay'; 37 | 38 | /** 39 | * Sequelize-Relay Modules 40 | */ 41 | import { 42 | getModelsByClass, 43 | resolveArrayData, 44 | getArrayData, 45 | resolveArrayByClass, 46 | resolveModelsByClass 47 | } from 'sequelize-relay'; 48 | 49 | ``` -------------------------------------------------------------------------------- /sequelize/models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var Sequelize = require('sequelize'); 6 | var basename = path.basename(module.filename); 7 | var env = process.env.NODE_ENV || 'development'; 8 | var config = require(__dirname + '/../config/config.json')[env]; 9 | var db = {}; 10 | 11 | var sequelize; 12 | 13 | if (config.use_env_variable) { 14 | sequelize = new Sequelize(process.env[config.use_env_variable]); 15 | } else { 16 | sequelize = new Sequelize( 17 | config.database, config.username, config.password, config); 18 | } 19 | 20 | fs 21 | .readdirSync(__dirname) 22 | .filter(function (file) { 23 | return (file.indexOf('.') !== 0) && 24 | (file !== basename) && 25 | (file.slice(-3) === '.js'); 26 | }) 27 | .forEach(function (file) { 28 | var model = sequelize['import'](path.join(__dirname, file)); 29 | db[model.name] = model; 30 | }); 31 | 32 | Object.keys(db).forEach(function (modelName) { 33 | if (db[modelName].associate) { 34 | db[modelName].associate(db); 35 | } 36 | }); 37 | 38 | db.sequelize = sequelize; 39 | db.Sequelize = Sequelize; 40 | 41 | module.exports = db; 42 | 43 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](INTRO.md) 4 | * [Setup](docs/setup.md) 5 | * [Creating a Database](docs/creating_a_database.md) 6 | * [Sequelize](docs/sequelize/sequelizemd.md) 7 | * [Quick setup](docs/sequelize/quick_setup.md) 8 | * [Creating GraphQL Schema](docs/creating_graphql_schema_md.md) 9 | * [GraphQL-Relay](docs/graphql/graphql.md) 10 | * [Using nodeDefinitions](docs/graphql/using_nodedefinitions.md) 11 | * [Using connections](docs/graphql/using_connections.md) 12 | * [Connecting GraphQL and Relay](docs/connecting_graphql_and_relay_md.md) 13 | * [Connecting to React](docs/connecting_to_react_md.md) 14 | * [References](docs/References/summarymd.md) 15 | * [Module Imports](docs/References/module_imports.md) 16 | * [nodeDefinitions](docs/References/nodedefinitions.md) 17 | * [Connection Patterns](docs/References/connection_patterns.md) 18 | * [API Methods](docs/methods/SUMMARY.md) 19 | * [getArrayData](docs/methods/getArrayData.md) 20 | * [getModelsByClass](docs/methods/getModelsByClass.md) 21 | * [resolveArrayByClass](docs/methods/resolveArrayByClass.md) 22 | * [resolveArrayData](docs/methods/resolveArrayData.md) 23 | * [resolveModelsByClass](docs/methods/resolveModelsByClass.md) 24 | 25 | -------------------------------------------------------------------------------- /sequelize/seed.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'development'; 2 | 3 | const _ = require('lodash'); 4 | const faker = require('faker'); 5 | export const models = require('./models/index'); 6 | 7 | const seedDatabase = () => { 8 | var { Person } = models; 9 | return new Promise((resolve) => { 10 | return _.times(10, (i) => { 11 | return Person.create({ 12 | additionalName: faker.name.firstName(), 13 | address: faker.address.streetAddress(), 14 | email: faker.internet.email(), 15 | familyName: faker.name.lastName(), 16 | givenName: faker.name.firstName(), 17 | honorificPrefix: faker.name.prefix(), 18 | honorificSuffix: faker.name.suffix(), 19 | jobTitle: faker.name.jobTitle(), 20 | telephone: faker.phone.phoneNumber() 21 | }).then((person) => { 22 | return person.createArticle({ 23 | articleBody: faker.lorem.paragraphs(), 24 | articleSection: faker.company.bsNoun(), 25 | headline: faker.company.catchPhrase(), 26 | thumbnailUrl: faker.image.business() 27 | }).then(() => { 28 | if (i === 9) { 29 | resolve(); 30 | } 31 | }); 32 | }); 33 | }); 34 | }); 35 | }; 36 | 37 | export const connect = () => { 38 | return new Promise((resolve, reject) => { 39 | try { 40 | models.sequelize.sync().then(() => { 41 | return seedDatabase().then(() => { 42 | resolve(models); 43 | }); 44 | }); 45 | } catch (error) { 46 | reject(error); 47 | } 48 | }); 49 | }; 50 | -------------------------------------------------------------------------------- /sequelize/__tests__/database.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module Dependencies 3 | */ 4 | 5 | import { expect } from 'chai'; 6 | import { describe, it, before } from 'mocha'; 7 | import { connect } from '../seed'; 8 | 9 | 10 | const getPeople = (db => db.Person.findAll); 11 | 12 | const getArticles = (db => db.Article.findAll); 13 | 14 | 15 | describe('Database', () => { 16 | let db; 17 | 18 | before((done) => { 19 | connect().then((_db) => { 20 | db = _db; 21 | done(); 22 | }); 23 | }); 24 | 25 | it('connects to the database', (done) => { 26 | expect(db).to.not.be.an('undefined'); 27 | done(); 28 | }); 29 | 30 | it('has Person Model', (done) => { 31 | expect(db.Person).to.not.be.an('undefined'); 32 | done(); 33 | }); 34 | 35 | it('has Article Model', (done) => { 36 | expect(db.Article).to.not.be.an('undefined'); 37 | done(); 38 | }); 39 | 40 | describe('Populates', () => { 41 | 42 | it('populates articles', done => { 43 | expect(getArticles(db)).to.not.be.an('undefined'); 44 | done(); 45 | }); 46 | it('populates people', done => { 47 | expect(getPeople(db)).to.not.be.an('undefined'); 48 | done(); 49 | }); 50 | 51 | }); 52 | 53 | describe('Relationships', () => { 54 | 55 | it('articles have author', (done) => { 56 | db.Article.findAll().then(articles => { 57 | articles[0].getAuthor().then(author => { 58 | if (author) { 59 | done(); 60 | } 61 | }).catch(done); 62 | }).catch(done); 63 | }); 64 | 65 | }); 66 | 67 | 68 | }); 69 | 70 | -------------------------------------------------------------------------------- /PATENT: -------------------------------------------------------------------------------- 1 | GraphQL 2 | ------- 3 | Additional Grant of Patent Rights Version 2 4 | 5 | "Software" means the GraphQL software distributed by Facebook, Inc. 6 | 7 | Facebook, Inc. (“Facebook”) hereby grants to each recipient of the Software (“you”) a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (subject to the termination provision below) license under any Necessary Claims, to make, have made, use, sell, offer to sell, import, and otherwise transfer the Software. For avoidance of doubt, no license is granted under Facebook’s rights in any patent claims that are infringed by (i) modifications to the Software made by you or any third party or (ii) the Software in combination with any software or other technology. 8 | 9 | The license granted hereunder will terminate, automatically and without notice, if you (or any of your subsidiaries, corporate affiliates or agents) initiate directly or indirectly, or take a direct financial interest in, any Patent Assertion: (i) against Facebook or any of its subsidiaries or corporate affiliates, (ii) against any party if such Patent Assertion arises in whole or in part from any software, technology, product or service of Facebook or any of its subsidiaries or corporate affiliates, or (iii) against any party relating to the Software. Notwithstanding the foregoing, if Facebook or any of its subsidiaries or corporate affiliates files a lawsuit alleging patent infringement against you in the first instance, and you respond by filing a patent infringement counterclaim in that lawsuit against that party that is unrelated to the Software, the license granted hereunder will not terminate under section (i) of this paragraph due to such counterclaim. 10 | 11 | A “Necessary Claim” is a claim of a patent owned by Facebook that is necessarily infringed by the Software standing alone. 12 | 13 | A “Patent Assertion” is any lawsuit or other action alleging direct, indirect, or contributory infringement or inducement to infringe any patent, including a cross-claim or counterclaim. -------------------------------------------------------------------------------- /sequelize/models/Person.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Person Model 3 | * @see https://schema.org/Person 4 | * @type {Model} 5 | */ 6 | module.exports = function (sequelize, DataTypes) { 7 | 8 | var Person = sequelize.define('Person', { 9 | type: { 10 | type: new DataTypes.VIRTUAL(DataTypes.STRING), 11 | get() { 12 | return 'personType'; 13 | } 14 | }, 15 | additionalName: { 16 | type: DataTypes.STRING, 17 | description: 'An additional name for a Person, can be used for a ' + 18 | 'middle name.' 19 | }, 20 | address: { 21 | type: DataTypes.STRING, 22 | description: 'Physical address of the item.' 23 | }, 24 | email: { 25 | type: DataTypes.STRING, 26 | description: 'Email address', 27 | validate: { 28 | isEmail: true 29 | } 30 | }, 31 | familyName: { 32 | type: DataTypes.STRING, 33 | description: 'Family name. In the U.S., the last name of an Person. ' + 34 | 'This can be used along with givenName instead of the name property.' 35 | }, 36 | givenName: { 37 | type: DataTypes.STRING, 38 | description: 'Given name. In the U.S., the first name of a Person. ' + 39 | 'This can be used along with familyName instead of the name property.' 40 | }, 41 | honorificPrefix: { 42 | type: DataTypes.STRING, 43 | description: 'An honorific prefix preceding a Person\'s name such as ' + 44 | 'Dr/Mrs/Mr.' 45 | }, 46 | honorificSuffix: { 47 | type: DataTypes.STRING, 48 | description: 'An honorific suffix preceding a Person\'s name such as ' + 49 | 'M.D. /PhD/MSCSW.' 50 | }, 51 | jobTitle: { 52 | type: DataTypes.STRING, 53 | description: 'The job title of the person ' + 54 | '(for example, Financial Manager).' 55 | }, 56 | telephone: { 57 | type: DataTypes.STRING, 58 | description: 'The telephone number.' 59 | } 60 | }, { 61 | classMethods: { 62 | associate: (models) => { 63 | Person.hasMany(models.Article, { 64 | foreignKey: 'AuthorId' 65 | }); 66 | } 67 | } 68 | }); 69 | return Person; 70 | }; 71 | -------------------------------------------------------------------------------- /docs/sequelize/quick_setup.md: -------------------------------------------------------------------------------- 1 | # Sequelize - Quick setup 2 | ![Sequelize Logo](sequelize.png) 3 | 4 | 5 | ## Overview 6 | 7 | - We need to setup models, this can be done with CLI tools or boilerplate. 8 | - We need to setup the database, for this doc we are using sqlite3 9 | - We need to make sure the connection to the database is established before we try to do anything else. 10 | 11 | 12 | ## Model setup 13 | 14 | We need to create some model so we have a table that we can use with sequelize. 15 | 16 | ### Setup models by cloning the repo 17 | 18 | You can have a similar setup that the documentation assumes by copying the files from the [repo here](https://github.com/MattMcFarland/sequelize-relay/tree/master/sequelize). 19 | 20 | ### Create models of your own 21 | 22 | The easiest way to add sequelize models is to use the sequelize CLI tool. More information is available [here](http://docs.sequelizejs.com/en/latest/docs/migrations/?highlight=CLI). 23 | 24 | ```sh 25 | npm install -g sequelize-cli && sequelize init 26 | ``` 27 | 28 | You should see a `models` directory with an `index.js` file that will import all sibling files, as well as a config.json file in a subdirectory labelled `config` 29 | 30 | 31 | ## Database setup 32 | 33 | Setup a database and configure it with the boilerplate config.json file 34 | 35 | ### sqlite3 36 | ```sh 37 | npm install sqlite3 --save-dev 38 | ``` 39 | 40 | Open up your `models/config/config.json` and edit like this: 41 | ```javascript 42 | { 43 | "development": { 44 | "dialect": "sqlite", 45 | "storage": "./db.development.sqlite", 46 | "logging": false 47 | }, 48 | "test": { 49 | "dialect": "sqlite", 50 | "storage": "./db.test.sqlite", 51 | "logging": false 52 | } 53 | } 54 | ``` 55 | 56 | ## Connection setup 57 | 58 | Last we want to make sure we are connected to the database before trying to do anything else. This is done with the `sequelize`.`sync` method. 59 | 60 | 61 | ### express.js connection 62 | 63 | With express.js you can wrap the http connection methods like so: 64 | 65 | ```javascript 66 | var models = require("../data/models"); 67 | models.sequelize.sync().then( () => { 68 | server.listen(port); 69 | }); 70 | ``` 71 | 72 | 73 | 74 | ### custom connection 75 | 76 | You can also write your own, the important part is you want to make sure your connection is established before doing much of anything else. 77 | 78 | The following is taken from sequelize-relay's test server: 79 | 80 | ```javascript 81 | export const models = require('./models/index'); 82 | export const connect = () => { 83 | return new Promise((resolve, reject) => { 84 | try { 85 | models.sequelize.sync().then(() => { 86 | resolve(models); 87 | }); 88 | } catch (error) { 89 | reject(error); 90 | } 91 | }); 92 | }; 93 | ``` 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GraphQL, Relay-JS, GraphQL-Relay 2 | 3 | BSD License 4 | 5 | For GraphQL software 6 | 7 | Copyright (c) 2015, Facebook, Inc. All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, 10 | are permitted provided that the following conditions are met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | * Neither the name Facebook nor the names of its contributors may be used to 20 | endorse or promote products derived from this software without specific 21 | prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 27 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 28 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 30 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | 34 | 35 | Sequelize-relay 36 | The MIT License (MIT) 37 | Copyright (c) 2015 Matt McFarland 38 | 39 | Permission is hereby granted, free of charge, to any person obtaining a copy 40 | of this software and associated documentation files (the "Software"), to deal 41 | in the Software without restriction, including without limitation the rights 42 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 43 | copies of the Software, and to permit persons to whom the Software is 44 | furnished to do so, subject to the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be included in 47 | all copies or substantial portions of the Software. 48 | 49 | 50 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 51 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 52 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 53 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 54 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 55 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 56 | USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sequelize-relay", 3 | "version": "1.0.2", 4 | "description": "A thin wrapper for sequelize and graphql-relay-js", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "pretest": "cp sequelize/db.fixture.sqlite ./db.test.sqlite && npm run lint && npm run check", 8 | "test": "mocha $npm_package_options_mocha", 9 | "travis": "scripts/travis.sh", 10 | "testonly": "mocha $npm_package_options_mocha", 11 | "seed": "mocha $npm_package_options_seed", 12 | "lint": "eslint src", 13 | "check": "flow check", 14 | "build": "rm -rf lib/* && babel src --ignore __tests__ --out-dir lib", 15 | "watch": "babel scripts/watch.js | node", 16 | "watch:seed": "babel scripts/watch-seed.js | node", 17 | "cover": "babel-node node_modules/.bin/isparta cover --root src --report html node_modules/.bin/_mocha -- $npm_package_options_mocha", 18 | "cover:lcov": "babel-node node_modules/.bin/isparta cover --root src --report lcovonly node_modules/.bin/_mocha -- $npm_package_options_mocha" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/MattMcFarland/sequelize-relay.git" 23 | }, 24 | "keywords": [ 25 | "sequelize", 26 | "graphql", 27 | "relayjs", 28 | "server", 29 | "database" 30 | ], 31 | "author": "Matt McFarland", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/MattMcFarland/sequelize-relay/issues" 35 | }, 36 | "homepage": "https://github.com/MattMcFarland/sequelize-relay#readme", 37 | "devDependencies": { 38 | "babel-cli": "^6.18.0", 39 | "babel-core": "^6.18.2", 40 | "babel-eslint": "^7.1.0", 41 | "babel-plugin-transform-flow-strip-types": "^6.18.0", 42 | "babel-polyfill": "^6.16.0", 43 | "babel-preset-es2015": "^6.18.0", 44 | "babel-preset-stage-0": "^6.16.0", 45 | "chai": "^4.0.1", 46 | "chai-as-promised": "^6.0.0", 47 | "coveralls": "^2.11.15", 48 | "eslint": "^3.11.1", 49 | "eslint-plugin-flowtype": "^2.4.0", 50 | "express": "^4.14.0", 51 | "express-graphql": "^0.6.1", 52 | "faker": "^3.1.0", 53 | "flow-bin": "^0.47.0", 54 | "isparta": "^4.0.0", 55 | "lodash": "^4.17.2", 56 | "mocha": "^3.1.2", 57 | "sane": "^1.4.1", 58 | "sqlite3": "^3.1.8", 59 | "graphql": "^0.10.1", 60 | "graphql-relay": "^0.5.0", 61 | "sequelize": "^3.26.0" 62 | }, 63 | "peerDependencies": { 64 | "graphql": ">=0.8.2", 65 | "graphql-relay": ">=0.4.4", 66 | "sequelize": ">=3.26.0" 67 | }, 68 | "directories": { 69 | "lib": "./lib" 70 | }, 71 | "files": [ 72 | "lib", 73 | "README.md", 74 | "LICENSE", 75 | "PATENT" 76 | ], 77 | "options": { 78 | "mocha": "--require scripts/mocha-bootload src/**/__tests__/**/*.js", 79 | "seed": "--require scripts/mocha-bootload sequelize/**/__tests__/**/*.js" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /INTRO.md: -------------------------------------------------------------------------------- 1 | # sequelize-relay 2 | [![NPM](https://nodei.co/npm/sequelize-relay.png?compact=true)](https://nodei.co/npm/sequelize-relay/) 3 | 4 | This is a library to allow the easy creation of Relay-compliant servers using 5 | [sequelize](https://github.com/sequelize/sequelize), 6 | [graphql-js](https://github.com/graphql/graphql-js) and 7 | [graphql-relay-js](https://github.com/graphql/graphql-relay-js). 8 | 9 | 10 | Fork me on GitHub 11 | 12 | ## Dependencies: 13 | - [sequelize](https://github.com/sequelize/sequelize) - 14 | an easy-to-use multi sql dialect ORM for Node.js & io.js. 15 | It currently supports MySQL, MariaDB, SQLite, PostgreSQL and MSSQL. 16 | - [graphql-relay-js](https://github.com/graphql/graphql-relay-js) - 17 | A library to help construct a graphql-js server supporting react-relay. 18 | - [graphql-js](https://github.com/graphql/graphql-js) - 19 | A reference implementation of GraphQL for JavaScript. 20 | 21 | ## Getting Started 22 | 23 | This library is designed to work with the 24 | [graphql-relay-js](https://github.com/graphql/graphql-relay-js) implementation 25 | of a GraphQL server using [Sequelize](https://github.com/sequelize/sequelize). 26 | 27 | 28 | 1. Setup a new npm project 29 | 2. Run `npm install graphql graphql-relay-js sequelize sequelize-relay --save-dev` 30 | 3. Setup a Sequelize Server 31 | 4. Setup your GraphQL Schema 32 | 5. Use graphql-relay-js, sequelize, and sequelize-relay helper functions and win. 33 | 6. Common Patterns, helper methods, etc in the References page. 34 | 35 | ## Methods 36 | 37 | * [getArrayData](docs/methods/getArrayData.md) - Converts an `Array` of `` instances to an `Array` of objects. 38 | 39 | * [getModelsByClass](docs/methods/getModelsByClass.md) - Returns an `Array` of `` instances that are of the passed-in `SequelizeClass`. 40 | 41 | * [resolveArrayByClass](docs/methods/resolveArrayByClass.md) - First, it internally resolves an an `Array` of `` instances that are of the passed-in `SequelizeClass`. Then it converts the array into a **promised** `Array` of `` objects. 42 | 43 | * [resolveArrayData](docs/methods/resolveArrayData.md) - Converts a **promised** `Array` of `` instances into a **promised** `Array` of `` objects. 44 | 45 | * [resolveModelsByClass](docs/methods/resolveModelsByClass.md) - Returns a **promised** `Array` of `` objects by `SequelizeClass`. 46 | 47 | -------------------------------------------------------------------------------- /src/data/methods.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | type SequelizeClass = { 4 | findAll: Function 5 | } 6 | 7 | type Attributes = Object; 8 | type SequelizeModel = { 9 | type: String, 10 | dataValues: Attributes 11 | } 12 | 13 | /** 14 | * Converts an array of instances to an array of 15 | * objects. 16 | * @param instances 17 | * @param withMethods {Boolean} false by default. 18 | * @returns {Array.} 19 | */ 20 | export function getArrayData( 21 | instances: Array, 22 | withMethods: boolean 23 | ): Array { 24 | 25 | if (withMethods) { 26 | return [].concat(...instances); 27 | } else { 28 | return [].concat(instances.map(model => { 29 | return Object.assign({}, { 30 | type: model.type 31 | }, { 32 | ...model.dataValues 33 | }); 34 | })); 35 | } 36 | } 37 | 38 | 39 | /** 40 | * Returns an `Array` of 41 | * instances that are of the passed-in `Class`. 42 | * @param SeqClass 43 | * @param query // optional query object 44 | * @returns {Array.} 45 | */ 46 | export function getModelsByClass( 47 | SeqClass: SequelizeClass, 48 | query: ?Object 49 | ): Array { 50 | return query ? SeqClass.findAll(query) : SeqClass.findAll(); 51 | } 52 | 53 | 54 | 55 | /** 56 | * First, it internally resolves an an `Array` of 57 | * instances that are of the passed-in `Class`. 58 | * Then it converts the array into a **promised** `Array` of 59 | * objects. 60 | * @param SeqClass 61 | * @param withMethods {Boolean} false by default. 62 | * @returns {Array.} 63 | */ 64 | export function resolveArrayByClass( 65 | SeqClass: SequelizeClass, 66 | withMethods: boolean = false 67 | ): Promise> { 68 | return new Promise((resolve, reject) => { 69 | resolveModelsByClass(SeqClass).then(m => { 70 | resolve(getArrayData(m, withMethods)); 71 | }).catch(reject); 72 | }); 73 | 74 | } 75 | 76 | 77 | 78 | /** 79 | * Converts a promised `Array` of instances into a 80 | * **promised** `Array` of objects. 81 | * @param instances 82 | * @param withMethods {Boolean} false by default. 83 | * @returns {Promise>} 84 | */ 85 | export function resolveArrayData( 86 | instances: Promise>, 87 | withMethods: boolean = false 88 | ): Promise> { 89 | return new Promise((resolve, reject) => { 90 | instances.then((models) => { 91 | resolve(getArrayData(models, withMethods)); 92 | }).catch(reject); 93 | }); 94 | } 95 | 96 | 97 | /** 98 | * Returns a **promised** `Array` of objects by `Class`. 99 | * 100 | * @param SeqClass 101 | * @param query // optional query object 102 | * @returns {Promise>} 103 | */ 104 | export function resolveModelsByClass( 105 | SeqClass: SequelizeClass, 106 | query: ?Object 107 | ): Promise> { 108 | return query ? SeqClass.findAll(query) : SeqClass.findAll(); 109 | } 110 | -------------------------------------------------------------------------------- /docs/methods/resolveArrayByClass.md: -------------------------------------------------------------------------------- 1 | ## resolveArrayByClass ⇒ `Promise>` 2 | **resolveArrayByClass(`SequelizeClass`, `withMethods :Boolean=false`) ⇒ `Promise>`** 3 | 4 | First, it internally resolves an an `Array` of SequelizeModel instances 5 | that are of the passed-in SequelizeClass. Then it converts the `Array` into a 6 | **promised** `Array` of Attributes objects. 7 | 8 | 9 | **Returns**: `Promise>` 10 | 11 | | Param | Type | Description | 12 | |------------------ |----------- |--------------------------------------- | 13 | | `SequelizeClass` | `Class` | A specific SequelizeClass to process. | 14 | | `withMethods` | `Boolean` | Populate Attributes objects with sequelize methods | 15 | 16 | 17 | 18 | ### Module Import 19 | ```javascript 20 | import { resolveModelsByClass } from 'sequelize-relay'; 21 | ``` 22 | 23 | ### About 24 | 25 | The `resolveArrayByClass` combines [resolveModelsByClass](resolveModelsByClass.md) and [getArrayData](getArrayData.md) 26 | into one function for easier use of the API. 27 | 28 | In a nut shell: 29 | 30 | ``` 31 | resolveModelsByClass(ClassName) 32 | === getArrayData(resolveModelsByClass(ClassName)); 33 | ``` 34 | 35 | For more detailed documentation see [resolveModelsByClass](resolveModelsByClass.md) and [getArrayData](getArrayData.md). 36 | 37 | 38 | ### Examples 39 | 40 | Consider the following GraphQL Schema Type for `queryType`: 41 | 42 | ```javascript 43 | var queryType = new GraphQLObjectType({ 44 | name: 'Query', 45 | fields: () => ({ 46 | people: { 47 | description: 'People', 48 | type: personConnection, 49 | args: connectionArgs, 50 | resolve: (root, args) => 51 | connectionFromPromisedArray(resolveArrayByClass(Person), args) 52 | }, 53 | peopleWithMethods: { 54 | description: 'People with methods', 55 | type: personConnection, 56 | args: connectionArgs, 57 | resolve: (root, args) => 58 | connectionFromPromisedArray(resolveArrayByClass(Person, true), args) 59 | }, 60 | articles: { 61 | description: 'Articles', 62 | type: articleConnection, 63 | args: connectionArgs, 64 | resolve: (root, args) => 65 | connectionFromPromisedArray(resolveArrayByClass(Article), args) 66 | }, 67 | node: nodeField 68 | }) 69 | }); 70 | 71 | ``` 72 | *For more information about `connectionArgs` and `connectionFromPromisesdArray`, [click here](https://github.com/graphql/graphql-relay-js#connections).* 73 | 74 | From there, we are able to pass graphql-relay queries like so: 75 | 76 | ``` 77 | { 78 | peopleWithMethods(first: 2) { 79 | pageInfo { 80 | startCursor 81 | hasNextPage 82 | } 83 | edges { 84 | cursor 85 | node { 86 | id 87 | givenName 88 | familyName 89 | address 90 | } 91 | } 92 | } 93 | } 94 | ``` 95 | 96 | 97 | ``` 98 | { 99 | articles(first: 2) { 100 | pageInfo { 101 | startCursor 102 | hasNextPage 103 | } 104 | edges { 105 | cursor 106 | node { 107 | id 108 | givenName 109 | familyName 110 | address 111 | } 112 | } 113 | } 114 | } 115 | ``` 116 | 117 | 118 | 119 | #### More Examples 120 | 121 | You can view more examples by reviewing the source code: 122 | 123 | - Full [Person Model](../../sequelize/models/Person.js) Example from test source 124 | - Full [GraphQL Setup](../../src/data/__tests__/connections.js) Example from test source 125 | -------------------------------------------------------------------------------- /docs/methods/getModelsByClass.md: -------------------------------------------------------------------------------- 1 | ## getModelsByClass ⇒ `Array` 2 | 3 | **getModelsByClass(`SequelizeClass`) ⇒ `Array`** 4 | 5 | Returns an `Array` of SequelizeModel instances that are of the passed-in SequelizeClass. 6 | 7 | **Returns**: `Array` 8 | 9 | | Param | Type | Description | 10 | |------------------ |----------- |--------------------------------------- | 11 | | `SequelizeClass` | `Class` | A specific SequelizeClass to process. | 12 | 13 | 14 | ---- 15 | 16 | ### Module Import 17 | ```javascript 18 | import { resolveModelsByClass } from 'sequelize-relay'; 19 | ``` 20 | 21 | ### About 22 | 23 | The `getModelsByClass` and [resolveModelsByClass](resolveModelsByClass.md) methods are very similar as they both return 24 | an Array of Attributes objects. The difference is `resolveModelsByClass` returns a **promised** 25 | Attributes `Array`, and the `getModelsByClass` method returns the Attributes `Array` immediately. 26 | 27 | ### Examples 28 | *For more information about how sequelize models work, [click here](http://docs.sequelizejs.com/en/latest/docs/models-definition/).* 29 | 30 | #### Example 1 31 | 32 | Consider the following sequelize script: 33 | 34 | ```javascript 35 | export var User = sequelize.define('user', { 36 | firstName: Sequelize.STRING, 37 | lastName: Sequelize.STRING 38 | }); 39 | ``` 40 | 41 | A simple db connection export: 42 | ```javascript 43 | export const models = require('./models/index'); 44 | 45 | export const connect = () => { 46 | return new Promise((resolve, reject) => { 47 | try { 48 | models.sequelize.sync().then(() => { 49 | resolve(models); 50 | }); 51 | } catch (error) { 52 | reject(error); 53 | } 54 | }); 55 | }; 56 | ``` 57 | 58 | Now let's pretend we created a few different Users and we wanted to retrieve a list, but only get their attributes. 59 | 60 | 61 | ```javascript 62 | import { models, connect } from 'myDatabase'; 63 | import { getArrayData, getModelsByClass } from 'sequelize-relay'; 64 | 65 | var User = models.User; 66 | connect.then(db => { 67 | console.log('connected to db!'); 68 | let usersList = getModelsByClass(User); 69 | console.log('The userlist has been collected', userList); 70 | console.log('Just the user props', getArrayData(userList)); 71 | console.log('Just user props and methods', getArrayData(userList, true)); 72 | }); 73 | ``` 74 | 75 | #### Example 2 76 | 77 | 78 | Using with Relay: 79 | ```javascript 80 | var queryType = new GraphQLObjectType({ 81 | name: 'Query', 82 | fields: () => ({ 83 | users: { 84 | description: 'Users', 85 | type: userConnection, 86 | args: connectionArgs, 87 | resolve: (root, args) => 88 | connectionFromArray(getModelsByClass(User), args) 89 | }, 90 | node: nodeField 91 | }) 92 | }); 93 | ``` 94 | *For more information about `connectionArgs` and `nodeField`, [click here](https://github.com/graphql/graphql-relay-js#connections).* 95 | 96 | Adds the appropriate connections, `edges`, `cursor`, etc... 97 | 98 | So now we can query our users table the relay-way like so: 99 | 100 | ``` 101 | users(first: 4) { 102 | pageInfo { 103 | startCursor 104 | endCursor 105 | } 106 | edges { 107 | id 108 | firstName 109 | lastName 110 | } 111 | } 112 | ``` 113 | #### More Examples 114 | 115 | You can view more examples by reviewing the source code: 116 | 117 | - Full [Person Model](../../sequelize/models/Person.js) Example from test source 118 | - Full [GraphQL Setup](../../src/data/__tests__/connections.js) Example from test source 119 | -------------------------------------------------------------------------------- /docs/methods/resolveModelsByClass.md: -------------------------------------------------------------------------------- 1 | ## resolveModelsByClass ⇒ `Array` 2 | **resolveModelsByClass(`SequelizeClass`) ⇒ `Promise>`** 3 | 4 | Returns a **promised** `Array` of SequelizeModel instances by SequelizeClass. 5 | 6 | **Returns**: Promise> 7 | 8 | | Param | Type | Description | 9 | |------------------ |----------- |--------------------------------------- | 10 | | `SequelizeClass` | `Class` | A specific `SequelizeClass` to process. | | 11 | 12 | 13 | ---- 14 | 15 | 16 | 17 | ### Module Import 18 | ```javascript 19 | import { resolveModelsByClass } from 'sequelize-relay'; 20 | ``` 21 | 22 | ### About 23 | 24 | The `resolveModelsByClass` and [getModelsByClass](getModelsByClass.md) methods are very similar as they both return 25 | an Array of Attributes objects. The difference is `getModelsByClass` returns the Attributes `Array` immediately, whereas 26 | `resolveModelsByClass` returns only the **promised** Attributes `Array`.. 27 | 28 | ### Examples 29 | *For more information about how sequelize models work, [click here](http://docs.sequelizejs.com/en/latest/docs/models-definition/).* 30 | 31 | #### Example 1 32 | 33 | Consider the following sequelize script: 34 | 35 | ```javascript 36 | export var User = sequelize.define('user', { 37 | firstName: Sequelize.STRING, 38 | lastName: Sequelize.STRING 39 | }); 40 | ``` 41 | 42 | A simple db connection export: 43 | ```javascript 44 | export const models = require('./models/index'); 45 | 46 | export const connect = () => { 47 | return new Promise((resolve, reject) => { 48 | try { 49 | models.sequelize.sync().then(() => { 50 | resolve(models); 51 | }); 52 | } catch (error) { 53 | reject(error); 54 | } 55 | }); 56 | }; 57 | ``` 58 | 59 | Now let's pretend we created a few different Users and we wanted to retrieve a list, but only get their attributes. 60 | 61 | 62 | ```javascript 63 | import { models, connect } from 'myDatabase'; 64 | import { getArrayData, resolveModelsByClass } from 'sequelize-relay'; 65 | 66 | var User = models.User; 67 | connect.then(db => { 68 | console.log('connected to db!'); 69 | resolveModelsByClass(User).then((usersList) => { 70 | console.log('The userlist has been collected', userList); 71 | console.log('Just the user props', getArrayData(userList)); 72 | console.log('Just user props and methods', getArrayData(userList, true)); 73 | }).catch(err => console.error(err)); 74 | }); 75 | ``` 76 | 77 | #### Example 2 78 | 79 | 80 | Using with Relay: 81 | ```javascript 82 | 83 | 84 | var queryType = new GraphQLObjectType({ 85 | name: 'Query', 86 | fields: () => ({ 87 | users: { 88 | description: 'Users', 89 | type: userConnection, 90 | args: connectionArgs, 91 | resolve: (root, args) => 92 | connectionFromPromisedArray(resolveModelsByClass(User), args) 93 | }, 94 | node: nodeField 95 | }) 96 | }); 97 | ``` 98 | *For more information about `connectionArgs` and `nodeField`, [click here](https://github.com/graphql/graphql-relay-js#connections).* 99 | 100 | Adds the appropriate connections, `edges`, `cursor`, etc... 101 | 102 | So now we can query our users table the relay-way like so: 103 | 104 | ``` 105 | users(first: 4) { 106 | pageInfo { 107 | startCursor 108 | endCursor 109 | } 110 | edges { 111 | id 112 | firstName 113 | lastName 114 | } 115 | } 116 | ``` 117 | 118 | 119 | 120 | 121 | #### More Examples 122 | 123 | You can view more examples by reviewing the source code: 124 | 125 | - Full [Person Model](../../sequelize/models/Person.js) Example from test source 126 | - Full [GraphQL Setup](../../src/data/__tests__/connections.js) Example from test source 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sequelize wrapper for Relay and GraphQL.js 2 | 3 | This is a library to allow the easy creation of Relay-compliant servers using 4 | [sequelize](https://github.com/sequelize/sequelize), 5 | [graphql-js](https://github.com/graphql/graphql-js) and 6 | [graphql-relay-js](https://github.com/graphql/graphql-relay-js). 7 | 8 | [![npm](https://img.shields.io/npm/v/sequelize-relay.svg)](https://www.npmjs.com/package/sequelize-relay) 9 | [![Travis](https://img.shields.io/travis/MattMcFarland/sequelize-relay.svg)](https://travis-ci.org/MattMcFarland/sequelize-relay) 10 | [![Coverage Status](https://coveralls.io/repos/MattMcFarland/sequelize-relay/badge.svg?branch=master&service=github)](https://coveralls.io/github/MattMcFarland/sequelize-relay?branch=master) 11 | 12 | ## Documentation 13 | 14 | For a comprehensive walk-through and more details [see the docs](https://mattmcfarland.gitbooks.io/sequelize-relay/content/index.html) 15 | 16 | ## Dependencies: 17 | - [sequelize](https://github.com/sequelize/sequelize) - 18 | an easy-to-use multi sql dialect ORM for Node.js & io.js. 19 | It currently supports MySQL, MariaDB, SQLite, PostgreSQL and MSSQL. 20 | - [graphql-relay-js](https://github.com/graphql/graphql-relay-js) - 21 | A library to help construct a graphql-js server supporting react-relay. 22 | - [graphql-js](https://github.com/graphql/graphql-js) - 23 | A reference implementation of GraphQL for JavaScript. 24 | 25 | ## Getting Started 26 | 27 | 28 | This library is designed to work with the 29 | [graphql-relay-js](https://github.com/graphql/graphql-relay-js) implementation 30 | of a GraphQL server using [Sequelize](https://github.com/sequelize/sequelize). 31 | 32 | Consider reviewing the documentation and tests found at [graphql-relay-js](https://github.com/graphql/graphql-relay-js) 33 | along with the [tests](src/data/__tests__) and documentation found [here](https://mattmcfarland.gitbooks.io/sequelize-relay/content/index.html). 34 | 35 | ## Using Sequelize Relay Library for GraphQL.js 36 | 37 | Install Relay Library for GraphQL.js 38 | 39 | ```sh 40 | npm install sequelize-relay 41 | ``` 42 | 43 | When building a schema for [GraphQL.js](https://github.com/graphql/graphql-js), 44 | the provided library functions can be used to simplify the creation of Relay 45 | patterns hand-in-hand with sequalize and graphql-relay: 46 | 47 | * [getArrayData](getArrayData.md) - Converts an `Array` of instances to an `Array` of objects. 48 | * [resolveArrayByClass](resolveArrayByClass.md) - First, it internally resolves an an `Array` of instances that are of the passed-in `SequelizeClass`. Then it converts the array into a **promised** `Array` of objects. 49 | * [resolveArrayData](resolveArrayData.md) - Converts a **promised** `Array` of instances into a **promised** `Array` of objects. 50 | 51 | * NEW! - Sequelize Queries are available as an argument: 52 | 53 | ``` 54 | articles: { 55 | description: 'Articles', 56 | type: articleConnection, 57 | args: connectionArgs, 58 | resolve: (root, args) => 59 | connectionFromPromisedArray( 60 | resolveModelsByClass(Article, { order: args.order}), args 61 | ) 62 | }, 63 | ``` 64 | 65 | [More methods here](https://mattmcfarland.gitbooks.io/sequelize-relay/content/docs/methods/SUMMARY.html) 66 | 67 | 68 | ## Contributing 69 | 70 | After cloning this repo, ensure dependencies are installed by running: 71 | 72 | ```sh 73 | npm install 74 | ``` 75 | 76 | This library is written in ES6 and uses [Babel](http://babeljs.io/) for ES5 77 | transpilation and [Flow](http://flowtype.org/) for type safety. Widely 78 | consumable JavaScript can be produced by running: 79 | 80 | ```sh 81 | npm run build 82 | ``` 83 | 84 | Once `npm run build` has run, you may `import` or `require()` directly from 85 | node. 86 | 87 | After developing, the full test suite can be evaluated by running: 88 | 89 | ```sh 90 | npm test 91 | ``` 92 | 93 | While actively developing, we recommend running 94 | 95 | ```sh 96 | npm run watch 97 | ``` 98 | 99 | in a terminal. This will watch the file system run lint, tests, and type 100 | checking automatically whenever you save a js file. 101 | 102 | To lint the JS files and run type interface checks run `npm run lint`. 103 | 104 | ### Running seeder: 105 | 106 | Seeder is used to generate db.development.sqlite, which is then manually 107 | copied to db.fixture.sqlite for unit tests. if db changes unit tests have 108 | to changes, so it makes more sense to just have a fixed db. 109 | 110 | ``` 111 | npm run seed 112 | ``` 113 | 114 | 115 | But db is subject to change, so keeping the seeding feature in for now. 116 | -------------------------------------------------------------------------------- /docs/methods/resolveArrayData.md: -------------------------------------------------------------------------------- 1 | ## resolveArrayData ⇒ `Promise>` 2 | **resolveArrayData(`Promise>`, `withMethods :Boolean=false`) ⇒ `Promise>`** 3 | 4 | Converts a **promised** `Array` of SequelizeModel instances into a **promised** 5 | `Array` of Attributes objects. 6 | 7 | 8 | **Returns**: `Promise>` 9 | 10 | 11 | | Param | Type | Description | 12 | |------------------ |----------- |--------------------------------------- | 13 | | `promiseInstances` | `Promise` | A a **promised** `Array` of SequelizeModel instances | 14 | | `withMethods` | `Boolean` | Populate Attributes objects with sequelize methods | 15 | 16 | 17 | ---- 18 | 19 | 20 | ### Module Import 21 | ```javascript 22 | import { resovleArrayData } from 'sequelize-relay'; 23 | ``` 24 | 25 | ### About 26 | 27 | The `resolveArrayData` and [getArrayData](getArrayData.md) methods are very similar as they both return 28 | an Array of Attributes objects. The difference is that the `getArrayData` method expects an Attributes `Array`, and 29 | `resolveArrayData` expects a **promised** Attributes `Array` instead. 30 | 31 | 32 | ### Examples 33 | 34 | #### Example 1 35 | 36 | ```javascript 37 | var User = sequelize.define('user', { 38 | firstName: Sequelize.STRING, 39 | lastName: Sequelize.STRING 40 | }); 41 | 42 | User.sync({force: true}).then(function () { 43 | // Table created 44 | return User.create({ 45 | firstName: 'John', 46 | lastName: 'Hancock' 47 | }); 48 | }); 49 | ``` 50 | 51 | Now let's pretend we created 10 different Users and we wanted to retrieve a list of all 10, but only get their attributes. 52 | 53 | ```javascript 54 | import { Users } from 'myCoolDatabase'; 55 | 56 | async function getUserList () { 57 | return await resolveArrayData(Users.findAll()); 58 | } // => [{firstName: 'John' ...}, {...}] 59 | ``` 60 | 61 | #### Example 2 62 | 63 | Given this [Person Model](../../sequelize/models/Person.js) 64 | *For more information about how sequelize models work, [click here](http://docs.sequelizejs.com/en/latest/docs/models-definition/).* 65 | 66 | Consider the following GraphQL Schema Type for `personType` (shortened for brevity): 67 | 68 | ```javascript 69 | var personType = new GraphQLObjectType({ 70 | fields: () => ({ 71 | ..., 72 | articlesAuthored: { 73 | type: articleConnection, 74 | args: connectionArgs, 75 | resolve: (person, args) => 76 | connectionFromPromisedArray( 77 | resolveArrayData(person.getArticles()), args 78 | ) 79 | } 80 | }) 81 | }); 82 | ``` 83 | *For more information about `connectionArgs` and `connectionFromPromisesdArray`, [click here](https://github.com/graphql/graphql-relay-js#connections).* 84 | 85 | `person.getArticles`, a sequelize method, will be passed in as our argument 86 | to `resolveArrayData` - which will then in turn return a correctly 87 | structured promise to `connectionFromPRomisedArray` which is a method 88 | imported from thw `graphql-relay-js` library. 89 | 90 | We are running our helper methods along with graphql-relay and graphql 91 | libraries, the usage of `resolveArrayData` can be noted here: 92 | 93 | ```javascript 94 | resolve: (person, args) => 95 | connectionFromPromisedArray( 96 | resolveArrayData(person.getArticles()), args 97 | ) 98 | ``` 99 | 100 | So when we run the following Relay style Query: 101 | 102 | ``` 103 | query PersonRefetchQuery { 104 | node(id: "UGVyc29uOjI=") { 105 | id 106 | ... on Person { 107 | id 108 | givenName 109 | familyName 110 | address 111 | articlesAuthored { 112 | edges { 113 | node { 114 | id 115 | headline 116 | thumbnailUrl 117 | } 118 | } 119 | } 120 | } 121 | } 122 | ``` 123 | 124 | We get: 125 | 126 | ```json 127 | { 128 | "data": { 129 | "node": { 130 | "id": "UGVyc29uOjI=", 131 | "givenName": "Amir", 132 | "familyName": "Schmeler", 133 | "address": "197 Mina Gardens", 134 | "articlesAuthored": { 135 | "edges": [ 136 | { 137 | "node": { 138 | "id": "QXJ0aWNsZToy", 139 | "headline": "Open-source object-oriented approach", 140 | "thumbnailUrl": "http://lorempixel.com/640/480/business" 141 | } 142 | } 143 | ] 144 | } 145 | } 146 | } 147 | } 148 | ``` 149 | 150 | 151 | 152 | 153 | #### More Examples 154 | 155 | You can view more examples by reviewing the source code: 156 | 157 | - Full [Person Model](../../sequelize/models/Person.js) Example from test source 158 | - Full [GraphQL Setup](../../src/data/__tests__/connections.js) Example from test source 159 | -------------------------------------------------------------------------------- /docs/References/connection_patterns.md: -------------------------------------------------------------------------------- 1 | # Connection Patterns 2 | 3 | ### foo 4 | ```javascript 5 | var fooType = new GraphQLObjectType({ 6 | name: 'Foo', 7 | fields: () => ({ 8 | id: globalIdField(), 9 | someProp: { 10 | type: GraphQLString, 11 | resolve: foo => foo.prop 12 | }, 13 | anotherProp: { 14 | type: GraphQLString, 15 | resolve: foo => foo.anotherProp 16 | } 17 | }), 18 | interfaces: [nodeInterface] 19 | }); 20 | 21 | 22 | var {connectionType: fooConnection} = 23 | connectionDefinitions({nodeType: fooType}); 24 | 25 | 26 | ``` 27 | ### bar 28 | ```javascript 29 | var barType = new GraphQLObjectType({ 30 | name: 'Bar', 31 | fields: () => ({ 32 | id: globalIdField(), 33 | someProp: { 34 | type: GraphQLString, 35 | resolve: bar => bar.prop 36 | }, 37 | anotherProp: { 38 | type: GraphQLString, 39 | resolve: bar => bar.anotherProp 40 | } 41 | }), 42 | fooFriends: { 43 | type: fooConnection, 44 | args: connectionArgs, 45 | resolve: (bar, args) => 46 | connectionFromPromisedArray( 47 | resolveArrayData(bar.getFooFriends()), args 48 | ) 49 | } 50 | interfaces: [nodeInterface] 51 | }); 52 | 53 | var {connectionType: barConnection} = 54 | connectionDefinitions({nodeType: barType}); 55 | ``` 56 | 57 | ### baz 58 | ```javascript 59 | var bazType = new GraphQLObjectType({ 60 | name: 'baz', 61 | fields: () => ({ 62 | id: globalIdField(), 63 | someProp: { 64 | type: GraphQLString, 65 | resolve: baz => baz.prop 66 | }, 67 | anotherProp: { 68 | type: GraphQLString, 69 | resolve: baz => baz.anotherProp 70 | }, 71 | fooFriends: { 72 | type: fooConnection, 73 | args: connectionArgs, 74 | resolve: (baz, args) => 75 | connectionFromPromisedArray( 76 | resolveArrayData(baz.getFooFreindss()), args 77 | ) 78 | } 79 | }), 80 | interfaces: [nodeInterface] 81 | }); 82 | 83 | var {connectionType: bazConnection} = 84 | connectionDefinitions({nodeType: bazType}); 85 | 86 | 87 | ``` 88 | 89 | #### the connectionDefintions again: 90 | They are above, but it might be worth sharing them one more time: 91 | 92 | 93 | ### foo 94 | ```javascript 95 | 96 | var {connectionType: fooConnection} = 97 | connectionDefinitions({nodeType: fooType}); 98 | 99 | 100 | ``` 101 | ### bar 102 | ```javascript 103 | 104 | var {connectionType: barConnection} = 105 | connectionDefinitions({nodeType: barType}); 106 | 107 | 108 | ``` 109 | ### baz 110 | ```javascript 111 | 112 | 113 | var {connectionType: bazConnection} = 114 | connectionDefinitions({nodeType: bazType}); 115 | 116 | 117 | ``` 118 | 119 | ## connectionArgs 120 | 121 | Connections are useful when dealing with relationships. Let's Presume that `getFooFriends` is a valid sequelize method (could arguably be as such). 122 | 123 | 124 | ### baz 125 | ```javascript 126 | var bazType = new GraphQLObjectType({ 127 | name: 'baz', 128 | fields: () => ({ 129 | id: globalIdField(), 130 | someProp: { 131 | type: GraphQLString, 132 | resolve: baz => baz.prop 133 | }, 134 | anotherProp: { 135 | type: GraphQLString, 136 | resolve: baz => baz.anotherProp 137 | }, 138 | fooFriends: { 139 | type: fooConnection, 140 | args: connectionArgs, 141 | resolve: (baz, args) => 142 | connectionFromPromisedArray( 143 | resolveArrayData(baz.getFooFreindss()), args 144 | ) 145 | } 146 | }), 147 | interfaces: [nodeInterface] 148 | }); 149 | 150 | var {connectionType: bazConnection} = 151 | connectionDefinitions({nodeType: bazType}); 152 | ``` 153 | 154 | Notice we are using `connectionArgs`, `connectionDefinitions`, and `noteInterface`? 155 | 156 | ### bar 157 | ```javascript 158 | var barType = new GraphQLObjectType({ 159 | name: 'Bar', 160 | fields: () => ({ 161 | id: globalIdField(), 162 | someProp: { 163 | type: GraphQLString, 164 | resolve: bar => bar.prop 165 | }, 166 | anotherProp: { 167 | type: GraphQLString, 168 | resolve: bar => bar.anotherProp 169 | } 170 | }), 171 | fooFriends: { 172 | type: fooConnection, 173 | args: connectionArgs, 174 | resolve: (bar, args) => 175 | connectionFromPromisedArray( 176 | resolveArrayData(bar.getFooFriends()), args 177 | ) 178 | } 179 | interfaces: [nodeInterface] 180 | }); 181 | 182 | var {connectionType: barConnection} = 183 | connectionDefinitions({nodeType: barType}); 184 | ``` 185 | 186 | Again, we see that we are using `connectionArgs`, `connectionDefinitions`, and `noteInterface`.... 187 | 188 | 189 | Following the patterns depicted above will guarantee you retrieve all of the `relay` `edges` and `pageInfo` - cursors and all.. 190 | 191 | 192 | The only `sequelize-relay` method used for these examples was `resolveArrayData` 193 | 194 | -------------------------------------------------------------------------------- /sequelize/rawPeople.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "personType", 4 | "id": 1, 5 | "additionalName": "Lilyan", 6 | "address": "40831 Chad Rue", 7 | "email": "Aryanna99@yahoo.com", 8 | "familyName": "Reinger", 9 | "givenName": "Jaylan", 10 | "honorificPrefix": "Dr.", 11 | "honorificSuffix": "IV", 12 | "jobTitle": "Internal Program Officer", 13 | "telephone": "259-536-5663 x8533", 14 | "createdAt": "2015-12-23T16:46:24.449Z", 15 | "updatedAt": "2015-12-23T16:46:24.449Z" 16 | }, 17 | { 18 | "type": "personType", 19 | "id": 2, 20 | "additionalName": "Viva", 21 | "address": "197 Mina Gardens", 22 | "email": "Creola5@gmail.com", 23 | "familyName": "Schmeler", 24 | "givenName": "Amir", 25 | "honorificPrefix": "Miss", 26 | "honorificSuffix": "DVM", 27 | "jobTitle": "Lead Creative Executive", 28 | "telephone": "718-964-7388 x29503", 29 | "createdAt": "2015-12-23T16:46:24.455Z", 30 | "updatedAt": "2015-12-23T16:46:24.455Z" 31 | }, 32 | { 33 | "type": "personType", 34 | "id": 3, 35 | "additionalName": "Urban", 36 | "address": "109 Ottilie Pass", 37 | "email": "Marlen.White@gmail.com", 38 | "familyName": "Adams", 39 | "givenName": "Bobbie", 40 | "honorificPrefix": "Dr.", 41 | "honorificSuffix": "III", 42 | "jobTitle": "Corporate Infrastructure Engineer", 43 | "telephone": "(869) 709-9551 x31769", 44 | "createdAt": "2015-12-23T16:46:24.456Z", 45 | "updatedAt": "2015-12-23T16:46:24.456Z" 46 | }, 47 | { 48 | "type": "personType", 49 | "id": 4, 50 | "additionalName": "Everardo", 51 | "address": "60340 Gleason Heights", 52 | "email": "Kyle92@yahoo.com", 53 | "familyName": "Abbott", 54 | "givenName": "Berta", 55 | "honorificPrefix": "Dr.", 56 | "honorificSuffix": "III", 57 | "jobTitle": "Human Tactics Specialist", 58 | "telephone": "1-265-145-9618 x199", 59 | "createdAt": "2015-12-23T16:46:24.457Z", 60 | "updatedAt": "2015-12-23T16:46:24.457Z" 61 | }, 62 | { 63 | "type": "personType", 64 | "id": 5, 65 | "additionalName": "Gussie", 66 | "address": "13458 Dayana Ramp", 67 | "email": "Hailie_Boyle94@gmail.com", 68 | "familyName": "Fahey", 69 | "givenName": "Eleonore", 70 | "honorificPrefix": "Ms.", 71 | "honorificSuffix": "MD", 72 | "jobTitle": "Global Interactions Associate", 73 | "telephone": "246-139-3322 x196", 74 | "createdAt": "2015-12-23T16:46:24.458Z", 75 | "updatedAt": "2015-12-23T16:46:24.458Z" 76 | }, 77 | { 78 | "type": "personType", 79 | "id": 6, 80 | "additionalName": "Aidan", 81 | "address": "3382 O'Conner Cliff", 82 | "email": "Loyce_Donnelly@yahoo.com", 83 | "familyName": "Mueller", 84 | "givenName": "Jennie", 85 | "honorificPrefix": "Mrs.", 86 | "honorificSuffix": "V", 87 | "jobTitle": "Human Identity Associate", 88 | "telephone": "805-079-1652 x66842", 89 | "createdAt": "2015-12-23T16:46:24.459Z", 90 | "updatedAt": "2015-12-23T16:46:24.459Z" 91 | }, 92 | { 93 | "type": "personType", 94 | "id": 7, 95 | "additionalName": "Gloria", 96 | "address": "4552 Swift Inlet", 97 | "email": "Leilani41@gmail.com", 98 | "familyName": "Rogahn", 99 | "givenName": "Adrienne", 100 | "honorificPrefix": "Ms.", 101 | "honorificSuffix": "DDS", 102 | "jobTitle": "Dynamic Communications Technician", 103 | "telephone": "1-048-314-3269 x395", 104 | "createdAt": "2015-12-23T16:46:24.460Z", 105 | "updatedAt": "2015-12-23T16:46:24.460Z" 106 | }, 107 | { 108 | "type": "personType", 109 | "id": 8, 110 | "additionalName": "Pamela", 111 | "address": "1249 Merlin Trail", 112 | "email": "Anais_VonRueden85@gmail.com", 113 | "familyName": "Gulgowski", 114 | "givenName": "Genoveva", 115 | "honorificPrefix": "Miss", 116 | "honorificSuffix": "DVM", 117 | "jobTitle": "Regional Security Representative", 118 | "telephone": "340.537.5704", 119 | "createdAt": "2015-12-23T16:46:24.461Z", 120 | "updatedAt": "2015-12-23T16:46:24.461Z" 121 | }, 122 | { 123 | "type": "personType", 124 | "id": 9, 125 | "additionalName": "Hailee", 126 | "address": "5637 Will Road", 127 | "email": "Lavada.Tillman@hotmail.com", 128 | "familyName": "Stehr", 129 | "givenName": "Aiyana", 130 | "honorificPrefix": "Miss", 131 | "honorificSuffix": "IV", 132 | "jobTitle": "Investor Integration Strategist", 133 | "telephone": "(712) 600-5091", 134 | "createdAt": "2015-12-23T16:46:24.462Z", 135 | "updatedAt": "2015-12-23T16:46:24.462Z" 136 | }, 137 | { 138 | "type": "personType", 139 | "id": 10, 140 | "additionalName": "Jeffrey", 141 | "address": "66543 Rick Lock", 142 | "email": "Friedrich_Block1@gmail.com", 143 | "familyName": "Johns", 144 | "givenName": "Gracie", 145 | "honorificPrefix": "Miss", 146 | "honorificSuffix": "V", 147 | "jobTitle": "Legacy Web Director", 148 | "telephone": "306-361-6895", 149 | "createdAt": "2015-12-23T16:46:24.463Z", 150 | "updatedAt": "2015-12-23T16:46:24.463Z" 151 | } 152 | ] 153 | -------------------------------------------------------------------------------- /docs/methods/getArrayData.md: -------------------------------------------------------------------------------- 1 | ## getArrayData ⇒ `Array` 2 | 3 | **getArrayData(`Array`, `withMethods :Boolean = false`) ⇒ `Array`** 4 | 5 | **Description:** Convert `Array` of SequelizeModel instances to `Array` of Attributes objects. 6 | 7 | **Returns**: `Array` 8 | 9 | | Param | Type | Description | 10 | |------------------ |----------- |--------------------------------------- | 11 | | `SequelizeModels` | `Array` | Convert instances in array to Attributes | 12 | | `withMethods` | `Boolean` | Populate `` objects with sequelize methods | 13 | 14 | 15 | ---- 16 | 17 | 18 | ### Module Import 19 | ```javascript 20 | import { getArrayData } from 'sequelize-relay'; 21 | ``` 22 | 23 | ### About 24 | 25 | The `getArrayData` and [resolveArrayData](resolveArrayData.md) methods are very similar as they both return 26 | an Array of Attributes objects. The difference is `resolveArrayData` expects a **promised** 27 | Attributes `Array`, but the `getArrayData` method expects an Attributes `Array`. 28 | 29 | ### Examples 30 | *For more information about how sequelize models work, [click here](http://docs.sequelizejs.com/en/latest/docs/models-definition/).* 31 | 32 | #### Example 1 33 | 34 | ```javascript 35 | var User = sequelize.define('user', { 36 | firstName: Sequelize.STRING, 37 | lastName: Sequelize.STRING 38 | }); 39 | 40 | User.sync({force: true}).then(function () { 41 | // Table created 42 | return User.create({ 43 | firstName: 'John', 44 | lastName: 'Hancock' 45 | }); 46 | }); 47 | ``` 48 | 49 | Now let's pretend we created a few different Users and we wanted to retrieve a list, but only get their attributes. 50 | 51 | > NOTE: Sequelize will automatically create helper functions and also pluralize Users in the db object. 52 | 53 | ```javascript 54 | db.Users.findAll().then(function (users) => { 55 | console.log(users); // complex multi array with circular objects, etc. good for some use-cases. 56 | var justThePropsPlease = getArrayData(users); // flattened array like the SQL table. 57 | var propsAndMethods = getArrayData(users, true); // flattened array with only getters/setters excluding static methods. 58 | }); 59 | ``` 60 | 61 | #### Example 2 62 | 63 | Consider a `sequelize` model named `Person`: 64 | 65 | ```javascript 66 | import { Person } from 'myDatabase'; 67 | import { getArrayData, getModelsByClass } from 'sequelize-relay'; 68 | 69 | async function getFlatArrayOfPeople () { 70 | let sequelizeArray = await getModelsByClass(Person); 71 | return getArrayData(sequelizeArray); 72 | } 73 | ``` 74 | 75 | #### Example 3 76 | Given this [Person Model](../../sequelize/models/Person.js) 77 | 78 | *For more information about how sequelize models work, [click here](http://docs.sequelizejs.com/en/latest/docs/models-definition/).* 79 | 80 | Consider the following GraphQL Schema Type for `personType` (shortened for brevity): 81 | 82 | 83 | ```javascript 84 | var personType = new GraphQLObjectType({ 85 | fields: () => ({ 86 | ..., 87 | articlesAuthored: { 88 | type: articleConnection, 89 | args: connectionArgs, 90 | resolve: (person, args) => 91 | connectionFromArray( 92 | getArrayData(person.getArticles()), args 93 | ) 94 | } 95 | }) 96 | }); 97 | ``` 98 | *For more information about `connectionArgs` and `connectionFromArray`, [click here](https://github.com/graphql/graphql-relay-js#connections).* 99 | 100 | `person.getArticles`, a sequelize method, will be passed in as our argument 101 | to `resolveArrayData` - which will then in turn return a correctly 102 | structured promise to `connectionFromPRomisedArray` which is a method 103 | imported from thw `graphql-relay-js` library. 104 | 105 | We are running our helper methods along with graphql-relay and graphql 106 | libraries, the usage of `resolveArrayData` can be noted here: 107 | 108 | ```javascript 109 | resolve: (person, args) => 110 | connectionFromArray( 111 | resolveArrayData(person.getArticles()), args 112 | ) 113 | ``` 114 | 115 | So when we run the following Relay style Query: 116 | 117 | ``` 118 | query PersonRefetchQuery { 119 | node(id: "UGVyc29uOjI=") { 120 | id 121 | ... on Person { 122 | id 123 | givenName 124 | familyName 125 | address 126 | articlesAuthored { 127 | edges { 128 | node { 129 | id 130 | headline 131 | thumbnailUrl 132 | } 133 | } 134 | } 135 | } 136 | } 137 | ``` 138 | 139 | We get: 140 | 141 | ```json 142 | { 143 | "data": { 144 | "node": { 145 | "id": "UGVyc29uOjI=", 146 | "givenName": "Amir", 147 | "familyName": "Schmeler", 148 | "address": "197 Mina Gardens", 149 | "articlesAuthored": { 150 | "edges": [ 151 | { 152 | "node": { 153 | "id": "QXJ0aWNsZToy", 154 | "headline": "Open-source object-oriented approach", 155 | "thumbnailUrl": "http://lorempixel.com/640/480/business" 156 | } 157 | } 158 | ] 159 | } 160 | } 161 | } 162 | } 163 | ``` 164 | 165 | #### More Examples 166 | 167 | You can view more examples by reviewing the source code: 168 | 169 | - Full [Person Model](../../sequelize/models/Person.js) Example from test source 170 | - Full [GraphQL Setup](../../src/data/__tests__/connections.js) Example from test source 171 | -------------------------------------------------------------------------------- /scripts/watch-seed.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import sane from 'sane'; 11 | import { resolve as resolvePath } from 'path'; 12 | import { spawn } from 'child_process'; 13 | import flowBinPath from 'flow-bin'; 14 | 15 | 16 | process.env.PATH += ':./node_modules/.bin'; 17 | 18 | var cmd = resolvePath(__dirname); 19 | var srcDir = resolvePath(cmd, './src'); 20 | 21 | function exec(command, options) { 22 | return new Promise(function (resolve, reject) { 23 | var child = spawn(command, options, { 24 | cmd: cmd, 25 | env: process.env, 26 | stdio: 'inherit' 27 | }); 28 | child.on('exit', function (code) { 29 | if (code === 0) { 30 | resolve(true); 31 | } else { 32 | reject(new Error('Error code: ' + code)); 33 | } 34 | }); 35 | }); 36 | } 37 | 38 | var flowServer = spawn(flowBinPath, ['server'], { 39 | cmd: cmd, 40 | env: process.env 41 | }); 42 | 43 | var watcher = sane(srcDir, { glob: ['**/*.*'] }) 44 | .on('ready', startWatch) 45 | .on('add', changeFile) 46 | .on('delete', deleteFile) 47 | .on('change', changeFile); 48 | 49 | process.on('SIGINT', function () { 50 | watcher.close(); 51 | flowServer.kill(); 52 | console.log(CLEARLINE + yellow(invert('stopped watching'))); 53 | process.exit(); 54 | }); 55 | 56 | var isChecking; 57 | var needsCheck; 58 | var toCheck = {}; 59 | var timeout; 60 | 61 | function startWatch() { 62 | process.stdout.write(CLEARSCREEN + green(invert('watching...'))); 63 | } 64 | 65 | function changeFile(filepath, root, stat) { 66 | if (!stat.isDirectory()) { 67 | toCheck[filepath] = true; 68 | debouncedCheck(); 69 | } 70 | } 71 | 72 | function deleteFile(filepath) { 73 | delete toCheck[filepath]; 74 | debouncedCheck(); 75 | } 76 | 77 | function debouncedCheck() { 78 | needsCheck = true; 79 | clearTimeout(timeout); 80 | timeout = setTimeout(guardedCheck, 250); 81 | } 82 | 83 | function guardedCheck() { 84 | if (isChecking || !needsCheck) { 85 | return; 86 | } 87 | isChecking = true; 88 | var filepaths = Object.keys(toCheck); 89 | toCheck = {}; 90 | needsCheck = false; 91 | checkFiles(filepaths).then(() => { 92 | isChecking = false; 93 | process.nextTick(guardedCheck); 94 | }); 95 | } 96 | 97 | function checkFiles(filepaths) { 98 | console.log('\u001b[2J'); 99 | 100 | return parseFiles(filepaths) 101 | .then(() => runTests(filepaths)) 102 | .then(testSuccess => lintFiles(filepaths) 103 | .then(lintSuccess => typecheckStatus() 104 | .then(typecheckSuccess => 105 | testSuccess && lintSuccess && typecheckSuccess))) 106 | .catch(() => false) 107 | .then(success => { 108 | process.stdout.write( 109 | '\n' + (success ? '' : '\x07') + green(invert('watching...')) 110 | ); 111 | }); 112 | } 113 | 114 | // Checking steps 115 | 116 | function parseFiles(filepaths) { 117 | console.log('Checking Syntax'); 118 | 119 | return Promise.all(filepaths.map(filepath => { 120 | if (isJS(filepath) && !isTest(filepath)) { 121 | return exec('babel', [ 122 | '--optional', 'runtime', 123 | '--out-file', '/dev/null', 124 | srcPath(filepath) 125 | ]); 126 | } 127 | })); 128 | } 129 | 130 | function runTests(filepaths) { 131 | console.log('\nRunning Tests'); 132 | 133 | return exec('mocha', [ 134 | '--reporter', 'progress', 135 | '--require', 'scripts/mocha-bootload' 136 | ].concat( 137 | allTests(filepaths) ? filepaths.map(srcPath) : ['sequelize/**/__tests__/**/*.js'] 138 | )).catch(() => false); 139 | } 140 | 141 | function lintFiles(filepaths) { 142 | console.log('Linting Code\n'); 143 | 144 | return filepaths.reduce((prev, filepath) => prev.then(prevSuccess => { 145 | process.stdout.write(' ' + filepath + ' ...'); 146 | return exec('eslint', [srcPath(filepath)]) 147 | .catch(() => false) 148 | .then(success => { 149 | console.log(CLEARLINE + ' ' + (success ? CHECK : X) + ' ' + filepath); 150 | return prevSuccess && success; 151 | }); 152 | }), Promise.resolve(true)); 153 | } 154 | 155 | function typecheckStatus() { 156 | console.log('\nType Checking\n'); 157 | return exec(flowBinPath, ['status']).catch(() => false); 158 | } 159 | 160 | // Filepath 161 | 162 | function srcPath(filepath) { 163 | return resolvePath(srcDir, filepath); 164 | } 165 | 166 | // Predicates 167 | 168 | function isJS(filepath) { 169 | return filepath.indexOf('.js') === filepath.length - 3; 170 | } 171 | 172 | function allTests(filepaths) { 173 | return filepaths.length > 0 && filepaths.every(isTest); 174 | } 175 | 176 | function isTest(filepath) { 177 | return isJS(filepath) && ~filepath.indexOf('__tests__/'); 178 | } 179 | 180 | // Print helpers 181 | 182 | var CLEARSCREEN = '\u001b[2J'; 183 | var CLEARLINE = '\r\x1B[K'; 184 | var CHECK = green('\u2713'); 185 | var X = red('\u2718'); 186 | 187 | function invert(str) { 188 | return `\u001b[7m ${str} \u001b[27m`; 189 | } 190 | 191 | function red(str) { 192 | return `\x1B[K\u001b[1m\u001b[31m${str}\u001b[39m\u001b[22m`; 193 | } 194 | 195 | function green(str) { 196 | return `\x1B[K\u001b[1m\u001b[32m${str}\u001b[39m\u001b[22m`; 197 | } 198 | 199 | function yellow(str) { 200 | return `\x1B[K\u001b[1m\u001b[33m${str}\u001b[39m\u001b[22m`; 201 | } -------------------------------------------------------------------------------- /scripts/watch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import sane from 'sane'; 11 | import { resolve as resolvePath } from 'path'; 12 | import { spawn } from 'child_process'; 13 | import flowBinPath from 'flow-bin'; 14 | 15 | 16 | process.env.PATH += ':./node_modules/.bin'; 17 | 18 | var cmd = resolvePath(__dirname); 19 | var srcDir = resolvePath(cmd, './src'); 20 | 21 | function exec(command, options) { 22 | return new Promise(function (resolve, reject) { 23 | var child = spawn(command, options, { 24 | cmd: cmd, 25 | env: process.env, 26 | stdio: 'inherit' 27 | }); 28 | child.on('exit', function (code) { 29 | if (code === 0) { 30 | resolve(true); 31 | } else { 32 | reject(new Error('Error code: ' + code)); 33 | } 34 | }); 35 | }); 36 | } 37 | 38 | var flowServer = spawn(flowBinPath, ['server'], { 39 | cmd: cmd, 40 | env: process.env 41 | }); 42 | 43 | var watcher = sane(srcDir, { glob: ['**/*.*'] }) 44 | .on('ready', startWatch) 45 | .on('add', changeFile) 46 | .on('delete', deleteFile) 47 | .on('change', changeFile); 48 | 49 | process.on('SIGINT', function () { 50 | watcher.close(); 51 | flowServer.kill(); 52 | console.log(CLEARLINE + yellow(invert('stopped watching'))); 53 | process.exit(); 54 | }); 55 | 56 | var isChecking; 57 | var needsCheck; 58 | var toCheck = {}; 59 | var timeout; 60 | 61 | function startWatch() { 62 | process.stdout.write(CLEARSCREEN + green(invert('watching...'))); 63 | } 64 | 65 | function changeFile(filepath, root, stat) { 66 | if (!stat.isDirectory()) { 67 | toCheck[filepath] = true; 68 | debouncedCheck(); 69 | } 70 | } 71 | 72 | function deleteFile(filepath) { 73 | delete toCheck[filepath]; 74 | debouncedCheck(); 75 | } 76 | 77 | function debouncedCheck() { 78 | needsCheck = true; 79 | clearTimeout(timeout); 80 | timeout = setTimeout(guardedCheck, 250); 81 | } 82 | 83 | function guardedCheck() { 84 | if (isChecking || !needsCheck) { 85 | return; 86 | } 87 | isChecking = true; 88 | var filepaths = Object.keys(toCheck); 89 | toCheck = {}; 90 | needsCheck = false; 91 | checkFiles(filepaths).then(() => { 92 | isChecking = false; 93 | process.nextTick(guardedCheck); 94 | }); 95 | } 96 | 97 | function checkFiles(filepaths) { 98 | console.log('\u001b[2J'); 99 | 100 | return parseFiles(filepaths) 101 | .then(() => runTests(filepaths)) 102 | .then(testSuccess => lintFiles(filepaths) 103 | .then(lintSuccess => typecheckStatus() 104 | .then(typecheckSuccess => 105 | testSuccess && lintSuccess && typecheckSuccess))) 106 | .catch(() => false) 107 | .then(success => { 108 | process.stdout.write( 109 | '\n' + (success ? '' : '\x07') + green(invert('watching...')) 110 | ); 111 | }); 112 | } 113 | 114 | // Checking steps 115 | 116 | function parseFiles(filepaths) { 117 | console.log('Checking Syntax'); 118 | 119 | return Promise.all(filepaths.map(filepath => { 120 | if (isJS(filepath) && !isTest(filepath)) { 121 | return exec('babel', [ 122 | '--optional', 'runtime', 123 | '--out-file', '/dev/null', 124 | srcPath(filepath) 125 | ]); 126 | } 127 | })); 128 | } 129 | 130 | function runTests(filepaths) { 131 | console.log('\nRunning Tests'); 132 | 133 | return exec('mocha', [ 134 | '--reporter', 'spec', 135 | '--require', 'scripts/mocha-bootload' 136 | ].concat( 137 | allTests(filepaths) ? filepaths.map(srcPath) : ['src/**/__tests__/**/*.js'] 138 | )).catch(() => false); 139 | } 140 | 141 | function lintFiles(filepaths) { 142 | console.log('Linting Code\n'); 143 | 144 | return filepaths.reduce((prev, filepath) => prev.then(prevSuccess => { 145 | process.stdout.write(' ' + filepath + ' ...'); 146 | return exec('eslint', [ 147 | srcPath(filepath)]) 148 | .catch(() => false) 149 | .then(success => { 150 | console.log(CLEARLINE + ' ' + (success ? CHECK : X) + ' ' + filepath); 151 | return prevSuccess && success; 152 | }); 153 | }), Promise.resolve(true)); 154 | } 155 | 156 | function typecheckStatus() { 157 | console.log('\nType Checking\n'); 158 | return exec(flowBinPath, ['status']).catch(() => false); 159 | } 160 | 161 | // Filepath 162 | 163 | function srcPath(filepath) { 164 | return resolvePath(srcDir, filepath); 165 | } 166 | 167 | // Predicates 168 | 169 | function isJS(filepath) { 170 | return filepath.indexOf('.js') === filepath.length - 3; 171 | } 172 | 173 | function allTests(filepaths) { 174 | return filepaths.length > 0 && filepaths.every(isTest); 175 | } 176 | 177 | function isTest(filepath) { 178 | return isJS(filepath) && ~filepath.indexOf('__tests__/'); 179 | } 180 | 181 | // Print helpers 182 | 183 | var CLEARSCREEN = '\u001b[2J'; 184 | var CLEARLINE = '\r\x1B[K'; 185 | var CHECK = green('\u2713'); 186 | var X = red('\u2718'); 187 | 188 | function invert(str) { 189 | return `\u001b[7m ${str} \u001b[27m`; 190 | } 191 | 192 | function red(str) { 193 | return `\x1B[K\u001b[1m\u001b[31m${str}\u001b[39m\u001b[22m`; 194 | } 195 | 196 | function green(str) { 197 | return `\x1B[K\u001b[1m\u001b[32m${str}\u001b[39m\u001b[22m`; 198 | } 199 | 200 | function yellow(str) { 201 | return `\x1B[K\u001b[1m\u001b[33m${str}\u001b[39m\u001b[22m`; 202 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "arrowFunctions": true, 4 | "blockBindings": true, 5 | "classes": true, 6 | "defaultParams": true, 7 | "destructuring": true, 8 | "forOf": true, 9 | "generators": true, 10 | "modules": true, 11 | "objectLiteralComputedProperties": true, 12 | "objectLiteralShorthandMethods": true, 13 | "objectLiteralShorthandProperties": true, 14 | "spread": true, 15 | "templateStrings": true, 16 | "env": { 17 | "node": true, 18 | "es6": true 19 | }, 20 | "rules": { 21 | "comma-dangle": 0, 22 | "no-cond-assign": 2, 23 | "no-console": 0, 24 | "no-constant-condition": 2, 25 | "no-control-regex": 0, 26 | "no-debugger": 0, 27 | "no-dupe-args": 2, 28 | "no-dupe-keys": 2, 29 | "no-duplicate-case": 2, 30 | "no-empty": 2, 31 | "no-empty-character-class": 2, 32 | "no-ex-assign": 2, 33 | "no-extra-boolean-cast": 2, 34 | "no-extra-semi": 2, 35 | "no-func-assign": 2, 36 | "no-inner-declarations": [ 37 | 2, 38 | "functions" 39 | ], 40 | "no-invalid-regexp": 2, 41 | "no-irregular-whitespace": 2, 42 | "no-negated-in-lhs": 2, 43 | "no-obj-calls": 2, 44 | "no-regex-spaces": 2, 45 | "no-reserved-keys": 0, 46 | "no-sparse-arrays": 2, 47 | "no-unreachable": 2, 48 | "use-isnan": 2, 49 | "valid-jsdoc": 0, 50 | "valid-typeof": 2, 51 | "block-scoped-var": 0, 52 | "complexity": 0, 53 | "consistent-return": 0, 54 | "curly": [ 55 | 2, 56 | "all" 57 | ], 58 | "default-case": 0, 59 | "dot-notation": 0, 60 | "eqeqeq": 2, 61 | "guard-for-in": 2, 62 | "no-alert": 2, 63 | "no-caller": 2, 64 | "no-div-regex": 2, 65 | "no-eq-null": 0, 66 | "no-eval": 2, 67 | "no-extend-native": 2, 68 | "no-extra-bind": 2, 69 | "no-fallthrough": 2, 70 | "no-floating-decimal": 2, 71 | "no-implied-eval": 2, 72 | "no-iterator": 2, 73 | "no-labels": 0, 74 | "no-lone-blocks": 0, 75 | "no-loop-func": 0, 76 | "no-multi-spaces": 2, 77 | "no-multi-str": 2, 78 | "no-native-reassign": 0, 79 | "no-new": 2, 80 | "no-new-func": 0, 81 | "no-new-wrappers": 2, 82 | "no-octal": 2, 83 | "no-octal-escape": 2, 84 | "no-param-reassign": 2, 85 | "no-process-env": 0, 86 | "no-proto": 2, 87 | "no-redeclare": 2, 88 | "no-return-assign": 2, 89 | "no-script-url": 2, 90 | "no-self-compare": 0, 91 | "no-sequences": 2, 92 | "no-throw-literal": 2, 93 | "no-unused-expressions": 2, 94 | "no-void": 2, 95 | "no-warning-comments": 0, 96 | "no-with": 2, 97 | "radix": 2, 98 | "vars-on-top": 0, 99 | "wrap-iife": 2, 100 | "yoda": [ 101 | 2, 102 | "never", 103 | { 104 | "exceptRange": true 105 | } 106 | ], 107 | "strict": 0, 108 | "no-catch-shadow": 2, 109 | "no-delete-var": 2, 110 | "no-label-var": 2, 111 | "no-shadow": 2, 112 | "no-shadow-restricted-names": 2, 113 | "no-undef": 2, 114 | "no-undef-init": 2, 115 | "no-undefined": 0, 116 | "no-unused-vars": [ 117 | 2, 118 | { 119 | "vars": "all", 120 | "args": "after-used" 121 | } 122 | ], 123 | "no-use-before-define": 0, 124 | "handle-callback-err": [ 125 | 2, 126 | "error" 127 | ], 128 | "no-mixed-requires": [ 129 | 2, 130 | true 131 | ], 132 | "no-new-require": 2, 133 | "no-path-concat": 2, 134 | "no-process-exit": 0, 135 | "no-restricted-modules": 0, 136 | "no-sync": 2, 137 | "brace-style": [ 138 | 2, 139 | "1tbs", 140 | { 141 | "allowSingleLine": true 142 | } 143 | ], 144 | "comma-spacing": 0, 145 | "comma-style": [ 146 | 2, 147 | "last" 148 | ], 149 | "consistent-this": 0, 150 | "eol-last": 2, 151 | "func-names": 0, 152 | "func-style": 0, 153 | "key-spacing": [ 154 | 2, 155 | { 156 | "beforeColon": false, 157 | "afterColon": true 158 | } 159 | ], 160 | "max-nested-callbacks": 0, 161 | "new-cap": 0, 162 | "new-parens": 2, 163 | "newline-after-var": 0, 164 | "no-array-constructor": 2, 165 | "no-inline-comments": 0, 166 | "no-lonely-if": 2, 167 | "no-mixed-spaces-and-tabs": 2, 168 | "no-multiple-empty-lines": 0, 169 | "no-nested-ternary": 0, 170 | "no-new-object": 2, 171 | "no-spaced-func": 2, 172 | "no-ternary": 0, 173 | "no-trailing-spaces": 0, 174 | "no-underscore-dangle": 0, 175 | "one-var": [ 176 | 2, 177 | "never" 178 | ], 179 | "operator-assignment": [ 180 | 2, 181 | "always" 182 | ], 183 | "padded-blocks": 0, 184 | "quote-props": [ 185 | 2, 186 | "as-needed" 187 | ], 188 | "quotes": [ 189 | 2, 190 | "single" 191 | ], 192 | "semi": [ 193 | 2, 194 | "always" 195 | ], 196 | "semi-spacing": [ 197 | 2, 198 | { 199 | "before": false, 200 | "after": true 201 | } 202 | ], 203 | "sort-vars": 0, 204 | "space-before-blocks": [ 205 | 2, 206 | "always" 207 | ], 208 | "space-before-function-paren": [ 209 | 2, 210 | { 211 | "anonymous": "always", 212 | "named": "never" 213 | } 214 | ], 215 | "space-in-brackets": 0, 216 | "space-in-parens": 0, 217 | "space-infix-ops": [ 218 | 2, 219 | { 220 | "int32Hint": false 221 | } 222 | ], 223 | "space-unary-ops": [ 224 | 2, 225 | { 226 | "words": true, 227 | "nonwords": false 228 | } 229 | ], 230 | "spaced-comment": [ 231 | 2, 232 | "always" 233 | ], 234 | "wrap-regex": 0, 235 | "no-var": 0, 236 | "max-len": [2, 80, 4] 237 | } 238 | } -------------------------------------------------------------------------------- /sequelize/schema.js: -------------------------------------------------------------------------------- 1 | /** 2 | * GraphQL Library 3 | */ 4 | import { 5 | GraphQLSchema, 6 | GraphQLObjectType, 7 | GraphQLString, 8 | } from 'graphql'; 9 | 10 | 11 | 12 | 13 | /** 14 | * GraphQL-Relay Modules 15 | */ 16 | import { 17 | nodeDefinitions, 18 | fromGlobalId, 19 | globalIdField, 20 | connectionFromPromisedArray, 21 | connectionArgs, 22 | connectionDefinitions, 23 | } from 'graphql-relay'; 24 | 25 | import { 26 | getAll, 27 | mappedArray 28 | } from '../data/methods'; 29 | 30 | import { 31 | models 32 | } from '../../sequelize'; 33 | 34 | const { Person, Article } = models; 35 | 36 | 37 | /** 38 | * We get the node interface and field from the relay library. 39 | * 40 | * The first method is the way we resolve an ID to its object. 41 | * The second is the way we resolve an object that implements node to its type. 42 | */ 43 | var {nodeInterface, nodeField} = nodeDefinitions( 44 | (globalId) => { 45 | var {type, id} = fromGlobalId(globalId); 46 | // console.log('nodeDefinitions', type, id, globalId); 47 | switch (type) { 48 | case 'Person': 49 | return Person.findByPrimary(id); 50 | case 'Article': 51 | return Article.findByPrimary(id); 52 | default: 53 | return null; 54 | } 55 | }, 56 | (obj) => { 57 | switch (obj.type) { 58 | case 'personType': 59 | return personType; 60 | case 'articleType': 61 | return articleType; 62 | default: 63 | return null; 64 | } 65 | } 66 | ); 67 | 68 | var articleType = new GraphQLObjectType({ 69 | name: 'Article', 70 | description: 'An article, such as a news article or piece of ' + 71 | 'investigative report. Newspapers and magazines have articles of many ' + 72 | 'different types and this is intended to cover them all.', 73 | fields: () => ({ 74 | id: globalIdField(), 75 | articleBody: { 76 | type: GraphQLString, 77 | description: 'The actual body of the article.', 78 | resolve: article => article.articleBody 79 | }, 80 | articleSection: { 81 | type: GraphQLString, 82 | description: 'Articles may belong to one or more "sections" in a ' + 83 | 'magazine or newspaper, such as Sports, Lifestyle, etc.', 84 | resolve: article => article.articleSection 85 | }, 86 | headline: { 87 | description: 'Headline of the article.', 88 | type: GraphQLString, 89 | resolve: article => article.headline() 90 | }, 91 | thumbnailUrl: { 92 | description: 'A URL path to the thumbnail image relevant to ' + 93 | 'the Article.', 94 | type: GraphQLString, 95 | resolve: article => article.thumbnailUrl 96 | }, 97 | author: { 98 | description: 'Returns the Comment that has been articleged or null ' + 99 | 'if it is not a comment.', 100 | type: personType, 101 | resolve: article => article.getAuthor() 102 | } 103 | }), 104 | interfaces: [nodeInterface] 105 | }); 106 | 107 | var {connectionType: articleConnection} = 108 | connectionDefinitions({nodeType: articleType}); 109 | 110 | var personType = new GraphQLObjectType({ 111 | name: 'Vote', 112 | description: 'A vote object that is applied to posts, questions, ' + 113 | 'answers, comments, etc', 114 | fields: () => ({ 115 | id: globalIdField(), 116 | address: { 117 | type: GraphQLString, 118 | description: 'Physical address of the item.', 119 | resolve: person => person.address 120 | }, 121 | email: { 122 | type: GraphQLString, 123 | description: 'Email address', 124 | resolve: person => person.email 125 | }, 126 | familyName: { 127 | type: GraphQLString, 128 | description: 'Family name. In the U.S., the last name of an Person. ' + 129 | 'This can be used along with givenName instead of the name property.', 130 | resolve: person => person.familyName 131 | }, 132 | givenName: { 133 | type: GraphQLString, 134 | description: 'Given name. In the U.S., the first name of a Person. ' + 135 | 'This can be used along with familyName instead of the name property.', 136 | resolve: person => person.givenName 137 | }, 138 | honorificPrefix: { 139 | type: GraphQLString, 140 | description: 'An honorific prefix preceding a Person\'s name such as ' + 141 | 'Dr/Mrs/Mr.', 142 | resolve: person => person.honorificPrefix 143 | }, 144 | honorificSuffix: { 145 | type: GraphQLString, 146 | description: 'An honorific suffix preceding a Person\'s name such as ' + 147 | 'M.D. /PhD/MSCSW.', 148 | resolve: person => person.honorificSuffix 149 | }, 150 | jobTitle: { 151 | type: GraphQLString, 152 | description: 'The job title of the person ' + 153 | '(for example, Financial Manager).', 154 | resolve: person => person.jobTitle 155 | }, 156 | telephone: { 157 | type: GraphQLString, 158 | description: 'The telephone number.', 159 | resolve: person => person.telephone 160 | }, 161 | articlesAuthored: { 162 | type: articleConnection, 163 | args: connectionArgs, 164 | resolve: (person, args) => 165 | connectionFromPromisedArray(mappedArray(person.getArticles()), args) 166 | } 167 | }), 168 | interfaces: [nodeInterface] 169 | }); 170 | 171 | 172 | var {connectionType: personConnection} = 173 | connectionDefinitions({nodeType: personType}); 174 | 175 | 176 | var queryType = new GraphQLObjectType({ 177 | name: 'Query', 178 | fields: () => ({ 179 | people: { 180 | decription: 'People', 181 | type: personConnection, 182 | args: connectionArgs, 183 | resolve: (root, args) => 184 | connectionFromPromisedArray(mappedArray(getAll(Person)), args) 185 | }, 186 | articles: { 187 | decription: 'Articles', 188 | type: personConnection, 189 | args: connectionArgs, 190 | resolve: (root, args) => 191 | connectionFromPromisedArray(mappedArray(getAll(Person)), args) 192 | }, 193 | node: nodeField 194 | }) 195 | }); 196 | 197 | export default new GraphQLSchema({ 198 | query: queryType 199 | }); 200 | -------------------------------------------------------------------------------- /docs/References/nodedefinitions.md: -------------------------------------------------------------------------------- 1 | # nodeDefinitions 2 | 3 | `nodeDefinitions` are configured in the schema.js file. 4 | 5 | ### Module Imports 6 | 7 | ```javascript 8 | import { 9 | nodeDefinitions, 10 | fromGlobalId, 11 | globalIdField, 12 | connectionArgs, 13 | connectionDefinitions, 14 | } from 'graphql-relay'; 15 | ``` 16 | 17 | 18 | ### Defining nodeInterface and nodeField 19 | The following is an excerpt for a prototype version of [wanted-tuts.com](https://wanted-tuts.com) 20 | 21 | ```javascript 22 | /** 23 | * We get the node interface and field from the relay library. 24 | * 25 | * The first method is the way we resolve an ID to its object. 26 | * The second is the way we resolve an object that implements node to its type. 27 | */ 28 | var {nodeInterface, nodeField} = nodeDefinitions( 29 | (globalId) => { 30 | var {type, id} = fromGlobalId(globalId); 31 | // console.log('nodeDefinitions', type, id, globalId); 32 | switch (type) { 33 | case 'User': 34 | return getUser(id); 35 | case 'Comment': 36 | return getComment(id); 37 | case 'Tag': 38 | return getTag(id); 39 | case 'Vote': 40 | return getVote(id); 41 | case 'Flag': 42 | return getFlag(id); 43 | case 'Tutorial': 44 | return getTutorial(id); 45 | case 'StormUser': 46 | return getStormUser(id); 47 | case 'Request': 48 | return getRequest(id); 49 | default: 50 | return null; 51 | } 52 | }, 53 | (obj) => { 54 | switch (obj.type) { 55 | case 'userType': 56 | return userType; 57 | case 'commentType': 58 | return commentType; 59 | case 'tagType': 60 | return tagType; 61 | case 'voteType': 62 | return voteType; 63 | case 'flagType': 64 | return flagType; 65 | case 'tutorialType': 66 | return tutorialType; 67 | case 'StormUser': 68 | return StormUser; 69 | case 'requestType': 70 | return requestType; 71 | default: 72 | return null; 73 | } 74 | } 75 | ); 76 | ``` 77 | 78 | ### nodeField usage in the wild 79 | nodeField appears to be used at the value of `node` in a root query. **(see the bottom of this snippet)** 80 | ```javascript 81 | var queryType = new GraphQLObjectType({ 82 | name: 'Query', 83 | fields: () => ({ 84 | users: { 85 | decription: 'Sitewide users', 86 | type: userConnection, 87 | args: connectionArgs, 88 | resolve: (root, args) => 89 | connectionFromPromisedArray(resolveModelsByClass(User), args) 90 | }, 91 | comments: { 92 | decription: 'Sitewide User comments', 93 | type: commentConnection, 94 | args: connectionArgs, 95 | resolve: (root, args) => 96 | connectionFromPromisedArray(resolveModelsByClass(Comment), args) 97 | }, 98 | tags: { 99 | decription: 'Sitewide tags used for categorizing posts.', 100 | type: tagConnection, 101 | args: connectionArgs, 102 | resolve: (root, args) => 103 | connectionFromPromisedArray(resolveModelsByClass(Tag), args) 104 | }, 105 | votes: { 106 | decription: 'Sitewide votes across the site.', 107 | type: voteConnection, 108 | args: connectionArgs, 109 | resolve: (root, args) => 110 | connectionFromPromisedArray(resolveModelsByClass(Vote), args) 111 | }, 112 | flags: { 113 | decription: 'Sitewide flags across the site.', 114 | type: flagConnection, 115 | args: connectionArgs, 116 | resolve: (root, args) => 117 | connectionFromPromisedArray(resolveModelsByClass(Flag), args) 118 | }, 119 | tutorials: { 120 | description: 'Tutorials added to the site as a request fufillment or ' + 121 | 'anything else.', 122 | type: tutorialConnection, 123 | args: connectionArgs, 124 | resolve: (root, args) => 125 | connectionFromPromisedArray(resolveModelsByClass(Tutorial), args) 126 | }, 127 | requests: { 128 | decription: 'Tutorial Request Posts', 129 | args: connectionArgs, 130 | type: requestConnection, 131 | resolve: (root, args) => 132 | connectionFromPromisedArray(resolveModelsByClass(Request), args) 133 | }, 134 | node: nodeField // <- Bam 135 | }) 136 | }); 137 | ``` 138 | 139 | 140 | ### nodeInterface usage 141 | `nodeInterface` appears to be used for every query type that is not root. 142 | 143 | for example: 144 | 145 | ```javascript 146 | var requestType = new GraphQLObjectType({ 147 | name: 'Request', 148 | description: 'Tutorial Request', 149 | fields: () => ({ 150 | id: globalIdField(), 151 | url: { 152 | type: GraphQLString, 153 | description: 'href location to the tutorial request page.', 154 | resolve: request => request.url 155 | }, 156 | dateCreated: { 157 | type: GraphQLString, 158 | description: 'The date on which the Tutorial Request was created ' + 159 | 'or the item was added to a DataFeed.', 160 | resolve: request => request.createdAt 161 | }, 162 | dateModified: { 163 | type: GraphQLString, 164 | description: 'The date on which the Tag was most recently modified ' + 165 | 'or when the item\'s entry was modified within a DataFeed.', 166 | resolve: request => request.updatedAt 167 | }, 168 | author: { 169 | type: userType, 170 | description: 'The user who authored this request.', 171 | resolve: request => request.getAuthor() 172 | }, 173 | headline: { 174 | type: GraphQLString, 175 | description: 'Also considered the title of this request.', 176 | resolve: request => request.headline 177 | }, 178 | slug: { 179 | type: GraphQLString, 180 | description: 'The headline sluggified for creating seo friendly url.', 181 | resolve: request => slugify(request.updatedAt) 182 | }, 183 | content: { 184 | type: GraphQLString, 185 | description: 'The main body content of the tutorial request, ' + 186 | 'contains markdown syntax.', 187 | resolve: request => request.content 188 | }, 189 | comments: { 190 | type: commentConnection, 191 | description: 'List of comments posted on this tutorial request.', 192 | args: connectionArgs, 193 | resolve: (request, args) => 194 | connectionFromPromisedArray(mappedArray(request.getComments()), args) 195 | }, 196 | tags: { 197 | type: tagConnection, 198 | description: 'List of tags posted on this tutorial request.', 199 | args: connectionArgs, 200 | resolve: (request, args) => 201 | connectionFromPromisedArray(mappedArray(request.getTags()), args) 202 | }, 203 | tutorials: { 204 | type: tutorialConnection, 205 | description: 'List of tutorials posted as replies to this tutorial ' + 206 | 'request.', 207 | args: connectionArgs, 208 | resolve: (request, args) => 209 | connectionFromPromisedArray(mappedArray(request.getTutorials()), args) 210 | }, 211 | votes: { 212 | type: voteConnection, 213 | description: 'List of vote objects containing the users and ' + 214 | 'their votes.', 215 | args: connectionArgs, 216 | resolve: (request, args) => 217 | connectionFromPromisedArray(mappedArray(request.getComments()), args) 218 | }, 219 | flags: { 220 | type: flagConnection, 221 | description: 'List of flag objects containing the users and ' + 222 | 'their flags.', 223 | args: connectionArgs, 224 | resolve: (request, args) => 225 | connectionFromPromisedArray(mappedArray(request.getFlags()), args) 226 | }, 227 | score: { 228 | type: GraphQLInt, 229 | description: 'Total score of the post after considering all of ' + 230 | 'the down/up votes.', 231 | resolve: request => request.score 232 | }, 233 | downVoteCount: { 234 | type: GraphQLInt, 235 | description: 'Sum total of down votes', 236 | resolve: request => request.downVoteCount 237 | }, 238 | upVoteCount: { 239 | type: GraphQLInt, 240 | description: 'Sum total of up votes', 241 | resolve: request => request.upVoteCount 242 | } 243 | }), 244 | interfaces: [nodeInterface] // <- nodeInterface 245 | }); 246 | ``` 247 | 248 | 249 | ### nodeType and connectDefinitions 250 | The following code shows `nodeType` in use via `connectionDefinitions` 251 | 252 | ```javascript 253 | var {connectionType: flagConnection} = 254 | connectionDefinitions({nodeType: flagType}); 255 | ``` 256 | 257 | .. and so on... 258 | 259 | ```javascript 260 | var {connectionType: fooConnection} = 261 | connectionDefinitions({nodeType: fooType}); 262 | ``` 263 | 264 | -------------------------------------------------------------------------------- /docs/graphql/using_nodedefinitions.md: -------------------------------------------------------------------------------- 1 | # GraphQL-Relay - Using nodeDefinitions 2 | ![GraphQL Logo](graphql.jpg) 3 | 4 | This section is about using [graphql-relay-js](https://github.com/graphql/graphql-relay-js) with sequalize-relay. 5 | 6 | --- 7 | 8 | This guide aims to help explain how to connect to `nodeDefinitions` with `sequelize`. 9 | 10 | ## Setting up relay nodes: 11 | 12 | We can use `nodeDefinitions` from `graphql-relay-js` in conjunction with `sequelize` helpers to setup the node definition. This setup does not need the `sequelize-relay` library. 13 | 14 | What we need to do is wire up a `sequelize` model to the `nodeDefinitions` function. 15 | 16 | ## import the modules: 17 | 18 | ```javascript 19 | import { 20 | nodeDefinitions, 21 | fromGlobalId 22 | } from 'graphql-relay-js'; 23 | ``` 24 | 25 | I have personally used the following boilerplate to setup the `nodeDefinitions`. I welcome other implementations, but the first step for me is to copy and paste the following code into the `schema` file 26 | 27 | 28 | ## nodeDefinitions boilerplate: 29 | ```javascript 30 | /** 31 | * We get the node interface and field from the relay library. 32 | * 33 | * The first method is the way we resolve an ID to its object. 34 | * The second is the way we resolve an object that implements 35 | * node to its type. 36 | */ 37 | var {nodeInterface, nodeField} = nodeDefinitions( 38 | (globalId) => { 39 | var {type, id} = fromGlobalId(globalId); 40 | switch (type) { 41 | // we will use sequelize to resolve the id of its object 42 | default: 43 | return null; 44 | } 45 | }, 46 | (obj) => { 47 | // we will use sequelize to resolve the object tha timplements node 48 | // to its type. 49 | switch (obj.type) { 50 | default: 51 | return null; 52 | } 53 | } 54 | ); 55 | ``` 56 | 57 | 58 | > You can quickly setup sequelize by following [this guide](../sequelize/quick_setup_md) or by reading the [sequelize "GettingStarted" Guide](http://docs.sequelizejs.com/en/latest/docs/getting-started/)._ 59 | 60 | 61 | Consider a sequelize model called `person` with the code below: 62 | 63 | ### models/Person.js 64 | ```javascript 65 | 66 | module.exports = function (sequelize, DataTypes) { 67 | 68 | var Person = sequelize.define('Person', { 69 | address: { 70 | type: DataTypes.STRING, 71 | description: 'Physical address of the person.' 72 | }, 73 | email: { 74 | type: DataTypes.STRING, 75 | description: 'Email address', 76 | validate: { 77 | isEmail: true 78 | } 79 | }, 80 | givenName: { 81 | type: DataTypes.STRING, 82 | description: 'Given name. In the U.S., the first name of a Person. ' + 83 | 'This can be used along with familyName instead of the name property.' 84 | } 85 | }); 86 | return Person; 87 | }; 88 | 89 | ``` 90 | 91 | To connect with `nodeDefinitions`, we need to add virtual types to our `sequelize` model schema: 92 | 93 | #### Add personType VIRTUAL to the model 94 | **This adds a field without putting it in the SQL table** 95 | 96 | 97 | ```javascript 98 | type: { 99 | type: new DataTypes.VIRTUAL(DataTypes.STRING), 100 | get() { 101 | return 'personType'; 102 | } 103 | } 104 | ``` 105 | 106 | ### models/Person.js with type added 107 | ```javascript 108 | 109 | module.exports = function (sequelize, DataTypes) { 110 | 111 | var Person = sequelize.define('Person', { 112 | type: { 113 | type: new DataTypes.VIRTUAL(DataTypes.STRING), 114 | get() { 115 | return 'personType'; 116 | } 117 | }, 118 | address: { 119 | type: DataTypes.STRING, 120 | description: 'Physical address of the person.' 121 | }, 122 | email: { 123 | type: DataTypes.STRING, 124 | description: 'Email address', 125 | validate: { 126 | isEmail: true 127 | } 128 | }, 129 | givenName: { 130 | type: DataTypes.STRING, 131 | description: 'Given name. In the U.S., the first name of a Person. ' + 132 | 'This can be used along with familyName instead of the name property.' 133 | } 134 | }); 135 | return Person; 136 | }; 137 | 138 | ``` 139 | 140 | 141 | By adding the `type` field returning `personType` - we can then wire it up to `nodeDefinitions` like so: 142 | 143 | 144 | ## nodeDefintions Update 145 | 146 | ```javascript 147 | /** 148 | * We get the node interface and field from the relay library. 149 | * 150 | * The first method is the way we resolve an ID to its object. 151 | * The second is the way we resolve an object that implements 152 | * node to its type. 153 | */ 154 | var {nodeInterface, nodeField} = nodeDefinitions( 155 | (globalId) => { 156 | var {type, id} = fromGlobalId(globalId); 157 | switch (type) { 158 | case 'Person': 159 | return Person.findByPrimary(id); 160 | default: 161 | return null; 162 | } 163 | }, 164 | (obj) => { 165 | // we will use sequelize to resolve the object tha timplements node 166 | // to its type. 167 | switch (obj.type) { 168 | case 'personType': 169 | return personType; 170 | default: 171 | return null; 172 | } 173 | } 174 | ); 175 | ``` 176 | 177 | ### Import more libraries 178 | At the top of the file, we want to import other libraries that will be in use by `queryType` and `personType`, as well as connection helpers. 179 | 180 | ```javascript 181 | import { 182 | GraphQLObjectType, 183 | GraphQLSchema, 184 | GraphQLString, 185 | graphql, 186 | } from 'graphql'; 187 | 188 | 189 | import { 190 | nodeDefinitions, 191 | fromGlobalId, 192 | globalIdField, 193 | connectionFromPromisedArray, 194 | connectionArgs, 195 | connectionDefinitions, 196 | } from 'graphql-relay'; 197 | 198 | import { 199 | getModelsByClass, 200 | resolveArrayData, 201 | getArrayData, 202 | resolveArrayByClass, 203 | resolveModelsByClass 204 | } from 'sequelize-relay'; 205 | 206 | ``` 207 | 208 | 209 | ### Define Person and personType 210 | Well the code above doesnt explain where Person or personType is coming from. This shall be demystified now: 211 | 212 | 213 | - Person is the Model Class that we created earlier from Person.js 214 | - personType is a GraphQL type schema that needs to be created. 215 | 216 | 217 | ** Adding person** 218 | ```javascript 219 | import { Person } from './models'; 220 | ``` 221 | ** Adding personType ** 222 | 223 | ```javascript 224 | 225 | var personType = new GraphQLObjectType({ 226 | name: 'Person', 227 | description: 'A Person', 228 | fields: () => ({ 229 | id: globalIdField(), 230 | address: { 231 | type: GraphQLString, 232 | description: 'Physical address of the item.', 233 | resolve: person => person.address 234 | }, 235 | email: { 236 | type: GraphQLString, 237 | description: 'Email address', 238 | resolve: person => person.email 239 | }, 240 | givenName: { 241 | type: GraphQLString, 242 | description: 'Given name. In the U.S., the first name of an Person.', 243 | resolve: person => person.familyName 244 | } 245 | }), 246 | interfaces: [nodeInterface] // <-- Hooking up the nodeInterface 247 | }); 248 | ``` 249 | 250 | ### add Connection 251 | > The connection methods below are also in the [Using connections](./using_connections.md) Guide. 252 | 253 | ```javascript 254 | var {connectionType: personConnection} = 255 | connectionDefinitions({nodeType: personType}); 256 | ``` 257 | 258 | ### queryType 259 | ```javascript 260 | var queryType = new GraphQLObjectType({ 261 | name: 'Query', 262 | fields: () => ({ 263 | people: { 264 | description: 'People', 265 | type: personConnection, 266 | args: connectionArgs, 267 | resolve: (root, args) => 268 | connectionFromPromisedArray(resolveArrayByClass(Person), args) 269 | } 270 | node: nodeField 271 | }) 272 | }); 273 | ``` 274 | > The connection methods shown above are explained further in the [Using connections](./using_connections.md) Guide. 275 | 276 | ### GraphQL Query 277 | You can now use the node like so: 278 | 279 | ```graphQL 280 | query PersonRefetchQuery { 281 | node(id: "UGVyc29uOjI=") { 282 | id 283 | ... on Person { 284 | id 285 | givenName 286 | email 287 | address 288 | } 289 | } 290 | ``` 291 | #### returns => 292 | 293 | ```json 294 | 295 | data: { 296 | node: { 297 | id: 'UGVyc29uOjI=', 298 | email: 'Creola5@gmail.com', 299 | honorificPrefix: 'Miss', 300 | honorificSuffix: 'DVM', 301 | jobTitle: 'Lead Creative Executive', 302 | telephone: '718-964-7388 x29503', 303 | givenName: 'Amir', 304 | familyName: 'Schmeler', 305 | address: '197 Mina Gardens' 306 | } 307 | } 308 | 309 | ``` 310 | 311 | Look at the [test-specs here](https://github.com/MattMcFarland/sequelize-relay/blob/master/src/data/__tests__/connections.js#L811-L850) to see how this works without brevity. 312 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Emacs template 3 | # -*- mode: gitignore; -*- 4 | 5 | \#*\# 6 | /.emacs.desktop 7 | /.emacs.desktop.lock 8 | *.elc 9 | auto-save-list 10 | tramp 11 | .\#* 12 | 13 | # Org-mode 14 | .org-id-locations 15 | *_archive 16 | 17 | # flymake-mode 18 | *_flymake.* 19 | 20 | # eshell files 21 | /eshell/history 22 | /eshell/lastdir 23 | 24 | # elpa packages 25 | /elpa/ 26 | 27 | # reftex files 28 | *.rel 29 | 30 | # AUCTeX auto folder 31 | /auto/ 32 | 33 | # cask packages 34 | .cask/ 35 | ### VisualStudio template 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.userosscache 43 | *.sln.docstates 44 | 45 | # User-specific files (MonoDevelop/Xamarin Studio) 46 | *.userprefs 47 | 48 | # Build results 49 | [Dd]ebug/ 50 | [Dd]ebugPublic/ 51 | [Rr]elease/ 52 | [Rr]eleases/ 53 | x64/ 54 | x86/ 55 | build/ 56 | bld/ 57 | [Bb]in/ 58 | [Oo]bj/ 59 | 60 | # Visual Studio 2015 cache/options directory 61 | .vs/ 62 | # Uncomment if you have tasks that create the project's static files in wwwroot 63 | #wwwroot/ 64 | 65 | # MSTest test Results 66 | [Tt]est[Rr]esult*/ 67 | [Bb]uild[Ll]og.* 68 | 69 | # NUNIT 70 | *.VisualState.xml 71 | TestResult.xml 72 | 73 | # Build Results of an ATL Project 74 | [Dd]ebugPS/ 75 | [Rr]eleasePS/ 76 | dlldata.c 77 | 78 | # DNX 79 | project.lock.json 80 | artifacts/ 81 | 82 | *_i.c 83 | *_p.c 84 | *_i.h 85 | *.ilk 86 | *.meta 87 | *.obj 88 | *.pch 89 | *.pdb 90 | *.pgc 91 | *.pgd 92 | *.rsp 93 | *.sbr 94 | *.tlb 95 | *.tli 96 | *.tlh 97 | *.tmp 98 | *.vspscc 99 | *.vssscc 100 | .builds 101 | *.pidb 102 | *.svclog 103 | *.scc 104 | 105 | # Chutzpah Test files 106 | _Chutzpah* 107 | 108 | # Visual C++ cache files 109 | ipch/ 110 | *.aps 111 | *.ncb 112 | *.opensdf 113 | *.sdf 114 | *.cachefile 115 | 116 | # Visual Studio profiler 117 | *.psess 118 | *.vsp 119 | *.vspx 120 | 121 | # TFS 2012 Local Workspace 122 | $tf/ 123 | 124 | # Guidance Automation Toolkit 125 | *.gpState 126 | 127 | # ReSharper is a .NET coding add-in 128 | _ReSharper*/ 129 | *.[Rr]e[Ss]harper 130 | *.DotSettings.user 131 | 132 | # JustCode is a .NET coding add-in 133 | .JustCode 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # NCrunch 142 | _NCrunch_* 143 | .*crunch*.local.xml 144 | nCrunchTemp_* 145 | 146 | # MightyMoose 147 | *.mm.* 148 | AutoTest.Net/ 149 | 150 | # Web workbench (sass) 151 | .sass-cache/ 152 | 153 | # Installshield output folder 154 | [Ee]xpress/ 155 | 156 | # DocProject is a documentation generator add-in 157 | DocProject/buildhelp/ 158 | DocProject/Help/*.HxT 159 | DocProject/Help/*.HxC 160 | DocProject/Help/*.hhc 161 | DocProject/Help/*.hhk 162 | DocProject/Help/*.hhp 163 | DocProject/Help/Html2 164 | DocProject/Help/html 165 | 166 | # Click-Once directory 167 | publish/ 168 | 169 | # Publish Web Output 170 | *.[Pp]ublish.xml 171 | *.azurePubxml 172 | # but database connection strings (with potential passwords) will be unencrypted 173 | *.pubxml 174 | *.publishproj 175 | 176 | # NuGet Packages 177 | *.nupkg 178 | # The packages folder can be ignored because of Package Restore 179 | **/packages/* 180 | # except build/, which is used as an MSBuild target. 181 | !**/packages/build/ 182 | # Uncomment if necessary however generally it will be regenerated when needed 183 | #!**/packages/repositories.config 184 | 185 | # Windows Azure Build Output 186 | csx/ 187 | *.build.csdef 188 | 189 | # Windows Store app package directory 190 | AppPackages/ 191 | 192 | # Visual Studio cache files 193 | # files ending in .cache can be ignored 194 | *.[Cc]ache 195 | # but keep track of directories ending in .cache 196 | !*.[Cc]ache/ 197 | 198 | # Others 199 | 200 | *.dbmdl 201 | *.dbproj.schemaview 202 | *.pfx 203 | *.publishsettings 204 | node_modules/ 205 | orleans.codegen.cs 206 | 207 | # RIA/Silverlight projects 208 | Generated_Code/ 209 | 210 | # Backup & report files from converting an old project file 211 | # to a newer Visual Studio version. Backup files are not needed, 212 | # because we have git ;-) 213 | _UpgradeReport_Files/ 214 | Backup*/ 215 | UpgradeLog*.XML 216 | UpgradeLog*.htm 217 | 218 | # SQL Server files 219 | *.mdf 220 | *.ldf 221 | 222 | # Business Intelligence projects 223 | *.rdl.data 224 | *.bim.layout 225 | *.bim_*.settings 226 | 227 | # Microsoft Fakes 228 | FakesAssemblies/ 229 | 230 | # Node.js Tools for Visual Studio 231 | .ntvs_analysis.dat 232 | 233 | # Visual Studio 6 build log 234 | *.plg 235 | 236 | # Visual Studio 6 workspace options file 237 | *.opt 238 | 239 | # Visual Studio LightSwitch build output 240 | **/*.HTMLClient/GeneratedArtifacts 241 | **/*.DesktopClient/GeneratedArtifacts 242 | **/*.DesktopClient/ModelManifest.xml 243 | **/*.Server/GeneratedArtifacts 244 | **/*.Server/ModelManifest.xml 245 | _Pvt_Extensions 246 | ### Windows template 247 | # Windows image file caches 248 | Thumbs.db 249 | ehthumbs.db 250 | 251 | # Folder config file 252 | Desktop.ini 253 | 254 | # Recycle Bin used on file shares 255 | $RECYCLE.BIN/ 256 | 257 | # Windows Installer files 258 | *.cab 259 | *.msi 260 | *.msm 261 | *.msp 262 | 263 | # Windows shortcuts 264 | *.lnk 265 | ### Vim template 266 | [._]*.s[a-w][a-z] 267 | [._]s[a-w][a-z] 268 | *.un~ 269 | Session.vim 270 | .netrwhist 271 | 272 | ### Xcode template 273 | # Xcode 274 | # 275 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 276 | 277 | ## Build generated 278 | DerivedData 279 | 280 | ## Various settings 281 | *.pbxuser 282 | !default.pbxuser 283 | *.mode1v3 284 | !default.mode1v3 285 | *.mode2v3 286 | !default.mode2v3 287 | *.perspectivev3 288 | !default.perspectivev3 289 | xcuserdata 290 | 291 | ## Other 292 | *.xccheckout 293 | *.moved-aside 294 | *.xcuserstate 295 | ### Node template 296 | # Logs 297 | logs 298 | 299 | npm-debug.log* 300 | 301 | # Runtime data 302 | pids 303 | *.pid 304 | *.seed 305 | 306 | # Directory for instrumented libs generated by jscoverage/JSCover 307 | lib-cov 308 | 309 | # Coverage directory used by tools like istanbul 310 | coverage 311 | 312 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 313 | .grunt 314 | 315 | # node-waf configuration 316 | .lock-wscript 317 | 318 | # Compiled binary addons (http://nodejs.org/api/addons.html) 319 | build/Release 320 | 321 | # Dependency directory 322 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 323 | 324 | 325 | ### JetBrains template 326 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 327 | 328 | *.iml 329 | 330 | ## Directory-based project format: 331 | .idea/ 332 | # if you remove the above rule, at least ignore the following: 333 | 334 | # User-specific stuff: 335 | # .idea/workspace.xml 336 | # .idea/tasks.xml 337 | # .idea/dictionaries 338 | 339 | # Sensitive or high-churn files: 340 | # .idea/dataSources.ids 341 | # .idea/dataSources.xml 342 | # .idea/sqlDataSources.xml 343 | # .idea/dynamic.xml 344 | # .idea/uiDesigner.xml 345 | 346 | # Gradle: 347 | # .idea/gradle.xml 348 | # .idea/libraries 349 | 350 | # Mongo Explorer plugin: 351 | # .idea/mongoSettings.xml 352 | 353 | ## File-based project format: 354 | *.ipr 355 | *.iws 356 | 357 | ## Plugin-specific files: 358 | 359 | # IntelliJ 360 | /out/ 361 | 362 | # mpeltonen/sbt-idea plugin 363 | .idea_modules/ 364 | 365 | # JIRA plugin 366 | atlassian-ide-plugin.xml 367 | 368 | # Crashlytics plugin (for Android Studio and IntelliJ) 369 | com_crashlytics_export_strings.xml 370 | crashlytics.properties 371 | crashlytics-build.properties 372 | ### Cloud9 template 373 | # Cloud9 IDE - http://c9.io 374 | .c9revisions 375 | .c9 376 | ### Linux template 377 | 378 | # KDE directory preferences 379 | .directory 380 | 381 | # Linux trash folder which might appear on any partition or disk 382 | .Trash-* 383 | ### Eclipse template 384 | *.pydevproject 385 | .metadata 386 | .gradle 387 | bin/ 388 | *.bak 389 | *.swp 390 | *~.nib 391 | local.properties 392 | .settings/ 393 | .loadpath 394 | 395 | # Eclipse Core 396 | .project 397 | 398 | # External tool builders 399 | .externalToolBuilders/ 400 | 401 | # Locally stored "Eclipse launch configurations" 402 | *.launch 403 | 404 | # CDT-specific 405 | .cproject 406 | 407 | # JDT-specific (Eclipse Java Development Tools) 408 | .classpath 409 | 410 | # Java annotation processor (APT) 411 | .factorypath 412 | 413 | # PDT-specific 414 | .buildpath 415 | 416 | # sbteclipse plugin 417 | .target 418 | 419 | # TeXlipse plugin 420 | .texlipse 421 | ### OSX template 422 | .DS_Store 423 | .AppleDouble 424 | .LSOverride 425 | 426 | # Icon must end with two \r 427 | Icon 428 | 429 | # Thumbnails 430 | ._* 431 | 432 | # Files that might appear in the root of a volume 433 | .DocumentRevisions-V100 434 | .fseventsd 435 | .Spotlight-V100 436 | .TemporaryItems 437 | .Trashes 438 | .VolumeIcon.icns 439 | 440 | # Directories potentially created on remote AFP share 441 | .AppleDB 442 | .AppleDesktop 443 | Network Trash Folder 444 | Temporary Items 445 | .apdisk 446 | 447 | db.development.sqlite 448 | db.test.sqlite 449 | 450 | raw*.json 451 | 452 | lib -------------------------------------------------------------------------------- /docs/graphql/using_connections.md: -------------------------------------------------------------------------------- 1 | # GraphQL-Relay - Using connections 2 | ![GraphQL Logo](graphql.jpg) 3 | 4 | This section is about using [graphql-relay-js](https://github.com/graphql/graphql-relay-js) with sequalize-relay. 5 | 6 | --- 7 | 8 | 9 | Connections are a big part of the graphql-relay-js library, and sequelize-relay works with it in the following ways: 10 | 11 | 12 | ## Setup nodes for connections 13 | `nodeDefinitions` should be configured to allow for using the connection helpers... 14 | 15 | ```javascript 16 | // nodeDefinitions is a sequelize-relay-js method 17 | 18 | /** 19 | * We get the node interface and field from the relay library. 20 | * 21 | * The first method is the way we resolve an ID to its object. 22 | * The second is the way we resolve an object that implements 23 | * node to its type. 24 | */ 25 | var {nodeInterface, nodeField} = nodeDefinitions( 26 | (globalId) => { 27 | var {type, id} = fromGlobalId(globalId); 28 | switch (type) { 29 | case 'Person': 30 | // sequelize method 31 | return Person.findByPrimary(id); 32 | case 'Article': 33 | // sequelize method 34 | return Article.findByPrimary(id); 35 | default: 36 | return null; 37 | 38 | } 39 | }, 40 | (obj) => { 41 | switch (obj.type) { 42 | // the types are pulled from sequelize. 43 | case 'personType': 44 | return personType; 45 | case 'articleType': 46 | return articleType; 47 | 48 | default: 49 | return null; 50 | 51 | } 52 | } 53 | ); 54 | ``` 55 | 56 | For this to work, we need to add virtual types to our sequelize model schema: 57 | 58 | #### Article.js 59 | Article.js is a standard sequelize model with the addition of the `type` field which is a `DataTypes.VIRTUAL` 60 | 61 | Using `DataTypes.VIRTUAL`: 62 | ```javascript 63 | type: { 64 | type: new DataTypes.VIRTUAL(DataTypes.STRING), 65 | get() { 66 | return 'articleType'; 67 | } 68 | } 69 | ``` 70 | 71 | By adding the `type` field returning `articleType` - the node is complete. 72 | 73 | 74 | ```javascript 75 | module.exports = function (sequelize: Sequelize, DataTypes) { 76 | var Article = sequelize.define('Article', { 77 | type: { 78 | type: new DataTypes.VIRTUAL(DataTypes.STRING), 79 | get() { 80 | return 'articleType'; 81 | } 82 | }, 83 | articleBody: { 84 | type: DataTypes.TEXT, 85 | description: 'The actual body of the article.' 86 | }, 87 | articleSection: { 88 | type: DataTypes.STRING, 89 | description: 'Articles may belong to one or more "sections" in a ' + 90 | 'magazine or newspaper, such as Sports, Lifestyle, etc.' 91 | }, 92 | headline: { 93 | type: DataTypes.STRING, 94 | description: 'Headline of the article.' 95 | }, 96 | thumbnailUrl: { 97 | type: DataTypes.STRING, 98 | description: 'A URL path to the thumbnail image relevant to the Article.' 99 | } 100 | }, { 101 | classMethods: { 102 | associate: (models) => { 103 | Article.belongsTo(models.Person, {as: 'Author'}); 104 | } 105 | } 106 | }); 107 | return Article; 108 | }; 109 | 110 | ``` 111 | 112 | #### Person.js 113 | ```javascript 114 | module.exports = function (sequelize, DataTypes) { 115 | 116 | var Person = sequelize.define('Person', { 117 | type: { 118 | type: new DataTypes.VIRTUAL(DataTypes.STRING), 119 | get() { 120 | return 'personType'; 121 | } 122 | }, 123 | additionalName: { 124 | type: DataTypes.STRING, 125 | description: 'An additional name for a Person, can be used for a ' + 126 | 'middle name.' 127 | }, 128 | address: { 129 | type: DataTypes.STRING, 130 | description: 'Physical address of the item.' 131 | }, 132 | email: { 133 | type: DataTypes.STRING, 134 | description: 'Email address', 135 | validate: { 136 | isEmail: true 137 | } 138 | }, 139 | familyName: { 140 | type: DataTypes.STRING, 141 | description: 'Family name. In the U.S., the last name of an Person. ' + 142 | 'This can be used along with givenName instead of the name property.' 143 | }, 144 | givenName: { 145 | type: DataTypes.STRING, 146 | description: 'Given name. In the U.S., the first name of a Person. ' + 147 | 'This can be used along with familyName instead of the name property.' 148 | }, 149 | honorificPrefix: { 150 | type: DataTypes.STRING, 151 | description: 'An honorific prefix preceding a Person\'s name such as ' + 152 | 'Dr/Mrs/Mr.' 153 | }, 154 | honorificSuffix: { 155 | type: DataTypes.STRING, 156 | description: 'An honorific suffix preceding a Person\'s name such as ' + 157 | 'M.D. /PhD/MSCSW.' 158 | }, 159 | jobTitle: { 160 | type: DataTypes.STRING, 161 | description: 'The job title of the person ' + 162 | '(for example, Financial Manager).' 163 | }, 164 | telephone: { 165 | type: DataTypes.STRING, 166 | description: 'The telephone number.' 167 | } 168 | }, { 169 | classMethods: { 170 | associate: (models) => { 171 | Person.hasMany(models.Article, { 172 | foreignKey: 'AuthorId' 173 | }); 174 | } 175 | } 176 | }); 177 | return Person; 178 | }; 179 | ``` 180 | 181 | 182 | The `connections` are then mapped in the `graphQL` schema file(s). 183 | 184 | ### articleType 185 | 186 | ```javascript 187 | var articleType = new GraphQLObjectType({ 188 | name: 'Article', 189 | description: 'An article, such as a news article or piece of ' + 190 | 'investigative report. Newspapers and magazines have articles of many ' + 191 | 'different types and this is intended to cover them all.', 192 | fields: () => ({ 193 | id: globalIdField(), 194 | articleBody: { 195 | type: GraphQLString, 196 | description: 'The actual body of the article.', 197 | resolve: article => article.articleBody 198 | }, 199 | articleSection: { 200 | type: GraphQLString, 201 | description: 'Articles may belong to one or more "sections" in a ' + 202 | 'magazine or newspaper, such as Sports, Lifestyle, etc.', 203 | resolve: article => article.articleSection 204 | }, 205 | headline: { 206 | description: 'Headline of the article.', 207 | type: GraphQLString, 208 | resolve: article => article.headline 209 | }, 210 | thumbnailUrl: { 211 | description: 'A URL path to the thumbnail image relevant to ' + 212 | 'the Article.', 213 | type: GraphQLString, 214 | resolve: article => article.thumbnailUrl 215 | }, 216 | author: { 217 | description: 'Returns the Author or null.', 218 | type: personType, 219 | resolve: article => article.getAuthor() 220 | } 221 | }), 222 | interfaces: [nodeInterface] 223 | }); 224 | ``` 225 | 226 | The relay spec is then applied once we create the connection: 227 | 228 | ```javascript 229 | 230 | var {connectionType: articleConnection} = 231 | connectionDefinitions({nodeType: articleType}); 232 | 233 | ``` 234 | 235 | ## Connection Patterns 236 | You can see the patterns for connections as follows 237 | 238 | ### foo 239 | ```javascript 240 | var fooType = new GraphQLObjectType({ 241 | name: 'Foo', 242 | fields: () => ({ 243 | id: globalIdField(), 244 | someProp: { 245 | type: GraphQLString, 246 | resolve: foo => foo.prop 247 | }, 248 | anotherProp: { 249 | type: GraphQLString, 250 | resolve: foo => foo.anotherProp 251 | } 252 | }), 253 | interfaces: [nodeInterface] 254 | }); 255 | 256 | 257 | var {connectionType: fooConnection} = 258 | connectionDefinitions({nodeType: fooType}); 259 | 260 | 261 | ``` 262 | ### bar 263 | ```javascript 264 | var barType = new GraphQLObjectType({ 265 | name: 'Bar', 266 | fields: () => ({ 267 | id: globalIdField(), 268 | someProp: { 269 | type: GraphQLString, 270 | resolve: bar => bar.prop 271 | }, 272 | anotherProp: { 273 | type: GraphQLString, 274 | resolve: bar => bar.anotherProp 275 | } 276 | }), 277 | fooFriends: { 278 | type: fooConnection, 279 | args: connectionArgs, 280 | resolve: (bar, args) => 281 | connectionFromPromisedArray( 282 | resolveArrayData(bar.getFooFriends()), args 283 | ) 284 | } 285 | interfaces: [nodeInterface] 286 | }); 287 | 288 | var {connectionType: barConnection} = 289 | connectionDefinitions({nodeType: barType}); 290 | ``` 291 | 292 | ### baz 293 | ```javascript 294 | var bazType = new GraphQLObjectType({ 295 | name: 'baz', 296 | fields: () => ({ 297 | id: globalIdField(), 298 | someProp: { 299 | type: GraphQLString, 300 | resolve: baz => baz.prop 301 | }, 302 | anotherProp: { 303 | type: GraphQLString, 304 | resolve: baz => baz.anotherProp 305 | }, 306 | fooFriends: { 307 | type: fooConnection, 308 | args: connectionArgs, 309 | resolve: (baz, args) => 310 | connectionFromPromisedArray( 311 | resolveArrayData(baz.getFooFreindss()), args 312 | ) 313 | } 314 | }), 315 | interfaces: [nodeInterface] 316 | }); 317 | 318 | var {connectionType: bazConnection} = 319 | connectionDefinitions({nodeType: bazType}); 320 | 321 | 322 | ``` 323 | 324 | #### the connectionDefintions again: 325 | They are above, but it might be worth sharing them one more time: 326 | 327 | 328 | ### foo 329 | ```javascript 330 | 331 | var {connectionType: fooConnection} = 332 | connectionDefinitions({nodeType: fooType}); 333 | 334 | 335 | ``` 336 | ### bar 337 | ```javascript 338 | 339 | var {connectionType: barConnection} = 340 | connectionDefinitions({nodeType: barType}); 341 | 342 | 343 | ``` 344 | ### baz 345 | ```javascript 346 | 347 | 348 | var {connectionType: bazConnection} = 349 | connectionDefinitions({nodeType: bazType}); 350 | 351 | 352 | ``` 353 | 354 | ## connectionArgs 355 | 356 | Connections are useful when dealing with relationships. Let's Presume that `getFooFriends` is a valid sequelize method (could arguably be as such). 357 | 358 | 359 | ### baz 360 | ```javascript 361 | var bazType = new GraphQLObjectType({ 362 | name: 'baz', 363 | fields: () => ({ 364 | id: globalIdField(), 365 | someProp: { 366 | type: GraphQLString, 367 | resolve: baz => baz.prop 368 | }, 369 | anotherProp: { 370 | type: GraphQLString, 371 | resolve: baz => baz.anotherProp 372 | }, 373 | fooFriends: { 374 | type: fooConnection, 375 | args: connectionArgs, 376 | resolve: (baz, args) => 377 | connectionFromPromisedArray( 378 | resolveArrayData(baz.getFooFreindss()), args 379 | ) 380 | } 381 | }), 382 | interfaces: [nodeInterface] 383 | }); 384 | 385 | var {connectionType: bazConnection} = 386 | connectionDefinitions({nodeType: bazType}); 387 | ``` 388 | 389 | Notice we are using `connectionArgs`, `connectionDefinitions`, and `noteInterface`? 390 | 391 | ### bar 392 | ```javascript 393 | var barType = new GraphQLObjectType({ 394 | name: 'Bar', 395 | fields: () => ({ 396 | id: globalIdField(), 397 | someProp: { 398 | type: GraphQLString, 399 | resolve: bar => bar.prop 400 | }, 401 | anotherProp: { 402 | type: GraphQLString, 403 | resolve: bar => bar.anotherProp 404 | } 405 | }), 406 | fooFriends: { 407 | type: fooConnection, 408 | args: connectionArgs, 409 | resolve: (bar, args) => 410 | connectionFromPromisedArray( 411 | resolveArrayData(bar.getFooFriends()), args 412 | ) 413 | } 414 | interfaces: [nodeInterface] 415 | }); 416 | 417 | var {connectionType: barConnection} = 418 | connectionDefinitions({nodeType: barType}); 419 | ``` 420 | 421 | Again, we see that we are using `connectionArgs`, `connectionDefinitions`, and `noteInterface`.... 422 | 423 | 424 | Following the patterns depicted above will guarantee you retrieve all of the `relay` `edges` and `pageInfo` - cursors and all.. 425 | 426 | 427 | The only `sequelize-relay` method used for these examples was `resolveArrayData` 428 | 429 | 430 | 431 | 432 | 433 | 434 | -------------------------------------------------------------------------------- /src/data/__tests__/connections.js: -------------------------------------------------------------------------------- 1 | /** 2 | * personConnection.js 3 | */ 4 | 5 | 6 | import { 7 | GraphQLObjectType, 8 | GraphQLSchema, 9 | GraphQLString, 10 | graphql, 11 | } from 'graphql'; 12 | 13 | 14 | import { 15 | nodeDefinitions, 16 | fromGlobalId, 17 | globalIdField, 18 | connectionFromPromisedArray, 19 | connectionArgs, 20 | connectionDefinitions, 21 | } from 'graphql-relay'; 22 | 23 | import { 24 | getModelsByClass, 25 | resolveArrayData, 26 | getArrayData, 27 | resolveArrayByClass, 28 | resolveModelsByClass 29 | } from '../methods'; 30 | 31 | import { 32 | models, connect 33 | } from '../../../sequelize'; 34 | 35 | 36 | import { expect } from 'chai'; 37 | import { describe, it, before } from 'mocha'; 38 | 39 | const { Person, Article } = models; 40 | var {nodeInterface, nodeField} = nodeDefinitions( 41 | (globalId) => { 42 | var {type, id} = fromGlobalId(globalId); 43 | switch (type) { 44 | case 'Person': 45 | return Person.findByPrimary(id); 46 | case 'Article': 47 | return Article.findByPrimary(id); 48 | /* 49 | default: 50 | return null; 51 | */ 52 | } 53 | }, 54 | (obj) => { 55 | switch (obj.type) { 56 | case 'personType': 57 | return personType; 58 | case 'articleType': 59 | return articleType; 60 | /* 61 | default: 62 | return null; 63 | */ 64 | } 65 | } 66 | ); 67 | 68 | var articleType = new GraphQLObjectType({ 69 | name: 'Article', 70 | description: 'An article, such as a news article or piece of ' + 71 | 'investigative report. Newspapers and magazines have articles of many ' + 72 | 'different types and this is intended to cover them all.', 73 | fields: () => ({ 74 | id: globalIdField(), 75 | articleBody: { 76 | type: GraphQLString, 77 | description: 'The actual body of the article.', 78 | resolve: article => article.articleBody 79 | }, 80 | articleSection: { 81 | type: GraphQLString, 82 | description: 'Articles may belong to one or more "sections" in a ' + 83 | 'magazine or newspaper, such as Sports, Lifestyle, etc.', 84 | resolve: article => article.articleSection 85 | }, 86 | headline: { 87 | description: 'Headline of the article.', 88 | type: GraphQLString, 89 | resolve: article => article.headline 90 | }, 91 | thumbnailUrl: { 92 | description: 'A URL path to the thumbnail image relevant to ' + 93 | 'the Article.', 94 | type: GraphQLString, 95 | resolve: article => article.thumbnailUrl 96 | }, 97 | author: { 98 | description: 'Returns the Author or null.', 99 | type: personType, 100 | resolve: article => article.getAuthor() 101 | } 102 | }), 103 | interfaces: [nodeInterface] 104 | }); 105 | 106 | var {connectionType: articleConnection} = 107 | connectionDefinitions({nodeType: articleType}); 108 | 109 | 110 | var personType = new GraphQLObjectType({ 111 | name: 'Person', 112 | description: 'A Person', 113 | fields: () => ({ 114 | id: globalIdField(), 115 | address: { 116 | type: GraphQLString, 117 | description: 'Physical address of the item.', 118 | resolve: person => person.address 119 | }, 120 | email: { 121 | type: GraphQLString, 122 | description: 'Email address', 123 | resolve: person => person.email 124 | }, 125 | familyName: { 126 | type: GraphQLString, 127 | description: 'Family name. In the U.S., the last name of an Person. ' + 128 | 'This can be used along with givenName instead of the name property.', 129 | resolve: person => person.familyName 130 | }, 131 | givenName: { 132 | type: GraphQLString, 133 | description: 'Given name. In the U.S., the first name of a Person. ' + 134 | 'This can be used along with familyName instead of the name property.', 135 | resolve: person => person.givenName 136 | }, 137 | honorificPrefix: { 138 | type: GraphQLString, 139 | description: 'An honorific prefix preceding a Person\'s name such as ' + 140 | 'Dr/Mrs/Mr.', 141 | resolve: person => person.honorificPrefix 142 | }, 143 | honorificSuffix: { 144 | type: GraphQLString, 145 | description: 'An honorific suffix preceding a Person\'s name such as ' + 146 | 'M.D. /PhD/MSCSW.', 147 | resolve: person => person.honorificSuffix 148 | }, 149 | jobTitle: { 150 | type: GraphQLString, 151 | description: 'The job title of the person ' + 152 | '(for example, Financial Manager).', 153 | resolve: person => person.jobTitle 154 | }, 155 | telephone: { 156 | type: GraphQLString, 157 | description: 'The telephone number.', 158 | resolve: person => person.telephone 159 | }, 160 | articlesAuthored: { 161 | type: articleConnection, 162 | args: connectionArgs, 163 | resolve: (person, args) => 164 | connectionFromPromisedArray( 165 | resolveArrayData(person.getArticles()), args 166 | ) 167 | } 168 | }), 169 | 170 | interfaces: [nodeInterface] 171 | }); 172 | 173 | var {connectionType: personConnection} = 174 | connectionDefinitions({nodeType: personType}); 175 | 176 | var queryType = new GraphQLObjectType({ 177 | name: 'Query', 178 | fields: () => ({ 179 | people: { 180 | description: 'People', 181 | type: personConnection, 182 | args: connectionArgs, 183 | resolve: (root, args) => 184 | connectionFromPromisedArray(resolveArrayByClass(Person), args) 185 | }, 186 | peopleWithMethods: { 187 | description: 'People with methods', 188 | type: personConnection, 189 | args: connectionArgs, 190 | resolve: (root, args) => 191 | connectionFromPromisedArray(resolveArrayByClass(Person, true), args) 192 | }, 193 | articles: { 194 | description: 'Articles', 195 | type: articleConnection, 196 | args: connectionArgs, 197 | resolve: (root, args) => 198 | connectionFromPromisedArray( 199 | resolveModelsByClass(Article), args 200 | ) 201 | }, 202 | node: nodeField 203 | }) 204 | }); 205 | 206 | 207 | describe('test relay connections with sequelize', () => { 208 | 209 | 210 | var schema = new GraphQLSchema({ 211 | query: queryType 212 | }); 213 | 214 | 215 | let db; 216 | 217 | 218 | before((done) => { 219 | connect().then((_db) => { 220 | db = _db; 221 | done(); 222 | }); 223 | }); 224 | 225 | it('connects to the database', () => { 226 | expect(db).to.not.be.an('undefined'); 227 | }); 228 | 229 | describe('getModelsByClass(Person) => sequelizeArray', () => { 230 | 231 | let sequelizeArray; 232 | it('returns a promised array', async () => { 233 | sequelizeArray = await getModelsByClass(Person); 234 | expect(sequelizeArray).to.be.a('array'); 235 | }); 236 | 237 | it('returns promised array with dataValues property. ', async () => { 238 | sequelizeArray = await getModelsByClass(Person); 239 | expect(sequelizeArray[0]).to.have.property('dataValues'); 240 | }); 241 | 242 | describe('getArrayData(sequelizeArray) => consumableArray', () => { 243 | 244 | let consumableArray; 245 | it('returns a promise that resolves to ' + 246 | 'consumable array', async () => { 247 | consumableArray = await getArrayData(sequelizeArray); 248 | expect(consumableArray).to.be.an('array'); 249 | 250 | }); 251 | 252 | it('resolves to consumable data', () => { 253 | 254 | // remove dates from the array 255 | var theArray = consumableArray.map(item => { 256 | let obj = {}; 257 | Object.keys(item).forEach((key) => { 258 | if (key !== 'createdAt' && key !== 'updatedAt') { 259 | obj[key] = item[key]; 260 | } 261 | }); 262 | return obj; 263 | }); 264 | 265 | 266 | 267 | var expected = [ 268 | { 269 | type: 'personType', 270 | id: 1, 271 | additionalName: 'Lilyan', 272 | address: '40831 Chad Rue', 273 | email: 'Aryanna99@yahoo.com', 274 | familyName: 'Reinger', 275 | givenName: 'Jaylan', 276 | honorificPrefix: 'Dr.', 277 | honorificSuffix: 'IV', 278 | jobTitle: 'Internal Program Officer', 279 | telephone: '259-536-5663 x8533' 280 | }, 281 | { 282 | type: 'personType', 283 | id: 2, 284 | additionalName: 'Viva', 285 | address: '197 Mina Gardens', 286 | email: 'Creola5@gmail.com', 287 | familyName: 'Schmeler', 288 | givenName: 'Amir', 289 | honorificPrefix: 'Miss', 290 | honorificSuffix: 'DVM', 291 | jobTitle: 'Lead Creative Executive', 292 | telephone: '718-964-7388 x29503' 293 | }, 294 | { 295 | type: 'personType', 296 | id: 3, 297 | additionalName: 'Urban', 298 | address: '109 Ottilie Pass', 299 | email: 'Marlen.White@gmail.com', 300 | familyName: 'Adams', 301 | givenName: 'Bobbie', 302 | honorificPrefix: 'Dr.', 303 | honorificSuffix: 'III', 304 | jobTitle: 'Corporate Infrastructure Engineer', 305 | telephone: '(869) 709-9551 x31769' 306 | }, 307 | { 308 | type: 'personType', 309 | id: 4, 310 | additionalName: 'Everardo', 311 | address: '60340 Gleason Heights', 312 | email: 'Kyle92@yahoo.com', 313 | familyName: 'Abbott', 314 | givenName: 'Berta', 315 | honorificPrefix: 'Dr.', 316 | honorificSuffix: 'III', 317 | jobTitle: 'Human Tactics Specialist', 318 | telephone: '1-265-145-9618 x199' 319 | }, 320 | { 321 | type: 'personType', 322 | id: 5, 323 | additionalName: 'Gussie', 324 | address: '13458 Dayana Ramp', 325 | email: 'Hailie_Boyle94@gmail.com', 326 | familyName: 'Fahey', 327 | givenName: 'Eleonore', 328 | honorificPrefix: 'Ms.', 329 | honorificSuffix: 'MD', 330 | jobTitle: 'Global Interactions Associate', 331 | telephone: '246-139-3322 x196' 332 | }, 333 | { 334 | type: 'personType', 335 | id: 6, 336 | additionalName: 'Aidan', 337 | address: '3382 O\'Conner Cliff', 338 | email: 'Loyce_Donnelly@yahoo.com', 339 | familyName: 'Mueller', 340 | givenName: 'Jennie', 341 | honorificPrefix: 'Mrs.', 342 | honorificSuffix: 'V', 343 | jobTitle: 'Human Identity Associate', 344 | telephone: '805-079-1652 x66842' 345 | }, 346 | { 347 | type: 'personType', 348 | id: 7, 349 | additionalName: 'Gloria', 350 | address: '4552 Swift Inlet', 351 | email: 'Leilani41@gmail.com', 352 | familyName: 'Rogahn', 353 | givenName: 'Adrienne', 354 | honorificPrefix: 'Ms.', 355 | honorificSuffix: 'DDS', 356 | jobTitle: 'Dynamic Communications Technician', 357 | telephone: '1-048-314-3269 x395' 358 | }, 359 | { 360 | type: 'personType', 361 | id: 8, 362 | additionalName: 'Pamela', 363 | address: '1249 Merlin Trail', 364 | email: 'Anais_VonRueden85@gmail.com', 365 | familyName: 'Gulgowski', 366 | givenName: 'Genoveva', 367 | honorificPrefix: 'Miss', 368 | honorificSuffix: 'DVM', 369 | jobTitle: 'Regional Security Representative', 370 | telephone: '340.537.5704' 371 | }, 372 | { 373 | type: 'personType', 374 | id: 9, 375 | additionalName: 'Hailee', 376 | address: '5637 Will Road', 377 | email: 'Lavada.Tillman@hotmail.com', 378 | familyName: 'Stehr', 379 | givenName: 'Aiyana', 380 | honorificPrefix: 'Miss', 381 | honorificSuffix: 'IV', 382 | jobTitle: 'Investor Integration Strategist', 383 | telephone: '(712) 600-5091' 384 | }, 385 | { 386 | type: 'personType', 387 | id: 10, 388 | additionalName: 'Jeffrey', 389 | address: '66543 Rick Lock', 390 | email: 'Friedrich_Block1@gmail.com', 391 | familyName: 'Johns', 392 | givenName: 'Gracie', 393 | honorificPrefix: 'Miss', 394 | honorificSuffix: 'V', 395 | jobTitle: 'Legacy Web Director', 396 | telephone: '306-361-6895' 397 | } 398 | ]; 399 | expect(theArray).to.deep.equal(expected); 400 | }); 401 | 402 | 403 | }); 404 | 405 | 406 | }); 407 | describe('resolveModelsByClass : sequelizeArgs', () => { 408 | let queriedArray; 409 | it('sorts ascending', async () => { 410 | queriedArray = await resolveModelsByClass(Person, { 411 | order: 'givenName ASC'}); 412 | expect(queriedArray).to.be.a('array'); 413 | let expected = [ 414 | 'Adrienne', 415 | 'Aiyana', 416 | 'Amir', 417 | 'Berta', 418 | 'Bobbie', 419 | 'Eleonore', 420 | 'Genoveva', 421 | 'Gracie', 422 | 'Jaylan', 423 | 'Jennie' 424 | ]; 425 | let actual = queriedArray.map(item => { 426 | return item.dataValues.givenName; 427 | }); 428 | expect(actual).to.deep.equal(expected); 429 | }); 430 | }); 431 | 432 | describe('getModelsByClass : sequelizeArgs', () => { 433 | let queriedArray; 434 | it('sorts ascending', async () => { 435 | queriedArray = await getModelsByClass(Person, { 436 | order: 'givenName ASC'}); 437 | expect(queriedArray).to.be.a('array'); 438 | let expected = [ 439 | 'Adrienne', 440 | 'Aiyana', 441 | 'Amir', 442 | 'Berta', 443 | 'Bobbie', 444 | 'Eleonore', 445 | 'Genoveva', 446 | 'Gracie', 447 | 'Jaylan', 448 | 'Jennie' 449 | ]; 450 | let actual = queriedArray.map(item => { 451 | return item.dataValues.givenName; 452 | }); 453 | expect(actual).to.deep.equal(expected); 454 | }); 455 | }); 456 | 457 | describe('getModelsByClass(Article) => sequelizeArray', () => { 458 | 459 | let sequelizeArray; 460 | it('returns a promised array', async () => { 461 | sequelizeArray = await getModelsByClass(Person); 462 | expect(sequelizeArray).to.be.a('array'); 463 | }); 464 | 465 | it('returns promised array with dataValues property. ', async () => { 466 | sequelizeArray = await getModelsByClass(Article); 467 | expect(sequelizeArray[0]).to.have.property('dataValues'); 468 | }); 469 | 470 | describe('getArrayData(sequelizeArray) => consumableArray', () => { 471 | 472 | let consumableArray; 473 | it('returns a promise that resolves to ' + 474 | 'consumable array', async () => { 475 | consumableArray = await getArrayData(sequelizeArray); 476 | expect(consumableArray).to.be.an('array'); 477 | 478 | }); 479 | 480 | it('resolves to consumable data', () => { 481 | 482 | // remove dates from the array 483 | var theArray = consumableArray.map(item => { 484 | let obj = {}; 485 | Object.keys(item).forEach((key) => { 486 | if (key !== 'createdAt' && key !== 'updatedAt') { 487 | obj[key] = item[key]; 488 | } 489 | }); 490 | return obj; 491 | }); 492 | var expected = [ 493 | { 494 | type: 'articleType', 495 | id: 1, 496 | articleBody: 'Ut et qui blanditiis laboriosam omnis.\n' + 497 | 'Aut assumenda quis eum necessitatibus incidunt.\n' + 498 | 'Necessitatibus quo consequatur facere nostrum optio et ' + 499 | 'distinctio vel.\nDelectus ut quis.\n \rQuos laborum fuga nisi ' + 500 | 'illo tempore ' + 501 | 'aut quia facilis molestiae.\nAut ut id et et officiis officia ' + 502 | 'consequuntur dolorem eos.\nQuia in fuga qui illum ut voluptates ' + 503 | 'sed.\nUt sint ipsa dicta id repudiandae quibusdam.\n' + 504 | 'Eos est perspiciatis dolor distinctio rem reprehenderit illum ' + 505 | 'hic.\n \rSit voluptates ratione quis numquam necessitatibus ' + 506 | 'omnis officia autem.\nDistinctio asperiores molestiae.\nAliquam ' + 507 | 'asperiores fuga.\nNesciunt architecto quia sed.', 508 | articleSection: 'e-tailers', 509 | headline: 'Team-oriented 24/7 artificial intelligence', 510 | thumbnailUrl: 'http://lorempixel.com/640/480/business', 511 | AuthorId: 1 }, 512 | { type: 'articleType', 513 | id: 2, 514 | articleBody: 'Enim optio quod.\nVelit asperiores ut aut enim ' + 515 | 'quibusdam cum.\nIste magni iure est quia ut natus et occaecati ' + 516 | 'laboriosam.\n \rEnim harum nostrum voluptas a ea.\nLaudantium ' + 517 | 'sed enim est et.\nUnde ipsa ducimus fuga quia dolor facilis.\n' + 518 | 'Excepturi quis pariatur qui.\nEt optio laudantium praesentium ' + 519 | 'ipsa quis.\nEarum id sed.\n \rQuia ab repellendus et molestias ' + 520 | 'explicabo.\nExplicabo quo non quia.\nSit fugiat minus magnam ' + 521 | 'omnis voluptates non non.\nA ipsam et debitis.\nEa sit unde ' + 522 | 'voluptatem atque voluptatem.\nLibero iusto aliquid amet.', 523 | articleSection: 'action-items', 524 | headline: 'Open-source object-oriented approach', 525 | thumbnailUrl: 'http://lorempixel.com/640/480/business', 526 | AuthorId: 2 }, 527 | { type: 'articleType', 528 | id: 3, 529 | articleBody: 'Sunt impedit modi fugit fugiat quo.\nQui et sed ' + 530 | 'maxime quaerat et laboriosam eaque.\nDolorem voluptatem hic ' + 531 | 'facere voluptates.\nDeserunt et enim voluptas ipsa in possimus ' + 532 | 'voluptates non.\n \rRem et veniam eum cupiditate beatae ' + 533 | 'tenetur voluptatem incidunt quis.\nLabore ut fugit eos debitis ' + 534 | 'possimus ipsa.\nPlaceat voluptas et enim.\n \rQui nemo dicta ' + 535 | 'rem enim magnam doloribus cumque.\nDolore quos quo aut magni ' + 536 | 'ipsam.\nEum ab et quis laudantium rem distinctio aliquam.\nId ' + 537 | 'ex quaerat est sapiente et officiis aut expedita.\nQuis quidem ' + 538 | 'atque illum temporibus eos nulla sint voluptatibus.\nSit iusto ' + 539 | 'facilis porro id earum voluptatem explicabo temporibus autem.', 540 | articleSection: 'niches', 541 | headline: 'Managed content-based task-force', 542 | thumbnailUrl: 'http://lorempixel.com/640/480/business', 543 | AuthorId: 3 }, 544 | { type: 'articleType', 545 | id: 4, 546 | articleBody: 'Sit expedita est.\nSoluta quia rerum eum qui ' + 547 | 'libero voluptas illo.\nAut nihil quasi quisquam iure voluptates' + 548 | ' enim at.\nEt voluptatem excepturi perferendis totam.\nNihil ' + 549 | 'aliquam iusto ut nihil autem natus quae eveniet.\nEx omnis a' + 550 | 'nimi expedita consequatur debitis quasi natus sit neque.\n ' + 551 | '\rEsse sint veniam perspiciatis placeat.\nVoluptas veniam o' + 552 | 'ccaecati illum unde magnam in.\nEt natus ab delectus quasi ' + 553 | 'neque.\n \rOfficia quia provident consequatur debitis labor' + 554 | 'um ullam consequuntur sint.\nFuga libero ratione.\nIllum ut' + 555 | ' eius nostrum voluptatem delectus saepe pariatur tempore.', 556 | articleSection: 'e-business', 557 | headline: 'Horizontal systematic function', 558 | thumbnailUrl: 'http://lorempixel.com/640/480/business', 559 | AuthorId: 4 }, 560 | { type: 'articleType', 561 | id: 5, 562 | articleBody: 'Necessitatibus id quod et.\nDolores qui cupidit' + 563 | 'ate natus consequatur quia ut ducimus.\nAtque quia quis anim' + 564 | 'i consequatur aut.\nQuis ut sed nihil omnis fuga blanditiis.' + 565 | '\nAliquam commodi voluptatum molestias placeat quod quam ear' + 566 | 'um.\nQui autem et nulla laborum.\n \rAccusamus voluptates id' + 567 | ' quia fugit et aut et.\nRem totam nesciunt quos esse ducimus' + 568 | ' tempore non et dolorum.\nRerum id eum reprehenderit harum q' + 569 | 'uia.\nDelectus non cumque ipsa optio et.\nAccusantium sed do' + 570 | 'lores.\n \rFuga qui ut voluptatem tempore mollitia similique' + 571 | ' quasi nesciunt aperiam.\nEt eum accusantium aspernatur harum' + 572 | ' et est.\nIllum quibusdam nobis laboriosam temporibus fugit m' + 573 | 'olestiae numquam recusandae.\nEa maxime laudantium doloremque' + 574 | ' assumenda quas officiis repudiandae corrupti et.\nNon tempo' + 575 | 'ra ad ut atque quae.\nQuam sunt sequi et voluptatem sapiente' + 576 | ' voluptate.', 577 | articleSection: 'e-business', 578 | headline: 'Quality-focused 24/7 attitude', 579 | thumbnailUrl: 'http://lorempixel.com/640/480/business', 580 | AuthorId: 5 }, 581 | { type: 'articleType', 582 | id: 6, 583 | articleBody: 'Et accusantium magnam quia voluptate ex.\nAssum' + 584 | 'enda pariatur est omnis blanditiis est totam.\nSoluta facili' + 585 | 's tenetur ut temporibus sit quo atque.\nDolores aut non sint' + 586 | ' reiciendis ut.\n \rAut quo quaerat ab.\nNihil et quia ipsa ' + 587 | 'accusamus omnis.\nAut occaecati maxime.\n \rSed ea et ab eiu' + 588 | 's reiciendis voluptas dolore rem.\nOmnis iste et id ipsam et ' + 589 | 'facilis voluptatem minima et.\nFacilis perspiciatis asperior' + 590 | 'es sit temporibus delectus explicabo adipisci molestiae.', 591 | articleSection: 'interfaces', 592 | headline: 'Multi-tiered transitional encryption', 593 | thumbnailUrl: 'http://lorempixel.com/640/480/business', 594 | AuthorId: 6 }, 595 | { type: 'articleType', 596 | id: 7, 597 | articleBody: 'Quisquam ab impedit.\nSaepe earum natus occaec' + 598 | 'ati id doloremque hic rem.\nInventore voluptatum consequatu' + 599 | 'r ea sed eum est nihil eaque.\nRem et minus.\nAutem iste cor' + 600 | 'poris quasi.\n \rMinus asperiores perspiciatis.\nFuga labor' + 601 | 'um possimus quis magni.\nBeatae aut odit quidem ea.\nId inci' + 602 | 'dunt ea.\n \rQuia dolor quos iusto est ut.\nLaborum totam vo' + 603 | 'luptatem expedita tempora enim alias explicabo in.\nQuis eos' + 604 | ' aspernatur ut molestias ullam qui minus maxime.\nAliquid re' + 605 | 'm odio enim dolore perspiciatis voluptates.', 606 | articleSection: 'synergies', 607 | headline: 'Profound exuding productivity', 608 | thumbnailUrl: 'http://lorempixel.com/640/480/business', 609 | AuthorId: 7 }, 610 | { type: 'articleType', 611 | id: 8, 612 | articleBody: 'Vel ea praesentium quibusdam.\nAutem explicabo' + 613 | ' eum.\nError illo tenetur.\nAccusamus et rerum rerum in quia' + 614 | '.\nDolorem quibusdam ea quasi voluptatem tenetur quaerat com' + 615 | 'modi labore.\nAutem sed excepturi itaque molestiae ipsam.\n ' + 616 | '\rTempore alias possimus qui commodi aliquam officia.\nQuo ex' + 617 | 'ercitationem ut laudantium commodi eum quisquam amet nam iure' + 618 | '.\nNobis sapiente est iste expedita quis amet quisquam vero f' + 619 | 'uga.\nDolor voluptas est error porro non odio nihil quae.\n ' + 620 | '\rEos optio minima tempora officiis.\nSunt consequuntur ullam' + 621 | ' et est.\nVoluptatem dolor dolores.', 622 | articleSection: 'infrastructures', 623 | headline: 'Customer-focused coherent emulation', 624 | thumbnailUrl: 'http://lorempixel.com/640/480/business', 625 | AuthorId: 8 }, 626 | { type: 'articleType', 627 | id: 9, 628 | articleBody: 'Architecto ea minima et qui sed delectus quibu' + 629 | 'sdam ratione.\nEnim est possimus magnam quia.\nIpsam fugiat ' + 630 | 'sit itaque odio praesentium neque assumenda id ex.\nEt qui e' + 631 | 't modi dolorum alias voluptatum.\n \rAccusantium nostrum fa' + 632 | 'cilis enim.\nAnimi omnis molestiae.\nSuscipit qui fugit dele' + 633 | 'ctus necessitatibus et beatae sint.\nAliquid aspernatur aut' + 634 | 'em.\n \rAtque maxime cum laboriosam eum omnis esse rerum re' + 635 | 'prehenderit ut.\nCupiditate quae ut.\nDoloribus animi elige' + 636 | 'ndi consequatur asperiores sed delectus qui.\nVoluptatem es' + 637 | 't deleniti nostrum quia qui et.', 638 | articleSection: 'platforms', 639 | headline: 'Reduced explicit collaboration', 640 | thumbnailUrl: 'http://lorempixel.com/640/480/business', 641 | AuthorId: 9 }, 642 | { type: 'articleType', 643 | id: 10, 644 | articleBody: 'Eos alias sint cum voluptates sit.\nVoluptatib' + 645 | 'us voluptas adipisci illum.\nQui corrupti eveniet facere en' + 646 | 'im consequatur similique.\nQui omnis porro molestiae et eni' + 647 | 'm consequatur quam ea.\nEos qui enim.\nFuga necessitatibus ' + 648 | 'delectus sunt eum ut ducimus.\n \rPariatur nihil magnam mol' + 649 | 'estias ab dolor doloremque voluptas.\nEsse possimus et dolo' + 650 | 'r aperiam.\nId tempora et maxime ducimus ipsa.\nDistinctio' + 651 | ' repudiandae occaecati in consequuntur non voluptate.\nIll' + 652 | 'um voluptates voluptatem corporis impedit nobis.\n \rItaque' + 653 | ' ullam autem earum.\nSed iusto ab aut et quia.\nCum aut imp' + 654 | 'edit dolor nobis culpa non pariatur.\nNam harum praesentium' + 655 | ' et quidem et ut cumque beatae rerum.\nLaborum non assumend' + 656 | 'a voluptas ea totam.\nMaxime itaque voluptatem.', 657 | articleSection: 'methodologies', 658 | headline: 'Seamless tertiary structure', 659 | thumbnailUrl: 'http://lorempixel.com/640/480/business', 660 | AuthorId: 10 661 | } 662 | ]; 663 | expect(theArray).to.deep.equal(expected); 664 | }); 665 | 666 | 667 | }); 668 | 669 | 670 | }); 671 | 672 | describe('conversion to relay specification', () => { 673 | it('can handle errors', (done) => { 674 | var query = ` 675 | query ukQuery { 676 | node(id: "aaa=") { 677 | id 678 | ... on Foo { 679 | id 680 | } 681 | }`; 682 | graphql(schema, query).then((theResponse) => { 683 | expect(theResponse.errors.length).to.equal(1); 684 | done(); 685 | }).catch(done); 686 | }); 687 | describe('resolveArrayByClass(Person)', () => { 688 | 689 | it('has connection and edge fields', (done) => { 690 | var query = ` 691 | { 692 | people(first: 2) { 693 | pageInfo { 694 | startCursor 695 | hasNextPage 696 | } 697 | edges { 698 | cursor 699 | node { 700 | id 701 | givenName 702 | familyName 703 | address 704 | } 705 | } 706 | } 707 | }`; 708 | var expected = { 709 | data: { 710 | people: { 711 | pageInfo: { 712 | startCursor: 'YXJyYXljb25uZWN0aW9uOjA=', 713 | hasNextPage: true 714 | }, 715 | edges: [ 716 | { 717 | cursor: 'YXJyYXljb25uZWN0aW9uOjA=', 718 | node: { 719 | id: 'UGVyc29uOjE=', 720 | givenName: 'Jaylan', 721 | familyName: 'Reinger', 722 | address: '40831 Chad Rue' 723 | } 724 | }, 725 | { 726 | cursor: 'YXJyYXljb25uZWN0aW9uOjE=', 727 | node: { 728 | id: 'UGVyc29uOjI=', 729 | givenName: 'Amir', 730 | familyName: 'Schmeler', 731 | address: '197 Mina Gardens' 732 | } 733 | } 734 | ] 735 | } 736 | } 737 | }; 738 | graphql(schema, query).then((theResponse) => { 739 | expect(theResponse).to.deep.equal(expected); 740 | done(); 741 | }).catch(done); 742 | }); 743 | 744 | it('can get next page', (done) => { 745 | var query = ` 746 | { 747 | people(first: 2, after: "YXJyYXljb25uZWN0aW9uOjE") { 748 | pageInfo { 749 | startCursor 750 | hasNextPage 751 | } 752 | edges { 753 | cursor 754 | node { 755 | id 756 | givenName 757 | familyName 758 | address 759 | } 760 | } 761 | } 762 | }`; 763 | var expected = { 764 | data: { 765 | people: { 766 | pageInfo: { 767 | startCursor: 'YXJyYXljb25uZWN0aW9uOjI=', 768 | hasNextPage: true 769 | }, 770 | edges: [ 771 | { 772 | cursor: 'YXJyYXljb25uZWN0aW9uOjI=', 773 | node: { 774 | id: 'UGVyc29uOjM=', 775 | givenName: 'Bobbie', 776 | familyName: 'Adams', 777 | address: '109 Ottilie Pass' 778 | } 779 | }, 780 | { 781 | cursor: 'YXJyYXljb25uZWN0aW9uOjM=', 782 | node: { 783 | id: 'UGVyc29uOjQ=', 784 | givenName: 'Berta', 785 | familyName: 'Abbott', 786 | address: '60340 Gleason Heights' 787 | } 788 | } 789 | ] 790 | } 791 | } 792 | }; 793 | graphql(schema, query).then((theResponse) => { 794 | expect(theResponse).to.deep.equal(expected); 795 | done(); 796 | }).catch(done); 797 | }); 798 | 799 | it('can get previous page', (done) => { 800 | var query = ` 801 | { 802 | people(last: 2, before: "YXJyYXljb25uZWN0aW9uOjM=") { 803 | pageInfo { 804 | startCursor 805 | hasPreviousPage 806 | } 807 | edges { 808 | cursor 809 | node { 810 | id 811 | givenName 812 | familyName 813 | address 814 | } 815 | } 816 | } 817 | }`; 818 | var expected = { 819 | data: { 820 | people: { 821 | pageInfo: { 822 | startCursor: 'YXJyYXljb25uZWN0aW9uOjE=', 823 | hasPreviousPage: true 824 | }, 825 | edges: [ 826 | { 827 | cursor: 'YXJyYXljb25uZWN0aW9uOjE=', 828 | node: { 829 | id: 'UGVyc29uOjI=', 830 | givenName: 'Amir', 831 | familyName: 'Schmeler', 832 | address: '197 Mina Gardens' 833 | } 834 | }, 835 | { 836 | cursor: 'YXJyYXljb25uZWN0aW9uOjI=', 837 | node: { 838 | id: 'UGVyc29uOjM=', 839 | givenName: 'Bobbie', 840 | familyName: 'Adams', 841 | address: '109 Ottilie Pass' 842 | } 843 | } 844 | ] 845 | } 846 | } 847 | }; 848 | graphql(schema, query).then((theResponse) => { 849 | expect(theResponse).to.deep.equal(expected); 850 | done(); 851 | }).catch(done); 852 | }); 853 | 854 | it('can perform refetch', (done) => { 855 | var query = ` 856 | query PersonRefetchQuery { 857 | node(id: "UGVyc29uOjI=") { 858 | id 859 | ... on Person { 860 | id 861 | honorificPrefix 862 | givenName 863 | familyName 864 | honorificSuffix 865 | jobTitle 866 | telephone 867 | email 868 | address 869 | } 870 | } 871 | }`; 872 | var expected = { 873 | data: { 874 | node: { 875 | id: 'UGVyc29uOjI=', 876 | email: 'Creola5@gmail.com', 877 | honorificPrefix: 'Miss', 878 | honorificSuffix: 'DVM', 879 | jobTitle: 'Lead Creative Executive', 880 | telephone: '718-964-7388 x29503', 881 | givenName: 'Amir', 882 | familyName: 'Schmeler', 883 | address: '197 Mina Gardens' 884 | } 885 | } 886 | }; 887 | graphql(schema, query).then((theResponse) => { 888 | expect(theResponse).to.deep.equal(expected); 889 | done(); 890 | }).catch(done); 891 | }); 892 | 893 | }); 894 | 895 | describe('resolveArrayByClass(Person, true) With Methods', () => { 896 | 897 | it('has connection and edge fields', (done) => { 898 | var query = ` 899 | { 900 | peopleWithMethods(first: 2) { 901 | pageInfo { 902 | startCursor 903 | hasNextPage 904 | } 905 | edges { 906 | cursor 907 | node { 908 | id 909 | givenName 910 | familyName 911 | address 912 | } 913 | } 914 | } 915 | }`; 916 | var expected = { 917 | data: { 918 | peopleWithMethods: { 919 | pageInfo: { 920 | startCursor: 'YXJyYXljb25uZWN0aW9uOjA=', 921 | hasNextPage: true 922 | }, 923 | edges: [ 924 | { 925 | cursor: 'YXJyYXljb25uZWN0aW9uOjA=', 926 | node: { 927 | id: 'UGVyc29uOjE=', 928 | givenName: 'Jaylan', 929 | familyName: 'Reinger', 930 | address: '40831 Chad Rue' 931 | } 932 | }, 933 | { 934 | cursor: 'YXJyYXljb25uZWN0aW9uOjE=', 935 | node: { 936 | id: 'UGVyc29uOjI=', 937 | givenName: 'Amir', 938 | familyName: 'Schmeler', 939 | address: '197 Mina Gardens' 940 | } 941 | } 942 | ] 943 | } 944 | } 945 | }; 946 | graphql(schema, query).then((theResponse) => { 947 | expect(theResponse).to.deep.equal(expected); 948 | done(); 949 | }).catch(done); 950 | }); 951 | 952 | it('can get next page', (done) => { 953 | var query = ` 954 | { 955 | peopleWithMethods(first: 2, after: "YXJyYXljb25uZWN0aW9uOjE") { 956 | pageInfo { 957 | startCursor 958 | hasNextPage 959 | } 960 | edges { 961 | cursor 962 | node { 963 | id 964 | givenName 965 | familyName 966 | address 967 | } 968 | } 969 | } 970 | }`; 971 | var expected = { 972 | data: { 973 | peopleWithMethods: { 974 | pageInfo: { 975 | startCursor: 'YXJyYXljb25uZWN0aW9uOjI=', 976 | hasNextPage: true 977 | }, 978 | edges: [ 979 | { 980 | cursor: 'YXJyYXljb25uZWN0aW9uOjI=', 981 | node: { 982 | id: 'UGVyc29uOjM=', 983 | givenName: 'Bobbie', 984 | familyName: 'Adams', 985 | address: '109 Ottilie Pass' 986 | } 987 | }, 988 | { 989 | cursor: 'YXJyYXljb25uZWN0aW9uOjM=', 990 | node: { 991 | id: 'UGVyc29uOjQ=', 992 | givenName: 'Berta', 993 | familyName: 'Abbott', 994 | address: '60340 Gleason Heights' 995 | } 996 | } 997 | ] 998 | } 999 | } 1000 | }; 1001 | graphql(schema, query).then((theResponse) => { 1002 | expect(theResponse).to.deep.equal(expected); 1003 | done(); 1004 | }).catch(done); 1005 | }); 1006 | 1007 | it('can get previous page', (done) => { 1008 | var query = ` 1009 | { 1010 | peopleWithMethods(last: 2, before: "YXJyYXljb25uZWN0aW9uOjM=") { 1011 | pageInfo { 1012 | startCursor 1013 | hasPreviousPage 1014 | } 1015 | edges { 1016 | cursor 1017 | node { 1018 | id 1019 | givenName 1020 | familyName 1021 | address 1022 | } 1023 | } 1024 | } 1025 | }`; 1026 | var expected = { 1027 | data: { 1028 | peopleWithMethods: { 1029 | pageInfo: { 1030 | startCursor: 'YXJyYXljb25uZWN0aW9uOjE=', 1031 | hasPreviousPage: true 1032 | }, 1033 | edges: [ 1034 | { 1035 | cursor: 'YXJyYXljb25uZWN0aW9uOjE=', 1036 | node: { 1037 | id: 'UGVyc29uOjI=', 1038 | givenName: 'Amir', 1039 | familyName: 'Schmeler', 1040 | address: '197 Mina Gardens' 1041 | } 1042 | }, 1043 | { 1044 | cursor: 'YXJyYXljb25uZWN0aW9uOjI=', 1045 | node: { 1046 | id: 'UGVyc29uOjM=', 1047 | givenName: 'Bobbie', 1048 | familyName: 'Adams', 1049 | address: '109 Ottilie Pass' 1050 | } 1051 | } 1052 | ] 1053 | } 1054 | } 1055 | }; 1056 | graphql(schema, query).then((theResponse) => { 1057 | expect(theResponse).to.deep.equal(expected); 1058 | done(); 1059 | }).catch(done); 1060 | }); 1061 | 1062 | }); 1063 | 1064 | 1065 | describe('resolveArrayData(person.getArticles())', () => { 1066 | it('can get authored posts', (done) => { 1067 | var query = ` 1068 | query PersonRefetchQuery { 1069 | node(id: "UGVyc29uOjI=") { 1070 | id 1071 | ... on Person { 1072 | id 1073 | givenName 1074 | familyName 1075 | address 1076 | articlesAuthored { 1077 | edges { 1078 | node { 1079 | id 1080 | headline 1081 | thumbnailUrl 1082 | } 1083 | } 1084 | } 1085 | } 1086 | } 1087 | }`; 1088 | var expected = { 1089 | data: { 1090 | node: { 1091 | id: 'UGVyc29uOjI=', 1092 | givenName: 'Amir', 1093 | familyName: 'Schmeler', 1094 | address: '197 Mina Gardens', 1095 | articlesAuthored: { 1096 | edges: [ 1097 | { 1098 | node: { 1099 | id: 'QXJ0aWNsZToy', 1100 | headline: 'Open-source object-oriented approach', 1101 | thumbnailUrl: 'http://lorempixel.com/640/480/business' 1102 | } 1103 | } 1104 | ] 1105 | } 1106 | } 1107 | } 1108 | }; 1109 | graphql(schema, query).then((theResponse) => { 1110 | expect(theResponse).to.deep.equal(expected); 1111 | done(); 1112 | }).catch(done); 1113 | }); 1114 | }); 1115 | 1116 | describe('resolveModelsByClass(Article)', () => { 1117 | 1118 | it('has connection and edge fields', (done) => { 1119 | var query = ` 1120 | { 1121 | articles(first: 2) { 1122 | pageInfo { 1123 | startCursor 1124 | hasNextPage 1125 | } 1126 | edges { 1127 | node { 1128 | id 1129 | headline, 1130 | articleBody, 1131 | articleSection, 1132 | headline, 1133 | thumbnailUrl 1134 | } 1135 | } 1136 | } 1137 | }`; 1138 | var expected = { 1139 | data: { 1140 | articles: { 1141 | pageInfo: { 1142 | hasNextPage: true, 1143 | startCursor: 'YXJyYXljb25uZWN0aW9uOjA=' 1144 | }, 1145 | edges: [ 1146 | { 1147 | node: { 1148 | articleBody: 'Ut et qui blanditiis laboriosam omnis.\nA' + 1149 | 'ut assumenda quis eum necessitatibus incidunt.\nNecess' + 1150 | 'itatibus quo consequatur facere nostrum optio et disti' + 1151 | 'nctio vel.\nDelectus ut quis.\n \rQuos laborum fuga ni' + 1152 | 'si illo tempore aut quia facilis molestiae.\nAut ut id' + 1153 | ' et et officiis officia consequuntur dolorem eos.\nQui' + 1154 | 'a in fuga qui illum ut voluptates sed.\nUt sint ipsa d' + 1155 | 'icta id repudiandae quibusdam.\nEos est perspiciatis d' + 1156 | 'olor distinctio rem reprehenderit illum hic.\n \rSit v' + 1157 | 'oluptates ratione quis numquam necessitatibus omnis of' + 1158 | 'ficia autem.\nDistinctio asperiores molestiae.\nAliqua' + 1159 | 'm asperiores fuga.\nNesciunt architecto quia sed.', 1160 | articleSection: 'e-tailers', 1161 | headline: 'Team-oriented 24/7 artificial intelligence', 1162 | id: 'QXJ0aWNsZTox', 1163 | thumbnailUrl: 'http://lorempixel.com/640/480/business' 1164 | } 1165 | }, 1166 | { 1167 | node: { 1168 | articleBody: 'Enim optio quod.\nVelit asperiores ut aut' + 1169 | ' enim quibusdam cum.\nIste magni iure est quia ut natu' + 1170 | 's et occaecati laboriosam.\n \rEnim harum nostrum volu' + 1171 | 'ptas a ea.\nLaudantium sed enim est et.\nUnde ipsa duc' + 1172 | 'imus fuga quia dolor facilis.\nExcepturi quis pariatur' + 1173 | ' qui.\nEt optio laudantium praesentium ipsa quis.\nEar' + 1174 | 'um id sed.\n \rQuia ab repellendus et molestias explic' + 1175 | 'abo.\nExplicabo quo non quia.\nSit fugiat minus magnam' + 1176 | ' omnis voluptates non non.\nA ipsam et debitis.\nEa si' + 1177 | 't unde voluptatem atque voluptatem.\nLibero iusto aliq' + 1178 | 'uid amet.', 1179 | articleSection: 'action-items', 1180 | headline: 'Open-source object-oriented approach', 1181 | id: 'QXJ0aWNsZToy', 1182 | thumbnailUrl: 'http://lorempixel.com/640/480/business' 1183 | } 1184 | } 1185 | ] 1186 | } 1187 | } 1188 | }; 1189 | graphql(schema, query).then((theResponse) => { 1190 | expect(theResponse).to.deep.equal(expected); 1191 | done(); 1192 | }).catch(done); 1193 | }); 1194 | it('has authors', (done) => { 1195 | var query = ` 1196 | { 1197 | articles(first: 2) { 1198 | pageInfo { 1199 | startCursor 1200 | hasNextPage 1201 | } 1202 | edges { 1203 | node { 1204 | id 1205 | headline, 1206 | articleBody, 1207 | articleSection, 1208 | headline, 1209 | thumbnailUrl 1210 | author { 1211 | id 1212 | givenName 1213 | } 1214 | } 1215 | } 1216 | } 1217 | }`; 1218 | var expected = { 1219 | data: { 1220 | articles: { 1221 | pageInfo: { 1222 | hasNextPage: true, 1223 | startCursor: 'YXJyYXljb25uZWN0aW9uOjA=' 1224 | }, 1225 | edges: [ 1226 | { 1227 | node: { 1228 | articleBody: 'Ut et qui blanditiis laboriosam omnis.\nA' + 1229 | 'ut assumenda quis eum necessitatibus incidunt.\nNecess' + 1230 | 'itatibus quo consequatur facere nostrum optio et disti' + 1231 | 'nctio vel.\nDelectus ut quis.\n \rQuos laborum fuga ni' + 1232 | 'si illo tempore aut quia facilis molestiae.\nAut ut id' + 1233 | ' et et officiis officia consequuntur dolorem eos.\nQui' + 1234 | 'a in fuga qui illum ut voluptates sed.\nUt sint ipsa d' + 1235 | 'icta id repudiandae quibusdam.\nEos est perspiciatis d' + 1236 | 'olor distinctio rem reprehenderit illum hic.\n \rSit v' + 1237 | 'oluptates ratione quis numquam necessitatibus omnis of' + 1238 | 'ficia autem.\nDistinctio asperiores molestiae.\nAliqua' + 1239 | 'm asperiores fuga.\nNesciunt architecto quia sed.', 1240 | articleSection: 'e-tailers', 1241 | headline: 'Team-oriented 24/7 artificial intelligence', 1242 | id: 'QXJ0aWNsZTox', 1243 | thumbnailUrl: 'http://lorempixel.com/640/480/business', 1244 | author: { 1245 | givenName: 'Jaylan', 1246 | id: 'UGVyc29uOjE=' 1247 | } 1248 | } 1249 | }, 1250 | { 1251 | node: { 1252 | articleBody: 'Enim optio quod.\nVelit asperiores ut aut' + 1253 | ' enim quibusdam cum.\nIste magni iure est quia ut natu' + 1254 | 's et occaecati laboriosam.\n \rEnim harum nostrum volu' + 1255 | 'ptas a ea.\nLaudantium sed enim est et.\nUnde ipsa duc' + 1256 | 'imus fuga quia dolor facilis.\nExcepturi quis pariatur' + 1257 | ' qui.\nEt optio laudantium praesentium ipsa quis.\nEar' + 1258 | 'um id sed.\n \rQuia ab repellendus et molestias explic' + 1259 | 'abo.\nExplicabo quo non quia.\nSit fugiat minus magnam' + 1260 | ' omnis voluptates non non.\nA ipsam et debitis.\nEa si' + 1261 | 't unde voluptatem atque voluptatem.\nLibero iusto aliq' + 1262 | 'uid amet.', 1263 | articleSection: 'action-items', 1264 | headline: 'Open-source object-oriented approach', 1265 | id: 'QXJ0aWNsZToy', 1266 | thumbnailUrl: 'http://lorempixel.com/640/480/business', 1267 | author: { 1268 | givenName: 'Amir', 1269 | id: 'UGVyc29uOjI=' 1270 | } 1271 | } 1272 | } 1273 | ] 1274 | } 1275 | } 1276 | }; 1277 | graphql(schema, query).then((theResponse) => { 1278 | expect(theResponse).to.deep.equal(expected); 1279 | done(); 1280 | }).catch(done); 1281 | }); 1282 | it('can perform refetch', (done) => { 1283 | var query = ` 1284 | query ArticleRefetchQuery { 1285 | node(id: "QXJ0aWNsZToy") { 1286 | id 1287 | ... on Article { 1288 | id 1289 | headline 1290 | } 1291 | } 1292 | }`; 1293 | var expected = { 1294 | data: { 1295 | node: { 1296 | id: 'QXJ0aWNsZToy', 1297 | headline: 'Open-source object-oriented approach' 1298 | } 1299 | } 1300 | }; 1301 | graphql(schema, query).then((theResponse) => { 1302 | expect(theResponse).to.deep.equal(expected); 1303 | done(); 1304 | }).catch(done); 1305 | }); 1306 | it('can get the author', (done) => { 1307 | var query = ` 1308 | query ArticleRefetchQuery { 1309 | node(id: "QXJ0aWNsZToy") { 1310 | id 1311 | ... on Article { 1312 | id 1313 | headline 1314 | author { 1315 | id 1316 | givenName 1317 | } 1318 | } 1319 | } 1320 | }`; 1321 | var expected = { 1322 | data: { 1323 | node: { 1324 | id: 'QXJ0aWNsZToy', 1325 | headline: 'Open-source object-oriented approach', 1326 | author: { 1327 | givenName: 'Amir', 1328 | id: 'UGVyc29uOjI=' 1329 | } 1330 | } 1331 | } 1332 | }; 1333 | graphql(schema, query).then((theResponse) => { 1334 | expect(theResponse).to.deep.equal(expected); 1335 | done(); 1336 | }).catch(done); 1337 | }); 1338 | }); 1339 | 1340 | }); 1341 | 1342 | }); 1343 | 1344 | 1345 | 1346 | 1347 | --------------------------------------------------------------------------------