Effect v4 的 Encoding 模块整合:effect/encoding 子模块合并进顶层 Encoding API
2026/9/14 13:31:04 网站建设 项目流程

Effect v4 的 Encoding 模块整合:effect/encoding 子模块合并进顶层 Encoding API

【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect

本篇围绕 Effect 仓库中一条 Changeset 变更记录(.changeset/pre/consolidate-encoding.md)展开,说明 v4 中effect/encoding子模块(Base64、Base64Url、Hex、EncodingError)被合并进顶层Encoding模块、函数统一加前缀、子路径导出被移除这一 API 重组的来龙去脉。读完你将掌握:新版Encoding的完整函数清单与签名、EncodingError错误模型、Base64/Hex 解码的校验规则,以及如何从旧的effect/encoding子路径导入迁移到顶层 API。

一、变更内容:一条 Changeset 说了什么

变更声明位于 consolidate-encoding.md,全文仅一段:

Encoding: consolidateeffect/encodingsub-modules (Base64, Base64Url, Hex, EncodingError) into a top-levelEncodingmodule. Functions are now prefixed:encodeBase64,decodeBase64,encodeHex,decodeHex, etc. Theeffect/encodingsub-path export is removed.

按 changesets 规范解读这条记录:

  • 文件头 YAML frontmatter 为"effect": patch,即本次变更作用于effect包本身,属于补丁级别的 API 结构调整(对使用者而言是导入路径的破坏性变更,但对包发布流程按 pre 模式管理——该文件位于.changeset/pre/目录,对应 v4 预发布阶段);
  • 仓库当前 effect 包 版本号为4.0.0-rc.115Encoding.ts模块头注释标注@since 4.0.0,二者互相印证这是 v4 预发布周期内的整合工作。

变更包含三件事,后文逐一验证:

  1. Base64Base64UrlHexEncodingError四个子模块的公共能力合并进顶层Encoding模块;
  2. 函数统一加编码类型前缀:encodeBase64decodeBase64encodeHexdecodeHex等,取代子模块内裸名encode/decode
  3. effect/encoding子路径导出被移除。

二、子路径导出的移除:从 package.json 验证

检查 packages/effect/package.json 的exports字段可以确认第 3 点:当前版本中已不存在./encoding这一子路径条目,与编码相关的导出仅剩./unstable/encoding(后者指向src/unstable/encoding/index.ts,包含 Toml、Yaml、Sse、Ndjson 等不稳定文本编解码,与本条 Changeset 合并的 Base64/Hex 是不同层面的功能,不要混淆)。

顶层Encoding模块的出口在 packages/effect/src/index.ts 第 167 行:

export * as Encoding from "./Encoding.ts"

即使用者只需从主入口effect导入命名空间:

import { Encoding } from "effect"

而不再需要(也不能再)写import { Base64 } from "effect/encoding"之类的子路径导入。

三、整合后的完整 API 面

整合后的 Encoding.ts 模块头注释给出了整体契约:模块在字符串、UTF-8 文本与Uint8Array字节之间转换;encode 系列直接返回字符串decode 系列返回Result.Result,非法输入以EncodingError形式报告而不是抛异常。函数按 Changeset 所述统一前缀,当前完整清单如下。

3.1 Base64(RFC4648 标准字母表,带=填充)

函数签名说明
encodeBase64(input: Uint8Array \| string) => string字符串输入先按 UTF-8 转字节再编码;Uint8Array直接编码,输出为标准字母表加=填充
decodeBase64(str: string) => Result.Result<Uint8Array, EncodingError>解码为字节,失败返回Result.fail
decodeBase64String(str: string) => Result.Result<string, EncodingError>解码为 UTF-8 文本

用法示例(摘自 Encoding.ts 的 JSDoc 内嵌测试):

import { Encoding, Result } from "effect" // 编码字符串 Encoding.encodeBase64("hello") // => "aGVsbG8=" // 编码二进制数据 const bytes = new Uint8Array([72, 101, 108, 108, 111]) Encoding.encodeBase64(bytes) // => "SGVsbG8=" // 解码字节 / 文本 Encoding.decodeBase64("SGVsbG8=") // => Result.succeed(new Uint8Array([72, 101, 108, 108, 111])) Encoding.decodeBase64String("aGVsbG8=") // => Result.succeed("hello")

