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

safeersoft-pdf-reporter

v1.0.5

Published

Framework-agnostic PDF generation library with chunking, merging, S3 upload, and email delivery

Downloads

74

Readme

safeersoft-pdf-reporter

npm version License: MIT Node.js CI

A framework-agnostic TypeScript library for generating PDF reports with chunking, merging, S3 upload, and email delivery capabilities. Built with Puppeteer and pdf-lib for high-performance PDF generation.

Features

  • 🚀 High Performance: Concurrent chunk processing with configurable limits and memory optimization
  • 📊 Rich Templates: Built-in templates with RTL support, images, charts, and custom styling
  • 🔧 Framework Agnostic: Works with any Node.js framework (Express, NestJS, Fastify, Koa) or standalone scripts
  • ☁️ S3 Integration: Optional upload to Amazon S3 with presigned URLs and custom ACLs
  • 📧 Email Delivery: Send PDFs as attachments or download links with customizable templates
  • 🎨 Template System: Pluggable templates with custom compilers, CSS injection, and dynamic content
  • 🔀 PDF Operations: Merge, split, extract pages, analyze, and validate existing PDFs
  • 💾 Memory Efficient: Sequential processing to handle datasets of any size (tested with 100k+ records)
  • 🛡️ Type Safe: Full TypeScript support with comprehensive type definitions and IntelliSense
  • 🪝 Extensible: Hook system for custom processing logic, data transformation, and workflow integration
  • 📈 Performance Monitoring: Built-in estimation, timing, and resource usage tracking
  • 🌐 Internationalization: Multi-language support with RTL text, custom fonts, and localization helpers
  • 🔒 Security: Input validation, sanitization, and secure file handling
  • 🐛 Error Handling: Comprehensive error types with detailed context and recovery suggestions

Quick Start

Installation

npm install safeersoft-pdf-reporter

# Optional peer dependencies
npm install @aws-sdk/client-s3 nodemailer  # For S3 and email features

Basic Usage

import { generatePdf } from 'safeersoft-pdf-reporter';

const result = await generatePdf({
  title: 'Sales Report',
  data: [
    { name: 'John Doe', sales: 1200, region: 'North', commission: 0.15, lastSale: '2024-01-15' },
    { name: 'Jane Smith', sales: 1500, region: 'South', commission: 0.18, lastSale: '2024-01-20' },
    { name: 'Bob Johnson', sales: 980, region: 'East', commission: 0.12, lastSale: '2024-01-18' },
  ],
  columns: [
    { key: 'name', title: 'Sales Rep', dataIndex: 'name', flex: 3 },
    { key: 'sales', title: 'Sales ($)', dataIndex: 'sales', flex: 2, type: 'currency' },
    { key: 'region', title: 'Region', dataIndex: 'region', flex: 2 },
    { key: 'commission', title: 'Commission', dataIndex: 'commission', flex: 2, type: 'percentage' },
    { key: 'lastSale', title: 'Last Sale', dataIndex: 'lastSale', flex: 2, type: 'date' },
  ],
  userInfo: {
    companyName: 'Acme Corporation',
    companyLogo: 'https://example.com/logo.png',
    name: 'Report Generator',
    email: '[email protected]',
  },
});

console.log(`Generated PDF: ${result.sizeBytes} bytes, ${result.pageCount} pages`);
console.log(`File saved as: ${result.fileName}`);
// result.buffer contains the PDF data - can be saved to file or sent as response

Advanced Configuration

import { generatePdf, consoleLogger } from 'safeersoft-pdf-reporter';

const result = await generatePdf({
  title: 'Comprehensive Sales Analysis',
  data: salesData,
  columns: salesColumns,
  
  // Company branding and information
  userInfo: {
    companyName: 'Acme Corporation',
    companyLogo: 'https://cdn.example.com/logo.png',
    name: 'Sales Analytics Team',
    email: '[email protected]',
    phone: '+1-555-0123',
    address: '123 Business St, Suite 100, City, State 12345',
  },
  
  // Header information sections
  infoSection: [
    { label: 'Report Period', value: 'Q1 2024' },
    { label: 'Generated By', value: 'Sales Analytics System' },
    { label: 'Department', value: 'Sales & Marketing' },
    { label: 'Confidentiality', value: 'Internal Use Only' },
  ],
  
  // Internationalization and localization
  locale: 'en-US',
  translationFn: (key) => translations[key] || key,
  
  // Performance optimization
  chunking: {
    enabled: true,
    chunkSize: 150,
    maxConcurrency: 3,
  },
  
  // PDF formatting options
  pdf: {
    format: 'A4',
    orientation: 'portrait',
    margin: {
      top: '15mm',
      bottom: '15mm',
      left: '10mm',
      right: '10mm',
    },
    displayHeaderFooter: true,
    printBackground: true,
  },
  
  // Advanced browser configuration
  puppeteer: {
    headless: 'new',
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--disable-dev-shm-usage',
      '--disable-gpu',
      '--max-old-space-size=4096',
    ],
    timeout: 30000,
  },
  
  // Custom logging
  logging: consoleLogger,
  
  // Overall timeout
  timeoutMs: 300000, // 5 minutes
});

With S3 Upload

import { generatePdf } from 'safeersoft-pdf-reporter';

const result = await generatePdf({
  title: 'Quarterly Financial Report',
  data: financialData,
  columns: financialColumns,
  s3: {
    bucket: 'company-reports-bucket',
    region: 'us-east-1',
    folder: 'quarterly-reports/2024/Q1',
    
    // Optional: Provide credentials (or use IAM roles/environment variables)
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
    
    // File access control
    acl: 'private', // 'public-read', 'private', 'authenticated-read'
    
    // Custom metadata
    metadata: {
      department: 'finance',
      quarter: 'Q1-2024',
      confidentiality: 'internal',
    },
    
    // Cache control for CDN
    cacheControl: 'max-age=86400', // 24 hours
    
    // Content disposition for downloads
    contentDisposition: 'attachment; filename="Q1-2024-Financial-Report.pdf"',
  },
});

console.log(`PDF uploaded to S3: ${result.s3?.url}`);
console.log(`S3 Key: ${result.s3?.key}`);
console.log(`Bucket: ${result.s3?.bucket}`);
console.log(`Presigned URL (24h): ${result.s3?.presignedUrl}`);

With Email Delivery

import { generatePdf } from 'safeersoft-pdf-reporter';

const result = await generatePdf({
  title: 'Monthly Performance Report',
  data: performanceData,
  columns: performanceColumns,
  email: {
    to: ['[email protected]', '[email protected]'],
    cc: ['[email protected]'],
    bcc: ['[email protected]'],
    from: '[email protected]',
    replyTo: '[email protected]',
    subject: 'Monthly Performance Report - {{date}}',
    
    // Email template with dynamic content
    htmlTemplate: `
      <h2>Monthly Performance Report</h2>
      <p>Dear {{recipientName}},</p>
      <p>Please find attached the monthly performance report for {{period}}.</p>
      <p><strong>Report Summary:</strong></p>
      <ul>
        <li>Total Records: {{recordCount}}</li>
        <li>Report Pages: {{pageCount}}</li>
        <li>Generated: {{generatedDate}}</li>
      </ul>
      <p>Best regards,<br>Analytics Team</p>
    `,
    
    // Plain text fallback
    textTemplate: `
      Monthly Performance Report
      
      Dear {{recipientName}},
      
      Please find attached the monthly performance report for {{period}}.
      
      Report Summary:
      - Total Records: {{recordCount}}
      - Report Pages: {{pageCount}}
      - Generated: {{generatedDate}}
      
      Best regards,
      Analytics Team
    `,
    
    // Attachment mode: 'attachment' (default) or 'link' (S3 URL)
    attachmentMode: 'attachment',
    
    // SMTP configuration
    smtp: {
      host: 'smtp.company.com',
      port: 587,
      secure: false, // true for 465, false for other ports
      auth: {
        user: process.env.SMTP_USER,
        pass: process.env.SMTP_PASS,
      },
      
      // Advanced SMTP options
      connectionTimeout: 10000,
      greetingTimeout: 5000,
      socketTimeout: 10000,
      
      // TLS options
      tls: {
        rejectUnauthorized: false,
        ciphers: 'SSLv3',
      },
    },
    
    // Custom template variables
    templateVariables: {
      recipientName: 'Team',
      period: 'January 2024',
      generatedDate: new Date().toLocaleDateString(),
    },
    
    // Email priority
    priority: 'normal', // 'high', 'normal', 'low'
    
    // Custom headers
    headers: {
      'X-Report-Type': 'performance',
      'X-Department': 'analytics',
    },
  },
});

