Merge commit '36c66dd290d3ce6eb1ccd310d0c658d4a32bb8eb' as 'repos/effect'

This commit is contained in:
-Puter
2026-07-19 03:25:10 +05:30
parent dd1071cc10
commit 2daf979036
2214 changed files with 673090 additions and 0 deletions

View File

@@ -0,0 +1,163 @@
# Cause: Flattened Structure
In v3, `Cause<E>` was a recursive tree with six variants:
```
Empty | Fail<E> | Die | Interrupt | Sequential<E> | Parallel<E>
```
The `Sequential` and `Parallel` variants composed causes into a tree to
represent errors from finalizers or concurrent operations.
In v4, `Cause<E>` has been flattened to a simple wrapper around an array of
`Reason` values:
```ts
interface Cause<E> {
readonly reasons: ReadonlyArray<Reason<E>>
}
type Reason<E> = Fail<E> | Die | Interrupt
```
There are only three reason variants — `Fail`, `Die`, and `Interrupt`. The
`Empty`, `Sequential`, and `Parallel` variants have been removed. An empty
cause is represented by an empty `reasons` array. Multiple failures (from
concurrent or sequential composition) are collected into a flat array.
## Accessing Reasons
**v3** — pattern match on the recursive tree structure:
```ts
import { Cause } from "effect"
const handle = (cause: Cause.Cause<string>) => {
switch (cause._tag) {
case "Fail":
return cause.error
case "Die":
return cause.defect
case "Empty":
return undefined
case "Sequential":
return handle(cause.left)
case "Parallel":
return handle(cause.left)
case "Interrupt":
return cause.fiberId
}
}
```
**v4** — iterate over the flat `reasons` array:
```ts
import { Cause } from "effect"
const handle = (cause: Cause.Cause<string>) => {
for (const reason of cause.reasons) {
switch (reason._tag) {
case "Fail":
return reason.error
case "Die":
return reason.defect
case "Interrupt":
return reason.fiberId
}
}
}
```
## Reason Guards
The v3 type-level guards (`isFailType`, `isDieType`, `isInterruptType`, etc.)
have been replaced by reason-level guards:
| v3 | v4 |
| ------------------------------- | --------------------------------- |
| `Cause.isEmptyType(cause)` | `cause.reasons.length === 0` |
| `Cause.isFailType(cause)` | `Cause.isFailReason(reason)` |
| `Cause.isDieType(cause)` | `Cause.isDieReason(reason)` |
| `Cause.isInterruptType(cause)` | `Cause.isInterruptReason(reason)` |
| `Cause.isSequentialType(cause)` | Removed |
| `Cause.isParallelType(cause)` | Removed |
## Cause-Level Predicates
| v3 | v4 |
| -------------------------------- | -------------------------------- |
| `Cause.isFailure(cause)` | `Cause.hasFails(cause)` |
| `Cause.isDie(cause)` | `Cause.hasDies(cause)` |
| `Cause.isInterrupted(cause)` | `Cause.hasInterrupts(cause)` |
| `Cause.isInterruptedOnly(cause)` | `Cause.hasInterruptsOnly(cause)` |
## Constructors
| v3 | v4 |
| ------------------------------- | ---------------------------- |
| `Cause.empty` | `Cause.empty` |
| `Cause.fail(error)` | `Cause.fail(error)` |
| `Cause.die(defect)` | `Cause.die(defect)` |
| `Cause.interrupt(fiberId)` | `Cause.interrupt(fiberId)` |
| `Cause.sequential(left, right)` | `Cause.combine(left, right)` |
| `Cause.parallel(left, right)` | `Cause.combine(left, right)` |
In v4, `Cause.combine` concatenates the `reasons` arrays of two causes. The
distinction between sequential and parallel composition is no longer
represented in the data structure.
## Extractors
| v3 | v4 |
| ------------------------------ | ------------------------------------------ |
| `Cause.failureOption(cause)` | `Cause.findErrorOption(cause)` |
| `Cause.failureOrCause(cause)` | `Cause.findError(cause)` |
| `Cause.dieOption(cause)` | `Cause.findDefect(cause)` |
| `Cause.interruptOption(cause)` | `Cause.findInterrupt(cause)` |
| `Cause.failures(cause)` | `cause.reasons.filter(Cause.isFailReason)` |
| `Cause.defects(cause)` | `cause.reasons.filter(Cause.isDieReason)` |
| `Cause.interruptors(cause)` | `Cause.interruptors(cause)` |
Note: `findError` and `findDefect` return `Result.Result` instead of `Option`.
Use `findErrorOption` for the `Option`-based variant.
## Error Classes
All `*Exception` classes have been renamed to `*Error`:
| v3 | v4 |
| -------------------------------------- | ----------------------------- |
| `Cause.NoSuchElementException` | `Cause.NoSuchElementError` |
| `Cause.TimeoutException` | `Cause.TimeoutError` |
| `Cause.IllegalArgumentException` | `Cause.IllegalArgumentError` |
| `Cause.ExceededCapacityException` | `Cause.ExceededCapacityError` |
| `Cause.UnknownException` | `Cause.UnknownError` |
| `Cause.RuntimeException` | Removed |
| `Cause.InterruptedException` | Removed |
| `Cause.InvalidPubSubCapacityException` | Removed |
The corresponding guards follow the same pattern:
| v3 | v4 |
| -------------------------------------- | ---------------------------------- |
| `Cause.isNoSuchElementException(u)` | `Cause.isNoSuchElementError(u)` |
| `Cause.isTimeoutException(u)` | `Cause.isTimeoutError(u)` |
| `Cause.isIllegalArgumentException(u)` | `Cause.isIllegalArgumentError(u)` |
| `Cause.isExceededCapacityException(u)` | `Cause.isExceededCapacityError(u)` |
| `Cause.isUnknownException(u)` | `Cause.isUnknownError(u)` |
## New in v4
- **`Cause.fromReasons(reasons)`** — construct a `Cause` from an array of
`Reason` values.
- **`Cause.makeFailReason(error)`**, **`Cause.makeDieReason(defect)`**,
**`Cause.makeInterruptReason(fiberId)`** — construct individual `Reason`
values.
- **`Cause.annotate(cause, annotations)`** — attach annotations to a `Cause`.
- **`Cause.findFail(cause)`**, **`Cause.findDie(cause)`**,
**`Cause.findInterrupt(cause)`** — extract specific reason types using
the `Result` module.
- **`Cause.filterInterruptors(cause)`** — extract interrupting fiber IDs as
a `Result`.
- **`Cause.Done`** — a graceful completion signal for queues and streams.

View File

@@ -0,0 +1,72 @@
# Equality
## Structural Equality by Default
In v3, `Equal.equals` used **reference equality** for plain objects and arrays.
Structural comparison was only available inside a `structuralRegion`, which
temporarily enabled deep comparison. Outside a structural region, two distinct
objects with identical contents were not considered equal:
```ts
// v3
import { Equal } from "effect"
Equal.equals({ a: 1 }, { a: 1 }) // false — reference equality
Equal.equals([1, 2], [1, 2]) // false — reference equality
```
In v4, `Equal.equals` uses **structural equality** by default. Plain objects,
arrays, `Map`s, `Set`s, `Date`s, and `RegExp`s are compared by value without
opting in:
```ts
// v4
import { Equal } from "effect"
Equal.equals({ a: 1 }, { a: 1 }) // true
Equal.equals([1, [2, 3]], [1, [2, 3]]) // true
Equal.equals(new Map([["a", 1]]), new Map([["a", 1]])) // true
Equal.equals(new Set([1, 2]), new Set([1, 2])) // true
```
Objects that implement the `Equal` interface continue to use their custom
equality logic, same as v3.
## Opting Out: `byReference`
If you need reference equality for a specific object, v4 provides
`Equal.byReference` and `Equal.byReferenceUnsafe`:
```ts
import { Equal } from "effect"
const obj = Equal.byReference({ a: 1 })
Equal.equals(obj, { a: 1 }) // false — reference equality
```
- **`byReference(obj)`** — creates a `Proxy` that uses reference equality,
leaving the original object unchanged.
- **`byReferenceUnsafe(obj)`** — marks the object itself for reference
equality without creating a proxy. More performant but permanently changes
how the object is compared.
## `NaN` Equality
In v3, `Equal.equals(NaN, NaN)` returned `false` (following IEEE 754).
In v4, `NaN` is considered equal to `NaN`:
```ts
Equal.equals(NaN, NaN) // v3: false, v4: true
```
## `equivalence` → `asEquivalence`
The function that wraps `equals` as an `Equivalence` has been renamed:
```ts
// v3
Equal.equivalence<number>()
// v4
Equal.asEquivalence<number>()
```

View File

