From 900f40cae43f1739ad9d16db5a2726ad57f3e5a3 Mon Sep 17 00:00:00 2001 From: Yuanchao Zhu Date: Tue, 18 Jul 2023 13:11:00 -0700 Subject: [PATCH] Update operation tracker for optimistic updates Differential Revision: D46991164 fbshipit-source-id: 45ed1c088132a1ac28b5326e79697330eed266bc --- ...hOperationTrackerOptimisticUpdates-test.js | 282 +++++++++++++++++ ...erOptimisticUpdatesTestFragment.graphql.js | 67 ++++ ...ackerOptimisticUpdatesTestQuery.graphql.js | 146 +++++++++ ...icUpdatesTestViewerFriendsQuery.graphql.js | 286 ++++++++++++++++++ .../relay-runtime/store/OperationExecutor.js | 5 +- .../relay-runtime/util/RelayFeatureFlags.js | 2 + 6 files changed, 787 insertions(+), 1 deletion(-) create mode 100644 packages/react-relay/relay-hooks/__tests__/FragmentResource-WithOperationTrackerOptimisticUpdates-test.js create mode 100644 packages/react-relay/relay-hooks/__tests__/__generated__/FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment.graphql.js create mode 100644 packages/react-relay/relay-hooks/__tests__/__generated__/FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery.graphql.js create mode 100644 packages/react-relay/relay-hooks/__tests__/__generated__/FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery.graphql.js diff --git a/packages/react-relay/relay-hooks/__tests__/FragmentResource-WithOperationTrackerOptimisticUpdates-test.js b/packages/react-relay/relay-hooks/__tests__/FragmentResource-WithOperationTrackerOptimisticUpdates-test.js new file mode 100644 index 0000000000000..872789faddaad --- /dev/null +++ b/packages/react-relay/relay-hooks/__tests__/FragmentResource-WithOperationTrackerOptimisticUpdates-test.js @@ -0,0 +1,282 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall relay + */ + +'use strict'; +import type {LogEvent} from 'relay-runtime/store/RelayStoreTypes'; + +const {getFragment} = require('../../../relay-runtime'); +const {createFragmentResource} = require('../FragmentResource'); +const { + createOperationDescriptor, + createReaderSelector, + graphql, +} = require('relay-runtime'); +const RelayOperationTracker = require('relay-runtime/store/RelayOperationTracker'); +const RelayFeatureFlags = require('relay-runtime/util/RelayFeatureFlags'); +const {createMockEnvironment} = require('relay-test-utils'); +const {disallowWarnings} = require('relay-test-utils-internal'); + +disallowWarnings(); + +describe('FragmentResource with Operation Tracker for optimistic updates behavior', () => { + const componentName = 'TestComponent'; + let environment; + let UserFragment; + let FragmentResource; + let operationTracker; + let nodeOperation; + let nodeOperation2; + let logger; + let UserQuery; + let ViewerFriendsQuery; + let viewerOperation; + + beforeEach(() => { + RelayFeatureFlags.ENABLE_OPERATION_TRACKER_OPTIMISTIC_UPDATES = true; + operationTracker = new RelayOperationTracker(); + logger = jest.fn<[LogEvent], void>(); + environment = createMockEnvironment({ + operationTracker, + log: logger, + }); + + UserFragment = graphql` + fragment FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment on User { + id + name + } + `; + UserQuery = graphql` + query FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery( + $id: ID! + ) { + node(id: $id) { + __typename + ...FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment + } + } + `; + + ViewerFriendsQuery = graphql` + query FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery { + viewer { + actor { + friends(first: 1) @connection(key: "Viewer_friends") { + edges { + node { + ...FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment + } + } + } + } + } + } + `; + FragmentResource = createFragmentResource(environment); + nodeOperation = createOperationDescriptor(UserQuery, { + id: 'user-id-1', + }); + nodeOperation2 = createOperationDescriptor(UserQuery, { + id: 'user-id-2', + }); + viewerOperation = createOperationDescriptor(ViewerFriendsQuery, {}); + environment.execute({operation: viewerOperation}).subscribe({}); + environment.execute({operation: nodeOperation}).subscribe({}); + environment.subscribe( + environment.lookup(viewerOperation.fragment), + jest.fn(), + ); + + // We need to subscribe to a fragment in order for OperationTracker + // to be able to notify owners if they are affected by any pending operation + environment.subscribe( + environment.lookup( + createReaderSelector( + UserFragment, + 'user-id-1', + viewerOperation.request.variables, + viewerOperation.request, + ), + ), + jest.fn(), + ); + }); + + afterEach(() => { + RelayFeatureFlags.ENABLE_OPERATION_TRACKER_OPTIMISTIC_UPDATES = false; + }); + + it('should throw promise for pending operation affecting fragment owner', () => { + environment.commitPayload(viewerOperation, { + viewer: { + actor: { + id: 'viewer-id', + __typename: 'User', + friends: { + pageInfo: { + hasNextPage: true, + hasPrevPage: false, + startCursor: 'cursor-1', + endCursor: 'cursor-1', + }, + edges: [ + { + cursor: 'cursor-1', + node: { + id: 'user-id-1', + name: 'Alice', + __typename: 'User', + }, + }, + ], + }, + }, + }, + }); + + const fragmentRef = { + __id: 'user-id-1', + __fragments: { + FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment: {}, + }, + __fragmentOwner: nodeOperation.request, + }; + + const result = FragmentResource.read( + getFragment(UserFragment), + fragmentRef, + componentName, + ); + FragmentResource.subscribe(result, jest.fn()); + + // Execute the nodeOperation as a mutation and set the record as undefined in optimistic updater + environment + .executeMutation({ + operation: nodeOperation, + optimisticUpdater: store => { + const record = store.get('user-id-1'); + record?.setValue(undefined, 'name'); + }, + }) + .subscribe({}); + + // Check the pending opeartion for both the node and viewer query + const pendingOperationsForViewerOperation = + operationTracker.getPendingOperationsAffectingOwner( + viewerOperation.request, + )?.promise; + expect(pendingOperationsForViewerOperation).not.toBe(null); + + const pendingOperationsForNodeOperation = + operationTracker.getPendingOperationsAffectingOwner( + nodeOperation.request, + )?.promise; + expect(pendingOperationsForNodeOperation).not.toBe(null); + }); + + it('when an unrelated operation resolves while an optimistic response is currently applied', () => { + environment.commitPayload(viewerOperation, { + viewer: { + actor: { + id: 'viewer-id', + __typename: 'User', + friends: { + pageInfo: { + hasNextPage: true, + hasPrevPage: false, + startCursor: 'cursor-1', + endCursor: 'cursor-2', + }, + edges: [ + { + cursor: 'cursor-1', + node: { + id: 'user-id-1', + name: 'Alice', + __typename: 'User', + }, + }, + { + cursor: 'cursor-2', + node: { + id: 'user-id-2', + name: 'Bob', + __typename: 'User', + }, + }, + ], + }, + }, + }, + }); + + const fragmentRef = { + __id: 'user-id-1', + __fragments: { + FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment: {}, + }, + __fragmentOwner: nodeOperation.request, + }; + const fragmentRef2 = { + __id: 'user-id-2', + __fragments: { + FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment: {}, + }, + __fragmentOwner: nodeOperation2.request, + }; + + const result = FragmentResource.read( + getFragment(UserFragment), + fragmentRef, + componentName, + ); + FragmentResource.subscribe(result, jest.fn()); + const result2 = FragmentResource.read( + getFragment(UserFragment), + fragmentRef2, + componentName, + ); + FragmentResource.subscribe(result2, jest.fn()); + + // Execute the nodeOperation as a mutation and set the record as undefined in optimistic updater + environment + .executeMutation({ + operation: nodeOperation, + optimisticUpdater: store => { + const record = store.get('user-id-1'); + record?.setValue(undefined, 'name'); + }, + }) + .subscribe({}); + environment + .executeMutation({ + operation: nodeOperation2, + optimisticUpdater: store => { + const record = store.get('user-id-2'); + record?.setValue(undefined, 'name'); + }, + }) + .subscribe({}); + + const pendingOperationsForNodeOperation = + operationTracker.getPendingOperationsAffectingOwner( + nodeOperation.request, + ); + expect(pendingOperationsForNodeOperation?.pendingOperations.length).toBe(1); + const pendingOperationsForNodeOperation2 = + operationTracker.getPendingOperationsAffectingOwner( + nodeOperation2.request, + ); + expect(pendingOperationsForNodeOperation2?.pendingOperations.length).toBe( + 1, + ); + }); +}); diff --git a/packages/react-relay/relay-hooks/__tests__/__generated__/FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment.graphql.js b/packages/react-relay/relay-hooks/__tests__/__generated__/FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment.graphql.js new file mode 100644 index 0000000000000..8ebd8f953f82b --- /dev/null +++ b/packages/react-relay/relay-hooks/__tests__/__generated__/FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment.graphql.js @@ -0,0 +1,67 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @oncall relay + * + * @generated SignedSource<<9140b5d003eaf8ad8af413ad7e1005d5>> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { Fragment, ReaderFragment } from 'relay-runtime'; +import type { FragmentType } from "relay-runtime"; +declare export opaque type FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment$fragmentType: FragmentType; +export type FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment$data = {| + +id: string, + +name: ?string, + +$fragmentType: FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment$fragmentType, +|}; +export type FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment$key = { + +$data?: FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment$data, + +$fragmentSpreads: FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment$fragmentType, + ... +}; +*/ + +var node/*: ReaderFragment*/ = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +}; + +if (__DEV__) { + (node/*: any*/).hash = "608a07a152032988a6413ca6f1dbfbaf"; +} + +module.exports = ((node/*: any*/)/*: Fragment< + FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment$fragmentType, + FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment$data, +>*/); diff --git a/packages/react-relay/relay-hooks/__tests__/__generated__/FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery.graphql.js b/packages/react-relay/relay-hooks/__tests__/__generated__/FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery.graphql.js new file mode 100644 index 0000000000000..91382633a1dc1 --- /dev/null +++ b/packages/react-relay/relay-hooks/__tests__/__generated__/FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery.graphql.js @@ -0,0 +1,146 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @oncall relay + * + * @generated SignedSource<<74a5dbdc8516bd27deb03e9589c5ec06>> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +import type { FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment$fragmentType } from "./FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment.graphql"; +export type FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery$variables = {| + id: string, +|}; +export type FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery$data = {| + +node: ?{| + +__typename: string, + +$fragmentSpreads: FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment$fragmentType, + |}, +|}; +export type FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery = {| + response: FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery$data, + variables: FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery$variables, +|}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "1e4b5355ae26c1d5586da76240492e10", + "id": null, + "metadata": {}, + "name": "FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery", + "operationKind": "query", + "text": "query FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery(\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment\n id\n }\n}\n\nfragment FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment on User {\n id\n name\n}\n" + } +}; +})(); + +if (__DEV__) { + (node/*: any*/).hash = "14c673af170df5bad4f4905bb7368415"; +} + +module.exports = ((node/*: any*/)/*: Query< + FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery$variables, + FragmentResourceWithOperationTrackerOptimisticUpdatesTestQuery$data, +>*/); diff --git a/packages/react-relay/relay-hooks/__tests__/__generated__/FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery.graphql.js b/packages/react-relay/relay-hooks/__tests__/__generated__/FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery.graphql.js new file mode 100644 index 0000000000000..f841262a0c5d3 --- /dev/null +++ b/packages/react-relay/relay-hooks/__tests__/__generated__/FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery.graphql.js @@ -0,0 +1,286 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @oncall relay + * + * @generated SignedSource<> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest, Query } from 'relay-runtime'; +import type { FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment$fragmentType } from "./FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment.graphql"; +export type FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery$variables = {||}; +export type FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery$data = {| + +viewer: ?{| + +actor: ?{| + +friends: ?{| + +edges: ?$ReadOnlyArray, + |}, + |}, + |}, +|}; +export type FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery = {| + response: FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery$data, + variables: FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery$variables, +|}; +*/ + +var node/*: ConcreteRequest*/ = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null +}, +v2 = { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null +}, +v3 = [ + { + "kind": "Literal", + "name": "first", + "value": 1 + } +], +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Viewer", + "kind": "LinkedField", + "name": "viewer", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": null, + "kind": "LinkedField", + "name": "actor", + "plural": false, + "selections": [ + { + "alias": "friends", + "args": null, + "concreteType": "FriendsConnection", + "kind": "LinkedField", + "name": "__Viewer_friends_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "FriendsEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment" + }, + (v0/*: any*/) + ], + "storageKey": null + }, + (v1/*: any*/) + ], + "storageKey": null + }, + (v2/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Viewer", + "kind": "LinkedField", + "name": "viewer", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": null, + "kind": "LinkedField", + "name": "actor", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": (v3/*: any*/), + "concreteType": "FriendsConnection", + "kind": "LinkedField", + "name": "friends", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "FriendsEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v4/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + (v0/*: any*/) + ], + "storageKey": null + }, + (v1/*: any*/) + ], + "storageKey": null + }, + (v2/*: any*/) + ], + "storageKey": "friends(first:1)" + }, + { + "alias": null, + "args": (v3/*: any*/), + "filters": null, + "handle": "connection", + "key": "Viewer_friends", + "kind": "LinkedHandle", + "name": "friends" + }, + (v4/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "a722ba9082a20e95ed461fd70017e783", + "id": null, + "metadata": { + "connection": [ + { + "count": null, + "cursor": null, + "direction": "forward", + "path": [ + "viewer", + "actor", + "friends" + ] + } + ] + }, + "name": "FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery", + "operationKind": "query", + "text": "query FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery {\n viewer {\n actor {\n __typename\n friends(first: 1) {\n edges {\n node {\n ...FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment\n id\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n }\n }\n}\n\nfragment FragmentResourceWithOperationTrackerOptimisticUpdatesTestFragment on User {\n id\n name\n}\n" + } +}; +})(); + +if (__DEV__) { + (node/*: any*/).hash = "f9b64d236a796cfb14e99418ead325ba"; +} + +module.exports = ((node/*: any*/)/*: Query< + FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery$variables, + FragmentResourceWithOperationTrackerOptimisticUpdatesTestViewerFriendsQuery$data, +>*/); diff --git a/packages/relay-runtime/store/OperationExecutor.js b/packages/relay-runtime/store/OperationExecutor.js index 428a2e34cdb2d..0e232c176ef66 100644 --- a/packages/relay-runtime/store/OperationExecutor.js +++ b/packages/relay-runtime/store/OperationExecutor.js @@ -614,7 +614,10 @@ class Executor { ); // OK: only called on construction and when receiving an optimistic payload from network, // which doesn't fall-through to the regular next() handling - this._runPublishQueue(); + const updatedOwners = this._runPublishQueue(); + if (RelayFeatureFlags.ENABLE_OPERATION_TRACKER_OPTIMISTIC_UPDATES) { + this._updateOperationTracker(updatedOwners); + } } _processOptimisticFollowups( diff --git a/packages/relay-runtime/util/RelayFeatureFlags.js b/packages/relay-runtime/util/RelayFeatureFlags.js index 776357aa76e26..3e79cd4d9f3cd 100644 --- a/packages/relay-runtime/util/RelayFeatureFlags.js +++ b/packages/relay-runtime/util/RelayFeatureFlags.js @@ -39,6 +39,7 @@ export type FeatureFlags = { // read())`, so we are experimenting with this loose behavior which should be // more compatible. ENABLE_LOOSE_SUBSCRIPTION_ATTRIBUTION: boolean, + ENABLE_OPERATION_TRACKER_OPTIMISTIC_UPDATES: boolean, }; const RelayFeatureFlags: FeatureFlags = { @@ -59,6 +60,7 @@ const RelayFeatureFlags: FeatureFlags = { ENABLE_QUERY_RENDERER_SET_STATE_PREVENTION: false, LOG_MISSING_RECORDS_IN_PROD: false, ENABLE_LOOSE_SUBSCRIPTION_ATTRIBUTION: false, + ENABLE_OPERATION_TRACKER_OPTIMISTIC_UPDATES: false, }; module.exports = RelayFeatureFlags;