Skip to content

Commit c08b7a1

Browse files
committed
Add Appendix A: Persisted Documents
1 parent 25c6968 commit c08b7a1

File tree

2 files changed

+258
-0
lines changed

2 files changed

+258
-0
lines changed
+256
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
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 hexidecimal characters (in lower case).
48+
49+
The payload of a _SHA256 hex document identifier_ must be produced via the
50+
lower-case hexidecimal 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 hexidecimal _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 utilising 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 increases the
171+
cacheability of the request.
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).

spec/GraphQLOverHTTP.md

+2
Original file line numberDiff line numberDiff line change
@@ -738,3 +738,5 @@ payload is `application/json` then the client MUST NOT rely on the body to be a
738738
well-formed _GraphQL response_ since the source of the response may not be the
739739
server but instead some intermediary such as API gateways, proxies, firewalls,
740740
etc.
741+
742+
# [Appendix: Persisted Document](Appendix%20A%20--%20Persisted%20Documents.md)

0 commit comments

Comments
 (0)