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

@unisim/doc

v0.6.0

Published

The suite's document stack: read Word, OpenDocument, RTF, HTML, Markdown, CSV, JSON and text into one model, and write it back out as PDF, text, HTML, Markdown, CSV or JSON. No runtime dependencies.

Readme

@unisim/doc

The suite's document stack. Reads Word, OpenDocument, RTF, HTML, Markdown, CSV, JSON and plain text into one model, and writes that model back out as PDF, text, HTML, Markdown, CSV or JSON.

No runtime dependencies. That is not incidental — one of its two consumers (Universal Converter) is sold on being offline from first visit with nothing fetched, so a dependency here would cost that product its pitch.

import { readToDoc, convertDocument, DEFAULT_DOC_SETTINGS } from '@unisim/doc'

// The model, for a caller that wants to inspect or combine documents
const doc = await readToDoc(file)

// Or straight to an output
const { blob, ext, notices, pages } = await convertDocument(file, {
  ...DEFAULT_DOC_SETTINGS,
  format: 'pdf',
})

convertDocument hands back the target extension, not a filename. Naming the output file is the caller's job on purpose: the app knows what the source was called and which of its own naming rules apply, and a library offering a second opinion is how you end up chasing a .png.png.

Why it exists

It existed twice. On 2026-08-13 two sessions that could not see each other shipped the same 1,200–1,500 lines on the same afternoon — a ZIP reader, a namespace-agnostic XML layer, an OOXML walker, an ODF walker, and a tokenise → wrap → paginate layout engine — into Universal PDF and Universal Converter. Both files ended up carrying a header saying "a fix to one is a fix to neither."

This is Converter's version, because it was the better one: a model designed as a hub (nine readers × six writers) rather than grown out of a Markdown renderer, with heading level 4, real image blocks, underline/strike on a run, and DocNotice — a channel for reporting what a conversion lost, which the other had no equivalent of. Universal PDF's model had exactly one thing this lacked, pagesetup, which was merged in.

What it does not do

It re-typesets a document; it does not reproduce the page. Fonts, columns, headers and footers, text boxes and floating shapes do not survive. Anything a reader has to drop is reported through RichDoc.notices rather than discovered later — the failure worth designing against is someone assuming the output is a copy.

Two writers, on purpose

The PDF writer here goes through pdfcore — base-14 fonts, its own AFM tables, no dependency. A consumer that already carries pdf-lib for other reasons (Universal PDF does, for signing, redaction and forms) gains nothing by dropping it, and can take the readers and the model from here while keeping its own writer. The seam is that write/pdf.ts depends only on pdfcore's small surface — addPage / drawText / drawRect / drawImage / widthOfText.

Notes for anyone editing it

  • Imports carry a .js extension. Not .ts (which needs allowImportingTsExtensions, only legal with noEmit) and not extensionless (emitted verbatim, so dist/index.js would import ./convert and Node would refuse it). The first build of this package made exactly that mistake.
  • The model is the intersection of what these formats can express, not the union. A DOCX can hold a text box rotated 3° over a chart; Markdown cannot. Anything outside the vocabulary is flattened on the way in, by the reader that knows what it meant.
  • pagesetup is an instruction, not a property, because that is how the source formats model it — orientation belongs to a section, and a section runs until the next break. ⚠️ The two office formats express it in opposite directions: OOXML's w:sectPr sits on the last paragraph of the section it describes, ODF's style:master-page-name on the first paragraph of the new one. Reading them the same way puts every change one page out.

Tests

npm test

Covers what is DOM-free: the hand-written PDF byte layer (including that the xref offsets actually point at their objects — a reader that repairs a damaged table will open a broken file, so "it opened in Chrome" proves nothing), pages of differing sizes in one document, and the setSize guard rail. The rest is DOM-bound and is covered by the consuming apps' browser suites — in particular Universal PDF's e2e/office-import.e2e.mjs, which drives real .docx/.odt fixtures and asserts on the page sizes pdf.js reads back out.