effect-ts
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEffect-TS (v4)
Effect-TS (v4)
Patterns from effect-solutions and the Effect source. This covers the latest v4 APIs.
本指南内容源自effect-solutions及Effect源码,涵盖最新v4版本API的使用模式。
Source-First Rule
源码优先原则
When working in any repo that uses Effect ( or in package/dependency files), reference the official Effect source before writing, reviewing, or refactoring Effect code. Do not rely on stale memory, blog posts, or high-level docs alone.
effect@effect/*- If the tool is available, use it for
effect_source,status, andhydrateinstead of hand-rolled shell commands.search - First check for a repo-local shallow source mirror at .
.agent-sources/effect/ - If it is missing, create it before doing Effect work:
mkdir -p .agent-sources && git clone --depth 1 --filter=blob:none https://github.com/effect-ts/effect.git .agent-sources/effect - Keep the mirror out of product commits. If needed, add to
.agent-sources/, not the project.git/info/exclude, unless Joel explicitly wants it committed..gitignore - Search the mirror for current patterns and APIs, especially under and package tests/examples, before calling something an Effect best practice.
packages/effect/src/
在任何使用Effect的仓库中(package/dependency文件包含或),编写、审查或重构Effect代码前,请先参考官方Effect源码。不要仅依赖过时的记忆、博客文章或高层文档。
effect@effect/*- 如果工具可用,请使用它执行
effect_source、status和hydrate操作,而非手动编写shell命令。search - 首先检查仓库本地的浅源码镜像:。
.agent-sources/effect/ - 如果镜像缺失,请在开展Effect相关工作前创建:
mkdir -p .agent-sources && git clone --depth 1 --filter=blob:none https://github.com/effect-ts/effect.git .agent-sources/effect - 不要将该镜像提交到产品代码中。如有需要,将添加到
.agent-sources/,而非项目的.git/info/exclude,除非Joel明确要求提交。.gitignore - 在称某内容为Effect最佳实践前,请先在镜像中搜索当前的模式和API,尤其关注目录及包的测试/示例代码。
packages/effect/src/
Local Source References
本地源码参考
- repo-local Effect source mirror (canonical for current work):
.agent-sources/effect/ - effect-solutions (best practices, docs, examples):
~/Code/kitlangton/effect-solutions/ - fallback global Effect monorepo:
~/Code/effect-ts/effect/ - Search source for implementations:
grep -r "pattern" .agent-sources/effect/packages/effect/src/
- 仓库本地Effect源码镜像(当前工作的标准参考):
.agent-sources/effect/ - effect-solutions(最佳实践、文档、示例):
~/Code/kitlangton/effect-solutions/ - 备用全局Effect单仓库:
~/Code/effect-ts/effect/ - 搜索源码实现:
grep -r "pattern" .agent-sources/effect/packages/effect/src/
Effect.gen and Effect.fn
Effect.gen 和 Effect.fn
Effect.gentypescript
import { Effect } from "effect"
const program = Effect.gen(function* () {
const data = yield* fetchData
yield* Effect.logInfo(`Processing: ${data}`)
return yield* processData(data)
})Effect.fntypescript
const processUser = Effect.fn("processUser")(function* (userId: string) {
yield* Effect.logInfo(`Processing user ${userId}`)
const user = yield* getUser(userId)
return yield* processData(user)
})
// Second argument for cross-cutting concerns (retry, timeout)
const fetchWithRetry = Effect.fn("fetchWithRetry")(
function* (url: string) {
const data = yield* fetchData(url)
return yield* processData(data)
},
flow(
Effect.retry(Schedule.recurs(3)),
Effect.timeout("5 seconds")
)
)Effect.gentypescript
import { Effect } from "effect"
const program = Effect.gen(function* () {
const data = yield* fetchData
yield* Effect.logInfo(`Processing: ${data}`)
return yield* processData(data)
})Effect.fntypescript
const processUser = Effect.fn("processUser")(function* (userId: string) {
yield* Effect.logInfo(`Processing user ${userId}`)
const user = yield* getUser(userId)
return yield* processData(user)
})
// 第二个参数用于横切关注点(重试、超时)
const fetchWithRetry = Effect.fn("fetchWithRetry")(
function* (url: string) {
const data = yield* fetchData(url)
return yield* processData(data)
},
flow(
Effect.retry(Schedule.recurs(3)),
Effect.timeout("5 seconds")
)
)ServiceMap.Service
ServiceMap.Service
Define services as classes with a unique tag and typed interface:
typescript
import { Effect, ServiceMap } from "effect"
class Database extends ServiceMap.Service<
Database,
{
readonly query: (sql: string) => Effect.Effect<unknown[]>
readonly execute: (sql: string) => Effect.Effect<void>
}
>()("@app/Database") {}Implement with or , using for all methods:
Layer.effectLayer.syncEffect.fntypescript
import { Effect, Layer } from "effect"
class Users extends ServiceMap.Service<
Users,
{
readonly findById: (id: UserId) => Effect.Effect<User, UserNotFoundError>
readonly all: () => Effect.Effect<readonly User[]>
}
>()("@app/Users") {
static readonly layer = Layer.effect(
Users,
Effect.gen(function* () {
const http = yield* HttpClient.HttpClient
const findById = Effect.fn("Users.findById")(function* (id: UserId) {
const response = yield* http.get(`/users/${id}`)
return yield* HttpClientResponse.schemaBodyJson(User)(response)
})
const all = Effect.fn("Users.all")(function* () {
const response = yield* http.get("/users")
return yield* HttpClientResponse.schemaBodyJson(Schema.Array(User))(response)
})
return { findById, all }
})
)
}Rules:
- Tag identifiers must be unique. Use pattern
@app/ServiceName - Service methods should have (dependencies via Layer, not method signatures)
R = never - Use properties
readonly
See references/services-and-layers.md for service-driven development, test layers, layer memoization, and full composition patterns.
将服务定义为带有唯一标签和类型化接口的类:
typescript
import { Effect, ServiceMap } from "effect"
class Database extends ServiceMap.Service<
Database,
{
readonly query: (sql: string) => Effect.Effect<unknown[]>
readonly execute: (sql: string) => Effect.Effect<void>
}
>()("@app/Database") {}使用或实现服务,所有方法均使用:
Layer.effectLayer.syncEffect.fntypescript
import { Effect, Layer } from "effect"
class Users extends ServiceMap.Service<
Users,
{
readonly findById: (id: UserId) => Effect.Effect<User, UserNotFoundError>
readonly all: () => Effect.Effect<readonly User[]>
}
>()("@app/Users") {
static readonly layer = Layer.effect(
Users,
Effect.gen(function* () {
const http = yield* HttpClient.HttpClient
const findById = Effect.fn("Users.findById")(function* (id: UserId) {
const response = yield* http.get(`/users/${id}`)
return yield* HttpClientResponse.schemaBodyJson(User)(response)
})
const all = Effect.fn("Users.all")(function* () {
const response = yield* http.get("/users")
return yield* HttpClientResponse.schemaBodyJson(Schema.Array(User))(response)
})
return { findById, all }
})
)
}规则:
- 标签标识符必须唯一,使用格式
@app/ServiceName - 服务方法应设置(依赖通过Layer注入,而非方法签名)
R = never - 使用属性
readonly
如需了解服务驱动开发、测试层、层记忆化及完整组合模式,请查看references/services-and-layers.md。
Schema.Class and Branded Types
Schema.Class 和 Branded Types
Use for domain records. Brand all entity IDs and domain primitives:
Schema.Classtypescript
import { Schema } from "effect"
const UserId = Schema.String.pipe(Schema.brand("UserId"))
type UserId = typeof UserId.Type
const Email = Schema.String.pipe(Schema.brand("Email"))
type Email = typeof Email.Type
class User extends Schema.Class("User")({
id: UserId,
name: Schema.String,
email: Email,
createdAt: Schema.Date,
}) {
get displayName() { return `${this.name} (${this.email})` }
}
// Construct with makeUnsafe for brands
const userId = UserId.makeUnsafe("user-123")Use + for variants (OR types):
Schema.TaggedClassSchema.Uniontypescript
import { Match, Schema } from "effect"
class Success extends Schema.TaggedClass("Success")("Success", {
value: Schema.Number,
}) {}
class Failure extends Schema.TaggedClass("Failure")("Failure", {
error: Schema.String,
}) {}
const Result = Schema.Union([Success, Failure])
type Result = typeof Result.Type
// Exhaustive pattern matching
const render = (r: Result) => Match.valueTags(r, {
Success: ({ value }) => `Got: ${value}`,
Failure: ({ error }) => `Error: ${error}`,
})See references/data-modeling.md for JSON encoding, Schema.Literals, validation, and full patterns.
使用定义领域记录。为所有实体ID和领域原语添加品牌标识:
Schema.Classtypescript
import { Schema } from "effect"
const UserId = Schema.String.pipe(Schema.brand("UserId"))
type UserId = typeof UserId.Type
const Email = Schema.String.pipe(Schema.brand("Email"))
type Email = typeof Email.Type
class User extends Schema.Class("User")({
id: UserId,
name: Schema.String,
email: Email,
createdAt: Schema.Date,
}) {
get displayName() { return `${this.name} (${this.email})` }
}
// 使用makeUnsafe构造带品牌标识的类型
const userId = UserId.makeUnsafe("user-123")使用 + 定义变体(或类型):
Schema.TaggedClassSchema.Uniontypescript
import { Match, Schema } from "effect"
class Success extends Schema.TaggedClass("Success")("Success", {
value: Schema.Number,
}) {}
class Failure extends Schema.TaggedClass("Failure")("Failure", {
error: Schema.String,
}) {}
const Result = Schema.Union([Success, Failure])
type Result = typeof Result.Type
// 穷举模式匹配
const render = (r: Result) => Match.valueTags(r, {
Success: ({ value }) => `Got: ${value}`,
Failure: ({ error }) => `Error: ${error}`,
})如需了解JSON编码、Schema.Literals、验证及完整模式,请查看references/data-modeling.md。
Schema.TaggedErrorClass
Schema.TaggedErrorClass
Define domain errors with . They are yieldable (no needed):
Schema.TaggedErrorClassEffect.failtypescript
import { Schema } from "effect"
class UserNotFoundError extends Schema.TaggedErrorClass("UserNotFoundError")(
"UserNotFoundError",
{ userId: UserId, message: Schema.String }
) {}
// Yieldable: yield directly in generators
const getUser = Effect.fn("getUser")(function* (id: UserId) {
const user = yield* findUser(id)
if (!user) yield* new UserNotFoundError({ userId: id, message: "Not found" })
return user
})Recover with / :
catchTagcatchTagstypescript
// Single tag
const recovered = program.pipe(
Effect.catchTag("UserNotFoundError", (e) =>
Effect.succeed(`User ${e.userId} missing`)
)
)
// Multiple tags
const recovered2 = program.pipe(
Effect.catchTags({
UserNotFoundError: (e) => Effect.succeed("not found"),
ValidationError: (e) => Effect.succeed("invalid"),
})
)See references/error-handling.md for defects, Schema.Defect, and recovery patterns.
使用定义领域错误。这类错误可直接yield(无需):
Schema.TaggedErrorClassEffect.failtypescript
import { Schema } from "effect"
class UserNotFoundError extends Schema.TaggedErrorClass("UserNotFoundError")(
"UserNotFoundError",
{ userId: UserId, message: Schema.String }
) {}
// 可直接yield:在生成器中直接抛出
const getUser = Effect.fn("getUser")(function* (id: UserId) {
const user = yield* findUser(id)
if (!user) yield* new UserNotFoundError({ userId: id, message: "Not found" })
return user
})使用 / 进行恢复:
catchTagcatchTagstypescript
// 单个标签
const recovered = program.pipe(
Effect.catchTag("UserNotFoundError", (e) =>
Effect.succeed(`User ${e.userId} missing`)
)
)
// 多个标签
const recovered2 = program.pipe(
Effect.catchTags({
UserNotFoundError: (e) => Effect.succeed("not found"),
ValidationError: (e) => Effect.succeed("invalid"),
})
)如需了解缺陷、Schema.Defect及恢复模式,请查看references/error-handling.md。
Layer Composition
Layer 组合
Compose layers with (incremental, flat types) and (parallel):
Layer.provideMergeLayer.mergetypescript
import { Effect, Layer } from "effect"
// Compose layers for the app
const appLayer = UserService.layer.pipe(
Layer.provideMerge(DatabaseLayer),
Layer.provideMerge(LoggerLayer),
Layer.provideMerge(ConfigLayer),
)
// Provide once at the entry point
const main = program.pipe(Effect.provide(appLayer))
Effect.runPromise(main)Key rules:
- Store parameterized layers in constants (layer memoization by reference identity)
- Provide once at app entry, not scattered throughout code
- Use for synchronous implementations,
Layer.syncfor effectful onesLayer.effect
使用(增量式、扁平类型)和(并行式)组合层:
Layer.provideMergeLayer.mergetypescript
import { Effect, Layer } from "effect"
// 组合应用所需的层
const appLayer = UserService.layer.pipe(
Layer.provideMerge(DatabaseLayer),
Layer.provideMerge(LoggerLayer),
Layer.provideMerge(ConfigLayer),
)
// 在入口点一次性提供层
const main = program.pipe(Effect.provide(appLayer))
Effect.runPromise(main)核心规则:
- 将参数化层存储在常量中(通过引用标识实现层记忆化)
- 在应用入口一次性提供层,不要分散在代码各处
- 同步实现使用,有副作用的实现使用
Layer.syncLayer.effect
Testing Quick Start
测试快速入门
typescript
import { describe, expect, it } from "@effect/vitest"
import { Effect, Layer } from "effect"
it.effect("queries database", () =>
Effect.gen(function* () {
const db = yield* Database
const results = yield* db.query("SELECT *")
expect(results.length).toBe(2)
}).pipe(Effect.provide(Database.testLayer))
)- Use for Effect-based tests (provides TestContext with TestClock)
it.effect - Use for real time / real clock
it.live - Provide fresh layers per test to prevent state leakage
- Use only when sharing expensive resources across a suite
it.layer
See references/testing.md for the full worked example and advanced patterns.
typescript
import { describe, expect, it } from "@effect/vitest"
import { Effect, Layer } from "effect"
it.effect("queries database", () =>
Effect.gen(function* () {
const db = yield* Database
const results = yield* db.query("SELECT *")
expect(results.length).toBe(2)
}).pipe(Effect.provide(Database.testLayer))
)- 使用编写基于Effect的测试(提供带有TestClock的TestContext)
it.effect - 使用进行实时/真实时钟测试
it.live - 为每个测试提供全新的层,防止状态泄漏
- 仅在套件间共享昂贵资源时使用
it.layer
如需完整示例及高级模式,请查看references/testing.md。
Pipe for Instrumentation
使用Pipe进行 instrumentation
typescript
const program = fetchData.pipe(
Effect.timeout("5 seconds"),
Effect.retry(Schedule.exponential("100 millis").pipe(
Schedule.compose(Schedule.recurs(3))
)),
Effect.tap((data) => Effect.logInfo(`Fetched: ${data}`)),
Effect.withSpan("fetchData"),
)typescript
const program = fetchData.pipe(
Effect.timeout("5 seconds"),
Effect.retry(Schedule.exponential("100 millis").pipe(
Schedule.compose(Schedule.recurs(3))
)),
Effect.tap((data) => Effect.logInfo(`Fetched: ${data}`)),
Effect.withSpan("fetchData"),
)Anti-Patterns
反模式
| Do Not | Do Instead |
|---|---|
| |
| |
| |
| Keep everything effectful |
| |
| |
| |
| |
| |
Scatter | Provide once at app entry |
| Call parameterized layer constructors inline | Store layers in constants (memoization) |
| 请勿使用 | 推荐做法 |
|---|---|
| 使用带结构化数据的 |
| 使用 |
在 | 使用 |
在服务中使用 | 保持所有操作均为effectful |
使用 | 使用 |
在领域类型中使用 | 使用 |
使用 | 使用 |
使用 | 使用 |
使用 | 使用 |
分散调用 | 在应用入口一次性提供 |
| 内联调用参数化层构造函数 | 将层存储在常量中(记忆化) |
Reference Files
参考文件
Load these as needed for deeper patterns:
- Services & Layers: ServiceMap.Service, service-driven development, test layers, layer memoization, provide vs provideMerge
- Data Modeling: Schema.Class, branded types, variants, Match.valueTags, JSON encoding
- Schema Decisions: Schema.Class vs Struct vs TaggedClass decision flowchart, migration patterns
- Error Handling: Schema.TaggedErrorClass, catch/catchTag/catchTags, defects, Schema.Defect, TypeId/refail patterns
- Testing: @effect/vitest setup, it.effect/it.live/it.layer, TestClock, Effect.flip, FiberRef isolation, worked example
- HTTP Clients: HttpClient, request building, response decoding, middleware, retries, typed API service
- CLI: Command.make, Arguments, Flags, subcommands, worked task manager example
- Config: Config module, schema validation, ConfigProvider, Redacted, config layers
- Processes & Scopes: Fork types, Scope.extend, Command for child processes, killable background tasks
- Setup: tsconfig, Effect Language Service, project structure, module settings
按需加载以下文件以了解更深入的模式:
- 服务与层:ServiceMap.Service、服务驱动开发、测试层、层记忆化、provide与provideMerge对比
- 数据建模:Schema.Class、品牌类型、变体、Match.valueTags、JSON编码
- Schema决策:Schema.Class、Struct、TaggedClass决策流程图、迁移模式
- 错误处理:Schema.TaggedErrorClass、catch/catchTag/catchTags、缺陷、Schema.Defect、TypeId/refail模式
- 测试:@effect/vitest配置、it.effect/it.live/it.layer、TestClock、Effect.flip、FiberRef隔离、完整示例
- HTTP客户端:HttpClient、请求构建、响应解码、中间件、重试、类型化API服务
- CLI:Command.make、参数、标志、子命令、任务管理器示例
- 配置:Config模块、Schema验证、ConfigProvider、Redacted、配置层
- 进程与作用域:Fork类型、Scope.extend、子进程Command、可终止后台任务
- 项目搭建:tsconfig、Effect语言服务、项目结构、模块设置