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

@clearmist-labs/comic-archive-handler

v2.1.0

Published

Detect, convert, and manipulate comic book archives: container conversion, edit metadata, image re-encoding, perceptual + content hashing.

Readme

@clearmist-labs/comic-archive-handler

CI

A toolkit for detecting, converting, and manipulating comic book archives (CBAS/CBZ/CB7/CBR/CBT/CBA).

  • Detect archive types and container formats
  • Convert between zip/rar/tar/asar/7z containers
  • Read & write ComicInfo.xml/MetronInfo.xml metadata
  • Validate metadata with full field coverage of both schemas and XSD-backed validation
  • Re-encode page images to different formats
  • Hash archives perceptually and by content
  • Rename pages sequentially
  • Strip non-essential files from archives, or remove a single named entry
  • Extract archives to a plain directory of files
  • Benchmark archive/image format combinations for creation speed, random read speed, and size

Installation

npm install @clearmist-labs/comic-archive-handler

Usage

import fs from 'node:fs/promises';
import cah from '@clearmist-labs/comic-archive-handler';
// or: import { detectArchiveType, convertArchive } from '@clearmist-labs/comic-archive-handler';

/* Convert a cbz to cba buffer */
const type = await cah.detectArchiveType('/path/to/comic.cbz'); // 'zip'
const asarBuffer = await cah.convertArchive('/path/to/comic.cbz', 'asar');

/* Inspect its metadata */
const { present, schema } = await cah.hasComicMetadata(asarBuffer);
const { metadata } = await cah.readArchiveMetadata(asarBuffer);

/* Add some more metadata then validate we did it correctly */
const withMetadata = await cah.addMetadataToArchive(asarBuffer, { title: 'Example', series: 'Example Series', number: '1' }, 'ComicInfo');
const { valid, issues } = await cah.validateMetadataXml(cah.metadataToComicInfoXml({ title: 'Example' }), 'ComicInfo');

/* Make sure our images use the standard naming format */
const renamed = await cah.renameArchiveImagesSequentially(withMetadata); // P00001.jpg, P00002.jpg, ...

/* Get rid of anything we don't recognize */
const stripped = await cah.stripNonEssentialFiles(renamed); // keeps only images + .xml

/* Convert all of the images to WebP */
const webp = await cah.convertArchiveImages(stripped, 'webp', { webp: { quality: 92 } });

/* Generate hashes */
const contentHash = await cah.sha256Archive(webp); // For asar this hashes only the content region (not the header/index).
const pageHash = await cah.sha256ArchiveEntry(webp, 'P00001.webp');
const phash = await cah.computeArchiveImagePHash(webp, 'P00001.webp'); // 64-bit bigint

/* Write the buffer to disk */
await fs.writeFile('/path/to/output.cba', webp);

Streaming large archives to disk

Every archive-producing function accepts an output option (a file path or a Writable) to stream the result directly there instead of collecting it into a Buffer:

await cah.convertArchive('/path/to/large.cbz', 'zip', { output: '/path/to/output.cbz' });

Benchmarking archive/image format combinations

benchmarkArchive extracts a comic archive, generates every writable-container × page-image-format combination (16 archives: zip/tar/asar/7z × webp/avif/png/jpg), times each one's creation and random single-page read speed, and writes the generated archives, each format's converted pages, and a markdown report to a timestamped reports/<datetime>/ directory:

const result = await cah.benchmarkArchive('/path/to/comic.cbz');
console.log(result.reportPath); // ./reports/2026-09-12T19-47-00Z/report.md

API

See the API reference for descriptions, options, examples, exported types, and custom errors.

Most runtime exports are available both as named exports and as properties on the default export. Type-only exports are available to TypeScript consumers but are not runtime properties of the default export.

File format support

Archive containers

| Format | Extension | Detectable | Readable | Streaming read | Readablewithoutdecompression | | ------ | --------- | ---------- | -------- | -------------- | ------------------------------------ | | Asar | .cbas | ✅ | ✅ | ✅ | ✅ | | Tar | .cbt | ✅ | ✅ | ✅ | ✅ | | Zip | .cbz | ✅ | ✅ | ✅ | ❌ | | 7z | .cb7 | ✅ | ✅ | ❌ | ❌ | | Rar | .cbr | ✅ | ✅ | ❌ | ❌ | | Ace | .cba | ✅ | ❌ | ❌ | ❌ |