@@ -0,0 +1,106 @@
# Error Handling: `catch*` Renamings
The `catch` combinators on `Effect` have been renamed in v4. The general
pattern: `catchAll*` is shortened to `catch*`, and the `catchSome*` family is
replaced by `catchFilter` / `catchCauseFilter`.
## Renamings
| v3 | v4 |
| ------------------------ | ------------------------------ |
| `Effect.catchAll` | `Effect.catch` |
| `Effect.catchAllCause` | `Effect.catchCause` |
| `Effect.catchAllDefect` | `Effect.catchDefect` |
| `Effect.catchTag` | `Effect.catchTag` (unchanged) |
| `Effect.catchTags` | `Effect.catchTags` (unchanged) |
| `Effect.catchIf` | `Effect.catchIf` (unchanged) |
| `Effect.catchSome` | `Effect.catchFilter` |
| `Effect.catchSomeCause` | `Effect.catchCauseFilter` |
| `Effect.catchSomeDefect` | Removed |
## `Effect.catchAll` → `Effect.catch`
**v3**
```ts
import { Effect } from "effect"
const program = Effect.fail("error").pipe(
Effect.catchAll((error) => Effect.succeed(`recovered: ${error}`))
)
```
**v4**
```ts
import { Effect } from "effect"
const program = Effect.fail("error").pipe(
Effect.catch((error) => Effect.succeed(`recovered: ${error}`))
)
```
## `Effect.catchAllCause` → `Effect.catchCause`
**v3**
```ts
import { Effect } from "effect"
const program = Effect.die("defect").pipe(
Effect.catchAllCause((cause) => Effect.succeed("recovered"))
)
```
**v4**
```ts
import { Cause, Effect } from "effect"
const program = Effect.die("defect").pipe(
Effect.catchCause((cause) => Effect.succeed("recovered"))
)
```
## `Effect.catchSome` → `Effect.catchFilter`
In v3, `catchSome` took a function returning `Option<Effect>`. In v4,
`catchFilter` uses the `Filter` module instead.
**v3**
```ts
import { Effect, Option } from "effect"
const program = Effect.fail(42).pipe(
Effect.catchSome((error) =>
error === 42
? Option.some(Effect.succeed("caught"))
: Option.none()
)
)
```
**v4**
```ts
import { Effect, Filter } from "effect"
const program = Effect.fail(42).pipe(
Effect.catchFilter(
Filter.fromPredicate((error: number) => error === 42),
(error) => Effect.succeed("caught")
)
)
```
## New in v4
- **`Effect.catchReason(errorTag, reasonTag, handler)`** — catches a specific
`reason` within a tagged error without removing the parent error from the
error channel. Useful for handling nested error causes (e.g. an `AiError`
with a `reason: RateLimitError | QuotaExceededError`).
- **`Effect.catchReasons(errorTag, cases)`** — like `catchReason` but handles
multiple reason tags at once via an object of handlers.
- **`Effect.catchEager(handler)`** — an optimization variant of `catch` that
evaluates synchronous recovery effects immediately.

View File

@@ -0,0 +1,74 @@
# Fiber Keep-Alive: Automatic Process Lifetime Management
In v3, the core `effect` runtime did **not** keep the Node.js process alive while
fibers were suspended on certain asynchronous operations. If a fiber was waiting on
something like `Deferred.await` and there was no other work scheduled on the
event loop, the process would exit immediately — the fiber's suspension did not
register as pending work from Node.js's perspective.
The only way to prevent this was to use `runMain` from `@effect/platform-node`
(or `@effect/platform-bun`), which installed a long-lived `setInterval` timer
to hold the process open until the root fiber completed.
In v4, **the keep-alive mechanism is built into the core runtime**.
## The Problem in v3
Consider the following program:
```ts
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() {
const deferred = yield* Deferred.make<string>()
yield* Deferred.await(deferred)
})
Effect.runPromise(program)
```
In v3, when the main fiber reached `yield* Deferred.await(deferred)`, it suspended
while waiting for the worker fiber to complete the deferred. However, from the
JavaScript runtime's perspective, the event loop had no more work to do. Thus,
the process would exit.
The workaround was to use `runMain` from the platform package, which installs
a timer that holds the process open until the root fiber completes:
```ts
import { NodeRuntime } from "@effect/platform-node"
NodeRuntime.runMain(program)
```
## What Changed in v4
In v4, the Effect fiber runtime automatically manages a reference-counted
keep-alive timer.
This means the following program works in v4 **without** `runMain`:
```ts
import { Deferred, Effect, Fiber } from "effect"
const program = Effect.gen(function*() {
const deferred = yield* Deferred.make<string>()
// The process stays alive while waiting — no runMain needed
yield* Deferred.await(deferred)
})
Effect.runPromise(program)
```
## `runMain` Is Still Recommended
Even though the core runtime now handles keep-alive, `runMain` from the platform
packages is still the recommended way to run Effect programs. It provides:
- **Signal handling** — listens for `SIGINT` / `SIGTERM` and interrupts the
root fiber gracefully.
- **Exit code management** — calls `process.exit(code)` when the program fails
or receives a signal.
- **Error reporting** — reports unhandled errors to the console.

View File

@@ -0,0 +1,109 @@
# FiberRef: `FiberRef` → `Context.Reference`
In v4, `FiberRef`, `FiberRefs`, `FiberRefsPatch`, and `Differ` have been removed.
Fiber-local state is now handled by `Context.Reference` — the same mechanism
used for services with default values.
## Built-in References
v3's built-in `FiberRef` values are now `Context.Reference` values exported
from `References` and related modules.
| v3 FiberRef | v4 Reference |
| ----------------------------------- | ---------------------------------- |
| `FiberRef.currentConcurrency` | `References.CurrentConcurrency` |
| `FiberRef.currentLogLevel` | `References.CurrentLogLevel` |
| `FiberRef.currentMinimumLogLevel` | `References.MinimumLogLevel` |
| `FiberRef.currentLogAnnotations` | `References.CurrentLogAnnotations` |
| `FiberRef.currentLogSpan` | `References.CurrentLogSpans` |
| `FiberRef.currentScheduler` | `References.Scheduler` |
| `FiberRef.currentMaxOpsBeforeYield` | `References.MaxOpsBeforeYield` |
| `FiberRef.currentTracerEnabled` | `References.TracerEnabled` |
| `FiberRef.unhandledErrorLogLevel` | `References.UnhandledLogLevel` |
## Reading References
In v3, `FiberRef.get` retrieved the current value. In v4, references are
services — `yield*` them directly.
**v3**
```ts
import { Effect, FiberRef } from "effect"
const program = Effect.gen(function*() {
const level = yield* FiberRef.get(FiberRef.currentLogLevel)
console.log(level)
})
```
**v4**
```ts
import { Effect, References } from "effect"
const program = Effect.gen(function*() {
const level = yield* References.CurrentLogLevel
console.log(level) // "Info" (default)
})
```
## Scoped Updates (`Effect.locally` → `Effect.provideService`)
v3's `Effect.locally` set a `FiberRef` value for the duration of an effect. In
v4, use `Effect.provideService` with the reference.
**v3**
```ts
import { Effect, FiberRef, LogLevel } from "effect"
const program = Effect.locally(
myEffect,
FiberRef.currentLogLevel,
LogLevel.Debug
)
```
**v4**
```ts
import { Effect, References } from "effect"
const program = Effect.provideService(
myEffect,
References.CurrentLogLevel,
"Debug"
)
```
## Writing References
v3's `FiberRef.set` mutated the current fiber's ref value. In v4, references are
set via `Effect.provideService`, which scopes the value to the provided effect.
**v3**
```ts
import { Effect, FiberRef } from "effect"
const program = Effect.gen(function*() {
yield* FiberRef.set(FiberRef.currentConcurrency, 10)
// subsequent code sees concurrency = 10
})
```
**v4**
```ts
import { Effect, References } from "effect"
const program = Effect.provideService(
Effect.gen(function*() {
const concurrency = yield* References.CurrentConcurrency
console.log(concurrency) // 10
}),
References.CurrentConcurrency,
10
)
```

View File

@@ -0,0 +1,94 @@
# Forking: Renamed Combinators and New Options
The `fork*` family of combinators has been renamed in v4 for clarity, and all
variants now accept an options object for controlling fiber startup behavior.
## Renamings
| v3 | v4 | Description |
| ----------------------------- | ------------------- | -------------------------------------------- |
| `Effect.fork` | `Effect.forkChild` | Fork as a child of the current fiber |
| `Effect.forkDaemon` | `Effect.forkDetach` | Fork detached from parent lifecycle |
| `Effect.forkScoped` | `Effect.forkScoped` | Fork tied to the current `Scope` (unchanged) |
| `Effect.forkIn` | `Effect.forkIn` | Fork in a specific `Scope` (unchanged) |
| `Effect.forkAll` | — | Removed |
| `Effect.forkWithErrorHandler` | — | Removed |
## `Effect.fork` → `Effect.forkChild`
**v3**
```ts
import { Effect } from "effect"
const fiber = Effect.fork(myEffect)
```
**v4**
```ts
import { Effect } from "effect"
const fiber = Effect.forkChild(myEffect)
```
## `Effect.forkDaemon` → `Effect.forkDetach`
**v3**
```ts
import { Effect } from "effect"
const fiber = Effect.forkDaemon(myEffect)
```
**v4**
```ts
import { Effect } from "effect"
const fiber = Effect.forkDetach(myEffect)
```
## Fork Options
In v4, `forkChild`, `forkDetach`, `forkScoped`, and `forkIn` all accept an
optional options object with the following fields:
```ts
{
readonly startImmediately?: boolean | undefined
readonly uninterruptible?: boolean | "inherit" | undefined
}
```
- **`startImmediately`** — When `true`, the forked fiber begins executing
immediately rather than being deferred. Defaults to `undefined` (deferred).
- **`uninterruptible`** — Controls whether the forked fiber can be interrupted.
`true` makes it uninterruptible, `"inherit"` inherits the parent's
interruptibility, and `undefined` uses the default behavior.
**Usage as data-last (curried)**
```ts
import { Effect } from "effect"
const fiber = myEffect.pipe(
Effect.forkChild({ startImmediately: true })
)
```
**Usage as data-first**
```ts
import { Effect } from "effect"
const fiber = Effect.forkChild(myEffect, { startImmediately: true })
```
## Removed Combinators
**`Effect.forkAll`** and **`Effect.forkWithErrorHandler`** have been removed in
v4. For `forkAll`, fork effects individually with `forkChild` or use
higher-level concurrency combinators. For error handling on forked fibers,
observe the fiber's result via `Fiber.join` or `Fiber.await`.