console.log(`Email sent: ${result.email?.sent}`);
console.log(`Message ID: ${result.email?.messageId}`);
console.log(`Recipients: ${result.email?.recipients?.join(', ')}`);

Advanced Usage

Large Dataset Processing

For large datasets, the library automatically chunks data for optimal performance:

import { generateOptimizedPdf, estimatePdfGeneration } from 'safeersoft-pdf-reporter';

// Get comprehensive performance estimates
const estimate = await estimatePdfGeneration({
  data: largeDataset, // 10,000+ rows
  columns: reportColumns,
  options: {
    template: 'modern-business',
    chunking: {
      chunkSize: 200,
      maxConcurrency: 4,
    },
  },
});

console.log(`📊 Performance Estimation:`);
console.log(`  ⏱️  Estimated time: ${estimate.estimatedTimeMs}ms (${(estimate.estimatedTimeMs / 1000).toFixed(1)}s)`);
console.log(`  💾 Estimated memory: ${estimate.estimatedMemoryMB}MB`);
console.log(`  📄 Estimated pages: ${estimate.estimatedPages}`);
console.log(`  🧩 Recommended chunks: ${estimate.chunking.chunks}`);
console.log(`  ⚡ Concurrency: ${estimate.chunking.maxConcurrency}`);
console.log(`  📋 Recommendations:`, estimate.recommendations);

// Generate with intelligent optimization
const result = await generateOptimizedPdf({
  title: 'Large Dataset Analysis Report',
  data: largeDataset,
  columns: reportColumns,
  
  // Automatic chunking optimization
  chunking: {
    chunkSize: estimate.chunking.rowsPerChunk,
    maxConcurrency: estimate.chunking.maxConcurrency,
    enabled: true,
  },
  
  // Performance monitoring hooks
  hooks: {
    beforeChunkRender: async (params) => {
      console.log(`🔄 Processing chunk ${params.chunkInfo?.current}/${params.chunkInfo?.total}`);
      console.log(`   📊 Rows: ${params.data.length}`);
    },
    
    afterChunkRender: async (buffer, params) => {
      const sizeMB = (buffer.length / 1024 / 1024).toFixed(2);
      console.log(`✅ Chunk ${params.chunkIndex + 1} completed: ${sizeMB}MB`);
    },
    
    beforeMerge: async (chunks) => {
      const totalSize = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
      console.log(`🔗 Merging ${chunks.length} chunks (${(totalSize / 1024 / 1024).toFixed(2)}MB total)`);
    },
    
    afterMerge: async (finalPdf) => {
      console.log(`🎉 Merge completed: ${(finalPdf.length / 1024 / 1024).toFixed(2)}MB final size`);
    },
  },
});

console.log(`📈 Final Results:`);
console.log(`  📄 Pages: ${result.pageCount}`);
console.log(`  💾 Size: ${(result.sizeBytes / 1024 / 1024).toFixed(2)}MB`);
console.log(`  ⏱️  Time: ${result.processingTime}ms`);

Advanced Template System

Create sophisticated custom templates with dynamic content:

import { registerTemplate, generatePdf } from 'safeersoft-pdf-reporter';

