1use js_sys::{Array, Float32Array, Object, Reflect};
7use serde::{Deserialize, Serialize};
8use wasm_bindgen::prelude::*;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct QueryConfig {
13 pub embedding: Vec<f32>,
15 pub k: usize,
17 pub filter: Option<serde_json::Value>,
19 pub ef_search: Option<usize>,
21}
22
23#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum CausalConeType {
27 Past,
29 Future,
31 LightCone,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct CausalQueryConfig {
38 pub query: QueryConfig,
40 pub reference_time: f64,
42 pub cone_type: CausalConeType,
44 pub velocity: Option<f32>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(tag = "type", rename_all = "snake_case")]
51pub enum TopologicalQuery {
52 PersistentHomology {
54 dimension: usize,
55 epsilon_min: f32,
56 epsilon_max: f32,
57 },
58 BettiNumbers { max_dimension: usize },
60 SheafConsistency { section_ids: Vec<String> },
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct CausalResult {
67 pub id: String,
69 pub score: f32,
71 pub causal_distance: Option<usize>,
73 pub temporal_distance: f64,
75 pub pattern: Option<serde_json::Value>,
77}
78
79pub fn js_array_to_vec_f32(arr: &Array) -> Result<Vec<f32>, JsValue> {
81 let mut vec = Vec::with_capacity(arr.length() as usize);
82 for i in 0..arr.length() {
83 let val = arr.get(i);
84 if let Some(num) = val.as_f64() {
85 vec.push(num as f32);
86 } else {
87 return Err(JsValue::from_str(&format!(
88 "Array element at index {} is not a number",
89 i
90 )));
91 }
92 }
93 Ok(vec)
94}
95
96pub fn vec_f32_to_js_array(vec: &[f32]) -> Float32Array {
98 Float32Array::from(vec)
99}
100
101pub fn js_object_to_json(obj: &JsValue) -> Result<serde_json::Value, JsValue> {
103 serde_wasm_bindgen::from_value(obj.clone())
104 .map_err(|e| JsValue::from_str(&format!("Failed to convert to JSON: {}", e)))
105}
106
107pub fn json_to_js_object(value: &serde_json::Value) -> Result<JsValue, JsValue> {
109 serde_wasm_bindgen::to_value(value)
110 .map_err(|e| JsValue::from_str(&format!("Failed to convert from JSON: {}", e)))
111}
112
113pub fn create_js_error(message: &str, kind: &str) -> JsValue {
115 let obj = Object::new();
116 Reflect::set(&obj, &"message".into(), &message.into()).unwrap();
117 Reflect::set(&obj, &"kind".into(), &kind.into()).unwrap();
118 Reflect::set(&obj, &"name".into(), &"ExoError".into()).unwrap();
119 obj.into()
120}
121
122pub fn validate_dimensions(vec: &[f32], expected: usize) -> Result<(), JsValue> {
124 if vec.len() != expected {
125 return Err(create_js_error(
126 &format!(
127 "Dimension mismatch: expected {}, got {}",
128 expected,
129 vec.len()
130 ),
131 "DimensionError",
132 ));
133 }
134 Ok(())
135}
136
137pub fn validate_not_empty(vec: &[f32]) -> Result<(), JsValue> {
139 if vec.is_empty() {
140 return Err(create_js_error("Vector cannot be empty", "ValidationError"));
141 }
142 Ok(())
143}
144
145pub fn validate_k(k: usize) -> Result<(), JsValue> {
147 if k == 0 {
148 return Err(create_js_error(
149 "k must be greater than 0",
150 "ValidationError",
151 ));
152 }
153 Ok(())
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_causal_cone_type_serialization() {
162 let cone = CausalConeType::Past;
163 let json = serde_json::to_string(&cone).unwrap();
164 assert_eq!(json, "\"past\"");
165 }
166
167 #[test]
168 fn test_topological_query_serialization() {
169 let query = TopologicalQuery::PersistentHomology {
170 dimension: 2,
171 epsilon_min: 0.1,
172 epsilon_max: 1.0,
173 };
174 let json = serde_json::to_value(&query).unwrap();
175 assert_eq!(json["type"], "persistent_homology");
176 }
177}