npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tauri-plugin-libsql-api

v0.1.0

Published

Tauri plugin for libsql with encryption support

Readme

Tauri libsql 插件

一个用于 libsql 的 Tauri 插件,内置 AES-256-CBC 加密、Drizzle ORM 支持,以及浏览器兼容的迁移运行器。

为什么选择这个插件?

1. Rust ORM 在应用开发中很痛苦

在 Rust 中使用原始 SQL 很冗长,而 Rust ORM(Diesel、SeaORM)需要在 Rust 中定义模式,与 TypeScript 前端配合不佳,并且增加了显著的构建复杂性。对于真正的业务逻辑在 TypeScript 中的 Tauri 应用,你也希望在 TypeScript 中编写数据库代码。

2. 无需 Node.js 运行时的 Drizzle ORM

Drizzle ORM 非常出色 —— 类型安全的查询、简洁的迁移系统、出色的开发体验。但它通常需要 Node.js 或 Bun 运行时来直接打开数据库文件。Tauri 的 WebView 没有这样的运行时。

这个插件通过 Drizzle 的 sqlite-proxy 模式解决了这个问题:Drizzle 生成 SQL,代理通过 Tauri 的 invoke() 将其发送到 Rust 插件,Rust 插件使用 libsql 执行它。你的 TypeScript 代码使用完整的 Drizzle ORM,零 Node.js 依赖。

3. 在 WebView 中工作的迁移

Drizzle 内置的迁移器使用 Node 的 fs 模块在运行时从磁盘读取 .sql 文件 —— 这在浏览器/WebView 环境中不存在。有两种解决方法:

  • Tauri 资源文件夹 —— 将文件打包为应用资源,通过 Tauri 的 asset 协议读取。可以工作,但需要额外的 Tauri 配置。
  • Vite import.meta.glob (这个插件的方法) —— Vite 在构建时将 SQL 文件内容直接打包到 JavaScript 中。无需运行时文件系统访问,无需额外配置。
// Vite 在构建时解析这些 —— SQL 文本被内联到 JS 包中
const migrations = import.meta.glob<string>("./drizzle/*.sql", {
  eager: true,
  query: "?raw",
  import: "default",
});

await migrate("sqlite:myapp.db", migrations);

这个插件中的 migrate() 函数接收预加载的 SQL 字符串,在 __drizzle_migrations 表中跟踪已应用的迁移,并按顺序运行待处理的迁移。

4. 内置加密

@tauri-apps/plugin-sql(使用 sqlx)不支持加密。这个插件使用 libsql 的原生 AES-256-CBC 加密,无需额外的原生库或 FFI 包装器。


功能特性

  • 完整的 SQLite 兼容性 通过 libsql
  • 原生加密 —— AES-256-CBC,可在插件级别或每个数据库配置
  • Drizzle ORM 集成 —— sqlite-proxy 模式与 createDrizzleProxy
  • 迁移运行器 —— 浏览器安全的 migrate(),通过 Vite 在构建时打包 SQL 文件
  • API 兼容 适用于 @tauri-apps/plugin-sql(在适用的地方)
  • 跨平台:macOS、Windows、Linux、iOS、Android 已测试
    • [x] MacOS
    • [ ] Windows
    • [ ] Linux
    • [ ] iOS
    • [ ] Android

安装

Rust

[dependencies]
tauri-plugin-libsql = "0.1.0"

JavaScript / TypeScript

npm install tauri-plugin-libsql-api
# 或
pnpm add tauri-plugin-libsql-api

快速开始

1. 注册插件(Rust)

// src-tauri/src/lib.rs

// 默认:数据库相对于当前工作目录解析
tauri::Builder::default()
    .plugin(tauri_plugin_libsql::init())
    .run(tauri::generate_context!())
    .expect("error while running tauri application");

要在固定位置存储数据库:

use std::path::PathBuf;

let config = tauri_plugin_libsql::Config {
    base_path: Some(PathBuf::from("/path/to/data")),
    encryption: None,
};

tauri::Builder::default()
    .plugin(tauri_plugin_libsql::init_with_config(config))
    .run(tauri::generate_context!())
    .expect("error while running tauri application");

2. 使用 Database 类(TypeScript)

import { Database } from 'tauri-plugin-libsql-api';

const db = await Database.load('sqlite:myapp.db');

await db.execute(
  'INSERT INTO users (name) VALUES ($1)',
  ['Alice']
);