// Register a comprehensive business report template
registerTemplate('executive-dashboard', (params) => {
  const { title, data, columns, userInfo, translationFn: t = k => k } = params;
  
  // Calculate summary statistics
  const totalRevenue = data.reduce((sum, row) => sum + (row.revenue || 0), 0);
  const avgRevenue = totalRevenue / data.length;
  const topPerformer = data.sort((a, b) => (b.revenue || 0) - (a.revenue || 0))[0];
  
  // Group data by categories
  const byRegion = data.reduce((acc, row) => {
    const region = row.region || 'Unknown';
    acc[region] = (acc[region] || 0) + (row.revenue || 0);
    return acc;
  }, {});
  
  const html = `
    <!DOCTYPE html>
    <html lang="${params.locale || 'en'}">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>${title}</title>
        <style>
          * { box-sizing: border-box; margin: 0; padding: 0; }
          
          body { 
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            color: #333;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
          }
          
          .container {
            background: white;
            border-radius: 20px;
            box-shadow: 0 25px 50px rgba(0,0,0,0.15);
            overflow: hidden;
            max-width: 1200px;
            margin: 0 auto;
          }
          
          .header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 40px;
            text-align: center;
            position: relative;
          }
          
          .header::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-image: 
              radial-gradient(circle at 25% 25%, rgba(255,255,255,0.1) 2px, transparent 2px),
              radial-gradient(circle at 75% 75%, rgba(255,255,255,0.1) 2px, transparent 2px);
            background-size: 50px 50px;
            animation: float 20s infinite linear;
          }
          
          @keyframes float {
            0% { transform: translate(0, 0); }
            100% { transform: translate(-50px, -50px); }
          }
          
          .header h1 {
            font-size: 2.8em;
            font-weight: 700;
            margin-bottom: 10px;
            position: relative;
            z-index: 1;
          }
          
          .header .subtitle {
            font-size: 1.2em;
            opacity: 0.9;
            position: relative;
            z-index: 1;
          }
          
          .company-info {
            display: flex;
            align-items: center;
            justify-content: center;
            margin-top: 20px;
            position: relative;
            z-index: 1;
          }
          
          .company-logo {
            width: 60px;
            height: 60px;
            margin-right: 15px;
            border-radius: 50%;
            border: 3px solid rgba(255,255,255,0.3);
          }
          
          .executive-summary {
            padding: 40px;
            background: linear-gradient(135deg, #f8f9fa, #e9ecef);
            border-bottom: 1px solid #dee2e6;
          }
          
          .summary-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 25px;
            margin-bottom: 30px;
          }
          
          .summary-card {
            background: white;
            padding: 25px;
            border-radius: 15px;
            box-shadow: 0 8px 25px rgba(0,0,0,0.1);
            border: 1px solid #e9ecef;
            text-align: center;
            transition: transform 0.3s ease;
          }
          
          .summary-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 15px 35px rgba(0,0,0,0.15);
          }
          
          .summary-value {
            font-size: 2.5em;
            font-weight: 700;
            background: linear-gradient(135deg, #667eea, #764ba2);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            margin-bottom: 8px;
          }
          
          .summary-label {
            color: #666;
            font-weight: 600;
            font-size: 0.9em;
            text-transform: uppercase;
            letter-spacing: 0.5px;
          }
          
          .content {
            padding: 40px;
          }
          
          .section {
            margin-bottom: 40px;
          }
          
          .section-title {
            font-size: 1.8em;
            font-weight: 600;
            color: #495057;
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 3px solid #667eea;
            position: relative;
          }
          
          .section-title::after {
            content: '';
            position: absolute;
            bottom: -3px;
            left: 0;
            width: 50px;
            height: 3px;
            background: #764ba2;
          }
          
          .data-table {
            width: 100%;
            border-collapse: collapse;
            background: white;
            border-radius: 15px;
            overflow: hidden;
            box-shadow: 0 10px 30px rgba(0,0,0,0.1);
            margin: 20px 0;
          }
          
          .data-table th {
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            padding: 20px 15px;
            text-align: left;
            font-weight: 600;
            font-size: 0.9em;
            text-transform: uppercase;
            letter-spacing: 0.5px;
          }
          
          .data-table td {
            padding: 18px 15px;
            border-bottom: 1px solid #f1f3f4;
            transition: background-color 0.3s ease;
          }
          
          .data-table tr:hover td {
            background-color: #f8f9fa;
          }
          
          .data-table tr:last-child td {
            border-bottom: none;
          }
          
          .region-chart {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
            gap: 15px;
            margin: 20px 0;
          }
          
          .region-item {
            background: white;
            padding: 20px;
            border-radius: 12px;
            box-shadow: 0 6px 20px rgba(0,0,0,0.08);
            text-align: center;
            border: 1px solid #e9ecef;
          }
          
          .region-name {
            font-weight: 600;
            color: #495057;
            margin-bottom: 8px;
          }
          
          .region-value {
            font-size: 1.4em;
            font-weight: 700;
            color: #667eea;
          }
          
          .footer-info {
            background: #f8f9fa;
            padding: 30px 40px;
            border-top: 1px solid #dee2e6;
            color: #666;
            text-align: center;
            font-size: 0.9em;
          }
          
          .currency { color: #28a745; font-weight: 600; }
          .percentage { color: #007bff; font-weight: 600; }
          .date { color: #6c757d; }
          .highlight { background: linear-gradient(135deg, #fff3cd, #ffeaa7); padding: 2px 8px; border-radius: 4px; }
          
          @media print {
            body { background: white; padding: 0; }
            .container { box-shadow: none; border-radius: 0; }
          }
        </style>
      </head>
      <body>
        <div class="container">
          <div class="header">
            <div class="company-info">
              ${userInfo?.companyLogo ? `<img src="${userInfo.companyLogo}" alt="Company Logo" class="company-logo">` : ''}
              <div>
                <h1>${title}</h1>
                <div class="subtitle">${userInfo?.companyName || 'Executive Dashboard'}</div>
              </div>
            </div>
          </div>
          
          <div class="executive-summary">
            <h2 style="text-align: center; margin-bottom: 30px; color: #495057;">Executive Summary</h2>
            <div class="summary-grid">
              <div class="summary-card">
                <div class="summary-value">${data.length.toLocaleString()}</div>
                <div class="summary-label">${t('Total Records')}</div>
              </div>
              <div class="summary-card">
                <div class="summary-value">$${(totalRevenue / 1000).toFixed(0)}K</div>
                <div class="summary-label">${t('Total Revenue')}</div>
              </div>
              <div class="summary-card">
                <div class="summary-value">$${(avgRevenue / 1000).toFixed(1)}K</div>
                <div class="summary-label">${t('Average Revenue')}</div>
              </div>
              <div class="summary-card">
                <div class="summary-value">${Object.keys(byRegion).length}</div>
                <div class="summary-label">${t('Active Regions')}</div>
              </div>
            </div>
            
            ${topPerformer ? `
              <div style="text-align: center; padding: 20px; background: white; border-radius: 12px; box-shadow: 0 6px 20px rgba(0,0,0,0.08);">
                <h3 style="color: #495057; margin-bottom: 10px;">🏆 Top Performer</h3>
                <div style="font-size: 1.3em; font-weight: 600; color: #667eea;">${topPerformer.name || 'Unknown'}</div>
                <div style="color: #28a745; font-weight: 600;">$${(topPerformer.revenue || 0).toLocaleString()}</div>
              </div>
            ` : ''}
          </div>
          
          <div class="content">
            <div class="section">
              <h2 class="section-title">${t('Performance by Region')}</h2>
              <div class="region-chart">
                ${Object.entries(byRegion).map(([region, revenue]) => `
                  <div class="region-item">
                    <div class="region-name">${region}</div>
                    <div class="region-value">$${((revenue as number) / 1000).toFixed(0)}K</div>
                  </div>
                `).join('')}
              </div>
            </div>
            
            <div class="section">
              <h2 class="section-title">${t('Detailed Performance Data')}</h2>
              <table class="data-table">
                <thead>
                  <tr>
                    ${columns.map(col => `<th>${col.title}</th>`).join('')}
                  </tr>
                </thead>
                <tbody>
                  ${data.slice(0, 50).map(row => `
                    <tr>
                      ${columns.map(col => {
                        let value = row[col.dataIndex];
                        let className = '';
                        
                        if (col.type === 'currency' && typeof value === 'number') {
                          value = `$${value.toLocaleString()}`;
                          className = 'currency';
                        } else if (col.type === 'percentage' && typeof value === 'number') {
                          value = `${(value * 100).toFixed(1)}%`;
                          className = 'percentage';
                        } else if (col.type === 'date') {
                          className = 'date';
                        }
                        
                        return `<td class="${className}">${value || ''}</td>`;
                      }).join('')}
                    </tr>
                  `).join('')}
                </tbody>
              </table>
              
              ${data.length > 50 ? `
                <div style="text-align: center; margin-top: 20px; color: #666; font-style: italic;">
                  Showing first 50 of ${data.length.toLocaleString()} total records
                </div>
              ` : ''}
            </div>
          </div>
          
          <div class="footer-info">
            <div><strong>${t('Generated')}:</strong> ${new Date().toLocaleString()}</div>
            <div><strong>${t('Report Type')}:</strong> Executive Dashboard</div>
            <div><strong>${t('Confidentiality')}:</strong> Internal Use Only</div>
            ${userInfo?.name ? `<div><strong>${t('Prepared by')}:</strong> ${userInfo.name}</div>` : ''}
          </div>
        </div>
      </body>
    </html>
  `;

  return {
    html,
    header: `
      <div style="display: flex; justify-content: space-between; align-items: center; padding: 10px 20px; background: linear-gradient(135deg, #667eea, #764ba2); color: white; font-size: 10px;">
        <div><strong>${title}</strong></div>
        <div>Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>
        <div>${new Date().toLocaleDateString()}</div>
      </div>
    `,
    footer: `
      <div style="text-align: center; padding: 8px; font-size: 9px; color: #666; border-top: 1px solid #eee;">
        Confidential - Generated by Safeersoft PDF Reporter | ${userInfo?.companyName || 'Company Name'}
      </div>
    `,
  };
});

// Use the advanced template
const result = await generatePdf({
  title: 'Q1 2024 Executive Dashboard',
  data: executiveData,
  columns: executiveColumns,
  template: 'executive-dashboard',
  locale: 'en-US',
  userInfo: {
    companyName: 'TechCorp Industries',
    companyLogo: 'https://cdn.company.com/logo.png',
    name: 'Executive Analytics Team',
    email: '[email protected]',
  },
  translationFn: (key) => {
    const translations = {
      'Total Records': 'Total Records',
      'Total Revenue': 'Total Revenue',
      'Average Revenue': 'Average Revenue',
      'Active Regions': 'Active Regions',
      'Performance by Region': 'Performance by Region',
      'Detailed Performance Data': 'Detailed Performance Data',
      'Generated': 'Generated',
      'Report Type': 'Report Type',
      'Confidentiality': 'Confidentiality',
      'Prepared by': 'Prepared by',
    };
    return translations[key] || key;
  },
});

Create and register custom templates:

import { registerTemplate, generatePdf } from 'safeersoft-pdf-reporter';

// Register a custom template
registerTemplate('invoice', (params) => {
  const { title, data, columns, userInfo } = params;
  
  const html = `
    <html>
      <body>
        <h1>${title}</h1>
        <div>Company: ${userInfo?.companyName}</div>
        <table>
          <thead>
            ${columns.map(col => `<th>${col.title}</th>`).join('')}
          </thead>
          <tbody>
            ${data.map(row => 
              `<tr>${columns.map(col => `<td>${row[col.dataIndex] || ''}</td>`).join('')}</tr>`
            ).join('')}
          </tbody>
        </table>
      </body>
    </html>
  `;

  return {
    html,
    header: `<div>${title} - Page <span class="pageNumber"></span></div>`,
    footer: `<div>${new Date().toLocaleDateString()}</div>`,
  };
});

