Skip to content

Commit

Permalink
Introduce @defer.
Browse files Browse the repository at this point in the history
Authored-by: Rob Richard <rob@1stdibs.com>
Co-authored-by: Benjie Gillam <benjie@jemjie.com>
Co-authored-by: Yaacov Rydzinski <yaacovCR@gmail.com>
  • Loading branch information
yaacovCR and benjie committed Dec 7, 2023
1 parent 52fd063 commit edf9158
Show file tree
Hide file tree
Showing 4 changed files with 858 additions and 218 deletions.
83 changes: 64 additions & 19 deletions spec/Section 3 -- Type System.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{AnalyzeSelectionSet()} algorithm.

Response serialization formats capable of representing ordered maps should
maintain this ordering. Serialization formats which can only represent unordered
Expand Down Expand Up @@ -1941,10 +1942,10 @@ by a validator, executor, or client tool such as a code generator.

GraphQL implementations should provide the `@skip` and `@include` directives.

GraphQL implementations are not required to implement the `@stream` directive.
If the directive is implemented, it must be implemented according to this
specification. GraphQL implementations that do not support the `@stream`
directive must not make it available via introspection.
GraphQL implementations 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 implementations that do not
support these directives must not make them available via introspection.

GraphQL implementations that support the type system definition language must
provide the `@deprecated` directive if representing deprecated portions of the
Expand Down Expand Up @@ -2167,6 +2168,50 @@ to the relevant IETF specification.
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
non-deferred data is delivered in the initial response and data deferred is
delivered in a subsequent response. `@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](#note-088b7)). 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 payload.
`label` must be unique label across all `@defer` and `@stream` directives in a
document. `label` must not be provided as a variable.

### @stream

```graphql
Expand Down Expand Up @@ -2201,20 +2246,20 @@ query myQuery($shouldStream: Boolean) {
- `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 payload.
`label` must be unique label across all `@stream` directives in a document.
`label` must not be provided as a variable.
`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 stream parts of a response 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 `@stream` directives on each
execution. However, the specification allows advanced use cases where the
service can determine that it is more performant to not stream. Therefore,
GraphQL clients _must_ be able to process a response that ignores the `@stream`
directive. 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.
Note: The ability to defer and/or stream parts of a response 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. 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.
82 changes: 49 additions & 33 deletions spec/Section 5 -- Validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ unambiguous. Therefore any two field selections which might both be encountered
for the same object are only valid if they are equivalent.

During execution, the simultaneous execution of fields with the same response
name is accomplished by {CollectSubfields()}.
name is accomplished by {AnalyzeSelectionSet()}.

For simple hand-written GraphQL, this rule is obviously a clear developer error,
however nested fragments can make this difficult to detect manually.
Expand Down Expand Up @@ -1528,33 +1528,35 @@ query ($foo: Boolean = true, $bar: Boolean = false) {
}
```

### Stream Directives Are Used On Valid Root Field
### Defer And Stream Directives Are Used On Valid Root Field

**Formal Specification**

- For every {directive} in a document.
- Let {directiveName} be the name of {directive}.
- Let {mutationType} be the root Mutation type in {schema}.
- Let {subscriptionType} be the root Subscription type in {schema}.
- If {directiveName} is "stream":
- If {directiveName} is "defer" or "stream":
- The parent type of {directive} must not be {mutationType} or
{subscriptionType}.

**Explanatory Text**

The stream directives is not allowed to be used on root fields of the mutation
or subscription type.
The defer and stream directives are not allowed to be used on root fields of the
mutation or subscription type.

For example, the following document will not pass validation because `@stream`
For example, the following document will not pass validation because `@defer`
has been used on a root mutation field:

```raw graphql counter-example
mutation {
mutationField @stream
... @defer {
mutationField
}
}
```

### Stream Directives Are Used On Valid Operations
### Defer And Stream Directives Are Used On Valid Operations

**Formal Specification**

Expand All @@ -1567,7 +1569,7 @@ mutation {
- Let {fragmentName} be the name of {fragment}.
- Add {fragmentName} to {subscriptionFragments}.
- For every {directive} in a document:
- If {directiveName} is not "stream":
- If {directiveName} is not "defer" or "stream":
- Continue to the next {directive}.
- Let {ancestor} be the ancestor operation or fragment definition of
{directive}.
Expand All @@ -1584,30 +1586,33 @@ mutation {

**Explanatory Text**

The stream directives can not be used to stream data in subscription operations.
If these directives appear in a subscription operation they must be disabled
using the "if" argument. This rule will not permit any stream directives on a
subscription operation that cannot be disabled using the "if" argument.
The defer and stream directives can not be used to defer or stream data in
subscription operations. If these directives appear in a subscription operation
they must be disabled using the "if" argument. This rule will not permit any
defer or stream directives on a subscription operation that cannot be disabled
using the "if" argument.

For example, the following document will not pass validation because `@stream`
For example, the following document will not pass validation because `@defer`
has been used in a subscription operation with no "if" argument defined:

```raw graphql counter-example
subscription sub {
newMessage @stream {
body
newMessage {
... @defer {
body
}
}
}
```

### Stream Directive Labels Are Unique
### Defer And Stream Directive Labels Are Unique

**Formal Specification**

- Let {labelValues} be an empty set.
- For every {directive} in the document:
- Let {directiveName} be the name of {directive}.
- If {directiveName} is "stream":
- If {directiveName} is "defer" or "stream":
- For every {argument} in {directive}:
- Let {argumentName} be the name of {argument}.
- Let {argumentValue} be the value passed to {argument}.
Expand All @@ -1618,40 +1623,51 @@ subscription sub {

**Explanatory Text**

The `@stream` directive accepts an argument "label". This label may be used by
GraphQL clients to uniquely identify response payloads. If a label is passed, it
must not be a variable and it must be unique within all other `@stream`
directives in the document.
The `@defer` and `@stream` directives each accept an argument "label". This
label may be used by GraphQL clients to uniquely identify response payloads. If
a label is passed, it must not be a variable and it must be unique within all
other `@defer` and `@stream` directives in the document.

For example the following document is valid:

```graphql example
{
pets @stream {
name
dog {
...fragmentOne
...fragmentTwo @defer(label: "dogDefer")
}
pets @stream(label: "petStream") {
owner {
name
}
name
}
}

fragment fragmentOne on Dog {
name
}

fragment fragmentTwo on Dog {
owner {
name
}
}
```

For example, the following document will not pass validation because the same
label is used in different `@stream` directives.:
label is used in different `@defer` and `@stream` directives.:

```raw graphql counter-example
{
pets @stream(label: "MyLabel") {
name
dog {
...fragmentOne @defer(label: "MyLabel")
}
pets @stream(label: "MyLabel") {
owner {
name
}
name
}
}
fragment fragmentOne on Dog {
name
}
```

### Stream Directives Are Used On List Fields
Expand Down
Loading

0 comments on commit edf9158

Please sign in to comment.