Skip to content

Commit 147c3f0

Browse files
robrichardyaacovCR
authored andcommitted
wait for parent async record to ensure correct order of payloads
# Conflicts: # spec/Section 6 -- Execution.md
1 parent 1f4104e commit 147c3f0

File tree

2 files changed

+108
-85
lines changed

2 files changed

+108
-85
lines changed

cspell.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ words:
1111
- openwebfoundation
1212
- parallelization
1313
- structs
14-
- sublist
1514
- subselection
1615
# Fictional characters / examples
1716
- alderaan

spec/Section 6 -- Execution.md

Lines changed: 108 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -417,13 +417,13 @@ First, the selection set is turned into a grouped field set; then, each
417417
represented field in the grouped field set produces an entry into a response
418418
map.
419419

420-
ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues,
421-
subsequentPayloads, parentPath):
420+
ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues, path,
421+
subsequentPayloads, asyncRecord):
422422

423+
- If {path} is not provided, initialize it to an empty list.
423424
- If {subsequentPayloads} is not provided, initialize it to the empty set.
424-
- If {parentPath} is not provided, initialize it to an empty list.
425425
- Let {groupedFieldSet} be the result of {CollectFields(objectType, objectValue,
426-
selectionSet, variableValues, subsequentPayloads, parentPath)}.
426+
selectionSet, variableValues, path subsequentPayloads, asyncRecord)}.
427427
- Initialize {resultMap} to an empty ordered map.
428428
- For each {groupedFieldSet} as {responseKey} and {fields}:
429429
- Let {fieldName} be the name of the first entry in {fields}. Note: This value
@@ -432,7 +432,7 @@ subsequentPayloads, parentPath):
432432
{objectType}.
433433
- If {fieldType} is defined:
434434
- Let {responseValue} be {ExecuteField(objectType, objectValue, fieldType,
435-
fields, variableValues, subsequentPayloads, parentPath)}.
435+
fields, variableValues, path, subsequentPayloads, asyncRecord)}.
436436
- Set {responseValue} as the value for {responseKey} in {resultMap}.
437437
- Return {resultMap}.
438438

@@ -584,8 +584,8 @@ The depth-first-search order of the field groups produced by {CollectFields()}
584584
is maintained through execution, ensuring that fields appear in the executed
585585
response in a stable and predictable order.
586586

587-
CollectFields(objectType, objectValue, selectionSet, variableValues,
588-
deferredFragments, parentPath, visitedFragments):
587+
CollectFields(objectType, objectValue, selectionSet, variableValues, path,
588+
subsequentPayloads, asyncRecord, visitedFragments):
589589