const users = await db.select<{ id: number; name: string }[]>(
  'SELECT * FROM users'
);

await db.close();

数据库位置

相对路径(例如 sqlite:myapp.db)相对于 base_path 解析:

  • 默认std::env::current_dir() —— 你启动 Tauri 进程的目录
  • 自定义:在插件配置中设置 base_path(见上文)
  • 绝对路径 按原样使用
  • 内存中sqlite::memory:

相对路径被规范化(.. 组件被折叠)并且必须保持在 base_path 内。会逸出的路径(例如 sqlite:../../secret)将被拒绝并返回错误。


Drizzle ORM 集成

设置

import { drizzle } from 'drizzle-orm/sqlite-proxy';
import { createDrizzleProxy } from 'tauri-plugin-libsql-api';
import * as schema from './schema';

const db = drizzle(createDrizzleProxy('sqlite:myapp.db'), { schema });

const users = await db.select().from(schema.users);

createDrizzleProxy 在首次使用时延迟加载数据库连接,因此使用它时无需单独调用 Database.load()

使用加密

import { createDrizzleProxyWithEncryption } from 'tauri-plugin-libsql-api';

const db = drizzle(
  createDrizzleProxyWithEncryption({
    path: 'sqlite:encrypted.db',
    encryption: {
      cipher: 'aes256cbc',
      key: myKey32Bytes, // number[] | Uint8Array, 32 字节
    },
  }),
  { schema }
);

迁移

标准的 drizzle-orm/sqlite-proxy/migrator 在运行时从文件系统读取,这在 Tauri WebView 中无法工作。这个插件提供了一个 migrate() 函数,它接受由 Vite 的 import.meta.glob 预打包的 SQL 内容。

工作流程

1. 定义你的模式 (src/lib/schema.ts):

import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';

export const users = sqliteTable('users', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  name: text('name').notNull(),
});

2. 配置 drizzle-kit (drizzle.config.ts):

import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  dialect: 'sqlite',
  schema: './src/lib/schema.ts',
  out: './drizzle',
});

3. 生成迁移文件

npx drizzle-kit generate
# 创建 drizzle/0000_init.sql, drizzle/0001_add_column.sql, 等等

4. 在启动时运行迁移

import { Database, migrate } from 'tauri-plugin-libsql-api';

// Vite 在构建时将这些 SQL 文件打包到应用中
const migrations = import.meta.glob<string>('./drizzle/*.sql', {
  eager: true,
  query: '?raw',
  import: 'default',
});

// 启动顺序:加载 → 迁移 → 查询
await Database.load('sqlite:myapp.db');
await migrate('sqlite:myapp.db', migrations);

// 现在可以安全地查询
const db = drizzle(createDrizzleProxy('sqlite:myapp.db'), { schema });

migrate() 如何工作

  • 如果不存在,创建 __drizzle_migrations 跟踪表
  • 通过数字前缀解析迁移文件名(0000_0001_ 等)
  • 仅按顺序应用待处理的迁移
  • 通过文件名记录每个已应用的迁移

添加模式更改

# 1. 编辑 src/lib/schema.ts
# 2. 生成新迁移
npx drizzle-kit generate
# 3. 新迁移在下次应用启动时自动运行

选项

await migrate('sqlite:myapp.db', migrations, {
  migrationsTable: '__my_migrations', // 默认:'__drizzle_migrations'
});

加密

插件级别加密(应用于所有数据库)

在 Rust 中配置一次 —— 前端从不处理密钥:

let config = tauri_plugin_libsql::Config {
    base_path: None,
    encryption: Some(tauri_plugin_libsql::EncryptionConfig {
        cipher: tauri_plugin_libsql::Cipher::Aes256Cbc,
        key: my_32_byte_key, // Vec<u8>, 正好 32 字节
    }),
};

每个数据库加密(从前端)

const key = new Uint8Array(32);
crypto.getRandomValues(key);

const db = await Database.load({
  path: 'sqlite:secrets.db',
  encryption: {
    cipher: 'aes256cbc',
    key: Array.from(key), // number[] 或 Uint8Array
  },
});

安全注意事项:

  • AES-256-CBC 需要正好 32 字节
  • 将密钥存储在操作系统钥匙串或安全存储中 —— 丢失密钥 = 丢失数据
  • 首选插件级别加密;它将密钥排除在 JavaScript 之外

API 参考

