mrkeyoor.com_
Wed 23 Sept 00:36 UTC
npmUtilsupdated 22 Sept 2026

@ljharb/through review

@ljharb/through 2.3.14 is a maintained fork of the original `through` helper for old-style Node streams. It creates an object that is readable and writable, then binds that stream as `this` inside synchronous write and end callbacks. `this.queue(chunk)` sends or buffers output; `this.queue(null)` ends the readable side. The fork adds bundled TypeScript declarations and dependency upkeep while retaining the 2012-era contract. It fits legacy pipe chains, not new asynchronous transforms.

Verdict

Our @ljharb/through 2.3.14 install used 2 MB and passed npm audit, but its browser bundle failed and its synchronous stream contract still dates to the original helper. Install it for compatibility with old `through` code; use built-in `Transform` for a new pipeline.

We installed it

Lab card: what happened when we installed @ljharb/throughScreenshot of @ljharb/through documentation
Install✓ · 1.7s17 packages on disk · 2 MB
ImportESM import works · require() works · CommonJS package
Browsern/acould not be bundled for the browser (Node-only code, most likely)
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does @ljharb/through install cleanly?

Yes. In a fresh container with an empty cache, npm install @ljharb/through finished in 2 seconds, leaving 17 packages and 2 MB on disk. npm audit reported no known vulnerabilities.

Can @ljharb/through run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does @ljharb/through work with both ESM and CommonJS?

Yes. Both import '@ljharb/through' and require('@ljharb/through') worked in Node 22 in our run. The package is published as CommonJS.

Does @ljharb/through include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

@ljharb/through or through2: which should you use?

through2: Use it for callback transforms backed by Node's newer Transform stream contract. Our @ljharb/through 2.3.14 install used 2 MB and passed npm audit, but its browser bundle failed and its synchronous stream contract still dates to the original helper.

When should you not use @ljharb/through?

You are starting new Node code: built-in Transform and PassThrough avoid this dependency and follow current stream lifecycle rules

API stability5/5The 2.3.14 function still accepts write, end, and options arguments, and it keeps the original queue, pause, resume, and autoDestroy behavior described in the README. The fork's purpose is compatibility, so changing those calls would defeat it. This earns a 5 for existing consumers, with the warning that the stable surface preserves a pre-Streams2 model rather than adopting modern `Transform` semantics.
Docs3/5The README shows the two callback positions, explains why `queue()` should replace direct `data` emission, documents pause and resume, and gives the exact `autoDestroy: false` form. It leaves several operational facts to the source: null ends output, custom end callbacks own termination, write functions are synchronous, buffering has no configured ceiling, and errors do not receive the lifecycle of a modern Transform. Bundled declarations help TypeScript readers discover the remaining methods.
Maintenance3/5GitHub shows a 2025-02-08 last push, the repository is open, and the current 2.3.14 package carries declarations plus a single maintained dependency. There were 0 open issues and pull requests in the repository snapshot. Activity is modest, though the implementation is intentionally small and compatibility-focused. A score of 3 reflects recent upkeep without suggesting that the old stream design is receiving new capabilities.
Ecosystem3/5npm counted 3,211,186 downloads last week, and the helper works in ordinary Node pipe chains with bundled TypeScript declarations. Direct community activity is much smaller: the fork has 3 GitHub stars, its interface is CommonJS, and Node already ships the main alternatives. The volume is best read as established dependency-tree use and migration from the original `through`, rather than a growing plugin or framework ecosystem.

Use it if

  • An existing module expects the original `through(write, end, options)` callback and `this.queue()` contract
  • You need a small synchronous filter or mapper in a CommonJS Node stream chain
  • You are migrating from unscoped `through` and want the compatible fork with bundled declarations
  • Supporting Node versions far older than current `node:stream` helpers is a real project requirement
Skip it if

Setup reality

