Communitygithub.com

Turso / libSQL 模式

采用 libSQL 驱动和嵌入式副本的边缘托管 SQLite 数据库。

libSQL 模式 是什么?

该技能提供了使用 Turso(一种边缘托管的 libSQL 数据库服务)的全面模式和实践。它详细介绍了如何通过 `@libsql/client` 驱动进行客户端设置、查询执行、批处理操作以及交互式事务。核心哲学是优先使用 `batch()` 而非交互式 `transaction()`,因为前者能在单次往返中原子化执行多条语句,效率更高且更安全。该技能深入解释了嵌入式副本模式,该模式通过本地 SQLite 文件同步远程主数据库,实现微秒级读取延迟,但写入仍会转发到远程主数据库(延迟 15-50 毫秒)。它还涵盖了不同运行时(Node.js 与无文件系统访问的边缘运行时)的导入路径差异,并提供了清晰的决策框架,帮助开发者在 `batch()` 与 `transaction()`、嵌入式副本与纯远程连接以及事务模式(write/read/deferred)之间做出正确选择。针对常见错误给出了明确的警告。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agents-inc/skills/tree/HEAD/src/skills/api-baas-turso

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

libSQL 模式 是做什么的?

快速指南: 对于所有 Turso 数据库访问,请使用 @libsql/client。对于单个查询使用 execute(),对于原子化的多语句操作使用 batch()(优于交互式事务),仅当后续查询依赖于先前结果时才使用 transaction()。对于没有文件系统访问权限的边缘/无服务器运行时,请从 @libsql/client/web 导入。为了实现零延迟读取,请使用本地文件 URL 和 syncUrl 配置嵌入式副本。所有写入都会转发到主数据库——设计时需考虑 15-50ms 的写入延迟。Turso 底层是 SQLite:单写入者模型,不支持 ALTER TABLE ... ADD CONSTRAINT,没有存储过程。


<critical_requirements>

关键:使用此技能前必须了解

所有代码必须遵循 CLAUDE.md 中的项目约定 (kebab-case、具名导出、导入顺序、import type、具名常量)

(对于多语句原子操作,你必须使用带有事务模式的 batch()——它比交互式 transaction() 更快、更安全,因为它在单次往返中执行)

(在缺乏文件系统访问的边缘/无服务器运行时(Cloudflare Workers、Vercel Edge Functions)中,你必须从 @libsql/client/web 导入——基础的 @libsql/client 导入会拉取原生绑定,这些绑定在这些环境中会失败)

(你必须将事务模式("write"、"read" 或 "deferred")作为第二个参数传递给 batch() 和 transaction()——默认为 "deferred",这会在尝试 INSERT/UPDATE/DELETE 时静默地无法获取写入锁)

(当客户端在短生命周期的进程中不再需要时,你必须调用 client.close()——打开的客户端会持有连接和文件句柄)

(当客户端正在运行时,你绝对不能直接访问本地嵌入式副本数据库文件——并发访问会导致数据损坏)

</critical_requirements>


自动检测: Turso、libSQL、@libsql/client、createClient、turso.io、嵌入式副本、syncUrl、syncInterval、turso db、turso group、libsql、.turso.io、TURSO_DATABASE_URL、TURSO_AUTH_TOKEN

使用时机:

  • 从任何运行时(Node.js、边缘、无服务器)查询 Turso 托管的 SQLite 数据库
  • 设置嵌入式副本,以实现从远程主数据库同步的零延迟本地读取
  • 每个租户一个数据库的多租户 SaaS(Turso 支持数百万个数据库)
  • 需要数据库但无需处理连接池复杂性的无服务器/边缘函数
  • 使用 batch() 或交互式 transaction() 运行原子化的多语句操作
  • 通过 Turso CLI 管理数据库组和多区域部署

不宜使用的时机:

  • 需要强多写入者一致性的写入密集型工作负载(Turso 是单写入者,写入转发到主数据库)
  • 需要 PostgreSQL 功能的复杂关系查询(带有修改子查询的 CTE、存储过程、高级约束)
  • 跨多个数据库的复杂分布式事务
  • 大型分析数据集(受 SQLite 行大小和并发限制)

详细资源:


理念

Turso 通过托管 libSQL(SQLite 的一个分支)作为具有多区域复制功能的托管服务,将 SQLite 带到边缘。@libsql/client 驱动提供了一个统一的 API,无论您连接的是远程 Turso 数据库、本地 SQLite 文件、内存数据库还是从远程主数据库同步的嵌入式副本,其工作方式都相同。

