关于 effect-ts
面向 Effect-TS 项目的开发指南技能,参考资料涵盖核心 API、模式、测试和工具。它明确规定类型安全的 Effect、Schema 建模、模式匹配与验证约定。
主要功能
- 01按主题划分的参考文档
- 02Effect 组合与数据类型指南
- 03带穷尽检查的标签联合匹配
- 04错误与资源处理模式
- 05测试及验证清单
适用场景
- 01按统一规范实现 Effect-TS 功能
- 02建模类型化 Schema 与错误
- 03审查标签联合分支处理
- 04准备测试和项目检查
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ pedronauck/ skills --skill effect-ts安装后在 Claude Code 中输入/effect-ts确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api-platform-observability.mdbatching-and-caching.mdclass-patterns.mdconcurrency-and-resources.mdconstruction-and-style.mdcontrol-flow-and-runtime.mdcore-patterns.mddata-and-testing.mddata-types.mderror-handling-patterns.mdfoundations.mdlibrary-development-patterns.mdpattern-matching.mdquality-tooling-and-resources.mdschema-errors-config.mdschema-transforms-and-filters.mdsink.mdstreams-deep-dive.mdtesting-patterns.md详细介绍
Effect-TS 开发指南
本项目中 Effect-TS 的指南、模式和最佳实践。
参考文档
编写代码前,请先阅读相关参考文档。references/core-patterns.md 是主索引。
| 参考文档 | 主题 |
|---|---|
references/foundations.md | 设置、导入、TypeScript 配置 |
references/construction-and-style.md | Effect.gen、pipe、Effect.fn、Effect.fnUntraced |
references/schema-errors-config.md | Schema 建模、错误、配置、重试 |
references/pattern-matching.md | Match.type、Match.value、Match.tag、Match.exhaustive — 标记联合类型必须使用 |
references/control-flow-and-runtime.md | Effect.if、Effect.when、循环、runSync、runPromise、ManagedRuntime |
references/data-types.md | 所有数据类型:Option、Either、Data、Exit、Cause、Duration、DateTime、BigDecimal、Chunk、HashSet、Redacted |
references/data-and-testing.md | Option/Either/Array 快速参考、@effect/vitest 设置 |
references/concurrency-and-resources.md | 并发、Scope、终结器、资源 |
references/streams-deep-dive.md | 创建、操作、分组、分区、广播、缓冲、限流、错误处理 |
references/sink.md | Sink 构造器、收集、折叠、操作、并发、剩余项、Stream.transduce |
references/batching-and-caching.md | 请求批处理(RequestResolver)、cachedWithTTL |
references/schema-transforms-and-filters.md | Schema.transform、Schema.filter、细化 |
references/api-platform-observability.md | HttpApi、日志、追踪、跨度 |
references/class-patterns.md | Context.Tag 服务模式、层、记忆化、测试 |
references/error-handling-patterns.md | Data.TaggedError、Schema.TaggedError、错误组合、恢复 |
references/library-development-patterns.md | 禁止的模式、Effect.fn 与 Effect.fnUntraced、资源管理 |
references/testing-patterns.md | 使用 assert、TestClock、服务模拟的 @effect/vitest |
references/quality-tooling-and-resources.md | 反模式、验证清单、包 |
核心原则
- Effect 不只是用于异步操作 — 对任何可能失败的操作都使用
Effect - 不可变性 — 使用 Effect 的不可变数据结构(
Data、Chunk、HashSet) - 类型安全 — 在类型中追踪错误。错误通道中不得使用
any或unknown - 组合 — 通过组合小型 Effect 来构建程序
- Schema 优先 — 使用带品牌类型的
Schema定义数据模型 - 模式匹配 — 对标记联合类型的所有分支判断都使用
Match(绝不要针对_tag使用switch/if-else) - Effect 数据类型 — 使用
Option(而不是 null)、Either(而不是临时方案)、Duration(而不是原始毫秒值)、DateTime(而不是 Date)、BigDecimal(而不是浮点数)、Redacted(用于机密信息)。请参阅references/data-types.md
快速参考
导入约定
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
import * as Match from "effect/Match";
import * as Option from "effect/Option";
import * as Either from "effect/Either";
import * as Data from "effect/Data";
import * as Duration from "effect/Duration";
import { pipe } from "effect/Function";
// Also: DateTime, BigDecimal, Chunk, HashSet, Exit, Cause, Redacted
Effect.gen 与 pipe 和 Effect.fn
// Effect.gen — complex logic with branching
Effect.gen(function* () {
const user = yield* fetchUser(id);
if (user.isAdmin) yield* logAdminAccess(user);
return user;
});
// pipe — linear transformations
pipe(fetchData(), Effect.map(transform), Effect.flatMap(save));
// Effect.fn — traced reusable functions (public API)
const processUser = Effect.fn("processUser")(function* (userId: string) {
const user = yield* getUser(userId);
return yield* processData(user);
});
错误处理
进程内区分错误时使用 Data.TaggedError,需要可序列化的错误时使用 Schema.TaggedError。请参阅 references/error-handling-patterns.md。
export class NotFoundError extends Data.TaggedError("NotFoundError")<{
id: string;
}> {}
// Recovery
pipe(riskyOp, Effect.catchTag("NotFoundError", (e) => Effect.succeed(null)));
模式匹配(标记联合类型必须使用)
始终使用 Match — Match.exhaustive 会在编译时捕获遗漏的分支。请参阅 references/pattern-matching.md。
// Match.type — reusable matcher function
const handle = Match.type<Status>().pipe(
Match.tag("Pending", (s) => `Pending since ${s.requestedAt}`),
Match.tag("Approved", (s) => `Approved by ${s.approvedBy}`),
Match.exhaustive // Compile error if any variant is missing
);
// Match.valueTags — shorthand for immediate matching
Match.valueTags(status, {
Pending: (s) => `Pending since ${s.requestedAt}`,
Approved: (s) => `Approved by ${s.approvedBy}`,
});
服务模式(Context.Tag)
有关包含工厂方法和层的完整模式,请参阅 references/class-patterns.md。
export class MyService extends Context.Tag("@myapp/MyService")<
MyService,
{ readonly find: (id: string) => Effect.Effect<Result, NotFoundError> }
>() {
static readonly layer = Layer.effect(MyService, Effect.gen(function* () {
const db = yield* Database;
return MyService.of({ find: MyService.createFind(db) });
}));
}
测试
重要:对 it.effect 使用来自 @effect/vitest 的 assert。不要在 it.effect 中使用 expect。请参阅 references/testing-patterns.md。
import { assert, describe, it } from "@effect/vitest";
it.effect("processes data", () =>
Effect.gen(function* () {
const result = yield* processData("input");
assert.strictEqual(result, "expected");
}).pipe(Effect.provide(MyService.testLayer))
);
禁止的模式
// NEVER: try-catch in Effect.gen — use Effect.exit instead
Effect.gen(function* () {
try { yield* someEffect } catch (e) { } // WRONG — will never catch
});
// NEVER: Type assertions
const value = something as any; // FORBIDDEN
const value = something as never; // FORBIDDEN
// NEVER: Missing return on terminal yield
Effect.gen(function* () {
if (bad) { yield* Effect.fail("err") } // Missing return!
});
// NEVER: switch/if-else on _tag — use Match instead
switch (status._tag) { /* no exhaustiveness checking! */ }
// NEVER: Effect.runSync inside Effects
Effect.gen(function* () { Effect.runSync(sideEffect) }); // Loses error tracking
// NEVER: Native JS where Effect data types exist
const x: string | null = null; // Use Option<string>
const delay = 5000; // Use Duration.seconds(5)
const now = new Date(); // Use DateTime.now or DateTime.unsafeNow()
const price = 0.1 + 0.2; // Use BigDecimal for precision
const secret = "sk-1234"; // Use Redacted.make("sk-1234")
// NEVER: expect with it.effect
it.effect("test", () => Effect.gen(function* () {
expect(result).toBe(value) // WRONG — use assert.strictEqual
}));
// NEVER: Inline layers (breaks memoization)
Layer.provide(Postgres.layer({ url })) // Store in constant instead
验证清单
- 导入使用
import * as Module from "effect/Module" - 复杂逻辑使用
Effect.gen,线性逻辑使用pipe,公共 API 使用Effect.fn - 所有
_tag分支都使用Match和Match.exhaustive - 为领域原语(ID、Email)使用品牌类型
- 错误:使用
Data.TaggedError(用于区分)或Schema.TaggedError(可序列化) - 错误通道中不使用
any/unknown,不使用类型断言 - 不在
Effect.gen中使用 try-catch — 使用Effect.exit - 对终止效果(
Effect.fail、Effect.interrupt)使用return yield* - 服务:使用带静态工厂方法和
Effect.fn跟踪的Context.Tag - 层:使用
Layer.merge/Layer.provide,在常量中定义参数化层 - 资源:使用
Effect.acquireRelease或Effect.scoped - 可空值使用
Option,同步成功/失败使用Either - 时间值使用
Duration,日期使用DateTime(而不是Date) - 金融/精确计算使用
BigDecimal,机密使用Redacted - 结构相等比较使用
Data.struct/Data.Class,集合使用HashSet - 使用
Clock.currentTimeMillis,而不是Date.now() - 测试:使用来自
@effect/vitest的assert(而不是expect)和it.effect - 运行
pnpm run typecheck和pnpm run test
参考实现
参阅 /packages/looper/src/data/api-client/api-client.ts,了解 Context.Tag 服务模式。
Effect Solutions CLI
pnpm exec effect-solutions list # List all topics
pnpm exec effect-solutions show <slug...> # Read topics
pnpm exec effect-solutions search <term> # Search by keyword
评论
支持 Markdown