diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c804fc4b..95bdaad5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: matrix: node-version: [14, 16, 18] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - graphql-version: [15, 16] + graphql-version: [15, 16, 17.0.0-alpha.2] steps: - uses: actions/checkout@v2 diff --git a/package.json b/package.json index bd2440d1..a88c8fa7 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ }, "coverageThreshold": { "global": { - "branches": 92, + "branches": 91, "functions": 96, "lines": 96, "statements": 96 @@ -50,7 +50,7 @@ "graphql": ">=15" }, "devDependencies": { - "@graphql-tools/schema": "^8.3.1", + "@graphql-tools/schema": "^9.0.8", "@stryker-mutator/core": "^2.0.0", "@stryker-mutator/jest-runner": "^2.0.0", "@stryker-mutator/typescript": "^2.0.0", diff --git a/src/__tests__/json.test.ts b/src/__tests__/json.test.ts index 72c51a4a..b7b2e676 100644 --- a/src/__tests__/json.test.ts +++ b/src/__tests__/json.test.ts @@ -1,6 +1,5 @@ import fastJson from "fast-json-stringify"; import { - formatError, GraphQLBoolean, GraphQLError, GraphQLID, @@ -11,13 +10,13 @@ import { GraphQLString, parse, GraphQLInt, - GraphQLScalarType, versionInfo } from "graphql"; import { buildExecutionContext } from "graphql/execution/execute"; import { compileQuery } from "../index"; import { queryToJSONSchema } from "../json"; import { makeExecutableSchema } from "@graphql-tools/schema"; +import { formatError } from "../compat"; describe("json schema creator", () => { const BlogAuthor = new GraphQLObjectType({ diff --git a/src/__tests__/schema.test.ts b/src/__tests__/schema.test.ts index 8a4a1fb0..8a09d670 100644 --- a/src/__tests__/schema.test.ts +++ b/src/__tests__/schema.test.ts @@ -18,8 +18,7 @@ import { GraphQLString, IntrospectionQuery, parse, - printSchema, - versionInfo + printSchema } from "graphql"; import { compileQuery, isCompiledQuery } from "../index"; diff --git a/src/__tests__/subscription.test.ts b/src/__tests__/subscription.test.ts index 18c44642..765421f5 100644 --- a/src/__tests__/subscription.test.ts +++ b/src/__tests__/subscription.test.ts @@ -8,6 +8,7 @@ */ import { + ExecutionArgs, ExecutionResult, GraphQLBoolean, GraphQLInt, @@ -15,8 +16,7 @@ import { GraphQLObjectType, GraphQLSchema, GraphQLString, - parse, - SubscriptionArgs + parse } from "graphql"; import { compileQuery, isAsyncIterable, isCompiledQuery } from "../execution"; @@ -68,7 +68,7 @@ async function subscribe({ rootValue, contextValue, variableValues -}: SubscriptionArgs): Promise< +}: ExecutionArgs): Promise< AsyncIterableIterator | ExecutionResult > { const prepared = compileQuery(schema, document, operationName || ""); diff --git a/src/__tests__/variables.test.ts b/src/__tests__/variables.test.ts index eeced9b4..ce2c34d1 100644 --- a/src/__tests__/variables.test.ts +++ b/src/__tests__/variables.test.ts @@ -16,7 +16,8 @@ import { GraphQLScalarType, GraphQLSchema, GraphQLString, - parse + parse, + versionInfo } from "graphql"; import { GraphQLArgumentConfig } from "graphql/type/definition"; import { compileQuery, isCompiledQuery } from "../index"; @@ -283,7 +284,9 @@ describe("Execute: Handles inputs", () => { errors: [ { message: - 'Argument "input" of type "TestInputObject" has invalid value {b: ["A", null, "C"], c: false}.', + versionInfo.major < 17 + ? 'Argument "input" of type "TestInputObject" has invalid value {b: ["A", null, "C"], c: false}.' + : 'Argument "input" of type "TestInputObject" has invalid value { b: ["A", null, "C"], c: false }.', locations: [{ line: 3, column: 41 }] } ] diff --git a/src/ast.ts b/src/ast.ts index 2d39c07e..72e41df0 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -26,15 +26,13 @@ import { typeFromAST, valueFromASTUntyped, ValueNode, - VariableNode, - versionInfo + VariableNode } from "graphql"; -import { getFieldDef } from "graphql/execution/execute"; import { Kind, SelectionNode, TypeNode } from "graphql/language"; import { isAbstractType } from "graphql/type"; import { CompilationContext, GLOBAL_VARIABLES_NAME } from "./execution"; import createInspect from "./inspect"; -import { Maybe } from "./types"; +import { getGraphQLErrorOptions, resolveFieldDef } from "./compat"; export interface JitFieldNode extends FieldNode { __internalShouldInclude?: string; @@ -414,7 +412,7 @@ function compileSkipIncludeDirective( if (ifNode == null) { throw new GraphQLError( `Directive '${directive.name.value}' is missing required arguments: 'if'`, - [directive] + getGraphQLErrorOptions([directive]) ); } @@ -431,7 +429,7 @@ function compileSkipIncludeDirective( }' has an invalid value (${valueFromASTUntyped( ifNode.value )}). Expected type 'Boolean!'`, - [ifNode] + getGraphQLErrorOptions([ifNode]) ); } } @@ -454,9 +452,10 @@ function validateSkipIncludeVariableType( (it) => it.variable.name.value === variable.name.value ); if (variableDefinition == null) { - throw new GraphQLError(`Variable '${variable.name.value}' is not defined`, [ - variable - ]); + throw new GraphQLError( + `Variable '${variable.name.value}' is not defined`, + getGraphQLErrorOptions([variable]) + ); } if ( @@ -470,7 +469,7 @@ function validateSkipIncludeVariableType( `Variable '${variable.name.value}' of type '${typeNodeToString( variableDefinition.type )}' used in position expecting type 'Boolean!'`, - [variableDefinition] + getGraphQLErrorOptions([variableDefinition]) ); } } @@ -526,26 +525,7 @@ function getFieldEntryKey(node: FieldNode): string { return node.alias ? node.alias.value : node.name.value; } -/** - * Resolves the field on the given source object. In particular, this - * figures out the value that the field returns by calling its resolve function, - * then calls completeValue to complete promises, serialize scalars, or execute - * the sub-selection-set for objects. - */ -export function resolveFieldDef( - compilationContext: CompilationContext, - parentType: GraphQLObjectType, - fieldNodes: FieldNode[] -): Maybe> { - const fieldNode = fieldNodes[0]; - - if (versionInfo.major < 16) { - const fieldName = fieldNode.name.value; - return getFieldDef(compilationContext.schema, parentType, fieldName as any); - } - - return getFieldDef(compilationContext.schema, parentType, fieldNode as any); -} +export { resolveFieldDef }; /** * A memoized collection of relevant subfields in the context of the return @@ -686,7 +666,7 @@ export function getArgumentDefs( `Argument "${name}" of type "${argType}" has invalid value ${print( argumentNode.value )}.`, - argumentNode.value + getGraphQLErrorOptions(argumentNode.value) ); } @@ -709,7 +689,7 @@ export function getArgumentDefs( `"${argType}" must not be null.` : `Argument "${name}" of required type ` + `"${argType}" was not provided.`, - node + getGraphQLErrorOptions(node) ); } } diff --git a/src/compat.ts b/src/compat.ts index 4508ed9b..3aa1e013 100644 --- a/src/compat.ts +++ b/src/compat.ts @@ -1,8 +1,25 @@ -import * as GraphQL from "graphql"; +import { + GraphQLSchema, + GraphQLError, + versionInfo, + FieldNode, + GraphQLField +} from "graphql"; +import { GraphQLObjectType } from "graphql/type/definition"; +import { Maybe } from "graphql/jsutils/Maybe"; + +import { ASTNode, OperationDefinitionNode } from "graphql/language/ast"; +import * as errorUtilities from "graphql/error"; +import * as utilities from "graphql/utilities"; +import { GraphQLFormattedError } from "graphql/error"; +import { CompilationContext } from "./execution"; +import * as execute from "graphql/execution/execute"; + +/** + * A helper file to support backward compatibility for different versions of graphql-js. + */ /** - * A helper to support backward compatibility for different versions of graphql-js. - * * v15 does not have schema.getRootType * v16 has both * v17 will not have getOperationRootType @@ -15,13 +32,84 @@ import * as GraphQL from "graphql"; * GraphQL v17 would remove getOperationRootType. */ export function getOperationRootType( - schema: GraphQL.GraphQLSchema, - operation: GraphQL.OperationDefinitionNode -) { - if (GraphQL.getOperationRootType) { - return GraphQL.getOperationRootType(schema, operation); - } else { - // the use of any is to support graphql v15 types which will not use this codepath - return (schema as any).getRootType(operation.operation)!; + schema: GraphQLSchema, + operation: OperationDefinitionNode +): GraphQLObjectType { + if (versionInfo.major < 16) { + return (utilities as any).getOperationRootType(schema, operation); + } + + const type = (schema as any).getRootType(operation.operation); + + if (!type) { + throw new Error(`No root type for operation ${operation.operation}`); } + + return type; +} + +/** + * v16 and lower versions don't have .toJSON method on GraphQLError + * v17 does have .toJSON and doesn't have "formatError" export anymore + */ +export function formatError(error: GraphQLError): GraphQLFormattedError { + if (versionInfo.major < 16) { + return (errorUtilities as any).formatError(error); + } + + return (error as any).toJSON(); +} + +/** + * v17 dropped support for positional arguments in GraphQLError constructor + * https://github.com/graphql/graphql-js/pull/3577 + */ +export function getGraphQLErrorOptions( + nodes: Maybe | ASTNode> +): ConstructorParameters[1] { + if (versionInfo.major < 16) { + return nodes as any; + } + + return { nodes } as any; +} + +/** + * Resolves the field on the given source object. In particular, this + * figures out the value that the field returns by calling its resolve function, + * then calls completeValue to complete promises, serialize scalars, or execute + * the sub-selection-set for objects. + * + * v15 has getFieldDef that accepts field name + * v16 has getFieldDef that accepts field node + * v17 drops getFieldDef support and adds getField method + */ +export function resolveFieldDef( + compilationContext: CompilationContext, + parentType: GraphQLObjectType, + fieldNodes: FieldNode[] +): Maybe> { + const fieldNode = fieldNodes[0]; + + if (versionInfo.major < 16) { + const fieldName = fieldNode.name.value; + return (execute as any).getFieldDef( + compilationContext.schema, + parentType, + fieldName as any + ); + } + + if (versionInfo.major < 17) { + return (execute as any).getFieldDef( + compilationContext.schema, + parentType, + fieldNode as any + ); + } + + return (compilationContext.schema as any).getField( + parentType, + fieldNode.name.value + ); } diff --git a/src/execution.ts b/src/execution.ts index dac6d382..158115d5 100644 --- a/src/execution.ts +++ b/src/execution.ts @@ -47,7 +47,6 @@ import { ObjectPath, resolveFieldDef } from "./ast"; -import { getOperationRootType } from "./compat"; import { GraphQLError as GraphqlJitError } from "./error"; import createInspect from "./inspect"; import { queryToJSONSchema } from "./json"; @@ -62,6 +61,7 @@ import { compileVariableParsing, failToParseVariables } from "./variables"; +import { getGraphQLErrorOptions, getOperationRootType } from "./compat"; const inspect = createInspect(); @@ -1706,7 +1706,7 @@ function compileSubscriptionOperation( if (!field) { throw new GraphQLError( `The subscription field "${fieldName}" is not defined.`, - fieldNodes + getGraphQLErrorOptions(fieldNodes) ); } diff --git a/yarn.lock b/yarn.lock index 47b66016..47732a17 100644 --- a/yarn.lock +++ b/yarn.lock @@ -446,30 +446,30 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@graphql-tools/merge@^8.2.1": - version "8.2.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.2.1.tgz#bf83aa06a0cfc6a839e52a58057a84498d0d51ff" - integrity sha512-Q240kcUszhXiAYudjuJgNuLgy9CryDP3wp83NOZQezfA6h3ByYKU7xI6DiKrdjyVaGpYN3ppUmdj0uf5GaXzMA== - dependencies: - "@graphql-tools/utils" "^8.5.1" - tslib "~2.3.0" - -"@graphql-tools/schema@^8.3.1": - version "8.3.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.3.1.tgz#1ee9da494d2da457643b3c93502b94c3c4b68c74" - integrity sha512-3R0AJFe715p4GwF067G5i0KCr/XIdvSfDLvTLEiTDQ8V/hwbOHEKHKWlEBHGRQwkG5lwFQlW1aOn7VnlPERnWQ== - dependencies: - "@graphql-tools/merge" "^8.2.1" - "@graphql-tools/utils" "^8.5.1" - tslib "~2.3.0" +"@graphql-tools/merge@8.3.10": + version "8.3.10" + resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.3.10.tgz#81f374bc1e8c81d45cb1003d8ed05f181b7e6bd5" + integrity sha512-/hSg69JwqEA+t01wQmMGKPuaJ9VJBSz6uAXhbNNrTBJu8bmXljw305NVXM49pCwDKFVUGtbTqYrBeLcfT3RoYw== + dependencies: + "@graphql-tools/utils" "9.0.1" + tslib "^2.4.0" + +"@graphql-tools/schema@^9.0.8": + version "9.0.8" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-9.0.8.tgz#df3119c8543e6dacf425998f83aa714e2ee86eb0" + integrity sha512-PnES7sNkhQ/FdPQhP7cup0OIzwzQh+nfjklilU7YJzE209ACIyEQtxoNCfvPW5eV6hc9bWsBQeI3Jm4mMtwxNA== + dependencies: + "@graphql-tools/merge" "8.3.10" + "@graphql-tools/utils" "9.0.1" + tslib "^2.4.0" value-or-promise "1.0.11" -"@graphql-tools/utils@^8.5.1": - version "8.6.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.6.1.tgz#52c7eb108f2ca2fd01bdba8eef85077ead1bf882" - integrity sha512-uxcfHCocp4ENoIiovPxUWZEHOnbXqj3ekWc0rm7fUhW93a1xheARNHcNKhwMTR+UKXVJbTFQdGI1Rl5XdyvDBg== +"@graphql-tools/utils@9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-9.0.1.tgz#04933b34c3435ef9add4f8bdfdf452040376f9d0" + integrity sha512-z6FimVa5E44bHKmqK0/uMp9hHvHo2Tkt9A5rlLb40ReD/8IFKehSXLzM4b2N1vcP7mSsbXIdDK9Aoc8jT/he1Q== dependencies: - tslib "~2.3.0" + tslib "^2.4.0" "@graphql-typed-document-node/core@^3.1.1": version "3.1.1" @@ -2654,9 +2654,9 @@ graceful-fs@^4.2.4: integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== graphql@^16.0.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.2.0.tgz#de3150e80f1fc009590b92a9d16ab1b46e12b656" - integrity sha512-MuQd7XXrdOcmfwuLwC2jNvx0n3rxIuNYOxUtiee5XOmfrWo613ar2U8pE7aHAKh8VwfpifubpD9IP+EdEAEOsA== + version "16.6.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" + integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== has-ansi@^2.0.0: version "2.0.0" @@ -5374,10 +5374,10 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@~1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== -tslib@~2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== +tslib@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== tsutils@^3.21.0: version "3.21.0"