From b580de7d1ca14fe0abec3de51cdbdc10aeedcf07 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Sun, 23 Nov 2025 19:21:07 -0300 Subject: [PATCH 01/28] Artifact lookup cache ArtifactLookupCache is an object that keeps a mapping between relative artifact paths and their content hash. For avoiding triggering infinite reloads, the serialized object sorts the maps per key. It makes use of a Btree data structure to keep a reference to the ordered set of keys. --- .../src/artifact_lookup_cache.rs | 37 +++++++++++++++++++ crates/artifact_content/src/lib.rs | 4 +- crates/artifact_content/src/operation_text.rs | 2 +- 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 crates/artifact_content/src/artifact_lookup_cache.rs diff --git a/crates/artifact_content/src/artifact_lookup_cache.rs b/crates/artifact_content/src/artifact_lookup_cache.rs new file mode 100644 index 000000000..116ff60f1 --- /dev/null +++ b/crates/artifact_content/src/artifact_lookup_cache.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ArtifactLookupCache { + hashes: HashMap, + sorted_keys: BTreeSet, +} + +impl ArtifactLookupCache { + pub fn new() -> Self { + Self { + hashes: HashMap::new(), + sorted_keys: BTreeSet::new(), + } + } + + pub fn insert(&mut self, filename: String, hash: String) { + self.sorted_keys.insert(filename.clone()); + self.hashes.insert(filename, hash); + } + + pub fn get(&self, filename: &str) -> Option<&String> { + self.hashes.get(filename) + } + + fn to_ordered_map(&self) -> BTreeMap<&String, &String> { + self.sorted_keys + .iter() + .map(|k| (k, &self.hashes[k])) + .collect() + } + + pub fn write_to(&self, writer: W) -> Result<(), serde_json::Error> { + serde_json::to_writer_pretty(writer, &self.to_ordered_map()) + } +} diff --git a/crates/artifact_content/src/lib.rs b/crates/artifact_content/src/lib.rs index b9ab8837d..07dfbfdce 100644 --- a/crates/artifact_content/src/lib.rs +++ b/crates/artifact_content/src/lib.rs @@ -1,3 +1,4 @@ +mod artifact_lookup_cache; mod eager_reader_artifact; mod entrypoint_artifact; mod format_parameter_type; @@ -6,11 +7,12 @@ mod imperatively_loaded_fields; mod import_statements; mod iso_overload_file; mod normalization_ast_text; -mod operation_text; +pub mod operation_text; mod persisted_documents; mod raw_response_type; mod reader_ast; mod refetch_reader_artifact; mod ts_config; +pub use artifact_lookup_cache::ArtifactLookupCache; pub use generate_artifacts::get_artifact_path_and_content; diff --git a/crates/artifact_content/src/operation_text.rs b/crates/artifact_content/src/operation_text.rs index 238703187..eb501a7c9 100644 --- a/crates/artifact_content/src/operation_text.rs +++ b/crates/artifact_content/src/operation_text.rs @@ -68,7 +68,7 @@ pub(crate) fn generate_operation_text<'a, TNetworkProtocol: NetworkProtocol>( } } -fn hash(data: &str, algorithm: PersistedDocumentsHashAlgorithm) -> String { +pub fn hash(data: &str, algorithm: PersistedDocumentsHashAlgorithm) -> String { match algorithm { PersistedDocumentsHashAlgorithm::Md5 => { let mut md5 = Md5::new(); From 8ef67c497a75d679bc05308b56e1125532f9448a Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Sun, 23 Nov 2025 19:23:48 -0300 Subject: [PATCH 02/28] Generate and write to disk artifacts lookup hash --- .../src/generate_artifacts.rs | 2 + .../isograph_compiler/src/write_artifacts.rs | 32 +++ .../__isograph/.artifact_lookup_cache.json | 249 ++++++++++++++++++ 3 files changed, 283 insertions(+) create mode 100644 demos/pet-demo/src/components/__isograph/.artifact_lookup_cache.json diff --git a/crates/artifact_content/src/generate_artifacts.rs b/crates/artifact_content/src/generate_artifacts.rs index d5b996854..fdda52c7c 100644 --- a/crates/artifact_content/src/generate_artifacts.rs +++ b/crates/artifact_content/src/generate_artifacts.rs @@ -59,6 +59,8 @@ use crate::{ lazy_static! { pub static ref ENTRYPOINT_FILE_NAME: ArtifactFileName = "entrypoint.ts".intern().into(); + pub static ref ARTIFACT_LOOKUP_CACHE_FILE_NAME: ArtifactFileName = + ".artifact_lookup_cache.json".intern().into(); pub static ref ENTRYPOINT: ArtifactFilePrefix = "entrypoint".intern().into(); pub static ref ISO_TS_FILE_NAME: ArtifactFileName = "iso.ts".intern().into(); pub static ref ISO_TS: ArtifactFilePrefix = "iso".intern().into(); diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index 6dd150823..0989380b8 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -4,8 +4,12 @@ use std::{ path::PathBuf, }; +use artifact_content::{ + ArtifactLookupCache, generate_artifacts::ARTIFACT_LOOKUP_CACHE_FILE_NAME, operation_text::hash, +}; use common_lang_types::ArtifactPathAndContent; use intern::string_key::Lookup; +use isograph_config::PersistedDocumentsHashAlgorithm; use thiserror::Error; #[tracing::instrument(skip(paths_and_contents, artifact_directory))] @@ -29,6 +33,7 @@ pub(crate) fn write_artifacts_to_disk( })?; let mut count = 0; + let mut artifact_lookup_cache = ArtifactLookupCache::new(); for path_and_content in paths_and_contents { // Is this better than materializing paths_and_contents sooner? count += 1; @@ -59,7 +64,34 @@ pub(crate) fn write_artifacts_to_disk( path: absolute_file_path.clone(), message: e.to_string(), })?; + + let artifact_hash = hash( + &path_and_content.file_content, + PersistedDocumentsHashAlgorithm::Sha256, + ); + let relative_file_path = absolute_file_path + .strip_prefix(artifact_directory) + .expect("absolute paths should contain artifact_directory") + .to_string_lossy() + .to_string(); + artifact_lookup_cache.insert(relative_file_path, artifact_hash); } + + let absolute_file_path = artifact_directory.join(ARTIFACT_LOOKUP_CACHE_FILE_NAME.lookup()); + let file = File::create(&absolute_file_path).map_err(|e| { + GenerateArtifactsError::UnableToWriteToArtifactFile { + path: absolute_file_path.clone(), + message: e.to_string(), + } + })?; + + artifact_lookup_cache.write_to(file).map_err(|e| { + GenerateArtifactsError::UnableToWriteToArtifactFile { + path: absolute_file_path.clone(), + message: e.to_string(), + } + })?; + Ok(count) } diff --git a/demos/pet-demo/src/components/__isograph/.artifact_lookup_cache.json b/demos/pet-demo/src/components/__isograph/.artifact_lookup_cache.json new file mode 100644 index 000000000..fa23f10bf --- /dev/null +++ b/demos/pet-demo/src/components/__isograph/.artifact_lookup_cache.json @@ -0,0 +1,249 @@ +{ + "AdItem/AdItemDisplay/entrypoint.ts": "ef6c3080d736a650b3bd3505f1de055ee9597656fd8eb98a0c0a4e16604a7f4d", + "AdItem/AdItemDisplay/normalization_ast.ts": "afcd25dc1216625756a107bafa5591a4666bae911174c5548f01caf2e1f7d0aa", + "AdItem/AdItemDisplay/output_type.ts": "87833bd6f8b6b6c47f8bbed7c677d9b1d9042ec513e243bab5c32425710a1c5e", + "AdItem/AdItemDisplay/param_type.ts": "36a8a55d3dc3f0603c37c9b559a1945c7c2f226df0d08052c78378de83ae33d3", + "AdItem/AdItemDisplay/query_text.ts": "764202397a8bccf7c201d4343d75bffb7c32355ee23c6599a9ce4a364d299be5", + "AdItem/AdItemDisplay/raw_response_type.ts": "6cda41d794ffae2780063d2618084a76c727b6978dbea613e04395add40acb09", + "AdItem/AdItemDisplay/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", + "AdItem/AdItemDisplay/resolver_reader.ts": "bedac9a0198fa2c4155bab6a409fd1af8b39ba8564e0bcf9864c6a54458d0075", + "BlogItem/BlogItemDisplay/output_type.ts": "a0a1b452219a44a3b4e66cfb76fdfd5a1ea616c95471f491de622ed6afb8736c", + "BlogItem/BlogItemDisplay/param_type.ts": "1804d34d97989893db818a1f8376e38013b78e16eb439e94600ae03bcc461aea", + "BlogItem/BlogItemDisplay/resolver_reader.ts": "a0b7e9406f53044d7e80f126664f9f6666e3f35755f40a86e069471852fca89b", + "BlogItem/BlogItemMoreDetail/entrypoint.ts": "e447084762c108abbc439ad30505d9cd1b96b159f46cc4bca1f82df70842186e", + "BlogItem/BlogItemMoreDetail/normalization_ast.ts": "49ecc094c43e309f65d62ba6f4722ea6349d2d32b6ee6b5570c3ccdcbab7a769", + "BlogItem/BlogItemMoreDetail/output_type.ts": "08bb819cf3c94c7174bf218559889057c46e75b44f8d979c4a5cda13259945e6", + "BlogItem/BlogItemMoreDetail/param_type.ts": "36a985d184dd3ca9817ff02c3c94fdde374211bb5f05990df7429859c7342359", + "BlogItem/BlogItemMoreDetail/query_text.ts": "fea5a3153cdb8ed173502e20c2d2bafc86b9f0b3fb72e1480bf2e1d4840bbecc", + "BlogItem/BlogItemMoreDetail/raw_response_type.ts": "909517f147f164902b112ce86c2cea3ad593f7ac9965ddfd887303362013500e", + "BlogItem/BlogItemMoreDetail/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", + "BlogItem/BlogItemMoreDetail/resolver_reader.ts": "24d4f35d31cd1c4618fd595c49ddb7c9c7c834391091306d767e40e8dca8dae0", + "Checkin/CheckinDisplay/output_type.ts": "fd32844468df9d95698b4fed38ccc44073d46031c2c30730a3ff625092eba5b4", + "Checkin/CheckinDisplay/param_type.ts": "aa97260087fb843fec954cca5e0f792a2f231b9ff344d31a1a5b11ccd05b56db", + "Checkin/CheckinDisplay/resolver_reader.ts": "2a67d8332af394a8e34868f341964c02188dadad87e8ecd537cd08ee78c91f73", + "Checkin/__link/output_type.ts": "b4597916259c679ccc2e60ad2033a6975dc840844b5da671416bf497b2549f22", + "Checkin/make_super/output_type.ts": "a01567895f9dabd693bff08c03fbe983ebcbbbc207c4c8b10bd2ca76c9ba8afa", + "Checkin/make_super/refetch_reader.ts": "df0583caacc8470017752d91e7269385603113b69e10dbd082f921875c0e89e1", + "ICheckin/__link/output_type.ts": "6a9196b685503d0532d1690ca8e15001b9a738757e2e47e91ba1ea671095a8d4", + "Image/ImageDisplay/entrypoint.ts": "72bc5e19f380fff61132f1827e576ac6f2e6a7de5108a954307c0d54337d5b92", + "Image/ImageDisplay/normalization_ast.ts": "8ebd330f4eb52a9a8f91025f0307b9fda306dc0cea72d710e34f3323bd86eee2", + "Image/ImageDisplay/output_type.ts": "66b9764ea710f9212eb2e967db467b6df1412fbfe57d01cba6b15992c6853aa8", + "Image/ImageDisplay/param_type.ts": "283a4017a63b74c43c922f595d35d345a16e11c4516df51282119310f9c3a477", + "Image/ImageDisplay/query_text.ts": "9dc585354332436870ae62968b9c21279c9474b58d6e56e625600d578f0dc103", + "Image/ImageDisplay/raw_response_type.ts": "878be1b91c9b19f30839ecbac5f045451eb02931f109b344b224c113a6928f17", + "Image/ImageDisplay/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", + "Image/ImageDisplay/resolver_reader.ts": "f08ddefa71b6b57d4c94248803bddd633f41eb385bc4a3e3df04f7a410c73865", + "Image/ImageDisplayWrapper/output_type.ts": "99c260982d5a05e790687cca3b547d99c1e1107a25fce9b6404f2f9a1b097dab", + "Image/ImageDisplayWrapper/param_type.ts": "8648a6210cd9a255484188fd708af47ef0663527078d5dfe7f0677f1d8d0241d", + "Image/ImageDisplayWrapper/resolver_reader.ts": "b34617fc942c67029b80a772b953d306dab16e1759baf684ab3fdff25e4c67e1", + "Mutation/MutualBestFriendSetterOtherSide/entrypoint.ts": "ed81ac99621b3aa4bdd8ca1349ac04f187fac924bb4cc853a313daf491a482a3", + "Mutation/MutualBestFriendSetterOtherSide/normalization_ast.ts": "2e0f209afe5b24a92641c39a5b277706c2f8e8f309cede75abcf674895d133a1", + "Mutation/MutualBestFriendSetterOtherSide/output_type.ts": "b183abc26192e042518c39b294e4b8b1e209850b78f5bfc2608df373cc852d0e", + "Mutation/MutualBestFriendSetterOtherSide/param_type.ts": "61b422ac363dc3bbf89bc2d06ddfae9e4f9377dfe70eab4776806826dfafa8d2", + "Mutation/MutualBestFriendSetterOtherSide/parameters_type.ts": "1c89261cc9bbeabccaf9d6b81f29b15383a8fe77320a97058fc484e414885299", + "Mutation/MutualBestFriendSetterOtherSide/query_text.ts": "f7a9862918f4bf4a813f665037a5c2cd2b1ed2577388f1545176514df0877c6f", + "Mutation/MutualBestFriendSetterOtherSide/raw_response_type.ts": "a65efe0914df46f74a82236ce7956fc3faa53a276101e3cc733a4430bdd37ab7", + "Mutation/MutualBestFriendSetterOtherSide/refetch_reader.ts": "0d1e34a3fdf7e6a54a1686d4f7eda08815b5761cfe46dc005df011b526f53b56", + "Mutation/MutualBestFriendSetterOtherSide/resolver_reader.ts": "e3098ee7aed567e5cca0fc497bbaa22005ef0ddddef218504648a8501af6ac13", + "Mutation/MututalBestFriendSetterMutation/entrypoint.ts": "0afdfe3cc83d00095bb3191816c9a92ef867fc3dfbd767a8c1a3d943ced725c2", + "Mutation/MututalBestFriendSetterMutation/normalization_ast.ts": "44b1a92eae9959cd95e457200f998cd8430c396867ede0a18d6f7cf023c2e7db", + "Mutation/MututalBestFriendSetterMutation/output_type.ts": "20e3bdab13e98fcded68c3ad8bed2cf964c182f6533beaa3999f7520d4ac28c2", + "Mutation/MututalBestFriendSetterMutation/param_type.ts": "fc2d9a5cc87d6c3f54a07ee9ad0a0b0bdc98d6ab70b7e862118393b642fb9ffe", + "Mutation/MututalBestFriendSetterMutation/parameters_type.ts": "8386988d4df100fff725a0d7d9f8ee31508e564fb57e967865d742a79509124b", + "Mutation/MututalBestFriendSetterMutation/query_text.ts": "8e4471b40b4c388d476f83ec641b19e52fc6a12112d781d36494c24c2157c382", + "Mutation/MututalBestFriendSetterMutation/raw_response_type.ts": "cd3729f6b4864c75f16958dab427fdb92826581d92676fdfd5f6d1a84e52f756", + "Mutation/MututalBestFriendSetterMutation/resolver_reader.ts": "7d61d2a6d83eb9404f2f6e86d0eaaae98eae302f48b0be4f412922e1ab6146a8", + "Mutation/SetTagline/entrypoint.ts": "088e73afa71e74ffb78ca353999288edbfa98574acb2fd95974d29a7e90b5317", + "Mutation/SetTagline/normalization_ast.ts": "fb3cb878956358e9f76f2ce46b71e0d8eb68f74da10dd87fe6ae3f9be6d7de8d", + "Mutation/SetTagline/output_type.ts": "a0c3154c613ab9a0113d452966d3f4315ea4a39a6f7b5bc65132dddf4ad501a2", + "Mutation/SetTagline/param_type.ts": "d6c614cf427dbaa4105d0acfdf1912a6189a1837124b1588ce9aeae40e85ce0c", + "Mutation/SetTagline/parameters_type.ts": "eeb4c19076e646b4912304de3330edf25e9d8140f804fea0cb7cce3a930f5a8f", + "Mutation/SetTagline/query_text.ts": "2a8662c5934b3c6231ad6fcf7829db1c69958647bed367d7accf5118d3b10f3f", + "Mutation/SetTagline/raw_response_type.ts": "ebd522d6450712aeef4fc1d4eb4c425fe678c0fa212d024adb4e02371f6510ab", + "Mutation/SetTagline/resolver_reader.ts": "642b9d90e3c3d3f8c2ff3e66ff59064885d69f1366a0100c46be94442768a424", + "NewsfeedItem/NewsfeedAdOrBlog/output_type.ts": "5db19869495875ba56a5b91bde13dc11eedcde48084630142b325e7b6d6a5084", + "NewsfeedItem/NewsfeedAdOrBlog/param_type.ts": "88f98be0f8d4f9d3cd8dc40ee76707070eb72b0757971e64272bc25acf8e9a45", + "NewsfeedItem/NewsfeedAdOrBlog/resolver_reader.ts": "4e9472f92a31ac244bfe333168bf231032c5175c565fa0bd42ea88a49da027a1", + "NewsfeedItem/__link/output_type.ts": "0f9707f53d3518006c194df999d11518a3370f0807dca66fc83ec961b47b5657", + "NewsfeedItem/asAdItem/resolver_reader.ts": "022409d41e6634a4f3bd03fd1c14eb656097c0ad125b0ab0c685bc8ee220966b", + "NewsfeedItem/asBlogItem/resolver_reader.ts": "2ef3cbfcc8f289de3fdda7f4a172be616d7bdc6d2421f34c5102b67e99b15c0a", + "Pet/Avatar/output_type.ts": "7174edac4e4d3b2a25c973ab6c3be4d5dfb5ab1c7b6ce4e6351e671813235356", + "Pet/Avatar/param_type.ts": "55eed2ccde13516119f5926c4f19a9fd4e139d5675016e2acad07bacac4012b4", + "Pet/Avatar/resolver_reader.ts": "2759e9aa48cbade143e1e6cfae39b835d8de5c5b168048a6b30a001448fff768", + "Pet/FavoritePhraseLoader/output_type.ts": "a5817ccd37f1e1f43281d7b4ed5126ef5afc91ec971b1f2761b8e5b7295dc7bd", + "Pet/FavoritePhraseLoader/param_type.ts": "8458adad6d72756bc95b1b676cb0cfb4bd834128edfe320fbe99973f83964e42", + "Pet/FavoritePhraseLoader/resolver_reader.ts": "cf9c69d1e4a992e6c2ba68a8113f2d45aecf229a12c1facb628c9b9866bdedbc", + "Pet/FirstCheckinMakeSuperButton/output_type.ts": "935ec58abf14d71c8e61d1327bd93a0ad91be5287646f1a76f8999b0b9016d49", + "Pet/FirstCheckinMakeSuperButton/param_type.ts": "d32121d4d9333a7f4e9fa6f7c734fb2fa79d4473e3a909b444f0e2a2818985c0", + "Pet/FirstCheckinMakeSuperButton/resolver_reader.ts": "77e6bff1f51773e242d22672e2032ab7c318170948a4c81f1118760fb8ce4534", + "Pet/MutualBestFriendSetter/output_type.ts": "f64b7bb76c31f2e65dfb74280bba75b889f6360ab1caab95fbbfaaa76a5e831a", + "Pet/MutualBestFriendSetter/param_type.ts": "f2567e33f1929f4dfa9e448952e031e61bd6b53eb1244db17af144a3bfd1fe15", + "Pet/MutualBestFriendSetter/resolver_reader.ts": "17e052b40b932683a84fa0e9fe612c0f1bf41f3bf8e529891548c181c92610c1", + "Pet/PetBestFriendCard/output_type.ts": "150691b5769fd4d8797b392a12e5471f86cf18ed09f1c1a49be005135631a1fd", + "Pet/PetBestFriendCard/param_type.ts": "d5b0a689b19eb44d69d9804a88c38f70bf6388182c1b49d55c2ddb61a14e1906", + "Pet/PetBestFriendCard/resolver_reader.ts": "cbe475fd52d825838da7988dea194689427588f3babbaf64de5667b9dbf31117", + "Pet/PetCheckinsCard/__refetch__0.ts": "ea43fdb972040e56405b422c303c7d3e7309bcf6806448ba7dad834d963c9800", + "Pet/PetCheckinsCard/__refetch__query_text__0.ts": "1e68c254bde5a3bd192ea3f5682b11c9f91437109554c54e13a127a2e366d4fe", + "Pet/PetCheckinsCard/entrypoint.ts": "4e632bef5c8a3b9ec014cbba2b665a07d674d455617e40e0dc1f6f21199b70ce", + "Pet/PetCheckinsCard/normalization_ast.ts": "f5050cc4649285dddba92fe77300314c442538766c2f56f60efb1612e63320c9", + "Pet/PetCheckinsCard/output_type.ts": "88fe0e7296aa229c6e434c441ddb5ac422159ce652f3ffa02ad32e61d8462310", + "Pet/PetCheckinsCard/param_type.ts": "2688b882a118e3c24a3f911ae8c0d733eb0518481ce9d2489907c998e90a5c08", + "Pet/PetCheckinsCard/parameters_type.ts": "8e3ab8b9155109a9c6aefe98844f5a122a247bb9c3bf90a3c8021402af2d2e2c", + "Pet/PetCheckinsCard/query_text.ts": "7bae0104443db221db0ad1fc5d683145941aa621a666e8f166d634a18143682e", + "Pet/PetCheckinsCard/raw_response_type.ts": "12d840caa11ff291b7297bb214f1dc61b74df4e8933c82cd2722bb0f17f53c31", + "Pet/PetCheckinsCard/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", + "Pet/PetCheckinsCard/resolver_reader.ts": "c68c47bf0aa080104c235a3be59f0b9af161cb1ceb8a18ba03c4681d3742f86d", + "Pet/PetCheckinsCardList/__refetch__0.ts": "ea43fdb972040e56405b422c303c7d3e7309bcf6806448ba7dad834d963c9800", + "Pet/PetCheckinsCardList/__refetch__query_text__0.ts": "1e68c254bde5a3bd192ea3f5682b11c9f91437109554c54e13a127a2e366d4fe", + "Pet/PetCheckinsCardList/entrypoint.ts": "e2fc09e9e46c0f9acf3f1e0d043b20a05fc0fbe8bb55b855fff3f1c12016b531", + "Pet/PetCheckinsCardList/normalization_ast.ts": "f5050cc4649285dddba92fe77300314c442538766c2f56f60efb1612e63320c9", + "Pet/PetCheckinsCardList/output_type.ts": "b7da20d60345b83575c4b12174e90a8befa8fddb7e927d03259cb0a1a38dc6a3", + "Pet/PetCheckinsCardList/param_type.ts": "a06111038bed13752194d8acf67fb0e9da17f7faae7b272dfa5d466fa262eb71", + "Pet/PetCheckinsCardList/parameters_type.ts": "9c9eb23c65905f94d6e0418def540478a44b47ca48581010a9c3b8b5cc8d17d8", + "Pet/PetCheckinsCardList/query_text.ts": "4063acad529e58f56b83b8a80dad9a176592515dd6758ffc56a5e8702d242760", + "Pet/PetCheckinsCardList/raw_response_type.ts": "f561feb64e1ca6d539733ee08acce3908fa703bdd75167407f24137cccb12ad9", + "Pet/PetCheckinsCardList/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", + "Pet/PetCheckinsCardList/resolver_reader.ts": "d3422b8ee15395bf45381fc0c988441c962637ab18c6bc3acde5d641afb97a23", + "Pet/PetDetailDeferredRouteInnerComponent/output_type.ts": "98ec1f2f161301de965022d5c02655c7c6e1e99342a378047eb8f6bce4ab2d80", + "Pet/PetDetailDeferredRouteInnerComponent/param_type.ts": "2b469143b382f8c4bb3af9ae2428171c3ac85284e001b72ceaca03be9244438a", + "Pet/PetDetailDeferredRouteInnerComponent/resolver_reader.ts": "65c9346fec33ece625404cf541fdffd8870b466493fcbdac0b02995559c613d3", + "Pet/PetPhraseCard/output_type.ts": "eb0854b2f7f147d2028d76072de6d1bb659200bc8070f4fc085b0f2def90e022", + "Pet/PetPhraseCard/param_type.ts": "9d9cf30586df345abc38f64ecb2fc05fd378ba2c043bc349319eb82130a8b3e9", + "Pet/PetPhraseCard/resolver_reader.ts": "d135dc1923a96748aafb05d3a14cf16b3ebcc02cd4f4fdbf0886afbdb4d7d30c", + "Pet/PetStatsCard/output_type.ts": "3ab4f681d918c5f0d8f579ba6de92e34fcb26091e8864b2ef1fcbd5d9b918be6", + "Pet/PetStatsCard/param_type.ts": "87a0ad8fbff6eb5d71cd5a483ac068a7b54d7e6e033537e8f933505964d6704c", + "Pet/PetStatsCard/parameters_type.ts": "22e5790c10ffa53423e5871781039965e4e6fcb7ff30181780ec28fbe29cf8aa", + "Pet/PetStatsCard/resolver_reader.ts": "aaf5181afaea8b4199bbc3e511dc84b2e280724d16c6a26db835a03c2139e957", + "Pet/PetSummaryCard/output_type.ts": "0ee7284804606b5d0ee0e6d9bfcd3a0b4d48dd16f7705cef062268759d442faa", + "Pet/PetSummaryCard/param_type.ts": "d724860f5e8dd247bd3248d032e73c65a3782c713b81a799921459b36de3d679", + "Pet/PetSummaryCard/resolver_reader.ts": "440ed80c99d793e4703430b8d20dce9b002a344b18d41f83186646edd809ba08", + "Pet/PetTaglineCard/output_type.ts": "e5d4be7af014facf0ebf97383817cf3f63b2b8743246ea583f8b533961ea2c53", + "Pet/PetTaglineCard/param_type.ts": "a000971cc7c50161cd085b333d57a553f76ee678684ac3a750ecd07826af8a07", + "Pet/PetTaglineCard/resolver_reader.ts": "b6224a5ab59673d9919f6b2d2329cf679bbe2e19de8ef71a52747dc5507fcc3e", + "Pet/PetUpdater/output_type.ts": "1c2403cc29ecfd43c5b2967e8e202b4d563898d2f890d0630989c5c4c28e1ec0", + "Pet/PetUpdater/param_type.ts": "73ce7b406820d1993f6069cb1001b22c162828110b75ba7e9f20b9411a9d3de9", + "Pet/PetUpdater/resolver_reader.ts": "a334a2f4914099254e6ea4e24ae14c48ec448f750ba6c27b71507991f547c9df", + "Pet/Unreachable2/output_type.ts": "41536c1d572fa390962dacfcf1db8ad58f0eea9dad0e4affc42faef591942e7f", + "Pet/Unreachable2/param_type.ts": "b418452c8e15d7459508764004711fd2902c9c43fcf91dbd800d0fe439fa41fa", + "Pet/UnreachableFromEntrypoint/param_type.ts": "d0b26c017538b80e5e5f9318b45d3ba9bfcd942a5b27efd66915abed2c046535", + "Pet/__link/output_type.ts": "e55ab037a5306049ce15c825872f8f65eab9106891b2f6219999e02c8b5035d6", + "Pet/__refetch/output_type.ts": "88a33e770cbe2f404eeb40f63adb5326901a41a80576c27afe0248418da3ab8c", + "Pet/__refetch/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", + "Pet/checkinsPointer/output_type.ts": "90a228959b5af951f138e4a55a05513faa9ebf96b771580e58a50c47b29a356e", + "Pet/checkinsPointer/param_type.ts": "b14dc9f6bd95a359cc432da1d5fb50543fe31f589a7a2b029207551542e6ef2b", + "Pet/checkinsPointer/resolver_reader.ts": "1139a4a23b8027a20d09c19962301fcb5c884969764514a5e6ee0061badd1d2d", + "Pet/custom_pet_refetch/output_type.ts": "4619ca2f137cdff24e862f882e1d4a9bf81483e75d408545024cd462d5519d78", + "Pet/custom_pet_refetch/refetch_reader.ts": "cb5ab166aba10f8b73177435349edd6c6db7f5fb07f3f1bcb00e76c927f1e1fa", + "Pet/fullName/output_type.ts": "873d5798b14e504596e1e1b44c5f3b17e12e202024d101f11246e90809acb91f", + "Pet/fullName/param_type.ts": "bb234802fd3c9d6623574de95d78363a4633a9e3876a25a2c8d55af142577d19", + "Pet/fullName/resolver_reader.ts": "6663dbe1c28c0f8d30ffd6df0165ccbd948621f3750e86a9829b5455ba8ff2c0", + "Pet/set_best_friend/output_type.ts": "da38397ace175732f8511c1f8e7eda48b8242b7293396794f3da9ef8786ca97f", + "Pet/set_best_friend/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", + "Pet/set_best_friend_do_not_use/output_type.ts": "a4fd9fc3b4ee4553261b77d617d0cd801c94eaa1e090dd759e89d25115a1192d", + "Pet/set_pet_tagline/output_type.ts": "e71844dee67c3be867b7849737054b52899c03b476043dff0bc2d9d1a47eea49", + "Pet/set_pet_tagline/refetch_reader.ts": "eff70a060373c61d453dbf7ff8040e336aeda29c62be83957e433aec652d8600", + "PetStats/refetch_pet_stats/output_type.ts": "a21783000c5cadd45c8d39976d5689278cede701f7f7a29aaf376e5ef7f33aa3", + "PetStats/refetch_pet_stats/refetch_reader.ts": "cb5ab166aba10f8b73177435349edd6c6db7f5fb07f3f1bcb00e76c927f1e1fa", + "Query/HomeRoute/entrypoint.ts": "a834e57989d799cf3b5a79b17ddee919028b59890b6e4f5be8ba8c672ff1f9ae", + "Query/HomeRoute/normalization_ast.ts": "506e3cc48739a2e0fcf53b6859b671c59d03b00f161fba9cac9f5bba58cd1111", + "Query/HomeRoute/output_type.ts": "d6acba8764066f24058b52a1eaf3342edb472dead678af23a20f5040d3f6421b", + "Query/HomeRoute/param_type.ts": "ffd1c592f9ec35c716514879ec1f02d1d7e863e01c4c56f29cff21d16ce8c1f4", + "Query/HomeRoute/query_text.ts": "ad6a330f27a27d790b02385c5554fa4c1524a0e66b13e8d9e0679a536e02cb87", + "Query/HomeRoute/raw_response_type.ts": "ecf82fc9c36b636eff7cad10a94f2def9fca6853b08a0c7643c46694503a96b1", + "Query/HomeRoute/resolver_reader.ts": "2fc843e02227b1564e51896ca3d0b9877ed4c03d7d3a9078f2cd8f1aee365ed2", + "Query/Newsfeed/entrypoint.ts": "b2a50769ead9b161aa32b832eedad4444a9bbfb635f000852026743084e0d891", + "Query/Newsfeed/normalization_ast.ts": "0f528a28561e4ba2e65ad966431c28feeb4e20ca7635f796435303c461fcbba6", + "Query/Newsfeed/output_type.ts": "f4d95063e87382b35df6d7a3000fad4ef49e53ddce3d65c134ebf6d9ae6789b2", + "Query/Newsfeed/param_type.ts": "39be4f76a6e6d878effbae830858217a17c371f919936e22433559311d1cd803", + "Query/Newsfeed/query_text.ts": "40c0cf3a0b5cb623e5017601010b1e4cff38227e6028b33ba4cad58799a61c67", + "Query/Newsfeed/raw_response_type.ts": "5342e98b9dde1af4ab48da7b12c0beb421971a2903fd40b3cd6fdba82cf8bae4", + "Query/Newsfeed/resolver_reader.ts": "9e5f5a1e003e450d4699a7923afafe964113e158609c9b038c9536998dd7db38", + "Query/OnlyOneRootLoadablePet/entrypoint.ts": "90cd9f0dc3dc0b17629f0190aa28ef19cbec10b65fb58e303f7775ed7898a2fc", + "Query/OnlyOneRootLoadablePet/normalization_ast.ts": "ba40bded8989973088b2b53868ccb8e41d7db418032721b3d63fd3f8782ea031", + "Query/OnlyOneRootLoadablePet/output_type.ts": "0311adf8b9b180db8cf4cb30032514192f359566986d416416eb5246e51a3c7a", + "Query/OnlyOneRootLoadablePet/param_type.ts": "31dc7c4657118b115f0ad7bc185087e5ddc6f66192e26abb7bf2296f7c32272f", + "Query/OnlyOneRootLoadablePet/parameters_type.ts": "e160f5c65ecd9e51ed107029a9fd9801b4de3c14f693f00ae96c036d975f21f5", + "Query/OnlyOneRootLoadablePet/query_text.ts": "2b332f612188ecc31bacd69c5fbf9e8f42dca9c8cf71479ab3785a030e9bba44", + "Query/OnlyOneRootLoadablePet/raw_response_type.ts": "95e0f8a6affc4fc795d565d415781d27cfdf9f71856609b3461a476934c7a79e", + "Query/OnlyOneRootLoadablePet/resolver_reader.ts": "c4ab7f3fad25e5d04e1dd168a4e83965e3c6dba92dc7bdcf86acd0b6bf49797b", + "Query/PetByName/entrypoint.ts": "42123982bf0fa0894f4f3f425509b69d037dd9e2176b115212e909857ed0ca2c", + "Query/PetByName/normalization_ast.ts": "171cc05018831b65002bf7d752918c3d7a64dd626e4bf58fd1ebee0ab3b82943", + "Query/PetByName/output_type.ts": "9bb46203820820d85e5f5995a88b99ec590401e730b94b580f39aadfed74181b", + "Query/PetByName/param_type.ts": "4a8db3f1c3f011e9a069ed93a15f74ff43991dea16808efb59ef7c30114d78e2", + "Query/PetByName/parameters_type.ts": "a8b31b58f4f819e0595c7514129274f51d65bc179ac27615b7a2f36b6366f1ef", + "Query/PetByName/query_text.ts": "1bcbee20172fadd8804e01dc71ab7674c856ce48abe4d5f3eb3796553e69bffb", + "Query/PetByName/raw_response_type.ts": "7f560f5cf0d499c07d0d91bdd28cc87f1f6bba2dd6f36a452d9bee92c404b8d4", + "Query/PetByName/resolver_reader.ts": "404b59e4a4dbd5aee3bdb5abb761ea35cff0e05b0cbc198df6cfa7fddbccfec2", + "Query/PetCheckinListRoute/__refetch__0.ts": "3d97f68e6975eb037bf6dbc24425ee6d452d6b0636a8ad95365cae2980145a58", + "Query/PetCheckinListRoute/__refetch__query_text__0.ts": "24d48ba213e35322a44512c6586f4e3af9f4553492e08515f6a571f39be918dd", + "Query/PetCheckinListRoute/entrypoint.ts": "16def2fea2698f1c1280583c6356e21c8465ef419c0b5020f940e377f7609904", + "Query/PetCheckinListRoute/normalization_ast.ts": "58dbc0da283360d28ab8c1d1fd1b572512b853ad5c0fae2a1b46112320bce28f", + "Query/PetCheckinListRoute/output_type.ts": "e9d4b1a8edf2341f9810591a80d2d80b64eab7f7df3028b8a2e2a4e5de4dd29d", + "Query/PetCheckinListRoute/param_type.ts": "347da59ca627a2743f09e6aebe9adb4e90694b8fbfbcfd6bfa8b8e7366cad54f", + "Query/PetCheckinListRoute/parameters_type.ts": "d14161e6efb326a75fe67e7bb6a958bbd7ca203302620759dc57b0760bf41f8f", + "Query/PetCheckinListRoute/query_text.ts": "04ca696a8a8da1c735f470242866a144998a92c724ca0497fe2d83202c041abd", + "Query/PetCheckinListRoute/raw_response_type.ts": "b154864314187edbcf31503b5c60b414e32fc052ed510cbef2157e43068c09f5", + "Query/PetCheckinListRoute/resolver_reader.ts": "21f42307c85986553ccdc49aa5e43380375a2553a70dee65c4cd0254897729b5", + "Query/PetDetailDeferredRoute/entrypoint.ts": "68360a8a9ff42e12bcfd070b59cde8d102c2d2d711319fac942c0cf4b0fc04d7", + "Query/PetDetailDeferredRoute/normalization_ast.ts": "5d5ecba5249e7f63aae710137be6362c1b79a1f9e3715d0c0f7bbf6f3504e9c8", + "Query/PetDetailDeferredRoute/output_type.ts": "804e63795e041e77ea2c70547c1719b3ef006ef32b9c7e89a11d795b0cfe08b8", + "Query/PetDetailDeferredRoute/param_type.ts": "65a7d8ec3bdfda1e26457681d1c0513e8bf2c7613da743ac6482c274976142ab", + "Query/PetDetailDeferredRoute/parameters_type.ts": "e33d34b182ebf653708a734a5fab807d896cba8fc8601a7ca02b89dc6fea01ae", + "Query/PetDetailDeferredRoute/query_text.ts": "7be7144e273679ecf7ddc24630404af6ea9c84c61a5c0a4fd8d805844225d12c", + "Query/PetDetailDeferredRoute/raw_response_type.ts": "9cc354d88ac9b93e1865501d0c17bbf885f2bc456b9178660aa199fb75755713", + "Query/PetDetailDeferredRoute/resolver_reader.ts": "f991a17ad03fe1099ab0f15058e8ae362381aa7898ac014f7a207436fa914188", + "Query/PetDetailRoute/__refetch__0.ts": "7d56b1bab5c5b053348b32250d2ca43fb47ffa8fd73a4726f34a2b6c0007d908", + "Query/PetDetailRoute/__refetch__1.ts": "5f0fb39daa0d17a8973cbe73b171bcc3ea33fac74b0964ee2b9f844e70863fc3", + "Query/PetDetailRoute/__refetch__2.ts": "970badc8bc051f10a80371ef6d14f68bc41d6e513462c3cb24b39e323e620142", + "Query/PetDetailRoute/__refetch__3.ts": "47bf143f363605e3864620deb4e2dee569aea294b62d929fc1aa2418bbaf36a6", + "Query/PetDetailRoute/__refetch__4.ts": "055ba0f6f3c8d9fe98d606b2ce439392f92ed6764856ae5d1123a8998dc4a63e", + "Query/PetDetailRoute/__refetch__5.ts": "e2b37b52689380b9c660e08e8369bf3cbbb54065bc3d8c104698780dbc4ce8c4", + "Query/PetDetailRoute/__refetch__query_text__0.ts": "c3be93ba7f79ecc50b878e4261a31d8c92d002d5ad2f5e46a7236bc765b25049", + "Query/PetDetailRoute/__refetch__query_text__1.ts": "58cb12d5227e89dbfbaa9734284427c156beffdddc1739c65a936529410a2015", + "Query/PetDetailRoute/__refetch__query_text__2.ts": "3d7c37256116a7e98f18f7a1a40c81563d50390463c25259b6cfcf0bddd7b7a1", + "Query/PetDetailRoute/__refetch__query_text__3.ts": "f477bb37304821e62681d1dc81fe51304d1d7515fea9a826f1699d7264dd1b3d", + "Query/PetDetailRoute/__refetch__query_text__4.ts": "53e644ae0a4fa726a0e6fd983bd7501d5cbd1192564d3f46861c738ef34c7d3c", + "Query/PetDetailRoute/__refetch__query_text__5.ts": "9ff2d4cd1ef66d2a42cd03cfbcbabf8996e3e6530867453e25568cd3af90b0bc", + "Query/PetDetailRoute/entrypoint.ts": "39ccc0d539d1c5ab9de142529d398bbfac5e48a72675c6c0731d92f7e2196f6e", + "Query/PetDetailRoute/normalization_ast.ts": "ba8a05fb32ba9fcdf5f8a76f741a989742ca36ab8d2af2023523c8de76035fda", + "Query/PetDetailRoute/output_type.ts": "849f17a87b0104b296bf279ec58ad3e4ae64b7bc31f30bd2eaa8450ef2226b43", + "Query/PetDetailRoute/param_type.ts": "329e02f754ad5c1c113381561dd72c76fe022d9b8e87bc29320b7af726d49cd5", + "Query/PetDetailRoute/parameters_type.ts": "e33143383017660c2908cef05cdd51526bc437ba2f4253f26bcf3d85e3c59af9", + "Query/PetDetailRoute/query_text.ts": "e3ad18ca22e16321cf2bd3a2485c08a06062126614384a059477f3d2a7e5a532", + "Query/PetDetailRoute/raw_response_type.ts": "a3bc4d9cb5e86debc86f12eb5bea780cddea91192d2f49bd87c00316331743f7", + "Query/PetDetailRoute/resolver_reader.ts": "f720e2bfc907a803b14d721e77a13c326ad38daab9c2657319eb58f67ca5ce53", + "Query/PetFavoritePhrase/entrypoint.ts": "5f01f682d95d4200098913f61d31073234077094de7c82012d88a91dfd49f911", + "Query/PetFavoritePhrase/normalization_ast.ts": "90841080c740866416460e1bba30ba3a50f26af73c6eb5a4038afedf5f043ad5", + "Query/PetFavoritePhrase/output_type.ts": "a5cb33ab26136d068adca610092fc74f37ad2fbd2d532da84c9108fb81b7dfa7", + "Query/PetFavoritePhrase/param_type.ts": "70ec007c1024e05b6ce3cf4c012b59dc88486f4c95ab1d6cc60dd7d6c093353d", + "Query/PetFavoritePhrase/parameters_type.ts": "cbe7bdbed463fb4c61ce6a581d1d7427b797dcf748dcb7570b527a5dae792730", + "Query/PetFavoritePhrase/query_text.ts": "33343ee6c21fe3c1769b492151a8df596d75364db8d9f968fc5fd476cf9ac6ef", + "Query/PetFavoritePhrase/raw_response_type.ts": "5dee7406723cae116651a5f41f71af2bdbe4a58230ae9e92765e6e28df81b44d", + "Query/PetFavoritePhrase/refetch_reader.ts": "0d1e34a3fdf7e6a54a1686d4f7eda08815b5761cfe46dc005df011b526f53b56", + "Query/PetFavoritePhrase/resolver_reader.ts": "07941d24865ba668ba14562f771805c815e664022b3d2e87b57ff3f4abbe717a", + "Query/SmartestPetRoute/__refetch__0.ts": "86d93d935a99937abc1886221caac398648c053bd8a26132550d2806ef418f8c", + "Query/SmartestPetRoute/__refetch__1.ts": "bfce3b652a313d3f8a7c0345f0495c60e38a0ac7ee33ae3364a7be116dd2d750", + "Query/SmartestPetRoute/__refetch__query_text__0.ts": "8bc135df6d9510b015df9b5d38edb15427d2b0463c18a5e64c5e4c01a6207f97", + "Query/SmartestPetRoute/__refetch__query_text__1.ts": "ee2e66ff3a1339f38293cdd206568fd79049de29317128c0535a8a1cd4eb2f12", + "Query/SmartestPetRoute/entrypoint.ts": "bf728b00ae08aa29f998e76559f9756d0e7aea550cc8778c6d60bc5ff98e1d46", + "Query/SmartestPetRoute/normalization_ast.ts": "5afa545a213a13454609fe90f55bd2ed01fbecd7c4901531a1b090543ab6e542", + "Query/SmartestPetRoute/output_type.ts": "905edb1c17e8c1c2de3b6eb5197b8040d4199c452f9218c8ade7187ecbc8ce98", + "Query/SmartestPetRoute/param_type.ts": "5199e724d3b1b4a7f944e0613e3e161e0fb340ba24bdc01b17e6964131ced970", + "Query/SmartestPetRoute/query_text.ts": "c488a754454f26aad189684c78ec2c43c29ea05011ba2106215a1329235064ce", + "Query/SmartestPetRoute/raw_response_type.ts": "303e73ec923fd05b2064d638748c26c99fb3b40a8a027e56abfbdd7594096b68", + "Query/SmartestPetRoute/resolver_reader.ts": "cdb19aaa1fddab34ecef9ec5d05c2d794419aa6df29a2efa4005af22e912819f", + "Query/smartestPet/output_type.ts": "a01f669fc4d7dc6f0c8eb9fec07f57693f326ac04d363bffaabbd503d50a2a4e", + "Query/smartestPet/param_type.ts": "e957ff94b397b0303d4d5f4640bf3921f1530ca1d836cade439318d41728cca8", + "Query/smartestPet/resolver_reader.ts": "2bdc79417c62dae88ab63b9f4cafe70e736cb85cf23c5e2acf9acb7fa8d3dcdb", + "Viewer/NewsfeedPaginationComponent/entrypoint.ts": "600c627538542eebe5250b222dc66ea60a872c16bd26b2971254dcce995dc71f", + "Viewer/NewsfeedPaginationComponent/normalization_ast.ts": "6eeabd652394048cec4daf773acf4845883185523fc9ee4a7a47f34aeb5129bd", + "Viewer/NewsfeedPaginationComponent/output_type.ts": "6f8862f2f5042a653502fc95164dfc4d704febfdce304067226541e615fa0c70", + "Viewer/NewsfeedPaginationComponent/param_type.ts": "fb94104e64810d62b829d5eadfa97f00779c3c35debc913654b486beb2787cba", + "Viewer/NewsfeedPaginationComponent/parameters_type.ts": "d8d97e679a62a1b2fbaed59d1cff4d8516e312cfaeecab4a95834c96be8ab10a", + "Viewer/NewsfeedPaginationComponent/query_text.ts": "83090c96e3d93a978c03f42f38c4b3eac2d66808e20df2c1f932bdf98c2bfc7d", + "Viewer/NewsfeedPaginationComponent/raw_response_type.ts": "273d38a56b94f688bf1e7665ab3fbd00cc1e82d0d21b2d4b849f142a313b59de", + "Viewer/NewsfeedPaginationComponent/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", + "Viewer/NewsfeedPaginationComponent/resolver_reader.ts": "c96347d8c7dcdc0231cf55d9491a420e9b8cc7158ade853984d8eae077867c5a", + "iso.ts": "6efa63bfa778f8ada7b0b6b291ea072cf7d0fa56b732c74589e5c301364d273a", + "tsconfig.json": "1e3eb33a239b72f6dff0dc4cf9a4f940b6067ad0f27ff896c163b06aff24a93a" +} \ No newline at end of file From 89cafc5938db47e10d32f9056fdc6f0c37c5d508 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Mon, 24 Nov 2025 18:07:11 -0300 Subject: [PATCH 03/28] Rename artifact look cache to file system state Rename it to FileSystemState, modifies it to map the filepaths to a tuple the file hash and the content as string. Also, remove it's public method that writes to disk and add a methdo to handle to compare its keys with other state keys in order to write/delete to/from file system the proper artifacts. --- .../src/artifact_lookup_cache.rs | 37 ---------- .../artifact_content/src/file_system_state.rs | 72 +++++++++++++++++++ crates/artifact_content/src/lib.rs | 4 +- 3 files changed, 74 insertions(+), 39 deletions(-) delete mode 100644 crates/artifact_content/src/artifact_lookup_cache.rs create mode 100644 crates/artifact_content/src/file_system_state.rs diff --git a/crates/artifact_content/src/artifact_lookup_cache.rs b/crates/artifact_content/src/artifact_lookup_cache.rs deleted file mode 100644 index 116ff60f1..000000000 --- a/crates/artifact_content/src/artifact_lookup_cache.rs +++ /dev/null @@ -1,37 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, BTreeSet, HashMap}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ArtifactLookupCache { - hashes: HashMap, - sorted_keys: BTreeSet, -} - -impl ArtifactLookupCache { - pub fn new() -> Self { - Self { - hashes: HashMap::new(), - sorted_keys: BTreeSet::new(), - } - } - - pub fn insert(&mut self, filename: String, hash: String) { - self.sorted_keys.insert(filename.clone()); - self.hashes.insert(filename, hash); - } - - pub fn get(&self, filename: &str) -> Option<&String> { - self.hashes.get(filename) - } - - fn to_ordered_map(&self) -> BTreeMap<&String, &String> { - self.sorted_keys - .iter() - .map(|k| (k, &self.hashes[k])) - .collect() - } - - pub fn write_to(&self, writer: W) -> Result<(), serde_json::Error> { - serde_json::to_writer_pretty(writer, &self.to_ordered_map()) - } -} diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs new file mode 100644 index 000000000..536968ce5 --- /dev/null +++ b/crates/artifact_content/src/file_system_state.rs @@ -0,0 +1,72 @@ +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeSet, HashMap}; + +use crate::operation_text::hash; +use isograph_config::PersistedDocumentsHashAlgorithm; + +type Filepath = String; +type FileContent = String; +type FileHash = String; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FileSystemState { + state: HashMap, + pub sorted_keys: BTreeSet, +} + +impl FileSystemState { + pub fn new() -> Self { + Self { + state: HashMap::new(), + sorted_keys: BTreeSet::new(), + } + } + + pub fn insert(&mut self, filename: Filepath, content: FileContent) { + let hashed_content = hash(&content, PersistedDocumentsHashAlgorithm::Sha256); + self.sorted_keys.insert(filename.clone()); + self.state.insert(filename, (content, hashed_content)); + } + + pub fn get_hashed_content(&self, filename: &str) -> Option<&FileHash> { + self.state + .get(filename) + .map(|(_, hashed_content)| hashed_content) + } + + pub fn is_empty(&self) -> bool { + self.state.is_empty() + } + + fn difference(&self, other: &FileSystemState) -> Vec { + self.sorted_keys + .difference(&other.sorted_keys) + .map(|k| k.clone()) + .collect() + } + + fn intersection(&self, other: &FileSystemState) -> Vec { + self.sorted_keys + .intersection(&other.sorted_keys) + .map(|k| k.clone()) + .collect() + } + + pub fn compare(&self, other: &FileSystemState) -> (Vec, Vec) { + let to_delete = self.difference(other); + let mut to_add = other.difference(self); + let candidate_to_update = self.intersection(other); + for key in candidate_to_update { + if self.get_hashed_content(&key).unwrap() != other.get_hashed_content(&key).unwrap() { + to_add.push(key); + } + } + (to_delete, to_add) + } +} + +impl Default for FileSystemState { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/artifact_content/src/lib.rs b/crates/artifact_content/src/lib.rs index 07dfbfdce..9c2bc7bf2 100644 --- a/crates/artifact_content/src/lib.rs +++ b/crates/artifact_content/src/lib.rs @@ -1,6 +1,6 @@ -mod artifact_lookup_cache; mod eager_reader_artifact; mod entrypoint_artifact; +mod file_system_state; mod format_parameter_type; pub mod generate_artifacts; mod imperatively_loaded_fields; @@ -14,5 +14,5 @@ mod reader_ast; mod refetch_reader_artifact; mod ts_config; -pub use artifact_lookup_cache::ArtifactLookupCache; +pub use file_system_state::FileSystemState; pub use generate_artifacts::get_artifact_path_and_content; From 265c0a191dd9e119e076febbe196cc21127dcfc6 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Mon, 24 Nov 2025 18:07:57 -0300 Subject: [PATCH 04/28] Remove the persisted file system state --- .../src/generate_artifacts.rs | 2 - .../__isograph/.artifact_lookup_cache.json | 249 ------------------ 2 files changed, 251 deletions(-) delete mode 100644 demos/pet-demo/src/components/__isograph/.artifact_lookup_cache.json diff --git a/crates/artifact_content/src/generate_artifacts.rs b/crates/artifact_content/src/generate_artifacts.rs index fdda52c7c..d5b996854 100644 --- a/crates/artifact_content/src/generate_artifacts.rs +++ b/crates/artifact_content/src/generate_artifacts.rs @@ -59,8 +59,6 @@ use crate::{ lazy_static! { pub static ref ENTRYPOINT_FILE_NAME: ArtifactFileName = "entrypoint.ts".intern().into(); - pub static ref ARTIFACT_LOOKUP_CACHE_FILE_NAME: ArtifactFileName = - ".artifact_lookup_cache.json".intern().into(); pub static ref ENTRYPOINT: ArtifactFilePrefix = "entrypoint".intern().into(); pub static ref ISO_TS_FILE_NAME: ArtifactFileName = "iso.ts".intern().into(); pub static ref ISO_TS: ArtifactFilePrefix = "iso".intern().into(); diff --git a/demos/pet-demo/src/components/__isograph/.artifact_lookup_cache.json b/demos/pet-demo/src/components/__isograph/.artifact_lookup_cache.json deleted file mode 100644 index fa23f10bf..000000000 --- a/demos/pet-demo/src/components/__isograph/.artifact_lookup_cache.json +++ /dev/null @@ -1,249 +0,0 @@ -{ - "AdItem/AdItemDisplay/entrypoint.ts": "ef6c3080d736a650b3bd3505f1de055ee9597656fd8eb98a0c0a4e16604a7f4d", - "AdItem/AdItemDisplay/normalization_ast.ts": "afcd25dc1216625756a107bafa5591a4666bae911174c5548f01caf2e1f7d0aa", - "AdItem/AdItemDisplay/output_type.ts": "87833bd6f8b6b6c47f8bbed7c677d9b1d9042ec513e243bab5c32425710a1c5e", - "AdItem/AdItemDisplay/param_type.ts": "36a8a55d3dc3f0603c37c9b559a1945c7c2f226df0d08052c78378de83ae33d3", - "AdItem/AdItemDisplay/query_text.ts": "764202397a8bccf7c201d4343d75bffb7c32355ee23c6599a9ce4a364d299be5", - "AdItem/AdItemDisplay/raw_response_type.ts": "6cda41d794ffae2780063d2618084a76c727b6978dbea613e04395add40acb09", - "AdItem/AdItemDisplay/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", - "AdItem/AdItemDisplay/resolver_reader.ts": "bedac9a0198fa2c4155bab6a409fd1af8b39ba8564e0bcf9864c6a54458d0075", - "BlogItem/BlogItemDisplay/output_type.ts": "a0a1b452219a44a3b4e66cfb76fdfd5a1ea616c95471f491de622ed6afb8736c", - "BlogItem/BlogItemDisplay/param_type.ts": "1804d34d97989893db818a1f8376e38013b78e16eb439e94600ae03bcc461aea", - "BlogItem/BlogItemDisplay/resolver_reader.ts": "a0b7e9406f53044d7e80f126664f9f6666e3f35755f40a86e069471852fca89b", - "BlogItem/BlogItemMoreDetail/entrypoint.ts": "e447084762c108abbc439ad30505d9cd1b96b159f46cc4bca1f82df70842186e", - "BlogItem/BlogItemMoreDetail/normalization_ast.ts": "49ecc094c43e309f65d62ba6f4722ea6349d2d32b6ee6b5570c3ccdcbab7a769", - "BlogItem/BlogItemMoreDetail/output_type.ts": "08bb819cf3c94c7174bf218559889057c46e75b44f8d979c4a5cda13259945e6", - "BlogItem/BlogItemMoreDetail/param_type.ts": "36a985d184dd3ca9817ff02c3c94fdde374211bb5f05990df7429859c7342359", - "BlogItem/BlogItemMoreDetail/query_text.ts": "fea5a3153cdb8ed173502e20c2d2bafc86b9f0b3fb72e1480bf2e1d4840bbecc", - "BlogItem/BlogItemMoreDetail/raw_response_type.ts": "909517f147f164902b112ce86c2cea3ad593f7ac9965ddfd887303362013500e", - "BlogItem/BlogItemMoreDetail/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", - "BlogItem/BlogItemMoreDetail/resolver_reader.ts": "24d4f35d31cd1c4618fd595c49ddb7c9c7c834391091306d767e40e8dca8dae0", - "Checkin/CheckinDisplay/output_type.ts": "fd32844468df9d95698b4fed38ccc44073d46031c2c30730a3ff625092eba5b4", - "Checkin/CheckinDisplay/param_type.ts": "aa97260087fb843fec954cca5e0f792a2f231b9ff344d31a1a5b11ccd05b56db", - "Checkin/CheckinDisplay/resolver_reader.ts": "2a67d8332af394a8e34868f341964c02188dadad87e8ecd537cd08ee78c91f73", - "Checkin/__link/output_type.ts": "b4597916259c679ccc2e60ad2033a6975dc840844b5da671416bf497b2549f22", - "Checkin/make_super/output_type.ts": "a01567895f9dabd693bff08c03fbe983ebcbbbc207c4c8b10bd2ca76c9ba8afa", - "Checkin/make_super/refetch_reader.ts": "df0583caacc8470017752d91e7269385603113b69e10dbd082f921875c0e89e1", - "ICheckin/__link/output_type.ts": "6a9196b685503d0532d1690ca8e15001b9a738757e2e47e91ba1ea671095a8d4", - "Image/ImageDisplay/entrypoint.ts": "72bc5e19f380fff61132f1827e576ac6f2e6a7de5108a954307c0d54337d5b92", - "Image/ImageDisplay/normalization_ast.ts": "8ebd330f4eb52a9a8f91025f0307b9fda306dc0cea72d710e34f3323bd86eee2", - "Image/ImageDisplay/output_type.ts": "66b9764ea710f9212eb2e967db467b6df1412fbfe57d01cba6b15992c6853aa8", - "Image/ImageDisplay/param_type.ts": "283a4017a63b74c43c922f595d35d345a16e11c4516df51282119310f9c3a477", - "Image/ImageDisplay/query_text.ts": "9dc585354332436870ae62968b9c21279c9474b58d6e56e625600d578f0dc103", - "Image/ImageDisplay/raw_response_type.ts": "878be1b91c9b19f30839ecbac5f045451eb02931f109b344b224c113a6928f17", - "Image/ImageDisplay/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", - "Image/ImageDisplay/resolver_reader.ts": "f08ddefa71b6b57d4c94248803bddd633f41eb385bc4a3e3df04f7a410c73865", - "Image/ImageDisplayWrapper/output_type.ts": "99c260982d5a05e790687cca3b547d99c1e1107a25fce9b6404f2f9a1b097dab", - "Image/ImageDisplayWrapper/param_type.ts": "8648a6210cd9a255484188fd708af47ef0663527078d5dfe7f0677f1d8d0241d", - "Image/ImageDisplayWrapper/resolver_reader.ts": "b34617fc942c67029b80a772b953d306dab16e1759baf684ab3fdff25e4c67e1", - "Mutation/MutualBestFriendSetterOtherSide/entrypoint.ts": "ed81ac99621b3aa4bdd8ca1349ac04f187fac924bb4cc853a313daf491a482a3", - "Mutation/MutualBestFriendSetterOtherSide/normalization_ast.ts": "2e0f209afe5b24a92641c39a5b277706c2f8e8f309cede75abcf674895d133a1", - "Mutation/MutualBestFriendSetterOtherSide/output_type.ts": "b183abc26192e042518c39b294e4b8b1e209850b78f5bfc2608df373cc852d0e", - "Mutation/MutualBestFriendSetterOtherSide/param_type.ts": "61b422ac363dc3bbf89bc2d06ddfae9e4f9377dfe70eab4776806826dfafa8d2", - "Mutation/MutualBestFriendSetterOtherSide/parameters_type.ts": "1c89261cc9bbeabccaf9d6b81f29b15383a8fe77320a97058fc484e414885299", - "Mutation/MutualBestFriendSetterOtherSide/query_text.ts": "f7a9862918f4bf4a813f665037a5c2cd2b1ed2577388f1545176514df0877c6f", - "Mutation/MutualBestFriendSetterOtherSide/raw_response_type.ts": "a65efe0914df46f74a82236ce7956fc3faa53a276101e3cc733a4430bdd37ab7", - "Mutation/MutualBestFriendSetterOtherSide/refetch_reader.ts": "0d1e34a3fdf7e6a54a1686d4f7eda08815b5761cfe46dc005df011b526f53b56", - "Mutation/MutualBestFriendSetterOtherSide/resolver_reader.ts": "e3098ee7aed567e5cca0fc497bbaa22005ef0ddddef218504648a8501af6ac13", - "Mutation/MututalBestFriendSetterMutation/entrypoint.ts": "0afdfe3cc83d00095bb3191816c9a92ef867fc3dfbd767a8c1a3d943ced725c2", - "Mutation/MututalBestFriendSetterMutation/normalization_ast.ts": "44b1a92eae9959cd95e457200f998cd8430c396867ede0a18d6f7cf023c2e7db", - "Mutation/MututalBestFriendSetterMutation/output_type.ts": "20e3bdab13e98fcded68c3ad8bed2cf964c182f6533beaa3999f7520d4ac28c2", - "Mutation/MututalBestFriendSetterMutation/param_type.ts": "fc2d9a5cc87d6c3f54a07ee9ad0a0b0bdc98d6ab70b7e862118393b642fb9ffe", - "Mutation/MututalBestFriendSetterMutation/parameters_type.ts": "8386988d4df100fff725a0d7d9f8ee31508e564fb57e967865d742a79509124b", - "Mutation/MututalBestFriendSetterMutation/query_text.ts": "8e4471b40b4c388d476f83ec641b19e52fc6a12112d781d36494c24c2157c382", - "Mutation/MututalBestFriendSetterMutation/raw_response_type.ts": "cd3729f6b4864c75f16958dab427fdb92826581d92676fdfd5f6d1a84e52f756", - "Mutation/MututalBestFriendSetterMutation/resolver_reader.ts": "7d61d2a6d83eb9404f2f6e86d0eaaae98eae302f48b0be4f412922e1ab6146a8", - "Mutation/SetTagline/entrypoint.ts": "088e73afa71e74ffb78ca353999288edbfa98574acb2fd95974d29a7e90b5317", - "Mutation/SetTagline/normalization_ast.ts": "fb3cb878956358e9f76f2ce46b71e0d8eb68f74da10dd87fe6ae3f9be6d7de8d", - "Mutation/SetTagline/output_type.ts": "a0c3154c613ab9a0113d452966d3f4315ea4a39a6f7b5bc65132dddf4ad501a2", - "Mutation/SetTagline/param_type.ts": "d6c614cf427dbaa4105d0acfdf1912a6189a1837124b1588ce9aeae40e85ce0c", - "Mutation/SetTagline/parameters_type.ts": "eeb4c19076e646b4912304de3330edf25e9d8140f804fea0cb7cce3a930f5a8f", - "Mutation/SetTagline/query_text.ts": "2a8662c5934b3c6231ad6fcf7829db1c69958647bed367d7accf5118d3b10f3f", - "Mutation/SetTagline/raw_response_type.ts": "ebd522d6450712aeef4fc1d4eb4c425fe678c0fa212d024adb4e02371f6510ab", - "Mutation/SetTagline/resolver_reader.ts": "642b9d90e3c3d3f8c2ff3e66ff59064885d69f1366a0100c46be94442768a424", - "NewsfeedItem/NewsfeedAdOrBlog/output_type.ts": "5db19869495875ba56a5b91bde13dc11eedcde48084630142b325e7b6d6a5084", - "NewsfeedItem/NewsfeedAdOrBlog/param_type.ts": "88f98be0f8d4f9d3cd8dc40ee76707070eb72b0757971e64272bc25acf8e9a45", - "NewsfeedItem/NewsfeedAdOrBlog/resolver_reader.ts": "4e9472f92a31ac244bfe333168bf231032c5175c565fa0bd42ea88a49da027a1", - "NewsfeedItem/__link/output_type.ts": "0f9707f53d3518006c194df999d11518a3370f0807dca66fc83ec961b47b5657", - "NewsfeedItem/asAdItem/resolver_reader.ts": "022409d41e6634a4f3bd03fd1c14eb656097c0ad125b0ab0c685bc8ee220966b", - "NewsfeedItem/asBlogItem/resolver_reader.ts": "2ef3cbfcc8f289de3fdda7f4a172be616d7bdc6d2421f34c5102b67e99b15c0a", - "Pet/Avatar/output_type.ts": "7174edac4e4d3b2a25c973ab6c3be4d5dfb5ab1c7b6ce4e6351e671813235356", - "Pet/Avatar/param_type.ts": "55eed2ccde13516119f5926c4f19a9fd4e139d5675016e2acad07bacac4012b4", - "Pet/Avatar/resolver_reader.ts": "2759e9aa48cbade143e1e6cfae39b835d8de5c5b168048a6b30a001448fff768", - "Pet/FavoritePhraseLoader/output_type.ts": "a5817ccd37f1e1f43281d7b4ed5126ef5afc91ec971b1f2761b8e5b7295dc7bd", - "Pet/FavoritePhraseLoader/param_type.ts": "8458adad6d72756bc95b1b676cb0cfb4bd834128edfe320fbe99973f83964e42", - "Pet/FavoritePhraseLoader/resolver_reader.ts": "cf9c69d1e4a992e6c2ba68a8113f2d45aecf229a12c1facb628c9b9866bdedbc", - "Pet/FirstCheckinMakeSuperButton/output_type.ts": "935ec58abf14d71c8e61d1327bd93a0ad91be5287646f1a76f8999b0b9016d49", - "Pet/FirstCheckinMakeSuperButton/param_type.ts": "d32121d4d9333a7f4e9fa6f7c734fb2fa79d4473e3a909b444f0e2a2818985c0", - "Pet/FirstCheckinMakeSuperButton/resolver_reader.ts": "77e6bff1f51773e242d22672e2032ab7c318170948a4c81f1118760fb8ce4534", - "Pet/MutualBestFriendSetter/output_type.ts": "f64b7bb76c31f2e65dfb74280bba75b889f6360ab1caab95fbbfaaa76a5e831a", - "Pet/MutualBestFriendSetter/param_type.ts": "f2567e33f1929f4dfa9e448952e031e61bd6b53eb1244db17af144a3bfd1fe15", - "Pet/MutualBestFriendSetter/resolver_reader.ts": "17e052b40b932683a84fa0e9fe612c0f1bf41f3bf8e529891548c181c92610c1", - "Pet/PetBestFriendCard/output_type.ts": "150691b5769fd4d8797b392a12e5471f86cf18ed09f1c1a49be005135631a1fd", - "Pet/PetBestFriendCard/param_type.ts": "d5b0a689b19eb44d69d9804a88c38f70bf6388182c1b49d55c2ddb61a14e1906", - "Pet/PetBestFriendCard/resolver_reader.ts": "cbe475fd52d825838da7988dea194689427588f3babbaf64de5667b9dbf31117", - "Pet/PetCheckinsCard/__refetch__0.ts": "ea43fdb972040e56405b422c303c7d3e7309bcf6806448ba7dad834d963c9800", - "Pet/PetCheckinsCard/__refetch__query_text__0.ts": "1e68c254bde5a3bd192ea3f5682b11c9f91437109554c54e13a127a2e366d4fe", - "Pet/PetCheckinsCard/entrypoint.ts": "4e632bef5c8a3b9ec014cbba2b665a07d674d455617e40e0dc1f6f21199b70ce", - "Pet/PetCheckinsCard/normalization_ast.ts": "f5050cc4649285dddba92fe77300314c442538766c2f56f60efb1612e63320c9", - "Pet/PetCheckinsCard/output_type.ts": "88fe0e7296aa229c6e434c441ddb5ac422159ce652f3ffa02ad32e61d8462310", - "Pet/PetCheckinsCard/param_type.ts": "2688b882a118e3c24a3f911ae8c0d733eb0518481ce9d2489907c998e90a5c08", - "Pet/PetCheckinsCard/parameters_type.ts": "8e3ab8b9155109a9c6aefe98844f5a122a247bb9c3bf90a3c8021402af2d2e2c", - "Pet/PetCheckinsCard/query_text.ts": "7bae0104443db221db0ad1fc5d683145941aa621a666e8f166d634a18143682e", - "Pet/PetCheckinsCard/raw_response_type.ts": "12d840caa11ff291b7297bb214f1dc61b74df4e8933c82cd2722bb0f17f53c31", - "Pet/PetCheckinsCard/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", - "Pet/PetCheckinsCard/resolver_reader.ts": "c68c47bf0aa080104c235a3be59f0b9af161cb1ceb8a18ba03c4681d3742f86d", - "Pet/PetCheckinsCardList/__refetch__0.ts": "ea43fdb972040e56405b422c303c7d3e7309bcf6806448ba7dad834d963c9800", - "Pet/PetCheckinsCardList/__refetch__query_text__0.ts": "1e68c254bde5a3bd192ea3f5682b11c9f91437109554c54e13a127a2e366d4fe", - "Pet/PetCheckinsCardList/entrypoint.ts": "e2fc09e9e46c0f9acf3f1e0d043b20a05fc0fbe8bb55b855fff3f1c12016b531", - "Pet/PetCheckinsCardList/normalization_ast.ts": "f5050cc4649285dddba92fe77300314c442538766c2f56f60efb1612e63320c9", - "Pet/PetCheckinsCardList/output_type.ts": "b7da20d60345b83575c4b12174e90a8befa8fddb7e927d03259cb0a1a38dc6a3", - "Pet/PetCheckinsCardList/param_type.ts": "a06111038bed13752194d8acf67fb0e9da17f7faae7b272dfa5d466fa262eb71", - "Pet/PetCheckinsCardList/parameters_type.ts": "9c9eb23c65905f94d6e0418def540478a44b47ca48581010a9c3b8b5cc8d17d8", - "Pet/PetCheckinsCardList/query_text.ts": "4063acad529e58f56b83b8a80dad9a176592515dd6758ffc56a5e8702d242760", - "Pet/PetCheckinsCardList/raw_response_type.ts": "f561feb64e1ca6d539733ee08acce3908fa703bdd75167407f24137cccb12ad9", - "Pet/PetCheckinsCardList/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", - "Pet/PetCheckinsCardList/resolver_reader.ts": "d3422b8ee15395bf45381fc0c988441c962637ab18c6bc3acde5d641afb97a23", - "Pet/PetDetailDeferredRouteInnerComponent/output_type.ts": "98ec1f2f161301de965022d5c02655c7c6e1e99342a378047eb8f6bce4ab2d80", - "Pet/PetDetailDeferredRouteInnerComponent/param_type.ts": "2b469143b382f8c4bb3af9ae2428171c3ac85284e001b72ceaca03be9244438a", - "Pet/PetDetailDeferredRouteInnerComponent/resolver_reader.ts": "65c9346fec33ece625404cf541fdffd8870b466493fcbdac0b02995559c613d3", - "Pet/PetPhraseCard/output_type.ts": "eb0854b2f7f147d2028d76072de6d1bb659200bc8070f4fc085b0f2def90e022", - "Pet/PetPhraseCard/param_type.ts": "9d9cf30586df345abc38f64ecb2fc05fd378ba2c043bc349319eb82130a8b3e9", - "Pet/PetPhraseCard/resolver_reader.ts": "d135dc1923a96748aafb05d3a14cf16b3ebcc02cd4f4fdbf0886afbdb4d7d30c", - "Pet/PetStatsCard/output_type.ts": "3ab4f681d918c5f0d8f579ba6de92e34fcb26091e8864b2ef1fcbd5d9b918be6", - "Pet/PetStatsCard/param_type.ts": "87a0ad8fbff6eb5d71cd5a483ac068a7b54d7e6e033537e8f933505964d6704c", - "Pet/PetStatsCard/parameters_type.ts": "22e5790c10ffa53423e5871781039965e4e6fcb7ff30181780ec28fbe29cf8aa", - "Pet/PetStatsCard/resolver_reader.ts": "aaf5181afaea8b4199bbc3e511dc84b2e280724d16c6a26db835a03c2139e957", - "Pet/PetSummaryCard/output_type.ts": "0ee7284804606b5d0ee0e6d9bfcd3a0b4d48dd16f7705cef062268759d442faa", - "Pet/PetSummaryCard/param_type.ts": "d724860f5e8dd247bd3248d032e73c65a3782c713b81a799921459b36de3d679", - "Pet/PetSummaryCard/resolver_reader.ts": "440ed80c99d793e4703430b8d20dce9b002a344b18d41f83186646edd809ba08", - "Pet/PetTaglineCard/output_type.ts": "e5d4be7af014facf0ebf97383817cf3f63b2b8743246ea583f8b533961ea2c53", - "Pet/PetTaglineCard/param_type.ts": "a000971cc7c50161cd085b333d57a553f76ee678684ac3a750ecd07826af8a07", - "Pet/PetTaglineCard/resolver_reader.ts": "b6224a5ab59673d9919f6b2d2329cf679bbe2e19de8ef71a52747dc5507fcc3e", - "Pet/PetUpdater/output_type.ts": "1c2403cc29ecfd43c5b2967e8e202b4d563898d2f890d0630989c5c4c28e1ec0", - "Pet/PetUpdater/param_type.ts": "73ce7b406820d1993f6069cb1001b22c162828110b75ba7e9f20b9411a9d3de9", - "Pet/PetUpdater/resolver_reader.ts": "a334a2f4914099254e6ea4e24ae14c48ec448f750ba6c27b71507991f547c9df", - "Pet/Unreachable2/output_type.ts": "41536c1d572fa390962dacfcf1db8ad58f0eea9dad0e4affc42faef591942e7f", - "Pet/Unreachable2/param_type.ts": "b418452c8e15d7459508764004711fd2902c9c43fcf91dbd800d0fe439fa41fa", - "Pet/UnreachableFromEntrypoint/param_type.ts": "d0b26c017538b80e5e5f9318b45d3ba9bfcd942a5b27efd66915abed2c046535", - "Pet/__link/output_type.ts": "e55ab037a5306049ce15c825872f8f65eab9106891b2f6219999e02c8b5035d6", - "Pet/__refetch/output_type.ts": "88a33e770cbe2f404eeb40f63adb5326901a41a80576c27afe0248418da3ab8c", - "Pet/__refetch/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", - "Pet/checkinsPointer/output_type.ts": "90a228959b5af951f138e4a55a05513faa9ebf96b771580e58a50c47b29a356e", - "Pet/checkinsPointer/param_type.ts": "b14dc9f6bd95a359cc432da1d5fb50543fe31f589a7a2b029207551542e6ef2b", - "Pet/checkinsPointer/resolver_reader.ts": "1139a4a23b8027a20d09c19962301fcb5c884969764514a5e6ee0061badd1d2d", - "Pet/custom_pet_refetch/output_type.ts": "4619ca2f137cdff24e862f882e1d4a9bf81483e75d408545024cd462d5519d78", - "Pet/custom_pet_refetch/refetch_reader.ts": "cb5ab166aba10f8b73177435349edd6c6db7f5fb07f3f1bcb00e76c927f1e1fa", - "Pet/fullName/output_type.ts": "873d5798b14e504596e1e1b44c5f3b17e12e202024d101f11246e90809acb91f", - "Pet/fullName/param_type.ts": "bb234802fd3c9d6623574de95d78363a4633a9e3876a25a2c8d55af142577d19", - "Pet/fullName/resolver_reader.ts": "6663dbe1c28c0f8d30ffd6df0165ccbd948621f3750e86a9829b5455ba8ff2c0", - "Pet/set_best_friend/output_type.ts": "da38397ace175732f8511c1f8e7eda48b8242b7293396794f3da9ef8786ca97f", - "Pet/set_best_friend/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", - "Pet/set_best_friend_do_not_use/output_type.ts": "a4fd9fc3b4ee4553261b77d617d0cd801c94eaa1e090dd759e89d25115a1192d", - "Pet/set_pet_tagline/output_type.ts": "e71844dee67c3be867b7849737054b52899c03b476043dff0bc2d9d1a47eea49", - "Pet/set_pet_tagline/refetch_reader.ts": "eff70a060373c61d453dbf7ff8040e336aeda29c62be83957e433aec652d8600", - "PetStats/refetch_pet_stats/output_type.ts": "a21783000c5cadd45c8d39976d5689278cede701f7f7a29aaf376e5ef7f33aa3", - "PetStats/refetch_pet_stats/refetch_reader.ts": "cb5ab166aba10f8b73177435349edd6c6db7f5fb07f3f1bcb00e76c927f1e1fa", - "Query/HomeRoute/entrypoint.ts": "a834e57989d799cf3b5a79b17ddee919028b59890b6e4f5be8ba8c672ff1f9ae", - "Query/HomeRoute/normalization_ast.ts": "506e3cc48739a2e0fcf53b6859b671c59d03b00f161fba9cac9f5bba58cd1111", - "Query/HomeRoute/output_type.ts": "d6acba8764066f24058b52a1eaf3342edb472dead678af23a20f5040d3f6421b", - "Query/HomeRoute/param_type.ts": "ffd1c592f9ec35c716514879ec1f02d1d7e863e01c4c56f29cff21d16ce8c1f4", - "Query/HomeRoute/query_text.ts": "ad6a330f27a27d790b02385c5554fa4c1524a0e66b13e8d9e0679a536e02cb87", - "Query/HomeRoute/raw_response_type.ts": "ecf82fc9c36b636eff7cad10a94f2def9fca6853b08a0c7643c46694503a96b1", - "Query/HomeRoute/resolver_reader.ts": "2fc843e02227b1564e51896ca3d0b9877ed4c03d7d3a9078f2cd8f1aee365ed2", - "Query/Newsfeed/entrypoint.ts": "b2a50769ead9b161aa32b832eedad4444a9bbfb635f000852026743084e0d891", - "Query/Newsfeed/normalization_ast.ts": "0f528a28561e4ba2e65ad966431c28feeb4e20ca7635f796435303c461fcbba6", - "Query/Newsfeed/output_type.ts": "f4d95063e87382b35df6d7a3000fad4ef49e53ddce3d65c134ebf6d9ae6789b2", - "Query/Newsfeed/param_type.ts": "39be4f76a6e6d878effbae830858217a17c371f919936e22433559311d1cd803", - "Query/Newsfeed/query_text.ts": "40c0cf3a0b5cb623e5017601010b1e4cff38227e6028b33ba4cad58799a61c67", - "Query/Newsfeed/raw_response_type.ts": "5342e98b9dde1af4ab48da7b12c0beb421971a2903fd40b3cd6fdba82cf8bae4", - "Query/Newsfeed/resolver_reader.ts": "9e5f5a1e003e450d4699a7923afafe964113e158609c9b038c9536998dd7db38", - "Query/OnlyOneRootLoadablePet/entrypoint.ts": "90cd9f0dc3dc0b17629f0190aa28ef19cbec10b65fb58e303f7775ed7898a2fc", - "Query/OnlyOneRootLoadablePet/normalization_ast.ts": "ba40bded8989973088b2b53868ccb8e41d7db418032721b3d63fd3f8782ea031", - "Query/OnlyOneRootLoadablePet/output_type.ts": "0311adf8b9b180db8cf4cb30032514192f359566986d416416eb5246e51a3c7a", - "Query/OnlyOneRootLoadablePet/param_type.ts": "31dc7c4657118b115f0ad7bc185087e5ddc6f66192e26abb7bf2296f7c32272f", - "Query/OnlyOneRootLoadablePet/parameters_type.ts": "e160f5c65ecd9e51ed107029a9fd9801b4de3c14f693f00ae96c036d975f21f5", - "Query/OnlyOneRootLoadablePet/query_text.ts": "2b332f612188ecc31bacd69c5fbf9e8f42dca9c8cf71479ab3785a030e9bba44", - "Query/OnlyOneRootLoadablePet/raw_response_type.ts": "95e0f8a6affc4fc795d565d415781d27cfdf9f71856609b3461a476934c7a79e", - "Query/OnlyOneRootLoadablePet/resolver_reader.ts": "c4ab7f3fad25e5d04e1dd168a4e83965e3c6dba92dc7bdcf86acd0b6bf49797b", - "Query/PetByName/entrypoint.ts": "42123982bf0fa0894f4f3f425509b69d037dd9e2176b115212e909857ed0ca2c", - "Query/PetByName/normalization_ast.ts": "171cc05018831b65002bf7d752918c3d7a64dd626e4bf58fd1ebee0ab3b82943", - "Query/PetByName/output_type.ts": "9bb46203820820d85e5f5995a88b99ec590401e730b94b580f39aadfed74181b", - "Query/PetByName/param_type.ts": "4a8db3f1c3f011e9a069ed93a15f74ff43991dea16808efb59ef7c30114d78e2", - "Query/PetByName/parameters_type.ts": "a8b31b58f4f819e0595c7514129274f51d65bc179ac27615b7a2f36b6366f1ef", - "Query/PetByName/query_text.ts": "1bcbee20172fadd8804e01dc71ab7674c856ce48abe4d5f3eb3796553e69bffb", - "Query/PetByName/raw_response_type.ts": "7f560f5cf0d499c07d0d91bdd28cc87f1f6bba2dd6f36a452d9bee92c404b8d4", - "Query/PetByName/resolver_reader.ts": "404b59e4a4dbd5aee3bdb5abb761ea35cff0e05b0cbc198df6cfa7fddbccfec2", - "Query/PetCheckinListRoute/__refetch__0.ts": "3d97f68e6975eb037bf6dbc24425ee6d452d6b0636a8ad95365cae2980145a58", - "Query/PetCheckinListRoute/__refetch__query_text__0.ts": "24d48ba213e35322a44512c6586f4e3af9f4553492e08515f6a571f39be918dd", - "Query/PetCheckinListRoute/entrypoint.ts": "16def2fea2698f1c1280583c6356e21c8465ef419c0b5020f940e377f7609904", - "Query/PetCheckinListRoute/normalization_ast.ts": "58dbc0da283360d28ab8c1d1fd1b572512b853ad5c0fae2a1b46112320bce28f", - "Query/PetCheckinListRoute/output_type.ts": "e9d4b1a8edf2341f9810591a80d2d80b64eab7f7df3028b8a2e2a4e5de4dd29d", - "Query/PetCheckinListRoute/param_type.ts": "347da59ca627a2743f09e6aebe9adb4e90694b8fbfbcfd6bfa8b8e7366cad54f", - "Query/PetCheckinListRoute/parameters_type.ts": "d14161e6efb326a75fe67e7bb6a958bbd7ca203302620759dc57b0760bf41f8f", - "Query/PetCheckinListRoute/query_text.ts": "04ca696a8a8da1c735f470242866a144998a92c724ca0497fe2d83202c041abd", - "Query/PetCheckinListRoute/raw_response_type.ts": "b154864314187edbcf31503b5c60b414e32fc052ed510cbef2157e43068c09f5", - "Query/PetCheckinListRoute/resolver_reader.ts": "21f42307c85986553ccdc49aa5e43380375a2553a70dee65c4cd0254897729b5", - "Query/PetDetailDeferredRoute/entrypoint.ts": "68360a8a9ff42e12bcfd070b59cde8d102c2d2d711319fac942c0cf4b0fc04d7", - "Query/PetDetailDeferredRoute/normalization_ast.ts": "5d5ecba5249e7f63aae710137be6362c1b79a1f9e3715d0c0f7bbf6f3504e9c8", - "Query/PetDetailDeferredRoute/output_type.ts": "804e63795e041e77ea2c70547c1719b3ef006ef32b9c7e89a11d795b0cfe08b8", - "Query/PetDetailDeferredRoute/param_type.ts": "65a7d8ec3bdfda1e26457681d1c0513e8bf2c7613da743ac6482c274976142ab", - "Query/PetDetailDeferredRoute/parameters_type.ts": "e33d34b182ebf653708a734a5fab807d896cba8fc8601a7ca02b89dc6fea01ae", - "Query/PetDetailDeferredRoute/query_text.ts": "7be7144e273679ecf7ddc24630404af6ea9c84c61a5c0a4fd8d805844225d12c", - "Query/PetDetailDeferredRoute/raw_response_type.ts": "9cc354d88ac9b93e1865501d0c17bbf885f2bc456b9178660aa199fb75755713", - "Query/PetDetailDeferredRoute/resolver_reader.ts": "f991a17ad03fe1099ab0f15058e8ae362381aa7898ac014f7a207436fa914188", - "Query/PetDetailRoute/__refetch__0.ts": "7d56b1bab5c5b053348b32250d2ca43fb47ffa8fd73a4726f34a2b6c0007d908", - "Query/PetDetailRoute/__refetch__1.ts": "5f0fb39daa0d17a8973cbe73b171bcc3ea33fac74b0964ee2b9f844e70863fc3", - "Query/PetDetailRoute/__refetch__2.ts": "970badc8bc051f10a80371ef6d14f68bc41d6e513462c3cb24b39e323e620142", - "Query/PetDetailRoute/__refetch__3.ts": "47bf143f363605e3864620deb4e2dee569aea294b62d929fc1aa2418bbaf36a6", - "Query/PetDetailRoute/__refetch__4.ts": "055ba0f6f3c8d9fe98d606b2ce439392f92ed6764856ae5d1123a8998dc4a63e", - "Query/PetDetailRoute/__refetch__5.ts": "e2b37b52689380b9c660e08e8369bf3cbbb54065bc3d8c104698780dbc4ce8c4", - "Query/PetDetailRoute/__refetch__query_text__0.ts": "c3be93ba7f79ecc50b878e4261a31d8c92d002d5ad2f5e46a7236bc765b25049", - "Query/PetDetailRoute/__refetch__query_text__1.ts": "58cb12d5227e89dbfbaa9734284427c156beffdddc1739c65a936529410a2015", - "Query/PetDetailRoute/__refetch__query_text__2.ts": "3d7c37256116a7e98f18f7a1a40c81563d50390463c25259b6cfcf0bddd7b7a1", - "Query/PetDetailRoute/__refetch__query_text__3.ts": "f477bb37304821e62681d1dc81fe51304d1d7515fea9a826f1699d7264dd1b3d", - "Query/PetDetailRoute/__refetch__query_text__4.ts": "53e644ae0a4fa726a0e6fd983bd7501d5cbd1192564d3f46861c738ef34c7d3c", - "Query/PetDetailRoute/__refetch__query_text__5.ts": "9ff2d4cd1ef66d2a42cd03cfbcbabf8996e3e6530867453e25568cd3af90b0bc", - "Query/PetDetailRoute/entrypoint.ts": "39ccc0d539d1c5ab9de142529d398bbfac5e48a72675c6c0731d92f7e2196f6e", - "Query/PetDetailRoute/normalization_ast.ts": "ba8a05fb32ba9fcdf5f8a76f741a989742ca36ab8d2af2023523c8de76035fda", - "Query/PetDetailRoute/output_type.ts": "849f17a87b0104b296bf279ec58ad3e4ae64b7bc31f30bd2eaa8450ef2226b43", - "Query/PetDetailRoute/param_type.ts": "329e02f754ad5c1c113381561dd72c76fe022d9b8e87bc29320b7af726d49cd5", - "Query/PetDetailRoute/parameters_type.ts": "e33143383017660c2908cef05cdd51526bc437ba2f4253f26bcf3d85e3c59af9", - "Query/PetDetailRoute/query_text.ts": "e3ad18ca22e16321cf2bd3a2485c08a06062126614384a059477f3d2a7e5a532", - "Query/PetDetailRoute/raw_response_type.ts": "a3bc4d9cb5e86debc86f12eb5bea780cddea91192d2f49bd87c00316331743f7", - "Query/PetDetailRoute/resolver_reader.ts": "f720e2bfc907a803b14d721e77a13c326ad38daab9c2657319eb58f67ca5ce53", - "Query/PetFavoritePhrase/entrypoint.ts": "5f01f682d95d4200098913f61d31073234077094de7c82012d88a91dfd49f911", - "Query/PetFavoritePhrase/normalization_ast.ts": "90841080c740866416460e1bba30ba3a50f26af73c6eb5a4038afedf5f043ad5", - "Query/PetFavoritePhrase/output_type.ts": "a5cb33ab26136d068adca610092fc74f37ad2fbd2d532da84c9108fb81b7dfa7", - "Query/PetFavoritePhrase/param_type.ts": "70ec007c1024e05b6ce3cf4c012b59dc88486f4c95ab1d6cc60dd7d6c093353d", - "Query/PetFavoritePhrase/parameters_type.ts": "cbe7bdbed463fb4c61ce6a581d1d7427b797dcf748dcb7570b527a5dae792730", - "Query/PetFavoritePhrase/query_text.ts": "33343ee6c21fe3c1769b492151a8df596d75364db8d9f968fc5fd476cf9ac6ef", - "Query/PetFavoritePhrase/raw_response_type.ts": "5dee7406723cae116651a5f41f71af2bdbe4a58230ae9e92765e6e28df81b44d", - "Query/PetFavoritePhrase/refetch_reader.ts": "0d1e34a3fdf7e6a54a1686d4f7eda08815b5761cfe46dc005df011b526f53b56", - "Query/PetFavoritePhrase/resolver_reader.ts": "07941d24865ba668ba14562f771805c815e664022b3d2e87b57ff3f4abbe717a", - "Query/SmartestPetRoute/__refetch__0.ts": "86d93d935a99937abc1886221caac398648c053bd8a26132550d2806ef418f8c", - "Query/SmartestPetRoute/__refetch__1.ts": "bfce3b652a313d3f8a7c0345f0495c60e38a0ac7ee33ae3364a7be116dd2d750", - "Query/SmartestPetRoute/__refetch__query_text__0.ts": "8bc135df6d9510b015df9b5d38edb15427d2b0463c18a5e64c5e4c01a6207f97", - "Query/SmartestPetRoute/__refetch__query_text__1.ts": "ee2e66ff3a1339f38293cdd206568fd79049de29317128c0535a8a1cd4eb2f12", - "Query/SmartestPetRoute/entrypoint.ts": "bf728b00ae08aa29f998e76559f9756d0e7aea550cc8778c6d60bc5ff98e1d46", - "Query/SmartestPetRoute/normalization_ast.ts": "5afa545a213a13454609fe90f55bd2ed01fbecd7c4901531a1b090543ab6e542", - "Query/SmartestPetRoute/output_type.ts": "905edb1c17e8c1c2de3b6eb5197b8040d4199c452f9218c8ade7187ecbc8ce98", - "Query/SmartestPetRoute/param_type.ts": "5199e724d3b1b4a7f944e0613e3e161e0fb340ba24bdc01b17e6964131ced970", - "Query/SmartestPetRoute/query_text.ts": "c488a754454f26aad189684c78ec2c43c29ea05011ba2106215a1329235064ce", - "Query/SmartestPetRoute/raw_response_type.ts": "303e73ec923fd05b2064d638748c26c99fb3b40a8a027e56abfbdd7594096b68", - "Query/SmartestPetRoute/resolver_reader.ts": "cdb19aaa1fddab34ecef9ec5d05c2d794419aa6df29a2efa4005af22e912819f", - "Query/smartestPet/output_type.ts": "a01f669fc4d7dc6f0c8eb9fec07f57693f326ac04d363bffaabbd503d50a2a4e", - "Query/smartestPet/param_type.ts": "e957ff94b397b0303d4d5f4640bf3921f1530ca1d836cade439318d41728cca8", - "Query/smartestPet/resolver_reader.ts": "2bdc79417c62dae88ab63b9f4cafe70e736cb85cf23c5e2acf9acb7fa8d3dcdb", - "Viewer/NewsfeedPaginationComponent/entrypoint.ts": "600c627538542eebe5250b222dc66ea60a872c16bd26b2971254dcce995dc71f", - "Viewer/NewsfeedPaginationComponent/normalization_ast.ts": "6eeabd652394048cec4daf773acf4845883185523fc9ee4a7a47f34aeb5129bd", - "Viewer/NewsfeedPaginationComponent/output_type.ts": "6f8862f2f5042a653502fc95164dfc4d704febfdce304067226541e615fa0c70", - "Viewer/NewsfeedPaginationComponent/param_type.ts": "fb94104e64810d62b829d5eadfa97f00779c3c35debc913654b486beb2787cba", - "Viewer/NewsfeedPaginationComponent/parameters_type.ts": "d8d97e679a62a1b2fbaed59d1cff4d8516e312cfaeecab4a95834c96be8ab10a", - "Viewer/NewsfeedPaginationComponent/query_text.ts": "83090c96e3d93a978c03f42f38c4b3eac2d66808e20df2c1f932bdf98c2bfc7d", - "Viewer/NewsfeedPaginationComponent/raw_response_type.ts": "273d38a56b94f688bf1e7665ab3fbd00cc1e82d0d21b2d4b849f142a313b59de", - "Viewer/NewsfeedPaginationComponent/refetch_reader.ts": "db8134b7e2729fc79233b814beec1b1c2dbe7de17bdbdaa0add2a87ceda348f4", - "Viewer/NewsfeedPaginationComponent/resolver_reader.ts": "c96347d8c7dcdc0231cf55d9491a420e9b8cc7158ade853984d8eae077867c5a", - "iso.ts": "6efa63bfa778f8ada7b0b6b291ea072cf7d0fa56b732c74589e5c301364d273a", - "tsconfig.json": "1e3eb33a239b72f6dff0dc4cf9a4f940b6067ad0f27ff896c163b06aff24a93a" -} \ No newline at end of file From 92f455c83fec13f844466648cd168b0d467ba0b6 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Mon, 24 Nov 2025 18:13:11 -0300 Subject: [PATCH 05/28] Write only changed files to disk in watch mode First of all, keep the file system state in the CompilerState and pass the state to the compile function. A new struct ChangedArtifacts keeps all the artifacts that will be added and deleted. the function get_artifacts_to_write returns the changed artifacts based on the new and old file system states. --- crates/isograph_compiler/src/batch_compile.rs | 25 ++- .../src/changed_artifacts.rs | 30 ++++ .../isograph_compiler/src/compiler_state.rs | 3 + crates/isograph_compiler/src/lib.rs | 1 + crates/isograph_compiler/src/watch.rs | 6 +- .../isograph_compiler/src/write_artifacts.rs | 153 +++++++++++------- 6 files changed, 154 insertions(+), 64 deletions(-) create mode 100644 crates/isograph_compiler/src/changed_artifacts.rs diff --git a/crates/isograph_compiler/src/batch_compile.rs b/crates/isograph_compiler/src/batch_compile.rs index 2a487308b..94e3fe3ab 100644 --- a/crates/isograph_compiler/src/batch_compile.rs +++ b/crates/isograph_compiler/src/batch_compile.rs @@ -4,14 +4,14 @@ use crate::{ SourceError, compiler_state::CompilerState, with_duration::WithDuration, - write_artifacts::{GenerateArtifactsError, write_artifacts_to_disk}, + write_artifacts::{GenerateArtifactsError, get_artifacts_to_write, write_artifacts_to_disk}, }; use artifact_content::{ generate_artifacts::GetArtifactPathAndContentError, get_artifact_path_and_content, }; use colored::Colorize; use common_lang_types::CurrentWorkingDirectory; -use isograph_schema::{IsographDatabase, NetworkProtocol}; +use isograph_schema::NetworkProtocol; use prelude::Postfix; use pretty_duration::pretty_duration; use thiserror::Error; @@ -29,8 +29,10 @@ pub fn compile_and_print( current_working_directory: CurrentWorkingDirectory, ) -> Result<(), BatchCompileError> { info!("{}", "Starting to compile.".cyan()); - let state = CompilerState::new(config_location, current_working_directory)?; - print_result(WithDuration::new(|| compile::(&state.db))) + let mut state = CompilerState::new(config_location, current_working_directory)?; + print_result(WithDuration::new(|| { + compile::(&mut state) + })) } pub fn print_result( @@ -78,18 +80,25 @@ fn print_stats(elapsed_time: Duration, stats: CompilationStats) { } /// This the "workhorse" command of batch compilation. -#[tracing::instrument(skip(db))] +#[tracing::instrument(skip(state))] pub fn compile( - db: &IsographDatabase, + state: &mut CompilerState, ) -> Result { // Note: we calculate all of the artifact paths and contents first, so that writing to // disk can be as fast as possible and we minimize the chance that changes to the file // system occur while we're writing and we get unpredictable results. - + let db = &state.db; let config = db.get_isograph_config(); let (artifacts, stats) = get_artifact_path_and_content(db)?; + + let artifacts_to_write = get_artifacts_to_write( + artifacts, + &config.artifact_directory.absolute_path, + &mut state.file_system_state, + ); + let total_artifacts_written = - write_artifacts_to_disk(artifacts, &config.artifact_directory.absolute_path)?; + write_artifacts_to_disk(artifacts_to_write, &config.artifact_directory.absolute_path)?; CompilationStats { client_field_count: stats.client_field_count, diff --git a/crates/isograph_compiler/src/changed_artifacts.rs b/crates/isograph_compiler/src/changed_artifacts.rs new file mode 100644 index 000000000..ed26dd983 --- /dev/null +++ b/crates/isograph_compiler/src/changed_artifacts.rs @@ -0,0 +1,30 @@ +use std::{collections::HashMap, path::PathBuf}; + +use common_lang_types::ArtifactPathAndContent; + +pub struct ChangedArtifacts { + pub committed_artifacts: HashMap, + pub artifacts_to_delete: Vec, + pub cleanup_artifact_directory: bool, +} + +impl ChangedArtifacts { + pub fn new() -> Self { + Self { + committed_artifacts: HashMap::new(), + artifacts_to_delete: Vec::new(), + cleanup_artifact_directory: true, + } + } + pub fn delete(&mut self, paths: Vec) { + for path in paths { + self.artifacts_to_delete.push(path.clone()); + } + } +} + +impl Default for ChangedArtifacts { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/isograph_compiler/src/compiler_state.rs b/crates/isograph_compiler/src/compiler_state.rs index d3ae89995..16226e114 100644 --- a/crates/isograph_compiler/src/compiler_state.rs +++ b/crates/isograph_compiler/src/compiler_state.rs @@ -10,6 +10,7 @@ use pico::Database; use prelude::Postfix; use crate::{batch_compile::BatchCompileError, source_files::initialize_sources}; +use artifact_content::FileSystemState; const GC_DURATION_SECONDS: u64 = 60; @@ -17,6 +18,7 @@ const GC_DURATION_SECONDS: u64 = 60; pub struct CompilerState { pub db: IsographDatabase, pub last_gc_run: Instant, + pub file_system_state: FileSystemState, } impl CompilerState { @@ -31,6 +33,7 @@ impl CompilerState { Self { db, last_gc_run: Instant::now(), + file_system_state: FileSystemState::new(), } .ok() } diff --git a/crates/isograph_compiler/src/lib.rs b/crates/isograph_compiler/src/lib.rs index 31517b9f8..b7f3ca110 100644 --- a/crates/isograph_compiler/src/lib.rs +++ b/crates/isograph_compiler/src/lib.rs @@ -1,4 +1,5 @@ pub mod batch_compile; +pub mod changed_artifacts; mod compiler_state; mod read_files; mod source_files; diff --git a/crates/isograph_compiler/src/watch.rs b/crates/isograph_compiler/src/watch.rs index dfef89838..8b18b58ae 100644 --- a/crates/isograph_compiler/src/watch.rs +++ b/crates/isograph_compiler/src/watch.rs @@ -30,7 +30,9 @@ pub async fn handle_watch_command( let config = state.db.get_isograph_config().clone(); info!("{}", "Starting to compile.".cyan()); - let _ = print_result(WithDuration::new(|| compile::(&state.db))); + let _ = print_result(WithDuration::new(|| { + compile::(&mut state) + })); let (mut file_system_receiver, mut file_system_watcher) = create_debounced_file_watcher(&config); @@ -52,7 +54,7 @@ pub async fn handle_watch_command( update_sources(&mut state.db, &changes)?; state.run_garbage_collection(); }; - let result = WithDuration::new(|| compile::(&state.db)); + let result = WithDuration::new(|| compile::(&mut state)); let _ = print_result(result); } Err(errors) => return Err(errors.into()), diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index 0989380b8..1304091c7 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -1,96 +1,134 @@ use std::{ + collections::HashMap, fs::{self, File}, io::Write, path::PathBuf, }; -use artifact_content::{ - ArtifactLookupCache, generate_artifacts::ARTIFACT_LOOKUP_CACHE_FILE_NAME, operation_text::hash, -}; use common_lang_types::ArtifactPathAndContent; use intern::string_key::Lookup; -use isograph_config::PersistedDocumentsHashAlgorithm; use thiserror::Error; -#[tracing::instrument(skip(paths_and_contents, artifact_directory))] -pub(crate) fn write_artifacts_to_disk( +use crate::changed_artifacts::ChangedArtifacts; +use artifact_content::FileSystemState; + +pub fn get_artifacts_to_write( paths_and_contents: impl IntoIterator, artifact_directory: &PathBuf, + file_system_state: &mut FileSystemState, +) -> ChangedArtifacts { + let mut new_file_system_state = FileSystemState::new(); + let mut artifact_map: HashMap = HashMap::new(); + + for path_and_content in paths_and_contents { + let absolute_directory = match path_and_content.type_and_field { + Some(type_and_field) => artifact_directory + .join(type_and_field.parent_object_entity_name.lookup()) + .join(type_and_field.selectable_name.lookup()), + None => artifact_directory.clone(), + }; + + let absolute_file_path = absolute_directory.join(path_and_content.file_name.lookup()); + + let relative_file_path = absolute_file_path + .strip_prefix(artifact_directory) + .expect("absolute paths should contain artifact_directory") + .to_string_lossy() + .to_string(); + + new_file_system_state.insert( + relative_file_path.clone(), + path_and_content.file_content.clone(), + ); + artifact_map.insert(relative_file_path, (absolute_file_path, path_and_content)); + } + + let mut artifacts_to_disk = ChangedArtifacts::new(); + + if file_system_state.is_empty() { + artifacts_to_disk.committed_artifacts = artifact_map + .into_iter() + .map(|(_, (path, content))| (path, content)) + .collect(); + artifacts_to_disk.cleanup_artifact_directory = true; + } else { + let (to_delete, to_add) = file_system_state.compare(&new_file_system_state); + + for relative_path in to_add.into_iter() { + if let Some((absolute_path, content)) = artifact_map.remove(&relative_path) { + artifacts_to_disk + .committed_artifacts + .insert(absolute_path, content); + } + } + + artifacts_to_disk.artifacts_to_delete = to_delete + .into_iter() + .map(|path| artifact_directory.join(path)) + .collect(); + artifacts_to_disk.cleanup_artifact_directory = false; + } + + *file_system_state = new_file_system_state; + + artifacts_to_disk +} + +#[tracing::instrument(skip(artifacts_to_disk, artifact_directory))] +pub(crate) fn write_artifacts_to_disk( + artifacts_to_disk: ChangedArtifacts, + artifact_directory: &PathBuf, ) -> Result { - if artifact_directory.exists() { + if artifact_directory.exists() && artifacts_to_disk.cleanup_artifact_directory { fs::remove_dir_all(artifact_directory).map_err(|e| { GenerateArtifactsError::UnableToDeleteDirectory { path: artifact_directory.clone(), message: e.to_string(), } })?; + + fs::create_dir_all(artifact_directory).map_err(|e| { + GenerateArtifactsError::UnableToCreateDirectory { + path: artifact_directory.clone(), + message: e.to_string(), + } + })?; } - fs::create_dir_all(artifact_directory).map_err(|e| { - GenerateArtifactsError::UnableToCreateDirectory { - path: artifact_directory.clone(), - message: e.to_string(), - } - })?; let mut count = 0; - let mut artifact_lookup_cache = ArtifactLookupCache::new(); - for path_and_content in paths_and_contents { - // Is this better than materializing paths_and_contents sooner? + for (path, content) in artifacts_to_disk.committed_artifacts.iter() { count += 1; - let absolute_directory = match path_and_content.type_and_field { - Some(type_and_field) => artifact_directory - .join(type_and_field.parent_object_entity_name.lookup()) - .join(type_and_field.selectable_name.lookup()), - None => artifact_directory.clone(), - }; + let absolute_directory = path.parent().expect("path must have a parent"); + fs::create_dir_all(&absolute_directory).map_err(|e| { GenerateArtifactsError::UnableToCreateDirectory { - path: absolute_directory.clone(), + path: absolute_directory.to_path_buf().clone(), message: e.to_string(), } })?; - let absolute_file_path = absolute_directory.join(path_and_content.file_name.lookup()); - let mut file = File::create(&absolute_file_path).map_err(|e| { + let mut file = File::create(&path).map_err(|e| { GenerateArtifactsError::UnableToWriteToArtifactFile { - path: absolute_file_path.clone(), + path: path.clone(), message: e.to_string(), } })?; - file.write(path_and_content.file_content.as_bytes()) - .map_err(|e| GenerateArtifactsError::UnableToWriteToArtifactFile { - path: absolute_file_path.clone(), + file.write(content.file_content.as_bytes()).map_err(|e| { + GenerateArtifactsError::UnableToWriteToArtifactFile { + path: path.clone(), message: e.to_string(), - })?; - - let artifact_hash = hash( - &path_and_content.file_content, - PersistedDocumentsHashAlgorithm::Sha256, - ); - let relative_file_path = absolute_file_path - .strip_prefix(artifact_directory) - .expect("absolute paths should contain artifact_directory") - .to_string_lossy() - .to_string(); - artifact_lookup_cache.insert(relative_file_path, artifact_hash); + } + })?; } - let absolute_file_path = artifact_directory.join(ARTIFACT_LOOKUP_CACHE_FILE_NAME.lookup()); - let file = File::create(&absolute_file_path).map_err(|e| { - GenerateArtifactsError::UnableToWriteToArtifactFile { - path: absolute_file_path.clone(), - message: e.to_string(), - } - })?; - - artifact_lookup_cache.write_to(file).map_err(|e| { - GenerateArtifactsError::UnableToWriteToArtifactFile { - path: absolute_file_path.clone(), + for path in artifacts_to_disk.artifacts_to_delete.iter() { + fs::remove_file(path).map_err(|e| GenerateArtifactsError::UnableToDeleteArtifactFile { + path: path.clone(), message: e.to_string(), - } - })?; + })?; + } Ok(count) } @@ -118,4 +156,11 @@ pub enum GenerateArtifactsError { \nReason: {message:?}" )] UnableToDeleteDirectory { path: PathBuf, message: String }, + + #[error( + "Unable to delete artifact file at path {path:?}. \ + Is there another instance of the Isograph compiler running?\ + \nReason: {message:?}" + )] + UnableToDeleteArtifactFile { path: PathBuf, message: String }, } From bc1a23ecb76429686909cfe2473c13292d84502b Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Mon, 24 Nov 2025 18:14:23 -0300 Subject: [PATCH 06/28] Remove serialization trait --- crates/artifact_content/src/file_system_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index 536968ce5..299822ce4 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -8,7 +8,7 @@ type Filepath = String; type FileContent = String; type FileHash = String; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] pub struct FileSystemState { state: HashMap, pub sorted_keys: BTreeSet, From 6d4efc26cc52babeb4485c9040ef6a3208127073 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Mon, 24 Nov 2025 18:14:58 -0300 Subject: [PATCH 07/28] Remove unused imports --- crates/artifact_content/src/file_system_state.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index 299822ce4..a40f54f48 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -1,4 +1,3 @@ -use serde::{Deserialize, Serialize}; use std::collections::{BTreeSet, HashMap}; use crate::operation_text::hash; From 18406a581a2a4ed3821561647ad014caf1ffe4b8 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Mon, 24 Nov 2025 18:18:57 -0300 Subject: [PATCH 08/28] Rename commited_artifacts to artifacts_to_write --- crates/isograph_compiler/src/changed_artifacts.rs | 4 ++-- crates/isograph_compiler/src/write_artifacts.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/isograph_compiler/src/changed_artifacts.rs b/crates/isograph_compiler/src/changed_artifacts.rs index ed26dd983..e7ebf98b5 100644 --- a/crates/isograph_compiler/src/changed_artifacts.rs +++ b/crates/isograph_compiler/src/changed_artifacts.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, path::PathBuf}; use common_lang_types::ArtifactPathAndContent; pub struct ChangedArtifacts { - pub committed_artifacts: HashMap, + pub artifacts_to_write: HashMap, pub artifacts_to_delete: Vec, pub cleanup_artifact_directory: bool, } @@ -11,7 +11,7 @@ pub struct ChangedArtifacts { impl ChangedArtifacts { pub fn new() -> Self { Self { - committed_artifacts: HashMap::new(), + artifacts_to_write: HashMap::new(), artifacts_to_delete: Vec::new(), cleanup_artifact_directory: true, } diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index 1304091c7..d54cc473a 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -46,7 +46,7 @@ pub fn get_artifacts_to_write( let mut artifacts_to_disk = ChangedArtifacts::new(); if file_system_state.is_empty() { - artifacts_to_disk.committed_artifacts = artifact_map + artifacts_to_disk.artifacts_to_write = artifact_map .into_iter() .map(|(_, (path, content))| (path, content)) .collect(); @@ -57,7 +57,7 @@ pub fn get_artifacts_to_write( for relative_path in to_add.into_iter() { if let Some((absolute_path, content)) = artifact_map.remove(&relative_path) { artifacts_to_disk - .committed_artifacts + .artifacts_to_write .insert(absolute_path, content); } } @@ -96,7 +96,7 @@ pub(crate) fn write_artifacts_to_disk( } let mut count = 0; - for (path, content) in artifacts_to_disk.committed_artifacts.iter() { + for (path, content) in artifacts_to_disk.artifacts_to_write.iter() { count += 1; let absolute_directory = path.parent().expect("path must have a parent"); From 0d10bfff65048dc295f5fbb7920503701d96b3fb Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Mon, 24 Nov 2025 18:48:26 -0300 Subject: [PATCH 09/28] fix from merge conflict --- crates/isograph_compiler/src/batch_compile.rs | 9 +++++---- .../isograph_compiler/src/write_artifacts.rs | 20 +++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/crates/isograph_compiler/src/batch_compile.rs b/crates/isograph_compiler/src/batch_compile.rs index d2a84cfc6..5eadcef3f 100644 --- a/crates/isograph_compiler/src/batch_compile.rs +++ b/crates/isograph_compiler/src/batch_compile.rs @@ -1,7 +1,8 @@ use std::{path::PathBuf, time::Duration}; use crate::{ - compiler_state::CompilerState, with_duration::WithDuration, + compiler_state::CompilerState, + with_duration::WithDuration, write_artifacts::{get_artifacts_to_write, write_artifacts_to_disk}, }; use artifact_content::get_artifact_path_and_content; @@ -25,10 +26,11 @@ pub fn compile_and_print( ) -> DiagnosticVecResult<()> { info!("{}", "Starting to compile.".cyan()); let mut state = CompilerState::new(config_location, current_working_directory)?; - print_result(WithDuration::new(|| compile::(&mut state))) + print_result(WithDuration::new(|| { + compile::(&mut state) + })) } - pub fn print_result( result: WithDuration>, ) -> DiagnosticVecResult<()> { @@ -106,4 +108,3 @@ pub fn compile( } .wrap_ok() } - diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index 4b2c8244a..057ce7154 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -77,7 +77,7 @@ pub fn get_artifacts_to_write( pub(crate) fn write_artifacts_to_disk( artifacts_to_disk: ChangedArtifacts, artifact_directory: &PathBuf, -) -> DiagnosticResult { +) -> DiagnosticResult { if artifact_directory.exists() && artifacts_to_disk.cleanup_artifact_directory { fs::remove_dir_all(artifact_directory).map_err(|e| { unable_to_do_something_at_path_diagnostic( @@ -89,7 +89,11 @@ pub(crate) fn write_artifacts_to_disk( fs::create_dir_all(artifact_directory).map_err(|e| { let message = e.to_string(); - unable_to_do_something_at_path_diagnostic(artifact_directory, &message, "create directory") + unable_to_do_something_at_path_diagnostic( + artifact_directory, + &message, + "create directory", + ) })?; } @@ -125,13 +129,9 @@ pub(crate) fn write_artifacts_to_disk( } for path in artifacts_to_disk.artifacts_to_delete.iter() { - fs::remove_file(path).map_err(|e| - unable_to_do_something_at_path_diagnostic( - path, - &e.to_string(), - "delete file", - ) - )?; + fs::remove_file(path).map_err(|e| { + unable_to_do_something_at_path_diagnostic(path, &e.to_string(), "delete file") + })?; } Ok(count) @@ -149,4 +149,4 @@ pub fn unable_to_do_something_at_path_diagnostic( ), None, ) -} \ No newline at end of file +} From 58eea9a9f6ea021ca2c3776cb74c413a2488335a Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Tue, 25 Nov 2025 01:28:18 -0300 Subject: [PATCH 10/28] Add tracing to get_artifacts_to_write --- crates/isograph_compiler/src/write_artifacts.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index 057ce7154..4d8fc6fa4 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -11,6 +11,7 @@ use intern::string_key::Lookup; use crate::changed_artifacts::ChangedArtifacts; use artifact_content::FileSystemState; +#[tracing::instrument(skip(paths_and_contents, artifact_directory, file_system_state))] pub fn get_artifacts_to_write( paths_and_contents: impl IntoIterator, artifact_directory: &PathBuf, From 58ab26b14061c29a2e13eae1abdc7e7d716290f7 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Tue, 25 Nov 2025 01:33:00 -0300 Subject: [PATCH 11/28] fix merge conflict --- crates/isograph_compiler/src/write_artifacts.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index d3e330ec3..b60a25d7e 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -28,7 +28,8 @@ pub fn get_artifacts_to_write( None => artifact_directory.clone(), }; - let absolute_file_path = absolute_directory.join(path_and_content.artifact_path.file_name.lookup()); + let absolute_file_path = + absolute_directory.join(path_and_content.artifact_path.file_name.lookup()); let relative_file_path = absolute_file_path .strip_prefix(artifact_directory) From 7bfef3368a05e431a497fb296e841eea77d05097 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Tue, 25 Nov 2025 16:28:40 -0300 Subject: [PATCH 12/28] Replace ChangedFactory with a vector a FileSystemOperation --- .../artifact_content/src/file_system_state.rs | 232 ++++++++++++++---- .../src/file_system_operation.rs | 11 + crates/common_lang_types/src/lib.rs | 2 + .../common_lang_types/src/path_and_content.rs | 30 +++ crates/isograph_compiler/src/batch_compile.rs | 7 +- .../src/changed_artifacts.rs | 30 --- .../isograph_compiler/src/compiler_state.rs | 2 +- crates/isograph_compiler/src/lib.rs | 1 - .../isograph_compiler/src/write_artifacts.rs | 172 ++++--------- 9 files changed, 282 insertions(+), 205 deletions(-) create mode 100644 crates/common_lang_types/src/file_system_operation.rs delete mode 100644 crates/isograph_compiler/src/changed_artifacts.rs diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index a40f54f48..e0a37d5b9 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -1,71 +1,207 @@ -use std::collections::{BTreeSet, HashMap}; +use std::{ + collections::{HashMap, HashSet}, + path::PathBuf, +}; use crate::operation_text::hash; +use common_lang_types::{ + ArtifactFileName, ArtifactHash, ArtifactPathAndContent, FileContent, FileSystemOperation, + SelectableName, ServerObjectEntityName, +}; use isograph_config::PersistedDocumentsHashAlgorithm; -type Filepath = String; -type FileContent = String; -type FileHash = String; - -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct FileSystemState { - state: HashMap, - pub sorted_keys: BTreeSet, + root_files: HashMap, + nested_files: HashMap< + ServerObjectEntityName, + HashMap>, + >, } impl FileSystemState { - pub fn new() -> Self { - Self { - state: HashMap::new(), - sorted_keys: BTreeSet::new(), - } - } + pub fn insert(&mut self, path_and_content: &ArtifactPathAndContent) { + let value = ( + FileContent::from(path_and_content.file_content.clone()), + ArtifactHash::from(hash( + &path_and_content.file_content, + PersistedDocumentsHashAlgorithm::Md5, + )), + ); - pub fn insert(&mut self, filename: Filepath, content: FileContent) { - let hashed_content = hash(&content, PersistedDocumentsHashAlgorithm::Sha256); - self.sorted_keys.insert(filename.clone()); - self.state.insert(filename, (content, hashed_content)); + match &path_and_content.artifact_path.type_and_field { + Some(type_and_field) => { + self.nested_files + .entry(type_and_field.parent_object_entity_name) + .or_default() + .entry(type_and_field.selectable_name) + .or_default() + .insert(path_and_content.artifact_path.file_name, value); + } + None => { + self.root_files + .insert(path_and_content.artifact_path.file_name, value); + } + } } - pub fn get_hashed_content(&self, filename: &str) -> Option<&FileHash> { - self.state - .get(filename) - .map(|(_, hashed_content)| hashed_content) + pub fn from_artifacts(artifacts: Vec) -> Self { + let mut state = Self::default(); + for artifact in artifacts { + state.insert(&artifact); + } + state } pub fn is_empty(&self) -> bool { - self.state.is_empty() + self.root_files.is_empty() && self.nested_files.is_empty() } - fn difference(&self, other: &FileSystemState) -> Vec { - self.sorted_keys - .difference(&other.sorted_keys) - .map(|k| k.clone()) - .collect() - } + // Computes filesystem operations needed to transform this state into the target state. + // Returns operations to create, update, and delete files and directories. Files are updated + // only when their hash differs. If the current state is empty, emits a DeleteDirectory + // operation for the artifact root followed by recreation of all files. Empty directories + // are automatically removed after their files are deleted. + pub fn diff(&self, new: &Self, artifact_directory: &PathBuf) -> Vec { + let mut operations: Vec = Vec::new(); - fn intersection(&self, other: &FileSystemState) -> Vec { - self.sorted_keys - .intersection(&other.sorted_keys) - .map(|k| k.clone()) - .collect() - } + if self.is_empty() { + operations.push(FileSystemOperation::DeleteDirectory( + artifact_directory.to_path_buf(), + )); + + for (server_object, selectables) in &new.nested_files { + let server_object_path = artifact_directory.join(server_object.to_string()); + operations.push(FileSystemOperation::CreateDirectory( + server_object_path.clone(), + )); + + for (selectable, files) in selectables { + let selectable_path = server_object_path.join(selectable.to_string()); + operations.push(FileSystemOperation::CreateDirectory( + selectable_path.clone(), + )); - pub fn compare(&self, other: &FileSystemState) -> (Vec, Vec) { - let to_delete = self.difference(other); - let mut to_add = other.difference(self); - let candidate_to_update = self.intersection(other); - for key in candidate_to_update { - if self.get_hashed_content(&key).unwrap() != other.get_hashed_content(&key).unwrap() { - to_add.push(key); + for (file_name, (content, _)) in files { + let file_path = selectable_path.join(file_name.to_string()); + operations.push(FileSystemOperation::WriteFile(file_path, content.clone())); + } + } + + for (file_name, (content, _)) in &new.root_files { + let file_path = artifact_directory.join(file_name.to_string()); + operations.push(FileSystemOperation::WriteFile(file_path, content.clone())); + } + } + + return operations; + } + + let mut new_server_objects: HashSet<&ServerObjectEntityName> = HashSet::new(); + let mut new_selectables: HashSet<(&ServerObjectEntityName, &SelectableName)> = + HashSet::new(); + + for (server_object, selectables) in &new.nested_files { + new_server_objects.insert(server_object); + let server_object_path = artifact_directory.join(server_object.to_string()); + + if !self.nested_files.contains_key(server_object) { + operations.push(FileSystemOperation::CreateDirectory( + server_object_path.clone(), + )); + } + + for (selectable, files) in selectables { + new_selectables.insert((server_object, selectable)); + let selectable_path = server_object_path.join(selectable.to_string()); + + let should_create_dir = self + .nested_files + .get(server_object) + .and_then(|s| s.get(selectable)) + .is_none(); + + if should_create_dir { + operations.push(FileSystemOperation::CreateDirectory( + selectable_path.clone(), + )); + } + + for (file_name, (new_content, new_hash)) in files { + let file_path = selectable_path.join(file_name.to_string()); + + let should_write = self + .nested_files + .get(server_object) + .and_then(|s| s.get(selectable)) + .and_then(|f| f.get(file_name)) + .map(|(_, old_hash)| old_hash != new_hash) + .unwrap_or(true); + + if should_write { + operations.push(FileSystemOperation::WriteFile( + file_path, + new_content.clone(), + )); + } + } + } + } + + for (file_name, (new_content, new_hash)) in &new.root_files { + let file_path = artifact_directory.join(file_name.to_string()); + + let should_write = self + .root_files + .get(file_name) + .map(|(_old_content, old_hash)| old_hash != new_hash) + .unwrap_or(true); + + if should_write { + operations.push(FileSystemOperation::WriteFile( + file_path, + new_content.clone(), + )); + } + } + + for (server_object, selectables) in &self.nested_files { + let server_object_path = artifact_directory.join(server_object.to_string()); + + for (selectable, files) in selectables { + let selectable_path = server_object_path.join(selectable.to_string()); + + for file_name in files.keys() { + let exist_in_new = new + .nested_files + .get(server_object) + .and_then(|s| s.get(selectable)) + .and_then(|f| f.get(file_name)) + .is_some(); + + if !exist_in_new { + let file_path = selectable_path.join(file_name.to_string()); + operations.push(FileSystemOperation::DeleteFile(file_path)); + } + } + + if !new_selectables.contains(&(server_object, selectable)) { + operations.push(FileSystemOperation::DeleteDirectory(selectable_path)); + } + } + + if !new_server_objects.contains(server_object) { + operations.push(FileSystemOperation::DeleteDirectory(server_object_path)); + } + } + + for file_name in self.root_files.keys() { + if !new.root_files.contains_key(file_name) { + let file_path = artifact_directory.join(file_name.to_string()); + operations.push(FileSystemOperation::DeleteFile(file_path)); } } - (to_delete, to_add) - } -} -impl Default for FileSystemState { - fn default() -> Self { - Self::new() + return operations; } } diff --git a/crates/common_lang_types/src/file_system_operation.rs b/crates/common_lang_types/src/file_system_operation.rs new file mode 100644 index 000000000..c07891c5c --- /dev/null +++ b/crates/common_lang_types/src/file_system_operation.rs @@ -0,0 +1,11 @@ +use std::path::PathBuf; + +use crate::FileContent; + +#[derive(Debug, Clone)] +pub enum FileSystemOperation { + DeleteDirectory(PathBuf), + CreateDirectory(PathBuf), + WriteFile(PathBuf, FileContent), + DeleteFile(PathBuf), +} diff --git a/crates/common_lang_types/src/lib.rs b/crates/common_lang_types/src/lib.rs index 02e801148..789a4d8da 100644 --- a/crates/common_lang_types/src/lib.rs +++ b/crates/common_lang_types/src/lib.rs @@ -1,6 +1,7 @@ mod absolute_and_relative_path; mod diagnostic; mod entity_and_selectable_name; +mod file_system_operation; mod location; mod path_and_content; mod selectable_name; @@ -12,6 +13,7 @@ mod text_with_carats; pub use absolute_and_relative_path::*; pub use diagnostic::*; pub use entity_and_selectable_name::*; +pub use file_system_operation::*; pub use location::*; pub use path_and_content::*; pub use selectable_name::*; diff --git a/crates/common_lang_types/src/path_and_content.rs b/crates/common_lang_types/src/path_and_content.rs index 1d7cb813d..26d41edd6 100644 --- a/crates/common_lang_types/src/path_and_content.rs +++ b/crates/common_lang_types/src/path_and_content.rs @@ -1,5 +1,6 @@ use crate::{ArtifactFileName, ParentObjectEntityNameAndSelectableName}; +#[derive(Debug, Clone)] pub struct FileContent(pub String); impl From for FileContent { @@ -31,3 +32,32 @@ pub struct ArtifactPath { pub type_and_field: Option, pub file_name: ArtifactFileName, } + +#[derive(Debug, Clone)] +pub struct ArtifactHash(String); + +impl From for ArtifactHash { + fn from(value: String) -> Self { + ArtifactHash(value) + } +} + +impl std::fmt::Display for ArtifactHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::ops::Deref for ArtifactHash { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PartialEq for ArtifactHash { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} diff --git a/crates/isograph_compiler/src/batch_compile.rs b/crates/isograph_compiler/src/batch_compile.rs index 5eadcef3f..b0a97f996 100644 --- a/crates/isograph_compiler/src/batch_compile.rs +++ b/crates/isograph_compiler/src/batch_compile.rs @@ -3,7 +3,7 @@ use std::{path::PathBuf, time::Duration}; use crate::{ compiler_state::CompilerState, with_duration::WithDuration, - write_artifacts::{get_artifacts_to_write, write_artifacts_to_disk}, + write_artifacts::{apply_file_system_operations, get_file_system_operations}, }; use artifact_content::get_artifact_path_and_content; use colored::Colorize; @@ -91,14 +91,13 @@ pub fn compile( let config = db.get_isograph_config(); let (artifacts, stats) = get_artifact_path_and_content(db)?; - let artifacts_to_write = get_artifacts_to_write( + let file_system_operations = get_file_system_operations( artifacts, &config.artifact_directory.absolute_path, &mut state.file_system_state, ); - let total_artifacts_written = - write_artifacts_to_disk(artifacts_to_write, &config.artifact_directory.absolute_path)?; + let total_artifacts_written = apply_file_system_operations(file_system_operations)?; CompilationStats { client_field_count: stats.client_field_count, diff --git a/crates/isograph_compiler/src/changed_artifacts.rs b/crates/isograph_compiler/src/changed_artifacts.rs deleted file mode 100644 index e7ebf98b5..000000000 --- a/crates/isograph_compiler/src/changed_artifacts.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::{collections::HashMap, path::PathBuf}; - -use common_lang_types::ArtifactPathAndContent; - -pub struct ChangedArtifacts { - pub artifacts_to_write: HashMap, - pub artifacts_to_delete: Vec, - pub cleanup_artifact_directory: bool, -} - -impl ChangedArtifacts { - pub fn new() -> Self { - Self { - artifacts_to_write: HashMap::new(), - artifacts_to_delete: Vec::new(), - cleanup_artifact_directory: true, - } - } - pub fn delete(&mut self, paths: Vec) { - for path in paths { - self.artifacts_to_delete.push(path.clone()); - } - } -} - -impl Default for ChangedArtifacts { - fn default() -> Self { - Self::new() - } -} diff --git a/crates/isograph_compiler/src/compiler_state.rs b/crates/isograph_compiler/src/compiler_state.rs index 7cdd05b2b..170092e9b 100644 --- a/crates/isograph_compiler/src/compiler_state.rs +++ b/crates/isograph_compiler/src/compiler_state.rs @@ -33,7 +33,7 @@ impl CompilerState { Self { db, last_gc_run: Instant::now(), - file_system_state: FileSystemState::new(), + file_system_state: FileSystemState::default(), } .wrap_ok() } diff --git a/crates/isograph_compiler/src/lib.rs b/crates/isograph_compiler/src/lib.rs index b7f3ca110..31517b9f8 100644 --- a/crates/isograph_compiler/src/lib.rs +++ b/crates/isograph_compiler/src/lib.rs @@ -1,5 +1,4 @@ pub mod batch_compile; -pub mod changed_artifacts; mod compiler_state; mod read_files; mod source_files; diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index b60a25d7e..f89e5dcf7 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -1,139 +1,69 @@ -use std::{ - collections::HashMap, - fs::{self, File}, - io::Write, - path::PathBuf, -}; +use std::{fs, path::PathBuf}; -use common_lang_types::{ArtifactPathAndContent, Diagnostic, DiagnosticResult}; -use intern::string_key::Lookup; +use common_lang_types::{ + ArtifactPathAndContent, Diagnostic, DiagnosticResult, FileSystemOperation, +}; -use crate::changed_artifacts::ChangedArtifacts; use artifact_content::FileSystemState; #[tracing::instrument(skip(paths_and_contents, artifact_directory, file_system_state))] -pub fn get_artifacts_to_write( +pub fn get_file_system_operations( paths_and_contents: impl IntoIterator, artifact_directory: &PathBuf, file_system_state: &mut FileSystemState, -) -> ChangedArtifacts { - let mut new_file_system_state = FileSystemState::new(); - let mut artifact_map: HashMap = HashMap::new(); - - for path_and_content in paths_and_contents { - let absolute_directory = match path_and_content.artifact_path.type_and_field { - Some(type_and_field) => artifact_directory - .join(type_and_field.parent_object_entity_name.lookup()) - .join(type_and_field.selectable_name.lookup()), - None => artifact_directory.clone(), - }; - - let absolute_file_path = - absolute_directory.join(path_and_content.artifact_path.file_name.lookup()); - - let relative_file_path = absolute_file_path - .strip_prefix(artifact_directory) - .expect("absolute paths should contain artifact_directory") - .to_string_lossy() - .to_string(); - - new_file_system_state.insert( - relative_file_path.clone(), - path_and_content.file_content.clone(), - ); - artifact_map.insert(relative_file_path, (absolute_file_path, path_and_content)); - } - - let mut artifacts_to_disk = ChangedArtifacts::new(); - - if file_system_state.is_empty() { - artifacts_to_disk.artifacts_to_write = artifact_map - .into_iter() - .map(|(_, (path, content))| (path, content)) - .collect(); - artifacts_to_disk.cleanup_artifact_directory = true; - } else { - let (to_delete, to_add) = file_system_state.compare(&new_file_system_state); - - for relative_path in to_add.into_iter() { - if let Some((absolute_path, content)) = artifact_map.remove(&relative_path) { - artifacts_to_disk - .artifacts_to_write - .insert(absolute_path, content); - } - } - - artifacts_to_disk.artifacts_to_delete = to_delete - .into_iter() - .map(|path| artifact_directory.join(path)) - .collect(); - artifacts_to_disk.cleanup_artifact_directory = false; - } - +) -> Vec { + let artifacts: Vec = paths_and_contents.into_iter().collect(); + let new_file_system_state = FileSystemState::from_artifacts(artifacts); + let operations = file_system_state.diff(&new_file_system_state, artifact_directory); *file_system_state = new_file_system_state; - - artifacts_to_disk + operations } -#[tracing::instrument(skip(artifacts_to_disk, artifact_directory))] -pub(crate) fn write_artifacts_to_disk( - artifacts_to_disk: ChangedArtifacts, - artifact_directory: &PathBuf, +#[tracing::instrument(skip(operations))] +pub(crate) fn apply_file_system_operations( + operations: Vec, ) -> DiagnosticResult { - if artifact_directory.exists() && artifacts_to_disk.cleanup_artifact_directory { - fs::remove_dir_all(artifact_directory).map_err(|e| { - unable_to_do_something_at_path_diagnostic( - artifact_directory, - &e.to_string(), - "delete directory", - ) - })?; - - fs::create_dir_all(artifact_directory).map_err(|e| { - let message = e.to_string(); - unable_to_do_something_at_path_diagnostic( - artifact_directory, - &message, - "create directory", - ) - })?; - } - let mut count = 0; - for (path, content) in artifacts_to_disk.artifacts_to_write.iter() { - count += 1; - - let absolute_directory = path.parent().expect("path must have a parent"); - - fs::create_dir_all(&absolute_directory).map_err(|e| { - unable_to_do_something_at_path_diagnostic( - &absolute_directory.to_path_buf(), - &e.to_string(), - "create directory", - ) - })?; - - let mut file = File::create(&path).map_err(|e| { - unable_to_do_something_at_path_diagnostic( - &absolute_directory.to_path_buf(), - &e.to_string(), - "create directory", - ) - })?; - file.write(content.file_content.as_bytes()).map_err(|e| { - unable_to_do_something_at_path_diagnostic( - &path, - &e.to_string(), - "write contents of file", - ) - })?; - } + for operation in operations { + count += 1; - for path in artifacts_to_disk.artifacts_to_delete.iter() { - fs::remove_file(path).map_err(|e| { - unable_to_do_something_at_path_diagnostic(path, &e.to_string(), "delete file") - })?; + match operation { + FileSystemOperation::DeleteDirectory(path) => { + if path.exists() { + fs::remove_dir_all(path.clone()).map_err(|e| { + unable_to_do_something_at_path_diagnostic( + &path, + &e.to_string(), + "delete directory", + ) + })?; + } + } + FileSystemOperation::CreateDirectory(path) => { + fs::create_dir_all(path.clone()).map_err(|e| { + unable_to_do_something_at_path_diagnostic( + &path, + &e.to_string(), + "create directory", + ) + })?; + } + FileSystemOperation::WriteFile(path, content) => { + fs::write(path.clone(), content.as_bytes()).map_err(|e| { + unable_to_do_something_at_path_diagnostic( + &path, + &e.to_string(), + "write contents of file", + ) + })?; + } + FileSystemOperation::DeleteFile(path) => { + fs::remove_file(path.clone()).map_err(|e| { + unable_to_do_something_at_path_diagnostic(&path, &e.to_string(), "delete file") + })?; + } + } } Ok(count) From cf8a10146fe4c82e9f4d3aea13859270b9a51e00 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Tue, 25 Nov 2025 16:32:11 -0300 Subject: [PATCH 13/28] tracing->skip_all --- crates/isograph_compiler/src/write_artifacts.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index 2f089a599..4b668298b 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -7,7 +7,7 @@ use common_lang_types::{ use artifact_content::FileSystemState; #[tracing::instrument(skip_all)] -pub(crate) fn write_artifacts_to_disk( +pub(crate) fn get_file_system_operations( paths_and_contents: impl IntoIterator, artifact_directory: &PathBuf, file_system_state: &mut FileSystemState, @@ -19,7 +19,7 @@ pub(crate) fn write_artifacts_to_disk( operations } -#[tracing::instrument(skip(operations))] +#[tracing::instrument(skip_all)] pub(crate) fn apply_file_system_operations( operations: Vec, ) -> DiagnosticResult { From a8ba635c9ee82609804a67667d24bd826630ebd2 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Tue, 25 Nov 2025 18:19:13 -0300 Subject: [PATCH 14/28] FileSystemState unit tests --- .../artifact_content/src/file_system_state.rs | 323 ++++++++++++++++++ 1 file changed, 323 insertions(+) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index e0a37d5b9..daf03a960 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -205,3 +205,326 @@ impl FileSystemState { return operations; } } + +#[cfg(test)] +mod tests { + use super::*; + use common_lang_types::{ + ArtifactPath, ParentObjectEntityNameAndSelectableName, SelectableName, + ServerObjectEntityName, + }; + use intern::string_key::Intern; + use std::path::PathBuf; + + fn create_artifact( + server: Option<&str>, + selectable: Option<&str>, + file_name: &str, + content: &str, + ) -> ArtifactPathAndContent { + let type_and_field = match (server, selectable) { + (Some(s), Some(sel)) => Some(ParentObjectEntityNameAndSelectableName { + parent_object_entity_name: ServerObjectEntityName::from(s.intern()), + selectable_name: SelectableName::from(sel.intern()), + }), + _ => None, + }; + + ArtifactPathAndContent { + artifact_path: ArtifactPath { + type_and_field, + file_name: ArtifactFileName::from(file_name.intern()), + }, + file_content: FileContent::from(content.to_string()), + } + } + + #[test] + fn test_empty_state() { + let state = FileSystemState::default(); + assert!(state.is_empty()); + } + + #[test] + fn test_insert_root_file() { + let mut state = FileSystemState::default(); + let artifact = create_artifact(None, None, "package.json", "{}"); + + state.insert(&artifact); + + assert!(!state.is_empty()); + assert_eq!(state.root_files.len(), 1); + assert!( + state + .root_files + .contains_key(&ArtifactFileName::from("package.json".intern())) + ); + } + + #[test] + fn test_insert_nested_file() { + let mut state = FileSystemState::default(); + let artifact = create_artifact( + Some("User"), + Some("name"), + "query.graphql", + "query { user { name } }", + ); + + state.insert(&artifact); + + assert!(!state.is_empty()); + assert_eq!(state.nested_files.len(), 1); + + let server = &ServerObjectEntityName::from("User".intern()); + let selectable = &SelectableName::from("name".intern()); + let file_name = &ArtifactFileName::from("query.graphql".intern()); + + assert!( + state + .nested_files + .get(server) + .and_then(|s| s.get(selectable)) + .and_then(|f| f.get(file_name)) + .is_some() + ); + } + + #[test] + fn test_from_artifacts() { + let artifacts = vec![ + create_artifact(None, None, "schema.graphql", "type Query"), + create_artifact(Some("User"), Some("name"), "query.graphql", "query {}"), + create_artifact( + Some("User"), + Some("email"), + "mutation.graphql", + "mutation {}", + ), + ]; + + let state = FileSystemState::from_artifacts(artifacts); + + assert_eq!(state.root_files.len(), 1); + assert_eq!(state.nested_files.len(), 1); + + let user_server = &ServerObjectEntityName::from("User".intern()); + let selectables = state.nested_files.get(user_server).unwrap(); + assert_eq!(selectables.len(), 2); + } + + #[test] + fn test_diff_empty_to_new() { + let old_state = FileSystemState::default(); + let mut new_state = FileSystemState::default(); + + new_state.insert(&create_artifact(None, None, "root.txt", "content")); + new_state.insert(&create_artifact( + Some("User"), + Some("name"), + "query.graphql", + "query", + )); + + let artifact_dir = PathBuf::from("/artifacts"); + let ops = old_state.diff(&new_state, &artifact_dir); + + assert!(matches!(ops[0], FileSystemOperation::DeleteDirectory(_))); + + let create_dirs = ops + .iter() + .filter(|op| matches!(op, FileSystemOperation::CreateDirectory(_))) + .count(); + let write_files = ops + .iter() + .filter(|op| matches!(op, FileSystemOperation::WriteFile(_, _))) + .count(); + + assert_eq!(create_dirs, 2); // User dir and name dir + assert_eq!(write_files, 2); // root.txt and query.graphql + } + + #[test] + fn test_diff_no_changes() { + let artifact1 = create_artifact(None, None, "file.txt", "content"); + let artifact2 = create_artifact(None, None, "file.txt", "content"); + + let old_state = FileSystemState::from_artifacts(vec![artifact1]); + let new_state = FileSystemState::from_artifacts(vec![artifact2]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = old_state.diff(&new_state, &artifact_dir); + + assert_eq!(ops.len(), 0); + } + + #[test] + fn test_diff_file_content_changed() { + let old_artifact = create_artifact(None, None, "file.txt", "old content"); + let new_artifact = create_artifact(None, None, "file.txt", "new content"); + + let old_state = FileSystemState::from_artifacts(vec![old_artifact]); + let new_state = FileSystemState::from_artifacts(vec![new_artifact]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = old_state.diff(&new_state, &artifact_dir); + + assert_eq!(ops.len(), 1); + assert!(matches!(ops[0], FileSystemOperation::WriteFile(_, _))); + } + + #[test] + fn test_diff_add_new_file() { + let old_state = FileSystemState::from_artifacts(vec![create_artifact( + None, + None, + "existing.txt", + "content", + )]); + + let new_state = FileSystemState::from_artifacts(vec![ + create_artifact(None, None, "existing.txt", "content"), + create_artifact(None, None, "new.txt", "new content"), + ]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = old_state.diff(&new_state, &artifact_dir); + + assert_eq!(ops.len(), 1); + if let FileSystemOperation::WriteFile(path, _) = &ops[0] { + assert!(path.ends_with("new.txt")); + } else { + panic!("Expected WriteFile operation"); + } + } + + #[test] + fn test_diff_delete_file() { + let old_state = FileSystemState::from_artifacts(vec![ + create_artifact(None, None, "keep.txt", "content"), + create_artifact(None, None, "delete.txt", "content"), + ]); + + let new_state = FileSystemState::from_artifacts(vec![create_artifact( + None, None, "keep.txt", "content", + )]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = old_state.diff(&new_state, &artifact_dir); + + assert_eq!(ops.len(), 1); + if let FileSystemOperation::DeleteFile(path) = &ops[0] { + assert!(path.ends_with("delete.txt")); + } else { + panic!("Expected DeleteFile operation"); + } + } + + #[test] + fn test_diff_delete_empty_directory() { + let old_state = FileSystemState::from_artifacts(vec![create_artifact( + Some("User"), + Some("name"), + "query.graphql", + "query", + )]); + + let new_state = FileSystemState::default(); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = old_state.diff(&new_state, &artifact_dir); + + assert_eq!(ops.len(), 3); + assert!(matches!(ops[0], FileSystemOperation::DeleteFile(_))); + assert!(matches!(ops[1], FileSystemOperation::DeleteDirectory(_))); + assert!(matches!(ops[2], FileSystemOperation::DeleteDirectory(_))); + } + + #[test] + fn test_diff_nested_file_changes() { + let old_state = FileSystemState::from_artifacts(vec![create_artifact( + Some("User"), + Some("name"), + "query.graphql", + "old query", + )]); + + let new_state = FileSystemState::from_artifacts(vec![create_artifact( + Some("User"), + Some("name"), + "query.graphql", + "new query", + )]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = old_state.diff(&new_state, &artifact_dir); + + assert_eq!(ops.len(), 1); + assert!(matches!(ops[0], FileSystemOperation::WriteFile(_, _))); + } + + #[test] + fn test_diff_add_new_selectable() { + let old_state = FileSystemState::from_artifacts(vec![create_artifact( + Some("User"), + Some("name"), + "query.graphql", + "query", + )]); + + let new_state = FileSystemState::from_artifacts(vec![ + create_artifact(Some("User"), Some("name"), "query.graphql", "query"), + create_artifact(Some("User"), Some("email"), "query.graphql", "query"), + ]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = old_state.diff(&new_state, &artifact_dir); + + assert_eq!(ops.len(), 2); + assert!(matches!(ops[0], FileSystemOperation::CreateDirectory(_))); + assert!(matches!(ops[1], FileSystemOperation::WriteFile(_, _))); + } + + #[test] + fn test_diff_complex_scenario() { + let old_state = FileSystemState::from_artifacts(vec![ + create_artifact(None, None, "root.txt", "old root"), + create_artifact(Some("User"), Some("name"), "query.graphql", "old query"), + create_artifact(Some("User"), Some("email"), "query.graphql", "delete me"), + create_artifact(Some("Post"), Some("title"), "query.graphql", "post query"), + ]); + + let new_state = FileSystemState::from_artifacts(vec![ + create_artifact(None, None, "root.txt", "new root"), // changed + create_artifact(None, None, "new_root.txt", "new file"), // added + create_artifact(Some("User"), Some("name"), "query.graphql", "new query"), // changed + create_artifact(Some("Post"), Some("title"), "query.graphql", "post query"), // unchanged + create_artifact(Some("Comment"), Some("text"), "query.graphql", "comment"), // new server + ]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = old_state.diff(&new_state, &artifact_dir); + + let writes = ops + .iter() + .filter(|op| matches!(op, FileSystemOperation::WriteFile(_, _))) + .count(); + let deletes = ops + .iter() + .filter(|op| matches!(op, FileSystemOperation::DeleteFile(_))) + .count(); + let create_dirs = ops + .iter() + .filter(|op| matches!(op, FileSystemOperation::CreateDirectory(_))) + .count(); + let delete_dirs = ops + .iter() + .filter(|op| matches!(op, FileSystemOperation::DeleteDirectory(_))) + .count(); + + assert!(writes >= 3); // root.txt, new_root.txt, User/name/query.graphql, Comment/text/query.graphql + assert_eq!(deletes, 1); // User/email/query.graphql + assert!(create_dirs >= 2); // Comment and Comment/text + assert_eq!(delete_dirs, 1); // User/email directory + } +} From f9053f564bfbb16c89295eb7ad670c4981348c8b Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Wed, 26 Nov 2025 03:22:31 -0300 Subject: [PATCH 15/28] refactor mutable state to pure functions --- .../artifact_content/src/file_system_state.rs | 209 ++++++++---------- .../isograph_compiler/src/compiler_state.rs | 4 +- .../isograph_compiler/src/write_artifacts.rs | 20 +- 3 files changed, 116 insertions(+), 117 deletions(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index daf03a960..86d460677 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -20,41 +20,40 @@ pub struct FileSystemState { } impl FileSystemState { - pub fn insert(&mut self, path_and_content: &ArtifactPathAndContent) { - let value = ( - FileContent::from(path_and_content.file_content.clone()), - ArtifactHash::from(hash( - &path_and_content.file_content, - PersistedDocumentsHashAlgorithm::Md5, - )), - ); + pub fn diff_empty_to_new_state( + new: &Self, + artifact_directory: &PathBuf, + ) -> Vec { + let mut operations: Vec = Vec::new(); + operations.push(FileSystemOperation::DeleteDirectory( + artifact_directory.to_path_buf(), + )); - match &path_and_content.artifact_path.type_and_field { - Some(type_and_field) => { - self.nested_files - .entry(type_and_field.parent_object_entity_name) - .or_default() - .entry(type_and_field.selectable_name) - .or_default() - .insert(path_and_content.artifact_path.file_name, value); - } - None => { - self.root_files - .insert(path_and_content.artifact_path.file_name, value); + for (server_object, selectables) in &new.nested_files { + let server_object_path = artifact_directory.join(server_object); + operations.push(FileSystemOperation::CreateDirectory( + server_object_path.clone(), + )); + + for (selectable, files) in selectables { + let selectable_path = server_object_path.join(selectable); + operations.push(FileSystemOperation::CreateDirectory( + selectable_path.clone(), + )); + + for (file_name, (content, _)) in files { + let file_path = selectable_path.join(file_name); + operations.push(FileSystemOperation::WriteFile(file_path, content.clone())); + } } - } - } - pub fn from_artifacts(artifacts: Vec) -> Self { - let mut state = Self::default(); - for artifact in artifacts { - state.insert(&artifact); + for (file_name, (content, _)) in &new.root_files { + let file_path = artifact_directory.join(file_name); + operations.push(FileSystemOperation::WriteFile(file_path, content.clone())); + } } - state - } - pub fn is_empty(&self) -> bool { - self.root_files.is_empty() && self.nested_files.is_empty() + operations } // Computes filesystem operations needed to transform this state into the target state. @@ -65,45 +64,13 @@ impl FileSystemState { pub fn diff(&self, new: &Self, artifact_directory: &PathBuf) -> Vec { let mut operations: Vec = Vec::new(); - if self.is_empty() { - operations.push(FileSystemOperation::DeleteDirectory( - artifact_directory.to_path_buf(), - )); - - for (server_object, selectables) in &new.nested_files { - let server_object_path = artifact_directory.join(server_object.to_string()); - operations.push(FileSystemOperation::CreateDirectory( - server_object_path.clone(), - )); - - for (selectable, files) in selectables { - let selectable_path = server_object_path.join(selectable.to_string()); - operations.push(FileSystemOperation::CreateDirectory( - selectable_path.clone(), - )); - - for (file_name, (content, _)) in files { - let file_path = selectable_path.join(file_name.to_string()); - operations.push(FileSystemOperation::WriteFile(file_path, content.clone())); - } - } - - for (file_name, (content, _)) in &new.root_files { - let file_path = artifact_directory.join(file_name.to_string()); - operations.push(FileSystemOperation::WriteFile(file_path, content.clone())); - } - } - - return operations; - } - let mut new_server_objects: HashSet<&ServerObjectEntityName> = HashSet::new(); let mut new_selectables: HashSet<(&ServerObjectEntityName, &SelectableName)> = HashSet::new(); for (server_object, selectables) in &new.nested_files { new_server_objects.insert(server_object); - let server_object_path = artifact_directory.join(server_object.to_string()); + let server_object_path = artifact_directory.join(server_object); if !self.nested_files.contains_key(server_object) { operations.push(FileSystemOperation::CreateDirectory( @@ -113,7 +80,7 @@ impl FileSystemState { for (selectable, files) in selectables { new_selectables.insert((server_object, selectable)); - let selectable_path = server_object_path.join(selectable.to_string()); + let selectable_path = server_object_path.join(selectable); let should_create_dir = self .nested_files @@ -128,7 +95,7 @@ impl FileSystemState { } for (file_name, (new_content, new_hash)) in files { - let file_path = selectable_path.join(file_name.to_string()); + let file_path = selectable_path.join(file_name); let should_write = self .nested_files @@ -149,7 +116,7 @@ impl FileSystemState { } for (file_name, (new_content, new_hash)) in &new.root_files { - let file_path = artifact_directory.join(file_name.to_string()); + let file_path = artifact_directory.join(file_name); let should_write = self .root_files @@ -166,10 +133,10 @@ impl FileSystemState { } for (server_object, selectables) in &self.nested_files { - let server_object_path = artifact_directory.join(server_object.to_string()); + let server_object_path = artifact_directory.join(server_object); for (selectable, files) in selectables { - let selectable_path = server_object_path.join(selectable.to_string()); + let selectable_path = server_object_path.join(selectable); for file_name in files.keys() { let exist_in_new = new @@ -180,7 +147,7 @@ impl FileSystemState { .is_some(); if !exist_in_new { - let file_path = selectable_path.join(file_name.to_string()); + let file_path = selectable_path.join(file_name); operations.push(FileSystemOperation::DeleteFile(file_path)); } } @@ -197,7 +164,7 @@ impl FileSystemState { for file_name in self.root_files.keys() { if !new.root_files.contains_key(file_name) { - let file_path = artifact_directory.join(file_name.to_string()); + let file_path = artifact_directory.join(file_name); operations.push(FileSystemOperation::DeleteFile(file_path)); } } @@ -206,6 +173,45 @@ impl FileSystemState { } } +impl From> for FileSystemState { + fn from(artifacts: Vec) -> Self { + let mut root_files: HashMap = HashMap::new(); + let mut nested_files: HashMap< + ServerObjectEntityName, + HashMap>, + > = HashMap::new(); + + for artifact in artifacts { + let value = ( + FileContent::from(artifact.file_content.clone()), + ArtifactHash::from(hash( + &artifact.file_content, + PersistedDocumentsHashAlgorithm::Md5, + )), + ); + + match &artifact.artifact_path.type_and_field { + Some(type_and_field) => { + nested_files + .entry(type_and_field.parent_object_entity_name) + .or_default() + .entry(type_and_field.selectable_name) + .or_default() + .insert(artifact.artifact_path.file_name, value); + } + None => { + root_files.insert(artifact.artifact_path.file_name, value); + } + } + } + + FileSystemState { + root_files, + nested_files, + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -239,20 +245,11 @@ mod tests { } } - #[test] - fn test_empty_state() { - let state = FileSystemState::default(); - assert!(state.is_empty()); - } - #[test] fn test_insert_root_file() { - let mut state = FileSystemState::default(); let artifact = create_artifact(None, None, "package.json", "{}"); + let state = FileSystemState::from(vec![artifact]); - state.insert(&artifact); - - assert!(!state.is_empty()); assert_eq!(state.root_files.len(), 1); assert!( state @@ -263,7 +260,6 @@ mod tests { #[test] fn test_insert_nested_file() { - let mut state = FileSystemState::default(); let artifact = create_artifact( Some("User"), Some("name"), @@ -271,9 +267,8 @@ mod tests { "query { user { name } }", ); - state.insert(&artifact); + let state = FileSystemState::from(vec![artifact]); - assert!(!state.is_empty()); assert_eq!(state.nested_files.len(), 1); let server = &ServerObjectEntityName::from("User".intern()); @@ -303,7 +298,7 @@ mod tests { ), ]; - let state = FileSystemState::from_artifacts(artifacts); + let state = FileSystemState::from(artifacts); assert_eq!(state.root_files.len(), 1); assert_eq!(state.nested_files.len(), 1); @@ -316,15 +311,12 @@ mod tests { #[test] fn test_diff_empty_to_new() { let old_state = FileSystemState::default(); - let mut new_state = FileSystemState::default(); - - new_state.insert(&create_artifact(None, None, "root.txt", "content")); - new_state.insert(&create_artifact( + let new_state = FileSystemState::from(vec![create_artifact( Some("User"), Some("name"), "query.graphql", "query", - )); + )]); let artifact_dir = PathBuf::from("/artifacts"); let ops = old_state.diff(&new_state, &artifact_dir); @@ -349,8 +341,8 @@ mod tests { let artifact1 = create_artifact(None, None, "file.txt", "content"); let artifact2 = create_artifact(None, None, "file.txt", "content"); - let old_state = FileSystemState::from_artifacts(vec![artifact1]); - let new_state = FileSystemState::from_artifacts(vec![artifact2]); + let old_state = FileSystemState::from(vec![artifact1]); + let new_state = FileSystemState::from(vec![artifact2]); let artifact_dir = PathBuf::from("/__isograph"); let ops = old_state.diff(&new_state, &artifact_dir); @@ -363,8 +355,8 @@ mod tests { let old_artifact = create_artifact(None, None, "file.txt", "old content"); let new_artifact = create_artifact(None, None, "file.txt", "new content"); - let old_state = FileSystemState::from_artifacts(vec![old_artifact]); - let new_state = FileSystemState::from_artifacts(vec![new_artifact]); + let old_state = FileSystemState::from(vec![old_artifact]); + let new_state = FileSystemState::from(vec![new_artifact]); let artifact_dir = PathBuf::from("/__isograph"); let ops = old_state.diff(&new_state, &artifact_dir); @@ -375,14 +367,10 @@ mod tests { #[test] fn test_diff_add_new_file() { - let old_state = FileSystemState::from_artifacts(vec![create_artifact( - None, - None, - "existing.txt", - "content", - )]); + let old_state = + FileSystemState::from(vec![create_artifact(None, None, "existing.txt", "content")]); - let new_state = FileSystemState::from_artifacts(vec![ + let new_state = FileSystemState::from(vec![ create_artifact(None, None, "existing.txt", "content"), create_artifact(None, None, "new.txt", "new content"), ]); @@ -400,14 +388,13 @@ mod tests { #[test] fn test_diff_delete_file() { - let old_state = FileSystemState::from_artifacts(vec![ + let old_state = FileSystemState::from(vec![ create_artifact(None, None, "keep.txt", "content"), create_artifact(None, None, "delete.txt", "content"), ]); - let new_state = FileSystemState::from_artifacts(vec![create_artifact( - None, None, "keep.txt", "content", - )]); + let new_state = + FileSystemState::from(vec![create_artifact(None, None, "keep.txt", "content")]); let artifact_dir = PathBuf::from("/__isograph"); let ops = old_state.diff(&new_state, &artifact_dir); @@ -422,7 +409,7 @@ mod tests { #[test] fn test_diff_delete_empty_directory() { - let old_state = FileSystemState::from_artifacts(vec![create_artifact( + let old_state = FileSystemState::from(vec![create_artifact( Some("User"), Some("name"), "query.graphql", @@ -442,14 +429,14 @@ mod tests { #[test] fn test_diff_nested_file_changes() { - let old_state = FileSystemState::from_artifacts(vec![create_artifact( + let old_state = FileSystemState::from(vec![create_artifact( Some("User"), Some("name"), "query.graphql", "old query", )]); - let new_state = FileSystemState::from_artifacts(vec![create_artifact( + let new_state = FileSystemState::from(vec![create_artifact( Some("User"), Some("name"), "query.graphql", @@ -465,14 +452,14 @@ mod tests { #[test] fn test_diff_add_new_selectable() { - let old_state = FileSystemState::from_artifacts(vec![create_artifact( + let old_state = FileSystemState::from(vec![create_artifact( Some("User"), Some("name"), "query.graphql", "query", )]); - let new_state = FileSystemState::from_artifacts(vec![ + let new_state = FileSystemState::from(vec![ create_artifact(Some("User"), Some("name"), "query.graphql", "query"), create_artifact(Some("User"), Some("email"), "query.graphql", "query"), ]); @@ -487,14 +474,14 @@ mod tests { #[test] fn test_diff_complex_scenario() { - let old_state = FileSystemState::from_artifacts(vec![ + let old_state = FileSystemState::from(vec![ create_artifact(None, None, "root.txt", "old root"), create_artifact(Some("User"), Some("name"), "query.graphql", "old query"), create_artifact(Some("User"), Some("email"), "query.graphql", "delete me"), create_artifact(Some("Post"), Some("title"), "query.graphql", "post query"), ]); - let new_state = FileSystemState::from_artifacts(vec![ + let new_state = FileSystemState::from(vec![ create_artifact(None, None, "root.txt", "new root"), // changed create_artifact(None, None, "new_root.txt", "new file"), // added create_artifact(Some("User"), Some("name"), "query.graphql", "new query"), // changed diff --git a/crates/isograph_compiler/src/compiler_state.rs b/crates/isograph_compiler/src/compiler_state.rs index 170092e9b..5e6995b39 100644 --- a/crates/isograph_compiler/src/compiler_state.rs +++ b/crates/isograph_compiler/src/compiler_state.rs @@ -18,7 +18,7 @@ const GC_DURATION_SECONDS: u64 = 60; pub struct CompilerState { pub db: IsographDatabase, pub last_gc_run: Instant, - pub file_system_state: FileSystemState, + pub file_system_state: Option, } impl CompilerState { @@ -33,7 +33,7 @@ impl CompilerState { Self { db, last_gc_run: Instant::now(), - file_system_state: FileSystemState::default(), + file_system_state: None, } .wrap_ok() } diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index 4b668298b..d6e88762a 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -10,12 +10,24 @@ use artifact_content::FileSystemState; pub(crate) fn get_file_system_operations( paths_and_contents: impl IntoIterator, artifact_directory: &PathBuf, - file_system_state: &mut FileSystemState, + file_system_state: &mut Option, ) -> Vec { let artifacts: Vec = paths_and_contents.into_iter().collect(); - let new_file_system_state = FileSystemState::from_artifacts(artifacts); - let operations = file_system_state.diff(&new_file_system_state, artifact_directory); - *file_system_state = new_file_system_state; + let new_file_system_state = FileSystemState::from(artifacts); + let operations = match file_system_state { + None => { + let operations = FileSystemState::diff_empty_to_new_state( + &new_file_system_state, + artifact_directory, + ); + operations + } + Some(file_system_state) => { + let operations = file_system_state.diff(&new_file_system_state, artifact_directory); + operations + } + }; + *file_system_state = Some(new_file_system_state); operations } From 9322fa4ee44155cb043eb4f3a1d633d471c5aa71 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Wed, 26 Nov 2025 13:29:45 -0300 Subject: [PATCH 16/28] fix loop position --- crates/artifact_content/src/file_system_state.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index 86d460677..eacaec242 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -46,11 +46,11 @@ impl FileSystemState { operations.push(FileSystemOperation::WriteFile(file_path, content.clone())); } } + } - for (file_name, (content, _)) in &new.root_files { - let file_path = artifact_directory.join(file_name); - operations.push(FileSystemOperation::WriteFile(file_path, content.clone())); - } + for (file_name, (content, _)) in &new.root_files { + let file_path = artifact_directory.join(file_name); + operations.push(FileSystemOperation::WriteFile(file_path, content.clone())); } operations From 29fb325e242811d4ec9e3bb233c9d134ade3f548 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Wed, 26 Nov 2025 13:33:00 -0300 Subject: [PATCH 17/28] reduce redundant CreateDirectory operations --- crates/artifact_content/src/file_system_state.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index eacaec242..08f0bd774 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -31,9 +31,6 @@ impl FileSystemState { for (server_object, selectables) in &new.nested_files { let server_object_path = artifact_directory.join(server_object); - operations.push(FileSystemOperation::CreateDirectory( - server_object_path.clone(), - )); for (selectable, files) in selectables { let selectable_path = server_object_path.join(selectable); @@ -72,11 +69,6 @@ impl FileSystemState { new_server_objects.insert(server_object); let server_object_path = artifact_directory.join(server_object); - if !self.nested_files.contains_key(server_object) { - operations.push(FileSystemOperation::CreateDirectory( - server_object_path.clone(), - )); - } for (selectable, files) in selectables { new_selectables.insert((server_object, selectable)); From 4499ae3d4af9b27338f125b18e0ca8a9f35e3d61 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Wed, 26 Nov 2025 14:22:59 -0300 Subject: [PATCH 18/28] Use FileContent index insteaf of cloning it --- Cargo.lock | 1 + crates/artifact_content/Cargo.toml | 1 + .../artifact_content/src/file_system_state.rs | 345 +----------------- .../src/file_system_operation.rs | 4 +- crates/isograph_compiler/src/batch_compile.rs | 5 +- .../isograph_compiler/src/write_artifacts.rs | 15 +- crates/pico/src/lib.rs | 1 + 7 files changed, 36 insertions(+), 336 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f04c5aae5..e7304fc69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,6 +147,7 @@ dependencies = [ "lazy_static", "md-5", "pathdiff", + "pico", "prelude", "serde", "serde_json", diff --git a/crates/artifact_content/Cargo.toml b/crates/artifact_content/Cargo.toml index 5c03c6077..b90fd39a5 100644 --- a/crates/artifact_content/Cargo.toml +++ b/crates/artifact_content/Cargo.toml @@ -10,6 +10,7 @@ graphql_lang_types = { path = "../graphql_lang_types" } intern = { path = "../../relay-crates/intern" } isograph_schema = { path = "../isograph_schema" } isograph_config = { path = "../isograph_config" } +pico = { path = "../pico" } isograph_lang_types = { path = "../isograph_lang_types" } prelude = { path = "../prelude" } indexmap = { workspace = true } diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index 08f0bd774..d351eb02d 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -1,3 +1,4 @@ +use pico::Index; use std::{ collections::{HashMap, HashSet}, path::PathBuf, @@ -12,10 +13,10 @@ use isograph_config::PersistedDocumentsHashAlgorithm; #[derive(Debug, Clone, Default)] pub struct FileSystemState { - root_files: HashMap, + root_files: HashMap, ArtifactHash)>, nested_files: HashMap< ServerObjectEntityName, - HashMap>, + HashMap, ArtifactHash)>>, >, } @@ -38,16 +39,16 @@ impl FileSystemState { selectable_path.clone(), )); - for (file_name, (content, _)) in files { + for (file_name, (index, _)) in files { let file_path = selectable_path.join(file_name); - operations.push(FileSystemOperation::WriteFile(file_path, content.clone())); + operations.push(FileSystemOperation::WriteFile(file_path, index.clone())); } } } - for (file_name, (content, _)) in &new.root_files { + for (file_name, (index, _)) in &new.root_files { let file_path = artifact_directory.join(file_name); - operations.push(FileSystemOperation::WriteFile(file_path, content.clone())); + operations.push(FileSystemOperation::WriteFile(file_path, index.clone())); } operations @@ -69,7 +70,6 @@ impl FileSystemState { new_server_objects.insert(server_object); let server_object_path = artifact_directory.join(server_object); - for (selectable, files) in selectables { new_selectables.insert((server_object, selectable)); let selectable_path = server_object_path.join(selectable); @@ -86,7 +86,7 @@ impl FileSystemState { )); } - for (file_name, (new_content, new_hash)) in files { + for (file_name, (new_index, new_hash)) in files { let file_path = selectable_path.join(file_name); let should_write = self @@ -98,16 +98,14 @@ impl FileSystemState { .unwrap_or(true); if should_write { - operations.push(FileSystemOperation::WriteFile( - file_path, - new_content.clone(), - )); + operations + .push(FileSystemOperation::WriteFile(file_path, new_index.clone())); } } } } - for (file_name, (new_content, new_hash)) in &new.root_files { + for (file_name, (new_index, new_hash)) in &new.root_files { let file_path = artifact_directory.join(file_name); let should_write = self @@ -117,10 +115,7 @@ impl FileSystemState { .unwrap_or(true); if should_write { - operations.push(FileSystemOperation::WriteFile( - file_path, - new_content.clone(), - )); + operations.push(FileSystemOperation::WriteFile(file_path, new_index.clone())); } } @@ -165,17 +160,17 @@ impl FileSystemState { } } -impl From> for FileSystemState { - fn from(artifacts: Vec) -> Self { - let mut root_files: HashMap = HashMap::new(); +impl From<&[ArtifactPathAndContent]> for FileSystemState { + fn from(artifacts: &[ArtifactPathAndContent]) -> Self { + let mut root_files = HashMap::new(); let mut nested_files: HashMap< ServerObjectEntityName, - HashMap>, + HashMap, ArtifactHash)>>, > = HashMap::new(); - for artifact in artifacts { + for (index, artifact) in artifacts.iter().enumerate() { let value = ( - FileContent::from(artifact.file_content.clone()), + Index::new(index), ArtifactHash::from(hash( &artifact.file_content, PersistedDocumentsHashAlgorithm::Md5, @@ -203,307 +198,3 @@ impl From> for FileSystemState { } } } - -#[cfg(test)] -mod tests { - use super::*; - use common_lang_types::{ - ArtifactPath, ParentObjectEntityNameAndSelectableName, SelectableName, - ServerObjectEntityName, - }; - use intern::string_key::Intern; - use std::path::PathBuf; - - fn create_artifact( - server: Option<&str>, - selectable: Option<&str>, - file_name: &str, - content: &str, - ) -> ArtifactPathAndContent { - let type_and_field = match (server, selectable) { - (Some(s), Some(sel)) => Some(ParentObjectEntityNameAndSelectableName { - parent_object_entity_name: ServerObjectEntityName::from(s.intern()), - selectable_name: SelectableName::from(sel.intern()), - }), - _ => None, - }; - - ArtifactPathAndContent { - artifact_path: ArtifactPath { - type_and_field, - file_name: ArtifactFileName::from(file_name.intern()), - }, - file_content: FileContent::from(content.to_string()), - } - } - - #[test] - fn test_insert_root_file() { - let artifact = create_artifact(None, None, "package.json", "{}"); - let state = FileSystemState::from(vec![artifact]); - - assert_eq!(state.root_files.len(), 1); - assert!( - state - .root_files - .contains_key(&ArtifactFileName::from("package.json".intern())) - ); - } - - #[test] - fn test_insert_nested_file() { - let artifact = create_artifact( - Some("User"), - Some("name"), - "query.graphql", - "query { user { name } }", - ); - - let state = FileSystemState::from(vec![artifact]); - - assert_eq!(state.nested_files.len(), 1); - - let server = &ServerObjectEntityName::from("User".intern()); - let selectable = &SelectableName::from("name".intern()); - let file_name = &ArtifactFileName::from("query.graphql".intern()); - - assert!( - state - .nested_files - .get(server) - .and_then(|s| s.get(selectable)) - .and_then(|f| f.get(file_name)) - .is_some() - ); - } - - #[test] - fn test_from_artifacts() { - let artifacts = vec![ - create_artifact(None, None, "schema.graphql", "type Query"), - create_artifact(Some("User"), Some("name"), "query.graphql", "query {}"), - create_artifact( - Some("User"), - Some("email"), - "mutation.graphql", - "mutation {}", - ), - ]; - - let state = FileSystemState::from(artifacts); - - assert_eq!(state.root_files.len(), 1); - assert_eq!(state.nested_files.len(), 1); - - let user_server = &ServerObjectEntityName::from("User".intern()); - let selectables = state.nested_files.get(user_server).unwrap(); - assert_eq!(selectables.len(), 2); - } - - #[test] - fn test_diff_empty_to_new() { - let old_state = FileSystemState::default(); - let new_state = FileSystemState::from(vec![create_artifact( - Some("User"), - Some("name"), - "query.graphql", - "query", - )]); - - let artifact_dir = PathBuf::from("/artifacts"); - let ops = old_state.diff(&new_state, &artifact_dir); - - assert!(matches!(ops[0], FileSystemOperation::DeleteDirectory(_))); - - let create_dirs = ops - .iter() - .filter(|op| matches!(op, FileSystemOperation::CreateDirectory(_))) - .count(); - let write_files = ops - .iter() - .filter(|op| matches!(op, FileSystemOperation::WriteFile(_, _))) - .count(); - - assert_eq!(create_dirs, 2); // User dir and name dir - assert_eq!(write_files, 2); // root.txt and query.graphql - } - - #[test] - fn test_diff_no_changes() { - let artifact1 = create_artifact(None, None, "file.txt", "content"); - let artifact2 = create_artifact(None, None, "file.txt", "content"); - - let old_state = FileSystemState::from(vec![artifact1]); - let new_state = FileSystemState::from(vec![artifact2]); - - let artifact_dir = PathBuf::from("/__isograph"); - let ops = old_state.diff(&new_state, &artifact_dir); - - assert_eq!(ops.len(), 0); - } - - #[test] - fn test_diff_file_content_changed() { - let old_artifact = create_artifact(None, None, "file.txt", "old content"); - let new_artifact = create_artifact(None, None, "file.txt", "new content"); - - let old_state = FileSystemState::from(vec![old_artifact]); - let new_state = FileSystemState::from(vec![new_artifact]); - - let artifact_dir = PathBuf::from("/__isograph"); - let ops = old_state.diff(&new_state, &artifact_dir); - - assert_eq!(ops.len(), 1); - assert!(matches!(ops[0], FileSystemOperation::WriteFile(_, _))); - } - - #[test] - fn test_diff_add_new_file() { - let old_state = - FileSystemState::from(vec![create_artifact(None, None, "existing.txt", "content")]); - - let new_state = FileSystemState::from(vec![ - create_artifact(None, None, "existing.txt", "content"), - create_artifact(None, None, "new.txt", "new content"), - ]); - - let artifact_dir = PathBuf::from("/__isograph"); - let ops = old_state.diff(&new_state, &artifact_dir); - - assert_eq!(ops.len(), 1); - if let FileSystemOperation::WriteFile(path, _) = &ops[0] { - assert!(path.ends_with("new.txt")); - } else { - panic!("Expected WriteFile operation"); - } - } - - #[test] - fn test_diff_delete_file() { - let old_state = FileSystemState::from(vec![ - create_artifact(None, None, "keep.txt", "content"), - create_artifact(None, None, "delete.txt", "content"), - ]); - - let new_state = - FileSystemState::from(vec![create_artifact(None, None, "keep.txt", "content")]); - - let artifact_dir = PathBuf::from("/__isograph"); - let ops = old_state.diff(&new_state, &artifact_dir); - - assert_eq!(ops.len(), 1); - if let FileSystemOperation::DeleteFile(path) = &ops[0] { - assert!(path.ends_with("delete.txt")); - } else { - panic!("Expected DeleteFile operation"); - } - } - - #[test] - fn test_diff_delete_empty_directory() { - let old_state = FileSystemState::from(vec![create_artifact( - Some("User"), - Some("name"), - "query.graphql", - "query", - )]); - - let new_state = FileSystemState::default(); - - let artifact_dir = PathBuf::from("/__isograph"); - let ops = old_state.diff(&new_state, &artifact_dir); - - assert_eq!(ops.len(), 3); - assert!(matches!(ops[0], FileSystemOperation::DeleteFile(_))); - assert!(matches!(ops[1], FileSystemOperation::DeleteDirectory(_))); - assert!(matches!(ops[2], FileSystemOperation::DeleteDirectory(_))); - } - - #[test] - fn test_diff_nested_file_changes() { - let old_state = FileSystemState::from(vec![create_artifact( - Some("User"), - Some("name"), - "query.graphql", - "old query", - )]); - - let new_state = FileSystemState::from(vec![create_artifact( - Some("User"), - Some("name"), - "query.graphql", - "new query", - )]); - - let artifact_dir = PathBuf::from("/__isograph"); - let ops = old_state.diff(&new_state, &artifact_dir); - - assert_eq!(ops.len(), 1); - assert!(matches!(ops[0], FileSystemOperation::WriteFile(_, _))); - } - - #[test] - fn test_diff_add_new_selectable() { - let old_state = FileSystemState::from(vec![create_artifact( - Some("User"), - Some("name"), - "query.graphql", - "query", - )]); - - let new_state = FileSystemState::from(vec![ - create_artifact(Some("User"), Some("name"), "query.graphql", "query"), - create_artifact(Some("User"), Some("email"), "query.graphql", "query"), - ]); - - let artifact_dir = PathBuf::from("/__isograph"); - let ops = old_state.diff(&new_state, &artifact_dir); - - assert_eq!(ops.len(), 2); - assert!(matches!(ops[0], FileSystemOperation::CreateDirectory(_))); - assert!(matches!(ops[1], FileSystemOperation::WriteFile(_, _))); - } - - #[test] - fn test_diff_complex_scenario() { - let old_state = FileSystemState::from(vec![ - create_artifact(None, None, "root.txt", "old root"), - create_artifact(Some("User"), Some("name"), "query.graphql", "old query"), - create_artifact(Some("User"), Some("email"), "query.graphql", "delete me"), - create_artifact(Some("Post"), Some("title"), "query.graphql", "post query"), - ]); - - let new_state = FileSystemState::from(vec![ - create_artifact(None, None, "root.txt", "new root"), // changed - create_artifact(None, None, "new_root.txt", "new file"), // added - create_artifact(Some("User"), Some("name"), "query.graphql", "new query"), // changed - create_artifact(Some("Post"), Some("title"), "query.graphql", "post query"), // unchanged - create_artifact(Some("Comment"), Some("text"), "query.graphql", "comment"), // new server - ]); - - let artifact_dir = PathBuf::from("/__isograph"); - let ops = old_state.diff(&new_state, &artifact_dir); - - let writes = ops - .iter() - .filter(|op| matches!(op, FileSystemOperation::WriteFile(_, _))) - .count(); - let deletes = ops - .iter() - .filter(|op| matches!(op, FileSystemOperation::DeleteFile(_))) - .count(); - let create_dirs = ops - .iter() - .filter(|op| matches!(op, FileSystemOperation::CreateDirectory(_))) - .count(); - let delete_dirs = ops - .iter() - .filter(|op| matches!(op, FileSystemOperation::DeleteDirectory(_))) - .count(); - - assert!(writes >= 3); // root.txt, new_root.txt, User/name/query.graphql, Comment/text/query.graphql - assert_eq!(deletes, 1); // User/email/query.graphql - assert!(create_dirs >= 2); // Comment and Comment/text - assert_eq!(delete_dirs, 1); // User/email directory - } -} diff --git a/crates/common_lang_types/src/file_system_operation.rs b/crates/common_lang_types/src/file_system_operation.rs index c07891c5c..1ec010565 100644 --- a/crates/common_lang_types/src/file_system_operation.rs +++ b/crates/common_lang_types/src/file_system_operation.rs @@ -2,10 +2,12 @@ use std::path::PathBuf; use crate::FileContent; +use pico::Index; + #[derive(Debug, Clone)] pub enum FileSystemOperation { DeleteDirectory(PathBuf), CreateDirectory(PathBuf), - WriteFile(PathBuf, FileContent), + WriteFile(PathBuf, Index), DeleteFile(PathBuf), } diff --git a/crates/isograph_compiler/src/batch_compile.rs b/crates/isograph_compiler/src/batch_compile.rs index cf7a8fd7e..db244cdf2 100644 --- a/crates/isograph_compiler/src/batch_compile.rs +++ b/crates/isograph_compiler/src/batch_compile.rs @@ -92,12 +92,13 @@ pub fn compile( let (artifacts, stats) = get_artifact_path_and_content(db)?; let file_system_operations = get_file_system_operations( - artifacts, + &artifacts, &config.artifact_directory.absolute_path, &mut state.file_system_state, ); - let total_artifacts_written = apply_file_system_operations(file_system_operations)?; + let total_artifacts_written = + apply_file_system_operations(&file_system_operations, &artifacts)?; CompilationStats { client_field_count: stats.client_field_count, diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index d6e88762a..8d52e95ee 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -8,12 +8,11 @@ use artifact_content::FileSystemState; #[tracing::instrument(skip_all)] pub(crate) fn get_file_system_operations( - paths_and_contents: impl IntoIterator, + paths_and_contents: &[ArtifactPathAndContent], artifact_directory: &PathBuf, file_system_state: &mut Option, ) -> Vec { - let artifacts: Vec = paths_and_contents.into_iter().collect(); - let new_file_system_state = FileSystemState::from(artifacts); + let new_file_system_state = FileSystemState::from(paths_and_contents); let operations = match file_system_state { None => { let operations = FileSystemState::diff_empty_to_new_state( @@ -33,13 +32,12 @@ pub(crate) fn get_file_system_operations( #[tracing::instrument(skip_all)] pub(crate) fn apply_file_system_operations( - operations: Vec, + operations: &[FileSystemOperation], + artifacts: &[ArtifactPathAndContent], ) -> DiagnosticResult { let mut count = 0; for operation in operations { - count += 1; - match operation { FileSystemOperation::DeleteDirectory(path) => { if path.exists() { @@ -53,6 +51,7 @@ pub(crate) fn apply_file_system_operations( } } FileSystemOperation::CreateDirectory(path) => { + count += 1; fs::create_dir_all(path.clone()).map_err(|e| { unable_to_do_something_at_path_diagnostic( &path, @@ -62,6 +61,10 @@ pub(crate) fn apply_file_system_operations( })?; } FileSystemOperation::WriteFile(path, content) => { + let content = &artifacts + .get(content.idx) + .expect("index should be valid for artifacts vec") + .file_content; fs::write(path.clone(), content.as_bytes()).map_err(|e| { unable_to_do_something_at_path_diagnostic( &path, diff --git a/crates/pico/src/lib.rs b/crates/pico/src/lib.rs index a61dde2e5..fa3da7a52 100644 --- a/crates/pico/src/lib.rs +++ b/crates/pico/src/lib.rs @@ -19,6 +19,7 @@ pub use database::*; pub use derived_node::*; pub use dyn_eq::*; pub use execute_memoized_function::*; +pub use index::*; pub use intern::*; pub use memo_ref::*; pub use raw_ptr::*; From b7e5b3fbd9e6d4283295679b383d79c9657403b4 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Wed, 26 Nov 2025 14:31:51 -0300 Subject: [PATCH 19/28] remove references to &Copy --- crates/artifact_content/src/file_system_state.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index d351eb02d..ecad6151f 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -62,16 +62,16 @@ impl FileSystemState { pub fn diff(&self, new: &Self, artifact_directory: &PathBuf) -> Vec { let mut operations: Vec = Vec::new(); - let mut new_server_objects: HashSet<&ServerObjectEntityName> = HashSet::new(); - let mut new_selectables: HashSet<(&ServerObjectEntityName, &SelectableName)> = + let mut new_server_objects: HashSet = HashSet::new(); + let mut new_selectables: HashSet<(ServerObjectEntityName, &SelectableName)> = HashSet::new(); for (server_object, selectables) in &new.nested_files { - new_server_objects.insert(server_object); + new_server_objects.insert(*server_object); let server_object_path = artifact_directory.join(server_object); for (selectable, files) in selectables { - new_selectables.insert((server_object, selectable)); + new_selectables.insert((*server_object, selectable)); let selectable_path = server_object_path.join(selectable); let should_create_dir = self @@ -139,7 +139,7 @@ impl FileSystemState { } } - if !new_selectables.contains(&(server_object, selectable)) { + if !new_selectables.contains(&(*server_object, selectable)) { operations.push(FileSystemOperation::DeleteDirectory(selectable_path)); } } From 7388fcecd3a473a55afc585192e927b4b65b0c25 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Wed, 26 Nov 2025 14:44:05 -0300 Subject: [PATCH 20/28] turn diff a pure function --- .../artifact_content/src/file_system_state.rs | 26 +++++++------------ .../isograph_compiler/src/write_artifacts.rs | 12 +++++---- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index ecad6151f..506ef938e 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -21,16 +21,13 @@ pub struct FileSystemState { } impl FileSystemState { - pub fn diff_empty_to_new_state( - new: &Self, - artifact_directory: &PathBuf, - ) -> Vec { + pub fn recreate_all(state: &Self, artifact_directory: &PathBuf) -> Vec { let mut operations: Vec = Vec::new(); operations.push(FileSystemOperation::DeleteDirectory( artifact_directory.to_path_buf(), )); - for (server_object, selectables) in &new.nested_files { + for (server_object, selectables) in &state.nested_files { let server_object_path = artifact_directory.join(server_object); for (selectable, files) in selectables { @@ -46,7 +43,7 @@ impl FileSystemState { } } - for (file_name, (index, _)) in &new.root_files { + for (file_name, (index, _)) in &state.root_files { let file_path = artifact_directory.join(file_name); operations.push(FileSystemOperation::WriteFile(file_path, index.clone())); } @@ -54,12 +51,7 @@ impl FileSystemState { operations } - // Computes filesystem operations needed to transform this state into the target state. - // Returns operations to create, update, and delete files and directories. Files are updated - // only when their hash differs. If the current state is empty, emits a DeleteDirectory - // operation for the artifact root followed by recreation of all files. Empty directories - // are automatically removed after their files are deleted. - pub fn diff(&self, new: &Self, artifact_directory: &PathBuf) -> Vec { + pub fn diff(old: &Self, new: &Self, artifact_directory: &PathBuf) -> Vec { let mut operations: Vec = Vec::new(); let mut new_server_objects: HashSet = HashSet::new(); @@ -74,7 +66,7 @@ impl FileSystemState { new_selectables.insert((*server_object, selectable)); let selectable_path = server_object_path.join(selectable); - let should_create_dir = self + let should_create_dir = old .nested_files .get(server_object) .and_then(|s| s.get(selectable)) @@ -89,7 +81,7 @@ impl FileSystemState { for (file_name, (new_index, new_hash)) in files { let file_path = selectable_path.join(file_name); - let should_write = self + let should_write = old .nested_files .get(server_object) .and_then(|s| s.get(selectable)) @@ -108,7 +100,7 @@ impl FileSystemState { for (file_name, (new_index, new_hash)) in &new.root_files { let file_path = artifact_directory.join(file_name); - let should_write = self + let should_write = old .root_files .get(file_name) .map(|(_old_content, old_hash)| old_hash != new_hash) @@ -119,7 +111,7 @@ impl FileSystemState { } } - for (server_object, selectables) in &self.nested_files { + for (server_object, selectables) in &old.nested_files { let server_object_path = artifact_directory.join(server_object); for (selectable, files) in selectables { @@ -149,7 +141,7 @@ impl FileSystemState { } } - for file_name in self.root_files.keys() { + for file_name in old.root_files.keys() { if !new.root_files.contains_key(file_name) { let file_path = artifact_directory.join(file_name); operations.push(FileSystemOperation::DeleteFile(file_path)); diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index 8d52e95ee..a8b3addce 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -15,14 +15,16 @@ pub(crate) fn get_file_system_operations( let new_file_system_state = FileSystemState::from(paths_and_contents); let operations = match file_system_state { None => { - let operations = FileSystemState::diff_empty_to_new_state( - &new_file_system_state, - artifact_directory, - ); + let operations = + FileSystemState::recreate_all(&new_file_system_state, artifact_directory); operations } Some(file_system_state) => { - let operations = file_system_state.diff(&new_file_system_state, artifact_directory); + let operations = FileSystemState::diff( + &file_system_state, + &new_file_system_state, + artifact_directory, + ); operations } }; From 5451d26157b996436dadc6ef47d0bd167955711c Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Wed, 26 Nov 2025 15:01:14 -0300 Subject: [PATCH 21/28] rewrite tests --- .../artifact_content/src/file_system_state.rs | 312 ++++++++++++++++++ 1 file changed, 312 insertions(+) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index 506ef938e..5b27578dd 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -190,3 +190,315 @@ impl From<&[ArtifactPathAndContent]> for FileSystemState { } } } + +#[cfg(test)] +mod tests { + use super::*; + use common_lang_types::{ + ArtifactPath, ParentObjectEntityNameAndSelectableName, SelectableName, + ServerObjectEntityName, + }; + use intern::string_key::Intern; + use std::path::PathBuf; + + fn create_artifact( + server: Option<&str>, + selectable: Option<&str>, + file_name: &str, + content: &str, + ) -> ArtifactPathAndContent { + let type_and_field = match (server, selectable) { + (Some(s), Some(sel)) => Some(ParentObjectEntityNameAndSelectableName { + parent_object_entity_name: ServerObjectEntityName::from(s.intern()), + selectable_name: SelectableName::from(sel.intern()), + }), + _ => None, + }; + + ArtifactPathAndContent { + artifact_path: ArtifactPath { + type_and_field, + file_name: ArtifactFileName::from(file_name.intern()), + }, + file_content: FileContent::from(content.to_string()), + } + } + + #[test] + fn test_insert_root_file() { + let artifact = create_artifact(None, None, "package.json", "{}"); + let state = FileSystemState::from(&[artifact][..]); + + assert_eq!(state.root_files.len(), 1); + assert!( + state + .root_files + .contains_key(&ArtifactFileName::from("package.json".intern())) + ); + } + + #[test] + fn test_insert_nested_file() { + let artifact = create_artifact( + Some("User"), + Some("name"), + "query.graphql", + "query { user { name } }", + ); + + let state = FileSystemState::from(&[artifact][..]); + + assert_eq!(state.nested_files.len(), 1); + + let server = &ServerObjectEntityName::from("User".intern()); + let selectable = &SelectableName::from("name".intern()); + let file_name = &ArtifactFileName::from("query.graphql".intern()); + + assert!( + state + .nested_files + .get(server) + .and_then(|s| s.get(selectable)) + .and_then(|f| f.get(file_name)) + .is_some() + ); + } + + #[test] + fn test_from_artifacts() { + let artifacts = vec![ + create_artifact(None, None, "schema.graphql", "type Query"), + create_artifact(Some("User"), Some("name"), "query.graphql", "query {}"), + create_artifact( + Some("User"), + Some("email"), + "mutation.graphql", + "mutation {}", + ), + ]; + + let state = FileSystemState::from(&artifacts[..]); + + assert_eq!(state.root_files.len(), 1); + assert_eq!(state.nested_files.len(), 1); + + let user_server = &ServerObjectEntityName::from("User".intern()); + let selectables = state.nested_files.get(user_server).unwrap(); + assert_eq!(selectables.len(), 2); + } + + #[test] + fn test_diff_empty_to_new() { + let artifacts = vec![create_artifact( + Some("User"), + Some("name"), + "query.graphql", + "query", + )]; + let new_state = FileSystemState::from(&artifacts[..]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = FileSystemState::recreate_all(&new_state, &artifact_dir); + + assert!(matches!(ops[0], FileSystemOperation::DeleteDirectory(_))); + + let create_dirs = ops + .iter() + .filter(|op| matches!(op, FileSystemOperation::CreateDirectory(_))) + .count(); + let write_files = ops + .iter() + .filter(|op| matches!(op, FileSystemOperation::WriteFile(_, _))) + .count(); + + assert_eq!(create_dirs, 1); + assert_eq!(write_files, 1); + } + + #[test] + fn test_diff_no_changes() { + let artifact1 = vec![create_artifact(None, None, "file.txt", "content")]; + let artifact2 = vec![create_artifact(None, None, "file.txt", "content")]; + + let old_state = FileSystemState::from(&artifact1[..]); + let new_state = FileSystemState::from(&artifact2[..]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); + assert_eq!(ops.len(), 0); + } + + #[test] + fn test_diff_file_content_() { + let old_artifacts = vec![create_artifact(None, None, "file.txt", "old content")]; + let new_artifacts = vec![create_artifact(None, None, "file.txt", "new content")]; + + let old_state = FileSystemState::from(&old_artifacts[..]); + let new_state = FileSystemState::from(&new_artifacts[..]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); + + assert_eq!(ops.len(), 1); + assert!(matches!(ops[0], FileSystemOperation::WriteFile(_, _))); + } + + #[test] + fn test_diff_add_new_file() { + let old_artifacts = vec![create_artifact(None, None, "existing.txt", "content")]; + let new_artifacts = vec![ + create_artifact(None, None, "existing.txt", "content"), + create_artifact(None, None, "new.txt", "new content"), + ]; + + let old_state = FileSystemState::from(&old_artifacts[..]); + let new_state = FileSystemState::from(&new_artifacts[..]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); + + assert_eq!(ops.len(), 1); + if let FileSystemOperation::WriteFile(path, _) = &ops[0] { + assert!(path.ends_with("new.txt")); + } else { + panic!("Expected WriteFile operation"); + } + } + + #[test] + fn test_diff_delete_file() { + let old_artifacts = vec![ + create_artifact(None, None, "keep.txt", "content"), + create_artifact(None, None, "delete.txt", "content"), + ]; + let new_artifacts = vec![create_artifact(None, None, "keep.txt", "content")]; + + let old_state = FileSystemState::from(&old_artifacts[..]); + let new_state = FileSystemState::from(&new_artifacts[..]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); + + assert_eq!(ops.len(), 1); + if let FileSystemOperation::DeleteFile(path) = &ops[0] { + assert!(path.ends_with("delete.txt")); + } else { + panic!("Expected DeleteFile operation"); + } + } + + #[test] + fn test_diff_delete_empty_directory() { + let old_artifacts = vec![create_artifact( + Some("User"), + Some("name"), + "query.graphql", + "query", + )]; + let old_state = FileSystemState::from(&old_artifacts[..]); + + let new_state = FileSystemState::default(); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); + + assert_eq!(ops.len(), 3); + assert!(matches!(ops[0], FileSystemOperation::DeleteFile(_))); + assert!(matches!(ops[1], FileSystemOperation::DeleteDirectory(_))); + assert!(matches!(ops[2], FileSystemOperation::DeleteDirectory(_))); + } + + #[test] + fn test_diff_nested_file_changes() { + let old_artifacts = vec![create_artifact( + Some("User"), + Some("name"), + "query.graphql", + "old query", + )]; + let new_artifacts = vec![create_artifact( + Some("User"), + Some("name"), + "query.graphql", + "new query", + )]; + + let old_state = FileSystemState::from(&old_artifacts[..]); + let new_state = FileSystemState::from(&new_artifacts[..]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); + + assert_eq!(ops.len(), 1); + assert!(matches!(ops[0], FileSystemOperation::WriteFile(_, _))); + } + + #[test] + fn test_diff_add_new_selectable() { + let old_artifacts = vec![create_artifact( + Some("User"), + Some("name"), + "query.graphql", + "query", + )]; + let new_artifacts = vec![ + create_artifact(Some("User"), Some("name"), "query.graphql", "query"), + create_artifact(Some("User"), Some("email"), "query.graphql", "query"), + ]; + + let old_state = FileSystemState::from(&old_artifacts[..]); + let new_state = FileSystemState::from(&new_artifacts[..]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); + + assert_eq!(ops.len(), 2); + assert!(matches!(ops[0], FileSystemOperation::CreateDirectory(_))); + assert!(matches!(ops[1], FileSystemOperation::WriteFile(_, _))); + } + + #[test] + fn test_diff_complex_scenario() { + let old_artifacts = vec![ + create_artifact(None, None, "root.txt", "old root"), + create_artifact(Some("User"), Some("name"), "query.graphql", "old query"), + create_artifact(Some("User"), Some("email"), "query.graphql", "delete me"), + create_artifact(Some("Post"), Some("title"), "query.graphql", "post query"), + ]; + let new_artifacts = vec![ + create_artifact(None, None, "root.txt", "new root"), + create_artifact(None, None, "new_root.txt", "new file"), + create_artifact(Some("User"), Some("name"), "query.graphql", "new query"), + create_artifact(Some("Post"), Some("title"), "query.graphql", "post query"), + create_artifact(Some("Comment"), Some("text"), "query.graphql", "comment"), + ]; + + let old_state = FileSystemState::from(&old_artifacts[..]); + let new_state = FileSystemState::from(&new_artifacts[..]); + + let artifact_dir = PathBuf::from("/__isograph"); + let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); + + let writes = ops + .iter() + .filter(|op| matches!(op, FileSystemOperation::WriteFile(_, _))) + .count(); + let deletes = ops + .iter() + .filter(|op| matches!(op, FileSystemOperation::DeleteFile(_))) + .count(); + let create_dirs = ops + .iter() + .filter(|op| matches!(op, FileSystemOperation::CreateDirectory(_))) + .count(); + let delete_dirs = ops + .iter() + .filter(|op| matches!(op, FileSystemOperation::DeleteDirectory(_))) + .count(); + + assert!(writes >= 3); // root.txt, new_root.txt, User/name/query.graphql, Comment/text/query.graphql + assert_eq!(deletes, 1); // User/email/query.graphql + assert!(create_dirs >= 1); // Comment/text + assert_eq!(delete_dirs, 1); // User/email directory + } +} \ No newline at end of file From e578ec0e41b43d80bc36593bde8436cbf2c07279 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Wed, 26 Nov 2025 15:01:35 -0300 Subject: [PATCH 22/28] fmt --- .../artifact_content/src/file_system_state.rs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index 5b27578dd..48679f1a2 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -320,8 +320,8 @@ mod tests { let artifact1 = vec![create_artifact(None, None, "file.txt", "content")]; let artifact2 = vec![create_artifact(None, None, "file.txt", "content")]; - let old_state = FileSystemState::from(&artifact1[..]); - let new_state = FileSystemState::from(&artifact2[..]); + let old_state = FileSystemState::from(&artifact1[..]); + let new_state = FileSystemState::from(&artifact2[..]); let artifact_dir = PathBuf::from("/__isograph"); let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); @@ -333,7 +333,7 @@ mod tests { let old_artifacts = vec![create_artifact(None, None, "file.txt", "old content")]; let new_artifacts = vec![create_artifact(None, None, "file.txt", "new content")]; - let old_state = FileSystemState::from(&old_artifacts[..]); + let old_state = FileSystemState::from(&old_artifacts[..]); let new_state = FileSystemState::from(&new_artifacts[..]); let artifact_dir = PathBuf::from("/__isograph"); @@ -351,9 +351,9 @@ mod tests { create_artifact(None, None, "new.txt", "new content"), ]; - let old_state = FileSystemState::from(&old_artifacts[..]); + let old_state = FileSystemState::from(&old_artifacts[..]); let new_state = FileSystemState::from(&new_artifacts[..]); - + let artifact_dir = PathBuf::from("/__isograph"); let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); @@ -395,12 +395,12 @@ mod tests { "query.graphql", "query", )]; - let old_state = FileSystemState::from(&old_artifacts[..]); + let old_state = FileSystemState::from(&old_artifacts[..]); let new_state = FileSystemState::default(); let artifact_dir = PathBuf::from("/__isograph"); - let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); + let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); assert_eq!(ops.len(), 3); assert!(matches!(ops[0], FileSystemOperation::DeleteFile(_))); @@ -447,7 +447,7 @@ mod tests { ]; let old_state = FileSystemState::from(&old_artifacts[..]); - let new_state = FileSystemState::from(&new_artifacts[..]); + let new_state = FileSystemState::from(&new_artifacts[..]); let artifact_dir = PathBuf::from("/__isograph"); let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); @@ -466,18 +466,18 @@ mod tests { create_artifact(Some("Post"), Some("title"), "query.graphql", "post query"), ]; let new_artifacts = vec![ - create_artifact(None, None, "root.txt", "new root"), - create_artifact(None, None, "new_root.txt", "new file"), - create_artifact(Some("User"), Some("name"), "query.graphql", "new query"), - create_artifact(Some("Post"), Some("title"), "query.graphql", "post query"), - create_artifact(Some("Comment"), Some("text"), "query.graphql", "comment"), + create_artifact(None, None, "root.txt", "new root"), + create_artifact(None, None, "new_root.txt", "new file"), + create_artifact(Some("User"), Some("name"), "query.graphql", "new query"), + create_artifact(Some("Post"), Some("title"), "query.graphql", "post query"), + create_artifact(Some("Comment"), Some("text"), "query.graphql", "comment"), ]; - let old_state = FileSystemState::from(&old_artifacts[..]); - let new_state = FileSystemState::from(&new_artifacts[..]); + let old_state = FileSystemState::from(&old_artifacts[..]); + let new_state = FileSystemState::from(&new_artifacts[..]); let artifact_dir = PathBuf::from("/__isograph"); - let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); + let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); let writes = ops .iter() @@ -501,4 +501,4 @@ mod tests { assert!(create_dirs >= 1); // Comment/text assert_eq!(delete_dirs, 1); // User/email directory } -} \ No newline at end of file +} From e9dc5143cf7d635d6723a475623fd5d73338ae83 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Wed, 26 Nov 2025 15:03:47 -0300 Subject: [PATCH 23/28] rename unused arg --- crates/artifact_content/src/file_system_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index 48679f1a2..60c306ffd 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -103,7 +103,7 @@ impl FileSystemState { let should_write = old .root_files .get(file_name) - .map(|(_old_content, old_hash)| old_hash != new_hash) + .map(|(_, old_hash)| old_hash != new_hash) .unwrap_or(true); if should_write { From bf2297d854318eeb3b91b26eb9ae9970ef3f76fb Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Wed, 26 Nov 2025 15:14:42 -0300 Subject: [PATCH 24/28] Remove unecessary delete operations --- .../artifact_content/src/file_system_state.rs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index 60c306ffd..3d7ba3aeb 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -114,9 +114,19 @@ impl FileSystemState { for (server_object, selectables) in &old.nested_files { let server_object_path = artifact_directory.join(server_object); + if !new_server_objects.contains(server_object) { + operations.push(FileSystemOperation::DeleteDirectory(server_object_path)); + continue; + } + for (selectable, files) in selectables { let selectable_path = server_object_path.join(selectable); + if !new_selectables.contains(&(*server_object, selectable)) { + operations.push(FileSystemOperation::DeleteDirectory(selectable_path)); + continue; + } + for file_name in files.keys() { let exist_in_new = new .nested_files @@ -130,14 +140,6 @@ impl FileSystemState { operations.push(FileSystemOperation::DeleteFile(file_path)); } } - - if !new_selectables.contains(&(*server_object, selectable)) { - operations.push(FileSystemOperation::DeleteDirectory(selectable_path)); - } - } - - if !new_server_objects.contains(server_object) { - operations.push(FileSystemOperation::DeleteDirectory(server_object_path)); } } @@ -148,7 +150,7 @@ impl FileSystemState { } } - return operations; + operations } } @@ -402,10 +404,8 @@ mod tests { let artifact_dir = PathBuf::from("/__isograph"); let ops = FileSystemState::diff(&old_state, &new_state, &artifact_dir); - assert_eq!(ops.len(), 3); - assert!(matches!(ops[0], FileSystemOperation::DeleteFile(_))); - assert!(matches!(ops[1], FileSystemOperation::DeleteDirectory(_))); - assert!(matches!(ops[2], FileSystemOperation::DeleteDirectory(_))); + assert_eq!(ops.len(), 1); + assert!(matches!(ops[0], FileSystemOperation::DeleteDirectory(_))); } #[test] @@ -497,7 +497,7 @@ mod tests { .count(); assert!(writes >= 3); // root.txt, new_root.txt, User/name/query.graphql, Comment/text/query.graphql - assert_eq!(deletes, 1); // User/email/query.graphql + assert_eq!(deletes, 0); assert!(create_dirs >= 1); // Comment/text assert_eq!(delete_dirs, 1); // User/email directory } From ab17a254f45adb5b7013e9a6f45e1a80aef1b1fb Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Wed, 26 Nov 2025 17:07:23 -0300 Subject: [PATCH 25/28] nits: remove type annotations and unnecessary variable declarations --- .../artifact_content/src/file_system_state.rs | 13 ++++++------- .../isograph_compiler/src/write_artifacts.rs | 19 ++++++------------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index 3d7ba3aeb..fa047c74f 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -54,16 +54,15 @@ impl FileSystemState { pub fn diff(old: &Self, new: &Self, artifact_directory: &PathBuf) -> Vec { let mut operations: Vec = Vec::new(); - let mut new_server_objects: HashSet = HashSet::new(); - let mut new_selectables: HashSet<(ServerObjectEntityName, &SelectableName)> = - HashSet::new(); + let mut new_server_objects = HashSet::new(); + let mut new_selectables = HashSet::new(); for (server_object, selectables) in &new.nested_files { new_server_objects.insert(*server_object); let server_object_path = artifact_directory.join(server_object); for (selectable, files) in selectables { - new_selectables.insert((*server_object, selectable)); + new_selectables.insert((*server_object, *selectable)); let selectable_path = server_object_path.join(selectable); let should_create_dir = old @@ -122,7 +121,7 @@ impl FileSystemState { for (selectable, files) in selectables { let selectable_path = server_object_path.join(selectable); - if !new_selectables.contains(&(*server_object, selectable)) { + if !new_selectables.contains(&(*server_object, *selectable)) { operations.push(FileSystemOperation::DeleteDirectory(selectable_path)); continue; } @@ -253,14 +252,14 @@ mod tests { assert_eq!(state.nested_files.len(), 1); let server = &ServerObjectEntityName::from("User".intern()); - let selectable = &SelectableName::from("name".intern()); + let selectable = SelectableName::from("name".intern()); let file_name = &ArtifactFileName::from("query.graphql".intern()); assert!( state .nested_files .get(server) - .and_then(|s| s.get(selectable)) + .and_then(|s| s.get(&selectable)) .and_then(|f| f.get(file_name)) .is_some() ); diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index a8b3addce..683bd309d 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -14,19 +14,12 @@ pub(crate) fn get_file_system_operations( ) -> Vec { let new_file_system_state = FileSystemState::from(paths_and_contents); let operations = match file_system_state { - None => { - let operations = - FileSystemState::recreate_all(&new_file_system_state, artifact_directory); - operations - } - Some(file_system_state) => { - let operations = FileSystemState::diff( - &file_system_state, - &new_file_system_state, - artifact_directory, - ); - operations - } + None => FileSystemState::recreate_all(&new_file_system_state, artifact_directory), + Some(file_system_state) => FileSystemState::diff( + &file_system_state, + &new_file_system_state, + artifact_directory, + ), }; *file_system_state = Some(new_file_system_state); operations From 979e70d8e1a319cfd71a262f996f86a0f324e250 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Wed, 26 Nov 2025 17:35:08 -0300 Subject: [PATCH 26/28] improve variable names in diff --- .../artifact_content/src/file_system_state.rs | 90 ++++++++++--------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index fa047c74f..58adb7ed8 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -54,87 +54,95 @@ impl FileSystemState { pub fn diff(old: &Self, new: &Self, artifact_directory: &PathBuf) -> Vec { let mut operations: Vec = Vec::new(); - let mut new_server_objects = HashSet::new(); - let mut new_selectables = HashSet::new(); + let mut new_server_object_entity_name_set = HashSet::new(); + let mut new_selectable_set = HashSet::new(); - for (server_object, selectables) in &new.nested_files { - new_server_objects.insert(*server_object); - let server_object_path = artifact_directory.join(server_object); + for (new_server_object_entity_name, new_selectable_map) in &new.nested_files { + new_server_object_entity_name_set.insert(*new_server_object_entity_name); + let new_server_object_path = artifact_directory.join(*new_server_object_entity_name); - for (selectable, files) in selectables { - new_selectables.insert((*server_object, *selectable)); - let selectable_path = server_object_path.join(selectable); + let old_selectables_for_object = old.nested_files.get(new_server_object_entity_name); - let should_create_dir = old - .nested_files - .get(server_object) - .and_then(|s| s.get(selectable)) + for (new_selectable, new_files) in new_selectable_map { + new_selectable_set.insert((*new_server_object_entity_name, *new_selectable)); + let new_selectable_path = new_server_object_path.join(new_selectable); + + let should_create_dir = old_selectables_for_object + .and_then(|s| s.get(new_selectable)) .is_none(); if should_create_dir { operations.push(FileSystemOperation::CreateDirectory( - selectable_path.clone(), + new_selectable_path.clone(), )); } - for (file_name, (new_index, new_hash)) in files { - let file_path = selectable_path.join(file_name); + let old_files_for_selectable = + old_selectables_for_object.and_then(|s| s.get(new_selectable)); + + for (new_file_name, (new_index, new_hash)) in new_files { + let new_file_path = new_selectable_path.join(new_file_name); + + let old_file = old_files_for_selectable.and_then(|f| f.get(new_file_name)); - let should_write = old - .nested_files - .get(server_object) - .and_then(|s| s.get(selectable)) - .and_then(|f| f.get(file_name)) + let should_write = old_file .map(|(_, old_hash)| old_hash != new_hash) .unwrap_or(true); if should_write { - operations - .push(FileSystemOperation::WriteFile(file_path, new_index.clone())); + operations.push(FileSystemOperation::WriteFile( + new_file_path, + new_index.clone(), + )); } } } } - for (file_name, (new_index, new_hash)) in &new.root_files { - let file_path = artifact_directory.join(file_name); + for (new_file_name, (new_index, new_hash)) in &new.root_files { + let new_file_path = artifact_directory.join(new_file_name); let should_write = old .root_files - .get(file_name) + .get(new_file_name) .map(|(_, old_hash)| old_hash != new_hash) .unwrap_or(true); if should_write { - operations.push(FileSystemOperation::WriteFile(file_path, new_index.clone())); + operations.push(FileSystemOperation::WriteFile( + new_file_path, + new_index.clone(), + )); } } - for (server_object, selectables) in &old.nested_files { - let server_object_path = artifact_directory.join(server_object); + for (old_server_object_entity_name, old_selectable_map) in &old.nested_files { + let old_server_object_path = artifact_directory.join(*old_server_object_entity_name); - if !new_server_objects.contains(server_object) { - operations.push(FileSystemOperation::DeleteDirectory(server_object_path)); + if !new_server_object_entity_name_set.contains(old_server_object_entity_name) { + operations.push(FileSystemOperation::DeleteDirectory(old_server_object_path)); continue; } - for (selectable, files) in selectables { - let selectable_path = server_object_path.join(selectable); + for (old_selectable, old_files) in old_selectable_map { + let selectable_path = old_server_object_path.join(old_selectable); - if !new_selectables.contains(&(*server_object, *selectable)) { + if !new_selectable_set.contains(&(*old_server_object_entity_name, *old_selectable)) + { operations.push(FileSystemOperation::DeleteDirectory(selectable_path)); continue; } - for file_name in files.keys() { - let exist_in_new = new - .nested_files - .get(server_object) - .and_then(|s| s.get(selectable)) - .and_then(|f| f.get(file_name)) - .is_some(); + let new_files_for_selectable = new + .nested_files + .get(old_server_object_entity_name) + .and_then(|s| s.get(old_selectable)); + + for file_name in old_files.keys() { + let new_file = new_files_for_selectable.and_then(|f| f.get(file_name)); + let should_delete = new_file.is_none(); - if !exist_in_new { + if should_delete { let file_path = selectable_path.join(file_name); operations.push(FileSystemOperation::DeleteFile(file_path)); } From d9ed1a66408f79cfc81da7570b9d5b18b6a1afea Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Wed, 26 Nov 2025 18:56:21 -0300 Subject: [PATCH 27/28] a few more variable name improvements --- .../artifact_content/src/file_system_state.rs | 45 ++++++++++--------- .../isograph_compiler/src/write_artifacts.rs | 2 +- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index 58adb7ed8..a1935c88b 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -27,25 +27,31 @@ impl FileSystemState { artifact_directory.to_path_buf(), )); - for (server_object, selectables) in &state.nested_files { - let server_object_path = artifact_directory.join(server_object); + for (new_server_object_entity_name, new_selectable_map) in &state.nested_files { + let new_server_object_path = artifact_directory.join(new_server_object_entity_name); - for (selectable, files) in selectables { - let selectable_path = server_object_path.join(selectable); + for (new_selectable, new_files) in new_selectable_map { + let new_selectable_path = new_server_object_path.join(new_selectable); operations.push(FileSystemOperation::CreateDirectory( - selectable_path.clone(), + new_selectable_path.clone(), )); - for (file_name, (index, _)) in files { - let file_path = selectable_path.join(file_name); - operations.push(FileSystemOperation::WriteFile(file_path, index.clone())); + for (new_file_name, (new_index, _)) in new_files { + let new_file_path = new_selectable_path.join(new_file_name); + operations.push(FileSystemOperation::WriteFile( + new_file_path, + new_index.clone(), + )); } } } - for (file_name, (index, _)) in &state.root_files { - let file_path = artifact_directory.join(file_name); - operations.push(FileSystemOperation::WriteFile(file_path, index.clone())); + for (new_file_name, (new_index, _)) in &state.root_files { + let new_file_path = artifact_directory.join(new_file_name); + operations.push(FileSystemOperation::WriteFile( + new_file_path, + new_index.clone(), + )); } operations @@ -124,26 +130,25 @@ impl FileSystemState { continue; } + let new_selectable_map_for_object = new.nested_files.get(old_server_object_entity_name); + for (old_selectable, old_files) in old_selectable_map { - let selectable_path = old_server_object_path.join(old_selectable); + let old_selectable_path = old_server_object_path.join(old_selectable); if !new_selectable_set.contains(&(*old_server_object_entity_name, *old_selectable)) { - operations.push(FileSystemOperation::DeleteDirectory(selectable_path)); + operations.push(FileSystemOperation::DeleteDirectory(old_selectable_path)); continue; } - let new_files_for_selectable = new - .nested_files - .get(old_server_object_entity_name) - .and_then(|s| s.get(old_selectable)); + let new_files_for_selectable = + new_selectable_map_for_object.and_then(|s| s.get(old_selectable)); for file_name in old_files.keys() { let new_file = new_files_for_selectable.and_then(|f| f.get(file_name)); - let should_delete = new_file.is_none(); - if should_delete { - let file_path = selectable_path.join(file_name); + if new_file.is_none() { + let file_path = old_selectable_path.join(file_name); operations.push(FileSystemOperation::DeleteFile(file_path)); } } diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index 683bd309d..a1ecb650d 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -12,7 +12,7 @@ pub(crate) fn get_file_system_operations( artifact_directory: &PathBuf, file_system_state: &mut Option, ) -> Vec { - let new_file_system_state = FileSystemState::from(paths_and_contents); + let new_file_system_state = paths_and_contents.into(); let operations = match file_system_state { None => FileSystemState::recreate_all(&new_file_system_state, artifact_directory), Some(file_system_state) => FileSystemState::diff( From 75a8e99c58dc72e613395d7147e86de1f86f0459 Mon Sep 17 00:00:00 2001 From: Lucas Machado Date: Thu, 27 Nov 2025 13:49:01 -0300 Subject: [PATCH 28/28] some nit improvs --- .../artifact_content/src/file_system_state.rs | 21 ++++++++----------- .../isograph_compiler/src/write_artifacts.rs | 17 ++++++++------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/artifact_content/src/file_system_state.rs b/crates/artifact_content/src/file_system_state.rs index a1935c88b..03b127172 100644 --- a/crates/artifact_content/src/file_system_state.rs +++ b/crates/artifact_content/src/file_system_state.rs @@ -1,7 +1,7 @@ use pico::Index; use std::{ collections::{HashMap, HashSet}, - path::PathBuf, + path::Path, }; use crate::operation_text::hash; @@ -12,6 +12,7 @@ use common_lang_types::{ use isograph_config::PersistedDocumentsHashAlgorithm; #[derive(Debug, Clone, Default)] +#[expect(clippy::type_complexity)] pub struct FileSystemState { root_files: HashMap, ArtifactHash)>, nested_files: HashMap< @@ -21,7 +22,7 @@ pub struct FileSystemState { } impl FileSystemState { - pub fn recreate_all(state: &Self, artifact_directory: &PathBuf) -> Vec { + pub fn recreate_all(state: &Self, artifact_directory: &Path) -> Vec { let mut operations: Vec = Vec::new(); operations.push(FileSystemOperation::DeleteDirectory( artifact_directory.to_path_buf(), @@ -57,7 +58,7 @@ impl FileSystemState { operations } - pub fn diff(old: &Self, new: &Self, artifact_directory: &PathBuf) -> Vec { + pub fn diff(old: &Self, new: &Self, artifact_directory: &Path) -> Vec { let mut operations: Vec = Vec::new(); let mut new_server_object_entity_name_set = HashSet::new(); @@ -72,20 +73,15 @@ impl FileSystemState { for (new_selectable, new_files) in new_selectable_map { new_selectable_set.insert((*new_server_object_entity_name, *new_selectable)); let new_selectable_path = new_server_object_path.join(new_selectable); + let old_files_for_selectable = + old_selectables_for_object.and_then(|s| s.get(new_selectable)); - let should_create_dir = old_selectables_for_object - .and_then(|s| s.get(new_selectable)) - .is_none(); - - if should_create_dir { + if old_files_for_selectable.is_none() { operations.push(FileSystemOperation::CreateDirectory( new_selectable_path.clone(), )); } - let old_files_for_selectable = - old_selectables_for_object.and_then(|s| s.get(new_selectable)); - for (new_file_name, (new_index, new_hash)) in new_files { let new_file_path = new_selectable_path.join(new_file_name); @@ -166,6 +162,7 @@ impl FileSystemState { } } +#[expect(clippy::type_complexity)] impl From<&[ArtifactPathAndContent]> for FileSystemState { fn from(artifacts: &[ArtifactPathAndContent]) -> Self { let mut root_files = HashMap::new(); @@ -232,7 +229,7 @@ mod tests { ArtifactPathAndContent { artifact_path: ArtifactPath { type_and_field, - file_name: ArtifactFileName::from(file_name.intern()), + file_name: file_name.intern().into(), }, file_content: FileContent::from(content.to_string()), } diff --git a/crates/isograph_compiler/src/write_artifacts.rs b/crates/isograph_compiler/src/write_artifacts.rs index a1ecb650d..07fe7f88a 100644 --- a/crates/isograph_compiler/src/write_artifacts.rs +++ b/crates/isograph_compiler/src/write_artifacts.rs @@ -1,4 +1,7 @@ -use std::{fs, path::PathBuf}; +use std::{ + fs, + path::{Path, PathBuf}, +}; use common_lang_types::{ ArtifactPathAndContent, Diagnostic, DiagnosticResult, FileSystemOperation, @@ -9,14 +12,14 @@ use artifact_content::FileSystemState; #[tracing::instrument(skip_all)] pub(crate) fn get_file_system_operations( paths_and_contents: &[ArtifactPathAndContent], - artifact_directory: &PathBuf, + artifact_directory: &Path, file_system_state: &mut Option, ) -> Vec { let new_file_system_state = paths_and_contents.into(); let operations = match file_system_state { None => FileSystemState::recreate_all(&new_file_system_state, artifact_directory), Some(file_system_state) => FileSystemState::diff( - &file_system_state, + file_system_state, &new_file_system_state, artifact_directory, ), @@ -38,7 +41,7 @@ pub(crate) fn apply_file_system_operations( if path.exists() { fs::remove_dir_all(path.clone()).map_err(|e| { unable_to_do_something_at_path_diagnostic( - &path, + path, &e.to_string(), "delete directory", ) @@ -49,7 +52,7 @@ pub(crate) fn apply_file_system_operations( count += 1; fs::create_dir_all(path.clone()).map_err(|e| { unable_to_do_something_at_path_diagnostic( - &path, + path, &e.to_string(), "create directory", ) @@ -62,7 +65,7 @@ pub(crate) fn apply_file_system_operations( .file_content; fs::write(path.clone(), content.as_bytes()).map_err(|e| { unable_to_do_something_at_path_diagnostic( - &path, + path, &e.to_string(), "write contents of file", ) @@ -70,7 +73,7 @@ pub(crate) fn apply_file_system_operations( } FileSystemOperation::DeleteFile(path) => { fs::remove_file(path.clone()).map_err(|e| { - unable_to_do_something_at_path_diagnostic(&path, &e.to_string(), "delete file") + unable_to_do_something_at_path_diagnostic(path, &e.to_string(), "delete file") })?; } }