vox_codegen/targets/typescript/
server.rs1use heck::{ToLowerCamelCase, ToUpperCamelCase};
8use vox_types::{ServiceDescriptor, ShapeKind, classify_shape};
9
10use super::types::{ts_type_server_arg, ts_type_server_return};
11
12pub fn generate_handler_interface(service: &ServiceDescriptor) -> String {
16 let mut out = String::new();
17 let service_name = service.service_name.to_upper_camel_case();
18
19 out.push_str(&format!("// Handler interface for {service_name}\n"));
20 out.push_str(&format!("export interface {service_name}Handler {{\n"));
21
22 for method in service.methods {
23 let method_name = method.method_name.to_lower_camel_case();
24 let args = method
25 .args
26 .iter()
27 .map(|a| {
28 format!(
29 "{}: {}",
30 a.name.to_lower_camel_case(),
31 ts_type_server_arg(a.shape)
32 )
33 })
34 .collect::<Vec<_>>()
35 .join(", ");
36 let ret_ty = ts_type_server_return(method.return_shape);
37
38 out.push_str(&format!(
39 " {method_name}({args}): Promise<{ret_ty}> | {ret_ty};\n"
40 ));
41 }
42
43 out.push_str("}\n\n");
44 out
45}
46
47pub fn generate_dispatcher_class(service: &ServiceDescriptor) -> String {
57 use crate::render::hex_u64;
58
59 let mut out = String::new();
60 let service_name = service.service_name.to_upper_camel_case();
61 let service_name_lower = service.service_name.to_lower_camel_case();
62
63 out.push_str(&format!("// Dispatcher for {service_name}\n"));
64 out.push_str(&format!(
65 "export class {service_name}Dispatcher implements Dispatcher {{\n"
66 ));
67 out.push_str(&format!(
68 " private readonly handler: {service_name}Handler;\n\n"
69 ));
70 out.push_str(&format!(
71 " constructor(handler: {service_name}Handler) {{\n"
72 ));
73 out.push_str(" this.handler = handler;\n");
74 out.push_str(" }\n\n");
75
76 out.push_str(" getDescriptor(): ServiceDescriptor {\n");
78 out.push_str(&format!(" return {service_name_lower}_descriptor;\n"));
79 out.push_str(" }\n\n");
80
81 out.push_str(
83 " async dispatch(_context: RequestContext, method: MethodDescriptor, args: unknown[], call: VoxCall): Promise<void> {\n",
84 );
85
86 let mut first = true;
87 for method in service.methods {
88 let method_name = method.method_name.to_lower_camel_case();
89 let id = crate::method_id(method);
90 let is_fallible = matches!(
91 classify_shape(method.return_shape),
92 ShapeKind::Result { .. }
93 );
94
95 let arg_names: Vec<_> = method
97 .args
98 .iter()
99 .map(|a| a.name.to_lower_camel_case())
100 .collect();
101 let typed_args: Vec<_> = method
102 .args
103 .iter()
104 .enumerate()
105 .map(|(i, a)| format!("args[{i}] as {}", ts_type_server_arg(a.shape)))
106 .collect();
107
108 let tx_arg_indices: Vec<usize> = method
110 .args
111 .iter()
112 .enumerate()
113 .filter(|(_, a)| matches!(classify_shape(a.shape), ShapeKind::Tx { .. }))
114 .map(|(i, _)| i)
115 .collect();
116
117 let keyword = if first { "if" } else { "} else if" };
118 first = false;
119
120 out.push_str(&format!(
121 " {keyword} (method.id === {}n) {{\n",
122 hex_u64(id)
123 ));
124 out.push_str(" try {\n");
125 out.push_str(&format!(
126 " const result = await this.handler.{method_name}({});\n",
127 typed_args.join(", ")
128 ));
129
130 for i in &tx_arg_indices {
132 let arg_name = &arg_names[*i];
133 out.push_str(&format!(
134 " (args[{i}] as {{ close(): void }}).close(); // close {arg_name} before reply\n"
135 ));
136 }
137
138 if is_fallible {
139 out.push_str(" if (result.ok) call.reply(result.value); else call.replyErr(result.error);\n");
140 } else {
141 out.push_str(" call.reply(result);\n");
142 }
143
144 out.push_str(" } catch (error) {\n");
145 out.push_str(
146 " call.replyInternalError(error instanceof Error ? error.message : String(error));\n",
147 );
148 out.push_str(" }\n");
149 }
150
151 if !first {
152 out.push_str(" }\n");
153 }
154
155 out.push_str(" }\n");
156 out.push_str("}\n\n");
157 out
158}
159
160pub fn generate_server(service: &ServiceDescriptor) -> String {
162 let mut out = String::new();
163 out.push_str(&generate_handler_interface(service));
164 out.push_str(&generate_dispatcher_class(service));
165 out
166}