@@ -361,32 +361,81 @@ The Async Payload Records may be published lazily as requested, with the
361361internal state of the unpublished stream held by a Publisher Record unique to
362362the request.
363363
364- - {subsequentPayloads}: the set of Async Payload Records for this response that
365- have not yet been published.
364+ - {pending}: the set of Async Payload Records for this response that have not
365+ yet been completed.
366+ - {waiting}: the set of Async Payload Records for this response that have been
367+ completed, but are waiting for a parent to complete.
368+ - {waitingByParent}: an unordered map of uncompleted parent Async Payload
369+ Records to sets of completed child Async Payload Records.
370+ - {pushed}: a weakly held set of Async Payload Records for this response that
371+ have been completed, but are waiting for a parent to complete.
372+ - {current}: the set of Async Payload Records for this response that may be
373+ yielded on the next request.
374+ - {signal}: An asynchronous signal that can be awaited and triggered.
366375
367376## Create Publisher
368377
369378CreatePublisher():
370379
371380- Let {publisherRecord} be a publisher record.
372- - Initialize {subsequentPayloads} on {publisherRecord} to an empty set.
381+ - Initialize {pending} on {publisherRecord} to an empty set.
382+ - Initialize {waiting} on {publisherRecord} to an empty set.
383+ - Initialize {waitingByParent} on {publisherRecord} to an empty unordered map.
384+ - Initialize {pushed} on {publisherRecord} to an empty set.
385+ - Initialize {current} on {publisherRecord} to an empty set.
386+ - Initialize {signal}.
373387- Return {publisherRecord}.
374388
375389## Has Subsequent Payloads
376390
377391HasSubsequentPayloads(publisherRecord):
378392
379- - Let {subsequentPayloads} be the corresponding entry on {publisherRecord}.
380- - Let {size} be the number of payloads within {subsequentPayloads }.
381- - If {size} is greater than zero , return {true}.
393+ - Let {pending}, {waiting}, and {current} be the corresponding entries on
394+ {publisherRecord }.
395+ - If {pending}, {waiting}, or {current} is not empty , return {true}.
382396- Return {false}.
383397
384398## Add Payload
385399
386400AddPayload(payload, publisherRecord):
387401
388- - Let {subsequentPayloads} be the corresponding entry on {publisherRecord}.
389- - Add {payload} to {subsequentPayloads}.
402+ - Let {pending} be the corresponding entry on {publisherRecord}.
403+ - Add {payload} to {pending}.
404+
405+ ## Complete Payload
406+
407+ CompletePayload(payload, publisherRecord):
408+
409+ - Let {pending} be the corresponding entry on {publisherRecord}.
410+ - If {payload} is not within {pending}, return.
411+ - Remove {payload} from {pending}.
412+ - Let {parentRecord} be the corresponding entry on {payload}.
413+ - If {parentRecord} is not defined:
414+ - Call PushPayload(payload, publisherRecord).
415+ - Let {signal} be the corresponding entry on {publisherRecord}.
416+ - Trigger {signal}.
417+ - Otherwise:
418+ - Let {waiting} and {waitingByChildren} be the corresponding entries on
419+ {publisherRecord}.
420+ - Add {payload} to {waiting}.
421+ - Let {children} be the set in {waitingByParent} for {parentRecord}; if no
422+ such set exists, create it as an empty set.
423+ - Append {payload} to {children}.
424+
425+ ## Push Payload
426+
427+ PushPayload(payload, publisherRecord):
428+
429+ - Let {pushed}, {current}, and {waitingByParent} be the corresponding entries on
430+ {publisherRecord}.
431+ - Add {payload} to {pushed} and {current}.
432+ - Let {children} be the set in {waitingByParent} for {parentRecord}.
433+ - If {children} is not defined, return.
434+ - Let {waiting} be the corresponding entry on {publisherRecord}.
435+ - For each {child} in {children}:
436+ - Call {PushPayload(payload, publisherRecord)}.
437+ - Remove {child} from {waiting}.
438+ - Remove the set in {waitingByParent} for {parentRecord}.
390439
391440## Yield Subsequent Payloads
392441
@@ -396,15 +445,13 @@ payloads should be processed.
396445
397446YieldSubsequentPayloads(initialResponse, publisherRecord):
398447
399- - Let {subsequentPayloads} be the corresponding entry on {publisherRecord}.
400- - Let {initialRecords} be any items in {subsequentPayloads} with a completed
401- {dataExecution}.
448+ - Let {current} be the corresponding entry on {publisherRecord}.
402449- Initialize {initialIncremental} to an empty list.
403- - For each {record} in {initialRecords }:
404- - Remove {record} from {subsequentPayloads }.
450+ - For each {record} in {current }:
451+ - Remove {record} from {current }.
405452 - If {isCompletedIterator} on {record} is {true}:
406453 - Continue to the next record in {records}.
407- - Let {payload} be the completed result returned by {dataExecution }.
454+ - Let {payload} be the corresponding entry on {record }.
408455 - Append {payload} to {initialIncremental}.
409456- If {initialIncremental} is not empty:
410457 - Add an entry to {initialResponse} named ` incremental ` containing the value
@@ -416,17 +463,17 @@ YieldSubsequentPayloads(initialResponse, publisherRecord):
416463 - If {record} contains {iterator}:
417464 - Send a termination signal to {iterator}.
418465 - Return.
419- - Wait for at least one record in {subsequentPayloads} to have a completed
420- {dataExecution}.
466+ - Let {signal} be the corresponding entry on {publisherRecord}.
467+ - Wait for {signal} to be triggered.
468+ - Reinitialize {signal} on {publisherRecord}.
421469 - Let {subsequentResponse} be an unordered map with an entry {incremental}
422470 initialized to an empty list.
423- - Let {records} be the items in {subsequentPayloads} with a completed
424- {dataExecution}.
425- - For each {record} in {records}:
471+ - Let {current} be the corresponding entry on {publisherRecord}.
472+ - For each {record} in {current}:
426473 - Remove {record} from {subsequentPayloads}.
427474 - If {isCompletedIterator} on {record} is {true}:
428475 - Continue to the next record in {records}.
429- - Let {payload} be the completed result returned by {dataExecution }.
476+ - Let {payload} be the corresponding entry on {record }.
430477 - Append {payload} to the {incremental} entry on {subsequentResponse}.
431478 - If {subsequentPayloads} is empty:
432479 - Add an entry to {subsequentResponse} named ` hasNext ` with the value
@@ -505,20 +552,34 @@ outside of {ExecuteDeferredFragment} or {ExecuteStreamField}.
505552
506553FilterSubsequentPayloads(publisherRecord, nullPath, currentAsyncRecord):
507554
508- - Let {subsequentPayloads} be the corresponding entry on {publisherRecord}.
509- - For each {asyncRecord} in {subsequentPayloads}:
510- - If {asyncRecord} is the same record as {currentAsyncRecord}:
511- - Continue to the next record in {subsequentPayloads}.
512- - Initialize {index} to zero.
513- - While {index} is less then the length of {nullPath}:
514- - Initialize {nullPathItem} to the element at {index} in {nullPath}.
515- - Initialize {asyncRecordPathItem} to the element at {index} in the {path}
516- of {asyncRecord}.
517- - If {nullPathItem} is not equivalent to {asyncRecordPathItem}:
518- - Continue to the next record in {subsequentPayloads}.
519- - Increment {index} by one.
520- - Remove {asyncRecord} from {subsequentPayloads}. Optionally, cancel any
521- incomplete work in the execution of {asyncRecord}.
555+ - Let {pending}, {current}, {waiting}, and {waitingByParent} be the
556+ corresponding entries on {publisherRecord}.
557+ - For each {asyncRecord} in {pending} and {current}:
558+ - If {ShouldKeepPayload(asyncRecord, nullPath, currentAsyncRecord)} is {true}:
559+ - Continue to the next record in {set}.
560+ - Remove {asyncRecord} from {set}. Optionally, cancel any incomplete work in
561+ the execution of {asyncRecord}.
562+ - For each {asyncRecord} in {waiting}:
563+ - If {ShouldKeepPayload(asyncRecord, nullPath, currentAsyncRecord)} is {true}:
564+ - Continue to the next record in {waiting}.
565+ - Remove {asyncRecord} from {waiting}. Optionally, cancel any incomplete work
566+ in the execution of {asyncRecord}.
567+ - Let {parentRecord} be the corresponding entry on {asyncRecord}.
568+ - Let {children} be the set in {waitingByParent} for {parentRecord}.
569+ - Remove {asyncRecord} from {children}.
570+
571+ ShouldKeepPayload(asyncRecord, nullPath, currentAsyncRecord):
572+
573+ - If {asyncRecord} is the same record as {currentAsyncRecord}:
574+ - Return {true}.
575+ - Initialize {index} to zero.
576+ - While {index} is less then the length of {nullPath}:
577+ - Initialize {nullPathItem} to the element at {index} in {nullPath}.
578+ - Initialize {asyncRecordPathItem} to the element at {index} in the {path} of
579+ {asyncRecord}.
580+ - If {nullPathItem} is not equivalent to {asyncRecordPathItem}:
581+ - Return {true}.
582+ - Increment {index} by one. Return {false}.
522583
523584For example, assume the field ` alwaysThrows ` is a ` Non-Null ` type that always
524585raises a field error:
@@ -797,51 +858,49 @@ DoesFragmentTypeApply(objectType, fragmentType):
797858An Async Payload Record is either a Deferred Fragment Record or a Stream Record.
798859All Async Payload Records are structures containing:
799860
861+ - {parentRecord}: The generating parent Async Payload Record, not defined if
862+ this Async Payload Record is spawned by the initial result.
800863- {label}: value derived from the corresponding ` @defer ` or ` @stream ` directive.
801864- {path}: a list of field names and indices from root to the location of the
802865 corresponding ` @defer ` or ` @stream ` directive.
803866- {iterator}: The underlying iterator if created from a ` @stream ` directive.
804867- {isCompletedIterator}: a boolean indicating the payload record was generated
805868 from an iterator that has completed.
806869- {errors}: a list of field errors encountered during execution.
807- - {dataExecution}: A result that can notify when the corresponding execution has
808- completed.
870+ - {payload}: An unordered map containing the formatted payload.
809871
810872#### Execute Deferred Fragment
811873
812874ExecuteDeferredFragment(label, objectType, objectValue, groupedFieldSet, path,
813875variableValues, parentRecord, publisherRecord):
814876
815- - Let {deferRecord} be an async payload record created from {label} and {path}.
816- - Initialize {errors} on {deferRecord} to an empty list.
817- - Let {dataExecution} be the asynchronous future value of:
818- - Let {payload} be an unordered map.
819- - Initialize {resultMap} to an empty ordered map.
820- - For each {groupedFieldSet} as {responseKey} and {fields}:
821- - Let {fieldName} be the name of the first entry in {fields}. Note: This
822- value is unaffected if an alias is used.
823- - Let {fieldType} be the return type defined for the field {fieldName} of
824- {objectType}.
825- - If {fieldType} is defined:
826- - Let {responseValue} be {ExecuteField(objectType, objectValue, fieldType,
827- fields, variableValues, path, publisherRecord, asyncRecord)}.
828- - Set {responseValue} as the value for {responseKey} in {resultMap}.
829- - Append any encountered field errors to {errors}.
830- - If {parentRecord} is defined:
831- - Wait for the result of {dataExecution} on {parentRecord}.
832- - If {errors} is not empty:
833- - Add an entry to {payload} named ` errors ` with the value {errors}.
834- - If a field error was raised, causing a {null} to be propagated to
835- {responseValue}:
836- - Add an entry to {payload} named ` data ` with the value {null}.
837- - Otherwise:
838- - Add an entry to {payload} named ` data ` with the value {resultMap}.
839- - If {label} is defined:
840- - Add an entry to {payload} named ` label ` with the value {label}.
841- - Add an entry to {payload} named ` path ` with the value {path}.
842- - Return {payload}.
843- - Set {dataExecution} on {deferredFragmentRecord}.
877+ - Let {deferRecord} be an async payload record created from {parentRecord},
878+ {label}, and {path}.
844879- Call {AddPayload(deferRecord, publisherRecord)}.
880+ - Initialize {errors} on {deferRecord} to an empty list.
881+ - Initialize {resultMap} to an empty ordered map.
882+ - For each {groupedFieldSet} as {responseKey} and {fields}:
883+ - Let {fieldName} be the name of the first entry in {fields}. Note: This value
884+ is unaffected if an alias is used.
885+ - Let {fieldType} be the return type defined for the field {fieldName} of
886+ {objectType}.
887+ - If {fieldType} is defined:
888+ - Let {responseValue} be {ExecuteField(objectType, objectValue, fieldType,
889+ fields, variableValues, path, publisherRecord, asyncRecord)}.
890+ - Set {responseValue} as the value for {responseKey} in {resultMap}.
891+ - Append any encountered field errors to {errors}.
892+ - If {errors} is not empty:
893+ - Add an entry to {payload} named ` errors ` with the value {errors}.
894+ - If a field error was raised, causing a {null} to be propagated to
895+ {responseValue}:
896+ - Add an entry to {payload} named ` data ` with the value {null}.
897+ - Otherwise:
898+ - Add an entry to {payload} named ` data ` with the value {resultMap}.
899+ - If {label} is defined:
900+ - Add an entry to {payload} named ` label ` with the value {label}.
901+ - Add an entry to {payload} named ` path ` with the value {path}.
902+ - Set {payload} on {deferRecord}.
903+ - Call {CompletePayload(payload, publisherRecord)}.
845904
846905## Executing Fields
847906
@@ -968,43 +1027,40 @@ yielded items satisfies `initialCount` specified on the `@stream` directive.
9681027ExecuteStreamField(label, iterator, index, fields, innerType, path,
9691028streamRecord, variableValues, publisherRecord):
9701029
971- - Let {streamRecord} be an async payload record created from {label}, {path},
972- and {iterator}.
1030+ - Let {streamRecord} be an async payload record created from {parentRecord},
1031+ {label}, {path}, and {iterator}.
1032+ - Call {AddPayload(streamRecord, publisherRecord)}.
9731033- Initialize {errors} on {streamRecord} to an empty list.
9741034- Let {itemPath} be {path} with {index} appended.
975- - Let {dataExecution} be the asynchronous future value of:
976- - Wait for the next item from {iterator}.
977- - If an item is not retrieved because {iterator} has completed:
978- - Set {isCompletedIterator} to {true} on {streamRecord}.
979- - Return {null}.
980- - Let {payload} be an unordered map.
981- - If an item is not retrieved because of an error:
982- - Append the encountered error to {errors}.
1035+ - Wait for the next item from {iterator}.
1036+ - If an item is not retrieved because {iterator} has completed:
1037+ - Set {isCompletedIterator} to {true} on {streamRecord}.
1038+ - Return {null}.
1039+ - Let {payload} be an unordered map.
1040+ - If an item is not retrieved because of an error:
1041+ - Append the encountered error to {errors}.
1042+ - Add an entry to {payload} named ` items ` with the value {null}.
1043+ - Otherwise:
1044+ - Let {item} be the item retrieved from {iterator}.
1045+ - Let {data} be the result of calling {CompleteValue(innerType, fields, item,
1046+ variableValues, itemPath, publisherRecord, parentRecord)}.
1047+ - Append any encountered field errors to {errors}.
1048+ - Increment {index}.
1049+ - Call {ExecuteStreamField(label, iterator, index, fields, innerType, path,
1050+ streamRecord, variableValues, publisherRecord)}.
1051+ - If a field error was raised, causing a {null} to be propagated to {data},
1052+ and {innerType} is a Non-Nullable type:
9831053 - Add an entry to {payload} named ` items ` with the value {null}.
9841054 - Otherwise:
985- - Let {item} be the item retrieved from {iterator}.
986- - Let {data} be the result of calling {CompleteValue(innerType, fields,
987- item, variableValues, itemPath, publisherRecord, parentRecord)}.
988- - Append any encountered field errors to {errors}.
989- - Increment {index}.
990- - Call {ExecuteStreamField(label, iterator, index, fields, innerType, path,
991- streamRecord, variableValues, publisherRecord)}.
992- - If a field error was raised, causing a {null} to be propagated to {data},
993- and {innerType} is a Non-Nullable type:
994- - Add an entry to {payload} named ` items ` with the value {null}.
995- - Otherwise:
996- - Add an entry to {payload} named ` items ` with a list containing the value
997- {data}.
998- - If {errors} is not empty:
999- - Add an entry to {payload} named ` errors ` with the value {errors}.
1000- - If {label} is defined:
1001- - Add an entry to {payload} named ` label ` with the value {label}.
1002- - Add an entry to {payload} named ` path ` with the value {itemPath}.
1003- - If {parentRecord} is defined:
1004- - Wait for the result of {dataExecution} on {parentRecord}.
1005- - Return {payload}.
1006- - Set {dataExecution} on {streamRecord}.
1007- - Call {AddPayload(streamRecord, publisherRecord)}.
1055+ - Add an entry to {payload} named ` items ` with a list containing the value
1056+ {data}.
1057+ - If {errors} is not empty:
1058+ - Add an entry to {payload} named ` errors ` with the value {errors}.
1059+ - If {label} is defined:
1060+ - Add an entry to {payload} named ` label ` with the value {label}.
1061+ - Add an entry to {payload} named ` path ` with the value {itemPath}.
1062+ - Set {payload} on {streamRecord}.
1063+ - Call {CompletePayload(streamRecord, publisherRecord)}.
10081064
10091065CompleteValue(fieldType, fields, result, variableValues, path, publisherRecord,
10101066asyncRecord):
0 commit comments