Skip to content

oorabona/node-liblzma

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

482 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Node-liblzma

NPM Version NPM Downloads CI Status Documentation License Node Version TypeScript npm provenance codecov code style: biome

Native Node.js bindings for liblzma — XZ/LZMA2 compression with browser support via WebAssembly.

Table of Contents

What is liblzma/XZ?

XZ is a container for compressed archives. It offers one of the best compression ratios available, with a good balance between compression time and decompression speed/memory.

Only LZMA2 is supported for compression output. But the library can open and read any LZMA1 or LZMA2 compressed file.

Quick Start

npm install node-liblzma
import { xzAsync, unxzAsync, createXz, createUnxz } from 'node-liblzma';

// Simple: Compress a buffer
const compressed = await xzAsync(Buffer.from('Hello, World!'));
const decompressed = await unxzAsync(compressed);

// Streaming: Compress a file
import { createReadStream, createWriteStream } from 'fs';

createReadStream('input.txt')
  .pipe(createXz())
  .pipe(createWriteStream('output.xz'));

// With progress monitoring
const compressor = createXz();
compressor.on('progress', ({ bytesRead, bytesWritten }) => {
  console.log(`${bytesRead} bytes in → ${bytesWritten} bytes out`);
});
Promise style (with .then())
import { xzAsync, unxzAsync } from 'node-liblzma';

xzAsync(Buffer.from('Hello, World!'))
  .then(compressed => {
    console.log('Compressed size:', compressed.length);
    return unxzAsync(compressed);
  })
  .then(decompressed => {
    console.log('Decompressed:', decompressed.toString());
  })
  .catch(err => {
    console.error('Compression failed:', err);
  });
Callback style (Node.js traditional)
import { xz, unxz } from 'node-liblzma';

xz(Buffer.from('Hello, World!'), (err, compressed) => {
  if (err) throw err;
  unxz(compressed, (err, decompressed) => {
    if (err) throw err;
    console.log('Decompressed:', decompressed.toString());
  });
});

📖 Full API documentation: oorabona.github.io/node-liblzma

What's New

v3.0.0 — Browser & WASM Support

Live Demo — Try XZ compression in your browser.

  • Browser/WASM support: Full XZ compression and decompression via WebAssembly
    • Same API as Node.js (xzAsync, unxzAsync, createXz, createUnxz)
    • WASM binary: ~52KB gzipped (under 100KB budget)
    • Web Streams API for streaming compression/decompression
    • Zero-config inline mode: import from 'node-liblzma/inline'
  • CLI tool (nxz): Portable xz-like command line tool
    • Full xz compatibility: -z, -d, -l, -k, -f, -c, -o, -v, -q
    • Compression presets 0-9 with extreme mode (-e)
    • tar.xz archive support: create, list, extract (compatible with system tar)
    • Progress display, stdin/stdout piping, benchmarking (-B)
  • tar-xz package: Create/extract .tar.xz archives — Node.js streaming + browser WASM
  • Progress events: Monitor compression/decompression in real-time
  • XZ Utils 5.8.x: Updated to latest stable version
  • 519 tests: Comprehensive test suite with 100% code coverage

v2.0.0 — TypeScript Modernization

  • Full TypeScript migration: Complete rewrite from CoffeeScript
  • Promise-based APIs: xzAsync() and unxzAsync()
  • Modern tooling: Vitest, Biome, pnpm, pre-commit hooks
  • Node.js >= 16 required (updated from >= 12)
Legacy (N-API migration)

In previous versions, N-API replaced nan as the stable ABI for native modules.

Tested on: Linux x64, macOS (x64/arm64), Raspberry Pi 2/3/4, Windows.

Prebuilt binaries are bundled for: Windows x64, Linux x64, macOS x64/arm64.

Flag Description Default Values
USE_GLOBAL Use system liblzma library yes (no on Windows) yes, no
RUNTIME_LINK Static or shared linking shared static, shared
ENABLE_THREAD_SUPPORT Enable thread support yes yes, no

If no prebuilt binary matches your platform, node-gyp will compile from source automatically.

Browser Usage

Live Demo — Try XZ compression in your browser right now.