View File

@@ -0,0 +1,32 @@
# Generators
## `Effect.gen`: Passing `this`
In v3, you could pass a `self` value directly as the first argument to
`Effect.gen`. In v4, `self` must be wrapped in an options object.
**v3**
```ts
import { Effect } from "effect"
class MyService {
readonly local = 1
compute = Effect.gen(this, function*() {
return yield* Effect.succeed(this.local + 1)
})
}
```
**v4**
```ts
import { Effect } from "effect"
class MyService {
readonly local = 1
compute = Effect.gen({ self: this }, function*() {
return yield* Effect.succeed(this.local + 1)
})
}
```

View File

@@ -0,0 +1,98 @@
# Layer Memoization
In v3, each call to `Effect.provide` created its own memoization scope. Layers
were memoized / deduplicated within a single `Effect.provide` call, but would
**not** be shared across separate calls — so two `Effect.provide` calls with
overlapping layers would silently build those layers twice.
In v4, the underlying `MemoMap` data structure which facilitates memoization of
`Layer`s is shared between `Effect.provide` calls (unless explicitly disabled
via the `{ local: true }` option). Thus, layers are automatically memoized /
deduplicated across `Effect.provide` calls.
## Example
```ts
import { Console, Context, Effect, Layer } from "effect"
const MyService = Context.Service<{ readonly value: string }>("MyService")
const MyServiceLayer = Layer.effect(
MyService,
Effect.gen(function*() {
yield* Console.log("Building MyService")
return { value: "hello" }
})
)
const program = Effect.gen(function*() {
const a = yield* MyService
return a.value
})
// Same layer provided twice in separate provide calls
const main = program.pipe(
Effect.provide(MyServiceLayer),
Effect.provide(MyServiceLayer)
)
// Effect v3: "Building MyService" is logged TWICE
// Effect v4: "Building MyService" is logged ONCE
Effect.runPromise(main)
```
## Prefer Layer Composition Over Multipl Provides
Even though v4 memoizes across `provide` calls, **composing layers before
providing is still the recommended pattern**. Layer composition makes your
dependency graph explicit and lets you see the full structure in one place:
```ts
// Preferred — provide once
const main = program.pipe(Effect.provide(MyServiceLayer))
```
The auto-memoization feature is a safety net to avoid the footguns associated
with multiple `Effect.provide` calls present in v3. It is **NOT** a substitute
for proper layer composition.
## Opting Out of Shared Memoization
There are cases where you **want** a layer to be built fresh — for example,
test isolation or creating independent resource pools. v4 provides two
mechanisms:
### `Layer.fresh`
Wraps a layer so it always builds with a fresh memo map, bypassing the shared
cache. This existed in v3 as well.
```ts
import { Effect, Layer } from "effect"
const main = program.pipe(
Effect.provide(MyServiceLayer),
Effect.provide(Layer.fresh(MyServiceLayer))
)
// "Building MyService" is logged TWICE — fresh bypasses the shared cache
```
### `Effect.provide` with `{ local: true }`
New in v4. Builds the provided layer with a **local memo map** instead of the
fiber's shared one. The layer and all its sublayers are built from scratch and
are not shared with other `provide` calls.
```ts
import { Effect } from "effect"
const main = program.pipe(
Effect.provide(MyServiceLayer),
Effect.provide(MyServiceLayer, { local: true })
)
// "Building MyService" is logged TWICE — local creates its own memo map
```
Use `local: true` when you need an entire layer subtree to be isolated — for
example, when providing layers in a test harness where each test should get
independent resources.

View File

@@ -0,0 +1,88 @@
# Runtime: `Runtime<R>` Removed
In v3, `Runtime<R>` bundled a `Context<R>`, `RuntimeFlags`, and `FiberRefs`
into a single value used to execute effects:
```ts
// v3
interface Runtime<in R> {
readonly context: Context.Context<R>
readonly runtimeFlags: RuntimeFlags
readonly fiberRefs: FiberRefs
}
```
In v4, this type no longer exists and you can use `Context<R>` instead.
Run functions live directly on `Effect`, and the `Runtime` module is reduced to
process lifecycle utilities.
## `Runtime.runFork(runtime)` -> `Effect.runForkWith(services)`
In v3, running an effect with dependencies usually meant pulling the current
runtime from `Effect.runtime<R>()` and calling `Runtime.runFork(runtime)` inside
the main effect.
**v3**
```ts
import { Context, Effect, Runtime } from "effect"
class Logger extends Context.Tag("Logger")<Logger, {
readonly log: (message: string) => void
}>() {}
const program = Effect.gen(function*() {
const logger = yield* Logger
logger.log("Hello from Logger")
})
const main = Effect.gen(function*() {
const runtime = yield* Effect.runtime<Logger>()
return Runtime.runFork(runtime)(program)
}).pipe(
Effect.provideService(Logger, {
log: (message) => console.log(message)
})
)
const fiber = Effect.runFork(main)
```
In v4, use the same pattern with `Effect.context<R>()`, then run with
`Effect.runForkWith(services)`:
**v4**
```ts
import { Context, Effect } from "effect"
class Logger extends Context.Service<Logger, {
readonly log: (message: string) => void
}>()("Logger") {}
const program = Effect.gen(function*() {
const logger = yield* Logger
logger.log("Hello from Logger")
})
const main = Effect.gen(function*() {
const services = yield* Effect.context<Logger>()
return Effect.runForkWith(services)(program)
}).pipe(
Effect.provideContext(Context.make(Logger, {
log: (message) => console.log(message)
}))
)
const fiber = Effect.runFork(main)
```
If your effect has no service requirements, use `Effect.runFork(effect)`.
## `Runtime` Module Contents
The `Runtime` module now only contains:
- `Teardown` — interface for handling process exit
- `defaultTeardown` — default teardown implementation
- `makeRunMain` — creates platform-specific main runners

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
# Scope
## `Scope.extend` → `Scope.provide`
`Scope.extend` has been renamed to `Scope.provide` in v4. The behavior is
identical: it provides a `Scope` to an effect that requires one, removing
`Scope` from the effect's requirements without closing the scope when the
effect completes.
The new name better reflects the operation — you are providing a service (the
`Scope`) to an effect, consistent with how other services are provided in
Effect.
**v3**
```ts
import { Effect, Scope } from "effect"
const program = Effect.gen(function*() {
const scope = yield* Scope.make()
yield* Scope.extend(myEffect, scope)
})
```
**v4**
```ts
import { Effect, Scope } from "effect"
const program = Effect.gen(function*() {
const scope = yield* Scope.make()
yield* Scope.provide(scope)(myEffect)
})
```
Both data-first and data-last (curried) forms are supported:
```ts
// data-first
Scope.provide(myEffect, scope)
// data-last (curried)
myEffect.pipe(Scope.provide(scope))
```
## Quick Reference
| v3 | v4 |
| -------------- | --------------- |
| `Scope.extend` | `Scope.provide` |

View File

