effect-ts

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Effect-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.
references/core-patterns.md
is the master index.
ReferenceTopics
references/foundations.md
Setup, imports, TypeScript config
references/construction-and-style.md
Effect.gen
,
pipe
,
Effect.fn
,
Effect.fnUntraced
references/schema-errors-config.md
Schema modeling, errors, config, retry
references/pattern-matching.md
Match.type
,
Match.value
,
Match.tag
,
Match.exhaustive
— mandatory for tagged unions
references/control-flow-and-runtime.md
Effect.if
,
Effect.when
, loops,
runSync
,
runPromise
,
ManagedRuntime
references/data-types.md
All data types:
Option
,
Either
,
Data
,
Exit
,
Cause
,
Duration
,
DateTime
,
BigDecimal
,
Chunk
,
HashSet
,
Redacted
references/data-and-testing.md
Option/Either/Array quick ref,
@effect/vitest
setup
references/concurrency-and-resources.md
Concurrency,
Scope
, finalizers, resources
references/streams-deep-dive.md
Creating, operations, grouping, partitioning, broadcasting, buffering, throttling, error handling
references/sink.md
Sink constructors, collecting, folding, operations, concurrency, leftovers,
Stream.transduce
references/batching-and-caching.md
Request batching (
RequestResolver
),
cachedWithTTL
references/schema-transforms-and-filters.md
Schema.transform
,
Schema.filter
, refinements
references/api-platform-observability.md
HttpApi
, logging, tracing, spans
references/class-patterns.md
Context.Tag
service pattern, layers, memoization, testing
references/error-handling-patterns.md
Data.TaggedError
,
Schema.TaggedError
, error composition, recovery
references/library-development-patterns.md
Forbidden patterns,
Effect.fn
vs
Effect.fnUntraced
, resource management
references/testing-patterns.md
@effect/vitest
with
assert
,
TestClock
, service mocking
references/quality-tooling-and-resources.md
Anti-patterns, validation checklist, packages
编写代码前请阅读相关参考资料,
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
服务模式、层、memoization、测试
references/error-handling-patterns.md
Data.TaggedError
Schema.TaggedError
、错误组合、恢复
references/library-development-patterns.md
禁止模式、
Effect.fn
Effect.fnUntraced
对比、资源管理
references/testing-patterns.md
配合
assert
使用
@effect/vitest
TestClock
、服务Mock
references/quality-tooling-and-resources.md
反模式、验证检查清单、依赖包

Core Principles

