├── .editorconfig ├── .gitignore ├── .node-version ├── .npmignore ├── .travis.yml ├── README.md ├── bin └── cli.js ├── index.js ├── package-lock.json ├── package.json ├── src ├── aqtToQuery.ts ├── cli │ ├── cli.js │ └── retryHelper.js ├── coverageCalculator.ts ├── descriptionParser.ts ├── graphqlClient.ts ├── introspectionQuery.ts ├── queryGenerator.ts ├── reporters │ └── teamcity.js ├── schemaToQueries.ts └── schemaToQueryTree.ts ├── test ├── acceptance │ ├── commentTests.js │ └── exampleServer.js └── unit │ ├── mockData.js │ └── schemaToQueryTree │ ├── schemaToQueryTreeBase.test.js │ └── schemaToQueryTreeCodeCoverage.test.js └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | insert_final_newline=false 5 | indent_style=space 6 | indent_size=2 7 | 8 | [{.eslintrc,.babelrc,.stylelintrc,*.json,*.jsb3,*.jsb2,*.bowerrc}] 9 | indent_style=space 10 | indent_size=2 11 | 12 | [{.analysis_options,*.yml,*.yaml}] 13 | indent_style=space 14 | indent_size=2 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode/ 3 | .c9/ 4 | .idea/ 5 | dist/ 6 | lib/ 7 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 6.x.x -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode/ 3 | .c9/ 4 | .idea/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | matrix: 4 | include: 5 | - node_js: "6" 6 | 7 | before_script: 8 | - npm run build-ts 9 | 10 | script: 11 | - npm test 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build](https://api.travis-ci.org/opentable/graphql-query-generator.svg?branch=master)](https://travis-ci.org/opentable/graphql-query-generator) 2 | 3 | **This repo is no longer maintained**. Feel free to fork and enjoy it, but no further development is anticipated. 4 | 5 | # GraphQL Query Generator 6 | GraphQL Query Generator is a library/tool that helps you easily test your GraphQL endpoints using introspection! 7 | 8 | ## Getting Started 9 | So you want to test your GraphQL endpoint. This tool will generate all the queries that your GraphQL endpoint will have. However, for queries that require parameters, this tool will need annotations. So please follow the steps below to get started. 10 | 11 | ### 1. Annotate your queries (optional, although highly recommended): 12 | Create example queries that you want tested in the comments! 13 | ```graphql 14 | type Query { 15 | # RollDice has four examples 16 | # 17 | # Examples: 18 | # rollDice(numDice: 4, numSides: 2) 19 | # rollDice( numDice : 40 , numSides:2) 20 | # rollDice ( numDice: 2, numSides: 299 ) 21 | # rollDice ( 22 | # numDice:4, 23 | # numSides: 2342 24 | # ) 25 | rollDice(numDice: Int!, numSides: Int): RandomDie 26 | } 27 | ``` 28 | 29 | ### 2 Run the tool! 30 | 31 | You can use either the CLI or the library to get started! 32 | 33 | #### 2.1 Using the CLI 34 | 35 | Execute following commands to get this tool running. 36 | > NOTE: Whenever there are parameters required you need to provide them in Graphql schema by following our Examples notation. You can find it in [Usage](#1-annotate-your-queries-optional-although-highly-recommended) section. 37 | 38 | ``` 39 | npm i -g graphql-query-generator 40 | gql-test http://: 41 | gql-test --help # for more information 42 | ``` 43 | 44 | #### 2.2 Using the library 45 | If you want more control over the queries that are generated via this tool. Please see the following example: 46 | 47 | ```javascript 48 | const QueryGenerator = require('graphql-query-generator'); 49 | const request = require('request'); 50 | const assert = require('assert'); 51 | 52 | describe('Query generation', function() { 53 | const serverUrl = 'http://:/graphql'; 54 | let queries = null; 55 | 56 | before(() => { 57 | const queryGenerator = new QueryGenerator(serverUrl); 58 | queryPromise = queryGenerator.run(); 59 | }); 60 | 61 | it('Generates multiple queries', function() { 62 | this.timeout = 50000; 63 | 64 | return queryPromise 65 | .then(({queries, coverage}) =>{ 66 | console.log(`Coverage: ${coverage.coverageRatio}`); 67 | console.log(`skipped fields: ${coverage.notCoveredFields}`); 68 | return Promise.all(queries.map(query => requestToGraphQL(serverUrl, query))); 69 | }) 70 | .then(results => assert.equal(results.filter(x => x.statusCode !== 200).length, 0)); 71 | }); 72 | }); 73 | 74 | function requestToGraphQL(serverUrl, query) { 75 | return new Promise((resolve, reject) => { 76 | request(serverUrl, { 77 | method: 'POST', 78 | headers: { 79 | 'Content-Type': 'application/json' 80 | }, 81 | body:JSON.stringify({ 82 | "query": query, 83 | "variables": "{}", 84 | "operationName": null 85 | }) 86 | }, function (err, result) { 87 | if (err) return reject(err); 88 | 89 | resolve(result) 90 | }); 91 | }); 92 | } 93 | ``` 94 | 95 | This is an example of a test that will just check that it returns HTTP status code 200! It would be also good to check if, say, the body contains an error section. However, it's all up to you! 96 | 97 | 98 | ## Extras 99 | 100 | ### Opt out of certain queries 101 | 102 | When annotating, if you add `+NOFOLLOW` in examples will prevent this path from being followed when creating queries 103 | 104 | ```graphql 105 | type RandomDie { 106 | numSides: Int! 107 | rollOnce: Int! 108 | statistics(page: Int!): RandomnessStatistics! 109 | 110 | # A description for ignored field with parameters 111 | # 112 | # Examples: 113 | # ignoredWithExamples(parameter: 42) 114 | # +NOFOLLOW 115 | ignoredWithExamples(parameter: Int!): IgnoredSubtype 116 | 117 | # +NOFOLLOW 118 | ignoredNoParameters: IgnoredSubtype 119 | } 120 | ``` 121 | 122 | ## Contributing 123 | We welcome feedback! Please create an issue for feedback or issues. If you would like to contribute, open a PR and let's start talking! 124 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/cli/cli.js'); 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/queryGenerator'); 2 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-query-generator", 3 | "version": "0.5.5", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "8.9.5", 9 | "resolved": "http://registry.npmjs.org/@types/node/-/node-8.9.5.tgz", 10 | "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==", 11 | "dev": true 12 | }, 13 | "abbrev": { 14 | "version": "1.1.1", 15 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 16 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 17 | "dev": true 18 | }, 19 | "accepts": { 20 | "version": "1.3.5", 21 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 22 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 23 | "dev": true, 24 | "requires": { 25 | "mime-types": "2.1.18", 26 | "negotiator": "0.6.1" 27 | } 28 | }, 29 | "amdefine": { 30 | "version": "1.0.1", 31 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", 32 | "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", 33 | "dev": true 34 | }, 35 | "ansi-regex": { 36 | "version": "2.1.1", 37 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 38 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 39 | }, 40 | "ansi-styles": { 41 | "version": "2.2.1", 42 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 43 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" 44 | }, 45 | "argparse": { 46 | "version": "1.0.10", 47 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 48 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 49 | "dev": true, 50 | "requires": { 51 | "sprintf-js": "1.0.3" 52 | } 53 | }, 54 | "array-flatten": { 55 | "version": "1.1.1", 56 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 57 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", 58 | "dev": true 59 | }, 60 | "assertion-error": { 61 | "version": "1.1.0", 62 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 63 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 64 | "dev": true 65 | }, 66 | "async": { 67 | "version": "0.2.10", 68 | "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", 69 | "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", 70 | "dev": true 71 | }, 72 | "babel-runtime": { 73 | "version": "6.26.0", 74 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", 75 | "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", 76 | "dev": true, 77 | "requires": { 78 | "core-js": "2.5.3", 79 | "regenerator-runtime": "0.11.1" 80 | } 81 | }, 82 | "balanced-match": { 83 | "version": "1.0.0", 84 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 85 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 86 | "dev": true 87 | }, 88 | "biskviit": { 89 | "version": "1.0.1", 90 | "resolved": "https://registry.npmjs.org/biskviit/-/biskviit-1.0.1.tgz", 91 | "integrity": "sha1-A3oM1LcbnjMf2QoRIt4X3EnkIKc=", 92 | "requires": { 93 | "psl": "1.1.25" 94 | } 95 | }, 96 | "bluebird": { 97 | "version": "3.5.1", 98 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 99 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", 100 | "dev": true 101 | }, 102 | "brace-expansion": { 103 | "version": "1.1.11", 104 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 105 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 106 | "dev": true, 107 | "requires": { 108 | "balanced-match": "1.0.0", 109 | "concat-map": "0.0.1" 110 | } 111 | }, 112 | "browser-stdout": { 113 | "version": "1.3.0", 114 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", 115 | "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", 116 | "dev": true 117 | }, 118 | "bytes": { 119 | "version": "3.0.0", 120 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 121 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", 122 | "dev": true 123 | }, 124 | "chai": { 125 | "version": "3.5.0", 126 | "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", 127 | "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", 128 | "dev": true, 129 | "requires": { 130 | "assertion-error": "1.1.0", 131 | "deep-eql": "0.1.3", 132 | "type-detect": "1.0.0" 133 | } 134 | }, 135 | "chalk": { 136 | "version": "1.1.3", 137 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 138 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 139 | "requires": { 140 | "ansi-styles": "2.2.1", 141 | "escape-string-regexp": "1.0.5", 142 | "has-ansi": "2.0.0", 143 | "strip-ansi": "3.0.1", 144 | "supports-color": "2.0.0" 145 | } 146 | }, 147 | "commander": { 148 | "version": "2.15.0", 149 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.0.tgz", 150 | "integrity": "sha512-7B1ilBwtYSbetCgTY1NJFg+gVpestg0fdA1MhC1Vs4ssyfSXnCAjFr+QcQM9/RedXC0EaUx1sG8Smgw2VfgKEg==" 151 | }, 152 | "concat-map": { 153 | "version": "0.0.1", 154 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 155 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 156 | "dev": true 157 | }, 158 | "config-chain": { 159 | "version": "1.1.11", 160 | "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", 161 | "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", 162 | "dev": true, 163 | "requires": { 164 | "ini": "1.3.5", 165 | "proto-list": "1.2.4" 166 | } 167 | }, 168 | "content-disposition": { 169 | "version": "0.5.1", 170 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.1.tgz", 171 | "integrity": "sha1-h0dsamfI2qh+Muh2Ft+IO6f7Bxs=", 172 | "dev": true 173 | }, 174 | "content-type": { 175 | "version": "1.0.4", 176 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 177 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", 178 | "dev": true 179 | }, 180 | "cookie": { 181 | "version": "0.1.5", 182 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.5.tgz", 183 | "integrity": "sha1-armUiksa4hlSzSWIUwpHItQETXw=", 184 | "dev": true 185 | }, 186 | "cookie-signature": { 187 | "version": "1.0.6", 188 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 189 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", 190 | "dev": true 191 | }, 192 | "core-js": { 193 | "version": "2.5.3", 194 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", 195 | "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=", 196 | "dev": true 197 | }, 198 | "dataloader": { 199 | "version": "1.2.0", 200 | "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.2.0.tgz", 201 | "integrity": "sha1-P3PqZXxJLIYMFjM0itxVypvyEH4=", 202 | "dev": true 203 | }, 204 | "debug": { 205 | "version": "2.6.8", 206 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 207 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", 208 | "dev": true, 209 | "requires": { 210 | "ms": "2.0.0" 211 | } 212 | }, 213 | "deep-eql": { 214 | "version": "0.1.3", 215 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", 216 | "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", 217 | "dev": true, 218 | "requires": { 219 | "type-detect": "0.1.1" 220 | }, 221 | "dependencies": { 222 | "type-detect": { 223 | "version": "0.1.1", 224 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", 225 | "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", 226 | "dev": true 227 | } 228 | } 229 | }, 230 | "depd": { 231 | "version": "1.1.1", 232 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 233 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", 234 | "dev": true 235 | }, 236 | "destroy": { 237 | "version": "1.0.4", 238 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 239 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", 240 | "dev": true 241 | }, 242 | "diff": { 243 | "version": "3.2.0", 244 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", 245 | "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", 246 | "dev": true 247 | }, 248 | "dox": { 249 | "version": "0.9.0", 250 | "resolved": "https://registry.npmjs.org/dox/-/dox-0.9.0.tgz", 251 | "integrity": "sha1-vpewhcufSgt+gINdVH53uGh9Cgw=", 252 | "dev": true, 253 | "requires": { 254 | "commander": "2.9.0", 255 | "jsdoctypeparser": "1.2.0", 256 | "markdown-it": "7.0.1" 257 | }, 258 | "dependencies": { 259 | "commander": { 260 | "version": "2.9.0", 261 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", 262 | "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", 263 | "dev": true, 264 | "requires": { 265 | "graceful-readlink": "1.0.1" 266 | } 267 | } 268 | } 269 | }, 270 | "editorconfig": { 271 | "version": "0.13.3", 272 | "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.13.3.tgz", 273 | "integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==", 274 | "dev": true, 275 | "requires": { 276 | "bluebird": "3.5.1", 277 | "commander": "2.15.0", 278 | "lru-cache": "3.2.0", 279 | "semver": "5.5.0", 280 | "sigmund": "1.0.1" 281 | } 282 | }, 283 | "ee-first": { 284 | "version": "1.1.1", 285 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 286 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", 287 | "dev": true 288 | }, 289 | "encoding": { 290 | "version": "0.1.12", 291 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", 292 | "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", 293 | "requires": { 294 | "iconv-lite": "0.4.19" 295 | } 296 | }, 297 | "entities": { 298 | "version": "1.1.1", 299 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", 300 | "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", 301 | "dev": true 302 | }, 303 | "escape-html": { 304 | "version": "1.0.3", 305 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 306 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", 307 | "dev": true 308 | }, 309 | "escape-string-regexp": { 310 | "version": "1.0.5", 311 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 312 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 313 | }, 314 | "etag": { 315 | "version": "1.7.0", 316 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", 317 | "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=", 318 | "dev": true 319 | }, 320 | "express": { 321 | "version": "4.13.4", 322 | "resolved": "https://registry.npmjs.org/express/-/express-4.13.4.tgz", 323 | "integrity": "sha1-PAt288d1kMg0VzkGHsC9O6Bn7CQ=", 324 | "dev": true, 325 | "requires": { 326 | "accepts": "1.2.13", 327 | "array-flatten": "1.1.1", 328 | "content-disposition": "0.5.1", 329 | "content-type": "1.0.4", 330 | "cookie": "0.1.5", 331 | "cookie-signature": "1.0.6", 332 | "debug": "2.2.0", 333 | "depd": "1.1.1", 334 | "escape-html": "1.0.3", 335 | "etag": "1.7.0", 336 | "finalhandler": "0.4.1", 337 | "fresh": "0.3.0", 338 | "merge-descriptors": "1.0.1", 339 | "methods": "1.1.2", 340 | "on-finished": "2.3.0", 341 | "parseurl": "1.3.2", 342 | "path-to-regexp": "0.1.7", 343 | "proxy-addr": "1.0.10", 344 | "qs": "4.0.0", 345 | "range-parser": "1.0.3", 346 | "send": "0.13.1", 347 | "serve-static": "1.10.3", 348 | "type-is": "1.6.16", 349 | "utils-merge": "1.0.0", 350 | "vary": "1.0.1" 351 | }, 352 | "dependencies": { 353 | "accepts": { 354 | "version": "1.2.13", 355 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.13.tgz", 356 | "integrity": "sha1-5fHzkoxtlf2WVYw27D2dDeSm7Oo=", 357 | "dev": true, 358 | "requires": { 359 | "mime-types": "2.1.18", 360 | "negotiator": "0.5.3" 361 | } 362 | }, 363 | "debug": { 364 | "version": "2.2.0", 365 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 366 | "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", 367 | "dev": true, 368 | "requires": { 369 | "ms": "0.7.1" 370 | } 371 | }, 372 | "ms": { 373 | "version": "0.7.1", 374 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", 375 | "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", 376 | "dev": true 377 | }, 378 | "negotiator": { 379 | "version": "0.5.3", 380 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz", 381 | "integrity": "sha1-Jp1cR2gQ7JLtvntsLygxY4T5p+g=", 382 | "dev": true 383 | } 384 | } 385 | }, 386 | "express-graphql": { 387 | "version": "0.6.12", 388 | "resolved": "https://registry.npmjs.org/express-graphql/-/express-graphql-0.6.12.tgz", 389 | "integrity": "sha512-ouLWV0hRw4hnaLtXzzwhdC79ewxKbY2PRvm05mPc/zOH5W5WVCHDQ1SmNxEPBQdUeeSNh29aIqW9zEQkA3kMuA==", 390 | "dev": true, 391 | "requires": { 392 | "accepts": "1.3.5", 393 | "content-type": "1.0.4", 394 | "http-errors": "1.6.2", 395 | "raw-body": "2.3.2" 396 | } 397 | }, 398 | "fetch": { 399 | "version": "1.1.0", 400 | "resolved": "https://registry.npmjs.org/fetch/-/fetch-1.1.0.tgz", 401 | "integrity": "sha1-CoJ58Gvjf58Ou1Z1YKMKSA2lmi4=", 402 | "requires": { 403 | "biskviit": "1.0.1", 404 | "encoding": "0.1.12" 405 | } 406 | }, 407 | "finalhandler": { 408 | "version": "0.4.1", 409 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.1.tgz", 410 | "integrity": "sha1-haF8bFmpRxfSYtYSMNSw6+PUoU0=", 411 | "dev": true, 412 | "requires": { 413 | "debug": "2.2.0", 414 | "escape-html": "1.0.3", 415 | "on-finished": "2.3.0", 416 | "unpipe": "1.0.0" 417 | }, 418 | "dependencies": { 419 | "debug": { 420 | "version": "2.2.0", 421 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 422 | "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", 423 | "dev": true, 424 | "requires": { 425 | "ms": "0.7.1" 426 | } 427 | }, 428 | "ms": { 429 | "version": "0.7.1", 430 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", 431 | "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", 432 | "dev": true 433 | } 434 | } 435 | }, 436 | "forwarded": { 437 | "version": "0.1.2", 438 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 439 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", 440 | "dev": true 441 | }, 442 | "fresh": { 443 | "version": "0.3.0", 444 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", 445 | "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=", 446 | "dev": true 447 | }, 448 | "fs.realpath": { 449 | "version": "1.0.0", 450 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 451 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 452 | "dev": true 453 | }, 454 | "glob": { 455 | "version": "7.1.1", 456 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", 457 | "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", 458 | "dev": true, 459 | "requires": { 460 | "fs.realpath": "1.0.0", 461 | "inflight": "1.0.6", 462 | "inherits": "2.0.3", 463 | "minimatch": "3.0.4", 464 | "once": "1.4.0", 465 | "path-is-absolute": "1.0.1" 466 | } 467 | }, 468 | "graceful-readlink": { 469 | "version": "1.0.1", 470 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", 471 | "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", 472 | "dev": true 473 | }, 474 | "graphql": { 475 | "version": "0.9.6", 476 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.9.6.tgz", 477 | "integrity": "sha1-UUQh6dIlwp38j9MFRZq65YgV7yw=", 478 | "requires": { 479 | "iterall": "1.2.2" 480 | } 481 | }, 482 | "graphql-relay": { 483 | "version": "0.3.6", 484 | "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.3.6.tgz", 485 | "integrity": "sha1-FhJ5ExC99yBbn4RaduB+txpcIqk=", 486 | "dev": true, 487 | "requires": { 488 | "babel-runtime": "5.8.38" 489 | }, 490 | "dependencies": { 491 | "babel-runtime": { 492 | "version": "5.8.38", 493 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.38.tgz", 494 | "integrity": "sha1-HAsC62MxL18If/IEUIJ7QlydTBk=", 495 | "dev": true, 496 | "requires": { 497 | "core-js": "1.2.7" 498 | } 499 | }, 500 | "core-js": { 501 | "version": "1.2.7", 502 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", 503 | "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", 504 | "dev": true 505 | } 506 | } 507 | }, 508 | "growl": { 509 | "version": "1.9.2", 510 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", 511 | "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", 512 | "dev": true 513 | }, 514 | "has-ansi": { 515 | "version": "2.0.0", 516 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 517 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 518 | "requires": { 519 | "ansi-regex": "2.1.1" 520 | } 521 | }, 522 | "has-flag": { 523 | "version": "1.0.0", 524 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", 525 | "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", 526 | "dev": true 527 | }, 528 | "he": { 529 | "version": "1.1.1", 530 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 531 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 532 | "dev": true 533 | }, 534 | "http-errors": { 535 | "version": "1.6.2", 536 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 537 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 538 | "dev": true, 539 | "requires": { 540 | "depd": "1.1.1", 541 | "inherits": "2.0.3", 542 | "setprototypeof": "1.0.3", 543 | "statuses": "1.4.0" 544 | } 545 | }, 546 | "iconv-lite": { 547 | "version": "0.4.19", 548 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 549 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" 550 | }, 551 | "inflight": { 552 | "version": "1.0.6", 553 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 554 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 555 | "dev": true, 556 | "requires": { 557 | "once": "1.4.0", 558 | "wrappy": "1.0.2" 559 | } 560 | }, 561 | "inherits": { 562 | "version": "2.0.3", 563 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 564 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 565 | "dev": true 566 | }, 567 | "ini": { 568 | "version": "1.3.5", 569 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", 570 | "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", 571 | "dev": true 572 | }, 573 | "ipaddr.js": { 574 | "version": "1.0.5", 575 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.0.5.tgz", 576 | "integrity": "sha1-X6eM8wG4JceKvDBC2BJyMEnqI8c=", 577 | "dev": true 578 | }, 579 | "is-stream": { 580 | "version": "1.1.0", 581 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 582 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 583 | }, 584 | "isomorphic-fetch": { 585 | "version": "2.2.1", 586 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", 587 | "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", 588 | "dev": true, 589 | "requires": { 590 | "node-fetch": "1.7.3", 591 | "whatwg-fetch": "2.0.3" 592 | } 593 | }, 594 | "iterall": { 595 | "version": "1.2.2", 596 | "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz", 597 | "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==" 598 | }, 599 | "js-beautify": { 600 | "version": "1.7.5", 601 | "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.7.5.tgz", 602 | "integrity": "sha512-9OhfAqGOrD7hoQBLJMTA+BKuKmoEtTJXzZ7WDF/9gvjtey1koVLuZqIY6c51aPDjbNdNtIXAkiWKVhziawE9Og==", 603 | "dev": true, 604 | "requires": { 605 | "config-chain": "1.1.11", 606 | "editorconfig": "0.13.3", 607 | "mkdirp": "0.5.1", 608 | "nopt": "3.0.6" 609 | } 610 | }, 611 | "jsdoctest": { 612 | "version": "1.7.1", 613 | "resolved": "https://registry.npmjs.org/jsdoctest/-/jsdoctest-1.7.1.tgz", 614 | "integrity": "sha512-mSSYyKXNDerEEwhV7NiNRVU+KCNlkACrYP6T1odwoB9N47m28e8eU8O0SoG5iy8JcydcLUf0cd3u8aKUyDv8LA==", 615 | "dev": true, 616 | "requires": { 617 | "commander": "2.15.0", 618 | "dox": "0.9.0", 619 | "js-beautify": "1.7.5", 620 | "lodash": "4.17.5", 621 | "should": "11.2.1", 622 | "uglify-js": "3.3.15" 623 | }, 624 | "dependencies": { 625 | "uglify-js": { 626 | "version": "3.3.15", 627 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.15.tgz", 628 | "integrity": "sha512-bqtBCAINYXX/OkdnqMGpbXr+OPWc00hsozRpk+dAtfnbdk2jjKiLmyOkQ7zamg648lVMnzATL8JrSN6LmaVpYA==", 629 | "dev": true, 630 | "requires": { 631 | "commander": "2.15.0", 632 | "source-map": "0.6.1" 633 | } 634 | } 635 | } 636 | }, 637 | "jsdoctypeparser": { 638 | "version": "1.2.0", 639 | "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-1.2.0.tgz", 640 | "integrity": "sha1-597cFToRhJ/8UUEUSuhqfvDCU5I=", 641 | "dev": true, 642 | "requires": { 643 | "lodash": "3.10.1" 644 | }, 645 | "dependencies": { 646 | "lodash": { 647 | "version": "3.10.1", 648 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", 649 | "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", 650 | "dev": true 651 | } 652 | } 653 | }, 654 | "json3": { 655 | "version": "3.3.2", 656 | "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", 657 | "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", 658 | "dev": true 659 | }, 660 | "linkify-it": { 661 | "version": "2.0.3", 662 | "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz", 663 | "integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=", 664 | "dev": true, 665 | "requires": { 666 | "uc.micro": "1.0.5" 667 | } 668 | }, 669 | "lodash": { 670 | "version": "4.17.5", 671 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", 672 | "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" 673 | }, 674 | "lodash._baseassign": { 675 | "version": "3.2.0", 676 | "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", 677 | "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", 678 | "dev": true, 679 | "requires": { 680 | "lodash._basecopy": "3.0.1", 681 | "lodash.keys": "3.1.2" 682 | } 683 | }, 684 | "lodash._basecopy": { 685 | "version": "3.0.1", 686 | "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", 687 | "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", 688 | "dev": true 689 | }, 690 | "lodash._basecreate": { 691 | "version": "3.0.3", 692 | "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", 693 | "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", 694 | "dev": true 695 | }, 696 | "lodash._getnative": { 697 | "version": "3.9.1", 698 | "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", 699 | "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", 700 | "dev": true 701 | }, 702 | "lodash._isiterateecall": { 703 | "version": "3.0.9", 704 | "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", 705 | "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", 706 | "dev": true 707 | }, 708 | "lodash.create": { 709 | "version": "3.1.1", 710 | "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", 711 | "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", 712 | "dev": true, 713 | "requires": { 714 | "lodash._baseassign": "3.2.0", 715 | "lodash._basecreate": "3.0.3", 716 | "lodash._isiterateecall": "3.0.9" 717 | } 718 | }, 719 | "lodash.isarguments": { 720 | "version": "3.1.0", 721 | "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", 722 | "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", 723 | "dev": true 724 | }, 725 | "lodash.isarray": { 726 | "version": "3.0.4", 727 | "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", 728 | "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", 729 | "dev": true 730 | }, 731 | "lodash.keys": { 732 | "version": "3.1.2", 733 | "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", 734 | "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", 735 | "dev": true, 736 | "requires": { 737 | "lodash._getnative": "3.9.1", 738 | "lodash.isarguments": "3.1.0", 739 | "lodash.isarray": "3.0.4" 740 | } 741 | }, 742 | "lru-cache": { 743 | "version": "3.2.0", 744 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz", 745 | "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=", 746 | "dev": true, 747 | "requires": { 748 | "pseudomap": "1.0.2" 749 | } 750 | }, 751 | "markdown-it": { 752 | "version": "7.0.1", 753 | "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-7.0.1.tgz", 754 | "integrity": "sha1-8S2LiKk+ZCVDSN/Rg71wv2BWekI=", 755 | "dev": true, 756 | "requires": { 757 | "argparse": "1.0.10", 758 | "entities": "1.1.1", 759 | "linkify-it": "2.0.3", 760 | "mdurl": "1.0.1", 761 | "uc.micro": "1.0.5" 762 | } 763 | }, 764 | "mdurl": { 765 | "version": "1.0.1", 766 | "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", 767 | "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", 768 | "dev": true 769 | }, 770 | "media-typer": { 771 | "version": "0.3.0", 772 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 773 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", 774 | "dev": true 775 | }, 776 | "merge-descriptors": { 777 | "version": "1.0.1", 778 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 779 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", 780 | "dev": true 781 | }, 782 | "methods": { 783 | "version": "1.1.2", 784 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 785 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", 786 | "dev": true 787 | }, 788 | "mime": { 789 | "version": "1.3.4", 790 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 791 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=", 792 | "dev": true 793 | }, 794 | "mime-db": { 795 | "version": "1.33.0", 796 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", 797 | "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", 798 | "dev": true 799 | }, 800 | "mime-types": { 801 | "version": "2.1.18", 802 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", 803 | "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", 804 | "dev": true, 805 | "requires": { 806 | "mime-db": "1.33.0" 807 | } 808 | }, 809 | "minimatch": { 810 | "version": "3.0.4", 811 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 812 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 813 | "dev": true, 814 | "requires": { 815 | "brace-expansion": "1.1.11" 816 | } 817 | }, 818 | "minimist": { 819 | "version": "0.0.8", 820 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 821 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 822 | "dev": true 823 | }, 824 | "mkdirp": { 825 | "version": "0.5.1", 826 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 827 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 828 | "dev": true, 829 | "requires": { 830 | "minimist": "0.0.8" 831 | } 832 | }, 833 | "mocha": { 834 | "version": "3.5.3", 835 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", 836 | "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", 837 | "dev": true, 838 | "requires": { 839 | "browser-stdout": "1.3.0", 840 | "commander": "2.9.0", 841 | "debug": "2.6.8", 842 | "diff": "3.2.0", 843 | "escape-string-regexp": "1.0.5", 844 | "glob": "7.1.1", 845 | "growl": "1.9.2", 846 | "he": "1.1.1", 847 | "json3": "3.3.2", 848 | "lodash.create": "3.1.1", 849 | "mkdirp": "0.5.1", 850 | "supports-color": "3.1.2" 851 | }, 852 | "dependencies": { 853 | "commander": { 854 | "version": "2.9.0", 855 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", 856 | "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", 857 | "dev": true, 858 | "requires": { 859 | "graceful-readlink": "1.0.1" 860 | } 861 | }, 862 | "supports-color": { 863 | "version": "3.1.2", 864 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", 865 | "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", 866 | "dev": true, 867 | "requires": { 868 | "has-flag": "1.0.0" 869 | } 870 | } 871 | } 872 | }, 873 | "ms": { 874 | "version": "2.0.0", 875 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 876 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 877 | "dev": true 878 | }, 879 | "negotiator": { 880 | "version": "0.6.1", 881 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 882 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", 883 | "dev": true 884 | }, 885 | "node-fetch": { 886 | "version": "1.7.3", 887 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", 888 | "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", 889 | "requires": { 890 | "encoding": "0.1.12", 891 | "is-stream": "1.1.0" 892 | } 893 | }, 894 | "nopt": { 895 | "version": "3.0.6", 896 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", 897 | "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", 898 | "dev": true, 899 | "requires": { 900 | "abbrev": "1.1.1" 901 | } 902 | }, 903 | "on-finished": { 904 | "version": "2.3.0", 905 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 906 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 907 | "dev": true, 908 | "requires": { 909 | "ee-first": "1.1.1" 910 | } 911 | }, 912 | "once": { 913 | "version": "1.4.0", 914 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 915 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 916 | "dev": true, 917 | "requires": { 918 | "wrappy": "1.0.2" 919 | } 920 | }, 921 | "parseurl": { 922 | "version": "1.3.2", 923 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 924 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", 925 | "dev": true 926 | }, 927 | "path-is-absolute": { 928 | "version": "1.0.1", 929 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 930 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 931 | "dev": true 932 | }, 933 | "path-to-regexp": { 934 | "version": "0.1.7", 935 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 936 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", 937 | "dev": true 938 | }, 939 | "proto-list": { 940 | "version": "1.2.4", 941 | "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", 942 | "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", 943 | "dev": true 944 | }, 945 | "proxy-addr": { 946 | "version": "1.0.10", 947 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.10.tgz", 948 | "integrity": "sha1-DUCoL4Afw1VWfS7LZe/j8HfxIcU=", 949 | "dev": true, 950 | "requires": { 951 | "forwarded": "0.1.2", 952 | "ipaddr.js": "1.0.5" 953 | } 954 | }, 955 | "pseudomap": { 956 | "version": "1.0.2", 957 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 958 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", 959 | "dev": true 960 | }, 961 | "psl": { 962 | "version": "1.1.25", 963 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.25.tgz", 964 | "integrity": "sha512-Djug/g0La/23cfyh1GujTbrLs/dhUxEquv78at1zHs03oglR1FP54v1nr8J7nCKxLEs1tsNP0u3DGVrugGi/kA==" 965 | }, 966 | "qs": { 967 | "version": "4.0.0", 968 | "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz", 969 | "integrity": "sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=", 970 | "dev": true 971 | }, 972 | "range-parser": { 973 | "version": "1.0.3", 974 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.3.tgz", 975 | "integrity": "sha1-aHKCNTXGkuLCoBA4Jq/YLC4P8XU=", 976 | "dev": true 977 | }, 978 | "raw-body": { 979 | "version": "2.3.2", 980 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", 981 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", 982 | "dev": true, 983 | "requires": { 984 | "bytes": "3.0.0", 985 | "http-errors": "1.6.2", 986 | "iconv-lite": "0.4.19", 987 | "unpipe": "1.0.0" 988 | } 989 | }, 990 | "regenerator-runtime": { 991 | "version": "0.11.1", 992 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", 993 | "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", 994 | "dev": true 995 | }, 996 | "semver": { 997 | "version": "5.5.0", 998 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", 999 | "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", 1000 | "dev": true 1001 | }, 1002 | "send": { 1003 | "version": "0.13.1", 1004 | "resolved": "https://registry.npmjs.org/send/-/send-0.13.1.tgz", 1005 | "integrity": "sha1-ow1fTILIqbrprQCh2bG9vm8Zntc=", 1006 | "dev": true, 1007 | "requires": { 1008 | "debug": "2.2.0", 1009 | "depd": "1.1.1", 1010 | "destroy": "1.0.4", 1011 | "escape-html": "1.0.3", 1012 | "etag": "1.7.0", 1013 | "fresh": "0.3.0", 1014 | "http-errors": "1.3.1", 1015 | "mime": "1.3.4", 1016 | "ms": "0.7.1", 1017 | "on-finished": "2.3.0", 1018 | "range-parser": "1.0.3", 1019 | "statuses": "1.2.1" 1020 | }, 1021 | "dependencies": { 1022 | "debug": { 1023 | "version": "2.2.0", 1024 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 1025 | "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", 1026 | "dev": true, 1027 | "requires": { 1028 | "ms": "0.7.1" 1029 | } 1030 | }, 1031 | "http-errors": { 1032 | "version": "1.3.1", 1033 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", 1034 | "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", 1035 | "dev": true, 1036 | "requires": { 1037 | "inherits": "2.0.3", 1038 | "statuses": "1.2.1" 1039 | } 1040 | }, 1041 | "ms": { 1042 | "version": "0.7.1", 1043 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", 1044 | "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", 1045 | "dev": true 1046 | }, 1047 | "statuses": { 1048 | "version": "1.2.1", 1049 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz", 1050 | "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg=", 1051 | "dev": true 1052 | } 1053 | } 1054 | }, 1055 | "serve-static": { 1056 | "version": "1.10.3", 1057 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.10.3.tgz", 1058 | "integrity": "sha1-zlpuzTEB/tXsCYJ9rCKpwpv7BTU=", 1059 | "dev": true, 1060 | "requires": { 1061 | "escape-html": "1.0.3", 1062 | "parseurl": "1.3.2", 1063 | "send": "0.13.2" 1064 | }, 1065 | "dependencies": { 1066 | "debug": { 1067 | "version": "2.2.0", 1068 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 1069 | "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", 1070 | "dev": true, 1071 | "requires": { 1072 | "ms": "0.7.1" 1073 | } 1074 | }, 1075 | "http-errors": { 1076 | "version": "1.3.1", 1077 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", 1078 | "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", 1079 | "dev": true, 1080 | "requires": { 1081 | "inherits": "2.0.3", 1082 | "statuses": "1.2.1" 1083 | } 1084 | }, 1085 | "ms": { 1086 | "version": "0.7.1", 1087 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", 1088 | "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", 1089 | "dev": true 1090 | }, 1091 | "send": { 1092 | "version": "0.13.2", 1093 | "resolved": "https://registry.npmjs.org/send/-/send-0.13.2.tgz", 1094 | "integrity": "sha1-dl52B8gFVFK7pvCwUllTUJhgNt4=", 1095 | "dev": true, 1096 | "requires": { 1097 | "debug": "2.2.0", 1098 | "depd": "1.1.1", 1099 | "destroy": "1.0.4", 1100 | "escape-html": "1.0.3", 1101 | "etag": "1.7.0", 1102 | "fresh": "0.3.0", 1103 | "http-errors": "1.3.1", 1104 | "mime": "1.3.4", 1105 | "ms": "0.7.1", 1106 | "on-finished": "2.3.0", 1107 | "range-parser": "1.0.3", 1108 | "statuses": "1.2.1" 1109 | } 1110 | }, 1111 | "statuses": { 1112 | "version": "1.2.1", 1113 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz", 1114 | "integrity": "sha1-3e1FzBglbVHtQK7BQkidXGECbSg=", 1115 | "dev": true 1116 | } 1117 | } 1118 | }, 1119 | "setprototypeof": { 1120 | "version": "1.0.3", 1121 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 1122 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", 1123 | "dev": true 1124 | }, 1125 | "should": { 1126 | "version": "11.2.1", 1127 | "resolved": "https://registry.npmjs.org/should/-/should-11.2.1.tgz", 1128 | "integrity": "sha1-kPVRRVUtAc/CAGZuToGKHJZw7aI=", 1129 | "dev": true, 1130 | "requires": { 1131 | "should-equal": "1.0.1", 1132 | "should-format": "3.0.3", 1133 | "should-type": "1.4.0", 1134 | "should-type-adaptors": "1.1.0", 1135 | "should-util": "1.0.0" 1136 | } 1137 | }, 1138 | "should-equal": { 1139 | "version": "1.0.1", 1140 | "resolved": "http://registry.npmjs.org/should-equal/-/should-equal-1.0.1.tgz", 1141 | "integrity": "sha1-C26VFvJgGp+wuy3MNpr6HH4gCvc=", 1142 | "dev": true, 1143 | "requires": { 1144 | "should-type": "1.4.0" 1145 | } 1146 | }, 1147 | "should-format": { 1148 | "version": "3.0.3", 1149 | "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", 1150 | "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", 1151 | "dev": true, 1152 | "requires": { 1153 | "should-type": "1.4.0", 1154 | "should-type-adaptors": "1.1.0" 1155 | } 1156 | }, 1157 | "should-type": { 1158 | "version": "1.4.0", 1159 | "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", 1160 | "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", 1161 | "dev": true 1162 | }, 1163 | "should-type-adaptors": { 1164 | "version": "1.1.0", 1165 | "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", 1166 | "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", 1167 | "dev": true, 1168 | "requires": { 1169 | "should-type": "1.4.0", 1170 | "should-util": "1.0.0" 1171 | } 1172 | }, 1173 | "should-util": { 1174 | "version": "1.0.0", 1175 | "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz", 1176 | "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=", 1177 | "dev": true 1178 | }, 1179 | "sigmund": { 1180 | "version": "1.0.1", 1181 | "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", 1182 | "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", 1183 | "dev": true 1184 | }, 1185 | "source-map": { 1186 | "version": "0.6.1", 1187 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1188 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1189 | "dev": true 1190 | }, 1191 | "sprintf-js": { 1192 | "version": "1.0.3", 1193 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1194 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1195 | "dev": true 1196 | }, 1197 | "statuses": { 1198 | "version": "1.4.0", 1199 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 1200 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", 1201 | "dev": true 1202 | }, 1203 | "strip-ansi": { 1204 | "version": "3.0.1", 1205 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1206 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1207 | "requires": { 1208 | "ansi-regex": "2.1.1" 1209 | } 1210 | }, 1211 | "supports-color": { 1212 | "version": "2.0.0", 1213 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1214 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" 1215 | }, 1216 | "swapi-graphql": { 1217 | "version": "0.0.6", 1218 | "resolved": "https://registry.npmjs.org/swapi-graphql/-/swapi-graphql-0.0.6.tgz", 1219 | "integrity": "sha1-TnbS1TizIQNlpKgHpygnhq1mtl8=", 1220 | "dev": true, 1221 | "requires": { 1222 | "babel-runtime": "6.26.0", 1223 | "dataloader": "1.2.0", 1224 | "express": "4.13.4", 1225 | "express-graphql": "0.4.13", 1226 | "graphql": "0.4.18", 1227 | "graphql-relay": "0.3.6", 1228 | "isomorphic-fetch": "2.2.1" 1229 | }, 1230 | "dependencies": { 1231 | "bytes": { 1232 | "version": "2.4.0", 1233 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", 1234 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=", 1235 | "dev": true 1236 | }, 1237 | "core-js": { 1238 | "version": "1.2.7", 1239 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", 1240 | "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", 1241 | "dev": true 1242 | }, 1243 | "express-graphql": { 1244 | "version": "0.4.13", 1245 | "resolved": "https://registry.npmjs.org/express-graphql/-/express-graphql-0.4.13.tgz", 1246 | "integrity": "sha1-u5mQHcGBoadMyrSUYG96Ah7H4w0=", 1247 | "dev": true, 1248 | "requires": { 1249 | "content-type": "1.0.4", 1250 | "http-errors": "1.3.1", 1251 | "raw-body": "2.1.7" 1252 | } 1253 | }, 1254 | "graphql": { 1255 | "version": "0.4.18", 1256 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.4.18.tgz", 1257 | "integrity": "sha1-gLkj8tgB5Tc9+ZMWucWCnyBsSCw=", 1258 | "dev": true, 1259 | "requires": { 1260 | "babel-runtime": "5.8.38" 1261 | }, 1262 | "dependencies": { 1263 | "babel-runtime": { 1264 | "version": "5.8.38", 1265 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.38.tgz", 1266 | "integrity": "sha1-HAsC62MxL18If/IEUIJ7QlydTBk=", 1267 | "dev": true, 1268 | "requires": { 1269 | "core-js": "1.2.7" 1270 | } 1271 | } 1272 | } 1273 | }, 1274 | "http-errors": { 1275 | "version": "1.3.1", 1276 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", 1277 | "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", 1278 | "dev": true, 1279 | "requires": { 1280 | "inherits": "2.0.3", 1281 | "statuses": "1.4.0" 1282 | } 1283 | }, 1284 | "iconv-lite": { 1285 | "version": "0.4.13", 1286 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", 1287 | "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", 1288 | "dev": true 1289 | }, 1290 | "raw-body": { 1291 | "version": "2.1.7", 1292 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", 1293 | "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", 1294 | "dev": true, 1295 | "requires": { 1296 | "bytes": "2.4.0", 1297 | "iconv-lite": "0.4.13", 1298 | "unpipe": "1.0.0" 1299 | } 1300 | } 1301 | } 1302 | }, 1303 | "type-detect": { 1304 | "version": "1.0.0", 1305 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", 1306 | "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", 1307 | "dev": true 1308 | }, 1309 | "type-is": { 1310 | "version": "1.6.16", 1311 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 1312 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 1313 | "dev": true, 1314 | "requires": { 1315 | "media-typer": "0.3.0", 1316 | "mime-types": "2.1.18" 1317 | } 1318 | }, 1319 | "typescript": { 1320 | "version": "2.7.2", 1321 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz", 1322 | "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==", 1323 | "dev": true 1324 | }, 1325 | "uc.micro": { 1326 | "version": "1.0.5", 1327 | "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.5.tgz", 1328 | "integrity": "sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==", 1329 | "dev": true 1330 | }, 1331 | "uglify-to-browserify": { 1332 | "version": "1.0.2", 1333 | "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", 1334 | "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", 1335 | "dev": true 1336 | }, 1337 | "uglifyjs": { 1338 | "version": "2.4.10", 1339 | "resolved": "https://registry.npmjs.org/uglifyjs/-/uglifyjs-2.4.10.tgz", 1340 | "integrity": "sha1-YyknMZ+mo9o/yR+Xc6wnv+bD7pI=", 1341 | "dev": true, 1342 | "requires": { 1343 | "async": "0.2.10", 1344 | "source-map": "0.1.34", 1345 | "uglify-to-browserify": "1.0.2", 1346 | "yargs": "1.3.3" 1347 | }, 1348 | "dependencies": { 1349 | "source-map": { 1350 | "version": "0.1.34", 1351 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.34.tgz", 1352 | "integrity": "sha1-p8/omux7FoLDsZjQrPtH19CQVms=", 1353 | "dev": true, 1354 | "requires": { 1355 | "amdefine": "1.0.1" 1356 | } 1357 | } 1358 | } 1359 | }, 1360 | "unpipe": { 1361 | "version": "1.0.0", 1362 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1363 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", 1364 | "dev": true 1365 | }, 1366 | "utils-merge": { 1367 | "version": "1.0.0", 1368 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 1369 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=", 1370 | "dev": true 1371 | }, 1372 | "vary": { 1373 | "version": "1.0.1", 1374 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz", 1375 | "integrity": "sha1-meSYFWaihhGN+yuBc1ffeZM3bRA=", 1376 | "dev": true 1377 | }, 1378 | "whatwg-fetch": { 1379 | "version": "2.0.3", 1380 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", 1381 | "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=", 1382 | "dev": true 1383 | }, 1384 | "wrappy": { 1385 | "version": "1.0.2", 1386 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1387 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1388 | "dev": true 1389 | }, 1390 | "yargs": { 1391 | "version": "1.3.3", 1392 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-1.3.3.tgz", 1393 | "integrity": "sha1-BU3oth8i7v23IHBZ6u+da4P7kxo=", 1394 | "dev": true 1395 | } 1396 | } 1397 | } 1398 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-query-generator", 3 | "version": "0.5.5", 4 | "description": "Generates queries from the GraphQL endpoint via schema introspection.", 5 | "main": "index.js", 6 | "bin": { 7 | "gql-test": "./bin/cli.js" 8 | }, 9 | "directories": { 10 | "test": "tests" 11 | }, 12 | "scripts": { 13 | "test": "npm run build-ts && npm run jsdoctest && mocha test/ --recursive", 14 | "build-ts": "tsc", 15 | "watch-ts": "tsc -w", 16 | "jsdoctest": "mocha --require jsdoctest lib/" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/opentable/graphql-query-generator.git" 21 | }, 22 | "keywords": [], 23 | "author": "Piotr Bazydlo ", 24 | "license": "ISC", 25 | "bugs": { 26 | "url": "https://github.com/opentable/graphql-query-generator/issues" 27 | }, 28 | "homepage": "https://github.com/opentable/graphql-query-generator#readme", 29 | "devDependencies": { 30 | "@types/node": "^8.0.28", 31 | "chai": "^3.5.0", 32 | "express-graphql": "^0.6.6", 33 | "jsdoctest": "^1.7.0", 34 | "mocha": "^3.3.0", 35 | "swapi-graphql": "0.0.6", 36 | "typescript": "^2.5.2", 37 | "uglifyjs": "2.4.10" 38 | }, 39 | "dependencies": { 40 | "chalk": "^1.1.3", 41 | "commander": "^2.9.0", 42 | "fetch": "^1.1.0", 43 | "graphql": "^0.9.2", 44 | "lodash": "^4.17.4", 45 | "node-fetch": "^1.6.3" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/aqtToQuery.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | /** 4 | * @example 5 | * exports.default('name') // => 'f0: name' 6 | * exports.default(['name', 'surname', 'age']) // => 'f0: name f1: surname f2: age ' 7 | * exports.default(['name', 'name', 'name']) // => 'f0: name f1: name f2: name ' 8 | * exports.default({ people: 'name', countries: ['flag']}) // => 'q0_42: people { f42: name }q0_43: countries { f43: flag }' 9 | * exports.default(['id', 'name', { coordinates: ['lat', 'long'] }, { test: ['a']}]) 10 | * // => 'f0: id f1: name q2_42: coordinates { f42: lat f43: long } q3_42: test { f42: a } ' 11 | */ 12 | export default function queryTreeToGraphQLString(tree, parentIndex = 0) { 13 | let output : string = ''; 14 | 15 | if (_.isObject(tree) && !_.isArray(tree)) { 16 | let index : number = 42; 17 | _.forIn(tree, (value, key) => { 18 | var x = tree == tree; 19 | output += `q${parentIndex}_${index}: ${key} { ${queryTreeToGraphQLString(value, index)} }`; 20 | index++; 21 | }); 22 | } 23 | 24 | if (_.isArray(tree)) { 25 | _.map(tree, (item, index) => { 26 | output += `${queryTreeToGraphQLString(item, parentIndex + index)} `; 27 | }); 28 | } 29 | 30 | if (_.isString(tree)) { 31 | output = `f${parentIndex}: ${tree}`; 32 | } 33 | 34 | return output; 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /src/cli/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const graphQlClient = require('../graphqlClient').default; 3 | const chalk = require('chalk'); 4 | const retry = require('./retryHelper').retry; 5 | const QueryGenerator = require('../queryGenerator'); 6 | 7 | process.title = 'gql-query-generator'; 8 | 9 | let program = require('commander'); 10 | 11 | let serverUrl = null; 12 | 13 | program 14 | .version(require('../../package.json').version) 15 | .arguments('') 16 | .action(function (url) { 17 | serverUrl = url; 18 | }) 19 | .option('-v, --verbose', 'Displays all the query information') 20 | .option('-p, --parallel', 'Executes all queries in parallel') 21 | .option('-r, --retryCount ', 'Number of times to retry the query generator if it fails', parseInt) 22 | .option('-t, --retrySnoozeTime ', 'Time in milliseconds to wait before retries', parseInt) 23 | .parse(process.argv); 24 | 25 | if (serverUrl === null) { 26 | console.log('Please specify the graphql endpoint for the serverUrl'); 27 | program.outputHelp(); 28 | process.exit(1); 29 | } 30 | 31 | const queryGenerator = new QueryGenerator(serverUrl); 32 | 33 | let failedTests = 0; 34 | let passedTests = 0; 35 | let retryCount = program.retryCount || 0; 36 | let retrySnoozeTime = program.retrySnoozeTime || 1000; 37 | 38 | retry(() => queryGenerator.run(), retryCount, retrySnoozeTime) 39 | .then(({ queries, coverage }) => { 40 | console.log(`Fetched ${queries.length} queries, get to work!`); 41 | 42 | return maybeSerialisePromises( 43 | queries.map(query => 44 | graphQlClient(serverUrl, query) 45 | .then((res) => res.json()) 46 | .then((result) => { 47 | if (result.errors) { 48 | return Promise.reject(result); 49 | } 50 | 51 | if (program.verbose) { 52 | console.log(chalk.grey(query)); 53 | } 54 | 55 | process.stdout.write('.'); 56 | passedTests++; 57 | }) 58 | .catch((result) => { 59 | console.log(chalk.red('FAIL')); 60 | if (result.errors) { 61 | console.log('Following errors occured:\n'); 62 | result.errors.forEach(formatError); 63 | console.log(''); 64 | } 65 | console.log(chalk.grey('Full query:\n', query)); 66 | failedTests++; 67 | }) 68 | ) 69 | ).then(() => { 70 | if (failedTests > 0) { 71 | console.log(chalk.bold.red(`${failedTests}/${failedTests + passedTests} queries failed.`)); 72 | console.log(formatCoverageData(coverage)); 73 | return process.exit(1); 74 | } 75 | 76 | console.log(chalk.bold.green(`\nAll ${passedTests} tests passed.`)) 77 | console.log(formatCoverageData(coverage)); 78 | }); 79 | }) 80 | .catch((error) => { 81 | console.log(chalk.red(`\nFailed to get queries from server:\n${error}`)); 82 | return process.exit(1); 83 | }); 84 | 85 | function formatError(err) { 86 | const pathMessage = err.path ? `\n\tPath: ${err.path.join('.')}` : '' 87 | return console.log(err.message + pathMessage); 88 | } 89 | 90 | function formatCoverageData(coverage) { 91 | const coveragePercentage = (coverage.coverageRatio * 100).toFixed(2); 92 | return ` 93 | ======================================= 94 | Overall coverage: ${coveragePercentage}% 95 | --------------------------------------- 96 | Fields not covered by queries: 97 | 98 | ${coverage.notCoveredFields.join('\n')} 99 | --------------------------------------- 100 | Overall coverage: ${coveragePercentage}% 101 | `; 102 | } 103 | 104 | function maybeSerialisePromises(promises) { 105 | if (program.parallel) { 106 | return Promise.all(promises); 107 | } 108 | 109 | if (promises.length > 1) { 110 | return promises[0].then(() => 111 | maybeSerialisePromises(promises.slice(1)) 112 | ); 113 | } else if (promises.length === 1) { 114 | return promises[0]; 115 | } 116 | 117 | return Promise.resolve(); 118 | } -------------------------------------------------------------------------------- /src/cli/retryHelper.js: -------------------------------------------------------------------------------- 1 | function wait(intervalTime) { 2 | return new Promise(function(resolve) { 3 | setTimeout(resolve, intervalTime); 4 | }); 5 | } 6 | 7 | function retry(myPromiseFactory, retries, intervalTime) { 8 | if(retries === 0) return myPromiseFactory(); 9 | 10 | return myPromiseFactory().catch(function(){ 11 | return wait(intervalTime).then(function(){ 12 | retries--; 13 | return retry(myPromiseFactory, retries, intervalTime); 14 | }); 15 | }); 16 | } 17 | 18 | module.exports.retry = retry; 19 | -------------------------------------------------------------------------------- /src/coverageCalculator.ts: -------------------------------------------------------------------------------- 1 | import schemaToQueryTree from './schemaToQueryTree'; 2 | import * as _ from 'lodash'; 3 | 4 | const { getQueryFields, getQueryFieldsModes } = schemaToQueryTree; 5 | 6 | /** 7 | * @example 8 | * exports.default('ObjectContainingTwoDeeplyNestedObjects', require('../test/unit/mockData')) 9 | * // => { coverageRatio: 1, notCoveredFields: []} 10 | * exports.default('DeeplyNestedObjectWithPartialNoFollow', require('../test/unit/mockData')) 11 | * // => { coverageRatio: 0.5, notCoveredFields: ["DeeplyNestedObject___NOFollowPart", "DeeplyNestedObject___DeeplyNestedObject___DeepNest", "DeeplyNestedObject___ObjectField___NotSoDeepNest"]} 12 | */ 13 | export default function coverageCalculator (rootName, schema) { 14 | const sharedSkipListForGetQueryableFields = []; 15 | const sharedSkipListForGetAllFields = [] 16 | 17 | let allQuerableFields = []; 18 | let allAllFields = []; 19 | 20 | _.forIn(schema[rootName].fields, (field) => { 21 | const queryableFields = getQueryFields(getQueryFieldsModes.QUERYABLE_FIELDS, field, schema, sharedSkipListForGetQueryableFields); 22 | const allFields = getQueryFields(getQueryFieldsModes.ALL_FIELDS, field, schema, sharedSkipListForGetAllFields); 23 | allQuerableFields = _.union(allQuerableFields, queryableFields); 24 | allAllFields = _.union(allAllFields, allFields); 25 | }); 26 | 27 | return { 28 | coverageRatio: allQuerableFields.length / allAllFields.length, 29 | notCoveredFields: _.difference(allAllFields, allQuerableFields) 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /src/descriptionParser.ts: -------------------------------------------------------------------------------- 1 | const examplesSection = new RegExp(/Example[s]?:/); 2 | 3 | export default { 4 | /** 5 | * @example 6 | * exports.default.getExamplesFrom('Emples:country(cId: 1)') // => [] 7 | * exports.default.getExamplesFrom('Emples:countrycId: 1)') // => [] 8 | * exports.default.getExamplesFrom('Examples:country(cId: 1)') // => ['country(cId: 1)'] 9 | * exports.default.getExamplesFrom('Examples: country(cId: 1)') // => ['country(cId: 1)'] 10 | * exports.default.getExamplesFrom('Examples:\ncountry(cId: 1)') // => ['country(cId: 1)'] 11 | * exports.default.getExamplesFrom('Example:country(cId: 1)') // => ['country(cId: 1)'] 12 | * exports.default.getExamplesFrom('Examples: country(cId:1)') // => ['country(cId:1)'] 13 | * exports.default.getExamplesFrom('Examples:country(\ncId: 1\n)') // => ['country(\ncId: 1\n)'] 14 | * exports.default.getExamplesFrom('Examples:country(cId: 1, cName: "Test")') // => ['country(cId: 1, cName: "Test")'] 15 | * exports.default.getExamplesFrom('Examples:country(cId: 1, cName: "Test")\nmetro(mId: 100)') // => ['country(cId: 1, cName: "Test")', 'metro(mId: 100)'] 16 | */ 17 | getExamplesFrom: function getExamplesFrom(comment) { 18 | if (!comment) { 19 | return []; 20 | } 21 | 22 | const what = comment.split(examplesSection); 23 | if (what.length !== 2) return []; 24 | const examplesDescription = what[1]; 25 | let result : Array = []; 26 | let matches : any | null = null; 27 | const test = new RegExp(/(\s*([_A-Za-z]\w*)\s*\([^)]*\)\s*)/g); 28 | // Forgive me 29 | while ((matches = test.exec(examplesDescription)) && matches.length > 1) { 30 | result.push(matches[1].trim()); 31 | } 32 | 33 | return result; 34 | }, 35 | /** 36 | * @example 37 | * exports.default.shouldFollow('Examples:country(\ncId: 1\n)\n+NOFOLLOW\n') // => false 38 | * exports.default.shouldFollow('Examples:country(\ncId: 1\n)\n +NOFOLLOW\n') // => false 39 | * exports.default.shouldFollow('+NOFOLLOW\nExamples:country(\ncId: 1\n)') // => false 40 | * exports.default.shouldFollow(' +NOFOLLOW\nExamples:country(\ncId: 1\n)') // => false 41 | * exports.default.shouldFollow('Examples+NOFOLLOW:country(\ncId: 1\n)') // => true 42 | * exports.default.shouldFollow('Examples:country(\ncId: 1\n)+NOFOLLOW') // => true 43 | */ 44 | shouldFollow(description) { 45 | if (!description) { 46 | return true; 47 | } 48 | 49 | return description.match(/(^\s*\+NOFOLLOW|\n\s*\+NOFOLLOW)/) === null; 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /src/graphqlClient.ts: -------------------------------------------------------------------------------- 1 | import * as fetch from 'node-fetch'; 2 | 3 | function createQuery(query) { 4 | var body = { 5 | "query": query, 6 | "variables": {}, 7 | "operationName": null 8 | }; 9 | 10 | return JSON.stringify(body); 11 | } 12 | 13 | export default function query(url, graphQuery) { 14 | const queryPromise = fetch(url, { 15 | method: 'POST', 16 | headers: { 'Content-Type': 'application/json' }, 17 | body: createQuery(graphQuery) 18 | }); 19 | 20 | return queryPromise; 21 | } 22 | -------------------------------------------------------------------------------- /src/introspectionQuery.ts: -------------------------------------------------------------------------------- 1 | export default `{ 2 | __schema{ 3 | queryType { 4 | name 5 | } 6 | types { 7 | name 8 | kind 9 | fields { 10 | name 11 | type { 12 | name 13 | ofType { 14 | kind 15 | name 16 | ofType { 17 | kind 18 | name 19 | ofType { 20 | kind 21 | name 22 | } 23 | } 24 | } 25 | } 26 | description 27 | args { 28 | name 29 | type { 30 | kind 31 | ofType { 32 | kind 33 | } 34 | } 35 | defaultValue 36 | } 37 | } 38 | } 39 | } 40 | }`; -------------------------------------------------------------------------------- /src/queryGenerator.ts: -------------------------------------------------------------------------------- 1 | import introspectionQuery from './introspectionQuery'; 2 | import query from './graphqlClient'; 3 | import schemaToQueries from './schemaToQueries'; 4 | import calculateCoverage from './coverageCalculator'; 5 | 6 | module.exports = function QueryGenerator(url) { 7 | function buildTypeDictionary(__schema) { 8 | let result = {}; 9 | __schema.types.forEach(type => result[type.name] = type); 10 | return result; 11 | } 12 | 13 | this.run = function () { 14 | return query(url, introspectionQuery) 15 | .then((res) => { 16 | if (!res.ok) { 17 | return res.text() 18 | .then((responseText) => { 19 | return Promise.reject(`Introspection query failed with status ${res.status}.\nResponse text:\n${responseText}`); 20 | }); 21 | } 22 | return res.json(); 23 | }) 24 | .then(result => { 25 | const queryTypeName = result.data['__schema'].queryType.name; 26 | const typeDictionary = buildTypeDictionary(result.data['__schema']); 27 | const queries = schemaToQueries(queryTypeName, typeDictionary); 28 | const coverage = calculateCoverage(queryTypeName, typeDictionary); 29 | return { queries, coverage }; 30 | }); 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /src/reporters/teamcity.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentable/graphql-query-generator/f8546f5002aedff7da0cdbba02868694cb46f3ad/src/reporters/teamcity.js -------------------------------------------------------------------------------- /src/schemaToQueries.ts: -------------------------------------------------------------------------------- 1 | import queryTreeToGraphQLString from './aqtToQuery'; 2 | import schemaToQueryTree from './schemaToQueryTree'; 3 | import * as _ from 'lodash'; 4 | 5 | export default function schemaToQueries (rootName, schema) { 6 | const queries : Array = []; 7 | const sharedSkipList = []; 8 | 9 | _.forIn(schema[rootName].fields, (field) => { 10 | const queryTree = schemaToQueryTree.buildQueryTreeFromField(field, schema, sharedSkipList); 11 | 12 | if (queryTree !== null) { 13 | queries.push(`{ ${queryTreeToGraphQLString(queryTree)} }`); 14 | } 15 | }); 16 | 17 | return queries; 18 | }; 19 | -------------------------------------------------------------------------------- /src/schemaToQueryTree.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import * as graphql from 'graphql'; 3 | import descriptionParser from './descriptionParser'; 4 | 5 | const { getExamplesFrom, shouldFollow } = descriptionParser; 6 | 7 | /** 8 | * @example 9 | * isNotNullable({ type: { kind: 'NON_NULL' } }) // => true 10 | * isNotNullable({ 11 | * type: { 12 | * kind: 'LIST', 13 | * ofType: { kind: 'NON_NULL' } 14 | * } 15 | * }) // => true 16 | * isNotNullable({ type: { kind: 'OBJECT' } }) // => false 17 | */ 18 | function isNotNullable(arg) { 19 | const argType = arg.type.kind; 20 | 21 | if (argType === 'NON_NULL') { 22 | return true; 23 | } 24 | 25 | if (argType === 'LIST') { 26 | // we do not handle lists of lists of lists of lists 27 | return arg.type.ofType.kind === 'NON_NULL'; 28 | } 29 | 30 | return false; 31 | } 32 | 33 | /** 34 | * @example 35 | * getFields({ 36 | * type: { name: 'SomeTypeName' } 37 | * }, { 38 | * SomeTypeName: { 39 | * fields: [{ name: 'f1' }, { name: 'f2' }] 40 | * } 41 | * } 42 | * ) 43 | * // => [{ name: 'f1' }, { name: 'f2' }] 44 | */ 45 | function getFields(field, typeDictionary) { 46 | const typeName = magiclyExtractFieldTypeName(field); 47 | const allFields = typeDictionary[typeName].fields; 48 | // return _.filter(allFields, childField => !_.some(childField.args, isNotNullable)); 49 | return allFields; 50 | } 51 | 52 | /** 53 | * @example 54 | * getFieldNameOrExamplesIfNecessary({name: 'Name', args: []}) // => ['Name'] 55 | * getFieldNameOrExamplesIfNecessary({ 56 | * name: 'People', 57 | * args: [{type:{kind: 'NON_NULL'}}], 58 | * description: 'Examples: People(test: 1)' 59 | * }) 60 | * // => ['People(test: 1)'] 61 | * getFieldNameOrExamplesIfNecessary({ 62 | * name: 'People', 63 | * args: [{type:{kind: 'NULL'}}] 64 | * }) 65 | * // => ['People'] 66 | */ 67 | function getFieldNameOrExamplesIfNecessary(field) { 68 | if (!shouldFollow(field.description)) { 69 | return []; 70 | } 71 | 72 | if (!field.args || field.args.length === 0) { 73 | return [field.name]; 74 | } 75 | 76 | const queries = getExamplesFrom(field.description); 77 | 78 | if (queries.length === 0) { 79 | if (_.some(field.args, isNotNullable)) { 80 | // ignore fields that have parameters, but we have not specified yet 81 | return []; 82 | } 83 | return [field.name]; 84 | } 85 | 86 | return queries; 87 | } 88 | 89 | /** 90 | * @example 91 | * magiclyExtractFieldTypeName({ type: { name: 'Person' } }) // => 'Person' 92 | * magiclyExtractFieldTypeName({ 93 | * type: { 94 | * name: 'NotMe', 95 | * ofType: { 96 | * name: 'MeNeither', 97 | * ofType: { name: 'TestType' } 98 | * } 99 | * } 100 | * }) 101 | * // => 'TestType' 102 | */ 103 | function magiclyExtractFieldTypeName(field) { 104 | let typeName = field.type.name; 105 | var ofType = field.type.ofType; 106 | while (ofType) { 107 | typeName = ofType.name; 108 | ofType = ofType.ofType; 109 | } 110 | 111 | return typeName; 112 | } 113 | 114 | /** 115 | * @example 116 | * getSkipKey({ name: 'TypeName' }, { name: 'FieldName' }, { name: 'ParentTypeName' }) // => 'FieldName-TypeName-ParentTypeName' 117 | * getSkipKey({ name: 'TypeName' }, { name: 'FieldName' }, null) // => 'FieldName-TypeName-ROOT' 118 | */ 119 | function getSkipKey(fieldTypeDefinition, field, parentFieldTypeDefinition) { 120 | const parentFieldTypeName = parentFieldTypeDefinition ? parentFieldTypeDefinition.name : 'ROOT'; 121 | return `${field.name}-${fieldTypeDefinition.name}-${parentFieldTypeName}`; 122 | } 123 | 124 | const getQueryFieldsModes = { 125 | QUERYABLE_FIELDS: 'QUERYABLE_FIELDS', 126 | ALL_FIELDS: 'ALL_FIELDS' 127 | }; 128 | 129 | export default { 130 | getQueryFieldsModes: getQueryFieldsModes, 131 | getQueryFields: function getQueryFields(mode, field, typeDictionary, visitedFields, isRoot = true, parentFieldTypeDefinition = null) { 132 | let queryFields : Array = []; 133 | const fieldTypeName = magiclyExtractFieldTypeName(field); 134 | const fieldTypeDefinition = typeDictionary[fieldTypeName]; 135 | if (isRoot) { 136 | queryFields.push(`${fieldTypeName}___${field.name}`); 137 | if (mode === getQueryFieldsModes.QUERYABLE_FIELDS) { 138 | const queriesBecauseRootAndWeDontKnowIfWeShouldStart = getFieldNameOrExamplesIfNecessary(field); 139 | if (queriesBecauseRootAndWeDontKnowIfWeShouldStart.length === 0) { 140 | return []; 141 | } 142 | } 143 | } 144 | 145 | if (fieldTypeDefinition.fields === null) { // isLeafType 146 | // Current field has already been added one step up recursion chain 147 | // so we do not need to add it again 148 | return queryFields; 149 | } 150 | 151 | if (fieldTypeDefinition.kind === 'OBJECT') { 152 | visitedFields.push(getSkipKey(fieldTypeDefinition, field, parentFieldTypeDefinition)); 153 | } 154 | 155 | const allFields = getFields(field, typeDictionary); 156 | _.forIn(allFields, (childField) => { 157 | const childFieldTypeName = magiclyExtractFieldTypeName(childField); 158 | const childFieldType = typeDictionary[childFieldTypeName]; 159 | const noOfPossibleQueries = getFieldNameOrExamplesIfNecessary(childField).length; 160 | const wasChildFieldAlreadyVisited = visitedFields.indexOf(getSkipKey(childFieldType, childField, fieldTypeDefinition)) >= 0; 161 | 162 | if ( 163 | (noOfPossibleQueries > 0 || mode === getQueryFieldsModes.ALL_FIELDS) && 164 | !wasChildFieldAlreadyVisited 165 | ) { 166 | queryFields.push(`${fieldTypeName}___${childFieldTypeName}___${childField.name}`); 167 | const subWalk = getQueryFields(mode, childField, typeDictionary, visitedFields, false, fieldTypeDefinition); 168 | queryFields = queryFields.concat(subWalk); 169 | } 170 | }); 171 | 172 | return queryFields; 173 | }, 174 | buildQueryTreeFromField: function buildQueryTreeFromField(field, typeDictionary, skipped : Array = [], parentFieldTypeDefinition = null) { 175 | const fieldTypeName = magiclyExtractFieldTypeName(field); 176 | const fieldTypeDefinition = typeDictionary[fieldTypeName]; 177 | 178 | // this are base invocations for all subsequent queries 179 | const queriesForRootField = getFieldNameOrExamplesIfNecessary(field); 180 | if (fieldTypeDefinition.fields === null) { // isLeafType 181 | if (queriesForRootField.length === 0) { 182 | return null; 183 | } 184 | 185 | return queriesForRootField; 186 | } 187 | 188 | if (fieldTypeDefinition.kind === 'OBJECT') { 189 | skipped.push(getSkipKey(fieldTypeDefinition, field, parentFieldTypeDefinition)); 190 | } 191 | 192 | let queryNode : any | null = null; 193 | const allFields = getFields(field, typeDictionary); 194 | 195 | _.forIn(allFields, (childField) => { 196 | const childFieldTypeName = magiclyExtractFieldTypeName(childField); 197 | const childFieldType = typeDictionary[childFieldTypeName]; 198 | 199 | if (skipped.indexOf(getSkipKey(childFieldType, childField, fieldTypeDefinition)) === -1) { 200 | queriesForRootField.forEach((rootFieldQuery) => { 201 | queryNode = queryNode || {}; 202 | queryNode[rootFieldQuery] = queryNode[rootFieldQuery] || []; 203 | queryNode[rootFieldQuery].push(buildQueryTreeFromField(childField, typeDictionary, skipped, fieldTypeDefinition)); 204 | }); 205 | } 206 | }); 207 | 208 | return queryNode; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /test/acceptance/commentTests.js: -------------------------------------------------------------------------------- 1 | const should = require('chai').should(); 2 | const app = require('./exampleServer'); 3 | const QueryGenerator = require('../../lib/queryGenerator'); 4 | 5 | describe('Query generation', () => { 6 | 7 | const serverUrl = 'http://localhost:12345/graphql'; 8 | let queryPromise = null; 9 | 10 | before(() => app.then(()=>{ 11 | const queryGenerator = new QueryGenerator(serverUrl); 12 | queryPromise = queryGenerator.run(); 13 | })); 14 | 15 | it('Generates multiple queries', () => { 16 | return queryPromise 17 | .then(({queries}) => { 18 | (queries[0].match(/rollDice/g) || []).length.should.equal(4); 19 | }); 20 | 21 | }); 22 | 23 | it('Ignores fields with +NOFOLLOW in description', () => { 24 | return queryPromise 25 | .then(({queries, coverage}) => { 26 | (queries[0].match(/ignoredWithExamples/g) || []).length.should.equal(0); 27 | (queries[0].match(/ignoredNoParameters/g) || []).length.should.equal(0); 28 | }); 29 | }); 30 | 31 | it('Uses Examples section for scalar fields with non-nullable args', () => { 32 | return queryPromise 33 | .then(({queries}) => { 34 | // 8 because we have two examples for rollXTimes and 4 examples of parent 35 | (queries[0].match(/rollXTimes\(times. [0-9]+\)/g) || []).length.should.equal(8); 36 | }); 37 | }); 38 | 39 | it('Calculates valid coverage', () => { 40 | return queryPromise 41 | .then(({coverage}) => { 42 | coverage.coverageRatio.should.be.at.least(0); 43 | coverage.coverageRatio.should.be.at.most(1); 44 | if(coverage.coverageRatio < 1.0) { 45 | coverage.notCoveredFields.length.should.be.at.least(1); 46 | } else { 47 | coverage.notCoveredFields.length.should.equal(0); 48 | } 49 | }); 50 | }); 51 | 52 | }); -------------------------------------------------------------------------------- /test/acceptance/exampleServer.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var graphqlHTTP = require('express-graphql'); 3 | var { buildSchema } = require('graphql'); 4 | 5 | var schema = buildSchema(` 6 | 7 | type IgnoredSubtype { 8 | aValue: Int 9 | } 10 | 11 | type RandomnessStatistics { 12 | explanation: String! 13 | } 14 | 15 | type RandomDie { 16 | numSides: Int! 17 | rollOnce: Int! 18 | statistics(page: Int!): RandomnessStatistics! 19 | 20 | # Scalar field with non nullable arg and example section 21 | # Examples: 22 | # rollXTimes(times: 10) 23 | # rollXTimes(times: 11) 24 | rollXTimes(times: Int!): Int! 25 | 26 | # A description for ignored field with parameters 27 | # 28 | # Examples: 29 | # ignoredWithExamples(parameter: 42) 30 | # +NOFOLLOW 31 | ignoredWithExamples(parameter: Int!): IgnoredSubtype 32 | 33 | # +NOFOLLOW 34 | ignoredNoParameters: IgnoredSubtype 35 | } 36 | 37 | type Query { 38 | # RollDice has four examples 39 | # 40 | # Examples: 41 | # rollDice(numDice: 4, numSides: 2) 42 | # rollDice( numDice : 40 , numSides:2) 43 | # rollDice ( numDice: 2, numSides: 299 ) 44 | # rollDice ( 45 | # numDice:4, 46 | # numSides: 2342 47 | # ) 48 | rollDice(numDice: Int!, numSides: Int): RandomDie 49 | } 50 | `); 51 | 52 | class IgnoredSubtype { 53 | constructor() { 54 | this.aValue = 42; 55 | } 56 | } 57 | 58 | class RandomnessStatistics { 59 | constructor() { 60 | this.explanation = "Because we can"; 61 | } 62 | } 63 | 64 | class RandomDie { 65 | constructor() { 66 | this.numSides = 4; // chosen by fair dice roll 67 | this.rollOnce = 1; // guaranteed to be random 68 | this.rollXTimes = () => 12; 69 | this.statistics = () => new RandomnessStatistics(); 70 | this.ignoredWithExamples = () => new IgnoredSubtype(); 71 | this.ignoredNoParameters = () => new IgnoredSubtype(); 72 | } 73 | } 74 | 75 | var app = express(); 76 | 77 | app.use('/graphql', graphqlHTTP({ 78 | schema: schema, 79 | rootValue: { rollDice: () => { return new RandomDie(); } }, 80 | graphiql: true, 81 | })); 82 | 83 | 84 | 85 | module.exports = new Promise((resolve, reject) => { 86 | app.listen(12345, resolve); 87 | }); 88 | -------------------------------------------------------------------------------- /test/unit/mockData.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ScalarField: { 3 | name: 'ScalarField', 4 | kind: 'SCALAR', 5 | fields: null 6 | }, 7 | ObjectField: { 8 | name: 'ObjectField', 9 | kind: 'OBJECT', 10 | fields: [ 11 | { name: 'MyScalar', type: { name: 'ScalarField' }, args: [] }, 12 | { name: 'MyScalar2', type: { name: 'ScalarField' }, args: [] } 13 | ] 14 | }, 15 | ObjectNestingOtherObject: { 16 | name: 'ObjectNestingOtherObject', 17 | kind: 'OBJECT', 18 | fields: [ 19 | { name: 'NestedObject', type: { name: 'ObjectField' }, args: [] }, 20 | { name: 'NestedScalar', type: { name: 'ScalarField' }, args: [] } 21 | ] 22 | }, 23 | DeeplyNestedObject: { 24 | name: 'DeeplyNestedObject', 25 | kind: 'OBJECT', 26 | fields: [ 27 | { name: 'DeepNest', type: { name: 'DeeplyNestedObject' }, args: [] }, 28 | { name: 'NotSoDeepNest', type: { name: 'ObjectField' }, args: [] } 29 | ] 30 | }, 31 | DeeplyNestedObject2: { 32 | name: 'DeeplyNestedObject2', 33 | kind: 'OBJECT', 34 | fields: [ 35 | { name: 'DeepNest', type: { name: 'DeeplyNestedObject' }, args: [] } 36 | ] 37 | }, 38 | ObjectContainingTwoDeeplyNestedObjects: { 39 | name: 'ObjectContainingTwoDeeplyNestedObjects', 40 | kind: 'OBJECT', 41 | fields: [ 42 | { name: 'DeepNest', type: { name: 'DeeplyNestedObject' }, args: [] }, 43 | { name: 'DeepNest2', type: { name: 'DeeplyNestedObject2' }, args: [] } 44 | ] 45 | }, 46 | DeeplyNestedObjectWithPartialNoFollow: { 47 | name: 'DeeplyNestedObjectWithPartialNoFollow', 48 | kind: 'OBJECT', 49 | fields: [ 50 | { name: 'NotSoDeepNest', type: { name: 'ObjectField' }, args: [] }, 51 | { 52 | name: 'NOFollowPart', 53 | type: { name: 'DeeplyNestedObject' }, 54 | args: [ 55 | { 56 | "name": "ip", 57 | "description": "", 58 | "type": { 59 | "kind": "NON_NULL", 60 | "name": null, 61 | "ofType": { 62 | "kind": "SCALAR", 63 | "name": "String", 64 | "ofType": null 65 | } 66 | }, 67 | "defaultValue": null 68 | } 69 | ] 70 | } 71 | ] 72 | } 73 | }; -------------------------------------------------------------------------------- /test/unit/schemaToQueryTree/schemaToQueryTreeBase.test.js: -------------------------------------------------------------------------------- 1 | const should = require('chai').should(); 2 | const buildQueryTreeFromField = require('../../../lib/schemaToQueryTree').default.buildQueryTreeFromField; 3 | const mockData = require('../mockData'); 4 | 5 | describe('Build query tree from field', () => { 6 | let typeDictionary = null; 7 | 8 | beforeEach(() => { 9 | typeDictionary = mockData 10 | }); 11 | 12 | it('should handle simple fields', () => { 13 | buildQueryTreeFromField({ 14 | type: { 15 | name: 'ScalarField' 16 | }, 17 | name: 'FetchParentField', 18 | args: [] 19 | }, 20 | typeDictionary 21 | ).should.deep.equal(['FetchParentField']); 22 | }); 23 | 24 | it('should handle simple objects', () => { 25 | const ignoreList = []; 26 | const result = buildQueryTreeFromField({ 27 | type: { 28 | name: 'ObjectField' 29 | }, 30 | name: 'MyObjectField', 31 | args: [] 32 | }, 33 | typeDictionary, ignoreList 34 | ); 35 | result.MyObjectField[0].should.deep.equal(['MyScalar']); 36 | result.MyObjectField[1].should.deep.equal(['MyScalar2']); 37 | ignoreList.length.should.equal(1); 38 | ignoreList[0].should.equal('MyObjectField-ObjectField-ROOT'); 39 | }); 40 | 41 | it('should handle nested objects', () => { 42 | const ignoreList = []; 43 | const result = buildQueryTreeFromField({ 44 | type: { 45 | name: 'ObjectNestingOtherObject' 46 | }, 47 | name: 'MyObjectWithNested', 48 | args: [] 49 | }, 50 | typeDictionary, ignoreList 51 | ); 52 | result.MyObjectWithNested[0].should.include.all.keys('NestedObject'); 53 | result.MyObjectWithNested[0].NestedObject[0].should.deep.equal(['MyScalar']); 54 | result.MyObjectWithNested[0].NestedObject[1].should.deep.equal(['MyScalar2']); 55 | result.MyObjectWithNested[1].should.deep.equal(['NestedScalar']); 56 | ignoreList.length.should.equal(2); 57 | ignoreList[0].should.equal('MyObjectWithNested-ObjectNestingOtherObject-ROOT'); 58 | ignoreList[1].should.equal('NestedObject-ObjectField-ObjectNestingOtherObject'); 59 | }); 60 | 61 | it('should handle circular dependencies', () => { 62 | const ignoreList = []; 63 | const result = buildQueryTreeFromField({ 64 | type: { 65 | name: 'DeeplyNestedObject' 66 | }, 67 | name: 'MyCircle', 68 | args: [] 69 | }, 70 | typeDictionary, ignoreList 71 | ); 72 | result.MyCircle.length.should.equal(1); 73 | result.MyCircle[0].should.include.all.keys('DeepNest'); 74 | result.MyCircle[0].DeepNest.length.should.equal(1); 75 | result.MyCircle[0].DeepNest[0].NotSoDeepNest.length.should.equal(2); 76 | result.MyCircle[0].DeepNest[0].NotSoDeepNest[0].should.deep.equal(['MyScalar']); 77 | }); 78 | 79 | it('should handle very similar objects[test covering skipList naming bug]', () => { 80 | const ignoreList = []; 81 | const result = buildQueryTreeFromField({ 82 | type: { 83 | name: 'ObjectContainingTwoDeeplyNestedObjects' 84 | }, 85 | name: 'MyBug', 86 | args: [] 87 | }, 88 | typeDictionary, ignoreList 89 | ); 90 | result.MyBug.length.should.equal(2); 91 | should.not.equal(result.MyBug[0], null); 92 | should.not.equal(result.MyBug[1], null); 93 | }); 94 | 95 | it('should not support default value for non nullable args[NOT IMPLEMENTED YET!]', () => { 96 | const arg = { 97 | defaultValue: '192.168.0.1', 98 | description: "Comprehensive description.", 99 | name: "ip", 100 | type: {kind: "NON_NULL", name: null, ofType: {kind: "SCALAR", name: "String", ofType: null}} 101 | }; 102 | const result = buildQueryTreeFromField({ 103 | type: { 104 | name: 'ObjectField' 105 | }, 106 | name: 'MyObjectField', 107 | args: [arg] 108 | }, 109 | typeDictionary 110 | ); 111 | 112 | should.equal(null, result); 113 | }); 114 | 115 | it('should use example from description for non nullable args', () => { 116 | const arg = { 117 | defaultValue: null, 118 | description: "Comprehensive description.", 119 | name: "ip", 120 | type: {kind: "NON_NULL", name: null, ofType: {kind: "SCALAR", name: "String", ofType: null}} 121 | }; 122 | const result = buildQueryTreeFromField({ 123 | type: { 124 | name: 'ObjectField' 125 | }, 126 | name: 'MyObjectField', 127 | args: [arg], 128 | description: 'Example: MyObjectField(ip: "192.168.0.1")' 129 | }, 130 | typeDictionary 131 | ); 132 | result['MyObjectField(ip: "192.168.0.1")'][0].should.deep.equal(['MyScalar']); 133 | result['MyObjectField(ip: "192.168.0.1")'][1].should.deep.equal(['MyScalar2']); 134 | }); 135 | 136 | it('should ignore nullable args', () => { 137 | const arg = { 138 | defaultValue: null, 139 | description: "Comprehensive description.", 140 | name: "ip", 141 | type: {kind: "SCALAR", name: "String", ofType: null} 142 | }; 143 | const result = buildQueryTreeFromField({ 144 | type: { 145 | name: 'ObjectField' 146 | }, 147 | name: 'MyObjectField', 148 | args: [arg] 149 | }, 150 | typeDictionary 151 | ); 152 | result.MyObjectField[0].should.deep.equal(['MyScalar']); 153 | result.MyObjectField[1].should.deep.equal(['MyScalar2']); 154 | }); 155 | 156 | it('should ignore nullable args for SCALAR fields', () => { 157 | const arg = { 158 | defaultValue: null, 159 | description: "Comprehensive description.", 160 | name: "ip", 161 | type: {kind: "SCALAR", name: "String", ofType: null} 162 | }; 163 | const result = buildQueryTreeFromField({ 164 | type: { 165 | name: 'ScalarField' 166 | }, 167 | name: 'MyScalar', 168 | args: [arg] 169 | }, 170 | typeDictionary 171 | ); 172 | result.should.deep.equal(['MyScalar']); 173 | }); 174 | 175 | it('should use single example from description for non-nullable args for SCALAR fields', () => { 176 | const arg = { 177 | defaultValue: null, 178 | description: "Comprehensive description.", 179 | name: "ip", 180 | type: {kind: "NON_NULL", name: null, ofType: {kind: "SCALAR", name: "String", ofType: null}} 181 | }; 182 | const result = buildQueryTreeFromField({ 183 | type: { 184 | name: 'ScalarField' 185 | }, 186 | name: 'MyScalar', 187 | args: [arg], 188 | description: 'Example: MyScalar(ip: "192.168.0.1")' 189 | }, 190 | typeDictionary 191 | ); 192 | result.should.deep.equal(['MyScalar(ip: "192.168.0.1")']); 193 | }); 194 | 195 | it('should use multiple examples from description for non-nullable args for SCALAR fields', () => { 196 | const arg = { 197 | defaultValue: null, 198 | description: "Comprehensive description.", 199 | name: "ip", 200 | type: {kind: "NON_NULL", name: null, ofType: {kind: "SCALAR", name: "String", ofType: null}} 201 | }; 202 | const result = buildQueryTreeFromField({ 203 | type: { 204 | name: 'ScalarField' 205 | }, 206 | name: 'MyScalar', 207 | args: [arg], 208 | description: 'Example: \nMyScalar(ip: "192.168.0.1")\nMyScalar(ip: "192.168.0.2")' 209 | }, 210 | typeDictionary 211 | ); 212 | result.should.deep.equal(['MyScalar(ip: "192.168.0.1")','MyScalar(ip: "192.168.0.2")']); 213 | }); 214 | }); -------------------------------------------------------------------------------- /test/unit/schemaToQueryTree/schemaToQueryTreeCodeCoverage.test.js: -------------------------------------------------------------------------------- 1 | const should = require('chai').should(); 2 | const { getQueryFields, getQueryFieldsModes } = require('../../../lib/schemaToQueryTree').default; 3 | const mockData = require('../mockData'); 4 | 5 | describe('Build coverage', () => { 6 | let typeDictionary = null; 7 | 8 | beforeEach(() => { 9 | typeDictionary = mockData 10 | }); 11 | 12 | it('should be able to fetch all fields', () => { 13 | const result = getQueryFields( 14 | getQueryFieldsModes.ALL_FIELDS, 15 | { 16 | type: { 17 | name: 'DeeplyNestedObjectWithPartialNoFollow' 18 | }, 19 | name: 'Test', 20 | args: [] 21 | }, 22 | typeDictionary, 23 | [] 24 | ); 25 | result.length.should.equal(9); 26 | result.filter(r => r.indexOf('NOFollowPart') > 0).length.should.equal(1); 27 | }); 28 | 29 | it('should be able to fetch only queryable fields', () => { 30 | const result = getQueryFields( 31 | getQueryFieldsModes.QUERYABLE_FIELDS, 32 | { 33 | type: { 34 | name: 'DeeplyNestedObjectWithPartialNoFollow' 35 | }, 36 | name: 'Test', 37 | args: [] 38 | }, 39 | typeDictionary, 40 | [] 41 | ); 42 | result.length.should.equal(4); 43 | result.filter(r => r.indexOf('NOFollowPart') > 0).length.should.equal(0); 44 | }); 45 | 46 | it('should not return querable fields if root object is not querable', () => { 47 | const result = getQueryFields( 48 | getQueryFieldsModes.QUERYABLE_FIELDS, 49 | { 50 | name: 'NOFollowPart', 51 | type: { name: 'DeeplyNestedObject' }, 52 | args: [ 53 | { 54 | "name": "ip", 55 | "description": "", 56 | "type": { 57 | "kind": "NON_NULL", 58 | "name": null, 59 | "ofType": { 60 | "kind": "SCALAR", 61 | "name": "String", 62 | "ofType": null 63 | } 64 | }, 65 | "defaultValue": null 66 | } 67 | ] 68 | }, 69 | typeDictionary, 70 | []); 71 | result.length.should.equal(0); 72 | }); 73 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib/", // path to output directory 4 | "sourceMap": true, // allow sourcemap support 5 | "strictNullChecks": true, // enable strict null checks as a best practice 6 | "module": "none", // specify module code generation 7 | "moduleResolution": "node", 8 | "baseUrl": ".", 9 | "target": "es6", // specify ECMAScript target version 10 | "types": [ 11 | "node" 12 | ], 13 | "typeRoots": [ 14 | "node_modules/@types" 15 | ], 16 | "allowJs": true // allow a partial TypeScript and JavaScript codebase 17 | }, 18 | "include": [ 19 | "./src/" 20 | ] 21 | } --------------------------------------------------------------------------------