diff --git a/src/execution/ResolveInfo.ts b/src/execution/ResolveInfo.ts new file mode 100644 index 0000000000..7d11f63e47 --- /dev/null +++ b/src/execution/ResolveInfo.ts @@ -0,0 +1,107 @@ +import type { ObjMap } from '../jsutils/ObjMap'; +import type { Path } from '../jsutils/Path'; + +import type { + FieldNode, + FragmentDefinitionNode, + OperationDefinitionNode, +} from '../language/ast'; + +import type { + GraphQLField, + GraphQLObjectType, + GraphQLOutputType, + GraphQLResolveInfo, +} from '../type/definition'; +import type { GraphQLSchema } from '../type/schema'; + +interface ExecutionDetails { + schema: GraphQLSchema; + fragments: ObjMap; + rootValue: unknown; + operation: OperationDefinitionNode; + variableValues: { [key: string]: unknown }; +} + +/** @internal */ +/** @internal */ +export class ResolveInfo implements GraphQLResolveInfo { + private _executionDetails: ExecutionDetails; + private _fieldDef: GraphQLField; + private _fieldNodes: ReadonlyArray; + private _parentType: GraphQLObjectType; + private _path: Path; + + private _fieldName: string | undefined; + private _returnType: GraphQLOutputType | undefined; + private _schema: GraphQLSchema | undefined; + private _fragments: ObjMap | undefined; + private _rootValue: unknown; + private _rootValueDefined?: boolean; + private _operation: OperationDefinitionNode | undefined; + private _variableValues: { [key: string]: unknown } | undefined; + + constructor( + executionDetails: ExecutionDetails, + fieldDef: GraphQLField, + fieldNodes: ReadonlyArray, + parentType: GraphQLObjectType, + path: Path, + ) { + this._executionDetails = executionDetails; + this._fieldDef = fieldDef; + this._fieldNodes = fieldNodes; + this._parentType = parentType; + this._path = path; + } + + get fieldName(): string { + this._fieldName ??= this._fieldDef.name; + return this._fieldName; + } + + get fieldNodes(): ReadonlyArray { + return this._fieldNodes; + } + + get returnType(): GraphQLOutputType { + this._returnType ??= this._fieldDef.type; + return this._returnType; + } + + get parentType(): GraphQLObjectType { + return this._parentType; + } + + get path(): Path { + return this._path; + } + + get schema(): GraphQLSchema { + this._schema ??= this._executionDetails.schema; + return this._schema; + } + + get fragments(): ObjMap { + this._fragments ??= this._executionDetails.fragments; + return this._fragments; + } + + get rootValue(): unknown { + if (!this._rootValueDefined) { + this._rootValueDefined = true; + this._rootValue = this._executionDetails.rootValue; + } + return this._rootValue; + } + + get operation(): OperationDefinitionNode { + this._operation ??= this._executionDetails.operation; + return this._operation; + } + + get variableValues(): { [key: string]: unknown } { + this._variableValues ??= this._executionDetails.variableValues; + return this._variableValues; + } +} diff --git a/src/execution/__tests__/ResolveInfo-test.ts b/src/execution/__tests__/ResolveInfo-test.ts new file mode 100644 index 0000000000..40862e8ec5 --- /dev/null +++ b/src/execution/__tests__/ResolveInfo-test.ts @@ -0,0 +1,99 @@ +import { expect } from 'chai'; +import { describe, it } from 'mocha'; + +import { invariant } from '../../jsutils/invariant'; + +import { Kind } from '../../language/kinds'; +import { parse } from '../../language/parser'; + +import { GraphQLObjectType } from '../../type/definition'; +import { GraphQLString } from '../../type/scalars'; +import { GraphQLSchema } from '../../type/schema'; + +import { collectFields } from '../collectFields'; +import { ResolveInfo } from '../ResolveInfo'; + +describe('ResolveInfo', () => { + const query = new GraphQLObjectType({ + name: 'Query', + fields: { test: { type: GraphQLString } }, + }); + + const document = parse('{ test }'); + + const operation = document.definitions[0]; + invariant(operation.kind === Kind.OPERATION_DEFINITION); + + const executionDetails = { + schema: new GraphQLSchema({ query }), + operation, + fragments: {}, + rootValue: { test: 'root' }, + variableValues: {}, + }; + + const { schema, fragments, rootValue, variableValues } = executionDetails; + + const groupedFieldSet = collectFields( + schema, + fragments, + variableValues, + query, + operation.selectionSet, + ); + + const fieldDetailsList = groupedFieldSet.get('test'); + invariant(fieldDetailsList != null); + + const path = { key: 'test', prev: undefined, typename: 'Query' }; + + const resolveInfo = new ResolveInfo( + executionDetails, + query.getFields().test, + fieldDetailsList, + query, + path, + ); + + it('exposes fieldName', () => { + expect(resolveInfo.fieldName).to.equal('test'); + }); + + it('exposes fieldNodes', () => { + const retrievedFieldNodes = resolveInfo.fieldNodes; + expect(retrievedFieldNodes).to.deep.equal(fieldDetailsList); + expect(retrievedFieldNodes).to.equal(resolveInfo.fieldNodes); // ensure same reference + }); + + it('exposes returnType', () => { + expect(resolveInfo.returnType).to.equal(query.getFields().test.type); + }); + + it('exposes parentType', () => { + expect(resolveInfo.parentType).to.equal(query); + }); + + it('exposes path', () => { + expect(resolveInfo.path).to.deep.equal(path); + }); + + it('exposes schema', () => { + expect(resolveInfo.schema).to.equal(schema); + }); + + it('exposes fragments', () => { + expect(resolveInfo.fragments).to.equal(fragments); + }); + + it('exposes rootValue', () => { + expect(resolveInfo.rootValue).to.equal(rootValue); + }); + + it('exposes operation', () => { + expect(resolveInfo.operation).to.equal(operation); + }); + + it('exposes variableValues', () => { + expect(resolveInfo.variableValues).to.equal(variableValues); + }); +}); diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 1e5ec12c9a..ed644711e4 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -609,6 +609,7 @@ function executeField( } /** + * @deprecated will be removed in v17, use ResolveInfo class instead * @internal */ export function buildResolveInfo( diff --git a/src/execution/subscribe.ts b/src/execution/subscribe.ts index 78dc97916e..1196d757bd 100644 --- a/src/execution/subscribe.ts +++ b/src/execution/subscribe.ts @@ -22,6 +22,7 @@ import { // eslint-disable-next-line import/no-deprecated assertValidExecutionArguments, buildExecutionContext, + // eslint-disable-next-line import/no-deprecated buildResolveInfo, execute, getFieldDef, @@ -228,6 +229,7 @@ async function executeSubscription( } const path = addPath(undefined, responseName, rootType.name); + // eslint-disable-next-line import/no-deprecated const info = buildResolveInfo( exeContext, fieldDef,