use js_sys::{Array, Function};
use tokio::sync::broadcast::error::RecvError;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use crate::wasm::{WasmFramework, to_js_error};
#[wasm_bindgen]
impl WasmFramework {
pub async fn stats(&self) -> Result<JsValue, JsValue> {
let stats = self.framework.stats().await.map_err(to_js_error)?;
let obj = js_sys::Object::new();
js_sys::Reflect::set(
&obj,
&"concept_count".into(),
&(stats.concept_count as u32).into(),
)
.map_err(|_| JsValue::from_str("failed to set JS property"))?;
js_sys::Reflect::set(
&obj,
&"db_size_bytes".into(),
&stats
.db_size_bytes
.map_or(JsValue::NULL, |v| (v as f64).into()),
)
.map_err(|_| JsValue::from_str("failed to set JS property"))?;
Ok(obj.into())
}
pub async fn concept_count(&self) -> Result<usize, JsValue> {
let sing = self.framework.singularity.read().await;
Ok(sing.len())
}
pub async fn update_concept_metadata(
&self,
id: String,
metadata_json: String,
) -> Result<(), JsValue> {
let metadata: std::collections::HashMap<String, serde_json::Value> =
serde_json::from_str(&metadata_json)
.map_err(|e| JsValue::from_str(&format!("invalid metadata JSON: {e}")))?;
self.framework
.update_concept_metadata(&id, metadata)
.await
.map_err(to_js_error)
}
pub async fn clear_associations(&self, id: String) -> Result<(), JsValue> {
let mut sing = self.framework.singularity.write().await;
sing.clear_associations(&id).map_err(to_js_error)
}
pub async fn neighbors(&self, id: String, min_strength: f32) -> Result<Array, JsValue> {
let sing = self.framework.singularity.read().await;
let neighbors = sing.neighbors(&id, min_strength);
let array = Array::new();
for (to, strength) in neighbors {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &"to".into(), &to.into())
.map_err(|_| JsValue::from_str("failed to set JS property"))?;
js_sys::Reflect::set(&obj, &"strength".into(), &strength.into())
.map_err(|_| JsValue::from_str("failed to set JS property"))?;
array.push(&obj);
}
Ok(array)
}
pub async fn bfs(&self, start: String) -> Result<Array, JsValue> {
use crate::graph_traversal::TraversalConfig;
let sing = self.framework.singularity.read().await;
let config = TraversalConfig::default();
let results = sing.bfs(&start, &config).map_err(to_js_error)?;
let array = Array::new();
for (id, depth) in results {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &"id".into(), &id.into())
.map_err(|_| JsValue::from_str("failed to set JS property"))?;
js_sys::Reflect::set(&obj, &"depth".into(), &(depth as f64).into())
.map_err(|_| JsValue::from_str("failed to set JS property"))?;
array.push(&obj);
}
Ok(array)
}
pub async fn shortest_path(&self, from: String, to: String) -> Result<Array, JsValue> {
let path = self
.framework
.shortest_path(&from, &to)
.await
.map_err(to_js_error)?;
let array = Array::new();
if let Some(nodes) = path {
for id in nodes {
array.push(&id.into());
}
}
Ok(array)
}
pub async fn probe_filtered(
&self,
vector: &[u8],
top_k: usize,
filter_json: String,
) -> Result<Array, JsValue> {
let query = crate::hyperdim::HVec10240::from_bytes(vector).map_err(to_js_error)?;
let filter: crate::metadata_filter::MetadataFilter = serde_json::from_str(&filter_json)
.map_err(|e| JsValue::from_str(&format!("invalid filter JSON: {e}")))?;
let results = self
.framework
.probe_filtered(&query, top_k, &filter)
.await
.map_err(to_js_error)?;
let array = Array::new();
for (id, score) in results {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &"id".into(), &id.into())
.map_err(|_| JsValue::from_str("failed to set JS property"))?;
js_sys::Reflect::set(&obj, &"score".into(), &score.into())
.map_err(|_| JsValue::from_str("failed to set JS property"))?;
array.push(&obj);
}
Ok(array)
}
pub async fn traverse(
&self,
start: String,
max_depth: u32,
min_strength: f32,
) -> Result<Array, JsValue> {
let mut config = crate::graph_traversal::TraversalConfig::default();
config.max_depth = max_depth as usize;
config.min_strength = min_strength;
let results = self
.framework
.traverse(&start, config)
.await
.map_err(to_js_error)?;
let array = Array::new();
for (id, depth) in results {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &"id".into(), &id.into())
.map_err(|_| JsValue::from_str("failed to set JS property"))?;
js_sys::Reflect::set(&obj, &"depth".into(), &(depth as f64).into())
.map_err(|_| JsValue::from_str("failed to set JS property"))?;
array.push(&obj);
}
Ok(array)
}
pub fn on_event(&self, callback: Function) {
let mut receiver = self.framework.subscribe();
spawn_local(async move {
loop {
match receiver.recv().await {
Ok(event) => {
let js_event = memory_event_to_js_value(&event);
let _ = callback.call1(&JsValue::NULL, &js_event);
}
Err(RecvError::Lagged(_)) => continue,
Err(RecvError::Closed) => break,
}
}
});
}
pub async fn inject_text(&self, id: String, text: String) -> Result<(), JsValue> {
self.framework
.inject_text(&id, &text)
.await
.map_err(to_js_error)
}
pub async fn probe_text(&self, query: String, top_k: usize) -> Result<Array, JsValue> {
let results = self
.framework
.probe_text(&query, top_k)
.await
.map_err(to_js_error)?;
let array = Array::new();
for (id, similarity) in results {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &"id".into(), &id.into())
.map_err(|_| JsValue::from_str("failed to set JS property"))?;
js_sys::Reflect::set(&obj, &"similarity".into(), &similarity.into())
.map_err(|_| JsValue::from_str("failed to set JS property"))?;
array.push(&obj);
}
Ok(array)
}
}
fn memory_event_to_js_value(event: &crate::framework_events::MemoryEvent) -> JsValue {
let obj = js_sys::Object::new();
match event {
crate::framework_events::MemoryEvent::ConceptInjected { id, timestamp } => {
let _ = js_sys::Reflect::set(&obj, &"type".into(), &"ConceptInjected".into());
let _ = js_sys::Reflect::set(&obj, &"id".into(), &id.clone().into());
let _ = js_sys::Reflect::set(&obj, &"timestamp".into(), &(*timestamp as f64).into());
}
crate::framework_events::MemoryEvent::ConceptUpdated { id, timestamp } => {
let _ = js_sys::Reflect::set(&obj, &"type".into(), &"ConceptUpdated".into());
let _ = js_sys::Reflect::set(&obj, &"id".into(), &id.clone().into());
let _ = js_sys::Reflect::set(&obj, &"timestamp".into(), &(*timestamp as f64).into());
}
crate::framework_events::MemoryEvent::ConceptDeleted { id, timestamp } => {
let _ = js_sys::Reflect::set(&obj, &"type".into(), &"ConceptDeleted".into());
let _ = js_sys::Reflect::set(&obj, &"id".into(), &id.clone().into());
let _ = js_sys::Reflect::set(&obj, &"timestamp".into(), &(*timestamp as f64).into());
}
crate::framework_events::MemoryEvent::Associated { from, to, strength } => {
let _ = js_sys::Reflect::set(&obj, &"type".into(), &"Associated".into());
let _ = js_sys::Reflect::set(&obj, &"from".into(), &from.clone().into());
let _ = js_sys::Reflect::set(&obj, &"to".into(), &to.clone().into());
let _ = js_sys::Reflect::set(&obj, &"strength".into(), &(*strength as f64).into());
}
crate::framework_events::MemoryEvent::Disassociated { from, to } => {
let _ = js_sys::Reflect::set(&obj, &"type".into(), &"Disassociated".into());
let _ = js_sys::Reflect::set(&obj, &"from".into(), &from.clone().into());
let _ = js_sys::Reflect::set(&obj, &"to".into(), &to.clone().into());
}
}
obj.into()
}