@@ -153,15 +153,16 @@ variableValues):
153
153
- Let {remainingDefers} be an empty list.
154
154
- Let {remainingStreams} be an empty list.
155
155
- Let {pending} be an empty list.
156
+ - Let {completedDeferRecords} be an empty set.
156
157
- 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}.
165
166
- For each entry {streamDetails} in {initialStreams}:
166
167
- Let {id} be {nextId} and increment {nextId} by one.
167
168
- Let {path} be the value for the key {path} in {streamDetails}.
@@ -195,54 +196,54 @@ variableValues):
195
196
- Reset {completed} to an empty list.
196
197
- While {remainingDefers} is not empty or {remainingStreams} is not empty:
197
198
- 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 }.
235
236
- For each entry {incrementalPayload} in {batchIncremental}, append
236
237
{incrementalPayload} to {incremental}.
237
238
- 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}.
246
247
- For each entry {streamDetails} in {batchStreams}:
247
248
- Let {id} be {nextId} and increment {nextId} by one.
248
249
- Let {path} be the value for the key {path} in {streamDetails}.
@@ -274,7 +275,8 @@ variableValues):
274
275
{remainingValueIndex}.
275
276
- Let {path} be a copy of {parentPath} with {index} appended.
276
277
- 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)}.
278
280
- Let {childErrors} be the list of all _ field error_ raised while
279
281
completing the value.
280
282
- Let {incrementalPayload} be an unordered object containing {path},
@@ -323,6 +325,36 @@ TODO: Consider rewording to something like: Let {longestCommonPathPrefix} be the
323
325
longest list such that every entry in {paths} starts with
324
326
{longestCommonPathPrefix}. Note: This may be the empty list.
325
327
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
+
326
358
### Mutation
327
359
328
360
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
539
571
response map.
540
572
541
573
ExecuteGroupedFieldSet(groupedFieldSet, objectType, objectValue, variableValues,
542
- parentPath):
574
+ parentPath, deferPaths ):
543
575
544
576
- If {parentPath} is not provided, initialize it to an empty list.
545
577
- 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 .
547
579
- Let {streams} be an empty list.
548
580
- For each {groupedFieldSet} as {responseKey} and {fieldDetails}:
549
581
- Let {fieldDetail} be the first entry in {fieldDetails}.
@@ -553,15 +585,22 @@ parentPath):
553
585
alias is used.
554
586
- Let {fieldType} be the return type defined for the field {fieldName} of
555
587
{objectType}.
588
+ - Let {fields} be a list of all the values of the {field} key in the entries
589
+ of {fieldDetails}.
556
590
- 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}.
560
601
- Otherwise:
561
- - Let {fields} be a list of all the values of the {field} key in the
562
- entries of {fieldDetails}.
563
602
- Let {resolvedValue} be the result of running {ExecuteField(objectType,
564
- objectValue, fieldType, fields , variableValues, path)}.
603
+ objectValue, fieldType, fieldDetails , variableValues, path)}.
565
604
- Let {nullableFieldType} be the inner type of {fieldType} if {fieldType}
566
605
is a non-nullable type, otherwise let {nullableFieldType} be
567
606
{fieldType}.
@@ -582,7 +621,7 @@ parentPath):
582
621
- Let {initialValues} be the first {initialCount} entries in
583
622
{resolvedValue}, and {remainingValues} be the remainder.
584
623
- Let {initialResponseValue}, {childDefers}, {childStreams} be the
585
- result of running {CompleteValue(nullableFieldType, fields ,
624
+ result of running {CompleteValue(nullableFieldType, fieldDetails ,
586
625
initialValues, variableValues, path)}.
587
626
- Add the entries of {childDefers} into {defers}. Note: {childDefers}
588
627
and {defers} will never have keys in common.
@@ -596,7 +635,7 @@ parentPath):
596
635
- Append {streamDetails} to {streams}.
597
636
- Otherwise:
598
637
- Let {responseValue}, {childDefers} and {childStreams} be the result of
599
- running {CompleteValue(fieldType, fields , resolvedValue,
638
+ running {CompleteValue(fieldType, fieldDetails , resolvedValue,
600
639
variableValues, path)}.
601
640
- Add the entries of {childDefers} into {defers}. Note: {childDefers}
602
641
and {defers} will never have keys in common.
@@ -747,13 +786,11 @@ The depth-first-search order of the field groups produced by {CollectFields()}
747
786
is maintained through execution, ensuring that fields appear in the executed
748
787
response in a stable and predictable order.
749
788
750
- CollectFields(objectType, selectionSet, variableValues, isDeferred ,
751
- visitedFragments ):
789
+ CollectFields(objectType, selectionSet, variableValues, visitedFragments ,
790
+ parentPath, deferPath ):
752
791
753
- - Initialize {groupedFieldSet} to an empty ordered map of lists.
754
- - If {isDeferred} is not provided, initialize it to {false}.
755
792
- 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.
757
794
- For each {selection} in {selectionSet}:
758
795
- If {selection} provides the directive ` @skip ` , let {skipDirective} be that
759
796
directive.
@@ -771,7 +808,7 @@ visitedFragments):
771
808
otherwise the field name).
772
809
- Let {groupForResponseKey} be the list in {groupedFieldSet} for
773
810
{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 }.
775
812
- Append {fieldDetail} to the {groupForResponseKey}.
776
813
- If {selection} is a {FragmentSpread}:
777
814
- Let {fragmentSpreadName} be the name of {selection}.
@@ -785,16 +822,16 @@ visitedFragments):
785
822
- Let {fragmentType} be the type condition on {fragment}.
786
823
- If {DoesFragmentTypeApply(objectType, fragmentType)} is false, continue
787
824
with the next {selection} in {selectionSet}.
788
- - Let {fragmentIsDeferred } be {isDeferred }.
825
+ - Let {fragmentDeferPath } be {deferPath }.
789
826
- If {selection} provides the directive ` @defer ` , let {deferDirective} be
790
827
that directive.
791
828
- If {deferDirective}'s {if} argument is not {false} and is not a variable
792
829
in {variableValues} with the value {false}:
793
- - Let {fragmentIsDeferred } be {true }.
830
+ - Let {fragmentDeferPath } be {parentPath }.
794
831
- Let {fragmentSelectionSet} be the top-level selection set of {fragment}.
795
832
- Let {fragmentGroupedFieldSet} be the result of running
796
833
{CollectFields(objectType, fragmentSelectionSet, variableValues,
797
- fragmentIsDeferred, visitedFragments )}.
834
+ visitedFragments, parentPath, fragmentDeferPath )}.
798
835
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
799
836
- Let {responseKey} be the response key shared by all fields in
800
837
{fragmentGroup}.
@@ -806,16 +843,16 @@ visitedFragments):
806
843
- If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType,
807
844
fragmentType)} is false, continue with the next {selection} in
808
845
{selectionSet}.
809
- - Let {fragmentIsDeferred } be {isDeferred }.
846
+ - Let {fragmentDeferPath } be {deferPath }.
810
847
- If {selection} provides the directive ` @defer ` , let {deferDirective} be
811
848
that directive.
812
849
- If {deferDirective}'s {if} argument is not {false} and is not a variable
813
850
in {variableValues} with the value {false}:
814
- - Let {fragmentIsDeferred } be {true }.
851
+ - Let {fragmentDeferPath } be {parentPath }.
815
852
- Let {fragmentSelectionSet} be the top-level selection set of {selection}.
816
853
- Let {fragmentGroupedFieldSet} be the result of running
817
854
{CollectFields(objectType, fragmentSelectionSet, variableValues,
818
- fragmentIsDeferred, visitedFragments )}.
855
+ visitedFragments, parentPath, fragmentDeferPath )}.
819
856
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
820
857
- Let {responseKey} be the response key shared by all fields in
821
858
{fragmentGroup}.
@@ -854,16 +891,18 @@ CollectRootFields(rootType, operationSelectionSet, variableValues):
854
891
855
892
Object subfield collection processes a field's sub-selection sets:
856
893
857
- CollectSubfields(objectType, fields , variableValues):
894
+ CollectSubfields(objectType, fieldDetails , variableValues, parentPath ):
858
895
859
896
- Initialize {visitedFragments} to the empty set.
860
897
- 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}.
862
901
- Let {fieldSelectionSet} be the selection set of {field}.
863
902
- If {fieldSelectionSet} is null or empty, continue to the next field.
864
903
- Let {fieldGroupedFieldSet} be the result of calling
865
904
{CollectFields(objectType, fragmentSelectionSet, variableValues,
866
- visitedFragments)}.
905
+ visitedFragments, parentPath, deferPath )}.
867
906
- For each {fieldGroup} in {fieldGroupedFieldSet}:
868
907
- Let {responseKey} be the response key shared by all fields in
869
908
{fragmentGroup}.
@@ -975,12 +1014,12 @@ After resolving the value for a field, it is completed by ensuring it adheres to
975
1014
the expected return type. If the return type is another Object type, then the
976
1015
field execution process continues recursively.
977
1016
978
- CompleteValue(fieldType, fields , result, variableValues, path):
1017
+ CompleteValue(fieldType, fieldDetails , result, variableValues, path):
979
1018
980
1019
- If the {fieldType} is a Non-Null type:
981
1020
- Let {innerType} be the inner type of {fieldType}.
982
1021
- Let {completedResult}, {defers} and {streams} be the result of running
983
- {CompleteValue(innerType, fields , result, variableValues, path)}.
1022
+ {CompleteValue(innerType, fieldDetails , result, variableValues, path)}.
984
1023
- If {completedResult} is {null}, raise a _ field error_ .
985
1024
- Return {completedResult}, {defers} and {streams}.
986
1025
- If {result} is {null} (or another internal value similar to {null} such as
@@ -998,8 +1037,8 @@ CompleteValue(fieldType, fields, result, variableValues, path):
998
1037
- For each entry {resultItem} at zero-based index {resultIndex} in {result}:
999
1038
- Let {listItemPath} be a copy of {path} with {resultIndex} appended.
1000
1039
- 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)}.
1003
1042
- Add the entries of {childDefers} into {defers}. Note: {childDefers} and
1004
1043
{defers} will never have keys in common.
1005
1044
- For each entry {stream} in {childStreams}, append {stream} to {streams}.
@@ -1018,7 +1057,7 @@ CompleteValue(fieldType, fields, result, variableValues, path):
1018
1057
- Let {objectType} be the result of running {ResolveAbstractType(fieldType,
1019
1058
result)}.
1020
1059
- Let {groupedSubfieldSet} be the result of calling
1021
- {CollectSubfields(objectType, fields , variableValues)}.
1060
+ {CollectSubfields(objectType, fieldDetails , variableValues, path )}.
1022
1061
- Let {completedResult}, {defers} and {streams} be the result of running
1023
1062
{ExecuteGroupedFieldSet(groupedSubfieldSet, objectType, result,
1024
1063
variableValues, path)} _ normally_ (allowing for parallelization).
0 commit comments