sdk

Radroots SDK and bindings
git clone https://radroots.dev/git/sdk.git
Log | Files | Refs | README

commit 7367d7b31df365b92b016b851cefc79ead6bb434
parent 5fdc9b9ab675cd96077dbee9e383ae786035afdf
Author: triesap <tyson@radroots.org>
Date:   Wed, 24 Jun 2026 22:21:26 +0000

replica: harden schema dto parity

Diffstat:
Mcrates/replica_db_schema_bindings/src/lib.rs | 4++++
Mpackages/replica-db-schema-bindings/src/generated/types.ts | 8++++++++
Mtools/xtask/src/dto_render.rs | 25++++++++++++++++++++++++-
Mtools/xtask/src/dto_roots.rs | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 106 insertions(+), 1 deletion(-)

diff --git a/crates/replica_db_schema_bindings/src/lib.rs b/crates/replica_db_schema_bindings/src/lib.rs @@ -22,6 +22,10 @@ mod tests { assert!(actual.contains(&"Farm")); assert!(actual.contains(&"GcsLocation")); + assert!(actual.contains(&"IGcsLocationFindMany")); assert!(actual.contains(&"IGcsLocationFindManyResolve")); + assert!(actual.contains(&"IMediaImageFindMany")); + assert!(actual.contains(&"INostrProfileFindMany")); + assert!(actual.contains(&"INostrRelayFindMany")); } } diff --git a/packages/replica-db-schema-bindings/src/generated/types.ts b/packages/replica-db-schema-bindings/src/generated/types.ts @@ -212,6 +212,8 @@ export type IGcsLocationFieldsFilter = { id?: string, created_at?: string, updat export type IGcsLocationFieldsPartial = { d_tag?: ReplicaDbJsonValue | null, lat?: ReplicaDbJsonValue | null, lng?: ReplicaDbJsonValue | null, geohash?: ReplicaDbJsonValue | null, point?: ReplicaDbJsonValue | null, polygon?: ReplicaDbJsonValue | null, accuracy?: ReplicaDbJsonValue | null, altitude?: ReplicaDbJsonValue | null, tag_0?: ReplicaDbJsonValue | null, label?: ReplicaDbJsonValue | null, area?: ReplicaDbJsonValue | null, elevation?: ReplicaDbJsonValue | null, soil?: ReplicaDbJsonValue | null, climate?: ReplicaDbJsonValue | null, gc_id?: ReplicaDbJsonValue | null, gc_name?: ReplicaDbJsonValue | null, gc_admin1_id?: ReplicaDbJsonValue | null, gc_admin1_name?: ReplicaDbJsonValue | null, gc_country_id?: ReplicaDbJsonValue | null, gc_country_name?: ReplicaDbJsonValue | null, }; +export type IGcsLocationFindMany = { filter: IGcsLocationFieldsFilter | null, } | { rel: GcsLocationFindManyRel, }; + export type IGcsLocationFindManyResolve = IResultList<GcsLocation>; export type IGcsLocationFindOne = IGcsLocationFindOneArgs | IGcsLocationFindOneRelArgs; @@ -274,6 +276,8 @@ export type IMediaImageFieldsFilter = { id?: string, created_at?: string, update export type IMediaImageFieldsPartial = { file_path?: ReplicaDbJsonValue | null, mime_type?: ReplicaDbJsonValue | null, res_base?: ReplicaDbJsonValue | null, res_path?: ReplicaDbJsonValue | null, label?: ReplicaDbJsonValue | null, description?: ReplicaDbJsonValue | null, }; +export type IMediaImageFindMany = { filter: IMediaImageFieldsFilter | null, } | { rel: MediaImageFindManyRel, }; + export type IMediaImageFindManyResolve = IResultList<MediaImage>; export type IMediaImageFindOne = IMediaImageFindOneArgs | IMediaImageFindOneRelArgs; @@ -336,6 +340,8 @@ export type INostrProfileFieldsFilter = { id?: string, created_at?: string, upda export type INostrProfileFieldsPartial = { public_key?: ReplicaDbJsonValue | null, profile_type?: ReplicaDbJsonValue | null, name?: ReplicaDbJsonValue | null, display_name?: ReplicaDbJsonValue | null, about?: ReplicaDbJsonValue | null, website?: ReplicaDbJsonValue | null, picture?: ReplicaDbJsonValue | null, banner?: ReplicaDbJsonValue | null, nip05?: ReplicaDbJsonValue | null, lud06?: ReplicaDbJsonValue | null, lud16?: ReplicaDbJsonValue | null, }; +export type INostrProfileFindMany = { filter: INostrProfileFieldsFilter | null, } | { rel: NostrProfileFindManyRel, }; + export type INostrProfileFindManyResolve = IResultList<NostrProfile>; export type INostrProfileFindOne = INostrProfileFindOneArgs | INostrProfileFindOneRelArgs; @@ -370,6 +376,8 @@ export type INostrRelayFieldsFilter = { id?: string, created_at?: string, update export type INostrRelayFieldsPartial = { url?: ReplicaDbJsonValue | null, relay_id?: ReplicaDbJsonValue | null, name?: ReplicaDbJsonValue | null, description?: ReplicaDbJsonValue | null, pubkey?: ReplicaDbJsonValue | null, contact?: ReplicaDbJsonValue | null, supported_nips?: ReplicaDbJsonValue | null, software?: ReplicaDbJsonValue | null, version?: ReplicaDbJsonValue | null, data?: ReplicaDbJsonValue | null, }; +export type INostrRelayFindMany = { filter: INostrRelayFieldsFilter | null, } | { rel: NostrRelayFindManyRel, }; + export type INostrRelayFindManyResolve = IResultList<NostrRelay>; export type INostrRelayFindOne = INostrRelayFindOneArgs | INostrRelayFindOneRelArgs; diff --git a/tools/xtask/src/dto_render.rs b/tools/xtask/src/dto_render.rs @@ -191,7 +191,9 @@ fn render_untagged_variant( imports: &mut BTreeMap<String, BTreeSet<String>>, ) -> Result<String, String> { let rendered: Result<String, String> = match &variant.shape { - VariantShape::Unit => Ok("undefined".to_owned()), + VariantShape::Unit => { + Err("untagged unit variants are unsupported for JSON DTO output".to_owned()) + } VariantShape::Newtype(ty) => render_type_ref(ty, None, registry, options, imports), VariantShape::Tuple(items) => { let rendered = items @@ -837,6 +839,27 @@ mod tests { } #[test] + fn rejects_untagged_unit_variants() { + let mut registry = Registry::new(); + registry.register_type( + RustTypeId::new("sdk", "MaybeReady"), + TypeDef::Enum( + EnumDef::new("MaybeReady", "MaybeReady", EnumRepr::Untagged, span()).with_variant( + VariantDef::new("Ready", "ready", VariantShape::Unit, span()), + ), + ), + ); + + let error = render_registry_types(&registry, &DtoRegistryRenderOptions::default()) + .expect_err("untagged unit variant blocks render"); + + assert_eq!( + error, + "untagged unit variants are unsupported for JSON DTO output while rendering untagged enum MaybeReady.Ready" + ); + } + + #[test] fn requires_explicit_large_integer_policy() { let mut registry = Registry::new(); registry.register_type( diff --git a/tools/xtask/src/dto_roots.rs b/tools/xtask/src/dto_roots.rs @@ -328,6 +328,8 @@ fn with_events_indexed_sdk_wrappers(body: &str) -> String { #[cfg(test)] mod tests { + use std::collections::BTreeSet; + use super::{ DTO_PACKAGE_ROOTS, MANUAL_DESCRIPTOR_FAMILIES, SDK_LOCAL_WRAPPER_ALLOWANCES, package_root_set, @@ -341,6 +343,28 @@ mod tests { include_str!("../../../packages/replica-db-schema-bindings/src/generated/types.ts"); const TRADE_BINDINGS_TYPES_TS: &str = include_str!("../../../packages/trade-bindings/src/generated/types.ts"); + const REPLICA_SCHEMA_MODEL_SOURCES: &[&str] = &[ + include_str!("../../../../lib/crates/replica_db_schema/src/models/farm.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/farm_gcs_location.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/farm_member.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/farm_member_claim.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/farm_tag.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/gcs_location.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/log_error.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/media_image.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/nostr_event_head.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/nostr_profile.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/nostr_profile_relay.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/nostr_relay.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/plot.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/plot_gcs_location.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/plot_tag.rs"), + include_str!("../../../../lib/crates/replica_db_schema/src/models/trade_product.rs"), + include_str!( + "../../../../lib/crates/replica_db_schema/src/models/trade_product_location.rs" + ), + include_str!("../../../../lib/crates/replica_db_schema/src/models/trade_product_media.rs"), + ]; const EVENTS_TYPE_INVENTORY: &[&str] = &[ "JobFeedbackStatus", "JobInputType", @@ -596,6 +620,22 @@ mod tests { } #[test] + fn replica_db_schema_generated_types_match_source_public_inventory() { + let actual = type_inventory(REPLICA_DB_SCHEMA_BINDINGS_TYPES_TS) + .into_iter() + .collect::<BTreeSet<_>>(); + let missing = source_public_schema_type_inventory() + .into_iter() + .filter(|name| !actual.contains(name)) + .collect::<Vec<_>>(); + + assert!( + missing.is_empty(), + "missing generated replica schema exports: {missing:?}" + ); + } + + #[test] fn trade_package_imports_source_owned_support_types() { assert!(TRADE_BINDINGS_TYPES_TS.contains("from \"@radroots/core-bindings\"")); assert!(TRADE_BINDINGS_TYPES_TS.contains("from \"@radroots/events-bindings\"")); @@ -618,6 +658,36 @@ mod tests { .collect() } + fn source_public_schema_type_inventory() -> Vec<&'static str> { + let mut names = BTreeSet::new(); + + for source in REPLICA_SCHEMA_MODEL_SOURCES { + for line in source.lines() { + if let Some(name) = public_rust_type_name(line) + && !name.ends_with("Ts") + { + names.insert(name); + } + } + } + + names.into_iter().collect() + } + + fn public_rust_type_name(line: &'static str) -> Option<&'static str> { + let line = line.trim_start(); + + ["pub struct ", "pub enum ", "pub type "] + .into_iter() + .find_map(|prefix| { + line.strip_prefix(prefix).map(|rest| { + rest.split(|char: char| !(char == '_' || char.is_ascii_alphanumeric())) + .next() + .expect("type name") + }) + }) + } + fn type_declaration<'a>(types_ts: &'a str, name: &str) -> &'a str { types_ts .lines()