sdk

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

commit a85410e02913454ff7e4bfcabf66b3828228f694
parent cb3c38e2ad8c8c7d598b00bb966d88c5e7221d53
Author: triesap <tyson@radroots.org>
Date:   Sun, 28 Jun 2026 00:14:49 +0000

ts: render constants through dto_bindgen

- Move events constants and kinds onto dto_bindgen TypeScriptModule descriptors.

- Move identity constants onto dto_bindgen constant declarations.

- Remove raw TypeScript source handling from the SDK package output path.

- Add module-backed generated output tests for constants and kinds.

Diffstat:
MCargo.lock | 2++
Mcrates/events_bindings/Cargo.toml | 1+
Mcrates/events_bindings/src/lib.rs | 4++--
Mcrates/events_bindings/src/model.rs | 58++++++++++++++++++++++++++++++----------------------------
Mcrates/identity_bindings/Cargo.toml | 1+
Mcrates/identity_bindings/src/lib.rs | 28+++++++++++++++++++++++-----
Mpackages/events-bindings/src/generated/kinds.ts | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/identity-bindings/src/generated/constants.ts | 2++
Mtools/xtask/src/output.rs | 89++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
9 files changed, 209 insertions(+), 49 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1753,6 +1753,7 @@ dependencies = [ name = "radroots_events_bindings" version = "0.1.0" dependencies = [ + "dto_bindgen_backend_ts", "radroots_events", ] @@ -1830,6 +1831,7 @@ dependencies = [ name = "radroots_identity_bindings" version = "0.1.0" dependencies = [ + "dto_bindgen_backend_ts", "radroots_identity", ] diff --git a/crates/events_bindings/Cargo.toml b/crates/events_bindings/Cargo.toml @@ -10,4 +10,5 @@ homepage.workspace = true publish = false [dependencies] +dto_bindgen_backend_ts = { workspace = true } radroots_events = { workspace = true } diff --git a/crates/events_bindings/src/lib.rs b/crates/events_bindings/src/lib.rs @@ -12,8 +12,8 @@ mod tests { #[test] fn preserves_event_constant_exports() { - let constants = constants_module(); - let kinds_ts = kinds_module(); + let constants = constants_module().render_source(); + let kinds_ts = kinds_module().render_source(); assert!(constants.contains("RADROOTS_LISTING_PRODUCT_TAG_KEYS")); assert!(constants.contains(RADROOTS_LISTING_PRODUCT_TAG_KEYS[0])); assert!(kinds_ts.contains("KIND_LISTING")); diff --git a/crates/events_bindings/src/model.rs b/crates/events_bindings/src/model.rs @@ -1,14 +1,37 @@ +use dto_bindgen_backend_ts::{ + TypeScriptDeclaration, TypeScriptImport, TypeScriptModule, TypeScriptType, TypeScriptValue, +}; use radroots_events::{kinds, listing::RADROOTS_LISTING_PRODUCT_TAG_KEYS}; -pub fn constants_module() -> String { - format!( - "import type {{ RadrootsListingProductTagKeys }} from \"./types.js\";\n\nexport const RADROOTS_LISTING_PRODUCT_TAG_KEYS: RadrootsListingProductTagKeys = {};", - render_string_array(&RADROOTS_LISTING_PRODUCT_TAG_KEYS) - ) +pub fn constants_module() -> TypeScriptModule { + TypeScriptModule::new("src/generated/constants.ts") + .with_import(TypeScriptImport::type_only( + ["RadrootsListingProductTagKeys"], + "./types.js", + )) + .with_declaration(TypeScriptDeclaration::constant( + "RADROOTS_LISTING_PRODUCT_TAG_KEYS", + Some(TypeScriptType::named("RadrootsListingProductTagKeys")), + TypeScriptValue::array( + RADROOTS_LISTING_PRODUCT_TAG_KEYS + .iter() + .map(|value| TypeScriptValue::string(*value)) + .collect::<Vec<_>>(), + ), + )) } -pub fn kinds_module() -> String { - render_number_constants(EVENT_KIND_EXPORTS) +pub fn kinds_module() -> TypeScriptModule { + EVENT_KIND_EXPORTS.iter().fold( + TypeScriptModule::new("src/generated/kinds.ts"), + |module, (name, value)| { + module.with_declaration(TypeScriptDeclaration::constant( + *name, + None, + TypeScriptValue::number(i64::from(*value)), + )) + }, + ) } const EVENT_KIND_EXPORTS: &[(&str, u32)] = &[ @@ -147,24 +170,3 @@ const EVENT_KIND_EXPORTS: &[(&str, u32)] = &[ ("KIND_JOB_RESULT_MAX", kinds::KIND_JOB_RESULT_MAX), ("KIND_JOB_FEEDBACK", kinds::KIND_JOB_FEEDBACK), ]; - -fn render_number_constants(exports: &[(&str, u32)]) -> String { - let mut rendered = String::new(); - for (name, value) in exports { - rendered.push_str("export const "); - rendered.push_str(name); - rendered.push_str(" = "); - rendered.push_str(&value.to_string()); - rendered.push_str(";\n"); - } - rendered -} - -fn render_string_array(values: &[&str]) -> String { - let items = values - .iter() - .map(|value| format!("{value:?}")) - .collect::<Vec<_>>() - .join(", "); - format!("[{items}]") -} diff --git a/crates/identity_bindings/Cargo.toml b/crates/identity_bindings/Cargo.toml @@ -10,4 +10,5 @@ homepage.workspace = true publish = false [dependencies] +dto_bindgen_backend_ts = { workspace = true } radroots_identity = { workspace = true } diff --git a/crates/identity_bindings/src/lib.rs b/crates/identity_bindings/src/lib.rs @@ -1,13 +1,31 @@ pub use radroots_identity as upstream; +use dto_bindgen_backend_ts::{TypeScriptDeclaration, TypeScriptModule, TypeScriptValue}; use radroots_identity::{ RADROOTS_USERNAME_MAX_LEN, RADROOTS_USERNAME_MIN_LEN, RADROOTS_USERNAME_REGEX, }; -pub fn constants_module() -> String { - format!( - "export const RADROOTS_USERNAME_MIN_LEN = {RADROOTS_USERNAME_MIN_LEN};\nexport const RADROOTS_USERNAME_MAX_LEN = {RADROOTS_USERNAME_MAX_LEN};\nexport const RADROOTS_USERNAME_REGEX = {RADROOTS_USERNAME_REGEX:?};" - ) +pub fn constants_module() -> TypeScriptModule { + TypeScriptModule::new("src/generated/constants.ts") + .with_declaration(TypeScriptDeclaration::constant( + "RADROOTS_USERNAME_MIN_LEN", + None, + usize_value(RADROOTS_USERNAME_MIN_LEN), + )) + .with_declaration(TypeScriptDeclaration::constant( + "RADROOTS_USERNAME_MAX_LEN", + None, + usize_value(RADROOTS_USERNAME_MAX_LEN), + )) + .with_declaration(TypeScriptDeclaration::constant( + "RADROOTS_USERNAME_REGEX", + None, + TypeScriptValue::string(RADROOTS_USERNAME_REGEX), + )) +} + +fn usize_value(value: usize) -> TypeScriptValue { + TypeScriptValue::number(i64::try_from(value).expect("TypeScript constant fits in i64")) } #[cfg(test)] @@ -19,7 +37,7 @@ mod tests { #[test] fn preserves_username_constant_exports() { - let rendered = constants_module(); + let rendered = constants_module().render_source(); assert!(rendered.contains("RADROOTS_USERNAME_MIN_LEN")); assert!(rendered.contains(&RADROOTS_USERNAME_MIN_LEN.to_string())); assert!(rendered.contains("RADROOTS_USERNAME_MAX_LEN")); diff --git a/packages/events-bindings/src/generated/kinds.ts b/packages/events-bindings/src/generated/kinds.ts @@ -1,76 +1,149 @@ // @generated by cargo xtask generate ts // Do not edit by hand. export const KIND_PROFILE = 0; + export const KIND_POST = 1; + export const KIND_FOLLOW = 3; + export const KIND_REACTION = 7; + export const KIND_SEAL = 13; + export const KIND_MESSAGE = 14; + export const KIND_MESSAGE_FILE = 15; + export const KIND_GIFT_WRAP = 1059; + export const KIND_COMMENT = 1111; + export const KIND_GEOCHAT = 20000; + export const KIND_LIST_MUTE = 10000; + export const KIND_LIST_PINNED_NOTES = 10001; + export const KIND_LIST_READ_WRITE_RELAYS = 10002; + export const KIND_LIST_BOOKMARKS = 10003; + export const KIND_LIST_COMMUNITIES = 10004; + export const KIND_LIST_PUBLIC_CHATS = 10005; + export const KIND_LIST_BLOCKED_RELAYS = 10006; + export const KIND_LIST_SEARCH_RELAYS = 10007; + export const KIND_LIST_SIMPLE_GROUPS = 10009; + export const KIND_LIST_RELAY_FEEDS = 10012; + export const KIND_LIST_INTERESTS = 10015; + export const KIND_LIST_MEDIA_FOLLOWS = 10020; + export const KIND_LIST_EMOJIS = 10030; + export const KIND_LIST_DM_RELAYS = 10050; + export const KIND_LIST_GOOD_WIKI_AUTHORS = 10101; + export const KIND_LIST_GOOD_WIKI_RELAYS = 10102; + export const KIND_LIST_SET_FOLLOW = 30000; + export const KIND_LIST_SET_GENERIC = 30001; + export const KIND_LIST_SET_RELAY = 30002; + export const KIND_LIST_SET_BOOKMARK = 30003; + export const KIND_LIST_SET_CURATION = 30004; + export const KIND_LIST_SET_VIDEO = 30005; + export const KIND_LIST_SET_PICTURE = 30006; + export const KIND_LIST_SET_KIND_MUTE = 30007; + export const KIND_LIST_SET_INTEREST = 30015; + export const KIND_LIST_SET_EMOJI = 30030; + export const KIND_LIST_SET_RELEASE_ARTIFACT = 30063; + export const KIND_LIST_SET_APP_CURATION = 30267; + export const KIND_LIST_SET_CALENDAR = 31924; + export const KIND_LIST_SET_STARTER_PACK = 39089; + export const KIND_LIST_SET_MEDIA_STARTER_PACK = 39092; + export const KIND_FARM = 30340; + export const KIND_PLOT = 30350; + export const KIND_COOP = 30360; + export const KIND_DOCUMENT = 30361; + export const KIND_RESOURCE_AREA = 30370; + export const KIND_RESOURCE_HARVEST_CAP = 30371; + export const KIND_ACCOUNT_CLAIM = 30380; + export const KIND_APP_DATA = 30078; + export const KIND_LISTING = 30402; + export const KIND_APPLICATION_HANDLER = 31990; + export const KIND_TRADE_LISTING_VALIDATION_REQUEST = 5321; + export const KIND_TRADE_LISTING_VALIDATION_RESULT = 6321; + export const KIND_TRADE_TRANSITION_PROOF_REQUEST = 5322; + export const KIND_TRADE_TRANSITION_PROOF_RESULT = 6322; + export const KIND_ORDER_REQUEST = 3422; + export const KIND_ORDER_DECISION = 3423; + export const KIND_ORDER_REVISION_PROPOSAL = 3424; + export const KIND_ORDER_REVISION_DECISION = 3425; + export const KIND_ORDER_CANCELLATION = 3432; + export const KIND_TRADE_QUESTION = 3426; + export const KIND_TRADE_ANSWER = 3427; + export const KIND_TRADE_DISCOUNT_REQUEST = 3428; + export const KIND_TRADE_DISCOUNT_OFFER = 3429; + export const KIND_TRADE_DISCOUNT_ACCEPT = 3430; + export const KIND_TRADE_FORBIDDEN_3431 = 3431; + export const KIND_TRADE_FULFILLMENT_UPDATE = 3433; + export const KIND_TRADE_RECEIPT = 3434; + export const KIND_TRADE_VALIDATION_RECEIPT = 3440; + export const KIND_JOB_REQUEST_MIN = 5000; + export const KIND_JOB_REQUEST_MAX = 5999; + export const KIND_JOB_RESULT_MIN = 6000; + export const KIND_JOB_RESULT_MAX = 6999; + export const KIND_JOB_FEEDBACK = 7000; diff --git a/packages/identity-bindings/src/generated/constants.ts b/packages/identity-bindings/src/generated/constants.ts @@ -1,5 +1,7 @@ // @generated by cargo xtask generate ts // Do not edit by hand. export const RADROOTS_USERNAME_MIN_LEN = 3; + export const RADROOTS_USERNAME_MAX_LEN = 30; + export const RADROOTS_USERNAME_REGEX = "^(?!.*\\.\\.)(?!\\.)(?!.*\\.$)[a-z0-9._-]{3,30}$"; diff --git a/tools/xtask/src/output.rs b/tools/xtask/src/output.rs @@ -23,21 +23,21 @@ pub struct GeneratedFile { pub enum TsSource { DtoRegistry(DtoTypesModule), - Raw(String), + Module(TypeScriptModule), } impl TsSource { fn render(&self) -> String { match self { Self::DtoRegistry(module) => module.body_ts().to_owned(), - Self::Raw(body) => body.clone(), + Self::Module(module) => module.render_source(), } } fn imports(&self) -> Option<&str> { match self { Self::DtoRegistry(module) => module.imports_ts(), - Self::Raw(_) => None, + Self::Module(_) => None, } } } @@ -92,8 +92,10 @@ pub fn package_outputs() -> Result<Vec<PackageOutput>, String> { spec: spec_by_key("events"), types_ts: Some(TsSource::DtoRegistry(dto_roots::events_types_module()?)), types_imports: Vec::new(), - constants_ts: Some(TsSource::Raw(radroots_events_bindings::constants_module())), - kinds_ts: Some(TsSource::Raw(radroots_events_bindings::kinds_module())), + constants_ts: Some(TsSource::Module( + radroots_events_bindings::constants_module(), + )), + kinds_ts: Some(TsSource::Module(radroots_events_bindings::kinds_module())), }, PackageOutput { spec: spec_by_key("events_indexed"), @@ -108,7 +110,9 @@ pub fn package_outputs() -> Result<Vec<PackageOutput>, String> { spec: spec_by_key("identity"), types_ts: None, types_imports: Vec::new(), - constants_ts: Some(TsSource::Raw(radroots_identity_bindings::constants_module())), + constants_ts: Some(TsSource::Module( + radroots_identity_bindings::constants_module(), + )), kinds_ts: None, }, PackageOutput { @@ -212,26 +216,31 @@ fn render_index(output: &PackageOutput) -> String { mod tests { use super::{PackageOutput, TsSource, package_outputs, render_ts}; use crate::package_matrix::package_specs; - use dto_bindgen_backend_ts::{DtoTypesModule, TypeScriptImport}; + use dto_bindgen_backend_ts::{ + DtoTypesModule, TypeScriptDeclaration, TypeScriptImport, TypeScriptModule, TypeScriptType, + }; const TRADE_BINDINGS_TYPES_TS: &str = include_str!("../../../packages/trade-bindings/src/generated/types.ts"); const REPLICA_DB_SCHEMA_BINDINGS_TYPES_TS: &str = include_str!("../../../packages/replica-db-schema-bindings/src/generated/types.ts"); + const EVENTS_BINDINGS_CONSTANTS_TS: &str = + include_str!("../../../packages/events-bindings/src/generated/constants.ts"); + const EVENTS_BINDINGS_KINDS_TS: &str = + include_str!("../../../packages/events-bindings/src/generated/kinds.ts"); + const IDENTITY_BINDINGS_CONSTANTS_TS: &str = + include_str!("../../../packages/identity-bindings/src/generated/constants.ts"); #[test] fn renders_sdk_header() { - let output = render_ts(&TsSource::Raw("export type A = string;".to_owned()), None); + let output = render_ts(&test_module(), None); assert!(output.starts_with("// @generated by cargo xtask generate ts")); assert!(output.contains("export type A = string;")); } #[test] fn renders_import_prelude_after_header() { - let output = render_ts( - &TsSource::Raw("export type A = string;".to_owned()), - Some("import type { B } from \"b\";\n\n"), - ); + let output = render_ts(&test_module(), Some("import type { B } from \"b\";\n\n")); assert!(output.starts_with( "// @generated by cargo xtask generate ts\n// Do not edit by hand.\nimport type" )); @@ -239,8 +248,8 @@ mod tests { } #[test] - fn renders_raw_sources() { - let output = render_ts(&TsSource::Raw("export type A = string;".to_owned()), None); + fn renders_module_sources() { + let output = render_ts(&test_module(), None); assert_eq!( output, "// @generated by cargo xtask generate ts\n// Do not edit by hand.\nexport type A = string;\n" @@ -339,4 +348,56 @@ mod tests { assert_eq!(types.contents, REPLICA_DB_SCHEMA_BINDINGS_TYPES_TS); } + + #[test] + fn events_constants_and_kinds_use_modules_and_match_checked_in_files() { + let output = package_outputs() + .expect("package outputs") + .into_iter() + .find(|output| output.spec.key == "events") + .expect("events output"); + + assert!(matches!(output.constants_ts, Some(TsSource::Module(_)))); + assert!(matches!(output.kinds_ts, Some(TsSource::Module(_)))); + + let files = output.files(); + let constants = files + .iter() + .find(|file| file.relative_path == "src/generated/constants.ts") + .expect("constants file"); + let kinds = files + .iter() + .find(|file| file.relative_path == "src/generated/kinds.ts") + .expect("kinds file"); + + assert_eq!(constants.contents, EVENTS_BINDINGS_CONSTANTS_TS); + assert_eq!(kinds.contents, EVENTS_BINDINGS_KINDS_TS); + } + + #[test] + fn identity_constants_use_module_and_match_checked_in_file() { + let output = package_outputs() + .expect("package outputs") + .into_iter() + .find(|output| output.spec.key == "identity") + .expect("identity output"); + + assert!(matches!(output.constants_ts, Some(TsSource::Module(_)))); + + let constants = output + .files() + .into_iter() + .find(|file| file.relative_path == "src/generated/constants.ts") + .expect("constants file"); + + assert_eq!(constants.contents, IDENTITY_BINDINGS_CONSTANTS_TS); + } + + fn test_module() -> TsSource { + TsSource::Module( + TypeScriptModule::new("src/generated/test.ts").with_declaration( + TypeScriptDeclaration::type_alias("A", TypeScriptType::String), + ), + ) + } }