peek-stream review
peek-stream 1.1.3 is a CommonJS Transform that reads the first line of a Node stream, passes that sample to your selector, and swaps in the parser returned through an error-first callback. It then replays the sampled bytes and any remaining part of the chunk into that parser. This lets a pipeline choose CSV, newline-delimited JSON, or another line-recognizable format while preserving streaming. Version 1.1.3 replaced the deprecated Buffer constructor with `buffer-from`; that March 2018 release is still the newest package. Detection rules and parsers are entirely your code.
peek-stream 1.1.3 installed in 1.3 seconds but pulled in 17 packages, shipped no types, and failed our browser build; it remains acceptable inside a tested CommonJS stream pipeline that already depends on its swap contract. New code can usually own the small prefix-buffering transform and avoid relying on an unreleased codebase from 2018.
We installed it
| Install | ✓ · 1.3s | 17 packages on disk · 1 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package |
| Browser | n/a | could not be bundled for the browser (Node-only code, most likely) |
| Types | — | no TypeScript types found |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does peek-stream install cleanly?
Yes. In a fresh container with an empty cache, npm install peek-stream finished in 1 seconds, leaving 17 packages and 1 MB on disk. npm audit reported no known vulnerabilities.
Can peek-stream 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 peek-stream work with both ESM and CommonJS?
Yes. Both import 'peek-stream' and require('peek-stream') worked in Node 22 in our run. The package is published as CommonJS.
Does peek-stream include TypeScript types?
No type declarations were found in our install, so TypeScript users need their own declarations.
peek-stream or through2: which should you use?
through2: Use it when a small custom prefix buffer is easy to own and exact framing behavior matters. peek-stream 1.1.3 installed in 1.3 seconds but pulled in 17 packages, shipped no types, and failed our browser build; it remains acceptable inside a tested CommonJS stream pipeline that already depends on its swap contract.
When should you not use peek-stream?
New maintenance is required. The last default-branch code commit and npm release both date to March 2018.
Use it if
- A classic Node stream can choose its parser reliably from the first line or a fixed byte prefix.
- The input must stay streaming instead of being collected in memory before format selection.
- An existing CommonJS pipeline already uses through2-style transforms and callback error handling.
- Parser selection may complete asynchronously and can always call the supplied swap callback.
- New maintenance is required. The last default-branch code commit and npm release both date to March 2018.
- A signature can appear after the first newline or beyond 65,535 bytes. The default detector never sees later content.
- TypeScript or ESM packaging is a baseline requirement. The package ships no declarations, exports map, or native ESM entry.
- A custom delimiter is needed. Version 1.1.3 searches for byte 10 and only treats a preceding byte 13 as part of that line ending.
- The first record can be attacker-controlled and must fail closed. `strict` defaults to false, so the byte limit triggers selection unless you turn strict mode on.
- Browser streams are the target. Our esbuild browser build failed, and the implementation is built around Node Buffer plus classic duplex streams.
Setup reality
We installed peek-stream 1.1.3 in a fresh Node 22 Bookworm sandbox in 1.3 seconds. It left 17 packages and 1 MB on disk, while peek-stream itself was 32 KB unpacked. npm audit reported 0 known vulnerabilities. The package declares 3 direct dependencies and 0 peers, uses the MIT license, and has no TypeScript declarations. It is CommonJS without an exports map; both require() and ESM import worked in our checks.
No native build, credentials, or config file is involved. The selected parser is your dependency and must act as both a readable and writable stream. The callback receives the first-line Buffer and a swap(error, parser) function. Call swap once. peek-stream writes the sample into the chosen parser after the callback, so forwarding the same Buffer yourself duplicates the beginning. An asynchronous decision is allowed, but input remains pending until the callback arrives.
By default the stream waits for LF and buffers up to 65,535 bytes. With strict: false, reaching that limit calls the selector even when no newline appeared; with strict: true, it emits No newline found. Strict mode raises the same error if input ends first. The source also supports undocumented newline: false, which waits for the byte cap or end rather than scanning lines. A first object-mode chunk bypasses byte buffering and selects immediately.
Use stream.pipeline() or attach an error listener because selector failures and framing errors leave through the returned duplex. Ambiguous commas, a byte-order mark, comments, and corrupt first records need explicit rules in your detector. Our esbuild browser bundle failed, matching Node-only code. Although GitHub shows a 2023 repository push timestamp, the default branch's newest commit is the 1.1.3 release from 2018; later pull-request activity did not produce a package update.
Patterns
Choose CSV or newline-delimited JSON select-line-format
const peek = require('peek-stream')
const csv = require('csv-parser')
const ldjson = require('ldjson-stream')
const parser = peek((sample, swap) => {
const line = sample.toString('utf8')
if (line.trimStart().startsWith('{')) return swap(null, ldjson())
if (line.includes(',')) return swap(null, csv())
swap(new Error('unsupported input format'))
})peek-stream supplies no format test. Check the more specific signature first because valid JSON text can contain commas.
Collect pipeline errors in one callback pipe-errors
const { pipeline } = require('node:stream')
pipeline(source, parser, destination, (error) => {
if (error) console.error('parse failed:', error.message)
})`pipeline()` catches source, selector, parser, and destination failures more reliably than a bare chain of `pipe()` calls.
Set a smaller detection buffer cap-sample
const parser = peek({ maxBuffer: 8192 }, (sample, swap) => {
swap(null, chooseParser(sample))
})Without strict mode, 8,192 buffered bytes trigger the selector even if no LF has arrived.
Reject an unterminated first record require-newline
const parser = peek(
{ maxBuffer: 8192, strict: true },
(sample, swap) => swap(null, chooseParser(sample))
)Strict mode emits `No newline found` at the byte cap and when the source ends before LF.
Inspect fixed-position magic bytes peek-fixed-prefix
const parser = peek(
{ maxBuffer: 16, newline: false },
(prefix, swap) => swap(null, parserForMagicBytes(prefix))
)`newline: false` exists in 1.1.3 source but not its README. Selection waits for 16 bytes or the end of input.
Resolve the parser asynchronously select-parser-async
const parser = peek((sample, swap) => {
lookupParser(sample).then(
selected => swap(null, selected),
error => swap(error)
)
})The writable side stays blocked until `swap()` runs. Forward promise rejection or a failed lookup can leave the pipeline waiting forever.
Fail on unknown magic bytes reject-unknown-format
const parser = peek({ maxBuffer: 4, newline: false }, (sample, swap) => {
const magic = sample.toString('hex')
if (magic === '504b0304') return swap(null, unzipParser())
swap(new Error(`unsupported magic: ${magic}`))
})An error passed to `swap()` is emitted by the returned duplex. Add an error handler or use `pipeline()`.
Let the wrapper replay inspected bytes avoid-sample-duplication
const parser = peek((sample, swap) => {
console.log('selected from', sample.length, 'bytes')
swap(null, createParser())
})Do not write `sample` to the parser yourself. Version 1.1.3 forwards the sample and the rest of its original chunk after swapping.
Make empty input an explicit error handle-empty-source
const parser = peek((sample, swap) => {
if (sample.length === 0) return swap(new Error('empty input'))
swap(null, chooseParser(sample))
})With strict mode off, ending before any bytes calls the selector with an empty Buffer.
Select from the first object route-object-mode
const parser = peek((first, swap) => {
const selected = first.type === 'event'
? eventTransform()
: fallbackTransform()
swap(null, selected)
})
objectSource.pipe(parser).pipe(objectDestination)A non-Buffer, non-string first chunk selects immediately. The returned transform must accept and emit matching object-mode values.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| through2 | npm | Use it when a small custom prefix buffer is easy to own and exact framing behavior matters. |
| streamx | npm | Use it as the maintained stream base for a new high-throughput detector and parser pipeline. |
| readable-stream | npm | Use it when broad Node stream compatibility matters more than dynamic parser swapping. |
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.

