exo_wasm/
types.rs

1//! Type conversions for JavaScript interoperability
2//!
3//! This module provides type conversions between Rust and JavaScript types
4//! for seamless WASM integration.
5
6use js_sys::{Array, Float32Array, Object, Reflect};
7use serde::{Deserialize, Serialize};
8use wasm_bindgen::prelude::*;
9
10/// JavaScript-compatible query configuration
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct QueryConfig {
13    /// Query vector (will be converted from Float32Array)
14    pub embedding: Vec<f32>,
15    /// Number of results to return
16    pub k: usize,
17    /// Optional metadata filter
18    pub filter: Option<serde_json::Value>,
19    /// Optional ef_search parameter for HNSW
20    pub ef_search: Option<usize>,
21}
22
23/// Causal cone type for temporal queries
24#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum CausalConeType {
27    /// Past light cone (all events that could have influenced this point)
28    Past,
29    /// Future light cone (all events this point could influence)
30    Future,
31    /// Custom light cone with specified velocity
32    LightCone,
33}
34
35/// Causal query configuration
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct CausalQueryConfig {
38    /// Base query configuration
39    pub query: QueryConfig,
40    /// Reference timestamp (milliseconds since epoch)
41    pub reference_time: f64,
42    /// Cone type
43    pub cone_type: CausalConeType,
44    /// Optional velocity parameter for light cone queries (in ms^-1)
45    pub velocity: Option<f32>,
46}
47
48/// Topological query types for advanced substrate operations
49#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(tag = "type", rename_all = "snake_case")]
51pub enum TopologicalQuery {
52    /// Find persistent homology features
53    PersistentHomology {
54        dimension: usize,
55        epsilon_min: f32,
56        epsilon_max: f32,
57    },
58    /// Compute Betti numbers (topological invariants)
59    BettiNumbers { max_dimension: usize },
60    /// Check sheaf consistency
61    SheafConsistency { section_ids: Vec<String> },
62}
63
64/// Result from causal query
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct CausalResult {
67    /// Pattern ID
68    pub id: String,
69    /// Similarity score
70    pub score: f32,
71    /// Causal distance (number of hops in causal graph)
72    pub causal_distance: Option<usize>,
73    /// Temporal distance (milliseconds)
74    pub temporal_distance: f64,
75    /// Optional pattern data
76    pub pattern: Option<serde_json::Value>,
77}
78
79/// Convert JavaScript array to Rust Vec<f32>
80pub 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
96/// Convert Rust Vec<f32> to JavaScript Float32Array
97pub fn vec_f32_to_js_array(vec: &[f32]) -> Float32Array {
98    Float32Array::from(vec)
99}
100
101/// Convert JavaScript object to JSON value
102pub 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
107/// Convert JSON value to JavaScript object
108pub 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
113/// Helper to create JavaScript error objects
114pub 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
122/// Helper to validate vector dimensions
123pub 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
137/// Helper to validate vector is not empty
138pub 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
145/// Helper to validate k parameter
146pub 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}