import { inspect } from '../jsutils/inspect.js';
import { invariant } from '../jsutils/invariant.js';
import { isAsyncIterable } from '../jsutils/isAsyncIterable.js';
import { isIterableObject } from '../jsutils/isIterableObject.js';
import { isObjectLike } from '../jsutils/isObjectLike.js';
import { isPromise } from '../jsutils/isPromise.js';
import type { Maybe } from '../jsutils/Maybe.js';
import { memoize3 } from '../jsutils/memoize3.js';
import type { ObjMap } from '../jsutils/ObjMap.js';
import type { Path } from '../jsutils/Path.js';
import { addPath, pathToArray } from '../jsutils/Path.js';
import { promiseForObject } from '../jsutils/promiseForObject.js';
import type { PromiseOrValue } from '../jsutils/PromiseOrValue.js';
import { promiseReduce } from '../jsutils/promiseReduce.js';
import { GraphQLError } from '../error/GraphQLError.js';
import { locatedError } from '../error/locatedError.js';
import type {
DocumentNode,
FieldNode,
FragmentDefinitionNode,
OperationDefinitionNode,
} from '../language/ast.js';
import { OperationTypeNode } from '../language/ast.js';
import { Kind } from '../language/kinds.js';
import type {
GraphQLAbstractType,
GraphQLField,
GraphQLFieldResolver,
GraphQLLeafType,
GraphQLList,
GraphQLObjectType,
GraphQLOutputType,
GraphQLResolveInfo,
GraphQLTypeResolver,
} from '../type/definition.js';
import {
isAbstractType,
isLeafType,
isListType,
isNonNullType,
isObjectType,
} from '../type/definition.js';
import { GraphQLStreamDirective } from '../type/directives.js';
import type { GraphQLSchema } from '../type/schema.js';
import { assertValidSchema } from '../type/validate.js';
import type { DeferUsageSet, FieldPlan } from './buildFieldPlan.js';
import { buildFieldPlan } from './buildFieldPlan.js';
import type {
DeferUsage,
FieldGroup,
GroupedFieldSet,
} from './collectFields.js';
import {
collectFields,
collectSubfields as _collectSubfields,
} from './collectFields.js';
import type {
CancellableStreamRecord,
DeferredGroupedFieldSetRecord,
DeferredGroupedFieldSetResult,
ExecutionResult,
ExperimentalIncrementalExecutionResults,
IncrementalDataRecord,
StreamItemsRecord,
StreamItemsResult,
SubsequentResultRecord,
} from './IncrementalPublisher.js';
import {
buildIncrementalResponse,
DeferredFragmentRecord,
isReconcilableStreamItemsResult,
} from './IncrementalPublisher.js';
import { mapAsyncIterable } from './mapAsyncIterable.js';
import {
getArgumentValues,
getDirectiveValues,
getVariableValues,
} from './values.js';
const collectSubfields = memoize3(
(
exeContext: ExecutionContext,
returnType: GraphQLObjectType,
fieldGroup: FieldGroup,
) =>
_collectSubfields(
exeContext.schema,
exeContext.fragments,
exeContext.variableValues,
exeContext.operation,
returnType,
fieldGroup,
),
);
export interface ExecutionContext {
schema: GraphQLSchema;
fragments: ObjMap<FragmentDefinitionNode>;
rootValue: unknown;
contextValue: unknown;
operation: OperationDefinitionNode;
variableValues: { [variable: string]: unknown };
fieldResolver: GraphQLFieldResolver<any, any>;
typeResolver: GraphQLTypeResolver<any, any>;
subscribeFieldResolver: GraphQLFieldResolver<any, any>;
errors: Array<GraphQLError>;
cancellableStreams: Set<CancellableStreamRecord>;
}
interface IncrementalContext {
errors: Array<GraphQLError>;
deferUsageSet?: DeferUsageSet | undefined;
}
export interface ExecutionArgs {
schema: GraphQLSchema;
document: DocumentNode;
rootValue?: unknown;
contextValue?: unknown;
variableValues?: Maybe<{ readonly [variable: string]: unknown }>;
operationName?: Maybe<string>;
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
}
export interface StreamUsage {
label: string | undefined;
initialCount: number;
fieldGroup: FieldGroup;
}
type GraphQLWrappedResult<T> = [T, Array<IncrementalDataRecord>];
const UNEXPECTED_EXPERIMENTAL_DIRECTIVES =
'The provided schema unexpectedly contains experimental directives (@defer or @stream). These directives may only be utilized if experimental execution features are explicitly enabled.';
const UNEXPECTED_MULTIPLE_PAYLOADS =
'Executing this GraphQL operation would unexpectedly produce multiple payloads (due to @defer or @stream directive)';
export function execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
if (args.schema.getDirective('defer') || args.schema.getDirective('stream')) {
throw new Error(UNEXPECTED_EXPERIMENTAL_DIRECTIVES);
}
const result = experimentalExecuteIncrementally(args);
if (!isPromise(result)) {
if ('initialResult' in result) {
throw new Error(UNEXPECTED_MULTIPLE_PAYLOADS);
}
return result;
}
return result.then((incrementalResult) => {
if ('initialResult' in incrementalResult) {
throw new Error(UNEXPECTED_MULTIPLE_PAYLOADS);
}
return incrementalResult;
});
}
export function experimentalExecuteIncrementally(
args: ExecutionArgs,
): PromiseOrValue<ExecutionResult | ExperimentalIncrementalExecutionResults> {
const exeContext = buildExecutionContext(args);
if (!('schema' in exeContext)) {
return { errors: exeContext };
}
return executeOperation(exeContext);
}
function executeOperation(
exeContext: ExecutionContext,
): PromiseOrValue<ExecutionResult | ExperimentalIncrementalExecutionResults> {
try {
const { operation, schema, fragments, variableValues, rootValue } =
exeContext;
const rootType = schema.getRootType(operation.operation);
if (rootType == null) {
throw new GraphQLError(
`Schema is not configured to execute ${operation.operation} operation.`,
{ nodes: operation },
);
}
const collectedFields = collectFields(
schema,
fragments,
variableValues,
rootType,
operation,
);
let groupedFieldSet = collectedFields.groupedFieldSet;
const newDeferUsages = collectedFields.newDeferUsages;
let graphqlWrappedResult: PromiseOrValue<
GraphQLWrappedResult<ObjMap<unknown>>
>;
if (newDeferUsages.length === 0) {
graphqlWrappedResult = executeRootGroupedFieldSet(
exeContext,
operation.operation,
rootType,
rootValue,
groupedFieldSet,
undefined,
);
} else {
const fieldPLan = buildFieldPlan(groupedFieldSet);
groupedFieldSet = fieldPLan.groupedFieldSet;
const newGroupedFieldSets = fieldPLan.newGroupedFieldSets;
const newDeferMap = addNewDeferredFragments(newDeferUsages, new Map());
graphqlWrappedResult = executeRootGroupedFieldSet(
exeContext,
operation.operation,
rootType,
rootValue,
groupedFieldSet,
newDeferMap,
);
if (newGroupedFieldSets.size > 0) {
const newDeferredGroupedFieldSetRecords =
executeDeferredGroupedFieldSets(
exeContext,
rootType,
rootValue,
undefined,
undefined,
newGroupedFieldSets,
newDeferMap,
);
graphqlWrappedResult = withNewDeferredGroupedFieldSets(
graphqlWrappedResult,
newDeferredGroupedFieldSetRecords,
);
}
}
if (isPromise(graphqlWrappedResult)) {
return graphqlWrappedResult.then(
(resolved) => buildDataResponse(exeContext, resolved[0], resolved[1]),
(error) => ({
data: null,
errors: withError(exeContext.errors, error),
}),
);
}
return buildDataResponse(
exeContext,
graphqlWrappedResult[0],
graphqlWrappedResult[1],
);
} catch (error) {
return { data: null, errors: withError(exeContext.errors, error) };
}
}
function withNewDeferredGroupedFieldSets(
result: PromiseOrValue<GraphQLWrappedResult<ObjMap<unknown>>>,
newDeferredGroupedFieldSetRecords: ReadonlyArray<DeferredGroupedFieldSetRecord>,
): PromiseOrValue<GraphQLWrappedResult<ObjMap<unknown>>> {
if (isPromise(result)) {
return result.then((resolved) => {
resolved[1].push(...newDeferredGroupedFieldSetRecords);
return resolved;
});
}
result[1].push(...newDeferredGroupedFieldSetRecords);
return result;
}
function withError(
errors: Array<GraphQLError>,
error: GraphQLError,
): ReadonlyArray<GraphQLError> {
return errors.length === 0 ? [error] : [...errors, error];
}
function buildDataResponse(
exeContext: ExecutionContext,
data: ObjMap<unknown>,
incrementalDataRecords: ReadonlyArray<IncrementalDataRecord>,
): ExecutionResult | ExperimentalIncrementalExecutionResults {
const errors = exeContext.errors;
if (incrementalDataRecords.length === 0) {
return errors.length > 0 ? { errors, data } : { data };
}
return buildIncrementalResponse(
exeContext,
data,
errors,
incrementalDataRecords,
);
}
export function executeSync(args: ExecutionArgs): ExecutionResult {
const result = experimentalExecuteIncrementally(args);
if (isPromise(result) || 'initialResult' in result) {
throw new Error('GraphQL execution failed to complete synchronously.');
}
return result;
}
export function buildExecutionContext(
args: ExecutionArgs,
): ReadonlyArray<GraphQLError> | ExecutionContext {
const {
schema,
document,
rootValue,
contextValue,
variableValues: rawVariableValues,
operationName,
fieldResolver,
typeResolver,
subscribeFieldResolver,
} = args;
assertValidSchema(schema);
let operation: OperationDefinitionNode | undefined;
const fragments: ObjMap<FragmentDefinitionNode> = Object.create(null);
for (const definition of document.definitions) {
switch (definition.kind) {
case Kind.OPERATION_DEFINITION:
if (operationName == null) {
if (operation !== undefined) {
return [
new GraphQLError(
'Must provide operation name if query contains multiple operations.',
),
];
}
operation = definition;
} else if (definition.name?.value === operationName) {
operation = definition;
}
break;
case Kind.FRAGMENT_DEFINITION:
fragments[definition.name.value] = definition;
break;
default:
}
}
if (!operation) {
if (operationName != null) {
return [new GraphQLError(`Unknown operation named "${operationName}".`)];
}
return [new GraphQLError('Must provide an operation.')];
}
const variableDefinitions = operation.variableDefinitions ?? [];
const coercedVariableValues = getVariableValues(
schema,
variableDefinitions,
rawVariableValues ?? {},
{ maxErrors: 50 },
);
if (coercedVariableValues.errors) {
return coercedVariableValues.errors;
}
return {
schema,
fragments,
rootValue,
contextValue,
operation,
variableValues: coercedVariableValues.coerced,
fieldResolver: fieldResolver ?? defaultFieldResolver,
typeResolver: typeResolver ?? defaultTypeResolver,
subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver,
errors: [],
cancellableStreams: new Set(),
};
}
function buildPerEventExecutionContext(
exeContext: ExecutionContext,
payload: unknown,
): ExecutionContext {
return {
...exeContext,
errors: [],
rootValue: payload,
};
}
function executeRootGroupedFieldSet(
exeContext: ExecutionContext,
operation: OperationTypeNode,
rootType: GraphQLObjectType,
rootValue: unknown,
groupedFieldSet: GroupedFieldSet,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLWrappedResult<ObjMap<unknown>>> {
switch (operation) {
case OperationTypeNode.QUERY:
return executeFields(
exeContext,
rootType,
rootValue,
undefined,
groupedFieldSet,
undefined,
deferMap,
);
case OperationTypeNode.MUTATION:
return executeFieldsSerially(
exeContext,
rootType,
rootValue,
undefined,
groupedFieldSet,
undefined,
deferMap,
);
case OperationTypeNode.SUBSCRIPTION:
return executeFields(
exeContext,
rootType,
rootValue,
undefined,
groupedFieldSet,
undefined,
deferMap,
);
}
}
function executeFieldsSerially(
exeContext: ExecutionContext,
parentType: GraphQLObjectType,
sourceValue: unknown,
path: Path | undefined,
groupedFieldSet: GroupedFieldSet,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLWrappedResult<ObjMap<unknown>>> {
return promiseReduce(
groupedFieldSet,
(graphqlWrappedResult, [responseName, fieldGroup]) => {
const fieldPath = addPath(path, responseName, parentType.name);
const result = executeField(
exeContext,
parentType,
sourceValue,
fieldGroup,
fieldPath,
incrementalContext,
deferMap,
);
if (result === undefined) {
return graphqlWrappedResult;
}
if (isPromise(result)) {
return result.then((resolved) => {
graphqlWrappedResult[0][responseName] = resolved[0];
graphqlWrappedResult[1].push(...resolved[1]);
return graphqlWrappedResult;
});
}
graphqlWrappedResult[0][responseName] = result[0];
graphqlWrappedResult[1].push(...result[1]);
return graphqlWrappedResult;
},
[Object.create(null), []] as GraphQLWrappedResult<ObjMap<unknown>>,
);
}
function executeFields(
exeContext: ExecutionContext,
parentType: GraphQLObjectType,
sourceValue: unknown,
path: Path | undefined,
groupedFieldSet: GroupedFieldSet,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLWrappedResult<ObjMap<unknown>>> {
const results = Object.create(null);
const graphqlWrappedResult: GraphQLWrappedResult<ObjMap<unknown>> = [
results,
[],
];
let containsPromise = false;
try {
for (const [responseName, fieldGroup] of groupedFieldSet) {
const fieldPath = addPath(path, responseName, parentType.name);
const result = executeField(
exeContext,
parentType,
sourceValue,
fieldGroup,
fieldPath,
incrementalContext,
deferMap,
);
if (result !== undefined) {
if (isPromise(result)) {
results[responseName] = result.then((resolved) => {
graphqlWrappedResult[1].push(...resolved[1]);
return resolved[0];
});
containsPromise = true;
} else {
results[responseName] = result[0];
graphqlWrappedResult[1].push(...result[1]);
}
}
}
} catch (error) {
if (containsPromise) {
return promiseForObject(results, () => {
}).finally(() => {
throw error;
}) as never;
}
throw error;
}
if (!containsPromise) {
return graphqlWrappedResult;
}
return promiseForObject(results, (resolved) => [
resolved,
graphqlWrappedResult[1],
]);
}
function toNodes(fieldGroup: FieldGroup): ReadonlyArray<FieldNode> {
return fieldGroup.map((fieldDetails) => fieldDetails.node);
}
function executeField(
exeContext: ExecutionContext,
parentType: GraphQLObjectType,
source: unknown,
fieldGroup: FieldGroup,
path: Path,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLWrappedResult<unknown>> | undefined {
const fieldName = fieldGroup[0].node.name.value;
const fieldDef = exeContext.schema.getField(parentType, fieldName);
if (!fieldDef) {
return;
}
const returnType = fieldDef.type;
const resolveFn = fieldDef.resolve ?? exeContext.fieldResolver;
const info = buildResolveInfo(
exeContext,
fieldDef,
toNodes(fieldGroup),
parentType,
path,
);
try {
const args = getArgumentValues(
fieldDef,
fieldGroup[0].node,
exeContext.variableValues,
);
const contextValue = exeContext.contextValue;
const result = resolveFn(source, args, contextValue, info);
if (isPromise(result)) {
return completePromisedValue(
exeContext,
returnType,
fieldGroup,
info,
path,
result,
incrementalContext,
deferMap,
);
}
const completed = completeValue(
exeContext,
returnType,
fieldGroup,
info,
path,
result,
incrementalContext,
deferMap,
);
if (isPromise(completed)) {
return completed.then(undefined, (rawError) => {
const errors = (incrementalContext ?? exeContext).errors;
handleFieldError(rawError, returnType, fieldGroup, path, errors);
return [null, []];
});
}
return completed;
} catch (rawError) {
const errors = (incrementalContext ?? exeContext).errors;
handleFieldError(rawError, returnType, fieldGroup, path, errors);
return [null, []];
}
}
export function buildResolveInfo(
exeContext: ExecutionContext,
fieldDef: GraphQLField<unknown, unknown>,
fieldNodes: ReadonlyArray<FieldNode>,
parentType: GraphQLObjectType,
path: Path,
): GraphQLResolveInfo {
return {
fieldName: fieldDef.name,
fieldNodes,
returnType: fieldDef.type,
parentType,
path,
schema: exeContext.schema,
fragments: exeContext.fragments,
rootValue: exeContext.rootValue,
operation: exeContext.operation,
variableValues: exeContext.variableValues,
};
}
function handleFieldError(
rawError: unknown,
returnType: GraphQLOutputType,
fieldGroup: FieldGroup,
path: Path,
errors: Array<GraphQLError>,
): void {
const error = locatedError(rawError, toNodes(fieldGroup), pathToArray(path));
if (isNonNullType(returnType)) {
throw error;
}
errors.push(error);
}
function completeValue(
exeContext: ExecutionContext,
returnType: GraphQLOutputType,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
path: Path,
result: unknown,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLWrappedResult<unknown>> {
if (result instanceof Error) {
throw result;
}
if (isNonNullType(returnType)) {
const completed = completeValue(
exeContext,
returnType.ofType,
fieldGroup,
info,
path,
result,
incrementalContext,
deferMap,
);
if ((completed as GraphQLWrappedResult<unknown>)[0] === null) {
throw new Error(
`Cannot return null for non-nullable field ${info.parentType.name}.${info.fieldName}.`,
);
}
return completed;
}
if (result == null) {
return [null, []];
}
if (isListType(returnType)) {
return completeListValue(
exeContext,
returnType,
fieldGroup,
info,
path,
result,
incrementalContext,
deferMap,
);
}
if (isLeafType(returnType)) {
return [completeLeafValue(returnType, result), []];
}
if (isAbstractType(returnType)) {
return completeAbstractValue(
exeContext,
returnType,
fieldGroup,
info,
path,
result,
incrementalContext,
deferMap,
);
}
if (isObjectType(returnType)) {
return completeObjectValue(
exeContext,
returnType,
fieldGroup,
info,
path,
result,
incrementalContext,
deferMap,
);
}
invariant(
false,
'Cannot complete value of unexpected output type: ' + inspect(returnType),
);
}
async function completePromisedValue(
exeContext: ExecutionContext,
returnType: GraphQLOutputType,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
path: Path,
result: Promise<unknown>,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): Promise<GraphQLWrappedResult<unknown>> {
try {
const resolved = await result;
let completed = completeValue(
exeContext,
returnType,
fieldGroup,
info,
path,
resolved,
incrementalContext,
deferMap,
);
if (isPromise(completed)) {
completed = await completed;
}
return completed;
} catch (rawError) {
const errors = (incrementalContext ?? exeContext).errors;
handleFieldError(rawError, returnType, fieldGroup, path, errors);
return [null, []];
}
}
function getStreamUsage(
exeContext: ExecutionContext,
fieldGroup: FieldGroup,
path: Path,
): StreamUsage | undefined {
if (typeof path.key === 'number') {
return;
}
if (
(fieldGroup as unknown as { _streamUsage: StreamUsage })._streamUsage !==
undefined
) {
return (fieldGroup as unknown as { _streamUsage: StreamUsage })
._streamUsage;
}
const stream = getDirectiveValues(
GraphQLStreamDirective,
fieldGroup[0].node,
exeContext.variableValues,
);
if (!stream) {
return;
}
if (stream.if === false) {
return;
}
invariant(
typeof stream.initialCount === 'number',
'initialCount must be a number',
);
invariant(
stream.initialCount >= 0,
'initialCount must be a positive integer',
);
invariant(
exeContext.operation.operation !== OperationTypeNode.SUBSCRIPTION,
'`@stream` directive not supported on subscription operations. Disable `@stream` by setting the `if` argument to `false`.',
);
const streamedFieldGroup: FieldGroup = fieldGroup.map((fieldDetails) => ({
node: fieldDetails.node,
deferUsage: undefined,
}));
const streamUsage = {
initialCount: stream.initialCount,
label: typeof stream.label === 'string' ? stream.label : undefined,
fieldGroup: streamedFieldGroup,
};
(fieldGroup as unknown as { _streamUsage: StreamUsage })._streamUsage =
streamUsage;
return streamUsage;
}
async function completeAsyncIteratorValue(
exeContext: ExecutionContext,
itemType: GraphQLOutputType,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
path: Path,
asyncIterator: AsyncIterator<unknown>,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): Promise<GraphQLWrappedResult<ReadonlyArray<unknown>>> {
let containsPromise = false;
const completedResults: Array<unknown> = [];
const graphqlWrappedResult: GraphQLWrappedResult<Array<unknown>> = [
completedResults,
[],
];
let index = 0;
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
while (true) {
if (streamUsage && index >= streamUsage.initialCount) {
const returnFn = asyncIterator.return;
let streamRecord: SubsequentResultRecord | CancellableStreamRecord;
if (returnFn === undefined) {
streamRecord = {
label: streamUsage.label,
path,
} as SubsequentResultRecord;
} else {
streamRecord = {
label: streamUsage.label,
path,
earlyReturn: returnFn.bind(asyncIterator),
};
exeContext.cancellableStreams.add(streamRecord);
}
const firstStreamItems = firstAsyncStreamItems(
streamRecord,
path,
index,
asyncIterator,
exeContext,
streamUsage.fieldGroup,
info,
itemType,
);
graphqlWrappedResult[1].push(firstStreamItems);
break;
}
const itemPath = addPath(path, index, undefined);
let iteration;
try {
iteration = await asyncIterator.next();
} catch (rawError) {
throw locatedError(rawError, toNodes(fieldGroup), pathToArray(path));
}
if (iteration.done) {
break;
}
const item = iteration.value;
if (isPromise(item)) {
completedResults.push(
completePromisedListItemValue(
item,
graphqlWrappedResult,
exeContext,
itemType,
fieldGroup,
info,
itemPath,
incrementalContext,
deferMap,
),
);
containsPromise = true;
} else if (
completeListItemValue(
item,
completedResults,
graphqlWrappedResult,
exeContext,
itemType,
fieldGroup,
info,
itemPath,
incrementalContext,
deferMap,
)
) {
containsPromise = true;
}
index++;
}
return containsPromise
? Promise.all(completedResults).then((resolved) => [
resolved,
graphqlWrappedResult[1],
])
: graphqlWrappedResult;
}
function completeListValue(
exeContext: ExecutionContext,
returnType: GraphQLList<GraphQLOutputType>,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
path: Path,
result: unknown,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLWrappedResult<ReadonlyArray<unknown>>> {
const itemType = returnType.ofType;
if (isAsyncIterable(result)) {
const asyncIterator = result[Symbol.asyncIterator]();
return completeAsyncIteratorValue(
exeContext,
itemType,
fieldGroup,
info,
path,
asyncIterator,
incrementalContext,
deferMap,
);
}
if (!isIterableObject(result)) {
throw new GraphQLError(
`Expected Iterable, but did not find one for field "${info.parentType.name}.${info.fieldName}".`,
);
}
return completeIterableValue(
exeContext,
itemType,
fieldGroup,
info,
path,
result,
incrementalContext,
deferMap,
);
}
function completeIterableValue(
exeContext: ExecutionContext,
itemType: GraphQLOutputType,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
path: Path,
items: Iterable<unknown>,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLWrappedResult<ReadonlyArray<unknown>>> {
let containsPromise = false;
const completedResults: Array<unknown> = [];
const graphqlWrappedResult: GraphQLWrappedResult<Array<unknown>> = [
completedResults,
[],
];
let index = 0;
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
const iterator = items[Symbol.iterator]();
let iteration = iterator.next();
while (!iteration.done) {
const item = iteration.value;
if (streamUsage && index >= streamUsage.initialCount) {
const streamRecord: SubsequentResultRecord = {
label: streamUsage.label,
path,
};
const firstStreamItems = firstSyncStreamItems(
streamRecord,
item,
index,
iterator,
exeContext,
streamUsage.fieldGroup,
info,
itemType,
);
graphqlWrappedResult[1].push(firstStreamItems);
break;
}
const itemPath = addPath(path, index, undefined);
if (isPromise(item)) {
completedResults.push(
completePromisedListItemValue(
item,
graphqlWrappedResult,
exeContext,
itemType,
fieldGroup,
info,
itemPath,
incrementalContext,
deferMap,
),
);
containsPromise = true;
} else if (
completeListItemValue(
item,
completedResults,
graphqlWrappedResult,
exeContext,
itemType,
fieldGroup,
info,
itemPath,
incrementalContext,
deferMap,
)
) {
containsPromise = true;
}
index++;
iteration = iterator.next();
}
return containsPromise
? Promise.all(completedResults).then((resolved) => [
resolved,
graphqlWrappedResult[1],
])
: graphqlWrappedResult;
}
function completeListItemValue(
item: unknown,
completedResults: Array<unknown>,
parent: GraphQLWrappedResult<Array<unknown>>,
exeContext: ExecutionContext,
itemType: GraphQLOutputType,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
itemPath: Path,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): boolean {
try {
const completedItem = completeValue(
exeContext,
itemType,
fieldGroup,
info,
itemPath,
item,
incrementalContext,
deferMap,
);
if (isPromise(completedItem)) {
completedResults.push(
completedItem.then(
(resolved) => {
parent[1].push(...resolved[1]);
return resolved[0];
},
(rawError) => {
const errors = (incrementalContext ?? exeContext).errors;
handleFieldError(rawError, itemType, fieldGroup, itemPath, errors);
return null;
},
),
);
return true;
}
completedResults.push(completedItem[0]);
parent[1].push(...completedItem[1]);
} catch (rawError) {
const errors = (incrementalContext ?? exeContext).errors;
handleFieldError(rawError, itemType, fieldGroup, itemPath, errors);
completedResults.push(null);
}
return false;
}
async function completePromisedListItemValue(
item: unknown,
parent: GraphQLWrappedResult<Array<unknown>>,
exeContext: ExecutionContext,
itemType: GraphQLOutputType,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
itemPath: Path,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): Promise<unknown> {
try {
const resolved = await item;
let completed = completeValue(
exeContext,
itemType,
fieldGroup,
info,
itemPath,
resolved,
incrementalContext,
deferMap,
);
if (isPromise(completed)) {
completed = await completed;
}
parent[1].push(...completed[1]);
return completed[0];
} catch (rawError) {
const errors = (incrementalContext ?? exeContext).errors;
handleFieldError(rawError, itemType, fieldGroup, itemPath, errors);
return null;
}
}
function completeLeafValue(
returnType: GraphQLLeafType,
result: unknown,
): unknown {
const serializedResult = returnType.serialize(result);
if (serializedResult == null) {
throw new Error(
`Expected \`${inspect(returnType)}.serialize(${inspect(result)})\` to ` +
`return non-nullable value, returned: ${inspect(serializedResult)}`,
);
}
return serializedResult;
}
function completeAbstractValue(
exeContext: ExecutionContext,
returnType: GraphQLAbstractType,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
path: Path,
result: unknown,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLWrappedResult<ObjMap<unknown>>> {
const resolveTypeFn = returnType.resolveType ?? exeContext.typeResolver;
const contextValue = exeContext.contextValue;
const runtimeType = resolveTypeFn(result, contextValue, info, returnType);
if (isPromise(runtimeType)) {
return runtimeType.then((resolvedRuntimeType) =>
completeObjectValue(
exeContext,
ensureValidRuntimeType(
resolvedRuntimeType,
exeContext,
returnType,
fieldGroup,
info,
result,
),
fieldGroup,
info,
path,
result,
incrementalContext,
deferMap,
),
);
}
return completeObjectValue(
exeContext,
ensureValidRuntimeType(
runtimeType,
exeContext,
returnType,
fieldGroup,
info,
result,
),
fieldGroup,
info,
path,
result,
incrementalContext,
deferMap,
);
}
function ensureValidRuntimeType(
runtimeTypeName: unknown,
exeContext: ExecutionContext,
returnType: GraphQLAbstractType,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
result: unknown,
): GraphQLObjectType {
if (runtimeTypeName == null) {
throw new GraphQLError(
`Abstract type "${returnType.name}" must resolve to an Object type at runtime for field "${info.parentType.name}.${info.fieldName}". Either the "${returnType.name}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`,
{ nodes: toNodes(fieldGroup) },
);
}
if (isObjectType(runtimeTypeName)) {
throw new GraphQLError(
'Support for returning GraphQLObjectType from resolveType was removed in graphql-js@16.0.0 please return type name instead.',
);
}
if (typeof runtimeTypeName !== 'string') {
throw new GraphQLError(
`Abstract type "${returnType.name}" must resolve to an Object type at runtime for field "${info.parentType.name}.${info.fieldName}" with ` +
`value ${inspect(result)}, received "${inspect(runtimeTypeName)}".`,
);
}
const runtimeType = exeContext.schema.getType(runtimeTypeName);
if (runtimeType == null) {
throw new GraphQLError(
`Abstract type "${returnType.name}" was resolved to a type "${runtimeTypeName}" that does not exist inside the schema.`,
{ nodes: toNodes(fieldGroup) },
);
}
if (!isObjectType(runtimeType)) {
throw new GraphQLError(
`Abstract type "${returnType.name}" was resolved to a non-object type "${runtimeTypeName}".`,
{ nodes: toNodes(fieldGroup) },
);
}
if (!exeContext.schema.isSubType(returnType, runtimeType)) {
throw new GraphQLError(
`Runtime Object type "${runtimeType.name}" is not a possible type for "${returnType.name}".`,
{ nodes: toNodes(fieldGroup) },
);
}
return runtimeType;
}
function completeObjectValue(
exeContext: ExecutionContext,
returnType: GraphQLObjectType,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
path: Path,
result: unknown,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLWrappedResult<ObjMap<unknown>>> {
if (returnType.isTypeOf) {
const isTypeOf = returnType.isTypeOf(result, exeContext.contextValue, info);
if (isPromise(isTypeOf)) {
return isTypeOf.then((resolvedIsTypeOf) => {
if (!resolvedIsTypeOf) {
throw invalidReturnTypeError(returnType, result, fieldGroup);
}
return collectAndExecuteSubfields(
exeContext,
returnType,
fieldGroup,
path,
result,
incrementalContext,
deferMap,
);
});
}
if (!isTypeOf) {
throw invalidReturnTypeError(returnType, result, fieldGroup);
}
}
return collectAndExecuteSubfields(
exeContext,
returnType,
fieldGroup,
path,
result,
incrementalContext,
deferMap,
);
}
function invalidReturnTypeError(
returnType: GraphQLObjectType,
result: unknown,
fieldGroup: FieldGroup,
): GraphQLError {
return new GraphQLError(
`Expected value of type "${returnType.name}" but got: ${inspect(result)}.`,
{ nodes: toNodes(fieldGroup) },
);
}
function addNewDeferredFragments(
newDeferUsages: ReadonlyArray<DeferUsage>,
newDeferMap: Map<DeferUsage, DeferredFragmentRecord>,
path?: Path | undefined,
): ReadonlyMap<DeferUsage, DeferredFragmentRecord> {
for (const newDeferUsage of newDeferUsages) {
const parentDeferUsage = newDeferUsage.parentDeferUsage;
const parent =
parentDeferUsage === undefined
? undefined
: deferredFragmentRecordFromDeferUsage(parentDeferUsage, newDeferMap);
const deferredFragmentRecord = new DeferredFragmentRecord({
path,
label: newDeferUsage.label,
parent,
});
newDeferMap.set(newDeferUsage, deferredFragmentRecord);
}
return newDeferMap;
}
function deferredFragmentRecordFromDeferUsage(
deferUsage: DeferUsage,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord>,
): DeferredFragmentRecord {
return deferMap.get(deferUsage)!;
}
function collectAndExecuteSubfields(
exeContext: ExecutionContext,
returnType: GraphQLObjectType,
fieldGroup: FieldGroup,
path: Path,
result: unknown,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLWrappedResult<ObjMap<unknown>>> {
const collectedSubfields = collectSubfields(
exeContext,
returnType,
fieldGroup,
);
let groupedFieldSet = collectedSubfields.groupedFieldSet;
const newDeferUsages = collectedSubfields.newDeferUsages;
if (deferMap === undefined && newDeferUsages.length === 0) {
return executeFields(
exeContext,
returnType,
result,
path,
groupedFieldSet,
incrementalContext,
undefined,
);
}
const subFieldPlan = buildSubFieldPlan(
groupedFieldSet,
incrementalContext?.deferUsageSet,
);
groupedFieldSet = subFieldPlan.groupedFieldSet;
const newGroupedFieldSets = subFieldPlan.newGroupedFieldSets;
const newDeferMap = addNewDeferredFragments(
newDeferUsages,
new Map(deferMap),
path,
);
const subFields = executeFields(
exeContext,
returnType,
result,
path,
groupedFieldSet,
incrementalContext,
newDeferMap,
);
if (newGroupedFieldSets.size > 0) {
const newDeferredGroupedFieldSetRecords = executeDeferredGroupedFieldSets(
exeContext,
returnType,
result,
path,
incrementalContext?.deferUsageSet,
newGroupedFieldSets,
newDeferMap,
);
return withNewDeferredGroupedFieldSets(
subFields,
newDeferredGroupedFieldSetRecords,
);
}
return subFields;
}
function buildSubFieldPlan(
originalGroupedFieldSet: GroupedFieldSet,
deferUsageSet: DeferUsageSet | undefined,
): FieldPlan {
let fieldPlan = (
originalGroupedFieldSet as unknown as { _fieldPlan: FieldPlan }
)._fieldPlan;
if (fieldPlan !== undefined) {
return fieldPlan;
}
fieldPlan = buildFieldPlan(originalGroupedFieldSet, deferUsageSet);
(originalGroupedFieldSet as unknown as { _fieldPlan: FieldPlan })._fieldPlan =
fieldPlan;
return fieldPlan;
}
export const defaultTypeResolver: GraphQLTypeResolver<unknown, unknown> =
function (value, contextValue, info, abstractType) {
if (isObjectLike(value) && typeof value.__typename === 'string') {
return value.__typename;
}
const possibleTypes = info.schema.getPossibleTypes(abstractType);
const promisedIsTypeOfResults = [];
for (let i = 0; i < possibleTypes.length; i++) {
const type = possibleTypes[i];
if (type.isTypeOf) {
const isTypeOfResult = type.isTypeOf(value, contextValue, info);
if (isPromise(isTypeOfResult)) {
promisedIsTypeOfResults[i] = isTypeOfResult;
} else if (isTypeOfResult) {
return type.name;
}
}
}
if (promisedIsTypeOfResults.length) {
return Promise.all(promisedIsTypeOfResults).then((isTypeOfResults) => {
for (let i = 0; i < isTypeOfResults.length; i++) {
if (isTypeOfResults[i]) {
return possibleTypes[i].name;
}
}
});
}
};
export const defaultFieldResolver: GraphQLFieldResolver<unknown, unknown> =
function (source: any, args, contextValue, info) {
if (isObjectLike(source) || typeof source === 'function') {
const property = source[info.fieldName];
if (typeof property === 'function') {
return source[info.fieldName](args, contextValue, info);
}
return property;
}
};
export function subscribe(
args: ExecutionArgs,
): PromiseOrValue<
AsyncGenerator<ExecutionResult, void, void> | ExecutionResult
> {
const exeContext = buildExecutionContext(args);
if (!('schema' in exeContext)) {
return { errors: exeContext };
}
const resultOrStream = createSourceEventStreamImpl(exeContext);
if (isPromise(resultOrStream)) {
return resultOrStream.then((resolvedResultOrStream) =>
mapSourceToResponse(exeContext, resolvedResultOrStream),
);
}
return mapSourceToResponse(exeContext, resultOrStream);
}
function mapSourceToResponse(
exeContext: ExecutionContext,
resultOrStream: ExecutionResult | AsyncIterable<unknown>,
): AsyncGenerator<ExecutionResult, void, void> | ExecutionResult {
if (!isAsyncIterable(resultOrStream)) {
return resultOrStream;
}
return mapAsyncIterable(
resultOrStream,
(payload: unknown) =>
executeOperation(
buildPerEventExecutionContext(exeContext, payload),
) as ExecutionResult,
);
}
export function createSourceEventStream(
args: ExecutionArgs,
): PromiseOrValue<AsyncIterable<unknown> | ExecutionResult> {
const exeContext = buildExecutionContext(args);
if (!('schema' in exeContext)) {
return { errors: exeContext };
}
return createSourceEventStreamImpl(exeContext);
}
function createSourceEventStreamImpl(
exeContext: ExecutionContext,
): PromiseOrValue<AsyncIterable<unknown> | ExecutionResult> {
try {
const eventStream = executeSubscription(exeContext);
if (isPromise(eventStream)) {
return eventStream.then(undefined, (error) => ({ errors: [error] }));
}
return eventStream;
} catch (error) {
return { errors: [error] };
}
}
function executeSubscription(
exeContext: ExecutionContext,
): PromiseOrValue<AsyncIterable<unknown>> {
const { schema, fragments, operation, variableValues, rootValue } =
exeContext;
const rootType = schema.getSubscriptionType();
if (rootType == null) {
throw new GraphQLError(
'Schema is not configured to execute subscription operation.',
{ nodes: operation },
);
}
const { groupedFieldSet } = collectFields(
schema,
fragments,
variableValues,
rootType,
operation,
);
const firstRootField = groupedFieldSet.entries().next().value as [
string,
FieldGroup,
];
const [responseName, fieldGroup] = firstRootField;
const fieldName = fieldGroup[0].node.name.value;
const fieldDef = schema.getField(rootType, fieldName);
const fieldNodes = fieldGroup.map((fieldDetails) => fieldDetails.node);
if (!fieldDef) {
throw new GraphQLError(
`The subscription field "${fieldName}" is not defined.`,
{ nodes: fieldNodes },
);
}
const path = addPath(undefined, responseName, rootType.name);
const info = buildResolveInfo(
exeContext,
fieldDef,
fieldNodes,
rootType,
path,
);
try {
const args = getArgumentValues(fieldDef, fieldNodes[0], variableValues);
const contextValue = exeContext.contextValue;
const resolveFn = fieldDef.subscribe ?? exeContext.subscribeFieldResolver;
const result = resolveFn(rootValue, args, contextValue, info);
if (isPromise(result)) {
return result.then(assertEventStream).then(undefined, (error) => {
throw locatedError(error, fieldNodes, pathToArray(path));
});
}
return assertEventStream(result);
} catch (error) {
throw locatedError(error, fieldNodes, pathToArray(path));
}
}
function assertEventStream(result: unknown): AsyncIterable<unknown> {
if (result instanceof Error) {
throw result;
}
if (!isAsyncIterable(result)) {
throw new GraphQLError(
'Subscription field must return Async Iterable. ' +
`Received: ${inspect(result)}.`,
);
}
return result;
}
function executeDeferredGroupedFieldSets(
exeContext: ExecutionContext,
parentType: GraphQLObjectType,
sourceValue: unknown,
path: Path | undefined,
parentDeferUsages: DeferUsageSet | undefined,
newGroupedFieldSets: Map<DeferUsageSet, GroupedFieldSet>,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord>,
): ReadonlyArray<DeferredGroupedFieldSetRecord> {
const newDeferredGroupedFieldSetRecords: Array<DeferredGroupedFieldSetRecord> =
[];
for (const [deferUsageSet, groupedFieldSet] of newGroupedFieldSets) {
const deferredFragmentRecords = getDeferredFragmentRecords(
deferUsageSet,
deferMap,
);
const executor = () =>
executeDeferredGroupedFieldSet(
deferredFragmentRecords,
exeContext,
parentType,
sourceValue,
path,
groupedFieldSet,
{
errors: [],
deferUsageSet,
},
deferMap,
);
const deferredGroupedFieldSetRecord: DeferredGroupedFieldSetRecord = {
deferredFragmentRecords,
result: shouldDefer(parentDeferUsages, deferUsageSet)
? Promise.resolve().then(executor)
: executor(),
};
newDeferredGroupedFieldSetRecords.push(deferredGroupedFieldSetRecord);
}
return newDeferredGroupedFieldSetRecords;
}
function shouldDefer(
parentDeferUsages: undefined | DeferUsageSet,
deferUsages: DeferUsageSet,
): boolean {
return (
parentDeferUsages === undefined ||
!Array.from(deferUsages).every((deferUsage) =>
parentDeferUsages.has(deferUsage),
)
);
}
function executeDeferredGroupedFieldSet(
deferredFragmentRecords: ReadonlyArray<DeferredFragmentRecord>,
exeContext: ExecutionContext,
parentType: GraphQLObjectType,
sourceValue: unknown,
path: Path | undefined,
groupedFieldSet: GroupedFieldSet,
incrementalContext: IncrementalContext,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord>,
): PromiseOrValue<DeferredGroupedFieldSetResult> {
let result;
try {
result = executeFields(
exeContext,
parentType,
sourceValue,
path,
groupedFieldSet,
incrementalContext,
deferMap,
);
} catch (error) {
return {
deferredFragmentRecords,
path: pathToArray(path),
errors: withError(incrementalContext.errors, error),
};
}
if (isPromise(result)) {
return result.then(
(resolved) =>
buildDeferredGroupedFieldSetResult(
incrementalContext.errors,
deferredFragmentRecords,
path,
resolved,
),
(error) => ({
deferredFragmentRecords,
path: pathToArray(path),
errors: withError(incrementalContext.errors, error),
}),
);
}
return buildDeferredGroupedFieldSetResult(
incrementalContext.errors,
deferredFragmentRecords,
path,
result,
);
}
function buildDeferredGroupedFieldSetResult(
errors: ReadonlyArray<GraphQLError>,
deferredFragmentRecords: ReadonlyArray<DeferredFragmentRecord>,
path: Path | undefined,
result: GraphQLWrappedResult<ObjMap<unknown>>,
): DeferredGroupedFieldSetResult {
return {
deferredFragmentRecords,
path: pathToArray(path),
result:
errors.length === 0 ? { data: result[0] } : { data: result[0], errors },
incrementalDataRecords: result[1],
};
}
function getDeferredFragmentRecords(
deferUsages: DeferUsageSet,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord>,
): ReadonlyArray<DeferredFragmentRecord> {
return Array.from(deferUsages).map((deferUsage) =>
deferredFragmentRecordFromDeferUsage(deferUsage, deferMap),
);
}
function firstSyncStreamItems(
streamRecord: SubsequentResultRecord,
initialItem: PromiseOrValue<unknown>,
initialIndex: number,
iterator: Iterator<unknown>,
exeContext: ExecutionContext,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
itemType: GraphQLOutputType,
): StreamItemsRecord {
const path = streamRecord.path;
const initialPath = addPath(path, initialIndex, undefined);
const firstStreamItems: StreamItemsRecord = {
streamRecord,
result: Promise.resolve().then(() => {
let result = completeStreamItems(
streamRecord,
initialPath,
initialItem,
exeContext,
{ errors: [] },
fieldGroup,
info,
itemType,
);
const results = [result];
let currentIndex = initialIndex;
let iteration = iterator.next();
let erroredSynchronously = false;
while (!iteration.done) {
if (!isPromise(result) && !isReconcilableStreamItemsResult(result)) {
erroredSynchronously = true;
break;
}
const item = iteration.value;
currentIndex++;
const currentPath = addPath(path, currentIndex, undefined);
result = completeStreamItems(
streamRecord,
currentPath,
item,
exeContext,
{ errors: [] },
fieldGroup,
info,
itemType,
);
results.push(result);
iteration = iterator.next();
}
currentIndex = results.length - 1;
let currentResult = erroredSynchronously
? results[currentIndex]
: prependNextStreamItems(results[currentIndex], {
streamRecord,
result: { streamRecord },
});
while (currentIndex-- > 0) {
currentResult = prependNextStreamItems(results[currentIndex], {
streamRecord,
result: currentResult,
});
}
return currentResult;
}),
};
return firstStreamItems;
}
function prependNextStreamItems(
result: PromiseOrValue<StreamItemsResult>,
nextStreamItems: StreamItemsRecord,
): PromiseOrValue<StreamItemsResult> {
if (isPromise(result)) {
return result.then((resolved) =>
prependNextResolvedStreamItems(resolved, nextStreamItems),
);
}
return prependNextResolvedStreamItems(result, nextStreamItems);
}
function prependNextResolvedStreamItems(
result: StreamItemsResult,
nextStreamItems: StreamItemsRecord,
): StreamItemsResult {
return isReconcilableStreamItemsResult(result)
? {
...result,
incrementalDataRecords: [
nextStreamItems,
...result.incrementalDataRecords,
],
}
: result;
}
function firstAsyncStreamItems(
streamRecord: SubsequentResultRecord,
path: Path,
initialIndex: number,
asyncIterator: AsyncIterator<unknown>,
exeContext: ExecutionContext,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
itemType: GraphQLOutputType,
): StreamItemsRecord {
const firstStreamItems: StreamItemsRecord = {
streamRecord,
result: getNextAsyncStreamItemsResult(
streamRecord,
path,
initialIndex,
asyncIterator,
exeContext,
fieldGroup,
info,
itemType,
),
};
return firstStreamItems;
}
async function getNextAsyncStreamItemsResult(
streamRecord: SubsequentResultRecord,
path: Path,
index: number,
asyncIterator: AsyncIterator<unknown>,
exeContext: ExecutionContext,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
itemType: GraphQLOutputType,
): Promise<StreamItemsResult> {
let iteration;
try {
iteration = await asyncIterator.next();
} catch (error) {
return {
streamRecord,
errors: [locatedError(error, toNodes(fieldGroup), pathToArray(path))],
};
}
if (iteration.done) {
return { streamRecord };
}
const itemPath = addPath(path, index, undefined);
const result = completeStreamItems(
streamRecord,
itemPath,
iteration.value,
exeContext,
{ errors: [] },
fieldGroup,
info,
itemType,
);
const nextStreamItems: StreamItemsRecord = {
streamRecord,
result: getNextAsyncStreamItemsResult(
streamRecord,
path,
index,
asyncIterator,
exeContext,
fieldGroup,
info,
itemType,
),
};
return prependNextStreamItems(result, nextStreamItems);
}
function completeStreamItems(
streamRecord: SubsequentResultRecord,
itemPath: Path,
item: unknown,
exeContext: ExecutionContext,
incrementalContext: IncrementalContext,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
itemType: GraphQLOutputType,
): PromiseOrValue<StreamItemsResult> {
if (isPromise(item)) {
return completePromisedValue(
exeContext,
itemType,
fieldGroup,
info,
itemPath,
item,
incrementalContext,
new Map(),
).then(
(resolvedItem) =>
buildStreamItemsResult(
incrementalContext.errors,
streamRecord,
resolvedItem,
),
(error) => ({
streamRecord,
errors: withError(incrementalContext.errors, error),
}),
);
}
let result: PromiseOrValue<GraphQLWrappedResult<unknown>>;
try {
try {
result = completeValue(
exeContext,
itemType,
fieldGroup,
info,
itemPath,
item,
incrementalContext,
new Map(),
);
} catch (rawError) {
handleFieldError(
rawError,
itemType,
fieldGroup,
itemPath,
incrementalContext.errors,
);
result = [null, []];
}
} catch (error) {
return {
streamRecord,
errors: withError(incrementalContext.errors, error),
};
}
if (isPromise(result)) {
return result
.then(undefined, (rawError) => {
handleFieldError(
rawError,
itemType,
fieldGroup,
itemPath,
incrementalContext.errors,
);
return [null, []] as GraphQLWrappedResult<unknown>;
})
.then(
(resolvedItem) =>
buildStreamItemsResult(
incrementalContext.errors,
streamRecord,
resolvedItem,
),
(error) => ({
streamRecord,
errors: withError(incrementalContext.errors, error),
}),
);
}
return buildStreamItemsResult(
incrementalContext.errors,
streamRecord,
result,
);
}
function buildStreamItemsResult(
errors: ReadonlyArray<GraphQLError>,
streamRecord: SubsequentResultRecord,
result: GraphQLWrappedResult<unknown>,
): StreamItemsResult {
return {
streamRecord,
result:
errors.length === 0
? { items: [result[0]] }
: {
items: [result[0]],
errors: [...errors],
},
incrementalDataRecords: result[1],
};
}