effect-ts

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Effect-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 (
effect
or
@effect/*
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.
  • If the
    effect_source
    tool is available, use it for
    status
    ,
    hydrate
    , and
    search
    instead of hand-rolled shell commands.
  • 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
    .agent-sources/
    to
    .git/info/exclude
    , not the project
    .gitignore
    , unless Joel explicitly wants it committed.
  • Search the mirror for current patterns and APIs, especially under
    packages/effect/src/
    and package tests/examples, before calling something an Effect best practice.
在任何使用Effect的仓库中(package/dependency文件包含
effect
或
@effect/*
),编写、审查或重构Effect代码前,请先参考官方Effect源码。不要仅依赖过时的记忆、博客文章或高层文档。
  • 如果
    effect_source
    工具可用,请使用它执行
    status
    、
    hydrate
    和
    search
    操作,而非手动编写shell命令。
  • 首先检查仓库本地的浅源码镜像:
    .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
    ,而非项目的
    .gitignore
    ,除非Joel明确要求提交。
  • 在称某内容为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.gen
provides sequential, readable composition (like async/await for Effect):
typescript
import { Effect } from "effect"

const program = Effect.gen(function* () {
  const data = yield* fetchData
  yield* Effect.logInfo(`Processing: ${data}`)
  return yield* processData(data)
})
Effect.fn
adds call-site tracing and named spans. Use for all service methods:
typescript
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.gen
提供顺序化、可读性强的组合方式(类似Effect版的async/await):
typescript
import { Effect } from "effect"

const program = Effect.gen(function* () {
  const data = yield* fetchData
  yield* Effect.logInfo(`Processing: ${data}`)
  return yield* processData(data)
})
Effect.fn
添加调用站点追踪和命名跨度。所有服务方法均应使用:
typescript
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
Layer.effect
or
Layer.sync
, using
Effect.fn
for all methods:
typescript
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
    @app/ServiceName
    pattern
  • Service methods should have
    R = never
    (dependencies via Layer, not method signatures)
  • Use
    readonly
    properties
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.effect
或
Layer.sync
实现服务,所有方法均使用
Effect.fn
:
typescript
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
    格式
  • 服务方法应设置
    R = never
    (依赖通过Layer注入,而非方法签名)
  • 使用
    readonly
    属性
如需了解服务驱动开发、测试层、层记忆化及完整组合模式,请查看references/services-and-layers.md。

Schema.Class and Branded Types

Schema.Class 和 Branded Types

Use
Schema.Class
for domain records. Brand all entity IDs and domain primitives:
typescript
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
Schema.TaggedClass
+
Schema.Union
for variants (OR types):
typescript
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.
使用
Schema.Class
定义领域记录。为所有实体ID和领域原语添加品牌标识:
typescript
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.TaggedClass
+
Schema.Union
定义变体(或类型):
typescript
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
Schema.TaggedErrorClass
. They are yieldable (no
Effect.fail
needed):
typescript
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
catchTag
/
catchTags
:
typescript
// 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.
使用
Schema.TaggedErrorClass
定义领域错误。这类错误可直接yield(无需
Effect.fail
):
typescript
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
})
使用
catchTag
/
catchTags
进行恢复:
typescript
// 单个标签
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
Layer.provideMerge
(incremental, flat types) and
Layer.merge
(parallel):
typescript
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
    Layer.sync
    for synchronous implementations,
    Layer.effect
    for effectful ones
使用
Layer.provideMerge
(增量式、扁平类型)和
Layer.merge
(并行式)组合层:
typescript
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.sync
    ,有副作用的实现使用
    Layer.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
    it.effect
    for Effect-based tests (provides TestContext with TestClock)
  • Use
    it.live
    for real time / real clock
  • Provide fresh layers per test to prevent state leakage
  • Use
    it.layer
    only when sharing expensive resources across a suite
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))
)
  • 使用
    it.effect
    编写基于Effect的测试(提供带有TestClock的TestContext)
  • 使用
    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 NotDo Instead
console.log(...)
Effect.log(...)
with structured data
process.env.KEY
Config.string("KEY")
or
Config.redacted("KEY")
throw new Error()
inside
Effect.gen
yield* new TaggedError({...})
or
Effect.fail(...)
Effect.runSync(...)
inside services
Keep everything effectful
Effect.catchAll(() => ...)
losing type info
Effect.catchTag
/
Effect.catchTags
null
/
undefined
in domain types
Option<T>
with
Option.match
Option.getOrThrow(...)
Option.match({ onNone, onSome })
or
Option.getOrElse
Effect.Service
(v3)
ServiceMap.Service
(v4)
Schema.TaggedError<T>()
(v3)
Schema.TaggedErrorClass("Tag")("Tag", {...})
(v4)
Scatter
Effect.provide
calls
Provide once at app entry
Call parameterized layer constructors inlineStore layers in constants (memoization)
请勿使用推荐做法
console.log(...)
使用带结构化数据的
Effect.log(...)
process.env.KEY
使用
Config.string("KEY")
或
Config.redacted("KEY")
在
Effect.gen
中使用
throw new Error()
使用
yield* new TaggedError({...})
或
Effect.fail(...)
在服务中使用
Effect.runSync(...)
保持所有操作均为effectful
使用
Effect.catchAll(() => ...)
丢失类型信息
使用
Effect.catchTag
/
Effect.catchTags
在领域类型中使用
null
/
undefined
使用
Option<T>
配合
Option.match
使用
Option.getOrThrow(...)
使用
Option.match({ onNone, onSome })
或
Option.getOrElse
使用
Effect.Service
(v3版本)
使用
ServiceMap.Service
(v4版本)
使用
Schema.TaggedError<T>()
(v3版本)
使用
Schema.TaggedErrorClass("Tag")("Tag", {...})
(v4版本)
分散调用
Effect.provide
在应用入口一次性提供
内联调用参数化层构造函数将层存储在常量中(记忆化)

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语言服务、项目结构、模块设置