use std::collections::BTreeSet;
use convert_case::{Case, Casing};
use crate::codegen::ServiceHandler;
pub(crate) fn generate_dts(services: &[ServiceHandler<'_>]) -> String {
let bindings = services
.first()
.and_then(|s| s.config.bindings.as_ref())
.expect("bindings config required for wasm output");
let aggregate = &bindings.aggregate_client_name;
let mut model_types: BTreeSet<String> = BTreeSet::new();
for service in services {
for method in service.methods() {
if let Some(t) = method.input_type() {
model_types.insert(t.to_string());
}
if let Some(t) = method.output_type() {
model_types.insert(t.to_string());
}
}
}
let mut out = String::new();
out.push_str("// @generated — do not edit by hand.\n");
out.push_str(
"// TypeScript declarations for the wasm-bindgen browser client.\n\
// Request/response values are plain objects (the JSON form of the proto messages).\n\n",
);
if model_types.is_empty() {
out.push_str("// (no message types referenced)\n\n");
} else {
out.push_str("import type {\n");
for ty in &model_types {
out.push_str(&format!(" {ty},\n"));
}
out.push_str("} from \"./models\";\n\n");
}
for service in services {
let class = service.low_level_client_type().to_string();
out.push_str(&format!(
"/** WASM/browser binding for the `{}` service. */\n",
service.plan.base_path
));
out.push_str(&format!("export class {class} {{\n"));
for method in service.methods() {
out.push_str(&method_signature(&method));
}
out.push_str("}\n\n");
}
out.push_str(
"/** Browser entry point. Construct from a base URL; the browser manages the session. */\n",
);
out.push_str(&format!("export class {aggregate} {{\n"));
out.push_str(
" /** @param baseUrl Absolute base URL of the API (same-origin for cookie auth). */\n",
);
out.push_str(" constructor(baseUrl: string);\n");
for service in services {
let accessor = service.plan.base_path.to_case(Case::Camel);
let class = service.low_level_client_type().to_string();
out.push_str(&format!(
" /** Access the `{}` service. */\n {accessor}(): {class};\n",
service.plan.base_path
));
}
out.push_str("}\n");
out
}
fn method_signature(method: &crate::codegen::MethodHandler<'_>) -> String {
let name = method.binding_method_name_str().to_case(Case::Camel);
let param = match method.input_type() {
Some(ty) => format!("request: {ty}"),
None => String::new(),
};
let ret = match method.output_type() {
Some(ty) => format!("Promise<{ty}>"),
None => "Promise<void>".to_string(),
};
let mut sig = String::new();
if let Some(doc) = method.plan.metadata.documentation.as_deref() {
let one_line = doc.replace('\n', " ");
let trimmed = one_line.trim();
if !trimmed.is_empty() {
sig.push_str(&format!(" /** {trimmed} */\n"));
}
}
sig.push_str(&format!(" {name}({param}): {ret};\n"));
sig
}