diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index b855de085..7e348fac2 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -247,13 +247,14 @@ query getName { * Let {subscriptionType} be the root Subscription type in {schema}. * Let {selectionSet} be the top level selection set on {subscription}. * Let {variableValues} be the empty set. -* Let {groupedFieldSet} be the result of - {CollectFields(subscriptionType, selectionSet, variableValues)}. -* {groupedFieldSet} must have exactly one entry. +* Let {groupedFieldSetExcludingIntrospection} be the result of + {CollectFields(subscriptionType, selectionSet, variableValues, true)}. +* {groupedFieldSetExcludingIntrospection} must have exactly one entry. **Explanatory Text** -Subscription operations must have exactly one root field. +Subscription operations must have exactly one non-introspection root field. +(Subscription operations may have any number of introspection root fields.) Valid examples: @@ -279,6 +280,18 @@ fragment newMessageFields on Subscription { } ``` +Introspection fields are not counted. The following example is also valid: + +```graphql counter-example +subscription sub { + newMessage { + body + sender + } + __typename +} +``` + Invalid: ```graphql counter-example @@ -305,22 +318,19 @@ fragment multipleSubscriptions on Subscription { } ``` -Introspection fields are counted. The following example is also invalid: +Introspection fields are not counted. The following example is also invalid: ```graphql counter-example subscription sub { - newMessage { - body - sender - } __typename } ``` -Note: While each subscription must have exactly one root field, a document may -contain any number of operations, each of which may contain different root -fields. When executed, a document containing multiple subscription operations -must provide the operation name as described in {GetOperation()}. +Note: While each subscription must have exactly one non-introspection root +field, a document may contain any number of operations, each of which may +contain different root fields. When executed, a document containing multiple +subscription operations must provide the operation name as described in +{GetOperation()}. ## Fields diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 236bff77a..94241a376 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -250,10 +250,10 @@ CreateSourceEventStream(subscription, schema, variableValues, initialValue): * Let {subscriptionType} be the root Subscription type in {schema}. * Assert: {subscriptionType} is an Object type. - * Let {groupedFieldSet} be the result of - {CollectFields(subscriptionType, selectionSet, variableValues)}. - * If {groupedFieldSet} does not have exactly one entry, throw a query error. - * Let {fields} be the value of the first entry in {groupedFieldSet}. + * Let {groupedFieldSetExcludingIntrospection} be the result of + {CollectFields(subscriptionType, selectionSet, variableValues, true)}. + * If {groupedFieldSetExcludingIntrospection} does not have exactly one entry, throw a query error. + * Let {fields} be the value of the first entry in {groupedFieldSetExcludingIntrospection}. * Let {fieldName} be the name of the first entry in {fields}. Note: This value is unaffected if an alias is used. * Let {field} be the first entry in {fields}. @@ -477,8 +477,9 @@ The depth-first-search order of the field groups produced by {CollectFields()} is maintained through execution, ensuring that fields appear in the executed response in a stable and predictable order. -CollectFields(objectType, selectionSet, variableValues, visitedFragments): +CollectFields(objectType, selectionSet, variableValues, excludeIntrospection, visitedFragments): + * If {excludeIntrospection} is not provided, initialize it to {false}. * If {visitedFragments} is not provided, initialize it to the empty set. * Initialize {groupedFields} to an empty ordered map of lists. * For each {selection} in {selectionSet}: @@ -489,10 +490,12 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): * If {includeDirective}'s {if} argument is not {true} and is not a variable in {variableValues} with the value {true}, continue with the next {selection} in {selectionSet}. * If {selection} is a {Field}: - * Let {responseKey} be the response key of {selection} (the alias if defined, otherwise the field name). - * Let {groupForResponseKey} be the list in {groupedFields} for - {responseKey}; if no such list exists, create it as an empty list. - * Append {selection} to the {groupForResponseKey}. + * Let {fieldName} be the field name of {selection}. + * If {excludeIntrospection} is {false} or {fieldName} does not begin with the characters "__" (two underscores): + * Let {responseKey} be the response key of {selection} (the alias if defined, otherwise the field name). + * Let {groupForResponseKey} be the list in {groupedFields} for + {responseKey}; if no such list exists, create it as an empty list. + * Append {selection} to the {groupForResponseKey}. * If {selection} is a {FragmentSpread}: * Let {fragmentSpreadName} be the name of {selection}. * If {fragmentSpreadName} is in {visitedFragments}, continue with the @@ -507,7 +510,7 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): with the next {selection} in {selectionSet}. * Let {fragmentSelectionSet} be the top-level selection set of {fragment}. * Let {fragmentGroupedFieldSet} be the result of calling - {CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments)}. + {CollectFields(objectType, fragmentSelectionSet, variableValues, excludeIntrospection, 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 @@ -518,7 +521,7 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): * If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType, fragmentType)} is false, continue with the next {selection} in {selectionSet}. * Let {fragmentSelectionSet} be the top-level selection set of {selection}. - * Let {fragmentGroupedFieldSet} be the result of calling {CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments)}. + * Let {fragmentGroupedFieldSet} be the result of calling {CollectFields(objectType, fragmentSelectionSet, variableValues, excludeIntrospection, 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