Ace is a dead format and should never be used.

Rar is closed sourced. Writing rar files requires commercial software. I strongly suggest not using CBR as your archive choice.

7z has the smallest storage size, but reading and writing have the largest overhead and it's not possible to stream read the contents; the entire archive must first be decompressed.

Zip is the most common format and is widely supported, but its random read speed is terrible; even worse than 7z and the file contents must be decompressed before serving.

Asar and Tar are both similar in that they support read streams without requiring decompression. These are the differences:

  • For whole archive streaming Tar is the best.
  • For streaming individual content files Asar is the best.
  • Random file read is poor with Tar and excellent with Asar (6ms vs 1ms).

This means that both are good candidates for remote digital libraries. Asar if the server sends one page at a time; Tar if the server sends the entire archive contents to the client.

Page images

| Format | Detectable | Readable | Writable | Archival quality | | ------ | ---------- | -------- | -------- | ---------------- | | WebP | ✅ | ✅ | ✅ | Excellent | | AVIF | ✅ | ✅ | ✅ | Excellent | | PNG | ✅ | ✅ | ✅ | Excellent | | JPEG | ✅ | ✅ | ✅ | Good | | GIF | ✅ | ✅ | ❌ | Poor | | BMP | ✅ | ✅ | ❌ | Poor | | TIFF | ✅ | ✅ | ❌ | Poor |

The best choice for image quality and size is WebP at quality 90 to 95.

The best choice for wide compatibility across very old devices is JPG.

GIF, BMP, and TIFF are intentionally not included as writable because of their poor image quality or large file size.

Notes and known limitations

  • ACE (CBA) is entirely unsupported. ACE archives are still detected (detectArchiveType/isAce), but every read, write, and conversion operation throws UnsupportedOperationError: ACE is a dead format that hasn't been updated since 2011, has multiple known security vulnerabilities, and has no maintained JS/WASM decoder or bundleable cross-platform binary to build support on.
  • RAR is read-only. The UnRAR source license permits decompression only, not building a compatible compressor, so convertArchive(input, 'rar') always throws UnsupportedOperationError.
  • 7z read/write uses the standalone 7zzs binary bundled by 7zip-bin-full, spawned directly (not through node-7z, which has no way to control the child process's working directory). Both directions round-trip through a temporary staging directory since the 7-Zip CLI operates on real files, not in-memory buffers.
  • asar reads are fully streaming (direct byte-range reads against the archive, no extraction), but writes require a staging directory since @electron/asar's createPackage API only accepts a source directory on disk.
  • asar metadata lives in the archive's own header, not as a ComicInfo.xml/MetronInfo.xml entry. hasComicMetadata/readArchiveMetadata/addMetadataToArchive/removeComicMetadata all handle this transparently for asar inputs (metadata is set/read as a comicMetadata key on the header JSON alongside the existing files key. See src/internal/asarHeader.ts's writeAsarHeaderPatch/serializeAsarHeader), so calling code doesn't need to branch on archive type. Adding metadata to an asar archive patches its header directly rather than repackaging via createPackage, and is correspondingly cheaper than the equivalent zip/tar/7z write. A ComicInfo.xml/MetronInfo.xml entry inside an asar archive is never read as metadata.
  • Any operation that needs a staging directory (asar writes, any 7z operation) accepts a tempDir option; if omitted, a directory under os.tmpdir() is used, and a clear FilesystemAccessError is thrown if no writable directory is available.
  • Metadata conversion between the canonical schema and ComicInfo.xml/MetronInfo.xml is intentionally lossy in both directions. See the field-mapping table in src/metadata/schema.ts.
  • PNG conversion's quality option only has an effect when palette: true is also set since it sets the palette quantizer's maximum quality.

Official schemas

These are separate works and are included here to help us validate schemas against the officialy published schemas.

License

MIT