Rust浏览器数据库操作库idb的使用:Web应用本地存储与IndexedDB交互的Rust解决方案

Rust浏览器数据库操作库idb的使用:Web应用本地存储与IndexedDB交互的Rust解决方案

idb是一个基于futures的crate,用于在浏览器中使用webassembly与IndexedDB进行交互。

使用方法

要使用idb,需要在项目根目录运行以下命令:

cargo add idb

如果不想使用async/await语法,可以禁用futures功能:

cargo add idb --no-default-features

禁用futures功能后,可以使用请求上的on_successon_error方法附加回调。

示例

创建新数据库可以使用Factory::open

use idb::{Database, DatabaseEvent, Error, Factory, IndexParams, KeyPath, ObjectStoreParams};

async fn create_database() -> Result<Database, Error> {
    // 从全局范围获取工厂实例
    let factory = Factory::new()?;

    // 为数据库创建打开请求
    let mut open_request = factory.open("test", Some(1)).unwrap();

    // 为数据库添加升级处理程序
    open_request.on_upgrade_needed(|event| {
        // 从事件中获取数据库实例
        let database = event.database().unwrap();

        // 准备对象存储参数
        let mut store_params = ObjectStoreParams::new();
        store_params.auto_increment(true);
        store_params.key_path(Some(KeyPath::new_single("id")));

        // 创建对象存储
        let store = database
            .create_object_store("employees", store_params)
            .unwrap();

        // 准备索引参数
        let mut index_params = IndexParams::new();
        index_params.unique(true);

        // 在对象存储上创建索引
        store
            .create_index("email", KeyPath::new_single("email"), Some(index_params))
            .unwrap();
    });

    // `await`打开请求
    open_request.await
}

向对象存储添加数据可以使用ObjectStore::add

use idb::{Database, Error, TransactionMode};
use serde::Serialize;
use serde_wasm_bindgen::Serializer;
use wasm_bindgen::JsValue;

async fn add_data(database: &Database) -> Result<JsValue, Error> {
    // 创建读写事务
    let transaction = database.transaction(&["employees"], TransactionMode::ReadWrite)?;

    // 获取对象存储
    let store = transaction.object_store("employees").unwrap();

    // 准备要添加的数据
    let employee = serde_json::json!({
        "name": "John Doe",
        "email": "john@example.com",
    });

    // 向对象存储添加数据
    let id = store
        .add(
            &employee.serialize(&Serializer::json_compatible()).unwrap(),
            None,
        )
        .unwrap()
        .await?;

    // 提交事务
    transaction.commit()?.await?;

    Ok(id)
}

从对象存储获取数据可以使用ObjectStore::get

use idb::{Database, Error, TransactionMode};
use serde_json::Value;
use wasm_bindgen::JsValue;

async fn get_data(database: &Database, id: JsValue) -

1 回复

Rust浏览器数据库操作库idb的使用:Web应用本地存储与IndexedDB交互的Rust解决方案

介绍

idb是一个Rust库,用于在Web应用中与浏览器的IndexedDB数据库进行交互。它提供了类型安全的API来操作浏览器内置的本地数据库系统,适合需要在客户端存储大量结构化数据的Web应用。

IndexedDB是一种底层API,用于在客户端存储大量结构化数据,而idb库为Rust开发者提供了更符合Rust习惯的方式来使用这个功能。

