use mlf_codegen::{register_generator, CodeGenerator, GeneratorContext}; use mlf_lang::ast::*; use std::fmt::Write; pub struct TypeScriptGenerator; impl TypeScriptGenerator { pub const NAME: &'static str = "typescript"; fn generate_type(&self, ty: &Type, ctx: &GeneratorContext) -> Result { match ty { Type::Primitive { kind, .. } => Ok(match kind { PrimitiveType::Null => "null".to_string(), PrimitiveType::Boolean => "boolean".to_string(), PrimitiveType::Integer => "number".to_string(), PrimitiveType::String => "string".to_string(), PrimitiveType::Bytes => "Uint8Array".to_string(), PrimitiveType::Blob => "Blob".to_string(), // Annotation idea: @tsType("CustomBlobType") }), Type::Reference { path, .. } => { // Check if it's from the standard library let path_str = path.to_string(); // Map standard library types to TypeScript types Ok(match path_str.as_str() { "Datetime" => "string".to_string(), // ISO 8601 "Did" | "AtUri" | "Cid" | "AtIdentifier" | "Handle" | "Nsid" | "Tid" | "RecordKey" | "Uri" | "Language" => { "string".to_string() } _ => { // Local or cross-file reference path.segments.last().unwrap().name.clone() } }) } Type::Array { inner, .. } => { let inner_type = self.generate_type(inner, ctx)?; Ok(format!("{}[]", inner_type)) } Type::Union { types, .. } => { let type_strings: Result, _> = types .iter() .map(|t| self.generate_type(t, ctx)) .collect(); Ok(type_strings?.join(" | ")) } Type::Object { fields, .. } => { let mut obj = String::from("{\n"); for field in fields { if !field.docs.is_empty() { obj.push_str(" /** "); obj.push_str(&field.docs[0].text); obj.push_str(" */\n"); } obj.push_str(" "); obj.push_str(&field.name.name); if field.optional { obj.push('?'); } obj.push_str(": "); obj.push_str(&self.generate_type(&field.ty, ctx)?); obj.push_str(";\n"); } obj.push('}'); Ok(obj) } Type::Parenthesized { inner, .. } => { let inner_type = self.generate_type(inner, ctx)?; Ok(format!("({})", inner_type)) } Type::Constrained { base, .. } => { // For constrained types, just use the base type // Annotations like @min, @max could be added for runtime validation libraries self.generate_type(base, ctx) } Type::Unknown { .. } => Ok("unknown".to_string()), } } fn generate_doc_comment(&self, docs: &[DocComment]) -> String { if docs.is_empty() { return String::new(); } let mut result = String::from("/**\n"); for doc in docs { result.push_str(" * "); result.push_str(&doc.text); result.push('\n'); } result.push_str(" */\n"); result } } impl CodeGenerator for TypeScriptGenerator { fn name(&self) -> &'static str { Self::NAME } fn description(&self) -> &'static str { "Generate TypeScript type definitions and client code" } fn file_extension(&self) -> &'static str { ".ts" } fn generate(&self, ctx: &GeneratorContext) -> Result { let mut output = String::new(); // Header comment writeln!(output, "/**").unwrap(); writeln!(output, " * Generated from {}", ctx.namespace).unwrap(); writeln!(output, " * Do not edit manually").unwrap(); writeln!(output, " */").unwrap(); writeln!(output).unwrap(); // Generate code for each item for item in &ctx.lexicon.items { match item { Item::Record(record) => { output.push_str(&self.generate_doc_comment(&record.docs)); writeln!(output, "export interface {} {{", record.name.name).unwrap(); for field in &record.fields { if !field.docs.is_empty() { write!(output, " /** {} */\n", field.docs[0].text).unwrap(); } write!(output, " {}", field.name.name).unwrap(); if field.optional { write!(output, "?").unwrap(); } writeln!(output, ": {};", self.generate_type(&field.ty, ctx)?).unwrap(); } writeln!(output, "}}\n").unwrap(); } Item::DefType(def) => { output.push_str(&self.generate_doc_comment(&def.docs)); writeln!( output, "export type {} = {};\n", def.name.name, self.generate_type(&def.ty, ctx)? ).unwrap(); } Item::InlineType(inline) => { output.push_str(&self.generate_doc_comment(&inline.docs)); writeln!( output, "export type {} = {};\n", inline.name.name, self.generate_type(&inline.ty, ctx)? ).unwrap(); } Item::Token(token) => { output.push_str(&self.generate_doc_comment(&token.docs)); writeln!(output, "export const {} = Symbol('{}');\n", token.name.name, token.name.name).unwrap(); } Item::Query(_) | Item::Procedure(_) | Item::Subscription(_) => { // TODO: Generate client methods for these // Annotation idea: @clientMethod for custom generation } Item::Use(_) => { // Skip use statements in output } } } Ok(output) } } // Register the TypeScript generator pub static TYPESCRIPT_GENERATOR: TypeScriptGenerator = TypeScriptGenerator; register_generator!(TYPESCRIPT_GENERATOR);