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

@veripublica/epubveri-wasm

v0.13.3

Published

WebAssembly bindings for epubveri — a pure-Rust, JVM-free EPUB validator that runs in the browser.

Readme

epubveri-wasm

WebAssembly bindings for epubveri — a pure-Rust EPUB validator. Validate an .epub entirely in the browser (or any JS runtime): no JVM, no server round-trip, no native dependencies. The bytes never leave the page.

This is the WASM delivery of epubveri, a small/fast/embeddable alternative to the official Java epubcheck. It reuses epubcheck-compatible message IDs (RSC-…, OPF-…, HTM-…, …) so existing toolchains recognize the output.

Install

npm install @veripublica/epubveri-wasm

Usage (bundlers — webpack / Vite / Rollup)

The published package is built for bundlers, so there's no manual init step — your bundler loads the .wasm for you:

import { validate, version } from "@veripublica/epubveri-wasm";

const bytes = new Uint8Array(await file.arrayBuffer()); // a File / fetched .epub
const report = validate(bytes, undefined); // second arg: profile or undefined

console.log(report.status, report.summary); // "ok" | "problems", { error, warning, … }
for (const it of report.items) {
  console.log(`${it.severity} ${it.code}: ${it.message}`, it.location ?? "");
}

report is the veripublica machine envelope's inputs[i] shape — the same object the CLI's --format json emits per input (minus path), so one parser reads CLI, CI and browser output alike.

Using it directly in a browser without a bundler? Build the web target instead (wasm-pack build . --target web), which exposes an async init() you await once before calling validate() — that's what the demo below uses.

Return shape (fully typed — real .d.ts ships in the package)

interface Report {
  status: string;        // "ok" (valid) | "problems" (error/fatal findings remain)
  summary: { fatal?: number; error: number; warning: number; info?: number; usage?: number };
  items: Item[];
}
interface Item {
  type: string;          // "finding"
  code: string;          // epubcheck-compatible, e.g. "RSC-005"
  rule?: string;         // epubveri's finer sub-code, when present
  severity: string;      // "fatal" | "error" | "warning" | "info" | "usage"
  location?: string;     // container-relative path, when available
  position?: { line: number; column: number };
  message: string;       // epubveri's own message wording
  data?: {
    params: string[];            // the values interpolated into `message`
    element_path?: string;       // XPath-style path to the offending node
    namespaces: Map<string, string>;  // bindings that resolve element_path — a Map, see below
    advisory_basis?: string;     // "spec-ahead" | "spec-silent", on ADV-*/NEXT-* only
    violation_kind?: string;     // which of six kinds a schema violation is
  };
}

function validate(
  bytes: Uint8Array,
  profile?: string | null,
  advisory?: boolean | null,
): Report;
function version(): string;

Profiles

profile mirrors epubcheck's --profile flag: pass "dict", "edupub", "idx", "preview", or undefined/null for default behavior. Unknown names are treated as undefined (permissive).

Advisory checks

advisory mirrors the CLI --advisory flag: pass true to also emit the opt-in findings epubcheck has no verdict on, in two families, both at usage severity:

  • NEXT-* — a published specification requires it and epubcheck has not implemented it yet, so it becomes an ordinary error the day it catches up (today: the EPUB 3.4 rules).
  • ADV-* — no specification says anything, but the book is still probably wrong (unknown CSS property and descriptor names, a type selector naming no known element, an EPUB 2 package written in EPUB 3, two navigation entries landing on one document).

It is off by default: leaving the argument out, or passing false/undefined, produces a byte-identical report, so existing two-argument callers are unaffected. Neither family ever affects status — a book that passes epubcheck passes epubveri, with or without the flag.

Two differences from the CLI

PKG-016 is not reported here. That check is about the .epub file extension being lowercase, and this entry point only ever sees bytes, never a filename.

data.namespaces is a Map, not a plain object. That is how a Rust map crosses into JavaScript here, and it is the one place this binding's shape differs from the CLI's JSON, where the same field is an object. Use data.namespaces.get("opf"); data.namespaces["opf"] is silently undefined. Everything else in data — added in 0.10.0, having previously been CLI-only — is exactly the CLI's shape.

Nothing is filtered here. The CLI hides usage-severity findings from its human report unless you pass -u; this binding is a machine interface and always returns every finding, exactly like --format json. Filter on severity yourself if your UI wants the CLI's default view.

Try the demo

The demo/ folder has a zero-dependency drag-and-drop page. From the crate root:

wasm-pack build . --target web --out-name epubveri   # produces pkg/
# then serve this folder over HTTP (wasm needs http://, not file://):
#   any static server works, e.g. `miniserve .` or `python3 -m http.server`
# open http://localhost:8000/demo/

Building from source

cargo install wasm-pack

# the published npm package (bundler target, @veripublica scope):
wasm-pack build . --target bundler --scope veripublica --out-name epubveri

# or the web target used by the demo above:
wasm-pack build . --target web --out-name epubveri

The generated package lands in pkg/ (git-ignored). Each --target (bundler / web / nodejs) emits different JS glue, so pick the one that matches how you'll load it; use --target nodejs for Node.

License

Dual-licensed: AGPL-3.0-only OR a commercial license (LicenseRef-veripublica-Commercial). Open-source use is free under the AGPL; closed/commercial embedders should contact the author for a commercial license. Both texts ship inside the npm package — LICENSE (the AGPL) and LICENSE.COMMERCIAL.md (what the LicenseRef- above means, and who to ask).

The . in LICENSE.COMMERCIAL.md is load-bearing — do not rename it to a hyphen. These two files are copies of the repository root's LICENSE and LICENSE-COMMERCIAL.md; wasm-pack only picks up licenses from the crate directory, so the copies have to live here. npm then always packs a file matching license optionally followed by a dotted extension, whatever files says — a hyphen does not match, and the file is silently dropped from the tarball. That is not hypothetical: it is what happened to LICENSE-COMMERCIAL.md in 0.5.10, which shipped the AGPL text with no word of the commercial option beside it. Relying on wasm-pack's own files list instead is not an option — it writes package.json before it copies the licenses, so a clean build never lists them.