-module(packkit@stream).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/packkit/stream.gleam").
-export([new_deflate_decoder/0, new_zlib_decoder/0, new_gzip_decoder/0, new_lz4_decoder/0, new_snappy_decoder/0, new_bzip2_decoder/0, new_lzw_decoder/0, new_xz_decoder/0, new_zstd_decoder/0, new_brotli_decoder/0, with_limits/2, push/2, finish/1, decode_chunks/2, new_deflate_encoder/0, new_zlib_encoder/0, new_gzip_encoder/0, new_lz4_encoder/0, new_snappy_encoder/0, new_bzip2_encoder/0, new_lzw_encoder/0, new_xz_encoder/0, new_zstd_encoder/0, new_brotli_encoder/0, encoder_with_limits/2, push_encoder/2, finish_encoder/1, encode_chunks/2]).
-export_type([decoder/0, decoder_kind/0, encoder/0, encoder_kind/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Streaming helpers for codec families.\n"
"\n"
" `packkit/stream` exposes opaque decoder states that buffer input\n"
" chunks and run a one-shot decode at `finish` time. The API matches\n"
" the streaming shape the spec calls for - `new_*_decoder`, `push`,\n"
" `finish` - so callers can already wire incremental pipelines even\n"
" while the underlying codec decoders are still eager.\n"
"\n"
" **Relationship to `packkit/codec.Codec`.** The streaming\n"
" constructors (`new_gzip_encoder`, `new_zstd_decoder`, ...) do NOT\n"
" accept a `packkit/codec.Codec` value, so the per-codec `Level` /\n"
" preset `Dictionary` settings carried by a `Codec` are not threaded\n"
" through this API; every stream uses the codec's default encode\n"
" strategy. This is intentional: a `Codec` is the config object\n"
" `packkit.compress` / `packkit.decompress` (the one-shot facade)\n"
" consumes, while `packkit/stream` only ever invokes the codec\n"
" module's bare `encode/1` / `decode/1` entry points. Callers that\n"
" need a non-default level or a preset dictionary should call the\n"
" per-codec module directly (`gzip.encode_with_header`,\n"
" `zlib.encode_with_dictionary`, `deflate.encode_dynamic`, …) and\n"
" drive the chunking themselves until the streaming API grows\n"
" codec-typed constructors. The `Limits` value, by contrast, IS\n"
" honoured incrementally — see [push] / [push_encoder].\n"
).
-opaque decoder() :: {decoder,
decoder_kind(),
list(bitstring()),
integer(),
packkit@limit:limits()}.
-type decoder_kind() :: deflate |
zlib |
gzip |
lz4 |
snappy |
bzip2 |
lzw |
xz |
zstd |
brotli.
-opaque encoder() :: {encoder,
encoder_kind(),
list(bitstring()),
integer(),
packkit@limit:limits()}.
-type encoder_kind() :: enc_deflate |
enc_zlib |
enc_gzip |
enc_lz4 |
enc_snappy |
enc_bzip2 |
enc_lzw |
enc_xz |
enc_zstd |
enc_brotli.
-file("src/packkit/stream.gleam", 123).
-spec new_decoder(decoder_kind(), packkit@limit:limits()) -> decoder().
new_decoder(Kind, Limits) ->
{decoder, Kind, [], 0, Limits}.
-file("src/packkit/stream.gleam", 72).
?DOC(" Start a new incremental DEFLATE decoder using the default limits.\n").
-spec new_deflate_decoder() -> decoder().
new_deflate_decoder() ->
new_decoder(deflate, packkit@limit:default()).
-file("src/packkit/stream.gleam", 77).
?DOC(" Start a new incremental zlib decoder using the default limits.\n").
-spec new_zlib_decoder() -> decoder().
new_zlib_decoder() ->
new_decoder(zlib, packkit@limit:default()).
-file("src/packkit/stream.gleam", 82).
?DOC(" Start a new incremental gzip decoder using the default limits.\n").
-spec new_gzip_decoder() -> decoder().
new_gzip_decoder() ->
new_decoder(gzip, packkit@limit:default()).
-file("src/packkit/stream.gleam", 87).
?DOC(" Start a new incremental LZ4 frame decoder using the default limits.\n").
-spec new_lz4_decoder() -> decoder().
new_lz4_decoder() ->
new_decoder(lz4, packkit@limit:default()).
-file("src/packkit/stream.gleam", 93).
?DOC(
" Start a new incremental Snappy (framed) decoder using the default\n"
" limits. See [packkit/snappy] for the raw-block variants.\n"
).
-spec new_snappy_decoder() -> decoder().
new_snappy_decoder() ->
new_decoder(snappy, packkit@limit:default()).
-file("src/packkit/stream.gleam", 98).
?DOC(" Start a new incremental bzip2 decoder using the default limits.\n").
-spec new_bzip2_decoder() -> decoder().
new_bzip2_decoder() ->
new_decoder(bzip2, packkit@limit:default()).
-file("src/packkit/stream.gleam", 104).
?DOC(
" Start a new incremental Unix `.Z` (LZW) decoder using the default\n"
" limits.\n"
).
-spec new_lzw_decoder() -> decoder().
new_lzw_decoder() ->
new_decoder(lzw, packkit@limit:default()).
-file("src/packkit/stream.gleam", 109).
?DOC(" Start a new incremental xz decoder using the default limits.\n").
-spec new_xz_decoder() -> decoder().
new_xz_decoder() ->
new_decoder(xz, packkit@limit:default()).
-file("src/packkit/stream.gleam", 114).
?DOC(" Start a new incremental zstd decoder using the default limits.\n").
-spec new_zstd_decoder() -> decoder().
new_zstd_decoder() ->
new_decoder(zstd, packkit@limit:default()).
-file("src/packkit/stream.gleam", 119).
?DOC(" Start a new incremental brotli decoder using the default limits.\n").
-spec new_brotli_decoder() -> decoder().
new_brotli_decoder() ->
new_decoder(brotli, packkit@limit:default()).
-file("src/packkit/stream.gleam", 128).
?DOC(" Replace the limits used by an incremental decoder.\n").
-spec with_limits(decoder(), packkit@limit:limits()) -> decoder().
with_limits(Decoder, Limits) ->
{decoder,
erlang:element(2, Decoder),
erlang:element(3, Decoder),
erlang:element(4, Decoder),
Limits}.
-file("src/packkit/stream.gleam", 136).
?DOC(
" Append a chunk of input bytes to the decoder, enforcing\n"
" `max_input_bytes` incrementally. Returns a typed\n"
" `CodecLimitExceeded` if the running buffered byte count would\n"
" exceed the configured limit.\n"
).
-spec push(decoder(), bitstring()) -> {ok, decoder()} |
{error, packkit@error:codec_error()}.
push(Decoder, Chunk) ->
Chunk_size = erlang:byte_size(Chunk),
New_total = erlang:element(4, Decoder) + Chunk_size,
case New_total > packkit@limit:max_input_bytes(erlang:element(5, Decoder)) of
true ->
{error,
{codec_limit_exceeded, <<"max_input_bytes"/utf8>>, New_total}};
false ->
{ok,
{decoder,
erlang:element(2, Decoder),
[Chunk | erlang:element(3, Decoder)],
New_total,
erlang:element(5, Decoder)}}
end.
-file("src/packkit/stream.gleam", 160).
?DOC(" Finalize the decoder and return the full decoded payload.\n").
-spec finish(decoder()) -> {ok, bitstring()} |
{error, packkit@error:codec_error()}.
finish(Decoder) ->
Bytes = gleam_stdlib:bit_array_concat(
lists:reverse(erlang:element(3, Decoder))
),
case erlang:element(2, Decoder) of
deflate ->
packkit@deflate:decode_with_limits(
Bytes,
erlang:element(5, Decoder)
);
zlib ->
packkit@zlib:decode_with_limits(Bytes, erlang:element(5, Decoder));
gzip ->
_pipe = packkit@gzip:decode_with_limits(
Bytes,
erlang:element(5, Decoder)
),
gleam@result:map(
_pipe,
fun(Decoded) -> erlang:element(3, Decoded) end
);
lz4 ->
packkit@lz4:decode_with_limits(Bytes, erlang:element(5, Decoder));
snappy ->
packkit@snappy:decode_with_limits(Bytes, erlang:element(5, Decoder));
bzip2 ->
packkit@bzip2:decode_with_limits(Bytes, erlang:element(5, Decoder));
lzw ->
packkit@lzw:decode_with_limits(Bytes, erlang:element(5, Decoder));
xz ->
packkit@xz:decode_with_limits(Bytes, erlang:element(5, Decoder));
zstd ->
packkit@zstd:decode_with_limits(Bytes, erlang:element(5, Decoder));
brotli ->
packkit@brotli:decode_with_limits(Bytes, erlang:element(5, Decoder))
end.
-file("src/packkit/stream.gleam", 193).
-spec feed_all(decoder(), list(bitstring())) -> {ok, decoder()} |
{error, packkit@error:codec_error()}.
feed_all(Decoder, Chunks) ->
case Chunks of
[] ->
{ok, Decoder};
[Head | Rest] ->
gleam@result:'try'(
push(Decoder, Head),
fun(Next) -> feed_all(Next, Rest) end
)
end.
-file("src/packkit/stream.gleam", 185).
?DOC(
" Convenience helper that pushes every chunk through the decoder in\n"
" order and returns the final decoded payload. Surfaces the same\n"
" typed `CodecLimitExceeded` `push` would, so a long sequence of\n"
" chunks cannot silently overrun the input budget.\n"
).
-spec decode_chunks(decoder(), list(bitstring())) -> {ok, bitstring()} |
{error, packkit@error:codec_error()}.
decode_chunks(Decoder, Chunks) ->
gleam@result:'try'(feed_all(Decoder, Chunks), fun(Fed) -> finish(Fed) end).
-file("src/packkit/stream.gleam", 293).
-spec new_encoder(encoder_kind(), packkit@limit:limits()) -> encoder().
new_encoder(Kind, Limits) ->
{encoder, Kind, [], 0, Limits}.
-file("src/packkit/stream.gleam", 239).
?DOC(" Start a new incremental DEFLATE encoder using the default limits.\n").
-spec new_deflate_encoder() -> encoder().
new_deflate_encoder() ->
new_encoder(enc_deflate, packkit@limit:default()).
-file("src/packkit/stream.gleam", 244).
?DOC(" Start a new incremental zlib encoder using the default limits.\n").
-spec new_zlib_encoder() -> encoder().
new_zlib_encoder() ->
new_encoder(enc_zlib, packkit@limit:default()).
-file("src/packkit/stream.gleam", 252).
?DOC(
" Start a new incremental gzip encoder using the default limits.\n"
" The output uses `gzip.default_header()` — callers that need\n"
" per-stream metadata should keep using `gzip.encode` directly\n"
" until the streaming API grows an explicit header constructor.\n"
).
-spec new_gzip_encoder() -> encoder().
new_gzip_encoder() ->
new_encoder(enc_gzip, packkit@limit:default()).
-file("src/packkit/stream.gleam", 257).
?DOC(" Start a new incremental LZ4 frame encoder using the default limits.\n").
-spec new_lz4_encoder() -> encoder().
new_lz4_encoder() ->
new_encoder(enc_lz4, packkit@limit:default()).
-file("src/packkit/stream.gleam", 263).
?DOC(
" Start a new incremental Snappy (framed) encoder using the default\n"
" limits.\n"
).
-spec new_snappy_encoder() -> encoder().
new_snappy_encoder() ->
new_encoder(enc_snappy, packkit@limit:default()).
-file("src/packkit/stream.gleam", 268).
?DOC(" Start a new incremental bzip2 encoder using the default limits.\n").
-spec new_bzip2_encoder() -> encoder().
new_bzip2_encoder() ->
new_encoder(enc_bzip2, packkit@limit:default()).
-file("src/packkit/stream.gleam", 274).
?DOC(
" Start a new incremental Unix `.Z` (LZW) encoder using the default\n"
" limits.\n"
).
-spec new_lzw_encoder() -> encoder().
new_lzw_encoder() ->
new_encoder(enc_lzw, packkit@limit:default()).
-file("src/packkit/stream.gleam", 279).
?DOC(" Start a new incremental xz encoder using the default limits.\n").
-spec new_xz_encoder() -> encoder().
new_xz_encoder() ->
new_encoder(enc_xz, packkit@limit:default()).
-file("src/packkit/stream.gleam", 284).
?DOC(" Start a new incremental zstd encoder using the default limits.\n").
-spec new_zstd_encoder() -> encoder().
new_zstd_encoder() ->
new_encoder(enc_zstd, packkit@limit:default()).
-file("src/packkit/stream.gleam", 289).
?DOC(" Start a new incremental brotli encoder using the default limits.\n").
-spec new_brotli_encoder() -> encoder().
new_brotli_encoder() ->
new_encoder(enc_brotli, packkit@limit:default()).
-file("src/packkit/stream.gleam", 298).
?DOC(" Replace the limits used by an incremental encoder.\n").
-spec encoder_with_limits(encoder(), packkit@limit:limits()) -> encoder().
encoder_with_limits(Encoder, Limits) ->
{encoder,
erlang:element(2, Encoder),
erlang:element(3, Encoder),
erlang:element(4, Encoder),
Limits}.
-file("src/packkit/stream.gleam", 308).
?DOC(
" Append a chunk of plaintext bytes to the encoder, enforcing\n"
" `max_input_bytes` incrementally so an unbounded producer can't\n"
" trigger an unbounded `encode` allocation at `finish_encoder` time.\n"
).
-spec push_encoder(encoder(), bitstring()) -> {ok, encoder()} |
{error, packkit@error:codec_error()}.
push_encoder(Encoder, Chunk) ->
Chunk_size = erlang:byte_size(Chunk),
New_total = erlang:element(4, Encoder) + Chunk_size,
case New_total > packkit@limit:max_input_bytes(erlang:element(5, Encoder)) of
true ->
{error,
{codec_limit_exceeded, <<"max_input_bytes"/utf8>>, New_total}};
false ->
{ok,
{encoder,
erlang:element(2, Encoder),
[Chunk | erlang:element(3, Encoder)],
New_total,
erlang:element(5, Encoder)}}
end.
-file("src/packkit/stream.gleam", 336).
?DOC(
" Finalize the encoder and return the compressed payload. Each\n"
" codec is invoked through its default `encode/1` form; advanced\n"
" per-codec options (gzip header metadata, deflate stored blocks,\n"
" lz4 content size, bzip2 level) are not exposed through the\n"
" streaming wrapper and remain accessible via the per-codec module.\n"
).
-spec finish_encoder(encoder()) -> {ok, bitstring()} |
{error, packkit@error:codec_error()}.
finish_encoder(Encoder) ->
Bytes = gleam_stdlib:bit_array_concat(
lists:reverse(erlang:element(3, Encoder))
),
case erlang:element(2, Encoder) of
enc_deflate ->
packkit@deflate:encode(Bytes);
enc_zlib ->
packkit@zlib:encode(Bytes);
enc_gzip ->
packkit@gzip:encode(Bytes);
enc_lz4 ->
packkit@lz4:encode(Bytes);
enc_snappy ->
packkit@snappy:encode(Bytes);
enc_bzip2 ->
packkit@bzip2:encode(Bytes);
enc_lzw ->
packkit@lzw:encode(Bytes);
enc_xz ->
packkit@xz:encode(Bytes);
enc_zstd ->
packkit@zstd:encode(Bytes);
enc_brotli ->
packkit@brotli:encode(Bytes)
end.
-file("src/packkit/stream.gleam", 364).
-spec feed_encoder_all(encoder(), list(bitstring())) -> {ok, encoder()} |
{error, packkit@error:codec_error()}.
feed_encoder_all(Encoder, Chunks) ->
case Chunks of
[] ->
{ok, Encoder};
[Head | Rest] ->
gleam@result:'try'(
push_encoder(Encoder, Head),
fun(Next) -> feed_encoder_all(Next, Rest) end
)
end.
-file("src/packkit/stream.gleam", 356).
?DOC(
" Convenience helper that pushes every plaintext chunk through the\n"
" encoder in order and returns the final compressed payload. Mirrors\n"
" `decode_chunks` so callers can drive both directions with the same\n"
" shape (list of input chunks → single output buffer).\n"
).
-spec encode_chunks(encoder(), list(bitstring())) -> {ok, bitstring()} |
{error, packkit@error:codec_error()}.
encode_chunks(Encoder, Chunks) ->
gleam@result:'try'(
feed_encoder_all(Encoder, Chunks),
fun(Fed) -> finish_encoder(Fed) end
).