Skip to content

add spec edits for references #998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 73 additions & 2 deletions spec/Section 2 -- Language.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ which returns the result:

## Fragments

FragmentSpread : ... FragmentName Directives?
FragmentSpread : ... Reference? FragmentName Directives?

FragmentDefinition : fragment FragmentName TypeCondition Directives?
SelectionSet
Expand Down Expand Up @@ -663,9 +663,80 @@ be present and `likers` will not. Conversely when the result is a `Page`,
}
```

## Fragment Spread References

Reference : Name : SignalSet

SignalSet: { Signal+ }

Signal : `selected`

Note: Additional signal types may be added to facilitate incremental delivery or
other functionality.

By default, utilizing fragment spreads does not alter the response shape. It may
sometimes be convenient, however, to include a reference within the response
signalling referring to a fragment containing information about its execution,
e.g. whether its selections were collected and spread. In particular, this may
be useful when fragments are included conditionally, such as with
[type conditions](#sec-type-conditions), especially in the presence of
potentially complex type hierarchies.

For example:

```graphql example
query FragmentReferences {
nodes(ids: [1, 42]) {
id
UserFields { selected }: ...userFragment
SuperUserFields { selected }: ...superUserFragment
}
}

fragment userFragment on User {
friends {
count
}
}

fragment superUserFragment on SuperUser {
privilegeLevel
}
```

The `nodes` root field returns a list where each element could potentially be of
many types. When the object in the `nodes` result is a `User`,
`UserFields.selected` will be present. If the result is also a `SuperUser`,
`SuperUserFields.selected` will be present.

```json example
{
"nodes": [
{
"id": 1,
"UserFields": { "selected": null },
"friends": { "count": 1234 }
},
{
"id": 42,
"UserFields": { "selected": null },
"friends": { "count": 5678 },
"SuperUserFields": { "selected": null },
"privilegeLevel": 20
}
]
}
```

Note: The value corresponding to the `selected` signal within a reference is not
specified.

Note: References may not be included for fragments whose parent type is the root
subscription type.

### Inline Fragments

InlineFragment : ... TypeCondition? Directives? SelectionSet
InlineFragment : Reference? ... TypeCondition? Directives? SelectionSet

Fragments can also be defined inline within a selection set. This is useful for
conditionally including fields based on a type condition or applying a directive
Expand Down
14 changes: 14 additions & 0 deletions spec/Section 5 -- Validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,20 @@ FieldsInSetCanMerge(set):
- Let {mergedSet} be the result of adding the selection set of {fieldA} and
the selection set of {fieldB}.
- {FieldsInSetCanMerge(mergedSet)} must be true.
- {ReferencesInSetCanMerge(set)} must be true.

ReferencesInSetCanMerge(references):

- Let {fieldResponseNames} be the set of response names for fields within {set}
including visiting fragments and inline fragments.
- Let {references} be the set of references in {set}, including visiting
fragments and inline fragments.
- For each {reference} in {references}:
- The name of {reference} must not be contained by {fieldResponseNames}.
- Given each pair of members {referenceA} and {referenceB} in {references}:
- If the parent types of {fieldA} and {fieldB} are equal or if either is not
an Object Type:
- {referenceA} and {referenceB} must have different names.

SameResponseShape(fieldA, fieldB):

Expand Down
65 changes: 51 additions & 14 deletions spec/Section 6 -- Execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,8 @@ map.

ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues):

- Let {groupedFieldSet} be the result of {CollectFields(objectType,
selectionSet, variableValues)}.
- Let {groupedFieldSet} and {groupedSignals} be the result of
{CollectFields(objectType, selectionSet, variableValues)}.
- Initialize {resultMap} to an empty ordered map.
- For each {groupedFieldSet} as {responseKey} and {fields}:
- Let {fieldName} be the name of the first entry in {fields}. Note: This value
Expand All @@ -346,6 +346,12 @@ ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues):
- Let {responseValue} be {ExecuteField(objectType, objectValue, fieldType,
fields, variableValues)}.
- Set {responseValue} as the value for {responseKey} in {resultMap}.
- For each {groupedSignals} as {referenceName} and {requestedSignals}:
- Initialize {signalValues} to an empty ordered map.
- For each {signal} in {requestedSignals}:
- If the signal type of {signal} is `selected`:
- Set {null} as the value for `selected` in {signalValues}.
- Set {signalValues} as the value for {referenceName} in {resultMap}.
- Return {resultMap}.

Note: {resultMap} is ordered by which fields appear first in the operation. This
Expand Down Expand Up @@ -461,11 +467,12 @@ A correct executor must generate the following result for that selection set:

### Field Collection

Before execution, the selection set is converted to a grouped field set by
calling {CollectFields()}. Each entry in the grouped field set is a list of
fields that share a response key (the alias if defined, otherwise the field
name). This ensures all fields with the same response key (including those in
referenced fragments) are executed at the same time.
Before execution, the selection set is converted to a grouped field set and
grouped reference signal set by calling {CollectFields()}. Each entry in the
grouped field set is a list of fields that share a response key (the alias if
defined, otherwise the field name). This ensures all fields with the same
response key (including those in referenced fragments) are executed at the same
time.

As an example, collecting the fields of this selection set would collect two
instances of the field `a` and one of field `b`:
Expand Down Expand Up @@ -494,6 +501,7 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments):

- If {visitedFragments} is not provided, initialize it to the empty set.
- Initialize {groupedFields} to an empty ordered map of lists.
- Initialize {groupedSignals} to an empty ordered map of sets.
- For each {selection} in {selectionSet}:
- If {selection} provides the directive `@skip`, let {skipDirective} be that
directive.
Expand Down Expand Up @@ -523,32 +531,52 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments):
- Let {fragmentType} be the type condition on {fragment}.
- If {DoesFragmentTypeApply(objectType, fragmentType)} is false, continue
with the next {selection} in {selectionSet}.
- If a {reference} is defined for the given {fragment}:
- Let {signalsForReference} be the result of calling
{CollectSignals(reference)}.
- Let {referenceName} be the name of {reference}.
- Set the entry for {referenceName} in {groupedSignals} to
{signalsForReference}.
- Let {fragmentSelectionSet} be the top-level selection set of {fragment}.
- Let {fragmentGroupedFieldSet} be the result of calling
{CollectFields(objectType, fragmentSelectionSet, variableValues,
visitedFragments)}.
- Let {fragmentGroupedFieldSet} and {fragmentRequestedSignals} be the result
of calling {CollectFields(objectType, fragmentSelectionSet,
variableValues, visitedFragments)}.
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
- Let {responseKey} be the response key shared by all fields in
{fragmentGroup}.
- Let {groupForResponseKey} be the list in {groupedFields} for
{responseKey}; if no such list exists, create it as an empty list.
- Append all items in {fragmentGroup} to {groupForResponseKey}.
- For each {fragmentRequestedSignals} as {referenceName} and
{signalsForReference}:
- Set the entry for {referenceName} in {groupedSignals} to
{signalsForReference}.
- If {selection} is an {InlineFragment}:
- Let {fragmentType} be the type condition on {selection}.
- If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType,
fragmentType)} is false, continue with the next {selection} in
{selectionSet}.
- If a {reference} is defined for the given {fragment}:
- Let {signalsForReference} be the result of calling
{CollectSignals(reference)}.
- Let {referenceName} be the name of {reference}.
- Set the entry for {referenceName} in {requestedSignals} to
{signalsForReference}.
- Let {fragmentSelectionSet} be the top-level selection set of {selection}.
- Let {fragmentGroupedFieldSet} be the result of calling
{CollectFields(objectType, fragmentSelectionSet, variableValues,
visitedFragments)}.
- Let {fragmentGroupedFieldSet} and {fragmentRequestedSignals} be the result
of calling {CollectFields(objectType, fragmentSelectionSet,
variableValues, visitedFragments)}.
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
- Let {responseKey} be the response key shared by all fields in
{fragmentGroup}.
- Let {groupForResponseKey} be the list in {groupedFields} for
{responseKey}; if no such list exists, create it as an empty list.
- Append all items in {fragmentGroup} to {groupForResponseKey}.
- Return {groupedFields}.
- For each {fragmentRequestedSignals} as {referenceName} and
{signalsForReference}:
- Set the entry for {referenceName} in {groupedSignals} to
{signalsForReference}.
- Return {groupedFields} and {groupedSignals}.

DoesFragmentTypeApply(objectType, fragmentType):

Expand All @@ -562,6 +590,15 @@ DoesFragmentTypeApply(objectType, fragmentType):
- if {objectType} is a possible type of {fragmentType}, return {true}
otherwise return {false}.

CollectSignals(reference):

- Let {signalsForReference} be an empty set.
- Let {signalSet} be the signal set of {reference}.
- For each {signal} in {signalSet}:
- Let {signalType} be the signal type of {signal}.
- Add {signalType} to {signalsForReference}.
- Return {signalsForReference}.

Note: The steps in {CollectFields()} evaluating the `@skip` and `@include`
directives may be applied in either order since they apply commutatively.

Expand Down