From 7b3b0779b570c828e7deb2ca550d9373c457dbbb Mon Sep 17 00:00:00 2001 From: Monica Tang Date: Tue, 2 Jan 2024 14:50:49 -0800 Subject: [PATCH] Fix client->client nullable model issue for SingularConcrete object output types Reviewed By: alunyov Differential Revision: D51871450 fbshipit-source-id: bdc4d12e97fdb94b9e4e1f6d853db76ead5bf714 --- compiler/crates/relay-codegen/Cargo.toml | 1 + .../crates/relay-codegen/src/build_ast.rs | 122 ++++++++++++++++-- ...layResolverNullableModelClientEdge-test.js | 6 +- ...odelTestFieldWithArgumentsQuery.graphql.js | 15 ++- ...ieldWithRootFragmentLegacyQuery.graphql.js | 15 ++- ...lTestFieldWithRootFragmentQuery.graphql.js | 15 ++- ...odelTestNullWeakClientEdgeQuery.graphql.js | 15 ++- ...estSuspendedWeakClientEdgeQuery.graphql.js | 15 ++- ...yResolverModelTestTodoNullQuery.graphql.js | 15 ++- ...RelayResolverModelTestTodoQuery.graphql.js | 15 ++- ...ModelTestTodoWithInterfaceQuery.graphql.js | 15 ++- ...delTestTodoWithPluralFieldQuery.graphql.js | 15 ++- ...lClientEdgeTest_LiveModel_Query.graphql.js | 15 ++- ...lientEdgeTest_StrongModel_Query.graphql.js | 15 ++- packages/relay-runtime/store/RelayReader.js | 27 +++- 15 files changed, 294 insertions(+), 27 deletions(-) diff --git a/compiler/crates/relay-codegen/Cargo.toml b/compiler/crates/relay-codegen/Cargo.toml index b2c3c0a8959ab..7c5f387e4ebbe 100644 --- a/compiler/crates/relay-codegen/Cargo.toml +++ b/compiler/crates/relay-codegen/Cargo.toml @@ -38,6 +38,7 @@ path = "tests/json_codegen_test.rs" [dependencies] common = { path = "../common" } +docblock-shared = { path = "../docblock-shared" } fnv = "1.0" graphql-ir = { path = "../graphql-ir" } graphql-syntax = { path = "../graphql-syntax" } diff --git a/compiler/crates/relay-codegen/src/build_ast.rs b/compiler/crates/relay-codegen/src/build_ast.rs index 52fa088cba314..4261c3692cee6 100644 --- a/compiler/crates/relay-codegen/src/build_ast.rs +++ b/compiler/crates/relay-codegen/src/build_ast.rs @@ -7,8 +7,10 @@ use std::path::PathBuf; +use common::Location; use common::NamedItem; use common::WithLocation; +use docblock_shared::RELAY_RESOLVER_MODEL_INSTANCE_FIELD; use graphql_ir::Argument; use graphql_ir::Condition; use graphql_ir::ConditionValue; @@ -1199,6 +1201,73 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> { } } + fn build_client_edge_model_resolver( + &mut self, + location: Location, + type_name: StringKey, + is_model_live: bool, + relay_resolver_metadata: &RelayResolverMetadata, + ) -> Primitive { + // Generate the id fragment relative JS import path. + let importing_artifact_path = &self + .project_config + .artifact_path_for_definition(self.definition_source_location); + let id_fragment_artifact_name = self + .project_config + .name + .generate_name_for_object_and_field(type_name, CODEGEN_CONSTANTS.id); + let artifact_path = self.project_config.create_path_for_artifact( + location.source_location(), + id_fragment_artifact_name.clone(), + ); + let backing_field_js_module_path = self + .project_config + .js_module_import_identifier(importing_artifact_path, &artifact_path); + // Generate the resolver module for the output type. + let resolver_module_path = self.project_config.js_module_import_identifier( + importing_artifact_path, + &PathBuf::from(location.source_location().path().intern().lookup()), + ); + let resolver_js_module = JSModuleDependency { + path: resolver_module_path, + import_name: ModuleImportName::Named { + name: type_name, + import_as: Some(type_name), + }, + }; + let resolver_module = Primitive::RelayResolverModel { + graphql_module_path: backing_field_js_module_path, + graphql_module_name: id_fragment_artifact_name.clone().intern(), + js_module: resolver_js_module, + injected_field_name_details: Some((CODEGEN_CONSTANTS.id, true)), + }; + let path = format!( + "{}.{}", + relay_resolver_metadata.field_path, *RELAY_RESOLVER_MODEL_INSTANCE_FIELD + ) + .intern(); + let kind = if is_model_live { + CODEGEN_CONSTANTS.relay_live_resolver + } else { + CODEGEN_CONSTANTS.relay_resolver + }; + let field = relay_resolver_metadata.field(self.schema); + let object_props = object! { + alias: Primitive::Null, + args: self.build_reader_relay_resolver_args(relay_resolver_metadata), + fragment: Primitive::Key(self.object(object! { + args: Primitive::SkippableNull, + kind: Primitive::String(CODEGEN_CONSTANTS.fragment_spread), + name: Primitive::String(id_fragment_artifact_name.intern()), + })), + kind: Primitive::String(kind), + name: Primitive::String(field.name.item), + resolver_module: resolver_module, + path: Primitive::String(path), + }; + Primitive::Key(self.object(object_props)) + } + fn build_reader_relay_resolver_args( &mut self, relay_resolver_metadata: &RelayResolverMetadata, @@ -1575,7 +1644,7 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> { fn build_reader_client_edge( &mut self, context: &mut ContextualMetadata, - client_edge_metadata: ClientEdgeMetadata<'_>, + client_edge_metadata: &ClientEdgeMetadata<'_>, required_metadata: Option, ) -> Primitive { context.has_client_edges = true; @@ -1630,17 +1699,52 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> { client_edge_selections_key: selections_item, })) } - ClientEdgeMetadataDirective::ClientObject { type_name, .. } => { + ClientEdgeMetadataDirective::ClientObject { type_name, location, is_model_live, has_model_instance_field, .. } => { let concrete_type = match type_name { Some(type_name) => Primitive::String(type_name.0), None => Primitive::Null, }; - Primitive::Key(self.object(object! { - kind: Primitive::String(CODEGEN_CONSTANTS.client_edge_to_client_object), - concrete_type: concrete_type, - client_edge_backing_field_key: backing_field, - client_edge_selections_key: selections_item, - })) + let field_directives = match &client_edge_metadata.backing_field { + Selection::ScalarField(field) => Some(&field.directives), + Selection::FragmentSpread(frag_spread) => Some(&frag_spread.directives), + _ => panic!( + "Expected Client Edge backing field to be a Relay Resolver. {:?}", + client_edge_metadata.backing_field + ), + }; + let model_resolver_field = if let Some(field_directives) = field_directives { + let resolver_metadata = RelayResolverMetadata::find(field_directives).unwrap(); + let is_weak_resolver = matches!(resolver_metadata.output_type_info, ResolverOutputTypeInfo::Composite(_)); + let should_add_model_resolver = !is_weak_resolver && has_model_instance_field; + if should_add_model_resolver { + Some(self.build_client_edge_model_resolver( + location, + type_name.unwrap().to_string().intern(), + is_model_live, + resolver_metadata, + )) + } else { + None + } + } else { + None + }; + if model_resolver_field.is_some() { + Primitive::Key(self.object(object! { + kind: Primitive::String(CODEGEN_CONSTANTS.client_edge_to_client_object), + concrete_type: concrete_type, + client_edge_model_resolver: model_resolver_field.unwrap(), + client_edge_backing_field_key: backing_field, + client_edge_selections_key: selections_item, + })) + } else { + Primitive::Key(self.object(object! { + kind: Primitive::String(CODEGEN_CONSTANTS.client_edge_to_client_object), + concrete_type: concrete_type, + client_edge_backing_field_key: backing_field, + client_edge_selections_key: selections_item, + })) + } } }; @@ -1665,7 +1769,7 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> { RequiredMetadataDirective::find(&inline_frag.directives).cloned(); self.build_reader_client_edge( context, - client_edge_metadata, + &client_edge_metadata, required_metadata, ) } diff --git a/packages/react-relay/__tests__/RelayResolverNullableModelClientEdge-test.js b/packages/react-relay/__tests__/RelayResolverNullableModelClientEdge-test.js index 299fc0bf67363..ac16fecbe050a 100644 --- a/packages/react-relay/__tests__/RelayResolverNullableModelClientEdge-test.js +++ b/packages/react-relay/__tests__/RelayResolverNullableModelClientEdge-test.js @@ -190,8 +190,7 @@ describe.each([ , ); - // TODO: T162471299 this should be 'Todo was null' - expect(renderer.toJSON()).toEqual('Todo was not null or undefined'); + expect(renderer.toJSON()).toEqual('Todo was null'); }); test('client edge to ID with no corresponding weak object', () => { @@ -255,8 +254,7 @@ describe.each([ , ); - // TODO: T162471299 this should be 'strong model was null' - expect(renderer.toJSON()).toEqual('strong model was not null or undefined'); + expect(renderer.toJSON()).toEqual('strong model was null'); }); test('client edge to server ID with no corresponding server object', () => { diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithArgumentsQuery.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithArgumentsQuery.graphql.js index 4795f541a589d..9adecd2237d4c 100644 --- a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithArgumentsQuery.graphql.js +++ b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithArgumentsQuery.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<> + * @generated SignedSource<<11dae29226f182a675d4a100fbfdecc9>> * @flow * @lightSyntaxTransform * @nogrep @@ -105,6 +105,19 @@ return { { "kind": "ClientEdgeToClientObject", "concreteType": "TodoModel", + "modelResolver": { + "alias": null, + "args": (v1/*: any*/), + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel__id" + }, + "kind": "RelayLiveResolver", + "name": "todo_model", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__id.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').TodoModel, 'id', true), + "path": "todo_model.__relay_model_instance" + }, "backingField": { "alias": null, "args": (v1/*: any*/), diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithRootFragmentLegacyQuery.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithRootFragmentLegacyQuery.graphql.js index 8773644b7cbbb..ea30090d097f7 100644 --- a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithRootFragmentLegacyQuery.graphql.js +++ b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithRootFragmentLegacyQuery.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<<2aa80d619472545875baf64c512a3381>> + * @generated SignedSource<<44de462792fedee1553676a6702412fa>> * @flow * @lightSyntaxTransform * @nogrep @@ -84,6 +84,19 @@ return { { "kind": "ClientEdgeToClientObject", "concreteType": "TodoModel", + "modelResolver": { + "alias": null, + "args": (v1/*: any*/), + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel__id" + }, + "kind": "RelayLiveResolver", + "name": "todo_model", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__id.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').TodoModel, 'id', true), + "path": "todo_model.__relay_model_instance" + }, "backingField": { "alias": null, "args": (v1/*: any*/), diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithRootFragmentQuery.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithRootFragmentQuery.graphql.js index bde7fd74885be..acf96846d1d7e 100644 --- a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithRootFragmentQuery.graphql.js +++ b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithRootFragmentQuery.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<> + * @generated SignedSource<<8a29c4fb27515a6f9203143f1fff4c64>> * @flow * @lightSyntaxTransform * @nogrep @@ -84,6 +84,19 @@ return { { "kind": "ClientEdgeToClientObject", "concreteType": "TodoModel", + "modelResolver": { + "alias": null, + "args": (v1/*: any*/), + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel__id" + }, + "kind": "RelayLiveResolver", + "name": "todo_model", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__id.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').TodoModel, 'id', true), + "path": "todo_model.__relay_model_instance" + }, "backingField": { "alias": null, "args": (v1/*: any*/), diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestNullWeakClientEdgeQuery.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestNullWeakClientEdgeQuery.graphql.js index 132720d5587f6..ef4bb42b5e195 100644 --- a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestNullWeakClientEdgeQuery.graphql.js +++ b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestNullWeakClientEdgeQuery.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<> + * @generated SignedSource<<781b78955e6a157f0c515ddca9967c59>> * @flow * @lightSyntaxTransform * @nogrep @@ -105,6 +105,19 @@ return { { "kind": "ClientEdgeToClientObject", "concreteType": "TodoModel", + "modelResolver": { + "alias": null, + "args": (v1/*: any*/), + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel__id" + }, + "kind": "RelayLiveResolver", + "name": "todo_model", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__id.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').TodoModel, 'id', true), + "path": "todo_model.__relay_model_instance" + }, "backingField": { "alias": null, "args": (v1/*: any*/), diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestSuspendedWeakClientEdgeQuery.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestSuspendedWeakClientEdgeQuery.graphql.js index e09b193252912..be9b356a36fe2 100644 --- a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestSuspendedWeakClientEdgeQuery.graphql.js +++ b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestSuspendedWeakClientEdgeQuery.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<> + * @generated SignedSource<> * @flow * @lightSyntaxTransform * @nogrep @@ -106,6 +106,19 @@ return { { "kind": "ClientEdgeToClientObject", "concreteType": "TodoModel", + "modelResolver": { + "alias": null, + "args": (v1/*: any*/), + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel__id" + }, + "kind": "RelayLiveResolver", + "name": "todo_model", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__id.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').TodoModel, 'id', true), + "path": "todo_model.__relay_model_instance" + }, "backingField": { "alias": null, "args": (v1/*: any*/), diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoNullQuery.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoNullQuery.graphql.js index c5d7feb0ca0e9..25e6d34e11259 100644 --- a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoNullQuery.graphql.js +++ b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoNullQuery.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<<6bee1dc1952c6faf691f5addcb7012a1>> + * @generated SignedSource<<7998f440d9c074504a09dfd2f7921340>> * @flow * @lightSyntaxTransform * @nogrep @@ -68,6 +68,19 @@ return { { "kind": "ClientEdgeToClientObject", "concreteType": "TodoModel", + "modelResolver": { + "alias": null, + "args": null, + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel__id" + }, + "kind": "RelayLiveResolver", + "name": "todo_model_null", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__id.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').TodoModel, 'id', true), + "path": "todo_model_null.__relay_model_instance" + }, "backingField": { "alias": null, "args": null, diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoQuery.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoQuery.graphql.js index 427d93a2173b1..ced3e49b9b916 100644 --- a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoQuery.graphql.js +++ b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoQuery.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<> + * @generated SignedSource<<5a53d5a13001059accaa9aa637fdc422>> * @flow * @lightSyntaxTransform * @nogrep @@ -92,6 +92,19 @@ return { { "kind": "ClientEdgeToClientObject", "concreteType": "TodoModel", + "modelResolver": { + "alias": null, + "args": (v1/*: any*/), + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel__id" + }, + "kind": "RelayLiveResolver", + "name": "todo_model", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__id.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').TodoModel, 'id', true), + "path": "todo_model.__relay_model_instance" + }, "backingField": { "alias": null, "args": (v1/*: any*/), diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoWithInterfaceQuery.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoWithInterfaceQuery.graphql.js index c2534ecfda1c6..ab82aa5933e00 100644 --- a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoWithInterfaceQuery.graphql.js +++ b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoWithInterfaceQuery.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<> + * @generated SignedSource<<60cab30ed3539ce77f7cf9ac0e7f20db>> * @flow * @lightSyntaxTransform * @nogrep @@ -108,6 +108,19 @@ return { { "kind": "ClientEdgeToClientObject", "concreteType": "TodoModel", + "modelResolver": { + "alias": null, + "args": (v1/*: any*/), + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel__id" + }, + "kind": "RelayLiveResolver", + "name": "todo_model", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__id.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').TodoModel, 'id', true), + "path": "todo_model.__relay_model_instance" + }, "backingField": { "alias": null, "args": (v1/*: any*/), diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoWithPluralFieldQuery.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoWithPluralFieldQuery.graphql.js index a8ab8ebec57a6..89602a66de5f2 100644 --- a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoWithPluralFieldQuery.graphql.js +++ b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestTodoWithPluralFieldQuery.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<> + * @generated SignedSource<<4a137d3961b407fd539097f0a223da84>> * @flow * @lightSyntaxTransform * @nogrep @@ -92,6 +92,19 @@ return { { "kind": "ClientEdgeToClientObject", "concreteType": "TodoModel", + "modelResolver": { + "alias": null, + "args": (v1/*: any*/), + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel__id" + }, + "kind": "RelayLiveResolver", + "name": "todo_model", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__id.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').TodoModel, 'id', true), + "path": "todo_model.__relay_model_instance" + }, "backingField": { "alias": null, "args": (v1/*: any*/), diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_LiveModel_Query.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_LiveModel_Query.graphql.js index 7ec199dd8f0c2..30e1e9602e86f 100644 --- a/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_LiveModel_Query.graphql.js +++ b/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_LiveModel_Query.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<<7c49d4f0a9493239580668b7301b444e>> + * @generated SignedSource<<0da54bc9fc5eb62a2d2a1694f3f61226>> * @flow * @lightSyntaxTransform * @nogrep @@ -76,6 +76,19 @@ return { { "kind": "ClientEdgeToClientObject", "concreteType": "TodoModel", + "modelResolver": { + "alias": null, + "args": null, + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "TodoModel__id" + }, + "kind": "RelayLiveResolver", + "name": "edge_to_live_object_does_not_exist", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__id.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').TodoModel, 'id', true), + "path": "edge_to_live_object_does_not_exist.__relay_model_instance" + }, "backingField": { "alias": null, "args": null, diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_StrongModel_Query.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_StrongModel_Query.graphql.js index f13e1826caf95..8a6405355c1c8 100644 --- a/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_StrongModel_Query.graphql.js +++ b/packages/react-relay/__tests__/__generated__/RelayResolverNullableModelClientEdgeTest_StrongModel_Query.graphql.js @@ -6,7 +6,7 @@ * * @oncall relay * - * @generated SignedSource<> + * @generated SignedSource<> * @flow * @lightSyntaxTransform * @nogrep @@ -64,6 +64,19 @@ return { { "kind": "ClientEdgeToClientObject", "concreteType": "StrongModel", + "modelResolver": { + "alias": null, + "args": null, + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "StrongModel__id" + }, + "kind": "RelayResolver", + "name": "edge_to_strong_model_does_not_exist", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./StrongModel__id.graphql'), require('./../RelayResolverNullableModelClientEdge-test').StrongModel, 'id', true), + "path": "edge_to_strong_model_does_not_exist.__relay_model_instance" + }, "backingField": { "alias": null, "args": null, diff --git a/packages/relay-runtime/store/RelayReader.js b/packages/relay-runtime/store/RelayReader.js index eeea1047da335..0414ca6868aeb 100644 --- a/packages/relay-runtime/store/RelayReader.js +++ b/packages/relay-runtime/store/RelayReader.js @@ -512,8 +512,19 @@ class RelayReader { record: Record, data: SelectorData, ): mixed { - const {fragment} = field; const parentRecordID = RelayModernRecord.getDataID(record); + const result = this._readResolverFieldImpl(field, parentRecordID); + + const applicationName = field.alias ?? field.name; + data[applicationName] = result; + return result; + } + + _readResolverFieldImpl( + field: ReaderRelayResolver | ReaderRelayLiveResolver, + parentRecordID: DataID, + ): mixed { + const {fragment} = field; // Found when reading the resolver fragment, which can happen either when // evaluating the resolver and it calls readFragment, or when checking if the @@ -613,8 +624,6 @@ class RelayReader { updatedDataIDs, ); - const applicationName = field.alias ?? field.name; - data[applicationName] = result; return result; } @@ -749,6 +758,18 @@ class RelayReader { validClientEdgeResolverResponse.id, this._resolverCache, ); + if (field.modelResolver != null) { + const model = this._readResolverFieldImpl( + field.modelResolver, + storeID, + ); + if (model == null) { + // If the model resolver returns undefined, we should still return null + // to match GQL behavior. + data[applicationName] = null; + return null; + } + } this._clientEdgeTraversalPath.push(traversalPathSegment); const prevData = data[applicationName];