Database.load(pathOrOptions)

// 简单用法
const db = await Database.load('sqlite:myapp.db');

// 使用加密
const db = await Database.load({
  path: 'sqlite:myapp.db',
  encryption: { cipher: 'aes256cbc', key: myKey },
});

db.execute(query, values?)

const result = await db.execute(
  'INSERT INTO todos (title) VALUES ($1)',
  ['Buy milk']
);
// result.rowsAffected, result.lastInsertId

db.select<T>(query, values?)

const rows = await db.select<{ id: number; title: string }[]>(
  'SELECT * FROM todos WHERE completed = $1',
  [0]
);

db.batch(queries)

在单个事务中原子执行多个 SQL 语句。用于 DDL 或批量 DML。语句不能使用绑定参数($1 占位符)—— 对参数化查询使用 execute()

await db.batch([
  'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)',
  'CREATE INDEX idx_users_name ON users(name)',
]);

db.sync()

从 Turso 远程拉取最新更改到本地副本。对纯本地数据库无操作(无错误返回)。需要 replication 功能。

await db.sync();

db.close()

await db.close();

migrate(dbPath, migrationFiles, options?)

import { migrate } from 'tauri-plugin-libsql-api';

const migrations = import.meta.glob<string>('./drizzle/*.sql', {
  eager: true,
  query: '?raw',
  import: 'default',
});

await migrate('sqlite:myapp.db', migrations);

createDrizzleProxy(dbPath)

返回一个用于 drizzle() 的 sqlite-proxy 回调。延迟加载连接。

createDrizzleProxyWithEncryption(options)

同上,但带加密配置。

getConfig()

import { getConfig } from 'tauri-plugin-libsql-api';

const { encrypted } = await getConfig();

权限

添加到你的 tauri.conf.json

{
  "plugins": {
    "libsql": {}
  }
}

或配置细粒度能力:

{
  "identifier": "libsql:default",
  "permissions": [
    "libsql:allow-load",
    "libsql:allow-batch",
    "libsql:allow-execute",
    "libsql:allow-select",
    "libsql:allow-close"
  ]
}

与 @tauri-apps/plugin-sql 的比较

| 功能 | tauri-plugin-libsql | @tauri-apps/plugin-sql | |---------|---------------------|------------------------| | SQLite | ✅ libsql | ✅ sqlx | | 加密 | ✅ 内置 AES-256-CBC | ❌ | | Drizzle ORM | ✅ | ✅ | | 迁移运行器 | ✅ 浏览器安全 | ❌ | | MySQL / PostgreSQL | ❌ | ✅ | | API 兼容性 | 部分 | 完整 |


Turso / 远程数据库

该插件支持两种由 libsql 提供支持的远程连接模式。

嵌入式副本(推荐用于 Tauri)

本地 SQLite 文件与 Turso 云数据库保持同步。查询从本地文件读取(快速、离线可用),写入同步到远程。

1. 在你的应用 Cargo.toml 中启用 replication 功能

tauri-plugin-libsql = { version = "0.1.0", features = ["replication"] }

2. 使用 syncUrlauthToken 加载:

import { Database, migrate } from 'tauri-plugin-libsql-api';

const db = await Database.load({
  path: 'sqlite:local.db',           // 本地副本文件
  syncUrl: 'libsql://mydb-org.turso.io',
  authToken: 'your-turso-auth-token',
});

// 按需同步(例如在应用恢复 / 网络重连时)
await db.sync();

Database.load() 时,初始同步将最新数据从 Turso 拉取到本地文件。后续的 sync() 调用拉取增量更改。

使用 Drizzle ORM:

const migrations = import.meta.glob<string>('./drizzle/*.sql', {
  eager: true, query: '?raw', import: 'default',
});

const db = await Database.load({
  path: 'sqlite:local.db',
  syncUrl: 'libsql://mydb-org.turso.io',
  authToken: import.meta.env.VITE_TURSO_AUTH_TOKEN,
});

await migrate(db.path, migrations);

const drizzleDb = drizzle(createDrizzleProxy(db.path), { schema });

纯远程

所有查询直接在 Turso 上执行 —— 没有本地文件。每个查询都需要网络。

启用 remote 功能:

tauri-plugin-libsql = { version = "0.1.0", features = ["remote"] }
const db = await Database.load({
  path: 'libsql://mydb-org.turso.io',
  authToken: 'your-turso-auth-token',
});

