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-solutionsEffect源码,涵盖最新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语言服务、项目结构、模块设置