|
| 1 | +# A. Appendix: Persisted Documents |
| 2 | + |
| 3 | +This appendix defines an optional extension to the GraphQL-over-HTTP protocol |
| 4 | +that allows for the usage of "persisted documents". |
| 5 | + |
| 6 | +:: A _persisted document_ is a GraphQL document (strictly: an |
| 7 | +[`ExecutableDocument`](https://spec.graphql.org/draft/#ExecutableDocument)) that |
| 8 | +has been persisted such that the server may retrieve it based on an identifier |
| 9 | +indicated in the HTTP request. |
| 10 | + |
| 11 | +This feature can be used as an operation allow-list, as a way of improving the |
| 12 | +caching of GraphQL operations, or just as a way of reducing the bandwidth |
| 13 | +consumed from sending the full GraphQL Document to the server on each request. |
| 14 | + |
| 15 | +Typically, support for the _persisted document_ feature is implemented via a |
| 16 | +"middleware" that sits in front of the GraphQL service and transforms a |
| 17 | +_persisted document request_ into a _GraphQL-over-HTTP request_. |
| 18 | + |
| 19 | +:: A _persisted operation_ is a _persisted document_ which contains only one |
| 20 | +GraphQL operation and all the fragments this operation references (recursively). |
| 21 | + |
| 22 | +## Identifying a Document |
| 23 | + |
| 24 | +:: A _document identifier_ is a string-based identifier that uniquely identifies |
| 25 | +a GraphQL Document. |
| 26 | + |
| 27 | +Note: A _document identifier_ must be unique, otherwise there is a risk of |
| 28 | +responses confusing the client. Even if the selection sets are identical, even |
| 29 | +whitespace changes may change the location from which errors are raised, and |
| 30 | +thus should generate different document identifiers. |
| 31 | + |
| 32 | +A _document identifier_ must either be a _prefixed document identifier_ or a |
| 33 | +_custom document identifier_. |
| 34 | + |
| 35 | +### Prefixed Document Identifier |
| 36 | + |
| 37 | +:: A _prefixed document identifier_ is a document identifier that contains at |
| 38 | +least one colon symbol (`:`). The text before the first colon symbol is called |
| 39 | +the {prefix}, and the text after it is called the {payload}. The {prefix} |
| 40 | +identifies the method of identification used. Applications may use their own |
| 41 | +identification methods by ensuring that the prefix starts `x-`; otherwise, all |
| 42 | +prefixes are reserved for reasons of future expansion. |
| 43 | + |
| 44 | +### SHA256 Hex Document Identifier |
| 45 | + |
| 46 | +:: A _SHA256 hex document identifier_ is a _prefixed document identifier_ where |
| 47 | +{prefix} is `sha256` and {payload} is 64 hexadecimal characters (in lower case). |
| 48 | + |
| 49 | +The payload of a _SHA256 hex document identifier_ must be produced via the |
| 50 | +lower-case hexadecimal encoding of the SHA256 hash (as specified in |
| 51 | +[RFC4634](https://datatracker.ietf.org/doc/html/rfc4634)) of the Source Text of |
| 52 | +the GraphQL Document (as specified in |
| 53 | +[the Language section of the GraphQL specification](https://spec.graphql.org/draft/#sec-Language)). |
| 54 | + |
| 55 | +A service which accepts a _persisted document request_ SHOULD support the |
| 56 | +_SHA256 hex document identifier_ for compatibility. |
| 57 | + |
| 58 | +#### Example |
| 59 | + |
| 60 | +The following GraphQL query (with no trailing newline): |
| 61 | + |
| 62 | +```graphql example |
| 63 | +query ($id: ID!) { |
| 64 | + user(id: $id) { |
| 65 | + name |
| 66 | + } |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +Would have the following _SHA256 hex document identifier_: |
| 71 | + |
| 72 | +```example |
| 73 | +sha256:7dba4bd717b41f10434822356a93c32b1fb4907b983e854300ad839f84cdcd6e |
| 74 | +``` |
| 75 | + |
| 76 | +Whereas the same query with all optional whitespace omitted: |
| 77 | + |
| 78 | +```raw graphql example |
| 79 | +query($id:ID!){user(id:$id){name}} |
| 80 | +``` |
| 81 | + |
| 82 | +Would have this different _SHA256 hex document identifier_: |
| 83 | + |
| 84 | +```example |
| 85 | +sha256:71f7dc5758652baac68e4a10c50be732b741c892ade2883a99358f52b555286b |
| 86 | +``` |
| 87 | + |
| 88 | +### Custom Document Identifier |
| 89 | + |
| 90 | +:: A _custom document identifier_ is a document identifier that contains no |
| 91 | +colon symbols (`:`). The meaning of a custom document identifier is |
| 92 | +implementation specific. |
| 93 | + |
| 94 | +Note: A 32 character hexadecimal _custom document identifier_ is likely to be an |
| 95 | +MD5 hash of the GraphQL document, as traditionally used by Relay. |
| 96 | + |
| 97 | +## Persisting a Document |
| 98 | + |
| 99 | +A client utilizing persisted documents MUST generate a _document identifier_ for |
| 100 | +each GraphQL Document it wishes to issue to the server, and SHOULD ensure that |
| 101 | +the server can retrieve this GraphQL Document given the document identifier. |
| 102 | + |
| 103 | +Note: The method through which the client and server achieve this is |
| 104 | +implementation specific. Typically persisted documents are stored into some kind |
| 105 | +of trusted shared key-value store at client build time (either directly, or |
| 106 | +indirectly via an authenticated request to the server) such that the server may |
| 107 | +retrieve them given the identifier at request time. It is generally good |
| 108 | +practice for the client to issue both the GraphQL Document and the document |
| 109 | +identifier to the server; the server should then regenerate the document |
| 110 | +identifier from the GraphQL Document independently, and check that the |
| 111 | +identifiers match before storing the Document. An alternative approach has the |
| 112 | +client issue the GraphQL Document to the server, and the server returns an |
| 113 | +arbitrary _custom document identifier_ that the client should incorporate into |
| 114 | +its bundle. |
| 115 | + |
| 116 | +When using persisted documents as an operation allowlist, the client MUST |
| 117 | +persist the documents it uses ahead of time in a secure manner (preventing |
| 118 | +untrusted third parties from adding their own persisted document) such that the |
| 119 | +server will be able to retrieve the identified document within a _persisted |
| 120 | +document request_ and know that it is trusted. |
| 121 | + |
| 122 | +## Persisted Document Request |
| 123 | + |
| 124 | +A server MAY accept a _persisted document request_ via `GET` or `POST`. |
| 125 | + |
| 126 | +### Persisted Document Request Parameters |
| 127 | + |
| 128 | +:: A _persisted document request_ is an HTTP request that encodes the following |
| 129 | +parameters in one of the manners described in this specification: |
| 130 | + |
| 131 | +- {documentId} - (_Required_, string): The string identifier for the Document. |
| 132 | +- {operationName} - (_Optional_, string): The name of the Operation in the |
| 133 | + identified Document to execute. |
| 134 | +- {variables} - (_Optional_, map): Values for any Variables defined by the |
| 135 | + Operation. |
| 136 | +- {extensions} - (_Optional_, map): This entry is reserved for implementors to |
| 137 | + extend the protocol however they see fit. |
| 138 | + |
| 139 | +### GET |
| 140 | + |
| 141 | +For a _persisted document request_ using HTTP GET, parameters SHOULD be provided |
| 142 | +in the query component of the request URL, encoded in the |
| 143 | +`application/x-www-form-urlencoded` format as specified by the |
| 144 | +[WhatWG URLSearchParams class](https://url.spec.whatwg.org/#interface-urlsearchparams). |
| 145 | + |
| 146 | +The {documentId} parameter must be a string _document identifier_. |
| 147 | + |
| 148 | +The {operationName} parameter, if present, must be a string. |
| 149 | + |
| 150 | +Each of the {variables} and {extensions} parameters, if used, MUST be encoded as |
| 151 | +a JSON string. |
| 152 | + |
| 153 | +Setting the value of the {operationName} parameter to the empty string is |
| 154 | +equivalent to omitting the {operationName} parameter. |
| 155 | + |
| 156 | +A client MAY provide the _persisted document request_ parameters in another way |
| 157 | +if the server supports that. |
| 158 | + |
| 159 | +Note: A common alternative pattern is to use a dedicated URL for each _persisted |
| 160 | +operation_ (e.g. |
| 161 | +`https://example.com/graphql/sha256:71f7dc5758652baac68e4a10c50be732b741c892ade2883a99358f52b555286b`). |
| 162 | + |
| 163 | +#### Canonical Parameters |
| 164 | + |
| 165 | +Parameters SHOULD be provided in the order given in the list above, any optional |
| 166 | +parameters which have no value SHOULD be omitted, and parameters encoded as JSON |
| 167 | +string SHOULD use the most compressed form (with all optional whitespace |
| 168 | +omitted). A server MAY reject requests where this is not adhered to. |
| 169 | + |
| 170 | +Note: Ensuring that parameters are in their canonical form helps improve cache |
| 171 | +hit ratios. |
| 172 | + |
| 173 | +#### Example |
| 174 | + |
| 175 | +Executing the GraphQL Document identified by |
| 176 | +`"sha256:71f7dc5758652baac68e4a10c50be732b741c892ade2883a99358f52b555286b"` with |
| 177 | +the following query variables: |
| 178 | + |
| 179 | +```raw json example |
| 180 | +{"id":"QVBJcy5ndXJ1"} |
| 181 | +``` |
| 182 | + |
| 183 | +This request could be sent via an HTTP GET as follows: |
| 184 | + |
| 185 | +```url example |
| 186 | +https://example.com/graphql?documentId=sha256:71f7dc5758652baac68e4a10c50be732b741c892ade2883a99358f52b555286b&variables=%7B%22id%22%3A%22QVBJcy5ndXJ1%22%7D |
| 187 | +``` |
| 188 | + |
| 189 | +### POST |
| 190 | + |
| 191 | +For a _persisted document request_ using HTTP POST, the request MUST have a body |
| 192 | +which contains values of the _persisted document request_ parameters encoded in |
| 193 | +one of the officially recognized GraphQL media types, or another media type |
| 194 | +supported by the server. |
| 195 | + |
| 196 | +#### JSON Encoding |
| 197 | + |
| 198 | +When encoded in JSON, a _persisted document request_ is encoded as a JSON object |
| 199 | +(map), with the properties specified by the persisted document request: |
| 200 | + |
| 201 | +- {documentId} - the string identifier for the Document |
| 202 | +- {operationName} - an optional string |
| 203 | +- {variables} - an optional object (map), the keys of which are the variable |
| 204 | + names and the values of which are the variable values |
| 205 | +- {extensions} - an optional object (map) |
| 206 | + |
| 207 | +#### Example |
| 208 | + |
| 209 | +If we wanted to execute the following GraphQL query: |
| 210 | + |
| 211 | +```raw graphql example |
| 212 | +query ($id: ID!) { |
| 213 | + user(id: $id) { |
| 214 | + name |
| 215 | + } |
| 216 | +} |
| 217 | +``` |
| 218 | + |
| 219 | +With the following query variables: |
| 220 | + |
| 221 | +```json example |
| 222 | +{ |
| 223 | + "id": "QVBJcy5ndXJ1" |
| 224 | +} |
| 225 | +``` |
| 226 | + |
| 227 | +This request could be sent via an HTTP POST to the relevant URL using the JSON |
| 228 | +encoding with the headers: |
| 229 | + |
| 230 | +```headers example |
| 231 | +Content-Type: application/json |
| 232 | +Accept: application/graphql-response+json |
| 233 | +``` |
| 234 | + |
| 235 | +And the body: |
| 236 | + |
| 237 | +```json example |
| 238 | +{ |
| 239 | + "documentId": "sha256:7dba4bd717b41f10434822356a93c32b1fb4907b983e854300ad839f84cdcd6e", |
| 240 | + "variables": { |
| 241 | + "id": "QVBJcy5ndXJ1" |
| 242 | + } |
| 243 | +} |
| 244 | +``` |
| 245 | + |
| 246 | +## Persisted Document Response |
| 247 | + |
| 248 | +When a server that implements _persisted documents_ receives a well-formed |
| 249 | +_persisted document request_, it must return a well‐formed _GraphQL response_. |
| 250 | + |
| 251 | +The server should retrieve the GraphQL Document identified by the {documentId} |
| 252 | +parameter. If the server fails to retrieve the document, it MUST respond with a |
| 253 | +well-formed _GraphQL response_ consisting of a single error. Otherwise, it |
| 254 | +should construct a _GraphQL-over-HTTP request_ using this document and the other |
| 255 | +parameters of the _persisted document request_, and then follow the details in |
| 256 | +the [Response section](#sec-Response). |
0 commit comments