Skip to content

MagicFun1241/zip-bun

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bun Zip Library

npm

logo

A high-performance ZIP archive library for Bun, built with native C bindings using the miniz compression library and Bun's C compiler.

Features

  • High Performance: Native C bindings for maximum speed
  • Full ZIP Support: Create, read, and extract ZIP archives
  • Memory-Based Operations: Create and manipulate ZIP archives entirely in memory
  • Memory-Based Reading: Read ZIP archives directly from memory data
  • Compression Control: Multiple compression levels (no compression to best compression)
  • TypeScript Support: Full TypeScript definitions and type safety
  • Comprehensive Testing: Full test suite with coverage
  • Cross Platform: Works on macOS, Linux, and Windows

Installation

npm install zip-bun
# or
bun add zip-bun

Quick Start

Creating a ZIP Archive

import { createArchive, CompressionLevel } from "zip-bun";

// Create a new ZIP archive
const writer = createArchive("archive.zip");

// Add files with different compression levels
const textData = new TextEncoder().encode("Hello, World!");
writer.addFile("hello.txt", textData, CompressionLevel.DEFAULT);

const jsonData = new TextEncoder().encode('{"message": "Hello"}');
writer.addFile("data.json", jsonData, CompressionLevel.BEST_COMPRESSION);

// Don't forget to finalize the archive
writer.finalize();

Creating a ZIP Archive in Memory

import { createMemoryArchive } from "zip-bun";

// Create a memory-based ZIP archive
const writer = createMemoryArchive();

// Add files
const textData = new TextEncoder().encode("Hello, World!");
writer.addFile("hello.txt", textData, CompressionLevel.DEFAULT);

// Get the ZIP data as a Uint8Array
const zipData = writer.finalizeToMemory();

// Use the data (e.g., save to file, send over network, etc.)
await Bun.write("archive.zip", zipData);

Reading a ZIP Archive from Memory

import { openMemoryArchive } from "zip-bun";

// Read a ZIP archive from memory data
const zipData = await Bun.file("archive.zip").arrayBuffer();
const reader = openMemoryArchive(new Uint8Array(zipData));

// Use the reader just like a file-based reader
console.log(`Archive contains ${reader.getFileCount()} files`);

// Extract files
const data = reader.extractFile(0);
const text = new TextDecoder().decode(data);
console.log(text);

reader.close();

Reading a ZIP Archive

import { openArchive } from "zip-bun";

// Open an existing ZIP archive
const reader = openArchive("archive.zip");

// Get information about files
console.log(`Archive contains ${reader.getFileCount()} files`);

// List all files
for (let i = 0; i < reader.getFileCount(); i++) {
  const fileInfo = reader.getFileInfo(i);
  console.log(`${fileInfo.filename}: ${fileInfo.uncompressedSize} bytes`);
}

// Extract a specific file
const data = reader.extractFile(0);
const text = new TextDecoder().decode(data);
console.log(text);

// Or extract by filename
const fileData = reader.extractFileByName("hello.txt");

// Don't forget to close the reader
reader.close();

API Reference

Compression Levels

enum CompressionLevel {
  NO_COMPRESSION = 0,      // No compression
  BEST_SPEED = 1,          // Fastest compression
  DEFAULT = 6,             // Default compression
  BEST_COMPRESSION = 9     // Best compression ratio
}

Core Classes

ZipArchiveWriter

A class for creating ZIP archives, supporting both file-based and memory-based operations.

Constructor:

// File-based ZIP
createArchive(filename: string): ZipArchiveWriter

// Memory-based ZIP
createMemoryArchive(): ZipArchiveWriter
// or
createArchive(): ZipArchiveWriter  // No filename creates memory-based archive

Methods:

// Add a file to the archive
addFile(
  filename: string, 
  data: Uint8Array | ArrayBuffer | DataView, 
  compressionLevel?: CompressionLevel
): boolean

// Finalize file-based archive (writes to disk)
finalize(): boolean

// Finalize memory-based archive (returns Uint8Array)
finalizeToMemory(): Uint8Array

ZipArchiveReader

A class for reading and extracting from ZIP archives, supporting both file-based and memory-based archives.

Constructor:

// File-based ZIP
openArchive(filename: string): ZipArchiveReader

// Memory-based ZIP
openMemoryArchive(data: Uint8Array | ArrayBuffer | DataView): ZipArchiveReader

Methods:

// Get the number of files in the archive
getFileCount(): number

// Get information about a file by index
getFileInfo(index: number): ZipFileInfo

// Extract a file by index
extractFile(index: number): Uint8Array

// Extract a file by name
extractFileByName(filename: string): Uint8Array

// Find the index of a file by name (returns -1 if not found)
findFile(filename: string): number

// Close the archive reader
close(): boolean

Interfaces

ZipFileInfo

