在 Tauri 2 桌面应用中集成 RxDB:基于 SQLite RxStorage 的本地优先数据库实战
【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址: https://gitcode.com/gh_mirrors/rx/rxdb
本篇技术指南以 examples/tauri 示例项目为主线,完整讲解如何在一个 Tauri 2 桌面应用中接入 RxDB,并通过 SQLite RxStorage 将数据库落盘到系统本地 SQLite。你将在本指南中掌握:Tauri 项目的环境准备与启动流程、Rust 侧 SQL 插件与自定义命令的注册方式、RxDB 数据库与集合的初始化模式,以及如何借助响应式查询实现界面数据自动刷新。
一、示例概览:一个英雄列表桌面应用
该示例实现了一个简单的"英雄列表"(heroes-list)应用:用户可以在界面中输入英雄的名字(name)与颜色(color),数据被持久化到本地 SQLite 数据库中,并实时反映在列表 UI 上。整体技术栈为:
- Tauri 2 桌面框架(前端基于 Vite + 原生 HTML/JS,后端基于 Rust)
- SQLite RxStorage(RxDB 的 SQLite 存储层)
- Tauri SQL 插件(Rust 与前端之间的 SQLite 桥接)
从仓库结构看,示例包含完整的工程骨架:
- index.html:应用唯一页面,包含英雄列表容器(
#heroes-list)与添加英雄的表单 - src/main.js:前端入口,负责创建 RxDB 实例、订阅查询并绑定插入逻辑
- src/database.js:RxDB 数据库与集合的创建封装
- src-tauri:Rust 侧工程,含 lib.rs、tauri.conf.json 与权限声明 capabilities/default.json
- test/specs/example.e2e.js:基于 WebdriverIO 的端到端测试
二、环境准备与启动步骤
2.1 前置依赖
运行该示例前需要准备:
- Node.js 与 npm/yarn 环境(用于前端构建与 RxDB 安装);
- Rust 工具链(用于编译 Tauri 的 Rust 后端,示例依赖 tauri 2、tauri-plugin-sql 等 crate,见 Cargo.toml);
- Tauri CLI(项目已将其声明为 devDependency,见 package.json);
- 系统级的 Tauri 编译依赖(不同操作系统所需的 WebKitGTK / WebView2 等,属于 Tauri 官方要求)。
2.2 启动步骤
官方 README 给出的完整步骤如下(对应 examples/tauri/README.md):
- 克隆整个 RxDB 仓库(本仓库即为该仓库的镜像);
- 进入项目根目录并安装依赖:
cd rxdb && npm install; - 进入示例目录:
cd examples/tauri; - 安装本地化依赖:
npm run preinstall && npm install -D; - 启动开发模式:
npm run tauri dev。
其中preinstall脚本会在安装前执行preinstall:rxdb,其实现是:
"preinstall:rxdb": "(cd ../../ && npx yarn@1.22.22 pack ../../ --filename ./examples/tauri/rxdb-local.tgz)"这段命令将仓库根目录的 RxDB 源码打包成本地 tarballrxdb-local.tgz,随后package.json中的"rxdb": "file:rxdb-local.tgz"会基于这份本地源码包安装 RxDB。这样做保证了示例始终与仓库中的 RxDB 源码保持同步,而不是使用 npm 上可能滞后的发布版本。
提示:仓库中还提供了 reinstall.sh,它会清除
node_modules与rxdb-local.tgz,然后重新执行 preinstall 与依赖安装,适合在 RxDB 源码更新后一键重建本地环境。
2.3 Vite 与 Tauri 的端口约定
examples/tauri/vite.config.js 为 Tauri 开发做了针对性配置:
clearScreen: false:避免 Vite 清屏掩盖 Rust 编译错误;server.port: 1420, strictPort: true:Tauri 期望固定端口 1420,端口被占用时直接报错而非换端口;hmr.port: 1421:热更新走 WebSocket 端口 1421(仅在设置了TAURI_DEV_HOST时启用 HMR);server.watch.ignored: ["**/src-tauri/**"]:告诉 Vite 忽略对src-tauri目录的监听,避免 Rust 文件的变动触发前端不必要的重载。
与之对应,tauri.conf.json 的build段声明了:
"build": { "beforeDevCommand": "npm run dev", "devUrl": "http://localhost:1420", "beforeBuildCommand": "npm run build", "frontendDist": "../dist" }即开发模式先启动 Vite 开发服务器(端口 1420)再拉起 Tauri 窗口;构建模式则先执行npm run build(tsc && vite build),产物输出到../dist供 Rust 侧打包。
三、Rust 侧:SQL 插件与自定义命令
3.1 注册 SQL 插件
Tauri 应用需要在 Rust 侧注册 SQL 插件,前端才能通过@tauri-apps/plugin-sql访问 SQLite。见 src-tauri/src/lib.rs:
pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_sql::Builder::new().build()) .plugin(tauri_plugin_opener::init()) .invoke_handler(tauri::generate_handler![get_db_suffix]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }同时 Cargo.toml 中开启了 SQLite 特性:
tauri-plugin-sql = { version = "2", features = ["sqlite"] }3.2 自定义 Rust 命令:生成数据库后缀
为了让每次运行都能获得独立、全新的数据库实例,示例在 Rust 侧定义了一个命令get_db_suffix,返回当前时间的毫秒时间戳:
#[tauri::command] fn get_db_suffix() -> String { let start = SystemTime::now(); let since_the_epoch = start .duration_since(UNIX_EPOCH) .expect("Time went backwards"); format!("{}", since_the_epoch.as_millis()) }前端通过invoke("get_db_suffix", {})调用该命令(见 src/main.js),并将返回值拼接到数据库名"heroesdb" + dbSuffix上。这样每次启动都会创建带时间戳后缀的新数据库,避免了旧数据的干扰,这一设计也体现在端到端测试的多次运行场景中。
3.3 权限声明(Capabilities)
Tauri 2 引入了基于 capability 的权限模型。capabilities/default.json 为main窗口声明了:
"permissions": [ "core:default", "opener:default", "sql:default", "sql:allow-execute" ]其中sql:default与sql:allow-execute允许前端通过 SQL 插件执行 SQL 语句。缺少这些权限时,前端调用 SQL 插件会被 Tauri 安全层拒绝。
四、前端:初始化 RxDB 与 SQLite RxStorage
4.1 创建 RxDB 实例
examples/tauri/src/database.js 封装了数据库与集合的创建:
import { createRxDatabase, addRxPlugin } from "rxdb"; import { RxDBQueryBuilderPlugin } from "rxdb/plugins/query-builder"; import { RxDBDevModePlugin } from "rxdb/plugins/dev-mode"; addRxPlugin(RxDBQueryBuilderPlugin); addRxPlugin(RxDBDevModePlugin); const heroSchema = { title: "hero schema", description: "describes a simple hero", version: 0, primaryKey: "name", type: "object", properties: { name: { type: "string", maxLength: 100 }, color: { type: "string" }, }, required: ["name", "color"], }; export async function getDatabase(name, storage) { const db = await createRxDatabase({ name, storage }); await db.addCollections({ heroes: { schema: heroSchema } }); return db; }这里有两个值得注意的设计点:
getDatabase(name, storage)将存储层作为参数注入,使得数据库创建逻辑与具体存储实现解耦,测试时可轻松替换为内存存储等实现;- 注册了
RxDBQueryBuilderPlugin与RxDBDevModePlugin:前者提供链式查询构建器能力,后者在开发阶段对 schema、查询等进行运行时校验(该插件不应在生产环境使用,它只应在开发模式启用)。
4.2 SQLite RxStorage 的接入
真正的关键在 src/main.js:
import { invoke } from "@tauri-apps/api/core"; import sqlite3 from "@tauri-apps/plugin-sql"; import { getRxStorageSQLiteTrial, getSQLiteBasicsTauri, } from "rxdb/plugins/storage-sqlite"; import { wrappedValidateAjvStorage } from "rxdb/plugins/validate-ajv"; const dbSuffix = await invoke("get_db_suffix", {}); const storage = getRxStorageSQLiteTrial({ sqliteBasics: getSQLiteBasicsTauri(sqlite3), }); const db = await getDatabase( "heroesdb" + dbSuffix, wrappedValidateAjvStorage({ storage: storage }), );接入链路分为三步:
getSQLiteBasicsTauri(sqlite3):将 Tauri SQL 插件封装为 RxDB SQLite 存储所需的"SQLite 基础能力"(连接、查询、事务等);getRxStorageSQLiteTrial({ sqliteBasics }):基于上述能力构造 RxDB 的 SQLite 存储实例;wrappedValidateAjvStorage({ storage }):在其外层叠加 AJV 文档校验,确保写入数据符合 heroSchema 定义。
其中getRxStorageSQLiteTrial名称中的 "Trial" 表明这是 SQLite RxStorage 的试用版实现,正式使用时应参考 SQLite RxStorage 文档 选择合适的变体(例如非试用版或带加密的版本)。命名还揭示了 RxDB 存储层的分层思想:RxStorage 与数据校验是正交的两层,可以任意组合。
五、响应式查询:让 UI 自动跟随数据变化
5.1 订阅查询结果
示例最核心的 RxDB 能力展示是对查询结果的订阅(src/main.js):
db.heroes .find() .sort({ name: "asc" }) .$.subscribe(function (heroes) { if (!heroes) { heroesList.innerHTML = "Loading.."; return; } heroesList.innerHTML = heroes .map((hero) => { return ( "<li>" + '<div class="color-box" style="background:' + hero.color + '"></div>' + '<div class="name" name="' + hero.name + '">' + hero.name + "</div>" + "</li>" ); }) .reduce((pre, cur) => (pre += cur), ""); });db.heroes.find().sort({ name: "asc" }).$是一个可观察的(Observable)查询流:当集合内数据发生任何增删改时,RxDB 会重新执行查询并向订阅者推送最新结果,前端无需手动管理数据刷新。这正是 RxDB 响应式特性的典型用法——数据层变化自动驱动 UI 渲染。
5.2 插入数据
表单提交通过全局函数addHero完成(src/main.js):
window.addHero = async function () { const name = document.querySelector('input[name="name"]').value; const color = document.querySelector('input[name="color"]').value; const obj = { name: name, color: color }; await db.heroes.insert(obj); };页面中的按钮通过onclick="addHero();"绑定(见 index.html)。由于 schema 要求name与color均为必填字段,且name是主键,重复插入同名英雄会被主键冲突规则拦截(开发模式下 DevMode 插件还会给出更详细的校验提示)。
六、端到端测试:验证应用行为
6.1 运行测试
README 指出,运行测试前必须安装 Tauri Driver 及相关系统依赖(tauri-driver是 Tauri 官方的 WebDriver 实现,用于驱动真实窗口执行自动化测试)。
测试脚本定义在 package.json:
"test": "npm run tauri build -- --no-bundle && wdio run wdio.conf.cjs"即先构建 release 版可执行文件(不打包安装包),再以 WebdriverIO 执行测试。
6.2 测试配置解析
wdio.conf.cjs 的核心配置:
capabilities: [ { maxInstances: 1, 'tauri:options': { application: './src-tauri/target/release/tauri', }, }, ], beforeSession: () => (tauriDriver = spawn( path.resolve(os.homedir(), '.cargo', 'bin', 'tauri-driver'), [], { stdio: [null, process.stdout, process.stderr] } )), afterSession: () => tauriDriver.kill(),它在每个测试会话开始前自动拉起tauri-driver进程,会话结束后将其终止,被测应用指向 release 构建产物./src-tauri/target/release/tauri。
6.3 测试用例
test/specs/example.e2e.js 包含两个用例:
- 页面加载成功:断言页面标题文本为
RxDB Heroes Tauri; - 插入英雄:通过输入框(
#input-name、#input-color)填写Iron Man/red并点击#input-submit,然后等待列表中只出现一个名为Iron Man的元素(waitForExist超时 20 秒,并断言元素数量为 1)。
第二个用例同时验证了完整链路:前端插入 → RxDB 写入 SQLite → 响应式订阅触发 → DOM 更新出现新条目。由于每次运行都使用新的时间戳后缀数据库,测试的多次执行之间不会互相污染。
七、小结:该示例揭示的 RxDB 桌面端最佳实践
- 存储层与逻辑层分离:
getDatabase(name, storage)接受注入的 storage,使数据库代码可复用、可测试; - 适配器模式:RxDB 通过
getSQLiteBasicsTauri将 Tauri SQL 插件适配为统一的 SQLite 存储接口,因此同一套 RxDB API 可横跨浏览器、Node.js、Electron、Tauri 等不同运行时; - 响应式驱动 UI:查询订阅流(
.$.subscribe)让数据变更自动映射到界面,这是构建本地优先(local-first)应用的基石; - 开发/生产分离:DevMode 插件仅用于开发期校验,生产构建不应包含。
若想在此基础上扩展,可以结合仓库中的 SQLite RxStorage 文档 了解其实现细节与更多配置选项,或在 examples/electron、examples/vite-vanilla-ts 等示例中对比 RxDB 在其他桌面/前端场景下的接入方式。
【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址: https://gitcode.com/gh_mirrors/rx/rxdb
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考