webassembly/src/core/kvs
2025-04-22 13:00:10 +04:00
..
error.rs ... 2025-04-22 13:00:10 +04:00
mod.rs ... 2025-04-22 13:00:10 +04:00
README.md ... 2025-04-22 13:00:10 +04:00
store.rs ... 2025-04-22 13:00:10 +04:00

Key-Value Store (KVS) Module

This module provides a simple key-value store implementation with dual backends:

  • IndexedDB for WebAssembly applications running in browsers
  • In-memory storage for testing and non-browser environments

Overview

The KVS module provides a simple, yet powerful interface for storing and retrieving data. In a browser environment, it uses IndexedDB as the underlying storage mechanism, which provides a robust, persistent storage solution that works offline and can handle large amounts of data. In non-browser environments, it uses an in-memory store for testing purposes.

Features

  • Simple API: Easy-to-use methods for common operations like get, set, delete
  • Type Safety: Generic methods that preserve your data types through serialization/deserialization
  • Error Handling: Comprehensive error types for robust error handling
  • Async/Await: Modern async interface for all operations
  • Serialization: Automatic serialization/deserialization of complex data types

Core Components

KvsStore

The main struct that provides access to the key-value store, with different implementations based on the environment:

// In WebAssembly environments (browsers)
pub struct KvsStore {
    db: Arc<Database>,
    store_name: String,
}

// In non-WebAssembly environments (for testing)
pub struct KvsStore {
    data: Arc<Mutex<HashMap<String, String>>>,
}

Error Types

The module defines several error types to handle different failure scenarios:

pub enum KvsError {
    Idb(String),
    KeyNotFound(String),
    Serialization(String),
    Deserialization(String),
    Other(String),
}

Usage Examples

Opening a Store

let store = KvsStore::open("my_database", "my_store").await?;

Storing Values

// Store a simple string
store.set("string_key", &"Hello, world!").await?;

// Store a complex object
let user = User {
    id: 1,
    name: "John Doe".to_string(),
    email: "john@example.com".to_string(),
};
store.set("user_1", &user).await?;

Retrieving Values

// Get a string
let value: String = store.get("string_key").await?;

// Get a complex object
let user: User = store.get("user_1").await?;

Checking if a Key Exists

if store.contains("user_1").await? {
    // Key exists
}

Deleting Values

store.delete("user_1").await?;

Listing All Keys

let keys = store.keys().await?;
for key in keys {
    println!("Found key: {}", key);
}

Clearing the Store

store.clear().await?;

Error Handling

The module uses a custom Result type that wraps KvsError:

type Result<T> = std::result::Result<T, KvsError>;

Example of error handling:

match store.get::<User>("nonexistent_key").await {
    Ok(user) => {
        // Process user
    },
    Err(KvsError::KeyNotFound(key)) => {
        println!("Key not found: {}", key);
    },
    Err(e) => {
        println!("An error occurred: {}", e);
    }
}

Implementation Details

The KVS module uses:

  • Dual backend architecture:
    • IndexedDB for browser environments via the idb crate (direct Rust implementation)
    • In-memory HashMap for testing and non-browser environments
  • Conditional compilation with #[cfg(target_arch = "wasm32")] to select the appropriate implementation
  • Serde for serialization/deserialization
  • Wasm-bindgen for JavaScript interop in browser environments
  • Async/await for non-blocking operations
  • Arc and Mutex for thread-safe access to the in-memory store

Note: This implementation uses the idb crate to interact with IndexedDB directly from Rust, eliminating the need for a JavaScript bridge file.

Testing

The module includes comprehensive tests in src/tests/kvs_tests.rs that verify all functionality works as expected.

Running the Tests

Thanks to the dual implementation, tests can be run in two ways:

Standard Rust Tests

The in-memory implementation allows tests to run in a standard Rust environment without requiring a browser:

cargo test

This runs all tests using the in-memory implementation, which is perfect for CI/CD pipelines and quick development testing.

WebAssembly Tests in Browser

For testing the actual IndexedDB implementation, you can use wasm-bindgen-test to run tests in a browser environment:

  1. Install wasm-pack if you haven't already:

    cargo install wasm-pack
    
  2. Run the tests in a headless browser:

    wasm-pack test --headless --firefox
    

    You can also use Chrome or Safari:

    wasm-pack test --headless --chrome
    wasm-pack test --headless --safari
    
  3. Run tests in a browser with a UI (for debugging):

    wasm-pack test --firefox
    
  4. Run specific tests:

    wasm-pack test --firefox -- --filter kvs_tests
    

Test Structure

The tests are organized to test each functionality of the KVS module:

  1. Basic Operations: Tests for opening a store, setting/getting values
  2. Complex Data: Tests for storing and retrieving complex objects
  3. Error Handling: Tests for handling nonexistent keys and errors
  4. Management Operations: Tests for listing keys, checking existence, and clearing the store

Each test follows a pattern:

  • Set up the test environment
  • Perform the operation being tested
  • Verify the results
  • Clean up after the test

In-Memory Implementation

The module includes a built-in in-memory implementation that is automatically used in non-WebAssembly environments. This implementation:

  • Uses a HashMap<String, String> wrapped in Arc<Mutex<>> for thread safety
  • Provides the same API as the IndexedDB implementation
  • Automatically serializes/deserializes values using serde_json
  • Makes testing much easier by eliminating the need for a browser environment

This dual implementation approach means you don't need to create separate mocks for testing - the module handles this automatically through conditional compilation.