diff --git a/package-lock.json b/package-lock.json index a323e1f..eb422b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@repeaterjs/repeater": "^3.0.6", - "graphql": "^17.0.0-alpha.3" + "graphql": "^17.0.0-alpha.8" }, "devDependencies": { "@changesets/cli": "^2.27.12", @@ -4027,7 +4027,9 @@ "license": "MIT" }, "node_modules/graphql": { - "version": "17.0.0-alpha.3", + "version": "17.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-17.0.0-alpha.8.tgz", + "integrity": "sha512-j9Jn56NCWVaLMt1hSNkMDoCuAisBwY3bxp/5tbrJuPtNtHg9dAf4NjKnlVDCksVP3jBVcipFaEXKWsdNxTlcyg==", "license": "MIT", "engines": { "node": "^16.19.0 || ^18.14.0 || >=19.7.0" diff --git a/package.json b/package.json index f62557c..bc45418 100644 --- a/package.json +++ b/package.json @@ -40,12 +40,12 @@ "test": "npm run lint && npm run check && npm run testonly && npm run prettier:check && npm run check:spelling", "lint": "eslint --cache --max-warnings 0 .", "check": "tsc --pretty", - "testonly": "mocha --full-trace 'src/**/__tests__/**/*-test.ts'", + "testonly": "mocha --full-trace src/**/__tests__/**/*-test.ts", "testonly:cover": "c8 npm run testonly", "testonly:watch": "npm run testonly -- --watch", "prettier": "prettier --cache --cache-strategy metadata --write --list-different .", "prettier:check": "prettier --cache --cache-strategy metadata --check .", - "check:spelling": "cspell --cache --no-progress '**/*'", + "check:spelling": "cspell --cache --no-progress \"**/*\"", "build:npm:dual": "node --loader ts-node/esm resources/build-npm-dual.ts", "build:npm:esm-only": "node --loader ts-node/esm resources/build-npm-esm-only.ts", "build:deno": "node --loader ts-node/esm resources/build-deno.ts", @@ -77,6 +77,6 @@ }, "dependencies": { "@repeaterjs/repeater": "^3.0.6", - "graphql": "^17.0.0-alpha.3" + "graphql": "^17.0.0-alpha.8" } } diff --git a/src/stitch/Planner.ts b/src/stitch/Planner.ts index eaaeeb0..0a6f889 100644 --- a/src/stitch/Planner.ts +++ b/src/stitch/Planner.ts @@ -28,7 +28,7 @@ import { invariant } from '../utilities/invariant.js'; import { memoize2 } from '../utilities/memoize2.js'; import { memoize3 } from '../utilities/memoize3.js'; -import type { Subschema, SuperSchema } from './SuperSchema.js'; +import type { Subschema, SuperSchema, VariableValues } from './SuperSchema.js'; export interface RootPlan { superSchema: SuperSchema; @@ -59,8 +59,6 @@ interface SelectionSplit { otherSelections: ReadonlyArray; } -const emptyObject = {}; - export const createPlanner = memoize2( (superSchema: SuperSchema, operation: OperationDefinitionNode) => new Planner(superSchema, operation), @@ -85,11 +83,7 @@ export class Planner { this.variableDefinitions = operation.variableDefinitions ?? []; } - createRootPlan( - variableValues: { - [key: string]: unknown; - } = emptyObject, - ): RootPlan | GraphQLError { + createRootPlan(variableValues: VariableValues): RootPlan | GraphQLError { const rootType = this.superSchema.getRootType(this.operation.operation); if (rootType === undefined) { @@ -432,14 +426,8 @@ export class Planner { selectionSplit.ownSelections, { kind: Kind.FIELD, - name: { - kind: Kind.NAME, - value: TypeNameMetaFieldDef.name, - }, - alias: { - kind: Kind.NAME, - value: '__stitching__typename', - }, + name: { kind: Kind.NAME, value: TypeNameMetaFieldDef.name }, + alias: { kind: Kind.NAME, value: '__stitching__typename' }, }, ); } diff --git a/src/stitch/SuperSchema.ts b/src/stitch/SuperSchema.ts index 5a1603e..2a3db3c 100644 --- a/src/stitch/SuperSchema.ts +++ b/src/stitch/SuperSchema.ts @@ -1,4 +1,5 @@ import type { + ConstValueNode, DirectiveLocation, DocumentNode, ExecutionResult, @@ -26,6 +27,7 @@ import type { VariableDefinitionNode, } from 'graphql'; import { + coerceInputLiteral, coerceInputValue, execute, GraphQLDirective, @@ -48,21 +50,16 @@ import { Kind, OperationTypeNode, print, - valueFromAST, + validateInputValue, } from 'graphql'; -import type { ObjMap } from '../types/ObjMap.js'; +import type { ObjMap, ReadOnlyObjMap } from '../types/ObjMap.js'; import type { PromiseOrValue } from '../types/PromiseOrValue.js'; import type { SimpleAsyncGenerator } from '../types/SimpleAsyncGenerator.js'; import { AccumulatorMap } from '../utilities/AccumulatorMap.js'; -import { inspect } from '../utilities/inspect.js'; import { printPathArray } from '../utilities/printPathArray.js'; -type CoercedVariableValues = - | { errors: ReadonlyArray; coerced?: never } - | { coerced: { [variable: string]: unknown }; errors?: never }; - const operations = [ OperationTypeNode.QUERY, OperationTypeNode.MUTATION, @@ -85,6 +82,27 @@ export interface Subschema { subscriber?: Subscriber; } +export interface VariableValues { + readonly sources: ReadOnlyObjMap; + readonly coerced: ReadOnlyObjMap; +} + +interface VariableValueSource { + readonly signature: GraphQLVariableSignature; + readonly value?: unknown; +} + +export interface GraphQLVariableSignature { + name: string; + type: GraphQLInputType; + defaultValue?: never; + default: { literal: ConstValueNode } | undefined; +} + +type VariableValuesOrErrors = + | { variableValues: VariableValues; errors?: never } + | { errors: ReadonlyArray; variableValues?: never }; + /** * @internal */ @@ -122,11 +140,7 @@ export class SuperSchema { const introspectionSubschema: Subschema = { schema: this.mergedSchema, - executor: (args) => - execute({ - ...args, - schema: this.mergedSchema, - }), + executor: (args) => execute({ ...args, schema: this.mergedSchema }), }; for (const [name, type] of Object.entries(this.mergedSchema.getTypeMap())) { if (!name.startsWith('__')) { @@ -531,11 +545,11 @@ export class SuperSchema { varDefNodes: ReadonlyArray, inputs: { readonly [variable: string]: unknown }, options?: { maxErrors?: number }, - ): CoercedVariableValues { + ): VariableValuesOrErrors { const errors = []; const maxErrors = options?.maxErrors; try { - const coerced = this._coerceVariableValues( + const variableValues = this._coerceVariableValues( varDefNodes, inputs, (error) => { @@ -549,7 +563,7 @@ export class SuperSchema { ); if (errors.length === 0) { - return { coerced }; + return { variableValues }; } } catch (error) { errors.push(error); @@ -588,74 +602,85 @@ export class SuperSchema { varDefNodes: ReadonlyArray, inputs: { readonly [variable: string]: unknown }, onError: (error: GraphQLError) => void, - ): { [variable: string]: unknown } { - const coercedValues: { [variable: string]: unknown } = {}; + hideSuggestions?: boolean, + ): VariableValues { + const sources: ObjMap = Object.create(null); + const coerced: ObjMap = Object.create(null); for (const varDefNode of varDefNodes) { - const varName = varDefNode.variable.name.value; - const varType = this._typeFromAST(varDefNode.type); - if (!isInputType(varType)) { - // Must use input types for variables. This should be caught during - // validation, however is checked again here for safety. - const varTypeStr = print(varDefNode.type); - onError( - new GraphQLError( - `Variable "$${varName}" expected value of type "${varTypeStr}" which cannot be used as an input type.`, - { nodes: varDefNode.type }, - ), - ); + const varSignature = this._getVariableSignature(varDefNode); + + if (varSignature instanceof GraphQLError) { + onError(varSignature); continue; } + const { name: varName, type: varType } = varSignature; + let value: unknown; if (!Object.hasOwn(inputs, varName)) { + sources[varName] = { signature: varSignature }; if (varDefNode.defaultValue) { - coercedValues[varName] = valueFromAST( + coerced[varName] = coerceInputLiteral( varDefNode.defaultValue, varType, ); - } else if (isNonNullType(varType)) { - const varTypeStr = inspect(varType); - onError( - new GraphQLError( - `Variable "$${varName}" of required type "${varTypeStr}" was not provided.`, - { nodes: varDefNode }, - ), - ); + continue; + } else if (!isNonNullType(varType)) { + // Non-provided values for nullable variables are omitted. + continue; } - continue; + } else { + value = inputs[varName]; + sources[varName] = { signature: varSignature, value }; } - const value = inputs[varName]; - if (value === null && isNonNullType(varType)) { - const varTypeStr = inspect(varType); - onError( - new GraphQLError( - `Variable "$${varName}" of non-null type "${varTypeStr}" must not be null.`, - { nodes: varDefNode }, - ), + const coercedValue = coerceInputValue(value, varType); + if (coercedValue !== undefined) { + coerced[varName] = coercedValue; + } else { + validateInputValue( + value, + varType, + (error, path) => { + onError( + new GraphQLError( + `Variable "$${varName}" has invalid value${printPathArray(path)}: ${ + error.message + }`, + { nodes: varDefNode, originalError: error }, + ), + ); + }, + hideSuggestions, ); - continue; } + } - coercedValues[varName] = coerceInputValue( - value, - varType, - (path, invalidValue, error) => { - let prefix = - `Variable "$${varName}" got invalid value ` + inspect(invalidValue); - if (path.length > 0) { - prefix += ` at "${varName}${printPathArray(path)}"`; - } - onError( - new GraphQLError(prefix + '; ' + error.message, { - nodes: varDefNode, - originalError: error.originalError, - }), - ); - }, + return { sources, coerced }; + } + + _getVariableSignature( + varDefNode: VariableDefinitionNode, + ): GraphQLVariableSignature | GraphQLError { + const varName = varDefNode.variable.name.value; + const varType = this._typeFromAST(varDefNode.type); + + if (!isInputType(varType)) { + // Must use input types for variables. This should be caught during + // validation, however is checked again here for safety. + const varTypeStr = print(varDefNode.type); + return new GraphQLError( + `Variable "$${varName}" expected value of type "${varTypeStr}" which cannot be used as an input type.`, + { nodes: varDefNode.type }, ); } - return coercedValues; + const defaultValue = varDefNode.defaultValue; + + return { + name: varName, + type: varType, + default: defaultValue && { literal: defaultValue }, + }; } getSubschemaId(subschema: Subschema): string { diff --git a/src/stitch/__tests__/Planner-test.ts b/src/stitch/__tests__/Planner-test.ts index a82ca11..62aaa56 100644 --- a/src/stitch/__tests__/Planner-test.ts +++ b/src/stitch/__tests__/Planner-test.ts @@ -20,14 +20,7 @@ import type { Subschema } from '../SuperSchema.js'; import { SuperSchema } from '../SuperSchema.js'; function getSubschema(schema: GraphQLSchema): Subschema { - return { - schema, - executor: (args) => - execute({ - ...args, - schema, - }), - }; + return { schema, executor: (args) => execute({ ...args, schema }) }; } function createRootPlan( @@ -40,7 +33,7 @@ function createRootPlan( const planner = new Planner(superSchema, operation); - const rootPlan = planner.createRootPlan(); + const rootPlan = planner.createRootPlan({ coerced: {}, sources: {} }); invariant(!(rootPlan instanceof GraphQLError)); diff --git a/src/stitch/__tests__/compose-test.ts b/src/stitch/__tests__/compose-test.ts index 97b94f3..90be8b0 100644 --- a/src/stitch/__tests__/compose-test.ts +++ b/src/stitch/__tests__/compose-test.ts @@ -28,12 +28,7 @@ import { SuperSchema } from '../SuperSchema.js'; function getSubschema(schema: GraphQLSchema, rootValue: unknown): Subschema { return { schema, - executor: (args) => - execute({ - ...args, - schema, - rootValue, - }), + executor: (args) => execute({ ...args, schema, rootValue }), }; } @@ -45,7 +40,10 @@ function executeWithComposer( invariant(queryType !== undefined); - const plan = new Planner(superSchema, operation).createRootPlan(); + const plan = new Planner(superSchema, operation).createRootPlan({ + coerced: {}, + sources: {}, + }); invariant(!(plan instanceof GraphQLError)); @@ -67,9 +65,7 @@ function executeWithComposer( subschemaPlanResults.push({ subschemaPlan, - initialResult: subschemaPlan.toSubschema.executor({ - document, - }), + initialResult: subschemaPlan.toSubschema.executor({ document }), }); } @@ -121,20 +117,10 @@ describe('Composer', () => { expect(result).to.deep.equal({ data: { - __schema: { - queryType: { - name: 'Query', - }, - }, - __type: { - name: 'Query', - }, - someObject: { - someField: 'someField', - }, - anotherObject: { - someField: 'someField', - }, + __schema: { queryType: { name: 'Query' } }, + __type: { name: 'Query' }, + someObject: { someField: 'someField' }, + anotherObject: { someField: 'someField' }, }, }); }); @@ -177,10 +163,7 @@ describe('Composer', () => { expect(result).to.deep.equal({ data: { - someObject: { - someField: 'someField', - anotherField: 'anotherField', - }, + someObject: { someField: 'someField', anotherField: 'anotherField' }, }, }); }); @@ -282,16 +265,8 @@ describe('Composer', () => { expect(result).to.deep.equal({ data: { - someObject: [ - { - someField: ['someField'], - }, - ], - anotherObject: [ - { - someField: ['someField'], - }, - ], + someObject: [{ someField: ['someField'] }], + anotherObject: [{ someField: ['someField'] }], }, }); }); @@ -338,14 +313,8 @@ describe('Composer', () => { expect(result).to.deep.equal({ data: { someObject: [ - { - someField: ['someFieldA'], - anotherField: ['anotherField'], - }, - { - someField: ['someFieldB'], - anotherField: ['anotherField'], - }, + { someField: ['someFieldA'], anotherField: ['anotherField'] }, + { someField: ['someFieldB'], anotherField: ['anotherField'] }, ], }, }); diff --git a/src/stitch/__tests__/graphql-js/abstract-test.ts b/src/stitch/__tests__/graphql-js/abstract-test.ts index b2ef1e7..e680a22 100644 --- a/src/stitch/__tests__/graphql-js/abstract-test.ts +++ b/src/stitch/__tests__/graphql-js/abstract-test.ts @@ -72,9 +72,7 @@ describe('Execute: Handles execution of abstract types', () => { it('isTypeOf used to resolve runtime type for Interface', async () => { const PetType = new GraphQLInterfaceType({ name: 'Pet', - fields: { - name: { type: GraphQLString }, - }, + fields: { name: { type: GraphQLString } }, }); const DogType = new GraphQLObjectType({ @@ -135,14 +133,8 @@ describe('Execute: Handles execution of abstract types', () => { expect(await executeQuery({ schema, query })).to.deep.equal({ data: { pets: [ - { - name: 'Odie', - woofs: true, - }, - { - name: 'Garfield', - meows: false, - }, + { name: 'Odie', woofs: true }, + { name: 'Garfield', meows: false }, ], }, }); @@ -151,9 +143,7 @@ describe('Execute: Handles execution of abstract types', () => { it('isTypeOf can throw', async () => { const PetType = new GraphQLInterfaceType({ name: 'Pet', - fields: { - name: { type: GraphQLString }, - }, + fields: { name: { type: GraphQLString } }, }); const DogType = new GraphQLObjectType({ @@ -212,9 +202,7 @@ describe('Execute: Handles execution of abstract types', () => { `; expectJSON(await executeQuery({ schema, query })).toDeepEqual({ - data: { - pets: [null, null], - }, + data: { pets: [null, null] }, errors: [ { message: 'We are testing this error', @@ -233,9 +221,7 @@ describe('Execute: Handles execution of abstract types', () => { it('isTypeOf can return false', async () => { const PetType = new GraphQLInterfaceType({ name: 'Pet', - fields: { - name: { type: GraphQLString }, - }, + fields: { name: { type: GraphQLString } }, }); const DogType = new GraphQLObjectType({ @@ -253,12 +239,7 @@ describe('Execute: Handles execution of abstract types', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', - fields: { - pet: { - type: PetType, - resolve: () => ({}), - }, - }, + fields: { pet: { type: PetType, resolve: () => ({}) } }, }), types: [DogType], }); @@ -344,14 +325,8 @@ describe('Execute: Handles execution of abstract types', () => { expect(await executeQuery({ schema, query })).to.deep.equal({ data: { pets: [ - { - name: 'Odie', - woofs: true, - }, - { - name: 'Garfield', - meows: false, - }, + { name: 'Odie', woofs: true }, + { name: 'Garfield', meows: false }, ], }, }); @@ -368,9 +343,7 @@ describe('Execute: Handles execution of abstract types', () => { } throw error; }, - fields: { - name: { type: GraphQLString }, - }, + fields: { name: { type: GraphQLString } }, }); const DogType = new GraphQLObjectType({ @@ -421,9 +394,7 @@ describe('Execute: Handles execution of abstract types', () => { `; expectJSON(await executeQuery({ schema, query })).toDeepEqual({ - data: { - pets: [null, null], - }, + data: { pets: [null, null] }, errors: [ { message: 'We are testing this error', @@ -500,30 +471,16 @@ describe('Execute: Handles execution of abstract types', () => { const rootValue = { pets: [ - { - __typename: 'Dog', - name: 'Odie', - woofs: true, - }, - { - __typename: 'Cat', - name: 'Garfield', - meows: false, - }, + { __typename: 'Dog', name: 'Odie', woofs: true }, + { __typename: 'Cat', name: 'Garfield', meows: false }, ], }; expect(await executeQuery({ schema, query, rootValue })).to.deep.equal({ data: { pets: [ - { - name: 'Odie', - woofs: true, - }, - { - name: 'Garfield', - meows: false, - }, + { name: 'Odie', woofs: true }, + { name: 'Garfield', meows: false }, ], }, }); @@ -566,30 +523,16 @@ describe('Execute: Handles execution of abstract types', () => { const rootValue = { pets: [ - { - __typename: 'Dog', - name: 'Odie', - woofs: true, - }, - { - __typename: 'Cat', - name: 'Garfield', - meows: false, - }, + { __typename: 'Dog', name: 'Odie', woofs: true }, + { __typename: 'Cat', name: 'Garfield', meows: false }, ], }; expect(await executeQuery({ schema, query, rootValue })).to.deep.equal({ data: { pets: [ - { - name: 'Odie', - woofs: true, - }, - { - name: 'Garfield', - meows: false, - }, + { name: 'Odie', woofs: true }, + { name: 'Garfield', meows: false }, ], }, }); @@ -630,11 +573,7 @@ describe('Execute: Handles execution of abstract types', () => { expectJSON(result).toDeepEqual({ data: { pet: null }, errors: [ - { - message, - locations: [{ line: 3, column: 9 }], - path: ['pet'], - }, + { message, locations: [{ line: 3, column: 9 }], path: ['pet'] }, ], }); }, @@ -661,7 +600,7 @@ describe('Execute: Handles execution of abstract types', () => { // @ts-expect-error assertInterfaceType(schema.getType('Pet')).resolveType = () => []; expectError({ forTypeName: undefined }).toEqual( - 'Abstract type "Pet" must resolve to an Object type at runtime for field "Query.pet" with value { __typename: undefined }, received "[]".', + 'Abstract type "Pet" must resolve to an Object type at runtime for field "Query.pet" with value { __typename: undefined }, received "[]", which is not a valid Object type name.', ); // FIXME: workaround since we can't inject resolveType into SDL @@ -669,7 +608,7 @@ describe('Execute: Handles execution of abstract types', () => { assertInterfaceType(schema.getType('Pet')).resolveType = () => schema.getType('Cat'); expectError({ forTypeName: undefined }).toEqual( - 'Support for returning GraphQLObjectType from resolveType was removed in graphql-js@16.0.0 please return type name instead.', + 'Abstract type "Pet" must resolve to an Object type at runtime for field "Query.pet" with value { __typename: undefined }, received "Cat", which is not a valid Object type name.', ); }); }); diff --git a/src/stitch/__tests__/graphql-js/execute-test.ts b/src/stitch/__tests__/graphql-js/execute-test.ts index 75c0b26..bacb9a8 100644 --- a/src/stitch/__tests__/graphql-js/execute-test.ts +++ b/src/stitch/__tests__/graphql-js/execute-test.ts @@ -176,10 +176,7 @@ describe('Execute: Handles basic execution tasks', () => { deep: { b: 'Banana', c: 'Cherry', - deeper: { - b: 'Banana', - c: 'Cherry', - }, + deeper: { b: 'Banana', c: 'Cherry' }, }, }, }); @@ -237,7 +234,15 @@ describe('Execute: Handles basic execution tasks', () => { expect(resolvedInfo).to.deep.include({ fieldNodes: [field], path: { prev: undefined, key: 'result', typename: 'Test' }, - variableValues: { var: 'abc' }, + variableValues: { + coerced: { var: 'abc' }, + sources: { + var: { + signature: { name: 'var', type: GraphQLString, default: undefined }, + value: 'abc', + }, + }, + }, }); }); @@ -291,11 +296,7 @@ describe('Execute: Handles basic execution tasks', () => { prev: { key: 0, typename: undefined, - prev: { - key: 'l1', - typename: 'SomeQuery', - prev: undefined, - }, + prev: { key: 'l1', typename: 'SomeQuery', prev: undefined }, }, }); }); @@ -547,9 +548,7 @@ describe('Execute: Handles basic execution tasks', () => { type: new GraphQLList( new GraphQLObjectType({ name: 'Food', - fields: { - name: { type: GraphQLString }, - }, + fields: { name: { type: GraphQLString } }, }), ), resolve() { @@ -636,14 +635,8 @@ describe('Execute: Handles basic execution tasks', () => { const A: GraphQLObjectType = new GraphQLObjectType({ name: 'A', fields: () => ({ - nullableA: { - type: A, - resolve: () => ({}), - }, - nonNullA: { - type: new GraphQLNonNull(A), - resolve: () => ({}), - }, + nullableA: { type: A, resolve: () => ({}) }, + nonNullA: { type: new GraphQLNonNull(A), resolve: () => ({}) }, throws: { type: new GraphQLNonNull(GraphQLString), resolve: () => { @@ -655,12 +648,7 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'query', - fields: () => ({ - nullableA: { - type: A, - resolve: () => ({}), - }, - }), + fields: () => ({ nullableA: { type: A, resolve: () => ({}) } }), }), }); @@ -680,11 +668,7 @@ describe('Execute: Handles basic execution tasks', () => { const result = executeSync({ schema, document }); expectJSON(result).toDeepEqual({ - data: { - nullableA: { - aliasedA: null, - }, - }, + data: { nullableA: { aliasedA: null } }, errors: [ { message: 'Catch me if you can', @@ -699,9 +683,7 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Type', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), }); const document = parse('{ a }'); @@ -715,9 +697,7 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Type', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), }); const document = parse('query Example { a }'); @@ -731,9 +711,7 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Type', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), }); @@ -752,9 +730,7 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Type', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), }); const document = parse('fragment Example on Type { a }'); @@ -770,9 +746,7 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Type', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), }); const document = parse(` @@ -795,9 +769,7 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Type', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), }); const document = parse(` @@ -816,9 +788,7 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Type', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), }); const document = parse('{ a }'); @@ -834,21 +804,15 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Q', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), mutation: new GraphQLObjectType({ name: 'M', - fields: { - c: { type: GraphQLString }, - }, + fields: { c: { type: GraphQLString } }, }), subscription: new GraphQLObjectType({ name: 'S', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), }); const document = parse(` @@ -867,15 +831,11 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Q', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), mutation: new GraphQLObjectType({ name: 'M', - fields: { - c: { type: GraphQLString }, - }, + fields: { c: { type: GraphQLString } }, }), }); const document = parse(` @@ -893,15 +853,11 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Q', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), subscription: new GraphQLObjectType({ name: 'S', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), }); const document = parse(` @@ -980,9 +936,7 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Type', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), }); const document = parse(` @@ -1000,50 +954,38 @@ describe('Execute: Handles basic execution tasks', () => { const rootValue = { a: 'b' }; const result = executeSync({ schema, document, rootValue }); - expect(result).to.deep.equal({ - data: { a: 'b' }, - }); + expect(result).to.deep.equal({ data: { a: 'b' } }); }); it('ignores missing sub selections on fields', () => { const someType = new GraphQLObjectType({ name: 'SomeType', - fields: { - b: { type: GraphQLString }, - }, + fields: { b: { type: GraphQLString } }, }); const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', - fields: { - a: { type: someType }, - }, + fields: { a: { type: someType } }, }), }); const document = parse('{ a }'); const rootValue = { a: { b: 'c' } }; const result = executeSync({ schema, document, rootValue }); - expect(result).to.deep.equal({ - data: { a: {} }, - }); + expect(result).to.deep.equal({ data: { a: {} } }); }); it('does not include illegal fields in output', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Q', - fields: { - a: { type: GraphQLString }, - }, + fields: { a: { type: GraphQLString } }, }), }); const document = parse('{ thisIsIllegalDoNotIncludeMe }'); const result = executeSync({ schema, document }); - expect(result).to.deep.equal({ - data: {}, - }); + expect(result).to.deep.equal({ data: {} }); }); it('does not include arguments that were not set', () => { @@ -1069,9 +1011,7 @@ describe('Execute: Handles basic execution tasks', () => { const result = executeSync({ schema, document }); expect(result).to.deep.equal({ - data: { - field: '{ a: true, c: false, e: 0 }', - }, + data: { field: '{ a: true, c: false, e: 0 }' }, }); }); @@ -1104,22 +1044,16 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', - fields: { - specials: { type: new GraphQLList(SpecialType) }, - }, + fields: { specials: { type: new GraphQLList(SpecialType) } }, }), }); const document = parse('{ specials { value } }'); - const rootValue = { - specials: [new Special('foo'), new NotSpecial('bar')], - }; + const rootValue = { specials: [new Special('foo'), new NotSpecial('bar')] }; const result = executeSync({ schema, document, rootValue }); expectJSON(result).toDeepEqual({ - data: { - specials: [{ value: 'foo' }, null], - }, + data: { specials: [{ value: 'foo' }, null] }, errors: [ { message: @@ -1150,10 +1084,7 @@ describe('Execute: Handles basic execution tasks', () => { query: new GraphQLObjectType({ name: 'Query', fields: { - customScalar: { - type: customScalar, - resolve: () => 'CUSTOM_VALUE', - }, + customScalar: { type: customScalar, resolve: () => 'CUSTOM_VALUE' }, }, }), }); @@ -1164,7 +1095,7 @@ describe('Execute: Handles basic execution tasks', () => { errors: [ { message: - 'Expected `CustomScalar.serialize("CUSTOM_VALUE")` to return non-nullable value, returned: undefined', + 'Expected `CustomScalar.coerceOutputValue("CUSTOM_VALUE")` to return non-nullable value, returned: undefined', locations: [{ line: 1, column: 3 }], path: ['customScalar'], }, @@ -1176,9 +1107,7 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', - fields: { - foo: { type: GraphQLString }, - }, + fields: { foo: { type: GraphQLString } }, }), }); @@ -1196,9 +1125,7 @@ describe('Execute: Handles basic execution tasks', () => { const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', - fields: { - foo: { type: GraphQLString }, - }, + fields: { foo: { type: GraphQLString } }, }), }); const document = parse('{ foo }'); @@ -1220,25 +1147,19 @@ describe('Execute: Handles basic execution tasks', () => { const fooInterface = new GraphQLInterfaceType({ name: 'FooInterface', - fields: { - bar: { type: GraphQLString }, - }, + fields: { bar: { type: GraphQLString } }, }); const fooObject = new GraphQLObjectType({ name: 'FooObject', interfaces: [fooInterface], - fields: { - bar: { type: GraphQLString }, - }, + fields: { bar: { type: GraphQLString } }, }); const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', - fields: { - foo: { type: fooInterface }, - }, + fields: { foo: { type: fooInterface } }, }), types: [fooObject], }); diff --git a/src/stitch/__tests__/graphql-js/nonnull-test.ts b/src/stitch/__tests__/graphql-js/nonnull-test.ts index b82798a..b6f800d 100644 --- a/src/stitch/__tests__/graphql-js/nonnull-test.ts +++ b/src/stitch/__tests__/graphql-js/nonnull-test.ts @@ -159,9 +159,7 @@ describe('Execute: handles non-nullable types', () => { it('that returns null', async () => { const result = await executeSyncAndAsync(query, nullingData); - expect(result).to.deep.equal({ - data: { sync: null }, - }); + expect(result).to.deep.equal({ data: { sync: null } }); }); it('that throws', async () => { @@ -544,11 +542,7 @@ describe('Execute: handles non-nullable types', () => { fields: { withNonNullArg: { type: GraphQLString, - args: { - cannotBeNull: { - type: new GraphQLNonNull(GraphQLString), - }, - }, + args: { cannotBeNull: { type: new GraphQLNonNull(GraphQLString) } }, resolve: (_, args) => 'Passed: ' + String(args.cannotBeNull), }, }, @@ -566,9 +560,7 @@ describe('Execute: handles non-nullable types', () => { }); expect(result).to.deep.equal({ - data: { - withNonNullArg: 'Passed: literal value', - }, + data: { withNonNullArg: 'Passed: literal value' }, }); }); @@ -580,15 +572,11 @@ describe('Execute: handles non-nullable types', () => { withNonNullArg (cannotBeNull: $testVar) } `), - variableValues: { - testVar: 'variable value', - }, + variableValues: { testVar: 'variable value' }, }); expect(result).to.deep.equal({ - data: { - withNonNullArg: 'Passed: variable value', - }, + data: { withNonNullArg: 'Passed: variable value' }, }); }); @@ -606,9 +594,7 @@ describe('Execute: handles non-nullable types', () => { }); expect(result).to.deep.equal({ - data: { - withNonNullArg: 'Passed: default value', - }, + data: { withNonNullArg: 'Passed: default value' }, }); }); @@ -625,13 +611,11 @@ describe('Execute: handles non-nullable types', () => { }); expectJSON(result).toDeepEqual({ - data: { - withNonNullArg: null, - }, + data: { withNonNullArg: null }, errors: [ { message: - 'Argument "cannotBeNull" of required type "String!" was not provided.', + 'Argument "Query.withNonNullArg(cannotBeNull:)" of required type "String!" was not provided.', locations: [{ line: 3, column: 13 }], path: ['withNonNullArg'], }, @@ -652,13 +636,11 @@ describe('Execute: handles non-nullable types', () => { }); expectJSON(result).toDeepEqual({ - data: { - withNonNullArg: null, - }, + data: { withNonNullArg: null }, errors: [ { message: - 'Argument "cannotBeNull" of non-null type "String!" must not be null.', + 'Argument "Query.withNonNullArg(cannotBeNull:)" has invalid value: Expected value of non-null type "String!" not to be null.', locations: [{ line: 3, column: 42 }], path: ['withNonNullArg'], }, @@ -682,13 +664,11 @@ describe('Execute: handles non-nullable types', () => { }); expectJSON(result).toDeepEqual({ - data: { - withNonNullArg: null, - }, + data: { withNonNullArg: null }, errors: [ { message: - 'Argument "cannotBeNull" of required type "String!" was provided the variable "$testVar" which was not provided a runtime value.', + 'Argument "Query.withNonNullArg(cannotBeNull:)" has invalid value: Expected variable "$testVar" provided to type "String!" to provide a runtime value.', locations: [{ line: 3, column: 42 }], path: ['withNonNullArg'], }, @@ -704,19 +684,15 @@ describe('Execute: handles non-nullable types', () => { withNonNullArg (cannotBeNull: $testVar) } `), - variableValues: { - testVar: null, - }, + variableValues: { testVar: null }, }); expectJSON(result).toDeepEqual({ - data: { - withNonNullArg: null, - }, + data: { withNonNullArg: null }, errors: [ { message: - 'Argument "cannotBeNull" of non-null type "String!" must not be null.', + 'Argument "Query.withNonNullArg(cannotBeNull:)" has invalid value: Expected variable "$testVar" provided to non-null type "String!" not to be null.', locations: [{ line: 3, column: 43 }], path: ['withNonNullArg'], }, diff --git a/src/stitch/__tests__/graphql-js/subscribe-test.ts b/src/stitch/__tests__/graphql-js/subscribe-test.ts index 600d008..4b4b6d1 100644 --- a/src/stitch/__tests__/graphql-js/subscribe-test.ts +++ b/src/stitch/__tests__/graphql-js/subscribe-test.ts @@ -48,10 +48,7 @@ const EmailType = new GraphQLObjectType({ const InboxType = new GraphQLObjectType({ name: 'Inbox', fields: { - total: { - type: GraphQLInt, - resolve: (inbox) => inbox.emails.length, - }, + total: { type: GraphQLInt, resolve: (inbox) => inbox.emails.length }, unread: { type: GraphQLInt, resolve: (inbox) => @@ -63,17 +60,12 @@ const InboxType = new GraphQLObjectType({ const QueryType = new GraphQLObjectType({ name: 'Query', - fields: { - inbox: { type: InboxType }, - }, + fields: { inbox: { type: InboxType } }, }); const EmailEventType = new GraphQLObjectType({ name: 'EmailEvent', - fields: { - email: { type: EmailType }, - inbox: { type: InboxType }, - }, + fields: { email: { type: EmailType }, inbox: { type: InboxType } }, }); const emailSchema = new GraphQLSchema({ @@ -83,9 +75,7 @@ const emailSchema = new GraphQLSchema({ fields: { importantEmail: { type: EmailEventType, - args: { - priority: { type: GraphQLInt }, - }, + args: { priority: { type: GraphQLInt } }, }, }, }), @@ -131,12 +121,7 @@ function createSubscription( importantEmail: pubsub.getSubscriber((newEmail) => { emails.push(newEmail); - return { - importantEmail: { - email: newEmail, - inbox: data.inbox, - }, - }; + return { importantEmail: { email: newEmail, inbox: data.inbox } }; }), }; @@ -150,9 +135,7 @@ function createSubscription( const DummyQueryType = new GraphQLObjectType({ name: 'Query', - fields: { - dummy: { type: GraphQLString }, - }, + fields: { dummy: { type: GraphQLString } }, }); function subscribeWithBadFn( @@ -162,9 +145,7 @@ function subscribeWithBadFn( query: DummyQueryType, subscription: new GraphQLObjectType({ name: 'Subscription', - fields: { - foo: { type: GraphQLString, subscribe: subscribeFn }, - }, + fields: { foo: { type: GraphQLString, subscribe: subscribeFn } }, }), }); const document = parse('subscription { foo }'); @@ -180,10 +161,7 @@ describe('Subscription Initialization Phase', () => { query: DummyQueryType, subscription: new GraphQLObjectType({ name: 'Subscription', - fields: { - foo: { type: GraphQLString }, - bar: { type: GraphQLString }, - }, + fields: { foo: { type: GraphQLString }, bar: { type: GraphQLString } }, }), }); @@ -218,12 +196,7 @@ describe('Subscription Initialization Phase', () => { query: DummyQueryType, subscription: new GraphQLObjectType({ name: 'Subscription', - fields: { - foo: { - type: GraphQLString, - subscribe: fooGenerator, - }, - }, + fields: { foo: { type: GraphQLString, subscribe: fooGenerator } }, }), }); @@ -290,9 +263,7 @@ describe('Subscription Initialization Phase', () => { query: DummyQueryType, subscription: new GraphQLObjectType({ name: 'Subscription', - fields: { - foo: { type: GraphQLString }, - }, + fields: { foo: { type: GraphQLString } }, }), }); @@ -388,9 +359,7 @@ describe('Subscription Initialization Phase', () => { query: DummyQueryType, subscription: new GraphQLObjectType({ name: 'Subscription', - fields: { - foo: { type: GraphQLString }, - }, + fields: { foo: { type: GraphQLString } }, }), }); const document = parse('subscription { unknownField }'); @@ -421,9 +390,7 @@ describe('Subscription Initialization Phase', () => { query: DummyQueryType, subscription: new GraphQLObjectType({ name: 'Subscription', - fields: { - foo: { type: GraphQLString }, - }, + fields: { foo: { type: GraphQLString } }, }), }); @@ -496,10 +463,7 @@ describe('Subscription Initialization Phase', () => { subscription: new GraphQLObjectType({ name: 'Subscription', fields: { - foo: { - type: GraphQLString, - args: { arg: { type: GraphQLInt } }, - }, + foo: { type: GraphQLString, args: { arg: { type: GraphQLInt } } }, }, }), }); @@ -518,7 +482,7 @@ describe('Subscription Initialization Phase', () => { errors: [ { message: - 'Variable "$arg" got invalid value "meow"; Int cannot represent non-integer value: "meow"', + 'Variable "$arg" has invalid value: Int cannot represent non-integer value: "meow"', locations: [{ line: 2, column: 21 }], }, ], @@ -554,14 +518,8 @@ describe('Subscription Publish Phase', () => { value: { data: { importantEmail: { - email: { - from: 'yuzhi@graphql.org', - subject: 'Alright', - }, - inbox: { - unread: 1, - total: 2, - }, + email: { from: 'yuzhi@graphql.org', subject: 'Alright' }, + inbox: { unread: 1, total: 2 }, }, }, }, @@ -596,10 +554,7 @@ describe('Subscription Publish Phase', () => { subject: 'Alright', asyncSubject: 'Alright', }, - inbox: { - unread: 1, - total: 2, - }, + inbox: { unread: 1, total: 2 }, }, }, }, @@ -634,14 +589,8 @@ describe('Subscription Publish Phase', () => { value: { data: { importantEmail: { - email: { - from: 'yuzhi@graphql.org', - subject: 'Alright', - }, - inbox: { - unread: 1, - total: 2, - }, + email: { from: 'yuzhi@graphql.org', subject: 'Alright' }, + inbox: { unread: 1, total: 2 }, }, }, }, @@ -663,14 +612,8 @@ describe('Subscription Publish Phase', () => { value: { data: { importantEmail: { - email: { - from: 'hyo@graphql.org', - subject: 'Tools', - }, - inbox: { - unread: 2, - total: 3, - }, + email: { from: 'hyo@graphql.org', subject: 'Tools' }, + inbox: { unread: 2, total: 3 }, }, }, }, @@ -721,14 +664,8 @@ describe('Subscription Publish Phase', () => { value: { data: { importantEmail: { - email: { - from: 'yuzhi@graphql.org', - subject: 'Alright', - }, - inbox: { - unread: 1, - total: 2, - }, + email: { from: 'yuzhi@graphql.org', subject: 'Alright' }, + inbox: { unread: 1, total: 2 }, }, }, }, @@ -751,14 +688,8 @@ describe('Subscription Publish Phase', () => { value: { data: { importantEmail: { - email: { - from: 'yuzhi@graphql.org', - subject: 'Alright 2', - }, - inbox: { - unread: 2, - total: 3, - }, + email: { from: 'yuzhi@graphql.org', subject: 'Alright 2' }, + inbox: { unread: 2, total: 3 }, }, }, }, @@ -787,14 +718,8 @@ describe('Subscription Publish Phase', () => { value: { data: { importantEmail: { - email: { - from: 'yuzhi@graphql.org', - subject: 'Alright', - }, - inbox: { - unread: 1, - total: 2, - }, + email: { from: 'yuzhi@graphql.org', subject: 'Alright' }, + inbox: { unread: 1, total: 2 }, }, }, }, @@ -813,10 +738,7 @@ describe('Subscription Publish Phase', () => { }), ).to.equal(false); - expect(await payload).to.deep.equal({ - done: true, - value: undefined, - }); + expect(await payload).to.deep.equal({ done: true, value: undefined }); }); it('should not trigger when subscription is thrown', async () => { @@ -841,14 +763,8 @@ describe('Subscription Publish Phase', () => { value: { data: { importantEmail: { - email: { - from: 'yuzhi@graphql.org', - subject: 'Alright', - }, - inbox: { - unread: 1, - total: 2, - }, + email: { from: 'yuzhi@graphql.org', subject: 'Alright' }, + inbox: { unread: 1, total: 2 }, }, }, }, @@ -920,14 +836,8 @@ describe('Subscription Publish Phase', () => { value: { data: { importantEmail: { - email: { - from: 'yuzhi@graphql.org', - subject: 'Message', - }, - inbox: { - unread: 2, - total: 3, - }, + email: { from: 'yuzhi@graphql.org', subject: 'Message' }, + inbox: { unread: 2, total: 3 }, }, }, }, @@ -940,14 +850,8 @@ describe('Subscription Publish Phase', () => { value: { data: { importantEmail: { - email: { - from: 'yuzhi@graphql.org', - subject: 'Message 2', - }, - inbox: { - unread: 2, - total: 3, - }, + email: { from: 'yuzhi@graphql.org', subject: 'Message 2' }, + inbox: { unread: 2, total: 3 }, }, }, }, @@ -986,9 +890,7 @@ describe('Subscription Publish Phase', () => { expect(await subscription.next()).to.deep.equal({ done: false, - value: { - data: { newMessage: 'Hello' }, - }, + value: { data: { newMessage: 'Hello' } }, }); // An error in execution is presented as such. @@ -1010,9 +912,7 @@ describe('Subscription Publish Phase', () => { // Subsequent events are still executed. expectJSON(await subscription.next()).toDeepEqual({ done: false, - value: { - data: { newMessage: 'Bonjour' }, - }, + value: { data: { newMessage: 'Bonjour' } }, }); expectJSON(await subscription.next()).toDeepEqual({ @@ -1047,9 +947,7 @@ describe('Subscription Publish Phase', () => { expect(await subscription.next()).to.deep.equal({ done: false, - value: { - data: { newMessage: 'Hello' }, - }, + value: { data: { newMessage: 'Hello' } }, }); await expectPromise(subscription.next()).toRejectWith('test error'); diff --git a/src/stitch/__tests__/graphql-js/variables-test.ts b/src/stitch/__tests__/graphql-js/variables-test.ts index 68293b3..6dc9ede 100644 --- a/src/stitch/__tests__/graphql-js/variables-test.ts +++ b/src/stitch/__tests__/graphql-js/variables-test.ts @@ -137,9 +137,7 @@ describe('Execute: Handles inputs', () => { `); expect(result).to.deep.equal({ - data: { - fieldWithObjectInput: '{ a: "foo", b: ["bar"], c: "baz" }', - }, + data: { fieldWithObjectInput: '{ a: "foo", b: ["bar"], c: "baz" }' }, }); }); @@ -151,9 +149,7 @@ describe('Execute: Handles inputs', () => { `); expect(result).to.deep.equal({ - data: { - fieldWithObjectInput: '{ a: "foo", b: ["bar"], c: "baz" }', - }, + data: { fieldWithObjectInput: '{ a: "foo", b: ["bar"], c: "baz" }' }, }); }); @@ -179,9 +175,7 @@ describe('Execute: Handles inputs', () => { `); expect(result).to.deep.equal({ - data: { - fieldWithObjectInput: '{ b: ["A", null, "C"], c: "C" }', - }, + data: { fieldWithObjectInput: '{ b: ["A", null, "C"], c: "C" }' }, }); }); @@ -193,13 +187,11 @@ describe('Execute: Handles inputs', () => { `); expectJSON(result).toDeepEqual({ - data: { - fieldWithObjectInput: null, - }, + data: { fieldWithObjectInput: null }, errors: [ { message: - 'Argument "input" has invalid value ["foo", "bar", "baz"].', + 'Argument "TestType.fieldWithObjectInput(input:)" has invalid value: Expected value of type "TestInputObject" to be an object, found: ["foo", "bar", "baz"].', path: ['fieldWithObjectInput'], locations: [{ line: 3, column: 41 }], }, @@ -234,9 +226,7 @@ describe('Execute: Handles inputs', () => { const result = executeQuery(doc, params); expect(result).to.deep.equal({ - data: { - fieldWithObjectInput: '{ a: "foo", b: ["bar"], c: "baz" }', - }, + data: { fieldWithObjectInput: '{ a: "foo", b: ["bar"], c: "baz" }' }, }); }); @@ -252,9 +242,7 @@ describe('Execute: Handles inputs', () => { ); expect(result).to.deep.equal({ - data: { - fieldWithNullableStringInput: null, - }, + data: { fieldWithNullableStringInput: null }, }); }); @@ -268,9 +256,7 @@ describe('Execute: Handles inputs', () => { ); expect(result).to.deep.equal({ - data: { - fieldWithNullableStringInput: 'null', - }, + data: { fieldWithNullableStringInput: 'null' }, }); }); @@ -282,9 +268,7 @@ describe('Execute: Handles inputs', () => { `); expect(result).to.deep.equal({ - data: { - fieldWithObjectInput: '{ a: "foo", b: ["bar"], c: "baz" }', - }, + data: { fieldWithObjectInput: '{ a: "foo", b: ["bar"], c: "baz" }' }, }); }); @@ -299,9 +283,7 @@ describe('Execute: Handles inputs', () => { ); expect(result).to.deep.equal({ - data: { - fieldWithNullableStringInput: '"Variable value"', - }, + data: { fieldWithNullableStringInput: '"Variable value"' }, }); }); @@ -315,9 +297,7 @@ describe('Execute: Handles inputs', () => { ); expect(result).to.deep.equal({ - data: { - fieldWithNullableStringInput: 'null', - }, + data: { fieldWithNullableStringInput: 'null' }, }); }); @@ -333,9 +313,7 @@ describe('Execute: Handles inputs', () => { ); expect(result).to.deep.equal({ - data: { - fieldWithNullableStringInput: 'null', - }, + data: { fieldWithNullableStringInput: 'null' }, }); }); @@ -344,9 +322,7 @@ describe('Execute: Handles inputs', () => { const result = executeQuery(doc, params); expect(result).to.deep.equal({ - data: { - fieldWithObjectInput: '{ a: "foo", b: ["bar"], c: "baz" }', - }, + data: { fieldWithObjectInput: '{ a: "foo", b: ["bar"], c: "baz" }' }, }); }); @@ -369,7 +345,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$input" got invalid value null at "input.c"; Expected non-nullable type "String!" not to be null.', + 'Variable "$input" has invalid value.c: Expected value of non-null type "String!" not to be null.', locations: [{ line: 2, column: 16 }], }, ], @@ -383,7 +359,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$input" got invalid value "foo bar"; Expected type "TestInputObject" to be an object.', + 'Variable "$input" has invalid value: Expected value of type "TestInputObject" to be an object, found: "foo bar".', locations: [{ line: 2, column: 16 }], }, ], @@ -397,7 +373,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$input" got invalid value { a: "foo", b: "bar" }; Field "c" of required type "String!" was not provided.', + 'Variable "$input" has invalid value: Expected value of type "TestInputObject" to include required field "c", found: { a: "foo", b: "bar" }.', locations: [{ line: 2, column: 16 }], }, ], @@ -416,12 +392,12 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$input" got invalid value { a: "foo" } at "input.na"; Field "c" of required type "String!" was not provided.', + 'Variable "$input" has invalid value.na: Expected value of type "TestInputObject" to include required field "c", found: { a: "foo" }.', locations: [{ line: 2, column: 18 }], }, { message: - 'Variable "$input" got invalid value { na: { a: "foo" } }; Field "nb" of required type "String!" was not provided.', + 'Variable "$input" has invalid value: Expected value of type "TestNestedInputObject" to include required field "nb", found: { na: { a: "foo" } }.', locations: [{ line: 2, column: 18 }], }, ], @@ -438,7 +414,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$input" got invalid value { a: "foo", b: "bar", c: "baz", extra: "dog" }; Field "extra" is not defined by type "TestInputObject".', + 'Variable \"$input\" has invalid value: Expected value of type \"TestInputObject\" not to include unknown field \"extra\", found: { a: \"foo\", b: \"bar\", c: \"baz\", extra: \"dog\" }.', locations: [{ line: 2, column: 16 }], }, ], @@ -478,9 +454,7 @@ describe('Execute: Handles inputs', () => { `); expect(result).to.deep.equal({ - data: { - fieldWithNonNullableEnumInput: 'null', - }, + data: { fieldWithNonNullableEnumInput: 'null' }, }); }); }); @@ -494,9 +468,7 @@ describe('Execute: Handles inputs', () => { `); expect(result).to.deep.equal({ - data: { - fieldWithNullableStringInput: null, - }, + data: { fieldWithNullableStringInput: null }, }); }); @@ -508,9 +480,7 @@ describe('Execute: Handles inputs', () => { `); expect(result).to.deep.equal({ - data: { - fieldWithNullableStringInput: null, - }, + data: { fieldWithNullableStringInput: null }, }); }); @@ -522,9 +492,7 @@ describe('Execute: Handles inputs', () => { `); expect(result).to.deep.equal({ - data: { - fieldWithNullableStringInput: null, - }, + data: { fieldWithNullableStringInput: null }, }); }); @@ -537,9 +505,7 @@ describe('Execute: Handles inputs', () => { const result = executeQuery(doc, { value: null }); expect(result).to.deep.equal({ - data: { - fieldWithNullableStringInput: 'null', - }, + data: { fieldWithNullableStringInput: 'null' }, }); }); @@ -552,9 +518,7 @@ describe('Execute: Handles inputs', () => { const result = executeQuery(doc, { value: 'a' }); expect(result).to.deep.equal({ - data: { - fieldWithNullableStringInput: '"a"', - }, + data: { fieldWithNullableStringInput: '"a"' }, }); }); @@ -566,9 +530,7 @@ describe('Execute: Handles inputs', () => { `); expect(result).to.deep.equal({ - data: { - fieldWithNullableStringInput: '"a"', - }, + data: { fieldWithNullableStringInput: '"a"' }, }); }); }); @@ -582,9 +544,7 @@ describe('Execute: Handles inputs', () => { `); expect(result).to.deep.equal({ - data: { - fieldWithNullableStringInput: '"default"', - }, + data: { fieldWithNullableStringInput: '"default"' }, }); }); @@ -596,9 +556,7 @@ describe('Execute: Handles inputs', () => { `); expect(result).to.deep.equal({ - data: { - fieldWithNonNullableStringInput: '"default"', - }, + data: { fieldWithNonNullableStringInput: '"default"' }, }); }); @@ -613,7 +571,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$value" of required type "String!" was not provided.', + 'Variable "$value" has invalid value: Expected a value of non-null type "String!" to be provided.', locations: [{ line: 2, column: 16 }], }, ], @@ -632,7 +590,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$value" of non-null type "String!" must not be null.', + 'Variable "$value" has invalid value: Expected value of non-null type "String!" not to be null.', locations: [{ line: 2, column: 16 }], }, ], @@ -648,9 +606,7 @@ describe('Execute: Handles inputs', () => { const result = executeQuery(doc, { value: 'a' }); expect(result).to.deep.equal({ - data: { - fieldWithNonNullableStringInput: '"a"', - }, + data: { fieldWithNonNullableStringInput: '"a"' }, }); }); @@ -662,9 +618,7 @@ describe('Execute: Handles inputs', () => { `); expect(result).to.deep.equal({ - data: { - fieldWithNonNullableStringInput: '"a"', - }, + data: { fieldWithNonNullableStringInput: '"a"' }, }); }); @@ -672,13 +626,11 @@ describe('Execute: Handles inputs', () => { const result = executeQuery('{ fieldWithNonNullableStringInput }'); expectJSON(result).toDeepEqual({ - data: { - fieldWithNonNullableStringInput: null, - }, + data: { fieldWithNonNullableStringInput: null }, errors: [ { message: - 'Argument "input" of required type "String!" was not provided.', + 'Argument "TestType.fieldWithNonNullableStringInput(input:)" of required type "String!" was not provided.', locations: [{ line: 1, column: 3 }], path: ['fieldWithNonNullableStringInput'], }, @@ -698,7 +650,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$value" got invalid value [1, 2, 3]; String cannot represent a non string value: [1, 2, 3]', + 'Variable "$value" has invalid value: String cannot represent a non string value: [1, 2, 3]', locations: [{ line: 2, column: 16 }], }, ], @@ -720,13 +672,11 @@ describe('Execute: Handles inputs', () => { `); expectJSON(result).toDeepEqual({ - data: { - fieldWithNonNullableStringInput: null, - }, + data: { fieldWithNonNullableStringInput: null }, errors: [ { message: - 'Argument "input" of required type "String!" was provided the variable "$foo" which was not provided a runtime value.', + 'Argument "TestType.fieldWithNonNullableStringInput(input:)" has invalid value: Expected variable "$foo" provided to type "String!" to provide a runtime value.', locations: [{ line: 3, column: 50 }], path: ['fieldWithNonNullableStringInput'], }, @@ -781,7 +731,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$input" of non-null type "[String]!" must not be null.', + 'Variable "$input" has invalid value: Expected value of non-null type "[String]!" not to be null.', locations: [{ line: 2, column: 16 }], }, ], @@ -844,7 +794,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$input" got invalid value null at "input[1]"; Expected non-nullable type "String!" not to be null.', + 'Variable "$input" has invalid value[1]: Expected value of non-null type "String!" not to be null.', locations: [{ line: 2, column: 16 }], }, ], @@ -863,7 +813,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$input" of non-null type "[String!]!" must not be null.', + 'Variable "$input" has invalid value: Expected value of non-null type "[String!]!" not to be null.', locations: [{ line: 2, column: 16 }], }, ], @@ -893,7 +843,7 @@ describe('Execute: Handles inputs', () => { errors: [ { message: - 'Variable "$input" got invalid value null at "input[1]"; Expected non-nullable type "String!" not to be null.', + 'Variable "$input" has invalid value[1]: Expected value of non-null type "String!" not to be null.', locations: [{ line: 2, column: 16 }], }, ], @@ -944,9 +894,7 @@ describe('Execute: Handles inputs', () => { const result = executeQuery('{ fieldWithDefaultArgumentValue }'); expect(result).to.deep.equal({ - data: { - fieldWithDefaultArgumentValue: '"Hello World"', - }, + data: { fieldWithDefaultArgumentValue: '"Hello World"' }, }); }); @@ -958,9 +906,7 @@ describe('Execute: Handles inputs', () => { `); expect(result).to.deep.equal({ - data: { - fieldWithDefaultArgumentValue: '"Hello World"', - }, + data: { fieldWithDefaultArgumentValue: '"Hello World"' }, }); }); @@ -972,12 +918,11 @@ describe('Execute: Handles inputs', () => { `); expectJSON(result).toDeepEqual({ - data: { - fieldWithDefaultArgumentValue: null, - }, + data: { fieldWithDefaultArgumentValue: null }, errors: [ { - message: 'Argument "input" has invalid value WRONG_TYPE.', + message: + 'Argument "TestType.fieldWithDefaultArgumentValue(input:)" has invalid value: String cannot represent a non string value: WRONG_TYPE', locations: [{ line: 3, column: 48 }], path: ['fieldWithDefaultArgumentValue'], }, @@ -1017,7 +962,7 @@ describe('Execute: Handles inputs', () => { function invalidValueError(value: number, index: number) { return { - message: `Variable "$input" got invalid value ${value} at "input[${index}]"; String cannot represent a non string value: ${value}`, + message: `Variable "$input" has invalid value at [${index}]: String cannot represent a non string value: ${value}`, locations: [{ line: 2, column: 14 }], }; } diff --git a/src/stitch/buildExecutionContext.ts b/src/stitch/buildExecutionContext.ts index f6c66e5..6e3f081 100644 --- a/src/stitch/buildExecutionContext.ts +++ b/src/stitch/buildExecutionContext.ts @@ -9,14 +9,14 @@ import { inlineFragments } from '../utilities/inlineFragments.js'; import type { Planner } from './Planner.js'; import { createPlanner } from './Planner.js'; -import type { Subschema } from './SuperSchema.js'; +import type { Subschema, VariableValues } from './SuperSchema.js'; import { SuperSchema } from './SuperSchema.js'; export interface ExecutionContext { operation: OperationDefinitionNode; planner: Planner; rawVariableValues: { readonly [variable: string]: unknown } | undefined; - coercedVariableValues: { [variable: string]: unknown }; + variableValues: VariableValues; } export interface ExecutionArgs { @@ -80,24 +80,22 @@ export function buildExecutionContext( /* c8 ignore next */ const variableDefinitions = operation.variableDefinitions ?? []; - const coercedVariableValues = superSchema.getVariableValues( + const variableValuesOrErrors = superSchema.getVariableValues( variableDefinitions, rawVariableValues ?? {}, { maxErrors: 50 }, ); - if (coercedVariableValues.errors) { - return coercedVariableValues.errors; + if (variableValuesOrErrors.errors) { + return variableValuesOrErrors.errors; } - const coerced = coercedVariableValues.coerced; - operation = inlineFragments(operation, fragments); return { operation, planner: createPlanner(superSchema, operation), rawVariableValues, - coercedVariableValues: coerced, + variableValues: variableValuesOrErrors.variableValues, }; } diff --git a/src/stitch/execute.ts b/src/stitch/execute.ts index 6d477ec..3cc7d80 100644 --- a/src/stitch/execute.ts +++ b/src/stitch/execute.ts @@ -23,10 +23,9 @@ export function execute(args: ExecutionArgs): PromiseOrValue { return { errors: exeContext }; } - const { operation, planner, rawVariableValues, coercedVariableValues } = - exeContext; + const { operation, planner, rawVariableValues, variableValues } = exeContext; - const plan = planner.createRootPlan(coercedVariableValues); + const plan = planner.createRootPlan(variableValues); if (plan instanceof GraphQLError) { return { data: null, errors: [plan] }; } diff --git a/src/stitch/subscribe.ts b/src/stitch/subscribe.ts index ab74a41..03cfdd1 100644 --- a/src/stitch/subscribe.ts +++ b/src/stitch/subscribe.ts @@ -26,11 +26,10 @@ export function subscribe( return { errors: exeContext }; } - const { operation, planner, rawVariableValues, coercedVariableValues } = - exeContext; + const { operation, planner, rawVariableValues, variableValues } = exeContext; invariant(operation.operation === OperationTypeNode.SUBSCRIPTION); - const plan = planner.createRootPlan(coercedVariableValues); + const plan = planner.createRootPlan(variableValues); if (plan instanceof GraphQLError) { return { errors: [plan] }; } @@ -68,22 +67,14 @@ export function subscribe( ], }; - const result = subscriber({ - document, - variables: rawVariableValues, - }); + const result = subscriber({ document, variables: rawVariableValues }); if (isPromise(result)) { return result.then((resolved) => { if (isAsyncIterable(resolved)) { return mapAsyncIterable(resolved, (payload) => compose( - [ - { - subschemaPlan, - initialResult: payload, - }, - ], + [{ subschemaPlan, initialResult: payload }], plan.superSchema, rawVariableValues, ), @@ -97,12 +88,7 @@ export function subscribe( if (isAsyncIterable(result)) { return mapAsyncIterable(result, (payload) => compose( - [ - { - subschemaPlan, - initialResult: payload, - }, - ], + [{ subschemaPlan, initialResult: payload }], plan.superSchema, rawVariableValues, ), diff --git a/src/utilities/__tests__/applySkipIncludeDirectives-test.ts b/src/utilities/__tests__/applySkipIncludeDirectives-test.ts index f9e5b9d..b7095f8 100644 --- a/src/utilities/__tests__/applySkipIncludeDirectives-test.ts +++ b/src/utilities/__tests__/applySkipIncludeDirectives-test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import type { OperationDefinitionNode } from 'graphql'; -import { getOperationAST, parse, print } from 'graphql'; +import { getOperationAST, GraphQLBoolean, parse, print } from 'graphql'; import { describe, it } from 'mocha'; import { dedent } from '../../__testUtils__/dedent.js'; @@ -22,12 +22,47 @@ describe('applySkipIncludeDirectives', () => { document, 'withSkipInclude', ) as OperationDefinitionNode; - const transformedAST = applySkipIncludeDirectives(operation, { - skipA: true, - skipB: false, - includeC: true, - includeD: false, - }); + const variableValues = { + coerced: { skipA: true, skipB: false, includeC: true, includeD: false }, + sources: { + skipA: { + signature: { + name: 'skipA', + type: GraphQLBoolean, + default: undefined, + }, + value: true, + }, + skipB: { + signature: { + name: 'skipB', + type: GraphQLBoolean, + default: undefined, + }, + value: false, + }, + includeC: { + signature: { + name: 'includeC', + type: GraphQLBoolean, + default: undefined, + }, + value: true, + }, + includeD: { + signature: { + name: 'includeD', + type: GraphQLBoolean, + default: undefined, + }, + value: false, + }, + }, + }; + const transformedAST = applySkipIncludeDirectives( + operation, + variableValues, + ); expect(print(transformedAST)).to.equal(dedent` query withSkipInclude($skipA: Boolean, $skipB: Boolean, $includeC: Boolean, $includeD: Boolean) { @@ -51,19 +86,52 @@ describe('applySkipIncludeDirectives', () => { document, 'withSkipInclude', ) as OperationDefinitionNode; - const aTransformedAST = applySkipIncludeDirectives(operation, { - skipA: true, - skipB: false, - includeC: true, - includeD: false, - }); + const variableValues = { + coerced: { skipA: true, skipB: false, includeC: true, includeD: false }, + sources: { + skipA: { + signature: { + name: 'skipA', + type: GraphQLBoolean, + default: undefined, + }, + value: true, + }, + skipB: { + signature: { + name: 'skipB', + type: GraphQLBoolean, + default: undefined, + }, + value: false, + }, + includeC: { + signature: { + name: 'includeC', + type: GraphQLBoolean, + default: undefined, + }, + value: true, + }, + includeD: { + signature: { + name: 'includeD', + type: GraphQLBoolean, + default: undefined, + }, + value: false, + }, + }, + }; - const anotherTransformedAST = applySkipIncludeDirectives(operation, { - skipA: true, - skipB: false, - includeC: true, - includeD: false, - }); + const aTransformedAST = applySkipIncludeDirectives( + operation, + variableValues, + ); + const anotherTransformedAST = applySkipIncludeDirectives( + operation, + variableValues, + ); expect(aTransformedAST).to.equal(anotherTransformedAST); }); diff --git a/src/utilities/applySkipIncludeDirectives.ts b/src/utilities/applySkipIncludeDirectives.ts index a728288..0da4939 100644 --- a/src/utilities/applySkipIncludeDirectives.ts +++ b/src/utilities/applySkipIncludeDirectives.ts @@ -18,6 +18,7 @@ import { appendToArray, emptyArray } from './appendToArray.js'; import { memoize2 } from './memoize2.js'; import { updateNode } from './updateNode.js'; import { visitWithMemo } from './visitWithMemo.js'; +import type { VariableValues } from '../stitch/SuperSchema.js'; /** * Function that applies the @skip and @include directives to a given OperationDefinitionNode or FragmentDefinitionNode. @@ -26,7 +27,7 @@ export const applySkipIncludeDirectives = memoize2(_applySkipIncludeDirectives); function _applySkipIncludeDirectives< T extends OperationDefinitionNode | FragmentDefinitionNode, ->(node: T, variableValues: { [key: string]: unknown }): T { +>(node: T, variableValues: VariableValues): T { return visitWithMemo( node, { @@ -49,7 +50,7 @@ function _applySkipIncludeDirectives< function applyDirectivesToSelection( node: FieldNode | FragmentSpreadNode | InlineFragmentNode, - variableValues: { [key: string]: unknown }, + variableValues: VariableValues, ): ASTNode | null { const directives: ReadonlyArray | undefined = node.directives; diff --git a/src/utilities/visitWithMemo.ts b/src/utilities/visitWithMemo.ts index db2c02a..62d1c84 100644 --- a/src/utilities/visitWithMemo.ts +++ b/src/utilities/visitWithMemo.ts @@ -70,22 +70,9 @@ const DocumentKeys: { VariableDefinition: ['variable', 'type', 'defaultValue', 'directives'], Variable: ['name'], SelectionSet: ['selections'], - Field: [ - 'alias', - 'name', - 'arguments', - 'directives', - 'selectionSet', - // Note: Client Controlled Nullability is experimental and may be changed - // or removed in the future. - 'nullabilityAssertion', - ], + Field: ['alias', 'name', 'arguments', 'directives', 'selectionSet'], Argument: ['name', 'value'], - // Note: Client Controlled Nullability is experimental and may be changed - // or removed in the future. - ListNullabilityOperator: ['nullabilityAssertion'], - NonNullAssertion: ['nullabilityAssertion'], - ErrorBoundary: ['nullabilityAssertion'], + FragmentArgument: ['name', 'value'], FragmentSpread: ['name', 'directives'], InlineFragment: ['typeCondition', 'directives', 'selectionSet'],