@@ -0,0 +1,235 @@
# Services: `Context.Tag` → `Context.Service`
In v3, services were defined using `Context.Tag`, `Context.GenericTag`,
`Effect.Tag`, or `Effect.Service`. In v4, all of these have been replaced by
`Context.Service`.
The underlying runtime data structure is a typed map from service identifiers to
their implementations.
## Defining Services
**v3: `Context.GenericTag`**
```ts
import { Context } from "effect"
interface Database {
readonly query: (sql: string) => string
}
const Database = Context.GenericTag<Database>("Database")
```
**v4: `Context.Service` (function syntax)**
```ts
import { Context } from "effect"
interface Database {
readonly query: (sql: string) => string
}
const Database = Context.Service<Database>("Database")
```
## Class-Based Services
**v3: `Context.Tag` class syntax**
```ts
import { Context } from "effect"
class Database extends Context.Tag("Database")<Database, {
readonly query: (sql: string) => string
}>() {}
```
**v4: `Context.Service` class syntax**
```ts
import { Context } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => string
}>()("Database") {}
```
Note the difference in argument order: in v3, the identifier string is passed to
`Context.Tag(id)` before the type parameters. In v4, the type parameters come
first via `Context.Service<Self, Shape>()` and the identifier string is
passed to the returned constructor `(id)`.
## `Effect.Tag` Accessors → `Context.Service` with `use`
v3's `Effect.Tag` provided proxy access to service methods as static properties
on the tag class (accessors). This allowed calling service methods directly
without first yielding the service:
```ts
// v3 — static accessor proxy
const program = Notifications.notify("hello")
```
This pattern had significant limitations. The proxy was implemented via mapped
types over the service shape, which meant **generic methods lost their type
parameters**. A service method like `get<T>(key: string): Effect<T>` would
have its generic erased when accessed through the proxy, collapsing to
`get(key: string): Effect<unknown>`. For the same reason, overloaded signatures
were not preserved.
In v4, accessors are removed. The most direct replacement is `Service.use`,
which receives the service instance and runs a callback:
**v3**
```ts
import { Effect } from "effect"
class Notifications extends Effect.Tag("Notifications")<Notifications, {
readonly notify: (message: string) => Effect.Effect<void>
}>() {}
// Static proxy access
const program = Notifications.notify("hello")
```
**v4 — `use`**
```ts
import { Context, Effect } from "effect"
class Notifications extends Context.Service<Notifications, {
readonly notify: (message: string) => Effect.Effect<void>
}>()("Notifications") {}
// use: access the service and call a method in one step
const program = Notifications.use((n) => n.notify("hello"))
```
`use` takes an effectful callback `(service: Shape) => Effect<A, E, R>` and
returns an `Effect<A, E, R | Identifier>`. `useSync` takes a pure callback
`(service: Shape) => A` and returns an `Effect<A, never, Identifier>`. Both
return Effects — `useSync` just allows the accessor function itself to be
synchronous:
```ts
// ┌─── Effect<void, never, Notifications>
// ▼
const program = Notifications.use((n) => n.notify("hello"))
// ┌─── Effect<number, never, Config>
// ▼
const port = Config.useSync((c) => c.port)
```
**Prefer `yield*` over `use` in most cases.** While `use` is a convenient
one-liner, it makes it easy to accidentally leak service dependencies into
return values. When you call `use`, the service is available inside the
callback but the dependency is not visible at the call site — making it harder
to track which services your code depends on. Using `yield*` in a generator
makes dependencies explicit and keeps service access co-located with the rest
of your effect logic:
```ts
const program = Effect.gen(function*() {
const notifications = yield* Notifications
yield* notifications.notify("hello")
yield* notifications.notify("world")
})
```
## `Effect.Service` → `Context.Service` with `make`
v3's `Effect.Service` allowed defining a service with an effectful constructor
and dependencies inline. In v4, use `Context.Service` with a `make` option.
**v3**
In v3, `Effect.Service` automatically generated a `.Default` layer from the
provided constructor, and wired `dependencies` into it:
```ts
import { Effect, Layer } from "effect"
class Logger extends Effect.Service<Logger>()("Logger", {
effect: Effect.gen(function*() {
const config = yield* Config
return { log: (msg: string) => Effect.log(`[${config.prefix}] ${msg}`) }
}),
dependencies: [Config.Default]
}) {}
// Logger.Default is auto-generated: Layer<Logger, never, never>
// (dependencies are already wired in)
const program = Effect.gen(function*() {
const logger = yield* Logger
yield* logger.log("hello")
}).pipe(Effect.provide(Logger.Default))
```
**v4**
In v4, `Context.Service` with `make` stores the constructor effect on the
class but does **not** auto-generate a layer. Define layers explicitly using
`Layer.effect`:
```ts
import { Context, Effect, Layer } from "effect"
class Logger extends Context.Service<Logger>()("Logger", {
make: Effect.gen(function*() {
const config = yield* Config
return { log: (msg: string) => Effect.log(`[${config.prefix}] ${msg}`) }
})
}) {
// Build the layer yourself from the make effect
static readonly layer = Layer.effect(this, this.make).pipe(
Layer.provide(Config.layer)
)
}
```
The `dependencies` option no longer exists. Wire dependencies via
`Layer.provide` as shown above.
Note: v4 adopts the convention of naming layers with `layer` (e.g.
`Logger.layer`) instead of v3's `Default` or `Live`. Use `layer`
for the primary layer and descriptive suffixes for variants (e.g.
`layerTest`, `layerConfig`).
## References (Services with Defaults)
**v3: `Context.Reference`**
```ts
import { Context } from "effect"
class LogLevel extends Context.Reference<LogLevel>()("LogLevel", {
defaultValue: () => "info" as const
}) {}
```
**v4: `Context.Reference`**
```ts
import { Context } from "effect"
const LogLevel = Context.Reference<"info" | "warn" | "error">("LogLevel", {
defaultValue: () => "info" as const
})
```
## Quick Reference
| v3 | v4 |
| ------------------------------------- | --------------------------------------- |
| `Context.GenericTag<T>(id)` | `Context.Service<T>(id)` |
| `Context.Tag(id)<Self, Shape>()` | `Context.Service<Self, Shape>()(id)` |
| `Effect.Tag(id)<Self, Shape>()` | `Context.Service<Self, Shape>()(id)` |
| `Effect.Service<Self>()(id, opts)` | `Context.Service<Self>()(id, { make })` |
| `Context.Reference<Self>()(id, opts)` | `Context.Reference<T>(id, opts)` |
| `Context.make(tag, impl)` | `Context.make(tag, impl)` |
| `Context.get(ctx, tag)` | `Context.get(map, tag)` |
| `Context.add(ctx, tag, impl)` | `Context.add(map, tag, impl)` |
| `Context.mergeAll(...)` | `Context.mergeAll(...)` |

View File

