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

pause-stream

pause-stream is a classic Node stream that buffers everything written to it while paused and flushes the buffer when you resume. You attach it between a producer you cannot control and a consumer that is not ready yet, call pause(), and no data is lost in between. The important thing to know before you install it: version 0.0.11 has exactly two lines of code. Its index.js reads 'through@2 handles this by default!' followed by module.exports = require('through'). require('pause-stream') and require('through') return the identical function object, verified by strict equality. Everything the package does is done by through 2.3.8, which the package depends on.

Verdict

pause-stream is a two-line re-export of through that has been archived since 2018 and unreleased since 2013, and 6.1 million weekly downloads do not change that. If you must keep classic streams, depend on through directly; if you are writing anything new, PassThrough from node:stream is the honest answer.

API stability4/5The surface has not moved since 0.0.11 shipped in September 2013, and it cannot move, because the package delegates entirely to through 2.3.8, which last published in July 2015. write, end, pause, resume, queue, push and destroy behave exactly as they did a decade ago. That is stability by abandonment rather than by design: nothing will break, and nothing will be fixed either, including the streams1 semantics that modern Node helpers cannot work with.
Docs2/5The README describes a stream that strictly buffers when paused, shows one usage example, and then admits at the bottom that this is now the default case of through, with a link to the commit that made it so. What it never says plainly is that the package no longer contains an implementation. Nothing documents the unbounded buffer, the lack of error forwarding through pipe, or how the stream interacts with stream.pipeline. Reading through's README and its 120 lines of source is faster and more accurate than reading this one.
Maintenance1/5The GitHub repository has been archived since June 2018 with the last push on 2018-06-12, two open issues, and 42 stars. The npm package last published version 0.0.11 on 2013-09-20 and is listed under a placeholder maintainer account rather than a named person. Its only dependency, through, last published in 2015. Nothing here is being watched, which matters more than usual for a package pulling 6.1 million installs a week through other people's dependency trees.
Ecosystem2/5The 6.1 million weekly downloads are almost entirely transitive: old build tools and CLI packages that were written against streams1 and never updated. There are no TypeScript declarations, no ESM build, no browser story and no framework integrations. Its parent package through gets 46 million weekly downloads, so the ecosystem that matters is through's, and every integration question resolves there. As a direct dependency in a new project it has essentially no community.

Use it if

  • You are maintaining a codebase from the streams1 era that already imports pause-stream and you want to understand what it actually does before touching it
  • A producer starts emitting before your consumer is wired up and you need a small buffer in between, on old-style Node streams that predate Readable and Writable
  • You need pause and resume semantics where write() returns false while paused and buffered chunks replay in order on resume, with no object-mode or highWaterMark configuration
  • Something deep in your dependency tree already pulls it in and you are deciding whether to dedupe it against through rather than remove it
Skip it if

Setup reality

Installation is npm install pause-stream and nothing else: one dependency on through ~2.3, no native build, no config. The real setup surprise is that there is nothing to set up because there is nothing here. Reading index.js takes five seconds and settles the question: the module body is a comment and a re-export of through. So every question you have about pause-stream is really a question about through 2.3.8, and the pause-stream README is a 2013 document describing behavior that lives in a different package. Practical consequences follow from that. The stream you get back is constructed with new Stream(), the legacy base class, so it is not an instanceof Readable and modern helpers treat it as a foreign object. pause() sets a flag and returns the stream, which is why the README's badlyBehavedStream.pipe(ps.pause()) chains. While paused, write() returns false and chunks accumulate in an in-memory array; resume() drains that array by emitting data events synchronously, then emits drain. Nothing bounds the array, so pausing in front of a producer that ignores the false return value is an unbounded memory buffer with a friendly name. There is a destroy() method, but it only sets flags, empties the buffer and emits close, which is not what stream.pipeline expects, so mixing this stream into a pipeline() call will not give you the cleanup you think it does. If you are adding a buffer to new code, PassThrough from node:stream does the same job with real backpressure and no dependency.

Patterns

Check what you actually installedconfirm-it-is-through

const pauseStream = require('pause-stream')
const through = require('through')

console.log(pauseStream === through)
// true

// package/index.js in pause-stream@0.0.11 is:
//   //through@2 handles this by default!
//   module.exports = require('through')

Strict equality holds because the module re-exports the same function object. Any behaviour question about pause-stream is a question about through 2.3.8.

Hold data until the destination existsbuffer-until-consumer-ready

const pause = require('pause-stream')

const ps = pause()
source.pipe(ps.pause())

