Skip to content

Commit 1a1c71a

Browse files
committed
batch deferred fields by defer path
1 parent 16171e9 commit 1a1c71a

File tree

1 file changed

+123
-84
lines changed

1 file changed

+123
-84
lines changed

spec/Section 6 -- Execution.md

Lines changed: 123 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,16 @@ variableValues):
153153
- Let {remainingDefers} be an empty list.
154154
- Let {remainingStreams} be an empty list.
155155
- Let {pending} be an empty list.
156+
- Let {completedDeferRecords} be an empty set.
156157
- If {initialDefers} is not an empty object:
157-
- Let {id} be {nextId} and increment {nextId} by one.
158-
- Let {path} be the result of running
159-
{LongestCommonPathPrefix(initialDefers)}.
160-
- Let {pendingPayload} be an unordered map containing {id}, {path}.
161-
- Add {pendingPayload} to {pending}.
162-
- Let {defers} be {initialDefers}.
163-
- Let {pendingDefer} be an unordered map containing {id}, {defers}.
164-
- Append {pendingDefer} to {remainingDefers}.
158+
- For each entry in {initialDefers} as {deferPath} and {deferRecords}:
159+
- Let {id} be {nextId} and increment {nextId} by one.
160+
- Let {path} be {deferPath}.
161+
- Let {pendingPayload} be an unordered map containing {id}, {path}.
162+
- Add {pendingPayload} to {pending}.
163+
- Let {defers} be {deferRecords}.
164+
- Let {pendingDefer} be an unordered map containing {id}, {defers}.
165+
- Append {pendingDefer} to {remainingDefers}.
165166
- For each entry {streamDetails} in {initialStreams}:
166167
- Let {id} be {nextId} and increment {nextId} by one.
167168
- Let {path} be the value for the key {path} in {streamDetails}.
@@ -195,54 +196,54 @@ variableValues):
195196
- Reset {completed} to an empty list.
196197
- While {remainingDefers} is not empty or {remainingStreams} is not empty:
197198
- If {remainingDefers} is not empty:
198-
- Let {pendingDefer} be the first entry in {remainingDefers}.
199-
- Remove {pendingDefer} from {remainingDefers}.
200-
- Let {thisId} be the value for key {id} in {pendingDefer}.
201-
- Let {defers} be the value for key {defers} in {pendingDefer}.
202-
- Note: A single `@defer` directive may output multiple incremental payloads
203-
at different paths; it is essential that these multiple incremental
204-
payloads are received by the client as part of a single event in order to
205-
maintain consistency for the client. This is why these incremental
206-
payloads are batched together rather than being flushed to the event
207-
stream as early as possible.
208-
- Let {batchIncremental} be an empty list.
209-
- Let {batchErrors} be an empty list.
210-
- Let {batchDefers} be an empty unordered map.
211-
- Let {batchStreams} be an empty list.
212-
- For each key {path} and value {deferredDetailsForPath} in {defers}, in
213-
parallel:
214-
- Let {objectType} be the value for key {objectType} in
215-
{deferredDetailsForPath}.
216-
- Let {objectValue} be the value for key {objectValue} in
217-
{deferredDetailsForPath}.
218-
- Let {fieldDetails} be the value for key {fieldDetails} in
219-
{deferredDetailsForPath}.
220-
- Let {fields} be a list of all the values of the {field} key in the
221-
entries of {fieldDetails}.
222-
- Let {selectionSet} be a new selection set consisting of {fields}.
223-
- Let {data}, {childDefers} and {childStreams} be the result of running
224-
{ExecuteGroupedFieldSet(selectionSet, objectType, objectValue,
225-
variableValues, path)}.
226-
- Let {childErrors} be the list of all _field error_ raised while
227-
executing the selection set.
228-
- Let {incrementalPayload} be an unordered object containing {path},
229-
{data}, and the key {errors} with value {childErrors}.
230-
- Append {incrementalPayload} to {batchIncremental}.
231-
- Add the entries of {childDefers} into {batchDefers}. Note: {childDefers}
232-
and {batchDefers} will never have keys in common.
233-
- For each entry {stream} in {childStreams}, append {stream} to
234-
{batchStreams}.
199+
- Wait until at least one key in {remainingDefers} has a completed result in
200+
every associated {deferRecord}.
201+
- Let {completedDeferPaths} be the list of keys in {remainingDefers} that
202+
have a completed result in every associated {deferRecord}.
203+
- For each {deferPath} in {completedDeferPaths}:
204+
- Let {pendingDefers} be the map at key {deferPath} in {remainingDefers}.
205+
- Remove {pendingDefer} from {remainingDefers}.
206+
- Let {thisId} be the value for key {id} in {pendingDefer}.
207+
- Let {defers} be the value for key {defers} in {pendingDefer}.
208+
- Note: A single `@defer` directive may output multiple incremental
209+
payloads at different paths; it is essential that these multiple
210+
incremental payloads are received by the client as part of a single
211+
event in order to maintain consistency for the client. This is why these
212+
incremental payloads are batched together rather than being flushed to
213+
the event stream as early as possible.
214+
- Let {batchIncremental} be an empty list.
215+
- Let {batchErrors} be an empty list.
216+
- Let {batchDefers} be an empty unordered map.
217+
- Let {batchStreams} be an empty list.
218+
- For each key {path} and value {deferRecord} in {defers}:
219+
- If {completedDeferRecords} contains {deferRecord}:
220+
- Continue to the next key in {defers}.
221+
- Let {data} be the value for key {data} in {deferredDetailsForPath}.
222+
- Let {childErrors} be the value for key {childErrors} in {deferRecord}.
223+
- Let {childDefers} be the value for key {childDefers} in {deferRecord}.
224+
- Let {childStreams} be the value for key {childStreams} in
225+
{deferRecord}.
226+
- Let {childErrors} be the list of all _field error_ raised while
227+
executing the selection set.
228+
- Let {incrementalPayload} be an unordered object containing {path},
229+
{data}, and the key {errors} with value {childErrors}.
230+
- Append {incrementalPayload} to {batchIncremental}.
231+
- Add the entries of {childDefers} into {batchDefers}. Note:
232+
{childDefers} and {batchDefers} will never have keys in common.
233+
- For each entry {stream} in {childStreams}, append {stream} to
234+
{batchStreams}.
235+
- Add {deferRecord} to {completedDeferRecords}.
235236
- For each entry {incrementalPayload} in {batchIncremental}, append
236237
{incrementalPayload} to {incremental}.
237238
- If {batchDefers} is not an empty object:
238-
- Let {id} be {nextId} and increment {nextId} by one.
239-
- Let {path} be the result of running
240-
{LongestCommonPathPrefix(batchDefers)}.
241-
- Let {pendingPayload} be an unordered map containing {id}, {path}.
242-
- Add {pendingPayload} to {pending}.
243-
- Let {defers} be {batchDefers}.
244-
- Let {pendingDefer} be an unordered map containing {id}, {defers}.
245-
- Append {pendingDefer} to {remainingDefers}.
239+
- For each entry in {batchDefers} as {deferPath} and {deferRecords}:
240+
- Let {id} be {nextId} and increment {nextId} by one.
241+
- Let {path} be {deferPath}.
242+
- Let {pendingPayload} be an unordered map containing {id}, {path}.
243+
- Add {pendingPayload} to {pending}.
244+
- Let {defers} be {deferRecords}.
245+
- Let {pendingDefer} be an unordered map containing {id}, {defers}.
246+
- Append {pendingDefer} to {remainingDefers}.
246247
- For each entry {streamDetails} in {batchStreams}:
247248
- Let {id} be {nextId} and increment {nextId} by one.
248249
- Let {path} be the value for the key {path} in {streamDetails}.
@@ -274,7 +275,8 @@ variableValues):
274275
{remainingValueIndex}.
275276
- Let {path} be a copy of {parentPath} with {index} appended.
276277
- Let {value}, {childDefers} and {childStreams} be the result of running
277-
{CompleteValue(itemType, fields, remainingValue, variableValues, path)}.
278+
{CompleteValue(itemType, fieldDetails, remainingValue, variableValues,
279+
path)}.
278280
- Let {childErrors} be the list of all _field error_ raised while
279281
completing the value.
280282
- Let {incrementalPayload} be an unordered object containing {path},
@@ -323,6 +325,36 @@ TODO: Consider rewording to something like: Let {longestCommonPathPrefix} be the
323325
longest list such that every entry in {paths} starts with
324326
{longestCommonPathPrefix}. Note: This may be the empty list.
325327

