UNPKG

the-moby-effect

Version:
423 lines (383 loc) 15 kB
import type * as Array from "effect/Array"; import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"; import * as Channel from "effect/Channel"; import * as Effect from "effect/Effect"; import * as Function from "effect/Function"; import * as Pipeable from "effect/Pipeable"; import * as Predicate from "effect/Predicate"; import * as Sink from "effect/Sink"; import * as Stream from "effect/Stream"; import * as Tuple from "effect/Tuple"; import * as Socket from "effect/unstable/socket/Socket"; import type * as MobyDemux from "../../MobyDemux.js"; import * as internalCompressed from "./compressed.js"; /** @internal */ export const RawContentType = "application/vnd.docker.raw-stream" as const; /** @internal */ export const RawSocketTypeId: MobyDemux.RawSocketTypeId = Symbol.for( "the-moby-effect/demux/RawSocket" ) as MobyDemux.RawSocketTypeId; /** @internal */ export const RawChannelTypeId: MobyDemux.RawChannelTypeId = Symbol.for( "the-moby-effect/demux/RawChannel" ) as MobyDemux.RawChannelTypeId; /** @internal */ export const makeRawSocket = (underlying: Socket.Socket): MobyDemux.RawSocket => ({ underlying, "content-type": RawContentType, [RawSocketTypeId]: RawSocketTypeId, pipe() { return Pipeable.pipeArguments(this, arguments); }, }); /** @internal */ export const makeRawChannel = <IE = unknown, OE = Socket.SocketError, R = never>( underlying: Channel.Channel< Array.NonEmptyReadonlyArray<Uint8Array>, OE, void, Array.NonEmptyReadonlyArray<string | Uint8Array | Socket.CloseEvent>, IE, unknown, R > ): MobyDemux.RawChannel<IE, IE | OE, R> => ({ underlying, "content-type": RawContentType, [RawChannelTypeId]: RawChannelTypeId, pipe() { return Pipeable.pipeArguments(this, arguments); }, }); /** @internal */ export const isRawSocket = (u: unknown): u is MobyDemux.RawSocket => Predicate.hasProperty(u, RawSocketTypeId); /** @internal */ export const isRawChannel = <IE = unknown, OE = Socket.SocketError, R = never>( u: unknown ): u is MobyDemux.RawChannel<IE, IE | OE, R> => Predicate.hasProperty(u, RawChannelTypeId); /** @internal */ export const never: MobyDemux.RawChannel<never, never, never> = makeRawChannel<never, never, never>(Channel.never); /** @internal */ export const neverWith = <IE>(): MobyDemux.RawChannel<IE, IE, never> => makeRawChannel<IE, never, never>(Channel.never); /** @internal */ export const responseIsRawResponse = (response: HttpClientResponse.HttpClientResponse): boolean => response.headers["content-type"] === RawContentType; /** @internal */ export const asRawChannel = <IE = never, OE = Socket.SocketError, R = never>( input: MobyDemux.EitherRawInput<IE, OE, R> ): MobyDemux.RawChannel<IE, OE, R> => isRawSocket(input) ? (makeRawChannel(Socket.toChannel<IE>(input.underlying)) as unknown as MobyDemux.RawChannel<IE, OE, R>) : (input as MobyDemux.RawChannel<IE, OE, R>); /** @internal */ export const rawToStream = <IE = never, OE = Socket.SocketError, R = never>( input: MobyDemux.EitherRawInput<IE, OE, R> ): Stream.Stream<Uint8Array, IE | OE, R> => Stream.pipeThroughChannelOrFail(Stream.empty, asRawChannel(input).underlying); /** @internal */ export const rawToSink = <IE = never, OE = Socket.SocketError, R = never>( input: MobyDemux.EitherRawInput<IE, OE, R> ): Sink.Sink<void, string | Uint8Array | Socket.CloseEvent, Uint8Array, IE | OE, R> => Sink.fromChannel( Function.pipe( asRawChannel(input).underlying, Channel.drain, Channel.mapDone(() => [void 0] as const) ) ); /** @internal */ export const rawFromStreamWith = <IE>() => <E, R>(input: Stream.Stream<Uint8Array, IE | E, R>): MobyDemux.RawChannel<IE, IE | E, R> => makeRawChannel<IE, IE | E, R>(Stream.toChannel(input)); /** @internal */ export const rawFromStream = <E, R>(input: Stream.Stream<Uint8Array, E, R>): MobyDemux.RawChannel<never, E, R> => rawFromStreamWith<never>()(input); // /** @internal */ // export const rawFromSink = <E, R>( // input: Sink.Sink<void, string | Uint8Array | Socket.CloseEvent, Uint8Array, E, R> // ): MobyDemux.RawChannel<never, E, R> => makeRawChannel<never, E, R>(Sink.toChannel(input)); /** @internal */ export const interleaveRaw = < IE1 = never, IE2 = never, OE1 = Socket.SocketError, OE2 = Socket.SocketError, R1 = never, R2 = never, >( stdout: MobyDemux.EitherRawInput<IE1, OE1, R1>, stderr: MobyDemux.EitherRawInput<IE2, OE2, R2> ): Stream.Stream<Uint8Array, IE1 | IE2 | OE1 | OE2, R1 | R2> => { return Stream.interleave(rawToStream(stdout), rawToStream(stderr)); }; /** @internal */ export const mergeRawToTaggedStream = < IE1 = never, IE2 = never, OE1 = Socket.SocketError, OE2 = Socket.SocketError, R1 = never, R2 = never, >( stdout: MobyDemux.EitherRawInput<IE1, OE1, R1>, stderr: MobyDemux.EitherRawInput<IE2, OE2, R2>, options?: { bufferSize?: number | undefined } | undefined ): Stream.Stream< { _tag: "stdout"; value: Uint8Array } | { _tag: "stderr"; value: Uint8Array }, IE1 | IE2 | OE1 | OE2, R1 | R2 > => Function.pipe( Stream.merge( Stream.map(rawToStream(stdout), (value) => ({ _tag: "stdout", value }) as const), Stream.map(rawToStream(stderr), (value) => ({ _tag: "stderr", value }) as const), { haltStrategy: "both" } ), Stream.buffer({ capacity: options?.bufferSize ?? 16 }) ); /** @internal */ export const demuxRawToSingleSink = Function.dual< // Data-last signature. <A1, L1, E1, E2, R1, R2>( source: Stream.Stream<string | Uint8Array | Socket.CloseEvent, E1, R1>, sink: Sink.Sink<A1, string, L1, E2, R2>, options?: { encoding?: string | undefined } | undefined ) => <IE = never, OE = Socket.SocketError, R3 = never>( socket: MobyDemux.EitherRawInput<E1 | IE, OE, R3> ) => Effect.Effect<A1, E1 | E2 | IE | OE, R1 | R2 | R3>, // Data-first signature. <A1, L1, E1, E2, R1, R2, IE = never, OE = Socket.SocketError, R3 = never>( socket: MobyDemux.EitherRawInput<E1 | IE, OE, R3>, source: Stream.Stream<string | Uint8Array | Socket.CloseEvent, E1, R1>, sink: Sink.Sink<A1, string, L1, E2, R2>, options?: { encoding?: string | undefined } | undefined ) => Effect.Effect<A1, E1 | E2 | IE | OE, R1 | R2 | R3> >( (arguments_) => isRawSocket(arguments_[0]) || isRawChannel(arguments_[0]), (socket, source, sink, options) => Function.pipe( source, Stream.pipeThroughChannelOrFail(asRawChannel(socket).underlying), Stream.decodeText({ encoding: options?.encoding }), Stream.run(sink) ) ); /** @internal */ export const demuxStdioRawTupled: < A1 = void, A2 = void, L1 = never, L2 = never, E1 = never, E2 = never, E3 = never, R1 = never, R2 = never, R3 = never, IE1 = never, IE2 = never, IE3 = never, OE1 = Socket.SocketError, OE2 = Socket.SocketError, OE3 = Socket.SocketError, R4 = never, R5 = never, R6 = never, >( sockets: MobyDemux.HeterogeneousStdioTupledRawInput< A1, A2, L1, L2, E1, E2, E3, R1, R2, R3, IE1, IE2, IE3, OE1, OE2, OE3, R4, R5, R6 >, options?: { encoding?: string | undefined } | undefined ) => Effect.Effect< MobyDemux.CompressedDemuxOutput<A1, A2>, E1 | E2 | E3 | IE1 | IE2 | IE3 | OE1 | OE2 | OE3, R1 | R2 | R3 | R4 | R5 | R6 > = Effect.fnUntraced(function* (sockets, options) { const { stderr, stdin, stdout } = sockets; const stdinSocketObject = Predicate.isNotUndefined(stdin) ? { stdin: stdin?.[1] } : {}; const stdoutSocketObject = Predicate.isNotUndefined(stdout) ? { stdout: stdout?.[0] } : {}; const stderrSocketObject = Predicate.isNotUndefined(stderr) ? { stderr: stderr?.[0] } : {}; const stdinIoObject = Predicate.isNotUndefined(stdin) ? { stdin: stdin?.[0] } : { stdin: Stream.empty as Stream.Stream<never, never, never> }; const stdoutIoObject = Predicate.isNotUndefined(stdout) ? { stdout: stdout?.[1] } : { stdout: Sink.succeed<void>(void 0) as unknown as Sink.Sink<never, string, never, never, never> }; const stderrIoObject = Predicate.isNotUndefined(stderr) ? { stderr: stderr?.[1] } : { stderr: Sink.succeed<void>(void 0) as unknown as Sink.Sink<never, string, never, never, never> }; // `sockets` guarantees at least one of stdin/stdout/stderr is defined, but spreading the // three conditional objects widens to a union that also includes the all-absent case, which // HeterogeneousStdioRawInput deliberately does not model. TypeScript 7 tracks this precisely // where 5.9 did not, so restate the invariant here. const rawSockets = { ...stdinSocketObject, ...stdoutSocketObject, ...stderrSocketObject } as Exclude< typeof stdinSocketObject & typeof stdoutSocketObject & typeof stderrSocketObject, Record<string, never> >; return yield* demuxStdioRawToSeparateSinks( rawSockets, { ...stdinIoObject, ...stdoutIoObject, ...stderrIoObject }, options ); }); /** @internal */ export const demuxStdioRawToSingleSink = Function.dual< // Single sink, data-last signature. <A1, L1, E1, E2, R1, R2>( source: Stream.Stream<string | Uint8Array | Socket.CloseEvent, E1, R1>, sink: Sink.Sink<A1, string, L1, E2, R2>, options?: { encoding?: string | undefined } | undefined ) => < IE1 = never, IE2 = never, IE3 = never, OE1 = Socket.SocketError, OE2 = Socket.SocketError, OE3 = Socket.SocketError, R3 = never, R4 = never, R5 = never, >( sockets: MobyDemux.HeterogeneousStdioRawInput<IE1 | E1, IE2, IE3, OE1, OE2, OE3, R3, R4, R5> ) => Effect.Effect<A1, E1 | E2 | IE1 | IE2 | IE3 | OE1 | OE2 | OE3, R1 | R2 | R3 | R4 | R5>, // Single sink, data-first signature. < A1, L1, E1, E2, R1, R2, IE1 = never, IE2 = never, IE3 = never, OE1 = Socket.SocketError, OE2 = Socket.SocketError, OE3 = Socket.SocketError, R3 = never, R4 = never, R5 = never, >( sockets: MobyDemux.HeterogeneousStdioRawInput<IE1 | E1, IE2, IE3, OE1, OE2, OE3, R3, R4, R5>, source: Stream.Stream<string | Uint8Array | Socket.CloseEvent, E1, R1>, sink: Sink.Sink<A1, string, L1, E2, R2>, options?: { encoding?: string | undefined } | undefined ) => Effect.Effect<A1, E1 | E2 | IE1 | IE2 | IE3 | OE1 | OE2 | OE3, R1 | R2 | R3 | R4 | R5> >( (arguments_) => "stdin" in arguments_[0] || "stdout" in arguments_[0] || "stderr" in arguments_[0], Effect.fnUntraced(function* (sockets, source, sink, options) { const { stderr, stdin, stdout } = sockets; const stdoutStream = Predicate.isUndefined(stdout) ? Stream.empty : rawToStream(stdout); const stderrStream = Predicate.isUndefined(stderr) ? Stream.empty : rawToStream(stderr); const runInput = Predicate.isNotUndefined(stdin) ? demuxRawToSingleSink(stdin, source, Sink.drain, options) : Stream.run(source, Sink.drain); const runOutput = Stream.interleave(stdoutStream, stderrStream).pipe( Stream.decodeText({ encoding: options?.encoding }), Stream.run(sink) ); return yield* Effect.map( Effect.all({ ranStdin: runInput, ranOutput: runOutput }, { concurrency: 2 }), ({ ranOutput }) => ranOutput ); }) ); /** @internal */ export const demuxStdioRawToSeparateSinks = Function.dual< // Multiple sinks, data-last signature. <A1, A2, L1, L2, E1, E2, E3, R1, R2, R3>( io: { stdin: Stream.Stream<string | Uint8Array | Socket.CloseEvent, E1, R1>; stdout: Sink.Sink<A1, string, L1, E2, R2>; stderr: Sink.Sink<A2, string, L2, E3, R3>; }, options?: { encoding?: string | undefined } | undefined ) => < IE1 = never, IE2 = never, IE3 = never, OE1 = Socket.SocketError, OE2 = Socket.SocketError, OE3 = Socket.SocketError, R4 = never, R5 = never, R6 = never, >( sockets: MobyDemux.HeterogeneousStdioRawInput<IE1 | E1, IE2, IE3, OE1, OE2, OE3, R4, R5, R6> ) => Effect.Effect< MobyDemux.CompressedDemuxOutput<A1, A2>, E1 | E2 | E3 | IE1 | IE2 | IE3 | OE1 | OE2 | OE3, R1 | R2 | R3 | R4 | R5 | R6 >, // Multiple sinks, data-first signature. < A1, A2, L1, L2, E1, E2, E3, R1, R2, R3, IE1 = never, IE2 = never, IE3 = never, OE1 = Socket.SocketError, OE2 = Socket.SocketError, OE3 = Socket.SocketError, R4 = never, R5 = never, R6 = never, >( sockets: MobyDemux.HeterogeneousStdioRawInput<IE1 | E1, IE2, IE3, OE1, OE2, OE3, R4, R5, R6>, io: { stdin: Stream.Stream<string | Uint8Array | Socket.CloseEvent, E1, R1>; stdout: Sink.Sink<A1, string, L1, E2, R2>; stderr: Sink.Sink<A2, string, L2, E3, R3>; }, options?: { encoding?: string | undefined } | undefined ) => Effect.Effect< MobyDemux.CompressedDemuxOutput<A1, A2>, E1 | E2 | E3 | IE1 | IE2 | IE3 | OE1 | OE2 | OE3, R1 | R2 | R3 | R4 | R5 | R6 > >( (arguments_) => !("stdin" in arguments_[0] && Stream.isStream(arguments_[0]["stdin"])), Effect.fnUntraced(function* (sockets, io, options) { const { stderr, stdin, stdout } = sockets; const runStdin = Predicate.isNotUndefined(stdin) ? demuxRawToSingleSink(stdin, io.stdin, Sink.drain, options) : Stream.run(io.stdin, Sink.succeed<void>(void 0)); const runStdout = Predicate.isNotUndefined(stdout) ? demuxRawToSingleSink(stdout, Stream.never, io.stdout, options) : Stream.run(Stream.empty, io.stdout); const runStderr = Predicate.isNotUndefined(stderr) ? demuxRawToSingleSink(stderr, Stream.never, io.stderr, options) : Stream.run(Stream.empty, io.stderr); return yield* Effect.map( Effect.all({ ranStdin: runStdin, ranStdout: runStdout, ranStderr: runStderr }, { concurrency: 3 }), ({ ranStderr, ranStdout }) => internalCompressed.compressDemuxOutput(Tuple.make(ranStdout, ranStderr)) ); }) );