safeer-pdf-generator
Version:
Framework-agnostic PDF generation library with chunking, merging, S3 upload, and email delivery
271 lines • 9.29 kB
JavaScript
import { ConfigurationError } from '../core/errors.js';
/**
* Validate PDF generation options
*/
export function validatePdfOptions(options) {
if (!options.title || typeof options.title !== 'string') {
throw new ConfigurationError('Title is required and must be a string', 'title');
}
if (!Array.isArray(options.data)) {
throw new ConfigurationError('Data must be an array', 'data');
}
if (!Array.isArray(options.columns)) {
throw new ConfigurationError('Columns must be an array', 'columns');
}
// Validate columns
for (let i = 0; i < options.columns.length; i++) {
const column = options.columns[i];
if (!isValidColumn(column)) {
throw new ConfigurationError(`Invalid column definition at index ${i}`, 'columns');
}
}
// Validate chunking options
if (options.chunking) {
if (options.chunking.chunkSize &&
(typeof options.chunking.chunkSize !== 'number' || options.chunking.chunkSize < 1)) {
throw new ConfigurationError('Chunk size must be a positive number', 'chunking.chunkSize');
}
if (options.chunking.maxConcurrency &&
(typeof options.chunking.maxConcurrency !== 'number' || options.chunking.maxConcurrency < 1)) {
throw new ConfigurationError('Max concurrency must be a positive number', 'chunking.maxConcurrency');
}
}
// Validate timeout
if (options.timeoutMs && (typeof options.timeoutMs !== 'number' || options.timeoutMs < 1000)) {
throw new ConfigurationError('Timeout must be at least 1000ms', 'timeoutMs');
}
}
/**
* Check if column definition is valid
*/
export function isValidColumn(column) {
return (typeof column === 'object' &&
column !== null &&
typeof column.key === 'string' &&
typeof column.title === 'string' &&
typeof column.dataIndex === 'string');
}
/**
* Validate S3 upload configuration
*/
export function validateS3Config(config) {
if (typeof config !== 'object' || config === null) {
throw new ConfigurationError('S3 config must be an object', 's3');
}
if (!config.bucket || typeof config.bucket !== 'string') {
throw new ConfigurationError('S3 bucket is required', 's3.bucket');
}
if (!config.region || typeof config.region !== 'string') {
throw new ConfigurationError('S3 region is required', 's3.region');
}
}
/**
* Validate and enhance SMTP configuration
*/
export function validateSmtpConfig(config) {
if (config.smtp) {
const { port, secure } = config.smtp;
// Validate port/secure combination
if (port === 465 && secure === false) {
throw new ConfigurationError('Port 465 requires secure=true (SSL). Use port 587 with secure=false for STARTTLS.', 'email.smtp.port');
}
if (port === 25 && secure === true) {
throw new ConfigurationError('Port 25 should use secure=false. Use port 465 for SSL or 587 for STARTTLS.', 'email.smtp.port');
}
// Auto-correct common misconfigurations
if (port === 465 && secure === undefined) {
config.smtp.secure = true;
console.warn('Auto-corrected: Port 465 detected, setting secure=true');
}
if (port === 587 && secure === undefined) {
config.smtp.secure = false;
console.warn('Auto-corrected: Port 587 detected, setting secure=false');
}
}
}
/**
* Apply smart configuration defaults based on the SMTP host
*/
export function applySmartEmailDefaults(config) {
if (config.smtp?.host && !config.smtp.port) {
const host = config.smtp.host.toLowerCase();
if (host.includes('gmail')) {
config.smtp.port = 587;
config.smtp.secure = false;
console.info('Applied Gmail defaults: port 587, secure=false');
}
else if (host.includes('outlook') || host.includes('office365')) {
config.smtp.port = 587;
config.smtp.secure = false;
console.info('Applied Outlook/Office365 defaults: port 587, secure=false');
}
else if (host.includes('yahoo')) {
config.smtp.port = 587;
config.smtp.secure = false;
console.info('Applied Yahoo defaults: port 587, secure=false');
}
else if (host.includes('sendgrid')) {
config.smtp.port = 587;
config.smtp.secure = false;
console.info('Applied SendGrid defaults: port 587, secure=false');
}
else if (host.includes('mailgun')) {
config.smtp.port = 587;
config.smtp.secure = false;
console.info('Applied Mailgun defaults: port 587, secure=false');
}
}
}
/**
* Validate email configuration
*/
export function validateEmailConfig(config) {
if (typeof config !== 'object' || config === null) {
throw new ConfigurationError('Email config must be an object', 'email');
}
if (!config.to || typeof config.to !== 'string') {
throw new ConfigurationError('Email recipient is required', 'email.to');
}
if (!isValidEmail(config.to)) {
throw new ConfigurationError('Invalid email address format', 'email.to');
}
// Validate transport or SMTP config
if (!config.transport && !config.smtp) {
throw new ConfigurationError('Either transport or SMTP config is required', 'email');
}
if (config.smtp) {
if (!config.smtp.host || typeof config.smtp.host !== 'string') {
throw new ConfigurationError('SMTP host is required', 'email.smtp.host');
}
if (typeof config.smtp.port !== 'number' || config.smtp.port < 1 || config.smtp.port > 65535) {
throw new ConfigurationError('SMTP port must be a valid port number', 'email.smtp.port');
}
if (!config.smtp.auth || !config.smtp.auth.user || !config.smtp.auth.pass) {
throw new ConfigurationError('SMTP authentication is required', 'email.smtp.auth');
}
// Apply smart defaults first
applySmartEmailDefaults(config);
// Then validate the configuration
validateSmtpConfig(config);
}
}
/**
* Validate email address format
*/
export function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Check if value is a Buffer
*/
export function isBuffer(value) {
return Buffer.isBuffer(value);
}
/**
* Check if value is a valid logging adapter
*/
export function isValidLogger(logger) {
return (typeof logger === 'object' &&
logger !== null &&
typeof logger.debug === 'function' &&
typeof logger.info === 'function' &&
typeof logger.warn === 'function' &&
typeof logger.error === 'function');
}
/**
* Sanitize filename for cross-platform compatibility
*/
export function sanitizeFilename(filename) {
// Remove or replace invalid characters
return filename
.replace(/[<>:"/\\|?*\x00-\x1f]/g, '-')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
}
/**
* Validate that a value is a positive integer
*/
export function isPositiveInteger(value) {
return typeof value === 'number' && Number.isInteger(value) && value > 0;
}
/**
* Validate that a value is a non-negative number
*/
export function isNonNegativeNumber(value) {
return typeof value === 'number' && !isNaN(value) && value >= 0;
}
/**
* Check if an object has a specific property
*/
export function hasProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
/**
* Deep clone an object (simple version for configuration)
*/
export function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof Array) {
return obj.map(item => deepClone(item));
}
if (typeof obj === 'object') {
const cloned = {};
for (const key in obj) {
if (hasProperty(obj, key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
return obj;
}
/**
* Check if a string is a valid URL
*/
export function isValidUrl(url) {
try {
new URL(url);
return true;
}
catch {
return false;
}
}
/**
* Ensure a value is an array
*/
export function ensureArray(value) {
return Array.isArray(value) ? value : [value];
}
/**
* Get nested property value safely
*/
export function getNestedProperty(obj, path) {
return path.split('.').reduce((current, key) => {
return current && typeof current === 'object' ? current[key] : undefined;
}, obj);
}
/**
* Set nested property value safely
*/
export function setNestedProperty(obj, path, value) {
const keys = path.split('.');
const lastKey = keys.pop();
if (!lastKey)
return;
const target = keys.reduce((current, key) => {
if (!current[key] || typeof current[key] !== 'object') {
current[key] = {};
}
return current[key];
}, obj);
target[lastKey] = value;
}
//# sourceMappingURL=guards.js.map