-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Spec edits for incremental delivery, Section 3 & 7 only #1124
Open
robrichard
wants to merge
4
commits into
incremental-integration
Choose a base branch
from
incremental-integration-response
base: incremental-integration
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+502
−13
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
# C. 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 stream might look like: | ||
|
||
Payload 1, the initial response does not contain any deferred or streamed | ||
results in the `data` entry. The initial response contains a `hasNext` entry, | ||
indicating that subsequent payloads will be delivered. There are two Pending | ||
Responses indicating that results for both the `@defer` and `@stream` in the | ||
query will be delivered in the subsequent payloads. | ||
|
||
```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 | ||
} | ||
``` | ||
|
||
Payload 2, contains the deferred data and the first streamed list item. There is | ||
one Completed Result, 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 | ||
} | ||
``` | ||
|
||
Payload 3, contains the final stream payload. In this example, the underlying | ||
iterator does not close synchronously so {hasNext} is set to {true}. If this | ||
iterator did close synchronously, {hasNext} would be set to {false} and this | ||
would be the final response. | ||
|
||
```json example | ||
{ | ||
"incremental": [ | ||
{ | ||
"id": "1", | ||
"items": [{ "title": "Return of the Jedi" }] | ||
} | ||
], | ||
"hasNext": true | ||
} | ||
``` | ||
|
||
Payload 4, contains no incremental data. {hasNext} set to {false} indicates the | ||
end of the response stream. This response is sent when the underlying iterator | ||
of the `films` field closes. | ||
|
||
```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 | ||
} | ||
} | ||
``` | ||
|
||
The response stream might look like: | ||
|
||
Payload 1, the initial response contains the results of the `firstName` field. | ||
Even though it is also present in the `HomeWorldFragment`, it must be returned | ||
in the initial payload because it is also defined outside of any fragments with | ||
the `@defer` directive. Additionally, There are two Pending Responses indicating | ||
that results for both `@defer`s in the query will be delivered in the subsequent | ||
payloads. | ||
|
||
```json example | ||
{ | ||
"data": { | ||
"person": { | ||
"firstName": "Luke" | ||
} | ||
}, | ||
"pending": [ | ||
{ "id": "0", "path": ["person"], "label": "homeWorldDefer" }, | ||
{ "id": "1", "path": ["person"], "label": "nameAndWorld" } | ||
], | ||
"hasNext": true | ||
} | ||
``` | ||
|
||
Payload 2, contains the deferred data from `HomeWorldFragment`. There is one | ||
Completed Result, 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. | ||
|
||
The second Incremental Result contains the data for the `terrain` field. This | ||
incremental result contains a `subPath` property to indicate to clients that the | ||
path of this result can be determined by concatenating the path from the Pending | ||
Result with id `"0"` and this `subPath` entry. | ||
|
||
```json example | ||
{ | ||
"incremental": [ | ||
{ | ||
"id": "0", | ||
"data": { "homeWorld": { "name": "Tatooine" } } | ||
}, | ||
{ | ||
"id": "0", | ||
"subPath": ["homeWorld"], | ||
"data": { "terrain": "desert" } | ||
} | ||
], | ||
"completed": [{ "id": "0" }], | ||
"hasNext": true | ||
} | ||
``` | ||
|
||
Payload 3, contains the remaining data from the `NameAndHomeWorldFragment`. | ||
`lastName` is the only remaining field that has not been delivered in a previous | ||
payload. | ||
|
||
```json example | ||
{ | ||
"incremental": [ | ||
{ | ||
"id": "1", | ||
"data": { "lastName": "Skywalker" } | ||
} | ||
], | ||
"completed": [{ "id": "1" }], | ||
"hasNext": false | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -794,8 +794,9 @@ 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 temporarily skipped 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 | ||
|
@@ -1946,6 +1947,14 @@ GraphQL implementations that support the type system definition language must | |
provide the `@deprecated` directive if representing deprecated portions of the | ||
schema. | ||
|
||
GraphQL services are not required to implement the `@defer` and `@stream` | ||
directives. If either or both of these directives are implemented, they must be | ||
implemented according to this specification. GraphQL services that do not | ||
support these directives must not make them available via introspection. The | ||
[Directives Are Defined](#sec-Directives-Are-Defined) validation rule will | ||
prevent GraphQL Operations containing the `@defer` or `@stream` directive from | ||
being executed by a GraphQL service that does not implement these directives. | ||
|
||
GraphQL implementations that support the type system definition language should | ||
provide the `@specifiedBy` directive if representing custom scalar definitions. | ||
|
||
|
@@ -2162,3 +2171,101 @@ to the relevant IETF specification. | |
```graphql example | ||
scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122") | ||
``` | ||
|
||
### @defer | ||
|
||
```graphql | ||
directive @defer( | ||
label: String | ||
if: Boolean! = true | ||
) on FRAGMENT_SPREAD | INLINE_FRAGMENT | ||
``` | ||
|
||
The `@defer` directive may be provided for fragment spreads and inline fragments | ||
to inform the executor to delay the execution of the current fragment to | ||
indicate deprioritization of the current fragment. A query with `@defer` | ||
directive will cause the request to potentially return multiple responses, where | ||
deferred data is delivered in subsequent responses. `@include` and `@skip` take | ||
precedence over `@defer`. | ||
|
||
```graphql example | ||
query myQuery($shouldDefer: Boolean) { | ||
user { | ||
name | ||
...someFragment @defer(label: "someLabel", if: $shouldDefer) | ||
} | ||
} | ||
fragment someFragment on User { | ||
id | ||
profile_picture { | ||
uri | ||
} | ||
} | ||
``` | ||
|
||
#### @defer Arguments | ||
|
||
- `if: Boolean! = true` - When `true`, fragment _should_ be deferred (see | ||
related note below). When `false`, fragment will not be deferred and data will | ||
be included in the initial response. Defaults to `true` when omitted. | ||
- `label: String` - May be used by GraphQL clients to identify the data from | ||
responses and associate it with the corresponding defer directive. If | ||
provided, the GraphQL service must add it to the corresponding pending object | ||
in the response. `label` must be unique label across all `@defer` and | ||
`@stream` directives in a document. `label` must not be provided as a | ||
variable. | ||
|
||
### @stream | ||
|
||
```graphql | ||
directive @stream( | ||
label: String | ||
if: Boolean! = true | ||
initialCount: Int = 0 | ||
) on FIELD | ||
``` | ||
|
||
The `@stream` directive may be provided for a field of `List` type so that the | ||
backend can leverage technology such as asynchronous iterators to provide a | ||
partial list in the initial response, and additional list items in subsequent | ||
responses. `@include` and `@skip` take precedence over `@stream`. The | ||
[Stream Directives Are Used On List Fields](#sec-Stream-Directives-Are-Used-On-List-Fields) | ||
validation rule is used to prevent the `@stream` directive from being applied to | ||
a field that is not a `List` type. | ||
|
||
```graphql example | ||
query myQuery($shouldStream: Boolean) { | ||
user { | ||
friends(first: 10) { | ||
nodes @stream(label: "friendsStream", initialCount: 5, if: $shouldStream) | ||
} | ||
} | ||
} | ||
``` | ||
|
||
#### @stream Arguments | ||
|
||
- `if: Boolean! = true` - When `true`, field _should_ be streamed (see related | ||
note below). When `false`, the field will not be streamed and all list items | ||
will be included in the initial response. Defaults to `true` when omitted. | ||
- `label: String` - May be used by GraphQL clients to identify the data from | ||
responses and associate it with the corresponding stream directive. If | ||
provided, the GraphQL service must add it to the corresponding pending object | ||
in the response. `label` must be unique label across all `@defer` and | ||
`@stream` directives in a document. `label` must not be provided as a | ||
variable. | ||
- `initialCount: Int` - The number of list items the service should return as | ||
part of the initial response. If omitted, defaults to `0`. A field error will | ||
be raised if the value of this argument is less than `0`. | ||
|
||
Note: The ability to defer and/or stream parts of a response can have a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Love this section!!! |
||
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. Therefore, GraphQL clients _must_ be able to process a response | ||
that ignores the `@defer` and/or `@stream` directives. This also applies to the | ||
`initialCount` argument on the `@stream` directive. Clients _must_ be able to | ||
process a streamed response that contains a different number of initial list | ||
items than what was specified in the `initialCount` argument. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nits: let's not mention "initial response" because for the case of @defer nested inside @stream. It's not always guaranteed to be in response #0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated!