6.3 KiB
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 aTransformStream<Uint8Array, Uint8Array>. - Plugin-OO wrapper
SlidingBytesTransformextendingBaseTransformwith hook/event/dispose semantics inherited from the base class. - Configurable
size(window length) andstride(emission step). stride < sizeproduces overlapping windows (overlap =size - stride).stride === sizeproduces non-overlapping windows (equivalent to a drop-tailchunkBytes).stride > sizeproduces gapped windows that discardstride - sizebytes 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
sizeis dropped on flush — sliding windows are fixed-length by definition. - Synchronous
RangeErrorfor invalidsizeorstride(<= 0, non-finite).
Usage: Basic Example
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
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<Uint8Array, Uint8Array>();
// Plug into any ReadableStream<Uint8Array> 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<Uint8Array, Uint8Array>
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<T>(input: T): Promise<T>— degenerate single-shot: returnsinputunchanged. Real windowing requires stream-mode use because the operator is stateful across chunks.toTransformStream<A, B>(): TransformStream<A, B>— delegates toslidingBytes(size, stride). Each call returns a fresh stream.
interface SlidingBytesTransformConfig
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 offsets0, 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. Whenstride > sizeand 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
Uint8Arrayrather than asubarrayview. This is required because the carry buffer is replaced on every transform call and asubarraywould alias memory whose lifetime ends at the next chunk. The cost is onesize-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 exposestail: 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 withchunkBytesinstead. - 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. UsetoTransformStream()for real work. - Validation. Both the factory and the class throw
RangeErrorfor non-finite or non-positivesize/stride. Validation runs synchronously at construction so misuse fails before any data flows.