Streaming cross-platform unzip library for Node.js compatible with fstream and fs.ReadStream
npx @tessl/cli install tessl/npm-unzip@0.1.0Unzip is a streaming cross-platform unzip library for Node.js that provides simple APIs for parsing and extracting zip files. It offers compatibility with fstream and fs.ReadStream interfaces without compiled dependencies, using Node.js's built-in zlib support for inflation.
npm install unzipconst unzip = require('unzip');
// Or destructured
const { Parse, Extract } = require('unzip');ES6 modules (if supported by your Node.js version):
import unzip from 'unzip';
import { Parse, Extract } from 'unzip';const fs = require('fs');
const unzip = require('unzip');
// Extract entire zip archive to a directory
fs.createReadStream('path/to/archive.zip')
.pipe(unzip.Extract({ path: 'output/path' }));const fs = require('fs');
const unzip = require('unzip');
// Parse zip file and process individual entries
fs.createReadStream('path/to/archive.zip')
.pipe(unzip.Parse())
.on('entry', function (entry) {
const fileName = entry.path;
const type = entry.type; // 'Directory' or 'File'
const size = entry.size;
if (fileName === "target-file.txt") {
entry.pipe(fs.createWriteStream('output.txt'));
} else {
entry.autodrain(); // Important: drain unused entries
}
});Unzip is built around three core components:
The library uses a streaming approach for memory efficiency, processing zip files without loading entire contents into memory.
Parse zip files and access individual entries with streaming support.
/**
* Creates a streaming zip parser (can be called with or without 'new')
* @param {Object} opts - Configuration options
* @param {boolean} opts.verbose - Enable verbose logging (default: false)
* @returns {Parse} Transform stream for parsing zip archives
*/
function Parse(opts);
/**
* Factory method equivalent to new Parse(opts) - preferred way to create instances
* @param {Object} opts - Configuration options
* @returns {Parse} Transform stream instance
*/
Parse.create = function(opts);
/**
* Enhanced pipe method with fstream compatibility
* Automatically calls dest.add(entry) for each entry if destination has an add method
* @param {Object} dest - Destination stream or fstream Writer
* @param {Object} opts - Standard pipe options
* @returns {Object} The destination stream
*/
Parse.prototype.pipe = function(dest, opts);
/**
* Enhanced event listener registration that tracks 'entry' listeners
* When entry listeners are added, the parser will emit entry events and provide entry streams
* @param {string} type - Event type ('entry', 'error', 'close', etc.)
* @param {Function} listener - Event handler function
* @returns {Parse} This Parse instance for chaining
*/
Parse.prototype.addListener = function(type, listener);
Parse.prototype.on = function(type, listener); // Alias for addListenerThe Parse stream emits the following events:
Extract entire zip archives to filesystem directories.
/**
* Creates a writable stream for extracting zip archives to directories (can be called with or without 'new')
* @param {Object} opts - Configuration options
* @param {string} opts.path - Target extraction directory (required)
* @param {boolean} opts.verbose - Enable verbose logging (default: false)
* @returns {Extract} Writable stream for zip extraction
*/
function Extract(opts);The Extract stream emits the following events:
Individual zip entries are represented as Entry streams.
/**
* PassThrough stream representing a zip entry
* Note: Entry instances are created internally by Parse, not directly by users
* @constructor
*/
function Entry();
/**
* Automatically drain entry content to prevent memory issues
* Call this for entries you don't intend to process
*/
Entry.prototype.autodrain = function();Entry objects have the following properties (set by Parse during processing):
/**
* Parse stream class (extends Node.js Transform stream)
*/
class Parse {
constructor(opts);
// Enhanced pipe method with automatic fstream compatibility
// Calls dest.add(entry) for each entry if destination has add method
pipe(dest, opts);
// Enhanced event listener methods that track 'entry' listeners
// When entry listeners are registered, parser provides entry streams
addListener(type, listener);
on(type, listener); // Alias for addListener
// Standard Transform stream methods available
write(chunk, encoding, callback);
end();
// ... other Transform stream methods
}
/**
* Extract stream class (extends Node.js Writable stream)
*/
class Extract {
constructor(opts);
// Standard Writable stream methods available
write(chunk, encoding, callback);
end();
// ... other Writable stream methods
}
/**
* Entry stream class (extends Node.js PassThrough stream)
*/
class Entry {
constructor();
autodrain();
// Properties set by Parse during zip processing
path: string;
type: 'File' | 'Directory';
size: number;
props: object;
// Standard PassThrough stream methods available
pipe(destination, options);
read(size);
write(chunk, encoding, callback);
// ... other stream methods
}
/**
* Configuration options for Parse
*/
interface ParseOptions {
verbose?: boolean;
}
/**
* Configuration options for Extract
*/
interface ExtractOptions {
path: string;
verbose?: boolean;
}The library emits errors for various conditions:
const parser = unzip.Parse();
parser.on('error', function(err) {
console.error('Parse error:', err.message);
});
const extractor = unzip.Extract({ path: './output' });
extractor.on('error', function(err) {
console.error('Extract error:', err.message);
});const fs = require('fs');
const fstream = require('fstream');
const unzip = require('unzip');
const readStream = fs.createReadStream('archive.zip');
const writeStream = fstream.Writer('output/path');
readStream
.pipe(unzip.Parse())
.pipe(writeStream);fs.createReadStream('archive.zip')
.pipe(unzip.Parse())
.on('entry', function (entry) {
if (entry.path.endsWith('.txt')) {
// Extract only text files
entry.pipe(fs.createWriteStream(`output/${entry.path}`));
} else {
// Skip other files
entry.autodrain();
}
});Important: Always call entry.autodrain() for entries you don't process to prevent memory leaks:
fs.createReadStream('large-archive.zip')
.pipe(unzip.Parse())
.on('entry', function (entry) {
if (entry.type === 'Directory') {
// Skip directories
entry.autodrain();
} else if (entry.size > 1000000) {
// Skip large files
entry.autodrain();
} else {
// Process small files
entry.pipe(fs.createWriteStream(entry.path));
}
});The Parse class automatically detects when piped to fstream destinations and calls dest.add(entry) for each zip entry:
// Enhanced pipe method detects fstream Writers and calls add() automatically
fs.createReadStream("archive.zip")
.pipe(unzip.Parse())
.pipe(fstream.Writer("output/path")); // Automatically calls writeStream.add(entry)The Parse class optimizes behavior based on whether entry listeners are registered:
// Without entry listeners - entries are not processed into streams (memory efficient)
fs.createReadStream("archive.zip")
.pipe(unzip.Parse())
.pipe(fstream.Writer("output"));
// With entry listeners - entries become readable streams
fs.createReadStream("archive.zip")
.pipe(unzip.Parse())
.on("entry", function(entry) {
// Entry is now a readable stream with available properties
console.log("Processing:", entry.path, "Type:", entry.type);
if (entry.size) console.log("Size:", entry.size);
entry.autodrain(); // Important: drain if not processing
});