/** * Describes effectful sources that emit values over time. * * A `Stream` can emit many `A` values, fail with `E`, and require * services `R` while it is being consumed. Streams are useful for data that is * pulled in steps, such as values from collections, queues, pubsubs, schedules, * callbacks, async iterables, or platform streams. The APIs here cover the full * stream lifecycle: create a stream, transform or combine it, control buffering * and timing, handle failures, and finally consume it. * * @stability stable * @since 2.0.0 */ // @effect-diagnostics returnEffectInGen:off import * as Arr from "./Array.ts" import * as ByteSize from "./ByteSize.ts" import * as Cause from "./Cause.ts" import * as Channel from "./Channel.ts" import { Clock } from "./Clock.ts" import * as Context from "./Context.ts" import * as Duration from "./Duration.ts" import * as Effect from "./Effect.ts" import * as Equal from "./Equal.ts" import * as ExecutionPlan from "./ExecutionPlan.ts" import * as Exit from "./Exit.ts" import * as Fiber from "./Fiber.ts" import type * as Filter from "./Filter.ts" import type { LazyArg } from "./Function.ts" import { constant, constTrue, constVoid, dual, identity } from "./Function.ts" import type { TypeLambda } from "./HKT.ts" import * as Count from "./internal/count.ts" import * as internalExecutionPlan from "./internal/executionPlan.ts" import * as internal from "./internal/stream.ts" import { addSpanStackTrace } from "./internal/tracer.ts" import * as Iterable from "./Iterable.ts" import * as Latch from "./Latch.ts" import type * as Layer from "./Layer.ts" import type { Severity } from "./LogLevel.ts" import * as MutableHashMap from "./MutableHashMap.ts" import * as MutableList from "./MutableList.ts" import * as Option from "./Option.ts" import type { Pipeable } from "./Pipeable.ts" import type { Predicate, Refinement } from "./Predicate.ts" import { hasProperty, isNotUndefined, isTagged } from "./Predicate.ts" import * as PubSub from "./PubSub.ts" import * as Pull from "./Pull.ts" import * as Queue from "./Queue.ts" import * as RcMap from "./RcMap.ts" import * as RcRef from "./RcRef.ts" import * as Result from "./Result.ts" import * as Schedule from "./Schedule.ts" import * as Scope from "./Scope.ts" import * as Sink from "./Sink.ts" import { isString } from "./String.ts" import type * as Take from "./Take.ts" import type { ParentSpan, SpanOptions } from "./Tracer.ts" import type { Concurrency, Covariant, ExcludeReason, ExcludeTag, ExtractReason, ExtractTag, NarrowReason, NoInfer, OmitReason, ReasonOf, ReasonTags, Simplify, Tags, TupleOf, unassigned } from "./Types.ts" import type * as Unify from "./Unify.ts" /** * String literal type used as the unique brand for `Stream` values. * * @stability stable * @category type IDs * @since 4.0.0 */ export type TypeId = "~effect/Stream" /** * Runtime identifier stored on `Stream` values and used by `isStream` to * recognize them. * * **Details** * * This marker is part of the runtime representation of `Stream` values. Prefer * `isStream` when narrowing unknown values. * * @see {@link isStream} for the public guard that checks this identifier * * @stability stable * @category type IDs * @since 4.0.0 */ export const TypeId: TypeId = "~effect/Stream" /** * A `Stream` describes a program that can emit many `A` values, fail * with `E`, and require `R`. * * **Details** * * Streams are pull-based with backpressure and emit chunks to amortize effect * evaluation. They support monadic composition and error handling similar to * `Effect`, adapted for multiple values. * * **Example** (Creating and consuming streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const values = await Effect.runPromise( * Stream.make(1, 2, 3).pipe( * Stream.map((n) => n * 2), * Stream.runCollect * ) * ) * values // => [2, 4, 6] * ``` * * @stability stable * @category models * @since 2.0.0 */ export interface Stream extends Variance, Pipeable { readonly channel: Channel.Channel, E, void, unknown, unknown, unknown, R> [Unify.typeSymbol]?: unknown [Unify.unifySymbol]?: StreamUnify [Unify.ignoreSymbol]?: StreamUnifyIgnore } /** * Type-level unification hook for Stream within the Effect type system. * * @stability stable * @category models * @since 2.0.0 */ export interface StreamUnify extends Effect.EffectUnify { Stream?: () => A[Unify.typeSymbol] extends Stream | infer _ ? Stream : never } /** * Type-level marker that excludes Stream from unification. * * @stability stable * @category models * @since 2.0.0 */ export interface StreamUnifyIgnore { Effect?: true } /** * Type lambda for Stream used in higher-kinded type operations. * * **Example** (Using the stream type lambda) * * ```ts import.meta.vitest * import { Effect, HKT, Stream } from "effect" * * // Create a Stream type using the type lambda * type NumberStream = HKT.Kind * // Equivalent to: Stream * const stream: NumberStream = Stream.make(1, 2, 3) * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] * ``` * * @stability stable * @category utility types * @since 2.0.0 */ export interface StreamTypeLambda extends TypeLambda { readonly type: Stream } /** * Type-level variance marker for `Stream`. * * **Details** * * The emitted value `A`, error `E`, and service requirement `R` type * parameters are covariant. * * @stability stable * @category models * @since 2.0.0 */ export interface Variance { readonly [TypeId]: VarianceStruct } /** * Structural encoding used by `Variance` to record each `Stream` type * parameter's variance. * * **Details** * * `_A`, `_E`, and `_R` are covariant markers. * * @stability stable * @category models * @since 3.4.0 */ export interface VarianceStruct { readonly _A: Covariant readonly _E: Covariant readonly _R: Covariant } /** * Extract the success type from a Stream type. * * **Example** (Extracting the success type from a Stream type) * * ```ts import.meta.vitest * import { Stream } from "effect" * * type NumberStream = Stream.Stream * type SuccessType = Stream.Success * const value: SuccessType = 42 * ``` * * @stability stable * @category utility types * @since 3.4.0 */ export type Success = T extends Stream ? _A : never /** * Extract the error type from a Stream type. * * **Example** (Extracting the error type from a Stream type) * * ```ts import.meta.vitest * import { Stream } from "effect" * * type NumberStream = Stream.Stream * type ErrorType = Stream.Error * const error: ErrorType = "boom" * ``` * * @stability stable * @category utility types * @since 3.4.0 */ export type Error = T extends Stream ? _E : never /** * Extract the services type from a Stream type. * * **Example** (Extracting the services type from a Stream type) * * ```ts import.meta.vitest * import { Stream } from "effect" * * interface Database { * query: (sql: string) => unknown * } * type NumberStream = Stream.Stream * type RequiredServices = Stream.Services * const services: RequiredServices = { db: { query: (sql) => sql } } * services.db.query("SELECT 1") // => "SELECT 1" * ``` * * @stability stable * @category utility types * @since 4.0.0 */ export type Services = T extends Stream ? _R : never /** * Checks whether a value is a Stream. * * **Example** (Checking whether a value is a Stream) * * ```ts import.meta.vitest * import { Stream } from "effect" * * Stream.isStream(Stream.make(1, 2, 3)) // => true * Stream.isStream({ data: [1, 2, 3] }) // => false * ``` * * @stability stable * @category guards * @since 4.0.0 */ export const isStream = (u: unknown): u is Stream => hasProperty(u, TypeId) /** * The default chunk size used by Stream constructors and combinators. * * **Example** (Reading the default chunk size) * * ```ts import.meta.vitest * import { Stream } from "effect" * * Stream.DefaultChunkSize // => 4096 * ``` * * @stability stable * @category constants * @since 2.0.0 */ export const DefaultChunkSize: number = Channel.DefaultChunkSize /** * Describes how merged streams decide when to halt. * * @stability stable * @category models * @since 4.0.0 */ export type HaltStrategy = Channel.HaltStrategy /** * Creates a stream from a array-emitting `Channel`. * * **Example** (Creating a stream from an array-emitting channel) * * ```ts import.meta.vitest * import { Channel, Effect, Stream } from "effect" * * const channel = Channel.succeed([1, 2, 3] as const) * const stream = Stream.fromChannel(channel) * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const fromChannel: , E, R>( channel: Channel.Channel ) => Stream ? A : never, E, R> = internal.fromChannel /** * Creates a stream from an effect. * * **Example** (Creating a stream from an effect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.fromEffect(Effect.succeed(42)) * await Effect.runPromise(Stream.runCollect(stream)) // => [42] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const fromEffect = (effect: Effect.Effect): Stream => fromChannel(Channel.fromEffect(Effect.map(effect, Arr.of))) /** * Accesses a service from the context and emits it as a single element. * * **Example** (Accessing a service as a stream) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class Greeter extends Context.Service string * }>()("Greeter") {} * * const stream = Stream.service(Greeter).pipe( * Stream.map((greeter) => greeter.greet("World")) * ) * * await Effect.runPromise( * stream.pipe( * Stream.provideService(Greeter, { * greet: (name) => `Hello, ${name}!` * }), * Stream.runCollect * ) * ) // => ["Hello, World!"] * ``` * * @stability stable * @category accessors * @since 4.0.0 */ export const service = (service: Context.Key): Stream => fromEffect(Effect.service(service)) /** * Optionally accesses a service from the context and emits the result as a * single element. * * **When to use** * * Use when you need a stream that emits an optional service from the context * without requiring that service to be present. * * **Example** (Accessing an optional service as a stream) * * ```ts import.meta.vitest * import { Context, Effect, Option, Stream } from "effect" * * class Greeter extends Context.Service string * }>()("Greeter") {} * * const stream = Stream.serviceOption(Greeter).pipe( * Stream.map((maybeGreeter) => * Option.match(maybeGreeter, { * onNone: () => "No greeter", * onSome: (greeter) => greeter.greet("World") * }) * ) * ) * * await Effect.runPromise( * stream.pipe( * Stream.provideService(Greeter, { * greet: (name) => `Hello, ${name}!` * }), * Stream.runCollect * ) * ) // => ["Hello, World!"] * ``` * * @stability stable * @category accessors * @since 4.0.0 */ export const serviceOption = (service: Context.Key): Stream> => fromEffect(Effect.serviceOption(service)) /** * Creates a stream that runs the effect and emits no elements. * * **Example** (Draining an effect into a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * let drained = false * await Effect.runPromise( * Stream.fromEffectDrain(Effect.sync(() => { * drained = true * })).pipe(Stream.runDrain) * ) * drained // => true * ``` * * @stability stable * @category constructors * @since 4.0.0 */ export const fromEffectDrain = (effect: Effect.Effect): Stream => fromPull(Effect.succeed(Effect.flatMap(effect, () => Cause.done()))) /** * Creates a stream from an effect producing a value of type `A` which repeats forever. * * **Example** (Repeating an effect forever) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * let n = 0 * const stream = Stream.fromEffectRepeat(Effect.sync(() => ++n)).pipe(Stream.take(5)) * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3, 4, 5] * ``` * * @stability stable * @category constructors * @since 4.0.0 */ export const fromEffectRepeat = (effect: Effect.Effect): Stream, R> => fromPull(Effect.succeed(Effect.map(effect, Arr.of))) /** * Creates a stream from an effect producing a value of type `A`, which is * repeated using the specified schedule. * * **Example** (Repeating an effect with a schedule) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const stream = Stream.fromEffectSchedule(Effect.succeed("ping"), Schedule.recurs(2)) * await Effect.runPromise(Stream.runCollect(stream)) // => ["ping", "ping", "ping"] * ``` * * @stability stable * @category constructors * @since 4.0.0 */ export const fromEffectSchedule = ( effect: Effect.Effect, schedule: Schedule.Schedule ): Stream => fromPull(Effect.gen(function*() { const step = yield* Schedule.toStepWithMetadata(schedule) let s = yield* Effect.provideService(effect, Schedule.CurrentMetadata, Schedule.CurrentMetadata.defaultValue()) let initial = true const pull = Effect.suspend(() => step(s as AS)).pipe( Effect.flatMap((meta) => Effect.provideService(effect, Schedule.CurrentMetadata, meta)), Effect.map((next) => { s = next return Arr.of(next) }) ) as Pull.Pull, E | ES, void, R | RS> return Effect.suspend(() => { if (initial) { initial = false return Effect.succeed(Arr.of(s)) } return pull }) })) /** * Creates a stream that emits `void` immediately once, then emits another * `void` after each specified interval. * * **Example** (Emitting ticks on an interval) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * await Effect.runPromise(Stream.tick(0).pipe(Stream.take(3), Stream.runCollect)) // => [undefined, undefined, undefined] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const tick = (interval: Duration.Input): Stream => fromPull(Effect.sync(() => { let first = true const effect = Effect.succeed(Arr.of(undefined)) const delayed = Effect.delay(effect, interval) return Effect.suspend(() => { if (first) { first = false return effect } return delayed }) })) /** * Creates a stream from a pull effect, such as one produced by `Stream.toPull`. * * **Details** * * A pull effect yields chunks on demand and completes when the upstream stream ends. * See `Stream.toPull` for a matching producer. * * **Example** (Creating a stream from a pull effect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.scoped( * Effect.gen(function*() { * const source = Stream.make(1, 2, 3) * const pull = yield* Stream.toPull(source) * const stream = Stream.fromPull(Effect.succeed(pull)) * return yield* Stream.runCollect(stream) * }) * ) * * await Effect.runPromise(program) // => [1, 2, 3] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const fromPull = ( pull: Effect.Effect, E, void, R>, EX, RX> ): Stream | EX, R | RX> => fromChannel(Channel.fromPull(pull)) /** * Derives a stream by transforming its pull effect. * * **Example** (Transforming a pull effect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * * const transformed = Stream.transformPull(stream, (pull) => Effect.succeed(pull)) * * await Effect.runPromise(Stream.runCollect(transformed)) // => [1, 2, 3] * ``` * * @stability stable * @category constructors * @since 4.0.0 */ export const transformPull = ( self: Stream, f: (pull: Pull.Pull, E, void>, scope: Scope.Scope) => Effect.Effect< Pull.Pull, E2, void, R2>, EX, RX > ): Stream, R | R2 | RX> => fromChannel( Channel.fromTransform((_, scope) => Effect.flatMap(Channel.toPullScoped(self.channel, scope), (pull) => f(pull as any, scope)) ) ) /** * Transforms a stream by effectfully transforming its pull effect. * * **Details** * * A forked scope is also provided to the transformation function, which is * closed once the resulting stream has finished processing. * * **Example** (Transforming a stream by effectfully transforming its pull effect) * * ```ts import.meta.vitest * import { Effect, Scope, Stream } from "effect" * * const finalized: Array = [] * const stream = Stream.make(1, 2, 3) * * const transformed = Stream.transformPullBracket( * stream, * (pull, _scope, forkedScope) => * Effect.gen(function*() { * yield* Scope.addFinalizer(forkedScope, Effect.sync(() => finalized.push(true))) * return pull * }) * ) * * await Effect.runPromise(Stream.runCollect(transformed)) // => [1, 2, 3] * finalized // => [true] * ``` * * @stability stable * @category constructors * @since 4.0.0 */ export const transformPullBracket = ( self: Stream, f: ( pull: Pull.Pull, E, void, R>, scope: Scope.Scope, forkedScope: Scope.Scope ) => Effect.Effect< Pull.Pull, E2, void, R2>, EX, RX > ): Stream, R | R2 | RX> => fromChannel( Channel.fromTransformBracket((_, scope, forkedScope) => Effect.flatMap(Channel.toPullScoped(self.channel, scope), (pull) => f(pull, scope, forkedScope)) ) ) /** * Creates a channel from a stream. * * **Example** (Converting a stream to a channel) * * ```ts import.meta.vitest * import { Channel, Effect, Stream } from "effect" * * const channel = Stream.toChannel(Stream.make(1, 2, 3)) * const values = await Effect.runPromise(Channel.runCollect(channel)) * values.flat() // => [1, 2, 3] * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const toChannel = ( stream: Stream ): Channel.Channel, E, void, unknown, unknown, unknown, R> => stream.channel /** * Creates a stream from a callback that can emit values into a queue. * * **When to use** * * Use when you need callback-based code to emit stream values by offering to a * `Queue`, or signal stream completion through the `Queue` module APIs. * * By default it uses an "unbounded" buffer size. * You can customize the buffer size and strategy by passing an object as the * second argument with the `bufferSize` and `strategy` fields. * * **Example** (Creating a stream from a callback that can emit values into a queue) * * ```ts import.meta.vitest * import { Effect, Queue, Stream } from "effect" * * const stream = Stream.callback((queue) => * Effect.sync(() => { * // Emit values to the stream * Queue.offerUnsafe(queue, 1) * Queue.offerUnsafe(queue, 2) * Queue.offerUnsafe(queue, 3) * // Signal completion * Queue.endUnsafe(queue) * }) * ) * * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] * ``` * * @stability stable * @category constructors * @since 4.0.0 */ export const callback = ( f: (queue: Queue.Queue) => Effect.Effect, options?: { readonly bufferSize?: number | undefined readonly strategy?: "sliding" | "dropping" | "suspend" | undefined } ): Stream> => fromChannel(Channel.callbackArray(f, options)) /** * Creates an empty stream. * * **Example** (Creating an empty stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * await Effect.runPromise(Stream.runCollect(Stream.empty)) // => [] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const empty: Stream = fromChannel(Channel.empty) /** * Creates a single-valued pure stream. * * **Example** (Creating a single-valued pure stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * await Effect.runPromise(Stream.runCollect(Stream.succeed(3))) // => [3] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const succeed = (value: A): Stream => fromChannel(Channel.succeed(Arr.of(value))) /** * Creates a stream from a sequence of values. * * **Example** (Creating a stream from a sequence of values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const make = >(...values: As): Stream => fromArray(values) /** * Creates a stream that synchronously evaluates a function and emits the result as a single value. * * **Details** * * The function is evaluated each time the stream is run. * * **Example** (Evaluating a value synchronously) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * await Effect.runPromise(Stream.sync(() => 2 + 1).pipe(Stream.runCollect)) // => [3] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const sync = (evaluate: LazyArg): Stream => fromChannel(Channel.sync(() => Arr.of(evaluate()))) /** * Creates a lazily constructed stream. * * **Details** * * The stream factory is evaluated each time the stream is run. * * **Example** (Creating a lazily constructed stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * await Effect.runPromise(Stream.suspend(() => Stream.make(1, 2, 3)).pipe(Stream.runCollect)) // => [1, 2, 3] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const suspend = (stream: LazyArg>): Stream => { const suspended = fromChannel(Channel.suspend(() => stream().channel)) suspendedStreams.set(suspended, stream) return suspended } const suspendedStreams = new WeakMap, LazyArg>>() /** * Terminates with the specified error. * * **Example** (Failing a stream) * * ```ts import.meta.vitest * import { Effect, Exit, Stream } from "effect" * * await Effect.runPromise(Effect.exit(Stream.runCollect(Stream.fail("Uh oh!")))) // => Exit.fail("Uh oh!") * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const fail = (error: E): Stream => fromChannel(Channel.fail(error)) /** * Terminates with the specified lazily evaluated error. * * **Example** (Failing a stream lazily) * * ```ts import.meta.vitest * import { Effect, Exit, Stream } from "effect" * * const stream = Stream.failSync(() => "Uh oh!") * * await Effect.runPromise(Stream.runCollect(stream).pipe(Effect.exit)) // => Exit.fail("Uh oh!") * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const failSync = (evaluate: LazyArg): Stream => fromChannel(Channel.failSync(evaluate)) /** * Creates a stream that fails with the specified `Cause`. * * **Example** (Failing with a cause) * * ```ts import.meta.vitest * import { Cause, Effect, Stream } from "effect" * * const stream = Stream.failCause(Cause.fail("Database connection failed")).pipe( * Stream.catchCause(() => Stream.succeed("recovered")) * ) * * await Effect.runPromise(Stream.runCollect(stream)) // => ["recovered"] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const failCause = (cause: Cause.Cause): Stream => fromChannel(Channel.failCause(cause)) /** * The stream that dies with the specified defect. * * **Example** (Dying with a defect) * * ```ts import.meta.vitest * import { Cause, Effect, Exit, Stream } from "effect" * * const defect = new Error("Boom") * const stream = Stream.die(defect) * * await Effect.runPromise(Effect.exit(Stream.runCollect(stream))) // => Exit.failCause(Cause.die(defect)) * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const die = (defect: unknown): Stream => fromChannel(Channel.die(defect)) /** * The stream that always fails with the specified lazily evaluated `Cause`. * * **Example** (Failing with a lazy cause) * * ```ts import.meta.vitest * import { Cause, Effect, Exit, Stream } from "effect" * * const stream = Stream.failCauseSync(() => * Cause.fail("Connection timeout after retries") * ) * * await Effect.runPromise(Stream.runCollect(stream).pipe(Effect.exit)) // => Exit.fail("Connection timeout after retries") * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const failCauseSync = (evaluate: LazyArg>): Stream => fromChannel(Channel.failCauseSync(evaluate)) /** * Creates a stream that consumes values from an iterator. * * **Details** * * The `maxChunkSize` parameter controls how many values are pulled per chunk. * Finite fractional values are rounded down, while `NaN` and non-positive * values are treated as `1`. * * **Example** (Consuming values from an iterator) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * function* numbers() { * yield 1 * yield 2 * yield 3 * } * * const stream = Stream.fromIteratorSucceed(numbers()) * * const program = Effect.gen(function* () { * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const fromIteratorSucceed = (iterator: IterableIterator, maxChunkSize?: number): Stream => fromChannel(Channel.fromIteratorArray(() => iterator, maxChunkSize)) /** * Creates a new `Stream` from an iterable collection of values. * * **Details** * * - `chunkSize`: Maximum number of values emitted per chunk. Finite fractional * values are rounded down, while `NaN` and non-positive values are treated as * `1`. * * **Example** (Creating a stream from an iterable) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const numbers = [1, 2, 3] * * const program = Effect.gen(function*() { * const stream = Stream.fromIterable(numbers) * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const fromIterable = ( iterable: Iterable, options?: { readonly chunkSize?: number | undefined } ): Stream => Array.isArray(iterable) && options?.chunkSize === undefined ? fromArray(iterable) : fromChannel(Channel.fromIterableArray(iterable, options?.chunkSize)) /** * Creates a stream from an effect producing an iterable of values. * * **When to use** * * Use when the iterable must be acquired from an Effect before the stream emits, * and acquisition services or failures should be part of the stream. * * **Example** (Creating a stream from an iterable effect) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class UserRepo extends Context.Service> * }>()("UserRepo") {} * * const listUsers = Effect.service(UserRepo).pipe( * Effect.andThen((repo) => repo.list) * ) * * const stream = Stream.fromIterableEffect(listUsers) * * const program = Effect.gen(function*() { * const users = yield* stream.pipe( * Stream.provideService(UserRepo, { * list: Effect.succeed(["user1", "user2"]) * }), * Stream.runCollect * ) * users // => ["user1", "user2"] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const fromIterableEffect = (iterable: Effect.Effect, E, R>): Stream => unwrap(Effect.map(iterable, fromIterable)) /** * Creates a stream by repeatedly running an effect that yields an iterable of values. * * **Example** (Repeating an iterable effect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.fromIterableEffectRepeat(Effect.succeed([1, 2])).pipe( * Stream.take(5) * ) * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 1, 2, 1] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 4.0.0 */ export const fromIterableEffectRepeat = ( iterable: Effect.Effect, E, R> ): Stream, R> => flatMap(fromEffectRepeat(iterable), fromIterable) /** * Creates a stream from an array of values. * * **Example** (Creating a stream from an array of values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.fromArray([1, 2, 3]) * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 4.0.0 */ export const fromArray = (array: ReadonlyArray): Stream => Arr.isReadonlyArrayNonEmpty(array) ? fromChannel(Channel.succeed(array)) : empty /** * Creates a stream from an effect that produces an array of values. * * **When to use** * * Use when the array must be acquired from an Effect before the stream emits, * and acquisition services or failures should be part of the stream. * * **Example** (Creating a stream from an effect that produces an array of values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.fromArrayEffect(Effect.succeed(["Ada", "Grace"])) * const values = yield* Stream.runCollect(stream) * values // => ["Ada", "Grace"] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 4.0.0 */ export const fromArrayEffect = ( effect: Effect.Effect, E, R> ): Stream, R> => unwrap(Effect.map(effect, fromArray)) as any /** * Creates a stream from an arbitrary number of arrays. * * **Example** (Creating a stream from an arbitrary number of arrays) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.fromArrays([1, 2], [3, 4]) * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 3, 4] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 4.0.0 */ export const fromArrays = >>( ...arrays: Arr ): Stream => fromChannel(Channel.fromArray(Arr.filter(arrays, Arr.isReadonlyArrayNonEmpty))) /** * Creates a stream that pulls values from a `Queue.Dequeue`. * * **Details** * * The stream emits non-empty batches of queued values and ends when the queue * fails with `Cause.Done`; other queue failures are propagated. * * **Example** (Creating a stream from a queue of values) * * ```ts import.meta.vitest * import { Cause, Effect, Queue, Stream } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.unbounded() * yield* Queue.offer(queue, 1) * yield* Queue.offer(queue, 2) * yield* Queue.offer(queue, 3) * yield* Queue.end(queue) * * const stream = Stream.fromQueue(queue) * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const fromQueue = (queue: Queue.Dequeue): Stream> => fromChannel(Channel.fromQueueArray(queue)) /** * Creates a stream from a subscription to a `PubSub`. * * **Example** (Creating a stream from a subscription to a PubSub) * * ```ts import.meta.vitest * import { Effect, Fiber, PubSub, Stream } from "effect" * * const program = Effect.gen(function*() { * const pubsub = yield* PubSub.unbounded({ replay: 3 }) * * const fiber = yield* Stream.fromPubSub(pubsub).pipe( * Stream.take(3), * Stream.runCollect, * Effect.forkChild * ) * * yield* PubSub.publish(pubsub, 1) * yield* PubSub.publish(pubsub, 2) * yield* PubSub.publish(pubsub, 3) * * const values = yield* Fiber.join(fiber) * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const fromPubSub = (pubsub: PubSub.PubSub): Stream => fromChannel(Channel.fromPubSubArray(pubsub)) /** * Creates a stream from a PubSub of `Take` values. * * **Details** * * `Take` values include end and failure signals. * * **Example** (Creating a stream from PubSub takes) * * ```ts import.meta.vitest * import { Effect, Exit, PubSub, Stream, Take } from "effect" * * const program = Effect.gen(function*() { * const pubsub = yield* PubSub.unbounded>({ * replay: 3 * }) * * yield* PubSub.publish(pubsub, [1]) * yield* PubSub.publish(pubsub, [2]) * yield* PubSub.publish(pubsub, Exit.succeed(undefined)) * * const values = yield* Stream.fromPubSubTake(pubsub).pipe(Stream.runCollect) * values // => [1, 2] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 4.0.0 */ export const fromPubSubTake = (pubsub: PubSub.PubSub>): Stream => fromChannel(Channel.fromPubSubTake(pubsub)) /** * Creates a stream from a lazily supplied Web `ReadableStream`. * * **Details** * * The stream reads from a `ReadableStreamDefaultReader` and maps read failures * with `onError`. Finalization cancels the reader and releases its lock, or * only releases the lock when `releaseLockOnEnd` is set. A rejected cancel * hook becomes a defect. * * **Example** (Creating a stream from a ReadableStream) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class StreamError extends Data.TaggedError("StreamError")<{ readonly cause: unknown }> {} * * const readableStream = new ReadableStream({ * start(controller) { * controller.enqueue(1) * controller.enqueue(2) * controller.enqueue(3) * controller.close() * } * }) * * const program = Effect.gen(function*() { * const stream = Stream.fromReadableStream({ * evaluate: () => readableStream, * onError: (cause) => new StreamError({ cause }) * }) * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const fromReadableStream = ( options: { readonly evaluate: LazyArg> readonly onError: (error: unknown) => E readonly releaseLockOnEnd?: boolean | undefined } ): Stream => fromChannel(Channel.fromReadableStream(options)) /** * Creates a stream from an AsyncIterable. * * **Example** (Creating a stream from an AsyncIterable) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class StreamError extends Data.TaggedError("StreamError")<{ readonly cause: unknown }> {} * * const iterable = (async function*() { * yield 1 * yield 2 * yield 3 * })() * * await Effect.runPromise(Effect.gen(function*() { * const stream = Stream.fromAsyncIterable(iterable, (cause) => new StreamError({ cause })) * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 3] * })) * * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const fromAsyncIterable = ( iterable: AsyncIterable, onError: (error: unknown) => E ): Stream => fromChannel(Channel.fromAsyncIterableArray(iterable, onError)) /** * Creates a stream that emits each output of a schedule that does not require input, * for as long as the schedule continues. * * **Example** (Creating a stream from a schedule) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { * const schedule = Schedule.recurs(3) * const stream = Stream.fromSchedule(schedule) * const values = yield* Stream.runCollect(stream) * values // => [0, 1, 2] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const fromSchedule = (schedule: Schedule.Schedule): Stream => fromPull( Effect.map( Schedule.toStepWithSleep(schedule), (step) => Pull.catchDone(Effect.map(step(void 0), Arr.of), () => Cause.done()) ) ) /** * Creates a stream from a PubSub subscription. * * **When to use** * * Use when you already have a `PubSub.Subscription` and want to expose its * messages as a `Stream`, with `Stream.take` or cancellation controlling how * many values are consumed. * * **Example** (Creating a stream from a PubSub subscription) * * ```ts import.meta.vitest * import { Effect, PubSub, Stream } from "effect" * * const program = Effect.scoped(Effect.gen(function*() { * const pubsub = yield* PubSub.unbounded() * const subscription = yield* PubSub.subscribe(pubsub) * * yield* PubSub.publish(pubsub, 1) * yield* PubSub.publish(pubsub, 2) * * const stream = Stream.fromSubscription(subscription) * const values = yield* stream.pipe(Stream.take(2), Stream.runCollect) * values // => [1, 2] * })) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 4.0.0 */ export const fromSubscription = (pubsub: PubSub.Subscription): Stream => fromChannel(Channel.fromSubscriptionArray(pubsub)) /** * Interface representing an event listener target. * * @stability stable * @category models * @since 3.4.0 */ export interface EventListener { addEventListener( event: string, f: (event: A) => void, options?: { readonly capture?: boolean readonly passive?: boolean readonly once?: boolean readonly signal?: AbortSignal } | boolean ): void removeEventListener( event: string, f: (event: A) => void, options?: { readonly capture?: boolean } | boolean ): void } /** * Creates a stream from an event listener. * * **Gotchas** * * Event listeners cannot backpressure the source. When a bounded `bufferSize` * is full, additional events are dropped rather than suspending the listener. * * **Example** (Creating a stream from an event listener) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * class NumberTarget implements Stream.EventListener { * addEventListener(event: string, f: (event: number) => void) { * if (event === "data") { * f(1) * f(2) * f(3) * } * } * removeEventListener(_event: string, _f: (event: number) => void) {} * } * * await Effect.runPromise(Effect.gen(function*() { * const stream = Stream.fromEventListener(new NumberTarget(), "data").pipe( * Stream.take(3) * ) * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 3] * })) * * ``` * * @stability stable * @category constructors * @since 3.1.0 */ export const fromEventListener = ( target: EventListener, type: string, options?: boolean | { readonly capture?: boolean readonly passive?: boolean readonly once?: boolean readonly bufferSize?: number | undefined } | undefined ): Stream => callback((queue) => { const once = typeof options === "object" && options.once function emit(event: any) { Queue.offerUnsafe(queue, event) if (once) Queue.endUnsafe(queue) } return Effect.acquireRelease( Effect.sync(() => target.addEventListener(type, emit, options)), () => Effect.sync(() => target.removeEventListener(type, emit, options)) ) }, { bufferSize: typeof options === "object" ? options.bufferSize : undefined }) /** * Creates a stream by repeatedly applying an effectful step function to a * state. * * **Details** * * Each `readonly [value, nextState]` result emits `value` and continues with * `nextState`; returning `undefined` ends the stream. * * **Example** (Unfolding stream state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.unfold(1, (n) => Effect.succeed([n, n + 1] as const)) * const values = yield* Stream.runCollect(stream.pipe(Stream.take(5))) * values // => [ 1, 2, 3, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const unfold = ( s: S, f: (s: S) => Effect.Effect ): Stream => fromPull(Effect.sync(() => { let state = s return Effect.flatMap(Effect.suspend(() => f(state)), (next) => { if (next === undefined) return Cause.done() state = next[1] return Effect.succeed(Arr.of(next[0])) }) })) /** * Creates a stream by repeatedly evaluating an effectful page function. * * **When to use** * * Use to consume paginated APIs where each step returns a batch of values * together with an optional next state. * * **Details** * * This is similar to {@link unfold}, but each step can emit zero or more values * and independently decide whether another state should be requested. * * **Example** (Paginating stream state) * * ```ts import.meta.vitest * import { Effect, Option, Stream } from "effect" * * const stream = Stream.paginate(0, (n: number) => * Effect.succeed( * [ * [n], * n < 3 ? Option.some(n + 1) : Option.none() * ] as const * )) * * await Effect.runPromise(Stream.runCollect(stream)) // => [0, 1, 2, 3] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const paginate = ( s: S, f: ( s: S ) => Effect.Effect, Option.Option], E, R> ): Stream => fromPull(Effect.sync(() => { let state = s let done = false return Effect.suspend(function loop(): Pull.Pull, E, void, R> { if (done) return Cause.done() return Effect.flatMap(f(state), ([a, s]) => { if (Option.isNone(s)) { done = true } else { state = s.value } if (!Arr.isReadonlyArrayNonEmpty(a)) return loop() return Effect.succeed(a) }) }) })) /** * Creates an infinite stream by repeatedly applying a function to a seed value. * * **Example** (Iterating from a seed value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.iterate(1, (n) => n + 1).pipe(Stream.take(3)) * * const program = Effect.gen(function* () { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const iterate = (value: A, next: (value: A) => A): Stream => unfold(value, (a) => Effect.succeed([a, next(a)])) /** * Constructs a stream from a range of integers, including both endpoints. * * **Details** * * If the provided `min` is greater than `max`, the stream will not emit any * values. Finite fractional `chunkSize` values are rounded down, while `NaN` * and non-positive values are treated as `1`. * * **Example** (Creating a numeric range) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.range(1, 5).pipe(Stream.runCollect) * values // => [ 1, 2, 3, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const range = ( min: number, max: number, chunkSize = Channel.DefaultChunkSize ): Stream => min > max ? empty : fromPull(Effect.sync(() => { const size = Count.normalizeNonEmpty(chunkSize) let start = min let done = false return Effect.suspend(() => { if (done) return Cause.done() const remaining = max - start + 1 if (remaining > size) { const chunk = Arr.range(start, start + size - 1) start += size return Effect.succeed(chunk) } const chunk = Arr.range(start, start + remaining - 1) done = true return Effect.succeed(chunk) }) })) /** * The stream that never produces any value or fails with any error. * * **Example** (Creating a never-ending stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Stream.never.pipe( * Stream.take(0), * Stream.runCollect * ) * * await Effect.runPromise(program) // => [] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const never: Stream = fromChannel(Channel.never) /** * Creates a stream produced from an `Effect`. * * **Example** (Unwrapping a stream effect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const effect = Effect.succeed(Stream.make(1, 2, 3)) * * const stream = Stream.unwrap(effect) * * const program = Effect.gen(function*() { * const chunk = yield* Stream.runCollect(stream) * chunk // => [ 1, 2, 3 ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export const unwrap = ( effect: Effect.Effect, E, R> ): Stream> => fromChannel(Channel.unwrap(Effect.map(effect, toChannel))) /** * Runs a stream that requires `Scope` in a managed scope, ensuring its * finalizers are run when the stream completes. * * **Example** (Scoping a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const stream = Stream.scoped( * Stream.fromEffect( * Effect.acquireRelease( * Effect.sync(() => { * events.push("acquire") * return "resource" * }), * () => Effect.sync(() => events.push("release")) * ) * ) * ) * * await Effect.runPromise(Stream.runCollect(stream)) // => ["resource"] * events // => ["acquire", "release"] * ``` * * @stability stable * @category resource management * @since 2.0.0 */ export const scoped = ( self: Stream ): Stream> => fromChannel(Channel.scoped(self.channel)) /** * Transforms the elements of this stream using the supplied function. * * **Example** (Mapping stream values) * * ```ts import.meta.vitest * import { Effect, Option, Stream } from "effect" * * const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.map((n, i) => n + i)) * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 3, 5] * ``` * * @stability stable * @category mapping * @since 2.0.0 */ export const map: { /** * Transforms the elements of this stream using the supplied function. * * **Example** (Mapping stream values) * * ```ts import.meta.vitest * import { Effect, Option, Stream } from "effect" * * const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.map((n, i) => n + i)) * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 3, 5] * ``` * * @stability stable * @category mapping * @since 2.0.0 */ (f: (a: A, i: number) => B): (self: Stream) => Stream /** * Transforms the elements of this stream using the supplied function. * * **Example** (Mapping stream values) * * ```ts import.meta.vitest * import { Effect, Option, Stream } from "effect" * * const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.map((n, i) => n + i)) * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 3, 5] * ``` * * @stability stable * @category mapping * @since 2.0.0 */ (self: Stream, f: (a: A, i: number) => B): Stream } = dual(2, (self: Stream, f: (a: A, i: number) => B): Stream => suspend(() => { let i = 0 return fromChannel(Channel.map( self.channel, Arr.map((o) => f(o, i++)) )) })) /** * Replaces every element of the stream with the provided constant value. * * **Example** (Replacing stream elements) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.as("x"), * Stream.runCollect * ) * values // => [ 'x', 'x', 'x' ] * }) * * await Effect.runPromise(program) * ``` * * @see {@link map} for deriving the replacement value from each element * * @stability stable * @category mapping * @since 4.0.0 */ export const as: { /** * Replaces every element of the stream with the provided constant value. * * **Example** (Replacing stream elements) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.as("x"), * Stream.runCollect * ) * values // => [ 'x', 'x', 'x' ] * }) * * await Effect.runPromise(program) * ``` * * @see {@link map} for deriving the replacement value from each element * * @stability stable * @category mapping * @since 4.0.0 */ (value: B): (self: Stream) => Stream /** * Replaces every element of the stream with the provided constant value. * * **Example** (Replacing stream elements) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.as("x"), * Stream.runCollect * ) * values // => [ 'x', 'x', 'x' ] * }) * * await Effect.runPromise(program) * ``` * * @see {@link map} for deriving the replacement value from each element * * @stability stable * @category mapping * @since 4.0.0 */ (self: Stream, value: B): Stream } = dual(2, (self: Stream, value: B): Stream => map(self, () => value)) /** * Maps both the failure and success channels of a stream. * * **Details** * * Maps every typed error in the cause, preserving defects, interrupts and * reason order. Unlike typed recovery, mapping does not convert errors to defects. * * **Example** (Mapping both the failure and success channels of a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const mapper = { * onElement: (value: number) => value * 2, * onError: (error: string) => `error: ${error}` * } * * const program = Effect.gen(function*() { * const success = yield* Stream.make(1, 2).pipe( * Stream.mapBoth(mapper), * Stream.runCollect * ) * success // => [ 2, 4 ] * * const failure = yield* Stream.fail("boom").pipe( * Stream.mapBoth(mapper), * Stream.catch((error: string) => Stream.succeed(error)), * Stream.runCollect * ) * failure // => [ 'error: boom' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 2.0.0 */ export const mapBoth: { /** * Maps both the failure and success channels of a stream. * * **Details** * * Maps every typed error in the cause, preserving defects, interrupts and * reason order. Unlike typed recovery, mapping does not convert errors to defects. * * **Example** (Mapping both the failure and success channels of a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const mapper = { * onElement: (value: number) => value * 2, * onError: (error: string) => `error: ${error}` * } * * const program = Effect.gen(function*() { * const success = yield* Stream.make(1, 2).pipe( * Stream.mapBoth(mapper), * Stream.runCollect * ) * success // => [ 2, 4 ] * * const failure = yield* Stream.fail("boom").pipe( * Stream.mapBoth(mapper), * Stream.catch((error: string) => Stream.succeed(error)), * Stream.runCollect * ) * failure // => [ 'error: boom' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 2.0.0 */ ( options: { readonly onElement: (a: A) => A2; readonly onError: (e: E) => E2 } ): (self: Stream) => Stream /** * Maps both the failure and success channels of a stream. * * **Details** * * Maps every typed error in the cause, preserving defects, interrupts and * reason order. Unlike typed recovery, mapping does not convert errors to defects. * * **Example** (Mapping both the failure and success channels of a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const mapper = { * onElement: (value: number) => value * 2, * onError: (error: string) => `error: ${error}` * } * * const program = Effect.gen(function*() { * const success = yield* Stream.make(1, 2).pipe( * Stream.mapBoth(mapper), * Stream.runCollect * ) * success // => [ 2, 4 ] * * const failure = yield* Stream.fail("boom").pipe( * Stream.mapBoth(mapper), * Stream.catch((error: string) => Stream.succeed(error)), * Stream.runCollect * ) * failure // => [ 'error: boom' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 2.0.0 */ ( self: Stream, options: { readonly onElement: (a: A) => A2; readonly onError: (e: E) => E2 } ): Stream } = dual(2, ( self: Stream, options: { readonly onElement: (a: A) => A2; readonly onError: (e: E) => E2 } ): Stream => self.pipe( map(options.onElement), mapError(options.onError) )) /** * Transforms each emitted chunk using the provided function, which receives the chunk and its index. * * **Example** (Mapping stream chunks) * * ```ts import.meta.vitest * import { Array, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.rechunk(2), * Stream.mapArray((chunk, index) => Array.map(chunk, (n) => n + index)), * Stream.runCollect * ) * result // => [ 1, 2, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ export const mapArray: { /** * Transforms each emitted chunk using the provided function, which receives the chunk and its index. * * **Example** (Mapping stream chunks) * * ```ts import.meta.vitest * import { Array, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.rechunk(2), * Stream.mapArray((chunk, index) => Array.map(chunk, (n) => n + index)), * Stream.runCollect * ) * result // => [ 1, 2, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ ( f: (a: Arr.NonEmptyReadonlyArray, i: number) => Arr.NonEmptyReadonlyArray ): (self: Stream) => Stream /** * Transforms each emitted chunk using the provided function, which receives the chunk and its index. * * **Example** (Mapping stream chunks) * * ```ts import.meta.vitest * import { Array, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.rechunk(2), * Stream.mapArray((chunk, index) => Array.map(chunk, (n) => n + index)), * Stream.runCollect * ) * result // => [ 1, 2, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ ( self: Stream, f: (a: Arr.NonEmptyReadonlyArray, i: number) => Arr.NonEmptyReadonlyArray ): Stream } = dual(2, ( self: Stream, f: (a: Arr.NonEmptyReadonlyArray, i: number) => Arr.NonEmptyReadonlyArray ): Stream => fromChannel(Channel.map(self.channel, f))) /** * Maps over elements of the stream with the specified effectful function. * * **When to use** * * Use when each stream element transformation needs an Effect, service * dependency, failure channel, or configured concurrency. * * **Example** (Effectfully mapping stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const stream = Stream.make(1, 2, 3) * * const mappedStream = stream.pipe( * Stream.mapEffect((n) => * Effect.sync(() => { * events.push(`Processing: ${n}`) * return n * 2 * }) * ) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(mappedStream) * result // => [2, 4, 6] * }) * * await Effect.runPromise(program) * events // => ["Processing: 1", "Processing: 2", "Processing: 3"] * ``` * * @stability stable * @category mapping * @since 2.0.0 */ export const mapEffect: { /** * Maps over elements of the stream with the specified effectful function. * * **When to use** * * Use when each stream element transformation needs an Effect, service * dependency, failure channel, or configured concurrency. * * **Example** (Effectfully mapping stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const stream = Stream.make(1, 2, 3) * * const mappedStream = stream.pipe( * Stream.mapEffect((n) => * Effect.sync(() => { * events.push(`Processing: ${n}`) * return n * 2 * }) * ) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(mappedStream) * result // => [2, 4, 6] * }) * * await Effect.runPromise(program) * events // => ["Processing: 1", "Processing: 2", "Processing: 3"] * ``` * * @stability stable * @category mapping * @since 2.0.0 */ ( f: (a: A, i: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined readonly unordered?: boolean | undefined } | undefined ): (self: Stream) => Stream /** * Maps over elements of the stream with the specified effectful function. * * **When to use** * * Use when each stream element transformation needs an Effect, service * dependency, failure channel, or configured concurrency. * * **Example** (Effectfully mapping stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const stream = Stream.make(1, 2, 3) * * const mappedStream = stream.pipe( * Stream.mapEffect((n) => * Effect.sync(() => { * events.push(`Processing: ${n}`) * return n * 2 * }) * ) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(mappedStream) * result // => [2, 4, 6] * }) * * await Effect.runPromise(program) * events // => ["Processing: 1", "Processing: 2", "Processing: 3"] * ``` * * @stability stable * @category mapping * @since 2.0.0 */ ( self: Stream, f: (a: A, i: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined readonly unordered?: boolean | undefined } | undefined ): Stream } = dual((args) => isStream(args[0]), ( self: Stream, f: (a: A, i: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined readonly unordered?: boolean | undefined } | undefined ): Stream => self.channel.pipe( Channel.flattenArray, Channel.mapEffect(f, options), Channel.map(Arr.of), fromChannel )) /** * Flattens a stream of `Effect` values into a stream of their results. * * **When to use** * * Use when stream elements already are effects and their successes should become * stream elements while their failures enter the stream error channel. * * **Example** (Flattening a stream of Effect values into a stream of their results) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(Effect.succeed(1), Effect.succeed(2), Effect.succeed(3)) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream.pipe(Stream.flattenEffect())) * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const flattenEffect: < Arg extends Stream, any, any> | { readonly concurrency?: Concurrency | undefined readonly unordered?: boolean | undefined } | undefined = { readonly concurrency?: Concurrency | undefined readonly unordered?: boolean | undefined } >( selfOrOptions?: Arg, options?: { readonly concurrency?: Concurrency | undefined readonly unordered?: boolean | undefined } | undefined ) => [Arg] extends [Stream, infer _E, infer _R>] ? Stream<_A, _EX | _E, _RX | _R> : (self: Stream, E, R>) => Stream = dual( (args) => isStream(args[0]), ( self: Stream, E, R>, options?: { readonly concurrency?: Concurrency | undefined readonly unordered?: boolean | undefined } | undefined ): Stream => mapEffect(self, identity, options) ) /** * Maps over non-empty array chunks emitted by the stream effectfully. * * **When to use** * * Use when transformation needs to see and replace each non-empty emitted chunk * effectfully instead of mapping individual stream elements. * * **Example** (Effectfully mapping stream chunks) * * ```ts import.meta.vitest * import { Array, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.fromArray([1, 2, 3, 4]).pipe( * Stream.rechunk(2), * Stream.mapArrayEffect((chunk, index) => * Effect.succeed(Array.map(chunk, (n) => n + index * 10)) * ), * Stream.runCollect * ) * result // => [ 1, 2, 13, 14 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ export const mapArrayEffect: { /** * Maps over non-empty array chunks emitted by the stream effectfully. * * **When to use** * * Use when transformation needs to see and replace each non-empty emitted chunk * effectfully instead of mapping individual stream elements. * * **Example** (Effectfully mapping stream chunks) * * ```ts import.meta.vitest * import { Array, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.fromArray([1, 2, 3, 4]).pipe( * Stream.rechunk(2), * Stream.mapArrayEffect((chunk, index) => * Effect.succeed(Array.map(chunk, (n) => n + index * 10)) * ), * Stream.runCollect * ) * result // => [ 1, 2, 13, 14 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ ( f: (a: Arr.NonEmptyReadonlyArray, i: number) => Effect.Effect, E2, R2> ): (self: Stream) => Stream /** * Maps over non-empty array chunks emitted by the stream effectfully. * * **When to use** * * Use when transformation needs to see and replace each non-empty emitted chunk * effectfully instead of mapping individual stream elements. * * **Example** (Effectfully mapping stream chunks) * * ```ts import.meta.vitest * import { Array, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.fromArray([1, 2, 3, 4]).pipe( * Stream.rechunk(2), * Stream.mapArrayEffect((chunk, index) => * Effect.succeed(Array.map(chunk, (n) => n + index * 10)) * ), * Stream.runCollect * ) * result // => [ 1, 2, 13, 14 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ ( self: Stream, f: (a: Arr.NonEmptyReadonlyArray, i: number) => Effect.Effect, E2, R2> ): Stream } = dual(2, ( self: Stream, f: (a: Arr.NonEmptyReadonlyArray, i: number) => Effect.Effect, E2, R2> ): Stream => fromChannel(Channel.mapEffect(self.channel, f))) /** * Lifts failures and successes into a `Result`, yielding a stream that cannot fail. * * **Details** * * The stream ends after the first failure, emitting a `Result.fail` value. * * **Example** (Converting failures to results) * * ```ts import.meta.vitest * import { Effect, Result, Stream } from "effect" * * const program = Effect.gen(function*() { * const results = yield* Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("boom")), * Stream.result, * Stream.map(Result.match({ * onFailure: (error) => `failure: ${error}`, * onSuccess: (value) => `success: ${value}` * })), * Stream.runCollect * ) * results // => [ 'success: 1', 'success: 2', 'failure: boom' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ export const result = (self: Stream): Stream, never, R> => self.pipe( map(Result.succeed), catch_((e) => succeed(Result.fail(e))) ) /** * Runs the provided effect for each element while preserving the elements. * * **Example** (Tapping stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const result = yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.tap((n) => Effect.sync(() => events.push(`before mapping: ${n}`))), * Stream.map((n) => n * 2), * Stream.tap((n) => Effect.sync(() => events.push(`after mapping: ${n}`))), * Stream.runCollect * ) * * result // => [2, 4, 6] * }) * * await Effect.runPromise(program) * events // => ["before mapping: 1", "after mapping: 2", "before mapping: 2", "after mapping: 4", "before mapping: 3", "after mapping: 6"] * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const tap: { /** * Runs the provided effect for each element while preserving the elements. * * **Example** (Tapping stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const result = yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.tap((n) => Effect.sync(() => events.push(`before mapping: ${n}`))), * Stream.map((n) => n * 2), * Stream.tap((n) => Effect.sync(() => events.push(`after mapping: ${n}`))), * Stream.runCollect * ) * * result // => [2, 4, 6] * }) * * await Effect.runPromise(program) * events // => ["before mapping: 1", "after mapping: 2", "before mapping: 2", "after mapping: 4", "before mapping: 3", "after mapping: 6"] * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ ( f: (a: NoInfer) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined } | undefined ): (self: Stream) => Stream /** * Runs the provided effect for each element while preserving the elements. * * **Example** (Tapping stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const result = yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.tap((n) => Effect.sync(() => events.push(`before mapping: ${n}`))), * Stream.map((n) => n * 2), * Stream.tap((n) => Effect.sync(() => events.push(`after mapping: ${n}`))), * Stream.runCollect * ) * * result // => [2, 4, 6] * }) * * await Effect.runPromise(program) * events // => ["before mapping: 1", "after mapping: 2", "before mapping: 2", "after mapping: 4", "before mapping: 3", "after mapping: 6"] * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ ( self: Stream, f: (a: NoInfer) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined } | undefined ): Stream } = dual((args) => isStream(args[0]), ( self: Stream, f: (a: NoInfer) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined } | undefined ): Stream => mapEffect( self, (a) => Effect.as(f(a), a), options )) /** * Returns a stream that effectfully "peeks" at elements and failures. * * **Example** (Tapping values and errors) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("boom")), * Stream.tapBoth({ * onElement: (value) => Effect.sync(() => events.push(`seen: ${value}`)), * onError: (error) => Effect.sync(() => events.push(`error: ${error}`)) * }), * Stream.catch(() => Stream.make(3)) * ) * const result = yield* Stream.runCollect(stream) * result // => [1, 2, 3] * }) * * await Effect.runPromise(program) * events // => ["seen: 1", "seen: 2", "error: boom"] * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const tapBoth: { /** * Returns a stream that effectfully "peeks" at elements and failures. * * **Example** (Tapping values and errors) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("boom")), * Stream.tapBoth({ * onElement: (value) => Effect.sync(() => events.push(`seen: ${value}`)), * onError: (error) => Effect.sync(() => events.push(`error: ${error}`)) * }), * Stream.catch(() => Stream.make(3)) * ) * const result = yield* Stream.runCollect(stream) * result // => [1, 2, 3] * }) * * await Effect.runPromise(program) * events // => ["seen: 1", "seen: 2", "error: boom"] * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ ( options: { readonly onElement: (a: NoInfer) => Effect.Effect readonly onError: (a: NoInfer) => Effect.Effect readonly concurrency?: Concurrency | undefined } ): (self: Stream) => Stream /** * Returns a stream that effectfully "peeks" at elements and failures. * * **Example** (Tapping values and errors) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("boom")), * Stream.tapBoth({ * onElement: (value) => Effect.sync(() => events.push(`seen: ${value}`)), * onError: (error) => Effect.sync(() => events.push(`error: ${error}`)) * }), * Stream.catch(() => Stream.make(3)) * ) * const result = yield* Stream.runCollect(stream) * result // => [1, 2, 3] * }) * * await Effect.runPromise(program) * events // => ["seen: 1", "seen: 2", "error: boom"] * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ ( self: Stream, options: { readonly onElement: (a: NoInfer) => Effect.Effect readonly onError: (a: NoInfer) => Effect.Effect readonly concurrency?: Concurrency | undefined } ): Stream } = dual(2, ( self: Stream, options: { readonly onElement: (a: NoInfer) => Effect.Effect readonly onError: (a: NoInfer) => Effect.Effect readonly concurrency?: Concurrency | undefined } ): Stream => self.pipe( tapError(options.onError), tap(options.onElement, { concurrency: options.concurrency }) )) /** * Runs a sink for all stream elements while still emitting them downstream. * * **Example** (Tapping values with a sink) * * ```ts import.meta.vitest * import { Effect, Ref, Sink, Stream } from "effect" * * const program = Effect.gen(function*() { * const seen = yield* Ref.make>([]) * const sink = Sink.forEach((value: number) => * Ref.update(seen, (items) => [...items, value]) * ) * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.tapSink(sink), * Stream.runCollect * ) * const tapped = yield* Ref.get(seen) * tapped // => [ 1, 2, 3 ] * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const tapSink: { /** * Runs a sink for all stream elements while still emitting them downstream. * * **Example** (Tapping values with a sink) * * ```ts import.meta.vitest * import { Effect, Ref, Sink, Stream } from "effect" * * const program = Effect.gen(function*() { * const seen = yield* Ref.make>([]) * const sink = Sink.forEach((value: number) => * Ref.update(seen, (items) => [...items, value]) * ) * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.tapSink(sink), * Stream.runCollect * ) * const tapped = yield* Ref.get(seen) * tapped // => [ 1, 2, 3 ] * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ (sink: Sink.Sink): (self: Stream) => Stream /** * Runs a sink for all stream elements while still emitting them downstream. * * **Example** (Tapping values with a sink) * * ```ts import.meta.vitest * import { Effect, Ref, Sink, Stream } from "effect" * * const program = Effect.gen(function*() { * const seen = yield* Ref.make>([]) * const sink = Sink.forEach((value: number) => * Ref.update(seen, (items) => [...items, value]) * ) * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.tapSink(sink), * Stream.runCollect * ) * const tapped = yield* Ref.get(seen) * tapped // => [ 1, 2, 3 ] * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ (self: Stream, sink: Sink.Sink): Stream } = dual( 2, ( self: Stream, sink: Sink.Sink ): Stream => transformPullBracket( self, Effect.fnUntraced(function*(pull, _, scope) { const upstreamLatch = Latch.makeUnsafe() const sinkLatch = Latch.makeUnsafe() let chunk: Arr.NonEmptyReadonlyArray | undefined = undefined let causeSink: Cause.Cause | undefined = undefined let sinkDone = false let streamDone = false const sinkUpstream = upstreamLatch.whenOpen(Effect.suspend(() => { if (chunk) { const arr = chunk! chunk = undefined if (!streamDone) upstreamLatch.closeUnsafe() return Effect.as(sinkLatch.open, arr) } return Cause.done() })) yield* Effect.suspend(() => sink.transform(sinkUpstream, scope)).pipe( (eff) => Effect.onExitPrimitive(eff, (exit) => { sinkDone = true if (Exit.isFailure(exit)) { causeSink = exit.cause } return sinkLatch.open }, true), Effect.forkIn(scope) ) const pullAndOffer = pull.pipe( Effect.flatMap((chunk_) => { // The sink may have exited while the upstream pull was suspended. if (causeSink) return Effect.failCause(causeSink) if (sinkDone) return Effect.succeed(chunk_) chunk = chunk_ sinkLatch.closeUnsafe() upstreamLatch.openUnsafe() return Effect.as(sinkLatch.await, chunk_) }), Pull.catchDone((): Effect.Effect => { if (causeSink) return Effect.failCause(causeSink) if (sinkDone) return Cause.done() streamDone = true sinkLatch.closeUnsafe() upstreamLatch.openUnsafe() return Effect.flatMap(sinkLatch.await, (): Pull.Pull => causeSink ? Effect.failCause(causeSink) : Cause.done()) }) ) return Effect.suspend((): Pull.Pull, E | E2, void, R> => { if (causeSink) { return Effect.failCause(causeSink) } else if (sinkDone) { return pull } return pullAndOffer }) }) ) ) /** * Maps each element to a stream and flattens the resulting streams. * * **Details** * * With the default sequential concurrency, inner streams are concatenated in * input order. When `concurrency` is greater than `1` or `"unbounded"`, * multiple inner streams may run at the same time and their outputs are merged * as they arrive. * * **Example** (Flat mapping stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.flatMap((n) => Stream.make(n, n * 2)), * Stream.runCollect * ) * values // => [ 1, 2, 2, 4, 3, 6 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const flatMap: { /** * Maps each element to a stream and flattens the resulting streams. * * **Details** * * With the default sequential concurrency, inner streams are concatenated in * input order. When `concurrency` is greater than `1` or `"unbounded"`, * multiple inner streams may run at the same time and their outputs are merged * as they arrive. * * **Example** (Flat mapping stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.flatMap((n) => Stream.make(n, n * 2)), * Stream.runCollect * ) * values // => [ 1, 2, 2, 4, 3, 6 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ ( f: (a: A) => Stream, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined } | undefined ): (self: Stream) => Stream /** * Maps each element to a stream and flattens the resulting streams. * * **Details** * * With the default sequential concurrency, inner streams are concatenated in * input order. When `concurrency` is greater than `1` or `"unbounded"`, * multiple inner streams may run at the same time and their outputs are merged * as they arrive. * * **Example** (Flat mapping stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.flatMap((n) => Stream.make(n, n * 2)), * Stream.runCollect * ) * values // => [ 1, 2, 2, 4, 3, 6 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ ( self: Stream, f: (a: A) => Stream, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined } | undefined ): Stream } = dual((args) => isStream(args[0]), ( self: Stream, f: (a: A) => Stream, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined } | undefined ): Stream => self.channel.pipe( Channel.flattenArray, Channel.flatMap((a) => f(a).channel, options), fromChannel )) /** * Switches to the latest stream produced by the mapping function, interrupting * the previous stream when a new element arrives. * * **Example** (Switching to the latest stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Stream.make(1, 2, 3).pipe( * Stream.switchMap((n) => (n === 3 ? Stream.make(n) : Stream.never)), * Stream.runCollect * ) * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* program * result // => [ 3 ] * })) * ``` * * @stability stable * @category sequencing * @since 4.0.0 */ export const switchMap: { /** * Switches to the latest stream produced by the mapping function, interrupting * the previous stream when a new element arrives. * * **Example** (Switching to the latest stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Stream.make(1, 2, 3).pipe( * Stream.switchMap((n) => (n === 3 ? Stream.make(n) : Stream.never)), * Stream.runCollect * ) * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* program * result // => [ 3 ] * })) * ``` * * @stability stable * @category sequencing * @since 4.0.0 */ ( f: (a: A) => Stream, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined } | undefined ): (self: Stream) => Stream /** * Switches to the latest stream produced by the mapping function, interrupting * the previous stream when a new element arrives. * * **Example** (Switching to the latest stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Stream.make(1, 2, 3).pipe( * Stream.switchMap((n) => (n === 3 ? Stream.make(n) : Stream.never)), * Stream.runCollect * ) * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* program * result // => [ 3 ] * })) * ``` * * @stability stable * @category sequencing * @since 4.0.0 */ ( self: Stream, f: (a: A) => Stream, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined } | undefined ): Stream } = dual((args) => isStream(args[0]), ( self: Stream, f: (a: A) => Stream, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined } | undefined ): Stream => self.channel.pipe( Channel.flattenArray, Channel.switchMap((a) => f(a).channel, options), fromChannel )) /** * Flattens a stream of streams into a single stream. * * **Details** * * With the default sequential concurrency, inner streams are concatenated in * strict order. When `concurrency` is greater than `1` or `"unbounded"`, * multiple inner streams may run at the same time and their outputs are merged * as they arrive. * * **Example** (Flattening nested streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const streamOfStreams = Stream.make( * Stream.make(1, 2), * Stream.make(3, 4), * Stream.make(5, 6) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(Stream.flatten(streamOfStreams)) * values // => [ 1, 2, 3, 4, 5, 6 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const flatten: < Arg extends Stream, any, any> | { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined } | undefined = { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined } >( selfOrOptions?: Arg, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined } | undefined ) => [Arg] extends [Stream, infer _E2, infer _R2>] ? Stream<_A, _E | _E2, _R | _R2> : (self: Stream, E2, R2>) => Stream = dual( (args) => isStream(args[0]), ( self: Stream, E2, R2>, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined } | undefined ): Stream => flatMap(self, identity, options) ) /** * Flattens a stream of non-empty arrays into a stream of elements. * * **Example** (Flattening a stream of non-empty arrays into a stream of elements) * * ```ts import.meta.vitest * import { Array, Effect, Stream } from "effect" * * const stream = Stream.make(Array.make(1, 2), Array.make(3)) * * const program = Effect.gen(function* () { * const result = yield* Stream.runCollect(Stream.flattenArray(stream)) * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 4.0.0 */ export const flattenArray = (self: Stream, E, R>): Stream => fromChannel(Channel.flattenArray(self.channel)) /** * Converts this stream to one that runs its effects but emits no elements. * * **Example** (Draining stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(1, 6).pipe(Stream.drain, Stream.runCollect) * result // => [] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const drain = (self: Stream): Stream => fromChannel(Channel.drain(self.channel)) /** * Runs the provided stream in the background while this stream runs, interrupting it * when this stream completes and failing if the background stream fails or defects. * * **Example** (Draining a stream in the background) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const foreground = Stream.make(1, 2) * const background = Stream.fromEffect(Effect.sync(() => events.push("background task"))) * * const program = Effect.gen(function*() { * const values = yield* foreground.pipe( * Stream.drainFork(background), * Stream.runCollect * ) * values // => [1, 2] * }) * * await Effect.runPromise(program) * events // => ["background task"] * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const drainFork: { /** * Runs the provided stream in the background while this stream runs, interrupting it * when this stream completes and failing if the background stream fails or defects. * * **Example** (Draining a stream in the background) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const foreground = Stream.make(1, 2) * const background = Stream.fromEffect(Effect.sync(() => events.push("background task"))) * * const program = Effect.gen(function*() { * const values = yield* foreground.pipe( * Stream.drainFork(background), * Stream.runCollect * ) * values // => [1, 2] * }) * * await Effect.runPromise(program) * events // => ["background task"] * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ (that: Stream): (self: Stream) => Stream /** * Runs the provided stream in the background while this stream runs, interrupting it * when this stream completes and failing if the background stream fails or defects. * * **Example** (Draining a stream in the background) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const foreground = Stream.make(1, 2) * const background = Stream.fromEffect(Effect.sync(() => events.push("background task"))) * * const program = Effect.gen(function*() { * const values = yield* foreground.pipe( * Stream.drainFork(background), * Stream.runCollect * ) * values // => [1, 2] * }) * * await Effect.runPromise(program) * events // => ["background task"] * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ (self: Stream, that: Stream): Stream } = dual( 2, (self: Stream, that: Stream): Stream => mergeEffect(self, runDrain(that)) ) /** * Repeats the entire stream according to the provided schedule. * * **Example** (Repeating a stream on a schedule) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function* () { * const result = yield* Stream.make(1).pipe( * Stream.repeat(Schedule.recurs(4)), * Stream.runCollect * ) * * result // => [ 1, 1, 1, 1, 1 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category repetition * @since 2.0.0 */ export const repeat: { /** * Repeats the entire stream according to the provided schedule. * * **Example** (Repeating a stream on a schedule) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function* () { * const result = yield* Stream.make(1).pipe( * Stream.repeat(Schedule.recurs(4)), * Stream.runCollect * ) * * result // => [ 1, 1, 1, 1, 1 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category repetition * @since 2.0.0 */ ( schedule: | Schedule.Schedule | (( $: (_: Schedule.Schedule) => Schedule.Schedule ) => Schedule.Schedule) ): (self: Stream) => Stream /** * Repeats the entire stream according to the provided schedule. * * **Example** (Repeating a stream on a schedule) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function* () { * const result = yield* Stream.make(1).pipe( * Stream.repeat(Schedule.recurs(4)), * Stream.runCollect * ) * * result // => [ 1, 1, 1, 1, 1 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category repetition * @since 2.0.0 */ ( self: Stream, schedule: | Schedule.Schedule | (( $: (_: Schedule.Schedule) => Schedule.Schedule ) => Schedule.Schedule) ): Stream } = dual(2, ( self: Stream, schedule: | Schedule.Schedule | (( $: (_: Schedule.Schedule) => Schedule.Schedule ) => Schedule.Schedule) ): Stream => fromChannel(Channel.repeat(self.channel, schedule))) /** * Schedules the stream's elements according to the provided schedule. * * **Example** (Scheduling stream elements) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.schedule(Schedule.recurs(3)), * Stream.runCollect * ) * * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category rate limiting * @since 2.0.0 */ export const schedule: { /** * Schedules the stream's elements according to the provided schedule. * * **Example** (Scheduling stream elements) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.schedule(Schedule.recurs(3)), * Stream.runCollect * ) * * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category rate limiting * @since 2.0.0 */ (schedule: Schedule.Schedule, E2, R2>): (self: Stream) => Stream /** * Schedules the stream's elements according to the provided schedule. * * **Example** (Scheduling stream elements) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.schedule(Schedule.recurs(3)), * Stream.runCollect * ) * * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category rate limiting * @since 2.0.0 */ (self: Stream, schedule: Schedule.Schedule, E2, R2>): Stream } = dual(2, ( self: Stream, schedule: Schedule.Schedule, E2, R2> ): Stream => self.channel.pipe( Channel.flattenArray, Channel.schedule(schedule), Channel.map(Arr.of), fromChannel )) /** * Ends the stream if it does not produce a value within the specified duration. * * **Example** (Timing out a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1).pipe( * Stream.concat(Stream.never), * Stream.timeout("1 second"), * Stream.runCollect * ) * values // => [ 1 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category delays & timeouts * @since 2.0.0 */ export const timeout: { /** * Ends the stream if it does not produce a value within the specified duration. * * **Example** (Timing out a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1).pipe( * Stream.concat(Stream.never), * Stream.timeout("1 second"), * Stream.runCollect * ) * values // => [ 1 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category delays & timeouts * @since 2.0.0 */ (duration: Duration.Input): (self: Stream) => Stream /** * Ends the stream if it does not produce a value within the specified duration. * * **Example** (Timing out a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1).pipe( * Stream.concat(Stream.never), * Stream.timeout("1 second"), * Stream.runCollect * ) * values // => [ 1 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category delays & timeouts * @since 2.0.0 */ (self: Stream, duration: Duration.Input): Stream } = dual( 2, (self: Stream, duration: Duration.Input): Stream => timeoutOrElse(self, { duration, orElse: () => empty }) ) /** * Switches to a fallback stream if this stream does not emit a value within * the specified duration. * * **When to use** * * Use when a stream should continue with another stream if an upstream pull * waits longer than the allowed duration. * * **Details** * * The timeout is checked for each pull. A zero duration uses `orElse` * immediately, while an infinite duration leaves the original stream * unchanged. * * **Gotchas** * * The fallback stream is not timed after the switch. * * @see {@link timeout} for ending the stream instead of switching to a fallback stream * * @stability stable * @category delays & timeouts * @since 4.0.0 */ export const timeoutOrElse: { /** * Switches to a fallback stream if this stream does not emit a value within * the specified duration. * * **When to use** * * Use when a stream should continue with another stream if an upstream pull * waits longer than the allowed duration. * * **Details** * * The timeout is checked for each pull. A zero duration uses `orElse` * immediately, while an infinite duration leaves the original stream * unchanged. * * **Gotchas** * * The fallback stream is not timed after the switch. * * @see {@link timeout} for ending the stream instead of switching to a fallback stream * * @stability stable * @category delays & timeouts * @since 4.0.0 */ ( options: { readonly duration: Duration.Input readonly orElse: () => Stream } ): (self: Stream) => Stream /** * Switches to a fallback stream if this stream does not emit a value within * the specified duration. * * **When to use** * * Use when a stream should continue with another stream if an upstream pull * waits longer than the allowed duration. * * **Details** * * The timeout is checked for each pull. A zero duration uses `orElse` * immediately, while an infinite duration leaves the original stream * unchanged. * * **Gotchas** * * The fallback stream is not timed after the switch. * * @see {@link timeout} for ending the stream instead of switching to a fallback stream * * @stability stable * @category delays & timeouts * @since 4.0.0 */ ( self: Stream, options: { readonly duration: Duration.Input readonly orElse: () => Stream } ): Stream } = dual( 2, ( self: Stream, options: { readonly duration: Duration.Input readonly orElse: () => Stream } ): Stream => { const duration = Duration.fromInputUnsafe(options.duration) if (!Duration.isFinite(duration)) return self if (Duration.isZero(duration)) return suspend(options.orElse) const timeoutSymbol = Symbol() return catchCause( suspend(() => { const parent = Fiber.getCurrent()! const clock = parent.getRef(Clock) const durationMs = Duration.toMillis(duration) let deadline: number | undefined = undefined const latch = Latch.makeUnsafe(false) return merge( transformPull(self, (pull, _scope) => Effect.suspend(() => { deadline = clock.currentTimeMillisUnsafe() + durationMs latch.openUnsafe() return pull }).pipe( Effect.map((arr) => { latch.closeUnsafe() deadline = undefined return arr }), Effect.succeed )), fromEffectDrain(Effect.gen(function*() { while (true) { yield* latch.await if (deadline === undefined) continue yield* Effect.sleep(deadline - clock.currentTimeMillisUnsafe()) if (deadline === undefined) continue const remaining = deadline - clock.currentTimeMillisUnsafe() if (remaining > 0) continue return yield* Effect.die(timeoutSymbol) } })), { haltStrategy: "left" } ) }), (cause): Stream => { const isTimeout = cause.reasons.find((r) => r._tag === "Die" && r.defect === timeoutSymbol) if (isTimeout) return options.orElse() return failCause(cause as Cause.Cause) } ) } ) /** * Repeats each element of the stream according to the provided schedule, * including the original emission. * * **Example** (Repeating stream elements) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make("A", "B", "C").pipe( * Stream.repeatElements(Schedule.recurs(1)), * Stream.runCollect * ) * values // => [ 'A', 'A', 'B', 'B', 'C', 'C' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category repetition * @since 2.0.0 */ export const repeatElements: { /** * Repeats each element of the stream according to the provided schedule, * including the original emission. * * **Example** (Repeating stream elements) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make("A", "B", "C").pipe( * Stream.repeatElements(Schedule.recurs(1)), * Stream.runCollect * ) * values // => [ 'A', 'A', 'B', 'B', 'C', 'C' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category repetition * @since 2.0.0 */ (schedule: Schedule.Schedule): (self: Stream) => Stream /** * Repeats each element of the stream according to the provided schedule, * including the original emission. * * **Example** (Repeating stream elements) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make("A", "B", "C").pipe( * Stream.repeatElements(Schedule.recurs(1)), * Stream.runCollect * ) * values // => [ 'A', 'A', 'B', 'B', 'C', 'C' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category repetition * @since 2.0.0 */ (self: Stream, schedule: Schedule.Schedule): Stream } = dual( 2, ( self: Stream, schedule: Schedule.Schedule ): Stream => fromChannel(Channel.fromTransform((upstream, scope) => Effect.map( Channel.toTransform(Channel.flattenArray(self.channel))(upstream, scope), (pullElement) => { let pullRepeat: Pull.Pull, E | E2, void, R | R2> | undefined = undefined const pull: Pull.Pull< Arr.NonEmptyReadonlyArray, E, void, R | R2 > = Effect.gen(function*() { const element = yield* pullElement const chunk = Arr.of(element) const step = yield* Schedule.toStepWithSleep(schedule) pullRepeat = step(element).pipe( Effect.as(chunk), Pull.catchDone((_) => { pullRepeat = undefined return pull }) ) return chunk }) return Effect.suspend(() => pullRepeat ?? pull) } ) )) ) /** * Repeats this stream forever. * * **Example** (Repeating a stream forever) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make("A", "B").pipe( * Stream.forever, * Stream.take(5) * ) * * const program = Effect.gen(function*() { * const output = yield* Stream.runCollect(stream) * output // => [ 'A', 'B', 'A', 'B', 'A' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category repetition * @since 2.0.0 */ export const forever = (self: Stream): Stream => fromChannel(Channel.forever(self.channel)) /** * Flattens the iterables emitted by this stream into the stream's structure. * * **Example** (Flattening iterable values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.make([1, 2], [3, 4]).pipe(Stream.flattenIterable) * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 4.0.0 */ export const flattenIterable = (self: Stream, E, R>): Stream => flatMap(self, fromIterable) /** * Unwraps `Take` values, emitting elements from non-empty arrays and ending or * failing when the `Exit` signals completion. * * **Example** (Flattening Take values) * * ```ts import.meta.vitest * import { Array, Effect, Exit, Stream } from "effect" * * const program = Effect.gen(function*() { * const takes = Stream.make( * Array.make(1, 2), * Array.make(3), * Exit.succeed(undefined) * ) * * const values = yield* Stream.flattenTake(takes).pipe(Stream.runCollect) * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const flattenTake = (self: Stream, E2, R>): Stream => self.channel.pipe( Channel.flattenArray, Channel.flattenTake, fromChannel ) /** * Concatenates two streams, emitting all elements from the first stream * followed by all elements from the second stream. * * **Example** (Concatenating streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.concat(Stream.make(1, 2, 3), Stream.make(4, 5, 6)) * * await Effect.runPromise(Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 3, 4, 5, 6 ] * })) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const concat: { /** * Concatenates two streams, emitting all elements from the first stream * followed by all elements from the second stream. * * **Example** (Concatenating streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.concat(Stream.make(1, 2, 3), Stream.make(4, 5, 6)) * * await Effect.runPromise(Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 3, 4, 5, 6 ] * })) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ (that: Stream): (self: Stream) => Stream /** * Concatenates two streams, emitting all elements from the first stream * followed by all elements from the second stream. * * **Example** (Concatenating streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.concat(Stream.make(1, 2, 3), Stream.make(4, 5, 6)) * * await Effect.runPromise(Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 3, 4, 5, 6 ] * })) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ (self: Stream, that: Stream): Stream } = dual( 2, (self: Stream, that: Stream): Stream => { const stream = fromChannel( Channel.flatten(Channel.fromIterator(() => concatChannels(self, that))) ) concatParts.set(stream, [self, that]) return stream } ) // The operands of each `concat` result, so a chain of `concat` calls can run as // a single layer instead of one layer per call const concatParts = new WeakMap, readonly [Stream, Stream]>() // Yields the channel of each non-`concat` stream in a chain, in order. A // `suspend` part is evaluated when it is reached and its result joins the chain function* concatChannels(self: Stream, that: Stream) { const stack = [that, self] while (stack.length > 0) { const stream = stack.pop()! const parts = concatParts.get(stream) if (parts !== undefined) { stack.push(parts[1], parts[0]) continue } const evaluate = suspendedStreams.get(stream) if (evaluate === undefined) { yield stream.channel continue } // Evaluated here rather than in an effect, so retrying an interrupted pull // cannot evaluate it twice. `Channel.empty` keeps a yield point try { stack.push(evaluate()) } catch (defect) { stack.push(die(defect)) } yield Channel.empty } } /** * Prepends the values from the provided iterable before the stream's elements. * * **Example** (Prepending values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(3, 4).pipe( * Stream.prepend([1, 2]), * Stream.runCollect * ) * * values // => [ 1, 2, 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const prepend: { /** * Prepends the values from the provided iterable before the stream's elements. * * **Example** (Prepending values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(3, 4).pipe( * Stream.prepend([1, 2]), * Stream.runCollect * ) * * values // => [ 1, 2, 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ (values: Iterable): (self: Stream) => Stream /** * Prepends the values from the provided iterable before the stream's elements. * * **Example** (Prepending values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(3, 4).pipe( * Stream.prepend([1, 2]), * Stream.runCollect * ) * * values // => [ 1, 2, 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ (self: Stream, values: Iterable): Stream } = dual(2, ( self: Stream, values: Iterable ): Stream => concat(fromIterable(values), self)) /** * Merges two streams, emitting elements from both as they arrive. * * **Details** * * By default, the merged stream ends when both streams end. Use * `haltStrategy` to change the termination behavior. * * **Example** (Merging stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const fast = Stream.make(1, 2, 3) * const slow = Stream.fromEffect(Effect.delay(Effect.succeed(4), "50 millis")) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(Stream.merge(fast, slow)) * result // => [ 1, 2, 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ export const merge: { /** * Merges two streams, emitting elements from both as they arrive. * * **Details** * * By default, the merged stream ends when both streams end. Use * `haltStrategy` to change the termination behavior. * * **Example** (Merging stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const fast = Stream.make(1, 2, 3) * const slow = Stream.fromEffect(Effect.delay(Effect.succeed(4), "50 millis")) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(Stream.merge(fast, slow)) * result // => [ 1, 2, 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ ( that: Stream, options?: { readonly haltStrategy?: HaltStrategy | undefined } | undefined ): (self: Stream) => Stream /** * Merges two streams, emitting elements from both as they arrive. * * **Details** * * By default, the merged stream ends when both streams end. Use * `haltStrategy` to change the termination behavior. * * **Example** (Merging stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const fast = Stream.make(1, 2, 3) * const slow = Stream.fromEffect(Effect.delay(Effect.succeed(4), "50 millis")) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(Stream.merge(fast, slow)) * result // => [ 1, 2, 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ ( self: Stream, that: Stream, options?: { readonly haltStrategy?: HaltStrategy | undefined } | undefined ): Stream } = dual( (args) => isStream(args[0]) && isStream(args[1]), ( self: Stream, that: Stream, options?: { readonly haltStrategy?: HaltStrategy | undefined } | undefined ): Stream => fromChannel(Channel.merge(toChannel(self), toChannel(that), options)) ) /** * Merges this stream with a background effect, keeping the stream's elements. * * **When to use** * * Use when an effect should run concurrently for the lifetime of a stream while * only the stream's elements remain in the output. * * **Details** * * The effect runs concurrently, fails the stream if it fails, and is interrupted * when the stream completes. * * **Example** (Merging with a background effect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.mergeEffect(Effect.sync(() => events.push("side task"))), * Stream.runCollect * ) * * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * events // => ["side task"] * ``` * * @stability stable * @category merging * @since 4.0.0 */ export const mergeEffect: { /** * Merges this stream with a background effect, keeping the stream's elements. * * **When to use** * * Use when an effect should run concurrently for the lifetime of a stream while * only the stream's elements remain in the output. * * **Details** * * The effect runs concurrently, fails the stream if it fails, and is interrupted * when the stream completes. * * **Example** (Merging with a background effect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.mergeEffect(Effect.sync(() => events.push("side task"))), * Stream.runCollect * ) * * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * events // => ["side task"] * ``` * * @stability stable * @category merging * @since 4.0.0 */ (effect: Effect.Effect): (self: Stream) => Stream /** * Merges this stream with a background effect, keeping the stream's elements. * * **When to use** * * Use when an effect should run concurrently for the lifetime of a stream while * only the stream's elements remain in the output. * * **Details** * * The effect runs concurrently, fails the stream if it fails, and is interrupted * when the stream completes. * * **Example** (Merging with a background effect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.mergeEffect(Effect.sync(() => events.push("side task"))), * Stream.runCollect * ) * * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * events // => ["side task"] * ``` * * @stability stable * @category merging * @since 4.0.0 */ (self: Stream, effect: Effect.Effect): Stream } = dual( 2, (self: Stream, effect: Effect.Effect): Stream => self.channel.pipe( Channel.mergeEffect(effect), fromChannel ) ) /** * Merges this stream and the specified stream together, tagging values from the * left stream as `Result.succeed` and values from the right stream as `Result.fail`. * * **When to use** * * Use when values from both streams should be emitted and downstream code needs * left values wrapped as successful `Result` values and right values wrapped as * failed `Result` values. * * **Example** (Merging streams into results) * * ```ts import.meta.vitest * import { Effect, Result, Stream } from "effect" * * const left = Stream.fromEffect(Effect.succeed("left")) * const right = Stream.fromEffect(Effect.delay(Effect.succeed("right"), "10 millis")) * * const merged = left.pipe( * Stream.mergeResult(right), * Stream.map( * Result.match({ * onFailure: (value) => `right:${value}`, * onSuccess: (value) => `left:${value}` * }) * ) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(merged) * result // => [ 'left:left', 'right:right' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 4.0.0 */ export const mergeResult: { /** * Merges this stream and the specified stream together, tagging values from the * left stream as `Result.succeed` and values from the right stream as `Result.fail`. * * **When to use** * * Use when values from both streams should be emitted and downstream code needs * left values wrapped as successful `Result` values and right values wrapped as * failed `Result` values. * * **Example** (Merging streams into results) * * ```ts import.meta.vitest * import { Effect, Result, Stream } from "effect" * * const left = Stream.fromEffect(Effect.succeed("left")) * const right = Stream.fromEffect(Effect.delay(Effect.succeed("right"), "10 millis")) * * const merged = left.pipe( * Stream.mergeResult(right), * Stream.map( * Result.match({ * onFailure: (value) => `right:${value}`, * onSuccess: (value) => `left:${value}` * }) * ) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(merged) * result // => [ 'left:left', 'right:right' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 4.0.0 */ (that: Stream): (self: Stream) => Stream, E2 | E, R2 | R> /** * Merges this stream and the specified stream together, tagging values from the * left stream as `Result.succeed` and values from the right stream as `Result.fail`. * * **When to use** * * Use when values from both streams should be emitted and downstream code needs * left values wrapped as successful `Result` values and right values wrapped as * failed `Result` values. * * **Example** (Merging streams into results) * * ```ts import.meta.vitest * import { Effect, Result, Stream } from "effect" * * const left = Stream.fromEffect(Effect.succeed("left")) * const right = Stream.fromEffect(Effect.delay(Effect.succeed("right"), "10 millis")) * * const merged = left.pipe( * Stream.mergeResult(right), * Stream.map( * Result.match({ * onFailure: (value) => `right:${value}`, * onSuccess: (value) => `left:${value}` * }) * ) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(merged) * result // => [ 'left:left', 'right:right' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 4.0.0 */ (self: Stream, that: Stream): Stream, E | E2, R | R2> } = dual( 2, ( self: Stream, that: Stream ): Stream, E | E2, R | R2> => merge( map(self, Result.succeed), map(that, Result.fail) ) ) /** * Merges two streams while emitting only the values from the left stream. * * **When to use** * * Use when the right stream is needed for its effects or failures, but downstream * consumers should only receive values from the left stream. * * **Details** * * The right stream still runs for its effects, and any failures from the right * stream are propagated. The merged stream completes when the left stream * completes, interrupting the right stream. * * **Example** (Merging streams while keeping left values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const left = Stream.make(1, 2) * const right = Stream.make("a", "b") * const values = yield* left.pipe(Stream.mergeLeft(right), Stream.runCollect) * values // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ export const mergeLeft: { /** * Merges two streams while emitting only the values from the left stream. * * **When to use** * * Use when the right stream is needed for its effects or failures, but downstream * consumers should only receive values from the left stream. * * **Details** * * The right stream still runs for its effects, and any failures from the right * stream are propagated. The merged stream completes when the left stream * completes, interrupting the right stream. * * **Example** (Merging streams while keeping left values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const left = Stream.make(1, 2) * const right = Stream.make("a", "b") * const values = yield* left.pipe(Stream.mergeLeft(right), Stream.runCollect) * values // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ (right: Stream): (left: Stream) => Stream /** * Merges two streams while emitting only the values from the left stream. * * **When to use** * * Use when the right stream is needed for its effects or failures, but downstream * consumers should only receive values from the left stream. * * **Details** * * The right stream still runs for its effects, and any failures from the right * stream are propagated. The merged stream completes when the left stream * completes, interrupting the right stream. * * **Example** (Merging streams while keeping left values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const left = Stream.make(1, 2) * const right = Stream.make("a", "b") * const values = yield* left.pipe(Stream.mergeLeft(right), Stream.runCollect) * values // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ (left: Stream, right: Stream): Stream } = dual( 2, (left: Stream, right: Stream): Stream => mergeEffect(left, runDrain(right)) ) /** * Merges this stream and the specified stream together, emitting only the * values from the right stream while the left stream runs for its effects. * * **When to use** * * Use when the left stream is needed for its effects or failures, but downstream * consumers should only receive values from the right stream. * * **Details** * * The merged stream ends when the right stream completes, interrupting the * left stream. Failures from the left stream still fail the merged stream. * * **Example** (Merging streams while keeping right values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const left = Stream.make("left-1", "left-2").pipe( * Stream.tap(() => Effect.sync(() => undefined)) * ) * const right = Stream.make(1, 2) * * const merged = Stream.mergeRight(left, right) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(merged) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ export const mergeRight: { /** * Merges this stream and the specified stream together, emitting only the * values from the right stream while the left stream runs for its effects. * * **When to use** * * Use when the left stream is needed for its effects or failures, but downstream * consumers should only receive values from the right stream. * * **Details** * * The merged stream ends when the right stream completes, interrupting the * left stream. Failures from the left stream still fail the merged stream. * * **Example** (Merging streams while keeping right values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const left = Stream.make("left-1", "left-2").pipe( * Stream.tap(() => Effect.sync(() => undefined)) * ) * const right = Stream.make(1, 2) * * const merged = Stream.mergeRight(left, right) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(merged) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ (right: Stream): (left: Stream) => Stream /** * Merges this stream and the specified stream together, emitting only the * values from the right stream while the left stream runs for its effects. * * **When to use** * * Use when the left stream is needed for its effects or failures, but downstream * consumers should only receive values from the right stream. * * **Details** * * The merged stream ends when the right stream completes, interrupting the * left stream. Failures from the left stream still fail the merged stream. * * **Example** (Merging streams while keeping right values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const left = Stream.make("left-1", "left-2").pipe( * Stream.tap(() => Effect.sync(() => undefined)) * ) * const right = Stream.make(1, 2) * * const merged = Stream.mergeRight(left, right) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(merged) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ (left: Stream, right: Stream): Stream } = dual( 2, (left: Stream, right: Stream): Stream => mergeEffect(right, runDrain(left)) ) /** * Merges a collection of streams, running up to the specified number concurrently. * * **When to use** * * Use to merge an iterable of already-created streams while bounding how many * inner streams may run at the same time. * * **Details** * * The `concurrency` option is required and may be a number or `"unbounded"`. * `bufferSize` controls buffering between inner streams, and outputs are * emitted as they arrive under concurrent merging. * * **Example** (Merging streams with bounded concurrency) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const streams = [ * Stream.fromEffect(Effect.delay(Effect.succeed("A"), "20 millis")), * Stream.fromEffect(Effect.delay(Effect.succeed("B"), "10 millis")) * ] * * const program = Effect.gen(function*() { * const values = yield* Stream.mergeAll(streams, { concurrency: 2 }).pipe( * Stream.runCollect * ) * values // => [ 'B', 'A' ] * }) * * await Effect.runPromise(program) * ``` * * @see {@link merge} for merging exactly two streams and choosing a halt strategy * @see {@link flatten} for flattening a stream that already emits streams * * @stability stable * @category merging * @since 2.0.0 */ export const mergeAll: { /** * Merges a collection of streams, running up to the specified number concurrently. * * **When to use** * * Use to merge an iterable of already-created streams while bounding how many * inner streams may run at the same time. * * **Details** * * The `concurrency` option is required and may be a number or `"unbounded"`. * `bufferSize` controls buffering between inner streams, and outputs are * emitted as they arrive under concurrent merging. * * **Example** (Merging streams with bounded concurrency) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const streams = [ * Stream.fromEffect(Effect.delay(Effect.succeed("A"), "20 millis")), * Stream.fromEffect(Effect.delay(Effect.succeed("B"), "10 millis")) * ] * * const program = Effect.gen(function*() { * const values = yield* Stream.mergeAll(streams, { concurrency: 2 }).pipe( * Stream.runCollect * ) * values // => [ 'B', 'A' ] * }) * * await Effect.runPromise(program) * ``` * * @see {@link merge} for merging exactly two streams and choosing a halt strategy * @see {@link flatten} for flattening a stream that already emits streams * * @stability stable * @category merging * @since 2.0.0 */ ( options: { readonly concurrency: Concurrency readonly bufferSize?: number | undefined } ): (streams: Iterable>) => Stream /** * Merges a collection of streams, running up to the specified number concurrently. * * **When to use** * * Use to merge an iterable of already-created streams while bounding how many * inner streams may run at the same time. * * **Details** * * The `concurrency` option is required and may be a number or `"unbounded"`. * `bufferSize` controls buffering between inner streams, and outputs are * emitted as they arrive under concurrent merging. * * **Example** (Merging streams with bounded concurrency) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const streams = [ * Stream.fromEffect(Effect.delay(Effect.succeed("A"), "20 millis")), * Stream.fromEffect(Effect.delay(Effect.succeed("B"), "10 millis")) * ] * * const program = Effect.gen(function*() { * const values = yield* Stream.mergeAll(streams, { concurrency: 2 }).pipe( * Stream.runCollect * ) * values // => [ 'B', 'A' ] * }) * * await Effect.runPromise(program) * ``` * * @see {@link merge} for merging exactly two streams and choosing a halt strategy * @see {@link flatten} for flattening a stream that already emits streams * * @stability stable * @category merging * @since 2.0.0 */ ( streams: Iterable>, options: { readonly concurrency: Concurrency readonly bufferSize?: number | undefined } ): Stream } = dual(2, ( streams: Iterable>, options: { readonly concurrency: Concurrency readonly bufferSize?: number | undefined } ): Stream => flatten(fromIterable(streams), options)) /** * Creates the cartesian product of two streams, running the `right` stream for * each element in the `left` stream. * * **Details** * * See also `Stream.zip` for the more common point-wise variant. * * **Example** (Computing cartesian products) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const left = Stream.make(1, 2) * const right = Stream.make("a", "b") * const values = yield* Stream.runCollect(Stream.cross(left, right)) * values // => [ [ 1, 'a' ], [ 1, 'b' ], [ 2, 'a' ], [ 2, 'b' ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ export const cross: { /** * Creates the cartesian product of two streams, running the `right` stream for * each element in the `left` stream. * * **Details** * * See also `Stream.zip` for the more common point-wise variant. * * **Example** (Computing cartesian products) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const left = Stream.make(1, 2) * const right = Stream.make("a", "b") * const values = yield* Stream.runCollect(Stream.cross(left, right)) * values // => [ [ 1, 'a' ], [ 1, 'b' ], [ 2, 'a' ], [ 2, 'b' ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (right: Stream): (left: Stream) => Stream<[AL, AR], EL | ER, RL | RR> /** * Creates the cartesian product of two streams, running the `right` stream for * each element in the `left` stream. * * **Details** * * See also `Stream.zip` for the more common point-wise variant. * * **Example** (Computing cartesian products) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const left = Stream.make(1, 2) * const right = Stream.make("a", "b") * const values = yield* Stream.runCollect(Stream.cross(left, right)) * values // => [ [ 1, 'a' ], [ 1, 'b' ], [ 2, 'a' ], [ 2, 'b' ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (left: Stream, right: Stream): Stream<[AL, AR], EL | ER, RL | RR> } = dual(2, ( left: Stream, right: Stream ): Stream<[AL, AR], EL | ER, RL | RR> => crossWith(left, right, (l, r) => [l, r])) /** * Creates a cartesian product of elements from two streams using a function. * * **Details** * * The `right` stream is rerun for every element in the `left` stream. * * See also `Stream.zipWith` for the more common point-wise variant. * * **Example** (Combining cartesian products) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const left = Stream.make(1, 2) * const right = Stream.make("a", "b") * const combined = Stream.crossWith(left, right, (n, s) => `${n}-${s}`) * const result = yield* Stream.runCollect(combined) * result // => [ '1-a', '1-b', '2-a', '2-b' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ export const crossWith: { /** * Creates a cartesian product of elements from two streams using a function. * * **Details** * * The `right` stream is rerun for every element in the `left` stream. * * See also `Stream.zipWith` for the more common point-wise variant. * * **Example** (Combining cartesian products) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const left = Stream.make(1, 2) * const right = Stream.make("a", "b") * const combined = Stream.crossWith(left, right, (n, s) => `${n}-${s}`) * const result = yield* Stream.runCollect(combined) * result // => [ '1-a', '1-b', '2-a', '2-b' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (right: Stream, f: (left: AL, right: AR) => A): (left: Stream) => Stream /** * Creates a cartesian product of elements from two streams using a function. * * **Details** * * The `right` stream is rerun for every element in the `left` stream. * * See also `Stream.zipWith` for the more common point-wise variant. * * **Example** (Combining cartesian products) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const left = Stream.make(1, 2) * const right = Stream.make("a", "b") * const combined = Stream.crossWith(left, right, (n, s) => `${n}-${s}`) * const result = yield* Stream.runCollect(combined) * result // => [ '1-a', '1-b', '2-a', '2-b' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ ( left: Stream, right: Stream, f: (left: AL, right: AR) => A ): Stream } = dual(3, ( left: Stream, right: Stream, f: (left: AL, right: AR) => A ): Stream => flatMap(left, (l) => map(right, (r) => f(l, r)))) /** * Zips two streams point-wise with a combining function, ending when either stream ends. * * **Example** (Zipping streams with a function) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream1 = Stream.make(1, 2, 3, 4, 5, 6) * const stream2 = Stream.make("a", "b", "c") * * const zipped = Stream.zipWith(stream1, stream2, (n, s) => `${n}-${s}`) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(zipped) * result // => [ '1-a', '2-b', '3-c' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ export const zipWith: { /** * Zips two streams point-wise with a combining function, ending when either stream ends. * * **Example** (Zipping streams with a function) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream1 = Stream.make(1, 2, 3, 4, 5, 6) * const stream2 = Stream.make("a", "b", "c") * * const zipped = Stream.zipWith(stream1, stream2, (n, s) => `${n}-${s}`) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(zipped) * result // => [ '1-a', '2-b', '3-c' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (right: Stream, f: (left: AL, right: AR) => A): (left: Stream) => Stream /** * Zips two streams point-wise with a combining function, ending when either stream ends. * * **Example** (Zipping streams with a function) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream1 = Stream.make(1, 2, 3, 4, 5, 6) * const stream2 = Stream.make("a", "b", "c") * * const zipped = Stream.zipWith(stream1, stream2, (n, s) => `${n}-${s}`) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(zipped) * result // => [ '1-a', '2-b', '3-c' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ ( left: Stream, right: Stream, f: (left: AL, right: AR) => A ): Stream } = dual(3, ( left: Stream, right: Stream, f: (left: AL, right: AR) => A ): Stream => suspend(() => { // Track consumed elements with cursors instead of slicing leftovers let leftIndex = 0 let rightIndex = 0 return zipWithArray(left, right, (leftArr, rightArr) => { const length = Math.min(leftArr.length - leftIndex, rightArr.length - rightIndex) const output = Arr.makeBy(length, (i) => f(leftArr[leftIndex + i], rightArr[rightIndex + i])) leftIndex += length rightIndex += length const leftDone = leftIndex === leftArr.length const rightDone = rightIndex === rightArr.length if (leftDone) leftIndex = 0 if (rightDone) rightIndex = 0 return [output, leftDone ? [] : leftArr, rightDone ? [] : rightArr] }) })) /** * Zips two streams by applying a function to non-empty arrays of elements. * * **Details** * * The function returns output plus leftover arrays that carry into the next pull. * * **Example** (Zipping stream chunks) * * ```ts import.meta.vitest * import { Array, Effect, Stream } from "effect" * * const left = Stream.fromArrays([1, 2, 3], [4, 5]) * const right = Stream.fromArrays(["a", "b"], ["c", "d", "e"]) * * const zipped = Stream.zipWithArray(left, right, (leftChunk, rightChunk) => { * const minLength = Math.min(leftChunk.length, rightChunk.length) * const output = Array.makeBy(minLength, (i) => [leftChunk[i], rightChunk[i]] as const) * * return [output, leftChunk.slice(minLength), rightChunk.slice(minLength)] * }) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(zipped) * result // => [ [ 1, 'a' ], [ 2, 'b' ], [ 3, 'c' ], [ 4, 'd' ], [ 5, 'e' ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 4.0.0 */ export const zipWithArray: { /** * Zips two streams by applying a function to non-empty arrays of elements. * * **Details** * * The function returns output plus leftover arrays that carry into the next pull. * * **Example** (Zipping stream chunks) * * ```ts import.meta.vitest * import { Array, Effect, Stream } from "effect" * * const left = Stream.fromArrays([1, 2, 3], [4, 5]) * const right = Stream.fromArrays(["a", "b"], ["c", "d", "e"]) * * const zipped = Stream.zipWithArray(left, right, (leftChunk, rightChunk) => { * const minLength = Math.min(leftChunk.length, rightChunk.length) * const output = Array.makeBy(minLength, (i) => [leftChunk[i], rightChunk[i]] as const) * * return [output, leftChunk.slice(minLength), rightChunk.slice(minLength)] * }) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(zipped) * result // => [ [ 1, 'a' ], [ 2, 'b' ], [ 3, 'c' ], [ 4, 'd' ], [ 5, 'e' ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 4.0.0 */ ( right: Stream, f: ( left: Arr.NonEmptyReadonlyArray, right: Arr.NonEmptyReadonlyArray ) => readonly [ output: Arr.NonEmptyReadonlyArray, leftoverLeft: ReadonlyArray, leftoverRight: ReadonlyArray ] ): (left: Stream) => Stream /** * Zips two streams by applying a function to non-empty arrays of elements. * * **Details** * * The function returns output plus leftover arrays that carry into the next pull. * * **Example** (Zipping stream chunks) * * ```ts import.meta.vitest * import { Array, Effect, Stream } from "effect" * * const left = Stream.fromArrays([1, 2, 3], [4, 5]) * const right = Stream.fromArrays(["a", "b"], ["c", "d", "e"]) * * const zipped = Stream.zipWithArray(left, right, (leftChunk, rightChunk) => { * const minLength = Math.min(leftChunk.length, rightChunk.length) * const output = Array.makeBy(minLength, (i) => [leftChunk[i], rightChunk[i]] as const) * * return [output, leftChunk.slice(minLength), rightChunk.slice(minLength)] * }) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(zipped) * result // => [ [ 1, 'a' ], [ 2, 'b' ], [ 3, 'c' ], [ 4, 'd' ], [ 5, 'e' ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 4.0.0 */ ( left: Stream, right: Stream, f: ( left: Arr.NonEmptyReadonlyArray, right: Arr.NonEmptyReadonlyArray ) => readonly [ output: Arr.NonEmptyReadonlyArray, leftoverLeft: ReadonlyArray, leftoverRight: ReadonlyArray ] ): Stream } = dual(3, ( left: Stream, right: Stream, f: ( left: Arr.NonEmptyReadonlyArray, right: Arr.NonEmptyReadonlyArray ) => readonly [ output: Arr.NonEmptyReadonlyArray, leftoverLeft: ReadonlyArray, leftoverRight: ReadonlyArray ] ): Stream => fromChannel(Channel.fromTransformBracket(Effect.fnUntraced(function*(_, scope) { const pullLeft = yield* Channel.toPullScoped(left.channel, scope) const pullRight = yield* Channel.toPullScoped(right.channel, scope) const pullBoth = Effect.gen(function*() { const fiberLeft = yield* Effect.forkIn(pullLeft, scope) const fiberRight = yield* Effect.forkIn(pullRight, scope) return (yield* Fiber.joinAll([fiberLeft, fiberRight])) as [ Arr.NonEmptyReadonlyArray, Arr.NonEmptyReadonlyArray ] }) let leftArray: Arr.NonEmptyReadonlyArray | undefined let rightArray: Arr.NonEmptyReadonlyArray | undefined const pull: Effect.Effect< Arr.NonEmptyReadonlyArray, EL | ER | Cause.Done, RL | RR > = Effect.gen(function*() { const [left, right] = leftArray ? [leftArray, rightArray ?? (yield* pullRight)] : rightArray ? [yield* pullLeft, rightArray] : yield* pullBoth const [output, leftoverLeft, leftoverRight] = f(left, right) leftArray = Arr.isReadonlyArrayNonEmpty(leftoverLeft) ? leftoverLeft : undefined rightArray = Arr.isReadonlyArrayNonEmpty(leftoverRight) ? leftoverRight : undefined return output }) return pull })))) /** * Zips this stream with another point-wise and emits tuples of elements from * both streams. The new stream ends when either stream ends. * * **Example** (Zipping streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream1 = Stream.make(1, 2, 3) * const stream2 = Stream.make("a", "b", "c") * * const zipped = Stream.zip(stream1, stream2) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(zipped) * result // => [ [ 1, 'a' ], [ 2, 'b' ], [ 3, 'c' ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ export const zip: { /** * Zips this stream with another point-wise and emits tuples of elements from * both streams. The new stream ends when either stream ends. * * **Example** (Zipping streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream1 = Stream.make(1, 2, 3) * const stream2 = Stream.make("a", "b", "c") * * const zipped = Stream.zip(stream1, stream2) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(zipped) * result // => [ [ 1, 'a' ], [ 2, 'b' ], [ 3, 'c' ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (that: Stream): (self: Stream) => Stream<[A, A2], E2 | E, R2 | R> /** * Zips this stream with another point-wise and emits tuples of elements from * both streams. The new stream ends when either stream ends. * * **Example** (Zipping streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream1 = Stream.make(1, 2, 3) * const stream2 = Stream.make("a", "b", "c") * * const zipped = Stream.zip(stream1, stream2) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(zipped) * result // => [ [ 1, 'a' ], [ 2, 'b' ], [ 3, 'c' ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (self: Stream, that: Stream): Stream<[A, A2], E | E2, R | R2> } = dual( 2, ( self: Stream, that: Stream ): Stream<[A, A2], E | E2, R | R2> => zipWith(self, that, (a, a2) => [a, a2]) ) /** * Zips this stream with another point-wise and keeps only the values from * the left stream. * * **Details** * * The resulting stream ends when either side ends. * * **Example** (Zipping streams while keeping left values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream1 = Stream.make(1, 2, 3, 4) * const stream2 = Stream.make("a", "b") * * const program = Effect.gen(function*() { * const result = yield* Stream.zipLeft(stream1, stream2).pipe(Stream.runCollect) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ export const zipLeft: { /** * Zips this stream with another point-wise and keeps only the values from * the left stream. * * **Details** * * The resulting stream ends when either side ends. * * **Example** (Zipping streams while keeping left values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream1 = Stream.make(1, 2, 3, 4) * const stream2 = Stream.make("a", "b") * * const program = Effect.gen(function*() { * const result = yield* Stream.zipLeft(stream1, stream2).pipe(Stream.runCollect) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (right: Stream): (left: Stream) => Stream /** * Zips this stream with another point-wise and keeps only the values from * the left stream. * * **Details** * * The resulting stream ends when either side ends. * * **Example** (Zipping streams while keeping left values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream1 = Stream.make(1, 2, 3, 4) * const stream2 = Stream.make("a", "b") * * const program = Effect.gen(function*() { * const result = yield* Stream.zipLeft(stream1, stream2).pipe(Stream.runCollect) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (left: Stream, right: Stream): Stream } = dual( 2, ( left: Stream, right: Stream ): Stream => zipWith(left, right, (a) => a) ) /** * Zips this stream with another point-wise, keeping only right values and ending when either stream ends. * * **Example** (Zipping streams while keeping right values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream1 = Stream.make(1, 2) * const stream2 = Stream.make("a", "b", "c", "d") * * const program = Effect.gen(function*() { * const result = yield* Stream.zipRight(stream1, stream2).pipe(Stream.runCollect) * result // => [ 'a', 'b' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ export const zipRight: { /** * Zips this stream with another point-wise, keeping only right values and ending when either stream ends. * * **Example** (Zipping streams while keeping right values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream1 = Stream.make(1, 2) * const stream2 = Stream.make("a", "b", "c", "d") * * const program = Effect.gen(function*() { * const result = yield* Stream.zipRight(stream1, stream2).pipe(Stream.runCollect) * result // => [ 'a', 'b' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (right: Stream): (left: Stream) => Stream /** * Zips this stream with another point-wise, keeping only right values and ending when either stream ends. * * **Example** (Zipping streams while keeping right values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream1 = Stream.make(1, 2) * const stream2 = Stream.make("a", "b", "c", "d") * * const program = Effect.gen(function*() { * const result = yield* Stream.zipRight(stream1, stream2).pipe(Stream.runCollect) * result // => [ 'a', 'b' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (left: Stream, right: Stream): Stream } = dual( 2, ( left: Stream, right: Stream ): Stream => zipWith(left, right, (_, a) => a) ) /** * Zips this stream with another point-wise and emits tuples of elements from * both streams, flattening the left tuple. * * **Details** * * The new stream will end when one of the sides ends. * * **Example** (Zipping and flattening tuples) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream1 = Stream.make( * [1, "a"] as const, * [2, "b"] as const, * [3, "c"] as const * ) * const stream2 = Stream.make("x", "y", "z") * const result = yield* Stream.zipFlatten(stream1, stream2).pipe(Stream.runCollect) * * result // => [ [ 1, 'a', 'x' ], [ 2, 'b', 'y' ], [ 3, 'c', 'z' ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ export const zipFlatten: { /** * Zips this stream with another point-wise and emits tuples of elements from * both streams, flattening the left tuple. * * **Details** * * The new stream will end when one of the sides ends. * * **Example** (Zipping and flattening tuples) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream1 = Stream.make( * [1, "a"] as const, * [2, "b"] as const, * [3, "c"] as const * ) * const stream2 = Stream.make("x", "y", "z") * const result = yield* Stream.zipFlatten(stream1, stream2).pipe(Stream.runCollect) * * result // => [ [ 1, 'a', 'x' ], [ 2, 'b', 'y' ], [ 3, 'c', 'z' ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (that: Stream): , E, R>(self: Stream) => Stream<[...A, A2], E2 | E, R2 | R> /** * Zips this stream with another point-wise and emits tuples of elements from * both streams, flattening the left tuple. * * **Details** * * The new stream will end when one of the sides ends. * * **Example** (Zipping and flattening tuples) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream1 = Stream.make( * [1, "a"] as const, * [2, "b"] as const, * [3, "c"] as const * ) * const stream2 = Stream.make("x", "y", "z") * const result = yield* Stream.zipFlatten(stream1, stream2).pipe(Stream.runCollect) * * result // => [ [ 1, 'a', 'x' ], [ 2, 'b', 'y' ], [ 3, 'c', 'z' ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ , E, R, A2, E2, R2>(self: Stream, that: Stream): Stream<[...A, A2], E | E2, R | R2> } = dual( 2, , E, R, A2, E2, R2>( self: Stream, that: Stream ): Stream<[...A, A2], E | E2, R | R2> => zipWith(self, that, (a, a2) => [...a, a2]) ) /** * Zips this stream together with the index of elements. * * **Example** (Zipping elements with indices) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const indexed = yield* Stream.make("a", "b", "c", "d").pipe( * Stream.zipWithIndex, * Stream.runCollect * ) * indexed // => [ [ 'a', 0 ], [ 'b', 1 ], [ 'c', 2 ], [ 'd', 3 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ export const zipWithIndex = (self: Stream): Stream<[A, number], E, R> => map(self, (a, i) => [a, i]) /** * Zips each element with the next element, pairing the final element with * `Option.none()`. * * **Example** (Zipping elements with next values) * * ```ts import.meta.vitest * import { Effect, Option, Stream } from "effect" * * const stream = Stream.zipWithNext(Stream.make(1, 2, 3, 4)) * * await Effect.runPromise(Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [[1, Option.some(2)], [2, Option.some(3)], [3, Option.some(4)], [4, Option.none()]] * })) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ export const zipWithNext = (self: Stream): Stream<[A, Option.Option], E, R> => mapAccumArray(self, Option.none, (acc, arr) => { let i = 0 if (acc._tag === "None") { i = 1 acc = Option.some(arr[0]) as Option.Some } const pairs = Arr.empty<[A, Option.Option]>() for (; i < arr.length; i++) { const value = acc.value acc = Option.some(arr[i]) as Option.Some pairs.push([value, acc]) } return [acc, pairs] }, { onHalt(state) { return state._tag === "Some" ? [[state.value, Option.none()]] : [] } }) /** * Zips each element with its previous element, starting with `None`. * * **Example** (Zipping elements with previous values) * * ```ts import.meta.vitest * import { Effect, Option, Stream } from "effect" * * const stream = Stream.zipWithPrevious(Stream.make(1, 2, 3, 4)) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [[Option.none(), 1], [Option.some(1), 2], [Option.some(2), 3], [Option.some(3), 4]] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ export const zipWithPrevious = (self: Stream): Stream<[Option.Option, A], E, R> => mapAccumArray(self, Option.none, (acc, arr) => { const pairs = Arr.empty<[Option.Option, A]>() for (let i = 0; i < arr.length; i++) { const value = arr[i] pairs.push([acc, value]) acc = Option.some(arr[i]) } return [acc, pairs] }) /** * Zips each element with its previous and next values. * * **Example** (Zipping elements with neighbors) * * ```ts import.meta.vitest * import { Console, Effect, Option, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.zipWithPreviousAndNext, * Stream.runCollect * ) * values // => [[Option.none(), 1, Option.some(2)], [Option.some(1), 2, Option.some(3)], [Option.some(2), 3, Option.none()]] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ export const zipWithPreviousAndNext = ( self: Stream ): Stream<[Option.Option, A, Option.Option], E, R> => mapAccumArray(self, () => ({ prev: Option.none(), current: Option.none() }), (acc, arr) => { let i = 0 let current: A if (acc.current._tag === "None") { i = 1 current = arr[0] acc.current = Option.some(current) } else { current = acc.current.value } const pairs = Arr.empty<[Option.Option, A, Option.Option]>() for (; i < arr.length; i++) { const element = arr[i] acc.current = Option.some(element) as Option.Some pairs.push([acc.prev, current, acc.current]) acc.prev = Option.some(current) current = element } return [acc, pairs] }, { onHalt(acc) { return acc.current._tag === "Some" ? [[acc.prev, acc.current.value, Option.none()]] : [] } }) /** * Zips multiple streams so that when a value is emitted by any stream, it is * combined with the latest values from the other streams to produce a result. * * **When to use** * * Use when each stream should contribute its latest value after all streams have * emitted at least once. * * **Gotchas** * * Note: tracking the latest value is done on a per-array basis. That means * that emitted elements that are not the last value in arrays will never be * used for zipping. * * **Example** (Zipping latest values from many streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.zipLatestAll( * Stream.make(1, 2, 3).pipe(Stream.rechunk(1)), * Stream.make("a", "b", "c").pipe(Stream.rechunk(1)), * Stream.make(true, false, true).pipe(Stream.rechunk(1)) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [[1, "a", true], [2, "a", true], [2, "b", true], [2, "b", false], [3, "b", false], [3, "c", false], [3, "c", true]] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 3.3.0 */ export const zipLatestAll = >>( ...streams: T ): Stream< [T[number]] extends [never] ? never : { [K in keyof T]: T[K] extends Stream ? A : never }, [T[number]] extends [never] ? never : T[number] extends Stream ? _E : never, [T[number]] extends [never] ? never : T[number] extends Stream ? _R : never > => fromChannel(Channel.suspend(() => { const latest: Array = [] const emitted = new Set() const readyLatch = Latch.makeUnsafe() const emptyLatch = Latch.makeUnsafe() return Channel.mergeAll( Channel.fromArray( streams.map((s, i) => s.channel.pipe( Channel.flattenArray, Channel.mapEffect((a) => { latest[i] = a if (!emitted.has(i)) { emitted.add(i) if (emitted.size < streams.length) { return readyLatch.await as Effect.Effect } return Effect.as(readyLatch.open, Arr.of(latest.slice())) } return Effect.succeed(Arr.of(latest.slice())) }), Channel.filter(isNotUndefined), // An input ending without a value makes a complete tuple impossible. Channel.mapDoneEffect(() => emitted.has(i) ? Effect.void : emptyLatch.open) ) ) ), { concurrency: "unbounded", bufferSize: 0 } ).pipe(Channel.interruptWhen(emptyLatch.await)) })) as any /** * Combines two streams by emitting each new element with the latest value from the other stream. * * **When to use** * * Use when two streams should start emitting combined pairs after both have * produced at least one value. * * **Gotchas** * * Note: tracking the latest value is done on a per-array basis. That means * that emitted elements that are not the last value in arrays will never be * used for zipping. * * **Example** (Zipping latest values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.zipLatest( * Stream.make(1), * Stream.make("a") * ).pipe(Stream.runCollect) * * result // => [ [ 1, 'a' ] ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ export const zipLatest: { /** * Combines two streams by emitting each new element with the latest value from the other stream. * * **When to use** * * Use when two streams should start emitting combined pairs after both have * produced at least one value. * * **Gotchas** * * Note: tracking the latest value is done on a per-array basis. That means * that emitted elements that are not the last value in arrays will never be * used for zipping. * * **Example** (Zipping latest values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.zipLatest( * Stream.make(1), * Stream.make("a") * ).pipe(Stream.runCollect) * * result // => [ [ 1, 'a' ] ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (right: Stream): (left: Stream) => Stream<[AL, AR], EL | ER, RL | RR> /** * Combines two streams by emitting each new element with the latest value from the other stream. * * **When to use** * * Use when two streams should start emitting combined pairs after both have * produced at least one value. * * **Gotchas** * * Note: tracking the latest value is done on a per-array basis. That means * that emitted elements that are not the last value in arrays will never be * used for zipping. * * **Example** (Zipping latest values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.zipLatest( * Stream.make(1), * Stream.make("a") * ).pipe(Stream.runCollect) * * result // => [ [ 1, 'a' ] ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (left: Stream, right: Stream): Stream<[AL, AR], EL | ER, RL | RR> } = dual( 2, ( left: Stream, right: Stream ): Stream<[AL, AR], EL | ER, RL | RR> => zipLatestAll(left, right) ) /** * Combines the latest values from both streams whenever either emits, using * the provided function. * * **When to use** * * Use when two streams should start emitting custom combined values after both * have produced at least one value. * * **Gotchas** * * Note: tracking the latest value is done on a per-array basis. That means * that emitted elements that are not the last value in arrays will never be * used for zipping. * * **Example** (Zipping latest values with a function) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.rechunk(1), * Stream.zipLatestWith( * Stream.make(10, 20).pipe(Stream.rechunk(1)), * (n, m) => n + m * ), * Stream.runCollect * ) * * result // => [ 11, 12, 22, 23 ] * })) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ export const zipLatestWith: { /** * Combines the latest values from both streams whenever either emits, using * the provided function. * * **When to use** * * Use when two streams should start emitting custom combined values after both * have produced at least one value. * * **Gotchas** * * Note: tracking the latest value is done on a per-array basis. That means * that emitted elements that are not the last value in arrays will never be * used for zipping. * * **Example** (Zipping latest values with a function) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.rechunk(1), * Stream.zipLatestWith( * Stream.make(10, 20).pipe(Stream.rechunk(1)), * (n, m) => n + m * ), * Stream.runCollect * ) * * result // => [ 11, 12, 22, 23 ] * })) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ (right: Stream, f: (left: AL, right: AR) => A): (left: Stream) => Stream /** * Combines the latest values from both streams whenever either emits, using * the provided function. * * **When to use** * * Use when two streams should start emitting custom combined values after both * have produced at least one value. * * **Gotchas** * * Note: tracking the latest value is done on a per-array basis. That means * that emitted elements that are not the last value in arrays will never be * used for zipping. * * **Example** (Zipping latest values with a function) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.rechunk(1), * Stream.zipLatestWith( * Stream.make(10, 20).pipe(Stream.rechunk(1)), * (n, m) => n + m * ), * Stream.runCollect * ) * * result // => [ 11, 12, 22, 23 ] * })) * ``` * * @stability stable * @category zipping * @since 2.0.0 */ ( left: Stream, right: Stream, f: (left: AL, right: AR) => A ): Stream } = dual( 3, ( left: Stream, right: Stream, f: (left: AL, right: AR) => A ): Stream => map(zipLatestAll(left, right), ([a, a2]) => f(a, a2)) ) /** * Runs all streams concurrently until one stream emits its first value, then * mirrors that winning stream and interrupts the rest. * * **Details** * * Failures or completion from losing streams before a winner is chosen are * ignored unless every stream fails or completes before emitting. After a * winner is chosen, that stream's later failures are propagated. * * **Example** (Racing multiple streams) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.raceAll( * Stream.empty, * Stream.make(0, 1, 2) * ).pipe(Stream.runCollect) * result // => [ 0, 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category racing * @since 3.5.0 */ export const raceAll = >>( ...streams: S ): Stream, Error, Services> => fromChannel(Channel.fromTransform((_, scope) => Effect.sync(() => { let winner: | Pull.Pull>, Error, void, Services> | undefined const race = Effect.raceAll(streams.map((stream) => { const childScope = Scope.forkUnsafe(scope) return Channel.toPullScoped(stream.channel, childScope).pipe( Effect.flatMap((pull) => Effect.zip(Effect.succeed(pull), pull)), Effect.onExit((exit) => { if (exit._tag === "Success") { if (winner) { return Scope.close(childScope, exit) } winner = exit.value[0] return Effect.void } return Scope.close(childScope, exit) }), Effect.map(([, chunk]) => chunk) ) })) return Effect.suspend(() => winner ?? race) }) )) /** * Runs both streams concurrently until one stream emits its first value, then * mirrors that winning stream and interrupts the other. * * **Details** * * A failure or completion from one side before the other side emits does not * win the race unless both sides fail or complete before emitting. After a * winner is chosen, that stream's later failures are propagated. * * **Example** (Racing two streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.race( * Stream.empty, * Stream.make(0, 1, 2) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 0, 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category racing * @since 3.7.0 */ export const race: { /** * Runs both streams concurrently until one stream emits its first value, then * mirrors that winning stream and interrupts the other. * * **Details** * * A failure or completion from one side before the other side emits does not * win the race unless both sides fail or complete before emitting. After a * winner is chosen, that stream's later failures are propagated. * * **Example** (Racing two streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.race( * Stream.empty, * Stream.make(0, 1, 2) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 0, 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category racing * @since 3.7.0 */ (right: Stream): (left: Stream) => Stream /** * Runs both streams concurrently until one stream emits its first value, then * mirrors that winning stream and interrupts the other. * * **Details** * * A failure or completion from one side before the other side emits does not * win the race unless both sides fail or complete before emitting. After a * winner is chosen, that stream's later failures are propagated. * * **Example** (Racing two streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.race( * Stream.empty, * Stream.make(0, 1, 2) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 0, 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category racing * @since 3.7.0 */ (left: Stream, right: Stream): Stream } = dual(2, ( left: Stream, right: Stream ): Stream => raceAll(left, right)) /** * Filters a stream to the elements that satisfy a predicate. * * **Example** (Filtering stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3, 4).pipe( * Stream.filter((n) => n % 2 === 0) * ) * const values = yield* Stream.runCollect(stream) * values // => [ 2, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const filter: { /** * Filters a stream to the elements that satisfy a predicate. * * **Example** (Filtering stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3, 4).pipe( * Stream.filter((n) => n % 2 === 0) * ) * const values = yield* Stream.runCollect(stream) * values // => [ 2, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (refinement: Refinement, B>): (self: Stream) => Stream /** * Filters a stream to the elements that satisfy a predicate. * * **Example** (Filtering stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3, 4).pipe( * Stream.filter((n) => n % 2 === 0) * ) * const values = yield* Stream.runCollect(stream) * values // => [ 2, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (predicate: Predicate>): (self: Stream) => Stream /** * Filters a stream to the elements that satisfy a predicate. * * **Example** (Filtering stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3, 4).pipe( * Stream.filter((n) => n % 2 === 0) * ) * const values = yield* Stream.runCollect(stream) * values // => [ 2, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (self: Stream, refinement: Refinement): Stream /** * Filters a stream to the elements that satisfy a predicate. * * **Example** (Filtering stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3, 4).pipe( * Stream.filter((n) => n % 2 === 0) * ) * const values = yield* Stream.runCollect(stream) * values // => [ 2, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (self: Stream, predicate: Predicate): Stream } = dual( 2, ( self: Stream, predicate: Predicate ): Stream => fromChannel(Channel.filterArray(toChannel(self), predicate)) ) /** * Filters and maps stream elements in one pass using a `Filter`. * * **When to use** * * Use to keep only stream elements accepted by a `Filter` and emit each filter * success value. * * **Details** * * `Result.succeed` values are emitted and `Result.fail` values are skipped. * * @see {@link filter} for keeping original elements with a boolean predicate or refinement * @see {@link filterMapEffect} for an effectful `Filter` * @see {@link partition} for consuming both filter success and failure values * * @stability stable * @category filtering * @since 2.0.0 */ export const filterMap: { /** * Filters and maps stream elements in one pass using a `Filter`. * * **When to use** * * Use to keep only stream elements accepted by a `Filter` and emit each filter * success value. * * **Details** * * `Result.succeed` values are emitted and `Result.fail` values are skipped. * * @see {@link filter} for keeping original elements with a boolean predicate or refinement * @see {@link filterMapEffect} for an effectful `Filter` * @see {@link partition} for consuming both filter success and failure values * * @stability stable * @category filtering * @since 2.0.0 */ (filter: Filter.Filter, B, X>): (self: Stream) => Stream /** * Filters and maps stream elements in one pass using a `Filter`. * * **When to use** * * Use to keep only stream elements accepted by a `Filter` and emit each filter * success value. * * **Details** * * `Result.succeed` values are emitted and `Result.fail` values are skipped. * * @see {@link filter} for keeping original elements with a boolean predicate or refinement * @see {@link filterMapEffect} for an effectful `Filter` * @see {@link partition} for consuming both filter success and failure values * * @stability stable * @category filtering * @since 2.0.0 */ (self: Stream, filter: Filter.Filter): Stream } = dual( 2, ( self: Stream, filter: Filter.Filter ): Stream => fromChannel(Channel.filterMapArray(toChannel(self), filter)) ) /** * Filters elements in a single pass effectfully. * * **Example** (Effectfully filtering stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4).pipe(Stream.filterEffect((n) => Effect.succeed(n > 2))) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const filterEffect: { /** * Filters elements in a single pass effectfully. * * **Example** (Effectfully filtering stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4).pipe(Stream.filterEffect((n) => Effect.succeed(n > 2))) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (predicate: (a: NoInfer, i: number) => Effect.Effect): (self: Stream) => Stream /** * Filters elements in a single pass effectfully. * * **Example** (Effectfully filtering stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4).pipe(Stream.filterEffect((n) => Effect.succeed(n > 2))) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ ( self: Stream, predicate: (a: NoInfer, i: number) => Effect.Effect ): Stream } = dual( 2, ( self: Stream, predicate: (a: NoInfer, i: number) => Effect.Effect ): Stream => fromChannel(Channel.filterArrayEffect(toChannel(self), predicate)) ) /** * Filters and maps elements in one pass effectfully using a `FilterEffect`. * * **When to use** * * Use to apply effectful logic that can reject stream elements or emit * transformed values before they continue downstream. * * **Details** * * `Result.succeed` values are emitted, `Result.fail` values are skipped, and * effect failures fail the stream. * * @see {@link filterMap} for the synchronous `Filter` variant * @see {@link filterEffect} for effectfully keeping original elements * @see {@link mapEffect} for effectfully transforming every element * * @stability stable * @category filtering * @since 2.0.0 */ export const filterMapEffect: { /** * Filters and maps elements in one pass effectfully using a `FilterEffect`. * * **When to use** * * Use to apply effectful logic that can reject stream elements or emit * transformed values before they continue downstream. * * **Details** * * `Result.succeed` values are emitted, `Result.fail` values are skipped, and * effect failures fail the stream. * * @see {@link filterMap} for the synchronous `Filter` variant * @see {@link filterEffect} for effectfully keeping original elements * @see {@link mapEffect} for effectfully transforming every element * * @stability stable * @category filtering * @since 2.0.0 */ (filter: Filter.FilterEffect, B, X, EX, RX>): (self: Stream) => Stream /** * Filters and maps elements in one pass effectfully using a `FilterEffect`. * * **When to use** * * Use to apply effectful logic that can reject stream elements or emit * transformed values before they continue downstream. * * **Details** * * `Result.succeed` values are emitted, `Result.fail` values are skipped, and * effect failures fail the stream. * * @see {@link filterMap} for the synchronous `Filter` variant * @see {@link filterEffect} for effectfully keeping original elements * @see {@link mapEffect} for effectfully transforming every element * * @stability stable * @category filtering * @since 2.0.0 */ (self: Stream, filter: Filter.FilterEffect): Stream } = dual( 2, ( self: Stream, filter: Filter.FilterEffect ): Stream => fromChannel(Channel.filterMapArrayEffect(toChannel(self), filter)) ) /** * Partitions a stream using a `Filter` and exposes passing and failing values * as scoped queues. * * **Details** * * The queues are backed by a fiber in the current scope and should be consumed * while that scope remains open. Each queue fails with the stream error or * `Cause.Done` when the source ends. * * **Example** (Partitioning a stream into queues) * * ```ts import.meta.vitest * import { Effect, Result, Stream } from "effect" * * const program = Effect.gen(function*() { * const [passes, fails] = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.partitionQueue((n) => n % 2 === 0 ? Result.succeed(n) : Result.fail(n)) * ) * * const passValues = yield* Stream.fromQueue(passes).pipe(Stream.runCollect) * const failValues = yield* Stream.fromQueue(fails).pipe(Stream.runCollect) * * passValues // => [ 2, 4 ] * failValues // => [ 1, 3 ] * }) * * await Effect.runPromise(Effect.scoped(program)) * ``` * * @stability stable * @category filtering * @since 4.0.0 */ export const partitionQueue: { /** * Partitions a stream using a `Filter` and exposes passing and failing values * as scoped queues. * * **Details** * * The queues are backed by a fiber in the current scope and should be consumed * while that scope remains open. Each queue fails with the stream error or * `Cause.Done` when the source ends. * * **Example** (Partitioning a stream into queues) * * ```ts import.meta.vitest * import { Effect, Result, Stream } from "effect" * * const program = Effect.gen(function*() { * const [passes, fails] = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.partitionQueue((n) => n % 2 === 0 ? Result.succeed(n) : Result.fail(n)) * ) * * const passValues = yield* Stream.fromQueue(passes).pipe(Stream.runCollect) * const failValues = yield* Stream.fromQueue(fails).pipe(Stream.runCollect) * * passValues // => [ 2, 4 ] * failValues // => [ 1, 3 ] * }) * * await Effect.runPromise(Effect.scoped(program)) * ``` * * @stability stable * @category filtering * @since 4.0.0 */ ( filter: Filter.Filter, Pass, Fail>, options?: { readonly capacity?: number | "unbounded" | undefined } ): (self: Stream) => Effect.Effect< [ passes: Queue.Dequeue, fails: Queue.Dequeue ], never, R | Scope.Scope > /** * Partitions a stream using a `Filter` and exposes passing and failing values * as scoped queues. * * **Details** * * The queues are backed by a fiber in the current scope and should be consumed * while that scope remains open. Each queue fails with the stream error or * `Cause.Done` when the source ends. * * **Example** (Partitioning a stream into queues) * * ```ts import.meta.vitest * import { Effect, Result, Stream } from "effect" * * const program = Effect.gen(function*() { * const [passes, fails] = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.partitionQueue((n) => n % 2 === 0 ? Result.succeed(n) : Result.fail(n)) * ) * * const passValues = yield* Stream.fromQueue(passes).pipe(Stream.runCollect) * const failValues = yield* Stream.fromQueue(fails).pipe(Stream.runCollect) * * passValues // => [ 2, 4 ] * failValues // => [ 1, 3 ] * }) * * await Effect.runPromise(Effect.scoped(program)) * ``` * * @stability stable * @category filtering * @since 4.0.0 */ ( self: Stream, filter: Filter.Filter, Pass, Fail>, options?: { readonly capacity?: number | "unbounded" | undefined } ): Effect.Effect< [ passes: Queue.Dequeue, fails: Queue.Dequeue ], never, R | Scope.Scope > } = dual( (args) => isStream(args[0]), Effect.fnUntraced( function*( self: Stream, filter: Filter.Filter, Pass, Fail>, options?: { readonly capacity?: number | "unbounded" | undefined } ): Effect.fn.Return< [ passes: Queue.Dequeue, fails: Queue.Dequeue ], never, R | Scope.Scope > { const scope = yield* Effect.scope const pull = yield* Channel.toPullScoped(self.channel, scope) const capacity = options?.capacity === "unbounded" ? undefined : options?.capacity ?? DefaultChunkSize const passes = yield* Queue.make({ capacity }) const fails = yield* Queue.make({ capacity }) yield* Effect.gen(function*() { while (true) { const chunk = yield* pull const excluded: Array = [] const satisfying: Array = [] for (let i = 0; i < chunk.length; i++) { const result = filter(chunk[i] as NoInfer) if (Result.isFailure(result)) { excluded.push(result.failure) } else { satisfying.push(result.success) } } let passFiber: Fiber.Fiber | undefined = undefined if (satisfying.length > 0) { const leftover = Queue.offerAllUnsafe(passes, satisfying) if (leftover.length > 0) { passFiber = yield* Effect.forkChild(Queue.offerAll(passes, leftover)) } } if (excluded.length > 0) { const leftover = Queue.offerAllUnsafe(fails, excluded) if (leftover.length > 0) { yield* Queue.offerAll(fails, leftover) } } if (passFiber) yield* Fiber.join(passFiber) } }).pipe( Effect.onError((cause) => { Queue.failCauseUnsafe(passes, cause) Queue.failCauseUnsafe(fails, cause) return Effect.void }), Effect.forkIn(scope) ) return [passes, fails] } ) ) const fromQueueShutdownOnEnd = (queue: Queue.Dequeue): Stream> => fromChannel(Channel.fromTransform((_, scope) => Effect.as( Scope.addFinalizer(scope, Queue.shutdown(queue as Queue.Queue)), Queue.takeAll(queue) ) )) /** * Splits a stream with an effectful `Filter`, returning scoped streams for * filter successes and failures. * * **When to use** * * Use when you need to classify each stream element with an effectful `Filter` * and consume both passing and failing mapped values as streams. * * **Details** * * The returned streams are backed by queues in the current scope and should be * consumed while that scope remains open. The first stream emits success values * from the filter, and the second emits failure values. * * @see {@link partition} for the pure `Filter` variant * @see {@link partitionQueue} for the lower-level queue result * @see {@link filterMapEffect} for effectful filtering that discards failed filter results * * @stability stable * @category filtering * @since 4.0.0 */ export const partitionEffect: { /** * Splits a stream with an effectful `Filter`, returning scoped streams for * filter successes and failures. * * **When to use** * * Use when you need to classify each stream element with an effectful `Filter` * and consume both passing and failing mapped values as streams. * * **Details** * * The returned streams are backed by queues in the current scope and should be * consumed while that scope remains open. The first stream emits success values * from the filter, and the second emits failure values. * * @see {@link partition} for the pure `Filter` variant * @see {@link partitionQueue} for the lower-level queue result * @see {@link filterMapEffect} for effectful filtering that discards failed filter results * * @stability stable * @category filtering * @since 4.0.0 */ ( filter: Filter.FilterEffect, Pass, Fail, EX, RX>, options?: { readonly capacity?: number | "unbounded" | undefined readonly concurrency?: Concurrency | undefined } ): (self: Stream) => Effect.Effect< [ passes: Stream, fails: Stream ], never, R | RX | Scope.Scope > /** * Splits a stream with an effectful `Filter`, returning scoped streams for * filter successes and failures. * * **When to use** * * Use when you need to classify each stream element with an effectful `Filter` * and consume both passing and failing mapped values as streams. * * **Details** * * The returned streams are backed by queues in the current scope and should be * consumed while that scope remains open. The first stream emits success values * from the filter, and the second emits failure values. * * @see {@link partition} for the pure `Filter` variant * @see {@link partitionQueue} for the lower-level queue result * @see {@link filterMapEffect} for effectful filtering that discards failed filter results * * @stability stable * @category filtering * @since 4.0.0 */ ( self: Stream, filter: Filter.FilterEffect, Pass, Fail, EX, RX>, options?: { readonly capacity?: number | "unbounded" | undefined readonly concurrency?: Concurrency | undefined } ): Effect.Effect< [ passes: Stream, fails: Stream ], never, R | RX | Scope.Scope > } = dual( (args) => isStream(args[0]), ( self: Stream, filter: Filter.FilterEffect, Pass, Fail, EX, RX>, options?: { readonly capacity?: number | "unbounded" | undefined readonly concurrency?: Concurrency | undefined } ): Effect.Effect< [ passes: Stream, fails: Stream ], never, R | RX | Scope.Scope > => Effect.map( partitionQueue, E | EX, R | RX, Pass, Fail>( mapEffect(self, (a) => filter(a as NoInfer), options), (result) => result, options ), ([passes, fails]) => [fromQueueShutdownOnEnd(passes), fromQueueShutdownOnEnd(fails)] as const ) ) /** * Splits a stream into scoped passing and failing substreams using a * `Filter`. * * **Details** * * The returned streams are backed by queues in the current scope and should be * consumed while that scope remains open. The faster stream may advance up to * `capacity` elements ahead of the slower one. The first stream emits the * filter successes and the second emits the filter failures. The default * capacity is 16. * * **Example** (Partitioning a stream) * * ```ts import.meta.vitest * import { Effect, Result, Stream } from "effect" * * const program = Effect.gen(function*() { * const [passes, fails] = yield* Stream.partition( * Stream.make(1, 2, 3, 4), * (n) => n % 2 === 0 ? Result.succeed(n) : Result.fail(n) * ) * const evens = yield* Stream.runCollect(passes) * const odds = yield* Stream.runCollect(fails) * evens // => [ 2, 4 ] * odds // => [ 1, 3 ] * }) * await Effect.runPromise(Effect.scoped(program)) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const partition: { /** * Splits a stream into scoped passing and failing substreams using a * `Filter`. * * **Details** * * The returned streams are backed by queues in the current scope and should be * consumed while that scope remains open. The faster stream may advance up to * `capacity` elements ahead of the slower one. The first stream emits the * filter successes and the second emits the filter failures. The default * capacity is 16. * * **Example** (Partitioning a stream) * * ```ts import.meta.vitest * import { Effect, Result, Stream } from "effect" * * const program = Effect.gen(function*() { * const [passes, fails] = yield* Stream.partition( * Stream.make(1, 2, 3, 4), * (n) => n % 2 === 0 ? Result.succeed(n) : Result.fail(n) * ) * const evens = yield* Stream.runCollect(passes) * const odds = yield* Stream.runCollect(fails) * evens // => [ 2, 4 ] * odds // => [ 1, 3 ] * }) * await Effect.runPromise(Effect.scoped(program)) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ ( filter: Filter.Filter, Pass, Fail>, options?: { readonly capacity?: number | "unbounded" | undefined } ): ( self: Stream ) => Effect.Effect< [passes: Stream, fails: Stream], never, R | Scope.Scope > /** * Splits a stream into scoped passing and failing substreams using a * `Filter`. * * **Details** * * The returned streams are backed by queues in the current scope and should be * consumed while that scope remains open. The faster stream may advance up to * `capacity` elements ahead of the slower one. The first stream emits the * filter successes and the second emits the filter failures. The default * capacity is 16. * * **Example** (Partitioning a stream) * * ```ts import.meta.vitest * import { Effect, Result, Stream } from "effect" * * const program = Effect.gen(function*() { * const [passes, fails] = yield* Stream.partition( * Stream.make(1, 2, 3, 4), * (n) => n % 2 === 0 ? Result.succeed(n) : Result.fail(n) * ) * const evens = yield* Stream.runCollect(passes) * const odds = yield* Stream.runCollect(fails) * evens // => [ 2, 4 ] * odds // => [ 1, 3 ] * }) * await Effect.runPromise(Effect.scoped(program)) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ ( self: Stream, filter: Filter.Filter, Pass, Fail>, options?: { readonly capacity?: number | "unbounded" | undefined } ): Effect.Effect< [passes: Stream, fails: Stream], never, R | Scope.Scope > } = dual( (args) => isStream(args[0]), ( self: Stream, filter: Filter.Filter, Pass, Fail>, options?: { readonly capacity?: number | "unbounded" | undefined } ): Effect.Effect< [passes: Stream, fails: Stream], never, R | Scope.Scope > => Effect.map( partitionQueue(self, filter, { capacity: options?.capacity ?? 16 }), ([passes, fails]) => [fromQueueShutdownOnEnd(passes), fromQueueShutdownOnEnd(fails)] as const ) ) /** * Returns the specified stream if the given condition is satisfied, otherwise * returns an empty stream. * * **Example** (Conditionally keeping a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect( * Stream.when(Stream.make(1, 2, 3), Effect.succeed(false)) * ) * result // => [] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const when: { /** * Returns the specified stream if the given condition is satisfied, otherwise * returns an empty stream. * * **Example** (Conditionally keeping a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect( * Stream.when(Stream.make(1, 2, 3), Effect.succeed(false)) * ) * result // => [] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (test: Effect.Effect): (self: Stream) => Stream /** * Returns the specified stream if the given condition is satisfied, otherwise * returns an empty stream. * * **Example** (Conditionally keeping a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect( * Stream.when(Stream.make(1, 2, 3), Effect.succeed(false)) * ) * result // => [] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (self: Stream, test: Effect.Effect): Stream } = dual(2, ( self: Stream, test: Effect.Effect ): Stream => test.pipe( Effect.map((pass) => pass ? self : empty), unwrap )) /** * Runs a sink to peel off enough elements to produce a value and returns that * value with the remaining stream in a scope. * * **Details** * * The returned stream is only valid within the scope. * * **Example** (Peeling a stream with a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * const stream = Stream.fromArrays([1, 2, 3], [4, 5, 6]) * const sink = Sink.take(3) * * const program = Effect.scoped( * Effect.gen(function*() { * const [peeled, rest] = yield* Stream.peel(stream, sink) * const remaining = yield* Stream.runCollect(rest) * const result = [peeled, remaining] // => [[1, 2, 3], [4, 5, 6]] * }) * ) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const peel: { /** * Runs a sink to peel off enough elements to produce a value and returns that * value with the remaining stream in a scope. * * **Details** * * The returned stream is only valid within the scope. * * **Example** (Peeling a stream with a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * const stream = Stream.fromArrays([1, 2, 3], [4, 5, 6]) * const sink = Sink.take(3) * * const program = Effect.scoped( * Effect.gen(function*() { * const [peeled, rest] = yield* Stream.peel(stream, sink) * const remaining = yield* Stream.runCollect(rest) * const result = [peeled, remaining] // => [[1, 2, 3], [4, 5, 6]] * }) * ) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (sink: Sink.Sink): (self: Stream) => Effect.Effect<[A2, Stream], E2 | E, Scope.Scope | R2 | R> /** * Runs a sink to peel off enough elements to produce a value and returns that * value with the remaining stream in a scope. * * **Details** * * The returned stream is only valid within the scope. * * **Example** (Peeling a stream with a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * const stream = Stream.fromArrays([1, 2, 3], [4, 5, 6]) * const sink = Sink.take(3) * * const program = Effect.scoped( * Effect.gen(function*() { * const [peeled, rest] = yield* Stream.peel(stream, sink) * const remaining = yield* Stream.runCollect(rest) * const result = [peeled, remaining] // => [[1, 2, 3], [4, 5, 6]] * }) * ) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (self: Stream, sink: Sink.Sink): Effect.Effect<[A2, Stream], E | E2, Scope.Scope | R | R2> } = dual( 2, Effect.fnUntraced(function*( self: Stream, sink: Sink.Sink ): Effect.fn.Return<[A2, Stream], E | E2, Scope.Scope | R | R2> { let cause: Cause.Cause> | undefined = undefined const originalPull = yield* Channel.toPull(self.channel) const pull: Pull.Pull< Arr.NonEmptyReadonlyArray, E > = Effect.catchCause(originalPull, (cause_) => { cause = cause_ return Effect.failCause(cause_) }) const [result, leftover] = yield* Effect.scopedWith((scope) => sink.transform(pull as Pull.Pull>, scope) ) let stream: Stream = cause ? empty : fromPull(Effect.succeed(originalPull)) if (leftover) { stream = concat(fromArray(leftover), stream) } return [result, stream] }) ) /** * Buffers up to `capacity` elements so a faster producer can progress * independently of a slower consumer. * * **Details** * * Finite buffers use the configured queue strategy: `"suspend"` applies * backpressure, while `"dropping"` and `"sliding"` may discard elements when * the buffer is full. This combinator destroys chunking; use `Stream.rechunk` * afterward if you need fixed chunk sizes. * * **Example** (Buffering stream elements) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.buffer({ capacity: 1 }), * Stream.runCollect * ) * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category buffering * @since 2.0.0 */ export const buffer: { /** * Buffers up to `capacity` elements so a faster producer can progress * independently of a slower consumer. * * **Details** * * Finite buffers use the configured queue strategy: `"suspend"` applies * backpressure, while `"dropping"` and `"sliding"` may discard elements when * the buffer is full. This combinator destroys chunking; use `Stream.rechunk` * afterward if you need fixed chunk sizes. * * **Example** (Buffering stream elements) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.buffer({ capacity: 1 }), * Stream.runCollect * ) * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category buffering * @since 2.0.0 */ ( options: { readonly capacity: "unbounded" } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined } ): (self: Stream) => Stream /** * Buffers up to `capacity` elements so a faster producer can progress * independently of a slower consumer. * * **Details** * * Finite buffers use the configured queue strategy: `"suspend"` applies * backpressure, while `"dropping"` and `"sliding"` may discard elements when * the buffer is full. This combinator destroys chunking; use `Stream.rechunk` * afterward if you need fixed chunk sizes. * * **Example** (Buffering stream elements) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.buffer({ capacity: 1 }), * Stream.runCollect * ) * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category buffering * @since 2.0.0 */ ( self: Stream, options: { readonly capacity: "unbounded" } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined } ): Stream } = dual(2, ( self: Stream, options: { readonly capacity: "unbounded" } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined } ): Stream => fromChannel(Channel.bufferArray(self.channel, options))) /** * Allows a faster producer to progress independently of a slower consumer by * buffering up to `capacity` chunks in a queue. * * **Details** * * Finite buffers use the configured queue strategy: `"suspend"` applies * backpressure, while `"dropping"` and `"sliding"` may discard chunks when the * buffer is full. This combinator preserves chunking and is best with * power-of-2 capacities. * * **Example** (Buffering stream chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.fromArrays([1, 2], [3, 4]).pipe( * Stream.bufferArray({ capacity: 2 }), * Stream.runCollect * ) * result // => [ 1, 2, 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category buffering * @since 4.0.0 */ export const bufferArray: { /** * Allows a faster producer to progress independently of a slower consumer by * buffering up to `capacity` chunks in a queue. * * **Details** * * Finite buffers use the configured queue strategy: `"suspend"` applies * backpressure, while `"dropping"` and `"sliding"` may discard chunks when the * buffer is full. This combinator preserves chunking and is best with * power-of-2 capacities. * * **Example** (Buffering stream chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.fromArrays([1, 2], [3, 4]).pipe( * Stream.bufferArray({ capacity: 2 }), * Stream.runCollect * ) * result // => [ 1, 2, 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category buffering * @since 4.0.0 */ ( options: { readonly capacity: "unbounded" } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined } ): (self: Stream) => Stream /** * Allows a faster producer to progress independently of a slower consumer by * buffering up to `capacity` chunks in a queue. * * **Details** * * Finite buffers use the configured queue strategy: `"suspend"` applies * backpressure, while `"dropping"` and `"sliding"` may discard chunks when the * buffer is full. This combinator preserves chunking and is best with * power-of-2 capacities. * * **Example** (Buffering stream chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.fromArrays([1, 2], [3, 4]).pipe( * Stream.bufferArray({ capacity: 2 }), * Stream.runCollect * ) * result // => [ 1, 2, 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category buffering * @since 4.0.0 */ ( self: Stream, options: { readonly capacity: "unbounded" } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined } ): Stream } = dual(2, ( self: Stream, options: { readonly capacity: "unbounded" } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined } ): Stream => fromChannel(Channel.buffer(self.channel, options))) /** * Switches over to the stream produced by the provided function in case this * one fails. Allows recovery from all causes of failure, including * interruption if the stream is uninterruptible. * * **Example** (Catching stream causes) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("Oops!")), * Stream.concat(Stream.make(3, 4)) * ) * * const recovered = stream.pipe( * Stream.catchCause(() => Stream.make(999)) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(recovered) * values // => [ 1, 2, 999 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ export const catchCause: { /** * Switches over to the stream produced by the provided function in case this * one fails. Allows recovery from all causes of failure, including * interruption if the stream is uninterruptible. * * **Example** (Catching stream causes) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("Oops!")), * Stream.concat(Stream.make(3, 4)) * ) * * const recovered = stream.pipe( * Stream.catchCause(() => Stream.make(999)) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(recovered) * values // => [ 1, 2, 999 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ (f: (cause: Cause.Cause) => Stream): (self: Stream) => Stream /** * Switches over to the stream produced by the provided function in case this * one fails. Allows recovery from all causes of failure, including * interruption if the stream is uninterruptible. * * **Example** (Catching stream causes) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("Oops!")), * Stream.concat(Stream.make(3, 4)) * ) * * const recovered = stream.pipe( * Stream.catchCause(() => Stream.make(999)) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(recovered) * values // => [ 1, 2, 999 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ (self: Stream, f: (cause: Cause.Cause) => Stream): Stream } = dual(2, ( self: Stream, f: (cause: Cause.Cause) => Stream ): Stream => self.channel.pipe( Channel.catchCause((cause) => f(cause).channel), fromChannel )) /** * Recovers from defects using the provided function. * * **Details** * * Typed failures and interruptions are not caught. * * **Gotchas** * * `catchDefect` recovers any cause that contains a defect, including causes * that also contain typed errors. Typed error handlers such as `catch` skip * those causes, so defects always take precedence. * * **Example** (Recovering from a defect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.die("boom").pipe( * Stream.catchDefect((defect) => Stream.succeed(`recovered: ${defect}`)) * ) * * const result = Effect.runSync(Stream.runCollect(stream)) * result // => ["recovered: boom"] * ``` * * @stability stable * @category error handling * @since 4.0.0 */ export const catchDefect: { /** * Recovers from defects using the provided function. * * **Details** * * Typed failures and interruptions are not caught. * * **Gotchas** * * `catchDefect` recovers any cause that contains a defect, including causes * that also contain typed errors. Typed error handlers such as `catch` skip * those causes, so defects always take precedence. * * **Example** (Recovering from a defect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.die("boom").pipe( * Stream.catchDefect((defect) => Stream.succeed(`recovered: ${defect}`)) * ) * * const result = Effect.runSync(Stream.runCollect(stream)) * result // => ["recovered: boom"] * ``` * * @stability stable * @category error handling * @since 4.0.0 */ (f: (defect: unknown) => Stream): (self: Stream) => Stream /** * Recovers from defects using the provided function. * * **Details** * * Typed failures and interruptions are not caught. * * **Gotchas** * * `catchDefect` recovers any cause that contains a defect, including causes * that also contain typed errors. Typed error handlers such as `catch` skip * those causes, so defects always take precedence. * * **Example** (Recovering from a defect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.die("boom").pipe( * Stream.catchDefect((defect) => Stream.succeed(`recovered: ${defect}`)) * ) * * const result = Effect.runSync(Stream.runCollect(stream)) * result // => ["recovered: boom"] * ``` * * @stability stable * @category error handling * @since 4.0.0 */ (self: Stream, f: (defect: unknown) => Stream): Stream } = dual(2, ( self: Stream, f: (defect: unknown) => Stream ): Stream => self.channel.pipe( Channel.catchDefect((defect) => f(defect).channel), fromChannel )) /** * Runs an effect when the stream fails without changing its values or error, * unless the tap effect itself fails. * * **Example** (Tapping stream causes) * * ```ts import.meta.vitest * import { Cause, Effect, Stream } from "effect" * * const observations: Array = [] * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("boom")), * Stream.tapCause((cause) => Effect.sync(() => observations.push(Cause.isReason(cause)))), * Stream.catch(() => Stream.succeed(0)) * ) * * const program = Effect.gen(function* () { * const result = yield* Stream.runCollect(stream) * result // => [1, 2, 0] * }) * * await Effect.runPromise(program) * observations // => [false] * ``` * * @stability stable * @category error handling * @since 2.0.0 */ export const tapCause: { /** * Runs an effect when the stream fails without changing its values or error, * unless the tap effect itself fails. * * **Example** (Tapping stream causes) * * ```ts import.meta.vitest * import { Cause, Effect, Stream } from "effect" * * const observations: Array = [] * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("boom")), * Stream.tapCause((cause) => Effect.sync(() => observations.push(Cause.isReason(cause)))), * Stream.catch(() => Stream.succeed(0)) * ) * * const program = Effect.gen(function* () { * const result = yield* Stream.runCollect(stream) * result // => [1, 2, 0] * }) * * await Effect.runPromise(program) * observations // => [false] * ``` * * @stability stable * @category error handling * @since 2.0.0 */ (f: (cause: Cause.Cause) => Effect.Effect): (self: Stream) => Stream /** * Runs an effect when the stream fails without changing its values or error, * unless the tap effect itself fails. * * **Example** (Tapping stream causes) * * ```ts import.meta.vitest * import { Cause, Effect, Stream } from "effect" * * const observations: Array = [] * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("boom")), * Stream.tapCause((cause) => Effect.sync(() => observations.push(Cause.isReason(cause)))), * Stream.catch(() => Stream.succeed(0)) * ) * * const program = Effect.gen(function* () { * const result = yield* Stream.runCollect(stream) * result // => [1, 2, 0] * }) * * await Effect.runPromise(program) * observations // => [false] * ``` * * @stability stable * @category error handling * @since 2.0.0 */ ( self: Stream, f: (cause: Cause.Cause) => Effect.Effect ): Stream } = dual(2, ( self: Stream, f: (cause: Cause.Cause) => Effect.Effect ): Stream => self.channel.pipe( Channel.tapCause(f), fromChannel )) const catch_: { ( f: (error: E) => Stream ): (self: Stream) => Stream ( self: Stream, f: (error: E) => Stream ): Stream } = dual(2, ( self: Stream, f: (error: E) => Stream ): Stream => fromChannel(Channel.catch(self.channel, (error) => f(error).channel))) export { /** * Switches over to the stream produced by the provided function if this one fails. * * **Gotchas** * * Defects take precedence over typed errors. If the cause also contains a * defect, the handler does not run and the failure is propagated with every * typed error converted to a defect. An interruption alongside a typed error * does not prevent recovery. * * **Example** (Catching stream failures) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("Oops!")), * Stream.catch(() => Stream.make(999)) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 999 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ catch_ as catch } /** * Peeks at errors effectfully without changing the stream unless the tap fails. * * **Example** (Effectfully peeking at errors) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const errors: Array = [] * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("boom")), * Stream.tapError((error) => Effect.sync(() => errors.push(error))), * Stream.catch(() => Stream.make(999)) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 999] * }) * * await Effect.runPromise(program) * errors // => ["boom"] * ``` * * @stability stable * @category error handling * @since 2.0.0 */ export const tapError: { /** * Peeks at errors effectfully without changing the stream unless the tap fails. * * **Example** (Effectfully peeking at errors) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const errors: Array = [] * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("boom")), * Stream.tapError((error) => Effect.sync(() => errors.push(error))), * Stream.catch(() => Stream.make(999)) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 999] * }) * * await Effect.runPromise(program) * errors // => ["boom"] * ``` * * @stability stable * @category error handling * @since 2.0.0 */ (f: (error: E) => Effect.Effect): (self: Stream) => Stream /** * Peeks at errors effectfully without changing the stream unless the tap fails. * * **Example** (Effectfully peeking at errors) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const errors: Array = [] * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("boom")), * Stream.tapError((error) => Effect.sync(() => errors.push(error))), * Stream.catch(() => Stream.make(999)) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 999] * }) * * await Effect.runPromise(program) * errors // => ["boom"] * ``` * * @stability stable * @category error handling * @since 2.0.0 */ (self: Stream, f: (error: E) => Effect.Effect): Stream } = dual(2, ( self: Stream, f: (error: E) => Effect.Effect ): Stream => self.channel.pipe( Channel.tapError(f), fromChannel )) /** * Peeks at errors with a matching `_tag` effectfully without changing the * stream unless the tap fails. * * **Example** (Effectfully peeking at a tagged error) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class NetworkError extends Data.TaggedError("NetworkError")<{ * statusCode: number * }> {} * * class ValidationError extends Data.TaggedError("ValidationError")<{ * field: string * }> {} * * const seen: Array = [] * const stream: Stream.Stream = Stream.fail( * new NetworkError({ statusCode: 504 }) * ) * * const program = stream.pipe( * Stream.tapErrorTag("NetworkError", (error) => Effect.sync(() => seen.push(error.statusCode))), * Stream.catch(() => Stream.make(0)), * Stream.runCollect * ) * * await Effect.runPromise(program) // => [ 0 ] * seen // => [ 504 ] * ``` * * @see {@link tapError} for peeking at every typed error * @see {@link catchTag} for recovering from a tagged error * * @stability stable * @category error handling * @since 4.0.0 */ export const tapErrorTag: { /** * Peeks at errors with a matching `_tag` effectfully without changing the * stream unless the tap fails. * * **Example** (Effectfully peeking at a tagged error) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class NetworkError extends Data.TaggedError("NetworkError")<{ * statusCode: number * }> {} * * class ValidationError extends Data.TaggedError("ValidationError")<{ * field: string * }> {} * * const seen: Array = [] * const stream: Stream.Stream = Stream.fail( * new NetworkError({ statusCode: 504 }) * ) * * const program = stream.pipe( * Stream.tapErrorTag("NetworkError", (error) => Effect.sync(() => seen.push(error.statusCode))), * Stream.catch(() => Stream.make(0)), * Stream.runCollect * ) * * await Effect.runPromise(program) // => [ 0 ] * seen // => [ 504 ] * ``` * * @see {@link tapError} for peeking at every typed error * @see {@link catchTag} for recovering from a tagged error * * @stability stable * @category error handling * @since 4.0.0 */ | Arr.NonEmptyReadonlyArray>, E, A1, E1, R1>( k: K, f: ( e: ExtractTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K> ) => Effect.Effect ): (self: Stream) => Stream /** * Peeks at errors with a matching `_tag` effectfully without changing the * stream unless the tap fails. * * **Example** (Effectfully peeking at a tagged error) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class NetworkError extends Data.TaggedError("NetworkError")<{ * statusCode: number * }> {} * * class ValidationError extends Data.TaggedError("ValidationError")<{ * field: string * }> {} * * const seen: Array = [] * const stream: Stream.Stream = Stream.fail( * new NetworkError({ statusCode: 504 }) * ) * * const program = stream.pipe( * Stream.tapErrorTag("NetworkError", (error) => Effect.sync(() => seen.push(error.statusCode))), * Stream.catch(() => Stream.make(0)), * Stream.runCollect * ) * * await Effect.runPromise(program) // => [ 0 ] * seen // => [ 504 ] * ``` * * @see {@link tapError} for peeking at every typed error * @see {@link catchTag} for recovering from a tagged error * * @stability stable * @category error handling * @since 4.0.0 */ < A, E, R, const K extends Tags | Arr.NonEmptyReadonlyArray>, R1, E1, A1 >( self: Stream, k: K, f: (e: ExtractTag ? K[number] : K>) => Effect.Effect ): Stream } = dual( 3, < A, E, R, const K extends Tags | Arr.NonEmptyReadonlyArray>, R1, E1, A1 >( self: Stream, k: K, f: (e: ExtractTag ? K[number] : K>) => Effect.Effect ): Stream => { const predicate = Array.isArray(k) ? ((e: E): e is ExtractTag ? K[number] : K> => hasProperty(e, "_tag") && k.includes(e._tag)) : isTagged(k as string) return tapError( self, (error) => predicate(error) ? f(error as ExtractTag ? K[number] : K>) : Effect.void ) } ) /** * Peeks at defects effectfully without changing the stream unless the tap * fails. * * **Example** (Effectfully peeking at defects) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const defects: Array = [] * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.die("boom")), * Stream.tapDefect((defect) => Effect.sync(() => defects.push(defect))), * Stream.catchCause(() => Stream.make(3)) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * defects // => [ 'boom' ] * ``` * * @see {@link tapCause} for peeking at the full failure cause * @see {@link catchDefect} for recovering from defects * * @stability stable * @category error handling * @since 4.0.0 */ export const tapDefect: { /** * Peeks at defects effectfully without changing the stream unless the tap * fails. * * **Example** (Effectfully peeking at defects) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const defects: Array = [] * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.die("boom")), * Stream.tapDefect((defect) => Effect.sync(() => defects.push(defect))), * Stream.catchCause(() => Stream.make(3)) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * defects // => [ 'boom' ] * ``` * * @see {@link tapCause} for peeking at the full failure cause * @see {@link catchDefect} for recovering from defects * * @stability stable * @category error handling * @since 4.0.0 */ (f: (defect: unknown) => Effect.Effect): (self: Stream) => Stream /** * Peeks at defects effectfully without changing the stream unless the tap * fails. * * **Example** (Effectfully peeking at defects) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const defects: Array = [] * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.die("boom")), * Stream.tapDefect((defect) => Effect.sync(() => defects.push(defect))), * Stream.catchCause(() => Stream.make(3)) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * defects // => [ 'boom' ] * ``` * * @see {@link tapCause} for peeking at the full failure cause * @see {@link catchDefect} for recovering from defects * * @stability stable * @category error handling * @since 4.0.0 */ (self: Stream, f: (defect: unknown) => Effect.Effect): Stream } = dual(2, ( self: Stream, f: (defect: unknown) => Effect.Effect ): Stream => tapCause(self, (cause) => { const defect = Cause.findDefect(cause) return Result.isSuccess(defect) ? f(defect.success) : Effect.void })) /** * Recovers from errors that match a predicate by switching to a recovery stream. * * **Details** * * When a failure matches the filter, the stream switches to the recovery * stream. Non-matching failures propagate downstream, so the error type is * preserved unless the filter narrows it. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching matching failures) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail(42)), * Stream.catchIf( * (error): error is 42 => error === 42, * () => Stream.make(999) * ) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 999 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ export const catchIf: { /** * Recovers from errors that match a predicate by switching to a recovery stream. * * **Details** * * When a failure matches the filter, the stream switches to the recovery * stream. Non-matching failures propagate downstream, so the error type is * preserved unless the filter narrows it. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching matching failures) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail(42)), * Stream.catchIf( * (error): error is 42 => error === 42, * () => Stream.make(999) * ) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 999 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ ( refinement: Refinement, EB>, f: (e: EB) => Stream, orElse?: ((e: Exclude) => Stream) | undefined ): ( self: Stream ) => Stream, E2 | E3 | (A3 extends unassigned ? Exclude : never), R | R2 | R3> /** * Recovers from errors that match a predicate by switching to a recovery stream. * * **Details** * * When a failure matches the filter, the stream switches to the recovery * stream. Non-matching failures propagate downstream, so the error type is * preserved unless the filter narrows it. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching matching failures) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail(42)), * Stream.catchIf( * (error): error is 42 => error === 42, * () => Stream.make(999) * ) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 999 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ ( predicate: Predicate>, f: (e: NoInfer) => Stream, orElse?: ((e: NoInfer) => Stream) | undefined ): ( self: Stream ) => Stream, E2 | E3 | (A3 extends unassigned ? E : never), R | R2 | R3> /** * Recovers from errors that match a predicate by switching to a recovery stream. * * **Details** * * When a failure matches the filter, the stream switches to the recovery * stream. Non-matching failures propagate downstream, so the error type is * preserved unless the filter narrows it. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching matching failures) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail(42)), * Stream.catchIf( * (error): error is 42 => error === 42, * () => Stream.make(999) * ) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 999 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ ( self: Stream, refinement: Refinement, EB>, f: (e: EB) => Stream, orElse?: ((e: Exclude) => Stream) | undefined ): Stream, E2 | E3 | (A3 extends unassigned ? Exclude : never), R | R2 | R3> /** * Recovers from errors that match a predicate by switching to a recovery stream. * * **Details** * * When a failure matches the filter, the stream switches to the recovery * stream. Non-matching failures propagate downstream, so the error type is * preserved unless the filter narrows it. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching matching failures) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail(42)), * Stream.catchIf( * (error): error is 42 => error === 42, * () => Stream.make(999) * ) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 999 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ ( self: Stream, predicate: Predicate, f: (e: E) => Stream, orElse?: ((e: E) => Stream) | undefined ): Stream, E2 | E3 | (A3 extends unassigned ? E : never), R | R2 | R3> } = dual((args) => isStream(args[0]), < A, E, R, A2, E2, R2, A3 = never, E3 = E, R3 = never >( self: Stream, predicate: Predicate, f: (failure: E) => Stream, orElse?: ((failure: E) => Stream) | undefined ): Stream => fromChannel( Channel.catchIf( toChannel(self), predicate, (e) => f(e).channel, orElse && ((e) => orElse(e).channel) ) )) /** * Recovers from errors that match a `Filter` by switching to a recovery * stream. * * **When to use** * * Use to recover from stream errors with a reusable `Filter` when matching can * also narrow or transform the error before choosing the recovery stream. * * **Details** * * Successful filter results are passed to `f`. Failed filter results go to * `orElse` when provided; otherwise the filter failure is re-failed. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * @see {@link catchIf} for predicate or refinement based recovery * @see {@link catchTag} for `_tag` based recovery from one tagged error * @see {@link catchTags} for `_tag` based recovery from multiple tagged errors * @see {@link catchCauseFilter} for filtering full causes * * @stability stable * @category error handling * @since 4.0.0 */ export const catchFilter: { /** * Recovers from errors that match a `Filter` by switching to a recovery * stream. * * **When to use** * * Use to recover from stream errors with a reusable `Filter` when matching can * also narrow or transform the error before choosing the recovery stream. * * **Details** * * Successful filter results are passed to `f`. Failed filter results go to * `orElse` when provided; otherwise the filter failure is re-failed. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * @see {@link catchIf} for predicate or refinement based recovery * @see {@link catchTag} for `_tag` based recovery from one tagged error * @see {@link catchTags} for `_tag` based recovery from multiple tagged errors * @see {@link catchCauseFilter} for filtering full causes * * @stability stable * @category error handling * @since 4.0.0 */ ( filter: Filter.Filter, EB, X>, f: (failure: EB) => Stream, orElse?: ((failure: X) => Stream) | undefined ): ( self: Stream ) => Stream, E2 | E3 | (A3 extends unassigned ? X : never), R | R2 | R3> /** * Recovers from errors that match a `Filter` by switching to a recovery * stream. * * **When to use** * * Use to recover from stream errors with a reusable `Filter` when matching can * also narrow or transform the error before choosing the recovery stream. * * **Details** * * Successful filter results are passed to `f`. Failed filter results go to * `orElse` when provided; otherwise the filter failure is re-failed. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * @see {@link catchIf} for predicate or refinement based recovery * @see {@link catchTag} for `_tag` based recovery from one tagged error * @see {@link catchTags} for `_tag` based recovery from multiple tagged errors * @see {@link catchCauseFilter} for filtering full causes * * @stability stable * @category error handling * @since 4.0.0 */ ( self: Stream, filter: Filter.Filter, EB, X>, f: (failure: EB) => Stream, orElse?: ((failure: X) => Stream) | undefined ): Stream, E2 | E3 | (A3 extends unassigned ? X : never), R | R2 | R3> } = dual((args) => isStream(args[0]), < A, E, R, EB, A2, E2, R2, X, A3 = never, E3 = X, R3 = never >( self: Stream, filter: Filter.Filter, EB, X>, f: (failure: EB) => Stream, orElse?: ((failure: X) => Stream) | undefined ): Stream => fromChannel( Channel.catchFilter( toChannel(self), filter, (e) => f(e).channel, orElse && ((e) => orElse(e).channel) ) )) /** * Recovers from failures whose `_tag` matches the provided value by switching to * the stream returned by `f`. * * **When to use** * * Use when you need to handle a specific error case from a stream whose error * type is a tagged union with a readonly `_tag` field. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching tagged failures) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class HttpError extends Data.TaggedError("HttpError")<{ message: string }> {} * * const stream = Stream.fail(new HttpError({ message: "timeout" })) * * const recovered = Stream.catchTag(stream, "HttpError", (error) => * Stream.make(`Recovered: ${error.message}`) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(recovered) * values // => [ 'Recovered: timeout' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ export const catchTag: { /** * Recovers from failures whose `_tag` matches the provided value by switching to * the stream returned by `f`. * * **When to use** * * Use when you need to handle a specific error case from a stream whose error * type is a tagged union with a readonly `_tag` field. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching tagged failures) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class HttpError extends Data.TaggedError("HttpError")<{ message: string }> {} * * const stream = Stream.fail(new HttpError({ message: "timeout" })) * * const recovered = Stream.catchTag(stream, "HttpError", (error) => * Stream.make(`Recovered: ${error.message}`) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(recovered) * values // => [ 'Recovered: timeout' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ < const K extends Tags | Arr.NonEmptyReadonlyArray>, E, A1, E1, R1, A2 = unassigned, E2 = never, R2 = never >( k: K, f: ( e: ExtractTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K> ) => Stream, orElse?: | ((e: ExcludeTag ? K[number] : K>) => Stream) | undefined ): ( self: Stream ) => Stream< A | A1 | Exclude, | E1 | E2 | (A2 extends unassigned ? ExcludeTag ? K[number] : K> : never), R | R1 | R2 > /** * Recovers from failures whose `_tag` matches the provided value by switching to * the stream returned by `f`. * * **When to use** * * Use when you need to handle a specific error case from a stream whose error * type is a tagged union with a readonly `_tag` field. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching tagged failures) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class HttpError extends Data.TaggedError("HttpError")<{ message: string }> {} * * const stream = Stream.fail(new HttpError({ message: "timeout" })) * * const recovered = Stream.catchTag(stream, "HttpError", (error) => * Stream.make(`Recovered: ${error.message}`) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(recovered) * values // => [ 'Recovered: timeout' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ < A, E, R, const K extends Tags | Arr.NonEmptyReadonlyArray>, R1, E1, A1, A2 = unassigned, E2 = never, R2 = never >( self: Stream, k: K, f: (e: ExtractTag ? K[number] : K>) => Stream, orElse?: | ((e: ExcludeTag ? K[number] : K>) => Stream) | undefined ): Stream< A | A1 | Exclude, | E1 | E2 | (A2 extends unassigned ? ExcludeTag ? K[number] : K> : never), R | R1 | R2 > } = dual( (args) => isStream(args[0]), < A, E, R, const K extends Tags | Arr.NonEmptyReadonlyArray>, R1, E1, A1, A2 = never, E2 = ExcludeTag ? K[number] : K>, R2 = never >( self: Stream, k: K, f: (e: ExtractTag ? K[number] : K>) => Stream, orElse?: | ((e: ExcludeTag ? K[number] : K>) => Stream) | undefined ): Stream => { const pred = Array.isArray(k) ? ((e: E): e is any => hasProperty(e, "_tag") && k.includes(e._tag)) : isTagged(k as string) return catchIf(self, pred, f, orElse as any) as any } ) /** * Switches to a recovery stream based on matching `_tag` handlers. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching tagged failures with handlers) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * class NotFound { * readonly _tag = "NotFound" * constructor(readonly resource: string) {} * } * * class Unauthorized { * readonly _tag = "Unauthorized" * constructor(readonly user: string) {} * } * * const stream = Stream.fail(new NotFound("profile")) * * const program = Effect.gen(function* () { * const result = yield* stream.pipe( * Stream.catchTags({ * NotFound: () => Stream.succeed("fallback"), * Unauthorized: () => Stream.succeed("login") * }), * Stream.runCollect * ) * result // => [ 'fallback' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ export const catchTags: { /** * Switches to a recovery stream based on matching `_tag` handlers. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching tagged failures with handlers) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * class NotFound { * readonly _tag = "NotFound" * constructor(readonly resource: string) {} * } * * class Unauthorized { * readonly _tag = "Unauthorized" * constructor(readonly user: string) {} * } * * const stream = Stream.fail(new NotFound("profile")) * * const program = Effect.gen(function* () { * const result = yield* stream.pipe( * Stream.catchTags({ * NotFound: () => Stream.succeed("fallback"), * Unauthorized: () => Stream.succeed("login") * }), * Stream.runCollect * ) * result // => [ 'fallback' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ < E, Cases extends & { [K in Extract["_tag"]]+?: ((error: Extract) => Stream) } & (unknown extends E ? {} : { [K in Exclude["_tag"]>]: never }), A2 = unassigned, E2 = never, R2 = never >( cases: Cases, orElse?: ((e: Exclude) => Stream) | undefined ): (self: Stream) => Stream< | A | Exclude | { [K in keyof Cases]: Cases[K] extends ((...args: Array) => Stream) ? A : never }[keyof Cases], | E2 | (A2 extends unassigned ? Exclude : never) | { [K in keyof Cases]: Cases[K] extends ((...args: Array) => Stream) ? E : never }[keyof Cases], | R | R2 | { [K in keyof Cases]: Cases[K] extends ((...args: Array) => Stream) ? R : never }[keyof Cases] > /** * Switches to a recovery stream based on matching `_tag` handlers. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching tagged failures with handlers) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * class NotFound { * readonly _tag = "NotFound" * constructor(readonly resource: string) {} * } * * class Unauthorized { * readonly _tag = "Unauthorized" * constructor(readonly user: string) {} * } * * const stream = Stream.fail(new NotFound("profile")) * * const program = Effect.gen(function* () { * const result = yield* stream.pipe( * Stream.catchTags({ * NotFound: () => Stream.succeed("fallback"), * Unauthorized: () => Stream.succeed("login") * }), * Stream.runCollect * ) * result // => [ 'fallback' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ < R, E, A, Cases extends & { [K in Extract["_tag"]]+?: ((error: Extract) => Stream) } & (unknown extends E ? {} : { [K in Exclude["_tag"]>]: never }), A2 = unassigned, E2 = never, R2 = never >( self: Stream, cases: Cases, orElse?: ((e: Exclude) => Stream) | undefined ): Stream< | A | Exclude | { [K in keyof Cases]: Cases[K] extends ((...args: Array) => Stream) ? A : never }[keyof Cases], | E2 | (A2 extends unassigned ? Exclude : never) | { [K in keyof Cases]: Cases[K] extends ((...args: Array) => Stream) ? E : never }[keyof Cases], | R | R2 | { [K in keyof Cases]: Cases[K] extends ((...args: Array) => Stream) ? R : never }[keyof Cases] > } = dual((args) => isStream(args[0]), (self, cases, orElse) => { let keys: Array return catchFilter( self, (e: any) => { keys ??= Object.keys(cases) return hasProperty(e, "_tag") && isString(e["_tag"]) && keys.includes(e["_tag"]) ? Result.succeed(e) : Result.fail(e) }, (e: any) => cases[e["_tag"] as string](e), orElse ) }) /** * Catches a specific reason within a tagged error. * * **When to use** * * Use to handle nested error causes without removing the parent error * from the error channel. * * **Details** * * The handler receives the unwrapped reason. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching a tagged error reason) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number * }> {} * * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ * limit: number * }> {} * * class AiError extends Data.TaggedError("AiError")<{ * reason: RateLimitError | QuotaExceededError * }> {} * * const stream = Stream.fail( * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) * ) * * const program = Effect.gen(function*() { * const values = yield* stream.pipe( * Stream.catchReason("AiError", "RateLimitError", (reason) => * Stream.succeed(`retry: ${reason.retryAfter}`) * ), * Stream.runCollect * ) * values // => [ 'retry: 60' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ export const catchReason: { /** * Catches a specific reason within a tagged error. * * **When to use** * * Use to handle nested error causes without removing the parent error * from the error channel. * * **Details** * * The handler receives the unwrapped reason. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching a tagged error reason) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number * }> {} * * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ * limit: number * }> {} * * class AiError extends Data.TaggedError("AiError")<{ * reason: RateLimitError | QuotaExceededError * }> {} * * const stream = Stream.fail( * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) * ) * * const program = Effect.gen(function*() { * const values = yield* stream.pipe( * Stream.catchReason("AiError", "RateLimitError", (reason) => * Stream.succeed(`retry: ${reason.retryAfter}`) * ), * Stream.runCollect * ) * values // => [ 'retry: 60' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ < K extends Tags, E, RK extends ReasonTags, K>>, A2, E2, R2, A3 = unassigned, E3 = never, R3 = never >( errorTag: K, reasonTag: RK, f: ( reason: ExtractReason, K>, RK>, error: NarrowReason, K>, RK> ) => Stream, orElse?: | (( reason: ExcludeReason, K>, RK>, error: OmitReason, K>, RK> ) => Stream) | undefined ): ( self: Stream ) => Stream< A | A2 | Exclude, ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), R | R2 | R3 > /** * Catches a specific reason within a tagged error. * * **When to use** * * Use to handle nested error causes without removing the parent error * from the error channel. * * **Details** * * The handler receives the unwrapped reason. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching a tagged error reason) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number * }> {} * * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ * limit: number * }> {} * * class AiError extends Data.TaggedError("AiError")<{ * reason: RateLimitError | QuotaExceededError * }> {} * * const stream = Stream.fail( * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) * ) * * const program = Effect.gen(function*() { * const values = yield* stream.pipe( * Stream.catchReason("AiError", "RateLimitError", (reason) => * Stream.succeed(`retry: ${reason.retryAfter}`) * ), * Stream.runCollect * ) * values // => [ 'retry: 60' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ < A, E, R, K extends Tags, RK extends ReasonTags>, A2, E2, R2, A3 = unassigned, E3 = never, R3 = never >( self: Stream, errorTag: K, reasonTag: RK, f: (reason: ExtractReason, RK>, error: NarrowReason, RK>) => Stream, orElse?: | ((reason: ExcludeReason, RK>, error: OmitReason, RK>) => Stream) | undefined ): Stream< A | A2 | Exclude, ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), R | R2 | R3 > } = dual( (args) => isStream(args[0]), < A, E, R, K extends Tags, RK extends ReasonTags>, A2, E2, R2, A3 = unassigned, E3 = never, R3 = never >( self: Stream, errorTag: K, reasonTag: RK, f: (reason: ExtractReason, RK>, error: NarrowReason, RK>) => Stream, orElse?: | ((reason: ExcludeReason, RK>, error: OmitReason, RK>) => Stream) | undefined ): Stream< A | A2 | Exclude, ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), R | R2 | R3 > => fromChannel( Channel.catchReason( toChannel(self), errorTag, reasonTag, (reason, error) => f(reason, error).channel, orElse && ((reason, error) => orElse(reason, error).channel) ) ) as any ) /** * Catches multiple reasons within a tagged error using an object of handlers. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching tagged error reasons) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number * }> {} * * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ * limit: number * }> {} * * class AiError extends Data.TaggedError("AiError")<{ * reason: RateLimitError | QuotaExceededError * }> {} * * const stream = Stream.fail( * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) * ) * * const program = Effect.gen(function*() { * const values = yield* stream.pipe( * Stream.catchReasons("AiError", { * RateLimitError: (reason) => Stream.succeed(`retry: ${reason.retryAfter}`), * QuotaExceededError: (reason) => Stream.succeed(`quota: ${reason.limit}`) * }), * Stream.runCollect * ) * values // => [ 'retry: 60' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ export const catchReasons: { /** * Catches multiple reasons within a tagged error using an object of handlers. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching tagged error reasons) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number * }> {} * * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ * limit: number * }> {} * * class AiError extends Data.TaggedError("AiError")<{ * reason: RateLimitError | QuotaExceededError * }> {} * * const stream = Stream.fail( * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) * ) * * const program = Effect.gen(function*() { * const values = yield* stream.pipe( * Stream.catchReasons("AiError", { * RateLimitError: (reason) => Stream.succeed(`retry: ${reason.retryAfter}`), * QuotaExceededError: (reason) => Stream.succeed(`quota: ${reason.limit}`) * }), * Stream.runCollect * ) * values // => [ 'retry: 60' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ < K extends Tags, E, Cases extends { [RK in ReasonTags, K>>]+?: ( reason: ExtractReason, K>, RK>, error: NarrowReason, K>, RK> ) => Stream }, A2 = unassigned, E2 = never, R2 = never >( errorTag: K, cases: Cases, orElse?: | (( reason: ExcludeReason, K>, Extract>, error: OmitReason, K>, Extract> ) => Stream) | undefined ): (self: Stream) => Stream< | A | Exclude | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Stream ? A : never }[keyof Cases], | ExcludeTag | E2 | (A2 extends unassigned ? ExtractTag : never) | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Stream ? E : never }[keyof Cases], | R | R2 | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Stream ? R : never }[keyof Cases] > /** * Catches multiple reasons within a tagged error using an object of handlers. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Catching tagged error reasons) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number * }> {} * * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ * limit: number * }> {} * * class AiError extends Data.TaggedError("AiError")<{ * reason: RateLimitError | QuotaExceededError * }> {} * * const stream = Stream.fail( * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) * ) * * const program = Effect.gen(function*() { * const values = yield* stream.pipe( * Stream.catchReasons("AiError", { * RateLimitError: (reason) => Stream.succeed(`retry: ${reason.retryAfter}`), * QuotaExceededError: (reason) => Stream.succeed(`quota: ${reason.limit}`) * }), * Stream.runCollect * ) * values // => [ 'retry: 60' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ < A, E, R, K extends Tags, Cases extends { [RK in ReasonTags>]+?: ( reason: ExtractReason, RK>, error: NarrowReason, RK> ) => Stream }, A2 = unassigned, E2 = never, R2 = never >( self: Stream, errorTag: K, cases: Cases, orElse?: | (( reason: ExcludeReason, K>, Extract>, error: OmitReason, K>, Extract> ) => Stream) | undefined ): Stream< | A | Exclude | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Stream ? A : never }[keyof Cases], | ExcludeTag | E2 | (A2 extends unassigned ? ExtractTag : never) | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Stream ? E : never }[keyof Cases], | R | R2 | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Stream ? R : never }[keyof Cases] > } = dual((args) => isStream(args[0]), (self, errorTag, cases, orElse) => { const handlers: Record Channel.Channel> = Object.create(null) for (const key of Object.keys(cases)) { const handler = (cases as any)[key] handlers[key] = (reason, error) => handler(reason, error).channel } const orElseHandler = orElse && ((reason: any, error: any) => orElse(reason, error).channel) return fromChannel( Channel.catchReasons(self.channel, errorTag as any, handlers as any, orElseHandler as any) as Channel.Channel< Arr.NonEmptyReadonlyArray, any, void, unknown, unknown, unknown, any > ) as any }) /** * Promotes nested reason errors into the stream error channel, replacing the * parent error. * * **Example** (Extracting the reason from a tagged error) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number * }> {} * * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ * limit: number * }> {} * * class AiError extends Data.TaggedError("AiError")<{ * reason: RateLimitError | QuotaExceededError * }> {} * * const stream: Stream.Stream = Stream.fail( * new AiError({ reason: new RateLimitError({ retryAfter: 30 }) }) * ) * * // Before: Stream * // After: Stream * const unwrapped = stream.pipe(Stream.unwrapReason("AiError")) * * const program = Effect.gen(function*() { * const error = yield* Effect.flip(Stream.runCollect(unwrapped)) * error._tag // => "RateLimitError" * }) * * await Effect.runPromise(program) * ``` * * @see {@link catchReason} for recovering from a specific reason * @see {@link catchReasons} for handling several reasons at once * * @stability stable * @category error handling * @since 4.0.0 */ export const unwrapReason: { /** * Promotes nested reason errors into the stream error channel, replacing the * parent error. * * **Example** (Extracting the reason from a tagged error) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number * }> {} * * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ * limit: number * }> {} * * class AiError extends Data.TaggedError("AiError")<{ * reason: RateLimitError | QuotaExceededError * }> {} * * const stream: Stream.Stream = Stream.fail( * new AiError({ reason: new RateLimitError({ retryAfter: 30 }) }) * ) * * // Before: Stream * // After: Stream * const unwrapped = stream.pipe(Stream.unwrapReason("AiError")) * * const program = Effect.gen(function*() { * const error = yield* Effect.flip(Stream.runCollect(unwrapped)) * error._tag // => "RateLimitError" * }) * * await Effect.runPromise(program) * ``` * * @see {@link catchReason} for recovering from a specific reason * @see {@link catchReasons} for handling several reasons at once * * @stability stable * @category error handling * @since 4.0.0 */ < K extends Effect.TagsWithReason, E >(errorTag: K): (self: Stream) => Stream | ReasonOf>, R> /** * Promotes nested reason errors into the stream error channel, replacing the * parent error. * * **Example** (Extracting the reason from a tagged error) * * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number * }> {} * * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ * limit: number * }> {} * * class AiError extends Data.TaggedError("AiError")<{ * reason: RateLimitError | QuotaExceededError * }> {} * * const stream: Stream.Stream = Stream.fail( * new AiError({ reason: new RateLimitError({ retryAfter: 30 }) }) * ) * * // Before: Stream * // After: Stream * const unwrapped = stream.pipe(Stream.unwrapReason("AiError")) * * const program = Effect.gen(function*() { * const error = yield* Effect.flip(Stream.runCollect(unwrapped)) * error._tag // => "RateLimitError" * }) * * await Effect.runPromise(program) * ``` * * @see {@link catchReason} for recovering from a specific reason * @see {@link catchReasons} for handling several reasons at once * * @stability stable * @category error handling * @since 4.0.0 */ < A, E, R, K extends Effect.TagsWithReason >(self: Stream, errorTag: K): Stream | ReasonOf>, R> } = dual( 2, < A, E, R, K extends Effect.TagsWithReason >( self: Stream, errorTag: K ): Stream | ReasonOf>, R> => catchFilter( self, (e: any) => { if (isTagged(e, errorTag) && hasProperty(e, "reason")) { return Result.succeed(e.reason) } return Result.fail(e) }, fail as any ) as any ) /** * Transforms the errors emitted by this stream using `f`. * * **Details** * * Maps every typed error in the cause, preserving defects, interrupts and * reason order. Unlike typed recovery, mapping does not convert errors to defects. * * **Example** (Mapping stream errors) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.fail("bad").pipe( * Stream.mapError((error) => `mapped: ${error}`), * Stream.catch((error) => Stream.make(`recovered from ${error}`)), * Stream.runCollect * ) * result // => [ 'recovered from mapped: bad' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ export const mapError: { /** * Transforms the errors emitted by this stream using `f`. * * **Details** * * Maps every typed error in the cause, preserving defects, interrupts and * reason order. Unlike typed recovery, mapping does not convert errors to defects. * * **Example** (Mapping stream errors) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.fail("bad").pipe( * Stream.mapError((error) => `mapped: ${error}`), * Stream.catch((error) => Stream.make(`recovered from ${error}`)), * Stream.runCollect * ) * result // => [ 'recovered from mapped: bad' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ (f: (error: E) => E2): (self: Stream) => Stream /** * Transforms the errors emitted by this stream using `f`. * * **Details** * * Maps every typed error in the cause, preserving defects, interrupts and * reason order. Unlike typed recovery, mapping does not convert errors to defects. * * **Example** (Mapping stream errors) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.fail("bad").pipe( * Stream.mapError((error) => `mapped: ${error}`), * Stream.catch((error) => Stream.make(`recovered from ${error}`)), * Stream.runCollect * ) * result // => [ 'recovered from mapped: bad' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ (self: Stream, f: (error: E) => E2): Stream } = dual(2, ( self: Stream, f: (error: E) => E2 ): Stream => fromChannel(Channel.mapError(self.channel, f))) /** * Recovers from stream failures by filtering the `Cause` and switching to a recovery stream. * Non-matching causes are re-emitted as failures. * * **Example** (Catching matching causes) * * ```ts import.meta.vitest * import { Cause, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const failingStream = Stream.fail("NetworkError") * const recovered = Stream.catchCauseIf( * failingStream, * (cause) => Cause.hasFails(cause), * (cause) => Stream.make(`Recovered: ${Cause.squash(cause)}`) * ) * * const output = yield* Stream.runCollect(recovered) * output // => [ 'Recovered: NetworkError' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ export const catchCauseIf: { /** * Recovers from stream failures by filtering the `Cause` and switching to a recovery stream. * Non-matching causes are re-emitted as failures. * * **Example** (Catching matching causes) * * ```ts import.meta.vitest * import { Cause, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const failingStream = Stream.fail("NetworkError") * const recovered = Stream.catchCauseIf( * failingStream, * (cause) => Cause.hasFails(cause), * (cause) => Stream.make(`Recovered: ${Cause.squash(cause)}`) * ) * * const output = yield* Stream.runCollect(recovered) * output // => [ 'Recovered: NetworkError' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ ( predicate: Predicate>, f: (cause: Cause.Cause) => Stream ): ( self: Stream ) => Stream /** * Recovers from stream failures by filtering the `Cause` and switching to a recovery stream. * Non-matching causes are re-emitted as failures. * * **Example** (Catching matching causes) * * ```ts import.meta.vitest * import { Cause, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const failingStream = Stream.fail("NetworkError") * const recovered = Stream.catchCauseIf( * failingStream, * (cause) => Cause.hasFails(cause), * (cause) => Stream.make(`Recovered: ${Cause.squash(cause)}`) * ) * * const output = yield* Stream.runCollect(recovered) * output // => [ 'Recovered: NetworkError' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 4.0.0 */ ( self: Stream, predicate: Predicate>, f: (cause: Cause.Cause) => Stream ): Stream } = dual(3, ( self: Stream, predicate: Predicate>, f: (cause: Cause.Cause) => Stream ): Stream => fromChannel( Channel.catchCauseIf( self.channel, predicate, (cause) => f(cause).channel ) )) /** * Recovers from stream failures by filtering the `Cause` and switching to a * recovery stream. * * **When to use** * * Use when you need to recover a stream only from causes selected by a * `Filter`, while giving the recovery both the selected value and the original * `Cause`. * * **Details** * * The filter is applied to the full `Cause`. A successful filter result is * passed to `f` together with the original cause; a failed filter result * re-fails with the residual cause. * * @see {@link catchCauseIf} for predicate-based cause selection * @see {@link catchFilter} for filtering typed error values instead of full causes * @see {@link catchCause} for recovering from every cause without filtering * * @stability stable * @category error handling * @since 4.0.0 */ export const catchCauseFilter: { /** * Recovers from stream failures by filtering the `Cause` and switching to a * recovery stream. * * **When to use** * * Use when you need to recover a stream only from causes selected by a * `Filter`, while giving the recovery both the selected value and the original * `Cause`. * * **Details** * * The filter is applied to the full `Cause`. A successful filter result is * passed to `f` together with the original cause; a failed filter result * re-fails with the residual cause. * * @see {@link catchCauseIf} for predicate-based cause selection * @see {@link catchFilter} for filtering typed error values instead of full causes * @see {@link catchCause} for recovering from every cause without filtering * * @stability stable * @category error handling * @since 4.0.0 */ >( filter: Filter.Filter, EB, X>, f: (failure: EB, cause: Cause.Cause) => Stream ): ( self: Stream ) => Stream | E2, R2 | R> /** * Recovers from stream failures by filtering the `Cause` and switching to a * recovery stream. * * **When to use** * * Use when you need to recover a stream only from causes selected by a * `Filter`, while giving the recovery both the selected value and the original * `Cause`. * * **Details** * * The filter is applied to the full `Cause`. A successful filter result is * passed to `f` together with the original cause; a failed filter result * re-fails with the residual cause. * * @see {@link catchCauseIf} for predicate-based cause selection * @see {@link catchFilter} for filtering typed error values instead of full causes * @see {@link catchCause} for recovering from every cause without filtering * * @stability stable * @category error handling * @since 4.0.0 */ >( self: Stream, filter: Filter.Filter, EB, X>, f: (failure: EB, cause: Cause.Cause) => Stream ): Stream | E2, R | R2> } = dual(3, >( self: Stream, filter: Filter.Filter, EB, X>, f: (failure: EB, cause: Cause.Cause) => Stream ): Stream | E2, R | R2> => fromChannel( Channel.catchCauseFilter( self.channel, filter, (failure, cause) => f(failure, cause).channel ) )) /** * Switches to a fallback stream if this stream is empty. * * **Example** (Switching on empty streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.empty.pipe( * Stream.orElseIfEmpty(() => Stream.make(1, 2)), * Stream.runCollect * ) * values // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ export const orElseIfEmpty: { /** * Switches to a fallback stream if this stream is empty. * * **Example** (Switching on empty streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.empty.pipe( * Stream.orElseIfEmpty(() => Stream.make(1, 2)), * Stream.runCollect * ) * values // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ (orElse: LazyArg>): (self: Stream) => Stream /** * Switches to a fallback stream if this stream is empty. * * **Example** (Switching on empty streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.empty.pipe( * Stream.orElseIfEmpty(() => Stream.make(1, 2)), * Stream.runCollect * ) * values // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ (self: Stream, orElse: LazyArg>): Stream } = dual(2, ( self: Stream, orElse: LazyArg> ): Stream => fromChannel(Channel.orElseIfEmpty( self.channel, (_) => toChannel(orElse()) ))) /** * Returns a stream that emits a fallback value when this stream fails. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Recovering with a fallback value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.fail("NetworkError").pipe( * Stream.orElseSucceed((error) => `Recovered: ${error}`) * ) * * const values = yield* Stream.runCollect(stream) * values // => [ 'Recovered: NetworkError' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ export const orElseSucceed: { /** * Returns a stream that emits a fallback value when this stream fails. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Recovering with a fallback value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.fail("NetworkError").pipe( * Stream.orElseSucceed((error) => `Recovered: ${error}`) * ) * * const values = yield* Stream.runCollect(stream) * values // => [ 'Recovered: NetworkError' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ (f: (error: E) => A2): (self: Stream) => Stream /** * Returns a stream that emits a fallback value when this stream fails. * * **Gotchas** * * If the cause contains a defect, the handler is skipped and every typed error * is converted to a defect, preserving reason order and interrupts. An * interruption alongside a typed error does not prevent recovery. * * **Example** (Recovering with a fallback value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.fail("NetworkError").pipe( * Stream.orElseSucceed((error) => `Recovered: ${error}`) * ) * * const values = yield* Stream.runCollect(stream) * values // => [ 'Recovered: NetworkError' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ (self: Stream, f: (error: E) => A2): Stream } = dual(2, ( self: Stream, f: (error: E) => A2 ): Stream => catch_(self, (e) => succeed(f(e)))) /** * Turns typed failures into defects, making the stream infallible. * * **Example** (Turning failures into defects) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.orDie, * Stream.runCollect * ) * * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ export const orDie = (self: Stream): Stream => fromChannel(Channel.orDie(self.channel)) /** * Ignores failures and ends the stream on error. * * **When to use** * * Use when you want a failing stream to end gracefully rather than propagate * the error. * * **Details** * * The `log` option controls whether the failure is logged before the stream * terminates. * * **Gotchas** * * Defects take precedence over typed errors. If the cause also contains a * defect, the failure is not ignored and is propagated with every typed error * converted to a defect. An interruption alongside a typed error does not * prevent the error from being ignored. * * **Example** (Ignoring stream failures) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.concat(Stream.fail("boom")), * Stream.ignore, * Stream.runCollect * ) * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * **Example** (Configuring ignore logging) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function*() { * const values = yield* Stream.fail("boom").pipe( * Stream.ignore({ log: false }), * Stream.runCollect * ) * values // => [] * })) * * ``` * * @see {@link ignoreCause} for a variant that also ignores defects, not just typed failures * * @stability stable * @category error handling * @since 4.0.0 */ export const ignore: < Arg extends Stream | { readonly log?: boolean | Severity | undefined } | undefined >( selfOrOptions: Arg, options?: { readonly log?: boolean | Severity | undefined } | undefined ) => [Arg] extends [Stream] ? Stream : (self: Stream) => Stream = dual( (args) => isStream(args[0]), ( self: Stream, options?: { readonly log?: boolean | Severity | undefined } | undefined ): Stream => fromChannel(Channel.ignore(self.channel, options)) ) /** * Ignores the stream's failure cause, including defects, and ends the stream. * * **When to use** * * Use when you need to silently suppress a stream's entire failure cause, * including both typed errors and defects, rather than propagate it downstream. * * **Example** (Ignoring stream failure causes) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function*() { * const values = yield* Stream.make(1, 2).pipe( * Stream.concat(Stream.die("boom")), * Stream.ignoreCause({ log: false }), * Stream.runCollect * ) * values // => [1, 2] * })) * * ``` * * @see {@link ignore} to ignore only typed failures without suppressing defects * * @stability stable * @category error handling * @since 4.0.0 */ export const ignoreCause: < Arg extends Stream | { readonly log?: boolean | Severity | undefined } | undefined >( streamOrOptions: Arg, options?: { readonly log?: boolean | Severity | undefined } | undefined ) => [Arg] extends [Stream] ? Stream : (self: Stream) => Stream = dual( (args) => isStream(args[0]), ( self: Stream, options?: { readonly log?: boolean | Severity | undefined } | undefined ): Stream => fromChannel(Channel.ignoreCause(self.channel, options)) ) /** * Retries the stream according to the given schedule when it fails. * * **Details** * * This retries the entire stream, so will re-execute all of the stream's * acquire operations. * * The schedule is reset as soon as the first element passes through the * stream again. * * **Gotchas** * * Defects take precedence over typed errors. A failure whose cause contains a * defect is not retried and is propagated with every typed error converted to a * defect. A typed error alongside an interruption is retried. * * **Example** (Retrying stream failures) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1).pipe( * Stream.concat(Stream.fail("boom")), * Stream.retry(Schedule.recurs(1)), * Stream.take(2), * Stream.runCollect * ) * * values // => [ 1, 1 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ export const retry: { /** * Retries the stream according to the given schedule when it fails. * * **Details** * * This retries the entire stream, so will re-execute all of the stream's * acquire operations. * * The schedule is reset as soon as the first element passes through the * stream again. * * **Gotchas** * * Defects take precedence over typed errors. A failure whose cause contains a * defect is not retried and is propagated with every typed error converted to a * defect. A typed error alongside an interruption is retried. * * **Example** (Retrying stream failures) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1).pipe( * Stream.concat(Stream.fail("boom")), * Stream.retry(Schedule.recurs(1)), * Stream.take(2), * Stream.runCollect * ) * * values // => [ 1, 1 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ ( policy: | Schedule.Schedule, E2, R2> | (( $: (_: Schedule.Schedule, SE, SR>) => Schedule.Schedule ) => Schedule.Schedule, E2, R2>) ): (self: Stream) => Stream /** * Retries the stream according to the given schedule when it fails. * * **Details** * * This retries the entire stream, so will re-execute all of the stream's * acquire operations. * * The schedule is reset as soon as the first element passes through the * stream again. * * **Gotchas** * * Defects take precedence over typed errors. A failure whose cause contains a * defect is not retried and is propagated with every typed error converted to a * defect. A typed error alongside an interruption is retried. * * **Example** (Retrying stream failures) * * ```ts import.meta.vitest * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1).pipe( * Stream.concat(Stream.fail("boom")), * Stream.retry(Schedule.recurs(1)), * Stream.take(2), * Stream.runCollect * ) * * values // => [ 1, 1 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category error handling * @since 2.0.0 */ ( self: Stream, policy: | Schedule.Schedule, E2, R2> | (( $: (_: Schedule.Schedule, SE, SR>) => Schedule.Schedule ) => Schedule.Schedule, E2, R2>) ): Stream } = dual( 2, ( self: Stream, policy: | Schedule.Schedule, E2, R2> | (( $: (_: Schedule.Schedule, SE, SR>) => Schedule.Schedule ) => Schedule.Schedule, E2, R2>) ): Stream => fromChannel(Channel.retry(self.channel, policy)) ) const retryWithoutReset = ( self: Stream, schedule: Schedule.Schedule ): Stream => unwrap(Effect.map(Schedule.toStepWithMetadata(schedule), (step) => { let meta = Schedule.CurrentMetadata.defaultValue() const loop = (): Stream => catch_( provideServiceEffect(self, Schedule.CurrentMetadata, Effect.sync(() => meta)), (error) => unwrap(Pull.catchDone( Effect.map(step(error), (meta_) => { meta = meta_ return unwrap(Effect.as(Effect.yieldNow, loop())) }), () => Effect.succeed(fail(error)) )) ) return loop() })) /** * Applies an `ExecutionPlan` to a stream, retrying with step-provided resources * until it succeeds or the plan is exhausted. * * **Details** * * By default, a failing step can fallback even after emitting elements; set * `preventFallbackOnPartialStream` to fail instead of mixing partial output with * a later fallback. * * Attempts can be observed from outside the stream by passing * `options.onEvent`, which receives an `ExecutionPlan.Event` before each * attempt and after it settles; see `Effect.withExecutionPlan` for the handler * semantics. When a downstream consumer stops pulling early (for example * `Stream.take` outside the plan), the truncated attempt reports * `AttemptSuccess`: the consumer stopped, not the source. * * **Example** (Applying an execution plan) * * ```ts import.meta.vitest * import { Context, Effect, ExecutionPlan, Layer, Stream } from "effect" * * class Service extends Context.Service()("Service", { * make: Effect.succeed({ * stream: Stream.fail("A") as Stream.Stream * }) * }) { * static Bad = Layer.succeed(Service, Service.of({ stream: Stream.fail("A") })) * static Good = Layer.succeed(Service, Service.of({ stream: Stream.make(1, 2, 3) })) * } * * const plan = ExecutionPlan.make( * { provide: Service.Bad }, * { provide: Service.Good } * ) * * const stream = Stream.unwrap(Effect.map(Service, (_) => _.stream)) * * const program = Effect.gen(function*() { * const items = yield* stream.pipe(Stream.withExecutionPlan(plan), Stream.runCollect) * items // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability unstable * @category error handling * @since 3.16.0 */ export const withExecutionPlan: { /** * Applies an `ExecutionPlan` to a stream, retrying with step-provided resources * until it succeeds or the plan is exhausted. * * **Details** * * By default, a failing step can fallback even after emitting elements; set * `preventFallbackOnPartialStream` to fail instead of mixing partial output with * a later fallback. * * Attempts can be observed from outside the stream by passing * `options.onEvent`, which receives an `ExecutionPlan.Event` before each * attempt and after it settles; see `Effect.withExecutionPlan` for the handler * semantics. When a downstream consumer stops pulling early (for example * `Stream.take` outside the plan), the truncated attempt reports * `AttemptSuccess`: the consumer stopped, not the source. * * **Example** (Applying an execution plan) * * ```ts import.meta.vitest * import { Context, Effect, ExecutionPlan, Layer, Stream } from "effect" * * class Service extends Context.Service()("Service", { * make: Effect.succeed({ * stream: Stream.fail("A") as Stream.Stream * }) * }) { * static Bad = Layer.succeed(Service, Service.of({ stream: Stream.fail("A") })) * static Good = Layer.succeed(Service, Service.of({ stream: Stream.make(1, 2, 3) })) * } * * const plan = ExecutionPlan.make( * { provide: Service.Bad }, * { provide: Service.Good } * ) * * const stream = Stream.unwrap(Effect.map(Service, (_) => _.stream)) * * const program = Effect.gen(function*() { * const items = yield* stream.pipe(Stream.withExecutionPlan(plan), Stream.runCollect) * items // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability unstable * @category error handling * @since 3.16.0 */ ( policy: ExecutionPlan.ExecutionPlan<{ provides: Provides; input: Input; error: PolicyE; requirements: R2 }>, options?: { readonly preventFallbackOnPartialStream?: boolean | undefined readonly onEvent?: ((event: ExecutionPlan.Event) => Effect.Effect) | undefined } ): (self: Stream) => Stream | RX> /** * Applies an `ExecutionPlan` to a stream, retrying with step-provided resources * until it succeeds or the plan is exhausted. * * **Details** * * By default, a failing step can fallback even after emitting elements; set * `preventFallbackOnPartialStream` to fail instead of mixing partial output with * a later fallback. * * Attempts can be observed from outside the stream by passing * `options.onEvent`, which receives an `ExecutionPlan.Event` before each * attempt and after it settles; see `Effect.withExecutionPlan` for the handler * semantics. When a downstream consumer stops pulling early (for example * `Stream.take` outside the plan), the truncated attempt reports * `AttemptSuccess`: the consumer stopped, not the source. * * **Example** (Applying an execution plan) * * ```ts import.meta.vitest * import { Context, Effect, ExecutionPlan, Layer, Stream } from "effect" * * class Service extends Context.Service()("Service", { * make: Effect.succeed({ * stream: Stream.fail("A") as Stream.Stream * }) * }) { * static Bad = Layer.succeed(Service, Service.of({ stream: Stream.fail("A") })) * static Good = Layer.succeed(Service, Service.of({ stream: Stream.make(1, 2, 3) })) * } * * const plan = ExecutionPlan.make( * { provide: Service.Bad }, * { provide: Service.Good } * ) * * const stream = Stream.unwrap(Effect.map(Service, (_) => _.stream)) * * const program = Effect.gen(function*() { * const items = yield* stream.pipe(Stream.withExecutionPlan(plan), Stream.runCollect) * items // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability unstable * @category error handling * @since 3.16.0 */ ( self: Stream, policy: ExecutionPlan.ExecutionPlan<{ provides: Provides; input: Input; error: PolicyE; requirements: R2 }>, options?: { readonly preventFallbackOnPartialStream?: boolean | undefined readonly onEvent?: ((event: ExecutionPlan.Event) => Effect.Effect) | undefined } ): Stream | RX> } = dual((args) => isStream(args[0]), ( self: Stream, policy: ExecutionPlan.ExecutionPlan<{ provides: Provides input: Input error: PolicyE requirements: R2 }>, options?: { readonly preventFallbackOnPartialStream?: boolean | undefined readonly onEvent?: ((event: ExecutionPlan.Event) => Effect.Effect) | undefined } ): Stream | RX> => suspend(() => { const preventFallbackOnPartialStream = options?.preventFallbackOnPartialStream ?? false let i = 0 let meta: ExecutionPlan.Metadata = { attempt: 0, stepIndex: 0 } const provideMeta = provideServiceEffect( ExecutionPlan.CurrentMetadata, Effect.sync(() => { meta = { attempt: meta.attempt + 1, stepIndex: i } return meta }) ) const emitter = options?.onEvent === undefined ? undefined : internalExecutionPlan.makeEventEmitter(options.onEvent, () => meta) let attemptState: internalExecutionPlan.AttemptState | undefined const instrument: (attempt: Stream) => Stream = emitter === undefined ? identity : (attempt) => onExit( onStart( attempt, Effect.map(emitter.begin, (state) => { attemptState = state }) ), (exit) => Effect.suspend(() => { if (attemptState === undefined) return Effect.void const state = attemptState attemptState = undefined return emitter.end(state, exit) }) ) let lastError = Option.none() const loop: Stream< A, E | PolicyE, R2 | Exclude > = suspend(() => { const step = policy.steps[i] if (!step) { return fail(Option.getOrThrow(lastError)) } let nextStream: Stream> = provideMeta( instrument(provide(self, step.provide)) ) let receivedElements = false if (Option.isSome(lastError)) { const error = lastError.value let attempted = false const wrapped = nextStream // ensure the schedule is applied at least once nextStream = suspend(() => { if (attempted) return wrapped attempted = true return fail(error) }) nextStream = retryWithoutReset(nextStream, internalExecutionPlan.scheduleFromStep(step, false) as any) } else { const schedule = internalExecutionPlan.scheduleFromStep(step, true) nextStream = schedule ? retryWithoutReset(nextStream, schedule as any) : nextStream } return catch_( preventFallbackOnPartialStream ? onFirst(nextStream, (_) => { receivedElements = true return Effect.void }) : nextStream, (error) => { i++ if (preventFallbackOnPartialStream && receivedElements) { return fail(error) } lastError = Option.some(error) return loop } ) }) return loop })) /** * Takes the first `n` elements from this stream. * * **Details** * * Finite fractional values of `n` are rounded down. `NaN` and non-positive * values return `Stream.empty` without evaluating the source stream. * * **Example** (Taking values from the left) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.take(3), * Stream.runCollect * ) * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const take: { /** * Takes the first `n` elements from this stream. * * **Details** * * Finite fractional values of `n` are rounded down. `NaN` and non-positive * values return `Stream.empty` without evaluating the source stream. * * **Example** (Taking values from the left) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.take(3), * Stream.runCollect * ) * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (n: number): (self: Stream) => Stream /** * Takes the first `n` elements from this stream. * * **Details** * * Finite fractional values of `n` are rounded down. `NaN` and non-positive * values return `Stream.empty` without evaluating the source stream. * * **Example** (Taking values from the left) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.take(3), * Stream.runCollect * ) * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (self: Stream, n: number): Stream } = dual( 2, (self: Stream, n: number): Stream => { const count = Count.normalize(n) return count === 0 ? empty : takeUntil(self, (_, i) => i === (count - 1)) } ) /** * Emits byte chunks until the configured limit would be exceeded, then drops * the crossing chunk and switches to a fallback stream. * * **Example** (Truncating at a byte limit) * * ```ts import.meta.vitest * import { ByteSize, Effect, Stream } from "effect" * * const program = Stream.make( * new Uint8Array([1, 2]), * new Uint8Array([3, 4, 5]) * ).pipe( * Stream.limitBytes(ByteSize.bytes(4), () => Stream.empty), * Stream.runCollect, * Effect.map((chunks) => chunks.map((chunk) => [...chunk])) * ) * * await Effect.runPromise(program) // => [[1, 2]] * ``` * * @stability unstable * @category filtering * @since 4.0.0 */ export const limitBytes: { /** * Emits byte chunks until the configured limit would be exceeded, then drops * the crossing chunk and switches to a fallback stream. * * **Example** (Truncating at a byte limit) * * ```ts import.meta.vitest * import { ByteSize, Effect, Stream } from "effect" * * const program = Stream.make( * new Uint8Array([1, 2]), * new Uint8Array([3, 4, 5]) * ).pipe( * Stream.limitBytes(ByteSize.bytes(4), () => Stream.empty), * Stream.runCollect, * Effect.map((chunks) => chunks.map((chunk) => [...chunk])) * ) * * await Effect.runPromise(program) // => [[1, 2]] * ``` * * @stability unstable * @category filtering * @since 4.0.0 */ (bytes: ByteSize.Input, onLimitReached: LazyArg>): (self: Stream) => Stream /** * Emits byte chunks until the configured limit would be exceeded, then drops * the crossing chunk and switches to a fallback stream. * * **Example** (Truncating at a byte limit) * * ```ts import.meta.vitest * import { ByteSize, Effect, Stream } from "effect" * * const program = Stream.make( * new Uint8Array([1, 2]), * new Uint8Array([3, 4, 5]) * ).pipe( * Stream.limitBytes(ByteSize.bytes(4), () => Stream.empty), * Stream.runCollect, * Effect.map((chunks) => chunks.map((chunk) => [...chunk])) * ) * * await Effect.runPromise(program) // => [[1, 2]] * ``` * * @stability unstable * @category filtering * @since 4.0.0 */ ( self: Stream, bytes: ByteSize.Input, onLimitReached: LazyArg> ): Stream } = dual(3, ( self: Stream, bytes: ByteSize.Input, onLimitReached: LazyArg> ): Stream => suspend(() => { const limit = ByteSize.fromInputUnsafe(bytes) let size = BigInt(0) let limitReached = false return concat( takeWhile(self, (chunk) => { const nextSize = size + BigInt(chunk.length) if (nextSize > limit) { limitReached = true return false } size = nextSize return true }), suspend(() => limitReached ? onLimitReached() : empty) ) })) /** * Keeps the last `n` elements from this stream. * * **Details** * * Finite fractional values of `n` are rounded down. `NaN` and non-positive * values return `Stream.empty` without evaluating the source stream. * * **Example** (Taking elements from the right) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.range(1, 6).pipe( * Stream.takeRight(3), * Stream.runCollect * ) * values // => [ 4, 5, 6 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const takeRight: { /** * Keeps the last `n` elements from this stream. * * **Details** * * Finite fractional values of `n` are rounded down. `NaN` and non-positive * values return `Stream.empty` without evaluating the source stream. * * **Example** (Taking elements from the right) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.range(1, 6).pipe( * Stream.takeRight(3), * Stream.runCollect * ) * values // => [ 4, 5, 6 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (n: number): (self: Stream) => Stream /** * Keeps the last `n` elements from this stream. * * **Details** * * Finite fractional values of `n` are rounded down. `NaN` and non-positive * values return `Stream.empty` without evaluating the source stream. * * **Example** (Taking elements from the right) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.range(1, 6).pipe( * Stream.takeRight(3), * Stream.runCollect * ) * values // => [ 4, 5, 6 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (self: Stream, n: number): Stream } = dual( 2, (self: Stream, n: number): Stream => { const count = Count.normalize(n) if (count === 0) return empty return mapAccumArray(self, MutableList.make, (list, arr) => { MutableList.appendAll(list, arr) if (list.length > count) { MutableList.takeNVoid(list, list.length - count) } return [list, emptyArr] }, { onHalt(list) { return MutableList.takeAll(list) } }) } ) /** * Takes elements until the predicate matches. * * **Details** * * When `excludeLast` is `true`, the matching element is dropped. * * **Example** (Taking until a predicate matches) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(1, 5) * * const program = Effect.gen(function*() { * const inclusive = yield* stream.pipe( * Stream.takeUntil((n) => n % 3 === 0), * Stream.runCollect * ) * inclusive // => [ 1, 2, 3 ] * * const exclusive = yield* stream.pipe( * Stream.takeUntil((n) => n % 3 === 0, { excludeLast: true }), * Stream.runCollect * ) * exclusive // => [ 1, 2 ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const takeUntil: { /** * Takes elements until the predicate matches. * * **Details** * * When `excludeLast` is `true`, the matching element is dropped. * * **Example** (Taking until a predicate matches) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(1, 5) * * const program = Effect.gen(function*() { * const inclusive = yield* stream.pipe( * Stream.takeUntil((n) => n % 3 === 0), * Stream.runCollect * ) * inclusive // => [ 1, 2, 3 ] * * const exclusive = yield* stream.pipe( * Stream.takeUntil((n) => n % 3 === 0, { excludeLast: true }), * Stream.runCollect * ) * exclusive // => [ 1, 2 ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ ( predicate: (a: NoInfer, n: number) => boolean, options?: { readonly excludeLast?: boolean | undefined } ): (self: Stream) => Stream /** * Takes elements until the predicate matches. * * **Details** * * When `excludeLast` is `true`, the matching element is dropped. * * **Example** (Taking until a predicate matches) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(1, 5) * * const program = Effect.gen(function*() { * const inclusive = yield* stream.pipe( * Stream.takeUntil((n) => n % 3 === 0), * Stream.runCollect * ) * inclusive // => [ 1, 2, 3 ] * * const exclusive = yield* stream.pipe( * Stream.takeUntil((n) => n % 3 === 0, { excludeLast: true }), * Stream.runCollect * ) * exclusive // => [ 1, 2 ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ ( self: Stream, predicate: (a: A, n: number) => boolean, options?: { readonly excludeLast?: boolean | undefined } ): Stream } = dual( (args) => isStream(args[0]), (self: Stream, predicate: (a: A, n: number) => boolean, options?: { readonly excludeLast?: boolean | undefined }): Stream => transformPull(self, (pull, _scope) => Effect.sync(() => { let i = 0 let done = false const pump: Pull.Pull, E, void, R> = Effect.flatMap( Effect.suspend(() => done ? Cause.done() : pull), (chunk) => { const index = chunk.findIndex((a) => predicate(a, i++)) if (index >= 0) { done = true const arr = chunk.slice(0, options?.excludeLast ? index : index + 1) return Arr.isReadonlyArrayNonEmpty(arr) ? Effect.succeed(arr) : Cause.done() } return Effect.succeed(chunk) } ) return pump })) ) /** * Takes stream elements until an effectful predicate returns `true`. * * **When to use** * * Use when the stopping condition needs an Effect or service and predicate * failure should fail the stream. * * **Example** (Taking until an effectful predicate matches) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(1, 5).pipe( * Stream.takeUntilEffect((n) => Effect.succeed(n % 3 === 0)), * Stream.runCollect * ) * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const takeUntilEffect: { /** * Takes stream elements until an effectful predicate returns `true`. * * **When to use** * * Use when the stopping condition needs an Effect or service and predicate * failure should fail the stream. * * **Example** (Taking until an effectful predicate matches) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(1, 5).pipe( * Stream.takeUntilEffect((n) => Effect.succeed(n % 3 === 0)), * Stream.runCollect * ) * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ ( predicate: (a: NoInfer, n: number) => Effect.Effect, options?: { readonly excludeLast?: boolean | undefined } ): (self: Stream) => Stream /** * Takes stream elements until an effectful predicate returns `true`. * * **When to use** * * Use when the stopping condition needs an Effect or service and predicate * failure should fail the stream. * * **Example** (Taking until an effectful predicate matches) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(1, 5).pipe( * Stream.takeUntilEffect((n) => Effect.succeed(n % 3 === 0)), * Stream.runCollect * ) * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ ( self: Stream, predicate: (a: A, n: number) => Effect.Effect, options?: { readonly excludeLast?: boolean | undefined } ): Stream } = dual((args) => isStream(args[0]), ( self: Stream, predicate: (a: A, n: number) => Effect.Effect, options?: { readonly excludeLast?: boolean | undefined } ): Stream => transformPull(self, (pull, _scope) => Effect.sync(() => { let i = 0 let done = false return Effect.gen(function*() { if (done) return yield* Cause.done() const chunk = yield* pull for (let j = 0; j < chunk.length; j++) { if (yield* predicate(chunk[j], i++)) { done = true const arr = chunk.slice(0, options?.excludeLast ? j : j + 1) return Arr.isReadonlyArrayNonEmpty(arr) ? arr : yield* Cause.done() } } return chunk }) }))) /** * Takes the longest initial prefix of elements that satisfy the predicate. * * **Example** (Taking while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(1, 5).pipe( * Stream.takeWhile((n) => n % 3 !== 0) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const takeWhile: { /** * Takes the longest initial prefix of elements that satisfy the predicate. * * **Example** (Taking while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(1, 5).pipe( * Stream.takeWhile((n) => n % 3 !== 0) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (refinement: (a: NoInfer, n: number) => a is B): (self: Stream) => Stream /** * Takes the longest initial prefix of elements that satisfy the predicate. * * **Example** (Taking while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(1, 5).pipe( * Stream.takeWhile((n) => n % 3 !== 0) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (predicate: (a: NoInfer, n: number) => boolean): (self: Stream) => Stream /** * Takes the longest initial prefix of elements that satisfy the predicate. * * **Example** (Taking while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(1, 5).pipe( * Stream.takeWhile((n) => n % 3 !== 0) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (self: Stream, refinement: (a: NoInfer, n: number) => a is B): Stream /** * Takes the longest initial prefix of elements that satisfy the predicate. * * **Example** (Taking while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(1, 5).pipe( * Stream.takeWhile((n) => n % 3 !== 0) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (self: Stream, predicate: (a: NoInfer, n: number) => boolean): Stream } = dual( 2, ( self: Stream, predicate: (a: A, n: number) => boolean ): Stream => transformPull(self, (pull, _scope) => Effect.sync(() => { let i = 0 let done = false const pump: Pull.Pull, E, void, R> = Effect.flatMap( Effect.suspend(() => done ? Cause.done() : pull), (chunk) => { const out: Array = [] for (let j = 0; j < chunk.length; j++) { if (!predicate(chunk[j], i++)) { done = true break } out.push(chunk[j]) } return Arr.isReadonlyArrayNonEmpty(out) ? Effect.succeed(out) : done ? Cause.done() : pump } ) return pump })) ) /** * Takes the longest initial prefix accepted by a `Filter` and emits the * filter's success values. * * **When to use** * * Use to keep the leading stream elements that a `Filter` accepts, emit the * filter's success values, and stop at the first filter failure. * * **Details** * * The stream stops at the first `Result.fail` returned by the filter. * * @see {@link takeWhile} for keeping original elements with a boolean predicate or refinement * @see {@link filterMap} for filtering across the whole stream instead of only the leading prefix * @see {@link dropWhileFilter} for dropping the accepted prefix and keeping the remaining original elements * * @stability stable * @category filtering * @since 4.0.0 */ export const takeWhileFilter: { /** * Takes the longest initial prefix accepted by a `Filter` and emits the * filter's success values. * * **When to use** * * Use to keep the leading stream elements that a `Filter` accepts, emit the * filter's success values, and stop at the first filter failure. * * **Details** * * The stream stops at the first `Result.fail` returned by the filter. * * @see {@link takeWhile} for keeping original elements with a boolean predicate or refinement * @see {@link filterMap} for filtering across the whole stream instead of only the leading prefix * @see {@link dropWhileFilter} for dropping the accepted prefix and keeping the remaining original elements * * @stability stable * @category filtering * @since 4.0.0 */ (f: Filter.Filter, B, X>): (self: Stream) => Stream /** * Takes the longest initial prefix accepted by a `Filter` and emits the * filter's success values. * * **When to use** * * Use to keep the leading stream elements that a `Filter` accepts, emit the * filter's success values, and stop at the first filter failure. * * **Details** * * The stream stops at the first `Result.fail` returned by the filter. * * @see {@link takeWhile} for keeping original elements with a boolean predicate or refinement * @see {@link filterMap} for filtering across the whole stream instead of only the leading prefix * @see {@link dropWhileFilter} for dropping the accepted prefix and keeping the remaining original elements * * @stability stable * @category filtering * @since 4.0.0 */ (self: Stream, f: Filter.Filter, B, X>): Stream } = dual( 2, ( self: Stream, filter: Filter.Filter, B, X> ): Stream => transformPull(self, (pull, _scope) => Effect.sync(() => { let done = false const pump: Pull.Pull, E, void, R> = Effect.flatMap( Effect.suspend(() => done ? Cause.done() : pull), (chunk) => { const out: Array = [] for (let j = 0; j < chunk.length; j++) { const result = filter(chunk[j]) if (Result.isFailure(result)) { done = true break } out.push(result.success) } return Arr.isReadonlyArrayNonEmpty(out) ? Effect.succeed(out) : done ? Cause.done() : pump } ) return pump })) ) /** * Takes elements from the stream while the effectful predicate is `true`. * * **When to use** * * Use when the leading-prefix predicate needs an Effect or service and the * stream should stop before the first false result. * * **Example** (Effectfully taking while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(1, 5).pipe( * Stream.takeWhileEffect((n) => Effect.succeed(n % 3 !== 0)), * Stream.runCollect * ) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 4.0.0 */ export const takeWhileEffect: { /** * Takes elements from the stream while the effectful predicate is `true`. * * **When to use** * * Use when the leading-prefix predicate needs an Effect or service and the * stream should stop before the first false result. * * **Example** (Effectfully taking while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(1, 5).pipe( * Stream.takeWhileEffect((n) => Effect.succeed(n % 3 !== 0)), * Stream.runCollect * ) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 4.0.0 */ (predicate: (a: NoInfer, n: number) => Effect.Effect): (self: Stream) => Stream /** * Takes elements from the stream while the effectful predicate is `true`. * * **When to use** * * Use when the leading-prefix predicate needs an Effect or service and the * stream should stop before the first false result. * * **Example** (Effectfully taking while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(1, 5).pipe( * Stream.takeWhileEffect((n) => Effect.succeed(n % 3 !== 0)), * Stream.runCollect * ) * result // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 4.0.0 */ ( self: Stream, predicate: (a: NoInfer, n: number) => Effect.Effect ): Stream } = dual(2, ( self: Stream, predicate: (a: NoInfer, n: number) => Effect.Effect ) => takeUntilEffect(self, (a, n) => Effect.map( predicate(a, n), (b) => !b ), { excludeLast: true })) /** * Drops the first `n` elements from this stream. * * **Details** * * Finite fractional values of `n` are rounded down. `NaN` and non-positive * values return the source stream unchanged. * * **Example** (Dropping values from the left) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * const result = Stream.drop(stream, 2) * * const program = Effect.gen(function*() { * const items = yield* Stream.runCollect(result) * items // => [ 3, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const drop: { /** * Drops the first `n` elements from this stream. * * **Details** * * Finite fractional values of `n` are rounded down. `NaN` and non-positive * values return the source stream unchanged. * * **Example** (Dropping values from the left) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * const result = Stream.drop(stream, 2) * * const program = Effect.gen(function*() { * const items = yield* Stream.runCollect(result) * items // => [ 3, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (n: number): (self: Stream) => Stream /** * Drops the first `n` elements from this stream. * * **Details** * * Finite fractional values of `n` are rounded down. `NaN` and non-positive * values return the source stream unchanged. * * **Example** (Dropping values from the left) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * const result = Stream.drop(stream, 2) * * const program = Effect.gen(function*() { * const items = yield* Stream.runCollect(result) * items // => [ 3, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (self: Stream, n: number): Stream } = dual( 2, (self: Stream, n: number): Stream => { const count = Count.normalize(n) if (count === 0) return self return transformPull(self, (pull, _scope) => Effect.sync(() => { let dropped = 0 const pump: Pull.Pull, E, void, R> = pull.pipe( Effect.flatMap((chunk) => { if (dropped >= count) return Effect.succeed(chunk) dropped += chunk.length if (dropped <= count) return pump return Effect.succeed(chunk.slice(count - dropped) as Arr.NonEmptyArray) }) ) return pump })) } ) /** * Drops elements until the specified predicate evaluates to `true`, then drops * that matching element. * * **Example** (Dropping until a predicate matches) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * const result = Stream.dropUntil(stream, (n) => n >= 3) * * await Effect.runPromise(Effect.gen(function*() { * const output = yield* Stream.runCollect(result) * output // => [ 4, 5 ] * })) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const dropUntil: { /** * Drops elements until the specified predicate evaluates to `true`, then drops * that matching element. * * **Example** (Dropping until a predicate matches) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * const result = Stream.dropUntil(stream, (n) => n >= 3) * * await Effect.runPromise(Effect.gen(function*() { * const output = yield* Stream.runCollect(result) * output // => [ 4, 5 ] * })) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (predicate: (a: NoInfer, index: number) => boolean): (self: Stream) => Stream /** * Drops elements until the specified predicate evaluates to `true`, then drops * that matching element. * * **Example** (Dropping until a predicate matches) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * const result = Stream.dropUntil(stream, (n) => n >= 3) * * await Effect.runPromise(Effect.gen(function*() { * const output = yield* Stream.runCollect(result) * output // => [ 4, 5 ] * })) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ ( self: Stream, predicate: (a: NoInfer, index: number) => boolean ): Stream } = dual(2, ( self: Stream, predicate: (a: NoInfer, index: number) => boolean ): Stream => drop(dropWhile(self, (a, i) => !predicate(a, i)), 1)) /** * Drops all elements of the stream until the specified effectful predicate * evaluates to `true`. * * **When to use** * * Use when dropping the leading prefix requires an Effect or service and the * first matching element should also be dropped. * * **Example** (Dropping until an effectful predicate matches) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(1, 5).pipe( * Stream.dropUntilEffect((n) => Effect.succeed(n % 3 === 0)), * Stream.runCollect * ) * result // => [ 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const dropUntilEffect: { /** * Drops all elements of the stream until the specified effectful predicate * evaluates to `true`. * * **When to use** * * Use when dropping the leading prefix requires an Effect or service and the * first matching element should also be dropped. * * **Example** (Dropping until an effectful predicate matches) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(1, 5).pipe( * Stream.dropUntilEffect((n) => Effect.succeed(n % 3 === 0)), * Stream.runCollect * ) * result // => [ 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ ( predicate: (a: NoInfer, index: number) => Effect.Effect ): (self: Stream) => Stream /** * Drops all elements of the stream until the specified effectful predicate * evaluates to `true`. * * **When to use** * * Use when dropping the leading prefix requires an Effect or service and the * first matching element should also be dropped. * * **Example** (Dropping until an effectful predicate matches) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(1, 5).pipe( * Stream.dropUntilEffect((n) => Effect.succeed(n % 3 === 0)), * Stream.runCollect * ) * result // => [ 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ ( self: Stream, predicate: (a: NoInfer, index: number) => Effect.Effect ): Stream } = dual(2, ( self: Stream, predicate: (a: NoInfer, index: number) => Effect.Effect ): Stream => drop( dropWhileEffect( self, (a, i) => Effect.map(predicate(a, i), (b) => !b) ), 1 )) /** * Drops elements from the stream while the specified predicate evaluates to `true`. * * **Example** (Dropping while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.dropWhile((n) => n < 3), * Stream.runCollect * ) * values // => [ 3, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const dropWhile: { /** * Drops elements from the stream while the specified predicate evaluates to `true`. * * **Example** (Dropping while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.dropWhile((n) => n < 3), * Stream.runCollect * ) * values // => [ 3, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (predicate: (a: NoInfer, index: number) => boolean): (self: Stream) => Stream /** * Drops elements from the stream while the specified predicate evaluates to `true`. * * **Example** (Dropping while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.dropWhile((n) => n < 3), * Stream.runCollect * ) * values // => [ 3, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ ( self: Stream, predicate: (a: NoInfer, index: number) => boolean ): Stream } = dual(2, ( self: Stream, predicate: (a: A, index: number) => boolean ): Stream => transformPull(self, (pull, _scope) => Effect.sync(() => { let dropping = true let index = 0 const filtered: Pull.Pull, E> = Effect.flatMap(pull, (arr) => { const found = arr.findIndex((a) => !predicate(a, index++)) if (found === -1) return filtered dropping = false return Effect.succeed(arr.slice(found) as Arr.NonEmptyArray) }) return Effect.suspend(() => dropping ? filtered : pull) }))) /** * Drops elements while the filter succeeds. * * **When to use** * * Use when you need to remove a leading stream prefix based on a synchronous * `Filter` result while preserving the remaining original stream elements. * * **Details** * * `Result.succeed` drops the current element. The first `Result.fail` stops * dropping, emits that original element, and the rest of the source stream is * emitted without further filtering. * * @see {@link dropWhile} for boolean predicate prefix dropping * @see {@link takeWhileFilter} for keeping the accepted prefix as filter success values * @see {@link dropWhileEffect} for effectful predicate prefix dropping * * @stability stable * @category filtering * @since 4.0.0 */ export const dropWhileFilter: { /** * Drops elements while the filter succeeds. * * **When to use** * * Use when you need to remove a leading stream prefix based on a synchronous * `Filter` result while preserving the remaining original stream elements. * * **Details** * * `Result.succeed` drops the current element. The first `Result.fail` stops * dropping, emits that original element, and the rest of the source stream is * emitted without further filtering. * * @see {@link dropWhile} for boolean predicate prefix dropping * @see {@link takeWhileFilter} for keeping the accepted prefix as filter success values * @see {@link dropWhileEffect} for effectful predicate prefix dropping * * @stability stable * @category filtering * @since 4.0.0 */ (filter: Filter.Filter, B, X>): (self: Stream) => Stream /** * Drops elements while the filter succeeds. * * **When to use** * * Use when you need to remove a leading stream prefix based on a synchronous * `Filter` result while preserving the remaining original stream elements. * * **Details** * * `Result.succeed` drops the current element. The first `Result.fail` stops * dropping, emits that original element, and the rest of the source stream is * emitted without further filtering. * * @see {@link dropWhile} for boolean predicate prefix dropping * @see {@link takeWhileFilter} for keeping the accepted prefix as filter success values * @see {@link dropWhileEffect} for effectful predicate prefix dropping * * @stability stable * @category filtering * @since 4.0.0 */ (self: Stream, filter: Filter.Filter, B, X>): Stream } = dual(2, ( self: Stream, filter: Filter.Filter, B, X> ): Stream => transformPull(self, (pull, _scope) => Effect.sync(() => { let dropping = true const filtered: Pull.Pull, E> = Effect.flatMap(pull, (arr) => { const found = arr.findIndex((a) => Result.isFailure(filter(a))) if (found === -1) return filtered dropping = false return Effect.succeed(arr.slice(found) as Arr.NonEmptyArray) }) return Effect.suspend(() => dropping ? filtered : pull) }))) /** * Drops elements while the specified effectful predicate evaluates to `true`. * * **Example** (Effectfully dropping while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.dropWhileEffect((n) => Effect.succeed(n < 3)), * Stream.runCollect * ) * result // => [ 3, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const dropWhileEffect: { /** * Drops elements while the specified effectful predicate evaluates to `true`. * * **Example** (Effectfully dropping while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.dropWhileEffect((n) => Effect.succeed(n < 3)), * Stream.runCollect * ) * result // => [ 3, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ ( predicate: (a: NoInfer, index: number) => Effect.Effect ): (self: Stream) => Stream /** * Drops elements while the specified effectful predicate evaluates to `true`. * * **Example** (Effectfully dropping while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.dropWhileEffect((n) => Effect.succeed(n < 3)), * Stream.runCollect * ) * result // => [ 3, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ ( self: Stream, predicate: (a: A, index: number) => Effect.Effect ): Stream } = dual(2, ( self: Stream, predicate: (a: NoInfer, index: number) => Effect.Effect ): Stream => transformPull(self, (pull, _scope) => Effect.sync(() => { let dropping = true let index = 0 const filtered: Pull.Pull, E | E2, void, R2> = Effect.gen(function*() { while (true) { const arr = yield* pull for (let i = 0; i < arr.length; i++) { const drop = yield* predicate(arr[i], index++) if (drop) continue dropping = false return arr.slice(i) as Arr.NonEmptyArray } } }) return Effect.suspend((): Pull.Pull, E | E2, void, R | R2> => dropping ? filtered : pull ) }))) /** * Drops the last specified number of elements from this stream. * * **Details** * * Keeps the last `n` elements in memory to drop them on completion. Finite * fractional values of `n` are rounded down. `NaN` and non-positive values * return the source stream unchanged. * * **Example** (Dropping values from the right) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.dropRight(2), * Stream.runCollect * ) * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ export const dropRight: { /** * Drops the last specified number of elements from this stream. * * **Details** * * Keeps the last `n` elements in memory to drop them on completion. Finite * fractional values of `n` are rounded down. `NaN` and non-positive values * return the source stream unchanged. * * **Example** (Dropping values from the right) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.dropRight(2), * Stream.runCollect * ) * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (n: number): (self: Stream) => Stream /** * Drops the last specified number of elements from this stream. * * **Details** * * Keeps the last `n` elements in memory to drop them on completion. Finite * fractional values of `n` are rounded down. `NaN` and non-positive values * return the source stream unchanged. * * **Example** (Dropping values from the right) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.dropRight(2), * Stream.runCollect * ) * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category filtering * @since 2.0.0 */ (self: Stream, n: number): Stream } = dual( 2, (self: Stream, n: number): Stream => { const count = Count.normalize(n) if (count === 0) return self return transformPull(self, (pull, _scope) => Effect.sync(() => { const list = MutableList.make() const emit: Pull.Pull, E> = Effect.flatMap(pull, (arr) => { MutableList.appendAllUnsafe(list, arr) const toTake = list.length - count const items = MutableList.takeN(list, toTake) return Arr.isArrayNonEmpty(items) ? Effect.succeed(items) : emit }) return emit })) } ) /** * Exposes the underlying chunks as a stream of non-empty arrays. * * **Example** (Exposing stream chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const chunks = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.rechunk(2), * Stream.chunks, * Stream.runCollect * ) * chunks // => [ [ 1, 2 ], [ 3, 4 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ export const chunks = (self: Stream): Stream, E, R> => self.channel.pipe( Channel.map(Arr.of), fromChannel ) /** * Groups the stream into arrays of the specified size, preserving element order. * * **Details** * * Finite fractional sizes are rounded down. `NaN` and non-positive sizes are * treated as `1`. * * **Example** (Rechunking stream elements) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.rechunk(2), * Stream.chunks, * Stream.runCollect * ) * result // => [ [ 1, 2 ], [ 3, 4 ], [ 5 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ export const rechunk: { /** * Groups the stream into arrays of the specified size, preserving element order. * * **Details** * * Finite fractional sizes are rounded down. `NaN` and non-positive sizes are * treated as `1`. * * **Example** (Rechunking stream elements) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.rechunk(2), * Stream.chunks, * Stream.runCollect * ) * result // => [ [ 1, 2 ], [ 3, 4 ], [ 5 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (size: number): (self: Stream) => Stream /** * Groups the stream into arrays of the specified size, preserving element order. * * **Details** * * Finite fractional sizes are rounded down. `NaN` and non-positive sizes are * treated as `1`. * * **Example** (Rechunking stream elements) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.rechunk(2), * Stream.chunks, * Stream.runCollect * ) * result // => [ [ 1, 2 ], [ 3, 4 ], [ 5 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (self: Stream, size: number): Stream } = dual(2, (self: Stream, target: number): Stream => { target = Count.normalizeNonEmpty(target) return transformPull(self, (pull, _scope) => Effect.sync(() => { let chunk = Arr.empty() as Arr.NonEmptyArray let index = 0 let current: Arr.NonEmptyReadonlyArray | undefined let done = false return Effect.suspend(function loop(): Pull.Pull, E, void, R> { if (done) return Cause.done() else if (current === undefined) { return Effect.flatMap(pull, (arr) => { if (chunk.length === 0 && arr.length === target) { return Effect.succeed(arr) } else if (chunk.length + arr.length < target) { for (let i = 0; i < arr.length; i++) { chunk.push(arr[i]) } return loop() } current = arr return loop() }) } for (; index < current.length;) { chunk.push(current[index++]) if (chunk.length === target) { const result = chunk chunk = [] as any return Effect.succeed(result) } } index = 0 current = undefined return loop() }).pipe( Pull.catchDone(() => { if (chunk.length === 0) return Cause.done() const result = chunk done = true chunk = [] as any return Effect.succeed(result) }) ) })) }) /** * Emits a sliding window of `n` elements. * * **Details** * * Finite fractional window sizes are rounded down. `NaN` and non-positive * sizes are treated as `1`. * * **Example** (Emitting sliding windows) * * ```ts import.meta.vitest * import { Effect, pipe, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* pipe( * Stream.make(1, 2, 3, 4, 5), * Stream.sliding(2), * Stream.runCollect * ) * result // => [ [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ] ] * })) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ export const sliding: { /** * Emits a sliding window of `n` elements. * * **Details** * * Finite fractional window sizes are rounded down. `NaN` and non-positive * sizes are treated as `1`. * * **Example** (Emitting sliding windows) * * ```ts import.meta.vitest * import { Effect, pipe, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* pipe( * Stream.make(1, 2, 3, 4, 5), * Stream.sliding(2), * Stream.runCollect * ) * result // => [ [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ] ] * })) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (chunkSize: number): (self: Stream) => Stream, E, R> /** * Emits a sliding window of `n` elements. * * **Details** * * Finite fractional window sizes are rounded down. `NaN` and non-positive * sizes are treated as `1`. * * **Example** (Emitting sliding windows) * * ```ts import.meta.vitest * import { Effect, pipe, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* pipe( * Stream.make(1, 2, 3, 4, 5), * Stream.sliding(2), * Stream.runCollect * ) * result // => [ [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ] ] * })) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (self: Stream, chunkSize: number): Stream, E, R> } = dual( 2, (self: Stream, chunkSize: number): Stream, E, R> => slidingSize(self, chunkSize, 1) ) /** * Emits sliding windows of `chunkSize` elements, advancing by `stepSize`. * * **Details** * * Finite fractional window and step sizes are rounded down. `NaN` and * non-positive sizes are treated as `1`. * * **Example** (Emitting sliding windows with a step size) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const chunks = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.slidingSize(3, 2), * Stream.runCollect * ) * chunks // => [ [ 1, 2, 3 ], [ 3, 4, 5 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ export const slidingSize: { /** * Emits sliding windows of `chunkSize` elements, advancing by `stepSize`. * * **Details** * * Finite fractional window and step sizes are rounded down. `NaN` and * non-positive sizes are treated as `1`. * * **Example** (Emitting sliding windows with a step size) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const chunks = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.slidingSize(3, 2), * Stream.runCollect * ) * chunks // => [ [ 1, 2, 3 ], [ 3, 4, 5 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (chunkSize: number, stepSize: number): (self: Stream) => Stream, E, R> /** * Emits sliding windows of `chunkSize` elements, advancing by `stepSize`. * * **Details** * * Finite fractional window and step sizes are rounded down. `NaN` and * non-positive sizes are treated as `1`. * * **Example** (Emitting sliding windows with a step size) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const chunks = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.slidingSize(3, 2), * Stream.runCollect * ) * chunks // => [ [ 1, 2, 3 ], [ 3, 4, 5 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (self: Stream, chunkSize: number, stepSize: number): Stream, E, R> } = dual( 3, (self: Stream, chunkSize: number, stepSize: number): Stream, E, R> => { const windowSize = Count.normalizeNonEmpty(chunkSize) const step = Count.normalizeNonEmpty(stepSize) return transformPull(self, (upstream, _scope) => Effect.sync(() => { let cause: Cause.Cause | null = null const list = MutableList.make() let emitted = false let skip = 0 const pull: Pull.Pull< Arr.NonEmptyReadonlyArray>, E | Cause.Done > = Effect.matchCauseEffect(upstream, { onSuccess(arr) { MutableList.appendAllUnsafe(list, arr) if (skip > 0) { const length = list.length MutableList.takeNVoid(list, skip) skip = Math.max(0, skip - length) } if (list.length < windowSize) return pull emitted = true const chunks = [] as any as Arr.NonEmptyArray> while (list.length >= windowSize) { if (windowSize === step) { chunks.push(MutableList.takeN(list, windowSize) as any) } else { chunks.push(MutableList.toArrayN(list, windowSize) as any) const length = list.length MutableList.takeNVoid(list, step) skip = Math.max(0, step - length) } } return Effect.succeed(chunks) }, onFailure(cause_) { // The buffer has already advanced by step after a full window. if (list.length === 0 || (emitted && list.length <= windowSize - step)) return Effect.failCause(cause_) cause = cause_ return Effect.succeed(Arr.of(MutableList.takeAll(list) as any)) } }) return Effect.suspend(() => cause ? Effect.failCause(cause) : pull) })) } ) /** * Splits the stream into non-empty groups whenever the predicate matches. * * **Details** * * Matching elements act as delimiters and are not included in the output. * * **Example** (Splitting on matching values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(0, 9).pipe( * Stream.split((n) => n % 4 === 0), * Stream.runCollect * ) * result // => [ [ 1, 2, 3 ], [ 5, 6, 7 ], [ 9 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ export const split: { /** * Splits the stream into non-empty groups whenever the predicate matches. * * **Details** * * Matching elements act as delimiters and are not included in the output. * * **Example** (Splitting on matching values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(0, 9).pipe( * Stream.split((n) => n % 4 === 0), * Stream.runCollect * ) * result // => [ [ 1, 2, 3 ], [ 5, 6, 7 ], [ 9 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (refinement: Refinement, B>): (self: Stream) => Stream>, E, R> /** * Splits the stream into non-empty groups whenever the predicate matches. * * **Details** * * Matching elements act as delimiters and are not included in the output. * * **Example** (Splitting on matching values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(0, 9).pipe( * Stream.split((n) => n % 4 === 0), * Stream.runCollect * ) * result // => [ [ 1, 2, 3 ], [ 5, 6, 7 ], [ 9 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (predicate: Predicate>): (self: Stream) => Stream, E, R> /** * Splits the stream into non-empty groups whenever the predicate matches. * * **Details** * * Matching elements act as delimiters and are not included in the output. * * **Example** (Splitting on matching values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(0, 9).pipe( * Stream.split((n) => n % 4 === 0), * Stream.runCollect * ) * result // => [ [ 1, 2, 3 ], [ 5, 6, 7 ], [ 9 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (self: Stream, refinement: Refinement): Stream>, E, R> /** * Splits the stream into non-empty groups whenever the predicate matches. * * **Details** * * Matching elements act as delimiters and are not included in the output. * * **Example** (Splitting on matching values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.range(0, 9).pipe( * Stream.split((n) => n % 4 === 0), * Stream.runCollect * ) * result // => [ [ 1, 2, 3 ], [ 5, 6, 7 ], [ 9 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (self: Stream, predicate: Predicate): Stream, E, R> } = dual(2, ( self: Stream, predicate: Predicate> ): Stream, E, R> => mapAccumArray(self, Arr.empty, (acc, arr) => { const out = Arr.empty>() for (let i = 0; i < arr.length; i++) { if (predicate(arr[i])) { if (Arr.isArrayNonEmpty(acc)) { out.push(acc) acc = [] } } else { acc.push(arr[i]) } } return [acc, out] }, { onHalt(arr) { return Arr.isArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr } })) /** * Combines elements from this stream and the specified stream by repeatedly * applying a stateful function that can pull from either side. * * **Details** * * Where possible, prefer `Stream.combineArray` for a more efficient * implementation. * * **Example** (Combining streams with state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.combine( * Stream.make("A", "B", "C"), * Stream.make(1, 2, 3), * () => true, * (takeLeft, pullLeft, pullRight) => * takeLeft * ? Effect.map(pullLeft, (value) => [`L:${value}`, false] as const) * : Effect.map(pullRight, (value) => [`R:${value}`, true] as const) * ) * * const program = Effect.gen(function*() { * const output = yield* Stream.runCollect(stream) * output // => [ 'L:A', 'R:1', 'L:B', 'R:2', 'L:C', 'R:3' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ export const combine: { /** * Combines elements from this stream and the specified stream by repeatedly * applying a stateful function that can pull from either side. * * **Details** * * Where possible, prefer `Stream.combineArray` for a more efficient * implementation. * * **Example** (Combining streams with state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.combine( * Stream.make("A", "B", "C"), * Stream.make(1, 2, 3), * () => true, * (takeLeft, pullLeft, pullRight) => * takeLeft * ? Effect.map(pullLeft, (value) => [`L:${value}`, false] as const) * : Effect.map(pullRight, (value) => [`R:${value}`, true] as const) * ) * * const program = Effect.gen(function*() { * const output = yield* Stream.runCollect(stream) * output // => [ 'L:A', 'R:1', 'L:B', 'R:2', 'L:C', 'R:3' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ ( that: Stream, s: LazyArg, f: ( s: S, pullLeft: Pull.Pull, pullRight: Pull.Pull ) => Effect.Effect ): (self: Stream) => Stream /** * Combines elements from this stream and the specified stream by repeatedly * applying a stateful function that can pull from either side. * * **Details** * * Where possible, prefer `Stream.combineArray` for a more efficient * implementation. * * **Example** (Combining streams with state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.combine( * Stream.make("A", "B", "C"), * Stream.make(1, 2, 3), * () => true, * (takeLeft, pullLeft, pullRight) => * takeLeft * ? Effect.map(pullLeft, (value) => [`L:${value}`, false] as const) * : Effect.map(pullRight, (value) => [`R:${value}`, true] as const) * ) * * const program = Effect.gen(function*() { * const output = yield* Stream.runCollect(stream) * output // => [ 'L:A', 'R:1', 'L:B', 'R:2', 'L:C', 'R:3' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ ( self: Stream, that: Stream, s: LazyArg, f: ( s: S, pullLeft: Pull.Pull, pullRight: Pull.Pull ) => Effect.Effect ): Stream } = dual(4, ( self: Stream, that: Stream, s: LazyArg, f: ( s: S, pullLeft: Pull.Pull, pullRight: Pull.Pull ) => Effect.Effect ): Stream => Channel.combine( Channel.flattenArray(self.channel), Channel.flattenArray(that.channel), s, f ).pipe( Channel.map(Arr.of), fromChannel )) /** * Combines two streams chunk-by-chunk with a stateful pull function. * * **When to use** * * Use to coordinate pulling chunks from two streams when each emitted chunk * depends on both sides and local state. * * **Details** * * The combining function receives the current state and pull functions for the * left and right streams. It returns the next non-empty chunk together with the * next state. * * **Example** (Combining stream chunks with state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.combineArray( * Stream.make(10, 20), * () => true, * (useLeft, pullLeft, pullRight) => * Effect.gen(function*() { * const array = useLeft ? yield* pullLeft : yield* pullRight * return [array, !useLeft] as const * }) * ) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 10, 20 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 4.0.0 */ export const combineArray: { /** * Combines two streams chunk-by-chunk with a stateful pull function. * * **When to use** * * Use to coordinate pulling chunks from two streams when each emitted chunk * depends on both sides and local state. * * **Details** * * The combining function receives the current state and pull functions for the * left and right streams. It returns the next non-empty chunk together with the * next state. * * **Example** (Combining stream chunks with state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.combineArray( * Stream.make(10, 20), * () => true, * (useLeft, pullLeft, pullRight) => * Effect.gen(function*() { * const array = useLeft ? yield* pullLeft : yield* pullRight * return [array, !useLeft] as const * }) * ) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 10, 20 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 4.0.0 */ ( that: Stream, s: LazyArg, f: ( s: S, pullLeft: Pull.Pull, E, void>, pullRight: Pull.Pull, E2, void> ) => Effect.Effect, S], E3, R3> ): (self: Stream) => Stream, R2 | R3 | R> /** * Combines two streams chunk-by-chunk with a stateful pull function. * * **When to use** * * Use to coordinate pulling chunks from two streams when each emitted chunk * depends on both sides and local state. * * **Details** * * The combining function receives the current state and pull functions for the * left and right streams. It returns the next non-empty chunk together with the * next state. * * **Example** (Combining stream chunks with state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.combineArray( * Stream.make(10, 20), * () => true, * (useLeft, pullLeft, pullRight) => * Effect.gen(function*() { * const array = useLeft ? yield* pullLeft : yield* pullRight * return [array, !useLeft] as const * }) * ) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 10, 20 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 4.0.0 */ ( self: Stream, that: Stream, s: LazyArg, f: ( s: S, pullLeft: Pull.Pull, E, void>, pullRight: Pull.Pull, E2, void> ) => Effect.Effect, S], E3, R3> ): Stream, R | R2 | R3> } = dual(4, ( self: Stream, that: Stream, s: LazyArg, f: ( s: S, pullLeft: Pull.Pull, E, void>, pullRight: Pull.Pull, E2, void> ) => Effect.Effect, S], E3, R3> ): Stream, R | R2 | R3> => fromChannel(Channel.combine( self.channel, that.channel, s, f ))) /** * Maps elements statefully, emitting zero or more outputs per input. * * **Example** (Statefully mapping stream values) * * ```ts import.meta.vitest * import { Console, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const totals = yield* Stream.make(0, 1, 2, 3, 4, 5, 6).pipe( * Stream.mapAccum(() => 0, (total, n) => { * const next = total + n * return [next, [next]] as const * }), * Stream.runCollect * ) * * totals // => [0, 1, 3, 6, 10, 15, 21] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 2.0.0 */ export const mapAccum: { /** * Maps elements statefully, emitting zero or more outputs per input. * * **Example** (Statefully mapping stream values) * * ```ts import.meta.vitest * import { Console, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const totals = yield* Stream.make(0, 1, 2, 3, 4, 5, 6).pipe( * Stream.mapAccum(() => 0, (total, n) => { * const next = total + n * return [next, [next]] as const * }), * Stream.runCollect * ) * * totals // => [0, 1, 3, 6, 10, 15, 21] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 2.0.0 */ ( initial: LazyArg, f: (s: S, a: A) => readonly [state: S, values: ReadonlyArray], options?: { readonly onHalt?: ((state: S) => ReadonlyArray) | undefined } ): (self: Stream) => Stream /** * Maps elements statefully, emitting zero or more outputs per input. * * **Example** (Statefully mapping stream values) * * ```ts import.meta.vitest * import { Console, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const totals = yield* Stream.make(0, 1, 2, 3, 4, 5, 6).pipe( * Stream.mapAccum(() => 0, (total, n) => { * const next = total + n * return [next, [next]] as const * }), * Stream.runCollect * ) * * totals // => [0, 1, 3, 6, 10, 15, 21] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 2.0.0 */ ( self: Stream, initial: LazyArg, f: (s: S, a: A) => readonly [state: S, values: ReadonlyArray], options?: { readonly onHalt?: ((state: S) => ReadonlyArray) | undefined } ): Stream } = dual((args) => isStream(args[0]), ( self: Stream, initial: LazyArg, f: (s: S, a: A) => readonly [state: S, values: ReadonlyArray], options?: { readonly onHalt?: ((state: S) => ReadonlyArray) | undefined } ): Stream => fromChannel(Channel.mapAccum( self.channel, initial, (state, arr) => { const acc = Arr.empty() for (let index = 0; index < arr.length; index++) { const [newState, values] = f(state, arr[index]) state = newState for (let i = 0; i < values.length; i++) { acc.push(values[i]) } } return [state, Arr.isArrayNonEmpty(acc) ? Arr.of(acc) : emptyArr] }, options?.onHalt ? { onHalt(state) { const arr = options.onHalt!(state) return Arr.isReadonlyArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr } } : undefined ))) /** * Maps over non-empty chunk arrays statefully, emitting zero or more values per chunk. * * **Details** * * The mapping function runs once per chunk and the state is threaded across chunks. * * **Example** (Statefully mapping stream chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const output = yield* Stream.make(1, 2, 3, 4, 5, 6).pipe( * Stream.rechunk(2), * Stream.mapAccumArray(() => 0, (sum: number, chunk) => { * const next = chunk.reduce((acc, n) => acc + n, sum) * return [next, [next]] * }), * Stream.runCollect * ) * output // => [ 3, 10, 21 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ export const mapAccumArray: { /** * Maps over non-empty chunk arrays statefully, emitting zero or more values per chunk. * * **Details** * * The mapping function runs once per chunk and the state is threaded across chunks. * * **Example** (Statefully mapping stream chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const output = yield* Stream.make(1, 2, 3, 4, 5, 6).pipe( * Stream.rechunk(2), * Stream.mapAccumArray(() => 0, (sum: number, chunk) => { * const next = chunk.reduce((acc, n) => acc + n, sum) * return [next, [next]] * }), * Stream.runCollect * ) * output // => [ 3, 10, 21 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ ( initial: LazyArg, f: (s: S, a: Arr.NonEmptyReadonlyArray) => readonly [state: S, values: ReadonlyArray], options?: { readonly onHalt?: ((state: S) => ReadonlyArray) | undefined } ): (self: Stream) => Stream /** * Maps over non-empty chunk arrays statefully, emitting zero or more values per chunk. * * **Details** * * The mapping function runs once per chunk and the state is threaded across chunks. * * **Example** (Statefully mapping stream chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const output = yield* Stream.make(1, 2, 3, 4, 5, 6).pipe( * Stream.rechunk(2), * Stream.mapAccumArray(() => 0, (sum: number, chunk) => { * const next = chunk.reduce((acc, n) => acc + n, sum) * return [next, [next]] * }), * Stream.runCollect * ) * output // => [ 3, 10, 21 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ ( self: Stream, initial: LazyArg, f: (s: S, a: Arr.NonEmptyReadonlyArray) => readonly [state: S, values: ReadonlyArray], options?: { readonly onHalt?: ((state: S) => ReadonlyArray) | undefined } ): Stream } = dual((args) => isStream(args[0]), ( self: Stream, initial: LazyArg, f: (s: S, a: Arr.NonEmptyReadonlyArray) => readonly [state: S, values: ReadonlyArray], options?: { readonly onHalt?: ((state: S) => ReadonlyArray) | undefined } ): Stream => fromChannel(Channel.mapAccum( self.channel, initial, (state, arr) => { const [newState, values] = f(state, arr) state = newState return [state, Arr.isReadonlyArrayNonEmpty(values) ? Arr.of(values) : emptyArr] }, options?.onHalt ? { onHalt(state) { const arr = options.onHalt!(state) return Arr.isReadonlyArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr } } : undefined ))) const emptyArr = Arr.empty() /** * Maps each element statefully and effectfully, emitting zero or more output * values per input. * * **When to use** * * Use when stateful element mapping needs Effects or can fail while emitting * zero or more values per input element. * * **Details** * * The mapping effect receives the current state and element, then returns the * next state plus the values to emit. The state is threaded through the * stream. * * **Example** (Effectfully mapping stream values with state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 1, 1).pipe( * Stream.mapAccumEffect(() => 0, (total, n) => * Effect.succeed([total + n, [total + n]]) * ), * Stream.runCollect * ) * * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 2.0.0 */ export const mapAccumEffect: { /** * Maps each element statefully and effectfully, emitting zero or more output * values per input. * * **When to use** * * Use when stateful element mapping needs Effects or can fail while emitting * zero or more values per input element. * * **Details** * * The mapping effect receives the current state and element, then returns the * next state plus the values to emit. The state is threaded through the * stream. * * **Example** (Effectfully mapping stream values with state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 1, 1).pipe( * Stream.mapAccumEffect(() => 0, (total, n) => * Effect.succeed([total + n, [total + n]]) * ), * Stream.runCollect * ) * * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 2.0.0 */ ( initial: LazyArg, f: (s: S, a: A) => Effect.Effect], E2, R2>, options?: { readonly onHalt?: ((state: S) => ReadonlyArray) | undefined } ): (self: Stream) => Stream /** * Maps each element statefully and effectfully, emitting zero or more output * values per input. * * **When to use** * * Use when stateful element mapping needs Effects or can fail while emitting * zero or more values per input element. * * **Details** * * The mapping effect receives the current state and element, then returns the * next state plus the values to emit. The state is threaded through the * stream. * * **Example** (Effectfully mapping stream values with state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 1, 1).pipe( * Stream.mapAccumEffect(() => 0, (total, n) => * Effect.succeed([total + n, [total + n]]) * ), * Stream.runCollect * ) * * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 2.0.0 */ ( self: Stream, initial: LazyArg, f: (s: S, a: A) => Effect.Effect], E2, R2>, options?: { readonly onHalt?: ((state: S) => ReadonlyArray) | undefined } ): Stream } = dual((args) => isStream(args[0]), ( self: Stream, initial: LazyArg, f: (s: S, a: A) => Effect.Effect], E2, R2>, options?: { readonly onHalt?: ((state: S) => ReadonlyArray) | undefined } ): Stream => self.channel.pipe( Channel.flattenArray, Channel.mapAccum( initial, (state, a) => Effect.map( f(state, a), ([state, values]) => [ state, Arr.isReadonlyArrayNonEmpty(values) ? Arr.of(values) : Arr.empty>() ] ), options?.onHalt ? { onHalt(state) { const arr = options.onHalt!(state) return Arr.isReadonlyArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr } } : undefined ), fromChannel )) /** * Maps each non-empty input chunk statefully and effectfully, emitting zero or * more output values per chunk. * * **When to use** * * Use when stateful mapping should process each emitted non-empty chunk with an * Effect instead of each element separately. * * **Details** * * The mapping effect receives the current state and chunk, then returns the * next state plus the values to emit. The state is threaded across chunks. * * **Example** (Effectfully mapping stream chunks with state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const totals = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.rechunk(2), * Stream.mapAccumArrayEffect(() => 0, (total, chunk) => * Effect.gen(function*() { * const next = chunk.reduce((sum, value) => sum + value, total) * return [next, [next]] as const * }) * ), * Stream.runCollect * ) * totals // => [ 3, 10 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ export const mapAccumArrayEffect: { /** * Maps each non-empty input chunk statefully and effectfully, emitting zero or * more output values per chunk. * * **When to use** * * Use when stateful mapping should process each emitted non-empty chunk with an * Effect instead of each element separately. * * **Details** * * The mapping effect receives the current state and chunk, then returns the * next state plus the values to emit. The state is threaded across chunks. * * **Example** (Effectfully mapping stream chunks with state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const totals = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.rechunk(2), * Stream.mapAccumArrayEffect(() => 0, (total, chunk) => * Effect.gen(function*() { * const next = chunk.reduce((sum, value) => sum + value, total) * return [next, [next]] as const * }) * ), * Stream.runCollect * ) * totals // => [ 3, 10 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ ( initial: LazyArg, f: (s: S, a: Arr.NonEmptyReadonlyArray) => Effect.Effect], E2, R2>, options?: { readonly onHalt?: ((state: S) => ReadonlyArray) | undefined } ): (self: Stream) => Stream /** * Maps each non-empty input chunk statefully and effectfully, emitting zero or * more output values per chunk. * * **When to use** * * Use when stateful mapping should process each emitted non-empty chunk with an * Effect instead of each element separately. * * **Details** * * The mapping effect receives the current state and chunk, then returns the * next state plus the values to emit. The state is threaded across chunks. * * **Example** (Effectfully mapping stream chunks with state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const totals = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.rechunk(2), * Stream.mapAccumArrayEffect(() => 0, (total, chunk) => * Effect.gen(function*() { * const next = chunk.reduce((sum, value) => sum + value, total) * return [next, [next]] as const * }) * ), * Stream.runCollect * ) * totals // => [ 3, 10 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ ( self: Stream, initial: LazyArg, f: (s: S, a: Arr.NonEmptyReadonlyArray) => Effect.Effect], E2, R2>, options?: { readonly onHalt?: ((state: S) => ReadonlyArray) | undefined } ): Stream } = dual((args) => isStream(args[0]), ( self: Stream, initial: LazyArg, f: (s: S, a: Arr.NonEmptyReadonlyArray) => Effect.Effect], E2, R2>, options?: { readonly onHalt?: ((state: S) => ReadonlyArray) | undefined } ): Stream => self.channel.pipe( Channel.mapAccum( initial, (state, a) => Effect.map( f(state, a), ([state, values]) => [ state, Arr.isReadonlyArrayNonEmpty(values) ? Arr.of(values) : emptyArr ] ), options?.onHalt ? { onHalt(state) { const arr = options.onHalt!(state) return Arr.isReadonlyArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr } } : undefined ), fromChannel )) /** * Accumulates state across the stream, emitting the initial state and each updated state. * * **Example** (Scanning stream state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.scan(() => 0, (acc, n) => acc + n), * Stream.runCollect * ) * values // => [ 0, 1, 3, 6 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category accumulation * @since 2.0.0 */ export const scan: { /** * Accumulates state across the stream, emitting the initial state and each updated state. * * **Example** (Scanning stream state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.scan(() => 0, (acc, n) => acc + n), * Stream.runCollect * ) * values // => [ 0, 1, 3, 6 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category accumulation * @since 2.0.0 */ (initial: LazyArg, f: (s: S, a: A) => S): (self: Stream) => Stream /** * Accumulates state across the stream, emitting the initial state and each updated state. * * **Example** (Scanning stream state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.scan(() => 0, (acc, n) => acc + n), * Stream.runCollect * ) * values // => [ 0, 1, 3, 6 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category accumulation * @since 2.0.0 */ (self: Stream, initial: LazyArg, f: (s: S, a: A) => S): Stream } = dual(3, ( self: Stream, initial: LazyArg, f: (s: S, a: A) => S ): Stream => suspend(() => { const seed = initial() return concat( succeed(seed), fromChannel(Channel.mapAccum(self.channel, () => seed, (state, arr) => { const states = Arr.empty() as Arr.NonEmptyArray for (let index = 0; index < arr.length; index++) { state = f(state, arr[index]) states.push(state) } return [state, Arr.of(states)] })) ) })) /** * Accumulates state effectfully and emits the initial state plus each accumulated state. * * **Example** (Effectfully scanning stream state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const states = yield* Stream.make(1, 2, 3).pipe( * Stream.scanEffect(() => 0, (sum, n) => Effect.succeed(sum + n)), * Stream.runCollect * ) * states // => [ 0, 1, 3, 6 ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category accumulation * @since 2.0.0 */ export const scanEffect: { /** * Accumulates state effectfully and emits the initial state plus each accumulated state. * * **Example** (Effectfully scanning stream state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const states = yield* Stream.make(1, 2, 3).pipe( * Stream.scanEffect(() => 0, (sum, n) => Effect.succeed(sum + n)), * Stream.runCollect * ) * states // => [ 0, 1, 3, 6 ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category accumulation * @since 2.0.0 */ (initial: LazyArg, f: (s: S, a: A) => Effect.Effect): (self: Stream) => Stream /** * Accumulates state effectfully and emits the initial state plus each accumulated state. * * **Example** (Effectfully scanning stream state) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const states = yield* Stream.make(1, 2, 3).pipe( * Stream.scanEffect(() => 0, (sum, n) => Effect.succeed(sum + n)), * Stream.runCollect * ) * states // => [ 0, 1, 3, 6 ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category accumulation * @since 2.0.0 */ ( self: Stream, initial: LazyArg, f: (s: S, a: A) => Effect.Effect ): Stream } = dual(3, ( self: Stream, initial: LazyArg, f: (s: S, a: A) => Effect.Effect ): Stream => suspend(() => self.channel.pipe( Channel.flattenArray, Channel.scanEffect(initial(), f), Channel.map(Arr.of), fromChannel ) )) /** * Drops earlier elements within the debounce window and emits only the latest element after the pause. * * **Example** (Debouncing stream elements) * * ```ts import.meta.vitest * import { Duration, Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3).pipe(Stream.debounce(Duration.zero)) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 3 ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category rate limiting * @since 2.0.0 */ export const debounce: { /** * Drops earlier elements within the debounce window and emits only the latest element after the pause. * * **Example** (Debouncing stream elements) * * ```ts import.meta.vitest * import { Duration, Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3).pipe(Stream.debounce(Duration.zero)) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 3 ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category rate limiting * @since 2.0.0 */ (duration: Duration.Input): (self: Stream) => Stream /** * Drops earlier elements within the debounce window and emits only the latest element after the pause. * * **Example** (Debouncing stream elements) * * ```ts import.meta.vitest * import { Duration, Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3).pipe(Stream.debounce(Duration.zero)) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 3 ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category rate limiting * @since 2.0.0 */ (self: Stream, duration: Duration.Input): Stream } = dual( 2, (self: Stream, duration: Duration.Input): Stream => transformPull( self, Effect.fnUntraced(function*(pull, scope) { const clock = yield* Clock const durationMs = Duration.toMillis(Duration.fromInputUnsafe(duration)) let lastArr: Arr.NonEmptyReadonlyArray | undefined let cause: Cause.Cause | undefined let emitAtMs = Infinity const pullLatch = Latch.makeUnsafe() const emitLatch = Latch.makeUnsafe() const endLatch = Latch.makeUnsafe() yield* pull.pipe( pullLatch.whenOpen, Effect.flatMap((arr) => { emitLatch.openUnsafe() lastArr = arr emitAtMs = clock.currentTimeMillisUnsafe() + durationMs return Effect.void }), Effect.forever({ disableYield: true }), Effect.onError((cause_) => { cause = cause_ emitAtMs = clock.currentTimeMillisUnsafe() emitLatch.openUnsafe() endLatch.openUnsafe() return Effect.void }), Effect.forkIn(scope) ) const sleepLoop = Effect.suspend(function loop(): Pull.Pull, E, void, R> { const now = clock.currentTimeMillisUnsafe() const timeMs = emitAtMs < now ? durationMs : Math.min(durationMs, emitAtMs - now) return Effect.flatMap(Effect.raceFirst(Effect.sleep(timeMs), endLatch.await), () => { const now = clock.currentTimeMillisUnsafe() if (now < emitAtMs) { return loop() } else if (lastArr) { emitLatch.closeUnsafe() pullLatch.closeUnsafe() const eff = Effect.succeed(Arr.of(Arr.lastNonEmpty(lastArr))) lastArr = undefined return eff } else if (cause) { return Effect.failCause(cause!) } return loop() }) }) return Effect.suspend(() => { if (cause) { if (lastArr) { const eff = Effect.succeed(Arr.of(Arr.lastNonEmpty(lastArr))) lastArr = undefined return eff } return Effect.failCause(cause) } pullLatch.openUnsafe() return emitLatch.whenOpen(sleepLoop) }) }) ) ) /** * Rate-limits stream chunks with an effectful cost function. * * **When to use** * * Use to throttle chunks when computing each chunk's cost requires an effect. * * **Details** * * Uses a token bucket. The bucket can accumulate up to `units + burst` tokens, * and each chunk consumes the cost returned by the effectful `cost` function. * * If using the "enforce" strategy, arrays that do not meet the bandwidth * constraints are dropped. If using the "shape" strategy, arrays are delayed * until they can be emitted without exceeding the bandwidth constraints. * * Defaults to the "shape" strategy. * * **Example** (Throttling stream chunks effectfully) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(0, 5).pipe( * Stream.rechunk(1), * Stream.throttleEffect({ * cost: (arr) => Effect.succeed(arr.length), * units: 1, * duration: 0, * strategy: "shape" * }) * ) * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 0, 1, 2, 3, 4, 5 ] * })) * ``` * * @stability stable * @category rate limiting * @since 2.0.0 */ export const throttleEffect: { /** * Rate-limits stream chunks with an effectful cost function. * * **When to use** * * Use to throttle chunks when computing each chunk's cost requires an effect. * * **Details** * * Uses a token bucket. The bucket can accumulate up to `units + burst` tokens, * and each chunk consumes the cost returned by the effectful `cost` function. * * If using the "enforce" strategy, arrays that do not meet the bandwidth * constraints are dropped. If using the "shape" strategy, arrays are delayed * until they can be emitted without exceeding the bandwidth constraints. * * Defaults to the "shape" strategy. * * **Example** (Throttling stream chunks effectfully) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(0, 5).pipe( * Stream.rechunk(1), * Stream.throttleEffect({ * cost: (arr) => Effect.succeed(arr.length), * units: 1, * duration: 0, * strategy: "shape" * }) * ) * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 0, 1, 2, 3, 4, 5 ] * })) * ``` * * @stability stable * @category rate limiting * @since 2.0.0 */ ( options: { readonly cost: (arr: Arr.NonEmptyReadonlyArray) => Effect.Effect readonly units: number readonly duration: Duration.Input readonly burst?: number | undefined readonly strategy?: "enforce" | "shape" | undefined } ): (self: Stream) => Stream /** * Rate-limits stream chunks with an effectful cost function. * * **When to use** * * Use to throttle chunks when computing each chunk's cost requires an effect. * * **Details** * * Uses a token bucket. The bucket can accumulate up to `units + burst` tokens, * and each chunk consumes the cost returned by the effectful `cost` function. * * If using the "enforce" strategy, arrays that do not meet the bandwidth * constraints are dropped. If using the "shape" strategy, arrays are delayed * until they can be emitted without exceeding the bandwidth constraints. * * Defaults to the "shape" strategy. * * **Example** (Throttling stream chunks effectfully) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(0, 5).pipe( * Stream.rechunk(1), * Stream.throttleEffect({ * cost: (arr) => Effect.succeed(arr.length), * units: 1, * duration: 0, * strategy: "shape" * }) * ) * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ 0, 1, 2, 3, 4, 5 ] * })) * ``` * * @stability stable * @category rate limiting * @since 2.0.0 */ ( self: Stream, options: { readonly cost: (arr: Arr.NonEmptyReadonlyArray) => Effect.Effect readonly units: number readonly duration: Duration.Input readonly burst?: number | undefined readonly strategy?: "enforce" | "shape" | undefined } ): Stream } = dual( 2, ( self: Stream, options: { readonly cost: (arr: Arr.NonEmptyReadonlyArray) => Effect.Effect readonly units: number readonly duration: Duration.Input readonly burst?: number | undefined readonly strategy?: "enforce" | "shape" | undefined } ): Stream => { const burst = options.burst ?? 0 if (options.strategy === "enforce") { return throttleEnforceEffect(self, options.cost, options.units, options.duration, burst) } return throttleShapeEffect(self, options.cost, options.units, options.duration, burst) } ) const throttleEnforceEffect = ( self: Stream, cost: (arr: Arr.NonEmptyReadonlyArray) => Effect.Effect, units: number, duration: Duration.Input, burst: number ): Stream => transformPull(self, (pull) => Effect.clockWith((clock) => { const durationMs = Duration.toMillis(Duration.fromInputUnsafe(duration)) const max = units + burst < 0 ? Number.POSITIVE_INFINITY : units + burst let tokens = units let timestampMs = clock.currentTimeMillisUnsafe() return Effect.succeed( Effect.flatMap(pull, function loop(arr): Pull.Pull, E | E2, void, R | R2> { return Effect.flatMap(cost(arr), (weight) => { const currentMs = clock.currentTimeMillisUnsafe() const elapsed = currentMs - timestampMs const cycles = elapsed / durationMs const sum = tokens + (cycles * units) const available = sum < 0 ? max : Math.min(sum, max) if (weight <= available) { tokens = available - weight timestampMs = currentMs return Effect.succeed(arr) } // Drop the array and continue return Effect.flatMap(pull, loop) }) }) ) })) const throttleShapeEffect = ( self: Stream, cost: (arr: Arr.NonEmptyReadonlyArray) => Effect.Effect, units: number, duration: Duration.Input, burst: number ): Stream => transformPull(self, (pull) => Effect.clockWith((clock) => { const durationMs = Duration.toMillis(Duration.fromInputUnsafe(duration)) const max = units + burst < 0 ? Number.POSITIVE_INFINITY : units + burst let tokens = units let timestampNanos = clock.monotonicTimeNanosUnsafe() return Effect.succeed(Effect.flatMap(pull, (arr) => Effect.flatMap(cost(arr), (weight) => { const currentNanos = clock.monotonicTimeNanosUnsafe() const elapsed = Number(currentNanos - timestampNanos) / 1_000_000 const cycles = elapsed / durationMs const sum = tokens + (cycles * units) const available = Math.min(sum, max) const remaining = available - weight if (remaining >= 0) { tokens = remaining timestampNanos = currentNanos return Effect.succeed(arr) } const waitCycles = -remaining / units const delayMs = Math.max(0, waitCycles * durationMs) if (delayMs > 0) { return Effect.flatMap(Effect.sleep(delayMs), () => { tokens = remaining timestampNanos = currentNanos return Effect.succeed(arr) }) } tokens = remaining timestampNanos = currentNanos return Effect.succeed(arr) }))) })) /** * Rate-limits stream chunks with a synchronous cost function. * * **When to use** * * Use to throttle chunks when each chunk's cost can be computed synchronously. * * **Details** * * Uses a token bucket. The bucket can accumulate up to `units + burst` tokens, * and each chunk consumes the cost returned by `cost`. * * If using the "enforce" strategy, arrays that do not meet the bandwidth * constraints are dropped. If using the "shape" strategy, arrays are delayed * until they can be emitted without exceeding the bandwidth constraints. * * Defaults to the "shape" strategy. * * **Example** (Throttling stream chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(0, 5).pipe( * Stream.rechunk(1), * Stream.throttle({ * cost: (arr) => arr.length, * units: 1, * duration: 0, * strategy: "shape" * }) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 0, 1, 2, 3, 4, 5 ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category rate limiting * @since 2.0.0 */ export const throttle: { /** * Rate-limits stream chunks with a synchronous cost function. * * **When to use** * * Use to throttle chunks when each chunk's cost can be computed synchronously. * * **Details** * * Uses a token bucket. The bucket can accumulate up to `units + burst` tokens, * and each chunk consumes the cost returned by `cost`. * * If using the "enforce" strategy, arrays that do not meet the bandwidth * constraints are dropped. If using the "shape" strategy, arrays are delayed * until they can be emitted without exceeding the bandwidth constraints. * * Defaults to the "shape" strategy. * * **Example** (Throttling stream chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(0, 5).pipe( * Stream.rechunk(1), * Stream.throttle({ * cost: (arr) => arr.length, * units: 1, * duration: 0, * strategy: "shape" * }) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 0, 1, 2, 3, 4, 5 ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category rate limiting * @since 2.0.0 */ ( options: { readonly cost: (arr: Arr.NonEmptyReadonlyArray) => number readonly units: number readonly duration: Duration.Input readonly burst?: number | undefined readonly strategy?: "enforce" | "shape" | undefined } ): (self: Stream) => Stream /** * Rate-limits stream chunks with a synchronous cost function. * * **When to use** * * Use to throttle chunks when each chunk's cost can be computed synchronously. * * **Details** * * Uses a token bucket. The bucket can accumulate up to `units + burst` tokens, * and each chunk consumes the cost returned by `cost`. * * If using the "enforce" strategy, arrays that do not meet the bandwidth * constraints are dropped. If using the "shape" strategy, arrays are delayed * until they can be emitted without exceeding the bandwidth constraints. * * Defaults to the "shape" strategy. * * **Example** (Throttling stream chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.range(0, 5).pipe( * Stream.rechunk(1), * Stream.throttle({ * cost: (arr) => arr.length, * units: 1, * duration: 0, * strategy: "shape" * }) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 0, 1, 2, 3, 4, 5 ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category rate limiting * @since 2.0.0 */ ( self: Stream, options: { readonly cost: (arr: Arr.NonEmptyReadonlyArray) => number readonly units: number readonly duration: Duration.Input readonly burst?: number | undefined readonly strategy?: "enforce" | "shape" | undefined } ): Stream } = dual( 2, ( self: Stream, options: { readonly cost: (arr: Arr.NonEmptyReadonlyArray) => number readonly units: number readonly duration: Duration.Input readonly burst?: number | undefined readonly strategy?: "enforce" | "shape" | undefined } ): Stream => throttleEffect(self, { ...options, cost: (arr) => Effect.succeed(options.cost(arr)) }) ) /** * Partitions the stream into non-empty arrays of the specified size. * * **Details** * * The final array may be smaller if there are not enough elements to fill it. * Finite fractional sizes are rounded down. `NaN` and non-positive sizes are * treated as `1`. * * **Example** (Grouping elements by size) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const grouped = yield* Stream.range(1, 8).pipe( * Stream.grouped(3), * Stream.runCollect * ) * grouped // => [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ export const grouped: { /** * Partitions the stream into non-empty arrays of the specified size. * * **Details** * * The final array may be smaller if there are not enough elements to fill it. * Finite fractional sizes are rounded down. `NaN` and non-positive sizes are * treated as `1`. * * **Example** (Grouping elements by size) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const grouped = yield* Stream.range(1, 8).pipe( * Stream.grouped(3), * Stream.runCollect * ) * grouped // => [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (n: number): (self: Stream) => Stream, E, R> /** * Partitions the stream into non-empty arrays of the specified size. * * **Details** * * The final array may be smaller if there are not enough elements to fill it. * Finite fractional sizes are rounded down. `NaN` and non-positive sizes are * treated as `1`. * * **Example** (Grouping elements by size) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const grouped = yield* Stream.range(1, 8).pipe( * Stream.grouped(3), * Stream.runCollect * ) * grouped // => [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (self: Stream, n: number): Stream, E, R> } = dual( 2, (self: Stream, n: number): Stream, E, R> => chunks(rechunk(self, n)) ) /** * Partitions the stream into arrays, emitting when the chunk size is reached * or the duration passes. * * **Details** * * The duration starts when the first element of a group arrives, so an idle * stream does not wake up. Finite fractional chunk sizes are rounded down. * `NaN` and non-positive sizes are treated as `1`. * * **Example** (Grouping elements by size or time) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.groupedWithin(2, "5 seconds"), * Stream.runCollect * ) * values // => [ [ 1, 2 ], [ 3 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ export const groupedWithin: { /** * Partitions the stream into arrays, emitting when the chunk size is reached * or the duration passes. * * **Details** * * The duration starts when the first element of a group arrives, so an idle * stream does not wake up. Finite fractional chunk sizes are rounded down. * `NaN` and non-positive sizes are treated as `1`. * * **Example** (Grouping elements by size or time) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.groupedWithin(2, "5 seconds"), * Stream.runCollect * ) * values // => [ [ 1, 2 ], [ 3 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (chunkSize: number, duration: Duration.Input): (self: Stream) => Stream, E, R> /** * Partitions the stream into arrays, emitting when the chunk size is reached * or the duration passes. * * **Details** * * The duration starts when the first element of a group arrives, so an idle * stream does not wake up. Finite fractional chunk sizes are rounded down. * `NaN` and non-positive sizes are treated as `1`. * * **Example** (Grouping elements by size or time) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.groupedWithin(2, "5 seconds"), * Stream.runCollect * ) * values // => [ [ 1, 2 ], [ 3 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ (self: Stream, chunkSize: number, duration: Duration.Input): Stream, E, R> } = dual(3, ( self: Stream, chunkSize: number, duration: Duration.Input ): Stream, E, R> => aggregateWithin( self, Sink.take(Count.normalizeNonEmpty(chunkSize)), Schedule.spaced(duration) )) /** * Groups elements into keyed substreams using an effectful classifier. * * **Example** (Grouping elements into keyed substreams using an effectful classifier) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const grouped = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.groupBy((n) => * Effect.succeed([n % 2 === 0 ? "even" : "odd", n] as const) * ), * Stream.mapEffect( * Effect.fnUntraced(function*([key, stream]) { * return [key, yield* Stream.runCollect(stream)] as const * }), * { concurrency: "unbounded" } * ), * Stream.runCollect * ) * * grouped // => [ [ 'odd', [ 1, 3, 5 ] ], [ 'even', [ 2, 4 ] ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ export const groupBy: { /** * Groups elements into keyed substreams using an effectful classifier. * * **Example** (Grouping elements into keyed substreams using an effectful classifier) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const grouped = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.groupBy((n) => * Effect.succeed([n % 2 === 0 ? "even" : "odd", n] as const) * ), * Stream.mapEffect( * Effect.fnUntraced(function*([key, stream]) { * return [key, yield* Stream.runCollect(stream)] as const * }), * { concurrency: "unbounded" } * ), * Stream.runCollect * ) * * grouped // => [ [ 'odd', [ 1, 3, 5 ] ], [ 'even', [ 2, 4 ] ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ ( f: (a: NoInfer) => Effect.Effect, options?: { readonly bufferSize?: number | undefined readonly idleTimeToLive?: Duration.Input | undefined } ): (self: Stream) => Stream], E | E2, R | R2> /** * Groups elements into keyed substreams using an effectful classifier. * * **Example** (Grouping elements into keyed substreams using an effectful classifier) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const grouped = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.groupBy((n) => * Effect.succeed([n % 2 === 0 ? "even" : "odd", n] as const) * ), * Stream.mapEffect( * Effect.fnUntraced(function*([key, stream]) { * return [key, yield* Stream.runCollect(stream)] as const * }), * { concurrency: "unbounded" } * ), * Stream.runCollect * ) * * grouped // => [ [ 'odd', [ 1, 3, 5 ] ], [ 'even', [ 2, 4 ] ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ ( self: Stream, f: (a: NoInfer) => Effect.Effect, options?: { readonly bufferSize?: number | undefined readonly idleTimeToLive?: Duration.Input | undefined } ): Stream], E | E2, R | R2> } = dual((args) => isStream(args[0]), ( self: Stream, f: (a: NoInfer) => Effect.Effect, options?: { readonly bufferSize?: number | undefined readonly idleTimeToLive?: Duration.Input | undefined } ): Stream], E | E2, R | R2> => groupByImpl( self, Effect.fnUntraced(function*(arr, queues, queueMap) { for (let i = 0; i < arr.length; i++) { const [key, value] = yield* f(arr[i]) const oentry = MutableHashMap.get(queueMap, key) const queue = Option.isSome(oentry) ? oentry.value : yield* Effect.scoped(RcMap.get(queues, key)) yield* RcMap.touch(queues, key) yield* Queue.offer(queue, value) } }), options )) /** * Groups elements by a key and emits a stream per key. * * **Example** (Grouping elements by key) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const grouped = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.groupByKey((n) => n % 2 === 0 ? "even" : "odd"), * Stream.mapEffect( * ([key, stream]) => * Stream.runCollect(stream).pipe( * Effect.map((values) => [key, values] as const) * ), * { concurrency: "unbounded" } * ), * Stream.runCollect * ) * grouped // => [ [ 'odd', [ 1, 3, 5 ] ], [ 'even', [ 2, 4 ] ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ export const groupByKey: { /** * Groups elements by a key and emits a stream per key. * * **Example** (Grouping elements by key) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const grouped = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.groupByKey((n) => n % 2 === 0 ? "even" : "odd"), * Stream.mapEffect( * ([key, stream]) => * Stream.runCollect(stream).pipe( * Effect.map((values) => [key, values] as const) * ), * { concurrency: "unbounded" } * ), * Stream.runCollect * ) * grouped // => [ [ 'odd', [ 1, 3, 5 ] ], [ 'even', [ 2, 4 ] ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ ( f: (a: NoInfer) => K, options?: { readonly bufferSize?: number | undefined readonly idleTimeToLive?: Duration.Input | undefined } ): (self: Stream) => Stream], E, R> /** * Groups elements by a key and emits a stream per key. * * **Example** (Grouping elements by key) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const grouped = yield* Stream.make(1, 2, 3, 4, 5).pipe( * Stream.groupByKey((n) => n % 2 === 0 ? "even" : "odd"), * Stream.mapEffect( * ([key, stream]) => * Stream.runCollect(stream).pipe( * Effect.map((values) => [key, values] as const) * ), * { concurrency: "unbounded" } * ), * Stream.runCollect * ) * grouped // => [ [ 'odd', [ 1, 3, 5 ] ], [ 'even', [ 2, 4 ] ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category grouping * @since 2.0.0 */ ( self: Stream, f: (a: NoInfer) => K, options?: { readonly bufferSize?: number | undefined readonly idleTimeToLive?: Duration.Input | undefined } ): Stream], E, R> } = dual((args) => isStream(args[0]), ( self: Stream, f: (a: NoInfer) => K, options?: { readonly bufferSize?: number | undefined readonly idleTimeToLive?: Duration.Input | undefined } ): Stream], E, R> => suspend(() => { const batch = MutableHashMap.empty>() return groupByImpl( self, Effect.fnUntraced(function*(arr, queues, queueMap) { for (let i = 0; i < arr.length; i++) { const key = f(arr[i]) const ovalues = MutableHashMap.get(batch, key) if (Option.isNone(ovalues)) { MutableHashMap.set(batch, key, [arr[i]]) } else { ovalues.value.push(arr[i]) } } for (const [key, values] of batch) { const oentry = MutableHashMap.get(queueMap, key) const queue = Option.isSome(oentry) ? oentry.value : yield* Effect.scoped(RcMap.get(queues, key)) yield* RcMap.touch(queues, key) yield* Queue.offerAll(queue, values) } MutableHashMap.clear(batch) }), options ) })) const groupByImpl = ( self: Stream, f: ( arr: Arr.NonEmptyReadonlyArray, queues: RcMap.RcMap>, queueMap: MutableHashMap.MutableHashMap> ) => Effect.Effect, options?: { readonly bufferSize?: number | undefined readonly idleTimeToLive?: Duration.Input | undefined } ): Stream], E | E2, R | R2> => transformPullBracket( self, Effect.fnUntraced(function*(pull, scope, forkedScope) { const out = yield* Queue.unbounded], E | E2 | Cause.Done>() yield* Scope.addFinalizer(scope, Queue.shutdown(out)) const queueMap = MutableHashMap.empty>() const queues = yield* RcMap.make({ lookup: (key: K) => Effect.acquireRelease( Queue.make({ capacity: options?.bufferSize ?? 4096 }).pipe( Effect.tap((queue) => { MutableHashMap.set(queueMap, key, queue) return Queue.offer(out, [key, fromQueueShutdownOnEnd(queue)]) }) ), (queue) => { MutableHashMap.remove(queueMap, key) return Queue.end(queue) } ), idleTimeToLive: options?.idleTimeToLive ?? Duration.infinity }).pipe(Scope.provide(forkedScope)) yield* Effect.whileLoop({ while: constTrue, body: constant(Effect.flatMap(pull, (arr) => f(arr, queues, queueMap))), step: constVoid }).pipe( Effect.catchCause((cause) => Queue.failCause(out, cause)), Effect.forkIn(scope) ) return Queue.takeAll(out) }) ) /** * Groups consecutive elements that have equal keys into non-empty arrays. * * **When to use** * * Use when you already have a stream ordered by the grouping key and want to * emit each consecutive run as a non-empty array while keeping later * non-adjacent runs separate. * * **Details** * * The key is computed with `f`; adjacent elements whose keys are equal by * `Equal.equals` are emitted as one `[key, group]`. Later non-adjacent runs * with the same key are emitted separately. * * @see {@link groupByKey} for grouping all elements with the same key across the stream * @see {@link groupBy} for custom grouped stream construction * * @stability stable * @category grouping * @since 2.0.0 */ export const groupAdjacentBy: { /** * Groups consecutive elements that have equal keys into non-empty arrays. * * **When to use** * * Use when you already have a stream ordered by the grouping key and want to * emit each consecutive run as a non-empty array while keeping later * non-adjacent runs separate. * * **Details** * * The key is computed with `f`; adjacent elements whose keys are equal by * `Equal.equals` are emitted as one `[key, group]`. Later non-adjacent runs * with the same key are emitted separately. * * @see {@link groupByKey} for grouping all elements with the same key across the stream * @see {@link groupBy} for custom grouped stream construction * * @stability stable * @category grouping * @since 2.0.0 */ (f: (a: NoInfer) => K): (self: Stream) => Stream], E, R> /** * Groups consecutive elements that have equal keys into non-empty arrays. * * **When to use** * * Use when you already have a stream ordered by the grouping key and want to * emit each consecutive run as a non-empty array while keeping later * non-adjacent runs separate. * * **Details** * * The key is computed with `f`; adjacent elements whose keys are equal by * `Equal.equals` are emitted as one `[key, group]`. Later non-adjacent runs * with the same key are emitted separately. * * @see {@link groupByKey} for grouping all elements with the same key across the stream * @see {@link groupBy} for custom grouped stream construction * * @stability stable * @category grouping * @since 2.0.0 */ (self: Stream, f: (a: NoInfer) => K): Stream], E, R> } = dual(2, ( self: Stream, f: (a: NoInfer) => K ): Stream], E, R> => transformPull(self, (pull, _scope) => Effect.sync(() => { let currentKey: K = undefined as any let group: Arr.NonEmptyArray | undefined let toEmit = Arr.empty]>() const loop: Pull.Pull< Arr.NonEmptyReadonlyArray]>, E > = pull.pipe( Effect.flatMap((chunk) => { for (let i = 0; i < chunk.length; i++) { const item = chunk[i] const key = f(item) if (group === undefined) { currentKey = key group = [item] continue } else if (Equal.equals(key, currentKey)) { group.push(item) continue } toEmit.push([currentKey, group]) currentKey = key group = [item] } if (Arr.isArrayNonEmpty(toEmit)) { const out = toEmit toEmit = [] return Effect.succeed(out) } return loop }) ) let done = false return Pull.catchDone(Effect.suspend(() => done ? Cause.done() : loop), () => { done = true const out = group group = undefined return out && Arr.isArrayNonEmpty(out) ? Effect.succeed(Arr.of([currentKey, out])) : Cause.done() }) }))) /** * Applies a sink transducer to the stream and emits each sink result. * * **Example** (Transducing with a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * const program = Effect.gen(function* () { * const result = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.transduce(Sink.take(2)), * Stream.runCollect * ) * * result // => [ [ 1, 2 ], [ 3, 4 ], [] ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category aggregation * @since 2.0.0 */ export const transduce: { /** * Applies a sink transducer to the stream and emits each sink result. * * **Example** (Transducing with a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * const program = Effect.gen(function* () { * const result = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.transduce(Sink.take(2)), * Stream.runCollect * ) * * result // => [ [ 1, 2 ], [ 3, 4 ], [] ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category aggregation * @since 2.0.0 */ (sink: Sink.Sink): (self: Stream) => Stream /** * Applies a sink transducer to the stream and emits each sink result. * * **Example** (Transducing with a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * const program = Effect.gen(function* () { * const result = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.transduce(Sink.take(2)), * Stream.runCollect * ) * * result // => [ [ 1, 2 ], [ 3, 4 ], [] ] * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category aggregation * @since 2.0.0 */ (self: Stream, sink: Sink.Sink): Stream } = dual( 2, ( self: Stream, sink: Sink.Sink ): Stream => transformPull(self, (upstream, scope) => Effect.sync(() => { let done: Exit.Exit | E> | undefined let leftover: Arr.NonEmptyReadonlyArray | undefined const upstreamWithLeftover = Effect.suspend(() => { if (leftover !== undefined) { const chunk = leftover leftover = undefined return Effect.succeed(chunk) } return upstream }).pipe( Effect.catch((error) => { done = Exit.fail(error) return Cause.done() }) ) const pull = Effect.map( Effect.suspend(() => sink.transform(upstreamWithLeftover, scope)), ([value, leftover_]) => { leftover = leftover_ return Arr.of(value) } ) return Effect.suspend((): Pull.Pull< Arr.NonEmptyReadonlyArray, E | E2, void, R2 > => done ? done : pull) })) ) /** * Aggregates elements using the provided sink and emits each sink result as a stream element. * * **Details** * * The stream runs the upstream and downstream in separate fibers, so the sink can keep * consuming input while downstream is busy processing the previous output. * * **Example** (Aggregating with a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function* () { * const aggregated = yield* Stream.runCollect( * Stream.make(1, 2, 3, 4, 5, 6).pipe( * Stream.aggregate( * Sink.foldUntil(() => 0, 3, (sum, n) => Effect.succeed(sum + n)) * ) * ) * ) * aggregated // => [ 6, 15 ] * })) * ``` * * @stability stable * @category aggregation * @since 2.0.0 */ export const aggregate: { /** * Aggregates elements using the provided sink and emits each sink result as a stream element. * * **Details** * * The stream runs the upstream and downstream in separate fibers, so the sink can keep * consuming input while downstream is busy processing the previous output. * * **Example** (Aggregating with a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function* () { * const aggregated = yield* Stream.runCollect( * Stream.make(1, 2, 3, 4, 5, 6).pipe( * Stream.aggregate( * Sink.foldUntil(() => 0, 3, (sum, n) => Effect.succeed(sum + n)) * ) * ) * ) * aggregated // => [ 6, 15 ] * })) * ``` * * @stability stable * @category aggregation * @since 2.0.0 */ (sink: Sink.Sink): (self: Stream) => Stream /** * Aggregates elements using the provided sink and emits each sink result as a stream element. * * **Details** * * The stream runs the upstream and downstream in separate fibers, so the sink can keep * consuming input while downstream is busy processing the previous output. * * **Example** (Aggregating with a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function* () { * const aggregated = yield* Stream.runCollect( * Stream.make(1, 2, 3, 4, 5, 6).pipe( * Stream.aggregate( * Sink.foldUntil(() => 0, 3, (sum, n) => Effect.succeed(sum + n)) * ) * ) * ) * aggregated // => [ 6, 15 ] * })) * ``` * * @stability stable * @category aggregation * @since 2.0.0 */ (self: Stream, sink: Sink.Sink): Stream } = dual(2, ( self: Stream, sink: Sink.Sink ): Stream => aggregateWithin(self, sink, Schedule.forever)) /** * Aggregates elements with a sink, emitting each result when the sink completes or the schedule triggers. * * **Details** * * The schedule can flush the current aggregation even if the sink has not finished. * It is stepped at most once per aggregation, when the aggregation receives its * first element, with the previous aggregation's output as input. An aggregation * the sink completes immediately, such as one built from leftovers, may finish * without stepping the schedule. * * Time between aggregations is hidden from the schedule, so an idle stream does * not run it. Time-based schedules such as `Schedule.fixed` and * `Schedule.upTo({ duration })` only count time spent aggregating; use * `Schedule.spaced` for a flush timer. * * When the schedule ends, the stream emits the current aggregation and any sink * leftovers, then ends without pulling more from upstream. * * **Example** (Aggregating with a sink and schedule) * * ```ts import.meta.vitest * import { Effect, Schedule, Sink, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function* () { * const aggregated = yield* Stream.runCollect( * Stream.make(1, 2, 3, 4, 5, 6).pipe( * Stream.aggregateWithin( * Sink.foldUntil(() => 0, 3, (sum, n) => Effect.succeed(sum + n)), * Schedule.forever * ) * ) * ) * aggregated // => [ 6, 15 ] * })) * ``` * * @stability stable * @category aggregation * @since 2.0.0 */ export const aggregateWithin: { /** * Aggregates elements with a sink, emitting each result when the sink completes or the schedule triggers. * * **Details** * * The schedule can flush the current aggregation even if the sink has not finished. * It is stepped at most once per aggregation, when the aggregation receives its * first element, with the previous aggregation's output as input. An aggregation * the sink completes immediately, such as one built from leftovers, may finish * without stepping the schedule. * * Time between aggregations is hidden from the schedule, so an idle stream does * not run it. Time-based schedules such as `Schedule.fixed` and * `Schedule.upTo({ duration })` only count time spent aggregating; use * `Schedule.spaced` for a flush timer. * * When the schedule ends, the stream emits the current aggregation and any sink * leftovers, then ends without pulling more from upstream. * * **Example** (Aggregating with a sink and schedule) * * ```ts import.meta.vitest * import { Effect, Schedule, Sink, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function* () { * const aggregated = yield* Stream.runCollect( * Stream.make(1, 2, 3, 4, 5, 6).pipe( * Stream.aggregateWithin( * Sink.foldUntil(() => 0, 3, (sum, n) => Effect.succeed(sum + n)), * Schedule.forever * ) * ) * ) * aggregated // => [ 6, 15 ] * })) * ``` * * @stability stable * @category aggregation * @since 2.0.0 */ ( sink: Sink.Sink, schedule: Schedule.Schedule, E3, R3> ): (self: Stream) => Stream /** * Aggregates elements with a sink, emitting each result when the sink completes or the schedule triggers. * * **Details** * * The schedule can flush the current aggregation even if the sink has not finished. * It is stepped at most once per aggregation, when the aggregation receives its * first element, with the previous aggregation's output as input. An aggregation * the sink completes immediately, such as one built from leftovers, may finish * without stepping the schedule. * * Time between aggregations is hidden from the schedule, so an idle stream does * not run it. Time-based schedules such as `Schedule.fixed` and * `Schedule.upTo({ duration })` only count time spent aggregating; use * `Schedule.spaced` for a flush timer. * * When the schedule ends, the stream emits the current aggregation and any sink * leftovers, then ends without pulling more from upstream. * * **Example** (Aggregating with a sink and schedule) * * ```ts import.meta.vitest * import { Effect, Schedule, Sink, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function* () { * const aggregated = yield* Stream.runCollect( * Stream.make(1, 2, 3, 4, 5, 6).pipe( * Stream.aggregateWithin( * Sink.foldUntil(() => 0, 3, (sum, n) => Effect.succeed(sum + n)), * Schedule.forever * ) * ) * ) * aggregated // => [ 6, 15 ] * })) * ``` * * @stability stable * @category aggregation * @since 2.0.0 */ ( self: Stream, sink: Sink.Sink, schedule: Schedule.Schedule, E3, R3> ): Stream } = dual(3, ( self: Stream, sink: Sink.Sink, schedule: Schedule.Schedule, E3, R3> ): Stream => fromChannel(Channel.fromTransformBracket(Effect.fnUntraced(function*(_upstream, _, scope) { const pull = yield* Channel.toPullScoped(self.channel, _) const pullLatch = Latch.makeUnsafe(false) const scheduleStep = Symbol() const buffer = yield* Queue.make | typeof scheduleStep, E | Cause.Done>({ capacity: 0 }) yield* pull.pipe( pullLatch.whenOpen, Effect.flatMap((arr) => { pullLatch.closeUnsafe() return Queue.offer(buffer, arr) }), Effect.forever, // Keep autoYield enabled so the schedule can run. Effect.catchCause((cause) => Queue.failCause(buffer, cause)), Effect.forkIn(scope) ) // The schedule clock excludes time between aggregations. const clock = yield* Clock const step = yield* Schedule.toStep(schedule) const hasInput = Latch.makeUnsafe(false) let scheduleTime = clock.currentTimeMillisUnsafe() let openedAt = 0 let lastOutput = Option.none() let leftover: Arr.NonEmptyReadonlyArray | undefined const onInput = (chunk: X): Effect.Effect => { if (hasInput.openUnsafe()) { openedAt = clock.currentTimeMillisUnsafe() } return Effect.succeed(chunk) } const stepToBuffer: Pull.Pull = hasInput.await.pipe( Effect.flatMap(() => step(scheduleTime, lastOutput)), Effect.flatMap(([, delay]) => Effect.sleep(delay)), Effect.flatMap(() => Queue.offer(buffer, scheduleStep)), // End input so the sink drains leftovers without further upstream pulls. Pull.catchDone(() => Queue.end(buffer)), Effect.flatMap(() => Effect.never) ) const sinkUpstream = Effect.suspend((): Pull.Pull, E> => { if (leftover !== undefined) { const chunk = leftover leftover = undefined return onInput(chunk) } if (buffer.state._tag === "Open") { pullLatch.openUnsafe() } return Effect.flatMap(Queue.take(buffer), (arr) => arr === scheduleStep ? Cause.done() : onInput(arr)) }) const emit = Effect.flatMap(([value, leftover_]: Sink.End) => { if (hasInput.isOpen()) { scheduleTime += clock.currentTimeMillisUnsafe() - openedAt } else if (buffer.state._tag === "Done") { return Cause.done() } lastOutput = Option.some(value) leftover = leftover_ return Effect.succeed(Arr.of(value)) }) return Effect.suspend((): Pull.Pull, E | E2 | E3, void, R2 | R3> => { if (buffer.state._tag === "Done" && leftover === undefined) { return buffer.state.exit as Exit.Exit | E> } hasInput.closeUnsafe() const pull = emit(Effect.suspend(() => sink.transform(sinkUpstream as any, scope))) return buffer.state._tag === "Open" ? Effect.raceFirst(pull, stepToBuffer) : pull }) })))) /** * Creates a fixed-size tuple of streams that each emit * the same elements as the source stream. * * **Details** * * The source stream starts after all downstream streams have been subscribed. * With the default suspend strategy, the source can only advance `capacity` * chunks ahead of the slowest downstream stream. If a downstream stream is * interrupted, it unsubscribes from the broadcast so it no longer contributes * backpressure. Finite fractional values of `n` are rounded down. `NaN` and * non-positive values create no downstream streams. * * **Example** (Broadcasting to two consumers) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.scoped( * Effect.gen(function*() { * const [left, right] = yield* Stream.make(1, 2, 3).pipe( * Stream.broadcastN({ n: 2, capacity: 8 }) * ) * * const values = yield* Effect.all([ * Stream.runCollect(left), * Stream.runCollect(right) * ], { concurrency: "unbounded" }) * * values // => [ [ 1, 2, 3 ], [ 1, 2, 3 ] ] * }) * ) * * await Effect.runPromise(program) * ``` * * @stability stable * @category broadcasting * @since 4.0.0 */ export const broadcastN: { /** * Creates a fixed-size tuple of streams that each emit * the same elements as the source stream. * * **Details** * * The source stream starts after all downstream streams have been subscribed. * With the default suspend strategy, the source can only advance `capacity` * chunks ahead of the slowest downstream stream. If a downstream stream is * interrupted, it unsubscribes from the broadcast so it no longer contributes * backpressure. Finite fractional values of `n` are rounded down. `NaN` and * non-positive values create no downstream streams. * * **Example** (Broadcasting to two consumers) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.scoped( * Effect.gen(function*() { * const [left, right] = yield* Stream.make(1, 2, 3).pipe( * Stream.broadcastN({ n: 2, capacity: 8 }) * ) * * const values = yield* Effect.all([ * Stream.runCollect(left), * Stream.runCollect(right) * ], { concurrency: "unbounded" }) * * values // => [ [ 1, 2, 3 ], [ 1, 2, 3 ] ] * }) * ) * * await Effect.runPromise(program) * ``` * * @stability stable * @category broadcasting * @since 4.0.0 */ ( options: { readonly n: N readonly capacity: "unbounded" readonly replay?: number | undefined } | { readonly n: N readonly capacity: number readonly strategy?: "sliding" | "dropping" | "suspend" | undefined readonly replay?: number | undefined } ): (self: Stream) => Effect.Effect>, never, Scope.Scope | R> /** * Creates a fixed-size tuple of streams that each emit * the same elements as the source stream. * * **Details** * * The source stream starts after all downstream streams have been subscribed. * With the default suspend strategy, the source can only advance `capacity` * chunks ahead of the slowest downstream stream. If a downstream stream is * interrupted, it unsubscribes from the broadcast so it no longer contributes * backpressure. Finite fractional values of `n` are rounded down. `NaN` and * non-positive values create no downstream streams. * * **Example** (Broadcasting to two consumers) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.scoped( * Effect.gen(function*() { * const [left, right] = yield* Stream.make(1, 2, 3).pipe( * Stream.broadcastN({ n: 2, capacity: 8 }) * ) * * const values = yield* Effect.all([ * Stream.runCollect(left), * Stream.runCollect(right) * ], { concurrency: "unbounded" }) * * values // => [ [ 1, 2, 3 ], [ 1, 2, 3 ] ] * }) * ) * * await Effect.runPromise(program) * ``` * * @stability stable * @category broadcasting * @since 4.0.0 */ ( self: Stream, options: { readonly n: N readonly capacity: "unbounded" readonly replay?: number | undefined } | { readonly capacity: number readonly n: N readonly strategy?: "sliding" | "dropping" | "suspend" | undefined readonly replay?: number | undefined } ): Effect.Effect>, never, Scope.Scope | R> } = dual( 2, Effect.fnUntraced(function*( self: Stream, options: { readonly n: N readonly capacity: "unbounded" readonly replay?: number | undefined } | { readonly n: N readonly capacity: number readonly strategy?: "sliding" | "dropping" | "suspend" | undefined readonly replay?: number | undefined } ) { const n = Count.normalize(options.n) const pubsub = yield* makePubSub>(options) const streams = new Array(n) const parentScope = yield* Scope.Scope for (let i = 0; i < n; i++) { const scope = Scope.forkUnsafe(parentScope) const subscription = yield* PubSub.subscribe(pubsub).pipe( Effect.provideService(Scope.Scope, scope) ) streams[i] = Channel.fromEffectTake(PubSub.take(subscription)).pipe( Channel.onExit((exit) => Scope.close(scope, exit)), fromChannel ) } yield* Channel.runForEach(self.channel, (value) => PubSub.publish(pubsub, value)).pipe( Effect.onExit((exit) => PubSub.end(pubsub, exit)), Effect.forkScoped ) return streams as TupleOf> }) ) const makePubSub = ( options: { readonly capacity: "unbounded" readonly replay?: number | undefined } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined readonly replay?: number | undefined } ) => Effect.acquireRelease( options.capacity === "unbounded" ? PubSub.unbounded(options) : options.strategy === "dropping" ? PubSub.dropping(options) : options.strategy === "sliding" ? PubSub.sliding(options) : PubSub.bounded(options), PubSub.shutdown ) /** * Creates a PubSub-backed stream that multicasts the source to all subscribers. * * **Details** * * The returned stream is scoped and uses the provided PubSub capacity and replay settings. * * **Example** (Broadcasting a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.scoped( * Effect.gen(function* () { * const broadcasted = yield* Stream.broadcast(Stream.fromArray([1, 2, 3]), { * capacity: 8, * replay: 3 * }) * * const [left, right] = yield* Effect.all([ * Stream.runCollect(broadcasted), * Stream.runCollect(broadcasted) * ], { concurrency: "unbounded" }) * * const result = [left, right] // => [[1, 2, 3], [1, 2, 3]] * }) * ) * * await Effect.runPromise(program) * ``` * * @stability stable * @category broadcasting * @since 2.0.0 */ export const broadcast: { /** * Creates a PubSub-backed stream that multicasts the source to all subscribers. * * **Details** * * The returned stream is scoped and uses the provided PubSub capacity and replay settings. * * **Example** (Broadcasting a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.scoped( * Effect.gen(function* () { * const broadcasted = yield* Stream.broadcast(Stream.fromArray([1, 2, 3]), { * capacity: 8, * replay: 3 * }) * * const [left, right] = yield* Effect.all([ * Stream.runCollect(broadcasted), * Stream.runCollect(broadcasted) * ], { concurrency: "unbounded" }) * * const result = [left, right] // => [[1, 2, 3], [1, 2, 3]] * }) * ) * * await Effect.runPromise(program) * ``` * * @stability stable * @category broadcasting * @since 2.0.0 */ ( options: { readonly capacity: "unbounded" readonly replay?: number | undefined } | { readonly capacity: number readonly strategy?: "sliding" | "dropping" | "suspend" | undefined readonly replay?: number | undefined } ): (self: Stream) => Effect.Effect, never, Scope.Scope | R> /** * Creates a PubSub-backed stream that multicasts the source to all subscribers. * * **Details** * * The returned stream is scoped and uses the provided PubSub capacity and replay settings. * * **Example** (Broadcasting a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.scoped( * Effect.gen(function* () { * const broadcasted = yield* Stream.broadcast(Stream.fromArray([1, 2, 3]), { * capacity: 8, * replay: 3 * }) * * const [left, right] = yield* Effect.all([ * Stream.runCollect(broadcasted), * Stream.runCollect(broadcasted) * ], { concurrency: "unbounded" }) * * const result = [left, right] // => [[1, 2, 3], [1, 2, 3]] * }) * ) * * await Effect.runPromise(program) * ``` * * @stability stable * @category broadcasting * @since 2.0.0 */ ( self: Stream, options: { readonly capacity: "unbounded" readonly replay?: number | undefined } | { readonly capacity: number readonly strategy?: "sliding" | "dropping" | "suspend" | undefined readonly replay?: number | undefined } ): Effect.Effect, never, Scope.Scope | R> } = dual(2, ( self: Stream, options: { readonly capacity: "unbounded" readonly replay?: number | undefined } | { readonly capacity: number readonly strategy?: "sliding" | "dropping" | "suspend" | undefined readonly replay?: number | undefined } ): Effect.Effect, never, Scope.Scope | R> => Effect.map(toPubSubTake(self, options), fromPubSubTake)) /** * Returns a new Stream that multicasts the original stream, subscribing when the first consumer starts. * * **Details** * * The upstream continues running while there is at least one consumer and is finalized after the last one exits. * If `idleTimeToLive` is set, the upstream is kept alive for that duration so a later subscriber can continue from * the next element instead of restarting. * * **Example** (Sharing a stream) * * ```ts import.meta.vitest * import { Deferred, Effect, Ref, Stream } from "effect" * * const result = await Effect.runPromise( * Effect.scoped( * Effect.gen(function*() { * const firstReady = yield* Deferred.make() * const secondReady = yield* Deferred.make() * const acquisitions = yield* Ref.make(0) * const source = Stream.fromEffect(Ref.update(acquisitions, (n) => n + 1)).pipe( * Stream.drain, * Stream.concat(Stream.make(0)), * Stream.concat( * Stream.fromEffect(Effect.all([Deferred.await(firstReady), Deferred.await(secondReady)])).pipe(Stream.drain) * ), * Stream.concat(Stream.make(1, 2, 3)) * ) * const shared = yield* Stream.share(source, { capacity: 16, replay: 1 }) * const consume = (ready: Deferred.Deferred) => * shared.pipe( * Stream.tap((value) => value === 0 ? Deferred.succeed(ready, void 0) : Effect.void), * Stream.filter((value) => value !== 0), * Stream.runCollect * ) * * const values = yield* Effect.all([consume(firstReady), consume(secondReady)], { concurrency: "unbounded" }) * return { values, acquisitions: yield* Ref.get(acquisitions) } * }) * ) * ) * result // => { values: [[1, 2, 3], [1, 2, 3]], acquisitions: 1 } * ``` * * @stability stable * @category broadcasting * @since 3.8.0 */ export const share: { /** * Returns a new Stream that multicasts the original stream, subscribing when the first consumer starts. * * **Details** * * The upstream continues running while there is at least one consumer and is finalized after the last one exits. * If `idleTimeToLive` is set, the upstream is kept alive for that duration so a later subscriber can continue from * the next element instead of restarting. * * **Example** (Sharing a stream) * * ```ts import.meta.vitest * import { Deferred, Effect, Ref, Stream } from "effect" * * const result = await Effect.runPromise( * Effect.scoped( * Effect.gen(function*() { * const firstReady = yield* Deferred.make() * const secondReady = yield* Deferred.make() * const acquisitions = yield* Ref.make(0) * const source = Stream.fromEffect(Ref.update(acquisitions, (n) => n + 1)).pipe( * Stream.drain, * Stream.concat(Stream.make(0)), * Stream.concat( * Stream.fromEffect(Effect.all([Deferred.await(firstReady), Deferred.await(secondReady)])).pipe(Stream.drain) * ), * Stream.concat(Stream.make(1, 2, 3)) * ) * const shared = yield* Stream.share(source, { capacity: 16, replay: 1 }) * const consume = (ready: Deferred.Deferred) => * shared.pipe( * Stream.tap((value) => value === 0 ? Deferred.succeed(ready, void 0) : Effect.void), * Stream.filter((value) => value !== 0), * Stream.runCollect * ) * * const values = yield* Effect.all([consume(firstReady), consume(secondReady)], { concurrency: "unbounded" }) * return { values, acquisitions: yield* Ref.get(acquisitions) } * }) * ) * ) * result // => { values: [[1, 2, 3], [1, 2, 3]], acquisitions: 1 } * ``` * * @stability stable * @category broadcasting * @since 3.8.0 */ ( options: { readonly capacity: "unbounded" readonly replay?: number | undefined readonly idleTimeToLive?: Duration.Input | undefined } | { readonly capacity: number readonly strategy?: "sliding" | "dropping" | "suspend" | undefined readonly replay?: number | undefined readonly idleTimeToLive?: Duration.Input | undefined } ): (self: Stream) => Effect.Effect, never, Scope.Scope | R> /** * Returns a new Stream that multicasts the original stream, subscribing when the first consumer starts. * * **Details** * * The upstream continues running while there is at least one consumer and is finalized after the last one exits. * If `idleTimeToLive` is set, the upstream is kept alive for that duration so a later subscriber can continue from * the next element instead of restarting. * * **Example** (Sharing a stream) * * ```ts import.meta.vitest * import { Deferred, Effect, Ref, Stream } from "effect" * * const result = await Effect.runPromise( * Effect.scoped( * Effect.gen(function*() { * const firstReady = yield* Deferred.make() * const secondReady = yield* Deferred.make() * const acquisitions = yield* Ref.make(0) * const source = Stream.fromEffect(Ref.update(acquisitions, (n) => n + 1)).pipe( * Stream.drain, * Stream.concat(Stream.make(0)), * Stream.concat( * Stream.fromEffect(Effect.all([Deferred.await(firstReady), Deferred.await(secondReady)])).pipe(Stream.drain) * ), * Stream.concat(Stream.make(1, 2, 3)) * ) * const shared = yield* Stream.share(source, { capacity: 16, replay: 1 }) * const consume = (ready: Deferred.Deferred) => * shared.pipe( * Stream.tap((value) => value === 0 ? Deferred.succeed(ready, void 0) : Effect.void), * Stream.filter((value) => value !== 0), * Stream.runCollect * ) * * const values = yield* Effect.all([consume(firstReady), consume(secondReady)], { concurrency: "unbounded" }) * return { values, acquisitions: yield* Ref.get(acquisitions) } * }) * ) * ) * result // => { values: [[1, 2, 3], [1, 2, 3]], acquisitions: 1 } * ``` * * @stability stable * @category broadcasting * @since 3.8.0 */ ( self: Stream, options: { readonly capacity: "unbounded" readonly replay?: number | undefined readonly idleTimeToLive?: Duration.Input | undefined } | { readonly capacity: number readonly strategy?: "sliding" | "dropping" | "suspend" | undefined readonly replay?: number | undefined readonly idleTimeToLive?: Duration.Input | undefined } ): Effect.Effect, never, Scope.Scope | R> } = dual(2, ( self: Stream, options: { readonly capacity: "unbounded" readonly replay?: number | undefined readonly idleTimeToLive?: Duration.Input | undefined } | { readonly capacity: number readonly strategy?: "sliding" | "dropping" | "suspend" | undefined readonly replay?: number | undefined readonly idleTimeToLive?: Duration.Input | undefined } ): Effect.Effect, never, Scope.Scope | R> => Effect.map( RcRef.make({ acquire: broadcast(self, options), idleTimeToLive: options.idleTimeToLive }), (ref) => unwrap(RcRef.get(ref)) )) /** * Pipes this stream through a channel that consumes and emits chunked elements. * * **Details** * * The channel receives `NonEmptyReadonlyArray` chunks and can transform both the * output elements and error type. * * **Example** (Piping through a channel) * * ```ts import.meta.vitest * import { Array, Channel, Effect, Stream } from "effect" * * type NumberChunk = readonly [number, ...Array] * * const doubleChunks = Channel.identity().pipe( * Channel.map((chunk) => Array.map(chunk, (n) => n * 2)) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.rechunk(2), * Stream.pipeThroughChannel(doubleChunks), * Stream.runCollect * ) * result // => [ 2, 4, 6 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const pipeThroughChannel: { /** * Pipes this stream through a channel that consumes and emits chunked elements. * * **Details** * * The channel receives `NonEmptyReadonlyArray` chunks and can transform both the * output elements and error type. * * **Example** (Piping through a channel) * * ```ts import.meta.vitest * import { Array, Channel, Effect, Stream } from "effect" * * type NumberChunk = readonly [number, ...Array] * * const doubleChunks = Channel.identity().pipe( * Channel.map((chunk) => Array.map(chunk, (n) => n * 2)) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.rechunk(2), * Stream.pipeThroughChannel(doubleChunks), * Stream.runCollect * ) * result // => [ 2, 4, 6 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ ( channel: Channel.Channel, E2, unknown, Arr.NonEmptyReadonlyArray, E, unknown, R2> ): (self: Stream) => Stream /** * Pipes this stream through a channel that consumes and emits chunked elements. * * **Details** * * The channel receives `NonEmptyReadonlyArray` chunks and can transform both the * output elements and error type. * * **Example** (Piping through a channel) * * ```ts import.meta.vitest * import { Array, Channel, Effect, Stream } from "effect" * * type NumberChunk = readonly [number, ...Array] * * const doubleChunks = Channel.identity().pipe( * Channel.map((chunk) => Array.map(chunk, (n) => n * 2)) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.rechunk(2), * Stream.pipeThroughChannel(doubleChunks), * Stream.runCollect * ) * result // => [ 2, 4, 6 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ ( self: Stream, channel: Channel.Channel, E2, unknown, Arr.NonEmptyReadonlyArray, E, unknown, R2> ): Stream } = dual(2, ( self: Stream, channel: Channel.Channel, E2, unknown, Arr.NonEmptyReadonlyArray, E, unknown, R2> ): Stream => fromChannel(Channel.pipeTo(self.channel, channel))) /** * Pipes values through the provided channel while preserving this stream's * failures alongside any channel failures. * * **Details** * * Upstream failures are not passed to the channel, so the resulting stream can * fail with either the original stream error or the channel error. * * **Example** (Piping through a channel with failures) * * ```ts import.meta.vitest * import { Array, Channel, Effect, Stream } from "effect" * * type NumberChunk = readonly [number, ...Array] * * const stringifyChunks = Channel.identity().pipe( * Channel.map((chunk) => Array.map(chunk, String)) * ) * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.rechunk(2), * Stream.pipeThroughChannelOrFail(stringifyChunks), * Stream.runCollect * ) * * result // => ["1", "2", "3"] * })) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const pipeThroughChannelOrFail: { /** * Pipes values through the provided channel while preserving this stream's * failures alongside any channel failures. * * **Details** * * Upstream failures are not passed to the channel, so the resulting stream can * fail with either the original stream error or the channel error. * * **Example** (Piping through a channel with failures) * * ```ts import.meta.vitest * import { Array, Channel, Effect, Stream } from "effect" * * type NumberChunk = readonly [number, ...Array] * * const stringifyChunks = Channel.identity().pipe( * Channel.map((chunk) => Array.map(chunk, String)) * ) * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.rechunk(2), * Stream.pipeThroughChannelOrFail(stringifyChunks), * Stream.runCollect * ) * * result // => ["1", "2", "3"] * })) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ ( channel: Channel.Channel, E2, unknown, Arr.NonEmptyReadonlyArray, E, unknown, R2> ): (self: Stream) => Stream /** * Pipes values through the provided channel while preserving this stream's * failures alongside any channel failures. * * **Details** * * Upstream failures are not passed to the channel, so the resulting stream can * fail with either the original stream error or the channel error. * * **Example** (Piping through a channel with failures) * * ```ts import.meta.vitest * import { Array, Channel, Effect, Stream } from "effect" * * type NumberChunk = readonly [number, ...Array] * * const stringifyChunks = Channel.identity().pipe( * Channel.map((chunk) => Array.map(chunk, String)) * ) * * await Effect.runPromise(Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.rechunk(2), * Stream.pipeThroughChannelOrFail(stringifyChunks), * Stream.runCollect * ) * * result // => ["1", "2", "3"] * })) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ ( self: Stream, channel: Channel.Channel, E2, unknown, Arr.NonEmptyReadonlyArray, E, unknown, R2> ): Stream } = dual(2, ( self: Stream, channel: Channel.Channel, E2, unknown, Arr.NonEmptyReadonlyArray, E, unknown, R2> ): Stream => fromChannel(Channel.pipeToOrFail(self.channel, channel))) /** * Pipes the stream through `Sink.toChannel`, emitting only the sink leftovers. * * **Details** * * If the sink completes mid-chunk, the remaining elements become the output stream. * * **Example** (Piping through a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * const program = Effect.gen(function*() { * const leftovers = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.pipeThrough(Sink.take(2)), * Stream.runCollect * ) * * leftovers // => [ 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const pipeThrough: { /** * Pipes the stream through `Sink.toChannel`, emitting only the sink leftovers. * * **Details** * * If the sink completes mid-chunk, the remaining elements become the output stream. * * **Example** (Piping through a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * const program = Effect.gen(function*() { * const leftovers = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.pipeThrough(Sink.take(2)), * Stream.runCollect * ) * * leftovers // => [ 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ (sink: Sink.Sink): (self: Stream) => Stream /** * Pipes the stream through `Sink.toChannel`, emitting only the sink leftovers. * * **Details** * * If the sink completes mid-chunk, the remaining elements become the output stream. * * **Example** (Piping through a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * const program = Effect.gen(function*() { * const leftovers = yield* Stream.make(1, 2, 3, 4).pipe( * Stream.pipeThrough(Sink.take(2)), * Stream.runCollect * ) * * leftovers // => [ 3, 4 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ (self: Stream, sink: Sink.Sink): Stream } = dual( 2, (self: Stream, sink: Sink.Sink): Stream => self.channel.pipe( Channel.pipeToOrFail(Sink.toChannel(sink)), Channel.concatWith(([_, leftover]) => leftover ? Channel.succeed(leftover) : Channel.empty), fromChannel ) ) /** * Collects all elements into an array and emits it as a single element. * * **Example** (Collecting values into a stream element) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * * const program = Effect.gen(function*() { * const collected = yield* stream.pipe(Stream.collect, Stream.runCollect) * collected[0] // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category accumulation * @since 4.0.0 */ export const collect = (self: Stream): Stream, E, R> => fromEffect(runCollect(self)) /** * Accumulates elements into a growing array, emitting the cumulative array for each input chunk. * * **Example** (Accumulating stream elements) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const accumulated = yield* Stream.runCollect( * Stream.fromArray([1, 2, 3]).pipe( * Stream.rechunk(1), * Stream.accumulate * ) * ) * accumulated // => [ [ 1 ], [ 1, 2 ], [ 1, 2, 3 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category accumulation * @since 2.0.0 */ export const accumulate = (self: Stream): Stream, E, R> => mapAccumArray(self, Arr.empty, (acc, as) => { const combined = Arr.appendAll(acc, as) return [combined, [combined]] }) /** * Emits only elements that differ from the previous one. * * **Example** (Emitting changed values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.fromIterable([1, 1, 2, 2, 3]).pipe( * Stream.changes, * Stream.runCollect * ) * * values // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category deduplication * @since 2.0.0 */ export const changes = (self: Stream): Stream => changesWith(self, Equal.equals) /** * Returns a stream that only emits elements that are not equal to the previously emitted element, as determined by the specified predicate. * * **Example** (Emitting values that changed by equivalence) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make("A", "a", "B", "b", "b").pipe( * Stream.changesWith((left, right) => left.toLowerCase() === right.toLowerCase()) * ) * * await Effect.runPromise( * Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 'A', 'B' ] * }) * ) * ``` * * @stability stable * @category deduplication * @since 2.0.0 */ export const changesWith: { /** * Returns a stream that only emits elements that are not equal to the previously emitted element, as determined by the specified predicate. * * **Example** (Emitting values that changed by equivalence) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make("A", "a", "B", "b", "b").pipe( * Stream.changesWith((left, right) => left.toLowerCase() === right.toLowerCase()) * ) * * await Effect.runPromise( * Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 'A', 'B' ] * }) * ) * ``` * * @stability stable * @category deduplication * @since 2.0.0 */ (f: (x: A, y: A) => boolean): (self: Stream) => Stream /** * Returns a stream that only emits elements that are not equal to the previously emitted element, as determined by the specified predicate. * * **Example** (Emitting values that changed by equivalence) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make("A", "a", "B", "b", "b").pipe( * Stream.changesWith((left, right) => left.toLowerCase() === right.toLowerCase()) * ) * * await Effect.runPromise( * Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 'A', 'B' ] * }) * ) * ``` * * @stability stable * @category deduplication * @since 2.0.0 */ (self: Stream, f: (x: A, y: A) => boolean): Stream } = dual( 2, (self: Stream, f: (x: A, y: A) => boolean): Stream => transformPull(self, (pull, _scope) => Effect.sync(() => { let first = true let last: A return Effect.flatMap(pull, function loop(arr): Pull.Pull, E> { const out: Array = [] let i = 0 if (first) { first = false last = arr[0] i = 1 out.push(last) } for (; i < arr.length; i++) { const a = arr[i] if (f(a, last)) continue last = a out.push(a) } return Arr.isArrayNonEmpty(out) ? Effect.succeed(out) : Effect.flatMap(pull, loop) }) })) ) /** * Emits only elements that differ from the previous element, using an effectful equality check. * * **Details** * * The predicate runs for each element after the first; returning `true` treats it as equal and skips it. * * **Example** (Effectfully emitting changed values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.make(1, 1, 2, 2, 3, 3).pipe( * Stream.changesWithEffect((a, b) => Effect.succeed(a === b)) * ) * const result = yield* Stream.runCollect(stream) * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category deduplication * @since 2.0.0 */ export const changesWithEffect: { /** * Emits only elements that differ from the previous element, using an effectful equality check. * * **Details** * * The predicate runs for each element after the first; returning `true` treats it as equal and skips it. * * **Example** (Effectfully emitting changed values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.make(1, 1, 2, 2, 3, 3).pipe( * Stream.changesWithEffect((a, b) => Effect.succeed(a === b)) * ) * const result = yield* Stream.runCollect(stream) * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category deduplication * @since 2.0.0 */ (f: (x: A, y: A) => Effect.Effect): (self: Stream) => Stream /** * Emits only elements that differ from the previous element, using an effectful equality check. * * **Details** * * The predicate runs for each element after the first; returning `true` treats it as equal and skips it. * * **Example** (Effectfully emitting changed values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.make(1, 1, 2, 2, 3, 3).pipe( * Stream.changesWithEffect((a, b) => Effect.succeed(a === b)) * ) * const result = yield* Stream.runCollect(stream) * result // => [ 1, 2, 3 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category deduplication * @since 2.0.0 */ (self: Stream, f: (x: A, y: A) => Effect.Effect): Stream } = dual( 2, ( self: Stream, f: (x: A, y: A) => Effect.Effect ): Stream => transformPull(self, (pull, _scope) => Effect.sync(() => { let first = true let last: A return Effect.flatMap( pull, Effect.fnUntraced(function* loop(arr): Generator< Pull.Pull, Arr.NonEmptyReadonlyArray, any > { const out: Array = [] let i = 0 if (first) { first = false last = arr[0] i = 1 out.push(last) } for (; i < arr.length; i++) { const a = arr[i] if (yield* f(a, last)) continue last = a out.push(a) } return Arr.isArrayNonEmpty(out) ? out : yield* Effect.flatMap(pull, Effect.fnUntraced(loop)) }) ) })) ) /** * Decodes Uint8Array chunks into strings using TextDecoder with an optional encoding. * * **Example** (Decoding Uint8Array chunks into strings using TextDecoder with an optional encoding) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const encoder = new TextEncoder() * const stream = Stream.make( * encoder.encode("Hello"), * encoder.encode(" World") * ) * * const program = Effect.gen(function*() { * const decoded = yield* stream.pipe( * Stream.decodeText, * Stream.runCollect * ) * decoded // => [ 'Hello', ' World' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category text * @since 2.0.0 */ export const decodeText: < Arg extends Stream | { readonly encoding?: string | undefined } | undefined = { readonly encoding?: string | undefined } >( streamOrOptions?: Arg, options?: { readonly encoding?: string | undefined } | undefined ) => [Arg] extends [Stream] ? Stream : (self: Stream) => Stream = dual( (args) => isStream(args[0]), (self: Stream, options?: { readonly encoding?: string | undefined }): Stream => self.channel.pipe( Channel.pipeTo(Channel.decodeText(options?.encoding)), fromChannel ) ) /** * Encodes a stream of strings into UTF-8 `Uint8Array` chunks. * * **Example** (Encoding a stream of strings into UTF-8 Uint8Array chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make("Hello", " ", "World") * const program = Effect.gen(function*() { * const encoded = Stream.encodeText(stream) * const chunks = yield* Stream.runCollect(encoded) * const bytes = chunks.map((chunk) => [...chunk]) * bytes // => [ [ 72, 101, 108, 108, 111 ], [ 32 ], [ 87, 111, 114, 108, 100 ] ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category text * @since 2.0.0 */ export const encodeText = (self: Stream): Stream => suspend(() => { const encoder = new TextEncoder() return map(self, (chunk) => encoder.encode(chunk)) }) /** * Splits a stream of strings into lines, handling `\n`, `\r`, and `\r\n` delimiters across chunks. * * **Example** (Splitting streamed text into lines) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * await Effect.runPromise(Effect.gen(function* () { * const lines = yield* Stream.runCollect( * Stream.make("a\nb\r\n", "c\n").pipe(Stream.splitLines) * ) * lines // => [ 'a', 'b', 'c' ] * })) * ``` * * @stability stable * @category text * @since 2.0.0 */ export const splitLines = (self: Stream): Stream => self.channel.pipe( Channel.pipeTo(Channel.splitLines()), fromChannel ) /** * Inserts the provided element between emitted elements. * * **Example** (Interspersing stream elements) * * ```ts import.meta.vitest * import { Console, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3, 4).pipe(Stream.intersperse(0)) * const result = yield* Stream.runCollect(stream) * result // => [1, 0, 2, 0, 3, 0, 4] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const intersperse: { /** * Inserts the provided element between emitted elements. * * **Example** (Interspersing stream elements) * * ```ts import.meta.vitest * import { Console, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3, 4).pipe(Stream.intersperse(0)) * const result = yield* Stream.runCollect(stream) * result // => [1, 0, 2, 0, 3, 0, 4] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ (element: A2): (self: Stream) => Stream /** * Inserts the provided element between emitted elements. * * **Example** (Interspersing stream elements) * * ```ts import.meta.vitest * import { Console, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3, 4).pipe(Stream.intersperse(0)) * const result = yield* Stream.runCollect(stream) * result // => [1, 0, 2, 0, 3, 0, 4] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ (self: Stream, element: A2): Stream } = dual(2, (self: Stream, element: A2): Stream => mapArray(self, (arr, i) => { const out: Arr.NonEmptyArray = i === 0 ? [] as any : [element] const lastIndex = arr.length - 1 for (let j = 0; j < arr.length; j++) { if (j === lastIndex) { out.push(arr[j]) } else { out.push(arr[j], element) } } return out })) /** * Adds a start value, middle value, and end value around stream elements. * * **Details** * * The start and end values are always emitted, even when the stream is empty. * * **Example** (Interspersing stream affixes) * * ```ts import.meta.vitest * import { Console, Effect, Stream } from "effect" * * const stream = Stream.make("a", "b", "c").pipe( * Stream.intersperseAffixes({ start: "[", middle: ",", end: "]" }) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => ["[", "a", ",", "b", ",", "c", "]"] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ export const intersperseAffixes: { /** * Adds a start value, middle value, and end value around stream elements. * * **Details** * * The start and end values are always emitted, even when the stream is empty. * * **Example** (Interspersing stream affixes) * * ```ts import.meta.vitest * import { Console, Effect, Stream } from "effect" * * const stream = Stream.make("a", "b", "c").pipe( * Stream.intersperseAffixes({ start: "[", middle: ",", end: "]" }) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => ["[", "a", ",", "b", ",", "c", "]"] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ (options: { readonly start: A2; readonly middle: A3; readonly end: A4 }): (self: Stream) => Stream /** * Adds a start value, middle value, and end value around stream elements. * * **Details** * * The start and end values are always emitted, even when the stream is empty. * * **Example** (Interspersing stream affixes) * * ```ts import.meta.vitest * import { Console, Effect, Stream } from "effect" * * const stream = Stream.make("a", "b", "c").pipe( * Stream.intersperseAffixes({ start: "[", middle: ",", end: "]" }) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => ["[", "a", ",", "b", ",", "c", "]"] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category sequencing * @since 2.0.0 */ ( self: Stream, options: { readonly start: A2; readonly middle: A3; readonly end: A4 } ): Stream } = dual(2, ( self: Stream, options: { readonly start: A2; readonly middle: A3; readonly end: A4 } ): Stream => succeed(options.start).pipe( concat(intersperse(self, options.middle)), concat(succeed(options.end)) )) /** * Interleaves this stream with the specified stream by alternating pulls from * each stream; when one ends, the remaining values from the other stream are * emitted. * * **Example** (Interleaving streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.interleave( * Stream.make(2, 3), * Stream.make(5, 6, 7) * ) * * const program = Effect.gen(function*() { * const collected = yield* Stream.runCollect(stream) * collected // => [ 2, 5, 3, 6, 7 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ export const interleave: { /** * Interleaves this stream with the specified stream by alternating pulls from * each stream; when one ends, the remaining values from the other stream are * emitted. * * **Example** (Interleaving streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.interleave( * Stream.make(2, 3), * Stream.make(5, 6, 7) * ) * * const program = Effect.gen(function*() { * const collected = yield* Stream.runCollect(stream) * collected // => [ 2, 5, 3, 6, 7 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ (that: Stream): (self: Stream) => Stream /** * Interleaves this stream with the specified stream by alternating pulls from * each stream; when one ends, the remaining values from the other stream are * emitted. * * **Example** (Interleaving streams) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.interleave( * Stream.make(2, 3), * Stream.make(5, 6, 7) * ) * * const program = Effect.gen(function*() { * const collected = yield* Stream.runCollect(stream) * collected // => [ 2, 5, 3, 6, 7 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ (self: Stream, that: Stream): Stream } = dual( 2, (self: Stream, that: Stream): Stream => interleaveWith( self, that, fromIterable(Iterable.forever([true, false])) ) ) /** * Interleaves two streams deterministically by following a boolean decider stream. * * **Details** * * The decider controls how many elements are pulled; if one side ends, pulls for * that side are ignored. * * **Example** (Interleaving two streams deterministically by following a boolean decider stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const left = Stream.make(1, 3, 5) * const right = Stream.make(2, 4, 6) * const decider = Stream.make(true, false, false, true, true) * * const values = yield* Stream.runCollect( * Stream.interleaveWith(left, right, decider) * ) * * values // => [ 1, 2, 4, 3, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ export const interleaveWith: { /** * Interleaves two streams deterministically by following a boolean decider stream. * * **Details** * * The decider controls how many elements are pulled; if one side ends, pulls for * that side are ignored. * * **Example** (Interleaving two streams deterministically by following a boolean decider stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const left = Stream.make(1, 3, 5) * const right = Stream.make(2, 4, 6) * const decider = Stream.make(true, false, false, true, true) * * const values = yield* Stream.runCollect( * Stream.interleaveWith(left, right, decider) * ) * * values // => [ 1, 2, 4, 3, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ (that: Stream, decider: Stream): (self: Stream) => Stream /** * Interleaves two streams deterministically by following a boolean decider stream. * * **Details** * * The decider controls how many elements are pulled; if one side ends, pulls for * that side are ignored. * * **Example** (Interleaving two streams deterministically by following a boolean decider stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const left = Stream.make(1, 3, 5) * const right = Stream.make(2, 4, 6) * const decider = Stream.make(true, false, false, true, true) * * const values = yield* Stream.runCollect( * Stream.interleaveWith(left, right, decider) * ) * * values // => [ 1, 2, 4, 3, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category merging * @since 2.0.0 */ ( self: Stream, that: Stream, decider: Stream ): Stream } = dual(3, ( self: Stream, that: Stream, decider: Stream ): Stream => fromChannel(Channel.fromTransform(Effect.fnUntraced(function*(upstream, scope) { const pullDecider = yield* Channel.toTransform(Channel.flattenArray(decider.channel))(upstream, scope) const retry = Symbol() type retry = typeof retry let leftDone = false let rightDone = false const pullLeft = (yield* Channel.toTransform(Channel.flattenArray(self.channel))( upstream, scope )).pipe( Pull.catchDone(() => { leftDone = true return Effect.succeed(retry) }) ) const pullRight = (yield* Channel.toTransform(Channel.flattenArray(that.channel))( upstream, scope )).pipe( Pull.catchDone(() => { rightDone = true return Effect.succeed(retry) }) ) return Effect.gen(function*() { while (true) { if (leftDone && rightDone) { return yield* Cause.done() } const side = yield* pullDecider if (side && leftDone) continue if (!side && rightDone) continue const elem = yield* (side ? pullLeft : pullRight) if (elem === retry) continue return Arr.of(elem) } }) })))) /** * Interrupts the evaluation of this stream when the provided effect * completes. The given effect will be forked as part of this stream, and its * success will be discarded. This combinator will also interrupt any * in-progress element being pulled from upstream. * * **Details** * * If the effect completes with a failure before the stream completes, the * returned stream will emit that failure. * * **Example** (Interrupting when an effect completes) * * ```ts import.meta.vitest * import { Deferred, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const interrupt = yield* Deferred.make() * const stream = Stream.make(1, 2, 3).pipe( * Stream.tap((value) => * value === 2 * ? Deferred.succeed(interrupt, void 0) * : Effect.void * ), * Stream.interruptWhen(Deferred.await(interrupt)) * ) * * const result = yield* Stream.runCollect(stream) * result // => [ 1 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category interruption * @since 2.0.0 */ export const interruptWhen: { /** * Interrupts the evaluation of this stream when the provided effect * completes. The given effect will be forked as part of this stream, and its * success will be discarded. This combinator will also interrupt any * in-progress element being pulled from upstream. * * **Details** * * If the effect completes with a failure before the stream completes, the * returned stream will emit that failure. * * **Example** (Interrupting when an effect completes) * * ```ts import.meta.vitest * import { Deferred, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const interrupt = yield* Deferred.make() * const stream = Stream.make(1, 2, 3).pipe( * Stream.tap((value) => * value === 2 * ? Deferred.succeed(interrupt, void 0) * : Effect.void * ), * Stream.interruptWhen(Deferred.await(interrupt)) * ) * * const result = yield* Stream.runCollect(stream) * result // => [ 1 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category interruption * @since 2.0.0 */ (effect: Effect.Effect): (self: Stream) => Stream /** * Interrupts the evaluation of this stream when the provided effect * completes. The given effect will be forked as part of this stream, and its * success will be discarded. This combinator will also interrupt any * in-progress element being pulled from upstream. * * **Details** * * If the effect completes with a failure before the stream completes, the * returned stream will emit that failure. * * **Example** (Interrupting when an effect completes) * * ```ts import.meta.vitest * import { Deferred, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const interrupt = yield* Deferred.make() * const stream = Stream.make(1, 2, 3).pipe( * Stream.tap((value) => * value === 2 * ? Deferred.succeed(interrupt, void 0) * : Effect.void * ), * Stream.interruptWhen(Deferred.await(interrupt)) * ) * * const result = yield* Stream.runCollect(stream) * result // => [ 1 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category interruption * @since 2.0.0 */ (self: Stream, effect: Effect.Effect): Stream } = dual( 2, (self: Stream, effect: Effect.Effect): Stream => fromChannel(Channel.interruptWhen(self.channel, effect)) ) /** * Stops a stream after the current pull when an effect completes. * * **When to use** * * Use to stop before the next pull after an external signal completes. * * **Details** * * The effect is forked, its success value is discarded, and its failure fails * the stream. * * **Gotchas** * * This does not interrupt or truncate an in-progress pull. A pull may emit * multiple elements in a single chunk, in which case the entire chunk is * emitted. Use {@link interruptWhen} when the stream should be interrupted * immediately. * * **Example** (Halting a stream after an effect completes) * * ```ts import.meta.vitest * import { Deferred, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const halt = yield* Deferred.make() * const values = yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.tap((value) => value === 2 ? Deferred.succeed(halt, void 0) : Effect.void), * Stream.haltWhen(Deferred.await(halt)), * Stream.runCollect * ) * values // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category interruption * @since 2.0.0 */ export const haltWhen: { /** * Stops a stream after the current pull when an effect completes. * * **When to use** * * Use to stop before the next pull after an external signal completes. * * **Details** * * The effect is forked, its success value is discarded, and its failure fails * the stream. * * **Gotchas** * * This does not interrupt or truncate an in-progress pull. A pull may emit * multiple elements in a single chunk, in which case the entire chunk is * emitted. Use {@link interruptWhen} when the stream should be interrupted * immediately. * * **Example** (Halting a stream after an effect completes) * * ```ts import.meta.vitest * import { Deferred, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const halt = yield* Deferred.make() * const values = yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.tap((value) => value === 2 ? Deferred.succeed(halt, void 0) : Effect.void), * Stream.haltWhen(Deferred.await(halt)), * Stream.runCollect * ) * values // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category interruption * @since 2.0.0 */ (effect: Effect.Effect): (self: Stream) => Stream /** * Stops a stream after the current pull when an effect completes. * * **When to use** * * Use to stop before the next pull after an external signal completes. * * **Details** * * The effect is forked, its success value is discarded, and its failure fails * the stream. * * **Gotchas** * * This does not interrupt or truncate an in-progress pull. A pull may emit * multiple elements in a single chunk, in which case the entire chunk is * emitted. Use {@link interruptWhen} when the stream should be interrupted * immediately. * * **Example** (Halting a stream after an effect completes) * * ```ts import.meta.vitest * import { Deferred, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const halt = yield* Deferred.make() * const values = yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.tap((value) => value === 2 ? Deferred.succeed(halt, void 0) : Effect.void), * Stream.haltWhen(Deferred.await(halt)), * Stream.runCollect * ) * values // => [ 1, 2 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category interruption * @since 2.0.0 */ (self: Stream, effect: Effect.Effect): Stream } = dual( 2, (self: Stream, effect: Effect.Effect): Stream => fromChannel(Channel.haltWhen(self.channel, effect)) ) /** * Runs the provided finalizer when the stream exits, passing the exit value. * * **Example** (Running a finalizer on exit) * * ```ts import.meta.vitest * import { Effect, Exit, Stream } from "effect" * * const exits: Array = [] * const stream = Stream.make(1, 2, 3).pipe( * Stream.onExit((exit) => * Exit.isSuccess(exit) * ? Effect.sync(() => exits.push("success")) * : Effect.sync(() => exits.push("failure")) * ) * ) * * await Effect.runPromise(Effect.gen(function*() { * yield* Stream.runCollect(stream) * })) * exits // => ["success"] * ``` * * @stability stable * @category resource management * @since 4.0.0 */ export const onExit: { /** * Runs the provided finalizer when the stream exits, passing the exit value. * * **Example** (Running a finalizer on exit) * * ```ts import.meta.vitest * import { Effect, Exit, Stream } from "effect" * * const exits: Array = [] * const stream = Stream.make(1, 2, 3).pipe( * Stream.onExit((exit) => * Exit.isSuccess(exit) * ? Effect.sync(() => exits.push("success")) * : Effect.sync(() => exits.push("failure")) * ) * ) * * await Effect.runPromise(Effect.gen(function*() { * yield* Stream.runCollect(stream) * })) * exits // => ["success"] * ``` * * @stability stable * @category resource management * @since 4.0.0 */ ( finalizer: (exit: Exit.Exit) => Effect.Effect ): (self: Stream) => Stream /** * Runs the provided finalizer when the stream exits, passing the exit value. * * **Example** (Running a finalizer on exit) * * ```ts import.meta.vitest * import { Effect, Exit, Stream } from "effect" * * const exits: Array = [] * const stream = Stream.make(1, 2, 3).pipe( * Stream.onExit((exit) => * Exit.isSuccess(exit) * ? Effect.sync(() => exits.push("success")) * : Effect.sync(() => exits.push("failure")) * ) * ) * * await Effect.runPromise(Effect.gen(function*() { * yield* Stream.runCollect(stream) * })) * exits // => ["success"] * ``` * * @stability stable * @category resource management * @since 4.0.0 */ ( self: Stream, finalizer: (exit: Exit.Exit) => Effect.Effect ): Stream } = dual(2, ( self: Stream, finalizer: (exit: Exit.Exit) => Effect.Effect ): Stream => fromChannel(Channel.onExit(self.channel, finalizer))) /** * Runs the provided effect when the stream fails, passing the failure cause. * * **Gotchas** * * Note: Unlike `Effect.onError` there is no guarantee that the provided * effect will not be interrupted. * * **Example** (Running an effect on errors) * * ```ts import.meta.vitest * import { Cause, Effect, Stream } from "effect" * * const errors: Array = [] * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3).pipe( * Stream.concat(Stream.fail("boom")), * Stream.onError((cause) => Effect.sync(() => errors.push(String(Cause.squash(cause))))) * ) * * yield* Stream.runCollect(stream) * }) * * await Effect.runPromise(Effect.exit(program)) * errors // => ["boom"] * ``` * * @stability stable * @category resource management * @since 2.0.0 */ export const onError: { /** * Runs the provided effect when the stream fails, passing the failure cause. * * **Gotchas** * * Note: Unlike `Effect.onError` there is no guarantee that the provided * effect will not be interrupted. * * **Example** (Running an effect on errors) * * ```ts import.meta.vitest * import { Cause, Effect, Stream } from "effect" * * const errors: Array = [] * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3).pipe( * Stream.concat(Stream.fail("boom")), * Stream.onError((cause) => Effect.sync(() => errors.push(String(Cause.squash(cause))))) * ) * * yield* Stream.runCollect(stream) * }) * * await Effect.runPromise(Effect.exit(program)) * errors // => ["boom"] * ``` * * @stability stable * @category resource management * @since 2.0.0 */ (cleanup: (cause: Cause.Cause) => Effect.Effect): (self: Stream) => Stream /** * Runs the provided effect when the stream fails, passing the failure cause. * * **Gotchas** * * Note: Unlike `Effect.onError` there is no guarantee that the provided * effect will not be interrupted. * * **Example** (Running an effect on errors) * * ```ts import.meta.vitest * import { Cause, Effect, Stream } from "effect" * * const errors: Array = [] * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3).pipe( * Stream.concat(Stream.fail("boom")), * Stream.onError((cause) => Effect.sync(() => errors.push(String(Cause.squash(cause))))) * ) * * yield* Stream.runCollect(stream) * }) * * await Effect.runPromise(Effect.exit(program)) * errors // => ["boom"] * ``` * * @stability stable * @category resource management * @since 2.0.0 */ ( self: Stream, cleanup: (cause: Cause.Cause) => Effect.Effect ): Stream } = dual(2, ( self: Stream, cleanup: (cause: Cause.Cause) => Effect.Effect ): Stream => fromChannel(Channel.onError(self.channel, cleanup))) /** * Runs the provided effect before this stream starts. * * **Example** (Running an effect on start) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const stream = Stream.fromArray([1, 2, 3]).pipe( * Stream.onStart(Effect.sync(() => events.push("started"))) * ) * * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * events // => ["started"] * ``` * * @stability stable * @category sequencing * @since 3.6.0 */ export const onStart: { /** * Runs the provided effect before this stream starts. * * **Example** (Running an effect on start) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const stream = Stream.fromArray([1, 2, 3]).pipe( * Stream.onStart(Effect.sync(() => events.push("started"))) * ) * * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * events // => ["started"] * ``` * * @stability stable * @category sequencing * @since 3.6.0 */ (onStart: Effect.Effect): (self: Stream) => Stream /** * Runs the provided effect before this stream starts. * * **Example** (Running an effect on start) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const stream = Stream.fromArray([1, 2, 3]).pipe( * Stream.onStart(Effect.sync(() => events.push("started"))) * ) * * const values = yield* Stream.runCollect(stream) * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * events // => ["started"] * ``` * * @stability stable * @category sequencing * @since 3.6.0 */ (self: Stream, onStart: Effect.Effect): Stream } = dual(2, ( self: Stream, onStart: Effect.Effect ): Stream => fromChannel(Channel.onStart(self.channel, onStart))) /** * Runs the provided effect with the first element emitted by the stream. * * **Example** (Running an effect on the first value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const first: Array = [] * await Effect.runPromise(Effect.gen(function* () { * yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.onFirst((value) => Effect.sync(() => first.push(value))), * Stream.runDrain * ) * })) * first // => [1] * ``` * * @stability stable * @category sequencing * @since 4.0.0 */ export const onFirst: { /** * Runs the provided effect with the first element emitted by the stream. * * **Example** (Running an effect on the first value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const first: Array = [] * await Effect.runPromise(Effect.gen(function* () { * yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.onFirst((value) => Effect.sync(() => first.push(value))), * Stream.runDrain * ) * })) * first // => [1] * ``` * * @stability stable * @category sequencing * @since 4.0.0 */ (onFirst: (element: NoInfer) => Effect.Effect): (self: Stream) => Stream /** * Runs the provided effect with the first element emitted by the stream. * * **Example** (Running an effect on the first value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const first: Array = [] * await Effect.runPromise(Effect.gen(function* () { * yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.onFirst((value) => Effect.sync(() => first.push(value))), * Stream.runDrain * ) * })) * first // => [1] * ``` * * @stability stable * @category sequencing * @since 4.0.0 */ ( self: Stream, onFirst: (element: NoInfer) => Effect.Effect ): Stream } = dual(2, ( self: Stream, onFirst: (element: NoInfer) => Effect.Effect ): Stream => fromChannel(Channel.onFirst(self.channel, (arr) => onFirst(arr[0])))) /** * Runs the provided effect when the stream ends successfully. * * **Example** (Running an effect on end) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.onEnd(Effect.sync(() => events.push("ended"))), * Stream.runCollect * ) * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * events // => ["ended"] * ``` * * @stability stable * @category sequencing * @since 3.6.0 */ export const onEnd: { /** * Runs the provided effect when the stream ends successfully. * * **Example** (Running an effect on end) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.onEnd(Effect.sync(() => events.push("ended"))), * Stream.runCollect * ) * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * events // => ["ended"] * ``` * * @stability stable * @category sequencing * @since 3.6.0 */ (onEnd: Effect.Effect): (self: Stream) => Stream /** * Runs the provided effect when the stream ends successfully. * * **Example** (Running an effect on end) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( * Stream.onEnd(Effect.sync(() => events.push("ended"))), * Stream.runCollect * ) * values // => [1, 2, 3] * }) * * await Effect.runPromise(program) * events // => ["ended"] * ``` * * @stability stable * @category sequencing * @since 3.6.0 */ (self: Stream, onEnd: Effect.Effect): Stream } = dual(2, ( self: Stream, onEnd: Effect.Effect ): Stream => fromChannel(Channel.onEnd(self.channel, onEnd))) /** * Executes the provided finalizer after this stream's finalizers run. * * **Example** (Ensuring finalization) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const stream = Stream.fromArray([1, 2]).pipe( * Stream.ensuring(Effect.sync(() => events.push("cleanup"))) * ) * * const program = Effect.gen(function*() { * const collected = yield* Stream.runCollect(stream) * collected // => [1, 2] * }) * * await Effect.runPromise(program) * events // => ["cleanup"] * ``` * * @stability stable * @category resource management * @since 2.0.0 */ export const ensuring: { /** * Executes the provided finalizer after this stream's finalizers run. * * **Example** (Ensuring finalization) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const stream = Stream.fromArray([1, 2]).pipe( * Stream.ensuring(Effect.sync(() => events.push("cleanup"))) * ) * * const program = Effect.gen(function*() { * const collected = yield* Stream.runCollect(stream) * collected // => [1, 2] * }) * * await Effect.runPromise(program) * events // => ["cleanup"] * ``` * * @stability stable * @category resource management * @since 2.0.0 */ (finalizer: Effect.Effect): (self: Stream) => Stream /** * Executes the provided finalizer after this stream's finalizers run. * * **Example** (Ensuring finalization) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const events: Array = [] * const stream = Stream.fromArray([1, 2]).pipe( * Stream.ensuring(Effect.sync(() => events.push("cleanup"))) * ) * * const program = Effect.gen(function*() { * const collected = yield* Stream.runCollect(stream) * collected // => [1, 2] * }) * * await Effect.runPromise(program) * events // => ["cleanup"] * ``` * * @stability stable * @category resource management * @since 2.0.0 */ (self: Stream, finalizer: Effect.Effect): Stream } = dual( 2, (self: Stream, finalizer: Effect.Effect): Stream => fromChannel(Channel.ensuring(self.channel, finalizer)) ) /** * Provides a layer or context to the stream, removing the corresponding * service requirements. Use `options.local` to build the layer every time; by * default, a layer already built by an enclosing provide or runtime is reused; * sibling provide calls build their own copies. * * **Example** (Providing stream requirements) * * ```ts import.meta.vitest * import { Console, Context, Effect, Layer, Stream } from "effect" * * class Env extends Context.Service()("Env") {} * * const layer = Layer.succeed(Env)({ name: "Ada" }) * * const stream = Stream.fromEffect( * Effect.gen(function*() { * const env = yield* Effect.service(Env) * return `Hello, ${env.name}` * }) * ) * * const withEnv = stream.pipe(Stream.provide(layer)) * * await Effect.runPromise(Stream.runCollect(withEnv)) // => ["Hello, Ada"] * ``` * * @stability stable * @category providing services * @since 4.0.0 */ export const provide: { /** * Provides a layer or context to the stream, removing the corresponding * service requirements. Use `options.local` to build the layer every time; by * default, a layer already built by an enclosing provide or runtime is reused; * sibling provide calls build their own copies. * * **Example** (Providing stream requirements) * * ```ts import.meta.vitest * import { Console, Context, Effect, Layer, Stream } from "effect" * * class Env extends Context.Service()("Env") {} * * const layer = Layer.succeed(Env)({ name: "Ada" }) * * const stream = Stream.fromEffect( * Effect.gen(function*() { * const env = yield* Effect.service(Env) * return `Hello, ${env.name}` * }) * ) * * const withEnv = stream.pipe(Stream.provide(layer)) * * await Effect.runPromise(Stream.runCollect(withEnv)) // => ["Hello, Ada"] * ``` * * @stability stable * @category providing services * @since 4.0.0 */ ( layer: Layer.Layer | Context.Context, options?: { readonly local?: boolean | undefined } | undefined ): ( self: Stream ) => Stream | RL> /** * Provides a layer or context to the stream, removing the corresponding * service requirements. Use `options.local` to build the layer every time; by * default, a layer already built by an enclosing provide or runtime is reused; * sibling provide calls build their own copies. * * **Example** (Providing stream requirements) * * ```ts import.meta.vitest * import { Console, Context, Effect, Layer, Stream } from "effect" * * class Env extends Context.Service()("Env") {} * * const layer = Layer.succeed(Env)({ name: "Ada" }) * * const stream = Stream.fromEffect( * Effect.gen(function*() { * const env = yield* Effect.service(Env) * return `Hello, ${env.name}` * }) * ) * * const withEnv = stream.pipe(Stream.provide(layer)) * * await Effect.runPromise(Stream.runCollect(withEnv)) // => ["Hello, Ada"] * ``` * * @stability stable * @category providing services * @since 4.0.0 */ ( self: Stream, layer: Layer.Layer | Context.Context, options?: { readonly local?: boolean | undefined } | undefined ): Stream | RL> } = dual((args) => isStream(args[0]), ( self: Stream, layer: Layer.Layer | Context.Context, options?: { readonly local?: boolean | undefined } | undefined ): Stream | RL> => fromChannel(Channel.provide(self.channel, layer, options))) /** * Provides multiple services to the stream using a context. * * **Example** (Providing multiple services to the stream using a context) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class Config extends Context.Service()("Config") {} * class Greeter extends Context.Service string }>()("Greeter") {} * * const context = Context.make(Config, { prefix: "Hello" }).pipe( * Context.add(Greeter, { greet: (name: string) => `${name}!` }) * ) * * const stream = Stream.fromEffect( * Effect.gen(function*() { * const config = yield* Effect.service(Config) * const greeter = yield* Effect.service(Greeter) * return greeter.greet(config.prefix) * }) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(Stream.provideContext(stream, context)) * result // => [ 'Hello!' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category providing services * @since 2.0.0 */ export const provideContext: { /** * Provides multiple services to the stream using a context. * * **Example** (Providing multiple services to the stream using a context) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class Config extends Context.Service()("Config") {} * class Greeter extends Context.Service string }>()("Greeter") {} * * const context = Context.make(Config, { prefix: "Hello" }).pipe( * Context.add(Greeter, { greet: (name: string) => `${name}!` }) * ) * * const stream = Stream.fromEffect( * Effect.gen(function*() { * const config = yield* Effect.service(Config) * const greeter = yield* Effect.service(Greeter) * return greeter.greet(config.prefix) * }) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(Stream.provideContext(stream, context)) * result // => [ 'Hello!' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category providing services * @since 2.0.0 */ (context: Context.Context): (self: Stream) => Stream> /** * Provides multiple services to the stream using a context. * * **Example** (Providing multiple services to the stream using a context) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class Config extends Context.Service()("Config") {} * class Greeter extends Context.Service string }>()("Greeter") {} * * const context = Context.make(Config, { prefix: "Hello" }).pipe( * Context.add(Greeter, { greet: (name: string) => `${name}!` }) * ) * * const stream = Stream.fromEffect( * Effect.gen(function*() { * const config = yield* Effect.service(Config) * const greeter = yield* Effect.service(Greeter) * return greeter.greet(config.prefix) * }) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(Stream.provideContext(stream, context)) * result // => [ 'Hello!' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category providing services * @since 2.0.0 */ (self: Stream, context: Context.Context): Stream> } = dual( 2, (self: Stream, context: Context.Context): Stream> => fromChannel(Channel.provideContext(self.channel, context)) ) /** * Provides the stream with a single required service, eliminating that * requirement from its environment. * * **Example** (Providing a stream service) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class Greeter extends Context.Service string * }>()("Greeter") {} * * const stream = Stream.fromEffect( * Effect.service(Greeter).pipe( * Effect.map((greeter) => greeter.greet("Ada")) * ) * ) * * const program = Effect.gen(function*() { * const collected = yield* Stream.runCollect( * stream.pipe( * Stream.provideService(Greeter, { * greet: (name) => `Hello, ${name}` * }) * ) * ) * collected // => [ 'Hello, Ada' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category providing services * @since 2.0.0 */ export const provideService: { /** * Provides the stream with a single required service, eliminating that * requirement from its environment. * * **Example** (Providing a stream service) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class Greeter extends Context.Service string * }>()("Greeter") {} * * const stream = Stream.fromEffect( * Effect.service(Greeter).pipe( * Effect.map((greeter) => greeter.greet("Ada")) * ) * ) * * const program = Effect.gen(function*() { * const collected = yield* Stream.runCollect( * stream.pipe( * Stream.provideService(Greeter, { * greet: (name) => `Hello, ${name}` * }) * ) * ) * collected // => [ 'Hello, Ada' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category providing services * @since 2.0.0 */ (key: Context.Key, service: NoInfer): ( self: Stream ) => Stream> /** * Provides the stream with a single required service, eliminating that * requirement from its environment. * * **Example** (Providing a stream service) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class Greeter extends Context.Service string * }>()("Greeter") {} * * const stream = Stream.fromEffect( * Effect.service(Greeter).pipe( * Effect.map((greeter) => greeter.greet("Ada")) * ) * ) * * const program = Effect.gen(function*() { * const collected = yield* Stream.runCollect( * stream.pipe( * Stream.provideService(Greeter, { * greet: (name) => `Hello, ${name}` * }) * ) * ) * collected // => [ 'Hello, Ada' ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category providing services * @since 2.0.0 */ (self: Stream, key: Context.Key, service: NoInfer): Stream> } = dual(3, ( self: Stream, key: Context.Key, service: NoInfer ): Stream> => fromChannel(Channel.provideService(self.channel, key, service))) /** * Provides a service to the stream using an effect, removing the requirement and adding the effect's error and environment. * * **Example** (Providing a stream service effectfully) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class ApiConfig extends Context.Service()("ApiConfig") {} * * const stream = Stream.fromEffect( * Effect.gen(function*() { * const config = yield* Effect.service(ApiConfig) * return config.baseUrl * }) * ) * * const events: Array = [] * const withConfig = stream.pipe( * Stream.provideServiceEffect( * ApiConfig, * Effect.succeed({ baseUrl: "https://example.com" }).pipe( * Effect.tap(() => Effect.sync(() => events.push("loading"))) * ) * ) * ) * * await Effect.runPromise(Stream.runCollect(withConfig)) // => ["https://example.com"] * events // => ["loading"] * ``` * * @stability stable * @category providing services * @since 2.0.0 */ export const provideServiceEffect: { /** * Provides a service to the stream using an effect, removing the requirement and adding the effect's error and environment. * * **Example** (Providing a stream service effectfully) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class ApiConfig extends Context.Service()("ApiConfig") {} * * const stream = Stream.fromEffect( * Effect.gen(function*() { * const config = yield* Effect.service(ApiConfig) * return config.baseUrl * }) * ) * * const events: Array = [] * const withConfig = stream.pipe( * Stream.provideServiceEffect( * ApiConfig, * Effect.succeed({ baseUrl: "https://example.com" }).pipe( * Effect.tap(() => Effect.sync(() => events.push("loading"))) * ) * ) * ) * * await Effect.runPromise(Stream.runCollect(withConfig)) // => ["https://example.com"] * events // => ["loading"] * ``` * * @stability stable * @category providing services * @since 2.0.0 */ (key: Context.Key, service: Effect.Effect, ES, RS>): ( self: Stream ) => Stream | RS> /** * Provides a service to the stream using an effect, removing the requirement and adding the effect's error and environment. * * **Example** (Providing a stream service effectfully) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class ApiConfig extends Context.Service()("ApiConfig") {} * * const stream = Stream.fromEffect( * Effect.gen(function*() { * const config = yield* Effect.service(ApiConfig) * return config.baseUrl * }) * ) * * const events: Array = [] * const withConfig = stream.pipe( * Stream.provideServiceEffect( * ApiConfig, * Effect.succeed({ baseUrl: "https://example.com" }).pipe( * Effect.tap(() => Effect.sync(() => events.push("loading"))) * ) * ) * ) * * await Effect.runPromise(Stream.runCollect(withConfig)) // => ["https://example.com"] * events // => ["loading"] * ``` * * @stability stable * @category providing services * @since 2.0.0 */ ( self: Stream, key: Context.Key, service: Effect.Effect, ES, RS> ): Stream | RS> } = dual(3, ( self: Stream, key: Context.Key, service: Effect.Effect, ES, RS> ): Stream | RS> => fromChannel(Channel.provideServiceEffect(self.channel, key, service))) /** * Transforms the stream's required services by mapping the current context * to a new one. * * **Example** (Updating the stream context) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class Logger extends Context.Service()("Logger") {} * class Config extends Context.Service()("Config") {} * * const stream = Stream.fromEffect( * Effect.gen(function*() { * const logger = yield* Effect.service(Logger) * const config = yield* Effect.service(Config) * return `${logger.prefix}${config.name}` * }) * ) * * const updated = stream.pipe( * Stream.updateContext((context: Context.Context) => * Context.add(context, Config, { name: "World" }) * ) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(updated) * values // => [ 'Hello World' ] * }) * * await Effect.runPromise( * Effect.provideService(program, Logger, { prefix: "Hello " }) * ) * ``` * * @stability stable * @category providing services * @since 4.0.0 */ export const updateContext: { /** * Transforms the stream's required services by mapping the current context * to a new one. * * **Example** (Updating the stream context) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class Logger extends Context.Service()("Logger") {} * class Config extends Context.Service()("Config") {} * * const stream = Stream.fromEffect( * Effect.gen(function*() { * const logger = yield* Effect.service(Logger) * const config = yield* Effect.service(Config) * return `${logger.prefix}${config.name}` * }) * ) * * const updated = stream.pipe( * Stream.updateContext((context: Context.Context) => * Context.add(context, Config, { name: "World" }) * ) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(updated) * values // => [ 'Hello World' ] * }) * * await Effect.runPromise( * Effect.provideService(program, Logger, { prefix: "Hello " }) * ) * ``` * * @stability stable * @category providing services * @since 4.0.0 */ (f: (context: Context.Context) => Context.Context): ( self: Stream ) => Stream /** * Transforms the stream's required services by mapping the current context * to a new one. * * **Example** (Updating the stream context) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class Logger extends Context.Service()("Logger") {} * class Config extends Context.Service()("Config") {} * * const stream = Stream.fromEffect( * Effect.gen(function*() { * const logger = yield* Effect.service(Logger) * const config = yield* Effect.service(Config) * return `${logger.prefix}${config.name}` * }) * ) * * const updated = stream.pipe( * Stream.updateContext((context: Context.Context) => * Context.add(context, Config, { name: "World" }) * ) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(updated) * values // => [ 'Hello World' ] * }) * * await Effect.runPromise( * Effect.provideService(program, Logger, { prefix: "Hello " }) * ) * ``` * * @stability stable * @category providing services * @since 4.0.0 */ ( self: Stream, f: (context: Context.Context) => Context.Context ): Stream } = dual(2, ( self: Stream, f: (context: Context.Context) => Context.Context ): Stream => fromChannel(Channel.updateContext(self.channel, f))) /** * Updates a single service in the stream environment by applying a function. * * **Example** (Updating a stream service) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class Counter extends Context.Service()("Counter") {} * * const stream = Stream.fromEffect(Effect.service(Counter)).pipe( * Stream.updateService(Counter, (counter) => ({ count: counter.count + 1 })) * ) * * const program = Effect.gen(function*() { * const counters = yield* Stream.runCollect(stream) * const message = `Updated count: ${counters[0].count}` // => "Updated count: 1" * }) * * await Effect.runPromise(Effect.provideService(program, Counter, { count: 0 })) * ``` * * @stability stable * @category providing services * @since 2.0.0 */ export const updateService: { /** * Updates a single service in the stream environment by applying a function. * * **Example** (Updating a stream service) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class Counter extends Context.Service()("Counter") {} * * const stream = Stream.fromEffect(Effect.service(Counter)).pipe( * Stream.updateService(Counter, (counter) => ({ count: counter.count + 1 })) * ) * * const program = Effect.gen(function*() { * const counters = yield* Stream.runCollect(stream) * const message = `Updated count: ${counters[0].count}` // => "Updated count: 1" * }) * * await Effect.runPromise(Effect.provideService(program, Counter, { count: 0 })) * ``` * * @stability stable * @category providing services * @since 2.0.0 */ (key: Context.Key, f: (service: NoInfer) => S): ( self: Stream ) => Stream /** * Updates a single service in the stream environment by applying a function. * * **Example** (Updating a stream service) * * ```ts import.meta.vitest * import { Context, Effect, Stream } from "effect" * * class Counter extends Context.Service()("Counter") {} * * const stream = Stream.fromEffect(Effect.service(Counter)).pipe( * Stream.updateService(Counter, (counter) => ({ count: counter.count + 1 })) * ) * * const program = Effect.gen(function*() { * const counters = yield* Stream.runCollect(stream) * const message = `Updated count: ${counters[0].count}` // => "Updated count: 1" * }) * * await Effect.runPromise(Effect.provideService(program, Counter, { count: 0 })) * ``` * * @stability stable * @category providing services * @since 2.0.0 */ ( self: Stream, key: Context.Key, f: (service: NoInfer) => S ): Stream } = dual(3, ( self: Stream, service: Context.Key, f: (service: NoInfer) => S ): Stream => updateContext(self, (context) => Context.add( context, service, f(Context.get(context, service)) ))) /** * Wraps the stream with a new span for tracing. * * **Example** (Wrapping a stream in a span) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.withSpan("numbers")) * * await Effect.runPromise( * Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 3 ] * }) * ) * ``` * * @stability stable * @category tracing * @since 2.0.0 */ export const withSpan: { /** * Wraps the stream with a new span for tracing. * * **Example** (Wrapping a stream in a span) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.withSpan("numbers")) * * await Effect.runPromise( * Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 3 ] * }) * ) * ``` * * @stability stable * @category tracing * @since 2.0.0 */ (name: string, options?: SpanOptions): (self: Stream) => Stream> /** * Wraps the stream with a new span for tracing. * * **Example** (Wrapping a stream in a span) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.withSpan("numbers")) * * await Effect.runPromise( * Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) * values // => [ 1, 2, 3 ] * }) * ) * ``` * * @stability stable * @category tracing * @since 2.0.0 */ (self: Stream, name: string, options?: SpanOptions): Stream> } = function() { const dataFirst = isStream(arguments[0]) const name = dataFirst ? arguments[1] : arguments[0] const options = addSpanStackTrace(dataFirst ? arguments[2] : arguments[1]) if (dataFirst) { const self = arguments[0] as Stream return fromChannel(Channel.withSpan(self.channel, name, options)) } return (self: Stream) => fromChannel(Channel.withSpan(self.channel, name, options)) } as any /** * Provides the entry point for do-notation style stream composition. * * **Example** (Starting stream do notation) * * ```ts import.meta.vitest * import { Effect, pipe, Stream } from "effect" * * const program = pipe( * Stream.Do, * Stream.bind("value", () => Stream.fromArray([1, 2])), * Stream.let("next", ({ value }) => value + 1) * ) * * const effect = Effect.gen(function*() { * const collected = yield* Stream.runCollect(program) * collected // => [ { value: 1, next: 2 }, { value: 2, next: 3 } ] * }) * * await Effect.runPromise(effect) * ``` * * @stability stable * @category do notation * @since 2.0.0 */ export const Do: Stream<{}> = succeed({}) const let_: { , B>( name: N, f: (a: NoInfer) => B ): (self: Stream) => Stream & Record>, E, R> , E, R, B, N extends string>( self: Stream, name: N, f: (a: NoInfer) => B ): Stream & Record>, E, R> } = dual(3, , E, R, B, N extends string>( self: Stream, name: N, f: (a: NoInfer) => B ): Stream & Record>, E, R> => map(self, (a) => ({ ...a, [name]: f(a) } as any))) export { /** * Adds a computed field to the current Do-notation record. * * **Example** (Adding a computed field) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.Do.pipe( * Stream.let("x", () => 2), * Stream.let("y", ({ x }) => x * 3) * ) * * const program = Effect.gen(function*() { * const records = yield* Stream.runCollect(stream) * records // => [ { x: 2, y: 6 } ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category do notation * @since 2.0.0 */ let_ as let } /** * Binds the result of a stream to a field in the do-notation record. * * **Example** (Binding a stream value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Stream.Do.pipe( * Stream.bind("a", () => Stream.make(1, 2)), * Stream.bind("b", ({ a }) => Stream.succeed(a + 1)) * ) * * const result = Stream.runCollect(program) * * await Effect.runPromise(result) // => [{ a: 1, b: 2 }, { a: 2, b: 3 }] * ``` * * @stability stable * @category do notation * @since 2.0.0 */ export const bind: { /** * Binds the result of a stream to a field in the do-notation record. * * **Example** (Binding a stream value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Stream.Do.pipe( * Stream.bind("a", () => Stream.make(1, 2)), * Stream.bind("b", ({ a }) => Stream.succeed(a + 1)) * ) * * const result = Stream.runCollect(program) * * await Effect.runPromise(result) // => [{ a: 1, b: 2 }, { a: 2, b: 3 }] * ``` * * @stability stable * @category do notation * @since 2.0.0 */ , B, E2, R2>( name: N, f: (a: NoInfer) => Stream, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined } | undefined ): (self: Stream) => Stream & Record>, E2 | E, R2 | R> /** * Binds the result of a stream to a field in the do-notation record. * * **Example** (Binding a stream value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Stream.Do.pipe( * Stream.bind("a", () => Stream.make(1, 2)), * Stream.bind("b", ({ a }) => Stream.succeed(a + 1)) * ) * * const result = Stream.runCollect(program) * * await Effect.runPromise(result) // => [{ a: 1, b: 2 }, { a: 2, b: 3 }] * ``` * * @stability stable * @category do notation * @since 2.0.0 */ , E, R, B, E2, R2, N extends string>( self: Stream, name: N, f: (a: NoInfer) => Stream, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined } | undefined ): Stream & Record>, E | E2, R | R2> } = dual((args) => isStream(args[0]), , E, R, B, E2, R2, N extends string>( self: Stream, name: N, f: (a: NoInfer) => Stream, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined } | undefined ): Stream & Record>, E | E2, R | R2> => flatMap(self, (a) => map(f(a), (b) => ({ ...a, [name]: b } as any)), options)) /** * Binds an Effect-produced value into the do-notation record for each stream element. * * **Example** (Binding an effect value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.Do.pipe( * Stream.bind("value", () => Stream.make(1, 2)), * Stream.bindEffect("double", ({ value }) => Effect.succeed(value * 2)) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ { value: 1, double: 2 }, { value: 2, double: 4 } ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category do notation * @since 2.0.0 */ export const bindEffect: { /** * Binds an Effect-produced value into the do-notation record for each stream element. * * **Example** (Binding an effect value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.Do.pipe( * Stream.bind("value", () => Stream.make(1, 2)), * Stream.bindEffect("double", ({ value }) => Effect.succeed(value * 2)) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ { value: 1, double: 2 }, { value: 2, double: 4 } ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category do notation * @since 2.0.0 */ , B, E2, R2>( name: N, f: (a: NoInfer) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined readonly unordered?: boolean | undefined } | undefined ): (self: Stream) => Stream & Record>, E | E2, R | R2> /** * Binds an Effect-produced value into the do-notation record for each stream element. * * **Example** (Binding an effect value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.Do.pipe( * Stream.bind("value", () => Stream.make(1, 2)), * Stream.bindEffect("double", ({ value }) => Effect.succeed(value * 2)) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) * result // => [ { value: 1, double: 2 }, { value: 2, double: 4 } ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category do notation * @since 2.0.0 */ , E, R, B, E2, R2, N extends string>( self: Stream, name: N, f: (a: NoInfer) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined readonly unordered?: boolean | undefined } | undefined ): Stream & Record>, E | E2, R | R2> } = dual((args) => isStream(args[0]), , E, R, B, E2, R2, N extends string>( self: Stream, name: N, f: (a: NoInfer) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined readonly bufferSize?: number | undefined readonly unordered?: boolean | undefined } | undefined ): Stream & Record>, E | E2, R | R2> => mapEffect(self, (a) => Effect.map(f(a), (b) => ({ ...a, [name]: b } as any)), options)) /** * Maps each element into a record keyed by the provided name. * * **Example** (Binding values to a record key) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3).pipe(Stream.bindTo("value")) * * await Effect.runPromise(Stream.runCollect(stream)) // => [{ value: 1 }, { value: 2 }, { value: 3 }] * ``` * * @stability stable * @category do notation * @since 2.0.0 */ export const bindTo: { /** * Maps each element into a record keyed by the provided name. * * **Example** (Binding values to a record key) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3).pipe(Stream.bindTo("value")) * * await Effect.runPromise(Stream.runCollect(stream)) // => [{ value: 1 }, { value: 2 }, { value: 3 }] * ``` * * @stability stable * @category do notation * @since 2.0.0 */ (name: N): (self: Stream) => Stream<{ [K in N]: A }, E, R> /** * Maps each element into a record keyed by the provided name. * * **Example** (Binding values to a record key) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3).pipe(Stream.bindTo("value")) * * await Effect.runPromise(Stream.runCollect(stream)) // => [{ value: 1 }, { value: 2 }, { value: 3 }] * ``` * * @stability stable * @category do notation * @since 2.0.0 */ (self: Stream, name: N): Stream<{ [K in N]: A }, E, R> } = dual(2, ( self: Stream, name: N ): Stream<{ [K in N]: A }, E, R> => map(self, (a) => ({ [name]: a } as any))) /** * Runs a stream with a sink and returns the sink result. * * **Example** (Running a stream with a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * const program = Stream.run(Stream.make(1, 2, 3), Sink.sum) * * await Effect.runPromise(program) // => 6 * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const run: { /** * Runs a stream with a sink and returns the sink result. * * **Example** (Running a stream with a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * const program = Stream.run(Stream.make(1, 2, 3), Sink.sum) * * await Effect.runPromise(program) // => 6 * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (sink: Sink.Sink): (self: Stream) => Effect.Effect /** * Runs a stream with a sink and returns the sink result. * * **Example** (Running a stream with a sink) * * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * const program = Stream.run(Stream.make(1, 2, 3), Sink.sum) * * await Effect.runPromise(program) // => 6 * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (self: Stream, sink: Sink.Sink): Effect.Effect } = dual(2, ( self: Stream, sink: Sink.Sink ): Effect.Effect => Effect.scopedWith((scope) => Channel.toPullScoped(self.channel, scope).pipe( Effect.flatMap((upstream) => sink.transform(upstream as any, scope)), Effect.map(([a]) => a) ) )) /** * Runs the stream and collects all elements into an array. * * **Example** (Collecting stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * * const program = Effect.gen(function*() { * const collected = yield* Stream.runCollect(stream) * collected // => [ 1, 2, 3, 4, 5 ] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const runCollect = (self: Stream): Effect.Effect, E, R> => Channel.runFold( self.channel, () => [] as Array, (acc, chunk) => { for (let i = 0; i < chunk.length; i++) { acc.push(chunk[i]) } return acc } ) /** * Runs the stream and returns the number of elements emitted. * * **Example** (Counting stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * * const program = Effect.gen(function* () { * const count = yield* Stream.runCount(stream) * count // => 5 * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const runCount = (self: Stream): Effect.Effect => Channel.runFold(self.channel, () => 0, (acc, chunk) => acc + chunk.length) /** * Runs the stream and returns the numeric sum of its elements. * * **Example** (Summing stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const total = yield* Stream.runSum(Stream.make(1, 2, 3)) * total // => 6 * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const runSum = (self: Stream): Effect.Effect => Channel.runFold(self.channel, () => 0, (acc, chunk) => { for (let i = 0; i < chunk.length; i++) { acc += chunk[i] } return acc }) /** * Runs the stream and folds elements using a pure reducer. * * **Example** (Folding stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const total = yield* Stream.runFold( * Stream.make(1, 2, 3), * () => 0, * (acc, n) => acc + n * ) * total // => 6 * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const runFold: { /** * Runs the stream and folds elements using a pure reducer. * * **Example** (Folding stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const total = yield* Stream.runFold( * Stream.make(1, 2, 3), * () => 0, * (acc, n) => acc + n * ) * total // => 6 * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (initial: LazyArg, f: (acc: Z, a: A) => Z): ( self: Stream ) => Effect.Effect /** * Runs the stream and folds elements using a pure reducer. * * **Example** (Folding stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const total = yield* Stream.runFold( * Stream.make(1, 2, 3), * () => 0, * (acc, n) => acc + n * ) * total // => 6 * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (self: Stream, initial: LazyArg, f: (acc: Z, a: A) => Z): Effect.Effect } = dual(3, ( self: Stream, initial: LazyArg, f: (acc: Z, a: A) => Z ): Effect.Effect => Channel.runFold(self.channel, initial, (acc, arr) => { for (let i = 0; i < arr.length; i++) { acc = f(acc, arr[i]) } return acc })) /** * Runs the stream and folds elements using an effectful reducer. * * **When to use** * * Use when reducing stream elements needs Effects, services, or failures in the * reducer. * * **Example** (Effectfully folding stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const total = yield* Stream.runFoldEffect( * Stream.make(1, 2, 3), * () => 0, * (acc, n) => Effect.succeed(acc + n) * ) * total // => 6 * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const runFoldEffect: { /** * Runs the stream and folds elements using an effectful reducer. * * **When to use** * * Use when reducing stream elements needs Effects, services, or failures in the * reducer. * * **Example** (Effectfully folding stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const total = yield* Stream.runFoldEffect( * Stream.make(1, 2, 3), * () => 0, * (acc, n) => Effect.succeed(acc + n) * ) * total // => 6 * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (initial: LazyArg, f: (acc: Z, a: A) => Effect.Effect): ( self: Stream ) => Effect.Effect /** * Runs the stream and folds elements using an effectful reducer. * * **When to use** * * Use when reducing stream elements needs Effects, services, or failures in the * reducer. * * **Example** (Effectfully folding stream values) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const total = yield* Stream.runFoldEffect( * Stream.make(1, 2, 3), * () => 0, * (acc, n) => Effect.succeed(acc + n) * ) * total // => 6 * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ ( self: Stream, initial: LazyArg, f: (acc: Z, a: A) => Effect.Effect ): Effect.Effect } = dual(3, ( self: Stream, initial: LazyArg, f: (acc: Z, a: A) => Effect.Effect ): Effect.Effect => Channel.runFoldEffect(self.channel, initial, (acc, arr) => { let i = 0 let s = acc return Effect.map( Effect.whileLoop({ while: () => i < arr.length, body: () => f(s, arr[i]), step(z) { s = z i++ } }), () => s ) })) /** * Runs the stream and returns the first element as an `Option`. * * **Example** (Getting the first stream value) * * ```ts import.meta.vitest * import { Effect, Option, Stream } from "effect" * * const program = Effect.gen(function*() { * const head = yield* Stream.runHead(Stream.make(1, 2, 3)) * Option.getOrThrow(head) // => 1 * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const runHead = (self: Stream): Effect.Effect, E, R> => Effect.map(Channel.runHead(self.channel), Option.map(Arr.getUnsafe(0))) /** * Runs the stream and returns the last element as an `Option`. * * **When to use** * * Use to consume a finite stream when only the final emitted element matters. * * **Details** * * `Option.some` contains the last emitted element. `Option.none` means the * stream completed without emitting. * * **Gotchas** * * The returned effect waits for the stream to complete before it can produce a * value. * * @see {@link runHead} for consuming only the first emitted element * @see {@link runCollect} for collecting every emitted element * @see {@link runDrain} for consuming the stream while discarding emitted elements * * @stability stable * @category destructors * @since 2.0.0 */ export const runLast = (self: Stream): Effect.Effect, E, R> => Effect.map(Channel.runLast(self.channel), Option.map(Arr.lastNonEmpty)) /** * Runs the provided effectful callback for each element of the stream. * * **Example** (Running an effect for each value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * const values: Array = [] * * const program = Effect.gen(function*() { * yield* Stream.runForEach(stream, (n) => Effect.sync(() => values.push(`Processing: ${n}`))) * }) * * await Effect.runPromise(program) * values // => ["Processing: 1", "Processing: 2", "Processing: 3"] * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const runForEach: { /** * Runs the provided effectful callback for each element of the stream. * * **Example** (Running an effect for each value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * const values: Array = [] * * const program = Effect.gen(function*() { * yield* Stream.runForEach(stream, (n) => Effect.sync(() => values.push(`Processing: ${n}`))) * }) * * await Effect.runPromise(program) * values // => ["Processing: 1", "Processing: 2", "Processing: 3"] * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (f: (a: A) => Effect.Effect): (self: Stream) => Effect.Effect /** * Runs the provided effectful callback for each element of the stream. * * **Example** (Running an effect for each value) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * const values: Array = [] * * const program = Effect.gen(function*() { * yield* Stream.runForEach(stream, (n) => Effect.sync(() => values.push(`Processing: ${n}`))) * }) * * await Effect.runPromise(program) * values // => ["Processing: 1", "Processing: 2", "Processing: 3"] * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (self: Stream, f: (a: A) => Effect.Effect): Effect.Effect } = dual(2, ( self: Stream, f: (a: A) => Effect.Effect ): Effect.Effect => Channel.runForEach(self.channel, (arr) => { let i = 0 return Effect.whileLoop({ while: () => i < arr.length, body: () => f(arr[i++]), step: constVoid }) })) /** * Runs the stream, applying the effectful predicate to each element and * stopping when it returns `false`. * * **Example** (Running effects while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const values: Array = [] * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3, 4, 5) * * yield* Stream.runForEachWhile(stream, (n) => * Effect.gen(function*() { * yield* Effect.sync(() => values.push(n)) * return n < 3 * }) * ) * }) * * await Effect.runPromise(program) * values // => [1, 2, 3] * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const runForEachWhile: { /** * Runs the stream, applying the effectful predicate to each element and * stopping when it returns `false`. * * **Example** (Running effects while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const values: Array = [] * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3, 4, 5) * * yield* Stream.runForEachWhile(stream, (n) => * Effect.gen(function*() { * yield* Effect.sync(() => values.push(n)) * return n < 3 * }) * ) * }) * * await Effect.runPromise(program) * values // => [1, 2, 3] * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (f: (a: A) => Effect.Effect): (self: Stream) => Effect.Effect /** * Runs the stream, applying the effectful predicate to each element and * stopping when it returns `false`. * * **Example** (Running effects while a predicate holds) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const values: Array = [] * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3, 4, 5) * * yield* Stream.runForEachWhile(stream, (n) => * Effect.gen(function*() { * yield* Effect.sync(() => values.push(n)) * return n < 3 * }) * ) * }) * * await Effect.runPromise(program) * values // => [1, 2, 3] * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (self: Stream, f: (a: A) => Effect.Effect): Effect.Effect } = dual(2, ( self: Stream, f: (a: A) => Effect.Effect ): Effect.Effect => Channel.runForEachWhile(self.channel, (arr) => { let done = false let i = 0 return Effect.map( Effect.whileLoop({ while: () => !done && i < arr.length, body: () => f(arr[i]), step(b) { i++ if (!b) done = true } }), () => !done ) })) /** * Consumes the stream in chunks, passing each non-empty array to the callback. * * **Example** (Consuming stream chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * const chunks: Array = [] * const program = Effect.gen(function*() { * yield* Stream.runForEachArray( * stream, * (chunk) => Effect.sync(() => chunks.push(chunk.join(", "))) * ) * }) * * await Effect.runPromise(program) * chunks // => ["1, 2, 3, 4, 5"] * ``` * * @stability stable * @category destructors * @since 4.0.0 */ export const runForEachArray: { /** * Consumes the stream in chunks, passing each non-empty array to the callback. * * **Example** (Consuming stream chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * const chunks: Array = [] * const program = Effect.gen(function*() { * yield* Stream.runForEachArray( * stream, * (chunk) => Effect.sync(() => chunks.push(chunk.join(", "))) * ) * }) * * await Effect.runPromise(program) * chunks // => ["1, 2, 3, 4, 5"] * ``` * * @stability stable * @category destructors * @since 4.0.0 */ (f: (a: Arr.NonEmptyReadonlyArray) => Effect.Effect): (self: Stream) => Effect.Effect /** * Consumes the stream in chunks, passing each non-empty array to the callback. * * **Example** (Consuming stream chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * const chunks: Array = [] * const program = Effect.gen(function*() { * yield* Stream.runForEachArray( * stream, * (chunk) => Effect.sync(() => chunks.push(chunk.join(", "))) * ) * }) * * await Effect.runPromise(program) * chunks // => ["1, 2, 3, 4, 5"] * ``` * * @stability stable * @category destructors * @since 4.0.0 */ ( self: Stream, f: (a: Arr.NonEmptyReadonlyArray) => Effect.Effect ): Effect.Effect } = dual(2, ( self: Stream, f: (a: Arr.NonEmptyReadonlyArray) => Effect.Effect ): Effect.Effect => Channel.runForEach(self.channel, f)) /** * Runs the stream for its effects, discarding emitted elements. * * **Example** (Draining a stream run) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const values: Array = [] * const program = Effect.gen(function*() { * const stream = Stream.make(1, 2, 3).pipe( * Stream.mapEffect((n) => Effect.sync(() => values.push(n))) * ) * * yield* Stream.runDrain(stream) * }) * * await Effect.runPromise(program) * values // => [1, 2, 3] * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const runDrain = (self: Stream): Effect.Effect => Channel.runDrain(self.channel) /** * Returns a scoped pull for manually consuming the stream's output chunks. * * **Details** * * The pull fails with `Cause.Done` when the stream ends and with the stream * error on failure. * * **Example** (Creating a scoped pull) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * * const program = Effect.scoped( * Effect.gen(function*() { * const pull = yield* Stream.toPull(stream) * const chunk = yield* pull * chunk // => [ 1, 2, 3 ] * }) * ) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const toPull = ( self: Stream ): Effect.Effect, E>, never, R | Scope.Scope> => Channel.toPull(self.channel) /** * Concatenates all emitted strings into a single string. * * **Example** (Joining strings from a stream) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make("Hello", " ", "World", "!") * const program = Effect.gen(function*() { * const text = yield* Stream.mkString(stream) * text // => "Hello World!" * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const mkString = (self: Stream): Effect.Effect => Channel.runFold( self.channel, () => "", (acc, chunk) => acc + chunk.join("") ) /** * Concatenates the stream's `Uint8Array` chunks into a single `ArrayBuffer`. * * **Gotchas** * * This materializes the full content in memory. The source stream must not * reuse or mutate emitted buffers, which are retained until collection completes. * * **Example** (Joining byte chunks into an ArrayBuffer) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Stream.make( * new Uint8Array([1, 2]), * new Uint8Array([3, 4]) * ).pipe( * Stream.mkArrayBuffer, * Effect.map((buffer) => [...new Uint8Array(buffer)]) * ) * * await Effect.runPromise(program) // => [1, 2, 3, 4] * ``` * * @stability stable * @category destructors * @since 4.0.0 */ export const mkArrayBuffer = (self: Stream): Effect.Effect => Effect.map(Channel.mkUint8Array(self.channel), (bytes) => bytes.buffer) /** * Concatenates the stream's `Uint8Array` chunks into a single `Uint8Array`. * * **Gotchas** * * This materializes the full content in memory. The source stream must not * reuse or mutate emitted buffers, which are retained until collection completes. * * **Example** (Joining Uint8Array chunks) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(new Uint8Array([1, 2]), new Uint8Array([3, 4])) * const program = Effect.gen(function*() { * const bytes = yield* Stream.mkUint8Array(stream) * const values = Array.from(bytes) // => [1, 2, 3, 4] * }) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 4.0.0 */ export const mkUint8Array = (self: Stream): Effect.Effect, E, R> => Channel.mkUint8Array(self.channel) /** * Converts the stream to a `ReadableStream` using the provided services. * * **When to use** * * Use when bridging to Web Streams and you already have the `Context` required * to run the stream outside an `Effect`. * * **Details** * * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. * * When `signal` aborts, the stream is interrupted even if the reader has * stopped pulling, and the `ReadableStream` closes. * * **Example** (Converting to a ReadableStream with services) * * ```ts import.meta.vitest * import { Context, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * const readableStream = Stream.toReadableStreamWith(stream, Context.empty()) * const values = await globalThis.Array.fromAsync(readableStream) * values // => [ 1, 2, 3, 4, 5 ] * ``` * * @stability stable * @category destructors * @since 4.0.0 */ export const toReadableStreamWith: { /** * Converts the stream to a `ReadableStream` using the provided services. * * **When to use** * * Use when bridging to Web Streams and you already have the `Context` required * to run the stream outside an `Effect`. * * **Details** * * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. * * When `signal` aborts, the stream is interrupted even if the reader has * stopped pulling, and the `ReadableStream` closes. * * **Example** (Converting to a ReadableStream with services) * * ```ts import.meta.vitest * import { Context, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * const readableStream = Stream.toReadableStreamWith(stream, Context.empty()) * const values = await globalThis.Array.fromAsync(readableStream) * values // => [ 1, 2, 3, 4, 5 ] * ``` * * @stability stable * @category destructors * @since 4.0.0 */ ( context: Context.Context, options?: { readonly strategy?: QueuingStrategy | undefined readonly signal?: AbortSignal | undefined } ): (self: Stream) => ReadableStream /** * Converts the stream to a `ReadableStream` using the provided services. * * **When to use** * * Use when bridging to Web Streams and you already have the `Context` required * to run the stream outside an `Effect`. * * **Details** * * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. * * When `signal` aborts, the stream is interrupted even if the reader has * stopped pulling, and the `ReadableStream` closes. * * **Example** (Converting to a ReadableStream with services) * * ```ts import.meta.vitest * import { Context, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * const readableStream = Stream.toReadableStreamWith(stream, Context.empty()) * const values = await globalThis.Array.fromAsync(readableStream) * values // => [ 1, 2, 3, 4, 5 ] * ``` * * @stability stable * @category destructors * @since 4.0.0 */ ( self: Stream, context: Context.Context, options?: { readonly strategy?: QueuingStrategy | undefined readonly signal?: AbortSignal | undefined } ): ReadableStream } = dual( (args) => isStream(args[0]), ( self: Stream, context: Context.Context, options?: { readonly strategy?: QueuingStrategy | undefined readonly signal?: AbortSignal | undefined } ): ReadableStream => { let currentResolve: (() => void) | undefined = undefined let fiber: Fiber.Fiber | undefined = undefined let cancelled = false const latch = Latch.makeUnsafe(false) const signal = options?.signal // Interrupting the fiber directly also stops a consumer waiting for a pull. const onAbort = () => fiber!.interruptUnsafe() return new ReadableStream({ start(controller) { fiber = Effect.runFork(Effect.provideContext( Channel.runForEach( Channel.transformPull(self.channel, (pull) => Effect.succeed(latch.whenOpen(pull))), (chunk) => Effect.sync(() => { latch.closeUnsafe() for (let i = 0; i < chunk.length; i++) { controller.enqueue(chunk[i]) } currentResolve!() currentResolve = undefined }) ), context )) fiber.addObserver((exit) => { signal?.removeEventListener("abort", onAbort) if (cancelled) return if (exit._tag === "Failure" && signal?.aborted !== true) { controller.error(Cause.squash(exit.cause)) } else { controller.close() } }) if (signal !== undefined && fiber.pollUnsafe() === undefined) { if (signal.aborted) onAbort() else signal.addEventListener("abort", onAbort, { once: true }) } }, pull() { return new Promise((resolve) => { currentResolve = resolve latch.openUnsafe() }) }, cancel() { if (!fiber) return cancelled = true return Effect.runPromise(Effect.asVoid(Fiber.interrupt(fiber))) } }, options?.strategy) } ) /** * Converts a stream to a `ReadableStream`. * * **Details** * * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. * * **Example** (Converting a stream to a ReadableStream) * * ```ts import.meta.vitest * import { Stream } from "effect" * * const readableStream = Stream.toReadableStream(Stream.make(1, 2, 3)) * const values = await globalThis.Array.fromAsync(readableStream) * values // => [ 1, 2, 3 ] * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const toReadableStream: { /** * Converts a stream to a `ReadableStream`. * * **Details** * * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. * * **Example** (Converting a stream to a ReadableStream) * * ```ts import.meta.vitest * import { Stream } from "effect" * * const readableStream = Stream.toReadableStream(Stream.make(1, 2, 3)) * const values = await globalThis.Array.fromAsync(readableStream) * values // => [ 1, 2, 3 ] * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (options?: { readonly strategy?: QueuingStrategy | undefined }): ( self: Stream ) => ReadableStream /** * Converts a stream to a `ReadableStream`. * * **Details** * * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. * * **Example** (Converting a stream to a ReadableStream) * * ```ts import.meta.vitest * import { Stream } from "effect" * * const readableStream = Stream.toReadableStream(Stream.make(1, 2, 3)) * const values = await globalThis.Array.fromAsync(readableStream) * values // => [ 1, 2, 3 ] * ``` * * @stability stable * @category destructors * @since 2.0.0 */ ( self: Stream, options?: { readonly strategy?: QueuingStrategy | undefined } ): ReadableStream } = dual( (args) => isStream(args[0]), ( self: Stream, options?: { readonly strategy?: QueuingStrategy | undefined } ): ReadableStream => toReadableStreamWith(self, Context.empty(), options) ) /** * Creates an Effect that builds a ReadableStream from the stream. * * **When to use** * * Use when bridging to Web Streams from inside an `Effect` so the required * services can be captured from the current context. * * **Details** * * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. * * **Example** (Creating a ReadableStream effect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * * const effect = Effect.gen(function*() { * const readableStream = yield* Stream.toReadableStreamEffect(stream) * readableStream instanceof ReadableStream // => true * }) * * await Effect.runPromise(effect) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const toReadableStreamEffect: { /** * Creates an Effect that builds a ReadableStream from the stream. * * **When to use** * * Use when bridging to Web Streams from inside an `Effect` so the required * services can be captured from the current context. * * **Details** * * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. * * **Example** (Creating a ReadableStream effect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * * const effect = Effect.gen(function*() { * const readableStream = yield* Stream.toReadableStreamEffect(stream) * readableStream instanceof ReadableStream // => true * }) * * await Effect.runPromise(effect) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (options?: { readonly strategy?: QueuingStrategy | undefined }): ( self: Stream ) => Effect.Effect, never, R> /** * Creates an Effect that builds a ReadableStream from the stream. * * **When to use** * * Use when bridging to Web Streams from inside an `Effect` so the required * services can be captured from the current context. * * **Details** * * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. * * **Example** (Creating a ReadableStream effect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3, 4, 5) * * const effect = Effect.gen(function*() { * const readableStream = yield* Stream.toReadableStreamEffect(stream) * readableStream instanceof ReadableStream // => true * }) * * await Effect.runPromise(effect) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ ( self: Stream, options?: { readonly strategy?: QueuingStrategy | undefined } ): Effect.Effect, never, R> } = dual( (args) => isStream(args[0]), ( self: Stream, options?: { readonly strategy?: QueuingStrategy | undefined } ): Effect.Effect, never, R> => Effect.map( Effect.context(), (context) => toReadableStreamWith(self, context, options) ) ) /** * Converts the stream to an `AsyncIterable` using the provided services. * * **When to use** * * Use when converting outside an Effect and you already have the `Context` * needed to run the stream. * * **Example** (Converting to an AsyncIterable with services) * * ```ts import.meta.vitest * import { Context, Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * const iterable = Stream.toAsyncIterableWith(stream, Context.empty()) * * await globalThis.Array.fromAsync(iterable) // => [1, 2, 3] * ``` * * @stability stable * @category destructors * @since 4.0.0 */ export const toAsyncIterableWith: { /** * Converts the stream to an `AsyncIterable` using the provided services. * * **When to use** * * Use when converting outside an Effect and you already have the `Context` * needed to run the stream. * * **Example** (Converting to an AsyncIterable with services) * * ```ts import.meta.vitest * import { Context, Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * const iterable = Stream.toAsyncIterableWith(stream, Context.empty()) * * await globalThis.Array.fromAsync(iterable) // => [1, 2, 3] * ``` * * @stability stable * @category destructors * @since 4.0.0 */ (context: Context.Context): (self: Stream) => AsyncIterable /** * Converts the stream to an `AsyncIterable` using the provided services. * * **When to use** * * Use when converting outside an Effect and you already have the `Context` * needed to run the stream. * * **Example** (Converting to an AsyncIterable with services) * * ```ts import.meta.vitest * import { Context, Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * const iterable = Stream.toAsyncIterableWith(stream, Context.empty()) * * await globalThis.Array.fromAsync(iterable) // => [1, 2, 3] * ``` * * @stability stable * @category destructors * @since 4.0.0 */ (self: Stream, context: Context.Context): AsyncIterable } = dual( 2, ( self: Stream, context: Context.Context ): AsyncIterable => ({ [Symbol.asyncIterator]() { const runPromise = Effect.runPromiseWith(context) const runFork = Effect.runForkWith(context) const scope = Scope.makeUnsafe() let pull: Pull.Pull, E, void, R> | undefined let currentIter: Iterator | undefined let currentFiber: Fiber.Fiber, E | Cause.Done> | undefined let closePromise: Promise> | undefined const close = (exit: Exit.Exit): Promise> => { if (closePromise) return closePromise const fiber = currentFiber closePromise = runPromise(Effect.as( Effect.andThen( fiber ? Fiber.interrupt(fiber) : Effect.void, Scope.close(scope, exit) ), { done: true, value: undefined } )) return closePromise } const closeAndReportError = async (exit: Exit.Exit): Promise => { try { await close(exit) } catch (error) { await runPromise(Effect.logError("Suppressed error while closing Stream async iterator", error)) } } return { async next(): Promise> { if (closePromise) return closePromise if (currentIter) { const next = currentIter.next() if (!next.done) return next currentIter = undefined } const fiber = runFork( pull ?? Effect.flatMap(Channel.toPullScoped(self.channel, scope), (nextPull) => { pull = nextPull return nextPull }) ) currentFiber = fiber const exit = await runPromise(Fiber.await(fiber)) if (currentFiber === fiber) { currentFiber = undefined } if (Exit.isSuccess(exit)) { currentIter = exit.value[Symbol.iterator]() return currentIter.next() } else if (Pull.isDoneCause(exit.cause)) { return close(Exit.void) } if (closePromise && Cause.hasInterruptsOnly(exit.cause)) { return closePromise } await closeAndReportError(exit) throw Cause.squash(exit.cause) }, return() { return close(Exit.void) }, async throw(error) { await closeAndReportError(Exit.die(error)) throw error } } } }) ) /** * Creates an effect that yields an `AsyncIterable` using the current services. * * **When to use** * * Use when the `AsyncIterable` should be created inside Effect with the current * context supplying the stream's services. * * **Example** (Creating an AsyncIterable effect) * * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * * const program = Effect.gen(function*() { * const iterable = yield* Stream.toAsyncIterableEffect(stream) * return yield* Effect.promise(() => globalThis.Array.fromAsync(iterable)) * }) * * await Effect.runPromise(program) // => [1, 2, 3] * ``` * * @stability stable * @category destructors * @since 3.15.0 */ export const toAsyncIterableEffect = (self: Stream): Effect.Effect, never, R> => Effect.map( Effect.context(), (context) => toAsyncIterableWith(self, context) ) /** * Converts a stream to an `AsyncIterable` for `for await...of` consumption. * * **Example** (Converting to an async iterable) * * ```ts import.meta.vitest * import { Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * * await globalThis.Array.fromAsync(Stream.toAsyncIterable(stream)) // => [1, 2, 3] * ``` * * @stability stable * @category destructors * @since 3.15.0 */ export const toAsyncIterable = (self: Stream): AsyncIterable => toAsyncIterableWith(self, Context.empty()) /** * Runs the stream, publishing elements into the provided PubSub. * * **Details** * * `shutdownOnEnd` controls whether the PubSub is shut down when the stream ends. * It only shuts down when set to `true`. * * **Example** (Running a stream into a PubSub) * * ```ts import.meta.vitest * import { Effect, PubSub, Stream } from "effect" * * const program = Effect.scoped(Effect.gen(function* () { * const pubsub = yield* PubSub.unbounded() * const subscription = yield* PubSub.subscribe(pubsub) * * yield* Stream.runIntoPubSub(Stream.fromIterable([1, 2]), pubsub) * * const first = yield* PubSub.take(subscription) * const second = yield* PubSub.take(subscription) * * first // => 1 * second // => 2 * })) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const runIntoPubSub: { /** * Runs the stream, publishing elements into the provided PubSub. * * **Details** * * `shutdownOnEnd` controls whether the PubSub is shut down when the stream ends. * It only shuts down when set to `true`. * * **Example** (Running a stream into a PubSub) * * ```ts import.meta.vitest * import { Effect, PubSub, Stream } from "effect" * * const program = Effect.scoped(Effect.gen(function* () { * const pubsub = yield* PubSub.unbounded() * const subscription = yield* PubSub.subscribe(pubsub) * * yield* Stream.runIntoPubSub(Stream.fromIterable([1, 2]), pubsub) * * const first = yield* PubSub.take(subscription) * const second = yield* PubSub.take(subscription) * * first // => 1 * second // => 2 * })) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ ( pubsub: PubSub.PubSub, options?: { readonly shutdownOnEnd?: boolean | undefined } | undefined ): (self: Stream) => Effect.Effect /** * Runs the stream, publishing elements into the provided PubSub. * * **Details** * * `shutdownOnEnd` controls whether the PubSub is shut down when the stream ends. * It only shuts down when set to `true`. * * **Example** (Running a stream into a PubSub) * * ```ts import.meta.vitest * import { Effect, PubSub, Stream } from "effect" * * const program = Effect.scoped(Effect.gen(function* () { * const pubsub = yield* PubSub.unbounded() * const subscription = yield* PubSub.subscribe(pubsub) * * yield* Stream.runIntoPubSub(Stream.fromIterable([1, 2]), pubsub) * * const first = yield* PubSub.take(subscription) * const second = yield* PubSub.take(subscription) * * first // => 1 * second // => 2 * })) * * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ ( self: Stream, pubsub: PubSub.PubSub, options?: { readonly shutdownOnEnd?: boolean | undefined } | undefined ): Effect.Effect } = dual((args) => isStream(args[0]), ( self: Stream, pubsub: PubSub.PubSub, options?: { readonly shutdownOnEnd?: boolean | undefined } | undefined ): Effect.Effect => Channel.runIntoPubSubArray(self.channel, pubsub, options)) /** * Converts a stream to a PubSub of emitted values for concurrent consumption. * * **Details** * * `shutdownOnEnd` indicates whether the PubSub should be shut down when the * stream ends. By default this is `true`. * * **Example** (Converting a stream to a PubSub for concurrent consumption) * * ```ts import.meta.vitest * import { Effect, PubSub, Stream } from "effect" * * const program = Effect.scoped(Effect.gen(function* () { * const pubsub = yield* Stream.fromArray([1, 2]).pipe( * Stream.toPubSub({ capacity: 8 }) * ) * const subscription = yield* PubSub.subscribe(pubsub) * const first = yield* PubSub.take(subscription) * * first // => 1 * })) * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const toPubSub: { /** * Converts a stream to a PubSub of emitted values for concurrent consumption. * * **Details** * * `shutdownOnEnd` indicates whether the PubSub should be shut down when the * stream ends. By default this is `true`. * * **Example** (Converting a stream to a PubSub for concurrent consumption) * * ```ts import.meta.vitest * import { Effect, PubSub, Stream } from "effect" * * const program = Effect.scoped(Effect.gen(function* () { * const pubsub = yield* Stream.fromArray([1, 2]).pipe( * Stream.toPubSub({ capacity: 8 }) * ) * const subscription = yield* PubSub.subscribe(pubsub) * const first = yield* PubSub.take(subscription) * * first // => 1 * })) * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ ( options: { readonly capacity: "unbounded" readonly replay?: number | undefined readonly shutdownOnEnd?: boolean | undefined } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined readonly replay?: number | undefined readonly shutdownOnEnd?: boolean | undefined } ): (self: Stream) => Effect.Effect, never, R | Scope.Scope> /** * Converts a stream to a PubSub of emitted values for concurrent consumption. * * **Details** * * `shutdownOnEnd` indicates whether the PubSub should be shut down when the * stream ends. By default this is `true`. * * **Example** (Converting a stream to a PubSub for concurrent consumption) * * ```ts import.meta.vitest * import { Effect, PubSub, Stream } from "effect" * * const program = Effect.scoped(Effect.gen(function* () { * const pubsub = yield* Stream.fromArray([1, 2]).pipe( * Stream.toPubSub({ capacity: 8 }) * ) * const subscription = yield* PubSub.subscribe(pubsub) * const first = yield* PubSub.take(subscription) * * first // => 1 * })) * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ ( self: Stream, options: { readonly capacity: "unbounded" readonly replay?: number | undefined readonly shutdownOnEnd?: boolean | undefined } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined readonly replay?: number | undefined readonly shutdownOnEnd?: boolean | undefined } ): Effect.Effect, never, R | Scope.Scope> } = dual( 2, ( self: Stream, options: { readonly capacity: "unbounded" readonly replay?: number | undefined readonly shutdownOnEnd?: boolean | undefined } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined readonly replay?: number | undefined readonly shutdownOnEnd?: boolean | undefined } ): Effect.Effect, never, R | Scope.Scope> => Channel.toPubSubArray(self.channel, options) ) /** * Converts a stream to a PubSub of `Take` values for concurrent consumption. * * **Details** * * Chunks are published as `Take` values. When the stream ends, the PubSub is * ended with the stream's `Exit`, so every subscriber, including one that * subscribes later, observes completion or failure after its buffered chunks. * * **Example** (Converting to a PubSub of takes) * * ```ts import.meta.vitest * import { Effect, PubSub, Stream } from "effect" * * const program = Effect.gen(function* () { * const pubsub = yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.toPubSubTake({ capacity: 8 }) * ) * const subscription = yield* PubSub.subscribe(pubsub) * const take = yield* PubSub.take(subscription) * * if (Array.isArray(take)) { * take // => [ 1, 2, 3 ] * } * }) * await Effect.runPromise(Effect.scoped(program)) * ``` * * @stability stable * @category destructors * @since 4.0.0 */ export const toPubSubTake: { /** * Converts a stream to a PubSub of `Take` values for concurrent consumption. * * **Details** * * Chunks are published as `Take` values. When the stream ends, the PubSub is * ended with the stream's `Exit`, so every subscriber, including one that * subscribes later, observes completion or failure after its buffered chunks. * * **Example** (Converting to a PubSub of takes) * * ```ts import.meta.vitest * import { Effect, PubSub, Stream } from "effect" * * const program = Effect.gen(function* () { * const pubsub = yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.toPubSubTake({ capacity: 8 }) * ) * const subscription = yield* PubSub.subscribe(pubsub) * const take = yield* PubSub.take(subscription) * * if (Array.isArray(take)) { * take // => [ 1, 2, 3 ] * } * }) * await Effect.runPromise(Effect.scoped(program)) * ``` * * @stability stable * @category destructors * @since 4.0.0 */ ( options: { readonly capacity: "unbounded" readonly replay?: number | undefined } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined readonly replay?: number | undefined } ): (self: Stream) => Effect.Effect>, never, R | Scope.Scope> /** * Converts a stream to a PubSub of `Take` values for concurrent consumption. * * **Details** * * Chunks are published as `Take` values. When the stream ends, the PubSub is * ended with the stream's `Exit`, so every subscriber, including one that * subscribes later, observes completion or failure after its buffered chunks. * * **Example** (Converting to a PubSub of takes) * * ```ts import.meta.vitest * import { Effect, PubSub, Stream } from "effect" * * const program = Effect.gen(function* () { * const pubsub = yield* Stream.fromArray([1, 2, 3]).pipe( * Stream.toPubSubTake({ capacity: 8 }) * ) * const subscription = yield* PubSub.subscribe(pubsub) * const take = yield* PubSub.take(subscription) * * if (Array.isArray(take)) { * take // => [ 1, 2, 3 ] * } * }) * await Effect.runPromise(Effect.scoped(program)) * ``` * * @stability stable * @category destructors * @since 4.0.0 */ ( self: Stream, options: { readonly capacity: "unbounded" readonly replay?: number | undefined } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined readonly replay?: number | undefined } ): Effect.Effect>, never, R | Scope.Scope> } = dual( 2, ( self: Stream, options: { readonly capacity: "unbounded" readonly replay?: number | undefined } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined readonly replay?: number | undefined } ): Effect.Effect>, never, R | Scope.Scope> => Channel.toPubSubTake(self.channel, options) ) /** * Creates a scoped dequeue that is fed by the stream for concurrent * consumption. * * **Details** * * Elements are offered to the queue as the stream runs. Stream completion is * signaled with `Cause.Done`, stream failures fail the queue, and the queue is * shut down when the surrounding scope closes. * * **Example** (Converting a stream to a Queue for concurrent consumption) * * ```ts import.meta.vitest * import { Effect, Queue, Stream } from "effect" * * const program = Effect.gen(function* () { * const queue = yield* Stream.toQueue(Stream.fromIterable([1, 2, 3]), { capacity: 8 }) * const chunk = yield* Queue.takeBetween(queue, 1, 3) * chunk // => [ 1, 2, 3 ] * }) * await Effect.runPromise(Effect.scoped(program)) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const toQueue: { /** * Creates a scoped dequeue that is fed by the stream for concurrent * consumption. * * **Details** * * Elements are offered to the queue as the stream runs. Stream completion is * signaled with `Cause.Done`, stream failures fail the queue, and the queue is * shut down when the surrounding scope closes. * * **Example** (Converting a stream to a Queue for concurrent consumption) * * ```ts import.meta.vitest * import { Effect, Queue, Stream } from "effect" * * const program = Effect.gen(function* () { * const queue = yield* Stream.toQueue(Stream.fromIterable([1, 2, 3]), { capacity: 8 }) * const chunk = yield* Queue.takeBetween(queue, 1, 3) * chunk // => [ 1, 2, 3 ] * }) * await Effect.runPromise(Effect.scoped(program)) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ ( options: { readonly capacity: "unbounded" } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined } ): (self: Stream) => Effect.Effect, never, R | Scope.Scope> /** * Creates a scoped dequeue that is fed by the stream for concurrent * consumption. * * **Details** * * Elements are offered to the queue as the stream runs. Stream completion is * signaled with `Cause.Done`, stream failures fail the queue, and the queue is * shut down when the surrounding scope closes. * * **Example** (Converting a stream to a Queue for concurrent consumption) * * ```ts import.meta.vitest * import { Effect, Queue, Stream } from "effect" * * const program = Effect.gen(function* () { * const queue = yield* Stream.toQueue(Stream.fromIterable([1, 2, 3]), { capacity: 8 }) * const chunk = yield* Queue.takeBetween(queue, 1, 3) * chunk // => [ 1, 2, 3 ] * }) * await Effect.runPromise(Effect.scoped(program)) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ ( self: Stream, options: { readonly capacity: "unbounded" } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined } ): Effect.Effect, never, R | Scope.Scope> } = dual( 2, ( self: Stream, options: { readonly capacity: "unbounded" } | { readonly capacity: number readonly strategy?: "dropping" | "sliding" | "suspend" | undefined } ): Effect.Effect, never, R | Scope.Scope> => Channel.toQueueArray(self.channel, options) ) /** * Runs the stream, offering each element to the provided queue and ending it * with `Cause.Done` when the stream completes. * * **Example** (Running a stream into a queue) * * ```ts import.meta.vitest * import { Cause, Effect, Queue, Stream } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(4) * * yield* Effect.forkChild( * Stream.runIntoQueue(Stream.fromIterable([1, 2, 3]), queue) * ) * * const values = [ * yield* Queue.take(queue), * yield* Queue.take(queue), * yield* Queue.take(queue) * ] * const done = yield* Effect.flip(Queue.take(queue)) * * values // => [ 1, 2, 3 ] * done._tag === "Done" // => true * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ export const runIntoQueue: { /** * Runs the stream, offering each element to the provided queue and ending it * with `Cause.Done` when the stream completes. * * **Example** (Running a stream into a queue) * * ```ts import.meta.vitest * import { Cause, Effect, Queue, Stream } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(4) * * yield* Effect.forkChild( * Stream.runIntoQueue(Stream.fromIterable([1, 2, 3]), queue) * ) * * const values = [ * yield* Queue.take(queue), * yield* Queue.take(queue), * yield* Queue.take(queue) * ] * const done = yield* Effect.flip(Queue.take(queue)) * * values // => [ 1, 2, 3 ] * done._tag === "Done" // => true * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (queue: Queue.Queue): (self: Stream) => Effect.Effect /** * Runs the stream, offering each element to the provided queue and ending it * with `Cause.Done` when the stream completes. * * **Example** (Running a stream into a queue) * * ```ts import.meta.vitest * import { Cause, Effect, Queue, Stream } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(4) * * yield* Effect.forkChild( * Stream.runIntoQueue(Stream.fromIterable([1, 2, 3]), queue) * ) * * const values = [ * yield* Queue.take(queue), * yield* Queue.take(queue), * yield* Queue.take(queue) * ] * const done = yield* Effect.flip(Queue.take(queue)) * * values // => [ 1, 2, 3 ] * done._tag === "Done" // => true * }) * await Effect.runPromise(program) * ``` * * @stability stable * @category destructors * @since 2.0.0 */ (self: Stream, queue: Queue.Queue): Effect.Effect } = dual(2, ( self: Stream, queue: Queue.Queue ): Effect.Effect => Channel.runIntoQueueArray(self.channel, queue))