├── 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 | 
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 | 
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 | 
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 | [](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 |
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 | [](https://www.npmjs.com/package/sequelize-relay)
9 | [](https://travis-ci.org/MattMcFarland/sequelize-relay)
10 | [](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 | 
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 | 
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 |
--------------------------------------------------------------------------------