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

@bigbinary/neeto-pdf-frontend

v1.0.20

Published

A repo acts as the source of truth for the new nano's structure, configs, data etc.

Downloads

3,284

Readme

neeto-pdf-nano

Add reusable PDF templates, field mapping, preview, download, and email attachments to a Rails and React application.

  • neeto-pdf-engine: storage, APIs, and server-side rendering.
  • @bigbinary/neeto-pdf-frontend: documents, editor, preview, and email attachment UI.

The host supplies an owner for reusable templates and a data record whose values fill each PDF. It also owns authorization, routes, field normalization, email settings, and value formatting.

Required host stack: Rails, Active Record, Active Storage, PostgreSQL, NeetoCommonsBackend::Api::BaseController, React Query, Axios, i18next, neetoUI, neetoMolecules, Formik, and Tailwind CSS.

Backend setup

1. Install the engine

source "NEETO_GEM_SERVER_URL" do
  gem "neeto-pdf-engine"
end
bundle install

2. Copy the document migration

db/migrate/20260706120000_create_neeto_pdf_engine_documents.rb

Give it a free host timestamp, then run:

bundle exec rails db:migrate

Do not run the general engine migration installer; this repository contains unrelated template migrations. owner_id is a UUID. Adapt and test the copied migration first if the owner uses another primary-key type.

3. Configure the owner and render context

Create config/initializers/neeto_pdf_engine.rb:

# frozen_string_literal: true

NeetoPdfEngine.owner_class = "DocumentOwner"
NeetoPdfEngine.render_context_resolver = lambda do |owner:, record_id:|
  PdfDocuments::RenderContextResolver.call(owner:, record_id:)
end

Replace DocumentOwner with the real model. It must be the current organization or support where(organization: current_organization). Add:

class DocumentOwner < ApplicationRecord
  belongs_to :organization

  has_many :neeto_pdf_documents,
    as: :owner,
    class_name: "NeetoPdfEngine::Document",
    dependent: :destroy
end

Implement the render context:

module PdfDocuments
  class RenderContextResolver
    def self.call(owner:, record_id:)
      record = owner.data_records.find(record_id) # Use the real association.

      {
        value_resolver: ValueResolver.new(owner:, record:),
        filename_context: { id: record.id }
      }
    end
  end
end

Always find the data record through the owner. The value resolver must respond to call(mapping):

module PdfDocuments
  class ValueResolver
    def initialize(owner:, record:)
      @owner = owner
      @record = record
    end

    def call(mapping)
      return system_value(mapping["field_slug"]) if mapping["source"] == "system"

      value_for(mapping["field_slug"])
    end

    private

      attr_reader :owner, :record

      def value_for(field_slug)
        # Return the host-formatted value.
      end

      def system_value(field_slug)
        case field_slug
        when "form_name" then owner.name
        when "submission_id" then record.id
        when "submission_date" then I18n.l(record.created_at, format: :long)
        else ""
        end
      end
  end
end

The built-in system keys are form_name, submission_id, and submission_date; the host defines their meaning. For an image, return:

{
  type: "image",
  data: attachment.download,
  content_type: attachment.blob.content_type
}

Use image/svg+xml for SVG data. Return text, a number, or "" for other mappings.

4. Mount and protect the engine

Mount the engine:

mount NeetoPdfEngine::Engine, at: "/neeto_pdf"
const PDF_API_BASE_URL = "/neeto_pdf/api/v1";

The engine authenticates and scopes owners to the current organization. The host must add server authorization to NeetoPdfEngine::Api::V1::DocumentsController, including the render action. canManage only changes the UI.

5. Handle host lifecycle rules

When applicable, add NeetoPdfEngine::Document to host incineration, clone PDF documents with a cloned owner, and remap saved record_id values to cloned fields.

Frontend setup

1. Install the package and PDF runtimes

yarn add @bigbinary/neeto-pdf-frontend [email protected] [email protected]

The package uses the host's Axios case transforms, React Query provider, and i18next instance.

2. Include styles and translations

There is no PDF stylesheet. Keep neetoUI and neetoMolecules styles enabled, and make Tailwind scan @bigbinary packages. For Tailwind CSS 4, adjust this path relative to the host stylesheet:

@import "tailwindcss" important;
@source "../../../../node_modules/@bigbinary";

The standard neeto-commons build loads package translations. Other builds must merge @bigbinary/neeto-pdf-frontend/app/javascript/src/translations/en.json into their i18next resources.

3. Serve the PDF.js worker

Add:

{
  "scripts": {
    "sync:pdf-worker": "mkdir -p public && cp -f node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdf.worker.min.js"
  }
}

Run it before dev and build. Serve the result at:

/pdf.worker.min.js

The generated worker may be ignored in Git.

Use the documents page

import { PdfDocumentsPage } from "@bigbinary/neeto-pdf-frontend";