328+
A Deferred Field Record is a structure containing:
329+
330+
- {path} a list of field names and indices from root to the location of the
331+
field in the response
332+
- {executionResult}: A structure that will only be available after asynchronous
333+
execution of this field is complete. Implementors are not required to
334+
immediately begin this execution. This structure contains:
335+
- {errors}: The list of all _field error_ raised while executing the selection
336+
set.
337+
- {data}: The completed result of executing the field.
338+
- {childDefers}: Any downstream defers discovered while executing this field.
339+
- {childStreams}: Any downstream streams discovered while executing this
340+
field.
341+
342+
ExecuteDeferredField(objectType, objectValue, fieldDetails, variableValues,
343+
path):
344+
345+
- Let {deferRecord} be a DeferredFieldRecord created from {path}.
346+
- Let {groupedFieldSet} be a new grouped field set consisting of {fieldDetails}.
347+
- Let {executionResult} be the asynchronous future value of:
348+
- Let {data}, {childDefers} and {childStreams} be the result of running
349+
{ExecuteGroupedFieldSet(groupedFieldSet, objectType, objectValue,
350+
variableValues, path)}.
351+
- Let {childErrors} be the list of all _field error_ raised while executing
352+
the selection set.
353+
- Return {data}, {errors}, {childDefers}, {childStreams}.
354+
- Assign {executionResult}, {errors}, {childDefers}, {childStreams} to
355+
{deferRecord}.
356+
- Return {deferRecord}.
357+
326358
### Mutation
327359