Information about a file in a ZIP archive.

interface ZipFileInfo {
  filename: string;        // Name of the file
  comment: string;         // File comment
  uncompressedSize: number; // Original file size
  compressedSize: number;   // Compressed file size
  directory: boolean;      // Whether this is a directory
  encrypted: boolean;      // Whether the file is encrypted
}

FileData

Supported data types for adding files to archives.

type FileData = Uint8Array | ArrayBuffer | DataView;

Convenience Functions

File-Based Operations

// Create a ZIP archive from a directory
zipDirectory(
  sourceDir: string, 
  outputFile: string, 
  compressionLevel?: CompressionLevel
): Promise<void>

// Extract all files from a ZIP archive
extractArchive(
  zipFile: string, 
  outputDir: string
): Promise<void>

Memory-Based Operations

// Create a ZIP archive from a directory in memory
zipDirectoryToMemory(
  sourceDir: string, 
  compressionLevel?: CompressionLevel
): Promise<Uint8Array>

// Open a ZIP archive from memory data
openMemoryArchive(data: Uint8Array | ArrayBuffer | DataView): ZipArchiveReader

Examples

File-Based ZIP Operations

Creating a ZIP with Multiple Files

import { createArchive, CompressionLevel } from "zip-bun";

const writer = createArchive("backup.zip");

// Add text files
const readmeData = new TextEncoder().encode("# My Project\n\nThis is a README file.");
writer.addFile("README.md", readmeData, CompressionLevel.DEFAULT);

// Add JSON configuration
const configData = new TextEncoder().encode('{"version": "1.0.0", "debug": false}');
writer.addFile("config.json", configData, CompressionLevel.BEST_COMPRESSION);

// Add binary files
const imageData = await Bun.file("image.png").arrayBuffer();
writer.addFile("image.png", new Uint8Array(imageData), CompressionLevel.BEST_SPEED);

writer.finalize();

Creating a ZIP from a Directory

import { zipDirectory, CompressionLevel } from "zip-bun";

// Create a ZIP from a directory
await zipDirectory("my-project", "project-backup.zip", CompressionLevel.BEST_COMPRESSION);

Extracting All Files from a ZIP

import { openArchive } from "zip-bun";

const reader = openArchive("backup.zip");

for (let i = 0; i < reader.getFileCount(); i++) {
  const fileInfo = reader.getFileInfo(i);
  
  if (!fileInfo.directory) {
    const data = reader.extractFile(i);
    
    // Save to file system
    await Bun.write(fileInfo.filename, data);
    console.log(`Extracted: ${fileInfo.filename}`);
  }
}

reader.close();

Extracting a ZIP to a Directory

import { extractArchive } from "zip-bun";

// Extract all files from a ZIP to a directory
await extractArchive("backup.zip", "extracted-files");

Memory-Based ZIP Operations

Creating a ZIP in Memory

import { createMemoryArchive, CompressionLevel } from "zip-bun";

const writer = createMemoryArchive();

// Add files to memory-based archive
const textData = new TextEncoder().encode("Hello, World!");
writer.addFile("hello.txt", textData, CompressionLevel.DEFAULT);

const jsonData = new TextEncoder().encode('{"key": "value"}');
writer.addFile("data.json", jsonData, CompressionLevel.BEST_COMPRESSION);

// Get the ZIP data as Uint8Array
const zipData = writer.finalizeToMemory();

// Use the data
await Bun.write("output.zip", zipData);
// or send over network
// await fetch("https://api.example.com/upload", {
//   method: "POST",
//   body: zipData
// });

Creating a ZIP from Directory in Memory

import { zipDirectoryToMemory, CompressionLevel } from "zip-bun";

// Create a ZIP from a directory in memory
const zipData = await zipDirectoryToMemory("my-project", CompressionLevel.BEST_COMPRESSION);

// Use the memory-based ZIP data
await Bun.write("project.zip", zipData);

Reading a ZIP Archive from Memory

import { openMemoryArchive } from "zip-bun";

// Read a ZIP archive from memory data
const zipData = await Bun.file("archive.zip").arrayBuffer();
const reader = openMemoryArchive(new Uint8Array(zipData));

// Use the reader just like a file-based reader
console.log(`Archive contains ${reader.getFileCount()} files`);

// Extract files
const data = reader.extractFile(0);
const text = new TextDecoder().decode(data);
console.log(text);

reader.close();

Round-trip Memory Operations

import { createMemoryArchive, openMemoryArchive } from "zip-bun";

// Create a memory-based ZIP
const writer = createMemoryArchive();
const textData = new TextEncoder().encode("Hello, World!");
writer.addFile("hello.txt", textData, CompressionLevel.DEFAULT);

// Get the ZIP data
const zipData = writer.finalizeToMemory();

