CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-unzipper

Cross-platform streaming API for ZIP file extraction and manipulation in Node.js environments

Pending
Overview
Eval results
Files

open.mddocs/

Random Access Interface

Promise-based API for opening ZIP files from multiple sources (filesystem, URLs, S3, buffers) and accessing their contents with random access patterns. This high-level interface provides convenient methods for reading ZIP files without streaming, ideal for applications that need to inspect or selectively extract files.

Capabilities

File System Access

Opens ZIP files from local filesystem paths.

/**
 * Opens a ZIP file from the filesystem
 * @param path - File system path to the ZIP file
 * @param options - Optional configuration object
 * @returns Promise resolving to Directory object
 */
static async file(path: string, options?: OpenOptions): Promise<Directory>;

Usage Examples:

const unzipper = require("unzipper");

// Basic file opening
const directory = await unzipper.Open.file("archive.zip");
console.log(`Archive contains ${directory.files.length} files`);

// List all files
directory.files.forEach(file => {
  console.log(`${file.type}: ${file.path}`);
});

// Find specific file
const readme = directory.files.find(f => f.path === "README.md");
if (readme) {
  const content = await readme.buffer();
  console.log(content.toString());
}

// Extract specific files
const configFile = directory.files.find(f => f.path.endsWith("config.json"));
if (configFile) {
  const configStream = configFile.stream();
  configStream.pipe(fs.createWriteStream("config.json"));
}

URL Access

Opens ZIP files from HTTP/HTTPS URLs using a provided request library.

/**
 * Opens a ZIP file from a URL
 * @param requestLibrary - HTTP request library (e.g., 'request' module)
 * @param url - URL to the ZIP file
 * @param options - Optional configuration object
 * @returns Promise resolving to Directory object
 */
static async url(requestLibrary: any, url: string, options?: OpenOptions): Promise<Directory>;

Usage Examples:

const request = require("request");

// Download and open ZIP file
const directory = await unzipper.Open.url(request, "https://example.com/archive.zip");

// Process files from remote ZIP
for (const file of directory.files) {
  if (file.type === "File" && file.path.endsWith(".txt")) {
    const content = await file.buffer();
    console.log(`${file.path}: ${content.length} bytes`);
  }
}

// Stream large file from remote ZIP
const largeFile = directory.files.find(f => f.path === "data/large-dataset.csv");
if (largeFile) {
  largeFile.stream().pipe(fs.createWriteStream("dataset.csv"));
}

Amazon S3 Access

Opens ZIP files stored in Amazon S3 buckets.

/**
 * Opens a ZIP file from Amazon S3
 * @param awsSdk - AWS SDK instance
 * @param params - S3 parameters (Bucket, Key, etc.)
 * @param options - Optional configuration object
 * @returns Promise resolving to Directory object
 */
static async s3(awsSdk: any, params: S3Parameters, options?: OpenOptions): Promise<Directory>;

interface S3Parameters {
  /** S3 bucket name */
  Bucket: string;
  /** S3 object key */
  Key: string;
  /** Optional version ID for versioned objects */
  VersionId?: string;
  /** Additional S3 parameters as per AWS SDK */
  [key: string]: any;
}

Usage Examples:

const AWS = require("aws-sdk");
const s3 = new AWS.S3();

// Open ZIP file from S3
const directory = await unzipper.Open.s3(s3, {
  Bucket: "my-bucket",
  Key: "archives/data.zip"
});

// Process S3-hosted ZIP contents
const manifest = directory.files.find(f => f.path === "manifest.json");
if (manifest) {
  const manifestData = JSON.parse((await manifest.buffer()).toString());
  console.log("Archive manifest:", manifestData);
}

// Extract multiple files from S3 ZIP
const imageFiles = directory.files.filter(f => 
  f.type === "File" && /\.(jpg|png|gif)$/i.test(f.path)
);

for (const imageFile of imageFiles) {
  const fileName = path.basename(imageFile.path);
  imageFile.stream().pipe(fs.createWriteStream(`images/${fileName}`));
}

Buffer Access

Opens ZIP files from in-memory buffers.

/**
 * Opens a ZIP file from a memory buffer
 * @param buffer - Buffer containing ZIP file data
 * @param options - Optional configuration object
 * @returns Promise resolving to Directory object
 */
static async buffer(buffer: Buffer, options?: OpenOptions): Promise<Directory>;

Usage Examples:

// Open ZIP from buffer (e.g., from database or API)
const zipBuffer = await loadZipFromDatabase();
const directory = await unzipper.Open.buffer(zipBuffer);

// Process in-memory ZIP
directory.files.forEach(async (file) => {
  if (file.path.startsWith("config/")) {
    const content = await file.buffer();
    await processConfigFile(file.path, content);
  }
});

// Convert buffer ZIP to file system
const tempFiles = directory.files.filter(f => f.type === "File");
for (const file of tempFiles) {
  const outputPath = `temp/${file.path}`;
  const outputDir = path.dirname(outputPath);
  
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }
  
  const content = await file.buffer();
  fs.writeFileSync(outputPath, content);
}

Custom Source Access

Opens ZIP files from custom source implementations.

/**
 * Opens a ZIP file from a custom source
 * @param source - Custom source implementation
 * @param options - Optional configuration object
 * @returns Promise resolving to Directory object
 */
