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

@pyreon/document

v0.51.0

Published

Universal document rendering for Pyreon — one template, every output format (HTML, PDF, DOCX, email, XLSX, Markdown, and more)

Downloads

654

Readme

@pyreon/document

Universal document rendering — one template, 20 output formats.

A platform-agnostic document tree (18 primitives) plus a renderer registry that produces HTML, PDF, DOCX, PPTX, XLSX, email-safe HTML, Markdown, plain text, CSV, SVG, JSON / JSONL, and direct payloads for Slack / Teams / Discord / Telegram / Notion / Confluence / WhatsApp / Google Chat. Use it for invoices, reports, exports, transactional emails, and "send to anywhere" surfaces — write the document tree once, render to whatever the caller needs. Heavy renderers (PDF, DOCX, XLSX, PPTX) lazy-load — they ship in the published lib/ but only land in the consumer's bundle when their format is invoked.

Install

bun add @pyreon/document @pyreon/core @pyreon/reactivity

Quick start — JSX form

import { Document, Page, Heading, Text, Table, render } from '@pyreon/document'

const doc = (
  <Document title="Q4 Report" author="Finance">
    <Page>
      <Heading>Sales Report</Heading>
      <Text>Q4 2026 performance summary.</Text>
      <Table
        columns={['Region', 'Revenue']}
        rows={[
          ['US', '$1M'],
          ['EU', '$800K'],
        ]}
      />
    </Page>
  </Document>
)

await render(doc, 'pdf') // Uint8Array
await render(doc, 'email') // email-safe HTML string
await render(doc, 'md') // Markdown string
await render(doc, 'docx') // Uint8Array

Quick start — builder form

import { createDocument } from '@pyreon/document'

const doc = createDocument({ title: 'Report' })
  .heading('Sales Report')
  .text('Q4 performance summary.')
  .table({ columns: ['Region', 'Revenue'], rows: [['US', '$1M']] })

const pdf = await doc.toPdf()
const md = await doc.toMarkdown()
const slack = await doc.toSlack()

Builder methods mirror every renderer: toHtml / toPdf / toDocx / toPptx / toXlsx / toEmail / toMarkdown / toText / toCsv / toSvg / toSlack / toTeams / toDiscord / toTelegram / toNotion / toConfluence / toWhatsApp / toGoogleChat / toJson / toJsonl.

Primitives — 18 nodes

| Category | Primitives | |---|---| | Layout | Document, Page, PageBreak, Section, Row, Column, Spacer, Divider | | Content | Heading, Text, Image, Link, Button, Code, Quote | | Lists | List, ListItem | | Tables | Table |

Every primitive is a pure factory returning a DocNode — no runtime, no JSX runtime needed if you build the tree imperatively. isDocNode(value) narrows at boundaries.

Output formats (20)

'html' · 'pdf' · 'docx' · 'pptx' · 'email' · 'xlsx' · 'md' · 'text' · 'csv' · 'svg' · 'slack' · 'teams' · 'discord' · 'telegram' · 'notion' · 'confluence' · 'whatsapp' · 'google-chat' · 'json' · 'jsonl'

Different formats return different result types — string for the textual ones (HTML, Markdown, text, CSV, JSON, the chat payloads), Uint8Array for binary ones (PDF, DOCX, XLSX, PPTX), and string for SVG.

render(doc, format, options?)

import { render } from '@pyreon/document'

await render(doc, 'pdf', {
  baseUrl: 'https://example.com/', // resolve relative <Image> sources
  direction: 'ltr', // or 'rtl'
  fonts: { Inter: { normal: 'https://…/Inter.ttf' } },
  styles: { heading: { color: '#1F2937', fontSize: 24 } },
})

Browser download

import { download } from '@pyreon/document'

await download(doc, 'report.pdf')
await download(doc, 'report.docx')
await download(doc, 'data.xlsx')
await download(doc, 'tree.json')

download(node, filename, options?) renders + triggers a blob URL download — the file extension picks the format (.pdf → pdf, .md → markdown, .json → json, .jsonl/.ndjson → jsonl, …). Browser-only.

Custom renderers

import { registerRenderer, unregisterRenderer, type DocumentRenderer } from '@pyreon/document'

const sapRenderer: DocumentRenderer = {
  render(node, options) {
    return convertToSapIdoc(node)
  },
}

registerRenderer('sap-idoc', sapRenderer)

await render(doc, 'sap-idoc' as never) // user-extended format

unregisterRenderer('sap-idoc')

For tests: _resetRenderers() restores the built-in registry.

Reactive metadata via @pyreon/document-primitives

If you need the document tree to also render in the browser (live preview + export from the same source), use @pyreon/document-primitives for rocketstyle-based components that render in the DOM AND export here via @pyreon/connector-document. @pyreon/document itself is purely a renderer — no DOM mounting.

Lazy-loaded heavy renderers

Approximate chunk sizes (already vendored in lib/):

| Renderer | Approx chunk | |---|---| | PDF (pdfmake + fonts) | ~3 MB | | DOCX | ~700 KB | | XLSX | ~1.1 MB | | PPTX | ~400 KB |

These load only when their format is invoked. The 14 MB published lib/ is the trade-off for a single-install batteries-included experience; consumers tree-shake at their build step so unused renderers don't ship to end users.

Gotchas

  • Result type varies by formatrender(doc, 'pdf') returns Uint8Array, render(doc, 'md') returns string. The TypeScript return type is the union; narrow with the format literal.
  • download() is browser-only — calling it in Node throws.
  • Tables with mismatched column / row widths silently truncate or pad — validate upstream.
  • Relative <Image> sources need options.baseUrl to resolve under PDF / DOCX renderers (they fetch the image bytes server-side).
  • PDF requires external font URLs for non-default typefaces — pass via options.fonts. The default font is Roboto, served by pdfmake's CDN at build time of the vendored chunk.
  • The chat-platform renderers (slack / teams / discord / telegram / notion / confluence / whatsapp / google-chat) produce platform-specific payload strings — they are NOT generic markdown. Test against the real platform's preview before relying on a render.

Documentation

Full docs: pyreon.dev/docs/document (or docs/src/content/docs/document.md in this repo).

Inline links

An inline <Link> inside a <Text> paragraph keeps its link-ness across formats: PDF (URI annotation), DOCX (ExternalHyperlink), Slack (<url|label>), Telegram (<a href>), WhatsApp/plain text (label (url) — no link markup exists there), and the structural formats (html/email/md) natively. Inline bold/italic spans remain block-level (<Text bold>) — the full rich-text run model is a tracked follow-up.

License

MIT