const PdfDocuments = ({ ownerId, selectedPdfDocumentIds }) => (
  <PdfDocumentsPage
    apiBaseUrl="/neeto_pdf/api/v1"
    canManage={canManagePdfDocuments}
    ownerId={ownerId}
    selectedPdfDocumentIds={selectedPdfDocumentIds}
    onOpenEditor={document => openEditor(document.id)}
  />
);
  • onOpenEditor receives the complete document object.
  • selectedPdfDocumentIds only marks documents already used by email settings.
  • renderHeader and renderContent can fit the page into a host layout.
  • emailRoutes optionally accepts { submitter, receiver } links.
  • Pass canManage explicitly. It only changes the UI.

Uploads use neetoMolecules FileUpload. Active Storage must return signedId. Templates must be PDFs of 10 MB or less.

Use the editor

import { PdfEditorPage } from "@bigbinary/neeto-pdf-frontend";

<PdfEditorPage
  apiBaseUrl="/neeto_pdf/api/v1"
  canManage={canManagePdfDocuments}
  documentId={documentId}
  fields={pdfFields}
  homeUrl={pdfDocumentsUrl}
  isPreviewLoading={isPreviewLoading}
  mode={mode}
  ownerId={ownerId}
  previewPage={previewPage}
  previewRecord={previewRecord}
  previewTotal={previewTotal}
  onBack={goToPdfDocuments}
  onModeChange={setMode}
  onPreviewPageChange={setPreviewPage}
/>;

Use a real homeUrl. mode and onModeChange are only needed for host-controlled mode state.

Field shape

const pdfField = {
  id: "field-id",
  key: "stable-field-key",
  recordId: "field-id",
  slug: "stable-field-key",
  label: "Display label",
  kind: "email",
  renderType: "text",
  icon: EmailIcon,
  typeLabel: "Email",
};

Use a stable recordId, plain-text labels, and renderType: "image" only for image data. Exclude fields that cannot contain values.

Preview record shape

const previewRecord = { id: "data-record-id" };

The editor sends this ID to the render endpoint. The engine then calls render_context_resolver, keeping preview and attachment rendering on the same value path.

Add email attachment controls

import {
  PdfAttachmentSelector,
  PdfAttachmentsPreview,
} from "@bigbinary/neeto-pdf-frontend";
import { Form, Formik } from "formik";

<Formik
  initialValues={{ pdfDocumentIds: persistedPdfDocumentIds }}
  onSubmit={saveEmailSettings}
>
  <Form>
    <PdfAttachmentSelector
      documents={pdfDocuments}
      maxTotalSize={25 * 1024 * 1024}
    />
    <PdfAttachmentsPreview documents={pdfDocuments} />
  </Form>
</Formik>;
Formik:       pdfDocumentIds
Frontend API: customFields.pdfDocumentIds
Rails:        custom_fields["pdf_document_ids"]

Permit custom_fields[:pdf_document_ids] as an array. Remove deleted or inaccessible IDs before saving. Fetch documents with the same owner ID used by the editor. Pass maxTotalSize in bytes to prevent selections whose combined template size would exceed the email attachment limit. The selector displays the used size, disables PDFs that no longer fit, and explains the remaining space when a disabled PDF is clicked.

Render attachments on the server

render_context = PdfDocuments::RenderContextResolver.call(
  owner:,
  record_id: record.id
)

owner.neeto_pdf_documents.where(id: selected_pdf_document_ids).map do |document|
  NeetoPdfEngine::Documents::RendererService.new(
    document,
    **render_context
  ).process
end

Each result contains:

{
  filename: "document-record-id.pdf",
  mime_type: "application/pdf",
  content: "<binary PDF>",
  truncated_mapping_ids: []
}

The host attaches or encodes content. Encrypted PDFs raise NeetoPdfEngine::EncryptedDocumentError. Built-in fonts support ASCII and Latin-1; other characters are removed during server rendering.

API

With the engine mounted at /neeto_pdf:

GET    /neeto_pdf/api/v1/documents
POST   /neeto_pdf/api/v1/documents
GET    /neeto_pdf/api/v1/documents/:id
PATCH  /neeto_pdf/api/v1/documents/:id
PUT    /neeto_pdf/api/v1/documents/:id
PATCH  /neeto_pdf/api/v1/documents/:id/template_file
POST   /neeto_pdf/api/v1/documents/:id/clone
POST   /neeto_pdf/api/v1/documents/:id/render
DELETE /neeto_pdf/api/v1/documents/:id

Every request requires owner_id; clients cannot choose owner_type. Render also requires record_id and accepts temporary mappings without persisting them. Uploads accept an Active Storage signed blob ID or multipart template_file.

The default output filename is:

<parameterized-document-name>-{{id}}.pdf

filename_context[:id] replaces {{id}}.

Local development

gem "neeto-pdf-engine", path: "../neeto-pdf-nano"

For local frontend work, follow the frontend package development guide. To publish, follow the building and releasing packages guide.