// Use the custom template
const result = await generatePdf({
  title: 'Invoice #12345',
  data: invoiceItems,
  columns: invoiceColumns,
  template: 'invoice',
  userInfo: {
    companyName: 'Acme Corp',
  },
});

Comprehensive PDF Operations

Advanced PDF manipulation with detailed control:

import { 
  mergePdfs, 
  splitPdf, 
  extractPages, 
  getPdfInfo, 
  analyzePdfs, 
  validatePdfs 
} from 'safeersoft-pdf-reporter';

// Comprehensive PDF analysis
const pdfBuffers = [pdf1Buffer, pdf2Buffer, pdf3Buffer];
const analysisResults = await analyzePdfs(pdfBuffers);

console.log('📊 PDF Analysis Results:');
analysisResults.forEach((result, index) => {
  console.log(`  📄 PDF ${index + 1}:`);
  console.log(`    📋 Pages: ${result.pageCount}`);
  console.log(`    💾 Size: ${(result.fileSize / 1024).toFixed(1)}KB`);
  console.log(`    📐 Dimensions: ${result.dimensions.width}x${result.dimensions.height}`);
  console.log(`    🔒 Encrypted: ${result.isEncrypted ? 'Yes' : 'No'}`);
  console.log(`    ✅ Valid: ${result.isValid ? 'Yes' : 'No'}`);
  console.log(`    📅 Created: ${result.creationDate}`);
  console.log(`    ✏️  Modified: ${result.modificationDate}`);
  console.log(`    👤 Author: ${result.author || 'Unknown'}`);
  console.log(`    🏷️  Title: ${result.title || 'Untitled'}`);
});

// Validate PDF integrity
const validationResults = await validatePdfs(pdfBuffers);
console.log('\n🔍 PDF Validation:');
validationResults.forEach((result, index) => {
  console.log(`  📄 PDF ${index + 1}: ${result.isValid ? '✅ Valid' : '❌ Invalid'}`);
  if (!result.isValid) {
    console.log(`    ⚠️  Issues: ${result.errors.join(', ')}`);
  }
});

// Advanced merging with options
const mergedPdf = await mergePdfs(pdfBuffers, {
  // Add bookmarks for each source PDF
  addBookmarks: true,
  bookmarkTitles: ['Financial Report', 'Sales Analysis', 'Marketing Summary'],
  
  // Include metadata
  metadata: {
    title: 'Comprehensive Business Report',
    author: 'Analytics Team',
    subject: 'Q1 2024 Business Analysis',
    keywords: 'finance, sales, marketing, quarterly',
    creator: 'PDF Reporter Library',
  },
  
  // Page numbering options
  addPageNumbers: true,
  pageNumberStyle: 'Page {current} of {total}',
  pageNumberPosition: 'bottom-center',
  
  // Quality settings
  optimize: true,
  compressionLevel: 6, // 1-9, higher = more compression
  
  logging: consoleLogger,
});

console.log('\n🔗 Merge Results:');
console.log(`  📄 Total pages: ${mergedPdf.pageCount}`);
console.log(`  💾 Final size: ${(mergedPdf.length / 1024).toFixed(1)}KB`);

// Intelligent page extraction
const largeReport = await generatePdf({
  title: 'Large Business Report',
  data: largeDataset,
  columns: reportColumns,
});

// Extract executive summary (first 3 pages)
const executiveSummary = await extractPages(largeReport.buffer, [0, 1, 2], {
  maintainBookmarks: true,
  updatePageReferences: true,
  addExtractedPageInfo: true,
  
  metadata: {
    title: 'Executive Summary - Large Business Report',
    subject: 'Extracted Pages 1-3',
  },
});

// Extract financial data (specific pages)
const financialPages = await extractPages(largeReport.buffer, [5, 6, 7, 8], {
  addWatermark: {
    text: 'FINANCIAL DATA - CONFIDENTIAL',
    opacity: 0.1,
    fontSize: 48,
    color: '#FF0000',
    rotation: 45,
  },
});

// Split with advanced options
const individualPages = await splitPdf(largeReport.buffer, {
  // Naming pattern for split files
  filenamePattern: 'report-page-{pageNumber:03d}.pdf',
  
  // Include metadata in each page
  preserveMetadata: true,
  
  // Add page-specific metadata
  addPageMetadata: true,
  
  // Optimize each page
  optimize: true,
  
  // Progress callback
  onProgress: (current, total) => {
    console.log(`📄 Splitting: ${current}/${total} pages processed`);
  },
});

console.log('\n✂️  Split Results:');
console.log(`  📄 Created ${individualPages.length} individual page files`);
individualPages.forEach((page, index) => {
  console.log(`    Page ${index + 1}: ${(page.length / 1024).toFixed(1)}KB`);
});

// Batch processing example
const batchResults = await Promise.all([
  // Process multiple reports in parallel
  generatePdf({ title: 'Sales Report', data: salesData, columns: salesColumns }),
  generatePdf({ title: 'Finance Report', data: financeData, columns: financeColumns }),
  generatePdf({ title: 'HR Report', data: hrData, columns: hrColumns }),
]);

console.log('\n📊 Batch Processing Results:');
batchResults.forEach((result, index) => {
  console.log(`  Report ${index + 1}: ${result.pageCount} pages, ${(result.sizeBytes / 1024).toFixed(1)}KB`);
});

// Merge all batch results
const consolidatedReport = await mergePdfs(
  batchResults.map(result => result.buffer),
  {
    addBookmarks: true,
    bookmarkTitles: ['Sales Report', 'Finance Report', 'HR Report'],
    addSectionDividers: true,
    metadata: {
      title: 'Consolidated Business Reports',
      author: 'Business Intelligence Team',
    },
  }
);

Hooks and Customization

Add custom processing logic with hooks:

import { generatePdf } from 'safeersoft-pdf-reporter';

const result = await generatePdf({
  title: 'Custom Processing Report',
  data: reportData,
  columns: reportColumns,
  hooks: {
    beforeChunkRender: async (params) => {
      console.log(`Processing chunk ${params.chunkInfo?.current} of ${params.chunkInfo?.total}`);
    },
    
    afterChunkRender: async (buffer, params) => {
      console.log(`Chunk ${params.chunkIndex + 1} generated: ${buffer.length} bytes`);
    },
    
    transformHtml: (html, stage) => {
      if (stage === 'pre') {
        // Add watermark or modify HTML before PDF generation
        return html.replace('<body>', '<body><div class="watermark">CONFIDENTIAL</div>');
      }
      return html;
    },
    
    transformColumnValue: (value, column, row) => {
      if (column.dataIndex === 'price' && typeof value === 'number') {
        return `$${value.toFixed(2)}`; // Format currency
      }
      return value;
    },
  },
});

Configuration Options

PdfGenerationOptions

interface PdfGenerationOptions {
  // Required
  title: string;
  data: any[];
  columns: ColumnDefinition[];
  
  // Optional
  locale?: string;                    // 'en', 'ar', etc. for RTL support
  userInfo?: UserInfo;               // Company info, logos
  infoSection?: InfoSection[];       // Header information sections
  filteredColumnKeys?: string[];     // Columns to exclude
  translationFn?: (key: string) => string;  // i18n function
  
  // Chunking
  chunking?: {
    enabled?: boolean;
    chunkSize?: number;              // Rows per chunk (default: 100)
    maxConcurrency?: number;         // Parallel chunks (default: 2)
  };
  
  // Templates
  template?: string | TemplateCompilerFunction;
  headerTemplate?: string | Function;
  footerTemplate?: string | Function;
  
