1use crate::TsParams;
4use alux_ext::{ApplyAlg, HandlerContextAlg};
5use alux_jsonrpc::{JsonRpcAlg, JsonRpcFallibleAlg, JsonRpcMethodAlg, OutcomeAlg};
6use alux_shape::{ShapeOf, Spelling, words_of};
7use alux_shape_typescript::{TsShape, TsType};
8use std::collections::BTreeMap;
9
10pub const RUNTIME_PACKAGE: &str = "@alux-network/api";
15
16fn owned(shape: &TsType) -> Vec<(String, String)> {
18 shape.declarations().map(|(name, declaration)| (name.to_owned(), declaration.to_owned())).collect()
19}
20
21#[derive(Debug, Clone, Default, PartialEq, Eq)]
23pub struct TsModule {
24 declarations: BTreeMap<String, String>,
25 entries: BTreeMap<String, String>,
26}
27
28impl TsModule {
29 #[must_use]
31 pub fn render(&self) -> String {
32 let declarations: Vec<&str> = self.declarations.values().map(String::as_str).collect();
33 let entries: Vec<String> = self.entries.values().map(|entry| format!(" {entry},")).collect();
34 let program = format!("export const program = {{\n{}\n}} as const", entries.join("\n"));
35
36 if declarations.is_empty() { program } else { format!("{}\n\n{program}", declarations.join("\n\n")) }
37 }
38
39 #[must_use]
41 pub fn method_names(&self) -> Vec<&str> {
42 self.entries.keys().map(String::as_str).collect()
43 }
44}
45
46#[derive(Debug, Clone, Copy)]
48pub struct TsClient {
49 shapes: TsShape,
50 members: Spelling,
51}
52
53impl TsClient {
54 #[must_use]
56 pub fn new(members: Spelling) -> Self {
57 Self { shapes: TsShape::new(members), members }
58 }
59
60 fn method(
66 &self,
67 name: &'static str,
68 params: &[TsType],
69 labels: &[&str],
70 wire: &[&str],
71 answer: &TsType,
72 ) -> TsModule {
73 let mut declarations = BTreeMap::new();
74 let mut written = Vec::new();
75
76 for (index, param) in params.iter().enumerate() {
77 declarations.extend(owned(param));
78
79 written.push(match labels.get(index) {
81 Some(label) => format!("{}: {}", self.members.spell(&words_of(label)), param.expr()),
83 None => param.expr().to_owned(),
84 });
85 }
86
87 declarations.extend(owned(answer));
88
89 let entry = format!(
93 "{name}: method<[{}], {}>(\"{name}\", [{}])",
94 written.join(", "),
95 answer.expr(),
96 wire.iter().map(|name| format!("\"{name}\"")).collect::<Vec<_>>().join(", "),
97 );
98
99 TsModule { declarations, entries: BTreeMap::from([(name.to_owned(), entry)]) }
100 }
101}
102
103impl JsonRpcAlg for TsClient {
104 type Methods = TsModule;
105
106 fn jsonrpc_empty(&self) -> TsModule {
107 TsModule::default()
108 }
109
110 fn jsonrpc_merge(&self, left: TsModule, right: TsModule) -> TsModule {
111 let mut merged = left;
112 merged.declarations.extend(right.declarations);
113 merged.entries.extend(right.entries);
114
115 merged
116 }
117}
118
119impl<Context> HandlerContextAlg<Context> for TsClient
120where
121 Context: Send + Sync + 'static,
122{
123 type Handle = std::sync::Arc<Context>;
126}
127
128impl<Handle, Args, Output> JsonRpcMethodAlg<Handle, Args, Output> for TsClient
129where
130 Args: TsParams,
131 Output: ShapeOf<TsShape, Shape = TsType>,
132{
133 fn finish_jsonrpc_positional_method<Handler>(
134 &self,
135 name: &'static str,
136 arg_names: &'static [&'static str],
137 _handler: Handler,
138 ) -> TsModule
139 where
140 Handler: ApplyAlg<Handle, Args, Output = Output> + Send + Sync + 'static,
141 {
142 self.method(name, &Args::params(&self.shapes), arg_names, &[], &Output::shape_of(&self.shapes))
145 }
146
147 fn finish_jsonrpc_named_method<Handler>(
148 &self,
149 name: &'static str,
150 arg_names: &'static [&'static str],
151 _handler: Handler,
152 ) -> TsModule
153 where
154 Handler: ApplyAlg<Handle, Args, Output = Output> + Send + Sync + 'static,
155 {
156 self.method(name, &Args::params(&self.shapes), arg_names, arg_names, &Output::shape_of(&self.shapes))
157 }
158}
159
160impl<Handle, Args, Output> JsonRpcFallibleAlg<Handle, Args, Output> for TsClient
161where
162 Args: TsParams,
163 Output: OutcomeAlg,
164 Output::Value: ShapeOf<TsShape, Shape = TsType>,
165{
166 fn finish_jsonrpc_positional_fallible<Handler>(
167 &self,
168 name: &'static str,
169 arg_names: &'static [&'static str],
170 _handler: Handler,
171 ) -> TsModule
172 where
173 Handler: ApplyAlg<Handle, Args, Output = Output> + Send + Sync + 'static,
174 {
175 self.method(name, &Args::params(&self.shapes), arg_names, &[], &Output::Value::shape_of(&self.shapes))
177 }
178
179 fn finish_jsonrpc_named_fallible<Handler>(
180 &self,
181 name: &'static str,
182 arg_names: &'static [&'static str],
183 _handler: Handler,
184 ) -> TsModule
185 where
186 Handler: ApplyAlg<Handle, Args, Output = Output> + Send + Sync + 'static,
187 {
188 self.method(name, &Args::params(&self.shapes), arg_names, arg_names, &Output::Value::shape_of(&self.shapes))
189 }
190}