news 2026/9/16 22:18:21

@effect/sql-pglite 演进全解析:在 Effect 中集成 WASM 版 PostgreSQL(PGlite)客户端

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
@effect/sql-pglite 演进全解析:在 Effect 中集成 WASM 版 PostgreSQL(PGlite)客户端

@effect/sql-pglite 演进全解析:在 Effect 中集成 WASM 版 PostgreSQL(PGlite)客户端

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

本篇技术指南以@effect/sql-pglite包的 CHANGELOG.md 为主线,结合该包在 effect-smol 仓库中的源码与测试,系统讲解它如何把 PGlite(WASM 构建的 PostgreSQL)接入 Effect 的 SQL 客户端体系。读完你将掌握该包的核心能力清单(Postgres 方言编译、基于 savepoint 的事务、LISTEN/NOTIFY、数据目录导出、迁移器)、SQL 错误分类体系、关键 API 演进(如valuesUnpreparedUniqueViolation),并了解其与 effect 核心包的版本同步机制。

一、包的定位:从 Changelog 首次发布看设计意图

@effect/sql-pglite在 Changelog 中的4.0.0-beta.57条目(CHANGELOG.md 中的 Minor Changes)给出了该包最权威的定位声明:

新增@effect/sql-pglite包,封装@electric-sql/pglite,并配套 Effect SQL 客户端能力:Postgres 方言、通过 savepoint 实现的 Effect 托管事务、listen/notify、dumpDataDir/refreshArrayTypes,以及一个 Migrator。

PGlite 是 PostgreSQL 的 WASM 构建,可在浏览器、Node.js 与 Bun 中运行(见 README.md)。这意味着该包承担了两个关键职责:

  1. 桥接:把 PGlite 实例(PGliteInterface)转化为符合 EffectSqlClient契约的客户端;
  2. 增强:在通用 SQL 客户端之上补充 PGlite 特有的操作(监听通知、数据目录导出、数组类型刷新)。

package.json(package.json)中可以看到它仅依赖@electric-sql/pglite^0.5.6),而effect作为 peerDependency,从依赖设计上就决定了“核心逻辑在 effect,PGlite 只是接入方”的分层。

二、安装与包结构

安装命令(来自 README.md):

npm install effect@rc @effect/sql-pglite@rc

注意安装的是 rc 预发布版本,与 Changelog 中4.0.0-rc.*/4.0.0-beta.*的版本命名一致。包的源码结构非常精简(src 目录):

  • PgliteClient.ts:客户端主体,包含make/fromClient/layer等构造器与 Layer、SQL 语句编译器、错误分类逻辑;
  • PgliteMigrator.ts:迁移器,复用 effect 的共享迁移加载器;
  • index.ts:统一出口,分别导出PgliteClientPgliteMigrator两个命名空间。

三、核心能力清单与源码印证

3.1 两种客户端构建方式:托管实例 vs 外部实例

从 PgliteClient.ts 的类型定义看,PgliteClientConfig分为CreateLive两种形态:

  • Create:传入 PGlite 构造选项(PGliteOptions),由make内部创建受 Scope 管理的实例——Effect.acquireRelease创建并在作用域结束时close()(带 1000ms 超时),生命周期完全由 Effect 托管;
  • Live:传入调用方已创建好的liveClient,此时实例归调用方所有,Effect 客户端不会关闭它。

对应的两个构造器是make(options)fromClient(options),配合三个 Layer 工厂使用:

  • layer(config?):基于具体配置创建 Layer;
  • layerFrom(acquire):基于任意 acquire Effect 创建 Layer;
  • layerConfig(config):基于 EffectConfig(支持从环境变量等读取配置)创建 Layer。

三者都会同时提供PgliteClient与通用SqlClient两个服务标签(Context.make(PgliteClient, client) + Context.add(Client.SqlClient, client)),因此下游业务代码既可以按需访问 PGlite 特有能力,也可以只依赖标准的SqlClient

3.2 Postgres 方言编译器

makeCompiler生成符合 PostgreSQL 语法习惯的语句编译器(PgliteClient.ts):

  • 参数占位符使用 PostgreSQL 的$1$2形式;
  • 标识符使用双引号转义(escape基于Statement.defaultEscape("\""));
  • 支持onRecordUpdate(生成(values ...) AS alias(cols) RETURNING ...形式的批量更新);
  • 支持 JSON 自定义片段(PgJson),可选的transformJson会对接入的 JSON 值做名称转换。

测试用例(Client.test.ts)对这些编译行为给出了可直接验证的断言,例如:

const [query, params] = sql`INSERT INTO people ${sql.insert({ name: "Tim", age: 10 })}`.compile() // query === `INSERT INTO people ("name","age") VALUES ($1,$2)` // params === ["Tim", 10]

以及in助手、and助手、updateValues(生成FROM (values ($1),($2)) AS data("name"))等常用片段。

3.3 基于 savepoint 的 Effect 托管事务

