effect-ts
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEffect-TS Developer Guide
Effect-TS 开发者指南
Guidelines, patterns, and best practices for Effect-TS in this project.
本项目中使用Effect-TS的规范、模式和最佳实践。
Reference Documents
参考文档
Read the relevant reference before writing code. is the master index.
references/core-patterns.md| Reference | Topics |
|---|---|
| Setup, imports, TypeScript config |
| |
| Schema modeling, errors, config, retry |
| |
| |
| All data types: |
| Option/Either/Array quick ref, |
| Concurrency, |
| Creating, operations, grouping, partitioning, broadcasting, buffering, throttling, error handling |
| Sink constructors, collecting, folding, operations, concurrency, leftovers, |
| Request batching ( |
| |
| |
| |
| |
| Forbidden patterns, |
| |
| Anti-patterns, validation checklist, packages |
编写代码前请阅读相关参考资料, 是主索引。
references/core-patterns.md| 参考文档 | 涵盖主题 |
|---|---|
| 环境搭建、导入规范、TypeScript 配置 |
| |
| Schema 建模、错误处理、配置、重试 |
| |
| |
| 所有数据类型: |
| Option/Either/Array 快速参考、 |
| 并发、 |
| 创建、操作、分组、分区、广播、缓冲、节流、错误处理 |
| Sink 构造函数、收集、折叠、操作、并发、剩余值、 |
| 请求批量处理( |
| |
| |
| |
| |
| 禁止模式、 |
| 配合 |
| 反模式、验证检查清单、依赖包 |
Core Principles
核心原则
- Effect is not just for async — Use for any fallible operation
Effect - Immutability — Use Effect's immutable data structures (,
Data,Chunk)HashSet - Type Safety — Track errors in types. No or
anyin error channelsunknown - Composition — Build programs by composing small Effects
- Schema-First — Define data models using with branded types
Schema - Pattern Matching — Use for all branching over tagged unions (never
Match/switchonif-else)_tag - Effect Data Types — Use (not null),
Option(not ad-hoc),Either(not raw ms),Duration(not Date),DateTime(not floats),BigDecimal(for secrets). SeeRedactedreferences/data-types.md
- Effect 不只是用于异步场景 — 所有可能出错的操作都可以使用
Effect - 不可变性 — 使用Effect提供的不可变数据结构(、
Data、Chunk)HashSet - 类型安全 — 在类型中追踪错误,错误通道中不允许出现 或
anyunknown - 可组合性 — 通过组合小型Effect来构建完整程序
- Schema优先 — 使用带品牌类型的 定义数据模型
Schema - 模式匹配 — 所有标记联合类型的分支逻辑都使用 实现(绝对不要对
Match使用_tag/switch)if-else - Effect数据类型 — 使用 (替代null)、
Option(替代临时错误处理)、Either(替代原始毫秒数)、Duration(替代Date)、DateTime(替代浮点数)、BigDecimal(处理敏感信息),详见Redactedreferences/data-types.md
Quick Reference
快速参考
Import Convention
导入规范
typescript
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, Redactedtypescript
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, RedactedEffect.gen vs pipe vs Effect.fn
Effect.gen vs pipe vs Effect.fn
typescript
// 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);
});typescript
// Effect.gen — 复杂逻辑带分支场景使用
Effect.gen(function* () {
const user = yield* fetchUser(id);
if (user.isAdmin) yield* logAdminAccess(user);
return user;
});
// pipe — 线性转换场景使用
pipe(fetchData(), Effect.map(transform), Effect.flatMap(save));
// Effect.fn — 可追踪的可复用函数(公共API)使用
const processUser = Effect.fn("processUser")(function* (userId: string) {
const user = yield* getUser(userId);
return yield* processData(user);
});Error Handling
错误处理
Data.TaggedErrorSchema.TaggedErrorreferences/error-handling-patterns.mdtypescript
export class NotFoundError extends Data.TaggedError("NotFoundError")<{
id: string;
}> {}
// Recovery
pipe(riskyOp, Effect.catchTag("NotFoundError", (e) => Effect.succeed(null)));Data.TaggedErrorSchema.TaggedErrorreferences/error-handling-patterns.mdtypescript
export class NotFoundError extends Data.TaggedError("NotFoundError")<{
id: string;
}> {}
// 错误恢复
pipe(riskyOp, Effect.catchTag("NotFoundError", (e) => Effect.succeed(null)));Pattern Matching (Mandatory for Tagged Unions)
模式匹配(标记联合类型强制使用)
Always use — catches missing cases at compile time. See .
MatchMatch.exhaustivereferences/pattern-matching.mdtypescript
// 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}`,
});请始终使用 — 可在编译阶段捕获缺失的分支情况,详见 。
MatchMatch.exhaustivereferences/pattern-matching.mdtypescript
// Match.type — 可复用的匹配函数
const handle = Match.type<Status>().pipe(
Match.tag("Pending", (s) => `Pending since ${s.requestedAt}`),
Match.tag("Approved", (s) => `Approved by ${s.approvedBy}`),
Match.exhaustive // 如果缺少任意分支会触发编译错误
);
// Match.valueTags — 立即匹配的简写方式
Match.valueTags(status, {
Pending: (s) => `Pending since ${s.requestedAt}`,
Approved: (s) => `Approved by ${s.approvedBy}`,
});Service Pattern (Context.Tag)
服务模式(Context.Tag)
See for full pattern with factory methods and layers.
references/class-patterns.mdtypescript
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) });
}));
}包含工厂方法和层的完整模式详见 。
references/class-patterns.mdtypescript
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) });
}));
}Testing
测试
CRITICAL: Use from for . Never with . See .
assert@effect/vitestit.effectexpectit.effectreferences/testing-patterns.mdtypescript
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))
);重要提示: 请配合 提供的 使用,绝对不要在 中使用 ,详见 。
it.effect@effect/vitestassertit.effectexpectreferences/testing-patterns.mdtypescript
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))
);Forbidden Patterns
禁止模式
typescript
// 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 insteadtypescript
// 绝对不要:在 Effect.gen 中使用 try-catch — 请改用 Effect.exit
Effect.gen(function* () {
try { yield* someEffect } catch (e) { } // 错误写法 — 永远不会捕获到错误
});
// 绝对不要:类型断言
const value = something as any; // 禁止
const value = something as never; // 禁止
// 绝对不要:终端 yield 缺少 return
Effect.gen(function* () {
if (bad) { yield* Effect.fail("err") } // 缺少 return!
});
// 绝对不要:对 _tag 使用 switch/if-else — 请改用 Match
switch (status._tag) { /* 没有穷尽性检查! */ }
// 绝对不要:在 Effect 内部使用 Effect.runSync
Effect.gen(function* () { Effect.runSync(sideEffect) }); // 会丢失错误追踪
// 绝对不要:已有对应 Effect 数据类型的场景下使用原生 JS 类型
const x: string | null = null; // 请使用 Option<string>
const delay = 5000; // 请使用 Duration.seconds(5)
const now = new Date(); // 请使用 DateTime.now 或 DateTime.unsafeNow()
const price = 0.1 + 0.2; // 请使用 BigDecimal 保证精度
const secret = "sk-1234"; // 请使用 Redacted.make("sk-1234")
// 绝对不要:在 it.effect 中使用 expect
it.effect("test", () => Effect.gen(function* () {
expect(result).toBe(value) // 错误写法 — 请使用 assert.strictEqual
}));
// 绝对不要:内联层(会破坏缓存)
Layer.provide(Postgres.layer({ url })) // 请存储为常量Validation Checklist
验证检查清单
- Imports use
import * as Module from "effect/Module" - for complex logic,
Effect.genfor linear,pipefor public APIEffect.fn - for all
Matchbranching with_tagMatch.exhaustive - Branded types for domain primitives (IDs, Emails)
- Errors: (discrimination) or
Data.TaggedError(serializable)Schema.TaggedError - No /
anyin error channels, no type assertionsunknown - No try-catch in — use
Effect.genEffect.exit - for terminal effects (
return yield*,Effect.fail)Effect.interrupt - Services: with static factory methods and
Context.TagtracingEffect.fn - Layers: /
Layer.merge, parameterized layers in constantsLayer.provide - Resources: or
Effect.acquireReleaseEffect.scoped - for nullable values,
Optionfor sync success/failureEither - for time values,
Durationfor dates (notDateTime)Date - for financial/precise math,
BigDecimalfor secretsRedacted - /
Data.structfor structural equality,Data.Classfor setsHashSet - instead of
Clock.currentTimeMillisDate.now() - Tests: from
assert(not@effect/vitest) withexpectit.effect - Run and
pnpm run typecheckpnpm run test
- 导入使用 格式
import * as Module from "effect/Module" - 复杂逻辑用 ,线性转换用
Effect.gen,公共API用pipeEffect.fn - 所有 分支逻辑使用
_tag且配合MatchMatch.exhaustive - 领域基元(ID、邮箱)使用品牌类型
- 错误使用 (进程内区分)或
Data.TaggedError(可序列化)Schema.TaggedError - 错误通道中没有 /
any,没有类型断言unknown - 不在 中使用 try-catch — 请使用
Effect.genEffect.exit - 终端 Effect(、
Effect.fail)使用Effect.interruptreturn yield* - 服务使用 搭配静态工厂方法和
Context.Tag追踪Effect.fn - 层使用 /
Layer.merge,参数化层定义为常量Layer.provide - 资源使用 或
Effect.acquireReleaseEffect.scoped - 可空值使用 ,同步成功/失败场景使用
OptionEither - 时间值使用 ,日期使用
Duration(不使用DateTime)Date - 金融/高精度计算使用 ,敏感信息使用
BigDecimalRedacted - 结构相等性判断使用 /
Data.struct,集合使用Data.ClassHashSet - 使用 替代
Clock.currentTimeMillisDate.now() - 测试使用 提供的
@effect/vitest(不使用assert)搭配expectit.effect - 执行 和
pnpm run typecheckpnpm run test
Reference Implementation
参考实现
See for Context.Tag service pattern.
/packages/looper/src/data/api-client/api-client.tsContext.Tag 服务模式可参考 。
/packages/looper/src/data/api-client/api-client.tsEffect Solutions CLI
Effect Solutions CLI
bash
pnpm exec effect-solutions list # List all topics
pnpm exec effect-solutions show <slug...> # Read topics
pnpm exec effect-solutions search <term> # Search by keywordbash
pnpm exec effect-solutions list # 列出所有主题
pnpm exec effect-solutions show <slug...> # 查看主题内容
pnpm exec effect-solutions search <term> # 按关键词搜索