328360
If the operation is a mutation, the result of the operation is the result of
@@ -539,11 +571,11 @@ Each represented field in the grouped field set produces an entry into a
539571
response map.
540572

541573
ExecuteGroupedFieldSet(groupedFieldSet, objectType, objectValue, variableValues,
542-
parentPath):
574+
parentPath, deferPaths):
543575

544576
- If {parentPath} is not provided, initialize it to an empty list.
545577
- Initialize {resultMap} to an empty ordered map.
546-
- Let {defers} be an empty unordered map.
578+
- Let {defers} be an empty unordered map of unordered maps.
547579
- Let {streams} be an empty list.
548580
- For each {groupedFieldSet} as {responseKey} and {fieldDetails}:
549581
- Let {fieldDetail} be the first entry in {fieldDetails}.
@@ -553,15 +585,22 @@ parentPath):
553585
alias is used.
554586
- Let {fieldType} be the return type defined for the field {fieldName} of
555587
{objectType}.
588+
- Let {fields} be a list of all the values of the {field} key in the entries
589+
of {fieldDetails}.
556590
- If {fieldType} is defined:
557-
- If every entry in {fieldDetails} has {isDeferred} set to {true}:
558-
- Add an entry to {defers} with key {path} and value an unordered map
559-
containing {objectType}, {objectValue} and {fieldDetails}.
591+
- If every entry in {fieldDetails} has a {deferPath} which is not included
592+
in {deferPaths}:
593+
- Let {deferRecord} be the result of running
594+
{ExecuteDeferredField(objectType, objectValue, fields, variableValues,
595+
path)}.
596+
- For each {fieldDetail} in {fieldDetails}:
597+
- Let {deferForPath} be the map in {defers} for {path}; if no such map
598+
exists, create it as an empty unordered map.
599+
- Add an entry to {deferForPath} with key {path} and the value
600+
{deferRecord}.
560601
- Otherwise:
561-
- Let {fields} be a list of all the values of the {field} key in the
562-
entries of {fieldDetails}.
563602
- Let {resolvedValue} be the result of running {ExecuteField(objectType,
564-
objectValue, fieldType, fields, variableValues, path)}.
603+
objectValue, fieldType, fieldDetails, variableValues, path)}.
565604
- Let {nullableFieldType} be the inner type of {fieldType} if {fieldType}
566605
is a non-nullable type, otherwise let {nullableFieldType} be
567606
{fieldType}.
@@ -582,7 +621,7 @@ parentPath):
582621
- Let {initialValues} be the first {initialCount} entries in
583622
{resolvedValue}, and {remainingValues} be the remainder.
584623
- Let {initialResponseValue}, {childDefers}, {childStreams} be the
585-
result of running {CompleteValue(nullableFieldType, fields,
624+
result of running {CompleteValue(nullableFieldType, fieldDetails,
586625
initialValues, variableValues, path)}.
587626
- Add the entries of {childDefers} into {defers}. Note: {childDefers}
588627
and {defers} will never have keys in common.
@@ -596,7 +635,7 @@ parentPath):
596635
- Append {streamDetails} to {streams}.
597636
- Otherwise:
598637
- Let {responseValue}, {childDefers} and {childStreams} be the result of
599-
running {CompleteValue(fieldType, fields, resolvedValue,
638+
running {CompleteValue(fieldType, fieldDetails, resolvedValue,
600639
variableValues, path)}.
601640
- Add the entries of {childDefers} into {defers}. Note: {childDefers}
602641
and {defers} will never have keys in common.
@@ -747,13 +786,11 @@ The depth-first-search order of the field groups produced by {CollectFields()}
747786
is maintained through execution, ensuring that fields appear in the executed
748787
response in a stable and predictable order.
749788

750-
CollectFields(objectType, selectionSet, variableValues, isDeferred,
751-
visitedFragments):
789+
CollectFields(objectType, selectionSet, variableValues, visitedFragments,
790+
parentPath, deferPath):
752791

753-
- Initialize {groupedFieldSet} to an empty ordered map of lists.
754-
- If {isDeferred} is not provided, initialize it to {false}.
755792
- If {visitedFragments} is not provided, initialize it to the empty set.
756-
- Initialize {groupedFields} to an empty ordered map of lists.
793+
- Initialize {groupedFieldSet} to an empty ordered map of lists.
757794
- For each {selection} in {selectionSet}:
758795
- If {selection} provides the directive `@skip`, let {skipDirective} be that
759796
directive.
@@ -771,7 +808,7 @@ visitedFragments):
771808
otherwise the field name).
772809
- Let {groupForResponseKey} be the list in {groupedFieldSet} for
773810
{responseKey}; if no such list exists, create it as an empty list.
774-
- Let {fieldDetail} be an unordered map containing {field} and {isDeferred}.
811+
- Let {fieldDetail} be an unordered map containing {field} and {deferPath}.
775812
- Append {fieldDetail} to the {groupForResponseKey}.
776813
- If {selection} is a {FragmentSpread}:
777814
- Let {fragmentSpreadName} be the name of {selection}.
@@ -785,16 +822,16 @@ visitedFragments):
785822
- Let {fragmentType} be the type condition on {fragment}.
786823
- If {DoesFragmentTypeApply(objectType, fragmentType)} is false, continue
787824
with the next {selection} in {selectionSet}.
788-
- Let {fragmentIsDeferred} be {isDeferred}.
825+
- Let {fragmentDeferPath} be {deferPath}.
789826
- If {selection} provides the directive `@defer`, let {deferDirective} be
790827
that directive.
791828
- If {deferDirective}'s {if} argument is not {false} and is not a variable
792829
in {variableValues} with the value {false}:
793-
- Let {fragmentIsDeferred} be {true}.
830+
- Let {fragmentDeferPath} be {parentPath}.
794831
- Let {fragmentSelectionSet} be the top-level selection set of {fragment}.
795832
- Let {fragmentGroupedFieldSet} be the result of running
796833
{CollectFields(objectType, fragmentSelectionSet, variableValues,
797-
fragmentIsDeferred, visitedFragments)}.
834+
visitedFragments, parentPath, fragmentDeferPath)}.
798835
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
799836
- Let {responseKey} be the response key shared by all fields in
800837
{fragmentGroup}.
@@ -806,16 +843,16 @@ visitedFragments):
806843
- If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType,
807844
fragmentType)} is false, continue with the next {selection} in
808845
{selectionSet}.
809-
- Let {fragmentIsDeferred} be {isDeferred}.
846+
- Let {fragmentDeferPath} be {deferPath}.
810847
- If {selection} provides the directive `@defer`, let {deferDirective} be
811848
that directive.
812849
- If {deferDirective}'s {if} argument is not {false} and is not a variable
813850
in {variableValues} with the value {false}:
814-
- Let {fragmentIsDeferred} be {true}.
851+
- Let {fragmentDeferPath} be {parentPath}.
815852
- Let {fragmentSelectionSet} be the top-level selection set of {selection}.
816853
- Let {fragmentGroupedFieldSet} be the result of running
817854
{CollectFields(objectType, fragmentSelectionSet, variableValues,
818-
fragmentIsDeferred, visitedFragments)}.
855+
visitedFragments, parentPath, fragmentDeferPath)}.
819856
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
820857
- Let {responseKey} be the response key shared by all fields in
821858
{fragmentGroup}.
@@ -854,16 +891,18 @@ CollectRootFields(rootType, operationSelectionSet, variableValues):
854891

