Skip to content

Commit 13494c2

Browse files
committed
persisted document snippet
1 parent e9d0535 commit 13494c2

File tree

5 files changed

+212
-0
lines changed

5 files changed

+212
-0
lines changed

executable/persisted/README.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Persisted Documents
2+
3+
GraphQL _executable documents_ containing at least one operation definition
4+
and optional fragment defintions can be persisted with a schema.
5+
6+
## Overview
7+
8+
The optional `executables` argument of `@sdl` takes a list of input document files
9+
that are GraphQL _executable documents_.
10+
11+
```
12+
@sdl(
13+
files: []
14+
executables: [{ document: "operations.graphql", persist: true }]
15+
)
16+
```
17+
18+
When a schema is deployed the _executable documents_ must validate successfully
19+
against the schema.
20+
21+
By itself this can be used to validate applications' use
22+
of a GraphQL endpoint remain valid when the schema changes.
23+
This requires that the application loads GraphQL requests from
24+
files containing executable documents that are declared in the schema.
25+
26+
In addition any _executable document_ in the schema that is marked with `persist: true`
27+
is loaded as a _persisted document_.
28+
29+
A client uses a _persisted document_ by specifying its document identifier
30+
(based upon a SHA256 hash) in a request instead of the content of the _executable document_.
31+
32+
For example instead of this POST body for a request:
33+
34+
```
35+
{
36+
"query": "{__typname}"
37+
}
38+
```
39+
40+
a request can use this:
41+
42+
```
43+
{
44+
"documentId": "sha256:ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38"
45+
}
46+
```
47+
48+
if an _executable document_ was declared in the schema with the matching SHA256 hash.
49+
50+
The request parameters `operationName` and `variables` can be used as required with `documentId`.
51+
52+
## Benefits
53+
54+
Use of persisted documents typically saves network bandwidth as for real application requests
55+
the hash is smaller than the body of the _executable document_.
56+
57+
In addition query requests can use HTTP GET while maintaining a reasonable sized URL
58+
that improves caching of URL requests. For example the above request can be a coded as an HTTP GET:
59+
60+
```
61+
https://london.us-east-a.ibm.stepzen.net/api/customer/graphql?documentId=sha256:ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38
62+
```
63+
64+
## Executable Documents
65+
66+
A good practice is to ensure that a CI/CD process ensures that _executable documents_ declared as
67+
part of the schema using `@sdl(executables:)` are formatted consistentcy, using tooling such as `prettier`.
68+
69+
For example the simple _executable document_ shown above `{__typename}` stored in a document file
70+
and formatted will have contents (including a newline at the end):
71+
72+
```
73+
{
74+
__typename
75+
}
76+
```
77+
78+
and thus a document identifier of `sha256:8d8f7365e9e86fa8e3313fcaf2131b801eafe9549de22373089cf27511858b39`.
79+
80+
Clients can obtain the SHA256 hash for a document identifer using any standard mechanism for calculating hashes,
81+
for example on Linux/Unix systems this command can be used:
82+
83+
```
84+
shasum -a 256 operations.graphql
85+
```
86+
87+
When _executable document_ are persisted using `@sdl(executables:)` the schema calculates the document identifiers automatically.

executable/persisted/index.graphql

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
schema
2+
@sdl(
3+
files: []
4+
# executables defines a list of GraphQL executable documents that
5+
# are validated against the schema when deployed.
6+
# If an executable document is marked as persist: true
7+
# then it becomes a persisted document with the document identifier
8+
# being tha SHA 256 hash of the document file.
9+
executables: [{ document: "operations.graphql", persist: true }]
10+
) {
11+
query: Query
12+
}
13+
14+
type Query {
15+
customer(id: ID!): Customer
16+
}
17+
type Customer @mock {
18+
id: ID!
19+
name: String! @mockfn(name: "LastName")
20+
email: String @mockfn(name: "Email")
21+
phone: String @mockfn(name: "Phone")
22+
address: Address
23+
}
24+
type Address {
25+
city: String @mockfn(name: "City")
26+
zip: String @mockfn(name: "Zip")
27+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
query Customer($id: ID!) {
2+
customer(id: $id) {
3+
id
4+
name
5+
email
6+
phone
7+
address {
8+
city
9+
zip
10+
}
11+
}
12+
}
13+
14+
query CustomerName($id: ID!) {
15+
customer(id: $id) {
16+
name
17+
}
18+
}
19+
20+
query CustomerEmail($id: ID!) {
21+
customer(id: $id) {
22+
email
23+
}
24+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"endpoint": "api/miscellaneous"
3+
}

executable/persisted/tests/Test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
const fs = require("fs");
2+
const path = require("node:path");
3+
4+
const {
5+
deployAndRun,
6+
authTypes,
7+
getTestDescription,
8+
} = require("../../../tests/gqltest.js");
9+
10+
testDescription = getTestDescription("snippets", __dirname);
11+
12+
const requestsFile = path.join(path.dirname(__dirname), "operations.graphql");
13+
const requests = fs.readFileSync(requestsFile, "utf8").toString();
14+
15+
describe(testDescription, function () {
16+
const tests = [
17+
{
18+
label: "CustomerName",
19+
documentId:
20+
"sha256:9d50d8e35b5882139e836a126f5d6d5a28cf41c5efd80a6e67f920d284b5f6d0",
21+
operationName: "CustomerName",
22+
variables: {
23+
id: 1031,
24+
},
25+
expected: {
26+
customer: {
27+
name: "Tromp",
28+
},
29+
},
30+
authType: authTypes.adminKey,
31+
},
32+
{
33+
label: "CustomerName",
34+
documentId:
35+
"sha256:9d50d8e35b5882139e836a126f5d6d5a28cf41c5efd80a6e67f920d284b5f6d0",
36+
operationName: "CustomerEmail",
37+
variables: {
38+
id: 2845,
39+
},
40+
expected: {
41+
customer: {
42+
43+
},
44+
},
45+
authType: authTypes.adminKey,
46+
},
47+
{
48+
label: "Customer",
49+
documentId:
50+
"sha256:9d50d8e35b5882139e836a126f5d6d5a28cf41c5efd80a6e67f920d284b5f6d0",
51+
operationName: "Customer",
52+
variables: {
53+
id: 3293,
54+
},
55+
expected: {
56+
customer: {
57+
id: "3293",
58+
name: "Veum",
59+
email: null,
60+
phone: "5349179326",
61+
address: {
62+
city: "New Abshire",
63+
zip: "75624",
64+
},
65+
},
66+
},
67+
authType: authTypes.adminKey,
68+
},
69+
];
70+
return deployAndRun(__dirname, tests);
71+
});

0 commit comments

Comments
 (0)