Changelog 明确将“Effect-managed transactions via savepoints”列为核心能力。源码中transactionAcquirer使用Effect.uninterruptibleMask+ 信号量(Semaphore.makeUnsafe(1))保证同一时刻只有一个事务持有连接,并把信号量释放注册为当前作用域的 finalizer;嵌套事务则通过 savepoint 实现。

Transaction.test.ts 覆盖了四类典型场景,可作为理解其语义的权威参考:

  • 事务内提交(withTransaction commit):插入的数据在提交后可见;
  • 事务内回滚(withTransaction rollback):Effect 失败后整条记录消失;
  • 嵌套事务成功:外层与内层同时提交,两条记录都在;
  • 嵌套事务回滚:内层失败只回滚到 savepoint,外层记录保留(最终只有 1 条);
  • 并发嵌套事务:即使内层并发且部分失败,成功分支的数据依然保留。

3.4 listen/notify、dumpDataDir 与 refreshArrayTypes

PgliteClient接口在标准SqlClient之上扩展了四个 PGlite 专属能力(PgliteClient.ts):

成员签名说明
json(_: unknown) => Fragment构造 JSON 参数片段,配合::jsonb使用
listen(channel: string) => Stream<Stream<string, SqlError>>订阅频道,返回 Effect Stream
notify(channel: string, payload: string) => Effect<void, SqlError>发送通知(内部执行转义后的NOTIFY,payload 会做单引号转义)
dumpDataDir(compression?: "none" \| "gzip" \| "auto") => Effect<File \| Blob, SqlError>导出整个数据目录
refreshArrayTypesEffect<void, SqlError>刷新数组类型注册

这些操作统一经过信号量串行化(semaphore.withPermit),避免与常规查询并发时产生竞争。测试中可以看到典型用法:Client.test.ts 演示了listen("ch1", ...)+notify("ch1", "hello")的配对流程,以及创建mood[]枚举数组类型后调用refreshArrayTypes再插入数组值。

3.5 迁移器(Migrator)

PgliteMigrator.ts 导出runlayer

  • run(options):基于当前SqlClient执行待应用的迁移文件,返回已应用迁移的[id, name]列表;它不需要独立的 PGlite 服务,连接由活跃的SqlClient提供;
  • layer(options):在 Layer 构建期间执行迁移(Layer.effectDiscard(run(options)))。

迁移器复用 effect 的共享Migrator模块(export * from "effect/unstable/sql/Migrator"),并依赖统一的effect_sql_migrations记录表。Migrator.test.ts 验证了迁移按migration_id顺序执行并写入记录表;PersistedQueue.test.ts 则展示了迁移器的另一个用途——为PersistedQueue持久化队列自动建表并确保只记录一次迁移。

四、SQL 错误分类体系:UniqueViolation的引入

Changelog4.0.0-beta.65条目记录了一次重要的错误分类增强:

新增UniqueViolation作为新的 SQL 错误原因。受支持的唯一约束冲突现在归类为UniqueViolation,而不再落入更宽泛的ConstraintErrorUniqueViolation.constraint保存可用的约束/索引/键标识符,当无法取得可靠标识符时回退为"unknown"。该分类覆盖 PostgreSQL、PGlite、MySQL、MSSQL 以及 SQLite 家族的共享分类逻辑。

在 PgliteClient.ts 的classifyError中可以看到完整的映射表,它依据 PostgreSQL 的 SQLSTATE 错误码前缀分类:

SQLSTATE分类结果
08xxConnectionError(连接错误)
28xxAuthenticationError(认证错误)
42501AuthorizationError(权限错误)
42xxSqlSyntaxError(语法错误)
23505UniqueViolation(唯一约束冲突,携带 constraint)
23xx(其余)ConstraintError(一般完整性约束)
40P01DeadlockError
40001SerializationError
55P03LockTimeoutError
57014StatementTimeoutError
其他UnknownError

约束名的提取有精细的兜底逻辑(PgliteClient.ts 的pgConstraintFromCause):约束缺失、非字符串或全空白时统一回退为"unknown",有效值会先trim再返回。SqlErrorClassification.test.ts 正是围绕这三点写的测试:" users_email_key "会被修剪为users_email_key,而缺约束 / 数字约束 / 空白约束均返回"unknown"23503(外键)则保持ConstraintError不变。

五、关键 API 演进:valuesUnprepared与入口调整

Changelog 记录了若干影响使用方式的 API 变化:

  • 4.0.0-beta.86:新增Statement.valuesUnprepared,用于以数组形式返回未预编译 SQL 语句的行。对应实现在 Statement.ts 中,PgliteClient的底层连接通过executeValuesUnprepared(内部以rowMode: "array"查询)支撑它,返回ReadonlyArray<ReadonlyArray<unknown>>
  • 4.0.0-beta.103:移除了显式的./index入口("Removed explicit ./index entrypoints")。这与 package.json 中的 exports 配置相呼应——"./index": null"./*/index": null明确禁用了旧入口,导入请使用包根或具体子路径。
  • 4.0.0-rc.112:更新生产依赖到最新版本(由@tim-smart提交),属于常规的依赖刷新。

