diff --git a/spec/Appendix E -- Examples.md b/spec/Appendix E -- Examples.md new file mode 100644 index 000000000..618c95109 --- /dev/null +++ b/spec/Appendix E -- Examples.md @@ -0,0 +1,191 @@ +# 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 _incremental stream_ might look like: + +The _initial execution result_ does not contain any deferred or streamed results +in the {"data"} entry. The initial execution result contains a {"hasNext"} +entry, indicating that _execution update result_ will be delivered. There are +two _pending result_ indicating that results for both the `@defer` and `@stream` +in the query will be delivered in the execution update results. + +```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 +} +``` + +_Execution update result_ 1 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 +} +``` + +_Execution update result_ 2 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"} would be set to +{false} and this would be the final execution update result. + +```json example +{ + "incremental": [ + { + "id": "1", + "items": [{ "title": "Return of the Jedi" }] + } + ], + "hasNext": true +} +``` + +_Execution update result_ 3 contains no incremental data. {"hasNext"} set to +{false} indicates the end of the _incremental 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 _incremental stream_ might look like: + +The _initial execution result_ contains the results of the `firstName` field. +Even though it is also present in the `HomeWorldFragment`, it must be returned +in the initial response because it is also defined outside of any fragments with +the `@defer` directive. Additionally, there are two _pending result_ indicating +that results for both `@defer`s in the query will be delivered in the execution +update results. + +```json example +{ + "data": { + "person": { + "firstName": "Luke" + } + }, + "pending": [ + { "id": "0", "path": ["person"], "label": "homeWorldDefer" }, + { "id": "1", "path": ["person"], "label": "nameAndWorld" } + ], + "hasNext": true +} +``` + +_Execution update result_ 1 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"} entry 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 +} +``` + +_Execution update result_ 2 contains the remaining data from the +`NameAndHomeWorldFragment`. `lastName` is the only remaining field that has not +been delivered in a previous response. + +```json example +{ + "incremental": [ + { + "id": "1", + "data": { "lastName": "Skywalker" } + } + ], + "completed": [{ "id": "1" }], + "hasNext": false +} +``` diff --git a/spec/GraphQL.md b/spec/GraphQL.md index 7ac0717ac..0dbdfcdb0 100644 --- a/spec/GraphQL.md +++ b/spec/GraphQL.md @@ -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) diff --git a/spec/Section 7 -- Response.md b/spec/Section 7 -- Response.md index 4ece8639d..6668f932b 100644 --- a/spec/Section 7 -- Response.md +++ b/spec/Section 7 -- Response.md @@ -10,7 +10,8 @@ the case that any _execution error_ was raised and replaced with {null}. ## Response Format :: A GraphQL request returns a _response_. A _response_ is either an _execution -result_, a _response stream_, or a _request error result_. +result_, a _response stream_, an _incremental stream_, or a _request error +result_. ### Execution Result @@ -43,6 +44,14 @@ value of this entry is described in the "Extensions" section. subscription and the request included execution. A response stream must be a stream of _execution result_. +### Incremental Stream + +:: A GraphQL request returns an _incremental stream_ when the GraphQL service +has deferred or streamed data as a result of the `@defer` or `@stream` +directives. When the result of the GraphQL operation is an incremental stream, +the first value will be an _initial execution result_, followed by one or more +_execution update result_. + ### Request Error Result :: A GraphQL request returns a _request error result_ when one or more _request @@ -70,6 +79,59 @@ The _request error result_ map must not contain an entry with key {"data"}. The _request error result_ map may also contain an entry with key `extensions`. The value of this entry is described in the "Extensions" section. +### Initial Execution Result + +:: An _initial execution result_ is the first value yielded by an _incremental +stream_. + +An _initial execution result_ must be a map. + +The _initial execution result_ must contain an entry with key {"data"}, and may +contain entries with keys {"errors"} and {"extensions"}. The value of these +entries are defined in the same way as an _execution result_ as described in the +"Data", "Errors", and "Extensions" sections below. + +The _initial execution result_ must contain an entry with the key {"hasNext"}. +The value of this entry must be {true}. + +The _initial execution result_ may contain an entry with the key {"pending"}. +The value of this entry must be a non-empty list of _pending result_. Each +_pending result_ must be a map as described in the "Pending Result" section +below. + +The _initial execution result_ may contain an entry with they key +{"incremental"}. The value of this entry must be a non-empty list of +_incremental result_. Each _incremental result_ must be a map as described in +the "Incremental Result" section below. + +The _initial execution result_ may contain an entry with they key {"completed"}. +The value of this entry must be a non-empty list of _completed result_. Each +_completed result_ must be a map as described in the "Completed Result" section +below. + +### Execution Update Result + +:: An _execution update result_ is the value yielded by an _incremental stream_ +for all values except the first. + +An _execution update result_ must be a map. + +Unlike the _initial execution result_, an _execution update result_ must not +contain entries with keys {"data"} or {"errors"}. + +An _execution update result_ may contain an entry with the key {"extensions"}. +The value of this entry is described in the "Extensions" section. + +An _execution update result_ must contain an entry with the key {"hasNext"}. The +value of this entry must be {true} for all but the last response in the +_incremental stream_. The value of this entry must be {false} for the last +response of the incremental stream. + +The _initial execution result_ may contain entries with keys {"pending"}, +{"incremental"}, and/or {"completed"}. The value of these entries are defined in +the same way as an _initial execution result_ as described in the "Pending +Result", "Incremental Result", and "Completed Result" sections below. + ### Response Position @@ -94,6 +156,9 @@ represents a path in the response, not in the request. When a _response path_ is present on an _error result_, it identifies the _response position_ which raised the error. +When a _response path_ is present on an _incremental result_, it identifies the +_response position_ of the incremental data update. + A single field execution may result in multiple response positions. For example, ```graphql example @@ -323,15 +388,171 @@ discouraged. ### Extensions -The {"extensions"} entry in an _execution result_ or _request error result_, if -set, must have a map as its value. This entry is reserved for implementers to -extend the protocol however they see fit, and hence there are no additional -restrictions on its contents. +The {"extensions"} entry in an _execution result_, _request error result_, +_initial execution result_, or an _execution update result_, if set, must have a +map as its value. This entry is reserved for implementers to extend the protocol +however they see fit, and hence there are no additional restrictions on its +contents. + +### Pending Result + +:: A _pending result_ is used to communicate to clients that the GraphQL service +has chosen to incrementally deliver data associated with a `@defer` or `@stream` +directive. Each pending result corresponds to a specific `@defer` or `@stream` +directive located at a _response position_ in the response data. The presence of +a pending result indicates that clients should expect the associated data in +either the current response, or one of the following responses. + +**Pending Result Format** + +A _pending result_ must be a map. + +Every _pending result_ must contain an entry with the key {"id"} with a string +value. This {"id"} should be used by clients to correlate pending results with +_incremental result_ and _completed result_. The {"id"} value must be unique for +the entire _incremental stream_ response. There must not be any other pending +result in the _incremental stream_ that contains the same {"id"}. + +Every _pending result_ must contain an entry with the key {"path"}. When the +pending result is associated with a `@stream` directive, it indicates the list +at this _response position_ is not known to be complete. Clients should expect +the GraphQL Service to incrementally deliver the remainder list items of this +list. When the pending result is associated with a `@defer` directive, it +indicates that the response fields contained in the deferred fragment are not +known to be complete. Clients should expect the GraphQL Service to incrementally +deliver the remainder of the fields contained in the deferred fragment at this +_response position_. + +If the associated `@defer` or `@stream` directive contains a `label` argument, +the pending result must contain an entry {"label"} with the value of this +argument. Clients should use this entry to differentiate the _pending results_ +for different deferred fragments at the same _response position_. + +If a pending result is not returned for a `@defer` or `@stream` directive, +clients must assume that the GraphQL service chose not to incrementally deliver +this data, and the data can be found either in the {"data"} entry in the +_initial execution result_, or one of the prior _execution update result_ in the +_incremental stream_. + +:: The _associated pending result_ is a specific _pending result_ associated +with any given _incremental result_ or _completed result_. The associated +pending result can be determined by finding the pending result where the value +of its {"id"} entry is the same value of the {"id"} entry of the given +incremental result or completed result. The associated pending result must +appear in the _incremental stream_, in the same or prior _initial execution +result_ or _execution update result_ as the given incremental result or +completed result. + +### Incremental Result + +:: The _incremental result_ is used to deliver data that the GraphQL service has +chosen to incrementally deliver. An incremental result may be either an +_incremental list result_ or an _incremental object result_. + +An _incremental result_ must be a map. + +Every _incremental result_ must contain an entry with the key {"id"} with a +string value. The definition of _associated pending result_ describes how this +value is used to determine the associated pending result for a given +_incremental result_. + +#### Incremental List Result + +:: An _incremental list result_ is an _incremental result_ used to deliver +additional list items for a list field with a `@stream` directive. The +_associated pending result_ for this _incremental list result_ must be +associated with a `@stream` directive. + +The _response position_ for an _incremental list result_ is the {"path"} entry +from its _associated pending result_. + +**Incremental List Result Format** + +Every _incremental list result_ must contain an entry with the key {"id"}, used +to determine the _associated pending result_ for this _incremental result_. + +Every _incremental list result_ must contain an {"items"} entry. The {"items"} +entry must contain a list of additional list items for the list field in the +incremental list result's _response position_. The value of this entry must be a +list of the same type of the response field at this _response position_. + +If any _execution error_ were raised during the execution of the results in +{"items"} and these errors propagate to a _response position_ higher than the +_incremental list result_'s response position, the incremental list result is +considered failed and should not be included in the _incremental stream_. The +errors that caused this failure will be included in a _completed result_. + +If any _execution error_ were raised during the execution of the results in +{"items"} and these errors did not propagate to a path higher than the +_incremental list result_'s path, the incremental list result must contain an +entry with key {"errors"} containing these execution errors. The value of this +entry is described in the "Errors" section. + +#### Incremental Object Result + +:: An _incremental object result_ is an _incremental result_ used to deliver +additional response fields that were contained in one or more fragments with a +`@defer` directive. The _associated pending result_ for this _incremental object +result_ must be associated with a `@defer` directive. + +**Incremental Object Result Format** + +The _incremental object result_ may contain a {"subPath"} entry. If this entry +is present, the incremental object result's _response position_ can be +determined by concatenating the value of the _associated pending result_'s +{"path"} entry with the value of this {"subPath"} entry. If no {"subPath"} entry +is present, the _response position_ is the value of the associated pending +result's {"path"} entry. + +An _incremental object result_ may be used to deliver data for response fields +that were contained in more than one deferred fragments. In that case, the +_associated pending result_ of the incremental object result must be a _pending +result_ with the longest {"path"}. + +Every _incremental object result_ must contain a {"data"} entry. The {"data"} +entry must contain a map of additional response fields. The {"data"} entry in an +incremental object result will be of the type of the field at the incremental +object result's _response position_. + +If any _execution error_ were raised during the execution of the results in +{"data"} and these errors propagated to a _response position_ higher than the +_incremental object result_'s response position, the incremental object result +is considered failed and should not be included in the incremental stream. The +errors that caused this failure will be included in a _completed result_. + +If any _execution error_ were raised during the execution of the results in +{"data"} and these errors did not propagate to a _response position_ higher than +the _incremental object result_'s response position, the incremental object +result must contain an entry with key {"errors"} containing these execution +errors. The value of this entry is described in the "Errors" section. + +### Completed Result + +:: A _completed result_ is used to communicate that the GraphQL service has +completed the incremental delivery of the data associated with the _associated +pending result_. The corresponding data must have been completed in the same +_initial execution result_ or _execution update result_ in which this completed +result appears. + +**Completed Result Format** + +A _completed result_ must be a map. + +Every _completed result_ must contain an entry with the key {"id"} with a string +value. The definition of _associated pending result_ describes how this value is +used to determine the associated pending result for a given _completed result_. + +A _completed result_ may contain an {"errors"} entry. When the {"errors"} entry +is present, it informs clients that the delivery of the data from the +_associated pending result_ has failed, due to an execution error propagating to +a _response position_ higher than the _incremental result_'s response position. +The {"errors"} entry must contain these execution errors. The value of this +entry is described in the "Errors" section. ### Additional Entries To ensure future changes to the protocol do not break existing services and -clients, the _execution result_ and _request error result_ maps must not contain +clients, any of the maps described in the "Response" section must not contain any entries other than those described above. Clients must ignore any entries other than those described above.