Skip to content

Commit

Permalink
Shallow freeze resolver value
Browse files Browse the repository at this point in the history
Reviewed By: alunyov

Differential Revision: D51049743

fbshipit-source-id: 5c8926e84e3eb355d4eb5972dbcb556d8a6fa022
  • Loading branch information
tyao1 authored and facebook-github-bot committed Nov 15, 2023
1 parent 7f54255 commit 6519571
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 4 deletions.
8 changes: 4 additions & 4 deletions packages/react-relay/__tests__/RelayResolverModel-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@ function logFn(event: LogEvent): void {
beforeEach(() => {
RelayFeatureFlags.ENABLE_RELAY_RESOLVERS = true;
RelayFeatureFlags.ENABLE_CLIENT_EDGES = true;
RelayFeatureFlags.ENABLE_SHALLOW_FREEZE_RESOLVER_VALUES = true;
logEvents = [];
resetStore(logFn);
});

afterEach(() => {
RelayFeatureFlags.ENABLE_RELAY_RESOLVERS = false;
RelayFeatureFlags.ENABLE_CLIENT_EDGES = false;
RelayFeatureFlags.ENABLE_SHALLOW_FREEZE_RESOLVER_VALUES = false;
});

function createEnvironment() {
Expand Down Expand Up @@ -539,8 +541,7 @@ describe.each([
setIsHuman(true);
jest.runAllImmediates();
});
// TODO: Should be 0. Relay should not mutate the value here.
expect(renderer.toJSON()).toEqual('human:100');
expect(renderer.toJSON()).toEqual('human:0');

TestRenderer.act(() => {
renderer.unmount();
Expand Down Expand Up @@ -568,10 +569,9 @@ describe.each([
);
expect(renderer.toJSON()).toEqual('robot:0');

// TODO: should not throw on mutating the inner of the resolver value
expect(() => {
chargeBattery();
}).toThrow();
}).not.toThrow();

TestRenderer.act(() => {
renderer.unmount();
Expand Down
5 changes: 5 additions & 0 deletions packages/relay-runtime/store/ResolverCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import type {

const recycleNodesInto = require('../util/recycleNodesInto');
const {RELAY_LIVE_RESOLVER} = require('../util/RelayConcreteNode');
const RelayFeatureFlags = require('../util/RelayFeatureFlags');
const shallowFreeze = require('../util/shallowFreeze');
const {generateClientID} = require('./ClientID');
const RelayModernRecord = require('./RelayModernRecord');
const {
Expand Down Expand Up @@ -172,6 +174,9 @@ class RecordResolverCache implements ResolverCache {
linkedRecord = RelayModernRecord.create(linkedID, '__RELAY_RESOLVER__');

const evaluationResult = evaluate();
if (RelayFeatureFlags.ENABLE_SHALLOW_FREEZE_RESOLVER_VALUES) {
shallowFreeze(evaluationResult.resolverResult);
}
RelayModernRecord.setValue(
linkedRecord,
RELAY_RESOLVER_VALUE_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import type {LiveState} from 'relay-runtime';

const recycleNodesInto = require('../../util/recycleNodesInto');
const {RELAY_LIVE_RESOLVER} = require('../../util/RelayConcreteNode');
const RelayFeatureFlags = require('../../util/RelayFeatureFlags');
const shallowFreeze = require('../../util/shallowFreeze');
const {generateClientID, generateClientObjectClientID} = require('../ClientID');
const RelayModernRecord = require('../RelayModernRecord');
const {createNormalizationSelector} = require('../RelayModernSelector');
Expand Down Expand Up @@ -620,12 +622,18 @@ class LiveResolverCache implements ResolverCache {
nextOutputTypeRecordIDs,
);

if (RelayFeatureFlags.ENABLE_SHALLOW_FREEZE_RESOLVER_VALUES) {
shallowFreeze(resolverValue);
}
RelayModernRecord.setValue(
resolverRecord,
RELAY_RESOLVER_VALUE_KEY,
resolverValue,
);
} else {
if (RelayFeatureFlags.ENABLE_SHALLOW_FREEZE_RESOLVER_VALUES) {
shallowFreeze(value);
}
// For "classic" resolvers (or if the value is nullish), we are just setting their
// value as is.
RelayModernRecord.setValue(
Expand Down
2 changes: 2 additions & 0 deletions packages/relay-runtime/util/RelayFeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type FeatureFlags = {
STRING_INTERN_LEVEL: number,
LOG_MISSING_RECORDS_IN_PROD: boolean,
ENABLE_RELAY_OPERATION_TRACKER_SUSPENSE: boolean,
ENABLE_SHALLOW_FREEZE_RESOLVER_VALUES: boolean,

// Configure RelayStoreSubscriptions to mark a subscription as affected by an
// update if there are any overlapping IDs other than ROOT_ID or VIWER_ID,
Expand Down Expand Up @@ -63,6 +64,7 @@ const RelayFeatureFlags: FeatureFlags = {
ENABLE_OPERATION_TRACKER_OPTIMISTIC_UPDATES: false,
ENABLE_RELAY_OPERATION_TRACKER_SUSPENSE: false,
ENABLE_FIELD_ERROR_HANDLING: false,
ENABLE_SHALLOW_FREEZE_RESOLVER_VALUES: false,
};

module.exports = RelayFeatureFlags;
23 changes: 23 additions & 0 deletions packages/relay-runtime/util/shallowFreeze.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* 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
* @format
* @oncall relay
*/

'use strict';

// Shallow freeze to prevent Relay from mutating the value in recycleNodesInto or deepFreezing the value
module.exports = function shallowFreeze(value: mixed) {
if (
typeof value === 'object' &&
value != null &&
(Array.isArray(value) || value.constructor === Object)
) {
Object.freeze(value);
}
};

0 comments on commit 6519571

Please sign in to comment.