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

@portable/compress

v0.1.0

Published

Compress and decompress gzip and deflate in one call or through a bounded stream, and compute CRC-32, on CompressionStream, in any JavaScript runtime.

Readme

@portable/compress

One-shot gzip and deflate, a decompression stream with an output limit, and CRC-32, built on the platform's CompressionStream and DecompressionStream, so it ships no compression code of its own. Runs in browsers, Cloudflare Workers, Node 24+, Bun, and Deno.

Replaces the one-shot use of pako and fflate, and node:zlib's gzipSync, gunzipSync, deflateSync, inflateSync, and crc32.

npm install @portable/compress
import * as Compress from '@portable/compress';

const gzipped = await Compress.encode('hello world');
const bytes = await Compress.decode(gzipped, { maxBytes: 10_000_000 });

const response = await fetch('https://example.com/export.ndjson.gz');
const body = response.body!.pipeThrough(new Compress.DecoderStream({ maxBytes: 1_000_000_000 }));

API

| Export | Description | | --- | --- | | encode(data, { format, signal }) | Compresses a Uint8Array or a string (as UTF-8). Returns Promise<Uint8Array>. | | decode(data, { format, maxBytes, signal }) | Decompresses a Uint8Array. Returns Promise<Uint8Array>. A gzip file with several members decodes to all of them joined. | | new DecoderStream({ format, maxBytes, signal }) | A TransformStream<Uint8Array, Uint8Array> that decompresses. | | crc32(data, previous?) | The CRC-32 of a Uint8Array or a string (as UTF-8). Pass the previous result to continue a checksum over several pieces. | | Format | 'gzip' \| 'deflate' \| 'deflate-raw': gzip (RFC 1952), zlib (RFC 1950) and raw deflate (RFC 1951), the formats the platform supports. |

| Option | Default | Description | | --- | --- | --- | | format | 'gzip' | The compressed format. | | maxBytes | Infinity | The most bytes the output may hold. Past it, decode rejects, and DecoderStream fails and cancels its input, both with MaxBytesError. | | signal | | When it aborts, encode and decode reject with signal.reason, and DecoderStream fails with it and cancels its input. |

There is no compression level option and no dictionary, because CompressionStream has neither. To compress a stream, use the platform's CompressionStream.

Errors

| Error | When | | --- | --- | | TruncatedError | The input ended before the compressed stream did. | | InvalidInputError | The input is malformed, fails its checksum, or has bytes after its end. | | MaxBytesError | The output grew past maxBytes. Its maxBytes property holds the limit. |

The platform reports every failure as a TypeError, and each of these errors stores that TypeError as its cause.

Types

| Type | Description | | --- | --- | | Compress.Format | 'gzip', 'deflate' (zlib) or 'deflate-raw'. |

Why use this over pako, fflate, and node:zlib

pako and fflate implement deflate in JavaScript, although every runtime already ships it natively. pako is 45 kB minified (13 kB gzipped), and fflate ships its own inflate and deflate too. node:zlib doesn't run in browsers, and Cloudflare Workers need the nodejs_compat flag for it.

  • decode checks the gzip CRC-32 and length. fflate checks neither, so corrupt data decodes to wrong bytes, and fflate allocates its output from the length field, so a forged length gives truncated output (fflate #161, #181).
  • maxBytes stops a zip bomb. pako and fflate have no output limit (pako #310).
  • decode and DecoderStream throw TruncatedError for truncated input instead of returning partial output, and InvalidInputError for a bad trailer, in every runtime. Deno's DecompressionStream returns partial output for a cut-short deflate stream and reports a bad gzip trailer as input that ended early, so on Deno they check the end of the stream and the trailer themselves.
  • decode reads every member of a multi-member gzip file, such as BGZF or cat a.gz b.gz. fflate returns only the first member (fflate #102), and DecompressionStream throws.
  • DecoderStream joins input chunks that arrive while the decompressor is busy. DecompressionStream's output chunks are only as large as the input chunk allows, so 1 KiB network chunks of gzip give about 4 KiB of output each, and every chunk adds the per-chunk cost of the decompressor and of each later stage. On Node, 32 MB of gzipped JSON sent as 1 KiB chunks, followed by three more transforms, decoded in 196 ms with DecoderStream and 253 ms with DecompressionStream, with 3,728 output chunks instead of 8,346.

In Node, decode gunzips more slowly than fflate and node:zlib, because Node's DecompressionStream passes each 16 KiB of output through the libuv thread pool, and on a busy machine each pass waits for a thread. crc32 runs in JavaScript at about the speed of the crc-32 package, and node:zlib's runs in native code.

Operations per second from pnpm bench, higher is better, in Node 24.22.0, on Intel(R) Xeon(R) Processor @ 2.10GHz (4 cores). Each benchmark lists only the libraries that can do the same work, fastest first. The relative speed compares each library with @portable/compress, and the bars are scaled to the fastest library.

| Library | Version | Minified and gzipped | Runtime dependencies | Node built-ins | TypeScript types | | --- | --- | ---: | ---: | --- | --- | | @portable/compress | – | 2.4 kB | 2 | none | included | | pako | 3.0.2 | 13 kB | 0 | none | included | | fflate | 0.8.3 | 12 kB | 0 | none | included | | crc-32 | 1.2.2 | 1.1 kB | 0 | none | included | | node:zlib | – | built in | – | Node only | – | | DecompressionStream | – | built in | – | none | – |

Sizes cover the imports in the usage example, or the equivalent imports of each library, bundled for the browser by scripts/bundle-size.ts. Runtime dependencies count every package installed with the published package. A library that uses Node built-ins needs polyfills in a browser and the nodejs_compat flag in Cloudflare Workers.

Differences to check when you switch:

  • pako, fflate, and node:zlib have sync functions. Every function here is async, because CompressionStream is.
  • The strategy option of pako, fflate, and node:zlib has no equivalent. Keep fflate if you need a level, a dictionary, or a strategy.
  • node:zlib and pako ignore bytes after the end of the data, including a second zlib stream and zero padding after a gzip member. decode and DecoderStream fail with InvalidInputError on them.
  • zlib.createGunzip() reads every gzip member. DecoderStream reads one, as DecompressionStream does, and fails with InvalidInputError on a second. decode reads them all.
  • pako decodes a zlib stream whose header declares a smaller window than the data uses. decode decodes it too in Chromium, and throws InvalidInputError in Node.