veffect
Version:
powerful TypeScript validation library built on the robust foundation of Effect combining exceptional type safety, high performance, and developer experience. Taking inspiration from Effect's functional principles, VEffect delivers a balanced approach tha
1,372 lines (1,328 loc) • 280 kB
text/typescript
import * as Cause from "../Cause.js"
import type * as Channel from "../Channel.js"
import * as Chunk from "../Chunk.js"
import * as Clock from "../Clock.js"
import * as Context from "../Context.js"
import * as Deferred from "../Deferred.js"
import * as Duration from "../Duration.js"
import * as Effect from "../Effect.js"
import * as Either from "../Either.js"
import * as Equal from "../Equal.js"
import * as Exit from "../Exit.js"
import * as Fiber from "../Fiber.js"
import type { LazyArg } from "../Function.js"
import { constTrue, dual, identity, pipe } from "../Function.js"
import * as Layer from "../Layer.js"
import * as MergeDecision from "../MergeDecision.js"
import * as Option from "../Option.js"
import type * as Order from "../Order.js"
import { pipeArguments } from "../Pipeable.js"
import { hasProperty, isTagged, type Predicate, type Refinement } from "../Predicate.js"
import * as PubSub from "../PubSub.js"
import * as Queue from "../Queue.js"
import * as Ref from "../Ref.js"
import * as Runtime from "../Runtime.js"
import * as Schedule from "../Schedule.js"
import * as Scope from "../Scope.js"
import type * as Sink from "../Sink.js"
import type * as Stream from "../Stream.js"
import type * as Emit from "../StreamEmit.js"
import * as HaltStrategy from "../StreamHaltStrategy.js"
import type * as Take from "../Take.js"
import type * as Tracer from "../Tracer.js"
import * as Tuple from "../Tuple.js"
import type { MergeRecord, NoInfer } from "../Types.js"
import * as channel from "./channel.js"
import * as channelExecutor from "./channel/channelExecutor.js"
import * as MergeStrategy from "./channel/mergeStrategy.js"
import * as singleProducerAsyncInput from "./channel/singleProducerAsyncInput.js"
import * as core from "./core-stream.js"
import { RingBuffer } from "./ringBuffer.js"
import * as _sink from "./sink.js"
import * as DebounceState from "./stream/debounceState.js"
import * as emit from "./stream/emit.js"
import * as haltStrategy from "./stream/haltStrategy.js"
import * as Handoff from "./stream/handoff.js"
import * as HandoffSignal from "./stream/handoffSignal.js"
import * as pull from "./stream/pull.js"
import * as SinkEndReason from "./stream/sinkEndReason.js"
import * as ZipAllState from "./stream/zipAllState.js"
import * as ZipChunksState from "./stream/zipChunksState.js"
import * as InternalTake from "./take.js"
/** @internal */
const StreamSymbolKey = "effect/Stream"
/** @internal */
export const StreamTypeId: Stream.StreamTypeId = Symbol.for(
StreamSymbolKey
) as Stream.StreamTypeId
/** @internal */
const streamVariance = {
_R: (_: never) => _,
_E: (_: never) => _,
_A: (_: never) => _
}
/** @internal */
export class StreamImpl<out A, out E = never, out R = never> implements Stream.Stream<A, E, R> {
readonly [StreamTypeId] = streamVariance
constructor(
readonly channel: Channel.Channel<Chunk.Chunk<A>, unknown, E, unknown, unknown, unknown, R>
) {
}
pipe() {
return pipeArguments(this, arguments)
}
}
/** @internal */
export const isStream = (u: unknown): u is Stream.Stream<unknown, unknown, unknown> =>
hasProperty(u, StreamTypeId) || Effect.isEffect(u)
/** @internal */
export const DefaultChunkSize = 4096
/** @internal */
export const accumulate = <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<Chunk.Chunk<A>, E, R> =>
chunks(accumulateChunks(self))
/** @internal */
export const accumulateChunks = <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> => {
const accumulator = (
s: Chunk.Chunk<A>
): Channel.Channel<Chunk.Chunk<A>, Chunk.Chunk<A>, E, E, void, unknown> =>
core.readWith({
onInput: (input: Chunk.Chunk<A>) => {
const next = Chunk.appendAll(s, input)
return core.flatMap(
core.write(next),
() => accumulator(next)
)
},
onFailure: core.fail,
onDone: () => core.unit
})
return new StreamImpl(core.pipeTo(toChannel(self), accumulator(Chunk.empty())))
}
/** @internal */
export const acquireRelease = <A, E, R, R2, X>(
acquire: Effect.Effect<A, E, R>,
release: (resource: A, exit: Exit.Exit<unknown, unknown>) => Effect.Effect<X, never, R2>
): Stream.Stream<A, E, R | R2> => scoped(Effect.acquireRelease(acquire, release))
/** @internal */
export const aggregate = dual<
<B, A, A2, E2, R2>(
sink: Sink.Sink<B, A | A2, A2, E2, R2>
) => <E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<B, E2 | E, R2 | R>,
<A, E, R, B, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
sink: Sink.Sink<B, A | A2, A2, E2, R2>
) => Stream.Stream<B, E2 | E, R2 | R>
>(
2,
<A, E, R, B, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
sink: Sink.Sink<B, A | A2, A2, E2, R2>
): Stream.Stream<B, E2 | E, R2 | R> => aggregateWithin(self, sink, Schedule.forever)
)
/** @internal */
export const aggregateWithin = dual<
<B, A, A2, E2, R2, C, R3>(
sink: Sink.Sink<B, A | A2, A2, E2, R2>,
schedule: Schedule.Schedule<C, Option.Option<B>, R3>
) => <E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<B, E2 | E, R2 | R3 | R>,
<A, E, R, B, A2, E2, R2, C, R3>(
self: Stream.Stream<A, E, R>,
sink: Sink.Sink<B, A | A2, A2, E2, R2>,
schedule: Schedule.Schedule<C, Option.Option<B>, R3>
) => Stream.Stream<B, E2 | E, R2 | R3 | R>
>(
3,
<A, E, R, B, A2, E2, R2, C, R3>(
self: Stream.Stream<A, E, R>,
sink: Sink.Sink<B, A | A2, A2, E2, R2>,
schedule: Schedule.Schedule<C, Option.Option<B>, R3>
): Stream.Stream<B, E2 | E, R2 | R3 | R> =>
filterMap(
aggregateWithinEither(self, sink, schedule),
(_) =>
Either.match(_, {
onLeft: Option.none,
onRight: Option.some
})
)
)
/** @internal */
export const aggregateWithinEither = dual<
<B, A, A2, E2, R2, C, R3>(
sink: Sink.Sink<B, A | A2, A2, E2, R2>,
schedule: Schedule.Schedule<C, Option.Option<B>, R3>
) => <E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<Either.Either<B, C>, E2 | E, R2 | R3 | R>,
<A, E, R, B, A2, E2, R2, C, R3>(
self: Stream.Stream<A, E, R>,
sink: Sink.Sink<B, A | A2, A2, E2, R2>,
schedule: Schedule.Schedule<C, Option.Option<B>, R3>
) => Stream.Stream<Either.Either<B, C>, E2 | E, R2 | R3 | R>
>(
3,
<A, E, R, B, A2, E2, R2, C, R3>(
self: Stream.Stream<A, E, R>,
sink: Sink.Sink<B, A | A2, A2, E2, R2>,
schedule: Schedule.Schedule<C, Option.Option<B>, R3>
): Stream.Stream<Either.Either<B, C>, E2 | E, R2 | R3 | R> => {
const layer = Effect.all([
Handoff.make<HandoffSignal.HandoffSignal<A, E | E2>>(),
Ref.make<SinkEndReason.SinkEndReason>(SinkEndReason.ScheduleEnd),
Ref.make(Chunk.empty<A | A2>()),
Schedule.driver(schedule),
Ref.make(false),
Ref.make(false)
])
return pipe(
fromEffect(layer),
flatMap(([handoff, sinkEndReason, sinkLeftovers, scheduleDriver, consumed, endAfterEmit]) => {
const handoffProducer: Channel.Channel<never, Chunk.Chunk<A>, never, E | E2, unknown, unknown> = core
.readWithCause({
onInput: (input: Chunk.Chunk<A>) =>
core.flatMap(
core.fromEffect(pipe(
handoff,
Handoff.offer<HandoffSignal.HandoffSignal<A, E | E2>>(HandoffSignal.emit(input)),
Effect.when(() => Chunk.isNonEmpty(input))
)),
() => handoffProducer
),
onFailure: (cause) =>
core.fromEffect(
Handoff.offer<HandoffSignal.HandoffSignal<A, E | E2>>(
handoff,
HandoffSignal.halt(cause)
)
),
onDone: () =>
core.fromEffect(
Handoff.offer<HandoffSignal.HandoffSignal<A, E | E2>>(
handoff,
HandoffSignal.end(SinkEndReason.UpstreamEnd)
)
)
})
const handoffConsumer: Channel.Channel<Chunk.Chunk<A | A2>, unknown, E | E2, unknown, void, unknown> = pipe(
Ref.getAndSet(sinkLeftovers, Chunk.empty()),
Effect.flatMap((leftovers) => {
if (Chunk.isNonEmpty(leftovers)) {
return pipe(
Ref.set(consumed, true),
Effect.zipRight(Effect.succeed(pipe(
core.write(leftovers),
core.flatMap(() => handoffConsumer)
)))
)
}
return pipe(
Handoff.take(handoff),
Effect.map((signal) => {
switch (signal._tag) {
case HandoffSignal.OP_EMIT: {
return pipe(
core.fromEffect(Ref.set(consumed, true)),
channel.zipRight(core.write(signal.elements)),
channel.zipRight(core.fromEffect(Ref.get(endAfterEmit))),
core.flatMap((bool) => bool ? core.unit : handoffConsumer)
)
}
case HandoffSignal.OP_HALT: {
return core.failCause(signal.cause)
}
case HandoffSignal.OP_END: {
if (signal.reason._tag === SinkEndReason.OP_SCHEDULE_END) {
return pipe(
Ref.get(consumed),
Effect.map((bool) =>
bool ?
core.fromEffect(
pipe(
Ref.set(sinkEndReason, SinkEndReason.ScheduleEnd),
Effect.zipRight(Ref.set(endAfterEmit, true))
)
) :
pipe(
core.fromEffect(
pipe(
Ref.set(sinkEndReason, SinkEndReason.ScheduleEnd),
Effect.zipRight(Ref.set(endAfterEmit, true))
)
),
core.flatMap(() => handoffConsumer)
)
),
channel.unwrap
)
}
return pipe(
Ref.set<SinkEndReason.SinkEndReason>(sinkEndReason, signal.reason),
Effect.zipRight(Ref.set(endAfterEmit, true)),
core.fromEffect
)
}
}
})
)
}),
channel.unwrap
)
const timeout = (lastB: Option.Option<B>): Effect.Effect<C, Option.Option<never>, R2 | R3> =>
scheduleDriver.next(lastB)
const scheduledAggregator = (
sinkFiber: Fiber.RuntimeFiber<readonly [Chunk.Chunk<Chunk.Chunk<A | A2>>, B], E | E2>,
scheduleFiber: Fiber.RuntimeFiber<C, Option.Option<never>>,
scope: Scope.Scope
): Channel.Channel<Chunk.Chunk<Either.Either<B, C>>, unknown, E | E2, unknown, unknown, unknown, R2 | R3> => {
const forkSink = pipe(
Ref.set(consumed, false),
Effect.zipRight(Ref.set(endAfterEmit, false)),
Effect.zipRight(
pipe(
handoffConsumer,
channel.pipeToOrFail(_sink.toChannel(sink)),
core.collectElements,
channelExecutor.run,
Effect.forkIn(scope)
)
)
)
const handleSide = (
leftovers: Chunk.Chunk<Chunk.Chunk<A | A2>>,
b: B,
c: Option.Option<C>
): Channel.Channel<Chunk.Chunk<Either.Either<B, C>>, unknown, E | E2, unknown, unknown, unknown, R2 | R3> =>
pipe(
Ref.set(sinkLeftovers, Chunk.flatten(leftovers)),
Effect.zipRight(
Effect.map(Ref.get(sinkEndReason), (reason) => {
switch (reason._tag) {
case SinkEndReason.OP_SCHEDULE_END: {
return pipe(
Effect.all([
Ref.get(consumed),
forkSink,
pipe(timeout(Option.some(b)), Effect.forkIn(scope))
]),
Effect.map(([wasConsumed, sinkFiber, scheduleFiber]) => {
const toWrite = pipe(
c,
Option.match({
onNone: (): Chunk.Chunk<Either.Either<B, C>> => Chunk.of(Either.right(b)),
onSome: (c): Chunk.Chunk<Either.Either<B, C>> =>
Chunk.make(Either.right(b), Either.left(c))
})
)
if (wasConsumed) {
return pipe(
core.write(toWrite),
core.flatMap(() => scheduledAggregator(sinkFiber, scheduleFiber, scope))
)
}
return scheduledAggregator(sinkFiber, scheduleFiber, scope)
}),
channel.unwrap
)
}
case SinkEndReason.OP_UPSTREAM_END: {
return pipe(
Ref.get(consumed),
Effect.map((wasConsumed) =>
wasConsumed ?
core.write(Chunk.of<Either.Either<B, C>>(Either.right(b))) :
core.unit
),
channel.unwrap
)
}
}
})
),
channel.unwrap
)
return channel.unwrap(
Effect.raceWith(Fiber.join(sinkFiber), Fiber.join(scheduleFiber), {
onSelfDone: (sinkExit, _) =>
pipe(
Fiber.interrupt(scheduleFiber),
Effect.zipRight(pipe(
Effect.suspend(() => sinkExit),
Effect.map(([leftovers, b]) => handleSide(leftovers, b, Option.none()))
))
),
onOtherDone: (scheduleExit, _) =>
Effect.matchCauseEffect(Effect.suspend(() => scheduleExit), {
onFailure: (cause) =>
Either.match(
Cause.failureOrCause(cause),
{
onLeft: () =>
pipe(
handoff,
Handoff.offer<HandoffSignal.HandoffSignal<A, E | E2>>(
HandoffSignal.end(SinkEndReason.ScheduleEnd)
),
Effect.forkDaemon,
Effect.zipRight(
pipe(
Fiber.join(sinkFiber),
Effect.map(([leftovers, b]) => handleSide(leftovers, b, Option.none()))
)
)
),
onRight: (cause) =>
pipe(
handoff,
Handoff.offer<HandoffSignal.HandoffSignal<A, E | E2>>(
HandoffSignal.halt(cause)
),
Effect.forkDaemon,
Effect.zipRight(
pipe(
Fiber.join(sinkFiber),
Effect.map(([leftovers, b]) => handleSide(leftovers, b, Option.none()))
)
)
)
}
),
onSuccess: (c) =>
pipe(
handoff,
Handoff.offer<HandoffSignal.HandoffSignal<A, E | E2>>(
HandoffSignal.end(SinkEndReason.ScheduleEnd)
),
Effect.forkDaemon,
Effect.zipRight(
pipe(
Fiber.join(sinkFiber),
Effect.map(([leftovers, b]) => handleSide(leftovers, b, Option.some(c)))
)
)
)
})
})
)
}
return unwrapScoped(
pipe(
self,
toChannel,
core.pipeTo(handoffProducer),
channelExecutor.run,
Effect.forkScoped,
Effect.zipRight(
pipe(
handoffConsumer,
channel.pipeToOrFail(_sink.toChannel(sink)),
core.collectElements,
channelExecutor.run,
Effect.forkScoped,
Effect.flatMap((sinkFiber) =>
pipe(
Effect.forkScoped(timeout(Option.none())),
Effect.flatMap((scheduleFiber) =>
pipe(
Effect.scope,
Effect.map((scope) =>
new StreamImpl(
scheduledAggregator(sinkFiber, scheduleFiber, scope)
)
)
)
)
)
)
)
)
)
)
})
)
}
)
/** @internal */
export const as = dual<
<B>(value: B) => <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<B, E, R>,
<A, E, R, B>(self: Stream.Stream<A, E, R>, value: B) => Stream.Stream<B, E, R>
>(2, <A, E, R, B>(self: Stream.Stream<A, E, R>, value: B): Stream.Stream<B, E, R> => map(self, () => value))
/** @internal */
export const _async = <A, E = never, R = never>(
register: (
emit: Emit.Emit<R, E, A, void>
) => Effect.Effect<void, never, R> | void,
outputBuffer = 16
): Stream.Stream<A, E, R> =>
Effect.acquireRelease(
Queue.bounded<Take.Take<A, E>>(outputBuffer),
(queue) => Queue.shutdown(queue)
).pipe(
Effect.flatMap((output) =>
Effect.runtime<R>().pipe(
Effect.flatMap((runtime) =>
Effect.sync(() => {
const runPromiseExit = Runtime.runPromiseExit(runtime)
const canceler = register(emit.make<R, E, A, void>((resume) =>
InternalTake.fromPull(resume).pipe(
Effect.flatMap((take) => Queue.offer(output, take)),
Effect.asUnit,
runPromiseExit
).then((exit) => {
if (Exit.isFailure(exit)) {
if (!Cause.isInterrupted(exit.cause)) {
throw Cause.squash(exit.cause)
}
}
})
))
return canceler
})
),
Effect.map((value) => {
const loop: Channel.Channel<Chunk.Chunk<A>, unknown, E, unknown, void, unknown> = Queue.take(output).pipe(
Effect.flatMap((take) => InternalTake.done(take)),
Effect.match({
onFailure: (maybeError) =>
core.fromEffect(Queue.shutdown(output)).pipe(
channel.zipRight(Option.match(maybeError, {
onNone: () => core.unit,
onSome: (error) => core.fail(error)
}))
),
onSuccess: (chunk) => core.write(chunk).pipe(core.flatMap(() => loop))
}),
channel.unwrap
)
return fromChannel(loop).pipe(ensuring(value ?? Effect.unit))
})
)
),
unwrapScoped
)
/** @internal */
export const asyncEffect = <A, E = never, R = never>(
register: (emit: Emit.Emit<R, E, A, void>) => Effect.Effect<unknown, E, R>,
outputBuffer = 16
): Stream.Stream<A, E, R> =>
pipe(
Effect.acquireRelease(
Queue.bounded<Take.Take<A, E>>(outputBuffer),
(queue) => Queue.shutdown(queue)
),
Effect.flatMap((output) =>
pipe(
Effect.runtime<R>(),
Effect.flatMap((runtime) =>
pipe(
register(
emit.make((k) =>
pipe(
InternalTake.fromPull(k),
Effect.flatMap((take) => Queue.offer(output, take)),
Effect.asUnit,
Runtime.runPromiseExit(runtime)
).then((exit) => {
if (Exit.isFailure(exit)) {
if (!Cause.isInterrupted(exit.cause)) {
throw Cause.squash(exit.cause)
}
}
})
)
),
Effect.map(() => {
const loop: Channel.Channel<Chunk.Chunk<A>, unknown, E, unknown, void, unknown> = pipe(
Queue.take(output),
Effect.flatMap(InternalTake.done),
Effect.match({
onFailure: (maybeError) =>
pipe(
core.fromEffect(Queue.shutdown(output)),
channel.zipRight(Option.match(maybeError, { onNone: () => core.unit, onSome: core.fail }))
),
onSuccess: (chunk) => pipe(core.write(chunk), core.flatMap(() => loop))
}),
channel.unwrap
)
return loop
})
)
)
)
),
channel.unwrapScoped,
fromChannel
)
/** @internal */
export const asyncScoped = <A, E = never, R = never>(
register: (emit: Emit.Emit<R, E, A, void>) => Effect.Effect<unknown, E, R | Scope.Scope>,
outputBuffer = 16
): Stream.Stream<A, E, Exclude<R, Scope.Scope>> =>
pipe(
Effect.acquireRelease(
Queue.bounded<Take.Take<A, E>>(outputBuffer),
(queue) => Queue.shutdown(queue)
),
Effect.flatMap((output) =>
pipe(
Effect.runtime<R>(),
Effect.flatMap((runtime) =>
pipe(
register(
emit.make((k) =>
pipe(
InternalTake.fromPull(k),
Effect.flatMap((take) => Queue.offer(output, take)),
Effect.asUnit,
Runtime.runPromiseExit(runtime)
).then((exit) => {
if (Exit.isFailure(exit)) {
if (!Cause.isInterrupted(exit.cause)) {
throw Cause.squash(exit.cause)
}
}
})
)
),
Effect.zipRight(Ref.make(false)),
Effect.flatMap((ref) =>
pipe(
Ref.get(ref),
Effect.map((isDone) =>
isDone ?
pull.end() :
pipe(
Queue.take(output),
Effect.flatMap(InternalTake.done),
Effect.onError(() =>
pipe(
Ref.set(ref, true),
Effect.zipRight(Queue.shutdown(output))
)
)
)
)
)
)
)
)
)
),
scoped,
flatMap(repeatEffectChunkOption)
)
/** @internal */
export const branchAfter = dual<
<A, A2, E2, R2>(
n: number,
f: (input: Chunk.Chunk<A>) => Stream.Stream<A2, E2, R2>
) => <E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A2, E2 | E, R2 | R>,
<A, E, R, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
n: number,
f: (input: Chunk.Chunk<A>) => Stream.Stream<A2, E2, R2>
) => Stream.Stream<A2, E2 | E, R2 | R>
>(
3,
<A, E, R, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
n: number,
f: (input: Chunk.Chunk<A>) => Stream.Stream<A2, E2, R2>
) =>
suspend(() => {
const buffering = (
acc: Chunk.Chunk<A>
): Channel.Channel<Chunk.Chunk<A2>, Chunk.Chunk<A>, E2, never, unknown, unknown, R | R2> =>
core.readWith({
onInput: (input) => {
const nextSize = acc.length + input.length
if (nextSize >= n) {
const [b1, b2] = pipe(input, Chunk.splitAt(n - acc.length))
return running(pipe(acc, Chunk.appendAll(b1)), b2)
}
return buffering(pipe(acc, Chunk.appendAll(input)))
},
onFailure: core.fail,
onDone: () => running(acc, Chunk.empty())
})
const running = (
prefix: Chunk.Chunk<A>,
leftover: Chunk.Chunk<A>
): Channel.Channel<Chunk.Chunk<A2>, Chunk.Chunk<A>, E2, never, unknown, unknown, R | R2> =>
core.pipeTo(
channel.zipRight(
core.write(leftover),
channel.identityChannel()
),
toChannel(f(prefix))
)
return new StreamImpl(pipe(toChannel(self), channel.pipeToOrFail(buffering(Chunk.empty<A>()))))
})
)
/** @internal */
export const broadcast = dual<
<N extends number>(
n: N,
maximumLag: number
) => <A, E, R>(
self: Stream.Stream<A, E, R>
) => Effect.Effect<Stream.Stream.DynamicTuple<Stream.Stream<A, E>, N>, never, Scope.Scope | R>,
<A, E, R, N extends number>(
self: Stream.Stream<A, E, R>,
n: N,
maximumLag: number
) => Effect.Effect<Stream.Stream.DynamicTuple<Stream.Stream<A, E>, N>, never, Scope.Scope | R>
>(3, <A, E, R, N extends number>(
self: Stream.Stream<A, E, R>,
n: N,
maximumLag: number
): Effect.Effect<Stream.Stream.DynamicTuple<Stream.Stream<A, E>, N>, never, Scope.Scope | R> =>
pipe(
self,
broadcastedQueues(n, maximumLag),
Effect.map((tuple) =>
tuple.map((queue) => flattenTake(fromQueue(queue, { shutdown: true }))) as Stream.Stream.DynamicTuple<
Stream.Stream<A, E>,
N
>
)
))
/** @internal */
export const broadcastDynamic = dual<
(
maximumLag: number
) => <A, E, R>(self: Stream.Stream<A, E, R>) => Effect.Effect<Stream.Stream<A, E>, never, Scope.Scope | R>,
<A, E, R>(
self: Stream.Stream<A, E, R>,
maximumLag: number
) => Effect.Effect<Stream.Stream<A, E>, never, Scope.Scope | R>
>(2, <A, E, R>(
self: Stream.Stream<A, E, R>,
maximumLag: number
): Effect.Effect<Stream.Stream<A, E>, never, Scope.Scope | R> =>
pipe(
self,
broadcastedQueuesDynamic(maximumLag),
Effect.map((effect) => flattenTake(flatMap(scoped(effect), fromQueue)))
))
/** @internal */
export const broadcastedQueues = dual<
<N extends number>(
n: N,
maximumLag: number
) => <A, E, R>(
self: Stream.Stream<A, E, R>
) => Effect.Effect<Stream.Stream.DynamicTuple<Queue.Dequeue<Take.Take<A, E>>, N>, never, Scope.Scope | R>,
<A, E, R, N extends number>(
self: Stream.Stream<A, E, R>,
n: N,
maximumLag: number
) => Effect.Effect<Stream.Stream.DynamicTuple<Queue.Dequeue<Take.Take<A, E>>, N>, never, Scope.Scope | R>
>(3, <A, E, R, N extends number>(
self: Stream.Stream<A, E, R>,
n: N,
maximumLag: number
): Effect.Effect<Stream.Stream.DynamicTuple<Queue.Dequeue<Take.Take<A, E>>, N>, never, Scope.Scope | R> =>
Effect.flatMap(PubSub.bounded<Take.Take<A, E>>(maximumLag), (pubsub) =>
pipe(
Effect.all(Array.from({ length: n }, () => PubSub.subscribe(pubsub))) as Effect.Effect<
Stream.Stream.DynamicTuple<Queue.Dequeue<Take.Take<A, E>>, N>,
never,
R
>,
Effect.tap(() => Effect.forkScoped(runIntoPubSubScoped(self, pubsub)))
)))
/** @internal */
export const broadcastedQueuesDynamic = dual<
(
maximumLag: number
) => <A, E, R>(
self: Stream.Stream<A, E, R>
) => Effect.Effect<Effect.Effect<Queue.Dequeue<Take.Take<A, E>>, never, Scope.Scope>, never, Scope.Scope | R>,
<A, E, R>(
self: Stream.Stream<A, E, R>,
maximumLag: number
) => Effect.Effect<Effect.Effect<Queue.Dequeue<Take.Take<A, E>>, never, Scope.Scope>, never, Scope.Scope | R>
>(2, <A, E, R>(
self: Stream.Stream<A, E, R>,
maximumLag: number
): Effect.Effect<Effect.Effect<Queue.Dequeue<Take.Take<A, E>>, never, Scope.Scope>, never, Scope.Scope | R> =>
Effect.map(toPubSub(self, maximumLag), PubSub.subscribe))
/** @internal */
export const buffer = dual<
(
options: {
readonly capacity: "unbounded"
} | {
readonly capacity: number
readonly strategy?: "dropping" | "sliding" | "suspend" | undefined
}
) => <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, E, R>,
<A, E, R>(
self: Stream.Stream<A, E, R>,
options: {
readonly capacity: "unbounded"
} | {
readonly capacity: number
readonly strategy?: "dropping" | "sliding" | "suspend" | undefined
}
) => Stream.Stream<A, E, R>
>(2, <A, E, R>(
self: Stream.Stream<A, E, R>,
options: {
readonly capacity: "unbounded"
} | {
readonly capacity: number
readonly strategy?: "dropping" | "sliding" | "suspend" | undefined
}
): Stream.Stream<A, E, R> => {
if (options.capacity === "unbounded") {
return bufferUnbounded(self)
} else if (options.strategy === "dropping") {
return bufferDropping(self, options.capacity)
} else if (options.strategy === "sliding") {
return bufferSliding(self, options.capacity)
}
const queue = toQueueOfElements(self, options)
return new StreamImpl(
channel.unwrapScoped(
Effect.map(queue, (queue) => {
const process: Channel.Channel<Chunk.Chunk<A>, unknown, E, unknown, void, unknown> = pipe(
core.fromEffect(Queue.take(queue)),
core.flatMap(Exit.match({
onFailure: (cause) =>
pipe(
Cause.flipCauseOption(cause),
Option.match({ onNone: () => core.unit, onSome: core.failCause })
),
onSuccess: (value) => core.flatMap(core.write(Chunk.of(value)), () => process)
}))
)
return process
})
)
)
})
/** @internal */
export const bufferChunks = dual<
(options: {
readonly capacity: number
readonly strategy?: "dropping" | "sliding" | "suspend" | undefined
}) => <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, E, R>,
<A, E, R>(self: Stream.Stream<A, E, R>, options: {
readonly capacity: number
readonly strategy?: "dropping" | "sliding" | "suspend" | undefined
}) => Stream.Stream<A, E, R>
>(2, <A, E, R>(self: Stream.Stream<A, E, R>, options: {
readonly capacity: number
readonly strategy?: "dropping" | "sliding" | "suspend" | undefined
}): Stream.Stream<A, E, R> => {
if (options.strategy === "dropping") {
return bufferChunksDropping(self, options.capacity)
} else if (options.strategy === "sliding") {
return bufferChunksSliding(self, options.capacity)
}
const queue = toQueue(self, options)
return new StreamImpl(
channel.unwrapScoped(
Effect.map(queue, (queue) => {
const process: Channel.Channel<Chunk.Chunk<A>, unknown, E, unknown, void, unknown> = pipe(
core.fromEffect(Queue.take(queue)),
core.flatMap(InternalTake.match({
onEnd: () => core.unit,
onFailure: core.failCause,
onSuccess: (value) => pipe(core.write(value), core.flatMap(() => process))
}))
)
return process
})
)
)
})
const bufferChunksDropping = dual<
(capacity: number) => <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, E, R>,
<A, E, R>(self: Stream.Stream<A, E, R>, capacity: number) => Stream.Stream<A, E, R>
>(2, <A, E, R>(self: Stream.Stream<A, E, R>, capacity: number): Stream.Stream<A, E, R> => {
const queue = Effect.acquireRelease(
Queue.dropping<readonly [Take.Take<A, E>, Deferred.Deferred<void>]>(capacity),
(queue) => Queue.shutdown(queue)
)
return new StreamImpl(bufferSignal(queue, toChannel(self)))
})
const bufferChunksSliding = dual<
(capacity: number) => <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, E, R>,
<A, E, R>(self: Stream.Stream<A, E, R>, capacity: number) => Stream.Stream<A, E, R>
>(2, <A, E, R>(self: Stream.Stream<A, E, R>, capacity: number): Stream.Stream<A, E, R> => {
const queue = Effect.acquireRelease(
Queue.sliding<readonly [Take.Take<A, E>, Deferred.Deferred<void>]>(capacity),
(queue) => Queue.shutdown(queue)
)
return new StreamImpl(bufferSignal(queue, toChannel(self)))
})
const bufferDropping = dual<
(capacity: number) => <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, E, R>,
<A, E, R>(self: Stream.Stream<A, E, R>, capacity: number) => Stream.Stream<A, E, R>
>(2, <A, E, R>(self: Stream.Stream<A, E, R>, capacity: number): Stream.Stream<A, E, R> => {
const queue = Effect.acquireRelease(
Queue.dropping<readonly [Take.Take<A, E>, Deferred.Deferred<void>]>(capacity),
(queue) => Queue.shutdown(queue)
)
return new StreamImpl(bufferSignal(queue, toChannel(rechunk(1)(self))))
})
const bufferSliding = dual<
(capacity: number) => <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, E, R>,
<A, E, R>(self: Stream.Stream<A, E, R>, capacity: number) => Stream.Stream<A, E, R>
>(2, <A, E, R>(self: Stream.Stream<A, E, R>, capacity: number): Stream.Stream<A, E, R> => {
const queue = Effect.acquireRelease(
Queue.sliding<readonly [Take.Take<A, E>, Deferred.Deferred<void>]>(capacity),
(queue) => Queue.shutdown(queue)
)
return new StreamImpl(bufferSignal(queue, toChannel(pipe(self, rechunk(1)))))
})
const bufferUnbounded = <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> => {
const queue = toQueue(self, { strategy: "unbounded" })
return new StreamImpl(
channel.unwrapScoped(
Effect.map(queue, (queue) => {
const process: Channel.Channel<Chunk.Chunk<A>, unknown, E, unknown, void, unknown> = pipe(
core.fromEffect(Queue.take(queue)),
core.flatMap(InternalTake.match({
onEnd: () => core.unit,
onFailure: core.failCause,
onSuccess: (value) => core.flatMap(core.write(value), () => process)
}))
)
return process
})
)
)
}
const bufferSignal = <A, E, R>(
scoped: Effect.Effect<Queue.Queue<readonly [Take.Take<A, E>, Deferred.Deferred<void>]>, never, Scope.Scope>,
bufferChannel: Channel.Channel<Chunk.Chunk<A>, unknown, E, unknown, void, unknown, R>
): Channel.Channel<Chunk.Chunk<A>, unknown, E, unknown, void, unknown, R> => {
const producer = (
queue: Queue.Queue<readonly [Take.Take<A, E>, Deferred.Deferred<void>]>,
ref: Ref.Ref<Deferred.Deferred<void>>
): Channel.Channel<never, Chunk.Chunk<A>, never, E, unknown, unknown, R> => {
const terminate = (take: Take.Take<A, E>): Channel.Channel<never, Chunk.Chunk<A>, never, E, unknown, unknown, R> =>
pipe(
Ref.get(ref),
Effect.tap(Deferred.await),
Effect.zipRight(Deferred.make<void>()),
Effect.flatMap((deferred) =>
pipe(
Queue.offer(queue, [take, deferred] as const),
Effect.zipRight(Ref.set(ref, deferred)),
Effect.zipRight(Deferred.await(deferred))
)
),
Effect.asUnit,
core.fromEffect
)
return core.readWithCause({
onInput: (input: Chunk.Chunk<A>) =>
pipe(
Deferred.make<void>(),
Effect.flatMap(
(deferred) =>
pipe(
Queue.offer(queue, [InternalTake.chunk(input), deferred] as const),
Effect.flatMap((added) => pipe(Ref.set(ref, deferred), Effect.when(() => added)))
)
),
Effect.asUnit,
core.fromEffect,
core.flatMap(() => producer(queue, ref))
),
onFailure: (error) => terminate(InternalTake.failCause(error)),
onDone: () => terminate(InternalTake.end)
})
}
const consumer = (
queue: Queue.Queue<readonly [Take.Take<A, E>, Deferred.Deferred<void>]>
): Channel.Channel<Chunk.Chunk<A>, unknown, E, unknown, void, unknown, R> => {
const process: Channel.Channel<Chunk.Chunk<A>, unknown, E, unknown, void, unknown> = pipe(
core.fromEffect(Queue.take(queue)),
core.flatMap(([take, deferred]) =>
channel.zipRight(
core.fromEffect(Deferred.succeed(deferred, void 0)),
InternalTake.match(take, {
onEnd: () => core.unit,
onFailure: core.failCause,
onSuccess: (value) => pipe(core.write(value), core.flatMap(() => process))
})
)
)
)
return process
}
return channel.unwrapScoped(
pipe(
scoped,
Effect.flatMap((queue) =>
pipe(
Deferred.make<void>(),
Effect.tap((start) => Deferred.succeed(start, void 0)),
Effect.flatMap((start) =>
pipe(
Ref.make(start),
Effect.flatMap((ref) =>
pipe(
bufferChannel,
core.pipeTo(producer(queue, ref)),
channelExecutor.runScoped,
Effect.forkScoped
)
),
Effect.as(consumer(queue))
)
)
)
)
)
)
}
/** @internal */
export const catchAll = dual<
<E, A2, E2, R2>(
f: (error: E) => Stream.Stream<A2, E2, R2>
) => <A, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A2 | A, E2, R2 | R>,
<A, E, R, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
f: (error: E) => Stream.Stream<A2, E2, R2>
) => Stream.Stream<A2 | A, E2, R2 | R>
>(2, <A, E, R, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
f: (error: E) => Stream.Stream<A2, E2, R2>
): Stream.Stream<A2 | A, E2, R2 | R> =>
catchAllCause(self, (cause) =>
Either.match(Cause.failureOrCause(cause), {
onLeft: f,
onRight: failCause
})))
/** @internal */
export const catchAllCause = dual<
<E, A2, E2, R2>(
f: (cause: Cause.Cause<E>) => Stream.Stream<A2, E2, R2>
) => <A, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A2 | A, E2, R2 | R>,
<A, E, R, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
f: (cause: Cause.Cause<E>) => Stream.Stream<A2, E2, R2>
) => Stream.Stream<A2 | A, E2, R2 | R>
>(
2,
<A, E, R, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
f: (cause: Cause.Cause<E>) => Stream.Stream<A2, E2, R2>
): Stream.Stream<A | A2, E2, R | R2> =>
new StreamImpl<A | A2, E2, R | R2>(pipe(toChannel(self), core.catchAllCause((cause) => toChannel(f(cause)))))
)
/** @internal */
export const catchSome = dual<
<E, A2, E2, R2>(
pf: (error: E) => Option.Option<Stream.Stream<A2, E2, R2>>
) => <A, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A2 | A, E | E2, R2 | R>,
<A, E, R, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
pf: (error: E) => Option.Option<Stream.Stream<A2, E2, R2>>
) => Stream.Stream<A2 | A, E | E2, R2 | R>
>(
2,
<A, E, R, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
pf: (error: E) => Option.Option<Stream.Stream<A2, E2, R2>>
): Stream.Stream<A2 | A, E | E2, R2 | R> =>
pipe(self, catchAll((error) => pipe(pf(error), Option.getOrElse(() => fail<E | E2>(error)))))
)
/** @internal */
export const catchSomeCause = dual<
<E, A2, E2, R2>(
pf: (cause: Cause.Cause<E>) => Option.Option<Stream.Stream<A2, E2, R2>>
) => <A, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A2 | A, E | E2, R2 | R>,
<A, E, R, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
pf: (cause: Cause.Cause<E>) => Option.Option<Stream.Stream<A2, E2, R2>>
) => Stream.Stream<A2 | A, E | E2, R2 | R>
>(
2,
<A, E, R, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
pf: (cause: Cause.Cause<E>) => Option.Option<Stream.Stream<A2, E2, R2>>
): Stream.Stream<A2 | A, E | E2, R2 | R> =>
pipe(self, catchAllCause((cause) => pipe(pf(cause), Option.getOrElse(() => failCause<E | E2>(cause)))))
)
/* @internal */
export const catchTag = dual<
<K extends E["_tag"] & string, E extends { _tag: string }, A1, E1, R1>(
k: K,
f: (e: Extract<E, { _tag: K }>) => Stream.Stream<A1, E1, R1>
) => <A, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A | A1, Exclude<E, { _tag: K }> | E1, R | R1>,
<A, E extends { _tag: string }, R, K extends E["_tag"] & string, A1, E1, R1>(
self: Stream.Stream<A, E, R>,
k: K,
f: (e: Extract<E, { _tag: K }>) => Stream.Stream<A1, E1, R1>
) => Stream.Stream<A | A1, Exclude<E, { _tag: K }> | E1, R | R1>
>(3, (self, k, f) =>
catchAll(self, (e) => {
if ("_tag" in e && e["_tag"] === k) {
return f(e as any)
}
return fail(e as any)
}))
/** @internal */
export const catchTags: {
<
E extends { _tag: string },
Cases extends {
[K in E["_tag"]]+?: (error: Extract<E, { _tag: K }>) => Stream.Stream<any, any, any>
}
>(
cases: Cases
): <A, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<
| A
| {
[K in keyof Cases]: Cases[K] extends
((...args: Array<any>) => Stream.Stream.Variance<infer A, infer _E, infer _R>) ? A
: never
}[keyof Cases],
| Exclude<E, { _tag: keyof Cases }>
| {
[K in keyof Cases]: Cases[K] extends
((...args: Array<any>) => Stream.Stream.Variance<infer _A, infer E, infer _R>) ? E
: never
}[keyof Cases],
| R
| {
[K in keyof Cases]: Cases[K] extends
((...args: Array<any>) => Stream.Stream.Variance<infer _A, infer _E, infer R>) ? R
: never
}[keyof Cases]
>
<
A,
E extends { _tag: string },
R,
Cases extends {
[K in E["_tag"]]+?: (error: Extract<E, { _tag: K }>) => Stream.Stream<any, any, any>
}
>(
self: Stream.Stream<A, E, R>,
cases: Cases
): Stream.Stream<
| A
| {
[K in keyof Cases]: Cases[K] extends
((...args: Array<any>) => Stream.Stream.Variance<infer _R, infer _E, infer A>) ? A
: never
}[keyof Cases],
| Exclude<E, { _tag: keyof Cases }>
| {
[K in keyof Cases]: Cases[K] extends
((...args: Array<any>) => Stream.Stream.Variance<infer _R, infer E, infer _A>) ? E
: never
}[keyof Cases],
| R
| {
[K in keyof Cases]: Cases[K] extends
((...args: Array<any>) => Stream.Stream.Variance<infer R, infer _E, infer _A>) ? R
: never
}[keyof Cases]
>
} = dual(2, (self, cases) =>
catchAll(self, (e: any) => {
const keys = Object.keys(cases)
if ("_tag" in e && keys.includes(e["_tag"])) {
return cases[e["_tag"]](e as any)
}
return fail(e as any)
}))
/** @internal */
export const changes = <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> =>
pipe(self, changesWith((x, y) => Equal.equals(y)(x)))
/** @internal */
export const changesWith = dual<
<A>(f: (x: A, y: A) => boolean) => <E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, E, R>,
<A, E, R>(self: Stream.Stream<A, E, R>, f: (x: A, y: A) => boolean) => Stream.Stream<A, E, R>
>(2, <A, E, R>(self: Stream.Stream<A, E, R>, f: (x: A, y: A) => boolean): Stream.Stream<A, E, R> => {
const writer = (
last: Option.Option<A>
): Channel.Channel<Chunk.Chunk<A>, Chunk.Chunk<A>, E, E, void, unknown> =>
core.readWithCause({
onInput: (input: Chunk.Chunk<A>) => {
const [newLast, newChunk] = Chunk.reduce(
input,
[last, Chunk.empty<A>()] as const,
([option, outputs], output) => {
if (Option.isSome(option) && f(option.value, output)) {
return [Option.some(output), outputs] as const
}
return [Option.some(output), pipe(outputs, Chunk.append(output))] as const
}
)
return core.flatMap(
core.write(newChunk),
() => writer(newLast)
)
},
onFailure: core.failCause,
onDone: () => core.unit
})
return new StreamImpl(pipe(toChannel(self), core.pipeTo(writer(Option.none()))))
})
/** @internal */
export const changesWithEffect = dual<
<A, E2, R2>(
f: (x: A, y: A) => Effect.Effect<boolean, E2, R2>
) => <E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A, E2 | E, R2 | R>,
<A, E, R, E2, R2>(
self: Stream.Stream<A, E, R>,
f: (x: A, y: A) => Effect.Effect<boolean, E2, R2>
) => Stream.Stream<A, E2 | E, R2 | R>
>(
2,
<A, E, R, E2, R2>(
self: Stream.Stream<A, E, R>,
f: (x: A, y: A) => Effect.Effect<boolean, E2, R2>
): Stream.Stream<A, E2 | E, R2 | R> => {
const writer = (
last: Option.Option<A>
): Channel.Channel<Chunk.Chunk<A>, Chunk.Chunk<A>, E | E2, E | E2, void, unknown, R | R2> =>
core.readWithCause({
onInput: (input: Chunk.Chunk<A>) =>
pipe(
input,
Effect.reduce([last, Chunk.empty<A>()] as const, ([option, outputs], output) => {
if (Option.isSome(option)) {
return pipe(
f(option.value, output),
Effect.map((bool) =>
bool ?
[Option.some(output), outputs] as const :
[Option.some(output), pipe(outputs, Chunk.append(output))] as const
)
)
}
return Effect.succeed(
[
Option.some(output),
pipe(outputs, Chunk.append(output))
] as const
)
}),
core.fromEffect,
core.flatMap(([newLast, newChunk]) =>
pipe(
core.write(newChunk),
core.flatMap(() => writer(newLast))
)
)
),
onFailure: core.failCause,
onDone: () => core.unit
})
return new StreamImpl(pipe(toChannel(self), core.pipeTo(writer(Option.none()))))
}
)
/** @internal */
export const chunks = <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<Chunk.Chunk<A>, E, R> =>
pipe(self, mapChunks(Chunk.of))
/** @internal */
export const chunksWith = dual<
<A, E, R, A2, E2, R2>(
f: (stream: Stream.Stream<Chunk.Chunk<A>, E, R>) => Stream.Stream<Chunk.Chunk<A2>, E2, R2>
) => (self: Stream.Stream<A, E, R>) => Stream.Stream<A2, E | E2, R | R2>,
<A, E, R, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
f: (stream: Stream.Stream<Chunk.Chunk<A>, E, R>) => Stream.Stream<Chunk.Chunk<A2>, E2, R2>
) => Stream.Stream<A2, E | E2, R | R2>
>(
2,
<A, E, R, A2, E2, R2>(
self: Stream.Stream<A, E, R>,
f: (stream: Stream.Stream<Chunk.Chunk<A>, E, R>) => Stream.Stream<Chunk.Chunk<A2>, E2, R2>
): Stream.Stream<A2, E | E2, R | R2> => flattenChunks(f(chunks(self)))
)
const unsome = <A, E, R>(effect: Effect.Effect<A, Option.Option<E>, R>): Effect.Effect<Option.Option<A>, E, R> =>
Effect.catchAll(
Effect.asSome(effect),
(o) => o._tag === "None" ? Effect.succeedNone : Effect.fail(o.value)
)
/** @internal */
export const combine = dual<
<A2, E2, R2, S, R3, E, A, R4, R5, A3>(
that: Stream.Stream<A2, E2, R2>,
s: S,
f: (
s: S,
pullLeft: Effect.Effect<A, Option.Option<E>, R3>,
pullRight: Effect.Effect<A2, Option.Option<E2>, R4>
) => Effect.Effect<Exit.Exit<readonly [A3, S], Option.Option<E2 | E>>, never, R5>
) => <R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A3, E2 | E, R2 | R3 | R4 | R5 | R>,
<R, A2, E2, R2, S, R3, E, A, R4, R5, A3>(
self: Stream.Stream<A, E, R>,
that: Stream.Stream<A2, E2, R2>,
s: S,
f: (
s: S,
pullLeft: Effect.Effect<A, Option.Option<E>, R3>,
pullRight: Effect.Effect<A2, Option.Option<E2>, R4>
) => Effect.Effect<Exit.Exit<readonly [A3, S], Option.Option<E2 | E>>, never, R5>
) => Stream.Stream<A3, E2 | E, R2 | R3 | R4 | R5 | R>
>(4, <R, A2, E2, R2, S, R3, E, A, R4, R5, A3>(
self: Stream.Stream<A, E, R>,
that: Stream.Stream<A2, E2, R2>,
s: S,
f: (
s: S,
pullLeft: Effect.Effect<A, Option.Option<E>, R3>,
pullRight: Effect.Effect<A2, Option.Option<E2>, R4>
) => Effect.Effect<Exit.Exit<readonly [A3, S], Option.Option<E2 | E>>, never, R5>
): Stream.Stream<A3, E2 | E, R2 | R3 | R4 | R5 | R> => {
const producer = <Err, Elem>(
handoff: Handoff.Handoff<Exit.Exit<Elem, Option.Option<Err>>>,
latch: Handoff.Handoff<void>
): Channel.Channel<never, Elem, never, Err, unknown, unknown, R> =>
pipe(
core.fromEffect(Handoff.take(latch)),
channel.zipRight(core.readWithCause({
onInput: (input) =>
core.flatMap(
core.fromEffect(pipe(
handoff,
Handoff.offer<Exit.Exit<Elem, Option.Option<Err>>>(Exit.succeed(input))
)),
() => producer(handoff, latch)
),
onFailure: (cause) =>
core.fromEffect(
Handoff.offer<Exit.Exit<Elem, Option.Option<Err>>>(
handoff,
Exit.failCause(pipe(cause, Cause.map(Option.some)))
)
),
onDone: () =>
core.flatMap(
core.fromEffect(
Handoff.offer<Exit.Exit<Elem, Option.Option<Err>>>(
handoff,
Exit.fail(Option.none())
)
),
() => producer(handoff, latch)
)
}))
)
return new StreamImpl(
channel.unwrapScoped(
Effect.gen(function*($) {
const left = yield* $(Handoff.make<Exit.Exit<A, Option.Option<E>>>())
const right = yield* $(Handoff.make<Exit.Exit<A2, Option.Option<E2>>>())
const latchL = yield* $(Handoff.make<void>())
const latchR = yield* $(Handoff.make<void>())
yield* $(
toChannel(self),
channel.concatMap(channel.writeChunk),
core.pipeTo(producer(left, latchL)),
channelExecutor.runScoped,
Effect.forkScoped
)
yield* $(