decodeBase64的校验逻辑(Encoding.ts)值得注意:

  1. stripCrlf去除输入中的\n/\r——这是为了容忍 Base64 常被折行存储的实际情况;
  2. 长度必须是 4 的倍数,否则失败,消息形如Length must be a multiple of 4, but is N
  3. =只允许出现在末尾,且必须成规则出现(倒数第二位的=后必须紧跟另一个=),否则报Found a '=' character, but it is not at the end
  4. 逐 4 字符组按 6 位查表拼装 3 字节,非法字符由内部getBase64CodeTypeErrorInvalid character ...),被外层try/catch捕获后统一转成Result.fail,保证函数全程无 throw。

3.2 Base64Url(URL 安全字母表,去填充)

函数签名说明
encodeBase64Url(input: Uint8Array \| string) => string先做标准 Base64,再删除=并把+-/_
decodeBase64Url(str: string) => Result.Result<Uint8Array, EncodingError>同时接受填充与未填充两种形式
decodeBase64UrlString(str: string) => Result.Result<string, EncodingError>解码为 UTF-8 文本
Encoding.encodeBase64Url("hello?") // => "aGVsbG8_" Encoding.decodeBase64Url("SGVsbG8_") // => Result.succeed(new Uint8Array([72, 101, 108, 108, 111, 63])) Encoding.decodeBase64UrlString("aGVsbG8_") // => Result.succeed("hello?")

从实现看(Encoding.ts),decodeBase64Url是一条「归一化后复用标准解码」的调用链:先校验长度模 4 不为 1、再校验字符集正则/^[-_A-Z0-9]*?={0,2}$/i,然后按缺失的填充补回=,把-/_还原为+//,最后直接委托给decodeBase64。这解释了为什么 URL 变体的错误消息里module字段写作"Base64"的情况不存在——URL 分支的自有校验错误带module: "Base64Url",而还原后的底层解码错误继承标准 Base64 的模块名。

3.3 Hex(十六进制,小写输出)

函数签名说明
encodeHex(input: Uint8Array \| string) => string输出小写十六进制文本
randomHex(length: number) => string生成随机小写 hex(基于Math.random(),非密码学安全
decodeHex(str: string) => Result.Result<Uint8Array, EncodingError>解码字节,要求偶数长度
decodeHexString(str: string) => Result.Result<string, EncodingError>解码为 UTF-8 文本
Encoding.encodeHex("hello") // => "68656c6c6f" Encoding.decodeHex("48656c6c6f") // => Result.succeed(new Uint8Array([72, 101, 108, 108, 111])) Encoding.decodeHexString("68656c6c6f") // => Result.succeed("hello")

两个使用注意点直接来自源码注释:

  • decodeHex先把输入经TextEncoder转成字节再校验偶数长度,因此非 ASCII 输入会被当作字节序列计长,失败消息形如Length must be a multiple of 2, but is N;字符映射同时接受0-9a-fA-F(见内部函数fromHexChar,Encoding.ts),即解码对大小写不敏感,而编码一律小写。
  • randomHex的 JSDoc 明确警告:该函数使用Math.random()不用于安全敏感场景;需要安全随机值时应使用Crypto服务的randomBytes再经encodeHex编码。实现上它对 16/32 位长度(trace/span 标识符的常见长度)有专门快路径,用单次String.fromCharCode生成扁平字符串以避免 rope 展开销。

3.4 统一错误类型 EncodingError

整合后的错误面也按 Changeset 所述并入同一模块(Encoding.ts):

export class EncodingError extends Data.TaggedError("EncodingError")<{ kind: "Decode" | "Encode" module: string input: unknown message: string }>
  • kind区分失败发生在解码还是编码阶段;
  • module记录报告失败的编码模块("Base64"/"Base64Url"/"Hex");
  • input保留触发失败的原始输入,便于日志定位;
  • 类型守卫为isEncodingError(u): u is EncodingError,基于EncodingErrorTypeId(值"~effect/Encoding/EncodingError")运行时标记判断,可安全地对unknown收窄。

典型处理写法:

import { Encoding, Result } from "effect" const bytes = Result.flatMap( Encoding.decodeBase64(urlParam), (b) => /* ... */ b ) // 或显式检查 Result.match( Encoding.decodeBase64UrlString(token), { onFailure: (e) => e.kind, onSuccess: (s) => s } )

由于错误是结构化Data.TaggedError而非裸Error,它可以参与Effect.catch的类型收窄与匹配,这与 Effect 全库「解码类 API 一律返回Result/失败态而非 throw」的约定一致。

四、对照 v3:迁移指南中的对应关系

仓库自带的大型迁移文档 migration/v3-to-v4.md 中Encoding一节(约 L10245–L10259)补充了旧 API 的去向,与本条 Changeset 的整合方向一致:

  • Encoding.DecodeException/Encoding.EncodeExceptionEncoding.EncodingError:解码与编码两类异常统一为同一个错误类,靠kindDecode/Encode)区分;
  • 两个*TypeId标记 → 共享一个Encoding.EncodingErrorTypeId
  • Encoding.isDecodeException/Encoding.isEncodeExceptionEncoding.isEncodingError,需要按阶段收窄时再测试kind === "Decode"kind === "Encode"
  • 旧的Encoding.encodeUriComponent/Encoding.decodeUriComponent没有随整合进入新模块:迁移指南建议用Result.try包裹encodeURIComponent/decodeURIComponent,或直接使用Schema.StringFromUriComponent编解码。

