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:
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(®istry, &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(
+ ®istry,
+ &core_import_options(®istry, 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,