import { inspect } from '../jsutils/inspect.ts';
import { invariant } from '../jsutils/invariant.ts';
import { isAsyncIterable } from '../jsutils/isAsyncIterable.ts';
import { isIterableObject } from '../jsutils/isIterableObject.ts';
import { isPromise, isPromiseLike } from '../jsutils/isPromise.ts';
import { memoize2 } from '../jsutils/memoize2.ts';
import { memoize3 } from '../jsutils/memoize3.ts';
import type { ObjMap } from '../jsutils/ObjMap.ts';
import type { Path } from '../jsutils/Path.ts';
import { addPath, pathToArray } from '../jsutils/Path.ts';
import { promiseForObject } from '../jsutils/promiseForObject.ts';
import type { PromiseOrValue } from '../jsutils/PromiseOrValue.ts';
import { promiseReduce } from '../jsutils/promiseReduce.ts';
import { ensureGraphQLError } from '../error/ensureGraphQLError.ts';
import type { GraphQLFormattedError } from '../error/GraphQLError.ts';
import { GraphQLError } from '../error/GraphQLError.ts';
import { locatedError } from '../error/locatedError.ts';
import type { FieldNode } from '../language/ast.ts';
import { OperationTypeNode } from '../language/ast.ts';
import type {
GraphQLAbstractType,
GraphQLLeafType,
GraphQLList,
GraphQLObjectType,
GraphQLOutputType,
GraphQLResolveInfo,
GraphQLResolveInfoHelpers,
} from '../type/definition.ts';
import {
isAbstractType,
isLeafType,
isListType,
isNonNullType,
isObjectType,
} from '../type/definition.ts';
import type { GraphQLSchema } from '../type/schema.ts';
import type {
GraphQLExecuteRootSelectionSetContext,
GraphQLResolveContext,
MinimalTracingChannel,
} from '../diagnostics.ts';
import {
executeRootSelectionSetChannel,
resolveChannel,
shouldTrace,
traceMixed,
} from '../diagnostics.ts';
import { AbortedGraphQLExecutionError } from './AbortedGraphQLExecutionError.ts';
import { buildResolveInfo } from './buildResolveInfo.ts';
import { withCancellation } from './cancellablePromise.ts';
import type {
DeferUsage,
FieldDetailsList,
GroupedFieldSet,
} from './collectFields.ts';
import {
collectFields,
collectSubfields as _collectSubfields,
} from './collectFields.ts';
import { collectIteratorPromises } from './collectIteratorPromises.ts';
import type { SharedExecutionContext } from './createSharedExecutionContext.ts';
import { createSharedExecutionContext } from './createSharedExecutionContext.ts';
import type { ValidatedExecutionArgs } from './ExecutionArgs.ts';
import type { StreamUsage } from './getStreamUsage.ts';
import { getStreamUsage as _getStreamUsage } from './getStreamUsage.ts';
import { runAsyncWorkFinishedHook } from './hooks.ts';
import { returnIteratorCatchingErrors } from './returnIteratorCatchingErrors.ts';
import { getArgumentValues } from './values.ts';
export const collectSubfields: (
validatedExecutionArgs: ValidatedExecutionArgs,
returnType: GraphQLObjectType,
fieldDetailsList: FieldDetailsList,
) => ReturnType<typeof _collectSubfields> = memoize3(
(
validatedExecutionArgs: ValidatedExecutionArgs,
returnType: GraphQLObjectType,
fieldDetailsList: FieldDetailsList,
) => {
const { schema, fragments, variableValues, hideSuggestions } =
validatedExecutionArgs;
return _collectSubfields(
schema,
fragments,
variableValues,
returnType,
fieldDetailsList,
hideSuggestions,
);
},
);
export const getStreamUsage: typeof _getStreamUsage = memoize2(
(
validatedExecutionArgs: ValidatedExecutionArgs,
fieldDetailsList: FieldDetailsList,
) => _getStreamUsage(validatedExecutionArgs, fieldDetailsList),
);
class CollectedErrors {
private _errorPositions: Set<Path | undefined>;
private _errors: Array<GraphQLError>;
constructor() {
this._errorPositions = new Set<Path | undefined>();
this._errors = [];
}
get errors(): ReadonlyArray<GraphQLError> {
return this._errors;
}
add(error: GraphQLError, path: Path | undefined): void {
if (this.hasNulledPosition(path)) {
return;
}
this._errorPositions.add(path);
this._errors.push(error);
}
hasNulledPosition(startPath: Path | undefined): boolean {
let path = startPath;
while (path !== undefined) {
if (this._errorPositions.has(path)) {
return true;
}
path = path.prev;
}
return this._errorPositions.has(undefined);
}
}
export interface ExecutionResult<
TData = ObjMap<unknown>,
TExtensions = ObjMap<unknown>,
> {
errors?: ReadonlyArray<GraphQLError>;
data?: TData | null;
extensions?: TExtensions;
}
export interface FormattedExecutionResult<
TData = ObjMap<unknown>,
TExtensions = ObjMap<unknown>,
> {
errors?: ReadonlyArray<GraphQLFormattedError>;
data?: TData | null;
extensions?: TExtensions;
}
const defaultAbortReason = new Error('This operation was aborted');
export class Executor<
TPositionContext = undefined,
TAlternativeInitialResponse = ExecutionResult,
> {
validatedExecutionArgs: ValidatedExecutionArgs;
aborted: boolean;
abortReason: unknown;
sharedExecutionContext: SharedExecutionContext;
collectedErrors: CollectedErrors;
abortResultPromise: (() => void) | undefined;
resolverAbortController: AbortController | undefined;
getAbortSignal: () => AbortSignal | undefined;
getAsyncHelpers: () => GraphQLResolveInfoHelpers;
promiseAll: <T>(
values: ReadonlyArray<PromiseOrValue<T>>,
) => Promise<Array<T>>;
constructor(
validatedExecutionArgs: ValidatedExecutionArgs,
sharedExecutionContext?: SharedExecutionContext,
) {
this.validatedExecutionArgs = validatedExecutionArgs;
this.aborted = false;
this.abortReason = defaultAbortReason;
this.collectedErrors = new CollectedErrors();
if (sharedExecutionContext === undefined) {
this.resolverAbortController = new AbortController();
this.sharedExecutionContext = createSharedExecutionContext(
this.resolverAbortController.signal,
);
} else {
this.sharedExecutionContext = sharedExecutionContext;
}
const { getAbortSignal, getAsyncHelpers, promiseAll } =
this.sharedExecutionContext;
this.getAbortSignal = getAbortSignal;
this.getAsyncHelpers = getAsyncHelpers;
this.promiseAll = promiseAll;
}
executeRootSelectionSet(
serially?: boolean,
): PromiseOrValue<ExecutionResult | TAlternativeInitialResponse> {
if (!shouldTrace(executeRootSelectionSetChannel)) {
return this.executeRootSelectionSetImpl(serially);
}
return traceMixed(
executeRootSelectionSetChannel,
this.buildExecuteContextFromValidatedArgs(this.validatedExecutionArgs),
() => this.executeRootSelectionSetImpl(serially),
);
}
buildExecuteContextFromValidatedArgs(
args: ValidatedExecutionArgs,
): GraphQLExecuteRootSelectionSetContext {
return {
schema: args.schema,
document: args.document,
operation: args.operation,
rawVariableValues: args.rawVariableValues,
operationName: args.operation.name?.value,
operationType: args.operation.operation,
};
}
executeRootSelectionSetImpl(
serially?: boolean,
): PromiseOrValue<ExecutionResult | TAlternativeInitialResponse> {
const externalAbortSignal = this.validatedExecutionArgs.externalAbortSignal;
let removeExternalAbortListener: (() => void) | undefined;
if (externalAbortSignal) {
externalAbortSignal.throwIfAborted();
const onExternalAbort = () => {
this.abort(externalAbortSignal.reason);
};
removeExternalAbortListener = () =>
externalAbortSignal.removeEventListener('abort', onExternalAbort);
externalAbortSignal.addEventListener('abort', onExternalAbort);
}
const maybeRemoveExternalAbortListener = () => {
removeExternalAbortListener?.();
};
let result: PromiseOrValue<ObjMap<unknown>>;
try {
const {
schema,
fragments,
rootValue,
operation,
variableValues,
hideSuggestions,
} = this.validatedExecutionArgs;
const { operation: operationType, selectionSet } = operation;
const rootType = schema.getRootType(operationType);
if (rootType == null) {
throw new GraphQLError(
`Schema is not configured to execute ${operationType} operation.`,
{ nodes: operation },
);
}
const { groupedFieldSet, newDeferUsages } = collectFields(
schema,
fragments,
variableValues,
rootType,
selectionSet,
hideSuggestions,
);
result = this.executeCollectedRootFields(
rootType,
rootValue,
groupedFieldSet,
serially ?? operationType === OperationTypeNode.MUTATION,
newDeferUsages,
);
if (isPromise(result)) {
const promise = result.then(
(data) => {
maybeRemoveExternalAbortListener();
return this.buildResponse(data);
},
(error: unknown) => {
maybeRemoveExternalAbortListener();
this.collectedErrors.add(ensureGraphQLError(error), undefined);
return this.buildResponse(null);
},
);
this.sharedExecutionContext.asyncWorkTracker.add(promise);
const { promise: cancellablePromise, abort: abortResultPromise } =
withCancellation(promise.then((resolved) => this.finish(resolved)));
this.abortResultPromise = () => {
abortResultPromise(this.createAbortedExecutionError(promise));
};
if (this.aborted) {
this.abortResultPromise();
}
return cancellablePromise;
}
maybeRemoveExternalAbortListener();
} catch (error) {
maybeRemoveExternalAbortListener();
this.collectedErrors.add(ensureGraphQLError(error), undefined);
return this.finish(this.buildResponse(null));
}
return this.finish(this.buildResponse(result));
}
abort(reason?: unknown): void {
if (this.aborted) {
return;
}
this.aborted = true;
if (reason !== undefined) {
this.abortReason = reason;
}
this.abortResultPromise?.();
this.resolverAbortController?.abort(this.abortReason);
}
finish<T>(result: T): T {
if (this.aborted) {
throw this.createAbortedExecutionError(result);
}
this.aborted = true;
return result;
}
createAbortedExecutionError<T>(
result: PromiseOrValue<T>,
): AbortedGraphQLExecutionError<T> {
return new AbortedGraphQLExecutionError(this.abortReason, result);
}
getFinishSharedExecution(): () => void {
const resolverAbortController = this.resolverAbortController;
const asyncWorkFinishedHook =
this.validatedExecutionArgs.hooks?.asyncWorkFinished;
if (asyncWorkFinishedHook === undefined) {
return () => resolverAbortController?.abort();
}
const validatedExecutionArgs = this.validatedExecutionArgs;
const sharedExecutionContext = this.sharedExecutionContext;
return () => {
resolverAbortController?.abort();
runAsyncWorkFinishedHook(
validatedExecutionArgs,
sharedExecutionContext,
asyncWorkFinishedHook,
);
};
}
buildResponse(
data: ObjMap<unknown> | null,
): ExecutionResult | TAlternativeInitialResponse {
this.getFinishSharedExecution()();
const errors = this.collectedErrors.errors;
return errors.length ? { errors, data } : { data };
}
executeCollectedRootFields(
rootType: GraphQLObjectType,
rootValue: unknown,
originalGroupedFieldSet: GroupedFieldSet,
serially: boolean,
_newDeferUsages: ReadonlyArray<DeferUsage>,
): PromiseOrValue<ObjMap<unknown>> {
return this.executeRootGroupedFieldSet(
rootType,
rootValue,
originalGroupedFieldSet,
serially,
undefined,
);
}
executeRootGroupedFieldSet(
rootType: GraphQLObjectType,
rootValue: unknown,
groupedFieldSet: GroupedFieldSet,
serially: boolean,
positionContext?: TPositionContext,
): PromiseOrValue<ObjMap<unknown>> {
return serially
? this.executeFieldsSerially(
rootType,
rootValue,
undefined,
groupedFieldSet,
positionContext,
)
: this.executeFields(
rootType,
rootValue,
undefined,
groupedFieldSet,
positionContext,
);
}
executeFieldsSerially(
parentType: GraphQLObjectType,
sourceValue: unknown,
path: Path | undefined,
groupedFieldSet: GroupedFieldSet,
positionContext: TPositionContext | undefined,
): PromiseOrValue<ObjMap<unknown>> {
let tracingChannel = shouldTrace(resolveChannel)
? resolveChannel
: undefined;
return promiseReduce(
groupedFieldSet,
(results, [responseName, fieldDetailsList]) => {
if (this.aborted) {
throw new Error('Aborted!');
}
const fieldPath = addPath(path, responseName, parentType.name);
const result = this.executeField(
parentType,
sourceValue,
fieldDetailsList,
fieldPath,
positionContext,
tracingChannel,
);
if (result === undefined) {
return results;
}
if (isPromise(result)) {
return result.then((resolved) => {
results[responseName] = resolved;
tracingChannel = shouldTrace(resolveChannel)
? resolveChannel
: undefined;
return results;
});
}
results[responseName] = result;
return results;
},
Object.create(null),
);
}
executeFields(
parentType: GraphQLObjectType,
sourceValue: unknown,
path: Path | undefined,
groupedFieldSet: GroupedFieldSet,
positionContext: TPositionContext | undefined,
): PromiseOrValue<ObjMap<unknown>> {
const results = Object.create(null);
let containsPromise = false;
const tracingChannel = shouldTrace(resolveChannel)
? resolveChannel
: undefined;
try {
for (const [responseName, fieldDetailsList] of groupedFieldSet) {
const fieldPath = addPath(path, responseName, parentType.name);
const result = this.executeField(
parentType,
sourceValue,
fieldDetailsList,
fieldPath,
positionContext,
tracingChannel,
);
if (result !== undefined) {
results[responseName] = result;
if (isPromise(result)) {
containsPromise = true;
}
}
}
} catch (error) {
if (containsPromise) {
this.sharedExecutionContext.asyncWorkTracker.addValues(
Object.values(results),
);
}
throw error;
}
if (!containsPromise) {
return results;
}
return promiseForObject(results, this.promiseAll);
}
executeField(
parentType: GraphQLObjectType,
source: unknown,
fieldDetailsList: FieldDetailsList,
path: Path,
positionContext: TPositionContext | undefined,
tracingChannel: MinimalTracingChannel<GraphQLResolveContext> | undefined,
): PromiseOrValue<unknown> {
const validatedExecutionArgs = this.validatedExecutionArgs;
const { schema, contextValue, variableValues, hideSuggestions } =
validatedExecutionArgs;
const firstFieldDetails = fieldDetailsList[0];
const firstNode = firstFieldDetails.node;
const fieldName = firstNode.name.value;
const fieldDef = schema.getField(parentType, fieldName);
if (!fieldDef) {
return;
}
const returnType = fieldDef.type;
let resolveFn = fieldDef.resolve ?? validatedExecutionArgs.fieldResolver;
if (tracingChannel !== undefined) {
const originalResolveFn = resolveFn;
resolveFn = (s, args, c, info) =>
traceMixed(
tracingChannel,
this.buildResolveContext(args, info, fieldDef.resolve === undefined),
() => originalResolveFn(s, args, c, info),
);
}
const info = buildResolveInfo(
validatedExecutionArgs,
fieldDef,
toNodes(fieldDetailsList),
parentType,
path,
this.getAbortSignal,
this.getAsyncHelpers,
);
try {
const args = getArgumentValues(
fieldDef,
firstNode,
variableValues,
firstFieldDetails.fragmentVariableValues,
hideSuggestions,
);
const result = resolveFn(source, args, contextValue, info);
if (isPromiseLike(result)) {
return this.completePromisedValue(
returnType,
fieldDetailsList,
info,
path,
result,
positionContext,
);
}
const completed = this.completeValue(
returnType,
fieldDetailsList,
info,
path,
result,
positionContext,
);
if (isPromise(completed)) {
return completed.then(undefined, (rawError: unknown) => {
this.handleFieldError(rawError, returnType, fieldDetailsList, path);
return null;
});
}
return completed;
} catch (rawError) {
this.handleFieldError(rawError, returnType, fieldDetailsList, path);
return null;
}
}
buildResolveContext(
args: ObjMap<unknown>,
info: GraphQLResolveInfo,
isDefaultResolver: boolean,
): GraphQLResolveContext {
let cachedFieldPath: string | undefined;
return {
fieldName: info.fieldName,
alias: String(info.path.key),
parentType: info.parentType.name,
fieldType: String(info.returnType),
args,
isDefaultResolver,
get fieldPath() {
cachedFieldPath ??= pathToArray(info.path).join('.');
return cachedFieldPath;
},
};
}
handleFieldError(
rawError: unknown,
returnType: GraphQLOutputType,
fieldDetailsList: FieldDetailsList,
path: Path,
): void {
const error = locatedError(
rawError,
toNodes(fieldDetailsList),
pathToArray(path),
);
if (
this.validatedExecutionArgs.errorPropagation &&
isNonNullType(returnType)
) {
throw error;
}
this.collectedErrors.add(error, path);
}
completeValue(
returnType: GraphQLOutputType,
fieldDetailsList: FieldDetailsList,
info: GraphQLResolveInfo,
path: Path,
result: unknown,
positionContext: TPositionContext | undefined,
): PromiseOrValue<unknown> {
if (result instanceof Error) {
throw result;
}
if (isNonNullType(returnType)) {
const completed = this.completeValue(
returnType.ofType,
fieldDetailsList,
info,
path,
result,
positionContext,
);
if (completed === null) {
throw new Error(
`Cannot return null for non-nullable field ${info.parentType}.${info.fieldName}.`,
);
}
return completed;
}
if (result == null) {
return null;
}
if (isListType(returnType)) {
return this.completeListValue(
returnType,
fieldDetailsList,
info,
path,
result,
positionContext,
);
}
if (isLeafType(returnType)) {
return this.completeLeafValue(returnType, result);
}
if (isAbstractType(returnType)) {
return this.completeAbstractValue(
returnType,
fieldDetailsList,
info,
path,
result,
positionContext,
);
}
if (isObjectType(returnType)) {
return this.completeObjectValue(
returnType,
fieldDetailsList,
info,
path,
result,
positionContext,
);
}
invariant(
false,
'Cannot complete value of unexpected output type: ' + inspect(returnType),
);
}
async completePromisedValue(
returnType: GraphQLOutputType,
fieldDetailsList: FieldDetailsList,
info: GraphQLResolveInfo,
path: Path,
result: PromiseLike<unknown>,
positionContext: TPositionContext | undefined,
): Promise<unknown> {
try {
const resolved = await result;
if (this.aborted) {
throw new Error('Aborted!');
}
let completed = this.completeValue(
returnType,
fieldDetailsList,
info,
path,
resolved,
positionContext,
);
if (isPromise(completed)) {
completed = await completed;
}
return completed;
} catch (rawError) {
this.handleFieldError(rawError, returnType, fieldDetailsList, path);
return null;
}
}
async completeAsyncIterableValue(
itemType: GraphQLOutputType,
fieldDetailsList: FieldDetailsList,
info: GraphQLResolveInfo,
path: Path,
items: AsyncIterable<unknown>,
positionContext: TPositionContext | undefined,
): Promise<ReadonlyArray<unknown>> {
const streamUsage =
typeof path.key === 'number'
? undefined
: getStreamUsage(this.validatedExecutionArgs, fieldDetailsList);
let containsPromise = false;
const completedResults: Array<unknown> = [];
const asyncIterator = items[Symbol.asyncIterator]();
let index = 0;
let iteration;
try {
while (true) {
if (
streamUsage?.initialCount === index &&
this.handleStream(
index,
path,
{ handle: asyncIterator, isAsync: true },
streamUsage,
info,
itemType,
)
) {
break;
}
const itemPath = addPath(path, index, undefined);
try {
iteration = await asyncIterator.next();
} catch (rawError) {
throw locatedError(
rawError,
toNodes(fieldDetailsList),
pathToArray(path),
);
}
if (this.aborted || iteration.done) {
break;
}
const item = iteration.value;
if (
this.completeMaybePromisedListItemValue(
item,
completedResults,
itemType,
fieldDetailsList,
info,
itemPath,
positionContext,
)
) {
containsPromise = true;
}
index++;
}
} catch (error) {
this.sharedExecutionContext.asyncWorkTracker.add(
returnIteratorCatchingErrors(asyncIterator),
);
if (containsPromise) {
this.sharedExecutionContext.asyncWorkTracker.addValues(
completedResults,
);
}
throw error;
}
if (this.aborted) {
if (!iteration?.done) {
this.sharedExecutionContext.asyncWorkTracker.add(
returnIteratorCatchingErrors(asyncIterator),
);
}
throw new Error('Aborted!');
}
return containsPromise
? this.promiseAll(completedResults)
: completedResults;
}
handleStream(
_index: number,
_path: Path,
_iterator:
| { handle: Iterator<unknown>; isAsync?: never }
| { handle: AsyncIterator<unknown>; isAsync: true },
_streamUsage: StreamUsage,
_info: GraphQLResolveInfo,
_itemType: GraphQLOutputType,
): boolean {
return false;
}
completeListValue(
returnType: GraphQLList<GraphQLOutputType>,
fieldDetailsList: FieldDetailsList,
info: GraphQLResolveInfo,
path: Path,
result: unknown,
positionContext: TPositionContext | undefined,
): PromiseOrValue<ReadonlyArray<unknown>> {
const itemType = returnType.ofType;
if (isAsyncIterable(result)) {
return this.completeAsyncIterableValue(
itemType,
fieldDetailsList,
info,
path,
result,
positionContext,
);
}
if (!isIterableObject(result)) {
throw new GraphQLError(
`Expected Iterable, but did not find one for field "${info.parentType}.${info.fieldName}".`,
);
}
return this.completeIterableValue(
itemType,
fieldDetailsList,
info,
path,
result,
positionContext,
);
}
completeIterableValue(
itemType: GraphQLOutputType,
fieldDetailsList: FieldDetailsList,
info: GraphQLResolveInfo,
path: Path,
items: Iterable<unknown>,
positionContext: TPositionContext | undefined,
): PromiseOrValue<ReadonlyArray<unknown>> {
const streamUsage =
typeof path.key === 'number'
? undefined
: getStreamUsage(this.validatedExecutionArgs, fieldDetailsList);
let containsPromise = false;
const completedResults: Array<unknown> = [];
let index = 0;
const iterator = items[Symbol.iterator]();
try {
while (true) {
if (
streamUsage?.initialCount === index &&
this.handleStream(
index,
path,
{ handle: iterator },
streamUsage,
info,
itemType,
)
) {
break;
}
const iteration = iterator.next();
if (iteration.done) {
break;
}
const item = iteration.value;
const itemPath = addPath(path, index, undefined);
if (
this.completeMaybePromisedListItemValue(
item,
completedResults,
itemType,
fieldDetailsList,
info,
itemPath,
positionContext,
)
) {
containsPromise = true;
}
index++;
}
} catch (error) {
const asyncWorkTracker = this.sharedExecutionContext.asyncWorkTracker;
if (containsPromise) {
asyncWorkTracker.addValues(completedResults);
}
asyncWorkTracker.addValues(collectIteratorPromises(iterator));
throw error;
}
return containsPromise
? this.promiseAll(completedResults)
: completedResults;
}
completeMaybePromisedListItemValue(
item: unknown,
completedResults: Array<unknown>,
itemType: GraphQLOutputType,
fieldDetailsList: FieldDetailsList,
info: GraphQLResolveInfo,
itemPath: Path,
positionContext: TPositionContext | undefined,
): boolean {
if (isPromiseLike(item)) {
completedResults.push(
this.completePromisedListItemValue(
item,
itemType,
fieldDetailsList,
info,
itemPath,
positionContext,
),
);
return true;
} else if (
this.completeListItemValue(
item,
completedResults,
itemType,
fieldDetailsList,
info,
itemPath,
positionContext,
)
) {
return true;
}
return false;
}
completeListItemValue(
item: unknown,
completedResults: Array<unknown>,
itemType: GraphQLOutputType,
fieldDetailsList: FieldDetailsList,
info: GraphQLResolveInfo,
itemPath: Path,
positionContext: TPositionContext | undefined,
): boolean {
try {
const completedItem = this.completeValue(
itemType,
fieldDetailsList,
info,
itemPath,
item,
positionContext,
);
if (isPromise(completedItem)) {
completedResults.push(
completedItem.then(undefined, (rawError: unknown) => {
this.handleFieldError(
rawError,
itemType,
fieldDetailsList,
itemPath,
);
return null;
}),
);
return true;
}
completedResults.push(completedItem);
} catch (rawError) {
this.handleFieldError(rawError, itemType, fieldDetailsList, itemPath);
completedResults.push(null);
}
return false;
}
async completePromisedListItemValue(
item: PromiseLike<unknown>,
itemType: GraphQLOutputType,
fieldDetailsList: FieldDetailsList,
info: GraphQLResolveInfo,
itemPath: Path,
positionContext: TPositionContext | undefined,
): Promise<unknown> {
try {
const resolved = await item;
if (this.aborted) {
throw new Error('Aborted!');
}
let completed = this.completeValue(
itemType,
fieldDetailsList,
info,
itemPath,
resolved,
positionContext,
);
if (isPromise(completed)) {
completed = await completed;
}
return completed;
} catch (rawError) {
this.handleFieldError(rawError, itemType, fieldDetailsList, itemPath);
return null;
}
}
completeLeafValue(returnType: GraphQLLeafType, result: unknown): unknown {
const coerced = returnType.coerceOutputValue(result);
if (coerced == null) {
throw new Error(
`Expected \`${inspect(returnType)}.coerceOutputValue(${inspect(result)})\` to ` +
`return non-nullable value, returned: ${inspect(coerced)}`,
);
}
return coerced;
}
completeAbstractValue(
returnType: GraphQLAbstractType,
fieldDetailsList: FieldDetailsList,
info: GraphQLResolveInfo,
path: Path,
result: unknown,
positionContext: TPositionContext | undefined,
): PromiseOrValue<ObjMap<unknown>> {
const validatedExecutionArgs = this.validatedExecutionArgs;
const { schema, contextValue } = validatedExecutionArgs;
const resolveTypeFn =
returnType.resolveType ?? validatedExecutionArgs.typeResolver;
const runtimeType = resolveTypeFn(result, contextValue, info, returnType);
if (isPromiseLike(runtimeType)) {
return runtimeType.then((resolvedRuntimeType) => {
if (this.aborted) {
throw new Error('Aborted!');
}
return this.completeObjectValue(
this.ensureValidRuntimeType(
resolvedRuntimeType,
schema,
returnType,
fieldDetailsList,
info,
result,
),
fieldDetailsList,
info,
path,
result,
positionContext,
);
});
}
return this.completeObjectValue(
this.ensureValidRuntimeType(
runtimeType,
schema,
returnType,
fieldDetailsList,
info,
result,
),
fieldDetailsList,
info,
path,
result,
positionContext,
);
}
ensureValidRuntimeType(
runtimeTypeName: unknown,
schema: GraphQLSchema,
returnType: GraphQLAbstractType,
fieldDetailsList: FieldDetailsList,
info: GraphQLResolveInfo,
result: unknown,
): GraphQLObjectType {
if (runtimeTypeName == null) {
throw new GraphQLError(
`Abstract type "${returnType}" must resolve to an Object type at runtime for field "${info.parentType}.${info.fieldName}". Either the "${returnType}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`,
{ nodes: toNodes(fieldDetailsList) },
);
}
if (typeof runtimeTypeName !== 'string') {
throw new GraphQLError(
`Abstract type "${returnType}" must resolve to an Object type at runtime for field "${info.parentType}.${info.fieldName}" with ` +
`value ${inspect(result)}, received "${inspect(
runtimeTypeName,
)}", which is not a valid Object type name.`,
);
}
const runtimeType = schema.getType(runtimeTypeName);
if (runtimeType == null) {
throw new GraphQLError(
`Abstract type "${returnType}" was resolved to a type "${runtimeTypeName}" that does not exist inside the schema.`,
{ nodes: toNodes(fieldDetailsList) },
);
}
if (!isObjectType(runtimeType)) {
throw new GraphQLError(
`Abstract type "${returnType}" was resolved to a non-object type "${runtimeTypeName}".`,
{ nodes: toNodes(fieldDetailsList) },
);
}
if (!schema.isSubType(returnType, runtimeType)) {
throw new GraphQLError(
`Runtime Object type "${runtimeType}" is not a possible type for "${returnType}".`,
{ nodes: toNodes(fieldDetailsList) },
);
}
return runtimeType;
}
completeObjectValue(
returnType: GraphQLObjectType,
fieldDetailsList: FieldDetailsList,
info: GraphQLResolveInfo,
path: Path,
result: unknown,
positionContext: TPositionContext | undefined,
): PromiseOrValue<ObjMap<unknown>> {
if (returnType.isTypeOf) {
const isTypeOf = returnType.isTypeOf(
result,
this.validatedExecutionArgs.contextValue,
info,
);
if (isPromiseLike(isTypeOf)) {
return isTypeOf.then((resolvedIsTypeOf) => {
if (this.aborted) {
throw new Error('Aborted!');
}
if (!resolvedIsTypeOf) {
throw this.invalidReturnTypeError(
returnType,
result,
fieldDetailsList,
);
}
return this.collectAndExecuteSubfields(
returnType,
fieldDetailsList,
path,
result,
positionContext,
);
});
}
if (!isTypeOf) {
throw this.invalidReturnTypeError(returnType, result, fieldDetailsList);
}
}
return this.collectAndExecuteSubfields(
returnType,
fieldDetailsList,
path,
result,
positionContext,
);
}
invalidReturnTypeError(
returnType: GraphQLObjectType,
result: unknown,
fieldDetailsList: FieldDetailsList,
): GraphQLError {
return new GraphQLError(
`Expected value of type "${returnType}" but got: ${inspect(result)}.`,
{ nodes: toNodes(fieldDetailsList) },
);
}
collectAndExecuteSubfields(
returnType: GraphQLObjectType,
fieldDetailsList: FieldDetailsList,
path: Path,
result: unknown,
positionContext: TPositionContext | undefined,
): PromiseOrValue<ObjMap<unknown>> {
const { groupedFieldSet, newDeferUsages } = collectSubfields(
this.validatedExecutionArgs,
returnType,
fieldDetailsList,
);
return this.executeCollectedSubfields(
returnType,
result,
path,
groupedFieldSet,
newDeferUsages,
positionContext,
);
}
executeCollectedSubfields(
parentType: GraphQLObjectType,
sourceValue: unknown,
path: Path | undefined,
originalGroupedFieldSet: GroupedFieldSet,
_newDeferUsages: ReadonlyArray<DeferUsage>,
_positionContext: TPositionContext | undefined,
): PromiseOrValue<ObjMap<unknown>> {
return this.executeFields(
parentType,
sourceValue,
path,
originalGroupedFieldSet,
undefined,
);
}
}
function toNodes(fieldDetailsList: FieldDetailsList): ReadonlyArray<FieldNode> {
return fieldDetailsList.map((fieldDetails) => fieldDetails.node);
}