  // S3 Upload
  s3?: {
    bucket: string;
    region: string;
    accessKeyId?: string;           // Optional, uses AWS credential chain
    secretAccessKey?: string;
    folder?: string;
    acl?: string;                   // Default: 'public-read'
  };
  
  // Email
  email?: {
    to: string;
    from?: string;
    subject?: string;
    attachmentMode?: 'attachment' | 'link';  // Default: 'attachment'
    smtp?: {
      host: string;
      port: number;
      secure?: boolean;
      auth: { user: string; pass: string; };
    };
    transport?: any;                // Custom nodemailer transporter
  };
  
  // Advanced
  hooks?: HooksConfig;
  timeoutMs?: number;               // Overall timeout (default: 300000)
  logging?: LoggingAdapter;         // Custom logger
  puppeteer?: PuppeteerOptions;     // Puppeteer configuration
  pdf?: PdfOptions;                 // PDF format options
}

Framework Integration Examples

Express.js

import express from 'express';
import { generatePdf } from 'safeersoft-pdf-reporter';

const app = express();

app.post('/reports/pdf', async (req, res) => {
  try {
    const { title, data, columns } = req.body;
    
    const result = await generatePdf({
      title,
      data,
      columns,
      userInfo: {
        companyName: 'My Company',
        companyLogo: 'https://example.com/logo.png',
      },
    });

    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: error.message });
  }
});

NestJS

import { Injectable } from '@nestjs/common';
import { generatePdf, PdfGenerationOptions } from 'safeersoft-pdf-reporter';

@Injectable()
export class ReportsService {
  async generateReport(options: PdfGenerationOptions) {
    return await generatePdf({
      ...options,
      logging: {
        debug: (msg, ...args) => console.debug(`[PDF] ${msg}`, ...args),
        info: (msg, ...args) => console.log(`[PDF] ${msg}`, ...args),
        warn: (msg, ...args) => console.warn(`[PDF] ${msg}`, ...args),
        error: (msg, ...args) => console.error(`[PDF] ${msg}`, ...args),
      },
    });
  }
}

Fastify

import Fastify from 'fastify';
import { generatePdf } from 'safeersoft-pdf-reporter';

const fastify = Fastify();

fastify.post('/pdf-report', async (request, reply) => {
  const { title, data, columns } = request.body;
  
  const result = await generatePdf({ title, data, columns });
  
  reply
    .type('application/pdf')
    .header('Content-Disposition', `attachment; filename="${result.fileName}"`)
    .send(result.buffer);
});

Performance Guidelines

Memory Management

  • Large datasets: Use chunking with chunkSize: 200 and maxConcurrency: 2-4
  • Memory constraints: Reduce concurrency and chunk size
  • Sequential processing: Set maxConcurrency: 1 for memory-limited environments

Optimization Tips

// For large datasets
const options = {
  chunking: {
    chunkSize: 200,
    maxConcurrency: 3,
  },
  pdf: {
    margin: { top: '10mm', bottom: '10mm', left: '5mm', right: '5mm' },
  },
  puppeteer: {
    args: ['--max-old-space-size=4096'], // Increase heap size
  },
};

// For speed over memory efficiency
const options = {
  chunking: {
    chunkSize: 500,
    maxConcurrency: 5,
  },
};

Error Handling

import { 
  generatePdf, 
  PdfGenerationError, 
  TemplateError, 
  S3UploadError,
  EmailError,
  isPdfReporterError 
} from 'safeersoft-pdf-reporter';

try {
  const result = await generatePdf(options);
} catch (error) {
  if (isPdfReporterError(error)) {
    console.error(`PDF Reporter Error [${error.code}]:`, error.message);
    
    if (error instanceof S3UploadError) {
      console.error(`S3 Upload failed for bucket: ${error.bucket}`);
    } else if (error instanceof EmailError) {
      console.error(`Email failed for recipient: ${error.recipient}`);
    }
  } else {
    console.error('Unexpected error:', error);
  }
}

Performance Optimization

Optimize PDF generation for large datasets and high-throughput scenarios:

import { 
  generatePdf, 
  analyzePdfPerformance, 
  optimizePdfGeneration,
  createPerformanceProfile 
} from 'safeersoft-pdf-reporter';

// Create performance profile for optimization
const performanceProfile = createPerformanceProfile({
  maxMemoryUsage: '2GB',
  cpuCores: 4,
  concurrentProcesses: 2,
  expectedPageCount: 50,
  expectedDataSize: 10000,
  complexityLevel: 'high',
});

// Optimized configuration for large datasets
const optimizedConfig = {
  title: 'Large Financial Report',
  data: financialData, // 100,000+ records
  columns: reportColumns,
  
  // Performance optimizations
  chunking: {
    enabled: true,
    chunkSize: 2000,
    concurrency: 2,
    chunkTimeout: 30000,
    memoryThreshold: 0.8,
  },
  
  // PDF optimizations
  pdfOptions: {
    compress: true,
    optimization: {
      images: { quality: 85, maxWidth: 1200 },
      fonts: { subset: true, embed: false },
      content: { removeUnusedObjects: true, compressStreams: true },
    },
    preferCSSPageSize: true,
    generateTaggedPDF: false,
  },
  
  onProgress: (chunk, totalChunks, stats) => {
    console.log(`📊 Processing chunk ${chunk}/${totalChunks}`);
    console.log(`  💾 Memory usage: ${stats.memoryUsage.toFixed(1)}MB`);
  },
};

const result = await generatePdf(optimizedConfig);
console.log(`📈 Generated ${result.pageCount} pages in ${result.processingTime}ms`);

Security & Access Control

Implement security features for sensitive documents:

import { generatePdf, encryptPdf, addWatermark } from 'safeersoft-pdf-reporter';

const secureConfig = {
  title: 'Confidential Financial Report',
  data: sensitiveData,
  columns: reportColumns,
  
  security: {
    encryption: {
      userPassword: 'view-password-123',
      ownerPassword: 'admin-password-456',
      permissions: {
        printing: 'lowResolution',
        modifyContents: false,
        copy: false,
      },
    },
    watermark: {
      text: 'CONFIDENTIAL - INTERNAL USE ONLY',
      opacity: 0.15,
      fontSize: 72,
      color: '#FF0000',
      rotation: 45,
    },
  },
};

const secureResult = await generatePdf(secureConfig);
console.log('🔒 Secure PDF generated with encryption and watermark');

Internationalization

Support multiple languages and regions:

import { generatePdf, setLocale, formatCurrency, formatDate } from 'safeersoft-pdf-reporter';

// Configure Spanish locale
setLocale('es-ES');

const internationalConfig = {
  title: 'Informe Financiero Q1 2024',
  data: financialData,
  columns: [
    { 
      title: 'Fecha', 
      dataIndex: 'date',
      format: (value) => formatDate(value, 'es-ES')
    },
    { 
      title: 'Cantidad', 
      dataIndex: 'amount',
      format: (value) => formatCurrency(value, 'EUR', 'es-ES')
    },
  ],
  
  localization: {
    locale: 'es-ES',
    currency: 'EUR',
    timezone: 'Europe/Madrid',
    numberFormat: { decimal: ',', thousands: '.' },
  },
};

const localizedResult = await generatePdf(internationalConfig);
console.log('🌍 Localized PDF generated for Spanish market');

Troubleshooting

Common Issues

Browser not found

# Install Chromium
sudo apt-get install chromium-browser

# Or set custom path
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

Memory issues with large datasets

const options = {
  chunking: { chunkSize: 50, maxConcurrency: 1 },
  puppeteer: { args: ['--max-old-space-size=2048'] },
};

