From 00244e24fe974eb33e6c0746dcb3ab592ad50cd2 Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Thu, 15 Aug 2024 16:46:02 +0100 Subject: [PATCH 01/17] chore: reorganise metadata into dependency crate and inject link types --- Cargo.lock | 42 ++++++++++++ Cargo.toml | 29 +++++++- crates/hdk_utils/Cargo.toml | 11 +++ crates/hdk_utils/src/lib.rs | 25 +++++++ crates/holoom_types/src/lib.rs | 2 - crates/holoom_types/src/metadata.rs | 27 -------- crates/metadata/handlers/Cargo.toml | 16 +++++ crates/metadata/handlers/src/get_all.rs | 22 ++++++ crates/metadata/handlers/src/get_item.rs | 31 +++++++++ crates/metadata/handlers/src/lib.rs | 3 + crates/metadata/handlers/src/update_item.rs | 36 ++++++++++ crates/metadata/types/Cargo.toml | 13 ++++ crates/metadata/types/src/lib.rs | 15 ++++ crates/metadata/validation/Cargo.toml | 14 ++++ .../validation/src/lib.rs} | 2 +- .../username_registry_coordinator/Cargo.toml | 2 + .../src/user_metadata.rs | 68 ++----------------- crates/username_registry_integrity/Cargo.toml | 2 + .../src/link_types.rs | 19 ++++-- .../username_registry_validation/src/lib.rs | 2 - 20 files changed, 282 insertions(+), 99 deletions(-) create mode 100644 crates/hdk_utils/Cargo.toml create mode 100644 crates/hdk_utils/src/lib.rs delete mode 100644 crates/holoom_types/src/metadata.rs create mode 100644 crates/metadata/handlers/Cargo.toml create mode 100644 crates/metadata/handlers/src/get_all.rs create mode 100644 crates/metadata/handlers/src/get_item.rs create mode 100644 crates/metadata/handlers/src/lib.rs create mode 100644 crates/metadata/handlers/src/update_item.rs create mode 100644 crates/metadata/types/Cargo.toml create mode 100644 crates/metadata/types/src/lib.rs create mode 100644 crates/metadata/validation/Cargo.toml rename crates/{username_registry_validation/src/user_metadata.rs => metadata/validation/src/lib.rs} (97%) diff --git a/Cargo.lock b/Cargo.lock index 733b70a..a2ee879 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3083,6 +3083,13 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "hdk_utils" +version = "0.0.1" +dependencies = [ + "hdk", +] + [[package]] name = "headers" version = "0.3.9" @@ -5017,6 +5024,25 @@ dependencies = [ "autocfg 1.1.0", ] +[[package]] +name = "metadata_types" +version = "0.0.1" +dependencies = [ + "hdi", + "serde", + "ts-rs", +] + +[[package]] +name = "metadata_validation" +version = "0.0.1" +dependencies = [ + "bincode", + "hdi", + "metadata_types", + "serde", +] + [[package]] name = "mime" version = "0.3.17" @@ -8683,6 +8709,18 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "user_metadata_handlers" +version = "0.0.1" +dependencies = [ + "bincode", + "hdk", + "hdk_utils", + "metadata_types", + "serde", + "ts-rs", +] + [[package]] name = "username_registry_coordinator" version = "0.0.1" @@ -8693,8 +8731,10 @@ dependencies = [ "holoom_types", "indexmap 2.2.6", "jaq_wrapper", + "metadata_types", "serde", "serde_json", + "user_metadata_handlers", "username_registry_integrity", "username_registry_utils", "username_registry_validation", @@ -8706,6 +8746,8 @@ version = "0.0.1" dependencies = [ "hdi", "holoom_types", + "metadata_types", + "metadata_validation", "serde", "username_registry_validation", ] diff --git a/Cargo.toml b/Cargo.toml index 99c5a37..80944af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,22 @@ opt-level = "z" [workspace] resolver = "2" -members = ["crates/*"] +members = [ + "crates/hdk_utils", + "crates/metadata/types", + "crates/metadata/handlers", + "crates/metadata/validation", + "crates/holoom_dna_tests", + "crates/holoom_types", + "crates/jaq_wrapper", + "crates/ping_coordinator", + "crates/records_coordinator", + "crates/signer_coordinator", + "crates/username_registry_coordinator", + "crates/username_registry_integrity", + "crates/username_registry_utils", + "crates/username_registry_validation", +] [workspace.dependencies] hdi = "=0.5.0-dev.9" @@ -25,6 +40,18 @@ holochain = { version = "=0.4.0-dev.12", default-features = false, features = [ ] } holochain_keystore = "=0.4.0-dev.12" +[workspace.dependencies.hdk_utils] +path = "crates/hdk_utils" + +[workspace.dependencies.metadata_types] +path = "crates/metadata/types" + +[workspace.dependencies.metadata_validation] +path = "crates/metadata/validation" + +[workspace.dependencies.user_metadata_handlers] +path = "crates/metadata/handlers" + [workspace.dependencies.holoom_types] path = "crates/holoom_types" diff --git a/crates/hdk_utils/Cargo.toml b/crates/hdk_utils/Cargo.toml new file mode 100644 index 0000000..ea4211c --- /dev/null +++ b/crates/hdk_utils/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "hdk_utils" +version = "0.0.1" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] +name = "hdk_utils" + +[dependencies] +hdk = { workspace = true } diff --git a/crates/hdk_utils/src/lib.rs b/crates/hdk_utils/src/lib.rs new file mode 100644 index 0000000..4a5417d --- /dev/null +++ b/crates/hdk_utils/src/lib.rs @@ -0,0 +1,25 @@ +use hdk::prelude::*; + +pub fn create_link( + base_address: impl Into, + target_address: impl Into, + link_type: impl TryInto, + tag: impl Into, +) -> ExternResult { + let ScopedLinkType { + zome_index, + zome_type: link_type, + } = link_type + .try_into() + .map_err(|_| wasm_error!("Isn't valid link type for this zome".to_string()))?; + HDK.with(|h| { + h.borrow().create_link(CreateLinkInput::new( + base_address.into(), + target_address.into(), + zome_index, + link_type, + tag.into(), + ChainTopOrdering::default(), + )) + }) +} diff --git a/crates/holoom_types/src/lib.rs b/crates/holoom_types/src/lib.rs index a43508a..dbbfac2 100644 --- a/crates/holoom_types/src/lib.rs +++ b/crates/holoom_types/src/lib.rs @@ -6,9 +6,7 @@ use ts_rs::TS; pub mod external_id; pub use external_id::*; pub mod evm_signing_offer; -pub mod metadata; pub mod recipe; -pub use metadata::*; pub mod wallet; pub use wallet::*; pub mod username; diff --git a/crates/holoom_types/src/metadata.rs b/crates/holoom_types/src/metadata.rs deleted file mode 100644 index 1ee6e4b..0000000 --- a/crates/holoom_types/src/metadata.rs +++ /dev/null @@ -1,27 +0,0 @@ -use hdi::prelude::*; -use serde::{Deserialize, Serialize}; -use ts_rs::TS; - -#[derive(Serialize, Deserialize, Debug, TS)] -#[ts(export)] -pub struct MetadataItem { - pub name: String, - pub value: String, -} - -#[derive(Serialize, Deserialize, Debug, TS)] -#[ts(export)] -pub struct UpdateMetadataItemPayload { - #[ts(type = "AgentPubKey")] - pub agent_pubkey: AgentPubKey, - pub name: String, - pub value: String, -} - -#[derive(Serialize, Deserialize, Debug, TS)] -#[ts(export)] -pub struct GetMetadataItemValuePayload { - #[ts(type = "AgentPubKey")] - pub agent_pubkey: AgentPubKey, - pub name: String, -} diff --git a/crates/metadata/handlers/Cargo.toml b/crates/metadata/handlers/Cargo.toml new file mode 100644 index 0000000..d4c18ab --- /dev/null +++ b/crates/metadata/handlers/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "user_metadata_handlers" +version = "0.0.1" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] +name = "user_metadata_handlers" + +[dependencies] +serde = { workspace = true } +hdk = { workspace = true } +metadata_types = { workspace = true } +bincode = { workspace = true } +hdk_utils = { workspace = true } +ts-rs = "9" diff --git a/crates/metadata/handlers/src/get_all.rs b/crates/metadata/handlers/src/get_all.rs new file mode 100644 index 0000000..7568cb4 --- /dev/null +++ b/crates/metadata/handlers/src/get_all.rs @@ -0,0 +1,22 @@ +use std::collections::HashMap; + +use hdk::prelude::*; +use metadata_types::{InjectMetadataLinkTypes, MetadataItem}; + +pub fn handler(agent_pubkey: AgentPubKey) -> ExternResult> +where + LT: InjectMetadataLinkTypes, +{ + let links = + get_links(GetLinksInputBuilder::try_new(agent_pubkey, LT::agent_metadata())?.build())?; + let mut out = HashMap::default(); + for link in links { + let item: MetadataItem = bincode::deserialize(&link.tag.into_inner()).map_err(|_| { + wasm_error!(WasmErrorInner::Guest( + "Failed to deserialize MetadataItem".into() + )) + })?; + out.insert(item.name, item.value); + } + Ok(out) +} diff --git a/crates/metadata/handlers/src/get_item.rs b/crates/metadata/handlers/src/get_item.rs new file mode 100644 index 0000000..684b7b4 --- /dev/null +++ b/crates/metadata/handlers/src/get_item.rs @@ -0,0 +1,31 @@ +use hdk::prelude::*; +use metadata_types::{InjectMetadataLinkTypes, MetadataItem}; +use ts_rs::TS; + +#[derive(Serialize, Deserialize, Debug, TS)] +#[ts(export)] +pub struct Input { + #[ts(type = "AgentPubKey")] + pub agent_pubkey: AgentPubKey, + pub name: String, +} + +pub fn handler(input: Input) -> ExternResult> +where + LT: InjectMetadataLinkTypes, +{ + let links = get_links( + GetLinksInputBuilder::try_new(input.agent_pubkey, LT::agent_metadata())?.build(), + )?; + for link in links { + let item: MetadataItem = bincode::deserialize(&link.tag.into_inner()).map_err(|_| { + wasm_error!(WasmErrorInner::Guest( + "Failed to deserialize MetadataItem".into() + )) + })?; + if input.name == item.name { + return Ok(Some(item.value)); + } + } + Ok(None) +} diff --git a/crates/metadata/handlers/src/lib.rs b/crates/metadata/handlers/src/lib.rs new file mode 100644 index 0000000..de3f767 --- /dev/null +++ b/crates/metadata/handlers/src/lib.rs @@ -0,0 +1,3 @@ +pub mod get_all; +pub mod get_item; +pub mod update_item; diff --git a/crates/metadata/handlers/src/update_item.rs b/crates/metadata/handlers/src/update_item.rs new file mode 100644 index 0000000..d0d3a5e --- /dev/null +++ b/crates/metadata/handlers/src/update_item.rs @@ -0,0 +1,36 @@ +use hdk::prelude::*; +use metadata_types::{InjectMetadataLinkTypes, MetadataItem}; + +pub fn handler(item: MetadataItem) -> ExternResult<()> +where + LT: InjectMetadataLinkTypes, +{ + let agent_pubkey = agent_info()?.agent_initial_pubkey; + let links = get_links( + GetLinksInputBuilder::try_new(agent_pubkey.clone(), LT::agent_metadata())?.build(), + )?; + for link in links { + let existing_item: MetadataItem = + bincode::deserialize(&link.tag.into_inner()).map_err(|_| { + wasm_error!(WasmErrorInner::Guest( + "Failed to deserialize MetadataItem".into() + )) + })?; + if existing_item.name == item.name { + // Remove old MetadataItem + delete_link(link.create_link_hash)?; + } + } + let tag_bytes = bincode::serialize(&item).map_err(|_| { + wasm_error!(WasmErrorInner::Guest( + "Failed to serialize MetadataItem".into() + )) + })?; + hdk_utils::create_link( + agent_pubkey.clone(), + agent_pubkey, // unused and irrelevant + LT::agent_metadata(), + LinkTag(tag_bytes), + )?; + Ok(()) +} diff --git a/crates/metadata/types/Cargo.toml b/crates/metadata/types/Cargo.toml new file mode 100644 index 0000000..c75128d --- /dev/null +++ b/crates/metadata/types/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "metadata_types" +version = "0.0.1" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] +name = "metadata_types" + +[dependencies] +serde = { workspace = true } +hdi = { workspace = true } +ts-rs = "9" diff --git a/crates/metadata/types/src/lib.rs b/crates/metadata/types/src/lib.rs new file mode 100644 index 0000000..8d5648f --- /dev/null +++ b/crates/metadata/types/src/lib.rs @@ -0,0 +1,15 @@ +use hdi::{link::LinkTypeFilterExt, prelude::ScopedLinkType}; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(Serialize, Deserialize, Debug, TS)] +#[ts(export)] +pub struct MetadataItem { + pub name: String, + pub value: String, +} + +pub trait InjectMetadataLinkTypes { + type LinkType: LinkTypeFilterExt + TryInto; + fn agent_metadata() -> Self::LinkType; +} diff --git a/crates/metadata/validation/Cargo.toml b/crates/metadata/validation/Cargo.toml new file mode 100644 index 0000000..0b1b7cf --- /dev/null +++ b/crates/metadata/validation/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "metadata_validation" +version = "0.0.1" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] +name = "metadata_validation" + +[dependencies] +hdi = { workspace = true } +serde = { workspace = true } +metadata_types = { workspace = true } +bincode = { workspace = true } diff --git a/crates/username_registry_validation/src/user_metadata.rs b/crates/metadata/validation/src/lib.rs similarity index 97% rename from crates/username_registry_validation/src/user_metadata.rs rename to crates/metadata/validation/src/lib.rs index 06ef711..8099e0a 100644 --- a/crates/username_registry_validation/src/user_metadata.rs +++ b/crates/metadata/validation/src/lib.rs @@ -1,5 +1,5 @@ use hdi::prelude::*; -use holoom_types::MetadataItem; +use metadata_types::MetadataItem; pub fn validate_create_link_user_metadata( action: CreateLink, diff --git a/crates/username_registry_coordinator/Cargo.toml b/crates/username_registry_coordinator/Cargo.toml index 4bcfb8c..0d49c3a 100644 --- a/crates/username_registry_coordinator/Cargo.toml +++ b/crates/username_registry_coordinator/Cargo.toml @@ -15,6 +15,8 @@ username_registry_integrity = { workspace = true } holoom_types = { workspace = true } username_registry_validation = { workspace = true } username_registry_utils = { workspace = true } +metadata_types = { workspace = true } +user_metadata_handlers = { workspace = true } jaq_wrapper = { workspace = true } indexmap = "2.2.6" serde_json = { workspace = true } diff --git a/crates/username_registry_coordinator/src/user_metadata.rs b/crates/username_registry_coordinator/src/user_metadata.rs index e66e53d..c3f4d70 100644 --- a/crates/username_registry_coordinator/src/user_metadata.rs +++ b/crates/username_registry_coordinator/src/user_metadata.rs @@ -1,77 +1,21 @@ use std::collections::HashMap; use hdk::prelude::*; -use holoom_types::{GetMetadataItemValuePayload, MetadataItem, UpdateMetadataItemPayload}; -use username_registry_integrity::*; +use username_registry_integrity::LinkTypes; #[hdk_extern] -pub fn update_metadata_item(payload: UpdateMetadataItemPayload) -> ExternResult<()> { - let links = get_links( - GetLinksInputBuilder::try_new(payload.agent_pubkey.clone(), LinkTypes::AgentMetadata)? - .build(), - )?; - for link in links { - let existing_item: MetadataItem = - bincode::deserialize(&link.tag.into_inner()).map_err(|_| { - wasm_error!(WasmErrorInner::Guest( - "Failed to deserialize MetadataItem".into() - )) - })?; - if existing_item.name == payload.name { - // Remove old MetadataItem - delete_link(link.create_link_hash)?; - } - } - let item = MetadataItem { - name: payload.name, - value: payload.value, - }; - let tag_bytes = bincode::serialize(&item).map_err(|_| { - wasm_error!(WasmErrorInner::Guest( - "Failed to serialize MetadataItem".into() - )) - })?; - create_link( - payload.agent_pubkey.clone(), - payload.agent_pubkey, // unused and irrelevant - LinkTypes::AgentMetadata, - LinkTag(tag_bytes), - )?; - Ok(()) +pub fn update_metadata_item(item: metadata_types::MetadataItem) -> ExternResult<()> { + user_metadata_handlers::update_item::handler::(item) } #[hdk_extern] pub fn get_metadata_item_value( - payload: GetMetadataItemValuePayload, + input: user_metadata_handlers::get_item::Input, ) -> ExternResult> { - let links = get_links( - GetLinksInputBuilder::try_new(payload.agent_pubkey, LinkTypes::AgentMetadata)?.build(), - )?; - for link in links { - let item: MetadataItem = bincode::deserialize(&link.tag.into_inner()).map_err(|_| { - wasm_error!(WasmErrorInner::Guest( - "Failed to deserialize MetadataItem".into() - )) - })?; - if payload.name == item.name { - return Ok(Some(item.value)); - } - } - Ok(None) + user_metadata_handlers::get_item::handler::(input) } #[hdk_extern] pub fn get_metadata(agent_pubkey: AgentPubKey) -> ExternResult> { - let links = - get_links(GetLinksInputBuilder::try_new(agent_pubkey, LinkTypes::AgentMetadata)?.build())?; - let mut out = HashMap::default(); - for link in links { - let item: MetadataItem = bincode::deserialize(&link.tag.into_inner()).map_err(|_| { - wasm_error!(WasmErrorInner::Guest( - "Failed to deserialize MetadataItem".into() - )) - })?; - out.insert(item.name, item.value); - } - Ok(out) + user_metadata_handlers::get_all::handler::(agent_pubkey) } diff --git a/crates/username_registry_integrity/Cargo.toml b/crates/username_registry_integrity/Cargo.toml index a0f9d12..d0b7e8f 100644 --- a/crates/username_registry_integrity/Cargo.toml +++ b/crates/username_registry_integrity/Cargo.toml @@ -12,3 +12,5 @@ hdi = { workspace = true } serde = { workspace = true } holoom_types = { workspace = true } username_registry_validation = { workspace = true } +metadata_types = { workspace = true } +metadata_validation = { workspace = true } diff --git a/crates/username_registry_integrity/src/link_types.rs b/crates/username_registry_integrity/src/link_types.rs index efe895f..26b0b27 100644 --- a/crates/username_registry_integrity/src/link_types.rs +++ b/crates/username_registry_integrity/src/link_types.rs @@ -1,4 +1,5 @@ use hdi::prelude::*; +use metadata_types::InjectMetadataLinkTypes; use username_registry_validation::*; #[derive(Serialize, Deserialize)] @@ -16,6 +17,13 @@ pub enum LinkTypes { EvmAddressToSigningOffer, } +impl InjectMetadataLinkTypes for LinkTypes { + type LinkType = LinkTypes; + fn agent_metadata() -> Self::LinkType { + Self::AgentMetadata + } +} + impl LinkTypes { pub fn validate_create( self, @@ -33,9 +41,12 @@ impl LinkTypes { tag, ) } - LinkTypes::AgentMetadata => { - validate_create_link_user_metadata(action, base_address, target_address, tag) - } + LinkTypes::AgentMetadata => metadata_validation::validate_create_link_user_metadata( + action, + base_address, + target_address, + tag, + ), LinkTypes::AgentToWalletAttestations => { validate_create_link_agent_to_wallet_attestations( action, @@ -110,7 +121,7 @@ impl LinkTypes { tag, ) } - LinkTypes::AgentMetadata => validate_delete_link_user_metadata( + LinkTypes::AgentMetadata => metadata_validation::validate_delete_link_user_metadata( action, original_action, base_address, diff --git a/crates/username_registry_validation/src/lib.rs b/crates/username_registry_validation/src/lib.rs index a39669d..062d6ad 100644 --- a/crates/username_registry_validation/src/lib.rs +++ b/crates/username_registry_validation/src/lib.rs @@ -2,8 +2,6 @@ pub mod username_attestation; pub use username_attestation::*; pub mod wallet_attestation; pub use wallet_attestation::*; -pub mod user_metadata; -pub use user_metadata::*; pub mod oracle_document; pub use oracle_document::*; pub mod agent_username_attestation; From b1277701c2885f107add1fefdafd93b3cd11886c Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Thu, 15 Aug 2024 17:38:06 +0100 Subject: [PATCH 02/17] chore: rename to user_metadata; add typeshare + doc comments --- .vscode/settings.json | 1 + Cargo.lock | 69 ++++++++++++------- Cargo.toml | 17 ++--- crates/metadata/handlers/src/get_item.rs | 31 --------- crates/metadata/types/src/lib.rs | 15 ---- .../handlers/Cargo.toml | 3 +- .../handlers/src/get_all.rs | 2 +- crates/user_metadata/handlers/src/get_item.rs | 21 ++++++ .../handlers/src/lib.rs | 0 .../handlers/src/update_item.rs | 2 +- .../types/Cargo.toml | 6 +- crates/user_metadata/types/src/lib.rs | 18 +++++ .../validation/Cargo.toml | 7 +- .../validation/src/lib.rs | 2 +- .../username_registry_coordinator/Cargo.toml | 3 +- .../src/user_metadata.rs | 25 +++++-- crates/username_registry_integrity/Cargo.toml | 4 +- .../src/link_types.rs | 32 +++++---- flake.nix | 1 + 19 files changed, 147 insertions(+), 112 deletions(-) delete mode 100644 crates/metadata/handlers/src/get_item.rs delete mode 100644 crates/metadata/types/src/lib.rs rename crates/{metadata => user_metadata}/handlers/Cargo.toml (85%) rename crates/{metadata => user_metadata}/handlers/src/get_all.rs (90%) create mode 100644 crates/user_metadata/handlers/src/get_item.rs rename crates/{metadata => user_metadata}/handlers/src/lib.rs (100%) rename crates/{metadata => user_metadata}/handlers/src/update_item.rs (94%) rename crates/{metadata => user_metadata}/types/Cargo.toml (63%) create mode 100644 crates/user_metadata/types/src/lib.rs rename crates/{metadata => user_metadata}/validation/Cargo.toml (55%) rename crates/{metadata => user_metadata}/validation/src/lib.rs (97%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 32d5af0..3b1b0fa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,7 @@ "pubkey", "Solana", "tryorama", + "typeshare", "workdir", "zome", "zomes" diff --git a/Cargo.lock b/Cargo.lock index a2ee879..1c3138d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5024,25 +5024,6 @@ dependencies = [ "autocfg 1.1.0", ] -[[package]] -name = "metadata_types" -version = "0.0.1" -dependencies = [ - "hdi", - "serde", - "ts-rs", -] - -[[package]] -name = "metadata_validation" -version = "0.0.1" -dependencies = [ - "bincode", - "hdi", - "metadata_types", - "serde", -] - [[package]] name = "mime" version = "0.3.17" @@ -8556,6 +8537,28 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "typeshare" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f17399b76c2e743d58eac0635d7686e9c00f48cd4776f00695d9882a7d3187" +dependencies = [ + "chrono", + "serde", + "serde_json", + "typeshare-annotation", +] + +[[package]] +name = "typeshare-annotation" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a615d6c2764852a2e88a4f16e9ce1ea49bb776b5872956309e170d63a042a34f" +dependencies = [ + "quote", + "syn 2.0.52", +] + [[package]] name = "ucd-trie" version = "0.1.6" @@ -8716,9 +8719,26 @@ dependencies = [ "bincode", "hdk", "hdk_utils", - "metadata_types", "serde", - "ts-rs", + "user_metadata_types", +] + +[[package]] +name = "user_metadata_types" +version = "0.0.1" +dependencies = [ + "hdi", + "serde", + "typeshare", +] + +[[package]] +name = "user_metadata_validation" +version = "0.0.1" +dependencies = [ + "bincode", + "hdi", + "user_metadata_types", ] [[package]] @@ -8731,10 +8751,11 @@ dependencies = [ "holoom_types", "indexmap 2.2.6", "jaq_wrapper", - "metadata_types", "serde", "serde_json", + "typeshare", "user_metadata_handlers", + "user_metadata_types", "username_registry_integrity", "username_registry_utils", "username_registry_validation", @@ -8746,9 +8767,9 @@ version = "0.0.1" dependencies = [ "hdi", "holoom_types", - "metadata_types", - "metadata_validation", "serde", + "user_metadata_types", + "user_metadata_validation", "username_registry_validation", ] diff --git a/Cargo.toml b/Cargo.toml index 80944af..9ca326c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,9 @@ opt-level = "z" resolver = "2" members = [ "crates/hdk_utils", - "crates/metadata/types", - "crates/metadata/handlers", - "crates/metadata/validation", + "crates/user_metadata/types", + "crates/user_metadata/handlers", + "crates/user_metadata/validation", "crates/holoom_dna_tests", "crates/holoom_types", "crates/jaq_wrapper", @@ -39,18 +39,19 @@ holochain = { version = "=0.4.0-dev.12", default-features = false, features = [ "test_utils", ] } holochain_keystore = "=0.4.0-dev.12" +typeshare = "1" [workspace.dependencies.hdk_utils] path = "crates/hdk_utils" -[workspace.dependencies.metadata_types] -path = "crates/metadata/types" +[workspace.dependencies.user_metadata_types] +path = "crates/user_metadata/types" -[workspace.dependencies.metadata_validation] -path = "crates/metadata/validation" +[workspace.dependencies.user_metadata_validation] +path = "crates/user_metadata/validation" [workspace.dependencies.user_metadata_handlers] -path = "crates/metadata/handlers" +path = "crates/user_metadata/handlers" [workspace.dependencies.holoom_types] path = "crates/holoom_types" diff --git a/crates/metadata/handlers/src/get_item.rs b/crates/metadata/handlers/src/get_item.rs deleted file mode 100644 index 684b7b4..0000000 --- a/crates/metadata/handlers/src/get_item.rs +++ /dev/null @@ -1,31 +0,0 @@ -use hdk::prelude::*; -use metadata_types::{InjectMetadataLinkTypes, MetadataItem}; -use ts_rs::TS; - -#[derive(Serialize, Deserialize, Debug, TS)] -#[ts(export)] -pub struct Input { - #[ts(type = "AgentPubKey")] - pub agent_pubkey: AgentPubKey, - pub name: String, -} - -pub fn handler(input: Input) -> ExternResult> -where - LT: InjectMetadataLinkTypes, -{ - let links = get_links( - GetLinksInputBuilder::try_new(input.agent_pubkey, LT::agent_metadata())?.build(), - )?; - for link in links { - let item: MetadataItem = bincode::deserialize(&link.tag.into_inner()).map_err(|_| { - wasm_error!(WasmErrorInner::Guest( - "Failed to deserialize MetadataItem".into() - )) - })?; - if input.name == item.name { - return Ok(Some(item.value)); - } - } - Ok(None) -} diff --git a/crates/metadata/types/src/lib.rs b/crates/metadata/types/src/lib.rs deleted file mode 100644 index 8d5648f..0000000 --- a/crates/metadata/types/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -use hdi::{link::LinkTypeFilterExt, prelude::ScopedLinkType}; -use serde::{Deserialize, Serialize}; -use ts_rs::TS; - -#[derive(Serialize, Deserialize, Debug, TS)] -#[ts(export)] -pub struct MetadataItem { - pub name: String, - pub value: String, -} - -pub trait InjectMetadataLinkTypes { - type LinkType: LinkTypeFilterExt + TryInto; - fn agent_metadata() -> Self::LinkType; -} diff --git a/crates/metadata/handlers/Cargo.toml b/crates/user_metadata/handlers/Cargo.toml similarity index 85% rename from crates/metadata/handlers/Cargo.toml rename to crates/user_metadata/handlers/Cargo.toml index d4c18ab..b73f9da 100644 --- a/crates/metadata/handlers/Cargo.toml +++ b/crates/user_metadata/handlers/Cargo.toml @@ -10,7 +10,6 @@ name = "user_metadata_handlers" [dependencies] serde = { workspace = true } hdk = { workspace = true } -metadata_types = { workspace = true } +user_metadata_types = { workspace = true } bincode = { workspace = true } hdk_utils = { workspace = true } -ts-rs = "9" diff --git a/crates/metadata/handlers/src/get_all.rs b/crates/user_metadata/handlers/src/get_all.rs similarity index 90% rename from crates/metadata/handlers/src/get_all.rs rename to crates/user_metadata/handlers/src/get_all.rs index 7568cb4..30eda43 100644 --- a/crates/metadata/handlers/src/get_all.rs +++ b/crates/user_metadata/handlers/src/get_all.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use hdk::prelude::*; -use metadata_types::{InjectMetadataLinkTypes, MetadataItem}; +use user_metadata_types::{InjectMetadataLinkTypes, MetadataItem}; pub fn handler(agent_pubkey: AgentPubKey) -> ExternResult> where diff --git a/crates/user_metadata/handlers/src/get_item.rs b/crates/user_metadata/handlers/src/get_item.rs new file mode 100644 index 0000000..3b91afe --- /dev/null +++ b/crates/user_metadata/handlers/src/get_item.rs @@ -0,0 +1,21 @@ +use hdk::prelude::*; +use user_metadata_types::{InjectMetadataLinkTypes, MetadataItem}; + +pub fn handler(agent_pubkey: AgentPubKey, name: String) -> ExternResult> +where + LT: InjectMetadataLinkTypes, +{ + let links = + get_links(GetLinksInputBuilder::try_new(agent_pubkey, LT::agent_metadata())?.build())?; + for link in links { + let item: MetadataItem = bincode::deserialize(&link.tag.into_inner()).map_err(|_| { + wasm_error!(WasmErrorInner::Guest( + "Failed to deserialize MetadataItem".into() + )) + })?; + if name == item.name { + return Ok(Some(item.value)); + } + } + Ok(None) +} diff --git a/crates/metadata/handlers/src/lib.rs b/crates/user_metadata/handlers/src/lib.rs similarity index 100% rename from crates/metadata/handlers/src/lib.rs rename to crates/user_metadata/handlers/src/lib.rs diff --git a/crates/metadata/handlers/src/update_item.rs b/crates/user_metadata/handlers/src/update_item.rs similarity index 94% rename from crates/metadata/handlers/src/update_item.rs rename to crates/user_metadata/handlers/src/update_item.rs index d0d3a5e..be22606 100644 --- a/crates/metadata/handlers/src/update_item.rs +++ b/crates/user_metadata/handlers/src/update_item.rs @@ -1,5 +1,5 @@ use hdk::prelude::*; -use metadata_types::{InjectMetadataLinkTypes, MetadataItem}; +use user_metadata_types::{InjectMetadataLinkTypes, MetadataItem}; pub fn handler(item: MetadataItem) -> ExternResult<()> where diff --git a/crates/metadata/types/Cargo.toml b/crates/user_metadata/types/Cargo.toml similarity index 63% rename from crates/metadata/types/Cargo.toml rename to crates/user_metadata/types/Cargo.toml index c75128d..5af54af 100644 --- a/crates/metadata/types/Cargo.toml +++ b/crates/user_metadata/types/Cargo.toml @@ -1,13 +1,13 @@ [package] -name = "metadata_types" +name = "user_metadata_types" version = "0.0.1" edition = "2021" [lib] crate-type = ["cdylib", "rlib"] -name = "metadata_types" +name = "user_metadata_types" [dependencies] serde = { workspace = true } hdi = { workspace = true } -ts-rs = "9" +typeshare = { workspace = true } diff --git a/crates/user_metadata/types/src/lib.rs b/crates/user_metadata/types/src/lib.rs new file mode 100644 index 0000000..20a2fc2 --- /dev/null +++ b/crates/user_metadata/types/src/lib.rs @@ -0,0 +1,18 @@ +use hdi::{link::LinkTypeFilterExt, prelude::ScopedLinkType}; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +/// A key-value pair, representing an item of a user's metadata +#[typeshare] +#[derive(Serialize, Deserialize, Debug)] +pub struct MetadataItem { + pub name: String, + pub value: String, +} + +/// A helper trait that allows the coordinator zome to inject the corresponding integrity zome's +/// relevant `LinkTypes` information into this crate, avoid a circular dependency. +pub trait InjectMetadataLinkTypes { + type LinkType: LinkTypeFilterExt + TryInto; + fn agent_metadata() -> Self::LinkType; +} diff --git a/crates/metadata/validation/Cargo.toml b/crates/user_metadata/validation/Cargo.toml similarity index 55% rename from crates/metadata/validation/Cargo.toml rename to crates/user_metadata/validation/Cargo.toml index 0b1b7cf..ea425c7 100644 --- a/crates/metadata/validation/Cargo.toml +++ b/crates/user_metadata/validation/Cargo.toml @@ -1,14 +1,13 @@ [package] -name = "metadata_validation" +name = "user_metadata_validation" version = "0.0.1" edition = "2021" [lib] crate-type = ["cdylib", "rlib"] -name = "metadata_validation" +name = "user_metadata_validation" [dependencies] hdi = { workspace = true } -serde = { workspace = true } -metadata_types = { workspace = true } +user_metadata_types = { workspace = true } bincode = { workspace = true } diff --git a/crates/metadata/validation/src/lib.rs b/crates/user_metadata/validation/src/lib.rs similarity index 97% rename from crates/metadata/validation/src/lib.rs rename to crates/user_metadata/validation/src/lib.rs index 8099e0a..0a1f504 100644 --- a/crates/metadata/validation/src/lib.rs +++ b/crates/user_metadata/validation/src/lib.rs @@ -1,5 +1,5 @@ use hdi::prelude::*; -use metadata_types::MetadataItem; +use user_metadata_types::MetadataItem; pub fn validate_create_link_user_metadata( action: CreateLink, diff --git a/crates/username_registry_coordinator/Cargo.toml b/crates/username_registry_coordinator/Cargo.toml index 0d49c3a..0034e9f 100644 --- a/crates/username_registry_coordinator/Cargo.toml +++ b/crates/username_registry_coordinator/Cargo.toml @@ -15,9 +15,10 @@ username_registry_integrity = { workspace = true } holoom_types = { workspace = true } username_registry_validation = { workspace = true } username_registry_utils = { workspace = true } -metadata_types = { workspace = true } +user_metadata_types = { workspace = true } user_metadata_handlers = { workspace = true } jaq_wrapper = { workspace = true } indexmap = "2.2.6" serde_json = { workspace = true } hex = { workspace = true } +typeshare = { workspace = true } diff --git a/crates/username_registry_coordinator/src/user_metadata.rs b/crates/username_registry_coordinator/src/user_metadata.rs index c3f4d70..fd7886b 100644 --- a/crates/username_registry_coordinator/src/user_metadata.rs +++ b/crates/username_registry_coordinator/src/user_metadata.rs @@ -1,20 +1,35 @@ use std::collections::HashMap; +use typeshare::typeshare; use hdk::prelude::*; use username_registry_integrity::LinkTypes; +/// Upsert a key-value item of your own metadata #[hdk_extern] -pub fn update_metadata_item(item: metadata_types::MetadataItem) -> ExternResult<()> { +pub fn update_metadata_item(item: user_metadata_types::MetadataItem) -> ExternResult<()> { user_metadata_handlers::update_item::handler::(item) } +/// The input argument to `get_metadata_item_value`` +#[typeshare] +#[derive(Serialize, Deserialize, Debug)] +pub struct GetMetadataItemValueInput { + /// The agent whose metadata you wish to query + pub agent_pubkey: AgentPubKey, + /// the key for the particular metadata item + pub name: String, +} + +/// Retrieve a particular metadata value for a given key on an agent. +/// +/// Return `None` if the key-pair isn't found, which means the user hasn't set it, or the +/// information hasn't been gossiped. #[hdk_extern] -pub fn get_metadata_item_value( - input: user_metadata_handlers::get_item::Input, -) -> ExternResult> { - user_metadata_handlers::get_item::handler::(input) +pub fn get_metadata_item_value(input: GetMetadataItemValueInput) -> ExternResult> { + user_metadata_handlers::get_item::handler::(input.agent_pubkey, input.name) } +/// Retrieves all metadata known for the specified user as key-value map #[hdk_extern] pub fn get_metadata(agent_pubkey: AgentPubKey) -> ExternResult> { user_metadata_handlers::get_all::handler::(agent_pubkey) diff --git a/crates/username_registry_integrity/Cargo.toml b/crates/username_registry_integrity/Cargo.toml index d0b7e8f..45df262 100644 --- a/crates/username_registry_integrity/Cargo.toml +++ b/crates/username_registry_integrity/Cargo.toml @@ -12,5 +12,5 @@ hdi = { workspace = true } serde = { workspace = true } holoom_types = { workspace = true } username_registry_validation = { workspace = true } -metadata_types = { workspace = true } -metadata_validation = { workspace = true } +user_metadata_types = { workspace = true } +user_metadata_validation = { workspace = true } diff --git a/crates/username_registry_integrity/src/link_types.rs b/crates/username_registry_integrity/src/link_types.rs index 26b0b27..98e1892 100644 --- a/crates/username_registry_integrity/src/link_types.rs +++ b/crates/username_registry_integrity/src/link_types.rs @@ -1,5 +1,5 @@ use hdi::prelude::*; -use metadata_types::InjectMetadataLinkTypes; +use user_metadata_types::InjectMetadataLinkTypes; use username_registry_validation::*; #[derive(Serialize, Deserialize)] @@ -41,12 +41,14 @@ impl LinkTypes { tag, ) } - LinkTypes::AgentMetadata => metadata_validation::validate_create_link_user_metadata( - action, - base_address, - target_address, - tag, - ), + LinkTypes::AgentMetadata => { + user_metadata_validation::validate_create_link_user_metadata( + action, + base_address, + target_address, + tag, + ) + } LinkTypes::AgentToWalletAttestations => { validate_create_link_agent_to_wallet_attestations( action, @@ -121,13 +123,15 @@ impl LinkTypes { tag, ) } - LinkTypes::AgentMetadata => metadata_validation::validate_delete_link_user_metadata( - action, - original_action, - base_address, - target_address, - tag, - ), + LinkTypes::AgentMetadata => { + user_metadata_validation::validate_delete_link_user_metadata( + action, + original_action, + base_address, + target_address, + tag, + ) + } LinkTypes::AgentToWalletAttestations => { validate_delete_link_agent_to_wallet_attestations( action, diff --git a/flake.nix b/flake.nix index 16fa305..b741d16 100644 --- a/flake.nix +++ b/flake.nix @@ -21,6 +21,7 @@ # add further packages from nixpkgs nodejs cargo-nextest + typeshare ]; }; }; From 4f9f28cf11e0fe886567a4e0111f8c8360107986 Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Fri, 16 Aug 2024 10:40:09 +0100 Subject: [PATCH 03/17] fix: unwanted wbindgen exports --- Cargo.lock | 6 ++---- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c3138d..36944fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8540,8 +8540,7 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "typeshare" version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f17399b76c2e743d58eac0635d7686e9c00f48cd4776f00695d9882a7d3187" +source = "git+https://github.com/8e8b2c/typeshare?rev=ccb29d34411824f2758109c3e844a8f8bc147b63#ccb29d34411824f2758109c3e844a8f8bc147b63" dependencies = [ "chrono", "serde", @@ -8552,8 +8551,7 @@ dependencies = [ [[package]] name = "typeshare-annotation" version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a615d6c2764852a2e88a4f16e9ce1ea49bb776b5872956309e170d63a042a34f" +source = "git+https://github.com/8e8b2c/typeshare?rev=ccb29d34411824f2758109c3e844a8f8bc147b63#ccb29d34411824f2758109c3e844a8f8bc147b63" dependencies = [ "quote", "syn 2.0.52", diff --git a/Cargo.toml b/Cargo.toml index 9ca326c..437e394 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ holochain = { version = "=0.4.0-dev.12", default-features = false, features = [ "test_utils", ] } holochain_keystore = "=0.4.0-dev.12" -typeshare = "1" +typeshare = { git = "https://github.com/8e8b2c/typeshare", rev = "ccb29d34411824f2758109c3e844a8f8bc147b63", default-features = false } [workspace.dependencies.hdk_utils] path = "crates/hdk_utils" From c580766d02a9230971ea240fc2d8e0580832fc8b Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Fri, 16 Aug 2024 10:42:43 +0100 Subject: [PATCH 04/17] feat: hook up typeshare artefacts --- .../src/user_metadata.rs | 3 +- package-lock.json | 464 ++++++++++++++++++ packages/client/src/holoom-client.ts | 1 - packages/types/package.json | 4 +- .../types/scripts/add-imports-to-typeshare.ts | 27 + .../types/scripts/extract-fn-bindings.mjs | 40 +- packages/types/src/dependency-types.ts | 6 + packages/types/src/index.ts | 2 + .../src/types/GetMetadataItemValuePayload.ts | 7 - packages/types/src/types/MetadataItem.ts | 3 - .../src/types/UpdateMetadataItemPayload.ts | 8 - packages/types/src/types/index.ts | 3 - packages/types/src/typeshare-generated.ts | 20 + .../UsernameRegistryCoordinator.ts | 10 +- 14 files changed, 566 insertions(+), 32 deletions(-) create mode 100644 packages/types/scripts/add-imports-to-typeshare.ts create mode 100644 packages/types/src/dependency-types.ts delete mode 100644 packages/types/src/types/GetMetadataItemValuePayload.ts delete mode 100644 packages/types/src/types/MetadataItem.ts delete mode 100644 packages/types/src/types/UpdateMetadataItemPayload.ts create mode 100644 packages/types/src/typeshare-generated.ts diff --git a/crates/username_registry_coordinator/src/user_metadata.rs b/crates/username_registry_coordinator/src/user_metadata.rs index fd7886b..16a51a8 100644 --- a/crates/username_registry_coordinator/src/user_metadata.rs +++ b/crates/username_registry_coordinator/src/user_metadata.rs @@ -2,11 +2,12 @@ use std::collections::HashMap; use typeshare::typeshare; use hdk::prelude::*; +use user_metadata_types::MetadataItem; use username_registry_integrity::LinkTypes; /// Upsert a key-value item of your own metadata #[hdk_extern] -pub fn update_metadata_item(item: user_metadata_types::MetadataItem) -> ExternResult<()> { +pub fn update_metadata_item(item: MetadataItem) -> ExternResult<()> { user_metadata_handlers::update_item::handler::(item) } diff --git a/package-lock.json b/package-lock.json index 7e70819..5d27e4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -942,6 +942,22 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", + "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", @@ -5405,6 +5421,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", + "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/get-uri": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", @@ -8511,6 +8539,15 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", @@ -9662,6 +9699,432 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, + "node_modules/tsx": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.17.0.tgz", + "integrity": "sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==", + "dev": true, + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", + "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", + "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", + "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", + "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", + "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", + "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", + "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", + "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", + "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", + "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", + "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", + "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", + "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", + "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", + "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", + "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", + "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", + "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", + "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", + "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", + "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", + "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", + "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.0", + "@esbuild/android-arm": "0.23.0", + "@esbuild/android-arm64": "0.23.0", + "@esbuild/android-x64": "0.23.0", + "@esbuild/darwin-arm64": "0.23.0", + "@esbuild/darwin-x64": "0.23.0", + "@esbuild/freebsd-arm64": "0.23.0", + "@esbuild/freebsd-x64": "0.23.0", + "@esbuild/linux-arm": "0.23.0", + "@esbuild/linux-arm64": "0.23.0", + "@esbuild/linux-ia32": "0.23.0", + "@esbuild/linux-loong64": "0.23.0", + "@esbuild/linux-mips64el": "0.23.0", + "@esbuild/linux-ppc64": "0.23.0", + "@esbuild/linux-riscv64": "0.23.0", + "@esbuild/linux-s390x": "0.23.0", + "@esbuild/linux-x64": "0.23.0", + "@esbuild/netbsd-x64": "0.23.0", + "@esbuild/openbsd-arm64": "0.23.0", + "@esbuild/openbsd-x64": "0.23.0", + "@esbuild/sunos-x64": "0.23.0", + "@esbuild/win32-arm64": "0.23.0", + "@esbuild/win32-ia32": "0.23.0", + "@esbuild/win32-x64": "0.23.0" + } + }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -10965,6 +11428,7 @@ "rimraf": "^5.0.5", "rollup": "^4.12.0", "rollup-plugin-cleanup": "^3.2.1", + "tsx": "^4.17.0", "typedoc": "^0.25.13" }, "peerDependencies": { diff --git a/packages/client/src/holoom-client.ts b/packages/client/src/holoom-client.ts index 2d2a48f..05276f2 100644 --- a/packages/client/src/holoom-client.ts +++ b/packages/client/src/holoom-client.ts @@ -95,7 +95,6 @@ export class HoloomClient { */ async setMetadata(name: string, value: string) { await this.usernameRegistryCoordinator.updateMetadataItem({ - agent_pubkey: this.myPubKey, name, value, }); diff --git a/packages/types/package.json b/packages/types/package.json index 9ea45d8..face4ac 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -30,7 +30,8 @@ }, "scripts": { "build": "rimraf dist && npm run build:browser && npm run build:node", - "prepare:bindings": "rimraf src/types/* && node scripts/prepare-bindings.mjs && rimraf src/zome-functions/* && node scripts/extract-fn-bindings.mjs", + "typeshare": "typeshare -l typescript -o src/typeshare-generated.ts ../../crates && tsx scripts/add-imports-to-typeshare.ts", + "prepare:bindings": "npm run typeshare && rimraf src/types/* && node scripts/prepare-bindings.mjs && rimraf src/zome-functions/* && node scripts/extract-fn-bindings.mjs", "build:browser": "rollup -c rollup.browser.config.ts --configPlugin typescript", "build:node": "rollup -c rollup.node.config.ts --configPlugin typescript" }, @@ -47,6 +48,7 @@ "rimraf": "^5.0.5", "rollup": "^4.12.0", "rollup-plugin-cleanup": "^3.2.1", + "tsx": "^4.17.0", "typedoc": "^0.25.13" }, "peerDependencies": { diff --git a/packages/types/scripts/add-imports-to-typeshare.ts b/packages/types/scripts/add-imports-to-typeshare.ts new file mode 100644 index 0000000..061796a --- /dev/null +++ b/packages/types/scripts/add-imports-to-typeshare.ts @@ -0,0 +1,27 @@ +import fs from "fs/promises"; +import prettier from "prettier"; + +const HOLOCHAIN_TYPES = ["ActionHash", "AgentPubKey", "Record", "Signature"]; + +const DEPENDENCY_TYPES_PATH = "src/dependency-types.ts"; +const TYPESHARE_GENERATED_PATH = "src/typeshare-generated.ts"; + +async function main() { + const depTypesContent = await fs.readFile(DEPENDENCY_TYPES_PATH, "utf8"); + const depTypes = Array.from( + depTypesContent.matchAll(/\nexport\s+\w+\s+(\w+)/g) + ).map((match) => match[1]); + + let content = await fs.readFile(TYPESHARE_GENERATED_PATH, "utf8"); + content = + ` + // Import prepended by scripts/add-imports-to-typeshare.ts + import {${HOLOCHAIN_TYPES.join(", ")}} from "@holochain/client"; + import {${depTypes.join(", ")}} from "./dependency-types"; + ` + content; + + content = await prettier.format(content, { parser: "typescript" }); + await fs.writeFile(TYPESHARE_GENERATED_PATH, content); +} + +main().catch(console.error); diff --git a/packages/types/scripts/extract-fn-bindings.mjs b/packages/types/scripts/extract-fn-bindings.mjs index e82d6f3..7ae02f7 100644 --- a/packages/types/scripts/extract-fn-bindings.mjs +++ b/packages/types/scripts/extract-fn-bindings.mjs @@ -91,13 +91,24 @@ async function extractFnBindingsForCrate(name, typesTransform) { } }); - const splitDeps = { holochain: ["AppClient"], holoom: [] }; + const splitDeps = { + holochain: ["AppClient"], + holoom: [], + deps: [], + typeshare: [], + }; for (const dep of deps) { const [location, name] = dep.split(":"); splitDeps[location].push(name); } let classFile = `import {${splitDeps.holochain.sort().join(", ")}} from '@holochain/client';\n`; + if (splitDeps.deps.length) { + classFile += `import {${splitDeps.deps.sort().join(", ")}} from '../dependency-types';\n`; + } + if (splitDeps.typeshare.length) { + classFile += `import {${splitDeps.typeshare.sort().join(", ")}} from '../typeshare-generated';\n`; + } if (splitDeps.holoom.length) { classFile += `import {${splitDeps.holoom.sort().join(", ")}} from '../types';\n`; } @@ -134,10 +145,27 @@ const HOLOCHAIN_TYPES = ["ActionHash", "AgentPubKey", "Record", "Signature"]; class TypeTransform { static async init() { - const tsFiles = await fs.readdir(`${CRATES_DIR}/holoom_types/bindings`); - const holoomTypes = tsFiles.map((name) => name.slice(0, -3)); const transform = new TypeTransform(); - transform.holoomTypes = holoomTypes; + + const tsFiles = await fs.readdir(`${CRATES_DIR}/holoom_types/bindings`); + transform.holoomTypes = tsFiles.map((name) => name.slice(0, -3)); + + const depTypesContent = await fs.readFile( + "src/dependency-types.ts", + "utf8" + ); + transform.depTypes = Array.from( + depTypesContent.matchAll(/\nexport\s+\w+\s+(\w+)/g) + ).map((match) => match[1]); + + const typeshareContent = await fs.readFile( + "src/typeshare-generated.ts", + "utf8" + ); + transform.typeshareTypes = Array.from( + typeshareContent.matchAll(/\nexport\s+\w+\s+(\w+)/g) + ).map((match) => match[1]); + return transform; } @@ -219,6 +247,10 @@ class TypeTransform { transformShallow(rustType) { if (HOLOCHAIN_TYPES.includes(rustType)) { return { type: rustType, deps: new Set([`holochain:${rustType}`]) }; + } else if (this.depTypes.includes(rustType)) { + return { type: rustType, deps: new Set([`dep:${rustType}`]) }; + } else if (this.typeshareTypes.includes(rustType)) { + return { type: rustType, deps: new Set([`typeshare:${rustType}`]) }; } else if (this.holoomTypes.includes(rustType)) { return { type: rustType, deps: new Set([`holoom:${rustType}`]) }; } diff --git a/packages/types/src/dependency-types.ts b/packages/types/src/dependency-types.ts new file mode 100644 index 0000000..edb070c --- /dev/null +++ b/packages/types/src/dependency-types.ts @@ -0,0 +1,6 @@ +// typeshare isn't able to generate types outside scope of the source code +// being parsed. Instead, we hand-author them here. We don't currently have a +// strategy for ensuring that these types stay in sync with the external rust +// crates that they represent types from. + +export type EthAddress = Uint8Array; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 94c610c..8e18db4 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,2 +1,4 @@ export * from "./types"; export * from "./zome-functions"; +export * from "./typeshare-generated"; +export * from "./dependency-types"; diff --git a/packages/types/src/types/GetMetadataItemValuePayload.ts b/packages/types/src/types/GetMetadataItemValuePayload.ts deleted file mode 100644 index 12ba4eb..0000000 --- a/packages/types/src/types/GetMetadataItemValuePayload.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AgentPubKey } from "@holochain/client"; -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type GetMetadataItemValuePayload = { - agent_pubkey: AgentPubKey; - name: string; -}; diff --git a/packages/types/src/types/MetadataItem.ts b/packages/types/src/types/MetadataItem.ts deleted file mode 100644 index 3aa80a0..0000000 --- a/packages/types/src/types/MetadataItem.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type MetadataItem = { name: string; value: string }; diff --git a/packages/types/src/types/UpdateMetadataItemPayload.ts b/packages/types/src/types/UpdateMetadataItemPayload.ts deleted file mode 100644 index 2fe23a7..0000000 --- a/packages/types/src/types/UpdateMetadataItemPayload.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { AgentPubKey } from "@holochain/client"; -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type UpdateMetadataItemPayload = { - agent_pubkey: AgentPubKey; - name: string; - value: string; -}; diff --git a/packages/types/src/types/index.ts b/packages/types/src/types/index.ts index 9ad428a..9f2144b 100644 --- a/packages/types/src/types/index.ts +++ b/packages/types/src/types/index.ts @@ -7,11 +7,9 @@ export * from "./EvmSigningOffer"; export * from "./EvmU256Item"; export * from "./ExecuteRecipePayload"; export * from "./ExternalIdAttestation"; -export * from "./GetMetadataItemValuePayload"; export * from "./IngestExternalIdAttestationRequestPayload"; export * from "./JqInstructionArgumentNames"; export * from "./LocalHoloomSignal"; -export * from "./MetadataItem"; export * from "./OracleDocument"; export * from "./Recipe"; export * from "./RecipeArgument"; @@ -28,6 +26,5 @@ export * from "./SignableBytes"; export * from "./SignedEvmSigningOffer"; export * from "./SignedEvmU256Array"; export * from "./SignedUsername"; -export * from "./UpdateMetadataItemPayload"; export * from "./UsernameAttestation"; export * from "./WalletAttestation"; diff --git a/packages/types/src/typeshare-generated.ts b/packages/types/src/typeshare-generated.ts new file mode 100644 index 0000000..eb5c485 --- /dev/null +++ b/packages/types/src/typeshare-generated.ts @@ -0,0 +1,20 @@ +// Import prepended by scripts/add-imports-to-typeshare.ts +import { ActionHash, AgentPubKey, Record, Signature } from "@holochain/client"; +import { EthAddress } from "./dependency-types"; +/* + Generated by typeshare 1.9.2 +*/ + +/** A key-value pair, representing an item of a user's metadata */ +export interface MetadataItem { + name: string; + value: string; +} + +/** The input argument to `get_metadata_item_value`` */ +export interface GetMetadataItemValueInput { + /** The agent whose metadata you wish to query */ + agent_pubkey: AgentPubKey; + /** the key for the particular metadata item */ + name: string; +} diff --git a/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts b/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts index 63d06c1..675a7c5 100644 --- a/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts +++ b/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts @@ -1,4 +1,8 @@ import { ActionHash, AgentPubKey, AppClient, Record } from "@holochain/client"; +import { + GetMetadataItemValueInput, + MetadataItem, +} from "../typeshare-generated"; import { ChainWalletSignature, ConfirmExternalIdRequestPayload, @@ -7,7 +11,6 @@ import { EvmSignatureOverRecipeExecutionRequest, ExecuteRecipePayload, ExternalIdAttestation, - GetMetadataItemValuePayload, IngestExternalIdAttestationRequestPayload, OracleDocument, Recipe, @@ -17,7 +20,6 @@ import { ResolveEvmSignatureOverRecipeExecutionRequestPayload, SendExternalIdAttestationRequestPayload, SignedUsername, - UpdateMetadataItemPayload, UsernameAttestation, WalletAttestation, } from "../types"; @@ -240,7 +242,7 @@ export class UsernameRegistryCoordinator { } async getMetadataItemValue( - payload: GetMetadataItemValuePayload, + payload: GetMetadataItemValueInput, ): Promise { return this.client.callZome({ role_name: this.roleName, @@ -441,7 +443,7 @@ export class UsernameRegistryCoordinator { }); } - async updateMetadataItem(payload: UpdateMetadataItemPayload): Promise { + async updateMetadataItem(payload: MetadataItem): Promise { return this.client.callZome({ role_name: this.roleName, zome_name: this.zomeName, From ec2356393171d980647b5c1cda5d70aaaed7136c Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Fri, 16 Aug 2024 10:58:13 +0100 Subject: [PATCH 05/17] chore: update metadata sweettest --- Cargo.lock | 2 ++ crates/holoom_dna_tests/Cargo.toml | 2 ++ .../src/tests/username_registry/user_metadata.rs | 11 +++++------ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36944fd..7b43871 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3842,6 +3842,8 @@ dependencies = [ "holoom_types", "serde", "tokio", + "user_metadata_types", + "username_registry_coordinator", "username_registry_utils", "username_registry_validation", ] diff --git a/crates/holoom_dna_tests/Cargo.toml b/crates/holoom_dna_tests/Cargo.toml index 4bce3ba..e17dfd6 100644 --- a/crates/holoom_dna_tests/Cargo.toml +++ b/crates/holoom_dna_tests/Cargo.toml @@ -10,6 +10,8 @@ name = "holoom_dna_tests" [dependencies] serde = { workspace = true } holoom_types = { workspace = true } +user_metadata_types = { workspace = true } +username_registry_coordinator = { workspace = true } hdk = { workspace = true, features = ["encoding", "test_utils"] } holochain = { workspace = true, default-features = false, features = [ "test_utils", diff --git a/crates/holoom_dna_tests/src/tests/username_registry/user_metadata.rs b/crates/holoom_dna_tests/src/tests/username_registry/user_metadata.rs index 2ade9a5..5aa0964 100644 --- a/crates/holoom_dna_tests/src/tests/username_registry/user_metadata.rs +++ b/crates/holoom_dna_tests/src/tests/username_registry/user_metadata.rs @@ -1,7 +1,8 @@ use std::collections::HashMap; use holochain::conductor::api::error::ConductorApiError; -use holoom_types::{GetMetadataItemValuePayload, UpdateMetadataItemPayload}; +use user_metadata_types::MetadataItem; +use username_registry_coordinator::user_metadata::GetMetadataItemValueInput; use crate::TestSetup; @@ -22,8 +23,7 @@ async fn users_can_only_update_their_own_metadata() { .bob_call( "username_registry", "update_metadata_item", - UpdateMetadataItemPayload { - agent_pubkey: setup.alice_pubkey(), + MetadataItem { name: "foo".into(), value: "bar".into(), }, @@ -36,8 +36,7 @@ async fn users_can_only_update_their_own_metadata() { .alice_call( "username_registry", "update_metadata_item", - UpdateMetadataItemPayload { - agent_pubkey: setup.alice_pubkey(), + MetadataItem { name: "foo".into(), value: "bar2".into(), }, @@ -52,7 +51,7 @@ async fn users_can_only_update_their_own_metadata() { .bob_call( "username_registry", "get_metadata_item_value", - GetMetadataItemValuePayload { + GetMetadataItemValueInput { agent_pubkey: setup.alice_pubkey(), name: "foo".into(), }, From 5e248989bb3a4cbbd2e0d89fee39dbaff087517f Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Fri, 16 Aug 2024 11:22:19 +0100 Subject: [PATCH 06/17] fix: rollback update_metadata_item interface change for sake of test --- Cargo.lock | 1 - crates/holoom_dna_tests/Cargo.toml | 1 - .../tests/username_registry/user_metadata.rs | 11 +++++--- .../user_metadata/handlers/src/update_item.rs | 3 +-- .../src/user_metadata.rs | 25 ++++++++++++++++--- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b43871..62ca539 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3842,7 +3842,6 @@ dependencies = [ "holoom_types", "serde", "tokio", - "user_metadata_types", "username_registry_coordinator", "username_registry_utils", "username_registry_validation", diff --git a/crates/holoom_dna_tests/Cargo.toml b/crates/holoom_dna_tests/Cargo.toml index e17dfd6..18ab06c 100644 --- a/crates/holoom_dna_tests/Cargo.toml +++ b/crates/holoom_dna_tests/Cargo.toml @@ -10,7 +10,6 @@ name = "holoom_dna_tests" [dependencies] serde = { workspace = true } holoom_types = { workspace = true } -user_metadata_types = { workspace = true } username_registry_coordinator = { workspace = true } hdk = { workspace = true, features = ["encoding", "test_utils"] } holochain = { workspace = true, default-features = false, features = [ diff --git a/crates/holoom_dna_tests/src/tests/username_registry/user_metadata.rs b/crates/holoom_dna_tests/src/tests/username_registry/user_metadata.rs index 5aa0964..ac0eb70 100644 --- a/crates/holoom_dna_tests/src/tests/username_registry/user_metadata.rs +++ b/crates/holoom_dna_tests/src/tests/username_registry/user_metadata.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; use holochain::conductor::api::error::ConductorApiError; -use user_metadata_types::MetadataItem; -use username_registry_coordinator::user_metadata::GetMetadataItemValueInput; +use username_registry_coordinator::user_metadata::{ + GetMetadataItemValueInput, UpdateMetadataItemInput, +}; use crate::TestSetup; @@ -23,7 +24,8 @@ async fn users_can_only_update_their_own_metadata() { .bob_call( "username_registry", "update_metadata_item", - MetadataItem { + UpdateMetadataItemInput { + agent_pubkey: setup.alice_pubkey(), name: "foo".into(), value: "bar".into(), }, @@ -36,7 +38,8 @@ async fn users_can_only_update_their_own_metadata() { .alice_call( "username_registry", "update_metadata_item", - MetadataItem { + UpdateMetadataItemInput { + agent_pubkey: setup.alice_pubkey(), name: "foo".into(), value: "bar2".into(), }, diff --git a/crates/user_metadata/handlers/src/update_item.rs b/crates/user_metadata/handlers/src/update_item.rs index be22606..244caac 100644 --- a/crates/user_metadata/handlers/src/update_item.rs +++ b/crates/user_metadata/handlers/src/update_item.rs @@ -1,11 +1,10 @@ use hdk::prelude::*; use user_metadata_types::{InjectMetadataLinkTypes, MetadataItem}; -pub fn handler(item: MetadataItem) -> ExternResult<()> +pub fn handler(agent_pubkey: AgentPubKey, item: MetadataItem) -> ExternResult<()> where LT: InjectMetadataLinkTypes, { - let agent_pubkey = agent_info()?.agent_initial_pubkey; let links = get_links( GetLinksInputBuilder::try_new(agent_pubkey.clone(), LT::agent_metadata())?.build(), )?; diff --git a/crates/username_registry_coordinator/src/user_metadata.rs b/crates/username_registry_coordinator/src/user_metadata.rs index 16a51a8..5980ddf 100644 --- a/crates/username_registry_coordinator/src/user_metadata.rs +++ b/crates/username_registry_coordinator/src/user_metadata.rs @@ -5,10 +5,29 @@ use hdk::prelude::*; use user_metadata_types::MetadataItem; use username_registry_integrity::LinkTypes; +/// The input argument to `update_metadata_item`` +#[typeshare] +#[derive(Serialize, Deserialize, Debug)] +pub struct UpdateMetadataItemInput { + /// This has to be set to your own key. The only reason this field isn't + /// instead inferred is for the sake of enabling testing of the fail case. + pub agent_pubkey: AgentPubKey, + /// The key for the particular metadata item + pub name: String, + /// The value to assign to the key + pub value: String, +} + /// Upsert a key-value item of your own metadata #[hdk_extern] -pub fn update_metadata_item(item: MetadataItem) -> ExternResult<()> { - user_metadata_handlers::update_item::handler::(item) +pub fn update_metadata_item(input: UpdateMetadataItemInput) -> ExternResult<()> { + user_metadata_handlers::update_item::handler::( + input.agent_pubkey, + MetadataItem { + name: input.name, + value: input.value, + }, + ) } /// The input argument to `get_metadata_item_value`` @@ -17,7 +36,7 @@ pub fn update_metadata_item(item: MetadataItem) -> ExternResult<()> { pub struct GetMetadataItemValueInput { /// The agent whose metadata you wish to query pub agent_pubkey: AgentPubKey, - /// the key for the particular metadata item + /// The key for the particular metadata item pub name: String, } From 4960c85d0634cc8e7aff1e86dc1529b8ff2bd6da Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Fri, 16 Aug 2024 11:56:01 +0100 Subject: [PATCH 07/17] fix: oops - part of prev commit rollback --- packages/types/src/typeshare-generated.ts | 15 ++++++++++++++- .../zome-functions/UsernameRegistryCoordinator.ts | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/types/src/typeshare-generated.ts b/packages/types/src/typeshare-generated.ts index eb5c485..fba40e2 100644 --- a/packages/types/src/typeshare-generated.ts +++ b/packages/types/src/typeshare-generated.ts @@ -11,10 +11,23 @@ export interface MetadataItem { value: string; } +/** The input argument to `update_metadata_item`` */ +export interface UpdateMetadataItemInput { + /** + * This has to be set to your own key. The only reason this field isn't + * instead inferred is for the sake of enabling testing of the fail case. + */ + agent_pubkey: AgentPubKey; + /** The key for the particular metadata item */ + name: string; + /** The value to assign to the key */ + value: string; +} + /** The input argument to `get_metadata_item_value`` */ export interface GetMetadataItemValueInput { /** The agent whose metadata you wish to query */ agent_pubkey: AgentPubKey; - /** the key for the particular metadata item */ + /** The key for the particular metadata item */ name: string; } diff --git a/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts b/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts index 675a7c5..c069707 100644 --- a/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts +++ b/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts @@ -1,7 +1,7 @@ import { ActionHash, AgentPubKey, AppClient, Record } from "@holochain/client"; import { GetMetadataItemValueInput, - MetadataItem, + UpdateMetadataItemInput, } from "../typeshare-generated"; import { ChainWalletSignature, @@ -443,7 +443,7 @@ export class UsernameRegistryCoordinator { }); } - async updateMetadataItem(payload: MetadataItem): Promise { + async updateMetadataItem(payload: UpdateMetadataItemInput): Promise { return this.client.callZome({ role_name: this.roleName, zome_name: this.zomeName, From a04023ffec51173d765a13e25cc4366b392c5556 Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Fri, 16 Aug 2024 11:56:54 +0100 Subject: [PATCH 08/17] chore: mv user_metadata to features directory --- Cargo.toml | 24 ++++++------------- .../user_metadata/handlers/Cargo.toml | 0 .../user_metadata/handlers/src/get_all.rs | 0 .../user_metadata/handlers/src/get_item.rs | 0 .../user_metadata/handlers/src/lib.rs | 0 .../user_metadata/handlers/src/update_item.rs | 0 .../user_metadata/types/Cargo.toml | 0 .../user_metadata/types/src/lib.rs | 0 .../user_metadata/validation/Cargo.toml | 0 .../user_metadata/validation/src/lib.rs | 0 packages/types/package.json | 2 +- 11 files changed, 8 insertions(+), 18 deletions(-) rename {crates => features}/user_metadata/handlers/Cargo.toml (100%) rename {crates => features}/user_metadata/handlers/src/get_all.rs (100%) rename {crates => features}/user_metadata/handlers/src/get_item.rs (100%) rename {crates => features}/user_metadata/handlers/src/lib.rs (100%) rename {crates => features}/user_metadata/handlers/src/update_item.rs (100%) rename {crates => features}/user_metadata/types/Cargo.toml (100%) rename {crates => features}/user_metadata/types/src/lib.rs (100%) rename {crates => features}/user_metadata/validation/Cargo.toml (100%) rename {crates => features}/user_metadata/validation/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 437e394..f0003b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,20 +7,10 @@ opt-level = "z" [workspace] resolver = "2" members = [ - "crates/hdk_utils", - "crates/user_metadata/types", - "crates/user_metadata/handlers", - "crates/user_metadata/validation", - "crates/holoom_dna_tests", - "crates/holoom_types", - "crates/jaq_wrapper", - "crates/ping_coordinator", - "crates/records_coordinator", - "crates/signer_coordinator", - "crates/username_registry_coordinator", - "crates/username_registry_integrity", - "crates/username_registry_utils", - "crates/username_registry_validation", + "features/*/types", + "features/*/validation", + "features/*/handlers", + "crates/*", ] [workspace.dependencies] @@ -45,13 +35,13 @@ typeshare = { git = "https://github.com/8e8b2c/typeshare", rev = "ccb29d34411824 path = "crates/hdk_utils" [workspace.dependencies.user_metadata_types] -path = "crates/user_metadata/types" +path = "features/user_metadata/types" [workspace.dependencies.user_metadata_validation] -path = "crates/user_metadata/validation" +path = "features/user_metadata/validation" [workspace.dependencies.user_metadata_handlers] -path = "crates/user_metadata/handlers" +path = "features/user_metadata/handlers" [workspace.dependencies.holoom_types] path = "crates/holoom_types" diff --git a/crates/user_metadata/handlers/Cargo.toml b/features/user_metadata/handlers/Cargo.toml similarity index 100% rename from crates/user_metadata/handlers/Cargo.toml rename to features/user_metadata/handlers/Cargo.toml diff --git a/crates/user_metadata/handlers/src/get_all.rs b/features/user_metadata/handlers/src/get_all.rs similarity index 100% rename from crates/user_metadata/handlers/src/get_all.rs rename to features/user_metadata/handlers/src/get_all.rs diff --git a/crates/user_metadata/handlers/src/get_item.rs b/features/user_metadata/handlers/src/get_item.rs similarity index 100% rename from crates/user_metadata/handlers/src/get_item.rs rename to features/user_metadata/handlers/src/get_item.rs diff --git a/crates/user_metadata/handlers/src/lib.rs b/features/user_metadata/handlers/src/lib.rs similarity index 100% rename from crates/user_metadata/handlers/src/lib.rs rename to features/user_metadata/handlers/src/lib.rs diff --git a/crates/user_metadata/handlers/src/update_item.rs b/features/user_metadata/handlers/src/update_item.rs similarity index 100% rename from crates/user_metadata/handlers/src/update_item.rs rename to features/user_metadata/handlers/src/update_item.rs diff --git a/crates/user_metadata/types/Cargo.toml b/features/user_metadata/types/Cargo.toml similarity index 100% rename from crates/user_metadata/types/Cargo.toml rename to features/user_metadata/types/Cargo.toml diff --git a/crates/user_metadata/types/src/lib.rs b/features/user_metadata/types/src/lib.rs similarity index 100% rename from crates/user_metadata/types/src/lib.rs rename to features/user_metadata/types/src/lib.rs diff --git a/crates/user_metadata/validation/Cargo.toml b/features/user_metadata/validation/Cargo.toml similarity index 100% rename from crates/user_metadata/validation/Cargo.toml rename to features/user_metadata/validation/Cargo.toml diff --git a/crates/user_metadata/validation/src/lib.rs b/features/user_metadata/validation/src/lib.rs similarity index 100% rename from crates/user_metadata/validation/src/lib.rs rename to features/user_metadata/validation/src/lib.rs diff --git a/packages/types/package.json b/packages/types/package.json index face4ac..45fbaec 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -30,7 +30,7 @@ }, "scripts": { "build": "rimraf dist && npm run build:browser && npm run build:node", - "typeshare": "typeshare -l typescript -o src/typeshare-generated.ts ../../crates && tsx scripts/add-imports-to-typeshare.ts", + "typeshare": "typeshare -l typescript -o src/typeshare-generated.ts ../../features ../../crates && tsx scripts/add-imports-to-typeshare.ts", "prepare:bindings": "npm run typeshare && rimraf src/types/* && node scripts/prepare-bindings.mjs && rimraf src/zome-functions/* && node scripts/extract-fn-bindings.mjs", "build:browser": "rollup -c rollup.browser.config.ts --configPlugin typescript", "build:node": "rollup -c rollup.node.config.ts --configPlugin typescript" From 020f9c49c5d49ea6655663bbe89fdf057e831d91 Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Fri, 16 Aug 2024 12:01:35 +0100 Subject: [PATCH 09/17] fix: also missing from rollback --- packages/client/src/holoom-client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/src/holoom-client.ts b/packages/client/src/holoom-client.ts index 05276f2..2d2a48f 100644 --- a/packages/client/src/holoom-client.ts +++ b/packages/client/src/holoom-client.ts @@ -95,6 +95,7 @@ export class HoloomClient { */ async setMetadata(name: string, value: string) { await this.usernameRegistryCoordinator.updateMetadataItem({ + agent_pubkey: this.myPubKey, name, value, }); From ad9e2f64bbc63d0bbee1b2fc88145a27ee9e3b02 Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Fri, 16 Aug 2024 16:24:34 +0100 Subject: [PATCH 10/17] chore: port metadata test to tryorama --- .../src/tests/username_registry/mod.rs | 1 - .../tests/username_registry/user_metadata.rs | 65 ------------------- .../only_authority.test.ts | 12 +--- .../src/user_metadata/user_only.test.ts | 61 +++++++++++++++++ 4 files changed, 63 insertions(+), 76 deletions(-) delete mode 100644 crates/holoom_dna_tests/src/tests/username_registry/user_metadata.rs create mode 100644 packages/tryorama/src/user_metadata/user_only.test.ts diff --git a/crates/holoom_dna_tests/src/tests/username_registry/mod.rs b/crates/holoom_dna_tests/src/tests/username_registry/mod.rs index 6d00bb4..239007b 100644 --- a/crates/holoom_dna_tests/src/tests/username_registry/mod.rs +++ b/crates/holoom_dna_tests/src/tests/username_registry/mod.rs @@ -1,6 +1,5 @@ mod external_id_attestation; mod oracle; mod recipe; -mod user_metadata; mod username_attestation; mod wallet_attestation; diff --git a/crates/holoom_dna_tests/src/tests/username_registry/user_metadata.rs b/crates/holoom_dna_tests/src/tests/username_registry/user_metadata.rs deleted file mode 100644 index ac0eb70..0000000 --- a/crates/holoom_dna_tests/src/tests/username_registry/user_metadata.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::collections::HashMap; - -use holochain::conductor::api::error::ConductorApiError; -use username_registry_coordinator::user_metadata::{ - GetMetadataItemValueInput, UpdateMetadataItemInput, -}; - -use crate::TestSetup; - -#[tokio::test(flavor = "multi_thread")] -async fn users_can_only_update_their_own_metadata() { - let setup = TestSetup::authority_and_alice_bob().await; - setup.conductors.exchange_peer_info().await; - - // Alice starts with no metadata - let initial_metadata: HashMap = setup - .alice_call("username_registry", "get_metadata", setup.alice_pubkey()) - .await - .unwrap(); - assert_eq!(initial_metadata, HashMap::default()); - - // Bob cannot set Alice's metadata - let res1: Result<(), ConductorApiError> = setup - .bob_call( - "username_registry", - "update_metadata_item", - UpdateMetadataItemInput { - agent_pubkey: setup.alice_pubkey(), - name: "foo".into(), - value: "bar".into(), - }, - ) - .await; - assert!(res1.is_err()); - - // Alice sets an item - let _: () = setup - .alice_call( - "username_registry", - "update_metadata_item", - UpdateMetadataItemInput { - agent_pubkey: setup.alice_pubkey(), - name: "foo".into(), - value: "bar2".into(), - }, - ) - .await - .unwrap(); - - setup.consistency().await; - - // Bob sees new item - let value1: String = setup - .bob_call( - "username_registry", - "get_metadata_item_value", - GetMetadataItemValueInput { - agent_pubkey: setup.alice_pubkey(), - name: "foo".into(), - }, - ) - .await - .unwrap(); - assert_eq!(value1, String::from("bar2")); -} diff --git a/packages/tryorama/src/external_attestation/only_authority.test.ts b/packages/tryorama/src/external_attestation/only_authority.test.ts index c83dd41..544d310 100644 --- a/packages/tryorama/src/external_attestation/only_authority.test.ts +++ b/packages/tryorama/src/external_attestation/only_authority.test.ts @@ -13,14 +13,7 @@ test("Only authority can create ExternalIdAttestations", async () => { const authorityCoordinators = bindCoordinators(authority); const aliceCoordinators = bindCoordinators(alice); - // Shortcut peer discovery through gossip and register all agents in every - // conductor of the scenario. - await scenario.shareAllAgents(); - //--------------------------------------------------------------- - console.log( - "\n************************* START TEST ****************************\n" - ); - + // Alice cannot create External ID Attestation await expect( aliceCoordinators.usernameRegistry.createExternalIdAttestation({ request_id: "3563", @@ -33,8 +26,8 @@ test("Only authority can create ExternalIdAttestations", async () => { "Only the Username Registry Authority can create external ID attestations" ) ); - console.log("Checked Alice cannot create external ID attestation"); + // Authority can create External ID Attestation await expect( authorityCoordinators.usernameRegistry.createExternalIdAttestation({ request_id: "3563", @@ -43,6 +36,5 @@ test("Only authority can create ExternalIdAttestations", async () => { display_name: "Alice", }) ).resolves.toBeTruthy(); - console.log("Checked authority can create external ID attestation"); }); }); diff --git a/packages/tryorama/src/user_metadata/user_only.test.ts b/packages/tryorama/src/user_metadata/user_only.test.ts new file mode 100644 index 0000000..4012500 --- /dev/null +++ b/packages/tryorama/src/user_metadata/user_only.test.ts @@ -0,0 +1,61 @@ +import { expect, test } from "vitest"; +import { runScenario } from "@holochain/tryorama"; + +import { overrideHappBundle } from "../utils/setup-happ.js"; +import { bindCoordinators } from "../utils/bindings.js"; +import { fakeAgentPubKey } from "@holochain/client"; + +test("Users can only update their own metadata", async () => { + await runScenario(async (scenario) => { + const appBundleSource = await overrideHappBundle(await fakeAgentPubKey()); + const [alice, bob] = await scenario.addPlayersWithApps([ + { appBundleSource }, + { appBundleSource }, + ]); + const aliceCoordinators = bindCoordinators(alice); + const bobCoordinators = bindCoordinators(bob); + await scenario.shareAllAgents(); + + // Alice starts with no metadata + await expect( + aliceCoordinators.usernameRegistry.getMetadata(alice.agentPubKey) + ).resolves.toEqual({}); + + // Bob cannot set Alice's metadata + await expect( + bobCoordinators.usernameRegistry.updateMetadataItem({ + agent_pubkey: alice.agentPubKey, + name: "foo", + value: "bar", + }) + ).rejects.toSatisfy((err: Error) => + err.message.includes( + "Only the owner can embed metadata in their link tags" + ) + ); + + // Alice sets an item + await expect( + aliceCoordinators.usernameRegistry.updateMetadataItem({ + agent_pubkey: alice.agentPubKey, + name: "foo", + value: "bar2", + }) + ).resolves.not.toThrow(); + + // Bob sees new item once gossiped + while (true) { + const value = await bobCoordinators.usernameRegistry.getMetadataItemValue( + { + agent_pubkey: alice.agentPubKey, + name: "foo", + } + ); + if (value) { + expect(value).toBe("bar2"); + break; + } + await new Promise((r) => setTimeout(r, 500)); + } + }); +}); From 778aa5a07eecafe4e02564a6c5c2f4cb553c418c Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Mon, 19 Aug 2024 14:30:55 +0100 Subject: [PATCH 11/17] fix: Seems CI test leans on this? --- .../tryorama/src/external_attestation/only_authority.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tryorama/src/external_attestation/only_authority.test.ts b/packages/tryorama/src/external_attestation/only_authority.test.ts index 544d310..3e2ad10 100644 --- a/packages/tryorama/src/external_attestation/only_authority.test.ts +++ b/packages/tryorama/src/external_attestation/only_authority.test.ts @@ -9,7 +9,7 @@ test("Only authority can create ExternalIdAttestations", async () => { const { authority, appBundleSource } = await setupBundleAndAuthorityPlayer(scenario); const alice = await scenario.addPlayerWithApp(appBundleSource); - + await scenario.shareAllAgents(); const authorityCoordinators = bindCoordinators(authority); const aliceCoordinators = bindCoordinators(alice); From 5f8e0bf5887581168707d94f79354881774915bf Mon Sep 17 00:00:00 2001 From: 8e8b2c <138928994+8e8b2c@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:27:48 +0100 Subject: [PATCH 12/17] feat: validation error types (#101) * feat: generate and attach types for validation errors * fix: bad crate deps * chore: tidy generated bindings * chore: rm unused * feat: gen integrity enums; test validation directly via records coordinator * chore: tidy * chore: test user_metadata ownership validation directly; rm unneeded coordinator arg; switch from bincode to ExternIO --- .vscode/settings.json | 1 + Cargo.lock | 10 +- Cargo.toml | 1 - crates/records_coordinator/Cargo.toml | 1 + crates/records_coordinator/src/lib.rs | 84 ++++ .../username_registry_coordinator/Cargo.toml | 1 - .../src/user_metadata.rs | 14 +- crates/username_registry_integrity/Cargo.toml | 2 + crates/username_registry_integrity/src/lib.rs | 1 + .../src/link_types.rs | 32 +- .../src/rejection_detail.rs | 35 ++ crates/username_registry_utils/Cargo.toml | 1 - .../username_registry_validation/Cargo.toml | 1 - features/user_metadata/handlers/Cargo.toml | 1 - .../user_metadata/handlers/src/get_all.rs | 2 +- .../user_metadata/handlers/src/get_item.rs | 2 +- .../user_metadata/handlers/src/update_item.rs | 9 +- features/user_metadata/validation/Cargo.toml | 3 +- features/user_metadata/validation/src/lib.rs | 62 ++- package-lock.json | 3 +- package.json | 2 +- packages/client/src/holoom-client.ts | 1 - packages/tryorama/src/signer/sign.test.ts | 4 +- .../tryorama/src/user_metadata/raw.test.ts | 107 +++++ .../src/user_metadata/user_only.test.ts | 19 +- packages/tryorama/src/utils/bindings.ts | 9 +- packages/tryorama/src/utils/gossip.ts | 20 + packages/tryorama/src/utils/time.ts | 3 + packages/types/package.json | 10 +- .../types/scripts/add-imports-to-typeshare.ts | 3 +- ...fn-bindings.mjs => extract-fn-bindings.ts} | 84 ++-- .../types/scripts/extract-integrity-enums.ts | 176 ++++++++ packages/types/scripts/holochain-types.ts | 10 + ...epare-bindings.mjs => prepare-bindings.ts} | 6 +- packages/types/src/dependency-types.ts | 5 + packages/types/src/errors.ts | 42 ++ packages/types/src/index.ts | 2 + .../UsernameRegistryIntegrity.ts | 22 + packages/types/src/integrity-enums/index.ts | 5 + packages/types/src/types/WalletAttestation.ts | 2 +- packages/types/src/typeshare-generated.ts | 72 ++- .../src/zome-functions/PingCoordinator.ts | 19 +- .../src/zome-functions/RecordsCoordinator.ts | 41 +- .../src/zome-functions/SignerCoordinator.ts | 21 +- .../UsernameRegistryCoordinator.ts | 419 ++++++------------ 45 files changed, 937 insertions(+), 433 deletions(-) create mode 100644 crates/username_registry_integrity/src/rejection_detail.rs create mode 100644 packages/tryorama/src/user_metadata/raw.test.ts create mode 100644 packages/tryorama/src/utils/gossip.ts create mode 100644 packages/tryorama/src/utils/time.ts rename packages/types/scripts/{extract-fn-bindings.mjs => extract-fn-bindings.ts} (79%) create mode 100644 packages/types/scripts/extract-integrity-enums.ts create mode 100644 packages/types/scripts/holochain-types.ts rename packages/types/scripts/{prepare-bindings.mjs => prepare-bindings.ts} (85%) create mode 100644 packages/types/src/errors.ts create mode 100644 packages/types/src/integrity-enums/UsernameRegistryIntegrity.ts create mode 100644 packages/types/src/integrity-enums/index.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 3b1b0fa..2bd54da 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "happ", "Holochain", "Holoom", + "msgpack", "PKCE", "pubkey", "Solana", diff --git a/Cargo.lock b/Cargo.lock index c349027..d3fc8f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6390,6 +6390,7 @@ version = "0.0.1" dependencies = [ "hdk", "serde", + "typeshare", ] [[package]] @@ -8714,7 +8715,6 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" name = "user_metadata_handlers" version = "0.0.1" dependencies = [ - "bincode", "hdk", "hdk_utils", "serde", @@ -8734,8 +8734,9 @@ dependencies = [ name = "user_metadata_validation" version = "0.0.1" dependencies = [ - "bincode", "hdi", + "serde", + "typeshare", "user_metadata_types", ] @@ -8743,7 +8744,6 @@ dependencies = [ name = "username_registry_coordinator" version = "0.0.1" dependencies = [ - "bincode", "hdk", "hex", "holoom_types", @@ -8766,6 +8766,8 @@ dependencies = [ "hdi", "holoom_types", "serde", + "serde_json", + "typeshare", "user_metadata_types", "user_metadata_validation", "username_registry_validation", @@ -8775,7 +8777,6 @@ dependencies = [ name = "username_registry_utils" version = "0.0.1" dependencies = [ - "bincode", "bs58 0.5.0", "ed25519-dalek", "hdi", @@ -8789,7 +8790,6 @@ dependencies = [ name = "username_registry_validation" version = "0.0.1" dependencies = [ - "bincode", "bs58 0.5.0", "ed25519-dalek", "hdi", diff --git a/Cargo.toml b/Cargo.toml index a45d181..774dcde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,6 @@ hdk = "=0.4.0-dev.10" holo_hash = { version = "=0.4.0-dev.8", features = ["encoding"] } serde = "1" serde_json = "1" -bincode = "1.3.3" hex = "0.4.3" alloy-primitives = { version = "0.6.3", features = ["serde", "k256"] } ed25519-dalek = { version = "2.1.1", features = ["serde"] } diff --git a/crates/records_coordinator/Cargo.toml b/crates/records_coordinator/Cargo.toml index d650c9a..0df9350 100644 --- a/crates/records_coordinator/Cargo.toml +++ b/crates/records_coordinator/Cargo.toml @@ -10,3 +10,4 @@ name = "records_coordinator" [dependencies] hdk = { workspace = true } serde = { workspace = true } +typeshare = { workspace = true } diff --git a/crates/records_coordinator/src/lib.rs b/crates/records_coordinator/src/lib.rs index 9613140..4681e68 100644 --- a/crates/records_coordinator/src/lib.rs +++ b/crates/records_coordinator/src/lib.rs @@ -1,10 +1,94 @@ use hdk::prelude::*; +use typeshare::typeshare; #[hdk_extern] pub fn get_record(action_hash: ActionHash) -> ExternResult> { get(action_hash, GetOptions::network()) } +/// Input to the `create_app_entry_raw` function +#[derive(Serialize, Deserialize, Debug)] +#[typeshare] +pub struct CreateAppEntryRawInput { + /// The index of the zome that defines the entry type + pub zome_index: ZomeIndex, + /// The index of the entry definition within the zome + pub entry_def_index: EntryDefIndex, + /// The msgpack serialised app entry content + pub entry_bytes: SerializedBytes, +} + +/// Directly exposes the hdk functionality for creating entries. This is useful for validation +/// logic tests, because it removes the need for entry coordinator zomes to define create_* methods +/// for unintended actions, merely for the sake of asserting fail cases. +/// +/// For now, assumes all entries are public +#[hdk_extern] +pub fn create_app_entry_raw(input: CreateAppEntryRawInput) -> ExternResult { + let create_input = CreateInput { + entry_location: EntryDefLocation::App(AppEntryDefLocation { + zome_index: input.zome_index, + entry_def_index: input.entry_def_index, + }), + entry: Entry::App(AppEntryBytes(input.entry_bytes)), + entry_visibility: EntryVisibility::default(), + chain_top_ordering: ChainTopOrdering::default(), + }; + create(create_input) +} + +/// Directly exposes the hdk functionality for deleting entries. This is useful for validation +/// logic tests, because it removes the need for entry coordinator zomes to define delete_* methods +/// for unintended actions, merely for the sake of asserting fail cases. +#[hdk_extern] +pub fn delete_entry_raw(action_hash: ActionHash) -> ExternResult { + let delete_input = DeleteInput { + deletes_action_hash: action_hash, + chain_top_ordering: ChainTopOrdering::default(), + }; + delete(delete_input) +} + +/// Input to the `create_link_raw` function +#[derive(Serialize, Deserialize, Debug)] +#[typeshare] +pub struct CreateLinkRawInput { + /// The 'form' address of the link + base_address: AnyLinkableHash, + /// The 'to' address of the link + target_address: AnyLinkableHash, + /// The index of the zome in which the link was defined + zome_index: ZomeIndex, + /// The index of the link definition within the zome + link_type: LinkType, + /// Freeform data attached to the link + tag: LinkTag, +} + +/// Directly exposes the hdk functionality for creating links. This is useful for validation logic +/// tests, because it removes the need for entry coordinator zomes to define create_* methods for +/// unintended actions, merely for the sake of asserting fail cases. +#[hdk_extern] +pub fn create_link_raw(input: CreateLinkRawInput) -> ExternResult { + let input = CreateLinkInput { + base_address: input.base_address, + target_address: input.target_address, + zome_index: input.zome_index, + link_type: input.link_type, + tag: input.tag, + chain_top_ordering: ChainTopOrdering::default(), + }; + HDK.with(|h| h.borrow().create_link(input)) +} + +/// Directly exposes the hdk functionality for deleting links. This is useful for validation logic +/// tests, because it removes the need for entry coordinator zomes to define delete_* methods for +/// unintended actions, merely for the sake of asserting fail cases. +#[hdk_extern] +pub fn delete_link_raw(create_link_action_hash: ActionHash) -> ExternResult { + delete_link(create_link_action_hash) +} + #[hdk_extern] pub fn get_chain_status(agent: AgentPubKey) -> ExternResult { get_agent_activity(agent, ChainQueryFilter::default(), ActivityRequest::Status) diff --git a/crates/username_registry_coordinator/Cargo.toml b/crates/username_registry_coordinator/Cargo.toml index 0034e9f..53bb825 100644 --- a/crates/username_registry_coordinator/Cargo.toml +++ b/crates/username_registry_coordinator/Cargo.toml @@ -10,7 +10,6 @@ name = "username_registry_coordinator" [dependencies] serde = { workspace = true } hdk = { workspace = true } -bincode = { workspace = true } username_registry_integrity = { workspace = true } holoom_types = { workspace = true } username_registry_validation = { workspace = true } diff --git a/crates/username_registry_coordinator/src/user_metadata.rs b/crates/username_registry_coordinator/src/user_metadata.rs index 5980ddf..043b646 100644 --- a/crates/username_registry_coordinator/src/user_metadata.rs +++ b/crates/username_registry_coordinator/src/user_metadata.rs @@ -9,9 +9,6 @@ use username_registry_integrity::LinkTypes; #[typeshare] #[derive(Serialize, Deserialize, Debug)] pub struct UpdateMetadataItemInput { - /// This has to be set to your own key. The only reason this field isn't - /// instead inferred is for the sake of enabling testing of the fail case. - pub agent_pubkey: AgentPubKey, /// The key for the particular metadata item pub name: String, /// The value to assign to the key @@ -21,13 +18,10 @@ pub struct UpdateMetadataItemInput { /// Upsert a key-value item of your own metadata #[hdk_extern] pub fn update_metadata_item(input: UpdateMetadataItemInput) -> ExternResult<()> { - user_metadata_handlers::update_item::handler::( - input.agent_pubkey, - MetadataItem { - name: input.name, - value: input.value, - }, - ) + user_metadata_handlers::update_item::handler::(MetadataItem { + name: input.name, + value: input.value, + }) } /// The input argument to `get_metadata_item_value`` diff --git a/crates/username_registry_integrity/Cargo.toml b/crates/username_registry_integrity/Cargo.toml index 45df262..a580ad8 100644 --- a/crates/username_registry_integrity/Cargo.toml +++ b/crates/username_registry_integrity/Cargo.toml @@ -10,7 +10,9 @@ name = "username_registry_integrity" [dependencies] hdi = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } holoom_types = { workspace = true } username_registry_validation = { workspace = true } user_metadata_types = { workspace = true } user_metadata_validation = { workspace = true } +typeshare = { workspace = true } diff --git a/crates/username_registry_integrity/src/lib.rs b/crates/username_registry_integrity/src/lib.rs index a3f2fa1..bf52e2d 100644 --- a/crates/username_registry_integrity/src/lib.rs +++ b/crates/username_registry_integrity/src/lib.rs @@ -2,6 +2,7 @@ use hdi::prelude::*; pub mod entry_types; pub use entry_types::*; pub mod link_types; +pub mod rejection_detail; pub use link_types::*; #[hdk_extern] diff --git a/crates/username_registry_integrity/src/link_types.rs b/crates/username_registry_integrity/src/link_types.rs index 98e1892..1ffda54 100644 --- a/crates/username_registry_integrity/src/link_types.rs +++ b/crates/username_registry_integrity/src/link_types.rs @@ -2,6 +2,8 @@ use hdi::prelude::*; use user_metadata_types::InjectMetadataLinkTypes; use username_registry_validation::*; +use crate::rejection_detail::ValidationRejectionDetail; + #[derive(Serialize, Deserialize)] #[hdk_link_types] pub enum LinkTypes { @@ -42,12 +44,16 @@ impl LinkTypes { ) } LinkTypes::AgentMetadata => { - user_metadata_validation::validate_create_link_user_metadata( - action, - base_address, - target_address, - tag, + ValidationRejectionDetail::CreateAgentMetadataLinkRejectionReasons( + user_metadata_validation::validate_create_link_user_metadata( + action, + base_address, + target_address, + tag, + )? + .into(), ) + .to_validation_result() } LinkTypes::AgentToWalletAttestations => { validate_create_link_agent_to_wallet_attestations( @@ -124,13 +130,17 @@ impl LinkTypes { ) } LinkTypes::AgentMetadata => { - user_metadata_validation::validate_delete_link_user_metadata( - action, - original_action, - base_address, - target_address, - tag, + ValidationRejectionDetail::DeleteAgentMetadataLinkRejectionReasons( + user_metadata_validation::validate_delete_link_user_metadata( + action, + original_action, + base_address, + target_address, + tag, + )? + .into(), ) + .to_validation_result() } LinkTypes::AgentToWalletAttestations => { validate_delete_link_agent_to_wallet_attestations( diff --git a/crates/username_registry_integrity/src/rejection_detail.rs b/crates/username_registry_integrity/src/rejection_detail.rs new file mode 100644 index 0000000..cdbc233 --- /dev/null +++ b/crates/username_registry_integrity/src/rejection_detail.rs @@ -0,0 +1,35 @@ +use hdi::prelude::*; +use typeshare::typeshare; +use user_metadata_validation::{ + CreateAgentMetadataLinkRejectionReason, DeleteAgentMetadataLinkRejectionReason, +}; + +#[derive(Serialize)] +#[serde(tag = "type", content = "reasons")] +#[typeshare] +pub enum ValidationRejectionDetail { + CreateAgentMetadataLinkRejectionReasons(Vec), + DeleteAgentMetadataLinkRejectionReasons(Vec), +} + +impl ValidationRejectionDetail { + pub fn to_validation_result(self) -> ExternResult { + let is_valid = match &self { + ValidationRejectionDetail::CreateAgentMetadataLinkRejectionReasons(reasons) => { + reasons.is_empty() + } + ValidationRejectionDetail::DeleteAgentMetadataLinkRejectionReasons(reasons) => { + reasons.is_empty() + } + }; + if is_valid { + Ok(ValidateCallbackResult::Valid) + } else { + Ok(ValidateCallbackResult::Invalid(format!( + "__REASONS_START__{}__REASONS_END__", + serde_json::to_string(&self) + .map_err(|_| wasm_error!("Couldn't serialize rejection reasons".to_string()))? + ))) + } + } +} diff --git a/crates/username_registry_utils/Cargo.toml b/crates/username_registry_utils/Cargo.toml index 756ca7a..7778cef 100644 --- a/crates/username_registry_utils/Cargo.toml +++ b/crates/username_registry_utils/Cargo.toml @@ -11,7 +11,6 @@ name = "username_registry_utils" hdi = { workspace = true } holo_hash = { workspace = true } serde = { workspace = true } -bincode = { workspace = true } holoom_types = { workspace = true } ed25519-dalek = { workspace = true } bs58 = { workspace = true } diff --git a/crates/username_registry_validation/Cargo.toml b/crates/username_registry_validation/Cargo.toml index 87c0252..efaea2e 100644 --- a/crates/username_registry_validation/Cargo.toml +++ b/crates/username_registry_validation/Cargo.toml @@ -12,7 +12,6 @@ hdi = { workspace = true } holo_hash = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -bincode = { workspace = true } holoom_types = { workspace = true } ed25519-dalek = { workspace = true } bs58 = { workspace = true } diff --git a/features/user_metadata/handlers/Cargo.toml b/features/user_metadata/handlers/Cargo.toml index b73f9da..f664fab 100644 --- a/features/user_metadata/handlers/Cargo.toml +++ b/features/user_metadata/handlers/Cargo.toml @@ -11,5 +11,4 @@ name = "user_metadata_handlers" serde = { workspace = true } hdk = { workspace = true } user_metadata_types = { workspace = true } -bincode = { workspace = true } hdk_utils = { workspace = true } diff --git a/features/user_metadata/handlers/src/get_all.rs b/features/user_metadata/handlers/src/get_all.rs index 30eda43..88bc858 100644 --- a/features/user_metadata/handlers/src/get_all.rs +++ b/features/user_metadata/handlers/src/get_all.rs @@ -11,7 +11,7 @@ where get_links(GetLinksInputBuilder::try_new(agent_pubkey, LT::agent_metadata())?.build())?; let mut out = HashMap::default(); for link in links { - let item: MetadataItem = bincode::deserialize(&link.tag.into_inner()).map_err(|_| { + let item: MetadataItem = ExternIO(link.tag.into_inner()).decode().map_err(|_| { wasm_error!(WasmErrorInner::Guest( "Failed to deserialize MetadataItem".into() )) diff --git a/features/user_metadata/handlers/src/get_item.rs b/features/user_metadata/handlers/src/get_item.rs index 3b91afe..eab09dc 100644 --- a/features/user_metadata/handlers/src/get_item.rs +++ b/features/user_metadata/handlers/src/get_item.rs @@ -8,7 +8,7 @@ where let links = get_links(GetLinksInputBuilder::try_new(agent_pubkey, LT::agent_metadata())?.build())?; for link in links { - let item: MetadataItem = bincode::deserialize(&link.tag.into_inner()).map_err(|_| { + let item: MetadataItem = ExternIO(link.tag.into_inner()).decode().map_err(|_| { wasm_error!(WasmErrorInner::Guest( "Failed to deserialize MetadataItem".into() )) diff --git a/features/user_metadata/handlers/src/update_item.rs b/features/user_metadata/handlers/src/update_item.rs index 244caac..755d261 100644 --- a/features/user_metadata/handlers/src/update_item.rs +++ b/features/user_metadata/handlers/src/update_item.rs @@ -1,16 +1,17 @@ use hdk::prelude::*; use user_metadata_types::{InjectMetadataLinkTypes, MetadataItem}; -pub fn handler(agent_pubkey: AgentPubKey, item: MetadataItem) -> ExternResult<()> +pub fn handler(item: MetadataItem) -> ExternResult<()> where LT: InjectMetadataLinkTypes, { + let agent_pubkey = agent_info()?.agent_initial_pubkey; let links = get_links( GetLinksInputBuilder::try_new(agent_pubkey.clone(), LT::agent_metadata())?.build(), )?; for link in links { let existing_item: MetadataItem = - bincode::deserialize(&link.tag.into_inner()).map_err(|_| { + ExternIO(link.tag.into_inner()).decode().map_err(|_| { wasm_error!(WasmErrorInner::Guest( "Failed to deserialize MetadataItem".into() )) @@ -20,7 +21,7 @@ where delete_link(link.create_link_hash)?; } } - let tag_bytes = bincode::serialize(&item).map_err(|_| { + let tag_bytes = ExternIO::encode(item).map_err(|_| { wasm_error!(WasmErrorInner::Guest( "Failed to serialize MetadataItem".into() )) @@ -29,7 +30,7 @@ where agent_pubkey.clone(), agent_pubkey, // unused and irrelevant LT::agent_metadata(), - LinkTag(tag_bytes), + LinkTag(tag_bytes.into_vec()), )?; Ok(()) } diff --git a/features/user_metadata/validation/Cargo.toml b/features/user_metadata/validation/Cargo.toml index ea425c7..72e1c99 100644 --- a/features/user_metadata/validation/Cargo.toml +++ b/features/user_metadata/validation/Cargo.toml @@ -10,4 +10,5 @@ name = "user_metadata_validation" [dependencies] hdi = { workspace = true } user_metadata_types = { workspace = true } -bincode = { workspace = true } +serde = { workspace = true } +typeshare = { workspace = true } diff --git a/features/user_metadata/validation/src/lib.rs b/features/user_metadata/validation/src/lib.rs index 0a1f504..3a23937 100644 --- a/features/user_metadata/validation/src/lib.rs +++ b/features/user_metadata/validation/src/lib.rs @@ -1,44 +1,68 @@ use hdi::prelude::*; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; use user_metadata_types::MetadataItem; +/// Reasons for which a create `AgentMetadata` link action can fail validation. +#[derive(Serialize, Deserialize)] +#[typeshare] +pub enum CreateAgentMetadataLinkRejectionReason { + /// The base address is the agent pubkey of the user who is being annotated with metadata. + /// As a user can only author their own metadata, the base address has match their own pubkey. + BaseAddressMustBeOwner, + + /// The link tag content doesn't match the expected key-value schema struct `MetadataItem`. + BadTagSerialization, +} + +/// Gathers any reasons for rejecting a create `AgentMetadata` link action pub fn validate_create_link_user_metadata( action: CreateLink, base_address: AnyLinkableHash, _target_address: AnyLinkableHash, tag: LinkTag, -) -> ExternResult { - let agent_pubkey = AgentPubKey::try_from(base_address).map_err(|e| wasm_error!(e))?; +) -> ExternResult> { + use CreateAgentMetadataLinkRejectionReason::*; + let mut rejection_reasons = Vec::new(); - if action.author != agent_pubkey { - return Ok(ValidateCallbackResult::Invalid( - "Only the owner can embed metadata in their link tags".into(), - )); + if AnyLinkableHash::from(action.author) != base_address { + rejection_reasons.push(BaseAddressMustBeOwner); } + // The contents of the target_address is unused and irrelevant // Check the tag is valid - let _item: MetadataItem = bincode::deserialize(&tag.into_inner()).map_err(|_| { - wasm_error!(WasmErrorInner::Guest( - "Failed to deserialize MetadataItem".into() - )) - })?; - Ok(ValidateCallbackResult::Valid) + if ExternIO(tag.into_inner()).decode::().is_err() { + rejection_reasons.push(BadTagSerialization) + } + + Ok(rejection_reasons) +} + +/// Reasons for which a delete `AgentMetadata` link action can fail validation. +#[derive(Serialize)] +#[typeshare] +pub enum DeleteAgentMetadataLinkRejectionReason { + /// The user attempting to delete the metadata item is not the owner and therefore doesn't + /// have permission. + DeleterIsNotOwner, } + +/// Gathers any reasons for rejecting a delete `AgentMetadata`` link action pub fn validate_delete_link_user_metadata( action: DeleteLink, _original_action: CreateLink, base_address: AnyLinkableHash, _target_address: AnyLinkableHash, _tag: LinkTag, -) -> ExternResult { - let agent_pubkey = AgentPubKey::try_from(base_address).map_err(|e| wasm_error!(e))?; +) -> ExternResult> { + use DeleteAgentMetadataLinkRejectionReason::*; + let mut rejection_reasons = Vec::new(); - if action.author != agent_pubkey { - return Ok(ValidateCallbackResult::Invalid( - "Only the owner can delete their metadata tags".into(), - )); + if AnyLinkableHash::from(action.author) != base_address { + rejection_reasons.push(DeleterIsNotOwner) } - Ok(ValidateCallbackResult::Valid) + Ok(rejection_reasons) } diff --git a/package-lock.json b/package-lock.json index 988792f..f90f393 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11439,7 +11439,8 @@ "rollup": "^4.12.0", "rollup-plugin-cleanup": "^3.2.1", "tsx": "^4.17.0", - "typedoc": "^0.25.13" + "typedoc": "^0.25.13", + "yaml": "^2.5.0" }, "peerDependencies": { "@holochain/client": "^0.18.0-dev" diff --git a/package.json b/package.json index bb92cf9..9a267ee 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "scripts": { "build:dna": "scripts/build_dna.sh", - "test:tryorama": "npm run build:dna && npm run -w @holoom/types build && npm t -w @holoom/tryorama", + "test:tryorama": "npm run build:dna && npm run prepare-build:types && npm t -w @holoom/tryorama", "test:dna": "npm run build:dna && cargo nextest run -j 1", "prepare-build:types": "rimraf crates/holoom_types/bindings && cargo test --package holoom_types && npm run -w @holoom/types prepare:bindings && npm run -w @holoom/types build", "build:client": "npm run build -w @holoom/client", diff --git a/packages/client/src/holoom-client.ts b/packages/client/src/holoom-client.ts index 2d2a48f..05276f2 100644 --- a/packages/client/src/holoom-client.ts +++ b/packages/client/src/holoom-client.ts @@ -95,7 +95,6 @@ export class HoloomClient { */ async setMetadata(name: string, value: string) { await this.usernameRegistryCoordinator.updateMetadataItem({ - agent_pubkey: this.myPubKey, name, value, }); diff --git a/packages/tryorama/src/signer/sign.test.ts b/packages/tryorama/src/signer/sign.test.ts index d144f24..54620ac 100644 --- a/packages/tryorama/src/signer/sign.test.ts +++ b/packages/tryorama/src/signer/sign.test.ts @@ -3,7 +3,7 @@ import { runScenario } from "@holochain/tryorama"; import { overrideHappBundle } from "../utils/setup-happ.js"; import { bindCoordinators } from "../utils/bindings.js"; -import { fakeAgentPubKey } from "@holochain/client"; +import { fakeAgentPubKey, sliceCore32 } from "@holochain/client"; import { sha512 } from "@noble/hashes/sha512"; import * as ed from "@noble/ed25519"; import { encode } from "@msgpack/msgpack"; @@ -26,7 +26,7 @@ test("Sign message and verify signature", async () => { ed.verifyAsync( signature, encode(message), // signed as serialised :-/ - alice.agentPubKey.slice(3, 35) // get_raw_32() + sliceCore32(alice.agentPubKey) ) ).resolves.toBe(true); }); diff --git a/packages/tryorama/src/user_metadata/raw.test.ts b/packages/tryorama/src/user_metadata/raw.test.ts new file mode 100644 index 0000000..e4f1194 --- /dev/null +++ b/packages/tryorama/src/user_metadata/raw.test.ts @@ -0,0 +1,107 @@ +import { expect, test } from "vitest"; +import { runScenario } from "@holochain/tryorama"; + +import { overrideHappBundle } from "../utils/setup-happ.js"; +import { bindCoordinators } from "../utils/bindings.js"; +import { + fakeAgentPubKey, + hashFrom32AndType, + HoloHash, + sliceCore32, +} from "@holochain/client"; +import { + ValidationRejectionDetail, + CreateAgentMetadataLinkRejectionReason, + ValidationError, + IntegrityZomeIndex, + UsernameRegistryIntegrityLinkTypeIndex, + DeleteAgentMetadataLinkRejectionReason, +} from "@holoom/types"; +import { encode } from "@msgpack/msgpack"; +import { untilRecordKnown } from "../utils/gossip.js"; + +test("Direct user_metadata validation", async () => { + await runScenario(async (scenario) => { + const appBundleSource = await overrideHappBundle(await fakeAgentPubKey()); + const [alice, bob] = await scenario.addPlayersWithApps([ + { appBundleSource }, + { appBundleSource }, + ]); + await scenario.shareAllAgents(); + const aliceCoordinators = bindCoordinators(alice); + const bobCoordinators = bindCoordinators(bob); + + // AnyLinkableHash retypes AgentPubKey to EntryHash + const intoEntryHash = (hash: HoloHash) => + hashFrom32AndType(sliceCore32(hash), "Entry"); + + // Cannot create badly encoded metadata + try { + await aliceCoordinators.records.createLinkRaw({ + base_address: intoEntryHash(alice.agentPubKey), + target_address: intoEntryHash(alice.agentPubKey), + zome_index: IntegrityZomeIndex.UsernameRegistryIntegrity, + link_type: UsernameRegistryIntegrityLinkTypeIndex.AgentMetadata, + tag: new Uint8Array([]), + }); + expect.unreachable("Cannot create badly encoded metadata"); + } catch (err) { + expect(ValidationError.getDetail(err)).toEqual( + { + type: "CreateAgentMetadataLinkRejectionReasons", + reasons: [CreateAgentMetadataLinkRejectionReason.BadTagSerialization], + } + ); + } + + // Bob cannot create a metadata item for Alice's agent + try { + await bobCoordinators.records.createLinkRaw({ + base_address: intoEntryHash(alice.agentPubKey), + target_address: intoEntryHash(alice.agentPubKey), + zome_index: IntegrityZomeIndex.UsernameRegistryIntegrity, + link_type: UsernameRegistryIntegrityLinkTypeIndex.AgentMetadata, + tag: encode({ name: "foo", value: "bar" }), + }); + expect.unreachable("Cannot create metadata for another user"); + } catch (err) { + expect(ValidationError.getDetail(err)).toEqual( + { + type: "CreateAgentMetadataLinkRejectionReasons", + reasons: [ + CreateAgentMetadataLinkRejectionReason.BaseAddressMustBeOwner, + ], + } + ); + } + + // Alice can create a metadata item for her agent + const createLinkAh = await aliceCoordinators.records.createLinkRaw({ + base_address: intoEntryHash(alice.agentPubKey), + target_address: intoEntryHash(alice.agentPubKey), + zome_index: IntegrityZomeIndex.UsernameRegistryIntegrity, + link_type: UsernameRegistryIntegrityLinkTypeIndex.AgentMetadata, + tag: encode({ name: "foo", value: "bar" }), + }); + + await untilRecordKnown(createLinkAh, bobCoordinators); + + // Bob cannot delete Alice's metadata + try { + await bobCoordinators.records.deleteLinkRaw(createLinkAh); + expect.unreachable("Cannot delete metadata you don't own"); + } catch (err) { + expect(ValidationError.getDetail(err)).toEqual( + { + type: "DeleteAgentMetadataLinkRejectionReasons", + reasons: [DeleteAgentMetadataLinkRejectionReason.DeleterIsNotOwner], + } + ); + } + + // Alice can delete her metadata + await expect( + aliceCoordinators.records.deleteLinkRaw(createLinkAh) + ).resolves.not.toThrow(); + }); +}); diff --git a/packages/tryorama/src/user_metadata/user_only.test.ts b/packages/tryorama/src/user_metadata/user_only.test.ts index 4012500..700f2d2 100644 --- a/packages/tryorama/src/user_metadata/user_only.test.ts +++ b/packages/tryorama/src/user_metadata/user_only.test.ts @@ -4,6 +4,11 @@ import { runScenario } from "@holochain/tryorama"; import { overrideHappBundle } from "../utils/setup-happ.js"; import { bindCoordinators } from "../utils/bindings.js"; import { fakeAgentPubKey } from "@holochain/client"; +import { + ValidationRejectionDetail, + CreateAgentMetadataLinkRejectionReason, + ValidationError, +} from "@holoom/types"; test("Users can only update their own metadata", async () => { await runScenario(async (scenario) => { @@ -21,23 +26,9 @@ test("Users can only update their own metadata", async () => { aliceCoordinators.usernameRegistry.getMetadata(alice.agentPubKey) ).resolves.toEqual({}); - // Bob cannot set Alice's metadata - await expect( - bobCoordinators.usernameRegistry.updateMetadataItem({ - agent_pubkey: alice.agentPubKey, - name: "foo", - value: "bar", - }) - ).rejects.toSatisfy((err: Error) => - err.message.includes( - "Only the owner can embed metadata in their link tags" - ) - ); - // Alice sets an item await expect( aliceCoordinators.usernameRegistry.updateMetadataItem({ - agent_pubkey: alice.agentPubKey, name: "foo", value: "bar2", }) diff --git a/packages/tryorama/src/utils/bindings.ts b/packages/tryorama/src/utils/bindings.ts index c54cb03..9c62b82 100644 --- a/packages/tryorama/src/utils/bindings.ts +++ b/packages/tryorama/src/utils/bindings.ts @@ -1,11 +1,18 @@ import { AppClient } from "@holochain/client"; import { Player } from "@holochain/tryorama"; -import { UsernameRegistryCoordinator, SignerCoordinator } from "@holoom/types"; +import { + UsernameRegistryCoordinator, + SignerCoordinator, + RecordsCoordinator, +} from "@holoom/types"; export function bindCoordinators(player: Player) { const appClient = player.cells[0] as unknown as AppClient; return { + records: new RecordsCoordinator(appClient), signer: new SignerCoordinator(appClient), usernameRegistry: new UsernameRegistryCoordinator(appClient), }; } + +export type BoundCoordinators = ReturnType; diff --git a/packages/tryorama/src/utils/gossip.ts b/packages/tryorama/src/utils/gossip.ts new file mode 100644 index 0000000..e883c03 --- /dev/null +++ b/packages/tryorama/src/utils/gossip.ts @@ -0,0 +1,20 @@ +import { ActionHash, encodeHashToBase64 } from "@holochain/client"; +import { BoundCoordinators } from "./bindings"; +import { untilMsLater } from "./time"; + +export async function untilRecordKnown( + actionHash: ActionHash, + playerCoordinators: BoundCoordinators, + delay = 500, + timeout = 10_000 +) { + const deadline = Date.now() + timeout; + while (Date.now() < deadline) { + const record = await playerCoordinators.records.getRecord(actionHash); + if (record) return; + await untilMsLater(delay); + } + throw new Error( + `${encodeHashToBase64(actionHash)} not gossiped after ${timeout}ms` + ); +} diff --git a/packages/tryorama/src/utils/time.ts b/packages/tryorama/src/utils/time.ts new file mode 100644 index 0000000..90727e1 --- /dev/null +++ b/packages/tryorama/src/utils/time.ts @@ -0,0 +1,3 @@ +export function untilMsLater(ms: number) { + return new Promise((r) => setTimeout(r, ms)); +} diff --git a/packages/types/package.json b/packages/types/package.json index 45fbaec..4640852 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -30,8 +30,11 @@ }, "scripts": { "build": "rimraf dist && npm run build:browser && npm run build:node", - "typeshare": "typeshare -l typescript -o src/typeshare-generated.ts ../../features ../../crates && tsx scripts/add-imports-to-typeshare.ts", - "prepare:bindings": "npm run typeshare && rimraf src/types/* && node scripts/prepare-bindings.mjs && rimraf src/zome-functions/* && node scripts/extract-fn-bindings.mjs", + "prepare:bindings": "npm run prepare:typeshare && npm run prepare:ts-rs-bindings && npm run prepare:fn-bindings && npm run prepare:integrity-enums", + "prepare:typeshare": "typeshare -l typescript -o src/typeshare-generated.ts ../../features ../../crates && tsx scripts/add-imports-to-typeshare.ts", + "prepare:ts-rs-bindings": "rimraf src/types/* && tsx scripts/prepare-bindings.ts", + "prepare:fn-bindings": "rimraf src/zome-functions/* && tsx scripts/extract-fn-bindings.ts", + "prepare:integrity-enums": "rimraf src/integrity-enums/* && tsx scripts/extract-integrity-enums.ts", "build:browser": "rollup -c rollup.browser.config.ts --configPlugin typescript", "build:node": "rollup -c rollup.node.config.ts --configPlugin typescript" }, @@ -49,7 +52,8 @@ "rollup": "^4.12.0", "rollup-plugin-cleanup": "^3.2.1", "tsx": "^4.17.0", - "typedoc": "^0.25.13" + "typedoc": "^0.25.13", + "yaml": "^2.5.0" }, "peerDependencies": { "@holochain/client": "^0.18.0-dev" diff --git a/packages/types/scripts/add-imports-to-typeshare.ts b/packages/types/scripts/add-imports-to-typeshare.ts index 061796a..7706ed1 100644 --- a/packages/types/scripts/add-imports-to-typeshare.ts +++ b/packages/types/scripts/add-imports-to-typeshare.ts @@ -1,7 +1,6 @@ import fs from "fs/promises"; import prettier from "prettier"; - -const HOLOCHAIN_TYPES = ["ActionHash", "AgentPubKey", "Record", "Signature"]; +import { HOLOCHAIN_TYPES } from "./holochain-types"; const DEPENDENCY_TYPES_PATH = "src/dependency-types.ts"; const TYPESHARE_GENERATED_PATH = "src/typeshare-generated.ts"; diff --git a/packages/types/scripts/extract-fn-bindings.mjs b/packages/types/scripts/extract-fn-bindings.ts similarity index 79% rename from packages/types/scripts/extract-fn-bindings.mjs rename to packages/types/scripts/extract-fn-bindings.ts index 7ae02f7..49c93d9 100644 --- a/packages/types/scripts/extract-fn-bindings.mjs +++ b/packages/types/scripts/extract-fn-bindings.ts @@ -1,6 +1,7 @@ import fs from "fs/promises"; import { glob } from "glob"; import prettier from "prettier"; +import { HOLOCHAIN_TYPES } from "./holochain-types"; const CRATES_DIR = "../../crates"; const coordinator_regex = /^.+_coordinator$/; @@ -27,15 +28,35 @@ const extern_regex = const SKIPPED_METHODS = ["init", "recv_remote_signal"]; -async function extractFnBindingsForCrate(name, typesTransform) { +interface Binding { + fnName: string; + inputName: string; + inputType: string; + outputType: string; +} + +async function extractFnBindingsForCrate( + name: string, + typesTransform: TypeTransform +) { console.log("Start extracting fn bindings for:", name); const rustFiles = await glob(`${CRATES_DIR}/${name}/src/**/*.rs`); - const bindings = []; - let deps = new Set(); + const bindings: Binding[] = []; + let deps = new Set(); await Promise.all( rustFiles.map(async (filePath) => { const content = await fs.readFile(filePath, "utf-8"); - const matches = content.matchAll(extern_regex); + const matches = Array.from(content.matchAll(extern_regex)); + + const externCount = Array.from( + content.matchAll(/#\[hdk_extern\]/g) + ).length; + if (externCount !== matches.length) { + console.error( + `hdk_extern regex only captured ${matches.length} / ${externCount} occurrences in ${filePath}` + ); + } + for (const match of matches) { const [ _fullMatch, @@ -70,23 +91,15 @@ async function extractFnBindingsForCrate(name, typesTransform) { bindings.sort((x1, x2) => (x1.fnName < x2.fnName ? -1 : 1)); const methodStrs = bindings.map((binding) => { + const camelFnName = snakeToCamel(binding.fnName); if (binding.inputType === "void") { - return `async ${snakeToCamel(binding.fnName)}(): Promise<${binding.outputType}> { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "${binding.fnName}", - payload: null, - }) + return `async ${camelFnName}(): Promise<${binding.outputType}> { + return this.callFn("${binding.fnName}"); }`; } else { - return `async ${snakeToCamel(binding.fnName)}(payload: ${binding.inputType}): Promise<${binding.outputType}> { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "${binding.fnName}", - payload, - }) + const camelInputName = snakeToCamel(binding.inputName); + return `async ${camelFnName}(${camelInputName}: ${binding.inputType}): Promise<${binding.outputType}> { + return this.callFn("${binding.fnName}", ${camelInputName}); }`; } }); @@ -112,6 +125,8 @@ async function extractFnBindingsForCrate(name, typesTransform) { if (splitDeps.holoom.length) { classFile += `import {${splitDeps.holoom.sort().join(", ")}} from '../types';\n`; } + classFile += `import { ValidationError } from "../errors";\n`; + const className = snakeToUpperCamel(name); classFile += ` export class ${className} { @@ -120,6 +135,15 @@ async function extractFnBindingsForCrate(name, typesTransform) { private readonly roleName = 'holoom', private readonly zomeName = '${name.replace("_coordinator", "")}', ) {} + + callFn(fn_name: string, payload?: unknown) { + return this.client.callZome({ + role_name: this.roleName, + zome_name: this.zomeName, + fn_name, + payload, + }).catch(ValidationError.tryCastThrow); + } ${methodStrs.join("\n\n")} } @@ -141,20 +165,22 @@ const snakeToUpperCamel = (str) => { return str.charAt(0).toUpperCase() + str.slice(1); }; -const HOLOCHAIN_TYPES = ["ActionHash", "AgentPubKey", "Record", "Signature"]; - class TypeTransform { - static async init() { - const transform = new TypeTransform(); + constructor( + readonly holoomTypes: string[], + readonly depTypes: string[], + readonly typeshareTypes: string[] + ) {} + static async init() { const tsFiles = await fs.readdir(`${CRATES_DIR}/holoom_types/bindings`); - transform.holoomTypes = tsFiles.map((name) => name.slice(0, -3)); + const holoomTypes = tsFiles.map((name) => name.slice(0, -3)); const depTypesContent = await fs.readFile( "src/dependency-types.ts", "utf8" ); - transform.depTypes = Array.from( + const depTypes = Array.from( depTypesContent.matchAll(/\nexport\s+\w+\s+(\w+)/g) ).map((match) => match[1]); @@ -162,14 +188,14 @@ class TypeTransform { "src/typeshare-generated.ts", "utf8" ); - transform.typeshareTypes = Array.from( + const typeshareTypes = Array.from( typeshareContent.matchAll(/\nexport\s+\w+\s+(\w+)/g) ).map((match) => match[1]); - return transform; + return new TypeTransform(holoomTypes, depTypes, typeshareTypes); } - transform(rustType) { + transform(rustType): { type: string; deps: Set } { const withDelimiters = rustType.replace(/([a-z0-9]+|[^\w])/gi, "$1†"); const parts = withDelimiters .split("†") @@ -229,7 +255,7 @@ class TypeTransform { throw new Error(`Type not determined for '${rustType}'`); } - transformElemsFromParts(parts) { + transformElemsFromParts(parts): { types: string[]; deps: Set } { const rustElems = parts .join("") .split(/,\s*?/) @@ -244,7 +270,7 @@ class TypeTransform { return { types, deps }; } - transformShallow(rustType) { + transformShallow(rustType): { type: string; deps: Set } { if (HOLOCHAIN_TYPES.includes(rustType)) { return { type: rustType, deps: new Set([`holochain:${rustType}`]) }; } else if (this.depTypes.includes(rustType)) { diff --git a/packages/types/scripts/extract-integrity-enums.ts b/packages/types/scripts/extract-integrity-enums.ts new file mode 100644 index 0000000..41d65f7 --- /dev/null +++ b/packages/types/scripts/extract-integrity-enums.ts @@ -0,0 +1,176 @@ +import fs from "fs/promises"; +import { glob } from "glob"; +import yaml from "yaml"; +import prettier from "prettier"; + +const CRATES_DIR = "../../crates"; +const integrity_regex = /^.+_integrity$/; + +async function main() { + const crates = await fs.readdir(CRATES_DIR); + const classNames = await Promise.all( + crates + .filter((name) => integrity_regex.test(name)) + .map((name) => extractEntryEnumsForCrate(name)) + ); + + classNames.filter((name) => !!name).sort(); + let indexContent = classNames + .map((className) => `export * from "./${className}";\n`) + .join(""); + + const zomeIndices = await getZomeIndices(); + indexContent += ` + export enum IntegrityZomeIndex { + ${zomeIndices.map((name, idx) => `${name} = ${idx}`).join(",\n")} + } + `; + + indexContent = await prettier.format(indexContent, { parser: "typescript" }); + fs.writeFile("./src/integrity-enums/index.ts", indexContent); +} + +async function getZomeIndices() { + const yamlContent = await fs.readFile("../../workdir/dna.yaml", "utf8"); + const dnaManifest = yaml.parse(yamlContent); + return dnaManifest.integrity.zomes.map((zome) => + snakeToUpperCamel(zome.name) + ); +} + +class EntryTypesEnumParser { + state: "searching" | "enum-started" | "enum-ended" = "searching"; + defs: string[] = []; + + parseLine(line: string) { + switch (this.state) { + case "searching": { + if (line === "pub enum EntryTypes {") { + this.state = "enum-started"; + } + return; + } + case "enum-started": { + if (line === "}") { + this.state = "enum-ended"; + } else { + const match = line.match(/^\s+(\w+)\(\w+\),$/); + if (!match) { + console.error("Bad line:", line); + throw new Error("Invalid EntryType variant"); + } + this.defs.push(match[1]); + } + return; + } + case "enum-ended": { + if (line === "pub enum EntryTypes {") { + throw new Error("Repeat definition"); + } + return; + } + } + } +} + +class LinkTypesEnumParser { + state: "searching" | "enum-started" | "enum-ended" = "searching"; + defs: string[] = []; + + parseLine(line: string) { + switch (this.state) { + case "searching": { + if (line === "pub enum LinkTypes {") { + this.state = "enum-started"; + } + return; + } + case "enum-started": { + if (line === "}") { + this.state = "enum-ended"; + } else { + const match = line.match(/^\s+(\w+),$/); + if (!match) { + console.error("Bad line:", line); + throw new Error("Invalid LinkType variant"); + } + this.defs.push(match[1]); + } + return; + } + case "enum-ended": { + if (line === "pub enum LinkTypes {") { + throw new Error("Repeat definition"); + } + return; + } + } + } +} + +async function extractEntryEnumsForCrate(name: string) { + console.log("Start extracting entry enums for:", name); + const rustFiles = await glob(`${CRATES_DIR}/${name}/src/**/*.rs`); + let entryDefs: string[] | undefined; + let linkDefs: string[] | undefined; + await Promise.all( + rustFiles.map(async (filePath) => { + const lines = (await fs.readFile(filePath, "utf-8")).split("\n"); + const entryTypesParser = new EntryTypesEnumParser(); + const linkTypesParser = new LinkTypesEnumParser(); + for (const line of lines) { + entryTypesParser.parseLine(line); + linkTypesParser.parseLine(line); + } + if (entryTypesParser.state === "enum-ended") { + if (entryDefs) { + throw new Error("entry defs already parsed"); + } + entryDefs = entryTypesParser.defs; + } + if (linkTypesParser.state === "enum-ended") { + if (linkDefs) { + throw new Error("link defs already parsed"); + } + linkDefs = linkTypesParser.defs; + } + }) + ); + + if (!entryDefs && !linkDefs) return; + + const zomeNameUpper = snakeToUpperCamel(name); + let content = ""; + if (entryDefs?.length) { + content += ` + export enum ${zomeNameUpper}EntryTypeIndex { + ${entryDefs.map((def, idx) => `${def} = ${idx},`).join("\n")} + } + `; + } + if (linkDefs?.length) { + content += ` + export enum ${zomeNameUpper}LinkTypeIndex { + ${linkDefs.map((def, idx) => `${def} = ${idx},`).join("\n")} + } + `; + } + + content = await prettier.format(content, { parser: "typescript" }); + await fs.writeFile(`./src/integrity-enums/${zomeNameUpper}.ts`, content); + return zomeNameUpper; +} + +const snakeToCamel = (str) => + str + .toLowerCase() + .replace(/([-_][a-z])/g, (group) => + group.toUpperCase().replace("-", "").replace("_", "") + ); + +const snakeToUpperCamel = (str) => { + str = snakeToCamel(str); + return str.charAt(0).toUpperCase() + str.slice(1); +}; + +main().catch(console.error); diff --git a/packages/types/scripts/holochain-types.ts b/packages/types/scripts/holochain-types.ts new file mode 100644 index 0000000..491d921 --- /dev/null +++ b/packages/types/scripts/holochain-types.ts @@ -0,0 +1,10 @@ +export const HOLOCHAIN_TYPES = [ + "ActionHash", + "AgentPubKey", + "Record", + "Signature", + "ZomeIndex", + "LinkTag", + "LinkType", + "AnyLinkableHash", +]; diff --git a/packages/types/scripts/prepare-bindings.mjs b/packages/types/scripts/prepare-bindings.ts similarity index 85% rename from packages/types/scripts/prepare-bindings.mjs rename to packages/types/scripts/prepare-bindings.ts index eb4047e..4a41ac7 100644 --- a/packages/types/scripts/prepare-bindings.mjs +++ b/packages/types/scripts/prepare-bindings.ts @@ -1,15 +1,17 @@ import fs from "fs/promises"; import prettier from "prettier"; +import { HOLOCHAIN_TYPES } from "./holochain-types"; const BINDINGS_PATH = "../../crates/holoom_types/bindings/"; -const HC_TYPES = ["AgentPubKey", "ActionHash", "Record", "Signature"]; async function main() { const files = await fs.readdir(BINDINGS_PATH); for (const file of files) { // Insert missing imports let content = await fs.readFile(BINDINGS_PATH + file, "utf8"); - const imports = HC_TYPES.filter((typeName) => content.includes(typeName)); + const imports = HOLOCHAIN_TYPES.filter((typeName) => + content.includes(typeName) + ); if (imports.length) { // Prepend imports const importLine = `import { ${imports.join( diff --git a/packages/types/src/dependency-types.ts b/packages/types/src/dependency-types.ts index edb070c..68e5f78 100644 --- a/packages/types/src/dependency-types.ts +++ b/packages/types/src/dependency-types.ts @@ -3,4 +3,9 @@ // strategy for ensuring that these types stay in sync with the external rust // crates that they represent types from. +// Type alias for `alloy_primitives::Address` export type EthAddress = Uint8Array; + +// (I don't know why these aren't exported from @holochain/client) +export type EntryDefIndex = number; +export type SerializedBytes = Uint8Array; diff --git a/packages/types/src/errors.ts b/packages/types/src/errors.ts new file mode 100644 index 0000000..1c14878 --- /dev/null +++ b/packages/types/src/errors.ts @@ -0,0 +1,42 @@ +import { ValidationRejectionDetail } from "./typeshare-generated"; + +export class ValidationError extends Error { + constructor( + message: string, + readonly detail: ValidationRejectionDetail + ) { + super(message); + } + + static tryCast(error: Error): Error { + if (error.message.includes("InvalidCommit")) { + const fail = () => { + console.error( + `Badly formed invalid commit reasons message: ${error.message}` + ); + return error; + }; + const parts1 = error.message.split("__REASONS_START__"); + if (parts1.length !== 2) return fail(); + const parts2 = parts1[1].split("__REASONS_END__"); + if (parts2.length !== 2) return fail(); + try { + const detail = JSON.parse(parts2[0]); + return new ValidationError(error.message, detail); + } catch { + return fail(); + } + } + return error; + } + + static tryCastThrow(error: Error): never { + throw ValidationError.tryCast(error); + } + + static getDetail(error: unknown) { + if (error instanceof ValidationError) { + return error.detail; + } + } +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 8e18db4..97ec2d5 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -2,3 +2,5 @@ export * from "./types"; export * from "./zome-functions"; export * from "./typeshare-generated"; export * from "./dependency-types"; +export * from "./integrity-enums"; +export * from "./errors"; diff --git a/packages/types/src/integrity-enums/UsernameRegistryIntegrity.ts b/packages/types/src/integrity-enums/UsernameRegistryIntegrity.ts new file mode 100644 index 0000000..934d36f --- /dev/null +++ b/packages/types/src/integrity-enums/UsernameRegistryIntegrity.ts @@ -0,0 +1,22 @@ +export enum UsernameRegistryIntegrityEntryTypeIndex { + UsernameAttestation = 0, + WalletAttestation = 1, + ExternalIdAttestation = 2, + OracleDocument = 3, + Recipe = 4, + RecipeExecution = 5, + SignedEvmSigningOffer = 6, +} + +export enum UsernameRegistryIntegrityLinkTypeIndex { + AgentToUsernameAttestations = 0, + AgentMetadata = 1, + AgentToWalletAttestations = 2, + AgentToExternalIdAttestation = 3, + ExternalIdToAttestation = 4, + NameToOracleDocument = 5, + RelateOracleDocumentName = 6, + NameToRecipe = 7, + NameToSigningOffer = 8, + EvmAddressToSigningOffer = 9, +} diff --git a/packages/types/src/integrity-enums/index.ts b/packages/types/src/integrity-enums/index.ts new file mode 100644 index 0000000..c9f8732 --- /dev/null +++ b/packages/types/src/integrity-enums/index.ts @@ -0,0 +1,5 @@ +export * from "./UsernameRegistryIntegrity"; + +export enum IntegrityZomeIndex { + UsernameRegistryIntegrity = 0, +} diff --git a/packages/types/src/types/WalletAttestation.ts b/packages/types/src/types/WalletAttestation.ts index 019d207..d860d2f 100644 --- a/packages/types/src/types/WalletAttestation.ts +++ b/packages/types/src/types/WalletAttestation.ts @@ -1,4 +1,4 @@ -import { AgentPubKey, ActionHash, Signature } from "@holochain/client"; +import { ActionHash, AgentPubKey, Signature } from "@holochain/client"; // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ChainWalletSignature } from "./ChainWalletSignature"; diff --git a/packages/types/src/typeshare-generated.ts b/packages/types/src/typeshare-generated.ts index fba40e2..ed3d57a 100644 --- a/packages/types/src/typeshare-generated.ts +++ b/packages/types/src/typeshare-generated.ts @@ -1,6 +1,15 @@ // Import prepended by scripts/add-imports-to-typeshare.ts -import { ActionHash, AgentPubKey, Record, Signature } from "@holochain/client"; -import { EthAddress } from "./dependency-types"; +import { + ActionHash, + AgentPubKey, + Record, + Signature, + ZomeIndex, + LinkTag, + LinkType, + AnyLinkableHash, +} from "@holochain/client"; +import { EthAddress, EntryDefIndex, SerializedBytes } from "./dependency-types"; /* Generated by typeshare 1.9.2 */ @@ -11,13 +20,32 @@ export interface MetadataItem { value: string; } +/** Input to the `create_app_entry_raw` function */ +export interface CreateAppEntryRawInput { + /** The index of the zome that defines the entry type */ + zome_index: ZomeIndex; + /** The index of the entry definition within the zome */ + entry_def_index: EntryDefIndex; + /** The msgpack serialised app entry content */ + entry_bytes: SerializedBytes; +} + +/** Input to the `create_link_raw` function */ +export interface CreateLinkRawInput { + /** The 'form' address of the link */ + base_address: AnyLinkableHash; + /** The 'to' address of the link */ + target_address: AnyLinkableHash; + /** The index of the zome in which the link was defined */ + zome_index: ZomeIndex; + /** The index of the link definition within the zome */ + link_type: LinkType; + /** Freeform data attached to the link */ + tag: LinkTag; +} + /** The input argument to `update_metadata_item`` */ export interface UpdateMetadataItemInput { - /** - * This has to be set to your own key. The only reason this field isn't - * instead inferred is for the sake of enabling testing of the fail case. - */ - agent_pubkey: AgentPubKey; /** The key for the particular metadata item */ name: string; /** The value to assign to the key */ @@ -31,3 +59,33 @@ export interface GetMetadataItemValueInput { /** The key for the particular metadata item */ name: string; } + +/** Reasons for which a create `AgentMetadata` link action can fail validation. */ +export enum CreateAgentMetadataLinkRejectionReason { + /** + * The base address is the agent pubkey of the user who is being annotated with metadata. + * As a user can only author their own metadata, the base address has match their own pubkey. + */ + BaseAddressMustBeOwner = "BaseAddressMustBeOwner", + /** The link tag content doesn't match the expected key-value schema struct `MetadataItem`. */ + BadTagSerialization = "BadTagSerialization", +} + +/** Reasons for which a delete `AgentMetadata` link action can fail validation. */ +export enum DeleteAgentMetadataLinkRejectionReason { + /** + * The user attempting to delete the metadata item is not the owner and therefore doesn't + * have permission. + */ + DeleterIsNotOwner = "DeleterIsNotOwner", +} + +export type ValidationRejectionDetail = + | { + type: "CreateAgentMetadataLinkRejectionReasons"; + reasons: CreateAgentMetadataLinkRejectionReason[]; + } + | { + type: "DeleteAgentMetadataLinkRejectionReasons"; + reasons: DeleteAgentMetadataLinkRejectionReason[]; + }; diff --git a/packages/types/src/zome-functions/PingCoordinator.ts b/packages/types/src/zome-functions/PingCoordinator.ts index af9c334..1a1f9b1 100644 --- a/packages/types/src/zome-functions/PingCoordinator.ts +++ b/packages/types/src/zome-functions/PingCoordinator.ts @@ -1,4 +1,5 @@ import { AppClient } from "@holochain/client"; +import { ValidationError } from "../errors"; export class PingCoordinator { constructor( @@ -7,12 +8,18 @@ export class PingCoordinator { private readonly zomeName = "ping", ) {} + callFn(fn_name: string, payload?: unknown) { + return this.client + .callZome({ + role_name: this.roleName, + zome_name: this.zomeName, + fn_name, + payload, + }) + .catch(ValidationError.tryCastThrow); + } + async ping(): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "ping", - payload: null, - }); + return this.callFn("ping"); } } diff --git a/packages/types/src/zome-functions/RecordsCoordinator.ts b/packages/types/src/zome-functions/RecordsCoordinator.ts index e8f1f02..abdb8ca 100644 --- a/packages/types/src/zome-functions/RecordsCoordinator.ts +++ b/packages/types/src/zome-functions/RecordsCoordinator.ts @@ -1,4 +1,9 @@ import { ActionHash, AppClient, Record } from "@holochain/client"; +import { + CreateAppEntryRawInput, + CreateLinkRawInput, +} from "../typeshare-generated"; +import { ValidationError } from "../errors"; export class RecordsCoordinator { constructor( @@ -7,12 +12,34 @@ export class RecordsCoordinator { private readonly zomeName = "records", ) {} - async getRecord(payload: ActionHash): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_record", - payload, - }); + callFn(fn_name: string, payload?: unknown) { + return this.client + .callZome({ + role_name: this.roleName, + zome_name: this.zomeName, + fn_name, + payload, + }) + .catch(ValidationError.tryCastThrow); + } + + async createAppEntryRaw(input: CreateAppEntryRawInput): Promise { + return this.callFn("create_app_entry_raw", input); + } + + async createLinkRaw(input: CreateLinkRawInput): Promise { + return this.callFn("create_link_raw", input); + } + + async deleteEntryRaw(actionHash: ActionHash): Promise { + return this.callFn("delete_entry_raw", actionHash); + } + + async deleteLinkRaw(createLinkActionHash: ActionHash): Promise { + return this.callFn("delete_link_raw", createLinkActionHash); + } + + async getRecord(actionHash: ActionHash): Promise { + return this.callFn("get_record", actionHash); } } diff --git a/packages/types/src/zome-functions/SignerCoordinator.ts b/packages/types/src/zome-functions/SignerCoordinator.ts index 8427917..9381650 100644 --- a/packages/types/src/zome-functions/SignerCoordinator.ts +++ b/packages/types/src/zome-functions/SignerCoordinator.ts @@ -1,5 +1,6 @@ import { AppClient, Signature } from "@holochain/client"; import { SignableBytes } from "../types"; +import { ValidationError } from "../errors"; export class SignerCoordinator { constructor( @@ -8,12 +9,18 @@ export class SignerCoordinator { private readonly zomeName = "signer", ) {} - async signMessage(payload: SignableBytes): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "sign_message", - payload, - }); + callFn(fn_name: string, payload?: unknown) { + return this.client + .callZome({ + role_name: this.roleName, + zome_name: this.zomeName, + fn_name, + payload, + }) + .catch(ValidationError.tryCastThrow); + } + + async signMessage(message: SignableBytes): Promise { + return this.callFn("sign_message", message); } } diff --git a/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts b/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts index c069707..eb7c43c 100644 --- a/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts +++ b/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts @@ -23,6 +23,7 @@ import { UsernameAttestation, WalletAttestation, } from "../types"; +import { ValidationError } from "../errors"; export class UsernameRegistryCoordinator { constructor( @@ -31,424 +32,258 @@ export class UsernameRegistryCoordinator { private readonly zomeName = "username_registry", ) {} - async attestWalletSignature(payload: ChainWalletSignature): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "attest_wallet_signature", - payload, - }); + callFn(fn_name: string, payload?: unknown) { + return this.client + .callZome({ + role_name: this.roleName, + zome_name: this.zomeName, + fn_name, + payload, + }) + .catch(ValidationError.tryCastThrow); + } + + async attestWalletSignature( + chainWalletSignature: ChainWalletSignature, + ): Promise { + return this.callFn("attest_wallet_signature", chainWalletSignature); } async confirmExternalIdRequest( payload: ConfirmExternalIdRequestPayload, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "confirm_external_id_request", - payload, - }); + return this.callFn("confirm_external_id_request", payload); } async createExternalIdAttestation( - payload: ExternalIdAttestation, + attestation: ExternalIdAttestation, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "create_external_id_attestation", - payload, - }); + return this.callFn("create_external_id_attestation", attestation); } - async createOracleDocument(payload: OracleDocument): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "create_oracle_document", - payload, - }); + async createOracleDocument(oracleDocument: OracleDocument): Promise { + return this.callFn("create_oracle_document", oracleDocument); } - async createRecipe(payload: Recipe): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "create_recipe", - payload, - }); + async createRecipe(recipe: Recipe): Promise { + return this.callFn("create_recipe", recipe); } - async createRecipeExecution(payload: RecipeExecution): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "create_recipe_execution", - payload, - }); + async createRecipeExecution( + recipeExecution: RecipeExecution, + ): Promise { + return this.callFn("create_recipe_execution", recipeExecution); } async createSignedEvmSigningOffer( payload: CreateEvmSigningOfferPayload, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "create_signed_evm_signing_offer", - payload, - }); + return this.callFn("create_signed_evm_signing_offer", payload); } async createUsernameAttestation( - payload: UsernameAttestation, + usernameAttestation: UsernameAttestation, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "create_username_attestation", - payload, - }); + return this.callFn("create_username_attestation", usernameAttestation); } - async createWalletAttestation(payload: WalletAttestation): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "create_wallet_attestation", - payload, - }); + async createWalletAttestation( + walletAttestation: WalletAttestation, + ): Promise { + return this.callFn("create_wallet_attestation", walletAttestation); } - async deleteExternalIdAttestation(payload: ActionHash): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "delete_external_id_attestation", - payload, - }); + async deleteExternalIdAttestation( + originalAttestationHash: ActionHash, + ): Promise { + return this.callFn( + "delete_external_id_attestation", + originalAttestationHash, + ); } - async deleteUsernameAttestation(payload: ActionHash): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "delete_username_attestation", - payload, - }); + async deleteUsernameAttestation( + originalUsernameAttestationHash: ActionHash, + ): Promise { + return this.callFn( + "delete_username_attestation", + originalUsernameAttestationHash, + ); } - async doesAgentHaveUsername(payload: AgentPubKey): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "does_agent_have_username", - payload, - }); + async doesAgentHaveUsername(agent: AgentPubKey): Promise { + return this.callFn("does_agent_have_username", agent); } async executeRecipe(payload: ExecuteRecipePayload): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "execute_recipe", - payload, - }); + return this.callFn("execute_recipe", payload); } async getAllExternalIdAhs(): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_all_external_id_ahs", - payload: null, - }); + return this.callFn("get_all_external_id_ahs"); } async getAllUsernameAttestations(): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_all_username_attestations", - payload: null, - }); - } - - async getAttestationForExternalId(payload: string): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_attestation_for_external_id", - payload, - }); + return this.callFn("get_all_username_attestations"); + } + + async getAttestationForExternalId( + externalId: string, + ): Promise { + return this.callFn("get_attestation_for_external_id", externalId); } async getAuthority(): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_authority", - payload: null, - }); - } - - async getEvmWalletBindingMessage(payload: Uint8Array): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_evm_wallet_binding_message", - payload, - }); + return this.callFn("get_authority"); } - async getExternalIdAttestation(payload: ActionHash): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_external_id_attestation", - payload, - }); + async getEvmWalletBindingMessage(evmAddress: Uint8Array): Promise { + return this.callFn("get_evm_wallet_binding_message", evmAddress); + } + + async getExternalIdAttestation( + externalIdAh: ActionHash, + ): Promise { + return this.callFn("get_external_id_attestation", externalIdAh); } async getExternalIdAttestationsForAgent( - payload: AgentPubKey, + agentPubkey: AgentPubKey, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_external_id_attestations_for_agent", - payload, - }); + return this.callFn("get_external_id_attestations_for_agent", agentPubkey); } async getLatestEvmSigningOfferAhForName( - payload: string, + name: string, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_latest_evm_signing_offer_ah_for_name", - payload, - }); + return this.callFn("get_latest_evm_signing_offer_ah_for_name", name); } - async getMetadata(payload: AgentPubKey): Promise<{ [key: string]: string }> { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_metadata", - payload, - }); + async getMetadata( + agentPubkey: AgentPubKey, + ): Promise<{ [key: string]: string }> { + return this.callFn("get_metadata", agentPubkey); } async getMetadataItemValue( - payload: GetMetadataItemValueInput, + input: GetMetadataItemValueInput, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_metadata_item_value", - payload, - }); + return this.callFn("get_metadata_item_value", input); } - async getOracleDocumentLinkAhsForName( - payload: string, - ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_oracle_document_link_ahs_for_name", - payload, - }); + async getOracleDocumentLinkAhsForName(name: string): Promise { + return this.callFn("get_oracle_document_link_ahs_for_name", name); } - async getRelatedOracleDocumentNames(payload: string): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_related_oracle_document_names", - payload, - }); + async getRelatedOracleDocumentNames(relationName: string): Promise { + return this.callFn("get_related_oracle_document_names", relationName); } - async getRelationLinkAhs(payload: string): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_relation_link_ahs", - payload, - }); + async getRelationLinkAhs(relationName: string): Promise { + return this.callFn("get_relation_link_ahs", relationName); } async getSigningOfferAhsForEvmAddress( - payload: Uint8Array, + evmAddress: Uint8Array, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_signing_offer_ahs_for_evm_address", - payload, - }); + return this.callFn("get_signing_offer_ahs_for_evm_address", evmAddress); } - async getSolanaWalletBindingMessage(payload: Uint8Array): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_solana_wallet_binding_message", - payload, - }); + async getSolanaWalletBindingMessage( + solanaAddress: Uint8Array, + ): Promise { + return this.callFn("get_solana_wallet_binding_message", solanaAddress); } - async getUsernameAttestation(payload: ActionHash): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_username_attestation", - payload, - }); + async getUsernameAttestation( + usernameAttestationHash: ActionHash, + ): Promise { + return this.callFn("get_username_attestation", usernameAttestationHash); } async getUsernameAttestationForAgent( - payload: AgentPubKey, + agent: AgentPubKey, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_username_attestation_for_agent", - payload, - }); + return this.callFn("get_username_attestation_for_agent", agent); } - async getWalletAttestation(payload: ActionHash): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_wallet_attestation", - payload, - }); + async getWalletAttestation( + walletAttestationHash: ActionHash, + ): Promise { + return this.callFn("get_wallet_attestation", walletAttestationHash); } - async getWalletAttestationsForAgent(payload: AgentPubKey): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "get_wallet_attestations_for_agent", - payload, - }); + async getWalletAttestationsForAgent(agent: AgentPubKey): Promise { + return this.callFn("get_wallet_attestations_for_agent", agent); } async ingestEvmSignatureOverRecipeExecutionRequest( payload: EvmSignatureOverRecipeExecutionRequest, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "ingest_evm_signature_over_recipe_execution_request", + return this.callFn( + "ingest_evm_signature_over_recipe_execution_request", payload, - }); + ); } async ingestExternalIdAttestationRequest( payload: IngestExternalIdAttestationRequestPayload, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "ingest_external_id_attestation_request", - payload, - }); + return this.callFn("ingest_external_id_attestation_request", payload); } - async ingestSignedUsername(payload: SignedUsername): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "ingest_signed_username", - payload, - }); + async ingestSignedUsername(signedUsername: SignedUsername): Promise { + return this.callFn("ingest_signed_username", signedUsername); } async rejectEvmSignatureOverRecipeExecutionRequest( payload: RejectEvmSignatureOverRecipeExecutionRequestPayload, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "reject_evm_signature_over_recipe_execution_request", + return this.callFn( + "reject_evm_signature_over_recipe_execution_request", payload, - }); + ); } async rejectExternalIdRequest( payload: RejectExternalIdRequestPayload, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "reject_external_id_request", - payload, - }); + return this.callFn("reject_external_id_request", payload); } - async relateOracleDocument(payload: DocumentRelationTag): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "relate_oracle_document", - payload, - }); + async relateOracleDocument(relationTag: DocumentRelationTag): Promise { + return this.callFn("relate_oracle_document", relationTag); } async resolveEvmSignatureOverRecipeExecutionRequest( payload: ResolveEvmSignatureOverRecipeExecutionRequestPayload, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "resolve_evm_signature_over_recipe_execution_request", + return this.callFn( + "resolve_evm_signature_over_recipe_execution_request", payload, - }); + ); } async sendExternalIdAttestationRequest( payload: SendExternalIdAttestationRequestPayload, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "send_external_id_attestation_request", - payload, - }); + return this.callFn("send_external_id_attestation_request", payload); } async sendRequestForEvmSignatureOverRecipeExecution( - payload: EvmSignatureOverRecipeExecutionRequest, + request: EvmSignatureOverRecipeExecutionRequest, ): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "send_request_for_evm_signature_over_recipe_execution", - payload, - }); + return this.callFn( + "send_request_for_evm_signature_over_recipe_execution", + request, + ); } - async signUsernameToAttest(payload: string): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "sign_username_to_attest", - payload, - }); + async signUsernameToAttest(username: string): Promise { + return this.callFn("sign_username_to_attest", username); } - async updateMetadataItem(payload: UpdateMetadataItemInput): Promise { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name: "update_metadata_item", - payload, - }); + async updateMetadataItem(input: UpdateMetadataItemInput): Promise { + return this.callFn("update_metadata_item", input); } } From 0bc9b41598e421ced634cfd27dbb1185f019ce49 Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Wed, 21 Aug 2024 11:56:22 +0100 Subject: [PATCH 13/17] chore: see if single threaded tryorama un-breaks CI --- packages/tryorama/vitest.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tryorama/vitest.config.ts b/packages/tryorama/vitest.config.ts index 2221292..7d65e76 100644 --- a/packages/tryorama/vitest.config.ts +++ b/packages/tryorama/vitest.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { + poolOptions: { threads: { singleThread: true } }, testTimeout: 60 * 1000 * 3, // 3 mins }, }); From be5c9b126d972ec75d37129e3ac84d98ca59a14f Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Wed, 21 Aug 2024 13:17:05 +0100 Subject: [PATCH 14/17] fix: CI missing dep timing bug workaround --- packages/types/scripts/extract-fn-bindings.ts | 11 +++--- packages/types/src/call-zome-helper.ts | 37 +++++++++++++++++++ .../src/zome-functions/PingCoordinator.ts | 17 ++++----- .../src/zome-functions/RecordsCoordinator.ts | 17 ++++----- .../src/zome-functions/SignerCoordinator.ts | 17 ++++----- .../UsernameRegistryCoordinator.ts | 17 ++++----- 6 files changed, 75 insertions(+), 41 deletions(-) create mode 100644 packages/types/src/call-zome-helper.ts diff --git a/packages/types/scripts/extract-fn-bindings.ts b/packages/types/scripts/extract-fn-bindings.ts index 49c93d9..fa611e5 100644 --- a/packages/types/scripts/extract-fn-bindings.ts +++ b/packages/types/scripts/extract-fn-bindings.ts @@ -125,7 +125,7 @@ async function extractFnBindingsForCrate( if (splitDeps.holoom.length) { classFile += `import {${splitDeps.holoom.sort().join(", ")}} from '../types';\n`; } - classFile += `import { ValidationError } from "../errors";\n`; + classFile += `import { callZomeAndTransformError } from "../call-zome-helper";\n`; const className = snakeToUpperCamel(name); classFile += ` @@ -137,12 +137,13 @@ async function extractFnBindingsForCrate( ) {} callFn(fn_name: string, payload?: unknown) { - return this.client.callZome({ - role_name: this.roleName, - zome_name: this.zomeName, + return callZomeAndTransformError( + this.client, + this.roleName, + this.zomeName, fn_name, payload, - }).catch(ValidationError.tryCastThrow); + ); } ${methodStrs.join("\n\n")} diff --git a/packages/types/src/call-zome-helper.ts b/packages/types/src/call-zome-helper.ts new file mode 100644 index 0000000..336d45a --- /dev/null +++ b/packages/types/src/call-zome-helper.ts @@ -0,0 +1,37 @@ +import { AppClient } from "@holochain/client"; +import { ValidationError } from "./errors"; + +const MISSING_ACTION_REGEX = + /Source chain error: InvalidCommit error: The dependency AnyDhtHash\(uhCkk[^\)]{48}\) was not found on the DHT/; + +export async function callZomeAndTransformError( + client: AppClient, + role_name: string, + zome_name: string, + fn_name: string, + payload: unknown +) { + const invoke = () => + client + .callZome({ + role_name, + zome_name, + fn_name, + payload, + }) + .catch(ValidationError.tryCastThrow); + try { + const result = await invoke(); + return result; + } catch (err) { + if (err instanceof Error && MISSING_ACTION_REGEX.test(err.message)) { + // This appears to be some timing related error. So far I've only seen + // it occur in CI. For now we'll allow one retry after a short wait as a + // work around. + await new Promise((r) => setTimeout(r, 500)); + const result = await invoke(); + return result; + } + throw err; + } +} diff --git a/packages/types/src/zome-functions/PingCoordinator.ts b/packages/types/src/zome-functions/PingCoordinator.ts index 1a1f9b1..5a9130a 100644 --- a/packages/types/src/zome-functions/PingCoordinator.ts +++ b/packages/types/src/zome-functions/PingCoordinator.ts @@ -1,5 +1,5 @@ import { AppClient } from "@holochain/client"; -import { ValidationError } from "../errors"; +import { callZomeAndTransformError } from "../call-zome-helper"; export class PingCoordinator { constructor( @@ -9,14 +9,13 @@ export class PingCoordinator { ) {} callFn(fn_name: string, payload?: unknown) { - return this.client - .callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name, - payload, - }) - .catch(ValidationError.tryCastThrow); + return callZomeAndTransformError( + this.client, + this.roleName, + this.zomeName, + fn_name, + payload, + ); } async ping(): Promise { diff --git a/packages/types/src/zome-functions/RecordsCoordinator.ts b/packages/types/src/zome-functions/RecordsCoordinator.ts index abdb8ca..a5fe4cb 100644 --- a/packages/types/src/zome-functions/RecordsCoordinator.ts +++ b/packages/types/src/zome-functions/RecordsCoordinator.ts @@ -3,7 +3,7 @@ import { CreateAppEntryRawInput, CreateLinkRawInput, } from "../typeshare-generated"; -import { ValidationError } from "../errors"; +import { callZomeAndTransformError } from "../call-zome-helper"; export class RecordsCoordinator { constructor( @@ -13,14 +13,13 @@ export class RecordsCoordinator { ) {} callFn(fn_name: string, payload?: unknown) { - return this.client - .callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name, - payload, - }) - .catch(ValidationError.tryCastThrow); + return callZomeAndTransformError( + this.client, + this.roleName, + this.zomeName, + fn_name, + payload, + ); } async createAppEntryRaw(input: CreateAppEntryRawInput): Promise { diff --git a/packages/types/src/zome-functions/SignerCoordinator.ts b/packages/types/src/zome-functions/SignerCoordinator.ts index 9381650..bc3115e 100644 --- a/packages/types/src/zome-functions/SignerCoordinator.ts +++ b/packages/types/src/zome-functions/SignerCoordinator.ts @@ -1,6 +1,6 @@ import { AppClient, Signature } from "@holochain/client"; import { SignableBytes } from "../types"; -import { ValidationError } from "../errors"; +import { callZomeAndTransformError } from "../call-zome-helper"; export class SignerCoordinator { constructor( @@ -10,14 +10,13 @@ export class SignerCoordinator { ) {} callFn(fn_name: string, payload?: unknown) { - return this.client - .callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name, - payload, - }) - .catch(ValidationError.tryCastThrow); + return callZomeAndTransformError( + this.client, + this.roleName, + this.zomeName, + fn_name, + payload, + ); } async signMessage(message: SignableBytes): Promise { diff --git a/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts b/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts index eb7c43c..7140238 100644 --- a/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts +++ b/packages/types/src/zome-functions/UsernameRegistryCoordinator.ts @@ -23,7 +23,7 @@ import { UsernameAttestation, WalletAttestation, } from "../types"; -import { ValidationError } from "../errors"; +import { callZomeAndTransformError } from "../call-zome-helper"; export class UsernameRegistryCoordinator { constructor( @@ -33,14 +33,13 @@ export class UsernameRegistryCoordinator { ) {} callFn(fn_name: string, payload?: unknown) { - return this.client - .callZome({ - role_name: this.roleName, - zome_name: this.zomeName, - fn_name, - payload, - }) - .catch(ValidationError.tryCastThrow); + return callZomeAndTransformError( + this.client, + this.roleName, + this.zomeName, + fn_name, + payload, + ); } async attestWalletSignature( From 2e6a7faa1afefe1d6108ef42ab833f2ede83b884 Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Wed, 21 Aug 2024 15:25:05 +0100 Subject: [PATCH 15/17] debug raw test --- packages/tryorama/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tryorama/package.json b/packages/tryorama/package.json index 9718fdc..19da130 100644 --- a/packages/tryorama/package.json +++ b/packages/tryorama/package.json @@ -2,7 +2,7 @@ "name": "@holoom/tryorama", "private": true, "scripts": { - "test": "TRYORAMA_LOG_LEVEL=error vitest run" + "test": "TRYORAMA_LOG_LEVEL=debug vitest run raw" }, "dependencies": { "@holochain/client": "^0.18.0-dev.8", From 954367bd59e666be1c511f6c88010fbce5cfc36c Mon Sep 17 00:00:00 2001 From: 8e8b2c <8e8b2c@proton.me> Date: Wed, 21 Aug 2024 15:30:28 +0100 Subject: [PATCH 16/17] kick --- packages/types/src/call-zome-helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/call-zome-helper.ts b/packages/types/src/call-zome-helper.ts index 336d45a..62906ef 100644 --- a/packages/types/src/call-zome-helper.ts +++ b/packages/types/src/call-zome-helper.ts @@ -1,5 +1,5 @@ -import { AppClient } from "@holochain/client"; import { ValidationError } from "./errors"; +import { AppClient } from "@holochain/client"; const MISSING_ACTION_REGEX = /Source chain error: InvalidCommit error: The dependency AnyDhtHash\(uhCkk[^\)]{48}\) was not found on the DHT/; From 71a229964df2b522ac7651718be9704733165321 Mon Sep 17 00:00:00 2001 From: 8e8b2c <138928994+8e8b2c@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:40:10 +0100 Subject: [PATCH 17/17] Update package.json --- packages/tryorama/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tryorama/package.json b/packages/tryorama/package.json index 19da130..6ddbfd0 100644 --- a/packages/tryorama/package.json +++ b/packages/tryorama/package.json @@ -2,7 +2,7 @@ "name": "@holoom/tryorama", "private": true, "scripts": { - "test": "TRYORAMA_LOG_LEVEL=debug vitest run raw" + "test": "TRYORAMA_LOG_LEVEL=debug vitest run" }, "dependencies": { "@holochain/client": "^0.18.0-dev.8",