diff --git a/packages/react-relay/__tests__/RelayResolvers-abstractTypeRootFragment-test.js b/packages/react-relay/__tests__/RelayResolvers-abstractTypeRootFragment-test.js index 87a507857be0..4a9da86d8b87 100644 --- a/packages/react-relay/__tests__/RelayResolvers-abstractTypeRootFragment-test.js +++ b/packages/react-relay/__tests__/RelayResolvers-abstractTypeRootFragment-test.js @@ -88,15 +88,6 @@ test('Can read a resolver with a rootFragment on an abstract type', async () => jest.runAllImmediates(); }); - // Incorrect! We provided `__isNode`! - expect(logEvents).toEqual([ - { - fieldPath: '', - kind: 'missing_expected_data.log', - owner: 'NodeResolversGreeting', - uiContext: undefined, - }, - ]); - // Incorrect! Should be the greeting. - expect(renderer?.toJSON()).toEqual(null); + expect(logEvents).toEqual([]); + expect(renderer?.toJSON()).toEqual('Hello Node with id 4!'); }); diff --git a/packages/relay-runtime/store/RelayResponseNormalizer.js b/packages/relay-runtime/store/RelayResponseNormalizer.js index 5e3251abf340..4a7e1161c610 100644 --- a/packages/relay-runtime/store/RelayResponseNormalizer.js +++ b/packages/relay-runtime/store/RelayResponseNormalizer.js @@ -16,6 +16,7 @@ import type {PayloadData, PayloadError} from '../network/RelayNetworkTypes'; import type { NormalizationActorChange, NormalizationDefer, + NormalizationInlineFragment, NormalizationLinkedField, NormalizationLiveResolverField, NormalizationModuleImport, @@ -54,6 +55,7 @@ const RelayModernRecord = require('./RelayModernRecord'); const {createNormalizationSelector} = require('./RelayModernSelector'); const { ROOT_ID, + ROOT_TYPE, TYPENAME_KEY, getArgumentValues, getHandleStorageKey, @@ -243,34 +245,7 @@ class RelayResponseNormalizer { break; } case 'InlineFragment': { - const {abstractKey} = selection; - if (abstractKey == null) { - const typeName = RelayModernRecord.getType(record); - if (typeName === selection.type) { - this._traverseSelections(selection, record, data); - } - } else { - // $FlowFixMe[method-unbinding] - data could be prototype less - const implementsInterface = Object.prototype.hasOwnProperty.call( - data, - abstractKey, - ); - const typeName = RelayModernRecord.getType(record); - const typeID = generateTypeID(typeName); - let typeRecord = this._recordSource.get(typeID); - if (typeRecord == null) { - typeRecord = RelayModernRecord.create(typeID, TYPE_SCHEMA_TYPE); - this._recordSource.set(typeID, typeRecord); - } - RelayModernRecord.setValue( - typeRecord, - abstractKey, - implementsInterface, - ); - if (implementsInterface) { - this._traverseSelections(selection, record, data); - } - } + this._normalizeInlineFragment(selection, record, data); break; } case 'TypeDiscriminator': { @@ -358,13 +333,62 @@ class RelayResponseNormalizer { } } + _normalizeInlineFragment( + selection: NormalizationInlineFragment, + record: Record, + data: PayloadData, + ) { + const {abstractKey} = selection; + if (abstractKey == null) { + const typeName = RelayModernRecord.getType(record); + if ( + typeName === selection.type || + // The root record type is a special `__Root` type and may not match the + // type on the ast, so ignore type mismatches at the root. We currently + // detect whether we're at the root by checking against ROOT_ID, but this + // does not work for mutations/subscriptions which generate unique root + // ids. This is acceptable in practice as we don't read data for + // mutations/subscriptions in a situation where we would use + // isMissingData to decide whether to suspend or not. + // TODO T96653810: Correctly detect reading from root of mutation/subscription + (typeName === ROOT_TYPE && + !RelayFeatureFlags.DISABLE_RESOLVER_ROOT_FRAGMENT_NORMALIZATION_BUG_FIX) + ) { + this._traverseSelections(selection, record, data); + } + } else { + // $FlowFixMe[method-unbinding] - data could be prototype less + const implementsInterface = Object.prototype.hasOwnProperty.call( + data, + abstractKey, + ); + const typeName = RelayModernRecord.getType(record); + const typeID = generateTypeID(typeName); + let typeRecord = this._recordSource.get(typeID); + if (typeRecord == null) { + typeRecord = RelayModernRecord.create(typeID, TYPE_SCHEMA_TYPE); + this._recordSource.set(typeID, typeRecord); + } + RelayModernRecord.setValue(typeRecord, abstractKey, implementsInterface); + if (implementsInterface) { + this._traverseSelections(selection, record, data); + } + } + } + _normalizeResolver( resolver: NormalizationResolverField | NormalizationLiveResolverField, record: Record, data: PayloadData, ) { if (resolver.fragment != null) { - this._traverseSelections(resolver.fragment, record, data); + if ( + RelayFeatureFlags.DISABLE_RESOLVER_ROOT_FRAGMENT_NORMALIZATION_BUG_FIX + ) { + this._traverseSelections(resolver.fragment, record, data); + } else { + this._normalizeInlineFragment(resolver.fragment, record, data); + } } } diff --git a/packages/relay-runtime/util/RelayFeatureFlags.js b/packages/relay-runtime/util/RelayFeatureFlags.js index e779ba7000ec..362d770b4040 100644 --- a/packages/relay-runtime/util/RelayFeatureFlags.js +++ b/packages/relay-runtime/util/RelayFeatureFlags.js @@ -80,6 +80,10 @@ export type FeatureFlags = { // // See https://github.com/facebook/relay/issues/4882 CHECK_ALL_FRAGMENTS_FOR_MISSING_CLIENT_EDGES: boolean, + + // Killswitch in case the fix in https://github.com/facebook/relay/pull/5059 + // causes issues. + DISABLE_RESOLVER_ROOT_FRAGMENT_NORMALIZATION_BUG_FIX: boolean, }; const RelayFeatureFlags: FeatureFlags = { @@ -109,6 +113,7 @@ const RelayFeatureFlags: FeatureFlags = { ENABLE_TYPENAME_PREFIXED_DATA_ID: false, ENABLE_UI_CONTEXT_ON_RELAY_LOGGER: false, CHECK_ALL_FRAGMENTS_FOR_MISSING_CLIENT_EDGES: false, + DISABLE_RESOLVER_ROOT_FRAGMENT_NORMALIZATION_BUG_FIX: false, }; module.exports = RelayFeatureFlags;