static async custom(source: any, options?: OpenOptions): Promise<Directory>;

Directory Structure

The Directory object returned by Open methods provides access to the ZIP file contents.

interface Directory {
  /** Array of files and directories in the ZIP archive */
  files: File[];
  /** Extract all files to filesystem (if supported) */
  extract(): Promise<void>;
}

interface File {
  /** Path of the file/directory within the archive */
  path: string;
  /** Type of entry: 'File' or 'Directory' */
  type: 'File' | 'Directory';
  /** Creates a readable stream for the file content */
  stream(): ReadableStream;
  /** Returns the file content as a Buffer */
  buffer(): Promise<Buffer>;
}

Usage Examples:

const directory = await unzipper.Open.file("project.zip");

// Explore directory structure
console.log("Files in archive:");
directory.files
  .filter(f => f.type === "File")
  .forEach(f => console.log(`  ${f.path}`));

console.log("Directories in archive:");
directory.files
  .filter(f => f.type === "Directory")
  .forEach(f => console.log(`  ${f.path}/`));

// Extract entire archive (if supported)
await directory.extract();

// Work with specific files
const packageJson = directory.files.find(f => f.path === "package.json");
if (packageJson) {
  // As buffer
  const packageData = JSON.parse((await packageJson.buffer()).toString());
  console.log(`Package: ${packageData.name}@${packageData.version}`);
  
  // As stream
  packageJson.stream().pipe(fs.createWriteStream("package.json"));
}

Advanced Usage

Archive Inspection

const inspectArchive = async (zipPath) => {
  const directory = await unzipper.Open.file(zipPath);
  
  const stats = {
    totalFiles: 0,
    totalDirectories: 0,
    totalSize: 0,
    fileTypes: new Map(),
    largestFile: null,
    maxSize: 0
  };

  for (const file of directory.files) {
    if (file.type === "File") {
      stats.totalFiles++;
      
      // Get file size by reading buffer
      const buffer = await file.buffer();
      const size = buffer.length;
      stats.totalSize += size;
      
      if (size > stats.maxSize) {
        stats.maxSize = size;
        stats.largestFile = file.path;
      }
      
      // Track file extensions
      const ext = path.extname(file.path).toLowerCase();
      stats.fileTypes.set(ext, (stats.fileTypes.get(ext) || 0) + 1);
    } else {
      stats.totalDirectories++;
    }
  }

  return stats;
};

const stats = await inspectArchive("project.zip");
console.log(`Archive contains ${stats.totalFiles} files and ${stats.totalDirectories} directories`);
console.log(`Total size: ${stats.totalSize} bytes`);
console.log(`Largest file: ${stats.largestFile} (${stats.maxSize} bytes)`);

Selective Processing

const processArchiveByType = async (zipPath) => {
  const directory = await unzipper.Open.file(zipPath);
  
  const processors = {
    '.json': async (file) => {
      const content = JSON.parse((await file.buffer()).toString());
      console.log(`JSON file ${file.path}:`, Object.keys(content));
    },
    
    '.txt': async (file) => {
      const content = (await file.buffer()).toString();
      console.log(`Text file ${file.path}: ${content.split('\n').length} lines`);
    },
    
    '.md': async (file) => {
      const content = (await file.buffer()).toString();
      const headings = content.match(/^#+\s+.+$/gm) || [];
      console.log(`Markdown file ${file.path}: ${headings.length} headings`);
    }
  };

  for (const file of directory.files) {
    if (file.type === "File") {
      const ext = path.extname(file.path).toLowerCase();
      const processor = processors[ext];
      
      if (processor) {
        try {
          await processor(file);
        } catch (error) {
          console.error(`Error processing ${file.path}:`, error.message);
        }
      }
    }
  }
};

await processArchiveByType("documentation.zip");

Streaming Large Files

const streamLargeFiles = async (zipPath, outputDir) => {
  const directory = await unzipper.Open.file(zipPath);
  
  // Find files larger than 1MB
  const largeFiles = [];
  for (const file of directory.files) {
    if (file.type === "File") {
      // For large files, avoid loading entire buffer
      // Instead use stream processing
      const stream = file.stream();
      let size = 0;
      
      await new Promise((resolve, reject) => {
        stream.on('data', (chunk) => {
          size += chunk.length;
        });
        stream.on('end', () => {
          if (size > 1024 * 1024) { // 1MB
            largeFiles.push({ file, size });
          }
          resolve();
        });
        stream.on('error', reject);
      });
    }
  }

  console.log(`Found ${largeFiles.length} large files`);
  
  // Stream large files to disk
  for (const { file, size } of largeFiles) {
    console.log(`Streaming ${file.path} (${size} bytes)...`);
    const outputPath = path.join(outputDir, file.path);
    const outputDirPath = path.dirname(outputPath);
    
    if (!fs.existsSync(outputDirPath)) {
      fs.mkdirSync(outputDirPath, { recursive: true });
    }
    
    file.stream().pipe(fs.createWriteStream(outputPath));
  }
};

await streamLargeFiles("large-archive.zip", "output");

Install with Tessl CLI

npx tessl i tessl/npm-unzipper

docs

extraction.md

index.md

open.md

parse-one.md

parsing.md

tile.json