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_success
和on_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
}
注意事项
- IndexedDB操作是异步的,需要使用
.await
等待操作完成 - 事务在JavaScript中会自动提交,但在Rust中需要显式等待事务完成
- 数据库操作可能会失败,需要适当处理错误
- 在Web Worker中也可以使用IndexedDB
适用场景
- 需要离线功能的Web应用
- 需要在客户端存储大量结构化数据的应用
- 需要快速本地查询而不依赖服务器的应用
- 渐进式Web应用(PWA)的数据存储
idb
库为Rust开发者提供了访问浏览器IndexedDB的强大工具,使得在Web应用中实现复杂的本地数据存储和查询变得更加简单和安全。