lookupDestination((err, dest) => {
  if (err) return ps.destroy()
  ps.pipe(dest)
  ps.resume()
})

pause() returns the stream so it chains inside pipe(). Chunks that arrive before resume() sit in a plain array with no upper bound.

See the buffering happenpause-and-resume

const pause = require('pause-stream')

const ps = pause()
const seen = []
ps.on('data', (chunk) => seen.push(String(chunk)))

ps.pause()
ps.write('a')
ps.write('b')
console.log(seen.length) // 0

ps.resume()
console.log(seen.join(',')) // 'a,b'

resume() drains synchronously by emitting data events in order, then emits drain. Order is preserved; timing is not.

Stop writing when write() says falserespect-write-return

const pause = require('pause-stream')
const ps = pause()

ps.pause()
if (ps.write(chunk) === false) {
  producer.pause()
  ps.once('drain', () => producer.resume())
}

write() returns !paused, so it is the only backpressure signal there is. Ignoring it is how the internal array grows without limit.

Pass write and end handlers like through doestransform-with-handlers

const pause = require('pause-stream')

const upper = pause(
  function (data) { this.queue(String(data).toUpperCase()) },
  function () { this.queue(null) }
)

source.pipe(upper).pipe(destination)

This is through's constructor signature, undocumented in the pause-stream README. queue(null) is how you signal end from inside a handler.

Attach error listeners on every hophandle-errors-manually

const ps = require('pause-stream')()

source.on('error', onError)
ps.on('error', onError)
destination.on('error', onError)

source.pipe(ps).pipe(destination)

function onError (err) {
  source.destroy && source.destroy()
  ps.destroy()
}

Classic pipe() does not forward errors between streams. Every stream in the chain needs its own listener or an error becomes an uncaught exception.

Do not put it inside stream.pipelineavoid-pipeline

// Does not clean up the way you expect:
// await pipeline(source, require('pause-stream')(), destination)

// Use the modern equivalent instead:
const { PassThrough, pipeline } = require('node:stream')
const { promisify } = require('node:util')

await promisify(pipeline)(source, new PassThrough(), destination)

destroy() here only flips flags and emits close, so pipeline() cannot tear the chain down. PassThrough implements the contract pipeline expects.

Swap in a core PassThroughreplace-with-passthrough

const { PassThrough } = require('node:stream')

const buffer = new PassThrough({ highWaterMark: 1 << 20 })
source.pipe(buffer)
buffer.pause()

later(() => {
  buffer.pipe(destination)
  buffer.resume()
})

highWaterMark gives the bound that pause-stream never had, and the source is told to slow down instead of the buffer growing.

Collapse the duplicate dependencydedupe-against-through

// package.json
// -  "pause-stream": "0.0.11",
// +  "through": "~2.3"

-const pause = require('pause-stream')
+const through = require('through')

-const ps = pause()
+const ps = through()

Behaviour is identical because it is the same object. You lose one package from the tree and gain a README that describes the running code.

Watch the unbounded buffer while pausedinspect-buffer-growth

const ps = require('pause-stream')()
ps.pause()

let written = 0
const timer = setInterval(() => {
  console.log({ written, heapMb: Math.round(process.memoryUsage().heapUsed / 1e6) })
}, 1000)

for (const chunk of firehose) {
  ps.write(chunk)
  written++
}
clearInterval(timer)

There is no highWaterMark and no limit check in through's queue(), so heap use tracks how much you wrote while paused.

Push objects through without configurationobject-mode-note

const ps = require('pause-stream')()

ps.on('data', (record) => console.log(record.id))
ps.pause()
ps.write({ id: 1 })
ps.write({ id: 2 })
ps.resume()

There is no objectMode flag because there is no mode: chunks are stored and re-emitted as-is. Nothing converts to Buffer and nothing validates the type.

Know what end and destroy actually doend-and-destroy

const ps = require('pause-stream')()

ps.end('last chunk')  // writes, then ends; repeat calls are ignored

ps.destroy()          // clears the buffer, flips flags, emits 'close'
console.log(ps.writable, ps.readable) // false false

destroy() emits close but never error, and it does not touch the upstream source. Whatever is feeding this stream keeps running until you stop it yourself.

Alternatives

PackageRegistryPick it when
throughnpmYou want the package that pause-stream re-exports, without the extra hop, and you are keeping classic stream code alive
through2npmYou want the same shape of tiny transform helper but built on streams2 so pipe, backpressure and errors behave like the rest of modern Node
readable-streamnpmYou need the userland copy of Node's current stream implementation and can build the buffer with PassThrough yourself