diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index c2af59794..17641af42 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -27,9 +27,25 @@ jobs: runs-on: ${{ inputs.os }} steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - name: Cache turbo + uses: actions/cache@v4 with: - toolchain: 1.75.0 + path: .turbo + key: ${{ runner.os }}-turbo-${{ inputs.artifact-name }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ inputs.artifact-name }}- + - uses: pnpm/action-setup@v2 + - name: Use Node.js 22 + uses: actions/setup-node@v3 + with: + node-version: 22 + cache: 'pnpm' + - name: Install dependencies + run: pnpm install --frozen-lockfile --ignore-scripts + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable override: true target: ${{ inputs.target }} # more info here:- https://github.com/rust-lang/cargo/issues/13020 @@ -43,7 +59,7 @@ jobs: command: install args: cross - name: 'Build isograph_cli with cargo (${{inputs.target}})' - run: ${{ inputs.cross && 'cross' || 'cargo' }} build --target ${{ inputs.target }} --release + run: pnpm exec turbo ${{ inputs.cross && 'cross' || 'build' }} -- --target ${{ inputs.target }} --release - uses: actions/upload-artifact@v4 with: name: ${{ inputs.artifact-name }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b300e0fb3..6240b4f08 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,11 +61,6 @@ jobs: - folder: vite-demo steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.75.0 - override: true - target: x86_64-apple-darwin - name: 'Download cli binary' uses: actions/download-artifact@v4 with: @@ -78,6 +73,12 @@ jobs: - name: 'Check working directory status' run: './scripts/check-git-status.sh' + build-json-schema: + name: Build json schema + uses: ./.github/workflows/run-cargo-bin-and-ensure-no-changes.yml + with: + binary: build_json_schema + typecheck-demos: name: Typecheck and Lint Demos runs-on: ubuntu-latest @@ -181,9 +182,8 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install Rust - uses: actions-rs/toolchain@v1 + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - profile: minimal toolchain: stable components: rustfmt - name: Run cargo fmt @@ -199,22 +199,29 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install Rust - uses: actions-rs/toolchain@v1 + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - profile: minimal toolchain: stable components: rustfmt - name: Run cargo test run: cargo test + build-fixtures: + name: Build fixtures + uses: ./.github/workflows/run-cargo-bin-and-ensure-no-changes.yml + with: + binary: generate_isograph_fixtures + all-checks-passed: name: All checks passed runs-on: ubuntu-latest needs: [ + build-fixtures, build-js-packages, build-cli, build-cli-linux, + build-json-schema, build-demos, build-website, prettier, diff --git a/.github/workflows/run-cargo-bin-and-ensure-no-changes.yml b/.github/workflows/run-cargo-bin-and-ensure-no-changes.yml new file mode 100644 index 000000000..2c55a5eca --- /dev/null +++ b/.github/workflows/run-cargo-bin-and-ensure-no-changes.yml @@ -0,0 +1,27 @@ +on: + workflow_call: + inputs: + binary: + required: true + type: string + +jobs: + build-and-run-binary: + name: Run ${{ inputs.binary }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + override: true + target: x86_64-unknown-linux-musl + - name: Build ${{ inputs.binary }} with cargo + run: cargo build --bin ${{ inputs.binary }} --target x86_64-unknown-linux-musl --release + - name: Make artifact executable + run: chmod +x ./target/x86_64-unknown-linux-musl/release/${{ inputs.binary }} + - name: Run ${{ inputs.binary }} + run: ./target/x86_64-unknown-linux-musl/release/${{ inputs.binary }} + - name: Check working directory status + run: './scripts/check-git-status.sh' diff --git a/.gitignore b/.gitignore index e4a185676..4ab861af6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,11 @@ **/.next/** **/next-env.d.ts **/*.d.ts +**/.DS_Store # the built binary is downloaded into this folder during CI artifacts **/out/** + +.turbo \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index e26a78ac1..334fc5efe 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,3 +5,4 @@ **/.docusaurus/** **/dist/** **/relay-crates/** +isograph-config-schema.json diff --git a/Cargo.lock b/Cargo.lock index a2cb8ccfc..d0ec5f3e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -306,7 +306,7 @@ dependencies = [ [[package]] name = "common_lang_types" -version = "0.2.0" +version = "0.3.0" dependencies = [ "intern", "serde", @@ -395,6 +395,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "either" version = "1.13.0" @@ -535,7 +541,7 @@ dependencies = [ [[package]] name = "graphql_artifact_generation" -version = "0.2.0" +version = "0.3.0" dependencies = [ "common_lang_types", "graphql_lang_types", @@ -549,7 +555,7 @@ dependencies = [ [[package]] name = "graphql_lang_types" -version = "0.2.0" +version = "0.3.0" dependencies = [ "common_lang_types", "intern", @@ -560,7 +566,7 @@ dependencies = [ [[package]] name = "graphql_schema_parser" -version = "0.2.0" +version = "0.3.0" dependencies = [ "common_lang_types", "graphql-syntax", @@ -637,6 +643,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] @@ -724,7 +731,7 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "isograph_cli" -version = "0.2.0" +version = "0.3.0" dependencies = [ "clap 4.5.18", "colored", @@ -744,7 +751,7 @@ dependencies = [ [[package]] name = "isograph_compiler" -version = "0.2.0" +version = "0.3.0" dependencies = [ "colored", "common_lang_types", @@ -768,18 +775,23 @@ dependencies = [ [[package]] name = "isograph_config" -version = "0.2.0" +version = "0.3.0" dependencies = [ "colorize", "common_lang_types", + "schemars", "serde", "serde_json", "tracing", ] +[[package]] +name = "isograph_fixture_tests" +version = "0.3.0" + [[package]] name = "isograph_lang_parser" -version = "0.2.0" +version = "0.3.0" dependencies = [ "common_lang_types", "graphql_lang_types", @@ -791,7 +803,7 @@ dependencies = [ [[package]] name = "isograph_lang_types" -version = "0.2.0" +version = "0.3.0" dependencies = [ "common_lang_types", "graphql_lang_types", @@ -803,7 +815,7 @@ dependencies = [ [[package]] name = "isograph_lsp" -version = "0.2.0" +version = "0.3.0" dependencies = [ "common_lang_types", "crossbeam", @@ -822,7 +834,7 @@ dependencies = [ [[package]] name = "isograph_schema" -version = "0.2.0" +version = "0.3.0" dependencies = [ "colorize", "common_lang_types", @@ -1304,6 +1316,31 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.72", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1339,6 +1376,17 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "serde_fmt" version = "1.0.3" @@ -1420,7 +1468,7 @@ dependencies = [ [[package]] name = "string_key_newtype" -version = "0.2.0" +version = "0.3.0" dependencies = [ "intern", ] @@ -1580,7 +1628,7 @@ dependencies = [ [[package]] name = "tests" -version = "0.2.0" +version = "0.3.0" dependencies = [ "colorize", "common_lang_types", @@ -1775,7 +1823,7 @@ dependencies = [ [[package]] name = "u32_newtypes" -version = "0.2.0" +version = "0.3.0" [[package]] name = "unicase" diff --git a/Cargo.toml b/Cargo.toml index a5bde2073..c919de703 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["./crates/*"] resolver = "2" [workspace.package] -version = "0.2.0" +version = "0.3.0" edition = "2021" license = "MIT" @@ -22,10 +22,11 @@ notify-debouncer-full = "0.4.0" pathdiff = "0.2.1" pretty-duration = "0.1.1" regex = "1.6.0" +schemars = { version="0.8.11", features=["indexmap1"] } serde = "1.0.197" serde_json = "1.0.108" -strum = { version="0.25.0", features=["derive"] } +strum = { version = "0.25.0", features = ["derive"] } thiserror = "1.0.40" -tokio = { version="1.35.0", features=["full"] } +tokio = { version = "1.35.0", features = ["full"] } tracing = "0.1" tracing-subscriber = "0.3" diff --git a/crates/common_lang_types/Cargo.toml b/crates/common_lang_types/Cargo.toml index df5b2f5da..f1ed500b5 100644 --- a/crates/common_lang_types/Cargo.toml +++ b/crates/common_lang_types/Cargo.toml @@ -8,4 +8,4 @@ license = { workspace = true } [dependencies] intern = { path = "../../relay-crates/intern" } string_key_newtype = { path = "../string_key_newtype" } -serde = { workspace = true } +serde = { workspace = true } diff --git a/crates/graphql_artifact_generation/Cargo.toml b/crates/graphql_artifact_generation/Cargo.toml index 77c59cf63..a5b4137d8 100644 --- a/crates/graphql_artifact_generation/Cargo.toml +++ b/crates/graphql_artifact_generation/Cargo.toml @@ -7,8 +7,8 @@ license = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -pathdiff = { workspace = true } -lazy_static = { workspace = true } +pathdiff = { workspace = true } +lazy_static = { workspace = true } isograph_schema = { path = "../isograph_schema" } isograph_config = { path = "../isograph_config" } isograph_lang_types = { path = "../isograph_lang_types" } diff --git a/crates/graphql_artifact_generation/src/eager_reader_artifact.rs b/crates/graphql_artifact_generation/src/eager_reader_artifact.rs index 57dcd8982..d7dee8d07 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/entrypoint_artifact.rs b/crates/graphql_artifact_generation/src/entrypoint_artifact.rs index 00406a80a..0eeff4726 100644 --- a/crates/graphql_artifact_generation/src/entrypoint_artifact.rs +++ b/crates/graphql_artifact_generation/src/entrypoint_artifact.rs @@ -147,7 +147,7 @@ pub(crate) fn generate_entrypoint_artifacts_with_client_field_traversal_result<' generate_refetch_query_artifact_import(&refetch_paths_with_variables, file_extensions); let normalization_ast_text = - generate_normalization_ast_text(schema, merged_selection_map.values(), 0); + generate_normalization_ast_text(schema, merged_selection_map.values(), 1); let concrete_type = schema.server_field_data.object( if schema @@ -249,7 +249,7 @@ fn generate_refetch_query_artifact_import( RefetchQueryArtifactImport(output) } -impl<'schema> EntrypointArtifactInfo<'schema> { +impl EntrypointArtifactInfo<'_> { fn path_and_content( self, file_extensions: GenerateFileExtensionsOption, @@ -294,7 +294,10 @@ impl<'schema> EntrypointArtifactInfo<'schema> { import readerResolver from './{resolver_reader_file_name}{ts_file_extension}';\n\ {refetch_query_artifact_import}\n\n\ const queryText = '{query_text}';\n\n\ - const normalizationAst: NormalizationAst = {normalization_ast_text};\n\ + const normalizationAst: NormalizationAst = {{\n\ + {}kind: \"NormalizationAst\",\n\ + {}selections: {normalization_ast_text},\n\ + }};\n\ const artifact: IsographEntrypoint<\n\ {}{entrypoint_params_typename},\n\ {}{entrypoint_output_type_name}\n\ @@ -313,7 +316,7 @@ impl<'schema> EntrypointArtifactInfo<'schema> { {}}},\n\ }};\n\n\ export default artifact;\n", - " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", + " "," "," ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", ) } } diff --git a/crates/graphql_artifact_generation/src/generate_artifacts.rs b/crates/graphql_artifact_generation/src/generate_artifacts.rs index 9269d83a4..1db43c6d4 100644 --- a/crates/graphql_artifact_generation/src/generate_artifacts.rs +++ b/crates/graphql_artifact_generation/src/generate_artifacts.rs @@ -7,7 +7,7 @@ use graphql_lang_types::{ }; use intern::{string_key::Intern, Lookup}; -use isograph_config::{GenerateFileExtensionsOption, OptionalValidationLevel}; +use isograph_config::GenerateFileExtensionsOption; use isograph_lang_types::{ ArgumentKeyAndValue, ClientFieldId, NonConstantValue, SelectableServerFieldId, SelectionType, ServerFieldSelection, TypeAnnotation, UnionVariant, VariableDefinition, @@ -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, @@ -79,7 +79,7 @@ pub fn get_artifact_path_and_content( project_root: &Path, artifact_directory: &Path, file_extensions: GenerateFileExtensionsOption, - on_missing_babel_transform: OptionalValidationLevel, + no_babel_transform: bool, ) -> Vec { let mut encountered_client_field_map = BTreeMap::new(); let mut path_and_contents = vec![]; @@ -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,26 +285,31 @@ 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( schema, file_extensions, - on_missing_babel_transform, + no_babel_transform, )); path_and_contents @@ -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/imperatively_loaded_fields.rs b/crates/graphql_artifact_generation/src/imperatively_loaded_fields.rs index 6f2de199e..f71f25435 100644 --- a/crates/graphql_artifact_generation/src/imperatively_loaded_fields.rs +++ b/crates/graphql_artifact_generation/src/imperatively_loaded_fields.rs @@ -54,7 +54,10 @@ impl ImperativelyLoadedEntrypointArtifactInfo { format!( "import type {{ IsographEntrypoint, ReaderAst, FragmentReference, NormalizationAst, RefetchQueryNormalizationArtifact }} from '@isograph/react';\n\ const queryText = '{query_text}';\n\n\ - const normalizationAst: NormalizationAst = {normalization_ast};\n\ + const normalizationAst: NormalizationAst = {{\n\ + {}kind: \"NormalizationAst\",\n\ + {}selections: {normalization_ast},\n\ + }};\n\ const artifact: RefetchQueryNormalizationArtifact = {{\n\ {}kind: \"RefetchQuery\",\n\ {}networkRequestInfo: {{\n\ @@ -72,6 +75,8 @@ impl ImperativelyLoadedEntrypointArtifactInfo { " ", " ", " ", + " ", + " ", ) } @@ -103,7 +108,7 @@ pub(crate) fn get_artifact_for_imperatively_loaded_field( ); let normalization_ast_text = - generate_normalization_ast_text(schema, merged_selection_set.values(), 0); + generate_normalization_ast_text(schema, merged_selection_set.values(), 1); ImperativelyLoadedEntrypointArtifactInfo { normalization_ast_text, diff --git a/crates/graphql_artifact_generation/src/import_statements.rs b/crates/graphql_artifact_generation/src/import_statements.rs index 176feaf8a..36c75958b 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 85d347c0c..5543e1aee 100644 --- a/crates/graphql_artifact_generation/src/iso_overload_file.rs +++ b/crates/graphql_artifact_generation/src/iso_overload_file.rs @@ -1,5 +1,5 @@ use intern::Lookup; -use isograph_config::{GenerateFileExtensionsOption, OptionalValidationLevel}; +use isograph_config::GenerateFileExtensionsOption; use std::{cmp::Ordering, path::PathBuf}; use common_lang_types::{ArtifactPathAndContent, SelectableFieldName}; @@ -13,29 +13,45 @@ use crate::generate_artifacts::ISO_TS; fn build_iso_overload_for_entrypoint( validated_client_field: &ValidatedClientField, file_extensions: GenerateFileExtensionsOption, -) -> (String, String) { - let mut s: String = "".to_string(); - let import = format!( - "import entrypoint_{} from '../__isograph/{}/{}/entrypoint{}';\n", - validated_client_field.type_and_field.underscore_separated(), - validated_client_field.type_and_field.type_name, - validated_client_field.type_and_field.field_name, - file_extensions.ts() - ); + no_babel_transform: bool, +) -> (Option, String) { let formatted_field = format!( "entrypoint {}.{}", validated_client_field.type_and_field.type_name, validated_client_field.type_and_field.field_name ); - s.push_str(&format!( - " + match no_babel_transform { + true => ( + None, + format!( + " +export function iso( + param: T & MatchesWhitespaceAndString<'{}', T> +): void;\n", + formatted_field + ), + ), + false => { + let mut s: String = "".to_string(); + let import = format!( + "import entrypoint_{} from '../__isograph/{}/{}/entrypoint{}';\n", + validated_client_field.type_and_field.underscore_separated(), + validated_client_field.type_and_field.type_name, + validated_client_field.type_and_field.field_name, + file_extensions.ts() + ); + + s.push_str(&format!( + " export function iso( param: T & MatchesWhitespaceAndString<'{}', T> ): typeof entrypoint_{};\n", - formatted_field, - validated_client_field.type_and_field.underscore_separated(), - )); - (import, s) + formatted_field, + validated_client_field.type_and_field.underscore_separated(), + )); + (Some(import), s) + } + } } fn build_iso_overload_for_client_defined_field( @@ -80,7 +96,7 @@ export function iso( pub(crate) fn build_iso_overload_artifact( schema: &ValidatedSchema, file_extensions: GenerateFileExtensionsOption, - on_missing_babel_transform: OptionalValidationLevel, + no_babel_transform: bool, ) -> ArtifactPathAndContent { let mut imports = "import type { IsographEntrypoint } from '@isograph/react';\n".to_string(); let mut content = String::from( @@ -144,9 +160,11 @@ type MatchesWhitespaceAndString< let entrypoint_overloads = sorted_entrypoints(schema) .into_iter() - .map(|field| build_iso_overload_for_entrypoint(field, file_extensions)); + .map(|field| build_iso_overload_for_entrypoint(field, file_extensions, no_babel_transform)); for (import, entrypoint_overload) in entrypoint_overloads { - imports.push_str(&import); + if let Some(import) = import { + imports.push_str(&import); + } content.push_str(&entrypoint_overload); } @@ -159,20 +177,14 @@ export function iso(_isographLiteralText: string): {\n", ); - content.push_str(match on_missing_babel_transform { - OptionalValidationLevel::Error => { + content.push_str(match no_babel_transform { + false => { " throw new Error('iso: Unexpected invocation at runtime. Either the Babel transform ' + 'was not set up, or it failed to identify this call site. Make sure it ' + - 'is being used verbatim as `iso`.');" - } - OptionalValidationLevel::Warn => { - - " console.warn('iso: Unexpected invocation at runtime. Either the Babel transform ' + - 'was not set up, or it failed to identify this call site. Make sure it ' + - 'is being used verbatim as `iso`.'); - return (clientFieldResolver: any) => clientFieldResolver;" + 'is being used verbatim as `iso`. If you cannot use the babel transform, ' + + 'set options.no_babel_transform to true in your Isograph config. ');" } - OptionalValidationLevel::Ignore => { + true => { " return (clientFieldResolver: any) => clientFieldResolver;" } }); @@ -263,6 +275,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 789f1c281..076413a9b 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/graphql_lang_types/Cargo.toml b/crates/graphql_lang_types/Cargo.toml index 298fc9955..93cc429fd 100644 --- a/crates/graphql_lang_types/Cargo.toml +++ b/crates/graphql_lang_types/Cargo.toml @@ -7,6 +7,6 @@ license = { workspace = true } [dependencies] intern = { path = "../../relay-crates/intern" } common_lang_types = { path = "../common_lang_types" } -strum = { version="0.25.0", features=["derive"] } -serde = { workspace = true } -thiserror = { workspace = true } +strum = { version = "0.25.0", features = ["derive"] } +serde = { workspace = true } +thiserror = { workspace = true } diff --git a/crates/graphql_schema_parser/Cargo.toml b/crates/graphql_schema_parser/Cargo.toml index 6ad464880..7807c3db7 100644 --- a/crates/graphql_schema_parser/Cargo.toml +++ b/crates/graphql_schema_parser/Cargo.toml @@ -9,5 +9,5 @@ graphql-syntax = { path = "../../relay-crates/graphql-syntax" } intern = { path = "../../relay-crates/intern" } graphql_lang_types = { path = "../graphql_lang_types" } common_lang_types = { path = "../common_lang_types" } -logos = { workspace = true } +logos = { workspace = true } thiserror = { workspace = true } diff --git a/crates/graphql_schema_parser/src/peekable_lexer.rs b/crates/graphql_schema_parser/src/peekable_lexer.rs index bfc2439aa..3b39d63e7 100644 --- a/crates/graphql_schema_parser/src/peekable_lexer.rs +++ b/crates/graphql_schema_parser/src/peekable_lexer.rs @@ -46,7 +46,7 @@ impl<'source> PeekableLexer<'source> { self.end_index_of_last_parsed_token = self.current.span.end; let span = self.lexer_span(); // TODO why does self.current = ... not work here? - return std::mem::replace(&mut self.current, WithSpan::new(kind, span)); + std::mem::replace(&mut self.current, WithSpan::new(kind, span)) } pub fn peek(&self) -> WithSpan { diff --git a/crates/isograph_cli/Cargo.toml b/crates/isograph_cli/Cargo.toml index 17a5b9ee5..7406fb702 100644 --- a/crates/isograph_cli/Cargo.toml +++ b/crates/isograph_cli/Cargo.toml @@ -7,7 +7,7 @@ license = { workspace = true } [dependencies] isograph_compiler = { path = "../isograph_compiler" } isograph_config = { path = "../isograph_config" } -isograph_lsp= { path = "../isograph_lsp"} +isograph_lsp = { path = "../isograph_lsp" } colored = { workspace = true } clap = { workspace = true } thiserror = { workspace = true } diff --git a/crates/isograph_compiler/src/batch_compile.rs b/crates/isograph_compiler/src/batch_compile.rs index 8a11ebeb0..dd2ee3c40 100644 --- a/crates/isograph_compiler/src/batch_compile.rs +++ b/crates/isograph_compiler/src/batch_compile.rs @@ -59,10 +59,7 @@ pub fn print_result( #[derive(Error, Debug)] pub enum BatchCompileError { #[error("Unable to load schema file at path {path:?}.\nReason: {message}")] - UnableToLoadSchema { - path: PathBuf, - message: std::io::Error, - }, + UnableToLoadSchema { path: PathBuf, message: String }, #[error("Attempted to load the graphql schema at the following path: {path:?}, but that is not a file.")] SchemaNotAFile { path: PathBuf }, @@ -74,13 +71,10 @@ pub enum BatchCompileError { ProjectRootNotADirectory { path: PathBuf }, #[error("Unable to read the file at the following path: {path:?}.\nReason: {message}")] - UnableToReadFile { - path: PathBuf, - message: std::io::Error, - }, + UnableToReadFile { path: PathBuf, message: String }, - #[error("Unable to traverse directory.\nReason: {0}")] - UnableToTraverseDirectory(#[from] std::io::Error), + #[error("Unable to traverse directory.\nReason: {message}")] + UnableToTraverseDirectory { message: String }, #[error("Unable to parse schema.\n\n{0}")] UnableToParseSchema(#[from] WithLocation), diff --git a/crates/isograph_compiler/src/compiler_state.rs b/crates/isograph_compiler/src/compiler_state.rs index 1095faed8..d6affede5 100644 --- a/crates/isograph_compiler/src/compiler_state.rs +++ b/crates/isograph_compiler/src/compiler_state.rs @@ -1,9 +1,7 @@ use std::path::PathBuf; use graphql_artifact_generation::get_artifact_path_and_content; -use isograph_config::{ - create_config, CompilerConfig, GenerateFileExtensionsOption, OptionalValidationLevel, -}; +use isograph_config::{create_config, CompilerConfig, GenerateFileExtensionsOption}; use isograph_schema::{Schema, UnvalidatedSchema}; use crate::{ @@ -85,7 +83,7 @@ impl CompilerState { source_files, &self.config, self.config.options.generate_file_extensions, - self.config.options.on_missing_babel_transform, + self.config.options.no_babel_transform, )?; Ok(CompilationStats { client_field_count: stats.client_field_count, @@ -102,7 +100,7 @@ impl CompilerState { source_files, &self.config, self.config.options.generate_file_extensions, - self.config.options.on_missing_babel_transform, + self.config.options.no_babel_transform, )?; Ok(CompilationStats { client_field_count: stats.client_field_count, @@ -121,7 +119,7 @@ impl CompilerState { source_files, &self.config, self.config.options.generate_file_extensions, - self.config.options.on_missing_babel_transform, + self.config.options.no_babel_transform, )?; Ok(CompilationStats { client_field_count: stats.client_field_count, @@ -152,7 +150,7 @@ pub fn validate_and_create_artifacts_from_source_files( source_files: SourceFiles, config: &CompilerConfig, file_extensions: GenerateFileExtensionsOption, - on_missing_babel_transform: OptionalValidationLevel, + no_babel_transform: bool, ) -> Result { // Create schema let mut unvalidated_schema = UnvalidatedSchema::new(); @@ -169,7 +167,7 @@ pub fn validate_and_create_artifacts_from_source_files( &config.project_root, &config.artifact_directory, file_extensions, - on_missing_babel_transform, + no_babel_transform, ); let total_artifacts_written = write_artifacts_to_disk(artifacts, &config.artifact_directory)?; diff --git a/crates/isograph_compiler/src/isograph_literals.rs b/crates/isograph_compiler/src/isograph_literals.rs index ef9dd78c8..2171b39f4 100644 --- a/crates/isograph_compiler/src/isograph_literals.rs +++ b/crates/isograph_compiler/src/isograph_literals.rs @@ -56,9 +56,9 @@ pub fn read_file( let path_2 = path.clone(); // N.B. we have previously ensured that path is a file - let contents = std::fs::read(&path).map_err(|message| BatchCompileError::UnableToReadFile { + let contents = std::fs::read(&path).map_err(|e| BatchCompileError::UnableToReadFile { path: path_2, - message, + message: e.to_string(), })?; let contents = std::str::from_utf8(&contents) @@ -80,7 +80,9 @@ fn read_dir_recursive(root_js_path: &Path) -> Result, BatchCompileE visit_dirs_skipping_isograph(root_js_path, &mut |dir_entry| { paths.push(dir_entry.path()); }) - .map_err(BatchCompileError::from)?; + .map_err(|e| BatchCompileError::UnableToTraverseDirectory { + message: e.to_string(), + })?; Ok(paths) } diff --git a/crates/isograph_compiler/src/schema.rs b/crates/isograph_compiler/src/schema.rs index 17ef38bf7..4b58d8fd1 100644 --- a/crates/isograph_compiler/src/schema.rs +++ b/crates/isograph_compiler/src/schema.rs @@ -9,9 +9,9 @@ pub(crate) fn read_schema_file(path: &PathBuf) -> Result Result Result, /// The relative path to the folder where the compiler should look for Isograph literals pub project_root: PathBuf, /// The relative path to the folder where the compiler should create artifacts @@ -94,8 +99,8 @@ struct ConfigFile { #[serde(default)] pub schema_extensions: Vec, - /// Various that are of lesser importance - #[serde(default = "Default::default")] + /// Various options of less importance + #[serde(default)] pub options: ConfigFileOptions, } @@ -112,7 +117,7 @@ pub fn create_config(config_location: PathBuf) -> CompilerConfig { }, }; - let config_parsed: ConfigFile = serde_json::from_str(&config_contents) + let config_parsed: IsographProjectConfig = serde_json::from_str(&config_contents) .unwrap_or_else(|e| panic!("Error parsing config. Error: {}", e)); let mut config = config_location.clone(); @@ -179,17 +184,17 @@ pub fn create_config(config_location: PathBuf) -> CompilerConfig { } } -#[derive(Deserialize, Default)] +#[derive(Deserialize, Default, JsonSchema)] #[serde(default, deny_unknown_fields)] -struct ConfigFileOptions { +pub struct ConfigFileOptions { on_invalid_id_type: ConfigFileOptionalValidationLevel, - on_missing_babel_transform: ConfigFileOptionalValidationLevel, + no_babel_transform: bool, include_file_extensions_in_import_statements: bool, } -#[derive(Deserialize, Debug, Clone, Copy)] +#[derive(Deserialize, Debug, Clone, Copy, JsonSchema)] #[serde(rename_all = "snake_case")] -enum ConfigFileOptionalValidationLevel { +pub enum ConfigFileOptionalValidationLevel { /// If this validation error is encountered, it will be ignored Ignore, /// If this validation error is encountered, a warning will be issued @@ -207,9 +212,7 @@ impl Default for ConfigFileOptionalValidationLevel { fn create_options(options: ConfigFileOptions) -> ConfigOptions { ConfigOptions { on_invalid_id_type: create_optional_validation_level(options.on_invalid_id_type), - on_missing_babel_transform: create_optional_validation_level( - options.on_missing_babel_transform, - ), + no_babel_transform: options.no_babel_transform, generate_file_extensions: create_generate_file_extensions( options.include_file_extensions_in_import_statements, ), diff --git a/crates/isograph_config/src/main.rs b/crates/isograph_config/src/main.rs new file mode 100644 index 000000000..b87ab1abf --- /dev/null +++ b/crates/isograph_config/src/main.rs @@ -0,0 +1,14 @@ +use std::fs; + +use isograph_config::IsographProjectConfig; +use schemars::schema_for; + +fn main() { + let schema = schema_for!(IsographProjectConfig); + + fs::write( + "./libs/isograph-compiler/isograph-config-schema.json", + serde_json::to_string_pretty(&schema).unwrap(), + ) + .unwrap(); +} diff --git a/crates/isograph_fixture_tests/Cargo.toml b/crates/isograph_fixture_tests/Cargo.toml new file mode 100644 index 000000000..181935b09 --- /dev/null +++ b/crates/isograph_fixture_tests/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "isograph_fixture_tests" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } + +[[bin]] +name = "generate_isograph_fixtures" +path = "src/main.rs" diff --git a/crates/isograph_fixture_tests/src/main.rs b/crates/isograph_fixture_tests/src/main.rs new file mode 100644 index 000000000..3993d9595 --- /dev/null +++ b/crates/isograph_fixture_tests/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Generate Isograph fixtures!") +} diff --git a/crates/isograph_lang_parser/Cargo.toml b/crates/isograph_lang_parser/Cargo.toml index 7e1afec96..298fe8f81 100644 --- a/crates/isograph_lang_parser/Cargo.toml +++ b/crates/isograph_lang_parser/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "isograph_lang_parser" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true} +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } [dependencies] common_lang_types = { path = "../common_lang_types" } diff --git a/crates/isograph_lang_parser/src/peekable_lexer.rs b/crates/isograph_lang_parser/src/peekable_lexer.rs index 313e4de48..22d42c394 100644 --- a/crates/isograph_lang_parser/src/peekable_lexer.rs +++ b/crates/isograph_lang_parser/src/peekable_lexer.rs @@ -47,7 +47,7 @@ impl<'source> PeekableLexer<'source> { self.end_index_of_last_parsed_token = self.current.span.end; let span = self.lexer_span(); // TODO why does self.current = ... not work here? - return std::mem::replace(&mut self.current, WithSpan::new(kind, span)); + std::mem::replace(&mut self.current, WithSpan::new(kind, span)) } pub fn peek(&self) -> WithSpan { diff --git a/crates/isograph_lang_types/Cargo.toml b/crates/isograph_lang_types/Cargo.toml index ed3829eee..12353721c 100644 --- a/crates/isograph_lang_types/Cargo.toml +++ b/crates/isograph_lang_types/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "isograph_lang_types" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true} +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } [dependencies] common_lang_types = { path = "../common_lang_types" } diff --git a/crates/isograph_lang_types/src/client_field_declaration.rs b/crates/isograph_lang_types/src/client_field_declaration.rs index 8310acadf..f8b4ce34c 100644 --- a/crates/isograph_lang_types/src/client_field_declaration.rs +++ b/crates/isograph_lang_types/src/client_field_declaration.rs @@ -295,15 +295,11 @@ pub fn reachable_variables( NonConstantValue::Variable(name) => { vec![WithLocation::new(*name, non_constant_value.location)] } - NonConstantValue::Object(object) => { - return object - .iter() - .flat_map(|pair| reachable_variables(&pair.value)) - .collect(); - } - NonConstantValue::List(list) => { - return list.iter().flat_map(reachable_variables).collect(); - } + NonConstantValue::Object(object) => object + .iter() + .flat_map(|pair| reachable_variables(&pair.value)) + .collect(), + NonConstantValue::List(list) => list.iter().flat_map(reachable_variables).collect(), _ => vec![], } } diff --git a/crates/isograph_lsp/Cargo.toml b/crates/isograph_lsp/Cargo.toml index 363a49cab..af858f5cb 100644 --- a/crates/isograph_lsp/Cargo.toml +++ b/crates/isograph_lsp/Cargo.toml @@ -5,16 +5,19 @@ edition = { workspace = true } license = { workspace = true } [dependencies] -common_lang_types = { path = "../common_lang_types"} +common_lang_types = { path = "../common_lang_types" } intern = { path = "../../relay-crates/intern" } -isograph_compiler = { path = "../isograph_compiler"} -isograph_lang_parser = { path = "../isograph_lang_parser"} -isograph_lang_types = { path = "../isograph_lang_types"} -isograph_config = { path = "../isograph_config"} +isograph_compiler = { path = "../isograph_compiler" } +isograph_lang_parser = { path = "../isograph_lang_parser" } +isograph_lang_types = { path = "../isograph_lang_types" } +isograph_config = { path = "../isograph_config" } log = { workspace = true, features = ["kv_unstable", "kv_unstable_std"] } lsp-server = { workspace = true } lsp-types = { workspace = true } serde = { workspace = true, features = ["derive", "rc"] } -serde_json = { workspace = true, features = ["float_roundtrip", "unbounded_depth"] } +serde_json = { workspace = true, features = [ + "float_roundtrip", + "unbounded_depth", +] } crossbeam = { workspace = true } tokio = { workspace = true, features = ["full", "test-util", "tracing"] } 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 000000000..74ba9c445 --- /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 abe3cdc81..f5a12f9a6 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 44bc3b198..615756e20 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/isograph_schema.rs b/crates/isograph_schema/src/isograph_schema.rs index 3badde1bf..dcb0d7fde 100644 --- a/crates/isograph_schema/src/isograph_schema.rs +++ b/crates/isograph_schema/src/isograph_schema.rs @@ -287,7 +287,7 @@ impl ServerFieldData { pub type SchemaType<'a> = SelectionType<&'a SchemaObject, &'a SchemaScalar>; -pub fn get_name<'a>(schema_type: SchemaType<'a>) -> UnvalidatedTypeName { +pub fn get_name(schema_type: SchemaType<'_>) -> UnvalidatedTypeName { match schema_type { SelectionType::Object(object) => object.name.into(), SelectionType::Scalar(scalar) => scalar.name.item.into(), diff --git a/crates/isograph_schema/src/lib.rs b/crates/isograph_schema/src/lib.rs index 9367d1685..909a25cba 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 5eb80815b..f75ed1214 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 3cad920c2..061ce057e 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/crates/tests/Cargo.toml b/crates/tests/Cargo.toml index bda57aaf8..77d017abd 100644 --- a/crates/tests/Cargo.toml +++ b/crates/tests/Cargo.toml @@ -19,4 +19,4 @@ colorize = { workspace = true } serde = { workspace = true } [dev-dependencies] -graphql_schema_parser = { path = "../graphql_schema_parser"} +graphql_schema_parser = { path = "../graphql_schema_parser" } diff --git a/demos/disposable-state-ajax-demo/package.json b/demos/disposable-state-ajax-demo/package.json index ef6abb3f1..1cae26350 100644 --- a/demos/disposable-state-ajax-demo/package.json +++ b/demos/disposable-state-ajax-demo/package.json @@ -1,6 +1,6 @@ { "name": "disposable-state-ajax-demo", - "version": "0.2.0", + "version": "0.3.0", "private": true, "scripts": { "dev": "next dev", diff --git a/demos/github-demo/isograph.config.json b/demos/github-demo/isograph.config.json index d22a32976..f61a61cb3 100644 --- a/demos/github-demo/isograph.config.json +++ b/demos/github-demo/isograph.config.json @@ -1,4 +1,5 @@ { + "$schema": "../../libs/isograph-compiler/isograph-config-schema.json", "project_root": "./src/isograph-components", "schema": "./schema.graphql", "options": { diff --git a/demos/github-demo/package.json b/demos/github-demo/package.json index 82073c2ee..84a77ddb0 100644 --- a/demos/github-demo/package.json +++ b/demos/github-demo/package.json @@ -1,6 +1,6 @@ { "name": "github-demo", - "version": "0.2.0", + "version": "0.3.0", "private": true, "scripts": { "dev": "next dev", diff --git a/demos/github-demo/src/isograph-components/GithubDemo.tsx b/demos/github-demo/src/isograph-components/GithubDemo.tsx index 2a71baefc..2c75d34a0 100644 --- a/demos/github-demo/src/isograph-components/GithubDemo.tsx +++ b/demos/github-demo/src/isograph-components/GithubDemo.tsx @@ -77,7 +77,8 @@ function Router({ case 'PullRequest': return ; default: - const _exhaustiveCheck: never = route; + const _: never = route; + _; throw new Error('Unexpected route kind'); } } diff --git a/demos/github-demo/src/isograph-components/HomeRoute.tsx b/demos/github-demo/src/isograph-components/HomeRoute.tsx index 7e262a3b4..4fe946452 100644 --- a/demos/github-demo/src/isograph-components/HomeRoute.tsx +++ b/demos/github-demo/src/isograph-components/HomeRoute.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useResult, useLazyReference, subscribe } from '@isograph/react'; +import { useResult, useLazyReference } from '@isograph/react'; import { iso } from '@iso'; import { Container } from '@mui/material'; diff --git a/demos/github-demo/src/isograph-components/UserRepositoryList.tsx b/demos/github-demo/src/isograph-components/UserRepositoryList.tsx index b50f6c242..0beef454b 100644 --- a/demos/github-demo/src/isograph-components/UserRepositoryList.tsx +++ b/demos/github-demo/src/isograph-components/UserRepositoryList.tsx @@ -43,19 +43,7 @@ export const RepositoryList = iso(` return null; } const { node } = data; - return ( - - - - {node.nameWithOwner} - - - {node.stargazerCount} - {node.forkCount} - {node.pullRequests?.totalCount} - {node.watchers?.totalCount} - - ); + return ; })} @@ -93,19 +81,8 @@ export const RepositoryConnection = iso(` } edges { node { + RepositoryRow id - RepositoryLink - name - nameWithOwner - description - forkCount - pullRequests { - totalCount - } - stargazerCount - watchers { - totalCount - } } } } @@ -113,3 +90,37 @@ export const RepositoryConnection = iso(` `)(function UserRepositoryConnectionComponent({ data }) { return data.repositories; }); + +export const RepositoryRow = iso(` + field Repository.RepositoryRow @component { + RepositoryLink + name + nameWithOwner + description + forkCount + pullRequests { + totalCount + } + stargazerCount + watchers { + totalCount + } + } +`)(( + { data: repository }, + { setRoute }: { setRoute: (route: Route) => void }, +) => { + return ( + + + + {repository.nameWithOwner} + + + {repository.stargazerCount} + {repository.forkCount} + {repository.pullRequests?.totalCount} + {repository.watchers?.totalCount} + + ); +}); diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts index 6373b761a..28569e183 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts @@ -38,183 +38,186 @@ const queryText = 'query User__refetch ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "User", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "avatarUrl", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Linked", - fieldName: "repositories", - arguments: [ - [ - "first", - { kind: "Literal", value: 10 }, - ], - - [ - "after", - { kind: "Literal", value: null }, - ], - ], - concreteType: "RepositoryConnection", - selections: [ - { - kind: "Linked", - fieldName: "edges", - arguments: null, - concreteType: "RepositoryEdge", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: null, - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "description", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "forkCount", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - arguments: null, - }, - { - kind: "Linked", - fieldName: "owner", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "pullRequests", - arguments: null, - concreteType: "PullRequestConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "stargazerCount", - arguments: null, - }, - { - kind: "Linked", - fieldName: "watchers", - arguments: null, - concreteType: "UserConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - ], - }, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "User", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "avatarUrl", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Linked", + fieldName: "repositories", + arguments: [ + [ + "first", + { kind: "Literal", value: 10 }, ], - }, - { - kind: "Linked", - fieldName: "pageInfo", - arguments: null, - concreteType: "PageInfo", - selections: [ - { - kind: "Scalar", - fieldName: "endCursor", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hasNextPage", - arguments: null, - }, + + [ + "after", + { kind: "Literal", value: null }, ], - }, - ], - }, - ], - }, - ], - }, -]; + ], + concreteType: "RepositoryConnection", + selections: [ + { + kind: "Linked", + fieldName: "edges", + arguments: null, + concreteType: "RepositoryEdge", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: null, + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "description", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "forkCount", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + arguments: null, + }, + { + kind: "Linked", + fieldName: "owner", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "pullRequests", + arguments: null, + concreteType: "PullRequestConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "stargazerCount", + arguments: null, + }, + { + kind: "Linked", + fieldName: "watchers", + arguments: null, + concreteType: "UserConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Linked", + fieldName: "pageInfo", + arguments: null, + concreteType: "PageInfo", + selections: [ + { + kind: "Scalar", + fieldName: "endCursor", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hasNextPage", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts index 362f12223..66036f98f 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts @@ -43,167 +43,170 @@ const queryText = 'query HomePage {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "viewer", - arguments: null, - concreteType: "User", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "avatarUrl", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Linked", - fieldName: "repositories", - arguments: [ - [ - "first", - { kind: "Literal", value: 10 }, - ], - - [ - "after", - { kind: "Literal", value: null }, - ], - ], - concreteType: "RepositoryConnection", - selections: [ - { - kind: "Linked", - fieldName: "edges", - arguments: null, - concreteType: "RepositoryEdge", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: null, - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "description", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "forkCount", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - arguments: null, - }, - { - kind: "Linked", - fieldName: "owner", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "pullRequests", - arguments: null, - concreteType: "PullRequestConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "stargazerCount", - arguments: null, - }, - { - kind: "Linked", - fieldName: "watchers", - arguments: null, - concreteType: "UserConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - ], - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "viewer", + arguments: null, + concreteType: "User", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "avatarUrl", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Linked", + fieldName: "repositories", + arguments: [ + [ + "first", + { kind: "Literal", value: 10 }, ], - }, - { - kind: "Linked", - fieldName: "pageInfo", - arguments: null, - concreteType: "PageInfo", - selections: [ - { - kind: "Scalar", - fieldName: "endCursor", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hasNextPage", - arguments: null, - }, + + [ + "after", + { kind: "Literal", value: null }, ], - }, - ], - }, - ], - }, -]; + ], + concreteType: "RepositoryConnection", + selections: [ + { + kind: "Linked", + fieldName: "edges", + arguments: null, + concreteType: "RepositoryEdge", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: null, + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "description", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "forkCount", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + arguments: null, + }, + { + kind: "Linked", + fieldName: "owner", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "pullRequests", + arguments: null, + concreteType: "PullRequestConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "stargazerCount", + arguments: null, + }, + { + kind: "Linked", + fieldName: "watchers", + arguments: null, + concreteType: "UserConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Linked", + fieldName: "pageInfo", + arguments: null, + concreteType: "PageInfo", + selections: [ + { + kind: "Scalar", + fieldName: "endCursor", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hasNextPage", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__HomePage__param, Query__HomePage__output_type diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts index 75db112b5..1d34e6450 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts @@ -33,144 +33,147 @@ const queryText = 'query PullRequest ($repositoryOwner: String!, $repositoryName },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "repository", - arguments: [ - [ - "owner", - { kind: "Variable", name: "repositoryOwner" }, - ], +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "repository", + arguments: [ + [ + "owner", + { kind: "Variable", name: "repositoryOwner" }, + ], - [ - "name", - { kind: "Variable", name: "repositoryName" }, - ], - ], - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "pullRequest", - arguments: [ - [ - "number", - { kind: "Variable", name: "pullRequestNumber" }, - ], + [ + "name", + { kind: "Variable", name: "repositoryName" }, ], - concreteType: "PullRequest", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "bodyHTML", - arguments: null, - }, - { - kind: "Linked", - fieldName: "comments", - arguments: [ - [ - "last", - { kind: "Literal", value: 10 }, - ], + ], + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "pullRequest", + arguments: [ + [ + "number", + { kind: "Variable", name: "pullRequestNumber" }, ], - concreteType: "IssueCommentConnection", - selections: [ - { - kind: "Linked", - fieldName: "edges", - arguments: null, - concreteType: "IssueCommentEdge", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: null, - concreteType: "IssueComment", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "author", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "bodyText", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "createdAt", - arguments: null, - }, - ], - }, + ], + concreteType: "PullRequest", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "bodyHTML", + arguments: null, + }, + { + kind: "Linked", + fieldName: "comments", + arguments: [ + [ + "last", + { kind: "Literal", value: 10 }, ], - }, - ], - }, - { - kind: "Scalar", - fieldName: "title", - arguments: null, - }, - ], - }, - ], - }, - { - kind: "Linked", - fieldName: "viewer", - arguments: null, - concreteType: "User", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "avatarUrl", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + ], + concreteType: "IssueCommentConnection", + selections: [ + { + kind: "Linked", + fieldName: "edges", + arguments: null, + concreteType: "IssueCommentEdge", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: null, + concreteType: "IssueComment", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "author", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "bodyText", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "createdAt", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Scalar", + fieldName: "title", + arguments: null, + }, + ], + }, + ], + }, + { + kind: "Linked", + fieldName: "viewer", + arguments: null, + concreteType: "User", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "avatarUrl", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__PullRequest__param, Query__PullRequest__output_type diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts index 6be9f97c9..05c8c2ce2 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts @@ -58,254 +58,257 @@ const queryText = 'query RepositoryPage ($repositoryName: String!, $repositoryOw },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "repository", - arguments: [ - [ - "name", - { kind: "Variable", name: "repositoryName" }, - ], +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "repository", + arguments: [ + [ + "name", + { kind: "Variable", name: "repositoryName" }, + ], - [ - "owner", - { kind: "Variable", name: "repositoryOwner" }, - ], - ], - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - arguments: null, - }, - { - kind: "Linked", - fieldName: "parent", - arguments: null, - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - arguments: null, - }, - { - kind: "Linked", - fieldName: "owner", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, + [ + "owner", + { kind: "Variable", name: "repositoryOwner" }, ], - }, - { - kind: "Linked", - fieldName: "pullRequests", - arguments: [ - [ - "last", - { kind: "Variable", name: "first" }, + ], + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + arguments: null, + }, + { + kind: "Linked", + fieldName: "parent", + arguments: null, + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + arguments: null, + }, + { + kind: "Linked", + fieldName: "owner", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, ], - ], - concreteType: "PullRequestConnection", - selections: [ - { - kind: "Linked", - fieldName: "edges", - arguments: null, - concreteType: "PullRequestEdge", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: null, - concreteType: "PullRequest", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "author", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - { - kind: "InlineFragment", - type: "User", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "twitterUsername", - arguments: null, - }, - ], - }, - ], - }, - { - kind: "Scalar", - fieldName: "closed", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "createdAt", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "number", - arguments: null, - }, - { - kind: "Linked", - fieldName: "repository", - arguments: null, - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Linked", - fieldName: "owner", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, - ], - }, - { - kind: "Scalar", - fieldName: "title", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "totalCommentsCount", - arguments: null, - }, - ], - }, + }, + { + kind: "Linked", + fieldName: "pullRequests", + arguments: [ + [ + "last", + { kind: "Variable", name: "first" }, ], - }, - ], - }, - { - kind: "Scalar", - fieldName: "stargazerCount", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "viewerHasStarred", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "viewer", - arguments: null, - concreteType: "User", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "avatarUrl", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + ], + concreteType: "PullRequestConnection", + selections: [ + { + kind: "Linked", + fieldName: "edges", + arguments: null, + concreteType: "PullRequestEdge", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: null, + concreteType: "PullRequest", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "author", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + { + kind: "InlineFragment", + type: "User", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "twitterUsername", + arguments: null, + }, + ], + }, + ], + }, + { + kind: "Scalar", + fieldName: "closed", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "createdAt", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "number", + arguments: null, + }, + { + kind: "Linked", + fieldName: "repository", + arguments: null, + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Linked", + fieldName: "owner", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, + ], + }, + { + kind: "Scalar", + fieldName: "title", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "totalCommentsCount", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Scalar", + fieldName: "stargazerCount", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "viewerHasStarred", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "viewer", + arguments: null, + concreteType: "User", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "avatarUrl", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__RepositoryPage__param, Query__RepositoryPage__output_type diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts index 348b9cb3f..6727e887a 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts @@ -43,185 +43,188 @@ const queryText = 'query UserPage ($userLogin: String!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "user", - arguments: [ - [ - "login", - { kind: "Variable", name: "userLogin" }, - ], - ], - concreteType: "User", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Linked", - fieldName: "repositories", - arguments: [ - [ - "first", - { kind: "Literal", value: 10 }, - ], - - [ - "after", - { kind: "Literal", value: null }, - ], +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "user", + arguments: [ + [ + "login", + { kind: "Variable", name: "userLogin" }, ], - concreteType: "RepositoryConnection", - selections: [ - { - kind: "Linked", - fieldName: "edges", - arguments: null, - concreteType: "RepositoryEdge", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: null, - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "description", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "forkCount", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - arguments: null, - }, - { - kind: "Linked", - fieldName: "owner", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "pullRequests", - arguments: null, - concreteType: "PullRequestConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "stargazerCount", - arguments: null, - }, - { - kind: "Linked", - fieldName: "watchers", - arguments: null, - concreteType: "UserConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - ], - }, + ], + concreteType: "User", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Linked", + fieldName: "repositories", + arguments: [ + [ + "first", + { kind: "Literal", value: 10 }, ], - }, - { - kind: "Linked", - fieldName: "pageInfo", - arguments: null, - concreteType: "PageInfo", - selections: [ - { - kind: "Scalar", - fieldName: "endCursor", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hasNextPage", - arguments: null, - }, + + [ + "after", + { kind: "Literal", value: null }, ], - }, - ], - }, - ], - }, - { - kind: "Linked", - fieldName: "viewer", - arguments: null, - concreteType: "User", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "avatarUrl", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + ], + concreteType: "RepositoryConnection", + selections: [ + { + kind: "Linked", + fieldName: "edges", + arguments: null, + concreteType: "RepositoryEdge", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: null, + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "description", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "forkCount", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + arguments: null, + }, + { + kind: "Linked", + fieldName: "owner", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "pullRequests", + arguments: null, + concreteType: "PullRequestConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "stargazerCount", + arguments: null, + }, + { + kind: "Linked", + fieldName: "watchers", + arguments: null, + concreteType: "UserConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Linked", + fieldName: "pageInfo", + arguments: null, + concreteType: "PageInfo", + selections: [ + { + kind: "Scalar", + fieldName: "endCursor", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hasNextPage", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Linked", + fieldName: "viewer", + arguments: null, + concreteType: "User", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "avatarUrl", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__UserPage__param, Query__UserPage__output_type diff --git a/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/output_type.ts b/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/output_type.ts new file mode 100644 index 000000000..85aabc0f1 --- /dev/null +++ b/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/output_type.ts @@ -0,0 +1,4 @@ +import type { ExtractSecondParam, CombineWithIntrinsicAttributes } from '@isograph/react'; +import type React from 'react'; +import { RepositoryRow as resolver } from '../../../UserRepositoryList'; +export type Repository__RepositoryRow__output_type = (React.FC>>); diff --git a/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/param_type.ts b/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/param_type.ts new file mode 100644 index 000000000..f84cb5a5b --- /dev/null +++ b/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/param_type.ts @@ -0,0 +1,46 @@ +import { type Repository__RepositoryLink__output_type } from '../../Repository/RepositoryLink/output_type'; + +export type Repository__RepositoryRow__param = { + readonly data: { + readonly RepositoryLink: Repository__RepositoryLink__output_type, + /** +The name of the repository. + */ + readonly name: string, + /** +The repository's name with owner. + */ + readonly nameWithOwner: string, + /** +The description of the repository. + */ + readonly description: (string | null), + /** +Returns how many forks there are of this repository in the whole network. + */ + readonly forkCount: number, + /** +A list of pull requests that have been opened in the repository. + */ + readonly pullRequests: { + /** +Identifies the total count of items in the connection. + */ + readonly totalCount: number, + }, + /** +Returns a count of how many stargazers there are on this object + */ + readonly stargazerCount: number, + /** +A list of users watching the repository. + */ + readonly watchers: { + /** +Identifies the total count of items in the connection. + */ + readonly totalCount: number, + }, + }, + readonly parameters: Record, +}; diff --git a/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/resolver_reader.ts b/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/resolver_reader.ts new file mode 100644 index 000000000..77c50b638 --- /dev/null +++ b/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/resolver_reader.ts @@ -0,0 +1,86 @@ +import type {ComponentReaderArtifact, ExtractSecondParam, ReaderAst } from '@isograph/react'; +import { Repository__RepositoryRow__param } from './param_type'; +import { RepositoryRow as resolver } from '../../../UserRepositoryList'; +import Repository__RepositoryLink__resolver_reader from '../../Repository/RepositoryLink/resolver_reader'; + +const readerAst: ReaderAst = [ + { + kind: "Resolver", + alias: "RepositoryLink", + arguments: null, + readerArtifact: Repository__RepositoryLink__resolver_reader, + usedRefetchQueries: [], + }, + { + kind: "Scalar", + fieldName: "name", + alias: null, + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + alias: null, + arguments: null, + }, + { + kind: "Scalar", + fieldName: "description", + alias: null, + arguments: null, + }, + { + kind: "Scalar", + fieldName: "forkCount", + alias: null, + arguments: null, + }, + { + kind: "Linked", + fieldName: "pullRequests", + alias: null, + arguments: null, + condition: null, + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + alias: null, + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "stargazerCount", + alias: null, + arguments: null, + }, + { + kind: "Linked", + fieldName: "watchers", + alias: null, + arguments: null, + condition: null, + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + alias: null, + arguments: null, + }, + ], + }, +]; + +const artifact: ComponentReaderArtifact< + Repository__RepositoryRow__param, + ExtractSecondParam +> = { + kind: "ComponentReaderArtifact", + componentName: "Repository.RepositoryRow", + resolver, + readerAst, +}; + +export default artifact; diff --git a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts index 1c2e894c5..a2a76eb57 100644 --- a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts @@ -40,168 +40,171 @@ const queryText = 'query RepositoryConnection ($first: Int, $after: String, $id: },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "User", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "repositories", - arguments: [ - [ - "first", - { kind: "Variable", name: "first" }, - ], - - [ - "after", - { kind: "Variable", name: "after" }, - ], - ], - concreteType: "RepositoryConnection", - selections: [ - { - kind: "Linked", - fieldName: "edges", - arguments: null, - concreteType: "RepositoryEdge", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: null, - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "description", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "forkCount", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - arguments: null, - }, - { - kind: "Linked", - fieldName: "owner", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "pullRequests", - arguments: null, - concreteType: "PullRequestConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "stargazerCount", - arguments: null, - }, - { - kind: "Linked", - fieldName: "watchers", - arguments: null, - concreteType: "UserConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - ], - }, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "User", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "repositories", + arguments: [ + [ + "first", + { kind: "Variable", name: "first" }, ], - }, - { - kind: "Linked", - fieldName: "pageInfo", - arguments: null, - concreteType: "PageInfo", - selections: [ - { - kind: "Scalar", - fieldName: "endCursor", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hasNextPage", - arguments: null, - }, + + [ + "after", + { kind: "Variable", name: "after" }, ], - }, - ], - }, - ], - }, - ], - }, -]; + ], + concreteType: "RepositoryConnection", + selections: [ + { + kind: "Linked", + fieldName: "edges", + arguments: null, + concreteType: "RepositoryEdge", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: null, + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "description", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "forkCount", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + arguments: null, + }, + { + kind: "Linked", + fieldName: "owner", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "pullRequests", + arguments: null, + concreteType: "PullRequestConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "stargazerCount", + arguments: null, + }, + { + kind: "Linked", + fieldName: "watchers", + arguments: null, + concreteType: "UserConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Linked", + fieldName: "pageInfo", + arguments: null, + concreteType: "PageInfo", + selections: [ + { + kind: "Scalar", + fieldName: "endCursor", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hasNextPage", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< User__RepositoryConnection__param, User__RepositoryConnection__output_type diff --git a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/param_type.ts b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/param_type.ts index f56039b53..5d76bb0c9 100644 --- a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/param_type.ts +++ b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/param_type.ts @@ -1,4 +1,4 @@ -import { type Repository__RepositoryLink__output_type } from '../../Repository/RepositoryLink/output_type'; +import { type Repository__RepositoryRow__output_type } from '../../Repository/RepositoryRow/output_type'; import type { User__RepositoryConnection__parameters } from './parameters_type'; export type User__RepositoryConnection__param = { @@ -28,49 +28,11 @@ A list of edges. The item at the end of the edge. */ readonly node: ({ + readonly RepositoryRow: Repository__RepositoryRow__output_type, /** The Node ID of the Repository object */ readonly id: string, - readonly RepositoryLink: Repository__RepositoryLink__output_type, - /** -The name of the repository. - */ - readonly name: string, - /** -The repository's name with owner. - */ - readonly nameWithOwner: string, - /** -The description of the repository. - */ - readonly description: (string | null), - /** -Returns how many forks there are of this repository in the whole network. - */ - readonly forkCount: number, - /** -A list of pull requests that have been opened in the repository. - */ - readonly pullRequests: { - /** -Identifies the total count of items in the connection. - */ - readonly totalCount: number, - }, - /** -Returns a count of how many stargazers there are on this object - */ - readonly stargazerCount: number, - /** -A list of users watching the repository. - */ - readonly watchers: { - /** -Identifies the total count of items in the connection. - */ - readonly totalCount: number, - }, } | null), } | null)> | null), }, diff --git a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/resolver_reader.ts b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/resolver_reader.ts index 11105198c..764955150 100644 --- a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/resolver_reader.ts +++ b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/resolver_reader.ts @@ -2,7 +2,7 @@ import type { EagerReaderArtifact, ReaderAst } from '@isograph/react'; import { User__RepositoryConnection__param } from './param_type'; import { User__RepositoryConnection__output_type } from './output_type'; import { RepositoryConnection as resolver } from '../../../UserRepositoryList'; -import Repository__RepositoryLink__resolver_reader from '../../Repository/RepositoryLink/resolver_reader'; +import Repository__RepositoryRow__resolver_reader from '../../Repository/RepositoryRow/resolver_reader'; const readerAst: ReaderAst = [ { @@ -57,78 +57,18 @@ const readerAst: ReaderAst = [ arguments: null, condition: null, selections: [ - { - kind: "Scalar", - fieldName: "id", - alias: null, - arguments: null, - }, { kind: "Resolver", - alias: "RepositoryLink", + alias: "RepositoryRow", arguments: null, - readerArtifact: Repository__RepositoryLink__resolver_reader, + readerArtifact: Repository__RepositoryRow__resolver_reader, usedRefetchQueries: [], }, { kind: "Scalar", - fieldName: "name", - alias: null, - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - alias: null, - arguments: null, - }, - { - kind: "Scalar", - fieldName: "description", - alias: null, - arguments: null, - }, - { - kind: "Scalar", - fieldName: "forkCount", - alias: null, - arguments: null, - }, - { - kind: "Linked", - fieldName: "pullRequests", - alias: null, - arguments: null, - condition: null, - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - alias: null, - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "stargazerCount", - alias: null, - arguments: null, - }, - { - kind: "Linked", - fieldName: "watchers", + fieldName: "id", alias: null, arguments: null, - condition: null, - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - alias: null, - arguments: null, - }, - ], }, ], }, 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 aa9b966b7..6c7cc03ef 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/github-demo/src/isograph-components/__isograph/iso.ts b/demos/github-demo/src/isograph-components/__isograph/iso.ts index 4fe253da4..85346833b 100644 --- a/demos/github-demo/src/isograph-components/__isograph/iso.ts +++ b/demos/github-demo/src/isograph-components/__isograph/iso.ts @@ -15,6 +15,7 @@ import { type Query__RepositoryPage__param } from './Query/RepositoryPage/param_ import { type Query__UserDetail__param } from './Query/UserDetail/param_type'; import { type Query__UserPage__param } from './Query/UserPage/param_type'; import { type Repository__RepositoryLink__param } from './Repository/RepositoryLink/param_type'; +import { type Repository__RepositoryRow__param } from './Repository/RepositoryRow/param_type'; import { type Starrable__IsStarred__param } from './Starrable/IsStarred/param_type'; import { type User__Avatar__param } from './User/Avatar/param_type'; import { type User__RepositoryConnection__param } from './User/RepositoryConnection/param_type'; @@ -136,6 +137,10 @@ export function iso( param: T & MatchesWhitespaceAndString<'field Repository.RepositoryLink', T> ): IdentityWithParamComponent; +export function iso( + param: T & MatchesWhitespaceAndString<'field Repository.RepositoryRow', T> +): IdentityWithParamComponent; + export function iso( param: T & MatchesWhitespaceAndString<'field Starrable.IsStarred', T> ): IdentityWithParamComponent; @@ -175,5 +180,6 @@ export function iso(_isographLiteralText: string): { throw new Error('iso: Unexpected invocation at runtime. Either the Babel transform ' + 'was not set up, or it failed to identify this call site. Make sure it ' + - 'is being used verbatim as `iso`.'); + 'is being used verbatim as `iso`. If you cannot use the babel transform, ' + + 'set options.no_babel_transform to true in your Isograph config. '); } \ No newline at end of file diff --git a/demos/pet-demo/backend/package.json b/demos/pet-demo/backend/package.json index 638cfd794..4ee93fc4c 100644 --- a/demos/pet-demo/backend/package.json +++ b/demos/pet-demo/backend/package.json @@ -1,6 +1,6 @@ { "name": "pet-demo-backend", - "version": "0.2.0", + "version": "0.3.0", "private": true, "type": "module", "dependencies": { diff --git a/demos/pet-demo/isograph.config.json b/demos/pet-demo/isograph.config.json index fdfdb8e3f..594a1680b 100644 --- a/demos/pet-demo/isograph.config.json +++ b/demos/pet-demo/isograph.config.json @@ -1,4 +1,5 @@ { + "$schema": "../../libs/isograph-compiler/isograph-config-schema.json", "project_root": "./src/components", "schema": "./backend/schema.graphql", "schema_extensions": ["./backend/schema-extension.graphql"], diff --git a/demos/pet-demo/package.json b/demos/pet-demo/package.json index abb928b69..6da5c272c 100644 --- a/demos/pet-demo/package.json +++ b/demos/pet-demo/package.json @@ -1,6 +1,6 @@ { "name": "pet-demo", - "version": "0.2.0", + "version": "0.3.0", "private": true, "scripts": { "dev": "next dev", diff --git a/demos/pet-demo/src/components/FavoritePhrase.tsx b/demos/pet-demo/src/components/FavoritePhrase.tsx index 217c955a9..610909342 100644 --- a/demos/pet-demo/src/components/FavoritePhrase.tsx +++ b/demos/pet-demo/src/components/FavoritePhrase.tsx @@ -16,7 +16,21 @@ export const FavoritePhraseLoader = iso(` return ( <> {fragmentReference == UNASSIGNED_STATE ? ( - ) : ( diff --git a/demos/pet-demo/src/components/Newsfeed/NewsfeedPagination.tsx b/demos/pet-demo/src/components/Newsfeed/NewsfeedPagination.tsx index 0556d47bb..bc3cbf639 100644 --- a/demos/pet-demo/src/components/Newsfeed/NewsfeedPagination.tsx +++ b/demos/pet-demo/src/components/Newsfeed/NewsfeedPagination.tsx @@ -4,6 +4,12 @@ import { iso } from '@iso'; export const NewsfeedPaginationComponent = iso(` field Viewer.NewsfeedPaginationComponent($skip: Int!, $limit: Int!) { newsfeed(skip: $skip, limit: $limit) { + asAdItem { + id + } + asBlogItem { + id + } NewsfeedAdOrBlog } } diff --git a/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx b/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx index bcaa0764f..f59b5babd 100644 --- a/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx +++ b/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx @@ -1,4 +1,4 @@ -import React, { Suspense, useState } from 'react'; +import React, { Suspense } from 'react'; import { iso } from '@iso'; import { Card, CardContent, Container, Stack, Typography } from '@mui/material'; import { @@ -12,9 +12,7 @@ import { ErrorBoundary } from '../ErrorBoundary'; export const Newsfeed = iso(` field Query.Newsfeed @component { viewer { - newsfeed(skip: 0, limit: 6) { - NewsfeedAdOrBlog - } + initial: NewsfeedPaginationComponent(skip: 0, limit: 6) NewsfeedPaginationComponent @loadable } } @@ -23,10 +21,10 @@ export const Newsfeed = iso(` const paginationState = useSkipLimitPagination( viewer.NewsfeedPaginationComponent, - { skip: viewer.newsfeed.length }, + { skip: viewer.initial.length }, ); - const newsfeedItems = viewer.newsfeed.concat(paginationState.results); + const newsfeedItems = viewer.initial.concat(paginationState.results); const loadMore = () => { if (paginationState.kind === 'Complete') { @@ -42,9 +40,8 @@ export const Newsfeed = iso(` const onVisible = index === newsfeedItems.length - 1 ? loadMore : null; return ( - // the key is not needed here! - // eslint-disable-next-line react/jsx-key diff --git a/demos/pet-demo/src/components/Newsfeed/useIntersection.tsx b/demos/pet-demo/src/components/Newsfeed/useIntersection.tsx index 0f06d20e6..1e4c32847 100644 --- a/demos/pet-demo/src/components/Newsfeed/useIntersection.tsx +++ b/demos/pet-demo/src/components/Newsfeed/useIntersection.tsx @@ -1,4 +1,4 @@ -import { Ref, useEffect, useRef } from 'react'; +import { useEffect, useRef } from 'react'; export function useOnScreen( ref: React.RefObject, diff --git a/demos/pet-demo/src/components/PetDetailRoute.tsx b/demos/pet-demo/src/components/PetDetailRoute.tsx index 62892da75..abc16c4f2 100644 --- a/demos/pet-demo/src/components/PetDetailRoute.tsx +++ b/demos/pet-demo/src/components/PetDetailRoute.tsx @@ -53,6 +53,17 @@ export function PetDetailRouteLoader({ route }: { route: PetDetailRoute }) { const { fragmentReference } = useLazyReference( iso(`entrypoint Query.PetDetailRoute`), { id: route.id }, + { + onComplete: (data) => { + console.log( + 'The Query.PetDetailRoute network request has completed.', + data, + ); + }, + onError: () => { + console.log('The Query.PetDetailRoute network request errored out.'); + }, + }, ); return ( diff --git a/demos/pet-demo/src/components/PetTaglineCard.tsx b/demos/pet-demo/src/components/PetTaglineCard.tsx index bc5bd297d..4c83018b1 100644 --- a/demos/pet-demo/src/components/PetTaglineCard.tsx +++ b/demos/pet-demo/src/components/PetTaglineCard.tsx @@ -1,14 +1,14 @@ -import React from 'react'; +import React, { Suspense } from 'react'; import { iso } from '@iso'; import { Button, Card, CardContent } from '@mui/material'; -import { useImperativeReference } from '@isograph/react'; +import { FragmentReader, useImperativeReference } from '@isograph/react'; import { UNASSIGNED_STATE } from '@isograph/react-disposable-state'; export const PetTaglineCard = iso(` -field Pet.PetTaglineCard @component { - id - tagline -} + field Pet.PetTaglineCard @component { + id + tagline + } `)(function PetTaglineCardComponent({ data: pet }) { const { fragmentReference: mutationRef, @@ -23,7 +23,7 @@ field Pet.PetTaglineCard @component {

