effect-advanced
Original:🇺🇸 English
Translated
Advanced Effect-TS patterns for typed errors, dependency injection, concurrency, resource management, schema validation, and streaming. Use when building Effect programs — not simple Effect.succeed/fail questions, but multi-concern tasks like designing service layers with Layer composition, handling typed error hierarchies with tagged errors, managing concurrent fibers with structured concurrency, scoped resource lifecycles, schema-driven API contracts, or integrating Effect with existing Express/Hono/database stacks. Do not use for basic TypeScript or general functional programming questions.
5installs
Added on
NPX Install
npx skill4agent add trancong12102/agentskills effect-advancedTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Effect Advanced: Patterns, Conventions & Pitfalls
This skill defines the rules, conventions, and architectural decisions for building
production Effect-TS applications. It is intentionally opinionated to prevent common
pitfalls and enforce patterns that scale.
For detailed API documentation, use other appropriate tools (documentation lookup,
web search, etc.) — this skill focuses on how and why to use Effect idiomatically,
not the full API surface.
Table of Contents
- Core Conventions
- Error Handling Philosophy
- Dependency Injection Architecture
- Resource & Scope Rules
- Concurrency Model
- Common Pitfalls
- Reference Files
Core Conventions
Use Effect.gen
for business logic
Effect.genGenerators read like synchronous code and are strongly preferred over long /
chains for anything beyond trivial composition:
.pipe.flatMaptypescript
const program = Effect.gen(function* () {
const config = yield* ConfigService;
const user = yield* UserRepo.findById(config.userId);
return user;
});Reserve for data transformation pipelines and short combinator chains.
pipeNever throw — use Effect's error channel
| Instead of... | Use |
|---|---|
| |
| |
| Callback APIs | |
| Unrecoverable crashes | |
Functions over methods
Prefer over for composability and
tree-shaking. Flat imports () are fine for
applications; namespace imports () are
better for libraries.
Effect.map(e, f)e.pipe(Effect.map(f))import { Effect } from "effect"import * as Effect from "effect/Effect"@effect/schema
is deprecated
@effect/schemaSchema has been merged into core . Import from directly:
effect"effect"typescript
import { Schema } from "effect";
// NOT: import { Schema } from "@effect/schema"Use NodeRuntime.runMain
in production
NodeRuntime.runMainEffect.runPromiseSIGINTSIGTERMtypescript
import { NodeRuntime } from "@effect/platform-node";
NodeRuntime.runMain(program.pipe(Effect.provide(AppLayer)));Error Handling Philosophy
Failures vs defects — the fundamental distinction
| Aspect | Failure (expected) | Defect (unexpected) |
|---|---|---|
| API | | |
| Type channel | Tracked in | Never appears in |
| Recovery | | Only at system boundaries |
| Rule of thumb | You intend to handle it at call site | Bug or impossible state |
Always use tagged errors
Plain or string failures miss the value of Effect's typed error channel:
Errortypescript
class UserNotFound extends Data.TaggedError("UserNotFound")<{
readonly id: string;
}> {}
// Tagged errors are yieldable — no Effect.fail wrapper needed
const program = Effect.gen(function* () {
const user = yield* db.findUser(id);
if (!user) yield* new UserNotFound({ id });
return user;
});catchAll
does NOT catch defects
catchAllThis is the #1 error handling mistake:
typescript
Effect.catchAll(program, handler); // catches E only — NOT defects
Effect.catchAllCause(program, handler); // catches everything (E + defects + interrupts)Only use / at system boundaries (top-level error
handlers, HTTP response mappers).
catchAllCausecatchAllDefectDependency Injection Architecture
Service → Layer → Provide (once)
text
1. Define services with Context.Tag → "what do I need?"
2. Implement via Layers → "how is it built?"
3. Provide once at entry point → "wire it all together"Service methods must have R = never
R = neverDependencies belong in Layer composition, not method signatures:
typescript
// WRONG: leaks dependency to callers
findById: (id: string) => Effect.Effect<User, UserNotFound, Database>;
// RIGHT: Database is wired in the Layer
findById: (id: string) => Effect.Effect<User, UserNotFound>;Layer composition — know the operators
| Operation | When | Behavior |
|---|---|---|
| Independent services | Both build concurrently |
| A feeds B | upstream builds first |
| Force new instance | Bypasses memoization |
Critical: does NOT sequence construction. If B depends on A, use
, not .
Layer.mergeLayer.provideLayer.mergeOne Effect.provide
at the entry point
Effect.provideScattered calls create hidden dependencies and layer duplication:
providetypescript
// WRONG: provide scattered throughout codebase
const getUser = UserRepo.findById(id).pipe(Effect.provide(DbLayer));
// RIGHT: compose and provide once
const main = program.pipe(Effect.provide(AppLayer));
NodeRuntime.runMain(main);Resource & Scope Rules
Effect.scoped
is mandatory for acquireRelease
Effect.scopedacquireReleaseForgetting is the #1 resource management pitfall — resources
accumulate until the program exits:
Effect.scopedtypescript
// WRONG: scope never closes, connection leaks
const result = yield * getDbConnection;
// RIGHT: scope closes when block completes
const result =
yield *
Effect.scoped(
Effect.gen(function* () {
const conn = yield* getDbConnection;
return yield* conn.query("SELECT 1");
}),
);Release finalizers always run
On success, failure, AND interruption — guaranteed. The finalizer receives the
value for conditional cleanup.
ExitMultiple resources in one scope
typescript
Effect.scoped(
Effect.gen(function* () {
const conn = yield* Effect.acquireRelease(openConn(), closeConn);
const file = yield* Effect.acquireRelease(openFile(), closeFile);
// both released when scope closes, in REVERSE acquisition order
}),
);Concurrency Model
Prefer high-level APIs over raw fork
| API | Use case |
|---|---|
| Bounded parallel execution |
| Worker pool pattern |
| First to complete wins, others interrupted |
| Deadline on any effect |
Only reach for / when high-level APIs are insufficient.
Effect.forkFiberFork variants — know the lifecycle
| Function | Scope | Cleanup |
|---|---|---|
| Parent's scope | Auto-interrupted with parent |
| Global scope | Nothing cleans it up — you must |
| Nearest Scope | Tied to resource lifecycle |
Gotcha: leaks fibers if you forget to interrupt them.
forkDaemonCommon Pitfalls
-
Floating effects — creating an Effect without yielding or running it is a silent bug.inside a generator does nothing unless
Effect.log("msg")-ed.yield* -
won't catch defects — use
catchAllat system boundaries for full failure visibility.catchAllCause -
Missing—
Effect.scopedwithout a scope boundary leaks resources until program exit.acquireRelease -
Scattered— compose all layers and provide once at the entry point.
Effect.provide -
Point-free on overloaded functions —silently erases generics. Use explicit lambdas:
Effect.map(myOverloadedFn).Effect.map((x) => myOverloadedFn(x)) -
resume called multiple times — resume must be called exactly once. Multiple calls cause undefined behavior.
Effect.async -
silences errors — converts typed failures to untyped defects. Handle errors properly instead.
orDie -
for dependent services — merge doesn't sequence construction. Use
Layer.mergewhen one layer needs another's output.Layer.provide -
vs
Fiber.join—Fiber.awaitcan cause premature finalizer execution in edge cases. Preferjoinwhen resource safety matters.await -
on infinite streams — never call without a prior
runCollect. It will never terminate and consume unbounded memory.take -
Usingfor scoped tests — effects requiring
it.effectmust useScope, notit.scoped, or you get a type error.it.effect
Reference Files
Read the relevant reference file when working with a specific concern:
| File | When to read |
|---|---|
| Tagged errors, Cause, defect recovery, error mapping patterns |
| Services, Layers, composition, memoization, provide patterns |
| Fibers, fork variants, Deferred, Semaphore, structured concurrency |
| Scope, acquireRelease, Layer resources, fork + scope interaction |
| Schema definition, transforms, branded types, recursive schemas |
| Stream operators, chunking, backpressure, resourceful streams |
| @effect/vitest, TestClock, Layer mocking, Config mocking |
| HTTP client, FileSystem, Command, runtime, framework integration |