Skip to content

Commit

Permalink
introduce resolveField fn (#43)
Browse files Browse the repository at this point in the history
...into ExecutionContext
  • Loading branch information
yaacovCR committed Nov 1, 2021
1 parent b4e5dc0 commit ecfd106
Showing 1 changed file with 67 additions and 34 deletions.
101 changes: 67 additions & 34 deletions src/execution/executor.ts
Expand Up @@ -97,9 +97,9 @@ interface ExecutionContext {
variableValues: { [variable: string]: unknown };
fieldResolver: GraphQLFieldResolver<any, any>;
typeResolver: GraphQLTypeResolver<any, any>;
subscribeFieldResolver: GraphQLFieldResolver<any, any>;
forceQueryAlgorithm: boolean;
disableIncremental: boolean;
resolveField: FieldResolver;
errors: Array<GraphQLError>;
subsequentPayloads: Array<Promise<IteratorResult<DispatcherResult, void>>>;
initialResult?: ExecutionResult;
Expand Down Expand Up @@ -184,6 +184,14 @@ export type FieldsExecutor = (
errors: Array<GraphQLError>,
) => PromiseOrValue<ObjMap<unknown>>;

export type FieldResolver = (
exeContext: ExecutionContext,
fieldDef: GraphQLField<unknown, unknown>,
source: unknown,
info: GraphQLResolveInfo,
fieldNodes: ReadonlyArray<FieldNode>,
) => unknown;

/**
* Executor class responsible for implementing the Execution section of the GraphQL spec.
*
Expand Down Expand Up @@ -408,6 +416,37 @@ export class Executor {
);
}

buildFieldResolver =
(
resolverKey: 'resolve' | 'subscribe',
defaultResolver: GraphQLFieldResolver<unknown, unknown>,
) =>
(
exeContext: ExecutionContext,
fieldDef: GraphQLField<unknown, unknown>,
source: unknown,
info: GraphQLResolveInfo,
fieldNodes: ReadonlyArray<FieldNode>,
) => {
const resolveFn = fieldDef[resolverKey] ?? defaultResolver;

// Build a JS object of arguments from the field.arguments AST, using the
// variables scope to fulfill any variable references.
// TODO: find a way to memoize, in case this field is within a List type.
const args = getArgumentValues(
fieldDef,
fieldNodes[0],
exeContext.variableValues,
);

// The resolve function's optional third argument is a context value that
// is provided to every resolve function within an execution. It is commonly
// used to represent an authenticated user, or request-specific caches.
const contextValue = exeContext.contextValue;

return resolveFn(source, args, contextValue, info);
};

/**
* Constructs a ExecutionContext object from the arguments passed to
* execute, which we will pass throughout the other execution methods.
Expand Down Expand Up @@ -483,18 +522,25 @@ export class Executor {
return coercedVariableValues.errors;
}

const defaultResolveFieldValueFn = fieldResolver ?? defaultFieldResolver;
return {
schema,
fragments,
rootValue,
contextValue,
operation,
variableValues: coercedVariableValues.coerced,
fieldResolver: fieldResolver ?? defaultFieldResolver,
fieldResolver: defaultResolveFieldValueFn,
typeResolver: typeResolver ?? defaultTypeResolver,
subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver,
forceQueryAlgorithm: forceQueryAlgorithm ?? false,
disableIncremental: disableIncremental ?? false,
resolveField:
operation.operation === 'subscription' && !forceQueryAlgorithm
? this.buildFieldResolver(
'subscribe',
subscribeFieldResolver ?? defaultFieldResolver,
)
: this.buildFieldResolver('resolve', defaultResolveFieldValueFn),
errors: [],
subsequentPayloads: [],
iterators: [],
Expand All @@ -515,6 +561,10 @@ export class Executor {
...exeContext,
rootValue: payload,
forceQueryAlgorithm: true,
resolveField: this.buildFieldResolver(
'resolve',
exeContext.fieldResolver,
),
errors: [],
};
}
Expand Down Expand Up @@ -703,7 +753,6 @@ export class Executor {
}

const returnType = fieldDef.type;
const resolveFn = fieldDef.resolve ?? exeContext.fieldResolver;

const info = this.buildResolveInfo(
exeContext,
Expand All @@ -713,24 +762,17 @@ export class Executor {
path,
);

// Get the resolve function, regardless of if its result is normal or abrupt (error).
// Get the resolved field value, regardless of if its result is normal or abrupt (error).
// Then, complete the field
try {
// Build a JS object of arguments from the field.arguments AST, using the
// variables scope to fulfill any variable references.
// TODO: find a way to memoize, in case this field is within a List type.
const args = getArgumentValues(
const result = exeContext.resolveField(
exeContext,
fieldDef,
fieldNodes[0],
exeContext.variableValues,
source,
info,
fieldNodes,
);

// The resolve function's optional third argument is a context value that
// is provided to every resolve function within an execution. It is commonly
// used to represent an authenticated user, or request-specific caches.
const contextValue = exeContext.contextValue;

const result = resolveFn(source, args, contextValue, info);

let completed;
if (isPromise(result)) {
completed = result.then((resolved) =>
Expand Down Expand Up @@ -1574,22 +1616,13 @@ export class Executor {
);

try {
// Implements the "ResolveFieldEventStream" algorithm from GraphQL specification.
// It differs from "ResolveFieldValue" due to providing a different `resolveFn`.

// Build a JS object of arguments from the field.arguments AST, using the
// variables scope to fulfill any variable references.
const args = getArgumentValues(fieldDef, fieldNodes[0], variableValues);

// The resolve function's optional third argument is a context value that
// is provided to every resolve function within an execution. It is commonly
// used to represent an authenticated user, or request-specific caches.
const contextValue = exeContext.contextValue;

// Call the `subscribe()` resolver or the default resolver to produce an
// AsyncIterable yielding raw payloads.
const resolveFn = fieldDef.subscribe ?? exeContext.subscribeFieldResolver;
const eventStream = await resolveFn(rootValue, args, contextValue, info);
const eventStream = await exeContext.resolveField(
exeContext,
fieldDef,
rootValue,
info,
fieldNodes,
);

if (eventStream instanceof Error) {
throw eventStream;
Expand Down

0 comments on commit ecfd106

Please sign in to comment.