855892
Object subfield collection processes a field's sub-selection sets:
856893

857-
CollectSubfields(objectType, fields, variableValues):
894+
CollectSubfields(objectType, fieldDetails, variableValues, parentPath):
858895

859896
- Initialize {visitedFragments} to the empty set.
860897
- Initialize {groupedSubfieldSet} to an empty ordered map of lists.
861-
- For each {field} in {fields}:
898+
- For each {fieldDetail} in {fieldDetails}:
899+
- Let {field} be the value for the key {field} in {fieldDetail}.
900+
- Let {deferPath} be the value for the key {deferPath} in {fieldDetail}.
862901
- Let {fieldSelectionSet} be the selection set of {field}.
863902
- If {fieldSelectionSet} is null or empty, continue to the next field.
864903
- Let {fieldGroupedFieldSet} be the result of calling
865904
{CollectFields(objectType, fragmentSelectionSet, variableValues,
866-
visitedFragments)}.
905+
visitedFragments, parentPath, deferPath)}.
867906
- For each {fieldGroup} in {fieldGroupedFieldSet}:
868907
- Let {responseKey} be the response key shared by all fields in
869908
{fragmentGroup}.
@@ -975,12 +1014,12 @@ After resolving the value for a field, it is completed by ensuring it adheres to
9751014
the expected return type. If the return type is another Object type, then the
9761015
field execution process continues recursively.
9771016