Tagline

"{pet.tagline}"

- {mutationRef == UNASSIGNED_STATE ? ( + {mutationRef === UNASSIGNED_STATE ? ( - ) : null} + ) : ( + + + + )}
); }); export const setTagline = iso(` - field Mutation.SetTagline($input: SetPetTaglineParams!) { + field Mutation.SetTagline($input: SetPetTaglineParams!) @component { set_pet_tagline(input: $input) { pet { tagline } } } -`)(() => {}); +`)(({ data }) => { + return ( + <> + Nice! You updated the pet's tagline to{' '} + {data.set_pet_tagline?.pet?.tagline}! + + ); +}); diff --git a/demos/pet-demo/src/components/__isograph/AdItem/AdItemDisplay/entrypoint.ts b/demos/pet-demo/src/components/__isograph/AdItem/AdItemDisplay/entrypoint.ts index 4e09394ab..a0c1928fd 100644 --- a/demos/pet-demo/src/components/__isograph/AdItem/AdItemDisplay/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/AdItem/AdItemDisplay/entrypoint.ts @@ -15,47 +15,50 @@ const queryText = 'query AdItemDisplay ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, - ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "AdItem", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "advertiser", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "message", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, ], - }, - ], - }, -]; + ], + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "AdItem", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "advertiser", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "message", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< AdItem__AdItemDisplay__param, AdItem__AdItemDisplay__output_type 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 2000168ff..7562bd540 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/BlogItemMoreDetail/entrypoint.ts b/demos/pet-demo/src/components/__isograph/BlogItem/BlogItemMoreDetail/entrypoint.ts index 105ff064f..e725426d6 100644 --- a/demos/pet-demo/src/components/__isograph/BlogItem/BlogItemMoreDetail/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/BlogItem/BlogItemMoreDetail/entrypoint.ts @@ -14,42 +14,45 @@ const queryText = 'query BlogItemMoreDetail ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, - ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "BlogItem", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "moreContent", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, ], - }, - ], - }, -]; + ], + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "BlogItem", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "moreContent", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< BlogItem__BlogItemMoreDetail__param, BlogItem__BlogItemMoreDetail__output_type 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 f65548744..5bb1d6963 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/demos/pet-demo/src/components/__isograph/Image/ImageDisplay/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Image/ImageDisplay/entrypoint.ts index 369e9373c..0040205e4 100644 --- a/demos/pet-demo/src/components/__isograph/Image/ImageDisplay/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Image/ImageDisplay/entrypoint.ts @@ -14,42 +14,45 @@ const queryText = 'query ImageDisplay ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, - ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Image", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "url", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, ], - }, - ], - }, -]; + ], + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Image", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "url", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Image__ImageDisplay__param, Image__ImageDisplay__output_type diff --git a/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/entrypoint.ts index 054f5b224..bea919c28 100644 --- a/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/entrypoint.ts @@ -13,39 +13,42 @@ const queryText = 'mutation SetTagline ($input: SetPetTaglineParams!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "set_pet_tagline", - arguments: [ - [ - "input", - { kind: "Variable", name: "input" }, - ], - ], - concreteType: "SetPetTaglineResponse", - selections: [ - { - kind: "Linked", - fieldName: "pet", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "tagline", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "set_pet_tagline", + arguments: [ + [ + "input", + { kind: "Variable", name: "input" }, ], - }, - ], - }, -]; + ], + concreteType: "SetPetTaglineResponse", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "tagline", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Mutation__SetTagline__param, Mutation__SetTagline__output_type diff --git a/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/output_type.ts b/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/output_type.ts index 8da885229..ccd5908c6 100644 --- a/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/output_type.ts +++ b/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/output_type.ts @@ -1,3 +1,4 @@ +import type { ExtractSecondParam, CombineWithIntrinsicAttributes } from '@isograph/react'; import type React from 'react'; import { setTagline as resolver } from '../../../PetTaglineCard'; -export type Mutation__SetTagline__output_type = ReturnType; \ No newline at end of file +export type Mutation__SetTagline__output_type = (React.FC>>); diff --git a/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/resolver_reader.ts b/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/resolver_reader.ts index 6da5e1ce0..4e9d526a6 100644 --- a/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/resolver_reader.ts +++ b/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/resolver_reader.ts @@ -1,6 +1,5 @@ -import type { EagerReaderArtifact, ReaderAst } from '@isograph/react'; +import type {ComponentReaderArtifact, ExtractSecondParam, ReaderAst } from '@isograph/react'; import { Mutation__SetTagline__param } from './param_type'; -import { Mutation__SetTagline__output_type } from './output_type'; import { setTagline as resolver } from '../../../PetTaglineCard'; const readerAst: ReaderAst = [ @@ -35,11 +34,12 @@ const readerAst: ReaderAst = [ }, ]; -const artifact: EagerReaderArtifact< +const artifact: ComponentReaderArtifact< Mutation__SetTagline__param, - Mutation__SetTagline__output_type + ExtractSecondParam > = { - kind: "EagerReaderArtifact", + kind: "ComponentReaderArtifact", + componentName: "Mutation.SetTagline", resolver, readerAst, }; diff --git a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/__refetch__0.ts b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/__refetch__0.ts index d97fd72bc..ee3626fde 100644 --- a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/__refetch__0.ts +++ b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/__refetch__0.ts @@ -12,55 +12,58 @@ const queryText = 'mutation Pet__make_super ($checkin_id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "make_checkin_super", - arguments: [ - [ - "checkin_id", - { kind: "Variable", name: "checkin_id" }, - ], - ], - concreteType: "MakeCheckinSuperResponse", - selections: [ - { - kind: "Linked", - fieldName: "checkin", - arguments: null, - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "make_checkin_super", + arguments: [ + [ + "checkin_id", + { kind: "Variable", name: "checkin_id" }, ], - }, - ], - }, -]; + ], + concreteType: "MakeCheckinSuperResponse", + selections: [ + { + kind: "Linked", + fieldName: "checkin", + arguments: null, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/entrypoint.ts index dfd7fc509..3c9235f73 100644 --- a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/entrypoint.ts @@ -21,70 +21,73 @@ const queryText = 'query PetCheckinsCard ($skip: Int, $limit: Int, $id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Variable", name: "skip" }, - ], + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Variable", name: "skip" }, + ], - [ - "limit", - { kind: "Variable", name: "limit" }, + [ + "limit", + { kind: "Variable", name: "limit" }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, - ], - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Pet__PetCheckinsCard__param, Pet__PetCheckinsCard__output_type diff --git a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/__refetch__0.ts b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/__refetch__0.ts index d97fd72bc..ee3626fde 100644 --- a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/__refetch__0.ts +++ b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/__refetch__0.ts @@ -12,55 +12,58 @@ const queryText = 'mutation Pet__make_super ($checkin_id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "make_checkin_super", - arguments: [ - [ - "checkin_id", - { kind: "Variable", name: "checkin_id" }, - ], - ], - concreteType: "MakeCheckinSuperResponse", - selections: [ - { - kind: "Linked", - fieldName: "checkin", - arguments: null, - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "make_checkin_super", + arguments: [ + [ + "checkin_id", + { kind: "Variable", name: "checkin_id" }, ], - }, - ], - }, -]; + ], + concreteType: "MakeCheckinSuperResponse", + selections: [ + { + kind: "Linked", + fieldName: "checkin", + arguments: null, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/entrypoint.ts index 4de5ac267..820837603 100644 --- a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/entrypoint.ts @@ -21,70 +21,73 @@ const queryText = 'query PetCheckinsCardList ($skip: Int!, $limit: Int!, $id: ID },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Variable", name: "skip" }, - ], + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Variable", name: "skip" }, + ], - [ - "limit", - { kind: "Variable", name: "limit" }, + [ + "limit", + { kind: "Variable", name: "limit" }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, - ], - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Pet__PetCheckinsCardList__param, Pet__PetCheckinsCardList__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/HomeRoute/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/HomeRoute/entrypoint.ts index dcfe8ae2e..956cda1ac 100644 --- a/demos/pet-demo/src/components/__isograph/Query/HomeRoute/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/HomeRoute/entrypoint.ts @@ -13,36 +13,39 @@ const queryText = 'query HomeRoute {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "pets", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "picture", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "tagline", - arguments: null, - }, - ], - }, -]; +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "pets", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "picture", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "tagline", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__HomeRoute__param, Query__HomeRoute__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/Newsfeed/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/Newsfeed/entrypoint.ts index d2414e400..cb47034e2 100644 --- a/demos/pet-demo/src/components/__isograph/Query/Newsfeed/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/Newsfeed/entrypoint.ts @@ -27,104 +27,107 @@ const queryText = 'query Newsfeed {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "viewer", - arguments: null, - concreteType: "Viewer", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "newsfeed", - arguments: [ - [ - "skip", - { kind: "Literal", value: 0 }, - ], - - [ - "limit", - { kind: "Literal", value: 6 }, - ], - ], - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "InlineFragment", - type: "AdItem", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "viewer", + arguments: null, + concreteType: "Viewer", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "newsfeed", + arguments: [ + [ + "skip", + { kind: "Literal", value: 0 }, ], - }, - { - kind: "InlineFragment", - type: "BlogItem", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "author", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "content", - arguments: null, - }, - { - kind: "Linked", - fieldName: "image", - arguments: null, - concreteType: "Image", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "title", - arguments: null, - }, + + [ + "limit", + { kind: "Literal", value: 6 }, ], - }, - ], - }, - ], - }, -]; + ], + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "InlineFragment", + type: "AdItem", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + ], + }, + { + kind: "InlineFragment", + type: "BlogItem", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "author", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "content", + arguments: null, + }, + { + kind: "Linked", + fieldName: "image", + arguments: null, + concreteType: "Image", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "title", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__Newsfeed__param, Query__Newsfeed__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/Newsfeed/param_type.ts b/demos/pet-demo/src/components/__isograph/Query/Newsfeed/param_type.ts index c26d3ed3e..d96c1a960 100644 --- a/demos/pet-demo/src/components/__isograph/Query/Newsfeed/param_type.ts +++ b/demos/pet-demo/src/components/__isograph/Query/Newsfeed/param_type.ts @@ -1,4 +1,3 @@ -import { type NewsfeedItem__NewsfeedAdOrBlog__output_type } from '../../NewsfeedItem/NewsfeedAdOrBlog/output_type'; import { type Viewer__NewsfeedPaginationComponent__output_type } from '../../Viewer/NewsfeedPaginationComponent/output_type'; import { type LoadableField, type ExtractParameters } from '@isograph/react'; import { type Viewer__NewsfeedPaginationComponent__param } from '../../Viewer/NewsfeedPaginationComponent/param_type'; @@ -6,9 +5,7 @@ import { type Viewer__NewsfeedPaginationComponent__param } from '../../Viewer/Ne export type Query__Newsfeed__param = { readonly data: { readonly viewer: { - readonly newsfeed: ReadonlyArray<{ - readonly NewsfeedAdOrBlog: NewsfeedItem__NewsfeedAdOrBlog__output_type, - }>, + readonly initial: Viewer__NewsfeedPaginationComponent__output_type, readonly NewsfeedPaginationComponent: LoadableField< Viewer__NewsfeedPaginationComponent__param, Viewer__NewsfeedPaginationComponent__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/Newsfeed/resolver_reader.ts b/demos/pet-demo/src/components/__isograph/Query/Newsfeed/resolver_reader.ts index 226e8766c..7ea63fd18 100644 --- a/demos/pet-demo/src/components/__isograph/Query/Newsfeed/resolver_reader.ts +++ b/demos/pet-demo/src/components/__isograph/Query/Newsfeed/resolver_reader.ts @@ -1,7 +1,7 @@ import type {ComponentReaderArtifact, ExtractSecondParam, ReaderAst } from '@isograph/react'; import { Query__Newsfeed__param } from './param_type'; import { Newsfeed as resolver } from '../../../Newsfeed/NewsfeedRoute'; -import NewsfeedItem__NewsfeedAdOrBlog__resolver_reader from '../../NewsfeedItem/NewsfeedAdOrBlog/resolver_reader'; +import Viewer__NewsfeedPaginationComponent__resolver_reader from '../../Viewer/NewsfeedPaginationComponent/resolver_reader'; import Viewer__NewsfeedPaginationComponent__entrypoint from '../../Viewer/NewsfeedPaginationComponent/entrypoint'; const readerAst: ReaderAst = [ @@ -13,9 +13,8 @@ const readerAst: ReaderAst = [ condition: null, selections: [ { - kind: "Linked", - fieldName: "newsfeed", - alias: null, + kind: "Resolver", + alias: "initial", arguments: [ [ "skip", @@ -27,16 +26,8 @@ const readerAst: ReaderAst = [ { kind: "Literal", value: 6 }, ], ], - condition: null, - selections: [ - { - kind: "Resolver", - alias: "NewsfeedAdOrBlog", - arguments: null, - readerArtifact: NewsfeedItem__NewsfeedAdOrBlog__resolver_reader, - usedRefetchQueries: [], - }, - ], + readerArtifact: Viewer__NewsfeedPaginationComponent__resolver_reader, + usedRefetchQueries: [], }, { kind: "LoadablySelectedField", diff --git a/demos/pet-demo/src/components/__isograph/Query/PetByName/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/PetByName/entrypoint.ts index b70ba5441..e56220884 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetByName/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetByName/entrypoint.ts @@ -11,31 +11,34 @@ const queryText = 'query PetByName ($name: String!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "petByName", - arguments: [ - [ - "name", - { kind: "Variable", name: "name" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "petByName", + arguments: [ + [ + "name", + { kind: "Variable", name: "name" }, + ], ], - ], - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__PetByName__param, Query__PetByName__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/__refetch__0.ts b/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/__refetch__0.ts index b73c63ca6..c6775a614 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/__refetch__0.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/__refetch__0.ts @@ -11,50 +11,53 @@ const queryText = 'mutation Query__make_super ($checkin_id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "make_checkin_super", - arguments: [ - [ - "checkin_id", - { kind: "Variable", name: "checkin_id" }, - ], - ], - concreteType: "MakeCheckinSuperResponse", - selections: [ - { - kind: "Linked", - fieldName: "checkin", - arguments: null, - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - ], - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "make_checkin_super", + arguments: [ + [ + "checkin_id", + { kind: "Variable", name: "checkin_id" }, ], - }, - ], - }, -]; + ], + concreteType: "MakeCheckinSuperResponse", + selections: [ + { + kind: "Linked", + fieldName: "checkin", + arguments: null, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/entrypoint.ts index 2bc40039a..545e3555b 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/entrypoint.ts @@ -18,59 +18,62 @@ const queryText = 'query PetCheckinListRoute ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "pet", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Literal", value: 0 }, - ], + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Literal", value: 0 }, + ], - [ - "limit", - { kind: "Literal", value: 1 }, + [ + "limit", + { kind: "Literal", value: 1 }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__PetCheckinListRoute__param, Query__PetCheckinListRoute__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailDeferredRoute/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailDeferredRoute/entrypoint.ts index 15d46f99d..ca1481b53 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailDeferredRoute/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailDeferredRoute/entrypoint.ts @@ -11,31 +11,34 @@ const queryText = 'query PetDetailDeferredRoute ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "pet", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__PetDetailDeferredRoute__param, Query__PetDetailDeferredRoute__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__0.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__0.ts index 52a40343e..56ae2104b 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__0.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__0.ts @@ -38,187 +38,190 @@ const queryText = 'query Pet__refetch ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "age", - arguments: null, - }, - { - kind: "Linked", - fieldName: "best_friend_relationship", - arguments: null, - concreteType: "BestFriendRelationship", - selections: [ - { - kind: "Linked", - fieldName: "best_friend", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "picture", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "picture_together", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Literal", value: null }, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "age", + arguments: null, + }, + { + kind: "Linked", + fieldName: "best_friend_relationship", + arguments: null, + concreteType: "BestFriendRelationship", + selections: [ + { + kind: "Linked", + fieldName: "best_friend", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "picture", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "picture_together", + arguments: null, + }, ], + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Literal", value: null }, + ], - [ - "limit", - { kind: "Literal", value: null }, + [ + "limit", + { kind: "Literal", value: null }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "favorite_phrase", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nickname", - arguments: null, - }, - { - kind: "Linked", - fieldName: "potential_new_best_friends", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "stats", - arguments: null, - concreteType: "PetStats", - selections: [ - { - kind: "Scalar", - fieldName: "cuteness", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "energy", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hunger", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "intelligence", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "sociability", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "weight", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "tagline", - arguments: null, - }, - ], - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "favorite_phrase", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nickname", + arguments: null, + }, + { + kind: "Linked", + fieldName: "potential_new_best_friends", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "stats", + arguments: null, + concreteType: "PetStats", + selections: [ + { + kind: "Scalar", + fieldName: "cuteness", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "energy", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hunger", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "intelligence", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "sociability", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "weight", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "tagline", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__1.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__1.ts index 512986f9c..92d7c8df1 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__1.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__1.ts @@ -37,189 +37,192 @@ const queryText = 'mutation Query__set_best_friend ($id: ID!, $new_best_friend_i },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "set_pet_best_friend", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, - ], +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "set_pet_best_friend", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], - [ - "new_best_friend_id", - { kind: "Variable", name: "new_best_friend_id" }, + [ + "new_best_friend_id", + { kind: "Variable", name: "new_best_friend_id" }, + ], ], - ], - concreteType: "SetBestFriendResponse", - selections: [ - { - kind: "Linked", - fieldName: "pet", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "age", - arguments: null, - }, - { - kind: "Linked", - fieldName: "best_friend_relationship", - arguments: null, - concreteType: "BestFriendRelationship", - selections: [ - { - kind: "Linked", - fieldName: "best_friend", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "picture", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "picture_together", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Literal", value: null }, + concreteType: "SetBestFriendResponse", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "age", + arguments: null, + }, + { + kind: "Linked", + fieldName: "best_friend_relationship", + arguments: null, + concreteType: "BestFriendRelationship", + selections: [ + { + kind: "Linked", + fieldName: "best_friend", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "picture", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "picture_together", + arguments: null, + }, ], + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Literal", value: null }, + ], - [ - "limit", - { kind: "Literal", value: null }, + [ + "limit", + { kind: "Literal", value: null }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "favorite_phrase", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nickname", - arguments: null, - }, - { - kind: "Linked", - fieldName: "potential_new_best_friends", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "stats", - arguments: null, - concreteType: "PetStats", - selections: [ - { - kind: "Scalar", - fieldName: "cuteness", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "energy", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hunger", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "intelligence", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "sociability", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "weight", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "tagline", - arguments: null, - }, - ], - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "favorite_phrase", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nickname", + arguments: null, + }, + { + kind: "Linked", + fieldName: "potential_new_best_friends", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "stats", + arguments: null, + concreteType: "PetStats", + selections: [ + { + kind: "Scalar", + fieldName: "cuteness", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "energy", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hunger", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "intelligence", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "sociability", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "weight", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "tagline", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__2.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__2.ts index f76fd47f0..135103484 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__2.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__2.ts @@ -37,184 +37,187 @@ const queryText = 'mutation Query__set_pet_tagline ($input: SetPetTaglineParams! },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "set_pet_tagline", - arguments: [ - [ - "input", - { kind: "Variable", name: "input" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "set_pet_tagline", + arguments: [ + [ + "input", + { kind: "Variable", name: "input" }, + ], ], - ], - concreteType: "SetPetTaglineResponse", - selections: [ - { - kind: "Linked", - fieldName: "pet", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "age", - arguments: null, - }, - { - kind: "Linked", - fieldName: "best_friend_relationship", - arguments: null, - concreteType: "BestFriendRelationship", - selections: [ - { - kind: "Linked", - fieldName: "best_friend", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "picture", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "picture_together", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Literal", value: null }, + concreteType: "SetPetTaglineResponse", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "age", + arguments: null, + }, + { + kind: "Linked", + fieldName: "best_friend_relationship", + arguments: null, + concreteType: "BestFriendRelationship", + selections: [ + { + kind: "Linked", + fieldName: "best_friend", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "picture", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "picture_together", + arguments: null, + }, ], + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Literal", value: null }, + ], - [ - "limit", - { kind: "Literal", value: null }, + [ + "limit", + { kind: "Literal", value: null }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "favorite_phrase", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nickname", - arguments: null, - }, - { - kind: "Linked", - fieldName: "potential_new_best_friends", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "stats", - arguments: null, - concreteType: "PetStats", - selections: [ - { - kind: "Scalar", - fieldName: "cuteness", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "energy", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hunger", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "intelligence", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "sociability", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "weight", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "tagline", - arguments: null, - }, - ], - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "favorite_phrase", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nickname", + arguments: null, + }, + { + kind: "Linked", + fieldName: "potential_new_best_friends", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "stats", + arguments: null, + concreteType: "PetStats", + selections: [ + { + kind: "Scalar", + fieldName: "cuteness", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "energy", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hunger", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "intelligence", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "sociability", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "weight", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "tagline", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__3.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__3.ts index 0c8c993b9..8ea84f92b 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__3.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__3.ts @@ -12,55 +12,58 @@ const queryText = 'mutation Query__make_super ($checkin_id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "make_checkin_super", - arguments: [ - [ - "checkin_id", - { kind: "Variable", name: "checkin_id" }, - ], - ], - concreteType: "MakeCheckinSuperResponse", - selections: [ - { - kind: "Linked", - fieldName: "checkin", - arguments: null, - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "make_checkin_super", + arguments: [ + [ + "checkin_id", + { kind: "Variable", name: "checkin_id" }, ], - }, - ], - }, -]; + ], + concreteType: "MakeCheckinSuperResponse", + selections: [ + { + kind: "Linked", + fieldName: "checkin", + arguments: null, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__4.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__4.ts index 0dd86e038..41f1ffe99 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__4.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__4.ts @@ -12,59 +12,62 @@ const queryText = 'query Query__refetch_pet_stats ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "pet", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, - ], - ], - concreteType: "Pet", - selections: [ - { - kind: "Linked", - fieldName: "stats", - arguments: null, - concreteType: "PetStats", - selections: [ - { - kind: "Scalar", - fieldName: "cuteness", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "energy", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hunger", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "intelligence", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "sociability", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "weight", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, ], - }, - ], - }, -]; + ], + concreteType: "Pet", + selections: [ + { + kind: "Linked", + fieldName: "stats", + arguments: null, + concreteType: "PetStats", + selections: [ + { + kind: "Scalar", + fieldName: "cuteness", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "energy", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hunger", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "intelligence", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "sociability", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "weight", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/entrypoint.ts index d61e78034..575e34a13 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/entrypoint.ts @@ -51,176 +51,179 @@ const queryText = 'query PetDetailRoute ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "pet", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, - ], - ], - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "age", - arguments: null, - }, - { - kind: "Linked", - fieldName: "best_friend_relationship", - arguments: null, - concreteType: "BestFriendRelationship", - selections: [ - { - kind: "Linked", - fieldName: "best_friend", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "picture", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "picture_together", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, ], - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Literal", value: null }, + ], + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "age", + arguments: null, + }, + { + kind: "Linked", + fieldName: "best_friend_relationship", + arguments: null, + concreteType: "BestFriendRelationship", + selections: [ + { + kind: "Linked", + fieldName: "best_friend", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "picture", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "picture_together", + arguments: null, + }, ], + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Literal", value: null }, + ], - [ - "limit", - { kind: "Literal", value: null }, + [ + "limit", + { kind: "Literal", value: null }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "favorite_phrase", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nickname", - arguments: null, - }, - { - kind: "Linked", - fieldName: "potential_new_best_friends", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "stats", - arguments: null, - concreteType: "PetStats", - selections: [ - { - kind: "Scalar", - fieldName: "cuteness", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "energy", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hunger", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "intelligence", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "sociability", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "weight", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "tagline", - arguments: null, - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "favorite_phrase", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nickname", + arguments: null, + }, + { + kind: "Linked", + fieldName: "potential_new_best_friends", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "stats", + arguments: null, + concreteType: "PetStats", + selections: [ + { + kind: "Scalar", + fieldName: "cuteness", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "energy", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hunger", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "intelligence", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "sociability", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "weight", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "tagline", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__PetDetailRoute__param, Query__PetDetailRoute__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/PetFavoritePhrase/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/PetFavoritePhrase/entrypoint.ts index 4aee60bb5..7590f7880 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetFavoritePhrase/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetFavoritePhrase/entrypoint.ts @@ -12,36 +12,39 @@ const queryText = 'query PetFavoritePhrase ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "pet", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "favorite_phrase", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "favorite_phrase", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__PetFavoritePhrase__param, Query__PetFavoritePhrase__output_type diff --git a/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/entrypoint.ts index c5d2f1aac..fd8db8331 100644 --- a/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/entrypoint.ts @@ -30,120 +30,123 @@ const queryText = 'query NewsfeedPaginationComponent ($skip: Int!, $limit: Int!, },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Viewer", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "newsfeed", - arguments: [ - [ - "skip", - { kind: "Variable", name: "skip" }, - ], - - [ - "limit", - { kind: "Variable", name: "limit" }, - ], - ], - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "InlineFragment", - type: "AdItem", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Viewer", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "newsfeed", + arguments: [ + [ + "skip", + { kind: "Variable", name: "skip" }, ], - }, - { - kind: "InlineFragment", - type: "BlogItem", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "author", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "content", - arguments: null, - }, - { - kind: "Linked", - fieldName: "image", - arguments: null, - concreteType: "Image", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "title", - arguments: null, - }, + + [ + "limit", + { kind: "Variable", name: "limit" }, ], - }, - ], - }, - ], - }, - ], - }, -]; + ], + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "InlineFragment", + type: "AdItem", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + ], + }, + { + kind: "InlineFragment", + type: "BlogItem", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "author", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "content", + arguments: null, + }, + { + kind: "Linked", + fieldName: "image", + arguments: null, + concreteType: "Image", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "title", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Viewer__NewsfeedPaginationComponent__param, Viewer__NewsfeedPaginationComponent__output_type diff --git a/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/param_type.ts b/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/param_type.ts index 6a51b8842..8559a2671 100644 --- a/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/param_type.ts +++ b/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/param_type.ts @@ -4,6 +4,18 @@ import type { Viewer__NewsfeedPaginationComponent__parameters } from './paramete export type Viewer__NewsfeedPaginationComponent__param = { readonly data: { readonly newsfeed: ReadonlyArray<{ + /** +A client pointer for the AdItem type. + */ + readonly asAdItem: ({ + readonly id: string, + } | null), + /** +A client pointer for the BlogItem type. + */ + readonly asBlogItem: ({ + readonly id: string, + } | null), readonly NewsfeedAdOrBlog: NewsfeedItem__NewsfeedAdOrBlog__output_type, }>, }, diff --git a/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/resolver_reader.ts b/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/resolver_reader.ts index 87779da6b..018a6bb32 100644 --- a/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/resolver_reader.ts +++ b/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/resolver_reader.ts @@ -2,6 +2,8 @@ import type { EagerReaderArtifact, ReaderAst } from '@isograph/react'; import { Viewer__NewsfeedPaginationComponent__param } from './param_type'; import { Viewer__NewsfeedPaginationComponent__output_type } from './output_type'; import { NewsfeedPaginationComponent as resolver } from '../../../Newsfeed/NewsfeedPagination'; +import AdItem__asAdItem__resolver_reader from '../../AdItem/asAdItem/resolver_reader'; +import BlogItem__asBlogItem__resolver_reader from '../../BlogItem/asBlogItem/resolver_reader'; import NewsfeedItem__NewsfeedAdOrBlog__resolver_reader from '../../NewsfeedItem/NewsfeedAdOrBlog/resolver_reader'; const readerAst: ReaderAst = [ @@ -22,6 +24,36 @@ const readerAst: ReaderAst = [ ], condition: null, selections: [ + { + kind: "Linked", + fieldName: "asAdItem", + alias: null, + arguments: null, + condition: AdItem__asAdItem__resolver_reader, + selections: [ + { + kind: "Scalar", + fieldName: "id", + alias: null, + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "asBlogItem", + alias: null, + arguments: null, + condition: BlogItem__asBlogItem__resolver_reader, + selections: [ + { + kind: "Scalar", + fieldName: "id", + alias: null, + arguments: null, + }, + ], + }, { kind: "Resolver", alias: "NewsfeedAdOrBlog", diff --git a/demos/pet-demo/src/components/__isograph/iso.ts b/demos/pet-demo/src/components/__isograph/iso.ts index 5b533bd49..4e32e31d0 100644 --- a/demos/pet-demo/src/components/__isograph/iso.ts +++ b/demos/pet-demo/src/components/__isograph/iso.ts @@ -116,7 +116,7 @@ export function iso( export function iso( param: T & MatchesWhitespaceAndString<'field Mutation.SetTagline', T> -): IdentityWithParam; +): IdentityWithParamComponent; export function iso( param: T & MatchesWhitespaceAndString<'field NewsfeedItem.NewsfeedAdOrBlog', T> @@ -245,5 +245,6 @@ export function iso(_isographLiteralText: string): { throw new Error('iso: Unexpected invocation at runtime. Either the Babel transform ' + 'was not set up, or it failed to identify this call site. Make sure it ' + - 'is being used verbatim as `iso`.'); + 'is being used verbatim as `iso`. If you cannot use the babel transform, ' + + 'set options.no_babel_transform to true in your Isograph config. '); } \ No newline at end of file diff --git a/demos/vite-demo/isograph.config.json b/demos/vite-demo/isograph.config.json index ebf16a775..0eeafc821 100644 --- a/demos/vite-demo/isograph.config.json +++ b/demos/vite-demo/isograph.config.json @@ -1,4 +1,5 @@ { + "$schema": "../../libs/isograph-compiler/isograph-config-schema.json", "project_root": "./src/components", "schema": "./schema.graphql" } diff --git a/demos/vite-demo/package.json b/demos/vite-demo/package.json index bc03c62fd..75401235c 100644 --- a/demos/vite-demo/package.json +++ b/demos/vite-demo/package.json @@ -8,7 +8,7 @@ "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview", - "tsc": "echo TODO figure out why this fails in CI", + "tsc": "tsc -b", "iso": "../../target/debug/isograph_cli --config ./isograph.config.json", "iso-watch": "../../target/debug/isograph_cli --config ./isograph.config.json --watch" }, diff --git a/demos/vite-demo/src/components/__isograph/Query/HomePage/entrypoint.ts b/demos/vite-demo/src/components/__isograph/Query/HomePage/entrypoint.ts index 7667834a2..0c8065a5a 100644 --- a/demos/vite-demo/src/components/__isograph/Query/HomePage/entrypoint.ts +++ b/demos/vite-demo/src/components/__isograph/Query/HomePage/entrypoint.ts @@ -15,56 +15,59 @@ const queryText = 'query HomePage {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "getAllPokemon", - arguments: [ - [ - "take", - { kind: "Literal", value: 232 }, - ], +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "getAllPokemon", + arguments: [ + [ + "take", + { kind: "Literal", value: 232 }, + ], - [ - "offset", - { kind: "Literal", value: 93 }, + [ + "offset", + { kind: "Literal", value: 93 }, + ], ], - ], - concreteType: "Pokemon", - selections: [ - { - kind: "Scalar", - fieldName: "bulbapediaPage", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "forme", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "key", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "num", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "species", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "sprite", - arguments: null, - }, - ], - }, -]; + concreteType: "Pokemon", + selections: [ + { + kind: "Scalar", + fieldName: "bulbapediaPage", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "forme", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "key", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "num", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "species", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "sprite", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__HomePage__param, Query__HomePage__output_type diff --git a/demos/vite-demo/src/components/__isograph/iso.ts b/demos/vite-demo/src/components/__isograph/iso.ts index 8eac0efd9..4b2850233 100644 --- a/demos/vite-demo/src/components/__isograph/iso.ts +++ b/demos/vite-demo/src/components/__isograph/iso.ts @@ -70,5 +70,6 @@ export function iso(_isographLiteralText: string): { throw new Error('iso: Unexpected invocation at runtime. Either the Babel transform ' + 'was not set up, or it failed to identify this call site. Make sure it ' + - 'is being used verbatim as `iso`.'); + 'is being used verbatim as `iso`. If you cannot use the babel transform, ' + + 'set options.no_babel_transform to true in your Isograph config. '); } \ No newline at end of file diff --git a/docs-website/docs/abstract-types.md b/docs-website/docs/abstract-types.md new file mode 100644 index 000000000..0b9619978 --- /dev/null +++ b/docs-website/docs/abstract-types.md @@ -0,0 +1,37 @@ +# Refining from abstract to concrete types + +Fields with an abstract type (e.g. `node`) have special `asConcreteType` fields. These will be null if the field does not have that concrete type. + +For example: + +```jsx +export const UserLink = iso(` + field Actor.ActorGreeting @component { + login + asUser { + twitterUsername + } + } +`)(function UserLinkComponent( + { data }, +) { + return <>Hello {data.login}{ + data.asUser != null + ? ` (who goes by ${data.asUser.twitterUsername ?? ''} on Twitter!)` + : null + } +}); +``` + +The above code will result in the following query text generated: + +```graphql +login +... on User { + twitterUsername +} +``` + +## Data-driven dependencies + +Check out the [data driven dependencies](/docs/data-driven-dependencies/) documentation to see how to combine [`@loadable` fields](/docs/loadable-fields/), [pagination](/docs/pagination/) and `asConcreteType` fields to fetch the minimal amount of data and JavaScript needed! diff --git a/docs-website/docs/conditional-fetching.md b/docs-website/docs/conditional-fetching.md new file mode 100644 index 000000000..45a3185f3 --- /dev/null +++ b/docs-website/docs/conditional-fetching.md @@ -0,0 +1,36 @@ +# Conditionally fetching + +`useLazyReference`, `useImperativeReference`, `useClientSideDefer` and `useImperativeLoadableField` all accept a `FetchOptions` parameter. + +You can use this parameter to control whether to make a network request. For example: + +```jsx +const { fragmentReference: mutationRef, loadFragmentReference: loadMutation } = + useImperativeReference(iso(`entrypoint Mutation.SetTagline`)); + +const onClick = () => { + loadMutation( + // parameters + {}, + // FetchOptions + { + shouldFetch: 'Yes', + }, + ); +}; +``` + +`shouldFetch` can be `"Yes"`, `"No"` or `"IfNecessary"`. + +`"Yes"` forces the network request to be made. `"IfNecessary"` will avoid making the network request if there is sufficient data in the Isograph store to fulfill the request, and `"No"` will not make the network request. + +:::note +This is called a "fetch policy" in Relay. +::: + +## Why would I want `shouldFetch`: `"No"`? + +Two reasons: + +- The hooks `useLazyReference` and `useClientSideDefer` cannot be called conditionally. Passing `"No"` is a way of avoiding the network request, despite calling the hooks unconditionally. +- Otherwise, this isn't a very useful feature currently, because reading a fragment with missing data will cause it to suspend. However, in the future, we will loosen this requirement (i.e. you will be able to read fragments with missing data.) In that world, avoiding making a network request and rendering whatever data happens to be availabe in the Isograph store will be possible, and may even be useful in certain circumstances. diff --git a/docs-website/docs/data-driven-dependencies.md b/docs-website/docs/data-driven-dependencies.md new file mode 100644 index 000000000..d226213a4 --- /dev/null +++ b/docs-website/docs/data-driven-dependencies.md @@ -0,0 +1,163 @@ +# Data Driven Dependencies + +Congratulations! If you have gotten this far, you are about to learn about one of the coolest features of Isograph: the ability to load just the data and JavaScript that you need for a given page. + +To pull this off, we'll combine [`@loadable` fields](/docs/loadable-fields/), [pagination](/docs/pagination/) and [`asConcreteType`](/docs/abstract-types/) fields, so feel free to brush up on those before reading on. + +## Setting the scene + +Imagine you have a news feed containing text, images, videos, polls, events, songs, and albums, and not to mention variations of those that are only shown to users in some experiment or other. It is tremendously wasteful to download all of the JavaScript required to render an object of each type — very few or no users will see all possible variations! + +And not only does that waste your users' bandwidth, it also slows down how fast your page can start, as parsing all of that JavaScript isn't free. + +What can be done about it? Well, we should avoid loading the JavaScript for any news feed item types that we don't encounter. We call this "data driven dependencies", or 3D. + +And you guessed it — Isograph makes that easy! + +## Isograph makes it easy + +Let's see how that's done with Isograph. First, we use the `useConnectionSpecPagination` hook to fetch some news items: + +```jsx +export const Newsfeed = iso(` + field Query.Newsfeed @component { + firstPage: NewsfeedConnection(first: 10) + NewsfeedConnection @loadable + } +`)(function NewsfeedComponent({ data }) { + const pagination = useConnectionSpecPagination( + data.NewsfeedConnection, + data.firstPage.pageInfo, + ); + const repositories = (data.firstPage.edges ?? []).concat(pagination.results); + return repositories.map((data) => { + if (data == null || data.node == null) { + return null; + } + const { node } = data; + return ; + }); +}); + +export const NewsfeedPaginationComponent = iso(` + field Viewer.NewsfeedPaginationComponent($skip: Int!, $limit: Int!) { + newsfeed(skip: $skip, limit: $limit) { + NewsfeedItem + } + } +`)(({ data }) => { + return data.newsfeed; +}); +``` + +Here, we're also pre-fetching the first 10 items (via `firstPage`). + +### `NewsfeedRow` + +In our schema, `newsfeed` has type `NewsfeedItem`, which is a union of `TextItem`, `PhotoItem` and others. Armed with that knowledge, let's go ahead and define `NewsfeedItem`! + +```jsx +export const NewsfeedAdOrBlog = iso(` + field NewsfeedItem.NewsfeedAdOrBlog @component { + asTextItem { + TextItemDisplay + } + asPhotoItem { + PhotoItemDisplay + } + asRareItem { + TopSecretDisplay + } + } +`)(({ data: newsfeedItem }) => { + if (newsfeedItem.asTextItem != null) { + return ; + } else if (newsfeedItem.asPhotoItem != null) { + return ; + } else if (newsfeedItem.asRareItem != null) { + return ; + } + + // oops, it's a type we can't handle! Return null to not impact the + // the UI. + return null; +}); +``` + +That's great! Now, we render something different for each type of newsfeed item we encounter. + +### But wait + +But wait — wasn't the point of this to not load the JavaScript for the types of items we don't encounter? Yes! Let's do that, starting with `RareItem` and `TopSecretDisplay` (though you can, and should, do this for every variant.) + +We start by replacing `TopSecretDisplay` with `TopSecretDisplayWrapper`. (This will not be necessary once [this issue](https://github.com/isographlabs/isograph/issues/273) is tackled!) `TopSecretDisplayWrapper` is defined as follows: + +```jsx +export const TopSecretDisplayWrapper = iso(` + field RareItem.TopSecretDisplayWrapper @component { + TopSecretDisplay @loadable(lazyLoadArtifact: true) + } +`)(({ data }) => { + const { fragmentReference } = useClientSideDefer(data.TopSecretDisplay); + return ( + + + + ); +}); +``` + +Nice! Now, we only download the JavaScript for `TopSecretDisplay` if we actually encounter an item of the type `RareItem`. So, we've saved some bandwidth! + +### All is not well + +Unfortunately, we've introduced a regression. Now, not only is the JavaScript for `TopSecretDisplay` fetched only when needed, but the data for `TopSecretDisplay` is fetched only when needed. But GraphQL already gives us a way to fetch that data only if we encounter a newsfeed item with the `RareItem` type — inline fragments! And that's what our `asRareItem` selection compiles to: + +```graphql +... on RareItem { + # fields needed by TopSecretDisplay +} +``` + +:::note +Making this easy (i.e. by supporting `@loadable(lazyLoadArtifact: true, lazyLoadData: false)` or the like) is on the roadmap! This is a temporary workaround. +::: + +So, is there an inevitable tradeoff? No. We can restructure our app slightly to avoid this problem. We can restructure our `TopSecretDisplay` to take its data separately. Then, we select another field `TopSecretDisplayData` that provides that data! See how this all fits together: + +```jsx +export const NewsfeedAdOrBlog = iso(` + field NewsfeedItem.NewsfeedAdOrBlog @component { + asRareItem { + TopSecretDisplayData + TopSecretDisplayWrapper + } + # etc + } +`)(({ data: newsfeedItem }) => { + if (newsfeedItem.asRareItem != null) { + return ( + + ); + } + // etc +}); +``` + +`TopSecretDisplay` no longer selects any data of its own. + +Nice! Now, we fetch the data for `TopSecretDisplay` as part of the parent query and only if we encounter a newsfeed item with type `RareItem`, and load the JavaScript for `TopSecretDisplay` only if we encounter an item of this type! + +Neat! + +## Conclusion + +So, let's describe what we did. We fetched a list of newsfeed items. We used `asConcreteType` fields to allow us to refine a given concrete type, and then fetched the JavaScript for the given renderer only if we encountered an item of that type! + +And we did it all in userland! + +## PS + +We can also use these techniques (minus the pagination) to conditionally fetch JavaScript for fields that are nullable, and not just for fields that have an interface/union type! diff --git a/docs-website/docs/development-workflow.md b/docs-website/docs/development-workflow.md index 8d0738e31..4be2fa44e 100644 --- a/docs-website/docs/development-workflow.md +++ b/docs-website/docs/development-workflow.md @@ -16,7 +16,7 @@ The node.js and pnpm versions used by Isograph are specified in fields `engines. In order to ensure you are using the correct versions of these you should install `fnm` for your respective operating system by following [this](https://github.com/Schniz/fnm?tab=readme-ov-file#installation) guide. Optionally, configure fnm for your shell by following [this](https://github.com/Schniz/fnm?tab=readme-ov-file#shell-setup) guide. -Now, navigate to the root directory of your Isograph repository and run the following commands one by one: +Now, navigate to the root directory of your Isograph repository and run the following commands: ```bash fnm install --resolve-engines @@ -31,7 +31,7 @@ These commands will install the appropriate node.js and pnpm version used by Iso ### Rust -Isograph currently uses `rustc 1.81.0 (eeb90cda1 2024-09-04)`. Rust is fairly stable and we don't rely on anything crazy, so it should be safe to keep your `rustc` up-to-date. +Isograph is built (in CI) using the latest stable version. Rust is fairly stable and we don't rely on anything crazy, so it should be safe to keep your `rustc` up-to-date. You should also install [`bacon`](https://dystroy.org/bacon/) via `cargo install --locked bacon`. @@ -111,7 +111,7 @@ pnpm i ### Build the Isograph JavaScript libraries for use in demos ```sh -pnpm -r watch-libs +pnpm watch-libs ``` `watch-libs` will watch the source files for changes, and rebuild everything. If you only want to do it once, you can: @@ -152,8 +152,29 @@ pnpm backend ### Starting +- If you haven't yet, build the Isograph javascript libraries for the demos by running the following from root: + +```sh +pnpm i +pnpm watch-libs +``` + +or + +```sh +pnpm i +pnpm -r compile +``` + - Open VSCode in `isograph/vscode-extension` -- Open the "run and debug" sidebar, and click `Run and Debug`. If given a choice, select something related to "Extension development host". +- Run the following in `isograph/vscode-extension`: + +```sh +npm i +npm run build-local +``` + +- Open `src/extension.ts` in your editor, then open the "run and debug" sidebar and click `Run and Debug`. If given a choice, select something related to "Extension development host". - In this new window, open `isograph/demos/pet-demo`. - The VSCode extension should start when you open a JS, JSX, TS or TSX file. @@ -173,6 +194,7 @@ Every commit to `main` results in a build, which you can see in [npm](https://ww ## How to release a new "numbered" version of Isograph to npm - In all package.json files, bump the version number. Don't forget to bump the version number of imports. +- `pnpm i` - `git add . && git commit -m 'v0.1.0' && git tag v0.1.0 && git push` - See [this commit releasing 0.2.0](https://github.com/isographlabs/isograph/commit/e36acab1a018e18bdae0558be08952693af3b6a8) diff --git a/docs-website/docs/isograph-config.md b/docs-website/docs/isograph-config.md index dc92fe619..da4ae4394 100644 --- a/docs-website/docs/isograph-config.md +++ b/docs-website/docs/isograph-config.md @@ -6,7 +6,7 @@ The file should be named `isograph.config.json` and located at the root of your ## Config file contents -An example (complete) Isograph config is as follows: +An example (complete) Isograph config is as follows. It contains default `options`: ```json { @@ -15,7 +15,9 @@ An example (complete) Isograph config is as follows: "schema": "./backend/schema.graphql", "schema_extensions": ["./backend/schema-extension.graphql"], "options": { - "on_invalid_id_type": "error" + "on_invalid_id_type": "error", + "on_missing_babel_transform": "error", + "include_file_extensions_in_import_statements": false } } ``` diff --git a/docs-website/docs/loadable-fields.md b/docs-website/docs/loadable-fields.md index dc04dd809..561976a2d 100644 --- a/docs-website/docs/loadable-fields.md +++ b/docs-website/docs/loadable-fields.md @@ -2,11 +2,13 @@ ## Overview -Client fields can be selected loadably. If a client field is selected loadably, the resolver will not receive the client field result directly, but will instead receive a `LoadableField`. +Loadable fields are a fundamental Isograph concept. -The `LoadableField` is a wrapper around a function that, when called, will make a network request for the data needed by the client field. Making the network request creates a `FragmentReference` that must be disposed, in order to prevent memory leaks. +Client fields can be selected loadably (e.g. `BlogBody @loadable`). This means that the data for that `BlogBody` client field will not be included in the parent network request. Instead, the outer client field (`BlogPostDisplay`) will receive a `LoadableField` in place of that loadably selected client field. -As a result, usually, you should not call the `LoadableField` directly. Instead, pass it to a hook that knows what to do with it! +This `LoadableField` is a wrapper around a function that, when called, will make a network request for the data needed by the `BlogPost` client field. + +You usually would not call the `LoadableField` yourself. Instead, pass it to a hook that knows what to do with it! ## Basic walk-through @@ -169,4 +171,8 @@ export const BlogPostDisplay = iso(` ## Pagination -See [the pagination docs](../pagination). +Pagination is also built on loadable fields. See [the pagination docs](../pagination). + +## Data-driven dependencies + +Check out the [data driven dependencies](/docs/data-driven-dependencies/) documentation to see how to combine `@loadable` fields, [pagination](/docs/pagination/) and [`asConcreteType` fields](/docs/abstract-types/) to fetch the minimal amount of data and JavaScript needed! diff --git a/docs-website/docs/mutation.md b/docs-website/docs/mutation.md new file mode 100644 index 000000000..c4932050b --- /dev/null +++ b/docs-website/docs/mutation.md @@ -0,0 +1,184 @@ +# Mutations + +In Isograph, mutations aren't special. The distinguishing feature of mutations is often that you want to make the network request at a specific time, for example, in response to a user clicking a button. This document describes how to make network requests in response to events, which you can use to trigger a mutation. Okay, onward! + +:::note +Examples in this document are taken from [this file](https://github.com/isographlabs/isograph/blob/91d3020f7f28a9cd91c250a9457f6a6bc7fd1562/demos/pet-demo/src/components/PetTaglineCard.tsx). +::: + +## Walkthrough + +### Defining the mutation field + +First, define a client field on the `Mutation` object that calls the mutation you care about: + +```js +export const setTagline = iso(` + field Mutation.SetTagline($input: SetPetTaglineParams!) { + set_pet_tagline(input: $input) { + pet { + tagline + } + } + } +`)((()) => {}); +``` + +Make sure you select the fields that you want refetched and written into the store. In this case, we want to see the updated pet's tagline. We also return the data. + +:::note +Note also that we're naming the field `Mutation.SetTagline`, _not_ `Mutation.set_pet_tagline`. There already is a field named `Mutation.set_pet_tagline`, defined in the schema. So, if you attempt to define a client field named `Mutation.set_pet_tagline`, the Isograph compiler will emit an error and refuse to compile! +::: + +### Calling the mutation + +Next, call `useImperativeReference`. This gives you back a fragment reference and a `loadFragmentReference` function: + +```jsx +import { useImperativeReference } from '@isograph/react'; +import { UNASSIGNED_STATE } from '@isograph/react-disposable-state'; + +export const PetTaglineCard = iso(` + field Pet.PetTaglineCard @component { + id + tagline + } +`)(function PetTaglineCardComponent({ data: pet }) { + const { + fragmentReference: mutationRef, + loadFragmentReference: loadMutation, + } = useImperativeReference(iso(`entrypoint Mutation.SetTagline`)); + // ... +} +``` + +Next, call `loadFragmentReference` (`loadMutation` in this example) when a user clicks! + +```jsx +{ + mutationRef === UNASSIGNED_STATE ? ( + + ) : null; +} +``` + +Since we only want to set the tagline once, we check that `mutation === UNASSIGNED_STATE` before showing the button. + +### Reading the results + +What about reading the results of the mutation? There are two good ways to do this, and two additional ways that will work in the future. + +First, we can wait until the network response completes and see the component re-render with the updated tagline. + +For the second method, we can modify the mutation field as follows: + +```js +export const setTagline = iso(` + field Mutation.SetTagline($input: SetPetTaglineParams!) @component { + set_pet_tagline(input: $input) { + pet { + tagline + } + } + } +`)((({data})) => { + return ( + <> + Nice! You updated the pet's tagline to{' '} + {data.set_pet_tagline?.pet?.tagline}! + + ); +}); +``` + +Here, we add the `@component` annotation and return some JSX. + +Now, we can use this! Modify the `PetTaglineCardComponent` component as follows: + +```js +{ + mutationRef === UNASSIGNED_STATE ? ( + + ) : ( + + + + ); +} +``` + +#### Future methods + +The following methods will be available in the future: + +- When reading a fragment reference is not a hook (see [this issue](https://github.com/isographlabs/isograph/issues/273)), you should be able to read the fragment conditionally in the parent component. + +### Being notified when a mutation completes + +The `loadFragmentReference` (aka `loadMutation` in these docs) function accepts a `FetchOptions` object, which can include an `onCompleted` callback, e.g.: + +```jsx +loadMutation( + { + input: { + id: pet.id, + tagline: 'SUPER DOG', + }, + }, + { + onCompleted: () => { + console.log('Done setting'); + }, + }, +); +``` + +This `onCompleted` callback takes no parameters. **If you need to receive the results of the mutation, please comment on [this issue](https://github.com/isographlabs/isograph/issues/277).** + +### Being notified when a mutation errors out + +`FetchOptions` also accepts an `onError` callback: + +```jsx +loadMutation( + { + input: { + id: pet.id, + tagline: 'SUPER DOG', + }, + }, + { + onError: () => { + console.log('Oops'); + }, + }, +); +``` + +### Appending to a list in response to a mutation + +There are no good APIs to do this, currently. If you need this behavior, please [comment on this issue](https://github.com/isographlabs/isograph/issues/278) or reach out in the Discord. We're happy to help you out! diff --git a/docs-website/docs/pagination.md b/docs-website/docs/pagination.md index 7270a8ccb..b36b19f48 100644 --- a/docs-website/docs/pagination.md +++ b/docs-website/docs/pagination.md @@ -3,12 +3,20 @@ Loadable fields can also be used to paginate. :::note -This API is likely to get simplified substantially, as we support `@loadable` on linked server fields. We will also add support for Relay-style pagination. +This documentation demonstrates the `useSkipLimitPagination` hook. You can also use `useConnectionSpecPagination` if your connection field conforms to the [Relay connection spec](https://facebook.github.io/relay/graphql/connections.htm). +::: + +:::note +This API is likely to get simplified substantially, as we support `@loadable` on linked server fields. +::: + +:::note +See [this file](https://github.com/isographlabs/isograph/blob/dc7beaeab163159a9b38dbe3cbd731f7a03b3e38/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx) for a real-world example. ::: ## Walk through -Define a client field (without `@component`) that returns an array of items and accepts `skip` and `limit` parameters. It can accept other params: +First, define a client field (without `@component`) that returns an array of items and accepts `skip` and `limit` parameters. It can accept other params: ```tsx import { iso } from '@iso'; @@ -77,3 +85,35 @@ export const PetDetailDeferredRouteComponent = iso(` ); }); ``` + +## Prefetching initial pages + +Each pagination hook accepts a second `initialState` parameter. You can fetch data as part of the parent query (or anywhere, in fact!) and pass the appropriate value to that `initialState`. Consider: + +```jsx +export const Newsfeed = iso(` + field Query.Newsfeed @component { + viewer { + newsfeed(skip: 0, limit: 6) { + NewsfeedAdOrBlog + } + NewsfeedPaginationComponent @loadable + } + } +`)(function PetDetailRouteComponent({ data }) { + const viewer = data.viewer; + + const paginationState = useSkipLimitPagination( + viewer.NewsfeedPaginationComponent, + { skip: viewer.newsfeed.length }, + ); + + const newsfeedItems = viewer.newsfeed.concat(paginationState.results); + + // ... +}); +``` + +## Data-driven dependencies + +Check out the [data driven dependencies](/docs/data-driven-dependencies/) documentation to see how to combine [`@loadable` fields](/docs/loadable-fields/), pagination and [`asConcreteType` fields](/docs/abstract-types/) to fetch the minimal amount of data and JavaScript needed! diff --git a/docs-website/docs/parameters.md b/docs-website/docs/parameters.md new file mode 100644 index 000000000..ec290d20e --- /dev/null +++ b/docs-website/docs/parameters.md @@ -0,0 +1,63 @@ +# Parameters + +## Parameters and client fields + +Unlike in GraphQL, there are no global variables (yet!) in Isograph. Instead, all parameters used in a client field must be defined in that client field, and no parameters can be unused. Example: + +```jsx +export const PetDetailDeferredRouteComponent = iso(` + field Query.PetDetailRoute($id: ID!) @component { + pet(id: $id) { + PetDetail + } + } +`)(function PetDetailRouteComponent({ data }) { + // ... +}); +``` + +## Accessing parameters at runtime + +The parameters with which a client field was read can be accessed as part of that first parameter. For example: + +```jsx +export const PetDetailDeferredRouteComponent = iso(` + field Query.PetDetailRoute($id: ID!) @component { + pet(id: $id) { + PetDetail + } + } +`)(function PetDetailRouteComponent({ data, parameters }) { + console.log('hello from pet ' + parameters.id + '!'); + // ... +}); +``` + +## Loadable fields are variables + +Parameters can be omitted in isograph literals from loadably selected client fields. Those missing variables become parameters that you must pass when making the network request (i.e. loading the field): + +```jsx +export const BlogItem = iso(` + field BlogItem.BlogItemDisplay @component { + # Assume that BlogItemMoreDetail accepts an includeImages: Boolean! parameter + # that we are skipping here, which is allowed because the field is selected + # loadably: + BlogItemMoreDetail @loadable(lazyLoadArtifact: true) + } +`)(({ data: blogItem }) => { + const { fragmentReference, loadField } = useImperativeLoadableField( + blogItem.BlogItemMoreDetail, + ); + + const loadBlogItem = () => + loadField({ + // now, includeImages must be passed here: + includeImages: true, + }); +}); +``` + +## Typechecking + +There is no typechecking of variables, except to inasmuch as nullable variables are allowed to be missing. This feature is coming soon! diff --git a/docs-website/docs/quickstart.md b/docs-website/docs/quickstart.md index f69b88b0e..2a1cfd963 100644 --- a/docs-website/docs/quickstart.md +++ b/docs-website/docs/quickstart.md @@ -32,6 +32,10 @@ yarn add --dev @isograph/babel-plugin yarn add @isograph/react ``` +:::tip +For each of these packages, you should install the latest `main` version, which can be found [here](https://www.npmjs.com/package/@isograph/react?activeTab=versions). So e.g. `yarn add --dev @isograph/compiler@0.0.0-main-c8c7d9f2`. Lots of great features have shipped since we cut a release! +::: + Installing the compiler also adds the command `yarn iso` and `yarn iso --watch`. But hang tight — before this command works, you'll need to create a folder, download your schema and create an `isograph.config.json` file! ## Create an `isograph.config.json` @@ -146,6 +150,11 @@ function makeNetworkRequest( const json = await response.json(); if (response.ok) { + if (json.errors != null) { + throw new Error('GraphQLError', { + cause: json.errors, + }); + } return json; } else { throw new Error('NetworkError', { @@ -462,6 +471,6 @@ Now, if you refresh, the UI will be divided into subcomponents and look exactly Congratulations! You just built your first Isograph app. -Want more? Try extracting the sorted list of films into its own client field (no need to use `@component` for this one.) Use hooks in your components (they work!) Check out the [magic mutation fields](/docs/expose-field-directives/) documentation to learn about how Isograph lets you update your data. +Want more? Try extracting the sorted list of films into its own client field (no need to use `@component` for this one.) Use hooks in your components (they work!) Check out the [mutation](/docs/mutation/), [pagination](/docs/pagination/), and the [loadable fields](/docs/loadable-fields/) documentation! Or, [join the Discord](https://discord.gg/qcHUxb6deQ)! diff --git a/docs-website/docs/workflow.md b/docs-website/docs/workflow.md index a46032135..f83436622 100644 --- a/docs-website/docs/workflow.md +++ b/docs-website/docs/workflow.md @@ -18,7 +18,7 @@ A babel plugin transforms your `iso` literals. For example, `iso` entrypoint lit ## Debugging - You can use React dev tools. In particular, searching for `@component` will show you where all of your resolvers defined with `@component` are rendered. -- If you call `registerLogFunction` on your environment, you can get additional logs. +- The environment constructor takes an optional logger. Pass `console.log` to have Isograph print out everything it is doing, if you're curious! ## Workflow for modifying Isograph diff --git a/docs-website/sidebars.ts b/docs-website/sidebars.ts index 962853fe3..00cbb01aa 100644 --- a/docs-website/sidebars.ts +++ b/docs-website/sidebars.ts @@ -18,9 +18,11 @@ const sidebars: SidebarsConfig = { 'isograph-config', 'loadable-fields', 'pagination', - 'refetching', - 'expose-field-directives', - 'isograph-rules', + 'mutation', + 'conditional-fetching', + 'abstract-types', + 'data-driven-dependencies', + 'parameters', 'faq', { type: 'category', @@ -37,8 +39,16 @@ const sidebars: SidebarsConfig = { label: 'Design docs', items: ['design-docs/incremental-compilation'], }, - 'development-workflow', - 'backlog', + { + type: 'category', + label: 'Miscellaneous', + items: ['isograph-rules', 'development-workflow', 'backlog'], + }, + { + type: 'category', + label: 'Deprecated features', + items: ['expose-field-directives', 'refetching'], + }, ], }; diff --git a/libs/isograph-babel-plugin/compileTag.js b/libs/isograph-babel-plugin/compileTag.js index 17d5ddb64..4f56f1c6a 100644 --- a/libs/isograph-babel-plugin/compileTag.js +++ b/libs/isograph-babel-plugin/compileTag.js @@ -17,11 +17,15 @@ function compileTag(t, path, config) { // This throws if the tag is invalid compileImportStatement(t, path, type, field, 'entrypoint', config); } else if (keyword === 'field') { - if ( - t.isCallExpression(path.parentPath.node) && - path.parentPath.node.arguments.length === 1 - ) { - path.parentPath.replaceWith(path.parentPath.node.arguments[0]); + if (t.isCallExpression(path.parentPath.node)) { + const firstArg = path.parentPath.node.arguments[0]; + if (path.parentPath.node.arguments.length === 1 && firstArg != null) { + path.parentPath.replaceWith(firstArg); + } else { + throw new Error( + 'Invalid iso tag usage. The iso function should be passed at most one argument.', + ); + } } else { path.replaceWith( t.arrowFunctionExpression([t.identifier('x')], t.identifier('x')), @@ -45,26 +49,28 @@ const typeAndFieldRegex = new RegExp( * @param {babel.NodePath} path * */ function getTypeAndField(path) { - if (path.node.arguments.length !== 1) { + const firstArg = path.node.arguments[0]; + if (path.node.arguments.length !== 1 || firstArg == null) { throw new Error( `BabelPluginIsograph: Iso invocation require one parameter, found ${path.node.arguments.length}`, ); } - if (path.node.arguments[0].type !== 'TemplateLiteral') { + if (firstArg.type !== 'TemplateLiteral') { throw new Error( 'BabelPluginIsograph: Only template literals are allowed in iso fragments.', ); } - const quasis = path.node.arguments[0].quasis; - if (quasis.length !== 1) { + const quasis = firstArg.quasis; + const firstQuasi = quasis[0]; + if (quasis.length !== 1 || firstQuasi == null) { throw new Error( 'BabelPluginIsograph: Substitutions are not allowed in iso fragments.', ); } - const content = quasis[0].value.raw; + const content = firstQuasi.value.raw; const typeAndField = typeAndFieldRegex.exec(content); const keyword = typeAndField?.[1]; diff --git a/libs/isograph-babel-plugin/package.json b/libs/isograph-babel-plugin/package.json index 0aca74dfd..364f4bebb 100644 --- a/libs/isograph-babel-plugin/package.json +++ b/libs/isograph-babel-plugin/package.json @@ -2,7 +2,7 @@ "name": "@isograph/babel-plugin", "description": "A Babel plugin for use with Isograph applications.", "homepage": "https://isograph.dev", - "version": "0.2.0", + "version": "0.3.0", "keywords": [ "graphql", "isograph", diff --git a/libs/isograph-compiler/isograph-config-schema.json b/libs/isograph-compiler/isograph-config-schema.json new file mode 100644 index 000000000..69599ac44 --- /dev/null +++ b/libs/isograph-compiler/isograph-config-schema.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsographProjectConfig", + "type": "object", + "required": [ + "project_root", + "schema" + ], + "properties": { + "$schema": { + "description": "The user may hard-code the JSON Schema for their version of the config.", + "type": [ + "string", + "null" + ] + }, + "artifact_directory": { + "description": "The relative path to the folder where the compiler should create artifacts Defaults to the project_root directory.", + "type": [ + "string", + "null" + ] + }, + "options": { + "description": "Various options of less importance", + "allOf": [ + { + "$ref": "#/definitions/ConfigFileOptions" + } + ] + }, + "project_root": { + "description": "The relative path to the folder where the compiler should look for Isograph literals", + "type": "string" + }, + "schema": { + "description": "The relative path to the GraphQL schema", + "type": "string" + }, + "schema_extensions": { + "description": "The relative path to schema extensions", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "definitions": { + "ConfigFileOptionalValidationLevel": { + "oneOf": [ + { + "description": "If this validation error is encountered, it will be ignored", + "type": "string", + "enum": [ + "ignore" + ] + }, + { + "description": "If this validation error is encountered, a warning will be issued", + "type": "string", + "enum": [ + "warn" + ] + }, + { + "description": "If this validation error is encountered, the compilation will fail", + "type": "string", + "enum": [ + "error" + ] + } + ] + }, + "ConfigFileOptions": { + "type": "object", + "properties": { + "include_file_extensions_in_import_statements": { + "default": false, + "type": "boolean" + }, + "no_babel_transform": { + "default": false, + "type": "boolean" + }, + "on_invalid_id_type": { + "$ref": "#/definitions/ConfigFileOptionalValidationLevel" + } + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/libs/isograph-compiler/package.json b/libs/isograph-compiler/package.json index ca8515191..8b61eabef 100644 --- a/libs/isograph-compiler/package.json +++ b/libs/isograph-compiler/package.json @@ -1,6 +1,6 @@ { "name": "@isograph/compiler", - "version": "0.2.0", + "version": "0.3.0", "description": "Installs the `yarn iso` command, which runs the Isograph compiler.", "homepage": "https://isograph.dev", "main": "index.js", diff --git a/libs/isograph-disposable-types/package.json b/libs/isograph-disposable-types/package.json index 6222efb69..26352bed8 100644 --- a/libs/isograph-disposable-types/package.json +++ b/libs/isograph-disposable-types/package.json @@ -1,6 +1,6 @@ { "name": "@isograph/disposable-types", - "version": "0.2.0", + "version": "0.3.0", "description": "Common types for disposable items", "homepage": "https://isograph.dev", "main": "dist/index.js", diff --git a/libs/isograph-react-disposable-state/package.json b/libs/isograph-react-disposable-state/package.json index 865cbb8c0..06f831afe 100644 --- a/libs/isograph-react-disposable-state/package.json +++ b/libs/isograph-react-disposable-state/package.json @@ -1,6 +1,6 @@ { "name": "@isograph/react-disposable-state", - "version": "0.2.0", + "version": "0.3.0", "description": "Primitives for managing disposable state in React", "homepage": "https://isograph.dev", "main": "dist/index.js", @@ -18,13 +18,14 @@ "tsc": "tsc" }, "dependencies": { - "@isograph/disposable-types": "0.2.0" + "@isograph/disposable-types": "*" }, "peerDependencies": { "react": "18.3.1" }, "devDependencies": { "@types/react": "18.3.1", + "@types/react-test-renderer": "^18.3.0", "react-test-renderer": "^18.2.0", "typescript": "5.6.3" }, diff --git a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.test.tsx b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.test.tsx index f743da216..d8415b931 100644 --- a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.test.tsx +++ b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.test.tsx @@ -2,7 +2,7 @@ import { describe, test, vi, expect, assert } from 'vitest'; import { ParentCache } from './ParentCache'; import { ItemCleanupPair } from '@isograph/disposable-types'; import { useCachedResponsivePrecommitValue } from './useCachedResponsivePrecommitValue'; -import React from 'react'; +import React, { StrictMode, type ReactElement } from 'react'; import { create } from 'react-test-renderer'; import { CacheItem, CacheItemState } from './CacheItem'; @@ -51,9 +51,10 @@ function promiseAndResolver() { // The fact that sometimes we need to render in concurrent mode and sometimes // not is a bit worrisome. -async function awaitableCreate(Component, isConcurrent) { +async function awaitableCreate(Component: ReactElement, isConcurrent) { const element = create( - Component, + {Component}, + // @ts-expect-error isConcurrent ? { unstable_isConcurrent: true } : undefined, ); await shortPromise(); diff --git a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts index cbe3c86f2..21c2c9c9c 100644 --- a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts +++ b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts @@ -51,6 +51,14 @@ export function useCachedResponsivePrecommitValue( const lastCommittedParentCache = useRef | null>(null); useEffect(() => { + // react reruns all `useEffect` in HMR since it doesn't know if the + // code inside of useEffect has changed. Since this is a library + // user can't change this code so we are safe to skip this rerun. + // This also prevents `useEffect` from running twice in Strict Mode. + if (lastCommittedParentCache.current === parentCache) { + return; + } + lastCommittedParentCache.current = parentCache; // On commit, cacheItem may be disposed, because during the render phase, // we only temporarily retained the item, and the temporary retain could have diff --git a/libs/isograph-react-disposable-state/src/useDisposableState.ts b/libs/isograph-react-disposable-state/src/useDisposableState.ts index 46a63fe81..d5e979459 100644 --- a/libs/isograph-react-disposable-state/src/useDisposableState.ts +++ b/libs/isograph-react-disposable-state/src/useDisposableState.ts @@ -22,7 +22,6 @@ export function useDisposableState( const preCommitItem = useCachedResponsivePrecommitValue( parentCache, (pair) => { - itemCleanupPairRef.current?.[1](); itemCleanupPairRef.current = pair; }, ); @@ -47,14 +46,23 @@ export function useDisposableState( [stateFromDisposableStateHook], ); - useEffect(function cleanupItemCleanupPairRefIfSetStateNotCalled() { - return () => { - if (itemCleanupPairRef.current !== null) { - itemCleanupPairRef.current[1](); - itemCleanupPairRef.current = null; + const lastCommittedParentCache = useRef | null>(null); + + useEffect( + function cleanupItemCleanupPairRefIfSetStateNotCalled() { + if (lastCommittedParentCache.current === parentCache) { + return; } - }; - }, []); + lastCommittedParentCache.current = parentCache; + + return () => { + if (itemCleanupPairRef.current !== null) { + itemCleanupPairRef.current[1](); + } + }; + }, + [parentCache], + ); // Safety: we can be in one of three states. Pre-commit, in which case // preCommitItem is assigned, post-commit but before setState has been diff --git a/libs/isograph-react-disposable-state/src/useLazyDisposableState.test.tsx b/libs/isograph-react-disposable-state/src/useLazyDisposableState.test.tsx index 9d206cd63..38f4c3679 100644 --- a/libs/isograph-react-disposable-state/src/useLazyDisposableState.test.tsx +++ b/libs/isograph-react-disposable-state/src/useLazyDisposableState.test.tsx @@ -1,5 +1,5 @@ import { ItemCleanupPair } from '@isograph/disposable-types'; -import React, { useEffect, useState } from 'react'; +import React, { StrictMode, useEffect, useState } from 'react'; import { create } from 'react-test-renderer'; import { describe, expect, test, vi } from 'vitest'; import { ParentCache } from './ParentCache'; @@ -55,7 +55,12 @@ describe('useLazyDisposableState', async () => { return null; } - const root = create(, { unstable_isConcurrent: true }); + const root = create( + + + , + { unstable_isConcurrent: true }, + ); await committed.promise; expect(cache1.disposeItem).toHaveBeenCalled(); expect(cache1.cache.factory).toHaveBeenCalledOnce(); diff --git a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts index 5c251fd32..889be211f 100644 --- a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts +++ b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts @@ -5,8 +5,8 @@ import { useEffect, useRef } from 'react'; import type { ItemCleanupPair } from '@isograph/isograph-disposable-types'; import { ParentCache } from './ParentCache'; -import { type UnassignedState } from './useUpdatableDisposableState'; import { useCachedResponsivePrecommitValue } from './useCachedResponsivePrecommitValue'; +import { type UnassignedState } from './useUpdatableDisposableState'; /** * useLazyDisposableState @@ -23,16 +23,24 @@ export function useLazyDisposableState( state: T; } { const itemCleanupPairRef = useRef | null>(null); - const preCommitItem = useCachedResponsivePrecommitValue( parentCache, (pair) => { - itemCleanupPairRef.current?.[1](); itemCleanupPairRef.current = pair; }, ); + const lastCommittedParentCache = useRef | null>(null); useEffect(() => { + // react reruns all `useEffect` in HMR since it doesn't know if the + // code inside of useEffect has changed. Since this is a library + // user can't change this code so we are safe to skip this rerun. + // This also prevents `useEffect` from running twice in Strict Mode. + if (lastCommittedParentCache.current === parentCache) { + return; + } + lastCommittedParentCache.current = parentCache; + return () => { const cleanupFn = itemCleanupPairRef.current?.[1]; // TODO confirm useEffect is called in order. @@ -43,7 +51,7 @@ export function useLazyDisposableState( } return cleanupFn(); }; - }, []); + }, [parentCache]); const returnedItem = preCommitItem?.state ?? itemCleanupPairRef.current?.[0]; diff --git a/libs/isograph-react-disposable-state/src/useUpdatableDisposableState.test.tsx b/libs/isograph-react-disposable-state/src/useUpdatableDisposableState.test.tsx index e74c132c8..48459175e 100644 --- a/libs/isograph-react-disposable-state/src/useUpdatableDisposableState.test.tsx +++ b/libs/isograph-react-disposable-state/src/useUpdatableDisposableState.test.tsx @@ -1,5 +1,5 @@ import { describe, test, vi, expect } from 'vitest'; -import React from 'react'; +import React, { StrictMode } from 'react'; import { create } from 'react-test-renderer'; import { useUpdatableDisposableState, @@ -45,7 +45,8 @@ function promiseAndResolver() { // not is a bit worrisome. async function awaitableCreate(Component, isConcurrent) { const element = create( - Component, + {Component}, + isConcurrent ? { unstable_isConcurrent: true } : undefined, ); await shortPromise(); diff --git a/libs/isograph-react/isograph.config.json b/libs/isograph-react/isograph.config.json index 01e6a145d..168adf9b9 100644 --- a/libs/isograph-react/isograph.config.json +++ b/libs/isograph-react/isograph.config.json @@ -1,4 +1,5 @@ { + "$schema": "../isograph-compiler/isograph-config-schema.json", "project_root": "./src/tests", "schema": "./schema.graphql", "options": { diff --git a/libs/isograph-react/package.json b/libs/isograph-react/package.json index eadbf2dfa..7db940494 100644 --- a/libs/isograph-react/package.json +++ b/libs/isograph-react/package.json @@ -1,6 +1,6 @@ { "name": "@isograph/react", - "version": "0.2.0", + "version": "0.3.0", "description": "Use Isograph with React", "homepage": "https://isograph.dev", "main": "dist/index.js", @@ -19,7 +19,7 @@ "iso-watch": "../../target/debug/isograph_cli --config ./isograph.config.json --watch" }, "dependencies": { - "@isograph/disposable-types": "0.2.0", + "@isograph/disposable-types": "*", "@isograph/react-disposable-state": "*", "@isograph/reference-counted-pointer": "*" }, diff --git a/libs/isograph-react/src/core/areEqualWithDeepComparison.ts b/libs/isograph-react/src/core/areEqualWithDeepComparison.ts index 2a4bb5570..8574cbf0e 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/cache.ts b/libs/isograph-react/src/core/cache.ts index 2e796c250..7c3430d26 100644 --- a/libs/isograph-react/src/core/cache.ts +++ b/libs/isograph-react/src/core/cache.ts @@ -16,7 +16,7 @@ import { } from './IsographEnvironment'; import { IsographEntrypoint, - NormalizationAst, + type NormalizationAstNodes, NormalizationInlineFragment, NormalizationLinkedField, NormalizationScalarField, @@ -34,7 +34,7 @@ import { mergeObjectsUsingReaderAst } from './areEqualWithDeepComparison'; import { maybeMakeNetworkRequest } from './makeNetworkRequest'; import { wrapResolvedValue } from './PromiseWrapper'; import { logMessage } from './logging'; -import { DEFAULT_SHOULD_FETCH_VALUE, FetchOptions } from './check'; +import { FetchOptions } from './check'; export const TYPENAME_FIELD_NAME = '__typename'; @@ -89,18 +89,17 @@ export function getOrCreateCacheForArtifact< environment: IsographEnvironment, entrypoint: IsographEntrypoint, variables: ExtractParameters, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ): ParentCache> { const cacheKey = entrypoint.networkRequestInfo.queryText + JSON.stringify(stableCopy(variables)); const factory = () => { - const shouldFetch = fetchOptions?.shouldFetch ?? DEFAULT_SHOULD_FETCH_VALUE; const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest( environment, entrypoint, variables, - shouldFetch, + fetchOptions, ); const itemCleanupPair: ItemCleanupPair< @@ -143,7 +142,7 @@ export type NetworkResponseObject = { export function normalizeData( environment: IsographEnvironment, - normalizationAst: NormalizationAst, + normalizationAst: NormalizationAstNodes, networkResponse: NetworkResponseObject, variables: Variables, nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[], @@ -377,7 +376,7 @@ export type EncounteredIds = Map>; */ function normalizeDataIntoRecord( environment: IsographEnvironment, - normalizationAst: NormalizationAst, + normalizationAst: NormalizationAstNodes, networkResponseParentRecord: NetworkResponseObject, targetParentRecord: StoreRecord, targetParentRecordLink: Link, @@ -517,7 +516,7 @@ function normalizeLinkedField( const dataIds: (Link | null)[] = []; for (let i = 0; i < networkResponseData.length; i++) { const networkResponseObject = networkResponseData[i]; - if (networkResponseObject === null) { + if (networkResponseObject == null) { dataIds.push(null); continue; } diff --git a/libs/isograph-react/src/core/check.ts b/libs/isograph-react/src/core/check.ts index 24d5234d5..6ed25c0a4 100644 --- a/libs/isograph-react/src/core/check.ts +++ b/libs/isograph-react/src/core/check.ts @@ -1,5 +1,5 @@ import { getParentRecordKey } from './cache'; -import { NormalizationAst } from './entrypoint'; +import { NormalizationAstNodes } from './entrypoint'; import { Variables } from './FragmentReference'; import { getLink, @@ -13,8 +13,10 @@ export type ShouldFetch = 'Yes' | 'No' | 'IfNecessary'; export const DEFAULT_SHOULD_FETCH_VALUE: ShouldFetch = 'IfNecessary'; -export type FetchOptions = { +export type FetchOptions = { shouldFetch?: ShouldFetch; + onComplete?: (data: TReadOutData) => void; + onError?: () => void; }; export type CheckResult = @@ -28,7 +30,7 @@ export type CheckResult = export function check( environment: IsographEnvironment, - normalizationAst: NormalizationAst, + normalizationAst: NormalizationAstNodes, variables: Variables, root: Link, ): CheckResult { @@ -51,7 +53,7 @@ export function check( function checkFromRecord( environment: IsographEnvironment, - normalizationAst: NormalizationAst, + normalizationAst: NormalizationAstNodes, variables: Variables, record: StoreRecord, recordLink: Link, diff --git a/libs/isograph-react/src/core/componentCache.ts b/libs/isograph-react/src/core/componentCache.ts index 8cf6ed2d1..754feafd3 100644 --- a/libs/isograph-react/src/core/componentCache.ts +++ b/libs/isograph-react/src/core/componentCache.ts @@ -47,13 +47,11 @@ export function getOrCreateCachedComponent( rootLink: fragmentReference.root, }); - const firstParameter = { - data, - parameters: fragmentReference.variables, - }; - return readerWithRefetchQueries.readerArtifact.resolver( - firstParameter, + { + data, + parameters: fragmentReference.variables, + }, additionalRuntimeProps, ); } diff --git a/libs/isograph-react/src/core/entrypoint.ts b/libs/isograph-react/src/core/entrypoint.ts index 694fc7286..a935ef0d8 100644 --- a/libs/isograph-react/src/core/entrypoint.ts +++ b/libs/isograph-react/src/core/entrypoint.ts @@ -16,18 +16,19 @@ export type ReaderWithRefetchQueries< readonly nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[]; }; -export type NetworkRequestInfo = { +export type NetworkRequestInfo = { readonly kind: 'NetworkRequestInfo'; readonly queryText: string; - readonly normalizationAst: NormalizationAst; + readonly normalizationAst: TNormalizationAst; }; // This type should be treated as an opaque type. export type IsographEntrypoint< TReadFromStore extends { parameters: object; data: object }, TClientFieldValue, + TNormalizationAst = NormalizationAst, > = { readonly kind: 'Entrypoint'; - readonly networkRequestInfo: NetworkRequestInfo; + readonly networkRequestInfo: NetworkRequestInfo; readonly readerWithRefetchQueries: ReaderWithRefetchQueries< TReadFromStore, TClientFieldValue @@ -50,7 +51,13 @@ export type NormalizationAstNode = | NormalizationScalarField | NormalizationLinkedField | NormalizationInlineFragment; -export type NormalizationAst = ReadonlyArray; + +export type NormalizationAstNodes = ReadonlyArray; + +export type NormalizationAst = { + kind: 'NormalizationAst'; + selections: NormalizationAstNodes; +}; export type NormalizationScalarField = { readonly kind: 'Scalar'; @@ -62,20 +69,20 @@ export type NormalizationLinkedField = { readonly kind: 'Linked'; readonly fieldName: string; readonly arguments: Arguments | null; - readonly selections: NormalizationAst; + readonly selections: NormalizationAstNodes; readonly concreteType: TypeName | null; }; export type NormalizationInlineFragment = { readonly kind: 'InlineFragment'; readonly type: string; - readonly selections: NormalizationAst; + readonly selections: NormalizationAstNodes; }; // This is more like an entrypoint, but one specifically for a refetch query/mutation export type RefetchQueryNormalizationArtifact = { readonly kind: 'RefetchQuery'; - readonly networkRequestInfo: NetworkRequestInfo; + readonly networkRequestInfo: NetworkRequestInfo; readonly concreteType: TypeName; }; diff --git a/libs/isograph-react/src/core/garbageCollection.ts b/libs/isograph-react/src/core/garbageCollection.ts index 1442a179d..4a6ceac81 100644 --- a/libs/isograph-react/src/core/garbageCollection.ts +++ b/libs/isograph-react/src/core/garbageCollection.ts @@ -9,10 +9,10 @@ import { type TypeName, } from './IsographEnvironment'; import { getParentRecordKey } from './cache'; -import { NormalizationAst } from './entrypoint'; +import { NormalizationAstNodes } from './entrypoint'; export type RetainedQuery = { - readonly normalizationAst: NormalizationAst; + readonly normalizationAst: NormalizationAstNodes; readonly variables: {}; readonly root: Link; }; @@ -108,7 +108,7 @@ function recordReachableIdsFromRecord( store: IsographStore, currentRecord: StoreRecord, mutableRetainedIds: RetainedIds, - selections: NormalizationAst, + selections: NormalizationAstNodes, variables: Variables | null, ) { for (const selection of selections) { diff --git a/libs/isograph-react/src/core/logging.ts b/libs/isograph-react/src/core/logging.ts index 379a26092..5e9ace9e3 100644 --- a/libs/isograph-react/src/core/logging.ts +++ b/libs/isograph-react/src/core/logging.ts @@ -7,7 +7,7 @@ import { } from './IsographEnvironment'; import { IsographEntrypoint, - NormalizationAst, + type NormalizationAstNodes, RefetchQueryNormalizationArtifact, } from './entrypoint'; import { FragmentReference, Variables } from './FragmentReference'; @@ -25,7 +25,7 @@ export type LogMessage = } | { kind: 'AboutToNormalize'; - normalizationAst: NormalizationAst; + normalizationAst: NormalizationAstNodes; networkResponse: NetworkResponseObject; variables: Variables; } @@ -60,6 +60,11 @@ export type LogMessage = networkResponse: any; networkRequestId: string; } + | { + kind: 'ReceivedNetworkError'; + error: any; + networkRequestId: string; + } | { kind: 'MissingFieldHandlerCalled'; root: Link; diff --git a/libs/isograph-react/src/core/makeNetworkRequest.ts b/libs/isograph-react/src/core/makeNetworkRequest.ts index 200ead4fc..b29cd68eb 100644 --- a/libs/isograph-react/src/core/makeNetworkRequest.ts +++ b/libs/isograph-react/src/core/makeNetworkRequest.ts @@ -3,14 +3,14 @@ import { IsographEntrypoint, RefetchQueryNormalizationArtifact, } from './entrypoint'; -import { Variables } from './FragmentReference'; +import { ExtractParameters } from './FragmentReference'; import { garbageCollectEnvironment, RetainedQuery, retainQuery, unretainQuery, } from './garbageCollection'; -import { IsographEnvironment, ROOT_ID } from './IsographEnvironment'; +import { IsographEnvironment, Link, ROOT_ID } from './IsographEnvironment'; import { AnyError, PromiseWrapper, @@ -19,19 +19,26 @@ import { } from './PromiseWrapper'; import { normalizeData } from './cache'; import { logMessage } from './logging'; -import { check, ShouldFetch } from './check'; +import { check, DEFAULT_SHOULD_FETCH_VALUE, FetchOptions } from './check'; +import { readButDoNotEvaluate } from './read'; +import { getOrCreateCachedComponent } from './componentCache'; let networkRequestId = 0; -export function maybeMakeNetworkRequest( +export function maybeMakeNetworkRequest< + TReadFromStore extends { parameters: object; data: object }, + TClientFieldValue, +>( environment: IsographEnvironment, - artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint, - variables: Variables, - shouldFetch: ShouldFetch, + artifact: + | RefetchQueryNormalizationArtifact + | IsographEntrypoint, + variables: ExtractParameters, + fetchOptions?: FetchOptions, ): ItemCleanupPair> { - switch (shouldFetch) { + switch (fetchOptions?.shouldFetch ?? DEFAULT_SHOULD_FETCH_VALUE) { case 'Yes': { - return makeNetworkRequest(environment, artifact, variables); + return makeNetworkRequest(environment, artifact, variables, fetchOptions); } case 'No': { return [wrapResolvedValue(undefined), () => {}]; @@ -39,7 +46,7 @@ export function maybeMakeNetworkRequest( case 'IfNecessary': { const result = check( environment, - artifact.networkRequestInfo.normalizationAst, + artifact.networkRequestInfo.normalizationAst.selections, variables, { __link: ROOT_ID, @@ -49,16 +56,27 @@ export function maybeMakeNetworkRequest( if (result.kind === 'EnoughData') { return [wrapResolvedValue(undefined), () => {}]; } else { - return makeNetworkRequest(environment, artifact, variables); + return makeNetworkRequest( + environment, + artifact, + variables, + fetchOptions, + ); } } } } -export function makeNetworkRequest( +export function makeNetworkRequest< + TReadFromStore extends { parameters: object; data: object }, + TClientFieldValue, +>( environment: IsographEnvironment, - artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint, - variables: Variables, + artifact: + | RefetchQueryNormalizationArtifact + | IsographEntrypoint, + variables: ExtractParameters, + fetchOptions?: FetchOptions, ): ItemCleanupPair> { // TODO this should be a DataId and stored in the store const myNetworkRequestId = networkRequestId + ''; @@ -85,17 +103,20 @@ export function makeNetworkRequest( }); if (networkResponse.errors != null) { + try { + fetchOptions?.onError?.(); + } catch {} // @ts-expect-error Why are we getting the wrong constructor here? throw new Error('GraphQL network response had errors', { cause: networkResponse, }); } + const root = { __link: ROOT_ID, __typename: artifact.concreteType }; if (status.kind === 'UndisposedIncomplete') { - const root = { __link: ROOT_ID, __typename: artifact.concreteType }; normalizeData( environment, - artifact.networkRequestInfo.normalizationAst, + artifact.networkRequestInfo.normalizationAst.selections, networkResponse.data ?? {}, variables, artifact.kind === 'Entrypoint' @@ -104,7 +125,8 @@ export function makeNetworkRequest( root, ); const retainedQuery = { - normalizationAst: artifact.networkRequestInfo.normalizationAst, + normalizationAst: + artifact.networkRequestInfo.normalizationAst.selections, variables, root, }; @@ -114,6 +136,35 @@ export function makeNetworkRequest( }; retainQuery(environment, retainedQuery); } + + const onComplete = fetchOptions?.onComplete; + if (onComplete != null) { + let data = readDataForOnComplete( + artifact, + environment, + root, + variables, + ); + + try { + // @ts-expect-error this problem will be fixed when we remove RefetchQueryNormalizationArtifact + // (or we can fix this by having a single param of type { kind: 'Entrypoint', entrypoint, + // fetchOptions: FetchOptions } | { kind: 'RefetchQuery', refetchQuery, + // fetchOptions: FetchOptions }). + onComplete(data); + } catch {} + } + }) + .catch((e) => { + logMessage(environment, { + kind: 'ReceivedNetworkError', + networkRequestId: myNetworkRequestId, + error: e, + }); + try { + fetchOptions?.onError?.(); + } catch {} + throw e; }); const wrapper = wrapPromise(promise); @@ -149,3 +200,84 @@ type NetworkRequestStatus = readonly kind: 'UndisposedComplete'; readonly retainedQuery: RetainedQuery; }; + +function readDataForOnComplete< + TReadFromStore extends { parameters: object; data: object }, + TClientFieldValue, +>( + artifact: + | RefetchQueryNormalizationArtifact + | IsographEntrypoint, + environment: IsographEnvironment, + root: Link, + variables: ExtractParameters, +): TClientFieldValue | null { + // An entrypoint, but not a RefetchQueryNormalizationArtifact, has a reader ASTs. + // So, we can only pass data to onComplete if makeNetworkRequest was passed an entrypoint. + // This is awkward, since we don't express that in the types of the parameters + // (i.e. FetchOptions could be passed, along with a RefetchQueryNormalizationArtifact). + // + // However, this isn't a big deal: RefetchQueryNormalizationArtifact is going away. + if (artifact.kind === 'Entrypoint') { + // TODO this is a smell! + const fakeNetworkRequest = wrapResolvedValue(undefined); + // TODO this is a smell — we know the network response is not in flight, + // so we don't really care! + const fakeNetworkRequestOptions = { + suspendIfInFlight: false, + throwOnNetworkError: false, + }; + + const fragmentResult = readButDoNotEvaluate( + environment, + { + kind: 'FragmentReference', + // TODO this smells. + readerWithRefetchQueries: wrapResolvedValue( + artifact.readerWithRefetchQueries, + ), + root, + variables, + networkRequest: fakeNetworkRequest, + }, + fakeNetworkRequestOptions, + ).item; + const readerArtifact = artifact.readerWithRefetchQueries.readerArtifact; + switch (readerArtifact.kind) { + case 'ComponentReaderArtifact': { + // @ts-expect-error We should find a way to encode this in the type system: + // if we have a ComponentReaderArtifact, we will necessarily have a + // TClientFieldValue which is a React.FC<...> + return getOrCreateCachedComponent( + environment, + readerArtifact.componentName, + { + kind: 'FragmentReference', + readerWithRefetchQueries: wrapResolvedValue({ + kind: 'ReaderWithRefetchQueries', + readerArtifact: readerArtifact, + nestedRefetchQueries: + artifact.readerWithRefetchQueries.nestedRefetchQueries, + }), + root, + variables, + networkRequest: fakeNetworkRequest, + } as const, + fakeNetworkRequestOptions, + ); + } + case 'EagerReaderArtifact': { + return readerArtifact.resolver({ + data: fragmentResult, + parameters: variables, + }); + } + default: { + const _: never = readerArtifact; + _; + throw new Error('Expected case'); + } + } + } + return null; +} diff --git a/libs/isograph-react/src/core/read.ts b/libs/isograph-react/src/core/read.ts index b553db2a4..78d17ce75 100644 --- a/libs/isograph-react/src/core/read.ts +++ b/libs/isograph-react/src/core/read.ts @@ -33,7 +33,7 @@ import { ReaderAst } from './reader'; import { Arguments } from './util'; import { logMessage } from './logging'; import { CleanupFn } from '@isograph/disposable-types'; -import { DEFAULT_SHOULD_FETCH_VALUE, FetchOptions } from './check'; +import { FetchOptions } from './check'; export type WithEncounteredRecords = { readonly encounteredRecords: EncounteredIds; @@ -49,6 +49,7 @@ export function readButDoNotEvaluate< ): WithEncounteredRecords { const mutableEncounteredRecords: EncounteredIds = new Map(); + // TODO consider moving this to the outside const readerWithRefetchQueries = readPromise( fragmentReference.readerWithRefetchQueries, ); @@ -163,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]; @@ -342,10 +347,12 @@ function readData( }; } else { const refetchQueryIndex = field.refetchQuery; - if (refetchQueryIndex == null) { - throw new Error('refetchQuery is null in RefetchField'); - } const refetchQuery = nestedRefetchQueries[refetchQueryIndex]; + if (refetchQuery == null) { + throw new Error( + 'refetchQuery is null in RefetchField. This is indicative of a bug in Isograph.', + ); + } const refetchQueryArtifact = refetchQuery.artifact; const allowedVariables = refetchQuery.allowedVariables; @@ -371,9 +378,15 @@ function readData( } case 'Resolver': { const usedRefetchQueries = field.usedRefetchQueries; - const resolverRefetchQueries = usedRefetchQueries.map( - (index) => nestedRefetchQueries[index], - ); + const resolverRefetchQueries = usedRefetchQueries.map((index) => { + const resolverRefetchQuery = nestedRefetchQueries[index]; + if (resolverRefetchQuery == null) { + throw new Error( + 'resolverRefetchQuery is null in Resolver. This is indicative of a bug in Isograph.', + ); + } + return resolverRefetchQuery; + }); switch (field.readerArtifact.kind) { case 'EagerReaderArtifact': { @@ -453,7 +466,11 @@ function readData( recordLink: refetchReaderParams.recordLink, }; } else { - target[field.alias] = (args: any, fetchOptions?: FetchOptions) => { + target[field.alias] = ( + args: any, + // TODO get the associated type for FetchOptions from the loadably selected field + fetchOptions?: FetchOptions, + ) => { // TODO we should use the reader AST for this const includeReadOutData = (variables: any, readOutData: any) => { variables.id = readOutData.id; @@ -483,14 +500,12 @@ function readData( const fragmentReferenceAndDisposeFromEntrypoint = ( entrypoint: IsographEntrypoint, ): [FragmentReference, CleanupFn] => { - const shouldFetch = - fetchOptions?.shouldFetch ?? DEFAULT_SHOULD_FETCH_VALUE; const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest( environment, entrypoint, localVariables, - shouldFetch, + fetchOptions, ); const fragmentReference: FragmentReference = { @@ -547,15 +562,12 @@ function readData( if ( entrypointLoaderState.kind === 'EntrypointNotLoaded' ) { - const shouldFetch = - fetchOptions?.shouldFetch ?? - DEFAULT_SHOULD_FETCH_VALUE; const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest( environment, entrypoint, localVariables, - shouldFetch, + fetchOptions, ); entrypointLoaderState = { kind: 'NetworkRequestStarted', @@ -602,6 +614,7 @@ function readData( } break; } + default: { // Ensure we have covered all variants let _: never = field; @@ -641,7 +654,12 @@ function generateChildVariableMap( const childVars: Writable = {}; for (const [name, value] of fieldArguments) { if (value.kind === 'Variable') { - childVars[name] = variables[value.name]; + const variable = variables[value.name]; + // Variable could be null if it was not provided but has a default case, + // so we allow the loop to continue rather than throwing an error. + if (variable != null) { + childVars[name] = variable; + } } else { childVars[name] = value.value; } diff --git a/libs/isograph-react/src/core/reader.ts b/libs/isograph-react/src/core/reader.ts index a36e0655f..8d087ba77 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; @@ -156,5 +163,5 @@ export type LoadableField< // user-facing API. Users should only interact with LoadableFields via APIs // like useClientSideDefer. These APIs should have a nullable fetchOptions // parameter, and provide a default value ({}) to the LoadableField. - fetchOptions: FetchOptions, + fetchOptions: FetchOptions, ) => [StableId, Factory>]; diff --git a/libs/isograph-react/src/loadable-hooks/useClientSideDefer.ts b/libs/isograph-react/src/loadable-hooks/useClientSideDefer.ts index 439b70143..86c80063f 100644 --- a/libs/isograph-react/src/loadable-hooks/useClientSideDefer.ts +++ b/libs/isograph-react/src/loadable-hooks/useClientSideDefer.ts @@ -18,7 +18,7 @@ export function useClientSideDefer< ExtractParameters >, args?: Record, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ): { fragmentReference: FragmentReference }; export function useClientSideDefer< @@ -32,7 +32,7 @@ export function useClientSideDefer< Omit, keyof TProvidedArgs> >, args: Omit, keyof TProvidedArgs>, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ): { fragmentReference: FragmentReference }; export function useClientSideDefer< @@ -46,7 +46,7 @@ export function useClientSideDefer< Omit, keyof TProvidedArgs> >, args?: Omit, keyof TProvidedArgs>, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ): { fragmentReference: FragmentReference } { const [id, loader] = loadableField(args, fetchOptions ?? {}); const environment = useIsographEnvironment(); diff --git a/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts b/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts index c4d3a0694..92a839943 100644 --- a/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts +++ b/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts @@ -32,7 +32,10 @@ type UsePaginationReturnValue< } | { kind: 'Complete'; - fetchMore: (count: number, fetchOptions?: FetchOptions) => void; + fetchMore: ( + count: number, + fetchOptions?: FetchOptions>, + ) => void; results: ReadonlyArray; hasNextPage: boolean; }; @@ -91,7 +94,7 @@ export function useConnectionSpecPagination< Connection, UseConnectionSpecPaginationArgs >, - pageInfo?: PageInfo, + initialState?: PageInfo, ): UsePaginationReturnValue { const networkRequestOptions = { suspendIfInFlight: true, @@ -114,8 +117,16 @@ export function useConnectionSpecPagination< fragmentReference.readerWithRefetchQueries, ); + // invariant: readOutDataAndRecords.length === completedReferences.length + const data = readOutDataAndRecords[i]?.item; + if (data == null) { + throw new Error( + 'Parameter data is unexpectedly null. This is indicative of a bug in Isograph.', + ); + } + const firstParameter = { - data: readOutDataAndRecords[i].item, + data, parameters: fragmentReference.variables, }; @@ -165,10 +176,17 @@ export function useConnectionSpecPagination< fragmentReference.readerWithRefetchQueries, ); + const records = readOutDataAndRecords[i]; + if (records == null) { + throw new Error( + 'subscribeCompletedFragmentReferences records is unexpectedly null', + ); + } + return { fragmentReference, readerAst: readerWithRefetchQueries.readerArtifact.readerAst, - records: readOutDataAndRecords[i], + records, callback(_data) { rerender({}); }, @@ -179,7 +197,7 @@ export function useConnectionSpecPagination< const getFetchMore = (after: string | null) => - (count: number, fetchOptions?: FetchOptions): void => { + (count: number, fetchOptions?: FetchOptions>): void => { const loadedField = loadableField( { after: after, @@ -224,10 +242,9 @@ export function useConnectionSpecPagination< const loadedReferences = state === UNASSIGNED_STATE ? [] : state; - const mostRecentItem: LoadedFragmentReference< - TReadFromStore, - Connection - > | null = loadedReferences[loadedReferences.length - 1]; + const mostRecentItem: + | LoadedFragmentReference> + | undefined = loadedReferences[loadedReferences.length - 1]; const mostRecentFragmentReference = mostRecentItem?.[0].getItemIfNotDisposed(); @@ -276,9 +293,9 @@ export function useConnectionSpecPagination< if (!networkRequestStatus) { return { kind: 'Complete', - fetchMore: getFetchMore(pageInfo?.endCursor ?? null), + fetchMore: getFetchMore(initialState?.endCursor ?? null), results: [], - hasNextPage: pageInfo?.hasNextPage ?? true, + hasNextPage: initialState?.hasNextPage ?? true, }; } diff --git a/libs/isograph-react/src/loadable-hooks/useImperativeLoadableField.ts b/libs/isograph-react/src/loadable-hooks/useImperativeLoadableField.ts index 70a7bbff5..4797400d9 100644 --- a/libs/isograph-react/src/loadable-hooks/useImperativeLoadableField.ts +++ b/libs/isograph-react/src/loadable-hooks/useImperativeLoadableField.ts @@ -21,7 +21,7 @@ type UseImperativeLoadableFieldReturn< // TODO this should be void iff all args are provided by the query, like in // useClientSideDefer. args: Omit, keyof TProvidedArgs> | void, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ) => void; }; @@ -42,7 +42,7 @@ export function useImperativeLoadableField< return { loadField: ( args: Omit, keyof TProvidedArgs> | void, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ) => { const [_id, loader] = loadableField(args, fetchOptions ?? {}); setState(loader()); diff --git a/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts b/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts index 24bcc4a20..4862bd6e2 100644 --- a/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts +++ b/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts @@ -25,7 +25,10 @@ type UseSkipLimitReturnValue< > = | { readonly kind: 'Complete'; - readonly fetchMore: (count: number, fetchOptions?: FetchOptions) => void; + readonly fetchMore: ( + count: number, + fetchOptions?: FetchOptions>, + ) => void; readonly results: ReadonlyArray; } | { @@ -81,7 +84,7 @@ export function useSkipLimitPagination< ReadonlyArray, UseSkipLimitPaginationArgs >, - initialArgs?: { + initialState?: { skip?: number | void | null; }, ): UseSkipLimitReturnValue { @@ -106,8 +109,16 @@ export function useSkipLimitPagination< fragmentReference.readerWithRefetchQueries, ); + // invariant: readOutDataAndRecords.length === completedReferences.length + const data = readOutDataAndRecords[i]?.item; + if (data == null) { + throw new Error( + 'Parameter data is unexpectedly null. This is indicative of a bug in Isograph.', + ); + } + const firstParameter = { - data: readOutDataAndRecords[i].item, + data, parameters: fragmentReference.variables, }; @@ -150,10 +161,17 @@ export function useSkipLimitPagination< fragmentReference.readerWithRefetchQueries, ); + const records = readOutDataAndRecords[i]; + if (records == null) { + throw new Error( + 'subscribeCompletedFragmentReferences records is unexpectedly null', + ); + } + return { fragmentReference, readerAst: readerWithRefetchQueries.readerArtifact.readerAst, - records: readOutDataAndRecords[i], + records, callback(_data) { rerender({}); }, @@ -164,7 +182,10 @@ export function useSkipLimitPagination< const getFetchMore = (loadedSoFar: number) => - (count: number, fetchOptions?: FetchOptions): void => { + ( + count: number, + fetchOptions?: FetchOptions>, + ): void => { const loadedField = loadableField( { skip: loadedSoFar, @@ -209,8 +230,9 @@ export function useSkipLimitPagination< const loadedReferences = state === UNASSIGNED_STATE ? [] : state; - const mostRecentItem: LoadedFragmentReference | null = - loadedReferences[loadedReferences.length - 1]; + const mostRecentItem: + | LoadedFragmentReference + | undefined = loadedReferences[loadedReferences.length - 1]; const mostRecentFragmentReference = mostRecentItem?.[0].getItemIfNotDisposed(); @@ -259,7 +281,7 @@ export function useSkipLimitPagination< if (!networkRequestStatus) { return { kind: 'Complete', - fetchMore: getFetchMore(initialArgs?.skip ?? 0), + fetchMore: getFetchMore(initialState?.skip ?? 0), results: [], }; } diff --git a/libs/isograph-react/src/react/useImperativeReference.ts b/libs/isograph-react/src/react/useImperativeReference.ts index d12bee69c..a8f9897dd 100644 --- a/libs/isograph-react/src/react/useImperativeReference.ts +++ b/libs/isograph-react/src/react/useImperativeReference.ts @@ -11,7 +11,7 @@ import { useIsographEnvironment } from './IsographEnvironmentProvider'; import { ROOT_ID } from '../core/IsographEnvironment'; import { maybeMakeNetworkRequest } from '../core/makeNetworkRequest'; import { wrapResolvedValue } from '../core/PromiseWrapper'; -import { DEFAULT_SHOULD_FETCH_VALUE, FetchOptions } from '../core/check'; +import { FetchOptions } from '../core/check'; // TODO rename this to useImperativelyLoadedEntrypoint @@ -26,7 +26,7 @@ export function useImperativeReference< | UnassignedState; loadFragmentReference: ( variables: ExtractParameters, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ) => void; } { const { state, setState } = @@ -38,15 +38,13 @@ export function useImperativeReference< fragmentReference: state, loadFragmentReference: ( variables: ExtractParameters, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ) => { - const shouldFetch = - fetchOptions?.shouldFetch ?? DEFAULT_SHOULD_FETCH_VALUE; const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest( environment, entrypoint, variables, - shouldFetch, + fetchOptions, ); setState([ { diff --git a/libs/isograph-react/src/react/useLazyReference.ts b/libs/isograph-react/src/react/useLazyReference.ts index d9af76a4c..9af3bc02c 100644 --- a/libs/isograph-react/src/react/useLazyReference.ts +++ b/libs/isograph-react/src/react/useLazyReference.ts @@ -15,7 +15,7 @@ export function useLazyReference< >( entrypoint: IsographEntrypoint, variables: ExtractParameters, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ): { fragmentReference: FragmentReference; } { diff --git a/libs/isograph-react/src/tests/__isograph/Query/meName/entrypoint.ts b/libs/isograph-react/src/tests/__isograph/Query/meName/entrypoint.ts index 81294fca7..deea61e36 100644 --- a/libs/isograph-react/src/tests/__isograph/Query/meName/entrypoint.ts +++ b/libs/isograph-react/src/tests/__isograph/Query/meName/entrypoint.ts @@ -11,26 +11,29 @@ const queryText = 'query meName {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "me", - arguments: null, - concreteType: "Economist", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "me", + arguments: null, + concreteType: "Economist", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__meName__param, Query__meName__output_type diff --git a/libs/isograph-react/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts b/libs/isograph-react/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts index c1262b196..a2e836a55 100644 --- a/libs/isograph-react/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +++ b/libs/isograph-react/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts @@ -18,57 +18,60 @@ const queryText = 'query meNameSuccessor {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "me", - arguments: null, - concreteType: "Economist", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Linked", - fieldName: "successor", - arguments: null, - concreteType: "Economist", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "successor", - arguments: null, - concreteType: "Economist", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, - ], - }, - ], - }, -]; +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "me", + arguments: null, + concreteType: "Economist", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Linked", + fieldName: "successor", + arguments: null, + concreteType: "Economist", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "successor", + arguments: null, + concreteType: "Economist", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__meNameSuccessor__param, Query__meNameSuccessor__output_type diff --git a/libs/isograph-react/src/tests/__isograph/Query/nodeField/entrypoint.ts b/libs/isograph-react/src/tests/__isograph/Query/nodeField/entrypoint.ts index 083b2d325..c012c1a0c 100644 --- a/libs/isograph-react/src/tests/__isograph/Query/nodeField/entrypoint.ts +++ b/libs/isograph-react/src/tests/__isograph/Query/nodeField/entrypoint.ts @@ -11,31 +11,34 @@ const queryText = 'query nodeField ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - ], - }, -]; + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__nodeField__param, Query__nodeField__output_type diff --git a/libs/isograph-react/src/tests/__isograph/Query/subquery/entrypoint.ts b/libs/isograph-react/src/tests/__isograph/Query/subquery/entrypoint.ts index e22e746b1..fceffcb0f 100644 --- a/libs/isograph-react/src/tests/__isograph/Query/subquery/entrypoint.ts +++ b/libs/isograph-react/src/tests/__isograph/Query/subquery/entrypoint.ts @@ -13,39 +13,42 @@ const queryText = 'query subquery ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "query", - arguments: null, - concreteType: "Query", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "query", + arguments: null, + concreteType: "Query", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - ], - }, - ], - }, -]; + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__subquery__param, Query__subquery__output_type diff --git a/libs/isograph-react/src/tests/__isograph/iso.ts b/libs/isograph-react/src/tests/__isograph/iso.ts index 62eafeb95..2945e3361 100644 --- a/libs/isograph-react/src/tests/__isograph/iso.ts +++ b/libs/isograph-react/src/tests/__isograph/iso.ts @@ -95,5 +95,6 @@ export function iso(_isographLiteralText: string): { throw new Error('iso: Unexpected invocation at runtime. Either the Babel transform ' + 'was not set up, or it failed to identify this call site. Make sure it ' + - 'is being used verbatim as `iso`.'); + 'is being used verbatim as `iso`. If you cannot use the babel transform, ' + + 'set options.no_babel_transform to true in your Isograph config. '); } \ No newline at end of file diff --git a/libs/isograph-react/src/tests/garbageCollection.test.ts b/libs/isograph-react/src/tests/garbageCollection.test.ts index ba6e9cfd5..25310bde5 100644 --- a/libs/isograph-react/src/tests/garbageCollection.test.ts +++ b/libs/isograph-react/src/tests/garbageCollection.test.ts @@ -55,7 +55,8 @@ export const meNameField = iso(` import { meNameSuccessorRetainedQuery } from './meNameSuccessor'; const meNameEntrypoint = iso(`entrypoint Query.meName`); const meNameRetainedQuery = { - normalizationAst: meNameEntrypoint.networkRequestInfo.normalizationAst, + normalizationAst: + meNameEntrypoint.networkRequestInfo.normalizationAst.selections, variables: {}, root: { __link: ROOT_ID, __typename: 'Query' }, }; diff --git a/libs/isograph-react/src/tests/meNameSuccessor.ts b/libs/isograph-react/src/tests/meNameSuccessor.ts index b3267816e..c32290f6b 100644 --- a/libs/isograph-react/src/tests/meNameSuccessor.ts +++ b/libs/isograph-react/src/tests/meNameSuccessor.ts @@ -16,7 +16,7 @@ export const meNameField = iso(` const meNameSuccessorEntrypoint = iso(`entrypoint Query.meNameSuccessor`); export const meNameSuccessorRetainedQuery = { normalizationAst: - meNameSuccessorEntrypoint.networkRequestInfo.normalizationAst, + meNameSuccessorEntrypoint.networkRequestInfo.normalizationAst.selections, variables: {}, root: { __link: ROOT_ID, diff --git a/libs/isograph-react/src/tests/nodeQuery.ts b/libs/isograph-react/src/tests/nodeQuery.ts index 0dd93b522..c3e362a0f 100644 --- a/libs/isograph-react/src/tests/nodeQuery.ts +++ b/libs/isograph-react/src/tests/nodeQuery.ts @@ -13,7 +13,8 @@ export const nodeField = iso(` `)(() => {}); const nodeFieldEntrypoint = iso(`entrypoint Query.nodeField`); export const nodeFieldRetainedQuery: RetainedQuery = { - normalizationAst: nodeFieldEntrypoint.networkRequestInfo.normalizationAst, + normalizationAst: + nodeFieldEntrypoint.networkRequestInfo.normalizationAst.selections, variables: { id: 0 }, root: { __link: ROOT_ID, __typename: 'Query' }, }; diff --git a/libs/isograph-react/src/tests/normalizeData.test.ts b/libs/isograph-react/src/tests/normalizeData.test.ts index 7e7a846d1..b323fcf37 100644 --- a/libs/isograph-react/src/tests/normalizeData.test.ts +++ b/libs/isograph-react/src/tests/normalizeData.test.ts @@ -35,7 +35,7 @@ describe('normalizeData', () => { normalizeData( environment, - entrypoint.networkRequestInfo.normalizationAst, + entrypoint.networkRequestInfo.normalizationAst.selections, { query: { node____id___v_id: { __typename: 'Economist', id: '1' } }, }, diff --git a/libs/isograph-reference-counted-pointer/package.json b/libs/isograph-reference-counted-pointer/package.json index ae12d900f..a3f1b2bb4 100644 --- a/libs/isograph-reference-counted-pointer/package.json +++ b/libs/isograph-reference-counted-pointer/package.json @@ -1,6 +1,6 @@ { "name": "@isograph/reference-counted-pointer", - "version": "0.2.0", + "version": "0.3.0", "description": "Reference counted pointers enable sharing of disposable items.", "homepage": "https://isograph.dev", "main": "dist/index.js", @@ -16,7 +16,7 @@ "tsc": "tsc" }, "dependencies": { - "@isograph/disposable-types": "0.2.0" + "@isograph/disposable-types": "*" }, "devDependencies": { "typescript": "5.6.3" diff --git a/package.json b/package.json index cfbfeca4c..c2ca7f4e9 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,10 @@ { + "name": "isograph", "private": true, "devDependencies": { "gulp": "4.0.2", "prettier": "^3.2.4", + "turbo": "^2.2.3", "typescript": "5.6.3", "vitest": "^2.1.2" }, @@ -10,6 +12,8 @@ "watch-rs": "bacon -j build -p ./crates/", "check-rs": "bacon -j check -p ./crates/", "build": "cargo build", + "cross": "cross build", + "build-json-schema": "./target/debug/build_json_schema", "watch-pet-demo": "pnpm --filter=pet-demo iso-watch", "watch-github-demo": "pnpm --filter=github-demo iso-watch", "watch-isograph-react-demo": "pnpm --filter=@isograph/react iso-watch", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 234c80957..0cb26c848 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: prettier: specifier: ^3.2.4 version: 3.3.3 + turbo: + specifier: ^2.2.3 + version: 2.2.3 typescript: specifier: 5.6.3 version: 5.6.3 @@ -321,7 +324,7 @@ importers: libs/isograph-react: dependencies: '@isograph/disposable-types': - specifier: 0.2.0 + specifier: '*' version: link:../isograph-disposable-types '@isograph/react-disposable-state': specifier: '*' @@ -355,7 +358,7 @@ importers: libs/isograph-react-disposable-state: dependencies: '@isograph/disposable-types': - specifier: 0.2.0 + specifier: '*' version: link:../isograph-disposable-types react: specifier: 18.3.1 @@ -364,6 +367,9 @@ importers: '@types/react': specifier: 18.3.1 version: 18.3.1 + '@types/react-test-renderer': + specifier: ^18.3.0 + version: 18.3.0 react-test-renderer: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) @@ -374,7 +380,7 @@ importers: libs/isograph-reference-counted-pointer: dependencies: '@isograph/disposable-types': - specifier: 0.2.0 + specifier: '*' version: link:../isograph-disposable-types devDependencies: typescript: @@ -2267,6 +2273,9 @@ packages: '@types/react-router@5.1.20': resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} + '@types/react-test-renderer@18.3.0': + resolution: {integrity: sha512-HW4MuEYxfDbOHQsVlY/XtOvNHftCVEPhJF2pQXXwcUiUF+Oyb0usgp48HSgpK5rt8m9KZb22yqOeZm+rrVG8gw==} + '@types/react-transition-group@4.4.11': resolution: {integrity: sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==} @@ -7186,6 +7195,40 @@ packages: peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + turbo-darwin-64@2.2.3: + resolution: {integrity: sha512-Rcm10CuMKQGcdIBS3R/9PMeuYnv6beYIHqfZFeKWVYEWH69sauj4INs83zKMTUiZJ3/hWGZ4jet9AOwhsssLyg==} + cpu: [x64] + os: [darwin] + + turbo-darwin-arm64@2.2.3: + resolution: {integrity: sha512-+EIMHkuLFqUdJYsA3roj66t9+9IciCajgj+DVek+QezEdOJKcRxlvDOS2BUaeN8kEzVSsNiAGnoysFWYw4K0HA==} + cpu: [arm64] + os: [darwin] + + turbo-linux-64@2.2.3: + resolution: {integrity: sha512-UBhJCYnqtaeOBQLmLo8BAisWbc9v9daL9G8upLR+XGj6vuN/Nz6qUAhverN4Pyej1g4Nt1BhROnj6GLOPYyqxQ==} + cpu: [x64] + os: [linux] + + turbo-linux-arm64@2.2.3: + resolution: {integrity: sha512-hJYT9dN06XCQ3jBka/EWvvAETnHRs3xuO/rb5bESmDfG+d9yQjeTMlhRXKrr4eyIMt6cLDt1LBfyi+6CQ+VAwQ==} + cpu: [arm64] + os: [linux] + + turbo-windows-64@2.2.3: + resolution: {integrity: sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==} + cpu: [x64] + os: [win32] + + turbo-windows-arm64@2.2.3: + resolution: {integrity: sha512-fnNrYBCqn6zgKPKLHu4sOkihBI/+0oYFr075duRxqUZ+1aLWTAGfHZLgjVeLh3zR37CVzuerGIPWAEkNhkWEIw==} + cpu: [arm64] + os: [win32] + + turbo@2.2.3: + resolution: {integrity: sha512-5lDvSqIxCYJ/BAd6rQGK/AzFRhBkbu4JHVMLmGh/hCb7U3CqSnr5Tjwfy9vc+/5wG2DJ6wttgAaA7MoCgvBKZQ==} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -10280,6 +10323,10 @@ snapshots: '@types/history': 4.7.11 '@types/react': 18.3.1 + '@types/react-test-renderer@18.3.0': + dependencies: + '@types/react': 18.3.1 + '@types/react-transition-group@4.4.11': dependencies: '@types/react': 18.3.1 @@ -12087,7 +12134,7 @@ snapshots: eslint: 8.39.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(eslint@8.39.0))(eslint@8.39.0) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(eslint@8.39.0))(eslint@8.39.0))(eslint@8.39.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.39.0) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.39.0) eslint-plugin-react: 7.36.1(eslint@8.39.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.39.0) @@ -12137,7 +12184,7 @@ snapshots: is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(eslint@8.39.0))(eslint@8.39.0))(eslint@8.39.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.39.0) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node @@ -12185,7 +12232,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(eslint@8.39.0))(eslint@8.39.0))(eslint@8.39.0): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.39.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -16440,6 +16487,33 @@ snapshots: tslib: 1.14.1 typescript: 5.6.3 + turbo-darwin-64@2.2.3: + optional: true + + turbo-darwin-arm64@2.2.3: + optional: true + + turbo-linux-64@2.2.3: + optional: true + + turbo-linux-arm64@2.2.3: + optional: true + + turbo-windows-64@2.2.3: + optional: true + + turbo-windows-arm64@2.2.3: + optional: true + + turbo@2.2.3: + optionalDependencies: + turbo-darwin-64: 2.2.3 + turbo-darwin-arm64: 2.2.3 + turbo-linux-64: 2.2.3 + turbo-linux-arm64: 2.2.3 + turbo-windows-64: 2.2.3 + turbo-windows-arm64: 2.2.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/relay-crates/intern/src/atomic_arena.rs b/relay-crates/intern/src/atomic_arena.rs index f82bb336b..c2defac32 100644 --- a/relay-crates/intern/src/atomic_arena.rs +++ b/relay-crates/intern/src/atomic_arena.rs @@ -673,6 +673,7 @@ mod tests { use super::*; static mut ZERO: Zero<&str> = Zero::new("zero"); + #[allow(static_mut_refs)] static STRING_ARENA: AtomicArena<'static, &str> = AtomicArena::with_zero(unsafe { &ZERO }); /// For internal testing purposes we permit the unsafe synthesis of Refs. diff --git a/relay-crates/intern/src/intern.rs b/relay-crates/intern/src/intern.rs index 881ed44ed..2fe931494 100644 --- a/relay-crates/intern/src/intern.rs +++ b/relay-crates/intern/src/intern.rs @@ -242,7 +242,7 @@ impl InternTable { impl InternTable { /// The methods from here on are internal and private. - fn shards(&'static self) -> &Shards { + fn shards(&'static self) -> &'static Shards { self.shards.get_or_init(|| { let shards: Shards = ShardedSet::default(); if !self.arena.is_empty() { @@ -291,7 +291,7 @@ impl InternTable { /// Get a shared reference to the underlying `Id::Intern`. /// Usually you can rely on `deref` to do this implicitly. #[inline] - fn get(&'static self, r: Id) -> &Id::Intern { + fn get(&'static self, r: Id) -> &'static Id::Intern { &*self.arena.get(r.unwrap()) } diff --git a/tsconfig.build.json b/tsconfig.build.json index 288b569dd..3769b86f5 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -15,6 +15,7 @@ "noPropertyAccessFromIndexSignature": true, "noUnusedParameters": true, "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, "paths": { "@isograph/*": ["./libs/*"] }, diff --git a/turbo.json b/turbo.json new file mode 100644 index 000000000..022042937 --- /dev/null +++ b/turbo.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "//#build": { + "inputs": ["crates/**", "relay-crates/**", "Cargo.*"], + "outputs": ["**/release/isograph_cli*", "**/debug/isograph_cli*"] + }, + "//#cross": { + "inputs": ["crates/**", "relay-crates/**", "Cargo.*"], + "outputs": ["**/release/isograph_cli*", "**/debug/isograph_cli*"] + } + } +} diff --git a/vscode-extension/package-lock.json b/vscode-extension/package-lock.json index 4c134cd96..99703648d 100644 --- a/vscode-extension/package-lock.json +++ b/vscode-extension/package-lock.json @@ -25,9 +25,10 @@ "eslint-config-airbnb-typescript": "^17.0.0", "eslint-plugin-import": "^2.26.0", "prettier": "^2.6.2", - "typescript": "^4.6.3" + "typescript": "5.6.3" }, "engines": { + "node": "22.9.0", "vscode": "^1.60.0" } }, @@ -5207,16 +5208,17 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/uc.micro": { diff --git a/vscode-extension/package.json b/vscode-extension/package.json index 7521d0fde..7a612bc8a 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -65,11 +65,11 @@ "prettier-check": "prettier -c .", "prettier-write": "prettier --write .", "lint": "eslint --max-warnings 0 .", - "vscode:prepublish": "rm -f tsconfig.tsbuildinfo && rm -rf out && pnpm run esbuild-base -- --minify", + "vscode:prepublish": "rm -f tsconfig.tsbuildinfo && rm -rf out && npm run esbuild-base -- --minify", "build-local": "vsce package", "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node", - "esbuild": "pnpm run esbuild-base --sourcemap", - "esbuild-watch": "pnpm run esbuild-base --sourcemap --watch" + "esbuild": "npm run esbuild-base --sourcemap", + "esbuild-watch": "npm run esbuild-base --sourcemap --watch" }, "engines": { "node": "22.9.0",