核心原则

  1. Effect is not just for async — Use
    Effect
    for any fallible operation
  2. Immutability — Use Effect's immutable data structures (
    Data
    ,
    Chunk
    ,
    HashSet
    )
  3. Type Safety — Track errors in types. No
    any
    or
    unknown
    in error channels
  4. Composition — Build programs by composing small Effects
  5. Schema-First — Define data models using
    Schema
    with branded types
  6. Pattern Matching — Use
    Match
    for all branching over tagged unions (never
    switch
    /
    if-else
    on
    _tag
    )
  7. Effect Data Types — Use
    Option
    (not null),
    Either
    (not ad-hoc),
    Duration
    (not raw ms),
    DateTime
    (not Date),
    BigDecimal
    (not floats),
    Redacted
    (for secrets). See
    references/data-types.md
  1. Effect 不只是用于异步场景 — 所有可能出错的操作都可以使用
    Effect
  2. 不可变性 — 使用Effect提供的不可变数据结构(
    Data
    Chunk
    HashSet
  3. 类型安全 — 在类型中追踪错误,错误通道中不允许出现
    any
    unknown
  4. 可组合性 — 通过组合小型Effect来构建完整程序
  5. Schema优先 — 使用带品牌类型的
    Schema
    定义数据模型
  6. 模式匹配 — 所有标记联合类型的分支逻辑都使用
    Match
    实现(绝对不要对
    _tag
    使用
    switch
    /
    if-else
  7. Effect数据类型 — 使用
    Option
    (替代null)、
    Either
    (替代临时错误处理)、
    Duration
    (替代原始毫秒数)、
    DateTime
    (替代Date)、
    BigDecimal
    (替代浮点数)、
    Redacted
    (处理敏感信息),详见
    references/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, Redacted
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, Redacted

Effect.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.TaggedError
for in-process discrimination,
Schema.TaggedError
for serializable errors. See
references/error-handling-patterns.md
.
typescript
export class NotFoundError extends Data.TaggedError("NotFoundError")<{
  id: string;
}> {}

// Recovery
pipe(riskyOp, Effect.catchTag("NotFoundError", (e) => Effect.succeed(null)));
Data.TaggedError
用于进程内错误区分,
Schema.TaggedError
用于可序列化错误,详见
references/error-handling-patterns.md
typescript
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
Match
Match.exhaustive
catches missing cases at compile time. See
references/pattern-matching.md
.
typescript
// 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}`,
});
请始终使用
Match
Match.exhaustive
可在编译阶段捕获缺失的分支情况,详见
references/pattern-matching.md
typescript
// 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
references/class-patterns.md
for full pattern with factory methods and layers.
typescript
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.md
typescript
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
assert
from
@effect/vitest
for
it.effect
. Never
expect
with
it.effect
. See
references/testing-patterns.md
.
typescript
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/vitest
提供的
assert
使用,绝对不要在
it.effect
中使用
expect
,详见
references/testing-patterns.md
typescript
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 instead
typescript
// 绝对不要:在 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"
  • Effect.gen
    for complex logic,
    pipe
    for linear,
    Effect.fn
    for public API
  • Match
    for all
    _tag
    branching with
    Match.exhaustive
  • Branded types for domain primitives (IDs, Emails)
  • Errors:
    Data.TaggedError
    (discrimination) or
    Schema.TaggedError
    (serializable)
  • No
    any
    /
    unknown
    in error channels, no type assertions
  • No try-catch in
    Effect.gen
    — use
    Effect.exit
  • return yield*
    for terminal effects (
    Effect.fail
    ,
    Effect.interrupt
    )
  • Services:
    Context.Tag
    with static factory methods and
    Effect.fn
    tracing
  • Layers:
    Layer.merge
    /
    Layer.provide
    , parameterized layers in constants
  • Resources:
    Effect.acquireRelease
    or
    Effect.scoped
  • Option
    for nullable values,
    Either
    for sync success/failure
  • Duration
    for time values,
    DateTime
    for dates (not
    Date
    )
  • BigDecimal
    for financial/precise math,
    Redacted
    for secrets
  • Data.struct
    /
    Data.Class
    for structural equality,
    HashSet
    for sets
  • Clock.currentTimeMillis
    instead of
    Date.now()
  • Tests:
    assert
    from
    @effect/vitest
    (not
    expect
    ) with
    it.effect
  • Run
    pnpm run typecheck
    and
    pnpm run test
  • 导入使用
    import * as Module from "effect/Module"
    格式
  • 复杂逻辑用
    Effect.gen
    ,线性转换用
    pipe
    ,公共API用
    Effect.fn
  • 所有
    _tag
    分支逻辑使用
    Match
    且配合
    Match.exhaustive
  • 领域基元(ID、邮箱)使用品牌类型
  • 错误使用
    Data.TaggedError
    (进程内区分)或
    Schema.TaggedError
    (可序列化)
  • 错误通道中没有
    any
    /
    unknown
    ,没有类型断言
  • 不在
    Effect.gen
    中使用 try-catch — 请使用
    Effect.exit
  • 终端 Effect(
    Effect.fail
    Effect.interrupt
    )使用
    return yield*
  • 服务使用
    Context.Tag
    搭配静态工厂方法和
    Effect.fn
    追踪
  • 层使用
    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

Reference Implementation

参考实现

See
/packages/looper/src/data/api-client/api-client.ts
for Context.Tag service pattern.
Context.Tag 服务模式可参考
/packages/looper/src/data/api-client/api-client.ts

Effect 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 keyword
bash
pnpm exec effect-solutions list              # 列出所有主题
pnpm exec effect-solutions show <slug...>    # 查看主题内容
pnpm exec effect-solutions search <term>     # 按关键词搜索