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

@poli-page/sdk

v0.9.0

Published

Poli Page SDK for Node.js — render PDFs from HTML templates via the Poli Page API

Readme

Poli Page SDK for Node.js

Npm Downloads Ci Codeql Coverage Node Types Install size Bundle Deps Docs License

Official Node.js SDK for Poli Page — render polished PDFs from HTML templates via the Poli Page API.

Documentation: https://poli-page.github.io/sdk-node/

Install

npm install @poli-page/sdk

Requires Node.js 20.18 or later.

Quick start

Project mode — render a published template by slug

import { PoliPage } from '@poli-page/sdk';

const client = new PoliPage({ apiKey: process.env.POLI_PAGE_API_KEY! });

const pdf = await client.render.pdf({
  project: 'getting-started',
  template: 'welcome',
  version: '1.0.0',
  data: { name: 'World' },
});
// pdf is a Uint8Array

Every Poli Page org comes pre-provisioned with a getting-started/welcome template, so the snippet above runs as-is the moment you have an API key — no project setup needed. For your own templates, swap the slugs once you've pushed a version with the poli CLI:

const pdf = await client.render.pdf({
  project: 'billing',
  template: 'invoice',
  version: '1.0.0',
  data: { invoiceNumber: 'INV-001', total: 1280 },
});

Preview inline HTML

render.preview accepts raw HTML for live editing and visual inspection without producing a stored document. Use this for editor previews or layout tests.

const { html, totalPages, environment } = await client.render.preview({
  template: '<h1>Hello {{ name }}</h1>',
  data: { name: 'World' },
});
console.log(`Rendered ${totalPages} page(s) in ${environment} mode`);

render.pdf, render.pdfStream, and render.document require project modeproject + template, optionally pinned to a specific version (omit to render the current draft). Inline HTML is only accepted by render.preview. The SDK enforces this at compile time.

Write a PDF to disk

import { PoliPage } from '@poli-page/sdk';
import { renderToFile } from '@poli-page/sdk/node';

const client = new PoliPage({ apiKey: process.env.POLI_PAGE_API_KEY! });
await renderToFile(
  client,
  { project: 'getting-started', template: 'welcome', version: '1.0.0', data: { name: 'World' } },
  './welcome.pdf',
);

renderToFile streams response bytes directly to disk (bounded memory).

Try it locally — runnable demos

The repo ships three end-to-end demos that exercise every public method against the real API:

npm run demo        # Node ESM — start here
npm run demo:cjs    # Node CommonJS (verifies the dual exports map)
npm run demo:edge   # Cloudflare Worker (proof of edge-runtime support)

First run prompts for a pp_test_* key and saves it to .env. Subsequent runs are silent. Pick by target runtime — see demo/README.md for the full decision guide and what each demo proves.

Stream — for large PDFs or piping to S3 / HTTP responses

const stream = await client.render.pdfStream({
  project: 'billing',
  template: 'invoice',
  version: '1.0.0',
  data: { invoiceNumber: 'INV-001' },
});
// stream is a ReadableStream<Uint8Array>
await s3.upload({ Bucket: 'invoices', Key: 'INV-001.pdf', Body: stream }).promise();

Working with stored documents

