@effect/sql-libsql 实战指南:用 Effect SQL 统一访问 libSQL 本地与云端数据库
【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code
导读
@effect/sql-libsql是 Effect 生态中面向 libSQL、LibsqlMigrator.ts)与集成测试(Client.integration.test.ts)展开讲解,读完你将掌握:安装接入方式、完整的客户端配置参数语义、两种客户端构造方式(托管 vs 调用方持有)、事务与保存点机制、SQLite 整数模式的选择,以及如何用同一套迁移与查询 API 同时服务本地文件和远程 Turso 数据库。
一、什么是@effect/sql-libsql
libSQL 是 SQLite 的开放分支,兼容 SQLite 文件格式的同时扩展了嵌入式复制、远程同步等能力,Turso 等云服务即构建在其上。@effect/sql-libsql的角色是一个libSQL 适配器(adapter):它把@libsql/client的底层 API 桥接到 Effect SQL 的通用客户端服务SqlClient上。
从源码注释(LibsqlClient.ts)可以确认它的关键设计承诺:
- 复用 Effect SQL 的 SQLite 编译器:SQL 模板字符串经由
Statement.makeCompilerSqlite编译,行为与 Effect 生态内其他 SQLite 驱动保持一致; - 支持托管 SDK 客户端或调用方持有的 live 客户端两种模式;
- 统一错误分类:libSQL 与 SQLite 的失败都会被归类为
SqlError; - 提供带保存点(savepoint)的事务支持;
- 未实现流式查询:
executeStream直接抛出"executeStream not implemented"(源码),这是使用前需要明确的边界。
包的入口通过 barrel 文件导出两个命名空间:LibsqlClient(客户端主体)与LibsqlMigrator(迁移支持),见 index.ts。
二、安装与版本要求
README 给出的安装命令为:
npm install effect@rc @effect/sql-libsql@rc结合 package.json 可补充两点前提:
effect是peerDependency("effect": "workspace:^"),因此必须与应用中已有的 Effect 版本配对安装,一般建议与 Effect 的 RC 版本线保持一致;- 包自身的运行时依赖仅有一个:
"@libsql/client": "^0.17.4";开发/测试阶段额外依赖testcontainers(用于拉起 libSQL 服务端做集成测试)。
如果你使用 pnpm 工作区,也可以将两个依赖一并加入工作区后统一安装。由于当前仓库中该包以4.0.0-rc.112版本存在,且源码内所有导出均标注@since 4.0.0,本文示例均基于 Effect v4 /@effect/sql-libsqlv4 RC 语义。
三、客户端配置全解
LibsqlClientConfig是一个联合类型,分为两种形态(源码):基于连接参数的Full,和基于已有 SDK 实例的Live。两种形态共享一组Base选项。
3.1 共享选项(Base)
| 选项 | 类型 | 说明 |
|---|---|---|
spanAttributes | Record<string, unknown> | 附加到追踪 span 的键值属性;make内部会自动追加["db.system.name", "sqlite"](源码) |
transformResultNames | (str: string) => string | 对查询结果列名做转换(如 snake_case → camelCase),通过Statement.defaultTransforms作用于每一行结果 |
transformQueryNames | (str: string) => string | 对查询语句中的标识符名做转换,用于构建 SQLite 编译器 |
3.2 连接选项(Full)
Full形态传入 URL 与凭证,客户端由make内部创建并在作用域结束时自动关闭。核心参数:
url(必填):数据库地址,支持libsql:、http:/https:、ws:/wss:与file:协议,即可以指向本地文件、远端 HTTP 端点或 WebSocket 端点;authToken:数据库认证令牌,类型为Redacted.Redacted,底层会在调用Libsql.createClient前通过Redacted.value解封(源码);encryptionKey:数据库加密密钥,同样以Redacted包裹;syncUrl:远程同步服务器地址,用于嵌入式副本场景;syncInterval:同步间隔(秒);tls:仅对libsql:URL 生效,默认开启 TLS,设为false可关闭;intMode:SQLite 整数 → JavaScript 值的转换策略,取值"number"(默认)、"bigint"、"string"。默认number为双精度浮点,无法精确表示绝对值大于2^53 - 1的整数,读取此类大整数会抛RangeError;bigint可精确表示全部 SQLite 整数;string则原样输出字符串;concurrency:并发上限,默认最多 20 个并发请求,设0表示不限制并发。
一个同时使用本地文件与远程副本的示例配置:
import { Redacted } from "effect" import { LibsqlClient } from "@effect/sql-libsql" const config: LibsqlClient.LibsqlClientConfig = { url: "file:local.db", authToken: Redacted.make("your-token"), syncUrl: "libsql://your-db.turso.io", syncInterval: 60, intMode: "bigint", concurrency: 20 }3.3 Live 客户端选项(Live)
当你的应用已经在别处创建了@libsql/client的实例时,可传入liveClient复用:
import * as Libsql from "@libsql/client" import { LibsqlClient } from "@effect/sql-libsql" const sdk = Libsql.createClient({ url: "file:local.db" }) const client = LibsqlClient.make({ liveClient: sdk })注意关键的所有权约定:liveClient由调用方持有,Effect 客户端关闭时不会关闭它(源码注释与make中acquireRelease仅针对自建 SDK 分支可见,见 LibsqlClient.ts)。适合与现有代码共享连接池的场景。
四、三种构造方式:make/layer/layerConfig
LibsqlClient模块提供三种入口:
make(options):最底层构造器,返回Effect<LibsqlClient, never, Scope | Reactivity>,需要Effect.scoped或由 Layer 提供作用域才能安全使用(源码);layer(config):接收具体配置对象,产出同时提供LibsqlClient与通用SqlClient两个服务的 Layer(源码);layerConfig(config):接收EffectConfig描述,适合从环境变量/配置文件动态读取配置,失败类型为Config.ConfigError(源码)。
两种 Layer 都在内部Layer.provide(Reactivity.layer),说明该客户端依赖 Effect 的 Reactivity 基础设施。
标准用法(应用入口组装依赖):
import { Layer, Effect } from "effect" import { LibsqlClient } from "@effect/sql-libsql" const SqlLive = LibsqlClient.layer({ url: "libsql://your-db.turso.io", authToken: Redacted.make(process.env.TURSO_AUTH_TOKEN!) }) const program = Effect.gen(function*() { const sql = yield* LibsqlClient.LibsqlClient const rows = yield* sql`SELECT * FROM users` return rows }) program.pipe( Effect.provide(SqlLive), Effect.runPromise )从服务接口可以看到LibsqlClient通过Context.Service注册、携带运行时类型标记"~@effect/sql-libsql/LibsqlClient",并暴露config字段以便应用在运行期读取实际生效的配置(源码)。
五、SQL 执行 API 与类型化查询
客户端内部通过LibsqlConnectionImpl包装 SDK 的execute,并实现 Effect SQL 的Connection接口(源码)。你可以像使用其他 Effect SQL 驱动一样使用模板字符串查询:
// 插入 yield* sql`INSERT INTO test (name) VALUES ('hello')` // 查询并自动获得对象数组 const rows = yield* sql`SELECT * FROM test` // 列值数组形式(values) const values = yield* sql`select * from test`.values // 原始结果(raw),返回 SDK 的完整结果对象 const raw = yield* sql`INSERT INTO test (name) VALUES ('hello')`.raw以上用法均有集成测试佐证(Client.integration.test.ts):普通查询返回[{ id: 1, name: "hello" }]这样的对象数组;.values返回[[1, "hello"]];.raw返回包含columns、columnTypes、rows、rowsAffected、lastInsertRowid的完整结果对象(lastInsertRowid在 SDK 中以字符串呈现,如"1")。
在执行层,所有失败都会经过classifySqliteError被包装为SqlError,错误信息如"Failed to execute statement"/"Failed to begin transaction"会附带操作名称(源码)。
5.1 与 SqlResolver 组合
@effect/sql-libsql客户端同样可以作为SqlResolver的执行后端,实现 N+1 查询合并。集成测试展示了三种 Resolver 形态(Resolver.integration.test.ts):
SqlResolver.ordered:把并发请求批量合并为一次INSERT ... RETURNING *,并严格按请求顺序返回结果;结果数量不匹配时抛出SqlError.ResultLengthMismatch(actual/expected字段标明差异);SqlResolver.grouped:按RequestGroupKey/ResultGroupKey分组执行WHERE name IN (...),未命中的请求得到Cause.NoSuchElementError;SqlResolver.findById:以 ID 为键做单值查找,同样以NoSuchElementError表达缺省。
这一组合让「libSQL 后端 + 类型安全的数据访问」从 SQL 模板提升到带 Schema 校验的批量请求层。
六、事务与保存点(Savepoint)
make使用 Effect SQL 的Client.makeWithTransaction构建事务支持(源码),底层机制如下:
- 通过信号量(Semaphore)串行化事务获取:
Semaphore.make(1),保证同时只有一个写事务; acquireConnection在获取连接时立即调用 SDK 的transaction("write")开启写事务;- 保存点命名规范:
SAVEPOINT effect_sql_${id}/ROLLBACK TO SAVEPOINT effect_sql_${id},由 Effect SQL 通用层维护嵌套层级。
因此你可以在 libSQL 上使用统一的事务 API,并天然支持嵌套事务:
// 单层事务,失败自动回滚 yield* sql.withTransaction( sql`INSERT INTO test (name) VALUES ('hello')` ) // 嵌套事务(内层失败仅回滚内层保存点) yield* stmt.pipe( Effect.andThen(() => stmt.pipe(sql.withTransaction)), sql.withTransaction )集成测试验证了事务的关键语义(Client.integration.test.ts):内层Effect.fail("boom")后外层表内无残留数据(整体回滚);嵌套事务中内层失败时,外层提交的内容依然保留(total_rows为 1)。此外还有一个专门的回归测试「releases transaction serialization after begin fails」,确认begin失败时事务串行化信号量会被正确释放,不会阻塞后续事务(同文件 L19-L49)。
七、迁移:LibsqlMigrator
LibsqlMigrator.ts 将 Effect SQL 的通用迁移器适配到 libSQL,完整 re-export 了effect/unstable/sql/Migrator的加载器与错误类型,并提供两个入口:
run(options):基于当前SqlClient执行待应用的迁移,返回ReadonlyArray<[id: number, name: string]>,即已应用迁移的 ID 与名称;失败类型为MigrationError | SqlError;layer(options):在 Layer 构建期间执行迁移,本身不提供任何服务(Layer<never, ...>),适合放在应用依赖图的“启动阶段”。
迁移选项MigratorOptions来自共享 Migrator 模块(Migrator),通常包含 loader(从目录/内联数组加载 SQL 文件)、迁移表名等。典型用法:
import { LibsqlMigrator } from "@effect/sql-libsql" import * as Migrator from "effect/unstable/sql/Migrator" const MigrateLive = LibsqlMigrator.layer({ loader: Migrator.fromFileSystem("migrations") }) // 在提供 SqlLive 之后组装 program.pipe( Effect.provide(SqlLive), Effect.provide(MigrateLive) )八、测试基础设施:用 Testcontainers 拉起真实 libSQL 服务
仓库内的集成测试给出了一个可复制的测试模式(util.ts):使用testcontainers启动官方ghcr.io/tursodatabase/libsql-server:main镜像,以SQLD_NODE=primary环境变量 +sqld --http-listen-addr 0.0.0.0:8080命令暴露 HTTP 端点,轮询http://host:8080就绪后,用LibsqlClient.layer({ url })组装测试用 Layer。
const layerClient = Layer.unwrap( Effect.gen(function*() { const container = yield* LibsqlContainer return LibsqlClient.layer({ url: `http://${container.getHost()}:${container.getMappedPort(8080)}` }) }) )结合@effect/vitest的layer(...)((it) => { it.effect(...) })写法,可以为每个测试用例注入真实的 libSQL 数据库,验证 SQL 执行、事务与 Resolver 行为。若需要本地无容器环境,也可用url: "file:test.db"走嵌入式路径(需 SDK 支持)或使用liveClient注入 mock 实例——测试中「begin 失败释放信号量」的用例正是通过手工构造的liveClient完成的。
九、已知边界与注意事项
- 不支持流式查询:
executeStream未实现,需要逐行消费大结果集时应改用其他驱动或分页查询; intMode的精度取舍:默认"number"下读取超过2^53 - 1的整数会抛RangeError,涉及雪花 ID、大整数主键时应显式选择"bigint";- 并发上限:默认 20,高吞吐场景可按需调大或置 0 关闭限制;
- Redacted 凭证:
authToken/encryptionKey应使用Redacted包裹,避免在日志或追踪中泄露; - Live 客户端所有权:传入
liveClient后关闭由调用方负责,使用作用域化的make时则自动释放; - 版本配套:
@effect/sql-libsqlv4 面向 Effect v4(RC),使用时请保持effect@rc与@effect/sql-libsql@rc同线安装,避免与旧版 Effect 混用。
结语
@effect/sql-libsql把 libSQL 的本地与云端能力无缝接入 Effect SQL:一份配置、一套模板字符串 API、统一的事务与迁移语义,即可同时覆盖file:本地库与libsql:/http:/ws:远端库。结合layerConfig的环境变量驱动、SqlResolver的批量请求优化,以及 Testcontainers 的真实数据库集成测试,它可以作为 Effect 应用中类型安全、可测试、可观测的持久层基础组件。若你正在 Effect v4 项目中规划数据层,可以直接从 LibsqlClient.ts 与 Client.integration.test.ts 起步,将其作为接入与测试的参考蓝本。
【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考