npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

bzip2-codec

v1.1.0

Published

Dependency-free bzip2 compression and decompression for Web Streams

Readme

bzip2-codec

Dependency-free bzip2 compression and decompression for JavaScript, with WHATWG TransformStream APIs for files that should not be loaded entirely into memory.

The package is ESM-only and requires Node.js 22.12 or newer. The stream APIs also work in modern runtimes that provide the standard Web Streams globals.

Install

npm install bzip2-codec

Stream a large file

createDecompressionStream() accepts arbitrary Uint8Array input chunks and emits decompressed Uint8Array chunks:

import { createReadStream, createWriteStream } from 'node:fs';
import { Readable, Writable } from 'node:stream';
import { createDecompressionStream } from 'bzip2-codec';

const source = Readable.toWeb(createReadStream('archive.bz2')) as ReadableStream<Uint8Array>;
const destination = Writable.toWeb(createWriteStream('archive'));

await source.pipeThrough(createDecompressionStream()).pipeTo(destination);

Compression uses the same shape:

import { createReadStream, createWriteStream } from 'node:fs';
import { Readable, Writable } from 'node:stream';
import { createCompressionStream } from 'bzip2-codec';

const source = Readable.toWeb(createReadStream('archive')) as ReadableStream<Uint8Array>;
const destination = Writable.toWeb(createWriteStream('archive.bz2'));

await source.pipeThrough(createCompressionStream({ blockSize: 9 })).pipeTo(destination);

These are standard TransformStream<Uint8Array, Uint8Array> instances, so they compose with pipeThrough() and pipeTo() in browsers as well as Node.js.

Bzip2's Burrows-Wheeler transform operates on complete blocks. The streaming implementation therefore keeps the current block and its working data in memory, but never needs to collect the complete file. The default block size is 900,000 bytes and output is emitted in 64 KiB chunks.

In-memory API

For small values, the convenience functions return one Uint8Array:

import { compress, decompress } from 'bzip2-codec';

const encoded = compress(new TextEncoder().encode('hello'));
const decoded = decompress(encoded);

Unlike the stream APIs, compress() and decompress() necessarily collect the complete result in memory.

API

createCompressionStream(options?)

Returns a TransformStream<Uint8Array, Uint8Array>.

interface CompressOptions {
	/** Block size in units of 100,000 bytes. Default: 9. */
	blockSize?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
	/** Maximum size of each emitted chunk. Default: 65,536. */
	outputChunkSize?: number;
}

Larger blocks generally improve compression at the cost of memory and latency.

createDecompressionStream(options?)

Returns a TransformStream<Uint8Array, Uint8Array>. Input chunk boundaries have no relationship to bzip2 block boundaries and may occur at any byte.

interface DecompressOptions {
	/** Decode adjacent bzip2 members. Default: true. */
	concatenated?: boolean;
	/** Reject or ignore bytes after the final decoded member. Default: 'error'. */
	trailingData?: 'error' | 'ignore';
	/** Maximum total decompressed bytes. Default: Infinity. */
	maxOutputBytes?: number;
	/** Maximum size of each emitted chunk. Default: 65,536. */
	outputChunkSize?: number;
}

interface DecompressionStreamOptions extends DecompressOptions {
	/** Yield between decoded blocks after this much work. Disabled by default. */
	yieldAfterMs?: number;
}

Set maxOutputBytes when decoding untrusted input to enforce an application-specific expansion limit. A block is checksum-validated before any of its output is emitted.

Set yieldAfterMs when decompression shares a JavaScript thread with latency-sensitive work:

await source.pipeThrough(createDecompressionStream({ yieldAfterMs: 8 })).pipeTo(destination);

When the elapsed decoding time reaches the configured budget, the transformer yields to the event loop after the current checksum-validated bzip2 block. An individual block remains an atomic unit, so the interval is a responsiveness target rather than a hard deadline. Omitting yieldAfterMs retains the synchronous, maximum-throughput path without scheduling timers.

compress(input, options?)

Compresses a Uint8Array and returns a new Uint8Array. It accepts CompressOptions.

decompress(input, options?)

Decompresses a Uint8Array and returns a new Uint8Array. It accepts DecompressOptions.

Errors

Malformed data, checksum failures, output limits, and invalid decoder state reject the stream or throw a BzipError:

import { BzipError, decompress } from 'bzip2-codec';

try {
	decompress(data);
} catch (error) {
	if (error instanceof BzipError) {
		console.error(error.code, error.byteOffset, error.member, error.block);
	}
}

BzipError.code is stable for programmatic handling. Depending on where an error occurs, the instance also includes byteOffset, bitOffset, member, block, expected, and actual details. Invalid API arguments use the standard TypeError or RangeError classes.

Development

bun install
bun run typecheck
npm test
npm run test:interop
bun run build

npm run test:large additionally streams a local large fixture into a hash sink without collecting its output. Large fixtures in test_files/ are deliberately excluded from Git and npm packages.

To compare total peak resident memory for the in-memory API, a native Bun.file() stream, and a Node file-stream adapter, run:

bun run memory:decompress
bun run memory:decompress -- path/to/another-file.bz2

Each method runs in a fresh Bun process. The report verifies that every method produced the same byte count and SHA-256 digest, then obtains lifetime peak RSS from Bun's documented subprocess.resourceUsage() API.

License

GPL-3.0-or-later. See LICENSE for the full terms and NOTICE for third-party attributions.