Read Streams With a Byte Limit

Read an async iterable stream into a string while enforcing a hard byte ceiling and destroying the source as soon as it overflows.

JavascriptSecurity Boundaries

Code

snippet
import { Buffer } from "node:buffer";
import { Readable } from "node:stream";

export async function readStreamWithLimit(stream, maxBytes, options = {}) {
  if (stream === null || typeof stream !== "object" || typeof stream[Symbol.asyncIterator] !== "function") {
    throw new TypeError("stream must be an async iterable readable stream");
  }
  if (!Number.isInteger(maxBytes) || maxBytes < 1) {
    throw new RangeError("maxBytes must be a positive integer");
  }
  const { encoding = "utf8" } = options;
  const chunks = [];
  let total = 0;
  try {
    for await (const chunk of stream) {
      const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
      total += buffer.length;
      if (total > maxBytes) {
        if (typeof stream.destroy === "function") stream.destroy();
        throw new Error(`stream exceeded the ${maxBytes} byte limit`);
      }
      chunks.push(buffer);
    }
  } finally {
    if (typeof stream.destroy === "function" && !stream.destroyed) stream.destroy();
  }
  return Buffer.concat(chunks).toString(encoding);
}

const body = Readable.from(["hello ", "world"]);
console.log(await readStreamWithLimit(body, 64));
try {
  await readStreamWithLimit(Readable.from(["0123456789"]), 4);
} catch (error) {
  console.log(error.message);
}