Skip to content

Commit d40130d

Browse files
committed
Add directives and validation sections (#5)
* Add defer and stream directives to type system * Add defer/stream validation rules
1 parent 89983ce commit d40130d

File tree

3 files changed

+284
-2
lines changed

3 files changed

+284
-2
lines changed

cspell.yml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ ignoreRegExpList:
44
- /[a-z]{2,}'s/
55
words:
66
# Terms of art
7+
- deprioritization
78
- endianness
89
- interoperation
910
- monospace

spec/Section 3 -- Type System.md

+104-2
Original file line numberDiff line numberDiff line change
@@ -794,8 +794,9 @@ And will yield the subset of each object type queried:
794794
When querying an Object, the resulting mapping of fields are conceptually
795795
ordered in the same order in which they were encountered during execution,
796796
excluding fragments for which the type does not apply and fields or fragments
797-
that are skipped via `@skip` or `@include` directives. This ordering is
798-
correctly produced when using the {CollectFields()} algorithm.
797+
that are skipped via `@skip` or `@include` directives or temporarily skipped via
798+
`@defer`. This ordering is correctly produced when using the {CollectFields()}
799+
algorithm.
799800

800801
Response serialization formats capable of representing ordered maps should
801802
maintain this ordering. Serialization formats which can only represent unordered
@@ -1942,6 +1943,11 @@ by a validator, executor, or client tool such as a code generator.
19421943

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

1946+
GraphQL implementations are not required to implement the `@defer` and `@stream`
1947+
directives. If either or both of these directives are implemented, they must be
1948+
implemented according to this specification. GraphQL implementations that do not
1949+
support these directives must not make them available via introspection.
1950+
19451951
GraphQL implementations that support the type system definition language must
19461952
provide the `@deprecated` directive if representing deprecated portions of the
19471953
schema.
@@ -2162,3 +2168,99 @@ to the relevant IETF specification.
21622168
```graphql example
21632169
scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122")
21642170
```
2171+
2172+
### @defer
2173+
2174+
```graphql
2175+
directive @defer(
2176+
label: String
2177+
if: Boolean! = true
2178+
) on FRAGMENT_SPREAD | INLINE_FRAGMENT
2179+
```
2180+
2181+
The `@defer` directive may be provided for fragment spreads and inline fragments
2182+
to inform the executor to delay the execution of the current fragment to
2183+
indicate deprioritization of the current fragment. A query with `@defer`
2184+
directive will cause the request to potentially return multiple responses, where
2185+
non-deferred data is delivered in the initial response and data deferred is
2186+
delivered in a subsequent response. `@include` and `@skip` take precedence over
2187+
`@defer`.
2188+
2189+
```graphql example
2190+
query myQuery($shouldDefer: Boolean) {
2191+
user {
2192+
name
2193+
...someFragment @defer(label: "someLabel", if: $shouldDefer)
2194+
}
2195+
}
2196+
fragment someFragment on User {
2197+
id
2198+
profile_picture {
2199+
uri
2200+
}
2201+
}
2202+
```
2203+
2204+
#### @defer Arguments
2205+
2206+
- `if: Boolean! = true` - When `true`, fragment _should_ be deferred (see
2207+
related note below). When `false`, fragment will not be deferred and data will
2208+
be included in the initial response. Defaults to `true` when omitted.
2209+
- `label: String` - May be used by GraphQL clients to identify the data from
2210+
responses and associate it with the corresponding defer directive. If
2211+
provided, the GraphQL service must add it to the corresponding pending object
2212+
in the response. `label` must be unique label across all `@defer` and
2213+
`@stream` directives in a document. `label` must not be provided as a
2214+
variable.
2215+
2216+
### @stream
2217+
2218+
```graphql
2219+
directive @stream(
2220+
label: String
2221+
if: Boolean! = true
2222+
initialCount: Int = 0
2223+
) on FIELD
2224+
```
2225+
2226+
The `@stream` directive may be provided for a field of `List` type so that the
2227+
backend can leverage technology such as asynchronous iterators to provide a
2228+
partial list in the initial response, and additional list items in subsequent
2229+
responses. `@include` and `@skip` take precedence over `@stream`.
2230+
2231+
```graphql example
2232+
query myQuery($shouldStream: Boolean) {
2233+
user {
2234+
friends(first: 10) {
2235+
nodes @stream(label: "friendsStream", initialCount: 5, if: $shouldStream)
2236+
}
2237+
}
2238+
}
2239+
```
2240+
2241+
#### @stream Arguments
2242+
2243+
- `if: Boolean! = true` - When `true`, field _should_ be streamed (see related
2244+
note below). When `false`, the field will not be streamed and all list items
2245+
will be included in the initial response. Defaults to `true` when omitted.
2246+
- `label: String` - May be used by GraphQL clients to identify the data from
2247+
responses and associate it with the corresponding stream directive. If
2248+
provided, the GraphQL service must add it to the corresponding pending object
2249+
in the response. `label` must be unique label across all `@defer` and
2250+
`@stream` directives in a document. `label` must not be provided as a
2251+
variable.
2252+
- `initialCount: Int` - The number of list items the service should return as
2253+
part of the initial response. If omitted, defaults to `0`. A field error will
2254+
be raised if the value of this argument is less than `0`.
2255+
2256+
Note: The ability to defer and/or stream parts of a response can have a
2257+
potentially significant impact on application performance. Developers generally
2258+
need clear, predictable control over their application's performance. It is
2259+
highly recommended that GraphQL services honor the `@defer` and `@stream`
2260+
directives on each execution. However, the specification allows advanced use
2261+
cases where the service can determine that it is more performant to not defer
2262+
and/or stream. Therefore, GraphQL clients _must_ be able to process a response
2263+
that ignores the `@defer` and/or `@stream` directives. This also applies to the
2264+
`initialCount` argument on the `@stream` directive. Clients _must_ be able to
2265+
process a streamed response that contains a different number of initial list
2266+
items than what was specified in the `initialCount` argument.

spec/Section 5 -- Validation.md

+179
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ FieldsInSetCanMerge(set):
422422
{set} including visiting fragments and inline fragments.
423423
- Given each pair of members {fieldA} and {fieldB} in {fieldsForName}:
424424
- {SameResponseShape(fieldA, fieldB)} must be true.
425+
- {SameStreamDirective(fieldA, fieldB)} must be true.
425426
- If the parent types of {fieldA} and {fieldB} are equal or if either is not
426427
an Object Type:
427428
- {fieldA} and {fieldB} must have identical field names.
@@ -456,6 +457,16 @@ SameResponseShape(fieldA, fieldB):
456457
- If {SameResponseShape(subfieldA, subfieldB)} is {false}, return {false}.
457458
- Return {true}.
458459

460+
SameStreamDirective(fieldA, fieldB):
461+
462+
- If neither {fieldA} nor {fieldB} has a directive named `stream`.
463+
- Return {true}.
464+
- If both {fieldA} and {fieldB} have a directive named `stream`.
465+
- Let {streamA} be the directive named `stream` on {fieldA}.
466+
- Let {streamB} be the directive named `stream` on {fieldB}.
467+
- If {streamA} and {streamB} have identical sets of arguments, return {true}.
468+
- Return {false}.
469+
459470
Note: In prior versions of the spec the term "composite" was used to signal a
460471
type that is either an Object, Interface or Union type.
461472

@@ -1521,6 +1532,174 @@ query ($foo: Boolean = true, $bar: Boolean = false) {
15211532
}
15221533
```
15231534

1535+
### Defer And Stream Directives Are Used On Valid Root Field
1536+
1537+
** Formal Specification **
1538+
1539+
- For every {directive} in a document.
1540+
- Let {directiveName} be the name of {directive}.
1541+
- Let {mutationType} be the root Mutation type in {schema}.
1542+
- Let {subscriptionType} be the root Subscription type in {schema}.
1543+
- If {directiveName} is "defer" or "stream":
1544+
- The parent type of {directive} must not be {mutationType} or
1545+
{subscriptionType}.
1546+
1547+
**Explanatory Text**
1548+
1549+
The defer and stream directives are not allowed to be used on root fields of the
1550+
mutation or subscription type.
1551+
1552+
For example, the following document will not pass validation because `@defer`
1553+
has been used on a root mutation field:
1554+
1555+
```raw graphql counter-example
1556+
mutation {
1557+
... @defer {
1558+
mutationField
1559+
}
1560+
}
1561+
```
1562+
1563+
### Defer And Stream Directives Are Used On Valid Operations
1564+
1565+
** Formal Specification **
1566+
1567+
- Let {subscriptionFragments} be the empty set.
1568+
- For each {operation} in a document:
1569+
- If {operation} is a subscription operation:
1570+
- Let {fragments} be every fragment referenced by that {operation}
1571+
transitively.
1572+
- For each {fragment} in {fragments}:
1573+
- Let {fragmentName} be the name of {fragment}.
1574+
- Add {fragmentName} to {subscriptionFragments}.
1575+
- For every {directive} in a document:
1576+
- If {directiveName} is not "defer" or "stream":
1577+
- Continue to the next {directive}.
1578+
- Let {ancestor} be the ancestor operation or fragment definition of
1579+
{directive}.
1580+
- If {ancestor} is a fragment definition:
1581+
- If the fragment name of {ancestor} is not present in
1582+
{subscriptionFragments}:
1583+
- Continue to the next {directive}.
1584+
- If {ancestor} is not a subscription operation:
1585+
- Continue to the next {directive}.
1586+
- Let {if} be the argument named "if" on {directive}.
1587+
- {if} must be defined.
1588+
- Let {argumentValue} be the value passed to {if}.
1589+
- {argumentValue} must be a variable, or the boolean value "false".
1590+
1591+
**Explanatory Text**
1592+
1593+
The defer and stream directives can not be used to defer or stream data in
1594+
subscription operations. If these directives appear in a subscription operation
1595+
they must be disabled using the "if" argument. This rule will not permit any
1596+
defer or stream directives on a subscription operation that cannot be disabled
1597+
using the "if" argument.
1598+
1599+
For example, the following document will not pass validation because `@defer`
1600+
has been used in a subscription operation with no "if" argument defined:
1601+
1602+
```raw graphql counter-example
1603+
subscription sub {
1604+
newMessage {
1605+
... @defer {
1606+
body
1607+
}
1608+
}
1609+
}
1610+
```
1611+
1612+
### Defer And Stream Directive Labels Are Unique
1613+
1614+
** Formal Specification **
1615+
1616+
- Let {labelValues} be an empty set.
1617+
- For every {directive} in the document:
1618+
- Let {directiveName} be the name of {directive}.
1619+
- If {directiveName} is "defer" or "stream":
1620+
- For every {argument} in {directive}:
1621+
- Let {argumentName} be the name of {argument}.
1622+
- Let {argumentValue} be the value passed to {argument}.
1623+
- If {argumentName} is "label":
1624+
- {argumentValue} must not be a variable.
1625+
- {argumentValue} must not be present in {labelValues}.
1626+
- Append {argumentValue} to {labelValues}.
1627+
1628+
**Explanatory Text**
1629+
1630+
The `@defer` and `@stream` directives each accept an argument "label". This
1631+
label may be used by GraphQL clients to uniquely identify response payloads. If
1632+
a label is passed, it must not be a variable and it must be unique within all
1633+
other `@defer` and `@stream` directives in the document.
1634+
1635+
For example the following document is valid:
1636+
1637+
```graphql example
1638+
{
1639+
dog {
1640+
...fragmentOne
1641+
...fragmentTwo @defer(label: "dogDefer")
1642+
}
1643+
pets @stream(label: "petStream") {
1644+
name
1645+
}
1646+
}
1647+
1648+
fragment fragmentOne on Dog {
1649+
name
1650+
}
1651+
1652+
fragment fragmentTwo on Dog {
1653+
owner {
1654+
name
1655+
}
1656+
}
1657+
```
1658+
1659+
For example, the following document will not pass validation because the same
1660+
label is used in different `@defer` and `@stream` directives.:
1661+
1662+
```raw graphql counter-example
1663+
{
1664+
dog {
1665+
...fragmentOne @defer(label: "MyLabel")
1666+
}
1667+
pets @stream(label: "MyLabel") {
1668+
name
1669+
}
1670+
}
1671+
1672+
fragment fragmentOne on Dog {
1673+
name
1674+
}
1675+
```
1676+
1677+
### Stream Directives Are Used On List Fields
1678+
1679+
**Formal Specification**
1680+
1681+
- For every {directive} in a document.
1682+
- Let {directiveName} be the name of {directive}.
1683+
- If {directiveName} is "stream":
1684+
- Let {adjacent} be the AST node the directive affects.
1685+
- {adjacent} must be a List type.
1686+
1687+
**Explanatory Text**
1688+
1689+
GraphQL directive locations do not provide enough granularity to distinguish the
1690+
type of fields used in a GraphQL document. Since the stream directive is only
1691+
valid on list fields, an additional validation rule must be used to ensure it is
1692+
used correctly.
1693+
1694+
For example, the following document will only pass validation if `field` is
1695+
defined as a List type in the associated schema.
1696+
1697+
```graphql counter-example
1698+
query {
1699+
field @stream(initialCount: 0)
1700+
}
1701+
```
1702+
15241703
## Variables
15251704

15261705
### Variable Uniqueness

0 commit comments

Comments
 (0)