S3 permissions

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::your-bucket/*"
    }
  ]
}

Debug Logging

Enable detailed logging for troubleshooting:

import { generatePdf, consoleLogger, createCustomLogger } from 'safeersoft-pdf-reporter';

// Built-in console logger
const result = await generatePdf({
  title: 'Debug Report',
  data: debugData,
  columns: debugColumns,
  logging: consoleLogger,
});

// Custom logger with file output
const customLogger = createCustomLogger({
  level: 'debug',
  format: 'json',
  file: './logs/pdf-generation.log',
  console: true,
  timestamp: true,
});

const resultWithCustomLog = await generatePdf({
  title: 'Production Report',
  data: productionData,
  columns: productionColumns,
  logging: customLogger,
});

Performance Monitoring

Monitor and analyze PDF generation performance:

import { 
  generatePdf, 
  createPerformanceMonitor, 
  analyzePerformanceMetrics 
} from 'safeersoft-pdf-reporter';

// Create performance monitor
const monitor = createPerformanceMonitor({
  trackMemory: true,
  trackCPU: true,
  trackNetworkLatency: true,
  sampleInterval: 1000, // 1 second
});

const result = await generatePdf({
  title: 'Performance Monitored Report',
  data: largeDataset,
  columns: reportColumns,
  
  // Performance monitoring hooks
  hooks: {
    beforeGeneration: async () => {
      monitor.start();
      console.log('📊 Starting performance monitoring');
    },
    
    afterGeneration: async (result) => {
      const metrics = monitor.stop();
      console.log('📈 Performance Metrics:');
      console.log(`  ⏱️  Total time: ${metrics.totalTime}ms`);
      console.log(`  💾 Peak memory: ${metrics.peakMemory}MB`);
      console.log(`  🔄 CPU usage: ${metrics.avgCpuUsage}%`);
      console.log(`  📊 Throughput: ${metrics.recordsPerSecond} records/sec`);
      
      // Analyze and suggest optimizations
      const suggestions = analyzePerformanceMetrics(metrics);
      console.log('💡 Optimization suggestions:', suggestions);
    },
    
    onMemoryWarning: async (usage) => {
      console.warn(`⚠️  High memory usage detected: ${usage}MB`);
    },
  },
});

Advanced Error Recovery

Implement robust error handling and recovery mechanisms:

import { 
  generatePdf, 
  PdfGenerationError,
  RetryableError,
  createRetryPolicy,
  createFallbackStrategy 
} from 'safeersoft-pdf-reporter';

// Create retry policy for transient errors
const retryPolicy = createRetryPolicy({
  maxRetries: 3,
  baseDelay: 1000,
  maxDelay: 10000,
  backoffFactor: 2,
  retryCondition: (error) => error instanceof RetryableError,
});

// Create fallback strategy for critical failures
const fallbackStrategy = createFallbackStrategy({
  // Fallback to simplified template
  template: 'simple-table',
  
  // Reduce data complexity
  dataTransform: (data) => data.slice(0, 1000),
  
  // Use minimal options
  options: {
    chunking: { chunkSize: 50, maxConcurrency: 1 },
    pdf: { quality: 'low' },
  },
});

async function robustPdfGeneration(config) {
  try {
    return await generatePdf(config);
  } catch (error) {
    console.error('🚨 Primary generation failed:', error.message);
    
    if (retryPolicy.shouldRetry(error)) {
      console.log('🔄 Retrying with exponential backoff...');
      return await retryPolicy.execute(() => generatePdf(config));
    }
    
    if (fallbackStrategy.canHandle(error)) {
      console.log('🛡️  Attempting fallback strategy...');
      const fallbackConfig = fallbackStrategy.transform(config);
      return await generatePdf(fallbackConfig);
    }
    
    // Final fallback: generate error report
    console.log('📄 Generating error report...');
    return await generatePdf({
      title: 'PDF Generation Error Report',
      data: [{ error: error.message, timestamp: new Date() }],
      columns: [
        { title: 'Error', dataIndex: 'error' },
        { title: 'Timestamp', dataIndex: 'timestamp' },
      ],
      template: 'simple-table',
    });
  }
}

// Usage
const result = await robustPdfGeneration({
  title: 'Critical Business Report',
  data: criticalData,
  columns: criticalColumns,
});

Production Deployment

Docker Configuration

# Multi-stage build for optimized production image
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# Production stage
FROM node:18-alpine AS production

# Install Chromium and required dependencies
RUN apk add --no-cache \
    chromium \
    nss \
    freetype \
    harfbuzz \
    ca-certificates \
    ttf-freefont \
    && rm -rf /var/cache/apk/*

# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S pdfgen -u 1001

WORKDIR /app

# Copy built application
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json

# Set Puppeteer to use installed Chromium
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

# Create directories for temp files
RUN mkdir -p /app/temp /app/logs && \
    chown -R pdfgen:nodejs /app

USER pdfgen

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
  CMD node -e "console.log('Health check passed')" || exit 1

CMD ["node", "dist/server.js"]

Kubernetes Deployment

# kubernetes-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pdf-reporter-service
  labels:
    app: pdf-reporter
spec:
  replicas: 3
  selector:
    matchLabels:
      app: pdf-reporter
  template:
    metadata:
      labels:
        app: pdf-reporter
    spec:
      containers:
      - name: pdf-reporter
        image: your-registry/pdf-reporter:latest
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        - name: PDF_TEMP_DIR
          value: "/tmp/pdf-generation"
        - name: LOG_LEVEL
          value: "info"
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "2Gi"
            cpu: "1000m"
        livenessProbe:
          httpGet:
            path: /api/health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /api/health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
        volumeMounts:
        - name: temp-storage
          mountPath: /tmp/pdf-generation
      volumes:
      - name: temp-storage
        emptyDir:
          sizeLimit: 1Gi
---
apiVersion: v1
kind: Service
metadata:
  name: pdf-reporter-service
spec:
  selector:
    app: pdf-reporter
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3000
  type: LoadBalancer

Environment Configuration

# .env.production
NODE_ENV=production
PORT=3000

# PDF Generation Settings
PDF_TEMP_DIR=/tmp/pdf-generation
PDF_MAX_CONCURRENT_PROCESSES=4
PDF_MEMORY_LIMIT=2048
PDF_TIMEOUT=300000

# Browser Settings
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
PUPPETEER_ARGS=--no-sandbox,--disable-setuid-sandbox,--disable-dev-shm-usage

# AWS S3 Configuration
AWS_REGION=us-east-1
AWS_S3_BUCKET=company-reports
AWS_S3_PREFIX=pdf-reports

# Email Configuration
SMTP_HOST=smtp.company.com
SMTP_PORT=587
SMTP_SECURE=false
[email protected]
SMTP_PASS=your-smtp-password

# Monitoring and Logging
LOG_LEVEL=info
LOG_FORMAT=json
METRICS_ENABLED=true
HEALTH_CHECK_ENABLED=true

# Security
RATE_LIMIT_REQUESTS=100
RATE_LIMIT_WINDOW=900000
CORS_ORIGINS=https://app.company.com,https://dashboard.company.com

Advanced Integration Patterns

Message Queue Integration

// Bull Queue integration for background processing
import Queue from 'bull';
import { generatePdf } from 'safeersoft-pdf-reporter';

const pdfQueue = new Queue('PDF generation', {
  redis: { port: 6379, host: 'localhost' },
  defaultJobOptions: {
    removeOnComplete: 10,
    removeOnFail: 5,
    attempts: 3,
    backoff: 'exponential',
  },
});

pdfQueue.process('generate', async (job) => {
  const { title, data, columns, options } = job.data;
  
  // Update job progress
  job.progress(10);
  
  try {
    const result = await generatePdf({
      title,
      data,
      columns,
      ...options,
      hooks: {
        beforeChunkRender: async (params) => {
          const progress = 10 + (params.chunkInfo.current / params.chunkInfo.total) * 80;
          job.progress(Math.round(progress));
        },
      },
    });
    
    job.progress(100);
    return result;
  } catch (error) {
    throw new Error(`PDF generation failed: ${error.message}`);
  }
});

// Add job to queue
export async function enqueuePdfGeneration(config) {
  const job = await pdfQueue.add('generate', config, {
    priority: config.priority || 0,
    delay: config.delay || 0,
  });
  
  return {
    jobId: job.id,
    status: 'queued',
    estimatedCompletion: new Date(Date.now() + 30000),
  };
}

Microservices Architecture

// PDF Service with gRPC
import { Server, ServerCredentials } from '@grpc/grpc-js';
import { generatePdf } from 'safeersoft-pdf-reporter';

class PdfService {
  async GeneratePdf(call, callback) {
    try {
      const { title, data, columns, options } = call.request;
      
      const result = await generatePdf({
        title,
        data: JSON.parse(data),
        columns: JSON.parse(columns),
        ...JSON.parse(options || '{}'),
      });
      
      callback(null, {
        success: true,
        pdfBuffer: result.buffer,
        pageCount: result.pageCount,
        sizeBytes: result.sizeBytes,
        processingTime: result.processingTime,
      });
    } catch (error) {
      callback({
        code: grpc.status.INTERNAL,
        message: error.message,
      });
    }
  }
  
  async EstimateGeneration(call, callback) {
    try {
      const estimate = await estimatePdfGeneration(call.request);
      callback(null, estimate);
    } catch (error) {
      callback({
        code: grpc.status.INTERNAL,
        message: error.message,
      });
    }
  }
}

const server = new Server();
server.addService(PdfServiceDefinition, new PdfService());
server.bindAsync('0.0.0.0:50051', ServerCredentials.createInsecure(), () => {
  console.log('PDF gRPC service running on port 50051');
  server.start();
});

Event-Driven Architecture

// Event-driven PDF generation with EventEmitter
import { EventEmitter } from 'events';
import { generatePdf } from 'safeersoft-pdf-reporter';

class PdfGenerationOrchestrator extends EventEmitter {
  constructor() {
    super();
    this.setupEventHandlers();
  }
  
  setupEventHandlers() {
    this.on('pdf:requested', this.handlePdfRequest.bind(this));
    this.on('pdf:completed', this.handlePdfCompleted.bind(this));
    this.on('pdf:failed', this.handlePdfFailed.bind(this));
  }
  
  async handlePdfRequest({ requestId, config }) {
    try {
      this.emit('pdf:started', { requestId });
      
      const result = await generatePdf({
        ...config,
        hooks: {
          beforeChunkRender: (params) => {
            this.emit('pdf:progress', {
              requestId,
              progress: (params.chunkInfo.current / params.chunkInfo.total) * 100,
            });
          },
        },
      });
      
      this.emit('pdf:completed', { requestId, result });
    } catch (error) {
      this.emit('pdf:failed', { requestId, error });
    }
  }
  
  async handlePdfCompleted({ requestId, result }) {
    // Store result, send notifications, etc.
    console.log(`✅ PDF ${requestId} completed: ${result.pageCount} pages`);
    
    // Trigger downstream events
    this.emit('notification:send', {
      type: 'pdf_ready',
      requestId,
      metadata: result,
    });
  }
  
  async handlePdfFailed({ requestId, error }) {
    console.error(`❌ PDF ${requestId} failed:`, error.message);
    
    // Trigger error handling
    this.emit('error:handle', {
      type: 'pdf_generation_failure',
      requestId,
      error,
    });
  }
}

// Usage
const orchestrator = new PdfGenerationOrchestrator();

orchestrator.on('pdf:progress', ({ requestId, progress }) => {
  console.log(`📊 PDF ${requestId}: ${progress.toFixed(1)}% complete`);
});

orchestrator.emit('pdf:requested', {
  requestId: 'req-12345',
  config: {
    title: 'Annual Report',
    data: annualData,
    columns: reportColumns,
  },
});

Testing Strategies

Unit Testing

// test/pdf-generation.test.ts
import { generatePdf, estimatePdfGeneration } from 'safeersoft-pdf-reporter';
import { mockBrowser } from './mocks/browser-mock';

describe('PDF Generation', () => {
  beforeEach(() => {
    mockBrowser.reset();
  });
  
  test('should generate PDF with basic configuration', async () => {
    const result = await generatePdf({
      title: 'Test Report',
      data: [{ name: 'John', age: 30 }],
      columns: [
        { title: 'Name', dataIndex: 'name' },
        { title: 'Age', dataIndex: 'age' },
      ],
    });
    
    expect(result.buffer).toBeInstanceOf(Buffer);
    expect(result.pageCount).toBeGreaterThan(0);
    expect(result.sizeBytes).toBeGreaterThan(0);
  });
  
  test('should handle large datasets with chunking', async () => {
    const largeData = Array.from({ length: 1000 }, (_, i) => ({
      id: i,
      name: `Person ${i}`,
      value: Math.random() * 1000,
    }));
    
    const result = await generatePdf({
      title: 'Large Dataset Test',
      data: largeData,
      columns: [
        { title: 'ID', dataIndex: 'id' },
        { title: 'Name', dataIndex: 'name' },
        { title: 'Value', dataIndex: 'value' },
      ],
      chunking: {
        enabled: true,
        chunkSize: 100,
        maxConcurrency: 2,
      },
    });
    
    expect(result.pageCount).toBeGreaterThan(5);
    expect(result.processingTime).toBeGreaterThan(0);
  });
  
  test('should estimate generation performance accurately', async () => {
    const testData = Array.from({ length: 500 }, (_, i) => ({ id: i }));
    
    const estimate = await estimatePdfGeneration({
      data: testData,
      columns: [{ title: 'ID', dataIndex: 'id' }],
    });
    
    expect(estimate.estimatedTimeMs).toBeGreaterThan(0);
    expect(estimate.estimatedMemoryMB).toBeGreaterThan(0);
    expect(estimate.estimatedPages).toBeGreaterThan(0);
  });
});

Integration Testing

// test/integration/pdf-workflows.test.ts
import { generatePdf, mergePdfs, splitPdf } from 'safeersoft-pdf-reporter';
import fs from 'fs/promises';
import path from 'path';

describe('PDF Workflow Integration', () => {
  const outputDir = path.join(__dirname, 'output');
  
  beforeAll(async () => {
    await fs.mkdir(outputDir, { recursive: true });
  });
  
  afterAll(async () => {
    await fs.rmdir(outputDir, { recursive: true });
  });
  
  test('complete workflow: generate, split, merge', async () => {
    // Generate initial PDF
    const originalPdf = await generatePdf({
      title: 'Workflow Test Report',
      data: Array.from({ length: 100 }, (_, i) => ({ id: i, name: `Item ${i}` })),
      columns: [
        { title: 'ID', dataIndex: 'id' },
        { title: 'Name', dataIndex: 'name' },
      ],
    });
    
    expect(originalPdf.pageCount).toBeGreaterThan(1);
    
    // Split PDF into individual pages
    const pages = await splitPdf(originalPdf.buffer);
    expect(pages.length).toBe(originalPdf.pageCount);
    
    // Merge selected pages back together
    const selectedPages = pages.slice(0, 3);
    const mergedPdf = await mergePdfs(selectedPages);
    
    expect(mergedPdf.length).toBeGreaterThan(0);
    
    // Save results for manual inspection
    await fs.writeFile(
      path.join(outputDir, 'original.pdf'),
      originalPdf.buffer
    );
    await fs.writeFile(
      path.join(outputDir, 'merged.pdf'),
      mergedPdf
    );
  });
});

Load Testing

// test/load/performance.test.ts
import { generatePdf } from 'safeersoft-pdf-reporter';
import { performance } from 'perf_hooks';

describe('Performance Load Tests', () => {
  test('should handle concurrent PDF generation', async () => {
    const concurrentRequests = 10;
    const dataSize = 500;
    
    const testData = Array.from({ length: dataSize }, (_, i) => ({
      id: i,
      name: `Person ${i}`,
      email: `person${i}@example.com`,
      value: Math.random() * 1000,
    }));
    
    const columns = [
      { title: 'ID', dataIndex: 'id' },
      { title: 'Name', dataIndex: 'name' },
      { title: 'Email', dataIndex: 'email' },
      { title: 'Value', dataIndex: 'value', type: 'currency' },
    ];
    
    const startTime = performance.now();
    
    const promises = Array.from({ length: concurrentRequests }, (_, i) =>
      generatePdf({
        title: `Concurrent Report ${i}`,
        data: testData,
        columns,
        chunking: {
          enabled: true,
          chunkSize: 100,
          maxConcurrency: 2,
        },
      })
    );
    
    const results = await Promise.all(promises);
    const endTime = performance.now();
    
    const totalTime = endTime - startTime;
    const avgTime = totalTime / concurrentRequests;
    
    console.log(`📊 Load Test Results:`);
    console.log(`  Total time: ${totalTime.toFixed(2)}ms`);
    console.log(`  Average time per PDF: ${avgTime.toFixed(2)}ms`);
    console.log(`  Total pages generated: ${results.reduce((sum, r) => sum + r.pageCount, 0)}`);
    console.log(`  Total size: ${(results.reduce((sum, r) => sum + r.sizeBytes, 0) / 1024 / 1024).toFixed(2)}MB`);
    
    expect(results).toHaveLength(concurrentRequests);
    expect(avgTime).toBeLessThan(30000); // Should complete within 30 seconds
  });
});

Real-World Usage Scenarios

Financial Reports

import { generatePdf, registerTemplate } from 'safeersoft-pdf-reporter';

// Register financial template with charts and KPIs
registerTemplate('financial-dashboard', (params) => {
  const { data, title, userInfo } = params;
  
  // Calculate financial metrics
  const revenue = data.reduce((sum, item) => sum + (item.revenue || 0), 0);
  const profit = data.reduce((sum, item) => sum + (item.profit || 0), 0);
  const growthRate = ((revenue - params.previousRevenue) / params.previousRevenue) * 100;
  
  return {
    html: `
      <!DOCTYPE html>
      <html>
        <head>
          <style>
            .financial-kpi { 
              display: grid; 
              grid-template-columns: repeat(4, 1fr); 
              gap: 20px; 
              margin: 30px 0; 
            }
            .kpi-card { 
              background: linear-gradient(135deg, #667eea, #764ba2); 
              color: white; 
              padding: 25px; 
              border-radius: 15px; 
              text-align: center; 
            }
            .kpi-value { font-size: 2.5em; font-weight: 700; }
            .kpi-label { font-size: 0.9em; opacity: 0.9; }
            .chart-container { 
              background: white; 
              padding: 30px; 
              border-radius: 15px; 
              margin: 20px 0; 
              box-shadow: 0 10px 30px rgba(0,0,0,0.1); 
            }
          </style>
        </head>
        <body>
          <div class="financial-header">
            <h1>${title}</h1>
            <p>Period: ${params.period} | Generated: ${new Date().toLocaleDateString()}</p>
          </div>
          
          <div class="financial-kpi">
            <div class="kpi-card">
              <div class="kpi-value">$${(revenue / 1000000).toFixed(1)}M</div>
              <div class="kpi-label">Total Revenue</div>
            </div>
            <div class="kpi-card">
              <div class="kpi-value">$${(profit / 1000000).toFixed(1)}M</div>
              <div class="kpi-label">Net Profit</div>
            </div>
            <div class="kpi-card">
              <div class="kpi-value">${growthRate.toFixed(1)}%</div>
              <div class="kpi-label">Growth Rate</div>
            </div>
            <div class="kpi-card">
              <div class="kpi-value">${((profit / revenue) * 100).toFixed(1)}%</div>
              <div class="kpi-label">Profit Margin</div>
            </div>
          </div>
          
          <div class="chart-container">
            <h3>Revenue Breakdown by Department</h3>
            <!-- Revenue chart would go here -->
          </div>
          
          <table>
            <thead>
              <tr>
                <th>Department</th>
                <th>Revenue</th>
                <th>Profit</th>
                <th>Margin</th>
                <th>Growth</th>
              </tr>
            </thead>
            <tbody>
              ${data.map(item => `
                <tr>
                  <td>${item.department}</td>
                  <td>$${item.revenue?.toLocaleString()}</td>
                  <td>$${item.profit?.toLocaleString()}</td>
                  <td>${((item.profit / item.revenue) * 100).toFixed(1)}%</td>
                  <td style="color: ${item.growth > 0 ? '#28a745' : '#dc3545'}">
                    ${item.growth > 0 ? '+' : ''}${item.growth?.toFixed(1)}%
                  </td>
                </tr>
              `).join('')}
            </tbody>
          </table>
        </body>
      </html>
    `,
  };
});

// Generate financial report
const financialReport = await generatePdf({
  title: 'Q1 2024 Financial Dashboard',
  data: financialData,
  columns: financialColumns,
  template: 'financial-dashboard',
  userInfo: {
    companyName: 'TechCorp Financial',
    companyLogo: 'https://company.com/logo.png',
    name: 'CFO Office',
  },
  // Auto-save to S3 and email to stakeholders
  s3: {
    bucket: 'financial-reports',
    folder: 'quarterly/2024/Q1',
    acl: 'private',
  },
  email: {
    to: ['[email protected]', '[email protected]'],
    subject: 'Q1 2024 Financial Dashboard',
    attachmentMode: 'link', // Send S3 link instead of attachment
  },
});

Employee Performance Reports

// HR Performance template with employee photos and ratings
const hrPerformanceConfig = {
  title: 'Annual Performance Review - 2024',
  data: employeeData.map(emp => ({
    ...emp,
    performanceScore: calculatePerformanceScore(emp),
    ratingColor: getRatingColor(emp.rating),
    photoUrl: `https://hr.company.com/photos/${emp.employeeId}.jpg`,
  })),
  columns: [
    { title: 'Employee', dataIndex: 'name', flex: 3 },
    { title: 'Department', dataIndex: 'department', flex: 2 },
    { title: 'Performance Score', dataIndex: 'performanceScore', flex: 2, type: 'percentage' },
    { title: 'Rating', dataIndex: 'rating', flex: 2 },
    { title: 'Goals Met', dataIndex: 'goalsMet', flex: 2, type: 'boolean' },
  ],
  template: 'employee-performance',
  
  // Advanced PDF security for confidential HR data
  security: {
    encryption: {
      userPassword: generateSecurePassword(),
      ownerPassword: process.env.HR_MASTER_PASSWORD,
      permissions: {
        printing: 'lowResolution',
        modifyContents: false,
        copy: false,
        modifyAnnotations: false,
      },
    },
    watermark: {
      text: 'CONFIDENTIAL - HR USE ONLY',
      opacity: 0.1,
      fontSize: 72,
      color: '#FF0000',
    },
  },
  
  // Advanced chunking for large employee datasets
  chunking: {
    enabled: true,
    chunkSize: 50, // 50 employees per chunk
    maxConcurrency: 2,
    chunkBy: 'department', // Group by department
  },
};

const hrReport = await generatePdf(hrPerformanceConfig);

Multi-Language Reports

// Internationalized report with RTL support
const multiLanguageConfig = {
  title: 'تقرير المبيعات الشهري', // Arabic title
  data: salesData,
  columns: [
    { title: 'اسم العميل', dataIndex: 'customerName', flex: 3 }, // Arabic headers
    { title: 'المبلغ', dataIndex: 'amount', flex: 2, type: 'currency' },
    { title: 'التاريخ', dataIndex: 'date', flex: 2, type: 'date' },
    { title: 'المنطقة', dataIndex: 'region', flex: 2