590590
- If {visitedFragments} is not provided, initialize it to the empty set.
591591
- Initialize {groupedFields} to an empty ordered map of lists.
@@ -624,14 +624,16 @@ deferredFragments, parentPath, visitedFragments):
624624
with the next {selection} in {selectionSet}.
625625
- Let {fragmentSelectionSet} be the top-level selection set of {fragment}.
626626
- If {deferDirective} is defined:
627-
- Let {deferredFragment} be the result of calling
628-
{DeferFragment(objectType, objectValue, fragmentSelectionSet,
629-
parentPath)}.
630-
- Append {deferredFragment} to {deferredFragments}.
627+
- Let {label} be the value or the variable to {deferDirective}'s {label}
628+
argument.
629+
- Let {deferredFragmentRecord} be the result of calling
630+
{CreateDeferredFragmentRecord(label, objectType, objectValue,
631+
fragmentSelectionSet, path, asyncRecord)}.
632+
- Append {deferredFragmentRecord} to {subsequentPayloads}.
631633
- Continue with the next {selection} in {selectionSet}.
632634
- Let {fragmentGroupedFieldSet} be the result of calling
633-
{CollectFields(objectType, fragmentSelectionSet, variableValues,
634-
visitedFragments)}.
635+
{CollectFields(objectType, objectValue, fragmentSelectionSet,
636+
variableValues, path, subsequentPayloads, asyncRecord, visitedFragments)}.
635637
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
636638
- Let {responseKey} be the response key shared by all fields in
637639
{fragmentGroup}.
@@ -648,14 +650,16 @@ deferredFragments, parentPath, visitedFragments):
648650
be that directive.
649651
- If {deferDirective}'s {if} argument is {true} or is a variable in
650652
{variableValues} with the value {true}:
651-
- Let {deferredFragment} be the result of calling
652-
{DeferFragment(objectType, objectValue, fragmentSelectionSet,
653-
parentPath)}.
654-
- Append {deferredFragment} to {deferredFragments}.
653+
- Let {label} be the value or the variable to {deferDirective}'s {label}
654+
argument.
655+
- Let {deferredFragmentRecord} be the result of calling
656+
{CreateDeferredFragmentRecord(label, objectType, objectValue,
657+
fragmentSelectionSet, path, asyncRecord)}.
658+
- Append {deferredFragmentRecord} to {subsequentPayloads}.
655659
- Continue with the next {selection} in {selectionSet}.
656660
- Let {fragmentGroupedFieldSet} be the result of calling
657-
{CollectFields(objectType, fragmentSelectionSet, variableValues,
658-
visitedFragments, parentPath)}.
661+
{CollectFields(objectType, objectValue, fragmentSelectionSet,
662+
variableValues, path, subsequentPayloads, asyncRecord, visitedFragments)}.
659663
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
660664
- Let {responseKey} be the response key shared by all fields in
661665
{fragmentGroup}.
@@ -679,44 +683,53 @@ DoesFragmentTypeApply(objectType, fragmentType):
679683
- if {objectType} is a possible type of {fragmentType}, return {true}
680684
otherwise return {false}.
681685

682-
DeferFragment(objectType, objectValue, fragmentSelectionSet, parentPath):
686+
#### Async Payload Record
687+
688+
An Async Payload Record is either a Deferred Fragment Record or a Stream Record.
689+
All Async Payload Records are structures containing:
683690

684-
- Let {label} be the value or the variable to {deferDirective}'s {label}
685-
argument.
686-
- Let {deferredFragmentRecord} be the result of calling
687-
{CreateDeferredFragmentRecord(label, objectType, objectValue,
688-
fragmentSelectionSet, parentPath)}.
689-
- return {deferredFragmentRecord}.
691+
- {label}: value derived from the corresponding `@defer` or `@stream` directive.
692+
- {parentRecord}: optionally an Async Payload Record.
693+
- {errors}: a list of field errors encountered during execution.
694+
- {dataExecution}: A result that can notify when the corresponding execution has
695+
completed.
696+
- {path}: a list of field names and indices from root to the location of the
697+
corresponding `@defer` or `@stream` directive.
690698

691699
#### Deferred Fragment Record
692700

693701
Let {deferredFragmentRecord} be an inline fragment or fragment spread with
694702
`@defer` provided.
695703

696-
Deferred Fragment Record is a structure containing:
704+
Deferred Fragment Record is a structure containing all the entries of Async
705+
Payload Record, and additionally:
697706

698-
- {label}: value derived from the `@defer` directive.
699707
- {objectType}: of the {deferredFragmentRecord}.
700708
- {objectValue}: of the {deferredFragmentRecord}.
701709
- {fragmentSelectionSet}: the top level selection set of
702710
{deferredFragmentRecord}.
703-
- {path}: a list of field names and indices from root to
704-
{deferredFragmentRecord}.
705711

706712
CreateDeferredFragmentRecord(label, objectType, objectValue,
707-
fragmentSelectionSet, path):
713+
fragmentSelectionSet, path, parentRecord):
708714

709-
- If {path} is not provided, initialize it to an empty list.
710715
- Construct a deferred fragment record based on the parameters passed in.
716+
- Initialize {errors} to an empty list.
711717

