Skip to content

Commit eab9204

Browse files
authored
Merge pull request #1 from robrichard/updates
hasNext, initialCount, and updates to Response section
2 parents 00a6556 + 1806812 commit eab9204

5 files changed

+103
-39
lines changed

rfcs/DeferStream.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Each subsequent payload will be an object with the following properties
6868
* `label`: The string that was passed to the label argument of the `@defer` or `@stream` directive that corresponds to this results.
6969
* `data`: The data that is being delivered incrementally.
7070
* `path`: a list of keys (with plural indexes) from the root of the response to the insertion point that informs the client how to patch a subsequent delta payload into the original payload.
71-
* `isFinal`: A boolean that is present and `false` when there are more payloads that will be sent for this operation.
71+
* `hasNext`: A boolean that is present and `true` when there are more payloads that will be sent for this operation. The last payload in a multi payload response should return `hasNext: false`. `hasNext` is not required for single-payload responses to preserve backwards compatibility.
7272
* `errors`: An array that will be present and contain any field errors that are produced while executing the deferred or streamed selection set.
7373
* `extensions`: For implementors to extend the protocol
7474

@@ -104,30 +104,31 @@ fragment GroupAdminFragment {
104104
// payload 1
105105
{
106106
data: {id: 1},
107-
isFinal: false
107+
hasNext: true
108108
}
109109
110110
// payload 2
111111
{
112112
label: "friendStream"
113113
path: [“viewer”, “friends”, 1],
114114
data: {id: 4},
115-
isFinal: false
115+
hasNext: true
116116
}
117117
118118
// payload 3
119119
{
120120
label: "friendStream"
121121
path: [“viewer”, “friends”, 2],
122122
data: {id: 5},
123-
isFinal: false
123+
hasNext: true
124124
}
125125
126126
// payload 4
127127
{
128128
label: "groupAdminDefer",
129129
path: [“viewer”],
130130
data: {managed_groups: [{id: 1, id: 2}]}
131+
hasNext: false
131132
}
132133
```
133134

spec/Section 3 -- Type System.md

+12-7
Original file line numberDiff line numberDiff line change
@@ -1753,7 +1753,12 @@ A GraphQL schema describes directives which are used to annotate various parts
17531753
of a GraphQL document as an indicator that they should be evaluated differently
17541754
by a validator, executor, or client tool such as a code generator.
17551755

1756-
GraphQL implementations should provide the `@skip`, `@include`, `@defer` and `@stream` directives.
1756+
GraphQL implementations should provide the `@skip` and `@include` directives.
1757+
1758+
GraphQL implementations are not required to implement the `@defer` and `@stream`
1759+
directives. If they are implemented, they must be implemented according to the
1760+
specification. GraphQL implementations that do not support these directives must
1761+
not make them available via introspection.
17571762

17581763
GraphQL implementations that support the type system definition language must
17591764
provide the `@deprecated` directive if representing deprecated portions of
@@ -1924,14 +1929,14 @@ type ExampleType {
19241929

19251930
### @defer
19261931
```graphql
1927-
directive @defer(label: String!, if: Boolean) on FRAGMENT_SPREAD | INLINE_FRAGMENT
1932+
directive @defer(label: String, if: Boolean) on FRAGMENT_SPREAD | INLINE_FRAGMENT
19281933
```
19291934
The `@defer` directive may be provided for fragment spreads and inline fragments to
19301935
inform the executor to delay the execution of the current fragment to indicate
19311936
deprioritization of the current fragment. A query with `@defer` directive will cause
19321937
the request to potentially return multiple responses, where non-deferred data is
1933-
delivered in the initial response and data deferred delivered in a subsequent response.
1934-
`@include` and `@skip` take presedence over `@defer`.
1938+
delivered in the initial response and data deferred is delivered in a subsequent
1939+
response. `@include` and `@skip` take presedence over `@defer`.
19351940

19361941
```graphql example
19371942
query myQuery($shouldDefer: Boolean) {
@@ -1950,17 +1955,17 @@ fragment someFragment on User {
19501955

19511956
### @stream
19521957
```graphql
1953-
directive @stream(label: String!, initial_count: Int!, if: Boolean) on FIELD
1958+
directive @stream(label: String, initialCount: Int!, if: Boolean) on FIELD
19541959
```
19551960
The `@stream` directive may be provided for a field of `List` type so that the
1956-
backend can leverage technology such asynchronous iterators to provide a partial
1961+
backend can leverage technology such as asynchronous iterators to provide a partial
19571962
list in the initial response, and additional list items in subsequent responses.
19581963
`@include` and `@skip` take presedence over `@stream`.
19591964
```graphql example
19601965
query myQuery($shouldDefer: Boolean) {
19611966
user {
19621967
friends(first: 10) {
1963-
nodes @stream(label: "friendsStream", initial_count: 5)
1968+
nodes @stream(label: "friendsStream", initialCount: 5)
19641969
}
19651970
}
19661971
}

spec/Section 5 -- Validation.md

+11
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ FieldsInSetCanMerge(set):
411411
{set} including visiting fragments and inline fragments.
412412
* Given each pair of members {fieldA} and {fieldB} in {fieldsForName}:
413413
* {SameResponseShape(fieldA, fieldB)} must be true.
414+
* {SameStreamDirective(fieldA, fieldB)} must be true.
414415
* If the parent types of {fieldA} and {fieldB} are equal or if either is not
415416
an Object Type:
416417
* {fieldA} and {fieldB} must have identical field names.
@@ -444,6 +445,16 @@ SameResponseShape(fieldA, fieldB):
444445
* If {SameResponseShape(subfieldA, subfieldB)} is false, return false.
445446
* Return true.
446447

448+
SameStreamDirective(fieldA, fieldB):
449+
450+
* If neither {fieldA} nor {fieldB} has a directive named `stream`.
451+
* Return true.
452+
* If both {fieldA} and {fieldB} have a directive named `stream`.
453+
* Let {streamA} be the directive named `stream` on {fieldA}.
454+
* Let {streamB} be the directive named `stream` on {fieldB}.
455+
* If {streamA} and {streamB} have identical sets of arguments, return true.
456+
* Return false.
457+
447458
**Explanatory Text**
448459

449460
If multiple field selections with the same response names are encountered

spec/Section 6 -- Execution.md

+24-18
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ request is determined by the result of executing this operation according to the
2626
"Executing Operations” section below.
2727

2828
ExecuteRequest(schema, document, operationName, variableValues, initialValue):
29-
Note: the execution assumes implementing language support coroutines.
29+
Note: the execution assumes implementing language supports coroutines.
3030
Alternatively, the socket can provide a write buffer pointer to allow {ExecuteRequest()}
3131
to directly write payloads into the buffer.
3232
* Let {operation} be the result of {GetOperation(document, operationName)}.
@@ -141,6 +141,10 @@ ExecuteQuery(query, schema, variableValues, initialValue):
141141
* For each {payload} in {subsequentPayloads}:
142142
* If {payload} is a Deferred Fragment Record:
143143
* Yield the value from calling {ResolveDeferredFragmentRecord(payload, variableValues, subsequentPayloads)}.
144+
* If {payload} is not the final payload in {subsequentPayloads}
145+
* Add an entry to {payload} named `hasNext` with the value {true}.
146+
* If {payload} is the final payload in {subsequentPayloads}
147+
* Add an entry to {payload} named `hasNext` with the value {false}.
144148
* If {payload} is a Stream Record:
145149
* Yield ResolveStreamRecord(payload, variableValues, subsequentPayloads).
146150

@@ -333,7 +337,7 @@ response map.
333337
ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues, subsequentPayloads, parentPath):
334338

335339
* If {subsequentPayloads} is not provided, initialize it to the empty set.
336-
* If {parentPath} is not provided, initialize it to an emtpy list.
340+
* If {parentPath} is not provided, initialize it to an empty list.
337341
* Let {groupedFieldSet} be the result of
338342
{CollectFields(objectType, objectValue, selectionSet, variableValues, subsequentPayloads, parentPath)}.
339343
* Initialize {resultMap} to an empty ordered map.
@@ -464,11 +468,13 @@ Before execution, the selection set is converted to a grouped field set by
464468
calling {CollectFields()}. Each entry in the grouped field set is a list of
465469
fields that share a response key (the alias if defined, otherwise the field
466470
name). This ensures all fields with the same response key included via
467-
referenced fragments are executed at the same time. A deferred seclection set's
468-
fields will not be included in the grouped field set. Rather, a record
469-
representing the deferred fragment and addition context will be stored in a
470-
list. The executor revisits and resume execution for the list of deferred
471-
fragment records after the initial execution finishes.
471+
referenced fragments are executed at the same time. A deferred selection
472+
set's fields will not be included in the grouped field set. Rather, a record
473+
representing the deferred fragment and additional context will be stored in a
474+
list. The executor revisits and resumes execution for the list of deferred
475+
fragment records after the initial execution is initiated. This deferred
476+
execution would ‘re-execute’ fields with the same response key that were
477+
present in the grouped field set.
472478

473479

474480
As an example, collecting the fields of this selection set would collect two
@@ -607,7 +613,7 @@ ExecuteField(objectType, objectValue, fieldType, fields, variableValues, subsequ
607613
* Let {fieldName} be the field name of {field}.
608614
* Let {argumentValues} be the result of {CoerceArgumentValues(objectType, field, variableValues)}
609615
* If {field} provides the directive `@stream`, let {streamDirective} be that directive.
610-
* Let {initialCount} be the value or variable provided to {streamDirective}'s {initial_count} argument.
616+
* Let {initialCount} be the value or variable provided to {streamDirective}'s {initialCount} argument.
611617
* Let {resolvedValue} be {ResolvedFieldGenerator(objectType, objectValue, fieldName, argumentValues, initialCount)}.
612618
* Let {result} be the result of calling {CompleteValue(fieldType, fields, resolvedValue, variableValues, subsequentPayloads, parentPath)}.
613619
* Append {fieldName} to the {path} field of every {subsequentPayloads}.
@@ -682,16 +688,16 @@ This is exposed via {ResolveFieldValue}, which produces a value for a given
682688
field on a type for a real value. In addition, {ResolveFieldGenerator} will be
683689
exposed to produce an iterator for a field with `List` return type.
684690
The internal system may optionally define a generator function. In the case
685-
where the generator is not defined, the GraphQL executor provide a default generator.
686-
For example, a trivial generator that yield the entire list upon the first iteration.
691+
where the generator is not defined, the GraphQL executor provides a default generator.
692+
For example, a trivial generator that yields the entire list upon the first iteration.
687693

688694
As an example, a {ResolveFieldValue} might accept the {objectType} `Person`, the {field}
689695
{"soulMate"}, and the {objectValue} representing John Lennon. It would be
690696
expected to yield the value representing Yoko Ono.
691697

692698
A {ResolveFieldGenerator} might accept the {objectType} `MusicBand`, the {field}
693699
{"members"}, and the {objectValue} representing Beatles. It would be expected to yield
694-
a iterator of values representing, John Lennon, Paul, McCartney, Ringo Starr and
700+
a iterator of values representing, John Lennon, Paul McCartney, Ringo Starr and
695701
George Harrison.
696702

697703
ResolveFieldValue(objectType, objectValue, fieldName, argumentValues):
@@ -701,7 +707,7 @@ ResolveFieldValue(objectType, objectValue, fieldName, argumentValues):
701707

702708
ResolveFieldGenerator(objectType, objectValue, fieldName, argumentValues, initialCount):
703709
* If {objectType} provide an internal function {generatorResolver} for
704-
generating partitially resolved valueof a list field named {fieldName}:
710+
generating partitially resolved value of a list field named {fieldName}:
705711
* Let {generatorResolver} be the internal function.
706712
* Return the iterator from calling {generatorResolver}, providing
707713
{objectValue}, {argumentValues} and {initialCount}.
@@ -712,7 +718,7 @@ Note: It is common for {resolver} to be asynchronous due to relying on reading
712718
an underlying database or networked service to produce a value. This
713719
necessitates the rest of a GraphQL executor to handle an asynchronous
714720
execution flow. In addition, a commom implementation of {generator} is to leverage
715-
asynchronos iterators or asynchronos generators provided by many programing languages.
721+
asynchronous iterators or asynchronous generators provided by many programming languages.
716722

717723
### Value Completion
718724

@@ -721,7 +727,7 @@ to the expected return type. If the return type is another Object type, then
721727
the field execution process continues recursively. In the case where a value
722728
returned for a list type field is an iterator due to `@stream` specified on the
723729
field, value completition iterates over the iterator until the number of items
724-
yield by the iterator satisfies `initial_count` specified on the `@stream` directive.
730+
yield by the iterator satisfies `initialCount` specified on the `@stream` directive.
725731
Unresolved items in the iterator will be stored in a stream record which the executor
726732
resumes to execute after the initial execution finishes.
727733

@@ -765,11 +771,11 @@ CompleteValue(fieldType, fields, result, variableValues, subsequentPayloads, par
765771
* If {result} is {null} (or another internal value similar to {null} such as
766772
{undefined} or {NaN}), return {null}.
767773
* If {fieldType} is a List type:
768-
* If {result} is a iterator:
769-
* Let {field} be thte first entry in {fields}.
774+
* If {result} is an iterator:
775+
* Let {field} be the first entry in {fields}.
770776
* Let {innerType} be the inner type of {fieldType}.
771777
* Let {streamDirective} be the `@stream` directived provided on {field}.
772-
* Let {initialCount} be the value or variable provided to {streamDirective}'s {initial_count} argument.
778+
* Let {initialCount} be the value or variable provided to {streamDirective}'s {initialCount} argument.
773779
* Let {label} be the value or variable provided to {streamDirective}'s {label} argument.
774780
* Let {resolvedItems} be an empty list
775781
* For each {members} in {result}:
@@ -780,7 +786,7 @@ CompleteValue(fieldType, fields, result, variableValues, subsequentPayloads, par
780786
* Let {streamRecord} be the result of calling {CreateStreamRecord(label, initialCount, result, remainingItems, initialCount, fields, innerType, parentPath)}
781787
* Append {streamRecord} to {subsequentPayloads}.
782788
* Let {result} be {initialItems}.
783-
* Exit For each loop.
789+
* Exit for each loop.
784790
* If {result} is not a collection of values, throw a field error.
785791
* Let {innerType} be the inner type of {fieldType}.
786792
* Return a list where each list item is the result of calling

spec/Section 7 -- Response.md

+51-10
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ request.
88
A response may contain both a partial response as well as encountered errors in
99
the case that a field error occurred on a field which was replaced with {null}.
1010

11+
A response to a GraphQL operation must be a map or an event stream of maps. The
12+
value of this map is described in the "Response Format" section.
1113

1214
## Response Format
1315

14-
A response to a GraphQL operation must be a map.
15-
1616
If the operation encountered any errors, the response map must contain an
1717
entry with key `errors`. The value of this entry is described in the "Errors"
1818
section. If the operation completed without encountering any errors, this entry
@@ -23,6 +23,24 @@ with key `data`. The value of this entry is described in the "Data" section. If
2323
the operation failed before execution, due to a syntax error, missing
2424
information, or validation error, this entry must not be present.
2525

26+
When the response of the GraphQL operation is an event stream, the first value
27+
will be the initial response. All subsequent values may contain `label` and
28+
`path` entries. These two entries are used by clients to identify the the
29+
`@defer` or `@stream` directive from the GraphQL operation that triggered this
30+
value to be returned by the event stream. The combination of these two entries
31+
must be unique across all values returned by the event stream.
32+
33+
If the response of the GraphQL operation is an event stream, each response map
34+
must contain an entry with key `hasNext`. The value of this entry is `true` for
35+
all but the last response in the stream. The value of this entry is `false` for
36+
the last response of the stream. This entry is not required for GraphQL
37+
operations that return a single response map.
38+
39+
The GraphQL server may determine there are no more values in the event stream
40+
after a previous value with `hasNext`: `true` has been emitted. In this case
41+
the last value in the event stream should be a map without `data`, `label`,
42+
and `path` entries, and a `hasNext` entry with a value of `false`.
43+
2644
The response map may also contain an entry with key `extensions`. This entry,
2745
if set, must have a map as its value. This entry is reserved for implementors
2846
to extend the protocol however they see fit, and hence there are no additional
@@ -43,6 +61,11 @@ requested operation. If the operation was a query, this output will be an
4361
object of the schema's query root type; if the operation was a mutation, this
4462
output will be an object of the schema's mutation root type.
4563

64+
If the result of the operation is an event stream, the `data` entry in
65+
subsequent values will be an object of the type of a particular field in the
66+
GraphQL result. The adjacent `path` field will contain the path segments of
67+
the field this data is associated with.
68+
4669
If an error was encountered before execution begins, the `data` entry should
4770
not be present in the result.
4871

@@ -82,14 +105,8 @@ associated syntax element.
82105
If an error can be associated to a particular field in the GraphQL result, it
83106
must contain an entry with the key `path` that details the path of the
84107
response field which experienced the error. This allows clients to identify
85-
whether a `null` result is intentional or caused by a runtime error.
86-
87-
This field should be a list of path segments starting at the root of the
88-
response and ending with the field associated with the error. Path segments
89-
that represent fields should be strings, and path segments that
90-
represent list indices should be 0-indexed integers. If the error happens
91-
in an aliased field, the path to the error should use the aliased name, since
92-
it represents a path in the response, not in the query.
108+
whether a `null` result is intentional or caused by a runtime error. The value
109+
of this field is described in the "Path" section.
93110

94111
For example, if fetching one of the friends' names fails in the following
95112
query:
@@ -220,6 +237,30 @@ still discouraged.
220237
```
221238

222239

240+
## Path
241+
242+
A `path` field allows for the association to a particular field in a GraphQL
243+
result. This field should be a list of path segments starting at the root of the
244+
response and ending with the field to be associated with. Path segments
245+
that represent fields should be strings, and path segments that
246+
represent list indices should be 0-indexed integers. If the path is associated to
247+
an aliased field, the path should use the aliased name, since it represents a path in the response, not in the query.
248+
249+
When the `path` field is present on a GraphQL response, it indicates that the
250+
`data` field is not the root query or mutation result, but is rather associated to
251+
a particular field in the root result.
252+
253+
When the `path` field is present on an "Error result", it indicates the response field which experienced the error.
254+
255+
## Label
256+
257+
If the response of the GraphQL operation is an event stream, subsequent values
258+
may contain a string field `label`. This `label` is the same label passed to
259+
the `@defer` or `@stream` directive that triggered this value. This allows
260+
clients to identify which `@defer` or `@stream` directive is associated with
261+
this value. `label` will not be present if the corresponding `@defer` or
262+
`@stream` directive is not passed a `label` argument.
263+
223264
## Serialization Format
224265

225266
GraphQL does not require a specific serialization format. However, clients

0 commit comments

Comments
 (0)