| 1 | /** |
| 2 | * Describes effectful sources that emit values over time. |
| 3 | * |
| 4 | * A `Stream<A, E, R>` can emit many `A` values, fail with `E`, and require |
| 5 | * services `R` while it is being consumed. Streams are useful for data that is |
| 6 | * pulled in steps, such as values from collections, queues, pubsubs, schedules, |
| 7 | * callbacks, async iterables, or platform streams. The APIs here cover the full |
| 8 | * stream lifecycle: create a stream, transform or combine it, control buffering |
| 9 | * and timing, handle failures, and finally consume it. |
| 10 | * |
| 11 | * @since 2.0.0 |
| 12 | */ |
| 13 | // @effect-diagnostics returnEffectInGen:off |
| 14 | import * as Arr from "./Array.ts" |
| 15 | import * as ByteSize from "./ByteSize.ts" |
| 16 | import * as Cause from "./Cause.ts" |
| 17 | import * as Channel from "./Channel.ts" |
| 18 | import { Clock } from "./Clock.ts" |
| 19 | import * as Context from "./Context.ts" |
| 20 | import * as Duration from "./Duration.ts" |
| 21 | import * as Effect from "./Effect.ts" |
| 22 | import * as Equal from "./Equal.ts" |
| 23 | import * as ExecutionPlan from "./ExecutionPlan.ts" |
| 24 | import * as Exit from "./Exit.ts" |
| 25 | import * as Fiber from "./Fiber.ts" |
| 26 | import type * as Filter from "./Filter.ts" |
| 27 | import type { LazyArg } from "./Function.ts" |
| 28 | import { constant, constTrue, constVoid, dual, identity } from "./Function.ts" |
| 29 | import type { TypeLambda } from "./HKT.ts" |
| 30 | import * as Count from "./internal/count.ts" |
| 31 | import * as internalExecutionPlan from "./internal/executionPlan.ts" |
| 32 | import * as internal from "./internal/stream.ts" |
| 33 | import { addSpanStackTrace } from "./internal/tracer.ts" |
| 34 | import * as Iterable from "./Iterable.ts" |
| 35 | import * as Latch from "./Latch.ts" |
| 36 | import type * as Layer from "./Layer.ts" |
| 37 | import type { Severity } from "./LogLevel.ts" |
| 38 | import * as MutableHashMap from "./MutableHashMap.ts" |
| 39 | import * as MutableList from "./MutableList.ts" |
| 40 | import * as Option from "./Option.ts" |
| 41 | import type { Pipeable } from "./Pipeable.ts" |
| 42 | import type { Predicate, Refinement } from "./Predicate.ts" |
| 43 | import { hasProperty, isNotUndefined, isTagged } from "./Predicate.ts" |
| 44 | import * as PubSub from "./PubSub.ts" |
| 45 | import * as Pull from "./Pull.ts" |
| 46 | import * as Queue from "./Queue.ts" |
| 47 | import * as RcMap from "./RcMap.ts" |
| 48 | import * as RcRef from "./RcRef.ts" |
| 49 | import * as Result from "./Result.ts" |
| 50 | import * as Schedule from "./Schedule.ts" |
| 51 | import * as Scope from "./Scope.ts" |
| 52 | import * as Sink from "./Sink.ts" |
| 53 | import { isString } from "./String.ts" |
| 54 | import type * as Take from "./Take.ts" |
| 55 | import type { ParentSpan, SpanOptions } from "./Tracer.ts" |
| 56 | import type { |
| 57 | Concurrency, |
| 58 | Covariant, |
| 59 | ExcludeReason, |
| 60 | ExcludeTag, |
| 61 | ExtractReason, |
| 62 | ExtractTag, |
| 63 | NarrowReason, |
| 64 | NoInfer, |
| 65 | OmitReason, |
| 66 | ReasonOf, |
| 67 | ReasonTags, |
| 68 | Simplify, |
| 69 | Tags, |
| 70 | TupleOf, |
| 71 | unassigned |
| 72 | } from "./Types.ts" |
| 73 | import type * as Unify from "./Unify.ts" |
| 74 | |
| 75 | /** |
| 76 | * String literal type used as the unique brand for `Stream` values. |
| 77 | * |
| 78 | * @category type IDs |
| 79 | * @since 4.0.0 |
| 80 | */ |
| 81 | export type TypeId = "~effect/Stream" |
| 82 | |
| 83 | /** |
| 84 | * Runtime identifier stored on `Stream` values and used by `isStream` to |
| 85 | * recognize them. |
| 86 | * |
| 87 | * **Details** |
| 88 | * |
| 89 | * This marker is part of the runtime representation of `Stream` values. Prefer |
| 90 | * `isStream` when narrowing unknown values. |
| 91 | * |
| 92 | * @see {@link isStream} for the public guard that checks this identifier |
| 93 | * |
| 94 | * @category type IDs |
| 95 | * @since 4.0.0 |
| 96 | */ |
| 97 | export const TypeId: TypeId = "~effect/Stream" |
| 98 | |
| 99 | /** |
| 100 | * A `Stream<A, E, R>` describes a program that can emit many `A` values, fail |
| 101 | * with `E`, and require `R`. |
| 102 | * |
| 103 | * **Details** |
| 104 | * |
| 105 | * Streams are pull-based with backpressure and emit chunks to amortize effect |
| 106 | * evaluation. They support monadic composition and error handling similar to |
| 107 | * `Effect`, adapted for multiple values. |
| 108 | * |
| 109 | * **Example** (Creating and consuming streams) |
| 110 | * |
| 111 | * ```ts import.meta.vitest |
| 112 | * import { Effect, Stream } from "effect" |
| 113 | * |
| 114 | * const values = await Effect.runPromise( |
| 115 | * Stream.make(1, 2, 3).pipe( |
| 116 | * Stream.map((n) => n * 2), |
| 117 | * Stream.runCollect |
| 118 | * ) |
| 119 | * ) |
| 120 | * values // => [2, 4, 6] |
| 121 | * ``` |
| 122 | * |
| 123 | * @category models |
| 124 | * @since 2.0.0 |
| 125 | */ |
| 126 | export interface Stream<out A, out E = never, out R = never> extends Variance<A, E, R>, Pipeable { |
| 127 | readonly channel: Channel.Channel<Arr.NonEmptyReadonlyArray<A>, E, void, unknown, unknown, unknown, R> |
| 128 | [Unify.typeSymbol]?: unknown |
| 129 | [Unify.unifySymbol]?: StreamUnify<this> |
| 130 | [Unify.ignoreSymbol]?: StreamUnifyIgnore |
| 131 | } |
| 132 | |
| 133 | /** |
| 134 | * Type-level unification hook for Stream within the Effect type system. |
| 135 | * |
| 136 | * @category models |
| 137 | * @since 2.0.0 |
| 138 | */ |
| 139 | export interface StreamUnify<A extends { [Unify.typeSymbol]?: any }> extends Effect.EffectUnify<A> { |
| 140 | Stream?: () => A[Unify.typeSymbol] extends Stream<infer A0, infer E0, infer R0> | infer _ ? Stream<A0, E0, R0> : never |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * Type-level marker that excludes Stream from unification. |
| 145 | * |
| 146 | * @category models |
| 147 | * @since 2.0.0 |
| 148 | */ |
| 149 | export interface StreamUnifyIgnore { |
| 150 | Effect?: true |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * Type lambda for Stream used in higher-kinded type operations. |
| 155 | * |
| 156 | * **Example** (Using the stream type lambda) |
| 157 | * |
| 158 | * ```ts import.meta.vitest |
| 159 | * import { Effect, HKT, Stream } from "effect" |
| 160 | * |
| 161 | * // Create a Stream type using the type lambda |
| 162 | * type NumberStream = HKT.Kind<Stream.StreamTypeLambda, never, never, string, number> |
| 163 | * // Equivalent to: Stream<number, string, never> |
| 164 | * const stream: NumberStream = Stream.make(1, 2, 3) |
| 165 | * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] |
| 166 | * ``` |
| 167 | * |
| 168 | * @category utility types |
| 169 | * @since 2.0.0 |
| 170 | */ |
| 171 | export interface StreamTypeLambda extends TypeLambda { |
| 172 | readonly type: Stream<this["Target"], this["Out1"], this["Out2"]> |
| 173 | } |
| 174 | |
| 175 | /** |
| 176 | * Type-level variance marker for `Stream`. |
| 177 | * |
| 178 | * **Details** |
| 179 | * |
| 180 | * The emitted value `A`, error `E`, and service requirement `R` type |
| 181 | * parameters are covariant. |
| 182 | * |
| 183 | * @category models |
| 184 | * @since 2.0.0 |
| 185 | */ |
| 186 | export interface Variance<out A, out E, out R> { |
| 187 | readonly [TypeId]: VarianceStruct<A, E, R> |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * Structural encoding used by `Variance` to record each `Stream` type |
| 192 | * parameter's variance. |
| 193 | * |
| 194 | * **Details** |
| 195 | * |
| 196 | * `_A`, `_E`, and `_R` are covariant markers. |
| 197 | * |
| 198 | * @category models |
| 199 | * @since 3.4.0 |
| 200 | */ |
| 201 | export interface VarianceStruct<out A, out E, out R> { |
| 202 | readonly _A: Covariant<A> |
| 203 | readonly _E: Covariant<E> |
| 204 | readonly _R: Covariant<R> |
| 205 | } |
| 206 | |
| 207 | /** |
| 208 | * Extract the success type from a Stream type. |
| 209 | * |
| 210 | * **Example** (Extracting the success type from a Stream type) |
| 211 | * |
| 212 | * ```ts import.meta.vitest |
| 213 | * import { Stream } from "effect" |
| 214 | * |
| 215 | * type NumberStream = Stream.Stream<number, string, never> |
| 216 | * type SuccessType = Stream.Success<NumberStream> |
| 217 | * const value: SuccessType = 42 |
| 218 | * ``` |
| 219 | * |
| 220 | * @category utility types |
| 221 | * @since 3.4.0 |
| 222 | */ |
| 223 | export type Success<T> = T extends Stream<infer _A, infer _E, infer _R> ? _A : never |
| 224 | |
| 225 | /** |
| 226 | * Extract the error type from a Stream type. |
| 227 | * |
| 228 | * **Example** (Extracting the error type from a Stream type) |
| 229 | * |
| 230 | * ```ts import.meta.vitest |
| 231 | * import { Stream } from "effect" |
| 232 | * |
| 233 | * type NumberStream = Stream.Stream<number, string, never> |
| 234 | * type ErrorType = Stream.Error<NumberStream> |
| 235 | * const error: ErrorType = "boom" |
| 236 | * ``` |
| 237 | * |
| 238 | * @category utility types |
| 239 | * @since 3.4.0 |
| 240 | */ |
| 241 | export type Error<T> = T extends Stream<infer _A, infer _E, infer _R> ? _E : never |
| 242 | |
| 243 | /** |
| 244 | * Extract the services type from a Stream type. |
| 245 | * |
| 246 | * **Example** (Extracting the services type from a Stream type) |
| 247 | * |
| 248 | * ```ts import.meta.vitest |
| 249 | * import { Stream } from "effect" |
| 250 | * |
| 251 | * interface Database { |
| 252 | * query: (sql: string) => unknown |
| 253 | * } |
| 254 | * type NumberStream = Stream.Stream<number, string, { db: Database }> |
| 255 | * type RequiredServices = Stream.Services<NumberStream> |
| 256 | * const services: RequiredServices = { db: { query: (sql) => sql } } |
| 257 | * services.db.query("SELECT 1") // => "SELECT 1" |
| 258 | * ``` |
| 259 | * |
| 260 | * @category utility types |
| 261 | * @since 4.0.0 |
| 262 | */ |
| 263 | export type Services<T> = T extends Stream<infer _A, infer _E, infer _R> ? _R : never |
| 264 | |
| 265 | /** |
| 266 | * Checks whether a value is a Stream. |
| 267 | * |
| 268 | * **Example** (Checking whether a value is a Stream) |
| 269 | * |
| 270 | * ```ts import.meta.vitest |
| 271 | * import { Stream } from "effect" |
| 272 | * |
| 273 | * Stream.isStream(Stream.make(1, 2, 3)) // => true |
| 274 | * Stream.isStream({ data: [1, 2, 3] }) // => false |
| 275 | * ``` |
| 276 | * |
| 277 | * @category guards |
| 278 | * @since 4.0.0 |
| 279 | */ |
| 280 | export const isStream = (u: unknown): u is Stream<unknown, unknown, unknown> => hasProperty(u, TypeId) |
| 281 | |
| 282 | /** |
| 283 | * The default chunk size used by Stream constructors and combinators. |
| 284 | * |
| 285 | * **Example** (Reading the default chunk size) |
| 286 | * |
| 287 | * ```ts import.meta.vitest |
| 288 | * import { Stream } from "effect" |
| 289 | * |
| 290 | * Stream.DefaultChunkSize // => 4096 |
| 291 | * ``` |
| 292 | * |
| 293 | * @category constants |
| 294 | * @since 2.0.0 |
| 295 | */ |
| 296 | export const DefaultChunkSize: number = Channel.DefaultChunkSize |
| 297 | |
| 298 | /** |
| 299 | * Describes how merged streams decide when to halt. |
| 300 | * |
| 301 | * @category models |
| 302 | * @since 4.0.0 |
| 303 | */ |
| 304 | export type HaltStrategy = Channel.HaltStrategy |
| 305 | |
| 306 | /** |
| 307 | * Creates a stream from a array-emitting `Channel`. |
| 308 | * |
| 309 | * **Example** (Creating a stream from an array-emitting channel) |
| 310 | * |
| 311 | * ```ts import.meta.vitest |
| 312 | * import { Channel, Effect, Stream } from "effect" |
| 313 | * |
| 314 | * const channel = Channel.succeed([1, 2, 3] as const) |
| 315 | * const stream = Stream.fromChannel(channel) |
| 316 | * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] |
| 317 | * ``` |
| 318 | * |
| 319 | * @category constructors |
| 320 | * @since 2.0.0 |
| 321 | */ |
| 322 | export const fromChannel: <Arr extends Arr.NonEmptyReadonlyArray<any>, E, R>( |
| 323 | channel: Channel.Channel<Arr, E, void, unknown, unknown, unknown, R> |
| 324 | ) => Stream<Arr extends Arr.NonEmptyReadonlyArray<infer A> ? A : never, E, R> = internal.fromChannel |
| 325 | |
| 326 | /** |
| 327 | * Creates a stream from an effect. |
| 328 | * |
| 329 | * **Example** (Creating a stream from an effect) |
| 330 | * |
| 331 | * ```ts import.meta.vitest |
| 332 | * import { Effect, Stream } from "effect" |
| 333 | * |
| 334 | * const stream = Stream.fromEffect(Effect.succeed(42)) |
| 335 | * await Effect.runPromise(Stream.runCollect(stream)) // => [42] |
| 336 | * ``` |
| 337 | * |
| 338 | * @category constructors |
| 339 | * @since 2.0.0 |
| 340 | */ |
| 341 | export const fromEffect = <A, E, R>(effect: Effect.Effect<A, E, R>): Stream<A, E, R> => |
| 342 | fromChannel(Channel.fromEffect(Effect.map(effect, Arr.of))) |
| 343 | |
| 344 | /** |
| 345 | * Accesses a service from the context and emits it as a single element. |
| 346 | * |
| 347 | * **Example** (Accessing a service as a stream) |
| 348 | * |
| 349 | * ```ts import.meta.vitest |
| 350 | * import { Context, Effect, Stream } from "effect" |
| 351 | * |
| 352 | * class Greeter extends Context.Service<Greeter, { |
| 353 | * readonly greet: (name: string) => string |
| 354 | * }>()("Greeter") {} |
| 355 | * |
| 356 | * const stream = Stream.service(Greeter).pipe( |
| 357 | * Stream.map((greeter) => greeter.greet("World")) |
| 358 | * ) |
| 359 | * |
| 360 | * await Effect.runPromise( |
| 361 | * stream.pipe( |
| 362 | * Stream.provideService(Greeter, { |
| 363 | * greet: (name) => `Hello, ${name}!` |
| 364 | * }), |
| 365 | * Stream.runCollect |
| 366 | * ) |
| 367 | * ) // => ["Hello, World!"] |
| 368 | * ``` |
| 369 | * |
| 370 | * @category accessors |
| 371 | * @since 4.0.0 |
| 372 | */ |
| 373 | export const service = <I, S>(service: Context.Key<I, S>): Stream<S, never, I> => fromEffect(Effect.service(service)) |
| 374 | |
| 375 | /** |
| 376 | * Optionally accesses a service from the context and emits the result as a |
| 377 | * single element. |
| 378 | * |
| 379 | * **When to use** |
| 380 | * |
| 381 | * Use when you need a stream that emits an optional service from the context |
| 382 | * without requiring that service to be present. |
| 383 | * |
| 384 | * **Example** (Accessing an optional service as a stream) |
| 385 | * |
| 386 | * ```ts import.meta.vitest |
| 387 | * import { Context, Effect, Option, Stream } from "effect" |
| 388 | * |
| 389 | * class Greeter extends Context.Service<Greeter, { |
| 390 | * readonly greet: (name: string) => string |
| 391 | * }>()("Greeter") {} |
| 392 | * |
| 393 | * const stream = Stream.serviceOption(Greeter).pipe( |
| 394 | * Stream.map((maybeGreeter) => |
| 395 | * Option.match(maybeGreeter, { |
| 396 | * onNone: () => "No greeter", |
| 397 | * onSome: (greeter) => greeter.greet("World") |
| 398 | * }) |
| 399 | * ) |
| 400 | * ) |
| 401 | * |
| 402 | * await Effect.runPromise( |
| 403 | * stream.pipe( |
| 404 | * Stream.provideService(Greeter, { |
| 405 | * greet: (name) => `Hello, ${name}!` |
| 406 | * }), |
| 407 | * Stream.runCollect |
| 408 | * ) |
| 409 | * ) // => ["Hello, World!"] |
| 410 | * ``` |
| 411 | * |
| 412 | * @category accessors |
| 413 | * @since 4.0.0 |
| 414 | */ |
| 415 | export const serviceOption = <I, S>(service: Context.Key<I, S>): Stream<Option.Option<S>> => |
| 416 | fromEffect(Effect.serviceOption(service)) |
| 417 | |
| 418 | /** |
| 419 | * Creates a stream that runs the effect and emits no elements. |
| 420 | * |
| 421 | * **Example** (Draining an effect into a stream) |
| 422 | * |
| 423 | * ```ts import.meta.vitest |
| 424 | * import { Effect, Stream } from "effect" |
| 425 | * |
| 426 | * let drained = false |
| 427 | * await Effect.runPromise( |
| 428 | * Stream.fromEffectDrain(Effect.sync(() => { |
| 429 | * drained = true |
| 430 | * })).pipe(Stream.runDrain) |
| 431 | * ) |
| 432 | * drained // => true |
| 433 | * ``` |
| 434 | * |
| 435 | * @category constructors |
| 436 | * @since 4.0.0 |
| 437 | */ |
| 438 | export const fromEffectDrain = <A, E, R>(effect: Effect.Effect<A, E, R>): Stream<never, E, R> => |
| 439 | fromPull(Effect.succeed(Effect.flatMap(effect, () => Cause.done()))) |
| 440 | |
| 441 | /** |
| 442 | * Creates a stream from an effect producing a value of type `A` which repeats forever. |
| 443 | * |
| 444 | * **Example** (Repeating an effect forever) |
| 445 | * |
| 446 | * ```ts import.meta.vitest |
| 447 | * import { Effect, Stream } from "effect" |
| 448 | * |
| 449 | * let n = 0 |
| 450 | * const stream = Stream.fromEffectRepeat(Effect.sync(() => ++n)).pipe(Stream.take(5)) |
| 451 | * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3, 4, 5] |
| 452 | * ``` |
| 453 | * |
| 454 | * @category constructors |
| 455 | * @since 4.0.0 |
| 456 | */ |
| 457 | export const fromEffectRepeat = <A, E, R>(effect: Effect.Effect<A, E, R>): Stream<A, Pull.ExcludeDone<E>, R> => |
| 458 | fromPull(Effect.succeed(Effect.map(effect, Arr.of))) |
| 459 | |
| 460 | /** |
| 461 | * Creates a stream from an effect producing a value of type `A`, which is |
| 462 | * repeated using the specified schedule. |
| 463 | * |
| 464 | * **Example** (Repeating an effect with a schedule) |
| 465 | * |
| 466 | * ```ts import.meta.vitest |
| 467 | * import { Effect, Schedule, Stream } from "effect" |
| 468 | * |
| 469 | * const stream = Stream.fromEffectSchedule(Effect.succeed("ping"), Schedule.recurs(2)) |
| 470 | * await Effect.runPromise(Stream.runCollect(stream)) // => ["ping", "ping", "ping"] |
| 471 | * ``` |
| 472 | * |
| 473 | * @category constructors |
| 474 | * @since 4.0.0 |
| 475 | */ |
| 476 | export const fromEffectSchedule = <A, E, R, X, AS extends A, ES, RS>( |
| 477 | effect: Effect.Effect<A, E, R>, |
| 478 | schedule: Schedule.Schedule<X, AS, ES, RS> |
| 479 | ): Stream<A, E | ES, R | RS> => |
| 480 | fromPull(Effect.gen(function*() { |
| 481 | const step = yield* Schedule.toStepWithMetadata(schedule) |
| 482 | let s = yield* Effect.provideService(effect, Schedule.CurrentMetadata, Schedule.CurrentMetadata.defaultValue()) |
| 483 | let initial = true |
| 484 | const pull = Effect.suspend(() => step(s as AS)).pipe( |
| 485 | Effect.flatMap((meta) => Effect.provideService(effect, Schedule.CurrentMetadata, meta)), |
| 486 | Effect.map((next) => { |
| 487 | s = next |
| 488 | return Arr.of(next) |
| 489 | }) |
| 490 | ) as Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E | ES, void, R | RS> |
| 491 | return Effect.suspend(() => { |
| 492 | if (initial) { |
| 493 | initial = false |
| 494 | return Effect.succeed(Arr.of(s)) |
| 495 | } |
| 496 | return pull |
| 497 | }) |
| 498 | })) |
| 499 | |
| 500 | /** |
| 501 | * Creates a stream that emits `void` immediately once, then emits another |
| 502 | * `void` after each specified interval. |
| 503 | * |
| 504 | * **Example** (Emitting ticks on an interval) |
| 505 | * |
| 506 | * ```ts import.meta.vitest |
| 507 | * import { Effect, Stream } from "effect" |
| 508 | * |
| 509 | * await Effect.runPromise(Stream.tick(0).pipe(Stream.take(3), Stream.runCollect)) // => [undefined, undefined, undefined] |
| 510 | * ``` |
| 511 | * |
| 512 | * @category constructors |
| 513 | * @since 2.0.0 |
| 514 | */ |
| 515 | export const tick = (interval: Duration.Input): Stream<void> => |
| 516 | fromPull(Effect.sync(() => { |
| 517 | let first = true |
| 518 | const effect = Effect.succeed(Arr.of<void>(undefined)) |
| 519 | const delayed = Effect.delay(effect, interval) |
| 520 | return Effect.suspend(() => { |
| 521 | if (first) { |
| 522 | first = false |
| 523 | return effect |
| 524 | } |
| 525 | return delayed |
| 526 | }) |
| 527 | })) |
| 528 | |
| 529 | /** |
| 530 | * Creates a stream from a pull effect, such as one produced by `Stream.toPull`. |
| 531 | * |
| 532 | * **Details** |
| 533 | * |
| 534 | * A pull effect yields chunks on demand and completes when the upstream stream ends. |
| 535 | * See `Stream.toPull` for a matching producer. |
| 536 | * |
| 537 | * **Example** (Creating a stream from a pull effect) |
| 538 | * |
| 539 | * ```ts import.meta.vitest |
| 540 | * import { Effect, Stream } from "effect" |
| 541 | * |
| 542 | * const program = Effect.scoped( |
| 543 | * Effect.gen(function*() { |
| 544 | * const source = Stream.make(1, 2, 3) |
| 545 | * const pull = yield* Stream.toPull(source) |
| 546 | * const stream = Stream.fromPull(Effect.succeed(pull)) |
| 547 | * return yield* Stream.runCollect(stream) |
| 548 | * }) |
| 549 | * ) |
| 550 | * |
| 551 | * await Effect.runPromise(program) // => [1, 2, 3] |
| 552 | * ``` |
| 553 | * |
| 554 | * @category constructors |
| 555 | * @since 2.0.0 |
| 556 | */ |
| 557 | export const fromPull = <A, E, R, EX, RX>( |
| 558 | pull: Effect.Effect<Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E, void, R>, EX, RX> |
| 559 | ): Stream<A, Pull.ExcludeDone<E> | EX, R | RX> => fromChannel(Channel.fromPull(pull)) |
| 560 | |
| 561 | /** |
| 562 | * Derives a stream by transforming its pull effect. |
| 563 | * |
| 564 | * **Example** (Transforming a pull effect) |
| 565 | * |
| 566 | * ```ts import.meta.vitest |
| 567 | * import { Effect, Stream } from "effect" |
| 568 | * |
| 569 | * const stream = Stream.make(1, 2, 3) |
| 570 | * |
| 571 | * const transformed = Stream.transformPull(stream, (pull) => Effect.succeed(pull)) |
| 572 | * |
| 573 | * await Effect.runPromise(Stream.runCollect(transformed)) // => [1, 2, 3] |
| 574 | * ``` |
| 575 | * |
| 576 | * @category constructors |
| 577 | * @since 4.0.0 |
| 578 | */ |
| 579 | export const transformPull = <A, E, R, B, E2, R2, EX, RX>( |
| 580 | self: Stream<A, E, R>, |
| 581 | f: (pull: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E, void>, scope: Scope.Scope) => Effect.Effect< |
| 582 | Pull.Pull<Arr.NonEmptyReadonlyArray<B>, E2, void, R2>, |
| 583 | EX, |
| 584 | RX |
| 585 | > |
| 586 | ): Stream<B, EX | Pull.ExcludeDone<E2>, R | R2 | RX> => |
| 587 | fromChannel( |
| 588 | Channel.fromTransform((_, scope) => |
| 589 | Effect.flatMap(Channel.toPullScoped(self.channel, scope), (pull) => f(pull as any, scope)) |
| 590 | ) |
| 591 | ) |
| 592 | |
| 593 | /** |
| 594 | * Transforms a stream by effectfully transforming its pull effect. |
| 595 | * |
| 596 | * **Details** |
| 597 | * |
| 598 | * A forked scope is also provided to the transformation function, which is |
| 599 | * closed once the resulting stream has finished processing. |
| 600 | * |
| 601 | * **Example** (Transforming a stream by effectfully transforming its pull effect) |
| 602 | * |
| 603 | * ```ts import.meta.vitest |
| 604 | * import { Effect, Scope, Stream } from "effect" |
| 605 | * |
| 606 | * const finalized: Array<boolean> = [] |
| 607 | * const stream = Stream.make(1, 2, 3) |
| 608 | * |
| 609 | * const transformed = Stream.transformPullBracket( |
| 610 | * stream, |
| 611 | * (pull, _scope, forkedScope) => |
| 612 | * Effect.gen(function*() { |
| 613 | * yield* Scope.addFinalizer(forkedScope, Effect.sync(() => finalized.push(true))) |
| 614 | * return pull |
| 615 | * }) |
| 616 | * ) |
| 617 | * |
| 618 | * await Effect.runPromise(Stream.runCollect(transformed)) // => [1, 2, 3] |
| 619 | * finalized // => [true] |
| 620 | * ``` |
| 621 | * |
| 622 | * @category constructors |
| 623 | * @since 4.0.0 |
| 624 | */ |
| 625 | export const transformPullBracket = <A, E, R, B, E2, R2, EX, RX>( |
| 626 | self: Stream<A, E, R>, |
| 627 | f: ( |
| 628 | pull: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E, void, R>, |
| 629 | scope: Scope.Scope, |
| 630 | forkedScope: Scope.Scope |
| 631 | ) => Effect.Effect< |
| 632 | Pull.Pull<Arr.NonEmptyReadonlyArray<B>, E2, void, R2>, |
| 633 | EX, |
| 634 | RX |
| 635 | > |
| 636 | ): Stream<B, EX | Pull.ExcludeDone<E2>, R | R2 | RX> => |
| 637 | fromChannel( |
| 638 | Channel.fromTransformBracket((_, scope, forkedScope) => |
| 639 | Effect.flatMap(Channel.toPullScoped(self.channel, scope), (pull) => f(pull, scope, forkedScope)) |
| 640 | ) |
| 641 | ) |
| 642 | |
| 643 | /** |
| 644 | * Creates a channel from a stream. |
| 645 | * |
| 646 | * **Example** (Converting a stream to a channel) |
| 647 | * |
| 648 | * ```ts import.meta.vitest |
| 649 | * import { Channel, Effect, Stream } from "effect" |
| 650 | * |
| 651 | * const channel = Stream.toChannel(Stream.make(1, 2, 3)) |
| 652 | * const values = await Effect.runPromise(Channel.runCollect(channel)) |
| 653 | * values.flat() // => [1, 2, 3] |
| 654 | * ``` |
| 655 | * |
| 656 | * @category destructors |
| 657 | * @since 2.0.0 |
| 658 | */ |
| 659 | export const toChannel = <A, E, R>( |
| 660 | stream: Stream<A, E, R> |
| 661 | ): Channel.Channel<Arr.NonEmptyReadonlyArray<A>, E, void, unknown, unknown, unknown, R> => stream.channel |
| 662 | |
| 663 | /** |
| 664 | * Creates a stream from a callback that can emit values into a queue. |
| 665 | * |
| 666 | * **When to use** |
| 667 | * |
| 668 | * Use when you need callback-based code to emit stream values by offering to a |
| 669 | * `Queue`, or signal stream completion through the `Queue` module APIs. |
| 670 | * |
| 671 | * By default it uses an "unbounded" buffer size. |
| 672 | * You can customize the buffer size and strategy by passing an object as the |
| 673 | * second argument with the `bufferSize` and `strategy` fields. |
| 674 | * |
| 675 | * **Example** (Creating a stream from a callback that can emit values into a queue) |
| 676 | * |
| 677 | * ```ts import.meta.vitest |
| 678 | * import { Effect, Queue, Stream } from "effect" |
| 679 | * |
| 680 | * const stream = Stream.callback<number>((queue) => |
| 681 | * Effect.sync(() => { |
| 682 | * // Emit values to the stream |
| 683 | * Queue.offerUnsafe(queue, 1) |
| 684 | * Queue.offerUnsafe(queue, 2) |
| 685 | * Queue.offerUnsafe(queue, 3) |
| 686 | * // Signal completion |
| 687 | * Queue.endUnsafe(queue) |
| 688 | * }) |
| 689 | * ) |
| 690 | * |
| 691 | * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] |
| 692 | * ``` |
| 693 | * |
| 694 | * @category constructors |
| 695 | * @since 4.0.0 |
| 696 | */ |
| 697 | export const callback = <A, E = never, R = never>( |
| 698 | f: (queue: Queue.Queue<A, E | Cause.Done>) => Effect.Effect<unknown, E, R | Scope.Scope>, |
| 699 | options?: { |
| 700 | readonly bufferSize?: number | undefined |
| 701 | readonly strategy?: "sliding" | "dropping" | "suspend" | undefined |
| 702 | } |
| 703 | ): Stream<A, E, Exclude<R, Scope.Scope>> => fromChannel(Channel.callbackArray(f, options)) |
| 704 | |
| 705 | /** |
| 706 | * Creates an empty stream. |
| 707 | * |
| 708 | * **Example** (Creating an empty stream) |
| 709 | * |
| 710 | * ```ts import.meta.vitest |
| 711 | * import { Effect, Stream } from "effect" |
| 712 | * |
| 713 | * await Effect.runPromise(Stream.runCollect(Stream.empty)) // => [] |
| 714 | * ``` |
| 715 | * |
| 716 | * @category constructors |
| 717 | * @since 2.0.0 |
| 718 | */ |
| 719 | export const empty: Stream<never> = fromChannel(Channel.empty) |
| 720 | |
| 721 | /** |
| 722 | * Creates a single-valued pure stream. |
| 723 | * |
| 724 | * **Example** (Creating a single-valued pure stream) |
| 725 | * |
| 726 | * ```ts import.meta.vitest |
| 727 | * import { Effect, Stream } from "effect" |
| 728 | * |
| 729 | * await Effect.runPromise(Stream.runCollect(Stream.succeed(3))) // => [3] |
| 730 | * ``` |
| 731 | * |
| 732 | * @category constructors |
| 733 | * @since 2.0.0 |
| 734 | */ |
| 735 | export const succeed = <A>(value: A): Stream<A> => fromChannel(Channel.succeed(Arr.of(value))) |
| 736 | |
| 737 | /** |
| 738 | * Creates a stream from a sequence of values. |
| 739 | * |
| 740 | * **Example** (Creating a stream from a sequence of values) |
| 741 | * |
| 742 | * ```ts import.meta.vitest |
| 743 | * import { Effect, Stream } from "effect" |
| 744 | * |
| 745 | * const stream = Stream.make(1, 2, 3) |
| 746 | * |
| 747 | * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] |
| 748 | * ``` |
| 749 | * |
| 750 | * @category constructors |
| 751 | * @since 2.0.0 |
| 752 | */ |
| 753 | export const make = <const As extends ReadonlyArray<any>>(...values: As): Stream<As[number]> => fromArray(values) |
| 754 | |
| 755 | /** |
| 756 | * Creates a stream that synchronously evaluates a function and emits the result as a single value. |
| 757 | * |
| 758 | * **Details** |
| 759 | * |
| 760 | * The function is evaluated each time the stream is run. |
| 761 | * |
| 762 | * **Example** (Evaluating a value synchronously) |
| 763 | * |
| 764 | * ```ts import.meta.vitest |
| 765 | * import { Effect, Stream } from "effect" |
| 766 | * |
| 767 | * await Effect.runPromise(Stream.sync(() => 2 + 1).pipe(Stream.runCollect)) // => [3] |
| 768 | * ``` |
| 769 | * |
| 770 | * @category constructors |
| 771 | * @since 2.0.0 |
| 772 | */ |
| 773 | export const sync = <A>(evaluate: LazyArg<A>): Stream<A> => fromChannel(Channel.sync(() => Arr.of(evaluate()))) |
| 774 | |
| 775 | /** |
| 776 | * Creates a lazily constructed stream. |
| 777 | * |
| 778 | * **Details** |
| 779 | * |
| 780 | * The stream factory is evaluated each time the stream is run. |
| 781 | * |
| 782 | * **Example** (Creating a lazily constructed stream) |
| 783 | * |
| 784 | * ```ts import.meta.vitest |
| 785 | * import { Effect, Stream } from "effect" |
| 786 | * |
| 787 | * await Effect.runPromise(Stream.suspend(() => Stream.make(1, 2, 3)).pipe(Stream.runCollect)) // => [1, 2, 3] |
| 788 | * ``` |
| 789 | * |
| 790 | * @category constructors |
| 791 | * @since 2.0.0 |
| 792 | */ |
| 793 | export const suspend = <A, E, R>(stream: LazyArg<Stream<A, E, R>>): Stream<A, E, R> => |
| 794 | fromChannel(Channel.suspend(() => stream().channel)) |
| 795 | |
| 796 | /** |
| 797 | * Terminates with the specified error. |
| 798 | * |
| 799 | * **Example** (Failing a stream) |
| 800 | * |
| 801 | * ```ts import.meta.vitest |
| 802 | * import { Effect, Exit, Stream } from "effect" |
| 803 | * |
| 804 | * await Effect.runPromise(Effect.exit(Stream.runCollect(Stream.fail("Uh oh!")))) // => Exit.fail("Uh oh!") |
| 805 | * ``` |
| 806 | * |
| 807 | * @category constructors |
| 808 | * @since 2.0.0 |
| 809 | */ |
| 810 | export const fail = <E>(error: E): Stream<never, E> => fromChannel(Channel.fail(error)) |
| 811 | |
| 812 | /** |
| 813 | * Terminates with the specified lazily evaluated error. |
| 814 | * |
| 815 | * **Example** (Failing a stream lazily) |
| 816 | * |
| 817 | * ```ts import.meta.vitest |
| 818 | * import { Effect, Exit, Stream } from "effect" |
| 819 | * |
| 820 | * const stream = Stream.failSync(() => "Uh oh!") |
| 821 | * |
| 822 | * await Effect.runPromise(Stream.runCollect(stream).pipe(Effect.exit)) // => Exit.fail("Uh oh!") |
| 823 | * ``` |
| 824 | * |
| 825 | * @category constructors |
| 826 | * @since 2.0.0 |
| 827 | */ |
| 828 | export const failSync = <E>(evaluate: LazyArg<E>): Stream<never, E> => fromChannel(Channel.failSync(evaluate)) |
| 829 | |
| 830 | /** |
| 831 | * Creates a stream that fails with the specified `Cause`. |
| 832 | * |
| 833 | * **Example** (Failing with a cause) |
| 834 | * |
| 835 | * ```ts import.meta.vitest |
| 836 | * import { Cause, Effect, Stream } from "effect" |
| 837 | * |
| 838 | * const stream = Stream.failCause(Cause.fail("Database connection failed")).pipe( |
| 839 | * Stream.catchCause(() => Stream.succeed("recovered")) |
| 840 | * ) |
| 841 | * |
| 842 | * await Effect.runPromise(Stream.runCollect(stream)) // => ["recovered"] |
| 843 | * ``` |
| 844 | * |
| 845 | * @category constructors |
| 846 | * @since 2.0.0 |
| 847 | */ |
| 848 | export const failCause = <E>(cause: Cause.Cause<E>): Stream<never, E> => fromChannel(Channel.failCause(cause)) |
| 849 | |
| 850 | /** |
| 851 | * The stream that dies with the specified defect. |
| 852 | * |
| 853 | * **Example** (Dying with a defect) |
| 854 | * |
| 855 | * ```ts import.meta.vitest |
| 856 | * import { Cause, Effect, Exit, Stream } from "effect" |
| 857 | * |
| 858 | * const defect = new Error("Boom") |
| 859 | * const stream = Stream.die(defect) |
| 860 | * |
| 861 | * await Effect.runPromise(Effect.exit(Stream.runCollect(stream))) // => Exit.failCause(Cause.die(defect)) |
| 862 | * ``` |
| 863 | * |
| 864 | * @category constructors |
| 865 | * @since 2.0.0 |
| 866 | */ |
| 867 | export const die = (defect: unknown): Stream<never> => fromChannel(Channel.die(defect)) |
| 868 | |
| 869 | /** |
| 870 | * The stream that always fails with the specified lazily evaluated `Cause`. |
| 871 | * |
| 872 | * **Example** (Failing with a lazy cause) |
| 873 | * |
| 874 | * ```ts import.meta.vitest |
| 875 | * import { Cause, Effect, Exit, Stream } from "effect" |
| 876 | * |
| 877 | * const stream = Stream.failCauseSync(() => |
| 878 | * Cause.fail("Connection timeout after retries") |
| 879 | * ) |
| 880 | * |
| 881 | * await Effect.runPromise(Stream.runCollect(stream).pipe(Effect.exit)) // => Exit.fail("Connection timeout after retries") |
| 882 | * ``` |
| 883 | * |
| 884 | * @category constructors |
| 885 | * @since 2.0.0 |
| 886 | */ |
| 887 | export const failCauseSync = <E>(evaluate: LazyArg<Cause.Cause<E>>): Stream<never, E> => |
| 888 | fromChannel(Channel.failCauseSync(evaluate)) |
| 889 | |
| 890 | /** |
| 891 | * Creates a stream that consumes values from an iterator. |
| 892 | * |
| 893 | * **Details** |
| 894 | * |
| 895 | * The `maxChunkSize` parameter controls how many values are pulled per chunk. |
| 896 | * Finite fractional values are rounded down, while `NaN` and non-positive |
| 897 | * values are treated as `1`. |
| 898 | * |
| 899 | * **Example** (Consuming values from an iterator) |
| 900 | * |
| 901 | * ```ts import.meta.vitest |
| 902 | * import { Effect, Stream } from "effect" |
| 903 | * |
| 904 | * function* numbers() { |
| 905 | * yield 1 |
| 906 | * yield 2 |
| 907 | * yield 3 |
| 908 | * } |
| 909 | * |
| 910 | * const stream = Stream.fromIteratorSucceed(numbers()) |
| 911 | * |
| 912 | * const program = Effect.gen(function* () { |
| 913 | * const values = yield* Stream.runCollect(stream) |
| 914 | * values // => [1, 2, 3] |
| 915 | * }) |
| 916 | * |
| 917 | * await Effect.runPromise(program) |
| 918 | * ``` |
| 919 | * |
| 920 | * @category constructors |
| 921 | * @since 2.0.0 |
| 922 | */ |
| 923 | export const fromIteratorSucceed = <A>(iterator: IterableIterator<A>, maxChunkSize?: number): Stream<A> => |
| 924 | fromChannel(Channel.fromIteratorArray(() => iterator, maxChunkSize)) |
| 925 | |
| 926 | /** |
| 927 | * Creates a new `Stream` from an iterable collection of values. |
| 928 | * |
| 929 | * **Details** |
| 930 | * |
| 931 | * - `chunkSize`: Maximum number of values emitted per chunk. Finite fractional |
| 932 | * values are rounded down, while `NaN` and non-positive values are treated as |
| 933 | * `1`. |
| 934 | * |
| 935 | * **Example** (Creating a stream from an iterable) |
| 936 | * |
| 937 | * ```ts import.meta.vitest |
| 938 | * import { Effect, Stream } from "effect" |
| 939 | * |
| 940 | * const numbers = [1, 2, 3] |
| 941 | * |
| 942 | * const program = Effect.gen(function*() { |
| 943 | * const stream = Stream.fromIterable(numbers) |
| 944 | * const values = yield* Stream.runCollect(stream) |
| 945 | * values // => [1, 2, 3] |
| 946 | * }) |
| 947 | * |
| 948 | * await Effect.runPromise(program) |
| 949 | * ``` |
| 950 | * |
| 951 | * @category constructors |
| 952 | * @since 2.0.0 |
| 953 | */ |
| 954 | export const fromIterable = <A>( |
| 955 | iterable: Iterable<A>, |
| 956 | options?: { |
| 957 | readonly chunkSize?: number | undefined |
| 958 | } |
| 959 | ): Stream<A> => |
| 960 | Array.isArray(iterable) && options?.chunkSize === undefined |
| 961 | ? fromArray(iterable) |
| 962 | : fromChannel(Channel.fromIterableArray(iterable, options?.chunkSize)) |
| 963 | |
| 964 | /** |
| 965 | * Creates a stream from an effect producing an iterable of values. |
| 966 | * |
| 967 | * **When to use** |
| 968 | * |
| 969 | * Use when the iterable must be acquired from an Effect before the stream emits, |
| 970 | * and acquisition services or failures should be part of the stream. |
| 971 | * |
| 972 | * **Example** (Creating a stream from an iterable effect) |
| 973 | * |
| 974 | * ```ts import.meta.vitest |
| 975 | * import { Context, Effect, Stream } from "effect" |
| 976 | * |
| 977 | * class UserRepo extends Context.Service<UserRepo, { |
| 978 | * readonly list: Effect.Effect<ReadonlyArray<string>> |
| 979 | * }>()("UserRepo") {} |
| 980 | * |
| 981 | * const listUsers = Effect.service(UserRepo).pipe( |
| 982 | * Effect.andThen((repo) => repo.list) |
| 983 | * ) |
| 984 | * |
| 985 | * const stream = Stream.fromIterableEffect(listUsers) |
| 986 | * |
| 987 | * const program = Effect.gen(function*() { |
| 988 | * const users = yield* stream.pipe( |
| 989 | * Stream.provideService(UserRepo, { |
| 990 | * list: Effect.succeed(["user1", "user2"]) |
| 991 | * }), |
| 992 | * Stream.runCollect |
| 993 | * ) |
| 994 | * users // => ["user1", "user2"] |
| 995 | * }) |
| 996 | * |
| 997 | * await Effect.runPromise(program) |
| 998 | * ``` |
| 999 | * |
| 1000 | * @category constructors |
| 1001 | * @since 2.0.0 |
| 1002 | */ |
| 1003 | export const fromIterableEffect = <A, E, R>(iterable: Effect.Effect<Iterable<A>, E, R>): Stream<A, E, R> => |
| 1004 | unwrap(Effect.map(iterable, fromIterable)) |
| 1005 | |
| 1006 | /** |
| 1007 | * Creates a stream by repeatedly running an effect that yields an iterable of values. |
| 1008 | * |
| 1009 | * **Example** (Repeating an iterable effect) |
| 1010 | * |
| 1011 | * ```ts import.meta.vitest |
| 1012 | * import { Effect, Stream } from "effect" |
| 1013 | * |
| 1014 | * const program = Effect.gen(function*() { |
| 1015 | * const stream = Stream.fromIterableEffectRepeat(Effect.succeed([1, 2])).pipe( |
| 1016 | * Stream.take(5) |
| 1017 | * ) |
| 1018 | * const values = yield* Stream.runCollect(stream) |
| 1019 | * values // => [1, 2, 1, 2, 1] |
| 1020 | * }) |
| 1021 | * |
| 1022 | * await Effect.runPromise(program) |
| 1023 | * ``` |
| 1024 | * |
| 1025 | * @category constructors |
| 1026 | * @since 4.0.0 |
| 1027 | */ |
| 1028 | export const fromIterableEffectRepeat = <A, E, R>( |
| 1029 | iterable: Effect.Effect<Iterable<A>, E, R> |
| 1030 | ): Stream<A, Pull.ExcludeDone<E>, R> => flatMap(fromEffectRepeat(iterable), fromIterable) |
| 1031 | |
| 1032 | /** |
| 1033 | * Creates a stream from an array of values. |
| 1034 | * |
| 1035 | * **Example** (Creating a stream from an array of values) |
| 1036 | * |
| 1037 | * ```ts import.meta.vitest |
| 1038 | * import { Effect, Stream } from "effect" |
| 1039 | * |
| 1040 | * const program = Effect.gen(function*() { |
| 1041 | * const stream = Stream.fromArray([1, 2, 3]) |
| 1042 | * const values = yield* Stream.runCollect(stream) |
| 1043 | * values // => [1, 2, 3] |
| 1044 | * }) |
| 1045 | * |
| 1046 | * await Effect.runPromise(program) |
| 1047 | * ``` |
| 1048 | * |
| 1049 | * @category constructors |
| 1050 | * @since 4.0.0 |
| 1051 | */ |
| 1052 | export const fromArray = <A>(array: ReadonlyArray<A>): Stream<A> => |
| 1053 | Arr.isReadonlyArrayNonEmpty(array) ? fromChannel(Channel.succeed(array)) : empty |
| 1054 | |
| 1055 | /** |
| 1056 | * Creates a stream from an effect that produces an array of values. |
| 1057 | * |
| 1058 | * **When to use** |
| 1059 | * |
| 1060 | * Use when the array must be acquired from an Effect before the stream emits, |
| 1061 | * and acquisition services or failures should be part of the stream. |
| 1062 | * |
| 1063 | * **Example** (Creating a stream from an effect that produces an array of values) |
| 1064 | * |
| 1065 | * ```ts import.meta.vitest |
| 1066 | * import { Effect, Stream } from "effect" |
| 1067 | * |
| 1068 | * const program = Effect.gen(function*() { |
| 1069 | * const stream = Stream.fromArrayEffect(Effect.succeed(["Ada", "Grace"])) |
| 1070 | * const values = yield* Stream.runCollect(stream) |
| 1071 | * values // => ["Ada", "Grace"] |
| 1072 | * }) |
| 1073 | * |
| 1074 | * await Effect.runPromise(program) |
| 1075 | * ``` |
| 1076 | * |
| 1077 | * @category constructors |
| 1078 | * @since 4.0.0 |
| 1079 | */ |
| 1080 | export const fromArrayEffect = <A, E, R>( |
| 1081 | effect: Effect.Effect<ReadonlyArray<A>, E, R> |
| 1082 | ): Stream<A, Pull.ExcludeDone<E>, R> => unwrap(Effect.map(effect, fromArray)) as any |
| 1083 | |
| 1084 | /** |
| 1085 | * Creates a stream from an arbitrary number of arrays. |
| 1086 | * |
| 1087 | * **Example** (Creating a stream from an arbitrary number of arrays) |
| 1088 | * |
| 1089 | * ```ts import.meta.vitest |
| 1090 | * import { Effect, Stream } from "effect" |
| 1091 | * |
| 1092 | * const program = Effect.gen(function*() { |
| 1093 | * const stream = Stream.fromArrays([1, 2], [3, 4]) |
| 1094 | * const values = yield* Stream.runCollect(stream) |
| 1095 | * values // => [1, 2, 3, 4] |
| 1096 | * }) |
| 1097 | * |
| 1098 | * await Effect.runPromise(program) |
| 1099 | * ``` |
| 1100 | * |
| 1101 | * @category constructors |
| 1102 | * @since 4.0.0 |
| 1103 | */ |
| 1104 | export const fromArrays = <Arr extends ReadonlyArray<ReadonlyArray<any>>>( |
| 1105 | ...arrays: Arr |
| 1106 | ): Stream<Arr[number][number]> => fromChannel(Channel.fromArray(Arr.filter(arrays, Arr.isReadonlyArrayNonEmpty))) |
| 1107 | |
| 1108 | /** |
| 1109 | * Creates a stream that pulls values from a `Queue.Dequeue`. |
| 1110 | * |
| 1111 | * **Details** |
| 1112 | * |
| 1113 | * The stream emits non-empty batches of queued values and ends when the queue |
| 1114 | * fails with `Cause.Done`; other queue failures are propagated. |
| 1115 | * |
| 1116 | * **Example** (Creating a stream from a queue of values) |
| 1117 | * |
| 1118 | * ```ts import.meta.vitest |
| 1119 | * import { Cause, Effect, Queue, Stream } from "effect" |
| 1120 | * |
| 1121 | * const program = Effect.gen(function*() { |
| 1122 | * const queue = yield* Queue.unbounded<number, Cause.Done>() |
| 1123 | * yield* Queue.offer(queue, 1) |
| 1124 | * yield* Queue.offer(queue, 2) |
| 1125 | * yield* Queue.offer(queue, 3) |
| 1126 | * yield* Queue.end(queue) |
| 1127 | * |
| 1128 | * const stream = Stream.fromQueue(queue) |
| 1129 | * const values = yield* Stream.runCollect(stream) |
| 1130 | * values // => [1, 2, 3] |
| 1131 | * }) |
| 1132 | * |
| 1133 | * await Effect.runPromise(program) |
| 1134 | * ``` |
| 1135 | * |
| 1136 | * @category constructors |
| 1137 | * @since 2.0.0 |
| 1138 | */ |
| 1139 | export const fromQueue = <A, E>(queue: Queue.Dequeue<A, E>): Stream<A, Exclude<E, Cause.Done>> => |
| 1140 | fromChannel(Channel.fromQueueArray(queue)) |
| 1141 | |
| 1142 | /** |
| 1143 | * Creates a stream from a subscription to a `PubSub`. |
| 1144 | * |
| 1145 | * **Example** (Creating a stream from a subscription to a PubSub) |
| 1146 | * |
| 1147 | * ```ts import.meta.vitest |
| 1148 | * import { Effect, Fiber, PubSub, Stream } from "effect" |
| 1149 | * |
| 1150 | * const program = Effect.gen(function*() { |
| 1151 | * const pubsub = yield* PubSub.unbounded<number>({ replay: 3 }) |
| 1152 | * |
| 1153 | * const fiber = yield* Stream.fromPubSub(pubsub).pipe( |
| 1154 | * Stream.take(3), |
| 1155 | * Stream.runCollect, |
| 1156 | * Effect.forkChild |
| 1157 | * ) |
| 1158 | * |
| 1159 | * yield* PubSub.publish(pubsub, 1) |
| 1160 | * yield* PubSub.publish(pubsub, 2) |
| 1161 | * yield* PubSub.publish(pubsub, 3) |
| 1162 | * |
| 1163 | * const values = yield* Fiber.join(fiber) |
| 1164 | * values // => [1, 2, 3] |
| 1165 | * }) |
| 1166 | * |
| 1167 | * await Effect.runPromise(program) |
| 1168 | * ``` |
| 1169 | * |
| 1170 | * @category constructors |
| 1171 | * @since 2.0.0 |
| 1172 | */ |
| 1173 | export const fromPubSub = <A>(pubsub: PubSub.PubSub<A>): Stream<A> => fromChannel(Channel.fromPubSubArray(pubsub)) |
| 1174 | |
| 1175 | /** |
| 1176 | * Creates a stream from a PubSub of `Take` values. |
| 1177 | * |
| 1178 | * **Details** |
| 1179 | * |
| 1180 | * `Take` values include end and failure signals. |
| 1181 | * |
| 1182 | * **Example** (Creating a stream from PubSub takes) |
| 1183 | * |
| 1184 | * ```ts import.meta.vitest |
| 1185 | * import { Effect, Exit, PubSub, Stream, Take } from "effect" |
| 1186 | * |
| 1187 | * const program = Effect.gen(function*() { |
| 1188 | * const pubsub = yield* PubSub.unbounded<Take.Take<number, string>>({ |
| 1189 | * replay: 3 |
| 1190 | * }) |
| 1191 | * |
| 1192 | * yield* PubSub.publish(pubsub, [1]) |
| 1193 | * yield* PubSub.publish(pubsub, [2]) |
| 1194 | * yield* PubSub.publish(pubsub, Exit.succeed<void>(undefined)) |
| 1195 | * |
| 1196 | * const values = yield* Stream.fromPubSubTake(pubsub).pipe(Stream.runCollect) |
| 1197 | * values // => [1, 2] |
| 1198 | * }) |
| 1199 | * |
| 1200 | * await Effect.runPromise(program) |
| 1201 | * ``` |
| 1202 | * |
| 1203 | * @category constructors |
| 1204 | * @since 4.0.0 |
| 1205 | */ |
| 1206 | export const fromPubSubTake = <A, E>(pubsub: PubSub.PubSub<Take.Take<A, E>>): Stream<A, E> => |
| 1207 | fromChannel(Channel.fromPubSubTake(pubsub)) |
| 1208 | |
| 1209 | /** |
| 1210 | * Creates a stream from a lazily supplied Web `ReadableStream`. |
| 1211 | * |
| 1212 | * **Details** |
| 1213 | * |
| 1214 | * The stream reads from a `ReadableStreamDefaultReader`, maps read failures |
| 1215 | * with `onError`, and closes the reader when the stream finalizes. By default |
| 1216 | * the reader is canceled; set `releaseLockOnEnd` to release the lock instead. |
| 1217 | * |
| 1218 | * **Example** (Creating a stream from a ReadableStream) |
| 1219 | * |
| 1220 | * ```ts import.meta.vitest |
| 1221 | * import { Data, Effect, Stream } from "effect" |
| 1222 | * |
| 1223 | * class StreamError extends Data.TaggedError("StreamError")<{ readonly cause: unknown }> {} |
| 1224 | * |
| 1225 | * const readableStream = new ReadableStream({ |
| 1226 | * start(controller) { |
| 1227 | * controller.enqueue(1) |
| 1228 | * controller.enqueue(2) |
| 1229 | * controller.enqueue(3) |
| 1230 | * controller.close() |
| 1231 | * } |
| 1232 | * }) |
| 1233 | * |
| 1234 | * const program = Effect.gen(function*() { |
| 1235 | * const stream = Stream.fromReadableStream({ |
| 1236 | * evaluate: () => readableStream, |
| 1237 | * onError: (cause) => new StreamError({ cause }) |
| 1238 | * }) |
| 1239 | * const values = yield* Stream.runCollect(stream) |
| 1240 | * values // => [1, 2, 3] |
| 1241 | * }) |
| 1242 | * |
| 1243 | * await Effect.runPromise(program) |
| 1244 | * ``` |
| 1245 | * |
| 1246 | * @category constructors |
| 1247 | * @since 2.0.0 |
| 1248 | */ |
| 1249 | export const fromReadableStream = <A, E>( |
| 1250 | options: { |
| 1251 | readonly evaluate: LazyArg<ReadableStream<A>> |
| 1252 | readonly onError: (error: unknown) => E |
| 1253 | readonly releaseLockOnEnd?: boolean | undefined |
| 1254 | } |
| 1255 | ): Stream<A, E> => fromChannel(Channel.fromReadableStream(options)) |
| 1256 | |
| 1257 | /** |
| 1258 | * Creates a stream from an AsyncIterable. |
| 1259 | * |
| 1260 | * **Example** (Creating a stream from an AsyncIterable) |
| 1261 | * |
| 1262 | * ```ts import.meta.vitest |
| 1263 | * import { Data, Effect, Stream } from "effect" |
| 1264 | * |
| 1265 | * class StreamError extends Data.TaggedError("StreamError")<{ readonly cause: unknown }> {} |
| 1266 | * |
| 1267 | * const iterable = (async function*() { |
| 1268 | * yield 1 |
| 1269 | * yield 2 |
| 1270 | * yield 3 |
| 1271 | * })() |
| 1272 | * |
| 1273 | * await Effect.runPromise(Effect.gen(function*() { |
| 1274 | * const stream = Stream.fromAsyncIterable(iterable, (cause) => new StreamError({ cause })) |
| 1275 | * const values = yield* Stream.runCollect(stream) |
| 1276 | * values // => [1, 2, 3] |
| 1277 | * })) |
| 1278 | * |
| 1279 | * ``` |
| 1280 | * |
| 1281 | * @category constructors |
| 1282 | * @since 2.0.0 |
| 1283 | */ |
| 1284 | export const fromAsyncIterable = <A, E>( |
| 1285 | iterable: AsyncIterable<A>, |
| 1286 | onError: (error: unknown) => E |
| 1287 | ): Stream<A, E> => fromChannel(Channel.fromAsyncIterableArray(iterable, onError)) |
| 1288 | |
| 1289 | /** |
| 1290 | * Creates a stream that emits each output of a schedule that does not require input, |
| 1291 | * for as long as the schedule continues. |
| 1292 | * |
| 1293 | * **Example** (Creating a stream from a schedule) |
| 1294 | * |
| 1295 | * ```ts import.meta.vitest |
| 1296 | * import { Effect, Schedule, Stream } from "effect" |
| 1297 | * |
| 1298 | * const program = Effect.gen(function*() { |
| 1299 | * const schedule = Schedule.recurs(3) |
| 1300 | * const stream = Stream.fromSchedule(schedule) |
| 1301 | * const values = yield* Stream.runCollect(stream) |
| 1302 | * values // => [0, 1, 2] |
| 1303 | * }) |
| 1304 | * |
| 1305 | * await Effect.runPromise(program) |
| 1306 | * ``` |
| 1307 | * |
| 1308 | * @category constructors |
| 1309 | * @since 2.0.0 |
| 1310 | */ |
| 1311 | export const fromSchedule = <O, E, R>(schedule: Schedule.Schedule<O, unknown, E, R>): Stream<O, E, R> => |
| 1312 | fromPull( |
| 1313 | Effect.map( |
| 1314 | Schedule.toStepWithSleep(schedule), |
| 1315 | (step) => Pull.catchDone(Effect.map(step(void 0), Arr.of), () => Cause.done()) |
| 1316 | ) |
| 1317 | ) |
| 1318 | |
| 1319 | /** |
| 1320 | * Creates a stream from a PubSub subscription. |
| 1321 | * |
| 1322 | * **When to use** |
| 1323 | * |
| 1324 | * Use when you already have a `PubSub.Subscription` and want to expose its |
| 1325 | * messages as a `Stream`, with `Stream.take` or cancellation controlling how |
| 1326 | * many values are consumed. |
| 1327 | * |
| 1328 | * **Example** (Creating a stream from a PubSub subscription) |
| 1329 | * |
| 1330 | * ```ts import.meta.vitest |
| 1331 | * import { Effect, PubSub, Stream } from "effect" |
| 1332 | * |
| 1333 | * const program = Effect.scoped(Effect.gen(function*() { |
| 1334 | * const pubsub = yield* PubSub.unbounded<number>() |
| 1335 | * const subscription = yield* PubSub.subscribe(pubsub) |
| 1336 | * |
| 1337 | * yield* PubSub.publish(pubsub, 1) |
| 1338 | * yield* PubSub.publish(pubsub, 2) |
| 1339 | * |
| 1340 | * const stream = Stream.fromSubscription(subscription) |
| 1341 | * const values = yield* stream.pipe(Stream.take(2), Stream.runCollect) |
| 1342 | * values // => [1, 2] |
| 1343 | * })) |
| 1344 | * |
| 1345 | * await Effect.runPromise(program) |
| 1346 | * ``` |
| 1347 | * |
| 1348 | * @category constructors |
| 1349 | * @since 4.0.0 |
| 1350 | */ |
| 1351 | export const fromSubscription = <A>(pubsub: PubSub.Subscription<A>): Stream<A> => |
| 1352 | fromChannel(Channel.fromSubscriptionArray(pubsub)) |
| 1353 | |
| 1354 | /** |
| 1355 | * Interface representing an event listener target. |
| 1356 | * |
| 1357 | * @category models |
| 1358 | * @since 3.4.0 |
| 1359 | */ |
| 1360 | export interface EventListener<A = unknown> { |
| 1361 | addEventListener( |
| 1362 | event: string, |
| 1363 | f: (event: A) => void, |
| 1364 | options?: { |
| 1365 | readonly capture?: boolean |
| 1366 | readonly passive?: boolean |
| 1367 | readonly once?: boolean |
| 1368 | readonly signal?: AbortSignal |
| 1369 | } | boolean |
| 1370 | ): void |
| 1371 | removeEventListener( |
| 1372 | event: string, |
| 1373 | f: (event: A) => void, |
| 1374 | options?: { |
| 1375 | readonly capture?: boolean |
| 1376 | } | boolean |
| 1377 | ): void |
| 1378 | } |
| 1379 | |
| 1380 | /** |
| 1381 | * Creates a stream from an event listener. |
| 1382 | * |
| 1383 | * **Example** (Creating a stream from an event listener) |
| 1384 | * |
| 1385 | * ```ts import.meta.vitest |
| 1386 | * import { Effect, Stream } from "effect" |
| 1387 | * |
| 1388 | * class NumberTarget implements Stream.EventListener<number> { |
| 1389 | * addEventListener(event: string, f: (event: number) => void) { |
| 1390 | * if (event === "data") { |
| 1391 | * f(1) |
| 1392 | * f(2) |
| 1393 | * f(3) |
| 1394 | * } |
| 1395 | * } |
| 1396 | * removeEventListener(_event: string, _f: (event: number) => void) {} |
| 1397 | * } |
| 1398 | * |
| 1399 | * await Effect.runPromise(Effect.gen(function*() { |
| 1400 | * const stream = Stream.fromEventListener(new NumberTarget(), "data").pipe( |
| 1401 | * Stream.take(3) |
| 1402 | * ) |
| 1403 | * const values = yield* Stream.runCollect(stream) |
| 1404 | * values // => [1, 2, 3] |
| 1405 | * })) |
| 1406 | * |
| 1407 | * ``` |
| 1408 | * |
| 1409 | * @category constructors |
| 1410 | * @since 3.1.0 |
| 1411 | */ |
| 1412 | export const fromEventListener = <A = unknown>( |
| 1413 | target: EventListener, |
| 1414 | type: string, |
| 1415 | options?: boolean | { |
| 1416 | readonly capture?: boolean |
| 1417 | readonly passive?: boolean |
| 1418 | readonly once?: boolean |
| 1419 | readonly bufferSize?: number | undefined |
| 1420 | } | undefined |
| 1421 | ): Stream<A> => |
| 1422 | callback<A>((queue) => { |
| 1423 | const once = typeof options === "object" && options.once |
| 1424 | |
| 1425 | function emit(event: any) { |
| 1426 | Queue.offerUnsafe(queue, event) |
| 1427 | if (once) Queue.endUnsafe(queue) |
| 1428 | } |
| 1429 | return Effect.acquireRelease( |
| 1430 | Effect.sync(() => target.addEventListener(type, emit, options)), |
| 1431 | () => Effect.sync(() => target.removeEventListener(type, emit, options)) |
| 1432 | ) |
| 1433 | }, { bufferSize: typeof options === "object" ? options.bufferSize : undefined }) |
| 1434 | |
| 1435 | /** |
| 1436 | * Creates a stream by repeatedly applying an effectful step function to a |
| 1437 | * state. |
| 1438 | * |
| 1439 | * **Details** |
| 1440 | * |
| 1441 | * Each `readonly [value, nextState]` result emits `value` and continues with |
| 1442 | * `nextState`; returning `undefined` ends the stream. |
| 1443 | * |
| 1444 | * **Example** (Unfolding stream state) |
| 1445 | * |
| 1446 | * ```ts import.meta.vitest |
| 1447 | * import { Effect, Stream } from "effect" |
| 1448 | * |
| 1449 | * const program = Effect.gen(function*() { |
| 1450 | * const stream = Stream.unfold(1, (n) => Effect.succeed([n, n + 1] as const)) |
| 1451 | * const values = yield* Stream.runCollect(stream.pipe(Stream.take(5))) |
| 1452 | * values // => [ 1, 2, 3, 4, 5 ] |
| 1453 | * }) |
| 1454 | * |
| 1455 | * await Effect.runPromise(program) |
| 1456 | * ``` |
| 1457 | * |
| 1458 | * @category constructors |
| 1459 | * @since 2.0.0 |
| 1460 | */ |
| 1461 | export const unfold = <S, A, E, R>( |
| 1462 | s: S, |
| 1463 | f: (s: S) => Effect.Effect<readonly [A, S] | undefined, E, R> |
| 1464 | ): Stream<A, E, R> => |
| 1465 | fromPull(Effect.sync(() => { |
| 1466 | let state = s |
| 1467 | return Effect.flatMap(Effect.suspend(() => f(state)), (next) => { |
| 1468 | if (next === undefined) return Cause.done() |
| 1469 | state = next[1] |
| 1470 | return Effect.succeed(Arr.of(next[0])) |
| 1471 | }) |
| 1472 | })) |
| 1473 | |
| 1474 | /** |
| 1475 | * Creates a stream by repeatedly evaluating an effectful page function. |
| 1476 | * |
| 1477 | * **When to use** |
| 1478 | * |
| 1479 | * Use to consume paginated APIs where each step returns a batch of values |
| 1480 | * together with an optional next state. |
| 1481 | * |
| 1482 | * **Details** |
| 1483 | * |
| 1484 | * This is similar to {@link unfold}, but each step can emit zero or more values |
| 1485 | * and independently decide whether another state should be requested. |
| 1486 | * |
| 1487 | * **Example** (Paginating stream state) |
| 1488 | * |
| 1489 | * ```ts import.meta.vitest |
| 1490 | * import { Effect, Option, Stream } from "effect" |
| 1491 | * |
| 1492 | * const stream = Stream.paginate(0, (n: number) => |
| 1493 | * Effect.succeed( |
| 1494 | * [ |
| 1495 | * [n], |
| 1496 | * n < 3 ? Option.some(n + 1) : Option.none<number>() |
| 1497 | * ] as const |
| 1498 | * )) |
| 1499 | * |
| 1500 | * await Effect.runPromise(Stream.runCollect(stream)) // => [0, 1, 2, 3] |
| 1501 | * ``` |
| 1502 | * |
| 1503 | * @category constructors |
| 1504 | * @since 2.0.0 |
| 1505 | */ |
| 1506 | export const paginate = <S, A, E = never, R = never>( |
| 1507 | s: S, |
| 1508 | f: ( |
| 1509 | s: S |
| 1510 | ) => Effect.Effect<readonly [ReadonlyArray<A>, Option.Option<S>], E, R> |
| 1511 | ): Stream<A, E, R> => |
| 1512 | fromPull(Effect.sync(() => { |
| 1513 | let state = s |
| 1514 | let done = false |
| 1515 | return Effect.suspend(function loop(): Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E, void, R> { |
| 1516 | if (done) return Cause.done() |
| 1517 | return Effect.flatMap(f(state), ([a, s]) => { |
| 1518 | if (Option.isNone(s)) { |
| 1519 | done = true |
| 1520 | } else { |
| 1521 | state = s.value |
| 1522 | } |
| 1523 | if (!Arr.isReadonlyArrayNonEmpty(a)) return loop() |
| 1524 | return Effect.succeed(a) |
| 1525 | }) |
| 1526 | }) |
| 1527 | })) |
| 1528 | |
| 1529 | /** |
| 1530 | * Creates an infinite stream by repeatedly applying a function to a seed value. |
| 1531 | * |
| 1532 | * **Example** (Iterating from a seed value) |
| 1533 | * |
| 1534 | * ```ts import.meta.vitest |
| 1535 | * import { Effect, Stream } from "effect" |
| 1536 | * |
| 1537 | * const stream = Stream.iterate(1, (n) => n + 1).pipe(Stream.take(3)) |
| 1538 | * |
| 1539 | * const program = Effect.gen(function* () { |
| 1540 | * const values = yield* Stream.runCollect(stream) |
| 1541 | * values // => [ 1, 2, 3 ] |
| 1542 | * }) |
| 1543 | * |
| 1544 | * await Effect.runPromise(program) |
| 1545 | * ``` |
| 1546 | * |
| 1547 | * @category constructors |
| 1548 | * @since 2.0.0 |
| 1549 | */ |
| 1550 | export const iterate = <A>(value: A, next: (value: A) => A): Stream<A> => |
| 1551 | unfold(value, (a) => Effect.succeed([a, next(a)])) |
| 1552 | |
| 1553 | /** |
| 1554 | * Constructs a stream from a range of integers, including both endpoints. |
| 1555 | * |
| 1556 | * **Details** |
| 1557 | * |
| 1558 | * If the provided `min` is greater than `max`, the stream will not emit any |
| 1559 | * values. Finite fractional `chunkSize` values are rounded down, while `NaN` |
| 1560 | * and non-positive values are treated as `1`. |
| 1561 | * |
| 1562 | * **Example** (Creating a numeric range) |
| 1563 | * |
| 1564 | * ```ts import.meta.vitest |
| 1565 | * import { Effect, Stream } from "effect" |
| 1566 | * |
| 1567 | * const program = Effect.gen(function*() { |
| 1568 | * const values = yield* Stream.range(1, 5).pipe(Stream.runCollect) |
| 1569 | * values // => [ 1, 2, 3, 4, 5 ] |
| 1570 | * }) |
| 1571 | * |
| 1572 | * await Effect.runPromise(program) |
| 1573 | * ``` |
| 1574 | * |
| 1575 | * @category constructors |
| 1576 | * @since 2.0.0 |
| 1577 | */ |
| 1578 | export const range = ( |
| 1579 | min: number, |
| 1580 | max: number, |
| 1581 | chunkSize = Channel.DefaultChunkSize |
| 1582 | ): Stream<number> => |
| 1583 | min > max ? empty : fromPull(Effect.sync(() => { |
| 1584 | const size = Count.normalizeNonEmpty(chunkSize) |
| 1585 | let start = min |
| 1586 | let done = false |
| 1587 | return Effect.suspend(() => { |
| 1588 | if (done) return Cause.done() |
| 1589 | const remaining = max - start + 1 |
| 1590 | if (remaining > size) { |
| 1591 | const chunk = Arr.range(start, start + size - 1) |
| 1592 | start += size |
| 1593 | return Effect.succeed(chunk) |
| 1594 | } |
| 1595 | const chunk = Arr.range(start, start + remaining - 1) |
| 1596 | done = true |
| 1597 | return Effect.succeed(chunk) |
| 1598 | }) |
| 1599 | })) |
| 1600 | |
| 1601 | /** |
| 1602 | * The stream that never produces any value or fails with any error. |
| 1603 | * |
| 1604 | * **Example** (Creating a never-ending stream) |
| 1605 | * |
| 1606 | * ```ts import.meta.vitest |
| 1607 | * import { Effect, Stream } from "effect" |
| 1608 | * |
| 1609 | * const program = Stream.never.pipe( |
| 1610 | * Stream.take(0), |
| 1611 | * Stream.runCollect |
| 1612 | * ) |
| 1613 | * |
| 1614 | * await Effect.runPromise(program) // => [] |
| 1615 | * ``` |
| 1616 | * |
| 1617 | * @category constructors |
| 1618 | * @since 2.0.0 |
| 1619 | */ |
| 1620 | export const never: Stream<never> = fromChannel(Channel.never) |
| 1621 | |
| 1622 | /** |
| 1623 | * Creates a stream produced from an `Effect`. |
| 1624 | * |
| 1625 | * **Example** (Unwrapping a stream effect) |
| 1626 | * |
| 1627 | * ```ts import.meta.vitest |
| 1628 | * import { Effect, Stream } from "effect" |
| 1629 | * |
| 1630 | * const effect = Effect.succeed(Stream.make(1, 2, 3)) |
| 1631 | * |
| 1632 | * const stream = Stream.unwrap(effect) |
| 1633 | * |
| 1634 | * const program = Effect.gen(function*() { |
| 1635 | * const chunk = yield* Stream.runCollect(stream) |
| 1636 | * chunk // => [ 1, 2, 3 ] |
| 1637 | * }) |
| 1638 | * await Effect.runPromise(program) |
| 1639 | * ``` |
| 1640 | * |
| 1641 | * @category constructors |
| 1642 | * @since 2.0.0 |
| 1643 | */ |
| 1644 | export const unwrap = <A, E2, R2, E, R>( |
| 1645 | effect: Effect.Effect<Stream<A, E2, R2>, E, R> |
| 1646 | ): Stream<A, E | E2, R2 | Exclude<R, Scope.Scope>> => fromChannel(Channel.unwrap(Effect.map(effect, toChannel))) |
| 1647 | |
| 1648 | /** |
| 1649 | * Runs a stream that requires `Scope` in a managed scope, ensuring its |
| 1650 | * finalizers are run when the stream completes. |
| 1651 | * |
| 1652 | * **Example** (Scoping a stream) |
| 1653 | * |
| 1654 | * ```ts import.meta.vitest |
| 1655 | * import { Effect, Stream } from "effect" |
| 1656 | * |
| 1657 | * const events: Array<string> = [] |
| 1658 | * const stream = Stream.scoped( |
| 1659 | * Stream.fromEffect( |
| 1660 | * Effect.acquireRelease( |
| 1661 | * Effect.sync(() => { |
| 1662 | * events.push("acquire") |
| 1663 | * return "resource" |
| 1664 | * }), |
| 1665 | * () => Effect.sync(() => events.push("release")) |
| 1666 | * ) |
| 1667 | * ) |
| 1668 | * ) |
| 1669 | * |
| 1670 | * await Effect.runPromise(Stream.runCollect(stream)) // => ["resource"] |
| 1671 | * events // => ["acquire", "release"] |
| 1672 | * ``` |
| 1673 | * |
| 1674 | * @category resource management |
| 1675 | * @since 2.0.0 |
| 1676 | */ |
| 1677 | export const scoped = <A, E, R>( |
| 1678 | self: Stream<A, E, R> |
| 1679 | ): Stream<A, E, Exclude<R, Scope.Scope>> => fromChannel(Channel.scoped(self.channel)) |
| 1680 | |
| 1681 | /** |
| 1682 | * Transforms the elements of this stream using the supplied function. |
| 1683 | * |
| 1684 | * **Example** (Mapping stream values) |
| 1685 | * |
| 1686 | * ```ts import.meta.vitest |
| 1687 | * import { Effect, Option, Stream } from "effect" |
| 1688 | * |
| 1689 | * const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.map((n, i) => n + i)) |
| 1690 | * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 3, 5] |
| 1691 | * ``` |
| 1692 | * |
| 1693 | * @category mapping |
| 1694 | * @since 2.0.0 |
| 1695 | */ |
| 1696 | export const map: { |
| 1697 | <A, B>(f: (a: A, i: number) => B): <E, R>(self: Stream<A, E, R>) => Stream<B, E, R> |
| 1698 | <A, E, R, B>(self: Stream<A, E, R>, f: (a: A, i: number) => B): Stream<B, E, R> |
| 1699 | } = dual(2, <A, E, R, B>(self: Stream<A, E, R>, f: (a: A, i: number) => B): Stream<B, E, R> => |
| 1700 | suspend(() => { |
| 1701 | let i = 0 |
| 1702 | return fromChannel(Channel.map( |
| 1703 | self.channel, |
| 1704 | Arr.map((o) => f(o, i++)) |
| 1705 | )) |
| 1706 | })) |
| 1707 | |
| 1708 | /** |
| 1709 | * Replaces every element of the stream with the provided constant value. |
| 1710 | * |
| 1711 | * **Example** (Replacing stream elements) |
| 1712 | * |
| 1713 | * ```ts import.meta.vitest |
| 1714 | * import { Effect, Stream } from "effect" |
| 1715 | * |
| 1716 | * const program = Effect.gen(function*() { |
| 1717 | * const values = yield* Stream.make(1, 2, 3).pipe( |
| 1718 | * Stream.as("x"), |
| 1719 | * Stream.runCollect |
| 1720 | * ) |
| 1721 | * values // => [ 'x', 'x', 'x' ] |
| 1722 | * }) |
| 1723 | * |
| 1724 | * await Effect.runPromise(program) |
| 1725 | * ``` |
| 1726 | * |
| 1727 | * @see {@link map} for deriving the replacement value from each element |
| 1728 | * |
| 1729 | * @category mapping |
| 1730 | * @since 4.0.0 |
| 1731 | */ |
| 1732 | export const as: { |
| 1733 | <B>(value: B): <A, E, R>(self: Stream<A, E, R>) => Stream<B, E, R> |
| 1734 | <A, E, R, B>(self: Stream<A, E, R>, value: B): Stream<B, E, R> |
| 1735 | } = dual(2, <A, E, R, B>(self: Stream<A, E, R>, value: B): Stream<B, E, R> => map(self, () => value)) |
| 1736 | |
| 1737 | /** |
| 1738 | * Maps both the failure and success channels of a stream. |
| 1739 | * |
| 1740 | * **Example** (Mapping both the failure and success channels of a stream) |
| 1741 | * |
| 1742 | * ```ts import.meta.vitest |
| 1743 | * import { Effect, Stream } from "effect" |
| 1744 | * |
| 1745 | * const mapper = { |
| 1746 | * onElement: (value: number) => value * 2, |
| 1747 | * onError: (error: string) => `error: ${error}` |
| 1748 | * } |
| 1749 | * |
| 1750 | * const program = Effect.gen(function*() { |
| 1751 | * const success = yield* Stream.make(1, 2).pipe( |
| 1752 | * Stream.mapBoth(mapper), |
| 1753 | * Stream.runCollect |
| 1754 | * ) |
| 1755 | * success // => [ 2, 4 ] |
| 1756 | * |
| 1757 | * const failure = yield* Stream.fail("boom").pipe( |
| 1758 | * Stream.mapBoth(mapper), |
| 1759 | * Stream.catch((error: string) => Stream.succeed(error)), |
| 1760 | * Stream.runCollect |
| 1761 | * ) |
| 1762 | * failure // => [ 'error: boom' ] |
| 1763 | * }) |
| 1764 | * |
| 1765 | * await Effect.runPromise(program) |
| 1766 | * ``` |
| 1767 | * |
| 1768 | * @category mapping |
| 1769 | * @since 2.0.0 |
| 1770 | */ |
| 1771 | export const mapBoth: { |
| 1772 | <A, A2, E, E2>( |
| 1773 | options: { readonly onElement: (a: A) => A2; readonly onError: (e: E) => E2 } |
| 1774 | ): <R>(self: Stream<A, E, R>) => Stream<A2, E2, R> |
| 1775 | <A, E, R, A2, E2>( |
| 1776 | self: Stream<A, E, R>, |
| 1777 | options: { readonly onElement: (a: A) => A2; readonly onError: (e: E) => E2 } |
| 1778 | ): Stream<A2, E2, R> |
| 1779 | } = dual(2, <A, E, R, A2, E2>( |
| 1780 | self: Stream<A, E, R>, |
| 1781 | options: { readonly onElement: (a: A) => A2; readonly onError: (e: E) => E2 } |
| 1782 | ): Stream<A2, E2, R> => |
| 1783 | self.pipe( |
| 1784 | map(options.onElement), |
| 1785 | mapError(options.onError) |
| 1786 | )) |
| 1787 | |
| 1788 | /** |
| 1789 | * Transforms each emitted chunk using the provided function, which receives the chunk and its index. |
| 1790 | * |
| 1791 | * **Example** (Mapping stream chunks) |
| 1792 | * |
| 1793 | * ```ts import.meta.vitest |
| 1794 | * import { Array, Effect, Stream } from "effect" |
| 1795 | * |
| 1796 | * const program = Effect.gen(function*() { |
| 1797 | * const result = yield* Stream.make(1, 2, 3, 4).pipe( |
| 1798 | * Stream.rechunk(2), |
| 1799 | * Stream.mapArray((chunk, index) => Array.map(chunk, (n) => n + index)), |
| 1800 | * Stream.runCollect |
| 1801 | * ) |
| 1802 | * result // => [ 1, 2, 4, 5 ] |
| 1803 | * }) |
| 1804 | * |
| 1805 | * await Effect.runPromise(program) |
| 1806 | * ``` |
| 1807 | * |
| 1808 | * @category mapping |
| 1809 | * @since 4.0.0 |
| 1810 | */ |
| 1811 | export const mapArray: { |
| 1812 | <A, B>( |
| 1813 | f: (a: Arr.NonEmptyReadonlyArray<A>, i: number) => Arr.NonEmptyReadonlyArray<B> |
| 1814 | ): <E, R>(self: Stream<A, E, R>) => Stream<B, E, R> |
| 1815 | <A, E, R, B>( |
| 1816 | self: Stream<A, E, R>, |
| 1817 | f: (a: Arr.NonEmptyReadonlyArray<A>, i: number) => Arr.NonEmptyReadonlyArray<B> |
| 1818 | ): Stream<B, E, R> |
| 1819 | } = dual(2, <A, E, R, B>( |
| 1820 | self: Stream<A, E, R>, |
| 1821 | f: (a: Arr.NonEmptyReadonlyArray<A>, i: number) => Arr.NonEmptyReadonlyArray<B> |
| 1822 | ): Stream<B, E, R> => fromChannel(Channel.map(self.channel, f))) |
| 1823 | |
| 1824 | /** |
| 1825 | * Maps over elements of the stream with the specified effectful function. |
| 1826 | * |
| 1827 | * **When to use** |
| 1828 | * |
| 1829 | * Use when each stream element transformation needs an Effect, service |
| 1830 | * dependency, failure channel, or configured concurrency. |
| 1831 | * |
| 1832 | * **Example** (Effectfully mapping stream values) |
| 1833 | * |
| 1834 | * ```ts import.meta.vitest |
| 1835 | * import { Effect, Stream } from "effect" |
| 1836 | * |
| 1837 | * const events: Array<string> = [] |
| 1838 | * const stream = Stream.make(1, 2, 3) |
| 1839 | * |
| 1840 | * const mappedStream = stream.pipe( |
| 1841 | * Stream.mapEffect((n) => |
| 1842 | * Effect.sync(() => { |
| 1843 | * events.push(`Processing: ${n}`) |
| 1844 | * return n * 2 |
| 1845 | * }) |
| 1846 | * ) |
| 1847 | * ) |
| 1848 | * |
| 1849 | * const program = Effect.gen(function*() { |
| 1850 | * const result = yield* Stream.runCollect(mappedStream) |
| 1851 | * result // => [2, 4, 6] |
| 1852 | * }) |
| 1853 | * |
| 1854 | * await Effect.runPromise(program) |
| 1855 | * events // => ["Processing: 1", "Processing: 2", "Processing: 3"] |
| 1856 | * ``` |
| 1857 | * |
| 1858 | * @category mapping |
| 1859 | * @since 2.0.0 |
| 1860 | */ |
| 1861 | export const mapEffect: { |
| 1862 | <A, A2, E2, R2>( |
| 1863 | f: (a: A, i: number) => Effect.Effect<A2, E2, R2>, |
| 1864 | options?: { |
| 1865 | readonly concurrency?: Concurrency | undefined |
| 1866 | readonly unordered?: boolean | undefined |
| 1867 | } | undefined |
| 1868 | ): <E, R>(self: Stream<A, E, R>) => Stream<A2, E2 | E, R2 | R> |
| 1869 | <A, E, R, A2, E2, R2>( |
| 1870 | self: Stream<A, E, R>, |
| 1871 | f: (a: A, i: number) => Effect.Effect<A2, E2, R2>, |
| 1872 | options?: { |
| 1873 | readonly concurrency?: Concurrency | undefined |
| 1874 | readonly unordered?: boolean | undefined |
| 1875 | } | undefined |
| 1876 | ): Stream<A2, E | E2, R | R2> |
| 1877 | } = dual((args) => isStream(args[0]), <A, E, R, A2, E2, R2>( |
| 1878 | self: Stream<A, E, R>, |
| 1879 | f: (a: A, i: number) => Effect.Effect<A2, E2, R2>, |
| 1880 | options?: { |
| 1881 | readonly concurrency?: Concurrency | undefined |
| 1882 | readonly unordered?: boolean | undefined |
| 1883 | } | undefined |
| 1884 | ): Stream<A2, E | E2, R | R2> => |
| 1885 | self.channel.pipe( |
| 1886 | Channel.flattenArray, |
| 1887 | Channel.mapEffect(f, options), |
| 1888 | Channel.map(Arr.of), |
| 1889 | fromChannel |
| 1890 | )) |
| 1891 | |
| 1892 | /** |
| 1893 | * Flattens a stream of `Effect` values into a stream of their results. |
| 1894 | * |
| 1895 | * **When to use** |
| 1896 | * |
| 1897 | * Use when stream elements already are effects and their successes should become |
| 1898 | * stream elements while their failures enter the stream error channel. |
| 1899 | * |
| 1900 | * **Example** (Flattening a stream of Effect values into a stream of their results) |
| 1901 | * |
| 1902 | * ```ts import.meta.vitest |
| 1903 | * import { Effect, Stream } from "effect" |
| 1904 | * |
| 1905 | * const stream = Stream.make(Effect.succeed(1), Effect.succeed(2), Effect.succeed(3)) |
| 1906 | * |
| 1907 | * const program = Effect.gen(function*() { |
| 1908 | * const result = yield* Stream.runCollect(stream.pipe(Stream.flattenEffect())) |
| 1909 | * result // => [ 1, 2, 3 ] |
| 1910 | * }) |
| 1911 | * |
| 1912 | * await Effect.runPromise(program) |
| 1913 | * ``` |
| 1914 | * |
| 1915 | * @category sequencing |
| 1916 | * @since 2.0.0 |
| 1917 | */ |
| 1918 | export const flattenEffect: < |
| 1919 | Arg extends Stream<Effect.Effect<any, any, any>, any, any> | { |
| 1920 | readonly concurrency?: Concurrency | undefined |
| 1921 | readonly unordered?: boolean | undefined |
| 1922 | } | undefined = { |
| 1923 | readonly concurrency?: Concurrency | undefined |
| 1924 | readonly unordered?: boolean | undefined |
| 1925 | } |
| 1926 | >( |
| 1927 | selfOrOptions?: Arg, |
| 1928 | options?: { |
| 1929 | readonly concurrency?: Concurrency | undefined |
| 1930 | readonly unordered?: boolean | undefined |
| 1931 | } | undefined |
| 1932 | ) => [Arg] extends [Stream<Effect.Effect<infer _A, infer _EX, infer _RX>, infer _E, infer _R>] ? |
| 1933 | Stream<_A, _EX | _E, _RX | _R> |
| 1934 | : <A, EX, RX, E, R>(self: Stream<Effect.Effect<A, EX, RX>, E, R>) => Stream<A, EX | E, RX | R> = dual( |
| 1935 | (args) => isStream(args[0]), |
| 1936 | <A, E, R, EX, RX>( |
| 1937 | self: Stream<Effect.Effect<A, EX, RX>, E, R>, |
| 1938 | options?: { |
| 1939 | readonly concurrency?: Concurrency | undefined |
| 1940 | readonly unordered?: boolean | undefined |
| 1941 | } | undefined |
| 1942 | ): Stream<A, EX | E, RX | R> => mapEffect(self, identity, options) |
| 1943 | ) |
| 1944 | |
| 1945 | /** |
| 1946 | * Maps over non-empty array chunks emitted by the stream effectfully. |
| 1947 | * |
| 1948 | * **When to use** |
| 1949 | * |
| 1950 | * Use when transformation needs to see and replace each non-empty emitted chunk |
| 1951 | * effectfully instead of mapping individual stream elements. |
| 1952 | * |
| 1953 | * **Example** (Effectfully mapping stream chunks) |
| 1954 | * |
| 1955 | * ```ts import.meta.vitest |
| 1956 | * import { Array, Effect, Stream } from "effect" |
| 1957 | * |
| 1958 | * const program = Effect.gen(function*() { |
| 1959 | * const result = yield* Stream.fromArray([1, 2, 3, 4]).pipe( |
| 1960 | * Stream.rechunk(2), |
| 1961 | * Stream.mapArrayEffect((chunk, index) => |
| 1962 | * Effect.succeed(Array.map(chunk, (n) => n + index * 10)) |
| 1963 | * ), |
| 1964 | * Stream.runCollect |
| 1965 | * ) |
| 1966 | * result // => [ 1, 2, 13, 14 ] |
| 1967 | * }) |
| 1968 | * |
| 1969 | * await Effect.runPromise(program) |
| 1970 | * ``` |
| 1971 | * |
| 1972 | * @category mapping |
| 1973 | * @since 4.0.0 |
| 1974 | */ |
| 1975 | export const mapArrayEffect: { |
| 1976 | <A, B, E2, R2>( |
| 1977 | f: (a: Arr.NonEmptyReadonlyArray<A>, i: number) => Effect.Effect<Arr.NonEmptyReadonlyArray<B>, E2, R2> |
| 1978 | ): <E, R>(self: Stream<A, E, R>) => Stream<B, E | E2, R | R2> |
| 1979 | <A, E, R, B, E2, R2>( |
| 1980 | self: Stream<A, E, R>, |
| 1981 | f: (a: Arr.NonEmptyReadonlyArray<A>, i: number) => Effect.Effect<Arr.NonEmptyReadonlyArray<B>, E2, R2> |
| 1982 | ): Stream<B, E | E2, R | R2> |
| 1983 | } = dual(2, <A, E, R, B, E2, R2>( |
| 1984 | self: Stream<A, E, R>, |
| 1985 | f: (a: Arr.NonEmptyReadonlyArray<A>, i: number) => Effect.Effect<Arr.NonEmptyReadonlyArray<B>, E2, R2> |
| 1986 | ): Stream<B, E | E2, R | R2> => fromChannel(Channel.mapEffect(self.channel, f))) |
| 1987 | |
| 1988 | /** |
| 1989 | * Lifts failures and successes into a `Result`, yielding a stream that cannot fail. |
| 1990 | * |
| 1991 | * **Details** |
| 1992 | * |
| 1993 | * The stream ends after the first failure, emitting a `Result.fail` value. |
| 1994 | * |
| 1995 | * **Example** (Converting failures to results) |
| 1996 | * |
| 1997 | * ```ts import.meta.vitest |
| 1998 | * import { Effect, Result, Stream } from "effect" |
| 1999 | * |
| 2000 | * const program = Effect.gen(function*() { |
| 2001 | * const results = yield* Stream.make(1, 2).pipe( |
| 2002 | * Stream.concat(Stream.fail("boom")), |
| 2003 | * Stream.result, |
| 2004 | * Stream.map(Result.match({ |
| 2005 | * onFailure: (error) => `failure: ${error}`, |
| 2006 | * onSuccess: (value) => `success: ${value}` |
| 2007 | * })), |
| 2008 | * Stream.runCollect |
| 2009 | * ) |
| 2010 | * results // => [ 'success: 1', 'success: 2', 'failure: boom' ] |
| 2011 | * }) |
| 2012 | * |
| 2013 | * await Effect.runPromise(program) |
| 2014 | * ``` |
| 2015 | * |
| 2016 | * @category error handling |
| 2017 | * @since 4.0.0 |
| 2018 | */ |
| 2019 | export const result = <A, E, R>(self: Stream<A, E, R>): Stream<Result.Result<A, E>, never, R> => |
| 2020 | self.pipe( |
| 2021 | map(Result.succeed), |
| 2022 | catch_((e) => succeed(Result.fail(e))) |
| 2023 | ) |
| 2024 | |
| 2025 | /** |
| 2026 | * Runs the provided effect for each element while preserving the elements. |
| 2027 | * |
| 2028 | * **Example** (Tapping stream values) |
| 2029 | * |
| 2030 | * ```ts import.meta.vitest |
| 2031 | * import { Effect, Stream } from "effect" |
| 2032 | * |
| 2033 | * const events: Array<string> = [] |
| 2034 | * const program = Effect.gen(function*() { |
| 2035 | * const result = yield* Stream.fromArray([1, 2, 3]).pipe( |
| 2036 | * Stream.tap((n) => Effect.sync(() => events.push(`before mapping: ${n}`))), |
| 2037 | * Stream.map((n) => n * 2), |
| 2038 | * Stream.tap((n) => Effect.sync(() => events.push(`after mapping: ${n}`))), |
| 2039 | * Stream.runCollect |
| 2040 | * ) |
| 2041 | * |
| 2042 | * result // => [2, 4, 6] |
| 2043 | * }) |
| 2044 | * |
| 2045 | * await Effect.runPromise(program) |
| 2046 | * events // => ["before mapping: 1", "after mapping: 2", "before mapping: 2", "after mapping: 4", "before mapping: 3", "after mapping: 6"] |
| 2047 | * ``` |
| 2048 | * |
| 2049 | * @category sequencing |
| 2050 | * @since 2.0.0 |
| 2051 | */ |
| 2052 | export const tap: { |
| 2053 | <A, X, E2, R2>( |
| 2054 | f: (a: NoInfer<A>) => Effect.Effect<X, E2, R2>, |
| 2055 | options?: { |
| 2056 | readonly concurrency?: Concurrency | undefined |
| 2057 | } | undefined |
| 2058 | ): <E, R>(self: Stream<A, E, R>) => Stream<A, E2 | E, R2 | R> |
| 2059 | <A, E, R, X, E2, R2>( |
| 2060 | self: Stream<A, E, R>, |
| 2061 | f: (a: NoInfer<A>) => Effect.Effect<X, E2, R2>, |
| 2062 | options?: { |
| 2063 | readonly concurrency?: Concurrency | undefined |
| 2064 | } | undefined |
| 2065 | ): Stream<A, E | E2, R | R2> |
| 2066 | } = dual((args) => isStream(args[0]), <A, E, R, X, E2, R2>( |
| 2067 | self: Stream<A, E, R>, |
| 2068 | f: (a: NoInfer<A>) => Effect.Effect<X, E2, R2>, |
| 2069 | options?: { |
| 2070 | readonly concurrency?: Concurrency | undefined |
| 2071 | } | undefined |
| 2072 | ): Stream<A, E | E2, R | R2> => |
| 2073 | mapEffect( |
| 2074 | self, |
| 2075 | (a) => Effect.as(f(a), a), |
| 2076 | options |
| 2077 | )) |
| 2078 | |
| 2079 | /** |
| 2080 | * Returns a stream that effectfully "peeks" at elements and failures. |
| 2081 | * |
| 2082 | * **Example** (Tapping values and errors) |
| 2083 | * |
| 2084 | * ```ts import.meta.vitest |
| 2085 | * import { Effect, Stream } from "effect" |
| 2086 | * |
| 2087 | * const events: Array<string> = [] |
| 2088 | * const program = Effect.gen(function*() { |
| 2089 | * const stream = Stream.make(1, 2).pipe( |
| 2090 | * Stream.concat(Stream.fail("boom")), |
| 2091 | * Stream.tapBoth({ |
| 2092 | * onElement: (value) => Effect.sync(() => events.push(`seen: ${value}`)), |
| 2093 | * onError: (error) => Effect.sync(() => events.push(`error: ${error}`)) |
| 2094 | * }), |
| 2095 | * Stream.catch(() => Stream.make(3)) |
| 2096 | * ) |
| 2097 | * const result = yield* Stream.runCollect(stream) |
| 2098 | * result // => [1, 2, 3] |
| 2099 | * }) |
| 2100 | * |
| 2101 | * await Effect.runPromise(program) |
| 2102 | * events // => ["seen: 1", "seen: 2", "error: boom"] |
| 2103 | * ``` |
| 2104 | * |
| 2105 | * @category sequencing |
| 2106 | * @since 2.0.0 |
| 2107 | */ |
| 2108 | export const tapBoth: { |
| 2109 | <A, E, X, E2, R2, Y, E3, R3>( |
| 2110 | options: { |
| 2111 | readonly onElement: (a: NoInfer<A>) => Effect.Effect<X, E2, R2> |
| 2112 | readonly onError: (a: NoInfer<E>) => Effect.Effect<Y, E3, R3> |
| 2113 | readonly concurrency?: Concurrency | undefined |
| 2114 | } |
| 2115 | ): <R>(self: Stream<A, E, R>) => Stream<A, E | E2 | E3, R | R2 | R3> |
| 2116 | <A, E, R, X, E2, R2, Y, E3, R3>( |
| 2117 | self: Stream<A, E, R>, |
| 2118 | options: { |
| 2119 | readonly onElement: (a: NoInfer<A>) => Effect.Effect<X, E2, R2> |
| 2120 | readonly onError: (a: NoInfer<E>) => Effect.Effect<Y, E3, R3> |
| 2121 | readonly concurrency?: Concurrency | undefined |
| 2122 | } |
| 2123 | ): Stream<A, E | E2 | E3, R | R2 | R3> |
| 2124 | } = dual(2, <A, E, R, X, E2, R2, Y, E3, R3>( |
| 2125 | self: Stream<A, E, R>, |
| 2126 | options: { |
| 2127 | readonly onElement: (a: NoInfer<A>) => Effect.Effect<X, E2, R2> |
| 2128 | readonly onError: (a: NoInfer<E>) => Effect.Effect<Y, E3, R3> |
| 2129 | readonly concurrency?: Concurrency | undefined |
| 2130 | } |
| 2131 | ): Stream<A, E | E2 | E3, R | R2 | R3> => |
| 2132 | self.pipe( |
| 2133 | tapError(options.onError), |
| 2134 | tap(options.onElement, { concurrency: options.concurrency }) |
| 2135 | )) |
| 2136 | |
| 2137 | /** |
| 2138 | * Runs a sink for all stream elements while still emitting them downstream. |
| 2139 | * |
| 2140 | * **Example** (Tapping values with a sink) |
| 2141 | * |
| 2142 | * ```ts import.meta.vitest |
| 2143 | * import { Effect, Ref, Sink, Stream } from "effect" |
| 2144 | * |
| 2145 | * const program = Effect.gen(function*() { |
| 2146 | * const seen = yield* Ref.make<Array<number>>([]) |
| 2147 | * const sink = Sink.forEach((value: number) => |
| 2148 | * Ref.update(seen, (items) => [...items, value]) |
| 2149 | * ) |
| 2150 | * const result = yield* Stream.make(1, 2, 3).pipe( |
| 2151 | * Stream.tapSink(sink), |
| 2152 | * Stream.runCollect |
| 2153 | * ) |
| 2154 | * const tapped = yield* Ref.get(seen) |
| 2155 | * tapped // => [ 1, 2, 3 ] |
| 2156 | * result // => [ 1, 2, 3 ] |
| 2157 | * }) |
| 2158 | * |
| 2159 | * await Effect.runPromise(program) |
| 2160 | * ``` |
| 2161 | * |
| 2162 | * @category sequencing |
| 2163 | * @since 2.0.0 |
| 2164 | */ |
| 2165 | export const tapSink: { |
| 2166 | <A, E2, R2>(sink: Sink.Sink<unknown, A, unknown, E2, R2>): <E, R>(self: Stream<A, E, R>) => Stream<A, E2 | E, R2 | R> |
| 2167 | <A, E, R, E2, R2>(self: Stream<A, E, R>, sink: Sink.Sink<unknown, A, unknown, E2, R2>): Stream<A, E | E2, R | R2> |
| 2168 | } = dual( |
| 2169 | 2, |
| 2170 | <A, E, R, E2, R2>( |
| 2171 | self: Stream<A, E, R>, |
| 2172 | sink: Sink.Sink<unknown, A, unknown, E2, R2> |
| 2173 | ): Stream<A, E | E2, R | R2> => |
| 2174 | transformPullBracket( |
| 2175 | self, |
| 2176 | Effect.fnUntraced(function*(pull, _, scope) { |
| 2177 | const upstreamLatch = Latch.makeUnsafe() |
| 2178 | const sinkLatch = Latch.makeUnsafe() |
| 2179 | let chunk: Arr.NonEmptyReadonlyArray<A> | undefined = undefined |
| 2180 | let causeSink: Cause.Cause<E2> | undefined = undefined |
| 2181 | let sinkDone = false |
| 2182 | let streamDone = false |
| 2183 | |
| 2184 | const sinkUpstream = upstreamLatch.whenOpen(Effect.suspend(() => { |
| 2185 | if (chunk) { |
| 2186 | const arr = chunk! |
| 2187 | chunk = undefined |
| 2188 | if (!streamDone) upstreamLatch.closeUnsafe() |
| 2189 | return Effect.as(sinkLatch.open, arr) |
| 2190 | } |
| 2191 | return Cause.done() |
| 2192 | })) |
| 2193 | |
| 2194 | yield* Effect.suspend(() => sink.transform(sinkUpstream, scope)).pipe( |
| 2195 | (eff) => |
| 2196 | Effect.onExitPrimitive(eff, (exit) => { |
| 2197 | sinkDone = true |
| 2198 | if (Exit.isFailure(exit)) { |
| 2199 | causeSink = exit.cause |
| 2200 | } |
| 2201 | return sinkLatch.open |
| 2202 | }, true), |
| 2203 | Effect.forkIn(scope) |
| 2204 | ) |
| 2205 | |
| 2206 | const pullAndOffer = pull.pipe( |
| 2207 | Effect.flatMap((chunk_) => { |
| 2208 | chunk = chunk_ |
| 2209 | sinkLatch.closeUnsafe() |
| 2210 | upstreamLatch.openUnsafe() |
| 2211 | return Effect.as(sinkLatch.await, chunk_) |
| 2212 | }), |
| 2213 | Pull.catchDone(() => { |
| 2214 | streamDone = true |
| 2215 | sinkLatch.closeUnsafe() |
| 2216 | upstreamLatch.openUnsafe() |
| 2217 | return Effect.flatMap(sinkLatch.await, () => Cause.done()) |
| 2218 | }) |
| 2219 | ) |
| 2220 | |
| 2221 | return Effect.suspend((): Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E | E2, void, R> => { |
| 2222 | if (causeSink) { |
| 2223 | return Effect.failCause(causeSink) |
| 2224 | } else if (sinkDone) { |
| 2225 | return pull |
| 2226 | } |
| 2227 | return pullAndOffer |
| 2228 | }) |
| 2229 | }) |
| 2230 | ) |
| 2231 | ) |
| 2232 | |
| 2233 | /** |
| 2234 | * Maps each element to a stream and flattens the resulting streams. |
| 2235 | * |
| 2236 | * **Details** |
| 2237 | * |
| 2238 | * With the default sequential concurrency, inner streams are concatenated in |
| 2239 | * input order. When `concurrency` is greater than `1` or `"unbounded"`, |
| 2240 | * multiple inner streams may run at the same time and their outputs are merged |
| 2241 | * as they arrive. |
| 2242 | * |
| 2243 | * **Example** (Flat mapping stream values) |
| 2244 | * |
| 2245 | * ```ts import.meta.vitest |
| 2246 | * import { Effect, Stream } from "effect" |
| 2247 | * |
| 2248 | * const program = Effect.gen(function*() { |
| 2249 | * const values = yield* Stream.make(1, 2, 3).pipe( |
| 2250 | * Stream.flatMap((n) => Stream.make(n, n * 2)), |
| 2251 | * Stream.runCollect |
| 2252 | * ) |
| 2253 | * values // => [ 1, 2, 2, 4, 3, 6 ] |
| 2254 | * }) |
| 2255 | * |
| 2256 | * await Effect.runPromise(program) |
| 2257 | * ``` |
| 2258 | * |
| 2259 | * @category sequencing |
| 2260 | * @since 2.0.0 |
| 2261 | */ |
| 2262 | export const flatMap: { |
| 2263 | <A, A2, E2, R2>( |
| 2264 | f: (a: A) => Stream<A2, E2, R2>, |
| 2265 | options?: { |
| 2266 | readonly concurrency?: Concurrency | undefined |
| 2267 | readonly bufferSize?: number | undefined |
| 2268 | } | undefined |
| 2269 | ): <E, R>(self: Stream<A, E, R>) => Stream<A2, E2 | E, R2 | R> |
| 2270 | <A, E, R, A2, E2, R2>( |
| 2271 | self: Stream<A, E, R>, |
| 2272 | f: (a: A) => Stream<A2, E2, R2>, |
| 2273 | options?: { |
| 2274 | readonly concurrency?: Concurrency | undefined |
| 2275 | readonly bufferSize?: number | undefined |
| 2276 | } | undefined |
| 2277 | ): Stream<A2, E | E2, R | R2> |
| 2278 | } = dual((args) => isStream(args[0]), <A, E, R, A2, E2, R2>( |
| 2279 | self: Stream<A, E, R>, |
| 2280 | f: (a: A) => Stream<A2, E2, R2>, |
| 2281 | options?: { |
| 2282 | readonly concurrency?: Concurrency | undefined |
| 2283 | readonly bufferSize?: number | undefined |
| 2284 | } | undefined |
| 2285 | ): Stream<A2, E | E2, R | R2> => |
| 2286 | self.channel.pipe( |
| 2287 | Channel.flattenArray, |
| 2288 | Channel.flatMap((a) => f(a).channel, options), |
| 2289 | fromChannel |
| 2290 | )) |
| 2291 | |
| 2292 | /** |
| 2293 | * Switches to the latest stream produced by the mapping function, interrupting |
| 2294 | * the previous stream when a new element arrives. |
| 2295 | * |
| 2296 | * **Example** (Switching to the latest stream) |
| 2297 | * |
| 2298 | * ```ts import.meta.vitest |
| 2299 | * import { Effect, Stream } from "effect" |
| 2300 | * |
| 2301 | * const program = Stream.make(1, 2, 3).pipe( |
| 2302 | * Stream.switchMap((n) => (n === 3 ? Stream.make(n) : Stream.never)), |
| 2303 | * Stream.runCollect |
| 2304 | * ) |
| 2305 | * |
| 2306 | * await Effect.runPromise(Effect.gen(function*() { |
| 2307 | * const result = yield* program |
| 2308 | * result // => [ 3 ] |
| 2309 | * })) |
| 2310 | * ``` |
| 2311 | * |
| 2312 | * @category sequencing |
| 2313 | * @since 4.0.0 |
| 2314 | */ |
| 2315 | export const switchMap: { |
| 2316 | <A, A2, E2, R2>( |
| 2317 | f: (a: A) => Stream<A2, E2, R2>, |
| 2318 | options?: { |
| 2319 | readonly concurrency?: Concurrency | undefined |
| 2320 | readonly bufferSize?: number | undefined |
| 2321 | } | undefined |
| 2322 | ): <E, R>(self: Stream<A, E, R>) => Stream<A2, E2 | E, R2 | R> |
| 2323 | <A, E, R, A2, E2, R2>( |
| 2324 | self: Stream<A, E, R>, |
| 2325 | f: (a: A) => Stream<A2, E2, R2>, |
| 2326 | options?: { |
| 2327 | readonly concurrency?: Concurrency | undefined |
| 2328 | readonly bufferSize?: number | undefined |
| 2329 | } | undefined |
| 2330 | ): Stream<A2, E | E2, R | R2> |
| 2331 | } = dual((args) => isStream(args[0]), <A, E, R, A2, E2, R2>( |
| 2332 | self: Stream<A, E, R>, |
| 2333 | f: (a: A) => Stream<A2, E2, R2>, |
| 2334 | options?: { |
| 2335 | readonly concurrency?: Concurrency | undefined |
| 2336 | readonly bufferSize?: number | undefined |
| 2337 | } | undefined |
| 2338 | ): Stream<A2, E | E2, R | R2> => |
| 2339 | self.channel.pipe( |
| 2340 | Channel.flattenArray, |
| 2341 | Channel.switchMap((a) => f(a).channel, options), |
| 2342 | fromChannel |
| 2343 | )) |
| 2344 | |
| 2345 | /** |
| 2346 | * Flattens a stream of streams into a single stream. |
| 2347 | * |
| 2348 | * **Details** |
| 2349 | * |
| 2350 | * With the default sequential concurrency, inner streams are concatenated in |
| 2351 | * strict order. When `concurrency` is greater than `1` or `"unbounded"`, |
| 2352 | * multiple inner streams may run at the same time and their outputs are merged |
| 2353 | * as they arrive. |
| 2354 | * |
| 2355 | * **Example** (Flattening nested streams) |
| 2356 | * |
| 2357 | * ```ts import.meta.vitest |
| 2358 | * import { Effect, Stream } from "effect" |
| 2359 | * |
| 2360 | * const streamOfStreams = Stream.make( |
| 2361 | * Stream.make(1, 2), |
| 2362 | * Stream.make(3, 4), |
| 2363 | * Stream.make(5, 6) |
| 2364 | * ) |
| 2365 | * |
| 2366 | * const program = Effect.gen(function*() { |
| 2367 | * const values = yield* Stream.runCollect(Stream.flatten(streamOfStreams)) |
| 2368 | * values // => [ 1, 2, 3, 4, 5, 6 ] |
| 2369 | * }) |
| 2370 | * |
| 2371 | * await Effect.runPromise(program) |
| 2372 | * ``` |
| 2373 | * |
| 2374 | * @category sequencing |
| 2375 | * @since 2.0.0 |
| 2376 | */ |
| 2377 | export const flatten: < |
| 2378 | Arg extends Stream<Stream<any, any, any>, any, any> | { |
| 2379 | readonly concurrency?: Concurrency | undefined |
| 2380 | readonly bufferSize?: number | undefined |
| 2381 | } | undefined = { |
| 2382 | readonly concurrency?: Concurrency | undefined |
| 2383 | readonly bufferSize?: number | undefined |
| 2384 | } |
| 2385 | >( |
| 2386 | selfOrOptions?: Arg, |
| 2387 | options?: { |
| 2388 | readonly concurrency?: Concurrency | undefined |
| 2389 | readonly bufferSize?: number | undefined |
| 2390 | } | undefined |
| 2391 | ) => [Arg] extends [Stream<Stream<infer _A, infer _E, infer _R>, infer _E2, infer _R2>] ? Stream<_A, _E | _E2, _R | _R2> |
| 2392 | : <A, E, R, E2, R2>(self: Stream<Stream<A, E, R>, E2, R2>) => Stream<A, E | E2, R | R2> = dual( |
| 2393 | (args) => isStream(args[0]), |
| 2394 | <A, E, R, E2, R2>( |
| 2395 | self: Stream<Stream<A, E, R>, E2, R2>, |
| 2396 | options?: { |
| 2397 | readonly concurrency?: Concurrency | undefined |
| 2398 | readonly bufferSize?: number | undefined |
| 2399 | } | undefined |
| 2400 | ): Stream<A, E | E2, R | R2> => flatMap(self, identity, options) |
| 2401 | ) |
| 2402 | |
| 2403 | /** |
| 2404 | * Flattens a stream of non-empty arrays into a stream of elements. |
| 2405 | * |
| 2406 | * **Example** (Flattening a stream of non-empty arrays into a stream of elements) |
| 2407 | * |
| 2408 | * ```ts import.meta.vitest |
| 2409 | * import { Array, Effect, Stream } from "effect" |
| 2410 | * |
| 2411 | * const stream = Stream.make(Array.make(1, 2), Array.make(3)) |
| 2412 | * |
| 2413 | * const program = Effect.gen(function* () { |
| 2414 | * const result = yield* Stream.runCollect(Stream.flattenArray(stream)) |
| 2415 | * result // => [ 1, 2, 3 ] |
| 2416 | * }) |
| 2417 | * |
| 2418 | * await Effect.runPromise(program) |
| 2419 | * ``` |
| 2420 | * |
| 2421 | * @category sequencing |
| 2422 | * @since 4.0.0 |
| 2423 | */ |
| 2424 | export const flattenArray = <A, E, R>(self: Stream<Arr.NonEmptyReadonlyArray<A>, E, R>): Stream<A, E, R> => |
| 2425 | fromChannel(Channel.flattenArray(self.channel)) |
| 2426 | |
| 2427 | /** |
| 2428 | * Converts this stream to one that runs its effects but emits no elements. |
| 2429 | * |
| 2430 | * **Example** (Draining stream values) |
| 2431 | * |
| 2432 | * ```ts import.meta.vitest |
| 2433 | * import { Effect, Stream } from "effect" |
| 2434 | * |
| 2435 | * const program = Effect.gen(function*() { |
| 2436 | * const result = yield* Stream.range(1, 6).pipe(Stream.drain, Stream.runCollect) |
| 2437 | * result // => [] |
| 2438 | * }) |
| 2439 | * |
| 2440 | * await Effect.runPromise(program) |
| 2441 | * ``` |
| 2442 | * |
| 2443 | * @category sequencing |
| 2444 | * @since 2.0.0 |
| 2445 | */ |
| 2446 | export const drain = <A, E, R>(self: Stream<A, E, R>): Stream<never, E, R> => fromChannel(Channel.drain(self.channel)) |
| 2447 | |
| 2448 | /** |
| 2449 | * Runs the provided stream in the background while this stream runs, interrupting it |
| 2450 | * when this stream completes and failing if the background stream fails or defects. |
| 2451 | * |
| 2452 | * **Example** (Draining a stream in the background) |
| 2453 | * |
| 2454 | * ```ts import.meta.vitest |
| 2455 | * import { Effect, Stream } from "effect" |
| 2456 | * |
| 2457 | * const events: Array<string> = [] |
| 2458 | * const foreground = Stream.make(1, 2) |
| 2459 | * const background = Stream.fromEffect(Effect.sync(() => events.push("background task"))) |
| 2460 | * |
| 2461 | * const program = Effect.gen(function*() { |
| 2462 | * const values = yield* foreground.pipe( |
| 2463 | * Stream.drainFork(background), |
| 2464 | * Stream.runCollect |
| 2465 | * ) |
| 2466 | * values // => [1, 2] |
| 2467 | * }) |
| 2468 | * |
| 2469 | * await Effect.runPromise(program) |
| 2470 | * events // => ["background task"] |
| 2471 | * ``` |
| 2472 | * |
| 2473 | * @category sequencing |
| 2474 | * @since 2.0.0 |
| 2475 | */ |
| 2476 | export const drainFork: { |
| 2477 | <A2, E2, R2>(that: Stream<A2, E2, R2>): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E2 | E, R2 | R> |
| 2478 | <A, E, R, A2, E2, R2>(self: Stream<A, E, R>, that: Stream<A2, E2, R2>): Stream<A, E | E2, R | R2> |
| 2479 | } = dual( |
| 2480 | 2, |
| 2481 | <A, E, R, A2, E2, R2>(self: Stream<A, E, R>, that: Stream<A2, E2, R2>): Stream<A, E | E2, R | R2> => |
| 2482 | mergeEffect(self, runDrain(that)) |
| 2483 | ) |
| 2484 | |
| 2485 | /** |
| 2486 | * Repeats the entire stream according to the provided schedule. |
| 2487 | * |
| 2488 | * **Example** (Repeating a stream on a schedule) |
| 2489 | * |
| 2490 | * ```ts import.meta.vitest |
| 2491 | * import { Effect, Schedule, Stream } from "effect" |
| 2492 | * |
| 2493 | * const program = Effect.gen(function* () { |
| 2494 | * const result = yield* Stream.make(1).pipe( |
| 2495 | * Stream.repeat(Schedule.recurs(4)), |
| 2496 | * Stream.runCollect |
| 2497 | * ) |
| 2498 | * |
| 2499 | * result // => [ 1, 1, 1, 1, 1 ] |
| 2500 | * }) |
| 2501 | * |
| 2502 | * await Effect.runPromise(program) |
| 2503 | * ``` |
| 2504 | * |
| 2505 | * @category repetition |
| 2506 | * @since 2.0.0 |
| 2507 | */ |
| 2508 | export const repeat: { |
| 2509 | <B, E2, R2>( |
| 2510 | schedule: |
| 2511 | | Schedule.Schedule<B, void, E2, R2> |
| 2512 | | (( |
| 2513 | $: <SO, SE, SR>(_: Schedule.Schedule<SO, void, SE, SR>) => Schedule.Schedule<SO, void, SE, SR> |
| 2514 | ) => Schedule.Schedule<B, void, E2, R2>) |
| 2515 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E | E2, R2 | R> |
| 2516 | <A, E, R, B, E2, R2>( |
| 2517 | self: Stream<A, E, R>, |
| 2518 | schedule: |
| 2519 | | Schedule.Schedule<B, void, E2, R2> |
| 2520 | | (( |
| 2521 | $: <SO, SE, SR>(_: Schedule.Schedule<SO, void, SE, SR>) => Schedule.Schedule<SO, void, SE, SR> |
| 2522 | ) => Schedule.Schedule<B, void, E2, R2>) |
| 2523 | ): Stream<A, E | E2, R | R2> |
| 2524 | } = dual(2, <A, E, R, B, E2, R2>( |
| 2525 | self: Stream<A, E, R>, |
| 2526 | schedule: |
| 2527 | | Schedule.Schedule<B, void, E2, R2> |
| 2528 | | (( |
| 2529 | $: <SO, SE, SR>(_: Schedule.Schedule<SO, void, SE, SR>) => Schedule.Schedule<SO, void, SE, SR> |
| 2530 | ) => Schedule.Schedule<B, void, E2, R2>) |
| 2531 | ): Stream<A, E | E2, R | R2> => fromChannel(Channel.repeat(self.channel, schedule))) |
| 2532 | |
| 2533 | /** |
| 2534 | * Schedules the stream's elements according to the provided schedule. |
| 2535 | * |
| 2536 | * **Example** (Scheduling stream elements) |
| 2537 | * |
| 2538 | * ```ts import.meta.vitest |
| 2539 | * import { Effect, Schedule, Stream } from "effect" |
| 2540 | * |
| 2541 | * const program = Effect.gen(function*() { |
| 2542 | * const result = yield* Stream.make(1, 2, 3).pipe( |
| 2543 | * Stream.schedule(Schedule.recurs(3)), |
| 2544 | * Stream.runCollect |
| 2545 | * ) |
| 2546 | * |
| 2547 | * result // => [ 1, 2, 3 ] |
| 2548 | * }) |
| 2549 | * |
| 2550 | * await Effect.runPromise(program) |
| 2551 | * ``` |
| 2552 | * |
| 2553 | * @category rate limiting |
| 2554 | * @since 2.0.0 |
| 2555 | */ |
| 2556 | export const schedule: { |
| 2557 | <X, E2, R2, A>( |
| 2558 | schedule: Schedule.Schedule<X, NoInfer<A>, E2, R2> |
| 2559 | ): <E, R>(self: Stream<A, E, R>) => Stream<A, E | E2, R2 | R> |
| 2560 | <A, E, R, X, E2, R2>( |
| 2561 | self: Stream<A, E, R>, |
| 2562 | schedule: Schedule.Schedule<X, NoInfer<A>, E2, R2> |
| 2563 | ): Stream<A, E | E2, R | R2> |
| 2564 | } = dual(2, <A, E, R, X, E2, R2>( |
| 2565 | self: Stream<A, E, R>, |
| 2566 | schedule: Schedule.Schedule<X, NoInfer<A>, E2, R2> |
| 2567 | ): Stream<A, E | E2, R | R2> => |
| 2568 | self.channel.pipe( |
| 2569 | Channel.flattenArray, |
| 2570 | Channel.schedule(schedule), |
| 2571 | Channel.map(Arr.of), |
| 2572 | fromChannel |
| 2573 | )) |
| 2574 | |
| 2575 | /** |
| 2576 | * Ends the stream if it does not produce a value within the specified duration. |
| 2577 | * |
| 2578 | * **Example** (Timing out a stream) |
| 2579 | * |
| 2580 | * ```ts import.meta.vitest |
| 2581 | * import { Effect, Stream } from "effect" |
| 2582 | * |
| 2583 | * const program = Effect.gen(function*() { |
| 2584 | * const values = yield* Stream.make(1).pipe( |
| 2585 | * Stream.concat(Stream.never), |
| 2586 | * Stream.timeout("1 second"), |
| 2587 | * Stream.runCollect |
| 2588 | * ) |
| 2589 | * values // => [ 1 ] |
| 2590 | * }) |
| 2591 | * |
| 2592 | * await Effect.runPromise(program) |
| 2593 | * ``` |
| 2594 | * |
| 2595 | * @category delays & timeouts |
| 2596 | * @since 2.0.0 |
| 2597 | */ |
| 2598 | export const timeout: { |
| 2599 | (duration: Duration.Input): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 2600 | <A, E, R>(self: Stream<A, E, R>, duration: Duration.Input): Stream<A, E, R> |
| 2601 | } = dual( |
| 2602 | 2, |
| 2603 | <A, E, R>(self: Stream<A, E, R>, duration: Duration.Input): Stream<A, E, R> => |
| 2604 | timeoutOrElse(self, { |
| 2605 | duration, |
| 2606 | orElse: () => empty |
| 2607 | }) |
| 2608 | ) |
| 2609 | |
| 2610 | /** |
| 2611 | * Switches to a fallback stream if this stream does not emit a value within |
| 2612 | * the specified duration. |
| 2613 | * |
| 2614 | * **When to use** |
| 2615 | * |
| 2616 | * Use when a stream should continue with another stream if an upstream pull |
| 2617 | * waits longer than the allowed duration. |
| 2618 | * |
| 2619 | * **Details** |
| 2620 | * |
| 2621 | * The timeout is checked for each pull. A zero duration uses `orElse` |
| 2622 | * immediately, while an infinite duration leaves the original stream |
| 2623 | * unchanged. |
| 2624 | * |
| 2625 | * **Gotchas** |
| 2626 | * |
| 2627 | * The fallback stream is not timed after the switch. |
| 2628 | * |
| 2629 | * @see {@link timeout} for ending the stream instead of switching to a fallback stream |
| 2630 | * |
| 2631 | * @category delays & timeouts |
| 2632 | * @since 4.0.0 |
| 2633 | */ |
| 2634 | export const timeoutOrElse: { |
| 2635 | <B, E2, R2>(options: { |
| 2636 | readonly duration: Duration.Input |
| 2637 | readonly orElse: () => Stream<B, E2, R2> |
| 2638 | }): <A, E, R>(self: Stream<A, E, R>) => Stream<A | B, E | E2, R | R2> |
| 2639 | <A, E, R, B, E2, R2>( |
| 2640 | self: Stream<A, E, R>, |
| 2641 | options: { |
| 2642 | readonly duration: Duration.Input |
| 2643 | readonly orElse: () => Stream<B, E2, R2> |
| 2644 | } |
| 2645 | ): Stream<A | B, E | E2, R | R2> |
| 2646 | } = dual( |
| 2647 | 2, |
| 2648 | <A, E, R, B, E2, R2>( |
| 2649 | self: Stream<A, E, R>, |
| 2650 | options: { |
| 2651 | readonly duration: Duration.Input |
| 2652 | readonly orElse: () => Stream<B, E2, R2> |
| 2653 | } |
| 2654 | ): Stream<A | B, E | E2, R | R2> => { |
| 2655 | const duration = Duration.fromInputUnsafe(options.duration) |
| 2656 | if (!Duration.isFinite(duration)) return self |
| 2657 | if (Duration.isZero(duration)) return suspend(options.orElse) |
| 2658 | const timeoutSymbol = Symbol() |
| 2659 | return catchCause( |
| 2660 | suspend(() => { |
| 2661 | const parent = Fiber.getCurrent()! |
| 2662 | const clock = parent.getRef(Clock) |
| 2663 | const durationMs = Duration.toMillis(duration) |
| 2664 | let deadline: number | undefined = undefined |
| 2665 | const latch = Latch.makeUnsafe(false) |
| 2666 | return merge( |
| 2667 | transformPull(self, (pull, _scope) => |
| 2668 | Effect.suspend(() => { |
| 2669 | deadline = clock.currentTimeMillisUnsafe() + durationMs |
| 2670 | latch.openUnsafe() |
| 2671 | return pull |
| 2672 | }).pipe( |
| 2673 | Effect.map((arr) => { |
| 2674 | latch.closeUnsafe() |
| 2675 | deadline = undefined |
| 2676 | return arr |
| 2677 | }), |
| 2678 | Effect.succeed |
| 2679 | )), |
| 2680 | fromEffectDrain(Effect.gen(function*() { |
| 2681 | while (true) { |
| 2682 | yield* latch.await |
| 2683 | if (deadline === undefined) continue |
| 2684 | yield* Effect.sleep(deadline - clock.currentTimeMillisUnsafe()) |
| 2685 | if (deadline === undefined) continue |
| 2686 | const remaining = deadline - clock.currentTimeMillisUnsafe() |
| 2687 | if (remaining > 0) continue |
| 2688 | return yield* Effect.die(timeoutSymbol) |
| 2689 | } |
| 2690 | })), |
| 2691 | { haltStrategy: "left" } |
| 2692 | ) |
| 2693 | }), |
| 2694 | (cause): Stream<B, E | E2, R2> => { |
| 2695 | const isTimeout = cause.reasons.find((r) => r._tag === "Die" && r.defect === timeoutSymbol) |
| 2696 | if (isTimeout) return options.orElse() |
| 2697 | return failCause(cause as Cause.Cause<E>) |
| 2698 | } |
| 2699 | ) |
| 2700 | } |
| 2701 | ) |
| 2702 | |
| 2703 | /** |
| 2704 | * Repeats each element of the stream according to the provided schedule, |
| 2705 | * including the original emission. |
| 2706 | * |
| 2707 | * **Example** (Repeating stream elements) |
| 2708 | * |
| 2709 | * ```ts import.meta.vitest |
| 2710 | * import { Effect, Schedule, Stream } from "effect" |
| 2711 | * |
| 2712 | * const program = Effect.gen(function*() { |
| 2713 | * const values = yield* Stream.make("A", "B", "C").pipe( |
| 2714 | * Stream.repeatElements(Schedule.recurs(1)), |
| 2715 | * Stream.runCollect |
| 2716 | * ) |
| 2717 | * values // => [ 'A', 'A', 'B', 'B', 'C', 'C' ] |
| 2718 | * }) |
| 2719 | * |
| 2720 | * await Effect.runPromise(program) |
| 2721 | * ``` |
| 2722 | * |
| 2723 | * @category repetition |
| 2724 | * @since 2.0.0 |
| 2725 | */ |
| 2726 | export const repeatElements: { |
| 2727 | <B, E2, R2>( |
| 2728 | schedule: Schedule.Schedule<B, unknown, E2, R2> |
| 2729 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E | E2, R2 | R> |
| 2730 | <A, E, R, B, E2, R2>( |
| 2731 | self: Stream<A, E, R>, |
| 2732 | schedule: Schedule.Schedule<B, unknown, E2, R2> |
| 2733 | ): Stream<A, E | E2, R | R2> |
| 2734 | } = dual( |
| 2735 | 2, |
| 2736 | <A, E, R, B, E2, R2>( |
| 2737 | self: Stream<A, E, R>, |
| 2738 | schedule: Schedule.Schedule<B, unknown, E2, R2> |
| 2739 | ): Stream<A, E | E2, R | R2> => |
| 2740 | fromChannel(Channel.fromTransform((upstream, scope) => |
| 2741 | Effect.map( |
| 2742 | Channel.toTransform(Channel.flattenArray(self.channel))(upstream, scope), |
| 2743 | (pullElement) => { |
| 2744 | let pullRepeat: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E | E2, void, R | R2> | undefined = undefined |
| 2745 | |
| 2746 | const pull: Pull.Pull< |
| 2747 | Arr.NonEmptyReadonlyArray<A>, |
| 2748 | E, |
| 2749 | void, |
| 2750 | R | R2 |
| 2751 | > = Effect.gen(function*() { |
| 2752 | const element = yield* pullElement |
| 2753 | const chunk = Arr.of(element) |
| 2754 | const step = yield* Schedule.toStepWithSleep(schedule) |
| 2755 | pullRepeat = step(element).pipe( |
| 2756 | Effect.as(chunk), |
| 2757 | Pull.catchDone((_) => { |
| 2758 | pullRepeat = undefined |
| 2759 | return pull |
| 2760 | }) |
| 2761 | ) |
| 2762 | return chunk |
| 2763 | }) |
| 2764 | |
| 2765 | return Effect.suspend(() => pullRepeat ?? pull) |
| 2766 | } |
| 2767 | ) |
| 2768 | )) |
| 2769 | ) |
| 2770 | |
| 2771 | /** |
| 2772 | * Repeats this stream forever. |
| 2773 | * |
| 2774 | * **Example** (Repeating a stream forever) |
| 2775 | * |
| 2776 | * ```ts import.meta.vitest |
| 2777 | * import { Effect, Stream } from "effect" |
| 2778 | * |
| 2779 | * const stream = Stream.make("A", "B").pipe( |
| 2780 | * Stream.forever, |
| 2781 | * Stream.take(5) |
| 2782 | * ) |
| 2783 | * |
| 2784 | * const program = Effect.gen(function*() { |
| 2785 | * const output = yield* Stream.runCollect(stream) |
| 2786 | * output // => [ 'A', 'B', 'A', 'B', 'A' ] |
| 2787 | * }) |
| 2788 | * |
| 2789 | * await Effect.runPromise(program) |
| 2790 | * ``` |
| 2791 | * |
| 2792 | * @category repetition |
| 2793 | * @since 2.0.0 |
| 2794 | */ |
| 2795 | export const forever = <A, E, R>(self: Stream<A, E, R>): Stream<A, E, R> => fromChannel(Channel.forever(self.channel)) |
| 2796 | |
| 2797 | /** |
| 2798 | * Flattens the iterables emitted by this stream into the stream's structure. |
| 2799 | * |
| 2800 | * **Example** (Flattening iterable values) |
| 2801 | * |
| 2802 | * ```ts import.meta.vitest |
| 2803 | * import { Effect, Stream } from "effect" |
| 2804 | * |
| 2805 | * const program = Effect.gen(function*() { |
| 2806 | * const stream = Stream.make([1, 2], [3, 4]).pipe(Stream.flattenIterable) |
| 2807 | * const values = yield* Stream.runCollect(stream) |
| 2808 | * values // => [ 1, 2, 3, 4 ] |
| 2809 | * }) |
| 2810 | * |
| 2811 | * await Effect.runPromise(program) |
| 2812 | * ``` |
| 2813 | * |
| 2814 | * @category sequencing |
| 2815 | * @since 4.0.0 |
| 2816 | */ |
| 2817 | export const flattenIterable = <A, E, R>(self: Stream<Iterable<A>, E, R>): Stream<A, E, R> => |
| 2818 | flatMap(self, fromIterable) |
| 2819 | |
| 2820 | /** |
| 2821 | * Unwraps `Take` values, emitting elements from non-empty arrays and ending or |
| 2822 | * failing when the `Exit` signals completion. |
| 2823 | * |
| 2824 | * **Example** (Flattening Take values) |
| 2825 | * |
| 2826 | * ```ts import.meta.vitest |
| 2827 | * import { Array, Effect, Exit, Stream } from "effect" |
| 2828 | * |
| 2829 | * const program = Effect.gen(function*() { |
| 2830 | * const takes = Stream.make( |
| 2831 | * Array.make(1, 2), |
| 2832 | * Array.make(3), |
| 2833 | * Exit.succeed<void>(undefined) |
| 2834 | * ) |
| 2835 | * |
| 2836 | * const values = yield* Stream.flattenTake(takes).pipe(Stream.runCollect) |
| 2837 | * values // => [ 1, 2, 3 ] |
| 2838 | * }) |
| 2839 | * |
| 2840 | * await Effect.runPromise(program) |
| 2841 | * ``` |
| 2842 | * |
| 2843 | * @category sequencing |
| 2844 | * @since 2.0.0 |
| 2845 | */ |
| 2846 | export const flattenTake = <A, E, E2, R>(self: Stream<Take.Take<A, E>, E2, R>): Stream<A, E | E2, R> => |
| 2847 | self.channel.pipe( |
| 2848 | Channel.flattenArray, |
| 2849 | Channel.flattenTake, |
| 2850 | fromChannel |
| 2851 | ) |
| 2852 | |
| 2853 | /** |
| 2854 | * Concatenates two streams, emitting all elements from the first stream |
| 2855 | * followed by all elements from the second stream. |
| 2856 | * |
| 2857 | * **Example** (Concatenating streams) |
| 2858 | * |
| 2859 | * ```ts import.meta.vitest |
| 2860 | * import { Effect, Stream } from "effect" |
| 2861 | * |
| 2862 | * const stream = Stream.concat(Stream.make(1, 2, 3), Stream.make(4, 5, 6)) |
| 2863 | * |
| 2864 | * await Effect.runPromise(Effect.gen(function*() { |
| 2865 | * const values = yield* Stream.runCollect(stream) |
| 2866 | * values // => [ 1, 2, 3, 4, 5, 6 ] |
| 2867 | * })) |
| 2868 | * ``` |
| 2869 | * |
| 2870 | * @category sequencing |
| 2871 | * @since 2.0.0 |
| 2872 | */ |
| 2873 | export const concat: { |
| 2874 | <A2, E2, R2>(that: Stream<A2, E2, R2>): <A, E, R>(self: Stream<A, E, R>) => Stream<A | A2, E | E2, R | R2> |
| 2875 | <A, E, R, A2, E2, R2>(self: Stream<A, E, R>, that: Stream<A2, E2, R2>): Stream<A | A2, E | E2, R | R2> |
| 2876 | } = dual( |
| 2877 | 2, |
| 2878 | <A, E, R, A2, E2, R2>(self: Stream<A, E, R>, that: Stream<A2, E2, R2>): Stream<A | A2, E | E2, R | R2> => { |
| 2879 | const stream = fromChannel( |
| 2880 | Channel.flatten(Channel.fromIterator(() => concatChannels<A | A2, E | E2, R | R2>(self, that))) |
| 2881 | ) |
| 2882 | concatParts.set(stream, [self, that]) |
| 2883 | return stream |
| 2884 | } |
| 2885 | ) |
| 2886 | |
| 2887 | // The operands of each `concat` result, so a chain of `concat` calls can run as |
| 2888 | // a single layer instead of one layer per call |
| 2889 | const concatParts = new WeakMap<Stream<any, any, any>, readonly [Stream<any, any, any>, Stream<any, any, any>]>() |
| 2890 | |
| 2891 | // Yields the channel of each non-`concat` stream in a chain, in order |
| 2892 | function* concatChannels<A, E, R>(self: Stream<A, E, R>, that: Stream<A, E, R>) { |
| 2893 | const stack = [that, self] |
| 2894 | while (stack.length > 0) { |
| 2895 | const stream = stack.pop()! |
| 2896 | const parts = concatParts.get(stream) |
| 2897 | if (parts === undefined) yield stream.channel |
| 2898 | else stack.push(parts[1], parts[0]) |
| 2899 | } |
| 2900 | } |
| 2901 | |
| 2902 | /** |
| 2903 | * Prepends the values from the provided iterable before the stream's elements. |
| 2904 | * |
| 2905 | * **Example** (Prepending values) |
| 2906 | * |
| 2907 | * ```ts import.meta.vitest |
| 2908 | * import { Effect, Stream } from "effect" |
| 2909 | * |
| 2910 | * const program = Effect.gen(function*() { |
| 2911 | * const values = yield* Stream.make(3, 4).pipe( |
| 2912 | * Stream.prepend([1, 2]), |
| 2913 | * Stream.runCollect |
| 2914 | * ) |
| 2915 | * |
| 2916 | * values // => [ 1, 2, 3, 4 ] |
| 2917 | * }) |
| 2918 | * |
| 2919 | * await Effect.runPromise(program) |
| 2920 | * ``` |
| 2921 | * |
| 2922 | * @category sequencing |
| 2923 | * @since 2.0.0 |
| 2924 | */ |
| 2925 | export const prepend: { |
| 2926 | <B>(values: Iterable<B>): <A, E, R>(self: Stream<A, E, R>) => Stream<B | A, E, R> |
| 2927 | <A, E, R, B>(self: Stream<A, E, R>, values: Iterable<B>): Stream<A | B, E, R> |
| 2928 | } = dual(2, <A, E, R, B>( |
| 2929 | self: Stream<A, E, R>, |
| 2930 | values: Iterable<B> |
| 2931 | ): Stream<A | B, E, R> => concat(fromIterable(values), self)) |
| 2932 | |
| 2933 | /** |
| 2934 | * Merges two streams, emitting elements from both as they arrive. |
| 2935 | * |
| 2936 | * **Details** |
| 2937 | * |
| 2938 | * By default, the merged stream ends when both streams end. Use |
| 2939 | * `haltStrategy` to change the termination behavior. |
| 2940 | * |
| 2941 | * **Example** (Merging stream values) |
| 2942 | * |
| 2943 | * ```ts import.meta.vitest |
| 2944 | * import { Effect, Stream } from "effect" |
| 2945 | * |
| 2946 | * const fast = Stream.make(1, 2, 3) |
| 2947 | * const slow = Stream.fromEffect(Effect.delay(Effect.succeed(4), "50 millis")) |
| 2948 | * |
| 2949 | * const program = Effect.gen(function*() { |
| 2950 | * const result = yield* Stream.runCollect(Stream.merge(fast, slow)) |
| 2951 | * result // => [ 1, 2, 3, 4 ] |
| 2952 | * }) |
| 2953 | * |
| 2954 | * await Effect.runPromise(program) |
| 2955 | * ``` |
| 2956 | * |
| 2957 | * @category merging |
| 2958 | * @since 2.0.0 |
| 2959 | */ |
| 2960 | export const merge: { |
| 2961 | <A2, E2, R2>( |
| 2962 | that: Stream<A2, E2, R2>, |
| 2963 | options?: { |
| 2964 | readonly haltStrategy?: HaltStrategy | undefined |
| 2965 | } | undefined |
| 2966 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<A | A2, E | E2, R | R2> |
| 2967 | <A, E, R, A2, E2, R2>( |
| 2968 | self: Stream<A, E, R>, |
| 2969 | that: Stream<A2, E2, R2>, |
| 2970 | options?: { |
| 2971 | readonly haltStrategy?: HaltStrategy | undefined |
| 2972 | } | undefined |
| 2973 | ): Stream<A | A2, E | E2, R | R2> |
| 2974 | } = dual( |
| 2975 | (args) => isStream(args[0]) && isStream(args[1]), |
| 2976 | <A, E, R, A2, E2, R2>( |
| 2977 | self: Stream<A, E, R>, |
| 2978 | that: Stream<A2, E2, R2>, |
| 2979 | options?: { |
| 2980 | readonly haltStrategy?: HaltStrategy | undefined |
| 2981 | } | undefined |
| 2982 | ): Stream<A | A2, E | E2, R | R2> => fromChannel(Channel.merge(toChannel(self), toChannel(that), options)) |
| 2983 | ) |
| 2984 | |
| 2985 | /** |
| 2986 | * Merges this stream with a background effect, keeping the stream's elements. |
| 2987 | * |
| 2988 | * **When to use** |
| 2989 | * |
| 2990 | * Use when an effect should run concurrently for the lifetime of a stream while |
| 2991 | * only the stream's elements remain in the output. |
| 2992 | * |
| 2993 | * **Details** |
| 2994 | * |
| 2995 | * The effect runs concurrently, fails the stream if it fails, and is interrupted |
| 2996 | * when the stream completes. |
| 2997 | * |
| 2998 | * **Example** (Merging with a background effect) |
| 2999 | * |
| 3000 | * ```ts import.meta.vitest |
| 3001 | * import { Effect, Stream } from "effect" |
| 3002 | * |
| 3003 | * const events: Array<string> = [] |
| 3004 | * const program = Effect.gen(function*() { |
| 3005 | * const values = yield* Stream.make(1, 2, 3).pipe( |
| 3006 | * Stream.mergeEffect(Effect.sync(() => events.push("side task"))), |
| 3007 | * Stream.runCollect |
| 3008 | * ) |
| 3009 | * |
| 3010 | * values // => [1, 2, 3] |
| 3011 | * }) |
| 3012 | * |
| 3013 | * await Effect.runPromise(program) |
| 3014 | * events // => ["side task"] |
| 3015 | * ``` |
| 3016 | * |
| 3017 | * @category merging |
| 3018 | * @since 4.0.0 |
| 3019 | */ |
| 3020 | export const mergeEffect: { |
| 3021 | <A2, E2, R2>(effect: Effect.Effect<A2, E2, R2>): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E2 | E, R2 | R> |
| 3022 | <A, E, R, A2, E2, R2>(self: Stream<A, E, R>, effect: Effect.Effect<A2, E2, R2>): Stream<A, E | E2, R | R2> |
| 3023 | } = dual( |
| 3024 | 2, |
| 3025 | <A, E, R, A2, E2, R2>(self: Stream<A, E, R>, effect: Effect.Effect<A2, E2, R2>): Stream<A, E | E2, R | R2> => |
| 3026 | self.channel.pipe( |
| 3027 | Channel.mergeEffect(effect), |
| 3028 | fromChannel |
| 3029 | ) |
| 3030 | ) |
| 3031 | |
| 3032 | /** |
| 3033 | * Merges this stream and the specified stream together, tagging values from the |
| 3034 | * left stream as `Result.succeed` and values from the right stream as `Result.fail`. |
| 3035 | * |
| 3036 | * **When to use** |
| 3037 | * |
| 3038 | * Use when values from both streams should be emitted and downstream code needs |
| 3039 | * left values wrapped as successful `Result` values and right values wrapped as |
| 3040 | * failed `Result` values. |
| 3041 | * |
| 3042 | * **Example** (Merging streams into results) |
| 3043 | * |
| 3044 | * ```ts import.meta.vitest |
| 3045 | * import { Effect, Result, Stream } from "effect" |
| 3046 | * |
| 3047 | * const left = Stream.fromEffect(Effect.succeed("left")) |
| 3048 | * const right = Stream.fromEffect(Effect.delay(Effect.succeed("right"), "10 millis")) |
| 3049 | * |
| 3050 | * const merged = left.pipe( |
| 3051 | * Stream.mergeResult(right), |
| 3052 | * Stream.map( |
| 3053 | * Result.match({ |
| 3054 | * onFailure: (value) => `right:${value}`, |
| 3055 | * onSuccess: (value) => `left:${value}` |
| 3056 | * }) |
| 3057 | * ) |
| 3058 | * ) |
| 3059 | * |
| 3060 | * const program = Effect.gen(function*() { |
| 3061 | * const result = yield* Stream.runCollect(merged) |
| 3062 | * result // => [ 'left:left', 'right:right' ] |
| 3063 | * }) |
| 3064 | * |
| 3065 | * await Effect.runPromise(program) |
| 3066 | * ``` |
| 3067 | * |
| 3068 | * @category merging |
| 3069 | * @since 4.0.0 |
| 3070 | */ |
| 3071 | export const mergeResult: { |
| 3072 | <A2, E2, R2>( |
| 3073 | that: Stream<A2, E2, R2> |
| 3074 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<Result.Result<A, A2>, E2 | E, R2 | R> |
| 3075 | <A, E, R, A2, E2, R2>(self: Stream<A, E, R>, that: Stream<A2, E2, R2>): Stream<Result.Result<A, A2>, E | E2, R | R2> |
| 3076 | } = dual( |
| 3077 | 2, |
| 3078 | <A, E, R, A2, E2, R2>( |
| 3079 | self: Stream<A, E, R>, |
| 3080 | that: Stream<A2, E2, R2> |
| 3081 | ): Stream<Result.Result<A, A2>, E | E2, R | R2> => |
| 3082 | merge( |
| 3083 | map(self, Result.succeed), |
| 3084 | map(that, Result.fail) |
| 3085 | ) |
| 3086 | ) |
| 3087 | |
| 3088 | /** |
| 3089 | * Merges two streams while emitting only the values from the left stream. |
| 3090 | * |
| 3091 | * **When to use** |
| 3092 | * |
| 3093 | * Use when the right stream is needed for its effects or failures, but downstream |
| 3094 | * consumers should only receive values from the left stream. |
| 3095 | * |
| 3096 | * **Details** |
| 3097 | * |
| 3098 | * The right stream still runs for its effects, and any failures from the right |
| 3099 | * stream are propagated. The merged stream completes when the left stream |
| 3100 | * completes, interrupting the right stream. |
| 3101 | * |
| 3102 | * **Example** (Merging streams while keeping left values) |
| 3103 | * |
| 3104 | * ```ts import.meta.vitest |
| 3105 | * import { Effect, Stream } from "effect" |
| 3106 | * |
| 3107 | * const program = Effect.gen(function*() { |
| 3108 | * const left = Stream.make(1, 2) |
| 3109 | * const right = Stream.make("a", "b") |
| 3110 | * const values = yield* left.pipe(Stream.mergeLeft(right), Stream.runCollect) |
| 3111 | * values // => [ 1, 2 ] |
| 3112 | * }) |
| 3113 | * |
| 3114 | * await Effect.runPromise(program) |
| 3115 | * ``` |
| 3116 | * |
| 3117 | * @category merging |
| 3118 | * @since 2.0.0 |
| 3119 | */ |
| 3120 | export const mergeLeft: { |
| 3121 | <AR, ER, RR>(right: Stream<AR, ER, RR>): <AL, EL, RL>(left: Stream<AL, EL, RL>) => Stream<AL, ER | EL, RR | RL> |
| 3122 | <AL, EL, RL, AR, ER, RR>(left: Stream<AL, EL, RL>, right: Stream<AR, ER, RR>): Stream<AL, EL | ER, RL | RR> |
| 3123 | } = dual( |
| 3124 | 2, |
| 3125 | <AL, EL, RL, AR, ER, RR>(left: Stream<AL, EL, RL>, right: Stream<AR, ER, RR>): Stream<AL, EL | ER, RL | RR> => |
| 3126 | mergeEffect(left, runDrain(right)) |
| 3127 | ) |
| 3128 | |
| 3129 | /** |
| 3130 | * Merges this stream and the specified stream together, emitting only the |
| 3131 | * values from the right stream while the left stream runs for its effects. |
| 3132 | * |
| 3133 | * **When to use** |
| 3134 | * |
| 3135 | * Use when the left stream is needed for its effects or failures, but downstream |
| 3136 | * consumers should only receive values from the right stream. |
| 3137 | * |
| 3138 | * **Details** |
| 3139 | * |
| 3140 | * The merged stream ends when the right stream completes, interrupting the |
| 3141 | * left stream. Failures from the left stream still fail the merged stream. |
| 3142 | * |
| 3143 | * **Example** (Merging streams while keeping right values) |
| 3144 | * |
| 3145 | * ```ts import.meta.vitest |
| 3146 | * import { Effect, Stream } from "effect" |
| 3147 | * |
| 3148 | * const left = Stream.make("left-1", "left-2").pipe( |
| 3149 | * Stream.tap(() => Effect.sync(() => undefined)) |
| 3150 | * ) |
| 3151 | * const right = Stream.make(1, 2) |
| 3152 | * |
| 3153 | * const merged = Stream.mergeRight(left, right) |
| 3154 | * |
| 3155 | * const program = Effect.gen(function*() { |
| 3156 | * const result = yield* Stream.runCollect(merged) |
| 3157 | * result // => [ 1, 2 ] |
| 3158 | * }) |
| 3159 | * |
| 3160 | * await Effect.runPromise(program) |
| 3161 | * ``` |
| 3162 | * |
| 3163 | * @category merging |
| 3164 | * @since 2.0.0 |
| 3165 | */ |
| 3166 | export const mergeRight: { |
| 3167 | <AR, ER, RR>(right: Stream<AR, ER, RR>): <AL, EL, RL>(left: Stream<AL, EL, RL>) => Stream<AR, ER | EL, RR | RL> |
| 3168 | <AL, EL, RL, AR, ER, RR>(left: Stream<AL, EL, RL>, right: Stream<AR, ER, RR>): Stream<AR, EL | ER, RL | RR> |
| 3169 | } = dual( |
| 3170 | 2, |
| 3171 | <AL, EL, RL, AR, ER, RR>(left: Stream<AL, EL, RL>, right: Stream<AR, ER, RR>): Stream<AR, EL | ER, RL | RR> => |
| 3172 | mergeEffect(right, runDrain(left)) |
| 3173 | ) |
| 3174 | |
| 3175 | /** |
| 3176 | * Merges a collection of streams, running up to the specified number concurrently. |
| 3177 | * |
| 3178 | * **When to use** |
| 3179 | * |
| 3180 | * Use to merge an iterable of already-created streams while bounding how many |
| 3181 | * inner streams may run at the same time. |
| 3182 | * |
| 3183 | * **Details** |
| 3184 | * |
| 3185 | * The `concurrency` option is required and may be a number or `"unbounded"`. |
| 3186 | * `bufferSize` controls buffering between inner streams, and outputs are |
| 3187 | * emitted as they arrive under concurrent merging. |
| 3188 | * |
| 3189 | * **Example** (Merging streams with bounded concurrency) |
| 3190 | * |
| 3191 | * ```ts import.meta.vitest |
| 3192 | * import { Effect, Stream } from "effect" |
| 3193 | * |
| 3194 | * const streams = [ |
| 3195 | * Stream.fromEffect(Effect.delay(Effect.succeed("A"), "20 millis")), |
| 3196 | * Stream.fromEffect(Effect.delay(Effect.succeed("B"), "10 millis")) |
| 3197 | * ] |
| 3198 | * |
| 3199 | * const program = Effect.gen(function*() { |
| 3200 | * const values = yield* Stream.mergeAll(streams, { concurrency: 2 }).pipe( |
| 3201 | * Stream.runCollect |
| 3202 | * ) |
| 3203 | * values // => [ 'B', 'A' ] |
| 3204 | * }) |
| 3205 | * |
| 3206 | * await Effect.runPromise(program) |
| 3207 | * ``` |
| 3208 | * |
| 3209 | * @see {@link merge} for merging exactly two streams and choosing a halt strategy |
| 3210 | * @see {@link flatten} for flattening a stream that already emits streams |
| 3211 | * |
| 3212 | * @category merging |
| 3213 | * @since 2.0.0 |
| 3214 | */ |
| 3215 | export const mergeAll: { |
| 3216 | ( |
| 3217 | options: { |
| 3218 | readonly concurrency: Concurrency |
| 3219 | readonly bufferSize?: number | undefined |
| 3220 | } |
| 3221 | ): <A, E, R>(streams: Iterable<Stream<A, E, R>>) => Stream<A, E, R> |
| 3222 | <A, E, R>( |
| 3223 | streams: Iterable<Stream<A, E, R>>, |
| 3224 | options: { |
| 3225 | readonly concurrency: Concurrency |
| 3226 | readonly bufferSize?: number | undefined |
| 3227 | } |
| 3228 | ): Stream<A, E, R> |
| 3229 | } = dual(2, <A, E, R>( |
| 3230 | streams: Iterable<Stream<A, E, R>>, |
| 3231 | options: { |
| 3232 | readonly concurrency: Concurrency |
| 3233 | readonly bufferSize?: number | undefined |
| 3234 | } |
| 3235 | ): Stream<A, E, R> => flatten(fromIterable(streams), options)) |
| 3236 | |
| 3237 | /** |
| 3238 | * Creates the cartesian product of two streams, running the `right` stream for |
| 3239 | * each element in the `left` stream. |
| 3240 | * |
| 3241 | * **Details** |
| 3242 | * |
| 3243 | * See also `Stream.zip` for the more common point-wise variant. |
| 3244 | * |
| 3245 | * **Example** (Computing cartesian products) |
| 3246 | * |
| 3247 | * ```ts import.meta.vitest |
| 3248 | * import { Effect, Stream } from "effect" |
| 3249 | * |
| 3250 | * const program = Effect.gen(function*() { |
| 3251 | * const left = Stream.make(1, 2) |
| 3252 | * const right = Stream.make("a", "b") |
| 3253 | * const values = yield* Stream.runCollect(Stream.cross(left, right)) |
| 3254 | * values // => [ [ 1, 'a' ], [ 1, 'b' ], [ 2, 'a' ], [ 2, 'b' ] ] |
| 3255 | * }) |
| 3256 | * |
| 3257 | * await Effect.runPromise(program) |
| 3258 | * ``` |
| 3259 | * |
| 3260 | * @category zipping |
| 3261 | * @since 2.0.0 |
| 3262 | */ |
| 3263 | export const cross: { |
| 3264 | <AR, ER, RR>(right: Stream<AR, ER, RR>): <AL, EL, RL>(left: Stream<AL, EL, RL>) => Stream<[AL, AR], EL | ER, RL | RR> |
| 3265 | <AL, EL, RL, AR, ER, RR>(left: Stream<AL, EL, RL>, right: Stream<AR, ER, RR>): Stream<[AL, AR], EL | ER, RL | RR> |
| 3266 | } = dual(2, <AL, EL, RL, AR, ER, RR>( |
| 3267 | left: Stream<AL, EL, RL>, |
| 3268 | right: Stream<AR, ER, RR> |
| 3269 | ): Stream<[AL, AR], EL | ER, RL | RR> => crossWith(left, right, (l, r) => [l, r])) |
| 3270 | |
| 3271 | /** |
| 3272 | * Creates a cartesian product of elements from two streams using a function. |
| 3273 | * |
| 3274 | * **Details** |
| 3275 | * |
| 3276 | * The `right` stream is rerun for every element in the `left` stream. |
| 3277 | * |
| 3278 | * See also `Stream.zipWith` for the more common point-wise variant. |
| 3279 | * |
| 3280 | * **Example** (Combining cartesian products) |
| 3281 | * |
| 3282 | * ```ts import.meta.vitest |
| 3283 | * import { Effect, Stream } from "effect" |
| 3284 | * |
| 3285 | * const program = Effect.gen(function*() { |
| 3286 | * const left = Stream.make(1, 2) |
| 3287 | * const right = Stream.make("a", "b") |
| 3288 | * const combined = Stream.crossWith(left, right, (n, s) => `${n}-${s}`) |
| 3289 | * const result = yield* Stream.runCollect(combined) |
| 3290 | * result // => [ '1-a', '1-b', '2-a', '2-b' ] |
| 3291 | * }) |
| 3292 | * |
| 3293 | * await Effect.runPromise(program) |
| 3294 | * ``` |
| 3295 | * |
| 3296 | * @category zipping |
| 3297 | * @since 2.0.0 |
| 3298 | */ |
| 3299 | export const crossWith: { |
| 3300 | <AR, ER, RR, AL, A>( |
| 3301 | right: Stream<AR, ER, RR>, |
| 3302 | f: (left: AL, right: AR) => A |
| 3303 | ): <EL, RL>(left: Stream<AL, EL, RL>) => Stream<A, EL | ER, RL | RR> |
| 3304 | <AL, EL, RL, AR, ER, RR, A>( |
| 3305 | left: Stream<AL, EL, RL>, |
| 3306 | right: Stream<AR, ER, RR>, |
| 3307 | f: (left: AL, right: AR) => A |
| 3308 | ): Stream<A, EL | ER, RL | RR> |
| 3309 | } = dual(3, <AL, EL, RL, AR, ER, RR, A>( |
| 3310 | left: Stream<AL, EL, RL>, |
| 3311 | right: Stream<AR, ER, RR>, |
| 3312 | f: (left: AL, right: AR) => A |
| 3313 | ): Stream<A, EL | ER, RL | RR> => flatMap(left, (l) => map(right, (r) => f(l, r)))) |
| 3314 | |
| 3315 | /** |
| 3316 | * Zips two streams point-wise with a combining function, ending when either stream ends. |
| 3317 | * |
| 3318 | * **Example** (Zipping streams with a function) |
| 3319 | * |
| 3320 | * ```ts import.meta.vitest |
| 3321 | * import { Effect, Stream } from "effect" |
| 3322 | * |
| 3323 | * const stream1 = Stream.make(1, 2, 3, 4, 5, 6) |
| 3324 | * const stream2 = Stream.make("a", "b", "c") |
| 3325 | * |
| 3326 | * const zipped = Stream.zipWith(stream1, stream2, (n, s) => `${n}-${s}`) |
| 3327 | * |
| 3328 | * const program = Effect.gen(function*() { |
| 3329 | * const result = yield* Stream.runCollect(zipped) |
| 3330 | * result // => [ '1-a', '2-b', '3-c' ] |
| 3331 | * }) |
| 3332 | * |
| 3333 | * await Effect.runPromise(program) |
| 3334 | * ``` |
| 3335 | * |
| 3336 | * @category zipping |
| 3337 | * @since 2.0.0 |
| 3338 | */ |
| 3339 | export const zipWith: { |
| 3340 | <AR, ER, RR, AL, A>( |
| 3341 | right: Stream<AR, ER, RR>, |
| 3342 | f: (left: AL, right: AR) => A |
| 3343 | ): <EL, RL>(left: Stream<AL, EL, RL>) => Stream<A, EL | ER, RL | RR> |
| 3344 | <AL, EL, RL, AR, ER, RR, A>( |
| 3345 | left: Stream<AL, EL, RL>, |
| 3346 | right: Stream<AR, ER, RR>, |
| 3347 | f: (left: AL, right: AR) => A |
| 3348 | ): Stream<A, EL | ER, RL | RR> |
| 3349 | } = dual(3, <AL, EL, RL, AR, ER, RR, A>( |
| 3350 | left: Stream<AL, EL, RL>, |
| 3351 | right: Stream<AR, ER, RR>, |
| 3352 | f: (left: AL, right: AR) => A |
| 3353 | ): Stream<A, EL | ER, RL | RR> => zipWithArray(left, right, zipArrays(f))) |
| 3354 | |
| 3355 | const zipArrays = <AL, AR, A>( |
| 3356 | f: (left: AL, right: AR) => A |
| 3357 | ) => |
| 3358 | ( |
| 3359 | leftArr: Arr.NonEmptyReadonlyArray<AL>, |
| 3360 | rightArr: Arr.NonEmptyReadonlyArray<AR> |
| 3361 | ) => { |
| 3362 | const minLength = Math.min(leftArr.length, rightArr.length) |
| 3363 | const result: Arr.NonEmptyArray<A> = [] as any |
| 3364 | |
| 3365 | for (let i = 0; i < minLength; i++) { |
| 3366 | result.push(f(leftArr[i], rightArr[i])) |
| 3367 | } |
| 3368 | |
| 3369 | return [result, leftArr.slice(minLength), rightArr.slice(minLength)] as const |
| 3370 | } |
| 3371 | |
| 3372 | /** |
| 3373 | * Zips two streams by applying a function to non-empty arrays of elements. |
| 3374 | * |
| 3375 | * **Details** |
| 3376 | * |
| 3377 | * The function returns output plus leftover arrays that carry into the next pull. |
| 3378 | * |
| 3379 | * **Example** (Zipping stream chunks) |
| 3380 | * |
| 3381 | * ```ts import.meta.vitest |
| 3382 | * import { Array, Effect, Stream } from "effect" |
| 3383 | * |
| 3384 | * const left = Stream.fromArrays([1, 2, 3], [4, 5]) |
| 3385 | * const right = Stream.fromArrays(["a", "b"], ["c", "d", "e"]) |
| 3386 | * |
| 3387 | * const zipped = Stream.zipWithArray(left, right, (leftChunk, rightChunk) => { |
| 3388 | * const minLength = Math.min(leftChunk.length, rightChunk.length) |
| 3389 | * const output = Array.makeBy(minLength, (i) => [leftChunk[i], rightChunk[i]] as const) |
| 3390 | * |
| 3391 | * return [output, leftChunk.slice(minLength), rightChunk.slice(minLength)] |
| 3392 | * }) |
| 3393 | * |
| 3394 | * const program = Effect.gen(function*() { |
| 3395 | * const result = yield* Stream.runCollect(zipped) |
| 3396 | * result // => [ [ 1, 'a' ], [ 2, 'b' ], [ 3, 'c' ], [ 4, 'd' ], [ 5, 'e' ] ] |
| 3397 | * }) |
| 3398 | * |
| 3399 | * await Effect.runPromise(program) |
| 3400 | * ``` |
| 3401 | * |
| 3402 | * @category zipping |
| 3403 | * @since 4.0.0 |
| 3404 | */ |
| 3405 | export const zipWithArray: { |
| 3406 | <AR, ER, RR, AL, A>( |
| 3407 | right: Stream<AR, ER, RR>, |
| 3408 | f: ( |
| 3409 | left: Arr.NonEmptyReadonlyArray<AL>, |
| 3410 | right: Arr.NonEmptyReadonlyArray<AR> |
| 3411 | ) => readonly [ |
| 3412 | output: Arr.NonEmptyReadonlyArray<A>, |
| 3413 | leftoverLeft: ReadonlyArray<AL>, |
| 3414 | leftoverRight: ReadonlyArray<AR> |
| 3415 | ] |
| 3416 | ): <EL, RL>(left: Stream<AL, EL, RL>) => Stream<A, EL | ER, RL | RR> |
| 3417 | <AL, EL, RL, AR, ER, RR, A>( |
| 3418 | left: Stream<AL, EL, RL>, |
| 3419 | right: Stream<AR, ER, RR>, |
| 3420 | f: ( |
| 3421 | left: Arr.NonEmptyReadonlyArray<AL>, |
| 3422 | right: Arr.NonEmptyReadonlyArray<AR> |
| 3423 | ) => readonly [ |
| 3424 | output: Arr.NonEmptyReadonlyArray<A>, |
| 3425 | leftoverLeft: ReadonlyArray<AL>, |
| 3426 | leftoverRight: ReadonlyArray<AR> |
| 3427 | ] |
| 3428 | ): Stream<A, EL | ER, RL | RR> |
| 3429 | } = dual(3, <AL, EL, RL, AR, ER, RR, A>( |
| 3430 | left: Stream<AL, EL, RL>, |
| 3431 | right: Stream<AR, ER, RR>, |
| 3432 | f: ( |
| 3433 | left: Arr.NonEmptyReadonlyArray<AL>, |
| 3434 | right: Arr.NonEmptyReadonlyArray<AR> |
| 3435 | ) => readonly [ |
| 3436 | output: Arr.NonEmptyReadonlyArray<A>, |
| 3437 | leftoverLeft: ReadonlyArray<AL>, |
| 3438 | leftoverRight: ReadonlyArray<AR> |
| 3439 | ] |
| 3440 | ): Stream<A, EL | ER, RL | RR> => |
| 3441 | fromChannel(Channel.fromTransformBracket(Effect.fnUntraced(function*(_, scope) { |
| 3442 | const pullLeft = yield* Channel.toPullScoped(left.channel, scope) |
| 3443 | const pullRight = yield* Channel.toPullScoped(right.channel, scope) |
| 3444 | const pullBoth = Effect.gen(function*() { |
| 3445 | const fiberLeft = yield* Effect.forkIn(pullLeft, scope) |
| 3446 | const fiberRight = yield* Effect.forkIn(pullRight, scope) |
| 3447 | return (yield* Fiber.joinAll([fiberLeft, fiberRight])) as [ |
| 3448 | Arr.NonEmptyReadonlyArray<AL>, |
| 3449 | Arr.NonEmptyReadonlyArray<AR> |
| 3450 | ] |
| 3451 | }) |
| 3452 | |
| 3453 | type State = |
| 3454 | | { _tag: "PullBoth" } |
| 3455 | | { _tag: "PullLeft"; rightArray: Arr.NonEmptyReadonlyArray<AR> } |
| 3456 | | { _tag: "PullRight"; leftArray: Arr.NonEmptyReadonlyArray<AL> } |
| 3457 | let state: State = { _tag: "PullBoth" } |
| 3458 | |
| 3459 | const pull: Effect.Effect< |
| 3460 | Arr.NonEmptyReadonlyArray<A>, |
| 3461 | EL | ER | Cause.Done, |
| 3462 | RL | RR |
| 3463 | > = Effect.gen(function*() { |
| 3464 | const [left, right] = state._tag === "PullBoth" |
| 3465 | ? yield* pullBoth |
| 3466 | : state._tag === "PullLeft" |
| 3467 | ? [yield* pullLeft, state.rightArray] |
| 3468 | : [state.leftArray, yield* pullRight] |
| 3469 | const result = f(left, right) |
| 3470 | if (Arr.isReadonlyArrayNonEmpty(result[1])) { |
| 3471 | state = { _tag: "PullRight", leftArray: result[1] } |
| 3472 | } else if (Arr.isReadonlyArrayNonEmpty(result[2])) { |
| 3473 | state = { _tag: "PullLeft", rightArray: result[2] } |
| 3474 | } else { |
| 3475 | state = { _tag: "PullBoth" } |
| 3476 | } |
| 3477 | return result[0] |
| 3478 | }) |
| 3479 | |
| 3480 | return pull |
| 3481 | })))) |
| 3482 | |
| 3483 | /** |
| 3484 | * Zips this stream with another point-wise and emits tuples of elements from |
| 3485 | * both streams. The new stream ends when either stream ends. |
| 3486 | * |
| 3487 | * **Example** (Zipping streams) |
| 3488 | * |
| 3489 | * ```ts import.meta.vitest |
| 3490 | * import { Effect, Stream } from "effect" |
| 3491 | * |
| 3492 | * const stream1 = Stream.make(1, 2, 3) |
| 3493 | * const stream2 = Stream.make("a", "b", "c") |
| 3494 | * |
| 3495 | * const zipped = Stream.zip(stream1, stream2) |
| 3496 | * |
| 3497 | * const program = Effect.gen(function*() { |
| 3498 | * const result = yield* Stream.runCollect(zipped) |
| 3499 | * result // => [ [ 1, 'a' ], [ 2, 'b' ], [ 3, 'c' ] ] |
| 3500 | * }) |
| 3501 | * |
| 3502 | * await Effect.runPromise(program) |
| 3503 | * ``` |
| 3504 | * |
| 3505 | * @category zipping |
| 3506 | * @since 2.0.0 |
| 3507 | */ |
| 3508 | export const zip: { |
| 3509 | <A2, E2, R2>(that: Stream<A2, E2, R2>): <A, E, R>(self: Stream<A, E, R>) => Stream<[A, A2], E2 | E, R2 | R> |
| 3510 | <A, E, R, A2, E2, R2>(self: Stream<A, E, R>, that: Stream<A2, E2, R2>): Stream<[A, A2], E | E2, R | R2> |
| 3511 | } = dual( |
| 3512 | 2, |
| 3513 | <A, E, R, A2, E2, R2>( |
| 3514 | self: Stream<A, E, R>, |
| 3515 | that: Stream<A2, E2, R2> |
| 3516 | ): Stream<[A, A2], E | E2, R | R2> => zipWith(self, that, (a, a2) => [a, a2]) |
| 3517 | ) |
| 3518 | |
| 3519 | /** |
| 3520 | * Zips this stream with another point-wise and keeps only the values from |
| 3521 | * the left stream. |
| 3522 | * |
| 3523 | * **Details** |
| 3524 | * |
| 3525 | * The resulting stream ends when either side ends. |
| 3526 | * |
| 3527 | * **Example** (Zipping streams while keeping left values) |
| 3528 | * |
| 3529 | * ```ts import.meta.vitest |
| 3530 | * import { Effect, Stream } from "effect" |
| 3531 | * |
| 3532 | * const stream1 = Stream.make(1, 2, 3, 4) |
| 3533 | * const stream2 = Stream.make("a", "b") |
| 3534 | * |
| 3535 | * const program = Effect.gen(function*() { |
| 3536 | * const result = yield* Stream.zipLeft(stream1, stream2).pipe(Stream.runCollect) |
| 3537 | * result // => [ 1, 2 ] |
| 3538 | * }) |
| 3539 | * |
| 3540 | * await Effect.runPromise(program) |
| 3541 | * ``` |
| 3542 | * |
| 3543 | * @category zipping |
| 3544 | * @since 2.0.0 |
| 3545 | */ |
| 3546 | export const zipLeft: { |
| 3547 | <AR, ER, RR>(right: Stream<AR, ER, RR>): <AL, EL, RL>(left: Stream<AL, EL, RL>) => Stream<AL, ER | EL, RR | RL> |
| 3548 | <AL, EL, RL, AR, ER, RR>(left: Stream<AL, EL, RL>, right: Stream<AR, ER, RR>): Stream<AL, EL | ER, RL | RR> |
| 3549 | } = dual( |
| 3550 | 2, |
| 3551 | <AL, EL, RL, AR, ER, RR>( |
| 3552 | left: Stream<AL, EL, RL>, |
| 3553 | right: Stream<AR, ER, RR> |
| 3554 | ): Stream<AL, EL | ER, RL | RR> => |
| 3555 | zipWithArray(left, right, (leftArr, rightArr) => { |
| 3556 | const minLength = Math.min(leftArr.length, rightArr.length) |
| 3557 | const output = leftArr.slice(0, minLength) as Arr.NonEmptyArray<AL> |
| 3558 | const leftoverLeft = leftArr.slice(minLength) |
| 3559 | const leftoverRight = rightArr.slice(minLength) |
| 3560 | |
| 3561 | return [output, leftoverLeft, leftoverRight] as const |
| 3562 | }) |
| 3563 | ) |
| 3564 | |
| 3565 | /** |
| 3566 | * Zips this stream with another point-wise, keeping only right values and ending when either stream ends. |
| 3567 | * |
| 3568 | * **Example** (Zipping streams while keeping right values) |
| 3569 | * |
| 3570 | * ```ts import.meta.vitest |
| 3571 | * import { Effect, Stream } from "effect" |
| 3572 | * |
| 3573 | * const stream1 = Stream.make(1, 2) |
| 3574 | * const stream2 = Stream.make("a", "b", "c", "d") |
| 3575 | * |
| 3576 | * const program = Effect.gen(function*() { |
| 3577 | * const result = yield* Stream.zipRight(stream1, stream2).pipe(Stream.runCollect) |
| 3578 | * result // => [ 'a', 'b' ] |
| 3579 | * }) |
| 3580 | * |
| 3581 | * await Effect.runPromise(program) |
| 3582 | * ``` |
| 3583 | * |
| 3584 | * @category zipping |
| 3585 | * @since 2.0.0 |
| 3586 | */ |
| 3587 | export const zipRight: { |
| 3588 | <AR, ER, RR>(right: Stream<AR, ER, RR>): <AL, EL, RL>(left: Stream<AL, EL, RL>) => Stream<AR, ER | EL, RR | RL> |
| 3589 | <AL, EL, RL, AR, ER, RR>(left: Stream<AL, EL, RL>, right: Stream<AR, ER, RR>): Stream<AR, EL | ER, RL | RR> |
| 3590 | } = dual( |
| 3591 | 2, |
| 3592 | <AL, EL, RL, AR, ER, RR>( |
| 3593 | left: Stream<AL, EL, RL>, |
| 3594 | right: Stream<AR, ER, RR> |
| 3595 | ): Stream<AR, EL | ER, RL | RR> => |
| 3596 | zipWithArray(left, right, (leftArr, rightArr) => { |
| 3597 | const minLength = Math.min(leftArr.length, rightArr.length) |
| 3598 | const output = rightArr.slice(0, minLength) as Arr.NonEmptyArray<AR> |
| 3599 | const leftoverLeft = leftArr.slice(minLength) |
| 3600 | const leftoverRight = rightArr.slice(minLength) |
| 3601 | |
| 3602 | return [output, leftoverLeft, leftoverRight] as const |
| 3603 | }) |
| 3604 | ) |
| 3605 | |
| 3606 | /** |
| 3607 | * Zips this stream with another point-wise and emits tuples of elements from |
| 3608 | * both streams, flattening the left tuple. |
| 3609 | * |
| 3610 | * **Details** |
| 3611 | * |
| 3612 | * The new stream will end when one of the sides ends. |
| 3613 | * |
| 3614 | * **Example** (Zipping and flattening tuples) |
| 3615 | * |
| 3616 | * ```ts import.meta.vitest |
| 3617 | * import { Effect, Stream } from "effect" |
| 3618 | * |
| 3619 | * const program = Effect.gen(function*() { |
| 3620 | * const stream1 = Stream.make( |
| 3621 | * [1, "a"] as const, |
| 3622 | * [2, "b"] as const, |
| 3623 | * [3, "c"] as const |
| 3624 | * ) |
| 3625 | * const stream2 = Stream.make("x", "y", "z") |
| 3626 | * const result = yield* Stream.zipFlatten(stream1, stream2).pipe(Stream.runCollect) |
| 3627 | * |
| 3628 | * result // => [ [ 1, 'a', 'x' ], [ 2, 'b', 'y' ], [ 3, 'c', 'z' ] ] |
| 3629 | * }) |
| 3630 | * |
| 3631 | * await Effect.runPromise(program) |
| 3632 | * ``` |
| 3633 | * |
| 3634 | * @category zipping |
| 3635 | * @since 2.0.0 |
| 3636 | */ |
| 3637 | export const zipFlatten: { |
| 3638 | <A2, E2, R2>( |
| 3639 | that: Stream<A2, E2, R2> |
| 3640 | ): <A extends ReadonlyArray<any>, E, R>(self: Stream<A, E, R>) => Stream<[...A, A2], E2 | E, R2 | R> |
| 3641 | <A extends ReadonlyArray<any>, E, R, A2, E2, R2>( |
| 3642 | self: Stream<A, E, R>, |
| 3643 | that: Stream<A2, E2, R2> |
| 3644 | ): Stream<[...A, A2], E | E2, R | R2> |
| 3645 | } = dual( |
| 3646 | 2, |
| 3647 | <A extends ReadonlyArray<any>, E, R, A2, E2, R2>( |
| 3648 | self: Stream<A, E, R>, |
| 3649 | that: Stream<A2, E2, R2> |
| 3650 | ): Stream<[...A, A2], E | E2, R | R2> => zipWith(self, that, (a, a2) => [...a, a2]) |
| 3651 | ) |
| 3652 | |
| 3653 | /** |
| 3654 | * Zips this stream together with the index of elements. |
| 3655 | * |
| 3656 | * **Example** (Zipping elements with indices) |
| 3657 | * |
| 3658 | * ```ts import.meta.vitest |
| 3659 | * import { Effect, Stream } from "effect" |
| 3660 | * |
| 3661 | * const program = Effect.gen(function*() { |
| 3662 | * const indexed = yield* Stream.make("a", "b", "c", "d").pipe( |
| 3663 | * Stream.zipWithIndex, |
| 3664 | * Stream.runCollect |
| 3665 | * ) |
| 3666 | * indexed // => [ [ 'a', 0 ], [ 'b', 1 ], [ 'c', 2 ], [ 'd', 3 ] ] |
| 3667 | * }) |
| 3668 | * |
| 3669 | * await Effect.runPromise(program) |
| 3670 | * ``` |
| 3671 | * |
| 3672 | * @category zipping |
| 3673 | * @since 2.0.0 |
| 3674 | */ |
| 3675 | export const zipWithIndex = <A, E, R>(self: Stream<A, E, R>): Stream<[A, number], E, R> => map(self, (a, i) => [a, i]) |
| 3676 | |
| 3677 | /** |
| 3678 | * Zips each element with the next element, pairing the final element with |
| 3679 | * `Option.none()`. |
| 3680 | * |
| 3681 | * **Example** (Zipping elements with next values) |
| 3682 | * |
| 3683 | * ```ts import.meta.vitest |
| 3684 | * import { Effect, Option, Stream } from "effect" |
| 3685 | * |
| 3686 | * const stream = Stream.zipWithNext(Stream.make(1, 2, 3, 4)) |
| 3687 | * |
| 3688 | * await Effect.runPromise(Effect.gen(function*() { |
| 3689 | * const values = yield* Stream.runCollect(stream) |
| 3690 | * values // => [[1, Option.some(2)], [2, Option.some(3)], [3, Option.some(4)], [4, Option.none()]] |
| 3691 | * })) |
| 3692 | * ``` |
| 3693 | * |
| 3694 | * @category zipping |
| 3695 | * @since 2.0.0 |
| 3696 | */ |
| 3697 | export const zipWithNext = <A, E, R>(self: Stream<A, E, R>): Stream<[A, Option.Option<A>], E, R> => |
| 3698 | mapAccumArray(self, Option.none<A>, (acc, arr) => { |
| 3699 | let i = 0 |
| 3700 | if (acc._tag === "None") { |
| 3701 | i = 1 |
| 3702 | acc = Option.some(arr[0]) as Option.Some<A> |
| 3703 | } |
| 3704 | const pairs = Arr.empty<[A, Option.Option<A>]>() |
| 3705 | for (; i < arr.length; i++) { |
| 3706 | const value = acc.value |
| 3707 | acc = Option.some(arr[i]) as Option.Some<A> |
| 3708 | pairs.push([value, acc]) |
| 3709 | } |
| 3710 | return [acc, pairs] |
| 3711 | }, { |
| 3712 | onHalt(state) { |
| 3713 | return state._tag === "Some" ? [[state.value, Option.none<A>()]] : [] |
| 3714 | } |
| 3715 | }) |
| 3716 | |
| 3717 | /** |
| 3718 | * Zips each element with its previous element, starting with `None`. |
| 3719 | * |
| 3720 | * **Example** (Zipping elements with previous values) |
| 3721 | * |
| 3722 | * ```ts import.meta.vitest |
| 3723 | * import { Effect, Option, Stream } from "effect" |
| 3724 | * |
| 3725 | * const stream = Stream.zipWithPrevious(Stream.make(1, 2, 3, 4)) |
| 3726 | * |
| 3727 | * const program = Effect.gen(function*() { |
| 3728 | * const result = yield* Stream.runCollect(stream) |
| 3729 | * result // => [[Option.none(), 1], [Option.some(1), 2], [Option.some(2), 3], [Option.some(3), 4]] |
| 3730 | * }) |
| 3731 | * |
| 3732 | * await Effect.runPromise(program) |
| 3733 | * ``` |
| 3734 | * |
| 3735 | * @category zipping |
| 3736 | * @since 2.0.0 |
| 3737 | */ |
| 3738 | export const zipWithPrevious = <A, E, R>(self: Stream<A, E, R>): Stream<[Option.Option<A>, A], E, R> => |
| 3739 | mapAccumArray(self, Option.none<A>, (acc, arr) => { |
| 3740 | const pairs = Arr.empty<[Option.Option<A>, A]>() |
| 3741 | for (let i = 0; i < arr.length; i++) { |
| 3742 | const value = arr[i] |
| 3743 | pairs.push([acc, value]) |
| 3744 | acc = Option.some(arr[i]) |
| 3745 | } |
| 3746 | return [acc, pairs] |
| 3747 | }) |
| 3748 | |
| 3749 | /** |
| 3750 | * Zips each element with its previous and next values. |
| 3751 | * |
| 3752 | * **Example** (Zipping elements with neighbors) |
| 3753 | * |
| 3754 | * ```ts import.meta.vitest |
| 3755 | * import { Console, Effect, Option, Stream } from "effect" |
| 3756 | * |
| 3757 | * const program = Effect.gen(function*() { |
| 3758 | * const values = yield* Stream.make(1, 2, 3).pipe( |
| 3759 | * Stream.zipWithPreviousAndNext, |
| 3760 | * Stream.runCollect |
| 3761 | * ) |
| 3762 | * values // => [[Option.none(), 1, Option.some(2)], [Option.some(1), 2, Option.some(3)], [Option.some(2), 3, Option.none()]] |
| 3763 | * }) |
| 3764 | * |
| 3765 | * await Effect.runPromise(program) |
| 3766 | * ``` |
| 3767 | * |
| 3768 | * @category zipping |
| 3769 | * @since 2.0.0 |
| 3770 | */ |
| 3771 | export const zipWithPreviousAndNext = <A, E, R>( |
| 3772 | self: Stream<A, E, R> |
| 3773 | ): Stream<[Option.Option<A>, A, Option.Option<A>], E, R> => |
| 3774 | mapAccumArray(self, () => ({ |
| 3775 | prev: Option.none<A>(), |
| 3776 | current: Option.none<A>() |
| 3777 | }), (acc, arr) => { |
| 3778 | let i = 0 |
| 3779 | let current: A |
| 3780 | if (acc.current._tag === "None") { |
| 3781 | i = 1 |
| 3782 | current = arr[0] |
| 3783 | acc.current = Option.some(current) |
| 3784 | } else { |
| 3785 | current = acc.current.value |
| 3786 | } |
| 3787 | const pairs = Arr.empty<[Option.Option<A>, A, Option.Option<A>]>() |
| 3788 | for (; i < arr.length; i++) { |
| 3789 | const element = arr[i] |
| 3790 | acc.current = Option.some(element) as Option.Some<A> |
| 3791 | pairs.push([acc.prev, current, acc.current]) |
| 3792 | acc.prev = Option.some(current) |
| 3793 | current = element |
| 3794 | } |
| 3795 | return [acc, pairs] |
| 3796 | }, { |
| 3797 | onHalt(acc) { |
| 3798 | return acc.current._tag === "Some" ? [[acc.prev, acc.current.value, Option.none<A>()]] : [] |
| 3799 | } |
| 3800 | }) |
| 3801 | |
| 3802 | /** |
| 3803 | * Zips multiple streams so that when a value is emitted by any stream, it is |
| 3804 | * combined with the latest values from the other streams to produce a result. |
| 3805 | * |
| 3806 | * **When to use** |
| 3807 | * |
| 3808 | * Use when each stream should contribute its latest value after all streams have |
| 3809 | * emitted at least once. |
| 3810 | * |
| 3811 | * **Gotchas** |
| 3812 | * |
| 3813 | * Note: tracking the latest value is done on a per-array basis. That means |
| 3814 | * that emitted elements that are not the last value in arrays will never be |
| 3815 | * used for zipping. |
| 3816 | * |
| 3817 | * **Example** (Zipping latest values from many streams) |
| 3818 | * |
| 3819 | * ```ts import.meta.vitest |
| 3820 | * import { Effect, Stream } from "effect" |
| 3821 | * |
| 3822 | * const stream = Stream.zipLatestAll( |
| 3823 | * Stream.make(1, 2, 3).pipe(Stream.rechunk(1)), |
| 3824 | * Stream.make("a", "b", "c").pipe(Stream.rechunk(1)), |
| 3825 | * Stream.make(true, false, true).pipe(Stream.rechunk(1)) |
| 3826 | * ) |
| 3827 | * |
| 3828 | * const program = Effect.gen(function*() { |
| 3829 | * const result = yield* Stream.runCollect(stream) |
| 3830 | * result // => [[1, "a", true], [2, "a", true], [2, "b", true], [2, "b", false], [3, "b", false], [3, "c", false], [3, "c", true]] |
| 3831 | * }) |
| 3832 | * |
| 3833 | * await Effect.runPromise(program) |
| 3834 | * ``` |
| 3835 | * |
| 3836 | * @category zipping |
| 3837 | * @since 3.3.0 |
| 3838 | */ |
| 3839 | export const zipLatestAll = <T extends ReadonlyArray<Stream<any, any, any>>>( |
| 3840 | ...streams: T |
| 3841 | ): Stream< |
| 3842 | [T[number]] extends [never] ? never |
| 3843 | : { [K in keyof T]: T[K] extends Stream<infer A, infer _E, infer _R> ? A : never }, |
| 3844 | [T[number]] extends [never] ? never : T[number] extends Stream<infer _A, infer _E, infer _R> ? _E : never, |
| 3845 | [T[number]] extends [never] ? never : T[number] extends Stream<infer _A, infer _E, infer _R> ? _R : never |
| 3846 | > => |
| 3847 | fromChannel(Channel.suspend(() => { |
| 3848 | const latest: Array<any> = [] |
| 3849 | const emitted = new Set<number>() |
| 3850 | const readyLatch = Latch.makeUnsafe() |
| 3851 | return Channel.mergeAll( |
| 3852 | Channel.fromArray( |
| 3853 | streams.map((s, i) => |
| 3854 | s.channel.pipe( |
| 3855 | Channel.flattenArray, |
| 3856 | Channel.mapEffect((a) => { |
| 3857 | latest[i] = a |
| 3858 | if (!emitted.has(i)) { |
| 3859 | emitted.add(i) |
| 3860 | if (emitted.size < streams.length) { |
| 3861 | return readyLatch.await as Effect.Effect<undefined> |
| 3862 | } |
| 3863 | return Effect.as(readyLatch.open, Arr.of(latest.slice())) |
| 3864 | } |
| 3865 | return Effect.succeed(Arr.of(latest.slice())) |
| 3866 | }), |
| 3867 | Channel.filter(isNotUndefined) |
| 3868 | ) |
| 3869 | ) |
| 3870 | ), |
| 3871 | { |
| 3872 | concurrency: "unbounded", |
| 3873 | bufferSize: 0 |
| 3874 | } |
| 3875 | ) |
| 3876 | })) as any |
| 3877 | |
| 3878 | /** |
| 3879 | * Combines two streams by emitting each new element with the latest value from the other stream. |
| 3880 | * |
| 3881 | * **When to use** |
| 3882 | * |
| 3883 | * Use when two streams should start emitting combined pairs after both have |
| 3884 | * produced at least one value. |
| 3885 | * |
| 3886 | * **Gotchas** |
| 3887 | * |
| 3888 | * Note: tracking the latest value is done on a per-array basis. That means |
| 3889 | * that emitted elements that are not the last value in arrays will never be |
| 3890 | * used for zipping. |
| 3891 | * |
| 3892 | * **Example** (Zipping latest values) |
| 3893 | * |
| 3894 | * ```ts import.meta.vitest |
| 3895 | * import { Effect, Stream } from "effect" |
| 3896 | * |
| 3897 | * const program = Effect.gen(function*() { |
| 3898 | * const result = yield* Stream.zipLatest( |
| 3899 | * Stream.make(1), |
| 3900 | * Stream.make("a") |
| 3901 | * ).pipe(Stream.runCollect) |
| 3902 | * |
| 3903 | * result // => [ [ 1, 'a' ] ] |
| 3904 | * }) |
| 3905 | * await Effect.runPromise(program) |
| 3906 | * ``` |
| 3907 | * |
| 3908 | * @category zipping |
| 3909 | * @since 2.0.0 |
| 3910 | */ |
| 3911 | export const zipLatest: { |
| 3912 | <AR, ER, RR>( |
| 3913 | right: Stream<AR, ER, RR> |
| 3914 | ): <AL, EL, RL>(left: Stream<AL, EL, RL>) => Stream<[AL, AR], EL | ER, RL | RR> |
| 3915 | <AL, EL, RL, AR, ER, RR>( |
| 3916 | left: Stream<AL, EL, RL>, |
| 3917 | right: Stream<AR, ER, RR> |
| 3918 | ): Stream<[AL, AR], EL | ER, RL | RR> |
| 3919 | } = dual( |
| 3920 | 2, |
| 3921 | <AL, EL, RL, AR, ER, RR>( |
| 3922 | left: Stream<AL, EL, RL>, |
| 3923 | right: Stream<AR, ER, RR> |
| 3924 | ): Stream<[AL, AR], EL | ER, RL | RR> => zipLatestAll(left, right) |
| 3925 | ) |
| 3926 | |
| 3927 | /** |
| 3928 | * Combines the latest values from both streams whenever either emits, using |
| 3929 | * the provided function. |
| 3930 | * |
| 3931 | * **When to use** |
| 3932 | * |
| 3933 | * Use when two streams should start emitting custom combined values after both |
| 3934 | * have produced at least one value. |
| 3935 | * |
| 3936 | * **Gotchas** |
| 3937 | * |
| 3938 | * Note: tracking the latest value is done on a per-array basis. That means |
| 3939 | * that emitted elements that are not the last value in arrays will never be |
| 3940 | * used for zipping. |
| 3941 | * |
| 3942 | * **Example** (Zipping latest values with a function) |
| 3943 | * |
| 3944 | * ```ts import.meta.vitest |
| 3945 | * import { Effect, Stream } from "effect" |
| 3946 | * |
| 3947 | * await Effect.runPromise(Effect.gen(function*() { |
| 3948 | * const result = yield* Stream.make(1, 2, 3).pipe( |
| 3949 | * Stream.rechunk(1), |
| 3950 | * Stream.zipLatestWith( |
| 3951 | * Stream.make(10, 20).pipe(Stream.rechunk(1)), |
| 3952 | * (n, m) => n + m |
| 3953 | * ), |
| 3954 | * Stream.runCollect |
| 3955 | * ) |
| 3956 | * |
| 3957 | * result // => [ 11, 12, 22, 23 ] |
| 3958 | * })) |
| 3959 | * ``` |
| 3960 | * |
| 3961 | * @category zipping |
| 3962 | * @since 2.0.0 |
| 3963 | */ |
| 3964 | export const zipLatestWith: { |
| 3965 | <AR, ER, RR, AL, A>( |
| 3966 | right: Stream<AR, ER, RR>, |
| 3967 | f: (left: AL, right: AR) => A |
| 3968 | ): <EL, RL>(left: Stream<AL, EL, RL>) => Stream<A, EL | ER, RL | RR> |
| 3969 | <AL, EL, RL, AR, ER, RR, A>( |
| 3970 | left: Stream<AL, EL, RL>, |
| 3971 | right: Stream<AR, ER, RR>, |
| 3972 | f: (left: AL, right: AR) => A |
| 3973 | ): Stream<A, EL | ER, RL | RR> |
| 3974 | } = dual( |
| 3975 | 3, |
| 3976 | <AL, EL, RL, AR, ER, RR, A>( |
| 3977 | left: Stream<AL, EL, RL>, |
| 3978 | right: Stream<AR, ER, RR>, |
| 3979 | f: (left: AL, right: AR) => A |
| 3980 | ): Stream<A, EL | ER, RL | RR> => map(zipLatestAll(left, right), ([a, a2]) => f(a, a2)) |
| 3981 | ) |
| 3982 | |
| 3983 | /** |
| 3984 | * Runs all streams concurrently until one stream emits its first value, then |
| 3985 | * mirrors that winning stream and interrupts the rest. |
| 3986 | * |
| 3987 | * **Details** |
| 3988 | * |
| 3989 | * Failures or completion from losing streams before a winner is chosen are |
| 3990 | * ignored unless every stream fails or completes before emitting. After a |
| 3991 | * winner is chosen, that stream's later failures are propagated. |
| 3992 | * |
| 3993 | * **Example** (Racing multiple streams) |
| 3994 | * |
| 3995 | * ```ts import.meta.vitest |
| 3996 | * import { Effect, Schedule, Stream } from "effect" |
| 3997 | * |
| 3998 | * const program = Effect.gen(function*() { |
| 3999 | * const result = yield* Stream.raceAll( |
| 4000 | * Stream.empty, |
| 4001 | * Stream.make(0, 1, 2) |
| 4002 | * ).pipe(Stream.runCollect) |
| 4003 | * result // => [ 0, 1, 2 ] |
| 4004 | * }) |
| 4005 | * |
| 4006 | * await Effect.runPromise(program) |
| 4007 | * ``` |
| 4008 | * |
| 4009 | * @category racing |
| 4010 | * @since 3.5.0 |
| 4011 | */ |
| 4012 | export const raceAll = <S extends ReadonlyArray<Stream<any, any, any>>>( |
| 4013 | ...streams: S |
| 4014 | ): Stream<Success<S[number]>, Error<S[number]>, Services<S[number]>> => |
| 4015 | fromChannel(Channel.fromTransform((_, scope) => |
| 4016 | Effect.sync(() => { |
| 4017 | let winner: |
| 4018 | | Pull.Pull<Arr.NonEmptyReadonlyArray<Success<S[number]>>, Error<S[number]>, void, Services<S[number]>> |
| 4019 | | undefined |
| 4020 | const race = Effect.raceAll(streams.map((stream) => { |
| 4021 | const childScope = Scope.forkUnsafe(scope) |
| 4022 | return Channel.toPullScoped(stream.channel, childScope).pipe( |
| 4023 | Effect.flatMap((pull) => Effect.zip(Effect.succeed(pull), pull)), |
| 4024 | Effect.onExit((exit) => { |
| 4025 | if (exit._tag === "Success") { |
| 4026 | if (winner) { |
| 4027 | return Scope.close(childScope, exit) |
| 4028 | } |
| 4029 | winner = exit.value[0] |
| 4030 | return Effect.void |
| 4031 | } |
| 4032 | return Scope.close(childScope, exit) |
| 4033 | }), |
| 4034 | Effect.map(([, chunk]) => chunk) |
| 4035 | ) |
| 4036 | })) |
| 4037 | return Effect.suspend(() => winner ?? race) |
| 4038 | }) |
| 4039 | )) |
| 4040 | |
| 4041 | /** |
| 4042 | * Runs both streams concurrently until one stream emits its first value, then |
| 4043 | * mirrors that winning stream and interrupts the other. |
| 4044 | * |
| 4045 | * **Details** |
| 4046 | * |
| 4047 | * A failure or completion from one side before the other side emits does not |
| 4048 | * win the race unless both sides fail or complete before emitting. After a |
| 4049 | * winner is chosen, that stream's later failures are propagated. |
| 4050 | * |
| 4051 | * **Example** (Racing two streams) |
| 4052 | * |
| 4053 | * ```ts import.meta.vitest |
| 4054 | * import { Effect, Stream } from "effect" |
| 4055 | * |
| 4056 | * const stream = Stream.race( |
| 4057 | * Stream.empty, |
| 4058 | * Stream.make(0, 1, 2) |
| 4059 | * ) |
| 4060 | * |
| 4061 | * const program = Effect.gen(function*() { |
| 4062 | * const result = yield* Stream.runCollect(stream) |
| 4063 | * result // => [ 0, 1, 2 ] |
| 4064 | * }) |
| 4065 | * |
| 4066 | * await Effect.runPromise(program) |
| 4067 | * ``` |
| 4068 | * |
| 4069 | * @category racing |
| 4070 | * @since 3.7.0 |
| 4071 | */ |
| 4072 | export const race: { |
| 4073 | <AR, ER, RR>( |
| 4074 | right: Stream<AR, ER, RR> |
| 4075 | ): <AL, EL, RL>(left: Stream<AL, EL, RL>) => Stream<AL | AR, EL | ER, RL | RR> |
| 4076 | <AL, EL, RL, AR, ER, RR>( |
| 4077 | left: Stream<AL, EL, RL>, |
| 4078 | right: Stream<AR, ER, RR> |
| 4079 | ): Stream<AL | AR, EL | ER, RL | RR> |
| 4080 | } = dual(2, <AL, EL, RL, AR, ER, RR>( |
| 4081 | left: Stream<AL, EL, RL>, |
| 4082 | right: Stream<AR, ER, RR> |
| 4083 | ): Stream<AL | AR, EL | ER, RL | RR> => raceAll(left, right)) |
| 4084 | |
| 4085 | /** |
| 4086 | * Filters a stream to the elements that satisfy a predicate. |
| 4087 | * |
| 4088 | * **Example** (Filtering stream values) |
| 4089 | * |
| 4090 | * ```ts import.meta.vitest |
| 4091 | * import { Effect, Stream } from "effect" |
| 4092 | * |
| 4093 | * const program = Effect.gen(function*() { |
| 4094 | * const stream = Stream.make(1, 2, 3, 4).pipe( |
| 4095 | * Stream.filter((n) => n % 2 === 0) |
| 4096 | * ) |
| 4097 | * const values = yield* Stream.runCollect(stream) |
| 4098 | * values // => [ 2, 4 ] |
| 4099 | * }) |
| 4100 | * |
| 4101 | * await Effect.runPromise(program) |
| 4102 | * ``` |
| 4103 | * |
| 4104 | * @category filtering |
| 4105 | * @since 2.0.0 |
| 4106 | */ |
| 4107 | export const filter: { |
| 4108 | <A, B extends A>(refinement: Refinement<NoInfer<A>, B>): <E, R>(self: Stream<A, E, R>) => Stream<B, E, R> |
| 4109 | <A>(predicate: Predicate<NoInfer<A>>): <E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 4110 | <A, E, R, B extends A>(self: Stream<A, E, R>, refinement: Refinement<A, B>): Stream<B, E, R> |
| 4111 | <A, E, R>( |
| 4112 | self: Stream<A, E, R>, |
| 4113 | predicate: Predicate<A> |
| 4114 | ): Stream<A, E, R> |
| 4115 | } = dual( |
| 4116 | 2, |
| 4117 | <A, E, R>( |
| 4118 | self: Stream<A, E, R>, |
| 4119 | predicate: Predicate<A> |
| 4120 | ): Stream<A, E, R> => fromChannel(Channel.filterArray(toChannel(self), predicate)) |
| 4121 | ) |
| 4122 | |
| 4123 | /** |
| 4124 | * Filters and maps stream elements in one pass using a `Filter`. |
| 4125 | * |
| 4126 | * **When to use** |
| 4127 | * |
| 4128 | * Use to keep only stream elements accepted by a `Filter` and emit each filter |
| 4129 | * success value. |
| 4130 | * |
| 4131 | * **Details** |
| 4132 | * |
| 4133 | * `Result.succeed` values are emitted and `Result.fail` values are skipped. |
| 4134 | * |
| 4135 | * @see {@link filter} for keeping original elements with a boolean predicate or refinement |
| 4136 | * @see {@link filterMapEffect} for an effectful `Filter` |
| 4137 | * @see {@link partition} for consuming both filter success and failure values |
| 4138 | * |
| 4139 | * @category filtering |
| 4140 | * @since 2.0.0 |
| 4141 | */ |
| 4142 | export const filterMap: { |
| 4143 | <A, B, X>( |
| 4144 | filter: Filter.Filter<NoInfer<A>, B, X> |
| 4145 | ): <E, R>(self: Stream<A, E, R>) => Stream<B, E, R> |
| 4146 | <A, E, R, B, X>( |
| 4147 | self: Stream<A, E, R>, |
| 4148 | filter: Filter.Filter<A, B, X> |
| 4149 | ): Stream<B, E, R> |
| 4150 | } = dual( |
| 4151 | 2, |
| 4152 | <A, E, R, B, X>( |
| 4153 | self: Stream<A, E, R>, |
| 4154 | filter: Filter.Filter<A, B, X> |
| 4155 | ): Stream<B, E, R> => fromChannel(Channel.filterMapArray(toChannel(self), filter)) |
| 4156 | ) |
| 4157 | |
| 4158 | /** |
| 4159 | * Filters elements in a single pass effectfully. |
| 4160 | * |
| 4161 | * **Example** (Effectfully filtering stream values) |
| 4162 | * |
| 4163 | * ```ts import.meta.vitest |
| 4164 | * import { Effect, Stream } from "effect" |
| 4165 | * |
| 4166 | * const stream = Stream.make(1, 2, 3, 4).pipe(Stream.filterEffect((n) => Effect.succeed(n > 2))) |
| 4167 | * |
| 4168 | * const program = Effect.gen(function*() { |
| 4169 | * const result = yield* Stream.runCollect(stream) |
| 4170 | * result // => [ 3, 4 ] |
| 4171 | * }) |
| 4172 | * |
| 4173 | * await Effect.runPromise(program) |
| 4174 | * ``` |
| 4175 | * |
| 4176 | * @category filtering |
| 4177 | * @since 2.0.0 |
| 4178 | */ |
| 4179 | export const filterEffect: { |
| 4180 | <A, EX, RX>( |
| 4181 | predicate: (a: NoInfer<A>, i: number) => Effect.Effect<boolean, EX, RX> |
| 4182 | ): <E, R>(self: Stream<A, E, R>) => Stream<A, E | EX, R | RX> |
| 4183 | <A, E, R, EX, RX>( |
| 4184 | self: Stream<A, E, R>, |
| 4185 | predicate: (a: NoInfer<A>, i: number) => Effect.Effect<boolean, EX, RX> |
| 4186 | ): Stream<A, E | EX, R | RX> |
| 4187 | } = dual( |
| 4188 | 2, |
| 4189 | <A, E, R, EX, RX>( |
| 4190 | self: Stream<A, E, R>, |
| 4191 | predicate: (a: NoInfer<A>, i: number) => Effect.Effect<boolean, EX, RX> |
| 4192 | ): Stream<A, E | EX, R | RX> => fromChannel(Channel.filterArrayEffect(toChannel(self), predicate)) |
| 4193 | ) |
| 4194 | |
| 4195 | /** |
| 4196 | * Filters and maps elements in one pass effectfully using a `FilterEffect`. |
| 4197 | * |
| 4198 | * **When to use** |
| 4199 | * |
| 4200 | * Use to apply effectful logic that can reject stream elements or emit |
| 4201 | * transformed values before they continue downstream. |
| 4202 | * |
| 4203 | * **Details** |
| 4204 | * |
| 4205 | * `Result.succeed` values are emitted, `Result.fail` values are skipped, and |
| 4206 | * effect failures fail the stream. |
| 4207 | * |
| 4208 | * @see {@link filterMap} for the synchronous `Filter` variant |
| 4209 | * @see {@link filterEffect} for effectfully keeping original elements |
| 4210 | * @see {@link mapEffect} for effectfully transforming every element |
| 4211 | * |
| 4212 | * @category filtering |
| 4213 | * @since 2.0.0 |
| 4214 | */ |
| 4215 | export const filterMapEffect: { |
| 4216 | <A, B, X, EX, RX>( |
| 4217 | filter: Filter.FilterEffect<NoInfer<A>, B, X, EX, RX> |
| 4218 | ): <E, R>(self: Stream<A, E, R>) => Stream<B, E | EX, R | RX> |
| 4219 | <A, E, R, B, X, EX, RX>( |
| 4220 | self: Stream<A, E, R>, |
| 4221 | filter: Filter.FilterEffect<A, B, X, EX, RX> |
| 4222 | ): Stream<B, E | EX, R | RX> |
| 4223 | } = dual( |
| 4224 | 2, |
| 4225 | <A, E, R, B, X, EX, RX>( |
| 4226 | self: Stream<A, E, R>, |
| 4227 | filter: Filter.FilterEffect<A, B, X, EX, RX> |
| 4228 | ): Stream<B, E | EX, R | RX> => fromChannel(Channel.filterMapArrayEffect(toChannel(self), filter)) |
| 4229 | ) |
| 4230 | |
| 4231 | /** |
| 4232 | * Partitions a stream using a `Filter` and exposes passing and failing values |
| 4233 | * as scoped queues. |
| 4234 | * |
| 4235 | * **Details** |
| 4236 | * |
| 4237 | * The queues are backed by a fiber in the current scope and should be consumed |
| 4238 | * while that scope remains open. Each queue fails with the stream error or |
| 4239 | * `Cause.Done` when the source ends. |
| 4240 | * |
| 4241 | * **Example** (Partitioning a stream into queues) |
| 4242 | * |
| 4243 | * ```ts import.meta.vitest |
| 4244 | * import { Effect, Result, Stream } from "effect" |
| 4245 | * |
| 4246 | * const program = Effect.gen(function*() { |
| 4247 | * const [passes, fails] = yield* Stream.make(1, 2, 3, 4).pipe( |
| 4248 | * Stream.partitionQueue((n) => n % 2 === 0 ? Result.succeed(n) : Result.fail(n)) |
| 4249 | * ) |
| 4250 | * |
| 4251 | * const passValues = yield* Stream.fromQueue(passes).pipe(Stream.runCollect) |
| 4252 | * const failValues = yield* Stream.fromQueue(fails).pipe(Stream.runCollect) |
| 4253 | * |
| 4254 | * passValues // => [ 2, 4 ] |
| 4255 | * failValues // => [ 1, 3 ] |
| 4256 | * }) |
| 4257 | * |
| 4258 | * await Effect.runPromise(Effect.scoped(program)) |
| 4259 | * ``` |
| 4260 | * |
| 4261 | * @category filtering |
| 4262 | * @since 4.0.0 |
| 4263 | */ |
| 4264 | export const partitionQueue: { |
| 4265 | <A, Pass, Fail>(filter: Filter.Filter<NoInfer<A>, Pass, Fail>, options?: { |
| 4266 | readonly capacity?: number | "unbounded" | undefined |
| 4267 | }): <E, R>(self: Stream<A, E, R>) => Effect.Effect< |
| 4268 | [ |
| 4269 | passes: Queue.Dequeue<Pass, E | Cause.Done>, |
| 4270 | fails: Queue.Dequeue<Fail, E | Cause.Done> |
| 4271 | ], |
| 4272 | never, |
| 4273 | R | Scope.Scope |
| 4274 | > |
| 4275 | <A, E, R, Pass, Fail>( |
| 4276 | self: Stream<A, E, R>, |
| 4277 | filter: Filter.Filter<NoInfer<A>, Pass, Fail>, |
| 4278 | options?: { |
| 4279 | readonly capacity?: number | "unbounded" | undefined |
| 4280 | } |
| 4281 | ): Effect.Effect< |
| 4282 | [ |
| 4283 | passes: Queue.Dequeue<Pass, E | Cause.Done>, |
| 4284 | fails: Queue.Dequeue<Fail, E | Cause.Done> |
| 4285 | ], |
| 4286 | never, |
| 4287 | R | Scope.Scope |
| 4288 | > |
| 4289 | } = dual( |
| 4290 | (args) => isStream(args[0]), |
| 4291 | Effect.fnUntraced( |
| 4292 | function*<A, E, R, Pass, Fail>( |
| 4293 | self: Stream<A, E, R>, |
| 4294 | filter: Filter.Filter<NoInfer<A>, Pass, Fail>, |
| 4295 | options?: { |
| 4296 | readonly capacity?: number | "unbounded" | undefined |
| 4297 | } |
| 4298 | ): Effect.fn.Return< |
| 4299 | [ |
| 4300 | passes: Queue.Dequeue<Pass, E | Cause.Done>, |
| 4301 | fails: Queue.Dequeue<Fail, E | Cause.Done> |
| 4302 | ], |
| 4303 | never, |
| 4304 | R | Scope.Scope |
| 4305 | > { |
| 4306 | const scope = yield* Effect.scope |
| 4307 | const pull = yield* Channel.toPullScoped(self.channel, scope) |
| 4308 | const capacity = options?.capacity === "unbounded" ? undefined : options?.capacity ?? DefaultChunkSize |
| 4309 | const passes = yield* Queue.make<Pass, E | Cause.Done>({ capacity }) |
| 4310 | const fails = yield* Queue.make<Fail, E | Cause.Done>({ capacity }) |
| 4311 | |
| 4312 | yield* Effect.gen(function*() { |
| 4313 | while (true) { |
| 4314 | const chunk = yield* pull |
| 4315 | const excluded: Array<Fail> = [] |
| 4316 | const satisfying: Array<Pass> = [] |
| 4317 | for (let i = 0; i < chunk.length; i++) { |
| 4318 | const result = filter(chunk[i] as NoInfer<A>) |
| 4319 | if (Result.isFailure(result)) { |
| 4320 | excluded.push(result.failure) |
| 4321 | } else { |
| 4322 | satisfying.push(result.success) |
| 4323 | } |
| 4324 | } |
| 4325 | let passFiber: Fiber.Fiber<any> | undefined = undefined |
| 4326 | if (satisfying.length > 0) { |
| 4327 | const leftover = Queue.offerAllUnsafe(passes, satisfying) |
| 4328 | if (leftover.length > 0) { |
| 4329 | passFiber = yield* Effect.forkChild(Queue.offerAll(passes, leftover)) |
| 4330 | } |
| 4331 | } |
| 4332 | if (excluded.length > 0) { |
| 4333 | const leftover = Queue.offerAllUnsafe(fails, excluded) |
| 4334 | if (leftover.length > 0) { |
| 4335 | yield* Queue.offerAll(fails, leftover) |
| 4336 | } |
| 4337 | } |
| 4338 | if (passFiber) yield* Fiber.join(passFiber) |
| 4339 | } |
| 4340 | }).pipe( |
| 4341 | Effect.onError((cause) => { |
| 4342 | Queue.failCauseUnsafe(passes, cause) |
| 4343 | Queue.failCauseUnsafe(fails, cause) |
| 4344 | return Effect.void |
| 4345 | }), |
| 4346 | Effect.forkIn(scope) |
| 4347 | ) |
| 4348 | |
| 4349 | return [passes, fails] |
| 4350 | } |
| 4351 | ) |
| 4352 | ) |
| 4353 | |
| 4354 | /** |
| 4355 | * Splits a stream with an effectful `Filter`, returning scoped streams for |
| 4356 | * filter successes and failures. |
| 4357 | * |
| 4358 | * **When to use** |
| 4359 | * |
| 4360 | * Use when you need to classify each stream element with an effectful `Filter` |
| 4361 | * and consume both passing and failing mapped values as streams. |
| 4362 | * |
| 4363 | * **Details** |
| 4364 | * |
| 4365 | * The returned streams are backed by queues in the current scope and should be |
| 4366 | * consumed while that scope remains open. The first stream emits success values |
| 4367 | * from the filter, and the second emits failure values. |
| 4368 | * |
| 4369 | * @see {@link partition} for the pure `Filter` variant |
| 4370 | * @see {@link partitionQueue} for the lower-level queue result |
| 4371 | * @see {@link filterMapEffect} for effectful filtering that discards failed filter results |
| 4372 | * |
| 4373 | * @category filtering |
| 4374 | * @since 4.0.0 |
| 4375 | */ |
| 4376 | export const partitionEffect: { |
| 4377 | <A, Pass, Fail, EX, RX>(filter: Filter.FilterEffect<NoInfer<A>, Pass, Fail, EX, RX>, options?: { |
| 4378 | readonly capacity?: number | "unbounded" | undefined |
| 4379 | readonly concurrency?: Concurrency | undefined |
| 4380 | }): <E, R>(self: Stream<A, E, R>) => Effect.Effect< |
| 4381 | [ |
| 4382 | passes: Stream<Pass, E | EX>, |
| 4383 | fails: Stream<Fail, E | EX> |
| 4384 | ], |
| 4385 | never, |
| 4386 | R | RX | Scope.Scope |
| 4387 | > |
| 4388 | <A, E, R, Pass, Fail, EX, RX>( |
| 4389 | self: Stream<A, E, R>, |
| 4390 | filter: Filter.FilterEffect<NoInfer<A>, Pass, Fail, EX, RX>, |
| 4391 | options?: { |
| 4392 | readonly capacity?: number | "unbounded" | undefined |
| 4393 | readonly concurrency?: Concurrency | undefined |
| 4394 | } |
| 4395 | ): Effect.Effect< |
| 4396 | [ |
| 4397 | passes: Stream<Pass, E | EX>, |
| 4398 | fails: Stream<Fail, E | EX> |
| 4399 | ], |
| 4400 | never, |
| 4401 | R | RX | Scope.Scope |
| 4402 | > |
| 4403 | } = dual( |
| 4404 | (args) => isStream(args[0]), |
| 4405 | <A, E, R, Pass, Fail, EX, RX>( |
| 4406 | self: Stream<A, E, R>, |
| 4407 | filter: Filter.FilterEffect<NoInfer<A>, Pass, Fail, EX, RX>, |
| 4408 | options?: { |
| 4409 | readonly capacity?: number | "unbounded" | undefined |
| 4410 | readonly concurrency?: Concurrency | undefined |
| 4411 | } |
| 4412 | ): Effect.Effect< |
| 4413 | [ |
| 4414 | passes: Stream<Pass, E | EX>, |
| 4415 | fails: Stream<Fail, E | EX> |
| 4416 | ], |
| 4417 | never, |
| 4418 | R | RX | Scope.Scope |
| 4419 | > => |
| 4420 | Effect.map( |
| 4421 | partitionQueue<Result.Result<Pass, Fail>, E | EX, R | RX, Pass, Fail>( |
| 4422 | mapEffect(self, (a) => filter(a as NoInfer<A>), options), |
| 4423 | (result) => result, |
| 4424 | options |
| 4425 | ), |
| 4426 | ([passes, fails]) => [fromQueue(passes), fromQueue(fails)] as const |
| 4427 | ) |
| 4428 | ) |
| 4429 | |
| 4430 | /** |
| 4431 | * Splits a stream into scoped passing and failing substreams using a |
| 4432 | * `Filter`. |
| 4433 | * |
| 4434 | * **Details** |
| 4435 | * |
| 4436 | * The returned streams are backed by queues in the current scope and should be |
| 4437 | * consumed while that scope remains open. The faster stream may advance up to |
| 4438 | * `capacity` elements ahead of the slower one. The first stream emits the |
| 4439 | * filter successes and the second emits the filter failures. The default |
| 4440 | * capacity is 16. |
| 4441 | * |
| 4442 | * **Example** (Partitioning a stream) |
| 4443 | * |
| 4444 | * ```ts import.meta.vitest |
| 4445 | * import { Effect, Result, Stream } from "effect" |
| 4446 | * |
| 4447 | * const program = Effect.gen(function*() { |
| 4448 | * const [passes, fails] = yield* Stream.partition( |
| 4449 | * Stream.make(1, 2, 3, 4), |
| 4450 | * (n) => n % 2 === 0 ? Result.succeed(n) : Result.fail(n) |
| 4451 | * ) |
| 4452 | * const evens = yield* Stream.runCollect(passes) |
| 4453 | * const odds = yield* Stream.runCollect(fails) |
| 4454 | * evens // => [ 2, 4 ] |
| 4455 | * odds // => [ 1, 3 ] |
| 4456 | * }) |
| 4457 | * await Effect.runPromise(Effect.scoped(program)) |
| 4458 | * ``` |
| 4459 | * |
| 4460 | * @category filtering |
| 4461 | * @since 2.0.0 |
| 4462 | */ |
| 4463 | export const partition: { |
| 4464 | <A, Pass, Fail>( |
| 4465 | filter: Filter.Filter<NoInfer<A>, Pass, Fail>, |
| 4466 | options?: { |
| 4467 | readonly capacity?: number | "unbounded" | undefined |
| 4468 | } |
| 4469 | ): <E, R>( |
| 4470 | self: Stream<A, E, R> |
| 4471 | ) => Effect.Effect< |
| 4472 | [passes: Stream<Pass, E>, fails: Stream<Fail, E>], |
| 4473 | never, |
| 4474 | R | Scope.Scope |
| 4475 | > |
| 4476 | <A, E, R, Pass, Fail>( |
| 4477 | self: Stream<A, E, R>, |
| 4478 | filter: Filter.Filter<NoInfer<A>, Pass, Fail>, |
| 4479 | options?: { |
| 4480 | readonly capacity?: number | "unbounded" | undefined |
| 4481 | } |
| 4482 | ): Effect.Effect< |
| 4483 | [passes: Stream<Pass, E>, fails: Stream<Fail, E>], |
| 4484 | never, |
| 4485 | R | Scope.Scope |
| 4486 | > |
| 4487 | } = dual( |
| 4488 | (args) => isStream(args[0]), |
| 4489 | <A, E, R, Pass, Fail>( |
| 4490 | self: Stream<A, E, R>, |
| 4491 | filter: Filter.Filter<NoInfer<A>, Pass, Fail>, |
| 4492 | options?: { |
| 4493 | readonly capacity?: number | "unbounded" | undefined |
| 4494 | } |
| 4495 | ): Effect.Effect< |
| 4496 | [passes: Stream<Pass, E>, fails: Stream<Fail, E>], |
| 4497 | never, |
| 4498 | R | Scope.Scope |
| 4499 | > => |
| 4500 | Effect.map( |
| 4501 | partitionQueue(self, filter, { capacity: options?.capacity ?? 16 }), |
| 4502 | ([passes, fails]) => [fromQueue(passes), fromQueue(fails)] as const |
| 4503 | ) |
| 4504 | ) |
| 4505 | |
| 4506 | /** |
| 4507 | * Returns the specified stream if the given condition is satisfied, otherwise |
| 4508 | * returns an empty stream. |
| 4509 | * |
| 4510 | * **Example** (Conditionally keeping a stream) |
| 4511 | * |
| 4512 | * ```ts import.meta.vitest |
| 4513 | * import { Effect, Stream } from "effect" |
| 4514 | * |
| 4515 | * const program = Effect.gen(function*() { |
| 4516 | * const result = yield* Stream.runCollect( |
| 4517 | * Stream.when(Stream.make(1, 2, 3), Effect.succeed(false)) |
| 4518 | * ) |
| 4519 | * result // => [] |
| 4520 | * }) |
| 4521 | * |
| 4522 | * await Effect.runPromise(program) |
| 4523 | * ``` |
| 4524 | * |
| 4525 | * @category filtering |
| 4526 | * @since 2.0.0 |
| 4527 | */ |
| 4528 | export const when: { |
| 4529 | <EX = never, RX = never>( |
| 4530 | test: Effect.Effect<boolean, EX, RX> |
| 4531 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E | EX, R | RX> |
| 4532 | <A, E, R, EX = never, RX = never>( |
| 4533 | self: Stream<A, E, R>, |
| 4534 | test: Effect.Effect<boolean, EX, RX> |
| 4535 | ): Stream<A, E | EX, R | RX> |
| 4536 | } = dual(2, <A, E, R, EX = never, RX = never>( |
| 4537 | self: Stream<A, E, R>, |
| 4538 | test: Effect.Effect<boolean, EX, RX> |
| 4539 | ): Stream<A, E | EX, R | RX> => |
| 4540 | test.pipe( |
| 4541 | Effect.map((pass) => pass ? self : empty), |
| 4542 | unwrap |
| 4543 | )) |
| 4544 | |
| 4545 | /** |
| 4546 | * Runs a sink to peel off enough elements to produce a value and returns that |
| 4547 | * value with the remaining stream in a scope. |
| 4548 | * |
| 4549 | * **Details** |
| 4550 | * |
| 4551 | * The returned stream is only valid within the scope. |
| 4552 | * |
| 4553 | * **Example** (Peeling a stream with a sink) |
| 4554 | * |
| 4555 | * ```ts import.meta.vitest |
| 4556 | * import { Effect, Sink, Stream } from "effect" |
| 4557 | * |
| 4558 | * const stream = Stream.fromArrays([1, 2, 3], [4, 5, 6]) |
| 4559 | * const sink = Sink.take<number>(3) |
| 4560 | * |
| 4561 | * const program = Effect.scoped( |
| 4562 | * Effect.gen(function*() { |
| 4563 | * const [peeled, rest] = yield* Stream.peel(stream, sink) |
| 4564 | * const remaining = yield* Stream.runCollect(rest) |
| 4565 | * const result = [peeled, remaining] // => [[1, 2, 3], [4, 5, 6]] |
| 4566 | * }) |
| 4567 | * ) |
| 4568 | * |
| 4569 | * await Effect.runPromise(program) |
| 4570 | * ``` |
| 4571 | * |
| 4572 | * @category destructors |
| 4573 | * @since 2.0.0 |
| 4574 | */ |
| 4575 | export const peel: { |
| 4576 | <A2, A, E2, R2>( |
| 4577 | sink: Sink.Sink<A2, A, A, E2, R2> |
| 4578 | ): <E, R>(self: Stream<A, E, R>) => Effect.Effect<[A2, Stream<A, E, never>], E2 | E, Scope.Scope | R2 | R> |
| 4579 | <A, E, R, A2, E2, R2>( |
| 4580 | self: Stream<A, E, R>, |
| 4581 | sink: Sink.Sink<A2, A, A, E2, R2> |
| 4582 | ): Effect.Effect<[A2, Stream<A, E, never>], E | E2, Scope.Scope | R | R2> |
| 4583 | } = dual( |
| 4584 | 2, |
| 4585 | Effect.fnUntraced(function*<A, E, R, A2, E2, R2>( |
| 4586 | self: Stream<A, E, R>, |
| 4587 | sink: Sink.Sink<A2, A, A, E2, R2> |
| 4588 | ): Effect.fn.Return<[A2, Stream<A, E, never>], E | E2, Scope.Scope | R | R2> { |
| 4589 | let cause: Cause.Cause<E | Cause.Done<void>> | undefined = undefined |
| 4590 | const originalPull = yield* Channel.toPull(self.channel) |
| 4591 | const pull: Pull.Pull< |
| 4592 | Arr.NonEmptyReadonlyArray<A>, |
| 4593 | E |
| 4594 | > = Effect.catchCause(originalPull, (cause_) => { |
| 4595 | cause = cause_ |
| 4596 | return Effect.failCause(cause_) |
| 4597 | }) |
| 4598 | |
| 4599 | let stream = fromPull(Effect.succeed(pull)) as Stream<A, E> |
| 4600 | const leftover = yield* run(stream, sink) |
| 4601 | if (cause) return [leftover, empty] |
| 4602 | |
| 4603 | stream = fromPull(Effect.succeed(originalPull)) |
| 4604 | return [leftover, stream] |
| 4605 | }) |
| 4606 | ) |
| 4607 | |
| 4608 | /** |
| 4609 | * Buffers up to `capacity` elements so a faster producer can progress |
| 4610 | * independently of a slower consumer. |
| 4611 | * |
| 4612 | * **Details** |
| 4613 | * |
| 4614 | * Finite buffers use the configured queue strategy: `"suspend"` applies |
| 4615 | * backpressure, while `"dropping"` and `"sliding"` may discard elements when |
| 4616 | * the buffer is full. This combinator destroys chunking; use `Stream.rechunk` |
| 4617 | * afterward if you need fixed chunk sizes. |
| 4618 | * |
| 4619 | * **Example** (Buffering stream elements) |
| 4620 | * |
| 4621 | * ```ts import.meta.vitest |
| 4622 | * import { Effect, Stream } from "effect" |
| 4623 | * |
| 4624 | * const program = Effect.gen(function*() { |
| 4625 | * const values = yield* Stream.make(1, 2, 3).pipe( |
| 4626 | * Stream.buffer({ capacity: 1 }), |
| 4627 | * Stream.runCollect |
| 4628 | * ) |
| 4629 | * values // => [ 1, 2, 3 ] |
| 4630 | * }) |
| 4631 | * |
| 4632 | * await Effect.runPromise(program) |
| 4633 | * ``` |
| 4634 | * |
| 4635 | * @category buffering |
| 4636 | * @since 2.0.0 |
| 4637 | */ |
| 4638 | export const buffer: { |
| 4639 | ( |
| 4640 | options: { readonly capacity: "unbounded" } | { |
| 4641 | readonly capacity: number |
| 4642 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 4643 | } |
| 4644 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 4645 | <A, E, R>( |
| 4646 | self: Stream<A, E, R>, |
| 4647 | options: { readonly capacity: "unbounded" } | { |
| 4648 | readonly capacity: number |
| 4649 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 4650 | } |
| 4651 | ): Stream<A, E, R> |
| 4652 | } = dual(2, <A, E, R>( |
| 4653 | self: Stream<A, E, R>, |
| 4654 | options: { readonly capacity: "unbounded" } | { |
| 4655 | readonly capacity: number |
| 4656 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 4657 | } |
| 4658 | ): Stream<A, E, R> => fromChannel(Channel.bufferArray(self.channel, options))) |
| 4659 | |
| 4660 | /** |
| 4661 | * Allows a faster producer to progress independently of a slower consumer by |
| 4662 | * buffering up to `capacity` chunks in a queue. |
| 4663 | * |
| 4664 | * **Details** |
| 4665 | * |
| 4666 | * Finite buffers use the configured queue strategy: `"suspend"` applies |
| 4667 | * backpressure, while `"dropping"` and `"sliding"` may discard chunks when the |
| 4668 | * buffer is full. This combinator preserves chunking and is best with |
| 4669 | * power-of-2 capacities. |
| 4670 | * |
| 4671 | * **Example** (Buffering stream chunks) |
| 4672 | * |
| 4673 | * ```ts import.meta.vitest |
| 4674 | * import { Effect, Stream } from "effect" |
| 4675 | * |
| 4676 | * const program = Effect.gen(function*() { |
| 4677 | * const result = yield* Stream.fromArrays([1, 2], [3, 4]).pipe( |
| 4678 | * Stream.bufferArray({ capacity: 2 }), |
| 4679 | * Stream.runCollect |
| 4680 | * ) |
| 4681 | * result // => [ 1, 2, 3, 4 ] |
| 4682 | * }) |
| 4683 | * |
| 4684 | * await Effect.runPromise(program) |
| 4685 | * ``` |
| 4686 | * |
| 4687 | * @category buffering |
| 4688 | * @since 4.0.0 |
| 4689 | */ |
| 4690 | export const bufferArray: { |
| 4691 | ( |
| 4692 | options: { readonly capacity: "unbounded" } | { |
| 4693 | readonly capacity: number |
| 4694 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 4695 | } |
| 4696 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 4697 | <A, E, R>( |
| 4698 | self: Stream<A, E, R>, |
| 4699 | options: { readonly capacity: "unbounded" } | { |
| 4700 | readonly capacity: number |
| 4701 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 4702 | } |
| 4703 | ): Stream<A, E, R> |
| 4704 | } = dual(2, <A, E, R>( |
| 4705 | self: Stream<A, E, R>, |
| 4706 | options: { readonly capacity: "unbounded" } | { |
| 4707 | readonly capacity: number |
| 4708 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 4709 | } |
| 4710 | ): Stream<A, E, R> => fromChannel(Channel.buffer(self.channel, options))) |
| 4711 | |
| 4712 | /** |
| 4713 | * Switches over to the stream produced by the provided function in case this |
| 4714 | * one fails. Allows recovery from all causes of failure, including |
| 4715 | * interruption if the stream is uninterruptible. |
| 4716 | * |
| 4717 | * **Example** (Catching stream causes) |
| 4718 | * |
| 4719 | * ```ts import.meta.vitest |
| 4720 | * import { Effect, Stream } from "effect" |
| 4721 | * |
| 4722 | * const stream = Stream.make(1, 2).pipe( |
| 4723 | * Stream.concat(Stream.fail("Oops!")), |
| 4724 | * Stream.concat(Stream.make(3, 4)) |
| 4725 | * ) |
| 4726 | * |
| 4727 | * const recovered = stream.pipe( |
| 4728 | * Stream.catchCause(() => Stream.make(999)) |
| 4729 | * ) |
| 4730 | * |
| 4731 | * const program = Effect.gen(function*() { |
| 4732 | * const values = yield* Stream.runCollect(recovered) |
| 4733 | * values // => [ 1, 2, 999 ] |
| 4734 | * }) |
| 4735 | * |
| 4736 | * await Effect.runPromise(program) |
| 4737 | * ``` |
| 4738 | * |
| 4739 | * @category error handling |
| 4740 | * @since 2.0.0 |
| 4741 | */ |
| 4742 | export const catchCause: { |
| 4743 | <E, A2, E2, R2>( |
| 4744 | f: (cause: Cause.Cause<E>) => Stream<A2, E2, R2> |
| 4745 | ): <A, R>(self: Stream<A, E, R>) => Stream<A | A2, E2, R2 | R> |
| 4746 | <A, E, R, A2, E2, R2>( |
| 4747 | self: Stream<A, E, R>, |
| 4748 | f: (cause: Cause.Cause<E>) => Stream<A2, E2, R2> |
| 4749 | ): Stream<A | A2, E2, R | R2> |
| 4750 | } = dual(2, <A, E, R, A2, E2, R2>( |
| 4751 | self: Stream<A, E, R>, |
| 4752 | f: (cause: Cause.Cause<E>) => Stream<A2, E2, R2> |
| 4753 | ): Stream<A | A2, E2, R | R2> => |
| 4754 | self.channel.pipe( |
| 4755 | Channel.catchCause((cause) => f(cause).channel), |
| 4756 | fromChannel |
| 4757 | )) |
| 4758 | |
| 4759 | /** |
| 4760 | * Recovers from defects using the provided function. |
| 4761 | * |
| 4762 | * **Details** |
| 4763 | * |
| 4764 | * Typed failures and interruptions are not caught. |
| 4765 | * |
| 4766 | * **Example** (Recovering from a defect) |
| 4767 | * |
| 4768 | * ```ts import.meta.vitest |
| 4769 | * import { Effect, Stream } from "effect" |
| 4770 | * |
| 4771 | * const stream = Stream.die("boom").pipe( |
| 4772 | * Stream.catchDefect((defect) => Stream.succeed(`recovered: ${defect}`)) |
| 4773 | * ) |
| 4774 | * |
| 4775 | * const result = Effect.runSync(Stream.runCollect(stream)) |
| 4776 | * result // => ["recovered: boom"] |
| 4777 | * ``` |
| 4778 | * |
| 4779 | * @category error handling |
| 4780 | * @since 4.0.0 |
| 4781 | */ |
| 4782 | export const catchDefect: { |
| 4783 | <A2, E2, R2>( |
| 4784 | f: (defect: unknown) => Stream<A2, E2, R2> |
| 4785 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<A | A2, E | E2, R | R2> |
| 4786 | <A, E, R, A2, E2, R2>( |
| 4787 | self: Stream<A, E, R>, |
| 4788 | f: (defect: unknown) => Stream<A2, E2, R2> |
| 4789 | ): Stream<A | A2, E | E2, R | R2> |
| 4790 | } = dual(2, <A, E, R, A2, E2, R2>( |
| 4791 | self: Stream<A, E, R>, |
| 4792 | f: (defect: unknown) => Stream<A2, E2, R2> |
| 4793 | ): Stream<A | A2, E | E2, R | R2> => |
| 4794 | self.channel.pipe( |
| 4795 | Channel.catchDefect((defect) => f(defect).channel), |
| 4796 | fromChannel |
| 4797 | )) |
| 4798 | |
| 4799 | /** |
| 4800 | * Runs an effect when the stream fails without changing its values or error, |
| 4801 | * unless the tap effect itself fails. |
| 4802 | * |
| 4803 | * **Example** (Tapping stream causes) |
| 4804 | * |
| 4805 | * ```ts import.meta.vitest |
| 4806 | * import { Cause, Effect, Stream } from "effect" |
| 4807 | * |
| 4808 | * const observations: Array<boolean> = [] |
| 4809 | * const stream = Stream.make(1, 2).pipe( |
| 4810 | * Stream.concat(Stream.fail("boom")), |
| 4811 | * Stream.tapCause((cause) => Effect.sync(() => observations.push(Cause.isReason(cause)))), |
| 4812 | * Stream.catch(() => Stream.succeed(0)) |
| 4813 | * ) |
| 4814 | * |
| 4815 | * const program = Effect.gen(function* () { |
| 4816 | * const result = yield* Stream.runCollect(stream) |
| 4817 | * result // => [1, 2, 0] |
| 4818 | * }) |
| 4819 | * |
| 4820 | * await Effect.runPromise(program) |
| 4821 | * observations // => [false] |
| 4822 | * ``` |
| 4823 | * |
| 4824 | * @category error handling |
| 4825 | * @since 2.0.0 |
| 4826 | */ |
| 4827 | export const tapCause: { |
| 4828 | <E, A2, E2, R2>( |
| 4829 | f: (cause: Cause.Cause<E>) => Effect.Effect<A2, E2, R2> |
| 4830 | ): <A, R>(self: Stream<A, E, R>) => Stream<A, E | E2, R2 | R> |
| 4831 | <A, E, R, A2, E2, R2>( |
| 4832 | self: Stream<A, E, R>, |
| 4833 | f: (cause: Cause.Cause<E>) => Effect.Effect<A2, E2, R2> |
| 4834 | ): Stream<A, E | E2, R | R2> |
| 4835 | } = dual(2, <A, E, R, A2, E2, R2>( |
| 4836 | self: Stream<A, E, R>, |
| 4837 | f: (cause: Cause.Cause<E>) => Effect.Effect<A2, E2, R2> |
| 4838 | ): Stream<A, E | E2, R | R2> => |
| 4839 | self.channel.pipe( |
| 4840 | Channel.tapCause(f), |
| 4841 | fromChannel |
| 4842 | )) |
| 4843 | |
| 4844 | const catch_: { |
| 4845 | <E, A2, E2, R2>( |
| 4846 | f: (error: E) => Stream<A2, E2, R2> |
| 4847 | ): <A, R>(self: Stream<A, E, R>) => Stream<A | A2, E2, R2 | R> |
| 4848 | <A, E, R, A2, E2, R2>( |
| 4849 | self: Stream<A, E, R>, |
| 4850 | f: (error: E) => Stream<A2, E2, R2> |
| 4851 | ): Stream<A | A2, E2, R | R2> |
| 4852 | } = dual(2, <A, E, R, A2, E2, R2>( |
| 4853 | self: Stream<A, E, R>, |
| 4854 | f: (error: E) => Stream<A2, E2, R2> |
| 4855 | ): Stream<A | A2, E2, R | R2> => fromChannel(Channel.catch(self.channel, (error) => f(error).channel))) |
| 4856 | |
| 4857 | export { |
| 4858 | /** |
| 4859 | * Switches over to the stream produced by the provided function if this one fails. |
| 4860 | * |
| 4861 | * **Example** (Catching stream failures) |
| 4862 | * |
| 4863 | * ```ts import.meta.vitest |
| 4864 | * import { Effect, Stream } from "effect" |
| 4865 | * |
| 4866 | * const stream = Stream.make(1, 2).pipe( |
| 4867 | * Stream.concat(Stream.fail("Oops!")), |
| 4868 | * Stream.catch(() => Stream.make(999)) |
| 4869 | * ) |
| 4870 | * |
| 4871 | * const program = Effect.gen(function*() { |
| 4872 | * const values = yield* Stream.runCollect(stream) |
| 4873 | * values // => [ 1, 2, 999 ] |
| 4874 | * }) |
| 4875 | * |
| 4876 | * await Effect.runPromise(program) |
| 4877 | * ``` |
| 4878 | * |
| 4879 | * @category error handling |
| 4880 | * @since 4.0.0 |
| 4881 | */ |
| 4882 | catch_ as catch |
| 4883 | } |
| 4884 | |
| 4885 | /** |
| 4886 | * Peeks at errors effectfully without changing the stream unless the tap fails. |
| 4887 | * |
| 4888 | * **Example** (Effectfully peeking at errors) |
| 4889 | * |
| 4890 | * ```ts import.meta.vitest |
| 4891 | * import { Effect, Stream } from "effect" |
| 4892 | * |
| 4893 | * const errors: Array<string> = [] |
| 4894 | * const stream = Stream.make(1, 2).pipe( |
| 4895 | * Stream.concat(Stream.fail("boom")), |
| 4896 | * Stream.tapError((error) => Effect.sync(() => errors.push(error))), |
| 4897 | * Stream.catch(() => Stream.make(999)) |
| 4898 | * ) |
| 4899 | * |
| 4900 | * const program = Effect.gen(function*() { |
| 4901 | * const values = yield* Stream.runCollect(stream) |
| 4902 | * values // => [1, 2, 999] |
| 4903 | * }) |
| 4904 | * |
| 4905 | * await Effect.runPromise(program) |
| 4906 | * errors // => ["boom"] |
| 4907 | * ``` |
| 4908 | * |
| 4909 | * @category error handling |
| 4910 | * @since 2.0.0 |
| 4911 | */ |
| 4912 | export const tapError: { |
| 4913 | <E, A2, E2, R2>( |
| 4914 | f: (error: E) => Effect.Effect<A2, E2, R2> |
| 4915 | ): <A, R>(self: Stream<A, E, R>) => Stream<A, E | E2, R2 | R> |
| 4916 | <A, E, R, A2, E2, R2>( |
| 4917 | self: Stream<A, E, R>, |
| 4918 | f: (error: E) => Effect.Effect<A2, E2, R2> |
| 4919 | ): Stream<A, E | E2, R | R2> |
| 4920 | } = dual(2, <A, E, R, A2, E2, R2>( |
| 4921 | self: Stream<A, E, R>, |
| 4922 | f: (error: E) => Effect.Effect<A2, E2, R2> |
| 4923 | ): Stream<A, E | E2, R | R2> => |
| 4924 | self.channel.pipe( |
| 4925 | Channel.tapError(f), |
| 4926 | fromChannel |
| 4927 | )) |
| 4928 | |
| 4929 | /** |
| 4930 | * Peeks at errors with a matching `_tag` effectfully without changing the |
| 4931 | * stream unless the tap fails. |
| 4932 | * |
| 4933 | * **Example** (Effectfully peeking at a tagged error) |
| 4934 | * |
| 4935 | * ```ts import.meta.vitest |
| 4936 | * import { Data, Effect, Stream } from "effect" |
| 4937 | * |
| 4938 | * class NetworkError extends Data.TaggedError("NetworkError")<{ |
| 4939 | * statusCode: number |
| 4940 | * }> {} |
| 4941 | * |
| 4942 | * class ValidationError extends Data.TaggedError("ValidationError")<{ |
| 4943 | * field: string |
| 4944 | * }> {} |
| 4945 | * |
| 4946 | * const seen: Array<number> = [] |
| 4947 | * const stream: Stream.Stream<number, NetworkError | ValidationError> = Stream.fail( |
| 4948 | * new NetworkError({ statusCode: 504 }) |
| 4949 | * ) |
| 4950 | * |
| 4951 | * const program = stream.pipe( |
| 4952 | * Stream.tapErrorTag("NetworkError", (error) => Effect.sync(() => seen.push(error.statusCode))), |
| 4953 | * Stream.catch(() => Stream.make(0)), |
| 4954 | * Stream.runCollect |
| 4955 | * ) |
| 4956 | * |
| 4957 | * await Effect.runPromise(program) // => [ 0 ] |
| 4958 | * seen // => [ 504 ] |
| 4959 | * ``` |
| 4960 | * |
| 4961 | * @see {@link tapError} for peeking at every typed error |
| 4962 | * @see {@link catchTag} for recovering from a tagged error |
| 4963 | * |
| 4964 | * @category error handling |
| 4965 | * @since 4.0.0 |
| 4966 | */ |
| 4967 | export const tapErrorTag: { |
| 4968 | <const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>, E, A1, E1, R1>( |
| 4969 | k: K, |
| 4970 | f: ( |
| 4971 | e: ExtractTag<NoInfer<E>, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K> |
| 4972 | ) => Effect.Effect<A1, E1, R1> |
| 4973 | ): <A, R>(self: Stream<A, E, R>) => Stream<A, E | E1, R1 | R> |
| 4974 | < |
| 4975 | A, |
| 4976 | E, |
| 4977 | R, |
| 4978 | const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>, |
| 4979 | R1, |
| 4980 | E1, |
| 4981 | A1 |
| 4982 | >( |
| 4983 | self: Stream<A, E, R>, |
| 4984 | k: K, |
| 4985 | f: (e: ExtractTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Effect.Effect<A1, E1, R1> |
| 4986 | ): Stream<A, E | E1, R | R1> |
| 4987 | } = dual( |
| 4988 | 3, |
| 4989 | < |
| 4990 | A, |
| 4991 | E, |
| 4992 | R, |
| 4993 | const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>, |
| 4994 | R1, |
| 4995 | E1, |
| 4996 | A1 |
| 4997 | >( |
| 4998 | self: Stream<A, E, R>, |
| 4999 | k: K, |
| 5000 | f: (e: ExtractTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Effect.Effect<A1, E1, R1> |
| 5001 | ): Stream<A, E | E1, R | R1> => { |
| 5002 | const predicate = Array.isArray(k) |
| 5003 | ? ((e: E): e is ExtractTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K> => |
| 5004 | hasProperty(e, "_tag") && k.includes(e._tag)) |
| 5005 | : isTagged(k as string) |
| 5006 | return tapError( |
| 5007 | self, |
| 5008 | (error) => |
| 5009 | predicate(error) |
| 5010 | ? f(error as ExtractTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) |
| 5011 | : Effect.void |
| 5012 | ) |
| 5013 | } |
| 5014 | ) |
| 5015 | |
| 5016 | /** |
| 5017 | * Peeks at defects effectfully without changing the stream unless the tap |
| 5018 | * fails. |
| 5019 | * |
| 5020 | * **Example** (Effectfully peeking at defects) |
| 5021 | * |
| 5022 | * ```ts import.meta.vitest |
| 5023 | * import { Effect, Stream } from "effect" |
| 5024 | * |
| 5025 | * const defects: Array<unknown> = [] |
| 5026 | * const stream = Stream.make(1, 2).pipe( |
| 5027 | * Stream.concat(Stream.die("boom")), |
| 5028 | * Stream.tapDefect((defect) => Effect.sync(() => defects.push(defect))), |
| 5029 | * Stream.catchCause(() => Stream.make(3)) |
| 5030 | * ) |
| 5031 | * |
| 5032 | * const program = Effect.gen(function*() { |
| 5033 | * const values = yield* Stream.runCollect(stream) |
| 5034 | * values // => [ 1, 2, 3 ] |
| 5035 | * }) |
| 5036 | * |
| 5037 | * await Effect.runPromise(program) |
| 5038 | * defects // => [ 'boom' ] |
| 5039 | * ``` |
| 5040 | * |
| 5041 | * @see {@link tapCause} for peeking at the full failure cause |
| 5042 | * @see {@link catchDefect} for recovering from defects |
| 5043 | * |
| 5044 | * @category error handling |
| 5045 | * @since 4.0.0 |
| 5046 | */ |
| 5047 | export const tapDefect: { |
| 5048 | <B, E2, R2>( |
| 5049 | f: (defect: unknown) => Effect.Effect<B, E2, R2> |
| 5050 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E | E2, R | R2> |
| 5051 | <A, E, R, B, E2, R2>( |
| 5052 | self: Stream<A, E, R>, |
| 5053 | f: (defect: unknown) => Effect.Effect<B, E2, R2> |
| 5054 | ): Stream<A, E | E2, R | R2> |
| 5055 | } = dual(2, <A, E, R, B, E2, R2>( |
| 5056 | self: Stream<A, E, R>, |
| 5057 | f: (defect: unknown) => Effect.Effect<B, E2, R2> |
| 5058 | ): Stream<A, E | E2, R | R2> => |
| 5059 | tapCause(self, (cause) => { |
| 5060 | const defect = Cause.findDefect(cause) |
| 5061 | return Result.isSuccess(defect) ? f(defect.success) : Effect.void |
| 5062 | })) |
| 5063 | |
| 5064 | /** |
| 5065 | * Recovers from errors that match a predicate by switching to a recovery stream. |
| 5066 | * |
| 5067 | * **Details** |
| 5068 | * |
| 5069 | * When a failure matches the filter, the stream switches to the recovery |
| 5070 | * stream. Non-matching failures propagate downstream, so the error type is |
| 5071 | * preserved unless the filter narrows it. |
| 5072 | * |
| 5073 | * **Example** (Catching matching failures) |
| 5074 | * |
| 5075 | * ```ts import.meta.vitest |
| 5076 | * import { Effect, Stream } from "effect" |
| 5077 | * |
| 5078 | * const stream = Stream.make(1, 2).pipe( |
| 5079 | * Stream.concat(Stream.fail(42)), |
| 5080 | * Stream.catchIf( |
| 5081 | * (error): error is 42 => error === 42, |
| 5082 | * () => Stream.make(999) |
| 5083 | * ) |
| 5084 | * ) |
| 5085 | * |
| 5086 | * const program = Effect.gen(function*() { |
| 5087 | * const values = yield* Stream.runCollect(stream) |
| 5088 | * values // => [ 1, 2, 999 ] |
| 5089 | * }) |
| 5090 | * |
| 5091 | * await Effect.runPromise(program) |
| 5092 | * ``` |
| 5093 | * |
| 5094 | * @category error handling |
| 5095 | * @since 4.0.0 |
| 5096 | */ |
| 5097 | export const catchIf: { |
| 5098 | <E, EB extends E, A2, E2, R2, A3 = unassigned, E3 = never, R3 = never>( |
| 5099 | refinement: Refinement<NoInfer<E>, EB>, |
| 5100 | f: (e: EB) => Stream<A2, E2, R2>, |
| 5101 | orElse?: ((e: Exclude<E, EB>) => Stream<A3, E3, R3>) | undefined |
| 5102 | ): <A, R>( |
| 5103 | self: Stream<A, E, R> |
| 5104 | ) => Stream<A | A2 | Exclude<A3, unassigned>, E2 | E3 | (A3 extends unassigned ? Exclude<E, EB> : never), R | R2 | R3> |
| 5105 | <E, A2, E2, R2, A3 = unassigned, E3 = never, R3 = never>( |
| 5106 | predicate: Predicate<NoInfer<E>>, |
| 5107 | f: (e: NoInfer<E>) => Stream<A2, E2, R2>, |
| 5108 | orElse?: ((e: NoInfer<E>) => Stream<A3, E3, R3>) | undefined |
| 5109 | ): <A, R>( |
| 5110 | self: Stream<A, E, R> |
| 5111 | ) => Stream<A | A2 | Exclude<A3, unassigned>, E2 | E3 | (A3 extends unassigned ? E : never), R | R2 | R3> |
| 5112 | <A, E, R, EB extends E, A2, E2, R2, A3 = unassigned, E3 = never, R3 = never>( |
| 5113 | self: Stream<A, E, R>, |
| 5114 | refinement: Refinement<E, EB>, |
| 5115 | f: (e: EB) => Stream<A2, E2, R2>, |
| 5116 | orElse?: ((e: Exclude<E, EB>) => Stream<A3, E3, R3>) | undefined |
| 5117 | ): Stream<A | A2 | Exclude<A3, unassigned>, E2 | E3 | (A3 extends unassigned ? Exclude<E, EB> : never), R | R2 | R3> |
| 5118 | <A, E, R, A2, E2, R2, A3 = unassigned, E3 = never, R3 = never>( |
| 5119 | self: Stream<A, E, R>, |
| 5120 | predicate: Predicate<E>, |
| 5121 | f: (e: E) => Stream<A2, E2, R2>, |
| 5122 | orElse?: ((e: E) => Stream<A3, E3, R3>) | undefined |
| 5123 | ): Stream<A | A2 | Exclude<A3, unassigned>, E2 | E3 | (A3 extends unassigned ? E : never), R | R2 | R3> |
| 5124 | } = dual((args) => isStream(args[0]), < |
| 5125 | A, |
| 5126 | E, |
| 5127 | R, |
| 5128 | A2, |
| 5129 | E2, |
| 5130 | R2, |
| 5131 | A3 = never, |
| 5132 | E3 = E, |
| 5133 | R3 = never |
| 5134 | >( |
| 5135 | self: Stream<A, E, R>, |
| 5136 | predicate: Predicate<E>, |
| 5137 | f: (failure: E) => Stream<A2, E2, R2>, |
| 5138 | orElse?: ((failure: E) => Stream<A3, E3, R3>) | undefined |
| 5139 | ): Stream<A | A2 | A3, E2 | E3, R | R2 | R3> => |
| 5140 | fromChannel( |
| 5141 | Channel.catchIf( |
| 5142 | toChannel(self), |
| 5143 | predicate, |
| 5144 | (e) => f(e).channel, |
| 5145 | orElse && ((e) => orElse(e).channel) |
| 5146 | ) |
| 5147 | )) |
| 5148 | |
| 5149 | /** |
| 5150 | * Recovers from errors that match a `Filter` by switching to a recovery |
| 5151 | * stream. |
| 5152 | * |
| 5153 | * **When to use** |
| 5154 | * |
| 5155 | * Use to recover from stream errors with a reusable `Filter` when matching can |
| 5156 | * also narrow or transform the error before choosing the recovery stream. |
| 5157 | * |
| 5158 | * **Details** |
| 5159 | * |
| 5160 | * Successful filter results are passed to `f`. Failed filter results go to |
| 5161 | * `orElse` when provided; otherwise the filter failure is re-failed. |
| 5162 | * |
| 5163 | * @see {@link catchIf} for predicate or refinement based recovery |
| 5164 | * @see {@link catchTag} for `_tag` based recovery from one tagged error |
| 5165 | * @see {@link catchTags} for `_tag` based recovery from multiple tagged errors |
| 5166 | * @see {@link catchCauseFilter} for filtering full causes |
| 5167 | * |
| 5168 | * @category error handling |
| 5169 | * @since 4.0.0 |
| 5170 | */ |
| 5171 | export const catchFilter: { |
| 5172 | <E, EB, A2, E2, R2, X, A3 = unassigned, E3 = never, R3 = never>( |
| 5173 | filter: Filter.Filter<NoInfer<E>, EB, X>, |
| 5174 | f: (failure: EB) => Stream<A2, E2, R2>, |
| 5175 | orElse?: ((failure: X) => Stream<A3, E3, R3>) | undefined |
| 5176 | ): <A, R>( |
| 5177 | self: Stream<A, E, R> |
| 5178 | ) => Stream<A | A2 | Exclude<A3, unassigned>, E2 | E3 | (A3 extends unassigned ? X : never), R | R2 | R3> |
| 5179 | <A, E, R, EB, A2, E2, R2, X, A3 = unassigned, E3 = never, R3 = never>( |
| 5180 | self: Stream<A, E, R>, |
| 5181 | filter: Filter.Filter<NoInfer<E>, EB, X>, |
| 5182 | f: (failure: EB) => Stream<A2, E2, R2>, |
| 5183 | orElse?: ((failure: X) => Stream<A3, E3, R3>) | undefined |
| 5184 | ): Stream<A | A2 | Exclude<A3, unassigned>, E2 | E3 | (A3 extends unassigned ? X : never), R | R2 | R3> |
| 5185 | } = dual((args) => isStream(args[0]), < |
| 5186 | A, |
| 5187 | E, |
| 5188 | R, |
| 5189 | EB, |
| 5190 | A2, |
| 5191 | E2, |
| 5192 | R2, |
| 5193 | X, |
| 5194 | A3 = never, |
| 5195 | E3 = X, |
| 5196 | R3 = never |
| 5197 | >( |
| 5198 | self: Stream<A, E, R>, |
| 5199 | filter: Filter.Filter<NoInfer<E>, EB, X>, |
| 5200 | f: (failure: EB) => Stream<A2, E2, R2>, |
| 5201 | orElse?: ((failure: X) => Stream<A3, E3, R3>) | undefined |
| 5202 | ): Stream<A | A2 | A3, E2 | E3, R | R2 | R3> => |
| 5203 | fromChannel( |
| 5204 | Channel.catchFilter( |
| 5205 | toChannel(self), |
| 5206 | filter, |
| 5207 | (e) => f(e).channel, |
| 5208 | orElse && ((e) => orElse(e).channel) |
| 5209 | ) |
| 5210 | )) |
| 5211 | |
| 5212 | /** |
| 5213 | * Recovers from failures whose `_tag` matches the provided value by switching to |
| 5214 | * the stream returned by `f`. |
| 5215 | * |
| 5216 | * **When to use** |
| 5217 | * |
| 5218 | * Use when you need to handle a specific error case from a stream whose error |
| 5219 | * type is a tagged union with a readonly `_tag` field. |
| 5220 | * |
| 5221 | * **Example** (Catching tagged failures) |
| 5222 | * |
| 5223 | * ```ts import.meta.vitest |
| 5224 | * import { Data, Effect, Stream } from "effect" |
| 5225 | * |
| 5226 | * class HttpError extends Data.TaggedError("HttpError")<{ message: string }> {} |
| 5227 | * |
| 5228 | * const stream = Stream.fail(new HttpError({ message: "timeout" })) |
| 5229 | * |
| 5230 | * const recovered = Stream.catchTag(stream, "HttpError", (error) => |
| 5231 | * Stream.make(`Recovered: ${error.message}`) |
| 5232 | * ) |
| 5233 | * |
| 5234 | * const program = Effect.gen(function*() { |
| 5235 | * const values = yield* Stream.runCollect(recovered) |
| 5236 | * values // => [ 'Recovered: timeout' ] |
| 5237 | * }) |
| 5238 | * |
| 5239 | * await Effect.runPromise(program) |
| 5240 | * ``` |
| 5241 | * |
| 5242 | * @category error handling |
| 5243 | * @since 2.0.0 |
| 5244 | */ |
| 5245 | export const catchTag: { |
| 5246 | < |
| 5247 | const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>, |
| 5248 | E, |
| 5249 | A1, |
| 5250 | E1, |
| 5251 | R1, |
| 5252 | A2 = unassigned, |
| 5253 | E2 = never, |
| 5254 | R2 = never |
| 5255 | >( |
| 5256 | k: K, |
| 5257 | f: ( |
| 5258 | e: ExtractTag<NoInfer<E>, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K> |
| 5259 | ) => Stream<A1, E1, R1>, |
| 5260 | orElse?: |
| 5261 | | ((e: ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Stream<A2, E2, R2>) |
| 5262 | | undefined |
| 5263 | ): <A, R>( |
| 5264 | self: Stream<A, E, R> |
| 5265 | ) => Stream< |
| 5266 | A | A1 | Exclude<A2, unassigned>, |
| 5267 | | E1 |
| 5268 | | E2 |
| 5269 | | (A2 extends unassigned ? ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K> : never), |
| 5270 | R | R1 | R2 |
| 5271 | > |
| 5272 | < |
| 5273 | A, |
| 5274 | E, |
| 5275 | R, |
| 5276 | const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>, |
| 5277 | R1, |
| 5278 | E1, |
| 5279 | A1, |
| 5280 | A2 = unassigned, |
| 5281 | E2 = never, |
| 5282 | R2 = never |
| 5283 | >( |
| 5284 | self: Stream<A, E, R>, |
| 5285 | k: K, |
| 5286 | f: (e: ExtractTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Stream<A1, E1, R1>, |
| 5287 | orElse?: |
| 5288 | | ((e: ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Stream<A2, E2, R2>) |
| 5289 | | undefined |
| 5290 | ): Stream< |
| 5291 | A | A1 | Exclude<A2, unassigned>, |
| 5292 | | E1 |
| 5293 | | E2 |
| 5294 | | (A2 extends unassigned ? ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K> : never), |
| 5295 | R | R1 | R2 |
| 5296 | > |
| 5297 | } = dual( |
| 5298 | (args) => isStream(args[0]), |
| 5299 | < |
| 5300 | A, |
| 5301 | E, |
| 5302 | R, |
| 5303 | const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>, |
| 5304 | R1, |
| 5305 | E1, |
| 5306 | A1, |
| 5307 | A2 = never, |
| 5308 | E2 = ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>, |
| 5309 | R2 = never |
| 5310 | >( |
| 5311 | self: Stream<A, E, R>, |
| 5312 | k: K, |
| 5313 | f: (e: ExtractTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Stream<A1, E1, R1>, |
| 5314 | orElse?: |
| 5315 | | ((e: ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Stream<A2, E2, R2>) |
| 5316 | | undefined |
| 5317 | ): Stream<A1 | A | A2, E1 | E2, R1 | R | R2> => { |
| 5318 | const pred = Array.isArray(k) |
| 5319 | ? ((e: E): e is any => hasProperty(e, "_tag") && k.includes(e._tag)) |
| 5320 | : isTagged(k as string) |
| 5321 | return catchIf(self, pred, f, orElse as any) as any |
| 5322 | } |
| 5323 | ) |
| 5324 | |
| 5325 | /** |
| 5326 | * Switches to a recovery stream based on matching `_tag` handlers. |
| 5327 | * |
| 5328 | * **Example** (Catching tagged failures with handlers) |
| 5329 | * |
| 5330 | * ```ts import.meta.vitest |
| 5331 | * import { Effect, Stream } from "effect" |
| 5332 | * |
| 5333 | * class NotFound { |
| 5334 | * readonly _tag = "NotFound" |
| 5335 | * constructor(readonly resource: string) {} |
| 5336 | * } |
| 5337 | * |
| 5338 | * class Unauthorized { |
| 5339 | * readonly _tag = "Unauthorized" |
| 5340 | * constructor(readonly user: string) {} |
| 5341 | * } |
| 5342 | * |
| 5343 | * const stream = Stream.fail(new NotFound("profile")) |
| 5344 | * |
| 5345 | * const program = Effect.gen(function* () { |
| 5346 | * const result = yield* stream.pipe( |
| 5347 | * Stream.catchTags({ |
| 5348 | * NotFound: () => Stream.succeed("fallback"), |
| 5349 | * Unauthorized: () => Stream.succeed("login") |
| 5350 | * }), |
| 5351 | * Stream.runCollect |
| 5352 | * ) |
| 5353 | * result // => [ 'fallback' ] |
| 5354 | * }) |
| 5355 | * |
| 5356 | * await Effect.runPromise(program) |
| 5357 | * ``` |
| 5358 | * |
| 5359 | * @category error handling |
| 5360 | * @since 2.0.0 |
| 5361 | */ |
| 5362 | export const catchTags: { |
| 5363 | < |
| 5364 | E, |
| 5365 | Cases extends |
| 5366 | & { [K in Extract<E, { _tag: string }>["_tag"]]+?: ((error: Extract<E, { _tag: K }>) => Stream<any, any, any>) } |
| 5367 | & (unknown extends E ? {} : { [K in Exclude<keyof Cases, Extract<E, { _tag: string }>["_tag"]>]: never }), |
| 5368 | A2 = unassigned, |
| 5369 | E2 = never, |
| 5370 | R2 = never |
| 5371 | >( |
| 5372 | cases: Cases, |
| 5373 | orElse?: ((e: Exclude<E, { _tag: keyof Cases }>) => Stream<A2, E2, R2>) | undefined |
| 5374 | ): <A, R>(self: Stream<A, E, R>) => Stream< |
| 5375 | | A |
| 5376 | | Exclude<A2, unassigned> |
| 5377 | | { |
| 5378 | [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Stream<infer A, any, any>) ? A : never |
| 5379 | }[keyof Cases], |
| 5380 | | E2 |
| 5381 | | (A2 extends unassigned ? Exclude<E, { _tag: keyof Cases }> : never) |
| 5382 | | { |
| 5383 | [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Stream<any, infer E, any>) ? E : never |
| 5384 | }[keyof Cases], |
| 5385 | | R |
| 5386 | | R2 |
| 5387 | | { |
| 5388 | [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Stream<any, any, infer R>) ? R : never |
| 5389 | }[keyof Cases] |
| 5390 | > |
| 5391 | < |
| 5392 | R, |
| 5393 | E, |
| 5394 | A, |
| 5395 | Cases extends |
| 5396 | & { [K in Extract<E, { _tag: string }>["_tag"]]+?: ((error: Extract<E, { _tag: K }>) => Stream<any, any, any>) } |
| 5397 | & (unknown extends E ? {} : { [K in Exclude<keyof Cases, Extract<E, { _tag: string }>["_tag"]>]: never }), |
| 5398 | A2 = unassigned, |
| 5399 | E2 = never, |
| 5400 | R2 = never |
| 5401 | >( |
| 5402 | self: Stream<A, E, R>, |
| 5403 | cases: Cases, |
| 5404 | orElse?: ((e: Exclude<E, { _tag: keyof Cases }>) => Stream<A2, E2, R2>) | undefined |
| 5405 | ): Stream< |
| 5406 | | A |
| 5407 | | Exclude<A2, unassigned> |
| 5408 | | { |
| 5409 | [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Stream<infer A, any, any>) ? A : never |
| 5410 | }[keyof Cases], |
| 5411 | | E2 |
| 5412 | | (A2 extends unassigned ? Exclude<E, { _tag: keyof Cases }> : never) |
| 5413 | | { |
| 5414 | [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Stream<any, infer E, any>) ? E : never |
| 5415 | }[keyof Cases], |
| 5416 | | R |
| 5417 | | R2 |
| 5418 | | { |
| 5419 | [K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Stream<any, any, infer R>) ? R : never |
| 5420 | }[keyof Cases] |
| 5421 | > |
| 5422 | } = dual((args) => isStream(args[0]), (self, cases, orElse) => { |
| 5423 | let keys: Array<string> |
| 5424 | return catchFilter( |
| 5425 | self, |
| 5426 | (e: any) => { |
| 5427 | keys ??= Object.keys(cases) |
| 5428 | return hasProperty(e, "_tag") && isString(e["_tag"]) && keys.includes(e["_tag"]) |
| 5429 | ? Result.succeed(e) |
| 5430 | : Result.fail(e) |
| 5431 | }, |
| 5432 | (e: any) => cases[e["_tag"] as string](e), |
| 5433 | orElse |
| 5434 | ) |
| 5435 | }) |
| 5436 | |
| 5437 | /** |
| 5438 | * Catches a specific reason within a tagged error. |
| 5439 | * |
| 5440 | * **When to use** |
| 5441 | * |
| 5442 | * Use to handle nested error causes without removing the parent error |
| 5443 | * from the error channel. |
| 5444 | * |
| 5445 | * **Details** |
| 5446 | * |
| 5447 | * The handler receives the unwrapped reason. |
| 5448 | * |
| 5449 | * **Example** (Catching a tagged error reason) |
| 5450 | * |
| 5451 | * ```ts import.meta.vitest |
| 5452 | * import { Data, Effect, Stream } from "effect" |
| 5453 | * |
| 5454 | * class RateLimitError extends Data.TaggedError("RateLimitError")<{ |
| 5455 | * retryAfter: number |
| 5456 | * }> {} |
| 5457 | * |
| 5458 | * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ |
| 5459 | * limit: number |
| 5460 | * }> {} |
| 5461 | * |
| 5462 | * class AiError extends Data.TaggedError("AiError")<{ |
| 5463 | * reason: RateLimitError | QuotaExceededError |
| 5464 | * }> {} |
| 5465 | * |
| 5466 | * const stream = Stream.fail( |
| 5467 | * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) |
| 5468 | * ) |
| 5469 | * |
| 5470 | * const program = Effect.gen(function*() { |
| 5471 | * const values = yield* stream.pipe( |
| 5472 | * Stream.catchReason("AiError", "RateLimitError", (reason) => |
| 5473 | * Stream.succeed(`retry: ${reason.retryAfter}`) |
| 5474 | * ), |
| 5475 | * Stream.runCollect |
| 5476 | * ) |
| 5477 | * values // => [ 'retry: 60' ] |
| 5478 | * }) |
| 5479 | * |
| 5480 | * await Effect.runPromise(program) |
| 5481 | * ``` |
| 5482 | * |
| 5483 | * @category error handling |
| 5484 | * @since 4.0.0 |
| 5485 | */ |
| 5486 | export const catchReason: { |
| 5487 | < |
| 5488 | K extends Tags<E>, |
| 5489 | E, |
| 5490 | RK extends ReasonTags<ExtractTag<NoInfer<E>, K>>, |
| 5491 | A2, |
| 5492 | E2, |
| 5493 | R2, |
| 5494 | A3 = unassigned, |
| 5495 | E3 = never, |
| 5496 | R3 = never |
| 5497 | >( |
| 5498 | errorTag: K, |
| 5499 | reasonTag: RK, |
| 5500 | f: ( |
| 5501 | reason: ExtractReason<ExtractTag<NoInfer<E>, K>, RK>, |
| 5502 | error: NarrowReason<ExtractTag<NoInfer<E>, K>, RK> |
| 5503 | ) => Stream<A2, E2, R2>, |
| 5504 | orElse?: |
| 5505 | | (( |
| 5506 | reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, RK>, |
| 5507 | error: OmitReason<ExtractTag<NoInfer<E>, K>, RK> |
| 5508 | ) => Stream<A3, E3, R3>) |
| 5509 | | undefined |
| 5510 | ): <A, R>( |
| 5511 | self: Stream<A, E, R> |
| 5512 | ) => Stream< |
| 5513 | A | A2 | Exclude<A3, unassigned>, |
| 5514 | ExcludeTag<E, K> | E2 | E3 | (A3 extends unassigned ? ExtractTag<E, K> : never), |
| 5515 | R | R2 | R3 |
| 5516 | > |
| 5517 | < |
| 5518 | A, |
| 5519 | E, |
| 5520 | R, |
| 5521 | K extends Tags<E>, |
| 5522 | RK extends ReasonTags<ExtractTag<E, K>>, |
| 5523 | A2, |
| 5524 | E2, |
| 5525 | R2, |
| 5526 | A3 = unassigned, |
| 5527 | E3 = never, |
| 5528 | R3 = never |
| 5529 | >( |
| 5530 | self: Stream<A, E, R>, |
| 5531 | errorTag: K, |
| 5532 | reasonTag: RK, |
| 5533 | f: (reason: ExtractReason<ExtractTag<E, K>, RK>, error: NarrowReason<ExtractTag<E, K>, RK>) => Stream<A2, E2, R2>, |
| 5534 | orElse?: |
| 5535 | | ((reason: ExcludeReason<ExtractTag<E, K>, RK>, error: OmitReason<ExtractTag<E, K>, RK>) => Stream<A3, E3, R3>) |
| 5536 | | undefined |
| 5537 | ): Stream< |
| 5538 | A | A2 | Exclude<A3, unassigned>, |
| 5539 | ExcludeTag<E, K> | E2 | E3 | (A3 extends unassigned ? ExtractTag<E, K> : never), |
| 5540 | R | R2 | R3 |
| 5541 | > |
| 5542 | } = dual( |
| 5543 | (args) => isStream(args[0]), |
| 5544 | < |
| 5545 | A, |
| 5546 | E, |
| 5547 | R, |
| 5548 | K extends Tags<E>, |
| 5549 | RK extends ReasonTags<ExtractTag<E, K>>, |
| 5550 | A2, |
| 5551 | E2, |
| 5552 | R2, |
| 5553 | A3 = unassigned, |
| 5554 | E3 = never, |
| 5555 | R3 = never |
| 5556 | >( |
| 5557 | self: Stream<A, E, R>, |
| 5558 | errorTag: K, |
| 5559 | reasonTag: RK, |
| 5560 | f: (reason: ExtractReason<ExtractTag<E, K>, RK>, error: NarrowReason<ExtractTag<E, K>, RK>) => Stream<A2, E2, R2>, |
| 5561 | orElse?: |
| 5562 | | ((reason: ExcludeReason<ExtractTag<E, K>, RK>, error: OmitReason<ExtractTag<E, K>, RK>) => Stream<A3, E3, R3>) |
| 5563 | | undefined |
| 5564 | ): Stream< |
| 5565 | A | A2 | Exclude<A3, unassigned>, |
| 5566 | ExcludeTag<E, K> | E2 | E3 | (A3 extends unassigned ? ExtractTag<E, K> : never), |
| 5567 | R | R2 | R3 |
| 5568 | > => |
| 5569 | fromChannel( |
| 5570 | Channel.catchReason( |
| 5571 | toChannel(self), |
| 5572 | errorTag, |
| 5573 | reasonTag, |
| 5574 | (reason, error) => f(reason, error).channel, |
| 5575 | orElse && ((reason, error) => orElse(reason, error).channel) |
| 5576 | ) |
| 5577 | ) as any |
| 5578 | ) |
| 5579 | |
| 5580 | /** |
| 5581 | * Catches multiple reasons within a tagged error using an object of handlers. |
| 5582 | * |
| 5583 | * **Example** (Catching tagged error reasons) |
| 5584 | * |
| 5585 | * ```ts import.meta.vitest |
| 5586 | * import { Data, Effect, Stream } from "effect" |
| 5587 | * |
| 5588 | * class RateLimitError extends Data.TaggedError("RateLimitError")<{ |
| 5589 | * retryAfter: number |
| 5590 | * }> {} |
| 5591 | * |
| 5592 | * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ |
| 5593 | * limit: number |
| 5594 | * }> {} |
| 5595 | * |
| 5596 | * class AiError extends Data.TaggedError("AiError")<{ |
| 5597 | * reason: RateLimitError | QuotaExceededError |
| 5598 | * }> {} |
| 5599 | * |
| 5600 | * const stream = Stream.fail( |
| 5601 | * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) |
| 5602 | * ) |
| 5603 | * |
| 5604 | * const program = Effect.gen(function*() { |
| 5605 | * const values = yield* stream.pipe( |
| 5606 | * Stream.catchReasons("AiError", { |
| 5607 | * RateLimitError: (reason) => Stream.succeed(`retry: ${reason.retryAfter}`), |
| 5608 | * QuotaExceededError: (reason) => Stream.succeed(`quota: ${reason.limit}`) |
| 5609 | * }), |
| 5610 | * Stream.runCollect |
| 5611 | * ) |
| 5612 | * values // => [ 'retry: 60' ] |
| 5613 | * }) |
| 5614 | * |
| 5615 | * await Effect.runPromise(program) |
| 5616 | * ``` |
| 5617 | * |
| 5618 | * @category error handling |
| 5619 | * @since 4.0.0 |
| 5620 | */ |
| 5621 | export const catchReasons: { |
| 5622 | < |
| 5623 | K extends Tags<E>, |
| 5624 | E, |
| 5625 | Cases extends { |
| 5626 | [RK in ReasonTags<ExtractTag<NoInfer<E>, K>>]+?: ( |
| 5627 | reason: ExtractReason<ExtractTag<NoInfer<E>, K>, RK>, |
| 5628 | error: NarrowReason<ExtractTag<NoInfer<E>, K>, RK> |
| 5629 | ) => Stream<any, any, any> |
| 5630 | }, |
| 5631 | A2 = unassigned, |
| 5632 | E2 = never, |
| 5633 | R2 = never |
| 5634 | >( |
| 5635 | errorTag: K, |
| 5636 | cases: Cases, |
| 5637 | orElse?: |
| 5638 | | (( |
| 5639 | reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>, |
| 5640 | error: OmitReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>> |
| 5641 | ) => Stream<A2, E2, R2>) |
| 5642 | | undefined |
| 5643 | ): <A, R>(self: Stream<A, E, R>) => Stream< |
| 5644 | | A |
| 5645 | | Exclude<A2, unassigned> |
| 5646 | | { |
| 5647 | [RK in keyof Cases]: Cases[RK] extends (...args: Array<any>) => Stream<infer A, any, any> ? A : never |
| 5648 | }[keyof Cases], |
| 5649 | | ExcludeTag<E, K> |
| 5650 | | E2 |
| 5651 | | (A2 extends unassigned ? ExtractTag<E, K> : never) |
| 5652 | | { |
| 5653 | [RK in keyof Cases]: Cases[RK] extends (...args: Array<any>) => Stream<any, infer E, any> ? E : never |
| 5654 | }[keyof Cases], |
| 5655 | | R |
| 5656 | | R2 |
| 5657 | | { |
| 5658 | [RK in keyof Cases]: Cases[RK] extends (...args: Array<any>) => Stream<any, any, infer R> ? R : never |
| 5659 | }[keyof Cases] |
| 5660 | > |
| 5661 | < |
| 5662 | A, |
| 5663 | E, |
| 5664 | R, |
| 5665 | K extends Tags<E>, |
| 5666 | Cases extends { |
| 5667 | [RK in ReasonTags<ExtractTag<E, K>>]+?: ( |
| 5668 | reason: ExtractReason<ExtractTag<E, K>, RK>, |
| 5669 | error: NarrowReason<ExtractTag<E, K>, RK> |
| 5670 | ) => Stream<any, any, any> |
| 5671 | }, |
| 5672 | A2 = unassigned, |
| 5673 | E2 = never, |
| 5674 | R2 = never |
| 5675 | >( |
| 5676 | self: Stream<A, E, R>, |
| 5677 | errorTag: K, |
| 5678 | cases: Cases, |
| 5679 | orElse?: |
| 5680 | | (( |
| 5681 | reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>, |
| 5682 | error: OmitReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>> |
| 5683 | ) => Stream<A2, E2, R2>) |
| 5684 | | undefined |
| 5685 | ): Stream< |
| 5686 | | A |
| 5687 | | Exclude<A2, unassigned> |
| 5688 | | { |
| 5689 | [RK in keyof Cases]: Cases[RK] extends (...args: Array<any>) => Stream<infer A, any, any> ? A : never |
| 5690 | }[keyof Cases], |
| 5691 | | ExcludeTag<E, K> |
| 5692 | | E2 |
| 5693 | | (A2 extends unassigned ? ExtractTag<E, K> : never) |
| 5694 | | { |
| 5695 | [RK in keyof Cases]: Cases[RK] extends (...args: Array<any>) => Stream<any, infer E, any> ? E : never |
| 5696 | }[keyof Cases], |
| 5697 | | R |
| 5698 | | R2 |
| 5699 | | { |
| 5700 | [RK in keyof Cases]: Cases[RK] extends (...args: Array<any>) => Stream<any, any, infer R> ? R : never |
| 5701 | }[keyof Cases] |
| 5702 | > |
| 5703 | } = dual((args) => isStream(args[0]), (self, errorTag, cases, orElse) => { |
| 5704 | const handlers: Record<string, (reason: any, error: any) => Channel.Channel<any, any, any, any, any, any, any>> = |
| 5705 | Object.create(null) |
| 5706 | for (const key of Object.keys(cases)) { |
| 5707 | const handler = (cases as any)[key] |
| 5708 | handlers[key] = (reason, error) => handler(reason, error).channel |
| 5709 | } |
| 5710 | const orElseHandler = orElse && ((reason: any, error: any) => orElse(reason, error).channel) |
| 5711 | return fromChannel( |
| 5712 | Channel.catchReasons(self.channel, errorTag as any, handlers as any, orElseHandler as any) as Channel.Channel< |
| 5713 | Arr.NonEmptyReadonlyArray<any>, |
| 5714 | any, |
| 5715 | void, |
| 5716 | unknown, |
| 5717 | unknown, |
| 5718 | unknown, |
| 5719 | any |
| 5720 | > |
| 5721 | ) as any |
| 5722 | }) |
| 5723 | |
| 5724 | /** |
| 5725 | * Promotes nested reason errors into the stream error channel, replacing the |
| 5726 | * parent error. |
| 5727 | * |
| 5728 | * **Example** (Extracting the reason from a tagged error) |
| 5729 | * |
| 5730 | * ```ts import.meta.vitest |
| 5731 | * import { Data, Effect, Stream } from "effect" |
| 5732 | * |
| 5733 | * class RateLimitError extends Data.TaggedError("RateLimitError")<{ |
| 5734 | * retryAfter: number |
| 5735 | * }> {} |
| 5736 | * |
| 5737 | * class QuotaExceededError extends Data.TaggedError("QuotaExceededError")<{ |
| 5738 | * limit: number |
| 5739 | * }> {} |
| 5740 | * |
| 5741 | * class AiError extends Data.TaggedError("AiError")<{ |
| 5742 | * reason: RateLimitError | QuotaExceededError |
| 5743 | * }> {} |
| 5744 | * |
| 5745 | * const stream: Stream.Stream<string, AiError> = Stream.fail( |
| 5746 | * new AiError({ reason: new RateLimitError({ retryAfter: 30 }) }) |
| 5747 | * ) |
| 5748 | * |
| 5749 | * // Before: Stream<string, AiError> |
| 5750 | * // After: Stream<string, RateLimitError | QuotaExceededError> |
| 5751 | * const unwrapped = stream.pipe(Stream.unwrapReason("AiError")) |
| 5752 | * |
| 5753 | * const program = Effect.gen(function*() { |
| 5754 | * const error = yield* Effect.flip(Stream.runCollect(unwrapped)) |
| 5755 | * error._tag // => "RateLimitError" |
| 5756 | * }) |
| 5757 | * |
| 5758 | * await Effect.runPromise(program) |
| 5759 | * ``` |
| 5760 | * |
| 5761 | * @see {@link catchReason} for recovering from a specific reason |
| 5762 | * @see {@link catchReasons} for handling several reasons at once |
| 5763 | * |
| 5764 | * @category error handling |
| 5765 | * @since 4.0.0 |
| 5766 | */ |
| 5767 | export const unwrapReason: { |
| 5768 | < |
| 5769 | K extends Effect.TagsWithReason<E>, |
| 5770 | E |
| 5771 | >( |
| 5772 | errorTag: K |
| 5773 | ): <A, R>(self: Stream<A, E, R>) => Stream<A, ExcludeTag<E, K> | ReasonOf<ExtractTag<E, K>>, R> |
| 5774 | < |
| 5775 | A, |
| 5776 | E, |
| 5777 | R, |
| 5778 | K extends Effect.TagsWithReason<E> |
| 5779 | >( |
| 5780 | self: Stream<A, E, R>, |
| 5781 | errorTag: K |
| 5782 | ): Stream<A, ExcludeTag<E, K> | ReasonOf<ExtractTag<E, K>>, R> |
| 5783 | } = dual( |
| 5784 | 2, |
| 5785 | < |
| 5786 | A, |
| 5787 | E, |
| 5788 | R, |
| 5789 | K extends Effect.TagsWithReason<E> |
| 5790 | >( |
| 5791 | self: Stream<A, E, R>, |
| 5792 | errorTag: K |
| 5793 | ): Stream<A, ExcludeTag<E, K> | ReasonOf<ExtractTag<E, K>>, R> => |
| 5794 | catchFilter( |
| 5795 | self, |
| 5796 | (e: any) => { |
| 5797 | if (isTagged(e, errorTag) && hasProperty(e, "reason")) { |
| 5798 | return Result.succeed(e.reason) |
| 5799 | } |
| 5800 | return Result.fail(e) |
| 5801 | }, |
| 5802 | fail as any |
| 5803 | ) as any |
| 5804 | ) |
| 5805 | |
| 5806 | /** |
| 5807 | * Transforms the errors emitted by this stream using `f`. |
| 5808 | * |
| 5809 | * **Example** (Mapping stream errors) |
| 5810 | * |
| 5811 | * ```ts import.meta.vitest |
| 5812 | * import { Effect, Stream } from "effect" |
| 5813 | * |
| 5814 | * const program = Effect.gen(function*() { |
| 5815 | * const result = yield* Stream.fail("bad").pipe( |
| 5816 | * Stream.mapError((error) => `mapped: ${error}`), |
| 5817 | * Stream.catch((error) => Stream.make(`recovered from ${error}`)), |
| 5818 | * Stream.runCollect |
| 5819 | * ) |
| 5820 | * result // => [ 'recovered from mapped: bad' ] |
| 5821 | * }) |
| 5822 | * |
| 5823 | * await Effect.runPromise(program) |
| 5824 | * ``` |
| 5825 | * |
| 5826 | * @category error handling |
| 5827 | * @since 2.0.0 |
| 5828 | */ |
| 5829 | export const mapError: { |
| 5830 | <E, E2>(f: (error: E) => E2): <A, R>(self: Stream<A, E, R>) => Stream<A, E2, R> |
| 5831 | <A, E, R, E2>(self: Stream<A, E, R>, f: (error: E) => E2): Stream<A, E2, R> |
| 5832 | } = dual(2, <A, E, R, E2>( |
| 5833 | self: Stream<A, E, R>, |
| 5834 | f: (error: E) => E2 |
| 5835 | ): Stream<A, E2, R> => fromChannel(Channel.mapError(self.channel, f))) |
| 5836 | |
| 5837 | /** |
| 5838 | * Recovers from stream failures by filtering the `Cause` and switching to a recovery stream. |
| 5839 | * Non-matching causes are re-emitted as failures. |
| 5840 | * |
| 5841 | * **Example** (Catching matching causes) |
| 5842 | * |
| 5843 | * ```ts import.meta.vitest |
| 5844 | * import { Cause, Effect, Stream } from "effect" |
| 5845 | * |
| 5846 | * const program = Effect.gen(function*() { |
| 5847 | * const failingStream = Stream.fail("NetworkError") |
| 5848 | * const recovered = Stream.catchCauseIf( |
| 5849 | * failingStream, |
| 5850 | * (cause) => Cause.hasFails(cause), |
| 5851 | * (cause) => Stream.make(`Recovered: ${Cause.squash(cause)}`) |
| 5852 | * ) |
| 5853 | * |
| 5854 | * const output = yield* Stream.runCollect(recovered) |
| 5855 | * output // => [ 'Recovered: NetworkError' ] |
| 5856 | * }) |
| 5857 | * |
| 5858 | * await Effect.runPromise(program) |
| 5859 | * ``` |
| 5860 | * |
| 5861 | * @category error handling |
| 5862 | * @since 4.0.0 |
| 5863 | */ |
| 5864 | export const catchCauseIf: { |
| 5865 | <E, A2, E2, R2>( |
| 5866 | predicate: Predicate<Cause.Cause<E>>, |
| 5867 | f: (cause: Cause.Cause<E>) => Stream<A2, E2, R2> |
| 5868 | ): <A, R>( |
| 5869 | self: Stream<A, E, R> |
| 5870 | ) => Stream<A | A2, E | E2, R2 | R> |
| 5871 | <A, E, R, A2, E2, R2>( |
| 5872 | self: Stream<A, E, R>, |
| 5873 | predicate: Predicate<Cause.Cause<E>>, |
| 5874 | f: (cause: Cause.Cause<E>) => Stream<A2, E2, R2> |
| 5875 | ): Stream<A | A2, E | E2, R | R2> |
| 5876 | } = dual(3, <A, E, R, A2, E2, R2>( |
| 5877 | self: Stream<A, E, R>, |
| 5878 | predicate: Predicate<Cause.Cause<E>>, |
| 5879 | f: (cause: Cause.Cause<E>) => Stream<A2, E2, R2> |
| 5880 | ): Stream<A | A2, E | E2, R | R2> => |
| 5881 | fromChannel( |
| 5882 | Channel.catchCauseIf( |
| 5883 | self.channel, |
| 5884 | predicate, |
| 5885 | (cause) => f(cause).channel |
| 5886 | ) |
| 5887 | )) |
| 5888 | |
| 5889 | /** |
| 5890 | * Recovers from stream failures by filtering the `Cause` and switching to a |
| 5891 | * recovery stream. |
| 5892 | * |
| 5893 | * **When to use** |
| 5894 | * |
| 5895 | * Use when you need to recover a stream only from causes selected by a |
| 5896 | * `Filter`, while giving the recovery both the selected value and the original |
| 5897 | * `Cause`. |
| 5898 | * |
| 5899 | * **Details** |
| 5900 | * |
| 5901 | * The filter is applied to the full `Cause`. A successful filter result is |
| 5902 | * passed to `f` together with the original cause; a failed filter result |
| 5903 | * re-fails with the residual cause. |
| 5904 | * |
| 5905 | * @see {@link catchCauseIf} for predicate-based cause selection |
| 5906 | * @see {@link catchFilter} for filtering typed error values instead of full causes |
| 5907 | * @see {@link catchCause} for recovering from every cause without filtering |
| 5908 | * |
| 5909 | * @category error handling |
| 5910 | * @since 4.0.0 |
| 5911 | */ |
| 5912 | export const catchCauseFilter: { |
| 5913 | <E, EB, A2, E2, R2, X extends Cause.Cause<any>>( |
| 5914 | filter: Filter.Filter<Cause.Cause<E>, EB, X>, |
| 5915 | f: (failure: EB, cause: Cause.Cause<E>) => Stream<A2, E2, R2> |
| 5916 | ): <A, R>( |
| 5917 | self: Stream<A, E, R> |
| 5918 | ) => Stream<A | A2, Cause.Cause.Error<X> | E2, R2 | R> |
| 5919 | <A, E, R, EB, A2, E2, R2, X extends Cause.Cause<any>>( |
| 5920 | self: Stream<A, E, R>, |
| 5921 | filter: Filter.Filter<Cause.Cause<E>, EB, X>, |
| 5922 | f: (failure: EB, cause: Cause.Cause<E>) => Stream<A2, E2, R2> |
| 5923 | ): Stream<A | A2, Cause.Cause.Error<X> | E2, R | R2> |
| 5924 | } = dual(3, <A, E, R, EB, A2, E2, R2, X extends Cause.Cause<any>>( |
| 5925 | self: Stream<A, E, R>, |
| 5926 | filter: Filter.Filter<Cause.Cause<E>, EB, X>, |
| 5927 | f: (failure: EB, cause: Cause.Cause<E>) => Stream<A2, E2, R2> |
| 5928 | ): Stream<A | A2, Cause.Cause.Error<X> | E2, R | R2> => |
| 5929 | fromChannel( |
| 5930 | Channel.catchCauseFilter( |
| 5931 | self.channel, |
| 5932 | filter, |
| 5933 | (failure, cause) => f(failure, cause).channel |
| 5934 | ) |
| 5935 | )) |
| 5936 | |
| 5937 | /** |
| 5938 | * Switches to a fallback stream if this stream is empty. |
| 5939 | * |
| 5940 | * **Example** (Switching on empty streams) |
| 5941 | * |
| 5942 | * ```ts import.meta.vitest |
| 5943 | * import { Effect, Stream } from "effect" |
| 5944 | * |
| 5945 | * const program = Effect.gen(function*() { |
| 5946 | * const values = yield* Stream.empty.pipe( |
| 5947 | * Stream.orElseIfEmpty(() => Stream.make(1, 2)), |
| 5948 | * Stream.runCollect |
| 5949 | * ) |
| 5950 | * values // => [ 1, 2 ] |
| 5951 | * }) |
| 5952 | * |
| 5953 | * await Effect.runPromise(program) |
| 5954 | * ``` |
| 5955 | * |
| 5956 | * @category error handling |
| 5957 | * @since 2.0.0 |
| 5958 | */ |
| 5959 | export const orElseIfEmpty: { |
| 5960 | <E, A2, E2, R2>( |
| 5961 | orElse: LazyArg<Stream<A2, E2, R2>> |
| 5962 | ): <A, R>(self: Stream<A, E, R>) => Stream<A | A2, E | E2, R | R2> |
| 5963 | <A, E, R, A2, E2, R2>( |
| 5964 | self: Stream<A, E, R>, |
| 5965 | orElse: LazyArg<Stream<A2, E2, R2>> |
| 5966 | ): Stream<A | A2, E | E2, R | R2> |
| 5967 | } = dual(2, <A, E, R, A2, E2, R2>( |
| 5968 | self: Stream<A, E, R>, |
| 5969 | orElse: LazyArg<Stream<A2, E2, R2>> |
| 5970 | ): Stream<A | A2, E | E2, R | R2> => |
| 5971 | fromChannel(Channel.orElseIfEmpty( |
| 5972 | self.channel, |
| 5973 | (_) => toChannel(orElse()) |
| 5974 | ))) |
| 5975 | |
| 5976 | /** |
| 5977 | * Returns a stream that emits a fallback value when this stream fails. |
| 5978 | * |
| 5979 | * **Example** (Recovering with a fallback value) |
| 5980 | * |
| 5981 | * ```ts import.meta.vitest |
| 5982 | * import { Effect, Stream } from "effect" |
| 5983 | * |
| 5984 | * const program = Effect.gen(function*() { |
| 5985 | * const stream = Stream.fail("NetworkError").pipe( |
| 5986 | * Stream.orElseSucceed((error) => `Recovered: ${error}`) |
| 5987 | * ) |
| 5988 | * |
| 5989 | * const values = yield* Stream.runCollect(stream) |
| 5990 | * values // => [ 'Recovered: NetworkError' ] |
| 5991 | * }) |
| 5992 | * |
| 5993 | * await Effect.runPromise(program) |
| 5994 | * ``` |
| 5995 | * |
| 5996 | * @category error handling |
| 5997 | * @since 2.0.0 |
| 5998 | */ |
| 5999 | export const orElseSucceed: { |
| 6000 | <E, A2>( |
| 6001 | f: (error: E) => A2 |
| 6002 | ): <A, R>(self: Stream<A, E, R>) => Stream<A | A2, never, R> |
| 6003 | <A, E, R, A2>( |
| 6004 | self: Stream<A, E, R>, |
| 6005 | f: (error: E) => A2 |
| 6006 | ): Stream<A | A2, never, R> |
| 6007 | } = dual(2, <A, E, R, A2>( |
| 6008 | self: Stream<A, E, R>, |
| 6009 | f: (error: E) => A2 |
| 6010 | ): Stream<A | A2, never, R> => catch_(self, (e) => succeed(f(e)))) |
| 6011 | |
| 6012 | /** |
| 6013 | * Turns typed failures into defects, making the stream infallible. |
| 6014 | * |
| 6015 | * **Example** (Turning failures into defects) |
| 6016 | * |
| 6017 | * ```ts import.meta.vitest |
| 6018 | * import { Effect, Stream } from "effect" |
| 6019 | * |
| 6020 | * const program = Effect.gen(function*() { |
| 6021 | * const values = yield* Stream.make(1, 2, 3).pipe( |
| 6022 | * Stream.orDie, |
| 6023 | * Stream.runCollect |
| 6024 | * ) |
| 6025 | * |
| 6026 | * values // => [ 1, 2, 3 ] |
| 6027 | * }) |
| 6028 | * |
| 6029 | * await Effect.runPromise(program) |
| 6030 | * ``` |
| 6031 | * |
| 6032 | * @category error handling |
| 6033 | * @since 2.0.0 |
| 6034 | */ |
| 6035 | export const orDie = <A, E, R>(self: Stream<A, E, R>): Stream<A, never, R> => fromChannel(Channel.orDie(self.channel)) |
| 6036 | |
| 6037 | /** |
| 6038 | * Ignores failures and ends the stream on error. |
| 6039 | * |
| 6040 | * **When to use** |
| 6041 | * |
| 6042 | * Use when you want a failing stream to end gracefully rather than propagate |
| 6043 | * the error. |
| 6044 | * |
| 6045 | * **Details** |
| 6046 | * |
| 6047 | * The `log` option controls whether the failure is logged before the stream |
| 6048 | * terminates. |
| 6049 | * |
| 6050 | * **Example** (Ignoring stream failures) |
| 6051 | * |
| 6052 | * ```ts import.meta.vitest |
| 6053 | * import { Effect, Stream } from "effect" |
| 6054 | * |
| 6055 | * const program = Effect.gen(function*() { |
| 6056 | * const values = yield* Stream.make(1, 2, 3).pipe( |
| 6057 | * Stream.concat(Stream.fail("boom")), |
| 6058 | * Stream.ignore, |
| 6059 | * Stream.runCollect |
| 6060 | * ) |
| 6061 | * values // => [ 1, 2, 3 ] |
| 6062 | * }) |
| 6063 | * |
| 6064 | * await Effect.runPromise(program) |
| 6065 | * ``` |
| 6066 | * |
| 6067 | * **Example** (Configuring ignore logging) |
| 6068 | * |
| 6069 | * ```ts import.meta.vitest |
| 6070 | * import { Effect, Stream } from "effect" |
| 6071 | * |
| 6072 | * await Effect.runPromise(Effect.gen(function*() { |
| 6073 | * const values = yield* Stream.fail("boom").pipe( |
| 6074 | * Stream.ignore({ log: false }), |
| 6075 | * Stream.runCollect |
| 6076 | * ) |
| 6077 | * values // => [] |
| 6078 | * })) |
| 6079 | * |
| 6080 | * ``` |
| 6081 | * |
| 6082 | * @see {@link ignoreCause} for a variant that also ignores defects, not just typed failures |
| 6083 | * |
| 6084 | * @category error handling |
| 6085 | * @since 4.0.0 |
| 6086 | */ |
| 6087 | export const ignore: < |
| 6088 | Arg extends Stream<any, any, any> | { |
| 6089 | readonly log?: boolean | Severity | undefined |
| 6090 | } | undefined |
| 6091 | >( |
| 6092 | selfOrOptions: Arg, |
| 6093 | options?: { |
| 6094 | readonly log?: boolean | Severity | undefined |
| 6095 | } | undefined |
| 6096 | ) => [Arg] extends [Stream<infer A, infer _E, infer R>] ? Stream<A, never, R> |
| 6097 | : <A, E, R>(self: Stream<A, E, R>) => Stream<A, never, R> = dual( |
| 6098 | (args) => isStream(args[0]), |
| 6099 | <A, E, R>( |
| 6100 | self: Stream<A, E, R>, |
| 6101 | options?: { |
| 6102 | readonly log?: boolean | Severity | undefined |
| 6103 | } | undefined |
| 6104 | ): Stream<A, never, R> => fromChannel(Channel.ignore(self.channel, options)) |
| 6105 | ) |
| 6106 | |
| 6107 | /** |
| 6108 | * Ignores the stream's failure cause, including defects, and ends the stream. |
| 6109 | * |
| 6110 | * **When to use** |
| 6111 | * |
| 6112 | * Use when you need to silently suppress a stream's entire failure cause, |
| 6113 | * including both typed errors and defects, rather than propagate it downstream. |
| 6114 | * |
| 6115 | * **Example** (Ignoring stream failure causes) |
| 6116 | * |
| 6117 | * ```ts import.meta.vitest |
| 6118 | * import { Effect, Stream } from "effect" |
| 6119 | * |
| 6120 | * await Effect.runPromise(Effect.gen(function*() { |
| 6121 | * const values = yield* Stream.make(1, 2).pipe( |
| 6122 | * Stream.concat(Stream.die("boom")), |
| 6123 | * Stream.ignoreCause({ log: false }), |
| 6124 | * Stream.runCollect |
| 6125 | * ) |
| 6126 | * values // => [1, 2] |
| 6127 | * })) |
| 6128 | * |
| 6129 | * ``` |
| 6130 | * |
| 6131 | * @see {@link ignore} to ignore only typed failures without suppressing defects |
| 6132 | * |
| 6133 | * @category error handling |
| 6134 | * @since 4.0.0 |
| 6135 | */ |
| 6136 | export const ignoreCause: < |
| 6137 | Arg extends Stream<any, any, any> | { |
| 6138 | readonly log?: boolean | Severity | undefined |
| 6139 | } | undefined |
| 6140 | >( |
| 6141 | streamOrOptions: Arg, |
| 6142 | options?: { |
| 6143 | readonly log?: boolean | Severity | undefined |
| 6144 | } | undefined |
| 6145 | ) => [Arg] extends [Stream<infer A, infer _E, infer R>] ? Stream<A, never, R> |
| 6146 | : <A, E, R>(self: Stream<A, E, R>) => Stream<A, never, R> = dual( |
| 6147 | (args) => isStream(args[0]), |
| 6148 | <A, E, R>( |
| 6149 | self: Stream<A, E, R>, |
| 6150 | options?: { readonly log?: boolean | Severity | undefined } | undefined |
| 6151 | ): Stream<A, never, R> => fromChannel(Channel.ignoreCause(self.channel, options)) |
| 6152 | ) |
| 6153 | |
| 6154 | /** |
| 6155 | * Retries the stream according to the given schedule when it fails. |
| 6156 | * |
| 6157 | * **Details** |
| 6158 | * |
| 6159 | * This retries the entire stream, so will re-execute all of the stream's |
| 6160 | * acquire operations. |
| 6161 | * |
| 6162 | * The schedule is reset as soon as the first element passes through the |
| 6163 | * stream again. |
| 6164 | * |
| 6165 | * **Example** (Retrying stream failures) |
| 6166 | * |
| 6167 | * ```ts import.meta.vitest |
| 6168 | * import { Effect, Schedule, Stream } from "effect" |
| 6169 | * |
| 6170 | * const program = Effect.gen(function*() { |
| 6171 | * const values = yield* Stream.make(1).pipe( |
| 6172 | * Stream.concat(Stream.fail("boom")), |
| 6173 | * Stream.retry(Schedule.recurs(1)), |
| 6174 | * Stream.take(2), |
| 6175 | * Stream.runCollect |
| 6176 | * ) |
| 6177 | * |
| 6178 | * values // => [ 1, 1 ] |
| 6179 | * }) |
| 6180 | * |
| 6181 | * await Effect.runPromise(program) |
| 6182 | * ``` |
| 6183 | * |
| 6184 | * @category error handling |
| 6185 | * @since 2.0.0 |
| 6186 | */ |
| 6187 | export const retry: { |
| 6188 | <E, X, E2, R2>( |
| 6189 | policy: |
| 6190 | | Schedule.Schedule<X, NoInfer<E>, E2, R2> |
| 6191 | | (( |
| 6192 | $: <SO, SE, SR>(_: Schedule.Schedule<SO, NoInfer<E>, SE, SR>) => Schedule.Schedule<SO, E, SE, SR> |
| 6193 | ) => Schedule.Schedule<X, NoInfer<E>, E2, R2>) |
| 6194 | ): <A, R>(self: Stream<A, E, R>) => Stream<A, E | E2, R2 | R> |
| 6195 | <A, E, R, X, E2, R2>( |
| 6196 | self: Stream<A, E, R>, |
| 6197 | policy: |
| 6198 | | Schedule.Schedule<X, NoInfer<E>, E2, R2> |
| 6199 | | (( |
| 6200 | $: <SO, SE, SR>(_: Schedule.Schedule<SO, NoInfer<E>, SE, SR>) => Schedule.Schedule<SO, E, SE, SR> |
| 6201 | ) => Schedule.Schedule<X, NoInfer<E>, E2, R2>) |
| 6202 | ): Stream<A, E | E2, R2 | R> |
| 6203 | } = dual( |
| 6204 | 2, |
| 6205 | <A, E, R, X, E2, R2>( |
| 6206 | self: Stream<A, E, R>, |
| 6207 | policy: |
| 6208 | | Schedule.Schedule<X, NoInfer<E>, E2, R2> |
| 6209 | | (( |
| 6210 | $: <SO, SE, SR>(_: Schedule.Schedule<SO, NoInfer<E>, SE, SR>) => Schedule.Schedule<SO, E, SE, SR> |
| 6211 | ) => Schedule.Schedule<X, NoInfer<E>, E2, R2>) |
| 6212 | ): Stream<A, E | E2, R2 | R> => fromChannel(Channel.retry(self.channel, policy)) |
| 6213 | ) |
| 6214 | |
| 6215 | const retryWithoutReset = <A, E, R, X, E2, R2>( |
| 6216 | self: Stream<A, E, R>, |
| 6217 | schedule: Schedule.Schedule<X, E, E2, R2> |
| 6218 | ): Stream<A, E | E2, R | R2> => |
| 6219 | unwrap(Effect.map(Schedule.toStepWithMetadata(schedule), (step) => { |
| 6220 | let meta = Schedule.CurrentMetadata.defaultValue() |
| 6221 | const loop = (): Stream<A, E | E2, R | R2> => |
| 6222 | catch_( |
| 6223 | provideServiceEffect(self, Schedule.CurrentMetadata, Effect.sync(() => meta)), |
| 6224 | (error) => |
| 6225 | unwrap(Pull.catchDone( |
| 6226 | Effect.map(step(error), (meta_) => { |
| 6227 | meta = meta_ |
| 6228 | return unwrap(Effect.as(Effect.yieldNow, loop())) |
| 6229 | }), |
| 6230 | () => Effect.succeed(fail(error)) |
| 6231 | )) |
| 6232 | ) |
| 6233 | return loop() |
| 6234 | })) |
| 6235 | |
| 6236 | /** |
| 6237 | * Applies an `ExecutionPlan` to a stream, retrying with step-provided resources |
| 6238 | * until it succeeds or the plan is exhausted. |
| 6239 | * |
| 6240 | * **Details** |
| 6241 | * |
| 6242 | * By default, a failing step can fallback even after emitting elements; set |
| 6243 | * `preventFallbackOnPartialStream` to fail instead of mixing partial output with |
| 6244 | * a later fallback. |
| 6245 | * |
| 6246 | * Attempts can be observed from outside the stream by passing |
| 6247 | * `options.onEvent`, which receives an `ExecutionPlan.Event` before each |
| 6248 | * attempt and after it settles; see `Effect.withExecutionPlan` for the handler |
| 6249 | * semantics. When a downstream consumer stops pulling early (for example |
| 6250 | * `Stream.take` outside the plan), the truncated attempt reports |
| 6251 | * `AttemptSuccess`: the consumer stopped, not the source. |
| 6252 | * |
| 6253 | * **Example** (Applying an execution plan) |
| 6254 | * |
| 6255 | * ```ts import.meta.vitest |
| 6256 | * import { Context, Effect, ExecutionPlan, Layer, Stream } from "effect" |
| 6257 | * |
| 6258 | * class Service extends Context.Service<Service>()("Service", { |
| 6259 | * make: Effect.succeed({ |
| 6260 | * stream: Stream.fail("A") as Stream.Stream<number, string> |
| 6261 | * }) |
| 6262 | * }) { |
| 6263 | * static Bad = Layer.succeed(Service, Service.of({ stream: Stream.fail("A") })) |
| 6264 | * static Good = Layer.succeed(Service, Service.of({ stream: Stream.make(1, 2, 3) })) |
| 6265 | * } |
| 6266 | * |
| 6267 | * const plan = ExecutionPlan.make( |
| 6268 | * { provide: Service.Bad }, |
| 6269 | * { provide: Service.Good } |
| 6270 | * ) |
| 6271 | * |
| 6272 | * const stream = Stream.unwrap(Effect.map(Service, (_) => _.stream)) |
| 6273 | * |
| 6274 | * const program = Effect.gen(function*() { |
| 6275 | * const items = yield* stream.pipe(Stream.withExecutionPlan(plan), Stream.runCollect) |
| 6276 | * items // => [ 1, 2, 3 ] |
| 6277 | * }) |
| 6278 | * |
| 6279 | * await Effect.runPromise(program) |
| 6280 | * ``` |
| 6281 | * |
| 6282 | * @category error handling |
| 6283 | * @since 3.16.0 |
| 6284 | */ |
| 6285 | export const withExecutionPlan: { |
| 6286 | <Input, R2, Provides, PolicyE, RX = never>( |
| 6287 | policy: ExecutionPlan.ExecutionPlan<{ provides: Provides; input: Input; error: PolicyE; requirements: R2 }>, |
| 6288 | options?: { |
| 6289 | readonly preventFallbackOnPartialStream?: boolean | undefined |
| 6290 | readonly onEvent?: ((event: ExecutionPlan.Event<Input | PolicyE>) => Effect.Effect<void, never, RX>) | undefined |
| 6291 | } |
| 6292 | ): <A, E extends Input, R>(self: Stream<A, E, R>) => Stream<A, E | PolicyE, R2 | Exclude<R, Provides> | RX> |
| 6293 | <A, E extends Input, R, R2, Input, Provides, PolicyE, RX = never>( |
| 6294 | self: Stream<A, E, R>, |
| 6295 | policy: ExecutionPlan.ExecutionPlan<{ provides: Provides; input: Input; error: PolicyE; requirements: R2 }>, |
| 6296 | options?: { |
| 6297 | readonly preventFallbackOnPartialStream?: boolean | undefined |
| 6298 | readonly onEvent?: ((event: ExecutionPlan.Event<E | PolicyE>) => Effect.Effect<void, never, RX>) | undefined |
| 6299 | } |
| 6300 | ): Stream<A, E | PolicyE, R2 | Exclude<R, Provides> | RX> |
| 6301 | } = dual((args) => isStream(args[0]), <A, E extends Input, R, R2, Input, Provides, PolicyE, RX>( |
| 6302 | self: Stream<A, E, R>, |
| 6303 | policy: ExecutionPlan.ExecutionPlan<{ |
| 6304 | provides: Provides |
| 6305 | input: Input |
| 6306 | error: PolicyE |
| 6307 | requirements: R2 |
| 6308 | }>, |
| 6309 | options?: { |
| 6310 | readonly preventFallbackOnPartialStream?: boolean | undefined |
| 6311 | readonly onEvent?: ((event: ExecutionPlan.Event<E | PolicyE>) => Effect.Effect<void, never, RX>) | undefined |
| 6312 | } |
| 6313 | ): Stream<A, E | PolicyE, R2 | Exclude<R, Provides> | RX> => |
| 6314 | suspend(() => { |
| 6315 | const preventFallbackOnPartialStream = options?.preventFallbackOnPartialStream ?? false |
| 6316 | let i = 0 |
| 6317 | let meta: ExecutionPlan.Metadata = { |
| 6318 | attempt: 0, |
| 6319 | stepIndex: 0 |
| 6320 | } |
| 6321 | const provideMeta = provideServiceEffect( |
| 6322 | ExecutionPlan.CurrentMetadata, |
| 6323 | Effect.sync(() => { |
| 6324 | meta = { |
| 6325 | attempt: meta.attempt + 1, |
| 6326 | stepIndex: i |
| 6327 | } |
| 6328 | return meta |
| 6329 | }) |
| 6330 | ) |
| 6331 | const emitter = options?.onEvent === undefined |
| 6332 | ? undefined |
| 6333 | : internalExecutionPlan.makeEventEmitter(options.onEvent, () => meta) |
| 6334 | let attemptState: internalExecutionPlan.AttemptState | undefined |
| 6335 | const instrument: (attempt: Stream<A, E | PolicyE, any>) => Stream<A, E | PolicyE, any> = emitter === undefined |
| 6336 | ? identity |
| 6337 | : (attempt) => |
| 6338 | onExit( |
| 6339 | onStart( |
| 6340 | attempt, |
| 6341 | Effect.map(emitter.begin, (state) => { |
| 6342 | attemptState = state |
| 6343 | }) |
| 6344 | ), |
| 6345 | (exit) => |
| 6346 | Effect.suspend(() => { |
| 6347 | if (attemptState === undefined) return Effect.void |
| 6348 | const state = attemptState |
| 6349 | attemptState = undefined |
| 6350 | return emitter.end(state, exit) |
| 6351 | }) |
| 6352 | ) |
| 6353 | let lastError = Option.none<E | PolicyE>() |
| 6354 | const loop: Stream< |
| 6355 | A, |
| 6356 | E | PolicyE, |
| 6357 | R2 | Exclude<R, Provides> |
| 6358 | > = suspend(() => { |
| 6359 | const step = policy.steps[i] |
| 6360 | if (!step) { |
| 6361 | return fail(Option.getOrThrow(lastError)) |
| 6362 | } |
| 6363 | |
| 6364 | let nextStream: Stream<A, E | PolicyE, R2 | Exclude<R, Provides>> = provideMeta( |
| 6365 | instrument(provide(self, step.provide)) |
| 6366 | ) |
| 6367 | let receivedElements = false |
| 6368 | |
| 6369 | if (Option.isSome(lastError)) { |
| 6370 | const error = lastError.value |
| 6371 | let attempted = false |
| 6372 | const wrapped = nextStream |
| 6373 | // ensure the schedule is applied at least once |
| 6374 | nextStream = suspend(() => { |
| 6375 | if (attempted) return wrapped |
| 6376 | attempted = true |
| 6377 | return fail(error) |
| 6378 | }) |
| 6379 | nextStream = retryWithoutReset(nextStream, internalExecutionPlan.scheduleFromStep(step, false) as any) |
| 6380 | } else { |
| 6381 | const schedule = internalExecutionPlan.scheduleFromStep(step, true) |
| 6382 | nextStream = schedule ? retryWithoutReset(nextStream, schedule as any) : nextStream |
| 6383 | } |
| 6384 | |
| 6385 | return catch_( |
| 6386 | preventFallbackOnPartialStream ? |
| 6387 | onFirst(nextStream, (_) => { |
| 6388 | receivedElements = true |
| 6389 | return Effect.void |
| 6390 | }) : |
| 6391 | nextStream, |
| 6392 | (error) => { |
| 6393 | i++ |
| 6394 | if (preventFallbackOnPartialStream && receivedElements) { |
| 6395 | return fail(error) |
| 6396 | } |
| 6397 | lastError = Option.some(error) |
| 6398 | return loop |
| 6399 | } |
| 6400 | ) |
| 6401 | }) |
| 6402 | return loop |
| 6403 | })) |
| 6404 | |
| 6405 | /** |
| 6406 | * Takes the first `n` elements from this stream. |
| 6407 | * |
| 6408 | * **Details** |
| 6409 | * |
| 6410 | * Finite fractional values of `n` are rounded down. `NaN` and non-positive |
| 6411 | * values return `Stream.empty` without evaluating the source stream. |
| 6412 | * |
| 6413 | * **Example** (Taking values from the left) |
| 6414 | * |
| 6415 | * ```ts import.meta.vitest |
| 6416 | * import { Effect, Stream } from "effect" |
| 6417 | * |
| 6418 | * const program = Effect.gen(function*() { |
| 6419 | * const values = yield* Stream.make(1, 2, 3, 4, 5).pipe( |
| 6420 | * Stream.take(3), |
| 6421 | * Stream.runCollect |
| 6422 | * ) |
| 6423 | * values // => [ 1, 2, 3 ] |
| 6424 | * }) |
| 6425 | * |
| 6426 | * await Effect.runPromise(program) |
| 6427 | * ``` |
| 6428 | * |
| 6429 | * @category filtering |
| 6430 | * @since 2.0.0 |
| 6431 | */ |
| 6432 | export const take: { |
| 6433 | (n: number): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 6434 | <A, E, R>(self: Stream<A, E, R>, n: number): Stream<A, E, R> |
| 6435 | } = dual( |
| 6436 | 2, |
| 6437 | <A, E, R>(self: Stream<A, E, R>, n: number): Stream<A, E, R> => { |
| 6438 | const count = Count.normalize(n) |
| 6439 | return count === 0 ? empty : takeUntil(self, (_, i) => i === (count - 1)) |
| 6440 | } |
| 6441 | ) |
| 6442 | |
| 6443 | /** |
| 6444 | * Emits byte chunks until the configured limit would be exceeded, then drops |
| 6445 | * the crossing chunk and switches to a fallback stream. |
| 6446 | * |
| 6447 | * **Example** (Truncating at a byte limit) |
| 6448 | * |
| 6449 | * ```ts import.meta.vitest |
| 6450 | * import { ByteSize, Effect, Stream } from "effect" |
| 6451 | * |
| 6452 | * const program = Stream.make( |
| 6453 | * new Uint8Array([1, 2]), |
| 6454 | * new Uint8Array([3, 4, 5]) |
| 6455 | * ).pipe( |
| 6456 | * Stream.limitBytes(ByteSize.bytes(4), () => Stream.empty), |
| 6457 | * Stream.runCollect, |
| 6458 | * Effect.map((chunks) => chunks.map((chunk) => [...chunk])) |
| 6459 | * ) |
| 6460 | * |
| 6461 | * await Effect.runPromise(program) // => [[1, 2]] |
| 6462 | * ``` |
| 6463 | * |
| 6464 | * @category filtering |
| 6465 | * @since 4.0.0 |
| 6466 | */ |
| 6467 | export const limitBytes: { |
| 6468 | <E, R>( |
| 6469 | bytes: ByteSize.Input, |
| 6470 | onLimitReached: LazyArg<Stream<Uint8Array, E, R>> |
| 6471 | ): (self: Stream<Uint8Array, E, R>) => Stream<Uint8Array, E, R> |
| 6472 | <E, R>( |
| 6473 | self: Stream<Uint8Array, E, R>, |
| 6474 | bytes: ByteSize.Input, |
| 6475 | onLimitReached: LazyArg<Stream<Uint8Array, E, R>> |
| 6476 | ): Stream<Uint8Array, E, R> |
| 6477 | } = dual(3, <E, R>( |
| 6478 | self: Stream<Uint8Array, E, R>, |
| 6479 | bytes: ByteSize.Input, |
| 6480 | onLimitReached: LazyArg<Stream<Uint8Array, E, R>> |
| 6481 | ): Stream<Uint8Array, E, R> => |
| 6482 | suspend(() => { |
| 6483 | const limit = ByteSize.fromInputUnsafe(bytes) |
| 6484 | let size = BigInt(0) |
| 6485 | let limitReached = false |
| 6486 | return concat( |
| 6487 | takeWhile(self, (chunk) => { |
| 6488 | const nextSize = size + BigInt(chunk.length) |
| 6489 | if (nextSize > limit) { |
| 6490 | limitReached = true |
| 6491 | return false |
| 6492 | } |
| 6493 | size = nextSize |
| 6494 | return true |
| 6495 | }), |
| 6496 | suspend(() => limitReached ? onLimitReached() : empty) |
| 6497 | ) |
| 6498 | })) |
| 6499 | |
| 6500 | /** |
| 6501 | * Keeps the last `n` elements from this stream. |
| 6502 | * |
| 6503 | * **Details** |
| 6504 | * |
| 6505 | * Finite fractional values of `n` are rounded down. `NaN` and non-positive |
| 6506 | * values return `Stream.empty` without evaluating the source stream. |
| 6507 | * |
| 6508 | * **Example** (Taking elements from the right) |
| 6509 | * |
| 6510 | * ```ts import.meta.vitest |
| 6511 | * import { Effect, Stream } from "effect" |
| 6512 | * |
| 6513 | * const program = Effect.gen(function*() { |
| 6514 | * const values = yield* Stream.range(1, 6).pipe( |
| 6515 | * Stream.takeRight(3), |
| 6516 | * Stream.runCollect |
| 6517 | * ) |
| 6518 | * values // => [ 4, 5, 6 ] |
| 6519 | * }) |
| 6520 | * |
| 6521 | * await Effect.runPromise(program) |
| 6522 | * ``` |
| 6523 | * |
| 6524 | * @category filtering |
| 6525 | * @since 2.0.0 |
| 6526 | */ |
| 6527 | export const takeRight: { |
| 6528 | (n: number): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 6529 | <A, E, R>(self: Stream<A, E, R>, n: number): Stream<A, E, R> |
| 6530 | } = dual( |
| 6531 | 2, |
| 6532 | <A, E, R>(self: Stream<A, E, R>, n: number): Stream<A, E, R> => { |
| 6533 | const count = Count.normalize(n) |
| 6534 | if (count === 0) return empty |
| 6535 | return mapAccumArray(self, MutableList.make<A>, (list, arr) => { |
| 6536 | MutableList.appendAll(list, arr) |
| 6537 | if (list.length > count) { |
| 6538 | MutableList.takeNVoid(list, list.length - count) |
| 6539 | } |
| 6540 | return [list, emptyArr] |
| 6541 | }, { |
| 6542 | onHalt(list) { |
| 6543 | return MutableList.takeAll(list) |
| 6544 | } |
| 6545 | }) |
| 6546 | } |
| 6547 | ) |
| 6548 | |
| 6549 | /** |
| 6550 | * Takes elements until the predicate matches. |
| 6551 | * |
| 6552 | * **Details** |
| 6553 | * |
| 6554 | * When `excludeLast` is `true`, the matching element is dropped. |
| 6555 | * |
| 6556 | * **Example** (Taking until a predicate matches) |
| 6557 | * |
| 6558 | * ```ts import.meta.vitest |
| 6559 | * import { Effect, Stream } from "effect" |
| 6560 | * |
| 6561 | * const stream = Stream.range(1, 5) |
| 6562 | * |
| 6563 | * const program = Effect.gen(function*() { |
| 6564 | * const inclusive = yield* stream.pipe( |
| 6565 | * Stream.takeUntil((n) => n % 3 === 0), |
| 6566 | * Stream.runCollect |
| 6567 | * ) |
| 6568 | * inclusive // => [ 1, 2, 3 ] |
| 6569 | * |
| 6570 | * const exclusive = yield* stream.pipe( |
| 6571 | * Stream.takeUntil((n) => n % 3 === 0, { excludeLast: true }), |
| 6572 | * Stream.runCollect |
| 6573 | * ) |
| 6574 | * exclusive // => [ 1, 2 ] |
| 6575 | * }) |
| 6576 | * await Effect.runPromise(program) |
| 6577 | * ``` |
| 6578 | * |
| 6579 | * @category filtering |
| 6580 | * @since 2.0.0 |
| 6581 | */ |
| 6582 | export const takeUntil: { |
| 6583 | <A>(predicate: (a: NoInfer<A>, n: number) => boolean, options?: { |
| 6584 | readonly excludeLast?: boolean | undefined |
| 6585 | }): <E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 6586 | <A, E, R>(self: Stream<A, E, R>, predicate: (a: A, n: number) => boolean, options?: { |
| 6587 | readonly excludeLast?: boolean | undefined |
| 6588 | }): Stream<A, E, R> |
| 6589 | } = dual( |
| 6590 | (args) => isStream(args[0]), |
| 6591 | <A, E, R>(self: Stream<A, E, R>, predicate: (a: A, n: number) => boolean, options?: { |
| 6592 | readonly excludeLast?: boolean | undefined |
| 6593 | }): Stream<A, E, R> => |
| 6594 | transformPull(self, (pull, _scope) => |
| 6595 | Effect.sync(() => { |
| 6596 | let i = 0 |
| 6597 | let done = false |
| 6598 | const pump: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E, void, R> = Effect.flatMap( |
| 6599 | Effect.suspend(() => done ? Cause.done() : pull), |
| 6600 | (chunk) => { |
| 6601 | const index = chunk.findIndex((a) => predicate(a, i++)) |
| 6602 | if (index >= 0) { |
| 6603 | done = true |
| 6604 | const arr = chunk.slice(0, options?.excludeLast ? index : index + 1) |
| 6605 | return Arr.isReadonlyArrayNonEmpty(arr) ? Effect.succeed(arr) : Cause.done() |
| 6606 | } |
| 6607 | return Effect.succeed(chunk) |
| 6608 | } |
| 6609 | ) |
| 6610 | return pump |
| 6611 | })) |
| 6612 | ) |
| 6613 | |
| 6614 | /** |
| 6615 | * Takes stream elements until an effectful predicate returns `true`. |
| 6616 | * |
| 6617 | * **When to use** |
| 6618 | * |
| 6619 | * Use when the stopping condition needs an Effect or service and predicate |
| 6620 | * failure should fail the stream. |
| 6621 | * |
| 6622 | * **Example** (Taking until an effectful predicate matches) |
| 6623 | * |
| 6624 | * ```ts import.meta.vitest |
| 6625 | * import { Effect, Stream } from "effect" |
| 6626 | * |
| 6627 | * const program = Effect.gen(function*() { |
| 6628 | * const result = yield* Stream.range(1, 5).pipe( |
| 6629 | * Stream.takeUntilEffect((n) => Effect.succeed(n % 3 === 0)), |
| 6630 | * Stream.runCollect |
| 6631 | * ) |
| 6632 | * result // => [ 1, 2, 3 ] |
| 6633 | * }) |
| 6634 | * |
| 6635 | * await Effect.runPromise(program) |
| 6636 | * ``` |
| 6637 | * |
| 6638 | * @category filtering |
| 6639 | * @since 2.0.0 |
| 6640 | */ |
| 6641 | export const takeUntilEffect: { |
| 6642 | <A, E2, R2>( |
| 6643 | predicate: (a: NoInfer<A>, n: number) => Effect.Effect<boolean, E2, R2>, |
| 6644 | options?: { |
| 6645 | readonly excludeLast?: boolean | undefined |
| 6646 | } |
| 6647 | ): <E, R>(self: Stream<A, E, R>) => Stream<A, E2 | E, R2 | R> |
| 6648 | <A, E, R, E2, R2>( |
| 6649 | self: Stream<A, E, R>, |
| 6650 | predicate: (a: A, n: number) => Effect.Effect<boolean, E2, R2>, |
| 6651 | options?: { |
| 6652 | readonly excludeLast?: boolean | undefined |
| 6653 | } |
| 6654 | ): Stream<A, E | E2, R | R2> |
| 6655 | } = dual((args) => isStream(args[0]), <A, E, R, E2, R2>( |
| 6656 | self: Stream<A, E, R>, |
| 6657 | predicate: (a: A, n: number) => Effect.Effect<boolean, E2, R2>, |
| 6658 | options?: { |
| 6659 | readonly excludeLast?: boolean | undefined |
| 6660 | } |
| 6661 | ): Stream<A, E | E2, R | R2> => |
| 6662 | transformPull(self, (pull, _scope) => |
| 6663 | Effect.sync(() => { |
| 6664 | let i = 0 |
| 6665 | let done = false |
| 6666 | return Effect.gen(function*() { |
| 6667 | if (done) return yield* Cause.done() |
| 6668 | const chunk = yield* pull |
| 6669 | for (let j = 0; j < chunk.length; j++) { |
| 6670 | if (yield* predicate(chunk[j], i++)) { |
| 6671 | done = true |
| 6672 | const arr = chunk.slice(0, options?.excludeLast ? j : j + 1) |
| 6673 | return Arr.isReadonlyArrayNonEmpty(arr) ? arr : yield* Cause.done() |
| 6674 | } |
| 6675 | } |
| 6676 | return chunk |
| 6677 | }) |
| 6678 | }))) |
| 6679 | |
| 6680 | /** |
| 6681 | * Takes the longest initial prefix of elements that satisfy the predicate. |
| 6682 | * |
| 6683 | * **Example** (Taking while a predicate holds) |
| 6684 | * |
| 6685 | * ```ts import.meta.vitest |
| 6686 | * import { Effect, Stream } from "effect" |
| 6687 | * |
| 6688 | * const stream = Stream.range(1, 5).pipe( |
| 6689 | * Stream.takeWhile((n) => n % 3 !== 0) |
| 6690 | * ) |
| 6691 | * |
| 6692 | * const program = Effect.gen(function*() { |
| 6693 | * const result = yield* Stream.runCollect(stream) |
| 6694 | * result // => [ 1, 2 ] |
| 6695 | * }) |
| 6696 | * |
| 6697 | * await Effect.runPromise(program) |
| 6698 | * ``` |
| 6699 | * |
| 6700 | * @category filtering |
| 6701 | * @since 2.0.0 |
| 6702 | */ |
| 6703 | export const takeWhile: { |
| 6704 | <A, B extends A>(refinement: (a: NoInfer<A>, n: number) => a is B): <E, R>(self: Stream<A, E, R>) => Stream<B, E, R> |
| 6705 | <A>(predicate: (a: NoInfer<A>, n: number) => boolean): <E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 6706 | <A, E, R, B extends A>(self: Stream<A, E, R>, refinement: (a: NoInfer<A>, n: number) => a is B): Stream<B, E, R> |
| 6707 | <A, E, R>(self: Stream<A, E, R>, predicate: (a: NoInfer<A>, n: number) => boolean): Stream<A, E, R> |
| 6708 | } = dual( |
| 6709 | 2, |
| 6710 | <A, E, R>( |
| 6711 | self: Stream<A, E, R>, |
| 6712 | predicate: (a: A, n: number) => boolean |
| 6713 | ): Stream<A, E, R> => |
| 6714 | transformPull(self, (pull, _scope) => |
| 6715 | Effect.sync(() => { |
| 6716 | let i = 0 |
| 6717 | let done = false |
| 6718 | const pump: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E, void, R> = Effect.flatMap( |
| 6719 | Effect.suspend(() => done ? Cause.done() : pull), |
| 6720 | (chunk) => { |
| 6721 | const out: Array<A> = [] |
| 6722 | for (let j = 0; j < chunk.length; j++) { |
| 6723 | if (!predicate(chunk[j], i++)) { |
| 6724 | done = true |
| 6725 | break |
| 6726 | } |
| 6727 | out.push(chunk[j]) |
| 6728 | } |
| 6729 | return Arr.isReadonlyArrayNonEmpty(out) ? Effect.succeed(out) : done ? Cause.done() : pump |
| 6730 | } |
| 6731 | ) |
| 6732 | return pump |
| 6733 | })) |
| 6734 | ) |
| 6735 | |
| 6736 | /** |
| 6737 | * Takes the longest initial prefix accepted by a `Filter` and emits the |
| 6738 | * filter's success values. |
| 6739 | * |
| 6740 | * **When to use** |
| 6741 | * |
| 6742 | * Use to keep the leading stream elements that a `Filter` accepts, emit the |
| 6743 | * filter's success values, and stop at the first filter failure. |
| 6744 | * |
| 6745 | * **Details** |
| 6746 | * |
| 6747 | * The stream stops at the first `Result.fail` returned by the filter. |
| 6748 | * |
| 6749 | * @see {@link takeWhile} for keeping original elements with a boolean predicate or refinement |
| 6750 | * @see {@link filterMap} for filtering across the whole stream instead of only the leading prefix |
| 6751 | * @see {@link dropWhileFilter} for dropping the accepted prefix and keeping the remaining original elements |
| 6752 | * |
| 6753 | * @category filtering |
| 6754 | * @since 4.0.0 |
| 6755 | */ |
| 6756 | export const takeWhileFilter: { |
| 6757 | <A, B, X>(f: Filter.Filter<NoInfer<A>, B, X>): <E, R>(self: Stream<A, E, R>) => Stream<B, E, R> |
| 6758 | <A, E, R, B, X>(self: Stream<A, E, R>, f: Filter.Filter<NoInfer<A>, B, X>): Stream<B, E, R> |
| 6759 | } = dual( |
| 6760 | 2, |
| 6761 | <A, E, R, B, X>( |
| 6762 | self: Stream<A, E, R>, |
| 6763 | filter: Filter.Filter<NoInfer<A>, B, X> |
| 6764 | ): Stream<B, E, R> => |
| 6765 | transformPull(self, (pull, _scope) => |
| 6766 | Effect.sync(() => { |
| 6767 | let done = false |
| 6768 | const pump: Pull.Pull<Arr.NonEmptyReadonlyArray<B>, E, void, R> = Effect.flatMap( |
| 6769 | Effect.suspend(() => done ? Cause.done() : pull), |
| 6770 | (chunk) => { |
| 6771 | const out: Array<B> = [] |
| 6772 | for (let j = 0; j < chunk.length; j++) { |
| 6773 | const result = filter(chunk[j]) |
| 6774 | if (Result.isFailure(result)) { |
| 6775 | done = true |
| 6776 | break |
| 6777 | } |
| 6778 | out.push(result.success) |
| 6779 | } |
| 6780 | return Arr.isReadonlyArrayNonEmpty(out) ? Effect.succeed(out) : done ? Cause.done() : pump |
| 6781 | } |
| 6782 | ) |
| 6783 | return pump |
| 6784 | })) |
| 6785 | ) |
| 6786 | |
| 6787 | /** |
| 6788 | * Takes elements from the stream while the effectful predicate is `true`. |
| 6789 | * |
| 6790 | * **When to use** |
| 6791 | * |
| 6792 | * Use when the leading-prefix predicate needs an Effect or service and the |
| 6793 | * stream should stop before the first false result. |
| 6794 | * |
| 6795 | * **Example** (Effectfully taking while a predicate holds) |
| 6796 | * |
| 6797 | * ```ts import.meta.vitest |
| 6798 | * import { Effect, Stream } from "effect" |
| 6799 | * |
| 6800 | * const program = Effect.gen(function*() { |
| 6801 | * const result = yield* Stream.range(1, 5).pipe( |
| 6802 | * Stream.takeWhileEffect((n) => Effect.succeed(n % 3 !== 0)), |
| 6803 | * Stream.runCollect |
| 6804 | * ) |
| 6805 | * result // => [ 1, 2 ] |
| 6806 | * }) |
| 6807 | * |
| 6808 | * await Effect.runPromise(program) |
| 6809 | * ``` |
| 6810 | * |
| 6811 | * @category filtering |
| 6812 | * @since 4.0.0 |
| 6813 | */ |
| 6814 | export const takeWhileEffect: { |
| 6815 | <A, E2, R2>( |
| 6816 | predicate: (a: NoInfer<A>, n: number) => Effect.Effect<boolean, E2, R2> |
| 6817 | ): <E, R>(self: Stream<A, E, R>) => Stream<A, E | E2, R | R2> |
| 6818 | <A, E, R, E2, R2>( |
| 6819 | self: Stream<A, E, R>, |
| 6820 | predicate: (a: NoInfer<A>, n: number) => Effect.Effect<boolean, E2, R2> |
| 6821 | ): Stream<A, E | E2, R | R2> |
| 6822 | } = dual(2, <A, E, R, E2, R2>( |
| 6823 | self: Stream<A, E, R>, |
| 6824 | predicate: (a: NoInfer<A>, n: number) => Effect.Effect<boolean, E2, R2> |
| 6825 | ) => |
| 6826 | takeUntilEffect(self, (a, n) => |
| 6827 | Effect.map( |
| 6828 | predicate(a, n), |
| 6829 | (b) => !b |
| 6830 | ), { excludeLast: true })) |
| 6831 | |
| 6832 | /** |
| 6833 | * Drops the first `n` elements from this stream. |
| 6834 | * |
| 6835 | * **Details** |
| 6836 | * |
| 6837 | * Finite fractional values of `n` are rounded down. `NaN` and non-positive |
| 6838 | * values return the source stream unchanged. |
| 6839 | * |
| 6840 | * **Example** (Dropping values from the left) |
| 6841 | * |
| 6842 | * ```ts import.meta.vitest |
| 6843 | * import { Effect, Stream } from "effect" |
| 6844 | * |
| 6845 | * const stream = Stream.make(1, 2, 3, 4, 5) |
| 6846 | * const result = Stream.drop(stream, 2) |
| 6847 | * |
| 6848 | * const program = Effect.gen(function*() { |
| 6849 | * const items = yield* Stream.runCollect(result) |
| 6850 | * items // => [ 3, 4, 5 ] |
| 6851 | * }) |
| 6852 | * |
| 6853 | * await Effect.runPromise(program) |
| 6854 | * ``` |
| 6855 | * |
| 6856 | * @category filtering |
| 6857 | * @since 2.0.0 |
| 6858 | */ |
| 6859 | export const drop: { |
| 6860 | (n: number): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 6861 | <A, E, R>(self: Stream<A, E, R>, n: number): Stream<A, E, R> |
| 6862 | } = dual( |
| 6863 | 2, |
| 6864 | <A, E, R>(self: Stream<A, E, R>, n: number): Stream<A, E, R> => { |
| 6865 | const count = Count.normalize(n) |
| 6866 | if (count === 0) return self |
| 6867 | return transformPull(self, (pull, _scope) => |
| 6868 | Effect.sync(() => { |
| 6869 | let dropped = 0 |
| 6870 | const pump: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E, void, R> = pull.pipe( |
| 6871 | Effect.flatMap((chunk) => { |
| 6872 | if (dropped >= count) return Effect.succeed(chunk) |
| 6873 | dropped += chunk.length |
| 6874 | if (dropped <= count) return pump |
| 6875 | return Effect.succeed(chunk.slice(count - dropped) as Arr.NonEmptyArray<A>) |
| 6876 | }) |
| 6877 | ) |
| 6878 | return pump |
| 6879 | })) |
| 6880 | } |
| 6881 | ) |
| 6882 | |
| 6883 | /** |
| 6884 | * Drops elements until the specified predicate evaluates to `true`, then drops |
| 6885 | * that matching element. |
| 6886 | * |
| 6887 | * **Example** (Dropping until a predicate matches) |
| 6888 | * |
| 6889 | * ```ts import.meta.vitest |
| 6890 | * import { Effect, Stream } from "effect" |
| 6891 | * |
| 6892 | * const stream = Stream.make(1, 2, 3, 4, 5) |
| 6893 | * const result = Stream.dropUntil(stream, (n) => n >= 3) |
| 6894 | * |
| 6895 | * await Effect.runPromise(Effect.gen(function*() { |
| 6896 | * const output = yield* Stream.runCollect(result) |
| 6897 | * output // => [ 4, 5 ] |
| 6898 | * })) |
| 6899 | * ``` |
| 6900 | * |
| 6901 | * @category filtering |
| 6902 | * @since 2.0.0 |
| 6903 | */ |
| 6904 | export const dropUntil: { |
| 6905 | <A>(predicate: (a: NoInfer<A>, index: number) => boolean): <E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 6906 | <A, E, R>(self: Stream<A, E, R>, predicate: (a: NoInfer<A>, index: number) => boolean): Stream<A, E, R> |
| 6907 | } = dual(2, <A, E, R>( |
| 6908 | self: Stream<A, E, R>, |
| 6909 | predicate: (a: NoInfer<A>, index: number) => boolean |
| 6910 | ): Stream<A, E, R> => drop(dropWhile(self, (a, i) => !predicate(a, i)), 1)) |
| 6911 | |
| 6912 | /** |
| 6913 | * Drops all elements of the stream until the specified effectful predicate |
| 6914 | * evaluates to `true`. |
| 6915 | * |
| 6916 | * **When to use** |
| 6917 | * |
| 6918 | * Use when dropping the leading prefix requires an Effect or service and the |
| 6919 | * first matching element should also be dropped. |
| 6920 | * |
| 6921 | * **Example** (Dropping until an effectful predicate matches) |
| 6922 | * |
| 6923 | * ```ts import.meta.vitest |
| 6924 | * import { Effect, Stream } from "effect" |
| 6925 | * |
| 6926 | * const program = Effect.gen(function*() { |
| 6927 | * const result = yield* Stream.range(1, 5).pipe( |
| 6928 | * Stream.dropUntilEffect((n) => Effect.succeed(n % 3 === 0)), |
| 6929 | * Stream.runCollect |
| 6930 | * ) |
| 6931 | * result // => [ 4, 5 ] |
| 6932 | * }) |
| 6933 | * |
| 6934 | * await Effect.runPromise(program) |
| 6935 | * ``` |
| 6936 | * |
| 6937 | * @category filtering |
| 6938 | * @since 2.0.0 |
| 6939 | */ |
| 6940 | export const dropUntilEffect: { |
| 6941 | <A, E2, R2>( |
| 6942 | predicate: (a: NoInfer<A>, index: number) => Effect.Effect<boolean, E2, R2> |
| 6943 | ): <E, R>(self: Stream<A, E, R>) => Stream<A, E2 | E, R2 | R> |
| 6944 | <A, E, R, E2, R2>( |
| 6945 | self: Stream<A, E, R>, |
| 6946 | predicate: (a: NoInfer<A>, index: number) => Effect.Effect<boolean, E2, R2> |
| 6947 | ): Stream<A, E | E2, R | R2> |
| 6948 | } = dual(2, <A, E, R, E2, R2>( |
| 6949 | self: Stream<A, E, R>, |
| 6950 | predicate: (a: NoInfer<A>, index: number) => Effect.Effect<boolean, E2, R2> |
| 6951 | ): Stream<A, E | E2, R | R2> => |
| 6952 | drop( |
| 6953 | dropWhileEffect( |
| 6954 | self, |
| 6955 | (a, i) => Effect.map(predicate(a, i), (b) => !b) |
| 6956 | ), |
| 6957 | 1 |
| 6958 | )) |
| 6959 | |
| 6960 | /** |
| 6961 | * Drops elements from the stream while the specified predicate evaluates to `true`. |
| 6962 | * |
| 6963 | * **Example** (Dropping while a predicate holds) |
| 6964 | * |
| 6965 | * ```ts import.meta.vitest |
| 6966 | * import { Effect, Stream } from "effect" |
| 6967 | * |
| 6968 | * const program = Effect.gen(function*() { |
| 6969 | * const values = yield* Stream.make(1, 2, 3, 4, 5).pipe( |
| 6970 | * Stream.dropWhile((n) => n < 3), |
| 6971 | * Stream.runCollect |
| 6972 | * ) |
| 6973 | * values // => [ 3, 4, 5 ] |
| 6974 | * }) |
| 6975 | * |
| 6976 | * await Effect.runPromise(program) |
| 6977 | * ``` |
| 6978 | * |
| 6979 | * @category filtering |
| 6980 | * @since 2.0.0 |
| 6981 | */ |
| 6982 | export const dropWhile: { |
| 6983 | <A>(predicate: (a: NoInfer<A>, index: number) => boolean): <E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 6984 | <A, E, R>(self: Stream<A, E, R>, predicate: (a: NoInfer<A>, index: number) => boolean): Stream<A, E, R> |
| 6985 | } = dual(2, <A, E, R>( |
| 6986 | self: Stream<A, E, R>, |
| 6987 | predicate: (a: A, index: number) => boolean |
| 6988 | ): Stream<A, E, R> => |
| 6989 | transformPull(self, (pull, _scope) => |
| 6990 | Effect.sync(() => { |
| 6991 | let dropping = true |
| 6992 | let index = 0 |
| 6993 | const filtered: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E> = Effect.flatMap(pull, (arr) => { |
| 6994 | const found = arr.findIndex((a) => !predicate(a, index++)) |
| 6995 | if (found === -1) return filtered |
| 6996 | dropping = false |
| 6997 | return Effect.succeed(arr.slice(found) as Arr.NonEmptyArray<A>) |
| 6998 | }) |
| 6999 | return Effect.suspend(() => dropping ? filtered : pull) |
| 7000 | }))) |
| 7001 | |
| 7002 | /** |
| 7003 | * Drops elements while the filter succeeds. |
| 7004 | * |
| 7005 | * **When to use** |
| 7006 | * |
| 7007 | * Use when you need to remove a leading stream prefix based on a synchronous |
| 7008 | * `Filter` result while preserving the remaining original stream elements. |
| 7009 | * |
| 7010 | * **Details** |
| 7011 | * |
| 7012 | * `Result.succeed` drops the current element. The first `Result.fail` stops |
| 7013 | * dropping, emits that original element, and the rest of the source stream is |
| 7014 | * emitted without further filtering. |
| 7015 | * |
| 7016 | * @see {@link dropWhile} for boolean predicate prefix dropping |
| 7017 | * @see {@link takeWhileFilter} for keeping the accepted prefix as filter success values |
| 7018 | * @see {@link dropWhileEffect} for effectful predicate prefix dropping |
| 7019 | * |
| 7020 | * @category filtering |
| 7021 | * @since 4.0.0 |
| 7022 | */ |
| 7023 | export const dropWhileFilter: { |
| 7024 | <A, B, X>(filter: Filter.Filter<NoInfer<A>, B, X>): <E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 7025 | <A, E, R, B, X>(self: Stream<A, E, R>, filter: Filter.Filter<NoInfer<A>, B, X>): Stream<A, E, R> |
| 7026 | } = dual(2, <A, E, R, B, X>( |
| 7027 | self: Stream<A, E, R>, |
| 7028 | filter: Filter.Filter<NoInfer<A>, B, X> |
| 7029 | ): Stream<A, E, R> => |
| 7030 | transformPull(self, (pull, _scope) => |
| 7031 | Effect.sync(() => { |
| 7032 | let dropping = true |
| 7033 | const filtered: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E> = Effect.flatMap(pull, (arr) => { |
| 7034 | const found = arr.findIndex((a) => Result.isFailure(filter(a))) |
| 7035 | if (found === -1) return filtered |
| 7036 | dropping = false |
| 7037 | return Effect.succeed(arr.slice(found) as Arr.NonEmptyArray<A>) |
| 7038 | }) |
| 7039 | return Effect.suspend(() => dropping ? filtered : pull) |
| 7040 | }))) |
| 7041 | |
| 7042 | /** |
| 7043 | * Drops elements while the specified effectful predicate evaluates to `true`. |
| 7044 | * |
| 7045 | * **Example** (Effectfully dropping while a predicate holds) |
| 7046 | * |
| 7047 | * ```ts import.meta.vitest |
| 7048 | * import { Effect, Stream } from "effect" |
| 7049 | * |
| 7050 | * const program = Effect.gen(function*() { |
| 7051 | * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( |
| 7052 | * Stream.dropWhileEffect((n) => Effect.succeed(n < 3)), |
| 7053 | * Stream.runCollect |
| 7054 | * ) |
| 7055 | * result // => [ 3, 4, 5 ] |
| 7056 | * }) |
| 7057 | * |
| 7058 | * await Effect.runPromise(program) |
| 7059 | * ``` |
| 7060 | * |
| 7061 | * @category filtering |
| 7062 | * @since 2.0.0 |
| 7063 | */ |
| 7064 | export const dropWhileEffect: { |
| 7065 | <A, E2, R2>( |
| 7066 | predicate: (a: NoInfer<A>, index: number) => Effect.Effect<boolean, E2, R2> |
| 7067 | ): <E, R>(self: Stream<A, E, R>) => Stream<A, E2 | E, R2 | R> |
| 7068 | <A, E, R, E2, R2>( |
| 7069 | self: Stream<A, E, R>, |
| 7070 | predicate: (a: A, index: number) => Effect.Effect<boolean, E2, R2> |
| 7071 | ): Stream<A, E | E2, R | R2> |
| 7072 | } = dual(2, <A, E, R, E2, R2>( |
| 7073 | self: Stream<A, E, R>, |
| 7074 | predicate: (a: NoInfer<A>, index: number) => Effect.Effect<boolean, E2, R2> |
| 7075 | ): Stream<A, E | E2, R | R2> => |
| 7076 | transformPull(self, (pull, _scope) => |
| 7077 | Effect.sync(() => { |
| 7078 | let dropping = true |
| 7079 | let index = 0 |
| 7080 | const filtered: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E | E2, void, R2> = Effect.gen(function*() { |
| 7081 | while (true) { |
| 7082 | const arr = yield* pull |
| 7083 | for (let i = 0; i < arr.length; i++) { |
| 7084 | const drop = yield* predicate(arr[i], index++) |
| 7085 | if (drop) continue |
| 7086 | dropping = false |
| 7087 | return arr.slice(i) as Arr.NonEmptyArray<A> |
| 7088 | } |
| 7089 | } |
| 7090 | }) |
| 7091 | return Effect.suspend((): Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E | E2, void, R | R2> => |
| 7092 | dropping ? filtered : pull |
| 7093 | ) |
| 7094 | }))) |
| 7095 | |
| 7096 | /** |
| 7097 | * Drops the last specified number of elements from this stream. |
| 7098 | * |
| 7099 | * **Details** |
| 7100 | * |
| 7101 | * Keeps the last `n` elements in memory to drop them on completion. Finite |
| 7102 | * fractional values of `n` are rounded down. `NaN` and non-positive values |
| 7103 | * return the source stream unchanged. |
| 7104 | * |
| 7105 | * **Example** (Dropping values from the right) |
| 7106 | * |
| 7107 | * ```ts import.meta.vitest |
| 7108 | * import { Effect, Stream } from "effect" |
| 7109 | * |
| 7110 | * const program = Effect.gen(function*() { |
| 7111 | * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( |
| 7112 | * Stream.dropRight(2), |
| 7113 | * Stream.runCollect |
| 7114 | * ) |
| 7115 | * result // => [ 1, 2, 3 ] |
| 7116 | * }) |
| 7117 | * |
| 7118 | * await Effect.runPromise(program) |
| 7119 | * ``` |
| 7120 | * |
| 7121 | * @category filtering |
| 7122 | * @since 2.0.0 |
| 7123 | */ |
| 7124 | export const dropRight: { |
| 7125 | (n: number): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 7126 | <A, E, R>(self: Stream<A, E, R>, n: number): Stream<A, E, R> |
| 7127 | } = dual( |
| 7128 | 2, |
| 7129 | <A, E, R>(self: Stream<A, E, R>, n: number): Stream<A, E, R> => { |
| 7130 | const count = Count.normalize(n) |
| 7131 | if (count === 0) return self |
| 7132 | return transformPull(self, (pull, _scope) => |
| 7133 | Effect.sync(() => { |
| 7134 | const list = MutableList.make<A>() |
| 7135 | const emit: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E> = Effect.flatMap(pull, (arr) => { |
| 7136 | MutableList.appendAllUnsafe(list, arr) |
| 7137 | const toTake = list.length - count |
| 7138 | const items = MutableList.takeN(list, toTake) |
| 7139 | return Arr.isArrayNonEmpty(items) ? Effect.succeed(items) : emit |
| 7140 | }) |
| 7141 | return emit |
| 7142 | })) |
| 7143 | } |
| 7144 | ) |
| 7145 | |
| 7146 | /** |
| 7147 | * Exposes the underlying chunks as a stream of non-empty arrays. |
| 7148 | * |
| 7149 | * **Example** (Exposing stream chunks) |
| 7150 | * |
| 7151 | * ```ts import.meta.vitest |
| 7152 | * import { Effect, Stream } from "effect" |
| 7153 | * |
| 7154 | * const program = Effect.gen(function*() { |
| 7155 | * const chunks = yield* Stream.make(1, 2, 3, 4).pipe( |
| 7156 | * Stream.rechunk(2), |
| 7157 | * Stream.chunks, |
| 7158 | * Stream.runCollect |
| 7159 | * ) |
| 7160 | * chunks // => [ [ 1, 2 ], [ 3, 4 ] ] |
| 7161 | * }) |
| 7162 | * |
| 7163 | * await Effect.runPromise(program) |
| 7164 | * ``` |
| 7165 | * |
| 7166 | * @category grouping |
| 7167 | * @since 2.0.0 |
| 7168 | */ |
| 7169 | export const chunks = <A, E, R>(self: Stream<A, E, R>): Stream<Arr.NonEmptyReadonlyArray<A>, E, R> => |
| 7170 | self.channel.pipe( |
| 7171 | Channel.map(Arr.of), |
| 7172 | fromChannel |
| 7173 | ) |
| 7174 | |
| 7175 | /** |
| 7176 | * Groups the stream into arrays of the specified size, preserving element order. |
| 7177 | * |
| 7178 | * **Details** |
| 7179 | * |
| 7180 | * Finite fractional sizes are rounded down. `NaN` and non-positive sizes are |
| 7181 | * treated as `1`. |
| 7182 | * |
| 7183 | * **Example** (Rechunking stream elements) |
| 7184 | * |
| 7185 | * ```ts import.meta.vitest |
| 7186 | * import { Effect, Stream } from "effect" |
| 7187 | * |
| 7188 | * const program = Effect.gen(function*() { |
| 7189 | * const result = yield* Stream.make(1, 2, 3, 4, 5).pipe( |
| 7190 | * Stream.rechunk(2), |
| 7191 | * Stream.chunks, |
| 7192 | * Stream.runCollect |
| 7193 | * ) |
| 7194 | * result // => [ [ 1, 2 ], [ 3, 4 ], [ 5 ] ] |
| 7195 | * }) |
| 7196 | * |
| 7197 | * await Effect.runPromise(program) |
| 7198 | * ``` |
| 7199 | * |
| 7200 | * @category grouping |
| 7201 | * @since 2.0.0 |
| 7202 | */ |
| 7203 | export const rechunk: { |
| 7204 | (size: number): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 7205 | <A, E, R>(self: Stream<A, E, R>, size: number): Stream<A, E, R> |
| 7206 | } = dual(2, <A, E, R>(self: Stream<A, E, R>, target: number): Stream<A, E, R> => { |
| 7207 | target = Count.normalizeNonEmpty(target) |
| 7208 | return transformPull(self, (pull, _scope) => |
| 7209 | Effect.sync(() => { |
| 7210 | let chunk = Arr.empty<A>() as Arr.NonEmptyArray<A> |
| 7211 | let index = 0 |
| 7212 | let current: Arr.NonEmptyReadonlyArray<A> | undefined |
| 7213 | let done = false |
| 7214 | |
| 7215 | return Effect.suspend(function loop(): Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E, void, R> { |
| 7216 | if (done) return Cause.done() |
| 7217 | else if (current === undefined) { |
| 7218 | return Effect.flatMap(pull, (arr) => { |
| 7219 | if (chunk.length === 0 && arr.length === target) { |
| 7220 | return Effect.succeed(arr) |
| 7221 | } else if (chunk.length + arr.length < target) { |
| 7222 | for (let i = 0; i < arr.length; i++) { |
| 7223 | chunk.push(arr[i]) |
| 7224 | } |
| 7225 | return loop() |
| 7226 | } |
| 7227 | current = arr |
| 7228 | return loop() |
| 7229 | }) |
| 7230 | } |
| 7231 | for (; index < current.length;) { |
| 7232 | chunk.push(current[index++]) |
| 7233 | if (chunk.length === target) { |
| 7234 | const result = chunk |
| 7235 | chunk = [] as any |
| 7236 | return Effect.succeed(result) |
| 7237 | } |
| 7238 | } |
| 7239 | index = 0 |
| 7240 | current = undefined |
| 7241 | return loop() |
| 7242 | }).pipe( |
| 7243 | Pull.catchDone(() => { |
| 7244 | if (chunk.length === 0) return Cause.done() |
| 7245 | const result = chunk |
| 7246 | done = true |
| 7247 | chunk = [] as any |
| 7248 | return Effect.succeed(result) |
| 7249 | }) |
| 7250 | ) |
| 7251 | })) |
| 7252 | }) |
| 7253 | |
| 7254 | /** |
| 7255 | * Emits a sliding window of `n` elements. |
| 7256 | * |
| 7257 | * **Details** |
| 7258 | * |
| 7259 | * Finite fractional window sizes are rounded down. `NaN` and non-positive |
| 7260 | * sizes are treated as `1`. |
| 7261 | * |
| 7262 | * **Example** (Emitting sliding windows) |
| 7263 | * |
| 7264 | * ```ts import.meta.vitest |
| 7265 | * import { Effect, pipe, Stream } from "effect" |
| 7266 | * |
| 7267 | * await Effect.runPromise(Effect.gen(function*() { |
| 7268 | * const result = yield* pipe( |
| 7269 | * Stream.make(1, 2, 3, 4, 5), |
| 7270 | * Stream.sliding(2), |
| 7271 | * Stream.runCollect |
| 7272 | * ) |
| 7273 | * result // => [ [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ] ] |
| 7274 | * })) |
| 7275 | * ``` |
| 7276 | * |
| 7277 | * @category grouping |
| 7278 | * @since 2.0.0 |
| 7279 | */ |
| 7280 | export const sliding: { |
| 7281 | (chunkSize: number): <A, E, R>(self: Stream<A, E, R>) => Stream<Arr.NonEmptyReadonlyArray<A>, E, R> |
| 7282 | <A, E, R>(self: Stream<A, E, R>, chunkSize: number): Stream<Arr.NonEmptyReadonlyArray<A>, E, R> |
| 7283 | } = dual( |
| 7284 | 2, |
| 7285 | <A, E, R>(self: Stream<A, E, R>, chunkSize: number): Stream<Arr.NonEmptyReadonlyArray<A>, E, R> => |
| 7286 | slidingSize(self, chunkSize, 1) |
| 7287 | ) |
| 7288 | |
| 7289 | /** |
| 7290 | * Emits sliding windows of `chunkSize` elements, advancing by `stepSize`. |
| 7291 | * |
| 7292 | * **Details** |
| 7293 | * |
| 7294 | * Finite fractional window and step sizes are rounded down. `NaN` and |
| 7295 | * non-positive sizes are treated as `1`. |
| 7296 | * |
| 7297 | * **Example** (Emitting sliding windows with a step size) |
| 7298 | * |
| 7299 | * ```ts import.meta.vitest |
| 7300 | * import { Effect, Stream } from "effect" |
| 7301 | * |
| 7302 | * const program = Effect.gen(function*() { |
| 7303 | * const chunks = yield* Stream.make(1, 2, 3, 4, 5).pipe( |
| 7304 | * Stream.slidingSize(3, 2), |
| 7305 | * Stream.runCollect |
| 7306 | * ) |
| 7307 | * chunks // => [ [ 1, 2, 3 ], [ 3, 4, 5 ] ] |
| 7308 | * }) |
| 7309 | * |
| 7310 | * await Effect.runPromise(program) |
| 7311 | * ``` |
| 7312 | * |
| 7313 | * @category grouping |
| 7314 | * @since 2.0.0 |
| 7315 | */ |
| 7316 | export const slidingSize: { |
| 7317 | (chunkSize: number, stepSize: number): <A, E, R>(self: Stream<A, E, R>) => Stream<Arr.NonEmptyReadonlyArray<A>, E, R> |
| 7318 | <A, E, R>(self: Stream<A, E, R>, chunkSize: number, stepSize: number): Stream<Arr.NonEmptyReadonlyArray<A>, E, R> |
| 7319 | } = dual( |
| 7320 | 3, |
| 7321 | <A, E, R>(self: Stream<A, E, R>, chunkSize: number, stepSize: number): Stream<Arr.NonEmptyReadonlyArray<A>, E, R> => { |
| 7322 | const windowSize = Count.normalizeNonEmpty(chunkSize) |
| 7323 | const step = Count.normalizeNonEmpty(stepSize) |
| 7324 | return transformPull(self, (upstream, _scope) => |
| 7325 | Effect.sync(() => { |
| 7326 | let cause: Cause.Cause<E | Cause.Done> | null = null |
| 7327 | const list = MutableList.make<A>() |
| 7328 | let emitted = false |
| 7329 | let skip = 0 |
| 7330 | const pull: Pull.Pull< |
| 7331 | Arr.NonEmptyReadonlyArray<Arr.NonEmptyReadonlyArray<A>>, |
| 7332 | E | Cause.Done |
| 7333 | > = Effect.matchCauseEffect(upstream, { |
| 7334 | onSuccess(arr) { |
| 7335 | MutableList.appendAllUnsafe(list, arr) |
| 7336 | if (skip > 0) { |
| 7337 | const length = list.length |
| 7338 | MutableList.takeNVoid(list, skip) |
| 7339 | skip = Math.max(0, skip - length) |
| 7340 | } |
| 7341 | if (list.length < windowSize) return pull |
| 7342 | emitted = true |
| 7343 | const chunks = [] as any as Arr.NonEmptyArray<Arr.NonEmptyReadonlyArray<A>> |
| 7344 | while (list.length >= windowSize) { |
| 7345 | if (windowSize === step) { |
| 7346 | chunks.push(MutableList.takeN(list, windowSize) as any) |
| 7347 | } else { |
| 7348 | chunks.push(MutableList.toArrayN(list, windowSize) as any) |
| 7349 | const length = list.length |
| 7350 | MutableList.takeNVoid(list, step) |
| 7351 | skip = Math.max(0, step - length) |
| 7352 | } |
| 7353 | } |
| 7354 | return Effect.succeed(chunks) |
| 7355 | }, |
| 7356 | onFailure(cause_) { |
| 7357 | if (emitted) MutableList.takeNVoid(list, windowSize - step) |
| 7358 | if (list.length === 0) return Effect.failCause(cause_) |
| 7359 | cause = cause_ |
| 7360 | return Effect.succeed(Arr.of(MutableList.takeAll(list) as any)) |
| 7361 | } |
| 7362 | }) |
| 7363 | |
| 7364 | return Effect.suspend(() => cause ? Effect.failCause(cause) : pull) |
| 7365 | })) |
| 7366 | } |
| 7367 | ) |
| 7368 | |
| 7369 | /** |
| 7370 | * Splits the stream into non-empty groups whenever the predicate matches. |
| 7371 | * |
| 7372 | * **Details** |
| 7373 | * |
| 7374 | * Matching elements act as delimiters and are not included in the output. |
| 7375 | * |
| 7376 | * **Example** (Splitting on matching values) |
| 7377 | * |
| 7378 | * ```ts import.meta.vitest |
| 7379 | * import { Effect, Stream } from "effect" |
| 7380 | * |
| 7381 | * const program = Effect.gen(function*() { |
| 7382 | * const result = yield* Stream.range(0, 9).pipe( |
| 7383 | * Stream.split((n) => n % 4 === 0), |
| 7384 | * Stream.runCollect |
| 7385 | * ) |
| 7386 | * result // => [ [ 1, 2, 3 ], [ 5, 6, 7 ], [ 9 ] ] |
| 7387 | * }) |
| 7388 | * |
| 7389 | * await Effect.runPromise(program) |
| 7390 | * ``` |
| 7391 | * |
| 7392 | * @category grouping |
| 7393 | * @since 2.0.0 |
| 7394 | */ |
| 7395 | export const split: { |
| 7396 | <A, B extends A>( |
| 7397 | refinement: Refinement<NoInfer<A>, B> |
| 7398 | ): <E, R>(self: Stream<A, E, R>) => Stream<Arr.NonEmptyReadonlyArray<Exclude<A, B>>, E, R> |
| 7399 | <A>(predicate: Predicate<NoInfer<A>>): <E, R>(self: Stream<A, E, R>) => Stream<Arr.NonEmptyReadonlyArray<A>, E, R> |
| 7400 | <A, E, R, B extends A>( |
| 7401 | self: Stream<A, E, R>, |
| 7402 | refinement: Refinement<A, B> |
| 7403 | ): Stream<Arr.NonEmptyReadonlyArray<Exclude<A, B>>, E, R> |
| 7404 | <A, E, R>(self: Stream<A, E, R>, predicate: Predicate<A>): Stream<Arr.NonEmptyReadonlyArray<A>, E, R> |
| 7405 | } = dual(2, <A, E, R>( |
| 7406 | self: Stream<A, E, R>, |
| 7407 | predicate: Predicate<NoInfer<A>> |
| 7408 | ): Stream<Arr.NonEmptyReadonlyArray<A>, E, R> => |
| 7409 | mapAccumArray(self, Arr.empty<A>, (acc, arr) => { |
| 7410 | const out = Arr.empty<Arr.NonEmptyReadonlyArray<A>>() |
| 7411 | for (let i = 0; i < arr.length; i++) { |
| 7412 | if (predicate(arr[i])) { |
| 7413 | if (Arr.isArrayNonEmpty(acc)) { |
| 7414 | out.push(acc) |
| 7415 | acc = [] |
| 7416 | } |
| 7417 | } else { |
| 7418 | acc.push(arr[i]) |
| 7419 | } |
| 7420 | } |
| 7421 | return [acc, out] |
| 7422 | }, { |
| 7423 | onHalt(arr) { |
| 7424 | return Arr.isArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr |
| 7425 | } |
| 7426 | })) |
| 7427 | |
| 7428 | /** |
| 7429 | * Combines elements from this stream and the specified stream by repeatedly |
| 7430 | * applying a stateful function that can pull from either side. |
| 7431 | * |
| 7432 | * **Details** |
| 7433 | * |
| 7434 | * Where possible, prefer `Stream.combineArray` for a more efficient |
| 7435 | * implementation. |
| 7436 | * |
| 7437 | * **Example** (Combining streams with state) |
| 7438 | * |
| 7439 | * ```ts import.meta.vitest |
| 7440 | * import { Effect, Stream } from "effect" |
| 7441 | * |
| 7442 | * const stream = Stream.combine( |
| 7443 | * Stream.make("A", "B", "C"), |
| 7444 | * Stream.make(1, 2, 3), |
| 7445 | * () => true, |
| 7446 | * (takeLeft, pullLeft, pullRight) => |
| 7447 | * takeLeft |
| 7448 | * ? Effect.map(pullLeft, (value) => [`L:${value}`, false] as const) |
| 7449 | * : Effect.map(pullRight, (value) => [`R:${value}`, true] as const) |
| 7450 | * ) |
| 7451 | * |
| 7452 | * const program = Effect.gen(function*() { |
| 7453 | * const output = yield* Stream.runCollect(stream) |
| 7454 | * output // => [ 'L:A', 'R:1', 'L:B', 'R:2', 'L:C', 'R:3' ] |
| 7455 | * }) |
| 7456 | * |
| 7457 | * await Effect.runPromise(program) |
| 7458 | * ``` |
| 7459 | * |
| 7460 | * @category merging |
| 7461 | * @since 2.0.0 |
| 7462 | */ |
| 7463 | export const combine: { |
| 7464 | <A2, E2, R2, S, E, A, A3, E3, R3>( |
| 7465 | that: Stream<A2, E2, R2>, |
| 7466 | s: LazyArg<S>, |
| 7467 | f: ( |
| 7468 | s: S, |
| 7469 | pullLeft: Pull.Pull<A, E, void>, |
| 7470 | pullRight: Pull.Pull<A2, E2, void> |
| 7471 | ) => Effect.Effect<readonly [A3, S], E3, R3> |
| 7472 | ): <R>(self: Stream<A, E, R>) => Stream<A3, E3, R2 | R3 | R> |
| 7473 | <A, E, R, A2, E2, R2, S, A3, E3, R3>( |
| 7474 | self: Stream<A, E, R>, |
| 7475 | that: Stream<A2, E2, R2>, |
| 7476 | s: LazyArg<S>, |
| 7477 | f: ( |
| 7478 | s: S, |
| 7479 | pullLeft: Pull.Pull<A, E, void>, |
| 7480 | pullRight: Pull.Pull<A2, E2, void> |
| 7481 | ) => Effect.Effect<readonly [A3, S], E3, R3> |
| 7482 | ): Stream<A3, E3, R | R2 | R3> |
| 7483 | } = dual(4, <A, E, R, A2, E2, R2, S, A3, E3, R3>( |
| 7484 | self: Stream<A, E, R>, |
| 7485 | that: Stream<A2, E2, R2>, |
| 7486 | s: LazyArg<S>, |
| 7487 | f: ( |
| 7488 | s: S, |
| 7489 | pullLeft: Pull.Pull<A, E, void>, |
| 7490 | pullRight: Pull.Pull<A2, E2, void> |
| 7491 | ) => Effect.Effect<readonly [A3, S], E3, R3> |
| 7492 | ): Stream<A3, E3, R | R2 | R3> => |
| 7493 | Channel.combine( |
| 7494 | Channel.flattenArray(self.channel), |
| 7495 | Channel.flattenArray(that.channel), |
| 7496 | s, |
| 7497 | f |
| 7498 | ).pipe( |
| 7499 | Channel.map(Arr.of), |
| 7500 | fromChannel |
| 7501 | )) |
| 7502 | |
| 7503 | /** |
| 7504 | * Combines two streams chunk-by-chunk with a stateful pull function. |
| 7505 | * |
| 7506 | * **When to use** |
| 7507 | * |
| 7508 | * Use to coordinate pulling chunks from two streams when each emitted chunk |
| 7509 | * depends on both sides and local state. |
| 7510 | * |
| 7511 | * **Details** |
| 7512 | * |
| 7513 | * The combining function receives the current state and pull functions for the |
| 7514 | * left and right streams. It returns the next non-empty chunk together with the |
| 7515 | * next state. |
| 7516 | * |
| 7517 | * **Example** (Combining stream chunks with state) |
| 7518 | * |
| 7519 | * ```ts import.meta.vitest |
| 7520 | * import { Effect, Stream } from "effect" |
| 7521 | * |
| 7522 | * const stream = Stream.make(1, 2).pipe( |
| 7523 | * Stream.combineArray( |
| 7524 | * Stream.make(10, 20), |
| 7525 | * () => true, |
| 7526 | * (useLeft, pullLeft, pullRight) => |
| 7527 | * Effect.gen(function*() { |
| 7528 | * const array = useLeft ? yield* pullLeft : yield* pullRight |
| 7529 | * return [array, !useLeft] as const |
| 7530 | * }) |
| 7531 | * ) |
| 7532 | * ) |
| 7533 | * |
| 7534 | * const program = Effect.gen(function*() { |
| 7535 | * const values = yield* Stream.runCollect(stream) |
| 7536 | * values // => [ 1, 2, 10, 20 ] |
| 7537 | * }) |
| 7538 | * |
| 7539 | * await Effect.runPromise(program) |
| 7540 | * ``` |
| 7541 | * |
| 7542 | * @category merging |
| 7543 | * @since 4.0.0 |
| 7544 | */ |
| 7545 | export const combineArray: { |
| 7546 | <A2, E2, R2, S, E, A, A3, E3, R3>( |
| 7547 | that: Stream<A2, E2, R2>, |
| 7548 | s: LazyArg<S>, |
| 7549 | f: ( |
| 7550 | s: S, |
| 7551 | pullLeft: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E, void>, |
| 7552 | pullRight: Pull.Pull<Arr.NonEmptyReadonlyArray<A2>, E2, void> |
| 7553 | ) => Effect.Effect<readonly [Arr.NonEmptyReadonlyArray<A3>, S], E3, R3> |
| 7554 | ): <R>(self: Stream<A, E, R>) => Stream<A3, Pull.ExcludeDone<E3>, R2 | R3 | R> |
| 7555 | <R, A2, E2, R2, S, E, A, A3, E3, R3>( |
| 7556 | self: Stream<A, E, R>, |
| 7557 | that: Stream<A2, E2, R2>, |
| 7558 | s: LazyArg<S>, |
| 7559 | f: ( |
| 7560 | s: S, |
| 7561 | pullLeft: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E, void>, |
| 7562 | pullRight: Pull.Pull<Arr.NonEmptyReadonlyArray<A2>, E2, void> |
| 7563 | ) => Effect.Effect<readonly [Arr.NonEmptyReadonlyArray<A3>, S], E3, R3> |
| 7564 | ): Stream<A3, Pull.ExcludeDone<E3>, R | R2 | R3> |
| 7565 | } = dual(4, <R, A2, E2, R2, S, E, A, A3, E3, R3>( |
| 7566 | self: Stream<A, E, R>, |
| 7567 | that: Stream<A2, E2, R2>, |
| 7568 | s: LazyArg<S>, |
| 7569 | f: ( |
| 7570 | s: S, |
| 7571 | pullLeft: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E, void>, |
| 7572 | pullRight: Pull.Pull<Arr.NonEmptyReadonlyArray<A2>, E2, void> |
| 7573 | ) => Effect.Effect<readonly [Arr.NonEmptyReadonlyArray<A3>, S], E3, R3> |
| 7574 | ): Stream<A3, Pull.ExcludeDone<E3>, R | R2 | R3> => |
| 7575 | fromChannel(Channel.combine( |
| 7576 | self.channel, |
| 7577 | that.channel, |
| 7578 | s, |
| 7579 | f |
| 7580 | ))) |
| 7581 | |
| 7582 | /** |
| 7583 | * Maps elements statefully, emitting zero or more outputs per input. |
| 7584 | * |
| 7585 | * **Example** (Statefully mapping stream values) |
| 7586 | * |
| 7587 | * ```ts import.meta.vitest |
| 7588 | * import { Console, Effect, Stream } from "effect" |
| 7589 | * |
| 7590 | * const program = Effect.gen(function*() { |
| 7591 | * const totals = yield* Stream.make(0, 1, 2, 3, 4, 5, 6).pipe( |
| 7592 | * Stream.mapAccum(() => 0, (total, n) => { |
| 7593 | * const next = total + n |
| 7594 | * return [next, [next]] as const |
| 7595 | * }), |
| 7596 | * Stream.runCollect |
| 7597 | * ) |
| 7598 | * |
| 7599 | * totals // => [0, 1, 3, 6, 10, 15, 21] |
| 7600 | * }) |
| 7601 | * |
| 7602 | * await Effect.runPromise(program) |
| 7603 | * ``` |
| 7604 | * |
| 7605 | * @category mapping |
| 7606 | * @since 2.0.0 |
| 7607 | */ |
| 7608 | export const mapAccum: { |
| 7609 | <S, A, B>( |
| 7610 | initial: LazyArg<S>, |
| 7611 | f: (s: S, a: A) => readonly [state: S, values: ReadonlyArray<B>], |
| 7612 | options?: { |
| 7613 | readonly onHalt?: ((state: S) => ReadonlyArray<B>) | undefined |
| 7614 | } |
| 7615 | ): <E, R>(self: Stream<A, E, R>) => Stream<B, E, R> |
| 7616 | <A, E, R, S, B>( |
| 7617 | self: Stream<A, E, R>, |
| 7618 | initial: LazyArg<S>, |
| 7619 | f: (s: S, a: A) => readonly [state: S, values: ReadonlyArray<B>], |
| 7620 | options?: { |
| 7621 | readonly onHalt?: ((state: S) => ReadonlyArray<B>) | undefined |
| 7622 | } |
| 7623 | ): Stream<B, E, R> |
| 7624 | } = dual((args) => isStream(args[0]), <A, E, R, S, B>( |
| 7625 | self: Stream<A, E, R>, |
| 7626 | initial: LazyArg<S>, |
| 7627 | f: (s: S, a: A) => readonly [state: S, values: ReadonlyArray<B>], |
| 7628 | options?: { |
| 7629 | readonly onHalt?: ((state: S) => ReadonlyArray<B>) | undefined |
| 7630 | } |
| 7631 | ): Stream<B, E, R> => |
| 7632 | fromChannel(Channel.mapAccum( |
| 7633 | self.channel, |
| 7634 | initial, |
| 7635 | (state, arr) => { |
| 7636 | const acc = Arr.empty<B>() |
| 7637 | for (let index = 0; index < arr.length; index++) { |
| 7638 | const [newState, values] = f(state, arr[index]) |
| 7639 | state = newState |
| 7640 | acc.push(...values) |
| 7641 | } |
| 7642 | return [state, Arr.isArrayNonEmpty(acc) ? Arr.of(acc) : emptyArr] |
| 7643 | }, |
| 7644 | options?.onHalt ? |
| 7645 | { |
| 7646 | onHalt(state) { |
| 7647 | const arr = options.onHalt!(state) |
| 7648 | return Arr.isReadonlyArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr |
| 7649 | } |
| 7650 | } : |
| 7651 | undefined |
| 7652 | ))) |
| 7653 | |
| 7654 | /** |
| 7655 | * Maps over non-empty chunk arrays statefully, emitting zero or more values per chunk. |
| 7656 | * |
| 7657 | * **Details** |
| 7658 | * |
| 7659 | * The mapping function runs once per chunk and the state is threaded across chunks. |
| 7660 | * |
| 7661 | * **Example** (Statefully mapping stream chunks) |
| 7662 | * |
| 7663 | * ```ts import.meta.vitest |
| 7664 | * import { Effect, Stream } from "effect" |
| 7665 | * |
| 7666 | * const program = Effect.gen(function*() { |
| 7667 | * const output = yield* Stream.make(1, 2, 3, 4, 5, 6).pipe( |
| 7668 | * Stream.rechunk(2), |
| 7669 | * Stream.mapAccumArray(() => 0, (sum: number, chunk) => { |
| 7670 | * const next = chunk.reduce((acc, n) => acc + n, sum) |
| 7671 | * return [next, [next]] |
| 7672 | * }), |
| 7673 | * Stream.runCollect |
| 7674 | * ) |
| 7675 | * output // => [ 3, 10, 21 ] |
| 7676 | * }) |
| 7677 | * |
| 7678 | * await Effect.runPromise(program) |
| 7679 | * ``` |
| 7680 | * |
| 7681 | * @category mapping |
| 7682 | * @since 4.0.0 |
| 7683 | */ |
| 7684 | export const mapAccumArray: { |
| 7685 | <S, A, B>( |
| 7686 | initial: LazyArg<S>, |
| 7687 | f: (s: S, a: Arr.NonEmptyReadonlyArray<A>) => readonly [state: S, values: ReadonlyArray<B>], |
| 7688 | options?: { |
| 7689 | readonly onHalt?: ((state: S) => ReadonlyArray<B>) | undefined |
| 7690 | } |
| 7691 | ): <E, R>(self: Stream<A, E, R>) => Stream<B, E, R> |
| 7692 | <A, E, R, S, B>( |
| 7693 | self: Stream<A, E, R>, |
| 7694 | initial: LazyArg<S>, |
| 7695 | f: (s: S, a: Arr.NonEmptyReadonlyArray<A>) => readonly [state: S, values: ReadonlyArray<B>], |
| 7696 | options?: { |
| 7697 | readonly onHalt?: ((state: S) => ReadonlyArray<B>) | undefined |
| 7698 | } |
| 7699 | ): Stream<B, E, R> |
| 7700 | } = dual((args) => isStream(args[0]), <A, E, R, S, B>( |
| 7701 | self: Stream<A, E, R>, |
| 7702 | initial: LazyArg<S>, |
| 7703 | f: (s: S, a: Arr.NonEmptyReadonlyArray<A>) => readonly [state: S, values: ReadonlyArray<B>], |
| 7704 | options?: { |
| 7705 | readonly onHalt?: ((state: S) => ReadonlyArray<B>) | undefined |
| 7706 | } |
| 7707 | ): Stream<B, E, R> => |
| 7708 | fromChannel(Channel.mapAccum( |
| 7709 | self.channel, |
| 7710 | initial, |
| 7711 | (state, arr) => { |
| 7712 | const [newState, values] = f(state, arr) |
| 7713 | state = newState |
| 7714 | return [state, Arr.isReadonlyArrayNonEmpty(values) ? Arr.of(values) : emptyArr] |
| 7715 | }, |
| 7716 | options?.onHalt ? |
| 7717 | { |
| 7718 | onHalt(state) { |
| 7719 | const arr = options.onHalt!(state) |
| 7720 | return Arr.isReadonlyArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr |
| 7721 | } |
| 7722 | } : |
| 7723 | undefined |
| 7724 | ))) |
| 7725 | |
| 7726 | const emptyArr = Arr.empty<never>() |
| 7727 | |
| 7728 | /** |
| 7729 | * Maps each element statefully and effectfully, emitting zero or more output |
| 7730 | * values per input. |
| 7731 | * |
| 7732 | * **When to use** |
| 7733 | * |
| 7734 | * Use when stateful element mapping needs Effects or can fail while emitting |
| 7735 | * zero or more values per input element. |
| 7736 | * |
| 7737 | * **Details** |
| 7738 | * |
| 7739 | * The mapping effect receives the current state and element, then returns the |
| 7740 | * next state plus the values to emit. The state is threaded through the |
| 7741 | * stream. |
| 7742 | * |
| 7743 | * **Example** (Effectfully mapping stream values with state) |
| 7744 | * |
| 7745 | * ```ts import.meta.vitest |
| 7746 | * import { Effect, Stream } from "effect" |
| 7747 | * |
| 7748 | * const program = Effect.gen(function*() { |
| 7749 | * const result = yield* Stream.make(1, 1, 1).pipe( |
| 7750 | * Stream.mapAccumEffect(() => 0, (total, n) => |
| 7751 | * Effect.succeed([total + n, [total + n]]) |
| 7752 | * ), |
| 7753 | * Stream.runCollect |
| 7754 | * ) |
| 7755 | * |
| 7756 | * result // => [ 1, 2, 3 ] |
| 7757 | * }) |
| 7758 | * |
| 7759 | * await Effect.runPromise(program) |
| 7760 | * ``` |
| 7761 | * |
| 7762 | * @category mapping |
| 7763 | * @since 2.0.0 |
| 7764 | */ |
| 7765 | export const mapAccumEffect: { |
| 7766 | <S, A, B, E2, R2>( |
| 7767 | initial: LazyArg<S>, |
| 7768 | f: (s: S, a: A) => Effect.Effect<readonly [state: S, values: ReadonlyArray<B>], E2, R2>, |
| 7769 | options?: { |
| 7770 | readonly onHalt?: ((state: S) => ReadonlyArray<B>) | undefined |
| 7771 | } |
| 7772 | ): <E, R>(self: Stream<A, E, R>) => Stream<B, E | E2, R | R2> |
| 7773 | <A, E, R, S, B, E2, R2>( |
| 7774 | self: Stream<A, E, R>, |
| 7775 | initial: LazyArg<S>, |
| 7776 | f: (s: S, a: A) => Effect.Effect<readonly [state: S, values: ReadonlyArray<B>], E2, R2>, |
| 7777 | options?: { |
| 7778 | readonly onHalt?: ((state: S) => ReadonlyArray<B>) | undefined |
| 7779 | } |
| 7780 | ): Stream<B, E | E2, R | R2> |
| 7781 | } = dual((args) => isStream(args[0]), <A, E, R, S, B, E2, R2>( |
| 7782 | self: Stream<A, E, R>, |
| 7783 | initial: LazyArg<S>, |
| 7784 | f: (s: S, a: A) => Effect.Effect<readonly [state: S, values: ReadonlyArray<B>], E2, R2>, |
| 7785 | options?: { |
| 7786 | readonly onHalt?: ((state: S) => ReadonlyArray<B>) | undefined |
| 7787 | } |
| 7788 | ): Stream<B, E | E2, R | R2> => |
| 7789 | self.channel.pipe( |
| 7790 | Channel.flattenArray, |
| 7791 | Channel.mapAccum( |
| 7792 | initial, |
| 7793 | (state, a) => |
| 7794 | Effect.map( |
| 7795 | f(state, a), |
| 7796 | ([state, values]) => [ |
| 7797 | state, |
| 7798 | Arr.isReadonlyArrayNonEmpty(values) ? Arr.of(values) : Arr.empty<Arr.NonEmptyReadonlyArray<B>>() |
| 7799 | ] |
| 7800 | ), |
| 7801 | options?.onHalt ? |
| 7802 | { |
| 7803 | onHalt(state) { |
| 7804 | const arr = options.onHalt!(state) |
| 7805 | return Arr.isReadonlyArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr |
| 7806 | } |
| 7807 | } : |
| 7808 | undefined |
| 7809 | ), |
| 7810 | fromChannel |
| 7811 | )) |
| 7812 | |
| 7813 | /** |
| 7814 | * Maps each non-empty input chunk statefully and effectfully, emitting zero or |
| 7815 | * more output values per chunk. |
| 7816 | * |
| 7817 | * **When to use** |
| 7818 | * |
| 7819 | * Use when stateful mapping should process each emitted non-empty chunk with an |
| 7820 | * Effect instead of each element separately. |
| 7821 | * |
| 7822 | * **Details** |
| 7823 | * |
| 7824 | * The mapping effect receives the current state and chunk, then returns the |
| 7825 | * next state plus the values to emit. The state is threaded across chunks. |
| 7826 | * |
| 7827 | * **Example** (Effectfully mapping stream chunks with state) |
| 7828 | * |
| 7829 | * ```ts import.meta.vitest |
| 7830 | * import { Effect, Stream } from "effect" |
| 7831 | * |
| 7832 | * const program = Effect.gen(function*() { |
| 7833 | * const totals = yield* Stream.make(1, 2, 3, 4).pipe( |
| 7834 | * Stream.rechunk(2), |
| 7835 | * Stream.mapAccumArrayEffect(() => 0, (total, chunk) => |
| 7836 | * Effect.gen(function*() { |
| 7837 | * const next = chunk.reduce((sum, value) => sum + value, total) |
| 7838 | * return [next, [next]] as const |
| 7839 | * }) |
| 7840 | * ), |
| 7841 | * Stream.runCollect |
| 7842 | * ) |
| 7843 | * totals // => [ 3, 10 ] |
| 7844 | * }) |
| 7845 | * |
| 7846 | * await Effect.runPromise(program) |
| 7847 | * ``` |
| 7848 | * |
| 7849 | * @category mapping |
| 7850 | * @since 4.0.0 |
| 7851 | */ |
| 7852 | export const mapAccumArrayEffect: { |
| 7853 | <S, A, B, E2, R2>( |
| 7854 | initial: LazyArg<S>, |
| 7855 | f: (s: S, a: Arr.NonEmptyReadonlyArray<A>) => Effect.Effect<readonly [state: S, values: ReadonlyArray<B>], E2, R2>, |
| 7856 | options?: { |
| 7857 | readonly onHalt?: ((state: S) => ReadonlyArray<B>) | undefined |
| 7858 | } |
| 7859 | ): <E, R>(self: Stream<A, E, R>) => Stream<B, E | E2, R | R2> |
| 7860 | <A, E, R, S, B, E2, R2>( |
| 7861 | self: Stream<A, E, R>, |
| 7862 | initial: LazyArg<S>, |
| 7863 | f: (s: S, a: Arr.NonEmptyReadonlyArray<A>) => Effect.Effect<readonly [state: S, values: ReadonlyArray<B>], E2, R2>, |
| 7864 | options?: { |
| 7865 | readonly onHalt?: ((state: S) => ReadonlyArray<B>) | undefined |
| 7866 | } |
| 7867 | ): Stream<B, E | E2, R | R2> |
| 7868 | } = dual((args) => isStream(args[0]), <A, E, R, S, B, E2, R2>( |
| 7869 | self: Stream<A, E, R>, |
| 7870 | initial: LazyArg<S>, |
| 7871 | f: (s: S, a: Arr.NonEmptyReadonlyArray<A>) => Effect.Effect<readonly [state: S, values: ReadonlyArray<B>], E2, R2>, |
| 7872 | options?: { |
| 7873 | readonly onHalt?: ((state: S) => ReadonlyArray<B>) | undefined |
| 7874 | } |
| 7875 | ): Stream<B, E | E2, R | R2> => |
| 7876 | self.channel.pipe( |
| 7877 | Channel.mapAccum( |
| 7878 | initial, |
| 7879 | (state, a) => |
| 7880 | Effect.map( |
| 7881 | f(state, a), |
| 7882 | ([state, values]) => [ |
| 7883 | state, |
| 7884 | Arr.isReadonlyArrayNonEmpty(values) ? Arr.of(values) : emptyArr |
| 7885 | ] |
| 7886 | ), |
| 7887 | options?.onHalt ? |
| 7888 | { |
| 7889 | onHalt(state) { |
| 7890 | const arr = options.onHalt!(state) |
| 7891 | return Arr.isReadonlyArrayNonEmpty(arr) ? Arr.of(arr) : emptyArr |
| 7892 | } |
| 7893 | } : |
| 7894 | undefined |
| 7895 | ), |
| 7896 | fromChannel |
| 7897 | )) |
| 7898 | |
| 7899 | /** |
| 7900 | * Accumulates state across the stream, emitting the initial state and each updated state. |
| 7901 | * |
| 7902 | * **Example** (Scanning stream state) |
| 7903 | * |
| 7904 | * ```ts import.meta.vitest |
| 7905 | * import { Effect, Stream } from "effect" |
| 7906 | * |
| 7907 | * const program = Effect.gen(function*() { |
| 7908 | * const values = yield* Stream.make(1, 2, 3).pipe( |
| 7909 | * Stream.scan(() => 0, (acc, n) => acc + n), |
| 7910 | * Stream.runCollect |
| 7911 | * ) |
| 7912 | * values // => [ 0, 1, 3, 6 ] |
| 7913 | * }) |
| 7914 | * |
| 7915 | * await Effect.runPromise(program) |
| 7916 | * ``` |
| 7917 | * |
| 7918 | * @category accumulation |
| 7919 | * @since 2.0.0 |
| 7920 | */ |
| 7921 | export const scan: { |
| 7922 | <S, A>( |
| 7923 | initial: LazyArg<S>, |
| 7924 | f: (s: S, a: A) => S |
| 7925 | ): <E, R>(self: Stream<A, E, R>) => Stream<S, E, R> |
| 7926 | <A, E, R, S>( |
| 7927 | self: Stream<A, E, R>, |
| 7928 | initial: LazyArg<S>, |
| 7929 | f: (s: S, a: A) => S |
| 7930 | ): Stream<S, E, R> |
| 7931 | } = dual(3, <A, E, R, S>( |
| 7932 | self: Stream<A, E, R>, |
| 7933 | initial: LazyArg<S>, |
| 7934 | f: (s: S, a: A) => S |
| 7935 | ): Stream<S, E, R> => |
| 7936 | suspend(() => { |
| 7937 | const seed = initial() |
| 7938 | return concat( |
| 7939 | succeed(seed), |
| 7940 | fromChannel(Channel.mapAccum(self.channel, () => seed, (state, arr) => { |
| 7941 | const states = Arr.empty<S>() as Arr.NonEmptyArray<S> |
| 7942 | for (let index = 0; index < arr.length; index++) { |
| 7943 | state = f(state, arr[index]) |
| 7944 | states.push(state) |
| 7945 | } |
| 7946 | return [state, Arr.of(states)] |
| 7947 | })) |
| 7948 | ) |
| 7949 | })) |
| 7950 | |
| 7951 | /** |
| 7952 | * Accumulates state effectfully and emits the initial state plus each accumulated state. |
| 7953 | * |
| 7954 | * **Example** (Effectfully scanning stream state) |
| 7955 | * |
| 7956 | * ```ts import.meta.vitest |
| 7957 | * import { Effect, Stream } from "effect" |
| 7958 | * |
| 7959 | * const program = Effect.gen(function*() { |
| 7960 | * const states = yield* Stream.make(1, 2, 3).pipe( |
| 7961 | * Stream.scanEffect(() => 0, (sum, n) => Effect.succeed(sum + n)), |
| 7962 | * Stream.runCollect |
| 7963 | * ) |
| 7964 | * states // => [ 0, 1, 3, 6 ] |
| 7965 | * }) |
| 7966 | * await Effect.runPromise(program) |
| 7967 | * ``` |
| 7968 | * |
| 7969 | * @category accumulation |
| 7970 | * @since 2.0.0 |
| 7971 | */ |
| 7972 | export const scanEffect: { |
| 7973 | <S, A, E2, R2>( |
| 7974 | initial: LazyArg<S>, |
| 7975 | f: (s: S, a: A) => Effect.Effect<S, E2, R2> |
| 7976 | ): <E, R>(self: Stream<A, E, R>) => Stream<S, E | E2, R | R2> |
| 7977 | <A, E, R, S, E2, R2>( |
| 7978 | self: Stream<A, E, R>, |
| 7979 | initial: LazyArg<S>, |
| 7980 | f: (s: S, a: A) => Effect.Effect<S, E2, R2> |
| 7981 | ): Stream<S, E | E2, R | R2> |
| 7982 | } = dual(3, <A, E, R, S, E2, R2>( |
| 7983 | self: Stream<A, E, R>, |
| 7984 | initial: LazyArg<S>, |
| 7985 | f: (s: S, a: A) => Effect.Effect<S, E2, R2> |
| 7986 | ): Stream<S, E | E2, R | R2> => |
| 7987 | suspend(() => |
| 7988 | self.channel.pipe( |
| 7989 | Channel.flattenArray, |
| 7990 | Channel.scanEffect(initial(), f), |
| 7991 | Channel.map(Arr.of), |
| 7992 | fromChannel |
| 7993 | ) |
| 7994 | )) |
| 7995 | |
| 7996 | /** |
| 7997 | * Drops earlier elements within the debounce window and emits only the latest element after the pause. |
| 7998 | * |
| 7999 | * **Example** (Debouncing stream elements) |
| 8000 | * |
| 8001 | * ```ts import.meta.vitest |
| 8002 | * import { Duration, Effect, Stream } from "effect" |
| 8003 | * |
| 8004 | * const stream = Stream.make(1, 2, 3).pipe(Stream.debounce(Duration.zero)) |
| 8005 | * |
| 8006 | * const program = Effect.gen(function*() { |
| 8007 | * const values = yield* Stream.runCollect(stream) |
| 8008 | * values // => [ 3 ] |
| 8009 | * }) |
| 8010 | * await Effect.runPromise(program) |
| 8011 | * ``` |
| 8012 | * |
| 8013 | * @category rate limiting |
| 8014 | * @since 2.0.0 |
| 8015 | */ |
| 8016 | export const debounce: { |
| 8017 | (duration: Duration.Input): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 8018 | <A, E, R>(self: Stream<A, E, R>, duration: Duration.Input): Stream<A, E, R> |
| 8019 | } = dual( |
| 8020 | 2, |
| 8021 | <A, E, R>(self: Stream<A, E, R>, duration: Duration.Input): Stream<A, E, R> => |
| 8022 | transformPull( |
| 8023 | self, |
| 8024 | Effect.fnUntraced(function*(pull, scope) { |
| 8025 | const clock = yield* Clock |
| 8026 | const durationMs = Duration.toMillis(Duration.fromInputUnsafe(duration)) |
| 8027 | let lastArr: Arr.NonEmptyReadonlyArray<A> | undefined |
| 8028 | let cause: Cause.Cause<Cause.Done | E> | undefined |
| 8029 | let emitAtMs = Infinity |
| 8030 | const pullLatch = Latch.makeUnsafe() |
| 8031 | const emitLatch = Latch.makeUnsafe() |
| 8032 | const endLatch = Latch.makeUnsafe() |
| 8033 | |
| 8034 | yield* pull.pipe( |
| 8035 | pullLatch.whenOpen, |
| 8036 | Effect.flatMap((arr) => { |
| 8037 | emitLatch.openUnsafe() |
| 8038 | lastArr = arr |
| 8039 | emitAtMs = clock.currentTimeMillisUnsafe() + durationMs |
| 8040 | return Effect.void |
| 8041 | }), |
| 8042 | Effect.forever({ disableYield: true }), |
| 8043 | Effect.onError((cause_) => { |
| 8044 | cause = cause_ |
| 8045 | emitAtMs = clock.currentTimeMillisUnsafe() |
| 8046 | emitLatch.openUnsafe() |
| 8047 | endLatch.openUnsafe() |
| 8048 | return Effect.void |
| 8049 | }), |
| 8050 | Effect.forkIn(scope) |
| 8051 | ) |
| 8052 | |
| 8053 | const sleepLoop = Effect.suspend(function loop(): Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E, void, R> { |
| 8054 | const now = clock.currentTimeMillisUnsafe() |
| 8055 | const timeMs = emitAtMs < now ? durationMs : Math.min(durationMs, emitAtMs - now) |
| 8056 | return Effect.flatMap(Effect.raceFirst(Effect.sleep(timeMs), endLatch.await), () => { |
| 8057 | const now = clock.currentTimeMillisUnsafe() |
| 8058 | if (now < emitAtMs) { |
| 8059 | return loop() |
| 8060 | } else if (lastArr) { |
| 8061 | emitLatch.closeUnsafe() |
| 8062 | pullLatch.closeUnsafe() |
| 8063 | const eff = Effect.succeed(Arr.of(Arr.lastNonEmpty(lastArr))) |
| 8064 | lastArr = undefined |
| 8065 | return eff |
| 8066 | } else if (cause) { |
| 8067 | return Effect.failCause(cause!) |
| 8068 | } |
| 8069 | return loop() |
| 8070 | }) |
| 8071 | }) |
| 8072 | |
| 8073 | return Effect.suspend(() => { |
| 8074 | if (cause) { |
| 8075 | if (lastArr) { |
| 8076 | const eff = Effect.succeed(Arr.of(Arr.lastNonEmpty(lastArr))) |
| 8077 | lastArr = undefined |
| 8078 | return eff |
| 8079 | } |
| 8080 | return Effect.failCause(cause) |
| 8081 | } |
| 8082 | pullLatch.openUnsafe() |
| 8083 | return emitLatch.whenOpen(sleepLoop) |
| 8084 | }) |
| 8085 | }) |
| 8086 | ) |
| 8087 | ) |
| 8088 | |
| 8089 | /** |
| 8090 | * Rate-limits stream chunks with an effectful cost function. |
| 8091 | * |
| 8092 | * **When to use** |
| 8093 | * |
| 8094 | * Use to throttle chunks when computing each chunk's cost requires an effect. |
| 8095 | * |
| 8096 | * **Details** |
| 8097 | * |
| 8098 | * Uses a token bucket. The bucket can accumulate up to `units + burst` tokens, |
| 8099 | * and each chunk consumes the cost returned by the effectful `cost` function. |
| 8100 | * |
| 8101 | * If using the "enforce" strategy, arrays that do not meet the bandwidth |
| 8102 | * constraints are dropped. If using the "shape" strategy, arrays are delayed |
| 8103 | * until they can be emitted without exceeding the bandwidth constraints. |
| 8104 | * |
| 8105 | * Defaults to the "shape" strategy. |
| 8106 | * |
| 8107 | * **Example** (Throttling stream chunks effectfully) |
| 8108 | * |
| 8109 | * ```ts import.meta.vitest |
| 8110 | * import { Effect, Stream } from "effect" |
| 8111 | * |
| 8112 | * const stream = Stream.range(0, 5).pipe( |
| 8113 | * Stream.rechunk(1), |
| 8114 | * Stream.throttleEffect({ |
| 8115 | * cost: (arr) => Effect.succeed(arr.length), |
| 8116 | * units: 1, |
| 8117 | * duration: 0, |
| 8118 | * strategy: "shape" |
| 8119 | * }) |
| 8120 | * ) |
| 8121 | * |
| 8122 | * await Effect.runPromise(Effect.gen(function*() { |
| 8123 | * const result = yield* Stream.runCollect(stream) |
| 8124 | * result // => [ 0, 1, 2, 3, 4, 5 ] |
| 8125 | * })) |
| 8126 | * ``` |
| 8127 | * |
| 8128 | * @category rate limiting |
| 8129 | * @since 2.0.0 |
| 8130 | */ |
| 8131 | export const throttleEffect: { |
| 8132 | <A, E2, R2>(options: { |
| 8133 | readonly cost: (arr: Arr.NonEmptyReadonlyArray<A>) => Effect.Effect<number, E2, R2> |
| 8134 | readonly units: number |
| 8135 | readonly duration: Duration.Input |
| 8136 | readonly burst?: number | undefined |
| 8137 | readonly strategy?: "enforce" | "shape" | undefined |
| 8138 | }): <E, R>(self: Stream<A, E, R>) => Stream<A, E2 | E, R2 | R> |
| 8139 | <A, E, R, E2, R2>( |
| 8140 | self: Stream<A, E, R>, |
| 8141 | options: { |
| 8142 | readonly cost: (arr: Arr.NonEmptyReadonlyArray<A>) => Effect.Effect<number, E2, R2> |
| 8143 | readonly units: number |
| 8144 | readonly duration: Duration.Input |
| 8145 | readonly burst?: number | undefined |
| 8146 | readonly strategy?: "enforce" | "shape" | undefined |
| 8147 | } |
| 8148 | ): Stream<A, E | E2, R | R2> |
| 8149 | } = dual( |
| 8150 | 2, |
| 8151 | <A, E, R, E2, R2>( |
| 8152 | self: Stream<A, E, R>, |
| 8153 | options: { |
| 8154 | readonly cost: (arr: Arr.NonEmptyReadonlyArray<A>) => Effect.Effect<number, E2, R2> |
| 8155 | readonly units: number |
| 8156 | readonly duration: Duration.Input |
| 8157 | readonly burst?: number | undefined |
| 8158 | readonly strategy?: "enforce" | "shape" | undefined |
| 8159 | } |
| 8160 | ): Stream<A, E | E2, R | R2> => { |
| 8161 | const burst = options.burst ?? 0 |
| 8162 | if (options.strategy === "enforce") { |
| 8163 | return throttleEnforceEffect(self, options.cost, options.units, options.duration, burst) |
| 8164 | } |
| 8165 | return throttleShapeEffect(self, options.cost, options.units, options.duration, burst) |
| 8166 | } |
| 8167 | ) |
| 8168 | |
| 8169 | const throttleEnforceEffect = <A, E, R, E2, R2>( |
| 8170 | self: Stream<A, E, R>, |
| 8171 | cost: (arr: Arr.NonEmptyReadonlyArray<A>) => Effect.Effect<number, E2, R2>, |
| 8172 | units: number, |
| 8173 | duration: Duration.Input, |
| 8174 | burst: number |
| 8175 | ): Stream<A, E | E2, R | R2> => |
| 8176 | transformPull(self, (pull) => |
| 8177 | Effect.clockWith((clock) => { |
| 8178 | const durationMs = Duration.toMillis(Duration.fromInputUnsafe(duration)) |
| 8179 | const max = units + burst < 0 ? Number.POSITIVE_INFINITY : units + burst |
| 8180 | let tokens = units |
| 8181 | let timestampMs = clock.currentTimeMillisUnsafe() |
| 8182 | |
| 8183 | return Effect.succeed( |
| 8184 | Effect.flatMap(pull, function loop(arr): Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E | E2, void, R | R2> { |
| 8185 | return Effect.flatMap(cost(arr), (weight) => { |
| 8186 | const currentMs = clock.currentTimeMillisUnsafe() |
| 8187 | const elapsed = currentMs - timestampMs |
| 8188 | const cycles = elapsed / durationMs |
| 8189 | const sum = tokens + (cycles * units) |
| 8190 | const available = sum < 0 ? max : Math.min(sum, max) |
| 8191 | |
| 8192 | if (weight <= available) { |
| 8193 | tokens = available - weight |
| 8194 | timestampMs = currentMs |
| 8195 | return Effect.succeed(arr) |
| 8196 | } |
| 8197 | |
| 8198 | // Drop the array and continue |
| 8199 | return Effect.flatMap(pull, loop) |
| 8200 | }) |
| 8201 | }) |
| 8202 | ) |
| 8203 | })) |
| 8204 | |
| 8205 | const throttleShapeEffect = <A, E, R, E2, R2>( |
| 8206 | self: Stream<A, E, R>, |
| 8207 | cost: (arr: Arr.NonEmptyReadonlyArray<A>) => Effect.Effect<number, E2, R2>, |
| 8208 | units: number, |
| 8209 | duration: Duration.Input, |
| 8210 | burst: number |
| 8211 | ): Stream<A, E | E2, R | R2> => |
| 8212 | transformPull(self, (pull) => |
| 8213 | Effect.clockWith((clock) => { |
| 8214 | const durationMs = Duration.toMillis(Duration.fromInputUnsafe(duration)) |
| 8215 | const max = units + burst < 0 ? Number.POSITIVE_INFINITY : units + burst |
| 8216 | let tokens = units |
| 8217 | let timestampNanos = clock.monotonicTimeNanosUnsafe() |
| 8218 | |
| 8219 | return Effect.succeed(Effect.flatMap(pull, (arr) => |
| 8220 | Effect.flatMap(cost(arr), (weight) => { |
| 8221 | const currentNanos = clock.monotonicTimeNanosUnsafe() |
| 8222 | const elapsed = Number(currentNanos - timestampNanos) / 1_000_000 |
| 8223 | const cycles = elapsed / durationMs |
| 8224 | const sum = tokens + (cycles * units) |
| 8225 | const available = Math.min(sum, max) |
| 8226 | const remaining = available - weight |
| 8227 | |
| 8228 | if (remaining >= 0) { |
| 8229 | tokens = remaining |
| 8230 | timestampNanos = currentNanos |
| 8231 | return Effect.succeed(arr) |
| 8232 | } |
| 8233 | |
| 8234 | const waitCycles = -remaining / units |
| 8235 | const delayMs = Math.max(0, waitCycles * durationMs) |
| 8236 | |
| 8237 | if (delayMs > 0) { |
| 8238 | return Effect.flatMap(Effect.sleep(delayMs), () => { |
| 8239 | tokens = remaining |
| 8240 | timestampNanos = currentNanos |
| 8241 | return Effect.succeed(arr) |
| 8242 | }) |
| 8243 | } |
| 8244 | |
| 8245 | tokens = remaining |
| 8246 | timestampNanos = currentNanos |
| 8247 | return Effect.succeed(arr) |
| 8248 | }))) |
| 8249 | })) |
| 8250 | |
| 8251 | /** |
| 8252 | * Rate-limits stream chunks with a synchronous cost function. |
| 8253 | * |
| 8254 | * **When to use** |
| 8255 | * |
| 8256 | * Use to throttle chunks when each chunk's cost can be computed synchronously. |
| 8257 | * |
| 8258 | * **Details** |
| 8259 | * |
| 8260 | * Uses a token bucket. The bucket can accumulate up to `units + burst` tokens, |
| 8261 | * and each chunk consumes the cost returned by `cost`. |
| 8262 | * |
| 8263 | * If using the "enforce" strategy, arrays that do not meet the bandwidth |
| 8264 | * constraints are dropped. If using the "shape" strategy, arrays are delayed |
| 8265 | * until they can be emitted without exceeding the bandwidth constraints. |
| 8266 | * |
| 8267 | * Defaults to the "shape" strategy. |
| 8268 | * |
| 8269 | * **Example** (Throttling stream chunks) |
| 8270 | * |
| 8271 | * ```ts import.meta.vitest |
| 8272 | * import { Effect, Stream } from "effect" |
| 8273 | * |
| 8274 | * const stream = Stream.range(0, 5).pipe( |
| 8275 | * Stream.rechunk(1), |
| 8276 | * Stream.throttle({ |
| 8277 | * cost: (arr) => arr.length, |
| 8278 | * units: 1, |
| 8279 | * duration: 0, |
| 8280 | * strategy: "shape" |
| 8281 | * }) |
| 8282 | * ) |
| 8283 | * |
| 8284 | * const program = Effect.gen(function*() { |
| 8285 | * const values = yield* Stream.runCollect(stream) |
| 8286 | * values // => [ 0, 1, 2, 3, 4, 5 ] |
| 8287 | * }) |
| 8288 | * await Effect.runPromise(program) |
| 8289 | * ``` |
| 8290 | * |
| 8291 | * @category rate limiting |
| 8292 | * @since 2.0.0 |
| 8293 | */ |
| 8294 | export const throttle: { |
| 8295 | <A>(options: { |
| 8296 | readonly cost: (arr: Arr.NonEmptyReadonlyArray<A>) => number |
| 8297 | readonly units: number |
| 8298 | readonly duration: Duration.Input |
| 8299 | readonly burst?: number | undefined |
| 8300 | readonly strategy?: "enforce" | "shape" | undefined |
| 8301 | }): <E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 8302 | <A, E, R>( |
| 8303 | self: Stream<A, E, R>, |
| 8304 | options: { |
| 8305 | readonly cost: (arr: Arr.NonEmptyReadonlyArray<A>) => number |
| 8306 | readonly units: number |
| 8307 | readonly duration: Duration.Input |
| 8308 | readonly burst?: number | undefined |
| 8309 | readonly strategy?: "enforce" | "shape" | undefined |
| 8310 | } |
| 8311 | ): Stream<A, E, R> |
| 8312 | } = dual( |
| 8313 | 2, |
| 8314 | <A, E, R>( |
| 8315 | self: Stream<A, E, R>, |
| 8316 | options: { |
| 8317 | readonly cost: (arr: Arr.NonEmptyReadonlyArray<A>) => number |
| 8318 | readonly units: number |
| 8319 | readonly duration: Duration.Input |
| 8320 | readonly burst?: number | undefined |
| 8321 | readonly strategy?: "enforce" | "shape" | undefined |
| 8322 | } |
| 8323 | ): Stream<A, E, R> => |
| 8324 | throttleEffect(self, { |
| 8325 | ...options, |
| 8326 | cost: (arr) => Effect.succeed(options.cost(arr)) |
| 8327 | }) |
| 8328 | ) |
| 8329 | |
| 8330 | /** |
| 8331 | * Partitions the stream into non-empty arrays of the specified size. |
| 8332 | * |
| 8333 | * **Details** |
| 8334 | * |
| 8335 | * The final array may be smaller if there are not enough elements to fill it. |
| 8336 | * Finite fractional sizes are rounded down. `NaN` and non-positive sizes are |
| 8337 | * treated as `1`. |
| 8338 | * |
| 8339 | * **Example** (Grouping elements by size) |
| 8340 | * |
| 8341 | * ```ts import.meta.vitest |
| 8342 | * import { Effect, Stream } from "effect" |
| 8343 | * |
| 8344 | * const program = Effect.gen(function*() { |
| 8345 | * const grouped = yield* Stream.range(1, 8).pipe( |
| 8346 | * Stream.grouped(3), |
| 8347 | * Stream.runCollect |
| 8348 | * ) |
| 8349 | * grouped // => [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8 ] ] |
| 8350 | * }) |
| 8351 | * |
| 8352 | * await Effect.runPromise(program) |
| 8353 | * ``` |
| 8354 | * |
| 8355 | * @category grouping |
| 8356 | * @since 2.0.0 |
| 8357 | */ |
| 8358 | export const grouped: { |
| 8359 | (n: number): <A, E, R>(self: Stream<A, E, R>) => Stream<Arr.NonEmptyReadonlyArray<A>, E, R> |
| 8360 | <A, E, R>(self: Stream<A, E, R>, n: number): Stream<Arr.NonEmptyReadonlyArray<A>, E, R> |
| 8361 | } = dual( |
| 8362 | 2, |
| 8363 | <A, E, R>(self: Stream<A, E, R>, n: number): Stream<Arr.NonEmptyReadonlyArray<A>, E, R> => chunks(rechunk(self, n)) |
| 8364 | ) |
| 8365 | |
| 8366 | /** |
| 8367 | * Partitions the stream into arrays, emitting when the chunk size is reached |
| 8368 | * or the duration passes. |
| 8369 | * |
| 8370 | * **Details** |
| 8371 | * |
| 8372 | * The duration starts when the first element of a group arrives, so an idle |
| 8373 | * stream does not wake up. Finite fractional chunk sizes are rounded down. |
| 8374 | * `NaN` and non-positive sizes are treated as `1`. |
| 8375 | * |
| 8376 | * **Example** (Grouping elements by size or time) |
| 8377 | * |
| 8378 | * ```ts import.meta.vitest |
| 8379 | * import { Effect, Stream } from "effect" |
| 8380 | * |
| 8381 | * const program = Effect.gen(function*() { |
| 8382 | * const values = yield* Stream.make(1, 2, 3).pipe( |
| 8383 | * Stream.groupedWithin(2, "5 seconds"), |
| 8384 | * Stream.runCollect |
| 8385 | * ) |
| 8386 | * values // => [ [ 1, 2 ], [ 3 ] ] |
| 8387 | * }) |
| 8388 | * |
| 8389 | * await Effect.runPromise(program) |
| 8390 | * ``` |
| 8391 | * |
| 8392 | * @category grouping |
| 8393 | * @since 2.0.0 |
| 8394 | */ |
| 8395 | export const groupedWithin: { |
| 8396 | ( |
| 8397 | chunkSize: number, |
| 8398 | duration: Duration.Input |
| 8399 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<Array<A>, E, R> |
| 8400 | <A, E, R>(self: Stream<A, E, R>, chunkSize: number, duration: Duration.Input): Stream<Array<A>, E, R> |
| 8401 | } = dual(3, <A, E, R>( |
| 8402 | self: Stream<A, E, R>, |
| 8403 | chunkSize: number, |
| 8404 | duration: Duration.Input |
| 8405 | ): Stream<Array<A>, E, R> => |
| 8406 | aggregateWithin( |
| 8407 | self, |
| 8408 | Sink.take(Count.normalizeNonEmpty(chunkSize)), |
| 8409 | Schedule.spaced(duration) |
| 8410 | )) |
| 8411 | |
| 8412 | /** |
| 8413 | * Groups elements into keyed substreams using an effectful classifier. |
| 8414 | * |
| 8415 | * **Example** (Grouping elements into keyed substreams using an effectful classifier) |
| 8416 | * |
| 8417 | * ```ts import.meta.vitest |
| 8418 | * import { Effect, Stream } from "effect" |
| 8419 | * |
| 8420 | * const program = Effect.gen(function*() { |
| 8421 | * const grouped = yield* Stream.make(1, 2, 3, 4, 5).pipe( |
| 8422 | * Stream.groupBy((n) => |
| 8423 | * Effect.succeed([n % 2 === 0 ? "even" : "odd", n] as const) |
| 8424 | * ), |
| 8425 | * Stream.mapEffect( |
| 8426 | * Effect.fnUntraced(function*([key, stream]) { |
| 8427 | * return [key, yield* Stream.runCollect(stream)] as const |
| 8428 | * }), |
| 8429 | * { concurrency: "unbounded" } |
| 8430 | * ), |
| 8431 | * Stream.runCollect |
| 8432 | * ) |
| 8433 | * |
| 8434 | * grouped // => [ [ 'odd', [ 1, 3, 5 ] ], [ 'even', [ 2, 4 ] ] ] |
| 8435 | * }) |
| 8436 | * |
| 8437 | * await Effect.runPromise(program) |
| 8438 | * ``` |
| 8439 | * |
| 8440 | * @category grouping |
| 8441 | * @since 2.0.0 |
| 8442 | */ |
| 8443 | export const groupBy: { |
| 8444 | <A, K, V, E2, R2>( |
| 8445 | f: (a: NoInfer<A>) => Effect.Effect<readonly [K, V], E2, R2>, |
| 8446 | options?: { |
| 8447 | readonly bufferSize?: number | undefined |
| 8448 | readonly idleTimeToLive?: Duration.Input | undefined |
| 8449 | } |
| 8450 | ): <E, R>(self: Stream<A, E, R>) => Stream<readonly [K, Stream<V>], E | E2, R | R2> |
| 8451 | <A, E, R, K, V, E2, R2>( |
| 8452 | self: Stream<A, E, R>, |
| 8453 | f: (a: NoInfer<A>) => Effect.Effect<readonly [K, V], E2, R2>, |
| 8454 | options?: { |
| 8455 | readonly bufferSize?: number | undefined |
| 8456 | readonly idleTimeToLive?: Duration.Input | undefined |
| 8457 | } |
| 8458 | ): Stream<readonly [K, Stream<V>], E | E2, R | R2> |
| 8459 | } = dual((args) => isStream(args[0]), <A, E, R, K, V, E2, R2>( |
| 8460 | self: Stream<A, E, R>, |
| 8461 | f: (a: NoInfer<A>) => Effect.Effect<readonly [K, V], E2, R2>, |
| 8462 | options?: { |
| 8463 | readonly bufferSize?: number | undefined |
| 8464 | readonly idleTimeToLive?: Duration.Input | undefined |
| 8465 | } |
| 8466 | ): Stream<readonly [K, Stream<V>], E | E2, R | R2> => |
| 8467 | groupByImpl( |
| 8468 | self, |
| 8469 | Effect.fnUntraced(function*(arr, queues, queueMap) { |
| 8470 | for (let i = 0; i < arr.length; i++) { |
| 8471 | const [key, value] = yield* f(arr[i]) |
| 8472 | const oentry = MutableHashMap.get(queueMap, key) |
| 8473 | const queue = Option.isSome(oentry) |
| 8474 | ? oentry.value |
| 8475 | : yield* Effect.scoped(RcMap.get(queues, key)) |
| 8476 | yield* RcMap.touch(queues, key) |
| 8477 | yield* Queue.offer(queue, value) |
| 8478 | } |
| 8479 | }), |
| 8480 | options |
| 8481 | )) |
| 8482 | |
| 8483 | /** |
| 8484 | * Groups elements by a key and emits a stream per key. |
| 8485 | * |
| 8486 | * **Example** (Grouping elements by key) |
| 8487 | * |
| 8488 | * ```ts import.meta.vitest |
| 8489 | * import { Effect, Stream } from "effect" |
| 8490 | * |
| 8491 | * const program = Effect.gen(function*() { |
| 8492 | * const grouped = yield* Stream.make(1, 2, 3, 4, 5).pipe( |
| 8493 | * Stream.groupByKey((n) => n % 2 === 0 ? "even" : "odd"), |
| 8494 | * Stream.mapEffect( |
| 8495 | * ([key, stream]) => |
| 8496 | * Stream.runCollect(stream).pipe( |
| 8497 | * Effect.map((values) => [key, values] as const) |
| 8498 | * ), |
| 8499 | * { concurrency: "unbounded" } |
| 8500 | * ), |
| 8501 | * Stream.runCollect |
| 8502 | * ) |
| 8503 | * grouped // => [ [ 'odd', [ 1, 3, 5 ] ], [ 'even', [ 2, 4 ] ] ] |
| 8504 | * }) |
| 8505 | * |
| 8506 | * await Effect.runPromise(program) |
| 8507 | * ``` |
| 8508 | * |
| 8509 | * @category grouping |
| 8510 | * @since 2.0.0 |
| 8511 | */ |
| 8512 | export const groupByKey: { |
| 8513 | <A, K>( |
| 8514 | f: (a: NoInfer<A>) => K, |
| 8515 | options?: { |
| 8516 | readonly bufferSize?: number | undefined |
| 8517 | readonly idleTimeToLive?: Duration.Input | undefined |
| 8518 | } |
| 8519 | ): <E, R>(self: Stream<A, E, R>) => Stream<readonly [K, Stream<A>], E, R> |
| 8520 | <A, E, R, K>( |
| 8521 | self: Stream<A, E, R>, |
| 8522 | f: (a: NoInfer<A>) => K, |
| 8523 | options?: { |
| 8524 | readonly bufferSize?: number | undefined |
| 8525 | readonly idleTimeToLive?: Duration.Input | undefined |
| 8526 | } |
| 8527 | ): Stream<readonly [K, Stream<A>], E, R> |
| 8528 | } = dual((args) => isStream(args[0]), <A, E, R, K>( |
| 8529 | self: Stream<A, E, R>, |
| 8530 | f: (a: NoInfer<A>) => K, |
| 8531 | options?: { |
| 8532 | readonly bufferSize?: number | undefined |
| 8533 | readonly idleTimeToLive?: Duration.Input | undefined |
| 8534 | } |
| 8535 | ): Stream<readonly [K, Stream<A>], E, R> => |
| 8536 | suspend(() => { |
| 8537 | const batch = MutableHashMap.empty<K, Arr.NonEmptyArray<A>>() |
| 8538 | return groupByImpl( |
| 8539 | self, |
| 8540 | Effect.fnUntraced(function*(arr, queues, queueMap) { |
| 8541 | for (let i = 0; i < arr.length; i++) { |
| 8542 | const key = f(arr[i]) |
| 8543 | const ovalues = MutableHashMap.get(batch, key) |
| 8544 | if (Option.isNone(ovalues)) { |
| 8545 | MutableHashMap.set(batch, key, [arr[i]]) |
| 8546 | } else { |
| 8547 | ovalues.value.push(arr[i]) |
| 8548 | } |
| 8549 | } |
| 8550 | for (const [key, values] of batch) { |
| 8551 | const oentry = MutableHashMap.get(queueMap, key) |
| 8552 | const queue = Option.isSome(oentry) |
| 8553 | ? oentry.value |
| 8554 | : yield* Effect.scoped(RcMap.get(queues, key)) |
| 8555 | yield* RcMap.touch(queues, key) |
| 8556 | yield* Queue.offerAll(queue, values) |
| 8557 | } |
| 8558 | MutableHashMap.clear(batch) |
| 8559 | }), |
| 8560 | options |
| 8561 | ) |
| 8562 | })) |
| 8563 | |
| 8564 | const groupByImpl = <A, E, R, K, V, E2, R2>( |
| 8565 | self: Stream<A, E, R>, |
| 8566 | f: ( |
| 8567 | arr: Arr.NonEmptyReadonlyArray<A>, |
| 8568 | queues: RcMap.RcMap<K, Queue.Queue<V, Cause.Done>>, |
| 8569 | queueMap: MutableHashMap.MutableHashMap<K, Queue.Queue<V, Cause.Done>> |
| 8570 | ) => Effect.Effect<void, E2, R2>, |
| 8571 | options?: { |
| 8572 | readonly bufferSize?: number | undefined |
| 8573 | readonly idleTimeToLive?: Duration.Input | undefined |
| 8574 | } |
| 8575 | ): Stream<readonly [K, Stream<V>], E | E2, R | R2> => |
| 8576 | transformPullBracket( |
| 8577 | self, |
| 8578 | Effect.fnUntraced(function*(pull, scope, forkedScope) { |
| 8579 | const out = yield* Queue.unbounded<readonly [K, Stream<V>], E | E2 | Cause.Done<void>>() |
| 8580 | yield* Scope.addFinalizer(scope, Queue.shutdown(out)) |
| 8581 | |
| 8582 | const queueMap = MutableHashMap.empty<K, Queue.Queue<V, Cause.Done>>() |
| 8583 | const queues = yield* RcMap.make({ |
| 8584 | lookup: (key: K) => |
| 8585 | Effect.acquireRelease( |
| 8586 | Queue.make<V, Cause.Done>({ capacity: options?.bufferSize ?? 4096 }).pipe( |
| 8587 | Effect.tap((queue) => { |
| 8588 | MutableHashMap.set(queueMap, key, queue) |
| 8589 | return Queue.offer(out, [key, fromQueue(queue)]) |
| 8590 | }) |
| 8591 | ), |
| 8592 | (queue) => { |
| 8593 | MutableHashMap.remove(queueMap, key) |
| 8594 | return Queue.end(queue) |
| 8595 | } |
| 8596 | ), |
| 8597 | idleTimeToLive: options?.idleTimeToLive ?? Duration.infinity |
| 8598 | }).pipe(Scope.provide(forkedScope)) |
| 8599 | |
| 8600 | yield* Effect.whileLoop({ |
| 8601 | while: constTrue, |
| 8602 | body: constant(Effect.flatMap(pull, (arr) => f(arr, queues, queueMap))), |
| 8603 | step: constVoid |
| 8604 | }).pipe( |
| 8605 | Effect.catchCause((cause) => Queue.failCause(out, cause)), |
| 8606 | Effect.forkIn(scope) |
| 8607 | ) |
| 8608 | |
| 8609 | return Queue.takeAll(out) |
| 8610 | }) |
| 8611 | ) |
| 8612 | |
| 8613 | /** |
| 8614 | * Groups consecutive elements that have equal keys into non-empty arrays. |
| 8615 | * |
| 8616 | * **When to use** |
| 8617 | * |
| 8618 | * Use when you already have a stream ordered by the grouping key and want to |
| 8619 | * emit each consecutive run as a non-empty array while keeping later |
| 8620 | * non-adjacent runs separate. |
| 8621 | * |
| 8622 | * **Details** |
| 8623 | * |
| 8624 | * The key is computed with `f`; adjacent elements whose keys are equal by |
| 8625 | * `Equal.equals` are emitted as one `[key, group]`. Later non-adjacent runs |
| 8626 | * with the same key are emitted separately. |
| 8627 | * |
| 8628 | * @see {@link groupByKey} for grouping all elements with the same key across the stream |
| 8629 | * @see {@link groupBy} for custom grouped stream construction |
| 8630 | * |
| 8631 | * @category grouping |
| 8632 | * @since 2.0.0 |
| 8633 | */ |
| 8634 | export const groupAdjacentBy: { |
| 8635 | <A, K>( |
| 8636 | f: (a: NoInfer<A>) => K |
| 8637 | ): <E, R>(self: Stream<A, E, R>) => Stream<readonly [K, Arr.NonEmptyArray<A>], E, R> |
| 8638 | <A, E, R, K>( |
| 8639 | self: Stream<A, E, R>, |
| 8640 | f: (a: NoInfer<A>) => K |
| 8641 | ): Stream<readonly [K, Arr.NonEmptyArray<A>], E, R> |
| 8642 | } = dual(2, <A, E, R, K>( |
| 8643 | self: Stream<A, E, R>, |
| 8644 | f: (a: NoInfer<A>) => K |
| 8645 | ): Stream<readonly [K, Arr.NonEmptyArray<A>], E, R> => |
| 8646 | transformPull(self, (pull, _scope) => |
| 8647 | Effect.sync(() => { |
| 8648 | let currentKey: K = undefined as any |
| 8649 | let group: Arr.NonEmptyArray<A> | undefined |
| 8650 | let toEmit = Arr.empty<readonly [K, Arr.NonEmptyArray<A>]>() |
| 8651 | const loop: Pull.Pull< |
| 8652 | Arr.NonEmptyReadonlyArray<readonly [K, Arr.NonEmptyArray<A>]>, |
| 8653 | E |
| 8654 | > = pull.pipe( |
| 8655 | Effect.flatMap((chunk) => { |
| 8656 | for (let i = 0; i < chunk.length; i++) { |
| 8657 | const item = chunk[i] |
| 8658 | const key = f(item) |
| 8659 | if (group === undefined) { |
| 8660 | currentKey = key |
| 8661 | group = [item] |
| 8662 | continue |
| 8663 | } else if (Equal.equals(key, currentKey)) { |
| 8664 | group.push(item) |
| 8665 | continue |
| 8666 | } |
| 8667 | toEmit.push([currentKey, group]) |
| 8668 | currentKey = key |
| 8669 | group = [item] |
| 8670 | } |
| 8671 | if (Arr.isArrayNonEmpty(toEmit)) { |
| 8672 | const out = toEmit |
| 8673 | toEmit = [] |
| 8674 | return Effect.succeed(out) |
| 8675 | } |
| 8676 | return loop |
| 8677 | }) |
| 8678 | ) |
| 8679 | let done = false |
| 8680 | return Pull.catchDone(Effect.suspend(() => done ? Cause.done() : loop), () => { |
| 8681 | done = true |
| 8682 | const out = group |
| 8683 | group = undefined |
| 8684 | return out && Arr.isArrayNonEmpty(out) ? Effect.succeed(Arr.of([currentKey, out])) : Cause.done() |
| 8685 | }) |
| 8686 | }))) |
| 8687 | |
| 8688 | /** |
| 8689 | * Applies a sink transducer to the stream and emits each sink result. |
| 8690 | * |
| 8691 | * **Example** (Transducing with a sink) |
| 8692 | * |
| 8693 | * ```ts import.meta.vitest |
| 8694 | * import { Effect, Sink, Stream } from "effect" |
| 8695 | * |
| 8696 | * const program = Effect.gen(function* () { |
| 8697 | * const result = yield* Stream.make(1, 2, 3, 4).pipe( |
| 8698 | * Stream.transduce(Sink.take(2)), |
| 8699 | * Stream.runCollect |
| 8700 | * ) |
| 8701 | * |
| 8702 | * result // => [ [ 1, 2 ], [ 3, 4 ], [] ] |
| 8703 | * }) |
| 8704 | * await Effect.runPromise(program) |
| 8705 | * ``` |
| 8706 | * |
| 8707 | * @category aggregation |
| 8708 | * @since 2.0.0 |
| 8709 | */ |
| 8710 | export const transduce: { |
| 8711 | <A2, A, E2, R2>( |
| 8712 | sink: Sink.Sink<A2, A, A, E2, R2> |
| 8713 | ): <E, R>(self: Stream<A, E, R>) => Stream<A2, E2 | E, R2 | R> |
| 8714 | <A, E, R, A2, E2, R2>( |
| 8715 | self: Stream<A, E, R>, |
| 8716 | sink: Sink.Sink<A2, A, A, E2, R2> |
| 8717 | ): Stream<A2, E2 | E, R2 | R> |
| 8718 | } = dual( |
| 8719 | 2, |
| 8720 | <A, E, R, A2, E2, R2>( |
| 8721 | self: Stream<A, E, R>, |
| 8722 | sink: Sink.Sink<A2, A, A, E2, R2> |
| 8723 | ): Stream<A2, E2 | E, R2 | R> => |
| 8724 | transformPull(self, (upstream, scope) => |
| 8725 | Effect.sync(() => { |
| 8726 | let done: Exit.Exit<never, Cause.Done<void> | E> | undefined |
| 8727 | let leftover: Arr.NonEmptyReadonlyArray<A> | undefined |
| 8728 | const upstreamWithLeftover = Effect.suspend(() => { |
| 8729 | if (leftover !== undefined) { |
| 8730 | const chunk = leftover |
| 8731 | leftover = undefined |
| 8732 | return Effect.succeed(chunk) |
| 8733 | } |
| 8734 | return upstream |
| 8735 | }).pipe( |
| 8736 | Effect.catch((error) => { |
| 8737 | done = Exit.fail(error) |
| 8738 | return Cause.done() |
| 8739 | }) |
| 8740 | ) |
| 8741 | const pull = Effect.map( |
| 8742 | Effect.suspend(() => sink.transform(upstreamWithLeftover, scope)), |
| 8743 | ([value, leftover_]) => { |
| 8744 | leftover = leftover_ |
| 8745 | return Arr.of(value) |
| 8746 | } |
| 8747 | ) |
| 8748 | return Effect.suspend((): Pull.Pull< |
| 8749 | Arr.NonEmptyReadonlyArray<A2>, |
| 8750 | E | E2, |
| 8751 | void, |
| 8752 | R2 |
| 8753 | > => done ? done : pull) |
| 8754 | })) |
| 8755 | ) |
| 8756 | |
| 8757 | /** |
| 8758 | * Aggregates elements using the provided sink and emits each sink result as a stream element. |
| 8759 | * |
| 8760 | * **Details** |
| 8761 | * |
| 8762 | * The stream runs the upstream and downstream in separate fibers, so the sink can keep |
| 8763 | * consuming input while downstream is busy processing the previous output. |
| 8764 | * |
| 8765 | * **Example** (Aggregating with a sink) |
| 8766 | * |
| 8767 | * ```ts import.meta.vitest |
| 8768 | * import { Effect, Sink, Stream } from "effect" |
| 8769 | * |
| 8770 | * await Effect.runPromise(Effect.gen(function* () { |
| 8771 | * const aggregated = yield* Stream.runCollect( |
| 8772 | * Stream.make(1, 2, 3, 4, 5, 6).pipe( |
| 8773 | * Stream.aggregate( |
| 8774 | * Sink.foldUntil(() => 0, 3, (sum, n) => Effect.succeed(sum + n)) |
| 8775 | * ) |
| 8776 | * ) |
| 8777 | * ) |
| 8778 | * aggregated // => [ 6, 15 ] |
| 8779 | * })) |
| 8780 | * ``` |
| 8781 | * |
| 8782 | * @category aggregation |
| 8783 | * @since 2.0.0 |
| 8784 | */ |
| 8785 | export const aggregate: { |
| 8786 | <B, A, A2, E2, R2>( |
| 8787 | sink: Sink.Sink<B, A | A2, A2, E2, R2> |
| 8788 | ): <E, R>(self: Stream<A, E, R>) => Stream<B, E2 | E, R2 | R> |
| 8789 | <A, E, R, B, A2, E2, R2>( |
| 8790 | self: Stream<A, E, R>, |
| 8791 | sink: Sink.Sink<B, A | A2, A2, E2, R2> |
| 8792 | ): Stream<B, E | E2, R | R2> |
| 8793 | } = dual(2, <A, E, R, B, A2, E2, R2>( |
| 8794 | self: Stream<A, E, R>, |
| 8795 | sink: Sink.Sink<B, A | A2, A2, E2, R2> |
| 8796 | ): Stream<B, E | E2, R | R2> => aggregateWithin(self, sink, Schedule.forever)) |
| 8797 | |
| 8798 | /** |
| 8799 | * Aggregates elements with a sink, emitting each result when the sink completes or the schedule triggers. |
| 8800 | * |
| 8801 | * **Details** |
| 8802 | * |
| 8803 | * The schedule can flush the current aggregation even if the sink has not finished. |
| 8804 | * It is stepped at most once per aggregation, when the aggregation receives its |
| 8805 | * first element, with the previous aggregation's output as input. An aggregation |
| 8806 | * the sink completes immediately, such as one built from leftovers, may finish |
| 8807 | * without stepping the schedule. |
| 8808 | * |
| 8809 | * Time between aggregations is hidden from the schedule, so an idle stream does |
| 8810 | * not run it. Time-based schedules such as `Schedule.fixed` and |
| 8811 | * `Schedule.upTo({ duration })` only count time spent aggregating; use |
| 8812 | * `Schedule.spaced` for a flush timer. |
| 8813 | * |
| 8814 | * When the schedule ends, the stream emits the current aggregation and any sink |
| 8815 | * leftovers, then ends without pulling more from upstream. |
| 8816 | * |
| 8817 | * **Example** (Aggregating with a sink and schedule) |
| 8818 | * |
| 8819 | * ```ts import.meta.vitest |
| 8820 | * import { Effect, Schedule, Sink, Stream } from "effect" |
| 8821 | * |
| 8822 | * await Effect.runPromise(Effect.gen(function* () { |
| 8823 | * const aggregated = yield* Stream.runCollect( |
| 8824 | * Stream.make(1, 2, 3, 4, 5, 6).pipe( |
| 8825 | * Stream.aggregateWithin( |
| 8826 | * Sink.foldUntil(() => 0, 3, (sum, n) => Effect.succeed(sum + n)), |
| 8827 | * Schedule.forever |
| 8828 | * ) |
| 8829 | * ) |
| 8830 | * ) |
| 8831 | * aggregated // => [ 6, 15 ] |
| 8832 | * })) |
| 8833 | * ``` |
| 8834 | * |
| 8835 | * @category aggregation |
| 8836 | * @since 2.0.0 |
| 8837 | */ |
| 8838 | export const aggregateWithin: { |
| 8839 | <B, A, A2, E2, R2, C, E3, R3>( |
| 8840 | sink: Sink.Sink<B, A | A2, A2, E2, R2>, |
| 8841 | schedule: Schedule.Schedule<C, Option.Option<B>, E3, R3> |
| 8842 | ): <E, R>(self: Stream<A, E, R>) => Stream<B, E2 | E | E3, R2 | R3 | R> |
| 8843 | <A, E, R, B, A2, E2, R2, C, E3, R3>( |
| 8844 | self: Stream<A, E, R>, |
| 8845 | sink: Sink.Sink<B, A | A2, A2, E2, R2>, |
| 8846 | schedule: Schedule.Schedule<C, Option.Option<B>, E3, R3> |
| 8847 | ): Stream<B, E | E2 | E3, R | R2 | R3> |
| 8848 | } = dual(3, <A, E, R, B, A2, E2, R2, C, E3, R3>( |
| 8849 | self: Stream<A, E, R>, |
| 8850 | sink: Sink.Sink<B, A | A2, A2, E2, R2>, |
| 8851 | schedule: Schedule.Schedule<C, Option.Option<B>, E3, R3> |
| 8852 | ): Stream<B, E | E2 | E3, R | R2 | R3> => |
| 8853 | fromChannel(Channel.fromTransformBracket(Effect.fnUntraced(function*(_upstream, _, scope) { |
| 8854 | const pull = yield* Channel.toPullScoped(self.channel, _) |
| 8855 | |
| 8856 | const pullLatch = Latch.makeUnsafe(false) |
| 8857 | const scheduleStep = Symbol() |
| 8858 | const buffer = yield* Queue.make<Arr.NonEmptyReadonlyArray<A> | typeof scheduleStep, E | Cause.Done<void>>({ |
| 8859 | capacity: 0 |
| 8860 | }) |
| 8861 | |
| 8862 | yield* pull.pipe( |
| 8863 | pullLatch.whenOpen, |
| 8864 | Effect.flatMap((arr) => { |
| 8865 | pullLatch.closeUnsafe() |
| 8866 | return Queue.offer(buffer, arr) |
| 8867 | }), |
| 8868 | Effect.forever, // Keep autoYield enabled so the schedule can run. |
| 8869 | Effect.catchCause((cause) => Queue.failCause(buffer, cause)), |
| 8870 | Effect.forkIn(scope) |
| 8871 | ) |
| 8872 | |
| 8873 | // The schedule clock excludes time between aggregations. |
| 8874 | const clock = yield* Clock |
| 8875 | const step = yield* Schedule.toStep(schedule) |
| 8876 | const hasInput = Latch.makeUnsafe(false) |
| 8877 | let scheduleTime = clock.currentTimeMillisUnsafe() |
| 8878 | let openedAt = 0 |
| 8879 | let lastOutput = Option.none<B>() |
| 8880 | let leftover: Arr.NonEmptyReadonlyArray<A2> | undefined |
| 8881 | const onInput = <X>(chunk: X): Effect.Effect<X> => { |
| 8882 | if (hasInput.openUnsafe()) { |
| 8883 | openedAt = clock.currentTimeMillisUnsafe() |
| 8884 | } |
| 8885 | return Effect.succeed(chunk) |
| 8886 | } |
| 8887 | const stepToBuffer: Pull.Pull<never, E3, void, R3> = hasInput.await.pipe( |
| 8888 | Effect.flatMap(() => step(scheduleTime, lastOutput)), |
| 8889 | Effect.flatMap(([, delay]) => Effect.sleep(delay)), |
| 8890 | Effect.flatMap(() => Queue.offer(buffer, scheduleStep)), |
| 8891 | // End input so the sink drains leftovers without further upstream pulls. |
| 8892 | Pull.catchDone(() => Queue.end(buffer)), |
| 8893 | Effect.flatMap(() => Effect.never) |
| 8894 | ) |
| 8895 | |
| 8896 | const sinkUpstream = Effect.suspend((): Pull.Pull<Arr.NonEmptyReadonlyArray<A | A2>, E> => { |
| 8897 | if (leftover !== undefined) { |
| 8898 | const chunk = leftover |
| 8899 | leftover = undefined |
| 8900 | return onInput(chunk) |
| 8901 | } |
| 8902 | if (buffer.state._tag === "Open") { |
| 8903 | pullLatch.openUnsafe() |
| 8904 | } |
| 8905 | return Effect.flatMap(Queue.take(buffer), (arr) => arr === scheduleStep ? Cause.done() : onInput(arr)) |
| 8906 | }) |
| 8907 | const emit = Effect.flatMap(([value, leftover_]: Sink.End<B, A2>) => { |
| 8908 | if (hasInput.isOpen()) { |
| 8909 | scheduleTime += clock.currentTimeMillisUnsafe() - openedAt |
| 8910 | } else if (buffer.state._tag === "Done") { |
| 8911 | return Cause.done() |
| 8912 | } |
| 8913 | lastOutput = Option.some(value) |
| 8914 | leftover = leftover_ |
| 8915 | return Effect.succeed(Arr.of(value)) |
| 8916 | }) |
| 8917 | |
| 8918 | return Effect.suspend((): Pull.Pull<Arr.NonEmptyReadonlyArray<B>, E | E2 | E3, void, R2 | R3> => { |
| 8919 | if (buffer.state._tag === "Done" && leftover === undefined) { |
| 8920 | return buffer.state.exit as Exit.Exit<never, Cause.Done<void> | E> |
| 8921 | } |
| 8922 | hasInput.closeUnsafe() |
| 8923 | const pull = emit(Effect.suspend(() => sink.transform(sinkUpstream as any, scope))) |
| 8924 | return buffer.state._tag === "Open" ? Effect.raceFirst(pull, stepToBuffer) : pull |
| 8925 | }) |
| 8926 | })))) |
| 8927 | |
| 8928 | /** |
| 8929 | * Creates a fixed-size tuple of streams that each emit |
| 8930 | * the same elements as the source stream. |
| 8931 | * |
| 8932 | * **Details** |
| 8933 | * |
| 8934 | * The source stream starts after all downstream streams have been subscribed. |
| 8935 | * With the default suspend strategy, the source can only advance `capacity` |
| 8936 | * chunks ahead of the slowest downstream stream. If a downstream stream is |
| 8937 | * interrupted, it unsubscribes from the broadcast so it no longer contributes |
| 8938 | * backpressure. Finite fractional values of `n` are rounded down. `NaN` and |
| 8939 | * non-positive values create no downstream streams. |
| 8940 | * |
| 8941 | * **Example** (Broadcasting to two consumers) |
| 8942 | * |
| 8943 | * ```ts import.meta.vitest |
| 8944 | * import { Effect, Stream } from "effect" |
| 8945 | * |
| 8946 | * const program = Effect.scoped( |
| 8947 | * Effect.gen(function*() { |
| 8948 | * const [left, right] = yield* Stream.make(1, 2, 3).pipe( |
| 8949 | * Stream.broadcastN({ n: 2, capacity: 8 }) |
| 8950 | * ) |
| 8951 | * |
| 8952 | * const values = yield* Effect.all([ |
| 8953 | * Stream.runCollect(left), |
| 8954 | * Stream.runCollect(right) |
| 8955 | * ], { concurrency: "unbounded" }) |
| 8956 | * |
| 8957 | * values // => [ [ 1, 2, 3 ], [ 1, 2, 3 ] ] |
| 8958 | * }) |
| 8959 | * ) |
| 8960 | * |
| 8961 | * await Effect.runPromise(program) |
| 8962 | * ``` |
| 8963 | * |
| 8964 | * @category broadcasting |
| 8965 | * @since 4.0.0 |
| 8966 | */ |
| 8967 | export const broadcastN: { |
| 8968 | <const N extends number>( |
| 8969 | options: { |
| 8970 | readonly n: N |
| 8971 | readonly capacity: "unbounded" |
| 8972 | readonly replay?: number | undefined |
| 8973 | } | { |
| 8974 | readonly n: N |
| 8975 | readonly capacity: number |
| 8976 | readonly strategy?: "sliding" | "dropping" | "suspend" | undefined |
| 8977 | readonly replay?: number | undefined |
| 8978 | } |
| 8979 | ): <A, E, R>(self: Stream<A, E, R>) => Effect.Effect<TupleOf<N, Stream<A, E>>, never, Scope.Scope | R> |
| 8980 | <A, E, R, const N extends number>( |
| 8981 | self: Stream<A, E, R>, |
| 8982 | options: { |
| 8983 | readonly n: N |
| 8984 | readonly capacity: "unbounded" |
| 8985 | readonly replay?: number | undefined |
| 8986 | } | { |
| 8987 | readonly capacity: number |
| 8988 | readonly n: N |
| 8989 | readonly strategy?: "sliding" | "dropping" | "suspend" | undefined |
| 8990 | readonly replay?: number | undefined |
| 8991 | } |
| 8992 | ): Effect.Effect<TupleOf<N, Stream<A, E>>, never, Scope.Scope | R> |
| 8993 | } = dual( |
| 8994 | 2, |
| 8995 | Effect.fnUntraced(function*<A, E, R, const N extends number>( |
| 8996 | self: Stream<A, E, R>, |
| 8997 | options: { |
| 8998 | readonly n: N |
| 8999 | readonly capacity: "unbounded" |
| 9000 | readonly replay?: number | undefined |
| 9001 | } | { |
| 9002 | readonly n: N |
| 9003 | readonly capacity: number |
| 9004 | readonly strategy?: "sliding" | "dropping" | "suspend" | undefined |
| 9005 | readonly replay?: number | undefined |
| 9006 | } |
| 9007 | ) { |
| 9008 | const n = Count.normalize(options.n) |
| 9009 | const pubsub = yield* makePubSub<Take.Take<A, E>>(options) |
| 9010 | const streams = new Array(n) |
| 9011 | const parentScope = yield* Scope.Scope |
| 9012 | for (let i = 0; i < n; i++) { |
| 9013 | const scope = Scope.forkUnsafe(parentScope) |
| 9014 | const subscription = yield* PubSub.subscribe(pubsub).pipe( |
| 9015 | Effect.provideService(Scope.Scope, scope) |
| 9016 | ) |
| 9017 | streams[i] = Channel.fromEffectTake(PubSub.take(subscription)).pipe( |
| 9018 | Channel.onExit((exit) => Scope.close(scope, exit)), |
| 9019 | fromChannel |
| 9020 | ) |
| 9021 | } |
| 9022 | yield* Channel.runForEach(self.channel, (value) => PubSub.publish(pubsub, value)).pipe( |
| 9023 | Effect.onExit((exit) => PubSub.end(pubsub, exit)), |
| 9024 | Effect.forkScoped |
| 9025 | ) |
| 9026 | return streams as TupleOf<N, Stream<A, E>> |
| 9027 | }) |
| 9028 | ) |
| 9029 | |
| 9030 | const makePubSub = <A>( |
| 9031 | options: { |
| 9032 | readonly capacity: "unbounded" |
| 9033 | readonly replay?: number | undefined |
| 9034 | } | { |
| 9035 | readonly capacity: number |
| 9036 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 9037 | readonly replay?: number | undefined |
| 9038 | } |
| 9039 | ) => |
| 9040 | Effect.acquireRelease( |
| 9041 | options.capacity === "unbounded" |
| 9042 | ? PubSub.unbounded<A>(options) |
| 9043 | : options.strategy === "dropping" |
| 9044 | ? PubSub.dropping<A>(options) |
| 9045 | : options.strategy === "sliding" |
| 9046 | ? PubSub.sliding<A>(options) |
| 9047 | : PubSub.bounded<A>(options), |
| 9048 | PubSub.shutdown |
| 9049 | ) |
| 9050 | |
| 9051 | /** |
| 9052 | * Creates a PubSub-backed stream that multicasts the source to all subscribers. |
| 9053 | * |
| 9054 | * **Details** |
| 9055 | * |
| 9056 | * The returned stream is scoped and uses the provided PubSub capacity and replay settings. |
| 9057 | * |
| 9058 | * **Example** (Broadcasting a stream) |
| 9059 | * |
| 9060 | * ```ts import.meta.vitest |
| 9061 | * import { Effect, Stream } from "effect" |
| 9062 | * |
| 9063 | * const program = Effect.scoped( |
| 9064 | * Effect.gen(function* () { |
| 9065 | * const broadcasted = yield* Stream.broadcast(Stream.fromArray([1, 2, 3]), { |
| 9066 | * capacity: 8, |
| 9067 | * replay: 3 |
| 9068 | * }) |
| 9069 | * |
| 9070 | * const [left, right] = yield* Effect.all([ |
| 9071 | * Stream.runCollect(broadcasted), |
| 9072 | * Stream.runCollect(broadcasted) |
| 9073 | * ], { concurrency: "unbounded" }) |
| 9074 | * |
| 9075 | * const result = [left, right] // => [[1, 2, 3], [1, 2, 3]] |
| 9076 | * }) |
| 9077 | * ) |
| 9078 | * |
| 9079 | * await Effect.runPromise(program) |
| 9080 | * ``` |
| 9081 | * |
| 9082 | * @category broadcasting |
| 9083 | * @since 2.0.0 |
| 9084 | */ |
| 9085 | export const broadcast: { |
| 9086 | ( |
| 9087 | options: { |
| 9088 | readonly capacity: "unbounded" |
| 9089 | readonly replay?: number | undefined |
| 9090 | } | { |
| 9091 | readonly capacity: number |
| 9092 | readonly strategy?: "sliding" | "dropping" | "suspend" | undefined |
| 9093 | readonly replay?: number | undefined |
| 9094 | } |
| 9095 | ): <A, E, R>(self: Stream<A, E, R>) => Effect.Effect<Stream<A, E>, never, Scope.Scope | R> |
| 9096 | <A, E, R>( |
| 9097 | self: Stream<A, E, R>, |
| 9098 | options: { |
| 9099 | readonly capacity: "unbounded" |
| 9100 | readonly replay?: number | undefined |
| 9101 | } | { |
| 9102 | readonly capacity: number |
| 9103 | readonly strategy?: "sliding" | "dropping" | "suspend" | undefined |
| 9104 | readonly replay?: number | undefined |
| 9105 | } |
| 9106 | ): Effect.Effect<Stream<A, E>, never, Scope.Scope | R> |
| 9107 | } = dual(2, <A, E, R>( |
| 9108 | self: Stream<A, E, R>, |
| 9109 | options: { |
| 9110 | readonly capacity: "unbounded" |
| 9111 | readonly replay?: number | undefined |
| 9112 | } | { |
| 9113 | readonly capacity: number |
| 9114 | readonly strategy?: "sliding" | "dropping" | "suspend" | undefined |
| 9115 | readonly replay?: number | undefined |
| 9116 | } |
| 9117 | ): Effect.Effect<Stream<A, E>, never, Scope.Scope | R> => Effect.map(toPubSubTake(self, options), fromPubSubTake)) |
| 9118 | |
| 9119 | /** |
| 9120 | * Returns a new Stream that multicasts the original stream, subscribing when the first consumer starts. |
| 9121 | * |
| 9122 | * **Details** |
| 9123 | * |
| 9124 | * The upstream continues running while there is at least one consumer and is finalized after the last one exits. |
| 9125 | * If `idleTimeToLive` is set, the upstream is kept alive for that duration so a later subscriber can continue from |
| 9126 | * the next element instead of restarting. |
| 9127 | * |
| 9128 | * **Example** (Sharing a stream) |
| 9129 | * |
| 9130 | * ```ts import.meta.vitest |
| 9131 | * import { Deferred, Effect, Ref, Stream } from "effect" |
| 9132 | * |
| 9133 | * const result = await Effect.runPromise( |
| 9134 | * Effect.scoped( |
| 9135 | * Effect.gen(function*() { |
| 9136 | * const firstReady = yield* Deferred.make<void>() |
| 9137 | * const secondReady = yield* Deferred.make<void>() |
| 9138 | * const acquisitions = yield* Ref.make(0) |
| 9139 | * const source = Stream.fromEffect(Ref.update(acquisitions, (n) => n + 1)).pipe( |
| 9140 | * Stream.drain, |
| 9141 | * Stream.concat(Stream.make(0)), |
| 9142 | * Stream.concat( |
| 9143 | * Stream.fromEffect(Effect.all([Deferred.await(firstReady), Deferred.await(secondReady)])).pipe(Stream.drain) |
| 9144 | * ), |
| 9145 | * Stream.concat(Stream.make(1, 2, 3)) |
| 9146 | * ) |
| 9147 | * const shared = yield* Stream.share(source, { capacity: 16, replay: 1 }) |
| 9148 | * const consume = (ready: Deferred.Deferred<void>) => |
| 9149 | * shared.pipe( |
| 9150 | * Stream.tap((value) => value === 0 ? Deferred.succeed(ready, void 0) : Effect.void), |
| 9151 | * Stream.filter((value) => value !== 0), |
| 9152 | * Stream.runCollect |
| 9153 | * ) |
| 9154 | * |
| 9155 | * const values = yield* Effect.all([consume(firstReady), consume(secondReady)], { concurrency: "unbounded" }) |
| 9156 | * return { values, acquisitions: yield* Ref.get(acquisitions) } |
| 9157 | * }) |
| 9158 | * ) |
| 9159 | * ) |
| 9160 | * result // => { values: [[1, 2, 3], [1, 2, 3]], acquisitions: 1 } |
| 9161 | * ``` |
| 9162 | * |
| 9163 | * @category broadcasting |
| 9164 | * @since 3.8.0 |
| 9165 | */ |
| 9166 | export const share: { |
| 9167 | ( |
| 9168 | options: { |
| 9169 | readonly capacity: "unbounded" |
| 9170 | readonly replay?: number | undefined |
| 9171 | readonly idleTimeToLive?: Duration.Input | undefined |
| 9172 | } | { |
| 9173 | readonly capacity: number |
| 9174 | readonly strategy?: "sliding" | "dropping" | "suspend" | undefined |
| 9175 | readonly replay?: number | undefined |
| 9176 | readonly idleTimeToLive?: Duration.Input | undefined |
| 9177 | } |
| 9178 | ): <A, E, R>(self: Stream<A, E, R>) => Effect.Effect<Stream<A, E>, never, Scope.Scope | R> |
| 9179 | <A, E, R>( |
| 9180 | self: Stream<A, E, R>, |
| 9181 | options: { |
| 9182 | readonly capacity: "unbounded" |
| 9183 | readonly replay?: number | undefined |
| 9184 | readonly idleTimeToLive?: Duration.Input | undefined |
| 9185 | } | { |
| 9186 | readonly capacity: number |
| 9187 | readonly strategy?: "sliding" | "dropping" | "suspend" | undefined |
| 9188 | readonly replay?: number | undefined |
| 9189 | readonly idleTimeToLive?: Duration.Input | undefined |
| 9190 | } |
| 9191 | ): Effect.Effect<Stream<A, E>, never, Scope.Scope | R> |
| 9192 | } = dual(2, <A, E, R>( |
| 9193 | self: Stream<A, E, R>, |
| 9194 | options: { |
| 9195 | readonly capacity: "unbounded" |
| 9196 | readonly replay?: number | undefined |
| 9197 | readonly idleTimeToLive?: Duration.Input | undefined |
| 9198 | } | { |
| 9199 | readonly capacity: number |
| 9200 | readonly strategy?: "sliding" | "dropping" | "suspend" | undefined |
| 9201 | readonly replay?: number | undefined |
| 9202 | readonly idleTimeToLive?: Duration.Input | undefined |
| 9203 | } |
| 9204 | ): Effect.Effect<Stream<A, E>, never, Scope.Scope | R> => |
| 9205 | Effect.map( |
| 9206 | RcRef.make({ |
| 9207 | acquire: broadcast(self, options), |
| 9208 | idleTimeToLive: options.idleTimeToLive |
| 9209 | }), |
| 9210 | (ref) => unwrap(RcRef.get(ref)) |
| 9211 | )) |
| 9212 | |
| 9213 | /** |
| 9214 | * Pipes this stream through a channel that consumes and emits chunked elements. |
| 9215 | * |
| 9216 | * **Details** |
| 9217 | * |
| 9218 | * The channel receives `NonEmptyReadonlyArray` chunks and can transform both the |
| 9219 | * output elements and error type. |
| 9220 | * |
| 9221 | * **Example** (Piping through a channel) |
| 9222 | * |
| 9223 | * ```ts import.meta.vitest |
| 9224 | * import { Array, Channel, Effect, Stream } from "effect" |
| 9225 | * |
| 9226 | * type NumberChunk = readonly [number, ...Array<number>] |
| 9227 | * |
| 9228 | * const doubleChunks = Channel.identity<NumberChunk, never, unknown>().pipe( |
| 9229 | * Channel.map((chunk) => Array.map(chunk, (n) => n * 2)) |
| 9230 | * ) |
| 9231 | * |
| 9232 | * const program = Effect.gen(function*() { |
| 9233 | * const result = yield* Stream.fromArray([1, 2, 3]).pipe( |
| 9234 | * Stream.rechunk(2), |
| 9235 | * Stream.pipeThroughChannel(doubleChunks), |
| 9236 | * Stream.runCollect |
| 9237 | * ) |
| 9238 | * result // => [ 2, 4, 6 ] |
| 9239 | * }) |
| 9240 | * |
| 9241 | * await Effect.runPromise(program) |
| 9242 | * ``` |
| 9243 | * |
| 9244 | * @category sequencing |
| 9245 | * @since 2.0.0 |
| 9246 | */ |
| 9247 | export const pipeThroughChannel: { |
| 9248 | <R2, E, E2, A, A2>( |
| 9249 | channel: Channel.Channel<Arr.NonEmptyReadonlyArray<A2>, E2, unknown, Arr.NonEmptyReadonlyArray<A>, E, unknown, R2> |
| 9250 | ): <R>(self: Stream<A, E, R>) => Stream<A2, E2, R2 | R> |
| 9251 | <R, R2, E, E2, A, A2>( |
| 9252 | self: Stream<A, E, R>, |
| 9253 | channel: Channel.Channel<Arr.NonEmptyReadonlyArray<A2>, E2, unknown, Arr.NonEmptyReadonlyArray<A>, E, unknown, R2> |
| 9254 | ): Stream<A2, E2, R | R2> |
| 9255 | } = dual(2, <R, R2, E, E2, A, A2>( |
| 9256 | self: Stream<A, E, R>, |
| 9257 | channel: Channel.Channel<Arr.NonEmptyReadonlyArray<A2>, E2, unknown, Arr.NonEmptyReadonlyArray<A>, E, unknown, R2> |
| 9258 | ): Stream<A2, E2, R | R2> => fromChannel(Channel.pipeTo(self.channel, channel))) |
| 9259 | |
| 9260 | /** |
| 9261 | * Pipes values through the provided channel while preserving this stream's |
| 9262 | * failures alongside any channel failures. |
| 9263 | * |
| 9264 | * **Details** |
| 9265 | * |
| 9266 | * Upstream failures are not passed to the channel, so the resulting stream can |
| 9267 | * fail with either the original stream error or the channel error. |
| 9268 | * |
| 9269 | * **Example** (Piping through a channel with failures) |
| 9270 | * |
| 9271 | * ```ts import.meta.vitest |
| 9272 | * import { Array, Channel, Effect, Stream } from "effect" |
| 9273 | * |
| 9274 | * type NumberChunk = readonly [number, ...Array<number>] |
| 9275 | * |
| 9276 | * const stringifyChunks = Channel.identity<NumberChunk, "StreamError", unknown>().pipe( |
| 9277 | * Channel.map((chunk) => Array.map(chunk, String)) |
| 9278 | * ) |
| 9279 | * |
| 9280 | * await Effect.runPromise(Effect.gen(function*() { |
| 9281 | * const result = yield* Stream.make(1, 2, 3).pipe( |
| 9282 | * Stream.rechunk(2), |
| 9283 | * Stream.pipeThroughChannelOrFail(stringifyChunks), |
| 9284 | * Stream.runCollect |
| 9285 | * ) |
| 9286 | * |
| 9287 | * result // => ["1", "2", "3"] |
| 9288 | * })) |
| 9289 | * ``` |
| 9290 | * |
| 9291 | * @category sequencing |
| 9292 | * @since 2.0.0 |
| 9293 | */ |
| 9294 | export const pipeThroughChannelOrFail: { |
| 9295 | <R2, E, E2, A, A2>( |
| 9296 | channel: Channel.Channel<Arr.NonEmptyReadonlyArray<A2>, E2, unknown, Arr.NonEmptyReadonlyArray<A>, E, unknown, R2> |
| 9297 | ): <R>(self: Stream<A, E, R>) => Stream<A2, E | E2, R2 | R> |
| 9298 | <R, R2, E, E2, A, A2>( |
| 9299 | self: Stream<A, E, R>, |
| 9300 | channel: Channel.Channel<Arr.NonEmptyReadonlyArray<A2>, E2, unknown, Arr.NonEmptyReadonlyArray<A>, E, unknown, R2> |
| 9301 | ): Stream<A2, E | E2, R | R2> |
| 9302 | } = dual(2, <R, R2, E, E2, A, A2>( |
| 9303 | self: Stream<A, E, R>, |
| 9304 | channel: Channel.Channel<Arr.NonEmptyReadonlyArray<A2>, E2, unknown, Arr.NonEmptyReadonlyArray<A>, E, unknown, R2> |
| 9305 | ): Stream<A2, E | E2, R | R2> => fromChannel(Channel.pipeToOrFail(self.channel, channel))) |
| 9306 | |
| 9307 | /** |
| 9308 | * Pipes the stream through `Sink.toChannel`, emitting only the sink leftovers. |
| 9309 | * |
| 9310 | * **Details** |
| 9311 | * |
| 9312 | * If the sink completes mid-chunk, the remaining elements become the output stream. |
| 9313 | * |
| 9314 | * **Example** (Piping through a sink) |
| 9315 | * |
| 9316 | * ```ts import.meta.vitest |
| 9317 | * import { Effect, Sink, Stream } from "effect" |
| 9318 | * |
| 9319 | * const program = Effect.gen(function*() { |
| 9320 | * const leftovers = yield* Stream.make(1, 2, 3, 4).pipe( |
| 9321 | * Stream.pipeThrough(Sink.take(2)), |
| 9322 | * Stream.runCollect |
| 9323 | * ) |
| 9324 | * |
| 9325 | * leftovers // => [ 3, 4 ] |
| 9326 | * }) |
| 9327 | * |
| 9328 | * await Effect.runPromise(program) |
| 9329 | * ``` |
| 9330 | * |
| 9331 | * @category sequencing |
| 9332 | * @since 2.0.0 |
| 9333 | */ |
| 9334 | export const pipeThrough: { |
| 9335 | <A2, A, L, E2, R2>(sink: Sink.Sink<A2, A, L, E2, R2>): <E, R>(self: Stream<A, E, R>) => Stream<L, E2 | E, R2 | R> |
| 9336 | <A, E, R, A2, L, E2, R2>(self: Stream<A, E, R>, sink: Sink.Sink<A2, A, L, E2, R2>): Stream<L, E | E2, R | R2> |
| 9337 | } = dual( |
| 9338 | 2, |
| 9339 | <A, E, R, A2, L, E2, R2>(self: Stream<A, E, R>, sink: Sink.Sink<A2, A, L, E2, R2>): Stream<L, E | E2, R | R2> => |
| 9340 | self.channel.pipe( |
| 9341 | Channel.pipeToOrFail(Sink.toChannel(sink)), |
| 9342 | Channel.concatWith(([_, leftover]) => leftover ? Channel.succeed(leftover) : Channel.empty), |
| 9343 | fromChannel |
| 9344 | ) |
| 9345 | ) |
| 9346 | |
| 9347 | /** |
| 9348 | * Collects all elements into an array and emits it as a single element. |
| 9349 | * |
| 9350 | * **Example** (Collecting values into a stream element) |
| 9351 | * |
| 9352 | * ```ts import.meta.vitest |
| 9353 | * import { Effect, Stream } from "effect" |
| 9354 | * |
| 9355 | * const stream = Stream.make(1, 2, 3) |
| 9356 | * |
| 9357 | * const program = Effect.gen(function*() { |
| 9358 | * const collected = yield* stream.pipe(Stream.collect, Stream.runCollect) |
| 9359 | * collected[0] // => [ 1, 2, 3 ] |
| 9360 | * }) |
| 9361 | * |
| 9362 | * await Effect.runPromise(program) |
| 9363 | * ``` |
| 9364 | * |
| 9365 | * @category accumulation |
| 9366 | * @since 4.0.0 |
| 9367 | */ |
| 9368 | export const collect = <A, E, R>(self: Stream<A, E, R>): Stream<Array<A>, E, R> => fromEffect(runCollect(self)) |
| 9369 | |
| 9370 | /** |
| 9371 | * Accumulates elements into a growing array, emitting the cumulative array for each input chunk. |
| 9372 | * |
| 9373 | * **Example** (Accumulating stream elements) |
| 9374 | * |
| 9375 | * ```ts import.meta.vitest |
| 9376 | * import { Effect, Stream } from "effect" |
| 9377 | * |
| 9378 | * const program = Effect.gen(function*() { |
| 9379 | * const accumulated = yield* Stream.runCollect( |
| 9380 | * Stream.fromArray([1, 2, 3]).pipe( |
| 9381 | * Stream.rechunk(1), |
| 9382 | * Stream.accumulate |
| 9383 | * ) |
| 9384 | * ) |
| 9385 | * accumulated // => [ [ 1 ], [ 1, 2 ], [ 1, 2, 3 ] ] |
| 9386 | * }) |
| 9387 | * |
| 9388 | * await Effect.runPromise(program) |
| 9389 | * ``` |
| 9390 | * |
| 9391 | * @category accumulation |
| 9392 | * @since 2.0.0 |
| 9393 | */ |
| 9394 | export const accumulate = <A, E, R>(self: Stream<A, E, R>): Stream<Arr.NonEmptyArray<A>, E, R> => |
| 9395 | mapAccumArray(self, Arr.empty<A>, (acc, as) => { |
| 9396 | const combined = Arr.appendAll(acc, as) |
| 9397 | return [combined, [combined]] |
| 9398 | }) |
| 9399 | |
| 9400 | /** |
| 9401 | * Emits only elements that differ from the previous one. |
| 9402 | * |
| 9403 | * **Example** (Emitting changed values) |
| 9404 | * |
| 9405 | * ```ts import.meta.vitest |
| 9406 | * import { Effect, Stream } from "effect" |
| 9407 | * |
| 9408 | * const program = Effect.gen(function*() { |
| 9409 | * const values = yield* Stream.fromIterable([1, 1, 2, 2, 3]).pipe( |
| 9410 | * Stream.changes, |
| 9411 | * Stream.runCollect |
| 9412 | * ) |
| 9413 | * |
| 9414 | * values // => [ 1, 2, 3 ] |
| 9415 | * }) |
| 9416 | * |
| 9417 | * await Effect.runPromise(program) |
| 9418 | * ``` |
| 9419 | * |
| 9420 | * @category deduplication |
| 9421 | * @since 2.0.0 |
| 9422 | */ |
| 9423 | export const changes = <A, E, R>(self: Stream<A, E, R>): Stream<A, E, R> => changesWith(self, Equal.equals) |
| 9424 | |
| 9425 | /** |
| 9426 | * Returns a stream that only emits elements that are not equal to the previously emitted element, as determined by the specified predicate. |
| 9427 | * |
| 9428 | * **Example** (Emitting values that changed by equivalence) |
| 9429 | * |
| 9430 | * ```ts import.meta.vitest |
| 9431 | * import { Effect, Stream } from "effect" |
| 9432 | * |
| 9433 | * const stream = Stream.make("A", "a", "B", "b", "b").pipe( |
| 9434 | * Stream.changesWith((left, right) => left.toLowerCase() === right.toLowerCase()) |
| 9435 | * ) |
| 9436 | * |
| 9437 | * await Effect.runPromise( |
| 9438 | * Effect.gen(function*() { |
| 9439 | * const values = yield* Stream.runCollect(stream) |
| 9440 | * values // => [ 'A', 'B' ] |
| 9441 | * }) |
| 9442 | * ) |
| 9443 | * ``` |
| 9444 | * |
| 9445 | * @category deduplication |
| 9446 | * @since 2.0.0 |
| 9447 | */ |
| 9448 | export const changesWith: { |
| 9449 | <A>(f: (x: A, y: A) => boolean): <E, R>(self: Stream<A, E, R>) => Stream<A, E, R> |
| 9450 | <A, E, R>(self: Stream<A, E, R>, f: (x: A, y: A) => boolean): Stream<A, E, R> |
| 9451 | } = dual( |
| 9452 | 2, |
| 9453 | <A, E, R>(self: Stream<A, E, R>, f: (x: A, y: A) => boolean): Stream<A, E, R> => |
| 9454 | transformPull(self, (pull, _scope) => |
| 9455 | Effect.sync(() => { |
| 9456 | let first = true |
| 9457 | let last: A |
| 9458 | return Effect.flatMap(pull, function loop(arr): Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E> { |
| 9459 | const out: Array<A> = [] |
| 9460 | let i = 0 |
| 9461 | if (first) { |
| 9462 | first = false |
| 9463 | last = arr[0] |
| 9464 | i = 1 |
| 9465 | out.push(last) |
| 9466 | } |
| 9467 | for (; i < arr.length; i++) { |
| 9468 | const a = arr[i] |
| 9469 | if (f(a, last)) continue |
| 9470 | last = a |
| 9471 | out.push(a) |
| 9472 | } |
| 9473 | return Arr.isArrayNonEmpty(out) ? Effect.succeed(out) : Effect.flatMap(pull, loop) |
| 9474 | }) |
| 9475 | })) |
| 9476 | ) |
| 9477 | |
| 9478 | /** |
| 9479 | * Emits only elements that differ from the previous element, using an effectful equality check. |
| 9480 | * |
| 9481 | * **Details** |
| 9482 | * |
| 9483 | * The predicate runs for each element after the first; returning `true` treats it as equal and skips it. |
| 9484 | * |
| 9485 | * **Example** (Effectfully emitting changed values) |
| 9486 | * |
| 9487 | * ```ts import.meta.vitest |
| 9488 | * import { Effect, Stream } from "effect" |
| 9489 | * |
| 9490 | * const program = Effect.gen(function*() { |
| 9491 | * const stream = Stream.make(1, 1, 2, 2, 3, 3).pipe( |
| 9492 | * Stream.changesWithEffect((a, b) => Effect.succeed(a === b)) |
| 9493 | * ) |
| 9494 | * const result = yield* Stream.runCollect(stream) |
| 9495 | * result // => [ 1, 2, 3 ] |
| 9496 | * }) |
| 9497 | * |
| 9498 | * await Effect.runPromise(program) |
| 9499 | * ``` |
| 9500 | * |
| 9501 | * @category deduplication |
| 9502 | * @since 2.0.0 |
| 9503 | */ |
| 9504 | export const changesWithEffect: { |
| 9505 | <A, E2, R2>( |
| 9506 | f: (x: A, y: A) => Effect.Effect<boolean, E2, R2> |
| 9507 | ): <E, R>(self: Stream<A, E, R>) => Stream<A, E | E2, R | R2> |
| 9508 | <A, E, R, E2, R2>( |
| 9509 | self: Stream<A, E, R>, |
| 9510 | f: (x: A, y: A) => Effect.Effect<boolean, E2, R2> |
| 9511 | ): Stream<A, E | E2, R | R2> |
| 9512 | } = dual( |
| 9513 | 2, |
| 9514 | <A, E, R, E2, R2>( |
| 9515 | self: Stream<A, E, R>, |
| 9516 | f: (x: A, y: A) => Effect.Effect<boolean, E2, R2> |
| 9517 | ): Stream<A, E | E2, R | R2> => |
| 9518 | transformPull(self, (pull, _scope) => |
| 9519 | Effect.sync(() => { |
| 9520 | let first = true |
| 9521 | let last: A |
| 9522 | return Effect.flatMap( |
| 9523 | pull, |
| 9524 | Effect.fnUntraced(function* loop(arr): Generator< |
| 9525 | Pull.Pull<any, E | E2, void, R2>, |
| 9526 | Arr.NonEmptyReadonlyArray<A>, |
| 9527 | any |
| 9528 | > { |
| 9529 | const out: Array<A> = [] |
| 9530 | let i = 0 |
| 9531 | if (first) { |
| 9532 | first = false |
| 9533 | last = arr[0] |
| 9534 | i = 1 |
| 9535 | out.push(last) |
| 9536 | } |
| 9537 | for (; i < arr.length; i++) { |
| 9538 | const a = arr[i] |
| 9539 | if (yield* f(a, last)) continue |
| 9540 | last = a |
| 9541 | out.push(a) |
| 9542 | } |
| 9543 | return Arr.isArrayNonEmpty(out) ? out : yield* Effect.flatMap(pull, Effect.fnUntraced(loop)) |
| 9544 | }) |
| 9545 | ) |
| 9546 | })) |
| 9547 | ) |
| 9548 | |
| 9549 | /** |
| 9550 | * Decodes Uint8Array chunks into strings using TextDecoder with an optional encoding. |
| 9551 | * |
| 9552 | * **Example** (Decoding Uint8Array chunks into strings using TextDecoder with an optional encoding) |
| 9553 | * |
| 9554 | * ```ts import.meta.vitest |
| 9555 | * import { Effect, Stream } from "effect" |
| 9556 | * |
| 9557 | * const encoder = new TextEncoder() |
| 9558 | * const stream = Stream.make( |
| 9559 | * encoder.encode("Hello"), |
| 9560 | * encoder.encode(" World") |
| 9561 | * ) |
| 9562 | * |
| 9563 | * const program = Effect.gen(function*() { |
| 9564 | * const decoded = yield* stream.pipe( |
| 9565 | * Stream.decodeText, |
| 9566 | * Stream.runCollect |
| 9567 | * ) |
| 9568 | * decoded // => [ 'Hello', ' World' ] |
| 9569 | * }) |
| 9570 | * |
| 9571 | * await Effect.runPromise(program) |
| 9572 | * ``` |
| 9573 | * |
| 9574 | * @category text |
| 9575 | * @since 2.0.0 |
| 9576 | */ |
| 9577 | export const decodeText: < |
| 9578 | Arg extends Stream<Uint8Array, any, any> | { |
| 9579 | readonly encoding?: string | undefined |
| 9580 | } | undefined = { |
| 9581 | readonly encoding?: string | undefined |
| 9582 | } |
| 9583 | >( |
| 9584 | streamOrOptions?: Arg, |
| 9585 | options?: { |
| 9586 | readonly encoding?: string | undefined |
| 9587 | } | undefined |
| 9588 | ) => [Arg] extends [Stream<Uint8Array, infer _E, infer _R>] ? Stream<string, _E, _R> |
| 9589 | : <E, R>(self: Stream<Uint8Array, E, R>) => Stream<string, E, R> = dual( |
| 9590 | (args) => isStream(args[0]), |
| 9591 | <E, R>(self: Stream<Uint8Array, E, R>, options?: { |
| 9592 | readonly encoding?: string | undefined |
| 9593 | }): Stream<string, E, R> => |
| 9594 | suspend(() => { |
| 9595 | const decoder = new TextDecoder(options?.encoding) |
| 9596 | return map(self, (chunk) => decoder.decode(chunk, { stream: true })) |
| 9597 | }) |
| 9598 | ) |
| 9599 | |
| 9600 | /** |
| 9601 | * Encodes a stream of strings into UTF-8 `Uint8Array` chunks. |
| 9602 | * |
| 9603 | * **Example** (Encoding a stream of strings into UTF-8 Uint8Array chunks) |
| 9604 | * |
| 9605 | * ```ts import.meta.vitest |
| 9606 | * import { Effect, Stream } from "effect" |
| 9607 | * |
| 9608 | * const stream = Stream.make("Hello", " ", "World") |
| 9609 | * const program = Effect.gen(function*() { |
| 9610 | * const encoded = Stream.encodeText(stream) |
| 9611 | * const chunks = yield* Stream.runCollect(encoded) |
| 9612 | * const bytes = chunks.map((chunk) => [...chunk]) |
| 9613 | * bytes // => [ [ 72, 101, 108, 108, 111 ], [ 32 ], [ 87, 111, 114, 108, 100 ] ] |
| 9614 | * }) |
| 9615 | * |
| 9616 | * await Effect.runPromise(program) |
| 9617 | * ``` |
| 9618 | * |
| 9619 | * @category text |
| 9620 | * @since 2.0.0 |
| 9621 | */ |
| 9622 | export const encodeText = <E, R>(self: Stream<string, E, R>): Stream<Uint8Array, E, R> => |
| 9623 | suspend(() => { |
| 9624 | const encoder = new TextEncoder() |
| 9625 | return map(self, (chunk) => encoder.encode(chunk)) |
| 9626 | }) |
| 9627 | |
| 9628 | /** |
| 9629 | * Splits a stream of strings into lines, handling `\n`, `\r`, and `\r\n` delimiters across chunks. |
| 9630 | * |
| 9631 | * **Example** (Splitting streamed text into lines) |
| 9632 | * |
| 9633 | * ```ts import.meta.vitest |
| 9634 | * import { Effect, Stream } from "effect" |
| 9635 | * |
| 9636 | * await Effect.runPromise(Effect.gen(function* () { |
| 9637 | * const lines = yield* Stream.runCollect( |
| 9638 | * Stream.make("a\nb\r\n", "c\n").pipe(Stream.splitLines) |
| 9639 | * ) |
| 9640 | * lines // => [ 'a', 'b', 'c' ] |
| 9641 | * })) |
| 9642 | * ``` |
| 9643 | * |
| 9644 | * @category text |
| 9645 | * @since 2.0.0 |
| 9646 | */ |
| 9647 | export const splitLines = <E, R>(self: Stream<string, E, R>): Stream<string, E, R> => |
| 9648 | self.channel.pipe( |
| 9649 | Channel.pipeTo(Channel.splitLines()), |
| 9650 | fromChannel |
| 9651 | ) |
| 9652 | |
| 9653 | /** |
| 9654 | * Inserts the provided element between emitted elements. |
| 9655 | * |
| 9656 | * **Example** (Interspersing stream elements) |
| 9657 | * |
| 9658 | * ```ts import.meta.vitest |
| 9659 | * import { Console, Effect, Stream } from "effect" |
| 9660 | * |
| 9661 | * const program = Effect.gen(function*() { |
| 9662 | * const stream = Stream.make(1, 2, 3, 4).pipe(Stream.intersperse(0)) |
| 9663 | * const result = yield* Stream.runCollect(stream) |
| 9664 | * result // => [1, 0, 2, 0, 3, 0, 4] |
| 9665 | * }) |
| 9666 | * |
| 9667 | * await Effect.runPromise(program) |
| 9668 | * ``` |
| 9669 | * |
| 9670 | * @category sequencing |
| 9671 | * @since 2.0.0 |
| 9672 | */ |
| 9673 | export const intersperse: { |
| 9674 | <A2>(element: A2): <A, E, R>(self: Stream<A, E, R>) => Stream<A2 | A, E, R> |
| 9675 | <A, E, R, A2>(self: Stream<A, E, R>, element: A2): Stream<A | A2, E, R> |
| 9676 | } = dual(2, <A, E, R, A2>(self: Stream<A, E, R>, element: A2): Stream<A | A2, E, R> => |
| 9677 | mapArray(self, (arr, i) => { |
| 9678 | const out: Arr.NonEmptyArray<A | A2> = i === 0 ? [] as any : [element] |
| 9679 | const lastIndex = arr.length - 1 |
| 9680 | for (let j = 0; j < arr.length; j++) { |
| 9681 | if (j === lastIndex) { |
| 9682 | out.push(arr[j]) |
| 9683 | } else { |
| 9684 | out.push(arr[j], element) |
| 9685 | } |
| 9686 | } |
| 9687 | return out |
| 9688 | })) |
| 9689 | |
| 9690 | /** |
| 9691 | * Adds a start value, middle value, and end value around stream elements. |
| 9692 | * |
| 9693 | * **Details** |
| 9694 | * |
| 9695 | * The start and end values are always emitted, even when the stream is empty. |
| 9696 | * |
| 9697 | * **Example** (Interspersing stream affixes) |
| 9698 | * |
| 9699 | * ```ts import.meta.vitest |
| 9700 | * import { Console, Effect, Stream } from "effect" |
| 9701 | * |
| 9702 | * const stream = Stream.make("a", "b", "c").pipe( |
| 9703 | * Stream.intersperseAffixes({ start: "[", middle: ",", end: "]" }) |
| 9704 | * ) |
| 9705 | * |
| 9706 | * const program = Effect.gen(function*() { |
| 9707 | * const result = yield* Stream.runCollect(stream) |
| 9708 | * result // => ["[", "a", ",", "b", ",", "c", "]"] |
| 9709 | * }) |
| 9710 | * |
| 9711 | * await Effect.runPromise(program) |
| 9712 | * ``` |
| 9713 | * |
| 9714 | * @category sequencing |
| 9715 | * @since 2.0.0 |
| 9716 | */ |
| 9717 | export const intersperseAffixes: { |
| 9718 | <A2, A3, A4>( |
| 9719 | options: { readonly start: A2; readonly middle: A3; readonly end: A4 } |
| 9720 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<A2 | A3 | A4 | A, E, R> |
| 9721 | <A, E, R, A2, A3, A4>( |
| 9722 | self: Stream<A, E, R>, |
| 9723 | options: { readonly start: A2; readonly middle: A3; readonly end: A4 } |
| 9724 | ): Stream<A | A2 | A3 | A4, E, R> |
| 9725 | } = dual(2, <A, E, R, A2, A3, A4>( |
| 9726 | self: Stream<A, E, R>, |
| 9727 | options: { readonly start: A2; readonly middle: A3; readonly end: A4 } |
| 9728 | ): Stream<A | A2 | A3 | A4, E, R> => |
| 9729 | succeed(options.start).pipe( |
| 9730 | concat(intersperse(self, options.middle)), |
| 9731 | concat(succeed(options.end)) |
| 9732 | )) |
| 9733 | |
| 9734 | /** |
| 9735 | * Interleaves this stream with the specified stream by alternating pulls from |
| 9736 | * each stream; when one ends, the remaining values from the other stream are |
| 9737 | * emitted. |
| 9738 | * |
| 9739 | * **Example** (Interleaving streams) |
| 9740 | * |
| 9741 | * ```ts import.meta.vitest |
| 9742 | * import { Effect, Stream } from "effect" |
| 9743 | * |
| 9744 | * const stream = Stream.interleave( |
| 9745 | * Stream.make(2, 3), |
| 9746 | * Stream.make(5, 6, 7) |
| 9747 | * ) |
| 9748 | * |
| 9749 | * const program = Effect.gen(function*() { |
| 9750 | * const collected = yield* Stream.runCollect(stream) |
| 9751 | * collected // => [ 2, 5, 3, 6, 7 ] |
| 9752 | * }) |
| 9753 | * |
| 9754 | * await Effect.runPromise(program) |
| 9755 | * ``` |
| 9756 | * |
| 9757 | * @category merging |
| 9758 | * @since 2.0.0 |
| 9759 | */ |
| 9760 | export const interleave: { |
| 9761 | <A2, E2, R2>(that: Stream<A2, E2, R2>): <A, E, R>(self: Stream<A, E, R>) => Stream<A2 | A, E2 | E, R2 | R> |
| 9762 | <A, E, R, A2, E2, R2>(self: Stream<A, E, R>, that: Stream<A2, E2, R2>): Stream<A | A2, E | E2, R | R2> |
| 9763 | } = dual( |
| 9764 | 2, |
| 9765 | <A, E, R, A2, E2, R2>(self: Stream<A, E, R>, that: Stream<A2, E2, R2>): Stream<A | A2, E | E2, R | R2> => |
| 9766 | interleaveWith( |
| 9767 | self, |
| 9768 | that, |
| 9769 | fromIterable(Iterable.forever([true, false])) |
| 9770 | ) |
| 9771 | ) |
| 9772 | |
| 9773 | /** |
| 9774 | * Interleaves two streams deterministically by following a boolean decider stream. |
| 9775 | * |
| 9776 | * **Details** |
| 9777 | * |
| 9778 | * The decider controls how many elements are pulled; if one side ends, pulls for |
| 9779 | * that side are ignored. |
| 9780 | * |
| 9781 | * **Example** (Interleaving two streams deterministically by following a boolean decider stream) |
| 9782 | * |
| 9783 | * ```ts import.meta.vitest |
| 9784 | * import { Effect, Stream } from "effect" |
| 9785 | * |
| 9786 | * const program = Effect.gen(function*() { |
| 9787 | * const left = Stream.make(1, 3, 5) |
| 9788 | * const right = Stream.make(2, 4, 6) |
| 9789 | * const decider = Stream.make(true, false, false, true, true) |
| 9790 | * |
| 9791 | * const values = yield* Stream.runCollect( |
| 9792 | * Stream.interleaveWith(left, right, decider) |
| 9793 | * ) |
| 9794 | * |
| 9795 | * values // => [ 1, 2, 4, 3, 5 ] |
| 9796 | * }) |
| 9797 | * |
| 9798 | * await Effect.runPromise(program) |
| 9799 | * ``` |
| 9800 | * |
| 9801 | * @category merging |
| 9802 | * @since 2.0.0 |
| 9803 | */ |
| 9804 | export const interleaveWith: { |
| 9805 | <A2, E2, R2, E3, R3>( |
| 9806 | that: Stream<A2, E2, R2>, |
| 9807 | decider: Stream<boolean, E3, R3> |
| 9808 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<A2 | A, E2 | E3 | E, R2 | R3 | R> |
| 9809 | <A, E, R, A2, E2, R2, E3, R3>( |
| 9810 | self: Stream<A, E, R>, |
| 9811 | that: Stream<A2, E2, R2>, |
| 9812 | decider: Stream<boolean, E3, R3> |
| 9813 | ): Stream<A | A2, E | E2 | E3, R | R2 | R3> |
| 9814 | } = dual(3, <A, E, R, A2, E2, R2, E3, R3>( |
| 9815 | self: Stream<A, E, R>, |
| 9816 | that: Stream<A2, E2, R2>, |
| 9817 | decider: Stream<boolean, E3, R3> |
| 9818 | ): Stream<A | A2, E | E2 | E3, R | R2 | R3> => |
| 9819 | fromChannel(Channel.fromTransform(Effect.fnUntraced(function*(upstream, scope) { |
| 9820 | const pullDecider = yield* Channel.toTransform(Channel.flattenArray(decider.channel))(upstream, scope) |
| 9821 | const retry = Symbol() |
| 9822 | type retry = typeof retry |
| 9823 | let leftDone = false |
| 9824 | let rightDone = false |
| 9825 | const pullLeft = (yield* Channel.toTransform(Channel.flattenArray(self.channel))( |
| 9826 | upstream, |
| 9827 | scope |
| 9828 | )).pipe( |
| 9829 | Pull.catchDone(() => { |
| 9830 | leftDone = true |
| 9831 | return Effect.succeed<retry>(retry) |
| 9832 | }) |
| 9833 | ) |
| 9834 | const pullRight = (yield* Channel.toTransform(Channel.flattenArray(that.channel))( |
| 9835 | upstream, |
| 9836 | scope |
| 9837 | )).pipe( |
| 9838 | Pull.catchDone(() => { |
| 9839 | rightDone = true |
| 9840 | return Effect.succeed<retry>(retry) |
| 9841 | }) |
| 9842 | ) |
| 9843 | |
| 9844 | return Effect.gen(function*() { |
| 9845 | while (true) { |
| 9846 | if (leftDone && rightDone) { |
| 9847 | return yield* Cause.done() |
| 9848 | } |
| 9849 | const side = yield* pullDecider |
| 9850 | if (side && leftDone) continue |
| 9851 | if (!side && rightDone) continue |
| 9852 | const elem = yield* (side ? pullLeft : pullRight) |
| 9853 | if (elem === retry) continue |
| 9854 | return Arr.of(elem) |
| 9855 | } |
| 9856 | }) |
| 9857 | })))) |
| 9858 | |
| 9859 | /** |
| 9860 | * Interrupts the evaluation of this stream when the provided effect |
| 9861 | * completes. The given effect will be forked as part of this stream, and its |
| 9862 | * success will be discarded. This combinator will also interrupt any |
| 9863 | * in-progress element being pulled from upstream. |
| 9864 | * |
| 9865 | * **Details** |
| 9866 | * |
| 9867 | * If the effect completes with a failure before the stream completes, the |
| 9868 | * returned stream will emit that failure. |
| 9869 | * |
| 9870 | * **Example** (Interrupting when an effect completes) |
| 9871 | * |
| 9872 | * ```ts import.meta.vitest |
| 9873 | * import { Deferred, Effect, Stream } from "effect" |
| 9874 | * |
| 9875 | * const program = Effect.gen(function*() { |
| 9876 | * const interrupt = yield* Deferred.make<void>() |
| 9877 | * const stream = Stream.make(1, 2, 3).pipe( |
| 9878 | * Stream.tap((value) => |
| 9879 | * value === 2 |
| 9880 | * ? Deferred.succeed(interrupt, void 0) |
| 9881 | * : Effect.void |
| 9882 | * ), |
| 9883 | * Stream.interruptWhen(Deferred.await(interrupt)) |
| 9884 | * ) |
| 9885 | * |
| 9886 | * const result = yield* Stream.runCollect(stream) |
| 9887 | * result // => [ 1 ] |
| 9888 | * }) |
| 9889 | * |
| 9890 | * await Effect.runPromise(program) |
| 9891 | * ``` |
| 9892 | * |
| 9893 | * @category interruption |
| 9894 | * @since 2.0.0 |
| 9895 | */ |
| 9896 | export const interruptWhen: { |
| 9897 | <X, E2, R2>(effect: Effect.Effect<X, E2, R2>): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E2 | E, R2 | R> |
| 9898 | <A, E, R, X, E2, R2>(self: Stream<A, E, R>, effect: Effect.Effect<X, E2, R2>): Stream<A, E | E2, R | R2> |
| 9899 | } = dual( |
| 9900 | 2, |
| 9901 | <A, E, R, X, E2, R2>(self: Stream<A, E, R>, effect: Effect.Effect<X, E2, R2>): Stream<A, E | E2, R | R2> => |
| 9902 | fromChannel(Channel.interruptWhen(self.channel, effect)) |
| 9903 | ) |
| 9904 | |
| 9905 | /** |
| 9906 | * Stops a stream after the current pull when an effect completes. |
| 9907 | * |
| 9908 | * **When to use** |
| 9909 | * |
| 9910 | * Use to stop before the next pull after an external signal completes. |
| 9911 | * |
| 9912 | * **Details** |
| 9913 | * |
| 9914 | * The effect is forked, its success value is discarded, and its failure fails |
| 9915 | * the stream. |
| 9916 | * |
| 9917 | * **Gotchas** |
| 9918 | * |
| 9919 | * This does not interrupt or truncate an in-progress pull. A pull may emit |
| 9920 | * multiple elements in a single chunk, in which case the entire chunk is |
| 9921 | * emitted. Use {@link interruptWhen} when the stream should be interrupted |
| 9922 | * immediately. |
| 9923 | * |
| 9924 | * **Example** (Halting a stream after an effect completes) |
| 9925 | * |
| 9926 | * ```ts import.meta.vitest |
| 9927 | * import { Deferred, Effect, Stream } from "effect" |
| 9928 | * |
| 9929 | * const program = Effect.gen(function*() { |
| 9930 | * const halt = yield* Deferred.make<void>() |
| 9931 | * const values = yield* Stream.fromArray([1, 2, 3]).pipe( |
| 9932 | * Stream.tap((value) => value === 2 ? Deferred.succeed(halt, void 0) : Effect.void), |
| 9933 | * Stream.haltWhen(Deferred.await(halt)), |
| 9934 | * Stream.runCollect |
| 9935 | * ) |
| 9936 | * values // => [ 1, 2 ] |
| 9937 | * }) |
| 9938 | * |
| 9939 | * await Effect.runPromise(program) |
| 9940 | * ``` |
| 9941 | * |
| 9942 | * @category interruption |
| 9943 | * @since 2.0.0 |
| 9944 | */ |
| 9945 | export const haltWhen: { |
| 9946 | <X, E2, R2>(effect: Effect.Effect<X, E2, R2>): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E2 | E, R2 | R> |
| 9947 | <A, E, R, X, E2, R2>(self: Stream<A, E, R>, effect: Effect.Effect<X, E2, R2>): Stream<A, E | E2, R | R2> |
| 9948 | } = dual( |
| 9949 | 2, |
| 9950 | <A, E, R, X, E2, R2>(self: Stream<A, E, R>, effect: Effect.Effect<X, E2, R2>): Stream<A, E | E2, R | R2> => |
| 9951 | fromChannel(Channel.haltWhen(self.channel, effect)) |
| 9952 | ) |
| 9953 | |
| 9954 | /** |
| 9955 | * Runs the provided finalizer when the stream exits, passing the exit value. |
| 9956 | * |
| 9957 | * **Example** (Running a finalizer on exit) |
| 9958 | * |
| 9959 | * ```ts import.meta.vitest |
| 9960 | * import { Effect, Exit, Stream } from "effect" |
| 9961 | * |
| 9962 | * const exits: Array<string> = [] |
| 9963 | * const stream = Stream.make(1, 2, 3).pipe( |
| 9964 | * Stream.onExit((exit) => |
| 9965 | * Exit.isSuccess(exit) |
| 9966 | * ? Effect.sync(() => exits.push("success")) |
| 9967 | * : Effect.sync(() => exits.push("failure")) |
| 9968 | * ) |
| 9969 | * ) |
| 9970 | * |
| 9971 | * await Effect.runPromise(Effect.gen(function*() { |
| 9972 | * yield* Stream.runCollect(stream) |
| 9973 | * })) |
| 9974 | * exits // => ["success"] |
| 9975 | * ``` |
| 9976 | * |
| 9977 | * @category resource management |
| 9978 | * @since 4.0.0 |
| 9979 | */ |
| 9980 | export const onExit: { |
| 9981 | <E, R2>( |
| 9982 | finalizer: (exit: Exit.Exit<unknown, E>) => Effect.Effect<unknown, never, R2> |
| 9983 | ): <A, R>(self: Stream<A, E, R>) => Stream<A, E, R | R2> |
| 9984 | <A, E, R, R2>( |
| 9985 | self: Stream<A, E, R>, |
| 9986 | finalizer: (exit: Exit.Exit<unknown, E>) => Effect.Effect<unknown, never, R2> |
| 9987 | ): Stream<A, E, R | R2> |
| 9988 | } = dual(2, <A, E, R, R2>( |
| 9989 | self: Stream<A, E, R>, |
| 9990 | finalizer: (exit: Exit.Exit<unknown, E>) => Effect.Effect<unknown, never, R2> |
| 9991 | ): Stream<A, E, R | R2> => fromChannel(Channel.onExit(self.channel, finalizer))) |
| 9992 | |
| 9993 | /** |
| 9994 | * Runs the provided effect when the stream fails, passing the failure cause. |
| 9995 | * |
| 9996 | * **Gotchas** |
| 9997 | * |
| 9998 | * Note: Unlike `Effect.onError` there is no guarantee that the provided |
| 9999 | * effect will not be interrupted. |
| 10000 | * |
| 10001 | * **Example** (Running an effect on errors) |
| 10002 | * |
| 10003 | * ```ts import.meta.vitest |
| 10004 | * import { Cause, Effect, Stream } from "effect" |
| 10005 | * |
| 10006 | * const errors: Array<string> = [] |
| 10007 | * const program = Effect.gen(function*() { |
| 10008 | * const stream = Stream.make(1, 2, 3).pipe( |
| 10009 | * Stream.concat(Stream.fail("boom")), |
| 10010 | * Stream.onError((cause) => Effect.sync(() => errors.push(String(Cause.squash(cause))))) |
| 10011 | * ) |
| 10012 | * |
| 10013 | * yield* Stream.runCollect(stream) |
| 10014 | * }) |
| 10015 | * |
| 10016 | * await Effect.runPromise(Effect.exit(program)) |
| 10017 | * errors // => ["boom"] |
| 10018 | * ``` |
| 10019 | * |
| 10020 | * @category resource management |
| 10021 | * @since 2.0.0 |
| 10022 | */ |
| 10023 | export const onError: { |
| 10024 | <E, X, R2>( |
| 10025 | cleanup: (cause: Cause.Cause<E>) => Effect.Effect<X, never, R2> |
| 10026 | ): <A, R>(self: Stream<A, E, R>) => Stream<A, E, R2 | R> |
| 10027 | <A, E, R, X, R2>( |
| 10028 | self: Stream<A, E, R>, |
| 10029 | cleanup: (cause: Cause.Cause<E>) => Effect.Effect<X, never, R2> |
| 10030 | ): Stream<A, E, R | R2> |
| 10031 | } = dual(2, <A, E, R, X, R2>( |
| 10032 | self: Stream<A, E, R>, |
| 10033 | cleanup: (cause: Cause.Cause<E>) => Effect.Effect<X, never, R2> |
| 10034 | ): Stream<A, E, R | R2> => fromChannel(Channel.onError(self.channel, cleanup))) |
| 10035 | |
| 10036 | /** |
| 10037 | * Runs the provided effect before this stream starts. |
| 10038 | * |
| 10039 | * **Example** (Running an effect on start) |
| 10040 | * |
| 10041 | * ```ts import.meta.vitest |
| 10042 | * import { Effect, Stream } from "effect" |
| 10043 | * |
| 10044 | * const events: Array<string> = [] |
| 10045 | * const program = Effect.gen(function*() { |
| 10046 | * const stream = Stream.fromArray([1, 2, 3]).pipe( |
| 10047 | * Stream.onStart(Effect.sync(() => events.push("started"))) |
| 10048 | * ) |
| 10049 | * |
| 10050 | * const values = yield* Stream.runCollect(stream) |
| 10051 | * values // => [1, 2, 3] |
| 10052 | * }) |
| 10053 | * |
| 10054 | * await Effect.runPromise(program) |
| 10055 | * events // => ["started"] |
| 10056 | * ``` |
| 10057 | * |
| 10058 | * @category sequencing |
| 10059 | * @since 3.6.0 |
| 10060 | */ |
| 10061 | export const onStart: { |
| 10062 | <X, EX, RX>( |
| 10063 | onStart: Effect.Effect<X, EX, RX> |
| 10064 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E | EX, R | RX> |
| 10065 | <A, E, R, X, EX, RX>( |
| 10066 | self: Stream<A, E, R>, |
| 10067 | onStart: Effect.Effect<X, EX, RX> |
| 10068 | ): Stream<A, E | EX, R | RX> |
| 10069 | } = dual(2, <A, E, R, X, EX, RX>( |
| 10070 | self: Stream<A, E, R>, |
| 10071 | onStart: Effect.Effect<X, EX, RX> |
| 10072 | ): Stream<A, E | EX, R | RX> => fromChannel(Channel.onStart(self.channel, onStart))) |
| 10073 | |
| 10074 | /** |
| 10075 | * Runs the provided effect with the first element emitted by the stream. |
| 10076 | * |
| 10077 | * **Example** (Running an effect on the first value) |
| 10078 | * |
| 10079 | * ```ts import.meta.vitest |
| 10080 | * import { Effect, Stream } from "effect" |
| 10081 | * |
| 10082 | * const first: Array<number> = [] |
| 10083 | * await Effect.runPromise(Effect.gen(function* () { |
| 10084 | * yield* Stream.fromArray([1, 2, 3]).pipe( |
| 10085 | * Stream.onFirst((value) => Effect.sync(() => first.push(value))), |
| 10086 | * Stream.runDrain |
| 10087 | * ) |
| 10088 | * })) |
| 10089 | * first // => [1] |
| 10090 | * ``` |
| 10091 | * |
| 10092 | * @category sequencing |
| 10093 | * @since 4.0.0 |
| 10094 | */ |
| 10095 | export const onFirst: { |
| 10096 | <A, X, EX, RX>( |
| 10097 | onFirst: (element: NoInfer<A>) => Effect.Effect<X, EX, RX> |
| 10098 | ): <E, R>(self: Stream<A, E, R>) => Stream<A, E | EX, R | RX> |
| 10099 | <A, E, R, X, EX, RX>( |
| 10100 | self: Stream<A, E, R>, |
| 10101 | onFirst: (element: NoInfer<A>) => Effect.Effect<X, EX, RX> |
| 10102 | ): Stream<A, E | EX, R | RX> |
| 10103 | } = dual(2, <A, E, R, X, EX, RX>( |
| 10104 | self: Stream<A, E, R>, |
| 10105 | onFirst: (element: NoInfer<A>) => Effect.Effect<X, EX, RX> |
| 10106 | ): Stream<A, E | EX, R | RX> => fromChannel(Channel.onFirst(self.channel, (arr) => onFirst(arr[0])))) |
| 10107 | |
| 10108 | /** |
| 10109 | * Runs the provided effect when the stream ends successfully. |
| 10110 | * |
| 10111 | * **Example** (Running an effect on end) |
| 10112 | * |
| 10113 | * ```ts import.meta.vitest |
| 10114 | * import { Effect, Stream } from "effect" |
| 10115 | * |
| 10116 | * const events: Array<string> = [] |
| 10117 | * const program = Effect.gen(function*() { |
| 10118 | * const values = yield* Stream.make(1, 2, 3).pipe( |
| 10119 | * Stream.onEnd(Effect.sync(() => events.push("ended"))), |
| 10120 | * Stream.runCollect |
| 10121 | * ) |
| 10122 | * values // => [1, 2, 3] |
| 10123 | * }) |
| 10124 | * |
| 10125 | * await Effect.runPromise(program) |
| 10126 | * events // => ["ended"] |
| 10127 | * ``` |
| 10128 | * |
| 10129 | * @category sequencing |
| 10130 | * @since 3.6.0 |
| 10131 | */ |
| 10132 | export const onEnd: { |
| 10133 | <X, EX, RX>( |
| 10134 | onEnd: Effect.Effect<X, EX, RX> |
| 10135 | ): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E | EX, R | RX> |
| 10136 | <A, E, R, X, EX, RX>( |
| 10137 | self: Stream<A, E, R>, |
| 10138 | onEnd: Effect.Effect<X, EX, RX> |
| 10139 | ): Stream<A, E | EX, R | RX> |
| 10140 | } = dual(2, <A, E, R, X, EX, RX>( |
| 10141 | self: Stream<A, E, R>, |
| 10142 | onEnd: Effect.Effect<X, EX, RX> |
| 10143 | ): Stream<A, E | EX, R | RX> => fromChannel(Channel.onEnd(self.channel, onEnd))) |
| 10144 | |
| 10145 | /** |
| 10146 | * Executes the provided finalizer after this stream's finalizers run. |
| 10147 | * |
| 10148 | * **Example** (Ensuring finalization) |
| 10149 | * |
| 10150 | * ```ts import.meta.vitest |
| 10151 | * import { Effect, Stream } from "effect" |
| 10152 | * |
| 10153 | * const events: Array<string> = [] |
| 10154 | * const stream = Stream.fromArray([1, 2]).pipe( |
| 10155 | * Stream.ensuring(Effect.sync(() => events.push("cleanup"))) |
| 10156 | * ) |
| 10157 | * |
| 10158 | * const program = Effect.gen(function*() { |
| 10159 | * const collected = yield* Stream.runCollect(stream) |
| 10160 | * collected // => [1, 2] |
| 10161 | * }) |
| 10162 | * |
| 10163 | * await Effect.runPromise(program) |
| 10164 | * events // => ["cleanup"] |
| 10165 | * ``` |
| 10166 | * |
| 10167 | * @category resource management |
| 10168 | * @since 2.0.0 |
| 10169 | */ |
| 10170 | export const ensuring: { |
| 10171 | <R2>(finalizer: Effect.Effect<unknown, never, R2>): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E, R | R2> |
| 10172 | <A, E, R, R2>(self: Stream<A, E, R>, finalizer: Effect.Effect<unknown, never, R2>): Stream<A, E, R | R2> |
| 10173 | } = dual( |
| 10174 | 2, |
| 10175 | <A, E, R, R2>(self: Stream<A, E, R>, finalizer: Effect.Effect<unknown, never, R2>): Stream<A, E, R | R2> => |
| 10176 | fromChannel(Channel.ensuring(self.channel, finalizer)) |
| 10177 | ) |
| 10178 | |
| 10179 | /** |
| 10180 | * Provides a layer or context to the stream, removing the corresponding |
| 10181 | * service requirements. Use `options.local` to build the layer every time; by |
| 10182 | * default, layers are shared between provide calls. |
| 10183 | * |
| 10184 | * **Example** (Providing stream requirements) |
| 10185 | * |
| 10186 | * ```ts import.meta.vitest |
| 10187 | * import { Console, Context, Effect, Layer, Stream } from "effect" |
| 10188 | * |
| 10189 | * class Env extends Context.Service<Env, { readonly name: string }>()("Env") {} |
| 10190 | * |
| 10191 | * const layer = Layer.succeed(Env)({ name: "Ada" }) |
| 10192 | * |
| 10193 | * const stream = Stream.fromEffect( |
| 10194 | * Effect.gen(function*() { |
| 10195 | * const env = yield* Effect.service(Env) |
| 10196 | * return `Hello, ${env.name}` |
| 10197 | * }) |
| 10198 | * ) |
| 10199 | * |
| 10200 | * const withEnv = stream.pipe(Stream.provide(layer)) |
| 10201 | * |
| 10202 | * await Effect.runPromise(Stream.runCollect(withEnv)) // => ["Hello, Ada"] |
| 10203 | * ``` |
| 10204 | * |
| 10205 | * @category providing services |
| 10206 | * @since 4.0.0 |
| 10207 | */ |
| 10208 | export const provide: { |
| 10209 | <AL, EL = never, RL = never>( |
| 10210 | layer: Layer.Layer<AL, EL, RL> | Context.Context<AL>, |
| 10211 | options?: { |
| 10212 | readonly local?: boolean | undefined |
| 10213 | } | undefined |
| 10214 | ): <A, E, R>( |
| 10215 | self: Stream<A, E, R> |
| 10216 | ) => Stream<A, E | EL, Exclude<R, AL> | RL> |
| 10217 | <A, E, R, AL, EL = never, RL = never>( |
| 10218 | self: Stream<A, E, R>, |
| 10219 | layer: Layer.Layer<AL, EL, RL> | Context.Context<AL>, |
| 10220 | options?: { |
| 10221 | readonly local?: boolean | undefined |
| 10222 | } | undefined |
| 10223 | ): Stream<A, E | EL, Exclude<R, AL> | RL> |
| 10224 | } = dual((args) => isStream(args[0]), <A, E, R, AL, EL = never, RL = never>( |
| 10225 | self: Stream<A, E, R>, |
| 10226 | layer: Layer.Layer<AL, EL, RL> | Context.Context<AL>, |
| 10227 | options?: { |
| 10228 | readonly local?: boolean | undefined |
| 10229 | } | undefined |
| 10230 | ): Stream<A, E | EL, Exclude<R, AL> | RL> => fromChannel(Channel.provide(self.channel, layer, options))) |
| 10231 | |
| 10232 | /** |
| 10233 | * Provides multiple services to the stream using a context. |
| 10234 | * |
| 10235 | * **Example** (Providing multiple services to the stream using a context) |
| 10236 | * |
| 10237 | * ```ts import.meta.vitest |
| 10238 | * import { Context, Effect, Stream } from "effect" |
| 10239 | * |
| 10240 | * class Config extends Context.Service<Config, { readonly prefix: string }>()("Config") {} |
| 10241 | * class Greeter extends Context.Service<Greeter, { greet: (name: string) => string }>()("Greeter") {} |
| 10242 | * |
| 10243 | * const context = Context.make(Config, { prefix: "Hello" }).pipe( |
| 10244 | * Context.add(Greeter, { greet: (name: string) => `${name}!` }) |
| 10245 | * ) |
| 10246 | * |
| 10247 | * const stream = Stream.fromEffect( |
| 10248 | * Effect.gen(function*() { |
| 10249 | * const config = yield* Effect.service(Config) |
| 10250 | * const greeter = yield* Effect.service(Greeter) |
| 10251 | * return greeter.greet(config.prefix) |
| 10252 | * }) |
| 10253 | * ) |
| 10254 | * |
| 10255 | * const program = Effect.gen(function*() { |
| 10256 | * const result = yield* Stream.runCollect(Stream.provideContext(stream, context)) |
| 10257 | * result // => [ 'Hello!' ] |
| 10258 | * }) |
| 10259 | * |
| 10260 | * await Effect.runPromise(program) |
| 10261 | * ``` |
| 10262 | * |
| 10263 | * @category providing services |
| 10264 | * @since 2.0.0 |
| 10265 | */ |
| 10266 | export const provideContext: { |
| 10267 | <R2>(context: Context.Context<R2>): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E, Exclude<R, R2>> |
| 10268 | <A, E, R, R2>(self: Stream<A, E, R>, context: Context.Context<R2>): Stream<A, E, Exclude<R, R2>> |
| 10269 | } = dual( |
| 10270 | 2, |
| 10271 | <A, E, R, R2>(self: Stream<A, E, R>, context: Context.Context<R2>): Stream<A, E, Exclude<R, R2>> => |
| 10272 | fromChannel(Channel.provideContext(self.channel, context)) |
| 10273 | ) |
| 10274 | |
| 10275 | /** |
| 10276 | * Provides the stream with a single required service, eliminating that |
| 10277 | * requirement from its environment. |
| 10278 | * |
| 10279 | * **Example** (Providing a stream service) |
| 10280 | * |
| 10281 | * ```ts import.meta.vitest |
| 10282 | * import { Context, Effect, Stream } from "effect" |
| 10283 | * |
| 10284 | * class Greeter extends Context.Service<Greeter, { |
| 10285 | * greet: (name: string) => string |
| 10286 | * }>()("Greeter") {} |
| 10287 | * |
| 10288 | * const stream = Stream.fromEffect( |
| 10289 | * Effect.service(Greeter).pipe( |
| 10290 | * Effect.map((greeter) => greeter.greet("Ada")) |
| 10291 | * ) |
| 10292 | * ) |
| 10293 | * |
| 10294 | * const program = Effect.gen(function*() { |
| 10295 | * const collected = yield* Stream.runCollect( |
| 10296 | * stream.pipe( |
| 10297 | * Stream.provideService(Greeter, { |
| 10298 | * greet: (name) => `Hello, ${name}` |
| 10299 | * }) |
| 10300 | * ) |
| 10301 | * ) |
| 10302 | * collected // => [ 'Hello, Ada' ] |
| 10303 | * }) |
| 10304 | * |
| 10305 | * await Effect.runPromise(program) |
| 10306 | * ``` |
| 10307 | * |
| 10308 | * @category providing services |
| 10309 | * @since 2.0.0 |
| 10310 | */ |
| 10311 | export const provideService: { |
| 10312 | <I, S>( |
| 10313 | key: Context.Key<I, S>, |
| 10314 | service: NoInfer<S> |
| 10315 | ): <A, E, R>( |
| 10316 | self: Stream<A, E, R> |
| 10317 | ) => Stream<A, E, Exclude<R, I>> |
| 10318 | <A, E, R, I, S>( |
| 10319 | self: Stream<A, E, R>, |
| 10320 | key: Context.Key<I, S>, |
| 10321 | service: NoInfer<S> |
| 10322 | ): Stream<A, E, Exclude<R, I>> |
| 10323 | } = dual(3, <A, E, R, I, S>( |
| 10324 | self: Stream<A, E, R>, |
| 10325 | key: Context.Key<I, S>, |
| 10326 | service: NoInfer<S> |
| 10327 | ): Stream<A, E, Exclude<R, I>> => fromChannel(Channel.provideService(self.channel, key, service))) |
| 10328 | |
| 10329 | /** |
| 10330 | * Provides a service to the stream using an effect, removing the requirement and adding the effect's error and environment. |
| 10331 | * |
| 10332 | * **Example** (Providing a stream service effectfully) |
| 10333 | * |
| 10334 | * ```ts import.meta.vitest |
| 10335 | * import { Context, Effect, Stream } from "effect" |
| 10336 | * |
| 10337 | * class ApiConfig extends Context.Service<ApiConfig, { readonly baseUrl: string }>()("ApiConfig") {} |
| 10338 | * |
| 10339 | * const stream = Stream.fromEffect( |
| 10340 | * Effect.gen(function*() { |
| 10341 | * const config = yield* Effect.service(ApiConfig) |
| 10342 | * return config.baseUrl |
| 10343 | * }) |
| 10344 | * ) |
| 10345 | * |
| 10346 | * const events: Array<string> = [] |
| 10347 | * const withConfig = stream.pipe( |
| 10348 | * Stream.provideServiceEffect( |
| 10349 | * ApiConfig, |
| 10350 | * Effect.succeed({ baseUrl: "https://example.com" }).pipe( |
| 10351 | * Effect.tap(() => Effect.sync(() => events.push("loading"))) |
| 10352 | * ) |
| 10353 | * ) |
| 10354 | * ) |
| 10355 | * |
| 10356 | * await Effect.runPromise(Stream.runCollect(withConfig)) // => ["https://example.com"] |
| 10357 | * events // => ["loading"] |
| 10358 | * ``` |
| 10359 | * |
| 10360 | * @category providing services |
| 10361 | * @since 2.0.0 |
| 10362 | */ |
| 10363 | export const provideServiceEffect: { |
| 10364 | <I, S, ES, RS>( |
| 10365 | key: Context.Key<I, S>, |
| 10366 | service: Effect.Effect<NoInfer<S>, ES, RS> |
| 10367 | ): <A, E, R>( |
| 10368 | self: Stream<A, E, R> |
| 10369 | ) => Stream<A, E | ES, Exclude<R, I> | RS> |
| 10370 | <A, E, R, I, S, ES, RS>( |
| 10371 | self: Stream<A, E, R>, |
| 10372 | key: Context.Key<I, S>, |
| 10373 | service: Effect.Effect<NoInfer<S>, ES, RS> |
| 10374 | ): Stream<A, E | ES, Exclude<R, I> | RS> |
| 10375 | } = dual(3, <A, E, R, I, S, ES, RS>( |
| 10376 | self: Stream<A, E, R>, |
| 10377 | key: Context.Key<I, S>, |
| 10378 | service: Effect.Effect<NoInfer<S>, ES, RS> |
| 10379 | ): Stream<A, E | ES, Exclude<R, I> | RS> => fromChannel(Channel.provideServiceEffect(self.channel, key, service))) |
| 10380 | |
| 10381 | /** |
| 10382 | * Transforms the stream's required services by mapping the current context |
| 10383 | * to a new one. |
| 10384 | * |
| 10385 | * **Example** (Updating the stream context) |
| 10386 | * |
| 10387 | * ```ts import.meta.vitest |
| 10388 | * import { Context, Effect, Stream } from "effect" |
| 10389 | * |
| 10390 | * class Logger extends Context.Service<Logger, { prefix: string }>()("Logger") {} |
| 10391 | * class Config extends Context.Service<Config, { name: string }>()("Config") {} |
| 10392 | * |
| 10393 | * const stream = Stream.fromEffect( |
| 10394 | * Effect.gen(function*() { |
| 10395 | * const logger = yield* Effect.service(Logger) |
| 10396 | * const config = yield* Effect.service(Config) |
| 10397 | * return `${logger.prefix}${config.name}` |
| 10398 | * }) |
| 10399 | * ) |
| 10400 | * |
| 10401 | * const updated = stream.pipe( |
| 10402 | * Stream.updateContext((context: Context.Context<Logger>) => |
| 10403 | * Context.add(context, Config, { name: "World" }) |
| 10404 | * ) |
| 10405 | * ) |
| 10406 | * |
| 10407 | * const program = Effect.gen(function*() { |
| 10408 | * const values = yield* Stream.runCollect(updated) |
| 10409 | * values // => [ 'Hello World' ] |
| 10410 | * }) |
| 10411 | * |
| 10412 | * await Effect.runPromise( |
| 10413 | * Effect.provideService(program, Logger, { prefix: "Hello " }) |
| 10414 | * ) |
| 10415 | * ``` |
| 10416 | * |
| 10417 | * @category providing services |
| 10418 | * @since 4.0.0 |
| 10419 | */ |
| 10420 | export const updateContext: { |
| 10421 | <R, R2>( |
| 10422 | f: (context: Context.Context<R2>) => Context.Context<R> |
| 10423 | ): <A, E>( |
| 10424 | self: Stream<A, E, R> |
| 10425 | ) => Stream<A, E, R2> |
| 10426 | <A, E, R, R2>( |
| 10427 | self: Stream<A, E, R>, |
| 10428 | f: (context: Context.Context<R2>) => Context.Context<R> |
| 10429 | ): Stream<A, E, R2> |
| 10430 | } = dual(2, <A, E, R, R2>( |
| 10431 | self: Stream<A, E, R>, |
| 10432 | f: (context: Context.Context<R2>) => Context.Context<R> |
| 10433 | ): Stream<A, E, R2> => fromChannel(Channel.updateContext(self.channel, f))) |
| 10434 | |
| 10435 | /** |
| 10436 | * Updates a single service in the stream environment by applying a function. |
| 10437 | * |
| 10438 | * **Example** (Updating a stream service) |
| 10439 | * |
| 10440 | * ```ts import.meta.vitest |
| 10441 | * import { Context, Effect, Stream } from "effect" |
| 10442 | * |
| 10443 | * class Counter extends Context.Service<Counter, { count: number }>()("Counter") {} |
| 10444 | * |
| 10445 | * const stream = Stream.fromEffect(Effect.service(Counter)).pipe( |
| 10446 | * Stream.updateService(Counter, (counter) => ({ count: counter.count + 1 })) |
| 10447 | * ) |
| 10448 | * |
| 10449 | * const program = Effect.gen(function*() { |
| 10450 | * const counters = yield* Stream.runCollect(stream) |
| 10451 | * const message = `Updated count: ${counters[0].count}` // => "Updated count: 1" |
| 10452 | * }) |
| 10453 | * |
| 10454 | * await Effect.runPromise(Effect.provideService(program, Counter, { count: 0 })) |
| 10455 | * ``` |
| 10456 | * |
| 10457 | * @category providing services |
| 10458 | * @since 2.0.0 |
| 10459 | */ |
| 10460 | export const updateService: { |
| 10461 | <I, S>( |
| 10462 | key: Context.Key<I, S>, |
| 10463 | f: (service: NoInfer<S>) => S |
| 10464 | ): <A, E, R>( |
| 10465 | self: Stream<A, E, R> |
| 10466 | ) => Stream<A, E, R | I> |
| 10467 | <A, E, R, I, S>( |
| 10468 | self: Stream<A, E, R>, |
| 10469 | key: Context.Key<I, S>, |
| 10470 | f: (service: NoInfer<S>) => S |
| 10471 | ): Stream<A, E, R | I> |
| 10472 | } = dual(3, <A, E, R, I, S>( |
| 10473 | self: Stream<A, E, R>, |
| 10474 | service: Context.Key<I, S>, |
| 10475 | f: (service: NoInfer<S>) => S |
| 10476 | ): Stream<A, E, R | I> => |
| 10477 | updateContext(self, (context) => |
| 10478 | Context.add( |
| 10479 | context, |
| 10480 | service, |
| 10481 | f(Context.get(context, service)) |
| 10482 | ))) |
| 10483 | |
| 10484 | /** |
| 10485 | * Wraps the stream with a new span for tracing. |
| 10486 | * |
| 10487 | * **Example** (Wrapping a stream in a span) |
| 10488 | * |
| 10489 | * ```ts import.meta.vitest |
| 10490 | * import { Effect, Stream } from "effect" |
| 10491 | * |
| 10492 | * const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.withSpan("numbers")) |
| 10493 | * |
| 10494 | * await Effect.runPromise( |
| 10495 | * Effect.gen(function*() { |
| 10496 | * const values = yield* Stream.runCollect(stream) |
| 10497 | * values // => [ 1, 2, 3 ] |
| 10498 | * }) |
| 10499 | * ) |
| 10500 | * ``` |
| 10501 | * |
| 10502 | * @category tracing |
| 10503 | * @since 2.0.0 |
| 10504 | */ |
| 10505 | export const withSpan: { |
| 10506 | (name: string, options?: SpanOptions): <A, E, R>(self: Stream<A, E, R>) => Stream<A, E, Exclude<R, ParentSpan>> |
| 10507 | <A, E, R>(self: Stream<A, E, R>, name: string, options?: SpanOptions): Stream<A, E, Exclude<R, ParentSpan>> |
| 10508 | } = function() { |
| 10509 | const dataFirst = isStream(arguments[0]) |
| 10510 | const name = dataFirst ? arguments[1] : arguments[0] |
| 10511 | const options = addSpanStackTrace(dataFirst ? arguments[2] : arguments[1]) |
| 10512 | if (dataFirst) { |
| 10513 | const self = arguments[0] as Stream<any, any, any> |
| 10514 | return fromChannel(Channel.withSpan(self.channel, name, options)) |
| 10515 | } |
| 10516 | return (self: Stream<any, any, any>) => fromChannel(Channel.withSpan(self.channel, name, options)) |
| 10517 | } as any |
| 10518 | |
| 10519 | /** |
| 10520 | * Provides the entry point for do-notation style stream composition. |
| 10521 | * |
| 10522 | * **Example** (Starting stream do notation) |
| 10523 | * |
| 10524 | * ```ts import.meta.vitest |
| 10525 | * import { Effect, pipe, Stream } from "effect" |
| 10526 | * |
| 10527 | * const program = pipe( |
| 10528 | * Stream.Do, |
| 10529 | * Stream.bind("value", () => Stream.fromArray([1, 2])), |
| 10530 | * Stream.let("next", ({ value }) => value + 1) |
| 10531 | * ) |
| 10532 | * |
| 10533 | * const effect = Effect.gen(function*() { |
| 10534 | * const collected = yield* Stream.runCollect(program) |
| 10535 | * collected // => [ { value: 1, next: 2 }, { value: 2, next: 3 } ] |
| 10536 | * }) |
| 10537 | * |
| 10538 | * await Effect.runPromise(effect) |
| 10539 | * ``` |
| 10540 | * |
| 10541 | * @category do notation |
| 10542 | * @since 2.0.0 |
| 10543 | */ |
| 10544 | export const Do: Stream<{}> = succeed({}) |
| 10545 | |
| 10546 | const let_: { |
| 10547 | <N extends string, A extends Record<string, any>, B>( |
| 10548 | name: N, |
| 10549 | f: (a: NoInfer<A>) => B |
| 10550 | ): <E, R>(self: Stream<A, E, R>) => Stream<Simplify<Omit<A, N> & Record<N, B>>, E, R> |
| 10551 | <A extends Record<string, any>, E, R, B, N extends string>( |
| 10552 | self: Stream<A, E, R>, |
| 10553 | name: N, |
| 10554 | f: (a: NoInfer<A>) => B |
| 10555 | ): Stream<Simplify<Omit<A, N> & Record<N, B>>, E, R> |
| 10556 | } = dual(3, <A extends Record<string, any>, E, R, B, N extends string>( |
| 10557 | self: Stream<A, E, R>, |
| 10558 | name: N, |
| 10559 | f: (a: NoInfer<A>) => B |
| 10560 | ): Stream<Simplify<Omit<A, N> & Record<N, B>>, E, R> => map(self, (a) => ({ ...a, [name]: f(a) } as any))) |
| 10561 | export { |
| 10562 | /** |
| 10563 | * Adds a computed field to the current Do-notation record. |
| 10564 | * |
| 10565 | * **Example** (Adding a computed field) |
| 10566 | * |
| 10567 | * ```ts import.meta.vitest |
| 10568 | * import { Effect, Stream } from "effect" |
| 10569 | * |
| 10570 | * const stream = Stream.Do.pipe( |
| 10571 | * Stream.let("x", () => 2), |
| 10572 | * Stream.let("y", ({ x }) => x * 3) |
| 10573 | * ) |
| 10574 | * |
| 10575 | * const program = Effect.gen(function*() { |
| 10576 | * const records = yield* Stream.runCollect(stream) |
| 10577 | * records // => [ { x: 2, y: 6 } ] |
| 10578 | * }) |
| 10579 | * |
| 10580 | * await Effect.runPromise(program) |
| 10581 | * ``` |
| 10582 | * |
| 10583 | * @category do notation |
| 10584 | * @since 2.0.0 |
| 10585 | */ |
| 10586 | let_ as let |
| 10587 | } |
| 10588 | |
| 10589 | /** |
| 10590 | * Binds the result of a stream to a field in the do-notation record. |
| 10591 | * |
| 10592 | * **Example** (Binding a stream value) |
| 10593 | * |
| 10594 | * ```ts import.meta.vitest |
| 10595 | * import { Effect, Stream } from "effect" |
| 10596 | * |
| 10597 | * const program = Stream.Do.pipe( |
| 10598 | * Stream.bind("a", () => Stream.make(1, 2)), |
| 10599 | * Stream.bind("b", ({ a }) => Stream.succeed(a + 1)) |
| 10600 | * ) |
| 10601 | * |
| 10602 | * const result = Stream.runCollect(program) |
| 10603 | * |
| 10604 | * await Effect.runPromise(result) // => [{ a: 1, b: 2 }, { a: 2, b: 3 }] |
| 10605 | * ``` |
| 10606 | * |
| 10607 | * @category do notation |
| 10608 | * @since 2.0.0 |
| 10609 | */ |
| 10610 | export const bind: { |
| 10611 | <N extends string, A extends Record<string, any>, B, E2, R2>( |
| 10612 | name: N, |
| 10613 | f: (a: NoInfer<A>) => Stream<B, E2, R2>, |
| 10614 | options?: { |
| 10615 | readonly concurrency?: Concurrency | undefined |
| 10616 | readonly bufferSize?: number | undefined |
| 10617 | } | undefined |
| 10618 | ): <E, R>(self: Stream<A, E, R>) => Stream<Simplify<Omit<A, N> & Record<N, B>>, E2 | E, R2 | R> |
| 10619 | <A extends Record<string, any>, E, R, B, E2, R2, N extends string>( |
| 10620 | self: Stream<A, E, R>, |
| 10621 | name: N, |
| 10622 | f: (a: NoInfer<A>) => Stream<B, E2, R2>, |
| 10623 | options?: { |
| 10624 | readonly concurrency?: Concurrency | undefined |
| 10625 | readonly bufferSize?: number | undefined |
| 10626 | } | undefined |
| 10627 | ): Stream<Simplify<Omit<A, N> & Record<N, B>>, E | E2, R | R2> |
| 10628 | } = dual((args) => isStream(args[0]), <A extends Record<string, any>, E, R, B, E2, R2, N extends string>( |
| 10629 | self: Stream<A, E, R>, |
| 10630 | name: N, |
| 10631 | f: (a: NoInfer<A>) => Stream<B, E2, R2>, |
| 10632 | options?: { |
| 10633 | readonly concurrency?: Concurrency | undefined |
| 10634 | readonly bufferSize?: number | undefined |
| 10635 | } | undefined |
| 10636 | ): Stream<Simplify<Omit<A, N> & Record<N, B>>, E | E2, R | R2> => |
| 10637 | flatMap(self, (a) => map(f(a), (b) => ({ ...a, [name]: b } as any)), options)) |
| 10638 | |
| 10639 | /** |
| 10640 | * Binds an Effect-produced value into the do-notation record for each stream element. |
| 10641 | * |
| 10642 | * **Example** (Binding an effect value) |
| 10643 | * |
| 10644 | * ```ts import.meta.vitest |
| 10645 | * import { Effect, Stream } from "effect" |
| 10646 | * |
| 10647 | * const stream = Stream.Do.pipe( |
| 10648 | * Stream.bind("value", () => Stream.make(1, 2)), |
| 10649 | * Stream.bindEffect("double", ({ value }) => Effect.succeed(value * 2)) |
| 10650 | * ) |
| 10651 | * |
| 10652 | * const program = Effect.gen(function*() { |
| 10653 | * const result = yield* Stream.runCollect(stream) |
| 10654 | * result // => [ { value: 1, double: 2 }, { value: 2, double: 4 } ] |
| 10655 | * }) |
| 10656 | * |
| 10657 | * await Effect.runPromise(program) |
| 10658 | * ``` |
| 10659 | * |
| 10660 | * @category do notation |
| 10661 | * @since 2.0.0 |
| 10662 | */ |
| 10663 | export const bindEffect: { |
| 10664 | <N extends string, A extends Record<string, any>, B, E2, R2>( |
| 10665 | name: N, |
| 10666 | f: (a: NoInfer<A>) => Effect.Effect<B, E2, R2>, |
| 10667 | options?: { |
| 10668 | readonly concurrency?: Concurrency | undefined |
| 10669 | readonly bufferSize?: number | undefined |
| 10670 | readonly unordered?: boolean | undefined |
| 10671 | } | undefined |
| 10672 | ): <E, R>(self: Stream<A, E, R>) => Stream<Simplify<Omit<A, N> & Record<N, B>>, E | E2, R | R2> |
| 10673 | <A extends Record<string, any>, E, R, B, E2, R2, N extends string>( |
| 10674 | self: Stream<A, E, R>, |
| 10675 | name: N, |
| 10676 | f: (a: NoInfer<A>) => Effect.Effect<B, E2, R2>, |
| 10677 | options?: { |
| 10678 | readonly concurrency?: Concurrency | undefined |
| 10679 | readonly bufferSize?: number | undefined |
| 10680 | readonly unordered?: boolean | undefined |
| 10681 | } | undefined |
| 10682 | ): Stream<Simplify<Omit<A, N> & Record<N, B>>, E | E2, R | R2> |
| 10683 | } = dual((args) => isStream(args[0]), <A extends Record<string, any>, E, R, B, E2, R2, N extends string>( |
| 10684 | self: Stream<A, E, R>, |
| 10685 | name: N, |
| 10686 | f: (a: NoInfer<A>) => Effect.Effect<B, E2, R2>, |
| 10687 | options?: { |
| 10688 | readonly concurrency?: Concurrency | undefined |
| 10689 | readonly bufferSize?: number | undefined |
| 10690 | readonly unordered?: boolean | undefined |
| 10691 | } | undefined |
| 10692 | ): Stream<Simplify<Omit<A, N> & Record<N, B>>, E | E2, R | R2> => |
| 10693 | mapEffect(self, (a) => Effect.map(f(a), (b) => ({ ...a, [name]: b } as any)), options)) |
| 10694 | |
| 10695 | /** |
| 10696 | * Maps each element into a record keyed by the provided name. |
| 10697 | * |
| 10698 | * **Example** (Binding values to a record key) |
| 10699 | * |
| 10700 | * ```ts import.meta.vitest |
| 10701 | * import { Effect, Stream } from "effect" |
| 10702 | * |
| 10703 | * const stream = Stream.make(1, 2, 3).pipe(Stream.bindTo("value")) |
| 10704 | * |
| 10705 | * await Effect.runPromise(Stream.runCollect(stream)) // => [{ value: 1 }, { value: 2 }, { value: 3 }] |
| 10706 | * ``` |
| 10707 | * |
| 10708 | * @category do notation |
| 10709 | * @since 2.0.0 |
| 10710 | */ |
| 10711 | export const bindTo: { |
| 10712 | <N extends string>(name: N): <A, E, R>(self: Stream<A, E, R>) => Stream<{ [K in N]: A }, E, R> |
| 10713 | <A, E, R, N extends string>(self: Stream<A, E, R>, name: N): Stream<{ [K in N]: A }, E, R> |
| 10714 | } = dual(2, <A, E, R, N extends string>( |
| 10715 | self: Stream<A, E, R>, |
| 10716 | name: N |
| 10717 | ): Stream<{ [K in N]: A }, E, R> => map(self, (a) => ({ [name]: a } as any))) |
| 10718 | |
| 10719 | /** |
| 10720 | * Runs a stream with a sink and returns the sink result. |
| 10721 | * |
| 10722 | * **Example** (Running a stream with a sink) |
| 10723 | * |
| 10724 | * ```ts import.meta.vitest |
| 10725 | * import { Effect, Sink, Stream } from "effect" |
| 10726 | * |
| 10727 | * const program = Stream.run(Stream.make(1, 2, 3), Sink.sum) |
| 10728 | * |
| 10729 | * await Effect.runPromise(program) // => 6 |
| 10730 | * ``` |
| 10731 | * |
| 10732 | * @category destructors |
| 10733 | * @since 2.0.0 |
| 10734 | */ |
| 10735 | export const run: { |
| 10736 | <A2, A, L, E2, R2>( |
| 10737 | sink: Sink.Sink<A2, A, L, E2, R2> |
| 10738 | ): <E, R>(self: Stream<A, E, R>) => Effect.Effect<A2, E2 | E, R | R2> |
| 10739 | <A, E, R, L, A2, E2, R2>( |
| 10740 | self: Stream<A, E, R>, |
| 10741 | sink: Sink.Sink<A2, A, L, E2, R2> |
| 10742 | ): Effect.Effect<A2, E | E2, R | R2> |
| 10743 | } = dual(2, <A, E, R, L, A2, E2, R2>( |
| 10744 | self: Stream<A, E, R>, |
| 10745 | sink: Sink.Sink<A2, A, L, E2, R2> |
| 10746 | ): Effect.Effect<A2, E | E2, R | R2> => |
| 10747 | Effect.scopedWith((scope) => |
| 10748 | Channel.toPullScoped(self.channel, scope).pipe( |
| 10749 | Effect.flatMap((upstream) => sink.transform(upstream as any, scope)), |
| 10750 | Effect.map(([a]) => a) |
| 10751 | ) |
| 10752 | )) |
| 10753 | |
| 10754 | /** |
| 10755 | * Runs the stream and collects all elements into an array. |
| 10756 | * |
| 10757 | * **Example** (Collecting stream values) |
| 10758 | * |
| 10759 | * ```ts import.meta.vitest |
| 10760 | * import { Effect, Stream } from "effect" |
| 10761 | * |
| 10762 | * const stream = Stream.make(1, 2, 3, 4, 5) |
| 10763 | * |
| 10764 | * const program = Effect.gen(function*() { |
| 10765 | * const collected = yield* Stream.runCollect(stream) |
| 10766 | * collected // => [ 1, 2, 3, 4, 5 ] |
| 10767 | * }) |
| 10768 | * |
| 10769 | * await Effect.runPromise(program) |
| 10770 | * ``` |
| 10771 | * |
| 10772 | * @category destructors |
| 10773 | * @since 2.0.0 |
| 10774 | */ |
| 10775 | export const runCollect = <A, E, R>(self: Stream<A, E, R>): Effect.Effect<Array<A>, E, R> => |
| 10776 | Channel.runFold( |
| 10777 | self.channel, |
| 10778 | () => [] as Array<A>, |
| 10779 | (acc, chunk) => { |
| 10780 | for (let i = 0; i < chunk.length; i++) { |
| 10781 | acc.push(chunk[i]) |
| 10782 | } |
| 10783 | return acc |
| 10784 | } |
| 10785 | ) |
| 10786 | |
| 10787 | /** |
| 10788 | * Runs the stream and returns the number of elements emitted. |
| 10789 | * |
| 10790 | * **Example** (Counting stream values) |
| 10791 | * |
| 10792 | * ```ts import.meta.vitest |
| 10793 | * import { Effect, Stream } from "effect" |
| 10794 | * |
| 10795 | * const stream = Stream.make(1, 2, 3, 4, 5) |
| 10796 | * |
| 10797 | * const program = Effect.gen(function* () { |
| 10798 | * const count = yield* Stream.runCount(stream) |
| 10799 | * count // => 5 |
| 10800 | * }) |
| 10801 | * |
| 10802 | * await Effect.runPromise(program) |
| 10803 | * ``` |
| 10804 | * |
| 10805 | * @category destructors |
| 10806 | * @since 2.0.0 |
| 10807 | */ |
| 10808 | export const runCount = <A, E, R>(self: Stream<A, E, R>): Effect.Effect<number, E, R> => |
| 10809 | Channel.runFold(self.channel, () => 0, (acc, chunk) => acc + chunk.length) |
| 10810 | |
| 10811 | /** |
| 10812 | * Runs the stream and returns the numeric sum of its elements. |
| 10813 | * |
| 10814 | * **Example** (Summing stream values) |
| 10815 | * |
| 10816 | * ```ts import.meta.vitest |
| 10817 | * import { Effect, Stream } from "effect" |
| 10818 | * |
| 10819 | * const program = Effect.gen(function*() { |
| 10820 | * const total = yield* Stream.runSum(Stream.make(1, 2, 3)) |
| 10821 | * total // => 6 |
| 10822 | * }) |
| 10823 | * |
| 10824 | * await Effect.runPromise(program) |
| 10825 | * ``` |
| 10826 | * |
| 10827 | * @category destructors |
| 10828 | * @since 2.0.0 |
| 10829 | */ |
| 10830 | export const runSum = <E, R>(self: Stream<number, E, R>): Effect.Effect<number, E, R> => |
| 10831 | Channel.runFold(self.channel, () => 0, (acc, chunk) => { |
| 10832 | for (let i = 0; i < chunk.length; i++) { |
| 10833 | acc += chunk[i] |
| 10834 | } |
| 10835 | return acc |
| 10836 | }) |
| 10837 | |
| 10838 | /** |
| 10839 | * Runs the stream and folds elements using a pure reducer. |
| 10840 | * |
| 10841 | * **Example** (Folding stream values) |
| 10842 | * |
| 10843 | * ```ts import.meta.vitest |
| 10844 | * import { Effect, Stream } from "effect" |
| 10845 | * |
| 10846 | * const program = Effect.gen(function*() { |
| 10847 | * const total = yield* Stream.runFold( |
| 10848 | * Stream.make(1, 2, 3), |
| 10849 | * () => 0, |
| 10850 | * (acc, n) => acc + n |
| 10851 | * ) |
| 10852 | * total // => 6 |
| 10853 | * }) |
| 10854 | * |
| 10855 | * await Effect.runPromise(program) |
| 10856 | * ``` |
| 10857 | * |
| 10858 | * @category destructors |
| 10859 | * @since 2.0.0 |
| 10860 | */ |
| 10861 | export const runFold: { |
| 10862 | <Z, A>( |
| 10863 | initial: LazyArg<Z>, |
| 10864 | f: (acc: Z, a: A) => Z |
| 10865 | ): <E, R>( |
| 10866 | self: Stream<A, E, R> |
| 10867 | ) => Effect.Effect<Z, E, R> |
| 10868 | <A, E, R, Z>( |
| 10869 | self: Stream<A, E, R>, |
| 10870 | initial: LazyArg<Z>, |
| 10871 | f: (acc: Z, a: A) => Z |
| 10872 | ): Effect.Effect<Z, E, R> |
| 10873 | } = dual(3, <A, E, R, Z>( |
| 10874 | self: Stream<A, E, R>, |
| 10875 | initial: LazyArg<Z>, |
| 10876 | f: (acc: Z, a: A) => Z |
| 10877 | ): Effect.Effect<Z, E, R> => |
| 10878 | Channel.runFold(self.channel, initial, (acc, arr) => { |
| 10879 | for (let i = 0; i < arr.length; i++) { |
| 10880 | acc = f(acc, arr[i]) |
| 10881 | } |
| 10882 | return acc |
| 10883 | })) |
| 10884 | |
| 10885 | /** |
| 10886 | * Runs the stream and folds elements using an effectful reducer. |
| 10887 | * |
| 10888 | * **When to use** |
| 10889 | * |
| 10890 | * Use when reducing stream elements needs Effects, services, or failures in the |
| 10891 | * reducer. |
| 10892 | * |
| 10893 | * **Example** (Effectfully folding stream values) |
| 10894 | * |
| 10895 | * ```ts import.meta.vitest |
| 10896 | * import { Effect, Stream } from "effect" |
| 10897 | * |
| 10898 | * const program = Effect.gen(function*() { |
| 10899 | * const total = yield* Stream.runFoldEffect( |
| 10900 | * Stream.make(1, 2, 3), |
| 10901 | * () => 0, |
| 10902 | * (acc, n) => Effect.succeed(acc + n) |
| 10903 | * ) |
| 10904 | * total // => 6 |
| 10905 | * }) |
| 10906 | * |
| 10907 | * await Effect.runPromise(program) |
| 10908 | * ``` |
| 10909 | * |
| 10910 | * @category destructors |
| 10911 | * @since 2.0.0 |
| 10912 | */ |
| 10913 | export const runFoldEffect: { |
| 10914 | <Z, A, EX, RX>( |
| 10915 | initial: LazyArg<Z>, |
| 10916 | f: (acc: Z, a: A) => Effect.Effect<Z, EX, RX> |
| 10917 | ): <E, R>( |
| 10918 | self: Stream<A, E, R> |
| 10919 | ) => Effect.Effect<Z, E | EX, R | RX> |
| 10920 | <A, E, R, Z, EX, RX>( |
| 10921 | self: Stream<A, E, R>, |
| 10922 | initial: LazyArg<Z>, |
| 10923 | f: (acc: Z, a: A) => Effect.Effect<Z, EX, RX> |
| 10924 | ): Effect.Effect<Z, E | EX, R | RX> |
| 10925 | } = dual(3, <A, E, R, Z, EX, RX>( |
| 10926 | self: Stream<A, E, R>, |
| 10927 | initial: LazyArg<Z>, |
| 10928 | f: (acc: Z, a: A) => Effect.Effect<Z, EX, RX> |
| 10929 | ): Effect.Effect<Z, E | EX, R | RX> => |
| 10930 | Channel.runFoldEffect(self.channel, initial, (acc, arr) => { |
| 10931 | let i = 0 |
| 10932 | let s = acc |
| 10933 | return Effect.map( |
| 10934 | Effect.whileLoop({ |
| 10935 | while: () => i < arr.length, |
| 10936 | body: () => f(s, arr[i]), |
| 10937 | step(z) { |
| 10938 | s = z |
| 10939 | i++ |
| 10940 | } |
| 10941 | }), |
| 10942 | () => s |
| 10943 | ) |
| 10944 | })) |
| 10945 | |
| 10946 | /** |
| 10947 | * Runs the stream and returns the first element as an `Option`. |
| 10948 | * |
| 10949 | * **Example** (Getting the first stream value) |
| 10950 | * |
| 10951 | * ```ts import.meta.vitest |
| 10952 | * import { Effect, Option, Stream } from "effect" |
| 10953 | * |
| 10954 | * const program = Effect.gen(function*() { |
| 10955 | * const head = yield* Stream.runHead(Stream.make(1, 2, 3)) |
| 10956 | * Option.getOrThrow(head) // => 1 |
| 10957 | * }) |
| 10958 | * |
| 10959 | * await Effect.runPromise(program) |
| 10960 | * ``` |
| 10961 | * |
| 10962 | * @category destructors |
| 10963 | * @since 2.0.0 |
| 10964 | */ |
| 10965 | export const runHead = <A, E, R>(self: Stream<A, E, R>): Effect.Effect<Option.Option<A>, E, R> => |
| 10966 | Effect.map(Channel.runHead(self.channel), Option.map(Arr.getUnsafe(0))) |
| 10967 | |
| 10968 | /** |
| 10969 | * Runs the stream and returns the last element as an `Option`. |
| 10970 | * |
| 10971 | * **When to use** |
| 10972 | * |
| 10973 | * Use to consume a finite stream when only the final emitted element matters. |
| 10974 | * |
| 10975 | * **Details** |
| 10976 | * |
| 10977 | * `Option.some` contains the last emitted element. `Option.none` means the |
| 10978 | * stream completed without emitting. |
| 10979 | * |
| 10980 | * **Gotchas** |
| 10981 | * |
| 10982 | * The returned effect waits for the stream to complete before it can produce a |
| 10983 | * value. |
| 10984 | * |
| 10985 | * @see {@link runHead} for consuming only the first emitted element |
| 10986 | * @see {@link runCollect} for collecting every emitted element |
| 10987 | * @see {@link runDrain} for consuming the stream while discarding emitted elements |
| 10988 | * |
| 10989 | * @category destructors |
| 10990 | * @since 2.0.0 |
| 10991 | */ |
| 10992 | export const runLast = <A, E, R>(self: Stream<A, E, R>): Effect.Effect<Option.Option<A>, E, R> => |
| 10993 | Effect.map(Channel.runLast(self.channel), Option.map(Arr.lastNonEmpty)) |
| 10994 | |
| 10995 | /** |
| 10996 | * Runs the provided effectful callback for each element of the stream. |
| 10997 | * |
| 10998 | * **Example** (Running an effect for each value) |
| 10999 | * |
| 11000 | * ```ts import.meta.vitest |
| 11001 | * import { Effect, Stream } from "effect" |
| 11002 | * |
| 11003 | * const stream = Stream.make(1, 2, 3) |
| 11004 | * const values: Array<string> = [] |
| 11005 | * |
| 11006 | * const program = Effect.gen(function*() { |
| 11007 | * yield* Stream.runForEach(stream, (n) => Effect.sync(() => values.push(`Processing: ${n}`))) |
| 11008 | * }) |
| 11009 | * |
| 11010 | * await Effect.runPromise(program) |
| 11011 | * values // => ["Processing: 1", "Processing: 2", "Processing: 3"] |
| 11012 | * ``` |
| 11013 | * |
| 11014 | * @category destructors |
| 11015 | * @since 2.0.0 |
| 11016 | */ |
| 11017 | export const runForEach: { |
| 11018 | <A, X, E2, R2>( |
| 11019 | f: (a: A) => Effect.Effect<X, E2, R2> |
| 11020 | ): <E, R>(self: Stream<A, E, R>) => Effect.Effect<void, E2 | E, R2 | R> |
| 11021 | <A, E, R, X, E2, R2>( |
| 11022 | self: Stream<A, E, R>, |
| 11023 | f: (a: A) => Effect.Effect<X, E2, R2> |
| 11024 | ): Effect.Effect<void, E | E2, R | R2> |
| 11025 | } = dual(2, <A, E, R, X, E2, R2>( |
| 11026 | self: Stream<A, E, R>, |
| 11027 | f: (a: A) => Effect.Effect<X, E2, R2> |
| 11028 | ): Effect.Effect<void, E | E2, R | R2> => |
| 11029 | Channel.runForEach(self.channel, (arr) => { |
| 11030 | let i = 0 |
| 11031 | return Effect.whileLoop({ |
| 11032 | while: () => i < arr.length, |
| 11033 | body: () => f(arr[i++]), |
| 11034 | step: constVoid |
| 11035 | }) |
| 11036 | })) |
| 11037 | |
| 11038 | /** |
| 11039 | * Runs the stream, applying the effectful predicate to each element and |
| 11040 | * stopping when it returns `false`. |
| 11041 | * |
| 11042 | * **Example** (Running effects while a predicate holds) |
| 11043 | * |
| 11044 | * ```ts import.meta.vitest |
| 11045 | * import { Effect, Stream } from "effect" |
| 11046 | * |
| 11047 | * const values: Array<number> = [] |
| 11048 | * const program = Effect.gen(function*() { |
| 11049 | * const stream = Stream.make(1, 2, 3, 4, 5) |
| 11050 | * |
| 11051 | * yield* Stream.runForEachWhile(stream, (n) => |
| 11052 | * Effect.gen(function*() { |
| 11053 | * yield* Effect.sync(() => values.push(n)) |
| 11054 | * return n < 3 |
| 11055 | * }) |
| 11056 | * ) |
| 11057 | * }) |
| 11058 | * |
| 11059 | * await Effect.runPromise(program) |
| 11060 | * values // => [1, 2, 3] |
| 11061 | * ``` |
| 11062 | * |
| 11063 | * @category destructors |
| 11064 | * @since 2.0.0 |
| 11065 | */ |
| 11066 | export const runForEachWhile: { |
| 11067 | <A, E2, R2>( |
| 11068 | f: (a: A) => Effect.Effect<boolean, E2, R2> |
| 11069 | ): <E, R>(self: Stream<A, E, R>) => Effect.Effect<void, E2 | E, R2 | R> |
| 11070 | <A, E, R, E2, R2>( |
| 11071 | self: Stream<A, E, R>, |
| 11072 | f: (a: A) => Effect.Effect<boolean, E2, R2> |
| 11073 | ): Effect.Effect<void, E | E2, R | R2> |
| 11074 | } = dual(2, <A, E, R, E2, R2>( |
| 11075 | self: Stream<A, E, R>, |
| 11076 | f: (a: A) => Effect.Effect<boolean, E2, R2> |
| 11077 | ): Effect.Effect<void, E | E2, R | R2> => |
| 11078 | Channel.runForEachWhile(self.channel, (arr) => { |
| 11079 | let done = false |
| 11080 | let i = 0 |
| 11081 | return Effect.map( |
| 11082 | Effect.whileLoop({ |
| 11083 | while: () => !done && i < arr.length, |
| 11084 | body: () => f(arr[i]), |
| 11085 | step(b) { |
| 11086 | i++ |
| 11087 | if (!b) done = true |
| 11088 | } |
| 11089 | }), |
| 11090 | () => !done |
| 11091 | ) |
| 11092 | })) |
| 11093 | |
| 11094 | /** |
| 11095 | * Consumes the stream in chunks, passing each non-empty array to the callback. |
| 11096 | * |
| 11097 | * **Example** (Consuming stream chunks) |
| 11098 | * |
| 11099 | * ```ts import.meta.vitest |
| 11100 | * import { Effect, Stream } from "effect" |
| 11101 | * |
| 11102 | * const stream = Stream.make(1, 2, 3, 4, 5) |
| 11103 | * const chunks: Array<string> = [] |
| 11104 | * const program = Effect.gen(function*() { |
| 11105 | * yield* Stream.runForEachArray( |
| 11106 | * stream, |
| 11107 | * (chunk) => Effect.sync(() => chunks.push(chunk.join(", "))) |
| 11108 | * ) |
| 11109 | * }) |
| 11110 | * |
| 11111 | * await Effect.runPromise(program) |
| 11112 | * chunks // => ["1, 2, 3, 4, 5"] |
| 11113 | * ``` |
| 11114 | * |
| 11115 | * @category destructors |
| 11116 | * @since 4.0.0 |
| 11117 | */ |
| 11118 | export const runForEachArray: { |
| 11119 | <A, X, E2, R2>( |
| 11120 | f: (a: Arr.NonEmptyReadonlyArray<A>) => Effect.Effect<X, E2, R2> |
| 11121 | ): <E, R>(self: Stream<A, E, R>) => Effect.Effect<void, E2 | E, R2 | R> |
| 11122 | <A, E, R, X, E2, R2>( |
| 11123 | self: Stream<A, E, R>, |
| 11124 | f: (a: Arr.NonEmptyReadonlyArray<A>) => Effect.Effect<X, E2, R2> |
| 11125 | ): Effect.Effect<void, E | E2, R | R2> |
| 11126 | } = dual(2, <A, E, R, X, E2, R2>( |
| 11127 | self: Stream<A, E, R>, |
| 11128 | f: (a: Arr.NonEmptyReadonlyArray<A>) => Effect.Effect<X, E2, R2> |
| 11129 | ): Effect.Effect<void, E | E2, R | R2> => Channel.runForEach(self.channel, f)) |
| 11130 | |
| 11131 | /** |
| 11132 | * Runs the stream for its effects, discarding emitted elements. |
| 11133 | * |
| 11134 | * **Example** (Draining a stream run) |
| 11135 | * |
| 11136 | * ```ts import.meta.vitest |
| 11137 | * import { Effect, Stream } from "effect" |
| 11138 | * |
| 11139 | * const values: Array<number> = [] |
| 11140 | * const program = Effect.gen(function*() { |
| 11141 | * const stream = Stream.make(1, 2, 3).pipe( |
| 11142 | * Stream.mapEffect((n) => Effect.sync(() => values.push(n))) |
| 11143 | * ) |
| 11144 | * |
| 11145 | * yield* Stream.runDrain(stream) |
| 11146 | * }) |
| 11147 | * |
| 11148 | * await Effect.runPromise(program) |
| 11149 | * values // => [1, 2, 3] |
| 11150 | * ``` |
| 11151 | * |
| 11152 | * @category destructors |
| 11153 | * @since 2.0.0 |
| 11154 | */ |
| 11155 | export const runDrain = <A, E, R>(self: Stream<A, E, R>): Effect.Effect<void, E, R> => Channel.runDrain(self.channel) |
| 11156 | |
| 11157 | /** |
| 11158 | * Returns a scoped pull for manually consuming the stream's output chunks. |
| 11159 | * |
| 11160 | * **Details** |
| 11161 | * |
| 11162 | * The pull fails with `Cause.Done` when the stream ends and with the stream |
| 11163 | * error on failure. |
| 11164 | * |
| 11165 | * **Example** (Creating a scoped pull) |
| 11166 | * |
| 11167 | * ```ts import.meta.vitest |
| 11168 | * import { Effect, Stream } from "effect" |
| 11169 | * |
| 11170 | * const stream = Stream.make(1, 2, 3) |
| 11171 | * |
| 11172 | * const program = Effect.scoped( |
| 11173 | * Effect.gen(function*() { |
| 11174 | * const pull = yield* Stream.toPull(stream) |
| 11175 | * const chunk = yield* pull |
| 11176 | * chunk // => [ 1, 2, 3 ] |
| 11177 | * }) |
| 11178 | * ) |
| 11179 | * |
| 11180 | * await Effect.runPromise(program) |
| 11181 | * ``` |
| 11182 | * |
| 11183 | * @category destructors |
| 11184 | * @since 2.0.0 |
| 11185 | */ |
| 11186 | export const toPull = <A, E, R>( |
| 11187 | self: Stream<A, E, R> |
| 11188 | ): Effect.Effect<Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E>, never, R | Scope.Scope> => Channel.toPull(self.channel) |
| 11189 | |
| 11190 | /** |
| 11191 | * Concatenates all emitted strings into a single string. |
| 11192 | * |
| 11193 | * **Example** (Joining strings from a stream) |
| 11194 | * |
| 11195 | * ```ts import.meta.vitest |
| 11196 | * import { Effect, Stream } from "effect" |
| 11197 | * |
| 11198 | * const stream = Stream.make("Hello", " ", "World", "!") |
| 11199 | * const program = Effect.gen(function*() { |
| 11200 | * const text = yield* Stream.mkString(stream) |
| 11201 | * text // => "Hello World!" |
| 11202 | * }) |
| 11203 | * |
| 11204 | * await Effect.runPromise(program) |
| 11205 | * ``` |
| 11206 | * |
| 11207 | * @category destructors |
| 11208 | * @since 2.0.0 |
| 11209 | */ |
| 11210 | export const mkString = <E, R>(self: Stream<string, E, R>): Effect.Effect<string, E, R> => |
| 11211 | Channel.runFold( |
| 11212 | self.channel, |
| 11213 | () => "", |
| 11214 | (acc, chunk) => acc + chunk.join("") |
| 11215 | ) |
| 11216 | |
| 11217 | /** |
| 11218 | * Concatenates the stream's `Uint8Array` chunks into a single `ArrayBuffer`. |
| 11219 | * |
| 11220 | * **Gotchas** |
| 11221 | * |
| 11222 | * This materializes the full content in memory. The source stream must not |
| 11223 | * reuse or mutate emitted buffers, which are retained until collection completes. |
| 11224 | * |
| 11225 | * **Example** (Joining byte chunks into an ArrayBuffer) |
| 11226 | * |
| 11227 | * ```ts import.meta.vitest |
| 11228 | * import { Effect, Stream } from "effect" |
| 11229 | * |
| 11230 | * const program = Stream.make( |
| 11231 | * new Uint8Array([1, 2]), |
| 11232 | * new Uint8Array([3, 4]) |
| 11233 | * ).pipe( |
| 11234 | * Stream.mkArrayBuffer, |
| 11235 | * Effect.map((buffer) => [...new Uint8Array(buffer)]) |
| 11236 | * ) |
| 11237 | * |
| 11238 | * await Effect.runPromise(program) // => [1, 2, 3, 4] |
| 11239 | * ``` |
| 11240 | * |
| 11241 | * @category destructors |
| 11242 | * @since 4.0.0 |
| 11243 | */ |
| 11244 | export const mkArrayBuffer = <E, R>(self: Stream<Uint8Array, E, R>): Effect.Effect<ArrayBuffer, E, R> => |
| 11245 | Effect.map(Channel.mkUint8Array(self.channel), (bytes) => bytes.buffer) |
| 11246 | |
| 11247 | /** |
| 11248 | * Concatenates the stream's `Uint8Array` chunks into a single `Uint8Array`. |
| 11249 | * |
| 11250 | * **Gotchas** |
| 11251 | * |
| 11252 | * This materializes the full content in memory. The source stream must not |
| 11253 | * reuse or mutate emitted buffers, which are retained until collection completes. |
| 11254 | * |
| 11255 | * **Example** (Joining Uint8Array chunks) |
| 11256 | * |
| 11257 | * ```ts import.meta.vitest |
| 11258 | * import { Effect, Stream } from "effect" |
| 11259 | * |
| 11260 | * const stream = Stream.make(new Uint8Array([1, 2]), new Uint8Array([3, 4])) |
| 11261 | * const program = Effect.gen(function*() { |
| 11262 | * const bytes = yield* Stream.mkUint8Array(stream) |
| 11263 | * const values = Array.from(bytes) // => [1, 2, 3, 4] |
| 11264 | * }) |
| 11265 | * |
| 11266 | * await Effect.runPromise(program) |
| 11267 | * ``` |
| 11268 | * |
| 11269 | * @category destructors |
| 11270 | * @since 4.0.0 |
| 11271 | */ |
| 11272 | export const mkUint8Array = <E, R>(self: Stream<Uint8Array, E, R>): Effect.Effect<Uint8Array, E, R> => |
| 11273 | Channel.mkUint8Array(self.channel) |
| 11274 | |
| 11275 | /** |
| 11276 | * Converts the stream to a `ReadableStream` using the provided services. |
| 11277 | * |
| 11278 | * **When to use** |
| 11279 | * |
| 11280 | * Use when bridging to Web Streams and you already have the `Context` required |
| 11281 | * to run the stream outside an `Effect`. |
| 11282 | * |
| 11283 | * **Details** |
| 11284 | * |
| 11285 | * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. |
| 11286 | * |
| 11287 | * **Example** (Converting to a ReadableStream with services) |
| 11288 | * |
| 11289 | * ```ts import.meta.vitest |
| 11290 | * import { Context, Stream } from "effect" |
| 11291 | * |
| 11292 | * const stream = Stream.make(1, 2, 3, 4, 5) |
| 11293 | * const readableStream = Stream.toReadableStreamWith(stream, Context.empty()) |
| 11294 | * const values = await Array.fromAsync(readableStream) |
| 11295 | * values // => [ 1, 2, 3, 4, 5 ] |
| 11296 | * ``` |
| 11297 | * |
| 11298 | * @category destructors |
| 11299 | * @since 4.0.0 |
| 11300 | */ |
| 11301 | export const toReadableStreamWith: { |
| 11302 | <A, XR>( |
| 11303 | context: Context.Context<XR>, |
| 11304 | options?: { readonly strategy?: QueuingStrategy<A> | undefined } |
| 11305 | ): <E, R extends XR>(self: Stream<A, E, R>) => ReadableStream<A> |
| 11306 | <A, E, XR, R extends XR>( |
| 11307 | self: Stream<A, E, R>, |
| 11308 | context: Context.Context<XR>, |
| 11309 | options?: { readonly strategy?: QueuingStrategy<A> | undefined } |
| 11310 | ): ReadableStream<A> |
| 11311 | } = dual( |
| 11312 | (args) => isStream(args[0]), |
| 11313 | <A, E, XR, R extends XR>( |
| 11314 | self: Stream<A, E, R>, |
| 11315 | context: Context.Context<XR>, |
| 11316 | options?: { readonly strategy?: QueuingStrategy<A> | undefined } |
| 11317 | ): ReadableStream<A> => { |
| 11318 | let currentResolve: (() => void) | undefined = undefined |
| 11319 | let fiber: Fiber.Fiber<void, E> | undefined = undefined |
| 11320 | const latch = Latch.makeUnsafe(false) |
| 11321 | |
| 11322 | return new ReadableStream<A>({ |
| 11323 | start(controller) { |
| 11324 | fiber = Effect.runFork(Effect.provideContext( |
| 11325 | runForEachArray(self, (chunk) => |
| 11326 | latch.whenOpen(Effect.sync(() => { |
| 11327 | latch.closeUnsafe() |
| 11328 | for (let i = 0; i < chunk.length; i++) { |
| 11329 | controller.enqueue(chunk[i]) |
| 11330 | } |
| 11331 | currentResolve!() |
| 11332 | currentResolve = undefined |
| 11333 | }))), |
| 11334 | context |
| 11335 | )) |
| 11336 | fiber.addObserver((exit) => { |
| 11337 | if (exit._tag === "Failure") { |
| 11338 | controller.error(Cause.squash(exit.cause)) |
| 11339 | } else { |
| 11340 | controller.close() |
| 11341 | } |
| 11342 | }) |
| 11343 | }, |
| 11344 | pull() { |
| 11345 | return new Promise<void>((resolve) => { |
| 11346 | currentResolve = resolve |
| 11347 | latch.openUnsafe() |
| 11348 | }) |
| 11349 | }, |
| 11350 | cancel() { |
| 11351 | if (!fiber) return |
| 11352 | return Effect.runPromise(Effect.asVoid(Fiber.interrupt(fiber))) |
| 11353 | } |
| 11354 | }, options?.strategy) |
| 11355 | } |
| 11356 | ) |
| 11357 | |
| 11358 | /** |
| 11359 | * Converts a stream to a `ReadableStream`. |
| 11360 | * |
| 11361 | * **Details** |
| 11362 | * |
| 11363 | * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. |
| 11364 | * |
| 11365 | * **Example** (Converting a stream to a ReadableStream) |
| 11366 | * |
| 11367 | * ```ts import.meta.vitest |
| 11368 | * import { Stream } from "effect" |
| 11369 | * |
| 11370 | * const readableStream = Stream.toReadableStream(Stream.make(1, 2, 3)) |
| 11371 | * const values = await Array.fromAsync(readableStream) |
| 11372 | * values // => [ 1, 2, 3 ] |
| 11373 | * ``` |
| 11374 | * |
| 11375 | * @category destructors |
| 11376 | * @since 2.0.0 |
| 11377 | */ |
| 11378 | export const toReadableStream: { |
| 11379 | <A>( |
| 11380 | options?: { readonly strategy?: QueuingStrategy<A> | undefined } |
| 11381 | ): <E>( |
| 11382 | self: Stream<A, E> |
| 11383 | ) => ReadableStream<A> |
| 11384 | <A, E>( |
| 11385 | self: Stream<A, E>, |
| 11386 | options?: { readonly strategy?: QueuingStrategy<A> | undefined } |
| 11387 | ): ReadableStream<A> |
| 11388 | } = dual( |
| 11389 | (args) => isStream(args[0]), |
| 11390 | <A, E>( |
| 11391 | self: Stream<A, E>, |
| 11392 | options?: { readonly strategy?: QueuingStrategy<A> | undefined } |
| 11393 | ): ReadableStream<A> => toReadableStreamWith(self, Context.empty(), options) |
| 11394 | ) |
| 11395 | |
| 11396 | /** |
| 11397 | * Creates an Effect that builds a ReadableStream from the stream. |
| 11398 | * |
| 11399 | * **When to use** |
| 11400 | * |
| 11401 | * Use when bridging to Web Streams from inside an `Effect` so the required |
| 11402 | * services can be captured from the current context. |
| 11403 | * |
| 11404 | * **Details** |
| 11405 | * |
| 11406 | * See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. |
| 11407 | * |
| 11408 | * **Example** (Creating a ReadableStream effect) |
| 11409 | * |
| 11410 | * ```ts import.meta.vitest |
| 11411 | * import { Effect, Stream } from "effect" |
| 11412 | * |
| 11413 | * const stream = Stream.make(1, 2, 3, 4, 5) |
| 11414 | * |
| 11415 | * const effect = Effect.gen(function*() { |
| 11416 | * const readableStream = yield* Stream.toReadableStreamEffect(stream) |
| 11417 | * readableStream instanceof ReadableStream // => true |
| 11418 | * }) |
| 11419 | * |
| 11420 | * await Effect.runPromise(effect) |
| 11421 | * ``` |
| 11422 | * |
| 11423 | * @category destructors |
| 11424 | * @since 2.0.0 |
| 11425 | */ |
| 11426 | export const toReadableStreamEffect: { |
| 11427 | <A>( |
| 11428 | options?: { readonly strategy?: QueuingStrategy<A> | undefined } |
| 11429 | ): <E, R>( |
| 11430 | self: Stream<A, E, R> |
| 11431 | ) => Effect.Effect<ReadableStream<A>, never, R> |
| 11432 | <A, E, R>( |
| 11433 | self: Stream<A, E, R>, |
| 11434 | options?: { readonly strategy?: QueuingStrategy<A> | undefined } |
| 11435 | ): Effect.Effect<ReadableStream<A>, never, R> |
| 11436 | } = dual( |
| 11437 | (args) => isStream(args[0]), |
| 11438 | <A, E, R>( |
| 11439 | self: Stream<A, E, R>, |
| 11440 | options?: { readonly strategy?: QueuingStrategy<A> | undefined } |
| 11441 | ): Effect.Effect<ReadableStream<A>, never, R> => |
| 11442 | Effect.map( |
| 11443 | Effect.context<R>(), |
| 11444 | (context) => toReadableStreamWith(self, context, options) |
| 11445 | ) |
| 11446 | ) |
| 11447 | |
| 11448 | /** |
| 11449 | * Converts the stream to an `AsyncIterable` using the provided services. |
| 11450 | * |
| 11451 | * **When to use** |
| 11452 | * |
| 11453 | * Use when converting outside an Effect and you already have the `Context` |
| 11454 | * needed to run the stream. |
| 11455 | * |
| 11456 | * **Example** (Converting to an AsyncIterable with services) |
| 11457 | * |
| 11458 | * ```ts import.meta.vitest |
| 11459 | * import { Context, Stream } from "effect" |
| 11460 | * |
| 11461 | * const stream = Stream.make(1, 2, 3) |
| 11462 | * const iterable = Stream.toAsyncIterableWith(stream, Context.empty()) |
| 11463 | * |
| 11464 | * await Array.fromAsync(iterable) // => [1, 2, 3] |
| 11465 | * ``` |
| 11466 | * |
| 11467 | * @category destructors |
| 11468 | * @since 4.0.0 |
| 11469 | */ |
| 11470 | export const toAsyncIterableWith: { |
| 11471 | <XR>(context: Context.Context<XR>): <A, E, R extends XR>(self: Stream<A, E, R>) => AsyncIterable<A> |
| 11472 | <A, E, XR, R extends XR>( |
| 11473 | self: Stream<A, E, R>, |
| 11474 | context: Context.Context<XR> |
| 11475 | ): AsyncIterable<A> |
| 11476 | } = dual( |
| 11477 | 2, |
| 11478 | <A, E, XR, R extends XR>( |
| 11479 | self: Stream<A, E, R>, |
| 11480 | context: Context.Context<XR> |
| 11481 | ): AsyncIterable<A> => ({ |
| 11482 | [Symbol.asyncIterator]() { |
| 11483 | const runPromise = Effect.runPromiseWith(context) |
| 11484 | const runFork = Effect.runForkWith(context) |
| 11485 | const scope = Scope.makeUnsafe() |
| 11486 | let pull: Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E, void, R> | undefined |
| 11487 | let currentIter: Iterator<A> | undefined |
| 11488 | let currentFiber: Fiber.Fiber<Arr.NonEmptyReadonlyArray<A>, E | Cause.Done<void>> | undefined |
| 11489 | let closePromise: Promise<IteratorResult<A>> | undefined |
| 11490 | const close = (exit: Exit.Exit<unknown, unknown>): Promise<IteratorResult<A>> => { |
| 11491 | if (closePromise) return closePromise |
| 11492 | const fiber = currentFiber |
| 11493 | closePromise = runPromise(Effect.as( |
| 11494 | Effect.andThen( |
| 11495 | fiber ? Fiber.interrupt(fiber) : Effect.void, |
| 11496 | Scope.close(scope, exit) |
| 11497 | ), |
| 11498 | { done: true, value: undefined } |
| 11499 | )) |
| 11500 | return closePromise |
| 11501 | } |
| 11502 | const closeAndReportError = async (exit: Exit.Exit<unknown, unknown>): Promise<void> => { |
| 11503 | try { |
| 11504 | await close(exit) |
| 11505 | } catch (error) { |
| 11506 | await runPromise(Effect.logError("Suppressed error while closing Stream async iterator", error)) |
| 11507 | } |
| 11508 | } |
| 11509 | return { |
| 11510 | async next(): Promise<IteratorResult<A>> { |
| 11511 | if (closePromise) return closePromise |
| 11512 | if (currentIter) { |
| 11513 | const next = currentIter.next() |
| 11514 | if (!next.done) return next |
| 11515 | currentIter = undefined |
| 11516 | } |
| 11517 | const fiber = runFork( |
| 11518 | pull ?? |
| 11519 | Effect.flatMap(Channel.toPullScoped(self.channel, scope), (nextPull) => { |
| 11520 | pull = nextPull |
| 11521 | return nextPull |
| 11522 | }) |
| 11523 | ) |
| 11524 | currentFiber = fiber |
| 11525 | const exit = await runPromise(Fiber.await(fiber)) |
| 11526 | if (currentFiber === fiber) { |
| 11527 | currentFiber = undefined |
| 11528 | } |
| 11529 | if (Exit.isSuccess(exit)) { |
| 11530 | currentIter = exit.value[Symbol.iterator]() |
| 11531 | return currentIter.next() |
| 11532 | } else if (Pull.isDoneCause(exit.cause)) { |
| 11533 | return close(Exit.void) |
| 11534 | } |
| 11535 | if (closePromise && Cause.hasInterruptsOnly(exit.cause)) { |
| 11536 | return closePromise |
| 11537 | } |
| 11538 | await closeAndReportError(exit) |
| 11539 | throw Cause.squash(exit.cause) |
| 11540 | }, |
| 11541 | return() { |
| 11542 | return close(Exit.void) |
| 11543 | }, |
| 11544 | async throw(error) { |
| 11545 | await closeAndReportError(Exit.die(error)) |
| 11546 | throw error |
| 11547 | } |
| 11548 | } |
| 11549 | } |
| 11550 | }) |
| 11551 | ) |
| 11552 | |
| 11553 | /** |
| 11554 | * Creates an effect that yields an `AsyncIterable` using the current services. |
| 11555 | * |
| 11556 | * **When to use** |
| 11557 | * |
| 11558 | * Use when the `AsyncIterable` should be created inside Effect with the current |
| 11559 | * context supplying the stream's services. |
| 11560 | * |
| 11561 | * **Example** (Creating an AsyncIterable effect) |
| 11562 | * |
| 11563 | * ```ts import.meta.vitest |
| 11564 | * import { Effect, Stream } from "effect" |
| 11565 | * |
| 11566 | * const stream = Stream.make(1, 2, 3) |
| 11567 | * |
| 11568 | * const program = Effect.gen(function*() { |
| 11569 | * const iterable = yield* Stream.toAsyncIterableEffect(stream) |
| 11570 | * return yield* Effect.promise(() => Array.fromAsync(iterable)) |
| 11571 | * }) |
| 11572 | * |
| 11573 | * await Effect.runPromise(program) // => [1, 2, 3] |
| 11574 | * ``` |
| 11575 | * |
| 11576 | * @category destructors |
| 11577 | * @since 3.15.0 |
| 11578 | */ |
| 11579 | export const toAsyncIterableEffect = <A, E, R>(self: Stream<A, E, R>): Effect.Effect<AsyncIterable<A>, never, R> => |
| 11580 | Effect.map( |
| 11581 | Effect.context<R>(), |
| 11582 | (context) => toAsyncIterableWith(self, context) |
| 11583 | ) |
| 11584 | |
| 11585 | /** |
| 11586 | * Converts a stream to an `AsyncIterable` for `for await...of` consumption. |
| 11587 | * |
| 11588 | * **Example** (Converting to an async iterable) |
| 11589 | * |
| 11590 | * ```ts import.meta.vitest |
| 11591 | * import { Stream } from "effect" |
| 11592 | * |
| 11593 | * const stream = Stream.make(1, 2, 3) |
| 11594 | * |
| 11595 | * await Array.fromAsync(Stream.toAsyncIterable(stream)) // => [1, 2, 3] |
| 11596 | * ``` |
| 11597 | * |
| 11598 | * @category destructors |
| 11599 | * @since 3.15.0 |
| 11600 | */ |
| 11601 | export const toAsyncIterable = <A, E>(self: Stream<A, E>): AsyncIterable<A> => |
| 11602 | toAsyncIterableWith(self, Context.empty()) |
| 11603 | |
| 11604 | /** |
| 11605 | * Runs the stream, publishing elements into the provided PubSub. |
| 11606 | * |
| 11607 | * **Details** |
| 11608 | * |
| 11609 | * `shutdownOnEnd` controls whether the PubSub is shut down when the stream ends. |
| 11610 | * It only shuts down when set to `true`. |
| 11611 | * |
| 11612 | * **Example** (Running a stream into a PubSub) |
| 11613 | * |
| 11614 | * ```ts import.meta.vitest |
| 11615 | * import { Effect, PubSub, Stream } from "effect" |
| 11616 | * |
| 11617 | * const program = Effect.scoped(Effect.gen(function* () { |
| 11618 | * const pubsub = yield* PubSub.unbounded<number>() |
| 11619 | * const subscription = yield* PubSub.subscribe(pubsub) |
| 11620 | * |
| 11621 | * yield* Stream.runIntoPubSub(Stream.fromIterable([1, 2]), pubsub) |
| 11622 | * |
| 11623 | * const first = yield* PubSub.take(subscription) |
| 11624 | * const second = yield* PubSub.take(subscription) |
| 11625 | * |
| 11626 | * first // => 1 |
| 11627 | * second // => 2 |
| 11628 | * })) |
| 11629 | * |
| 11630 | * await Effect.runPromise(program) |
| 11631 | * ``` |
| 11632 | * |
| 11633 | * @category destructors |
| 11634 | * @since 2.0.0 |
| 11635 | */ |
| 11636 | export const runIntoPubSub: { |
| 11637 | <A>( |
| 11638 | pubsub: PubSub.PubSub<A>, |
| 11639 | options?: { |
| 11640 | readonly shutdownOnEnd?: boolean | undefined |
| 11641 | } | undefined |
| 11642 | ): <E, R>(self: Stream<A, E, R>) => Effect.Effect<void, E, R> |
| 11643 | <A, E, R>( |
| 11644 | self: Stream<A, E, R>, |
| 11645 | pubsub: PubSub.PubSub<A>, |
| 11646 | options?: { |
| 11647 | readonly shutdownOnEnd?: boolean | undefined |
| 11648 | } | undefined |
| 11649 | ): Effect.Effect<void, E, R> |
| 11650 | } = dual((args) => isStream(args[0]), <A, E, R>( |
| 11651 | self: Stream<A, E, R>, |
| 11652 | pubsub: PubSub.PubSub<A>, |
| 11653 | options?: { |
| 11654 | readonly shutdownOnEnd?: boolean | undefined |
| 11655 | } | undefined |
| 11656 | ): Effect.Effect<void, E, R> => Channel.runIntoPubSubArray(self.channel, pubsub, options)) |
| 11657 | |
| 11658 | /** |
| 11659 | * Converts a stream to a PubSub of emitted values for concurrent consumption. |
| 11660 | * |
| 11661 | * **Details** |
| 11662 | * |
| 11663 | * `shutdownOnEnd` indicates whether the PubSub should be shut down when the |
| 11664 | * stream ends. By default this is `true`. |
| 11665 | * |
| 11666 | * **Example** (Converting a stream to a PubSub for concurrent consumption) |
| 11667 | * |
| 11668 | * ```ts import.meta.vitest |
| 11669 | * import { Effect, PubSub, Stream } from "effect" |
| 11670 | * |
| 11671 | * const program = Effect.scoped(Effect.gen(function* () { |
| 11672 | * const pubsub = yield* Stream.fromArray([1, 2]).pipe( |
| 11673 | * Stream.toPubSub({ capacity: 8 }) |
| 11674 | * ) |
| 11675 | * const subscription = yield* PubSub.subscribe(pubsub) |
| 11676 | * const first = yield* PubSub.take(subscription) |
| 11677 | * |
| 11678 | * first // => 1 |
| 11679 | * })) |
| 11680 | * await Effect.runPromise(program) |
| 11681 | * ``` |
| 11682 | * |
| 11683 | * @category destructors |
| 11684 | * @since 2.0.0 |
| 11685 | */ |
| 11686 | export const toPubSub: { |
| 11687 | ( |
| 11688 | options: { |
| 11689 | readonly capacity: "unbounded" |
| 11690 | readonly replay?: number | undefined |
| 11691 | readonly shutdownOnEnd?: boolean | undefined |
| 11692 | } | { |
| 11693 | readonly capacity: number |
| 11694 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 11695 | readonly replay?: number | undefined |
| 11696 | readonly shutdownOnEnd?: boolean | undefined |
| 11697 | } |
| 11698 | ): <A, E, R>(self: Stream<A, E, R>) => Effect.Effect<PubSub.PubSub<A>, never, R | Scope.Scope> |
| 11699 | <A, E, R>( |
| 11700 | self: Stream<A, E, R>, |
| 11701 | options: { |
| 11702 | readonly capacity: "unbounded" |
| 11703 | readonly replay?: number | undefined |
| 11704 | readonly shutdownOnEnd?: boolean | undefined |
| 11705 | } | { |
| 11706 | readonly capacity: number |
| 11707 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 11708 | readonly replay?: number | undefined |
| 11709 | readonly shutdownOnEnd?: boolean | undefined |
| 11710 | } |
| 11711 | ): Effect.Effect<PubSub.PubSub<A>, never, R | Scope.Scope> |
| 11712 | } = dual( |
| 11713 | 2, |
| 11714 | <A, E, R>( |
| 11715 | self: Stream<A, E, R>, |
| 11716 | options: { |
| 11717 | readonly capacity: "unbounded" |
| 11718 | readonly replay?: number | undefined |
| 11719 | readonly shutdownOnEnd?: boolean | undefined |
| 11720 | } | { |
| 11721 | readonly capacity: number |
| 11722 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 11723 | readonly replay?: number | undefined |
| 11724 | readonly shutdownOnEnd?: boolean | undefined |
| 11725 | } |
| 11726 | ): Effect.Effect<PubSub.PubSub<A>, never, R | Scope.Scope> => Channel.toPubSubArray(self.channel, options) |
| 11727 | ) |
| 11728 | |
| 11729 | /** |
| 11730 | * Converts a stream to a PubSub of `Take` values for concurrent consumption. |
| 11731 | * |
| 11732 | * **Details** |
| 11733 | * |
| 11734 | * Chunks are published as `Take` values. When the stream ends, the PubSub is |
| 11735 | * ended with the stream's `Exit`, so every subscriber, including one that |
| 11736 | * subscribes later, observes completion or failure after its buffered chunks. |
| 11737 | * |
| 11738 | * **Example** (Converting to a PubSub of takes) |
| 11739 | * |
| 11740 | * ```ts import.meta.vitest |
| 11741 | * import { Effect, PubSub, Stream } from "effect" |
| 11742 | * |
| 11743 | * const program = Effect.gen(function* () { |
| 11744 | * const pubsub = yield* Stream.fromArray([1, 2, 3]).pipe( |
| 11745 | * Stream.toPubSubTake({ capacity: 8 }) |
| 11746 | * ) |
| 11747 | * const subscription = yield* PubSub.subscribe(pubsub) |
| 11748 | * const take = yield* PubSub.take(subscription) |
| 11749 | * |
| 11750 | * if (Array.isArray(take)) { |
| 11751 | * take // => [ 1, 2, 3 ] |
| 11752 | * } |
| 11753 | * }) |
| 11754 | * await Effect.runPromise(Effect.scoped(program)) |
| 11755 | * ``` |
| 11756 | * |
| 11757 | * @category destructors |
| 11758 | * @since 4.0.0 |
| 11759 | */ |
| 11760 | export const toPubSubTake: { |
| 11761 | ( |
| 11762 | options: { |
| 11763 | readonly capacity: "unbounded" |
| 11764 | readonly replay?: number | undefined |
| 11765 | } | { |
| 11766 | readonly capacity: number |
| 11767 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 11768 | readonly replay?: number | undefined |
| 11769 | } |
| 11770 | ): <A, E, R>(self: Stream<A, E, R>) => Effect.Effect<PubSub.PubSub<Take.Take<A, E>>, never, R | Scope.Scope> |
| 11771 | <A, E, R>( |
| 11772 | self: Stream<A, E, R>, |
| 11773 | options: { |
| 11774 | readonly capacity: "unbounded" |
| 11775 | readonly replay?: number | undefined |
| 11776 | } | { |
| 11777 | readonly capacity: number |
| 11778 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 11779 | readonly replay?: number | undefined |
| 11780 | } |
| 11781 | ): Effect.Effect<PubSub.PubSub<Take.Take<A, E>>, never, R | Scope.Scope> |
| 11782 | } = dual( |
| 11783 | 2, |
| 11784 | <A, E, R>( |
| 11785 | self: Stream<A, E, R>, |
| 11786 | options: { |
| 11787 | readonly capacity: "unbounded" |
| 11788 | readonly replay?: number | undefined |
| 11789 | } | { |
| 11790 | readonly capacity: number |
| 11791 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 11792 | readonly replay?: number | undefined |
| 11793 | } |
| 11794 | ): Effect.Effect<PubSub.PubSub<Take.Take<A, E>>, never, R | Scope.Scope> => |
| 11795 | Channel.toPubSubTake(self.channel, options) |
| 11796 | ) |
| 11797 | |
| 11798 | /** |
| 11799 | * Creates a scoped dequeue that is fed by the stream for concurrent |
| 11800 | * consumption. |
| 11801 | * |
| 11802 | * **Details** |
| 11803 | * |
| 11804 | * Elements are offered to the queue as the stream runs. Stream completion is |
| 11805 | * signaled with `Cause.Done`, stream failures fail the queue, and the queue is |
| 11806 | * shut down when the surrounding scope closes. |
| 11807 | * |
| 11808 | * **Example** (Converting a stream to a Queue for concurrent consumption) |
| 11809 | * |
| 11810 | * ```ts import.meta.vitest |
| 11811 | * import { Effect, Queue, Stream } from "effect" |
| 11812 | * |
| 11813 | * const program = Effect.gen(function* () { |
| 11814 | * const queue = yield* Stream.toQueue(Stream.fromIterable([1, 2, 3]), { capacity: 8 }) |
| 11815 | * const chunk = yield* Queue.takeBetween(queue, 1, 3) |
| 11816 | * chunk // => [ 1, 2, 3 ] |
| 11817 | * }) |
| 11818 | * await Effect.runPromise(Effect.scoped(program)) |
| 11819 | * ``` |
| 11820 | * |
| 11821 | * @category destructors |
| 11822 | * @since 2.0.0 |
| 11823 | */ |
| 11824 | export const toQueue: { |
| 11825 | ( |
| 11826 | options: { |
| 11827 | readonly capacity: "unbounded" |
| 11828 | } | { |
| 11829 | readonly capacity: number |
| 11830 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 11831 | } |
| 11832 | ): <A, E, R>(self: Stream<A, E, R>) => Effect.Effect<Queue.Dequeue<A, E | Cause.Done>, never, R | Scope.Scope> |
| 11833 | <A, E, R>( |
| 11834 | self: Stream<A, E, R>, |
| 11835 | options: { |
| 11836 | readonly capacity: "unbounded" |
| 11837 | } | { |
| 11838 | readonly capacity: number |
| 11839 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 11840 | } |
| 11841 | ): Effect.Effect<Queue.Dequeue<A, E | Cause.Done>, never, R | Scope.Scope> |
| 11842 | } = dual( |
| 11843 | 2, |
| 11844 | <A, E, R>( |
| 11845 | self: Stream<A, E, R>, |
| 11846 | options: { |
| 11847 | readonly capacity: "unbounded" |
| 11848 | } | { |
| 11849 | readonly capacity: number |
| 11850 | readonly strategy?: "dropping" | "sliding" | "suspend" | undefined |
| 11851 | } |
| 11852 | ): Effect.Effect<Queue.Dequeue<A, E | Cause.Done>, never, R | Scope.Scope> => |
| 11853 | Channel.toQueueArray(self.channel, options) |
| 11854 | ) |
| 11855 | |
| 11856 | /** |
| 11857 | * Runs the stream, offering each element to the provided queue and ending it |
| 11858 | * with `Cause.Done` when the stream completes. |
| 11859 | * |
| 11860 | * **Example** (Running a stream into a queue) |
| 11861 | * |
| 11862 | * ```ts import.meta.vitest |
| 11863 | * import { Cause, Effect, Queue, Stream } from "effect" |
| 11864 | * |
| 11865 | * const program = Effect.gen(function*() { |
| 11866 | * const queue = yield* Queue.bounded<number, Cause.Done>(4) |
| 11867 | * |
| 11868 | * yield* Effect.forkChild( |
| 11869 | * Stream.runIntoQueue(Stream.fromIterable([1, 2, 3]), queue) |
| 11870 | * ) |
| 11871 | * |
| 11872 | * const values = [ |
| 11873 | * yield* Queue.take(queue), |
| 11874 | * yield* Queue.take(queue), |
| 11875 | * yield* Queue.take(queue) |
| 11876 | * ] |
| 11877 | * const done = yield* Effect.flip(Queue.take(queue)) |
| 11878 | * |
| 11879 | * values // => [ 1, 2, 3 ] |
| 11880 | * done._tag === "Done" // => true |
| 11881 | * }) |
| 11882 | * await Effect.runPromise(program) |
| 11883 | * ``` |
| 11884 | * |
| 11885 | * @category destructors |
| 11886 | * @since 2.0.0 |
| 11887 | */ |
| 11888 | export const runIntoQueue: { |
| 11889 | <A, E>(queue: Queue.Queue<A, E | Cause.Done>): <R>(self: Stream<A, E, R>) => Effect.Effect<void, never, R> |
| 11890 | <A, E, R>(self: Stream<A, E, R>, queue: Queue.Queue<A, E | Cause.Done>): Effect.Effect<void, never, R> |
| 11891 | } = dual(2, <A, E, R>( |
| 11892 | self: Stream<A, E, R>, |
| 11893 | queue: Queue.Queue<A, E | Cause.Done> |
| 11894 | ): Effect.Effect<void, never, R> => Channel.runIntoQueueArray(self.channel, queue)) |