diff --git a/run_test.sh b/run_test.sh index 48b4b8d21..6f4af3594 100755 --- a/run_test.sh +++ b/run_test.sh @@ -1,18 +1,6 @@ #!/usr/bin/env bash set -e -rm -rf /tmp/hello-world -cd /tmp - -hc-scaffold example hello-world -cd hello-world - -nix develop --command bash -c " -set -e -npm i -npm t -" - rm -rf /tmp/forum-svelte cd /tmp @@ -27,18 +15,18 @@ hc-scaffold dna forum hc-scaffold zome posts --integrity dnas/forum/zomes/integrity/ --coordinator dnas/forum/zomes/coordinator/ hc-scaffold entry-type post --reference-entry-hash false --crud crud --link-from-original-to-each-update true --fields title:String:TextField,content:String:TextArea hc-scaffold entry-type comment --reference-entry-hash false --crud crud --link-from-original-to-each-update false --fields post_hash:ActionHash::Post -hc-scaffold entry-type likes --reference-entry-hash false --crud crd --fields like_hash:Option\::Likes,string_list:Vec\ +hc-scaffold entry-type like --reference-entry-hash false --crud crd --fields like_hash:Option\::Like,string_list:Vec\ hc-scaffold entry-type certificate --reference-entry-hash true --crud cr --fields post_hash:ActionHash::Post,agent:AgentPubKey::certified,certifications_hashes:Vec\::Certificate,certificate_type:Enum::CertificateType:TypeOne.TypeTwo,dna_hash:DnaHash hc-scaffold collection global all_posts post hc-scaffold collection by-author posts_by_author post hc-scaffold collection global all_posts_entry_hash post:EntryHash -hc-scaffold collection global all_likes likes +hc-scaffold collection global all_likes like hc-scaffold collection by-author posts_by_author_entry_hash post:EntryHash -hc-scaffold link-type post likes --delete true --bidireccional false -hc-scaffold link-type comment likes:EntryHash --delete true --bidireccional true -hc-scaffold link-type certificate:EntryHash likes --delete false --bidireccional false +hc-scaffold link-type post like --delete true --bidireccional false +hc-scaffold link-type comment like:EntryHash --delete true --bidireccional true +hc-scaffold link-type certificate:EntryHash like --delete false --bidireccional false hc-scaffold link-type agent:creator post:EntryHash --delete false --bidireccional true @@ -60,18 +48,18 @@ hc-scaffold dna forum hc-scaffold zome posts --integrity dnas/forum/zomes/integrity/ --coordinator dnas/forum/zomes/coordinator/ hc-scaffold entry-type post --reference-entry-hash false --crud crud --link-from-original-to-each-update true --fields title:String:TextField,content:String:TextArea hc-scaffold entry-type comment --reference-entry-hash false --crud crud --link-from-original-to-each-update false --fields post_hash:ActionHash::Post -hc-scaffold entry-type likes --reference-entry-hash false --crud crd --fields like_hash:Option\::Likes,string_list:Vec\ +hc-scaffold entry-type like --reference-entry-hash false --crud crd --fields like_hash:Option\::Like,string_list:Vec\ hc-scaffold entry-type certificate --reference-entry-hash true --crud cr --fields post_hash:ActionHash::Post,agent:AgentPubKey::certified,certifications_hashes:Vec\::Certificate,certificate_type:Enum::CertificateType:TypeOne.TypeTwo,dna_hash:DnaHash hc-scaffold collection global all_posts post hc-scaffold collection by-author posts_by_author post -hc-scaffold collection global all_likes likes +hc-scaffold collection global all_likes like hc-scaffold collection global all_posts_entry_hash post:EntryHash hc-scaffold collection by-author posts_by_author_entry_hash post:EntryHash -hc-scaffold link-type post likes --delete true --bidireccional false -hc-scaffold link-type comment likes:EntryHash --delete true --bidireccional true -hc-scaffold link-type certificate:EntryHash likes --delete false --bidireccional false +hc-scaffold link-type post like --delete true --bidireccional false +hc-scaffold link-type comment like:EntryHash --delete true --bidireccional true +hc-scaffold link-type certificate:EntryHash like --delete false --bidireccional false hc-scaffold link-type agent:creator post:EntryHash --delete false --bidireccional true nix develop --command bash -c " @@ -92,18 +80,18 @@ hc-scaffold dna forum hc-scaffold zome posts --integrity dnas/forum/zomes/integrity/ --coordinator dnas/forum/zomes/coordinator/ hc-scaffold entry-type post --reference-entry-hash false --crud crud --link-from-original-to-each-update true --fields title:String:TextField,content:String:TextArea hc-scaffold entry-type comment --reference-entry-hash false --crud crud --link-from-original-to-each-update false --fields post_hash:ActionHash::Post -hc-scaffold entry-type likes --reference-entry-hash false --crud crd --fields like_hash:Option\::Likes,string_list:Vec\ +hc-scaffold entry-type like --reference-entry-hash false --crud crd --fields like_hash:Option\::Like,string_list:Vec\ hc-scaffold entry-type certificate --reference-entry-hash true --crud cr --fields post_hash:ActionHash::Post,agent:AgentPubKey::certified,certifications_hashes:Vec\::Certificate,certificate_type:Enum::CertificateType:TypeOne.TypeTwo,dna_hash:DnaHash hc-scaffold collection global all_posts post hc-scaffold collection by-author posts_by_author post hc-scaffold collection global all_posts_entry_hash post:EntryHash hc-scaffold collection by-author posts_by_author_entry_hash post:EntryHash -hc-scaffold collection global all_likes likes +hc-scaffold collection global all_likes like -hc-scaffold link-type post likes --delete true --bidireccional false -hc-scaffold link-type comment likes:EntryHash --delete true --bidireccional true -hc-scaffold link-type certificate:EntryHash likes --delete false --bidireccional false +hc-scaffold link-type post like --delete true --bidireccional false +hc-scaffold link-type comment like:EntryHash --delete true --bidireccional true +hc-scaffold link-type certificate:EntryHash like --delete false --bidireccional false hc-scaffold link-type agent:creator post:EntryHash --delete false --bidireccional true nix develop --command bash -c " @@ -127,18 +115,18 @@ hc-scaffold dna forum hc-scaffold zome posts --integrity dnas/forum/zomes/integrity/ --coordinator dnas/forum/zomes/coordinator/ hc-scaffold entry-type post --reference-entry-hash false --crud crud --link-from-original-to-each-update true --fields title:String:TextField,content:String:TextArea hc-scaffold entry-type comment --reference-entry-hash false --crud crud --link-from-original-to-each-update false --fields post_hash:ActionHash::Post -hc-scaffold entry-type likes --reference-entry-hash false --crud crd --fields like_hash:Option\::Likes,string_list:Vec\ +hc-scaffold entry-type like --reference-entry-hash false --crud crd --fields like_hash:Option\::Like,string_list:Vec\ hc-scaffold entry-type certificate --reference-entry-hash true --crud cr --fields post_hash:ActionHash::Post,agent:AgentPubKey::certified,certifications_hashes:Vec\::Certificate,certificate_type:Enum::CertificateType:TypeOne.TypeTwo,dna_hash:DnaHash hc-scaffold collection global all_posts post hc-scaffold collection by-author posts_by_author post hc-scaffold collection global all_posts_entry_hash post:EntryHash -hc-scaffold collection global all_likes likes +hc-scaffold collection global all_likes like hc-scaffold collection by-author posts_by_author_entry_hash post:EntryHash -hc-scaffold link-type post likes --delete true --bidireccional false -hc-scaffold link-type comment likes:EntryHash --delete true --bidireccional true -hc-scaffold link-type certificate:EntryHash likes --delete false --bidireccional false +hc-scaffold link-type post like --delete true --bidireccional false +hc-scaffold link-type comment like:EntryHash --delete true --bidireccional true +hc-scaffold link-type certificate:EntryHash like --delete false --bidireccional false hc-scaffold link-type agent:creator post:EntryHash --delete false --bidireccional true @@ -148,3 +136,15 @@ npm i npm t npm run package " + +rm -rf /tmp/hello-world +cd /tmp + +hc-scaffold example hello-world +cd hello-world + +nix develop --command bash -c " +set -e +npm i +npm t +" diff --git a/src/scaffold/collection.rs b/src/scaffold/collection.rs index 7542bd711..a431b26a8 100644 --- a/src/scaffold/collection.rs +++ b/src/scaffold/collection.rs @@ -112,11 +112,11 @@ pub fn scaffold_collection( &link_type_name, &None, &Some(Referenceable::EntryType(entry_type.clone())), - false, + true, &PathBuf::from(format!("{}.rs", entry_type.entry_type.to_case(Case::Snake))), )?; - let (dna_file_tree, coordinator_zome) = add_collection_to_coordinators( + let (dna_file_tree, coordinator_zome, deletable) = add_collection_to_coordinators( zome_file_tree, collection_name, &link_type_name, @@ -139,5 +139,6 @@ pub fn scaffold_collection( &collection_type, collection_name, &entry_type, + deletable, ) } diff --git a/src/scaffold/collection/coordinator.rs b/src/scaffold/collection/coordinator.rs index b920d8f0d..e9b85837d 100644 --- a/src/scaffold/collection/coordinator.rs +++ b/src/scaffold/collection/coordinator.rs @@ -11,8 +11,9 @@ use crate::{ dna::DnaFileTree, entry_type::definitions::EntryTypeReference, zome::{ - coordinator::find_extern_function_or_choose, - utils::get_coordinator_zomes_for_integrity, ZomeFileTree, + coordinator::{find_extern_function_in_zomes, find_extern_function_or_choose}, + utils::get_coordinator_zomes_for_integrity, + ZomeFileTree, }, }, }; @@ -23,12 +24,7 @@ fn global_collection_getter( integrity_zome_name: &String, collection_name: &String, link_type_name: &String, - entry_type_reference: &EntryTypeReference, ) -> String { - let snake_to_hash_type = entry_type_reference - .hash_type() - .to_string() - .to_case(Case::Snake); let snake_collection_name = collection_name.to_case(Case::Snake); format!( @@ -36,28 +32,10 @@ fn global_collection_getter( use {integrity_zome_name}::*; #[hdk_extern] -pub fn get_{snake_collection_name}(_: ()) -> ExternResult> {{ +pub fn get_{snake_collection_name}(_: ()) -> ExternResult> {{ let path = Path::from("{snake_collection_name}"); - let links = get_links(path.path_entry_hash()?, LinkTypes::{link_type_name}, None)?; - - let get_input: Vec = links - .into_iter() - .map(|link| Ok(GetInput::new( - link.target.into_{snake_to_hash_type}().ok_or(wasm_error!(WasmErrorInner::Guest(String::from("No action hash associated with link"))))?.into(), - GetOptions::default(), - ))) - .collect::>>()?; - - // Get the records to filter out the deleted ones - let records = HDK.with(|hdk| hdk.borrow().get(get_input))?; - - let records: Vec = records - .into_iter() - .filter_map(|r| r) - .collect(); - - Ok(records) + get_links(path.path_entry_hash()?, LinkTypes::{link_type_name}, None) }} "#, ) @@ -67,38 +45,14 @@ fn by_author_collection_getter( integrity_zome_name: &String, collection_name: &String, link_type_name: &String, - entry_type_reference: &EntryTypeReference, ) -> String { - let snake_to_hash_type = entry_type_reference - .hash_type() - .to_string() - .to_case(Case::Snake); - format!( r#"use hdk::prelude::*; use {integrity_zome_name}::*; #[hdk_extern] -pub fn get_{collection_name}(author: AgentPubKey) -> ExternResult> {{ - let links = get_links(author, LinkTypes::{link_type_name}, None)?; - - let get_input: Vec = links - .into_iter() - .map(|link| Ok(GetInput::new( - link.target.into_{snake_to_hash_type}().ok_or(wasm_error!(WasmErrorInner::Guest(String::from("No action hash associated with link"))))?.into(), - GetOptions::default(), - ))) - .collect::>>()?; - - // Get the records to filter out the deleted ones - let records = HDK.with(|hdk| hdk.borrow().get(get_input))?; - - let records: Vec = records - .into_iter() - .filter_map(|r| r) - .collect(); - - Ok(records) +pub fn get_{collection_name}(author: AgentPubKey) -> ExternResult> {{ + get_links(author, LinkTypes::{link_type_name}, None) }} "#, ) @@ -222,13 +176,141 @@ fn add_create_link_in_create_function( Ok(dna_file_tree) } +fn add_delete_link_in_delete_function( + dna_file_tree: DnaFileTree, + coordinator_zomes_for_integrity: &Vec, + collection_name: &String, + link_type_name: &String, + collection_type: &CollectionType, + entry_type_reference: &EntryTypeReference, +) -> ScaffoldResult<(DnaFileTree, bool)> { + let dna_manifest_path = dna_file_tree.dna_manifest_path.clone(); + + let Some((chosen_coordinator_zome, fn_name)) = find_extern_function_in_zomes( + &dna_file_tree, + coordinator_zomes_for_integrity, + &format!( + "delete_{}", + entry_type_reference.entry_type.to_case(Case::Snake) + ), + )? else { + return Ok((dna_file_tree, false)); + }; + + let zome_file_tree = ZomeFileTree::from_zome_manifest(dna_file_tree, chosen_coordinator_zome)?; + + let snake_case_entry_type = entry_type_reference.entry_type.to_case(Case::Snake); + + let target_hash_variable = match entry_type_reference.reference_entry_hash { + true => format!( + r#"record.action().entry_hash().ok_or(wasm_error!(WasmErrorInner::Guest("Record does not have an entry".to_string())))?"# + ), + false => format!("&original_{snake_case_entry_type}_hash"), + }; + let into_hash_fn = match entry_type_reference.reference_entry_hash { + true => format!(r#"into_entry_hash()"#), + false => format!("into_action_hash()"), + }; + + let mut delete_link_stmts: Vec = vec![]; + match collection_type { + CollectionType::Global => { + delete_link_stmts.push(format!(r#"let path = Path::from("{}");"#, collection_name)); + delete_link_stmts.push(format!( + r#"let links = get_links(path.path_entry_hash()?, LinkTypes::{link_type_name}, None)?;"#, + )); + delete_link_stmts.push(format!( + r#"for link in links {{ + if let Some(hash) = link.target.{into_hash_fn} {{ + if hash.eq({target_hash_variable}) {{ + delete_link(link.create_link_hash)?; + }} + }} + }}"#, + )); + } + CollectionType::ByAuthor => { + delete_link_stmts.push(format!( + r#"let links = get_links(record.action().author().clone(), LinkTypes::{link_type_name}, None)?;"#, + )); + delete_link_stmts.push(format!( + r#"for link in links {{ + if let Some(hash) = link.target.{into_hash_fn} {{ + if hash.eq({target_hash_variable}) {{ + delete_link(link.create_link_hash)?; + }} + }} + }}"#, + )); + } + }; + + let stmts = delete_link_stmts + .into_iter() + .map(|s| syn::parse_str::(s.as_str())) + .collect::, syn::Error>>()?; + + let crate_src_path = zome_file_tree.zome_crate_path.join("src"); + + let mut file_tree = zome_file_tree.dna_file_tree.file_tree(); + + let v: Vec = crate_src_path + .clone() + .iter() + .map(|s| s.to_os_string()) + .collect(); + map_rust_files( + file_tree + .path_mut(&mut v.iter()) + .ok_or(ScaffoldError::PathNotFound(crate_src_path.clone()))?, + |_file_path, mut file| { + file.items = file + .items + .into_iter() + .map(|i| { + if let syn::Item::Fn(mut item_fn) = i.clone() { + if item_fn + .attrs + .iter() + .any(|a| a.path().segments.iter().any(|s| s.ident.eq("hdk_extern"))) + && item_fn.sig.ident.eq(&fn_name.sig.ident) + { + for new_stmt in stmts.clone() { + item_fn + .block + .stmts + .insert(item_fn.block.stmts.len() - 1, new_stmt); + } + return syn::Item::Fn(item_fn); + } + } + + i + }) + .collect(); + + Ok(file) + }, + ) + .map_err(|e| match e { + ScaffoldError::MalformedFile(path, error) => { + ScaffoldError::MalformedFile(crate_src_path.join(&path), error) + } + _ => e, + })?; + + let dna_file_tree = DnaFileTree::from_dna_manifest_path(file_tree, &dna_manifest_path)?; + + Ok((dna_file_tree, true)) +} + pub fn add_collection_to_coordinators( integrity_zome_file_tree: ZomeFileTree, collection_name: &String, link_type_name: &String, collection_type: &CollectionType, entry_type: &EntryTypeReference, -) -> ScaffoldResult<(DnaFileTree, ZomeManifest)> { +) -> ScaffoldResult<(DnaFileTree, ZomeManifest, bool)> { let integrity_zome_name = integrity_zome_file_tree.zome_manifest.name.0.to_string(); let dna_manifest_path = integrity_zome_file_tree .dna_file_tree @@ -273,18 +355,12 @@ pub fn add_collection_to_coordinators( let snake_link_type_name = collection_name.to_case(Case::Snake); let getter = match collection_type { - CollectionType::Global => global_collection_getter( - &integrity_zome_name, - collection_name, - link_type_name, - entry_type, - ), - CollectionType::ByAuthor => by_author_collection_getter( - &integrity_zome_name, - collection_name, - link_type_name, - entry_type, - ), + CollectionType::Global => { + global_collection_getter(&integrity_zome_name, collection_name, link_type_name) + } + CollectionType::ByAuthor => { + by_author_collection_getter(&integrity_zome_name, collection_name, link_type_name) + } }; let mut file_tree = zome_file_tree.dna_file_tree.file_tree(); @@ -317,5 +393,14 @@ pub fn add_collection_to_coordinators( entry_type, )?; - Ok((dna_file_tree, coordinator_zome)) + let (dna_file_tree, deletable) = add_delete_link_in_delete_function( + dna_file_tree, + &coordinator_zomes_for_integrity, + collection_name, + link_type_name, + collection_type, + entry_type, + )?; + + Ok((dna_file_tree, coordinator_zome, deletable)) } diff --git a/src/scaffold/entry_type.rs b/src/scaffold/entry_type.rs index 4fc148bf7..3dc2c4c73 100644 --- a/src/scaffold/entry_type.rs +++ b/src/scaffold/entry_type.rs @@ -166,7 +166,7 @@ pub fn scaffold_entry_type( &link_type_name(&l, &entry_def.referenceable()), &Some(l), &Some(entry_def.referenceable()), - false, + crud.delete, &PathBuf::from(format!("{}.rs", entry_def.name.to_case(Case::Snake))), )?; } diff --git a/src/scaffold/entry_type/coordinator.rs b/src/scaffold/entry_type/coordinator.rs index 5acdffc34..5678257cd 100644 --- a/src/scaffold/entry_type/coordinator.rs +++ b/src/scaffold/entry_type/coordinator.rs @@ -7,6 +7,7 @@ use crate::{ file_tree::{insert_file, map_file, map_rust_files}, scaffold::{ dna::DnaFileTree, + entry_type::definitions::FieldDefinition, link_type::{coordinator::get_links_handler, link_type_name}, zome::ZomeFileTree, }, @@ -346,11 +347,105 @@ pub fn update_{}(input: Update{}Input) -> ExternResult {{ ) } -pub fn delete_handler(entry_def_name: &String) -> String { - let snake_entry_def_name = entry_def_name.to_case(Case::Snake); +pub fn delete_handler(entry_def: &EntryDefinition) -> String { + let pascal_entry_def_name = entry_def.name.to_case(Case::Pascal); + let snake_entry_def_name = entry_def.name.to_case(Case::Snake); + + let linked_from_fields: Vec = entry_def + .fields + .iter() + .cloned() + .filter(|field| field.linked_from.is_some()) + .collect(); + + let delete_depending_links = match linked_from_fields.is_empty() { + true => format!( + r#" + let details = get_details(original_{snake_entry_def_name}_hash.clone(), GetOptions::default())? + .ok_or(wasm_error!(WasmErrorInner::Guest(String::from("{{pascal_entry_def_name}} not found"))))?; + let record = match details {{ + Details::Record(details) => Ok(details.record), + _ => Err(wasm_error!(WasmErrorInner::Guest(String::from( + "Malformed get details response" + )))), + }}?; + "# + ), + false => { + let mut delete_links = format!( + r#" + let details = get_details(original_{snake_entry_def_name}_hash.clone(), GetOptions::default())? + .ok_or(wasm_error!(WasmErrorInner::Guest(String::from("{{pascal_entry_def_name}} not found"))))?; + let record = match details {{ + Details::Record(details) => Ok(details.record), + _ => Err(wasm_error!(WasmErrorInner::Guest(String::from( + "Malformed get details response" + )))), + }}?; + let entry = record.entry().as_option().ok_or(wasm_error!(WasmErrorInner::Guest(String::from( + "{pascal_entry_def_name} record has no entry" + ))))?; + let {snake_entry_def_name} = {pascal_entry_def_name}::try_from(entry)?; + + "# + ); + for linked_from_field in linked_from_fields { + let linked_from = linked_from_field + .linked_from + .expect("Linked from is none after we filtered for some"); + let field_name = linked_from_field.field_name; + let link_type = link_type_name(&linked_from, &entry_def.referenceable()); + let delete_this_link = match linked_from_field.cardinality { + Cardinality::Single => format!( + r#" + let links = get_links({snake_entry_def_name}.{field_name}.clone(), LinkTypes::{link_type}, None)?; + for link in links {{ + if let Some(action_hash) = link.target.into_action_hash() {{ + if action_hash.eq(&original_{snake_entry_def_name}_hash) {{ + delete_link(link.create_link_hash)?; + }} + }} + }} + "# + ), + Cardinality::Option => format!( + r#" + if let Some(base_address) = {snake_entry_def_name}.{field_name}.clone() {{ + let links = get_links(base_address, LinkTypes::{link_type}, None)?; + for link in links {{ + if let Some(action_hash) = link.target.into_action_hash() {{ + if action_hash.eq(&original_{snake_entry_def_name}_hash) {{ + delete_link(link.create_link_hash)?; + }} + }} + }} + }} + "# + ), + Cardinality::Vector => format!( + r#" + for base_address in {snake_entry_def_name}.{field_name} {{ + let links = get_links(base_address.clone(), LinkTypes::{link_type}, None)?; + for link in links {{ + if let Some(action_hash) = link.target.into_action_hash() {{ + if action_hash.eq(&original_{snake_entry_def_name}_hash) {{ + delete_link(link.create_link_hash)?; + }} + }} + }} + }} + "# + ), + }; + delete_links.push_str(delete_this_link.as_str()); + } + delete_links + } + }; format!( r#"#[hdk_extern] pub fn delete_{snake_entry_def_name}(original_{snake_entry_def_name}_hash: ActionHash) -> ExternResult {{ + {delete_depending_links} delete_entry(original_{snake_entry_def_name}_hash) }} @@ -416,12 +511,14 @@ use {}::*; .push_str(update_handler(&entry_def.name, link_from_original_to_each_update).as_str()); } if crud.delete { - initial.push_str(delete_handler(&entry_def.name).as_str()); + initial.push_str(delete_handler(&entry_def).as_str()); } for f in &entry_def.fields { if let Some(linked_from) = &f.linked_from { - initial.push_str(get_links_handler(&linked_from, &entry_def.referenceable()).as_str()); + initial.push_str( + get_links_handler(&linked_from, &entry_def.referenceable(), crud.delete).as_str(), + ); } } diff --git a/src/scaffold/entry_type/integrity.rs b/src/scaffold/entry_type/integrity.rs index ce0928120..4e871412f 100644 --- a/src/scaffold/entry_type/integrity.rs +++ b/src/scaffold/entry_type/integrity.rs @@ -487,7 +487,7 @@ pub fn get_all_entry_types( let referenced_by_entry_hash = match find_extern_function_in_zomes( &zome_file_tree.dna_file_tree, &coordinators_for_zome, - &format!("read_{}", v), + &format!("get_{}", v.to_case(Case::Snake)), )? { Some((_z, item_fn)) => { match item_fn diff --git a/src/scaffold/link_type/coordinator.rs b/src/scaffold/link_type/coordinator.rs index 83740eff0..d593f9ca1 100644 --- a/src/scaffold/link_type/coordinator.rs +++ b/src/scaffold/link_type/coordinator.rs @@ -117,18 +117,20 @@ pub fn add_{singular_snake_to_entry_type}_for_{singular_snake_from_entry_type}(i pub fn get_links_handler( from_referenceable: &Referenceable, to_referenceable: &Referenceable, + delete: bool, ) -> String { match to_referenceable { Referenceable::Agent { .. } => { - get_links_handler_to_agent(from_referenceable, to_referenceable) + get_links_handler_to_agent(from_referenceable, to_referenceable, delete) } - Referenceable::EntryType(e) => get_links_handler_to_entry(from_referenceable, e), + Referenceable::EntryType(e) => get_links_handler_to_entry(from_referenceable, e, delete), } } fn get_links_handler_to_agent( from_referenceable: &Referenceable, to_referenceable: &Referenceable, + delete: bool, ) -> String { let from_hash_type = from_referenceable.hash_type().to_string(); let from_arg_name = from_referenceable.field_name(&Cardinality::Single); @@ -141,29 +143,42 @@ fn get_links_handler_to_agent( .to_string(&Cardinality::Vector) .to_case(Case::Snake); - format!( - r#"#[hdk_extern] -pub fn get_{plural_snake_to_entry_type}_for_{singular_snake_from_entry_type}({from_arg_name}: {from_hash_type}) -> ExternResult> {{ - let links = get_links({from_arg_name}, LinkTypes::{pascal_link_type_name}, None)?; - - let agents: Vec = links + let get_deleted_links_handler = if delete { + format!( + r#" +#[hdk_extern] +pub fn get_deleted_{plural_snake_to_entry_type}_for_{singular_snake_from_entry_type}( + {from_arg_name}: {from_hash_type}, +) -> ExternResult)>> {{ + let details = get_link_details({from_arg_name}, LinkTypes::{pascal_link_type_name}, None)?; + Ok(details + .into_inner() .into_iter() - .map(|link| Ok(AgentPubKey::from(link.target.into_entry_hash().ok_or(wasm_error!(WasmErrorInner::Guest(String::from("No entry hash associated with link"))))?))) - .collect::>>()?; + .filter(|(_link, deletes)| deletes.len() > 0) + .collect()) +}}"# + ) + } else { + format!("") + }; - Ok(agents) -}}"#, + format!( + r#"#[hdk_extern] +pub fn get_{plural_snake_to_entry_type}_for_{singular_snake_from_entry_type}({from_arg_name}: {from_hash_type}) -> ExternResult> {{ + get_links({from_arg_name}, LinkTypes::{pascal_link_type_name}, None) +}} +{get_deleted_links_handler} +"#, ) } fn get_links_handler_to_entry( from_referenceable: &Referenceable, to_entry_type: &EntryTypeReference, + delete: bool, ) -> String { let from_hash_type = from_referenceable.hash_type().to_string(); let from_arg_name = from_referenceable.field_name(&Cardinality::Single); - // let to_hash_type = to_entry_type.hash_type().to_string(); - let snake_to_hash_type = to_entry_type.hash_type().to_string().to_case(Case::Snake); let pascal_link_type_name = link_type_name( from_referenceable, @@ -176,27 +191,31 @@ fn get_links_handler_to_entry( .to_string(&Cardinality::Vector) .to_case(Case::Snake); - format!( - r#"#[hdk_extern] -pub fn get_{plural_snake_to_entry_type}_for_{singular_snake_from_entry_type}({from_arg_name}: {from_hash_type}) -> ExternResult> {{ - let links = get_links({from_arg_name}, LinkTypes::{pascal_link_type_name}, None)?; - - let get_input: Vec = links - .into_iter() - .map(|link| Ok(GetInput::new( - link.target.into_{snake_to_hash_type}().ok_or(wasm_error!(WasmErrorInner::Guest(String::from("No action hash associated with link"))))?.into(), - GetOptions::default(), - ))) - .collect::>>()?; - - // Get the records to filter out the deleted ones - let records: Vec = HDK.with(|hdk| hdk.borrow().get(get_input))? + let get_deleted_links_handler = match delete { + true => format!( + r#" +#[hdk_extern] +pub fn get_deleted_{plural_snake_to_entry_type}_for_{singular_snake_from_entry_type}( + {from_arg_name}: {from_hash_type}, +) -> ExternResult)>> {{ + let details = get_link_details({from_arg_name}, LinkTypes::{pascal_link_type_name}, None)?; + Ok(details + .into_inner() .into_iter() - .filter_map(|r| r) - .collect(); + .filter(|(_link, deletes)| deletes.len() > 0) + .collect()) +}}"# + ), + false => format!(""), + }; - Ok(records) -}}"#, + format!( + r#"#[hdk_extern] +pub fn get_{plural_snake_to_entry_type}_for_{singular_snake_from_entry_type}({from_arg_name}: {from_hash_type}) -> ExternResult> {{ + get_links({from_arg_name}, LinkTypes::{pascal_link_type_name}, None) +}} +{get_deleted_links_handler} +"#, ) } @@ -210,7 +229,6 @@ fn from_link_hash_type(hash_type: &String) -> String { } } - // Event to calendar fn remove_link_handlers( from_referenceable: &Referenceable, @@ -236,6 +254,12 @@ fn remove_link_handlers( let singular_snake_to_entry_type = to_referenceable .to_string(&Cardinality::Single) .to_case(Case::Snake); + let plural_snake_to_entry_type = to_referenceable + .to_string(&Cardinality::Vector) + .to_case(Case::Snake); + let plural_snake_from_entry_type = from_referenceable + .to_string(&Cardinality::Vector) + .to_case(Case::Snake); let from_link = from_link_hash_type(&to_hash_type); let from_inverse = from_link_hash_type(&from_hash_type); @@ -253,6 +277,7 @@ fn remove_link_handlers( ), false => format!(""), }; + format!( r#"#[derive(Serialize, Deserialize, Debug)] pub struct Remove{singular_pascal_to_entry_type}For{singular_pascal_from_entry_type}Input {{ @@ -271,7 +296,8 @@ pub fn remove_{singular_snake_to_entry_type}_for_{singular_snake_from_entry_type {bidireccional_remove} Ok(()) -}}"# +}} +"# ) } @@ -287,7 +313,7 @@ fn normal_handlers( r#" {}"#, - get_links_handler(to_referenceable, from_referenceable) + get_links_handler(to_referenceable, from_referenceable, delete) ), false => format!(""), }; @@ -308,7 +334,7 @@ use {integrity_zome_name}::*; {}"#, add_link_handler(from_referenceable, to_referenceable, bidireccional), - get_links_handler(from_referenceable, to_referenceable), + get_links_handler(from_referenceable, to_referenceable, delete), inverse_get, delete_link_handler ) @@ -366,6 +392,7 @@ pub fn add_link_type_functions_to_coordinator( })?; let dna_file_tree = DnaFileTree::from_dna_manifest_path(file_tree, &dna_manifest_path)?; + let zome_file_tree = ZomeFileTree::from_zome_manifest(dna_file_tree, zome_manifest)?; Ok(zome_file_tree) diff --git a/src/scaffold/link_type/integrity.rs b/src/scaffold/link_type/integrity.rs index 95ac61445..5887e35f3 100644 --- a/src/scaffold/link_type/integrity.rs +++ b/src/scaffold/link_type/integrity.rs @@ -425,6 +425,7 @@ fn signal_link_types_variants() -> ScaffoldResult> { syn::parse_str::( "LinkDeleted { action: SignedActionHashed, + create_link_action: SignedActionHashed, link_type: LinkTypes, }", )?, @@ -455,7 +456,7 @@ fn signal_action_match_arms() -> ScaffoldResult> { if let Ok(Some(link_type)) = LinkTypes::from_type(create_link.zome_index, create_link.link_type) { - emit_signal(Signal::LinkDeleted { action, link_type })?; + emit_signal(Signal::LinkDeleted { action, link_type, create_link_action: record.signed_action.clone() })?; } Ok(()) } diff --git a/src/scaffold/zome/coordinator.rs b/src/scaffold/zome/coordinator.rs index 897a40d08..535d5c643 100644 --- a/src/scaffold/zome/coordinator.rs +++ b/src/scaffold/zome/coordinator.rs @@ -211,21 +211,25 @@ pub fn find_all_extern_functions(zome_file_tree: &ZomeFileTree) -> ScaffoldResul .path(&mut v.iter()) .ok_or(ScaffoldError::PathNotFound(crate_src_path.clone()))?, &|_file_path, rust_file| { - rust_file.items.iter().find_map(|i| { - if let syn::Item::Fn(item_fn) = i.clone() { - if item_fn - .attrs - .iter() - .any(|a| a.path().segments.iter().any(|s| s.ident.eq("hdk_extern"))) - { - return Some(item_fn); + let extern_functions: Vec = rust_file + .items + .iter() + .filter_map(|i| { + if let syn::Item::Fn(item_fn) = i.clone() { + if item_fn + .attrs + .iter() + .any(|a| a.path().segments.iter().any(|s| s.ident.eq("hdk_extern"))) + { + return Some(item_fn); + } } - } - - None - }) + None + }) + .collect(); + Some(extern_functions) }, ); - Ok(hdk_extern_instances.values().cloned().collect()) + Ok(hdk_extern_instances.values().cloned().flatten().collect()) } diff --git a/src/templates.rs b/src/templates.rs index 644097606..1b27c0eff 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -178,6 +178,7 @@ impl HelperDef for MergeScope { Ok(()) } } + pub fn register_merge_scope<'a>(mut h: Handlebars<'a>) -> Handlebars<'a> { h.register_helper("merge_scope", Box::new(MergeScope)); diff --git a/src/templates/collection.rs b/src/templates/collection.rs index bc6101c0d..11355732c 100644 --- a/src/templates/collection.rs +++ b/src/templates/collection.rs @@ -24,6 +24,7 @@ pub struct ScaffoldCollectionData { pub collection_type: CollectionType, pub collection_name: String, pub referenceable: Referenceable, + pub deletable: bool, } pub fn scaffold_collection_templates( mut app_file_tree: FileTree, @@ -34,6 +35,7 @@ pub fn scaffold_collection_templates( collection_type: &CollectionType, collection_name: &String, entry_type_reference: &EntryTypeReference, + deletable: bool, ) -> ScaffoldResult { let data = ScaffoldCollectionData { app_name: app_name.clone(), @@ -42,6 +44,7 @@ pub fn scaffold_collection_templates( collection_name: collection_name.clone(), collection_type: collection_type.clone(), referenceable: Referenceable::EntryType(entry_type_reference.clone()), + deletable, }; let h = build_handlebars(&template_file_tree)?; diff --git a/src/versions.rs b/src/versions.rs index 86c8cbdad..d7c5e1b34 100644 --- a/src/versions.rs +++ b/src/versions.rs @@ -1,9 +1,9 @@ pub fn tryorama_version() -> String { - String::from("^0.15.0-rc.1") + String::from("^0.15.2") } pub fn holochain_client_version() -> String { - String::from("^0.15.0") + String::from("^0.16.5") } pub fn web_sdk_version() -> String { diff --git a/templates/lit/collection/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case collection_name}}.test.ts.hbs b/templates/lit/collection/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case collection_name}}.test.ts.hbs index 955845709..2312204b3 100644 --- a/templates/lit/collection/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case collection_name}}.test.ts.hbs +++ b/templates/lit/collection/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case collection_name}}.test.ts.hbs @@ -24,7 +24,7 @@ test('create a {{pascal_case referenceable.name}} and get {{lower_case collectio await scenario.shareAllAgents(); // Bob gets {{lower_case collection_name}} - let collectionOutput: Record[] = await bob.cells[0].callZome({ + let collectionOutput: Link[] = await bob.cells[0].callZome({ zome_name: "{{coordinator_zome_manifest.name}}", fn_name: "get_{{snake_case collection_name}}", payload: {{#if (eq collection_type.type "Global")}}null{{else}}alice.agentPubKey{{/if}} @@ -32,8 +32,8 @@ test('create a {{pascal_case referenceable.name}} and get {{lower_case collectio assert.equal(collectionOutput.length, 0); // Alice creates a {{pascal_case referenceable.name}} - const createdRecord: Record = await create{{pascal_case referenceable.name}}(alice.cells[0]); - assert.ok(createdRecord); + const createRecord: Record = await create{{pascal_case referenceable.name}}(alice.cells[0]); + assert.ok(createRecord); await dhtSync([alice, bob], alice.cells[0].cell_id[0]); @@ -44,7 +44,26 @@ test('create a {{pascal_case referenceable.name}} and get {{lower_case collectio payload: {{#if (eq collection_type.type "Global")}}null{{else}}alice.agentPubKey{{/if}} }); assert.equal(collectionOutput.length, 1); - assert.deepEqual(createdRecord, collectionOutput[0]); + assert.deepEqual({{#if (eq referenceable.hash_type "EntryHash")}}(createRecord.signed_action.hashed.content as NewEntryAction).entry_hash{{else}}createRecord.signed_action.hashed.hash{{/if}}, collectionOutput[0].target); +{{#if (and deletable (eq referenceable.hash_type "ActionHash"))}} + + // Alice deletes the {{pascal_case referenceable.name}} + await alice.cells[0].callZome({ + zome_name: "{{coordinator_zome_manifest.name}}", + fn_name: "delete_{{snake_case referenceable.name}}", + payload: createRecord.signed_action.hashed.hash + }); + + await dhtSync([alice, bob], alice.cells[0].cell_id[0]); + + // Bob gets {{lower_case collection_name}} again + collectionOutput = await bob.cells[0].callZome({ + zome_name: "{{coordinator_zome_manifest.name}}", + fn_name: "get_{{snake_case collection_name}}", + payload: {{#if (eq collection_type.type "Global")}}null{{else}}alice.agentPubKey{{/if}} + }); + assert.equal(collectionOutput.length, 0); +{{/if}} }); }); diff --git a/templates/lit/collection/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case collection_name}}.ts.hbs b/templates/lit/collection/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case collection_name}}.ts.hbs index 536233b74..12acd50ec 100644 --- a/templates/lit/collection/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case collection_name}}.ts.hbs +++ b/templates/lit/collection/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case collection_name}}.ts.hbs @@ -1,6 +1,6 @@ import { LitElement, html } from 'lit'; import { state, customElement, property } from 'lit/decorators.js'; -import { AppAgentClient, AgentPubKey, EntryHash, ActionHash, Record, NewEntryAction } from '@holochain/client'; +import { AppAgentClient, AgentPubKey, Link, EntryHash, ActionHash, Record, NewEntryAction } from '@holochain/client'; import { consume } from '@lit-labs/context'; import { Task } from '@lit-labs/task'; import '@material/mwc-circular-progress'; @@ -31,7 +31,7 @@ export class {{pascal_case collection_name}} extends LitElement { zome_name: '{{coordinator_zome_manifest.name}}', fn_name: 'get_{{snake_case collection_name}}', payload: {{#if (eq collection_type.type "ByAuthor")}}author{{else}}null{{/if}}, - }) as Promise>, () => [{{#if (eq collection_type.type "ByAuthor")}}this.author{{/if}}]); + }) as Promise>, () => [{{#if (eq collection_type.type "ByAuthor")}}this.author{{/if}}]); firstUpdated() { {{#if (eq collection_type.type "ByAuthor")}} @@ -69,7 +69,7 @@ export class {{pascal_case collection_name}} extends LitElement { pending: () => html`
`, - complete: (records) => this.renderList([...this.signaledHashes, ...records.map(r => {{#if (eq referenceable.hash_type "ActionHash")}}r.signed_action.hashed.hash{{else}}(r.signed_action.hashed.content as NewEntryAction).entry_hash{{/if}})]), + complete: (links) => this.renderList([...this.signaledHashes, ...links.map(l => l.target)]), error: (e: any) => html`Error fetching the {{lower_case (plural referenceable.name)}}: ${e.data.data}.` }); } diff --git a/templates/lit/entry-type/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case entry_type.name}}.test.ts.hbs b/templates/lit/entry-type/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case entry_type.name}}.test.ts.hbs index 61804cda6..c81447bdb 100644 --- a/templates/lit/entry-type/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case entry_type.name}}.test.ts.hbs +++ b/templates/lit/entry-type/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case entry_type.name}}.test.ts.hbs @@ -62,6 +62,21 @@ test('create and read {{pascal_case entry_type.name}}', async () => { payload: {{#if entry_type.reference_entry_hash}}(record.signed_action.hashed.content as NewEntryAction).entry_hash{{else}}record.signed_action.hashed.hash{{/if}}, }); assert.deepEqual(sample, decode((createReadOutput.entry as any).Present.entry) as any); + + {{#each entry_type.fields}} + {{#if linked_from}} + {{#if (ne (pascal_case linked_from.name) (pascal_case ../entry_type.name))}} + // Bob gets the {{pascal_case (plural linked_from.name)}} for the new {{pascal_case ../entry_type.name}} + let linksTo{{pascal_case (plural linked_from.name)}}: Link[] = await bob.cells[0].callZome({ + zome_name: "{{../coordinator_zome_manifest.name}}", + fn_name: "get_{{snake_case (plural ../entry_type.name)}}_for_{{snake_case linked_from.name}}", + payload: {{#if (eq cardinality "vector")}}sample.{{field_name}}[0]{{else}}sample.{{field_name}}{{/if}} + }); + assert.equal(linksTo{{pascal_case (plural linked_from.name)}}.length, 1); + assert.deepEqual(linksTo{{pascal_case (plural linked_from.name)}}[0].target, {{#if ../entry_type.reference_entry_hash}}(record.signed_action.hashed.content as NewEntryAction).entry_hash{{else}}record.signed_action.hashed.hash{{/if}}); + {{/if}} + {{/if}} + {{/each}} }); }); @@ -175,10 +190,29 @@ test('create and delete {{pascal_case entry_type.name}}', async () => { // conductor of the scenario. await scenario.shareAllAgents(); + const sample = await sample{{pascal_case entry_type.name}}(alice.cells[0]); + // Alice creates a {{pascal_case entry_type.name}} - const record: Record = await create{{pascal_case entry_type.name}}(alice.cells[0]); + const record: Record = await create{{pascal_case entry_type.name}}(alice.cells[0], sample); assert.ok(record); - + + await dhtSync([alice, bob], alice.cells[0].cell_id[0]); + + {{#each entry_type.fields}} + {{#if linked_from}} + {{#if (ne (pascal_case linked_from.name) (pascal_case ../entry_type.name))}} + // Bob gets the {{pascal_case (plural linked_from.name)}} for the new {{pascal_case ../entry_type.name}} + let linksTo{{pascal_case (plural linked_from.name)}}: Link[] = await bob.cells[0].callZome({ + zome_name: "{{../coordinator_zome_manifest.name}}", + fn_name: "get_{{snake_case (plural ../entry_type.name)}}_for_{{snake_case linked_from.name}}", + payload: {{#if (eq cardinality "vector")}}sample.{{field_name}}[0]{{else}}sample.{{field_name}}{{/if}} + }); + assert.equal(linksTo{{pascal_case (plural linked_from.name)}}.length, 1); + assert.deepEqual(linksTo{{pascal_case (plural linked_from.name)}}[0].target, {{#if ../entry_type.reference_entry_hash}}(record.signed_action.hashed.content as NewEntryAction).entry_hash{{else}}record.signed_action.hashed.hash{{/if}}); + {{/if}} + {{/if}} + {{/each}} + // Alice deletes the {{pascal_case entry_type.name}} const deleteActionHash = await alice.cells[0].callZome({ zome_name: "{{coordinator_zome_manifest.name}}", @@ -205,6 +239,29 @@ test('create and delete {{pascal_case entry_type.name}}', async () => { payload: record.signed_action.hashed.hash, }); assert.equal(deletesFor{{title_case entry_type.name}}.length, 1); + + {{#each entry_type.fields}} + {{#if linked_from}} + {{#if (ne (pascal_case linked_from.name) (pascal_case ../entry_type.name))}} + // Bob gets the {{pascal_case (plural linked_from.name)}} for the {{pascal_case ../entry_type.name}} again + linksTo{{pascal_case (plural linked_from.name)}} = await bob.cells[0].callZome({ + zome_name: "{{../coordinator_zome_manifest.name}}", + fn_name: "get_{{snake_case (plural ../entry_type.name)}}_for_{{snake_case linked_from.name}}", + payload: {{#if (eq cardinality "vector")}}sample.{{field_name}}[0]{{else}}sample.{{field_name}}{{/if}} + }); + assert.equal(linksTo{{pascal_case (plural linked_from.name)}}.length, 0); + + // Bob gets the deleted {{pascal_case (plural linked_from.name)}} for the {{pascal_case ../entry_type.name}} + const deletedLinksTo{{pascal_case (plural linked_from.name)}} = await bob.cells[0].callZome({ + zome_name: "{{../coordinator_zome_manifest.name}}", + fn_name: "get_deleted_{{snake_case (plural ../entry_type.name)}}_for_{{snake_case linked_from.name}}", + payload: {{#if (eq cardinality "vector")}}sample.{{field_name}}[0]{{else}}sample.{{field_name}}{{/if}} + }); + assert.equal(deletedLinksTo{{pascal_case (plural linked_from.name)}}.length, 1); + {{/if}} + {{/if}} + {{/each}} + }); }); {{/if}} diff --git "a/templates/lit/entry-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#each entry_type.fields}}{{#if (and linked_from (not (eq linked_from.hash_type 'AgentPubKey') ) )}}{{kebab_case (plural ..\302\241entry_type.name)}}-for-{{kebab_case linked_from.name}}.ts{{\302\241if}}{{\302\241each}}.hbs" "b/templates/lit/entry-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#each entry_type.fields}}{{#if (and linked_from (not (eq linked_from.hash_type 'AgentPubKey') ) )}}{{kebab_case (plural ..\302\241entry_type.name)}}-for-{{kebab_case linked_from.name}}.ts{{\302\241if}}{{\302\241each}}.hbs" index a84e1e04a..a3ad55b50 100644 --- "a/templates/lit/entry-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#each entry_type.fields}}{{#if (and linked_from (not (eq linked_from.hash_type 'AgentPubKey') ) )}}{{kebab_case (plural ..\302\241entry_type.name)}}-for-{{kebab_case linked_from.name}}.ts{{\302\241if}}{{\302\241each}}.hbs" +++ "b/templates/lit/entry-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#each entry_type.fields}}{{#if (and linked_from (not (eq linked_from.hash_type 'AgentPubKey') ) )}}{{kebab_case (plural ..\302\241entry_type.name)}}-for-{{kebab_case linked_from.name}}.ts{{\302\241if}}{{\302\241each}}.hbs" @@ -1,6 +1,6 @@ import { LitElement, html } from 'lit'; import { state, customElement, property } from 'lit/decorators.js'; -import { InstalledCell, Record, AppAgentClient, EntryHash, ActionHash, AgentPubKey } from '@holochain/client'; +import { InstalledCell, Record, Link, AppAgentClient, EntryHash, ActionHash, AgentPubKey } from '@holochain/client'; import { consume } from '@lit-labs/context'; import '@material/mwc-circular-progress'; import { Task } from '@lit-labs/task'; @@ -24,7 +24,7 @@ export class {{pascal_case (plural ../entry_type.name)}}For{{pascal_case linked_ zome_name: '{{../coordinator_zome_manifest.name}}', fn_name: 'get_{{snake_case (plural ../entry_type.name)}}_for_{{snake_case linked_from.name}}', payload: {{camel_case linked_from.singular_arg}}, - }) as Promise>, () => [this.{{camel_case linked_from.singular_arg}}]); + }) as Promise>, () => [this.{{camel_case linked_from.singular_arg}}]); firstUpdated() { if (this.{{camel_case linked_from.singular_arg}} === undefined) { @@ -32,13 +32,13 @@ export class {{pascal_case (plural ../entry_type.name)}}For{{pascal_case linked_ } } - renderList(hashes: Array<{{#if ../entry_type.reference_entry_hash}}EntryHash{{else}}ActionHash{{/if}}>) { - if (hashes.length === 0) return html`No {{lower_case (plural ../entry_type.name)}} found for this {{lower_case linked_from.name}}.`; + renderList(links: Array) { + if (links.length === 0) return html`No {{lower_case (plural ../entry_type.name)}} found for this {{lower_case linked_from.name}}.`; return html`
- ${hashes.map(hash => - html`<{{kebab_case ../entry_type.name}}-detail .{{camel_case ../entry_type.name}}Hash=${hash}>` + ${links.map(link => + html`<{{kebab_case ../entry_type.name}}-detail .{{camel_case ../entry_type.name}}Hash=${link.target}>` )}
`; @@ -49,7 +49,7 @@ export class {{pascal_case (plural ../entry_type.name)}}For{{pascal_case linked_ pending: () => html`
`, - complete: (hashes) => this.renderList(hashes), + complete: (links) => this.renderList(links), error: (e: any) => html`Error fetching {{lower_case (plural ../entry_type.name)}}: ${e.data.data}.` }); } diff --git "a/templates/lit/link-type/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#if to_referenceable}}{{kebab_case from_referenceable.name}}-to-{{kebab_case (plural to_referenceable.name)}}.test.ts{{\302\241if}}.hbs" "b/templates/lit/link-type/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#if to_referenceable}}{{kebab_case from_referenceable.name}}-to-{{kebab_case (plural to_referenceable.name)}}.test.ts{{\302\241if}}.hbs" index dd454e4f1..646fbb83b 100644 --- "a/templates/lit/link-type/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#if to_referenceable}}{{kebab_case from_referenceable.name}}-to-{{kebab_case (plural to_referenceable.name)}}.test.ts{{\302\241if}}.hbs" +++ "b/templates/lit/link-type/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#if to_referenceable}}{{kebab_case from_referenceable.name}}-to-{{kebab_case (plural to_referenceable.name)}}.test.ts{{\302\241if}}.hbs" @@ -1,7 +1,7 @@ import { assert, test } from "vitest"; import { runScenario, dhtSync, CallableCell } from '@holochain/tryorama'; -import { NewEntryAction, ActionHash, Record, AppBundleSource, fakeActionHash, fakeAgentPubKey, fakeEntryHash } from '@holochain/client'; +import { NewEntryAction, ActionHash, Record, Link, AppBundleSource, fakeActionHash, fakeAgentPubKey, fakeEntryHash } from '@holochain/client'; import { decode } from '@msgpack/msgpack'; {{#if (ne from_referenceable.hash_type "AgentPubKey")}} @@ -50,7 +50,7 @@ test('link a {{pascal_case from_referenceable.name}} to a {{pascal_case to_refer {{/if}} // Bob gets the links, should be empty - let linksOutput: Record[] = await bob.cells[0].callZome({ + let linksOutput: Link[] = await bob.cells[0].callZome({ zome_name: "{{coordinator_zome_manifest.name}}", fn_name: "get_{{plural (snake_case to_referenceable.name)}}_for_{{snake_case from_referenceable.name}}", payload: baseAddress @@ -77,7 +77,7 @@ test('link a {{pascal_case from_referenceable.name}} to a {{pascal_case to_refer }); assert.equal(linksOutput.length, 1); {{#if (ne to_referenceable.hash_type "AgentPubKey")}} - assert.deepEqual(targetRecord, linksOutput[0]); + assert.deepEqual(targetAddress, linksOutput[0].target); {{/if}} {{#if bidireccional}} @@ -90,7 +90,7 @@ test('link a {{pascal_case from_referenceable.name}} to a {{pascal_case to_refer }); assert.equal(linksOutput.length, 1); {{#if (ne from_referenceable.hash_type "AgentPubKey")}} - assert.deepEqual(baseRecord, linksOutput[0]); + assert.deepEqual(baseAddress, linksOutput[0].target); {{/if}} {{/if}} @@ -114,6 +114,14 @@ test('link a {{pascal_case from_referenceable.name}} to a {{pascal_case to_refer }); assert.equal(linksOutput.length, 0); + // Bob gets the deleted links + let deletedLinksOutput: Array<[SignedActionHashed, SignedActionHashed[]]> = await bob.cells[0].callZome({ + zome_name: "{{coordinator_zome_manifest.name}}", + fn_name: "get_deleted_{{plural (snake_case to_referenceable.name)}}_for_{{snake_case from_referenceable.name}}", + payload: baseAddress + }); + assert.equal(deletedLinksOutput.length, 1); + {{#if bidireccional}} // Bob gets the links in the inverse direction linksOutput = await bob.cells[0].callZome({ @@ -122,6 +130,14 @@ test('link a {{pascal_case from_referenceable.name}} to a {{pascal_case to_refer payload: targetAddress }); assert.equal(linksOutput.length, 0); + + // Bob gets the deleted links in the inverse direction + deletedLinksOutput = await bob.cells[0].callZome({ + zome_name: "{{coordinator_zome_manifest.name}}", + fn_name: "get_deleted_{{plural (snake_case from_referenceable.name)}}_for_{{snake_case to_referenceable.name}}", + payload: targetAddress + }); + assert.equal(deletedLinksOutput.length, 1); {{/if}} {{/if}} diff --git "a/templates/lit/link-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#if (and bidireccional (and to_referenceable (ne from_referenceable.hash_type 'AgentPubKey')))}}{{kebab_case (plural from_referenceable.name)}}-for-{{kebab_case to_referenceable.name}}.ts{{\302\241if}}.hbs" "b/templates/lit/link-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#if (and bidireccional (and to_referenceable (ne from_referenceable.hash_type 'AgentPubKey')))}}{{kebab_case (plural from_referenceable.name)}}-for-{{kebab_case to_referenceable.name}}.ts{{\302\241if}}.hbs" index 121587e76..3f98911d8 100644 --- "a/templates/lit/link-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#if (and bidireccional (and to_referenceable (ne from_referenceable.hash_type 'AgentPubKey')))}}{{kebab_case (plural from_referenceable.name)}}-for-{{kebab_case to_referenceable.name}}.ts{{\302\241if}}.hbs" +++ "b/templates/lit/link-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#if (and bidireccional (and to_referenceable (ne from_referenceable.hash_type 'AgentPubKey')))}}{{kebab_case (plural from_referenceable.name)}}-for-{{kebab_case to_referenceable.name}}.ts{{\302\241if}}.hbs" @@ -1,6 +1,6 @@ import { LitElement, html } from 'lit'; import { state, property, customElement } from 'lit/decorators.js'; -import { AgentPubKey, EntryHash, ActionHash, Record, AppAgentClient, NewEntryAction } from '@holochain/client'; +import { AgentPubKey, Link, EntryHash, ActionHash, Record, AppAgentClient, NewEntryAction } from '@holochain/client'; import { consume } from '@lit-labs/context'; import { Task } from '@lit-labs/task'; import { clientContext } from '../../contexts'; @@ -25,7 +25,7 @@ export class {{pascal_case (plural from_referenceable.name)}}For{{pascal_case to zome_name: '{{coordinator_zome_manifest.name}}', fn_name: 'get_{{snake_case (plural from_referenceable.name)}}_for_{{snake_case to_referenceable.name}}', payload: {{camel_case to_referenceable.singular_arg}}, - }) as Promise>, () => [this.{{camel_case to_referenceable.singular_arg}}]); + }) as Promise>, () => [this.{{camel_case to_referenceable.singular_arg}}]); @state() signaledHashes: Array<{{from_referenceable.hash_type}}> = []; @@ -62,7 +62,7 @@ export class {{pascal_case (plural from_referenceable.name)}}For{{pascal_case to pending: () => html`
`, - complete: (records) => this.renderList([...this.signaledHashes, ...records.map(r => {{#if (eq from_referenceable.hash_type "ActionHash")}}r.signed_action.hashed.hash{{else}}(r.signed_action.hashed.content as NewEntryAction).entry_hash{{/if}})]), + complete: (links) => this.renderList([...this.signaledHashes, ...links.map(l => l.target)]), error: (e: any) => html`Error fetching the {{lower_case (plural from_referenceable.name)}}: ${e.data.data}.` }); } diff --git "a/templates/lit/link-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#if (and to_referenceable (ne to_referenceable.hash_type 'AgentPubKey'))}}{{kebab_case (plural to_referenceable.name)}}-for-{{kebab_case from_referenceable.name}}.ts{{\302\241if}}.hbs" "b/templates/lit/link-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#if (and to_referenceable (ne to_referenceable.hash_type 'AgentPubKey'))}}{{kebab_case (plural to_referenceable.name)}}-for-{{kebab_case from_referenceable.name}}.ts{{\302\241if}}.hbs" index 8eb1f24af..3b022e566 100644 --- "a/templates/lit/link-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#if (and to_referenceable (ne to_referenceable.hash_type 'AgentPubKey'))}}{{kebab_case (plural to_referenceable.name)}}-for-{{kebab_case from_referenceable.name}}.ts{{\302\241if}}.hbs" +++ "b/templates/lit/link-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#if (and to_referenceable (ne to_referenceable.hash_type 'AgentPubKey'))}}{{kebab_case (plural to_referenceable.name)}}-for-{{kebab_case from_referenceable.name}}.ts{{\302\241if}}.hbs" @@ -1,6 +1,6 @@ import { LitElement, html } from 'lit'; import { state, property, customElement } from 'lit/decorators.js'; -import { AgentPubKey, EntryHash, ActionHash, Record, AppAgentClient, NewEntryAction } from '@holochain/client'; +import { AgentPubKey, Link, EntryHash, ActionHash, Record, AppAgentClient, NewEntryAction } from '@holochain/client'; import { consume } from '@lit-labs/context'; import { Task } from '@lit-labs/task'; import '@material/mwc-circular-progress'; @@ -28,7 +28,7 @@ export class {{pascal_case (plural to_referenceable.name)}}For{{pascal_case from zome_name: '{{coordinator_zome_manifest.name}}', fn_name: 'get_{{snake_case (plural to_referenceable.name)}}_for_{{snake_case from_referenceable.name}}', payload: {{camel_case from_referenceable.singular_arg}}, - }) as Promise>, () => [this.{{camel_case from_referenceable.singular_arg}}]); + }) as Promise>, () => [this.{{camel_case from_referenceable.singular_arg}}]); firstUpdated() { if (this.{{camel_case from_referenceable.singular_arg}} === undefined) { @@ -62,7 +62,7 @@ export class {{pascal_case (plural to_referenceable.name)}}For{{pascal_case from pending: () => html`
`, - complete: (records) => this.renderList([...this.signaledHashes, ...records.map(r => {{#if (eq to_referenceable.hash_type "ActionHash")}}r.signed_action.hashed.hash{{else}}(r.signed_action.hashed.content as NewEntryAction).entry_hash{{/if}})]), + complete: (links) => this.renderList([...this.signaledHashes, ...links.map(l => l.target)]), error: (e: any) => html`Error fetching the {{lower_case (plural to_referenceable.name)}}: ${e.data.data}.` }); } diff --git a/templates/svelte/collection/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case collection_name}}.test.ts.hbs b/templates/svelte/collection/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case collection_name}}.test.ts.hbs index 955845709..2312204b3 100644 --- a/templates/svelte/collection/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case collection_name}}.test.ts.hbs +++ b/templates/svelte/collection/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case collection_name}}.test.ts.hbs @@ -24,7 +24,7 @@ test('create a {{pascal_case referenceable.name}} and get {{lower_case collectio await scenario.shareAllAgents(); // Bob gets {{lower_case collection_name}} - let collectionOutput: Record[] = await bob.cells[0].callZome({ + let collectionOutput: Link[] = await bob.cells[0].callZome({ zome_name: "{{coordinator_zome_manifest.name}}", fn_name: "get_{{snake_case collection_name}}", payload: {{#if (eq collection_type.type "Global")}}null{{else}}alice.agentPubKey{{/if}} @@ -32,8 +32,8 @@ test('create a {{pascal_case referenceable.name}} and get {{lower_case collectio assert.equal(collectionOutput.length, 0); // Alice creates a {{pascal_case referenceable.name}} - const createdRecord: Record = await create{{pascal_case referenceable.name}}(alice.cells[0]); - assert.ok(createdRecord); + const createRecord: Record = await create{{pascal_case referenceable.name}}(alice.cells[0]); + assert.ok(createRecord); await dhtSync([alice, bob], alice.cells[0].cell_id[0]); @@ -44,7 +44,26 @@ test('create a {{pascal_case referenceable.name}} and get {{lower_case collectio payload: {{#if (eq collection_type.type "Global")}}null{{else}}alice.agentPubKey{{/if}} }); assert.equal(collectionOutput.length, 1); - assert.deepEqual(createdRecord, collectionOutput[0]); + assert.deepEqual({{#if (eq referenceable.hash_type "EntryHash")}}(createRecord.signed_action.hashed.content as NewEntryAction).entry_hash{{else}}createRecord.signed_action.hashed.hash{{/if}}, collectionOutput[0].target); +{{#if (and deletable (eq referenceable.hash_type "ActionHash"))}} + + // Alice deletes the {{pascal_case referenceable.name}} + await alice.cells[0].callZome({ + zome_name: "{{coordinator_zome_manifest.name}}", + fn_name: "delete_{{snake_case referenceable.name}}", + payload: createRecord.signed_action.hashed.hash + }); + + await dhtSync([alice, bob], alice.cells[0].cell_id[0]); + + // Bob gets {{lower_case collection_name}} again + collectionOutput = await bob.cells[0].callZome({ + zome_name: "{{coordinator_zome_manifest.name}}", + fn_name: "get_{{snake_case collection_name}}", + payload: {{#if (eq collection_type.type "Global")}}null{{else}}alice.agentPubKey{{/if}} + }); + assert.equal(collectionOutput.length, 0); +{{/if}} }); }); diff --git a/templates/svelte/collection/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{pascal_case collection_name}}.svelte.hbs b/templates/svelte/collection/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{pascal_case collection_name}}.svelte.hbs index 6ddfc7db2..29dc25bd3 100644 --- a/templates/svelte/collection/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{pascal_case collection_name}}.svelte.hbs +++ b/templates/svelte/collection/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{pascal_case collection_name}}.svelte.hbs @@ -41,14 +41,14 @@ onMount(async () => { async function fetch{{pascal_case (plural referenceable.name)}}() { try { - const records = await client.callZome({ + const links = await client.callZome({ cap_secret: null, role_name: '{{dna_role_name}}', zome_name: '{{snake_case coordinator_zome_manifest.name}}', fn_name: 'get_{{snake_case collection_name}}', payload: {{#if (eq collection_type.type "ByAuthor")}}author{{else}}null{{/if}}, }); - hashes = records.map(r => {{#if (eq referenceable.hash_type "ActionHash")}}r.signed_action.hashed.hash{{else}}(r.signed_action.hashed.content as NewEntryAction).entry_hash{{/if}}); + hashes = links.map(l => l.target); } catch (e) { error = e; } diff --git a/templates/svelte/entry-type/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case entry_type.name}}.test.ts.hbs b/templates/svelte/entry-type/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case entry_type.name}}.test.ts.hbs index 02e3e6e6d..4b303ada5 100644 --- a/templates/svelte/entry-type/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case entry_type.name}}.test.ts.hbs +++ b/templates/svelte/entry-type/tests/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{kebab_case entry_type.name}}.test.ts.hbs @@ -58,10 +58,25 @@ test('create and read {{pascal_case entry_type.name}}', async () => { // Bob gets the created {{pascal_case entry_type.name}} const createReadOutput: Record = await bob.cells[0].callZome({ zome_name: "{{coordinator_zome_manifest.name}}", - fn_name: "{{#if crud.update}}get_latest_{{snake_case entry_type.name}}{{else}}get_{{snake_case entry_type.name}}{{/if}}", + fn_name: "{{#if crud.update}}get_original_{{snake_case entry_type.name}}{{else}}get_{{snake_case entry_type.name}}{{/if}}", payload: {{#if entry_type.reference_entry_hash}}(record.signed_action.hashed.content as NewEntryAction).entry_hash{{else}}record.signed_action.hashed.hash{{/if}}, }); assert.deepEqual(sample, decode((createReadOutput.entry as any).Present.entry) as any); + + {{#each entry_type.fields}} + {{#if linked_from}} + {{#if (ne (pascal_case linked_from.name) (pascal_case ../entry_type.name))}} + // Bob gets the {{pascal_case (plural linked_from.name)}} for the new {{pascal_case ../entry_type.name}} + let linksTo{{pascal_case (plural linked_from.name)}}: Link[] = await bob.cells[0].callZome({ + zome_name: "{{../coordinator_zome_manifest.name}}", + fn_name: "get_{{snake_case (plural ../entry_type.name)}}_for_{{snake_case linked_from.name}}", + payload: {{#if (eq cardinality "vector")}}sample.{{field_name}}[0]{{else}}sample.{{field_name}}{{/if}} + }); + assert.equal(linksTo{{pascal_case (plural linked_from.name)}}.length, 1); + assert.deepEqual(linksTo{{pascal_case (plural linked_from.name)}}[0].target, {{#if ../entry_type.reference_entry_hash}}(record.signed_action.hashed.content as NewEntryAction).entry_hash{{else}}record.signed_action.hashed.hash{{/if}}); + {{/if}} + {{/if}} + {{/each}} }); }); @@ -175,10 +190,29 @@ test('create and delete {{pascal_case entry_type.name}}', async () => { // conductor of the scenario. await scenario.shareAllAgents(); + const sample = await sample{{pascal_case entry_type.name}}(alice.cells[0]); + // Alice creates a {{pascal_case entry_type.name}} - const record: Record = await create{{pascal_case entry_type.name}}(alice.cells[0]); + const record: Record = await create{{pascal_case entry_type.name}}(alice.cells[0], sample); assert.ok(record); - + + await dhtSync([alice, bob], alice.cells[0].cell_id[0]); + + {{#each entry_type.fields}} + {{#if linked_from}} + {{#if (ne (pascal_case linked_from.name) (pascal_case ../entry_type.name))}} + // Bob gets the {{pascal_case (plural linked_from.name)}} for the new {{pascal_case ../entry_type.name}} + let linksTo{{pascal_case (plural linked_from.name)}}: Link[] = await bob.cells[0].callZome({ + zome_name: "{{../coordinator_zome_manifest.name}}", + fn_name: "get_{{snake_case (plural ../entry_type.name)}}_for_{{snake_case linked_from.name}}", + payload: {{#if (eq cardinality "vector")}}sample.{{field_name}}[0]{{else}}sample.{{field_name}}{{/if}} + }); + assert.equal(linksTo{{pascal_case (plural linked_from.name)}}.length, 1); + assert.deepEqual(linksTo{{pascal_case (plural linked_from.name)}}[0].target, {{#if ../entry_type.reference_entry_hash}}(record.signed_action.hashed.content as NewEntryAction).entry_hash{{else}}record.signed_action.hashed.hash{{/if}}); + {{/if}} + {{/if}} + {{/each}} + // Alice deletes the {{pascal_case entry_type.name}} const deleteActionHash = await alice.cells[0].callZome({ zome_name: "{{coordinator_zome_manifest.name}}", @@ -204,7 +238,30 @@ test('create and delete {{pascal_case entry_type.name}}', async () => { fn_name: "get_all_deletes_for_{{snake_case entry_type.name}}", payload: record.signed_action.hashed.hash, }); - assert.equal(deletesFor{{pascal_case entry_type.name}}.length, 1); + assert.equal(deletesFor{{title_case entry_type.name}}.length, 1); + + {{#each entry_type.fields}} + {{#if linked_from}} + {{#if (ne (pascal_case linked_from.name) (pascal_case ../entry_type.name))}} + // Bob gets the {{pascal_case (plural linked_from.name)}} for the {{pascal_case ../entry_type.name}} again + linksTo{{pascal_case (plural linked_from.name)}} = await bob.cells[0].callZome({ + zome_name: "{{../coordinator_zome_manifest.name}}", + fn_name: "get_{{snake_case (plural ../entry_type.name)}}_for_{{snake_case linked_from.name}}", + payload: {{#if (eq cardinality "vector")}}sample.{{field_name}}[0]{{else}}sample.{{field_name}}{{/if}} + }); + assert.equal(linksTo{{pascal_case (plural linked_from.name)}}.length, 0); + + // Bob gets the deleted {{pascal_case (plural linked_from.name)}} for the {{pascal_case ../entry_type.name}} + const deletedLinksTo{{pascal_case (plural linked_from.name)}} = await bob.cells[0].callZome({ + zome_name: "{{../coordinator_zome_manifest.name}}", + fn_name: "get_deleted_{{snake_case (plural ../entry_type.name)}}_for_{{snake_case linked_from.name}}", + payload: {{#if (eq cardinality "vector")}}sample.{{field_name}}[0]{{else}}sample.{{field_name}}{{/if}} + }); + assert.equal(deletedLinksTo{{pascal_case (plural linked_from.name)}}.length, 1); + {{/if}} + {{/if}} + {{/each}} + }); }); {{/if}} diff --git "a/templates/svelte/entry-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#each entry_type.fields}}{{#if (and linked_from (not (eq linked_from.hash_type 'AgentPubKey') ) )}}{{pascal_case (plural ..\302\241entry_type.name)}}For{{pascal_case linked_from.name}}.svelte{{\302\241if}}{{\302\241each}}.hbs" "b/templates/svelte/entry-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#each entry_type.fields}}{{#if (and linked_from (not (eq linked_from.hash_type 'AgentPubKey') ) )}}{{pascal_case (plural ..\302\241entry_type.name)}}For{{pascal_case linked_from.name}}.svelte{{\302\241if}}{{\302\241each}}.hbs" index 1b0afd753..52c4e7232 100644 --- "a/templates/svelte/entry-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#each entry_type.fields}}{{#if (and linked_from (not (eq linked_from.hash_type 'AgentPubKey') ) )}}{{pascal_case (plural ..\302\241entry_type.name)}}For{{pascal_case linked_from.name}}.svelte{{\302\241if}}{{\302\241each}}.hbs" +++ "b/templates/svelte/entry-type/ui/src/{{dna_role_name}}/{{coordinator_zome_manifest.name}}/{{#each entry_type.fields}}{{#if (and linked_from (not (eq linked_from.hash_type 'AgentPubKey') ) )}}{{pascal_case (plural ..\302\241entry_type.name)}}For{{pascal_case linked_from.name}}.svelte{{\302\241if}}{{\302\241each}}.hbs" @@ -1,7 +1,7 @@