v3 to v4 Migration
v4 is a significant breaking release. The core abstractions are more precise, packages are consolidated, and several v3 convenience patterns were removed in favor of explicitness.
Breaking Changes Overview
| Area | v3 | v4 |
|---|---|---|
| Result type | Either | Result |
| Service definition | Context.Tag / Effect.Tag / Effect.Service | Context.Service |
| Catch-all | catchAll | catch |
| STM types | TRef, TMap, TSet | TxRef, TxHashMap, TxHashSet |
| TestClock | effect/TestClock | effect/testing/TestClock |
| Platform packages | @effect/platform, @effect/cli, @effect/rpc | effect/unstable/* |
| Layer memoization | per-provide call | automatic, shared |
| Fiber keep-alive | required runMain hack | automatic |
| Effect subtyping | Ref/Deferred/Fiber were Effects | plain values, use explicit methods |
Import Map Summary
The full import map lives in _repos/Effect-TS-effect/migration/v3-to-v4.md. Here are the renames you’ll hit most often:
effect/Either -> effect/Result
effect/TestClock -> effect/testing/TestClock
effect/FastCheck -> effect/testing/FastCheck
effect/TRef -> effect/TxRef
effect/TMap -> effect/TxHashMap
effect/TSet -> effect/TxHashSet
effect/TQueue -> effect/TxQueue
effect/TPubSub -> effect/TxPubSub
effect/JSONSchema -> effect/JsonSchema
effect/ParseResult -> effect/SchemaIssue / effect/SchemaParser
effect/FiberRef -> effect/References
effect/Inspectable -> effect/Formatter / effect/Redactable
@effect/typeclass/Semigroup -> effect/Combiner
@effect/typeclass/Monoid -> effect/Reducer
Platform Package Consolidation
@effect/platform/HttpClient -> effect/unstable/http/HttpClient
@effect/platform/HttpServer -> effect/unstable/http/HttpServer
@effect/platform/HttpApi* -> effect/unstable/httpapi/*
@effect/platform/FileSystem -> effect/FileSystem
@effect/platform/Path -> effect/Path
@effect/platform/Terminal -> effect/Terminal
@effect/cli/* -> effect/unstable/cli/*
@effect/rpc/* -> effect/unstable/rpc/*
@effect/cluster/* -> effect/unstable/cluster/*
@effect/workflow/* -> effect/unstable/workflow/*
@effect/ai/* -> effect/unstable/ai/*
@effect/sql/* -> effect/unstable/sql/*
@effect/opentelemetry/Otlp* -> effect/unstable/observability/Otlp*
@effect/experimental/Reactivity -> effect/unstable/reactivity/Reactivity
Gotcha: Some provider packages were removed entirely with no replacement:
@effect/ai-amazon-bedrock,@effect/ai-google,@effect/printer,@effect/printer-ansi,@effect/typeclass. Use native SDKs directly or rewrite against concrete v4 modules.
Yieldable Trait Changes
v3 made Ref, Deferred, Fiber, Option, Either, Config, and Context.Tag structural subtypes of Effect. You could yield* them or pass them to Effect combinators directly.
v4 replaces this with the Yieldable trait. Types implementing Yieldable can be used with yield* in generators but are not assignable to Effect.
What Still Works with yield*
EffectitselfOption(yields value or fails withNoSuchElementError)Result(yields success or fails with error)Config(yields config value or fails withConfigError)Context.Service(yields the service instance)
What Broke
// v3 - these were Effect subtypes
const value = yield* ref // Ref was Effect<A>
const value = yield* deferred // Deferred was Effect<A, E>
const value = yield* fiber // Fiber was Effect<A, E>
// v4 - use explicit methods
const value = yield* Ref.get(ref)
const value = yield* Deferred.await(deferred)
const value = yield* Fiber.join(fiber)
You also can’t pass these types to Effect combinators anymore:
// v3
Effect.map(Option.some(42), (n) => n + 1) // Option was an Effect
// v4
Effect.map(Option.some(42).asEffect(), (n) => n + 1) // explicit conversion
Context.Service Changes
All v3 service definition methods collapsed into Context.Service:
// v3 - four different ways to define services
const Db = Context.GenericTag<Db>("Db")
class Db extends Context.Tag("Db")<Db, Shape>() {}
class Db extends Effect.Tag("Db")<Db, Shape>() {}
class Db extends Effect.Service<Db>()("Db", { effect, dependencies }) {}
// v4 - one way
class Db extends Context.Service<Db, Shape>()("Db") {}
Key differences:
- Type parameters come first, identifier string comes second
Effect.Service’s auto-generated.Defaultlayer is gone; define layers explicitly- The
dependenciesoption is removed; wire dependencies withLayer.provide - v3’s static accessor proxy (
Notifications.notify("hello")) is removed; useyield*orService.use - Naming convention: use
layerinstead ofDefaultorLive
catch* Renames
| v3 | v4 |
|---|---|
catchAll | catch |
catchAllCause | catchCause |
catchAllDefect | catchDefect |
catchSome | catchFilter |
catchSomeCause | catchCauseFilter |
catchSomeDefect | removed |
catchTag | unchanged |
catchTags | unchanged |
catchIf | unchanged |
New in v4: catchReason, catchReasons, catchEager.
Layer Auto-Memoization
v3 memoized layers within a single Effect.provide call. Two separate provide calls with the same layer would build it twice.
v4 shares the memo map across Effect.provide calls. The same layer is built once per runtime unless you opt out with Layer.fresh or { local: true }.
// v3: "Building" logged twice. v4: logged once.
program.pipe(
Effect.provide(MyLayer),
Effect.provide(MyLayer)
)
Tip: Compose layers into a single layer and provide once. Auto-memoization is a safety net, not a replacement for explicit composition.
Fiber Keep-Alive
v3 required runMain from @effect/platform-node to hold the process open while fibers were suspended. Without it, Node.js would exit if the event loop had no pending work.
v4 builds the keep-alive mechanism into the core runtime. Effect.runPromise now holds the process alive automatically.
runMain is still recommended for production entry points because it handles signals and exit codes. But for scripts and tests, Effect.runPromise works without extra setup.
Unstable Namespace Consolidation
v4 merged many separate packages into effect/unstable/*:
| Namespace | Contains |
|---|---|
effect/unstable/http | HTTP client, server, router, middleware |
effect/unstable/httpapi | Schema-first HTTP API builder |
effect/unstable/rpc | Type-safe RPC |
effect/unstable/cluster | Distributed entities, sharding |
effect/unstable/workflow | Durable workflows |
effect/unstable/cli | Command-line apps |
effect/unstable/ai | Language models, chat, tools |
effect/unstable/sql | SQL clients, migrations, models |
effect/unstable/reactivity | Reactive state, atoms |
effect/unstable/observability | OTLP exporters |
effect/unstable/persistence | Key-value stores, rate limiters |
effect/unstable/encoding | MsgPack, NDJSON, SSE |
effect/unstable/workers | Worker threads |
effect/unstable/process | Child processes |
effect/unstable/devtools | DevTools server/client |
effect/unstable/eventlog | Event journals |
effect/unstable/socket | Socket client/server |
These are marked unstable because APIs may change between minor versions. The core effect module (Effect, Context, Layer, Stream, Schema, etc.) is stable.
STM Type Renames
| v3 | v4 |
|---|---|
TRef | TxRef |
TMap | TxHashMap |
TSet | TxHashSet |
TQueue | TxQueue |
TPubSub | TxPubSub |
TDeferred | TxDeferred |
TSemaphore | TxSemaphore |
TSubscriptionRef | TxSubscriptionRef |
TPriorityQueue | TxPriorityQueue |
TReentrantLock | TxReentrantLock |
Migration Strategy
Step 1: Update Imports
Start with a codemod or find-and-repass using the import map. Most renames are mechanical:
// Before
import { Either, TestClock } from "effect"
import { HttpClient } from "@effect/platform/HttpClient"
// After
import { Result } from "effect"
import { TestClock } from "effect/testing/TestClock"
import { HttpClient } from "effect/unstable/http/HttpClient"
Step 2: Fix Service Definitions
Convert all Context.Tag, Effect.Tag, and Effect.Service definitions to Context.Service. Add explicit layer definitions where .Default was previously auto-generated.
Step 3: Fix Yieldable Errors
Search for direct yields of Ref, Deferred, and Fiber values. Replace with explicit method calls (Ref.get, Deferred.await, Fiber.join).
Step 4: Fix catch* Calls
Rename catchAll to catch, catchAllCause to catchCause, catchSome to catchFilter.
Step 5: Fix STM Types
Rename all T* types to Tx* equivalents.
Step 6: Verify Layer Wiring
Since .Default auto-generation is gone, check every service has an explicit layer. Compose them at the application entry point.
Tip: After mechanical fixes, run
tsc --noEmitand work through the remaining type errors. Most will be from the categories above. The migration files in_repos/Effect-TS-effect/migration/cover edge cases.