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
219 changes: 219 additions & 0 deletions spec/Appendix E -- Examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# E. Appendix: Examples

## Incremental Delivery Examples

### Example 1 - A query containing both defer and stream

```graphql example
query {
person(id: "cGVvcGxlOjE=") {
...HomeWorldFragment @defer(label: "homeWorldDefer")
name
films @stream(initialCount: 1, label: "filmsStream") {
title
}
}
}
fragment HomeWorldFragment on Person {
homeWorld {
name
}
}
```

The response to this request will be an _incremental stream_ consisting of an
_initial incremental stream result_ followed by one or more _incremental stream
update result_.

The _initial incremental stream result_ has:

- a {"data"} entry containing the results of the GraphQL operation except for
the `@defer` and `@stream` selections;
- a {"pending"} entry containing two _incremental pending notices_, one for the
`@defer` selection and for the the `@stream` selection, indicating that these
results will be delivered in a later _incremental stream update result_;
- a {"hasNext"} entry with the value {true}, indicating that the response is not
yet complete.

If an error were to occur, it would also have an {"errors"} entry; but not in
this example.

```json example
{
"data": {
"person": {
"name": "Luke Skywalker",
"films": [{ "title": "A New Hope" }]
}
},
"pending": [
{ "id": "0", "path": ["person"], "label": "homeWorldDefer" },
{ "id": "1", "path": ["person", "films"], "label": "filmsStream" }
],
"hasNext": true
}
```

Depending on the behavior of the backend and the time at which the deferred and
streamed resources resolve, the stream may produce results in different orders.
In this example, our first _incremental stream update result_ contains the
deferred data and the first streamed list item. There is one _incremental
completion notice_, indicating that the deferred data has been completely
delivered.

```json example
{
"incremental": [
{
"id": "0",
"data": { "homeWorld": { "name": "Tatooine" } }
},
{
"id": "1",
"items": [{ "title": "The Empire Strikes Back" }]
}
],
"completed": [
{"id": "0"}
]
"hasNext": true
}
```

The second _incremental stream update result_ contains the final stream results.
In this example, the underlying iterator does not close synchronously so
{"hasNext"} is set to {true}. If this iterator did close synchronously,
{"hasNext"} could be set to {false} and make this the final incremental stream
update result.

```json example
{
"incremental": [
{
"id": "1",
"items": [{ "title": "Return of the Jedi" }]
}
],
"hasNext": true
}
```

When the underlying iterator of the `films` field closes there is no more data
to deliver, so the third and final _incremental stream update result_ sets
{"hasNext"} to {false} to indicate the end of the _incremental stream_.

```json example
{
"hasNext": false
}
```

### Example 2 - A query containing overlapping defers

```graphql example
query {
person(id: "cGVvcGxlOjE=") {
...HomeWorldFragment @defer(label: "homeWorldDefer")
...NameAndHomeWorldFragment @defer(label: "nameAndWorld")
firstName
}
}
fragment HomeWorldFragment on Person {
homeWorld {
name
terrain
}
}

fragment NameAndHomeWorldFragment on Person {
firstName
lastName
homeWorld {
name
}
}
```

In this example the response is an _incremental stream_ of the following
results.

The _initial incremental stream result_ contains the results of the `firstName`
field. Even though it is also present in the `HomeWorldFragment`, it must be
returned in the initial incremental stream result because it is also defined
outside of any fragments with the `@defer` directive. Additionally, there are
two _incremental pending notices_ indicating that results for both `@defer`s in
the query will be delivered in later _incremental stream update result_.

```json example
{
"data": {
"person": {
"firstName": "Luke"
}
},
"pending": [
{ "id": "0", "path": ["person"], "label": "homeWorldDefer" },
{ "id": "1", "path": ["person"], "label": "nameAndWorld" }
],
"hasNext": true
}
```