主要特性

  • 类型安全的IndexedDB操作
  • 符合Rust习惯的异步API(基于Future
  • 事务支持
  • 索引和游标操作
  • 符合Rust错误处理习惯

使用方法

添加依赖

首先在Cargo.toml中添加依赖:

[dependencies]
idb = "0.10"
wasm-bindgen = "0.2"

基本使用示例

use idb::{Database, Factory, ObjectStoreParams, TransactionMode};
use wasm_bindgen::prelude::*;

#[wasm_bindgen(start)]
pub async fn run() -> Result<(), JsValue> {
    // 打开或创建数据库
    let factory = Factory::new()?;
    let mut db_open_request = factory.open("my_database", Some(1))?;
    
    // 设置数据库升级时的回调(第一次创建或版本升级时调用)
    db_open_request.on_upgrade_needed(|event| {
        let db = event.database();
        
        // 创建对象存储(类似于SQL中的表)
        let store_params = ObjectStoreParams::new()
            .key_path(Some(&JsValue::from_str("id")))
            .auto_increment(true);
        
        db.create_object_store("customers", store_params)?;
        
        Ok(())
    });
    
    // 等待数据库打开完成
    let db = db_open_request.await?;
    
    // 开始事务
    let transaction = db.transaction(&["customers"], TransactionMode::ReadWrite)?;
    let store = transaction.object_store("customers")?;
    
    // 添加数据
    let customer = js_sys::Object::new();
    js_sys::Reflect::set(&customer, &"name".into(), &"John Doe".into())?;
    js_sys::Reflect::set(&customer, &"email".into(), &"john@example.com".into())?;
    store.add(&customer, None)?;
    
    // 提交事务
    transaction.await?;
    
    // 查询数据
    let transaction = db.transaction(&["customers"], TransactionMode::ReadOnly)?;
    let store = transaction.object_store("customers")?;
    let all_customers: Vec<JsValue> = store.get_all(None, None)?.await?;
    
    for customer in all_customers {
        web_sys::console::log_1(&customer);
    }
    
    Ok(())
}

更复杂的示例:使用索引和游标

use idb::{Database, Factory, IndexParams, ObjectStoreParams, TransactionMode};
use wasm_b bindgen::prelude::*;

async fn setup_database() -> Result<Database, JsValue> {
    let factory = Factory::new()?;
    let mut db_open_request = factory.open("products_db", Some(2))?;
    
    db_open_request.on_upgrade_needed(|event| {
        let db = event.database();
        
        // 创建对象存储
        let store_params = ObjectStoreParams::new()
            .key_path(Some(&JsValue::from_str("sku")))
            .auto_increment(false);
        
        let store = db.create_object_store("products", store_params)?;
        
        // 创建索引
        let index_params = IndexParams::new()
            .unique(false);
        
        store.create_index("price", "price", Some(index_params))?;
        
        Ok(())
    });
    
    db_open_request.await
}

async fn add_products(db: &Database) -> Result<(), JsValue> {
    let transaction = db.transaction(&["products"], TransactionMode::ReadWrite)?;
    let store = transaction.object_store("products")?;
    
    let products = vec![
        json!({"sku": "001", "name": "Laptop", "price": 999.99}),
        json!({"sku": "002", "name": "Mouse", "price": 19.99}),
        json!({"sku": "003", "name": "Keyboard", "price": 49.99}),
    ];
    
    for product in products {
        store.add(&product, None)?;
    }
    
    transaction.await
}

async fn query_expensive_products(db: &Database, min_price: f64) -> Result<(), JsValue> {
    let transaction = db.transaction(&["products"], TransactionMode::ReadOnly)?;
    let store = transaction.object_store("products")?;
    let index = store.index("price")?;
    
    // 使用游标遍历价格高于min_price的产品
    let range = idb::KeyRange::lower_bound(&JsValue::from_f64(min_price), false)?;
    let mut cursor_request = index.open_cursor(Some(&range, None)?;
    
    while let Some(cursor) = cursor_request.await? {
        let value = cursor.value()?;
        web_sys::console::log_1(&value);
        cursor.continue_()?;
    }
    
    Ok(())
}

完整示例demo

下面是一个完整的PWA应用示例,展示了如何使用idb进行数据存储和管理:

use idb::{Database, Factory, IndexParams, ObjectStoreParams, TransactionMode};
use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};
use js_sys::{Object, JSON};

// 定义产品结构体
#[derive(Serialize, Deserialize)]
struct Product {
    sku: String,
    name: String,
    price: f64,
    stock: u32,
}

#[wasm_bindgen(start)]
pub async fn main() -> Result<(), JsValue> {
    // 初始化数据库
    let db = init_database().await?;
    
    // 添加示例产品
    let products = vec![
        Product { sku: "P100".to_string(), name: "智能手机".to_string(), price: 599.99, stock: 100 },
        Product { sku: "P101".to_string(), name: "平板电脑".to_string(), price: 399.99, stock: 50 },
        Product { sku: "P102".to_string(), name: "智能手表".to_string(), price: 199.99, stock: 75 },
    ];
    
    add_products(&db, &products).await?;
    
    // 查询价格高于300的产品
    query_products_above_price(&db, 300.0).await?;
    
    // 更新产品库存
    update_product_stock(&db, "P100", 80).await?;
    
    Ok(())
}

async fn init_database() -> Result<Database, JsValue> {
    let factory = Factory::new()?;
    let mut db_open_request = factory.open("inventory_db", Some(3))?;
    
    db_open_request.on_upgrade_needed(|event| {
        let db = event.database();
        
        // 创建产品对象存储
        let store_params = ObjectStoreParams::new()
            .key_path(Some(&JsValue::from_str("sku")))
            .auto_increment(false);
        
        let store = db.create_object_store("products", store_params)?;
        
        // 创建价格索引
        let price_index = IndexParams::new()
            .unique(false);
        store.create_index("price", "price", Some(price_index))?;
        
        // 创建库存索引
        let stock_index = IndexParams::new()
            .unique(false);
        store.create_index("stock", "stock", Some(stock_index))?;
        
        Ok(())
    });
    
    db_open_request.await
}

async fn add_products(db: &Database, products: &[Product]) -> Result<(), JsValue> {
    let transaction = db.transaction(&["products"], TransactionMode::ReadWrite)?;
    let store = transaction.object_store("products")?;
    
    for product in products {
        let js_product = Object::new();
        js_sys::Reflect::set(&js_product, &"sku".into(), &product.sku.into())?;
        js_sys::Reflect::set(&js_product, &"name".into(), &product.name.into())?;
        js_sys::Reflect::set(&js_product, &"price".into(), &product.price.into())?;
        js_sys::Reflect::set(&js_product, &"stock".into(), &product.stock.into())?;
        
        store.add(&js_product, None)?;
    }
    
    transaction.await
}

async fn query_products_above_price(db: &Database, min_price: f64) -> Result<(), JsValue> {
    let transaction = db.transaction(&["products"], TransactionMode::ReadOnly)?;
    let store = transaction.object_store("products")?;
    let index = store.index("price")?;
    
    // 创建价格范围查询
    let range = idb::KeyRange::lower_bound(&JsValue::from_f64(min_price), false)?;
    let mut cursor_request = index.open_cursor(Some(&range), None)?;
    
    while let Some(cursor) = cursor_request.await? {
        let value = cursor.value()?;
        let product_str = JSON::stringify(&value)?.as_string().unwrap();
        web_sys::console::log_1(&product_str.into());
        cursor.continue_()?;
    }
    
    Ok(())
}

async fn update_product_stock(db: &Database, sku: &str, new_stock: u32) -> Result<(), JsValue> {
    let transaction = db.transaction(&["products"], TransactionMode::ReadWrite)?;
    let store = transaction.object_store("products")?;
    
    // 获取产品
    let product: JsValue = store.get(&JsValue::from_str(sku))?.await?;
    
    if !product.is_undefined() {
        // 更新库存
        js_sys::Reflect::set(&product, &"stock".into(), &new_stock.into())?;
        store.put(&product, None)?;
    }
    
    transaction.await
}

注意事项

  1. IndexedDB操作是异步的,需要使用.await等待操作完成
  2. 事务在JavaScript中会自动提交,但在Rust中需要显式等待事务完成
  3. 数据库操作可能会失败,需要适当处理错误
  4. 在Web Worker中也可以使用IndexedDB

适用场景

  • 需要离线功能的Web应用
  • 需要在客户端存储大量结构化数据的应用
  • 需要快速本地查询而不依赖服务器的应用
  • 渐进式Web应用(PWA)的数据存储

idb库为Rust开发者提供了访问浏览器IndexedDB的强大工具,使得在Web应用中实现复杂的本地数据存储和查询变得更加简单和安全。

回到顶部