diff --git a/crates/graphql_artifact_generation/src/eager_reader_artifact.rs b/crates/graphql_artifact_generation/src/eager_reader_artifact.rs index 57dcd898..d7dee8d0 100644 --- a/crates/graphql_artifact_generation/src/eager_reader_artifact.rs +++ b/crates/graphql_artifact_generation/src/eager_reader_artifact.rs @@ -156,10 +156,10 @@ pub(crate) fn generate_eager_reader_condition_artifact( reader_imports_to_import_statement(&reader_imports, file_extensions); let reader_param_type = "{ data: any, parameters: Record }"; - let reader_output_type = "boolean"; + let reader_output_type = "Link | null"; let reader_content = format!( - "import type {{ EagerReaderArtifact, ReaderAst }} from '@isograph/react';\n\ + "import type {{ EagerReaderArtifact, ReaderAst, Link }} from '@isograph/react';\n\ {reader_import_statement}\n\ const readerAst: ReaderAst<{reader_param_type}> = {reader_ast};\n\n\ const artifact: EagerReaderArtifact<\n\ @@ -167,7 +167,7 @@ pub(crate) fn generate_eager_reader_condition_artifact( {}{reader_output_type}\n\ > = {{\n\ {}kind: \"EagerReaderArtifact\",\n\ - {}resolver: ({{ data }}) => data.__typename === \"{concrete_type}\",\n\ + {}resolver: ({{ data }}) => data.__typename === \"{concrete_type}\" ? data.link : null,\n\ {}readerAst,\n\ }};\n\n\ export default artifact;\n", @@ -196,6 +196,7 @@ pub(crate) fn generate_eager_reader_param_type_artifact( let mut param_type_imports = BTreeSet::new(); let mut loadable_fields = BTreeSet::new(); + let mut link_fields = false; let client_field_parameter_type = generate_client_field_parameter_type( schema, client_field.selection_set_for_parent_query(), @@ -203,12 +204,19 @@ pub(crate) fn generate_eager_reader_param_type_artifact( &mut param_type_imports, &mut loadable_fields, 1, + &mut link_fields, ); let param_type_import_statement = param_type_imports_to_import_statement(¶m_type_imports, file_extensions); let reader_param_type = format!("{}__{}__param", parent_type.name, client_field.name); + let link_field_imports = if link_fields { + "import type { Link } from '@isograph/react';\n".to_string() + } else { + "".to_string() + }; + let loadable_field_imports = if !loadable_fields.is_empty() { let param_imports = param_type_imports_to_import_param_statement(&loadable_fields, file_extensions); @@ -234,6 +242,7 @@ pub(crate) fn generate_eager_reader_param_type_artifact( let indent = " "; let param_type_content = format!( "{param_type_import_statement}\ + {link_field_imports}\ {loadable_field_imports}\ {parameters_import}\n\ export type {reader_param_type} = {{\n\ diff --git a/crates/graphql_artifact_generation/src/generate_artifacts.rs b/crates/graphql_artifact_generation/src/generate_artifacts.rs index 9269d83a..4db9e470 100644 --- a/crates/graphql_artifact_generation/src/generate_artifacts.rs +++ b/crates/graphql_artifact_generation/src/generate_artifacts.rs @@ -36,7 +36,7 @@ use crate::{ generate_entrypoint_artifacts_with_client_field_traversal_result, }, format_parameter_type::format_parameter_type, - import_statements::ParamTypeImports, + import_statements::{LinkImports, ParamTypeImports}, iso_overload_file::build_iso_overload_artifact, refetch_reader_artifact::{ generate_refetch_output_type_artifact, generate_refetch_reader_artifact, @@ -131,8 +131,9 @@ pub fn get_artifact_path_and_content( } FieldType::ClientField(encountered_client_field_id) => { let encountered_client_field = schema.client_field(*encountered_client_field_id); - // Generate reader ASTs for all encountered client fields, which may be reader or refetch reader + match &encountered_client_field.variant { + ClientFieldVariant::Link => (), ClientFieldVariant::UserWritten(info) => { path_and_contents.extend(generate_eager_reader_artifacts( schema, @@ -248,6 +249,7 @@ pub fn get_artifact_path_and_content( for user_written_client_field in schema.client_fields.iter().flat_map(|field| match field { ClientType::ClientField(field) => match field.variant { + ClientFieldVariant::Link => None, ClientFieldVariant::UserWritten(_) => Some(field), ClientFieldVariant::ImperativelyLoadedField(_) => None, }, @@ -283,20 +285,25 @@ pub fn get_artifact_path_and_content( for output_type_id in encountered_output_types { let client_field = schema.client_field(output_type_id); - let path_and_content = match client_field.variant { - ClientFieldVariant::UserWritten(info) => generate_eager_reader_output_type_artifact( - schema, - client_field, - project_root, - artifact_directory, - info, - file_extensions, - ), + let artifact_path_and_content = match client_field.variant { + ClientFieldVariant::Link => None, + ClientFieldVariant::UserWritten(info) => { + Some(generate_eager_reader_output_type_artifact( + schema, + client_field, + project_root, + artifact_directory, + info, + file_extensions, + )) + } ClientFieldVariant::ImperativelyLoadedField(_) => { - generate_refetch_output_type_artifact(schema, client_field) + Some(generate_refetch_output_type_artifact(schema, client_field)) } }; - path_and_contents.push(path_and_content); + if let Some(path_and_content) = artifact_path_and_content { + path_and_contents.push(path_and_content); + }; } path_and_contents.push(build_iso_overload_artifact( @@ -403,6 +410,7 @@ pub(crate) fn get_serialized_field_arguments( pub(crate) fn generate_output_type(client_field: &ValidatedClientField) -> ClientFieldOutputType { let variant = &client_field.variant; match variant { + ClientFieldVariant::Link => ClientFieldOutputType("Link".to_string()), ClientFieldVariant::UserWritten(info) => match info.user_written_component_variant { UserWrittenComponentVariant::Eager => { ClientFieldOutputType("ReturnType".to_string()) @@ -438,6 +446,7 @@ pub(crate) fn generate_client_field_parameter_type( nested_client_field_imports: &mut ParamTypeImports, loadable_fields: &mut ParamTypeImports, indentation_level: u8, + link_fields: &mut LinkImports, ) -> ClientFieldParameterType { // TODO use unwraps let mut client_field_parameter_type = "{\n".to_string(); @@ -450,6 +459,7 @@ pub(crate) fn generate_client_field_parameter_type( nested_client_field_imports, loadable_fields, indentation_level + 1, + link_fields, ); } client_field_parameter_type.push_str(&format!("{}}}", " ".repeat(indentation_level as usize))); @@ -457,6 +467,7 @@ pub(crate) fn generate_client_field_parameter_type( ClientFieldParameterType(client_field_parameter_type) } +#[allow(clippy::too_many_arguments)] fn write_param_type_from_selection( schema: &ValidatedSchema, query_type_declaration: &mut String, @@ -465,6 +476,7 @@ fn write_param_type_from_selection( nested_client_field_imports: &mut ParamTypeImports, loadable_fields: &mut ParamTypeImports, indentation_level: u8, + link_fields: &mut LinkImports, ) { match &selection.item { ServerFieldSelection::ScalarField(scalar_field_selection) => { @@ -516,55 +528,71 @@ fn write_param_type_from_selection( query_type_declaration .push_str(&" ".repeat(indentation_level as usize).to_string()); - nested_client_field_imports.insert(client_field.type_and_field); - let inner_output_type = format!( - "{}__output_type", - client_field.type_and_field.underscore_separated() - ); - - let output_type = match scalar_field_selection.associated_data.selection_variant - { - ValidatedIsographSelectionVariant::Regular => inner_output_type, - ValidatedIsographSelectionVariant::Loadable(_) => { - loadable_fields.insert(client_field.type_and_field); - let provided_arguments = get_provided_arguments( - client_field.variable_definitions.iter().map(|x| &x.item), - &scalar_field_selection.arguments, + match client_field.variant { + ClientFieldVariant::Link => { + *link_fields = true; + let output_type = "Link"; + query_type_declaration.push_str( + &(format!( + "readonly {}: {},\n", + scalar_field_selection.name_or_alias().item, + output_type + )), ); + } + ClientFieldVariant::UserWritten(_) + | ClientFieldVariant::ImperativelyLoadedField(_) => { + nested_client_field_imports.insert(client_field.type_and_field); + let inner_output_type = format!( + "{}__output_type", + client_field.type_and_field.underscore_separated() + ); + let output_type = match scalar_field_selection + .associated_data + .selection_variant + { + ValidatedIsographSelectionVariant::Regular => inner_output_type, + ValidatedIsographSelectionVariant::Loadable(_) => { + loadable_fields.insert(client_field.type_and_field); + let provided_arguments = get_provided_arguments( + client_field.variable_definitions.iter().map(|x| &x.item), + &scalar_field_selection.arguments, + ); - let indent = " ".repeat((indentation_level + 1) as usize); - let provided_args_type = if provided_arguments.is_empty() { - "".to_string() - } else { - format!( - ",\n{indent}Omit, keyof {}>", - client_field.type_and_field.underscore_separated(), - get_loadable_field_type_from_arguments( - schema, - provided_arguments + let indent = " ".repeat((indentation_level + 1) as usize); + let provided_args_type = if provided_arguments.is_empty() { + "".to_string() + } else { + format!( + ",\n{indent}Omit, keyof {}>", + client_field.type_and_field.underscore_separated(), + get_loadable_field_type_from_arguments( + schema, + provided_arguments + ) + ) + }; + + format!( + "LoadableField<\n\ + {indent}{}__param,\n\ + {indent}{inner_output_type}\ + {provided_args_type}\n\ + {}>", + client_field.type_and_field.underscore_separated(), + " ".repeat(indentation_level as usize), ) - ) + } }; - - format!( - "LoadableField<\n\ - {indent}{}__param,\n\ - {indent}{inner_output_type}\ - {provided_args_type}\n\ - {}>", - client_field.type_and_field.underscore_separated(), - " ".repeat(indentation_level as usize), - ) + query_type_declaration.push_str( + &(format!( + "readonly {}: {},\n", + scalar_field_selection.name_or_alias().item, + output_type + )), + ); } - }; - - query_type_declaration.push_str( - &(format!( - "readonly {}: {},\n", - scalar_field_selection.name_or_alias().item, - output_type - )), - ); + } } } } @@ -603,6 +631,7 @@ fn write_param_type_from_selection( nested_client_field_imports, loadable_fields, indentation_level, + link_fields, ) }), }; diff --git a/crates/graphql_artifact_generation/src/import_statements.rs b/crates/graphql_artifact_generation/src/import_statements.rs index 176feaf8..36c75958 100644 --- a/crates/graphql_artifact_generation/src/import_statements.rs +++ b/crates/graphql_artifact_generation/src/import_statements.rs @@ -22,6 +22,7 @@ impl ImportedFileCategory { pub(crate) type ReaderImports = BTreeSet<(ObjectTypeAndFieldName, ImportedFileCategory)>; pub(crate) type ParamTypeImports = BTreeSet; +pub(crate) type LinkImports = bool; pub(crate) fn reader_imports_to_import_statement( reader_imports: &ReaderImports, diff --git a/crates/graphql_artifact_generation/src/iso_overload_file.rs b/crates/graphql_artifact_generation/src/iso_overload_file.rs index 85d347c0..3a04edb9 100644 --- a/crates/graphql_artifact_generation/src/iso_overload_file.rs +++ b/crates/graphql_artifact_generation/src/iso_overload_file.rs @@ -263,6 +263,7 @@ fn user_written_fields( .iter() .filter_map(|client_field| match client_field { ClientType::ClientField(client_field) => match client_field.variant { + ClientFieldVariant::Link => None, ClientFieldVariant::UserWritten(info) => { Some((client_field, info.user_written_component_variant)) } diff --git a/crates/graphql_artifact_generation/src/reader_ast.rs b/crates/graphql_artifact_generation/src/reader_ast.rs index 789f1c28..076413a9 100644 --- a/crates/graphql_artifact_generation/src/reader_ast.rs +++ b/crates/graphql_artifact_generation/src/reader_ast.rs @@ -5,9 +5,9 @@ use isograph_lang_types::{ LoadableDirectiveParameters, RefetchQueryIndex, SelectionType, ServerFieldSelection, }; use isograph_schema::{ - categorize_field_loadability, transform_arguments_with_child_context, FieldType, Loadability, - NameAndArguments, NormalizationKey, ObjectTypeAndFieldName, PathToRefetchField, - RefetchedPathsMap, SchemaServerFieldVariant, ValidatedClientField, + categorize_field_loadability, transform_arguments_with_child_context, ClientFieldVariant, + FieldType, Loadability, NameAndArguments, NormalizationKey, ObjectTypeAndFieldName, + PathToRefetchField, RefetchedPathsMap, SchemaServerFieldVariant, ValidatedClientField, ValidatedIsographSelectionVariant, ValidatedLinkedFieldSelection, ValidatedScalarFieldSelection, ValidatedSchema, ValidatedSelection, VariableContext, }; @@ -204,20 +204,43 @@ fn scalar_client_defined_field_ast_node( indentation_level, scalar_field_selection, ), - None => user_written_variant_ast_node( - scalar_field_selection, - indentation_level, - client_field, - schema, - path, - root_refetched_paths, - reader_imports, - &client_field_variable_context, - parent_variable_context, - ), + None => match client_field.variant { + ClientFieldVariant::Link => { + link_variant_ast_node(scalar_field_selection, indentation_level) + } + ClientFieldVariant::UserWritten(_) | ClientFieldVariant::ImperativelyLoadedField(_) => { + user_written_variant_ast_node( + scalar_field_selection, + indentation_level, + client_field, + schema, + path, + root_refetched_paths, + reader_imports, + &client_field_variable_context, + parent_variable_context, + ) + } + }, } } +fn link_variant_ast_node( + scalar_field_selection: &ValidatedScalarFieldSelection, + indentation_level: u8, +) -> String { + let alias = scalar_field_selection.name_or_alias().item; + let indent_1 = " ".repeat(indentation_level as usize); + let indent_2 = " ".repeat((indentation_level + 1) as usize); + + format!( + "{indent_1}{{\n\ + {indent_2}kind: \"Link\",\n\ + {indent_2}alias: \"{alias}\",\n\ + {indent_1}}},\n", + ) +} + #[allow(clippy::too_many_arguments)] fn user_written_variant_ast_node( scalar_field_selection: &ValidatedScalarFieldSelection, diff --git a/crates/isograph_compiler/src/source_files.rs b/crates/isograph_compiler/src/source_files.rs index aad3c023..f96f9b95 100644 --- a/crates/isograph_compiler/src/source_files.rs +++ b/crates/isograph_compiler/src/source_files.rs @@ -67,6 +67,7 @@ impl SourceFiles { process_iso_literals(schema, self.contains_iso)?; process_exposed_fields(schema)?; schema.add_fields_to_subtypes(&outcome.type_refinement_maps.supertype_to_subtype_map)?; + schema.add_link_fields()?; schema .add_pointers_to_supertypes(&outcome.type_refinement_maps.subtype_to_supertype_map)?; add_refetch_fields_to_objects(schema)?; diff --git a/crates/isograph_schema/src/add_link_fields.rs b/crates/isograph_schema/src/add_link_fields.rs new file mode 100644 index 00000000..74ba9c44 --- /dev/null +++ b/crates/isograph_schema/src/add_link_fields.rs @@ -0,0 +1,52 @@ +use crate::{ + ClientField, ClientFieldVariant, ClientType, FieldType, ObjectTypeAndFieldName, + ProcessTypeDefinitionError, ProcessTypeDefinitionResult, UnvalidatedSchema, LINK_FIELD_NAME, +}; +use common_lang_types::{Location, WithLocation}; +use intern::string_key::Intern; + +impl UnvalidatedSchema { + pub fn add_link_fields(&mut self) -> ProcessTypeDefinitionResult<()> { + for object in &mut self.server_field_data.server_objects { + let field_name = (*LINK_FIELD_NAME).into(); + let next_client_field_id = self.client_fields.len().into(); + self.client_fields + .push(ClientType::ClientField(ClientField { + description: Some( + format!("A store Link for the {} type.", object.name) + .intern() + .into(), + ), + id: next_client_field_id, + name: field_name, + parent_object_id: object.id, + variable_definitions: vec![], + reader_selection_set: Some(vec![]), + variant: ClientFieldVariant::Link, + type_and_field: ObjectTypeAndFieldName { + field_name, + type_name: object.name, + }, + refetch_strategy: None, + })); + + if object + .encountered_fields + .insert( + field_name, + FieldType::ClientField(ClientType::ClientField(next_client_field_id)), + ) + .is_some() + { + return Err(WithLocation::new( + ProcessTypeDefinitionError::FieldExistsOnType { + field_name, + parent_type: object.name, + }, + Location::generated(), + )); + } + } + Ok(()) + } +} diff --git a/crates/isograph_schema/src/add_pointers_to_supertypes.rs b/crates/isograph_schema/src/add_pointers_to_supertypes.rs index abe3cdc8..f5a12f9a 100644 --- a/crates/isograph_schema/src/add_pointers_to_supertypes.rs +++ b/crates/isograph_schema/src/add_pointers_to_supertypes.rs @@ -4,11 +4,11 @@ use intern::string_key::Intern; use isograph_lang_types::{ScalarFieldSelection, ServerFieldSelection}; use crate::{ - FieldType, ProcessTypeDefinitionError, ProcessTypeDefinitionResult, SchemaObject, + ClientType, FieldType, ProcessTypeDefinitionError, ProcessTypeDefinitionResult, SchemaObject, SchemaServerField, SchemaServerFieldVariant, ServerFieldTypeAssociatedData, ServerFieldTypeAssociatedDataInlineFragment, UnvalidatedSchema, ValidatedIsographSelectionVariant, ValidatedScalarFieldAssociatedData, - ValidatedTypeRefinementMap, + ValidatedTypeRefinementMap, LINK_FIELD_NAME, }; use common_lang_types::Location; impl UnvalidatedSchema { @@ -56,7 +56,31 @@ impl UnvalidatedSchema { Span::todo_generated(), ); - let condition_selection_set = vec![typename_selection]; + let link_selection = WithSpan::new( + ServerFieldSelection::ScalarField(ScalarFieldSelection { + arguments: vec![], + associated_data: ValidatedScalarFieldAssociatedData { + location: FieldType::ClientField( + match *subtype + .encountered_fields + .get(&(*LINK_FIELD_NAME).into()) + .expect("Expected link to exist") + .as_client_field() + .expect("Expected link to be client field") + { + ClientType::ClientField(client_field_id) => client_field_id, + }, + ), + selection_variant: ValidatedIsographSelectionVariant::Regular, + }, + directives: vec![], + name: WithLocation::new(*LINK_FIELD_NAME, Location::generated()), + reader_alias: None, + }), + Span::todo_generated(), + ); + + let condition_selection_set = vec![typename_selection, link_selection]; let server_field = SchemaServerField { description: Some( diff --git a/crates/isograph_schema/src/create_merged_selection_set.rs b/crates/isograph_schema/src/create_merged_selection_set.rs index 44bc3b19..615756e2 100644 --- a/crates/isograph_schema/src/create_merged_selection_set.rs +++ b/crates/isograph_schema/src/create_merged_selection_set.rs @@ -18,7 +18,7 @@ use lazy_static::lazy_static; use crate::{ categorize_field_loadability, create_transformed_name_and_arguments, expose_field_directive::RequiresRefinement, transform_arguments_with_child_context, - transform_name_and_arguments_with_child_variable_context, FieldType, + transform_name_and_arguments_with_child_variable_context, ClientFieldVariant, FieldType, ImperativelyLoadedFieldVariant, Loadability, NameAndArguments, PathToRefetchField, RootOperationName, SchemaObject, SchemaServerFieldVariant, UnvalidatedVariableDefinition, ValidatedClientField, ValidatedIsographSelectionVariant, ValidatedScalarFieldSelection, @@ -44,6 +44,7 @@ lazy_static! { pub static ref REFETCH_FIELD_NAME: ScalarFieldName = "__refetch".intern().into(); pub static ref NODE_FIELD_NAME: LinkedFieldName = "node".intern().into(); pub static ref TYPENAME_FIELD_NAME: ScalarFieldName = "__typename".intern().into(); + pub static ref LINK_FIELD_NAME: ScalarFieldName = "link".intern().into(); } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -713,16 +714,22 @@ fn merge_validated_selections_into_selection_map( variant, ); } - None => merge_non_loadable_scalar_client_field( - parent_type, - schema, - parent_map, - merge_traversal_state, - newly_encountered_scalar_client_field, - encountered_client_field_map, - variable_context, - &scalar_field_selection.arguments, - ), + None => match newly_encountered_scalar_client_field.variant { + ClientFieldVariant::Link => {} + ClientFieldVariant::ImperativelyLoadedField(_) + | ClientFieldVariant::UserWritten(_) => { + merge_non_loadable_scalar_client_field( + parent_type, + schema, + parent_map, + merge_traversal_state, + newly_encountered_scalar_client_field, + encountered_client_field_map, + variable_context, + &scalar_field_selection.arguments, + ) + } + }, } merge_traversal_state diff --git a/crates/isograph_schema/src/lib.rs b/crates/isograph_schema/src/lib.rs index 9367d168..909a25cb 100644 --- a/crates/isograph_schema/src/lib.rs +++ b/crates/isograph_schema/src/lib.rs @@ -1,5 +1,6 @@ mod accessible_client_fields_iterator; mod add_fields_to_subtypes; +mod add_link_fields; mod add_pointers_to_supertypes; mod argument_map; mod create_merged_selection_set; diff --git a/crates/isograph_schema/src/process_client_field_declaration.rs b/crates/isograph_schema/src/process_client_field_declaration.rs index 5eb80815..f75ed121 100644 --- a/crates/isograph_schema/src/process_client_field_declaration.rs +++ b/crates/isograph_schema/src/process_client_field_declaration.rs @@ -199,6 +199,7 @@ pub struct UserWrittenClientFieldInfo { pub enum ClientFieldVariant { UserWritten(UserWrittenClientFieldInfo), ImperativelyLoadedField(ImperativelyLoadedFieldVariant), + Link, } lazy_static! { diff --git a/crates/isograph_schema/src/validate_schema.rs b/crates/isograph_schema/src/validate_schema.rs index 3cad920c..061ce057 100644 --- a/crates/isograph_schema/src/validate_schema.rs +++ b/crates/isograph_schema/src/validate_schema.rs @@ -331,6 +331,7 @@ pub fn categorize_field_loadability<'a>( selection_variant: &'a ValidatedIsographSelectionVariant, ) -> Option> { match &client_field.variant { + ClientFieldVariant::Link => None, ClientFieldVariant::UserWritten(_) => match selection_variant { ValidatedIsographSelectionVariant::Regular => None, ValidatedIsographSelectionVariant::Loadable((l, _)) => { diff --git a/demos/github-demo/src/isograph-components/__isograph/User/asUser/resolver_reader.ts b/demos/github-demo/src/isograph-components/__isograph/User/asUser/resolver_reader.ts index aa9b966b..6c7cc03e 100644 --- a/demos/github-demo/src/isograph-components/__isograph/User/asUser/resolver_reader.ts +++ b/demos/github-demo/src/isograph-components/__isograph/User/asUser/resolver_reader.ts @@ -1,4 +1,4 @@ -import type { EagerReaderArtifact, ReaderAst } from '@isograph/react'; +import type { EagerReaderArtifact, ReaderAst, Link } from '@isograph/react'; const readerAst: ReaderAst<{ data: any, parameters: Record }> = [ { @@ -7,14 +7,18 @@ const readerAst: ReaderAst<{ data: any, parameters: Record } alias: null, arguments: null, }, + { + kind: "Link", + alias: "link", + }, ]; const artifact: EagerReaderArtifact< { data: any, parameters: Record }, - boolean + Link | null > = { kind: "EagerReaderArtifact", - resolver: ({ data }) => data.__typename === "User", + resolver: ({ data }) => data.__typename === "User" ? data.link : null, readerAst, }; diff --git a/demos/pet-demo/src/components/__isograph/AdItem/asAdItem/resolver_reader.ts b/demos/pet-demo/src/components/__isograph/AdItem/asAdItem/resolver_reader.ts index 2000168f..7562bd54 100644 --- a/demos/pet-demo/src/components/__isograph/AdItem/asAdItem/resolver_reader.ts +++ b/demos/pet-demo/src/components/__isograph/AdItem/asAdItem/resolver_reader.ts @@ -1,4 +1,4 @@ -import type { EagerReaderArtifact, ReaderAst } from '@isograph/react'; +import type { EagerReaderArtifact, ReaderAst, Link } from '@isograph/react'; const readerAst: ReaderAst<{ data: any, parameters: Record }> = [ { @@ -7,14 +7,18 @@ const readerAst: ReaderAst<{ data: any, parameters: Record } alias: null, arguments: null, }, + { + kind: "Link", + alias: "link", + }, ]; const artifact: EagerReaderArtifact< { data: any, parameters: Record }, - boolean + Link | null > = { kind: "EagerReaderArtifact", - resolver: ({ data }) => data.__typename === "AdItem", + resolver: ({ data }) => data.__typename === "AdItem" ? data.link : null, readerAst, }; diff --git a/demos/pet-demo/src/components/__isograph/BlogItem/asBlogItem/resolver_reader.ts b/demos/pet-demo/src/components/__isograph/BlogItem/asBlogItem/resolver_reader.ts index f6554874..5bb1d696 100644 --- a/demos/pet-demo/src/components/__isograph/BlogItem/asBlogItem/resolver_reader.ts +++ b/demos/pet-demo/src/components/__isograph/BlogItem/asBlogItem/resolver_reader.ts @@ -1,4 +1,4 @@ -import type { EagerReaderArtifact, ReaderAst } from '@isograph/react'; +import type { EagerReaderArtifact, ReaderAst, Link } from '@isograph/react'; const readerAst: ReaderAst<{ data: any, parameters: Record }> = [ { @@ -7,14 +7,18 @@ const readerAst: ReaderAst<{ data: any, parameters: Record } alias: null, arguments: null, }, + { + kind: "Link", + alias: "link", + }, ]; const artifact: EagerReaderArtifact< { data: any, parameters: Record }, - boolean + Link | null > = { kind: "EagerReaderArtifact", - resolver: ({ data }) => data.__typename === "BlogItem", + resolver: ({ data }) => data.__typename === "BlogItem" ? data.link : null, readerAst, }; diff --git a/libs/isograph-react/src/core/areEqualWithDeepComparison.ts b/libs/isograph-react/src/core/areEqualWithDeepComparison.ts index 2a4bb557..8574cbf0 100644 --- a/libs/isograph-react/src/core/areEqualWithDeepComparison.ts +++ b/libs/isograph-react/src/core/areEqualWithDeepComparison.ts @@ -1,3 +1,4 @@ +import type { Link } from './IsographEnvironment'; import type { ReaderAst, ReaderLinkedField, ReaderScalarField } from './reader'; export function mergeUsingReaderAst( field: ReaderScalarField | ReaderLinkedField, @@ -96,6 +97,24 @@ export function mergeObjectsUsingReaderAst( } break; } + case 'Link': { + const key = field.alias; + // @ts-expect-error + const oldValue: Link = oldItemObject[key]; + // @ts-expect-error + const newValue: Link = newItemObject[key]; + + if ( + oldValue.__link !== newValue.__link || + oldValue.__typename !== newValue.__typename + ) { + canRecycle = false; + } else { + // @ts-expect-error + newItemObject[key] = oldValue; + } + break; + } case 'ImperativelyLoadedField': case 'LoadablySelectedField': break; diff --git a/libs/isograph-react/src/core/read.ts b/libs/isograph-react/src/core/read.ts index 2ccb9ecf..78d17ce7 100644 --- a/libs/isograph-react/src/core/read.ts +++ b/libs/isograph-react/src/core/read.ts @@ -164,6 +164,10 @@ function readData( target[field.alias ?? field.fieldName] = value; break; } + case 'Link': { + target[field.alias] = root; + break; + } case 'Linked': { const storeRecordName = getParentRecordKey(field, variables); const value = storeRecord[storeRecordName]; @@ -610,6 +614,7 @@ function readData( } break; } + default: { // Ensure we have covered all variants let _: never = field; diff --git a/libs/isograph-react/src/core/reader.ts b/libs/isograph-react/src/core/reader.ts index 41ff0abd..8d087ba7 100644 --- a/libs/isograph-react/src/core/reader.ts +++ b/libs/isograph-react/src/core/reader.ts @@ -79,7 +79,8 @@ export type ReaderAstNode = | ReaderLinkedField | ReaderNonLoadableResolverField | ReaderImperativelyLoadedField - | ReaderLoadableField; + | ReaderLoadableField + | ReaderLinkeField; // @ts-ignore export type ReaderAst = ReadonlyArray; @@ -90,6 +91,12 @@ export type ReaderScalarField = { readonly alias: string | null; readonly arguments: Arguments | null; }; + +export type ReaderLinkeField = { + readonly kind: 'Link'; + readonly alias: string; +}; + export type ReaderLinkedField = { readonly kind: 'Linked'; readonly fieldName: string;