从源码结构看,当前Encoding模块确实只含 Base64、Base64Url、Hex 三组编解码与统一错误类型,不含 URI 组件函数——这与迁移文档的描述相符。

五、底层实现速览(源码证据)

整合后所有能力集中在单文件 packages/effect/src/Encoding.ts 中,关键内部件:

  • 模块级共享TextEncoder/TextDecoder实例(L616–L617),encode*对字符串输入统一先encoder.encode(input)
  • 标准 Base64 手写查表实现:编码用 64 字母表base64abc(L665–L730),解码用 96 项逆向表base64codes(非法字符位置为 255,L732–L856),3 字节 → 4 字符组按位拼装(<< 18 | << 12 | << 6 |);
  • Base64Url 编码只是「标准编码 + 三次 replace」(L860–L861):去=+-/_
  • Hex 编码用预生成的 256 项byteToHex表拼接(L865–L873),解码逐对字符经fromHexChar转数值后(a << 4) | b合并。

这些实现细节说明整合后的模块是自包含的纯函数集合:无Effect依赖、无 Fiber/Scope 资源,可在任何上下文同步调用,这也是它能直接并入顶层Encoding命名空间而不引入额外服务依赖的原因。

六、迁移清单

从旧代码迁移到整合后的模块,按以下映射执行:

旧写法(v3 及之前)新写法(v4)
import { Base64 } from "effect/encoding"(子路径导入)import { Encoding } from "effect"
Base64.encode(x)/Base64.decode(x)Encoding.encodeBase64(x)/Encoding.decodeBase64(x)(或decodeBase64String
Base64Url.encode(x)/Base64Url.decode(x)Encoding.encodeBase64Url(x)/Encoding.decodeBase64Url(x)
Hex.encode(x)/Hex.decode(x)Encoding.encodeHex(x)/Encoding.decodeHex(x)
Encoding.isDecodeException(e)/isEncodeException(e)Encoding.isEncodingError(e),按e.kind区分阶段
捕获DecodeException/EncodeException统一处理EncodingErrorkind: "Decode" \| "Encode"

迁移时的两个行为细节:解码不再 throw,需按Result处理失败分支;decodeBase64Url兼容填充与未填充两种输入,而decodeBase64严格要求 4 倍数长度与末尾填充,跨端(如与只接受 unpadded 的第三方服务对接)时按对接方约定选对函数即可。

【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询