From 485b131ea9ffbd92a9fe5853338ba5e8b0050ca0 Mon Sep 17 00:00:00 2001 From: Joe Lutz Date: Fri, 10 Jun 2016 10:03:30 -0500 Subject: [PATCH 01/18] Fix typo (#387) --- src/type/__tests__/schema-test.js | 2 +- src/type/schema.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/type/__tests__/schema-test.js b/src/type/__tests__/schema-test.js index 881bc221b5..fd4b964548 100644 --- a/src/type/__tests__/schema-test.js +++ b/src/type/__tests__/schema-test.js @@ -53,7 +53,7 @@ describe('Type System: Schema', () => { }; expect(checkPossible).to.throw( 'Could not find possible implementing types for Interface in schema. ' + - 'Check that schema.types is defined and is an array ofall possible ' + + 'Check that schema.types is defined and is an array of all possible ' + 'types in the schema.' ); }); diff --git a/src/type/schema.js b/src/type/schema.js index 65dee8ac14..1beb360c3b 100644 --- a/src/type/schema.js +++ b/src/type/schema.js @@ -191,7 +191,7 @@ export class GraphQLSchema { invariant( Array.isArray(possibleTypes), `Could not find possible implementing types for ${abstractType} in ` + - 'schema. Check that schema.types is defined and is an array of' + + 'schema. Check that schema.types is defined and is an array of ' + 'all possible types in the schema.' ); possibleTypeMap[abstractType.name] = From 0bbdaecd51b776ba5ca36700910108ad1e607164 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Fri, 10 Jun 2016 23:04:03 +0800 Subject: [PATCH 02/18] Update babel-cli and flow-bin package references (#388) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f8ed1c83ff..78d2f2008e 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "babel-runtime": ">=6.0.0" }, "devDependencies": { - "babel-cli": "6.6.5", + "babel-cli": "6.7.7", "babel-eslint": "6.0.2", "babel-plugin-syntax-async-functions": "6.5.0", "babel-plugin-transform-class-properties": "6.6.0", @@ -65,7 +65,7 @@ "coveralls": "2.11.9", "eslint": "2.7.0", "eslint-plugin-babel": "3.2.0", - "flow-bin": "0.22.1", + "flow-bin": "0.24.2", "isparta": "4.0.0", "mocha": "2.4.5", "sane": "1.3.4" From 42dcb884d2ccdef5138031c2e1e534f983233525 Mon Sep 17 00:00:00 2001 From: Kevin Lacker Date: Fri, 10 Jun 2016 09:18:27 -0700 Subject: [PATCH 03/18] move babel config to the babelrc (#399) --- .babelrc | 13 +++++++++++++ .npmignore | 1 + package.json | 13 ------------- 3 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 .babelrc diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000000..b60c0720d8 --- /dev/null +++ b/.babelrc @@ -0,0 +1,13 @@ +{ + "presets": [ + "es2015" + ], + "plugins": [ + "syntax-async-functions", + "transform-class-properties", + "transform-flow-strip-types", + "transform-object-rest-spread", + "transform-regenerator", + "transform-runtime" + ] +} diff --git a/.npmignore b/.npmignore index f0fd2cb797..67debc79b0 100644 --- a/.npmignore +++ b/.npmignore @@ -7,6 +7,7 @@ .idea npm-debug.log +.babelrc CONTRIBUTING.md node_modules coverage diff --git a/package.json b/package.json index 78d2f2008e..01b154f5f9 100644 --- a/package.json +++ b/package.json @@ -20,19 +20,6 @@ "options": { "mocha": "--require ./resources/mocha-bootload --check-leaks --full-trace src/**/__tests__/**/*-test.js" }, - "babel": { - "presets": [ - "es2015" - ], - "plugins": [ - "syntax-async-functions", - "transform-class-properties", - "transform-flow-strip-types", - "transform-object-rest-spread", - "transform-regenerator", - "transform-runtime" - ] - }, "scripts": { "test": "npm run lint && npm run check && npm run testonly", "testonly": "babel-node ./node_modules/.bin/_mocha $npm_package_options_mocha", From 061807739999dd8d9b0d27bc66485fb2f65a9da1 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Fri, 10 Jun 2016 09:52:17 -0700 Subject: [PATCH 04/18] Revert "Update babel-cli and flow-bin package references" (#403) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 01b154f5f9..166a38448b 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "babel-runtime": ">=6.0.0" }, "devDependencies": { - "babel-cli": "6.7.7", + "babel-cli": "6.6.5", "babel-eslint": "6.0.2", "babel-plugin-syntax-async-functions": "6.5.0", "babel-plugin-transform-class-properties": "6.6.0", @@ -52,7 +52,7 @@ "coveralls": "2.11.9", "eslint": "2.7.0", "eslint-plugin-babel": "3.2.0", - "flow-bin": "0.24.2", + "flow-bin": "0.22.1", "isparta": "4.0.0", "mocha": "2.4.5", "sane": "1.3.4" From 572bbdaa3359d28c00fc4f450a4804c1d0cf0105 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Fri, 10 Jun 2016 10:25:15 -0700 Subject: [PATCH 05/18] Update all dependencies, include flow-specific lint handling --- .eslintrc | 11 +++++++++-- package.json | 24 +++++++++++++----------- src/language/lexer.js | 7 ++----- src/language/parser.js | 5 +++-- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/.eslintrc b/.eslintrc index 3a11e64e89..3c50bc0560 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,7 +2,9 @@ "parser": "babel-eslint", "plugins": [ - "babel" + "babel", + "flowtype", + "flow-vars" ], "env": { @@ -44,6 +46,11 @@ "array-bracket-spacing": 0, "generator-star-spacing": 0, + "flowtype/space-after-type-colon": [2, "always"], + "flowtype/space-before-type-colon": [2, "never"], + "flow-vars/define-flow-type": 2, + "flow-vars/use-flow-type": 2, + "arrow-spacing": 2, "block-scoped-var": 0, "brace-style": [2, "1tbs", {"allowSingleLine": true}], @@ -155,7 +162,7 @@ "no-return-assign": 2, "no-script-url": 2, "no-self-compare": 0, - "no-sequences": 2, + "no-sequences": 0, "no-shadow": 2, "no-shadow-restricted-names": 2, "no-spaced-func": 2, diff --git a/package.json b/package.json index 166a38448b..cd9417dbde 100644 --- a/package.json +++ b/package.json @@ -38,23 +38,25 @@ "babel-runtime": ">=6.0.0" }, "devDependencies": { - "babel-cli": "6.6.5", - "babel-eslint": "6.0.2", - "babel-plugin-syntax-async-functions": "6.5.0", - "babel-plugin-transform-class-properties": "6.6.0", - "babel-plugin-transform-flow-strip-types": "6.7.0", - "babel-plugin-transform-object-rest-spread": "6.6.5", - "babel-plugin-transform-regenerator": "6.6.5", - "babel-plugin-transform-runtime": "6.6.0", + "babel-cli": "6.9.0", + "babel-eslint": "6.0.4", + "babel-plugin-syntax-async-functions": "6.8.0", + "babel-plugin-transform-class-properties": "6.9.1", + "babel-plugin-transform-flow-strip-types": "6.8.0", + "babel-plugin-transform-object-rest-spread": "6.8.0", + "babel-plugin-transform-regenerator": "6.9.0", + "babel-plugin-transform-runtime": "6.9.0", "babel-preset-es2015": "6.6.0", "chai": "3.5.0", "chai-subset": "1.2.2", "coveralls": "2.11.9", - "eslint": "2.7.0", + "eslint": "2.11.1", "eslint-plugin-babel": "3.2.0", - "flow-bin": "0.22.1", + "eslint-plugin-flow-vars": "^0.4.0", + "eslint-plugin-flowtype": "2.2.7", + "flow-bin": "0.26.0", "isparta": "4.0.0", - "mocha": "2.4.5", + "mocha": "2.5.3", "sane": "1.3.4" } } diff --git a/src/language/lexer.js b/src/language/lexer.js index c3d783435e..d325b0ab65 100644 --- a/src/language/lexer.js +++ b/src/language/lexer.js @@ -193,22 +193,19 @@ function readToken(source: Source, fromPosition: number): Token { case 124: return makeToken(TokenKind.PIPE, position, position + 1); // } case 125: return makeToken(TokenKind.BRACE_R, position, position + 1); - // A-Z + // A-Z _ a-z case 65: case 66: case 67: case 68: case 69: case 70: case 71: case 72: case 73: case 74: case 75: case 76: case 77: case 78: case 79: case 80: case 81: case 82: case 83: case 84: case 85: case 86: case 87: case 88: case 89: case 90: - // _ case 95: - // a-z case 97: case 98: case 99: case 100: case 101: case 102: case 103: case 104: case 105: case 106: case 107: case 108: case 109: case 110: case 111: case 112: case 113: case 114: case 115: case 116: case 117: case 118: case 119: case 120: case 121: case 122: return readName(source, position); - // - + // - 0-9 case 45: - // 0-9 case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: return readNumber(source, position, code); diff --git a/src/language/parser.js b/src/language/parser.js index 11faabb4b0..e1fa423a1b 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -204,10 +204,11 @@ function parseDefinition(parser: Parser): Definition { if (peek(parser, TokenKind.NAME)) { switch (parser.token.value) { + // Note: subscription is an experimental non-spec addition. case 'query': case 'mutation': - // Note: subscription is an experimental non-spec addition. - case 'subscription': return parseOperationDefinition(parser); + case 'subscription': + return parseOperationDefinition(parser); case 'fragment': return parseFragmentDefinition(parser); From 6223245d14f37b472825bc6136798052ea23077f Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Fri, 10 Jun 2016 15:10:57 -0700 Subject: [PATCH 06/18] Errors thrown from resolvers have the execution path (#396) * Errors thrown from resolvers have the execution path This path is also passed in the `info` object to resolvers. This information is useful for ease of debugging and more detailed logging. * Remove PathedError * rename property executionPath to path * remove an unnecessary block * info.executionPath -> info.path * a minor tweak to make the body of executeFields look closer to executeFieldsSerially * remove the unnecessary clone of info * Add a test for a path with non-nullable fields * stylistic changes * remove stray property --- src/__tests__/starWarsIntrospection-test.js | 15 ++ src/__tests__/starWarsQuery-test.js | 164 ++++++++++++++++++++ src/__tests__/starWarsSchema.js | 21 +++ src/error/GraphQLError.js | 1 + src/error/locatedError.js | 4 +- src/execution/execute.js | 80 ++++++++-- src/type/definition.js | 1 + 7 files changed, 272 insertions(+), 14 deletions(-) diff --git a/src/__tests__/starWarsIntrospection-test.js b/src/__tests__/starWarsIntrospection-test.js index 6646825e34..e9faa7f95b 100644 --- a/src/__tests__/starWarsIntrospection-test.js +++ b/src/__tests__/starWarsIntrospection-test.js @@ -205,6 +205,13 @@ describe('Star Wars Introspection Tests', () => { kind: 'LIST' } }, + { + name: 'secretBackstory', + type: { + name: 'String', + kind: 'SCALAR' + } + }, { name: 'primaryFunction', type: { @@ -284,6 +291,14 @@ describe('Star Wars Introspection Tests', () => { } } }, + { + name: 'secretBackstory', + type: { + name: 'String', + kind: 'SCALAR', + ofType: null + } + }, { name: 'primaryFunction', type: { diff --git a/src/__tests__/starWarsQuery-test.js b/src/__tests__/starWarsQuery-test.js index 180e12b3af..ea3e51c798 100644 --- a/src/__tests__/starWarsQuery-test.js +++ b/src/__tests__/starWarsQuery-test.js @@ -11,6 +11,12 @@ import { expect } from 'chai'; import { describe, it } from 'mocha'; import { StarWarsSchema } from './starWarsSchema.js'; import { graphql } from '../graphql'; +import { + GraphQLObjectType, + GraphQLNonNull, + GraphQLSchema, + GraphQLString, +} from '../type'; // 80+ char lines are useful in describe/it, so ignore in this file. /* eslint-disable max-len */ @@ -364,4 +370,162 @@ describe('Star Wars Query Tests', () => { expect(result).to.deep.equal({ data: expected }); }); }); + + describe('Reporting errors raised in resolvers', () => { + it('Correctly reports error on accessing secretBackstory', async () => { + const query = ` + query HeroNameQuery { + hero { + name + secretBackstory + } + } + `; + const expected = { + hero: { + name: 'R2-D2', + secretBackstory: null + } + }; + const expectedErrors = [ 'secretBackstory is secret.' ]; + const result = await graphql(StarWarsSchema, query); + expect(result.data).to.deep.equal(expected); + expect(result.errors.map(e => e.message)).to.deep.equal(expectedErrors); + expect( + result.errors.map(e => e.path)).to.deep.equal( + [ [ 'hero', 'secretBackstory' ] ]); + }); + + it('Correctly reports error on accessing secretBackstory in a list', async () => { + const query = ` + query HeroNameQuery { + hero { + name + friends { + name + secretBackstory + } + } + } + `; + const expected = { + hero: { + name: 'R2-D2', + friends: [ + { + name: 'Luke Skywalker', + secretBackstory: null, + }, + { + name: 'Han Solo', + secretBackstory: null, + }, + { + name: 'Leia Organa', + secretBackstory: null, + }, + ] + } + }; + const expectedErrors = [ + 'secretBackstory is secret.', + 'secretBackstory is secret.', + 'secretBackstory is secret.', + ]; + const result = await graphql(StarWarsSchema, query); + expect(result.data).to.deep.equal(expected); + expect(result.errors.map(e => e.message)).to.deep.equal(expectedErrors); + expect( + result.errors.map(e => e.path) + ).to.deep.equal( + [ + [ 'hero', 'friends', 0, 'secretBackstory' ], + [ 'hero', 'friends', 1, 'secretBackstory' ], + [ 'hero', 'friends', 2, 'secretBackstory' ], + ]); + }); + + it('Correctly reports error on accessing through an alias', async () => { + const query = ` + query HeroNameQuery { + mainHero: hero { + name + story: secretBackstory + } + } + `; + const expected = { + mainHero: { + name: 'R2-D2', + story: null, + } + }; + const expectedErrors = [ + 'secretBackstory is secret.', + ]; + const result = await graphql(StarWarsSchema, query); + expect(result.data).to.deep.equal(expected); + expect(result.errors.map(e => e.message)).to.deep.equal(expectedErrors); + expect( + result.errors.map(e => e.path) + ).to.deep.equal([ [ 'mainHero', 'story' ] ]); + }); + + it('Full response path is included when fields are non-nullable', async () => { + const A = new GraphQLObjectType({ + name: 'A', + fields: () => ({ + nullableA: { + type: A, + resolve: () => ({}), + }, + nonNullA: { + type: new GraphQLNonNull(A), + resolve: () => ({}), + }, + throws: { + type: new GraphQLNonNull(GraphQLString), + resolve: () => { throw new Error('Catch me if you can'); }, + }, + }), + }); + const queryType = new GraphQLObjectType({ + name: 'query', + fields: () => ({ + nullableA: { + type: A, + resolve: () => ({}) + } + }), + }); + const schema = new GraphQLSchema({ + query: queryType, + }); + + const query = ` + query { + nullableA { + nullableA { + nonNullA { + nonNullA { + throws + } + } + } + } + } + `; + + const result = await graphql(schema, query); + const expected = { + nullableA: { + nullableA: null + } + }; + expect(result.data).to.deep.equal(expected); + expect( + result.errors.map(e => e.path)).to.deep.equal( + [ [ 'nullableA', 'nullableA', 'nonNullA', 'nonNullA', 'throws' ] ]); + }); + }); }); diff --git a/src/__tests__/starWarsSchema.js b/src/__tests__/starWarsSchema.js index e315986a66..0c9c34b637 100644 --- a/src/__tests__/starWarsSchema.js +++ b/src/__tests__/starWarsSchema.js @@ -102,6 +102,7 @@ const episodeEnum = new GraphQLEnumType({ * name: String * friends: [Character] * appearsIn: [Episode] + * secretBackstory: String * } */ const characterInterface = new GraphQLInterfaceType({ @@ -125,6 +126,10 @@ const characterInterface = new GraphQLInterfaceType({ type: new GraphQLList(episodeEnum), description: 'Which movies they appear in.', }, + secretBackstory: { + type: GraphQLString, + description: 'All secrets about their past.', + }, }), resolveType: character => { return getHuman(character.id) ? humanType : droidType; @@ -140,6 +145,7 @@ const characterInterface = new GraphQLInterfaceType({ * name: String * friends: [Character] * appearsIn: [Episode] + * secretBackstory: String * } */ const humanType = new GraphQLObjectType({ @@ -168,6 +174,13 @@ const humanType = new GraphQLObjectType({ type: GraphQLString, description: 'The home planet of the human, or null if unknown.', }, + secretBackstory: { + type: GraphQLString, + description: 'Where are they from and how they came to be who they are.', + resolve: () => { + throw new Error('secretBackstory is secret.'); + }, + }, }), interfaces: [ characterInterface ] }); @@ -181,6 +194,7 @@ const humanType = new GraphQLObjectType({ * name: String * friends: [Character] * appearsIn: [Episode] + * secretBackstory: String * primaryFunction: String * } */ @@ -206,6 +220,13 @@ const droidType = new GraphQLObjectType({ type: new GraphQLList(episodeEnum), description: 'Which movies they appear in.', }, + secretBackstory: { + type: GraphQLString, + description: 'Construction date and the name of the designer.', + resolve: () => { + throw new Error('secretBackstory is secret.'); + }, + }, primaryFunction: { type: GraphQLString, description: 'The primary function of the droid.', diff --git a/src/error/GraphQLError.js b/src/error/GraphQLError.js index 08fc5b883d..abd5760f6f 100644 --- a/src/error/GraphQLError.js +++ b/src/error/GraphQLError.js @@ -20,6 +20,7 @@ export class GraphQLError extends Error { source: Source; positions: Array; locations: any; + path: Array; originalError: ?Error; constructor( diff --git a/src/error/locatedError.js b/src/error/locatedError.js index 6bab9071ac..252fdf3cbd 100644 --- a/src/error/locatedError.js +++ b/src/error/locatedError.js @@ -18,7 +18,8 @@ import { GraphQLError } from './GraphQLError'; */ export function locatedError( originalError: ?Error, - nodes: Array + nodes: Array, + path: Array ): GraphQLError { const message = originalError ? originalError.message || String(originalError) : @@ -26,5 +27,6 @@ export function locatedError( const stack = originalError ? originalError.stack : null; const error = new GraphQLError(message, nodes, stack); error.originalError = originalError; + error.path = path; return error; } diff --git a/src/execution/execute.js b/src/execution/execute.js index 29e9de0e97..14cd51c847 100644 --- a/src/execution/execute.js +++ b/src/execution/execute.js @@ -237,10 +237,12 @@ function executeOperation( Object.create(null) ); + const exePath = []; + if (operation.operation === 'mutation') { - return executeFieldsSerially(exeContext, type, rootValue, fields); + return executeFieldsSerially(exeContext, type, rootValue, exePath, fields); } - return executeFields(exeContext, type, rootValue, fields); + return executeFields(exeContext, type, rootValue, exePath, fields); } /** @@ -287,16 +289,22 @@ function executeFieldsSerially( exeContext: ExecutionContext, parentType: GraphQLObjectType, sourceValue: mixed, + exePath: Array, fields: {[key: string]: Array} ): Promise { return Object.keys(fields).reduce( (prevPromise, responseName) => prevPromise.then(results => { const fieldASTs = fields[responseName]; + + const childExePath = exePath.slice(); + childExePath.push(responseName); + const result = resolveField( exeContext, parentType, sourceValue, - fieldASTs + fieldASTs, + childExePath ); if (result === undefined) { return results; @@ -322,6 +330,7 @@ function executeFields( exeContext: ExecutionContext, parentType: GraphQLObjectType, sourceValue: mixed, + exePath: Array, fields: {[key: string]: Array} ): Object { let containsPromise = false; @@ -329,11 +338,16 @@ function executeFields( const finalResults = Object.keys(fields).reduce( (results, responseName) => { const fieldASTs = fields[responseName]; + + const childExePath = exePath.slice(); + childExePath.push(responseName); + const result = resolveField( exeContext, parentType, sourceValue, - fieldASTs + fieldASTs, + childExePath ); if (result === undefined) { return results; @@ -526,7 +540,8 @@ function resolveField( exeContext: ExecutionContext, parentType: GraphQLObjectType, source: mixed, - fieldASTs: Array + fieldASTs: Array, + exePath: Array ): mixed { const fieldAST = fieldASTs[0]; const fieldName = fieldAST.name.value; @@ -565,6 +580,7 @@ function resolveField( rootValue: exeContext.rootValue, operation: exeContext.operation, variableValues: exeContext.variableValues, + path: exePath }; // Get the resolve function, regardless of if its result is normal @@ -576,6 +592,7 @@ function resolveField( returnType, fieldASTs, info, + exePath, result ); } @@ -605,12 +622,20 @@ function completeValueCatchingError( returnType: GraphQLType, fieldASTs: Array, info: GraphQLResolveInfo, + exePath: Array, result: mixed ): mixed { // If the field type is non-nullable, then it is resolved without any // protection from errors. if (returnType instanceof GraphQLNonNull) { - return completeValue(exeContext, returnType, fieldASTs, info, result); + return completeValue( + exeContext, + returnType, + fieldASTs, + info, + exePath, + result + ); } // Otherwise, error protection is applied, logging the error and resolving @@ -621,6 +646,7 @@ function completeValueCatchingError( returnType, fieldASTs, info, + exePath, result ); if (isThenable(completed)) { @@ -668,6 +694,7 @@ function completeValue( returnType: GraphQLType, fieldASTs: Array, info: GraphQLResolveInfo, + exePath: Array, result: mixed ): mixed { // If result is a Promise, apply-lift over completeValue. @@ -679,16 +706,17 @@ function completeValue( returnType, fieldASTs, info, + exePath, resolved ), // If rejected, create a located error, and continue to reject. - error => Promise.reject(locatedError(error, fieldASTs)) + error => Promise.reject(locatedError(error, fieldASTs, exePath)) ); } // If result is an Error, throw a located error. if (result instanceof Error) { - throw locatedError(result, fieldASTs); + throw locatedError(result, fieldASTs, exePath); } // If field type is NonNull, complete for inner type, and throw field error @@ -699,6 +727,7 @@ function completeValue( returnType.ofType, fieldASTs, info, + exePath, result ); if (completed === null) { @@ -718,7 +747,14 @@ function completeValue( // If field type is List, complete each item in the list with the inner type if (returnType instanceof GraphQLList) { - return completeListValue(exeContext, returnType, fieldASTs, info, result); + return completeListValue( + exeContext, + returnType, + fieldASTs, + info, + exePath, + result + ); } // If field type is a leaf type, Scalar or Enum, serialize to a valid value, @@ -737,6 +773,7 @@ function completeValue( returnType, fieldASTs, info, + exePath, result ); } @@ -748,6 +785,7 @@ function completeValue( returnType, fieldASTs, info, + exePath, result ); } @@ -768,6 +806,7 @@ function completeListValue( returnType: GraphQLList, fieldASTs: Array, info: GraphQLResolveInfo, + exePath: Array, result: mixed ): mixed { invariant( @@ -780,9 +819,21 @@ function completeListValue( // where the list contains no Promises by avoiding creating another Promise. const itemType = returnType.ofType; let containsPromise = false; - const completedResults = result.map(item => { - const completedItem = - completeValueCatchingError(exeContext, itemType, fieldASTs, info, item); + const completedResults = result.map((item, index) => { + // No need to modify the info object containing the path, + // since from here on it is not ever accessed by resolver functions. + const childExePath = exePath.slice(); + childExePath.push(index); + + const completedItem = completeValueCatchingError( + exeContext, + itemType, + fieldASTs, + info, + childExePath, + item + ); + if (!containsPromise && isThenable(completedItem)) { containsPromise = true; } @@ -814,6 +865,7 @@ function completeAbstractValue( returnType: GraphQLAbstractType, fieldASTs: Array, info: GraphQLResolveInfo, + exePath: Array, result: mixed ): mixed { const runtimeType = returnType.resolveType ? @@ -842,6 +894,7 @@ function completeAbstractValue( runtimeType, fieldASTs, info, + exePath, result ); } @@ -854,6 +907,7 @@ function completeObjectValue( returnType: GraphQLObjectType, fieldASTs: Array, info: GraphQLResolveInfo, + exePath: Array, result: mixed ): mixed { // If there is an isTypeOf predicate function, call it with the @@ -883,7 +937,7 @@ function completeObjectValue( } } - return executeFields(exeContext, returnType, result, subFieldASTs); + return executeFields(exeContext, returnType, result, exePath, subFieldASTs); } /** diff --git a/src/type/definition.js b/src/type/definition.js index 70f0bf2a6e..22d705ce2e 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -483,6 +483,7 @@ export type GraphQLResolveInfo = { rootValue: mixed, operation: OperationDefinition, variableValues: { [variableName: string]: mixed }, + path: Array } export type GraphQLFieldConfig = { From 1ed070fed63767e82470ca39d036539b99693c4c Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Fri, 10 Jun 2016 15:17:30 -0700 Subject: [PATCH 07/18] Variable naming follow-up to path generation --- src/error/locatedError.js | 2 +- src/execution/execute.js | 68 +++++++++++++++++---------------------- src/type/definition.js | 20 ++++++------ 3 files changed, 41 insertions(+), 49 deletions(-) diff --git a/src/error/locatedError.js b/src/error/locatedError.js index 252fdf3cbd..4bea3c57c9 100644 --- a/src/error/locatedError.js +++ b/src/error/locatedError.js @@ -26,7 +26,7 @@ export function locatedError( 'An unknown error occurred.'; const stack = originalError ? originalError.stack : null; const error = new GraphQLError(message, nodes, stack); - error.originalError = originalError; error.path = path; + error.originalError = originalError; return error; } diff --git a/src/execution/execute.js b/src/execution/execute.js index 14cd51c847..18b426e6eb 100644 --- a/src/execution/execute.js +++ b/src/execution/execute.js @@ -237,12 +237,12 @@ function executeOperation( Object.create(null) ); - const exePath = []; + const path = []; if (operation.operation === 'mutation') { - return executeFieldsSerially(exeContext, type, rootValue, exePath, fields); + return executeFieldsSerially(exeContext, type, rootValue, path, fields); } - return executeFields(exeContext, type, rootValue, exePath, fields); + return executeFields(exeContext, type, rootValue, path, fields); } /** @@ -289,22 +289,19 @@ function executeFieldsSerially( exeContext: ExecutionContext, parentType: GraphQLObjectType, sourceValue: mixed, - exePath: Array, + path: Array, fields: {[key: string]: Array} ): Promise { return Object.keys(fields).reduce( (prevPromise, responseName) => prevPromise.then(results => { const fieldASTs = fields[responseName]; - - const childExePath = exePath.slice(); - childExePath.push(responseName); - + const fieldPath = path.concat([ responseName ]); const result = resolveField( exeContext, parentType, sourceValue, fieldASTs, - childExePath + fieldPath ); if (result === undefined) { return results; @@ -330,7 +327,7 @@ function executeFields( exeContext: ExecutionContext, parentType: GraphQLObjectType, sourceValue: mixed, - exePath: Array, + path: Array, fields: {[key: string]: Array} ): Object { let containsPromise = false; @@ -338,16 +335,13 @@ function executeFields( const finalResults = Object.keys(fields).reduce( (results, responseName) => { const fieldASTs = fields[responseName]; - - const childExePath = exePath.slice(); - childExePath.push(responseName); - + const fieldPath = path.concat([ responseName ]); const result = resolveField( exeContext, parentType, sourceValue, fieldASTs, - childExePath + fieldPath ); if (result === undefined) { return results; @@ -541,7 +535,7 @@ function resolveField( parentType: GraphQLObjectType, source: mixed, fieldASTs: Array, - exePath: Array + path: Array ): mixed { const fieldAST = fieldASTs[0]; const fieldName = fieldAST.name.value; @@ -575,12 +569,12 @@ function resolveField( fieldASTs, returnType, parentType, + path, schema: exeContext.schema, fragments: exeContext.fragments, rootValue: exeContext.rootValue, operation: exeContext.operation, variableValues: exeContext.variableValues, - path: exePath }; // Get the resolve function, regardless of if its result is normal @@ -592,7 +586,7 @@ function resolveField( returnType, fieldASTs, info, - exePath, + path, result ); } @@ -622,7 +616,7 @@ function completeValueCatchingError( returnType: GraphQLType, fieldASTs: Array, info: GraphQLResolveInfo, - exePath: Array, + path: Array, result: mixed ): mixed { // If the field type is non-nullable, then it is resolved without any @@ -633,7 +627,7 @@ function completeValueCatchingError( returnType, fieldASTs, info, - exePath, + path, result ); } @@ -646,7 +640,7 @@ function completeValueCatchingError( returnType, fieldASTs, info, - exePath, + path, result ); if (isThenable(completed)) { @@ -694,7 +688,7 @@ function completeValue( returnType: GraphQLType, fieldASTs: Array, info: GraphQLResolveInfo, - exePath: Array, + path: Array, result: mixed ): mixed { // If result is a Promise, apply-lift over completeValue. @@ -706,17 +700,17 @@ function completeValue( returnType, fieldASTs, info, - exePath, + path, resolved ), // If rejected, create a located error, and continue to reject. - error => Promise.reject(locatedError(error, fieldASTs, exePath)) + error => Promise.reject(locatedError(error, fieldASTs, path)) ); } // If result is an Error, throw a located error. if (result instanceof Error) { - throw locatedError(result, fieldASTs, exePath); + throw locatedError(result, fieldASTs, path); } // If field type is NonNull, complete for inner type, and throw field error @@ -727,7 +721,7 @@ function completeValue( returnType.ofType, fieldASTs, info, - exePath, + path, result ); if (completed === null) { @@ -752,7 +746,7 @@ function completeValue( returnType, fieldASTs, info, - exePath, + path, result ); } @@ -773,7 +767,7 @@ function completeValue( returnType, fieldASTs, info, - exePath, + path, result ); } @@ -785,7 +779,7 @@ function completeValue( returnType, fieldASTs, info, - exePath, + path, result ); } @@ -806,7 +800,7 @@ function completeListValue( returnType: GraphQLList, fieldASTs: Array, info: GraphQLResolveInfo, - exePath: Array, + path: Array, result: mixed ): mixed { invariant( @@ -822,15 +816,13 @@ function completeListValue( const completedResults = result.map((item, index) => { // No need to modify the info object containing the path, // since from here on it is not ever accessed by resolver functions. - const childExePath = exePath.slice(); - childExePath.push(index); - + const fieldPath = path.concat([ index ]); const completedItem = completeValueCatchingError( exeContext, itemType, fieldASTs, info, - childExePath, + fieldPath, item ); @@ -865,7 +857,7 @@ function completeAbstractValue( returnType: GraphQLAbstractType, fieldASTs: Array, info: GraphQLResolveInfo, - exePath: Array, + path: Array, result: mixed ): mixed { const runtimeType = returnType.resolveType ? @@ -894,7 +886,7 @@ function completeAbstractValue( runtimeType, fieldASTs, info, - exePath, + path, result ); } @@ -907,7 +899,7 @@ function completeObjectValue( returnType: GraphQLObjectType, fieldASTs: Array, info: GraphQLResolveInfo, - exePath: Array, + path: Array, result: mixed ): mixed { // If there is an isTypeOf predicate function, call it with the @@ -937,7 +929,7 @@ function completeObjectValue( } } - return executeFields(exeContext, returnType, result, exePath, subFieldASTs); + return executeFields(exeContext, returnType, result, path, subFieldASTs); } /** diff --git a/src/type/definition.js b/src/type/definition.js index 22d705ce2e..c6d8945852 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -474,16 +474,16 @@ export type GraphQLFieldResolveFn = ( ) => mixed export type GraphQLResolveInfo = { - fieldName: string, - fieldASTs: Array, - returnType: GraphQLOutputType, - parentType: GraphQLCompositeType, - schema: GraphQLSchema, - fragments: { [fragmentName: string]: FragmentDefinition }, - rootValue: mixed, - operation: OperationDefinition, - variableValues: { [variableName: string]: mixed }, - path: Array + fieldName: string; + fieldASTs: Array; + returnType: GraphQLOutputType; + parentType: GraphQLCompositeType; + path: Array; + schema: GraphQLSchema; + fragments: { [fragmentName: string]: FragmentDefinition }; + rootValue: mixed; + operation: OperationDefinition; + variableValues: { [variableName: string]: mixed }; } export type GraphQLFieldConfig = { From 63f9985c535352f0d503807d1881c0b01a0cd868 Mon Sep 17 00:00:00 2001 From: Pavel Chertorogov Date: Thu, 30 Jun 2016 23:09:45 +0600 Subject: [PATCH 08/18] export type InputObjectConfigFieldMapThunk (#411) Needs for external flow type checking. --- src/type/definition.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/type/definition.js b/src/type/definition.js index c6d8945852..1ae929a287 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -917,7 +917,7 @@ export type InputObjectConfig = { description?: ?string; } -type InputObjectConfigFieldMapThunk = () => InputObjectConfigFieldMap; +export type InputObjectConfigFieldMapThunk = () => InputObjectConfigFieldMap; export type InputObjectFieldConfig = { type: GraphQLInputType; From 887183422f138ca8eeb71a9b3c3390d74542dec9 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Tue, 5 Jul 2016 15:25:50 -0700 Subject: [PATCH 09/18] Fix some flow issues in anticipation of Flow v0.28 --- src/execution/execute.js | 26 +++++++------- src/language/lexer.js | 5 +-- src/language/parser.js | 10 +++--- src/type/definition.js | 76 +++++++++++++++++++++------------------- src/type/schema.js | 26 +++++++------- 5 files changed, 75 insertions(+), 68 deletions(-) diff --git a/src/execution/execute.js b/src/execution/execute.js index 18b426e6eb..c53cb3a42e 100644 --- a/src/execution/execute.js +++ b/src/execution/execute.js @@ -307,7 +307,7 @@ function executeFieldsSerially( return results; } if (isThenable(result)) { - return ((result: any): Promise).then(resolvedResult => { + return ((result: any): Promise<*>).then(resolvedResult => { results[responseName] = resolvedResult; return results; }); @@ -648,7 +648,7 @@ function completeValueCatchingError( // error and resolve to null. // Note: we don't rely on a `catch` method, but we do expect "thenable" // to take a second callback for the error case. - return ((completed: any): Promise).then(undefined, error => { + return ((completed: any): Promise<*>).then(undefined, error => { exeContext.errors.push(error); return Promise.resolve(null); }); @@ -693,7 +693,7 @@ function completeValue( ): mixed { // If result is a Promise, apply-lift over completeValue. if (isThenable(result)) { - return ((result: any): Promise).then( + return ((result: any): Promise<*>).then( // Once resolved to a value, complete that value. resolved => completeValue( exeContext, @@ -727,7 +727,7 @@ function completeValue( if (completed === null) { throw new GraphQLError( `Cannot return null for non-nullable field ${ - info.parentType}.${info.fieldName}.`, + info.parentType.name}.${info.fieldName}.`, fieldASTs ); } @@ -787,7 +787,7 @@ function completeValue( // Not reachable. All possible output types have been considered. invariant( false, - `Cannot complete value of unexpected type "${returnType}".` + `Cannot complete value of unexpected type "${String(returnType)}".` ); } @@ -797,7 +797,7 @@ function completeValue( */ function completeListValue( exeContext: ExecutionContext, - returnType: GraphQLList, + returnType: GraphQLList<*>, fieldASTs: Array, info: GraphQLResolveInfo, path: Array, @@ -806,7 +806,7 @@ function completeListValue( invariant( Array.isArray(result), `User Error: expected iterable, but did not find one for field ${ - info.parentType}.${info.fieldName}.` + info.parentType.name}.${info.fieldName}.` ); // This is specified as a simple map, however we're optimizing the path @@ -866,17 +866,17 @@ function completeAbstractValue( if (!(runtimeType instanceof GraphQLObjectType)) { throw new GraphQLError( - `Abstract type ${returnType} must resolve to an Object type at runtime ` + - `for field ${info.parentType}.${info.fieldName} with value "${result}",` + - `received "${runtimeType}".`, + `Abstract type ${returnType.name} must resolve to an Object type at ` + + `runtime for field ${info.parentType.name}.${info.fieldName} with ` + + `value "${String(result)}", received "${String(runtimeType)}".`, fieldASTs ); } if (!exeContext.schema.isPossibleType(returnType, runtimeType)) { throw new GraphQLError( - `Runtime Object type "${runtimeType}" is not a possible type ` + - `for "${returnType}".`, + `Runtime Object type "${runtimeType.name}" is not a possible type ` + + `for "${returnType.name}".`, fieldASTs ); } @@ -908,7 +908,7 @@ function completeObjectValue( if (returnType.isTypeOf && !returnType.isTypeOf(result, exeContext.contextValue, info)) { throw new GraphQLError( - `Expected value of type "${returnType}" but got: ${result}.`, + `Expected value of type "${returnType.name}" but got: ${String(result)}.`, fieldASTs ); } diff --git a/src/language/lexer.js b/src/language/lexer.js index d325b0ab65..ac70f21713 100644 --- a/src/language/lexer.js +++ b/src/language/lexer.js @@ -75,8 +75,9 @@ export const TokenKind = { * A helper function to describe a token as a string for debugging */ export function getTokenDesc(token: Token): string { - return token.value ? - `${getTokenKindDesc(token.kind)} "${token.value}"` : + const value = token.value; + return value ? + `${getTokenKindDesc(token.kind)} "${value}"` : getTokenKindDesc(token.kind); } diff --git a/src/language/parser.js b/src/language/parser.js index e1fa423a1b..fd512e1a9a 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -492,21 +492,21 @@ function parseValueLiteral(parser: Parser, isConst: boolean): Value { case TokenKind.INT: advance(parser); return { - kind: INT, + kind: (INT: 'IntValue'), value: ((token.value: any): string), loc: loc(parser, token.start) }; case TokenKind.FLOAT: advance(parser); return { - kind: FLOAT, + kind: (FLOAT: 'FloatValue'), value: ((token.value: any): string), loc: loc(parser, token.start) }; case TokenKind.STRING: advance(parser); return { - kind: STRING, + kind: (STRING: 'StringValue'), value: ((token.value: any): string), loc: loc(parser, token.start) }; @@ -514,14 +514,14 @@ function parseValueLiteral(parser: Parser, isConst: boolean): Value { if (token.value === 'true' || token.value === 'false') { advance(parser); return { - kind: BOOLEAN, + kind: (BOOLEAN: 'BooleanValue'), value: token.value === 'true', loc: loc(parser, token.start) }; } else if (token.value !== 'null') { advance(parser); return { - kind: ENUM, + kind: (ENUM: 'EnumValue'), value: ((token.value: any): string), loc: loc(parser, token.start) }; diff --git a/src/type/definition.js b/src/type/definition.js index 1ae929a287..f8ad1bc95e 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -217,15 +217,16 @@ export class GraphQLScalarType { this.description = config.description; invariant( typeof config.serialize === 'function', - `${this} must provide "serialize" function. If this custom Scalar is ` + - 'also used as an input type, ensure "parseValue" and "parseLiteral" ' + + `${this.name} must provide "serialize" function. If this custom Scalar ` + + 'is also used as an input type, ensure "parseValue" and "parseLiteral" ' + 'functions are also provided.' ); if (config.parseValue || config.parseLiteral) { invariant( typeof config.parseValue === 'function' && typeof config.parseLiteral === 'function', - `${this} must provide both "parseValue" and "parseLiteral" functions.` + `${this.name} must provide both "parseValue" and "parseLiteral" ` + + 'functions.' ); } this._scalarConfig = config; @@ -315,7 +316,7 @@ export class GraphQLObjectType { if (config.isTypeOf) { invariant( typeof config.isTypeOf === 'function', - `${this} must provide "isTypeOf" as a function.` + `${this.name} must provide "isTypeOf" as a function.` ); } this.isTypeOf = config.isTypeOf; @@ -353,21 +354,22 @@ function defineInterfaces( } invariant( Array.isArray(interfaces), - `${type} interfaces must be an Array or a function which returns an Array.` + `${type.name} interfaces must be an Array or a function which returns ` + + 'an Array.' ); interfaces.forEach(iface => { invariant( iface instanceof GraphQLInterfaceType, - `${type} may only implement Interface types, it cannot ` + - `implement: ${iface}.` + `${type.name} may only implement Interface types, it cannot ` + + `implement: ${iface.name}.` ); if (typeof iface.resolveType !== 'function') { invariant( typeof type.isTypeOf === 'function', - `Interface Type ${iface} does not provide a "resolveType" function ` + - `and implementing Type ${type} does not provide a "isTypeOf" ` + - 'function. There is no way to resolve this implementing type ' + - 'during execution.' + `Interface Type ${iface.name} does not provide a "resolveType" ` + + `function and implementing Type ${type.name} does not provide a ` + + '"isTypeOf" function. There is no way to resolve this implementing ' + + 'type during execution.' ); } }); @@ -381,14 +383,14 @@ function defineFieldMap( const fieldMap: any = resolveMaybeThunk(fields); invariant( isPlainObj(fieldMap), - `${type} fields must be an object with field names as keys or a ` + + `${type.name} fields must be an object with field names as keys or a ` + 'function which returns such an object.' ); const fieldNames = Object.keys(fieldMap); invariant( fieldNames.length > 0, - `${type} fields must be an object with field names as keys or a ` + + `${type.name} fields must be an object with field names as keys or a ` + 'function which returns such an object.' ); @@ -401,29 +403,29 @@ function defineFieldMap( }; invariant( !field.hasOwnProperty('isDeprecated'), - `${type}.${fieldName} should provide "deprecationReason" instead ` + + `${type.name}.${fieldName} should provide "deprecationReason" instead ` + 'of "isDeprecated".' ); invariant( isOutputType(field.type), - `${type}.${fieldName} field type must be Output Type but ` + - `got: ${field.type}.` + `${type.name}.${fieldName} field type must be Output Type but ` + + `got: ${String(field.type)}.` ); if (!field.args) { field.args = []; } else { invariant( isPlainObj(field.args), - `${type}.${fieldName} args must be an object with argument names ` + - 'as keys.' + `${type.name}.${fieldName} args must be an object with argument ` + + 'names as keys.' ); field.args = Object.keys(field.args).map(argName => { assertValidName(argName); const arg = field.args[argName]; invariant( isInputType(arg.type), - `${type}.${fieldName}(${argName}:) argument type must be ` + - `Input Type but got: ${arg.type}.` + `${type.name}.${fieldName}(${argName}:) argument type must be ` + + `Input Type but got: ${String(arg.type)}.` ); return { name: argName, @@ -564,7 +566,7 @@ export class GraphQLInterfaceType { if (config.resolveType) { invariant( typeof config.resolveType === 'function', - `${this} must provide "resolveType" as a function.` + `${this.name} must provide "resolveType" as a function.` ); } this.resolveType = config.resolveType; @@ -635,7 +637,7 @@ export class GraphQLUnionType { if (config.resolveType) { invariant( typeof config.resolveType === 'function', - `${this} must provide "resolveType" as a function.` + `${this.name} must provide "resolveType" as a function.` ); } this.resolveType = config.resolveType; @@ -646,13 +648,14 @@ export class GraphQLUnionType { config.types.forEach(type => { invariant( type instanceof GraphQLObjectType, - `${this} may only contain Object types, it cannot contain: ${type}.` + `${this.name} may only contain Object types, it cannot contain: ` + + `${String(type)}.` ); if (typeof this.resolveType !== 'function') { invariant( typeof type.isTypeOf === 'function', - `Union Type ${this} does not provide a "resolveType" function ` + - `and possible Type ${type} does not provide a "isTypeOf" ` + + `Union Type ${this.name} does not provide a "resolveType" function ` + + `and possible Type ${type.name} does not provide a "isTypeOf" ` + 'function. There is no way to resolve this possible type ' + 'during execution.' ); @@ -783,24 +786,24 @@ function defineEnumValues( ): Array */> { invariant( isPlainObj(valueMap), - `${type} values must be an object with value names as keys.` + `${type.name} values must be an object with value names as keys.` ); const valueNames = Object.keys(valueMap); invariant( valueNames.length > 0, - `${type} values must be an object with value names as keys.` + `${type.name} values must be an object with value names as keys.` ); return valueNames.map(valueName => { assertValidName(valueName); const value = valueMap[valueName]; invariant( isPlainObj(value), - `${type}.${valueName} must refer to an object with a "value" key ` + - `representing an internal value but got: ${value}.` + `${type.name}.${valueName} must refer to an object with a "value" key ` + + `representing an internal value but got: ${String(value)}.` ); invariant( !value.hasOwnProperty('isDeprecated'), - `${type}.${valueName} should provide "deprecationReason" instead ` + + `${type.name}.${valueName} should provide "deprecationReason" instead ` + 'of "isDeprecated".' ); return { @@ -880,13 +883,13 @@ export class GraphQLInputObjectType { const fieldMap: any = resolveMaybeThunk(this._typeConfig.fields); invariant( isPlainObj(fieldMap), - `${this} fields must be an object with field names as keys or a ` + + `${this.name} fields must be an object with field names as keys or a ` + 'function which returns such an object.' ); const fieldNames = Object.keys(fieldMap); invariant( fieldNames.length > 0, - `${this} fields must be an object with field names as keys or a ` + + `${this.name} fields must be an object with field names as keys or a ` + 'function which returns such an object.' ); const resultFieldMap = {}; @@ -898,8 +901,8 @@ export class GraphQLInputObjectType { }; invariant( isInputType(field.type), - `${this}.${fieldName} field type must be Input Type but ` + - `got: ${field.type}.` + `${this.name}.${fieldName} field type must be Input Type but ` + + `got: ${String(field.type)}.` ); resultFieldMap[fieldName] = field; }); @@ -966,7 +969,7 @@ export class GraphQLList { constructor(type: T) { invariant( isType(type), - `Can only create List of a GraphQLType but got: ${type}.` + `Can only create List of a GraphQLType but got: ${String(type)}.` ); this.ofType = type; } @@ -1003,7 +1006,8 @@ export class GraphQLNonNull { constructor(type: T) { invariant( isType(type) && !(type instanceof GraphQLNonNull), - `Can only create NonNull of a Nullable GraphQLType but got: ${type}.` + 'Can only create NonNull of a Nullable GraphQLType but got: ' + + `${String(type)}.` ); this.ofType = type; } diff --git a/src/type/schema.js b/src/type/schema.js index 1beb360c3b..d9160656d4 100644 --- a/src/type/schema.js +++ b/src/type/schema.js @@ -284,16 +284,17 @@ function assertObjectImplementsInterface( // Assert interface field exists on object. invariant( objectField, - `"${iface}" expects field "${fieldName}" but "${object}" does not ` + - 'provide it.' + `"${iface.name}" expects field "${fieldName}" but "${object.name}" ` + + 'does not provide it.' ); // Assert interface field type is satisfied by object field type, by being // a valid subtype. (covariant) invariant( isTypeSubTypeOf(schema, objectField.type, ifaceField.type), - `${iface}.${fieldName} expects type "${ifaceField.type}" but ` + - `${object}.${fieldName} provides type "${objectField.type}".` + `${iface.name}.${fieldName} expects type "${String(ifaceField.type)}" ` + + 'but ' + + `${object.name}.${fieldName} provides type "${String(objectField.type)}".` ); // Assert each interface field arg is implemented. @@ -304,17 +305,18 @@ function assertObjectImplementsInterface( // Assert interface field arg exists on object field. invariant( objectArg, - `${iface}.${fieldName} expects argument "${argName}" but ` + - `${object}.${fieldName} does not provide it.` + `${iface.name}.${fieldName} expects argument "${argName}" but ` + + `${object.name}.${fieldName} does not provide it.` ); // Assert interface field arg type matches object field arg type. // (invariant) invariant( isEqualType(ifaceArg.type, objectArg.type), - `${iface}.${fieldName}(${argName}:) expects type "${ifaceArg.type}" ` + - `but ${object}.${fieldName}(${argName}:) provides ` + - `type "${objectArg.type}".` + `${iface.name}.${fieldName}(${argName}:) expects type ` + + `"${String(ifaceArg.type)}" but ` + + `${object.name}.${fieldName}(${argName}:) provides type ` + + `"${String(objectArg.type)}".` ); }); @@ -325,9 +327,9 @@ function assertObjectImplementsInterface( if (!ifaceArg) { invariant( !(objectArg.type instanceof GraphQLNonNull), - `${object}.${fieldName}(${argName}:) is of required type ` + - `"${objectArg.type}" but is not also provided by the ` + - `interface ${iface}.${fieldName}.` + `${object.name}.${fieldName}(${argName}:) is of required type ` + + `"${String(objectArg.type)}" but is not also provided by the ` + + `interface ${iface.name}.${fieldName}.` ); } }); From 3083fc5743ef228326588f0d1c6b1ebcdf0c4a52 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Tue, 5 Jul 2016 16:39:56 -0700 Subject: [PATCH 10/18] Additional flow issues corrected in anticipation of Flow v0.28 --- src/type/definition.js | 4 ++-- src/type/directives.js | 2 +- src/type/schema.js | 17 +++++++++-------- src/utilities/buildASTSchema.js | 8 +++++++- src/utilities/buildClientSchema.js | 2 +- src/utilities/extendSchema.js | 16 +++++++++++----- src/utilities/schemaPrinter.js | 13 +++++++------ .../rules/DefaultValuesOfCorrectType.js | 8 +++++--- .../rules/FragmentsOnCompositeTypes.js | 4 ++-- src/validation/rules/KnownArgumentNames.js | 2 +- src/validation/rules/KnownTypeNames.js | 2 +- .../rules/OverlappingFieldsCanBeMerged.js | 3 ++- src/validation/rules/PossibleFragmentSpreads.js | 4 ++-- .../rules/ProvidedNonNullArguments.js | 6 +++--- src/validation/rules/ScalarLeafs.js | 6 ++++-- .../rules/VariablesInAllowedPosition.js | 4 ++-- 16 files changed, 60 insertions(+), 41 deletions(-) diff --git a/src/type/definition.js b/src/type/definition.js index f8ad1bc95e..0ad784eaba 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -232,7 +232,7 @@ export class GraphQLScalarType { this._scalarConfig = config; } - serialize(value: mixed): ?InternalType { + serialize(value: InternalType): mixed { const serializer = this._scalarConfig.serialize; return serializer(value); } @@ -361,7 +361,7 @@ function defineInterfaces( invariant( iface instanceof GraphQLInterfaceType, `${type.name} may only implement Interface types, it cannot ` + - `implement: ${iface.name}.` + `implement: ${String(iface)}.` ); if (typeof iface.resolveType !== 'function') { invariant( diff --git a/src/type/directives.js b/src/type/directives.js index 9262ea22e1..07e2925a30 100644 --- a/src/type/directives.js +++ b/src/type/directives.js @@ -78,7 +78,7 @@ export class GraphQLDirective { invariant( isInputType(arg.type), `@${config.name}(${argName}:) argument type must be ` + - `Input Type but got: ${arg.type}.` + `Input Type but got: ${String(arg.type)}.` ); return { name: argName, diff --git a/src/type/schema.js b/src/type/schema.js index d9160656d4..1d1c7ade91 100644 --- a/src/type/schema.js +++ b/src/type/schema.js @@ -68,27 +68,28 @@ export class GraphQLSchema { invariant( config.query instanceof GraphQLObjectType, - `Schema query must be Object Type but got: ${config.query}.` + `Schema query must be Object Type but got: ${ + String(config.query)}.` ); this._queryType = config.query; invariant( !config.mutation || config.mutation instanceof GraphQLObjectType, `Schema mutation must be Object Type if provided but got: ${ - config.mutation}.` + String(config.mutation)}.` ); this._mutationType = config.mutation; invariant( !config.subscription || config.subscription instanceof GraphQLObjectType, `Schema subscription must be Object Type if provided but got: ${ - config.subscription}.` + String(config.subscription)}.` ); this._subscriptionType = config.subscription; invariant( !config.types || Array.isArray(config.types), - `Schema types must be Array if provided but got: ${config.types}.` + `Schema types must be Array if provided but got: ${String(config.types)}.` ); invariant( @@ -97,7 +98,7 @@ export class GraphQLSchema { directive => directive instanceof GraphQLDirective ), `Schema directives must be Array if provided but got: ${ - config.directives}.` + String(config.directives)}.` ); // Provide specified directives (e.g. @include and @skip) by default. this._directives = config.directives || specifiedDirectives; @@ -190,8 +191,8 @@ export class GraphQLSchema { const possibleTypes = this.getPossibleTypes(abstractType); invariant( Array.isArray(possibleTypes), - `Could not find possible implementing types for ${abstractType} in ` + - 'schema. Check that schema.types is defined and is an array of ' + + `Could not find possible implementing types for ${abstractType.name} ` + + 'in schema. Check that schema.types is defined and is an array of ' + 'all possible types in the schema.' ); possibleTypeMap[abstractType.name] = @@ -234,7 +235,7 @@ function typeMapReducer(map: TypeMap, type: ?GraphQLType): TypeMap { invariant( map[type.name] === type, 'Schema must contain unique named types but contains multiple ' + - `types named "${type}".` + `types named "${type.name}".` ); return map; } diff --git a/src/utilities/buildASTSchema.js b/src/utilities/buildASTSchema.js index 1bd4eaea9a..32298c9d5a 100644 --- a/src/utilities/buildASTSchema.js +++ b/src/utilities/buildASTSchema.js @@ -71,6 +71,10 @@ import { GraphQLDeprecatedDirective, } from '../type/directives'; +import type { + DirectiveLocationEnum +} from '../type/directives'; + import { __Schema, __Directive, @@ -254,7 +258,9 @@ export function buildASTSchema(ast: Document): GraphQLSchema { function getDirective(directiveAST: DirectiveDefinition): GraphQLDirective { return new GraphQLDirective({ name: directiveAST.name.value, - locations: directiveAST.locations.map(node => node.value), + locations: directiveAST.locations.map( + node => (node.value: DirectiveLocationEnum) + ), args: makeInputValues(directiveAST.arguments), }); } diff --git a/src/utilities/buildClientSchema.js b/src/utilities/buildClientSchema.js index 1161f1fea5..07ee726341 100644 --- a/src/utilities/buildClientSchema.js +++ b/src/utilities/buildClientSchema.js @@ -222,7 +222,7 @@ export function buildClientSchema( function buildScalarDef( scalarIntrospection: IntrospectionScalarType - ): GraphQLScalarType { + ): GraphQLScalarType { return new GraphQLScalarType({ name: scalarIntrospection.name, description: scalarIntrospection.description, diff --git a/src/utilities/extendSchema.js b/src/utilities/extendSchema.js index 2a7f57a75d..0b53793825 100644 --- a/src/utilities/extendSchema.js +++ b/src/utilities/extendSchema.js @@ -185,17 +185,17 @@ export function extendSchema( __TypeKind, }; - // Get the root Query, Mutation, and Subscription types. - const queryType = getTypeFromDef(schema.getQueryType()); + // Get the root Query, Mutation, and Subscription object types. + const queryType = getObjectTypeFromDef(schema.getQueryType()); const existingMutationType = schema.getMutationType(); const mutationType = existingMutationType ? - getTypeFromDef(existingMutationType) : + getObjectTypeFromDef(existingMutationType) : null; const existingSubscriptionType = schema.getSubscriptionType(); const subscriptionType = existingSubscriptionType ? - getTypeFromDef(existingSubscriptionType) : + getObjectTypeFromDef(existingSubscriptionType) : null; // Iterate through all types, getting the type definition for each, ensuring @@ -228,6 +228,12 @@ export function extendSchema( return type; } + function getObjectTypeFromDef(typeDef: GraphQLObjectType): GraphQLObjectType { + const type = _getNamedType(typeDef.name); + invariant(type instanceof GraphQLObjectType, 'Invalid schema'); + return type; + } + function getTypeFromAST(astNode: NamedType): GraphQLNamedType { const type = _getNamedType(astNode.name.value); if (!type) { @@ -302,7 +308,7 @@ export function extendSchema( return new GraphQLUnionType({ name: type.name, description: type.description, - types: type.getTypes().map(getTypeFromDef), + types: type.getTypes().map(getObjectTypeFromDef), resolveType: cannotExecuteClientSchema, }); } diff --git a/src/utilities/schemaPrinter.js b/src/utilities/schemaPrinter.js index 28709048ed..d03d8f6c6f 100644 --- a/src/utilities/schemaPrinter.js +++ b/src/utilities/schemaPrinter.js @@ -82,17 +82,17 @@ function printSchemaDefinition(schema: GraphQLSchema): string { const queryType = schema.getQueryType(); if (queryType) { - operationTypes.push(` query: ${queryType}`); + operationTypes.push(` query: ${queryType.name}`); } const mutationType = schema.getMutationType(); if (mutationType) { - operationTypes.push(` mutation: ${mutationType}`); + operationTypes.push(` mutation: ${mutationType.name}`); } const subscriptionType = schema.getSubscriptionType(); if (subscriptionType) { - operationTypes.push(` subscription: ${subscriptionType}`); + operationTypes.push(` subscription: ${subscriptionType.name}`); } return `schema {\n${operationTypes.join('\n')}\n}`; @@ -114,7 +114,7 @@ function printType(type: GraphQLType): string { return printInputObject(type); } -function printScalar(type: GraphQLScalarType): string { +function printScalar(type: GraphQLScalarType): string { return `scalar ${type.name}`; } @@ -156,7 +156,8 @@ function printFields(type) { const fieldMap = type.getFields(); const fields = Object.keys(fieldMap).map(fieldName => fieldMap[fieldName]); return fields.map( - f => ` ${f.name}${printArgs(f)}: ${f.type}${printDeprecated(f)}` + f => ' ' + f.name + printArgs(f) + ': ' + + String(f.type) + printDeprecated(f) ).join('\n'); } @@ -182,7 +183,7 @@ function printArgs(fieldOrDirectives) { } function printInputValue(arg) { - let argDecl = `${arg.name}: ${arg.type}`; + let argDecl = arg.name + ': ' + String(arg.type); if (!isNullish(arg.defaultValue)) { argDecl += ` = ${print(astFromValue(arg.defaultValue, arg.type))}`; } diff --git a/src/validation/rules/DefaultValuesOfCorrectType.js b/src/validation/rules/DefaultValuesOfCorrectType.js index 8ca795a0ec..7522a11b7a 100644 --- a/src/validation/rules/DefaultValuesOfCorrectType.js +++ b/src/validation/rules/DefaultValuesOfCorrectType.js @@ -21,8 +21,9 @@ export function defaultForNonNullArgMessage( type: GraphQLType, guessType: GraphQLType ): string { - return `Variable "$${varName}" of type "${type}" is required and will not ` + - `use the default value. Perhaps you meant to use type "${guessType}".`; + return `Variable "$${varName}" of type "${String(type)}" is required and ` + + 'will not use the default value. ' + + `Perhaps you meant to use type "${String(guessType)}".`; } export function badValueForDefaultArgMessage( @@ -32,7 +33,8 @@ export function badValueForDefaultArgMessage( verboseErrors?: [string] ): string { const message = verboseErrors ? '\n' + verboseErrors.join('\n') : ''; - return `Variable "$${varName}" has invalid default value ${value}.${message}`; + return `Variable "$${varName}" of type "${String(type)}" has invalid ` + + `default value ${value}.${message}`; } /** diff --git a/src/validation/rules/FragmentsOnCompositeTypes.js b/src/validation/rules/FragmentsOnCompositeTypes.js index f2b87a9321..892410ea23 100644 --- a/src/validation/rules/FragmentsOnCompositeTypes.js +++ b/src/validation/rules/FragmentsOnCompositeTypes.js @@ -18,7 +18,7 @@ import type { GraphQLType } from '../../type/definition'; export function inlineFragmentOnNonCompositeErrorMessage( type: GraphQLType ): string { - return `Fragment cannot condition on non composite type "${type}".`; + return `Fragment cannot condition on non composite type "${String(type)}".`; } export function fragmentOnNonCompositeErrorMessage( @@ -26,7 +26,7 @@ export function fragmentOnNonCompositeErrorMessage( type: GraphQLType ): string { return `Fragment "${fragName}" cannot condition on non composite ` + - `type "${type}".`; + `type "${String(type)}".`; } /** diff --git a/src/validation/rules/KnownArgumentNames.js b/src/validation/rules/KnownArgumentNames.js index f2ada50f81..42a58d2295 100644 --- a/src/validation/rules/KnownArgumentNames.js +++ b/src/validation/rules/KnownArgumentNames.js @@ -28,7 +28,7 @@ export function unknownArgMessage( suggestedArgs: Array ): string { let message = `Unknown argument "${argName}" on field "${fieldName}" of ` + - `type "${type}".`; + `type "${String(type)}".`; if (suggestedArgs.length) { message += ` Did you mean ${quotedOrList(suggestedArgs)}?`; } diff --git a/src/validation/rules/KnownTypeNames.js b/src/validation/rules/KnownTypeNames.js index 4bb9ddcec3..f692f94d59 100644 --- a/src/validation/rules/KnownTypeNames.js +++ b/src/validation/rules/KnownTypeNames.js @@ -19,7 +19,7 @@ export function unknownTypeMessage( type: GraphQLType, suggestedTypes: Array ): string { - let message = `Unknown type "${type}".`; + let message = `Unknown type "${String(type)}".`; if (suggestedTypes.length) { message += ` Did you mean ${quotedOrList(suggestedTypes)}?`; } diff --git a/src/validation/rules/OverlappingFieldsCanBeMerged.js b/src/validation/rules/OverlappingFieldsCanBeMerged.js index 52d9896f64..f60eee6fb0 100644 --- a/src/validation/rules/OverlappingFieldsCanBeMerged.js +++ b/src/validation/rules/OverlappingFieldsCanBeMerged.js @@ -571,7 +571,8 @@ function findConflict( if (type1 && type2 && doTypesConflict(type1, type2)) { return [ - [ responseName, `they return conflicting types ${type1} and ${type2}` ], + [ responseName, + `they return conflicting types ${String(type1)} and ${String(type2)}` ], [ ast1 ], [ ast2 ] ]; diff --git a/src/validation/rules/PossibleFragmentSpreads.js b/src/validation/rules/PossibleFragmentSpreads.js index a02bdc3f1a..a0d66ac42b 100644 --- a/src/validation/rules/PossibleFragmentSpreads.js +++ b/src/validation/rules/PossibleFragmentSpreads.js @@ -21,7 +21,7 @@ export function typeIncompatibleSpreadMessage( fragType: GraphQLType ): string { return `Fragment "${fragName}" cannot be spread here as objects of ` + - `type "${parentType}" can never be of type "${fragType}".`; + `type "${String(parentType)}" can never be of type "${String(fragType)}".`; } export function typeIncompatibleAnonSpreadMessage( @@ -29,7 +29,7 @@ export function typeIncompatibleAnonSpreadMessage( fragType: GraphQLType ): string { return 'Fragment cannot be spread here as objects of ' + - `type "${parentType}" can never be of type "${fragType}".`; + `type "${String(parentType)}" can never be of type "${String(fragType)}".`; } /** diff --git a/src/validation/rules/ProvidedNonNullArguments.js b/src/validation/rules/ProvidedNonNullArguments.js index a02f010036..fb952a6708 100644 --- a/src/validation/rules/ProvidedNonNullArguments.js +++ b/src/validation/rules/ProvidedNonNullArguments.js @@ -20,8 +20,8 @@ export function missingFieldArgMessage( argName: string, type: GraphQLType ): string { - return `Field "${fieldName}" argument "${argName}" of type "${type}" ` + - 'is required but not provided.'; + return `Field "${fieldName}" argument "${argName}" of type ` + + `"${String(type)}" is required but not provided.`; } export function missingDirectiveArgMessage( @@ -30,7 +30,7 @@ export function missingDirectiveArgMessage( type: GraphQLType ): string { return `Directive "@${directiveName}" argument "${argName}" of type ` + - `"${type}" is required but not provided.`; + `"${String(type)}" is required but not provided.`; } /** diff --git a/src/validation/rules/ScalarLeafs.js b/src/validation/rules/ScalarLeafs.js index e48b76edd8..44f476c5d6 100644 --- a/src/validation/rules/ScalarLeafs.js +++ b/src/validation/rules/ScalarLeafs.js @@ -19,14 +19,16 @@ export function noSubselectionAllowedMessage( field: string, type: GraphQLType ): string { - return `Field "${field}" of type "${type}" must not have a sub selection.`; + return `Field "${field}" of type "${String(type)}" must not have a ` + + 'sub selection.'; } export function requiredSubselectionMessage( field: string, type: GraphQLType ): string { - return `Field "${field}" of type "${type}" must have a sub selection.`; + return `Field "${field}" of type "${String(type)}" must have a ` + + 'sub selection.'; } /** diff --git a/src/validation/rules/VariablesInAllowedPosition.js b/src/validation/rules/VariablesInAllowedPosition.js index 930e7077e3..be03ea8133 100644 --- a/src/validation/rules/VariablesInAllowedPosition.js +++ b/src/validation/rules/VariablesInAllowedPosition.js @@ -21,8 +21,8 @@ export function badVarPosMessage( varType: GraphQLType, expectedType: GraphQLType ): string { - return `Variable "$${varName}" of type "${varType}" used in position ` + - `expecting type "${expectedType}".`; + return `Variable "$${varName}" of type "${String(varType)}" used in ` + + `position expecting type "${String(expectedType)}".`; } /** From f7c78ebf4b08d09e70b31cadffe4f0b941ce1ab0 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Tue, 5 Jul 2016 20:06:59 -0700 Subject: [PATCH 11/18] Introduce formal definition of "Thunk" to aid in fixing more issues uncovered by Flow v0.28 --- src/execution/execute.js | 2 +- src/type/definition.js | 97 +++++++++++++++--------------- src/type/scalars.js | 5 +- src/utilities/buildASTSchema.js | 36 +++++++---- src/utilities/buildClientSchema.js | 2 +- src/utilities/extendSchema.js | 41 ++++++++++--- src/utilities/schemaPrinter.js | 2 +- 7 files changed, 109 insertions(+), 76 deletions(-) diff --git a/src/execution/execute.js b/src/execution/execute.js index c53cb3a42e..5bfe5d1219 100644 --- a/src/execution/execute.js +++ b/src/execution/execute.js @@ -594,7 +594,7 @@ function resolveField( // Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField` // function. Returns the result of resolveFn or the abrupt-return Error object. function resolveOrError( - resolveFn: GraphQLFieldResolveFn, + resolveFn: GraphQLFieldResolveFn<*>, source: mixed, args: { [key: string]: mixed }, context: mixed, diff --git a/src/type/definition.js b/src/type/definition.js index 0ad784eaba..240a1d6d10 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -187,6 +187,17 @@ export function getNamedType(type: ?GraphQLType): ?GraphQLNamedType { } +/** + * Used while defining GraphQL types to allow for circular references in + * otherwise immutable type definitions. + */ +export type Thunk = (() => T) | T; + +function resolveThunk(thunk: Thunk): T { + return typeof thunk === 'function' ? thunk() : thunk; +} + + /** * Scalar Type Definition * @@ -204,13 +215,13 @@ export function getNamedType(type: ?GraphQLType): ?GraphQLNamedType { * }); * */ -export class GraphQLScalarType { +export class GraphQLScalarType { name: string; description: ?string; - _scalarConfig: GraphQLScalarTypeConfig; + _scalarConfig: GraphQLScalarTypeConfig; - constructor(config: GraphQLScalarTypeConfig) { + constructor(config: GraphQLScalarTypeConfig) { invariant(config.name, 'Type must be named.'); assertValidName(config.name); this.name = config.name; @@ -232,17 +243,17 @@ export class GraphQLScalarType { this._scalarConfig = config; } - serialize(value: InternalType): mixed { + serialize(value: TInternal): ?TExternal { const serializer = this._scalarConfig.serialize; return serializer(value); } - parseValue(value: mixed): ?InternalType { + parseValue(value: TExternal): ?TInternal { const parser = this._scalarConfig.parseValue; return parser ? parser(value) : null; } - parseLiteral(valueAST: Value): ?InternalType { + parseLiteral(valueAST: Value): ?TInternal { const parser = this._scalarConfig.parseLiteral; return parser ? parser(valueAST) : null; } @@ -252,12 +263,12 @@ export class GraphQLScalarType { } } -export type GraphQLScalarTypeConfig = { +export type GraphQLScalarTypeConfig = { name: string; description?: ?string; - serialize: (value: mixed) => ?InternalType; - parseValue?: (value: mixed) => ?InternalType; - parseLiteral?: (valueAST: Value) => ?InternalType; + serialize: (value: TInternal) => ?TExternal; + parseValue?: (value: TExternal) => ?TInternal; + parseLiteral?: (valueAST: Value) => ?TInternal; } @@ -304,11 +315,11 @@ export class GraphQLObjectType { description: ?string; isTypeOf: ?GraphQLIsTypeOfFn; - _typeConfig: GraphQLObjectTypeConfig; + _typeConfig: GraphQLObjectTypeConfig<*>; _fields: GraphQLFieldDefinitionMap; _interfaces: Array; - constructor(config: GraphQLObjectTypeConfig) { + constructor(config: GraphQLObjectTypeConfig<*>) { invariant(config.name, 'Type must be named.'); assertValidName(config.name); this.name = config.name; @@ -340,15 +351,11 @@ export class GraphQLObjectType { } } -function resolveMaybeThunk(thingOrThunk: T | () => T): T { - return typeof thingOrThunk === 'function' ? thingOrThunk() : thingOrThunk; -} - function defineInterfaces( type: GraphQLObjectType, - interfacesOrThunk: Array | ?GraphQLInterfacesThunk + interfacesThunk: Thunk> ): Array { - const interfaces = resolveMaybeThunk(interfacesOrThunk); + const interfaces = resolveThunk(interfacesThunk); if (!interfaces) { return []; } @@ -378,9 +385,9 @@ function defineInterfaces( function defineFieldMap( type: GraphQLNamedType, - fields: GraphQLFieldConfigMap | GraphQLFieldConfigMapThunk + fieldsThunk: Thunk> ): GraphQLFieldDefinitionMap { - const fieldMap: any = resolveMaybeThunk(fields); + const fieldMap = resolveThunk(fieldsThunk); invariant( isPlainObj(fieldMap), `${type.name} fields must be an object with field names as keys or a ` + @@ -397,8 +404,9 @@ function defineFieldMap( const resultFieldMap = {}; fieldNames.forEach(fieldName => { assertValidName(fieldName); + const fieldConfig = fieldMap[fieldName]; const field = { - ...fieldMap[fieldName], + ...fieldConfig, name: fieldName }; invariant( @@ -411,17 +419,18 @@ function defineFieldMap( `${type.name}.${fieldName} field type must be Output Type but ` + `got: ${String(field.type)}.` ); - if (!field.args) { + const argsConfig = fieldConfig.args; + if (!argsConfig) { field.args = []; } else { invariant( - isPlainObj(field.args), + isPlainObj(argsConfig), `${type.name}.${fieldName} args must be an object with argument ` + 'names as keys.' ); - field.args = Object.keys(field.args).map(argName => { + field.args = Object.keys(argsConfig).map(argName => { assertValidName(argName); - const arg = field.args[argName]; + const arg = argsConfig[argName]; invariant( isInputType(arg.type), `${type.name}.${fieldName}(${argName}:) argument type must be ` + @@ -444,18 +453,14 @@ function isPlainObj(obj) { return obj && typeof obj === 'object' && !Array.isArray(obj); } -export type GraphQLObjectTypeConfig = { +export type GraphQLObjectTypeConfig = { name: string; - interfaces?: GraphQLInterfacesThunk | Array; - fields: GraphQLFieldConfigMapThunk | GraphQLFieldConfigMap; - isTypeOf?: GraphQLIsTypeOfFn; + interfaces?: Thunk>; + fields: Thunk>; + isTypeOf?: ?GraphQLIsTypeOfFn; description?: ?string } -type GraphQLInterfacesThunk = () => Array; - -type GraphQLFieldConfigMapThunk = () => GraphQLFieldConfigMap; - export type GraphQLTypeResolveFn = ( value: mixed, context: mixed, @@ -463,13 +468,13 @@ export type GraphQLTypeResolveFn = ( ) => ?GraphQLObjectType export type GraphQLIsTypeOfFn = ( - value: mixed, + source: mixed, context: mixed, info: GraphQLResolveInfo ) => boolean -export type GraphQLFieldResolveFn = ( - source: mixed, +export type GraphQLFieldResolveFn = ( + source: TSource, args: {[argName: string]: mixed}, context: mixed, info: GraphQLResolveInfo @@ -488,10 +493,10 @@ export type GraphQLResolveInfo = { variableValues: { [variableName: string]: mixed }; } -export type GraphQLFieldConfig = { +export type GraphQLFieldConfig = { type: GraphQLOutputType; args?: GraphQLFieldConfigArgumentMap; - resolve?: GraphQLFieldResolveFn; + resolve?: GraphQLFieldResolveFn; deprecationReason?: ?string; description?: ?string; } @@ -506,8 +511,8 @@ export type GraphQLArgumentConfig = { description?: ?string; } -export type GraphQLFieldConfigMap = { - [fieldName: string]: GraphQLFieldConfig; +export type GraphQLFieldConfigMap = { + [fieldName: string]: GraphQLFieldConfig; }; export type GraphQLFieldDefinition = { @@ -515,7 +520,7 @@ export type GraphQLFieldDefinition = { description: ?string; type: GraphQLOutputType; args: Array; - resolve?: GraphQLFieldResolveFn; + resolve?: GraphQLFieldResolveFn<*>; deprecationReason?: ?string; } @@ -585,13 +590,13 @@ export class GraphQLInterfaceType { export type GraphQLInterfaceTypeConfig = { name: string, - fields: GraphQLFieldConfigMapThunk | GraphQLFieldConfigMap, + fields: Thunk>, /** * Optionally provide a custom type resolver function. If one is not provided, * the default implementation will call `isTypeOf` on each implementing * Object type. */ - resolveType?: GraphQLTypeResolveFn, + resolveType?: ?GraphQLTypeResolveFn, description?: ?string }; @@ -880,7 +885,7 @@ export class GraphQLInputObjectType { } _defineFieldMap(): InputObjectFieldMap { - const fieldMap: any = resolveMaybeThunk(this._typeConfig.fields); + const fieldMap: any = resolveThunk(this._typeConfig.fields); invariant( isPlainObj(fieldMap), `${this.name} fields must be an object with field names as keys or a ` + @@ -916,12 +921,10 @@ export class GraphQLInputObjectType { export type InputObjectConfig = { name: string; - fields: InputObjectConfigFieldMapThunk | InputObjectConfigFieldMap; + fields: Thunk; description?: ?string; } -export type InputObjectConfigFieldMapThunk = () => InputObjectConfigFieldMap; - export type InputObjectFieldConfig = { type: GraphQLInputType; defaultValue?: mixed; diff --git a/src/type/scalars.js b/src/type/scalars.js index 8e25e3d42d..853075b553 100644 --- a/src/type/scalars.js +++ b/src/type/scalars.js @@ -19,13 +19,12 @@ import { Kind } from '../language'; const MAX_INT = 2147483647; const MIN_INT = -2147483648; -function coerceInt(value) { +function coerceInt(value: number): ?number { const num = Number(value); if (num === num && num <= MAX_INT && num >= MIN_INT) { return (num < 0 ? Math.ceil : Math.floor)(num); } return null; - } export const GraphQLInt = new GraphQLScalarType({ @@ -46,7 +45,7 @@ export const GraphQLInt = new GraphQLScalarType({ } }); -function coerceFloat(value) { +function coerceFloat(value: number): ?number { const num = Number(value); return num === num ? num : null; } diff --git a/src/utilities/buildASTSchema.js b/src/utilities/buildASTSchema.js index 32298c9d5a..e5ef0e8776 100644 --- a/src/utilities/buildASTSchema.js +++ b/src/utilities/buildASTSchema.js @@ -92,11 +92,6 @@ import type { } from '../type/definition'; -type CompositeDefinition = - ObjectTypeDefinition | - InterfaceTypeDefinition | - UnionTypeDefinition; - function buildWrappedType( innerType: GraphQLType, inputTypeAST: Type @@ -259,9 +254,9 @@ export function buildASTSchema(ast: Document): GraphQLSchema { return new GraphQLDirective({ name: directiveAST.name.value, locations: directiveAST.locations.map( - node => (node.value: DirectiveLocationEnum) + node => ((node.value: any): DirectiveLocationEnum) ), - args: makeInputValues(directiveAST.arguments), + args: directiveAST.arguments && makeInputValues(directiveAST.arguments), }); } @@ -274,12 +269,24 @@ export function buildASTSchema(ast: Document): GraphQLSchema { return (type: any); } - function produceTypeDef(typeAST: Type): GraphQLType { + function produceType(typeAST: Type): GraphQLType { const typeName = getNamedTypeAST(typeAST).name.value; const typeDef = typeDefNamed(typeName); return buildWrappedType(typeDef, typeAST); } + function produceObjectType(typeAST: Type): GraphQLObjectType { + const type = produceType(typeAST); + invariant(type instanceof GraphQLObjectType, 'Expected Object type.'); + return type; + } + + function produceInterfaceType(typeAST: Type): GraphQLInterfaceType { + const type = produceType(typeAST); + invariant(type instanceof GraphQLInterfaceType, 'Expected Object type.'); + return type; + } + function typeDefNamed(typeName: string): GraphQLNamedType { if (innerTypeMap[typeName]) { return innerTypeMap[typeName]; @@ -329,12 +336,14 @@ export function buildASTSchema(ast: Document): GraphQLSchema { return new GraphQLObjectType(config); } - function makeFieldDefMap(def: CompositeDefinition) { + function makeFieldDefMap( + def: ObjectTypeDefinition | InterfaceTypeDefinition + ) { return keyValMap( def.fields, field => field.name.value, field => ({ - type: produceTypeDef(field.type), + type: produceType(field.type), args: makeInputValues(field.arguments), deprecationReason: getDeprecationReason(field.directives) }) @@ -342,7 +351,8 @@ export function buildASTSchema(ast: Document): GraphQLSchema { } function makeImplementedInterfaces(def: ObjectTypeDefinition) { - return def.interfaces.map(inter => produceTypeDef(inter)); + return def.interfaces && + def.interfaces.map(iface => produceInterfaceType(iface)); } function makeInputValues(values: Array) { @@ -350,7 +360,7 @@ export function buildASTSchema(ast: Document): GraphQLSchema { values, value => value.name.value, value => { - const type = produceTypeDef(value.type); + const type = produceType(value.type); return { type, defaultValue: valueFromAST(value.defaultValue, type) }; } ); @@ -385,7 +395,7 @@ export function buildASTSchema(ast: Document): GraphQLSchema { return new GraphQLUnionType({ name: def.name.value, resolveType: () => null, - types: def.types.map(t => produceTypeDef(t)), + types: def.types.map(t => produceObjectType(t)), }); } diff --git a/src/utilities/buildClientSchema.js b/src/utilities/buildClientSchema.js index 07ee726341..771de2b612 100644 --- a/src/utilities/buildClientSchema.js +++ b/src/utilities/buildClientSchema.js @@ -222,7 +222,7 @@ export function buildClientSchema( function buildScalarDef( scalarIntrospection: IntrospectionScalarType - ): GraphQLScalarType { + ): GraphQLScalarType { return new GraphQLScalarType({ name: scalarIntrospection.name, description: scalarIntrospection.description, diff --git a/src/utilities/extendSchema.js b/src/utilities/extendSchema.js index 0b53793825..2741b62e07 100644 --- a/src/utilities/extendSchema.js +++ b/src/utilities/extendSchema.js @@ -228,12 +228,6 @@ export function extendSchema( return type; } - function getObjectTypeFromDef(typeDef: GraphQLObjectType): GraphQLObjectType { - const type = _getNamedType(typeDef.name); - invariant(type instanceof GraphQLObjectType, 'Invalid schema'); - return type; - } - function getTypeFromAST(astNode: NamedType): GraphQLNamedType { const type = _getNamedType(astNode.name.value); if (!type) { @@ -246,6 +240,32 @@ export function extendSchema( return type; } + function getObjectTypeFromDef(typeDef: GraphQLObjectType): GraphQLObjectType { + const type = getTypeFromDef(typeDef); + invariant(type instanceof GraphQLObjectType, 'Must be Object type.'); + return type; + } + + function getObjectTypeFromAST(astNode: NamedType): GraphQLObjectType { + const type = getTypeFromAST(astNode); + invariant(type instanceof GraphQLObjectType, 'Must be Object type.'); + return type; + } + + function getInterfaceTypeFromDef( + typeDef: GraphQLInterfaceType + ): GraphQLInterfaceType { + const type = getTypeFromDef(typeDef); + invariant(type instanceof GraphQLInterfaceType, 'Must be Object type.'); + return type; + } + + function getInterfaceTypeFromAST(astNode: NamedType): GraphQLInterfaceType { + const type = getTypeFromAST(astNode); + invariant(type instanceof GraphQLInterfaceType, 'Must be Object type.'); + return type; + } + // Given a name, returns a type from either the existing schema or an // added type. function _getNamedType(typeName: string): ?GraphQLNamedType { @@ -316,7 +336,7 @@ export function extendSchema( function extendImplementedInterfaces( type: GraphQLObjectType ): Array { - const interfaces = type.getInterfaces().map(getTypeFromDef); + const interfaces = type.getInterfaces().map(getInterfaceTypeFromDef); // If there are any extensions to the interfaces, apply those here. const extensions = typeExtensionsMap[type.name]; @@ -331,7 +351,7 @@ export function extendSchema( [ namedType ] ); } - interfaces.push(getTypeFromAST(namedType)); + interfaces.push(getInterfaceTypeFromAST(namedType)); }); }); } @@ -418,7 +438,7 @@ export function extendSchema( function buildUnionType(typeAST: UnionTypeDefinition) { return new GraphQLUnionType({ name: typeAST.name.value, - types: typeAST.types.map(getTypeFromAST), + types: typeAST.types.map(getObjectTypeFromAST), resolveType: cannotExecuteClientSchema, }); } @@ -451,7 +471,8 @@ export function extendSchema( } function buildImplementedInterfaces(typeAST: ObjectTypeDefinition) { - return typeAST.interfaces.map(getTypeFromAST); + return typeAST.interfaces && + typeAST.interfaces.map(getInterfaceTypeFromAST); } function buildFieldMap(typeAST) { diff --git a/src/utilities/schemaPrinter.js b/src/utilities/schemaPrinter.js index d03d8f6c6f..67cbd40952 100644 --- a/src/utilities/schemaPrinter.js +++ b/src/utilities/schemaPrinter.js @@ -114,7 +114,7 @@ function printType(type: GraphQLType): string { return printInputObject(type); } -function printScalar(type: GraphQLScalarType): string { +function printScalar(type: GraphQLScalarType): string { return `scalar ${type.name}`; } From 516dfb9674b4dbde1ee17f715e4c0b6bc24a9f92 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Tue, 5 Jul 2016 21:20:19 -0700 Subject: [PATCH 12/18] Only type Scalar config rather than Scalar type, improve schema builder types --- src/type/definition.js | 19 +++++++----- src/type/scalars.js | 4 +-- src/utilities/buildASTSchema.js | 48 +++++++++++++++++++++--------- src/utilities/buildClientSchema.js | 2 +- src/utilities/extendSchema.js | 45 ++++++++++++++++++++++------ src/utilities/schemaPrinter.js | 2 +- 6 files changed, 85 insertions(+), 35 deletions(-) diff --git a/src/type/definition.js b/src/type/definition.js index 240a1d6d10..b74a69f52f 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -215,13 +215,13 @@ function resolveThunk(thunk: Thunk): T { * }); * */ -export class GraphQLScalarType { +export class GraphQLScalarType { name: string; description: ?string; - _scalarConfig: GraphQLScalarTypeConfig; + _scalarConfig: GraphQLScalarTypeConfig<*, *>; - constructor(config: GraphQLScalarTypeConfig) { + constructor(config: GraphQLScalarTypeConfig<*, *>) { invariant(config.name, 'Type must be named.'); assertValidName(config.name); this.name = config.name; @@ -243,17 +243,20 @@ export class GraphQLScalarType { this._scalarConfig = config; } - serialize(value: TInternal): ?TExternal { + // Serializes an internal value to include an a response. + serialize(value: mixed): mixed { const serializer = this._scalarConfig.serialize; return serializer(value); } - parseValue(value: TExternal): ?TInternal { + // Parses an externally provided value to use as an input. + parseValue(value: mixed): mixed { const parser = this._scalarConfig.parseValue; return parser ? parser(value) : null; } - parseLiteral(valueAST: Value): ?TInternal { + // Parses an externally provided literal value to use as an input. + parseLiteral(valueAST: Value): mixed { const parser = this._scalarConfig.parseLiteral; return parser ? parser(valueAST) : null; } @@ -266,8 +269,8 @@ export class GraphQLScalarType { export type GraphQLScalarTypeConfig = { name: string; description?: ?string; - serialize: (value: TInternal) => ?TExternal; - parseValue?: (value: TExternal) => ?TInternal; + serialize: (value: mixed) => ?TExternal; + parseValue?: (value: mixed) => ?TInternal; parseLiteral?: (valueAST: Value) => ?TInternal; } diff --git a/src/type/scalars.js b/src/type/scalars.js index 853075b553..170a01e8e1 100644 --- a/src/type/scalars.js +++ b/src/type/scalars.js @@ -19,7 +19,7 @@ import { Kind } from '../language'; const MAX_INT = 2147483647; const MIN_INT = -2147483648; -function coerceInt(value: number): ?number { +function coerceInt(value: mixed): ?number { const num = Number(value); if (num === num && num <= MAX_INT && num >= MIN_INT) { return (num < 0 ? Math.ceil : Math.floor)(num); @@ -45,7 +45,7 @@ export const GraphQLInt = new GraphQLScalarType({ } }); -function coerceFloat(value: number): ?number { +function coerceFloat(value: mixed): ?number { const num = Number(value); return num === num ? num : null; } diff --git a/src/utilities/buildASTSchema.js b/src/utilities/buildASTSchema.js index e5ef0e8776..cdab65ce3c 100644 --- a/src/utilities/buildASTSchema.js +++ b/src/utilities/buildASTSchema.js @@ -47,22 +47,35 @@ import type { DirectiveDefinition, } from '../language/ast'; +import { GraphQLSchema } from '../type/schema'; + +import { + GraphQLString, + GraphQLInt, + GraphQLFloat, + GraphQLBoolean, + GraphQLID, +} from '../type/scalars'; + import { - GraphQLSchema, GraphQLScalarType, GraphQLObjectType, GraphQLInterfaceType, GraphQLUnionType, GraphQLEnumType, GraphQLInputObjectType, - GraphQLString, - GraphQLInt, - GraphQLFloat, - GraphQLBoolean, - GraphQLID, GraphQLList, GraphQLNonNull, -} from '../type'; + isInputType, + isOutputType, +} from '../type/definition'; + +import type { + GraphQLType, + GraphQLNamedType, + GraphQLInputType, + GraphQLOutputType, +} from '../type/definition'; import { GraphQLDirective, @@ -86,11 +99,6 @@ import { __TypeKind, } from '../type/introspection'; -import type { - GraphQLType, - GraphQLNamedType -} from '../type/definition'; - function buildWrappedType( innerType: GraphQLType, @@ -275,6 +283,18 @@ export function buildASTSchema(ast: Document): GraphQLSchema { return buildWrappedType(typeDef, typeAST); } + function produceInputType(typeAST: Type): GraphQLInputType { + const type = produceType(typeAST); + invariant(isInputType(type), 'Expected Input type.'); + return (type: any); + } + + function produceOutputType(typeAST: Type): GraphQLOutputType { + const type = produceType(typeAST); + invariant(isOutputType(type), 'Expected Output type.'); + return (type: any); + } + function produceObjectType(typeAST: Type): GraphQLObjectType { const type = produceType(typeAST); invariant(type instanceof GraphQLObjectType, 'Expected Object type.'); @@ -343,7 +363,7 @@ export function buildASTSchema(ast: Document): GraphQLSchema { def.fields, field => field.name.value, field => ({ - type: produceType(field.type), + type: produceOutputType(field.type), args: makeInputValues(field.arguments), deprecationReason: getDeprecationReason(field.directives) }) @@ -360,7 +380,7 @@ export function buildASTSchema(ast: Document): GraphQLSchema { values, value => value.name.value, value => { - const type = produceType(value.type); + const type = produceInputType(value.type); return { type, defaultValue: valueFromAST(value.defaultValue, type) }; } ); diff --git a/src/utilities/buildClientSchema.js b/src/utilities/buildClientSchema.js index 771de2b612..1161f1fea5 100644 --- a/src/utilities/buildClientSchema.js +++ b/src/utilities/buildClientSchema.js @@ -222,7 +222,7 @@ export function buildClientSchema( function buildScalarDef( scalarIntrospection: IntrospectionScalarType - ): GraphQLScalarType { + ): GraphQLScalarType { return new GraphQLScalarType({ name: scalarIntrospection.name, description: scalarIntrospection.description, diff --git a/src/utilities/extendSchema.js b/src/utilities/extendSchema.js index 2741b62e07..fdf13963ba 100644 --- a/src/utilities/extendSchema.js +++ b/src/utilities/extendSchema.js @@ -24,6 +24,8 @@ import { GraphQLScalarType, GraphQLEnumType, GraphQLInputObjectType, + isInputType, + isOutputType, } from '../type/definition'; import { @@ -61,6 +63,8 @@ import { import type { GraphQLType, GraphQLNamedType, + GraphQLInputType, + GraphQLOutputType, } from '../type/definition'; import type { @@ -200,8 +204,9 @@ export function extendSchema( // Iterate through all types, getting the type definition for each, ensuring // that any type not directly referenced by a field will get created. - const types = Object.keys(schema.getTypeMap()).map(typeName => - getTypeFromDef(schema.getType(typeName)) + const typeMap = schema.getTypeMap(); + const types = Object.keys(typeMap).map(typeName => + getTypeFromDef(typeMap[typeName]) ); // Do the same with new types, appending to the list of defined types. @@ -266,6 +271,18 @@ export function extendSchema( return type; } + function getInputTypeFromAST(astNode: NamedType): GraphQLInputType { + const type = getTypeFromAST(astNode); + invariant(isInputType(type), 'Must be Input type.'); + return (type: any); + } + + function getOutputTypeFromAST(astNode: NamedType): GraphQLOutputType { + const type = getTypeFromAST(astNode); + invariant(isOutputType(type), 'Must be Output type.'); + return (type: any); + } + // Given a name, returns a type from either the existing schema or an // added type. function _getNamedType(typeName: string): ?GraphQLNamedType { @@ -387,7 +404,7 @@ export function extendSchema( ); } newFieldMap[fieldName] = { - type: buildFieldType(field.type), + type: buildOutputFieldType(field.type), args: buildInputValues(field.arguments), resolve: cannotExecuteClientSchema, }; @@ -480,7 +497,7 @@ export function extendSchema( typeAST.fields, field => field.name.value, field => ({ - type: buildFieldType(field.type), + type: buildOutputFieldType(field.type), args: buildInputValues(field.arguments), resolve: cannotExecuteClientSchema, }) @@ -492,7 +509,7 @@ export function extendSchema( values, value => value.name.value, value => { - const type = buildFieldType(value.type); + const type = buildInputFieldType(value.type); return { type, defaultValue: valueFromAST(value.defaultValue, type) @@ -501,14 +518,24 @@ export function extendSchema( ); } - function buildFieldType(typeAST: Type): GraphQLType { + function buildInputFieldType(typeAST: Type): GraphQLInputType { + if (typeAST.kind === LIST_TYPE) { + return new GraphQLList(buildInputFieldType(typeAST.type)); + } + if (typeAST.kind === NON_NULL_TYPE) { + return new GraphQLNonNull(buildInputFieldType(typeAST.type)); + } + return getInputTypeFromAST(typeAST); + } + + function buildOutputFieldType(typeAST: Type): GraphQLOutputType { if (typeAST.kind === LIST_TYPE) { - return new GraphQLList(buildFieldType(typeAST.type)); + return new GraphQLList(buildOutputFieldType(typeAST.type)); } if (typeAST.kind === NON_NULL_TYPE) { - return new GraphQLNonNull(buildFieldType(typeAST.type)); + return new GraphQLNonNull(buildOutputFieldType(typeAST.type)); } - return getTypeFromAST(typeAST); + return getOutputTypeFromAST(typeAST); } } diff --git a/src/utilities/schemaPrinter.js b/src/utilities/schemaPrinter.js index 67cbd40952..ad87c7b0e5 100644 --- a/src/utilities/schemaPrinter.js +++ b/src/utilities/schemaPrinter.js @@ -114,7 +114,7 @@ function printType(type: GraphQLType): string { return printInputObject(type); } -function printScalar(type: GraphQLScalarType): string { +function printScalar(type: GraphQLScalarType): string { return `scalar ${type.name}`; } From f06051e592362767e6531c1ceb653f1ef3873500 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Wed, 6 Jul 2016 11:52:25 -0700 Subject: [PATCH 13/18] More specific return types from methods in Schema --- src/type/definition.js | 4 ++- src/type/schema.js | 14 ++++++--- src/utilities/extendSchema.js | 57 +++++++++++++++-------------------- 3 files changed, 36 insertions(+), 39 deletions(-) diff --git a/src/type/definition.js b/src/type/definition.js index b74a69f52f..faa9f784b0 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -160,7 +160,9 @@ export type GraphQLNullableType = GraphQLInputObjectType | GraphQLList; -export function getNullableType(type: ?GraphQLType): ?GraphQLNullableType { +export function getNullableType( + type: ?T +): ?(T & GraphQLNullableType) { return type instanceof GraphQLNonNull ? type.ofType : type; } diff --git a/src/type/schema.js b/src/type/schema.js index 1d1c7ade91..412f459664 100644 --- a/src/type/schema.js +++ b/src/type/schema.js @@ -16,7 +16,11 @@ import { GraphQLList, GraphQLNonNull } from './definition'; -import type { GraphQLType, GraphQLAbstractType } from './definition'; +import type { + GraphQLType, + GraphQLNamedType, + GraphQLAbstractType +} from './definition'; import { GraphQLDirective, specifiedDirectives } from './directives'; import { __Schema } from './introspection'; import find from '../jsutils/find'; @@ -104,7 +108,7 @@ export class GraphQLSchema { this._directives = config.directives || specifiedDirectives; // Build type map now to detect any errors within this schema. - let initialTypes: Array = [ + let initialTypes: Array = [ this.getQueryType(), this.getMutationType(), this.getSubscriptionType(), @@ -164,7 +168,7 @@ export class GraphQLSchema { return this._typeMap; } - getType(name: string): ?GraphQLType { + getType(name: string): ?GraphQLNamedType { return this.getTypeMap()[name]; } @@ -214,13 +218,13 @@ export class GraphQLSchema { } } -type TypeMap = { [typeName: string]: GraphQLType } +type TypeMap = { [typeName: string]: GraphQLNamedType } type GraphQLSchemaConfig = { query: GraphQLObjectType; mutation?: ?GraphQLObjectType; subscription?: ?GraphQLObjectType; - types?: ?Array; + types?: ?Array; directives?: ?Array; } diff --git a/src/utilities/extendSchema.js b/src/utilities/extendSchema.js index fdf13963ba..d554b64933 100644 --- a/src/utilities/extendSchema.js +++ b/src/utilities/extendSchema.js @@ -190,16 +190,16 @@ export function extendSchema( }; // Get the root Query, Mutation, and Subscription object types. - const queryType = getObjectTypeFromDef(schema.getQueryType()); + const queryType = getTypeFromDef(schema.getQueryType()); const existingMutationType = schema.getMutationType(); const mutationType = existingMutationType ? - getObjectTypeFromDef(existingMutationType) : + getTypeFromDef(existingMutationType) : null; const existingSubscriptionType = schema.getSubscriptionType(); const subscriptionType = existingSubscriptionType ? - getObjectTypeFromDef(existingSubscriptionType) : + getTypeFromDef(existingSubscriptionType) : null; // Iterate through all types, getting the type definition for each, ensuring @@ -227,10 +227,10 @@ export function extendSchema( // Below are functions used for producing this schema that have closed over // this scope and have access to the schema, cache, and newly defined types. - function getTypeFromDef(typeDef: GraphQLNamedType): GraphQLNamedType { + function getTypeFromDef(typeDef: T): T { const type = _getNamedType(typeDef.name); - invariant(type, 'Invalid schema'); - return type; + invariant(type, 'Missing type from schema'); + return (type: any); } function getTypeFromAST(astNode: NamedType): GraphQLNamedType { @@ -245,29 +245,15 @@ export function extendSchema( return type; } - function getObjectTypeFromDef(typeDef: GraphQLObjectType): GraphQLObjectType { - const type = getTypeFromDef(typeDef); - invariant(type instanceof GraphQLObjectType, 'Must be Object type.'); - return type; - } - function getObjectTypeFromAST(astNode: NamedType): GraphQLObjectType { const type = getTypeFromAST(astNode); invariant(type instanceof GraphQLObjectType, 'Must be Object type.'); return type; } - function getInterfaceTypeFromDef( - typeDef: GraphQLInterfaceType - ): GraphQLInterfaceType { - const type = getTypeFromDef(typeDef); - invariant(type instanceof GraphQLInterfaceType, 'Must be Object type.'); - return type; - } - function getInterfaceTypeFromAST(astNode: NamedType): GraphQLInterfaceType { const type = getTypeFromAST(astNode); - invariant(type instanceof GraphQLInterfaceType, 'Must be Object type.'); + invariant(type instanceof GraphQLInterfaceType, 'Must be Interface type.'); return type; } @@ -308,7 +294,7 @@ export function extendSchema( // Given a type's introspection result, construct the correct // GraphQLType instance. - function extendType(type: GraphQLType): GraphQLType { + function extendType(type: GraphQLNamedType): GraphQLNamedType { if (type instanceof GraphQLObjectType) { return extendObjectType(type); } @@ -345,7 +331,7 @@ export function extendSchema( return new GraphQLUnionType({ name: type.name, description: type.description, - types: type.getTypes().map(getObjectTypeFromDef), + types: type.getTypes().map(getTypeFromDef), resolveType: cannotExecuteClientSchema, }); } @@ -353,7 +339,7 @@ export function extendSchema( function extendImplementedInterfaces( type: GraphQLObjectType ): Array { - const interfaces = type.getInterfaces().map(getInterfaceTypeFromDef); + const interfaces = type.getInterfaces().map(getTypeFromDef); // If there are any extensions to the interfaces, apply those here. const extensions = typeExtensionsMap[type.name]; @@ -415,17 +401,17 @@ export function extendSchema( return newFieldMap; } - function extendFieldType(type: GraphQLType): GraphQLType { - if (type instanceof GraphQLList) { - return new GraphQLList(extendFieldType(type.ofType)); + function extendFieldType(typeDef: T): T { + if (typeDef instanceof GraphQLList) { + return (new GraphQLList(extendFieldType(typeDef.ofType)): any); } - if (type instanceof GraphQLNonNull) { - return new GraphQLNonNull(extendFieldType(type.ofType)); + if (typeDef instanceof GraphQLNonNull) { + return (new GraphQLNonNull(extendFieldType(typeDef.ofType)): any); } - return getTypeFromDef(type); + return getTypeFromDef(typeDef); } - function buildType(typeAST: TypeDefinition): GraphQLType { + function buildType(typeAST: TypeDefinition): GraphQLNamedType { switch (typeAST.kind) { case OBJECT_TYPE_DEFINITION: return buildObjectType(typeAST); case INTERFACE_TYPE_DEFINITION: return buildInterfaceType(typeAST); @@ -434,6 +420,7 @@ export function extendSchema( case ENUM_TYPE_DEFINITION: return buildEnumType(typeAST); case INPUT_OBJECT_TYPE_DEFINITION: return buildInputObjectType(typeAST); } + throw new TypeError('Unknown type kind ' + typeAST.kind); } function buildObjectType(typeAST: ObjectTypeDefinition): GraphQLObjectType { @@ -523,7 +510,9 @@ export function extendSchema( return new GraphQLList(buildInputFieldType(typeAST.type)); } if (typeAST.kind === NON_NULL_TYPE) { - return new GraphQLNonNull(buildInputFieldType(typeAST.type)); + const nullableType = buildInputFieldType(typeAST.type); + invariant(!(nullableType instanceof GraphQLNonNull), 'Must be nullable'); + return new GraphQLNonNull(nullableType); } return getInputTypeFromAST(typeAST); } @@ -533,7 +522,9 @@ export function extendSchema( return new GraphQLList(buildOutputFieldType(typeAST.type)); } if (typeAST.kind === NON_NULL_TYPE) { - return new GraphQLNonNull(buildOutputFieldType(typeAST.type)); + const nullableType = buildOutputFieldType(typeAST.type); + invariant(!(nullableType instanceof GraphQLNonNull), 'Must be nullable'); + return new GraphQLNonNull(nullableType); } return getOutputTypeFromAST(typeAST); } From c7700f206579eef0046649f26e42c8cd336e6e74 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Wed, 6 Jul 2016 12:33:33 -0700 Subject: [PATCH 14/18] Upgrade to Flow v0.28. flow check now passes for the existing v0.26 as well as v0.27 and v0.28, so upgrading to the latest version. Fixes #412, #418, #420 --- package.json | 2 +- src/type/definition.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index cd9417dbde..69c7b2e91a 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "eslint-plugin-babel": "3.2.0", "eslint-plugin-flow-vars": "^0.4.0", "eslint-plugin-flowtype": "2.2.7", - "flow-bin": "0.26.0", + "flow-bin": "0.28.0", "isparta": "4.0.0", "mocha": "2.5.3", "sane": "1.3.4" diff --git a/src/type/definition.js b/src/type/definition.js index faa9f784b0..a55a3f5d8e 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -33,8 +33,8 @@ export type GraphQLType = GraphQLUnionType | GraphQLEnumType | GraphQLInputObjectType | - GraphQLList | - GraphQLNonNull; + GraphQLList | + GraphQLNonNull; export function isType(type: mixed): boolean { return ( @@ -158,7 +158,7 @@ export type GraphQLNullableType = GraphQLUnionType | GraphQLEnumType | GraphQLInputObjectType | - GraphQLList; + GraphQLList<*>; export function getNullableType( type: ?T From 188881b457f030ec1dff6c25458a1fbb66bc515b Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Wed, 6 Jul 2016 15:34:20 -0700 Subject: [PATCH 15/18] Removes depencency on babel-runtime. Simplifies babel transform using "loose" options and removing some transforms. This results in a subset of ES2015 environment, but this source has been pretty careful to avoid the more novel features in order to support more environments. The result for those using graphql via npm is fewer dependencies and less reliance on a modern JS environment, both good things when wanting to use graphql as a depencency in a client-side tool, like Apollo or Relay. Fixes #414 --- .babelrc | 20 ++++++++++++++++---- package.json | 29 ++++++++++++++++++++--------- resources/prepublish.sh | 5 ++++- src/language/visitor.js | 4 ++-- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/.babelrc b/.babelrc index b60c0720d8..a7f45b75e6 100644 --- a/.babelrc +++ b/.babelrc @@ -1,13 +1,25 @@ { - "presets": [ - "es2015" - ], "plugins": [ "syntax-async-functions", "transform-class-properties", "transform-flow-strip-types", "transform-object-rest-spread", + "transform-es2015-template-literals", + "transform-es2015-literals", + "transform-es2015-function-name", + "transform-es2015-arrow-functions", + "transform-es2015-block-scoped-functions", + ["transform-es2015-classes", {loose: true}], + "transform-es2015-object-super", + "transform-es2015-shorthand-properties", + "transform-es2015-duplicate-keys", + "transform-es2015-computed-properties", + "check-es2015-constants", + ["transform-es2015-spread", {loose: true}], + "transform-es2015-parameters", + ["transform-es2015-destructuring", {loose: true}], + "transform-es2015-block-scoping", + "transform-es2015-modules-commonjs", "transform-regenerator", - "transform-runtime" ] } diff --git a/package.json b/package.json index 69c7b2e91a..fe26fa2673 100644 --- a/package.json +++ b/package.json @@ -34,25 +34,36 @@ "preversion": ". ./resources/checkgit.sh && npm test", "prepublish": ". ./resources/prepublish.sh" }, - "dependencies": { - "babel-runtime": ">=6.0.0" - }, "devDependencies": { - "babel-cli": "6.9.0", - "babel-eslint": "6.0.4", + "babel-cli": "6.10.1", + "babel-eslint": "6.1.0", + "babel-plugin-check-es2015-constants": "6.8.0", "babel-plugin-syntax-async-functions": "6.8.0", - "babel-plugin-transform-class-properties": "6.9.1", + "babel-plugin-transform-class-properties": "6.10.2", + "babel-plugin-transform-es2015-arrow-functions": "6.8.0", + "babel-plugin-transform-es2015-block-scoped-functions": "6.8.0", + "babel-plugin-transform-es2015-block-scoping": "6.10.1", + "babel-plugin-transform-es2015-classes": "6.9.0", + "babel-plugin-transform-es2015-computed-properties": "6.8.0", + "babel-plugin-transform-es2015-destructuring": "6.9.0", + "babel-plugin-transform-es2015-duplicate-keys": "6.8.0", + "babel-plugin-transform-es2015-function-name": "6.9.0", + "babel-plugin-transform-es2015-literals": "6.8.0", + "babel-plugin-transform-es2015-modules-commonjs": "6.10.3", + "babel-plugin-transform-es2015-object-super": "6.8.0", + "babel-plugin-transform-es2015-parameters": "6.9.0", + "babel-plugin-transform-es2015-shorthand-properties": "6.8.0", + "babel-plugin-transform-es2015-spread": "6.8.0", + "babel-plugin-transform-es2015-template-literals": "6.8.0", "babel-plugin-transform-flow-strip-types": "6.8.0", "babel-plugin-transform-object-rest-spread": "6.8.0", "babel-plugin-transform-regenerator": "6.9.0", - "babel-plugin-transform-runtime": "6.9.0", - "babel-preset-es2015": "6.6.0", "chai": "3.5.0", "chai-subset": "1.2.2", "coveralls": "2.11.9", "eslint": "2.11.1", "eslint-plugin-babel": "3.2.0", - "eslint-plugin-flow-vars": "^0.4.0", + "eslint-plugin-flow-vars": "0.4.0", "eslint-plugin-flowtype": "2.2.7", "flow-bin": "0.28.0", "isparta": "4.0.0", diff --git a/resources/prepublish.sh b/resources/prepublish.sh index 16c8cc432b..d78f9ca887 100644 --- a/resources/prepublish.sh +++ b/resources/prepublish.sh @@ -26,5 +26,8 @@ babel src --ignore __tests__ --out-dir ./ # Ensure a vanilla package.json before deploying so other tools do not interpret # The built output as requiring any further transformation. node -e "var package = require('./package.json'); \ - delete package.babel; delete package.scripts; delete package.options; \ + delete package.babel; \ + delete package.scripts; \ + delete package.options; \ + delete package.devDependencies; \ require('fs').writeFileSync('package.json', JSON.stringify(package));" diff --git a/src/language/visitor.js b/src/language/visitor.js index 4ed7b11640..480c48a9c0 100644 --- a/src/language/visitor.js +++ b/src/language/visitor.js @@ -181,8 +181,8 @@ export function visit(root, visitor, keyMap) { } let editOffset = 0; for (let ii = 0; ii < edits.length; ii++) { - let [ editKey ] = edits[ii]; - const [ , editValue ] = edits[ii]; + let editKey = edits[ii][0]; + const editValue = edits[ii][1]; if (inArray) { editKey -= editOffset; } From e9fa66df99841509236df60c720b011225f00ddf Mon Sep 17 00:00:00 2001 From: Hafiz Ismail Date: Thu, 7 Jul 2016 06:37:16 +0800 Subject: [PATCH 16/18] Fix test assertions for validation test when using custom TypeInfo (#395) - Test should had broke for changes in #355 which improved validation messages with suggestions. - But `expect().to.deep.equal()` checks for the right number of errors but does not check for equality of error messages. --- src/validation/__tests__/validation-test.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/validation/__tests__/validation-test.js b/src/validation/__tests__/validation-test.js index 2a280257f8..412bfd99ec 100644 --- a/src/validation/__tests__/validation-test.js +++ b/src/validation/__tests__/validation-test.js @@ -64,10 +64,15 @@ describe('Validate: Supports full validation', () => { specifiedRules ); - expect(errors).to.deep.equal([ - new Error('Cannot query field "catOrDog" on type "QueryRoot".'), - new Error('Cannot query field "furColor" on type "Cat".'), - new Error('Cannot query field "isHousetrained" on type "Dog".'), + const errorMessages = errors.map(err => err.message); + + expect(errorMessages).to.deep.equal([ + 'Cannot query field "catOrDog" on type "QueryRoot". ' + + 'Did you mean "catOrDog"?', + 'Cannot query field "furColor" on type "Cat". ' + + 'Did you mean "furColor"?', + 'Cannot query field "isHousetrained" on type "Dog". ' + + 'Did you mean "isHousetrained"?' ]); }); From d06c883ee6165c64969e0c83d32de643f2610e6c Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Wed, 6 Jul 2016 15:49:55 -0700 Subject: [PATCH 17/18] Fix tests for node v0.10, widen test matrix --- .travis.yml | 3 ++- src/utilities/isValidJSValue.js | 11 ++++++----- src/utilities/isValidLiteralValue.js | 8 ++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3bac67660d..73744888f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: node_js node_js: - - "stable" + - "6" + - "5" - "4" - "iojs" - "0.12" diff --git a/src/utilities/isValidJSValue.js b/src/utilities/isValidJSValue.js index 7c04f5d5d8..5a571b9030 100644 --- a/src/utilities/isValidJSValue.js +++ b/src/utilities/isValidJSValue.js @@ -65,20 +65,21 @@ export function isValidJSValue(value: mixed, type: GraphQLInputType): [string] { const errors = []; // Ensure every provided field is defined. - for (const providedField of Object.keys(value)) { + Object.keys(value).forEach(providedField => { if (!fields[providedField]) { errors.push(`In field "${providedField}": Unknown field.`); } - } + }); // Ensure every defined field is valid. - for (const fieldName of Object.keys(fields)) { + Object.keys(fields).forEach(fieldName => { const newErrors = - isValidJSValue(value[fieldName], fields[fieldName].type); + isValidJSValue((value: any)[fieldName], fields[fieldName].type); errors.push(...(newErrors.map(error => `In field "${fieldName}": ${error}` ))); - } + }); + return errors; } diff --git a/src/utilities/isValidLiteralValue.js b/src/utilities/isValidLiteralValue.js index ea6e2397c0..211b8e56e1 100644 --- a/src/utilities/isValidLiteralValue.js +++ b/src/utilities/isValidLiteralValue.js @@ -85,17 +85,17 @@ export function isValidLiteralValue( // Ensure every provided field is defined. const fieldASTs = (valueAST: ObjectValue).fields; - for (const providedFieldAST of fieldASTs) { + fieldASTs.forEach(providedFieldAST => { if (!fields[providedFieldAST.name.value]) { errors.push( `In field "${providedFieldAST.name.value}": Unknown field.` ); } - } + }); // Ensure every defined field is valid. const fieldASTMap = keyMap(fieldASTs, fieldAST => fieldAST.name.value); - for (const fieldName of Object.keys(fields)) { + Object.keys(fields).forEach(fieldName => { const result = isValidLiteralValue( fields[fieldName].type, fieldASTMap[fieldName] && fieldASTMap[fieldName].value @@ -103,7 +103,7 @@ export function isValidLiteralValue( errors.push(...(result.map(error => `In field "${fieldName}": ${error}` ))); - } + }); return errors; } From 826cba3b76e87dbd25a01db5150f89624adaab32 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Wed, 6 Jul 2016 16:06:44 -0700 Subject: [PATCH 18/18] 0.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fe26fa2673..486a43a238 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graphql", - "version": "0.6.0", + "version": "0.6.1", "description": "A Query Language and Runtime which can target any service.", "contributors": [ "Lee Byron (http://leebyron.com/)",