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

@jobwright-io/piffjs

v0.2.1

Published

Native Node.js and Bun SDK for deterministic visual and semantic PDF comparison

Readme

Getting started

Piff compares PDF documents through a Rust pipeline. PDFium renders pages; Piff owns page pairing, alignment, visual comparison, semantic text evidence, resource limits, bindings, and CLI behavior.

Install the workspace dependencies and build the packages:

pnpm install
pnpm build

Build the CLI:

cargo build --locked -p piff --release

Local CLI runs need a PDFium library. Download or provide one, then pass its path explicitly:

./target/release/piff doctor \
  --pdfium artifacts/pdfium/linux-x64/lib/libpdfium.so

CLI

The binary has five commands:

piff compare before.pdf after.pdf --mode semantic --output report.json
piff diff before.pdf after.pdf
piff diff before.pdf after.pdf --format inline
piff diff before.pdf after.pdf --format json --context-lines 2 --compact
piff equal before.pdf after.pdf
piff series baseline.pdf candidate-a.pdf candidate-b.pdf --format inline
piff series baseline.pdf candidate-a.pdf candidate-b.pdf --format json --compact
piff doctor --pdfium /path/to/libpdfium.so

compare produces the complete visual and semantic report. diff defaults to semantic-only comparison, so it skips full-page rasterization; use --render full when the text diff also needs pixel evidence. --format inline prints a unified document stream with [before], [after], or [both] ownership. --format json returns the same block-scoped stream as the machine-readable text_diff object. equal stops at the first difference and is suitable for CI. doctor verifies the configured PDFium backend. series compares an ordered set of revisions. It uses the first PDF as the baseline by default; pass --strategy adjacent to compare each revision to its predecessor. Its inline output labels every line with a revision ID instead of inventing a global before/after side.

All commands support page matching, DPI, alignment, reading order, passwords, and bounded resource flags. Encrypted files can use --password, --before-password, or --after-password. Errors are structured JSON on stderr. Exit status 1 means the documents differ, 2 means execution failed, and 130 means cancellation.

TypeScript SDK

The core package is @jobwright-io/piffjs:

Install it from the public npm registry:

npm install @jobwright-io/piffjs
import { piff } from '@jobwright-io/piffjs'

const result = await piff(beforePdf, afterPdf, {
  mode: 'semantic',
  render: 'none',
  pageMatching: 'sequence',
  readingOrder: 'auto',
  contextLines: 2,
})

for (const operation of result.textDiff?.stream ?? []) {
  console.log(operation.kind, operation.side, operation.beforeText, operation.afterText)
}

For candidate generation, use the revision-neutral document-set primitive:

import { piffSet } from '@jobwright-io/piffjs'

const result = await piffSet([
  { id: 'baseline', label: 'Baseline CV', bytes: baselinePdf },
  { id: 'candidate-a', label: 'Candidate A', bytes: candidateA },
  { id: 'candidate-b', label: 'Candidate B', bytes: candidateB },
], {
  strategy: 'baseline',
  mode: 'semantic',
  render: 'none',
})

for (const change of result.changes) {
  console.log(change.kind, change.anchors.map((anchor) => anchor.revisionId))
  for (const variant of change.variants) {
    console.log(variant.revisionIds, variant.text)
  }
}

PdfChangeOperation is the multi-document primitive. It has revision-keyed anchors, grouped content variants, and optional pair-specific hunks in comparisons. A baseline block changed differently by two candidates is one operation with three revision anchors and three content variants; a candidate-only block has one introduced anchor. Figure, page, and visual-only changes use the same shape. PiffDocumentSet creates pair sessions on demand so a page preview can be requested lazily with renderPageDiff(fromRevisionId, toRevisionId, pageIndex).

In Node and Bun, the native document-set path loads each revision once when the password policy allows the handles to be shared. That avoids reparsing a baseline for every candidate and keeps adjacent intermediate revisions alive across their two edges. The browser/WASM fallback keeps the same result and preview API, but evaluates its edges through the existing pair calls.

The result is compact and serializable. semantic.blocks and textDiff.pages[].blocks are the canonical page-aware review units. textDiff.stream is the flattened document-order projection for inline consumers. Every operation carries explicit beforePage and afterPage ownership, PDF-point bounds, block-scoped line and word hunks, and a deterministic ID.

Additions are anchored only to the after side. Removals are anchored only to the before side. Modifications, moves, and reflows carry both sides when both documents contain the block. The result's renderMode and each page's visualComputed flag make the absence of raster evidence explicit. Repeated edge-positioned text can be labeled header or footer; a single-page heading remains body.

Use PiffSession when previews should be rendered lazily:

const session = await PiffSession.open(beforePdf, afterPdf, { mode: 'semantic' })
try {
  const result = await session.compare({
    signal: abortController.signal,
    onProgress(event) {
      console.log(event.phase, event.completed, event.total)
    },
  })
  const png = await session.renderPageDiff(0, { view: 'diff' })
} finally {
  await session.close()
}

