UNPKG

the-moby-effect

Version:
136 lines 6.83 kB
import * as Channel from "effect/Channel"; import * as Chunk from "effect/Chunk"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Filter from "effect/Filter"; import * as Function from "effect/Function"; import * as Pipeable from "effect/Pipeable"; import * as Predicate from "effect/Predicate"; import * as Schedule from "effect/Schedule"; import * as Schema from "effect/Schema"; 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 * as internalCompressed from "./compressed.js"; /** @internal */ export const MultiplexedContentType = "application/vnd.docker.multiplexed-stream"; /** @internal */ export const MultiplexedSocketTypeId = /*#__PURE__*/Symbol.for("the-moby-effect/demux/MultiplexedSocket"); /** @internal */ export const MultiplexedChannelTypeId = /*#__PURE__*/Symbol.for("the-moby-effect/demux/MultiplexedChannel"); /** @internal */ export const makeMultiplexedSocket = underlying => ({ underlying, "content-type": MultiplexedContentType, [MultiplexedSocketTypeId]: MultiplexedSocketTypeId, pipe() { return Pipeable.pipeArguments(this, arguments); } }); /** @internal */ export const makeMultiplexedChannel = underlying => ({ underlying, "content-type": MultiplexedContentType, [MultiplexedChannelTypeId]: MultiplexedChannelTypeId, pipe() { return Pipeable.pipeArguments(this, arguments); } }); /** @internal */ export const isMultiplexedSocket = u => Predicate.hasProperty(u, MultiplexedSocketTypeId); /** @internal */ export const isMultiplexedChannel = u => Predicate.hasProperty(u, MultiplexedChannelTypeId); /** @internal */ export const never = /*#__PURE__*/makeMultiplexedChannel(Channel.never); /** @internal */ export const neverWith = () => makeMultiplexedChannel(Channel.never); /** @internal */ export const responseIsMultiplexedResponse = response => response.headers["content-type"] === MultiplexedContentType; /** @internal */ export const MultiplexedHeaderType = { Stdin: 0, Stdout: 1, Stderr: 2 }; /** @internal */ export const MultiplexedSchema = /*#__PURE__*/Schema.Tuple([/*#__PURE__*/Schema.Enum(MultiplexedHeaderType), Schema.Uint8Array]); /** @internal */ export const asMultiplexedChannel = input => isMultiplexedSocket(input) ? makeMultiplexedChannel(Socket.toChannel(input.underlying)) : input; /** @internal */ export const multiplexedToStream = input => Stream.pipeThroughChannelOrFail(Stream.empty, asMultiplexedChannel(input).underlying); /** @internal */ export const multiplexedToSink = input => Sink.fromChannel(Function.pipe(asMultiplexedChannel(input).underlying, Channel.drain, Channel.mapDone(() => [void 0]))); /** @internal */ export const multiplexedFromStreamWith = () => input => makeMultiplexedChannel(Stream.toChannel(input)); /** @internal */ export const multiplexedFromStream = input => multiplexedFromStreamWith()(input); // /** @internal */ // export const multiplexedFromSink = <E, R>( // input: Sink.Sink<void, string | Uint8Array | Socket.CloseEvent, Uint8Array, E, R> // ): MobyDemux.MultiplexedChannel<never, E, R> => makeMultiplexedChannel<never, E, R>(Sink.toChannel(input)); /** @internal */ export const demuxMultiplexedFolderSink = /*#__PURE__*/Sink.reduceWhile(() => ({ headerBytesRead: 0, messageBytesRead: 0, headerBuffer: Chunk.empty(), messageBuffer: Chunk.empty(), messageSize: undefined, messageType: undefined }), ({ messageBytesRead, messageSize }) => messageSize === undefined || messageBytesRead < messageSize, (accumulator, input) => { // If we have not read the entire header yet if (accumulator.headerBytesRead < 8) { return { ...accumulator, headerBytesRead: accumulator.headerBytesRead + 1, headerBuffer: Chunk.append(accumulator.headerBuffer, input) }; } // Lazily evaluate the header and message size const uint8Array = () => new Uint8Array(Chunk.toReadonlyArray(accumulator.headerBuffer)); const type = () => accumulator.messageType ?? new DataView(uint8Array().buffer).getUint8(0); const size = () => accumulator.messageSize ?? new DataView(uint8Array().buffer).getUint32(4); // We can parse the header when we've read all 8 bytes and haven't parsed it yet const needToParseHeader = accumulator.headerBytesRead === 8 && Predicate.isUndefined(accumulator.messageType) && Predicate.isUndefined(accumulator.messageSize); // Otherwise, if we have read the entire header but not parsed it yet const parsedHeader = needToParseHeader ? { messageType: type(), messageSize: size() } : {}; return { ...accumulator, ...parsedHeader, messageBytesRead: accumulator.messageBytesRead + 1, messageBuffer: Chunk.append(accumulator.messageBuffer, input) }; }); /** @internal */ export const aggregate = multiplexedStream => Function.pipe(multiplexedStream, Stream.flattenIterable, Stream.aggregateWithin(demuxMultiplexedFolderSink, Schedule.fixed(Duration.infinity)), Stream.map(({ messageBuffer, messageType }) => Tuple.make(messageType, new Uint8Array(Chunk.toReadonlyArray(messageBuffer)))), Stream.mapEffect(multiplexed => Schema.decodeUnknownEffect(MultiplexedSchema)(multiplexed))); /** @internal */ export const demuxMultiplexedToSingleSink = /*#__PURE__*/Function.dual(arguments_ => isMultiplexedSocket(arguments_[0]) || isMultiplexedChannel(arguments_[0]), (socket, source, sink, options) => { const { underlying } = asMultiplexedChannel(socket); const textDecoder = new TextDecoder(options?.encoding); const toStrings = ([messageType, bytes]) => Tuple.make(messageType, textDecoder.decode(bytes, { stream: true })); return Function.pipe(source, Stream.pipeThroughChannelOrFail(underlying), aggregate, Stream.map(toStrings), Stream.run(sink)); }); /** @internal */ export const demuxMultiplexedToSeparateSinks = /*#__PURE__*/Function.dual(arguments_ => isMultiplexedSocket(arguments_[0]) || isMultiplexedChannel(arguments_[0]), (socket, source, sink1, sink2, options) => Function.pipe(source, Stream.pipeThroughChannelOrFail(asMultiplexedChannel(socket).underlying), aggregate, Stream.partition(Filter.fromPredicate(([messageType]) => messageType === MultiplexedHeaderType.Stderr), { bufferSize: options?.bufferSize ?? 16 }), Effect.map(([stdoutStream, stderrStream]) => [Function.pipe(stdoutStream, Stream.map(Tuple.get(1)), Stream.decodeText({ encoding: options?.encoding }), Stream.run(sink1)), Function.pipe(stderrStream, Stream.map(Tuple.get(1)), Stream.decodeText({ encoding: options?.encoding }), Stream.run(sink2))]), Effect.flatMap(streamers => Effect.all(streamers, { concurrency: 2 })), Effect.map(internalCompressed.compressDemuxOutput), Effect.scoped)); //# sourceMappingURL=multiplexed.js.map