lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 09f1b3c02b45a7ae01a0886c6ac8ab6f42e53924
parent 6c365becb0591623964537f02d78f2809c103dce
Author: triesap <tyson@radroots.org>
Date:   Wed, 24 Jun 2026 07:38:26 +0000

dto: render events bindings from registry

- Switch events TypeScript type output to the dto registry renderer.

- Render external data enums and path-aware integer policy errors in xtask.

- Preserve core imports and SDK-local tuple wrapper declarations for events output.

- Remove the stale hand-authored events type module while keeping constants and kinds for RCLD-05.

Diffstat:
Mtools/sdk_xtask_import/src/dto_render.rs | 120++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mtools/sdk_xtask_import/src/dto_roots.rs | 69++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mtools/sdk_xtask_import/src/output.rs | 17++---------------
3 files changed, 170 insertions(+), 36 deletions(-)

diff --git a/tools/sdk_xtask_import/src/dto_render.rs b/tools/sdk_xtask_import/src/dto_render.rs @@ -141,36 +141,79 @@ fn render_enum( imports: &mut BTreeMap<String, BTreeSet<String>>, ) -> Result<String, String> { match &def.repr { - EnumRepr::External - if def - .variants - .iter() - .all(|variant| matches!(variant.shape, VariantShape::Unit)) => - { - let variants = def - .variants - .iter() - .map(|variant| quote_string(&variant.wire_name)) - .collect::<Vec<_>>(); - Ok(format!( - "export type {} = {};", - enum_type_name(def), - render_union(variants) - )) - } + EnumRepr::External => render_external_enum(def, registry, options, imports), EnumRepr::Internal { tag } => { render_tagged_enum(def, tag, None, registry, options, imports) } EnumRepr::Adjacent { tag, content } => { render_tagged_enum(def, tag, Some(content.as_str()), registry, options, imports) } - EnumRepr::External | EnumRepr::Untagged => Err(format!( + EnumRepr::Untagged => Err(format!( "unsupported enum representation for {}", enum_type_name(def) )), } } +fn render_external_enum( + def: &EnumDef, + registry: &Registry, + options: &DtoRegistryRenderOptions, + imports: &mut BTreeMap<String, BTreeSet<String>>, +) -> Result<String, String> { + let variants = def + .variants + .iter() + .map(|variant| render_external_variant(def, variant, registry, options, imports)) + .collect::<Result<Vec<_>, _>>()?; + Ok(format!( + "export type {} = {};", + enum_type_name(def), + render_union(variants) + )) +} + +fn render_external_variant( + def: &EnumDef, + variant: &VariantDef, + registry: &Registry, + options: &DtoRegistryRenderOptions, + imports: &mut BTreeMap<String, BTreeSet<String>>, +) -> Result<String, String> { + let rendered: Result<String, String> = match &variant.shape { + VariantShape::Unit => Ok(quote_string(&variant.wire_name)), + VariantShape::Newtype(ty) => Ok(format!( + "{{ {}: {}, }}", + render_property_name(&variant.wire_name), + render_type_ref(ty, None, registry, options, imports)? + )), + VariantShape::Tuple(items) => { + let rendered = items + .iter() + .map(|item| render_type_ref(item, None, registry, options, imports)) + .collect::<Result<Vec<_>, _>>()?; + Ok(format!( + "{{ {}: [{}], }}", + render_property_name(&variant.wire_name), + rendered.join(", ") + )) + } + VariantShape::Struct(fields) => Ok(format!( + "{{ {}: {}, }}", + render_property_name(&variant.wire_name), + render_object_fields(fields, registry, options, imports)? + )), + }; + + rendered.map_err(|error| { + format!( + "{error} while rendering external enum {}.{}", + enum_type_name(def), + variant.rust_name + ) + }) +} + fn render_tagged_enum( def: &EnumDef, tag: &str, @@ -292,7 +335,13 @@ fn render_object_field( } else { "?" }; - let mut value = render_type_ref(&field.ty, field.int_repr, registry, options, imports)?; + let mut value = render_type_ref(&field.ty, field.int_repr, registry, options, imports) + .map_err(|error| { + format!( + "{error} while rendering field {} at {}", + field.target.typescript, field.source + ) + })?; if field.presence.nullable { value = render_nullable(value); } @@ -644,7 +693,7 @@ mod tests { assert_eq!( error, - "large integer field requires explicit numeric policy" + "large integer field requires explicit numeric policy while rendering field value at src/dto.rs:1:1" ); } @@ -715,4 +764,35 @@ mod tests { "export type Counter = { value: number, };\n\nexport type TransparentCounters = { maybeCount?: string | null, countList: Array<string>, fixedCounts: [string, string], byKey: Record<string, string>, namedCounter: Counter, };" ); } + + #[test] + fn renders_external_data_enums() { + let mut registry = Registry::new(); + registry.register_type( + RustTypeId::new("sdk", "ParseError"), + TypeDef::Enum( + EnumDef::new("ParseError", "ParseError", EnumRepr::External, span()) + .with_variant(VariantDef::new( + "InvalidKind", + "InvalidKind", + VariantShape::Newtype(TypeRef::Primitive(Primitive::U32)), + span(), + )) + .with_variant(VariantDef::new( + "InvalidUnit", + "InvalidUnit", + VariantShape::Unit, + span(), + )), + ), + ); + + let rendered = render_registry_types(&registry, &DtoRegistryRenderOptions::default()) + .expect("registry renders"); + + assert_eq!( + rendered.body_ts(), + "export type ParseError = { InvalidKind: number, } | \"InvalidUnit\";" + ); + } } diff --git a/tools/sdk_xtask_import/src/dto_roots.rs b/tools/sdk_xtask_import/src/dto_roots.rs @@ -1,4 +1,4 @@ -use dto_bindgen_core::{Registry, RootDescriptor, build_registry}; +use dto_bindgen_core::{Registry, RootDescriptor, RustTypeId, TypeId, build_registry}; use crate::dto_render::{DtoRegistryRenderOptions, DtoTypesModule, render_registry_types}; @@ -118,6 +118,20 @@ pub fn core_types_module() -> Result<DtoTypesModule, String> { )) } +pub fn events_types_module() -> Result<DtoTypesModule, String> { + let root_set = + package_root_set("events").ok_or_else(|| "missing events DTO roots".to_owned())?; + let registry = root_set.registry(); + let rendered = render_registry_types( + &registry, + &core_import_options(&registry, DtoRegistryRenderOptions::default()), + )?; + Ok(DtoTypesModule::new( + rendered.imports_ts().unwrap_or_default(), + with_events_sdk_wrappers(rendered.body_ts()), + )) +} + fn core_roots() -> Vec<RootDescriptor> { radroots_core::dto::dto_roots().into_iter().collect() } @@ -126,6 +140,59 @@ fn events_roots() -> Vec<RootDescriptor> { radroots_events::dto::dto_roots().into_iter().collect() } +fn core_import_options( + registry: &Registry, + mut options: DtoRegistryRenderOptions, +) -> DtoRegistryRenderOptions { + for export_name in [ + "RadrootsCoreCurrency", + "RadrootsCoreDecimal", + "RadrootsCoreDiscount", + "RadrootsCoreDiscountScope", + "RadrootsCoreDiscountThreshold", + "RadrootsCoreDiscountValue", + "RadrootsCoreMoney", + "RadrootsCorePercent", + "RadrootsCoreQuantity", + "RadrootsCoreQuantityPrice", + "RadrootsCoreUnit", + "RadrootsCoreUnitDimension", + ] { + if let Some(type_id) = core_type_id(registry, export_name) { + options = options.with_external_type(type_id, export_name, "@radroots/core-bindings"); + } + } + options +} + +fn core_type_id(registry: &Registry, rust_ident: &str) -> Option<TypeId> { + registry + .rust_id_to_type_id + .get(&RustTypeId::new("radroots_core", rust_ident)) + .copied() +} + +fn with_events_sdk_wrappers(body: &str) -> String { + let mut declarations = body + .split("\n\n") + .filter(|declaration| !declaration.trim().is_empty()) + .map(str::to_owned) + .collect::<Vec<_>>(); + declarations.push( + "export type RadrootsListingProductTagKeys = readonly [\"key\", \"title\", \"category\", \"summary\", \"process\", \"lot\", \"location\", \"profile\", \"year\"];" + .to_owned(), + ); + declarations.sort_by(|left, right| declaration_name(left).cmp(declaration_name(right))); + declarations.join("\n\n") +} + +fn declaration_name(declaration: &str) -> &str { + declaration + .strip_prefix("export type ") + .and_then(|rest| rest.split([' ', '<']).next()) + .unwrap_or(declaration) +} + #[cfg(test)] mod tests { use super::{ diff --git a/tools/sdk_xtask_import/src/output.rs b/tools/sdk_xtask_import/src/output.rs @@ -88,8 +88,8 @@ pub fn package_outputs() -> Result<Vec<PackageOutput>, String> { }, PackageOutput { spec: spec_by_key("events"), - types_ts: Some(TsSource::Module(radroots_events_bindings::types_module())), - types_imports_ts: Some(EVENTS_TYPES_IMPORTS_TS), + types_ts: Some(TsSource::DtoRegistry(dto_roots::events_types_module()?)), + types_imports_ts: None, constants_ts: Some(TsSource::Module( radroots_events_bindings::constants_module(), )), @@ -166,19 +166,6 @@ fn combined_imports(first: Option<&str>, second: Option<&str>) -> Option<String> } } -const EVENTS_TYPES_IMPORTS_TS: &str = r#"import type { - RadrootsCoreCurrency, - RadrootsCoreDecimal, - RadrootsCoreDiscount, - RadrootsCoreDiscountValue, - RadrootsCoreMoney, - RadrootsCoreQuantity, - RadrootsCoreQuantityPrice, - RadrootsCoreUnit, -} from "@radroots/core-bindings"; - -"#; - const REPLICA_DB_SCHEMA_TYPES_IMPORTS_TS: &str = r#"import type { IResult, IResultList,