├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── lib ├── index.js ├── schema │ ├── buildArgs.js │ ├── buildMutation.js │ ├── buildQuery.js │ └── index.js ├── type │ ├── customType.js │ └── index.js └── utils.js ├── package.json ├── src ├── index.js ├── schema │ ├── buildArgs.js │ ├── buildMutation.js │ ├── buildQuery.js │ └── index.js ├── type │ ├── customType.js │ └── index.js └── utils.js └── test ├── end2end.test.js ├── fixture ├── schoolModel.js ├── testServer.js └── userModel.js ├── schema.args.test.js ├── schema.mutation.test.js ├── schema.query.test.js ├── type.Buffer.test.js ├── type.Date.test.js ├── type.Mixed.test.js └── type.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "sourceMaps": "inline", 3 | "presets": ["es2015-node4", "es2017"], 4 | "plugins": [ 5 | "transform-runtime", 6 | "transform-async-to-generator" 7 | ] 8 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | !.npmignore 3 | !.babelrc 4 | !.travis.yml 5 | !.coveralls.yml 6 | 7 | *.DS_Store* 8 | *.log* 9 | .nyc_output 10 | 11 | node_modules/ 12 | coverage/ 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | // system file 2 | *.DS_Store* 3 | *.log* 4 | 5 | // specific files 6 | coverage/ 7 | src/ 8 | test/ 9 | 10 | // setting files 11 | .babelrc 12 | .travis.yml 13 | .nyc_output -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - node_modules 5 | node_js: 6 | - "4" 7 | notifications: 8 | email: false 9 | services: 10 | - mongodb 11 | install: 12 | - npm install 13 | script: 14 | - make coveralls -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Wang Zixiao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NODE_BIN = node_modules/.bin 2 | SRC = src 3 | DIST = lib 4 | 5 | lint: 6 | @echo Linting... 7 | @$(NODE_BIN)/standard --verbose | $(NODE_BIN)/snazzy src/ 8 | 9 | test: lint 10 | @ echo Testing... 11 | @$(NODE_BIN)/ava test/*.test.js --timeout=10s 12 | 13 | cover: lint 14 | @echo Testing... 15 | @$(NODE_BIN)/nyc --reporter=lcov $(NODE_BIN)/ava test/*.test.js 16 | @$(NODE_BIN)/nyc report 17 | 18 | coveralls: cover 19 | @cat ./coverage/lcov.info | $(NODE_BIN)/coveralls --verbose 20 | 21 | babel: test 22 | @echo Babel converting... 23 | @rm -rf lib 24 | @mkdir -p lib 25 | @$(NODE_BIN)/babel $(SRC) --out-dir $(DIST) 26 | 27 | publish: test babel 28 | @echo NPM publishing... 29 | @npm publish 30 | 31 | .PHONY: lint test babel cover coveralls publish 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Version](http://img.shields.io/npm/v/mooseql.svg)](https://www.npmjs.org/package/mooseql) 4 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) 5 | [![npm download][download-image]][download-url] 6 | [![Build Status](https://travis-ci.org/wwayne/mooseql.svg?branch=master)](https://travis-ci.org/wwayne/mooseql) 7 | [![Coverage Status](https://coveralls.io/repos/github/wwayne/mooseql/badge.svg?branch=master)](https://coveralls.io/github/wwayne/mooseql?branch=master) 8 | 9 | [download-image]: https://img.shields.io/npm/dm/mooseql.svg?style=flat-square 10 | [download-url]: https://npmjs.org/package/mooseql 11 | *** 12 | 13 | Both Graphql and Mongoose are aim for improving and simplifing the developing workflow, but it is verbose to use them together, you have to build repeated Graphql type based on Mongoose schema, create simple CRUD method for each model. MooseQL is created to glue Mongoose to Graphql for making everything to become easy again. 14 | 15 | ## Installation 16 | > npm install mongoose --save 17 | 18 | 19 | ## Getting Started 20 | #### 1. Creating Mongoose model as usual 21 | ```js 22 | import mongoose, { Schema } from 'mongoose' 23 | const userSchema = new Schema({ 24 | name: { 25 | first: { type: String, required: 'first name required' }, 26 | last: String 27 | }, 28 | school: { type: Schema.Types.ObjectId, ref: 'School' } 29 | }) 30 | export const UserModel = mongoose.model('User', userSchema) 31 | ``` 32 | 33 | #### 2. Passing model to MooseQL to get GraphQL schema 34 | ```js 35 | import mooseql from 'mooseql' 36 | const mySchema = mooseql([UserModel]) 37 | ``` 38 | #### 3. That's it, you can use it now, let's using express + express-graphql for example 39 | > Server side 40 | 41 | ```js 42 | import express from 'express' 43 | import graphqlHTTP from 'express-graphql' 44 | 45 | const app = express() 46 | app.use('/graphql', graphqlHTTP({ 47 | schema: mySchema 48 | })) 49 | app.listen(3000) 50 | ``` 51 | > Client side 52 | 53 | ```js 54 | - Create a new user 55 | 56 | post('/graphql') 57 | .send({ 58 | query: `mutation create { 59 | user: createUser( 60 | name_first: "wwayne" 61 | school: school.id 62 | ) { 63 | id, 64 | name { 65 | first, 66 | last 67 | }, 68 | school { 69 | id, 70 | schoolName 71 | } 72 | } 73 | }` 74 | }) 75 | ``` 76 | 77 | ## Query 78 | ##### See we have a Mongoose schema for User model: 79 | 80 | ``` 81 | { 82 | name: { 83 | first: String, 84 | last: { 85 | fst: String, 86 | snd: Number 87 | } 88 | }, 89 | userName: { type: String, required: [true, 'userName is required'] }, 90 | age: Number, 91 | isBot: Boolean, 92 | birth: { type: Date, default: Date.now }, 93 | binary: Buffer, 94 | info: Schema.Types.Mixed, 95 | hobbies: { type: [String], required: 'hobbies should have one at least' }, 96 | currentSchool: { type: Schema.Types.ObjectId, ref: 'School' }, 97 | education: [{ type: Schema.Types.ObjectId, ref: 'School' }] 98 | } 99 | ``` 100 | 101 | #### You can query like this after using mooseql to generate the schema 102 | 103 | ```js 104 | `{ user ( 105 | id: "${userInstance.id}" 106 | name_first: "wayne", 107 | userNames: ["wwayne01", "wwayne02"], 108 | age: 1, 109 | isBot: false, 110 | birth: "${new Date(2016, 8, 15)}", 111 | binary: "${new Buffer('wayne')}", 112 | hobbies: ["basketball"], 113 | currentSchool: "${schoolInstance.id}" 114 | education: ["${schoolInstance.id}"] 115 | ) { 116 | id 117 | name { 118 | first 119 | } 120 | currentSchool { 121 | id 122 | name 123 | } 124 | education { 125 | id 126 | } 127 | } 128 | }` 129 | ``` 130 | 131 | #### Key points: 132 | 133 | * Query by id or ids is available for every model 134 | * You can query by plural of the attribute, e.g. `name -> names` 135 | * If the attribute is plural, you can't query by its singular 136 | * When you want to use an object as query arguments, use `_` to replace `.`, e.g.: `name.first` -> `name_first` 137 | * Attribute of Mixed type is not supported to be query arguments 138 | * The response is always an Array 139 | 140 | ## Mutation 141 | #### Create 142 | The mutation of create is `create{modelName}`, if the attribute of the model is defined as **required** in Mongoose shema, it is required as well for create mutaion. Using User model for example: 143 | 144 | ```js 145 | `mutation createMyUser { 146 | user: createUser( 147 | userName: "wwayne", 148 | hobbies: ["This", "is", "Required"], 149 | currentSchool: "${schoolInstance.id}" 150 | ) { 151 | id 152 | userName 153 | currentSchool { 154 | id 155 | } 156 | } 157 | }` 158 | ``` 159 | 160 | #### Update 161 | The mutation of update is `update{modelName}`. Currently update only support single document update, which means the argument `id` is required and plural attribute can't be used as argument unless it is plural originally. For example, `userNames` is not supposed to be an argument, but `hobbies` and `education` could. 162 | 163 | Using User model for example: 164 | 165 | ```js 166 | `mutation updateMyUser { 167 | user: updateUser( 168 | id: "${userInstance.id}" 169 | userName: "wwayne" 170 | ) { 171 | id 172 | userName 173 | } 174 | }` 175 | ``` 176 | 177 | #### Delete 178 | The mutation of delete is `delete{modelName}`. Currently delete only support single document update, so it only accept `id` as argument. 179 | 180 | The response is `{ success: {Bool}, msg: {String or null} }` 181 | 182 | Using User model for example: 183 | 184 | ```js 185 | `mutation deleteMyUser { 186 | result: deleteUser( 187 | id: "${userInstance.id}" 188 | ) { 189 | success 190 | msg 191 | } 192 | }` 193 | ``` 194 | 195 | ## Supported type 196 | All Mongoose types are supported 197 | 198 | * String 199 | * Number 200 | * Date 201 | * Buffer 202 | * Boolean 203 | * Mixed (Can't be used as argument in query and mutation) 204 | * ObjectId (Must be accompanied with `ref`) 205 | * Array 206 | 207 | In addition, the object type is supported as well, but you have to use `_` to replace `.` when using it as argument, because Graphql only support /.[a-z][A-Z][0-9]/ as name convention for the moment. 208 | > Mongoose: name { first: { one: String, two: Number }, last: String } 209 | 210 | ``` 211 | user ( 212 | name_first_one: "firstOne", 213 | name_first_two: 21, 214 | name_last: "nameLast" 215 | ) { 216 | id 217 | } 218 | ``` 219 | 220 | ## Extend schema 221 | Mooseql will generate some basic CRUD methods which may not enough for you, then you need to define your own schema. 222 | 223 | #### buildTypes 224 | To make it easy, Mooseql exposes an method called `buildTypes` which used for converted Mongoose model to Graphql Type 225 | 226 | ``` 227 | import mooseql, { buildTypes } from 'mooseql' 228 | ``` 229 | #### Extend default schema 230 | See you have two models `UserModel` and `SchoolModel`, you can extend schema like following: 231 | 232 | ```js 233 | import mooseql, { buildTypes } from 'mooseql' 234 | 235 | // typeMap = {User: GraphqlUserType, School: GraphqlSchoolType } 236 | const typeMap = buildTypes([UserModel, SchoolModel]) 237 | 238 | const mySchema = mooseql([UserModel, SchoolModel], { 239 | mutation: { 240 | customAddUser: { 241 | type: typeMap['User'], 242 | args: { 243 | userName: { type: new GraphQLNonNull(GraphQLString) }, 244 | hobbies: { type: new GraphQLNonNull(new GraphQLList(GraphQLString)) }, 245 | age: { type: new GraphQLNonNull(GraphQLFloat) } 246 | }, 247 | resolve: async (_, args) => { 248 | const instance = new UserModel(args) 249 | return await instance.save() 250 | } 251 | } 252 | } 253 | }) 254 | 255 | ``` 256 | So it just pass `{ query: {Your custom queries}, mutation: {Your custom mutations} }` into `mooseql()` as the second argument. 257 | 258 | ## Advanced usage 259 | ### Make use of context 260 | In practice, we store the session in req.session or logged in user data in req.user, and it is usually passed to graphql as context. In this situation, you just need to add an option `context` in your Mongoose schema. Let's using **express + express-graphql + password + express-session** for example. 261 | 262 | See you have a model named Article, the author is logged in user, so Mongoose schema may like the following: 263 | 264 | ```js 265 | const articleSchema = new Schema({ 266 | title: String, 267 | author: { 268 | type: Schema.Types.ObjectId, 269 | required: true, 270 | ref: 'User', 271 | context: 'user.id' 272 | } 273 | }) 274 | ``` 275 | Since express-graphql will use `request` object as context if nothing set to context, and passport will set user object into `req.user`, express-session will use `req.session`. So your context is probably like 276 | 277 | ```js 278 | { 279 | user: userObject, 280 | session: sessionObject 281 | } 282 | ``` 283 | That's why you set `context: 'user.id'` in your Mongoose schema, because the path `author` only store an ObjectId, so it only cares about `user.id` instead of the completed user object. 284 | 285 | In this way, after user login, when you create an new article, you don't need to pass the autor as params even though it has been set to `required`, the author will be got from `req.user.id` automatically. The query string is like: 286 | 287 | ```js 288 | `mutation create { 289 | article: createArticle ( 290 | title: "How to use graphql" 291 | ) { 292 | id 293 | title 294 | author { 295 | id 296 | userName 297 | } 298 | } 299 | }` 300 | ``` 301 | 302 | ## License 303 | MIT -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _assign = require('babel-runtime/core-js/object/assign'); 4 | 5 | var _assign2 = _interopRequireDefault(_assign); 6 | 7 | var _graphql = require('graphql'); 8 | 9 | var _type = require('./type'); 10 | 11 | var _schema = require('./schema'); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | const mooseql = (models, customFields, opt) => { 16 | const typeMap = (0, _type.modelsToTypes)(models); 17 | 18 | var _buildSchema = (0, _schema.buildSchema)(models, typeMap); 19 | 20 | const query = _buildSchema.query; 21 | const mutation = _buildSchema.mutation; 22 | 23 | const customQuery = customFields && customFields.query; 24 | const customMutation = customFields && customFields.mutation; 25 | return new _graphql.GraphQLSchema({ 26 | query: new _graphql.GraphQLObjectType({ 27 | name: 'Query', 28 | fields: (0, _assign2.default)({}, query, customQuery) 29 | }), 30 | mutation: new _graphql.GraphQLObjectType({ 31 | name: 'Mutation', 32 | fields: (0, _assign2.default)({}, mutation, customMutation) 33 | }) 34 | }); 35 | }; 36 | 37 | mooseql.buildTypes = _type.modelsToTypes; 38 | 39 | module.exports = mooseql; 40 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9pbmRleC5qcyJdLCJuYW1lcyI6WyJtb29zZXFsIiwibW9kZWxzIiwiY3VzdG9tRmllbGRzIiwib3B0IiwidHlwZU1hcCIsInF1ZXJ5IiwibXV0YXRpb24iLCJjdXN0b21RdWVyeSIsImN1c3RvbU11dGF0aW9uIiwibmFtZSIsImZpZWxkcyIsImJ1aWxkVHlwZXMiLCJtb2R1bGUiLCJleHBvcnRzIl0sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQTs7QUFDQTs7QUFDQTs7OztBQUVBLE1BQU1BLFVBQVUsQ0FBQ0MsTUFBRCxFQUFTQyxZQUFULEVBQXVCQyxHQUF2QixLQUErQjtBQUM3QyxRQUFNQyxVQUFVLHlCQUFjSCxNQUFkLENBQWhCOztBQUQ2QyxxQkFFbkIseUJBQVlBLE1BQVosRUFBb0JHLE9BQXBCLENBRm1COztBQUFBLFFBRXRDQyxLQUZzQyxnQkFFdENBLEtBRnNDO0FBQUEsUUFFL0JDLFFBRitCLGdCQUUvQkEsUUFGK0I7O0FBRzdDLFFBQU1DLGNBQWNMLGdCQUFnQkEsYUFBYUcsS0FBakQ7QUFDQSxRQUFNRyxpQkFBaUJOLGdCQUFnQkEsYUFBYUksUUFBcEQ7QUFDQSxTQUFPLDJCQUFrQjtBQUN2QkQsV0FBTywrQkFBc0I7QUFDM0JJLFlBQU0sT0FEcUI7QUFFM0JDLGNBQVEsc0JBQWMsRUFBZCxFQUFrQkwsS0FBbEIsRUFBeUJFLFdBQXpCO0FBRm1CLEtBQXRCLENBRGdCO0FBS3ZCRCxjQUFVLCtCQUFzQjtBQUM5QkcsWUFBTSxVQUR3QjtBQUU5QkMsY0FBUSxzQkFBYyxFQUFkLEVBQWtCSixRQUFsQixFQUE0QkUsY0FBNUI7QUFGc0IsS0FBdEI7QUFMYSxHQUFsQixDQUFQO0FBVUQsQ0FmRDs7QUFpQkFSLFFBQVFXLFVBQVI7O0FBRUFDLE9BQU9DLE9BQVAsR0FBaUJiLE9BQWpCIiwiZmlsZSI6ImluZGV4LmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgR3JhcGhRTFNjaGVtYSwgR3JhcGhRTE9iamVjdFR5cGUgfSBmcm9tICdncmFwaHFsJ1xuaW1wb3J0IHsgbW9kZWxzVG9UeXBlcyB9IGZyb20gJy4vdHlwZSdcbmltcG9ydCB7IGJ1aWxkU2NoZW1hIH0gZnJvbSAnLi9zY2hlbWEnXG5cbmNvbnN0IG1vb3NlcWwgPSAobW9kZWxzLCBjdXN0b21GaWVsZHMsIG9wdCkgPT4ge1xuICBjb25zdCB0eXBlTWFwID0gbW9kZWxzVG9UeXBlcyhtb2RlbHMpXG4gIGNvbnN0IHtxdWVyeSwgbXV0YXRpb259ID0gYnVpbGRTY2hlbWEobW9kZWxzLCB0eXBlTWFwKVxuICBjb25zdCBjdXN0b21RdWVyeSA9IGN1c3RvbUZpZWxkcyAmJiBjdXN0b21GaWVsZHMucXVlcnlcbiAgY29uc3QgY3VzdG9tTXV0YXRpb24gPSBjdXN0b21GaWVsZHMgJiYgY3VzdG9tRmllbGRzLm11dGF0aW9uXG4gIHJldHVybiBuZXcgR3JhcGhRTFNjaGVtYSh7XG4gICAgcXVlcnk6IG5ldyBHcmFwaFFMT2JqZWN0VHlwZSh7XG4gICAgICBuYW1lOiAnUXVlcnknLFxuICAgICAgZmllbGRzOiBPYmplY3QuYXNzaWduKHt9LCBxdWVyeSwgY3VzdG9tUXVlcnkpXG4gICAgfSksXG4gICAgbXV0YXRpb246IG5ldyBHcmFwaFFMT2JqZWN0VHlwZSh7XG4gICAgICBuYW1lOiAnTXV0YXRpb24nLFxuICAgICAgZmllbGRzOiBPYmplY3QuYXNzaWduKHt9LCBtdXRhdGlvbiwgY3VzdG9tTXV0YXRpb24pXG4gICAgfSlcbiAgfSlcbn1cblxubW9vc2VxbC5idWlsZFR5cGVzID0gbW9kZWxzVG9UeXBlc1xuXG5tb2R1bGUuZXhwb3J0cyA9IG1vb3NlcWxcbiJdfQ== -------------------------------------------------------------------------------- /lib/schema/buildArgs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _keys = require('babel-runtime/core-js/object/keys'); 8 | 9 | var _keys2 = _interopRequireDefault(_keys); 10 | 11 | var _assign = require('babel-runtime/core-js/object/assign'); 12 | 13 | var _assign2 = _interopRequireDefault(_assign); 14 | 15 | var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); 16 | 17 | var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); 18 | 19 | var _entries = require('babel-runtime/core-js/object/entries'); 20 | 21 | var _entries2 = _interopRequireDefault(_entries); 22 | 23 | exports.default = function (type) { 24 | const fields = type._typeConfig.fields(); 25 | return (0, _entries2.default)(fields).reduce((args, _ref) => { 26 | var _ref2 = (0, _slicedToArray3.default)(_ref, 2); 27 | 28 | let key = _ref2[0]; 29 | let field = _ref2[1]; 30 | 31 | return (0, _assign2.default)(args, fieldToArg(key, field)); 32 | }, {}); 33 | }; 34 | 35 | var _graphql = require('graphql'); 36 | 37 | var _customType = require('../type/customType'); 38 | 39 | var _pluralize = require('pluralize'); 40 | 41 | var _pluralize2 = _interopRequireDefault(_pluralize); 42 | 43 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 44 | 45 | const fieldToArg = (key, field) => { 46 | const typeName = field.type.name || field.type.constructor.name; 47 | const required = field.required; 48 | const ref = field.ref; 49 | const context = field.context; 50 | 51 | let graphqlType; 52 | if (typeName !== 'GraphQLList') { 53 | graphqlType = nameToType(typeName, field); 54 | // Custom type for Object attribute in mongoose model. e.g. {name: {first, last}} 55 | if (!graphqlType) return buildObjectArgs(key, field); 56 | return buildArgs(key, graphqlType, { required: required, ref: ref, context: context }); 57 | } else { 58 | // Deal with List type 59 | graphqlType = nameToType(field.type.ofType.name, field); 60 | return { [key]: { type: new _graphql.GraphQLList(graphqlType), onlyPlural: true, required: required, ref: ref, context: context } }; 61 | } 62 | }; 63 | 64 | /** 65 | * Generate args based on type's fields 66 | * @parmas 67 | * - type {Object} built graphql type 68 | * @return 69 | * - defaultArgs {Object} argus based on type's fields, including singular and plural 70 | * @notice 71 | * - response has `id` and `ids` instead of _id 72 | * mongoose query should convert id to _id or mongoose won't support it 73 | * - name.first in Mongoose model will have args 'name_first' and 'name_firsts' 74 | * because graphql name convention only support _a-zA-Z0-9 75 | */ 76 | 77 | 78 | const buildArgs = (key, graphqlType, _ref3) => { 79 | let required = _ref3.required; 80 | let ref = _ref3.ref; 81 | let context = _ref3.context; 82 | 83 | if ((0, _keys2.default)(graphqlType).length === 0) return {}; 84 | const plural = _pluralize2.default.plural(key); 85 | const isPlural = plural === key; 86 | return isPlural ? { [key]: { type: new _graphql.GraphQLList(graphqlType), onlyPlural: true, required: required, ref: ref, context: context } } : { [key]: { type: graphqlType, required: required, ref: ref, context: context }, [plural]: { type: new _graphql.GraphQLList(graphqlType) } }; 87 | }; 88 | 89 | const nameToType = (typeName, field) => { 90 | const hasResolve = !!field.resolve; 91 | switch (typeName) { 92 | case 'ID': 93 | return _graphql.GraphQLID; 94 | case 'String': 95 | return _graphql.GraphQLString; 96 | case 'Float': 97 | return _graphql.GraphQLFloat; 98 | case 'Boolean': 99 | return _graphql.GraphQLBoolean; 100 | case 'Buffer': 101 | return _customType.GraphQLBuffer; 102 | case 'Date': 103 | return _customType.GraphQLDate; 104 | case 'Mixed': 105 | return {}; 106 | default: 107 | if (hasResolve) return _graphql.GraphQLID; // other models, use ID as reference 108 | return null; // Object attribute in mongoose model 109 | } 110 | }; 111 | 112 | // Build args for Object attribute of the mongoose model 113 | const buildObjectArgs = (parentKey, field) => { 114 | const fields = field.type._typeConfig.fields(); 115 | return (0, _entries2.default)(fields).map(_ref4 => { 116 | var _ref5 = (0, _slicedToArray3.default)(_ref4, 2); 117 | 118 | let key = _ref5[0]; 119 | let value = _ref5[1]; 120 | 121 | return fieldToArg(`${ parentKey }_${ key }`, value); 122 | }).reduce((args, myArg) => (0, _assign2.default)(args, myArg), {}); 123 | }; 124 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zY2hlbWEvYnVpbGRBcmdzLmpzIl0sIm5hbWVzIjpbInR5cGUiLCJmaWVsZHMiLCJfdHlwZUNvbmZpZyIsInJlZHVjZSIsImFyZ3MiLCJrZXkiLCJmaWVsZCIsImZpZWxkVG9BcmciLCJ0eXBlTmFtZSIsIm5hbWUiLCJjb25zdHJ1Y3RvciIsInJlcXVpcmVkIiwicmVmIiwiY29udGV4dCIsImdyYXBocWxUeXBlIiwibmFtZVRvVHlwZSIsImJ1aWxkT2JqZWN0QXJncyIsImJ1aWxkQXJncyIsIm9mVHlwZSIsIm9ubHlQbHVyYWwiLCJsZW5ndGgiLCJwbHVyYWwiLCJpc1BsdXJhbCIsImhhc1Jlc29sdmUiLCJyZXNvbHZlIiwicGFyZW50S2V5IiwibWFwIiwidmFsdWUiLCJteUFyZyJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztrQkF5QmUsVUFBVUEsSUFBVixFQUFnQjtBQUM3QixRQUFNQyxTQUFTRCxLQUFLRSxXQUFMLENBQWlCRCxNQUFqQixFQUFmO0FBQ0EsU0FBTyx1QkFBZUEsTUFBZixFQUNKRSxNQURJLENBQ0csQ0FBQ0MsSUFBRCxXQUF3QjtBQUFBOztBQUFBLFFBQWhCQyxHQUFnQjtBQUFBLFFBQVhDLEtBQVc7O0FBQzlCLFdBQU8sc0JBQWNGLElBQWQsRUFBb0JHLFdBQVdGLEdBQVgsRUFBZ0JDLEtBQWhCLENBQXBCLENBQVA7QUFDRCxHQUhJLEVBR0YsRUFIRSxDQUFQO0FBSUQsQzs7QUEvQkQ7O0FBT0E7O0FBSUE7Ozs7OztBQXNCQSxNQUFNQyxhQUFhLENBQUNGLEdBQUQsRUFBTUMsS0FBTixLQUFnQjtBQUNqQyxRQUFNRSxXQUFXRixNQUFNTixJQUFOLENBQVdTLElBQVgsSUFBbUJILE1BQU1OLElBQU4sQ0FBV1UsV0FBWCxDQUF1QkQsSUFBM0Q7QUFEaUMsUUFFekJFLFFBRnlCLEdBRUVMLEtBRkYsQ0FFekJLLFFBRnlCO0FBQUEsUUFFZkMsR0FGZSxHQUVFTixLQUZGLENBRWZNLEdBRmU7QUFBQSxR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d0IsS0FBcEIsQ0FGckIsRUFFa0QsRUFGbEQsQ0FBUDtBQUdELENBTEQiLCJmaWxlIjoiYnVpbGRBcmdzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgR3JhcGhRTElELFxuICBHcmFwaFFMU3RyaW5nLFxuICBHcmFwaFFMRmxvYXQsXG4gIEdyYXBoUUxCb29sZWFuLFxuICBHcmFwaFFMTGlzdFxufSBmcm9tICdncmFwaHFsJ1xuaW1wb3J0IHtcbiAgR3JhcGhRTEJ1ZmZlcixcbiAgR3JhcGhRTERhdGVcbn0gZnJvbSAnLi4vdHlwZS9jdXN0b21UeXBlJ1xuaW1wb3J0IHBsdXJhbGl6ZSBmcm9tICdwbHVyYWxpemUnXG5cbi8qKlxuICogR2VuZXJhdGUgYXJncyBiYXNlZCBvbiB0eXBlJ3MgZmllbGRzXG4gKiBAcGFybWFzXG4gKiAgLSB0eXBlIHtPYmplY3R9IGJ1aWx0IGdyYXBocWwgdHlwZVxuICogQHJldHVyblxuICogIC0gZGVmYXVsdEFyZ3Mge09iamVjdH0gYXJndXMgYmFzZWQgb24gdHlwZSdzIGZpZWxkcywgaW5jbHVkaW5nIHNpbmd1bGFyIGFuZCBwbHVyYWxcbiAqIEBub3RpY2VcbiAqICAtIHJlc3BvbnNlIGhhcyBgaWRgIGFuZCBgaWRzYCBpbnN0ZWFkIG9mIF9pZFxuICogICAgbW9uZ29vc2UgcXVlcnkgc2hvdWxkIGNvbnZlcnQgaWQgdG8gX2lkIG9yIG1vbmdvb3NlIHdvbid0IHN1cHBvcnQgaXRcbiAqICAtIG5hbWUuZmlyc3QgaW4gTW9uZ29vc2UgbW9kZWwgd2lsbCBoYXZlIGFyZ3MgJ25hbWVfZmlyc3QnIGFuZCAnbmFtZV9maXJzdHMnXG4gKiAgICBiZWNhdXNlIGdyYXBocWwgbmFtZSBjb252ZW50aW9uIG9ubHkgc3VwcG9ydCBfYS16QS1aMC05XG4gKi9cbmV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uICh0eXBlKSB7XG4gIGNvbnN0IGZpZWxkcyA9IHR5cGUuX3R5cGVDb25maWcuZmllbGRzKClcbiAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKGZpZWxkcylcbiAgICAucmVkdWNlKChhcmdzLCBba2V5LCBmaWVsZF0pID0+IHtcbiAgICAgIHJldHVybiBPYmplY3QuYXNzaWduKGFyZ3MsIGZpZWxkVG9Bcmcoa2V5LCBmaWVsZCkpXG4gICAgfSwge30pXG59XG5cbmNvbnN0IGZpZWxkVG9BcmcgPSAoa2V5LCBmaWVsZCkgPT4ge1xuICBjb25zdCB0eXBlTmFtZSA9IGZpZWxkLnR5cGUubmFtZSB8fCBmaWVsZC50eXBlLmNvbnN0cnVjdG9yLm5hbWVcbiAgY29uc3QgeyByZXF1aXJlZCwgcmVmLCBjb250ZXh0IH0gPSBmaWVsZFxuICBsZXQgZ3JhcGhxbFR5cGVcbiAgaWYgKHR5cGVOYW1lICE9PSAnR3JhcGhRTExpc3QnKSB7XG4gICAgZ3JhcGhxbFR5cGUgPSBuYW1lVG9UeXBlKHR5cGVOYW1lLCBmaWVsZClcbiAgICAvLyBDdXN0b20gdHlwZSBmb3IgT2JqZWN0IGF0dHJpYnV0ZSBpbiBtb25nb29zZSBtb2RlbC4gZS5nLiB7bmFtZToge2ZpcnN0LCBsYXN0fX1cbiAgICBpZiAoIWdyYXBocWxUeXBlKSByZXR1cm4gYnVpbGRPYmplY3RBcmdzKGtleSwgZmllbGQpXG4gICAgcmV0dXJuIGJ1aWxkQXJncyhrZXksIGdyYXBocWxUeXBlLCB7IHJlcXVpcmVkLCByZWYsIGNvbnRleHQgfSlcbiAgfSBlbHNlIHtcbiAgICAvLyBEZWFsIHdpdGggTGlzdCB0eXBlXG4gICAgZ3JhcGhxbFR5cGUgPSBuYW1lVG9UeXBlKGZpZWxkLnR5cGUub2ZUeXBlLm5hbWUsIGZpZWxkKVxuICAgIHJldHVybiB7W2tleV06IHsgdHlwZTogbmV3IEdyYXBoUUxMaXN0KGdyYXBocWxUeXBlKSwgb25seVBsdXJhbDogdHJ1ZSwgcmVxdWlyZWQsIHJlZiwgY29udGV4dCB9fVxuICB9XG59XG5cbmNvbnN0IGJ1aWxkQXJncyA9IChrZXksIGdyYXBocWxUeXBlLCB7IHJlcXVpcmVkLCByZWYsIGNvbnRleHQgfSkgPT4ge1xuICBpZiAoT2JqZWN0LmtleXMoZ3JhcGhxbFR5cGUpLmxlbmd0aCA9PT0gMCkgcmV0dXJuIHt9XG4gIGNvbnN0IHBsdXJhbCA9IHBsdXJhbGl6ZS5wbHVyYWwoa2V5KVxuICBjb25zdCBpc1BsdXJhbCA9IHBsdXJhbCA9PT0ga2V5XG4gIHJldHVybiBpc1BsdXJhbFxuICAgID8ge1trZXldOiB7IHR5cGU6IG5ldyBHcmFwaFFMTGlzdChncmFwaHFsVHlwZSksIG9ubHlQbHVyYWw6IHRydWUsIHJlcXVpcmVkLCByZWYsIGNvbnRleHQgfX1cbiAgICA6IHtba2V5XTogeyB0eXBlOiBncmFwaHFsVHlwZSwgcmVxdWlyZWQsIHJlZiwgY29udGV4dCB9LCBbcGx1cmFsXTogeyB0eXBlOiBuZXcgR3JhcGhRTExpc3QoZ3JhcGhxbFR5cGUpIH19XG59XG5cbmNvbnN0IG5hbWVUb1R5cGUgPSAodHlwZU5hbWUsIGZpZWxkKSA9PiB7XG4gIGNvbnN0IGhhc1Jlc29sdmUgPSAhIWZpZWxkLnJlc29sdmVcbiAgc3dpdGNoICh0eXBlTmFtZSkge1xuICAgIGNhc2UgJ0lEJzogcmV0dXJuIEdyYXBoUUxJRFxuICAgIGNhc2UgJ1N0cmluZyc6IHJldHVybiBHcmFwaFFMU3RyaW5nXG4gICAgY2FzZSAnRmxvYXQnOiByZXR1cm4gR3JhcGhRTEZsb2F0XG4gICAgY2FzZSAnQm9vbGVhbic6IHJldHVybiBHcmFwaFFMQm9vbGVhblxuICAgIGNhc2UgJ0J1ZmZlcic6IHJldHVybiBHcmFwaFFMQnVmZmVyXG4gICAgY2FzZSAnRGF0ZSc6IHJldHVybiBHcmFwaFFMRGF0ZVxuICAgIGNhc2UgJ01peGVkJzogcmV0dXJuIHt9XG4gICAgZGVmYXVsdDpcbiAgICAgIGlmIChoYXNSZXNvbHZlKSByZXR1cm4gR3JhcGhRTElEIC8vIG90aGVyIG1vZGVscywgdXNlIElEIGFzIHJlZmVyZW5jZVxuICAgICAgcmV0dXJuIG51bGwgLy8gT2JqZWN0IGF0dHJpYnV0ZSBpbiBtb25nb29zZSBtb2RlbFxuICB9XG59XG5cbi8vIEJ1aWxkIGFyZ3MgZm9yIE9iamVjdCBhdHRyaWJ1dGUgb2YgdGhlIG1vbmdvb3NlIG1vZGVsXG5jb25zdCBidWlsZE9iamVjdEFyZ3MgPSAocGFyZW50S2V5LCBmaWVsZCkgPT4ge1xuICBjb25zdCBmaWVsZHMgPSBmaWVsZC50eXBlLl90eXBlQ29uZmlnLmZpZWxkcygpXG4gIHJldHVybiBPYmplY3QuZW50cmllcyhmaWVsZHMpLm1hcCgoW2tleSwgdmFsdWVdKSA9PiB7XG4gICAgcmV0dXJuIGZpZWxkVG9BcmcoYCR7cGFyZW50S2V5fV8ke2tleX1gLCB2YWx1ZSlcbiAgfSkucmVkdWNlKChhcmdzLCBteUFyZykgPT4gKE9iamVjdC5hc3NpZ24oYXJncywgbXlBcmcpKSwge30pXG59XG4iXX0= -------------------------------------------------------------------------------- /lib/schema/buildMutation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _assign = require('babel-runtime/core-js/object/assign'); 8 | 9 | var _assign2 = _interopRequireDefault(_assign); 10 | 11 | var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); 12 | 13 | var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); 14 | 15 | var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); 16 | 17 | var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); 18 | 19 | var _entries = require('babel-runtime/core-js/object/entries'); 20 | 21 | var _entries2 = _interopRequireDefault(_entries); 22 | 23 | exports.default = function (model, type) { 24 | const modelName = model.modelName; 25 | const defaultArgs = (0, _buildArgs2.default)(type); 26 | return { 27 | [`create${ modelName }`]: buildCreate(model, type, defaultArgs), 28 | [`update${ modelName }`]: buildUpdate(model, type, defaultArgs), 29 | [`delete${ modelName }`]: buildDelete(model, type, defaultArgs) 30 | }; 31 | }; 32 | 33 | var _graphql = require('graphql'); 34 | 35 | var _buildArgs = require('./buildArgs'); 36 | 37 | var _buildArgs2 = _interopRequireDefault(_buildArgs); 38 | 39 | var _utils = require('../utils'); 40 | 41 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 42 | 43 | const buildCreate = (Model, type, defaultArgs) => { 44 | const createArgs = (0, _utils.filterArgs)(defaultArgs, { id: true, plural: true }); 45 | const contextArgs = (0, _entries2.default)(createArgs).filter(_ref => { 46 | var _ref2 = (0, _slicedToArray3.default)(_ref, 2); 47 | 48 | let key = _ref2[0]; 49 | let value = _ref2[1]; 50 | 51 | return value.context; 52 | }); 53 | return { 54 | type: type, 55 | args: createArgs, 56 | resolve: (() => { 57 | var _ref3 = (0, _asyncToGenerator3.default)(function* (root, args, context) { 58 | // Set up args that marked as context 59 | contextArgs.forEach(function (_ref4) { 60 | var _ref5 = (0, _slicedToArray3.default)(_ref4, 2); 61 | 62 | let key = _ref5[0]; 63 | let value = _ref5[1]; 64 | 65 | if (value.required) { 66 | // Check if an arg is required and has context 67 | // because it won't be marked as GraphqlNonNull in args 68 | const ctx = value.context.split('.')[0]; // user.id -> user 69 | if (context[ctx] === undefined) throw new Error(`${ key } is required`); 70 | } 71 | args[key] = (0, _utils.pickoutValue)(context, value.context); 72 | }); 73 | const instance = new Model((0, _utils.toMongooseArgs)(args)); 74 | return yield instance.save(); 75 | }); 76 | 77 | return function resolve(_x, _x2, _x3) { 78 | return _ref3.apply(this, arguments); 79 | }; 80 | })() 81 | }; 82 | }; 83 | 84 | /** 85 | * Build mutation for single model 86 | * @params 87 | * - model a mongoose model 88 | * - type a corresponding converted graphql type 89 | * @return 90 | * - {Object} e.g. { createUser: {type: userType, args, resolve}, updateUser, removeUser } 91 | */ 92 | 93 | 94 | const buildUpdate = (Model, type, defaultArgs) => { 95 | return { 96 | type: type, 97 | args: (0, _utils.filterArgs)(defaultArgs, { plural: true, required: true, idRequired: true }), 98 | resolve: (() => { 99 | var _ref6 = (0, _asyncToGenerator3.default)(function* (_, args) { 100 | const updateData = (0, _entries2.default)(args).filter(function (_ref7) { 101 | var _ref8 = (0, _slicedToArray3.default)(_ref7, 2); 102 | 103 | let key = _ref8[0]; 104 | let _ = _ref8[1]; 105 | 106 | if (key === 'id' || key === 'ids') return false; 107 | return true; 108 | }).map(function (_ref9) { 109 | var _ref10 = (0, _slicedToArray3.default)(_ref9, 2); 110 | 111 | let key = _ref10[0]; 112 | let value = _ref10[1]; 113 | 114 | return [key.replace('_', '.'), value]; 115 | }).reduce(function (args, _ref11) { 116 | var _ref12 = (0, _slicedToArray3.default)(_ref11, 2); 117 | 118 | let key = _ref12[0]; 119 | let value = _ref12[1]; 120 | return (0, _assign2.default)(args, { [key]: value }); 121 | }, {}); 122 | 123 | yield Model.update({ _id: args.id }, { $set: updateData }); 124 | return yield Model.findById(args.id); 125 | }); 126 | 127 | return function resolve(_x4, _x5) { 128 | return _ref6.apply(this, arguments); 129 | }; 130 | })() 131 | }; 132 | }; 133 | 134 | const buildDelete = (Model, type, defaultArgs) => { 135 | const returnType = new _graphql.GraphQLObjectType({ 136 | name: `${ Model.modelName }deleteMutationReturn`, 137 | fields: () => ({ 138 | success: { type: _graphql.GraphQLBoolean }, 139 | msg: { type: _graphql.GraphQLString } 140 | }) 141 | }); 142 | return { 143 | type: returnType, 144 | args: (0, _utils.filterArgs)(defaultArgs, { plural: true, idRequired: true, onlyId: true }), 145 | resolve: (() => { 146 | var _ref13 = (0, _asyncToGenerator3.default)(function* (_, args) { 147 | let res = { success: true, msg: null }; 148 | try { 149 | yield Model.findById(args.id).remove(); 150 | } catch (err) { 151 | res = { success: false, msg: err.message }; 152 | } 153 | return res; 154 | }); 155 | 156 | return function resolve(_x6, _x7) { 157 | return _ref13.apply(this, arguments); 158 | }; 159 | })() 160 | }; 161 | }; 162 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zY2hlbWEvYnVpbGRNdXRhdGlvbi5qcyJdLCJuYW1lcyI6WyJtb2RlbCIsInR5cGUiLCJtb2RlbE5hbWUiLCJkZWZhdWx0QXJncyIsImJ1aWxkQ3JlYXRlIiwiYnVpbGRVcGRhdGUiLCJidWlsZERlbGV0ZSIsIk1vZGVsIiwiY3JlYXRlQXJncyIsImlkIiwicGx1cmFsIiwiY29udGV4dEFyZ3MiLCJmaWx0ZXIiLCJrZXkiLCJ2YWx1ZSIsImNvbnRleHQiLCJhcmdzIiwicmVzb2x2ZSIsInJvb3QiLCJmb3JFYWNoIiwicmVxdWlyZWQiLCJjdHgiLCJzcGxpdCIsInVuZGVmaW5lZCIsIkVycm9yIiwiaW5zdGFuY2UiLCJzYXZlIiwiaWRSZXF1aXJlZCIsIl8iLCJ1cGRhdGVEYXRhIiwibWFwIiwicmVwbGFjZSIsInJlZHVjZSIsInVwZGF0ZSIsIl9pZCIsIiRzZXQiLCJmaW5kQnlJZCIsInJldHVyblR5cGUiLCJuYW1lIiwiZmllbGRzIiwic3VjY2VzcyIsIm1zZyIsIm9ubHlJZCIsInJlcyIsInJlbW92ZSIsImVyciIsIm1lc3NhZ2UiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7a0JBZ0JlLFVBQVVBLEtBQVYsRUFBaUJDLElBQWpCLEVBQXVCO0FBQ3BDLFFBQU1DLFlBQVlGLE1BQU1FLFNBQXhCO0FBQ0EsUUFBTUMsY0FBYyx5QkFBVUYsSUFBVixDQUFwQjtBQUNBLFNBQU87QUFDTCxLQUFFLFVBQVFDLFNBQVUsR0FBcEIsR0FBd0JFLFlBQVlKLEtBQVosRUFBbUJDLElBQW5CLEVBQXlCRSxXQUF6QixDQURuQjtBQUVMLEtBQUUsVUFBUUQsU0FBVSxHQUFwQixHQUF3QkcsWUFBWUwsS0FBWixFQUFtQkMsSUFBbkIsRUFBeUJFLFdBQXpCLENBRm5CO0FBR0wsS0FBRSxVQUFRRCxTQUFVLEdBQXBCLEdBQXdCSSxZQUFZTixLQUFaLEVBQW1CQyxJQUFuQixFQUF5QkUsV0FBekI7QUFIbkIsR0FBUDtBQUtELEM7O0FBeEJE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QUFrQkQsQ0FuQkQ7O0FBcUJBLE1BQU1ILGNBQWMsQ0FBQ0MsS0FBRCxFQUFRTixJQUFSLEVBQWNFLFdBQWQsS0FBOEI7QUFDaEQsUUFBTWtDLGFBQWEsK0JBQXNCO0FBQ3ZDQyxVQUFPLElBQUUvQixNQUFNTCxTQUFVLHVCQURjO0FBRXZDcUMsWUFBUSxPQUFPO0FBQ2JDLGVBQVMsRUFBRXZDLDZCQUFGLEVBREk7QUFFYndDLFdBQUssRUFBRXhDLDRCQUFGO0FBRlEsS0FBUDtBQUYrQixHQUF0QixDQUFuQjtBQU9BLFNBQU87QUFDTEEsVUFBTW9DLFVBREQ7QUFFTHJCLFVBQU0sdUJBQVdiLFdBQVgsRUFBd0IsRUFBRU8sUUFBUSxJQUFWLEVBQWdCaUIsWUFBWSxJQUE1QixFQUFrQ2UsUUFBUSxJQUExQyxFQUF4QixDQUZEO0FBR0x6QjtBQUFBLG1EQUFTLFdBQU9XLENBQVAsRUFBVVosSUFBVixFQUFtQjtBQUMxQixZQUFJMkIsTUFBTSxFQUFFSCxTQUFTLElBQVgsRUFBaUJDLEtBQUssSUFBdEIsRUFBVjtBQUNBLFlBQUk7QUFDRixnQkFBTWxDLE1BQU02QixRQUFOLENBQWVwQixLQUFLUCxFQUFwQixFQUF3Qm1DLE1BQXhCLEVBQU47QUFDRCxTQUZELENBRUUsT0FBT0MsR0FBUCxFQUFZO0FBQ1pGLGdCQUFNLEVBQUVILFNBQVMsS0FBWCxFQUFrQkMsS0FBS0ksSUFBSUMsT0FBM0IsRUFBTjtBQUNEO0FBQ0QsZUFBT0gsR0FBUDtBQUNELE9BUkQ7O0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFISyxHQUFQO0FBYUQsQ0FyQkQiLCJmaWxlIjoiYnVpbGRNdXRhdGlvbi5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIEdyYXBoUUxPYmplY3RUeXBlLFxuICBHcmFwaFFMQm9vbGVhbixcbiAgR3JhcGhRTFN0cmluZ1xufSBmcm9tICdncmFwaHFsJ1xuaW1wb3J0IGJ1aWxkQXJncyBmcm9tICcuL2J1aWxkQXJncydcbmltcG9ydCB7IGZpbHRlckFyZ3MsIHRvTW9uZ29vc2VBcmdzLCBwaWNrb3V0VmFsdWUgfSBmcm9tICcuLi91dGlscydcblxuLyoqXG4gKiBCdWlsZCBtdXRhdGlvbiBmb3Igc2luZ2xlIG1vZGVsXG4gKiBAcGFyYW1zXG4gKiAgLSBtb2RlbCBhIG1vbmdvb3NlIG1vZGVsXG4gKiAgLSB0eXBlIGEgY29ycmVzcG9uZGluZyBjb252ZXJ0ZWQgZ3JhcGhxbCB0eXBlXG4gKiBAcmV0dXJuXG4gKiAgLSB7T2JqZWN0fSBlLmcuIHsgY3JlYXRlVXNlcjoge3R5cGU6IHVzZXJUeXBlLCBhcmdzLCByZXNvbHZlfSwgdXBkYXRlVXNlciwgcmVtb3ZlVXNlciB9XG4gKi9cbmV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIChtb2RlbCwgdHlwZSkge1xuICBjb25zdCBtb2RlbE5hbWUgPSBtb2RlbC5tb2RlbE5hbWVcbiAgY29uc3QgZGVmYXVsdEFyZ3MgPSBidWlsZEFyZ3ModHlwZSlcbiAgcmV0dXJuIHtcbiAgICBbYGNyZWF0ZSR7bW9kZWxOYW1lfWBdOiBidWlsZENyZWF0ZShtb2RlbCwgdHlwZSwgZGVmYXVsdEFyZ3MpLFxuICAgIFtgdXBkYXRlJHttb2RlbE5hbWV9YF06IGJ1aWxkVXBkYXRlKG1vZGVsLCB0eXBlLCBkZWZhdWx0QXJncyksXG4gICAgW2BkZWxldGUke21vZGVsTmFtZX1gXTogYnVpbGREZWxldGUobW9kZWwsIHR5cGUsIGRlZmF1bHRBcmdzKVxuICB9XG59XG5cbmNvbnN0IGJ1aWxkQ3JlYXRlID0gKE1vZGVsLCB0eXBlLCBkZWZhdWx0QXJncykgPT4ge1xuICBjb25zdCBjcmVhdGVBcmdzID0gZmlsdGVyQXJncyhkZWZhdWx0QXJncywgeyBpZDogdHJ1ZSwgcGx1cmFsOiB0cnVlIH0pXG4gIGNvbnN0IGNvbnRleHRBcmdzID0gT2JqZWN0LmVudHJpZXMoY3JlYXRlQXJncykuZmlsdGVyKChba2V5LCB2YWx1ZV0pID0+IHtcbiAgICByZXR1cm4gdmFsdWUuY29udGV4dFxuICB9KVxuICByZXR1cm4ge1xuICAgIHR5cGUsXG4gICAgYXJnczogY3JlYXRlQXJncyxcbiAgICByZXNvbHZlOiBhc3luYyAocm9vdCwgYXJncywgY29udGV4dCkgPT4ge1xuICAgICAgLy8gU2V0IHVwIGFyZ3MgdGhhdCBtYXJrZWQgYXMgY29udGV4dFxuICAgICAgY29udGV4dEFyZ3MuZm9yRWFjaCgoW2tleSwgdmFsdWVdKSA9PiB7XG4gICAgICAgIGlmICh2YWx1ZS5yZXF1aXJlZCkge1xuICAgICAgICAgIC8vIENoZWNrIGlmIGFuIGFyZyBpcyByZXF1aXJlZCBhbmQgaGFzIGNvbnRleHRcbiAgICAgICAgICAvLyBiZWNhdXNlIGl0IHdvbid0IGJlIG1hcmtlZCBhcyBHcmFwaHFsTm9uTnVsbCBpbiBhcmdzXG4gICAgICAgICAgY29uc3QgY3R4ID0gdmFsdWUuY29udGV4dC5zcGxpdCgnLicpWzBdICAvLyB1c2VyLmlkIC0+IHVzZXJcbiAgICAgICAgICBpZiAoY29udGV4dFtjdHhdID09PSB1bmRlZmluZWQpIHRocm93IG5ldyBFcnJvcihgJHtrZXl9IGlzIHJlcXVpcmVkYClcbiAgICAgICAgfVxuICAgICAgICBhcmdzW2tleV0gPSBwaWNrb3V0VmFsdWUoY29udGV4dCwgdmFsdWUuY29udGV4dClcbiAgICAgIH0pXG4gICAgICBjb25zdCBpbnN0YW5jZSA9IG5ldyBNb2RlbCh0b01vbmdvb3NlQXJncyhhcmdzKSlcbiAgICAgIHJldHVybiBhd2FpdCBpbnN0YW5jZS5zYXZlKClcbiAgICB9XG4gIH1cbn1cblxuY29uc3QgYnVpbGRVcGRhdGUgPSAoTW9kZWwsIHR5cGUsIGRlZmF1bHRBcmdzKSA9PiB7XG4gIHJldHVybiB7XG4gICAgdHlwZSxcbiAgICBhcmdzOiBmaWx0ZXJBcmdzKGRlZmF1bHRBcmdzLCB7IHBsdXJhbDogdHJ1ZSwgcmVxdWlyZWQ6IHRydWUsIGlkUmVxdWlyZWQ6IHRydWUgfSksXG4gICAgcmVzb2x2ZTogYXN5bmMgKF8sIGFyZ3MpID0+IHtcbiAgICAgIGNvbnN0IHVwZGF0ZURhdGEgPSBPYmplY3QuZW50cmllcyhhcmdzKVxuICAgICAgICAuZmlsdGVyKChba2V5LCBfXSkgPT4ge1xuICAgICAgICAgIGlmIChrZXkgPT09ICdpZCcgfHwga2V5ID09PSAnaWRzJykgcmV0dXJuIGZhbHNlXG4gICAgICAgICAgcmV0dXJuIHRydWVcbiAgICAgICAgfSlcbiAgICAgICAgLm1hcCgoW2tleSwgdmFsdWVdKSA9PiB7XG4gICAgICAgICAgcmV0dXJuIFtrZXkucmVwbGFjZSgnXycsICcuJyksIHZhbHVlXVxuICAgICAgICB9KVxuICAgICAgICAucmVkdWNlKChhcmdzLCBba2V5LCB2YWx1ZV0pID0+IChPYmplY3QuYXNzaWduKGFyZ3MsIHtba2V5XTogdmFsdWV9KSksIHt9KVxuXG4gICAgICBhd2FpdCBNb2RlbC51cGRhdGUoeyBfaWQ6IGFyZ3MuaWQgfSwgeyAkc2V0OiB1cGRhdGVEYXRhIH0pXG4gICAgICByZXR1cm4gYXdhaXQgTW9kZWwuZmluZEJ5SWQoYXJncy5pZClcbiAgICB9XG4gIH1cbn1cblxuY29uc3QgYnVpbGREZWxldGUgPSAoTW9kZWwsIHR5cGUsIGRlZmF1bHRBcmdzKSA9PiB7XG4gIGNvbnN0IHJldHVyblR5cGUgPSBuZXcgR3JhcGhRTE9iamVjdFR5cGUoe1xuICAgIG5hbWU6IGAke01vZGVsLm1vZGVsTmFtZX1kZWxldGVNdXRhdGlvblJldHVybmAsXG4gICAgZmllbGRzOiAoKSA9PiAoe1xuICAgICAgc3VjY2VzczogeyB0eXBlOiBHcmFwaFFMQm9vbGVhbiB9LFxuICAgICAgbXNnOiB7IHR5cGU6IEdyYXBoUUxTdHJpbmcgfVxuICAgIH0pXG4gIH0pXG4gIHJldHVybiB7XG4gICAgdHlwZTogcmV0dXJuVHlwZSxcbiAgICBhcmdzOiBmaWx0ZXJBcmdzKGRlZmF1bHRBcmdzLCB7IHBsdXJhbDogdHJ1ZSwgaWRSZXF1aXJlZDogdHJ1ZSwgb25seUlkOiB0cnVlIH0pLFxuICAgIHJlc29sdmU6IGFzeW5jIChfLCBhcmdzKSA9PiB7XG4gICAgICBsZXQgcmVzID0geyBzdWNjZXNzOiB0cnVlLCBtc2c6IG51bGwgfVxuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgTW9kZWwuZmluZEJ5SWQoYXJncy5pZCkucmVtb3ZlKClcbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICByZXMgPSB7IHN1Y2Nlc3M6IGZhbHNlLCBtc2c6IGVyci5tZXNzYWdlIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiByZXNcbiAgICB9XG4gIH1cbn1cbiJdfQ== -------------------------------------------------------------------------------- /lib/schema/buildQuery.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _assign = require('babel-runtime/core-js/object/assign'); 8 | 9 | var _assign2 = _interopRequireDefault(_assign); 10 | 11 | var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); 12 | 13 | var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); 14 | 15 | var _entries = require('babel-runtime/core-js/object/entries'); 16 | 17 | var _entries2 = _interopRequireDefault(_entries); 18 | 19 | var _keys = require('babel-runtime/core-js/object/keys'); 20 | 21 | var _keys2 = _interopRequireDefault(_keys); 22 | 23 | var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); 24 | 25 | var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); 26 | 27 | exports.default = function (model, type) { 28 | const modelName = model.modelName; 29 | const defaultArgs = (0, _buildArgs2.default)(type); 30 | return { 31 | [modelName.toLowerCase()]: { 32 | type: new _graphql.GraphQLList(type), 33 | args: defaultArgs, 34 | resolve: (() => { 35 | var _ref = (0, _asyncToGenerator3.default)(function* (_, args) { 36 | if (hasRepeateArgs((0, _keys2.default)(args))) { 37 | throw new Error('Can not use singular and plural of an argument in same time'); 38 | } 39 | let onlyPlural; 40 | const query = (0, _entries2.default)(args).map(function (_ref2) { 41 | var _ref3 = (0, _slicedToArray3.default)(_ref2, 2); 42 | 43 | let arg = _ref3[0]; 44 | let value = _ref3[1]; 45 | 46 | onlyPlural = defaultArgs[arg]['onlyPlural']; 47 | arg = arg.replace('_', '.'); 48 | arg = arg === 'id' && '_id' || arg === 'ids' && '_ids' || arg; 49 | if (_pluralize2.default.singular(arg) === arg || onlyPlural) { 50 | return { [arg]: value }; 51 | } else { 52 | return { [_pluralize2.default.singular(arg)]: { $in: value } }; 53 | } 54 | }).reduce(function (query, item) { 55 | return (0, _assign2.default)(query, item); 56 | }, {}); 57 | 58 | return yield model.find(query); 59 | }); 60 | 61 | return function resolve(_x, _x2) { 62 | return _ref.apply(this, arguments); 63 | }; 64 | })() 65 | } 66 | }; 67 | }; 68 | 69 | var _graphql = require('graphql'); 70 | 71 | var _pluralize = require('pluralize'); 72 | 73 | var _pluralize2 = _interopRequireDefault(_pluralize); 74 | 75 | var _buildArgs = require('./buildArgs'); 76 | 77 | var _buildArgs2 = _interopRequireDefault(_buildArgs); 78 | 79 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 80 | 81 | // Valid if both singular and plural of a argument were provided 82 | let elem; 83 | 84 | /** 85 | * Build query for a sigle model 86 | * @params 87 | * - model a mongoose model 88 | * - type a corresponding converted graphql type 89 | * @return 90 | * - {Object} e.g. { user: {type: userType, args: {id, ids, userName, userNames...}, resolve} } 91 | */ 92 | 93 | const hasRepeateArgs = args => { 94 | if (args.length === 0) return false; 95 | elem = args.pop(); 96 | if (args.find(arg => arg === _pluralize2.default.plural(elem) || arg === _pluralize2.default.singular(elem))) return true; 97 | return hasRepeateArgs(args); 98 | }; 99 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zY2hlbWEvYnVpbGRRdWVyeS5qcyJdLCJuYW1lcyI6WyJtb2RlbCIsInR5cGUiLCJtb2RlbE5hbWUiLCJkZWZhdWx0QXJncyIsInRvTG93ZXJDYXNlIiwiYXJncyIsInJlc29sdmUiLCJfIiwiaGFzUmVwZWF0ZUFyZ3MiLCJFcnJvciIsIm9ubHlQbHVyYWwiLCJxdWVyeSIsIm1hcCIsImFyZyIsInZhbHVlIiwicmVwbGFjZSIsInNpbmd1bGFyIiwiJGluIiwicmVkdWNlIiwiaXRlbSIsImZpbmQiLCJlbGVtIiwibGVuZ3RoIiwicG9wIiwicGx1cmFsIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztrQkFZZSxVQUFVQSxLQUFWLEVBQWlCQyxJQUFqQixFQUF1QjtBQUNwQyxRQUFNQyxZQUFZRixNQUFNRSxTQUF4QjtBQUNBLFFBQU1DLGNBQWMseUJBQVVGLElBQVYsQ0FBcEI7QUFDQSxTQUFPO0FBQ0wsS0FBQ0MsVUFBVUUsV0FBVixFQUFELEdBQTJCO0FBQ3pCSCxZQUFNLHlCQUFnQkEsSUFBaEIsQ0FEbUI7QUFFekJJLFlBQU1GLFdBRm1CO0FBR3pCRztBQUFBLG1EQUFTLFdBQU9DLENBQVAsRUFBVUYsSUFBVixFQUFtQjtBQUMxQixjQUFJRyxlQUFlLG9CQUFZSCxJQUFaLENBQWYsQ0FBSixFQUF1QztBQUNyQyxrQkFBTSxJQUFJSSxLQUFKLENBQVUsNkRBQVYsQ0FBTjtBQUNEO0FBQ0QsY0FBSUMsVUFBSjtBQUNBLGdCQUFNQyxRQUFRLHVCQUFlTixJQUFmLEVBQXFCTyxHQUFyQixDQUF5QixpQkFBa0I7QUFBQTs7QUFBQSxnQkFBaEJDLEdBQWdCO0FBQUEsZ0JBQVhDLEtBQVc7O0FBQ3ZESix5QkFBYVAsWUFBWVUsR0FBWixFQUFpQixZQUFqQixDQUFiO0FBQ0FBLGtCQUFNQSxJQUFJRSxPQUFKLENBQVksR0FBWixFQUFpQixHQUFqQixDQUFOO0FBQ0FGLGtCQUFPQSxRQUFRLElBQVIsSUFBZ0IsS0FBakIsSUFBNEJBLFFBQVEsS0FBUixJQUFpQixNQUE3QyxJQUF3REEsR0FBOUQ7QUFDQSxnQkFBSSxvQkFBVUcsUUFBVixDQUFtQkgsR0FBbkIsTUFBNEJBLEdBQTVCLElBQW1DSCxVQUF2QyxFQUFtRDtBQUNqRCxxQkFBTyxFQUFFLENBQUNHLEdBQUQsR0FBT0MsS0FBVCxFQUFQO0FBQ0QsYUFGRCxNQUVPO0FBQ0wscUJBQU8sRUFBRSxDQUFDLG9CQUFVRSxRQUFWLENBQW1CSCxHQUFuQixDQUFELEdBQTJCLEVBQUVJLEtBQUtILEtBQVAsRUFBN0IsRUFBUDtBQUNEO0FBQ0YsV0FUYSxFQVNYSSxNQVRXLENBU0osVUFBQ1AsS0FBRCxFQUFRUSxJQUFSO0FBQUEsbUJBQWtCLHNCQUFjUixLQUFkLEVBQXFCUSxJQUFyQixDQUFsQjtBQUFBLFdBVEksRUFTMkMsRUFUM0MsQ0FBZDs7QUFXQSxpQkFBTyxNQUFNbkIsTUFBTW9CLElBQU4sQ0FBV1QsS0FBWCxDQUFiO0FBQ0QsU0FqQkQ7O0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFIeUI7QUFEdEIsR0FBUDtBQXdCRCxDOztBQXZDRDs7QUFDQTs7OztBQUNBOzs7Ozs7QUF1Q0E7QUFDQSxJQUFJVSxJQUFKOztBQXRDQTs7Ozs7Ozs7O0FBdUNBLE1BQU1iLGlCQUFrQkgsSUFBRCxJQUFVO0FBQy9CLE1BQUlBLEtBQUtpQixNQUFMLEtBQWdCLENBQXBCLEVBQXVCLE9BQU8sS0FBUDtBQUN2QkQsU0FBT2hCLEtBQUtrQixHQUFMLEVBQVA7QUFDQSxNQUFJbEIsS0FBS2UsSUFBTCxDQUFVUCxPQUFPQSxRQUFRLG9CQUFVVyxNQUFWLENBQWlCSCxJQUFqQixDQUFSLElBQWtDUixRQUFRLG9CQUFVRyxRQUFWLENBQW1CSyxJQUFuQixDQUEzRCxDQUFKLEVBQTBGLE9BQU8sSUFBUDtBQUMxRixTQUFPYixlQUFlSCxJQUFmLENBQVA7QUFDRCxDQUxEIiwiZmlsZSI6ImJ1aWxkUXVlcnkuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBHcmFwaFFMTGlzdCB9IGZyb20gJ2dyYXBocWwnXG5pbXBvcnQgcGx1cmFsaXplIGZyb20gJ3BsdXJhbGl6ZSdcbmltcG9ydCBidWlsZEFyZ3MgZnJvbSAnLi9idWlsZEFyZ3MnXG5cbi8qKlxuICogQnVpbGQgcXVlcnkgZm9yIGEgc2lnbGUgbW9kZWxcbiAqIEBwYXJhbXNcbiAqICAtIG1vZGVsIGEgbW9uZ29vc2UgbW9kZWxcbiAqICAtIHR5cGUgYSBjb3JyZXNwb25kaW5nIGNvbnZlcnRlZCBncmFwaHFsIHR5cGVcbiAqIEByZXR1cm5cbiAqICAtIHtPYmplY3R9IGUuZy4geyB1c2VyOiB7dHlwZTogdXNlclR5cGUsIGFyZ3M6IHtpZCwgaWRzLCB1c2VyTmFtZSwgdXNlck5hbWVzLi4ufSwgcmVzb2x2ZX0gfVxuICovXG5leHBvcnQgZGVmYXVsdCBmdW5jdGlvbiAobW9kZWwsIHR5cGUpIHtcbiAgY29uc3QgbW9kZWxOYW1lID0gbW9kZWwubW9kZWxOYW1lXG4gIGNvbnN0IGRlZmF1bHRBcmdzID0gYnVpbGRBcmdzKHR5cGUpXG4gIHJldHVybiB7XG4gICAgW21vZGVsTmFtZS50b0xvd2VyQ2FzZSgpXToge1xuICAgICAgdHlwZTogbmV3IEdyYXBoUUxMaXN0KHR5cGUpLFxuICAgICAgYXJnczogZGVmYXVsdEFyZ3MsXG4gICAgICByZXNvbHZlOiBhc3luYyAoXywgYXJncykgPT4ge1xuICAgICAgICBpZiAoaGFzUmVwZWF0ZUFyZ3MoT2JqZWN0LmtleXMoYXJncykpKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdDYW4gbm90IHVzZSBzaW5ndWxhciBhbmQgcGx1cmFsIG9mIGFuIGFyZ3VtZW50IGluIHNhbWUgdGltZScpXG4gICAgICAgIH1cbiAgICAgICAgbGV0IG9ubHlQbHVyYWxcbiAgICAgICAgY29uc3QgcXVlcnkgPSBPYmplY3QuZW50cmllcyhhcmdzKS5tYXAoKFthcmcsIHZhbHVlXSkgPT4ge1xuICAgICAgICAgIG9ubHlQbHVyYWwgPSBkZWZhdWx0QXJnc1thcmddWydvbmx5UGx1cmFsJ11cbiAgICAgICAgICBhcmcgPSBhcmcucmVwbGFjZSgnXycsICcuJylcbiAgICAgICAgICBhcmcgPSAoYXJnID09PSAnaWQnICYmICdfaWQnKSB8fCAoYXJnID09PSAnaWRzJyAmJiAnX2lkcycpIHx8IGFyZ1xuICAgICAgICAgIGlmIChwbHVyYWxpemUuc2luZ3VsYXIoYXJnKSA9PT0gYXJnIHx8IG9ubHlQbHVyYWwpIHtcbiAgICAgICAgICAgIHJldHVybiB7IFthcmddOiB2YWx1ZSB9XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHJldHVybiB7IFtwbHVyYWxpemUuc2luZ3VsYXIoYXJnKV06IHsgJGluOiB2YWx1ZSB9IH1cbiAgICAgICAgICB9XG4gICAgICAgIH0pLnJlZHVjZSgocXVlcnksIGl0ZW0pID0+IChPYmplY3QuYXNzaWduKHF1ZXJ5LCBpdGVtKSksIHt9KVxuXG4gICAgICAgIHJldHVybiBhd2FpdCBtb2RlbC5maW5kKHF1ZXJ5KVxuICAgICAgfVxuICAgIH1cbiAgfVxufVxuXG4vLyBWYWxpZCBpZiBib3RoIHNpbmd1bGFyIGFuZCBwbHVyYWwgb2YgYSBhcmd1bWVudCB3ZXJlIHByb3ZpZGVkXG5sZXQgZWxlbVxuY29uc3QgaGFzUmVwZWF0ZUFyZ3MgPSAoYXJncykgPT4ge1xuICBpZiAoYXJncy5sZW5ndGggPT09IDApIHJldHVybiBmYWxzZVxuICBlbGVtID0gYXJncy5wb3AoKVxuICBpZiAoYXJncy5maW5kKGFyZyA9PiBhcmcgPT09IHBsdXJhbGl6ZS5wbHVyYWwoZWxlbSkgfHwgYXJnID09PSBwbHVyYWxpemUuc2luZ3VsYXIoZWxlbSkpKSByZXR1cm4gdHJ1ZVxuICByZXR1cm4gaGFzUmVwZWF0ZUFyZ3MoYXJncylcbn1cbiJdfQ== -------------------------------------------------------------------------------- /lib/schema/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _assign = require('babel-runtime/core-js/object/assign'); 8 | 9 | var _assign2 = _interopRequireDefault(_assign); 10 | 11 | exports.buildSchema = buildSchema; 12 | 13 | var _buildQuery = require('./buildQuery'); 14 | 15 | var _buildQuery2 = _interopRequireDefault(_buildQuery); 16 | 17 | var _buildMutation = require('./buildMutation'); 18 | 19 | var _buildMutation2 = _interopRequireDefault(_buildMutation); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | /** 24 | * Build graphql CRUD schema based on models and types 25 | * @params 26 | * - models {Array} mongoose models 27 | * - typeMap {Object} map of model and corresponding graphql type 28 | * @return 29 | * - grapqhl schema which has query and mutation 30 | */ 31 | function buildSchema(models, typeMap) { 32 | let type; 33 | 34 | var _models$map$reduce = models.map(model => { 35 | type = typeMap[model.modelName]; 36 | return { 37 | query: (0, _buildQuery2.default)(model, type), 38 | mutation: (0, _buildMutation2.default)(model, type) 39 | }; 40 | }).reduce((fields, modelField) => { 41 | fields.query = (0, _assign2.default)({}, fields.query, modelField.query); 42 | fields.mutation = (0, _assign2.default)({}, fields.mutation, modelField.mutation); 43 | return fields; 44 | }, { query: {}, mutation: {} }); 45 | 46 | const query = _models$map$reduce.query; 47 | const mutation = _models$map$reduce.mutation; 48 | 49 | 50 | return { 51 | query: query, 52 | mutation: mutation 53 | }; 54 | } 55 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zY2hlbWEvaW5kZXguanMiXSwibmFtZXMiOlsiYnVpbGRTY2hlbWEiLCJtb2RlbHMiLCJ0eXBlTWFwIiwidHlwZSIsIm1hcCIsIm1vZGVsIiwibW9kZWxOYW1lIiwicXVlcnkiLCJtdXRhdGlvbiIsInJlZHVjZSIsImZpZWxkcyIsIm1vZGVsRmllbGQiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7UUFXZ0JBLFcsR0FBQUEsVzs7QUFYaEI7Ozs7QUFDQTs7Ozs7O0FBRUE7Ozs7Ozs7O0FBUU8sU0FBU0EsV0FBVCxDQUFzQkMsTUFBdEIsRUFBOEJDLE9BQTlCLEVBQXVDO0FBQzVDLE1BQUlDLElBQUo7O0FBRDRDLDJCQUVsQkYsT0FBT0csR0FBUCxDQUFXQyxTQUFTO0FBQzVDRixXQUFPRCxRQUFRRyxNQUFNQyxTQUFkLENBQVA7QUFDQSxXQUFPO0FBQ0xDLGFBQU8sMEJBQVdGLEtBQVgsRUFBa0JGLElBQWxCLENBREY7QUFFTEssZ0JBQVUsNkJBQWNILEtBQWQsRUFBcUJGLElBQXJCO0FBRkwsS0FBUDtBQUlELEdBTnlCLEVBTXZCTSxNQU51QixDQU1oQixDQUFDQyxNQUFELEVBQVNDLFVBQVQsS0FBd0I7QUFDaENELFdBQU9ILEtBQVAsR0FBZSxzQkFBYyxFQUFkLEVBQWtCRyxPQUFPSCxLQUF6QixFQUFnQ0ksV0FBV0osS0FBM0MsQ0FBZjtBQUNBRyxXQUFPRixRQUFQLEdBQWtCLHNCQUFjLEVBQWQsRUFBa0JFLE9BQU9GLFFBQXpCLEVBQW1DRyxXQUFXSCxRQUE5QyxDQUFsQjtBQUNBLFdBQU9FLE1BQVA7QUFDRCxHQVZ5QixFQVV2QixFQUFFSCxPQUFPLEVBQVQsRUFBYUMsVUFBVSxFQUF2QixFQVZ1QixDQUZrQjs7QUFBQSxRQUVyQ0QsS0FGcUMsc0JBRXJDQSxLQUZxQztBQUFBLFFBRTlCQyxRQUY4QixzQkFFOUJBLFFBRjhCOzs7QUFjNUMsU0FBTztBQUNMRCxnQkFESztBQUVMQztBQUZLLEdBQVA7QUFJRCIsImZpbGUiOiJpbmRleC5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBidWlsZFF1ZXJ5IGZyb20gJy4vYnVpbGRRdWVyeSdcbmltcG9ydCBidWlsZE11dGF0aW9uIGZyb20gJy4vYnVpbGRNdXRhdGlvbidcblxuLyoqXG4gKiBCdWlsZCBncmFwaHFsIENSVUQgc2NoZW1hIGJhc2VkIG9uIG1vZGVscyBhbmQgdHlwZXNcbiAqIEBwYXJhbXNcbiAqICAtIG1vZGVscyB7QXJyYXl9IG1vbmdvb3NlIG1vZGVsc1xuICogIC0gdHlwZU1hcCB7T2JqZWN0fSBtYXAgb2YgbW9kZWwgYW5kIGNvcnJlc3BvbmRpbmcgZ3JhcGhxbCB0eXBlXG4gKiBAcmV0dXJuXG4gKiAgLSBncmFwcWhsIHNjaGVtYSB3aGljaCBoYXMgcXVlcnkgYW5kIG11dGF0aW9uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBidWlsZFNjaGVtYSAobW9kZWxzLCB0eXBlTWFwKSB7XG4gIGxldCB0eXBlXG4gIGNvbnN0IHtxdWVyeSwgbXV0YXRpb259ID0gbW9kZWxzLm1hcChtb2RlbCA9PiB7XG4gICAgdHlwZSA9IHR5cGVNYXBbbW9kZWwubW9kZWxOYW1lXVxuICAgIHJldHVybiB7XG4gICAgICBxdWVyeTogYnVpbGRRdWVyeShtb2RlbCwgdHlwZSksXG4gICAgICBtdXRhdGlvbjogYnVpbGRNdXRhdGlvbihtb2RlbCwgdHlwZSlcbiAgICB9XG4gIH0pLnJlZHVjZSgoZmllbGRzLCBtb2RlbEZpZWxkKSA9PiB7XG4gICAgZmllbGRzLnF1ZXJ5ID0gT2JqZWN0LmFzc2lnbih7fSwgZmllbGRzLnF1ZXJ5LCBtb2RlbEZpZWxkLnF1ZXJ5KVxuICAgIGZpZWxkcy5tdXRhdGlvbiA9IE9iamVjdC5hc3NpZ24oe30sIGZpZWxkcy5tdXRhdGlvbiwgbW9kZWxGaWVsZC5tdXRhdGlvbilcbiAgICByZXR1cm4gZmllbGRzXG4gIH0sIHsgcXVlcnk6IHt9LCBtdXRhdGlvbjoge30gfSlcblxuICByZXR1cm4ge1xuICAgIHF1ZXJ5LFxuICAgIG11dGF0aW9uXG4gIH1cbn1cbiJdfQ== -------------------------------------------------------------------------------- /lib/type/customType.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.GraphQLMixed = exports.GraphQLDate = exports.GraphQLBuffer = undefined; 7 | 8 | var _graphql = require('graphql'); 9 | 10 | /** 11 | * Buffer type 12 | */ 13 | const coerceBuffer = value => { 14 | if (value instanceof Buffer) return value; 15 | throw new TypeError(`Type error: ${ value } is not instance of Buffer`); 16 | }; 17 | 18 | const GraphQLBuffer = exports.GraphQLBuffer = new _graphql.GraphQLScalarType({ 19 | name: 'Buffer', 20 | serialize: coerceBuffer, // serialize to query result 21 | parseValue: coerceBuffer, 22 | parseLiteral: ast => { 23 | // Read from args 24 | return typeof ast.value === 'string' && new Buffer(ast.value) || null; 25 | } 26 | }); 27 | 28 | /** 29 | * Date type 30 | */ 31 | const coerceDate = value => { 32 | if (value instanceof Date) return value; 33 | throw new TypeError(`Type error: ${ value } is not instance of Date`); 34 | }; 35 | 36 | const GraphQLDate = exports.GraphQLDate = new _graphql.GraphQLScalarType({ 37 | name: 'Date', 38 | serialize: coerceDate, 39 | parseValue: coerceDate, 40 | parseLiteral: ast => { 41 | const d = new Date(ast.value); 42 | return !isNaN(d.getTime()) && d || null; 43 | } 44 | }); 45 | 46 | /** 47 | * Mixed type 48 | */ 49 | const GraphQLMixed = exports.GraphQLMixed = new _graphql.GraphQLScalarType({ 50 | name: 'Mixed', 51 | serialize: value => value, 52 | parseValue: value => value, 53 | parseLiteral: ast => ast.value 54 | }); 55 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90eXBlL2N1c3RvbVR5cGUuanMiXSwibmFtZXMiOlsiY29lcmNlQnVmZmVyIiwidmFsdWUiLCJCdWZmZXIiLCJUeXBlRXJyb3IiLCJHcmFwaFFMQnVmZmVyIiwibmFtZSIsInNlcmlhbGl6ZSIsInBhcnNlVmFsdWUiLCJwYXJzZUxpdGVyYWwiLCJhc3QiLCJjb2VyY2VEYXRlIiwiRGF0ZSIsIkdyYXBoUUxEYXRlIiwiZCIsImlzTmFOIiwiZ2V0VGltZSIsIkdyYXBoUUxNaXhlZCJdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQUFBOztBQUVBOzs7QUFHQSxNQUFNQSxlQUFnQkMsS0FBRCxJQUFXO0FBQzlCLE1BQUlBLGlCQUFpQkMsTUFBckIsRUFBNkIsT0FBT0QsS0FBUDtBQUM3QixRQUFNLElBQUlFLFNBQUosQ0FBZSxnQkFBY0YsS0FBTSw2QkFBbkMsQ0FBTjtBQUNELENBSEQ7O0FBS08sTUFBTUcsd0NBQWdCLCtCQUFzQjtBQUNqREMsUUFBTSxRQUQyQztBQUVqREMsYUFBV04sWUFGc0MsRUFFeEI7QUFDekJPLGNBQVlQLFlBSHFDO0FBSWpEUSxnQkFBY0MsT0FBTztBQUNuQjtBQUNBLFdBQU8sT0FBT0EsSUFBSVIsS0FBWCxLQUFxQixRQUFyQixJQUFpQyxJQUFJQyxNQUFKLENBQVdPLElBQUlSLEtBQWYsQ0FBakMsSUFBMEQsSUFBakU7QUFDRDtBQVBnRCxDQUF0QixDQUF0Qjs7QUFVUDs7O0FBR0EsTUFBTVMsYUFBY1QsS0FBRCxJQUFXO0FBQzVCLE1BQUlBLGlCQUFpQlUsSUFBckIsRUFBMkIsT0FBT1YsS0FBUDtBQUMzQixRQUFNLElBQUlFLFNBQUosQ0FBZSxnQkFBY0YsS0FBTSwyQkFBbkMsQ0FBTjtBQUNELENBSEQ7O0FBS08sTUFBTVcsb0NBQWMsK0JBQXNCO0FBQy9DUCxRQUFNLE1BRHlDO0FBRS9DQyxhQUFXSSxVQUZvQztBQUcvQ0gsY0FBWUcsVUFIbUM7QUFJL0NGLGdCQUFjQyxPQUFPO0FBQ25CLFVBQU1JLElBQUksSUFBSUYsSUFBSixDQUFTRixJQUFJUixLQUFiLENBQVY7QUFDQSxXQUFPLENBQUNhLE1BQU1ELEVBQUVFLE9BQUYsRUFBTixDQUFELElBQXVCRixDQUF2QixJQUE0QixJQUFuQztBQUNEO0FBUDhDLENBQXRCLENBQXBCOztBQVVQOzs7QUFHTyxNQUFNRyxzQ0FBZSwrQkFBc0I7QUFDaERYLFFBQU0sT0FEMEM7QUFFaERDLGFBQVdMLFNBQVNBLEtBRjRCO0FBR2hETSxjQUFZTixTQUFTQSxLQUgyQjtBQUloRE8sZ0JBQWNDLE9BQU9BLElBQUlSO0FBSnVCLENBQXRCLENBQXJCIiwiZmlsZSI6ImN1c3RvbVR5cGUuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBHcmFwaFFMU2NhbGFyVHlwZSB9IGZyb20gJ2dyYXBocWwnXG5cbi8qKlxuICogQnVmZmVyIHR5cGVcbiAqL1xuY29uc3QgY29lcmNlQnVmZmVyID0gKHZhbHVlKSA9PiB7XG4gIGlmICh2YWx1ZSBpbnN0YW5jZW9mIEJ1ZmZlcikgcmV0dXJuIHZhbHVlXG4gIHRocm93IG5ldyBUeXBlRXJyb3IoYFR5cGUgZXJyb3I6ICR7dmFsdWV9IGlzIG5vdCBpbnN0YW5jZSBvZiBCdWZmZXJgKVxufVxuXG5leHBvcnQgY29uc3QgR3JhcGhRTEJ1ZmZlciA9IG5ldyBHcmFwaFFMU2NhbGFyVHlwZSh7XG4gIG5hbWU6ICdCdWZmZXInLFxuICBzZXJpYWxpemU6IGNvZXJjZUJ1ZmZlciwgLy8gc2VyaWFsaXplIHRvIHF1ZXJ5IHJlc3VsdFxuICBwYXJzZVZhbHVlOiBjb2VyY2VCdWZmZXIsXG4gIHBhcnNlTGl0ZXJhbDogYXN0ID0+IHtcbiAgICAvLyBSZWFkIGZyb20gYXJnc1xuICAgIHJldHVybiB0eXBlb2YgYXN0LnZhbHVlID09PSAnc3RyaW5nJyAmJiBuZXcgQnVmZmVyKGFzdC52YWx1ZSkgfHwgbnVsbFxuICB9XG59KVxuXG4vKipcbiAqIERhdGUgdHlwZVxuICovXG5jb25zdCBjb2VyY2VEYXRlID0gKHZhbHVlKSA9PiB7XG4gIGlmICh2YWx1ZSBpbnN0YW5jZW9mIERhdGUpIHJldHVybiB2YWx1ZVxuICB0aHJvdyBuZXcgVHlwZUVycm9yKGBUeXBlIGVycm9yOiAke3ZhbHVlfSBpcyBub3QgaW5zdGFuY2Ugb2YgRGF0ZWApXG59XG5cbmV4cG9ydCBjb25zdCBHcmFwaFFMRGF0ZSA9IG5ldyBHcmFwaFFMU2NhbGFyVHlwZSh7XG4gIG5hbWU6ICdEYXRlJyxcbiAgc2VyaWFsaXplOiBjb2VyY2VEYXRlLFxuICBwYXJzZVZhbHVlOiBjb2VyY2VEYXRlLFxuICBwYXJzZUxpdGVyYWw6IGFzdCA9PiB7XG4gICAgY29uc3QgZCA9IG5ldyBEYXRlKGFzdC52YWx1ZSlcbiAgICByZXR1cm4gIWlzTmFOKGQuZ2V0VGltZSgpKSAmJiBkIHx8IG51bGxcbiAgfVxufSlcblxuLyoqXG4gKiBNaXhlZCB0eXBlXG4gKi9cbmV4cG9ydCBjb25zdCBHcmFwaFFMTWl4ZWQgPSBuZXcgR3JhcGhRTFNjYWxhclR5cGUoe1xuICBuYW1lOiAnTWl4ZWQnLFxuICBzZXJpYWxpemU6IHZhbHVlID0+IHZhbHVlLFxuICBwYXJzZVZhbHVlOiB2YWx1ZSA9PiB2YWx1ZSxcbiAgcGFyc2VMaXRlcmFsOiBhc3QgPT4gYXN0LnZhbHVlXG59KVxuIl19 -------------------------------------------------------------------------------- /lib/type/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _keys = require('babel-runtime/core-js/object/keys'); 8 | 9 | var _keys2 = _interopRequireDefault(_keys); 10 | 11 | var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); 12 | 13 | var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); 14 | 15 | var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); 16 | 17 | var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); 18 | 19 | var _entries = require('babel-runtime/core-js/object/entries'); 20 | 21 | var _entries2 = _interopRequireDefault(_entries); 22 | 23 | var _assign = require('babel-runtime/core-js/object/assign'); 24 | 25 | var _assign2 = _interopRequireDefault(_assign); 26 | 27 | exports.modelsToTypes = modelsToTypes; 28 | 29 | var _graphql = require('graphql'); 30 | 31 | var _customType = require('./customType'); 32 | 33 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 34 | 35 | let _typeMap = {}; 36 | /** 37 | * Convert bundch of mongoose model to graphql types 38 | * build this as singleton so that it won't create graphQLType twice 39 | * 40 | * @params 41 | * - models {Array} list of mongoose models 42 | * @return 43 | * - typeMap {Object} key: modelName, value: type 44 | */ 45 | function modelsToTypes(models) { 46 | let typeMap = models.filter(model => { 47 | if (_typeMap[model.modelName]) return false; 48 | return true; 49 | }).reduce((map, model) => { 50 | return (0, _assign2.default)(map, { [model.modelName]: toType(model) }); 51 | }, {}); 52 | _typeMap = (0, _assign2.default)(_typeMap, typeMap); 53 | 54 | // Deal with ref after all types are defined 55 | (0, _entries2.default)(typeMap).forEach(_ref => { 56 | var _ref2 = (0, _slicedToArray3.default)(_ref, 2); 57 | 58 | let modelName = _ref2[0]; 59 | let type = _ref2[1]; 60 | 61 | const originFileds = type._typeConfig.fields(); 62 | const newTypeFileds = (0, _entries2.default)(originFileds).map(_ref3 => { 63 | var _ref4 = (0, _slicedToArray3.default)(_ref3, 2); 64 | 65 | let path = _ref4[0]; 66 | let pathValue = _ref4[1]; 67 | 68 | let newPathValue = (0, _assign2.default)({}, pathValue); 69 | if (newPathValue.ref) { 70 | const ref = newPathValue.ref; 71 | if (!_typeMap[ref]) throw TypeError(`${ref} is not a model`); 72 | const model = models.find(m => m.modelName === ref); 73 | const refModelType = _typeMap[ref]; 74 | if (newPathValue.type instanceof _graphql.GraphQLList) { 75 | newPathValue = (0, _assign2.default)({}, newPathValue, { 76 | type: new _graphql.GraphQLList(refModelType), 77 | resolve: (() => { 78 | var _ref5 = (0, _asyncToGenerator3.default)(function* (instance) { 79 | // TODO: args filter 80 | return yield model.find({ _id: { $in: instance[path] } }); 81 | }); 82 | 83 | return function resolve(_x) { 84 | return _ref5.apply(this, arguments); 85 | }; 86 | })() 87 | }); 88 | } else { 89 | newPathValue = (0, _assign2.default)({}, newPathValue, { 90 | type: refModelType, 91 | resolve: (() => { 92 | var _ref6 = (0, _asyncToGenerator3.default)(function* (instance) { 93 | return yield model.findById(instance[path]); 94 | }); 95 | 96 | return function resolve(_x2) { 97 | return _ref6.apply(this, arguments); 98 | }; 99 | })() 100 | }); 101 | } 102 | } 103 | return { [path]: newPathValue }; 104 | }).reduce((typeField, path) => (0, _assign2.default)(typeField, path), {}); 105 | 106 | typeMap[modelName]._typeConfig.fields = () => newTypeFileds; 107 | }); 108 | 109 | _typeMap = (0, _assign2.default)(_typeMap, typeMap); 110 | return _typeMap; 111 | } 112 | 113 | /* Convert a mongoose model to corresponding type */ 114 | const toType = model => { 115 | const exceptPath = ['_id', '__v']; 116 | const inheritOpts = ['ref', 'context']; 117 | const paths = model.schema.paths; 118 | let _fields = (0, _keys2.default)(paths).filter(path => exceptPath.indexOf(path) === -1).map(path => { 119 | const attr = paths[path]; 120 | let field = { type: pathToType(attr) }; 121 | // Find out special opt on mongoose model's path, use subPath's opt if path is an Array 122 | inheritOpts.forEach(opt => { 123 | if (attr.options[opt] || attr.instance === 'Array' && attr.caster.options && attr.caster.options[opt]) { 124 | field[opt] = attr.options[opt] || attr.caster.options[opt]; 125 | } 126 | }); 127 | // Mark required path 128 | const required = attr.options.required; 129 | if (Array.isArray(required) && required[0] || required) field.required = true; 130 | return { [path]: field }; 131 | }).reduce((fields, path) => { 132 | // make up object tpe, e.g { name: { first: {type: GraphQLString...}, last: {type: GraphQLString...} } } 133 | const pathKey = (0, _keys2.default)(path)[0]; 134 | const pathKeySplit = pathKey.split('.'); 135 | const pathKeyLength = pathKeySplit.length; 136 | if (pathKeyLength.length === 1) return (0, _assign2.default)(fields, path); 137 | pathKeySplit.reduce((fieldPostion, depth, index) => { 138 | if (index === pathKeyLength - 1) { 139 | fieldPostion[depth] = path[pathKey]; 140 | return; 141 | } 142 | fieldPostion[depth] = fieldPostion[depth] || {}; 143 | return fieldPostion[depth]; 144 | }, fields); 145 | return fields; 146 | }, { id: { type: _graphql.GraphQLID } }); 147 | 148 | // Deal with object attribute in mongoose model 149 | // e.g. {name: {first: String, last: Strinf}} -> {name: GraphQLType{fields: {first: GraphQLString, two: GraphQLString}}} 150 | _fields = (0, _entries2.default)(_fields).map(_ref7 => { 151 | var _ref8 = (0, _slicedToArray3.default)(_ref7, 2); 152 | 153 | let key = _ref8[0]; 154 | let attr = _ref8[1]; 155 | 156 | return { [key]: convertObject(attr, `${model.modelName}${key}`) }; 157 | }).reduce((fields, path) => (0, _assign2.default)(fields, path), {}); 158 | return new _graphql.GraphQLObjectType({ 159 | name: model.modelName, 160 | fields: () => _fields 161 | }); 162 | }; 163 | 164 | // Convert single path of mongoose to type 165 | const pathToType = path => { 166 | switch (path.instance) { 167 | case 'String': 168 | return _graphql.GraphQLString; 169 | case 'Number': 170 | // Float includes Int 171 | // @see https://github.com/graphql/graphql-js/blob/master/src/type/scalars.js#L69 172 | return _graphql.GraphQLFloat; 173 | case 'Date': 174 | return _customType.GraphQLDate; 175 | case 'Buffer': 176 | return _customType.GraphQLBuffer; 177 | case 'Boolean': 178 | return _graphql.GraphQLBoolean; 179 | case 'Mixed': 180 | return _customType.GraphQLMixed; 181 | case 'ObjectID': 182 | return _graphql.GraphQLID; 183 | case 'Array': 184 | const subType = pathToType(path.caster); 185 | return new _graphql.GraphQLList(subType); 186 | default: 187 | return _customType.GraphQLMixed; 188 | } 189 | }; 190 | 191 | // Convert model's object attribute 192 | const convertObject = (attr, parentName) => { 193 | if (attr.type && (attr.type instanceof _graphql.GraphQLScalarType || attr.type instanceof _graphql.GraphQLList)) return attr; 194 | const _fields2 = (0, _entries2.default)(attr).map(_ref9 => { 195 | var _ref10 = (0, _slicedToArray3.default)(_ref9, 2); 196 | 197 | let key = _ref10[0]; 198 | let vakue = _ref10[1]; 199 | 200 | return { [key]: convertObject(attr[key], `${parentName}${key}`) }; 201 | }).reduce((fields, path) => { 202 | // console.log(path) 203 | return (0, _assign2.default)(fields, path); 204 | }, {}); 205 | // console.log(fields) 206 | return { 207 | type: new _graphql.GraphQLObjectType({ 208 | name: `${parentName}`, 209 | fields: () => _fields2 210 | }) 211 | }; 212 | }; 213 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90eXBlL2luZGV4LmpzIl0sIm5hbWVzIjpbIm1vZGVsc1RvVHlwZXMiLCJfdHlwZU1hcCIsIm1vZGVscyIsInR5cGVNYXAiLCJmaWx0ZXIiLCJtb2RlbCIsIm1vZGVsTmFtZSIsInJlZHVjZSIsIm1hcCIsInRvVHlwZSIsImZvckVhY2giLCJ0eXBlIiwib3JpZ2luRmlsZWRzIiwiX3R5cGVDb25maWciLCJmaWVsZHMiLCJuZXdUeXBlRmlsZWRzIiwicGF0aCIsInBhdGhWYWx1ZSIsIm5ld1BhdGhWYWx1ZSIsInJlZiIsIlR5cGVFcnJvciIsImZpbmQiLCJtIiwicmVmTW9kZWxUeXBlIiwicmVzb2x2ZSIsImluc3RhbmNlIiwiX2lkIiwiJGluIiwiZmluZEJ5SWQiLCJ0eXBlRmllbGQiLCJleGNlcHRQYXRoIiwiaW5oZXJpdE9wdHMiLCJwYXRocyIsInNjaGVtYSIsImluZGV4T2YiLCJhdHRyIiwiZmllbGQiLCJwYXRoVG9UeXBlIiwib3B0Iiwib3B0aW9ucyIsImNhc3RlciIsInJlcXVpcmVkIiwiQXJyYXkiLCJpc0FycmF5IiwicGF0aEtleSIsInBhdGhLZXlTcGxpdCIsInNwbGl0IiwicGF0aEtleUxlbmd0aCIsImxlbmd0aCIsImZpZWxkUG9zdGlvbiIsImRlcHRoIiwiaW5kZXgiLCJpZCIsImtleSIsImNvbnZlcnRPYmplY3QiLCJuYW1lIiwic3ViVHlwZSIsInBhcmVudE5hbWUiLCJ2YWt1ZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7UUF5QmdCQSxhLEdBQUFBLGE7O0FBekJoQjs7QUFTQTs7OztBQU1BLElBQUlDLFdBQVcsRUFBZjtBQUNBOzs7Ozs7Ozs7QUFTTyxTQUFTRCxhQUFULENBQXdCRSxNQUF4QixFQUFnQztBQUNyQyxNQUFJQyxVQUFVRCxPQUFPRSxNQUFQLENBQWNDLFNBQVM7QUFDbkMsUUFBSUosU0FBU0ksTUFBTUMsU0FBZixDQUFKLEVBQStCLE9BQU8sS0FBUDtBQUMvQixXQUFPLElB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TUFBaEIsQ0FBaEI7QUFDQSxhQUFPLHlCQUFnQmdCLE9BQWhCLENBQVA7QUFDRjtBQUNFO0FBckJKO0FBdUJELENBeEJEOztBQTBCQTtBQUNBLE1BQU1GLGdCQUFnQixDQUFDbkIsSUFBRCxFQUFPc0IsVUFBUCxLQUFzQjtBQUMxQyxNQUFJdEIsS0FBS3hCLElBQUwsS0FBY3dCLEtBQUt4QixJQUFMLDBDQUEwQ3dCLEtBQUt4QixJQUFMLGdDQUF4RCxDQUFKLEVBQStGLE9BQU93QixJQUFQO0FBQy9GLFFBQU1yQixXQUFTLHVCQUFlcUIsSUFBZixFQUFxQjNCLEdBQXJCLENBQXlCLFNBQWtCO0FBQUE7O0FBQUEsUUFBaEI2QyxHQUFnQjtBQUFBLFFBQVhLLEtBQVc7O0FBQ3hELFdBQU8sRUFBRSxDQUFDTCxHQUFELEdBQU9DLGNBQWNuQixLQUFLa0IsR0FBTCxDQUFkLEVBQTBCLElBQUVJLFVBQVcsS0FBRUosR0FBSSxHQUE3QyxDQUFULEVBQVA7QUFDRCxHQUZjLEVBRVo5QyxNQUZZLENBRUwsQ0FBQ08sTUFBRCxFQUFTRSxJQUFULEtBQWtCO0FBQzFCO0FBQ0EsV0FBTyxzQkFBY0YsTUFBZCxFQUFzQkUsSUFBdEIsQ0FBUDtBQUNELEdBTGMsRUFLWixFQUxZLENBQWY7QUFNQTtBQUNBLFNBQU87QUFDTEwsVUFBTSwrQkFBc0I7QUFDMUI0QyxZQUFPLElBQUVFLFVBQVcsR0FETTtBQUUxQjNDLGNBQVEsTUFBT0E7QUFGVyxLQUF0QjtBQURELEdBQVA7QUFNRCxDQWZEIiwiZmlsZSI6ImluZGV4LmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgR3JhcGhRTElELFxuICBHcmFwaFFMT2JqZWN0VHlwZSxcbiAgR3JhcGhRTFN0cmluZyxcbiAgR3JhcGhRTEZsb2F0LFxuICBHcmFwaFFMQm9vbGVhbixcbiAgR3JhcGhRTExpc3QsXG4gIEdyYXBoUUxTY2FsYXJUeXBlXG59IGZyb20gJ2dyYXBocWwnXG5pbXBvcnQge1xuICBHcmFwaFFMQnVmZmVyLFxuICBHcmFwaFFMRGF0ZSxcbiAgR3JhcGhRTE1peGVkXG59IGZyb20gJy4vY3VzdG9tVHlwZSdcblxubGV0IF90eXBlTWFwID0ge31cbi8qKlxuICogQ29udmVydCBidW5kY2ggb2YgbW9uZ29vc2UgbW9kZWwgdG8gZ3JhcGhxbCB0eXBlc1xuICogYnVpbGQgdGhpcyBhcyBzaW5nbGV0b24gc28gdGhhdCBpdCB3b24ndCBjcmVhdGUgZ3JhcGhRTFR5cGUgdHdpY2VcbiAqXG4gKiBAcGFyYW1zXG4gKiAgLSBtb2RlbHMge0FycmF5fSBsaXN0IG9mIG1vbmdvb3NlIG1vZGVsc1xuICogQHJldHVyblxuICogIC0gdHlwZU1hcCB7T2JqZWN0fSBrZXk6IG1vZGVsTmFtZSwgdmFsdWU6IHR5cGVcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG1vZGVsc1RvVHlwZXMgKG1vZGVscykge1xuICBsZXQgdHlwZU1hcCA9IG1vZGVscy5maWx0ZXIobW9kZWwgPT4ge1xuICAgIGlmIChfdHlwZU1hcFttb2RlbC5tb2RlbE5hbWVdKSByZXR1cm4gZmFsc2VcbiAgICByZXR1cm4gdHJ1ZVxuICB9KS5yZWR1Y2UoKG1hcCwgbW9kZWwpID0+IHtcbiAgICByZXR1cm4gT2JqZWN0LmFzc2lnbihtYXAsIHsgW21vZGVsLm1vZGVsTmFtZV06IHRvVHlwZShtb2RlbCkgfSlcbiAgfSwge30pXG4gIF90eXBlTWFwID0gT2JqZWN0LmFzc2lnbihfdHlwZU1hcCwgdHlwZU1hcClcblxuICAvLyBEZWFsIHdpdGggcmVmIGFmdGVyIGFsbCB0eXBlcyBhcmUgZGVmaW5lZFxuICBPYmplY3QuZW50cmllcyh0eXBlTWFwKS5mb3JFYWNoKChbbW9kZWxOYW1lLCB0eXBlXSkgPT4ge1xuICAgIGNvbnN0IG9yaWdpbkZpbGVkcyA9IHR5cGUuX3R5cGVDb25maWcuZmllbGRzKClcbiAgICBjb25zdCBuZXdUeXBlRmlsZWRzID0gT2JqZWN0LmVudHJpZXMob3JpZ2luRmlsZWRzKVxuICAgICAgLm1hcCgoW3BhdGgsIHBhdGhWYWx1ZV0pID0+IHtcbiAgICAgICAgbGV0IG5ld1BhdGhWYWx1ZSA9IE9iamVjdC5hc3NpZ24oe30sIHBhdGhWYWx1ZSlcbiAgICAgICAgaWYgKG5ld1BhdGhWYWx1ZS5yZWYpIHtcbiAgICAgICAgICBjb25zdCByZWYgPSBuZXdQYXRoVmFsdWUucmVmXG4gICAgICAgICAgaWYgKCFfdHlwZU1hcFtyZWZdKSB0aHJvdyBUeXBlRXJyb3IoYCR7cmVmfSBpcyBub3QgYSBtb2RlbGApXG4gICAgICAgICAgY29uc3QgbW9kZWwgPSBtb2RlbHMuZmluZChtID0+IG0ubW9kZWxOYW1lID09PSByZWYpXG4gICAgICAgICAgY29uc3QgcmVmTW9kZWxUeXBlID0gX3R5cGVNYXBbcmVmXVxuICAgICAgICAgIGlmIChuZXdQYXRoVmFsdWUudHlwZSBpbnN0YW5jZW9mIEdyYXBoUUxMaXN0KSB7XG4gICAgICAgICAgICBuZXdQYXRoVmFsdWUgPSBPYmplY3QuYXNzaWduKHt9LCBuZXdQYXRoVmFsdWUsIHtcbiAgICAgICAgICAgICAgdHlwZTogbmV3IEdyYXBoUUxMaXN0KHJlZk1vZGVsVHlwZSksXG4gICAgICAgICAgICAgIHJlc29sdmU6IGFzeW5jIChpbnN0YW5jZSkgPT4ge1xuICAgICAgICAgICAgICAgIC8vIFRPRE86IGFyZ3MgZmlsdGVyXG4gICAgICAgICAgICAgICAgcmV0dXJuIGF3YWl0IG1vZGVsLmZpbmQoeyBfaWQ6IHsgJGluOiBpbnN0YW5jZVtwYXRoXSB9IH0pXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pXG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIG5ld1BhdGhWYWx1ZSA9IE9iamVjdC5hc3NpZ24oe30sIG5ld1BhdGhWYWx1ZSwge1xuICAgICAgICAgICAgICB0eXBlOiByZWZNb2RlbFR5cGUsXG4gICAgICAgICAgICAgIHJlc29sdmU6IGFzeW5jIChpbnN0YW5jZSkgPT4ge1xuICAgICAgICAgICAgICAgIHJldHVybiBhd2FpdCBtb2RlbC5maW5kQnlJZChpbnN0YW5jZVtwYXRoXSlcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSlcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHsgW3BhdGhdOiBuZXdQYXRoVmFsdWUgfVxuICAgICAgfSlcbiAgICAgIC5yZWR1Y2UoKHR5cGVGaWVsZCwgcGF0aCkgPT4gKE9iamVjdC5hc3NpZ24odHlwZUZpZWxkLCBwYXRoKSksIHt9KVxuXG4gICAgdHlwZU1hcFttb2RlbE5hbWVdLl90eXBlQ29uZmlnLmZpZWxkcyA9ICgpID0+IChuZXdUeXBlRmlsZWRzKVxuICB9KVxuXG4gIF90eXBlTWFwID0gT2JqZWN0LmFzc2lnbihfdHlwZU1hcCwgdHlwZU1hcClcbiAgcmV0dXJuIF90eXBlTWFwXG59XG5cbi8qIENvbnZlcnQgYSBtb25nb29zZSBtb2RlbCB0byBjb3JyZXNwb25kaW5nIHR5cGUgKi9cbmNvbnN0IHRvVHlwZSA9IChtb2RlbCkgPT4ge1xuICBjb25zdCBleGNlcHRQYXRoID0gWydfaWQnLCAnX192J11cbiAgY29uc3QgaW5oZXJpdE9wdHMgPSBbJ3JlZicsICdjb250ZXh0J11cbiAgY29uc3QgcGF0aHMgPSBtb2RlbC5zY2hlbWEucGF0aHNcbiAgbGV0IGZpZWxkcyA9IE9iamVjdC5rZXlzKHBhdGhzKVxuICAgIC5maWx0ZXIocGF0aCA9PiBleGNlcHRQYXRoLmluZGV4T2YocGF0aCkgPT09IC0xKVxuICAgIC5tYXAocGF0aCA9PiB7XG4gICAgICBjb25zdCBhdHRyID0gcGF0aHNbcGF0aF1cbiAgICAgIGxldCBmaWVsZCA9IHsgdHlwZTogcGF0aFRvVHlwZShhdHRyKSB9XG4gICAgICAvLyBGaW5kIG91dCBzcGVjaWFsIG9wdCBvbiBtb25nb29zZSBtb2RlbCdzIHBhdGgsIHVzZSBzdWJQYXRoJ3Mgb3B0IGlmIHBhdGggaXMgYW4gQXJyYXlcbiAgICAgIGluaGVyaXRPcHRzLmZvckVhY2gob3B0ID0+IHtcbiAgICAgICAgaWYgKGF0dHIub3B0aW9uc1tvcHRdIHx8IChhdHRyLmluc3RhbmNlID09PSAnQXJyYXknICYmIGF0dHIuY2FzdGVyLm9wdGlvbnNbb3B0XSkpIHtcbiAgICAgICAgICBmaWVsZFtvcHRdID0gYXR0ci5vcHRpb25zW29wdF0gfHwgYXR0ci5jYXN0ZXIub3B0aW9uc1tvcHRdXG4gICAgICAgIH1cbiAgICAgIH0pXG4gICAgICAvLyBNYXJrIHJlcXVpcmVkIHBhdGhcbiAgICAgIGNvbnN0IHJlcXVpcmVkID0gYXR0ci5vcHRpb25zLnJlcXVpcmVkXG4gICAgICBpZiAoQXJyYXkuaXNBcnJheShyZXF1aXJlZCkgJiYgcmVxdWlyZWRbMF0gfHwgcmVxdWlyZWQpIGZpZWxkLnJlcXVpcmVkID0gdHJ1ZVxuICAgICAgcmV0dXJuIHsgW3BhdGhdOiBmaWVsZCB9XG4gICAgfSlcbiAgICAucmVkdWNlKChmaWVsZHMsIHBhdGgpID0+IHtcbiAgICAgIC8vIG1ha2UgdXAgb2JqZWN0IHRwZSwgZS5nIHsgbmFtZTogeyBmaXJzdDoge3R5cGU6IEdyYXBoUUxTdHJpbmcuLi59LCBsYXN0OiB7dHlwZTogR3JhcGhRTFN0cmluZy4uLn0gfSB9XG4gICAgICBjb25zdCBwYXRoS2V5ID0gT2JqZWN0LmtleXMocGF0aClbMF1cbiAgICAgIGNvbnN0IHBhdGhLZXlTcGxpdCA9IHBhdGhLZXkuc3BsaXQoJy4nKVxuICAgICAgY29uc3QgcGF0aEtleUxlbmd0aCA9IHBhdGhLZXlTcGxpdC5sZW5ndGhcbiAgICAgIGlmIChwYXRoS2V5TGVuZ3RoLmxlbmd0aCA9PT0gMSkgcmV0dXJuIE9iamVjdC5hc3NpZ24oZmllbGRzLCBwYXRoKVxuICAgICAgcGF0aEtleVNwbGl0LnJlZHVjZSgoZmllbGRQb3N0aW9uLCBkZXB0aCwgaW5kZXgpID0+IHtcbiAgICAgICAgaWYgKGluZGV4ID09PSBwYXRoS2V5TGVuZ3RoIC0gMSkge1xuICAgICAgICAgIGZpZWxkUG9zdGlvbltkZXB0aF0gPSBwYXRoW3BhdGhLZXldXG4gICAgICAgICAgcmV0dXJuXG4gICAgICAgIH1cbiAgICAgICAgZmllbGRQb3N0aW9uW2RlcHRoXSA9IGZpZWxkUG9zdGlvbltkZXB0aF0gfHwge31cbiAgICAgICAgcmV0dXJuIGZpZWxkUG9zdGlvbltkZXB0aF1cbiAgICAgIH0sIGZpZWxkcylcbiAgICAgIHJldHVybiBmaWVsZHNcbiAgICB9LCB7IGlkOiB7IHR5cGU6IEdyYXBoUUxJRCB9IH0pXG5cbiAgLy8gRGVhbCB3aXRoIG9iamVjdCBhdHRyaWJ1dGUgaW4gbW9uZ29vc2UgbW9kZWxcbiAgLy8gZS5nLiB7bmFtZToge2ZpcnN0OiBTdHJpbmcsIGxhc3Q6IFN0cmluZn19IC0+IHtuYW1lOiBHcmFwaFFMVHlwZXtmaWVsZHM6IHtmaXJzdDogR3JhcGhRTFN0cmluZywgdHdvOiBHcmFwaFFMU3RyaW5nfX19XG4gIGZpZWxkcyA9IE9iamVjdC5lbnRyaWVzKGZpZWxkcykubWFwKChba2V5LCBhdHRyXSkgPT4ge1xuICAgIHJldHVybiB7IFtrZXldOiBjb252ZXJ0T2JqZWN0KGF0dHIsIGAke21vZGVsLm1vZGVsTmFtZX0ke2tleX1gKSB9XG4gIH0pLnJlZHVjZSgoZmllbGRzLCBwYXRoKSA9PiAoT2JqZWN0LmFzc2lnbihmaWVsZHMsIHBhdGgpKSwge30pXG4gIHJldHVybiBuZXcgR3JhcGhRTE9iamVjdFR5cGUoe1xuICAgIG5hbWU6IG1vZGVsLm1vZGVsTmFtZSxcbiAgICBmaWVsZHM6ICgpID0+IChmaWVsZHMpXG4gIH0pXG59XG5cbi8vIENvbnZlcnQgc2luZ2xlIHBhdGggb2YgbW9uZ29vc2UgdG8gdHlwZVxuY29uc3QgcGF0aFRvVHlwZSA9IChwYXRoKSA9PiB7XG4gIHN3aXRjaCAocGF0aC5pbnN0YW5jZSkge1xuICAgIGNhc2UgJ1N0cmluZyc6XG4gICAgICByZXR1cm4gR3JhcGhRTFN0cmluZ1xuICAgIGNhc2UgJ051bWJlcic6XG4gICAgICAvLyBGbG9hdCBpbmNsdWRlcyBJbnRcbiAgICAgIC8vIEBzZWUgaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwvZ3JhcGhxbC1qcy9ibG9iL21hc3Rlci9zcmMvdHlwZS9zY2FsYXJzLmpzI0w2OVxuICAgICAgcmV0dXJuIEdyYXBoUUxGbG9hdFxuICAgIGNhc2UgJ0RhdGUnOlxuICAgICAgcmV0dXJuIEdyYXBoUUxEYXRlXG4gICAgY2FzZSAnQnVmZmVyJzpcbiAgICAgIHJldHVybiBHcmFwaFFMQnVmZmVyXG4gICAgY2FzZSAnQm9vbGVhbic6XG4gICAgICByZXR1cm4gR3JhcGhRTEJvb2xlYW5cbiAgICBjYXNlICdNaXhlZCc6XG4gICAgICByZXR1cm4gR3JhcGhRTE1peGVkXG4gICAgY2FzZSAnT2JqZWN0SUQnOlxuICAgICAgcmV0dXJuIEdyYXBoUUxJRFxuICAgIGNhc2UgJ0FycmF5JzpcbiAgICAgIGNvbnN0IHN1YlR5cGUgPSBwYXRoVG9UeXBlKHBhdGguY2FzdGVyKVxuICAgICAgcmV0dXJuIG5ldyBHcmFwaFFMTGlzdChzdWJUeXBlKVxuICAgIGRlZmF1bHQ6XG4gICAgICByZXR1cm4gR3JhcGhRTE1peGVkXG4gIH1cbn1cblxuLy8gQ29udmVydCBtb2RlbCdzIG9iamVjdCBhdHRyaWJ1dGVcbmNvbnN0IGNvbnZlcnRPYmplY3QgPSAoYXR0ciwgcGFyZW50TmFtZSkgPT4ge1xuICBpZiAoYXR0ci50eXBlICYmIChhdHRyLnR5cGUgaW5zdGFuY2VvZiBHcmFwaFFMU2NhbGFyVHlwZSB8fCBhdHRyLnR5cGUgaW5zdGFuY2VvZiBHcmFwaFFMTGlzdCkpIHJldHVybiBhdHRyXG4gIGNvbnN0IGZpZWxkcyA9IE9iamVjdC5lbnRyaWVzKGF0dHIpLm1hcCgoW2tleSwgdmFrdWVdKSA9PiB7XG4gICAgcmV0dXJuIHsgW2tleV06IGNvbnZlcnRPYmplY3QoYXR0cltrZXldLCBgJHtwYXJlbnROYW1lfSR7a2V5fWApIH1cbiAgfSkucmVkdWNlKChmaWVsZHMsIHBhdGgpID0+IHtcbiAgICAvLyBjb25zb2xlLmxvZyhwYXRoKVxuICAgIHJldHVybiBPYmplY3QuYXNzaWduKGZpZWxkcywgcGF0aClcbiAgfSwge30pXG4gIC8vIGNvbnNvbGUubG9nKGZpZWxkcylcbiAgcmV0dXJuIHtcbiAgICB0eXBlOiBuZXcgR3JhcGhRTE9iamVjdFR5cGUoe1xuICAgICAgbmFtZTogYCR7cGFyZW50TmFtZX1gLFxuICAgICAgZmllbGRzOiAoKSA9PiAoZmllbGRzKVxuICAgIH0pXG4gIH1cbn1cbiJdfQ== -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); 8 | 9 | var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); 10 | 11 | var _entries = require('babel-runtime/core-js/object/entries'); 12 | 13 | var _entries2 = _interopRequireDefault(_entries); 14 | 15 | var _assign = require('babel-runtime/core-js/object/assign'); 16 | 17 | var _assign2 = _interopRequireDefault(_assign); 18 | 19 | exports.filterArgs = filterArgs; 20 | exports.toMongooseArgs = toMongooseArgs; 21 | exports.pickoutValue = pickoutValue; 22 | 23 | var _graphql = require('graphql'); 24 | 25 | var _pluralize = require('pluralize'); 26 | 27 | var _pluralize2 = _interopRequireDefault(_pluralize); 28 | 29 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 30 | 31 | /** 32 | * Filter arguments when doing CRUD 33 | * @params 34 | * - defaultArgs {Object} the result of buildArgs 35 | * - opt {Object {filter: {Bool}} options: id, plural, required, idRequired, onlyId 36 | */ 37 | function filterArgs(defaultArgs, opt) { 38 | opt = opt || {}; 39 | const packValueToNonNull = value => (0, _assign2.default)({}, value, { type: new _graphql.GraphQLNonNull(value.type) }); 40 | return (0, _entries2.default)(defaultArgs).filter(_ref => { 41 | var _ref2 = (0, _slicedToArray3.default)(_ref, 2); 42 | 43 | let arg = _ref2[0]; 44 | let value = _ref2[1]; 45 | 46 | if (opt.onlyId && arg !== 'id' && arg !== 'ids') return false; 47 | if (opt.id && (arg === 'id' || arg === 'ids')) return false; 48 | if (opt.plural && !value.onlyPlural && _pluralize2.default.plural(arg) === arg) return false; 49 | return true; 50 | }).map(_ref3 => { 51 | var _ref4 = (0, _slicedToArray3.default)(_ref3, 2); 52 | 53 | let arg = _ref4[0]; 54 | let value = _ref4[1]; 55 | 56 | let newValue = (0, _assign2.default)({}, value); 57 | if ((arg === 'id' || arg === 'ids') && opt.idRequired) newValue = packValueToNonNull(newValue); 58 | if (!opt.required && newValue.required && !newValue.context) newValue = packValueToNonNull(newValue); 59 | return [arg, newValue]; 60 | }).reduce((args, _ref5) => { 61 | var _ref6 = (0, _slicedToArray3.default)(_ref5, 2); 62 | 63 | let arg = _ref6[0]; 64 | let value = _ref6[1]; 65 | 66 | return (0, _assign2.default)(args, { [arg]: value }); 67 | }, {}); 68 | } 69 | 70 | /** 71 | * Convert args that graphql know to the args that mongoose know 72 | * so that the args can be used by mongoose to find or create 73 | */ 74 | function toMongooseArgs(args) { 75 | // Covert name_first to name: {first} 76 | let keyDepth = []; 77 | return (0, _entries2.default)(args).reduce((args, _ref7) => { 78 | var _ref8 = (0, _slicedToArray3.default)(_ref7, 2); 79 | 80 | let key = _ref8[0]; 81 | let value = _ref8[1]; 82 | 83 | keyDepth = key.split('_'); 84 | if (keyDepth.length === 1) return (0, _assign2.default)(args, { [key]: value }); 85 | keyDepth.reduce((args, depth, index) => { 86 | if (index === keyDepth.length - 1) { 87 | args[depth] = value; 88 | return; 89 | } 90 | args[depth] = args[depth] || {}; 91 | return args[depth]; 92 | }, args); 93 | return args; 94 | }, {}); 95 | } 96 | 97 | /** 98 | * Giving an object and a string, pick out wanted data 99 | * e.g. user { id: 'test' } and user.id => 'test' 100 | */ 101 | function pickoutValue(target, str) { 102 | const strDepth = str.split('.'); 103 | const newTarget = target[strDepth[0]]; 104 | if (newTarget === undefined) throw new Error('Cannot find the value'); 105 | if (strDepth.length === 1) return newTarget; 106 | return pickoutValue(newTarget, strDepth.splice(1).join('.')); 107 | } 108 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlscy5qcyJdLCJuYW1lcyI6WyJmaWx0ZXJBcmdzIiwidG9Nb25nb29zZUFyZ3MiLCJwaWNrb3V0VmFsdWUiLCJkZWZhdWx0QXJncyIsIm9wdCIsInBhY2tWYWx1ZVRvTm9uTnVsbCIsInZhbHVlIiwidHlwZSIsImZpbHRlciIsImFyZyIsIm9ubHlJZCIsImlkIiwicGx1cmFsIiwib25seVBsdXJhbCIsIm1hcCIsIm5ld1ZhbHVlIiwiaWRSZXF1aXJlZCIsInJlcXVpcmVkIiwiY29udGV4dCIsInJlZHVjZSIsImFyZ3MiLCJrZXlEZXB0aCIsImtleSIsInNwbGl0IiwibGVuZ3RoIiwiZGVwdGgiLCJpbmRleCIsInRhcmdldCIsInN0ciIsInN0ckRlcHRoIiwibmV3VGFyZ2V0IiwidW5kZWZpbmVkIiwiRXJyb3IiLCJzcGxpY2UiLCJqb2luIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7UUFTZ0JBLFUsR0FBQUEsVTtRQXlCQUMsYyxHQUFBQSxjO1FBc0JBQyxZLEdBQUFBLFk7O0FBeERoQjs7QUFDQTs7Ozs7O0FBRUE7Ozs7OztBQU1PLFNBQVNGLFVBQVQsQ0FBcUJHLFdBQXJCLEVBQWtDQyxHQUFsQyxFQUF1QztBQUM1Q0EsUUFBTUEsT0FBTyxFQUFiO0FBQ0EsUUFBTUMscUJBQXNCQyxLQUFELElBQVksc0JBQWMsRUFBZCxFQUFrQkEsS0FBbEIsRUFBeUIsRUFBQ0MsTUFBTSw0QkFBbUJELE1BQU1DLElBQXpCLENBQVAsRUFBekIsQ0FBdkM7QUFDQSxTQUFPLHVCQUFlSixXQUFmLEVBQ0pLLE1BREksQ0FDRyxRQUFrQjtBQUFBOztBQUFBLFFBQWhCQyxHQUFnQjtBQUFBLFFBQVhILEtBQVc7O0FBQ3hCLFFBQUlGLElBQUlNLE1BQUosSUFBY0QsUUFBUSxJQUF0QixJQUE4QkEsUUFBUSxLQUExQyxFQUFpRCxPQUFPLEtBQVA7QUFDakQsUUFBSUwsSUFBSU8sRUFBSixLQUFXRixRQUFRLElBQVIsSUFBZ0JBLFFBQVEsS0FBbkMsQ0FBSixFQUErQyxPQUFPLEtBQVA7QUFDL0MsUUFBSUwsSUFBSVEsTUFBSixJQUFjLENBQUNOLE1BQU1PLFVBQXJCLElBQW1DLG9CQUFVRCxNQUFWLENBQWlCSCxHQUFqQixNQUEwQkEsR0FBakUsRUFBc0UsT0FBTyxLQUFQO0FBQ3RFLFdBQU8sSUFBUDtBQUNELEdBTkksRUFPSkssR0FQSSxDQU9BLFNBQWtCO0FBQUE7O0FBQUEsUUFBaEJMLEdBQWdCO0FBQUEsUUFBWEgsS0FBVzs7QUFDckIsUUFBSVMsV0FBVyxzQkFBYyxFQUFkLEVBQWtCVCxLQUFsQixDQUFmO0FBQ0EsUUFBSSxDQUFDRyxRQUFRLElBQVIsSUFBZ0JBLFFBQVEsS0FBekIsS0FBbUNMLElBQUlZLFVBQTNDLEVBQXVERCxXQUFXVixtQkFBbUJVLFFBQW5CLENBQVg7QUFDdkQsUUFBSSxDQUFDWCxJQUFJYSxRQUFMLElBQWlCRixTQUFTRSxRQUExQixJQUFzQyxDQUFDRixTQUFTRyxPQUFwRCxFQUE2REgsV0FBV1YsbUJBQW1CVSxRQUFuQixDQUFYO0FBQzdELFdBQU8sQ0FBQ04sR0FBRCxFQUFNTSxRQUFOLENBQVA7QUFDRCxHQVpJLEVBYUpJLE1BYkksQ0FhRyxDQUFDQyxJQUFELFlBQXdCO0FBQUE7O0FBQUEsUUFBaEJYLEdBQWdCO0FBQUEsUUFBWEgsS0FBVzs7QUFDOUIsV0FBTyxzQkFBY2MsSUFBZCxFQUFvQixFQUFDLENBQUNYLEdBQUQsR0FBT0gsS0FBUixFQUFwQixDQUFQO0FBQ0QsR0FmSSxFQWVGLEVBZkUsQ0FBUDtBQWdCRDs7QUFFRDs7OztBQUlPLFNBQVNMLGNBQVQsQ0FBeUJtQixJQUF6QixFQUErQjtBQUNwQztBQUNBLE1BQUlDLFdBQVcsRUFBZjtBQUNBLFNBQU8sdUJBQWVELElBQWYsRUFBcUJELE1BQXJCLENBQTRCLENBQUNDLElBQUQsWUFBd0I7QUFBQTs7QUFBQSxRQUFoQkUsR0FBZ0I7QUFBQSxRQUFYaEIsS0FBVzs7QUFDekRlLGVBQVdDLElBQUlDLEtBQUosQ0FBVSxHQUFWLENBQVg7QUFDQSxRQUFJRixTQUFTRyxNQUFULEtBQW9CLENBQXhCLEVBQTJCLE9BQU8sc0JBQWNKLElBQWQsRUFBb0IsRUFBQyxDQUFDRSxHQUFELEdBQU9oQixLQUFSLEVBQXBCLENBQVA7QUFDM0JlLGFBQVNGLE1BQVQsQ0FBZ0IsQ0FBQ0MsSUFBRCxFQUFPSyxLQUFQLEVBQWNDLEtBQWQsS0FBd0I7QUFDdEMsVUFBSUEsVUFBVUwsU0FBU0csTUFBVCxHQUFrQixDQUFoQyxFQUFtQztBQUNqQ0osYUFBS0ssS0FBTCxJQUFjbkIsS0FBZDtBQUNBO0FBQ0Q7QUFDRGMsV0FBS0ssS0FBTCxJQUFjTCxLQUFLSyxLQUFMLEtBQWUsRUFBN0I7QUFDQSxhQUFPTCxLQUFLSyxLQUFMLENBQVA7QUFDRCxLQVBELEVBT0dMLElBUEg7QUFRQSxXQUFPQSxJQUFQO0FBQ0QsR0FaTSxFQVlKLEVBWkksQ0FBUDtBQWFEOztBQUVEOzs7O0FBSU8sU0FBU2xCLFlBQVQsQ0FBdUJ5QixNQUF2QixFQUErQkMsR0FBL0IsRUFBb0M7QUFDekMsUUFBTUMsV0FBV0QsSUFBSUwsS0FBSixDQUFVLEdBQVYsQ0FBakI7QUFDQSxRQUFNTyxZQUFZSCxPQUFPRSxTQUFTLENBQVQsQ0FBUCxDQUFsQjtBQUNBLE1BQUlDLGNBQWNDLFNBQWxCLEVBQTZCLE1BQU0sSUFBSUMsS0FBSixDQUFVLHVCQUFWLENBQU47QUFDN0IsTUFBSUgsU0FBU0wsTUFBVCxLQUFvQixDQUF4QixFQUEyQixPQUFPTSxTQUFQO0FBQzNCLFNBQU81QixhQUFhNEIsU0FBYixFQUF3QkQsU0FBU0ksTUFBVCxDQUFnQixDQUFoQixFQUFtQkMsSUFBbkIsQ0FBd0IsR0FBeEIsQ0FBeEIsQ0FBUDtBQUNEIiwiZmlsZSI6InV0aWxzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtHcmFwaFFMTm9uTnVsbH0gZnJvbSAnZ3JhcGhxbCdcbmltcG9ydCBwbHVyYWxpemUgZnJvbSAncGx1cmFsaXplJ1xuXG4vKipcbiAqIEZpbHRlciBhcmd1bWVudHMgd2hlbiBkb2luZyBDUlVEXG4gKiBAcGFyYW1zXG4gKiAgLSBkZWZhdWx0QXJncyB7T2JqZWN0fSB0aGUgcmVzdWx0IG9mIGJ1aWxkQXJnc1xuICogIC0gb3B0IHtPYmplY3Qge2ZpbHRlcjoge0Jvb2x9fSBvcHRpb25zOiBpZCwgcGx1cmFsLCByZXF1aXJlZCwgaWRSZXF1aXJlZCwgb25seUlkXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBmaWx0ZXJBcmdzIChkZWZhdWx0QXJncywgb3B0KSB7XG4gIG9wdCA9IG9wdCB8fCB7fVxuICBjb25zdCBwYWNrVmFsdWVUb05vbk51bGwgPSAodmFsdWUpID0+IChPYmplY3QuYXNzaWduKHt9LCB2YWx1ZSwge3R5cGU6IG5ldyBHcmFwaFFMTm9uTnVsbCh2YWx1ZS50eXBlKX0pKVxuICByZXR1cm4gT2JqZWN0LmVudHJpZXMoZGVmYXVsdEFyZ3MpXG4gICAgLmZpbHRlcigoW2FyZywgdmFsdWVdKSA9PiB7XG4gICAgICBpZiAob3B0Lm9ubHlJZCAmJiBhcmcgIT09ICdpZCcgJiYgYXJnICE9PSAnaWRzJykgcmV0dXJuIGZhbHNlXG4gICAgICBpZiAob3B0LmlkICYmIChhcmcgPT09ICdpZCcgfHwgYXJnID09PSAnaWRzJykpIHJldHVybiBmYWxzZVxuICAgICAgaWYgKG9wdC5wbHVyYWwgJiYgIXZhbHVlLm9ubHlQbHVyYWwgJiYgcGx1cmFsaXplLnBsdXJhbChhcmcpID09PSBhcmcpIHJldHVybiBmYWxzZVxuICAgICAgcmV0dXJuIHRydWVcbiAgICB9KVxuICAgIC5tYXAoKFthcmcsIHZhbHVlXSkgPT4ge1xuICAgICAgbGV0IG5ld1ZhbHVlID0gT2JqZWN0LmFzc2lnbih7fSwgdmFsdWUpXG4gICAgICBpZiAoKGFyZyA9PT0gJ2lkJyB8fCBhcmcgPT09ICdpZHMnKSAmJiBvcHQuaWRSZXF1aXJlZCkgbmV3VmFsdWUgPSBwYWNrVmFsdWVUb05vbk51bGwobmV3VmFsdWUpXG4gICAgICBpZiAoIW9wdC5yZXF1aXJlZCAmJiBuZXdWYWx1ZS5yZXF1aXJlZCAmJiAhbmV3VmFsdWUuY29udGV4dCkgbmV3VmFsdWUgPSBwYWNrVmFsdWVUb05vbk51bGwobmV3VmFsdWUpXG4gICAgICByZXR1cm4gW2FyZywgbmV3VmFsdWVdXG4gICAgfSlcbiAgICAucmVkdWNlKChhcmdzLCBbYXJnLCB2YWx1ZV0pID0+IHtcbiAgICAgIHJldHVybiBPYmplY3QuYXNzaWduKGFyZ3MsIHtbYXJnXTogdmFsdWV9KVxuICAgIH0sIHt9KVxufVxuXG4vKipcbiAqIENvbnZlcnQgYXJncyB0aGF0IGdyYXBocWwga25vdyB0byB0aGUgYXJncyB0aGF0IG1vbmdvb3NlIGtub3dcbiAqIHNvIHRoYXQgdGhlIGFyZ3MgY2FuIGJlIHVzZWQgYnkgbW9uZ29vc2UgdG8gZmluZCBvciBjcmVhdGVcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRvTW9uZ29vc2VBcmdzIChhcmdzKSB7XG4gIC8vIENvdmVydCBuYW1lX2ZpcnN0IHRvIG5hbWU6IHtmaXJzdH1cbiAgbGV0IGtleURlcHRoID0gW11cbiAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKGFyZ3MpLnJlZHVjZSgoYXJncywgW2tleSwgdmFsdWVdKSA9PiB7XG4gICAga2V5RGVwdGggPSBrZXkuc3BsaXQoJ18nKVxuICAgIGlmIChrZXlEZXB0aC5sZW5ndGggPT09IDEpIHJldHVybiBPYmplY3QuYXNzaWduKGFyZ3MsIHtba2V5XTogdmFsdWV9KVxuICAgIGtleURlcHRoLnJlZHVjZSgoYXJncywgZGVwdGgsIGluZGV4KSA9PiB7XG4gICAgICBpZiAoaW5kZXggPT09IGtleURlcHRoLmxlbmd0aCAtIDEpIHtcbiAgICAgICAgYXJnc1tkZXB0aF0gPSB2YWx1ZVxuICAgICAgICByZXR1cm5cbiAgICAgIH1cbiAgICAgIGFyZ3NbZGVwdGhdID0gYXJnc1tkZXB0aF0gfHwge31cbiAgICAgIHJldHVybiBhcmdzW2RlcHRoXVxuICAgIH0sIGFyZ3MpXG4gICAgcmV0dXJuIGFyZ3NcbiAgfSwge30pXG59XG5cbi8qKlxuICogR2l2aW5nIGFuIG9iamVjdCBhbmQgYSBzdHJpbmcsIHBpY2sgb3V0IHdhbnRlZCBkYXRhXG4gKiBlLmcuIHVzZXIgeyBpZDogJ3Rlc3QnIH0gYW5kIHVzZXIuaWQgPT4gJ3Rlc3QnXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwaWNrb3V0VmFsdWUgKHRhcmdldCwgc3RyKSB7XG4gIGNvbnN0IHN0ckRlcHRoID0gc3RyLnNwbGl0KCcuJylcbiAgY29uc3QgbmV3VGFyZ2V0ID0gdGFyZ2V0W3N0ckRlcHRoWzBdXVxuICBpZiAobmV3VGFyZ2V0ID09PSB1bmRlZmluZWQpIHRocm93IG5ldyBFcnJvcignQ2Fubm90IGZpbmQgdGhlIHZhbHVlJylcbiAgaWYgKHN0ckRlcHRoLmxlbmd0aCA9PT0gMSkgcmV0dXJuIG5ld1RhcmdldFxuICByZXR1cm4gcGlja291dFZhbHVlKG5ld1RhcmdldCwgc3RyRGVwdGguc3BsaWNlKDEpLmpvaW4oJy4nKSlcbn1cbiJdfQ== -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mooseql", 3 | "version": "0.1.6", 4 | "description": "Build GraphQL Schema based on Mongoose model", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "make test" 8 | }, 9 | "ava": { 10 | "require": [ 11 | "babel-register" 12 | ], 13 | "babel": "inherit" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/wwayne/mooseql.git" 18 | }, 19 | "keywords": [ 20 | "mongoose", 21 | "mongodb", 22 | "graphql", 23 | "graphql adapter", 24 | "adapter", 25 | "orm" 26 | ], 27 | "standard": { 28 | "parser": "babel-eslint", 29 | "ignore": [ 30 | "lib/" 31 | ] 32 | }, 33 | "author": "wwayne", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/wwayne/mooseql/issues" 37 | }, 38 | "engines": { 39 | "node": ">=4.0.0" 40 | }, 41 | "homepage": "https://github.com/wwayne/mooseql#readme", 42 | "peerDependencies": { 43 | "graphql": "^0.7.0", 44 | "mongoose": "^4.6.0" 45 | }, 46 | "dependencies": { 47 | "babel-runtime": "^6.11.6", 48 | "pluralize": "^3.0.0" 49 | }, 50 | "devDependencies": { 51 | "ava": "^0.16.0", 52 | "babel-cli": "^6.14.0", 53 | "babel-eslint": "^6.1.2", 54 | "babel-plugin-transform-async-to-generator": "^6.8.0", 55 | "babel-plugin-transform-runtime": "^6.15.0", 56 | "babel-preset-es2015-node4": "^2.1.0", 57 | "babel-preset-es2017": "^6.14.0", 58 | "babel-register": "^6.14.0", 59 | "coveralls": "^2.11.12", 60 | "express": "^4.14.0", 61 | "express-graphql": "^0.5.4", 62 | "nyc": "^8.1.0", 63 | "snazzy": "^4.0.1", 64 | "standard": "^8.0.0", 65 | "supertest": "^2.0.0", 66 | "supertest-as-promised": "^4.0.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema, GraphQLObjectType } from 'graphql' 2 | import { modelsToTypes } from './type' 3 | import { buildSchema } from './schema' 4 | 5 | const mooseql = (models, customFields, opt) => { 6 | const typeMap = modelsToTypes(models) 7 | const {query, mutation} = buildSchema(models, typeMap) 8 | const customQuery = customFields && customFields.query 9 | const customMutation = customFields && customFields.mutation 10 | return new GraphQLSchema({ 11 | query: new GraphQLObjectType({ 12 | name: 'Query', 13 | fields: Object.assign({}, query, customQuery) 14 | }), 15 | mutation: new GraphQLObjectType({ 16 | name: 'Mutation', 17 | fields: Object.assign({}, mutation, customMutation) 18 | }) 19 | }) 20 | } 21 | 22 | mooseql.buildTypes = modelsToTypes 23 | 24 | module.exports = mooseql 25 | -------------------------------------------------------------------------------- /src/schema/buildArgs.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLID, 3 | GraphQLString, 4 | GraphQLFloat, 5 | GraphQLBoolean, 6 | GraphQLList 7 | } from 'graphql' 8 | import { 9 | GraphQLBuffer, 10 | GraphQLDate 11 | } from '../type/customType' 12 | import pluralize from 'pluralize' 13 | 14 | /** 15 | * Generate args based on type's fields 16 | * @parmas 17 | * - type {Object} built graphql type 18 | * @return 19 | * - defaultArgs {Object} argus based on type's fields, including singular and plural 20 | * @notice 21 | * - response has `id` and `ids` instead of _id 22 | * mongoose query should convert id to _id or mongoose won't support it 23 | * - name.first in Mongoose model will have args 'name_first' and 'name_firsts' 24 | * because graphql name convention only support _a-zA-Z0-9 25 | */ 26 | export default function (type) { 27 | const fields = type._typeConfig.fields() 28 | return Object.entries(fields) 29 | .reduce((args, [key, field]) => { 30 | return Object.assign(args, fieldToArg(key, field)) 31 | }, {}) 32 | } 33 | 34 | const fieldToArg = (key, field) => { 35 | const typeName = field.type.name || field.type.constructor.name 36 | const { required, ref, context } = field 37 | let graphqlType 38 | if (typeName !== 'GraphQLList') { 39 | graphqlType = nameToType(typeName, field) 40 | // Custom type for Object attribute in mongoose model. e.g. {name: {first, last}} 41 | if (!graphqlType) return buildObjectArgs(key, field) 42 | return buildArgs(key, graphqlType, { required, ref, context }) 43 | } else { 44 | // Deal with List type 45 | graphqlType = nameToType(field.type.ofType.name, field) 46 | return {[key]: { type: new GraphQLList(graphqlType), onlyPlural: true, required, ref, context }} 47 | } 48 | } 49 | 50 | const buildArgs = (key, graphqlType, { required, ref, context }) => { 51 | if (Object.keys(graphqlType).length === 0) return {} 52 | const plural = pluralize.plural(key) 53 | const isPlural = plural === key 54 | return isPlural 55 | ? {[key]: { type: new GraphQLList(graphqlType), onlyPlural: true, required, ref, context }} 56 | : {[key]: { type: graphqlType, required, ref, context }, [plural]: { type: new GraphQLList(graphqlType) }} 57 | } 58 | 59 | const nameToType = (typeName, field) => { 60 | const hasResolve = !!field.resolve 61 | switch (typeName) { 62 | case 'ID': return GraphQLID 63 | case 'String': return GraphQLString 64 | case 'Float': return GraphQLFloat 65 | case 'Boolean': return GraphQLBoolean 66 | case 'Buffer': return GraphQLBuffer 67 | case 'Date': return GraphQLDate 68 | case 'Mixed': return {} 69 | default: 70 | if (hasResolve) return GraphQLID // other models, use ID as reference 71 | return null // Object attribute in mongoose model 72 | } 73 | } 74 | 75 | // Build args for Object attribute of the mongoose model 76 | const buildObjectArgs = (parentKey, field) => { 77 | const fields = field.type._typeConfig.fields() 78 | return Object.entries(fields).map(([key, value]) => { 79 | return fieldToArg(`${parentKey}_${key}`, value) 80 | }).reduce((args, myArg) => (Object.assign(args, myArg)), {}) 81 | } 82 | -------------------------------------------------------------------------------- /src/schema/buildMutation.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLObjectType, 3 | GraphQLBoolean, 4 | GraphQLString 5 | } from 'graphql' 6 | import buildArgs from './buildArgs' 7 | import { filterArgs, toMongooseArgs, pickoutValue } from '../utils' 8 | 9 | /** 10 | * Build mutation for single model 11 | * @params 12 | * - model a mongoose model 13 | * - type a corresponding converted graphql type 14 | * @return 15 | * - {Object} e.g. { createUser: {type: userType, args, resolve}, updateUser, removeUser } 16 | */ 17 | export default function (model, type) { 18 | const modelName = model.modelName 19 | const defaultArgs = buildArgs(type) 20 | return { 21 | [`create${modelName}`]: buildCreate(model, type, defaultArgs), 22 | [`update${modelName}`]: buildUpdate(model, type, defaultArgs), 23 | [`delete${modelName}`]: buildDelete(model, type, defaultArgs) 24 | } 25 | } 26 | 27 | const buildCreate = (Model, type, defaultArgs) => { 28 | const createArgs = filterArgs(defaultArgs, { id: true, plural: true }) 29 | const contextArgs = Object.entries(createArgs).filter(([key, value]) => { 30 | return value.context 31 | }) 32 | return { 33 | type, 34 | args: createArgs, 35 | resolve: async (root, args, context) => { 36 | // Set up args that marked as context 37 | contextArgs.forEach(([key, value]) => { 38 | if (value.required) { 39 | // Check if an arg is required and has context 40 | // because it won't be marked as GraphqlNonNull in args 41 | const ctx = value.context.split('.')[0] // user.id -> user 42 | if (context[ctx] === undefined) throw new Error(`${key} is required`) 43 | } 44 | args[key] = pickoutValue(context, value.context) 45 | }) 46 | const instance = new Model(toMongooseArgs(args)) 47 | return await instance.save() 48 | } 49 | } 50 | } 51 | 52 | const buildUpdate = (Model, type, defaultArgs) => { 53 | return { 54 | type, 55 | args: filterArgs(defaultArgs, { plural: true, required: true, idRequired: true }), 56 | resolve: async (_, args) => { 57 | const updateData = Object.entries(args) 58 | .filter(([key, _]) => { 59 | if (key === 'id' || key === 'ids') return false 60 | return true 61 | }) 62 | .map(([key, value]) => { 63 | return [key.replace('_', '.'), value] 64 | }) 65 | .reduce((args, [key, value]) => (Object.assign(args, {[key]: value})), {}) 66 | 67 | await Model.update({ _id: args.id }, { $set: updateData }) 68 | return await Model.findById(args.id) 69 | } 70 | } 71 | } 72 | 73 | const buildDelete = (Model, type, defaultArgs) => { 74 | const returnType = new GraphQLObjectType({ 75 | name: `${Model.modelName}deleteMutationReturn`, 76 | fields: () => ({ 77 | success: { type: GraphQLBoolean }, 78 | msg: { type: GraphQLString } 79 | }) 80 | }) 81 | return { 82 | type: returnType, 83 | args: filterArgs(defaultArgs, { plural: true, idRequired: true, onlyId: true }), 84 | resolve: async (_, args) => { 85 | let res = { success: true, msg: null } 86 | try { 87 | await Model.findById(args.id).remove() 88 | } catch (err) { 89 | res = { success: false, msg: err.message } 90 | } 91 | return res 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/schema/buildQuery.js: -------------------------------------------------------------------------------- 1 | import { GraphQLList } from 'graphql' 2 | import pluralize from 'pluralize' 3 | import buildArgs from './buildArgs' 4 | 5 | /** 6 | * Build query for a sigle model 7 | * @params 8 | * - model a mongoose model 9 | * - type a corresponding converted graphql type 10 | * @return 11 | * - {Object} e.g. { user: {type: userType, args: {id, ids, userName, userNames...}, resolve} } 12 | */ 13 | export default function (model, type) { 14 | const modelName = model.modelName 15 | const defaultArgs = buildArgs(type) 16 | return { 17 | [modelName.toLowerCase()]: { 18 | type: new GraphQLList(type), 19 | args: defaultArgs, 20 | resolve: async (_, args) => { 21 | if (hasRepeateArgs(Object.keys(args))) { 22 | throw new Error('Can not use singular and plural of an argument in same time') 23 | } 24 | let onlyPlural 25 | const query = Object.entries(args).map(([arg, value]) => { 26 | onlyPlural = defaultArgs[arg]['onlyPlural'] 27 | arg = arg.replace('_', '.') 28 | arg = (arg === 'id' && '_id') || (arg === 'ids' && '_ids') || arg 29 | if (pluralize.singular(arg) === arg || onlyPlural) { 30 | return { [arg]: value } 31 | } else { 32 | return { [pluralize.singular(arg)]: { $in: value } } 33 | } 34 | }).reduce((query, item) => (Object.assign(query, item)), {}) 35 | 36 | return await model.find(query) 37 | } 38 | } 39 | } 40 | } 41 | 42 | // Valid if both singular and plural of a argument were provided 43 | let elem 44 | const hasRepeateArgs = (args) => { 45 | if (args.length === 0) return false 46 | elem = args.pop() 47 | if (args.find(arg => arg === pluralize.plural(elem) || arg === pluralize.singular(elem))) return true 48 | return hasRepeateArgs(args) 49 | } 50 | -------------------------------------------------------------------------------- /src/schema/index.js: -------------------------------------------------------------------------------- 1 | import buildQuery from './buildQuery' 2 | import buildMutation from './buildMutation' 3 | 4 | /** 5 | * Build graphql CRUD schema based on models and types 6 | * @params 7 | * - models {Array} mongoose models 8 | * - typeMap {Object} map of model and corresponding graphql type 9 | * @return 10 | * - grapqhl schema which has query and mutation 11 | */ 12 | export function buildSchema (models, typeMap) { 13 | let type 14 | const {query, mutation} = models.map(model => { 15 | type = typeMap[model.modelName] 16 | return { 17 | query: buildQuery(model, type), 18 | mutation: buildMutation(model, type) 19 | } 20 | }).reduce((fields, modelField) => { 21 | fields.query = Object.assign({}, fields.query, modelField.query) 22 | fields.mutation = Object.assign({}, fields.mutation, modelField.mutation) 23 | return fields 24 | }, { query: {}, mutation: {} }) 25 | 26 | return { 27 | query, 28 | mutation 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/type/customType.js: -------------------------------------------------------------------------------- 1 | import { GraphQLScalarType } from 'graphql' 2 | 3 | /** 4 | * Buffer type 5 | */ 6 | const coerceBuffer = (value) => { 7 | if (value instanceof Buffer) return value 8 | throw new TypeError(`Type error: ${value} is not instance of Buffer`) 9 | } 10 | 11 | export const GraphQLBuffer = new GraphQLScalarType({ 12 | name: 'Buffer', 13 | serialize: coerceBuffer, // serialize to query result 14 | parseValue: coerceBuffer, 15 | parseLiteral: ast => { 16 | // Read from args 17 | return typeof ast.value === 'string' && new Buffer(ast.value) || null 18 | } 19 | }) 20 | 21 | /** 22 | * Date type 23 | */ 24 | const coerceDate = (value) => { 25 | if (value instanceof Date) return value 26 | throw new TypeError(`Type error: ${value} is not instance of Date`) 27 | } 28 | 29 | export const GraphQLDate = new GraphQLScalarType({ 30 | name: 'Date', 31 | serialize: coerceDate, 32 | parseValue: coerceDate, 33 | parseLiteral: ast => { 34 | const d = new Date(ast.value) 35 | return !isNaN(d.getTime()) && d || null 36 | } 37 | }) 38 | 39 | /** 40 | * Mixed type 41 | */ 42 | export const GraphQLMixed = new GraphQLScalarType({ 43 | name: 'Mixed', 44 | serialize: value => value, 45 | parseValue: value => value, 46 | parseLiteral: ast => ast.value 47 | }) 48 | -------------------------------------------------------------------------------- /src/type/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLID, 3 | GraphQLObjectType, 4 | GraphQLString, 5 | GraphQLFloat, 6 | GraphQLBoolean, 7 | GraphQLList, 8 | GraphQLScalarType 9 | } from 'graphql' 10 | import { 11 | GraphQLBuffer, 12 | GraphQLDate, 13 | GraphQLMixed 14 | } from './customType' 15 | 16 | let _typeMap = {} 17 | /** 18 | * Convert bundch of mongoose model to graphql types 19 | * build this as singleton so that it won't create graphQLType twice 20 | * 21 | * @params 22 | * - models {Array} list of mongoose models 23 | * @return 24 | * - typeMap {Object} key: modelName, value: type 25 | */ 26 | export function modelsToTypes (models) { 27 | let typeMap = models.filter(model => { 28 | if (_typeMap[model.modelName]) return false 29 | return true 30 | }).reduce((map, model) => { 31 | return Object.assign(map, { [model.modelName]: toType(model) }) 32 | }, {}) 33 | _typeMap = Object.assign(_typeMap, typeMap) 34 | 35 | // Deal with ref after all types are defined 36 | Object.entries(typeMap).forEach(([modelName, type]) => { 37 | const originFileds = type._typeConfig.fields() 38 | const newTypeFileds = Object.entries(originFileds) 39 | .map(([path, pathValue]) => { 40 | let newPathValue = Object.assign({}, pathValue) 41 | if (newPathValue.ref) { 42 | const ref = newPathValue.ref 43 | if (!_typeMap[ref]) throw TypeError(`${ref} is not a model`) 44 | const model = models.find(m => m.modelName === ref) 45 | const refModelType = _typeMap[ref] 46 | if (newPathValue.type instanceof GraphQLList) { 47 | newPathValue = Object.assign({}, newPathValue, { 48 | type: new GraphQLList(refModelType), 49 | resolve: async (instance) => { 50 | // TODO: args filter 51 | return await model.find({ _id: { $in: instance[path] } }) 52 | } 53 | }) 54 | } else { 55 | newPathValue = Object.assign({}, newPathValue, { 56 | type: refModelType, 57 | resolve: async (instance) => { 58 | return await model.findById(instance[path]) 59 | } 60 | }) 61 | } 62 | } 63 | return { [path]: newPathValue } 64 | }) 65 | .reduce((typeField, path) => (Object.assign(typeField, path)), {}) 66 | 67 | typeMap[modelName]._typeConfig.fields = () => (newTypeFileds) 68 | }) 69 | 70 | _typeMap = Object.assign(_typeMap, typeMap) 71 | return _typeMap 72 | } 73 | 74 | /* Convert a mongoose model to corresponding type */ 75 | const toType = (model) => { 76 | const exceptPath = ['_id', '__v'] 77 | const inheritOpts = ['ref', 'context'] 78 | const paths = model.schema.paths 79 | let fields = Object.keys(paths) 80 | .filter(path => exceptPath.indexOf(path) === -1) 81 | .map(path => { 82 | const attr = paths[path] 83 | let field = { type: pathToType(attr) } 84 | // Find out special opt on mongoose model's path, use subPath's opt if path is an Array 85 | inheritOpts.forEach(opt => { 86 | if (attr.options[opt] || (attr.instance === 'Array' && attr.caster.options && attr.caster.options[opt])) { 87 | field[opt] = attr.options[opt] || attr.caster.options[opt] 88 | } 89 | }) 90 | // Mark required path 91 | const required = attr.options.required 92 | if (Array.isArray(required) && required[0] || required) field.required = true 93 | return { [path]: field } 94 | }) 95 | .reduce((fields, path) => { 96 | // make up object tpe, e.g { name: { first: {type: GraphQLString...}, last: {type: GraphQLString...} } } 97 | const pathKey = Object.keys(path)[0] 98 | const pathKeySplit = pathKey.split('.') 99 | const pathKeyLength = pathKeySplit.length 100 | if (pathKeyLength.length === 1) return Object.assign(fields, path) 101 | pathKeySplit.reduce((fieldPostion, depth, index) => { 102 | if (index === pathKeyLength - 1) { 103 | fieldPostion[depth] = path[pathKey] 104 | return 105 | } 106 | fieldPostion[depth] = fieldPostion[depth] || {} 107 | return fieldPostion[depth] 108 | }, fields) 109 | return fields 110 | }, { id: { type: GraphQLID } }) 111 | 112 | // Deal with object attribute in mongoose model 113 | // e.g. {name: {first: String, last: Strinf}} -> {name: GraphQLType{fields: {first: GraphQLString, two: GraphQLString}}} 114 | fields = Object.entries(fields).map(([key, attr]) => { 115 | return { [key]: convertObject(attr, `${model.modelName}${key}`) } 116 | }).reduce((fields, path) => (Object.assign(fields, path)), {}) 117 | return new GraphQLObjectType({ 118 | name: model.modelName, 119 | fields: () => (fields) 120 | }) 121 | } 122 | 123 | // Convert single path of mongoose to type 124 | const pathToType = (path) => { 125 | switch (path.instance) { 126 | case 'String': 127 | return GraphQLString 128 | case 'Number': 129 | // Float includes Int 130 | // @see https://github.com/graphql/graphql-js/blob/master/src/type/scalars.js#L69 131 | return GraphQLFloat 132 | case 'Date': 133 | return GraphQLDate 134 | case 'Buffer': 135 | return GraphQLBuffer 136 | case 'Boolean': 137 | return GraphQLBoolean 138 | case 'Mixed': 139 | return GraphQLMixed 140 | case 'ObjectID': 141 | return GraphQLID 142 | case 'Array': 143 | const subType = pathToType(path.caster) 144 | return new GraphQLList(subType) 145 | default: 146 | return GraphQLMixed 147 | } 148 | } 149 | 150 | // Convert model's object attribute 151 | const convertObject = (attr, parentName) => { 152 | if (attr.type && (attr.type instanceof GraphQLScalarType || attr.type instanceof GraphQLList)) return attr 153 | const fields = Object.entries(attr).map(([key, vakue]) => { 154 | return { [key]: convertObject(attr[key], `${parentName}${key}`) } 155 | }).reduce((fields, path) => { 156 | // console.log(path) 157 | return Object.assign(fields, path) 158 | }, {}) 159 | // console.log(fields) 160 | return { 161 | type: new GraphQLObjectType({ 162 | name: `${parentName}`, 163 | fields: () => (fields) 164 | }) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import {GraphQLNonNull} from 'graphql' 2 | import pluralize from 'pluralize' 3 | 4 | /** 5 | * Filter arguments when doing CRUD 6 | * @params 7 | * - defaultArgs {Object} the result of buildArgs 8 | * - opt {Object {filter: {Bool}} options: id, plural, required, idRequired, onlyId 9 | */ 10 | export function filterArgs (defaultArgs, opt) { 11 | opt = opt || {} 12 | const packValueToNonNull = (value) => (Object.assign({}, value, {type: new GraphQLNonNull(value.type)})) 13 | return Object.entries(defaultArgs) 14 | .filter(([arg, value]) => { 15 | if (opt.onlyId && arg !== 'id' && arg !== 'ids') return false 16 | if (opt.id && (arg === 'id' || arg === 'ids')) return false 17 | if (opt.plural && !value.onlyPlural && pluralize.plural(arg) === arg) return false 18 | return true 19 | }) 20 | .map(([arg, value]) => { 21 | let newValue = Object.assign({}, value) 22 | if ((arg === 'id' || arg === 'ids') && opt.idRequired) newValue = packValueToNonNull(newValue) 23 | if (!opt.required && newValue.required && !newValue.context) newValue = packValueToNonNull(newValue) 24 | return [arg, newValue] 25 | }) 26 | .reduce((args, [arg, value]) => { 27 | return Object.assign(args, {[arg]: value}) 28 | }, {}) 29 | } 30 | 31 | /** 32 | * Convert args that graphql know to the args that mongoose know 33 | * so that the args can be used by mongoose to find or create 34 | */ 35 | export function toMongooseArgs (args) { 36 | // Covert name_first to name: {first} 37 | let keyDepth = [] 38 | return Object.entries(args).reduce((args, [key, value]) => { 39 | keyDepth = key.split('_') 40 | if (keyDepth.length === 1) return Object.assign(args, {[key]: value}) 41 | keyDepth.reduce((args, depth, index) => { 42 | if (index === keyDepth.length - 1) { 43 | args[depth] = value 44 | return 45 | } 46 | args[depth] = args[depth] || {} 47 | return args[depth] 48 | }, args) 49 | return args 50 | }, {}) 51 | } 52 | 53 | /** 54 | * Giving an object and a string, pick out wanted data 55 | * e.g. user { id: 'test' } and user.id => 'test' 56 | */ 57 | export function pickoutValue (target, str) { 58 | const strDepth = str.split('.') 59 | const newTarget = target[strDepth[0]] 60 | if (newTarget === undefined) throw new Error('Cannot find the value') 61 | if (strDepth.length === 1) return newTarget 62 | return pickoutValue(newTarget, strDepth.splice(1).join('.')) 63 | } 64 | -------------------------------------------------------------------------------- /test/end2end.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import request from 'supertest-as-promised' 3 | import mongoose from 'mongoose' 4 | 5 | import { UserModel } from './fixture/userModel' 6 | import bootServer from './fixture/testServer' 7 | 8 | let app 9 | test.before(async (t) => { 10 | mongoose.connect('mongodb://localhost/test') 11 | app = await bootServer() 12 | }) 13 | 14 | test('should create and query when giving correct params', async t => { 15 | let userId 16 | await request(app) 17 | .post('/graphql') 18 | .set('content-type', 'application/json') 19 | .send({ 20 | query: `mutation create { 21 | user: createUser( 22 | userName: "wzx" 23 | hobbies: ["basketball", "travelling"] 24 | ) { 25 | id, 26 | userName 27 | hobbies 28 | } 29 | }` 30 | }) 31 | .expect(200) 32 | .then(res => { 33 | userId = res.body.data.user.id 34 | t.not(res.body.data.user.id, undefined) 35 | t.is(res.body.data.user.userName, 'wzx') 36 | t.deepEqual(res.body.data.user.hobbies, ['basketball', 'travelling']) 37 | }) 38 | 39 | await request(app) 40 | .get(`/graphql?query={ user(id: "${userId}") { id } }`) 41 | .expect(200) 42 | .then(res => { 43 | t.is(res.body.data.user[0].id, userId) 44 | }) 45 | 46 | await request(app) 47 | .post('/graphql') 48 | .set('content-type', 'application/json') 49 | .send({ 50 | query: `mutation delete { 51 | deleteUser( 52 | id: "${userId}" 53 | ) { 54 | success, 55 | msg 56 | } 57 | }` 58 | }) 59 | .expect(200) 60 | .then(res => { 61 | t.true(res.body.data.deleteUser.success) 62 | }) 63 | }) 64 | 65 | test('should able to create custom query or mutation', async t => { 66 | let userId 67 | await request(app) 68 | .post('/graphql') 69 | .set('content-type', 'application/json') 70 | .send({ 71 | query: `mutation customCreate { 72 | user: customAddUser( 73 | userName: "wzx" 74 | hobbies: ["basketball", "travelling"] 75 | age: 10 76 | ) { 77 | id, 78 | userName 79 | hobbies 80 | age 81 | } 82 | }` 83 | }) 84 | .expect(200) 85 | .then(res => { 86 | userId = res.body.data.user.id 87 | t.not(res.body.data.user.id, undefined) 88 | t.is(res.body.data.user.userName, 'wzx') 89 | t.deepEqual(res.body.data.user.hobbies, ['basketball', 'travelling']) 90 | t.is(res.body.data.user.age, 10) 91 | }) 92 | await UserModel.findByIdAndRemove(userId) 93 | }) 94 | -------------------------------------------------------------------------------- /test/fixture/schoolModel.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from 'mongoose' 2 | import { 3 | GraphQLObjectType, 4 | GraphQLString, 5 | GraphQLFloat, 6 | GraphQLID 7 | } from 'graphql/type' 8 | 9 | /* Mongoose School model */ 10 | const schoolSchema = new Schema({ 11 | name: { type: String, required: 'name is required' }, 12 | principal: { 13 | type: String, 14 | ref: 'User', 15 | required: true, 16 | context: 'user.id' 17 | }, 18 | position: String, 19 | students: Number 20 | }) 21 | 22 | export const SchoolModel = mongoose.model('School', schoolSchema) 23 | 24 | /* Graphql School type */ 25 | export const schoolType = new GraphQLObjectType({ 26 | name: 'School', 27 | fields: () => ({ 28 | id: { type: GraphQLID }, 29 | name: { type: GraphQLString, required: true }, 30 | principal: { type: GraphQLID, ref: 'User', required: true, context: 'user.id' }, 31 | position: { type: GraphQLString }, 32 | students: { type: GraphQLFloat } 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /test/fixture/testServer.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import graphqlHTTP from 'express-graphql' 3 | import { 4 | GraphQLString, 5 | GraphQLList, 6 | GraphQLNonNull, 7 | GraphQLFloat 8 | } from 'graphql' 9 | 10 | import mooseql, { buildTypes } from '../../src' 11 | import { UserModel } from './userModel' 12 | import { SchoolModel } from './schoolModel' 13 | 14 | export default function () { 15 | return new Promise((resolve, reject) => { 16 | const app = express() 17 | const typeMap = buildTypes([UserModel, SchoolModel]) 18 | const mySchema = mooseql([UserModel, SchoolModel], { 19 | mutation: { 20 | customAddUser: { 21 | type: typeMap['User'], 22 | args: { 23 | userName: { type: new GraphQLNonNull(GraphQLString) }, 24 | hobbies: { type: new GraphQLNonNull(new GraphQLList(GraphQLString)) }, 25 | age: { type: new GraphQLNonNull(GraphQLFloat) } 26 | }, 27 | resolve: async (_, args) => { 28 | const instance = new UserModel(args) 29 | return await instance.save() 30 | } 31 | } 32 | } 33 | }) 34 | 35 | app.use('/graphql', graphqlHTTP({ 36 | schema: mySchema, 37 | graphiql: false 38 | })) 39 | app.listen(3000, (err) => { 40 | if (err) return reject(err) 41 | resolve(app) 42 | }) 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /test/fixture/userModel.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from 'mongoose' 2 | import { 3 | GraphQLObjectType, 4 | GraphQLString, 5 | GraphQLFloat, 6 | GraphQLBoolean, 7 | GraphQLID, 8 | GraphQLList 9 | } from 'graphql/type' 10 | import { 11 | GraphQLBuffer, 12 | GraphQLDate, 13 | GraphQLMixed 14 | } from '../../src/type/customType' 15 | import { schoolType } from './schoolModel' 16 | 17 | /* Mongoose User model */ 18 | const userSchema = new Schema({ 19 | name: { 20 | first: String, 21 | last: { 22 | fst: String, 23 | snd: Number 24 | } 25 | }, 26 | userName: { type: String, required: [true, 'userName is required'] }, 27 | age: Number, 28 | isBot: Boolean, 29 | birth: { type: Date, default: Date.now }, 30 | binary: Buffer, 31 | info: Schema.Types.Mixed, 32 | hobbies: { type: [String], required: 'hobbies should have one at least' }, 33 | currentSchool: { type: Schema.Types.ObjectId, ref: 'School' }, 34 | education: [{ type: Schema.Types.ObjectId, ref: 'School' }] 35 | }) 36 | 37 | export const UserModel = mongoose.model('User', userSchema) 38 | 39 | /* Graphql User type */ 40 | export const userType = new GraphQLObjectType({ 41 | name: 'User', 42 | fields: () => ({ 43 | id: { type: GraphQLID }, 44 | name: { 45 | type: new GraphQLObjectType({ 46 | name: 'Username', 47 | fields: () => ({ 48 | first: { type: GraphQLString }, 49 | last: { 50 | type: new GraphQLObjectType({ 51 | name: 'Usernamelast', 52 | fields: () => ({ 53 | fst: { type: GraphQLString }, 54 | snd: { type: GraphQLFloat } 55 | }) 56 | }) 57 | } 58 | }) 59 | }) 60 | }, 61 | userName: { type: GraphQLString, required: true }, 62 | age: { type: GraphQLFloat }, 63 | isBot: { type: GraphQLBoolean }, 64 | birth: { type: GraphQLDate }, 65 | binary: { type: GraphQLBuffer }, 66 | info: { type: GraphQLMixed }, 67 | hobbies: { type: new GraphQLList(GraphQLString), required: true }, 68 | currentSchool: { type: schoolType, resolve: () => {} }, 69 | education: { type: new GraphQLList(schoolType), resolve: () => {} } 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /test/schema.args.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { userType } from './fixture/userModel' 3 | import buildArgs from '../src/schema/buildArgs' 4 | 5 | let args 6 | test.before(t => { 7 | args = buildArgs(userType) 8 | }) 9 | 10 | test('it shold generate singular and plural args when giving a graphql type', t => { 11 | t.not(args.id, undefined) 12 | t.not(args.ids, undefined) 13 | t.not(args.name_first, undefined) 14 | t.not(args.name_firsts, undefined) 15 | t.not(args.name_last_fst, undefined) 16 | t.not(args.name_last_fsts, undefined) 17 | t.not(args.name_last_snd, undefined) 18 | t.not(args.name_last_snds, undefined) 19 | t.not(args.userName, undefined) 20 | t.not(args.userNames, undefined) 21 | t.not(args.age, undefined) 22 | t.not(args.ages, undefined) 23 | t.not(args.isBot, undefined) 24 | t.not(args.isBots, undefined) 25 | t.not(args.birth, undefined) 26 | t.not(args.births, undefined) 27 | t.not(args.binary, undefined) 28 | t.not(args.binaries, undefined) 29 | t.not(args.hobbies, undefined) 30 | t.deepEqual(args.hobby, undefined) 31 | t.not(args.currentSchool, undefined) 32 | t.not(args.currentSchools, undefined) 33 | t.not(args.education, undefined) 34 | t.deepEqual(args.educations, undefined) 35 | }) 36 | 37 | test('should label onlyPlural to those path what only have plural', t => { 38 | t.falsy(args.id.onlyPlural) 39 | t.true(args.hobbies.onlyPlural) 40 | t.true(args.education.onlyPlural) 41 | }) 42 | 43 | test('should label required: true to those required path', t => { 44 | t.falsy(args.id.required) 45 | t.true(args.userName.required) 46 | t.falsy(args.userNames.required) 47 | t.true(args.hobbies.required) 48 | }) 49 | -------------------------------------------------------------------------------- /test/schema.mutation.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import mongoose from 'mongoose' 3 | import { graphql, GraphQLObjectType, GraphQLSchema } from 'graphql' 4 | 5 | import { UserModel } from './fixture/userModel' 6 | import { SchoolModel } from './fixture/schoolModel' 7 | import { modelsToTypes } from '../src/type' 8 | import buildMutation from '../src/schema/buildMutation' 9 | 10 | let school 11 | let school2 12 | let user 13 | let userSchema 14 | let schoolSchema 15 | test.before(async t => { 16 | mongoose.connect('mongodb://localhost/test') 17 | 18 | const typeMap = modelsToTypes([UserModel, SchoolModel]) 19 | const userMutationField = buildMutation(UserModel, typeMap['User']) 20 | const schoolMutationField = buildMutation(SchoolModel, typeMap['School']) 21 | userSchema = new GraphQLSchema({ 22 | query: new GraphQLObjectType({ 23 | name: 'Query', 24 | fields: { user: { type: typeMap['User'] } } 25 | }), 26 | mutation: new GraphQLObjectType({ 27 | name: 'Mutation', 28 | fields: userMutationField 29 | }) 30 | }) 31 | schoolSchema = new GraphQLSchema({ 32 | query: new GraphQLObjectType({ 33 | name: 'Query', 34 | fields: { user: { type: typeMap['School'] } } 35 | }), 36 | mutation: new GraphQLObjectType({ 37 | name: 'Mutation', 38 | fields: schoolMutationField 39 | }) 40 | }) 41 | school = new SchoolModel({ 42 | name: 'universary', 43 | principal: 'wayne', 44 | position: 'sh', 45 | students: 100 46 | }) 47 | school2 = new SchoolModel({ 48 | name: 'highschool', 49 | principal: 'wayne', 50 | position: 'zh', 51 | students: 10 52 | }) 53 | user = new UserModel({ 54 | userName: 'wayne', 55 | name: { first: 'wang' }, 56 | age: 26, 57 | isBot: false, 58 | birth: new Date(1990, 7, 21), 59 | binary: new Buffer('wzx'), 60 | info: { any: { thing: 'foo' } }, 61 | hobbies: ['basketball', 'travelling'], 62 | currentSchool: school.id, 63 | education: [school.id] 64 | }) 65 | await user.save() 66 | await school.save() 67 | await school2.save() 68 | }) 69 | 70 | test.after(async t => { 71 | await UserModel.findByIdAndRemove(user.id) 72 | await SchoolModel.find({ _id: {$in: [school.id, school2.id]} }).remove() 73 | }) 74 | 75 | test('should create with any mongoose path when giving valid type', async t => { 76 | const queryRes = await graphql( 77 | userSchema, 78 | `mutation addUser { 79 | user: createUser( 80 | userName: "wzx", 81 | name_first: "wang", 82 | age: 26, 83 | isBot: false, 84 | birth: "${new Date(1990, 7, 21)}", 85 | binary: "${new Buffer('wzx')}", 86 | hobbies:["coding"], 87 | currentSchool: "${school.id}", 88 | education: ["${school.id}"] 89 | ) { 90 | id, 91 | userName, 92 | name { 93 | first 94 | }, 95 | age, 96 | isBot, 97 | birth, 98 | binary 99 | hobbies, 100 | currentSchool { 101 | id, 102 | name 103 | }, 104 | education { 105 | id 106 | } 107 | } 108 | }` 109 | ) 110 | const userData = queryRes.data.user 111 | t.is(userData.userName, 'wzx') 112 | t.is(userData.name.first, 'wang') 113 | t.is(userData.age, 26) 114 | t.false(userData.isBot) 115 | t.deepEqual(userData.birth, new Date(1990, 7, 21)) 116 | t.deepEqual(userData.binary, new Buffer('wzx')) 117 | t.deepEqual(userData.hobbies, ['coding']) 118 | t.is(userData.currentSchool.id, school.id) 119 | t.is(userData.currentSchool.name, school.name) 120 | t.is(userData.education[0].id, school.id) 121 | await UserModel.findByIdAndRemove(userData.id) 122 | }) 123 | 124 | test('should use correct data when context option is setting', async t => { 125 | const user = new UserModel({ 126 | userName: 'wayne', 127 | hobbies: ['basketball', 'travelling'] 128 | }) 129 | await user.save() 130 | const queryRes = await graphql( 131 | schoolSchema, 132 | `mutation add { 133 | school: createSchool ( 134 | name: "aSchool" 135 | ) { 136 | id 137 | principal { 138 | id 139 | userName 140 | } 141 | } 142 | } `, 143 | { rootValue: null }, 144 | { user: user } 145 | ) 146 | const data = queryRes.data.school 147 | t.is(data.principal.id, user.id) 148 | t.is(data.principal.userName, user.userName) 149 | await UserModel.findByIdAndRemove(user.id) 150 | await SchoolModel.findByIdAndRemove(data.id) 151 | }) 152 | 153 | test('should return error if context option not appear in the request context', async t => { 154 | const queryRes = await graphql( 155 | schoolSchema, 156 | `mutation add { 157 | school: createSchool ( 158 | name: "aSchool" 159 | ) { 160 | id 161 | principal { 162 | id 163 | userName 164 | } 165 | } 166 | } `, 167 | { rootValue: null }, 168 | { context: null } 169 | ) 170 | t.regex(queryRes.errors[0]['message'], /principal\D+required/) 171 | }) 172 | 173 | test('should update a single document attribute when giving valid id', async t => { 174 | const queryRes = await graphql( 175 | userSchema, 176 | `mutation update { 177 | user: updateUser ( 178 | id: "${user.id}", 179 | userName: "wwayne", 180 | name_first: "firstModified", 181 | hobbies: ["no", "no1"], 182 | currentSchool: "${school2.id}" 183 | ) { 184 | id, 185 | userName, 186 | name { 187 | first 188 | }, 189 | age, 190 | isBot, 191 | birth, 192 | binary 193 | hobbies, 194 | currentSchool { 195 | id, 196 | name 197 | }, 198 | education { 199 | id 200 | } 201 | } 202 | }` 203 | ) 204 | const userData = queryRes.data.user 205 | t.is(userData.userName, 'wwayne') 206 | t.is(userData.name.first, 'firstModified') 207 | t.deepEqual(userData.hobbies, ['no', 'no1']) 208 | t.is(userData.currentSchool.id, school2.id) 209 | t.is(userData.currentSchool.name, school2.name) 210 | }) 211 | 212 | test('should delete a document when giving valid id', async t => { 213 | const user = new UserModel({ 214 | userName: 'wayne', 215 | hobbies: ['basketball', 'travelling'] 216 | }) 217 | const queryRes = await graphql( 218 | userSchema, 219 | `mutation update { 220 | deleteUser ( 221 | id: "${user.id}" 222 | ) { 223 | success, 224 | msg 225 | } 226 | }` 227 | ) 228 | t.true(queryRes.data.deleteUser.success) 229 | t.is(queryRes.data.deleteUser.msg, null) 230 | }) 231 | 232 | test('should response with success false when giving invalid id for deleting', async t => { 233 | const queryRes = await graphql( 234 | userSchema, 235 | `mutation delete { 236 | deleteUser ( 237 | id: "57d79c1150e3ffd8adee7fxz" 238 | ) { 239 | success, 240 | msg 241 | } 242 | }` 243 | ) 244 | t.false(queryRes.data.deleteUser.success) 245 | t.not(queryRes.data.deleteUser.msg, null) 246 | }) 247 | 248 | -------------------------------------------------------------------------------- /test/schema.query.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { graphql, GraphQLObjectType, GraphQLSchema } from 'graphql' 3 | import mongoose from 'mongoose' 4 | 5 | import { UserModel } from './fixture/userModel' 6 | import { SchoolModel } from './fixture/schoolModel' 7 | import { modelsToTypes } from '../src/type' 8 | import buildQuery from '../src/schema/buildQuery' 9 | 10 | let school 11 | let user 12 | let user2 13 | let userSchema 14 | test.before(async t => { 15 | mongoose.connect('mongodb://localhost/test') 16 | 17 | const typeMap = modelsToTypes([UserModel, SchoolModel]) 18 | const userQueryField = buildQuery(UserModel, typeMap['User']) 19 | userSchema = new GraphQLSchema({ 20 | query: new GraphQLObjectType({ 21 | name: 'RootQuery', 22 | fields: userQueryField 23 | }) 24 | }) 25 | school = new SchoolModel({ 26 | name: 'universary', 27 | principal: 'wayne', 28 | position: 'sh', 29 | students: 100 30 | }) 31 | user = new UserModel({ 32 | userName: 'wayne', 33 | name: { first: 'wang' }, 34 | age: 26, 35 | isBot: false, 36 | birth: new Date(1990, 7, 21), 37 | binary: new Buffer('wzx'), 38 | info: { any: { thing: 'foo' } }, 39 | hobbies: ['basketball', 'travelling'], 40 | currentSchool: school.id, 41 | education: [school.id] 42 | }) 43 | user2 = new UserModel({ 44 | userName: 'jtc', 45 | name: { first: 'cheng' }, 46 | age: 24.5, 47 | isBot: false, 48 | birth: new Date(1992, 3, 3), 49 | binary: new Buffer('cjt'), 50 | info: 'anything', 51 | hobbies: ['drawing', 'travelling'], 52 | currentSchool: school.id, 53 | education: [school.id] 54 | }) 55 | await user.save() 56 | await user2.save() 57 | await school.save() 58 | }) 59 | 60 | test.after(async t => { 61 | await UserModel.find({ _id: { $in: [user.id, user2.id] } }).remove() 62 | await SchoolModel.findByIdAndRemove(school.id) 63 | }) 64 | 65 | test('should support find by id for each every model', async t => { 66 | const queryRes = await graphql(userSchema, `{ user(id: "${user.id}") { id } }`) 67 | t.is(queryRes.data.user[0].id, user.id) 68 | }) 69 | test('should support find a group of instance by ids for every model', async t => { 70 | const queryRes = await graphql(userSchema, `{ user(ids: ["${user.id}", "${user2.id}"]) { id } }`) 71 | t.is(queryRes.data.user[0].id, user.id) 72 | t.is(queryRes.data.user[1].id, user2.id) 73 | }) 74 | test('should support find by Object attrube of the origin model', async t => { 75 | const queryRes = await graphql(userSchema, `{ user(name_first: "${user.name.first}") { name { first } } }`) 76 | t.is(queryRes.data.user[0].name.first, user.name.first) 77 | }) 78 | test('should support find a group of Object attrube of the origin model', async t => { 79 | const queryRes = await graphql(userSchema, `{ user(name_firsts: ["${user.name.first}", "${user2.name.first}"]) { name { first } } }`) 80 | t.is(queryRes.data.user[0].name.first, user.name.first) 81 | t.is(queryRes.data.user[1].name.first, user2.name.first) 82 | }) 83 | test('should support find by String attrube of the origin model', async t => { 84 | const queryRes = await graphql(userSchema, `{ user(userName: "${user.userName}") { userName } }`) 85 | t.is(queryRes.data.user[0].userName, user.userName) 86 | }) 87 | test('should support find a group of String attrube of the origin model', async t => { 88 | const queryRes = await graphql(userSchema, `{ user(userNames: ["${user.userName}", "${user2.userName}"]) { userName } }`) 89 | t.is(queryRes.data.user[0].userName, user.userName) 90 | t.is(queryRes.data.user[1].userName, user2.userName) 91 | }) 92 | test('should support find by Number attrube of the origin model', async t => { 93 | const queryRes = await graphql(userSchema, `{ user(age: ${user.age}) { age } }`) 94 | t.is(queryRes.data.user[0].age, user.age) 95 | }) 96 | test('should support find a group of Number attrube of the origin model', async t => { 97 | const queryRes = await graphql(userSchema, `{ user(ages: [${user.age}, ${user2.age}]) { age } }`) 98 | t.is(queryRes.data.user[0].age, user.age) 99 | t.is(queryRes.data.user[1].age, user2.age) 100 | }) 101 | test('should support find by Bool attrube of the origin model', async t => { 102 | const queryRes = await graphql(userSchema, `{ user(isBot: ${user.isBot}) { isBot } }`) 103 | t.is(queryRes.data.user[0].isBot, user.isBot) 104 | }) 105 | test('should support find a group of Bool attribute of the origin model', async t => { 106 | const queryRes = await graphql(userSchema, `{ user(isBots: [${user.isBot}, ${user2.isBot}]) { isBot } }`) 107 | t.is(queryRes.data.user[0].isBot, user.isBot) 108 | t.is(queryRes.data.user[1].isBot, user2.isBot) 109 | }) 110 | test('should support find by Date attrube of the origin model', async t => { 111 | const queryRes = await graphql(userSchema, `{ user(birth: "${user.birth}") { birth } }`) 112 | t.deepEqual(queryRes.data.user[0].birth, user.birth) 113 | }) 114 | test('should support find a group of Date attrube of the origin model', async t => { 115 | const queryRes = await graphql(userSchema, `{ user(births: ["${user.birth}", "${user2.birth}"]) { birth } }`) 116 | t.deepEqual(queryRes.data.user[0].birth, user.birth) 117 | t.deepEqual(queryRes.data.user[1].birth, user2.birth) 118 | }) 119 | test('should support find by Buffer attrube of the origin model', async t => { 120 | const queryRes = await graphql(userSchema, `{ user(binary: "${user.binary}") { binary } }`) 121 | t.deepEqual(queryRes.data.user[0].binary, user.binary) 122 | }) 123 | test('should support find a group of Buffer attrube of the origin model', async t => { 124 | const queryRes = await graphql(userSchema, `{ user(binaries: ["${user.binary}", "${user2.binary}"]) { binary } }`) 125 | t.deepEqual(queryRes.data.user[0].binary, user.binary) 126 | t.deepEqual(queryRes.data.user[1].binary, user2.binary) 127 | }) 128 | test('should support find by Array attrube of the origin model', async t => { 129 | const queryRes = await graphql(userSchema, `{ user(hobbies: ["${user.hobbies[0]}", "${user.hobbies[1]}"]) { hobbies } }`) 130 | t.is(queryRes.data.user[0].hobbies.length, user.hobbies.length) 131 | t.is(queryRes.data.user[0].hobbies[0], user.hobbies[0]) 132 | t.is(queryRes.data.user[0].hobbies[1], user.hobbies[1]) 133 | }) 134 | test('should support find by Ref ID attrube of the origin model', async t => { 135 | const queryRes = await graphql(userSchema, `{ user(currentSchool: "${user.currentSchool}") { currentSchool {id} } }`) 136 | t.is(queryRes.data.user[0].currentSchool.id.toString(), user.currentSchool.toString()) 137 | }) 138 | test('should support find a group of Ref ID attrube of the origin model', async t => { 139 | const queryRes = await graphql( 140 | userSchema, 141 | `{ user(currentSchools: ["${user.currentSchool}", "${user2.currentSchool}"]) { currentSchool {id} } }` 142 | ) 143 | t.is(queryRes.data.user[0].currentSchool.id.toString(), user.currentSchool.toString()) 144 | t.is(queryRes.data.user[1].currentSchool.id.toString(), user2.currentSchool.toString()) 145 | }) 146 | test('should support find by Array of Ref ID attrube of the origin model', async t => { 147 | const queryRes = await graphql(userSchema, `{ user(education: ["${user.education[0]}"]) { education {id} } }`) 148 | t.is(queryRes.data.user[0].education[0].id.toString(), user.education[0].toString()) 149 | }) 150 | test('should be able to query with a set of args when they are giving in valid format', async t => { 151 | const queryRes = await graphql( 152 | userSchema, 153 | `{ user(id: "${user.id}" name_first: "${user.name.first}" age: ${user.age} hobbies: ["${user.hobbies[0]}", "${user.hobbies[1]}"]) { id } }` 154 | ) 155 | t.is(queryRes.data.user[0].id, user.id) 156 | }) 157 | test('should response with errors when giving invalid params', async t => { 158 | const queryRes = await graphql(userSchema, `{ user(id: "true") { id } }`) 159 | t.not(queryRes.errors, undefined) 160 | }) 161 | test('should return all instance when args not provided', async t => { 162 | const queryRes = await graphql(userSchema, `{ user { id } }`) 163 | t.is(queryRes.data.user.length, 2) 164 | }) 165 | -------------------------------------------------------------------------------- /test/type.Buffer.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { GraphQLBuffer } from '../src/type/customType' 3 | import { graphql, GraphQLSchema, GraphQLObjectType } from 'graphql' 4 | 5 | const buffer = new Buffer('buffer') 6 | 7 | test('should handle GraphQLBuffer type', async t => { 8 | const schema = new GraphQLSchema({ 9 | query: new GraphQLObjectType({ 10 | name: 'RootQuery', 11 | fields: { 12 | foo: { 13 | type: GraphQLBuffer, 14 | resolve: () => buffer 15 | } 16 | } 17 | }) 18 | }) 19 | const queryRes = await graphql(schema, `{ foo }`) 20 | t.deepEqual(queryRes, { data: { foo: buffer } }) 21 | }) 22 | 23 | test('should return error if type not conform to GraphQLBuffer', async t => { 24 | const schema = new GraphQLSchema({ 25 | query: new GraphQLObjectType({ 26 | name: 'RootQuery', 27 | fields: { 28 | foo: { 29 | type: GraphQLBuffer, 30 | resolve: () => 'string' 31 | } 32 | } 33 | }) 34 | }) 35 | const queryRes = await graphql(schema, `{ foo }`) 36 | t.not(queryRes.errors, undefined) 37 | }) 38 | 39 | test('should be able to use GraphQLBuffer as args type', async t => { 40 | const schema = new GraphQLSchema({ 41 | query: new GraphQLObjectType({ 42 | name: 'RootQuery', 43 | fields: { 44 | foo: { 45 | type: GraphQLBuffer, 46 | args: { 47 | bar: { type: GraphQLBuffer } 48 | }, 49 | resolve: (_, { bar }) => buffer 50 | } 51 | } 52 | }) 53 | }) 54 | const queryRes = await graphql(schema, `{ foo(bar: ${new Buffer('bar')}) }`) 55 | t.deepEqual(queryRes, { data: { foo: buffer } }) 56 | }) 57 | 58 | test('should handle null value under GraphQLBuffer', async t => { 59 | const schema = new GraphQLSchema({ 60 | query: new GraphQLObjectType({ 61 | name: 'RootQuery', 62 | fields: { 63 | foo: { 64 | type: GraphQLBuffer, 65 | resolve: () => null 66 | } 67 | } 68 | }) 69 | }) 70 | const queryRes = await graphql(schema, `{ foo }`) 71 | t.deepEqual(queryRes, { data: { foo: null } }) 72 | }) 73 | -------------------------------------------------------------------------------- /test/type.Date.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { GraphQLDate } from '../src/type/customType' 3 | import { graphql, GraphQLSchema, GraphQLObjectType } from 'graphql' 4 | 5 | const date = new Date() 6 | 7 | test('should handle GraphQLDate type', async t => { 8 | const schema = new GraphQLSchema({ 9 | query: new GraphQLObjectType({ 10 | name: 'RootQuery', 11 | fields: { 12 | foo: { 13 | type: GraphQLDate, 14 | resolve: () => date 15 | } 16 | } 17 | }) 18 | }) 19 | const queryRes = await graphql(schema, `{ foo }`) 20 | t.deepEqual(queryRes, { data: { foo: date } }) 21 | }) 22 | 23 | test('should return error if type not conform to GraphQLDate', async t => { 24 | const schema = new GraphQLSchema({ 25 | query: new GraphQLObjectType({ 26 | name: 'RootQuery', 27 | fields: { 28 | foo: { 29 | type: GraphQLDate, 30 | resolve: () => 123 31 | } 32 | } 33 | }) 34 | }) 35 | const queryRes = await graphql(schema, `{ foo }`) 36 | t.not(queryRes.errors, undefined) 37 | }) 38 | 39 | test('should be able to use GraphQLDate as args type', async t => { 40 | const schema = new GraphQLSchema({ 41 | query: new GraphQLObjectType({ 42 | name: 'RootQuery', 43 | fields: { 44 | foo: { 45 | type: GraphQLDate, 46 | args: { 47 | bar: { type: GraphQLDate } 48 | }, 49 | resolve: (_, { bar }) => date 50 | } 51 | } 52 | }) 53 | }) 54 | const queryRes = await graphql(schema, `{ foo(bar: "${new Date()}") }`) 55 | t.deepEqual(queryRes, { data: { foo: date } }) 56 | }) 57 | 58 | test('should handle null value under GraphQLDate', async t => { 59 | const schema = new GraphQLSchema({ 60 | query: new GraphQLObjectType({ 61 | name: 'RootQuery', 62 | fields: { 63 | foo: { 64 | type: GraphQLDate, 65 | resolve: () => null 66 | } 67 | } 68 | }) 69 | }) 70 | const queryRes = await graphql(schema, `{ foo }`) 71 | t.deepEqual(queryRes, { data: { foo: null } }) 72 | }) 73 | -------------------------------------------------------------------------------- /test/type.Mixed.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { GraphQLMixed } from '../src/type/customType' 3 | import { graphql, GraphQLSchema, GraphQLObjectType } from 'graphql' 4 | 5 | const mixed = {any: { thing: 'mix' }} 6 | 7 | test('should handle anything as GraphQLMixed type', async t => { 8 | const schema = new GraphQLSchema({ 9 | query: new GraphQLObjectType({ 10 | name: 'RootQuery', 11 | fields: { 12 | foo: { 13 | type: GraphQLMixed, 14 | resolve: () => mixed 15 | } 16 | } 17 | }) 18 | }) 19 | const queryRes = await graphql(schema, `{ foo }`) 20 | t.deepEqual(queryRes, { data: { foo: mixed } }) 21 | }) 22 | 23 | test('should handle null value under GraphQLMixed', async t => { 24 | const schema = new GraphQLSchema({ 25 | query: new GraphQLObjectType({ 26 | name: 'RootQuery', 27 | fields: { 28 | foo: { 29 | type: GraphQLMixed, 30 | resolve: () => null 31 | } 32 | } 33 | }) 34 | }) 35 | const queryRes = await graphql(schema, `{ foo }`) 36 | t.deepEqual(queryRes, { data: { foo: null } }) 37 | }) 38 | -------------------------------------------------------------------------------- /test/type.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { GraphQLList } from 'graphql/type' 3 | import { graphql, GraphQLObjectType, GraphQLSchema } from 'graphql' 4 | import mongoose from 'mongoose' 5 | 6 | import { UserModel, userType } from './fixture/userModel' 7 | import { SchoolModel, schoolType } from './fixture/schoolModel' 8 | import { modelsToTypes } from '../src/type' 9 | 10 | let typeMap 11 | let school 12 | let user 13 | test.before(async t => { 14 | mongoose.connect('mongodb://localhost/test') 15 | 16 | typeMap = modelsToTypes([UserModel, SchoolModel]) 17 | school = new SchoolModel({ 18 | name: 'universary', 19 | principal: 'wayne', 20 | position: 'sh', 21 | students: 100 22 | }) 23 | await school.save() 24 | user = new UserModel({ 25 | name: { 26 | first: 'wang', 27 | last: { 28 | fst: 'zi', 29 | snd: 21 30 | } 31 | }, 32 | userName: 'wayne', 33 | age: 26, 34 | isBot: false, 35 | birthe: new Date(1990, 7, 21), 36 | binary: new Buffer('wzx'), 37 | info: { any: { thing: 'foo' } }, 38 | hobbies: ['basketball', 'traviling'], 39 | currentSchool: school.id, 40 | education: [school.id] 41 | }) 42 | await SchoolModel.findByIdAndUpdate(school.id, { principal: user.id }) 43 | }) 44 | 45 | test.after(async t => { 46 | await SchoolModel.findByIdAndRemove(school.id) 47 | }) 48 | 49 | test('Mongoose model should be converted to graphql type correctly', t => { 50 | const convertedUserFields = typeMap['User']._typeConfig.fields() 51 | const userFields = userType._typeConfig.fields() 52 | 53 | t.is(Object.keys(typeMap).length, 2) 54 | t.is(typeMap['User'].name, userType.name) 55 | t.deepEqual(convertedUserFields.id, userFields.id) 56 | t.deepEqual(convertedUserFields.userName, userFields.userName) 57 | t.deepEqual(convertedUserFields.name.type._typeConfig.fields().first, userFields.name.type._typeConfig.fields().first) 58 | t.deepEqual(convertedUserFields.age, userFields.age) 59 | t.deepEqual(convertedUserFields.isBot, userFields.isBot) 60 | t.deepEqual(convertedUserFields.birth, userFields.birth) 61 | t.deepEqual(convertedUserFields.binary, userFields.binary) 62 | t.deepEqual(convertedUserFields.info, userFields.info) 63 | t.deepEqual(convertedUserFields.hobbies, userFields.hobbies) 64 | t.deepEqual(convertedUserFields.currentSchool.type.name, userFields.currentSchool.type.name) 65 | t.true(convertedUserFields.education.type instanceof GraphQLList) 66 | t.deepEqual(convertedUserFields.education.type.ofType.name, userFields.education.type.ofType.name) 67 | }) 68 | 69 | test('Special params should be converted from model to type', t => { 70 | const convertedSchoolFields = typeMap['School']._typeConfig.fields() 71 | const schoolFields = schoolType._typeConfig.fields() 72 | t.is(convertedSchoolFields.principal.required, schoolFields.principal.required) 73 | t.is(convertedSchoolFields.principal.context, schoolFields.principal.context) 74 | }) 75 | 76 | test('Mongoose model should be accessable to query after converting', async t => { 77 | const schema = new GraphQLSchema({ 78 | query: new GraphQLObjectType({ 79 | name: 'RootQuery', 80 | fields: { 81 | user: { 82 | type: typeMap['User'], 83 | resolve: () => user 84 | } 85 | } 86 | }) 87 | }) 88 | const queryRes = await graphql(schema, `{ 89 | user { 90 | id, 91 | name { 92 | first, 93 | last { 94 | fst, 95 | snd 96 | } 97 | } 98 | userName, 99 | age, 100 | isBot, 101 | birth, 102 | binary, 103 | info, 104 | hobbies, 105 | currentSchool { 106 | id, 107 | name 108 | }, 109 | education { 110 | id 111 | } 112 | } 113 | }`) 114 | const userRes = queryRes.data.user 115 | t.is(userRes.id, user.id) 116 | t.is(userRes.userName, user.userName) 117 | t.is(userRes.name.first, user.name.first) 118 | t.is(userRes.name.last.fst, user.name.last.fst) 119 | t.is(userRes.name.last.snd, user.name.last.snd) 120 | t.is(userRes.age, user.age) 121 | t.is(userRes.isBot, user.isBot) 122 | t.is(userRes.birth, user.birth) 123 | t.is(userRes.binary, user.binary) 124 | t.deepEqual(userRes.info, user.info) 125 | t.deepEqual(userRes.hobbies, ['basketball', 'traviling']) // Object.keys diff 126 | t.is(userRes.currentSchool.id, school.id) 127 | t.is(userRes.currentSchool.name, school.name) 128 | t.is(userRes.education[0].id, school.id) 129 | }) 130 | --------------------------------------------------------------------------------