npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@eazip/core

v0.2.0

Published

Create ZIP downloads from files and remote URLs in browser apps, with progress, ZIP64, and optional Eazip Cloud

Readme

@eazip/core

Zip downloads for browser apps — one function.

import { createZip } from '@eazip/core';

const result = await createZip({ files: input.files, zipName: 'photos.zip' });
result.download();

Files are zipped in the browser (ZIP64, per-file progress, no upload). files accepts File[], a FileList, Blobs, URL strings, or { url | file, filename } objects. Failed URL sources are skipped and reported instead of killing the export:

const result = await createZip({ files: urls });
result.status;        // 'completed' | 'partial'
result.errors;        // per-file skips: [{ code, message, filename }]
result.zips;          // one or more zips (see maxZipSizeBytes)

React app? Use @eazip/react — useEazip() + <EazipTray /> add a drop-in download tray on top of this package.

Install

npm install @eazip/core

Jobs: progress, cancellation, subscriptions

createZip is a thin wrapper over startZip, which returns a ZipJob synchronously:

import { startZip } from '@eazip/core';

const job = startZip({ files, zipName: 'export.zip' });

const unsubscribe = job.subscribe(() => {
  const { status, progress } = job.getSnapshot();
  console.log(status, progress?.filesCompleted, '/', progress?.filesTotal);
});

// job.abort();       // cancel
const result = await job.done;   // resolves on 'completed' | 'partial'
result.download();               // first zip
result.downloadAll();            // every zip

The snapshot/subscribe contract is useSyncExternalStore-compatible, so building UI on top of a job is trivial.

Splitting large exports

maxZipSizeBytes splits the output into multiple zips, each kept under the cap (best effort — a single file larger than the cap gets its own zip):

const result = await createZip({ files, zipName: 'export.zip', maxZipSizeBytes: 1_000_000_000 });
result.zips; // export_part01.zip, export_part02.zip, ...

Large downloads: the cloud strategy

Browser-side zipping holds everything in memory and dies with the tab. For thousands of files or multi-gigabyte exports, switch the same API to Eazip Cloud Public Sessions — the zip is built server-side:

const job = startZip({
  strategy: 'cloud',
  publicKey: 'pk_ez_...',        // from eazip.io
  files: urls,                    // URL sources, fetched by the Eazip API
  zipName: 'export.zip',
});

job.getSnapshot().session;        // { sessionId, clientSecret, expiresAt } — available immediately
const result = await job.done;    // polls with backoff; pauses in hidden tabs
result.download();                // signed URL, valid for 24h by default

Create a Public App to get the public key used by the Cloud strategy.

If your backend already knows the source URLs, keep that list off the browser and provide a session creator instead. The SDK still owns polling, completion, downloads, and cancellation:

const job = startZip({
  strategy: 'cloud',
  zipName: 'export.zip',
  filesTotal: 50_000,              // optional initial UI count
  createSession: async ({ signal, zipName, mode }) => {
    const response = await fetch('/api/exports/123/eazip-session', {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      signal,
      body: JSON.stringify({ zipName, mode }),
    });
    if (!response.ok) throw new Error('Failed to create Eazip session');
    return response.json();        // { sessionId, clientSecret, apiBaseUrl? }
  },
});

Cloud zips default to stream mode: the archive is generated on demand at download time, so it's ready sooner — a good fit for interactive, front-end exports. Each download regenerates the zip; pass mode: 'stored' to build it once and keep it in storage instead.

Cloud jobs are resumable: persist sessionId + clientSecret and pick the job back up after a reload:

import { resumeZip } from '@eazip/core';

const job = resumeZip({ sessionId, clientSecret });
await job.done;

Error handling

import {
  createZip,
  isEazipError,
  EazipRateLimitError,
  EazipSessionExpiredError,
} from '@eazip/core';

try {
  const result = await createZip({ strategy: 'cloud', publicKey, files });
} catch (error) {
  if (error instanceof EazipSessionExpiredError) {
    // links aged out — re-run the export
  } else if (error instanceof EazipRateLimitError) {
    console.log('retry after', error.retryAfterMs);
  } else if (isEazipError(error)) {
    console.log(error.code, error.message);
  }
}

Per-file problems never throw — they land in result.errors with the zip still produced (status: 'partial'). Only fatal problems (abort, invalid input, API failures) reject.

Turnstile-protected apps can pass turnstileToken, or provide onChallenge: (challenge) => Promise<string> to solve the challenge lazily — the SDK retries the session create once with the returned token.

Lower-level cloud access

SessionsClient exposes the raw Public Sessions API (create / get / poll with exponential backoff and Retry-After handling) when you want to manage the lifecycle yourself:

import { SessionsClient } from '@eazip/core/cloud';

const client = new SessionsClient({ publicKey: 'pk_ez_...', apiBaseUrl: 'https://api.eazip.io' });
const created = await client.create({ files });
const session = await client.poll(created.id, { clientSecret: created.clientSecret });

Entry points

  • @eazip/core — createZip/startZip/resumeZip, strategy-specific job starters, public types, and error classes
  • @eazip/core/local — browser zipping only (no cloud code)
  • @eazip/core/cloud — cloud sessions only (no zip.js)
  • @eazip/core/shared — types, errors, input/download utilities

Notes

  • Browser-first ESM. Local URL sources are fetched by the browser and must be CORS-accessible; cloud URL sources are fetched by the Eazip API.
  • @eazip/core supersedes the earlier @eazip/client beta.
  • Stable releases remain pre-1.0.0; see the repository changelog for any breaking 0.x changes.

License

MIT