@@ -336,6 +336,112 @@ serial):
336336 selection set.
337337- Return an unordered map containing {data} and {errors}.
338338
339+ ### Field Collection
340+
341+ Before execution, the selection set is converted to a grouped field set by
342+ calling {CollectFields()}. Each entry in the grouped field set is a list of
343+ fields that share a response key (the alias if defined, otherwise the field
344+ name). This ensures all fields with the same response key (including those in
345+ referenced fragments) are executed at the same time.
346+
347+ As an example, collecting the fields of this selection set would collect two
348+ instances of the field ` a ` and one of field ` b ` :
349+
350+ ``` graphql example
351+ {
352+ a {
353+ subfield1
354+ }
355+ ... ExampleFragment
356+ }
357+
358+ fragment ExampleFragment on Query {
359+ a {
360+ subfield2
361+ }
362+ b
363+ }
364+ ```
365+
366+ The depth-first-search order of the field groups produced by {CollectFields()}
367+ is maintained through execution, ensuring that fields appear in the executed
368+ response in a stable and predictable order.
369+
370+ CollectFields(objectType, selectionSet, variableValues, visitedFragments):
371+
372+ - If {visitedFragments} is not provided, initialize it to the empty set.
373+ - Initialize {groupedFields} to an empty ordered map of lists.
374+ - For each {selection} in {selectionSet}:
375+ - If {selection} provides the directive ` @skip ` , let {skipDirective} be that
376+ directive.
377+ - If {skipDirective}'s {if} argument is {true} or is a variable in
378+ {variableValues} with the value {true}, continue with the next {selection}
379+ in {selectionSet}.
380+ - If {selection} provides the directive ` @include ` , let {includeDirective} be
381+ that directive.
382+ - If {includeDirective}'s {if} argument is not {true} and is not a variable
383+ in {variableValues} with the value {true}, continue with the next
384+ {selection} in {selectionSet}.
385+ - If {selection} is a {Field}:
386+ - Let {responseKey} be the response key of {selection} (the alias if
387+ defined, otherwise the field name).
388+ - Let {groupForResponseKey} be the list in {groupedFields} for
389+ {responseKey}; if no such list exists, create it as an empty list.
390+ - Append {selection} to the {groupForResponseKey}.
391+ - If {selection} is a {FragmentSpread}:
392+ - Let {fragmentSpreadName} be the name of {selection}.
393+ - If {fragmentSpreadName} is in {visitedFragments}, continue with the next
394+ {selection} in {selectionSet}.
395+ - Add {fragmentSpreadName} to {visitedFragments}.
396+ - Let {fragment} be the Fragment in the current Document whose name is
397+ {fragmentSpreadName}.
398+ - If no such {fragment} exists, continue with the next {selection} in
399+ {selectionSet}.
400+ - Let {fragmentType} be the type condition on {fragment}.
401+ - If {DoesFragmentTypeApply(objectType, fragmentType)} is false, continue
402+ with the next {selection} in {selectionSet}.
403+ - Let {fragmentSelectionSet} be the top-level selection set of {fragment}.
404+ - Let {fragmentGroupedFieldSet} be the result of calling
405+ {CollectFields(objectType, fragmentSelectionSet, variableValues,
406+ visitedFragments)}.
407+ - For each {fragmentGroup} in {fragmentGroupedFieldSet}:
408+ - Let {responseKey} be the response key shared by all fields in
409+ {fragmentGroup}.
410+ - Let {groupForResponseKey} be the list in {groupedFields} for
411+ {responseKey}; if no such list exists, create it as an empty list.
412+ - Append all items in {fragmentGroup} to {groupForResponseKey}.
413+ - If {selection} is an {InlineFragment}:
414+ - Let {fragmentType} be the type condition on {selection}.
415+ - If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType,
416+ fragmentType)} is false, continue with the next {selection} in
417+ {selectionSet}.
418+ - Let {fragmentSelectionSet} be the top-level selection set of {selection}.
419+ - Let {fragmentGroupedFieldSet} be the result of calling
420+ {CollectFields(objectType, fragmentSelectionSet, variableValues,
421+ visitedFragments)}.
422+ - For each {fragmentGroup} in {fragmentGroupedFieldSet}:
423+ - Let {responseKey} be the response key shared by all fields in
424+ {fragmentGroup}.
425+ - Let {groupForResponseKey} be the list in {groupedFields} for
426+ {responseKey}; if no such list exists, create it as an empty list.
427+ - Append all items in {fragmentGroup} to {groupForResponseKey}.
428+ - Return {groupedFields}.
429+
430+ DoesFragmentTypeApply(objectType, fragmentType):
431+
432+ - If {fragmentType} is an Object Type:
433+ - if {objectType} and {fragmentType} are the same type, return {true},
434+ otherwise return {false}.
435+ - If {fragmentType} is an Interface Type:
436+ - if {objectType} is an implementation of {fragmentType}, return {true}
437+ otherwise return {false}.
438+ - If {fragmentType} is a Union:
439+ - if {objectType} is a possible type of {fragmentType}, return {true}
440+ otherwise return {false}.
441+
442+ Note: The steps in {CollectFields()} evaluating the ` @skip ` and ` @include `
443+ directives may be applied in either order since they apply commutatively.
444+
339445## Executing a Grouped Field Set
340446
341447To execute a grouped field set, the object value being evaluated and the object
@@ -471,112 +577,6 @@ A correct executor must generate the following result for that selection set:
471577}
472578```
473579
474- ### Field Collection
475-
476- Before execution, the selection set is converted to a grouped field set by
477- calling {CollectFields()}. Each entry in the grouped field set is a list of
478- fields that share a response key (the alias if defined, otherwise the field
479- name). This ensures all fields with the same response key (including those in
480- referenced fragments) are executed at the same time.
481-
482- As an example, collecting the fields of this selection set would collect two
483- instances of the field ` a ` and one of field ` b ` :
484-
485- ``` graphql example
486- {
487- a {
488- subfield1
489- }
490- ... ExampleFragment
491- }
492-
493- fragment ExampleFragment on Query {
494- a {
495- subfield2
496- }
497- b
498- }
499- ```
500-
501- The depth-first-search order of the field groups produced by {CollectFields()}
502- is maintained through execution, ensuring that fields appear in the executed
503- response in a stable and predictable order.
504-
505- CollectFields(objectType, selectionSet, variableValues, visitedFragments):
506-
507- - If {visitedFragments} is not provided, initialize it to the empty set.
508- - Initialize {groupedFields} to an empty ordered map of lists.
509- - For each {selection} in {selectionSet}:
510- - If {selection} provides the directive ` @skip ` , let {skipDirective} be that
511- directive.
512- - If {skipDirective}'s {if} argument is {true} or is a variable in
513- {variableValues} with the value {true}, continue with the next {selection}
514- in {selectionSet}.
515- - If {selection} provides the directive ` @include ` , let {includeDirective} be
516- that directive.
517- - If {includeDirective}'s {if} argument is not {true} and is not a variable
518- in {variableValues} with the value {true}, continue with the next
519- {selection} in {selectionSet}.
520- - If {selection} is a {Field}:
521- - Let {responseKey} be the response key of {selection} (the alias if
522- defined, otherwise the field name).
523- - Let {groupForResponseKey} be the list in {groupedFields} for
524- {responseKey}; if no such list exists, create it as an empty list.
525- - Append {selection} to the {groupForResponseKey}.
526- - If {selection} is a {FragmentSpread}:
527- - Let {fragmentSpreadName} be the name of {selection}.
528- - If {fragmentSpreadName} is in {visitedFragments}, continue with the next
529- {selection} in {selectionSet}.
530- - Add {fragmentSpreadName} to {visitedFragments}.
531- - Let {fragment} be the Fragment in the current Document whose name is
532- {fragmentSpreadName}.
533- - If no such {fragment} exists, continue with the next {selection} in
534- {selectionSet}.
535- - Let {fragmentType} be the type condition on {fragment}.
536- - If {DoesFragmentTypeApply(objectType, fragmentType)} is false, continue
537- with the next {selection} in {selectionSet}.
538- - Let {fragmentSelectionSet} be the top-level selection set of {fragment}.
539- - Let {fragmentGroupedFieldSet} be the result of calling
540- {CollectFields(objectType, fragmentSelectionSet, variableValues,
541- visitedFragments)}.
542- - For each {fragmentGroup} in {fragmentGroupedFieldSet}:
543- - Let {responseKey} be the response key shared by all fields in
544- {fragmentGroup}.
545- - Let {groupForResponseKey} be the list in {groupedFields} for
546- {responseKey}; if no such list exists, create it as an empty list.
547- - Append all items in {fragmentGroup} to {groupForResponseKey}.
548- - If {selection} is an {InlineFragment}:
549- - Let {fragmentType} be the type condition on {selection}.
550- - If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType,
551- fragmentType)} is false, continue with the next {selection} in
552- {selectionSet}.
553- - Let {fragmentSelectionSet} be the top-level selection set of {selection}.
554- - Let {fragmentGroupedFieldSet} be the result of calling
555- {CollectFields(objectType, fragmentSelectionSet, variableValues,
556- visitedFragments)}.
557- - For each {fragmentGroup} in {fragmentGroupedFieldSet}:
558- - Let {responseKey} be the response key shared by all fields in
559- {fragmentGroup}.
560- - Let {groupForResponseKey} be the list in {groupedFields} for
561- {responseKey}; if no such list exists, create it as an empty list.
562- - Append all items in {fragmentGroup} to {groupForResponseKey}.
563- - Return {groupedFields}.
564-
565- DoesFragmentTypeApply(objectType, fragmentType):
566-
567- - If {fragmentType} is an Object Type:
568- - if {objectType} and {fragmentType} are the same type, return {true},
569- otherwise return {false}.
570- - If {fragmentType} is an Interface Type:
571- - if {objectType} is an implementation of {fragmentType}, return {true}
572- otherwise return {false}.
573- - If {fragmentType} is a Union:
574- - if {objectType} is a possible type of {fragmentType}, return {true}
575- otherwise return {false}.
576-
577- Note: The steps in {CollectFields()} evaluating the ` @skip ` and ` @include `
578- directives may be applied in either order since they apply commutatively.
579-
580580## Executing Fields
581581
582582Each field requested in the grouped field set that is defined on the selected
0 commit comments