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

Areav3v4
Result typeEitherResult
Service definitionContext.Tag / Effect.Tag / Effect.ServiceContext.Service
Catch-allcatchAllcatch
STM typesTRef, TMap, TSetTxRef, TxHashMap, TxHashSet
TestClockeffect/TestClockeffect/testing/TestClock
Platform packages@effect/platform, @effect/cli, @effect/rpceffect/unstable/*
Layer memoizationper-provide callautomatic, shared
Fiber keep-aliverequired runMain hackautomatic
Effect subtypingRef/Deferred/Fiber were Effectsplain 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*

  • Effect itself
  • Option (yields value or fails with NoSuchElementError)
  • Result (yields success or fails with error)
  • Config (yields config value or fails with ConfigError)
  • 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 .Default layer is gone; define layers explicitly
  • The dependencies option is removed; wire dependencies with Layer.provide
  • v3’s static accessor proxy (Notifications.notify("hello")) is removed; use yield* or Service.use
  • Naming convention: use layer instead of Default or Live

catch* Renames

v3v4
catchAllcatch
catchAllCausecatchCause
catchAllDefectcatchDefect
catchSomecatchFilter
catchSomeCausecatchCauseFilter
catchSomeDefectremoved
catchTagunchanged
catchTagsunchanged
catchIfunchanged

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/*:

NamespaceContains
effect/unstable/httpHTTP client, server, router, middleware
effect/unstable/httpapiSchema-first HTTP API builder
effect/unstable/rpcType-safe RPC
effect/unstable/clusterDistributed entities, sharding
effect/unstable/workflowDurable workflows
effect/unstable/cliCommand-line apps
effect/unstable/aiLanguage models, chat, tools
effect/unstable/sqlSQL clients, migrations, models
effect/unstable/reactivityReactive state, atoms
effect/unstable/observabilityOTLP exporters
effect/unstable/persistenceKey-value stores, rate limiters
effect/unstable/encodingMsgPack, NDJSON, SSE
effect/unstable/workersWorker threads
effect/unstable/processChild processes
effect/unstable/devtoolsDevTools server/client
effect/unstable/eventlogEvent journals
effect/unstable/socketSocket 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

v3v4
TRefTxRef
TMapTxHashMap
TSetTxHashSet
TQueueTxQueue
TPubSubTxPubSub
TDeferredTxDeferred
TSemaphoreTxSemaphore
TSubscriptionRefTxSubscriptionRef
TPriorityQueueTxPriorityQueue
TReentrantLockTxReentrantLock

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 --noEmit and 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.