node-liblzma v3.0.0+ supports XZ compression in the browser via WebAssembly. The same API works in both Node.js and browsers — bundlers (Vite, Webpack, esbuild) automatically resolve the WASM-backed implementation.

Basic Usage

// Bundlers auto-resolve to WASM in browser, native in Node.js
import { xzAsync, unxzAsync, isXZ } from 'node-liblzma';

// Compress
const compressed = await xzAsync('Hello, browser!');

// Decompress
const original = await unxzAsync(compressed);

// Check if data is XZ-compressed
if (isXZ(someBuffer)) {
  const data = await unxzAsync(someBuffer);
}

Streaming with Web Streams API

import { createXz, createUnxz } from 'node-liblzma';

// Compress a fetch response
const response = await fetch('/large-file.bin');
const compressed = response.body.pipeThrough(createXz({ preset: 6 }));

// Decompress
const decompressed = compressedStream.pipeThrough(createUnxz());

Import Modes

Import When to use
node-liblzma Standard — bundler resolves to WASM (browser) or native (Node.js)
node-liblzma/wasm Explicit WASM usage in Node.js (no native addon needed)
node-liblzma/inline Zero-config — WASM embedded as base64 (no external file to serve)
// Explicit WASM (works in Node.js too, no native build required)
import { xzAsync } from 'node-liblzma/wasm';

// Inline mode (larger bundle, but no WASM file to configure)
import { ensureInlineInit, xzAsync } from 'node-liblzma/inline';
await ensureInlineInit(); // Decodes embedded base64 WASM
const compressed = await xzAsync(data);

Browser Limitations

  • No sync APIs: xzSync() / unxzSync() throw LZMAError in browsers
  • Presets 0-6 only: Presets 7-9 require more memory than WASM's 256MB limit
  • No filesystem: xzFile() / unxzFile() are not available
  • No Node Streams: Use createXz() / createUnxz() (Web TransformStream) instead of Xz / Unxz classes

Bundle Size

Component Raw Gzipped
liblzma.wasm ~107KB ~52KB
Glue code (liblzma.js) ~6KB ~2KB
Total ~113KB ~54KB

For detailed browser setup instructions, see docs/BROWSER.md.

CLI Tool (nxz)

This package includes nxz, a portable xz-like CLI tool that works on any platform with Node.js.

Installation

# Global installation (recommended for CLI usage)
npm install -g node-liblzma

# Then use directly
nxz --help

Quick Examples

# Compress a file (creates file.txt.xz, deletes original)
nxz file.txt

# Decompress (auto-detected from .xz extension)
nxz file.txt.xz

# Keep original file (-k)
nxz -k file.txt

# Maximum compression (-9) with extreme mode (-e)
nxz -9e large-file.bin

# Compress to stdout (-c) for piping
nxz -c file.txt > file.txt.xz

# List archive info (-l / -lv for verbose)
nxz -l file.txt.xz

# Benchmark native vs WASM performance (-B)
nxz -B file.txt

tar.xz Archives

nxz can create, list, and extract .tar.xz archives — auto-detected from file extension:

# Create a tar.xz archive from files/directories
nxz -T src/ lib/ README.md -o project.tar.xz

# List archive contents
nxz -Tl project.tar.xz

# Extract archive to a directory
nxz -Td project.tar.xz -o output/

# Extract with path stripping (like tar --strip-components)
nxz -Td project.tar.xz --strip=1 -o output/

Archives created by nxz are fully compatible with system tar -xJf.

All Options

