safeer-pdf-generator
Version:
Framework-agnostic PDF generation library with chunking, merging, S3 upload, and email delivery
214 lines • 8.17 kB
JavaScript
import { MergeService } from '../core/MergeService.js';
import { isBuffer } from '../utils/guards.js';
import { MergeError } from '../core/errors.js';
import { noOpLogger } from '../config/defaults.js';
/**
* Merge multiple PDF buffers into a single PDF
*/
export async function mergePdfs(pdfBuffers, options = {}) {
const logger = options.logging || noOpLogger;
// Validate inputs
if (!Array.isArray(pdfBuffers) || pdfBuffers.length === 0) {
throw new MergeError('At least one PDF buffer is required');
}
// Validate all inputs are buffers
for (let i = 0; i < pdfBuffers.length; i++) {
if (!isBuffer(pdfBuffers[i])) {
throw new MergeError(`Item at index ${i} is not a buffer`);
}
}
const mergeService = new MergeService(logger);
return await mergeService.mergePdfs(pdfBuffers, options);
}
/**
* Merge PDF files from file paths
*/
export async function mergePdfFiles(filePaths, options = {}) {
const { promises: fs } = await import('fs');
const logger = options.logging || noOpLogger;
if (!Array.isArray(filePaths) || filePaths.length === 0) {
throw new MergeError('At least one file path is required');
}
logger.info(`Loading ${filePaths.length} PDF files for merging`);
// Read all files
const buffers = [];
for (let i = 0; i < filePaths.length; i++) {
const filePath = filePaths[i];
try {
logger.debug(`Reading file: ${filePath}`);
const buffer = await fs.readFile(filePath);
buffers.push(buffer);
}
catch (error) {
throw new MergeError(`Failed to read file at index ${i}: ${filePath}`, filePaths.length, error);
}
}
return await mergePdfs(buffers, options);
}
/**
* Analyze PDF buffers without merging
*/
export async function analyzePdfs(pdfBuffers, options = {}) {
const logger = options.logging || noOpLogger;
if (!Array.isArray(pdfBuffers) || pdfBuffers.length === 0) {
throw new MergeError('At least one PDF buffer is required for analysis');
}
const mergeService = new MergeService(logger);
return await mergeService.analyzePdfs(pdfBuffers);
}
/**
* Validate PDF buffers
*/
export async function validatePdfs(pdfBuffers, options = {}) {
const logger = options.logging || noOpLogger;
if (!Array.isArray(pdfBuffers)) {
return {
valid: false,
errors: [{ chunkIndex: -1, error: 'Input is not an array' }],
};
}
if (pdfBuffers.length === 0) {
return {
valid: false,
errors: [{ chunkIndex: -1, error: 'No PDF buffers provided' }],
};
}
const mergeService = new MergeService(logger);
return await mergeService.validatePdfs(pdfBuffers);
}
/**
* Split a PDF into individual page buffers
*/
export async function splitPdf(pdfBuffer, options = {}) {
const logger = options.logging || noOpLogger;
if (!isBuffer(pdfBuffer)) {
throw new MergeError('Input must be a buffer');
}
if (pdfBuffer.length === 0) {
throw new MergeError('PDF buffer is empty');
}
const mergeService = new MergeService(logger);
return await mergeService.splitPdf(pdfBuffer);
}
/**
* Extract specific pages from a PDF
*/
export async function extractPages(pdfBuffer, pageIndices, options = {}) {
const logger = options.logging || noOpLogger;
if (!isBuffer(pdfBuffer)) {
throw new MergeError('Input must be a buffer');
}
if (!Array.isArray(pageIndices) || pageIndices.length === 0) {
throw new MergeError('Page indices must be a non-empty array');
}
// Validate page indices are numbers (but allow negative/out-of-range, service will filter)
for (const index of pageIndices) {
if (typeof index !== 'number' || !Number.isInteger(index)) {
throw new MergeError('All page indices must be integers');
}
}
const mergeService = new MergeService(logger);
return await mergeService.extractPages(pdfBuffer, pageIndices);
}
/**
* Get information about a PDF
*/
export async function getPdfInfo(pdfBuffer, options = {}) {
const logger = options.logging || noOpLogger;
if (!isBuffer(pdfBuffer)) {
throw new MergeError('Input must be a buffer');
}
if (pdfBuffer.length === 0) {
throw new MergeError('PDF buffer is empty');
}
const mergeService = new MergeService(logger);
return await mergeService.getPdfInfo(pdfBuffer);
}
/**
* Merge PDFs with custom page ranges
*/
export async function mergePdfsWithRanges(inputs, options = {}) {
const logger = options.logging || noOpLogger;
if (!Array.isArray(inputs) || inputs.length === 0) {
throw new MergeError('At least one input is required');
}
logger.info(`Merging ${inputs.length} PDFs with custom page ranges`);
const extractedBuffers = [];
const mergeService = new MergeService(logger);
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i];
if (!isBuffer(input.buffer)) {
throw new MergeError(`Input at index ${i} must have a buffer property`);
}
try {
let pageIndices;
if (input.pages) {
// Use specific pages
pageIndices = input.pages;
}
else if (input.startPage !== undefined || input.endPage !== undefined) {
// Use page range
const pdfInfo = await mergeService.getPdfInfo(input.buffer);
const startPage = input.startPage || 0;
const endPage = input.endPage !== undefined ? input.endPage : pdfInfo.pages - 1;
pageIndices = [];
for (let page = startPage; page <= Math.min(endPage, pdfInfo.pages - 1); page++) {
pageIndices.push(page);
}
}
else {
// Include all pages - just add the buffer as-is
extractedBuffers.push(input.buffer);
continue;
}
// Extract the specified pages
const extractedBuffer = await mergeService.extractPages(input.buffer, pageIndices);
extractedBuffers.push(extractedBuffer);
logger.debug(`Processed input ${i + 1}: extracted ${pageIndices.length} pages`);
}
catch (error) {
throw new MergeError(`Failed to process input at index ${i}`, inputs.length, error);
}
}
// Merge all extracted buffers
return await mergePdfs(extractedBuffers, options);
}
/**
* Batch merge multiple sets of PDFs
*/
export async function batchMergePdfs(batches, options = {}) {
const logger = options.logging || noOpLogger;
const concurrency = options.concurrency || 2;
if (!Array.isArray(batches) || batches.length === 0) {
throw new MergeError('At least one batch is required');
}
logger.info(`Batch merging ${batches.length} PDF sets with concurrency ${concurrency}`);
const results = [];
let currentIndex = 0;
const processBatch = async () => {
while (currentIndex < batches.length) {
const index = currentIndex++;
if (index >= batches.length)
return;
const batch = batches[index];
const batchName = options.batchNames?.[index] || `batch-${index + 1}`;
try {
logger.debug(`Processing ${batchName} with ${batch.length} PDFs`);
const mergedBuffer = await mergePdfs(batch, options);
results[index] = mergedBuffer;
logger.debug(`Completed ${batchName}`);
}
catch (error) {
throw new MergeError(`Failed to merge ${batchName}`, batches.length, error);
}
}
};
// Create worker promises with concurrency limit
const workers = Array(Math.min(concurrency, batches.length))
.fill(null)
.map(() => processBatch());
await Promise.all(workers);
logger.info(`Batch merge completed: ${results.length} merged PDFs`);
return results;
}
//# sourceMappingURL=mergePdfs.js.map