Skip to content
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
24 changes: 23 additions & 1 deletion spec/Appendix B -- Grammar Summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Punctuator :: one of ! $ & ( ) ... : = @ [ ] { | }

Name ::

- NameStart NameContinue\* [lookahead != NameContinue]
- NameStart NameContinue\* [lookahead != {NameContinue, `.`}]

NameStart ::

Expand All @@ -71,6 +71,11 @@ Digit :: one of

- `0` `1` `2` `3` `4` `5` `6` `7` `8` `9`

QualifiedName ::

- QualifiedName . Name [lookahead != `.`]
- Name . Name [lookahead != `.`]

IntValue :: IntegerPart [lookahead != {Digit, `.`, NameStart}]

IntegerPart ::
Expand Down Expand Up @@ -248,6 +253,7 @@ TypeSystemDefinition :
- SchemaDefinition
- TypeDefinition
- DirectiveDefinition
- ServiceDefinition

TypeSystemExtensionDocument : TypeSystemDefinitionOrExtension+

Expand Down Expand Up @@ -413,3 +419,19 @@ TypeSystemDirectiveLocation : one of
- `ENUM_VALUE`
- `INPUT_OBJECT`
- `INPUT_FIELD_DEFINITION`

ServiceDefinition :

- Description service [lookahead != `{`]
- Description? service { ServiceAttribute+ }

ServiceAttribute :

- ServiceCapabilities

ServiceCapabilities: capabilities { ServiceCapability+ }

ServiceCapability:

- Description? QualifiedName [lookahead != `(`]
- Description? QualifiedName ( StringValue )
14 changes: 13 additions & 1 deletion spec/Section 2 -- Language.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ lacks the punctuation often used to describe mathematical expressions.

Name ::

- NameStart NameContinue\* [lookahead != NameContinue]
- NameStart NameContinue\* [lookahead != {NameContinue, `.`}]

NameStart ::

