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:
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),
+ ),
+ )
+ }
}