@@ -0,0 +1,421 @@
# v3 to v4 Import and API Rename Maps
Mapped modules: 290
No counterpart: 43
API renames: 53
This file is intended for migration agents. It contains user-facing import
specifier mappings and API rename mappings.
Use the import map when rewriting import declarations. Use the API renames when
rewriting renamed symbols.
## Import Map
Each line is `v3 import -> v4 direct module import`. When a grouped v4 barrel
exists, the suggested barrel import is shown in parentheses.
```text
@effect/platform/ChannelSchema -> effect/ChannelSchema (barrel: effect)
@effect/platform/FileSystem -> effect/FileSystem (barrel: effect)
effect/JSONSchema -> effect/JsonSchema (barrel: effect)
@effect/platform/Path -> effect/Path (barrel: effect)
@effect/platform/Error -> effect/PlatformError (barrel: effect)
effect/Either -> effect/Result (barrel: effect)
@effect/platform/Terminal -> effect/Terminal (barrel: effect)
effect/TDeferred -> effect/TxDeferred (barrel: effect)
effect/TMap -> effect/TxHashMap (barrel: effect)
effect/TSet -> effect/TxHashSet (barrel: effect)
effect/TPriorityQueue -> effect/TxPriorityQueue (barrel: effect)
effect/TPubSub -> effect/TxPubSub (barrel: effect)
effect/TQueue -> effect/TxQueue (barrel: effect)
effect/TReentrantLock -> effect/TxReentrantLock (barrel: effect)
effect/TRef -> effect/TxRef (barrel: effect)
effect/TSemaphore -> effect/TxSemaphore (barrel: effect)
effect/TSubscriptionRef -> effect/TxSubscriptionRef (barrel: effect)
effect/FastCheck -> effect/testing/FastCheck (barrel: effect/testing)
effect/TestClock -> effect/testing/TestClock (barrel: effect/testing)
@effect/cli/Args -> effect/unstable/cli/Argument (barrel: effect/unstable/cli)
@effect/cli/ValidationError -> effect/unstable/cli/CliError (barrel: effect/unstable/cli)
@effect/cli/Command -> effect/unstable/cli/Command (barrel: effect/unstable/cli)
@effect/cli/CommandDescriptor -> effect/unstable/cli/Completions (barrel: effect/unstable/cli)
@effect/cli/Options -> effect/unstable/cli/Flag (barrel: effect/unstable/cli)
@effect/cli/BuiltInOptions -> effect/unstable/cli/GlobalFlag (barrel: effect/unstable/cli)
@effect/cli/HelpDoc -> effect/unstable/cli/HelpDoc (barrel: effect/unstable/cli)
@effect/cli/Primitive -> effect/unstable/cli/Primitive (barrel: effect/unstable/cli)
@effect/cli/Prompt -> effect/unstable/cli/Prompt (barrel: effect/unstable/cli)
@effect/cluster/ClusterCron -> effect/unstable/cluster/ClusterCron (barrel: effect/unstable/cluster)
@effect/cluster/ClusterError -> effect/unstable/cluster/ClusterError (barrel: effect/unstable/cluster)
@effect/cluster/ClusterMetrics -> effect/unstable/cluster/ClusterMetrics (barrel: effect/unstable/cluster)
@effect/cluster/ClusterSchema -> effect/unstable/cluster/ClusterSchema (barrel: effect/unstable/cluster)
@effect/cluster/ClusterWorkflowEngine -> effect/unstable/cluster/ClusterWorkflowEngine (barrel: effect/unstable/cluster)
@effect/cluster/DeliverAt -> effect/unstable/cluster/DeliverAt (barrel: effect/unstable/cluster)
@effect/cluster/Entity -> effect/unstable/cluster/Entity (barrel: effect/unstable/cluster)
@effect/cluster/EntityAddress -> effect/unstable/cluster/EntityAddress (barrel: effect/unstable/cluster)
@effect/cluster/EntityId -> effect/unstable/cluster/EntityId (barrel: effect/unstable/cluster)
@effect/cluster/EntityProxy -> effect/unstable/cluster/EntityProxy (barrel: effect/unstable/cluster)
@effect/cluster/EntityProxyServer -> effect/unstable/cluster/EntityProxyServer (barrel: effect/unstable/cluster)
@effect/cluster/EntityResource -> effect/unstable/cluster/EntityResource (barrel: effect/unstable/cluster)
@effect/cluster/EntityType -> effect/unstable/cluster/EntityType (barrel: effect/unstable/cluster)
@effect/cluster/Envelope -> effect/unstable/cluster/Envelope (barrel: effect/unstable/cluster)
@effect/cluster/HttpRunner -> effect/unstable/cluster/HttpRunner (barrel: effect/unstable/cluster)
@effect/cluster/K8sHttpClient -> effect/unstable/cluster/K8sHttpClient (barrel: effect/unstable/cluster)
@effect/cluster/MachineId -> effect/unstable/cluster/MachineId (barrel: effect/unstable/cluster)
@effect/cluster/Message -> effect/unstable/cluster/Message (barrel: effect/unstable/cluster)
@effect/cluster/MessageStorage -> effect/unstable/cluster/MessageStorage (barrel: effect/unstable/cluster)
@effect/cluster/Reply -> effect/unstable/cluster/Reply (barrel: effect/unstable/cluster)
@effect/cluster/Runner -> effect/unstable/cluster/Runner (barrel: effect/unstable/cluster)
@effect/cluster/RunnerAddress -> effect/unstable/cluster/RunnerAddress (barrel: effect/unstable/cluster)
@effect/cluster/RunnerHealth -> effect/unstable/cluster/RunnerHealth (barrel: effect/unstable/cluster)
@effect/cluster/RunnerServer -> effect/unstable/cluster/RunnerServer (barrel: effect/unstable/cluster)
@effect/cluster/RunnerStorage -> effect/unstable/cluster/RunnerStorage (barrel: effect/unstable/cluster)
@effect/cluster/Runners -> effect/unstable/cluster/Runners (barrel: effect/unstable/cluster)
@effect/cluster/ShardId -> effect/unstable/cluster/ShardId (barrel: effect/unstable/cluster)
@effect/cluster/Sharding -> effect/unstable/cluster/Sharding (barrel: effect/unstable/cluster)
@effect/cluster/ShardingConfig -> effect/unstable/cluster/ShardingConfig (barrel: effect/unstable/cluster)
@effect/cluster/ShardingRegistrationEvent -> effect/unstable/cluster/ShardingRegistrationEvent (barrel: effect/unstable/cluster)
@effect/cluster/SingleRunner -> effect/unstable/cluster/SingleRunner (barrel: effect/unstable/cluster)
@effect/cluster/Singleton -> effect/unstable/cluster/Singleton (barrel: effect/unstable/cluster)
@effect/cluster/SingletonAddress -> effect/unstable/cluster/SingletonAddress (barrel: effect/unstable/cluster)
@effect/cluster/Snowflake -> effect/unstable/cluster/Snowflake (barrel: effect/unstable/cluster)
@effect/cluster/SocketRunner -> effect/unstable/cluster/SocketRunner (barrel: effect/unstable/cluster)
@effect/cluster/SqlMessageStorage -> effect/unstable/cluster/SqlMessageStorage (barrel: effect/unstable/cluster)
@effect/cluster/SqlRunnerStorage -> effect/unstable/cluster/SqlRunnerStorage (barrel: effect/unstable/cluster)
@effect/cluster/TestRunner -> effect/unstable/cluster/TestRunner (barrel: effect/unstable/cluster)
@effect/experimental/DevTools -> effect/unstable/devtools/DevTools (barrel: effect/unstable/devtools)
@effect/experimental/DevTools/Client -> effect/unstable/devtools/DevToolsClient (barrel: effect/unstable/devtools)
@effect/experimental/DevTools/Domain -> effect/unstable/devtools/DevToolsSchema (barrel: effect/unstable/devtools)
@effect/experimental/DevTools/Server -> effect/unstable/devtools/DevToolsServer (barrel: effect/unstable/devtools)
@effect/platform/MsgPack -> effect/unstable/encoding/Msgpack (barrel: effect/unstable/encoding)
@effect/platform/Ndjson -> effect/unstable/encoding/Ndjson (barrel: effect/unstable/encoding)
@effect/experimental/Sse -> effect/unstable/encoding/Sse (barrel: effect/unstable/encoding)
@effect/ai/AiError -> effect/unstable/ai/AiError (barrel: effect/unstable/ai)
@effect/ai/Chat -> effect/unstable/ai/Chat (barrel: effect/unstable/ai)
@effect/ai/EmbeddingModel -> effect/unstable/ai/EmbeddingModel (barrel: effect/unstable/ai)
@effect/ai/IdGenerator -> effect/unstable/ai/IdGenerator (barrel: effect/unstable/ai)
@effect/ai/LanguageModel -> effect/unstable/ai/LanguageModel (barrel: effect/unstable/ai)
@effect/ai/McpSchema -> effect/unstable/ai/McpSchema (barrel: effect/unstable/ai)
@effect/ai/McpServer -> effect/unstable/ai/McpServer (barrel: effect/unstable/ai)
@effect/ai/Model -> effect/unstable/ai/Model (barrel: effect/unstable/ai)
@effect/ai/Prompt -> effect/unstable/ai/Prompt (barrel: effect/unstable/ai)
@effect/ai/Response -> effect/unstable/ai/Response (barrel: effect/unstable/ai)
@effect/ai/Telemetry -> effect/unstable/ai/Telemetry (barrel: effect/unstable/ai)
@effect/ai/Tokenizer -> effect/unstable/ai/Tokenizer (barrel: effect/unstable/ai)
@effect/ai/Tool -> effect/unstable/ai/Tool (barrel: effect/unstable/ai)
@effect/ai/Toolkit -> effect/unstable/ai/Toolkit (barrel: effect/unstable/ai)
@effect/experimental/Event -> effect/unstable/eventlog/Event (barrel: effect/unstable/eventlog)
@effect/experimental/EventGroup -> effect/unstable/eventlog/EventGroup (barrel: effect/unstable/eventlog)
@effect/experimental/EventJournal -> effect/unstable/eventlog/EventJournal (barrel: effect/unstable/eventlog)
@effect/experimental/EventLog -> effect/unstable/eventlog/EventLog (barrel: effect/unstable/eventlog)
@effect/experimental/EventLogEncryption -> effect/unstable/eventlog/EventLogEncryption (barrel: effect/unstable/eventlog)
@effect/experimental/EventLogRemote -> effect/unstable/eventlog/EventLogMessage (barrel: effect/unstable/eventlog)
@effect/experimental/EventLogRemote -> effect/unstable/eventlog/EventLogRemote (barrel: effect/unstable/eventlog)
@effect/experimental/EventLogServer -> effect/unstable/eventlog/EventLogServer (barrel: effect/unstable/eventlog)
@effect/experimental/EventLogServer -> effect/unstable/eventlog/EventLogServerEncrypted (barrel: effect/unstable/eventlog)
@effect/sql/SqlEventJournal -> effect/unstable/eventlog/SqlEventJournal (barrel: effect/unstable/eventlog)
@effect/sql/SqlEventLogServer -> effect/unstable/eventlog/SqlEventLogServerEncrypted (barrel: effect/unstable/eventlog)
@effect/platform/Cookies -> effect/unstable/http/Cookies (barrel: effect/unstable/http)
@effect/platform/Etag -> effect/unstable/http/Etag (barrel: effect/unstable/http)
@effect/platform/FetchHttpClient -> effect/unstable/http/FetchHttpClient (barrel: effect/unstable/http)
@effect/platform/Headers -> effect/unstable/http/Headers (barrel: effect/unstable/http)
@effect/platform/HttpBody -> effect/unstable/http/HttpBody (barrel: effect/unstable/http)
@effect/platform/HttpClient -> effect/unstable/http/HttpClient (barrel: effect/unstable/http)
@effect/platform/HttpClientError -> effect/unstable/http/HttpClientError (barrel: effect/unstable/http)
@effect/platform/HttpClientRequest -> effect/unstable/http/HttpClientRequest (barrel: effect/unstable/http)
@effect/platform/HttpClientResponse -> effect/unstable/http/HttpClientResponse (barrel: effect/unstable/http)
@effect/platform/HttpApp -> effect/unstable/http/HttpEffect (barrel: effect/unstable/http)
@effect/platform/HttpIncomingMessage -> effect/unstable/http/HttpIncomingMessage (barrel: effect/unstable/http)
@effect/platform/HttpMethod -> effect/unstable/http/HttpMethod (barrel: effect/unstable/http)
@effect/platform/HttpMiddleware -> effect/unstable/http/HttpMiddleware (barrel: effect/unstable/http)
@effect/platform/HttpPlatform -> effect/unstable/http/HttpPlatform (barrel: effect/unstable/http)
@effect/platform/HttpRouter -> effect/unstable/http/HttpRouter (barrel: effect/unstable/http)
@effect/platform/HttpServer -> effect/unstable/http/HttpServer (barrel: effect/unstable/http)
@effect/platform/HttpServerError -> effect/unstable/http/HttpServerError (barrel: effect/unstable/http)
@effect/platform/HttpServerRequest -> effect/unstable/http/HttpServerRequest (barrel: effect/unstable/http)
@effect/platform/HttpServerRespondable -> effect/unstable/http/HttpServerRespondable (barrel: effect/unstable/http)
@effect/platform/HttpServerResponse -> effect/unstable/http/HttpServerResponse (barrel: effect/unstable/http)
@effect/platform/HttpTraceContext -> effect/unstable/http/HttpTraceContext (barrel: effect/unstable/http)
@effect/platform/Multipart -> effect/unstable/http/Multipart (barrel: effect/unstable/http)
@effect/platform/Template -> effect/unstable/http/Template (barrel: effect/unstable/http)
@effect/platform/Url -> effect/unstable/http/Url (barrel: effect/unstable/http)
@effect/platform/UrlParams -> effect/unstable/http/UrlParams (barrel: effect/unstable/http)
@effect/platform/HttpApi -> effect/unstable/httpapi/HttpApi (barrel: effect/unstable/httpapi)
@effect/platform/HttpApiBuilder -> effect/unstable/httpapi/HttpApiBuilder (barrel: effect/unstable/httpapi)
@effect/platform/HttpApiClient -> effect/unstable/httpapi/HttpApiClient (barrel: effect/unstable/httpapi)
@effect/platform/HttpApiEndpoint -> effect/unstable/httpapi/HttpApiEndpoint (barrel: effect/unstable/httpapi)
@effect/platform/HttpApiError -> effect/unstable/httpapi/HttpApiError (barrel: effect/unstable/httpapi)
@effect/platform/HttpApiGroup -> effect/unstable/httpapi/HttpApiGroup (barrel: effect/unstable/httpapi)
@effect/platform/HttpApiMiddleware -> effect/unstable/httpapi/HttpApiMiddleware (barrel: effect/unstable/httpapi)
@effect/platform/HttpApiScalar -> effect/unstable/httpapi/HttpApiScalar (barrel: effect/unstable/httpapi)
@effect/platform/HttpApiSchema -> effect/unstable/httpapi/HttpApiSchema (barrel: effect/unstable/httpapi)
@effect/platform/HttpApiSecurity -> effect/unstable/httpapi/HttpApiSecurity (barrel: effect/unstable/httpapi)
@effect/platform/HttpApiSwagger -> effect/unstable/httpapi/HttpApiSwagger (barrel: effect/unstable/httpapi)
@effect/platform/OpenApi -> effect/unstable/httpapi/OpenApi (barrel: effect/unstable/httpapi)
@effect/opentelemetry/Otlp -> effect/unstable/observability/Otlp (barrel: effect/unstable/observability)
@effect/opentelemetry/internal/otlpExporter -> effect/unstable/observability/OtlpExporter (barrel: effect/unstable/observability)
@effect/opentelemetry/OtlpLogger -> effect/unstable/observability/OtlpLogger (barrel: effect/unstable/observability)
@effect/opentelemetry/OtlpMetrics -> effect/unstable/observability/OtlpMetrics (barrel: effect/unstable/observability)
@effect/opentelemetry/OtlpResource -> effect/unstable/observability/OtlpResource (barrel: effect/unstable/observability)
@effect/opentelemetry/OtlpSerialization -> effect/unstable/observability/OtlpSerialization (barrel: effect/unstable/observability)
@effect/opentelemetry/OtlpTracer -> effect/unstable/observability/OtlpTracer (barrel: effect/unstable/observability)
@effect/platform/KeyValueStore -> effect/unstable/persistence/KeyValueStore (barrel: effect/unstable/persistence)
@effect/experimental/Persistence -> effect/unstable/persistence/Persistable (barrel: effect/unstable/persistence)
@effect/experimental/PersistedCache -> effect/unstable/persistence/PersistedCache (barrel: effect/unstable/persistence)
@effect/experimental/PersistedQueue -> effect/unstable/persistence/PersistedQueue (barrel: effect/unstable/persistence)
@effect/experimental/Persistence -> effect/unstable/persistence/Persistence (barrel: effect/unstable/persistence)
@effect/experimental/RateLimiter -> effect/unstable/persistence/RateLimiter (barrel: effect/unstable/persistence)
@effect/platform/Command -> effect/unstable/process/ChildProcess (barrel: effect/unstable/process)
@effect/platform/CommandExecutor -> effect/unstable/process/ChildProcessSpawner (barrel: effect/unstable/process)
@effect/experimental/Reactivity -> effect/unstable/reactivity/Reactivity (barrel: effect/unstable/reactivity)
@effect/rpc/Rpc -> effect/unstable/rpc/Rpc (barrel: effect/unstable/rpc)
@effect/rpc/RpcClient -> effect/unstable/rpc/RpcClient (barrel: effect/unstable/rpc)
@effect/rpc/RpcClientError -> effect/unstable/rpc/RpcClientError (barrel: effect/unstable/rpc)
@effect/rpc/RpcGroup -> effect/unstable/rpc/RpcGroup (barrel: effect/unstable/rpc)
@effect/rpc/RpcMessage -> effect/unstable/rpc/RpcMessage (barrel: effect/unstable/rpc)
@effect/rpc/RpcMiddleware -> effect/unstable/rpc/RpcMiddleware (barrel: effect/unstable/rpc)
@effect/rpc/RpcSchema -> effect/unstable/rpc/RpcSchema (barrel: effect/unstable/rpc)
@effect/rpc/RpcSerialization -> effect/unstable/rpc/RpcSerialization (barrel: effect/unstable/rpc)
@effect/rpc/RpcServer -> effect/unstable/rpc/RpcServer (barrel: effect/unstable/rpc)
@effect/rpc/RpcTest -> effect/unstable/rpc/RpcTest (barrel: effect/unstable/rpc)
@effect/rpc/RpcWorker -> effect/unstable/rpc/RpcWorker (barrel: effect/unstable/rpc)
@effect/sql/Model -> effect/unstable/schema/Model (barrel: effect/unstable/schema)
@effect/experimental/VariantSchema -> effect/unstable/schema/VariantSchema (barrel: effect/unstable/schema)
@effect/platform/Socket -> effect/unstable/socket/Socket (barrel: effect/unstable/socket)
@effect/platform/SocketServer -> effect/unstable/socket/SocketServer (barrel: effect/unstable/socket)
@effect/sql/Migrator -> effect/unstable/sql/Migrator (barrel: effect/unstable/sql)
@effect/sql/SqlClient -> effect/unstable/sql/SqlClient (barrel: effect/unstable/sql)
@effect/sql/SqlConnection -> effect/unstable/sql/SqlConnection (barrel: effect/unstable/sql)
@effect/sql/SqlError -> effect/unstable/sql/SqlError (barrel: effect/unstable/sql)
@effect/sql/Model -> effect/unstable/sql/SqlModel (barrel: effect/unstable/sql)
@effect/sql/SqlResolver -> effect/unstable/sql/SqlResolver (barrel: effect/unstable/sql)
@effect/sql/SqlSchema -> effect/unstable/sql/SqlSchema (barrel: effect/unstable/sql)
@effect/sql/SqlStream -> effect/unstable/sql/SqlStream (barrel: effect/unstable/sql)
@effect/sql/Statement -> effect/unstable/sql/Statement (barrel: effect/unstable/sql)
@effect/platform/Transferable -> effect/unstable/workers/Transferable (barrel: effect/unstable/workers)
@effect/platform/Worker -> effect/unstable/workers/Worker (barrel: effect/unstable/workers)
@effect/platform/WorkerError -> effect/unstable/workers/WorkerError (barrel: effect/unstable/workers)
@effect/platform/WorkerRunner -> effect/unstable/workers/WorkerRunner (barrel: effect/unstable/workers)
@effect/workflow/Activity -> effect/unstable/workflow/Activity (barrel: effect/unstable/workflow)
@effect/workflow/DurableClock -> effect/unstable/workflow/DurableClock (barrel: effect/unstable/workflow)
@effect/workflow/DurableDeferred -> effect/unstable/workflow/DurableDeferred (barrel: effect/unstable/workflow)
@effect/workflow/DurableQueue -> effect/unstable/workflow/DurableQueue (barrel: effect/unstable/workflow)
@effect/workflow/Workflow -> effect/unstable/workflow/Workflow (barrel: effect/unstable/workflow)
@effect/workflow/WorkflowEngine -> effect/unstable/workflow/WorkflowEngine (barrel: effect/unstable/workflow)
@effect/workflow/WorkflowProxy -> effect/unstable/workflow/WorkflowProxy (barrel: effect/unstable/workflow)
@effect/workflow/WorkflowProxyServer -> effect/unstable/workflow/WorkflowProxyServer (barrel: effect/unstable/workflow)
effect/Array -> effect/Array (barrel: effect)
effect/BigDecimal -> effect/BigDecimal (barrel: effect)
effect/BigInt -> effect/BigInt (barrel: effect)
effect/Boolean -> effect/Boolean (barrel: effect)
effect/Brand -> effect/Brand (barrel: effect)
effect/Cache -> effect/Cache (barrel: effect)
effect/Cause -> effect/Cause (barrel: effect)
effect/Channel -> effect/Channel (barrel: effect)
effect/Chunk -> effect/Chunk (barrel: effect)
effect/Clock -> effect/Clock (barrel: effect)
@effect/typeclass/Semigroup -> effect/Combiner (barrel: effect)
effect/Config -> effect/Config (barrel: effect)
effect/ConfigProvider -> effect/ConfigProvider (barrel: effect)
effect/Console -> effect/Console (barrel: effect)
effect/Context -> effect/Context (barrel: effect)
effect/Cron -> effect/Cron (barrel: effect)
effect/Data -> effect/Data (barrel: effect)
effect/DateTime -> effect/DateTime (barrel: effect)
effect/Deferred -> effect/Deferred (barrel: effect)
effect/Differ -> effect/Differ (barrel: effect)
effect/Duration -> effect/Duration (barrel: effect)
effect/Effect -> effect/Effect (barrel: effect)
effect/Effectable -> effect/Effectable (barrel: effect)
effect/Encoding -> effect/Encoding (barrel: effect)
effect/Equal -> effect/Equal (barrel: effect)
effect/Equivalence -> effect/Equivalence (barrel: effect)
effect/ExecutionPlan -> effect/ExecutionPlan (barrel: effect)
effect/Exit -> effect/Exit (barrel: effect)
effect/Fiber -> effect/Fiber (barrel: effect)
effect/FiberHandle -> effect/FiberHandle (barrel: effect)
effect/FiberMap -> effect/FiberMap (barrel: effect)
effect/FiberSet -> effect/FiberSet (barrel: effect)
effect/Inspectable -> effect/Formatter (barrel: effect)
effect/Function -> effect/Function (barrel: effect)
effect/Graph -> effect/Graph (barrel: effect)
effect/HKT -> effect/HKT (barrel: effect)
effect/Hash -> effect/Hash (barrel: effect)
effect/HashMap -> effect/HashMap (barrel: effect)
effect/HashRing -> effect/HashRing (barrel: effect)
effect/HashSet -> effect/HashSet (barrel: effect)
effect/Inspectable -> effect/Inspectable (barrel: effect)
effect/Iterable -> effect/Iterable (barrel: effect)
effect/Layer -> effect/Layer (barrel: effect)
effect/LayerMap -> effect/LayerMap (barrel: effect)
effect/LogLevel -> effect/LogLevel (barrel: effect)
effect/Logger -> effect/Logger (barrel: effect)
effect/ManagedRuntime -> effect/ManagedRuntime (barrel: effect)
effect/Match -> effect/Match (barrel: effect)
effect/Metric -> effect/Metric (barrel: effect)
effect/MutableHashMap -> effect/MutableHashMap (barrel: effect)
effect/MutableHashSet -> effect/MutableHashSet (barrel: effect)
effect/MutableList -> effect/MutableList (barrel: effect)
effect/MutableRef -> effect/MutableRef (barrel: effect)
effect/NonEmptyIterable -> effect/NonEmptyIterable (barrel: effect)
effect/Number -> effect/Number (barrel: effect)
effect/Option -> effect/Option (barrel: effect)
effect/Order -> effect/Order (barrel: effect)
effect/Ordering -> effect/Ordering (barrel: effect)
effect/PartitionedSemaphore -> effect/PartitionedSemaphore (barrel: effect)
effect/Pipeable -> effect/Pipeable (barrel: effect)
effect/Pool -> effect/Pool (barrel: effect)
effect/Predicate -> effect/Predicate (barrel: effect)
effect/PrimaryKey -> effect/PrimaryKey (barrel: effect)
effect/PubSub -> effect/PubSub (barrel: effect)
effect/Queue -> effect/Queue (barrel: effect)
effect/Random -> effect/Random (barrel: effect)
effect/RcMap -> effect/RcMap (barrel: effect)
effect/RcRef -> effect/RcRef (barrel: effect)
effect/Record -> effect/Record (barrel: effect)
effect/Inspectable -> effect/Redactable (barrel: effect)
effect/Redacted -> effect/Redacted (barrel: effect)
@effect/typeclass/Monoid -> effect/Reducer (barrel: effect)
effect/Ref -> effect/Ref (barrel: effect)
effect/FiberRef -> effect/References (barrel: effect)
effect/RegExp -> effect/RegExp (barrel: effect)
effect/Request -> effect/Request (barrel: effect)
effect/RequestResolver -> effect/RequestResolver (barrel: effect)
effect/Resource -> effect/Resource (barrel: effect)
effect/Runtime -> effect/Runtime (barrel: effect)
effect/Schedule -> effect/Schedule (barrel: effect)
effect/Scheduler -> effect/Scheduler (barrel: effect)
effect/Schema -> effect/Schema (barrel: effect)
effect/SchemaAST -> effect/SchemaAST (barrel: effect)
effect/ParseResult -> effect/SchemaIssue (barrel: effect)
effect/ParseResult -> effect/SchemaParser (barrel: effect)
effect/Schema -> effect/SchemaTransformation (barrel: effect)
effect/Scope -> effect/Scope (barrel: effect)
effect/ScopedCache -> effect/ScopedCache (barrel: effect)
effect/ScopedRef -> effect/ScopedRef (barrel: effect)
effect/Sink -> effect/Sink (barrel: effect)
effect/Stream -> effect/Stream (barrel: effect)
effect/String -> effect/String (barrel: effect)
effect/Struct -> effect/Struct (barrel: effect)
effect/SubscriptionRef -> effect/SubscriptionRef (barrel: effect)
effect/Symbol -> effect/Symbol (barrel: effect)
effect/SynchronizedRef -> effect/SynchronizedRef (barrel: effect)
effect/Take -> effect/Take (barrel: effect)
effect/Tracer -> effect/Tracer (barrel: effect)
effect/Trie -> effect/Trie (barrel: effect)
effect/Tuple -> effect/Tuple (barrel: effect)
effect/Types -> effect/Types (barrel: effect)
effect/Unify -> effect/Unify (barrel: effect)
effect/Utils -> effect/Utils (barrel: effect)
```
## No Counterpart Imports
These v4 modules did not have a mapped v3 module. Treat them as v4-only unless a
more specific migration guide says otherwise.
```text
effect/ErrorReporter (barrel: effect)
effect/Filter (barrel: effect)
effect/JsonPatch (barrel: effect)
effect/JsonPointer (barrel: effect)
effect/Latch (barrel: effect)
effect/Newtype (barrel: effect)
effect/Optic (barrel: effect)
effect/Pull (barrel: effect)
effect/SchemaGetter (barrel: effect)
effect/SchemaRepresentation (barrel: effect)
effect/SchemaUtils (barrel: effect)
effect/Semaphore (barrel: effect)
effect/Stdio (barrel: effect)
effect/TxChunk (barrel: effect)
effect/UndefinedOr (barrel: effect)
effect/testing/TestConsole (barrel: effect/testing)
effect/testing/TestSchema (barrel: effect/testing)
effect/unstable/ai/AnthropicStructuredOutput (barrel: effect/unstable/ai)
effect/unstable/ai/OpenAiStructuredOutput (barrel: effect/unstable/ai)
effect/unstable/ai/ResponseIdTracker (barrel: effect/unstable/ai)
effect/unstable/cli/CliOutput (barrel: effect/unstable/cli)
effect/unstable/cli/Param (barrel: effect/unstable/cli)
effect/unstable/eventlog/EventLogServerUnencrypted (barrel: effect/unstable/eventlog)
effect/unstable/eventlog/EventLogSessionAuth (barrel: effect/unstable/eventlog)
effect/unstable/eventlog/SqlEventLogServerUnencrypted (barrel: effect/unstable/eventlog)
effect/unstable/http/FindMyWay (barrel: effect/unstable/http)
effect/unstable/http/HttpStaticServer (barrel: effect/unstable/http)
effect/unstable/http/Multipasta (barrel: effect/unstable/http)
effect/unstable/http/Multipasta/HeadersParser (barrel: effect/unstable/http)
effect/unstable/http/Multipasta/Node (barrel: effect/unstable/http)
effect/unstable/http/Multipasta/Search (barrel: effect/unstable/http)
effect/unstable/http/Multipasta/Web (barrel: effect/unstable/http)
effect/unstable/httpapi/HttpApiTest (barrel: effect/unstable/httpapi)
effect/unstable/observability/PrometheusMetrics (barrel: effect/unstable/observability)
effect/unstable/persistence/Redis (barrel: effect/unstable/persistence)
effect/unstable/reactivity/AsyncResult (barrel: effect/unstable/reactivity)
effect/unstable/reactivity/Atom (barrel: effect/unstable/reactivity)
effect/unstable/reactivity/AtomHttpApi (barrel: effect/unstable/reactivity)
effect/unstable/reactivity/AtomRef (barrel: effect/unstable/reactivity)
effect/unstable/reactivity/AtomRegistry (barrel: effect/unstable/reactivity)
effect/unstable/reactivity/AtomRpc (barrel: effect/unstable/reactivity)
effect/unstable/reactivity/Hydration (barrel: effect/unstable/reactivity)
effect/unstable/rpc/Utils (barrel: effect/unstable/rpc)
```
## API Renames
Each line is `v3 API -> v4 API`. Use these mappings when rewriting renamed
symbols from v3 source code to v4.
```text
Effect.async -> Effect.callback
Effect.zipRight -> Effect.andThen
Effect.zipLeft -> Effect.tap
Effect.either -> Effect.result
Effect.catchAll -> Effect.catch
Effect.catchAllCause -> Effect.catchCause
Effect.catchAllDefect -> Effect.catchDefect
Effect.catchSome -> Effect.catchIf
Effect.catchIf -> Effect.catchIf
Effect.optionFromOptional -> Effect.catchNoSuchElement
Effect.catchSomeCause -> Effect.catchCauseIf
Effect.tapErrorCause -> Effect.tapCause
Effect.ignoreLogged -> Effect.ignore
Effect.makeLatchUnsafe -> Latch.makeUnsafe
Effect.makeLatch -> Latch.make
Layer.scoped -> Layer.effect
Layer.scopedDiscard -> Layer.effectDiscard
Layer.tapErrorCause -> Layer.tapCause
Mailbox -> Queue.Queue
Mailbox.make -> Queue.make
Either -> Result.Result
Either.right -> Result.succeed
Either.left -> Result.fail
Scope.extend -> Scope.provide
Effect.makeSemaphoreUnsafe -> Semaphore.makeUnsafe
Effect.makeSemaphore -> Semaphore.make
Stream.Context -> Stream.Services
StreamHaltStrategy.HaltStrategy -> Stream.HaltStrategy
Stream.repeatEffect -> Stream.fromEffectRepeat
Stream.repeatEffectWithSchedule -> Stream.fromEffectSchedule
Stream.async -> Stream.callback
Stream.asyncEffect -> Stream.callback
Stream.asyncPush -> Stream.callback
Stream.asyncScoped -> Stream.callback
Stream.repeatEffectChunk -> Stream.fromIterableEffectRepeat
Stream.fromChunk -> Stream.fromArray
Stream.fromChunks -> Stream.fromArrays
Stream.mapChunks -> Stream.mapArray
Stream.mapChunksEffect -> Stream.mapArrayEffect
Stream.either -> Stream.result
Stream.flattenChunks -> Stream.flattenArray
Stream.flattenIterables -> Stream.flattenIterable
Stream.mergeEither -> Stream.mergeResult
Stream.zipWithChunks -> Stream.zipWithArray
Stream.bufferChunks -> Stream.bufferArray
Stream.catchAllCause -> Stream.catchCause
Stream.tapErrorCause -> Stream.tapCause
Stream.catchAll -> Stream.catch
Stream.catchSome -> Stream.catchIf
Stream.catchSomeCause -> Stream.catchCauseIf
Stream.combineChunks -> Stream.combineArray
provideSomeLayer -> Stream.provide
provideSomeContext -> Stream.provide
```