712718
ResolveDeferredFragmentRecord(deferredFragmentRecord, variableValues,
713719
subsequentPayloads):
714720

715-
- Let {label}, {objectType}, {objectValue}, {fragmentSelectionSet}, {path} be
716-
the corresponding fields in the deferred fragment record structure.
717-
- Let {payload} be the result of calling
718-
{ExecuteSelectionSet(fragmentSelectionSet, objectType, objectValue,
719-
variableValues, subsequentPayloads, path)}.
721+
- Let {label}, {objectType}, {objectValue}, {fragmentSelectionSet}, {path},
722+
{parentRecord} be the corresponding fields in the deferred fragment record
723+
structure.
724+
- Let {dataExecution} be the asynchronous future value of:
725+
- Let {payload} be the result of {ExecuteSelectionSet(fragmentSelectionSet,
726+
objectType, objectValue, variableValues, path, subsequentPayloads,
727+
deferredFragmentRecord)}.
728+
- If {parentRecord} is defined:
729+
- Wait for the result of {dataExecution} on {parentRecord}.
730+
- Return {payload}.
731+
- Set {dataExecution} on {deferredFragmentRecord}.
732+
- Let {payload} be the result of waiting for {dataExecution}.
720733
- Add an entry to {payload} named `label` with the value {label}.
721734
- Add an entry to {payload} named `path` with the value {path}.
722735
- Return {payload}.
@@ -729,28 +742,27 @@ coerces any provided argument values, then resolves a value for the field, and
729742
finally completes that value either by recursively executing another selection
730743
set or coercing a scalar value.
731744

732-
ExecuteField(objectType, objectValue, fieldType, fields, variableValues,
733-
subsequentPayloads, parentPath):
745+
ExecuteField(objectType, objectValue, fieldType, fields, variableValues, path,
746+
subsequentPayloads, asyncRecord):
734747

735748
- Let {field} be the first entry in {fields}.
736749
- Let {fieldName} be the field name of {field}.
750+
- Append {fieldName} to {path}.
737751
- Let {argumentValues} be the result of {CoerceArgumentValues(objectType, field,
738752
variableValues)}
739753
- If {field} provides the directive `@stream`, let {streamDirective} be that
740754
directive.
741755
- Let {initialCount} be the value or variable provided to {streamDirective}'s
742756
{initialCount} argument.
743757
- Let {resolvedValue} be {ResolveFieldGenerator(objectType, objectValue,
744-
fieldName, argumentValues, initialCount)}.
758+
fieldName, argumentValues)}.
745759
- Let {result} be the result of calling {CompleteValue(fieldType, fields,
746-
resolvedValue, variableValues, subsequentPayloads, parentPath)}.
747-
- Append {fieldName} to the {path} field of every {subsequentPayloads}.
760+
resolvedValue, variableValues, path, subsequentPayloads, asyncRecord)}.
748761
- Return {result}.
749762
- Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName,
750763
argumentValues)}.
751764
- Let {result} be the result of calling {CompleteValue(fieldType, fields,
752-
resolvedValue, variableValues, subsequentPayloads)}.
753-
- Append {fieldName} to the {path} for every {subsequentPayloads}.
765+
resolvedValue, variableValues, path, subsequentPayloads, asyncRecord)}.
754766
- Return {result}.
755767

756768
### Coercing Field Arguments
@@ -836,14 +848,13 @@ ResolveFieldValue(objectType, objectValue, fieldName, argumentValues):
836848
- Return the result of calling {resolver}, providing {objectValue} and
837849
{argumentValues}.
838850

839-
ResolveFieldGenerator(objectType, objectValue, fieldName, argumentValues,
840-
initialCount):
851+
ResolveFieldGenerator(objectType, objectValue, fieldName, argumentValues):
841852

