the-moby-effect
Version:
Moby/Docker API client built using effect-ts
136 lines • 6.83 kB
JavaScript
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