Expand Down Expand Up @@ -233,6 +233,18 @@ Any {Name} within a GraphQL type system must not start with two underscores
{"\_\_"} unless it is part of the [introspection system](#sec-Introspection) as
defined by this specification.

### Qualified Names

QualifiedName ::

- QualifiedName . Name [lookahead != `.`]
- Name . Name [lookahead != `.`]

A qualified name is a case-sensitive string composed of two or more names
separated by a period (`.`). A qualified name allows for a structured chain of
names which can be useful for scoping or applying namespaces. A _capability
identifier_ is an example of a {QualifiedName}.

## Descriptions

Description : StringValue
Expand Down
94 changes: 94 additions & 0 deletions spec/Section 3 -- Type System.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ TypeSystemDefinition :
- SchemaDefinition
- TypeDefinition
- DirectiveDefinition
- ServiceDefinition

The GraphQL language includes an
[IDL](https://en.wikipedia.org/wiki/Interface_description_language) used to
Expand Down Expand Up @@ -2227,3 +2228,96 @@ to the relevant IETF specification.
```graphql example
scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122")
```

## Service Definition

ServiceDefinition :

- Description service [lookahead != `{`]
- Description? service { ServiceAttribute+ }

ServiceAttribute :

- ServiceCapabilities

A GraphQL service is defined in terms of the capabilities that it offers which
are external to the schema.

All capabilities within a service must have unique identifiers.

### Service Capabilities

ServiceCapabilities: capabilities { ServiceCapability+ }

ServiceCapability:

- Description? QualifiedName [lookahead != `(`]
- Description? QualifiedName ( StringValue )

:: A _service capability_ describes a feature supported by the GraphQL service
but not directly expressible via the type system. This may include support for
new or experimental GraphQL syntactic or behavioral features, protocol support
(such as GraphQL over WebSockets or Server-Sent Events), or additional
operational information (such as endpoints for related services). Service
capabilities may be supplied by the GraphQL implementation, the service, or
both.

A _service capability_ is identified by a _capability identifier_ (a
{QualifiedName}), and may optionally have a string value.

**Capability Identifier**

:: A _capability identifier_ is a {QualifiedName} (a case-sensitive string value
composed of two or more {Name} separated by a period (`.`)) that uniquely
identifies a capability. This structure is inspired by reverse domain notation
to encourage global uniqueness and collision-resistance; it is recommended that
identifiers defined by specific projects, vendors, or implementations begin with
a prefix derived from a DNS name they control (e.g., {"com.example."}).

Clients must compare capability identifiers using exact (case-sensitive) string
equality.

**Reserved Capability Identifiers**

A _capability identifier_ must not start with an underscore {"\_"}; this is
reserved for future usage.

Capability identifiers beginning with the prefix {"graphql."} are reserved and
must not be used outside of official GraphQL Foundation specifications.
Identifiers beginning with the prefix {"graphql.rfc."} are reserved for RFC
proposals.

Any identifiers beginning with case-insensitive variants of {"graphql."},
{"org.graphql."} and {"gql."} are also reserved.

Implementers should not change the meaning of capability identifiers; instead, a
new capability identifier should be used when the meaning changes. Implementers
should ensure that capability identifiers remain stable and version-agnostic
where possible.

Note: Capability versioning, if needed, can be indicated using dot suffixes
(e.g.{ "org.example.capability.v2"}).

This system enables incremental feature adoption and richer tooling
interoperability, while avoiding tight coupling to specific implementations.

**Capability value**

For capabilities that require more information than a simple indication of
support, a string value may be specified.

For example, the capability {"graphql.onError"} does not require additional
information and thus does not specify a value; whereas
{"graphql.defaultErrorBehavior"} uses the value to indicate which _error
behavior_ is the default.

**Specified capabilities**

This version of the specification defines the following capabilities:

- {"graphql.defaultErrorBehavior"} - indicates the _default error behavior_ of
the service via the {value}. If not present, assume the _default error
behavior_ is {"PROPAGATE"}.
- {"graphql.onError"} - indicates that the service allows the client to specify
{onError} in a request to indicate the _error behavior_ the service should use
for the request. No {value} is provided.
43 changes: 40 additions & 3 deletions spec/Section 4 -- Introspection.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,14 @@ operation.

## Schema Introspection

The schema introspection system is accessible from the meta-fields `__schema`
and `__type` which are accessible from the type of the root of a query
operation.
The schema introspection system is accessible from the meta-fields `__schema`,
`__type` and `__service` which are accessible from the type of the root of a
query operation.

```graphql
__schema: __Schema!
__type(name: String!): __Type
__service: __Service!
```

Like all meta-fields, these are implicit and do not appear in the fields list in
Expand Down Expand Up @@ -218,6 +219,16 @@ enum __DirectiveLocation {
INPUT_OBJECT
INPUT_FIELD_DEFINITION
}

type __Service {
capabilities: [__Capability!]!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if this should be a more generic "metadata" rather than "capabilities"? It would be essentially the same thing, just a different name and different expectation of what to expect in the list. For example, if some service would like to advertise some form of versioning they could do {identifier: "com.example.version", value: "4"} but that's not really a "capability" per se?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've thought about this quite a bit, and I think capabilities is better - it's more indicative of intent/purpose.

The aim is that clients can use the "capabilities" of the service to inform their own behaviors in an automated way, such that end users can just point to a GraphQL endpoint and the client will take care of the rest - they don't need to configure it extensively (and the configuration can be automatically updated on each release without frontend engineers needing to do anything).

For example:

  • does it have the capability of handling fragment arguments? If not, I must transpile them.
  • does it have the capability of handling document directives? If not, I must find them invalid
  • does it have the capability of customizing error propagation behavior? If not, I must assume that it will propagate errors (and should not pass onError)
  • does it support subscriptions over websockets? SSE? If so, what are the endpoints?
  • does it support automated document persistence? If so, which protocol does it use and what are the relevant configuration parameters?
  • does it support notification of new schema releases? If so, at which endpoint can I subscribe to these events? (Especially useful for GraphiQL)
  • does it support incremental delivery? Which version?
  • does it support {RFC feature X}?
  • etc

The capabilities system can be used for metadata, but that's not its purpose; and calling it metadata significantly changes what is being proposed (and also encourages it to be used for feeding a huge amount more "metadata" into the system). Capabilities inform automated client decisions and actions; metadata is ancillary and often intended for human consumption.

I will try and ensure this is correctly reflected in the spec text.

}

type __Capability {
identifier: String!
description: String
value: String
}
```

### The \_\_Schema Type
Expand Down Expand Up @@ -500,3 +511,29 @@ Fields\:
{true}, deprecated arguments are also returned.
- `isRepeatable` must return a Boolean that indicates if the directive may be
used repeatedly at a single location.

### The \_\_Service Type

The `__Service` type is returned from the `__service` meta-field and provides
information about the GraphQL service, most notably about its capabilities.

Note: Services implementing an older version of this specification may not
support the `__service` meta-field or `__Service` type. Support may be probed
using the introspection query: `{ __type(name: "__Service") { name } }`, a
{null} result indicates lack of support.

Fields\:

- `capabilities` must return a list of `__Capability` detailing each _service
capability_ supported by the service.

### The \_\_Capability Type

A `__Capability` object describes a specific _service capability_, and has the
following fields\:

- `identifier` must return the string _capability identifier_ uniquely
identifying this service capability.
- `description` may return a String or {null}.
- `value` the String value of the service capability, or {null} if there is no
associated value.
107 changes: 86 additions & 21 deletions spec/Section 6 -- Execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,24 @@ A GraphQL service generates a response from a request via execution.
being executed. Conceptually, an initial value represents the "universe" of
data available via a GraphQL Service. It is common for a GraphQL Service to
always use the same initial value for every request.
- {onError} (optional): The _error behavior_ to apply to the request; see
[Handling Execution Errors](#sec-Handling-Execution-Errors). If {onError} is
provided and its value is not one of {"NULL"}, {"PROPAGATE"}, or {"HALT"},
then a _request error_ must be raised.
- {extensions} (optional): A map reserved for implementation-specific additional
information.

Given this information, the result of {ExecuteRequest(schema, document,
operationName, variableValues, initialValue)} produces the response, to be
formatted according to the Response section below.

Note: Previous versions of this specification did not define the {onError}
request attribute. Clients can detect support for {onError} by checking for the
{"org.graphql.onError"} capability. If this capability is not present, or if
capabilities themselves are not supported by introspection, then clients should
not include {onError} in the request and must assume the _error behavior_ is
{"PROPAGATE"}.

Implementations should not add additional properties to a _request_, which may
conflict with future editions of the GraphQL specification. Instead,
{extensions} provides a reserved location for implementation-specific additional
Expand Down Expand Up @@ -600,13 +611,26 @@ section.
</a>

If during {ExecuteCollectedFields()} a _response position_ with a non-null type
raises an _execution error_ then that error must propagate to the parent
response position (the entire selection set in the case of a field, or the
entire list in the case of a list position), either resolving to {null} if
allowed or being further propagated to a parent response position.

If this occurs, any sibling response positions which have not yet executed or
have not yet yielded a value may be cancelled to avoid unnecessary work.
raises an _execution error_, the error must be added to the {"errors"} list in
the _execution result_ and then handled according to the _error behavior_ of the
request:

- {"NULL"}: The _response position_ must be set to {null}, even if such position
is indicated by the schema to be non-nullable. (The client is responsible for
interpreting this {null} in conjunction with the {"errors"} list to
distinguish error results from intentional {null} values.)
- {"PROPAGATE"}: The _execution error_ must propagate to the parent _response
position_ (the entire selection set in the case of a field, or the entire list
in the case of a list position). The parent position resolves to {null} if
allowed, or else the error is further propagated to a parent response
position. Any sibling response positions that have not yet executed or have
not yet yielded a value may be cancelled to avoid unnecessary work.
- {"HALT"}: The current {ExecuteRootSelectionSet()} must be aborted immediately
and must yield an execution result with an {"errors"} list consisting of this
_execution error_ only and the {"data"} entry set to {null}. Any _response
position_ that has not yet executed or has not yet yielded a value may be
cancelled to avoid unnecessary work. (Note: For a subscription operation the
underlying stream is not terminated.)

Note: See [Handling Execution Errors](#sec-Handling-Execution-Errors) for more
about this behavior.
Expand Down Expand Up @@ -902,29 +926,60 @@ ResolveAbstractType(abstractType, objectValue):
</a>

An _execution error_ is an error raised during field execution, value resolution
or coercion, at a specific _response position_. While these errors must be
reported in the response, they are "handled" by producing partial {"data"} in
the _response_.
or coercion, at a specific _response position_. These errors must be added to
the {"errors"} list in the _response_, and are "handled" according to the _error
behavior_ of the request.

Note: An _execution error_ is distinct from a _request error_ which results in a
response with no {"data"}.

If a _response position_ resolves to {null} because of an execution error which
has already been added to the {"errors"} list in the _execution result_, the
{"errors"} list must not be further affected. That is, only one error should be
added to the errors list per _response position_.

:: The _error behavior_ of a request indicates how an _execution error_ is
handled. It may be specified using the optional {onError} attribute of the
_request_. If omitted, the _default error behavior_ of the service applies.
Valid values for _error behavior_ are {"NULL"}, {"PROPAGATE"} and {"HALT"}.

:: The _default error behavior_ of a service is implementation-defined. For
compatibility with existing clients, services should default to {"PROPAGATE"}
which reflects prior behavior. <!-- For new services, {"NULL"} is
recommended. --> The default error behavior is indicated via the {"org.graphql.defaultErrorBehavior"}
_service capability_.

Note: {"HALT"} is not recommended as the _default error behavior_ because it
prevents generating partial responses which may still contain useful data.

Regardless of error behavior, if a _response position_ with a non-null type
results in {null} due to the result of {ResolveFieldValue()} then an execution
error must be raised at that position as specified in {CompleteValue()}.

Note: This is distinct from a _request error_ which results in a _request error
result_ with no data.
The _error behavior_ of a request applies to every _execution error_ raised
during execution. The following sections describe the behavior of each valid
value:

If an execution error is raised while resolving a field (either directly or
nested inside any lists), it is handled as though the _response position_ at
which the error occurred resolved to {null}, and the error must be added to the
{"errors"} list in the _execution result_.
**{"NULL"}**

With {"NULL"}, a `Non-Null` _response position_ will have the value {null} if
and only if an error occurred at that position.

Note: Clients must inspect the {"errors"} list and use the {"path"} of each
error result to distinguish between intentional {null} values and those
resulting from an _execution error_.

**{"PROPAGATE"}**

With {"PROPAGATE"}, a `Non-Null` _response position_ must not contain {null} in
the _response_.

If the result of resolving a _response position_ is {null} (either due to the
result of {ResolveFieldValue()} or because an execution error was raised), and
that position is of a `Non-Null` type, then an execution error is raised at that
position. The error must be added to the {"errors"} list in the _execution
result_.

If a _response position_ resolves to {null} because of an execution error which
has already been added to the {"errors"} list in the _execution result_, the
{"errors"} list must not be further affected. That is, only one error should be
added to the errors list per _response position_.

Since `Non-Null` response positions cannot be {null}, execution errors are
propagated to be handled by the parent _response position_. If the parent
response position may be {null} then it resolves to {null}, otherwise if it is a
Expand All @@ -939,3 +994,13 @@ position_ must resolve to {null}. If the `List` type is also wrapped in a
If every _response position_ from the root of the request to the source of the
execution error has a `Non-Null` type, then the {"data"} entry in the _execution
result_ should be {null}.

**{"HALT"}**

With {"HALT"}, {ExecuteRootSelectionSet()} must cease immediately that the first
_execution error_ is raised. That error must be added to the {"errors"} list,
and {"data"} must be {null}.

Note: For subscription operations, processing of the current event is ceased,
but the subscription still remains in place and future events will be processed
as normal.