Skip to content

Commit b21afb6

Browse files
committed
refactor buildResolveInfo into a lazy class (#4530)
<img width="840" height="142" alt="image" src="https://github.com/user-attachments/assets/b55da6ae-d50b-40b6-b454-dc949ec4faf0" /> this is a breaking change only because `buildResolveInfo()` is technically accessible via deep import
1 parent 8be9697 commit b21afb6

4 files changed

Lines changed: 244 additions & 61 deletions

File tree

src/execution/ResolveInfo.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import type { ObjMap } from '../jsutils/ObjMap.js';
2+
import type { Path } from '../jsutils/Path.js';
3+
4+
import type {
5+
FieldNode,
6+
FragmentDefinitionNode,
7+
OperationDefinitionNode,
8+
} from '../language/ast.js';
9+
10+
import type {
11+
GraphQLField,
12+
GraphQLObjectType,
13+
GraphQLOutputType,
14+
GraphQLResolveInfo,
15+
GraphQLSchema,
16+
} from '../type/index.js';
17+
18+
import type { FieldDetailsList } from './collectFields.js';
19+
import type { ValidatedExecutionArgs } from './execute.js';
20+
import type { VariableValues } from './values.js';
21+
22+
/** @internal */
23+
export class ResolveInfo implements GraphQLResolveInfo {
24+
private _validatedExecutionArgs: ValidatedExecutionArgs;
25+
private _fieldDef: GraphQLField<unknown, unknown>;
26+
private _fieldDetailsList: FieldDetailsList;
27+
private _parentType: GraphQLObjectType;
28+
private _path: Path;
29+
private _abortSignal: AbortSignal | undefined;
30+
31+
private _fieldName: string | undefined;
32+
private _fieldNodes: ReadonlyArray<FieldNode> | undefined;
33+
private _returnType: GraphQLOutputType | undefined;
34+
private _schema: GraphQLSchema | undefined;
35+
private _fragments: ObjMap<FragmentDefinitionNode> | undefined;
36+
private _rootValue: unknown;
37+
private _rootValueDefined?: boolean;
38+
private _operation: OperationDefinitionNode | undefined;
39+
private _variableValues: VariableValues | undefined;
40+
41+
// eslint-disable-next-line max-params
42+
constructor(
43+
validatedExecutionArgs: ValidatedExecutionArgs,
44+
fieldDef: GraphQLField<unknown, unknown>,
45+
fieldDetailsList: FieldDetailsList,
46+
parentType: GraphQLObjectType,
47+
path: Path,
48+
abortSignal: AbortSignal | undefined,
49+
) {
50+
this._validatedExecutionArgs = validatedExecutionArgs;
51+
this._fieldDef = fieldDef;
52+
this._fieldDetailsList = fieldDetailsList;
53+
this._parentType = parentType;
54+
this._path = path;
55+
this._abortSignal = abortSignal;
56+
}
57+
58+
get fieldName(): string {
59+
this._fieldName ??= this._fieldDef.name;
60+
return this._fieldName;
61+
}
62+
63+
get fieldNodes(): ReadonlyArray<FieldNode> {
64+
this._fieldNodes ??= this._fieldDetailsList.map(
65+
(fieldDetails) => fieldDetails.node,
66+
);
67+
return this._fieldNodes;
68+
}
69+
70+
get returnType(): GraphQLOutputType {
71+
this._returnType ??= this._fieldDef.type;
72+
return this._returnType;
73+
}
74+
75+
get parentType(): GraphQLObjectType {
76+
return this._parentType;
77+
}
78+
79+
get path(): Path {
80+
return this._path;
81+
}
82+
83+
get schema(): GraphQLSchema {
84+
this._schema ??= this._validatedExecutionArgs.schema;
85+
return this._schema;
86+
}
87+
88+
get fragments(): ObjMap<FragmentDefinitionNode> {
89+
this._fragments ??= this._validatedExecutionArgs.fragmentDefinitions;
90+
return this._fragments;
91+
}
92+
93+
get rootValue(): unknown {
94+
if (!this._rootValueDefined) {
95+
this._rootValueDefined = true;
96+
this._rootValue = this._validatedExecutionArgs.rootValue;
97+
}
98+
return this._rootValue;
99+
}
100+
101+
get operation(): OperationDefinitionNode {
102+
this._operation ??= this._validatedExecutionArgs.operation;
103+
return this._operation;
104+
}
105+
106+
get variableValues(): VariableValues {
107+
this._variableValues ??= this._validatedExecutionArgs.variableValues;
108+
return this._variableValues;
109+
}
110+
111+
get abortSignal(): AbortSignal | undefined {
112+
return this._abortSignal;
113+
}
114+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { assert, expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import { parse } from '../../language/parser.js';
5+
6+
import {
7+
GraphQLObjectType,
8+
GraphQLSchema,
9+
GraphQLString,
10+
} from '../../type/index.js';
11+
12+
import { collectFields } from '../collectFields.js';
13+
import { validateExecutionArgs } from '../entrypoints.js';
14+
import { ResolveInfo } from '../ResolveInfo.js';
15+
16+
describe('ResolveInfo', () => {
17+
const query = new GraphQLObjectType({
18+
name: 'Query',
19+
fields: { test: { type: GraphQLString } },
20+
});
21+
22+
const abortController = new AbortController();
23+
const abortSignal = abortController.signal;
24+
25+
const validatedExecutionArgs = validateExecutionArgs({
26+
schema: new GraphQLSchema({ query }),
27+
document: parse(`{ test }`),
28+
rootValue: { test: 'root' },
29+
abortSignal,
30+
});
31+
32+
assert('schema' in validatedExecutionArgs);
33+
34+
const { schema, fragments, rootValue, operation, variableValues } =
35+
validatedExecutionArgs;
36+
37+
const collectedFields = collectFields(
38+
schema,
39+
fragments,
40+
variableValues,
41+
query,
42+
operation.selectionSet,
43+
false,
44+
);
45+
46+
const fieldDetailsList = collectedFields.groupedFieldSet.get('test');
47+
48+
assert(fieldDetailsList != null);
49+
50+
const path = { key: 'test', prev: undefined, typename: 'Query' };
51+
const resolveInfo = new ResolveInfo(
52+
validatedExecutionArgs,
53+
query.getFields().test,
54+
fieldDetailsList,
55+
query,
56+
path,
57+
abortSignal,
58+
);
59+
60+
it('exposes fieldName', () => {
61+
expect(resolveInfo.fieldName).to.equal('test');
62+
});
63+
64+
it('exposes fieldNodes', () => {
65+
const retrievedFieldNodes = resolveInfo.fieldNodes;
66+
expect(retrievedFieldNodes).to.deep.equal(
67+
fieldDetailsList.map((fieldDetails) => fieldDetails.node),
68+
);
69+
expect(retrievedFieldNodes).to.equal(resolveInfo.fieldNodes); // ensure same reference
70+
});
71+
72+
it('exposes returnType', () => {
73+
expect(resolveInfo.returnType).to.equal(query.getFields().test.type);
74+
});
75+
76+
it('exposes parentType', () => {
77+
expect(resolveInfo.parentType).to.equal(query);
78+
});
79+
80+
it('exposes path', () => {
81+
expect(resolveInfo.path).to.deep.equal(path);
82+
});
83+
84+
it('exposes schema', () => {
85+
expect(resolveInfo.schema).to.equal(schema);
86+
});
87+
88+
it('exposes fragments', () => {
89+
expect(resolveInfo.fragments).to.equal(
90+
validatedExecutionArgs.fragmentDefinitions,
91+
);
92+
});
93+
94+
it('exposes rootValue', () => {
95+
expect(resolveInfo.rootValue).to.equal(rootValue);
96+
});
97+
98+
it('exposes operation', () => {
99+
expect(resolveInfo.operation).to.equal(operation);
100+
});
101+
102+
it('exposes variableValues', () => {
103+
expect(resolveInfo.variableValues).to.equal(
104+
validatedExecutionArgs.variableValues,
105+
);
106+
});
107+
108+
it('exposes abortSignal', () => {
109+
expect(resolveInfo.abortSignal).to.equal(abortSignal);
110+
});
111+
});

src/execution/__tests__/executor-test.ts

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -208,36 +208,21 @@ describe('Execute: Handles basic execution tasks', () => {
208208

209209
executeSync({ schema, document, rootValue, variableValues });
210210

211-
expect(resolvedInfo).to.have.all.keys(
212-
'fieldName',
213-
'fieldNodes',
214-
'returnType',
215-
'parentType',
216-
'path',
217-
'schema',
218-
'fragments',
219-
'rootValue',
220-
'operation',
221-
'variableValues',
222-
'abortSignal',
223-
);
224-
225211
const operation = document.definitions[0];
226212
assert(operation.kind === Kind.OPERATION_DEFINITION);
227213

228-
expect(resolvedInfo).to.include({
214+
const field = operation.selectionSet.selections[0];
215+
216+
expect(resolvedInfo).to.deep.include({
229217
fieldName: 'test',
218+
fieldNodes: [field],
230219
returnType: GraphQLString,
231220
parentType: testType,
221+
path: { prev: undefined, key: 'result', typename: 'Test' },
232222
schema,
223+
fragments: {},
233224
rootValue,
234225
operation,
235-
});
236-
237-
const field = operation.selectionSet.selections[0];
238-
expect(resolvedInfo).to.deep.include({
239-
fieldNodes: [field],
240-
path: { prev: undefined, key: 'result', typename: 'Test' },
241226
variableValues: {
242227
sources: {
243228
var: {
@@ -251,6 +236,7 @@ describe('Execute: Handles basic execution tasks', () => {
251236
},
252237
coerced: { var: 'abc' },
253238
},
239+
abortSignal: undefined,
254240
});
255241
});
256242

src/execution/execute.ts

Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import { OperationTypeNode } from '../language/ast.js';
2424

2525
import type {
2626
GraphQLAbstractType,
27-
GraphQLField,
2827
GraphQLFieldResolver,
2928
GraphQLLeafType,
3029
GraphQLList,
@@ -58,6 +57,7 @@ import {
5857
collectSubfields as _collectSubfields,
5958
} from './collectFields.js';
6059
import { mapAsyncIterable } from './mapAsyncIterable.js';
60+
import { ResolveInfo } from './ResolveInfo.js';
6161
import type { VariableValues } from './values.js';
6262
import { getArgumentValues } from './values.js';
6363
import { withConcurrentAbruptClose } from './withConcurrentAbruptClose.js';
@@ -482,10 +482,10 @@ function executeField(
482482
const returnType = fieldDef.type;
483483
const resolveFn = fieldDef.resolve ?? validatedExecutionArgs.fieldResolver;
484484

485-
const info = buildResolveInfo(
485+
const info = new ResolveInfo(
486486
validatedExecutionArgs,
487487
fieldDef,
488-
toNodes(fieldDetailsList),
488+
fieldDetailsList,
489489
parentType,
490490
path,
491491
abortSignal,
@@ -556,37 +556,6 @@ function toNodes(fieldDetailsList: FieldDetailsList): ReadonlyArray<FieldNode> {
556556
return fieldDetailsList.map((fieldDetails) => fieldDetails.node);
557557
}
558558

559-
/**
560-
* TODO: consider no longer exporting this function
561-
* @internal
562-
*/
563-
export function buildResolveInfo(
564-
validatedExecutionArgs: ValidatedExecutionArgs,
565-
fieldDef: GraphQLField<unknown, unknown>,
566-
fieldNodes: ReadonlyArray<FieldNode>,
567-
parentType: GraphQLObjectType,
568-
path: Path,
569-
abortSignal: AbortSignal | undefined,
570-
): GraphQLResolveInfo {
571-
const { schema, fragmentDefinitions, rootValue, operation, variableValues } =
572-
validatedExecutionArgs;
573-
// The resolve function's optional fourth argument is a collection of
574-
// information about the current execution state.
575-
return {
576-
fieldName: fieldDef.name,
577-
fieldNodes,
578-
returnType: fieldDef.type,
579-
parentType,
580-
path,
581-
schema,
582-
fragments: fragmentDefinitions,
583-
rootValue,
584-
operation,
585-
variableValues,
586-
abortSignal,
587-
};
588-
}
589-
590559
function handleFieldError(
591560
rawError: unknown,
592561
exeContext: ExecutionContext,
@@ -1328,19 +1297,18 @@ function executeSubscription(
13281297
const fieldName = firstFieldNode.name.value;
13291298
const fieldDef = schema.getField(rootType, fieldName);
13301299

1331-
const fieldNodes = fieldDetailsList.map((fieldDetails) => fieldDetails.node);
13321300
if (!fieldDef) {
13331301
throw new GraphQLError(
13341302
`The subscription field "${fieldName}" is not defined.`,
1335-
{ nodes: fieldNodes },
1303+
{ nodes: toNodes(fieldDetailsList) },
13361304
);
13371305
}
13381306

13391307
const path = addPath(undefined, responseName, rootType.name);
1340-
const info = buildResolveInfo(
1308+
const info = new ResolveInfo(
13411309
validatedExecutionArgs,
13421310
fieldDef,
1343-
toNodes(fieldDetailsList),
1311+
fieldDetailsList,
13441312
rootType,
13451313
path,
13461314
abortSignal,
@@ -1385,14 +1353,18 @@ function executeSubscription(
13851353
},
13861354
(error: unknown) => {
13871355
abortSignalListener?.disconnect();
1388-
throw locatedError(error, fieldNodes, pathToArray(path));
1356+
throw locatedError(
1357+
error,
1358+
toNodes(fieldDetailsList),
1359+
pathToArray(path),
1360+
);
13891361
},
13901362
);
13911363
}
13921364

13931365
return assertEventStream(result);
13941366
} catch (error) {
1395-
throw locatedError(error, fieldNodes, pathToArray(path));
1367+
throw locatedError(error, toNodes(fieldDetailsList), pathToArray(path));
13961368
}
13971369
}
13981370

0 commit comments

Comments
 (0)