JavaScript support for GraphQL projects using graphql-pro’s OperationStore for persisted queries.
sync CLISee the OperationStore guide for server-side setup.
sync utilityThis package contains a command line utility, graphql-ruby-client sync:
$ graphql-ruby-client sync # ...
Authorizing with HMAC
Syncing 4 operations to http://myapp.com/graphql/operations...
3 added
1 not modified
0 failed
Generating client module in app/javascript/graphql/OperationStoreClient.js...
✓ Done!
sync Takes several options:
| option | description |
|---|---|
--url |
Sync API url |
--path |
Local directory to search for .graphql / .graphql.js files |
--relay-persisted-output |
Path to a .json file from relay-compiler ... --persist-output |
--client |
Client ID (created on server) |
--secret |
Client Secret (created on server) |
--outfile |
Destination for generated code |
--outfile-type |
What kind of code to generate (js or json) |
--add-typename |
Add __typename to all selection sets (for use with Apollo Client) |
--verbose |
Output some debug information |
You can see these and a few others with graphql-ruby-client sync --help.
graphql-ruby-client can persist queries from relay-compiler using the embedded @relayHash value. (This was created in Relay before 2.0.0. See below for Relay 2.0+.)
To sync your queries with the server, use the --path option to point to your __generated__ directory, for example:
# sync a Relay project
$ graphql-ruby-client sync --path=src/__generated__ --outfile=src/OperationStoreClient.js --url=...
Then, the generated code may be integrated with Relay’s Network Layer:
// ...
// require the generated module:
const OperationStoreClient = require('./OperationStoreClient')
// ...
function fetchQuery(operation, variables, cacheConfig, uploadables) {
const requestParams = {
variables,
operationName: operation.name,
}
if (process.env.NODE_ENV === "production")
// In production, use the stored operation
requestParams.operationId = OperationStoreClient.getOperationId(operation.name)
} else {
// In development, use the query text
requestParams.query = operation.text,
}
return fetch('/graphql', {
method: 'POST',
headers: { /*...*/ },
body: JSON.stringify(requestParams),
}).then(/graphql-ruby-doc-ja/*%20...%20*/);
}
// ...
(Only Relay Modern is supported. Legacy Relay can’t generate static queries.)
Relay 2.0+ includes a --persist-output option for relay-compiler which works perfectly with GraphQL-Ruby. (Relay’s own docs, for reference: https://relay.dev/docs/en/persisted-queries.)
When generating queries for Relay, include --persist-output:
$ relay-compiler ... --persist-output path/to/persisted-queries.json
Then, push Relay’s generated queries to your OperationStore server with --relay-persisted-output:
$ graphql-ruby-client sync --relay-persisted-output=path/to/persisted-queries.json --url=...
In this case, sync won’t generate a JavaScript module because relay-compiler has already prepared its queries for persisted use. Instead, update your network layer to include the client name and operation id in the HTTP params:
const operationStoreClientName = "MyRelayApp";
function fetchQuery(operation, variables,) {
return fetch('/graphql', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
// Pass the client name and the operation ID, joined by `/`
documentId: operationStoreClientName + "/" + operation.id,
// query: operation.text, // this is now obsolete because text is null
variables,
}),
}).then(response => {
return response.json();
});
}
(Inspired by https://relay.dev/docs/en/persisted-queries#network-layer-changes.)
Now, your Relay app will only send operation IDs over the wire to the server.
Use the --path option to point at your .graphql files:
$ graphql-ruby-client sync --path=src/graphql/ --url=...
Then, load the generated module and add its .apolloMiddleware to your network interface with .use([...]):
// load the generated module
var OperationStoreClient = require("./OperationStoreClient")
// attach it as middleware in production
// (in development, send queries to the server as normal)
if (process.env.NODE_ENV === "production") {
MyNetworkInterface.use([OperationStoreClient.apolloMiddleware])
}
Now, the middleware will replace query strings with operationIds.
Use the --path option to point at your .graphql files:
$ graphql-ruby-client sync --path=src/graphql/ --url=...
Then, load the generated module and add its .apolloLink to your Apollo Link:
// load the generated module
var OperationStoreClient = require("./OperationStoreClient")
// Integrate the link to another link:
const link = ApolloLink.from([
authLink,
OperationStoreClient.apolloLink,
httpLink,
])
// Create a client
const client = new ApolloClient({
link: link,
cache: new InMemoryCache(),
});
Update the controller: Apollo Link supports extra parameters nested as params[:extensions][:operationId], so update your controller to add that param to context:
# app/controllers/graphql_controller.rb
context = {
# ...
# Support Apollo Link:
operation_id: params[:extensions][:operationId]
}
Now, context[:operation_id] will be used to fetch a query from the database.
OperationStoreClient.getOperationId takes an operation name as input and returns the server-side alias for that operation:
var OperationStoreClient = require("./OperationStoreClient")
OperationStoreClient.getOperationId("AppHomeQuery") // => "my-frontend-app/7a8078c7555e20744cb1ff5a62e44aa92c6e0f02554868a15b8a1cbf2e776b6f"
OperationStoreClient.getOperationId("ProductDetailQuery") // => "my-frontend-app/6726a3b816e99b9971a1d25a1205ca81ecadc6eb1d5dd3a71028c4b01cc254c1"
Post the operationId in your GraphQL requests:
// Lookup the operation name:
var operationId = OperationStoreClient.getOperationId(operationName)
// Include it in the params:
$.post("/graphql", {
operationId: operationId,
variables: queryVariables,
}, function(response) {
// ...
})
OperationStore uses HMAC-SHA256 to authenticate requests.
Pass the key to graphql-ruby-client sync as --secret to authenticate it:
$ export MY_SECRET_KEY= "abcdefg..."
$ graphql-ruby-client sync ... --secret=$MY_SECRET_KEY
# ...
Authenticating with HMAC
# ...