UNPKG

the-moby-effect

Version:
125 lines (115 loc) 5.67 kB
import type * as Socket from "@effect/platform/Socket"; import type * as Chunk from "effect/Chunk"; import type * as ParseResult from "effect/ParseResult"; import type * as Scope from "effect/Scope"; import type * as MobyDemux from "../../MobyDemux.js"; import * as Channel from "effect/Channel"; import * as Effect from "effect/Effect"; import * as Either from "effect/Either"; import * as Exit from "effect/Exit"; import * as Function from "effect/Function"; import * as Option from "effect/Option"; import * as Queue from "effect/Queue"; import * as Sink from "effect/Sink"; import * as Stream from "effect/Stream"; import { demuxMultiplexedToSeparateSinks, isMultiplexedChannel, isMultiplexedSocket } from "./multiplexed.js"; import { makeRawChannel, rawFromStreamWith } from "./raw.js"; /** @internal */ export const fan = Function.dual< <IE = never, OE = Socket.SocketError, R = never>(options: { requestedCapacity: number; encoding?: string | undefined; }) => (multiplexedInput: MobyDemux.EitherMultiplexedInput<IE, OE, R>) => Effect.Effect< { stdin: MobyDemux.RawChannel<IE, IE | OE | ParseResult.ParseError, never>; stdout: MobyDemux.RawChannel<IE, IE | OE | ParseResult.ParseError, never>; stderr: MobyDemux.RawChannel<IE, IE | OE | ParseResult.ParseError, never>; }, never, Exclude<R, Scope.Scope> >, <IE = never, OE = Socket.SocketError, R = never>( multiplexedInput: MobyDemux.EitherMultiplexedInput<IE, OE, R>, options: { requestedCapacity: number; encoding?: string | undefined } ) => Effect.Effect< { stdin: MobyDemux.RawChannel<IE, IE | OE | ParseResult.ParseError, never>; stdout: MobyDemux.RawChannel<IE, IE | OE | ParseResult.ParseError, never>; stderr: MobyDemux.RawChannel<IE, IE | OE | ParseResult.ParseError, never>; }, never, Exclude<R, Scope.Scope> > >( (arguments_) => isMultiplexedChannel(arguments_[0]) || isMultiplexedSocket(arguments_[0]), Effect.fnUntraced(function* <IE = never, OE = Socket.SocketError, R = never>( multiplexedInput: MobyDemux.EitherMultiplexedInput<IE, OE, R>, options: { requestedCapacity: number; encoding?: string | undefined } ) { const mutex = yield* Effect.makeSemaphore(1); const context = yield* Effect.context<Exclude<R, Scope.Scope>>(); // Internal buffers const stdoutConsumerQueue = yield* Queue.bounded<string>(options.requestedCapacity); const stderrConsumerQueue = yield* Queue.bounded<string>(options.requestedCapacity); const stdinProducerQueue = yield* Queue.bounded< Either.Either<Chunk.Chunk<Uint8Array | string | Socket.CloseEvent>, Exit.Exit<void, IE>> >(options.requestedCapacity); // We MUST only touch the underlying once!!! Very important!!! // Demux everything to and fro the correct places. We can touch // this more than once because it is wrapped with a mutex. const motherChannel = demuxMultiplexedToSeparateSinks( multiplexedInput, Function.pipe( Stream.fromQueue(stdinProducerQueue, { shutdown: true }), Stream.map( Either.match({ onRight: Exit.succeed, onLeft: Function.flow( Exit.mapBoth({ onFailure: Option.some, onSuccess: Function.compose(Option.none, Exit.fail), }), Exit.flatten ), }) ), Stream.flattenExitOption, Stream.flattenChunks ), Sink.fromQueue(stdoutConsumerQueue, { shutdown: true }), Sink.fromQueue(stderrConsumerQueue, { shutdown: true }), { bufferSize: 32, encoding: options.encoding, } ) .pipe(mutex.withPermitsIfAvailable(1)) .pipe(Effect.asVoid) .pipe(Channel.fromEffect) .pipe(Channel.provideContext(context)); // Any one of these will kick off the mother demux, but only one will const independentStdinChannel = Channel.toQueue(stdinProducerQueue) .pipe(Channel.mapInput(Function.constVoid)) .pipe(Channel.zipLeft(motherChannel, { concurrent: true })) .pipe(makeRawChannel<IE, IE | OE | ParseResult.ParseError, never>); // Any one of these will kick off the mother demux, but only one will const independentStdoutChannel = Stream.fromQueue(stdoutConsumerQueue) .pipe(Stream.encodeText) .pipe(rawFromStreamWith<IE>()) .pipe(({ underlying }) => underlying) .pipe(Channel.zipLeft(motherChannel, { concurrent: true })) .pipe(makeRawChannel<IE, IE | OE | ParseResult.ParseError, never>); // Any one of these will kick off the mother demux, but only one will const independentStderrChannel = Stream.fromQueue(stderrConsumerQueue) .pipe(Stream.encodeText) .pipe(rawFromStreamWith<IE>()) .pipe(({ underlying }) => underlying) .pipe(Channel.zipLeft(motherChannel, { concurrent: true })) .pipe(makeRawChannel<IE, IE | OE | ParseResult.ParseError, never>); return { stdin: independentStdinChannel, stdout: independentStdoutChannel, stderr: independentStderrChannel, }; }) );