Option Long Description
-z --compress Force compression mode
-d --decompress Force decompression mode
-l --list List archive information
-T --tar Treat file as tar.xz archive (auto-detected for .tar.xz/.txz)
-B --benchmark Benchmark native vs WASM performance
-k --keep Keep original file (don't delete)
-f --force Overwrite existing output file
-c --stdout Write to stdout, keep original file
-o --output=FILE Write output to specified file (or directory for tar extract)
-v --verbose Show progress for large files
-q --quiet Suppress warning messages
-0..-9 Compression level (default: 6)
-e --extreme Extreme compression (slower)
--strip=N Strip N leading path components on tar extract
-h --help Show help
-V --version Show version
One-shot usage (without global install)
# Standalone nxz-cli package (recommended — smaller, faster install)
npx nxz-cli --help
pnpm dlx nxz-cli --help

# Or via the full node-liblzma package
npx --package node-liblzma nxz --help
pnpm dlx --package node-liblzma nxz --help

Exit Codes

Code Meaning
0 Success
1 Error (file not found, format error, etc.)
130 Interrupted (SIGINT/Ctrl+C)

Ecosystem Packages

node-liblzma powers a family of focused packages:

Package Description Install
node-liblzma Core XZ library — Node.js native + browser WASM npm i node-liblzma
tar-xz Create/extract .tar.xz archives — Node.js + browser npm i tar-xz
nxz-cli Standalone CLI — npx nxz-cli file.txt npx nxz-cli

tar-xz — tar.xz archives

Live Demo — Create and extract tar.xz archives in your browser.

A library for working with .tar.xz archives, with dual Node.js (streaming) and browser (buffer-based) APIs. This fills the gap left by node-tar which does not support .tar.xz.

// Node.js — streaming API
import { create, extract, list } from 'tar-xz';

await create({ file: 'archive.tar.xz', cwd: './src', files: ['index.ts', 'utils.ts'] });
const entries = await list({ file: 'archive.tar.xz' });
await extract({ file: 'archive.tar.xz', cwd: './output' });

// Browser — buffer-based API
import { createTarXz, extractTarXz, listTarXz } from 'tar-xz';

const archive = await createTarXz({ files: [{ name: 'hello.txt', content: data }] });
const files = await extractTarXz(archive);
const entries = await listTarXz(archive);

nxz-cli — standalone CLI

A lightweight wrapper package for running nxz without installing the full node-liblzma:

# No install needed
npx nxz-cli file.txt              # compress
npx nxz-cli -d file.txt.xz        # decompress
npx nxz-cli -T src/ -o app.tar.xz # create tar.xz archive

# Or install globally
npm install -g nxz-cli

API Reference

API Comparison with Zlib

The API mirrors Node.js Zlib for easy adoption:

// CommonJS
var lzma = require('node-liblzma');

// TypeScript / ES6 modules
import * as lzma from 'node-liblzma';
Zlib node-liblzma Arguments
createGzip createXz ([options])
createGunzip createUnxz ([options])
gzip xz (buf, [options], callback)
gunzip unxz (buf, [options], callback)
gzipSync xzSync (buf, [options])
gunzipSync unxzSync (buf, [options])
- xzAsync (buf, [options])Promise<Buffer>
- unxzAsync (buf, [options])Promise<Buffer>
- xzFile (input, output, [options])Promise<void>
- unxzFile (input, output, [options])Promise<void>

Options

Attribute Type Description Values
check number Integrity check check.NONE, check.CRC32, check.CRC64, check.SHA256
preset number Compression level (0-9) preset.DEFAULT (6), preset.EXTREME
mode number Compression mode mode.FAST, mode.NORMAL
threads number Thread count 0 = auto (all cores), 1 = single-threaded, N = N threads
filters array Filter chain filter.LZMA2, filter.X86, filter.ARM, etc.
chunkSize number Processing chunk size Default: 64KB

For further information, see the XZ SDK documentation.

Advanced Configuration

Thread Support

Multi-threaded compression is available when built with ENABLE_THREAD_SUPPORT=yes (default).

Value Behavior
0 Auto-detect: use all available CPU cores
1 Single-threaded (default)
N Use exactly N threads
import { createXz, hasThreads } from 'node-liblzma';

if (hasThreads()) {
  const compressor = createXz({ threads: 0 }); // auto-detect
}

Note: Threads only apply to compression, not decompression.

Progress Monitoring

Track compression and decompression progress in real-time:

import { createXz, createUnxz } from 'node-liblzma';

const compressor = createXz({ preset: 6 });

compressor.on('progress', ({ bytesRead, bytesWritten }) => {
  const ratio = bytesWritten / bytesRead;
  console.log(`Progress: ${bytesRead} in, ${bytesWritten} out (ratio: ${ratio.toFixed(2)})`);
});

inputStream.pipe(compressor).pipe(outputStream);

Progress events fire after each chunk is processed. Works with streams, not buffer APIs.

Concurrency Control with LZMAPool

For production environments with high concurrency needs:

import { LZMAPool } from 'node-liblzma';

const pool = new LZMAPool(10); // Max 10 concurrent operations

pool.on('metrics', (metrics) => {
  console.log(`Active: ${metrics.active}, Queued: ${metrics.queued}`);
});

const compressed = await pool.compress(buffer);
const decompressed = await pool.decompress(compressed);

File Compression Helpers

import { xzFile, unxzFile } from 'node-liblzma';

await xzFile('input.txt', 'output.txt.xz');
await unxzFile('output.txt.xz', 'restored.txt');

// With options
await xzFile('large-file.bin', 'compressed.xz', { preset: 9, threads: 4 });

Handles files > 512MB automatically via streams with lower memory footprint.

Error Handling

Typed error classes for precise error handling:

import {
  xzAsync, LZMAError, LZMAMemoryError, LZMADataError, LZMAFormatError
} from 'node-liblzma';

try {
  const compressed = await xzAsync(buffer);
} catch (error) {
  if (error instanceof LZMAMemoryError) {
    console.error('Out of memory:', error.message);
  } else if (error instanceof LZMADataError) {
    console.error('Corrupt data:', error.message);
  } else if (error instanceof LZMAFormatError) {
    console.error('Invalid format:', error.message);
  }
}

Available error classes: LZMAError (base), LZMAMemoryError, LZMAMemoryLimitError, LZMAFormatError, LZMAOptionsError, LZMADataError, LZMABufferError, LZMAProgrammingError.

Buffer Size Optimization

const stream = createXz({
  preset: lzma.preset.DEFAULT,
  chunkSize: 256 * 1024  // 256KB chunks (default: 64KB)
});
File Size Recommended chunkSize
< 1MB 64KB (default)
1-10MB 128-256KB
> 10MB 512KB-1MB

Maximum buffer size: 512MB per operation (security limit). For larger files, use streaming APIs.

Async Callback Contract (errno-based)

The low-level native callback follows an errno-style contract matching liblzma behavior:

  • Signature: (errno: number, availInAfter: number, availOutAfter: number)
  • Success: errno is LZMA_OK or LZMA_STREAM_END
  • Error: any other errno value

High-level APIs remain ergonomic — Promise functions resolve to Buffer or reject with Error, stream users listen to error events.

Benchmark

Performance Hierarchy

All three backends use the same liblzma library and produce identical compression ratios:

System xz  >  nxz native (C++ addon)  >  nxz WASM (Emscripten)
 fastest        ~1-2x slower               ~2-5x slower (decompress)
                                           ~1x (compress, large files)

Full Comparison (246 KB source code, preset 6)

Backend Compress Decompress Size Environment
System xz 5.8 81 ms 4 ms 76.7 KB C binary
nxz native 90 ms 3.4 ms 76.7 KB Node.js + C++ addon
nxz WASM 86 ms 7.9 ms 76.7 KB Node.js + Emscripten

Native vs WASM (nxz -B, preset 6)

Data Compress Decompress Notes
1 KB text WASM 2.8x slower WASM 4.9x slower Startup overhead dominates
135 KB binary ~1:1 WASM 2x slower Compression near-parity
246 KB source ~1:1 WASM 2.3x slower Realistic workload
1 MB random ~1:1 WASM 1.6x slower Gap narrows with size
Running benchmarks
# Compare nxz (native) vs system xz across file sizes
./scripts/benchmark.sh
./scripts/benchmark.sh -s 1,50,200 -p 6,9         # custom sizes/presets
./scripts/benchmark.sh -o csv > results.csv        # export as CSV/JSON

# Compare native addon vs WASM backend
nxz --benchmark file.txt
nxz -B -3 large-file.bin                           # with preset 3

When to Use What

Scenario Recommended
Browser WASM (only option)
Node.js, performance-critical Native addon
Node.js, no C++ toolchain available WASM (node-liblzma/wasm)
Cross-platform scripts nxz CLI
Batch processing many files System xz
CI/CD with Node.js already installed nxz CLI

Installation

npm install node-liblzma
# or
pnpm add node-liblzma

System Libraries

If prebuilt binaries don't match your platform, install system development libraries:

# Debian/Ubuntu
sudo apt-get install liblzma-dev

# macOS
brew install xz

# Windows (automatic download and build)
npm install node-liblzma --build-from-source

Build from Source

# Force rebuild with default options
npm install node-liblzma --build-from-source

# Disable thread support
ENABLE_THREAD_SUPPORT=no npm install node-liblzma --build-from-source
Custom XZ installation

If you compiled XZ outside of node-liblzma:

export CPATH=$HOME/path/to/headers
export LIBRARY_PATH=$HOME/path/to/lib
export LD_LIBRARY_PATH=$HOME/path/to/lib:$LD_LIBRARY_PATH
npm install

Testing

pnpm test              # Run all 519 tests
pnpm test:watch        # Watch mode
pnpm test:coverage     # Coverage report
pnpm type-check        # TypeScript type checking

Tests use Vitest with 100% code coverage across statements, branches, functions, and lines.

Migration Guide

v1.x → v2.0

Breaking changes and new features

Breaking Changes

  1. Node.js >= 16 required (was >= 12)
  2. ESM module format (import instead of require)
  3. TypeScript source (CoffeeScript removed)

New Features

// Promise-based APIs (recommended)
const compressed = await xzAsync(buffer);

// Typed error classes
import { LZMAMemoryError, LZMADataError } from 'node-liblzma';

// Concurrency control
const pool = new LZMAPool(10);
const results = await Promise.all(files.map(f => pool.compress(f)));

// File helpers
await xzFile('input.txt', 'output.txt.xz');

Tooling Changes

  • Linter: Biome (replaces ESLint + Prettier)
  • Tests: Vitest (replaces Mocha)
  • Package Manager: pnpm recommended

Contributing

We welcome contributions! See the full contributing guidelines below.

Development Setup

git clone https://github.com/oorabona/node-liblzma.git
cd node-liblzma
pnpm install
pnpm build
pnpm test

Development Workflow

pnpm test              # Run tests
pnpm test:watch        # Watch mode
pnpm test:coverage     # Coverage report
pnpm check             # Lint + format check (Biome)
pnpm check:write       # Auto-fix lint/format
pnpm type-check        # TypeScript types

We follow Conventional Commits:

feat: add LZMAPool for concurrency control
fix: resolve memory leak in FunctionReference
docs: add migration guide for v2.0

Pull Request Process

  1. Fork and create a feature branch (feat/, fix/, refactor/, docs/)
  2. Add tests for new functionality (100% coverage required)
  3. Run pnpm check:write && pnpm type-check && pnpm test
  4. Commit with conventional commits and push
  5. CI checks run automatically on PR

Troubleshooting

Build issues

"Cannot find liblzma library" — Install system dev package:

sudo apt-get install liblzma-dev    # Debian/Ubuntu
brew install xz                     # macOS

"node-gyp rebuild failed" — Install build tools:

sudo apt-get install build-essential python3    # Linux
xcode-select --install                          # macOS
npm install --global windows-build-tools        # Windows

"Prebuilt binary not found" — Build from source:

npm install node-liblzma --build-from-source
Runtime issues

LZMAMemoryError — Input too large for buffer API. Use streaming:

createReadStream('large.bin').pipe(createXz()).pipe(createWriteStream('large.xz'));

LZMADataError — File is not XZ-compressed or is corrupted. Verify with file compressed.xz or xz -t compressed.xz.

Slow on multi-core — Enable threads: createXz({ threads: 0 }) (auto-detect cores).

High memory with concurrency — Use LZMAPool to limit simultaneous operations.

Windows-specific

Build fails — Install Visual Studio Build Tools and set Python:

npm install --global windows-build-tools
npm config set python python3

Path issues — Use path.join() instead of hardcoded separators.

Related Projects

  • tar-xz — Create/extract tar.xz archives (powered by node-liblzma)
  • nxz-cli — Standalone CLI for XZ compression
  • lzma-purejs — Pure JavaScript LZMA implementation
  • node-xz — Node binding of XZ library
  • lzma-native — Complete XZ library bindings

Bugs

If you find one, feel free to contribute and post a new issue! PRs are accepted as well :)

If you compile with threads, you may see warnings about -Wmissing-field-initializers. This is normal and does not prevent threading from being active and working.

Acknowledgements

Kudos to addaleax for helping out with C++ stuff!

License

This software is released under LGPL-3.0+.

About

Handle Xz as easily as Zlib

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 5