Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
# chunk-bytes
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`chunkBytes` re-frames an arbitrary `Uint8Array` byte stream into fixed-size
frames of exactly `frameSize` bytes. Upstream producers rarely emit byte
chunks that align to a downstream protocol's frame boundary — file readers,
sockets, and decompression streams all hand out variable-length buffers. This
transform absorbs that variability so consumers can rely on stable frame
geometry, which is the precondition for length-prefixed framing, fixed-block
ciphers, and any sliding-window decoder.
## Features
- Fixed-size output frames with caller-supplied `frameSize`.
- Three tail policies for the residual: `emit` (default), `pad`, `drop`.
- Zero-copy frame emission via `Uint8Array.subarray` for full frames.
- Synchronous `RangeError` when `frameSize <= 0`.
- Plugin-OO wrapper (`ChunkBytesTransform`) for use with `BaseTransform`-aware
pipelines, with a `process()` that returns the full concatenated output.
- AbortSignal honoured by `process()` — rejects with `StreamAbortedError`.
## Usage: Basic Example
```ts
import { chunkBytes } from "@suaveplan/streams/transforms/chunk-bytes/chunk-bytes";
import { fromBytes } from "@suaveplan/streams/readable/from-bytes/from-bytes";
import { toArray } from "@suaveplan/streams/sinks/to-array/to-array";
const input = new Uint8Array(16);
for (let i = 0; i < 16; i++) input[i] = i + 1;
const frames = await toArray(
fromBytes(input, 7).pipeThrough(chunkBytes(4))
);
// frames.length === 4, every frame is exactly 4 bytes
```
## Usage: Advanced Example
Pad the trailing partial frame with zeroes (useful when feeding a fixed-block
cipher or a fixed-frame radio modem):
```ts
import { ChunkBytesTransform } from "@suaveplan/streams/transforms/chunk-bytes/chunk-bytes";
const t = new ChunkBytesTransform(8, { tail: "pad" });
const padded = await t.process(new Uint8Array([1, 2, 3, 4, 5]));
// padded === Uint8Array([1, 2, 3, 4, 5, 0, 0, 0])
// Or use it in a pipeline alongside any other transform:
readable.pipeThrough(t.toTransformStream()).pipeTo(downstream);
t.dispose();
```
Drop the trailing partial frame when downstream cannot handle short reads:
```ts
chunkBytes(1024, { tail: "drop" });
```
## API Reference
### `chunkBytes(frameSize, options?)`
- `frameSize: number` — exact byte count per output frame; must be `> 0`.
- `options.tail?: 'emit' | 'pad' | 'drop'` — residual policy. Defaults to
`'emit'`.
- Returns: `TransformStream<Uint8Array, Uint8Array>`.
- Throws: `RangeError` if `frameSize <= 0`. The stream errors with `TypeError`
on non-`Uint8Array` chunks.
### `class ChunkBytesTransform extends BaseTransform`
- `new ChunkBytesTransform(frameSize, options?)` — same parameters; throws
`RangeError` synchronously.
- `process(input: Uint8Array, options?): Promise<Uint8Array>` — pushes `input`
through an internal `chunkBytes` stream and returns the concatenated frames
as a single `Uint8Array`. Honours `options.signal`.
- `toTransformStream<A, B>()` — returns `chunkBytes(frameSize, options)`.
- `dispose()` — clears hooks/listeners (calls `super.dispose()`).
## Implementation Notes
The transform keeps a `carry` `Uint8Array` of bytes received but not yet
consumed by a complete frame. On each upstream chunk it concatenates `carry +
chunk`, emits as many `frameSize`-byte subarrays as fit, and copies the
residual into a fresh allocation so the upstream chunk can be released. The
`flush` callback resolves the residual against the configured tail policy.
Performance: full frames are emitted as `subarray` views (no copy). Only the
residual carry is copied, bounded by `frameSize - 1` bytes per upstream
chunk, so steady-state allocations are O(frameSize) regardless of upstream
chunk size. The `'pad'` policy allocates exactly one `frameSize` buffer at
end-of-stream.
Known limitations: the transform is byte-oriented; it does not understand
record boundaries beyond fixed length. For length-prefixed or
delimiter-separated framing, layer this on top of a higher-level decoder.