diff --git a/guides/javascript_client/relay_subscriptions.md b/guides/javascript_client/relay_subscriptions.md index 3fb2b2bb6c..d3df92ffa0 100644 --- a/guides/javascript_client/relay_subscriptions.md +++ b/guides/javascript_client/relay_subscriptions.md @@ -133,6 +133,22 @@ var subscriptionHandler = createRelaySubscriptionHandler({ var network = Network.create(fetchQuery, subscriptionHandler) ``` +## With Relay Persisted Queries + +If you're using Relay's built-in [persisted query support](https://relay.dev/docs/guides/persisted-queries/), you can pass `clientName:` to the handler in order to build IDs that work with the {% internal_link "OperationStore", "/operation_store/overview.html" %}. For example: + +```js +var subscriptionHandler = createRelaySubscriptionHandler({ + cable: createConsumer(...), + clientName: "web-frontend", // This should match the one you use for `sync` +}) + +// Create a Relay Modern network with the handler +var network = Network.create(fetchQuery, subscriptionHandler) +``` + +Then, the ActionCable handler will use Relay's provided operation IDs to interact with the OperationStore. + ## fetchOperation function The `fetchOperation` function can be extracted from your `fetchQuery` function. Its signature is: diff --git a/javascript_client/src/subscriptions/__tests__/createActionCableHandlerTest.ts b/javascript_client/src/subscriptions/__tests__/createActionCableHandlerTest.ts index df49280621..95a2dd414a 100644 --- a/javascript_client/src/subscriptions/__tests__/createActionCableHandlerTest.ts +++ b/javascript_client/src/subscriptions/__tests__/createActionCableHandlerTest.ts @@ -1,5 +1,6 @@ import { createActionCableHandler } from "../createActionCableHandler" import type { Consumer } from "@rails/actioncable" + describe("createActionCableHandler", () => { it("returns a function producing a disposable subscription", () => { var wasDisposed = false @@ -21,4 +22,43 @@ describe("createActionCableHandler", () => { expect(wasDisposed).toEqual(true) }) + + it("uses a provided clientName and operation.id", () => { + var handlers: any + var log: [string, any][]= [] + + var dummyActionCableConsumer = { + subscriptions: { + create: (_conn: any, newHandlers: any) => { + handlers = newHandlers + return { + perform: (evt: string, data: any) => { + log.push([evt, data]) + } + } + } + } + } + + var options = { + cable: (dummyActionCableConsumer as unknown) as Consumer, + clientName: "client-1", + } + + var producer = createActionCableHandler(options); + + producer( + {text: "", name: "", id: "abcdef"}, + {}, + {}, + { onError: () => {}, onNext: () => {}, onCompleted: () => {} } + ) + + handlers.connected() // trigger the GraphQL send + + expect(log).toEqual([ + ["send", { operationId: "client-1/abcdef", operationName: "", query: "", variables: {} }], + ["execute", { operationId: "client-1/abcdef", operationName: "", query: "", variables: {} }], + ]) + }) }) diff --git a/javascript_client/src/subscriptions/createActionCableHandler.ts b/javascript_client/src/subscriptions/createActionCableHandler.ts index a48747218a..fa8df2c28a 100644 --- a/javascript_client/src/subscriptions/createActionCableHandler.ts +++ b/javascript_client/src/subscriptions/createActionCableHandler.ts @@ -11,10 +11,11 @@ interface ActionCableHandlerOptions { cable: Consumer operations?: { getOperationId: Function} channelName?: string + clientName?: string } function createActionCableHandler(options: ActionCableHandlerOptions) { - return function (operation: { text: string, name: string}, variables: object, _cacheConfig: object, observer: {onError: Function, onNext: Function, onCompleted: Function}) { + return function (operation: { text: string, name: string, id?: string }, variables: object, _cacheConfig: object, observer: {onError: Function, onNext: Function, onCompleted: Function}) { // unique-ish var channelId = Math.round(Date.now() + Math.random() * 100000).toString(16) var cable = options.cable @@ -39,7 +40,8 @@ function createActionCableHandler(options: ActionCableHandlerOptions) { channelParams = { variables: variables, operationName: operation.name, - query: operation.text + query: operation.text, + operationId: (operation.id && options.clientName ? (options.clientName + "/" + operation.id) : null), } } channel.perform('send', channelParams) diff --git a/javascript_client/src/subscriptions/createRelaySubscriptionHandler.ts b/javascript_client/src/subscriptions/createRelaySubscriptionHandler.ts index 8d39c979d8..a4e623110b 100644 --- a/javascript_client/src/subscriptions/createRelaySubscriptionHandler.ts +++ b/javascript_client/src/subscriptions/createRelaySubscriptionHandler.ts @@ -12,7 +12,7 @@ function createLegacyRelaySubscriptionHandler(options: ActionCableHandlerOptions } else if ((options as AblyHandlerOptions).ably) { handler = createAblyHandler(options as AblyHandlerOptions) } else { - throw new Error("Missing options for subscribtion handler") + throw new Error("Missing options for subscription handler") } return handler } @@ -47,6 +47,7 @@ function createRelaySubscriptionHandler(options: ActionCableHandlerOptions | Pus { text: request.text || "", name: request.name, + id: request.id, }, variables, {},