Error Handling
Scope: Core API (@scaryterry/pdfium).
The library uses structured errors with specific codes and specialised error classes. Use this guide to handle failures predictably and recover safely.
Error Philosophy
Section titled “Error Philosophy”The library follows these principles:
- Fail fast: Errors are thrown immediately when detected
- Be specific: Each error has a unique code and descriptive message
- Provide context: Errors include relevant details for debugging
- Enable recovery: Error types indicate whether recovery is possible
Error Hierarchy
Section titled “Error Hierarchy”All library errors extend PDFiumError:
import { PDFiumError, InitialisationError, NetworkError, // extends InitialisationError DocumentError, PermissionsError, // extends DocumentError PageError, RenderError, MemoryError, TextError, ObjectError, WorkerError,} from '@scaryterry/pdfium';PDFiumError (base)├── InitialisationError — WASM/library init failures│ └── NetworkError — Network fetch failures (e.g. WASM URL unreachable)├── DocumentError — Document loading/saving│ └── PermissionsError — Document permission restrictions├── PageError — Page access issues├── RenderError — Rendering failures├── MemoryError — WASM memory issues├── TextError — Text extraction failures├── ObjectError — Page object/annotation access└── WorkerError — Worker communicationError Properties
Section titled “Error Properties”All PDFiumError instances have:
interface PDFiumError { code: PDFiumErrorCode; // Numeric error code message: string; // Human-readable description context?: Record<string, unknown>; // Additional details name: string; // Error class name}Example:
try { using document = await pdfium.openDocument(corruptedData);} catch (error) { if (error instanceof PDFiumError) { console.log(error.code); // 201 console.log(error.message); // "Invalid PDF format" console.log(error.name); // "DocumentError" console.log(error.context); // { fileSize: 12345 } }}Basic Error Handling
Section titled “Basic Error Handling”Catch All PDFium Errors
Section titled “Catch All PDFium Errors”try { using pdfium = await PDFium.init(); using document = await pdfium.openDocument(data); using page = document.getPage(0); const text = page.getText();} catch (error) { if (error instanceof PDFiumError) { console.error(`PDFium error ${error.code}: ${error.message}`); } else { throw error; // Re-throw non-PDFium errors }}Handle Specific Error Types
Section titled “Handle Specific Error Types”try { using document = await pdfium.openDocument(data);} catch (error) { if (error instanceof DocumentError) { // Document-specific handling } else if (error instanceof InitialisationError) { // Init-specific handling } else if (error instanceof PDFiumError) { // Generic PDFium error } else { throw error; }}Handle Specific Error Codes
Section titled “Handle Specific Error Codes”import { PDFiumErrorCode, DocumentError } from '@scaryterry/pdfium';
try { using document = await pdfium.openDocument(data);} catch (error) { if (error instanceof DocumentError) { switch (error.code) { case PDFiumErrorCode.DOC_PASSWORD_REQUIRED: // Prompt user for password break; case PDFiumErrorCode.DOC_PASSWORD_INCORRECT: // Show "wrong password" message break; case PDFiumErrorCode.DOC_FORMAT_INVALID: // File is not a valid PDF break; default: // Other document errors throw error; } }}Common Error Scenarios
Section titled “Common Error Scenarios”Password-Protected PDFs
Section titled “Password-Protected PDFs”import { PDFiumErrorCode, DocumentError } from '@scaryterry/pdfium';
async function openWithPassword( pdfium: PDFium, data: Uint8Array, maxAttempts = 3): Promise<PDFiumDocument> { let password: string | undefined; let attempts = 0;
while (attempts < maxAttempts) { try { return await pdfium.openDocument(data, { password }); } catch (error) { if (!(error instanceof DocumentError)) throw error;
if (error.code === PDFiumErrorCode.DOC_PASSWORD_REQUIRED) { password = await promptPassword('Enter PDF password:'); attempts++; } else if (error.code === PDFiumErrorCode.DOC_PASSWORD_INCORRECT) { password = await promptPassword('Incorrect password. Try again:'); attempts++; } else { throw error; } } }
throw new Error('Maximum password attempts exceeded');}Invalid PDF Files
Section titled “Invalid PDF Files”import { PDFiumErrorCode, DocumentError } from '@scaryterry/pdfium';
async function loadPDFSafely( pdfium: PDFium, data: Uint8Array): Promise<PDFiumDocument | null> { try { return await pdfium.openDocument(data); } catch (error) { if (error instanceof DocumentError) { if (error.code === PDFiumErrorCode.DOC_FORMAT_INVALID) { console.error('File is not a valid PDF'); return null; } } throw error; }}Render Dimension Limits
Section titled “Render Dimension Limits”import { PDFiumErrorCode, RenderError } from '@scaryterry/pdfium';
function renderWithFallback(page: PDFiumPage, scale: number) { try { return page.render({ scale }); } catch (error) { if (error instanceof RenderError) { if (error.code === PDFiumErrorCode.RENDER_INVALID_DIMENSIONS) { // Try with lower scale console.warn(`Scale ${scale} too large, falling back to 1`); return page.render({ scale: 1 }); } } throw error; }}Resource Disposal Errors
Section titled “Resource Disposal Errors”import { PDFiumErrorCode, PDFiumError } from '@scaryterry/pdfium';
function safelyAccessPage(document: PDFiumDocument, index: number) { try { return document.getPage(index); } catch (error) { if (error instanceof PDFiumError) { if (error.code === PDFiumErrorCode.RESOURCE_DISPOSED) { throw new Error('Document has been closed'); } } throw error; }}Error Recovery Strategies
Section titled “Error Recovery Strategies”Recoverable vs Non-Recoverable
Section titled “Recoverable vs Non-Recoverable”| Error Type | Recoverable? | Strategy |
|---|---|---|
DOC_PASSWORD_REQUIRED | Yes | Prompt for password |
DOC_PASSWORD_INCORRECT | Yes | Prompt again |
DOC_FORMAT_INVALID | No | Reject file |
PAGE_INDEX_OUT_OF_RANGE | Yes | Validate index |
RENDER_INVALID_DIMENSIONS | Yes | Reduce scale |
MEMORY_ALLOCATION_FAILED | Maybe | Free resources, retry |
WORKER_TIMEOUT | Yes | Increase timeout, retry |
RESOURCE_DISPOSED | No | Logic error, fix code |
Graceful Degradation
Section titled “Graceful Degradation”async function extractText(page: PDFiumPage): Promise<string | null> { try { return page.getText(); } catch (error) { if (error instanceof TextError) { console.warn('Text extraction failed:', error.message); return null; // Graceful degradation } throw error; }}
// Usageconst text = await extractText(page);if (text === null) { console.log('Text extraction unavailable for this page');}Retry with Exponential Backoff
Section titled “Retry with Exponential Backoff”async function retryOperation<T>( operation: () => Promise<T>, maxRetries = 3): Promise<T> { let lastError: Error | undefined;
for (let i = 0; i < maxRetries; i++) { try { return await operation(); } catch (error) { lastError = error as Error;
// Only retry on specific errors if (error instanceof WorkerError) { const delay = Math.pow(2, i) * 100; await new Promise(resolve => setTimeout(resolve, delay)); continue; }
throw error; // Don't retry other errors } }
throw lastError;}Error Logging
Section titled “Error Logging”Structured Logging
Section titled “Structured Logging”import { PDFiumError } from '@scaryterry/pdfium';
function logError(error: unknown, context: Record<string, unknown> = {}) { if (error instanceof PDFiumError) { console.error({ type: 'PDFiumError', errorName: error.name, errorCode: error.code, message: error.message, context: { ...error.context, ...context, }, stack: error.stack, }); } else if (error instanceof Error) { console.error({ type: 'Error', message: error.message, context, stack: error.stack, }); } else { console.error({ type: 'Unknown', value: error, context, }); }}
// Usagetry { using document = await pdfium.openDocument(data);} catch (error) { logError(error, { fileName: 'input.pdf', fileSize: data.length });}Error Reporting
Section titled “Error Reporting”interface ErrorReport { code: number; type: string; message: string; recoverable: boolean; userMessage: string;}
function createErrorReport(error: unknown): ErrorReport { if (!(error instanceof PDFiumError)) { return { code: -1, type: 'Unknown', message: String(error), recoverable: false, userMessage: 'An unexpected error occurred', }; }
const userMessages: Record<number, string> = { [PDFiumErrorCode.DOC_PASSWORD_REQUIRED]: 'This PDF requires a password', [PDFiumErrorCode.DOC_PASSWORD_INCORRECT]: 'The password is incorrect', [PDFiumErrorCode.DOC_FORMAT_INVALID]: 'This file is not a valid PDF', [PDFiumErrorCode.RENDER_INVALID_DIMENSIONS]: 'The image would be too large', // ... more mappings };
return { code: error.code, type: error.name, message: error.message, recoverable: isRecoverable(error.code), userMessage: userMessages[error.code] || 'An error occurred processing the PDF', };}
function isRecoverable(code: PDFiumErrorCode): boolean { return [ PDFiumErrorCode.DOC_PASSWORD_REQUIRED, PDFiumErrorCode.DOC_PASSWORD_INCORRECT, PDFiumErrorCode.PAGE_INDEX_OUT_OF_RANGE, PDFiumErrorCode.RENDER_INVALID_DIMENSIONS, PDFiumErrorCode.WORKER_TIMEOUT, ].includes(code);}Testing Error Handling
Section titled “Testing Error Handling”Unit Testing Errors
Section titled “Unit Testing Errors”import { describe, it, expect } from 'vitest';import { PDFium, DocumentError, PDFiumErrorCode } from '@scaryterry/pdfium';
describe('error handling', () => { it('throws DocumentError for invalid PDF', async () => { using pdfium = await PDFium.init();
await expect( pdfium.openDocument(new Uint8Array([1, 2, 3])) ).rejects.toThrow(DocumentError); });
it('throws correct error code for password-protected PDF', async () => { using pdfium = await PDFium.init(); const encryptedPdf = await loadEncryptedTestPDF();
try { await pdfium.openDocument(encryptedPdf); expect.fail('Should have thrown'); } catch (error) { expect(error).toBeInstanceOf(DocumentError); expect((error as DocumentError).code).toBe( PDFiumErrorCode.DOC_PASSWORD_REQUIRED ); } });});Best Practices
Section titled “Best Practices”- ✓ Always catch
PDFiumErrorat appropriate boundaries - ✓ Handle specific error codes for recoverable situations
- ✓ Log errors with context for debugging
- ✓ Provide user-friendly messages for common errors
- ✓ Clean up resources in
finallyblocks or useusing
- ✗ Swallow errors silently
- ✗ Catch and ignore
RESOURCE_DISPOSED— fix the code - ✗ Assume all errors are recoverable
- ✗ Expose internal error codes to end users
- ✗ Retry infinitely without backoff
See Also
Section titled “See Also”- Error Reference — Complete error code listing
- Resource Management — Preventing disposal errors
- Examples — Error handling examples