六、版本节奏与 effect 核心的同步机制

通读整个 Changelog 可以发现一个明显规律:几乎每个版本的 "Patch Changes" 都只包含 "Updated dependencies" 指向effect@4.0.0-x.y.z,这说明@effect/sql-pglite与 effect 核心走的是同版本号、同步发布策略。实际含义是:

  1. 每次 effect 核心更新,所有 SQL 包统一跟随升级,避免版本矩阵错位;
  2. 该包自身几乎没有独立功能变更——真正的 SQL 客户端基础设施(SqlClientSqlErrorStatementMigrator)都沉淀在 effect 核心中,本包只做 PGlite 的适配层;
  3. 因此升级时应保持effect@effect/sql-pglite版本一致(如均为4.0.0-rc.112),这从 package.json 的 peerDependencies(effect: workspace:^)也能看出。

从源码结构看,这种“核心在 effect、适配在 sql/*”的组织方式同样存在于packages/sql/pgmysql2sqlite等兄弟包(它们共享同一套SqlError分类与valuesUnprepared特性,可从各自 CHANGELOG 中看到同名条目)。

七、使用建议与注意事项

综合 Changelog、源码与测试,落地使用时有几点值得注意:

  • 生命周期:优先使用layer()/layerConfig()让 Effect 托管 PGlite 实例(Scope 结束自动关闭);若使用fromClient({ liveClient })包装外部实例,务必自行负责关闭,客户端不会替你清理。
  • 事务语义:嵌套事务基于 savepoint,内层失败只回滚到 savepoint;并发嵌套事务受信号量保护,同一连接不会同时跑多个事务。
  • 错误处理:针对唯一约束冲突,直接匹配UniqueViolation并读取constraint字段即可拿到约束名(可能为"unknown");其他完整性错误仍为ConstraintError
  • 版本对齐:安装时让effect@effect/sql-pglite保持同版本(当前均为4.0.0-rc.112),并留意 Changelog 中 "Updated dependencies" 列出的 effect 版本,这是判断兼容性的最快途径。
  • 持久化队列等扩展场景PgliteMigratorPersistedQueue可组合使用,迁移器会自动为队列建表并幂等记录迁移(见 PersistedQueue.test.ts)。

八、总结

@effect/sql-pglite是一个“小而精”的适配包:Changelog 中绝大部分条目是跟随 effect 核心的依赖同步,仅有的几条独立变更(包首发、UniqueViolation分类、valuesUnprepared、入口清理)恰好勾勒出它的全部技术边界。结合 PgliteClient.ts、PgliteMigrator.ts 与 test 目录,即可在浏览器、Node.js 或 Bun 中,用完全符合 Effect 生态惯用法的类型安全方式操作一个嵌入式的、无外部进程依赖的 PostgreSQL 数据库。

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 22:18:06

企业网络卡顿掉线?别再盲目换设备,系统性升级才是解药

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 22:17:42

Unity静态光照烘焙:创建、保存与跨场景复用LightmapData全流程

1. 项目概述&#xff1a;为什么“烘焙场景”是Unity中绕不开的硬功夫&#xff1f;在Unity里提到“烘焙”&#xff0c;老手第一反应不是厨房&#xff0c;而是光照——准确说是静态光照计算结果的离线预计算与持久化存储过程。它不是实时渲染的替代品&#xff0c;而是性能与画质的…

作者头像 李华
网站建设 2026/9/16 22:17:40

网站封装成APP实战指南:从WebView到TWA合规上架

1. 项目概述&#xff1a;为什么“把网站封装成APP”不是偷懒&#xff0c;而是务实选择最近在几个技术交流群里&#xff0c;总有人发问&#xff1a;“我有个现成的H5网站&#xff0c;能不能不重写代码&#xff0c;直接打包成安卓APP上架应用市场&#xff1f;”——这个问题背后&…

作者头像 李华
网站建设 2026/9/16 22:17:39

分歧驱动主动学习:让持续训练自动挑出高价值样本

做持续训练的项目&#xff0c;最怕的不是模型效果差&#xff0c;而是数据越攒越多、模型反而一直在原地踏步甚至开倒车。我之前负责一个业务文本分类系统&#xff0c;线上每天回流几千条新样本&#xff0c;全都送去人工标注不现实&#xff0c;随机抽一批丢进去训练又容易踩到雷…

作者头像 李华
网站建设 2026/9/16 22:17:19

Win10卸载Edge前必读:系统依赖与重装优化全解析

1. 先别急着动手&#xff1a;为什么全网都在搜"卸载Edge"坦白说&#xff0c;我也干过这事儿。有一次帮朋友清理一台被各种全家桶塞满的 Win10&#xff0c;一开机 Edge 就弹出来&#xff0c;内存占用排名前三&#xff0c;风扇呼呼转。当时我二话没说&#xff0c;一条命…

作者头像 李华
网站建设 2026/9/16 22:16:42

Altium Designer 16报错本质是设计合规性警报

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华