对于大多数 Tauri 应用,嵌入式副本是更好的选择 —— 它离线工作,读取速度明显更快。

关于 batch() 与嵌入式副本的注意事项:在某些版本中,libsql 的 execute_batch() 不能正确地通过嵌入式副本层路由写入。该插件使用显式 BEGIN/COMMIT 事务内的单个 execute() 调用来避免这个问题。

关于 URL 验证的注意事项:libsql 的构建器在内部对同步 URL 调用 unwrap(),格式错误的值(例如前导/尾随空格、错误的协议)可能导致 panic。该插件将其包装在 catch_unwind 中,因此错误的 URL 会作为适当的错误显示,而不是无限期挂起 IPC。


包大小

基于包含的 Todo List 演示应用(macOS、aarch64、release 构建):

| 格式 | 带加密 | 不带加密 | |--------|----------------|--------------------| | .app 包 | 15 MB | 15 MB | | .dmg 安装程序 | 6.0 MB | 5.9 MB |

禁用加密基本上节省不了什么 —— 与始终存在的 SQLite 原生库相比,AES 密码代码可以忽略不计。encryption 功能标志仍然存在,以避免编译加密相关代码,如果你想在编译时强制没有数据库可以被加密。

禁用加密

加密是默认功能。要选择退出,请禁用默认功能并仅选择你需要的:

Cargo.toml(在你的 Tauri 应用中):

tauri-plugin-libsql = { version = "0.1.0", default-features = false, features = ["core"] }

可用功能:

| 功能 | 默认 | 描述 | |---------|---------|-------------| | core | ✅ | 本地 SQLite 数据库(始终需要)| | encryption | ✅ | 通过 libsql 的 AES-256-CBC 加密 | | replication | ❌ | libsql 复制支持(添加 TLS)| | remote | ❌ | 远程数据库支持(计划中,见下文)|

encryption 被禁用时,向 Database.load() 传递 EncryptionConfig 将在运行时返回错误。TypeScript API 表面保持不变 —— 无需重新构建你的 JS 代码。


使用 AI 集成此插件

仓库根目录包含一个 SKILL.md 文件。它包含有关插件架构、启动顺序、迁移工作流、加密模式和常见错误的结构化上下文 —— 专为 AI 编码助手(Claude Code、Cursor、Copilot 等)编写。

使用 Claude Code

SKILL.md 复制到你项目的 .claude/skills/tauri-plugin-libsql/ 目录:

mkdir -p .claude/skills/tauri-plugin-libsql
cp /path/to/tauri-plugin-libsql/SKILL.md .claude/skills/tauri-plugin-libsql/

Claude Code 自动发现技能。复制后,你可以自然地提示:

"使用 tauri-plugin-libsql 为我的 Tauri 应用添加一个 notes 表。包括模式、迁移和启动顺序。"

Claude 将应用正确的启动顺序,对迁移使用 import.meta.glob,并处理 drizzle 代理模式,无需额外指导。

使用其他 AI 工具

直接将 SKILL.md 的内容粘贴到你的系统提示或上下文窗口中,然后描述你想构建什么。该技能涵盖足够的上下文,让 AI 能在第一次尝试就生成正确、可工作的代码。


项目结构

tauri-plugin-libsql/
├── src/                    # Rust 插件
│   ├── lib.rs              # 插件初始化、命令注册
│   ├── commands.rs         # load、execute、select、close、ping
│   ├── wrapper.rs          # DbConnection 包装 libsql
│   ├── decode.rs           # libsql::Value → serde_json::Value
│   ├── models.rs           # Cipher、EncryptionConfig、QueryResult
│   ├── error.rs            # 错误类型
│   ├── desktop.rs          # 桌面配置 & base_path
│   └── mobile.rs           # 移动端存根
├── guest-js/               # TypeScript 源代码
│   ├── index.ts            # Database 类、getConfig、重新导出
│   ├── drizzle.ts          # createDrizzleProxy、createDrizzleProxyWithEncryption
│   └── migrate.ts          # migrate() —— 浏览器安全的迁移运行器
├── permissions/            # Tauri 权限文件
├── examples/todo-list/     # 演示:带 Drizzle + 迁移的 Todo 应用(15 MB .app / 6 MB .dmg)
├── SKILL.md                # 适用于 Claude Code 和其他助手的 AI 技能上下文
├── build.rs
├── Cargo.toml
└── package.json