//! WASM component bindings. //! //! Provides JavaScript-friendly wrappers for thermodynamic components. use entropyk_components::port::{FluidId, Port}; use entropyk_components::Component; use wasm_bindgen::prelude::*; /// WASM wrapper for a thermodynamic component. #[wasm_bindgen] pub struct WasmComponent { pub(crate) inner: Box, name: String, } #[wasm_bindgen] impl WasmComponent { /// Get component type name. pub fn name(&self) -> String { self.name.clone() } } /// WASM wrapper for Compressor. #[wasm_bindgen] pub struct WasmCompressor { pub(crate) inner: entropyk_components::Compressor, } #[wasm_bindgen] impl WasmCompressor { /// Create a new Compressor with AHRI 540 performance model. /// /// # Arguments /// * `fluid` - Fluid identifier (e.g. "R134a", "R410A") /// * `speed_rpm` - Rotational speed in RPM /// * `displacement_m3_per_rev` - Displacement volume in m³/rev /// * `efficiency` - Mechanical efficiency (0.0 to 1.0) /// * `m1`..`m10` - AHRI 540 performance coefficients #[wasm_bindgen(constructor)] pub fn new( fluid: String, speed_rpm: f64, displacement_m3_per_rev: f64, efficiency: f64, m1: f64, m2: f64, m3: f64, m4: f64, m5: f64, m6: f64, m7: f64, m8: f64, m9: f64, m10: f64, ) -> Result { if speed_rpm <= 0.0 { return Err(js_sys::Error::new("speed_rpm must be positive").into()); } if displacement_m3_per_rev <= 0.0 { return Err(js_sys::Error::new("displacement_m3_per_rev must be positive").into()); } if !(0.0..=1.0).contains(&efficiency) { return Err(js_sys::Error::new("efficiency must be between 0.0 and 1.0").into()); } let coeffs = entropyk_components::Ahri540Coefficients::new(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10); let fluid_id = FluidId::new(&fluid); let p = entropyk_core::Pressure::from_bar(5.0); let h = entropyk_core::Enthalpy::from_joules_per_kg(400000.0); let suction = Port::new(fluid_id.clone(), p, h); let discharge = Port::new(fluid_id.clone(), p, h); let comp = entropyk_components::Compressor::new( coeffs, suction, discharge, speed_rpm, displacement_m3_per_rev, efficiency, ) .map_err(|e| js_sys::Error::new(&format!("Compressor creation failed: {}", e)))?; let suction_p = Port::new(fluid_id.clone(), p, h); let discharge_p = Port::new(fluid_id, p, h); let connected = comp .connect(suction_p, discharge_p) .map_err(|e| js_sys::Error::new(&format!("Compressor connect failed: {}", e)))?; Ok(WasmCompressor { inner: connected }) } /// Convert to a generic WasmComponent. pub fn into_component(self) -> WasmComponent { WasmComponent { inner: Box::new(self.inner), name: "Compressor".to_string(), } } } /// WASM wrapper for Condenser. #[wasm_bindgen] pub struct WasmCondenser { inner: entropyk_components::Condenser, } #[wasm_bindgen] impl WasmCondenser { /// Create a new condenser with thermal conductance UA (W/K). /// /// Rejects NaN, negative, zero, and infinite values. #[wasm_bindgen(constructor)] pub fn new(ua: f64) -> Result { if ua.is_nan() || ua.is_infinite() { return Err(js_sys::Error::new("UA must be a finite number").into()); } if ua <= 0.0 { return Err(js_sys::Error::new("UA must be positive").into()); } Ok(WasmCondenser { inner: entropyk_components::Condenser::new(ua), }) } /// Convert to a generic WasmComponent. pub fn into_component(self) -> WasmComponent { WasmComponent { inner: Box::new(self.inner), name: "Condenser".to_string(), } } } /// WASM wrapper for Evaporator. #[wasm_bindgen] pub struct WasmEvaporator { inner: entropyk_components::Evaporator, } #[wasm_bindgen] impl WasmEvaporator { /// Create a new evaporator with thermal conductance UA (W/K). /// /// Rejects NaN, negative, zero, and infinite values. #[wasm_bindgen(constructor)] pub fn new(ua: f64) -> Result { if ua.is_nan() || ua.is_infinite() { return Err(js_sys::Error::new("UA must be a finite number").into()); } if ua <= 0.0 { return Err(js_sys::Error::new("UA must be positive").into()); } Ok(WasmEvaporator { inner: entropyk_components::Evaporator::new(ua), }) } /// Convert to a generic WasmComponent. pub fn into_component(self) -> WasmComponent { WasmComponent { inner: Box::new(self.inner), name: "Evaporator".to_string(), } } } /// WASM wrapper for ExpansionValve. #[wasm_bindgen] pub struct WasmExpansionValve { pub(crate) inner: entropyk_components::ExpansionValve, } #[wasm_bindgen] impl WasmExpansionValve { /// Create a new expansion valve. /// /// # Arguments /// * `fluid` - Fluid identifier (e.g. "R134a", "R410A") /// * `capacity` - Optional valve capacity (kW). Pass 0.0 for no capacity limit. #[wasm_bindgen(constructor)] pub fn new(fluid: String, capacity: f64) -> Result { let fluid_id = FluidId::new(&fluid); let p = entropyk_core::Pressure::from_bar(10.0); let h = entropyk_core::Enthalpy::from_joules_per_kg(400000.0); let inlet = Port::new(fluid_id.clone(), p, h); let outlet = Port::new(fluid_id.clone(), p, h); let cap = if capacity > 0.0 { Some(capacity) } else { None }; let valve = entropyk_components::ExpansionValve::new(inlet, outlet, cap) .map_err(|e| js_sys::Error::new(&format!("ExpansionValve creation failed: {}", e)))?; let inlet_p = Port::new(fluid_id.clone(), p, h); let outlet_p = Port::new(fluid_id, p, h); let connected = valve .connect(inlet_p, outlet_p) .map_err(|e| js_sys::Error::new(&format!("ExpansionValve connect failed: {}", e)))?; Ok(WasmExpansionValve { inner: connected }) } /// Convert to a generic WasmComponent. pub fn into_component(self) -> WasmComponent { WasmComponent { inner: Box::new(self.inner), name: "ExpansionValve".to_string(), } } } /// WASM wrapper for Pipe. #[wasm_bindgen] pub struct WasmPipe { pub(crate) inner: entropyk_components::Pipe, } #[wasm_bindgen] impl WasmPipe { /// Create a new pipe. /// /// # Arguments /// * `fluid` - Fluid identifier (e.g. "Water") /// * `length` - Pipe length in meters (must be positive) /// * `diameter` - Pipe inner diameter in meters (must be positive) /// * `density` - Fluid density in kg/m³ (default: 1000.0 for water) /// * `viscosity` - Fluid dynamic viscosity in Pa·s (default: 0.001 for water) #[wasm_bindgen(constructor)] pub fn new( fluid: String, length: f64, diameter: f64, density: f64, viscosity: f64, ) -> Result { if length.is_nan() || length <= 0.0 { return Err(js_sys::Error::new("Pipe length must be a positive number").into()); } if diameter.is_nan() || diameter <= 0.0 { return Err(js_sys::Error::new("Pipe diameter must be a positive number").into()); } let geometry = entropyk_components::PipeGeometry::smooth(length, diameter) .map_err(|e| js_sys::Error::new(&format!("Invalid pipe geometry: {}", e)))?; let fluid_id = FluidId::new(&fluid); let p = entropyk_core::Pressure::from_bar(1.0); let h = entropyk_core::Enthalpy::from_joules_per_kg(100000.0); let inlet = Port::new(fluid_id.clone(), p, h); let outlet = Port::new(fluid_id.clone(), p, h); let pipe = entropyk_components::Pipe::new(geometry, inlet, outlet, density, viscosity) .map_err(|e| js_sys::Error::new(&format!("Pipe creation failed: {}", e)))?; let inlet_p = Port::new(fluid_id.clone(), p, h); let outlet_p = Port::new(fluid_id, p, h); let connected = pipe .connect(inlet_p, outlet_p) .map_err(|e| js_sys::Error::new(&format!("Pipe connect failed: {}", e)))?; Ok(WasmPipe { inner: connected }) } /// Convert to a generic WasmComponent. pub fn into_component(self) -> WasmComponent { WasmComponent { inner: Box::new(self.inner), name: "Pipe".to_string(), } } }