fzstd

High-performance, pure JavaScript Zstandard decompression in an 8kB package with streaming support.

Library
npm
v0.1.1
321stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
37/100Needs Attention
Development Activity0
Maintenance20
Community44
Maturity56
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
66/100Good
Architecture78
Code Quality58
Innovation72
Learning Curve55

fzstd is a pure JavaScript implementation of Zstandard decompression, built for environments where pulling in a WebAssembly Zstandard port is impractical or heavy for the payload size. Written by 101arrowz (also the author of fflate, a popular zlib implementation), it decodes Zstandard frames end to end — magic-number detection, FSE and Huffman entropy decoding, block reconstruction, and sliding-window backreferences — entirely in compiled TypeScript, at roughly 8kB minified and 3.8kB gzipped.

Its core decompress() function handles buffered decompression with an optional pre-allocated output buffer, while the Decompress class exposes a chunk-based push/ondata streaming API for processing data incrementally without holding an entire payload in memory. Because it needs neither WASM instantiation nor advance knowledge of the decompressed size, fzstd trades roughly 30-40% raw throughput versus WASM builds for a dramatically smaller footprint and genuine streaming support across the browser, Node.js, and Deno.

What You Get

  • Zero runtime dependencies - a single ~8kB minified module with no WASM binary or native bindings to bundle or load.
  • One-shot decompress() API - decompress a Zstandard-compressed Uint8Array in one call, with an optional pre-sized output buffer for better performance.
  • Streaming Decompress class - push data chunks incrementally via .push() and receive decoded output through an .ondata callback, ideal for large files or live streams.
  • Typed error codes - a ZstdErrorCode enum (InvalidData, WindowSizeTooLarge, DistanceTooFarBack, etc.) attached to thrown errors for precise error handling.
  • Universal module targets - ships CJS, ESM, and UMD builds so it works in Node.js, bundlers, and directly from a CDN <script> tag.

Common Use Cases

  • Decompressing .zst assets in the browser - fetch a Zstandard-compressed file over HTTP and decode it client-side without a WASM dependency.
  • Streaming decompression of large files - process multi-hundred-megabyte payloads in fixed-size chunks to avoid holding the whole decompressed buffer in memory.
  • Decoding Zstandard data in size-constrained bundles - use fzstd instead of a WASM zstd port when bundle size matters more than raw throughput.
  • Cross-runtime decompression - decode Zstandard payloads consistently across Node.js, Deno, and browser environments from one codebase.

Under The Hood

Architecture The entire decoder lives in one file, src/index.ts (~770 lines), implementing the Zstandard decode pipeline as a flat set of procedural stages: frame-header parsing (rzfh), FSE table construction (rfse), Huffman table construction and decode (rhu, dhu/dhu4), and block reconstruction (rzb) that handles raw, RLE, and compressed block types with sliding-window backreference resolution. State is threaded through a single DZstdState object with deliberately terse, single-letter keys to minimize minified size, rather than a class hierarchy or dependency-injected services; the two public entry points, decompress() (buffered) and the Decompress class (streaming, push/ondata), both drive the same underlying state machine. Because every stage destructures the same state object, changing its shape would ripple through nearly every function in the file.

Tech Stack Written entirely in TypeScript with no runtime dependencies — devDependencies are limited to typescript, terser (for UMD minification), ts-node, @types/node, and zstandard-wasm (used only in test scripts as a reference implementation, not at runtime). The build pipeline (scripts/buildUMD.ts, scripts/rewriteBuilds.ts) compiles CJS and ESM output via tsc and then hand-rolls a minified UMD bundle, publishing all three targets (main, module, unpkg/jsdelivr) so the same package works via require, import, or a CDN <script> tag, including Deno via Skypack.

Code Quality Tests live in tests/simple_cases_test.ts, run under Deno’s test runner against a handful of golden-vector cases (short text, a Lorem Ipsum paragraph, and one million repeated null bytes) — a real but narrow suite that doesn’t exercise the streaming API or error paths directly. Error handling is explicit and typed: a shared err() helper throws real Error objects tagged with a ZstdErrorCode from a fixed enum, with Error.captureStackTrace support, rather than swallowing failures. Naming is intentionally terse (single- and double-letter identifiers) traded for minified bundle size, but is consistently accompanied by inline comments explaining each abbreviation. There is no visible ESLint/Prettier config or CI workflow in the repository, and TypeScript typing is thorough throughout (explicit interfaces for HDT, FSEDT, DZstdState, and exported ZstdError/ZstdStreamHandler types).

What Makes It Unique Most fast Zstandard implementations for JavaScript environments are WebAssembly ports, which are typically 30-40% faster but come with two costs fzstd deliberately avoids: many WASM ports can’t stream (they allocate one large buffer up front) and some require the caller to know the decompressed size in advance. fzstd reads window size and content size directly from the Zstandard frame header, decides its own memory allocation, and supports genuine incremental streaming through its push/ondata API — all while staying dependency-free and around 8kB minified, at the cost of pure-JS execution speed on very large payloads.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search