Skip to main content

ruvector_router_wasm/
lib.rs

1//! WASM bindings for browser and WASI environments
2
3use ruvector_router_core::{
4    DistanceMetric as CoreDistanceMetric, SearchQuery as CoreSearchQuery, VectorDB as CoreVectorDB,
5    VectorEntry as CoreVectorEntry,
6};
7use std::collections::HashMap;
8use wasm_bindgen::prelude::*;
9
10#[wasm_bindgen]
11extern "C" {
12    #[wasm_bindgen(js_namespace = console)]
13    fn log(s: &str);
14}
15
16macro_rules! console_log {
17    ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
18}
19
20#[wasm_bindgen]
21#[derive(Clone, Copy)]
22pub enum DistanceMetric {
23    Euclidean,
24    Cosine,
25    DotProduct,
26    Manhattan,
27}
28
29impl From<DistanceMetric> for CoreDistanceMetric {
30    fn from(metric: DistanceMetric) -> Self {
31        match metric {
32            DistanceMetric::Euclidean => CoreDistanceMetric::Euclidean,
33            DistanceMetric::Cosine => CoreDistanceMetric::Cosine,
34            DistanceMetric::DotProduct => CoreDistanceMetric::DotProduct,
35            DistanceMetric::Manhattan => CoreDistanceMetric::Manhattan,
36        }
37    }
38}
39
40#[wasm_bindgen]
41pub struct VectorDB {
42    db: CoreVectorDB,
43}
44
45#[wasm_bindgen]
46impl VectorDB {
47    #[wasm_bindgen(constructor)]
48    pub fn new(dimensions: usize, storage_path: Option<String>) -> Result<VectorDB, JsValue> {
49        console_log!("Initializing VectorDB with {} dimensions", dimensions);
50
51        let mut builder = CoreVectorDB::builder().dimensions(dimensions);
52
53        if let Some(path) = storage_path {
54            builder = builder.storage_path(path);
55        }
56
57        let db = builder
58            .build()
59            .map_err(|e| JsValue::from_str(&format!("Failed to create database: {}", e)))?;
60
61        Ok(VectorDB { db })
62    }
63
64    #[wasm_bindgen]
65    pub fn insert(&mut self, id: String, vector: Vec<f32>) -> Result<String, JsValue> {
66        let entry = CoreVectorEntry {
67            id: id.clone(),
68            vector,
69            metadata: HashMap::new(),
70            timestamp: 0, // WASM doesn't have chrono in no_std easily
71        };
72
73        self.db
74            .insert(entry)
75            .map_err(|e| JsValue::from_str(&format!("Insert failed: {}", e)))
76    }
77
78    #[wasm_bindgen]
79    pub fn search(&self, vector: Vec<f32>, k: usize) -> Result<JsValue, JsValue> {
80        let query = CoreSearchQuery {
81            vector,
82            k,
83            filters: None,
84            threshold: None,
85            ef_search: None,
86        };
87
88        let results = self
89            .db
90            .search(query)
91            .map_err(|e| JsValue::from_str(&format!("Search failed: {}", e)))?;
92
93        // Convert results to JS value
94        let js_results: Vec<JsValue> = results
95            .into_iter()
96            .map(|r| {
97                let obj = js_sys::Object::new();
98                js_sys::Reflect::set(&obj, &"id".into(), &r.id.into()).ok();
99                js_sys::Reflect::set(&obj, &"score".into(), &r.score.into()).ok();
100                obj.into()
101            })
102            .collect();
103
104        Ok(js_sys::Array::from_iter(js_results).into())
105    }
106
107    #[wasm_bindgen]
108    pub fn delete(&mut self, id: String) -> Result<bool, JsValue> {
109        self.db
110            .delete(&id)
111            .map_err(|e| JsValue::from_str(&format!("Delete failed: {}", e)))
112    }
113
114    #[wasm_bindgen]
115    pub fn count(&self) -> Result<usize, JsValue> {
116        self.db
117            .count()
118            .map_err(|e| JsValue::from_str(&format!("Count failed: {}", e)))
119    }
120}
121
122#[wasm_bindgen(start)]
123pub fn start() {
124    console_log!("Ruvector WASM module loaded");
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use wasm_bindgen_test::*;
131
132    #[wasm_bindgen_test]
133    fn test_vector_db_creation() {
134        let db = VectorDB::new(3, None);
135        assert!(db.is_ok());
136    }
137}