Comparing version 17.0.0-alpha.3.canary.pr.4002.b3f6af2e83280d7830b2a01265e0977b7b68e2f4 to 17.0.0-alpha.3.canary.pr.4026.5922420b3b235970ee230497190e28c8290c8f16
@@ -1,18 +0,10 @@ | ||
import type { DeferUsage, FieldDetails } from './collectFields.js'; | ||
import type { DeferUsage, GroupedFieldSet } from './collectFields.js'; | ||
export type DeferUsageSet = ReadonlySet<DeferUsage>; | ||
export interface FieldGroup { | ||
fields: ReadonlyArray<FieldDetails>; | ||
deferUsages?: DeferUsageSet | undefined; | ||
} | ||
export type GroupedFieldSet = Map<string, FieldGroup>; | ||
export interface NewGroupedFieldSetDetails { | ||
export interface FieldPlan { | ||
groupedFieldSet: GroupedFieldSet; | ||
shouldInitiateDefer: boolean; | ||
newGroupedFieldSets: Map<DeferUsageSet, GroupedFieldSet>; | ||
} | ||
export declare function buildFieldPlan( | ||
fields: Map<string, ReadonlyArray<FieldDetails>>, | ||
originalGroupedFieldSet: GroupedFieldSet, | ||
parentDeferUsages?: DeferUsageSet, | ||
): { | ||
groupedFieldSet: GroupedFieldSet; | ||
newGroupedFieldSetDetailsMap: Map<DeferUsageSet, NewGroupedFieldSetDetails>; | ||
}; | ||
): FieldPlan; |
@@ -6,10 +6,13 @@ 'use strict'; | ||
const isSameSet_js_1 = require('../jsutils/isSameSet.js'); | ||
function buildFieldPlan(fields, parentDeferUsages = new Set()) { | ||
function buildFieldPlan( | ||
originalGroupedFieldSet, | ||
parentDeferUsages = new Set(), | ||
) { | ||
const groupedFieldSet = new Map(); | ||
const newGroupedFieldSetDetailsMap = new Map(); | ||
const newGroupedFieldSets = new Map(); | ||
const map = new Map(); | ||
for (const [responseKey, fieldDetailsList] of fields) { | ||
for (const [responseKey, fieldGroup] of originalGroupedFieldSet) { | ||
const deferUsageSet = new Set(); | ||
let inOriginalResult = false; | ||
for (const fieldDetails of fieldDetailsList) { | ||
for (const fieldDetails of fieldGroup) { | ||
const deferUsage = fieldDetails.deferUsage; | ||
@@ -34,50 +37,22 @@ if (deferUsage === undefined) { | ||
} | ||
map.set(responseKey, { deferUsageSet, fieldDetailsList }); | ||
map.set(responseKey, { deferUsageSet, fieldGroup }); | ||
} | ||
for (const [responseKey, { deferUsageSet, fieldDetailsList }] of map) { | ||
for (const [responseKey, { deferUsageSet, fieldGroup }] of map) { | ||
if ((0, isSameSet_js_1.isSameSet)(deferUsageSet, parentDeferUsages)) { | ||
let fieldGroup = groupedFieldSet.get(responseKey); | ||
if (fieldGroup === undefined) { | ||
fieldGroup = { | ||
fields: [], | ||
deferUsages: deferUsageSet, | ||
}; | ||
groupedFieldSet.set(responseKey, fieldGroup); | ||
} | ||
fieldGroup.fields.push(...fieldDetailsList); | ||
groupedFieldSet.set(responseKey, fieldGroup); | ||
continue; | ||
} | ||
let newGroupedFieldSetDetails = (0, getBySet_js_1.getBySet)( | ||
newGroupedFieldSetDetailsMap, | ||
let newGroupedFieldSet = (0, getBySet_js_1.getBySet)( | ||
newGroupedFieldSets, | ||
deferUsageSet, | ||
); | ||
let newGroupedFieldSet; | ||
if (newGroupedFieldSetDetails === undefined) { | ||
if (newGroupedFieldSet === undefined) { | ||
newGroupedFieldSet = new Map(); | ||
newGroupedFieldSetDetails = { | ||
groupedFieldSet: newGroupedFieldSet, | ||
shouldInitiateDefer: Array.from(deferUsageSet).some( | ||
(deferUsage) => !parentDeferUsages.has(deferUsage), | ||
), | ||
}; | ||
newGroupedFieldSetDetailsMap.set( | ||
deferUsageSet, | ||
newGroupedFieldSetDetails, | ||
); | ||
} else { | ||
newGroupedFieldSet = newGroupedFieldSetDetails.groupedFieldSet; | ||
newGroupedFieldSets.set(deferUsageSet, newGroupedFieldSet); | ||
} | ||
let fieldGroup = newGroupedFieldSet.get(responseKey); | ||
if (fieldGroup === undefined) { | ||
fieldGroup = { | ||
fields: [], | ||
deferUsages: deferUsageSet, | ||
}; | ||
newGroupedFieldSet.set(responseKey, fieldGroup); | ||
} | ||
fieldGroup.fields.push(...fieldDetailsList); | ||
newGroupedFieldSet.set(responseKey, fieldGroup); | ||
} | ||
return { | ||
groupedFieldSet, | ||
newGroupedFieldSetDetailsMap, | ||
newGroupedFieldSets, | ||
}; | ||
@@ -84,0 +59,0 @@ } |
@@ -17,2 +17,4 @@ import type { ObjMap } from '../jsutils/ObjMap.js'; | ||
} | ||
export type FieldGroup = ReadonlyArray<FieldDetails>; | ||
export type GroupedFieldSet = Map<string, FieldGroup>; | ||
/** | ||
@@ -36,3 +38,3 @@ * Given a selectionSet, collects all of the fields and returns them. | ||
): { | ||
fields: Map<string, ReadonlyArray<FieldDetails>>; | ||
groupedFieldSet: GroupedFieldSet; | ||
newDeferUsages: ReadonlyArray<DeferUsage>; | ||
@@ -58,6 +60,6 @@ }; | ||
returnType: GraphQLObjectType, | ||
fieldDetails: ReadonlyArray<FieldDetails>, | ||
fieldGroup: FieldGroup, | ||
): { | ||
fields: Map<string, ReadonlyArray<FieldDetails>>; | ||
groupedFieldSet: GroupedFieldSet; | ||
newDeferUsages: ReadonlyArray<DeferUsage>; | ||
}; |
@@ -44,3 +44,3 @@ 'use strict'; | ||
); | ||
return { fields: groupedFieldSet, newDeferUsages }; | ||
return { groupedFieldSet, newDeferUsages }; | ||
} | ||
@@ -65,3 +65,3 @@ exports.collectFields = collectFields; | ||
returnType, | ||
fieldDetails, | ||
fieldGroup, | ||
) { | ||
@@ -78,3 +78,3 @@ const context = { | ||
const newDeferUsages = []; | ||
for (const fieldDetail of fieldDetails) { | ||
for (const fieldDetail of fieldGroup) { | ||
const node = fieldDetail.node; | ||
@@ -92,3 +92,3 @@ if (node.selectionSet) { | ||
return { | ||
fields: subGroupedFieldSet, | ||
groupedFieldSet: subGroupedFieldSet, | ||
newDeferUsages, | ||
@@ -95,0 +95,0 @@ }; |
@@ -20,8 +20,9 @@ import type { Maybe } from '../jsutils/Maybe.js'; | ||
import type { GraphQLSchema } from '../type/schema.js'; | ||
import type { FieldGroup } from './buildFieldPlan.js'; | ||
import type { FieldGroup } from './collectFields.js'; | ||
import type { | ||
ExecutionResult, | ||
ExperimentalIncrementalExecutionResults, | ||
Future, | ||
} from './IncrementalPublisher.js'; | ||
import { IncrementalPublisher } from './IncrementalPublisher.js'; | ||
import { StreamRecord } from './IncrementalPublisher.js'; | ||
/** | ||
@@ -64,3 +65,6 @@ * Terminology | ||
subscribeFieldResolver: GraphQLFieldResolver<any, any>; | ||
incrementalPublisher: IncrementalPublisher; | ||
errors: Array<GraphQLError>; | ||
cancellableStreams: Set<StreamRecord>; | ||
errorPaths: Set<Path | undefined>; | ||
futures: Array<Future>; | ||
} | ||
@@ -67,0 +71,0 @@ export interface ExecutionArgs { |
@@ -44,4 +44,4 @@ 'use strict'; | ||
const buildSubFieldPlan = (0, memoize3_js_1.memoize3)( | ||
(exeContext, returnType, fieldGroup) => { | ||
const { fields: subFields, newDeferUsages } = (0, | ||
(exeContext, returnType, fieldGroup, deferUsages) => { | ||
const { groupedFieldSet: subGroupedFieldSet, newDeferUsages } = (0, | ||
collectFields_js_1.collectSubfields)( | ||
@@ -53,8 +53,8 @@ exeContext.schema, | ||
returnType, | ||
fieldGroup.fields, | ||
fieldGroup, | ||
); | ||
return { | ||
...(0, buildFieldPlan_js_1.buildFieldPlan)( | ||
subFields, | ||
fieldGroup.deferUsages, | ||
subGroupedFieldSet, | ||
deferUsages, | ||
), | ||
@@ -143,20 +143,74 @@ newDeferUsages, | ||
// in this case is the entire response. | ||
const incrementalPublisher = exeContext.incrementalPublisher; | ||
const initialResultRecord = | ||
new IncrementalPublisher_js_1.InitialResultRecord(); | ||
const { errors, errorPaths, futures, cancellableStreams } = exeContext; | ||
try { | ||
const data = executeOperation(exeContext, initialResultRecord); | ||
const data = executeOperation(exeContext); | ||
if ((0, isPromise_js_1.isPromise)(data)) { | ||
return data.then( | ||
(resolved) => | ||
incrementalPublisher.buildDataResponse(initialResultRecord, resolved), | ||
(error) => | ||
incrementalPublisher.buildErrorResponse(initialResultRecord, error), | ||
buildDataResponse( | ||
resolved, | ||
errors, | ||
errorPaths, | ||
futures, | ||
cancellableStreams, | ||
), | ||
(error) => buildErrorResponse(error, errors), | ||
); | ||
} | ||
return incrementalPublisher.buildDataResponse(initialResultRecord, data); | ||
return buildDataResponse( | ||
data, | ||
exeContext.errors, | ||
errorPaths, | ||
futures, | ||
cancellableStreams, | ||
); | ||
} catch (error) { | ||
return incrementalPublisher.buildErrorResponse(initialResultRecord, error); | ||
return buildErrorResponse(error, exeContext.errors); | ||
} | ||
} | ||
function buildDataResponse( | ||
data, | ||
errors, | ||
errorPaths, | ||
futures, | ||
cancellableStreams, | ||
) { | ||
const filteredFutures = filterFutures(errorPaths, futures); | ||
if (filteredFutures.length > 0) { | ||
return (0, IncrementalPublisher_js_1.buildIncrementalResponse)( | ||
data, | ||
errors, | ||
futures, | ||
cancellableStreams, | ||
); | ||
} | ||
return errors.length > 0 ? { errors, data } : { data }; | ||
} | ||
function filterFutures(errorPaths, futures) { | ||
if (errorPaths.size === 0) { | ||
return futures; | ||
} | ||
const filteredFutures = []; | ||
for (const future of futures) { | ||
let currentPath = (0, | ||
IncrementalPublisher_js_1.isDeferredGroupedFieldSetRecord)(future) | ||
? future.path | ||
: future.streamRecord.path; | ||
while (currentPath !== undefined) { | ||
if (errorPaths.has(currentPath)) { | ||
break; | ||
} | ||
currentPath = currentPath.prev; | ||
} | ||
if (errorPaths.has(currentPath)) { | ||
continue; | ||
} | ||
filteredFutures.push(future); | ||
} | ||
return filteredFutures; | ||
} | ||
function buildErrorResponse(error, errors) { | ||
errors.push(error); | ||
return { data: null, errors }; | ||
} | ||
/** | ||
@@ -257,3 +311,6 @@ * Also implements the "Executing requests" section of the GraphQL specification. | ||
subscribeFieldResolver ?? exports.defaultFieldResolver, | ||
incrementalPublisher: new IncrementalPublisher_js_1.IncrementalPublisher(), | ||
errors: [], | ||
errorPaths: new Set(), | ||
futures: [], | ||
cancellableStreams: new Set(), | ||
}; | ||
@@ -266,2 +323,3 @@ } | ||
rootValue: payload, | ||
errors: [], | ||
}; | ||
@@ -272,11 +330,5 @@ } | ||
*/ | ||
function executeOperation(exeContext, initialResultRecord) { | ||
const { | ||
operation, | ||
schema, | ||
fragments, | ||
variableValues, | ||
rootValue, | ||
incrementalPublisher, | ||
} = exeContext; | ||
function executeOperation(exeContext) { | ||
const { operation, schema, fragments, variableValues, rootValue, futures } = | ||
exeContext; | ||
const rootType = schema.getRootType(operation.operation); | ||
@@ -289,3 +341,4 @@ if (rootType == null) { | ||
} | ||
const { fields, newDeferUsages } = (0, collectFields_js_1.collectFields)( | ||
const { groupedFieldSet: originalGroupedFieldSet, newDeferUsages } = (0, | ||
collectFields_js_1.collectFields)( | ||
schema, | ||
@@ -297,16 +350,6 @@ fragments, | ||
); | ||
const { groupedFieldSet, newGroupedFieldSetDetailsMap } = (0, | ||
buildFieldPlan_js_1.buildFieldPlan)(fields); | ||
const newDeferMap = addNewDeferredFragments( | ||
incrementalPublisher, | ||
newDeferUsages, | ||
initialResultRecord, | ||
); | ||
const { groupedFieldSet, newGroupedFieldSets } = (0, | ||
buildFieldPlan_js_1.buildFieldPlan)(originalGroupedFieldSet); | ||
const newDeferMap = addNewDeferredFragments(newDeferUsages); | ||
const path = undefined; | ||
const newDeferredGroupedFieldSetRecords = addNewDeferredGroupedFieldSets( | ||
incrementalPublisher, | ||
newGroupedFieldSetDetailsMap, | ||
newDeferMap, | ||
path, | ||
); | ||
let result; | ||
@@ -321,3 +364,3 @@ switch (operation.operation) { | ||
groupedFieldSet, | ||
initialResultRecord, | ||
undefined, | ||
newDeferMap, | ||
@@ -333,3 +376,2 @@ ); | ||
groupedFieldSet, | ||
initialResultRecord, | ||
newDeferMap, | ||
@@ -347,14 +389,17 @@ ); | ||
groupedFieldSet, | ||
initialResultRecord, | ||
undefined, | ||
newDeferMap, | ||
); | ||
} | ||
executeDeferredGroupedFieldSets( | ||
exeContext, | ||
rootType, | ||
rootValue, | ||
path, | ||
newDeferredGroupedFieldSetRecords, | ||
newDeferMap, | ||
); | ||
if (newGroupedFieldSets.size > 0) { | ||
const newDeferredGroupedFieldSetRecords = executeDeferredGroupedFieldSets( | ||
exeContext, | ||
rootType, | ||
rootValue, | ||
path, | ||
newGroupedFieldSets, | ||
newDeferMap, | ||
); | ||
futures.push(...newDeferredGroupedFieldSetRecords); | ||
} | ||
return result; | ||
@@ -372,3 +417,2 @@ } | ||
groupedFieldSet, | ||
incrementalDataRecord, | ||
deferMap, | ||
@@ -390,3 +434,3 @@ ) { | ||
fieldPath, | ||
incrementalDataRecord, | ||
undefined, | ||
deferMap, | ||
@@ -398,4 +442,4 @@ ); | ||
if ((0, isPromise_js_1.isPromise)(result)) { | ||
return result.then((resolvedResult) => { | ||
results[responseName] = resolvedResult; | ||
return result.then((resolved) => { | ||
results[responseName] = resolved; | ||
return results; | ||
@@ -420,3 +464,3 @@ }); | ||
groupedFieldSet, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -439,3 +483,3 @@ ) { | ||
fieldPath, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -461,3 +505,3 @@ ); | ||
} | ||
// If there are no promises, we can just return the object | ||
// If there are no promises, we can just return the object and any futures | ||
if (!containsPromise) { | ||
@@ -472,3 +516,3 @@ return results; | ||
function toNodes(fieldGroup) { | ||
return fieldGroup.fields.map((fieldDetails) => fieldDetails.node); | ||
return fieldGroup.map((fieldDetails) => fieldDetails.node); | ||
} | ||
@@ -487,6 +531,6 @@ /** | ||
path, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
) { | ||
const fieldName = fieldGroup.fields[0].node.name.value; | ||
const fieldName = fieldGroup[0].node.name.value; | ||
const fieldDef = exeContext.schema.getField(parentType, fieldName); | ||
@@ -512,3 +556,3 @@ if (!fieldDef) { | ||
fieldDef, | ||
fieldGroup.fields[0].node, | ||
fieldGroup[0].node, | ||
exeContext.variableValues, | ||
@@ -529,3 +573,3 @@ ); | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -541,3 +585,3 @@ ); | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -555,5 +599,4 @@ ); | ||
path, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
); | ||
exeContext.incrementalPublisher.filter(path, incrementalDataRecord); | ||
return null; | ||
@@ -570,5 +613,4 @@ }); | ||
path, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
); | ||
exeContext.incrementalPublisher.filter(path, incrementalDataRecord); | ||
return null; | ||
@@ -604,3 +646,3 @@ } | ||
path, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
) { | ||
@@ -619,3 +661,5 @@ const error = (0, locatedError_js_1.locatedError)( | ||
// a null value for this field if one is encountered. | ||
exeContext.incrementalPublisher.addFieldError(incrementalDataRecord, error); | ||
const { errors, errorPaths } = incrementalContext ?? exeContext; | ||
errors.push(error); | ||
errorPaths.add(path); | ||
} | ||
@@ -650,3 +694,3 @@ /** | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -668,3 +712,3 @@ ) { | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -692,3 +736,3 @@ ); | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -712,3 +756,3 @@ ); | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -726,3 +770,3 @@ ); | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -747,3 +791,3 @@ ); | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -760,3 +804,3 @@ ) { | ||
resolved, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -775,5 +819,4 @@ ); | ||
path, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
); | ||
exeContext.incrementalPublisher.filter(path, incrementalDataRecord); | ||
return null; | ||
@@ -801,3 +844,3 @@ } | ||
directives_js_1.GraphQLStreamDirective, | ||
fieldGroup.fields[0].node, | ||
fieldGroup[0].node, | ||
exeContext.variableValues, | ||
@@ -823,12 +866,10 @@ ); | ||
); | ||
const streamedFieldGroup = { | ||
fields: fieldGroup.fields.map((fieldDetails) => ({ | ||
node: fieldDetails.node, | ||
deferUsage: undefined, | ||
})), | ||
}; | ||
const streamedReadonlyArray = fieldGroup.map((fieldDetails) => ({ | ||
node: fieldDetails.node, | ||
deferUsage: undefined, | ||
})); | ||
const streamUsage = { | ||
initialCount: stream.initialCount, | ||
label: typeof stream.label === 'string' ? stream.label : undefined, | ||
fieldGroup: streamedFieldGroup, | ||
fieldGroup: streamedReadonlyArray, | ||
}; | ||
@@ -849,3 +890,3 @@ fieldGroup._streamUsage = streamUsage; | ||
asyncIterator, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -860,23 +901,27 @@ ) { | ||
if (streamUsage && index >= streamUsage.initialCount) { | ||
const earlyReturn = asyncIterator.return; | ||
const streamRecord = new IncrementalPublisher_js_1.StreamRecord({ | ||
label: streamUsage.label, | ||
path, | ||
earlyReturn: | ||
earlyReturn === undefined | ||
? undefined | ||
: earlyReturn.bind(asyncIterator), | ||
earlyReturn: asyncIterator.return?.bind(asyncIterator), | ||
}); | ||
// eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
executeStreamAsyncIterator( | ||
exeContext.cancellableStreams.add(streamRecord); | ||
const firstStreamItems = firstAsyncStreamItems( | ||
streamRecord, | ||
path, | ||
index, | ||
toNodes(fieldGroup), | ||
asyncIterator, | ||
exeContext, | ||
streamUsage.fieldGroup, | ||
info, | ||
itemType, | ||
path, | ||
incrementalDataRecord, | ||
streamRecord, | ||
(currentItemPath, currentItem, currentIncrementalContext) => | ||
completeStreamItems( | ||
streamRecord, | ||
currentItemPath, | ||
currentItem, | ||
exeContext, | ||
currentIncrementalContext, | ||
streamUsage.fieldGroup, | ||
info, | ||
itemType, | ||
), | ||
); | ||
(incrementalContext ?? exeContext).futures.push(firstStreamItems); | ||
break; | ||
@@ -908,3 +953,3 @@ } | ||
itemPath, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -930,3 +975,3 @@ ) | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -944,3 +989,3 @@ ) { | ||
asyncIterator, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -958,31 +1003,36 @@ ); | ||
let containsPromise = false; | ||
let currentParents = incrementalDataRecord; | ||
const completedResults = []; | ||
let index = 0; | ||
let streamRecord; | ||
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, Path_js_1.addPath)(path, index, undefined); | ||
const iterator = result[Symbol.iterator](); | ||
let iteration = iterator.next(); | ||
while (!iteration.done) { | ||
const item = iteration.value; | ||
if (streamUsage && index >= streamUsage.initialCount) { | ||
if (streamRecord === undefined) { | ||
streamRecord = new IncrementalPublisher_js_1.StreamRecord({ | ||
label: streamUsage.label, | ||
path, | ||
}); | ||
} | ||
currentParents = executeStreamField( | ||
const streamRecord = new IncrementalPublisher_js_1.StreamRecord({ | ||
label: streamUsage.label, | ||
path, | ||
itemPath, | ||
}); | ||
const firstStreamItems = firstSyncStreamItems( | ||
streamRecord, | ||
item, | ||
exeContext, | ||
streamUsage.fieldGroup, | ||
info, | ||
itemType, | ||
currentParents, | ||
streamRecord, | ||
index, | ||
iterator, | ||
(currentItemPath, currentItem, currentIncrementalContext) => | ||
completeStreamItems( | ||
streamRecord, | ||
currentItemPath, | ||
currentItem, | ||
exeContext, | ||
currentIncrementalContext, | ||
streamUsage.fieldGroup, | ||
info, | ||
itemType, | ||
), | ||
); | ||
index++; | ||
continue; | ||
(incrementalContext ?? exeContext).futures.push(firstStreamItems); | ||
break; | ||
} | ||
// 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, Path_js_1.addPath)(path, index, undefined); | ||
if ( | ||
@@ -997,3 +1047,3 @@ completeListItemValue( | ||
itemPath, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -1005,6 +1055,4 @@ ) | ||
index++; | ||
iteration = iterator.next(); | ||
} | ||
if (streamRecord !== undefined) { | ||
exeContext.incrementalPublisher.setIsFinalRecord(currentParents); | ||
} | ||
return containsPromise ? Promise.all(completedResults) : completedResults; | ||
@@ -1025,3 +1073,3 @@ } | ||
itemPath, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -1038,3 +1086,3 @@ ) { | ||
item, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -1053,3 +1101,3 @@ ), | ||
item, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -1068,8 +1116,4 @@ ); | ||
itemPath, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
); | ||
exeContext.incrementalPublisher.filter( | ||
itemPath, | ||
incrementalDataRecord, | ||
); | ||
return null; | ||
@@ -1088,5 +1132,4 @@ }), | ||
itemPath, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
); | ||
exeContext.incrementalPublisher.filter(itemPath, incrementalDataRecord); | ||
completedResults.push(null); | ||
@@ -1124,3 +1167,3 @@ } | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -1147,3 +1190,3 @@ ) { | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -1167,3 +1210,3 @@ ), | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -1231,3 +1274,3 @@ ); | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -1251,3 +1294,3 @@ ) { | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -1267,3 +1310,3 @@ ); | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
@@ -1296,9 +1339,3 @@ ); | ||
*/ | ||
function addNewDeferredFragments( | ||
incrementalPublisher, | ||
newDeferUsages, | ||
incrementalDataRecord, | ||
deferMap, | ||
path, | ||
) { | ||
function addNewDeferredFragments(newDeferUsages, deferMap, path) { | ||
if (newDeferUsages.length === 0) { | ||
@@ -1313,8 +1350,5 @@ // Given no DeferUsages, return the existing map, creating one if necessary. | ||
const parentDeferUsage = newDeferUsage.parentDeferUsage; | ||
// If the parent defer usage is not defined, the parent result record is either: | ||
// - the InitialResultRecord, or | ||
// - a StreamItemsRecord, as `@defer` may be nested under `@stream`. | ||
const parent = | ||
parentDeferUsage === undefined | ||
? incrementalDataRecord | ||
? undefined | ||
: deferredFragmentRecordFromDeferUsage(parentDeferUsage, newDeferMap); | ||
@@ -1326,8 +1360,4 @@ // Instantiate the new record. | ||
label: newDeferUsage.label, | ||
parent, | ||
}); | ||
// Report the new record to the Incremental Publisher. | ||
incrementalPublisher.reportNewDeferFragmentRecord( | ||
deferredFragmentRecord, | ||
parent, | ||
); | ||
// Update the map. | ||
@@ -1342,36 +1372,2 @@ newDeferMap.set(newDeferUsage, deferredFragmentRecord); | ||
} | ||
function addNewDeferredGroupedFieldSets( | ||
incrementalPublisher, | ||
newGroupedFieldSetDetailsMap, | ||
deferMap, | ||
path, | ||
) { | ||
const newDeferredGroupedFieldSetRecords = []; | ||
for (const [ | ||
deferUsageSet, | ||
{ groupedFieldSet, shouldInitiateDefer }, | ||
] of newGroupedFieldSetDetailsMap) { | ||
const deferredFragmentRecords = getDeferredFragmentRecords( | ||
deferUsageSet, | ||
deferMap, | ||
); | ||
const deferredGroupedFieldSetRecord = | ||
new IncrementalPublisher_js_1.DeferredGroupedFieldSetRecord({ | ||
path, | ||
deferredFragmentRecords, | ||
groupedFieldSet, | ||
shouldInitiateDefer, | ||
}); | ||
incrementalPublisher.reportNewDeferredGroupedFieldSetRecord( | ||
deferredGroupedFieldSetRecord, | ||
); | ||
newDeferredGroupedFieldSetRecords.push(deferredGroupedFieldSetRecord); | ||
} | ||
return newDeferredGroupedFieldSetRecords; | ||
} | ||
function getDeferredFragmentRecords(deferUsages, deferMap) { | ||
return Array.from(deferUsages).map((deferUsage) => | ||
deferredFragmentRecordFromDeferUsage(deferUsage, deferMap), | ||
); | ||
} | ||
function collectAndExecuteSubfields( | ||
@@ -1383,22 +1379,14 @@ exeContext, | ||
result, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
deferMap, | ||
) { | ||
// Collect sub-fields to execute to complete this value. | ||
const { groupedFieldSet, newGroupedFieldSetDetailsMap, newDeferUsages } = | ||
buildSubFieldPlan(exeContext, returnType, fieldGroup); | ||
const incrementalPublisher = exeContext.incrementalPublisher; | ||
const newDeferMap = addNewDeferredFragments( | ||
incrementalPublisher, | ||
newDeferUsages, | ||
incrementalDataRecord, | ||
deferMap, | ||
path, | ||
); | ||
const newDeferredGroupedFieldSetRecords = addNewDeferredGroupedFieldSets( | ||
incrementalPublisher, | ||
newGroupedFieldSetDetailsMap, | ||
newDeferMap, | ||
path, | ||
); | ||
const { groupedFieldSet, newGroupedFieldSets, newDeferUsages } = | ||
buildSubFieldPlan( | ||
exeContext, | ||
returnType, | ||
fieldGroup, | ||
incrementalContext?.deferUsageSet, | ||
); | ||
const newDeferMap = addNewDeferredFragments(newDeferUsages, deferMap, path); | ||
const subFields = executeFields( | ||
@@ -1410,13 +1398,18 @@ exeContext, | ||
groupedFieldSet, | ||
incrementalDataRecord, | ||
incrementalContext, | ||
newDeferMap, | ||
); | ||
executeDeferredGroupedFieldSets( | ||
exeContext, | ||
returnType, | ||
result, | ||
path, | ||
newDeferredGroupedFieldSetRecords, | ||
newDeferMap, | ||
); | ||
if (newGroupedFieldSets.size > 0) { | ||
const newDeferredGroupedFieldSetRecords = executeDeferredGroupedFieldSets( | ||
exeContext, | ||
returnType, | ||
result, | ||
path, | ||
newGroupedFieldSets, | ||
newDeferMap, | ||
); | ||
(incrementalContext ?? exeContext).futures.push( | ||
...newDeferredGroupedFieldSetRecords, | ||
); | ||
} | ||
return subFields; | ||
@@ -1606,3 +1599,3 @@ } | ||
} | ||
const { fields } = (0, collectFields_js_1.collectFields)( | ||
const { groupedFieldSet } = (0, collectFields_js_1.collectFields)( | ||
schema, | ||
@@ -1614,7 +1607,7 @@ fragments, | ||
); | ||
const firstRootField = fields.entries().next().value; | ||
const [responseName, fieldDetailsList] = firstRootField; | ||
const fieldName = fieldDetailsList[0].node.name.value; | ||
const firstRootField = groupedFieldSet.entries().next().value; | ||
const [responseName, fieldGroup] = firstRootField; | ||
const fieldName = fieldGroup[0].node.name.value; | ||
const fieldDef = schema.getField(rootType, fieldName); | ||
const fieldNodes = fieldDetailsList.map((fieldDetails) => fieldDetails.node); | ||
const fieldNodes = fieldGroup.map((fieldDetails) => fieldDetails.node); | ||
if (!fieldDef) { | ||
@@ -1688,31 +1681,34 @@ throw new GraphQLError_js_1.GraphQLError( | ||
path, | ||
newDeferredGroupedFieldSetRecords, | ||
newGroupedFieldSets, | ||
deferMap, | ||
) { | ||
for (const deferredGroupedFieldSetRecord of newDeferredGroupedFieldSetRecords) { | ||
if (deferredGroupedFieldSetRecord.shouldInitiateDefer) { | ||
// eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
Promise.resolve().then(() => | ||
executeDeferredGroupedFieldSet( | ||
exeContext, | ||
parentType, | ||
sourceValue, | ||
path, | ||
deferredGroupedFieldSetRecord, | ||
deferMap, | ||
), | ||
); | ||
continue; | ||
} | ||
executeDeferredGroupedFieldSet( | ||
exeContext, | ||
parentType, | ||
sourceValue, | ||
path, | ||
deferredGroupedFieldSetRecord, | ||
const newDeferredGroupedFieldSetRecords = []; | ||
for (const [deferUsageSet, groupedFieldSet] of newGroupedFieldSets) { | ||
const deferredFragmentRecords = getDeferredFragmentRecords( | ||
deferUsageSet, | ||
deferMap, | ||
); | ||
const deferredGroupedFieldSetRecord = | ||
new IncrementalPublisher_js_1.DeferredGroupedFieldSetRecord({ | ||
path, | ||
deferUsageSet, | ||
deferredFragmentRecords, | ||
executor: (incrementalContext) => | ||
executeDeferredGroupedFieldSet( | ||
deferredFragmentRecords, | ||
exeContext, | ||
parentType, | ||
sourceValue, | ||
path, | ||
groupedFieldSet, | ||
incrementalContext, | ||
deferMap, | ||
), | ||
}); | ||
newDeferredGroupedFieldSetRecords.push(deferredGroupedFieldSetRecord); | ||
} | ||
return newDeferredGroupedFieldSetRecords; | ||
} | ||
function executeDeferredGroupedFieldSet( | ||
deferredFragmentRecords, | ||
exeContext, | ||
@@ -1722,7 +1718,9 @@ parentType, | ||
path, | ||
deferredGroupedFieldSetRecord, | ||
groupedFieldSet, | ||
incrementalContext, | ||
deferMap, | ||
) { | ||
let data; | ||
try { | ||
const incrementalResult = executeFields( | ||
data = executeFields( | ||
exeContext, | ||
@@ -1732,54 +1730,205 @@ parentType, | ||
path, | ||
deferredGroupedFieldSetRecord.groupedFieldSet, | ||
deferredGroupedFieldSetRecord, | ||
groupedFieldSet, | ||
incrementalContext, | ||
deferMap, | ||
); | ||
if ((0, isPromise_js_1.isPromise)(incrementalResult)) { | ||
incrementalResult.then( | ||
(resolved) => | ||
exeContext.incrementalPublisher.completeDeferredGroupedFieldSet( | ||
deferredGroupedFieldSetRecord, | ||
resolved, | ||
), | ||
(error) => | ||
exeContext.incrementalPublisher.markErroredDeferredGroupedFieldSet( | ||
deferredGroupedFieldSetRecord, | ||
error, | ||
), | ||
); | ||
return; | ||
} | ||
exeContext.incrementalPublisher.completeDeferredGroupedFieldSet( | ||
deferredGroupedFieldSetRecord, | ||
incrementalResult, | ||
); | ||
} catch (error) { | ||
exeContext.incrementalPublisher.markErroredDeferredGroupedFieldSet( | ||
deferredGroupedFieldSetRecord, | ||
error, | ||
incrementalContext.errors.push(error); | ||
return { | ||
deferredFragmentRecords, | ||
path: (0, Path_js_1.pathToArray)(path), | ||
data: null, | ||
errors: incrementalContext.errors, | ||
}; | ||
} | ||
const { errors, errorPaths, futures } = incrementalContext; | ||
if ((0, isPromise_js_1.isPromise)(data)) { | ||
return data.then( | ||
(resolved) => ({ | ||
deferredFragmentRecords, | ||
path: (0, Path_js_1.pathToArray)(path), | ||
data: resolved, | ||
futures: filterFutures(errorPaths, futures), | ||
errors, | ||
}), | ||
(error) => { | ||
incrementalContext.errors.push(error); | ||
return { | ||
deferredFragmentRecords, | ||
path: (0, Path_js_1.pathToArray)(path), | ||
data: null, | ||
errors, | ||
}; | ||
}, | ||
); | ||
} | ||
return { | ||
deferredFragmentRecords, | ||
path: (0, Path_js_1.pathToArray)(path), | ||
data, | ||
futures: filterFutures(errorPaths, futures), | ||
errors, | ||
}; | ||
} | ||
function executeStreamField( | ||
function getDeferredFragmentRecords(deferUsages, deferMap) { | ||
return Array.from(deferUsages).map((deferUsage) => | ||
deferredFragmentRecordFromDeferUsage(deferUsage, deferMap), | ||
); | ||
} | ||
function firstSyncStreamItems( | ||
streamRecord, | ||
initialItem, | ||
initialIndex, | ||
iterator, | ||
executor, | ||
) { | ||
const path = streamRecord.path; | ||
const initialPath = (0, Path_js_1.addPath)(path, initialIndex, undefined); | ||
const firstStreamItems = new IncrementalPublisher_js_1.StreamItemsRecord({ | ||
streamRecord, | ||
executor: (incrementalContext) => | ||
Promise.resolve().then(() => { | ||
const firstResult = executor( | ||
initialPath, | ||
initialItem, | ||
incrementalContext, | ||
); | ||
let currentIndex = initialIndex; | ||
let currentStreamItems = firstStreamItems; | ||
let iteration = iterator.next(); | ||
while (!iteration.done) { | ||
const item = iteration.value; | ||
currentIndex++; | ||
const currentPath = (0, Path_js_1.addPath)( | ||
path, | ||
currentIndex, | ||
undefined, | ||
); | ||
const nextStreamItems = | ||
new IncrementalPublisher_js_1.StreamItemsRecord({ | ||
streamRecord, | ||
executor: (nextIncrementalContext) => | ||
executor(currentPath, item, nextIncrementalContext), | ||
}); | ||
currentStreamItems.nextStreamItems = nextStreamItems; | ||
currentStreamItems = nextStreamItems; | ||
iteration = iterator.next(); | ||
} | ||
currentStreamItems.nextStreamItems = | ||
new IncrementalPublisher_js_1.StreamItemsRecord({ | ||
streamRecord, | ||
executor: () => ({ streamRecord }), | ||
}); | ||
return firstResult; | ||
}), | ||
}); | ||
return firstStreamItems; | ||
} | ||
function firstAsyncStreamItems( | ||
streamRecord, | ||
path, | ||
initialIndex, | ||
nodes, | ||
asyncIterator, | ||
executor, | ||
) { | ||
const firstStreamItems = new IncrementalPublisher_js_1.StreamItemsRecord({ | ||
streamRecord, | ||
executor: (incrementalContext) => | ||
Promise.resolve().then(() => | ||
getNextAsyncStreamItemsResult( | ||
streamRecord, | ||
firstStreamItems, | ||
path, | ||
initialIndex, | ||
incrementalContext, | ||
nodes, | ||
asyncIterator, | ||
executor, | ||
), | ||
), | ||
}); | ||
return firstStreamItems; | ||
} | ||
async function getNextAsyncStreamItemsResult( | ||
streamRecord, | ||
initialStreamItemsRecord, | ||
path, | ||
index, | ||
incrementalContext, | ||
nodes, | ||
asyncIterator, | ||
executor, | ||
) { | ||
let iteration; | ||
try { | ||
iteration = await asyncIterator.next(); | ||
} catch (error) { | ||
return { | ||
streamRecord, | ||
items: null, | ||
errors: [ | ||
(0, locatedError_js_1.locatedError)( | ||
error, | ||
nodes, | ||
(0, Path_js_1.pathToArray)(path), | ||
), | ||
], | ||
}; | ||
} | ||
if (iteration.done) { | ||
return { streamRecord }; | ||
} | ||
const itemPath = (0, Path_js_1.addPath)(path, index, undefined); | ||
const result = executor(itemPath, iteration.value, incrementalContext); | ||
const nextStreamItems = nextAsyncStreamItems( | ||
streamRecord, | ||
path, | ||
index + 1, | ||
nodes, | ||
asyncIterator, | ||
executor, | ||
); | ||
initialStreamItemsRecord.nextStreamItems = nextStreamItems; | ||
return result; | ||
} | ||
function nextAsyncStreamItems( | ||
streamRecord, | ||
path, | ||
initialIndex, | ||
nodes, | ||
asyncIterator, | ||
executor, | ||
) { | ||
const nextStreamItems = new IncrementalPublisher_js_1.StreamItemsRecord({ | ||
streamRecord, | ||
executor: (incrementalContext) => | ||
Promise.resolve().then(() => | ||
getNextAsyncStreamItemsResult( | ||
streamRecord, | ||
nextStreamItems, | ||
path, | ||
initialIndex, | ||
incrementalContext, | ||
nodes, | ||
asyncIterator, | ||
executor, | ||
), | ||
), | ||
}); | ||
return nextStreamItems; | ||
} | ||
function completeStreamItems( | ||
streamRecord, | ||
itemPath, | ||
item, | ||
exeContext, | ||
incrementalContext, | ||
fieldGroup, | ||
info, | ||
itemType, | ||
incrementalDataRecord, | ||
streamRecord, | ||
) { | ||
const incrementalPublisher = exeContext.incrementalPublisher; | ||
const streamItemsRecord = new IncrementalPublisher_js_1.StreamItemsRecord({ | ||
streamRecord, | ||
path: itemPath, | ||
}); | ||
incrementalPublisher.reportNewStreamItemsRecord( | ||
streamItemsRecord, | ||
incrementalDataRecord, | ||
); | ||
const { errors, errorPaths, futures } = incrementalContext; | ||
if ((0, isPromise_js_1.isPromise)(item)) { | ||
completePromisedValue( | ||
return completePromisedValue( | ||
exeContext, | ||
@@ -1791,18 +1940,20 @@ itemType, | ||
item, | ||
streamItemsRecord, | ||
incrementalContext, | ||
new Map(), | ||
).then( | ||
(value) => | ||
incrementalPublisher.completeStreamItemsRecord(streamItemsRecord, [ | ||
value, | ||
]), | ||
(resolvedItem) => ({ | ||
streamRecord, | ||
items: [resolvedItem], | ||
futures: filterFutures(errorPaths, futures), | ||
errors, | ||
}), | ||
(error) => { | ||
incrementalPublisher.filter(path, streamItemsRecord); | ||
incrementalPublisher.markErroredStreamItemsRecord( | ||
streamItemsRecord, | ||
error, | ||
); | ||
errors.push(error); | ||
return { | ||
streamRecord, | ||
items: null, | ||
errors, | ||
}; | ||
}, | ||
); | ||
return streamItemsRecord; | ||
} | ||
@@ -1819,3 +1970,3 @@ let completedItem; | ||
item, | ||
streamItemsRecord, | ||
incrementalContext, | ||
new Map(), | ||
@@ -1830,14 +1981,16 @@ ); | ||
itemPath, | ||
streamItemsRecord, | ||
incrementalContext, | ||
); | ||
completedItem = null; | ||
incrementalPublisher.filter(itemPath, streamItemsRecord); | ||
} | ||
} catch (error) { | ||
incrementalPublisher.filter(path, streamItemsRecord); | ||
incrementalPublisher.markErroredStreamItemsRecord(streamItemsRecord, error); | ||
return streamItemsRecord; | ||
incrementalContext.errors.push(error); | ||
return { | ||
streamRecord, | ||
items: null, | ||
errors, | ||
}; | ||
} | ||
if ((0, isPromise_js_1.isPromise)(completedItem)) { | ||
completedItem | ||
return completedItem | ||
.then(undefined, (rawError) => { | ||
@@ -1850,167 +2003,29 @@ handleFieldError( | ||
itemPath, | ||
streamItemsRecord, | ||
incrementalContext, | ||
); | ||
incrementalPublisher.filter(itemPath, streamItemsRecord); | ||
return null; | ||
}) | ||
.then( | ||
(value) => | ||
incrementalPublisher.completeStreamItemsRecord(streamItemsRecord, [ | ||
value, | ||
]), | ||
(resolvedItem) => ({ | ||
streamRecord, | ||
items: [resolvedItem], | ||
futures: filterFutures(errorPaths, futures), | ||
errors, | ||
}), | ||
(error) => { | ||
incrementalPublisher.filter(path, streamItemsRecord); | ||
incrementalPublisher.markErroredStreamItemsRecord( | ||
streamItemsRecord, | ||
error, | ||
); | ||
incrementalContext.errors.push(error); | ||
return { | ||
streamRecord, | ||
items: null, | ||
errors, | ||
}; | ||
}, | ||
); | ||
return streamItemsRecord; | ||
} | ||
incrementalPublisher.completeStreamItemsRecord(streamItemsRecord, [ | ||
completedItem, | ||
]); | ||
return streamItemsRecord; | ||
return { | ||
streamRecord, | ||
items: [completedItem], | ||
futures: filterFutures(errorPaths, futures), | ||
errors, | ||
}; | ||
} | ||
async function executeStreamAsyncIteratorItem( | ||
asyncIterator, | ||
exeContext, | ||
fieldGroup, | ||
info, | ||
itemType, | ||
streamItemsRecord, | ||
itemPath, | ||
) { | ||
let item; | ||
try { | ||
const iteration = await asyncIterator.next(); | ||
if (streamItemsRecord.streamRecord.errors.length > 0) { | ||
return { done: true, value: undefined }; | ||
} | ||
if (iteration.done) { | ||
exeContext.incrementalPublisher.setIsCompletedAsyncIterator( | ||
streamItemsRecord, | ||
); | ||
return { done: true, value: undefined }; | ||
} | ||
item = iteration.value; | ||
} catch (rawError) { | ||
throw (0, locatedError_js_1.locatedError)( | ||
rawError, | ||
toNodes(fieldGroup), | ||
streamItemsRecord.streamRecord.path, | ||
); | ||
} | ||
let completedItem; | ||
try { | ||
completedItem = completeValue( | ||
exeContext, | ||
itemType, | ||
fieldGroup, | ||
info, | ||
itemPath, | ||
item, | ||
streamItemsRecord, | ||
new Map(), | ||
); | ||
if ((0, isPromise_js_1.isPromise)(completedItem)) { | ||
completedItem = completedItem.then(undefined, (rawError) => { | ||
handleFieldError( | ||
rawError, | ||
exeContext, | ||
itemType, | ||
fieldGroup, | ||
itemPath, | ||
streamItemsRecord, | ||
); | ||
exeContext.incrementalPublisher.filter(itemPath, streamItemsRecord); | ||
return null; | ||
}); | ||
} | ||
return { done: false, value: completedItem }; | ||
} catch (rawError) { | ||
handleFieldError( | ||
rawError, | ||
exeContext, | ||
itemType, | ||
fieldGroup, | ||
itemPath, | ||
streamItemsRecord, | ||
); | ||
exeContext.incrementalPublisher.filter(itemPath, streamItemsRecord); | ||
return { done: false, value: null }; | ||
} | ||
} | ||
async function executeStreamAsyncIterator( | ||
initialIndex, | ||
asyncIterator, | ||
exeContext, | ||
fieldGroup, | ||
info, | ||
itemType, | ||
path, | ||
incrementalDataRecord, | ||
streamRecord, | ||
) { | ||
const incrementalPublisher = exeContext.incrementalPublisher; | ||
let index = initialIndex; | ||
let currentIncrementalDataRecord = incrementalDataRecord; | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
const itemPath = (0, Path_js_1.addPath)(path, index, undefined); | ||
const streamItemsRecord = new IncrementalPublisher_js_1.StreamItemsRecord({ | ||
streamRecord, | ||
path: itemPath, | ||
}); | ||
incrementalPublisher.reportNewStreamItemsRecord( | ||
streamItemsRecord, | ||
currentIncrementalDataRecord, | ||
); | ||
let iteration; | ||
try { | ||
// eslint-disable-next-line no-await-in-loop | ||
iteration = await executeStreamAsyncIteratorItem( | ||
asyncIterator, | ||
exeContext, | ||
fieldGroup, | ||
info, | ||
itemType, | ||
streamItemsRecord, | ||
itemPath, | ||
); | ||
} catch (error) { | ||
incrementalPublisher.filter(path, streamItemsRecord); | ||
incrementalPublisher.markErroredStreamItemsRecord( | ||
streamItemsRecord, | ||
error, | ||
); | ||
return; | ||
} | ||
const { done, value: completedItem } = iteration; | ||
if ((0, isPromise_js_1.isPromise)(completedItem)) { | ||
completedItem.then( | ||
(value) => | ||
incrementalPublisher.completeStreamItemsRecord(streamItemsRecord, [ | ||
value, | ||
]), | ||
(error) => { | ||
incrementalPublisher.filter(path, streamItemsRecord); | ||
incrementalPublisher.markErroredStreamItemsRecord( | ||
streamItemsRecord, | ||
error, | ||
); | ||
}, | ||
); | ||
} else { | ||
incrementalPublisher.completeStreamItemsRecord(streamItemsRecord, [ | ||
completedItem, | ||
]); | ||
} | ||
if (done) { | ||
break; | ||
} | ||
currentIncrementalDataRecord = streamItemsRecord; | ||
index++; | ||
} | ||
} |
import type { ObjMap } from '../jsutils/ObjMap.js'; | ||
import type { Path } from '../jsutils/Path.js'; | ||
import type { PromiseOrValue } from '../jsutils/PromiseOrValue.js'; | ||
import type { | ||
@@ -7,8 +8,3 @@ GraphQLError, | ||
} from '../error/GraphQLError.js'; | ||
import type { GroupedFieldSet } from './buildFieldPlan.js'; | ||
interface IncrementalUpdate<TData = unknown, TExtensions = ObjMap<unknown>> { | ||
pending: ReadonlyArray<PendingResult>; | ||
incremental: ReadonlyArray<IncrementalResult<TData, TExtensions>>; | ||
completed: ReadonlyArray<CompletedResult>; | ||
} | ||
import type { DeferUsageSet } from './buildFieldPlan.js'; | ||
/** | ||
@@ -71,3 +67,6 @@ * The result of GraphQL execution. | ||
TExtensions = ObjMap<unknown>, | ||
> extends Partial<IncrementalUpdate<TData, TExtensions>> { | ||
> { | ||
pending?: ReadonlyArray<PendingResult>; | ||
incremental?: ReadonlyArray<IncrementalResult<TData, TExtensions>>; | ||
completed?: ReadonlyArray<CompletedResult>; | ||
hasNext: boolean; | ||
@@ -149,113 +148,50 @@ extensions?: TExtensions; | ||
} | ||
/** | ||
* This class is used to publish incremental results to the client, enabling semi-concurrent | ||
* execution while preserving result order. | ||
* | ||
* The internal publishing state is managed as follows: | ||
* | ||
* '_released': the set of Subsequent Result records that are ready to be sent to the client, | ||
* i.e. their parents have completed and they have also completed. | ||
* | ||
* `_pending`: the set of Subsequent Result records that are definitely pending, i.e. their | ||
* parents have completed so that they can no longer be filtered. This includes all Subsequent | ||
* Result records in `released`, as well as the records that have not yet completed. | ||
* | ||
* @internal | ||
*/ | ||
export declare class IncrementalPublisher { | ||
private _nextId; | ||
private _released; | ||
private _pending; | ||
private _signalled; | ||
private _resolve; | ||
constructor(); | ||
reportNewDeferFragmentRecord( | ||
deferredFragmentRecord: DeferredFragmentRecord, | ||
parentIncrementalResultRecord: | ||
| InitialResultRecord | ||
| DeferredFragmentRecord | ||
| StreamItemsRecord, | ||
): void; | ||
reportNewDeferredGroupedFieldSetRecord( | ||
deferredGroupedFieldSetRecord: DeferredGroupedFieldSetRecord, | ||
): void; | ||
reportNewStreamItemsRecord( | ||
streamItemsRecord: StreamItemsRecord, | ||
parentIncrementalDataRecord: IncrementalDataRecord, | ||
): void; | ||
completeDeferredGroupedFieldSet( | ||
deferredGroupedFieldSetRecord: DeferredGroupedFieldSetRecord, | ||
data: ObjMap<unknown>, | ||
): void; | ||
markErroredDeferredGroupedFieldSet( | ||
deferredGroupedFieldSetRecord: DeferredGroupedFieldSetRecord, | ||
error: GraphQLError, | ||
): void; | ||
completeDeferredFragmentRecord( | ||
deferredFragmentRecord: DeferredFragmentRecord, | ||
): void; | ||
completeStreamItemsRecord( | ||
streamItemsRecord: StreamItemsRecord, | ||
items: Array<unknown>, | ||
): void; | ||
markErroredStreamItemsRecord( | ||
streamItemsRecord: StreamItemsRecord, | ||
error: GraphQLError, | ||
): void; | ||
setIsFinalRecord(streamItemsRecord: StreamItemsRecord): void; | ||
setIsCompletedAsyncIterator(streamItemsRecord: StreamItemsRecord): void; | ||
addFieldError( | ||
incrementalDataRecord: IncrementalDataRecord, | ||
error: GraphQLError, | ||
): void; | ||
buildDataResponse( | ||
initialResultRecord: InitialResultRecord, | ||
data: ObjMap<unknown> | null, | ||
): ExecutionResult | ExperimentalIncrementalExecutionResults; | ||
buildErrorResponse( | ||
initialResultRecord: InitialResultRecord, | ||
error: GraphQLError, | ||
): ExecutionResult; | ||
filter( | ||
nullPath: Path | undefined, | ||
erroringIncrementalDataRecord: IncrementalDataRecord, | ||
): void; | ||
private _pendingSourcesToResults; | ||
private _getNextId; | ||
private _subscribe; | ||
private _trigger; | ||
private _reset; | ||
private _introduce; | ||
private _release; | ||
private _push; | ||
private _getIncrementalResult; | ||
private _processPending; | ||
private _getIncrementalDeferResult; | ||
private _completedRecordToResult; | ||
private _publish; | ||
private _getChildren; | ||
private _getDescendants; | ||
private _nullsChildSubsequentResultRecord; | ||
private _matchesPath; | ||
} | ||
/** @internal */ | ||
export declare class InitialResultRecord { | ||
export declare function buildIncrementalResponse( | ||
result: ObjMap<unknown>, | ||
errors: ReadonlyArray<GraphQLError>, | ||
futures: ReadonlyArray<Future>, | ||
cancellableStreams: Set<StreamRecord>, | ||
): ExperimentalIncrementalExecutionResults; | ||
export declare function isDeferredFragmentRecord( | ||
subsequentResultRecord: SubsequentResultRecord, | ||
): subsequentResultRecord is DeferredFragmentRecord; | ||
export declare function isDeferredGroupedFieldSetRecord( | ||
future: Future, | ||
): future is DeferredGroupedFieldSetRecord; | ||
export interface IncrementalContext { | ||
deferUsageSet: DeferUsageSet | undefined; | ||
errors: Array<GraphQLError>; | ||
children: Set<SubsequentResultRecord>; | ||
constructor(); | ||
errorPaths: Set<Path>; | ||
futures: Array<Future>; | ||
} | ||
export interface DeferredGroupedFieldSetResult { | ||
deferredFragmentRecords: ReadonlyArray<DeferredFragmentRecord>; | ||
path: Array<string | number>; | ||
data: ObjMap<unknown> | null; | ||
futures?: ReadonlyArray<Future>; | ||
errors: ReadonlyArray<GraphQLError>; | ||
} | ||
export declare function isDeferredGroupedFieldSetResult( | ||
subsequentResult: DeferredGroupedFieldSetResult | StreamItemsResult, | ||
): subsequentResult is DeferredGroupedFieldSetResult; | ||
interface ReconcilableDeferredGroupedFieldSetResult | ||
extends DeferredGroupedFieldSetResult { | ||
data: ObjMap<unknown>; | ||
sent?: true | undefined; | ||
} | ||
export declare function isReconcilableDeferredGroupedFieldSetResult( | ||
deferredGroupedFieldSetResult: DeferredGroupedFieldSetResult, | ||
): deferredGroupedFieldSetResult is ReconcilableDeferredGroupedFieldSetResult; | ||
/** @internal */ | ||
export declare class DeferredGroupedFieldSetRecord { | ||
path: ReadonlyArray<string | number>; | ||
path: Path | undefined; | ||
deferredFragmentRecords: ReadonlyArray<DeferredFragmentRecord>; | ||
groupedFieldSet: GroupedFieldSet; | ||
shouldInitiateDefer: boolean; | ||
errors: Array<GraphQLError>; | ||
data: ObjMap<unknown> | undefined; | ||
sent: boolean; | ||
result: PromiseOrValue<DeferredGroupedFieldSetResult>; | ||
constructor(opts: { | ||
path: Path | undefined; | ||
deferUsageSet: DeferUsageSet; | ||
deferredFragmentRecords: ReadonlyArray<DeferredFragmentRecord>; | ||
groupedFieldSet: GroupedFieldSet; | ||
shouldInitiateDefer: boolean; | ||
executor: ( | ||
incrementalContext: IncrementalContext, | ||
) => PromiseOrValue<DeferredGroupedFieldSetResult>; | ||
}); | ||
@@ -265,12 +201,15 @@ } | ||
export declare class DeferredFragmentRecord { | ||
path: ReadonlyArray<string | number>; | ||
path: Path | undefined; | ||
label: string | undefined; | ||
id: string | undefined; | ||
children: Set<SubsequentResultRecord>; | ||
deferredGroupedFieldSetRecords: Set<DeferredGroupedFieldSetRecord>; | ||
errors: Array<GraphQLError>; | ||
filtered: boolean; | ||
pendingSent?: boolean; | ||
_pending: Set<DeferredGroupedFieldSetRecord>; | ||
constructor(opts: { path: Path | undefined; label: string | undefined }); | ||
deferredGroupedFieldSetRecords: Array<DeferredGroupedFieldSetRecord>; | ||
results: Array<DeferredGroupedFieldSetResult>; | ||
reconcilableResults: Array<ReconcilableDeferredGroupedFieldSetResult>; | ||
parent: DeferredFragmentRecord | undefined; | ||
children: Set<DeferredFragmentRecord>; | ||
id?: string | undefined; | ||
constructor(opts: { | ||
path: Path | undefined; | ||
label: string | undefined; | ||
parent: DeferredFragmentRecord | undefined; | ||
}); | ||
} | ||
@@ -280,7 +219,5 @@ /** @internal */ | ||
label: string | undefined; | ||
path: ReadonlyArray<string | number>; | ||
id: string | undefined; | ||
errors: Array<GraphQLError>; | ||
earlyReturn?: (() => Promise<unknown>) | undefined; | ||
pendingSent?: boolean; | ||
path: Path; | ||
earlyReturn: (() => Promise<unknown>) | undefined; | ||
id?: string | undefined; | ||
constructor(opts: { | ||
@@ -292,20 +229,34 @@ label: string | undefined; | ||
} | ||
interface NonTerminatingStreamItemsResult { | ||
streamRecord: StreamRecord; | ||
items: ReadonlyArray<unknown> | null; | ||
futures?: ReadonlyArray<Future>; | ||
errors: ReadonlyArray<GraphQLError>; | ||
} | ||
interface TerminatingStreamItemsResult { | ||
streamRecord: StreamRecord; | ||
items?: never; | ||
futures?: never; | ||
errors?: never; | ||
} | ||
export type StreamItemsResult = | ||
| NonTerminatingStreamItemsResult | ||
| TerminatingStreamItemsResult; | ||
/** @internal */ | ||
export declare class StreamItemsRecord { | ||
errors: Array<GraphQLError>; | ||
streamRecord: StreamRecord; | ||
path: ReadonlyArray<string | number>; | ||
items: Array<unknown> | undefined; | ||
children: Set<SubsequentResultRecord>; | ||
isFinalRecord?: boolean; | ||
isCompletedAsyncIterator?: boolean; | ||
isCompleted: boolean; | ||
filtered: boolean; | ||
constructor(opts: { streamRecord: StreamRecord; path: Path | undefined }); | ||
nextStreamItems: StreamItemsRecord | undefined; | ||
private _result; | ||
constructor(opts: { | ||
streamRecord: StreamRecord; | ||
executor: ( | ||
incrementalContext: IncrementalContext, | ||
) => PromiseOrValue<StreamItemsResult>; | ||
}); | ||
getResult(): PromiseOrValue<StreamItemsResult>; | ||
private _prependNextStreamItems; | ||
} | ||
export type IncrementalDataRecord = | ||
| InitialResultRecord | ||
| DeferredGroupedFieldSetRecord | ||
| StreamItemsRecord; | ||
type SubsequentResultRecord = DeferredFragmentRecord | StreamItemsRecord; | ||
export type Future = DeferredGroupedFieldSetRecord | StreamItemsRecord; | ||
export type FutureResult = DeferredGroupedFieldSetResult | StreamItemsResult; | ||
type SubsequentResultRecord = DeferredFragmentRecord | StreamRecord; | ||
export {}; |
@@ -7,7 +7,16 @@ 'use strict'; | ||
exports.DeferredGroupedFieldSetRecord = | ||
exports.InitialResultRecord = | ||
exports.IncrementalPublisher = | ||
exports.isReconcilableDeferredGroupedFieldSetResult = | ||
exports.isDeferredGroupedFieldSetResult = | ||
exports.isDeferredGroupedFieldSetRecord = | ||
exports.isDeferredFragmentRecord = | ||
exports.buildIncrementalResponse = | ||
void 0; | ||
const isPromise_js_1 = require('../jsutils/isPromise.js'); | ||
const Path_js_1 = require('../jsutils/Path.js'); | ||
const promiseWithResolvers_js_1 = require('../jsutils/promiseWithResolvers.js'); | ||
function buildIncrementalResponse(result, errors, futures, cancellableStreams) { | ||
const incrementalPublisher = new IncrementalPublisher(cancellableStreams); | ||
return incrementalPublisher.buildResponse(result, errors, futures); | ||
} | ||
exports.buildIncrementalResponse = buildIncrementalResponse; | ||
/** | ||
@@ -17,148 +26,129 @@ * This class is used to publish incremental results to the client, enabling semi-concurrent | ||
* | ||
* The internal publishing state is managed as follows: | ||
* | ||
* '_released': the set of Subsequent Result records that are ready to be sent to the client, | ||
* i.e. their parents have completed and they have also completed. | ||
* | ||
* `_pending`: the set of Subsequent Result records that are definitely pending, i.e. their | ||
* parents have completed so that they can no longer be filtered. This includes all Subsequent | ||
* Result records in `released`, as well as the records that have not yet completed. | ||
* | ||
* @internal | ||
*/ | ||
class IncrementalPublisher { | ||
constructor() { | ||
constructor(cancellableStreams) { | ||
this._cancellableStreams = cancellableStreams; | ||
this._nextId = 0; | ||
this._released = new Set(); | ||
this._pending = new Set(); | ||
this._completedResultQueue = []; | ||
this._newPending = new Set(); | ||
this._incremental = []; | ||
this._completed = []; | ||
this._reset(); | ||
} | ||
reportNewDeferFragmentRecord( | ||
deferredFragmentRecord, | ||
parentIncrementalResultRecord, | ||
) { | ||
parentIncrementalResultRecord.children.add(deferredFragmentRecord); | ||
buildResponse(data, errors, futures) { | ||
this._addFutures(futures); | ||
this._pruneEmpty(); | ||
const pending = this._pendingSourcesToResults(); | ||
const initialResult = | ||
errors.length === 0 | ||
? { data, pending, hasNext: true } | ||
: { errors, data, pending, hasNext: true }; | ||
return { | ||
initialResult, | ||
subsequentResults: this._subscribe(), | ||
}; | ||
} | ||
reportNewDeferredGroupedFieldSetRecord(deferredGroupedFieldSetRecord) { | ||
for (const deferredFragmentRecord of deferredGroupedFieldSetRecord.deferredFragmentRecords) { | ||
deferredFragmentRecord._pending.add(deferredGroupedFieldSetRecord); | ||
deferredFragmentRecord.deferredGroupedFieldSetRecords.add( | ||
deferredGroupedFieldSetRecord, | ||
); | ||
_addFutures(futures) { | ||
for (const future of futures) { | ||
if (isDeferredGroupedFieldSetRecord(future)) { | ||
for (const deferredFragmentRecord of future.deferredFragmentRecords) { | ||
this._addDeferredFragmentRecord(deferredFragmentRecord); | ||
} | ||
const result = future.result; | ||
if ((0, isPromise_js_1.isPromise)(result)) { | ||
// eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
result.then((resolved) => { | ||
this._enqueueCompletedDeferredGroupedFieldSet(resolved); | ||
}); | ||
} else { | ||
this._enqueueCompletedDeferredGroupedFieldSet(result); | ||
} | ||
continue; | ||
} | ||
const streamRecord = future.streamRecord; | ||
if (streamRecord.id === undefined) { | ||
this._newPending.add(streamRecord); | ||
} | ||
const result = future.getResult(); | ||
if ((0, isPromise_js_1.isPromise)(result)) { | ||
// eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
result.then((resolved) => { | ||
this._enqueueCompletedStreamItems(resolved); | ||
}); | ||
} else { | ||
this._enqueueCompletedStreamItems(result); | ||
} | ||
} | ||
} | ||
reportNewStreamItemsRecord(streamItemsRecord, parentIncrementalDataRecord) { | ||
if (isDeferredGroupedFieldSetRecord(parentIncrementalDataRecord)) { | ||
for (const parent of parentIncrementalDataRecord.deferredFragmentRecords) { | ||
parent.children.add(streamItemsRecord); | ||
_addDeferredFragmentRecord(deferredFragmentRecord) { | ||
const parent = deferredFragmentRecord.parent; | ||
if (parent === undefined) { | ||
if (deferredFragmentRecord.id !== undefined) { | ||
return; | ||
} | ||
} else { | ||
parentIncrementalDataRecord.children.add(streamItemsRecord); | ||
this._newPending.add(deferredFragmentRecord); | ||
return; | ||
} | ||
if (parent.children.has(deferredFragmentRecord)) { | ||
return; | ||
} | ||
parent.children.add(deferredFragmentRecord); | ||
this._addDeferredFragmentRecord(parent); | ||
} | ||
completeDeferredGroupedFieldSet(deferredGroupedFieldSetRecord, data) { | ||
deferredGroupedFieldSetRecord.data = data; | ||
for (const deferredFragmentRecord of deferredGroupedFieldSetRecord.deferredFragmentRecords) { | ||
deferredFragmentRecord._pending.delete(deferredGroupedFieldSetRecord); | ||
if (deferredFragmentRecord._pending.size === 0) { | ||
this.completeDeferredFragmentRecord(deferredFragmentRecord); | ||
_pruneEmpty() { | ||
const maybeEmptyNewPending = this._newPending; | ||
this._newPending = new Set(); | ||
for (const node of maybeEmptyNewPending) { | ||
if (isDeferredFragmentRecord(node)) { | ||
if (node.deferredGroupedFieldSetRecords.length > 0) { | ||
this._newPending.add(node); | ||
continue; | ||
} | ||
for (const child of node.children) { | ||
this._addNonEmptyNewPending(child); | ||
} | ||
} else { | ||
this._newPending.add(node); | ||
} | ||
} | ||
} | ||
markErroredDeferredGroupedFieldSet(deferredGroupedFieldSetRecord, error) { | ||
for (const deferredFragmentRecord of deferredGroupedFieldSetRecord.deferredFragmentRecords) { | ||
deferredFragmentRecord.errors.push(error); | ||
this.completeDeferredFragmentRecord(deferredFragmentRecord); | ||
_addNonEmptyNewPending(deferredFragmentRecord) { | ||
if (deferredFragmentRecord.deferredGroupedFieldSetRecords.length > 0) { | ||
this._newPending.add(deferredFragmentRecord); | ||
return; | ||
} | ||
/* c8 ignore next 5 */ | ||
// TODO: add test case for this, if when skipping an empty deferred fragment, the empty fragment has nested children. | ||
for (const child of deferredFragmentRecord.children) { | ||
this._addNonEmptyNewPending(child); | ||
} | ||
} | ||
completeDeferredFragmentRecord(deferredFragmentRecord) { | ||
this._release(deferredFragmentRecord); | ||
} | ||
completeStreamItemsRecord(streamItemsRecord, items) { | ||
streamItemsRecord.items = items; | ||
streamItemsRecord.isCompleted = true; | ||
this._release(streamItemsRecord); | ||
} | ||
markErroredStreamItemsRecord(streamItemsRecord, error) { | ||
streamItemsRecord.streamRecord.errors.push(error); | ||
this.setIsFinalRecord(streamItemsRecord); | ||
streamItemsRecord.isCompleted = true; | ||
streamItemsRecord.streamRecord.earlyReturn?.().catch(() => { | ||
// ignore error | ||
}); | ||
this._release(streamItemsRecord); | ||
} | ||
setIsFinalRecord(streamItemsRecord) { | ||
streamItemsRecord.isFinalRecord = true; | ||
} | ||
setIsCompletedAsyncIterator(streamItemsRecord) { | ||
streamItemsRecord.isCompletedAsyncIterator = true; | ||
this.setIsFinalRecord(streamItemsRecord); | ||
} | ||
addFieldError(incrementalDataRecord, error) { | ||
incrementalDataRecord.errors.push(error); | ||
} | ||
buildDataResponse(initialResultRecord, data) { | ||
for (const child of initialResultRecord.children) { | ||
if (child.filtered) { | ||
continue; | ||
_enqueueCompletedDeferredGroupedFieldSet(result) { | ||
let hasPendingParent = false; | ||
for (const deferredFragmentRecord of result.deferredFragmentRecords) { | ||
if (deferredFragmentRecord.id !== undefined) { | ||
hasPendingParent = true; | ||
} | ||
this._publish(child); | ||
deferredFragmentRecord.results.push(result); | ||
} | ||
const errors = initialResultRecord.errors; | ||
const initialResult = errors.length === 0 ? { data } : { errors, data }; | ||
const pending = this._pending; | ||
if (pending.size > 0) { | ||
const pendingSources = new Set(); | ||
for (const subsequentResultRecord of pending) { | ||
const pendingSource = isStreamItemsRecord(subsequentResultRecord) | ||
? subsequentResultRecord.streamRecord | ||
: subsequentResultRecord; | ||
pendingSources.add(pendingSource); | ||
} | ||
return { | ||
initialResult: { | ||
...initialResult, | ||
pending: this._pendingSourcesToResults(pendingSources), | ||
hasNext: true, | ||
}, | ||
subsequentResults: this._subscribe(), | ||
}; | ||
if (hasPendingParent) { | ||
this._completedResultQueue.push(result); | ||
this._trigger(); | ||
} | ||
return initialResult; | ||
} | ||
buildErrorResponse(initialResultRecord, error) { | ||
const errors = initialResultRecord.errors; | ||
errors.push(error); | ||
return { data: null, errors }; | ||
_enqueueCompletedStreamItems(result) { | ||
this._completedResultQueue.push(result); | ||
this._trigger(); | ||
} | ||
filter(nullPath, erroringIncrementalDataRecord) { | ||
const nullPathArray = (0, Path_js_1.pathToArray)(nullPath); | ||
const streams = new Set(); | ||
const children = this._getChildren(erroringIncrementalDataRecord); | ||
const descendants = this._getDescendants(children); | ||
for (const child of descendants) { | ||
if (!this._nullsChildSubsequentResultRecord(child, nullPathArray)) { | ||
continue; | ||
} | ||
child.filtered = true; | ||
if (isStreamItemsRecord(child)) { | ||
streams.add(child.streamRecord); | ||
} | ||
} | ||
streams.forEach((stream) => { | ||
stream.earlyReturn?.().catch(() => { | ||
// ignore error | ||
}); | ||
}); | ||
} | ||
_pendingSourcesToResults(pendingSources) { | ||
_pendingSourcesToResults() { | ||
const pendingResults = []; | ||
for (const pendingSource of pendingSources) { | ||
pendingSource.pendingSent = true; | ||
const id = this._getNextId(); | ||
for (const pendingSource of this._newPending) { | ||
const id = String(this._getNextId()); | ||
this._pending.add(pendingSource); | ||
pendingSource.id = id; | ||
const pendingResult = { | ||
id, | ||
path: pendingSource.path, | ||
path: (0, Path_js_1.pathToArray)(pendingSource.path), | ||
}; | ||
@@ -170,2 +160,3 @@ if (pendingSource.label !== undefined) { | ||
} | ||
this._newPending.clear(); | ||
return pendingResults; | ||
@@ -179,37 +170,50 @@ } | ||
const _next = async () => { | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
if (isDone) { | ||
return { value: undefined, done: true }; | ||
while (!isDone) { | ||
let pending = []; | ||
let completedResult; | ||
while ( | ||
(completedResult = this._completedResultQueue.shift()) !== undefined | ||
) { | ||
if (isDeferredGroupedFieldSetResult(completedResult)) { | ||
this._handleCompletedDeferredGroupedFieldSet(completedResult); | ||
} else { | ||
this._handleCompletedStreamItems(completedResult); | ||
} | ||
pending = [...pending, ...this._pendingSourcesToResults()]; | ||
} | ||
for (const item of this._released) { | ||
this._pending.delete(item); | ||
if (this._incremental.length > 0 || this._completed.length > 0) { | ||
const hasNext = this._pending.size > 0; | ||
if (!hasNext) { | ||
isDone = true; | ||
} | ||
const subsequentIncrementalExecutionResult = { hasNext }; | ||
if (pending.length > 0) { | ||
subsequentIncrementalExecutionResult.pending = pending; | ||
} | ||
if (this._incremental.length > 0) { | ||
subsequentIncrementalExecutionResult.incremental = | ||
this._incremental; | ||
} | ||
if (this._completed.length > 0) { | ||
subsequentIncrementalExecutionResult.completed = this._completed; | ||
} | ||
this._incremental = []; | ||
this._completed = []; | ||
return { value: subsequentIncrementalExecutionResult, done: false }; | ||
} | ||
const released = this._released; | ||
this._released = new Set(); | ||
const result = this._getIncrementalResult(released); | ||
if (this._pending.size === 0) { | ||
isDone = true; | ||
} | ||
if (result !== undefined) { | ||
return { value: result, done: false }; | ||
} | ||
// eslint-disable-next-line no-await-in-loop | ||
await this._signalled; | ||
} | ||
await returnStreamIterators().catch(() => { | ||
// ignore errors | ||
}); | ||
return { value: undefined, done: true }; | ||
}; | ||
const returnStreamIterators = async () => { | ||
const streams = new Set(); | ||
const descendants = this._getDescendants(this._pending); | ||
for (const subsequentResultRecord of descendants) { | ||
if (isStreamItemsRecord(subsequentResultRecord)) { | ||
streams.add(subsequentResultRecord.streamRecord); | ||
} | ||
} | ||
const promises = []; | ||
streams.forEach((streamRecord) => { | ||
if (streamRecord.earlyReturn) { | ||
for (const streamRecord of this._cancellableStreams) { | ||
if (streamRecord.earlyReturn !== undefined) { | ||
promises.push(streamRecord.earlyReturn()); | ||
} | ||
}); | ||
} | ||
await Promise.all(promises); | ||
@@ -249,96 +253,64 @@ }; | ||
} | ||
_introduce(item) { | ||
this._pending.add(item); | ||
} | ||
_release(item) { | ||
if (this._pending.has(item)) { | ||
this._released.add(item); | ||
this._trigger(); | ||
_handleCompletedDeferredGroupedFieldSet(result) { | ||
if (!isReconcilableDeferredGroupedFieldSetResult(result)) { | ||
for (const deferredFragmentRecord of result.deferredFragmentRecords) { | ||
const id = deferredFragmentRecord.id; | ||
if (id !== undefined) { | ||
this._completed.push({ id, errors: result.errors }); | ||
this._pending.delete(deferredFragmentRecord); | ||
} | ||
} | ||
return; | ||
} | ||
} | ||
_push(item) { | ||
this._released.add(item); | ||
this._pending.add(item); | ||
this._trigger(); | ||
} | ||
_getIncrementalResult(completedRecords) { | ||
const { pending, incremental, completed } = | ||
this._processPending(completedRecords); | ||
const hasNext = this._pending.size > 0; | ||
if (incremental.length === 0 && completed.length === 0 && hasNext) { | ||
return undefined; | ||
for (const deferredFragmentRecord of result.deferredFragmentRecords) { | ||
deferredFragmentRecord.reconcilableResults.push(result); | ||
} | ||
const result = { hasNext }; | ||
if (pending.length) { | ||
result.pending = pending; | ||
if (result.futures) { | ||
this._addFutures(result.futures); | ||
} | ||
if (incremental.length) { | ||
result.incremental = incremental; | ||
} | ||
if (completed.length) { | ||
result.completed = completed; | ||
} | ||
return result; | ||
} | ||
_processPending(completedRecords) { | ||
const newPendingSources = new Set(); | ||
const incrementalResults = []; | ||
const completedResults = []; | ||
for (const subsequentResultRecord of completedRecords) { | ||
for (const child of subsequentResultRecord.children) { | ||
if (child.filtered) { | ||
continue; | ||
} | ||
const pendingSource = isStreamItemsRecord(child) | ||
? child.streamRecord | ||
: child; | ||
if (!pendingSource.pendingSent) { | ||
newPendingSources.add(pendingSource); | ||
} | ||
this._publish(child); | ||
for (const deferredFragmentRecord of result.deferredFragmentRecords) { | ||
const id = deferredFragmentRecord.id; | ||
// TODO: add test case for this. | ||
// Presumably, this can occur if an error causes a fragment to be completed early, | ||
// while an asynchronous deferred grouped field set result is enqueued. | ||
/* c8 ignore next 3 */ | ||
if (id === undefined) { | ||
continue; | ||
} | ||
if (isStreamItemsRecord(subsequentResultRecord)) { | ||
if (subsequentResultRecord.isFinalRecord) { | ||
newPendingSources.delete(subsequentResultRecord.streamRecord); | ||
completedResults.push( | ||
this._completedRecordToResult(subsequentResultRecord.streamRecord), | ||
); | ||
} | ||
if (subsequentResultRecord.isCompletedAsyncIterator) { | ||
// async iterable resolver just finished but there may be pending payloads | ||
const fragmentResults = deferredFragmentRecord.reconcilableResults; | ||
if ( | ||
deferredFragmentRecord.deferredGroupedFieldSetRecords.length !== | ||
fragmentResults.length | ||
) { | ||
continue; | ||
} | ||
for (const fragmentResult of fragmentResults) { | ||
if (fragmentResult.sent) { | ||
continue; | ||
} | ||
if (subsequentResultRecord.streamRecord.errors.length > 0) { | ||
continue; | ||
} | ||
const incrementalResult = { | ||
// safe because `items` is always defined when the record is completed | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
items: subsequentResultRecord.items, | ||
// safe because `id` is defined once the stream has been released as pending | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
id: subsequentResultRecord.streamRecord.id, | ||
fragmentResult.sent = true; | ||
const { bestId, subPath } = this._getBestIdAndSubPath( | ||
id, | ||
deferredFragmentRecord, | ||
fragmentResult, | ||
); | ||
const incrementalEntry = { | ||
data: fragmentResult.data, | ||
id: bestId, | ||
}; | ||
if (subsequentResultRecord.errors.length > 0) { | ||
incrementalResult.errors = subsequentResultRecord.errors; | ||
if (result.errors.length > 0) { | ||
incrementalEntry.errors = fragmentResult.errors; | ||
} | ||
incrementalResults.push(incrementalResult); | ||
} else { | ||
newPendingSources.delete(subsequentResultRecord); | ||
completedResults.push( | ||
this._completedRecordToResult(subsequentResultRecord), | ||
); | ||
if (subsequentResultRecord.errors.length > 0) { | ||
continue; | ||
if (subPath !== undefined) { | ||
incrementalEntry.subPath = subPath; | ||
} | ||
for (const deferredGroupedFieldSetRecord of subsequentResultRecord.deferredGroupedFieldSetRecords) { | ||
if (!deferredGroupedFieldSetRecord.sent) { | ||
deferredGroupedFieldSetRecord.sent = true; | ||
const incrementalResult = this._getIncrementalDeferResult( | ||
deferredGroupedFieldSetRecord, | ||
); | ||
if (deferredGroupedFieldSetRecord.errors.length > 0) { | ||
incrementalResult.errors = deferredGroupedFieldSetRecord.errors; | ||
} | ||
incrementalResults.push(incrementalResult); | ||
this._incremental.push(incrementalEntry); | ||
} | ||
this._completed.push({ id }); | ||
this._pending.delete(deferredFragmentRecord); | ||
for (const child of deferredFragmentRecord.children) { | ||
this._newPending.add(child); | ||
for (const childResult of child.results) { | ||
if (!(0, isPromise_js_1.isPromise)(childResult)) { | ||
this._completedResultQueue.push(childResult); | ||
} | ||
@@ -348,135 +320,117 @@ } | ||
} | ||
return { | ||
pending: this._pendingSourcesToResults(newPendingSources), | ||
incremental: incrementalResults, | ||
completed: completedResults, | ||
}; | ||
this._pruneEmpty(); | ||
} | ||
_getIncrementalDeferResult(deferredGroupedFieldSetRecord) { | ||
const { data, deferredFragmentRecords } = deferredGroupedFieldSetRecord; | ||
let maxLength; | ||
let idWithLongestPath; | ||
for (const deferredFragmentRecord of deferredFragmentRecords) { | ||
const id = deferredFragmentRecord.id; | ||
if (id === undefined) { | ||
continue; | ||
} | ||
const length = deferredFragmentRecord.path.length; | ||
if (maxLength === undefined || length > maxLength) { | ||
maxLength = length; | ||
idWithLongestPath = id; | ||
} | ||
} | ||
const subPath = deferredGroupedFieldSetRecord.path.slice(maxLength); | ||
const incrementalDeferResult = { | ||
// safe because `data``is always defined when the record is completed | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
data: data, | ||
// safe because `id` is always defined once the fragment has been released | ||
// as pending and at least one fragment has been completed, so must have been | ||
// released as pending | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
id: idWithLongestPath, | ||
}; | ||
if (subPath.length > 0) { | ||
incrementalDeferResult.subPath = subPath; | ||
} | ||
return incrementalDeferResult; | ||
} | ||
_completedRecordToResult(completedRecord) { | ||
const result = { | ||
// safe because `id` is defined once the stream has been released as pending | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
id: completedRecord.id, | ||
}; | ||
if (completedRecord.errors.length > 0) { | ||
result.errors = completedRecord.errors; | ||
} | ||
return result; | ||
} | ||
_publish(subsequentResultRecord) { | ||
if (isStreamItemsRecord(subsequentResultRecord)) { | ||
if (subsequentResultRecord.isCompleted) { | ||
this._push(subsequentResultRecord); | ||
return; | ||
} | ||
this._introduce(subsequentResultRecord); | ||
_handleCompletedStreamItems(result) { | ||
const streamRecord = result.streamRecord; | ||
const id = streamRecord.id; | ||
// TODO: Consider adding invariant or non-null assertion, as this should never happen. Since the stream is converted into a linked list | ||
// for ordering purposes, if an entry errors, additional entries will not be processed. | ||
/* c8 ignore next 3 */ | ||
if (id === undefined) { | ||
return; | ||
} | ||
if (subsequentResultRecord._pending.size > 0) { | ||
this._introduce(subsequentResultRecord); | ||
} else if ( | ||
subsequentResultRecord.deferredGroupedFieldSetRecords.size > 0 || | ||
subsequentResultRecord.children.size > 0 | ||
) { | ||
this._push(subsequentResultRecord); | ||
} | ||
} | ||
_getChildren(erroringIncrementalDataRecord) { | ||
const children = new Set(); | ||
if (isDeferredGroupedFieldSetRecord(erroringIncrementalDataRecord)) { | ||
for (const erroringIncrementalResultRecord of erroringIncrementalDataRecord.deferredFragmentRecords) { | ||
for (const child of erroringIncrementalResultRecord.children) { | ||
children.add(child); | ||
} | ||
} | ||
if (result.items === undefined) { | ||
this._completed.push({ id }); | ||
this._pending.delete(streamRecord); | ||
this._cancellableStreams.delete(streamRecord); | ||
} else if (result.items === null) { | ||
this._completed.push({ | ||
id, | ||
errors: result.errors, | ||
}); | ||
this._pending.delete(streamRecord); | ||
this._cancellableStreams.delete(streamRecord); | ||
streamRecord.earlyReturn?.().catch(() => { | ||
/* c8 ignore next 1 */ | ||
// ignore error | ||
}); | ||
} else { | ||
for (const child of erroringIncrementalDataRecord.children) { | ||
children.add(child); | ||
const incrementalEntry = { | ||
id, | ||
items: result.items, // FIX! | ||
}; | ||
if (result.errors !== undefined && result.errors.length > 0) { | ||
incrementalEntry.errors = result.errors; | ||
} | ||
} | ||
return children; | ||
} | ||
_getDescendants(children, descendants = new Set()) { | ||
for (const child of children) { | ||
descendants.add(child); | ||
this._getDescendants(child.children, descendants); | ||
} | ||
return descendants; | ||
} | ||
_nullsChildSubsequentResultRecord(subsequentResultRecord, nullPath) { | ||
const incrementalDataRecords = isStreamItemsRecord(subsequentResultRecord) | ||
? [subsequentResultRecord] | ||
: subsequentResultRecord.deferredGroupedFieldSetRecords; | ||
for (const incrementalDataRecord of incrementalDataRecords) { | ||
if (this._matchesPath(incrementalDataRecord.path, nullPath)) { | ||
return true; | ||
this._incremental.push(incrementalEntry); | ||
if (result.futures) { | ||
this._addFutures(result.futures); | ||
this._pruneEmpty(); | ||
} | ||
} | ||
return false; | ||
} | ||
_matchesPath(testPath, basePath) { | ||
for (let i = 0; i < basePath.length; i++) { | ||
if (basePath[i] !== testPath[i]) { | ||
// testPath points to a path unaffected at basePath | ||
return false; | ||
_getBestIdAndSubPath( | ||
initialId, | ||
initialDeferredFragmentRecord, | ||
deferredGroupedFieldSetResult, | ||
) { | ||
let maxLength = (0, Path_js_1.pathToArray)( | ||
initialDeferredFragmentRecord.path, | ||
).length; | ||
let bestId = initialId; | ||
for (const deferredFragmentRecord of deferredGroupedFieldSetResult.deferredFragmentRecords) { | ||
if (deferredFragmentRecord === initialDeferredFragmentRecord) { | ||
continue; | ||
} | ||
const id = deferredFragmentRecord.id; | ||
// TODO: add test case for when an fragment has not been released, but might be processed for the shortest path. | ||
/* c8 ignore next 3 */ | ||
if (id === undefined) { | ||
continue; | ||
} | ||
const fragmentPath = (0, Path_js_1.pathToArray)( | ||
deferredFragmentRecord.path, | ||
); | ||
const length = fragmentPath.length; | ||
if (length > maxLength) { | ||
maxLength = length; | ||
bestId = id; | ||
} | ||
} | ||
return true; | ||
const subPath = deferredGroupedFieldSetResult.path.slice(maxLength); | ||
return { | ||
bestId, | ||
subPath: subPath.length > 0 ? subPath : undefined, | ||
}; | ||
} | ||
} | ||
exports.IncrementalPublisher = IncrementalPublisher; | ||
function isDeferredGroupedFieldSetRecord(incrementalDataRecord) { | ||
return incrementalDataRecord instanceof DeferredGroupedFieldSetRecord; | ||
function isDeferredFragmentRecord(subsequentResultRecord) { | ||
return subsequentResultRecord instanceof DeferredFragmentRecord; | ||
} | ||
function isStreamItemsRecord(subsequentResultRecord) { | ||
return subsequentResultRecord instanceof StreamItemsRecord; | ||
exports.isDeferredFragmentRecord = isDeferredFragmentRecord; | ||
function isDeferredGroupedFieldSetRecord(future) { | ||
return future instanceof DeferredGroupedFieldSetRecord; | ||
} | ||
/** @internal */ | ||
class InitialResultRecord { | ||
constructor() { | ||
this.errors = []; | ||
this.children = new Set(); | ||
} | ||
exports.isDeferredGroupedFieldSetRecord = isDeferredGroupedFieldSetRecord; | ||
function isDeferredGroupedFieldSetResult(subsequentResult) { | ||
return 'deferredFragmentRecords' in subsequentResult; | ||
} | ||
exports.InitialResultRecord = InitialResultRecord; | ||
exports.isDeferredGroupedFieldSetResult = isDeferredGroupedFieldSetResult; | ||
function isReconcilableDeferredGroupedFieldSetResult( | ||
deferredGroupedFieldSetResult, | ||
) { | ||
return deferredGroupedFieldSetResult.data !== null; | ||
} | ||
exports.isReconcilableDeferredGroupedFieldSetResult = | ||
isReconcilableDeferredGroupedFieldSetResult; | ||
/** @internal */ | ||
class DeferredGroupedFieldSetRecord { | ||
constructor(opts) { | ||
this.path = (0, Path_js_1.pathToArray)(opts.path); | ||
this.deferredFragmentRecords = opts.deferredFragmentRecords; | ||
this.groupedFieldSet = opts.groupedFieldSet; | ||
this.shouldInitiateDefer = opts.shouldInitiateDefer; | ||
this.errors = []; | ||
this.sent = false; | ||
const { path, deferUsageSet, deferredFragmentRecords, executor } = opts; | ||
this.path = path; | ||
this.deferredFragmentRecords = deferredFragmentRecords; | ||
const incrementalContext = { | ||
deferUsageSet, | ||
errors: [], | ||
errorPaths: new Set(), | ||
futures: [], | ||
}; | ||
for (const deferredFragmentRecord of this.deferredFragmentRecords) { | ||
deferredFragmentRecord.deferredGroupedFieldSetRecords.push(this); | ||
} | ||
this.result = this.deferredFragmentRecords.some( | ||
(deferredFragmentRecord) => deferredFragmentRecord.id !== undefined, | ||
) | ||
? executor(incrementalContext) | ||
: Promise.resolve().then(() => executor(incrementalContext)); | ||
} | ||
@@ -488,9 +442,9 @@ } | ||
constructor(opts) { | ||
this.path = (0, Path_js_1.pathToArray)(opts.path); | ||
this.path = opts.path; | ||
this.label = opts.label; | ||
this.deferredGroupedFieldSetRecords = []; | ||
this.results = []; | ||
this.reconcilableResults = []; | ||
this.parent = opts.parent; | ||
this.children = new Set(); | ||
this.filtered = false; | ||
this.deferredGroupedFieldSetRecords = new Set(); | ||
this.errors = []; | ||
this._pending = new Set(); | ||
} | ||
@@ -502,6 +456,6 @@ } | ||
constructor(opts) { | ||
this.label = opts.label; | ||
this.path = (0, Path_js_1.pathToArray)(opts.path); | ||
this.errors = []; | ||
this.earlyReturn = opts.earlyReturn; | ||
const { label, path, earlyReturn } = opts; | ||
this.label = label; | ||
this.path = path; | ||
this.earlyReturn = earlyReturn; | ||
} | ||
@@ -513,10 +467,29 @@ } | ||
constructor(opts) { | ||
this.streamRecord = opts.streamRecord; | ||
this.path = (0, Path_js_1.pathToArray)(opts.path); | ||
this.children = new Set(); | ||
this.errors = []; | ||
this.isCompleted = false; | ||
this.filtered = false; | ||
const { streamRecord, executor } = opts; | ||
this.streamRecord = streamRecord; | ||
const incrementalContext = { | ||
deferUsageSet: undefined, | ||
errors: [], | ||
errorPaths: new Set(), | ||
futures: [], | ||
}; | ||
this._result = executor(incrementalContext); | ||
} | ||
getResult() { | ||
if ((0, isPromise_js_1.isPromise)(this._result)) { | ||
return this._result.then((resolved) => | ||
this._prependNextStreamItems(resolved), | ||
); | ||
} | ||
return this._prependNextStreamItems(this._result); | ||
} | ||
_prependNextStreamItems(result) { | ||
return this.nextStreamItems === undefined | ||
? result | ||
: { | ||
...result, | ||
futures: [this.nextStreamItems, ...(result.futures ?? [])], | ||
}; | ||
} | ||
} | ||
exports.StreamItemsRecord = StreamItemsRecord; |
@@ -9,2 +9,5 @@ /** | ||
R, | ||
>(fn: (a1: A1, a2: A2, a3: A3) => R): (a1: A1, a2: A2, a3: A3) => R; | ||
T extends Array<unknown>, | ||
>( | ||
fn: (a1: A1, a2: A2, a3: A3, ...rest: T) => R, | ||
): (a1: A1, a2: A2, a3: A3, ...rest: T) => R; |
@@ -9,3 +9,3 @@ 'use strict'; | ||
let cache0; | ||
return function memoized(a1, a2, a3) { | ||
return function memoized(a1, a2, a3, ...rest) { | ||
if (cache0 === undefined) { | ||
@@ -26,3 +26,3 @@ cache0 = new WeakMap(); | ||
if (fnResult === undefined) { | ||
fnResult = fn(a1, a2, a3); | ||
fnResult = fn(a1, a2, a3, ...rest); | ||
cache2.set(a3, fnResult); | ||
@@ -29,0 +29,0 @@ } |
{ | ||
"name": "graphql", | ||
"version": "17.0.0-alpha.3.canary.pr.4002.b3f6af2e83280d7830b2a01265e0977b7b68e2f4", | ||
"version": "17.0.0-alpha.3.canary.pr.4026.5922420b3b235970ee230497190e28c8290c8f16", | ||
"description": "A Query Language and Runtime which can target any service.", | ||
@@ -35,7 +35,7 @@ "license": "MIT", | ||
"publishConfig": { | ||
"tag": "canary-pr-4002" | ||
"tag": "canary-pr-4026" | ||
}, | ||
"main": "index", | ||
"module": "index.mjs", | ||
"deprecated": "You are using canary version build from https://github.com/graphql/graphql-js/pull/4002, no gurantees provided so please use your own discretion." | ||
"deprecated": "You are using canary version build from https://github.com/graphql/graphql-js/pull/4026, no gurantees provided so please use your own discretion." | ||
} |
@@ -7,4 +7,4 @@ 'use strict'; | ||
const collectFields_js_1 = require('../../execution/collectFields.js'); | ||
function toNodes(fieldDetailsList) { | ||
return fieldDetailsList.map((fieldDetails) => fieldDetails.node); | ||
function toNodes(fieldGroup) { | ||
return fieldGroup.map((fieldDetails) => fieldDetails.node); | ||
} | ||
@@ -35,3 +35,3 @@ /** | ||
} | ||
const { fields } = (0, collectFields_js_1.collectFields)( | ||
const { groupedFieldSet } = (0, collectFields_js_1.collectFields)( | ||
schema, | ||
@@ -43,4 +43,4 @@ fragments, | ||
); | ||
if (fields.size > 1) { | ||
const fieldGroups = [...fields.values()]; | ||
if (groupedFieldSet.size > 1) { | ||
const fieldGroups = [...groupedFieldSet.values()]; | ||
const extraFieldGroups = fieldGroups.slice(1); | ||
@@ -59,3 +59,3 @@ const extraFieldSelections = extraFieldGroups.flatMap( | ||
} | ||
for (const fieldGroup of fields.values()) { | ||
for (const fieldGroup of groupedFieldSet.values()) { | ||
const fieldName = toNodes(fieldGroup)[0].name.value; | ||
@@ -62,0 +62,0 @@ if (fieldName.startsWith('__')) { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
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
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
1424136
44123