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

haruto-sdk

v0.1.0

Published

Official TypeScript/JavaScript SDK for the Haruto grounded PDF parsing API.

Readme

haruto-sdk

Official TypeScript/JavaScript SDK for the Haruto grounded PDF parsing API — self-hosted document intelligence for clinical protocols, claims, faxes, and hundred-thousand-page archives.

Every parsed element comes back grounded: page number + bounding box on each heading, paragraph, table cell, and figure, so downstream systems verify values against the source instead of trusting a transcription.

  • Zero dependencies — Node 18+, Bun, Deno, edge runtimes, browsers
  • Network-resilient by default — exponential-backoff retries on connection errors and 5xx; uploads retry only when the server never responded (never a double-submit)
  • Built for huge documents — async jobs, page-range results, webhooks
npm install haruto-sdk

Quickstart

import { Haruto, type ParsedDoc } from "haruto-sdk";

const hr = new Haruto("https://your-haruto-deploy.example.com", "wk_...");

// 1. upload — returns immediately with a job
const job = await hr.parse(fileBlob, { mode: "accurate" });

// 2. wait — polls with backoff, absorbs connection blips
await hr.waitFor(job.job_id);

// 3. fetch the grounded result
const doc: ParsedDoc = await hr.result(job.job_id);
for (const el of doc.elements) {
  // el.type   "heading" | "paragraph" | "table" | "figure" | ...
  // el.page   1-based page number
  // el.bbox   { x0, y0, x1, y1 } in PDF points, top-left origin
  // el.text   reading-order text
  // el.table  cell grid with row/col spans, when el.type === "table"
}

Small documents can skip the poll entirely:

const done = await hr.parse(fileBlob, { wait: true }); // blocks until parsed

Parse options

await hr.parse(fileBlob, {
  mode: "accurate",        // "accurate" (layout + table models, default) | "fast" (text-layer only, ~100x faster)
  ocr: "auto",             // "auto" (OCR only pages that need it, default) | "off" | "force"
  webhookUrl: "https://your.app/hooks/haruto",  // POSTed on completion
  filename: "protocol.pdf",
  wait: false,             // true = block until done (small docs)
});

Results

const doc = await hr.result(jobId);                       // full grounded JSON
const md  = await hr.result(jobId, "markdown");           // tables reconstructed
const txt = await hr.result(jobId, "text");               // reading-order text

// large documents: fetch a page range instead of the whole result
const slice = await hr.result(jobId, "json", "1200-1225");

Results are retained for a configurable window after completion (24 h by default on a standard deployment), then deleted — parsed content is a delivery, not an archive. A purged result returns 410.

Jobs

await hr.job(jobId);          // status, page counts, progress
await hr.jobs(25, 0);         // list your jobs
await hr.cancel(jobId);       // stop a running job
await hr.requeue(jobId);      // resume an interrupted job (crash recovery)

waitFor(jobId, pollMs, timeoutMs) polls until the job reaches a terminal state and throws HarutoError if the job failed.

Webhooks

Completion webhooks are signed. Verify with a constant-time compare and a five-minute replay window:

import { verifyWebhook } from "haruto-sdk";

const ok = await verifyWebhook(
  webhookSecret,
  rawBody,                                  // exact bytes received
  req.headers["x-haruto-timestamp"],
  req.headers["x-haruto-signature"],
);

Errors

Every non-2xx response throws HarutoError with .status and the server's message:

| Status | Meaning | |---|---| | 401 | bad or missing API key | | 402 | page quota exhausted | | 409 | result requested before the job succeeded | | 410 | result deleted by the retention window — re-submit | | 413 | upload exceeds the deployment's size limit | | 429 | rate limited | | 0 | network unreachable after all retries |

Self-hosting

Haruto runs entirely on your infrastructure — no LLMs, no external calls; documents never leave your network. Accuracy is measured, never estimated: the engine ships with a reproducible benchmark harness covering 17 parsers (including Reducto, Pulse, and LlamaParse) on exact machine-generated ground truth.

MIT © Harsh Sharma