Random access
Read one entry of a 4 GB archive with three range reads — the central directory is parsed lazily, payloads are zero-copy views. No “extract everything first”.
100 % TypeScript. Zero runtime dependencies. One API across Node ≥ 22, browsers, Deno, Bun and Workers — for modern apps and the agents that operate them. One engine, three surfaces: the library, the CLI and the MCP server.
npm install zipnative
npx zipnative-cli inspect release.zip --check deterministic,safe-names
claude mcp add zipnative -- npx -y zipnative-mcp
1.1 — additive, zero breaking changes: range access over remote and huge archives, cancellation and progress, raw transplant, canonicalisation of any archive, legacy name decoding, the Zip64 streaming opt-in. The API surface, the 39-code error vocabulary and the deterministic bytes stay frozen under semver. Both satellites pin zipnative ^1.0.0.
Not a faster deflate — a safer, reproducible, streaming archive engine built for scenarios the incumbents cannot do.
Read one entry of a 4 GB archive with three range reads — the central directory is parsed lazily, payloads are zero-copy views. No “extract everything first”.
Zip-slip, symlinks, duplicate names, decompression bombs, overlapping entries, Zip64 spoofing and EOCD ambiguity are all rejected unless you explicitly opt out. Every parser loop runs under a named, CWE-tagged bound.
Reproducible-build mode with a written contract: identical inputs, identical SHA-256, on every runtime — via a pure-TS deflate encoder whose constants are frozen public API.
Replace, add, remove or rename entries and save without recompressing the untouched 99 % — the append-only model proven in pdfnative's PDF incremental updates.
Create archives in fixed-size chunks with bounded memory, and read unseekable streams forward — data-descriptor entries included, thanks to a resumable pure-TS inflater.
zipnative/worker fans per-entry compression across a real pool (worker_threads and Web Workers) — byte-identical output, graceful degradation, never an infra failure.
openZipRange() opens a 50 GB archive on S3, behind HTTP Range or in a browser File with the memory of its central directory — you inject the byte-range source, the engine never fetches, every security check still applies. Cancel anything with an AbortSignal.
analyzeDeterminism() names what would drift; canonicalizeZip() rewrites a Gradle, Maven, zip or Explorer archive into a canonical form without recompression — under the same frozen byte contract. addFromReader() assembles artefacts from existing parts in O(bytes copied).
One zero-dependency engine, three ways to call it — the artefacts are interchangeable, so the choice is about who is calling. The full capability × surface matrix →
You write application code — Node, Deno, Bun, browser or Worker. The full surface: lazy reader, secure extraction, deterministic writer, incremental modifier, forward streaming, worker parallelism, codec injection, 39 frozen error codes.
npm install zipnativezipnativeYou drive a shell, a CI job, a container. 15 commands over stdin/stdout with an agent contract — a --json envelope carrying err.code verbatim, --dry-run, --summary / --fields, JSON Schemas, completion — and the filesystem trust boundary the engine refuses to be. Offline, always.
npx zipnative-cli <command>You give an AI assistant tool access — Claude Desktop, Claude Code, Cursor, VS Code. 13 tools with strict schemas, 7 prompts, sandboxed resources, stdio or loopback Streamable HTTP, and no network access of any kind.
npx -y zipnative-mcpNo surface encrypts, by policy — confidentiality belongs to the document layer. Encrypt first, then archive →
Sync-first, no classes, every error carries a stable machine-readable code.
import { openZip, extractZip } from 'zipnative';
const zip = openZip(bytes); // lazy — nothing decompressed
const manifest = zip.readEntry('manifest.json'); // one entry, CRC-verified
const files = extractZip(bytes); // traversal/bombs rejected by default
import { createZip } from 'zipnative';
const zip = createZip({ compression: { deterministic: true } });
zip.add('data.json', payload);
const bytes = zip.toBytes(); // same inputs → same SHA-256, anywhere
import { createZipModifier, openZip } from 'zipnative';
const mod = createZipModifier(openZip(bytes));
mod.replaceEntry('word/document.xml', updated);
const saved = mod.save(); // untouched entries never recompressed
import { openZipRange, canonicalizeZip } from 'zipnative';
const zip = await openZipRange({ size, read: (offset, length) => rangeFetch(url, offset, length) });
const manifest = await zip.readEntry('manifest.json'); // a tail window + the CD + this entry
const canonical = canonicalizeZip(foreignBytes); // any archive → reproducible, no recompression
fflate keeps the raw-deflate crown and we do not chase it — zipnative wins on scenarios, not drag races.
| zipnative | fflate | jszip | yauzl/yazl | adm-zip | |
|---|---|---|---|---|---|
| Zero runtime dependencies | ✓ | ✓ | ✗ | ✗ | ✗ |
| Random access (1 entry, no full parse) | ✓ | ✗ | ✗ | yauzl ✓ | ✗ |
| Streaming read + write | ✓ | partial | ✗ | one per lib | ✗ |
| Safe-extract defaults (slip/bomb/ambiguity) | ✓ | DIY | DIY | DIY | historical CVEs |
| Deterministic output (written contract) | ✓ | DIY | ✗ | ✗ | ✗ |
| Modify in place, no recompression | ✓ | ✗ | rewrite-all | ✗ | partial |
| Stable machine-readable error codes | ✓ | ✗ | ✗ | ✗ | ✗ |
| Browser + Node + Deno + Bun + Workers | ✓ | ✓ | ✓ | Node-only | Node-only |
| Raw deflate throughput | good (platform zlib) | best | slow | good | poor |
Measured scenarios, security checks always on — including the one we lose.
Windows 11, Node v22.17, tiny-entry corpus — the worst case for the random-access rows (with megabyte entries the gap becomes orders of magnitude). zipnative numbers include the always-on security checks. Full methodology: bench/RESULTS.md.
Every archive is untrusted input; the default code path is the safe one.
Extraction refuses traversal (../, absolute paths, drive letters,
NTFS streams), symlink entries and bombs; the reader refuses overlapping
entries, parser-differential headers and ambiguous end-of-central-directory
records — over a byte-range source exactly as over bytes in memory, and a
decoder you inject for legacy names cannot smuggle a path past the same gate.
Encrypted entries are detected and labelled by scheme (ZipCrypto, WinZip AES,
strong encryption), never decrypted in 1.x. Bounds are named, CWE-tagged,
documented and caller-configurable — raising one is always an explicit
decision. Every refusal throws a stable machine-readable error code.
Identical inputs, identical SHA-256 — on any runtime.
compression: { deterministic: true } pins the pure-TS encoder:
the mode for content addressing, caching, signatures and reproducible builds.
The contract is written down and golden-tested; changing the emitted bytes
is a semver-major event. Since 1.1 the contract covers archives you did not
build: canonicalizeZip() rewrites any ZIP into the canonical form
without recompression, and analyzeDeterminism() reports what would
otherwise drift — the reproducible builds guide.
Machine-readable ground truth at every level — never guessed, always verified.
Stable error codes (errors.json), a structured
diagnostics channel, executable recipes, and the mechanically extracted export
surface in api.json — plus
llms.txt and llms-full.txt
for context ingestion, with byte and approximate token budgets for every artefact
in llms-index.json so an agent can decide what to
fetch before spending the tokens. On the other surfaces the same discipline:
zipnative schema manifest and doctor --json on the CLI,
describe_engine and typed outputSchemas on the MCP server.
Developed under a human-in-the-loop AI governance policy.
zipnative is the sibling of pdfnative: the same doctrine — zero dependencies, closure factories, strict layers, sync-first, security by default — applied to the ZIP format. The incremental-update model, the AI governance policy and this documentation pipeline were extracted from pdfnative and hardened here.