mrkeyoor.com_
Sat 08 Aug 22:51 UTC
npmUtilsupdated 08 Aug 2026

byte-counter

byte-counter is a tiny pass-through transform that totals the bytes crossing a stream without changing the chunks. The default export wraps the standard Web Streams API for fetch responses and browser-style pipelines; byte-counter/node provides a Node.js Transform for files, HTTP bodies, and pipeline(). A named byteLength helper also measures UTF-8 strings and binary views without constructing a stream. It reports transferred bytes, not characters, records, formatted sizes, rates, or percentages.

Verdict

A focused, dependency-free choice when an ESM application on Node 20+ needs a passive byte total across Web or Node streams. Choose a progress or limiting transform if the count must drive policy rather than simple observation.

API stability3/5The public surface is exceptionally small: two default stream classes, one read-only count getter, and one byteLength function. The package uses explicit exports and separate type declarations, which reduces accidental API exposure. However, 0.1.0 is still the only release, so there is no version history proving that entry points, supported inputs, or the Node 20 floor will remain unchanged before 1.0.
Docs4/5The README shows both Web Streams and Node Transform usage, documents the /node subpath, lists every accepted byteLength input type, and demonstrates the UTF-8 result for an emoji. The source and tests clarify pass-through behavior and the read-only count. It does not document invalid runtime inputs returning zero, counter reuse, safe-integer limits, or Content-Length mismatches after decompression.
Maintenance4/5The package was published and the repository pushed in October 2025, the repository is not archived, and GitHub currently reports no open issues or pull requests. Its tiny dependency-free implementation lowers routine maintenance burden. The cautious deduction is one point lower than perfect because 0.1.0 is the sole release and the last code push is months old, leaving little evidence about response to compatibility changes.
Ecosystem3/5The package records 3,305,542 downloads for the measured week and uses standard TransformStream and node:stream contracts, so it composes with fetch, pipeline(), files, and HTTP bodies without adapters. On the other hand, the repository has 36 stars, the project has no plugin ecosystem, CommonJS is unsupported, and its Node 20 minimum excludes older production runtimes still found in long-lived services.

Use it if

  • You need the exact byte total for a Web Streams pipeline while preserving its chunks and backpressure
  • You need a Node Transform that can sit between an existing readable and writable without changing either endpoint
  • You want one ESM package with typed entry points for both Web Streams and Node streams
  • You need to compare bytes actually processed with an expected size after a transfer completes
Skip it if

Setup reality

Installation is only npm install byte-counter, with no runtime dependencies, native compilation, credentials, or configuration file. The compatibility decisions matter more than the installation. Version 0.1.0 is ESM-only and declares Node 20 or newer. Import the default byte-counter entry for Web Streams, but import byte-counter/node for Node's Transform API; using the wrong one produces an object with the wrong piping surface. The count is read-only and starts at zero, then accumulates for the lifetime of that counter, so create a fresh instance for each independent transfer and read the final value only after the pipeline has finished. The transform preserves chunks and normal stream backpressure, but it does not emit progress events, calculate a percentage, stop oversized input, reset itself, or compare against Content-Length. Strings are measured as UTF-8 by TextEncoder, while Uint8Array, Buffer, DataView, typed arrays, ArrayBuffer, and SharedArrayBuffer use byteLength. Unsupported runtime values quietly contribute zero in the JavaScript implementation, even though the TypeScript signature rejects them. Fetch adds another trap: Content-Length can describe an encoded response while response.body exposes decoded bytes, so equality is meaningful only when transfer encoding and content encoding are understood. The counter uses a JavaScript number, which is plenty for ordinary transfers but is not a bigint accounting primitive for totals beyond the safe-integer range.

Patterns

Measure a UTF-8 stringmeasure-utf8-string

import {byteLength} from 'byte-counter';

console.log(byteLength('Hello 👋'));
// 10

This counts UTF-8 bytes, not JavaScript UTF-16 code units or visible characters; the waving-hand emoji contributes four bytes.

Measure a binary viewmeasure-binary-view

import {byteLength} from 'byte-counter';

const packet = new Uint8Array([0, 127, 128, 255]);
console.log(byteLength(packet));
// 4

Buffer, DataView, typed arrays, ArrayBuffer, and SharedArrayBuffer are also accepted because the implementation reads their byteLength.

Count a fetched response bodycount-fetch-body

import ByteCounterStream from 'byte-counter';

const response = await fetch('https://example.com/file.zip');
if (!response.ok || !response.body) {
  throw new Error(`Download failed: ${response.status}`);
}

const counter = new ByteCounterStream();
await response.body
  .pipeThrough(counter)
  .pipeTo(new WritableStream({
    write(chunk) {
      consume(chunk);
    },
  }));

