use crate::codegen::naming::to_node_name;
use crate::core::backend::GeneratedFile;
use crate::core::config::{NodeCapsuleTypeConfig, ResolvedCrateConfig, TraitBridgeConfig};
use crate::core::ir::ApiSurface;
use std::collections::HashMap;
use std::path::PathBuf;
pub(super) fn generate(api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let mut value_names = std::collections::BTreeSet::new();
let mut type_names = std::collections::BTreeSet::new();
let capsule_types: HashMap<String, NodeCapsuleTypeConfig> = config
.node
.as_ref()
.map(|c| c.capsule_types.clone())
.unwrap_or_default();
for typ in api.types.iter() {
if !typ.is_trait
&& !capsule_types.contains_key(&typ.name)
&& !typ.name.ends_with("Builder")
&& !typ.name.ends_with("Update")
{
if typ.is_opaque {
value_names.insert(typ.name.clone());
} else {
type_names.insert(typ.name.clone());
}
}
}
for enum_def in &api.enums {
if enum_def.variants.iter().any(|v| !v.fields.is_empty()) {
type_names.insert(enum_def.name.clone());
} else {
value_names.insert(enum_def.name.clone());
}
}
for name in &value_names {
type_names.remove(name);
}
let exclude_functions: ahash::AHashSet<String> = config
.node
.as_ref()
.map(|c| c.exclude_functions.iter().cloned().collect())
.unwrap_or_default();
for func in &api.functions {
if !exclude_functions.contains(&func.name) {
value_names.insert(to_node_name(&func.name));
}
}
for bridge in &config.trait_bridges {
if let Some(name) = bridge.register_fn.as_deref() {
value_names.insert(to_node_name(name));
}
if let Some(name) = bridge.unregister_fn.as_deref() {
value_names.insert(to_node_name(name));
}
if let Some(name) = bridge.clear_fn.as_deref() {
value_names.insert(to_node_name(name));
}
}
let package_name = config.node_package_name();
let mut lines = vec![];
if !value_names.is_empty() {
lines.push("export {".to_string());
for name in &value_names {
lines.push(format!(" {name},"));
}
lines.push(format!("}} from \"{}\";", package_name));
lines.push("".to_string());
}
if !type_names.is_empty() {
lines.push("export type {".to_string());
for name in &type_names {
lines.push(format!(" {name},"));
}
lines.push(format!("}} from \"{}\";", package_name));
lines.push("".to_string());
}
let content = lines.join("\n");
let output_path = PathBuf::from("packages/typescript/src/index.ts");
let mut files = vec![GeneratedFile {
path: output_path,
content,
generated_header: true,
}];
let bridges: Vec<(String, &TraitBridgeConfig, &crate::core::ir::TypeDef)> = config
.trait_bridges
.iter()
.filter_map(|bridge_cfg| {
api.types
.iter()
.find(|t| t.is_trait && t.name == bridge_cfg.trait_name)
.map(|trait_def| (bridge_cfg.trait_name.clone(), bridge_cfg, trait_def))
})
.collect();
for (trait_name, _bridge_cfg, trait_def) in &bridges {
let ts_file_content = gen_single_typescript_trait_bridge(trait_name, trait_def);
let ts_file_path = PathBuf::from(format!("packages/typescript/src/bridges/{}Bridge.ts", trait_name));
files.push(GeneratedFile {
path: ts_file_path,
content: ts_file_content,
generated_header: true,
});
}
Ok(files)
}
fn gen_single_typescript_trait_bridge(trait_name: &str, trait_def: &crate::core::ir::TypeDef) -> String {
use heck::ToLowerCamelCase;
let bridge_interface_name = format!("{}Bridge", trait_name);
let adapter_class_name = format!("{}Adapter", trait_name);
let mut interface_methods = String::new();
let mut adapter_methods = String::new();
for method in &trait_def.methods {
let method_camel = method.name.to_lower_camel_case();
let has_error = method.error_type.is_some();
let params = method
.params
.iter()
.map(|p| {
let param_name = p.name.to_lower_camel_case();
format!("{}: string", param_name) })
.collect::<Vec<_>>()
.join(", ");
let return_type = "string";
let optional_mark = if has_error { "?" } else { "" };
interface_methods.push_str(&format!(
" {}{}({}): {};\n",
method_camel, optional_mark, params, return_type
));
let call_args = method
.params
.iter()
.map(|p| p.name.to_lower_camel_case())
.collect::<Vec<_>>()
.join(", ");
adapter_methods.push_str(&format!(
" async {}({}): Promise<{}> {{\n",
method_camel, params, return_type
));
adapter_methods.push_str(&format!(
" try {{\n const result = await this.bridge.{}({});\n",
method_camel, call_args
));
adapter_methods.push_str(" return JSON.stringify({ ok: result });\n");
adapter_methods.push_str(" } catch (e) {\n");
adapter_methods.push_str(" return JSON.stringify({ err: String(e) });\n");
adapter_methods.push_str(" }\n");
adapter_methods.push_str(" }\n\n");
}
let mut content = String::new();
content.push_str("/**\n");
content.push_str(" * Generated by alef. Do not edit by hand.\n");
content.push_str(" * \n");
content.push_str(" * TypeScript trait bridge for outbound plugin implementations.\n");
content.push_str(" */\n\n");
content.push_str(&format!(
"export interface {} {{\n name(): string;\n version(): string;\n initialize(): Promise<void>;\n shutdown(): Promise<void>;\n{}}}",
bridge_interface_name, interface_methods
));
content.push_str("\n\n");
content.push_str(&format!(
"export class {} {{\n constructor(private bridge: {}) {{}}\n\n{}}}",
adapter_class_name, bridge_interface_name, adapter_methods
));
content
}