# sliding-bytes _(no submodules)_ ## Purpose The `sliding-bytes` transform re-frames a `Uint8Array` byte stream into fixed-length overlapping (or gapped) byte windows, advancing by a caller-controlled stride per emission. It is the byte-oriented counterpart of the generic `window` transform: where `window` operates on item streams and emits `T[]` arrays, `slidingBytes` operates directly on bytes and emits zero-copy `Uint8Array` windows of length `size`. This is the right primitive when you need fixed-size frames with overlap semantics — for example, computing rolling hashes (Rabin-Karp, content-defined chunking previews), running a sliding FFT or convolution, batching audio samples for a vocoder, or feeding a signature-detection state machine that must straddle frame boundaries. Unlike `chunkBytes`, sliding windows can revisit the same byte across multiple emissions, which is load-bearing for any algorithm whose context window exceeds its hop size. ## Features - Functional factory `slidingBytes(size, stride)` returning a `TransformStream`. - Plugin-OO wrapper `SlidingBytesTransform` extending `BaseTransform` with hook/event/dispose semantics inherited from the base class. - Configurable `size` (window length) and `stride` (emission step). - `stride < size` produces overlapping windows (overlap = `size - stride`). - `stride === size` produces non-overlapping windows (equivalent to a drop-tail `chunkBytes`). - `stride > size` produces gapped windows that discard `stride - size` bytes between consecutive emissions. - Carry buffer that correctly accumulates and re-emits across upstream chunk boundaries, regardless of how the producer happens to slice its writes. - Trailing residual smaller than `size` is dropped on flush — sliding windows are fixed-length by definition. - Synchronous `RangeError` for invalid `size` or `stride` (`<= 0`, non-finite). ## Usage: Basic Example ```ts import { fromBytes } from "@suaveplan/streams/readable/from-bytes/from-bytes"; import { toArray } from "@suaveplan/streams/sinks/to-array/to-array"; import { slidingBytes } from "@suaveplan/streams/transforms/sliding-bytes/sliding-bytes"; const input = new Uint8Array(16); for (let i = 0; i < 16; i++) input[i] = i; const windows = await toArray( fromBytes(input, 16).pipeThrough(slidingBytes(8, 4)) ); // windows.length === 3 // windows[0] = bytes 0..7 // windows[1] = bytes 4..11 (overlaps windows[0] by 4 bytes) // windows[2] = bytes 8..15 (overlaps windows[1] by 4 bytes) ``` ## Usage: Advanced Example ```ts import { SlidingBytesTransform } from "@suaveplan/streams/transforms/sliding-bytes/sliding-bytes"; // Gapped sampling: read 4 bytes, skip 4, read 4, skip 4, ... const sampler = new SlidingBytesTransform({ size: 4, stride: 8 }); // Compose with the rest of the pipeline via the standard plugin surface. const stream = sampler.toTransformStream(); // Plug into any ReadableStream source. The transform is stateful // across chunk boundaries — the upstream is free to deliver bytes in any // chunk granularity (1 byte at a time, mega-chunks, anything in between) // and the windowing semantics are identical. const out: Uint8Array[] = []; await stream.readable.pipeTo( new WritableStream({ write(chunk) { out.push(chunk); }, }) ); ``` ## API Reference ### `slidingBytes(size: number, stride: number): TransformStream` Returns a WHATWG `TransformStream` that emits `Uint8Array` windows of length `size`, advancing by `stride` bytes per emission. - `size` — window length in bytes. Must be a finite number `> 0`. - `stride` — emission step in bytes. Must be a finite number `> 0`. Throws `RangeError` synchronously when either argument is non-finite or `<= 0`. Errors the stream with `TypeError` when an upstream chunk is not a `Uint8Array`. ### `class SlidingBytesTransform extends BaseTransform` Plugin-OO wrapper. Constructor takes a `SlidingBytesTransformConfig` with `size` and `stride` fields. Same validation as the functional factory. - `name` — `"sliding-bytes"`. - `process(input: T): Promise` — degenerate single-shot: returns `input` unchanged. Real windowing requires stream-mode use because the operator is stateful across chunks. - `toTransformStream(): TransformStream` — delegates to `slidingBytes(size, stride)`. Each call returns a fresh stream. ### `interface SlidingBytesTransformConfig` ```ts interface SlidingBytesTransformConfig { readonly size: number; readonly stride: number; } ``` ## Implementation Notes - **Carry buffer.** Each upstream chunk is concatenated onto a residual carry buffer. The transform then emits every complete `size`-byte window starting at offsets `0, stride, 2*stride, ...` within that combined buffer. Once no further window fits, the bytes from the next would-be start offset onward are retained as the new carry. When `stride > size` and the offset overshoots the buffer (the "gap" case), the carry is reset to empty so the inter-window bytes are correctly discarded. - **Zero-copy emission.** Each emitted window is copied into a fresh `Uint8Array` rather than a `subarray` view. This is required because the carry buffer is replaced on every transform call and a `subarray` would alias memory whose lifetime ends at the next chunk. The cost is one `size`-byte copy per emission; the win is no aliasing-induced memory pin. - **Flush policy.** The trailing residual that cannot fill a complete window is dropped. Unlike `chunkBytes` (which exposes `tail: emit | pad | drop`), sliding windows are only meaningful at exact length, so there is no configurable tail policy here. Callers needing partial-tail behaviour should compose with `chunkBytes` instead. - **Single-shot degeneracy.** `SlidingBytesTransform.process(input)` returns the input unchanged. The operator is fundamentally a streaming construct — a single buffer has only one "frame," and the most useful semantics for a one-call API is the identity. Use `toTransformStream()` for real work. - **Validation.** Both the factory and the class throw `RangeError` for non-finite or non-positive `size`/`stride`. Validation runs synchronously at construction so misuse fails before any data flows.