@graphql-tools/executor
Advanced tools
Comparing version 0.0.2-alpha-20221027131523-6d0f4d51 to 0.0.2-alpha-20221029152711-14f4f7a7
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getFieldDef = exports.createSourceEventStream = exports.subscribe = exports.defaultFieldResolver = exports.defaultTypeResolver = exports.buildResolveInfo = exports.buildExecutionContext = exports.assertValidExecutionArguments = exports.executeSync = exports.execute = void 0; | ||
exports.getFieldDef = exports.createSourceEventStream = exports.experimentalSubscribeIncrementally = exports.subscribe = exports.defaultFieldResolver = exports.defaultTypeResolver = exports.buildResolveInfo = exports.buildExecutionContext = exports.assertValidExecutionArguments = exports.executeSync = exports.experimentalExecuteIncrementally = exports.execute = void 0; | ||
const graphql_1 = require("graphql"); | ||
const utils_1 = require("@graphql-tools/utils"); | ||
const values_js_1 = require("./values.js"); | ||
const promiseForObject_js_1 = require("./promiseForObject.js"); | ||
const flattenAsyncIterable_js_1 = require("./flattenAsyncIterable.js"); | ||
const collectFields_js_1 = require("./collectFields.js"); | ||
const index_js_1 = require("../directives/index.js"); | ||
const invariant_js_1 = require("./invariant.js"); | ||
/** | ||
* A memoized collection of relevant subfields with regard to the return | ||
* type. Memoizing ensures the subfields are not repeatedly calculated, which | ||
* saves overhead when resolving lists of values. | ||
*/ | ||
const collectSubfields = (0, utils_1.memoize3)((exeContext, returnType, fieldNodes) => (0, collectFields_js_1.collectSubfields)(exeContext.schema, exeContext.fragments, exeContext.variableValues, returnType, fieldNodes)); | ||
const UNEXPECTED_MULTIPLE_PAYLOADS = 'Executing this GraphQL operation would unexpectedly produce multiple payloads (due to @defer or @stream directive)'; | ||
/** | ||
* Implements the "Executing requests" section of the GraphQL specification. | ||
@@ -16,4 +28,40 @@ * | ||
* a GraphQLError will be thrown immediately explaining the invalid input. | ||
* | ||
* This function does not support incremental delivery (`@defer` and `@stream`). | ||
* If an operation which would defer or stream data is executed with this | ||
* function, it will throw or resolve to an object containing an error instead. | ||
* Use `experimentalExecuteIncrementally` if you want to support incremental | ||
* delivery. | ||
*/ | ||
function execute(args) { | ||
const result = experimentalExecuteIncrementally(args); | ||
if (!(0, utils_1.isPromise)(result)) { | ||
if ('initialResult' in result) { | ||
throw new Error(UNEXPECTED_MULTIPLE_PAYLOADS); | ||
} | ||
return result; | ||
} | ||
return result.then(incrementalResult => { | ||
if ('initialResult' in incrementalResult) { | ||
return { | ||
errors: [(0, utils_1.createGraphQLError)(UNEXPECTED_MULTIPLE_PAYLOADS)], | ||
}; | ||
} | ||
return incrementalResult; | ||
}); | ||
} | ||
exports.execute = execute; | ||
/** | ||
* Implements the "Executing requests" section of the GraphQL specification, | ||
* including `@defer` and `@stream` as proposed in | ||
* https://github.com/graphql/graphql-spec/pull/742 | ||
* | ||
* This function returns a Promise of an ExperimentalIncrementalExecutionResults | ||
* object. This object either consists of a single ExecutionResult, or an | ||
* object containing an `initialResult` and a stream of `subsequentResults`. | ||
* | ||
* If the arguments to this function do not result in a legal execution context, | ||
* a GraphQLError will be thrown immediately explaining the invalid input. | ||
*/ | ||
function experimentalExecuteIncrementally(args) { | ||
// If a valid execution context cannot be created due to incorrect arguments, | ||
@@ -28,3 +76,3 @@ // a "Response" with only errors is returned. | ||
} | ||
exports.execute = execute; | ||
exports.experimentalExecuteIncrementally = experimentalExecuteIncrementally; | ||
function executeImpl(exeContext) { | ||
@@ -45,3 +93,15 @@ // Return a Promise that will eventually resolve to the data described by | ||
if ((0, utils_1.isPromise)(result)) { | ||
return result.then(data => buildResponse(data, exeContext.errors), error => { | ||
return result.then(data => { | ||
const initialResult = buildResponse(data, exeContext.errors); | ||
if (exeContext.subsequentPayloads.size > 0) { | ||
return { | ||
initialResult: { | ||
...initialResult, | ||
hasNext: true, | ||
}, | ||
subsequentResults: yieldSubsequentPayloads(exeContext), | ||
}; | ||
} | ||
return initialResult; | ||
}, error => { | ||
exeContext.errors.push(error); | ||
@@ -51,3 +111,13 @@ return buildResponse(null, exeContext.errors); | ||
} | ||
return buildResponse(result, exeContext.errors); | ||
const initialResult = buildResponse(result, exeContext.errors); | ||
if (exeContext.subsequentPayloads.size > 0) { | ||
return { | ||
initialResult: { | ||
...initialResult, | ||
hasNext: true, | ||
}, | ||
subsequentResults: yieldSubsequentPayloads(exeContext), | ||
}; | ||
} | ||
return initialResult; | ||
} | ||
@@ -65,5 +135,5 @@ catch (error) { | ||
function executeSync(args) { | ||
const result = execute(args); | ||
const result = experimentalExecuteIncrementally(args); | ||
// Assert that the execution was synchronous. | ||
if ((0, utils_1.isPromise)(result)) { | ||
if ((0, utils_1.isPromise)(result) || 'initialResult' in result) { | ||
throw new Error('GraphQL execution failed to complete synchronously.'); | ||
@@ -156,2 +226,3 @@ } | ||
subscribeFieldResolver: subscribeFieldResolver !== null && subscribeFieldResolver !== void 0 ? subscribeFieldResolver : exports.defaultFieldResolver, | ||
subsequentPayloads: new Set(), | ||
errors: [], | ||
@@ -165,2 +236,3 @@ }; | ||
rootValue: payload, | ||
subsequentPayloads: new Set(), | ||
errors: [], | ||
@@ -175,15 +247,21 @@ }; | ||
const rootType = (0, utils_1.getDefinedRootType)(schema, operation.operation, [operation]); | ||
const rootFields = (0, utils_1.collectFields)(schema, fragments, variableValues, rootType, operation.selectionSet); | ||
if (rootType == null) { | ||
(0, utils_1.createGraphQLError)(`Schema is not configured to execute ${operation.operation} operation.`, { | ||
nodes: operation, | ||
}); | ||
} | ||
const { fields: rootFields, patches } = (0, collectFields_js_1.collectFields)(schema, fragments, variableValues, rootType, operation.selectionSet); | ||
const path = undefined; | ||
switch (operation.operation) { | ||
case 'query': | ||
return executeFields(exeContext, rootType, rootValue, path, rootFields); | ||
case 'mutation': | ||
return executeFieldsSerially(exeContext, rootType, rootValue, path, rootFields); | ||
case 'subscription': | ||
// TODO: deprecate `subscribe` and move all logic here | ||
// Temporary solution until we finish merging execute and subscribe together | ||
return executeFields(exeContext, rootType, rootValue, path, rootFields); | ||
let result; | ||
if (operation.operation === 'mutation') { | ||
result = executeFieldsSerially(exeContext, rootType, rootValue, path, rootFields); | ||
} | ||
throw new Error(`Can only execute queries, mutations and subscriptions, got "${operation.operation}".`); | ||
else { | ||
result = executeFields(exeContext, rootType, rootValue, path, rootFields); | ||
} | ||
for (const patch of patches) { | ||
const { label, fields: patchFields } = patch; | ||
executeDeferredFragment(exeContext, rootType, rootValue, patchFields, label, path); | ||
} | ||
return result; | ||
} | ||
@@ -195,3 +273,3 @@ /** | ||
function executeFieldsSerially(exeContext, parentType, sourceValue, path, fields) { | ||
return (0, utils_1.promiseReduce)(fields.entries(), (results, [responseName, fieldNodes]) => { | ||
return (0, utils_1.promiseReduce)(fields, (results, [responseName, fieldNodes]) => { | ||
const fieldPath = (0, utils_1.addPath)(path, responseName, parentType.name); | ||
@@ -216,15 +294,26 @@ const result = executeField(exeContext, parentType, sourceValue, fieldNodes, fieldPath); | ||
*/ | ||
function executeFields(exeContext, parentType, sourceValue, path, fields) { | ||
function executeFields(exeContext, parentType, sourceValue, path, fields, asyncPayloadRecord) { | ||
const results = Object.create(null); | ||
let containsPromise = false; | ||
for (const [responseName, fieldNodes] of fields.entries()) { | ||
const fieldPath = (0, utils_1.addPath)(path, responseName, parentType.name); | ||
const result = executeField(exeContext, parentType, sourceValue, fieldNodes, fieldPath); | ||
if (result !== undefined) { | ||
results[responseName] = result; | ||
if ((0, utils_1.isPromise)(result)) { | ||
containsPromise = true; | ||
try { | ||
for (const [responseName, fieldNodes] of fields) { | ||
const fieldPath = (0, utils_1.addPath)(path, responseName, parentType.name); | ||
const result = executeField(exeContext, parentType, sourceValue, fieldNodes, fieldPath, asyncPayloadRecord); | ||
if (result !== undefined) { | ||
results[responseName] = result; | ||
if ((0, utils_1.isPromise)(result)) { | ||
containsPromise = true; | ||
} | ||
} | ||
} | ||
} | ||
catch (error) { | ||
if (containsPromise) { | ||
// Ensure that any promises returned by other fields are handled, as they may also reject. | ||
return (0, promiseForObject_js_1.promiseForObject)(results).finally(() => { | ||
throw error; | ||
}); | ||
} | ||
throw error; | ||
} | ||
// If there are no promises, we can just return the object | ||
@@ -237,9 +326,3 @@ if (!containsPromise) { | ||
// same map, but with any promises replaced with the values they resolved to. | ||
return Promise.all(Object.values(results)).then(resolvedValues => { | ||
const resolvedObject = Object.create(null); | ||
for (const [i, key] of Object.keys(results).entries()) { | ||
resolvedObject[key] = resolvedValues[i]; | ||
} | ||
return resolvedObject; | ||
}); | ||
return (0, promiseForObject_js_1.promiseForObject)(results); | ||
} | ||
@@ -252,4 +335,5 @@ /** | ||
*/ | ||
function executeField(exeContext, parentType, source, fieldNodes, path) { | ||
var _a; | ||
function executeField(exeContext, parentType, source, fieldNodes, path, asyncPayloadRecord) { | ||
var _a, _b; | ||
const errors = (_a = asyncPayloadRecord === null || asyncPayloadRecord === void 0 ? void 0 : asyncPayloadRecord.errors) !== null && _a !== void 0 ? _a : exeContext.errors; | ||
const fieldDef = getFieldDef(exeContext.schema, parentType, fieldNodes[0]); | ||
@@ -260,3 +344,3 @@ if (!fieldDef) { | ||
const returnType = fieldDef.type; | ||
const resolveFn = (_a = fieldDef.resolve) !== null && _a !== void 0 ? _a : exeContext.fieldResolver; | ||
const resolveFn = (_b = fieldDef.resolve) !== null && _b !== void 0 ? _b : exeContext.fieldResolver; | ||
const info = buildResolveInfo(exeContext, fieldDef, fieldNodes, parentType, path); | ||
@@ -276,6 +360,6 @@ // Get the resolve function, regardless of if its result is normal or abrupt (error). | ||
if ((0, utils_1.isPromise)(result)) { | ||
completed = result.then(resolved => completeValue(exeContext, returnType, fieldNodes, info, path, resolved)); | ||
completed = result.then(resolved => completeValue(exeContext, returnType, fieldNodes, info, path, resolved, asyncPayloadRecord)); | ||
} | ||
else { | ||
completed = completeValue(exeContext, returnType, fieldNodes, info, path, result); | ||
completed = completeValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord); | ||
} | ||
@@ -287,3 +371,5 @@ if ((0, utils_1.isPromise)(completed)) { | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(path)); | ||
return handleFieldError(error, returnType, exeContext); | ||
const handledError = handleFieldError(error, returnType, errors); | ||
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); | ||
return handledError; | ||
}); | ||
@@ -295,3 +381,5 @@ } | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(path)); | ||
return handleFieldError(error, returnType, exeContext); | ||
const handledError = handleFieldError(error, returnType, errors); | ||
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); | ||
return handledError; | ||
} | ||
@@ -320,3 +408,3 @@ } | ||
exports.buildResolveInfo = buildResolveInfo; | ||
function handleFieldError(error, returnType, exeContext) { | ||
function handleFieldError(error, returnType, errors) { | ||
// If the field type is non-nullable, then it is resolved without any | ||
@@ -329,3 +417,3 @@ // protection from errors, however it still properly locates the error. | ||
// a null value for this field if one is encountered. | ||
exeContext.errors.push(error); | ||
errors.push(error); | ||
return null; | ||
@@ -354,3 +442,3 @@ } | ||
*/ | ||
function completeValue(exeContext, returnType, fieldNodes, info, path, result) { | ||
function completeValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord) { | ||
// If result is an Error, throw a located error. | ||
@@ -363,3 +451,3 @@ if (result instanceof Error) { | ||
if ((0, graphql_1.isNonNullType)(returnType)) { | ||
const completed = completeValue(exeContext, returnType.ofType, fieldNodes, info, path, result); | ||
const completed = completeValue(exeContext, returnType.ofType, fieldNodes, info, path, result, asyncPayloadRecord); | ||
if (completed === null) { | ||
@@ -376,3 +464,3 @@ throw new Error(`Cannot return null for non-nullable field ${info.parentType.name}.${info.fieldName}.`); | ||
if ((0, graphql_1.isListType)(returnType)) { | ||
return completeListValue(exeContext, returnType, fieldNodes, info, path, result); | ||
return completeListValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord); | ||
} | ||
@@ -387,7 +475,7 @@ // If field type is a leaf type, Scalar or Enum, serialize to a valid value, | ||
if ((0, graphql_1.isAbstractType)(returnType)) { | ||
return completeAbstractValue(exeContext, returnType, fieldNodes, info, path, result); | ||
return completeAbstractValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord); | ||
} | ||
// If field type is Object, execute and complete all sub-selections. | ||
if ((0, graphql_1.isObjectType)(returnType)) { | ||
return completeObjectValue(exeContext, returnType, fieldNodes, info, path, result); | ||
return completeObjectValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord); | ||
} | ||
@@ -399,6 +487,35 @@ /* c8 ignore next 6 */ | ||
/** | ||
* Returns an object containing the `@stream` arguments if a field should be | ||
* streamed based on the experimental flag, stream directive present and | ||
* not disabled by the "if" argument. | ||
*/ | ||
function getStreamValues(exeContext, fieldNodes, path) { | ||
// do not stream inner lists of multi-dimensional lists | ||
if (typeof path.key === 'number') { | ||
return; | ||
} | ||
// validation only allows equivalent streams on multiple fields, so it is | ||
// safe to only check the first fieldNode for the stream directive | ||
const stream = (0, graphql_1.getDirectiveValues)(index_js_1.GraphQLStreamDirective, fieldNodes[0], exeContext.variableValues); | ||
if (!stream) { | ||
return; | ||
} | ||
if (stream['if'] === false) { | ||
return; | ||
} | ||
(0, invariant_js_1.invariant)(typeof stream['initialCount'] === 'number', 'initialCount must be a number'); | ||
(0, invariant_js_1.invariant)(stream['initialCount'] >= 0, 'initialCount must be a positive integer'); | ||
return { | ||
initialCount: stream['initialCount'], | ||
label: typeof stream['label'] === 'string' ? stream['label'] : undefined, | ||
}; | ||
} | ||
/** | ||
* Complete a async iterator value by completing the result and calling | ||
* recursively until all the results are completed. | ||
*/ | ||
async function completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator) { | ||
async function completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator, asyncPayloadRecord) { | ||
var _a; | ||
const errors = (_a = asyncPayloadRecord === null || asyncPayloadRecord === void 0 ? void 0 : asyncPayloadRecord.errors) !== null && _a !== void 0 ? _a : exeContext.errors; | ||
const stream = getStreamValues(exeContext, fieldNodes, path); | ||
let containsPromise = false; | ||
@@ -408,28 +525,22 @@ const completedResults = []; | ||
while (true) { | ||
const fieldPath = (0, utils_1.addPath)(path, index, undefined); | ||
if (stream && typeof stream.initialCount === 'number' && index >= stream.initialCount) { | ||
executeStreamIterator(index, iterator, exeContext, fieldNodes, info, itemType, path, stream.label, asyncPayloadRecord); | ||
break; | ||
} | ||
const itemPath = (0, utils_1.addPath)(path, index, undefined); | ||
let iteration; | ||
try { | ||
const { value, done } = await iterator.next(); | ||
if (done) { | ||
iteration = await iterator.next(); | ||
if (iteration.done) { | ||
break; | ||
} | ||
try { | ||
// TODO can the error checking logic be consolidated with completeListValue? | ||
const completedItem = completeValue(exeContext, itemType, fieldNodes, info, fieldPath, value); | ||
if ((0, utils_1.isPromise)(completedItem)) { | ||
containsPromise = true; | ||
} | ||
completedResults.push(completedItem); | ||
} | ||
catch (rawError) { | ||
completedResults.push(null); | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(fieldPath)); | ||
handleFieldError(error, itemType, exeContext); | ||
} | ||
} | ||
catch (rawError) { | ||
completedResults.push(null); | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(fieldPath)); | ||
handleFieldError(error, itemType, exeContext); | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(itemPath)); | ||
completedResults.push(handleFieldError(error, itemType, errors)); | ||
break; | ||
} | ||
if (completeListItemValue(iteration.value, completedResults, errors, exeContext, itemType, fieldNodes, info, itemPath, asyncPayloadRecord)) { | ||
containsPromise = true; | ||
} | ||
index += 1; | ||
@@ -443,7 +554,9 @@ } | ||
*/ | ||
function completeListValue(exeContext, returnType, fieldNodes, info, path, result) { | ||
function completeListValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord) { | ||
var _a; | ||
const itemType = returnType.ofType; | ||
const errors = (_a = asyncPayloadRecord === null || asyncPayloadRecord === void 0 ? void 0 : asyncPayloadRecord.errors) !== null && _a !== void 0 ? _a : exeContext.errors; | ||
if ((0, utils_1.isAsyncIterable)(result)) { | ||
const iterator = result[Symbol.asyncIterator](); | ||
return completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator); | ||
return completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator, asyncPayloadRecord); | ||
} | ||
@@ -453,36 +566,61 @@ if (!(0, utils_1.isIterableObject)(result)) { | ||
} | ||
const stream = getStreamValues(exeContext, fieldNodes, path); | ||
// This is specified as a simple map, however we're optimizing the path | ||
// where the list contains no Promises by avoiding creating another Promise. | ||
let containsPromise = false; | ||
const completedResults = Array.from(result, (item, index) => { | ||
let previousAsyncPayloadRecord = asyncPayloadRecord; | ||
const completedResults = []; | ||
let index = 0; | ||
for (const item of result) { | ||
// No need to modify the info object containing the path, | ||
// since from here on it is not ever accessed by resolver functions. | ||
const itemPath = (0, utils_1.addPath)(path, index, undefined); | ||
try { | ||
let completedItem; | ||
if ((0, utils_1.isPromise)(item)) { | ||
completedItem = item.then(resolved => completeValue(exeContext, itemType, fieldNodes, info, itemPath, resolved)); | ||
} | ||
else { | ||
completedItem = completeValue(exeContext, itemType, fieldNodes, info, itemPath, item); | ||
} | ||
if ((0, utils_1.isPromise)(completedItem)) { | ||
containsPromise = true; | ||
// Note: we don't rely on a `catch` method, but we do expect "thenable" | ||
// to take a second callback for the error case. | ||
return completedItem.then(undefined, rawError => { | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(itemPath)); | ||
return handleFieldError(error, itemType, exeContext); | ||
}); | ||
} | ||
return completedItem; | ||
if (stream && typeof stream.initialCount === 'number' && index >= stream.initialCount) { | ||
previousAsyncPayloadRecord = executeStreamField(path, itemPath, item, exeContext, fieldNodes, info, itemType, stream.label, previousAsyncPayloadRecord); | ||
index++; | ||
continue; | ||
} | ||
catch (rawError) { | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(itemPath)); | ||
return handleFieldError(error, itemType, exeContext); | ||
if (completeListItemValue(item, completedResults, errors, exeContext, itemType, fieldNodes, info, itemPath, asyncPayloadRecord)) { | ||
containsPromise = true; | ||
} | ||
}); | ||
index++; | ||
} | ||
return containsPromise ? Promise.all(completedResults) : completedResults; | ||
} | ||
/** | ||
* Complete a list item value by adding it to the completed results. | ||
* | ||
* Returns true if the value is a Promise. | ||
*/ | ||
function completeListItemValue(item, completedResults, errors, exeContext, itemType, fieldNodes, info, itemPath, asyncPayloadRecord) { | ||
try { | ||
let completedItem; | ||
if ((0, utils_1.isPromise)(item)) { | ||
completedItem = item.then(resolved => completeValue(exeContext, itemType, fieldNodes, info, itemPath, resolved, asyncPayloadRecord)); | ||
} | ||
else { | ||
completedItem = completeValue(exeContext, itemType, fieldNodes, info, itemPath, item, asyncPayloadRecord); | ||
} | ||
if ((0, utils_1.isPromise)(completedItem)) { | ||
// Note: we don't rely on a `catch` method, but we do expect "thenable" | ||
// to take a second callback for the error case. | ||
completedResults.push(completedItem.then(undefined, rawError => { | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(itemPath)); | ||
const handledError = handleFieldError(error, itemType, errors); | ||
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); | ||
return handledError; | ||
})); | ||
return true; | ||
} | ||
completedResults.push(completedItem); | ||
} | ||
catch (rawError) { | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(itemPath)); | ||
const handledError = handleFieldError(error, itemType, errors); | ||
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); | ||
completedResults.push(handledError); | ||
} | ||
return false; | ||
} | ||
/** | ||
* Complete a Scalar or Enum by serializing to a valid value, returning | ||
@@ -503,3 +641,3 @@ * null if serialization is not possible. | ||
*/ | ||
function completeAbstractValue(exeContext, returnType, fieldNodes, info, path, result) { | ||
function completeAbstractValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord) { | ||
var _a; | ||
@@ -510,5 +648,5 @@ const resolveTypeFn = (_a = returnType.resolveType) !== null && _a !== void 0 ? _a : exeContext.typeResolver; | ||
if ((0, utils_1.isPromise)(runtimeType)) { | ||
return runtimeType.then(resolvedRuntimeType => completeObjectValue(exeContext, ensureValidRuntimeType(resolvedRuntimeType, exeContext, returnType, fieldNodes, info, result), fieldNodes, info, path, result)); | ||
return runtimeType.then(resolvedRuntimeType => completeObjectValue(exeContext, ensureValidRuntimeType(resolvedRuntimeType, exeContext, returnType, fieldNodes, info, result), fieldNodes, info, path, result, asyncPayloadRecord)); | ||
} | ||
return completeObjectValue(exeContext, ensureValidRuntimeType(runtimeType, exeContext, returnType, fieldNodes, info, result), fieldNodes, info, path, result); | ||
return completeObjectValue(exeContext, ensureValidRuntimeType(runtimeType, exeContext, returnType, fieldNodes, info, result), fieldNodes, info, path, result, asyncPayloadRecord); | ||
} | ||
@@ -543,5 +681,3 @@ function ensureValidRuntimeType(runtimeTypeName, exeContext, returnType, fieldNodes, info, result) { | ||
*/ | ||
function completeObjectValue(exeContext, returnType, fieldNodes, info, path, result) { | ||
// Collect sub-fields to execute to complete this value. | ||
const subFieldNodes = (0, utils_1.collectSubFields)(exeContext.schema, exeContext.fragments, exeContext.variableValues, returnType, fieldNodes); | ||
function completeObjectValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord) { | ||
// If there is an isTypeOf predicate function, call it with the | ||
@@ -557,3 +693,3 @@ // current result. If isTypeOf returns false, then raise an error rather | ||
} | ||
return executeFields(exeContext, returnType, result, path, subFieldNodes); | ||
return collectAndExecuteSubfields(exeContext, returnType, fieldNodes, path, result, asyncPayloadRecord); | ||
}); | ||
@@ -565,3 +701,3 @@ } | ||
} | ||
return executeFields(exeContext, returnType, result, path, subFieldNodes); | ||
return collectAndExecuteSubfields(exeContext, returnType, fieldNodes, path, result, asyncPayloadRecord); | ||
} | ||
@@ -573,2 +709,12 @@ function invalidReturnTypeError(returnType, result, fieldNodes) { | ||
} | ||
function collectAndExecuteSubfields(exeContext, returnType, fieldNodes, path, result, asyncPayloadRecord) { | ||
// Collect sub-fields to execute to complete this value. | ||
const { fields: subFieldNodes, patches: subPatches } = collectSubfields(exeContext, returnType, fieldNodes); | ||
const subFields = executeFields(exeContext, returnType, result, path, subFieldNodes, asyncPayloadRecord); | ||
for (const subPatch of subPatches) { | ||
const { label, fields: subPatchFieldNodes } = subPatch; | ||
executeDeferredFragment(exeContext, returnType, result, subPatchFieldNodes, label, path, asyncPayloadRecord); | ||
} | ||
return subFields; | ||
} | ||
/** | ||
@@ -641,7 +787,7 @@ * If a resolveType function is not given, then a default resolve behavior is | ||
* If the client-provided arguments to this function do not result in a | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with | ||
* descriptive errors and no data will be returned. | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with descriptive | ||
* errors and no data will be returned. | ||
* | ||
* If the source stream could not be created due to faulty subscription | ||
* resolver logic or underlying systems, the promise will resolve to a single | ||
* If the source stream could not be created due to faulty subscription resolver | ||
* logic or underlying systems, the promise will resolve to a single | ||
* ExecutionResult containing `errors` and no `data`. | ||
@@ -652,5 +798,64 @@ * | ||
* | ||
* Accepts either an object with named arguments, or individual arguments. | ||
* This function does not support incremental delivery (`@defer` and `@stream`). | ||
* If an operation which would defer or stream data is executed with this | ||
* function, each `InitialIncrementalExecutionResult` and | ||
* `SubsequentIncrementalExecutionResult` in the result stream will be replaced | ||
* with an `ExecutionResult` with a single error stating that defer/stream is | ||
* not supported. Use `experimentalSubscribeIncrementally` if you want to | ||
* support incremental delivery. | ||
* | ||
* Accepts an object with named arguments. | ||
*/ | ||
function subscribe(args) { | ||
const maybePromise = experimentalSubscribeIncrementally(args); | ||
if ((0, utils_1.isPromise)(maybePromise)) { | ||
return maybePromise.then(resultOrIterable => (0, utils_1.isAsyncIterable)(resultOrIterable) | ||
? (0, utils_1.mapAsyncIterator)(resultOrIterable, ensureSingleExecutionResult) | ||
: resultOrIterable); | ||
} | ||
return (0, utils_1.isAsyncIterable)(maybePromise) ? (0, utils_1.mapAsyncIterator)(maybePromise, ensureSingleExecutionResult) : maybePromise; | ||
} | ||
exports.subscribe = subscribe; | ||
function ensureSingleExecutionResult(result) { | ||
if ('hasNext' in result) { | ||
return { | ||
errors: [(0, utils_1.createGraphQLError)(UNEXPECTED_MULTIPLE_PAYLOADS)], | ||
}; | ||
} | ||
return result; | ||
} | ||
/** | ||
* Implements the "Subscribe" algorithm described in the GraphQL specification, | ||
* including `@defer` and `@stream` as proposed in | ||
* https://github.com/graphql/graphql-spec/pull/742 | ||
* | ||
* Returns a Promise which resolves to either an AsyncIterator (if successful) | ||
* or an ExecutionResult (error). The promise will be rejected if the schema or | ||
* other arguments to this function are invalid, or if the resolved event stream | ||
* is not an async iterable. | ||
* | ||
* If the client-provided arguments to this function do not result in a | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with descriptive | ||
* errors and no data will be returned. | ||
* | ||
* If the source stream could not be created due to faulty subscription resolver | ||
* logic or underlying systems, the promise will resolve to a single | ||
* ExecutionResult containing `errors` and no `data`. | ||
* | ||
* If the operation succeeded, the promise resolves to an AsyncIterator, which | ||
* yields a stream of result representing the response stream. | ||
* | ||
* Each result may be an ExecutionResult with no `hasNext` (if executing the | ||
* event did not use `@defer` or `@stream`), or an | ||
* `InitialIncrementalExecutionResult` or `SubsequentIncrementalExecutionResult` | ||
* (if executing the event used `@defer` or `@stream`). In the case of | ||
* incremental execution results, each event produces a single | ||
* `InitialIncrementalExecutionResult` followed by one or more | ||
* `SubsequentIncrementalExecutionResult`s; all but the last have `hasNext: true`, | ||
* and the last has `hasNext: false`. There is no interleaving between results | ||
* generated from the same original event. | ||
* | ||
* Accepts an object with named arguments. | ||
*/ | ||
function experimentalSubscribeIncrementally(args) { | ||
// If a valid execution context cannot be created due to incorrect arguments, | ||
@@ -669,3 +874,12 @@ // a "Response" with only errors is returned. | ||
} | ||
exports.subscribe = subscribe; | ||
exports.experimentalSubscribeIncrementally = experimentalSubscribeIncrementally; | ||
async function* ensureAsyncIterable(someExecutionResult) { | ||
if ('initialResult' in someExecutionResult) { | ||
yield someExecutionResult.initialResult; | ||
yield* someExecutionResult.subsequentResults; | ||
} | ||
else { | ||
yield someExecutionResult; | ||
} | ||
} | ||
function mapSourceToResponse(exeContext, resultOrStream) { | ||
@@ -681,3 +895,3 @@ if (!(0, utils_1.isAsyncIterable)(resultOrStream)) { | ||
// "ExecuteQuery" algorithm, for which `execute` is also used. | ||
return (0, utils_1.mapAsyncIterator)(resultOrStream[Symbol.asyncIterator](), (payload) => executeImpl(buildPerEventExecutionContext(exeContext, payload))); | ||
return (0, flattenAsyncIterable_js_1.flattenAsyncIterable)((0, utils_1.mapAsyncIterator)(resultOrStream[Symbol.asyncIterator](), async (payload) => ensureAsyncIterable(await executeImpl(buildPerEventExecutionContext(exeContext, payload))))); | ||
} | ||
@@ -742,3 +956,3 @@ /** | ||
} | ||
const rootFields = (0, utils_1.collectFields)(schema, fragments, variableValues, rootType, operation.selectionSet); | ||
const { fields: rootFields } = (0, collectFields_js_1.collectFields)(schema, fragments, variableValues, rootType, operation.selectionSet); | ||
const [responseName, fieldNodes] = [...rootFields.entries()][0]; | ||
@@ -787,2 +1001,335 @@ const fieldName = fieldNodes[0].name.value; | ||
} | ||
function executeDeferredFragment(exeContext, parentType, sourceValue, fields, label, path, parentContext) { | ||
const asyncPayloadRecord = new DeferredFragmentRecord({ | ||
label, | ||
path, | ||
parentContext, | ||
exeContext, | ||
}); | ||
let promiseOrData; | ||
try { | ||
promiseOrData = executeFields(exeContext, parentType, sourceValue, path, fields, asyncPayloadRecord); | ||
if ((0, utils_1.isPromise)(promiseOrData)) { | ||
promiseOrData = promiseOrData.then(null, e => { | ||
asyncPayloadRecord.errors.push(e); | ||
return null; | ||
}); | ||
} | ||
} | ||
catch (e) { | ||
asyncPayloadRecord.errors.push(e); | ||
promiseOrData = null; | ||
} | ||
asyncPayloadRecord.addData(promiseOrData); | ||
} | ||
function executeStreamField(path, itemPath, item, exeContext, fieldNodes, info, itemType, label, parentContext) { | ||
const asyncPayloadRecord = new StreamRecord({ | ||
label, | ||
path: itemPath, | ||
parentContext, | ||
exeContext, | ||
}); | ||
let completedItem; | ||
try { | ||
try { | ||
if ((0, utils_1.isPromise)(item)) { | ||
completedItem = item.then(resolved => completeValue(exeContext, itemType, fieldNodes, info, itemPath, resolved, asyncPayloadRecord)); | ||
} | ||
else { | ||
completedItem = completeValue(exeContext, itemType, fieldNodes, info, itemPath, item, asyncPayloadRecord); | ||
} | ||
if ((0, utils_1.isPromise)(completedItem)) { | ||
// Note: we don't rely on a `catch` method, but we do expect "thenable" | ||
// to take a second callback for the error case. | ||
completedItem = completedItem.then(undefined, rawError => { | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(itemPath)); | ||
const handledError = handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); | ||
return handledError; | ||
}); | ||
} | ||
} | ||
catch (rawError) { | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(itemPath)); | ||
completedItem = handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); | ||
} | ||
} | ||
catch (error) { | ||
asyncPayloadRecord.errors.push(error); | ||
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); | ||
asyncPayloadRecord.addItems(null); | ||
return asyncPayloadRecord; | ||
} | ||
let completedItems; | ||
if ((0, utils_1.isPromise)(completedItem)) { | ||
completedItems = completedItem.then(value => [value], error => { | ||
asyncPayloadRecord.errors.push(error); | ||
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); | ||
return null; | ||
}); | ||
} | ||
else { | ||
completedItems = [completedItem]; | ||
} | ||
asyncPayloadRecord.addItems(completedItems); | ||
return asyncPayloadRecord; | ||
} | ||
async function executeStreamIteratorItem(iterator, exeContext, fieldNodes, info, itemType, asyncPayloadRecord, itemPath) { | ||
let item; | ||
try { | ||
const { value, done } = await iterator.next(); | ||
if (done) { | ||
asyncPayloadRecord.setIsCompletedIterator(); | ||
return { done, value: undefined }; | ||
} | ||
item = value; | ||
} | ||
catch (rawError) { | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(itemPath)); | ||
const value = handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
// don't continue if iterator throws | ||
return { done: true, value }; | ||
} | ||
let completedItem; | ||
try { | ||
completedItem = completeValue(exeContext, itemType, fieldNodes, info, itemPath, item, asyncPayloadRecord); | ||
if ((0, utils_1.isPromise)(completedItem)) { | ||
completedItem = completedItem.then(undefined, rawError => { | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(itemPath)); | ||
const handledError = handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); | ||
return handledError; | ||
}); | ||
} | ||
return { done: false, value: completedItem }; | ||
} | ||
catch (rawError) { | ||
const error = (0, graphql_1.locatedError)(rawError, fieldNodes, (0, utils_1.pathToArray)(itemPath)); | ||
const value = handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); | ||
return { done: false, value }; | ||
} | ||
} | ||
async function executeStreamIterator(initialIndex, iterator, exeContext, fieldNodes, info, itemType, path, label, parentContext) { | ||
let index = initialIndex; | ||
let previousAsyncPayloadRecord = parentContext !== null && parentContext !== void 0 ? parentContext : undefined; | ||
while (true) { | ||
const itemPath = (0, utils_1.addPath)(path, index, undefined); | ||
const asyncPayloadRecord = new StreamRecord({ | ||
label, | ||
path: itemPath, | ||
parentContext: previousAsyncPayloadRecord, | ||
iterator, | ||
exeContext, | ||
}); | ||
let iteration; | ||
try { | ||
iteration = await executeStreamIteratorItem(iterator, exeContext, fieldNodes, info, itemType, asyncPayloadRecord, itemPath); | ||
} | ||
catch (error) { | ||
asyncPayloadRecord.errors.push(error); | ||
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); | ||
asyncPayloadRecord.addItems(null); | ||
// entire stream has errored and bubbled upwards | ||
if (iterator === null || iterator === void 0 ? void 0 : iterator.return) { | ||
iterator.return().catch(() => { | ||
// ignore errors | ||
}); | ||
} | ||
return; | ||
} | ||
const { done, value: completedItem } = iteration; | ||
let completedItems; | ||
if ((0, utils_1.isPromise)(completedItem)) { | ||
completedItems = completedItem.then(value => [value], error => { | ||
asyncPayloadRecord.errors.push(error); | ||
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); | ||
return null; | ||
}); | ||
} | ||
else { | ||
completedItems = [completedItem]; | ||
} | ||
asyncPayloadRecord.addItems(completedItems); | ||
if (done) { | ||
break; | ||
} | ||
previousAsyncPayloadRecord = asyncPayloadRecord; | ||
index++; | ||
} | ||
} | ||
function filterSubsequentPayloads(exeContext, nullPath, currentAsyncRecord) { | ||
const nullPathArray = (0, utils_1.pathToArray)(nullPath); | ||
exeContext.subsequentPayloads.forEach(asyncRecord => { | ||
var _a; | ||
if (asyncRecord === currentAsyncRecord) { | ||
// don't remove payload from where error originates | ||
return; | ||
} | ||
for (let i = 0; i < nullPathArray.length; i++) { | ||
if (asyncRecord.path[i] !== nullPathArray[i]) { | ||
// asyncRecord points to a path unaffected by this payload | ||
return; | ||
} | ||
} | ||
// asyncRecord path points to nulled error field | ||
if (isStreamPayload(asyncRecord) && ((_a = asyncRecord.iterator) === null || _a === void 0 ? void 0 : _a.return)) { | ||
asyncRecord.iterator.return().catch(() => { | ||
// ignore error | ||
}); | ||
} | ||
exeContext.subsequentPayloads.delete(asyncRecord); | ||
}); | ||
} | ||
function getCompletedIncrementalResults(exeContext) { | ||
const incrementalResults = []; | ||
for (const asyncPayloadRecord of exeContext.subsequentPayloads) { | ||
const incrementalResult = {}; | ||
if (!asyncPayloadRecord.isCompleted) { | ||
continue; | ||
} | ||
exeContext.subsequentPayloads.delete(asyncPayloadRecord); | ||
if (isStreamPayload(asyncPayloadRecord)) { | ||
const items = asyncPayloadRecord.items; | ||
if (asyncPayloadRecord.isCompletedIterator) { | ||
// async iterable resolver just finished but there may be pending payloads | ||
continue; | ||
} | ||
incrementalResult.items = items; | ||
} | ||
else { | ||
const data = asyncPayloadRecord.data; | ||
incrementalResult.data = data !== null && data !== void 0 ? data : null; | ||
} | ||
incrementalResult.path = asyncPayloadRecord.path; | ||
if (asyncPayloadRecord.label) { | ||
incrementalResult.label = asyncPayloadRecord.label; | ||
} | ||
if (asyncPayloadRecord.errors.length > 0) { | ||
incrementalResult.errors = asyncPayloadRecord.errors; | ||
} | ||
incrementalResults.push(incrementalResult); | ||
} | ||
return incrementalResults; | ||
} | ||
function yieldSubsequentPayloads(exeContext) { | ||
let isDone = false; | ||
async function next() { | ||
if (isDone) { | ||
return { value: undefined, done: true }; | ||
} | ||
await Promise.race(Array.from(exeContext.subsequentPayloads).map(p => p.promise)); | ||
if (isDone) { | ||
// a different call to next has exhausted all payloads | ||
return { value: undefined, done: true }; | ||
} | ||
const incremental = getCompletedIncrementalResults(exeContext); | ||
const hasNext = exeContext.subsequentPayloads.size > 0; | ||
if (!incremental.length && hasNext) { | ||
return next(); | ||
} | ||
if (!hasNext) { | ||
isDone = true; | ||
} | ||
return { | ||
value: incremental.length ? { incremental, hasNext } : { hasNext }, | ||
done: false, | ||
}; | ||
} | ||
function returnStreamIterators() { | ||
const promises = []; | ||
exeContext.subsequentPayloads.forEach(asyncPayloadRecord => { | ||
var _a; | ||
if (isStreamPayload(asyncPayloadRecord) && ((_a = asyncPayloadRecord.iterator) === null || _a === void 0 ? void 0 : _a.return)) { | ||
promises.push(asyncPayloadRecord.iterator.return()); | ||
} | ||
}); | ||
return Promise.all(promises); | ||
} | ||
return { | ||
[Symbol.asyncIterator]() { | ||
return this; | ||
}, | ||
next, | ||
async return() { | ||
await returnStreamIterators(); | ||
isDone = true; | ||
return { value: undefined, done: true }; | ||
}, | ||
async throw(error) { | ||
await returnStreamIterators(); | ||
isDone = true; | ||
return Promise.reject(error); | ||
}, | ||
}; | ||
} | ||
class DeferredFragmentRecord { | ||
constructor(opts) { | ||
this.type = 'defer'; | ||
this.label = opts.label; | ||
this.path = (0, utils_1.pathToArray)(opts.path); | ||
this.parentContext = opts.parentContext; | ||
this.errors = []; | ||
this._exeContext = opts.exeContext; | ||
this._exeContext.subsequentPayloads.add(this); | ||
this.isCompleted = false; | ||
this.data = null; | ||
this.promise = new Promise(resolve => { | ||
this._resolve = MaybePromise => { | ||
resolve(MaybePromise); | ||
}; | ||
}).then(data => { | ||
this.data = data; | ||
this.isCompleted = true; | ||
}); | ||
} | ||
addData(data) { | ||
var _a, _b, _c; | ||
const parentData = (_a = this.parentContext) === null || _a === void 0 ? void 0 : _a.promise; | ||
if (parentData) { | ||
(_b = this._resolve) === null || _b === void 0 ? void 0 : _b.call(this, parentData.then(() => data)); | ||
return; | ||
} | ||
(_c = this._resolve) === null || _c === void 0 ? void 0 : _c.call(this, data); | ||
} | ||
} | ||
class StreamRecord { | ||
constructor(opts) { | ||
this.type = 'stream'; | ||
this.items = null; | ||
this.label = opts.label; | ||
this.path = (0, utils_1.pathToArray)(opts.path); | ||
this.parentContext = opts.parentContext; | ||
this.iterator = opts.iterator; | ||
this.errors = []; | ||
this._exeContext = opts.exeContext; | ||
this._exeContext.subsequentPayloads.add(this); | ||
this.isCompleted = false; | ||
this.items = null; | ||
this.promise = new Promise(resolve => { | ||
this._resolve = MaybePromise => { | ||
resolve(MaybePromise); | ||
}; | ||
}).then(items => { | ||
this.items = items; | ||
this.isCompleted = true; | ||
}); | ||
} | ||
addItems(items) { | ||
var _a, _b, _c; | ||
const parentData = (_a = this.parentContext) === null || _a === void 0 ? void 0 : _a.promise; | ||
if (parentData) { | ||
(_b = this._resolve) === null || _b === void 0 ? void 0 : _b.call(this, parentData.then(() => items)); | ||
return; | ||
} | ||
(_c = this._resolve) === null || _c === void 0 ? void 0 : _c.call(this, items); | ||
} | ||
setIsCompletedIterator() { | ||
this.isCompletedIterator = true; | ||
} | ||
} | ||
function isStreamPayload(asyncPayload) { | ||
return asyncPayload.type === 'stream'; | ||
} | ||
/** | ||
@@ -789,0 +1336,0 @@ * This method looks up the field on the given type definition. |
@@ -5,1 +5,2 @@ "use strict"; | ||
tslib_1.__exportStar(require("./execution/index.js"), exports); | ||
tslib_1.__exportStar(require("./directives/index.js"), exports); |
@@ -1,5 +0,17 @@ | ||
import { locatedError, Kind, isAbstractType, isLeafType, isListType, isNonNullType, isObjectType, assertValidSchema, SchemaMetaFieldDef, TypeMetaFieldDef, TypeNameMetaFieldDef, } from 'graphql'; | ||
import { collectFields, collectSubFields, createGraphQLError, inspect, isAsyncIterable, isIterableObject, isObjectLike, isPromise, mapAsyncIterator, pathToArray, addPath, getArgumentValues, promiseReduce, getDefinedRootType, } from '@graphql-tools/utils'; | ||
import { locatedError, Kind, isAbstractType, isLeafType, isListType, isNonNullType, isObjectType, assertValidSchema, getDirectiveValues, SchemaMetaFieldDef, TypeMetaFieldDef, TypeNameMetaFieldDef, } from 'graphql'; | ||
import { createGraphQLError, inspect, isAsyncIterable, isIterableObject, isObjectLike, isPromise, pathToArray, addPath, getArgumentValues, promiseReduce, memoize3, getDefinedRootType, mapAsyncIterator, } from '@graphql-tools/utils'; | ||
import { getVariableValues } from './values.js'; | ||
import { promiseForObject } from './promiseForObject.js'; | ||
import { flattenAsyncIterable } from './flattenAsyncIterable.js'; | ||
import { collectFields, collectSubfields as _collectSubfields } from './collectFields.js'; | ||
import { GraphQLStreamDirective } from '../directives/index.js'; | ||
import { invariant } from './invariant.js'; | ||
/** | ||
* A memoized collection of relevant subfields with regard to the return | ||
* type. Memoizing ensures the subfields are not repeatedly calculated, which | ||
* saves overhead when resolving lists of values. | ||
*/ | ||
const collectSubfields = memoize3((exeContext, returnType, fieldNodes) => _collectSubfields(exeContext.schema, exeContext.fragments, exeContext.variableValues, returnType, fieldNodes)); | ||
const UNEXPECTED_MULTIPLE_PAYLOADS = 'Executing this GraphQL operation would unexpectedly produce multiple payloads (due to @defer or @stream directive)'; | ||
/** | ||
* Implements the "Executing requests" section of the GraphQL specification. | ||
@@ -13,4 +25,39 @@ * | ||
* a GraphQLError will be thrown immediately explaining the invalid input. | ||
* | ||
* This function does not support incremental delivery (`@defer` and `@stream`). | ||
* If an operation which would defer or stream data is executed with this | ||
* function, it will throw or resolve to an object containing an error instead. | ||
* Use `experimentalExecuteIncrementally` if you want to support incremental | ||
* delivery. | ||
*/ | ||
export function execute(args) { | ||
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) { | ||
return { | ||
errors: [createGraphQLError(UNEXPECTED_MULTIPLE_PAYLOADS)], | ||
}; | ||
} | ||
return incrementalResult; | ||
}); | ||
} | ||
/** | ||
* Implements the "Executing requests" section of the GraphQL specification, | ||
* including `@defer` and `@stream` as proposed in | ||
* https://github.com/graphql/graphql-spec/pull/742 | ||
* | ||
* This function returns a Promise of an ExperimentalIncrementalExecutionResults | ||
* object. This object either consists of a single ExecutionResult, or an | ||
* object containing an `initialResult` and a stream of `subsequentResults`. | ||
* | ||
* If the arguments to this function do not result in a legal execution context, | ||
* a GraphQLError will be thrown immediately explaining the invalid input. | ||
*/ | ||
export function experimentalExecuteIncrementally(args) { | ||
// If a valid execution context cannot be created due to incorrect arguments, | ||
@@ -40,3 +87,15 @@ // a "Response" with only errors is returned. | ||
if (isPromise(result)) { | ||
return result.then(data => buildResponse(data, exeContext.errors), error => { | ||
return result.then(data => { | ||
const initialResult = buildResponse(data, exeContext.errors); | ||
if (exeContext.subsequentPayloads.size > 0) { | ||
return { | ||
initialResult: { | ||
...initialResult, | ||
hasNext: true, | ||
}, | ||
subsequentResults: yieldSubsequentPayloads(exeContext), | ||
}; | ||
} | ||
return initialResult; | ||
}, error => { | ||
exeContext.errors.push(error); | ||
@@ -46,3 +105,13 @@ return buildResponse(null, exeContext.errors); | ||
} | ||
return buildResponse(result, exeContext.errors); | ||
const initialResult = buildResponse(result, exeContext.errors); | ||
if (exeContext.subsequentPayloads.size > 0) { | ||
return { | ||
initialResult: { | ||
...initialResult, | ||
hasNext: true, | ||
}, | ||
subsequentResults: yieldSubsequentPayloads(exeContext), | ||
}; | ||
} | ||
return initialResult; | ||
} | ||
@@ -60,5 +129,5 @@ catch (error) { | ||
export function executeSync(args) { | ||
const result = execute(args); | ||
const result = experimentalExecuteIncrementally(args); | ||
// Assert that the execution was synchronous. | ||
if (isPromise(result)) { | ||
if (isPromise(result) || 'initialResult' in result) { | ||
throw new Error('GraphQL execution failed to complete synchronously.'); | ||
@@ -149,2 +218,3 @@ } | ||
subscribeFieldResolver: subscribeFieldResolver !== null && subscribeFieldResolver !== void 0 ? subscribeFieldResolver : defaultFieldResolver, | ||
subsequentPayloads: new Set(), | ||
errors: [], | ||
@@ -157,2 +227,3 @@ }; | ||
rootValue: payload, | ||
subsequentPayloads: new Set(), | ||
errors: [], | ||
@@ -167,15 +238,21 @@ }; | ||
const rootType = getDefinedRootType(schema, operation.operation, [operation]); | ||
const rootFields = collectFields(schema, fragments, variableValues, rootType, operation.selectionSet); | ||
if (rootType == null) { | ||
createGraphQLError(`Schema is not configured to execute ${operation.operation} operation.`, { | ||
nodes: operation, | ||
}); | ||
} | ||
const { fields: rootFields, patches } = collectFields(schema, fragments, variableValues, rootType, operation.selectionSet); | ||
const path = undefined; | ||
switch (operation.operation) { | ||
case 'query': | ||
return executeFields(exeContext, rootType, rootValue, path, rootFields); | ||
case 'mutation': | ||
return executeFieldsSerially(exeContext, rootType, rootValue, path, rootFields); | ||
case 'subscription': | ||
// TODO: deprecate `subscribe` and move all logic here | ||
// Temporary solution until we finish merging execute and subscribe together | ||
return executeFields(exeContext, rootType, rootValue, path, rootFields); | ||
let result; | ||
if (operation.operation === 'mutation') { | ||
result = executeFieldsSerially(exeContext, rootType, rootValue, path, rootFields); | ||
} | ||
throw new Error(`Can only execute queries, mutations and subscriptions, got "${operation.operation}".`); | ||
else { | ||
result = executeFields(exeContext, rootType, rootValue, path, rootFields); | ||
} | ||
for (const patch of patches) { | ||
const { label, fields: patchFields } = patch; | ||
executeDeferredFragment(exeContext, rootType, rootValue, patchFields, label, path); | ||
} | ||
return result; | ||
} | ||
@@ -187,3 +264,3 @@ /** | ||
function executeFieldsSerially(exeContext, parentType, sourceValue, path, fields) { | ||
return promiseReduce(fields.entries(), (results, [responseName, fieldNodes]) => { | ||
return promiseReduce(fields, (results, [responseName, fieldNodes]) => { | ||
const fieldPath = addPath(path, responseName, parentType.name); | ||
@@ -208,15 +285,26 @@ const result = executeField(exeContext, parentType, sourceValue, fieldNodes, fieldPath); | ||
*/ | ||
function executeFields(exeContext, parentType, sourceValue, path, fields) { | ||
function executeFields(exeContext, parentType, sourceValue, path, fields, asyncPayloadRecord) { | ||
const results = Object.create(null); | ||
let containsPromise = false; | ||
for (const [responseName, fieldNodes] of fields.entries()) { | ||
const fieldPath = addPath(path, responseName, parentType.name); | ||
const result = executeField(exeContext, parentType, sourceValue, fieldNodes, fieldPath); | ||
if (result !== undefined) { | ||
results[responseName] = result; | ||
if (isPromise(result)) { | ||
containsPromise = true; | ||
try { | ||
for (const [responseName, fieldNodes] of fields) { | ||
const fieldPath = addPath(path, responseName, parentType.name); | ||
const result = executeField(exeContext, parentType, sourceValue, fieldNodes, fieldPath, asyncPayloadRecord); | ||
if (result !== undefined) { | ||
results[responseName] = result; | ||
if (isPromise(result)) { | ||
containsPromise = true; | ||
} | ||
} | ||
} | ||
} | ||
catch (error) { | ||
if (containsPromise) { | ||
// Ensure that any promises returned by other fields are handled, as they may also reject. | ||
return promiseForObject(results).finally(() => { | ||
throw error; | ||
}); | ||
} | ||
throw error; | ||
} | ||
// If there are no promises, we can just return the object | ||
@@ -229,9 +317,3 @@ if (!containsPromise) { | ||
// same map, but with any promises replaced with the values they resolved to. | ||
return Promise.all(Object.values(results)).then(resolvedValues => { | ||
const resolvedObject = Object.create(null); | ||
for (const [i, key] of Object.keys(results).entries()) { | ||
resolvedObject[key] = resolvedValues[i]; | ||
} | ||
return resolvedObject; | ||
}); | ||
return promiseForObject(results); | ||
} | ||
@@ -244,4 +326,5 @@ /** | ||
*/ | ||
function executeField(exeContext, parentType, source, fieldNodes, path) { | ||
var _a; | ||
function executeField(exeContext, parentType, source, fieldNodes, path, asyncPayloadRecord) { | ||
var _a, _b; | ||
const errors = (_a = asyncPayloadRecord === null || asyncPayloadRecord === void 0 ? void 0 : asyncPayloadRecord.errors) !== null && _a !== void 0 ? _a : exeContext.errors; | ||
const fieldDef = getFieldDef(exeContext.schema, parentType, fieldNodes[0]); | ||
@@ -252,3 +335,3 @@ if (!fieldDef) { | ||
const returnType = fieldDef.type; | ||
const resolveFn = (_a = fieldDef.resolve) !== null && _a !== void 0 ? _a : exeContext.fieldResolver; | ||
const resolveFn = (_b = fieldDef.resolve) !== null && _b !== void 0 ? _b : exeContext.fieldResolver; | ||
const info = buildResolveInfo(exeContext, fieldDef, fieldNodes, parentType, path); | ||
@@ -268,6 +351,6 @@ // Get the resolve function, regardless of if its result is normal or abrupt (error). | ||
if (isPromise(result)) { | ||
completed = result.then(resolved => completeValue(exeContext, returnType, fieldNodes, info, path, resolved)); | ||
completed = result.then(resolved => completeValue(exeContext, returnType, fieldNodes, info, path, resolved, asyncPayloadRecord)); | ||
} | ||
else { | ||
completed = completeValue(exeContext, returnType, fieldNodes, info, path, result); | ||
completed = completeValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord); | ||
} | ||
@@ -279,3 +362,5 @@ if (isPromise(completed)) { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(path)); | ||
return handleFieldError(error, returnType, exeContext); | ||
const handledError = handleFieldError(error, returnType, errors); | ||
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); | ||
return handledError; | ||
}); | ||
@@ -287,3 +372,5 @@ } | ||
const error = locatedError(rawError, fieldNodes, pathToArray(path)); | ||
return handleFieldError(error, returnType, exeContext); | ||
const handledError = handleFieldError(error, returnType, errors); | ||
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); | ||
return handledError; | ||
} | ||
@@ -311,3 +398,3 @@ } | ||
} | ||
function handleFieldError(error, returnType, exeContext) { | ||
function handleFieldError(error, returnType, errors) { | ||
// If the field type is non-nullable, then it is resolved without any | ||
@@ -320,3 +407,3 @@ // protection from errors, however it still properly locates the error. | ||
// a null value for this field if one is encountered. | ||
exeContext.errors.push(error); | ||
errors.push(error); | ||
return null; | ||
@@ -345,3 +432,3 @@ } | ||
*/ | ||
function completeValue(exeContext, returnType, fieldNodes, info, path, result) { | ||
function completeValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord) { | ||
// If result is an Error, throw a located error. | ||
@@ -354,3 +441,3 @@ if (result instanceof Error) { | ||
if (isNonNullType(returnType)) { | ||
const completed = completeValue(exeContext, returnType.ofType, fieldNodes, info, path, result); | ||
const completed = completeValue(exeContext, returnType.ofType, fieldNodes, info, path, result, asyncPayloadRecord); | ||
if (completed === null) { | ||
@@ -367,3 +454,3 @@ throw new Error(`Cannot return null for non-nullable field ${info.parentType.name}.${info.fieldName}.`); | ||
if (isListType(returnType)) { | ||
return completeListValue(exeContext, returnType, fieldNodes, info, path, result); | ||
return completeListValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord); | ||
} | ||
@@ -378,7 +465,7 @@ // If field type is a leaf type, Scalar or Enum, serialize to a valid value, | ||
if (isAbstractType(returnType)) { | ||
return completeAbstractValue(exeContext, returnType, fieldNodes, info, path, result); | ||
return completeAbstractValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord); | ||
} | ||
// If field type is Object, execute and complete all sub-selections. | ||
if (isObjectType(returnType)) { | ||
return completeObjectValue(exeContext, returnType, fieldNodes, info, path, result); | ||
return completeObjectValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord); | ||
} | ||
@@ -390,6 +477,35 @@ /* c8 ignore next 6 */ | ||
/** | ||
* Returns an object containing the `@stream` arguments if a field should be | ||
* streamed based on the experimental flag, stream directive present and | ||
* not disabled by the "if" argument. | ||
*/ | ||
function getStreamValues(exeContext, fieldNodes, path) { | ||
// do not stream inner lists of multi-dimensional lists | ||
if (typeof path.key === 'number') { | ||
return; | ||
} | ||
// validation only allows equivalent streams on multiple fields, so it is | ||
// safe to only check the first fieldNode for the stream directive | ||
const stream = getDirectiveValues(GraphQLStreamDirective, fieldNodes[0], 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'); | ||
return { | ||
initialCount: stream['initialCount'], | ||
label: typeof stream['label'] === 'string' ? stream['label'] : undefined, | ||
}; | ||
} | ||
/** | ||
* Complete a async iterator value by completing the result and calling | ||
* recursively until all the results are completed. | ||
*/ | ||
async function completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator) { | ||
async function completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator, asyncPayloadRecord) { | ||
var _a; | ||
const errors = (_a = asyncPayloadRecord === null || asyncPayloadRecord === void 0 ? void 0 : asyncPayloadRecord.errors) !== null && _a !== void 0 ? _a : exeContext.errors; | ||
const stream = getStreamValues(exeContext, fieldNodes, path); | ||
let containsPromise = false; | ||
@@ -399,28 +515,22 @@ const completedResults = []; | ||
while (true) { | ||
const fieldPath = addPath(path, index, undefined); | ||
if (stream && typeof stream.initialCount === 'number' && index >= stream.initialCount) { | ||
executeStreamIterator(index, iterator, exeContext, fieldNodes, info, itemType, path, stream.label, asyncPayloadRecord); | ||
break; | ||
} | ||
const itemPath = addPath(path, index, undefined); | ||
let iteration; | ||
try { | ||
const { value, done } = await iterator.next(); | ||
if (done) { | ||
iteration = await iterator.next(); | ||
if (iteration.done) { | ||
break; | ||
} | ||
try { | ||
// TODO can the error checking logic be consolidated with completeListValue? | ||
const completedItem = completeValue(exeContext, itemType, fieldNodes, info, fieldPath, value); | ||
if (isPromise(completedItem)) { | ||
containsPromise = true; | ||
} | ||
completedResults.push(completedItem); | ||
} | ||
catch (rawError) { | ||
completedResults.push(null); | ||
const error = locatedError(rawError, fieldNodes, pathToArray(fieldPath)); | ||
handleFieldError(error, itemType, exeContext); | ||
} | ||
} | ||
catch (rawError) { | ||
completedResults.push(null); | ||
const error = locatedError(rawError, fieldNodes, pathToArray(fieldPath)); | ||
handleFieldError(error, itemType, exeContext); | ||
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); | ||
completedResults.push(handleFieldError(error, itemType, errors)); | ||
break; | ||
} | ||
if (completeListItemValue(iteration.value, completedResults, errors, exeContext, itemType, fieldNodes, info, itemPath, asyncPayloadRecord)) { | ||
containsPromise = true; | ||
} | ||
index += 1; | ||
@@ -434,7 +544,9 @@ } | ||
*/ | ||
function completeListValue(exeContext, returnType, fieldNodes, info, path, result) { | ||
function completeListValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord) { | ||
var _a; | ||
const itemType = returnType.ofType; | ||
const errors = (_a = asyncPayloadRecord === null || asyncPayloadRecord === void 0 ? void 0 : asyncPayloadRecord.errors) !== null && _a !== void 0 ? _a : exeContext.errors; | ||
if (isAsyncIterable(result)) { | ||
const iterator = result[Symbol.asyncIterator](); | ||
return completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator); | ||
return completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator, asyncPayloadRecord); | ||
} | ||
@@ -444,36 +556,61 @@ if (!isIterableObject(result)) { | ||
} | ||
const stream = getStreamValues(exeContext, fieldNodes, path); | ||
// This is specified as a simple map, however we're optimizing the path | ||
// where the list contains no Promises by avoiding creating another Promise. | ||
let containsPromise = false; | ||
const completedResults = Array.from(result, (item, index) => { | ||
let previousAsyncPayloadRecord = asyncPayloadRecord; | ||
const completedResults = []; | ||
let index = 0; | ||
for (const item of result) { | ||
// No need to modify the info object containing the path, | ||
// since from here on it is not ever accessed by resolver functions. | ||
const itemPath = addPath(path, index, undefined); | ||
try { | ||
let completedItem; | ||
if (isPromise(item)) { | ||
completedItem = item.then(resolved => completeValue(exeContext, itemType, fieldNodes, info, itemPath, resolved)); | ||
} | ||
else { | ||
completedItem = completeValue(exeContext, itemType, fieldNodes, info, itemPath, item); | ||
} | ||
if (isPromise(completedItem)) { | ||
containsPromise = true; | ||
// Note: we don't rely on a `catch` method, but we do expect "thenable" | ||
// to take a second callback for the error case. | ||
return completedItem.then(undefined, rawError => { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); | ||
return handleFieldError(error, itemType, exeContext); | ||
}); | ||
} | ||
return completedItem; | ||
if (stream && typeof stream.initialCount === 'number' && index >= stream.initialCount) { | ||
previousAsyncPayloadRecord = executeStreamField(path, itemPath, item, exeContext, fieldNodes, info, itemType, stream.label, previousAsyncPayloadRecord); | ||
index++; | ||
continue; | ||
} | ||
catch (rawError) { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); | ||
return handleFieldError(error, itemType, exeContext); | ||
if (completeListItemValue(item, completedResults, errors, exeContext, itemType, fieldNodes, info, itemPath, asyncPayloadRecord)) { | ||
containsPromise = true; | ||
} | ||
}); | ||
index++; | ||
} | ||
return containsPromise ? Promise.all(completedResults) : completedResults; | ||
} | ||
/** | ||
* Complete a list item value by adding it to the completed results. | ||
* | ||
* Returns true if the value is a Promise. | ||
*/ | ||
function completeListItemValue(item, completedResults, errors, exeContext, itemType, fieldNodes, info, itemPath, asyncPayloadRecord) { | ||
try { | ||
let completedItem; | ||
if (isPromise(item)) { | ||
completedItem = item.then(resolved => completeValue(exeContext, itemType, fieldNodes, info, itemPath, resolved, asyncPayloadRecord)); | ||
} | ||
else { | ||
completedItem = completeValue(exeContext, itemType, fieldNodes, info, itemPath, item, asyncPayloadRecord); | ||
} | ||
if (isPromise(completedItem)) { | ||
// Note: we don't rely on a `catch` method, but we do expect "thenable" | ||
// to take a second callback for the error case. | ||
completedResults.push(completedItem.then(undefined, rawError => { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); | ||
const handledError = handleFieldError(error, itemType, errors); | ||
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); | ||
return handledError; | ||
})); | ||
return true; | ||
} | ||
completedResults.push(completedItem); | ||
} | ||
catch (rawError) { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); | ||
const handledError = handleFieldError(error, itemType, errors); | ||
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); | ||
completedResults.push(handledError); | ||
} | ||
return false; | ||
} | ||
/** | ||
* Complete a Scalar or Enum by serializing to a valid value, returning | ||
@@ -494,3 +631,3 @@ * null if serialization is not possible. | ||
*/ | ||
function completeAbstractValue(exeContext, returnType, fieldNodes, info, path, result) { | ||
function completeAbstractValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord) { | ||
var _a; | ||
@@ -501,5 +638,5 @@ const resolveTypeFn = (_a = returnType.resolveType) !== null && _a !== void 0 ? _a : exeContext.typeResolver; | ||
if (isPromise(runtimeType)) { | ||
return runtimeType.then(resolvedRuntimeType => completeObjectValue(exeContext, ensureValidRuntimeType(resolvedRuntimeType, exeContext, returnType, fieldNodes, info, result), fieldNodes, info, path, result)); | ||
return runtimeType.then(resolvedRuntimeType => completeObjectValue(exeContext, ensureValidRuntimeType(resolvedRuntimeType, exeContext, returnType, fieldNodes, info, result), fieldNodes, info, path, result, asyncPayloadRecord)); | ||
} | ||
return completeObjectValue(exeContext, ensureValidRuntimeType(runtimeType, exeContext, returnType, fieldNodes, info, result), fieldNodes, info, path, result); | ||
return completeObjectValue(exeContext, ensureValidRuntimeType(runtimeType, exeContext, returnType, fieldNodes, info, result), fieldNodes, info, path, result, asyncPayloadRecord); | ||
} | ||
@@ -534,5 +671,3 @@ function ensureValidRuntimeType(runtimeTypeName, exeContext, returnType, fieldNodes, info, result) { | ||
*/ | ||
function completeObjectValue(exeContext, returnType, fieldNodes, info, path, result) { | ||
// Collect sub-fields to execute to complete this value. | ||
const subFieldNodes = collectSubFields(exeContext.schema, exeContext.fragments, exeContext.variableValues, returnType, fieldNodes); | ||
function completeObjectValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord) { | ||
// If there is an isTypeOf predicate function, call it with the | ||
@@ -548,3 +683,3 @@ // current result. If isTypeOf returns false, then raise an error rather | ||
} | ||
return executeFields(exeContext, returnType, result, path, subFieldNodes); | ||
return collectAndExecuteSubfields(exeContext, returnType, fieldNodes, path, result, asyncPayloadRecord); | ||
}); | ||
@@ -556,3 +691,3 @@ } | ||
} | ||
return executeFields(exeContext, returnType, result, path, subFieldNodes); | ||
return collectAndExecuteSubfields(exeContext, returnType, fieldNodes, path, result, asyncPayloadRecord); | ||
} | ||
@@ -564,2 +699,12 @@ function invalidReturnTypeError(returnType, result, fieldNodes) { | ||
} | ||
function collectAndExecuteSubfields(exeContext, returnType, fieldNodes, path, result, asyncPayloadRecord) { | ||
// Collect sub-fields to execute to complete this value. | ||
const { fields: subFieldNodes, patches: subPatches } = collectSubfields(exeContext, returnType, fieldNodes); | ||
const subFields = executeFields(exeContext, returnType, result, path, subFieldNodes, asyncPayloadRecord); | ||
for (const subPatch of subPatches) { | ||
const { label, fields: subPatchFieldNodes } = subPatch; | ||
executeDeferredFragment(exeContext, returnType, result, subPatchFieldNodes, label, path, asyncPayloadRecord); | ||
} | ||
return subFields; | ||
} | ||
/** | ||
@@ -630,7 +775,7 @@ * If a resolveType function is not given, then a default resolve behavior is | ||
* If the client-provided arguments to this function do not result in a | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with | ||
* descriptive errors and no data will be returned. | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with descriptive | ||
* errors and no data will be returned. | ||
* | ||
* If the source stream could not be created due to faulty subscription | ||
* resolver logic or underlying systems, the promise will resolve to a single | ||
* If the source stream could not be created due to faulty subscription resolver | ||
* logic or underlying systems, the promise will resolve to a single | ||
* ExecutionResult containing `errors` and no `data`. | ||
@@ -641,5 +786,63 @@ * | ||
* | ||
* Accepts either an object with named arguments, or individual arguments. | ||
* This function does not support incremental delivery (`@defer` and `@stream`). | ||
* If an operation which would defer or stream data is executed with this | ||
* function, each `InitialIncrementalExecutionResult` and | ||
* `SubsequentIncrementalExecutionResult` in the result stream will be replaced | ||
* with an `ExecutionResult` with a single error stating that defer/stream is | ||
* not supported. Use `experimentalSubscribeIncrementally` if you want to | ||
* support incremental delivery. | ||
* | ||
* Accepts an object with named arguments. | ||
*/ | ||
export function subscribe(args) { | ||
const maybePromise = experimentalSubscribeIncrementally(args); | ||
if (isPromise(maybePromise)) { | ||
return maybePromise.then(resultOrIterable => isAsyncIterable(resultOrIterable) | ||
? mapAsyncIterator(resultOrIterable, ensureSingleExecutionResult) | ||
: resultOrIterable); | ||
} | ||
return isAsyncIterable(maybePromise) ? mapAsyncIterator(maybePromise, ensureSingleExecutionResult) : maybePromise; | ||
} | ||
function ensureSingleExecutionResult(result) { | ||
if ('hasNext' in result) { | ||
return { | ||
errors: [createGraphQLError(UNEXPECTED_MULTIPLE_PAYLOADS)], | ||
}; | ||
} | ||
return result; | ||
} | ||
/** | ||
* Implements the "Subscribe" algorithm described in the GraphQL specification, | ||
* including `@defer` and `@stream` as proposed in | ||
* https://github.com/graphql/graphql-spec/pull/742 | ||
* | ||
* Returns a Promise which resolves to either an AsyncIterator (if successful) | ||
* or an ExecutionResult (error). The promise will be rejected if the schema or | ||
* other arguments to this function are invalid, or if the resolved event stream | ||
* is not an async iterable. | ||
* | ||
* If the client-provided arguments to this function do not result in a | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with descriptive | ||
* errors and no data will be returned. | ||
* | ||
* If the source stream could not be created due to faulty subscription resolver | ||
* logic or underlying systems, the promise will resolve to a single | ||
* ExecutionResult containing `errors` and no `data`. | ||
* | ||
* If the operation succeeded, the promise resolves to an AsyncIterator, which | ||
* yields a stream of result representing the response stream. | ||
* | ||
* Each result may be an ExecutionResult with no `hasNext` (if executing the | ||
* event did not use `@defer` or `@stream`), or an | ||
* `InitialIncrementalExecutionResult` or `SubsequentIncrementalExecutionResult` | ||
* (if executing the event used `@defer` or `@stream`). In the case of | ||
* incremental execution results, each event produces a single | ||
* `InitialIncrementalExecutionResult` followed by one or more | ||
* `SubsequentIncrementalExecutionResult`s; all but the last have `hasNext: true`, | ||
* and the last has `hasNext: false`. There is no interleaving between results | ||
* generated from the same original event. | ||
* | ||
* Accepts an object with named arguments. | ||
*/ | ||
export function experimentalSubscribeIncrementally(args) { | ||
// If a valid execution context cannot be created due to incorrect arguments, | ||
@@ -658,2 +861,11 @@ // a "Response" with only errors is returned. | ||
} | ||
async function* ensureAsyncIterable(someExecutionResult) { | ||
if ('initialResult' in someExecutionResult) { | ||
yield someExecutionResult.initialResult; | ||
yield* someExecutionResult.subsequentResults; | ||
} | ||
else { | ||
yield someExecutionResult; | ||
} | ||
} | ||
function mapSourceToResponse(exeContext, resultOrStream) { | ||
@@ -669,3 +881,3 @@ if (!isAsyncIterable(resultOrStream)) { | ||
// "ExecuteQuery" algorithm, for which `execute` is also used. | ||
return mapAsyncIterator(resultOrStream[Symbol.asyncIterator](), (payload) => executeImpl(buildPerEventExecutionContext(exeContext, payload))); | ||
return flattenAsyncIterable(mapAsyncIterator(resultOrStream[Symbol.asyncIterator](), async (payload) => ensureAsyncIterable(await executeImpl(buildPerEventExecutionContext(exeContext, payload))))); | ||
} | ||
@@ -729,3 +941,3 @@ /** | ||
} | ||
const rootFields = collectFields(schema, fragments, variableValues, rootType, operation.selectionSet); | ||
const { fields: rootFields } = collectFields(schema, fragments, variableValues, rootType, operation.selectionSet); | ||
const [responseName, fieldNodes] = [...rootFields.entries()][0]; | ||
@@ -774,2 +986,335 @@ const fieldName = fieldNodes[0].name.value; | ||
} | ||
function executeDeferredFragment(exeContext, parentType, sourceValue, fields, label, path, parentContext) { | ||
const asyncPayloadRecord = new DeferredFragmentRecord({ | ||
label, | ||
path, | ||
parentContext, | ||
exeContext, | ||
}); | ||
let promiseOrData; | ||
try { | ||
promiseOrData = executeFields(exeContext, parentType, sourceValue, path, fields, asyncPayloadRecord); | ||
if (isPromise(promiseOrData)) { | ||
promiseOrData = promiseOrData.then(null, e => { | ||
asyncPayloadRecord.errors.push(e); | ||
return null; | ||
}); | ||
} | ||
} | ||
catch (e) { | ||
asyncPayloadRecord.errors.push(e); | ||
promiseOrData = null; | ||
} | ||
asyncPayloadRecord.addData(promiseOrData); | ||
} | ||
function executeStreamField(path, itemPath, item, exeContext, fieldNodes, info, itemType, label, parentContext) { | ||
const asyncPayloadRecord = new StreamRecord({ | ||
label, | ||
path: itemPath, | ||
parentContext, | ||
exeContext, | ||
}); | ||
let completedItem; | ||
try { | ||
try { | ||
if (isPromise(item)) { | ||
completedItem = item.then(resolved => completeValue(exeContext, itemType, fieldNodes, info, itemPath, resolved, asyncPayloadRecord)); | ||
} | ||
else { | ||
completedItem = completeValue(exeContext, itemType, fieldNodes, info, itemPath, item, asyncPayloadRecord); | ||
} | ||
if (isPromise(completedItem)) { | ||
// Note: we don't rely on a `catch` method, but we do expect "thenable" | ||
// to take a second callback for the error case. | ||
completedItem = completedItem.then(undefined, rawError => { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); | ||
const handledError = handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); | ||
return handledError; | ||
}); | ||
} | ||
} | ||
catch (rawError) { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); | ||
completedItem = handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); | ||
} | ||
} | ||
catch (error) { | ||
asyncPayloadRecord.errors.push(error); | ||
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); | ||
asyncPayloadRecord.addItems(null); | ||
return asyncPayloadRecord; | ||
} | ||
let completedItems; | ||
if (isPromise(completedItem)) { | ||
completedItems = completedItem.then(value => [value], error => { | ||
asyncPayloadRecord.errors.push(error); | ||
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); | ||
return null; | ||
}); | ||
} | ||
else { | ||
completedItems = [completedItem]; | ||
} | ||
asyncPayloadRecord.addItems(completedItems); | ||
return asyncPayloadRecord; | ||
} | ||
async function executeStreamIteratorItem(iterator, exeContext, fieldNodes, info, itemType, asyncPayloadRecord, itemPath) { | ||
let item; | ||
try { | ||
const { value, done } = await iterator.next(); | ||
if (done) { | ||
asyncPayloadRecord.setIsCompletedIterator(); | ||
return { done, value: undefined }; | ||
} | ||
item = value; | ||
} | ||
catch (rawError) { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); | ||
const value = handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
// don't continue if iterator throws | ||
return { done: true, value }; | ||
} | ||
let completedItem; | ||
try { | ||
completedItem = completeValue(exeContext, itemType, fieldNodes, info, itemPath, item, asyncPayloadRecord); | ||
if (isPromise(completedItem)) { | ||
completedItem = completedItem.then(undefined, rawError => { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); | ||
const handledError = handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); | ||
return handledError; | ||
}); | ||
} | ||
return { done: false, value: completedItem }; | ||
} | ||
catch (rawError) { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); | ||
const value = handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord); | ||
return { done: false, value }; | ||
} | ||
} | ||
async function executeStreamIterator(initialIndex, iterator, exeContext, fieldNodes, info, itemType, path, label, parentContext) { | ||
let index = initialIndex; | ||
let previousAsyncPayloadRecord = parentContext !== null && parentContext !== void 0 ? parentContext : undefined; | ||
while (true) { | ||
const itemPath = addPath(path, index, undefined); | ||
const asyncPayloadRecord = new StreamRecord({ | ||
label, | ||
path: itemPath, | ||
parentContext: previousAsyncPayloadRecord, | ||
iterator, | ||
exeContext, | ||
}); | ||
let iteration; | ||
try { | ||
iteration = await executeStreamIteratorItem(iterator, exeContext, fieldNodes, info, itemType, asyncPayloadRecord, itemPath); | ||
} | ||
catch (error) { | ||
asyncPayloadRecord.errors.push(error); | ||
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); | ||
asyncPayloadRecord.addItems(null); | ||
// entire stream has errored and bubbled upwards | ||
if (iterator === null || iterator === void 0 ? void 0 : iterator.return) { | ||
iterator.return().catch(() => { | ||
// ignore errors | ||
}); | ||
} | ||
return; | ||
} | ||
const { done, value: completedItem } = iteration; | ||
let completedItems; | ||
if (isPromise(completedItem)) { | ||
completedItems = completedItem.then(value => [value], error => { | ||
asyncPayloadRecord.errors.push(error); | ||
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); | ||
return null; | ||
}); | ||
} | ||
else { | ||
completedItems = [completedItem]; | ||
} | ||
asyncPayloadRecord.addItems(completedItems); | ||
if (done) { | ||
break; | ||
} | ||
previousAsyncPayloadRecord = asyncPayloadRecord; | ||
index++; | ||
} | ||
} | ||
function filterSubsequentPayloads(exeContext, nullPath, currentAsyncRecord) { | ||
const nullPathArray = pathToArray(nullPath); | ||
exeContext.subsequentPayloads.forEach(asyncRecord => { | ||
var _a; | ||
if (asyncRecord === currentAsyncRecord) { | ||
// don't remove payload from where error originates | ||
return; | ||
} | ||
for (let i = 0; i < nullPathArray.length; i++) { | ||
if (asyncRecord.path[i] !== nullPathArray[i]) { | ||
// asyncRecord points to a path unaffected by this payload | ||
return; | ||
} | ||
} | ||
// asyncRecord path points to nulled error field | ||
if (isStreamPayload(asyncRecord) && ((_a = asyncRecord.iterator) === null || _a === void 0 ? void 0 : _a.return)) { | ||
asyncRecord.iterator.return().catch(() => { | ||
// ignore error | ||
}); | ||
} | ||
exeContext.subsequentPayloads.delete(asyncRecord); | ||
}); | ||
} | ||
function getCompletedIncrementalResults(exeContext) { | ||
const incrementalResults = []; | ||
for (const asyncPayloadRecord of exeContext.subsequentPayloads) { | ||
const incrementalResult = {}; | ||
if (!asyncPayloadRecord.isCompleted) { | ||
continue; | ||
} | ||
exeContext.subsequentPayloads.delete(asyncPayloadRecord); | ||
if (isStreamPayload(asyncPayloadRecord)) { | ||
const items = asyncPayloadRecord.items; | ||
if (asyncPayloadRecord.isCompletedIterator) { | ||
// async iterable resolver just finished but there may be pending payloads | ||
continue; | ||
} | ||
incrementalResult.items = items; | ||
} | ||
else { | ||
const data = asyncPayloadRecord.data; | ||
incrementalResult.data = data !== null && data !== void 0 ? data : null; | ||
} | ||
incrementalResult.path = asyncPayloadRecord.path; | ||
if (asyncPayloadRecord.label) { | ||
incrementalResult.label = asyncPayloadRecord.label; | ||
} | ||
if (asyncPayloadRecord.errors.length > 0) { | ||
incrementalResult.errors = asyncPayloadRecord.errors; | ||
} | ||
incrementalResults.push(incrementalResult); | ||
} | ||
return incrementalResults; | ||
} | ||
function yieldSubsequentPayloads(exeContext) { | ||
let isDone = false; | ||
async function next() { | ||
if (isDone) { | ||
return { value: undefined, done: true }; | ||
} | ||
await Promise.race(Array.from(exeContext.subsequentPayloads).map(p => p.promise)); | ||
if (isDone) { | ||
// a different call to next has exhausted all payloads | ||
return { value: undefined, done: true }; | ||
} | ||
const incremental = getCompletedIncrementalResults(exeContext); | ||
const hasNext = exeContext.subsequentPayloads.size > 0; | ||
if (!incremental.length && hasNext) { | ||
return next(); | ||
} | ||
if (!hasNext) { | ||
isDone = true; | ||
} | ||
return { | ||
value: incremental.length ? { incremental, hasNext } : { hasNext }, | ||
done: false, | ||
}; | ||
} | ||
function returnStreamIterators() { | ||
const promises = []; | ||
exeContext.subsequentPayloads.forEach(asyncPayloadRecord => { | ||
var _a; | ||
if (isStreamPayload(asyncPayloadRecord) && ((_a = asyncPayloadRecord.iterator) === null || _a === void 0 ? void 0 : _a.return)) { | ||
promises.push(asyncPayloadRecord.iterator.return()); | ||
} | ||
}); | ||
return Promise.all(promises); | ||
} | ||
return { | ||
[Symbol.asyncIterator]() { | ||
return this; | ||
}, | ||
next, | ||
async return() { | ||
await returnStreamIterators(); | ||
isDone = true; | ||
return { value: undefined, done: true }; | ||
}, | ||
async throw(error) { | ||
await returnStreamIterators(); | ||
isDone = true; | ||
return Promise.reject(error); | ||
}, | ||
}; | ||
} | ||
class DeferredFragmentRecord { | ||
constructor(opts) { | ||
this.type = 'defer'; | ||
this.label = opts.label; | ||
this.path = pathToArray(opts.path); | ||
this.parentContext = opts.parentContext; | ||
this.errors = []; | ||
this._exeContext = opts.exeContext; | ||
this._exeContext.subsequentPayloads.add(this); | ||
this.isCompleted = false; | ||
this.data = null; | ||
this.promise = new Promise(resolve => { | ||
this._resolve = MaybePromise => { | ||
resolve(MaybePromise); | ||
}; | ||
}).then(data => { | ||
this.data = data; | ||
this.isCompleted = true; | ||
}); | ||
} | ||
addData(data) { | ||
var _a, _b, _c; | ||
const parentData = (_a = this.parentContext) === null || _a === void 0 ? void 0 : _a.promise; | ||
if (parentData) { | ||
(_b = this._resolve) === null || _b === void 0 ? void 0 : _b.call(this, parentData.then(() => data)); | ||
return; | ||
} | ||
(_c = this._resolve) === null || _c === void 0 ? void 0 : _c.call(this, data); | ||
} | ||
} | ||
class StreamRecord { | ||
constructor(opts) { | ||
this.type = 'stream'; | ||
this.items = null; | ||
this.label = opts.label; | ||
this.path = pathToArray(opts.path); | ||
this.parentContext = opts.parentContext; | ||
this.iterator = opts.iterator; | ||
this.errors = []; | ||
this._exeContext = opts.exeContext; | ||
this._exeContext.subsequentPayloads.add(this); | ||
this.isCompleted = false; | ||
this.items = null; | ||
this.promise = new Promise(resolve => { | ||
this._resolve = MaybePromise => { | ||
resolve(MaybePromise); | ||
}; | ||
}).then(items => { | ||
this.items = items; | ||
this.isCompleted = true; | ||
}); | ||
} | ||
addItems(items) { | ||
var _a, _b, _c; | ||
const parentData = (_a = this.parentContext) === null || _a === void 0 ? void 0 : _a.promise; | ||
if (parentData) { | ||
(_b = this._resolve) === null || _b === void 0 ? void 0 : _b.call(this, parentData.then(() => items)); | ||
return; | ||
} | ||
(_c = this._resolve) === null || _c === void 0 ? void 0 : _c.call(this, items); | ||
} | ||
setIsCompletedIterator() { | ||
this.isCompletedIterator = true; | ||
} | ||
} | ||
function isStreamPayload(asyncPayload) { | ||
return asyncPayload.type === 'stream'; | ||
} | ||
/** | ||
@@ -776,0 +1321,0 @@ * This method looks up the field on the given type definition. |
export * from './execution/index.js'; | ||
export * from './directives/index.js'; |
{ | ||
"name": "@graphql-tools/executor", | ||
"version": "0.0.2-alpha-20221027131523-6d0f4d51", | ||
"version": "0.0.2-alpha-20221029152711-14f4f7a7", | ||
"sideEffects": false, | ||
@@ -9,3 +9,3 @@ "peerDependencies": { | ||
"dependencies": { | ||
"@graphql-tools/utils": "8.13.0", | ||
"@graphql-tools/utils": "8.14.0-alpha-20221029152711-14f4f7a7", | ||
"@graphql-typed-document-node/core": "3.1.1" | ||
@@ -12,0 +12,0 @@ }, |
@@ -1,3 +0,4 @@ | ||
import { GraphQLFormattedError, GraphQLError, FieldNode, FragmentDefinitionNode, OperationDefinitionNode, GraphQLField, GraphQLFieldResolver, GraphQLObjectType, GraphQLResolveInfo, GraphQLTypeResolver, GraphQLSchema } from 'graphql'; | ||
import { Maybe, Path, MaybePromise, ExecutionResult } from '@graphql-tools/utils'; | ||
import { GraphQLFormattedError, FieldNode, FragmentDefinitionNode, OperationDefinitionNode, GraphQLField, GraphQLFieldResolver, GraphQLObjectType, GraphQLResolveInfo, GraphQLTypeResolver, GraphQLSchema } from 'graphql'; | ||
import type { GraphQLError } from 'graphql'; | ||
import { Path, Maybe, MaybePromise, ExecutionResult } from '@graphql-tools/utils'; | ||
import { TypedDocumentNode } from '@graphql-typed-document-node/core'; | ||
@@ -29,3 +30,3 @@ /** | ||
*/ | ||
export interface ExecutionContext<TContext = any> { | ||
export interface ExecutionContext<TVariables = any, TContext = any> { | ||
schema: GraphQLSchema; | ||
@@ -36,5 +37,3 @@ fragments: Record<string, FragmentDefinitionNode>; | ||
operation: OperationDefinitionNode; | ||
variableValues: { | ||
[variable: string]: unknown; | ||
}; | ||
variableValues: TVariables; | ||
fieldResolver: GraphQLFieldResolver<any, TContext>; | ||
@@ -44,4 +43,5 @@ typeResolver: GraphQLTypeResolver<any, TContext>; | ||
errors: Array<GraphQLError>; | ||
subsequentPayloads: Set<AsyncPayloadRecord>; | ||
} | ||
export interface FormattedExecutionResult<TData = any, TExtensions = any> { | ||
export interface FormattedExecutionResult<TData = Record<string, unknown>, TExtensions = Record<string, unknown>> { | ||
errors?: ReadonlyArray<GraphQLFormattedError>; | ||
@@ -51,2 +51,50 @@ data?: TData | null; | ||
} | ||
export interface ExperimentalIncrementalExecutionResults<TData = Record<string, unknown>, TExtensions = Record<string, unknown>> { | ||
initialResult: InitialIncrementalExecutionResult<TData, TExtensions>; | ||
subsequentResults: AsyncGenerator<SubsequentIncrementalExecutionResult<TData, TExtensions>, void, void>; | ||
} | ||
export interface InitialIncrementalExecutionResult<TData = Record<string, unknown>, TExtensions = Record<string, unknown>> extends ExecutionResult<TData, TExtensions> { | ||
hasNext: boolean; | ||
incremental?: ReadonlyArray<IncrementalResult<TData, TExtensions>>; | ||
extensions?: TExtensions; | ||
} | ||
export interface FormattedInitialIncrementalExecutionResult<TData = Record<string, unknown>, TExtensions = Record<string, unknown>> extends FormattedExecutionResult<TData, TExtensions> { | ||
hasNext: boolean; | ||
incremental?: ReadonlyArray<FormattedIncrementalResult<TData, TExtensions>>; | ||
extensions?: TExtensions; | ||
} | ||
export interface SubsequentIncrementalExecutionResult<TData = Record<string, unknown>, TExtensions = Record<string, unknown>> { | ||
hasNext: boolean; | ||
incremental?: ReadonlyArray<IncrementalResult<TData, TExtensions>>; | ||
extensions?: TExtensions; | ||
} | ||
export interface FormattedSubsequentIncrementalExecutionResult<TData = Record<string, unknown>, TExtensions = Record<string, unknown>> { | ||
hasNext: boolean; | ||
incremental?: ReadonlyArray<FormattedIncrementalResult<TData, TExtensions>>; | ||
extensions?: TExtensions; | ||
} | ||
export interface IncrementalDeferResult<TData = Record<string, unknown>, TExtensions = Record<string, unknown>> extends ExecutionResult<TData, TExtensions> { | ||
path?: ReadonlyArray<string | number>; | ||
label?: string; | ||
} | ||
export interface FormattedIncrementalDeferResult<TData = Record<string, unknown>, TExtensions = Record<string, unknown>> extends FormattedExecutionResult<TData, TExtensions> { | ||
path?: ReadonlyArray<string | number>; | ||
label?: string; | ||
} | ||
export interface IncrementalStreamResult<TData = Array<unknown>, TExtensions = Record<string, unknown>> { | ||
errors?: ReadonlyArray<GraphQLError>; | ||
items?: TData | null; | ||
path?: ReadonlyArray<string | number>; | ||
label?: string; | ||
extensions?: TExtensions; | ||
} | ||
export interface FormattedIncrementalStreamResult<TData = Array<unknown>, TExtensions = Record<string, unknown>> { | ||
errors?: ReadonlyArray<GraphQLFormattedError>; | ||
items?: TData | null; | ||
path?: ReadonlyArray<string | number>; | ||
label?: string; | ||
extensions?: TExtensions; | ||
} | ||
export declare type IncrementalResult<TData = Record<string, unknown>, TExtensions = Record<string, unknown>> = IncrementalDeferResult<TData, TExtensions> | IncrementalStreamResult<TData, TExtensions>; | ||
export declare type FormattedIncrementalResult<TData = Record<string, unknown>, TExtensions = Record<string, unknown>> = FormattedIncrementalDeferResult<TData, TExtensions> | FormattedIncrementalStreamResult<TData, TExtensions>; | ||
export interface ExecutionArgs<TData = any, TVariables = any, TContext = any> { | ||
@@ -72,9 +120,24 @@ schema: GraphQLSchema; | ||
* a GraphQLError will be thrown immediately explaining the invalid input. | ||
* | ||
* This function does not support incremental delivery (`@defer` and `@stream`). | ||
* If an operation which would defer or stream data is executed with this | ||
* function, it will throw or resolve to an object containing an error instead. | ||
* Use `experimentalExecuteIncrementally` if you want to support incremental | ||
* delivery. | ||
*/ | ||
export declare function execute<TData = { | ||
[key: string]: any; | ||
}, TVariables = { | ||
[key: string]: any; | ||
}, TContext = any>(args: ExecutionArgs<TData, TVariables, TContext>): MaybePromise<ExecutionResult<TData>>; | ||
export declare function execute<TData = any, TVariables = any, TContext = any>(args: ExecutionArgs<TData, TVariables, TContext>): MaybePromise<ExecutionResult<TData>>; | ||
/** | ||
* Implements the "Executing requests" section of the GraphQL specification, | ||
* including `@defer` and `@stream` as proposed in | ||
* https://github.com/graphql/graphql-spec/pull/742 | ||
* | ||
* This function returns a Promise of an ExperimentalIncrementalExecutionResults | ||
* object. This object either consists of a single ExecutionResult, or an | ||
* object containing an `initialResult` and a stream of `subsequentResults`. | ||
* | ||
* If the arguments to this function do not result in a legal execution context, | ||
* a GraphQLError will be thrown immediately explaining the invalid input. | ||
*/ | ||
export declare function experimentalExecuteIncrementally<TData = any, TVariables = any, TContext = any>(args: ExecutionArgs<TData, TVariables, TContext>): MaybePromise<ExecutionResult<TData> | ExperimentalIncrementalExecutionResults<TData>>; | ||
/** | ||
* Also implements the "Executing requests" section of the GraphQL specification. | ||
@@ -101,7 +164,3 @@ * However, it guarantees to complete synchronously (or throw an error) assuming | ||
*/ | ||
export declare function buildExecutionContext<TData = { | ||
[key: string]: any; | ||
}, TVariables = { | ||
[key: string]: any; | ||
}, TContext = any>(args: ExecutionArgs<TData, TVariables, TContext>): ReadonlyArray<GraphQLError> | ExecutionContext; | ||
export declare function buildExecutionContext<TData = any, TVariables = any, TContext = any>(args: ExecutionArgs<TData, TVariables, TContext>): ReadonlyArray<GraphQLError> | ExecutionContext; | ||
/** | ||
@@ -139,7 +198,7 @@ * TODO: consider no longer exporting this function | ||
* If the client-provided arguments to this function do not result in a | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with | ||
* descriptive errors and no data will be returned. | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with descriptive | ||
* errors and no data will be returned. | ||
* | ||
* If the source stream could not be created due to faulty subscription | ||
* resolver logic or underlying systems, the promise will resolve to a single | ||
* If the source stream could not be created due to faulty subscription resolver | ||
* logic or underlying systems, the promise will resolve to a single | ||
* ExecutionResult containing `errors` and no `data`. | ||
@@ -150,6 +209,48 @@ * | ||
* | ||
* Accepts either an object with named arguments, or individual arguments. | ||
* This function does not support incremental delivery (`@defer` and `@stream`). | ||
* If an operation which would defer or stream data is executed with this | ||
* function, each `InitialIncrementalExecutionResult` and | ||
* `SubsequentIncrementalExecutionResult` in the result stream will be replaced | ||
* with an `ExecutionResult` with a single error stating that defer/stream is | ||
* not supported. Use `experimentalSubscribeIncrementally` if you want to | ||
* support incremental delivery. | ||
* | ||
* Accepts an object with named arguments. | ||
*/ | ||
export declare function subscribe(args: ExecutionArgs): MaybePromise<AsyncIterable<ExecutionResult> | ExecutionResult>; | ||
/** | ||
* Implements the "Subscribe" algorithm described in the GraphQL specification, | ||
* including `@defer` and `@stream` as proposed in | ||
* https://github.com/graphql/graphql-spec/pull/742 | ||
* | ||
* Returns a Promise which resolves to either an AsyncIterator (if successful) | ||
* or an ExecutionResult (error). The promise will be rejected if the schema or | ||
* other arguments to this function are invalid, or if the resolved event stream | ||
* is not an async iterable. | ||
* | ||
* If the client-provided arguments to this function do not result in a | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with descriptive | ||
* errors and no data will be returned. | ||
* | ||
* If the source stream could not be created due to faulty subscription resolver | ||
* logic or underlying systems, the promise will resolve to a single | ||
* ExecutionResult containing `errors` and no `data`. | ||
* | ||
* If the operation succeeded, the promise resolves to an AsyncIterator, which | ||
* yields a stream of result representing the response stream. | ||
* | ||
* Each result may be an ExecutionResult with no `hasNext` (if executing the | ||
* event did not use `@defer` or `@stream`), or an | ||
* `InitialIncrementalExecutionResult` or `SubsequentIncrementalExecutionResult` | ||
* (if executing the event used `@defer` or `@stream`). In the case of | ||
* incremental execution results, each event produces a single | ||
* `InitialIncrementalExecutionResult` followed by one or more | ||
* `SubsequentIncrementalExecutionResult`s; all but the last have `hasNext: true`, | ||
* and the last has `hasNext: false`. There is no interleaving between results | ||
* generated from the same original event. | ||
* | ||
* Accepts an object with named arguments. | ||
*/ | ||
export declare function experimentalSubscribeIncrementally(args: ExecutionArgs): MaybePromise<AsyncGenerator<ExecutionResult | InitialIncrementalExecutionResult | SubsequentIncrementalExecutionResult, void, void> | ExecutionResult>; | ||
/** | ||
* Implements the "CreateSourceEventStream" algorithm described in the | ||
@@ -183,2 +284,45 @@ * GraphQL specification, resolving the subscription source event stream. | ||
export declare function createSourceEventStream(args: ExecutionArgs): MaybePromise<AsyncIterable<unknown> | ExecutionResult>; | ||
declare class DeferredFragmentRecord { | ||
type: 'defer'; | ||
errors: Array<GraphQLError>; | ||
label: string | undefined; | ||
path: Array<string | number>; | ||
promise: Promise<void>; | ||
data: Record<string, unknown> | null; | ||
parentContext: AsyncPayloadRecord | undefined; | ||
isCompleted: boolean; | ||
_exeContext: ExecutionContext; | ||
_resolve?: (arg: MaybePromise<Record<string, unknown> | null>) => void; | ||
constructor(opts: { | ||
label: string | undefined; | ||
path: Path | undefined; | ||
parentContext: AsyncPayloadRecord | undefined; | ||
exeContext: ExecutionContext; | ||
}); | ||
addData(data: MaybePromise<Record<string, unknown> | null>): void; | ||
} | ||
declare class StreamRecord { | ||
type: 'stream'; | ||
errors: Array<GraphQLError>; | ||
label: string | undefined; | ||
path: Array<string | number>; | ||
items: Array<unknown> | null; | ||
promise: Promise<void>; | ||
parentContext: AsyncPayloadRecord | undefined; | ||
iterator: AsyncIterator<unknown> | undefined; | ||
isCompletedIterator?: boolean; | ||
isCompleted: boolean; | ||
_exeContext: ExecutionContext; | ||
_resolve?: (arg: MaybePromise<Array<unknown> | null>) => void; | ||
constructor(opts: { | ||
label: string | undefined; | ||
path: Path | undefined; | ||
iterator?: AsyncIterator<unknown>; | ||
parentContext: AsyncPayloadRecord | undefined; | ||
exeContext: ExecutionContext; | ||
}); | ||
addItems(items: MaybePromise<Array<unknown> | null>): void; | ||
setIsCompletedIterator(): void; | ||
} | ||
declare type AsyncPayloadRecord = DeferredFragmentRecord | StreamRecord; | ||
/** | ||
@@ -196,2 +340,2 @@ * This method looks up the field on the given type definition. | ||
export declare function getFieldDef(schema: GraphQLSchema, parentType: GraphQLObjectType, fieldNode: FieldNode): Maybe<GraphQLField<unknown, unknown>>; | ||
export { ExecutionResult } from '@graphql-tools/utils'; | ||
export {}; |
export * from './execution/index.js'; | ||
export * from './directives/index.js'; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
194557
51
3850
+ Added@graphql-tools/utils@8.14.0-alpha-20221029152711-14f4f7a7(transitive)
- Removed@graphql-tools/utils@8.13.0(transitive)
Updated@graphql-tools/utils@8.14.0-alpha-20221029152711-14f4f7a7