842853
- If {objectType} provide an internal function {generatorResolver} for
843854
generating partially resolved value of a list field named {fieldName}:
844855
- Let {generatorResolver} be the internal function.
845856
- Return the iterator from calling {generatorResolver}, providing
846-
{objectValue}, {argumentValues} and {initialCount}.
857+
{objectValue} and {argumentValues}.
847858
- Create {generator} from {ResolveFieldValue(objectType, objectValue, fieldName,
848859
argumentValues)}.
849860
- Return {generator}.
@@ -863,52 +874,58 @@ field execution process continues recursively. In the case where a value
863874
returned for a list type field is an iterator due to `@stream` specified on the
864875
field, value completion iterates over the iterator until the number of items
865876
yield by the iterator satisfies `initialCount` specified on the `@stream`
866-
directive. Unresolved items in the iterator will be stored in a stream record
867-
which the executor resumes to execute after the initial execution finishes.
877+
directive.
868878

869879
#### Stream Record
870880

871881
Let {streamField} be a list field with a `@stream` directive provided.
872882

873-
A Stream Record is a structure containing:
883+
A Stream Record is a structure containing all the entries of Async Payload
884+
Record, and additionally:
874885

875-
- {label}: value derived from the `@stream` directive's `label` argument.
876886
- {iterator}: created by {ResolveFieldGenerator}.
877-
- {resolvedItems}: items resolved from the {iterator} but not yet delivered.
878887
- {index}: indicating the position of the item in the complete list.
879-
- {path}: a list of field names and indices from root to {streamField}.
880888
- {fields}: the group of fields grouped by CollectFields() for {streamField}.
881889
- {innerType}: inner type of {streamField}'s type.
882890

883-
CreateStreamRecord(label, initialCount, iterator, resolvedItems, index, fields,
884-
innerType):
891+
CreateStreamRecord(label, iterator, index, fields, innerType, path,
892+
parentRecord):
885893

886894
- Construct a stream record based on the parameters passed in.
895+
- Initialize {errors} to an empty list.
887896

888897
ResolveStreamRecord(streamRecord, variableValues, subsequentPayloads):
889898

890-
- Let {label}, {iterator}, {resolvedItems}, {index}, {path}, {fields},
899+
- Let {label}, {parentRecord}, {iterator}, {index}, {path}, {fields},
891900
{innerType} be the correspondent fields on the Stream Record structure.
892-
- Wait for the next item from {iterator}.
893-
- If an item is not retrieved because {iterator} has completed:
894-
- Return {null}
895-
- Let {item} be the item retrieved from {iterator}.
896-
- Append {index} to {path}.
897-
- Increment {index}.
898-
- Let {payload} be the result of calling CompleteValue(innerType, fields, item,
899-
variableValues, subsequentPayloads, path)}.
901+
- Let {indexPath} be {path} with {index} appended.
902+
- Let {dataExecution} be the asynchronous future value of:
903+
- Wait for the next item from {iterator}.
904+
- If an item is not retrieved because {iterator} has completed:
905+
- Return {null}.
906+
- Let {item} be the item retrieved from {iterator}.
907+
- Let {payload} be the result of calling {CompleteValue(innerType, fields,
908+
item, variableValues, indexPath, subsequentPayloads, parentRecord)}.
909+
- Increment {index}.
910+
- Let {nextStreamRecord} be the result of calling {CreateStreamRecord(label,
911+
iterator, index, fields, innerType, path, streamRecord)}.
912+
- Append {nextStreamRecord} to {subsequentPayloads}.
913+
- If {parentRecord} is defined:
914+
- Wait for the result of {dataExecution} on {parentRecord}.
915+
- Return {payload}.
916+
- Set {dataExecution} on {streamRecord}.
917+
- Let {payload} be the result of waiting for {dataExecution}.
900918
- Add an entry to {payload} named `label` with the value {label}.
901-
- Add an entry to {payload} named `path` with the value {path}.
902-
- Append {streamRecord} to {subsequentPayloads}.
919+
- Add an entry to {payload} named `path` with the value {indexPath}.
903920
- Return {payload}.
904921