In this example, the first _incremental stream update result_ contains the
deferred data from `HomeWorldFragment`. There is one _incremental completion
notice_, indicating that `HomeWorldFragment` has been completely delivered.
Because the `homeWorld` field is present in two separate `@defer`s, it is
separated into its own _incremental result_. In this example, this incremental
result contains the id `"0"`, but since the `name` field was included in both
`HomeWorldFragment` and `NameAndHomeWorldFragment`, an id of `"1"` would also be
a valid response.

The second _incremental result_ in this _incremental stream update result_
contains the data for the `terrain` field. This _incremental result_ contains a
{"subPath"} entry to indicate to clients that the _response position_ of this
result can be determined by concatenating: the path from the _incremental
pending notice_ for id `"0"`, and the value of this {"subPath"} entry.

```json example
{
"incremental": [
{
"id": "0",
"data": { "homeWorld": { "name": "Tatooine" } }
},
{
"id": "0",
"subPath": ["homeWorld"],
"data": { "terrain": "desert" }
}
],
"completed": [{ "id": "0" }],
"hasNext": true
}
```

The second _incremental stream update result_ contains the remaining data from
the `NameAndHomeWorldFragment`. `lastName` is the only remaining field from this
selection that has not been delivered in a previous result. With this field now
delivered, clients are informed that the `NameAndHomeWorldFragment` has been
completed by the presence of the associated _incremental completion notice_.
Additionally, {"hasNext"} is set to {false} indicating the end of the
_incremental stream_.

This example demonstrates that it is necessary for clients to process the entire
incremental stream, as both the initial data and previous incremental results
(with a potentially different value for {"id"}) may be required to complete a
deferred fragment.

```json example
{
"incremental": [
{
"id": "1",
"data": { "lastName": "Skywalker" }
}
],
"completed": [{ "id": "1" }],
"hasNext": false
}
```
2 changes: 2 additions & 0 deletions spec/GraphQL.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,6 @@ working draft release can be found at

# [Appendix: Specified Definitions](Appendix%20D%20--%20Specified%20Definitions.md)

# [Appendix: Examples](Appendix%20E%20--%20Examples.md)

# [Appendix: Licensing](../LICENSE.md)
131 changes: 129 additions & 2 deletions spec/Section 3 -- Type System.md
Original file line number Diff line number Diff line change
Expand Up @@ -812,8 +812,8 @@ And will yield the subset of each object type queried:
When querying an Object, the resulting mapping of fields are conceptually
ordered in the same order in which they were encountered during execution,
excluding fragments for which the type does not apply and fields or fragments
that are skipped via `@skip` or `@include` directives. This ordering is
correctly produced when using the {CollectFields()} algorithm.
that are skipped via `@skip` or `@include` directives or postponed via `@defer`.
This ordering is correctly produced when using the {CollectFields()} algorithm.

Response serialization formats capable of representing ordered maps should
maintain this ordering. Serialization formats which can only represent unordered
Expand Down Expand Up @@ -2084,6 +2084,15 @@ GraphQL implementations that support the type system definition language must
provide the `@deprecated` directive if representing deprecated portions of the
schema.

GraphQL implementations may provide the `@defer` and/or `@stream` directives. If
either or both of these directives are provided, they must conform to the
requirements defined in this specification.

