Skip to content

Latest commit

 

History

History
219 lines (193 loc) · 6.11 KB

File metadata and controls

219 lines (193 loc) · 6.11 KB

E. Appendix: Examples

Incremental Delivery Examples

Example 1 - A query containing both defer and stream

query {
  person(id: "cGVvcGxlOjE=") {
    ...HomeWorldFragment @defer(label: "homeWorldDefer")
    name
    films @stream(initialCount: 1, label: "filmsStream") {
      title
    }
  }
}
fragment HomeWorldFragment on Person {
  homeWorld {
    name
  }
}

The response to this request will be an incremental stream consisting of an initial incremental stream result followed by one or more incremental stream update result.

The initial incremental stream result has:

  • a {"data"} entry containing the results of the GraphQL operation except for the @defer and @stream selections;
  • a {"pending"} entry containing two incremental pending notices, one for the @defer selection and for the the @stream selection, indicating that these results will be delivered in a later incremental stream update result;
  • a {"hasNext"} entry with the value {true}, indicating that the response is not yet complete.

If an error were to occur, it would also have an {"errors"} entry; but not in this 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
}

Depending on the behavior of the backend and the time at which the deferred and streamed resources resolve, the stream may produce results in different orders. In this example, our first incremental stream update result contains the deferred data and the first streamed list item. There is one incremental completion notice, indicating that the deferred data has been completely delivered.

{
  "incremental": [
    {
      "id": "0",
      "data": { "homeWorld": { "name": "Tatooine" } }
    },
    {
      "id": "1",
      "items": [{ "title": "The Empire Strikes Back" }]
    }
  ],
  "completed": [
    {"id": "0"}
  ]
  "hasNext": true
}

The second incremental stream update result 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"} could be set to {false} and make this the final incremental stream update result.

{
  "incremental": [
    {
      "id": "1",
      "items": [{ "title": "Return of the Jedi" }]
    }
  ],
  "hasNext": true
}

When the underlying iterator of the films field closes there is no more data to deliver, so the third and final incremental stream update result sets {"hasNext"} to {false} to indicate the end of the incremental stream.

{
  "hasNext": false
}

Example 2 - A query containing overlapping defers

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
  }
}

In this example the response is an incremental stream of the following results.

The initial incremental stream result contains the results of the firstName field. Even though it is also present in the HomeWorldFragment, it must be returned in the initial incremental stream result because it is also defined outside of any fragments with the @defer directive. Additionally, there are two incremental pending notices indicating that results for both @defers in the query will be delivered in later incremental stream update result.

{
  "data": {
    "person": {
      "firstName": "Luke"
    }
  },
  "pending": [
    { "id": "0", "path": ["person"], "label": "homeWorldDefer" },
    { "id": "1", "path": ["person"], "label": "nameAndWorld" }
  ],
  "hasNext": true
}

In this example, the first incremental stream update result contains the deferred data from HomeWorldFragment. There is one incremental completion notice, indicating that HomeWorldFragment has been completely delivered. Because the homeWorld field is present in two separate @defers, it is separated into its own incremental result. In this example, this incremental result contains the id "0", but since the name field was included in both HomeWorldFragment and NameAndHomeWorldFragment, an id of "1" would also be a valid response.

The second incremental result in this incremental stream update result contains the data for the terrain field. This incremental result contains a {"subPath"} entry to indicate to clients that the response position of this result can be determined by concatenating: the path from the incremental pending notice for id "0", and the value of this {"subPath"} entry.

{
  "incremental": [
    {
      "id": "0",
      "data": { "homeWorld": { "name": "Tatooine" } }
    },
    {
      "id": "0",
      "subPath": ["homeWorld"],
      "data": { "terrain": "desert" }
    }
  ],
  "completed": [{ "id": "0" }],
  "hasNext": true
}

The second incremental stream update result contains the remaining data from the NameAndHomeWorldFragment. lastName is the only remaining field from this selection that has not been delivered in a previous result. With this field now delivered, clients are informed that the NameAndHomeWorldFragment has been completed by the presence of the associated incremental completion notice. Additionally, {"hasNext"} is set to {false} indicating the end of the incremental stream.

This example demonstrates that it is necessary for clients to process the entire incremental stream, as both the initial data and previous incremental results (with a potentially different value for {"id"}) may be required to complete a deferred fragment.

{
  "incremental": [
    {
      "id": "1",
      "data": { "lastName": "Skywalker" }
    }
  ],
  "completed": [{ "id": "1" }],
  "hasNext": false
}