diff --git a/packages/relay-runtime/store/RelayModernQueryExecutor.js b/packages/relay-runtime/store/RelayModernQueryExecutor.js index f8058b379a3ca..c07a911596555 100644 --- a/packages/relay-runtime/store/RelayModernQueryExecutor.js +++ b/packages/relay-runtime/store/RelayModernQueryExecutor.js @@ -481,6 +481,21 @@ class Executor { this._updateOperationTracker(updatedOwners); this._processPayloadFollowups(payloadFollowups); } + if ( + this._isSubscriptionOperation && + RelayFeatureFlags.ENABLE_UNIQUE_SUBSCRIPTION_ROOT + ) { + // We attach the id to allow the `requestSubscription` to read from the store using + // the current id in its `onNext` callback + if (responsesWithData[0].extensions == null) { + // $FlowFixMe[cannot-write] + responsesWithData[0].extensions = { + __relay_subscription_root_id: this._operation.fragment.dataID, + }; + } else { + responsesWithData[0].extensions.__relay_subscription_root_id = this._operation.fragment.dataID; + } + } this._sink.next(response); } diff --git a/packages/relay-runtime/subscription/__tests__/__generated__/requestSubscriptionTestConfigCreateSubscription.graphql.js b/packages/relay-runtime/subscription/__tests__/__generated__/requestSubscriptionTestConfigCreateSubscription.graphql.js index c9a793993c825..40512a5ddc1fb 100644 --- a/packages/relay-runtime/subscription/__tests__/__generated__/requestSubscriptionTestConfigCreateSubscription.graphql.js +++ b/packages/relay-runtime/subscription/__tests__/__generated__/requestSubscriptionTestConfigCreateSubscription.graphql.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<124f99c9f18ad3078c8d1c6b69a7ec28>> + * @generated SignedSource<> * @flow * @lightSyntaxTransform * @nogrep @@ -16,11 +16,13 @@ /*:: import type { ConcreteRequest } from 'relay-runtime'; +type requestSubscriptionTestExtraFragment$ref = any; export type requestSubscriptionTestConfigCreateSubscriptionVariables = {||}; export type requestSubscriptionTestConfigCreateSubscriptionResponse = {| +configCreateSubscribe: ?{| +config: ?{| +name: ?string, + +$fragmentRefs: requestSubscriptionTestExtraFragment$ref, |}, |}, |}; @@ -31,44 +33,49 @@ export type requestSubscriptionTestConfigCreateSubscription = {| */ var node/*: ConcreteRequest*/ = (function(){ -var v0 = [ - { - "alias": null, - "args": null, - "concreteType": "ConfigCreateSubscriptResponsePayload", - "kind": "LinkedField", - "name": "configCreateSubscribe", - "plural": false, +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "requestSubscriptionTestConfigCreateSubscription", "selections": [ { "alias": null, "args": null, - "concreteType": "Config", + "concreteType": "ConfigCreateSubscriptResponsePayload", "kind": "LinkedField", - "name": "config", + "name": "configCreateSubscribe", "plural": false, "selections": [ { "alias": null, "args": null, - "kind": "ScalarField", - "name": "name", + "concreteType": "Config", + "kind": "LinkedField", + "name": "config", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "requestSubscriptionTestExtraFragment" + } + ], "storageKey": null } ], "storageKey": null } ], - "storageKey": null - } -]; -return { - "fragment": { - "argumentDefinitions": [], - "kind": "Fragment", - "metadata": null, - "name": "requestSubscriptionTestConfigCreateSubscription", - "selections": (v0/*: any*/), "type": "Subscription", "abstractKey": null }, @@ -77,23 +84,54 @@ return { "argumentDefinitions": [], "kind": "Operation", "name": "requestSubscriptionTestConfigCreateSubscription", - "selections": (v0/*: any*/) + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ConfigCreateSubscriptResponsePayload", + "kind": "LinkedField", + "name": "configCreateSubscribe", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Config", + "kind": "LinkedField", + "name": "config", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "isEnabled", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ] }, "params": { - "cacheID": "36c326050b83dafd66df29fd5f3fcf48", + "cacheID": "1b69adfae158cc6f1ccf259d412d1bf3", "id": null, "metadata": { "subscriptionName": "configCreateSubscribe" }, "name": "requestSubscriptionTestConfigCreateSubscription", "operationKind": "subscription", - "text": "subscription requestSubscriptionTestConfigCreateSubscription {\n configCreateSubscribe {\n config {\n name\n }\n }\n}\n" + "text": "subscription requestSubscriptionTestConfigCreateSubscription {\n configCreateSubscribe {\n config {\n name\n ...requestSubscriptionTestExtraFragment\n }\n }\n}\n\nfragment requestSubscriptionTestExtraFragment on Config {\n isEnabled\n}\n" } }; })(); if (__DEV__) { - (node/*: any*/).hash = "fa3a7ee3c0d4bd8282b692d2c1075dc0"; + (node/*: any*/).hash = "6b2edf520dcf571dde1465c394f0a52e"; } module.exports = node; diff --git a/packages/relay-runtime/subscription/__tests__/__generated__/requestSubscriptionTestExtraFragment.graphql.js b/packages/relay-runtime/subscription/__tests__/__generated__/requestSubscriptionTestExtraFragment.graphql.js new file mode 100644 index 0000000000000..8b0dee6b21a7b --- /dev/null +++ b/packages/relay-runtime/subscription/__tests__/__generated__/requestSubscriptionTestExtraFragment.graphql.js @@ -0,0 +1,56 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @generated SignedSource<> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ReaderFragment } from 'relay-runtime'; +import type { FragmentReference } from "relay-runtime"; +declare export opaque type requestSubscriptionTestExtraFragment$ref: FragmentReference; +declare export opaque type requestSubscriptionTestExtraFragment$fragmentType: requestSubscriptionTestExtraFragment$ref; +export type requestSubscriptionTestExtraFragment = {| + +isEnabled: ?boolean, + +$refType: requestSubscriptionTestExtraFragment$ref, +|}; +export type requestSubscriptionTestExtraFragment$data = requestSubscriptionTestExtraFragment; +export type requestSubscriptionTestExtraFragment$key = { + +$data?: requestSubscriptionTestExtraFragment$data, + +$fragmentRefs: requestSubscriptionTestExtraFragment$ref, + ... +}; +*/ + +var node/*: ReaderFragment*/ = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "requestSubscriptionTestExtraFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "isEnabled", + "storageKey": null + } + ], + "type": "Config", + "abstractKey": null +}; + +if (__DEV__) { + (node/*: any*/).hash = "93722b56e12ad71765eb789731338c25"; +} + +module.exports = node; diff --git a/packages/relay-runtime/subscription/__tests__/requestSubscription-test.js b/packages/relay-runtime/subscription/__tests__/requestSubscription-test.js index 103a08325aa2f..725bba3c30fbc 100644 --- a/packages/relay-runtime/subscription/__tests__/requestSubscription-test.js +++ b/packages/relay-runtime/subscription/__tests__/requestSubscription-test.js @@ -31,7 +31,6 @@ const {ROOT_ID} = require('../../store/RelayStoreUtils'); const {createMockEnvironment} = require('relay-test-utils-internal'); describe('requestSubscription-test', () => { - RelayFeatureFlags.ENABLE_UNIQUE_SUBSCRIPTION_ROOT = true; it('Config: `RANGE_ADD`', () => { const environment = createMockEnvironment(); const store = environment.getStore(); @@ -242,29 +241,37 @@ describe('requestSubscription-test', () => { store, }); }); - it('with cacheConfig', () => { - requestSubscription(environment, { - subscription: CommentCreateSubscription, - variables, - cacheConfig: { - metadata, - }, - }); - expect(cacheMetadata).toEqual(metadata); - }); + function runTests() { + it('with cacheConfig', () => { + requestSubscription(environment, { + subscription: CommentCreateSubscription, + variables, + cacheConfig: { + metadata, + }, + }); - it('without cacheConfig', () => { - requestSubscription(environment, { - subscription: CommentCreateSubscription, - variables, + expect(cacheMetadata).toEqual(metadata); }); - expect(cacheMetadata).toEqual(undefined); - }); + it('without cacheConfig', () => { + requestSubscription(environment, { + subscription: CommentCreateSubscription, + variables, + }); + + expect(cacheMetadata).toEqual(undefined); + }); + } + RelayFeatureFlags.ENABLE_UNIQUE_SUBSCRIPTION_ROOT = false; + runTests(); + RelayFeatureFlags.ENABLE_UNIQUE_SUBSCRIPTION_ROOT = true; + runTests(); }); it('does not overwrite existing data', () => { + RelayFeatureFlags.ENABLE_UNIQUE_SUBSCRIPTION_ROOT = true; const ConfigsQuery = getRequest(graphql` query requestSubscriptionTestConfigsQuery { viewer { @@ -279,11 +286,18 @@ describe('requestSubscription-test', () => { } `); + graphql` + fragment requestSubscriptionTestExtraFragment on Config { + isEnabled + } + `; + const ConfigCreateSubscription = getRequest(graphql` subscription requestSubscriptionTestConfigCreateSubscription { configCreateSubscribe { config { name + ...requestSubscriptionTestExtraFragment } } } @@ -364,7 +378,14 @@ describe('requestSubscription-test', () => { }); expect(onNext).toBeCalledTimes(1); expect(onNext.mock.calls[0][0]).toEqual({ - configCreateSubscribe: {config: {name: 'Mark'}}, + configCreateSubscribe: { + config: { + name: 'Mark', + __id: expect.any(String), + __fragments: {requestSubscriptionTestExtraFragment: {}}, + __fragmentOwner: expect.any(Object), + }, + }, }); environment.mock.nextValue(ConfigCreateSubscription, { @@ -396,7 +417,14 @@ describe('requestSubscription-test', () => { }); expect(onNext).toBeCalledTimes(2); expect(onNext.mock.calls[1][0]).toEqual({ - configCreateSubscribe: {config: {name: 'Zuck'}}, + configCreateSubscribe: { + config: { + name: 'Zuck', + __id: expect.any(String), + __fragments: {requestSubscriptionTestExtraFragment: {}}, + __fragmentOwner: expect.any(Object), + }, + }, }); }); }); diff --git a/packages/relay-runtime/subscription/requestSubscription.js b/packages/relay-runtime/subscription/requestSubscription.js index e50aa1a49acdb..16624d4568c56 100644 --- a/packages/relay-runtime/subscription/requestSubscription.js +++ b/packages/relay-runtime/subscription/requestSubscription.js @@ -22,6 +22,7 @@ const {generateUniqueClientID} = require('../store/ClientID'); const { createOperationDescriptor, } = require('../store/RelayModernOperationDescriptor'); +const {createReaderSelector} = require('../store/RelayModernSelector'); import type {DeclarativeMutationConfig} from '../mutations/RelayDeclarativeMutationConfig'; import type {GraphQLTaggedNode} from '../query/GraphQLTag'; @@ -91,17 +92,24 @@ function requestSubscription( updater, }) .map(responses => { + let selector = operation.fragment; if (RelayFeatureFlags.ENABLE_UNIQUE_SUBSCRIPTION_ROOT) { + let nextID; if (Array.isArray(responses)) { - // $FlowFixMe[incompatible-cast] - return (responses.map( - response => response.data, - ): TSubscriptionPayload); + nextID = responses[0]?.extensions?.__relay_subscription_root_id; + } else { + nextID = responses.extensions?.__relay_subscription_root_id; + } + if (typeof nextID === 'string') { + selector = createReaderSelector( + selector.node, + nextID, + selector.variables, + selector.owner, + ); } - // $FlowFixMe[incompatible-cast] - return (responses.data: TSubscriptionPayload); } - const data = environment.lookup(operation.fragment).data; + const data = environment.lookup(selector).data; // $FlowFixMe[incompatible-cast] return (data: TSubscriptionPayload); })