// Read the same ZIP data back from memory
const reader = openMemoryArchive(zipData);
const extractedData = reader.extractFile(0);
const extractedText = new TextDecoder().decode(extractedData);

console.log(extractedText); // "Hello, World!"
reader.close();

Working with Different Data Types

import { createMemoryArchive } from "zip-bun";

const writer = createMemoryArchive();

// Uint8Array
const uint8Data = new Uint8Array([1, 2, 3, 4, 5]);
writer.addFile("data.bin", uint8Data);

// ArrayBuffer
const arrayBuffer = new ArrayBuffer(8);
const view = new DataView(arrayBuffer);
view.setInt32(0, 42, true);
writer.addFile("config.bin", arrayBuffer);

// DataView
const dataView = new DataView(new ArrayBuffer(4));
dataView.setFloat32(0, 3.14, true);
writer.addFile("float.bin", dataView);

const zipData = writer.finalizeToMemory();

Memory-Based ZIP with Different Data Types

import { openMemoryArchive } from "zip-bun";

// Read ZIP from different data types
const zipData = await Bun.file("archive.zip").arrayBuffer();

// Using Uint8Array
const reader1 = openMemoryArchive(new Uint8Array(zipData));

// Using ArrayBuffer directly
const reader2 = openMemoryArchive(zipData);

// Using DataView
const dataView = new DataView(zipData);
const reader3 = openMemoryArchive(dataView);

// All readers work the same way
console.log(`Files: ${reader1.getFileCount()}`);
reader1.close();
reader2.close();
reader3.close();

Advanced Usage

Finding and Extracting Specific Files

import { openArchive } from "zip-bun";

const reader = openArchive("archive.zip");

// Find a specific file
const index = reader.findFile("config.json");
if (index >= 0) {
  const data = reader.extractFile(index);
  const config = JSON.parse(new TextDecoder().decode(data));
  console.log("Config:", config);
} else {
  console.log("config.json not found");
}

// Or extract directly by name
try {
  const data = reader.extractFileByName("config.json");
  const config = JSON.parse(new TextDecoder().decode(data));
  console.log("Config:", config);
} catch (error) {
  console.log("config.json not found");
}

reader.close();

Working with File Information

import { openArchive } from "zip-bun";

const reader = openArchive("archive.zip");

for (let i = 0; i < reader.getFileCount(); i++) {
  const fileInfo = reader.getFileInfo(i);
  
  console.log(`File: ${fileInfo.filename}`);
  console.log(`  Size: ${fileInfo.uncompressedSize} bytes`);
  console.log(`  Compressed: ${fileInfo.compressedSize} bytes`);
  console.log(`  Compression ratio: ${((1 - fileInfo.compressedSize / fileInfo.uncompressedSize) * 100).toFixed(1)}%`);
  console.log(`  Directory: ${fileInfo.directory}`);
  console.log(`  Encrypted: ${fileInfo.encrypted}`);
  console.log(`  Comment: ${fileInfo.comment}`);
}

reader.close();

Error Handling

import { createArchive, openArchive } from "zip-bun";

// Error handling for file-based operations
try {
  const writer = createArchive("output.zip");
  writer.addFile("test.txt", new TextEncoder().encode("Hello"));
  writer.finalize();
} catch (error) {
  console.error("Failed to create ZIP:", error.message);
}

// Error handling for memory-based operations
try {
  const writer = createMemoryArchive();
  writer.addFile("test.txt", new TextEncoder().encode("Hello"));
  const data = writer.finalizeToMemory();
} catch (error) {
  console.error("Failed to create memory ZIP:", error.message);
}

// Error handling for reading
try {
  const reader = openArchive("nonexistent.zip");
  reader.close();
} catch (error) {
  console.error("Failed to open ZIP:", error.message);
}

Performance

This library provides excellent performance through:

  • Native C Bindings: Direct calls to the miniz library
  • Zero-Copy Operations: Efficient memory management
  • Streaming Compression: Large files are handled efficiently
  • Optimized Algorithms: Uses proven compression algorithms
  • Memory-Based Operations: Avoid disk I/O for in-memory processing

Benchmarks

Operation File Size Time
Create ZIP 1MB ~50ms
Create Memory ZIP 1MB ~45ms
Extract ZIP 1MB ~30ms
Extract Memory ZIP 1MB ~25ms
Compress (BEST) 1MB ~100ms
Compress (SPEED) 1MB ~20ms
Directory to ZIP 10MB ~200ms
Directory to Memory ZIP 10MB ~180ms
Round-trip Memory 1MB ~70ms

Development

Running Tests

# Run all tests
bun test

# Run tests with coverage
bun test --coverage

# Run specific test file
bun test zip.test.ts

Building

# Build the project
bun run build

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Run the test suite
  6. Submit a pull request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • miniz - The underlying compression library
  • bun - The JavaScript runtime and C compiler