#![cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
use crate::engine::Db;
use crate::analytics;
use crate::handlers;
use serde_json::{Value, json};
const COMPACT_AFTER_WRITES: u32 = 500; const COMPACT_SIZE_BYTES: u64 = 5 * 1024 * 1024;
#[wasm_bindgen]
pub struct WorkerDb {
db: Db,
write_count: std::cell::Cell<u32>,
}
#[wasm_bindgen]
impl WorkerDb {
#[wasm_bindgen(constructor)]
pub async fn new(db_name: &str) -> Result<WorkerDb, JsValue> {
console_error_panic_hook::set_once();
let db = Db::open_wasm(db_name)
.await
.map_err(|e| JsValue::from_str(&format!("Failed to open database: {}", e)))?;
web_sys::console::log_1(&JsValue::from_str("✅ MoltenDB initialized in worker"));
Ok(WorkerDb { db, write_count: std::cell::Cell::new(0) })
}
#[wasm_bindgen]
pub fn handle_message(&self, data: JsValue) -> Result<JsValue, JsValue> {
let request: serde_json::Value = serde_wasm_bindgen::from_value(data)
.map_err(|e| JsValue::from_str(&format!("Invalid request: {}", e)))?;
let action = request["action"]
.as_str()
.ok_or_else(|| JsValue::from_str("Missing action field"))?;
let payload = {
let mut p = request.clone();
if let Some(obj) = p.as_object_mut() {
obj.remove("action");
}
p
};
let result = match action {
"get" => self.handle_get(&payload),
"set" => self.handle_set(&payload),
"update" => self.handle_update(&payload),
"delete" => self.handle_delete(&payload),
"compact" => self.handle_compact(),
"get_size" => self.handle_get_size(),
_ => Err(JsValue::from_str(&format!("Unknown action: {}", action))),
}?;
Ok(result)
}
fn handle_get_size(&self) -> Result<JsValue, JsValue> {
let size = self.db.storage.get_size()
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(serde_wasm_bindgen::to_value(&serde_json::json!({ "size": size }))?)
}
fn handle_compact(&self) -> Result<JsValue, JsValue> {
self.db
.compact()
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(serde_wasm_bindgen::to_value(&serde_json::json!({"status": "compacted"}))?)
}
fn handle_get(&self, request: &Value) -> Result<JsValue, JsValue> {
let (code, mut body) = handlers::process_get::process_get(&self.db, request, 5 * 1024 * 1024);
if let Some(obj) = body.as_object_mut() { obj.insert("statusCode".into(), serde_json::json!(code)); }
if code >= 400 { return Err(serde_wasm_bindgen::to_value(&body).map_err(|e| JsValue::from_str(&e.to_string()))?); }
serde_wasm_bindgen::to_value(&body).map_err(|e| JsValue::from_str(&e.to_string()))
}
fn handle_set(&self, request: &Value) -> Result<JsValue, JsValue> {
let (code, mut body) = handlers::process_set::process_set(&self.db, request, 5 * 1024 * 1024);
if let Some(obj) = body.as_object_mut() { obj.insert("statusCode".into(), serde_json::json!(code)); }
if code >= 400 { return Err(serde_wasm_bindgen::to_value(&body).map_err(|e| JsValue::from_str(&e.to_string()))?); }
self.maybe_compact();
serde_wasm_bindgen::to_value(&body).map_err(|e| JsValue::from_str(&e.to_string()))
}
fn handle_update(&self, request: &Value) -> Result<JsValue, JsValue> {
let (code, mut body) = handlers::process_update::process_update(&self.db, request, 5 * 1024 * 1024);
if let Some(obj) = body.as_object_mut() { obj.insert("statusCode".into(), serde_json::json!(code)); }
if code >= 400 { return Err(serde_wasm_bindgen::to_value(&body).map_err(|e| JsValue::from_str(&e.to_string()))?); }
self.maybe_compact();
serde_wasm_bindgen::to_value(&body).map_err(|e| JsValue::from_str(&e.to_string()))
}
fn handle_delete(&self, request: &Value) -> Result<JsValue, JsValue> {
let (code, mut body) = handlers::process_delete::process_delete(&self.db, request, 5 * 1024 * 1024);
if let Some(obj) = body.as_object_mut() { obj.insert("statusCode".into(), serde_json::json!(code)); }
if code >= 400 { return Err(serde_wasm_bindgen::to_value(&body).map_err(|e| JsValue::from_str(&e.to_string()))?); }
self.maybe_compact();
serde_wasm_bindgen::to_value(&body).map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen(js_name = analytics)]
pub fn analytics(&self, query_json: &str) -> String {
let query: analytics::AnalyticsQuery = match serde_json::from_str(query_json) {
Ok(q) => q,
Err(e) => return json!({ "error": format!("Invalid query: {}", e) }).to_string(),
};
let result = analytics::execute_query(&self.db, &query);
json!({
"result": result.result,
"metadata": {
"execution_time_ms": result.metadata.execution_time_ms,
"rows_scanned": result.metadata.rows_scanned
}
}).to_string()
}
fn maybe_compact(&self) {
let count = self.write_count.get() + 1;
self.write_count.set(count);
let count_triggered = count >= COMPACT_AFTER_WRITES;
let size_triggered = if !count_triggered {
self.db.storage.get_size()
.map(|s| s >= COMPACT_SIZE_BYTES)
.unwrap_or(false)
} else {
false
};
if count_triggered || size_triggered {
let reason = if count_triggered {
format!("write count reached {}", count)
} else {
"file size exceeded 5 MB".to_string()
};
web_sys::console::log_1(&JsValue::from_str(
&format!("🗜️ MoltenDB: auto-compaction triggered ({})", reason)
));
match self.db.compact() {
Ok(()) => {
self.write_count.set(0);
web_sys::console::log_1(&JsValue::from_str(
"✅ MoltenDB: auto-compaction complete"
));
}
Err(e) => {
web_sys::console::error_1(&JsValue::from_str(
&format!("❌ MoltenDB: auto-compaction failed: {}", e)
));
}
}
}
}
#[wasm_bindgen]
pub fn subscribe(&self, callback: js_sys::Function) {
let mut rx = self.db.subscribe();
wasm_bindgen_futures::spawn_local(async move {
while let Ok(msg) = rx.recv().await {
let _ = callback.call1(&wasm_bindgen::JsValue::NULL, &wasm_bindgen::JsValue::from_str(&msg));
}
});
}
}