ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

深入 @effect/sql-pglite:在 Effect 生态中使用 WASM 版 PostgreSQL 的完整实践指南

2026/9/16 1:16:30 拓冰建站 浏览量
深入 @effect/sql-pglite:在 Effect 生态中使用 WASM 版 PostgreSQL 的完整实践指南 深入 effect/sql-pglite在 Effect 生态中使用 WASM 版 PostgreSQL 的完整实践指南【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code导读effect/sql-pglite是 Effect SQL 官方生态中面向 PGlite 下的源码、测试与配置为第一手依据系统讲解其安装、客户端创建、SQL 编译助手、LISTEN/NOTIFY、数据目录导出、数组类型刷新、事务语义、错误分类与数据库迁移读完即可在浏览器或嵌入式场景中落地一套完整的、Effect 风格的类型安全数据层。一、包定位PGlite 与 Effect SQL 的桥梁从 PgliteClient.ts 顶部注释可以确认本包的核心职责将 Effect SQL 连接到来自electric-sql/pglite的嵌入式、兼容 PostgreSQL 的数据库既能创建一个由本包托管生命周期的 PGlite 实例也能包装一个外部已存在的 PGlite 实例并同时暴露为PgliteClient与通用的 Effect SQLSqlClient提供 PostgreSQL 风格 SQL 的执行能力并额外增加 JSON 值辅助、LISTEN/NOTIFY 消息、数据目录导出dump与数组类型刷新refreshArrayTypes等 PGlite 特性提供构建依赖注入Layer的工厂并把常见的 PostgreSQL 风格错误统一归类为 Effect SQL 的错误类型。从源码结构看该包通过 index.ts 只导出两个模块命名空间PgliteClient与PgliteMigrator整体 API 面非常收敛。在 package.json 中可以看到其运行时依赖仅有electric-sql/pglite^0.5.6而effect被声明为 peerDependencyworkspace 内以workspace:^关联即它必须配合 Effect 本身使用。二、安装根据 README.md 的官方安装说明需要同时安装effect与effect/sql-pglite并统一使用 RC 版本线以保证兼容npm install effectrc effect/sql-pgliterc当前仓库中该包的版本为4.0.0-rc.112见 package.json与之对应的 Effect 版本同样为4.0.0-rc.112二者通过 CHANGELOG 保持逐版本同步发布。若使用 pnpm workspace也可以像仓库内部那样用workspace:^引用同源版本。需要说明的是本包当前处于 RC预发布阶段API 仍可能随 Effect 的 unstable SQL 模块演进而调整。三、创建客户端四种接入方式PgliteClient的构造能力全部集中在 PgliteClient.ts核心入口包括API作用PgliteClient.make(options?)创建一个Scope 化的 PGlite SQL 客户端。未传liveClient时由本包负责创建并在作用域结束时关闭实例关闭操作带 1 秒超时传入liveClient时所有权归调用方Effect 客户端不会关闭它PgliteClient.fromClient({ liveClient, ... })基于一个已存在的 PGlite 实例构建客户端追加 SQL 客户端操作、LISTEN/NOTIFY、dump 与串行化访问能力PgliteClient.layer(config?)由具体配置构建 Layer同时提供PgliteClient与SqlClient两个服务PgliteClient.layerConfig(config)由Config包装的配置构建 Layer便于从环境变量/配置文件读取PgliteClient.layerFrom(acquire)由任意能产出PgliteClient的 Effect 构建 Layer其中layer/layerConfig/layerFrom返回的 Layer 会同时注册PgliteClient与通用SqlClient服务源码中通过Context.make(PgliteClient, client).pipe(Context.add(Client.SqlClient, client))实现这意味着注入该 Layer 后业务代码既可以取PgliteClient使用其扩展能力也可以只依赖标准的SqlClient。最简单的接入方式与测试 Client.test.ts 完全一致import { PgliteClient } from effect/sql-pglite const ClientLayer PgliteClient.layer()在浏览器或 Node 端通过 Effect 的Effect.run*运行时加载该 Layer即可开始执行 SQL。3.1 配置模型PgliteClientConfigmake接受的配置类型PgliteClientConfig是「Create | Live」两种变体的联合Create继承 PGlite 构造参数PGliteOptions用于让本包自建并托管实例Live携带liveClient: PGliteInterface用于包装外部实例例如测试中new PGlite()后传入。两种变体还共享一组Base选项选项说明spanAttributes附加到查询 span 的 OpenTelemetry 属性实现中还会自动追加db.system.name postgresqltransformResultNames对查询结果行字段名做转换如 camelCase ↔ snake_casetransformQueryNames对 SQL 中标识符做转换如peopleTest→people_test传入后编译器会在标识符转义前应用transformJson是否对 JSON 参数值做同样的转换默认true此外ConfigBase供layerConfig使用还包含了与 PGlite 创建相关的四个配置友好字段dataDir数据目录、username、database、relaxedDurability放松持久化以保证更快的非关键写入性能。3.2 语句编译器与占位符包内通过makeCompiler构建 PGlite 专属的 SQL 编译器关键行为源码见 PgliteClient.ts占位符采用 PostgreSQL 的$1、$2形式标识符使用双引号转义Statement.defaultEscape(\)onRecordUpdate支持把一组值渲染为(values ($1),($2)) AS data(name)这样的 VALUES 派生表供UPDATE ... FROM批量更新使用提供PgJson自定义片段支持把任意 JS 值作为 JSON 参数插入transformJson开启时还会先经过值转换。四、类型安全的 SQL 编写与常用助手PgliteClient完全继承 Effect SQL 的类型安全模板字符串语法。结合 Client.test.ts 中的断言可以验证以下可编译、可执行的写法const sql yield* PgliteClient.PgliteClient // 基本插入与查询 yield* sqlINSERT INTO test (name) VALUES (hello) const rows yield* sql{ id: number; name: string }SELECT * FROM test // rows [{ id: 1, name: hello }] // 结果以二维数组返回 const values yield* sqlSELECT * FROM test.values // values [[1, hello]] // 编译为 SQL 文本 参数便于查看生成的语句 const [query, params] sqlINSERT INTO people ${sql.insert({ name: Tim, age: 10 })}.compile() // query INSERT INTO people (name,age) VALUES ($1,$2) // params [Tim, 10]语句助手同样可用且由测试逐一断言过产物助手编译产物示例sql.update({ name: Tim })UPDATE people SET name $1sql.updateValues([{name:Tim},{name:John}], data)UPDATE people SET name data.name FROM (values ($1),($2)) AS data(name)sql.in([1, 2, x])... WHERE id IN ($1,$2,$3)sql.and([...])... WHERE (name IN ($1,$2) AND created_at $3)sql(people)将标识符安全转义为people4.1 标识符转换与 JSON 片段若在创建客户端时配置了transformQueryNames例如const compiler PgliteClient.makeCompiler((s) s.replace(/[A-Z]/g, (c) _${c.toLowerCase()})) compiler.compile(sqlSELECT * FROM ${sql(peopleTest)}, false) // SELECT * FROM people_testJSON 值的插入则使用sql.json(...)测试验证了往返一致性const rows yield* sql{ json: unknown }SELECT ${sql.json({ a: 1 })}::jsonb AS json // rows[0].json { a: 1 }五、PGlite 扩展能力LISTEN/NOTIFY、dump 与数组类型刷新PgliteClient接口在 PgliteClient.ts 中明确定义了四条超出通用SqlClient的能力pglite暴露底层PGliteInterface需要直接调用 PGlite 原生 API 时可绕过listen(channel): Streamstring, SqlError订阅频道把 PGlite 回调推送进Stream队列取消订阅通过acquireRelease保证清理notify(channel, payload): Effectvoid, SqlError执行NOTIFY频道名与字面量都经过转义escapeLiteral将单引号翻倍并通过信号量保证与 dump 等操作串行dumpDataDir(compression)导出数据目录compression取值none | gzip | auto返回File | Blob适合做持久化快照refreshArrayTypes刷新 PGlite 的数组类型缓存解决「先建枚举/自定义类型再插入数组」时类型未就绪的问题。Client.test.ts 中有对应的端到端验证// LISTEN / NOTIFY监听后投递 payload能原样收到 const deferred yield* Deferred.makestring() const unsub yield* Effect.tryPromise({ try: () sql.pglite.listen(ch1, (payload) Effect.runFork(Deferred.succeed(deferred, payload))) }) yield* sql.notify(ch1, hello) assert.strictEqual(yield* Deferred.await(deferred), hello) // dumpDataDir导出结果非空 const dump yield* sql.dumpDataDir(none) assert.isAbove((dump as Blob).size, 0) // refreshArrayTypes先建枚举类型与数组列再插入数组值 yield* sqlCREATE TYPE mood AS ENUM (sad, happy) yield* sqlCREATE TABLE test_moods (id SERIAL PRIMARY KEY, name TEXT, moods mood[]) yield* sql.refreshArrayTypes yield* sqlINSERT INTO test_moods (name, moods) VALUES (${test2}, ${[sad, happy]})六、事务语义提交、回滚与嵌套保存点事务由通用的sql.withTransaction驱动但 PGlite 客户端在实现上做了串行化保障fromClient内部用Semaphore.makeUnsafe(1)创建了单许可信号量普通连接获取与事务连接获取共用该信号量其中事务获取还通过Effect.uninterruptibleMaskScope.addFinalizer确保事务期间的信号量最终被释放源码见 PgliteClient.ts。Transaction.test.ts 覆盖了四类关键场景提交事务内插入的数据在外层可查回滚事务内 Effect 失败后外层查询为空表嵌套事务内层成功则两层都提交计数为 2嵌套回滚内层失败时通过保存点savepoint回滚内层外层保留计数为 1并验证了并发嵌套事务场景下成功者数据得以保留。七、错误分类PostgreSQL 错误码 → Effect SQL 错误PGlite 抛出的原生错误会被统一映射为 Effect SQL 的类型化错误映射逻辑集中在classifyError源码见 PgliteClient.tsPostgreSQL 错误码前缀/值映射结果08xx连接类ConnectionError28xx认证类AuthenticationError42501AuthorizationError42xx语法/语义类SqlSyntaxError23505UniqueViolation并附带裁剪空白后的constraint名23xx完整性约束类不含 23505ConstraintError40P01DeadlockError40001SerializationError55P03LockTimeoutError57014StatementTimeoutError其他UnknownErrorSqlErrorClassification.test.ts 精确验证了这些规则尤其是UniqueViolation对约束名的处理缺失、非字符串或纯空白时一律归一为unknown有效约束名会去除首尾空白如 users_email_key →users_email_key而23503外键违反则保持在ConstraintError而不是误判为唯一冲突。这让错误处理可以写成模式匹配而不是解析错误字符串。八、数据库迁移PgliteMigratorPgliteMigrator.ts 提供与 Effect SQL 一致的迁移能力直接重导出通用effect/unstable/sql/Migrator的加载器与错误类型run(options)使用当前上下文中的SqlClient执行未应用的迁移文件返回已应用迁移的[id, name]数组失败类型为Migrator.MigrationError | SqlErrorlayer(options)在 Layer 构建阶段自动执行迁移Layer.effectDiscard不要求独立的 PGlite 服务——连接完全来自已注册的SqlClient。Migrator.test.ts 展示了最小可用写法loader以[id, name, Effect]元组数组描述迁移PgliteMigrator.layer({ loader })组合PgliteClient.layer({})后两条迁移建表、插入依次执行并能在effect_sql_migrations表中查到[[1, init], [2, insert]]的记录。九、与持久化队列协同PersistedQueue测试目录中的 PersistedQueue.test.ts 展示了一个真实的组合用法以 PGlite 客户端作为底层存储运行effect/unstable/persistence的PersistedQueue.makeStoreSql({ tableName })生成持久化队列表结构。测试验证了迁移表只记录一次migration_id: 1, name: create_table且生成的索引符合预期idx_table_id、idx_table_take、idx_table_update加主键。这说明effect/sql-pglite不仅是查询客户端还能作为 Effect 持久化原语在浏览器端的落点。十、源码结构一览本包结构非常精简全部代码位于 .repos/effect-smol/packages/sql/pglitesrc/ index.ts # 唯一入口导出 PgliteClient 与 PgliteMigrator 两个命名空间 PgliteClient.ts # 客户端构造、编译器、连接实现、Layer 与错误分类 PgliteMigrator.ts # 迁移 run/layer复用 effect/unstable/sql/Migrator test/ Client.test.ts # 增删改查、语句助手、标识符转换、JSON、LISTEN/NOTIFY、dump Transaction.test.ts # 提交/回滚/嵌套事务/并发嵌套 Migrator.test.ts # 迁移执行与记录 PersistedQueue.test.ts SqlErrorClassification.test.ts package.json # 依赖 electric-sql/pglite ^0.5.6peer 依赖 effect结语effect/sql-pglite的价值在于把「无服务端、随应用嵌入的 PostgreSQLWASM」完整接入了 Effect 的依赖注入、结构化并发与错误模型同一份SqlClient代码既能在服务端跑真 PostgreSQL也能在浏览器跑 PGlite迁移、事务、类型化 SQL 与可观测 span 全部开箱即用。若要深入阅读实现细节与运行验证建议从 PgliteClient.ts 的fromClient/makeCompiler入手再对照 Client.test.ts 与 Transaction.test.ts 逐条运行测试即可获得对该包最完整的认识。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考