We installed @ljharb/through 2.3.14 in our sandbox in 1.7 seconds. The result was 17 packages and 2 MB on disk, with 0 known vulnerabilities from npm audit. The package is 84 KB unpacked, has 1 direct dependency, 0 peer dependencies, an MIT license, and an engine floor of Node 0.4. There was no native build, credential prompt, or configuration file.

The published entry point is CommonJS and has no exports map. require() and ESM import both worked under Node 22, and TypeScript declarations are bundled. esbuild could not produce a browser bundle in our test, which matches the package's dependence on Node stream behavior. Treat it as server-side code even if a bundler can polyfill parts of the stream stack.

Callbacks must be ordinary functions because the package binds the stream as this. The write path is synchronous and supplies no completion callback. this.queue() buffers chunks while paused, whereas this.emit('data', value) bypasses that buffer. Null is the end signal, so a custom end callback must call this.queue(null) or emit end; forgetting it leaves readers waiting.

Its pressure signal is also old-fashioned: write() returns the inverse of this.paused, not a high-water-mark calculation. A long pause can accumulate an unbounded internal buffer. Thrown callback errors escape synchronously, and source errors do not gain modern pipeline teardown just because this helper is present. Put the chain inside node:stream.pipeline() when coordinated cleanup matters.

Patterns

Create an unchanged pipe stage pass-through

const through = require('@ljharb/through');
const stage = through();
source.pipe(stage).pipe(destination);

With no callbacks, each input chunk is queued unchanged and the default end callback queues null.

Rewrite chunks synchronously map-chunks

const upper = through(function (chunk) {
  this.queue(Buffer.from(chunk.toString().toUpperCase()));
});

Use a normal function. An arrow function cannot receive the stream through the bound `this` value.

Drop chunks that fail a check filter-chunks

const nonempty = through(function (chunk) {
  if (chunk.length > 0) this.queue(chunk);
});

Returning without `queue()` discards that chunk; the write call still finishes synchronously.

Produce several outputs from one input emit-many

const duplicate = through(function (chunk) {
  this.queue(chunk);
  this.queue(chunk);
});

Every queued value becomes a data chunk. Null is reserved for ending the readable side.

Append output before the stream ends flush-final-value

const footer = through(
  function (chunk) { this.queue(chunk); },
  function () {
    this.queue('done\n');
    this.queue(null);
  }
);

Providing an end callback replaces the default one. Queue null yourself or downstream readers will keep waiting.

Pause and later drain queued chunks pause-output

const gate = through();
gate.pause();
source.pipe(gate);
setTimeout(() => gate.resume(), 100);

The queue has no configurable size ceiling. A producer can fill memory if the pause lasts while writes continue.

Observe the legacy pressure signal check-write-result

if (!stage.write(chunk)) {
  stage.once('drain', sendNext);
} else {
  sendNext();
}

`write()` returns false when this stream is paused. It does not calculate pressure from a writable highWaterMark.

Write one last value and finish end-with-chunk

const stage = through();
stage.on('data', console.log);
stage.end('last');

`end(value)` writes the supplied value before it runs the end callback, and later end calls are ignored.

Keep close separate from both end states disable-auto-close

const stage = through(write, end, { autoDestroy: false });

`autoDestroy` defaults to true and emits close after the readable and writable sides have ended. Change it only for a legacy consumer that expects another sequence.

Give the chain coordinated teardown pipeline-errors

const { pipeline } = require('node:stream');
const stage = through(function (chunk) { this.queue(chunk); });

pipeline(source, stage, destination, error => {
  if (error) console.error(error);
});

`pipeline()` centralizes errors and destroys the chain. The through helper alone does not provide that modern teardown behavior.

Alternatives

PackageRegistryPick it when
through2npmUse it for callback transforms backed by Node's newer Transform stream contract.
stream-transformnpmUse it for record transforms that need asynchronous completion or parallel work.
minipassnpmUse it when you want a compact modern stream implementation with a different API.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.