核心原则:

  1. batch 优于 transaction -- batch() 在单次往返中发送所有语句,并在隐式事务中执行它们。交互式 transaction() 需要多次往返并持有数据库锁(超时时间 5 秒)。除非您需要在查询之间进行条件逻辑判断,否则请使用 batch()。
  2. 写入始终命中主数据库 -- 即使使用嵌入式副本,写入也会转发到远程主数据库。写入延迟取决于到主数据库区域的距离,为 15-50ms。设计时应考虑这一点:乐观 UI、后台同步、避免写入密集型的热路径。
  3. 读取使用嵌入式副本 -- 从远程主数据库同步的本地 SQLite 文件。读取是微秒级的。写入转发到远程。成功写入后本地文件会更新(读你所写语义)。
  4. 两个导入路径 -- @libsql/client 包含用于 Node.js 的原生 SQLite 绑定,并支持 file: URL。@libsql/client/web 是纯 JS/WASM,用于边缘运行时(Cloudflare Workers、Vercel Edge Functions),无法打开本地文件。
  5. SQLite 语义 -- Turso 就是 SQLite。不支持 ADD CONSTRAINT、没有存储过程、没有 LISTEN/NOTIFY、单写入者 WAL 模式。在选择 Turso 之前,请了解 SQLite 的限制。

核心模式

模式 1:客户端设置

使用 createClient() 创建客户端。url 决定了连接类型:libsql:// 用于远程 Turso,file: 用于本地 SQLite(仅限 Node.js),:memory: 用于内存数据库(测试用)。始终对 authToken 使用环境变量——切勿硬编码凭据。

有关完整的设置模式,包括单例模块和错误示例,请参阅 examples/core.md。


模式 2:执行查询

execute() 运行单个 SQL 语句。始终使用带 args 的参数化查询——切勿使用字符串插值。

// 位置参数:args 为数组
await client.execute({
  sql: "SELECT * FROM users WHERE id = ?",
  args: [userId],
});

// 命名参数:args 为对象(裸名称匹配 SQL 中的 :name、@name、$name)
await client.execute({
  sql: "INSERT INTO users (name, email) VALUES (:name, :email)",
  args: { name, email },
});

返回包含 rows (Array<Row>)、columns、rowsAffected、lastInsertRowid (bigint) 的 ResultSet。有关类型化结果映射和错误示例,请参阅 examples/core.md。


模式 3:批处理操作

batch() 在单次往返中原子化地执行多条语句。全部成功或全部回滚。始终将事务模式指定为第二个参数。

const results = await client.batch(
  [
    { sql: "INSERT INTO users (name) VALUES (?)", args: ["Alice"] },
    {
      sql: "INSERT INTO audit_log (action, entity_id) VALUES (?, last_insert_rowid())",
      args: ["user_created"],
    },
  ],
  "write", // 对于 INSERT/UPDATE/DELETE 是必需的
);

对于变更操作使用 "write",对于仅 SELECT 操作使用 "read"(允许并行执行),使用 "deferred" 以只读模式开始并在需要时升级。last_insert_rowid() 在同一个批处理中的语句之间有效。有关多行插入和只读批处理模式,请参阅 examples/core.md;有关事务模式比较表,请参阅 reference.md。


模式 4:交互式事务

仅当后续查询依赖于前面查询的结果时,才使用 transaction()。它会持有数据库锁(空闲超时时间 5 秒)并需要多次往返。始终使用 try/catch/finally 和 close()。

const tx = await client.transaction("write");
try {
  const { rows } = await tx.execute({
    sql: "SELECT balance FROM accounts WHERE id = ?",
    args: [fromId],
  });
  // ... 基于结果的条件逻辑 ...
  await tx.commit();
} catch (error) {
  await tx.rollback();
  throw error;
} finally {
  tx.close();
}

如果所有语句都是预先确定的,没有条件逻辑,请改用 batch()。有关完整的带库存检查的购买示例,请参阅 examples/core.md。


模式 5:不同运行时的导入路径

// Node.js、Bun、Deno(具有文件系统访问权限、原生绑定)
import { createClient } from "@libsql/client";

// 没有文件系统的边缘/无服务器运行时(Cloudflare Workers、Vercel Edge)
import { createClient } from "@libsql/client/web";

基础的 @libsql/client 捆绑了在边缘运行时中会失败的原生 SQLite 绑定。@libsql/client/web 是纯 JS/WASM,但无法打开本地 file: URL。有关完整的 Cloudflare Worker 示例,请参阅 examples/core.md。


模式 6:嵌入式副本

从远程 Turso 主数据库同步的本地 SQLite 文件。读取是本地的(微秒级),写入转发到远程(15-50ms)。

const client = createClient({
  url: "file:local-replica.db",
  syncUrl: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
  syncInterval: 60, // 每 60 秒自动同步
});
await client.sync(); // 在首次读取前填充本地副本

关键点:file: URL 用于本地副本,syncUrl 用于远程主数据库,启动时调用 sync(),读取是本地的,写入转发到远程并具有读你所写语义。

使用时机: 虚拟机、VPS、容器或任何具有文件系统访问权限的长时间运行进程。在无文件系统的无服务器/边缘运行时中不可用。有关完整模式,包括手动同步、离线模式和加密,请参阅 examples/embedded-replicas.md。


模式 7:数据库组和多区域

