Skip to content

Commit 905b05b

Browse files
authored
Merge pull request #4798 from rmosolgo/js-generate-persisted-query-manifest-support
Support generate-persisted-query-manifest for OperationStore
2 parents d196056 + 86d6778 commit 905b05b

File tree

9 files changed

+218
-1
lines changed

9 files changed

+218
-1
lines changed

guides/javascript_client/sync.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ JavaScript support for GraphQL projects using [graphql-pro](https://graphql.pro)
1717
- [Apollo Link support](#use-with-apollo-link)
1818
- [Apollo Codegen Support](#use-with-apollo-codegen)
1919
- [Apollo Android support](#use-with-apollo-android)
20+
- [Apollo Persisted Queries Support](#use-with-apollo-persisted-queries)
2021
- [Plain JS support](#use-with-plain-javascript)
2122
- [Authorization](#authorization)
2223

@@ -263,6 +264,29 @@ end
263264

264265
You may also have to __update your app__ to send an identifier, so that the server can determine the "client name" used with the operation store. (Apollo Android sends a query hash, but the operation store expects IDs in the form `#{client_name}/#{query_hash}`.)
265266

267+
## Use with Apollo Persisted Queries
268+
269+
Apollo client has a [Persisted Queries Link](https://www.apollographql.com/docs/react/api/link/persisted-queries/). You can use that link with GraphQL-Pro's {% internal_link "OperationStore", "/operation_store/overview" %}. First, create a manifest with [`generate-persisted-query-manifest`](https://www.apollographql.com/docs/react/api/link/persisted-queries/#1-generate-operation-manifests), then, pass the path to that file to `sync`:
270+
271+
```sh
272+
$ graphql-ruby-client sync --apollo-persisted-query-manifest=path/to/manifest.json ...
273+
```
274+
275+
Then, configure Apollo Client to [use your persisted query manifest](https://www.apollographql.com/docs/react/api/link/persisted-queries/#persisted-queries-implementation).
276+
277+
Finally, update your controller to receive the operation ID and pass it as `context[:operation_id]`:
278+
279+
```ruby
280+
client_name = "..." # TODO: send the client name as a query param or header
281+
persisted_query_hash = params[:extensions][:persistedQuery][:sha256Hash]
282+
context = {
283+
# ...
284+
operation_id: "#{client_name}/#{persisted_query_hash}"
285+
}
286+
```
287+
288+
The `operation_id` will also need your client name. Using Apollo Client, you could send this as a [custom header](https://www.apollographql.com/docs/react/networking/basic-http-networking/#customizing-request-headers) or another way that works for your application (eg, session or user agent).
289+
266290
## Use with plain JavaScript
267291

268292
`OperationStoreClient.getOperationId` takes an operation name as input and returns the server-side alias for that operation:

javascript_client/src/__tests__/__snapshots__/syncTest.ts.snap

Lines changed: 101 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

javascript_client/src/__tests__/syncTest.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ describe("sync operations", () => {
4242
expect(generatedCode).toMatchSnapshot()
4343
})
4444
})
45+
46+
it("works with persisted query manifest", () => {
47+
var options = {
48+
client: "test-1",
49+
outfile: "./src/OperationStoreClient.js",
50+
apolloPersistedQueryManifest: "./src/sync/__tests__/generate-persisted-query-manifest.json",
51+
}
52+
53+
return sync(options).then(function() {
54+
var generatedCode = fs.readFileSync("./src/OperationStoreClient.js", "utf8")
55+
expect(generatedCode).toMatch('"TestQuery2": "xyz-123"')
56+
expect(generatedCode).toMatchSnapshot()
57+
})
58+
})
4559
})
4660

4761
describe("custom HTTP options", () => {

javascript_client/src/cli.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ optional arguments:
2424
(Outfile generation is skipped by default.)
2525
--apollo-android-operation-output=<path> Path to a .json file from Apollo-Android's "generateOperationOutput" feature.
2626
(Outfile generation is skipped by default.)
27+
--apollo-persisted-query-manifest=<path> Path to a .json file from Apollo's "generate-persisted-query-manifest" tool.
28+
(Outfile generation is skipped by default.)
2729
--mode=<mode> Treat files like a certain kind of project:
2830
relay: treat files like relay-compiler output
2931
project: treat files like a cohesive project (fragments are shared, names must be unique)
@@ -63,6 +65,7 @@ optional arguments:
6365
relayPersistedOutput: argv["relay-persisted-output"],
6466
apolloCodegenJsonOutput: argv["apollo-codegen-json-output"],
6567
apolloAndroidOperationOutput: argv["apollo-android-operation-output"],
68+
apolloPersistedQueryManifest: argv["apollo-persisted-query-manifest"],
6669
url: argv.url,
6770
client: argv.client,
6871
outfile: argv.outfile,

javascript_client/src/sync/__tests__/__snapshots__/preparePersistedQueryListTest.ts.snap

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"format": "apollo-persisted-query-manifest",
3+
"version": 1,
4+
"operations": [
5+
{
6+
"id": "4a29162b05ee4d82ad02e8f50af4bf112f47181ec558a7100a",
7+
"name": "TestQuery1",
8+
"type": "query",
9+
"body": "query TestQuery1 {\n testing {\n id\n label\n description\n __typename\n} }"
10+
},
11+
{
12+
"id": "xyz-123",
13+
"name": "TestQuery2",
14+
"type": "mutation",
15+
"body": "query TestQuery2 {\n testing2 {\n id2\n label\n description2\n __typename\n} }"
16+
}
17+
]
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import preparePersistedQueryList from "../preparePersistedQueryList"
2+
3+
it("reads generate-persisted-query-manifest output", () => {
4+
const manifestPath = "./src/sync/__tests__/generate-persisted-query-manifest.json"
5+
var ops = preparePersistedQueryList(manifestPath)
6+
expect(ops).toMatchSnapshot()
7+
})

javascript_client/src/sync/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { generateClientCode, gatherOperations, ClientOperation } from "./generat
44
import Logger from "./logger"
55
import fs from "fs"
66
import { removeClientFieldsFromString } from "./removeClientFields"
7+
import preparePersistedQueryList from "./preparePersistedQueryList"
78

89
interface SyncOptions {
910
path?: string,
1011
relayPersistedOutput?: string,
1112
apolloAndroidOperationOutput?: string,
1213
apolloCodegenJsonOutput?: string,
14+
apolloPersistedQueryManifest?: string,
1315
secret?: string
1416
url?: string,
1517
mode?: string,
@@ -32,6 +34,7 @@ interface SyncOptions {
3234
* @param {String} options.path - A glob to recursively search for `.graphql` files (Default is `./`)
3335
* @param {String} options.relayPersistedOutput - A path to a `.json` file from `relay-compiler`'s `--persist-output` option
3436
* @param {String} options.apolloCodegenJsonOutput - A path to a `.json` file from `apollo client:codegen ... --type json`
37+
* @param {String} options.apolloPersistedQueryManifest - A path to a `.json` file from `generate-persisted-query-manifest`
3538
* @param {String} options.secret - HMAC-SHA256 key which must match the server secret (default is no encryption)
3639
* @param {String} options.url - Target URL for sending prepared queries. If omitted, then an outfile is generated without sending operations to the server.
3740
* @param {String} options.mode - If `"file"`, treat each file separately. If `"project"`, concatenate all files and extract each operation. If `"relay"`, treat it as relay-compiler output
@@ -107,6 +110,10 @@ function sync(options: SyncOptions) {
107110
body: bodyWithoutClientFields,
108111
})
109112
})
113+
} else if (options.apolloPersistedQueryManifest) {
114+
var payload: { operations: ClientOperation[] } = {
115+
operations: preparePersistedQueryList(options.apolloPersistedQueryManifest)
116+
}
110117
} else {
111118
var payload = gatherOperations({
112119
path: graphqlGlob,
@@ -122,7 +129,7 @@ function sync(options: SyncOptions) {
122129
var outfile: string | null
123130
if (options.outfile) {
124131
outfile = options.outfile
125-
} else if (options.relayPersistedOutput || options.apolloAndroidOperationOutput || options.apolloCodegenJsonOutput) {
132+
} else if (options.relayPersistedOutput || options.apolloAndroidOperationOutput || options.apolloCodegenJsonOutput || options.apolloPersistedQueryManifest) {
126133
// These artifacts have embedded IDs in its generated files,
127134
// no need to generate an outfile.
128135
outfile = null
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import fs from "fs"
2+
3+
// Transform the output from generate-persisted-query-manifest
4+
// to something that OperationStore `sync` can use.
5+
export default function preparePersistedQueryList(pqlPath: string) {
6+
const pqlString = fs.readFileSync(pqlPath, "utf8")
7+
const pqlJson = JSON.parse(pqlString)
8+
return pqlJson.operations.map(function(persistedQueryConfig: { body: string, id: string, name: string, type: string }) {
9+
return {
10+
body: persistedQueryConfig.body,
11+
alias: persistedQueryConfig.id,
12+
name: persistedQueryConfig.name
13+
}
14+
})
15+
}

0 commit comments

Comments
 (0)