@@ -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,58 @@ 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 {executionResult} be the value for key {executionResult} in
222
+ {deferRecord}.
223
+ - Let {data} be the value for key {data} in {executionResult}.
224
+ - Let {childErrors} be the value for key {childErrors} in
225
+ {executionResult}.
226
+ - Let {childDefers} be the value for key {childDefers} in
227
+ {executionResult}.
228
+ - Let {childStreams} be the value for key {childStreams} in
229
+ {executionResult}.
230
+ - Let {childErrors} be the list of all _ field error_ raised while
231
+ executing the selection set.
232
+ - Let {incrementalPayload} be an unordered object containing {path},
233
+ {data}, and the key {errors} with value {childErrors}.
234
+ - Append {incrementalPayload} to {batchIncremental}.
235
+ - Add the entries of {childDefers} into {batchDefers}. Note:
236
+ {childDefers} and {batchDefers} will never have keys in common.
237
+ - For each entry {stream} in {childStreams}, append {stream} to
238
+ {batchStreams}.
239
+ - Add {deferRecord} to {completedDeferRecords}.
235
240
- For each entry {incrementalPayload} in {batchIncremental}, append
236
241
{incrementalPayload} to {incremental}.
237
242
- 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}.
243
+ - For each entry in {batchDefers} as {deferPath } and {deferRecords}:
244
+ - Let {id } be {nextId} and increment {nextId} by one.
245
+ - Let {path} be {deferPath }.
246
+ - Let {pendingPayload} be an unordered map containing {id}, {path}.
247
+ - Add {pendingPayload} to {pending}.
248
+ - Let {defers} be {deferRecords }.
249
+ - Let {pendingDefer} be an unordered map containing {id}, {defers}.
250
+ - Append {pendingDefer} to {remainingDefers}.
246
251
- For each entry {streamDetails} in {batchStreams}:
247
252
- Let {id} be {nextId} and increment {nextId} by one.
248
253
- Let {path} be the value for the key {path} in {streamDetails}.
@@ -274,7 +279,8 @@ variableValues):
274
279
{remainingValueIndex}.
275
280
- Let {path} be a copy of {parentPath} with {index} appended.
276
281
- Let {value}, {childDefers} and {childStreams} be the result of running
277
- {CompleteValue(itemType, fields, remainingValue, variableValues, path)}.
282
+ {CompleteValue(itemType, fieldDetails, remainingValue, variableValues,
283
+ path)}.
278
284
- Let {childErrors} be the list of all _ field error_ raised while
279
285
completing the value.
280
286
- Let {incrementalPayload} be an unordered object containing {path},
@@ -323,6 +329,36 @@ TODO: Consider rewording to something like: Let {longestCommonPathPrefix} be the
323
329
longest list such that every entry in {paths} starts with
324
330
{longestCommonPathPrefix}. Note: This may be the empty list.
325
331
332
+ A Deferred Field Record is a structure containing:
333
+
334
+ - {path} a list of field names and indices from root to the location of the
335
+ field in the response
336
+ - {executionResult}: A structure that will only be available after asynchronous
337
+ execution of this field is complete. Implementors are not required to
338
+ immediately begin this execution. This structure contains:
339
+ - {errors}: The list of all _ field error_ raised while executing the selection
340
+ set.
341
+ - {data}: The completed result of executing the field.
342
+ - {childDefers}: Any downstream defers discovered while executing this field.
343
+ - {childStreams}: Any downstream streams discovered while executing this
344
+ field.
345
+
346
+ ExecuteDeferredField(objectType, objectValue, fieldDetails, variableValues,
347
+ path):
348
+
349
+ - Let {deferRecord} be a DeferredFieldRecord created from {path}.
350
+ - Let {groupedFieldSet} be a new grouped field set consisting of {fieldDetails}.
351
+ - Let {executionResult} be the asynchronous future value of:
352
+ - Let {data}, {childDefers} and {childStreams} be the result of running
353
+ {ExecuteGroupedFieldSet(groupedFieldSet, objectType, objectValue,
354
+ variableValues, path)}.
355
+ - Let {childErrors} be the list of all _ field error_ raised while executing
356
+ the selection set.
357
+ - Return {data}, {errors}, {childDefers}, {childStreams}.
358
+ - Assign {executionResult}, {errors}, {childDefers}, {childStreams} to
359
+ {deferRecord}.
360
+ - Return {deferRecord}.
361
+
326
362
### Mutation
327
363
328
364
If the operation is a mutation, the result of the operation is the result of
@@ -539,11 +575,11 @@ Each represented field in the grouped field set produces an entry into a
539
575
response map.
540
576
541
577
ExecuteGroupedFieldSet(groupedFieldSet, objectType, objectValue, variableValues,
542
- parentPath):
578
+ parentPath, deferPaths ):
543
579
544
580
- If {parentPath} is not provided, initialize it to an empty list.
545
581
- Initialize {resultMap} to an empty ordered map.
546
- - Let {defers} be an empty unordered map.
582
+ - Let {defers} be an empty unordered map of unordered maps .
547
583
- Let {streams} be an empty list.
548
584
- For each {groupedFieldSet} as {responseKey} and {fieldDetails}:
549
585
- Let {fieldDetail} be the first entry in {fieldDetails}.
@@ -553,15 +589,22 @@ parentPath):
553
589
alias is used.
554
590
- Let {fieldType} be the return type defined for the field {fieldName} of
555
591
{objectType}.
592
+ - Let {fields} be a list of all the values of the {field} key in the entries
593
+ of {fieldDetails}.
556
594
- 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}.
595
+ - If every entry in {fieldDetails} has a {deferPath} which is not included
596
+ in {deferPaths}:
597
+ - Let {deferRecord} be the result of running
598
+ {ExecuteDeferredField(objectType, objectValue, fields, variableValues,
599
+ path)}.
600
+ - For each {fieldDetail} in {fieldDetails}:
601
+ - Let {deferForPath} be the map in {defers} for {path}; if no such map
602
+ exists, create it as an empty unordered map.
603
+ - Add an entry to {deferForPath} with key {path} and the value
604
+ {deferRecord}.
560
605
- Otherwise:
561
- - Let {fields} be a list of all the values of the {field} key in the
562
- entries of {fieldDetails}.
563
606
- Let {resolvedValue} be the result of running {ExecuteField(objectType,
564
- objectValue, fieldType, fields , variableValues, path)}.
607
+ objectValue, fieldType, fieldDetails , variableValues, path)}.
565
608
- Let {nullableFieldType} be the inner type of {fieldType} if {fieldType}
566
609
is a non-nullable type, otherwise let {nullableFieldType} be
567
610
{fieldType}.
@@ -582,7 +625,7 @@ parentPath):
582
625
- Let {initialValues} be the first {initialCount} entries in
583
626
{resolvedValue}, and {remainingValues} be the remainder.
584
627
- Let {initialResponseValue}, {childDefers}, {childStreams} be the
585
- result of running {CompleteValue(nullableFieldType, fields ,
628
+ result of running {CompleteValue(nullableFieldType, fieldDetails ,
586
629
initialValues, variableValues, path)}.
587
630
- Add the entries of {childDefers} into {defers}. Note: {childDefers}
588
631
and {defers} will never have keys in common.
@@ -596,7 +639,7 @@ parentPath):
596
639
- Append {streamDetails} to {streams}.
597
640
- Otherwise:
598
641
- Let {responseValue}, {childDefers} and {childStreams} be the result of
599
- running {CompleteValue(fieldType, fields , resolvedValue,
642
+ running {CompleteValue(fieldType, fieldDetails , resolvedValue,
600
643
variableValues, path)}.
601
644
- Add the entries of {childDefers} into {defers}. Note: {childDefers}
602
645
and {defers} will never have keys in common.
@@ -747,13 +790,11 @@ The depth-first-search order of the field groups produced by {CollectFields()}
747
790
is maintained through execution, ensuring that fields appear in the executed
748
791
response in a stable and predictable order.
749
792
750
- CollectFields(objectType, selectionSet, variableValues, isDeferred ,
751
- visitedFragments ):
793
+ CollectFields(objectType, selectionSet, variableValues, visitedFragments ,
794
+ parentPath, deferPath ):
752
795
753
- - Initialize {groupedFieldSet} to an empty ordered map of lists.
754
- - If {isDeferred} is not provided, initialize it to {false}.
755
796
- If {visitedFragments} is not provided, initialize it to the empty set.
756
- - Initialize {groupedFields } to an empty ordered map of lists.
797
+ - Initialize {groupedFieldSet } to an empty ordered map of lists.
757
798
- For each {selection} in {selectionSet}:
758
799
- If {selection} provides the directive ` @skip ` , let {skipDirective} be that
759
800
directive.
@@ -771,7 +812,7 @@ visitedFragments):
771
812
otherwise the field name).
772
813
- Let {groupForResponseKey} be the list in {groupedFieldSet} for
773
814
{responseKey}; if no such list exists, create it as an empty list.
774
- - Let {fieldDetail} be an unordered map containing {field} and {isDeferred }.
815
+ - Let {fieldDetail} be an unordered map containing {field} and {deferPath }.
775
816
- Append {fieldDetail} to the {groupForResponseKey}.
776
817
- If {selection} is a {FragmentSpread}:
777
818
- Let {fragmentSpreadName} be the name of {selection}.
@@ -785,16 +826,16 @@ visitedFragments):
785
826
- Let {fragmentType} be the type condition on {fragment}.
786
827
- If {DoesFragmentTypeApply(objectType, fragmentType)} is false, continue
787
828
with the next {selection} in {selectionSet}.
788
- - Let {fragmentIsDeferred } be {isDeferred }.
829
+ - Let {fragmentDeferPath } be {deferPath }.
789
830
- If {selection} provides the directive ` @defer ` , let {deferDirective} be
790
831
that directive.
791
832
- If {deferDirective}'s {if} argument is not {false} and is not a variable
792
833
in {variableValues} with the value {false}:
793
- - Let {fragmentIsDeferred } be {true }.
834
+ - Let {fragmentDeferPath } be {parentPath }.
794
835
- Let {fragmentSelectionSet} be the top-level selection set of {fragment}.
795
836
- Let {fragmentGroupedFieldSet} be the result of running
796
837
{CollectFields(objectType, fragmentSelectionSet, variableValues,
797
- fragmentIsDeferred, visitedFragments )}.
838
+ visitedFragments, parentPath, fragmentDeferPath )}.
798
839
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
799
840
- Let {responseKey} be the response key shared by all fields in
800
841
{fragmentGroup}.
@@ -806,16 +847,16 @@ visitedFragments):
806
847
- If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType,
807
848
fragmentType)} is false, continue with the next {selection} in
808
849
{selectionSet}.
809
- - Let {fragmentIsDeferred } be {isDeferred }.
850
+ - Let {fragmentDeferPath } be {deferPath }.
810
851
- If {selection} provides the directive ` @defer ` , let {deferDirective} be
811
852
that directive.
812
853
- If {deferDirective}'s {if} argument is not {false} and is not a variable
813
854
in {variableValues} with the value {false}:
814
- - Let {fragmentIsDeferred } be {true }.
855
+ - Let {fragmentDeferPath } be {parentPath }.
815
856
- Let {fragmentSelectionSet} be the top-level selection set of {selection}.
816
857
- Let {fragmentGroupedFieldSet} be the result of running
817
858
{CollectFields(objectType, fragmentSelectionSet, variableValues,
818
- fragmentIsDeferred, visitedFragments )}.
859
+ visitedFragments, parentPath, fragmentDeferPath )}.
819
860
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
820
861
- Let {responseKey} be the response key shared by all fields in
821
862
{fragmentGroup}.
@@ -854,16 +895,18 @@ CollectRootFields(rootType, operationSelectionSet, variableValues):
854
895
855
896
Object subfield collection processes a field's sub-selection sets:
856
897
857
- CollectSubfields(objectType, fields , variableValues):
898
+ CollectSubfields(objectType, fieldDetails , variableValues, parentPath ):
858
899
859
900
- Initialize {visitedFragments} to the empty set.
860
901
- Initialize {groupedSubfieldSet} to an empty ordered map of lists.
861
- - For each {field} in {fields}:
902
+ - For each {fieldDetail} in {fieldDetails}:
903
+ - Let {field} be the value for the key {field} in {fieldDetail}.
904
+ - Let {deferPath} be the value for the key {deferPath} in {fieldDetail}.
862
905
- Let {fieldSelectionSet} be the selection set of {field}.
863
906
- If {fieldSelectionSet} is null or empty, continue to the next field.
864
907
- Let {fieldGroupedFieldSet} be the result of calling
865
908
{CollectFields(objectType, fragmentSelectionSet, variableValues,
866
- visitedFragments)}.
909
+ visitedFragments, parentPath, deferPath )}.
867
910
- For each {fieldGroup} in {fieldGroupedFieldSet}:
868
911
- Let {responseKey} be the response key shared by all fields in
869
912
{fragmentGroup}.
@@ -975,12 +1018,12 @@ After resolving the value for a field, it is completed by ensuring it adheres to
975
1018
the expected return type. If the return type is another Object type, then the
976
1019
field execution process continues recursively.
977
1020
978
- CompleteValue(fieldType, fields , result, variableValues, path):
1021
+ CompleteValue(fieldType, fieldDetails , result, variableValues, path):
979
1022
980
1023
- If the {fieldType} is a Non-Null type:
981
1024
- Let {innerType} be the inner type of {fieldType}.
982
1025
- Let {completedResult}, {defers} and {streams} be the result of running
983
- {CompleteValue(innerType, fields , result, variableValues, path)}.
1026
+ {CompleteValue(innerType, fieldDetails , result, variableValues, path)}.
984
1027
- If {completedResult} is {null}, raise a _ field error_ .
985
1028
- Return {completedResult}, {defers} and {streams}.
986
1029
- If {result} is {null} (or another internal value similar to {null} such as
@@ -998,8 +1041,8 @@ CompleteValue(fieldType, fields, result, variableValues, path):
998
1041
- For each entry {resultItem} at zero-based index {resultIndex} in {result}:
999
1042
- Let {listItemPath} be a copy of {path} with {resultIndex} appended.
1000
1043
- Let {completedItemResult}, {childDefers} and {childStreams} be the result
1001
- of running {CompleteValue(innerType, fields , resultItem, variableValues ,
1002
- listItemPath)}.
1044
+ of running {CompleteValue(innerType, fieldDetails , resultItem,
1045
+ variableValues, listItemPath)}.
1003
1046
- Add the entries of {childDefers} into {defers}. Note: {childDefers} and
1004
1047
{defers} will never have keys in common.
1005
1048
- For each entry {stream} in {childStreams}, append {stream} to {streams}.
@@ -1018,7 +1061,7 @@ CompleteValue(fieldType, fields, result, variableValues, path):
1018
1061
- Let {objectType} be the result of running {ResolveAbstractType(fieldType,
1019
1062
result)}.
1020
1063
- Let {groupedSubfieldSet} be the result of calling
1021
- {CollectSubfields(objectType, fields , variableValues)}.
1064
+ {CollectSubfields(objectType, fieldDetails , variableValues, path )}.
1022
1065
- Let {completedResult}, {defers} and {streams} be the result of running
1023
1066
{ExecuteGroupedFieldSet(groupedSubfieldSet, objectType, result,
1024
1067
variableValues, path)} _ normally_ (allowing for parallelization).
0 commit comments