result.stats reports fractional millisecond timings for loading, page fingerprinting, page matching, raster rendering, pixel and figure comparison, region detection, semantic extraction, and the complete comparison. For native document-set sequence comparisons, a revision's low-resolution page fingerprints and extracted page runs are reused across edges; semantic block matching remains pair-specific. result.engine identifies the Piff runtime, the compiled PDFium API, and the exact PDFium artifact version when the loaded library ships a VERSION sidecar. Run the local benchmark with pnpm benchmark -- --json; its report separates preview wall time from native PNG encoding time.

Preview bytes stay in a bounded least-recently-used cache. Set the limit to suit the host and read its counters without exposing the cached image buffers:

const session = await PiffSession.open(
  beforePdf,
  afterPdf,
  { mode: 'semantic' },
  { maxPreviewCacheBytes: 32 * 1024 * 1024 },
)
const preview = await session.renderPageDiff(0)
console.log(session.cacheDiagnostics())

Benchmark and diagnostics integrations can request a preview with native PNG encoding timing:

const timedPreview = await session.renderPageDiffWithTiming(0, { view: 'diff' })
console.log(timedPreview.bytes.byteLength, timedPreview.encodeMs)

The current PDFium binding serializes PDFium work inside a process. Reuse a session for related requests, but use independent worker processes when throughput requires parallel document comparisons. Each worker should have its own memory budget and PDFium library instance.

Workspace

  • crates/piff-core/ contains raster comparison, alignment, regions, and page fingerprints.
  • crates/piff-pdfium/ owns PDF loading, rendering, page pairing, figures, and the public native result model.
  • crates/piff-semantic/ owns positioned text normalization, structure, matching, roles, and Git-like hunks.
  • crates/piff-napi/ exposes the asynchronous Node and Bun boundary.
  • crates/piff-wasm/ exposes the browser runtime boundary.
  • crates/piff-cli/ builds the piff binary.
  • packages/piff/ contains the typed Node and Bun SDK.
  • packages/piff-wasm/ contains the synchronous WASM adapter and worker boundary.
  • packages/piff-react/ is an optional consumer-facing React adapter.
  • scripts/ contains fixture generation, native staging, and regression checks.

Development

Run the repository checks:

cargo fmt --all -- --check
cargo test --workspace --locked
cargo clippy --workspace --all-targets --locked -- -D warnings
pnpm typecheck
pnpm build
pnpm verify:regressions
pnpm verify:cli
pnpm verify:golden
pnpm verify:hostile
pnpm verify:fuzz
pnpm benchmark -- --json

The regression suite uses ordinary PDF bytes and covers wording changes, additions and removals, page insertion, deletion and movement, translation, figures, repeated headers and footers, list and table blocks, malformed inputs, encrypted inputs, deterministic output, and resource limits. pnpm verify:golden always runs a generated local golden manifest for table rows, repeated roles, figure swaps, ligature text encoding, malformed input classification, repeatability, and preview caching. It can additionally check the pinned external corpus under references/. pnpm verify:hostile exercises SDK progress and cancellation at each pipeline phase, resource limits, malformed-input error stability, fresh-process determinism, and equivalent CLI failures. The fuzz targets under fuzz/ cover semantic normalization and the PDFium loading boundary; the PDF loading target requires PDFIUM_LIBRARY_PATH and should run in an isolated process. Run a bounded local campaign with pnpm fuzz -- --target all --seconds 30; it requires nightly Rust and cargo-fuzz. The scheduled fuzz workflow retains crash artifacts, and reviewed minimized PDF loading failures can be promoted with pnpm promote:fuzz into the golden corpus. The optional golden corpus under fixtures/golden/ checks real PDFs from pinned reference checkouts. Set PIFF_GOLDEN_REQUIRED=1 when a missing or changed fixture should fail the command.

Release deployment

The Release packages workflow publishes @jobwright-io/piffjs and its platform native packages to npm and GitHub Packages, then publishes the Rust crates. It runs for a published GitHub release or an explicit workflow_dispatch run with publish enabled from the matching version tag. GitHub Packages uses the workflow's scoped GITHUB_TOKEN. npm uses trusted publishing after the first release; the one-time NPM_TOKEN bootstrap secret should be revoked and removed once every npm package has its trusted publisher configured. Crates.io uses CARGO_REGISTRY_TOKEN. The React and browser adapter packages remain private until they have their own release contract. The native release targets glibc Linux, macOS, and Windows; musl Linux support remains a separate cross-compilation task. GitHub Releases also receive standalone piff CLI archives for those platforms. The CLI discovers the bundled PDFium library from its adjacent pdfium/ directory. Native packages and CLI archives include PDFium and third-party license notices under licenses/. Each staged native package also includes pdfium/VERSION and a deterministic artifact-manifest.json containing file sizes and SHA-256 checksums; release CI verifies the manifest against the pinned PDFium build before publication.

See ROADMAP.md for the completed milestones and deliberate non-goals.