use crate::TsParams;
use alux_ext::{ApplyAlg, HandlerContextAlg};
use alux_jsonrpc::{JsonRpcAlg, JsonRpcFallibleAlg, JsonRpcMethodAlg, OutcomeAlg};
use alux_shape::{ShapeOf, Spelling, words_of};
use alux_shape_typescript::{TsShape, TsType};
use std::collections::BTreeMap;
pub const RUNTIME_PACKAGE: &str = "@alux-network/api";
fn owned(shape: &TsType) -> Vec<(String, String)> {
shape.declarations().map(|(name, declaration)| (name.to_owned(), declaration.to_owned())).collect()
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TsModule {
declarations: BTreeMap<String, String>,
entries: BTreeMap<String, String>,
}
impl TsModule {
#[must_use]
pub fn render(&self) -> String {
let declarations: Vec<&str> = self.declarations.values().map(String::as_str).collect();
let entries: Vec<String> = self.entries.values().map(|entry| format!(" {entry},")).collect();
let program = format!("export const program = {{\n{}\n}} as const", entries.join("\n"));
if declarations.is_empty() { program } else { format!("{}\n\n{program}", declarations.join("\n\n")) }
}
#[must_use]
pub fn method_names(&self) -> Vec<&str> {
self.entries.keys().map(String::as_str).collect()
}
}
#[derive(Debug, Clone, Copy)]
pub struct TsClient {
shapes: TsShape,
members: Spelling,
}
impl TsClient {
#[must_use]
pub fn new(members: Spelling) -> Self {
Self { shapes: TsShape::new(members), members }
}
fn method(
&self,
name: &'static str,
params: &[TsType],
labels: &[&str],
wire: &[&str],
answer: &TsType,
) -> TsModule {
let mut declarations = BTreeMap::new();
let mut written = Vec::new();
for (index, param) in params.iter().enumerate() {
declarations.extend(owned(param));
written.push(match labels.get(index) {
Some(label) => format!("{}: {}", self.members.spell(&words_of(label)), param.expr()),
None => param.expr().to_owned(),
});
}
declarations.extend(owned(answer));
let entry = format!(
"{name}: method<[{}], {}>(\"{name}\", [{}])",
written.join(", "),
answer.expr(),
wire.iter().map(|name| format!("\"{name}\"")).collect::<Vec<_>>().join(", "),
);
TsModule { declarations, entries: BTreeMap::from([(name.to_owned(), entry)]) }
}
}
impl JsonRpcAlg for TsClient {
type Methods = TsModule;
fn jsonrpc_empty(&self) -> TsModule {
TsModule::default()
}
fn jsonrpc_merge(&self, left: TsModule, right: TsModule) -> TsModule {
let mut merged = left;
merged.declarations.extend(right.declarations);
merged.entries.extend(right.entries);
merged
}
}
impl<Context> HandlerContextAlg<Context> for TsClient
where
Context: Send + Sync + 'static,
{
type Handle = std::sync::Arc<Context>;
}
impl<Handle, Args, Output> JsonRpcMethodAlg<Handle, Args, Output> for TsClient
where
Args: TsParams,
Output: ShapeOf<TsShape, Shape = TsType>,
{
fn finish_jsonrpc_positional_method<Handler>(
&self,
name: &'static str,
arg_names: &'static [&'static str],
_handler: Handler,
) -> TsModule
where
Handler: ApplyAlg<Handle, Args, Output = Output> + Send + Sync + 'static,
{
self.method(name, &Args::params(&self.shapes), arg_names, &[], &Output::shape_of(&self.shapes))
}
fn finish_jsonrpc_named_method<Handler>(
&self,
name: &'static str,
arg_names: &'static [&'static str],
_handler: Handler,
) -> TsModule
where
Handler: ApplyAlg<Handle, Args, Output = Output> + Send + Sync + 'static,
{
self.method(name, &Args::params(&self.shapes), arg_names, arg_names, &Output::shape_of(&self.shapes))
}
}
impl<Handle, Args, Output> JsonRpcFallibleAlg<Handle, Args, Output> for TsClient
where
Args: TsParams,
Output: OutcomeAlg,
Output::Value: ShapeOf<TsShape, Shape = TsType>,
{
fn finish_jsonrpc_positional_fallible<Handler>(
&self,
name: &'static str,
arg_names: &'static [&'static str],
_handler: Handler,
) -> TsModule
where
Handler: ApplyAlg<Handle, Args, Output = Output> + Send + Sync + 'static,
{
self.method(name, &Args::params(&self.shapes), arg_names, &[], &Output::Value::shape_of(&self.shapes))
}
fn finish_jsonrpc_named_fallible<Handler>(
&self,
name: &'static str,
arg_names: &'static [&'static str],
_handler: Handler,
) -> TsModule
where
Handler: ApplyAlg<Handle, Args, Output = Output> + Send + Sync + 'static,
{
self.method(name, &Args::params(&self.shapes), arg_names, arg_names, &Output::Value::shape_of(&self.shapes))
}
}