use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
pub trait WasmStorage: Send + Sync {
fn name(&self) -> &str;
fn available(&self) -> bool;
fn quota(&self) -> Option<usize>;
fn used(&self) -> usize;
fn set(&mut self, key: &str, value: &[u8]) -> Result<(), StorageError>;
fn get(&self, key: &str) -> Result<Option<Vec<u8>>, StorageError>;
fn delete(&mut self, key: &str) -> Result<(), StorageError>;
fn list(&self, prefix: &str) -> Result<Vec<String>, StorageError>;
fn clear(&mut self) -> Result<(), StorageError>;
fn sync(&mut self) -> Result<(), StorageError>;
}
#[cfg(target_arch = "wasm32")]
pub trait WasmStorageAsync {
fn init(&mut self) -> impl std::future::Future<Output = Result<(), StorageError>> + Send;
fn set_async(&mut self, key: &str, value: &[u8]) -> impl std::future::Future<Output = Result<(), StorageError>> + Send;
fn get_async(&self, key: &str) -> impl std::future::Future<Output = Result<Option<Vec<u8>>, StorageError>> + Send;
fn delete_async(&mut self, key: &str) -> impl std::future::Future<Output = Result<(), StorageError>> + Send;
fn list_async(&self, prefix: &str) -> impl std::future::Future<Output = Result<Vec<String>, StorageError>> + Send;
fn clear_async(&mut self) -> impl std::future::Future<Output = Result<(), StorageError>> + Send;
fn sync_async(&mut self) -> impl std::future::Future<Output = Result<(), StorageError>> + Send;
}
#[derive(Debug, thiserror::Error)]
pub enum StorageError {
#[error("Storage not available")]
NotAvailable,
#[error("Quota exceeded")]
QuotaExceeded,
#[error("Key not found: {0}")]
KeyNotFound(String),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("IO error: {0}")]
Io(String),
#[error("JavaScript error: {0}")]
JsError(String),
}
pub struct MemoryStorage {
data: HashMap<String, Vec<u8>>,
max_size: usize,
current_size: usize,
}
impl MemoryStorage {
pub fn new(max_size_mb: usize) -> Self {
Self {
data: HashMap::new(),
max_size: max_size_mb * 1024 * 1024,
current_size: 0,
}
}
}
impl WasmStorage for MemoryStorage {
fn name(&self) -> &str {
"memory"
}
fn available(&self) -> bool {
true
}
fn quota(&self) -> Option<usize> {
Some(self.max_size)
}
fn used(&self) -> usize {
self.current_size
}
fn set(&mut self, key: &str, value: &[u8]) -> Result<(), StorageError> {
let old_size = self.data.get(key).map(|v| v.len()).unwrap_or(0);
let new_size = self.current_size - old_size + value.len();
if new_size > self.max_size {
return Err(StorageError::QuotaExceeded);
}
self.current_size = new_size;
self.data.insert(key.to_string(), value.to_vec());
Ok(())
}
fn get(&self, key: &str) -> Result<Option<Vec<u8>>, StorageError> {
Ok(self.data.get(key).cloned())
}
fn delete(&mut self, key: &str) -> Result<(), StorageError> {
if let Some(value) = self.data.remove(key) {
self.current_size -= value.len();
}
Ok(())
}
fn list(&self, prefix: &str) -> Result<Vec<String>, StorageError> {
Ok(self
.data
.keys()
.filter(|k| k.starts_with(prefix))
.cloned()
.collect())
}
fn clear(&mut self) -> Result<(), StorageError> {
self.data.clear();
self.current_size = 0;
Ok(())
}
fn sync(&mut self) -> Result<(), StorageError> {
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexedDbConfig {
pub db_name: String,
pub store_name: String,
pub metadata_store: String,
pub version: u32,
pub write_cache_enabled: bool,
pub cache_flush_threshold: usize,
pub compression_enabled: bool,
}
impl Default for IndexedDbConfig {
fn default() -> Self {
Self {
db_name: "heliosdb".to_string(),
store_name: "data".to_string(),
metadata_store: "metadata".to_string(),
version: 1,
write_cache_enabled: true,
cache_flush_threshold: 1024 * 1024, compression_enabled: true,
}
}
}
#[derive(Debug, Clone)]
enum CacheOp {
Put(Vec<u8>),
Delete,
}
pub struct IndexedDbStorage {
config: IndexedDbConfig,
read_cache: HashMap<String, Vec<u8>>,
write_cache: HashMap<String, CacheOp>,
cache_size: AtomicUsize,
initialized: AtomicBool,
deleted_keys: std::collections::HashSet<String>,
stats: IndexedDbStats,
}
#[derive(Debug, Default)]
pub struct IndexedDbStats {
pub reads: AtomicUsize,
pub writes: AtomicUsize,
pub cache_hits: AtomicUsize,
pub cache_misses: AtomicUsize,
pub bytes_written: AtomicUsize,
pub bytes_read: AtomicUsize,
pub flush_count: AtomicUsize,
}
impl IndexedDbStorage {
pub fn new(config: IndexedDbConfig) -> Self {
Self {
config,
read_cache: HashMap::new(),
write_cache: HashMap::new(),
cache_size: AtomicUsize::new(0),
initialized: AtomicBool::new(false),
deleted_keys: std::collections::HashSet::new(),
stats: IndexedDbStats::default(),
}
}
pub fn with_defaults() -> Self {
Self::new(IndexedDbConfig::default())
}
#[cfg(not(target_arch = "wasm32"))]
pub fn init(&mut self) -> Result<(), StorageError> {
self.initialized.store(true, Ordering::SeqCst);
Ok(())
}
fn needs_flush(&self) -> bool {
self.cache_size.load(Ordering::Relaxed) >= self.config.cache_flush_threshold
}
#[cfg(not(target_arch = "wasm32"))]
fn flush_cache(&mut self) -> Result<(), StorageError> {
self.write_cache.clear();
self.cache_size.store(0, Ordering::SeqCst);
self.stats.flush_count.fetch_add(1, Ordering::Relaxed);
Ok(())
}
pub fn stats(&self) -> &IndexedDbStats {
&self.stats
}
pub fn config(&self) -> &IndexedDbConfig {
&self.config
}
pub fn is_initialized(&self) -> bool {
self.initialized.load(Ordering::SeqCst)
}
pub fn export(&self) -> Result<String, StorageError> {
let data: HashMap<&String, &Vec<u8>> = self.read_cache.iter().collect();
serde_json::to_string(&data)
.map_err(|e| StorageError::Serialization(e.to_string()))
}
pub fn import(&mut self, json: &str) -> Result<usize, StorageError> {
let data: HashMap<String, Vec<u8>> = serde_json::from_str(json)
.map_err(|e| StorageError::Serialization(e.to_string()))?;
let count = data.len();
for (key, value) in data {
self.set(&key, &value)?;
}
Ok(count)
}
}
impl WasmStorage for IndexedDbStorage {
fn name(&self) -> &str {
"indexeddb"
}
fn available(&self) -> bool {
true
}
fn quota(&self) -> Option<usize> {
None
}
fn used(&self) -> usize {
self.read_cache.values().map(|v| v.len()).sum::<usize>()
+ self.write_cache.values()
.filter_map(|op| match op {
CacheOp::Put(data) => Some(data.len()),
CacheOp::Delete => None,
})
.sum::<usize>()
}
fn set(&mut self, key: &str, value: &[u8]) -> Result<(), StorageError> {
self.stats.writes.fetch_add(1, Ordering::Relaxed);
self.stats.bytes_written.fetch_add(value.len(), Ordering::Relaxed);
let old_size = self.read_cache.get(key).map(|v| v.len()).unwrap_or(0);
let size_delta = value.len() as isize - old_size as isize;
self.read_cache.insert(key.to_string(), value.to_vec());
self.deleted_keys.remove(key);
if self.config.write_cache_enabled {
self.write_cache.insert(key.to_string(), CacheOp::Put(value.to_vec()));
let current_size = self.cache_size.load(Ordering::Relaxed);
let new_size = if size_delta >= 0 {
current_size.saturating_add(size_delta as usize)
} else {
current_size.saturating_sub((-size_delta) as usize)
};
self.cache_size.store(new_size, Ordering::Relaxed);
if self.needs_flush() {
self.flush_cache()?;
}
}
Ok(())
}
fn get(&self, key: &str) -> Result<Option<Vec<u8>>, StorageError> {
self.stats.reads.fetch_add(1, Ordering::Relaxed);
if self.deleted_keys.contains(key) {
self.stats.cache_hits.fetch_add(1, Ordering::Relaxed);
return Ok(None);
}
if let Some(op) = self.write_cache.get(key) {
self.stats.cache_hits.fetch_add(1, Ordering::Relaxed);
return Ok(match op {
CacheOp::Put(data) => {
self.stats.bytes_read.fetch_add(data.len(), Ordering::Relaxed);
Some(data.clone())
}
CacheOp::Delete => None,
});
}
if let Some(data) = self.read_cache.get(key) {
self.stats.cache_hits.fetch_add(1, Ordering::Relaxed);
self.stats.bytes_read.fetch_add(data.len(), Ordering::Relaxed);
return Ok(Some(data.clone()));
}
self.stats.cache_misses.fetch_add(1, Ordering::Relaxed);
Ok(None)
}
fn delete(&mut self, key: &str) -> Result<(), StorageError> {
self.read_cache.remove(key);
self.deleted_keys.insert(key.to_string());
if self.config.write_cache_enabled {
self.write_cache.insert(key.to_string(), CacheOp::Delete);
}
Ok(())
}
fn list(&self, prefix: &str) -> Result<Vec<String>, StorageError> {
let mut keys: std::collections::HashSet<String> = self.read_cache
.keys()
.filter(|k| k.starts_with(prefix) && !self.deleted_keys.contains(*k))
.cloned()
.collect();
for (key, op) in &self.write_cache {
if key.starts_with(prefix) {
match op {
CacheOp::Put(_) => { keys.insert(key.clone()); }
CacheOp::Delete => { keys.remove(key); }
}
}
}
let mut result: Vec<String> = keys.into_iter().collect();
result.sort();
Ok(result)
}
fn clear(&mut self) -> Result<(), StorageError> {
self.read_cache.clear();
self.write_cache.clear();
self.deleted_keys.clear();
self.cache_size.store(0, Ordering::SeqCst);
Ok(())
}
fn sync(&mut self) -> Result<(), StorageError> {
self.flush_cache()
}
}
#[cfg(target_arch = "wasm32")]
mod wasm_impl {
use super::*;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{IdbDatabase, IdbObjectStore, IdbRequest, IdbTransaction};
use js_sys::{ArrayBuffer, Object, Reflect, Uint8Array};
use std::cell::RefCell;
use std::rc::Rc;
#[wasm_bindgen(inline_js = r#"
export function idb_open(name, version, store_name, metadata_store) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name, version);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create data store
if (!db.objectStoreNames.contains(store_name)) {
db.createObjectStore(store_name);
}
// Create metadata store
if (!db.objectStoreNames.contains(metadata_store)) {
db.createObjectStore(metadata_store);
}
};
});
}
export function idb_put(db, store_name, key, value) {
return new Promise((resolve, reject) => {
const tx = db.transaction(store_name, 'readwrite');
const store = tx.objectStore(store_name);
const request = store.put(value, key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
export function idb_get(db, store_name, key) {
return new Promise((resolve, reject) => {
const tx = db.transaction(store_name, 'readonly');
const store = tx.objectStore(store_name);
const request = store.get(key);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
export function idb_delete(db, store_name, key) {
return new Promise((resolve, reject) => {
const tx = db.transaction(store_name, 'readwrite');
const store = tx.objectStore(store_name);
const request = store.delete(key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
export function idb_clear(db, store_name) {
return new Promise((resolve, reject) => {
const tx = db.transaction(store_name, 'readwrite');
const store = tx.objectStore(store_name);
const request = store.clear();
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
export function idb_keys_with_prefix(db, store_name, prefix) {
return new Promise((resolve, reject) => {
const tx = db.transaction(store_name, 'readonly');
const store = tx.objectStore(store_name);
const request = store.getAllKeys();
request.onsuccess = () => {
const allKeys = request.result;
const filteredKeys = allKeys.filter(key =>
typeof key === 'string' && key.startsWith(prefix)
);
resolve(filteredKeys);
};
request.onerror = () => reject(request.error);
});
}
export function idb_batch_put(db, store_name, entries) {
return new Promise((resolve, reject) => {
const tx = db.transaction(store_name, 'readwrite');
const store = tx.objectStore(store_name);
for (const [key, value] of entries) {
store.put(value, key);
}
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
export function idb_close(db) {
db.close();
}
export function idb_available() {
return typeof indexedDB !== 'undefined';
}
"#)]
extern "C" {
#[wasm_bindgen(js_name = idb_open)]
pub async fn idb_open(
name: &str,
version: u32,
store_name: &str,
metadata_store: &str,
) -> JsValue;
#[wasm_bindgen(js_name = idb_put)]
pub async fn idb_put(db: &JsValue, store_name: &str, key: &str, value: &Uint8Array);
#[wasm_bindgen(js_name = idb_get)]
pub async fn idb_get(db: &JsValue, store_name: &str, key: &str) -> JsValue;
#[wasm_bindgen(js_name = idb_delete)]
pub async fn idb_delete(db: &JsValue, store_name: &str, key: &str);
#[wasm_bindgen(js_name = idb_clear)]
pub async fn idb_clear(db: &JsValue, store_name: &str);
#[wasm_bindgen(js_name = idb_keys_with_prefix)]
pub async fn idb_keys_with_prefix(db: &JsValue, store_name: &str, prefix: &str) -> JsValue;
#[wasm_bindgen(js_name = idb_batch_put)]
pub async fn idb_batch_put(db: &JsValue, store_name: &str, entries: &JsValue);
#[wasm_bindgen(js_name = idb_close)]
pub fn idb_close(db: &JsValue);
#[wasm_bindgen(js_name = idb_available)]
pub fn idb_available() -> bool;
}
impl IndexedDbStorage {
pub async fn init_async(&mut self) -> Result<(), StorageError> {
if !idb_available() {
return Err(StorageError::NotAvailable);
}
let db = idb_open(
&self.config.db_name,
self.config.version,
&self.config.store_name,
&self.config.metadata_store,
)
.await;
if db.is_undefined() || db.is_null() {
return Err(StorageError::JsError("Failed to open IndexedDB".to_string()));
}
self.initialized.store(true, Ordering::SeqCst);
Ok(())
}
pub async fn flush_cache_async(&mut self, db: &JsValue) -> Result<(), StorageError> {
if self.write_cache.is_empty() {
return Ok(());
}
let entries = js_sys::Array::new();
for (key, op) in &self.write_cache {
match op {
CacheOp::Put(data) => {
let pair = js_sys::Array::new();
pair.push(&JsValue::from_str(key));
pair.push(&Uint8Array::from(data.as_slice()).into());
entries.push(&pair);
}
CacheOp::Delete => {
idb_delete(db, &self.config.store_name, key).await;
}
}
}
if entries.length() > 0 {
idb_batch_put(db, &self.config.store_name, &entries).await;
}
self.write_cache.clear();
self.cache_size.store(0, Ordering::SeqCst);
self.stats.flush_count.fetch_add(1, Ordering::Relaxed);
Ok(())
}
pub async fn get_from_idb(&self, db: &JsValue, key: &str) -> Result<Option<Vec<u8>>, StorageError> {
let result = idb_get(db, &self.config.store_name, key).await;
if result.is_undefined() || result.is_null() {
return Ok(None);
}
let array = Uint8Array::new(&result);
let mut data = vec![0u8; array.length() as usize];
array.copy_to(&mut data);
Ok(Some(data))
}
pub async fn list_from_idb(&self, db: &JsValue, prefix: &str) -> Result<Vec<String>, StorageError> {
let result = idb_keys_with_prefix(db, &self.config.store_name, prefix).await;
let array = js_sys::Array::from(&result);
let mut keys = Vec::with_capacity(array.length() as usize);
for i in 0..array.length() {
if let Some(key) = array.get(i).as_string() {
keys.push(key);
}
}
keys.sort();
Ok(keys)
}
}
}
pub struct LocalStorage {
prefix: String,
max_size: usize,
}
impl LocalStorage {
pub fn new(prefix: &str) -> Self {
Self {
prefix: prefix.to_string(),
max_size: 5 * 1024 * 1024, }
}
fn prefixed_key(&self, key: &str) -> String {
format!("{}:{}", self.prefix, key)
}
}
impl WasmStorage for LocalStorage {
fn name(&self) -> &str {
"localstorage"
}
fn available(&self) -> bool {
true
}
fn quota(&self) -> Option<usize> {
Some(self.max_size)
}
fn used(&self) -> usize {
0
}
fn set(&mut self, key: &str, value: &[u8]) -> Result<(), StorageError> {
let _ = (key, value);
Ok(())
}
fn get(&self, key: &str) -> Result<Option<Vec<u8>>, StorageError> {
let _ = key;
Ok(None)
}
fn delete(&mut self, key: &str) -> Result<(), StorageError> {
let _ = key;
Ok(())
}
fn list(&self, prefix: &str) -> Result<Vec<String>, StorageError> {
let _ = prefix;
Ok(Vec::new())
}
fn clear(&mut self) -> Result<(), StorageError> {
Ok(())
}
fn sync(&mut self) -> Result<(), StorageError> {
Ok(())
}
}
pub struct OpfsStorage {
root_name: String,
}
impl OpfsStorage {
pub fn new(root_name: &str) -> Self {
Self {
root_name: root_name.to_string(),
}
}
pub async fn init(&mut self) -> Result<(), StorageError> {
Ok(())
}
}
impl WasmStorage for OpfsStorage {
fn name(&self) -> &str {
"opfs"
}
fn available(&self) -> bool {
true
}
fn quota(&self) -> Option<usize> {
None
}
fn used(&self) -> usize {
0
}
fn set(&mut self, key: &str, value: &[u8]) -> Result<(), StorageError> {
let _ = (key, value);
Ok(())
}
fn get(&self, key: &str) -> Result<Option<Vec<u8>>, StorageError> {
let _ = key;
Ok(None)
}
fn delete(&mut self, key: &str) -> Result<(), StorageError> {
let _ = key;
Ok(())
}
fn list(&self, prefix: &str) -> Result<Vec<String>, StorageError> {
let _ = prefix;
Ok(Vec::new())
}
fn clear(&mut self) -> Result<(), StorageError> {
Ok(())
}
fn sync(&mut self) -> Result<(), StorageError> {
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseState {
pub tables: HashMap<String, TableState>,
pub vector_stores: HashMap<String, VectorStoreState>,
pub branches: HashMap<String, BranchState>,
pub metadata: DatabaseMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TableState {
pub name: String,
pub columns: Vec<ColumnDef>,
pub rows: Vec<Vec<serde_json::Value>>,
pub indexes: Vec<IndexDef>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColumnDef {
pub name: String,
pub data_type: String,
pub nullable: bool,
pub default: Option<serde_json::Value>,
pub primary_key: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexDef {
pub name: String,
pub columns: Vec<String>,
pub unique: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorStoreState {
pub name: String,
pub dimensions: usize,
pub metric: String,
pub vectors: Vec<VectorEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorEntry {
pub id: String,
pub vector: Vec<f32>,
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BranchState {
pub name: String,
pub parent: Option<String>,
pub created_at: u64,
pub snapshot_key: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseMetadata {
pub version: String,
pub created_at: u64,
pub last_modified: u64,
pub settings: HashMap<String, serde_json::Value>,
}
pub fn serialize_state(state: &DatabaseState) -> Result<Vec<u8>, StorageError> {
serde_json::to_vec(state)
.map_err(|e| StorageError::Serialization(e.to_string()))
}
pub fn deserialize_state(data: &[u8]) -> Result<DatabaseState, StorageError> {
serde_json::from_slice(data)
.map_err(|e| StorageError::Serialization(e.to_string()))
}
pub fn compress(data: &[u8]) -> Vec<u8> {
data.to_vec()
}
pub fn decompress(data: &[u8]) -> Vec<u8> {
data.to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_indexeddb_config_default() {
let config = IndexedDbConfig::default();
assert_eq!(config.db_name, "heliosdb");
assert_eq!(config.store_name, "data");
assert_eq!(config.metadata_store, "metadata");
assert_eq!(config.version, 1);
assert!(config.write_cache_enabled);
assert_eq!(config.cache_flush_threshold, 1024 * 1024); assert!(config.compression_enabled);
}
#[test]
fn test_indexeddb_config_custom() {
let config = IndexedDbConfig {
db_name: "custom_db".to_string(),
store_name: "custom_store".to_string(),
metadata_store: "custom_meta".to_string(),
version: 2,
write_cache_enabled: false,
cache_flush_threshold: 512 * 1024,
compression_enabled: false,
};
assert_eq!(config.db_name, "custom_db");
assert_eq!(config.version, 2);
assert!(!config.write_cache_enabled);
assert!(!config.compression_enabled);
}
#[test]
fn test_indexeddb_config_serialization() {
let config = IndexedDbConfig::default();
let json = serde_json::to_string(&config).expect("serialize config");
let deserialized: IndexedDbConfig = serde_json::from_str(&json).expect("deserialize config");
assert_eq!(config.db_name, deserialized.db_name);
assert_eq!(config.version, deserialized.version);
}
#[test]
fn test_memory_storage_basic() {
let mut storage = MemoryStorage::new(1); assert_eq!(storage.name(), "memory");
assert!(storage.available());
assert_eq!(storage.quota(), Some(1024 * 1024));
assert_eq!(storage.used(), 0);
}
#[test]
fn test_memory_storage_set_get() {
let mut storage = MemoryStorage::new(1);
storage.set("key1", b"value1").expect("set key1");
storage.set("key2", b"value2").expect("set key2");
let val1 = storage.get("key1").expect("get key1");
assert_eq!(val1, Some(b"value1".to_vec()));
let val2 = storage.get("key2").expect("get key2");
assert_eq!(val2, Some(b"value2".to_vec()));
let val_missing = storage.get("missing").expect("get missing");
assert_eq!(val_missing, None);
}
#[test]
fn test_memory_storage_delete() {
let mut storage = MemoryStorage::new(1);
storage.set("key1", b"value1").expect("set");
assert_eq!(storage.used(), 6);
storage.delete("key1").expect("delete");
let val = storage.get("key1").expect("get");
assert_eq!(val, None);
assert_eq!(storage.used(), 0);
}
#[test]
fn test_memory_storage_list() {
let mut storage = MemoryStorage::new(1);
storage.set("users/1", b"alice").expect("set");
storage.set("users/2", b"bob").expect("set");
storage.set("orders/1", b"order1").expect("set");
let mut users = storage.list("users/").expect("list users");
users.sort();
assert_eq!(users, vec!["users/1", "users/2"]);
let orders = storage.list("orders/").expect("list orders");
assert_eq!(orders, vec!["orders/1"]);
let all = storage.list("").expect("list all");
assert_eq!(all.len(), 3);
}
#[test]
fn test_memory_storage_clear() {
let mut storage = MemoryStorage::new(1);
storage.set("key1", b"value1").expect("set");
storage.set("key2", b"value2").expect("set");
assert!(storage.used() > 0);
storage.clear().expect("clear");
assert_eq!(storage.used(), 0);
assert_eq!(storage.get("key1").expect("get"), None);
}
#[test]
fn test_memory_storage_quota_exceeded() {
let mut storage = MemoryStorage::new(1); let large_data = vec![0u8; 2 * 1024 * 1024];
let result = storage.set("large", &large_data);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), StorageError::QuotaExceeded));
}
#[test]
fn test_memory_storage_overwrite() {
let mut storage = MemoryStorage::new(1);
storage.set("key", b"short").expect("set short");
assert_eq!(storage.used(), 5);
storage.set("key", b"longer value").expect("set longer");
assert_eq!(storage.used(), 12);
let val = storage.get("key").expect("get");
assert_eq!(val, Some(b"longer value".to_vec()));
}
#[test]
fn test_memory_storage_sync_noop() {
let mut storage = MemoryStorage::new(1);
storage.set("key", b"value").expect("set");
storage.sync().expect("sync");
}
#[test]
fn test_indexeddb_storage_new() {
let storage = IndexedDbStorage::with_defaults();
assert_eq!(storage.name(), "indexeddb");
assert!(storage.available());
assert!(!storage.is_initialized());
}
#[test]
fn test_indexeddb_storage_init() {
let mut storage = IndexedDbStorage::with_defaults();
assert!(!storage.is_initialized());
storage.init().expect("init");
assert!(storage.is_initialized());
}
#[test]
fn test_indexeddb_storage_basic_ops() {
let mut storage = IndexedDbStorage::with_defaults();
storage.init().expect("init");
storage.set("key1", b"value1").expect("set");
let val = storage.get("key1").expect("get");
assert_eq!(val, Some(b"value1".to_vec()));
storage.set("key1", b"updated").expect("overwrite");
let val = storage.get("key1").expect("get updated");
assert_eq!(val, Some(b"updated".to_vec()));
let missing = storage.get("nonexistent").expect("get missing");
assert_eq!(missing, None);
}
#[test]
fn test_indexeddb_storage_delete() {
let mut storage = IndexedDbStorage::with_defaults();
storage.init().expect("init");
storage.set("key1", b"value1").expect("set");
storage.delete("key1").expect("delete");
let val = storage.get("key1").expect("get after delete");
assert_eq!(val, None);
}
#[test]
fn test_indexeddb_storage_list() {
let mut storage = IndexedDbStorage::with_defaults();
storage.init().expect("init");
storage.set("tables/users", b"schema1").expect("set");
storage.set("tables/orders", b"schema2").expect("set");
storage.set("indexes/idx1", b"index_data").expect("set");
let mut tables = storage.list("tables/").expect("list tables");
tables.sort();
assert_eq!(tables, vec!["tables/orders", "tables/users"]);
let indexes = storage.list("indexes/").expect("list indexes");
assert_eq!(indexes, vec!["indexes/idx1"]);
}
#[test]
fn test_indexeddb_storage_list_after_delete() {
let mut storage = IndexedDbStorage::with_defaults();
storage.init().expect("init");
storage.set("key1", b"value1").expect("set");
storage.set("key2", b"value2").expect("set");
storage.delete("key1").expect("delete");
let keys = storage.list("key").expect("list");
assert_eq!(keys, vec!["key2"]);
}
#[test]
fn test_indexeddb_storage_clear() {
let mut storage = IndexedDbStorage::with_defaults();
storage.init().expect("init");
storage.set("key1", b"value1").expect("set");
storage.set("key2", b"value2").expect("set");
storage.clear().expect("clear");
assert_eq!(storage.get("key1").expect("get"), None);
assert_eq!(storage.get("key2").expect("get"), None);
assert_eq!(storage.used(), 0);
}
#[test]
fn test_indexeddb_storage_statistics() {
let mut storage = IndexedDbStorage::with_defaults();
storage.init().expect("init");
storage.set("key1", b"value1").expect("set1");
storage.set("key2", b"value2").expect("set2");
let _ = storage.get("key1");
let _ = storage.get("key2");
let _ = storage.get("missing");
let stats = storage.stats();
assert_eq!(stats.writes.load(Ordering::Relaxed), 2);
assert_eq!(stats.reads.load(Ordering::Relaxed), 3);
assert!(stats.cache_hits.load(Ordering::Relaxed) >= 2);
assert_eq!(stats.cache_misses.load(Ordering::Relaxed), 1);
assert_eq!(stats.bytes_written.load(Ordering::Relaxed), 12); }
#[test]
fn test_indexeddb_storage_export_import() {
let mut storage = IndexedDbStorage::with_defaults();
storage.init().expect("init");
storage.set("key1", b"value1").expect("set");
storage.set("key2", b"value2").expect("set");
let exported = storage.export().expect("export");
assert!(exported.contains("key1"));
assert!(exported.contains("key2"));
let mut storage2 = IndexedDbStorage::with_defaults();
storage2.init().expect("init");
let count = storage2.import(&exported).expect("import");
assert_eq!(count, 2);
assert_eq!(storage2.get("key1").expect("get"), Some(b"value1".to_vec()));
assert_eq!(storage2.get("key2").expect("get"), Some(b"value2".to_vec()));
}
#[test]
fn test_indexeddb_storage_config_access() {
let config = IndexedDbConfig {
db_name: "testdb".to_string(),
..Default::default()
};
let storage = IndexedDbStorage::new(config);
assert_eq!(storage.config().db_name, "testdb");
}
#[test]
fn test_indexeddb_storage_write_cache_disabled() {
let config = IndexedDbConfig {
write_cache_enabled: false,
..Default::default()
};
let mut storage = IndexedDbStorage::new(config);
storage.init().expect("init");
storage.set("key", b"value").expect("set");
assert_eq!(storage.get("key").expect("get"), Some(b"value".to_vec()));
}
#[test]
fn test_indexeddb_storage_used_calculation() {
let mut storage = IndexedDbStorage::with_defaults();
storage.init().expect("init");
assert_eq!(storage.used(), 0);
storage.set("key1", b"value1").expect("set1"); storage.set("key2", b"12345678901234567890").expect("set2");
assert_eq!(storage.used(), 26);
}
#[test]
fn test_indexeddb_storage_sync() {
let mut storage = IndexedDbStorage::with_defaults();
storage.init().expect("init");
storage.set("key", b"value").expect("set");
storage.sync().expect("sync");
assert_eq!(storage.get("key").expect("get"), Some(b"value".to_vec()));
}
#[test]
fn test_serialize_deserialize_state() {
let mut tables = HashMap::new();
tables.insert("users".to_string(), TableState {
name: "users".to_string(),
schema: serde_json::json!({"columns": ["id", "name"]}),
row_count: 100,
});
let state = DatabaseState {
tables,
vector_stores: HashMap::new(),
branches: HashMap::new(),
current_branch: "main".to_string(),
metadata: DatabaseMetadata {
version: "1.0.0".to_string(),
created_at: 1234567890,
last_modified: 1234567890,
settings: HashMap::new(),
},
};
let serialized = serialize_state(&state).expect("serialize");
let deserialized = deserialize_state(&serialized).expect("deserialize");
assert_eq!(deserialized.current_branch, "main");
assert!(deserialized.tables.contains_key("users"));
}
#[test]
fn test_compress_decompress() {
let data = b"Hello, World! This is test data for compression.";
let compressed = compress(data);
let decompressed = decompress(&compressed);
assert_eq!(decompressed, data);
}
#[test]
fn test_storage_error_display() {
let err = StorageError::NotAvailable;
assert_eq!(err.to_string(), "Storage not available");
let err = StorageError::QuotaExceeded;
assert_eq!(err.to_string(), "Quota exceeded");
let err = StorageError::KeyNotFound("mykey".to_string());
assert_eq!(err.to_string(), "Key not found: mykey");
let err = StorageError::Serialization("parse error".to_string());
assert_eq!(err.to_string(), "Serialization error: parse error");
let err = StorageError::Io("disk full".to_string());
assert_eq!(err.to_string(), "IO error: disk full");
let err = StorageError::JsError("TypeError".to_string());
assert_eq!(err.to_string(), "JavaScript error: TypeError");
}
#[test]
fn test_table_state_serialization() {
let state = TableState {
name: "users".to_string(),
schema: serde_json::json!({
"columns": [
{"name": "id", "type": "integer"},
{"name": "name", "type": "text"}
]
}),
row_count: 42,
};
let json = serde_json::to_string(&state).expect("serialize");
let parsed: TableState = serde_json::from_str(&json).expect("deserialize");
assert_eq!(parsed.name, "users");
assert_eq!(parsed.row_count, 42);
}
#[test]
fn test_vector_store_state_serialization() {
let state = VectorStoreState {
name: "embeddings".to_string(),
dimensions: 1536,
metric: "cosine".to_string(),
vectors: vec![
VectorEntry {
id: "vec1".to_string(),
vector: vec![0.1, 0.2, 0.3],
metadata: Some(serde_json::json!({"label": "test"})),
},
],
};
let json = serde_json::to_string(&state).expect("serialize");
let parsed: VectorStoreState = serde_json::from_str(&json).expect("deserialize");
assert_eq!(parsed.dimensions, 1536);
assert_eq!(parsed.vectors.len(), 1);
}
#[test]
fn test_branch_state_serialization() {
let state = BranchState {
name: "feature-x".to_string(),
parent: Some("main".to_string()),
created_at: 1234567890,
snapshot_key: Some("snapshot_123".to_string()),
};
let json = serde_json::to_string(&state).expect("serialize");
let parsed: BranchState = serde_json::from_str(&json).expect("deserialize");
assert_eq!(parsed.name, "feature-x");
assert_eq!(parsed.parent, Some("main".to_string()));
}
#[test]
fn test_database_metadata_serialization() {
let mut settings = HashMap::new();
settings.insert("compression".to_string(), serde_json::json!(true));
let metadata = DatabaseMetadata {
version: "3.3.0".to_string(),
created_at: 1700000000,
last_modified: 1700001000,
settings,
};
let json = serde_json::to_string(&metadata).expect("serialize");
let parsed: DatabaseMetadata = serde_json::from_str(&json).expect("deserialize");
assert_eq!(parsed.version, "3.3.0");
assert!(parsed.settings.contains_key("compression"));
}
}