关于 effect-typescript
这是一项指导 TypeScript 开发者使用 Effect 处理 I/O 和资源管理的编码技能,并针对 darkmatter 项目提供约定,明确错误、依赖、清理、测试及运行时边界。
主要功能
- 01Effect I/O 使用指导
- 02类型化错误与服务层
- 03资源生命周期和清理模式
- 04darkmatter 的 Bun 与 Alchemy 约定
- 05effect-orpc 使用指导
适用场景
- 01为 TypeScript I/O 代码引入 Effect
- 02审查现有 Effect 实现
- 03使用 effect-orpc 设计类型化 RPC 处理器
- 04为 darkmatter 构建 TypeScript 基础设施
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ darkmatter/ skills --skill effect-typescript安装后在 Claude Code 中输入/effect-typescript确认已加载。
附带文件effect-package-map.md
详细介绍
Effect TypeScript
使用 Effect 处理应用 I/O,让失败、依赖项和资源生命周期都明确表达。只在边界处适配一次基于 Promise 的驱动程序或 SDK;内部操作保持使用 Effect。在需要 Promise 或启动程序的调用方或运行时边界运行 Effect。纯转换仍使用普通 TypeScript。
遵循 codebase-design 中的共享可读性规则和示例。将相关查询、绑定、解码和私有辅助函数保留在其适配器中;仅在能隐藏复杂性或实现有用复用时才提取。操作应自行负责完成、错误和清理,或者明确将这些责任交给调用方。
此技能将 Effect 指南适配到 darkmatter 约定:darkmatter 项目使用 Bun 命令而非 pnpm,并优先使用 Alchemy 部署基础设施。
Bootstrap
如果 $PATH 中没有 effect-solutions,请安装:bun add -g effect-solutions@latest。API 详情优先参考仓库中固定版本的 Effect 源码或已安装的包;两者都不可用时,使用与版本匹配的检出版本。
对于新的 Darkmatter TypeScript 仓库,请使依赖项与
darkmatter/template 根目录的目录保持一致。使用 Effect 4、@effect/tsgo、
@effect/vitest、@effect/platform-bun 和 effect-solutions;在自行设计 Agent 循环契约之前,优先使用 Effect Agent API(effect/unstable/ai)。
Guidelines
- 编写有副作用的代码时,参考
effect-solutionsCLI 推荐的最佳实践。 - 如果需要更具体的信息,参考目录中有一份本地检出的 Effect 源码。
When to use
- 涉及有意义的 I/O,且工作适合通过显式的失败、依赖项、资源、重试、验证或测试边界来处理。
- 代码已经使用 Effect,而你正在添加或审查 Effect 代码。
- 你正在决定 TypeScript/Bun 功能是否应使用 Effect。
- 任务涉及外部 API、数据库、队列、工作进程、CLI、计划任务、重试、配置、机密、结构化日志、运行时验证、资源清理或并发工作流。
- 你需要类型化的领域错误,而不是缺乏结构的抛出异常。
- 你需要通过服务和 Layers 切换线上/测试实现。
- 你正在部署 TypeScript 基础设施或工作进程,并需要遵循适配 Alchemy 的约定。
When NOT to use
- 小型一次性脚本用普通 TypeScript 就能清晰实现:读取一个文件、转换纯数据、写入一个文件,没有重试、注入依赖项或长期存在的资源。随着需求增长,在需要 I/O 的地方采用 Effect,并提取职责清晰的部分。仅凭代码行数并不足以成为拆散实现的理由;如有需要,应保留已配置的限制,并针对例外情况提供说明。
- 纯函数、简单的数据映射器、UI 本地状态或少量胶水代码不需要 Effect 包装。
- 项目没有 Effect 依赖,且该功能并不受益于类型化错误、Layers、资源安全、重试或可观测性。
- 团队只需要用普通异步代码快速修复问题。不要为了顺手重构而引入 Effect。
- 你无法说明服务、层、错误和测试的设计。请先停下来设计,而不是到处添加
Effect.runPromise调用。
Darkmatter 约定
- 使用 Bun 命令:
bun install、bun test、bun run <script>、bunx <tool>。 - 将上游的
pnpm示例机械地转换为 Bun 命令。例如:如果项目使用 Bun test,将pnpm test file.test.ts转换为bun test file.test.ts;如果test是 package 脚本,则转换为bun run test file.test.ts。 - 在相关场景中优先使用 Bun 运行时包,例如 Bun 入口点使用
@effect/platform-bun和BunRuntime.runMain。 - 对可部署的基础设施优先使用 Alchemy。将基础设施代码放在
alchemy.run.ts中,使用 Alchemy 创建资源,将资源绑定到 workers/services,并构建Alchemy.Stack。如果要编写 Alchemy 代码或添加新的基础设施代码,请参考 alchemy 技能。 - 应用数据优先使用 Postgres。对于查询较多的 TypeScript 适配器,使用 Kysely +
pg;如果通过 Effect 服务和 Layers 暴露持久化能力,则使用 Effect SQL Postgres。D1/sqlite 仅用于范围有限的边缘本地状态、演示,或项目明确决定采用的场景。 - 配置来自命名配置文件(
config/<name>.json),通过Config读取。没有选择器标志:交互式运行会从列表中选择(Prompt.select),非交互式运行会读取APP_CONFIG,或在列出配置名称后失败。环境变量和标志按键覆盖,并通过ConfigProvider.orElse链接;密钥来自同级的.sops.json文件,并由effect-sops读取。不要将程序的配置设计成标志或环境变量列表。参见 ADR-0014。
使用 effect-orpc 的类型化 RPC
当服务需要类型化 RPC 过程、生成或共享的 oRPC 客户端契约、Effect 服务注入以及类型化的 Effect 错误时,使用 effect-orpc。当路由是简单的 HTTP、webhook、静态内容、流式传输/SSE,或添加 RPC 契约只会增加繁琐流程且没有面向客户端的类型契约时,使用原生 Effect HttpRouter 或 Hono。
添加 effect-orpc 时,在 catalog 中固定 Effect 4 构建版本:
{
"workspaces": {
"catalog": {
"effect-orpc": "1.0.0-effect-v4.8",
"@orpc/server": "^1.13.0",
"@orpc/client": "^1.13.0",
"@orpc/contract": "^1.13.0",
"@orpc/shared": "^1.13.0"
}
}
}
不要安装 effect-orpc@latest;npm 的 latest 指向 Effect 3 系列(0.5.0)。目前,effect-v4 dist-tag 指向 1.0.0-effect-v4.8,其 peer 依赖要求使用 Effect 4(effect >=4.0.0-beta.98)以及上文所列的 oRPC 包。
优先采用以下上游模式:
- 从
eos.provide(AppLayer)或makeEffectORPC(runtime)构建过程。当一个作用域 Layer 应在应用关闭时获取一次并释放时(例如 Postgres 连接池、遥测 SDK 或共享缓存),使用由调用方负责管理的ManagedRuntime。 - 使用
.effect(function* (...) { ... })编写处理程序和 Effect 中间件,并使用生成器.use(function* ({ next }) { return yield* next(...) }),以确保服务需求、spans、取消操作和错误都留在 Effect 中。 - 使用
ORPCTaggedError建模 HTTP 错误,并将这些类传给.errors(...)/eoc.errors(...);不要将相同的错误契约重复定义为无关的抛出异常和响应 schemas。 - 如果仓库采用契约优先模式且已有 oRPC 契约,使用
implementEffect(contract, layerOrRuntime)。契约叶节点保留已声明的输入、输出和错误接口,同时添加.effect(...)处理程序。 - 仅当需要 Node
AsyncLocalStorage来跨原生 oRPC 中间件或框架边界保留 FiberRefs 时,才导入effect-orpc/node。如果请求本地的 FiberRef 状态是在 oRPC 流水线外创建、且必须能在处理程序中访问,则使用withFiberContext(() => next())。
包布局参考
请参阅 effect-package-map.md,了解领域归属、公共包入口、适配器、工作流、测试和运行时边界。该文档描述的是职责,而不是为每个函数或 Effect 导出指定文件。
上游参考
需要当前 Effect 源码或示例时,请使用 reference/effect 或 effect-solutions CLI,而不要依赖记忆:
reference/effect/AGENTS.md— 上游仓库规则,包括 pnpm 验证命令、生成的 barrel、changeset 和it.effect约定。reference/effect/packages/effect/— 核心库源码和测试。reference/effect/packages/platform-bun/和reference/effect/packages/platform-node/— 运行时/平台示例。reference/effect/packages/vitest/— 支持 Effect 的 Vitest 辅助工具。
如果全新检出时缺少子模块,请先初始化它,再使用本地参考资料:
git submodule update --init skills/effect-typescript/reference/effect
对于 darkmatter 应用开发,请将子模块视为只读,并将上游 pnpm 命令转换为仓库使用的包管理器。对于直接向上游 Effect 贡献代码,请严格遵循 reference/effect/AGENTS.md;不要在上游仓库中使用 darkmatter 的 Bun 默认设置。
JSON 编码与解码
使用 Schema.fromJsonString 解析 JSON 字符串,并通过你的 schema 一步完成验证。这样可以将 JSON.parse 与 schema 解码合并为一步,并在反向操作中将 JSON.stringify 与 schema 编码合并为一步:
import { Effect, Schema } from "effect"
const Row = Schema.Literals(["A", "B", "C", "D", "E", "F", "G", "H"])
const Column = Schema.Literals(["1", "2", "3", "4", "5", "6", "7", "8"])
class Position extends Schema.Class<Position>("Position")({
row: Row,
column: Column,
}) {}
class Move extends Schema.Class<Move>("Move")({
from: Position,
to: Position,
}) {}
// fromJsonString combines JSON.parse + schema decoding
// MoveFromJson is a schema that takes a JSON string and returns a Move
const MoveFromJson = Schema.fromJsonString(Move)
const program = Effect.gen(function* () {
// Parse and validate JSON string in one step
// Use MoveFromJson (not Move) to decode from JSON string
const jsonString = '{"from":{"row":"A","column":"1"},"to":{"row":"B","column":"2"}}'
const move = yield* Schema.decodeUnknownEffect(MoveFromJson)(jsonString)
yield* Effect.log("Decoded move", move)
// Encode to JSON string in one step (typed as string)
// Use MoveFromJson (not Move) to encode to JSON string
const json = yield* Schema.encodeEffect(MoveFromJson)(move)
return json
})