safeer-pdf-generator
v1.3.2
Published
Framework-agnostic PDF generation library with chunking, merging, S3 upload, and email delivery
Maintainers
Readme
safeer-pdf-generator
A powerful, framework-agnostic TypeScript library for generating PDF reports with chunking, merging, S3 upload, email delivery, lifecycle events, and webhook dispatch.
✨ Features
- 🚀 High Performance — Concurrent chunk processing with memory optimization
- 📊 3 Built-in Templates —
default,simple, andmodernwith full customization - 🎨 Custom Templates — Inline functions, CSS overrides, or register your own
- 🔧 Framework Agnostic — Works with Express, NestJS, Fastify, or standalone
- ☁️ S3 Integration — Direct upload to Amazon S3
- 📧 Email Delivery — Send PDFs as attachments or download links
- 🔀 PDF Operations — Merge, split, and manipulate existing PDFs
- 🌐 BYOB Browser — Bring Your Own Browser for connection pooling
- 📡 Lifecycle Events — Typed event emitters for every stage
- 🔔 Webhook Dispatcher — Auto-POST to your backend with HMAC signing
- 💾 Memory Efficient — Handle large datasets (100k+ records)
- 🛡️ TypeScript — Full type safety and IntelliSense support
📦 Installation
Prerequisites
This package requires Chromium for PDF generation:
# Install Chromium
sudo apt-get install chromium-browser
# Or set custom path
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browserPackage Installation
npm install safeer-pdf-generator
# Optional peer dependencies
npm install @aws-sdk/client-s3 nodemailer🚀 Quick Start
import { generatePdf } from 'safeer-pdf-generator';
const result = await generatePdf({
title: 'Sales Report',
data: [
{ name: 'John Doe', sales: 1200, region: 'North' },
{ name: 'Jane Smith', sales: 1500, region: 'South' },
],
columns: [
{ key: 'name', title: 'Name', dataIndex: 'name' },
{ key: 'sales', title: 'Sales', dataIndex: 'sales' },
{ key: 'region', title: 'Region', dataIndex: 'region' },
],
});
console.log(`PDF: ${result.fileName} (${result.pageCount} pages, ${result.durationMs}ms)`);🎨 Custom Templates
Built-in Templates
Three templates are available out of the box:
// Modern template (gradient header, card-style info)
await generatePdf({ ...options, template: 'modern' });
// Default template (classic table with company header)
await generatePdf({ ...options, template: 'default' });
// Simple template (minimal, lightweight)
await generatePdf({ ...options, template: 'simple' });Custom CSS & HTML Injection
Override styles and add HTML blocks using any built-in template:
const result = await generatePdf({
title: 'Styled Report',
data, columns,
template: 'modern',
customCss: `
.modern-table th {
background: linear-gradient(135deg, #7c3aed, #2563eb) !important;
color: #fff !important;
}
`,
customHtml: {
beforeTable: '<div style="padding: 12px; background: #f5f3ff;">Q4 2025 Summary</div>',
afterTable: '<div style="font-size: 11px; color: #64748b;">Confidential</div>',
},
});Inline Template Function
Full control over the generated HTML:
const result = await generatePdf({
title: 'Custom Report',
data, columns,
template: (params) => {
const { title, data, columns } = params;
const html = `
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>${title}</title></head>
<body>
<h1>${title}</h1>
<table>
<thead><tr>${columns.map(c => `<th>${c.title}</th>`).join('')}</tr></thead>
<tbody>
${data.map(row =>
`<tr>${columns.map(c => `<td>${row[c.dataIndex] ?? '—'}</td>`).join('')}</tr>`
).join('')}
</tbody>
</table>
</body>
</html>
`;
return { html, header: '', footer: '' };
},
});Registered Templates
Register a reusable template globally:
import { registerTemplate, generatePdf } from 'safeer-pdf-generator';
registerTemplate('invoice', (params) => {
const { title, data, columns } = params;
// ... return { html, header, footer }
});
// Use it by name anywhere
await generatePdf({ ...options, template: 'invoice' });🌐 BYOB (Bring Your Own Browser)
Reuse a shared Puppeteer browser instance across multiple generations — saves memory and startup time:
import puppeteer from 'puppeteer';
import { generatePdf } from 'safeer-pdf-generator';
const browser = await puppeteer.launch({ headless: true });
// Generate multiple PDFs using the same browser
for (const report of reports) {
await generatePdf({
...report,
puppeteer: { browserInstance: browser },
});
}
// You manage the browser lifecycle
await browser.close();📡 Lifecycle Events
Listen for typed events during PDF generation:
import { generatePdf, PdfEventEmitter } from 'safeer-pdf-generator';
const events = new PdfEventEmitter();
events.onEvent('generation:started', ({ title, rowCount }) => {
console.log(`Generating: ${title} (${rowCount} rows)`);
});
events.onEvent('generation:complete', ({ result }) => {
console.log(`Done: ${result.pageCount} pages in ${result.durationMs}ms`);
});
events.onEvent('s3:upload:complete', ({ url }) => {
notifyClient(url);
});
events.onEvent('error', ({ error, phase }) => {
alertOps(`PDF failed at ${phase}: ${error.message}`);
});
await generatePdf({ ...options, events });Available events: generation:started, generation:complete, chunk:processed, s3:upload:complete, email:sent, error
🔔 Webhook Dispatcher
Automatically POST to your backend when a PDF is ready. Includes HMAC-SHA256 signing and retry logic:
await generatePdf({
...options,
webhook: {
url: 'https://api.example.com/webhooks/pdf-ready',
secret: process.env.WEBHOOK_SECRET, // HMAC-SHA256 signing
metadata: { tenantId: 'acme', userId: 'u-123' }, // Pass-through data
timeoutMs: 10000,
},
});
// Your endpoint receives:
// POST { s3Url, title, fileName, sizeBytes, pageCount, durationMs, metadata }
// Headers: { X-Webhook-Signature: "sha256=abc123..." }☁️ S3 Upload & 📧 Email
const result = await generatePdf({
title: 'Monthly Report',
data: salesData,
columns: [
{ key: 'name', title: 'Sales Rep', dataIndex: 'name' },
{ key: 'sales', title: 'Sales', dataIndex: 'sales' },
],
s3: {
bucket: 'my-pdfs',
region: 'us-east-1',
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
email: {
to: '[email protected]',
attachmentMode: 'link',
smtp: {
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
},
},
});
console.log(`Uploaded: ${result.s3?.url}`);🏗️ Framework Examples
Express.js
import express from 'express';
import { generatePdf } from 'safeer-pdf-generator';
const app = express();
app.post('/generate-report', async (req, res) => {
try {
const result = await generatePdf({
title: req.body.title,
data: req.body.data,
columns: req.body.columns,
});
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="${result.fileName}"`);
res.send(result.buffer);
} catch (error) {
res.status(500).json({ error: 'PDF generation failed' });
}
});NestJS
import { Injectable } from '@nestjs/common';
import { generatePdf, PdfEventEmitter } from 'safeer-pdf-generator';
@Injectable()
export class PdfService {
async createReport(data: any[], title: string) {
const events = new PdfEventEmitter();
events.onEvent('generation:complete', ({ result }) => {
this.logger.log(`PDF ready: ${result.fileName}`);
});
return generatePdf({
title,
data,
columns: [
{ key: 'id', title: 'ID', dataIndex: 'id' },
{ key: 'name', title: 'Name', dataIndex: 'name' },
],
template: 'modern',
events,
});
}
}📊 Large Dataset Handling
import { generatePdf } from 'safeer-pdf-generator';
const result = await generatePdf({
title: 'Large Report',
data: largeDataset, // 100,000+ records
columns,
chunking: {
enabled: true,
chunkSize: 500, // Rows per chunk
maxConcurrency: 4, // Parallel chunk processing
},
});🔀 PDF Merging
import { mergePdfs } from 'safeer-pdf-generator';
const merged = await mergePdfs({
buffers: [buffer1, buffer2, buffer3],
});🔧 Configuration Reference
PdfGenerationOptions
| Option | Type | Default | Description |
|---|---|---|---|
| title | string | required | Report title |
| data | any[] | required | Array of data rows |
| columns | ColumnDefinition[] | required | Column definitions |
| template | string \| Function | 'default' | Template name or compiler function |
| customCss | string | undefined | CSS injected into the template |
| customHtml | { beforeTable?, afterTable? } | undefined | HTML injected around the table |
| locale | string | 'en' | Locale ('en', 'ar' for RTL) |
| userInfo | UserInfo | undefined | User info shown in header |
| infoSection | InfoSection[] | undefined | Key-value info cards |
| chunking | ChunkingOptions | { enabled: true, chunkSize: 100 } | Chunk processing config |
| s3 | S3UploadConfig \| false | false | S3 upload configuration |
| email | EmailSendConfig \| false | false | Email delivery configuration |
| webhook | WebhookConfig \| false | false | Webhook dispatch configuration |
| events | PdfEventEmitter | undefined | Lifecycle event emitter |
| puppeteer | PuppeteerOptions | { headless: true } | Puppeteer launch options |
| puppeteer.browserInstance | Browser | undefined | External browser (BYOB) |
| hooks | HooksConfig | undefined | Sync lifecycle hooks |
| logging | LoggingAdapter | noOpLogger | Logger implementation |
| timeoutMs | number | 300000 | Global timeout (ms) |
ColumnDefinition
| Field | Type | Description |
|---|---|---|
| key | string | Unique column identifier |
| title | string | Column header text |
| dataIndex | string | Property path in data objects (supports nested: 'address.city') |
| type | 'image' \| 'boolean' \| 'text' \| 'link' | Optional column type for custom rendering |
WebhookConfig
| Field | Type | Default | Description |
|---|---|---|---|
| url | string | required | Endpoint URL |
| secret | string | undefined | HMAC-SHA256 secret |
| metadata | Record<string, any> | undefined | Pass-through data |
| timeoutMs | number | 10000 | Request timeout |
🌟 Why Choose safeer-pdf-generator?
- Production Ready — Used in enterprise applications
- Memory Efficient — Handles massive datasets without memory issues
- Developer Friendly — Full TypeScript support with IntelliSense
- Framework Agnostic — Works with any Node.js framework
- Full Featured — Templates, S3, email, webhooks, events — all in one package
- Active Maintenance — Regular updates and community support
📚 Examples
| Example | Description | |---|---| | simple-usage.js | Basic PDF generation | | custom-template.js | Custom CSS, inline templates, and registry | | byob-and-events.js | Browser reuse + lifecycle events | | webhook-integration.js | Webhook dispatch with HMAC signing | | express-app/ | Full Express.js integration | | nestjs-simplified/ | NestJS service example |
📄 License
MIT © Safeersoft
