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

peek-stream

peek-stream is a small CommonJS utility that buffers the first line of a Node stream, hands that sample to your callback, and then swaps in the Transform stream you choose. The sample is replayed into the selected parser, so downstream consumers still see the complete input. It is mainly useful for sniffing a line-oriented format such as CSV versus newline-delimited JSON without reading the whole source into memory. It uses duplexify and through2 to present one stable duplex stream while its actual parser is selected at runtime.

Verdict

peek-stream solves one awkward classic Node-stream problem in very little code, and it remains reasonable inside an old pipeline that already depends on it. For new code, its 2018 release, undocumented option, callback-only API, and absent types make a small purpose-built Transform easier to own.

API stability4/5The exported function has kept the same compact contract for years: pass options and a callback, inspect the sample, then call swap with either an error or a duplex parser. Numeric maxBuffer remains accepted as shorthand, and strict, maxBuffer, and newline behavior are visible in a single source file. The API is unlikely to surprise an existing consumer, though the lack of releases means compatibility with newer stream changes is accidental rather than continuously tested.
Docs2/5The README gives a complete CSV-versus-LDJSON example and clearly states the first-line, 65,535-byte, maxBuffer, and strict behavior. That is enough for the happy path. It never documents newline: false, object-mode selection, end-of-stream behavior, error propagation, whether sampled bytes are replayed, or the required shape of the swapped parser. An open issue about the missing newline option has remained unresolved since 2016, so source reading is required for safe use.
Maintenance1/5The latest npm release, 1.1.3, dates to March 2018, and the latest commit on the default branch is the matching release commit. GitHub is not archived, but its four open items include three unmerged pull requests, one from 2026 seeking a through2 upgrade and two from 2023, plus a documentation issue from 2016. The repository metadata shows a 2023 push only because of pull-request activity, not maintained package code.
Ecosystem3/5peek-stream recorded 3,648,661 downloads in the measured week, largely because it sits in established dependency trees. It composes with ordinary Node Transform streams and the README demonstrates csv-parser and ldjson-stream, so parser choice is open-ended. Yet the repository has only 58 stars, no TypeScript declarations, no ESM entry, and no plugin ecosystem; its relevance comes from transitively installed classic-stream software rather than current mindshare.

Use it if

  • You consume a Node byte stream whose parser can be selected reliably from its first line
  • You need to preserve streaming and backpressure instead of buffering an entire upload before format detection
  • Your existing pipeline is CommonJS and already uses classic through2-style Node streams
  • Parser selection may be asynchronous and fits the package's error-first swap callback
Skip it if

Setup reality

Install with npm install peek-stream and require it from CommonJS. There are no peer dependencies, native modules, credentials, or config files, but the package brings buffer-from, duplexify, and through2. You must also install every parser that your selector can return. The selector receives a Buffer containing the first line without its newline; it must call swap(error, parser), where parser is a readable and writable stream. peek-stream writes the sampled bytes and any overflow into that parser for you, so writing the sample yourself duplicates data. Selection can be asynchronous, but the incoming side remains pending until swap is called, and forgetting that callback stalls the pipeline. By default the library waits for LF, buffers at most 65,535 bytes, and then selects using the accumulated bytes even if no newline arrived. Set strict: true if a missing newline at the byte limit or at end-of-stream should be an error. A source-only newline: false option makes it select after maxBuffer rather than by line, but the README does not document it. Object-mode chunks bypass byte accumulation and cause selection immediately on the first non-string, non-Buffer value. Always attach an error handler or use stream.pipeline, because a selector error and a strict-mode framing error are emitted through the returned duplex. The package has no built-in format detection; ambiguous commas, byte-order marks, comments, and malformed first records are your responsibility.

Patterns

Choose between CSV and newline-delimited JSONselect-csv-or-json

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'));
});

source.pipe(parser).on('data', console.log);

peek-stream does no detection itself. Order tests from most specific to least specific because a JSON string can also contain commas.

Propagate source, selector, and destination errorsuse-stream-pipeline

const { pipeline } = require('node:stream');

pipeline(source, parser, destination, (err) => {
  if (err) console.error('parse failed:', err.message);
});

pipeline is safer than a bare pipe chain because selector errors and downstream failures reach one completion callback.

Cap the bytes used for detectionlimit-first-line

const parser = peek({ maxBuffer: 8192 }, (sample, swap) => {
  swap(null, chooseParser(sample));
});

Without strict mode, reaching maxBuffer triggers selection with the bytes collected so far even when no newline was found.

Reject an oversized or unterminated first linereject-missing-newline

const parser = peek(
  { maxBuffer: 8192, strict: true },
  (sample, swap) => swap(null, chooseParser(sample))
);

strict also errors when the input ends before any newline, even if the buffered content is shorter than maxBuffer.

Inspect a fixed-size prefix instead of a linepeek-fixed-prefix

const parser = peek(
  { maxBuffer: 16, newline: false },
  (prefix, swap) => swap(null, parserForMagicBytes(prefix))
);

newline: false exists in version 1.1.3 source but is missing from the README. Selection waits until maxBuffer bytes or end-of-stream.

Choose a parser asynchronouslyselect-parser-async

const parser = peek((sample, swap) => {
  lookupParser(sample).then(
    (selected) => swap(null, selected),
    (err) => swap(err)
  );
});

Input remains blocked until swap is called exactly once. Convert promise rejection to swap(err) or the pipeline can hang.

Fail closed on an unknown samplereject-unknown-format

const parser = peek((sample, swap) => {
  const magic = sample.subarray(0, 4).toString('hex');
  if (magic === '504b0304') return swap(null, unzipParser());
  swap(new Error(`unsupported magic bytes: ${magic}`));
});

Passing an error emits it from the returned duplex. Do not silently select a permissive fallback for untrusted formats.

Let peek-stream replay the inspected bytespreserve-sampled-data

const parser = peek((sample, swap) => {
  console.log('detected from', sample.length, 'bytes');
  swap(null, createParser());
  // Do not call createParser().write(sample) here.
});

The library writes sample and overflow to the selected parser after swap. Manually forwarding either chunk duplicates the beginning of the input.

Reject an empty stream explicitlyhandle-empty-input

const parser = peek((sample, swap) => {
  if (sample.length === 0) return swap(new Error('empty input'));
  swap(null, chooseParser(sample));
});

With strict disabled, end-of-stream calls the selector with an empty Buffer. A detector should decide whether that is valid.

Select immediately from an object-mode chunkroute-object-stream

const parser = peek((firstObject, swap) => {
  const selected = firstObject.type === 'event'
    ? eventTransform()
    : fallbackTransform();
  swap(null, selected);
});

objectSource.pipe(parser).pipe(objectDestination);

Any first chunk that is neither a Buffer nor a string bypasses newline and byte-limit logic. The selected stream must support the same object-mode data.

Alternatives

PackageRegistryPick it when
through2npmYou can implement the small amount of prefix buffering yourself and want direct control over stream behavior
streamxnpmYou want a maintained stream implementation for a new high-throughput pipeline and can write your own detector
readable-streamnpmYou need a userland copy of Node stream primitives with broad runtime compatibility rather than dynamic parser swapping