Note: The [Directives Are Defined](#sec-Directives-Are-Defined) validation rule
ensures that GraphQL operations can only include directives available on the
schema; thus operations including `@defer` or `@stream` directives can only be
executed by a GraphQL service that supports them.

GraphQL implementations that support the type system definition language should
provide the `@specifiedBy` directive if representing custom scalar definitions.

Expand Down Expand Up @@ -2321,3 +2330,121 @@ input UserUniqueCondition @oneOf {
organizationAndEmail: OrganizationAndEmailInput
}
```

### @defer

```graphql
directive @defer(
if: Boolean! = true
label: String
) on FRAGMENT_SPREAD | INLINE_FRAGMENT
```

The `@defer` directive may be provided on a fragment spread or inline fragment
to indicate that execution of the related selection set should be deferred. When
a request includes the `@defer` directive, it may return an _incremental stream_
consisting of an _initial incremental stream result_ containing all non-deferred
data, followed by one or more _incremental stream update result_ including
deferred data.

The `@include` and `@skip` directives take precedence over `@defer`.

```graphql example
query myQuery($shouldDefer: Boolean! = true) {
user {
name
...someFragment @defer(if: $shouldDefer, label: "someLabel")
}
}
fragment someFragment on User {
id
profile_picture {
uri
}
}
```

#### @defer Arguments

- `if: Boolean! = true` - When `true`, fragment _should_ be deferred (see
[Client Handling of `@defer`/`@stream`](#sec-Client-handling-of-defer-stream)).
When `false`, fragment must not be deferred. Defaults to `true`.
- `label: String` - An optional string literal used by GraphQL clients to
identify data in the _incremental stream_ and associate it with the
corresponding defer directive. If provided, the GraphQL service must include
this label in the corresponding _incremental pending notice_ within the
_incremental stream_. The `label` argument must be unique across all `@defer`
and `@stream` directives in the document. Variables are disallowed (via
[Defer And Stream Directive Labels Are Unique](#sec-Defer-And-Stream-Directive-Labels-Are-Unique))
because their values may not be known during validation.

### @stream

```graphql
directive @stream(
if: Boolean! = true
label: String
initialCount: Int! = 0
) on FIELD
```

The `@stream` directive may be provided for a field whose type incorporates a
`List` type modifier. The directive enables returning a partial list initially,
followed by additional items in one or more _incremental stream update result_.
If the field type incorporates multiple `List` type modifiers, only the
outermost list is streamed.

Note: The mechanism through which items are streamed is implementation-defined
and may use technologies such as asynchronous iterators.

The `@include` and `@skip` directives take precedence over `@stream`.

```graphql example
query myQuery($shouldStream: Boolean! = true) {
user {
friends(first: 10)
@stream(if: $shouldStream, label: "friendsStream", initialCount: 5) {
name
}
}
}
```

#### @stream Arguments

- `if: Boolean! = true` - When `true`, field _should_ be streamed (see
[Client Handling of `@defer`/`@stream`](#sec-Client-handling-of-defer-stream)).
When `false`, the field must behave as if the `@stream` directive is not
present—it must not be streamed and all of the list items must be included.
Defaults to `true`.
- `label: String` - An optional string literal used by GraphQL clients to
identify data in the _incremental stream_ and associate it with the
corresponding stream directive. If provided, the GraphQL service must include
this label in the corresponding _incremental pending notice_ within the
_incremental stream_. The `label` argument must be unique across all `@defer`
and `@stream` directives in the document. Variables are disallowed (via
[Defer And Stream Directive Labels Are Unique](#sec-Defer-And-Stream-Directive-Labels-Are-Unique))
because their values may not be known during validation.
- `initialCount: Int! = 0` - The number of list items to include initially when
completing the parent selection set. If omitted, defaults to `0`. An execution
error will be raised if the value of this argument is less than `0`. When the
size of the list is greater than or equal to the value of `initialCount`, the
GraphQL service _must_ initially include at least as many list items as the
value of `initialCount` (see
[Client Handling of `@defer`/`@stream`](#sec-Client-handling-of-defer-stream)).

### Client Handling of @defer/@stream

The ability to defer and/or stream data can have a potentially significant
impact on application performance. Developers generally need clear, predictable
control over their application's performance. It is highly recommended that
GraphQL services honor the `@defer` and `@stream` directives on each execution.
However, the specification allows advanced use cases where the service can
determine that it is more performant to not defer and/or stream. Services can
make this determination on case by case basis; e.g. in a single operation, one
or more `@defer` and/or `@stream` may be acted upon while others are ignored.
Therefore, GraphQL clients _must_ be able to process a _response_ that ignores
individual `@defer` and/or `@stream` directives. This also applies to the
`initialCount` argument on the `@stream` directive. Clients must be able to
process a streamed field result that contains more initial list items than were
specified in the `initialCount` argument.
Loading
Loading