905-
CompleteValue(fieldType, fields, result, variableValues, subsequentPayloads,
906-
parentPath):
922+
CompleteValue(fieldType, fields, result, variableValues, path,
923+
subsequentPayloads, asyncRecord):
907924

908925
- If the {fieldType} is a Non-Null type:
909926
- Let {innerType} be the inner type of {fieldType}.
910927
- Let {completedResult} be the result of calling {CompleteValue(innerType,
911-
fields, result, variableValues)}.
928+
fields, result, variableValues, path)}.
912929
- If {completedResult} is {null}, raise a _field error_.
913930
- Return {completedResult}.
914931
- If {result} is {null} (or another internal value similar to {null} such as
@@ -923,26 +940,33 @@ parentPath):
923940
- If {initialCount} is less than zero, raise a _field error_.
924941
- Let {label} be the value or variable provided to {streamDirective}'s
925942
{label} argument.
926-
- Let {resolvedItems} be an empty list
927-
- For each {members} in {result}:
928-
- Append all items from {members} to {resolvedItems}.
929-
- If the length of {resolvedItems} is greater or equal to {initialCount}:
930-
- Let {initialItems} be the sublist of the first {initialCount} items
931-
from {resolvedItems}.
932-
- Let {remainingItems} be the sublist of the items in {resolvedItems}
933-
after the first {initialCount} items.
943+
- Let {initialItems} be an empty list
944+
- Let {index} be zero.
945+
- While {result} is not closed:
946+
- If {streamDirective} was not provided or {index} is not greater than or
947+
equal to {initialCount}:
948+
- Wait for the next item from {result}.
949+
- Let {resultItem} be the item retrieved from {result}.
950+
- Let {indexPath} be {path} with {index} appended.
951+
- Let {resolvedItem} be the result of calling {CompleteValue(innerType,
952+
fields, resultItem, variableValues, indexPath, subsequentPayloads,
953+
asyncRecord)}.
954+
- Append {resolvedItem} to {initialItems}.
955+
- Increment {index}.
956+
- If {streamDirective} was provided and {index} is greater than or equal
957+
to {initialCount}:
934958
- Let {streamRecord} be the result of calling {CreateStreamRecord(label,
935-
initialCount, result, remainingItems, initialCount, fields, innerType,
936-
parentPath)}
959+
result, index, fields, innerType, path, asyncRecord)}.
937960
- Append {streamRecord} to {subsequentPayloads}.
938961
- Let {result} be {initialItems}.
939-
- Exit for each loop.
962+
- Exit while loop.
963+
- Return {initialItems}.
940964
- If {result} is not a collection of values, raise a _field error_.
941965
- Let {innerType} be the inner type of {fieldType}.
942966
- Return a list where each list item is the result of calling
943-
{CompleteValue(innerType, fields, resultItem, variableValues,
944-
subsequentPayloads, parentPath)}, where {resultItem} is each item in
945-
{result}.
967+
{CompleteValue(innerType, fields, resultItem, variableValues, indexPath,
968+
subsequentPayloads, asyncRecord)}, where {resultItem} is each item in
969+
{result} and {indexPath} is {path} with the index of the item appended.
946970
- If {fieldType} is a Scalar or Enum type:
947971
- Return the result of {CoerceResult(fieldType, result)}.
948972
- If {fieldType} is an Object, Interface, or Union type:
@@ -952,7 +976,7 @@ parentPath):
952976
- Let {objectType} be {ResolveAbstractType(fieldType, result)}.
953977
- Let {subSelectionSet} be the result of calling {MergeSelectionSets(fields)}.
954978
- Return the result of evaluating {ExecuteSelectionSet(subSelectionSet,
955-
objectType, result, variableValues, subsequentPayloads, parentPath)}
979+
objectType, result, variableValues, path, subsequentPayloads, asyncRecord)}
956980
_normally_ (allowing for parallelization).
957981

958982
**Coercing Results**

0 commit comments

Comments
 (0)