diff --git a/.changeset/calm-houses-brake.md b/.changeset/calm-houses-brake.md
new file mode 100644
index 00000000000..d98f0b2b8d2
--- /dev/null
+++ b/.changeset/calm-houses-brake.md
@@ -0,0 +1,20 @@
+---
+"effect": minor
+---
+
+add `Logger.withLeveledConsole`
+
+In browsers and different platforms, `console.error` renders differently than `console.info`. This helps to distinguish between different levels of logging. `Logger.withLeveledConsole` takes any logger and calls the respective `Console` method based on the log level. For instance, `Effect.logError` will call `Console.error` and `Effect.logInfo` will call `Console.info`.
+
+To use it, you can replace the default logger with a `Logger.withLeveledConsole` logger:
+
+```ts
+import { Logger, Effect } from "effect"
+
+const loggerLayer = Logger.withLeveledConsole(Logger.stringLogger)
+
+Effect.gen(function* () {
+ yield* Effect.logError("an error")
+ yield* Effect.logInfo("an info")
+}).pipe(Effect.provide(loggerLayer))
+```
diff --git a/.changeset/eighty-lobsters-refuse.md b/.changeset/eighty-lobsters-refuse.md
new file mode 100644
index 00000000000..571aff3b769
--- /dev/null
+++ b/.changeset/eighty-lobsters-refuse.md
@@ -0,0 +1,5 @@
+---
+"effect": minor
+---
+
+Made `Ref`, `SynchronizedRed` and `SubscriptionRef` a subtype of `Effect`
diff --git a/.changeset/few-mayflies-speak.md b/.changeset/few-mayflies-speak.md
new file mode 100644
index 00000000000..1db7728fde1
--- /dev/null
+++ b/.changeset/few-mayflies-speak.md
@@ -0,0 +1,21 @@
+---
+"effect": minor
+---
+
+add Semaphore.withPermitsIfAvailable
+
+You can now use `Semaphore.withPermitsIfAvailable` to run an Effect only if the
+Semaphore has enough permits available. This is useful when you want to run an
+Effect only if you can acquire a permit without blocking.
+
+It will return an `Option.Some` with the result of the Effect if the permits were
+available, or `None` if they were not.
+
+```ts
+import { Effect } from "effect"
+
+Effect.gen(function* () {
+ const semaphore = yield* Effect.makeSemaphore(1)
+ semaphore.withPermitsIfAvailable(1)(Effect.void)
+})
+```
diff --git a/.changeset/gorgeous-toes-help.md b/.changeset/gorgeous-toes-help.md
new file mode 100644
index 00000000000..1bc6a01a71d
--- /dev/null
+++ b/.changeset/gorgeous-toes-help.md
@@ -0,0 +1,16 @@
+---
+"effect": minor
+---
+
+The `Deferred` is now a subtype of `Effect`. This change simplifies handling of deferred values, removing the need for explicit call `Deffer.await`.
+
+```typescript
+import { Effect, Deferred } from "effect"
+
+Effect.gen(function* () {
+ const deferred = yield* Deferred.make()
+
+ const before = yield* Deferred.await(deferred)
+ const after = yield* deferred
+})
+```
diff --git a/.changeset/honest-cups-wash.md b/.changeset/honest-cups-wash.md
new file mode 100644
index 00000000000..788db95a1dc
--- /dev/null
+++ b/.changeset/honest-cups-wash.md
@@ -0,0 +1,6 @@
+---
+"effect": minor
+"@effect/platform": patch
+---
+
+add Logger.prettyLoggerDefault, to prevent duplicate pretty loggers
diff --git a/.changeset/lucky-eagles-speak.md b/.changeset/lucky-eagles-speak.md
new file mode 100644
index 00000000000..c25560826a3
--- /dev/null
+++ b/.changeset/lucky-eagles-speak.md
@@ -0,0 +1,24 @@
+---
+"effect": minor
+---
+
+add Effect.makeLatch, for creating a simple async latch
+
+```ts
+import { Effect } from "effect"
+
+Effect.gen(function* () {
+ // Create a latch, starting in the closed state
+ const latch = yield* Effect.makeLatch(false)
+
+ // Fork a fiber that logs "open sesame" when the latch is opened
+ const fiber = yield* Effect.log("open sesame").pipe(
+ latch.whenOpen,
+ Effect.fork
+ )
+
+ // Open the latch
+ yield* latch.open
+ yield* fiber.await
+})
+```
diff --git a/.changeset/quick-roses-warn.md b/.changeset/quick-roses-warn.md
new file mode 100644
index 00000000000..bc59fe60d30
--- /dev/null
+++ b/.changeset/quick-roses-warn.md
@@ -0,0 +1,6 @@
+---
+"effect": minor
+"@effect/cli": patch
+---
+
+Add Number.round
diff --git a/.changeset/seven-lamps-nail.md b/.changeset/seven-lamps-nail.md
new file mode 100644
index 00000000000..7691b1b0f95
--- /dev/null
+++ b/.changeset/seven-lamps-nail.md
@@ -0,0 +1,16 @@
+---
+"effect": minor
+---
+
+The `Fiber` is now a subtype of `Effect`. This change removes the need for explicit call `Fiber.join`.
+
+```typescript
+import { Effect, Fiber } from "effect"
+
+Effect.gen(function*() {
+ const fiber = yield* Effect.fork(Effect.succeed(1))
+
+ const oldWay = yield* Fiber.join(fiber)
+ const now = yield* fiber
+}))
+```
diff --git a/.changeset/stream-share.md b/.changeset/stream-share.md
new file mode 100644
index 00000000000..f1dee66bd5e
--- /dev/null
+++ b/.changeset/stream-share.md
@@ -0,0 +1,10 @@
+---
+"effect": minor
+---
+
+add `Stream.share` api
+
+The `Stream.share` api is a ref counted variant of the broadcast apis.
+
+It allows you to share a stream between multiple consumers, and will close the
+upstream when the last consumer ends.
diff --git a/.changeset/strong-moose-tickle.md b/.changeset/strong-moose-tickle.md
new file mode 100644
index 00000000000..c5dde2ac2e5
--- /dev/null
+++ b/.changeset/strong-moose-tickle.md
@@ -0,0 +1,5 @@
+---
+"effect": minor
+---
+
+add Cause.Annotated
diff --git a/.changeset/tender-foxes-walk.md b/.changeset/tender-foxes-walk.md
new file mode 100644
index 00000000000..d7574348dcf
--- /dev/null
+++ b/.changeset/tender-foxes-walk.md
@@ -0,0 +1,87 @@
+---
+"@effect/platform-browser": minor
+"@effect/platform-node": minor
+"@effect/platform-bun": minor
+"@effect/platform": minor
+"@effect/rpc-http": minor
+---
+
+refactor /platform HttpClient
+
+#### HttpClient.fetch removed
+
+The `HttpClient.fetch` client implementation has been removed. Instead, you can
+access a `HttpClient` using the corresponding `Context.Tag`.
+
+```ts
+import { FetchHttpClient, HttpClient } from "@effect/platform"
+import { Effect } from "effect"
+
+Effect.gen(function* () {
+ const client = yield* HttpClient.HttpClient
+
+ // make a get request
+ yield* client.get("https://jsonplaceholder.typicode.com/todos/1")
+}).pipe(
+ Effect.scoped,
+ // the fetch client has been moved to the `FetchHttpClient` module
+ Effect.provide(FetchHttpClient.layer)
+)
+```
+
+#### `HttpClient` interface now uses methods
+
+Instead of being a function that returns the response, the `HttpClient`
+interface now uses methods to make requests.
+
+Some shorthand methods have been added to the `HttpClient` interface to make
+less complex requests easier.
+
+```ts
+import {
+ FetchHttpClient,
+ HttpClient,
+ HttpClientRequest
+} from "@effect/platform"
+import { Effect } from "effect"
+
+Effect.gen(function* () {
+ const client = yield* HttpClient.HttpClient
+
+ // make a get request
+ yield* client.get("https://jsonplaceholder.typicode.com/todos/1")
+ // make a post request
+ yield* client.post("https://jsonplaceholder.typicode.com/todos")
+
+ // execute a request instance
+ yield* client.execute(
+ HttpClientRequest.get("https://jsonplaceholder.typicode.com/todos/1")
+ )
+})
+```
+
+#### Scoped `HttpClientResponse` helpers removed
+
+The `HttpClientResponse` helpers that also eliminated the `Scope` have been removed.
+
+Instead, you can use the `HttpClientResponse` methods directly, and explicitly
+add a `Effect.scoped` to the pipeline.
+
+```ts
+import { FetchHttpClient, HttpClient } from "@effect/platform"
+import { Effect } from "effect"
+
+Effect.gen(function* () {
+ const client = yield* HttpClient.HttpClient
+
+ yield* client.get("https://jsonplaceholder.typicode.com/todos/1").pipe(
+ Effect.flatMap((response) => response.json),
+ Effect.scoped // eliminate the `Scope`
+ )
+})
+```
+
+#### Some apis have been renamed
+
+Including the `HttpClientRequest` body apis, which is to make them more
+discoverable.
diff --git a/.changeset/thick-coats-buy.md b/.changeset/thick-coats-buy.md
new file mode 100644
index 00000000000..8cc5fdb2e0c
--- /dev/null
+++ b/.changeset/thick-coats-buy.md
@@ -0,0 +1,5 @@
+---
+"@effect/platform": patch
+---
+
+Added refinement overloads to `HttpClient.filterOrFail` and `HttpClient.filterOrElse`
diff --git a/.changeset/twelve-dingos-destroy.md b/.changeset/twelve-dingos-destroy.md
new file mode 100644
index 00000000000..e59d3d3a071
--- /dev/null
+++ b/.changeset/twelve-dingos-destroy.md
@@ -0,0 +1,6 @@
+---
+"@effect/opentelemetry": minor
+"effect": minor
+---
+
+Cache some fiber references in the runtime to optimize reading in hot-paths
diff --git a/.changeset/violet-suns-chew.md b/.changeset/violet-suns-chew.md
new file mode 100644
index 00000000000..6371200bb70
--- /dev/null
+++ b/.changeset/violet-suns-chew.md
@@ -0,0 +1,26 @@
+---
+"effect": minor
+---
+
+Added `RcMap.keys` and `MutableHashMap.keys`.
+
+These functions allow you to get a list of keys currently stored in the underlying hash map.
+
+```ts
+const map = MutableHashMap.make([["a", "a"], ["b", "b"], ["c", "c"]])
+const keys = MutableHashMap.keys(map) // ["a", "b", "c"]
+```
+
+```ts
+Effect.gen(function* () {
+ const map = yield* RcMap.make({
+ lookup: (key) => Effect.succeed(key)
+ })
+
+ yield* RcMap.get(map, "a")
+ yield* RcMap.get(map, "b")
+ yield* RcMap.get(map, "c")
+
+ const keys = yield* RcMap.keys(map) // ["a", "b", "c"]
+})
+```
diff --git a/.changeset/wise-kiwis-tan.md b/.changeset/wise-kiwis-tan.md
new file mode 100644
index 00000000000..e0dd75dca88
--- /dev/null
+++ b/.changeset/wise-kiwis-tan.md
@@ -0,0 +1,16 @@
+---
+"effect": minor
+---
+
+The `FiberRef` is now a subtype of `Effect`. This change simplifies handling of deferred values, removing the need for explicit call `FiberRef.get`.
+
+```typescript
+import { Effect, FiberRef } from "effect"
+
+Effect.gen(function* () {
+ const fiberRef = yield* FiberRef.make("value")
+
+ const before = yield* FiberRef.get(fiberRef)
+ const after = yield* fiberRef
+})
+```
diff --git a/packages/cli/src/internal/prompt/number.ts b/packages/cli/src/internal/prompt/number.ts
index fc7442eb7ff..f2d69334bfd 100644
--- a/packages/cli/src/internal/prompt/number.ts
+++ b/packages/cli/src/internal/prompt/number.ts
@@ -5,6 +5,7 @@ import * as Optimize from "@effect/printer/Optimize"
import * as Schema from "@effect/schema/Schema"
import * as Arr from "effect/Array"
import * as Effect from "effect/Effect"
+import * as EffectNumber from "effect/Number"
import * as Option from "effect/Option"
import type * as Prompt from "../../Prompt.js"
import * as InternalPrompt from "../prompt.js"
@@ -20,11 +21,6 @@ interface State {
readonly error: Option.Option
}
-const round = (number: number, precision: number) => {
- const factor = Math.pow(10, precision)
- return Math.round(number * factor) / factor
-}
-
const parseInt = Schema.NumberFromString.pipe(
Schema.int(),
Schema.decodeUnknown
@@ -352,7 +348,7 @@ function handleProcessFloat(options: FloatOptions) {
})),
onSuccess: (n) =>
Effect.flatMap(
- Effect.sync(() => round(n, options.precision)),
+ Effect.sync(() => EffectNumber.round(n, options.precision)),
(rounded) =>
Effect.match(options.validate(rounded), {
onFailure: (error) =>
diff --git a/packages/cluster-node/examples/sample-connect.ts b/packages/cluster-node/examples/sample-connect.ts
index d030c849540..3aad0b4a602 100644
--- a/packages/cluster-node/examples/sample-connect.ts
+++ b/packages/cluster-node/examples/sample-connect.ts
@@ -30,25 +30,33 @@ const liveLayer = Effect.gen(function*() {
Layer.effectDiscard,
Layer.provide(Sharding.live),
Layer.provide(StorageFile.storageFile),
- Layer.provide(PodsRpc.podsRpc((podAddress) =>
- HttpRpcResolver.make(
- HttpClient.fetchOk.pipe(
- HttpClient.mapRequest(
- HttpClientRequest.prependUrl(`http://${podAddress.host}:${podAddress.port}/api/rest`)
- )
- )
- ).pipe(RpcResolver.toClient)
- )),
- Layer.provide(ShardManagerClientRpc.shardManagerClientRpc(
- (shardManagerUri) =>
+ Layer.provide(Layer.unwrapEffect(Effect.gen(function*() {
+ const client = yield* HttpClient.HttpClient
+ return PodsRpc.podsRpc((podAddress) =>
HttpRpcResolver.make(
- HttpClient.fetchOk.pipe(
+ client.pipe(
+ HttpClient.filterStatusOk,
HttpClient.mapRequest(
- HttpClientRequest.prependUrl(shardManagerUri)
+ HttpClientRequest.prependUrl(`http://${podAddress.host}:${podAddress.port}/api/rest`)
)
)
).pipe(RpcResolver.toClient)
- )),
+ )
+ }))),
+ Layer.provide(Layer.unwrapEffect(Effect.gen(function*() {
+ const client = yield* HttpClient.HttpClient
+ return ShardManagerClientRpc.shardManagerClientRpc(
+ (shardManagerUri) =>
+ HttpRpcResolver.make(
+ client.pipe(
+ HttpClient.filterStatusOk,
+ HttpClient.mapRequest(
+ HttpClientRequest.prependUrl(shardManagerUri)
+ )
+ )
+ ).pipe(RpcResolver.toClient)
+ )
+ }))),
Layer.provide(ShardingConfig.withDefaults({ shardingPort: 54322 })),
Layer.provide(Serialization.json),
Layer.provide(NodeHttpClient.layerUndici)
diff --git a/packages/cluster-node/examples/sample-manager.ts b/packages/cluster-node/examples/sample-manager.ts
index 01321024b01..571da95ae60 100644
--- a/packages/cluster-node/examples/sample-manager.ts
+++ b/packages/cluster-node/examples/sample-manager.ts
@@ -5,7 +5,14 @@ import * as StorageFile from "@effect/cluster-node/StorageFile"
import * as ManagerConfig from "@effect/cluster/ManagerConfig"
import * as PodsHealth from "@effect/cluster/PodsHealth"
import * as ShardManager from "@effect/cluster/ShardManager"
-import { HttpClient, HttpClientRequest, HttpMiddleware, HttpRouter, HttpServer } from "@effect/platform"
+import {
+ FetchHttpClient,
+ HttpClient,
+ HttpClientRequest,
+ HttpMiddleware,
+ HttpRouter,
+ HttpServer
+} from "@effect/platform"
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
import { RpcResolver } from "@effect/rpc"
import { HttpRpcResolver, HttpRpcRouter } from "@effect/rpc-http"
@@ -34,17 +41,21 @@ const liveShardingManager = Effect.never.pipe(
Layer.provide(ShardManager.live),
Layer.provide(StorageFile.storageFile),
Layer.provide(PodsHealth.local),
- Layer.provide(PodsRpc.podsRpc((podAddress) =>
- HttpRpcResolver.make(
- HttpClient.fetchOk.pipe(
- HttpClient.mapRequest(
- HttpClientRequest.prependUrl(`http://${podAddress.host}:${podAddress.port}/api/rest`)
+ Layer.provide(Layer.unwrapEffect(Effect.gen(function*() {
+ const client = yield* HttpClient.HttpClient
+ return PodsRpc.podsRpc((podAddress) =>
+ HttpRpcResolver.make(
+ client.pipe(
+ HttpClient.filterStatusOk,
+ HttpClient.mapRequest(
+ HttpClientRequest.prependUrl(`http://${podAddress.host}:${podAddress.port}/api/rest`)
+ )
)
- )
- ).pipe(RpcResolver.toClient)
- )),
+ ).pipe(RpcResolver.toClient)
+ )
+ }))),
Layer.provide(ManagerConfig.fromConfig),
- Layer.provide(HttpClient.layer)
+ Layer.provide(FetchHttpClient.layer)
)
Layer.launch(liveShardingManager).pipe(
diff --git a/packages/cluster-node/examples/sample-shard.ts b/packages/cluster-node/examples/sample-shard.ts
index 85854704a44..3bbfc38da32 100644
--- a/packages/cluster-node/examples/sample-shard.ts
+++ b/packages/cluster-node/examples/sample-shard.ts
@@ -62,25 +62,33 @@ const liveLayer = Sharding.registerEntity(
Layer.provide(HttpLive),
Layer.provideMerge(Sharding.live),
Layer.provide(StorageFile.storageFile),
- Layer.provide(PodsRpc.podsRpc((podAddress) =>
- HttpRpcResolver.make(
- HttpClient.fetchOk.pipe(
- HttpClient.mapRequest(
- HttpClientRequest.prependUrl(`http://${podAddress.host}:${podAddress.port}/api/rest`)
- )
- )
- ).pipe(RpcResolver.toClient)
- )),
- Layer.provide(ShardManagerClientRpc.shardManagerClientRpc(
- (shardManagerUri) =>
+ Layer.provide(Layer.unwrapEffect(Effect.gen(function*() {
+ const client = yield* HttpClient.HttpClient
+ return PodsRpc.podsRpc((podAddress) =>
HttpRpcResolver.make(
- HttpClient.fetchOk.pipe(
+ client.pipe(
+ HttpClient.filterStatusOk,
HttpClient.mapRequest(
- HttpClientRequest.prependUrl(shardManagerUri)
+ HttpClientRequest.prependUrl(`http://${podAddress.host}:${podAddress.port}/api/rest`)
)
)
).pipe(RpcResolver.toClient)
- )),
+ )
+ }))),
+ Layer.provide(Layer.unwrapEffect(Effect.gen(function*() {
+ const client = yield* HttpClient.HttpClient
+ return ShardManagerClientRpc.shardManagerClientRpc(
+ (shardManagerUri) =>
+ HttpRpcResolver.make(
+ client.pipe(
+ HttpClient.filterStatusOk,
+ HttpClient.mapRequest(
+ HttpClientRequest.prependUrl(shardManagerUri)
+ )
+ )
+ ).pipe(RpcResolver.toClient)
+ )
+ }))),
Layer.provide(Serialization.json),
Layer.provide(NodeHttpClient.layerUndici),
Layer.provide(ShardingConfig.fromConfig)
diff --git a/packages/effect/dtslint/Unify.ts b/packages/effect/dtslint/Unify.ts
index 4aabe4784d7..835d43df229 100644
--- a/packages/effect/dtslint/Unify.ts
+++ b/packages/effect/dtslint/Unify.ts
@@ -1,18 +1,26 @@
+import type * as Deferred from "effect/Deferred"
import type * as Effect from "effect/Effect"
import * as Either from "effect/Either"
+import type * as Exit from "effect/Exit"
+import type * as Fiber from "effect/Fiber"
+import type * as FiberRef from "effect/FiberRef"
import type * as Micro from "effect/Micro"
import type * as Option from "effect/Option"
+import type * as RcRef from "effect/RcRef"
+import type * as Ref from "effect/Ref"
import type * as Stream from "effect/Stream"
+import type * as SubscriptionRef from "effect/SubscriptionRef"
+import type * as SynchronizedRef from "effect/SynchronizedRef"
import * as Unify from "effect/Unify"
// $ExpectType Option
-export type option = Unify.Unify | Option.Option>
+export type OptionUnify = Unify.Unify | Option.Option>
// $ExpectType Either<"RA" | "RB", "LA" | "LB">
-export type either = Unify.Unify | Either.Either<"RB", "LB">>
+export type EitherUnify = Unify.Unify | Either.Either<"RB", "LB">>
// $ExpectType 0 | Option | Either<"RA" | "RB", "LA" | "LB">
-export type both = Unify.Unify<
+export type EitherOptionUnify = Unify.Unify<
Either.Either<"RA", "LA"> | Either.Either<"RB", "LB"> | Option.Option | Option.Option | 0
>
@@ -26,15 +34,85 @@ Unify.unify((n: N) => Math.random() > 0 ? Either.right(n) : Either.left("ok")
Unify.unify(Math.random() > 0 ? Either.right(10) : Either.left("ok"))
// $ExpectType Stream<0 | "a", "b" | 1, "c" | 2>
-export type SU = Unify.Unify<
+export type StreamUnify = Unify.Unify<
Stream.Stream<0, 1, 2> | Stream.Stream<"a", "b", "c">
>
// $ExpectType Micro<0 | "a", "b" | 1, "c" | 2>
-export type MU = Unify.Unify<
+export type MicroUnify = Unify.Unify<
Micro.Micro<0, 1, 2> | Micro.Micro<"a", "b", "c">
>
// $ExpectType Effect<0 | "a", "b" | 1, "c" | 2>
-export type EU = Unify.Unify<
- Effect.Effect<0, 1, 2> | Effect.Effect<"a", "b", "c">
+export type EffectUnify = Unify.Unify<
+ | Effect.Effect<0, 1, 2>
+ | Effect.Effect<"a", "b", "c">
+>
+// $ExpectType Exit<0 | "a", "b" | 1>
+export type ExitUnify = Unify.Unify<
+ | Exit.Exit<0, 1>
+ | Exit.Exit<"a", "b">
+>
+// $ExpectType Ref<1> | Ref<"a">
+export type RefUnify = Unify.Unify | Ref.Ref<"a">>
+// $ExpectType SynchronizedRef<1> | SynchronizedRef<"a">
+export type SynchronizedRefUnify = Unify.Unify<
+ | SynchronizedRef.SynchronizedRef<1>
+ | SynchronizedRef.SynchronizedRef<"a">
+>
+// $ExpectType SubscriptionRef<1> | SubscriptionRef<"a">
+export type SubscriptionRefUnify = Unify.Unify<
+ | SubscriptionRef.SubscriptionRef<1>
+ | SubscriptionRef.SubscriptionRef<"a">
+>
+// $ExpectType RcRef<"a" | 1, "b" | 2>
+export type RcRefUnify = Unify.Unify<
+ | RcRef.RcRef<1, 2>
+ | RcRef.RcRef<"a", "b">
+>
+// $ExpectType Deferred<1, 2> | Deferred<"a", "b">
+export type DeferredUnify = Unify.Unify<
+ | Deferred.Deferred<1, 2>
+ | Deferred.Deferred<"a", "b">
+>
+// $ExpectType FiberRef<1> | FiberRef<"a">
+export type FiberRefUnify = Unify.Unify<
+ | FiberRef.FiberRef<1>
+ | FiberRef.FiberRef<"a">
+>
+// $ExpectType Fiber<"a" | 1, "b" | 2>
+export type FiberUnify = Unify.Unify<
+ | Fiber.Fiber<1, 2>
+ | Fiber.Fiber<"a", "b">
+>
+// $ExpectType RuntimeFiber<"a" | 1, "b" | 2>
+export type RuntimeFiberUnify = Unify.Unify<
+ | Fiber.RuntimeFiber<1, 2>
+ | Fiber.RuntimeFiber<"a", "b">
+>
+
+// $ExpectType 0 | Option | Ref<1> | SynchronizedRef<1> | SubscriptionRef<1> | Deferred<1, 2> | Deferred<"a", "b"> | Fiber<"a" | 1, "b" | 2> | RuntimeFiber<"a" | 1, "b" | 2> | Ref<"A"> | SynchronizedRef<"A"> | SubscriptionRef<"A"> | FiberRef<12> | FiberRef<"a2"> | Either<1 | "A", 0 | "E"> | Effect<1 | "A", 0 | "E", "R" | "R1"> | RcRef<1 | "A", 0 | "E">
+export type AllUnify = Unify.Unify<
+ | Either.Either<1, 0>
+ | Either.Either<"A", "E">
+ | Option.Option
+ | Option.Option
+ | Effect.Effect<"A", "E", "R">
+ | Effect.Effect<1, 0, "R1">
+ | Ref.Ref<1>
+ | Ref.Ref<"A">
+ | SynchronizedRef.SynchronizedRef<1>
+ | SynchronizedRef.SynchronizedRef<"A">
+ | SubscriptionRef.SubscriptionRef<1>
+ | SubscriptionRef.SubscriptionRef<"A">
+ | RcRef.RcRef<1, 0>
+ | RcRef.RcRef<"A", "E">
+ | Deferred.Deferred<1, 2>
+ | Deferred.Deferred<"a", "b">
+ | FiberRef.FiberRef<12>
+ | FiberRef.FiberRef<"a2">
+ | Fiber.Fiber<1, 2>
+ | Fiber.Fiber<"a", "b">
+ | Fiber.RuntimeFiber<1, 2>
+ | Fiber.RuntimeFiber<"a", "b">
+ | 0
>
diff --git a/packages/effect/src/Cause.ts b/packages/effect/src/Cause.ts
index 7c7ce6c9a29..18733035059 100644
--- a/packages/effect/src/Cause.ts
+++ b/packages/effect/src/Cause.ts
@@ -23,6 +23,7 @@
*/
import type * as Channel from "./Channel.js"
import type * as Chunk from "./Chunk.js"
+import type * as Context from "./Context.js"
import type * as Effect from "./Effect.js"
import type * as Either from "./Either.js"
import type * as Equal from "./Equal.js"
@@ -166,6 +167,7 @@ export type Cause =
| Interrupt
| Sequential
| Parallel
+ | Annotated
/**
* @since 2.0.0
@@ -190,12 +192,13 @@ export declare namespace Cause {
* @category models
*/
export interface CauseReducer {
- emptyCase(context: C): Z
- failCase(context: C, error: E): Z
- dieCase(context: C, defect: unknown): Z
- interruptCase(context: C, fiberId: FiberId.FiberId): Z
- sequentialCase(context: C, left: Z, right: Z): Z
- parallelCase(context: C, left: Z, right: Z): Z
+ emptyCase(context: C, annotations: Context.Context): Z
+ failCase(context: C, error: E, annotations: Context.Context): Z
+ dieCase(context: C, defect: unknown, annotations: Context.Context): Z
+ interruptCase(context: C, fiberId: FiberId.FiberId, annotations: Context.Context): Z
+ sequentialCase(context: C, left: Z, right: Z, annotations: Context.Context): Z
+ parallelCase(context: C, left: Z, right: Z, annotations: Context.Context): Z
+ annotatedCase(context: C, out: Z, annotations: Context.Context, parentAnnotations: Context.Context): Z
}
/**
@@ -397,6 +400,19 @@ export interface Sequential extends Cause.Variance, Equal.Equal, Pipea
readonly right: Cause
}
+/**
+ * The `Annotated` cause represents a `Cause` which has been annotated with
+ * additional context.
+ *
+ * @since 3.8.0
+ * @category models
+ */
+export interface Annotated extends Cause.Variance, Equal.Equal, Pipeable, Inspectable {
+ readonly _tag: "Annotated"
+ readonly cause: Cause
+ readonly context: Context.Context
+}
+
/**
* Constructs a new `Empty` cause.
*
@@ -429,6 +445,28 @@ export const die: (defect: unknown) => Cause = internal.die
*/
export const interrupt: (fiberId: FiberId.FiberId) => Cause = internal.interrupt
+/**
+ * Constructs a new `Annotated` cause from the specified `cause` and `context`.
+ *
+ * @since 3.8.0
+ * @category constructors
+ */
+export const annotated: {
+ (context: Context.Context): (self: Cause) => Cause
+ (self: Cause, context: Context.Context): Cause
+} = internal.annotated
+
+/**
+ * Constructs a `Annotated` cause from the specified `cause`, `tag` and `value`.
+ *
+ * @since 3.8.0
+ * @category constructors
+ */
+export const annotate: {
+ (tag: Context.Tag, value: S): (self: Cause) => Cause
+ (self: Cause, tag: Context.Tag, value: S): Cause
+} = internal.annotate
+
/**
* Constructs a new `Parallel` cause from the specified `left` and `right`
* causes.
@@ -509,6 +547,15 @@ export const isSequentialType: (self: Cause) => self is Sequential = in
*/
export const isParallelType: (self: Cause) => self is Parallel = internal.isParallelType
+/**
+ * Returns `true` if the specified `Cause` is a `Annotated` type, `false`
+ * otherwise.
+ *
+ * @since 3.8.0
+ * @category refinements
+ */
+export const isAnnotatedType: (self: Cause) => self is Annotated = internal.isAnnotatedType
+
/**
* Returns the size of the cause, calculated as the number of individual `Cause`
* nodes found in the `Cause` semiring structure.
@@ -586,6 +633,16 @@ export const defects: (self: Cause) => Chunk.Chunk = internal.def
*/
export const interruptors: (self: Cause) => HashSet.HashSet = internal.interruptors
+/**
+ * Return the merged `Context` of all `Annotated` causes in the specified cause.
+ *
+ * Outer annotations shadow inner annotations.
+ *
+ * @since 3.8.0
+ * @category getters
+ */
+export const annotations: (self: Cause) => Context.Context = internal.annotations
+
/**
* Returns the `E` associated with the first `Fail` in this `Cause`, if one
* exists.
@@ -787,22 +844,24 @@ export const match: {
(
options: {
readonly onEmpty: Z
- readonly onFail: (error: E) => Z
- readonly onDie: (defect: unknown) => Z
- readonly onInterrupt: (fiberId: FiberId.FiberId) => Z
- readonly onSequential: (left: Z, right: Z) => Z
- readonly onParallel: (left: Z, right: Z) => Z
+ readonly onFail: (error: E, annotations: Context.Context) => Z
+ readonly onDie: (defect: unknown, annotations: Context.Context) => Z
+ readonly onInterrupt: (fiberId: FiberId.FiberId, annotations: Context.Context) => Z
+ readonly onSequential: (left: Z, right: Z, annotations: Context.Context) => Z
+ readonly onParallel: (left: Z, right: Z, annotations: Context.Context) => Z
+ readonly onAnnotated: (out: Z, context: Context.Context, annotations: Context.Context) => Z
}
): (self: Cause) => Z
(
self: Cause,
options: {
readonly onEmpty: Z
- readonly onFail: (error: E) => Z
- readonly onDie: (defect: unknown) => Z
- readonly onInterrupt: (fiberId: FiberId.FiberId) => Z
- readonly onSequential: (left: Z, right: Z) => Z
- readonly onParallel: (left: Z, right: Z) => Z
+ readonly onFail: (error: E, annotations: Context.Context) => Z
+ readonly onDie: (defect: unknown, annotations: Context.Context) => Z
+ readonly onInterrupt: (fiberId: FiberId.FiberId, annotations: Context.Context) => Z
+ readonly onSequential: (left: Z, right: Z, annotations: Context.Context) => Z
+ readonly onParallel: (left: Z, right: Z, annotations: Context.Context) => Z
+ readonly onAnnotated: (out: Z, context: Context.Context, annotations: Context.Context) => Z
}
): Z
} = internal.match
@@ -983,4 +1042,16 @@ export const prettyErrors: (cause: Cause) => Array = internal
* @since 2.0.0
* @category errors
*/
-export const originalError: (obj: E) => E = core.originalInstance
+export const originalError: (obj: E) => E = internal.originalInstance
+
+/**
+ * @since 3.8.0
+ * @category annotations
+ */
+export const FailureSpan: Context.Tag<"FailureSpan", Span> = internal.FailureSpan
+
+/**
+ * @since 3.8.0
+ * @category annotations
+ */
+export const InterruptorSpan: Context.Tag<"InterruptorSpan", Span> = internal.InterruptorSpan
diff --git a/packages/effect/src/Context.ts b/packages/effect/src/Context.ts
index 9cf2440bdd3..5258af70d4d 100644
--- a/packages/effect/src/Context.ts
+++ b/packages/effect/src/Context.ts
@@ -393,6 +393,19 @@ export const omit: >>(
) => (self: Context) => Context }[keyof S]>> =
internal.omit
+/**
+ * @since 3.8.0
+ */
+export const has: {
+ (tag: Tag): (self: Context) => self is Context
+ (self: Context, tag: Tag): self is Context
+} = internal.has
+
+/**
+ * @since 3.8.0
+ */
+export const isEmpty: (self: Context) => boolean = internal.isEmpty
+
/**
* @since 2.0.0
* @category constructors
diff --git a/packages/effect/src/Deferred.ts b/packages/effect/src/Deferred.ts
index ea32f092181..10a8cc13d9f 100644
--- a/packages/effect/src/Deferred.ts
+++ b/packages/effect/src/Deferred.ts
@@ -10,8 +10,8 @@ import * as core from "./internal/core.js"
import * as internal from "./internal/deferred.js"
import type * as MutableRef from "./MutableRef.js"
import type * as Option from "./Option.js"
-import type { Pipeable } from "./Pipeable.js"
import type * as Types from "./Types.js"
+import type * as Unify from "./Unify.js"
/**
* @since 2.0.0
@@ -37,11 +37,30 @@ export type DeferredTypeId = typeof DeferredTypeId
* @since 2.0.0
* @category models
*/
-export interface Deferred extends Deferred.Variance, Pipeable {
+export interface Deferred extends Effect.Effect, Deferred.Variance {
/** @internal */
readonly state: MutableRef.MutableRef>
/** @internal */
readonly blockingOn: FiberId.FiberId
+ readonly [Unify.typeSymbol]?: unknown
+ readonly [Unify.unifySymbol]?: DeferredUnify
+ readonly [Unify.ignoreSymbol]?: DeferredUnifyIgnore
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface DeferredUnify extends Effect.EffectUnify {
+ Deferred?: () => Extract>
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface DeferredUnifyIgnore extends Effect.EffectUnifyIgnore {
+ Effect?: true
}
/**
diff --git a/packages/effect/src/Effect.ts b/packages/effect/src/Effect.ts
index e667425550a..090039deb2c 100644
--- a/packages/effect/src/Effect.ts
+++ b/packages/effect/src/Effect.ts
@@ -5374,6 +5374,8 @@ export interface Permit {
export interface Semaphore {
/** when the given amount of permits are available, run the effect and release the permits when finished */
withPermits(permits: number): (self: Effect) => Effect
+ /** only if the given permits are available, run the effect and release the permits when finished */
+ withPermitsIfAvailable(permits: number): (self: Effect) => Effect, E, R>
/** take the given amount of permits, suspending if they are not yet available */
take(permits: number): Effect
/** release the given amount of permits, and return the resulting available permits */
@@ -5398,6 +5400,56 @@ export const unsafeMakeSemaphore: (permits: number) => Semaphore = circular.unsa
*/
export const makeSemaphore: (permits: number) => Effect = circular.makeSemaphore
+// -------------------------------------------------------------------------------------
+// latch
+// -------------------------------------------------------------------------------------
+
+/**
+ * @category latch
+ * @since 3.8.0
+ */
+export interface Latch {
+ /** open the latch, releasing all fibers waiting on it */
+ readonly open: Effect
+ /** release all fibers waiting on the latch, without opening it */
+ readonly release: Effect
+ /** wait for the latch to be opened */
+ readonly await: Effect
+ /** close the latch */
+ readonly close: Effect
+ /** only run the given effect when the latch is open */
+ readonly whenOpen: (self: Effect) => Effect
+}
+
+/**
+ * @category latch
+ * @since 3.8.0
+ */
+export const unsafeMakeLatch: (open?: boolean | undefined) => Latch = circular.unsafeMakeLatch
+
+/**
+ * @category latch
+ * @since 3.8.0
+ * @example
+ * import { Effect } from "effect"
+ *
+ * Effect.gen(function*() {
+ * // Create a latch, starting in the closed state
+ * const latch = yield* Effect.makeLatch(false)
+ *
+ * // Fork a fiber that logs "open sesame" when the latch is opened
+ * const fiber = yield* Effect.log("open sesame").pipe(
+ * latch.whenOpen,
+ * Effect.fork
+ * )
+ *
+ * // Open the latch
+ * yield* latch.open
+ * yield* fiber.await
+ * })
+ */
+export const makeLatch: (open?: boolean | undefined) => Effect = circular.makeLatch
+
// -------------------------------------------------------------------------------------
// execution
// -------------------------------------------------------------------------------------
diff --git a/packages/effect/src/Fiber.ts b/packages/effect/src/Fiber.ts
index 15f8623a3f9..3c3e76065b2 100644
--- a/packages/effect/src/Fiber.ts
+++ b/packages/effect/src/Fiber.ts
@@ -2,6 +2,8 @@
* @since 2.0.0
*/
import type * as Cause from "./Cause.js"
+import type { Context } from "./Context.js"
+import type { DefaultServices } from "./DefaultServices.js"
import type * as Effect from "./Effect.js"
import type * as Either from "./Either.js"
import type * as Exit from "./Exit.js"
@@ -16,10 +18,13 @@ import * as internal from "./internal/fiber.js"
import * as fiberRuntime from "./internal/fiberRuntime.js"
import type * as Option from "./Option.js"
import type * as order from "./Order.js"
-import type { Pipeable } from "./Pipeable.js"
import type * as RuntimeFlags from "./RuntimeFlags.js"
+import type { Scheduler } from "./Scheduler.js"
import type * as Scope from "./Scope.js"
+import type { Supervisor } from "./Supervisor.js"
+import type { AnySpan, Tracer } from "./Tracer.js"
import type * as Types from "./Types.js"
+import type * as Unify from "./Unify.js"
/**
* @since 2.0.0
@@ -57,7 +62,7 @@ export type RuntimeFiberTypeId = typeof RuntimeFiberTypeId
* @since 2.0.0
* @category models
*/
-export interface Fiber extends Fiber.Variance, Pipeable {
+export interface Fiber extends Effect.Effect, Fiber.Variance {
/**
* The identity of the fiber.
*/
@@ -92,6 +97,26 @@ export interface Fiber extends Fiber.Variance, Pipea
* resume immediately. Otherwise, the effect will resume when the fiber exits.
*/
interruptAsFork(fiberId: FiberId.FiberId): Effect.Effect
+
+ readonly [Unify.typeSymbol]?: unknown
+ readonly [Unify.unifySymbol]?: FiberUnify
+ readonly [Unify.ignoreSymbol]?: FiberUnifyIgnore
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface FiberUnify extends Effect.EffectUnify {
+ Fiber?: () => A[Unify.typeSymbol] extends Fiber | infer _ ? Fiber : never
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface FiberUnifyIgnore extends Effect.EffectUnifyIgnore {
+ Effect?: true
}
/**
@@ -155,6 +180,57 @@ export interface RuntimeFiber extends Fiber, Fiber.R
* resume immediately. Otherwise, the effect will resume when the fiber exits.
*/
unsafeInterruptAsFork(fiberId: FiberId.FiberId): void
+
+ /**
+ * Gets the current context
+ */
+ get currentContext(): Context
+
+ /**
+ * Gets the current context
+ */
+ get currentDefaultServices(): Context
+
+ /**
+ * Gets the current scheduler
+ */
+ get currentScheduler(): Scheduler
+
+ /**
+ * Gets the current tracer
+ */
+ get currentTracer(): Tracer
+
+ /**
+ * Gets the current span
+ */
+ get currentSpan(): AnySpan | undefined
+
+ /**
+ * Gets the current supervisor
+ */
+ get currentSupervisor(): Supervisor
+
+ readonly [Unify.typeSymbol]?: unknown
+ readonly [Unify.unifySymbol]?: RuntimeFiberUnify
+ readonly [Unify.ignoreSymbol]?: RuntimeFiberUnifyIgnore
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface RuntimeFiberUnify extends FiberUnify {
+ RuntimeFiber?: () => A[Unify.typeSymbol] extends RuntimeFiber | infer _ ? RuntimeFiber
+ : never
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface RuntimeFiberUnifyIgnore extends FiberUnifyIgnore {
+ Fiber?: true
}
/**
diff --git a/packages/effect/src/FiberHandle.ts b/packages/effect/src/FiberHandle.ts
index 4392172a82c..6944ae520ba 100644
--- a/packages/effect/src/FiberHandle.ts
+++ b/packages/effect/src/FiberHandle.ts
@@ -151,7 +151,8 @@ const isInternalInterruption = Cause.reduceWithContext(undefined, {
dieCase: constFalse,
interruptCase: (_, fiberId) => HashSet.has(FiberId.ids(fiberId), internalFiberIdId),
sequentialCase: (_, left, right) => left || right,
- parallelCase: (_, left, right) => left || right
+ parallelCase: (_, left, right) => left || right,
+ annotatedCase: (_, out) => out
})
/**
diff --git a/packages/effect/src/FiberMap.ts b/packages/effect/src/FiberMap.ts
index 57c0fff3732..4d473d43ca9 100644
--- a/packages/effect/src/FiberMap.ts
+++ b/packages/effect/src/FiberMap.ts
@@ -166,7 +166,8 @@ const isInternalInterruption = Cause.reduceWithContext(undefined, {
dieCase: constFalse,
interruptCase: (_, fiberId) => HashSet.has(FiberId.ids(fiberId), internalFiberIdId),
sequentialCase: (_, left, right) => left || right,
- parallelCase: (_, left, right) => left || right
+ parallelCase: (_, left, right) => left || right,
+ annotatedCase: (_, out) => out
})
/**
diff --git a/packages/effect/src/FiberRef.ts b/packages/effect/src/FiberRef.ts
index 463858adbcd..ae95e146b00 100644
--- a/packages/effect/src/FiberRef.ts
+++ b/packages/effect/src/FiberRef.ts
@@ -18,7 +18,6 @@ import type * as LogLevel from "./LogLevel.js"
import type * as LogSpan from "./LogSpan.js"
import type * as MetricLabel from "./MetricLabel.js"
import type * as Option from "./Option.js"
-import type { Pipeable } from "./Pipeable.js"
import type * as Request from "./Request.js"
import type * as RuntimeFlags from "./RuntimeFlags.js"
import * as Scheduler from "./Scheduler.js"
@@ -26,6 +25,7 @@ import type * as Scope from "./Scope.js"
import type * as Supervisor from "./Supervisor.js"
import type * as Tracer from "./Tracer.js"
import type * as Types from "./Types.js"
+import type * as Unify from "./Unify.js"
/**
* @since 2.0.0
@@ -43,7 +43,7 @@ export type FiberRefTypeId = typeof FiberRefTypeId
* @since 2.0.0
* @category model
*/
-export interface FiberRef extends Variance, Pipeable {
+export interface FiberRef extends Effect.Effect, Variance {
/** @internal */
readonly initial: A
/** @internal */
@@ -56,6 +56,25 @@ export interface FiberRef extends Variance, Pipeable {
readonly fork: unknown
/** @internal */
join(oldValue: A, newValue: A): A
+ readonly [Unify.typeSymbol]?: unknown
+ readonly [Unify.unifySymbol]?: FiberRefUnify
+ readonly [Unify.ignoreSymbol]?: FiberRefUnifyIgnore
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface FiberRefUnify extends Effect.EffectUnify {
+ FiberRef?: () => Extract>
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface FiberRefUnifyIgnore extends Effect.EffectUnifyIgnore {
+ Effect?: true
}
/**
diff --git a/packages/effect/src/FiberSet.ts b/packages/effect/src/FiberSet.ts
index 53e0a50a8cc..afb8562f711 100644
--- a/packages/effect/src/FiberSet.ts
+++ b/packages/effect/src/FiberSet.ts
@@ -155,7 +155,8 @@ const isInternalInterruption = Cause.reduceWithContext(undefined, {
dieCase: constFalse,
interruptCase: (_, fiberId) => HashSet.has(FiberId.ids(fiberId), internalFiberIdId),
sequentialCase: (_, left, right) => left || right,
- parallelCase: (_, left, right) => left || right
+ parallelCase: (_, left, right) => left || right,
+ annotatedCase: (_, out) => out
})
/**
diff --git a/packages/effect/src/Logger.ts b/packages/effect/src/Logger.ts
index 9bc058c128f..60a06251853 100644
--- a/packages/effect/src/Logger.ts
+++ b/packages/effect/src/Logger.ts
@@ -238,6 +238,28 @@ export const batched: {
*/
export const withConsoleLog: (self: Logger) => Logger = fiberRuntime.loggerWithConsoleLog
+/**
+ * Takes a `Logger` and returns a logger that calls the respective `Console` method
+ * based on the log level.
+ *
+ * @example
+ * import { Logger, Effect } from "effect"
+ *
+ * const loggerLayer = Logger.replace(
+ * Logger.defaultLogger,
+ * Logger.withLeveledConsole(Logger.stringLogger),
+ * )
+ *
+ * Effect.gen(function* () {
+ * yield* Effect.logError("an error")
+ * yield* Effect.logInfo("an info")
+ * }).pipe(Effect.provide(loggerLayer))
+ *
+ * @since 3.8.0
+ * @category console
+ */
+export const withLeveledConsole: (self: Logger) => Logger = fiberRuntime.loggerWithLeveledLog
+
/**
* @since 2.0.0
* @category console
@@ -475,6 +497,14 @@ export const prettyLogger: (
}
) => Logger = internal.prettyLogger
+/**
+ * A default version of the pretty logger.
+ *
+ * @since 3.8.0
+ * @category constructors
+ */
+export const prettyLoggerDefault: Logger = internal.prettyLoggerDefault
+
/**
* The structured logger provides detailed log outputs, structured in a way that
* retains comprehensive traceability of the events, suitable for deeper
diff --git a/packages/effect/src/MutableHashMap.ts b/packages/effect/src/MutableHashMap.ts
index 16071b33d52..1b5e0cabbba 100644
--- a/packages/effect/src/MutableHashMap.ts
+++ b/packages/effect/src/MutableHashMap.ts
@@ -159,6 +159,18 @@ export const get: {
return getFromBucket(self, bucket, key)
})
+/**
+ * @since 3.8.0
+ * @category elements
+ */
+export const keys = (self: MutableHashMap): Array => {
+ const keys: Array = []
+ for (const [key] of self) {
+ keys.push(key)
+ }
+ return keys
+}
+
const getFromBucket = (
self: MutableHashMap,
bucket: NonEmptyArray,
diff --git a/packages/effect/src/Number.ts b/packages/effect/src/Number.ts
index bd3181545b3..ad5ea1a8e66 100644
--- a/packages/effect/src/Number.ts
+++ b/packages/effect/src/Number.ts
@@ -492,3 +492,26 @@ export const parse = (s: string): Option => {
? option.none
: option.some(n)
}
+
+/**
+ * Returns the number rounded with the given precision.
+ *
+ * @param self - The number to round
+ * @param precision - The precision
+ *
+ * @example
+ * import { round } from "effect/Number"
+ *
+ * assert.deepStrictEqual(round(1.1234, 2), 1.12)
+ * assert.deepStrictEqual(round(1.567, 2), 1.57)
+ *
+ * @category math
+ * @since 3.8.0
+ */
+export const round: {
+ (precision: number): (self: number) => number
+ (self: number, precision: number): number
+} = dual(2, (self: number, precision: number): number => {
+ const factor = Math.pow(10, precision)
+ return Math.round(self * factor) / factor
+})
diff --git a/packages/effect/src/RcMap.ts b/packages/effect/src/RcMap.ts
index 54f0170da78..ed711001399 100644
--- a/packages/effect/src/RcMap.ts
+++ b/packages/effect/src/RcMap.ts
@@ -101,3 +101,9 @@ export const get: {
(key: K): (self: RcMap) => Effect.Effect
(self: RcMap, key: K): Effect.Effect
} = internal.get
+
+/**
+ * @since 3.8.0
+ * @category combinators
+ */
+export const keys: (self: RcMap) => Effect.Effect, E> = internal.keys
diff --git a/packages/effect/src/RcRef.ts b/packages/effect/src/RcRef.ts
index bbdf780a5eb..e014272d9fe 100644
--- a/packages/effect/src/RcRef.ts
+++ b/packages/effect/src/RcRef.ts
@@ -4,9 +4,10 @@
import type * as Duration from "./Duration.js"
import type * as Effect from "./Effect.js"
import * as internal from "./internal/rcRef.js"
-import { type Pipeable } from "./Pipeable.js"
+import type * as Readable from "./Readable.js"
import type * as Scope from "./Scope.js"
import type * as Types from "./Types.js"
+import type * as Unify from "./Unify.js"
/**
* @since 3.5.0
@@ -24,10 +25,31 @@ export type TypeId = typeof TypeId
* @since 3.5.0
* @category models
*/
-export interface RcRef extends Pipeable {
+export interface RcRef
+ extends Effect.Effect, Readable.Readable
+{
readonly [TypeId]: RcRef.Variance
+ readonly [Unify.typeSymbol]?: unknown
+ readonly [Unify.unifySymbol]?: RcRefUnify
+ readonly [Unify.ignoreSymbol]?: RcRefUnifyIgnore
}
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface RcRefUnify extends Effect.EffectUnify {
+ RcRef?: () => A[Unify.typeSymbol] extends RcRef | infer _ ? RcRef
+ : never
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface RcRefUnifyIgnore extends Effect.EffectUnifyIgnore {
+ Effect?: true
+}
/**
* @since 3.5.0
* @category models
diff --git a/packages/effect/src/Ref.ts b/packages/effect/src/Ref.ts
index 09be40ce7ba..0f63baef06c 100644
--- a/packages/effect/src/Ref.ts
+++ b/packages/effect/src/Ref.ts
@@ -4,8 +4,9 @@
import type * as Effect from "./Effect.js"
import * as internal from "./internal/ref.js"
import type * as Option from "./Option.js"
-import type { Readable } from "./Readable.js"
+import type * as Readable from "./Readable.js"
import type * as Types from "./Types.js"
+import type * as Unify from "./Unify.js"
/**
* @since 2.0.0
@@ -23,8 +24,27 @@ export type RefTypeId = typeof RefTypeId
* @since 2.0.0
* @category models
*/
-export interface Ref extends Ref.Variance, Readable {
+export interface Ref extends Ref.Variance, Effect.Effect, Readable.Readable {
modify(f: (a: A) => readonly [B, A]): Effect.Effect
+ readonly [Unify.typeSymbol]?: unknown
+ readonly [Unify.unifySymbol]?: RefUnify
+ readonly [Unify.ignoreSymbol]?: RefUnifyIgnore
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface RefUnify extends Effect.EffectUnify {
+ Ref?: () => Extract>
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface RefUnifyIgnore extends Effect.EffectUnifyIgnore {
+ Effect?: true
}
/**
diff --git a/packages/effect/src/Stream.ts b/packages/effect/src/Stream.ts
index 532668cab95..adbc0c9242c 100644
--- a/packages/effect/src/Stream.ts
+++ b/packages/effect/src/Stream.ts
@@ -549,6 +549,42 @@ export const broadcast: {
): Effect.Effect>, never, Scope.Scope | R>
} = internal.broadcast
+/**
+ * Returns a new Stream that multicasts the original Stream, subscribing to it as soon as the first consumer subscribes.
+ * As long as there is at least one consumer, the upstream will continue running and emitting data.
+ * When all consumers have exited, the upstream will be finalized.
+ *
+ * @since 3.8.0
+ * @category utils
+ */
+export const share: {
+ (
+ config: {
+ readonly capacity: "unbounded"
+ readonly replay?: number | undefined
+ readonly idleTimeToLive?: Duration.DurationInput | undefined
+ } | {
+ readonly capacity: number
+ readonly strategy?: "sliding" | "dropping" | "suspend" | undefined
+ readonly replay?: number | undefined
+ readonly idleTimeToLive?: Duration.DurationInput | undefined
+ }
+ ): (self: Stream) => Effect.Effect, never, R | Scope.Scope>
+ (
+ self: Stream,
+ config: {
+ readonly capacity: "unbounded"
+ readonly replay?: number | undefined
+ readonly idleTimeToLive?: Duration.DurationInput | undefined
+ } | {
+ readonly capacity: number
+ readonly strategy?: "sliding" | "dropping" | "suspend" | undefined
+ readonly replay?: number | undefined
+ readonly idleTimeToLive?: Duration.DurationInput | undefined
+ }
+ ): Effect.Effect, never, R | Scope.Scope>
+} = internal.share
+
/**
* Fan out the stream, producing a dynamic number of streams that have the
* same elements as this stream. The driver stream will only ever advance the
diff --git a/packages/effect/src/SubscriptionRef.ts b/packages/effect/src/SubscriptionRef.ts
index 36850a864ec..aac1045a5b3 100644
--- a/packages/effect/src/SubscriptionRef.ts
+++ b/packages/effect/src/SubscriptionRef.ts
@@ -10,6 +10,7 @@ import type * as Stream from "./Stream.js"
import type { Subscribable } from "./Subscribable.js"
import * as Synchronized from "./SynchronizedRef.js"
import type * as Types from "./Types.js"
+import type * as Unify from "./Unify.js"
/**
* @since 2.0.0
@@ -44,6 +45,27 @@ export interface SubscriptionRef
* to that value.
*/
readonly changes: Stream.Stream
+ readonly [Unify.typeSymbol]?: unknown
+ readonly [Unify.unifySymbol]?: SubscriptionRefUnify
+ readonly [Unify.ignoreSymbol]?: SubscriptionRefUnifyIgnore
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface SubscriptionRefUnify
+ extends Synchronized.SynchronizedRefUnify
+{
+ SubscriptionRef?: () => Extract>
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface SubscriptionRefUnifyIgnore extends Synchronized.SynchronizedRefUnifyIgnore {
+ SynchronizedRef?: true
}
/**
diff --git a/packages/effect/src/SynchronizedRef.ts b/packages/effect/src/SynchronizedRef.ts
index a125fb8202f..cf763ec0ba7 100644
--- a/packages/effect/src/SynchronizedRef.ts
+++ b/packages/effect/src/SynchronizedRef.ts
@@ -8,6 +8,7 @@ import * as internal from "./internal/synchronizedRef.js"
import type * as Option from "./Option.js"
import type * as Ref from "./Ref.js"
import type * as Types from "./Types.js"
+import type * as Unify from "./Unify.js"
/**
* @since 2.0.0
@@ -27,6 +28,25 @@ export type SynchronizedRefTypeId = typeof SynchronizedRefTypeId
*/
export interface SynchronizedRef extends SynchronizedRef.Variance, Ref.Ref {
modifyEffect(f: (a: A) => Effect.Effect): Effect.Effect
+ readonly [Unify.typeSymbol]?: unknown
+ readonly [Unify.unifySymbol]?: SynchronizedRefUnify
+ readonly [Unify.ignoreSymbol]?: SynchronizedRefUnifyIgnore
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface SynchronizedRefUnify extends Ref.RefUnify {
+ SynchronizedRef?: () => Extract>
+}
+
+/**
+ * @category models
+ * @since 3.8.0
+ */
+export interface SynchronizedRefUnifyIgnore extends Ref.RefUnifyIgnore {
+ Ref?: true
}
/**
diff --git a/packages/effect/src/internal/cause.ts b/packages/effect/src/internal/cause.ts
index bb705011d40..317fb97c17d 100644
--- a/packages/effect/src/internal/cause.ts
+++ b/packages/effect/src/internal/cause.ts
@@ -1,6 +1,7 @@
import * as Arr from "../Array.js"
import type * as Cause from "../Cause.js"
import * as Chunk from "../Chunk.js"
+import * as Context from "../Context.js"
import * as Either from "../Either.js"
import * as Equal from "../Equal.js"
import type * as FiberId from "../FiberId.js"
@@ -16,6 +17,7 @@ import { hasProperty, isFunction } from "../Predicate.js"
import type { AnySpan, Span } from "../Tracer.js"
import type { NoInfer } from "../Types.js"
import { getBugErrorMessage } from "./errors.js"
+import * as internalFiberId from "./fiberId.js"
import * as OpCodes from "./opCodes/cause.js"
// -----------------------------------------------------------------------------
@@ -64,6 +66,8 @@ const proto = {
case "Sequential":
case "Parallel":
return { _id: "Cause", _tag: this._tag, left: toJSON(this.left), right: toJSON(this.right) }
+ case "Annotated":
+ return { _id: "Cause", _tag: this._tag, cause: this.cause.toJSON(), context: this.context.toJSON() }
}
},
toString(this: Cause.Cause) {
@@ -90,7 +94,7 @@ export const fail = (error: E): Cause.Cause => {
const o = Object.create(proto)
o._tag = OpCodes.OP_FAIL
o.error = error
- return o
+ return rehydrateAnnotations(o, error)
}
/** @internal */
@@ -98,7 +102,7 @@ export const die = (defect: unknown): Cause.Cause => {
const o = Object.create(proto)
o._tag = OpCodes.OP_DIE
o.defect = defect
- return o
+ return rehydrateAnnotations(o, defect)
}
/** @internal */
@@ -127,6 +131,33 @@ export const sequential = (left: Cause.Cause, right: Cause.Cause):
return o
}
+/** @internal */
+export const annotated = dual<
+ (context: Context.Context) => (self: Cause.Cause) => Cause.Cause,
+ (self: Cause.Cause, context: Context.Context) => Cause.Cause
+>(2, (self, context) => {
+ if (self._tag === OpCodes.OP_ANNOTATED && self.context === context) {
+ return self
+ }
+ const o = Object.create(proto)
+ o._tag = OpCodes.OP_ANNOTATED
+ if (self._tag === OpCodes.OP_ANNOTATED) {
+ o.context = Context.merge(context, self.context)
+ o.cause = self.cause
+ } else {
+ o.context = context
+ o.cause = self
+ }
+ propagateAnnotations(o.cause, o.context)
+ return o
+})
+
+/** @internal */
+export const annotate = dual<
+ (tag: Context.Tag, value: S) => (self: Cause.Cause) => Cause.Cause,
+ (self: Cause.Cause, tag: Context.Tag, value: S) => Cause.Cause