commit 7934096d1848692a0485e2c08ceb9dc567f004f0 parent 97fdfe0efa84fb2cb5a9bd0984c978550b133be7 Author: triesap <tyson@radroots.org> Date: Mon, 27 Jul 2026 16:38:42 +0000 core: migrate workspace consumers - Migrate SDK examples tests WASM and trade bindings to canonical core APIs - Move source-owned language descriptors into the private core binding crate - Regenerate canonical TypeScript exports and cross-package imports - Adopt sealed transport accessors and validated receipt invariants Diffstat:
25 files changed, 436 insertions(+), 355 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock @@ -1888,9 +1888,7 @@ dependencies = [ name = "radroots-core" version = "0.1.0" dependencies = [ - "dto_bindgen", "rust_decimal", - "rust_decimal_macros", "serde", ] @@ -2065,6 +2063,8 @@ dependencies = [ name = "radroots_core_bindings" version = "0.1.0" dependencies = [ + "dto_bindgen", + "dto_bindgen_core", "radroots-core", ] @@ -2347,8 +2347,8 @@ dependencies = [ "dto_bindgen_backend_ts", "dto_bindgen_core", "flate2", - "radroots-core", "radroots-event", + "radroots_core_bindings", "radroots_event_bindings", "radroots_event_index", "radroots_identity_bindings", @@ -2583,16 +2583,6 @@ dependencies = [ ] [[package]] -name = "rust_decimal_macros" -version = "1.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74a5a6f027e892c7a035c6fddb50435a1fbf5a734ffc0c2a9fed4d0221440519" -dependencies = [ - "quote", - "syn", -] - -[[package]] name = "rustc-hash" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/crates/core_bindings/Cargo.toml b/crates/core_bindings/Cargo.toml @@ -10,4 +10,6 @@ homepage.workspace = true publish = false [dependencies] -radroots_core = { workspace = true } +dto_bindgen = { workspace = true } +dto_bindgen_core = { workspace = true } +radroots_core = { workspace = true, features = ["serde", "std"] } diff --git a/crates/core_bindings/src/dto.rs b/crates/core_bindings/src/dto.rs @@ -0,0 +1,139 @@ +#![allow(dead_code)] + +use dto_bindgen_core::RootDescriptor; + +pub fn dto_roots() -> Vec<RootDescriptor> { + vec![ + RootDescriptor::new::<DiscountDescriptor>(), + RootDescriptor::new::<DiscountScopeDescriptor>(), + RootDescriptor::new::<DiscountThresholdDescriptor>(), + RootDescriptor::new::<DiscountValueDescriptor>(), + RootDescriptor::new::<MoneyDescriptor>(), + RootDescriptor::new::<PercentDescriptor>(), + RootDescriptor::new::<QuantityDescriptor>(), + RootDescriptor::new::<QuantityPriceDescriptor>(), + RootDescriptor::new::<UnitDescriptor>(), + RootDescriptor::new::<UnitDimensionDescriptor>(), + ] +} + +#[derive(dto_bindgen::Dto)] +#[dto(as = "string")] +#[dto(ts(name = "Currency"))] +struct CurrencyDescriptor(String); + +#[derive(dto_bindgen::Dto)] +#[dto(as = "string")] +#[dto(ts(name = "Decimal"))] +struct DecimalDescriptor(String); + +#[derive(dto_bindgen::Dto)] +#[dto(ts(name = "Money"))] +struct MoneyDescriptor { + amount: DecimalDescriptor, + currency: CurrencyDescriptor, +} + +#[derive(dto_bindgen::Dto)] +#[dto(ts(name = "Percent"))] +struct PercentDescriptor { + value: DecimalDescriptor, +} + +#[derive(dto_bindgen::Dto)] +#[dto(as = "string_enum")] +#[dto(ts(name = "Unit"))] +enum UnitDescriptor { + #[dto(rename = "each")] + Each, + #[dto(rename = "kg")] + MassKg, + #[dto(rename = "g")] + MassG, + #[dto(rename = "oz")] + MassOz, + #[dto(rename = "lb")] + MassLb, + #[dto(rename = "l")] + VolumeL, + #[dto(rename = "ml")] + VolumeMl, +} + +#[derive(dto_bindgen::Dto)] +#[dto(as = "string_enum")] +#[dto(ts(name = "UnitDimension"))] +enum UnitDimensionDescriptor { + #[dto(rename = "count")] + Count, + #[dto(rename = "mass")] + Mass, + #[dto(rename = "volume")] + Volume, +} + +#[derive(dto_bindgen::Dto)] +#[dto(ts(name = "Quantity"))] +struct QuantityDescriptor { + amount: DecimalDescriptor, + unit: UnitDescriptor, + #[serde(skip_serializing_if = "Option::is_none")] + label: Option<String>, +} + +#[derive(dto_bindgen::Dto)] +#[dto(ts(name = "QuantityPrice"))] +struct QuantityPriceDescriptor { + amount: MoneyDescriptor, + quantity: QuantityDescriptor, +} + +#[derive(dto_bindgen::Dto)] +#[dto(as = "string_enum")] +#[dto(ts(name = "DiscountScope"))] +enum DiscountScopeDescriptor { + #[dto(rename = "bin")] + Bin, + #[dto(rename = "order_total")] + OrderTotal, +} + +#[derive(dto_bindgen::Dto)] +#[dto(ts(name = "DiscountThreshold"))] +#[serde(rename_all = "snake_case", tag = "kind", content = "amount")] +enum DiscountThresholdDescriptor { + BinCount { bin_id: String, min: u32 }, + OrderQuantity { min: QuantityDescriptor }, +} + +#[derive(dto_bindgen::Dto)] +#[dto(ts(name = "DiscountValue"))] +#[serde(rename_all = "snake_case", tag = "kind", content = "amount")] +enum DiscountValueDescriptor { + MoneyPerBin(MoneyDescriptor), + Percent(PercentDescriptor), +} + +#[derive(dto_bindgen::Dto)] +#[dto(ts(name = "Discount"))] +struct DiscountDescriptor { + scope: DiscountScopeDescriptor, + threshold: DiscountThresholdDescriptor, + value: DiscountValueDescriptor, +} + +#[cfg(test)] +mod tests { + use dto_bindgen_core::build_registry; + + use super::dto_roots; + + #[test] + fn canonical_core_descriptor_roots_build_without_diagnostics() { + let roots = dto_roots(); + let registry = build_registry(roots.clone()); + + assert!(!registry.has_errors(), "{:?}", registry.diagnostics); + assert_eq!(registry.roots.len(), roots.len()); + } +} diff --git a/crates/core_bindings/src/lib.rs b/crates/core_bindings/src/lib.rs @@ -1,3 +1,6 @@ +mod dto; + +pub use dto::dto_roots; pub use radroots_core as upstream; #[cfg(test)] @@ -7,10 +10,11 @@ mod tests { #[test] fn generated_core_types_are_source_rendered() { - assert!(GENERATED_TYPES_TS.contains("export type RadrootsCoreMoney")); - assert!(GENERATED_TYPES_TS.contains("export type RadrootsCoreQuantityPrice")); - assert!(GENERATED_TYPES_TS.contains("export type RadrootsCoreUnitDimension")); + assert!(GENERATED_TYPES_TS.contains("export type Money")); + assert!(GENERATED_TYPES_TS.contains("export type QuantityPrice")); + assert!(GENERATED_TYPES_TS.contains("export type UnitDimension")); assert!(GENERATED_TYPES_TS.contains("label?: string | null")); assert!(!GENERATED_TYPES_TS.contains("label: string | null")); + assert!(!GENERATED_TYPES_TS.contains("RadrootsCore")); } } diff --git a/crates/event_codec_wasm/src/lib.rs b/crates/event_codec_wasm/src/lib.rs @@ -833,10 +833,7 @@ pub fn group_roles_tags(group_json: &str) -> Result<String, RadrootsJsValue> { #[cfg(test)] mod tests { use super::*; - use radroots_core::{ - RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, - RadrootsCoreQuantityPrice, RadrootsCoreUnit, - }; + use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; use radroots_event::farm::RadrootsFarmRef; use radroots_event::farm_crdt::{ RADROOTS_FARM_CRDT_CHANGE_SCHEMA, RadrootsCrdtBackend, RadrootsFarmCrdtDocumentKind, @@ -887,10 +884,9 @@ mod tests { }; fn sample_listing() -> RadrootsOperationalListing { - let quantity = - RadrootsCoreQuantity::new(RadrootsCoreDecimal::from(1u32), RadrootsCoreUnit::Each); - let price = RadrootsCoreQuantityPrice::new( - RadrootsCoreMoney::new(RadrootsCoreDecimal::from(10u32), RadrootsCoreCurrency::USD), + let quantity = Quantity::new(Decimal::from(1u32), Unit::Each); + let price = QuantityPrice::new( + Money::new(Decimal::from(10u32), Currency::USD), quantity.clone(), ); diff --git a/crates/sdk/examples/runtime_local.rs b/crates/sdk/examples/runtime_local.rs @@ -1,8 +1,5 @@ use radroots_authority::{RadrootsActorContext, RadrootsLocalEventSigner}; -use radroots_core::{ - RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, - RadrootsCoreQuantityPrice, RadrootsCoreUnit, -}; +use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; use radroots_event::contract::RadrootsActorRole; use radroots_event::farm::RadrootsFarmRef; use radroots_event::ids::{RadrootsDTag, RadrootsInventoryBinId}; @@ -91,19 +88,10 @@ fn sample_listing(seller: &str) -> RadrootsOperationalListing { primary_bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), bins: vec![RadrootsOperationalListingBin { bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(1000u32), - RadrootsCoreUnit::MassG, - ), - price_per_canonical_unit: RadrootsCoreQuantityPrice { - amount: RadrootsCoreMoney::new( - RadrootsCoreDecimal::from(20u32), - RadrootsCoreCurrency::USD, - ), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(1u32), - RadrootsCoreUnit::MassG, - ), + quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG), + price_per_canonical_unit: QuantityPrice { + amount: Money::new(Decimal::from(20u32), Currency::USD), + quantity: Quantity::new(Decimal::from(1u32), Unit::MassG), }, display_amount: None, display_unit: None, @@ -114,7 +102,7 @@ fn sample_listing(seller: &str) -> RadrootsOperationalListing { resource_area: None, plot: None, discounts: None, - inventory_available: Some(RadrootsCoreDecimal::from(5u32)), + inventory_available: Some(Decimal::from(5u32)), availability: Some(RadrootsOperationalListingAvailability::Status { status: RadrootsOperationalListingStatus::Active, }), diff --git a/crates/sdk/examples/sdk_v1_listing_prepare.rs b/crates/sdk/examples/sdk_v1_listing_prepare.rs @@ -1,8 +1,5 @@ use radroots_authority::RadrootsActorContext; -use radroots_core::{ - RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, - RadrootsCoreQuantityPrice, RadrootsCoreUnit, -}; +use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; use radroots_event::contract::RadrootsActorRole; use radroots_event::farm::RadrootsFarmRef; use radroots_event::ids::{RadrootsDTag, RadrootsInventoryBinId}; @@ -54,19 +51,10 @@ fn sample_listing(seller: &str) -> RadrootsOperationalListing { primary_bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), bins: vec![RadrootsOperationalListingBin { bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(1000u32), - RadrootsCoreUnit::MassG, - ), - price_per_canonical_unit: RadrootsCoreQuantityPrice { - amount: RadrootsCoreMoney::new( - RadrootsCoreDecimal::from(20u32), - RadrootsCoreCurrency::USD, - ), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(1u32), - RadrootsCoreUnit::MassG, - ), + quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG), + price_per_canonical_unit: QuantityPrice { + amount: Money::new(Decimal::from(20u32), Currency::USD), + quantity: Quantity::new(Decimal::from(1u32), Unit::MassG), }, display_amount: None, display_unit: None, @@ -77,7 +65,7 @@ fn sample_listing(seller: &str) -> RadrootsOperationalListing { resource_area: None, plot: None, discounts: None, - inventory_available: Some(RadrootsCoreDecimal::from(5u32)), + inventory_available: Some(Decimal::from(5u32)), availability: Some(RadrootsOperationalListingAvailability::Status { status: RadrootsOperationalListingStatus::Active, }), diff --git a/crates/sdk/examples/sdk_v1_local_enqueue_and_mock_sync.rs b/crates/sdk/examples/sdk_v1_local_enqueue_and_mock_sync.rs @@ -1,8 +1,5 @@ use radroots_authority::{RadrootsActorContext, RadrootsLocalEventSigner}; -use radroots_core::{ - RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, - RadrootsCoreQuantityPrice, RadrootsCoreUnit, -}; +use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; use radroots_event::contract::RadrootsActorRole; use radroots_event::farm::RadrootsFarmRef; use radroots_event::ids::{RadrootsDTag, RadrootsInventoryBinId}; @@ -88,19 +85,10 @@ fn sample_listing(seller: &str) -> RadrootsOperationalListing { primary_bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), bins: vec![RadrootsOperationalListingBin { bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(1000u32), - RadrootsCoreUnit::MassG, - ), - price_per_canonical_unit: RadrootsCoreQuantityPrice { - amount: RadrootsCoreMoney::new( - RadrootsCoreDecimal::from(20u32), - RadrootsCoreCurrency::USD, - ), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(1u32), - RadrootsCoreUnit::MassG, - ), + quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG), + price_per_canonical_unit: QuantityPrice { + amount: Money::new(Decimal::from(20u32), Currency::USD), + quantity: Quantity::new(Decimal::from(1u32), Unit::MassG), }, display_amount: None, display_unit: None, @@ -111,7 +99,7 @@ fn sample_listing(seller: &str) -> RadrootsOperationalListing { resource_area: None, plot: None, discounts: None, - inventory_available: Some(RadrootsCoreDecimal::from(5u32)), + inventory_available: Some(Decimal::from(5u32)), availability: Some(RadrootsOperationalListingAvailability::Status { status: RadrootsOperationalListingStatus::Active, }), diff --git a/crates/sdk/src/sync_runtime.rs b/crates/sdk/src/sync_runtime.rs @@ -217,11 +217,11 @@ pub struct SyncTransportTargetSummary { impl SyncTransportTargetSummary { fn from_transport_target(target: &RadrootsTransportTarget) -> Self { Self { - transport_kind: target.kind.canonical_label(), - endpoint_uri: target.uri.as_str().to_owned(), - target_scope: target.scope.as_ref().map(|scope| scope.as_str().to_owned()), - target_label: target.label.as_ref().map(|label| label.as_str().to_owned()), - endpoint_fingerprint: target.fingerprint.as_str().to_owned(), + transport_kind: target.kind().canonical_label(), + endpoint_uri: target.uri().as_str().to_owned(), + target_scope: target.scope().map(|scope| scope.as_str().to_owned()), + target_label: target.label().map(|label| label.as_str().to_owned()), + endpoint_fingerprint: target.fingerprint().as_str().to_owned(), } } } diff --git a/crates/sdk/src/transport.rs b/crates/sdk/src/transport.rs @@ -297,8 +297,8 @@ impl TargetSet { pub fn nostr_relay_urls(&self) -> Vec<String> { self.targets .iter() - .filter(|target| target.kind == RadrootsTransportKind::Nostr) - .map(|target| target.uri.as_str().to_owned()) + .filter(|target| target.kind() == &RadrootsTransportKind::Nostr) + .map(|target| target.uri().as_str().to_owned()) .collect() } @@ -329,8 +329,8 @@ impl TargetSet { )); } for target in &targets { - if target.kind == RadrootsTransportKind::Reticulum - && target.uri.as_str() != RADROOTS_RETICULUM_ENDPOINT_URI + if target.kind() == &RadrootsTransportKind::Reticulum + && target.uri().as_str() != RADROOTS_RETICULUM_ENDPOINT_URI { return Err(RadrootsSdkError::InvalidRequest { message: format!( @@ -342,7 +342,7 @@ impl TargetSet { RadrootsTransportTargetSet::new(targets.clone())?; let canonical_targets = targets .iter() - .map(|target| target.fingerprint.to_string()) + .map(|target| target.fingerprint().to_string()) .collect::<BTreeSet<_>>() .into_iter() .collect(); @@ -714,19 +714,33 @@ fn reticulum_transport_status(profile_id: &str, endpoint_uri: &str) -> RadrootsT impl From<RadrootsTransportDeliveryReceipt> for TransportReceipt { fn from(value: RadrootsTransportDeliveryReceipt) -> Self { Self { - request_id: value.request_id, - target_receipts: value.target_receipts, + request_id: value.request_id().to_owned(), + target_set: value.target_set().clone(), + target_receipts: value.target_receipts().to_vec(), } } } #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] pub struct TransportReceipt { - pub request_id: String, - pub target_receipts: Vec<RadrootsTransportTargetReceipt>, + request_id: String, + target_set: RadrootsTransportTargetSet, + target_receipts: Vec<RadrootsTransportTargetReceipt>, } impl TransportReceipt { + pub fn request_id(&self) -> &str { + self.request_id.as_str() + } + + pub fn target_set(&self) -> &RadrootsTransportTargetSet { + &self.target_set + } + + pub fn target_receipts(&self) -> &[RadrootsTransportTargetReceipt] { + self.target_receipts.as_slice() + } + pub fn satisfied_target_count( &self, satisfaction_class: RadrootsTransportSatisfactionClass, @@ -738,11 +752,12 @@ impl TransportReceipt { } pub fn is_satisfied_by(&self, policy: &SatisfactionPolicy) -> Result<bool, RadrootsSdkError> { - Ok(RadrootsTransportDeliveryReceipt { - request_id: self.request_id.clone(), - target_receipts: self.target_receipts.clone(), - } - .is_satisfied_by(&policy.transport_satisfaction_policy()?)?) + let receipt = RadrootsTransportDeliveryReceipt::new( + self.request_id.clone(), + self.target_set.clone(), + self.target_receipts.clone(), + )?; + Ok(receipt.is_satisfied_by(&policy.transport_satisfaction_policy()?)?) } } diff --git a/crates/sdk/src/workflow_runtime.rs b/crates/sdk/src/workflow_runtime.rs @@ -395,7 +395,7 @@ fn reticulum_behavior_for_targets( ) -> RadrootsOutboxReticulumBehavior { if targets .iter() - .any(|target| target.kind == RadrootsTransportKind::Reticulum) + .any(|target| target.kind() == &RadrootsTransportKind::Reticulum) { outbox_reticulum_behavior(transport_profile) } else { @@ -998,11 +998,11 @@ fn runtime_request_digest( .iter() .map(|target| { serde_json::json!({ - "kind": target.kind.canonical_label(), - "uri": target.uri.as_str(), - "scope": target.scope.as_ref().map(|scope| scope.as_str()), - "label": target.label.as_ref().map(|label| label.as_str()), - "fingerprint": target.fingerprint.as_str() + "kind": target.kind().canonical_label(), + "uri": target.uri().as_str(), + "scope": target.scope().map(|scope| scope.as_str()), + "label": target.label().map(|label| label.as_str()), + "fingerprint": target.fingerprint().as_str() }) }) .collect::<Vec<_>>(); diff --git a/crates/sdk/tests/listings_runtime.rs b/crates/sdk/tests/listings_runtime.rs @@ -1,10 +1,7 @@ #![cfg(feature = "runtime")] use radroots_authority::RadrootsActorContext; -use radroots_core::{ - RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, - RadrootsCoreQuantityPrice, RadrootsCoreUnit, -}; +use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; use radroots_event::{ contract::RadrootsActorRole, farm::RadrootsFarmRef, @@ -94,19 +91,10 @@ fn listing(d_tag: &str, title: &str) -> RadrootsOperationalListing { primary_bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), bins: vec![RadrootsOperationalListingBin { bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(1000u32), - RadrootsCoreUnit::MassG, - ), - price_per_canonical_unit: RadrootsCoreQuantityPrice { - amount: RadrootsCoreMoney::new( - RadrootsCoreDecimal::from(20u32), - RadrootsCoreCurrency::USD, - ), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(1u32), - RadrootsCoreUnit::MassG, - ), + quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG), + price_per_canonical_unit: QuantityPrice { + amount: Money::new(Decimal::from(20u32), Currency::USD), + quantity: Quantity::new(Decimal::from(1u32), Unit::MassG), }, display_amount: None, display_unit: None, @@ -117,7 +105,7 @@ fn listing(d_tag: &str, title: &str) -> RadrootsOperationalListing { resource_area: None, plot: None, discounts: None, - inventory_available: Some(RadrootsCoreDecimal::from(5u32)), + inventory_available: Some(Decimal::from(5u32)), availability: Some(RadrootsOperationalListingAvailability::Status { status: RadrootsOperationalListingStatus::Active, }), diff --git a/crates/sdk/tests/runtime_foundation.rs b/crates/sdk/tests/runtime_foundation.rs @@ -45,7 +45,7 @@ async fn sdk_builder_validates_configured_relay_targets() { let sdk = RadrootsClient::builder() .transport_profile( nostr_profile( - [" wss://relay-b.example.com/ ", "wss://relay-a.example.com"], + ["WSS://RELAY-B.EXAMPLE.COM/", "wss://relay-a.example.com"], NostrRelayUrlPolicy::Public, ) .expect("profile"), @@ -620,7 +620,7 @@ fn sdk_error_contract_methods_cover_all_variants() { #[test] fn relay_target_set_validates_normalizes_preserves_order_and_caps() { let targets = TargetSet::nostr_relays( - [" wss://relay-b.example.com/ ", "wss://relay-a.example.com"], + ["WSS://RELAY-B.EXAMPLE.COM/", "wss://relay-a.example.com"], NostrRelayUrlPolicy::Public, ) .expect("targets"); @@ -668,6 +668,11 @@ fn relay_target_set_validates_normalizes_preserves_order_and_caps() { ); assert!(matches!( + TargetSet::nostr_relays([" wss://relay-a.example.com "], NostrRelayUrlPolicy::Public,), + Err(RadrootsSdkError::InvalidRelayUrl { .. }) + )); + + assert!(matches!( TargetSet::nostr_relays( ["wss://relay-a.example.com", "WSS://RELAY-A.EXAMPLE.COM/"], NostrRelayUrlPolicy::Public, diff --git a/crates/sdk/tests/sync_runtime.rs b/crates/sdk/tests/sync_runtime.rs @@ -2,10 +2,7 @@ use futures::future::BoxFuture; use radroots_authority::{RadrootsActorContext, RadrootsEventSigner}; -use radroots_core::{ - RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, - RadrootsCoreQuantityPrice, RadrootsCoreUnit, -}; +use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; use radroots_event::{ contract::RadrootsActorRole, farm::RadrootsFarmRef, @@ -359,21 +356,21 @@ impl RadrootsRelayPublishAdapter for RecordingPublishAdapter { self.raw_events .lock() .expect("raw event lock") - .push(request.signed_event.raw_json().to_owned()); + .push(request.signed_event().raw_json().to_owned()); self.request_times_ms .lock() .expect("request time lock") - .push(request.now_ms); + .push(request.now_ms()); self.idempotency_keys .lock() .expect("idempotency key lock") - .push(request.idempotency_key.clone()); + .push(request.idempotency_key().map(str::to_owned)); self.relay_batches .lock() .expect("relay batch lock") - .push(request.targets.relay_strings()); + .push(request.targets().relay_strings()); Ok(request - .targets + .targets() .relays() .iter() .map(|relay| { @@ -413,19 +410,10 @@ fn listing(d_tag: &str, title: &str) -> RadrootsOperationalListing { primary_bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), bins: vec![RadrootsOperationalListingBin { bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(1000u32), - RadrootsCoreUnit::MassG, - ), - price_per_canonical_unit: RadrootsCoreQuantityPrice { - amount: RadrootsCoreMoney::new( - RadrootsCoreDecimal::from(20u32), - RadrootsCoreCurrency::USD, - ), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(1u32), - RadrootsCoreUnit::MassG, - ), + quantity: Quantity::new(Decimal::from(1000u32), Unit::MassG), + price_per_canonical_unit: QuantityPrice { + amount: Money::new(Decimal::from(20u32), Currency::USD), + quantity: Quantity::new(Decimal::from(1u32), Unit::MassG), }, display_amount: None, display_unit: None, @@ -436,7 +424,7 @@ fn listing(d_tag: &str, title: &str) -> RadrootsOperationalListing { resource_area: None, plot: None, discounts: None, - inventory_available: Some(RadrootsCoreDecimal::from(5u32)), + inventory_available: Some(Decimal::from(5u32)), availability: Some(RadrootsOperationalListingAvailability::Status { status: RadrootsOperationalListingStatus::Active, }), diff --git a/crates/sdk/tests/unit/listings_runtime_tests.rs b/crates/sdk/tests/unit/listings_runtime_tests.rs @@ -1,9 +1,6 @@ use super::*; use crate::{RadrootsSdkLocalKeySigner, RadrootsSdkSignerProvider}; -use radroots_core::{ - RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, - RadrootsCoreQuantityPrice, RadrootsCoreUnit, -}; +use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit}; use radroots_event::{ contract::RadrootsActorRole, farm::RadrootsFarmRef, @@ -60,19 +57,10 @@ fn listing_for_seller(seller: &str, d_tag: &str, title: &str) -> RadrootsOperati primary_bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), bins: vec![RadrootsOperationalListingBin { bin_id: RadrootsInventoryBinId::parse("bin-1").expect("bin id"), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(12u32), - RadrootsCoreUnit::Each, - ), - price_per_canonical_unit: RadrootsCoreQuantityPrice { - amount: RadrootsCoreMoney::new( - RadrootsCoreDecimal::from(4u32), - RadrootsCoreCurrency::USD, - ), - quantity: RadrootsCoreQuantity::new( - RadrootsCoreDecimal::from(1u32), - RadrootsCoreUnit::Each, - ), + quantity: Quantity::new(Decimal::from(12u32), Unit::Each), + price_per_canonical_unit: QuantityPrice { + amount: Money::new(Decimal::from(4u32), Currency::USD), + quantity: Quantity::new(Decimal::from(1u32), Unit::Each), }, display_amount: None, display_unit: None, @@ -83,7 +71,7 @@ fn listing_for_seller(seller: &str, d_tag: &str, title: &str) -> RadrootsOperati resource_area: None, plot: None, discounts: None, - inventory_available: Some(RadrootsCoreDecimal::from(12u32)), + inventory_available: Some(Decimal::from(12u32)), availability: Some(RadrootsOperationalListingAvailability::Status { status: RadrootsOperationalListingStatus::Active, }), diff --git a/crates/sdk/tests/unit/runtime_tests.rs b/crates/sdk/tests/unit/runtime_tests.rs @@ -774,7 +774,7 @@ fn sqlite_wal_checkpoint_receipt_mapping_covers_edge_states() { } #[tokio::test] -async fn read_only_event_store_status_enforces_valid_stream_invariants() { +async fn read_only_event_store_status_respects_immutable_stream_invariants() { let sdk = RadrootsClient::builder().build().await.expect("sdk"); let pool = sdk._event_store.pool(); let valid_event_id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; @@ -805,48 +805,49 @@ async fn read_only_event_store_status_enforces_valid_stream_invariants() { assert_eq!(summary.total_events, 2); assert_eq!(summary.valid_stream_events, 1); - sqlx::query( + let legacy_mutation = sqlx::query( "UPDATE event_envelopes SET contract_status = 'supported', contract_id = 'radroots.legacy.supported.v0', projection_eligible = 1 WHERE event_id = ?", ) .bind(unsupported_event_id) .execute(pool) .await - .expect("legacy status"); - let legacy_summary = event_store_status_summary_from_pool(pool) - .await - .expect("legacy status summary"); - assert_eq!(legacy_summary.total_events, 2); - assert_eq!(legacy_summary.valid_stream_events, 1); - sqlx::query( - "UPDATE event_envelopes SET contract_status = 'unsupported', contract_id = NULL, projection_eligible = 0 WHERE event_id = ?", - ) - .bind(unsupported_event_id) - .execute(pool) - .await - .expect("restore admission status"); + .expect_err("derived admission classification must be immutable"); + assert!( + legacy_mutation + .to_string() + .contains("immutable after reconciliation") + ); - sqlx::query( + let verification_mutation = sqlx::query( "UPDATE event_envelopes SET verification_status = 'signature_invalid' WHERE event_id = ?", ) .bind(valid_event_id) .execute(pool) .await - .expect("invalid verification"); - assert_event_store_error(event_store_status_summary_from_pool(pool).await); - sqlx::query("UPDATE event_envelopes SET verification_status = 'verified' WHERE event_id = ?") - .bind(valid_event_id) - .execute(pool) - .await - .expect("restore verification"); + .expect_err("derived verification classification must be immutable"); + assert!( + verification_mutation + .to_string() + .contains("immutable after reconciliation") + ); - sqlx::query( + let class_mutation = sqlx::query( "UPDATE event_envelopes SET kind = 20000, event_class = 'ephemeral', projection_eligible = 0 WHERE event_id = ?", ) .bind(valid_event_id) .execute(pool) .await - .expect("ephemeral stored row"); - assert_event_store_error(event_store_status_summary_from_pool(pool).await); + .expect_err("raw and derived event classification must be immutable"); + assert!( + class_mutation + .to_string() + .contains("immutable after reconciliation") + ); + + let unchanged = event_store_status_summary_from_pool(pool) + .await + .expect("unchanged valid stream summary"); + assert_eq!(unchanged, summary); } #[tokio::test] diff --git a/crates/sdk/tests/unit/sync_runtime_tests.rs b/crates/sdk/tests/unit/sync_runtime_tests.rs @@ -309,11 +309,11 @@ fn delivery_target_record( RadrootsOutboxDeliveryTargetRecord { delivery_target_id, delivery_plan_id, - transport_kind: target.kind.clone(), - endpoint_uri: target.uri.clone(), - target_scope: target.scope.clone(), - target_label: target.label.clone(), - endpoint_fingerprint: target.fingerprint.clone(), + transport_kind: target.kind().clone(), + endpoint_uri: target.uri().clone(), + target_scope: target.scope().cloned(), + target_label: target.label().cloned(), + endpoint_fingerprint: target.fingerprint().clone(), status: RadrootsOutboxDeliveryTargetStatus::Pending, last_outcome_kind: None, attempt_count: 0, @@ -869,8 +869,8 @@ async fn radrootsd_push_empty_queue_and_private_helpers_are_deterministic() { let policy = RadrootsTransportSatisfactionPolicy::required_targets( RadrootsTransportSatisfactionClass::Accepted, vec![ - first_required.fingerprint.clone(), - second_required.fingerprint.clone(), + first_required.fingerprint().clone(), + second_required.fingerprint().clone(), ], ) .expect("required target policy"); @@ -883,7 +883,7 @@ async fn radrootsd_push_empty_queue_and_private_helpers_are_deterministic() { let remaining = radrootsd_required_remaining_targets(&policy, &active_targets) .expect("required remaining targets") .expect("required target policy"); - assert_eq!(remaining, vec![second_required.fingerprint]); + assert_eq!(remaining, vec![second_required.fingerprint().clone()]); assert_eq!( radrootsd_delivery_policy_from_remaining(2, remaining.len(), Some(&remaining), &policy) .expect("required target radrootsd policy"), @@ -997,11 +997,11 @@ fn radrootsd_outbox_target_conversion_rejects_reticulum_targets_before_behavior_ let record = RadrootsOutboxDeliveryTargetRecord { delivery_target_id: 1, delivery_plan_id: 1, - transport_kind: target.kind.clone(), - endpoint_uri: target.uri.clone(), - target_scope: target.scope.clone(), - target_label: target.label.clone(), - endpoint_fingerprint: target.fingerprint.clone(), + transport_kind: target.kind().clone(), + endpoint_uri: target.uri().clone(), + target_scope: target.scope().cloned(), + target_label: target.label().cloned(), + endpoint_fingerprint: target.fingerprint().clone(), status: RadrootsOutboxDeliveryTargetStatus::Pending, last_outcome_kind: None, attempt_count: 0, @@ -1174,9 +1174,9 @@ async fn radrootsd_local_validation_errors_release_claim_before_daemon_publish() assert!(!stored_before.event_store_ingested); assert_eq!(stored_before.event_store_ingested_at_ms, None); let reticulum_target = RadrootsTransportTarget::reticulum().expect("Reticulum target"); - claimed.delivery_targets[0].transport_kind = reticulum_target.kind; - claimed.delivery_targets[0].endpoint_uri = reticulum_target.uri; - claimed.delivery_targets[0].endpoint_fingerprint = reticulum_target.fingerprint; + claimed.delivery_targets[0].transport_kind = reticulum_target.kind().clone(); + claimed.delivery_targets[0].endpoint_uri = reticulum_target.uri().clone(); + claimed.delivery_targets[0].endpoint_fingerprint = reticulum_target.fingerprint().clone(); let sync = sdk.sync(); let adapter = RadrootsdPublishAdapter::new( RadrootsdPublishConfig::new(endpoint).with_timeout(Duration::from_millis(50)), @@ -1304,9 +1304,9 @@ async fn radrootsd_local_validation_failure_keeps_sibling_plan_ready_and_claimab let ingested_before = stored_before.event_store_ingested; let ingested_at_before = stored_before.event_store_ingested_at_ms; let reticulum_target = RadrootsTransportTarget::reticulum().expect("Reticulum target"); - claimed.delivery_targets[0].transport_kind = reticulum_target.kind; - claimed.delivery_targets[0].endpoint_uri = reticulum_target.uri; - claimed.delivery_targets[0].endpoint_fingerprint = reticulum_target.fingerprint; + claimed.delivery_targets[0].transport_kind = reticulum_target.kind().clone(); + claimed.delivery_targets[0].endpoint_uri = reticulum_target.uri().clone(); + claimed.delivery_targets[0].endpoint_fingerprint = reticulum_target.fingerprint().clone(); let sync = sdk.sync(); let adapter = RadrootsdPublishAdapter::new( RadrootsdPublishConfig::new(endpoint).with_timeout(Duration::from_millis(50)), @@ -1723,7 +1723,8 @@ impl OutboxPublishReceiptFixture for RadrootsOutboxPublishReceipt { Some(RadrootsTransportTargetLabel::parse("Farm relay").expect("label")), ) .expect("target") - .fingerprint, + .fingerprint() + .clone(), target_scope: Some("farm.local".to_owned()), target_label: Some("Farm relay".to_owned()), attempted: true, diff --git a/crates/sdk/tests/unit/transport_tests.rs b/crates/sdk/tests/unit/transport_tests.rs @@ -6,7 +6,7 @@ use super::{ use crate::{RadrootsSdkError, SDK_TRANSPORT_TARGET_MAX_COUNT}; use radroots_transport::{ RADROOTS_RETICULUM_ENDPOINT_URI, RadrootsTransportError, RadrootsTransportKind, - RadrootsTransportTarget, RadrootsTransportTargetFingerprint, RadrootsTransportTargetUri, + RadrootsTransportTarget, }; use crate::serializer_failure::assert_struct_serialize_error_paths; @@ -311,23 +311,6 @@ fn explicit_target_sets_reject_noncanonical_reticulum_endpoints() { RadrootsTransportError::InvalidTargetUri ); } - - let uri = RadrootsTransportTargetUri::parse("reticulum:local-alt").expect("target uri"); - let fingerprint = RadrootsTransportTargetFingerprint::from_target( - &RadrootsTransportKind::Reticulum, - &uri, - None, - ); - let err = TargetSet::transport_targets(vec![RadrootsTransportTarget { - kind: RadrootsTransportKind::Reticulum, - uri, - scope: None, - label: None, - fingerprint, - }]) - .expect_err("noncanonical Reticulum endpoint"); - - assert!(matches!(err, RadrootsSdkError::InvalidRequest { .. })); } #[test] diff --git a/crates/sdk/tests/unit/workflow_runtime_tests.rs b/crates/sdk/tests/unit/workflow_runtime_tests.rs @@ -226,7 +226,7 @@ fn workflow_digest_and_event_helpers_cover_error_and_input_paths() { ); assert_eq!(input.operation_kind, "workflow.test.v1"); assert_eq!( - input.delivery_plan.targets[0].uri.as_str(), + input.delivery_plan.targets[0].uri().as_str(), "wss://relay.example.com" ); assert!(input.event_store_inserted); diff --git a/crates/trade_bindings/src/dto.rs b/crates/trade_bindings/src/dto.rs @@ -45,12 +45,12 @@ macro_rules! imported_ts_type { }; } -imported_ts_type!(RadrootsCoreDecimalImport, "RadrootsCoreDecimal"); -imported_ts_type!(RadrootsCoreDiscountImport, "RadrootsCoreDiscount"); -imported_ts_type!(RadrootsCoreMoneyImport, "RadrootsCoreMoney"); -imported_ts_type!(RadrootsCoreQuantityImport, "RadrootsCoreQuantity"); -imported_ts_type!(RadrootsCoreQuantityPriceImport, "RadrootsCoreQuantityPrice"); -imported_ts_type!(RadrootsCoreUnitImport, "RadrootsCoreUnit"); +imported_ts_type!(DecimalImport, "Decimal"); +imported_ts_type!(DiscountImport, "Discount"); +imported_ts_type!(MoneyImport, "Money"); +imported_ts_type!(QuantityImport, "Quantity"); +imported_ts_type!(QuantityPriceImport, "QuantityPrice"); +imported_ts_type!(UnitImport, "Unit"); imported_ts_type!(RadrootsFarmRefImport, "RadrootsFarmRef"); imported_ts_type!( RadrootsOperationalListingAvailabilityImport, @@ -191,8 +191,8 @@ pub struct RadrootsTradeListingProjection { pub bins: Vec<RadrootsTradeListingBinProjection>, pub resource_area: Option<RadrootsResourceAreaRefImport>, pub plot: Option<RadrootsPlotRefImport>, - pub discounts: Option<Vec<RadrootsCoreDiscountImport>>, - pub inventory_available: Option<RadrootsCoreDecimalImport>, + pub discounts: Option<Vec<DiscountImport>>, + pub inventory_available: Option<DecimalImport>, pub availability: Option<RadrootsOperationalListingAvailabilityImport>, pub delivery_method: Option<RadrootsOperationalListingDeliveryMethodImport>, pub location: Option<RadrootsOperationalListingPublicLocationImport>, @@ -248,7 +248,7 @@ pub struct RadrootsTradeMarketplaceListingSummary { pub product_summary: Option<String>, pub listing_status: RadrootsTradeListingMarketStatus, pub location_primary: Option<String>, - pub inventory_available: Option<RadrootsCoreDecimalImport>, + pub inventory_available: Option<DecimalImport>, pub primary_bin_id: String, pub primary_bin_label: Option<String>, pub primary_bin_total: RadrootsOperationalListingTotal, diff --git a/packages/core-bindings/src/generated/types.ts b/packages/core-bindings/src/generated/types.ts @@ -1,25 +1,25 @@ // @generated by cargo xtask generate ts // Do not edit by hand. -export type RadrootsCoreCurrency = string; +export type Currency = string; -export type RadrootsCoreDecimal = string; +export type Decimal = string; -export type RadrootsCoreDiscount = { scope: RadrootsCoreDiscountScope, threshold: RadrootsCoreDiscountThreshold, value: RadrootsCoreDiscountValue, }; +export type Discount = { scope: DiscountScope, threshold: DiscountThreshold, value: DiscountValue, }; -export type RadrootsCoreDiscountScope = "bin" | "order_total"; +export type DiscountScope = "bin" | "order_total"; -export type RadrootsCoreDiscountThreshold = { kind: "bin_count", amount: { bin_id: string, min: number, }, } | { kind: "order_quantity", amount: { min: RadrootsCoreQuantity, }, }; +export type DiscountThreshold = { kind: "bin_count", amount: { bin_id: string, min: number, }, } | { kind: "order_quantity", amount: { min: Quantity, }, }; -export type RadrootsCoreDiscountValue = { kind: "money_per_bin", amount: RadrootsCoreMoney, } | { kind: "percent", amount: RadrootsCorePercent, }; +export type DiscountValue = { kind: "money_per_bin", amount: Money, } | { kind: "percent", amount: Percent, }; -export type RadrootsCoreMoney = { amount: string, currency: string, }; +export type Money = { amount: string, currency: string, }; -export type RadrootsCorePercent = { value: string, }; +export type Percent = { value: string, }; -export type RadrootsCoreQuantity = { amount: string, unit: RadrootsCoreUnit, label?: string | null, }; +export type Quantity = { amount: string, unit: Unit, label?: string | null, }; -export type RadrootsCoreQuantityPrice = { amount: RadrootsCoreMoney, quantity: RadrootsCoreQuantity, }; +export type QuantityPrice = { amount: Money, quantity: Quantity, }; -export type RadrootsCoreUnit = "each" | "kg" | "g" | "oz" | "lb" | "l" | "ml"; +export type Unit = "each" | "kg" | "g" | "oz" | "lb" | "l" | "ml"; -export type RadrootsCoreUnitDimension = "count" | "mass" | "volume"; +export type UnitDimension = "count" | "mass" | "volume"; diff --git a/packages/event-bindings/src/generated/types.ts b/packages/event-bindings/src/generated/types.ts @@ -1,6 +1,6 @@ // @generated by cargo xtask generate ts // Do not edit by hand. -import type { RadrootsCoreDiscount, RadrootsCoreMoney, RadrootsCoreQuantity, RadrootsCoreQuantityPrice, RadrootsCoreUnit } from "@radroots/core-bindings"; +import type { Decimal, Discount, Money, Quantity, QuantityPrice, Unit } from "@radroots/core-bindings"; export type JobFeedbackStatus = "payment_required" | "processing" | "error" | "success" | "partial"; @@ -116,11 +116,11 @@ export type RadrootsMessageRecipient = { public_key: string, relay_url?: string export type RadrootsNip01EventWireDto = { id: string, pubkey: string, created_at: number, kind: number, tags: Array<Array<string>>, content: string, sig: string, extra: { [key: string]: unknown }, }; -export type RadrootsOperationalListing = { d_tag: string, published_at?: string | null, farm?: RadrootsFarmRef, product: RadrootsOperationalListingProduct, primary_bin_id: string, bins: Array<RadrootsOperationalListingBin>, resource_area?: RadrootsResourceAreaRef | null, plot?: RadrootsPlotRef | null, discounts?: Array<RadrootsCoreDiscount> | null, inventory_available?: string | null, availability?: RadrootsOperationalListingAvailability | null, delivery_method?: RadrootsOperationalListingDeliveryMethod | null, location?: RadrootsOperationalListingPublicLocation | null, images?: Array<RadrootsOperationalListingImage> | null, }; +export type RadrootsOperationalListing = { d_tag: string, published_at?: string | null, farm?: RadrootsFarmRef, product: RadrootsOperationalListingProduct, primary_bin_id: string, bins: Array<RadrootsOperationalListingBin>, resource_area?: RadrootsResourceAreaRef | null, plot?: RadrootsPlotRef | null, discounts?: Array<Discount> | null, inventory_available?: Decimal | null, availability?: RadrootsOperationalListingAvailability | null, delivery_method?: RadrootsOperationalListingDeliveryMethod | null, location?: RadrootsOperationalListingPublicLocation | null, images?: Array<RadrootsOperationalListingImage> | null, }; export type RadrootsOperationalListingAvailability = { kind: "window", amount: { start?: string | null, end?: string | null, }, } | { kind: "status", amount: { status: RadrootsOperationalListingStatus, }, }; -export type RadrootsOperationalListingBin = { bin_id: string, quantity: RadrootsCoreQuantity, price_per_canonical_unit: RadrootsCoreQuantityPrice, display_amount?: string | null, display_unit?: RadrootsCoreUnit | null, display_label?: string | null, display_price?: RadrootsCoreMoney | null, display_price_unit?: RadrootsCoreUnit | null, }; +export type RadrootsOperationalListingBin = { bin_id: string, quantity: Quantity, price_per_canonical_unit: QuantityPrice, display_amount?: Decimal | null, display_unit?: Unit | null, display_label?: string | null, display_price?: Money | null, display_price_unit?: Unit | null, }; export type RadrootsOperationalListingDeliveryMethod = { kind: "pickup", } | { kind: "local_delivery", } | { kind: "shipping", } | { kind: "other", amount: { method: string, }, }; @@ -160,7 +160,7 @@ export type RadrootsResourceAreaLocation = { primary?: string | null, city?: str export type RadrootsResourceAreaRef = { pubkey: string, d_tag: string, }; -export type RadrootsResourceHarvestCap = { d_tag: string, resource_area: RadrootsResourceAreaRef, product: RadrootsResourceHarvestProduct, start: string, end: string, cap_quantity: RadrootsCoreQuantity, display_amount?: string | null, display_unit?: RadrootsCoreUnit | null, display_label?: string | null, tags?: Array<string> | null, }; +export type RadrootsResourceHarvestCap = { d_tag: string, resource_area: RadrootsResourceAreaRef, product: RadrootsResourceHarvestProduct, start: string, end: string, cap_quantity: Quantity, display_amount?: Decimal | null, display_unit?: Unit | null, display_label?: string | null, tags?: Array<string> | null, }; export type RadrootsResourceHarvestProduct = { key: string, category?: string | null, }; diff --git a/packages/trade-bindings/src/generated/types.ts b/packages/trade-bindings/src/generated/types.ts @@ -1,13 +1,13 @@ // @generated by cargo xtask generate ts // Do not edit by hand. -import type { RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreDiscount, RadrootsCoreMoney, RadrootsCoreQuantity, RadrootsCoreUnit } from "@radroots/core-bindings"; +import type { Currency, Decimal, Discount, Money, Quantity, Unit } from "@radroots/core-bindings"; import type { RadrootsFarmRef, RadrootsOperationalListing, RadrootsOperationalListingAvailability, RadrootsOperationalListingBin, RadrootsOperationalListingDeliveryMethod, RadrootsOperationalListingImage, RadrootsOperationalListingProduct, RadrootsOperationalListingPublicLocation, RadrootsPlotRef, RadrootsResourceAreaRef } from "@radroots/event-bindings"; -export type RadrootsOperationalListingSubtotal = { price_amount: RadrootsCoreMoney, price_currency: RadrootsCoreCurrency, quantity_amount: RadrootsCoreDecimal, quantity_unit: RadrootsCoreUnit, }; +export type RadrootsOperationalListingSubtotal = { price_amount: Money, price_currency: Currency, quantity_amount: Decimal, quantity_unit: Unit, }; -export type RadrootsOperationalListingTotal = { price_amount: RadrootsCoreMoney, price_currency: RadrootsCoreCurrency, quantity_amount: RadrootsCoreDecimal, quantity_unit: RadrootsCoreUnit, }; +export type RadrootsOperationalListingTotal = { price_amount: Money, price_currency: Currency, quantity_amount: Decimal, quantity_unit: Unit, }; -export type RadrootsOperationalListingTradeProjection = { listing_id: string, listing_addr: string, seller_pubkey: string, title: string, description: string, product_type: string, primary_bin_id: string, bin_quantity: RadrootsCoreQuantity, unit: RadrootsCoreUnit, unit_price: RadrootsCoreMoney, inventory_available: RadrootsCoreDecimal, availability: RadrootsOperationalListingAvailability, location: RadrootsOperationalListingPublicLocation, delivery_method: RadrootsOperationalListingDeliveryMethod, listing: RadrootsOperationalListing, }; +export type RadrootsOperationalListingTradeProjection = { listing_id: string, listing_addr: string, seller_pubkey: string, title: string, description: string, product_type: string, primary_bin_id: string, bin_quantity: Quantity, unit: Unit, unit_price: Money, inventory_available: Decimal, availability: RadrootsOperationalListingAvailability, location: RadrootsOperationalListingPublicLocation, delivery_method: RadrootsOperationalListingDeliveryMethod, listing: RadrootsOperationalListing, }; export type RadrootsTradeAgreementClaimV1 = { claim_mutation_id: string, proposal_mutation_id: string, candidate_id: string, candidate_author_pubkey: string, accepted_by_pubkey: string, reservation_commitment: string, }; @@ -39,7 +39,7 @@ export type RadrootsTradeListingFacets = { seller_pubkeys: Array<RadrootsTradeFa export type RadrootsTradeListingMarketStatus = "unknown" | "window" | "active" | "sold" | { other: { value: string, }, }; -export type RadrootsTradeListingProjection = { listing_addr: string, seller_pubkey: string, listing_id: string, farm: RadrootsFarmRef, product: RadrootsOperationalListingProduct, primary_bin_id: string, bins: Array<RadrootsTradeListingBinProjection>, resource_area?: RadrootsResourceAreaRef | null, plot?: RadrootsPlotRef | null, discounts?: Array<RadrootsCoreDiscount> | null, inventory_available?: RadrootsCoreDecimal | null, availability?: RadrootsOperationalListingAvailability | null, delivery_method?: RadrootsOperationalListingDeliveryMethod | null, location?: RadrootsOperationalListingPublicLocation | null, images?: Array<RadrootsOperationalListingImage> | null, trade_count: number, open_trade_count: number, terminal_trade_count: number, }; +export type RadrootsTradeListingProjection = { listing_addr: string, seller_pubkey: string, listing_id: string, farm: RadrootsFarmRef, product: RadrootsOperationalListingProduct, primary_bin_id: string, bins: Array<RadrootsTradeListingBinProjection>, resource_area?: RadrootsResourceAreaRef | null, plot?: RadrootsPlotRef | null, discounts?: Array<Discount> | null, inventory_available?: Decimal | null, availability?: RadrootsOperationalListingAvailability | null, delivery_method?: RadrootsOperationalListingDeliveryMethod | null, location?: RadrootsOperationalListingPublicLocation | null, images?: Array<RadrootsOperationalListingImage> | null, trade_count: number, open_trade_count: number, terminal_trade_count: number, }; export type RadrootsTradeListingQuery = { seller_pubkey?: string | null, farm_pubkey?: string | null, farm_id?: string | null, product_key?: string | null, product_category?: string | null, listing_status?: RadrootsTradeListingMarketStatus | null, }; @@ -47,7 +47,7 @@ export type RadrootsTradeListingSort = { field: RadrootsTradeListingSortField, d export type RadrootsTradeListingSortField = "listing_addr" | "product_title" | "product_category" | "seller_pubkey" | "inventory_available" | "open_trade_count" | "total_trade_count"; -export type RadrootsTradeMarketplaceListingSummary = { listing_addr: string, seller_pubkey: string, farm_pubkey: string, farm_id: string, product_key: string, product_title: string, product_category: string, product_summary?: string | null, listing_status: RadrootsTradeListingMarketStatus, location_primary?: string | null, inventory_available?: RadrootsCoreDecimal | null, primary_bin_id: string, primary_bin_label?: string | null, primary_bin_total: RadrootsOperationalListingTotal, trade_count: number, open_trade_count: number, terminal_trade_count: number, }; +export type RadrootsTradeMarketplaceListingSummary = { listing_addr: string, seller_pubkey: string, farm_pubkey: string, farm_id: string, product_key: string, product_title: string, product_category: string, product_summary?: string | null, listing_status: RadrootsTradeListingMarketStatus, location_primary?: string | null, inventory_available?: Decimal | null, primary_bin_id: string, primary_bin_label?: string | null, primary_bin_total: RadrootsOperationalListingTotal, trade_count: number, open_trade_count: number, terminal_trade_count: number, }; export type RadrootsTradeModerationFlag = { code: string, severity: RadrootsTradeModerationSeverity, status: RadrootsTradeModerationStatus, source?: string | null, reason?: string | null, }; diff --git a/tools/xtask/Cargo.toml b/tools/xtask/Cargo.toml @@ -16,7 +16,7 @@ path = "src/main.rs" [dependencies] dto_bindgen_backend_ts = { workspace = true } dto_bindgen_core = { workspace = true } -radroots_core = { workspace = true, features = ["dto-bindgen"] } +radroots_core_bindings = { path = "../../crates/core_bindings" } radroots_event = { workspace = true, features = ["dto-bindgen"] } radroots_event_bindings = { path = "../../crates/event_bindings" } radroots_event_index = { workspace = true, features = ["dto-bindgen"] } diff --git a/tools/xtask/src/dto_roots.rs b/tools/xtask/src/dto_roots.rs @@ -104,8 +104,8 @@ pub const MANUAL_DESCRIPTOR_FAMILIES: &[ManualDescriptorFamily] = &[ pub const SDK_LOCAL_WRAPPER_ALLOWANCES: &[SdkLocalWrapperAllowance] = &[ SdkLocalWrapperAllowance { package_key: "core", - shape_family: "RadrootsCoreCurrency and RadrootsCoreDecimal package aliases", - reason: "source descriptors correctly describe fields as strings, while package roots still need stable named TypeScript aliases", + shape_family: "Currency and Decimal package aliases", + reason: "string-backed source descriptors require canonical named TypeScript package aliases", }, SdkLocalWrapperAllowance { package_key: "replica_schema", @@ -124,15 +124,30 @@ pub const SDK_LOCAL_WRAPPER_ALLOWANCES: &[SdkLocalWrapperAllowance] = &[ }, ]; +const EVENT_CORE_EXTERNAL_OVERRIDES: &[DtoExternalOverride] = &[ + core_override("Currency"), + core_override("Decimal"), + core_override("Discount"), + core_override("DiscountScope"), + core_override("DiscountThreshold"), + core_override("DiscountValue"), + core_override("Money"), + core_override("Percent"), + core_override("Quantity"), + core_override("QuantityPrice"), + core_override("Unit"), + core_override("UnitDimension"), +]; + const TRADE_EXTERNAL_OVERRIDES: &[DtoExternalOverride] = &[ - core_override("RadrootsCoreCurrency"), - core_override("RadrootsCoreDecimal"), - core_override("RadrootsCoreDiscount"), - core_override("RadrootsCoreDiscountValue"), - core_override("RadrootsCoreMoney"), - core_override("RadrootsCoreQuantity"), - core_override("RadrootsCoreQuantityPrice"), - core_override("RadrootsCoreUnit"), + core_override("Currency"), + core_override("Decimal"), + core_override("Discount"), + core_override("DiscountValue"), + core_override("Money"), + core_override("Quantity"), + core_override("QuantityPrice"), + core_override("Unit"), event_override("RadrootsFarmRef"), event_override("RadrootsOperationalListing"), event_override("RadrootsOperationalListingAvailability"), @@ -180,8 +195,8 @@ pub fn core_types_module() -> Result<DtoTypesModule, String> { Ok(with_type_aliases_sorted( rendered, [ - type_alias("RadrootsCoreCurrency", TypeScriptType::String), - type_alias("RadrootsCoreDecimal", TypeScriptType::String), + type_alias("Currency", TypeScriptType::String), + type_alias("Decimal", TypeScriptType::String), ], )) } @@ -189,8 +204,9 @@ pub fn core_types_module() -> Result<DtoTypesModule, String> { pub fn event_types_module() -> Result<DtoTypesModule, String> { let root_set = package_root_set("event").ok_or_else(|| "missing event DTO roots".to_owned())?; let registry = root_set.registry(); - let options = core_import_options(®istry, DtoRegistryRenderOptions::default())?; + let options = core_import_options(DtoRegistryRenderOptions::default())?; let rendered = render_registry_types(®istry, &options)?; + let rendered = with_additional_type_imports(rendered, CORE_BINDINGS_PACKAGE_NAME, ["Discount"]); Ok(with_event_sdk_wrappers(rendered)) } @@ -219,7 +235,7 @@ pub fn trade_types_module() -> Result<DtoTypesModule, String> { } fn core_roots() -> Vec<RootDescriptor> { - radroots_core::dto::dto_roots().into_iter().collect() + radroots_core_bindings::dto_roots() } fn event_roots() -> Vec<RootDescriptor> { @@ -247,40 +263,23 @@ fn retired_order_event_dto_root(root: &RootDescriptor) -> bool { } fn core_import_options( - registry: &Registry, mut options: DtoRegistryRenderOptions, ) -> Result<DtoRegistryRenderOptions, String> { - for export_name in [ - "RadrootsCoreDiscount", - "RadrootsCoreDiscountScope", - "RadrootsCoreDiscountThreshold", - "RadrootsCoreDiscountValue", - "RadrootsCoreMoney", - "RadrootsCorePercent", - "RadrootsCoreQuantity", - "RadrootsCoreQuantityPrice", - "RadrootsCoreUnit", - ] { - options = - with_checked_external_type(registry, options, export_name, CORE_BINDINGS_PACKAGE_NAME)?; + let package_exports = BTreeMap::from([( + CORE_BINDINGS_PACKAGE_KEY, + type_exports(core_types_module()?.body_ts()), + )]); + for override_target in EVENT_CORE_EXTERNAL_OVERRIDES { + validate_external_override_target(*override_target, &package_exports)?; + options = options.with_external_override( + override_target.target_type, + override_target.import_name, + override_target.from, + ); } Ok(options) } -fn with_checked_external_type( - registry: &Registry, - options: DtoRegistryRenderOptions, - export_name: &str, - from: &str, -) -> Result<DtoRegistryRenderOptions, String> { - let type_id = registry - .type_id_by_export_name(export_name) - .map_err(|error| { - format!("failed to resolve external DTO type `{export_name}` from `{from}`: {error}") - })?; - Ok(options.with_external_type(type_id, export_name, from)) -} - fn trade_import_options( registry: &Registry, mut options: DtoRegistryRenderOptions, @@ -402,6 +401,34 @@ fn imported_type_inventory(imports_ts: &str) -> BTreeMap<String, BTreeSet<String imports } +fn with_additional_type_imports( + module: DtoTypesModule, + from: &str, + names: impl IntoIterator<Item = &'static str>, +) -> DtoTypesModule { + let mut imports = imported_type_inventory(module.imports_ts().unwrap_or_default()); + imports + .entry(from.to_owned()) + .or_default() + .extend(names.into_iter().map(str::to_owned)); + let imports_ts = imports + .into_iter() + .map(|(from, names)| { + format!( + "import type {{ {} }} from \"{from}\";", + names.into_iter().collect::<Vec<_>>().join(", ") + ) + }) + .collect::<Vec<_>>() + .join("\n"); + let imports_ts = if imports_ts.is_empty() { + imports_ts + } else { + format!("{imports_ts}\n\n") + }; + DtoTypesModule::new(imports_ts, module.body_ts()) +} + fn insert_import_names(imports: &mut BTreeMap<String, BTreeSet<String>>, from: &str, names: &str) { for name in names.split(',') { insert_import(imports, from, name); @@ -537,17 +564,19 @@ fn type_alias(name: impl Into<String>, type_expr: TypeScriptType) -> String { mod tests { use std::collections::{BTreeMap, BTreeSet}; - use dto_bindgen_core::{Registry, RustTypeId, SourceSpan, StructDef, TypeDef}; + use dto_bindgen_core::SourceSpan; use super::{ CORE_BINDINGS_PACKAGE_KEY, CORE_BINDINGS_PACKAGE_NAME, DTO_PACKAGE_ROOTS, DtoExternalOverride, MANUAL_DESCRIPTOR_FAMILIES, SDK_LOCAL_WRAPPER_ALLOWANCES, event_override, imported_type_inventory, package_root_set, trade_import_options, type_exports, validate_external_override_target, validate_external_override_usage, - with_checked_external_type, + with_additional_type_imports, }; use dto_bindgen_backend_ts::{DtoRegistryRenderOptions, DtoTypesModule}; + const CORE_BINDINGS_TYPES_TS: &str = + include_str!("../../../packages/core-bindings/src/generated/types.ts"); const EVENT_BINDINGS_TYPES_TS: &str = include_str!("../../../packages/event-bindings/src/generated/types.ts"); const EVENT_INDEX_BINDINGS_TYPES_TS: &str = @@ -576,6 +605,20 @@ mod tests { include_str!("../../../../lib/crates/replica_schema/src/models/trade_product_location.rs"), include_str!("../../../../lib/crates/replica_schema/src/models/trade_product_media.rs"), ]; + const CORE_TYPE_INVENTORY: &[&str] = &[ + "Currency", + "Decimal", + "Discount", + "DiscountScope", + "DiscountThreshold", + "DiscountValue", + "Money", + "Percent", + "Quantity", + "QuantityPrice", + "Unit", + "UnitDimension", + ]; const EVENT_TYPE_INVENTORY: &[&str] = &[ "JobFeedbackStatus", "JobInputType", @@ -753,11 +796,6 @@ mod tests { assert!( SDK_LOCAL_WRAPPER_ALLOWANCES .iter() - .any(|allowance| allowance.shape_family.contains("RadrootsCoreDecimal")) - ); - assert!( - SDK_LOCAL_WRAPPER_ALLOWANCES - .iter() .any(|allowance| { allowance.shape_family.contains("RadrootsEventIndexShardId") }) ); assert!(SDK_LOCAL_WRAPPER_ALLOWANCES.iter().any(|allowance| { @@ -768,49 +806,6 @@ mod tests { } #[test] - fn checked_external_type_lookup_rejects_missing_export() { - let registry = Registry::new(); - let error = with_checked_external_type( - ®istry, - DtoRegistryRenderOptions::default(), - "MissingType", - CORE_BINDINGS_PACKAGE_NAME, - ) - .expect_err("missing export must fail"); - - assert_eq!( - error, - "failed to resolve external DTO type `MissingType` from `@radroots/core-bindings`: no DTO type exports `MissingType`" - ); - } - - #[test] - fn checked_external_type_lookup_rejects_duplicate_export() { - let mut registry = Registry::new(); - registry.register_type( - RustTypeId::new("sdk", "sdk", "FirstDuplicate"), - TypeDef::Struct(StructDef::new("FirstDuplicate", "DuplicateType", span())), - ); - registry.register_type( - RustTypeId::new("sdk", "sdk", "SecondDuplicate"), - TypeDef::Struct(StructDef::new("SecondDuplicate", "DuplicateType", span())), - ); - - let error = with_checked_external_type( - ®istry, - DtoRegistryRenderOptions::default(), - "DuplicateType", - CORE_BINDINGS_PACKAGE_NAME, - ) - .expect_err("duplicate export must fail"); - - assert_eq!( - error, - "failed to resolve external DTO type `DuplicateType` from `@radroots/core-bindings`: DTO export name `DuplicateType` is ambiguous across 2 types" - ); - } - - #[test] fn trade_external_override_targets_resolve_to_generated_exports() { let registry = package_root_set("trade").expect("trade roots").registry(); @@ -856,7 +851,7 @@ mod tests { #[test] fn external_override_usage_rejects_local_emission_for_imported_type() { let module = DtoTypesModule::new( - "import type { RadrootsCoreDecimal } from \"@radroots/core-bindings\";\n\nimport type { RadrootsFarmRef } from \"@radroots/event-bindings\";\n\n", + "import type { Decimal } from \"@radroots/core-bindings\";\n\nimport type { RadrootsFarmRef } from \"@radroots/event-bindings\";\n\n", "export type RadrootsFarmRef = { pubkey: string, d_tag: string, };", ); let error = validate_external_override_usage(&module, &[event_override("RadrootsFarmRef")]) @@ -885,6 +880,20 @@ mod tests { } #[test] + fn additional_type_imports_merge_with_generated_package_imports() { + let module = DtoTypesModule::new( + "import type { Decimal, Money } from \"@radroots/core-bindings\";\n\n", + "export type Listing = { discounts: Array<Discount>, };", + ); + let merged = with_additional_type_imports(module, CORE_BINDINGS_PACKAGE_NAME, ["Discount"]); + + assert_eq!( + merged.imports_ts(), + Some("import type { Decimal, Discount, Money } from \"@radroots/core-bindings\";\n\n") + ); + } + + #[test] fn generated_type_export_inventory_parses_type_aliases() { assert_eq!( type_exports("export type Alpha = string;\nexport type Beta<T> = T;\n"), @@ -893,6 +902,14 @@ mod tests { } #[test] + fn core_type_inventory_matches_current_package_surface() { + let actual = type_inventory(CORE_BINDINGS_TYPES_TS); + + assert_eq!(actual, CORE_TYPE_INVENTORY); + assert!(!CORE_BINDINGS_TYPES_TS.contains("RadrootsCore")); + } + + #[test] fn event_type_inventory_matches_current_package_surface() { let actual = type_inventory(EVENT_BINDINGS_TYPES_TS);