Turso 将数据库组织成组。每个组有一个主区域和可选的副本位置。组中的所有数据库都继承其位置的设置。

无论哪个副本处理读取,所有写入都会发往主区域。添加更多位置可以改善全局的读取延迟,但不会减少写入延迟。写入延迟取决于到主区域的距离。

有关完整的 Turso CLI 命令参考(turso db create、turso group create、turso db shell 等),请参阅 reference.md。


<decision_framework>

决策框架

batch() 与 transaction()

所有 SQL 语句是否都是预先确定的(语句之间没有条件逻辑)?
+-- 是 --> 使用 batch()(单次往返,隐式事务,首选)
+-- 否 --> 后面的语句是否依赖于前面语句的结果?
    +-- 是 --> 使用 transaction()(交互式,多次往返,持有锁)
    +-- 否 --> 使用 batch()

导入路径选择

使用什么运行时环境?
+-- Node.js / Bun / Deno / 虚拟机 / 容器
|   +-- 需要嵌入式副本(本地文件)?
|   |   +-- 是 --> @libsql/client 配合 file: URL + syncUrl
|   |   +-- 否 --> @libsql/client 配合 libsql:// URL
+-- Cloudflare Workers / Vercel Edge / 浏览器 / 无文件系统的无服务器环境
    +-- @libsql/client/web(仅远程连接,不支持 file: URL)

嵌入式副本与纯远程连接

进程是否是长期运行的并具有文件系统访问权限?
+-- 是 --> 是否需要亚毫秒级读取延迟?
|   +-- 是 --> 嵌入式副本(file: URL + syncUrl)
|   +-- 否 --> 纯远程连接更简单(libsql:// URL)
+-- 否(无服务器、边缘、短生命周期)
    +-- 纯远程连接(@libsql/client/web,libsql:// URL)

事务模式选择

批处理/事务将执行哪些操作?
+-- 仅 SELECT 查询 --> "read"(允许在副本上并行执行)
+-- 任何 INSERT / UPDATE / DELETE --> "write"(获取排他锁)
+-- 调用时不确定 --> "deferred"(以只读开始,必要时升级)

</decision_framework>


<red_flags>

红旗警告

高优先级问题:

  • 在 batch()/transaction() 中缺少事务模式 -- 省略第二个参数默认为 "deferred",这以只读模式开始,可能静默地无法获取 INSERT/UPDATE/DELETE 的写入锁。始终显式指定模式。
  • 在边缘运行时中使用 @libsql/client -- 基础包捆绑了在 Cloudflare Workers、Vercel Edge Functions 及类似环境中会失败的原生 SQLite 绑定。应改用 @libsql/client/web。
  • SQL 中的字符串插值 -- execute(\SELECT * FROM users WHERE id = '${id}'`)是一种 SQL 注入漏洞。始终使用带args` 的参数化查询。
  • 直接访问嵌入式副本文件 -- 在 libSQL 客户端运行时使用另一个 SQLite 客户端打开本地 .db 文件会导致数据损坏。只能通过客户端访问。

中等优先级问题:

  • 在 batch() 足够时使用 transaction() -- 交互式事务会持有数据库锁(超时时间 5 秒),需要多次往返,并会阻塞其他写入者。对于预定的语句集,请使用 batch()。
  • 未调用 client.close() -- 在短生命周期的进程(CLI 脚本、测试清理)中,忘记关闭客户端会留下打开的连接和文件句柄。
  • 忽略嵌入式副本的写入延迟 -- 读取是微秒级的(本地),但写入是 15-50ms(转发到远程主数据库)。请相应地进行设计——避免紧密的写入循环。
  • syncInterval 设置过低 -- 每次同步都会拉取所有更改的帧(每帧 4KB)。在写入密集型数据库上,亚秒级的间隔会产生显著的网络和 I/O 开销。

常见错误:

  • 错误的包名 -- 包名是 @libsql/client,而不是 libsql-client、@turso/client 或 turso-client。
  • args 对象中的命名参数前缀 -- Args 使用裸名称:{ name: "Alice" } 匹配 SQL 中的 :name、@name 和 $name。不要包含前缀:{ ":name": "Alice" } 将无法匹配。
  • 期望 lastInsertRowid 是数字 -- 它是 bigint | undefined。如果您需要数字,请显式转换:Number(result.lastInsertRowid),但请注意非常大的 rowid 的精度损失。
  • 对原子操作使用 executeMultiple() -- executeMultiple() 运行原始 SQL 文本(分号分隔),没有参数化,也没有隐式事务。对于原子化的参数化操作,请使用 batch()。

陷阱与边缘案例:

  • batch() 语句共享一个事务但 NOT 并行 -- 它们按顺序执行。last_insert_rowid 从批处理中的前一条语句获取正确的值。changes() 类似地仅在第一个语句后是正确的。SQLite 的 1 AS sorting_order 技巧可以工作,但不保证跨批处理。

</red_flags>

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

相关技能