View File

@@ -0,0 +1,173 @@
# Effect Subtyping (v3) → Yieldable (v4)
In v3, many types were structural subtypes of `Effect` — they carried the
Effect type ID at runtime and could be used anywhere an `Effect` was expected.
This included `Ref`, `Deferred`, `Fiber`, `FiberRef`, `Config`, `Option`,
`Either`, `Context.Tag`, and others.
While convenient, this created a class of subtle bugs. Because these types
_were_ Effects, they could be silently passed to Effect combinators when you
intended to pass the value itself. For example, passing a `Ref` where you meant
to pass the value inside the `Ref`, or accidentally mapping over a `Deferred`
as an Effect instead of awaiting it.
v4 replaces this with the **`Yieldable`** trait: a narrower contract that
allows `yield*` in generators but does **not** make the type assignable to
`Effect`.
## The `Yieldable` Interface
```ts
interface Yieldable<Self, A, E = never, R = never> {
asEffect(): Effect<A, E, R>
[Symbol.iterator](): EffectIterator<Self>
}
```
Some example types that implement `Yieldable`:
- `Effect` itself
- `Option` — yields the value or fails with `NoSuchElementError`
- `Result` — yields the success or fails with the error
- `Config` — yields the config value or fails with `ConfigError`
- `Context.Service` — yields the service from the environment
Some example types that are **no longer** Effect subtypes and do **not**
implement `Yieldable`:
- `Ref` — use `Ref.get(ref)` to read
- `Deferred` — use `Deferred.await(deferred)` to wait
- `Fiber` — use `Fiber.join(fiber)` to await
## `yield*` Still Works
`yield*` in `Effect.gen` works with any `Yieldable`. The runtime calls
`.asEffect()` internally when yielding.
```ts
import { Effect, Option } from "effect"
// The type of program is `Effect<number, NoSuchElementError>`
const program = Effect.gen(function*() {
// yield* works with Yieldable types — same as v3
const value = yield* Option.some(42)
return value // 42
})
```
## Effect Combinators Require `.asEffect()`
In v3, you could pass a `Yieldable` type directly to Effect combinators because
it was a subtype of `Effect`. In v4, you must explicitly convert with
`.asEffect()`.
**v3** — Option is an Effect subtype, so this compiles:
```ts
import { Effect, Option } from "effect"
// Option<number> is assignable to Effect<number, NoSuchElementError>
const program = Effect.map(Option.some(42), (n) => n + 1)
```
**v4** — Option is not an Effect, so you must convert explicitly:
```ts
import { Effect, Option } from "effect"
// Option is Yieldable but not Effect — use .asEffect()
const program = Effect.map(Option.some(42).asEffect(), (n) => n + 1)
// Or more idiomatically, use a generator:
const program2 = Effect.gen(function*() {
const n = yield* Option.some(42)
return n + 1
})
```
## Types No Longer Subtypes of Effect
Several types that extended `Effect` in v3 no longer do so in v4. Use the
appropriate module functions instead.
**v3**`Ref` extends `Effect<A>`, yielding the current value:
```ts
import { Effect, Ref } from "effect"
const program = Effect.gen(function*() {
const ref = yield* Ref.make(0)
const value = yield* ref // Ref is an Effect<number>
})
```
**v4**`Ref` is a plain value, use `Ref.get`:
```ts
import { Effect, Ref } from "effect"
const program = Effect.gen(function*() {
const ref = yield* Ref.make(0)
const value = yield* Ref.get(ref)
})
```
**v3**`Deferred` extends `Effect<A, E>`, resolving when completed:
```ts
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() {
const deferred = yield* Deferred.make<string, never>()
const value = yield* deferred // Deferred is an Effect<string>
})
```
**v4**`Deferred` is a plain value, use `Deferred.await`:
```ts
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() {
const deferred = yield* Deferred.make<string, never>()
const value = yield* Deferred.await(deferred)
})
```
**v3**`Fiber` extends `Effect<A, E>`, joining on yield:
```ts
import { Effect, Fiber } from "effect"
const program = Effect.gen(function*() {
const fiber = yield* Effect.fork(task)
const result = yield* fiber // Fiber is an Effect<A, E>
})
```
**v4**`Fiber` is a plain value, use `Fiber.join`:
```ts
import { Effect, Fiber } from "effect"
const program = Effect.gen(function*() {
const fiber = yield* Effect.forkChild(task)
const result = yield* Fiber.join(fiber)
})
```
## Why This Changed
The v3 subtyping approach meant the type system could not distinguish between
"I have a Ref" and "I have an Effect that reads the Ref." This ambiguity led
to bugs that were difficult to diagnose:
- Passing a `Ref` to `Effect.map` would read the ref's value rather than
transforming the ref itself — often not the intended behavior.
- A `Deferred` in a data structure could silently be treated as an Effect,
causing unexpected awaits.
- Combinators like `Effect.all` would accept an array of `Ref` values and
silently read all of them, instead of producing a type error.
The `Yieldable` trait preserves the ergonomic `yield*` syntax in generators
while making the conversion to `Effect` explicit everywhere else.