[CORL-149] Persisted Queries (#2445)

* feat: enable persisted queries on the client

* fix: use `id` inside websocket message

* feat: initial server support for PQ

* feat: deeper server support

* feat: abstracted persisted query replacing logic
This commit is contained in:
Vinh
2019-08-15 21:03:32 +00:00
committed by Wyatt Johnson
parent 635e740fc0
commit 43b6a2cdcd
30 changed files with 1268 additions and 465 deletions
@@ -4,13 +4,16 @@ import {
Disposable,
Variables,
} from "react-relay-network-modern/es";
import { SubscriptionClient } from "subscriptions-transport-ws";
import {
OperationOptions,
SubscriptionClient,
} from "subscriptions-transport-ws";
import { ACCESS_TOKEN_PARAM, CLIENT_ID_PARAM } from "coral-common/constants";
import { ERROR_CODES } from "coral-common/errors";
/**
* SubscriptionRequest containts the subscription
* SubscriptionRequest contains the subscription
* request data that comes from Relay.
*/
export interface SubscriptionRequest {
@@ -109,17 +112,29 @@ export default function createManagedSubscriptionClient(
},
});
}
const subscription = subscriptionClient
.request({
operationName: operation.name,
query: operation.text!,
variables,
})
.subscribe({
next({ data }) {
observer.onNext({ data });
},
});
if (!operation.text && !operation.id) {
throw Error("Neither subscription query nor id was provided.");
}
const opts: OperationOptions = {
operationName: operation.name,
// subscriptions-transport-ws requires `query` to be set to an non-empty string.
// With persisted queries we only have the id, so set this to
// "PERSISTED_QUERY" to get around validation.
query: operation.text || "PERSISTED_QUERY",
variables,
};
// Query is not available which means we can use the id from persisted queries.
if (!operation.text) {
opts.id = operation.id;
}
const subscription = subscriptionClient.request(opts).subscribe({
next({ data }) {
observer.onNext({ data });
},
});
request.unsubscribe = () => {
subscription.unsubscribe();
};
@@ -1,6 +1,5 @@
import {
authMiddleware,
batchMiddleware,
cacheMiddleware,
RelayNetworkLayer,
retryMiddleware,
@@ -11,6 +10,7 @@ import {
import clientIDMiddleware from "./clientIDMiddleware";
import { ManagedSubscriptionClient } from "./createManagedSubscriptionClient";
import customErrorMiddleware from "./customErrorMiddleware";
import persistedQueriesGetMethodMiddleware from "./persistedQueriesGetMethodMiddleware";
export type TokenGetter = () => string;
@@ -51,11 +51,6 @@ export default function createNetwork(
urlMiddleware({
url: () => Promise.resolve(graphqlURL),
}),
batchMiddleware({
batchUrl: (requestMap: any) => Promise.resolve(graphqlURL),
batchTimeout: 0,
allowMutations: true,
}),
retryMiddleware({
fetchTimeout: 15000,
retryDelays: (attempt: number) => Math.pow(2, attempt + 4) * 100,
@@ -66,6 +61,7 @@ export default function createNetwork(
token: tokenGetter,
}),
clientIDMiddleware(clientID),
persistedQueriesGetMethodMiddleware,
],
{ subscribeFn: createSubscriptionFunction(subscriptionClient) }
);
@@ -0,0 +1,46 @@
import { Middleware, RelayRequestAny } from "react-relay-network-modern/es";
import { modifyQuery } from "coral-framework/utils";
function hasMutations(req: RelayRequestAny): boolean {
return req.isMutation();
}
function queriesAreEmpty(req: RelayRequestAny): boolean {
return req.getQueryString() === "";
}
/**
* persistedQueriesGetMethodMiddleware will use the GET method instead of POST for
* all request excluding mutations when persisted queries are used.
* The request data will be encoded in base64url and set in the GET query string under
* the variable "d=".
*/
const persistedQueriesGetMethodMiddleware: Middleware = next => async req => {
if (queriesAreEmpty(req) && !hasMutations(req)) {
// Pull the body out (serializing it) and delete it off of the original
// fetch options.
const body: Record<string, any> = JSON.parse(req.fetchOpts.body as string);
delete req.fetchOpts.body;
// Reconfigure the fetch for GET.
req.fetchOpts.method = "GET";
// Rebuild the query parameters for GET.
const params: Record<string, string> = { query: "" };
for (const key in body) {
if (!body.hasOwnProperty(key)) {
continue;
}
const value = body[key];
params[key] = typeof value === "string" ? value : JSON.stringify(value);
}
// Combine the new parameters onto the URL.
req.fetchOpts.url = modifyQuery(req.fetchOpts.url as string, params);
}
return next(req);
};
export default persistedQueriesGetMethodMiddleware;