diff --git a/package.json b/package.json index 3af7b45e91f..844a042c5f4 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "testonly": "mocha --full-trace src/**/__tests__/**/*-test.js", "testonly:cover": "nyc npm run testonly", "lint": "eslint --cache --report-unused-disable-directives src resources", - "benchmark": "node --predictable ./resources/benchmark.js", + "benchmark": "node --noconcurrent_sweeping --expose-gc --predictable ./resources/benchmark.js", "prettier": "prettier --ignore-path .gitignore --write --list-different \"**/*.{js,md,json,yml}\"", "prettier:check": "prettier --ignore-path .gitignore --check \"**/*.{js,md,json,yml}\"", "check": "flow check", diff --git a/resources/benchmark-fork.js b/resources/benchmark-fork.js index ac78e9e1d2a..e39b166be5a 100644 --- a/resources/benchmark-fork.js +++ b/resources/benchmark-fork.js @@ -11,7 +11,7 @@ function clock(count, fn) { for (let i = 0; i < count; ++i) { fn(); } - return Number(process.hrtime.bigint() - start) / count; + return Number(process.hrtime.bigint() - start); } if (require.main === module) { @@ -21,10 +21,14 @@ if (require.main === module) { const module = require(modulePath); clock(7, module.measure); // warm up + global.gc(); process.nextTick(() => { + const memBaseline = process.memoryUsage().heapUsed; + const clocked = clock(module.count, module.measure); process.send({ name: module.name, - clocked: clock(module.count, module.measure), + clocked: clocked / module.count, + memUsed: (process.memoryUsage().heapUsed - memBaseline) / module.count, }); }); } @@ -44,6 +48,9 @@ function sampleModule(modulePath) { } reject(error || new Error('Forked process closed without error')); }); + }).then(result => { + global.gc(); + return result; }); } diff --git a/resources/benchmark.js b/resources/benchmark.js index 244dacf50ba..dfff2741337 100644 --- a/resources/benchmark.js +++ b/resources/benchmark.js @@ -73,7 +73,8 @@ function babelBuild(dir) { rmdirRecursive('./benchmarkDist'); mkdirRecursive('./benchmarkDist'); - const babel = require('@babel/core'); + const babelPath = path.join(dir, 'node_modules', '@babel', 'core'); + const babel = require(babelPath); for (const filepath of readdirRecursive('./src')) { const srcPath = path.join('./src', filepath); const distPath = path.join('./benchmarkDist', filepath); @@ -101,9 +102,10 @@ async function collectSamples(modulePath) { // If time permits, increase sample size to reduce the margin of error. const start = Date.now(); while (samples.length < minSamples || (Date.now() - start) / 1e3 < maxTime) { - const { clocked } = await sampleModule(modulePath); + const { clocked, memUsed } = await sampleModule(modulePath); assert(clocked > 0); - samples.push(clocked); + assert(memUsed > 0); + samples.push({ clocked, memUsed }); } return samples; } @@ -125,15 +127,18 @@ function computeStats(samples) { // Compute the sample mean (estimate of the population mean). let mean = 0; - for (const x of samples) { - mean += x; + let meanMemUsed = 0; + for (const { clocked, memUsed } of samples) { + mean += clocked; + meanMemUsed += memUsed; } mean /= samples.length; + meanMemUsed /= samples.length; // Compute the sample variance (estimate of the population variance). let variance = 0; - for (const x of samples) { - variance += Math.pow(x - mean, 2); + for (const { clocked } of samples) { + variance += Math.pow(clocked - mean, 2); } variance /= samples.length - 1; @@ -156,8 +161,10 @@ function computeStats(samples) { const rme = (moe / mean) * 100 || 0; return { + memPerOp: Math.floor(meanMemUsed), ops: NS_PER_SEC / mean, deviation: rme, + numSamples: samples.length, }; } @@ -165,13 +172,17 @@ function beautifyBenchmark(results) { const nameMaxLen = maxBy(results, ({ name }) => name.length); const opsTop = maxBy(results, ({ ops }) => ops); const opsMaxLen = maxBy(results, ({ ops }) => beautifyNumber(ops).length); + const memPerOpMaxLen = maxBy( + results, + ({ memPerOp }) => beautifyBytes(memPerOp).length, + ); for (const result of results) { printBench(result); } function printBench(bench) { - const { name, ops, deviation, samples } = bench; + const { name, memPerOp, ops, deviation, numSamples } = bench; console.log( ' ' + nameStr() + @@ -181,7 +192,10 @@ function beautifyBenchmark(results) { grey('\xb1') + deviationStr() + cyan('%') + - grey(' (' + samples.length + ' runs sampled)'), + grey(' x ') + + memPerOpStr() + + '/op' + + grey(' (' + numSamples + ' runs sampled)'), ); function nameStr() { @@ -199,9 +213,19 @@ function beautifyBenchmark(results) { const colorFn = deviation > 5 ? red : deviation > 2 ? yellow : green; return colorFn(deviation.toFixed(2)); } + + function memPerOpStr() { + return beautifyBytes(memPerOp).padStart(memPerOpMaxLen); + } } } +function beautifyBytes(bytes) { + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log2(bytes) / 10); + return beautifyNumber(bytes / Math.pow(2, i * 10)) + ' ' + sizes[i]; +} + function beautifyNumber(num) { return Number(num.toFixed(num > 100 ? 0 : 2)).toLocaleString(); } diff --git a/src/__tests__/starWarsIntrospection-test.js b/src/__tests__/starWarsIntrospection-test.js index cbd97491882..7bcfcf9b818 100644 --- a/src/__tests__/starWarsIntrospection-test.js +++ b/src/__tests__/starWarsIntrospection-test.js @@ -7,152 +7,126 @@ import { graphqlSync } from '../graphql'; import { StarWarsSchema } from './starWarsSchema'; +function queryStarWars(source) { + const result = graphqlSync({ schema: StarWarsSchema, source }); + expect(Object.keys(result)).to.deep.equal(['data']); + return result.data; +} + describe('Star Wars Introspection Tests', () => { describe('Basic Introspection', () => { it('Allows querying the schema for types', () => { - const query = ` - query IntrospectionTypeQuery { + const data = queryStarWars(` + { __schema { types { name } } } - `; - const expected = { + `); + + // Include all types used by StarWars schema, introspection types and + // standard directives. For example, `Boolean` is used in `@skip`, + // `@include` and also inside introspection types. + expect(data).to.deep.equal({ __schema: { types: [ - { - name: 'Query', - }, - { - name: 'Episode', - }, - { - name: 'Character', - }, - { - name: 'String', - }, - { - name: 'Human', - }, - { - name: 'Droid', - }, - { - name: '__Schema', - }, - { - name: '__Type', - }, - { - name: '__TypeKind', - }, - { - name: 'Boolean', - }, - { - name: '__Field', - }, - { - name: '__InputValue', - }, - { - name: '__EnumValue', - }, - { - name: '__Directive', - }, - { - name: '__DirectiveLocation', - }, + { name: 'Query' }, + { name: 'Episode' }, + { name: 'Character' }, + { name: 'String' }, + { name: 'Human' }, + { name: 'Droid' }, + { name: '__Schema' }, + { name: '__Type' }, + { name: '__TypeKind' }, + { name: 'Boolean' }, + { name: '__Field' }, + { name: '__InputValue' }, + { name: '__EnumValue' }, + { name: '__Directive' }, + { name: '__DirectiveLocation' }, ], }, - }; - const result = graphqlSync(StarWarsSchema, query); - expect(result).to.deep.equal({ data: expected }); + }); }); it('Allows querying the schema for query type', () => { - const query = ` - query IntrospectionQueryTypeQuery { + const data = queryStarWars(` + { __schema { queryType { name } } } - `; - const expected = { + `); + + expect(data).to.deep.equal({ __schema: { queryType: { name: 'Query', }, }, - }; - const result = graphqlSync(StarWarsSchema, query); - expect(result).to.deep.equal({ data: expected }); + }); }); it('Allows querying the schema for a specific type', () => { - const query = ` - query IntrospectionDroidTypeQuery { + const data = queryStarWars(` + { __type(name: "Droid") { name } } - `; - const expected = { + `); + + expect(data).to.deep.equal({ __type: { name: 'Droid', }, - }; - const result = graphqlSync(StarWarsSchema, query); - expect(result).to.deep.equal({ data: expected }); + }); }); it('Allows querying the schema for an object kind', () => { - const query = ` - query IntrospectionDroidKindQuery { + const data = queryStarWars(` + { __type(name: "Droid") { name kind } } - `; - const expected = { + `); + + expect(data).to.deep.equal({ __type: { name: 'Droid', kind: 'OBJECT', }, - }; - const result = graphqlSync(StarWarsSchema, query); - expect(result).to.deep.equal({ data: expected }); + }); }); it('Allows querying the schema for an interface kind', () => { - const query = ` - query IntrospectionCharacterKindQuery { + const data = queryStarWars(` + { __type(name: "Character") { name kind } } - `; - const expected = { + `); + + expect(data).to.deep.equal({ __type: { name: 'Character', kind: 'INTERFACE', }, - }; - const result = graphqlSync(StarWarsSchema, query); - expect(result).to.deep.equal({ data: expected }); + }); }); it('Allows querying the schema for object fields', () => { - const query = ` - query IntrospectionDroidFieldsQuery { + const data = queryStarWars(` + { __type(name: "Droid") { name fields { @@ -164,64 +138,44 @@ describe('Star Wars Introspection Tests', () => { } } } - `; - const expected = { + `); + + expect(data).to.deep.equal({ __type: { name: 'Droid', fields: [ { name: 'id', - type: { - name: null, - kind: 'NON_NULL', - }, + type: { name: null, kind: 'NON_NULL' }, }, { name: 'name', - type: { - name: 'String', - kind: 'SCALAR', - }, + type: { name: 'String', kind: 'SCALAR' }, }, { name: 'friends', - type: { - name: null, - kind: 'LIST', - }, + type: { name: null, kind: 'LIST' }, }, { name: 'appearsIn', - type: { - name: null, - kind: 'LIST', - }, + type: { name: null, kind: 'LIST' }, }, { name: 'secretBackstory', - type: { - name: 'String', - kind: 'SCALAR', - }, + type: { name: 'String', kind: 'SCALAR' }, }, { name: 'primaryFunction', - type: { - name: 'String', - kind: 'SCALAR', - }, + type: { name: 'String', kind: 'SCALAR' }, }, ], }, - }; - - const result = graphqlSync(StarWarsSchema, query); - expect(result).to.deep.equal({ data: expected }); + }); }); it('Allows querying the schema for nested object fields', () => { - const query = ` - query IntrospectionDroidNestedFieldsQuery { + const data = queryStarWars(` + { __type(name: "Droid") { name fields { @@ -237,8 +191,9 @@ describe('Star Wars Introspection Tests', () => { } } } - `; - const expected = { + `); + + expect(data).to.deep.equal({ __type: { name: 'Droid', fields: [ @@ -301,14 +256,12 @@ describe('Star Wars Introspection Tests', () => { }, ], }, - }; - const result = graphqlSync(StarWarsSchema, query); - expect(result).to.deep.equal({ data: expected }); + }); }); it('Allows querying the schema for field args', () => { - const query = ` - query IntrospectionQueryTypeQuery { + const data = queryStarWars(` + { __schema { queryType { fields { @@ -330,8 +283,9 @@ describe('Star Wars Introspection Tests', () => { } } } - `; - const expected = { + `); + + expect(data).to.deep.equal({ __schema: { queryType: { fields: [ @@ -390,29 +344,25 @@ describe('Star Wars Introspection Tests', () => { ], }, }, - }; - - const result = graphqlSync(StarWarsSchema, query); - expect(result).to.deep.equal({ data: expected }); + }); }); it('Allows querying the schema for documentation', () => { - const query = ` - query IntrospectionDroidDescriptionQuery { + const data = queryStarWars(` + { __type(name: "Droid") { name description } } - `; - const expected = { + `); + + expect(data).to.deep.equal({ __type: { name: 'Droid', description: 'A mechanical creature in the Star Wars universe.', }, - }; - const result = graphqlSync(StarWarsSchema, query); - expect(result).to.deep.equal({ data: expected }); + }); }); }); }); diff --git a/src/type/__tests__/definition-test.js b/src/type/__tests__/definition-test.js index 3dd0f2eacb1..833e3460f21 100644 --- a/src/type/__tests__/definition-test.js +++ b/src/type/__tests__/definition-test.js @@ -192,6 +192,10 @@ describe('Type System: Objects', () => { isDeprecated: true, name: 'bar', args: [], + astNode: undefined, + resolve: undefined, + subscribe: undefined, + description: undefined, }); }); @@ -203,7 +207,17 @@ describe('Type System: Objects', () => { }), }); expect(objType.getFields()).to.deep.equal({ - f: { name: 'f', type: ScalarType, args: [], isDeprecated: false }, + f: { + name: 'f', + type: ScalarType, + args: [], + isDeprecated: false, + deprecationReason: undefined, + astNode: undefined, + resolve: undefined, + subscribe: undefined, + description: undefined, + }, }); }); @@ -233,6 +247,11 @@ describe('Type System: Objects', () => { }, ], isDeprecated: false, + deprecationReason: undefined, + astNode: undefined, + resolve: undefined, + subscribe: undefined, + description: undefined, }, }); }); @@ -669,7 +688,13 @@ describe('Type System: Input Objects', () => { }, }); expect(inputObjType.getFields()).to.deep.equal({ - f: { name: 'f', type: ScalarType }, + f: { + name: 'f', + description: undefined, + type: ScalarType, + defaultValue: undefined, + astNode: undefined, + }, }); }); @@ -681,7 +706,13 @@ describe('Type System: Input Objects', () => { }), }); expect(inputObjType.getFields()).to.deep.equal({ - f: { name: 'f', type: ScalarType }, + f: { + name: 'f', + description: undefined, + type: ScalarType, + defaultValue: undefined, + astNode: undefined, + }, }); }); diff --git a/src/type/__tests__/predicate-test.js b/src/type/__tests__/predicate-test.js index 7e655a7bab0..2a5f885b623 100644 --- a/src/type/__tests__/predicate-test.js +++ b/src/type/__tests__/predicate-test.js @@ -573,77 +573,87 @@ describe('Type predicates', () => { }); describe('isRequiredArgument', () => { - it('returns true for required arguments', () => { - const requiredArg = { + function buildArg(config) { + return { name: 'someArg', - type: GraphQLNonNull(GraphQLString), + description: undefined, + defaultValue: undefined, + astNode: undefined, + ...config, }; + } + + it('returns true for required arguments', () => { + const requiredArg = buildArg({ + type: GraphQLNonNull(GraphQLString), + }); expect(isRequiredArgument(requiredArg)).to.equal(true); }); it('returns false for optional arguments', () => { - const optArg1 = { - name: 'someArg', + const optArg1 = buildArg({ type: GraphQLString, - }; + }); expect(isRequiredArgument(optArg1)).to.equal(false); - const optArg2 = { - name: 'someArg', + const optArg2 = buildArg({ type: GraphQLString, defaultValue: null, - }; + }); expect(isRequiredArgument(optArg2)).to.equal(false); - const optArg3 = { - name: 'someArg', + const optArg3 = buildArg({ type: GraphQLList(GraphQLNonNull(GraphQLString)), - }; + }); expect(isRequiredArgument(optArg3)).to.equal(false); - const optArg4 = { - name: 'someArg', + const optArg4 = buildArg({ type: GraphQLNonNull(GraphQLString), defaultValue: 'default', - }; + }); expect(isRequiredArgument(optArg4)).to.equal(false); }); }); describe('isRequiredInputField', () => { + function buildInputField(config) { + return { + name: 'someInputField', + description: undefined, + defaultValue: undefined, + astNode: undefined, + ...config, + }; + } + it('returns true for required input field', () => { - const requiredField = { - name: 'someField', + const requiredField = buildInputField({ type: GraphQLNonNull(GraphQLString), - }; + }); expect(isRequiredInputField(requiredField)).to.equal(true); }); it('returns false for optional input field', () => { - const optField1 = { - name: 'someField', + const optField1 = buildInputField({ type: GraphQLString, - }; + }); expect(isRequiredInputField(optField1)).to.equal(false); - const optField2 = { - name: 'someField', + const optField2 = buildInputField({ type: GraphQLString, defaultValue: null, - }; + }); expect(isRequiredInputField(optField2)).to.equal(false); - const optField3 = { - name: 'someField', + const optField3 = buildInputField({ type: GraphQLList(GraphQLNonNull(GraphQLString)), - }; + }); expect(isRequiredInputField(optField3)).to.equal(false); - const optField4 = { - name: 'someField', + const optField4 = buildInputField({ type: GraphQLNonNull(GraphQLString), defaultValue: 'default', - }; + }); expect(isRequiredInputField(optField4)).to.equal(false); }); }); diff --git a/src/type/definition.js b/src/type/definition.js index c20b8ad3d8f..ee10fbe2ea7 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -558,9 +558,9 @@ export class GraphQLScalarType { this.parseValue = parseValue; this.parseLiteral = config.parseLiteral || (node => parseValue(valueFromASTUntyped(node))); - this.astNode = config.astNode; this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes); + devAssert(typeof config.name === 'string', 'Must provide name.'); devAssert( config.serialize == null || typeof config.serialize === 'function', @@ -663,9 +663,9 @@ export type GraphQLScalarTypeConfig = {| export class GraphQLObjectType { name: string; description: ?string; + isTypeOf: ?GraphQLIsTypeOfFn<*, *>; astNode: ?ObjectTypeDefinitionNode; extensionASTNodes: ?$ReadOnlyArray; - isTypeOf: ?GraphQLIsTypeOfFn<*, *>; _fields: Thunk>; _interfaces: Thunk>; @@ -673,9 +673,10 @@ export class GraphQLObjectType { constructor(config: GraphQLObjectTypeConfig<*, *>): void { this.name = config.name; this.description = config.description; + this.isTypeOf = config.isTypeOf; this.astNode = config.astNode; this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes); - this.isTypeOf = config.isTypeOf; + this._fields = defineFieldMap.bind(undefined, config); this._interfaces = defineInterfaces.bind(undefined, config); devAssert(typeof config.name === 'string', 'Must provide name.'); @@ -709,9 +710,9 @@ export class GraphQLObjectType { return { name: this.name, description: this.description, - isTypeOf: this.isTypeOf, interfaces: this.getInterfaces(), fields: fieldsToFieldsConfig(this.getFields()), + isTypeOf: this.isTypeOf, astNode: this.astNode, extensionASTNodes: this.extensionASTNodes || [], }; @@ -780,10 +781,15 @@ function defineFieldMap( })); return { - ...fieldConfig, - isDeprecated: Boolean(fieldConfig.deprecationReason), name: fieldName, + description: fieldConfig.description, + type: fieldConfig.type, args, + resolve: fieldConfig.resolve, + subscribe: fieldConfig.subscribe, + isDeprecated: Boolean(fieldConfig.deprecationReason), + deprecationReason: fieldConfig.deprecationReason, + astNode: fieldConfig.astNode, }; }); } @@ -794,12 +800,12 @@ function isPlainObj(obj) { function fieldsToFieldsConfig(fields) { return mapValue(fields, field => ({ + description: field.description, type: field.type, args: argsToArgsConfig(field.args), resolve: field.resolve, subscribe: field.subscribe, deprecationReason: field.deprecationReason, - description: field.description, astNode: field.astNode, })); } @@ -811,9 +817,9 @@ export function argsToArgsConfig( args, arg => arg.name, arg => ({ + description: arg.description, type: arg.type, defaultValue: arg.defaultValue, - description: arg.description, astNode: arg.astNode, }), ); @@ -821,10 +827,10 @@ export function argsToArgsConfig( export type GraphQLObjectTypeConfig = {| name: string, + description?: ?string, interfaces?: Thunk>, fields: Thunk>, isTypeOf?: ?GraphQLIsTypeOfFn, - description?: ?string, astNode?: ?ObjectTypeDefinitionNode, extensionASTNodes?: ?$ReadOnlyArray, |}; @@ -871,21 +877,21 @@ export type GraphQLFieldConfig< TContext, TArgs = { [argument: string]: any, ... }, > = {| + description?: ?string, type: GraphQLOutputType, args?: GraphQLFieldConfigArgumentMap, resolve?: GraphQLFieldResolver, subscribe?: GraphQLFieldResolver, deprecationReason?: ?string, - description?: ?string, astNode?: ?FieldDefinitionNode, |}; export type GraphQLFieldConfigArgumentMap = ObjMap; export type GraphQLArgumentConfig = {| + description?: ?string, type: GraphQLInputType, defaultValue?: mixed, - description?: ?string, astNode?: ?InputValueDefinitionNode, |}; @@ -905,16 +911,16 @@ export type GraphQLField< resolve?: GraphQLFieldResolver, subscribe?: GraphQLFieldResolver, isDeprecated?: boolean, - deprecationReason?: ?string, - astNode?: ?FieldDefinitionNode, + deprecationReason: ?string, + astNode: ?FieldDefinitionNode, |}; export type GraphQLArgument = {| name: string, + description: ?string, type: GraphQLInputType, - defaultValue?: mixed, - description?: ?string, - astNode?: ?InputValueDefinitionNode, + defaultValue: mixed, + astNode: ?InputValueDefinitionNode, |}; export function isRequiredArgument(arg: GraphQLArgument): boolean %checks { @@ -946,9 +952,9 @@ export type GraphQLFieldMap = ObjMap< export class GraphQLInterfaceType { name: string; description: ?string; + resolveType: ?GraphQLTypeResolver<*, *>; astNode: ?InterfaceTypeDefinitionNode; extensionASTNodes: ?$ReadOnlyArray; - resolveType: ?GraphQLTypeResolver<*, *>; _fields: Thunk>; _interfaces: Thunk>; @@ -956,9 +962,10 @@ export class GraphQLInterfaceType { constructor(config: GraphQLInterfaceTypeConfig<*, *>): void { this.name = config.name; this.description = config.description; + this.resolveType = config.resolveType; this.astNode = config.astNode; this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes); - this.resolveType = config.resolveType; + this._fields = defineFieldMap.bind(undefined, config); this._interfaces = defineInterfaces.bind(undefined, config); devAssert(typeof config.name === 'string', 'Must provide name.'); @@ -995,6 +1002,7 @@ export class GraphQLInterfaceType { interfaces: this.getInterfaces(), resolveType: this.resolveType, fields: fieldsToFieldsConfig(this.getFields()), + resolveType: this.resolveType, astNode: this.astNode, extensionASTNodes: this.extensionASTNodes || [], }; @@ -1012,6 +1020,7 @@ defineToJSON(GraphQLInterfaceType); export type GraphQLInterfaceTypeConfig = {| name: string, interfaces?: Thunk>, + description?: ?string, fields: Thunk>, /** * Optionally provide a custom type resolver function. If one is not provided, @@ -1019,7 +1028,6 @@ export type GraphQLInterfaceTypeConfig = {| * Object type. */ resolveType?: ?GraphQLTypeResolver, - description?: ?string, astNode?: ?InterfaceTypeDefinitionNode, extensionASTNodes?: ?$ReadOnlyArray, |}; @@ -1050,18 +1058,19 @@ export type GraphQLInterfaceTypeConfig = {| export class GraphQLUnionType { name: string; description: ?string; + resolveType: ?GraphQLTypeResolver<*, *>; astNode: ?UnionTypeDefinitionNode; extensionASTNodes: ?$ReadOnlyArray; - resolveType: ?GraphQLTypeResolver<*, *>; _types: Thunk>; constructor(config: GraphQLUnionTypeConfig<*, *>): void { this.name = config.name; this.description = config.description; + this.resolveType = config.resolveType; this.astNode = config.astNode; this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes); - this.resolveType = config.resolveType; + this._types = defineTypes.bind(undefined, config); devAssert(typeof config.name === 'string', 'Must provide name.'); devAssert( @@ -1086,8 +1095,8 @@ export class GraphQLUnionType { return { name: this.name, description: this.description, - resolveType: this.resolveType, types: this.getTypes(), + resolveType: this.resolveType, astNode: this.astNode, extensionASTNodes: this.extensionASTNodes || [], }; @@ -1115,6 +1124,7 @@ function defineTypes( export type GraphQLUnionTypeConfig = {| name: string, + description?: ?string, types: Thunk>, /** * Optionally provide a custom type resolver function. If one is not provided, @@ -1122,7 +1132,6 @@ export type GraphQLUnionTypeConfig = {| * Object type. */ resolveType?: ?GraphQLTypeResolver, - description?: ?string, astNode?: ?UnionTypeDefinitionNode, extensionASTNodes?: ?$ReadOnlyArray, |}; @@ -1163,6 +1172,7 @@ export class GraphQLEnumType /* */ { this.description = config.description; this.astNode = config.astNode; this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes); + this._values = defineEnumValues(this.name, config.values); this._valueLookup = new Map( this._values.map(enumValue => [enumValue.value, enumValue]), @@ -1260,18 +1270,18 @@ function defineEnumValues( return { name: valueName, description: value.description, + value: 'value' in value ? value.value : valueName, isDeprecated: Boolean(value.deprecationReason), deprecationReason: value.deprecationReason, astNode: value.astNode, - value: 'value' in value ? value.value : valueName, }; }); } export type GraphQLEnumTypeConfig /* */ = {| name: string, - values: GraphQLEnumValueConfigMap /* */, description?: ?string, + values: GraphQLEnumValueConfigMap /* */, astNode?: ?EnumTypeDefinitionNode, extensionASTNodes?: ?$ReadOnlyArray, |}; @@ -1279,19 +1289,19 @@ export type GraphQLEnumTypeConfig /* */ = {| export type GraphQLEnumValueConfigMap /* */ = ObjMap */>; export type GraphQLEnumValueConfig /* */ = {| + description?: ?string, value?: any /* T */, deprecationReason?: ?string, - description?: ?string, astNode?: ?EnumValueDefinitionNode, |}; export type GraphQLEnumValue /* */ = {| name: string, description: ?string, - isDeprecated?: boolean, - deprecationReason: ?string, - astNode?: ?EnumValueDefinitionNode, value: any /* T */, + isDeprecated: boolean, + deprecationReason: ?string, + astNode: ?EnumValueDefinitionNode, |}; /** @@ -1327,6 +1337,7 @@ export class GraphQLInputObjectType { this.description = config.description; this.astNode = config.astNode; this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes); + this._fields = defineInputFieldMap.bind(undefined, config); devAssert(typeof config.name === 'string', 'Must provide name.'); } @@ -1382,22 +1393,28 @@ function defineInputFieldMap( `${config.name}.${fieldName} field has a resolve property, but Input Types cannot define resolvers.`, ); - return { ...fieldConfig, name: fieldName }; + return { + name: fieldName, + description: fieldConfig.description, + type: fieldConfig.type, + defaultValue: fieldConfig.defaultValue, + astNode: fieldConfig.astNode, + }; }); } export type GraphQLInputObjectTypeConfig = {| name: string, - fields: Thunk, description?: ?string, + fields: Thunk, astNode?: ?InputObjectTypeDefinitionNode, extensionASTNodes?: ?$ReadOnlyArray, |}; export type GraphQLInputFieldConfig = {| + description?: ?string, type: GraphQLInputType, defaultValue?: mixed, - description?: ?string, astNode?: ?InputValueDefinitionNode, |}; @@ -1405,10 +1422,10 @@ export type GraphQLInputFieldConfigMap = ObjMap; export type GraphQLInputField = {| name: string, + description: ?string, type: GraphQLInputType, - defaultValue?: mixed, - description?: ?string, - astNode?: ?InputValueDefinitionNode, + defaultValue: mixed, + astNode: ?InputValueDefinitionNode, |}; export function isRequiredInputField( diff --git a/src/type/directives.js b/src/type/directives.js index 64640a5c481..9691994ebac 100644 --- a/src/type/directives.js +++ b/src/type/directives.js @@ -58,10 +58,10 @@ export class GraphQLDirective { constructor(config: GraphQLDirectiveConfig): void { this.name = config.name; this.description = config.description; - this.locations = config.locations; this.isRepeatable = config.isRepeatable != null && config.isRepeatable; this.astNode = config.astNode; + devAssert(config.name, 'Directive must be named.'); devAssert( Array.isArray(config.locations), diff --git a/src/type/introspection.js b/src/type/introspection.js index 1431417327b..bc180e36ad8 100644 --- a/src/type/introspection.js +++ b/src/type/introspection.js @@ -438,14 +438,26 @@ export const SchemaMetaFieldDef: GraphQLField = { description: 'Access the current type schema of this server.', args: [], resolve: (source, args, context, { schema }) => schema, + deprecationReason: undefined, + astNode: undefined, }; export const TypeMetaFieldDef: GraphQLField = { name: '__type', type: __Type, description: 'Request the type information of a single type.', - args: [{ name: 'name', type: GraphQLNonNull(GraphQLString) }], + args: [ + { + name: 'name', + description: undefined, + type: GraphQLNonNull(GraphQLString), + defaultValue: undefined, + astNode: undefined, + }, + ], resolve: (source, { name }, context, { schema }) => schema.getType(name), + deprecationReason: undefined, + astNode: undefined, }; export const TypeNameMetaFieldDef: GraphQLField = { @@ -454,6 +466,8 @@ export const TypeNameMetaFieldDef: GraphQLField = { description: 'The name of the current Object type at runtime.', args: [], resolve: (source, args, context, { parentType }) => parentType.name, + deprecationReason: undefined, + astNode: undefined, }; export const introspectionTypes = Object.freeze([ diff --git a/src/type/schema.js b/src/type/schema.js index 9a874f792bd..bc29d05a0c9 100644 --- a/src/type/schema.js +++ b/src/type/schema.js @@ -28,7 +28,6 @@ import { type GraphQLAbstractType, type GraphQLObjectType, type GraphQLInterfaceType, - isAbstractType, isObjectType, isInterfaceType, isUnionType, @@ -119,6 +118,7 @@ export function assertSchema(schema: mixed): GraphQLSchema { export class GraphQLSchema { astNode: ?SchemaDefinitionNode; extensionASTNodes: ?$ReadOnlyArray; + _queryType: ?GraphQLObjectType; _mutationType: ?GraphQLObjectType; _subscriptionType: ?GraphQLObjectType; @@ -159,14 +159,15 @@ export class GraphQLSchema { ); } + this.astNode = config.astNode; + this.extensionASTNodes = config.extensionASTNodes; + this.__allowedLegacyNames = config.allowedLegacyNames || []; this._queryType = config.query; this._mutationType = config.mutation; this._subscriptionType = config.subscription; // Provide specified directives (e.g. @include and @skip) by default. this._directives = config.directives || specifiedDirectives; - this.astNode = config.astNode; - this.extensionASTNodes = config.extensionASTNodes; // Build type map now to detect any errors within this schema. const initialTypes: Array = [ @@ -216,8 +217,6 @@ export class GraphQLSchema { } } } - } else if (isAbstractType(type) && !this._implementations[type.name]) { - this._implementations[type.name] = []; } } } @@ -272,17 +271,15 @@ export class GraphQLSchema { abstractType: GraphQLAbstractType, possibleType: GraphQLObjectType, ): boolean { - const possibleTypeMap = this._possibleTypeMap; - - if (!possibleTypeMap[abstractType.name]) { - const possibleTypes = this.getPossibleTypes(abstractType); - possibleTypeMap[abstractType.name] = possibleTypes.reduce((map, type) => { + if (this._possibleTypeMap[abstractType.name] == null) { + const map = Object.create(null); + for (const type of this.getPossibleTypes(abstractType)) { map[type.name] = true; - return map; - }, Object.create(null)); + } + this._possibleTypeMap[abstractType.name] = map; } - return Boolean(possibleTypeMap[abstractType.name][possibleType.name]); + return Boolean(this._possibleTypeMap[abstractType.name][possibleType.name]); } isImplementation( @@ -309,11 +306,11 @@ export class GraphQLSchema { allowedLegacyNames: $ReadOnlyArray, |} { return { - types: objectValues(this.getTypeMap()), - directives: this.getDirectives().slice(), query: this.getQueryType(), mutation: this.getMutationType(), subscription: this.getSubscriptionType(), + types: objectValues(this.getTypeMap()), + directives: this.getDirectives().slice(), astNode: this.astNode, extensionASTNodes: this.extensionASTNodes || [], assumeValid: this.__validationErrors !== undefined,