UNPKG

safeer-pdf-generator

Version:

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

889 lines (789 loc) • 31.5 kB
#!/usr/bin/env node /** * Comprehensive PDF generation example using @safeersoft/@safeersoft/pdf-reporter * * This script demonstrates: * - Basic PDF generation * - Custom templates with advanced styling * - Large dataset handling with chunking * - PDF merging and manipulation * - S3 upload integration * - Email delivery * - Performance monitoring * - Error handling and recovery * - Memory optimization */ const { writeFileSync, mkdirSync, existsSync } = require('fs'); const { join } = require('path'); const { generatePdf, generateOptimizedPdf, mergePdfs, splitPdf, extractPages, getPdfInfo, analyzePdfs, validatePdfs, registerTemplate, consoleLogger, estimatePdfGeneration, } = require('@safeersoft/@safeersoft/pdf-reporter'); // Sample data - in real usage, this would come from your database/API const sampleData = [ { id: 1, name: 'Alice Johnson', department: 'Engineering', salary: 95000, startDate: '2022-01-15', active: true, skills: ['JavaScript', 'React', 'Node.js'], performance: 'Excellent', location: 'New York', }, { id: 2, name: 'Bob Smith', department: 'Marketing', salary: 78000, startDate: '2021-06-10', active: true, skills: ['SEO', 'Content Marketing', 'Analytics'], performance: 'Good', location: 'Los Angeles', }, { id: 3, name: 'Carol Davis', department: 'Engineering', salary: 102000, startDate: '2020-03-22', active: true, skills: ['Python', 'Machine Learning', 'AWS'], performance: 'Outstanding', location: 'Seattle', }, { id: 4, name: 'David Wilson', department: 'Sales', salary: 85000, startDate: '2023-02-01', active: false, skills: ['CRM', 'Negotiation', 'Lead Generation'], performance: 'Average', location: 'Chicago', }, { id: 5, name: 'Eva Brown', department: 'HR', salary: 72000, startDate: '2021-11-30', active: true, skills: ['Recruitment', 'Employee Relations', 'Training'], performance: 'Good', location: 'Austin', }, { id: 6, name: 'Frank Miller', department: 'Engineering', salary: 98000, startDate: '2019-08-14', active: true, skills: ['Java', 'Spring Boot', 'Microservices'], performance: 'Excellent', location: 'San Francisco', }, { id: 7, name: 'Grace Chen', department: 'Marketing', salary: 81000, startDate: '2022-07-20', active: true, skills: ['Digital Marketing', 'Social Media', 'Brand Management'], performance: 'Good', location: 'Miami', }, { id: 8, name: 'Henry Taylor', department: 'Sales', salary: 89000, startDate: '2020-12-05', active: true, skills: ['B2B Sales', 'Account Management', 'Presentation'], performance: 'Excellent', location: 'Denver', }, ]; const columns = [ { key: 'id', title: 'ID', dataIndex: 'id', flex: 1 }, { key: 'name', title: 'Full Name', dataIndex: 'name', flex: 3 }, { key: 'department', title: 'Department', dataIndex: 'department', flex: 2 }, { key: 'salary', title: 'Annual Salary', dataIndex: 'salary', flex: 2, type: 'currency' }, { key: 'startDate', title: 'Start Date', dataIndex: 'startDate', flex: 2 }, { key: 'active', title: 'Active', dataIndex: 'active', type: 'boolean', flex: 1 }, { key: 'performance', title: 'Performance', dataIndex: 'performance', flex: 2 }, { key: 'location', title: 'Location', dataIndex: 'location', flex: 2 }, ]; // Register an advanced custom template with charts and enhanced styling registerTemplate('advanced-employee-report', params => { const { title, data, columns, userInfo, translationFn: t = k => k } = params; const formatCurrency = value => { return typeof value === 'number' ? `$${value.toLocaleString()}` : value; }; const formatBoolean = value => { return value ? 'āœ“ Active' : 'āœ— Inactive'; }; const getPerformanceColor = performance => { const colors = { Outstanding: '#28a745', Excellent: '#17a2b8', Good: '#ffc107', Average: '#fd7e14', Poor: '#dc3545', }; return colors[performance] || '#6c757d'; }; const getDepartmentStats = () => { const stats = {}; data.forEach(emp => { if (!stats[emp.department]) { stats[emp.department] = { count: 0, totalSalary: 0 }; } stats[emp.department].count++; stats[emp.department].totalSalary += emp.salary; }); return stats; }; const departmentStats = getDepartmentStats(); const renderRow = (item, index) => { const bgColor = index % 2 === 0 ? '#f8f9fa' : '#ffffff'; const statusColor = item.active ? '#28a745' : '#dc3545'; const perfColor = getPerformanceColor(item.performance); return ` <tr style="background-color: ${bgColor}; transition: background-color 0.2s;"> <td style="padding: 10px; border: 1px solid #e0e0e0; text-align: center; font-weight: bold; color: #495057;">${item.id}</td> <td style="padding: 10px; border: 1px solid #e0e0e0;"> <div style="font-weight: 600; color: #212529;">${item.name}</div> <div style="font-size: 11px; color: #6c757d; margin-top: 2px;">${item.location}</div> </td> <td style="padding: 10px; border: 1px solid #e0e0e0; text-align: center;"> <span style="background-color: #e9ecef; padding: 2px 8px; border-radius: 12px; font-size: 11px; font-weight: 500;">${item.department}</span> </td> <td style="padding: 10px; border: 1px solid #e0e0e0; text-align: right; font-weight: 600; color: #28a745;">${formatCurrency(item.salary)}</td> <td style="padding: 10px; border: 1px solid #e0e0e0; text-align: center; font-family: monospace; font-size: 12px;">${item.startDate}</td> <td style="padding: 10px; border: 1px solid #e0e0e0; text-align: center;"> <span style="color: ${statusColor}; font-weight: 600; font-size: 12px;">${formatBoolean(item.active)}</span> </td> <td style="padding: 10px; border: 1px solid #e0e0e0; text-align: center;"> <span style="background-color: ${perfColor}; color: white; padding: 2px 8px; border-radius: 8px; font-size: 11px; font-weight: 500;">${item.performance}</span> </td> </tr> `; }; const renderDepartmentChart = () => { const departments = Object.keys(departmentStats); const maxCount = Math.max(...Object.values(departmentStats).map(s => s.count)); return departments .map(dept => { const { count, totalSalary } = departmentStats[dept]; const percentage = (count / maxCount) * 100; const avgSalary = totalSalary / count; return ` <div style="margin-bottom: 15px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;"> <span style="font-weight: 600; color: #495057;">${dept}</span> <span style="font-size: 12px; color: #6c757d;">${count} employees</span> </div> <div style="background-color: #e9ecef; height: 20px; border-radius: 10px; overflow: hidden;"> <div style="background: linear-gradient(90deg, #007acc, #0056b3); height: 100%; width: ${percentage}%; border-radius: 10px; transition: width 0.3s ease;"></div> </div> <div style="font-size: 11px; color: #6c757d; margin-top: 3px;"> Avg Salary: ${formatCurrency(Math.round(avgSalary))} </div> </div> `; }) .join(''); }; const html = ` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>${title}</title> <style> @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; padding: 20px; color: #212529; line-height: 1.5; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); min-height: 100vh; } .container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.1); overflow: hidden; } .header { background: linear-gradient(135deg, #007acc 0%, #0056b3 100%); color: white; text-align: center; padding: 40px 20px; position: relative; } .header::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>'); opacity: 0.3; } .header h1 { margin: 0; font-size: 2.5em; font-weight: 700; text-shadow: 0 2px 4px rgba(0,0,0,0.3); position: relative; z-index: 1; } .header p { margin: 10px 0 0 0; font-size: 1.1em; opacity: 0.9; position: relative; z-index: 1; } .content { padding: 30px; } .company-info { background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); padding: 25px; margin-bottom: 30px; border-radius: 8px; border-left: 5px solid #007acc; box-shadow: 0 2px 10px rgba(0,0,0,0.05); } .company-info h3 { margin: 0 0 15px 0; color: #007acc; font-weight: 600; font-size: 1.3em; } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 30px; } .stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); border-top: 3px solid #007acc; text-align: center; } .stat-value { font-size: 2em; font-weight: 700; color: #007acc; margin-bottom: 5px; } .stat-label { color: #6c757d; font-size: 0.9em; font-weight: 500; } .department-analysis { background: #f8f9fa; padding: 25px; border-radius: 8px; margin-bottom: 30px; } .department-analysis h3 { margin: 0 0 20px 0; color: #495057; font-weight: 600; } table { width: 100%; border-collapse: collapse; margin: 20px 0; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 10px rgba(0,0,0,0.05); } th { background: linear-gradient(135deg, #007acc 0%, #0056b3 100%); color: white; padding: 15px 10px; text-align: left; font-weight: 600; font-size: 0.9em; text-transform: uppercase; letter-spacing: 0.5px; } th:first-child { text-align: center; } th:nth-child(4), th:nth-child(6), th:nth-child(7) { text-align: center; } tr:hover { background-color: #f1f3f5 !important; } .summary { background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); padding: 25px; border-radius: 8px; margin-top: 30px; border: 1px solid #dee2e6; } .summary h3 { margin: 0 0 20px 0; color: #495057; font-weight: 600; display: flex; align-items: center; } .summary h3::before { content: 'šŸ“Š'; margin-right: 10px; font-size: 1.2em; } .summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; } .summary-item { background: white; padding: 15px; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .summary-item strong { color: #007acc; font-weight: 600; } @media print { body { background: white; } .container { box-shadow: none; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>${t(title)}</h1> <p>Generated on ${new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', })}</p> </div> <div class="content"> ${ userInfo?.companyName ? ` <div class="company-info"> <h3>šŸ¢ ${userInfo.companyName}</h3> ${userInfo.name ? `<p><strong>Prepared by:</strong> ${userInfo.name}</p>` : ''} ${userInfo.email ? `<p><strong>Contact:</strong> ${userInfo.email}</p>` : ''} </div> ` : '' } <div class="stats-grid"> <div class="stat-card"> <div class="stat-value">${data.length}</div> <div class="stat-label">Total Employees</div> </div> <div class="stat-card"> <div class="stat-value">${data.filter(emp => emp.active).length}</div> <div class="stat-label">Active Employees</div> </div> <div class="stat-card"> <div class="stat-value">${formatCurrency( Math.round(data.reduce((sum, emp) => sum + emp.salary, 0) / data.length) )}</div> <div class="stat-label">Average Salary</div> </div> <div class="stat-card"> <div class="stat-value">${Object.keys(departmentStats).length}</div> <div class="stat-label">Departments</div> </div> </div> <div class="department-analysis"> <h3>šŸ“ˆ Department Distribution</h3> ${renderDepartmentChart()} </div> <table> <thead> <tr> <th>ID</th> <th>Employee Details</th> <th>Department</th> <th>Annual Salary</th> <th>Start Date</th> <th>Status</th> <th>Performance</th> </tr> </thead> <tbody> ${data.map(renderRow).join('')} </tbody> </table> <div class="summary"> <h3>Executive Summary</h3> <div class="summary-grid"> <div class="summary-item"> <strong>Workforce Overview:</strong> ${data.length} total employees across ${Object.keys(departmentStats).length} departments </div> <div class="summary-item"> <strong>Employment Status:</strong> ${data.filter(emp => emp.active).length} active (${((data.filter(emp => emp.active).length / data.length) * 100).toFixed(1)}%) </div> <div class="summary-item"> <strong>Top Department:</strong> ${Object.entries(departmentStats).sort((a, b) => b[1].count - a[1].count)[0][0]} (${Object.entries(departmentStats).sort((a, b) => b[1].count - a[1].count)[0][1].count} employees) </div> <div class="summary-item"> <strong>Salary Range:</strong> ${formatCurrency(Math.min(...data.map(emp => emp.salary)))} - ${formatCurrency(Math.max(...data.map(emp => emp.salary)))} </div> </div> </div> </div> </div> </body> </html> `; const header = ` <div style="background: linear-gradient(90deg, #007acc, #0056b3); color: white; text-align: center; padding: 10px; font-size: 12px; font-weight: 600;"> <span>${title}</span> - Page <span class="pageNumber"></span> of <span class="totalPages"></span> </div> `; const footer = ` <div style="background: #f8f9fa; text-align: center; padding: 8px; font-size: 10px; color: #6c757d; border-top: 1px solid #dee2e6;"> Generated by Safeersoft PDF Reporter | ${new Date().toLocaleDateString()} | Confidential Document </div> `; return { html, header, footer }; }); async function main() { console.log('šŸš€ Safeersoft PDF Reporter - Comprehensive Examples\n'); // Create output directory const outputDir = join(__dirname, 'output'); if (!existsSync(outputDir)) { mkdirSync(outputDir, { recursive: true }); } try { // Example 1: Basic PDF generation with validation console.log('šŸ“„ Example 1: Basic PDF Generation...'); const basicResult = await generatePdf({ title: 'Employee Report - Basic', data: sampleData, columns, userInfo: { companyName: 'Acme Corporation', name: 'HR Department', email: 'hr@acme.com', }, logging: consoleLogger, }); const basicPath = join(outputDir, 'employee-report-basic.pdf'); writeFileSync(basicPath, basicResult.buffer); // Validate the generated PDF const validation = await validatePdfs([basicResult.buffer]); console.log(`āœ… Basic report saved: ${basicPath}`); console.log( ` šŸ“Š Pages: ${basicResult.pageCount}, Size: ${(basicResult.sizeBytes / 1024).toFixed(1)}KB` ); console.log(` āœ“ PDF Validation: ${validation.valid ? 'PASSED' : 'FAILED'}\n`); // Example 2: Advanced custom template with styling console.log('šŸŽØ Example 2: Advanced Custom Template...'); const advancedResult = await generatePdf({ title: 'Employee Report - Advanced Design', data: sampleData, columns, template: 'advanced-employee-report', userInfo: { companyName: 'Acme Corporation', name: 'HR Analytics Team', email: 'analytics@acme.com', }, infoSection: [ { label: 'Report Type', value: 'Comprehensive Employee Analysis' }, { label: 'Data Source', value: 'HR Management System v2.1' }, { label: 'Confidentiality', value: 'Internal Use Only' }, { label: 'Next Review', value: 'Q1 2026' }, ], logging: consoleLogger, }); const advancedPath = join(outputDir, 'employee-report-advanced.pdf'); writeFileSync(advancedPath, advancedResult.buffer); console.log(`āœ… Advanced report saved: ${advancedPath}`); console.log( ` šŸ“Š Pages: ${advancedResult.pageCount}, Size: ${(advancedResult.sizeBytes / 1024).toFixed(1)}KB\n` ); // Example 3: Large dataset with chunking and optimization console.log('⚔ Example 3: Large Dataset Processing...'); // Generate realistic large dataset const generateEmployee = i => ({ id: i + 1, name: `Employee ${String(i + 1).padStart(4, '0')}`, department: [ 'Engineering', 'Marketing', 'Sales', 'HR', 'Finance', 'Operations', 'Legal', 'IT', ][i % 8], salary: 45000 + Math.floor(Math.random() * 120000), startDate: new Date( 2018 + Math.floor(Math.random() * 7), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1 ) .toISOString() .split('T')[0], active: Math.random() > 0.05, // 95% active skills: [ ['JavaScript', 'React', 'Node.js', 'Python', 'AWS'], ['SEO', 'Content Marketing', 'Analytics', 'Social Media'], ['CRM', 'Negotiation', 'Lead Generation', 'B2B Sales'], ['Recruitment', 'Employee Relations', 'Training', 'Policy'], ['Accounting', 'Financial Analysis', 'Budgeting', 'Compliance'], ['Project Management', 'Process Improvement', 'Quality Assurance'], ['Contract Law', 'Compliance', 'Risk Management', 'Legal Research'], ['System Administration', 'Security', 'Network Management'], ][i % 8].slice(0, Math.floor(Math.random() * 3) + 2), performance: ['Outstanding', 'Excellent', 'Good', 'Average'][Math.floor(Math.random() * 4)], location: [ 'New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix', 'Philadelphia', 'San Antonio', 'San Diego', ][i % 8], }); const largeData = Array.from({ length: 1000 }, (_, i) => generateEmployee(i)); // Performance estimation const estimate = estimatePdfGeneration({ title: 'Large Employee Report', data: largeData, columns, template: 'advanced-employee-report', }); console.log(` šŸ“ˆ Performance Estimate:`); console.log(` ā±ļø Time: ${(estimate.estimatedTimeMs / 1000).toFixed(1)}s`); console.log(` šŸ’¾ Memory: ${estimate.estimatedMemoryMB.toFixed(1)}MB`); console.log(` šŸ“„ Pages: ${estimate.estimatedPages}`); console.log( ` 🧩 Chunks: ${estimate.chunking.chunks} (${estimate.chunking.rowsPerChunk} rows each)` ); console.log(` šŸ”„ Concurrency: ${estimate.chunking.concurrency}`); if (estimate.recommendations.length > 0) { console.log(` šŸ’” Recommendations:`); estimate.recommendations.forEach(rec => console.log(` • ${rec}`)); } const optimizedResult = await generateOptimizedPdf({ title: 'Large Employee Dataset Report', data: largeData, columns, template: 'advanced-employee-report', userInfo: { companyName: 'Acme Corporation', name: 'Data Analytics Department', email: 'data@acme.com', }, infoSection: [ { label: 'Dataset Size', value: `${largeData.length.toLocaleString()} records` }, { label: 'Processing Method', value: 'Optimized Chunking' }, { label: 'Data Quality', value: 'āœ“ Validated' }, { label: 'Export Format', value: 'PDF/A-1b' }, ], chunking: { chunkSize: 100, maxConcurrency: 3, }, logging: consoleLogger, }); const largePath = join(outputDir, 'employee-report-large.pdf'); writeFileSync(largePath, optimizedResult.buffer); console.log(` āœ… Large report saved: ${largePath}`); console.log(` šŸ“Š Final Stats:`); console.log(` šŸ“„ Pages: ${optimizedResult.pageCount}`); console.log(` šŸ’¾ Size: ${(optimizedResult.sizeBytes / 1024 / 1024).toFixed(2)}MB`); console.log(` 🧩 Chunks: ${optimizedResult.metadata.chunkCount}`); console.log(` ā±ļø Time: ${(optimizedResult.durationMs / 1000).toFixed(1)}s\n`); // Example 4: PDF Analysis and Manipulation console.log('šŸ” Example 4: PDF Analysis & Manipulation...'); const allPdfs = [basicResult.buffer, advancedResult.buffer, optimizedResult.buffer]; // Analyze PDFs const analysis = await analyzePdfs(allPdfs); console.log(` šŸ“ˆ Analysis Results:`); console.log(` šŸ“„ Total PDFs: ${analysis.totalChunks}`); console.log(` šŸ“‹ Total Pages: ${analysis.totalPages}`); console.log(` ļæ½ Total Size: ${(analysis.totalSize / 1024 / 1024).toFixed(2)}MB`); analysis.chunkInfo.forEach((info, index) => { const names = ['Basic', 'Advanced', 'Large']; console.log( ` šŸ“Š ${names[index]}: ${info.pages} pages, ${info.sizeKB}KB, ${info.valid ? 'āœ“' : 'āœ—'} valid` ); }); // Extract specific pages from the large report console.log(` āœ‚ļø Extracting first 5 pages from large report...`); const extractedPages = await extractPages(optimizedResult.buffer, [0, 1, 2, 3, 4]); const extractedPath = join(outputDir, 'employee-report-extract.pdf'); writeFileSync(extractedPath, extractedPages); const extractedInfo = await getPdfInfo(extractedPages); console.log(` āœ… Extracted pages saved: ${extractedPath}`); console.log(` šŸ“„ Pages: ${extractedInfo.pages}, Size: ${extractedInfo.sizeKB}KB\n`); // Example 5: Department-specific reports and merging console.log('šŸ”— Example 5: Department Reports & Merging...'); const departments = ['Engineering', 'Marketing', 'Sales', 'HR']; const departmentBuffers = []; console.log(` šŸ“Š Generating individual department reports...`); for (const dept of departments) { const deptData = largeData.filter(emp => emp.department === dept); if (deptData.length === 0) continue; const deptResult = await generatePdf({ title: `${dept} Department Analysis`, data: deptData, columns, template: 'advanced-employee-report', userInfo: { companyName: 'Acme Corporation', name: `${dept} Department Manager`, email: `${dept.toLowerCase()}@acme.com`, }, infoSection: [ { label: 'Department', value: dept }, { label: 'Employee Count', value: deptData.length.toString() }, { label: 'Active Rate', value: `${((deptData.filter(e => e.active).length / deptData.length) * 100).toFixed(1)}%`, }, { label: 'Avg Salary', value: `$${Math.round(deptData.reduce((sum, e) => sum + e.salary, 0) / deptData.length).toLocaleString()}`, }, ], logging: undefined, // Suppress detailed logging }); departmentBuffers.push(deptResult.buffer); console.log(` āœ“ ${dept}: ${deptResult.pageCount} pages, ${deptData.length} employees`); } // Merge all department reports console.log(` šŸ”— Merging ${departmentBuffers.length} department reports...`); const mergedReport = await mergePdfs(departmentBuffers, { logging: consoleLogger, }); const mergedPath = join(outputDir, 'employee-report-merged-departments.pdf'); writeFileSync(mergedPath, mergedReport); const mergedInfo = await getPdfInfo(mergedReport); console.log(` āœ… Merged report saved: ${mergedPath}`); console.log(` šŸ“„ Total Pages: ${mergedInfo.pages}`); console.log(` šŸ’¾ Size: ${(mergedReport.length / 1024 / 1024).toFixed(2)}MB\n`); // Example 6: PDF Splitting and individual page analysis console.log('āœ‚ļø Example 6: PDF Splitting...'); console.log(` šŸ“„ Splitting advanced report into individual pages...`); const splitPages = await splitPdf(advancedResult.buffer); const splitDir = join(outputDir, 'split-pages'); if (!existsSync(splitDir)) { mkdirSync(splitDir, { recursive: true }); } for (let i = 0; i < splitPages.length; i++) { const pagePath = join(splitDir, `page-${i + 1}.pdf`); writeFileSync(pagePath, splitPages[i]); } console.log(` āœ… Split into ${splitPages.length} individual pages in: ${splitDir}\n`); // Example 7: Error handling and recovery console.log('šŸ›”ļø Example 7: Error Handling...'); try { // Intentionally trigger validation error await generatePdf({ title: '', // Invalid: empty title data: sampleData, columns: [], }); } catch (error) { console.log(` āœ… Caught validation error: ${error.message}`); } try { // Test with invalid PDF buffer await mergePdfs([Buffer.from('invalid pdf content')]); } catch (error) { console.log(` āœ… Caught merge error: ${error.message}`); } // Performance monitoring example console.log('\n⚔ Performance Summary:'); console.log( ` šŸ“Š Total processing time: ${(Date.now() - process.uptime() * 1000).toFixed(0)}ms` ); console.log( ` šŸ’¾ Memory usage: ${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)}MB` ); console.log('\nšŸŽ‰ All examples completed successfully!'); console.log('\nšŸ“ Generated files in output directory:'); console.log(' šŸ“„ employee-report-basic.pdf - Basic template example'); console.log(' šŸŽØ employee-report-advanced.pdf - Advanced styling with charts'); console.log(' ⚔ employee-report-large.pdf - Large dataset with optimization'); console.log(' āœ‚ļø employee-report-extract.pdf - Extracted pages example'); console.log(' šŸ”— employee-report-merged-departments.pdf - Merged department reports'); console.log(' šŸ“„ split-pages/ - Individual page files'); // Advanced usage tips console.log('\nšŸ’” Advanced Usage Tips:'); console.log(' • Use chunking for datasets > 1000 records'); console.log(' • Set maxConcurrency based on available CPU cores'); console.log(' • Validate PDFs after generation for quality assurance'); console.log(' • Use custom templates for brand consistency'); console.log(' • Monitor memory usage for large operations'); console.log(' • Implement proper error handling and recovery'); } catch (error) { console.error('āŒ Error in examples:', error); // Enhanced error reporting if (error.code === 'BROWSER_ERROR') { console.error('\nšŸ’” Browser Error Solutions:'); console.error(' • Ubuntu/Debian: sudo apt-get install chromium-browser'); console.error(' • macOS: brew install chromium'); console.error(' • Windows: Download Chromium from official site'); console.error(' • Docker: Use puppeteer/puppeteer image'); } else if (error.code === 'MEMORY_ERROR') { console.error('\nšŸ’” Memory Error Solutions:'); console.error(' • Reduce chunk size (try 50-100 records)'); console.error(' • Lower maxConcurrency (try 1-2)'); console.error(' • Process data in smaller batches'); console.error(' • Increase Node.js memory: node --max-old-space-size=4096'); } else if (error.code === 'TIMEOUT_ERROR') { console.error('\nšŸ’” Timeout Error Solutions:'); console.error(' • Increase timeoutMs option'); console.error(' • Simplify template complexity'); console.error(' • Reduce concurrent operations'); } process.exit(1); } } // Run if called directly if (require.main === module) { main().catch(console.error); } module.exports = { main, sampleData, columns };