Every render produces a stored document, accessible via documentId for later download or thumbnails. render.pdf and render.pdfStream are conveniences that chain a presigned-URL fetch internally to return bytes; render.document returns just the descriptor (skip the auto-download when you'll fetch the bytes later).

// 1. Render and store
const doc = await client.render.document({
  project: 'billing',
  template: 'invoice',
  version: '1.0.0',
  data: { invoiceNumber: 'INV-001' },
  metadata: { customerId: 'cust_123' },  // your own audit data
});
// doc.documentId, doc.pageCount, doc.sizeBytes, doc.presignedPdfUrl, doc.metadata, ...

// 2. Save doc.documentId in your database
await db.invoices.update({ id: 'INV-001' }, { documentId: doc.documentId });

// 3. Later, fetch a fresh presigned URL + download
const fresh = await client.documents.get(doc.documentId);
const pdf = await fresh.downloadPdf();

// 4. Generate thumbnails
const thumbs = await client.documents.thumbnails(doc.documentId, { width: 320, format: 'png' });

// 5. When done, soft-delete
await client.documents.delete(doc.documentId);

The presigned URL has a 15-minute TTL. If downloadPdf() fails with code: 'DOWNLOAD_FAILED' (HTTP 403 from S3), call documents.get(id) to refresh and retry.

Authentication & environments

The mode is determined by the API key prefix:

  • pp_test_… → sandbox mode (not billed, generous rate limits)
  • pp_live_… → live mode (billed, production rate limits)
  • pp_sa_… → service-account keys; environment matches the SA's configuration (sandbox or live)

All prefixes hit the same endpoint (https://api.poli.page). The SDK passes the key through as a Bearer token and never inspects the prefix — pick whichever fits your deploy model.

Methods

| Method | Returns | Description | | ------ | ------- | ----------- | | client.render.pdf(input) | Promise<Uint8Array> | Render a PDF, return bytes | | client.render.pdfStream(input) | Promise<ReadableStream<Uint8Array>> | Render and stream the response | | client.render.preview(input) | Promise<PreviewResult> | Paginated HTML preview | | client.render.document(input) | Promise<DocumentDescriptor> | Render and return descriptor (skip auto-download) | | client.documents.get(id) | Promise<DocumentDescriptor> | Retrieve a stored document | | client.documents.preview(id) | Promise<DocumentPreviewResult> | Stored document's paginated HTML | | client.documents.thumbnails(id, options) | Promise<Thumbnail[]> | Page thumbnails (PNG/JPEG, base64) | | client.documents.delete(id) | Promise<void> | Soft-delete a stored document | | renderToFile(client, input, path) (from @poli-page/sdk/node) | Promise<void> | Render and stream to disk (Node only) |

Configuration

| Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | apiKey | string | (required) | pp_test_* or pp_live_* API key | | baseUrl | string | https://api.poli.page | API base URL | | maxRetries | number | 2 | Max retry attempts on retryable errors | | retryDelay | number (ms) | 500 | Base delay before the first retry | | timeout | number (ms) | 60000 | Per-request timeout | | onRequest | function | — | Called before each HTTP attempt | | onResponse | function | — | Called on each successful response | | onRetry | function | — | Called when a retry is scheduled | | onError | function | — | Called when a call terminates in error |

Error handling

The SDK throws a single error type, PoliPageError, for every failure (API errors, network failures, timeouts, caller cancellation):

import { PoliPage, PoliPageError } from '@poli-page/sdk';

try {
  await client.render.pdf({ ... });
} catch (err) {
  if (err instanceof PoliPageError) {
    if (err.isAuthError())       return refreshCredentials();
    if (err.isRateLimitError())  return queueForLater();
    if (err.isValidationError()) console.error('Bad input:', err.message);
    if (err.isNetworkError())    console.error('Network/timeout');
    if (err.isRetryable())       /* SDK already retried up to maxRetries */;
    console.error(err.code, err.status, err.requestId);
  }
  throw err;
}

For lifecycle and billing failures, route the user to actionable messages rather than treating them as opaque errors:

try {
  await client.render.document({ ... });
} catch (err) {
  if (err instanceof PoliPageError) {
    if (err.code === 'PAYMENT_REQUIRED')       return showBanner('Subscription has unpaid invoices.');
    if (err.code === 'ORGANIZATION_CANCELLED') return showBanner('Subscription cancelled — service is read-only.');
    if (err.code === 'ORGANIZATION_PURGED')    return showBanner('Organization has been purged.');
    if (err.code === 'DOCUMENT_NOT_FOUND')     return show404();
    if (err.code === 'GONE')                   return show410();   // document was soft-deleted
    // ... existing predicate-based handling above ...
  }
  throw err;
}

→ Full error reference: https://poli-page.github.io/sdk-node/reference/errors/

Cancellation

Pass an AbortSignal to cancel a render in flight:

const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);

const pdf = await client.render.pdf({ ..., signal: controller.signal });

When the signal aborts, the SDK throws PoliPageError with code: 'aborted'.

Observability

Hooks fire at well-defined points. They are sync, optional, and never break the request:

const client = new PoliPage({
  apiKey: process.env.POLI_PAGE_API_KEY!,
  onRequest:  ({ method, url, attempt })           => log.debug({ method, url, attempt }),
  onResponse: ({ status, requestId, durationMs })  => metrics.histogram('poli.duration', durationMs),
  onRetry:    ({ attempt, delayMs, reason })       => log.warn(`retry ${attempt} after ${delayMs}ms: ${reason.code}`),
  onError:    (err)                                => sentry.captureException(err),
});

Retries & idempotency

The SDK retries on 5xx, 429, network errors, and timeouts. Backoff is exponential (retryDelay × 2^N) with jitter, capped by Retry-After when the server provides it. Every call sends an auto-generated Idempotency-Key (UUID v4); pass idempotencyKey in the input to override.

→ Full retry semantics: https://poli-page.github.io/sdk-node/production/retries-and-idempotency/

Type system

Full type definitions ship with the package. RenderInput is a discriminated union — invalid combos (e.g. project without template) fail at compile time.

Concurrency & thread-safety

The client is safe to share across all async operations in a single Node process. Construct it once at startup; parallel calls to render are independent and share the underlying fetch connection pool. The client carries no per-request mutable state, so a single instance per process is the expected pattern.

Runtime support

Server-side only. The SDK runs on any modern JavaScript server runtime:

  • Node.js 20.18 or later
  • Cloudflare Workers
  • Vercel Edge Runtime
  • Deno
  • Bun

Browsers are not supported. API keys (pp_test_*, pp_live_*) are secrets and must never be shipped to a browser. Call the SDK from your backend and proxy the result to the client.

The main entry is fully isomorphic (no node:* imports). The Node-only filesystem helper renderToFile lives at @poli-page/sdk/node and is imported separately when needed.

Requirements

Node.js 20.18 or later.

Documentation & support

License

MIT © Poli Page