console.log(counter.count);

Check response.body because it can be null, and read the final count only after pipeTo resolves.

Compare a completed fetch with Content-Lengthvalidate-content-length

const expected = Number(response.headers.get('content-length'));
const counter = new ByteCounterStream();
await response.body.pipeThrough(counter).pipeTo(destination);

if (Number.isFinite(expected) && counter.count !== expected) {
  throw new Error(`Expected ${expected} bytes, received ${counter.count}`);
}

Do not make this comparison blindly for compressed responses: Content-Length may describe encoded transfer bytes while fetch exposes decoded body bytes.

Write chunks through the Web Streams counterwrite-web-stream

import ByteCounterStream from 'byte-counter';

const counter = new ByteCounterStream();
const drain = counter.readable.pipeTo(new WritableStream({write() {}}));
const writer = counter.writable.getWriter();

await writer.write(new TextEncoder().encode('alpha'));
await writer.write(new TextEncoder().encode('beta'));
await writer.close();
await drain;

console.log(counter.count);
// 9

Consume the readable side while writing. Awaiting writes with no reader can stall when stream backpressure fills the internal queue.

Count bytes while copying a Node filecopy-node-file

import fs from 'node:fs';
import {pipeline} from 'node:stream/promises';
import ByteCounterStream from 'byte-counter/node';

const counter = new ByteCounterStream();
await pipeline(
  fs.createReadStream('input.bin'),
  counter,
  fs.createWriteStream('output.bin'),
);

console.log(`Copied ${counter.count} bytes`);

Use the /node entry point. The default export implements Web Streams and cannot be inserted directly into a classic Node pipe chain.

Count a Node readable without saving itcount-node-readable

import {Writable} from 'node:stream';
import {pipeline} from 'node:stream/promises';
import ByteCounterStream from 'byte-counter/node';

const counter = new ByteCounterStream();
const sink = new Writable({
  write(_chunk, _encoding, callback) {
    callback();
  },
});

await pipeline(source, counter, sink);
console.log(counter.count);

A transform still needs a downstream consumer. pipeline propagates source, counter, and sink errors and resolves only when all stages finish.

Count a Node HTTP responsecount-http-download

import {pipeline} from 'node:stream/promises';
import fs from 'node:fs';
import ByteCounterStream from 'byte-counter/node';

const counter = new ByteCounterStream();
await pipeline(response, counter, fs.createWriteStream('download.bin'));
console.log({status: response.statusCode, bytes: counter.count});

Validate the HTTP status before committing the file; the counter treats an error response body like any other bytes.

Count bytes written to an HTTP requestcount-http-upload

import {pipeline} from 'node:stream/promises';
import fs from 'node:fs';
import ByteCounterStream from 'byte-counter/node';

const counter = new ByteCounterStream();
await pipeline(fs.createReadStream('archive.tar'), counter, request);
console.log(`Request body: ${counter.count} bytes`);

This counts body bytes passed to the request stream, not HTTP headers or lower-level framing added by the network stack.

Sample a running Node transfersample-node-progress

const counter = new ByteCounterStream();
const timer = setInterval(() => {
  console.log(`${counter.count} bytes processed`);
}, 1000);

try {
  await pipeline(source, counter, destination);
} finally {
  clearInterval(timer);
}
console.log(`Final: ${counter.count}`);

The library emits no progress event. Sampling count is simple, but percentage and ETA require a known total plus your own timing logic.

Add a hard size limit after the counterreject-oversized-stream

import {Transform} from 'node:stream';

const limit = new Transform({
  transform(chunk, _encoding, callback) {
    if (counter.count > 10 * 1024 * 1024) {
      callback(new Error('Body exceeds 10 MiB'));
      return;
    }
    callback(null, chunk);
  },
});

await pipeline(source, counter, limit, destination);

byte-counter only observes. A separate stage must fail the pipeline, and placing it after the counter lets it inspect the updated total for the current chunk.

Use a fresh counter per transferisolate-transfer-counts

async function copyWithSize(source, destination) {
  const counter = new ByteCounterStream();
  await pipeline(source, counter, destination);
  return counter.count;
}

const firstBytes = await copyWithSize(firstSource, firstDestination);
const secondBytes = await copyWithSize(secondSource, secondDestination);

There is no reset method. Reusing one instance would accumulate its count and stream state rather than produce independent totals.

Alternatives

PackageRegistryPick it when
progress-streamnpmNode streams that need percentage, speed, elapsed time, and ETA callbacks rather than only a byte total
stream-meternpmNode-only code that also wants to stop a stream after a configured maximum size
byte-lengthnpmYou only need a function that measures a string or Buffer and do not need a transform stream
bytesnpmYou need to parse and format human-readable sizes such as 5 MB rather than count streamed data