978-
CompleteValue(fieldType, fields, result, variableValues, path):
1017+
CompleteValue(fieldType, fieldDetails, result, variableValues, path):
9791018

9801019
- If the {fieldType} is a Non-Null type:
9811020
- Let {innerType} be the inner type of {fieldType}.
9821021
- Let {completedResult}, {defers} and {streams} be the result of running
983-
{CompleteValue(innerType, fields, result, variableValues, path)}.
1022+
{CompleteValue(innerType, fieldDetails, result, variableValues, path)}.
9841023
- If {completedResult} is {null}, raise a _field error_.
9851024
- Return {completedResult}, {defers} and {streams}.
9861025
- If {result} is {null} (or another internal value similar to {null} such as
@@ -998,8 +1037,8 @@ CompleteValue(fieldType, fields, result, variableValues, path):
9981037
- For each entry {resultItem} at zero-based index {resultIndex} in {result}:
9991038
- Let {listItemPath} be a copy of {path} with {resultIndex} appended.
10001039
- Let {completedItemResult}, {childDefers} and {childStreams} be the result
1001-
of running {CompleteValue(innerType, fields, resultItem, variableValues,
1002-
listItemPath)}.
1040+
of running {CompleteValue(innerType, fieldDetails, resultItem,
1041+
variableValues, listItemPath)}.
10031042
- Add the entries of {childDefers} into {defers}. Note: {childDefers} and
10041043
{defers} will never have keys in common.
10051044
- For each entry {stream} in {childStreams}, append {stream} to {streams}.
@@ -1018,7 +1057,7 @@ CompleteValue(fieldType, fields, result, variableValues, path):
10181057
- Let {objectType} be the result of running {ResolveAbstractType(fieldType,
10191058
result)}.
10201059
- Let {groupedSubfieldSet} be the result of calling
1021-
{CollectSubfields(objectType, fields, variableValues)}.
1060+
{CollectSubfields(objectType, fieldDetails, variableValues, path)}.
10221061
- Let {completedResult}, {defers} and {streams} be the result of running
10231062
{ExecuteGroupedFieldSet(groupedSubfieldSet, objectType, result,
10241063
variableValues, path)} _normally_ (allowing for parallelization).

0 commit comments

Comments
 (0)