Error Handling Guide
Comprehensive guide on typed errors, retries, and edge cases.
Error Types
The library provides two main error classes:
PcoApiError
Base API error class with JSON:API error structure:
import { PcoApiError } from '@rachelallyson/planning-center-people-ts';
try {
await client.people.getById('invalid-id');
} catch (error) {
if (error instanceof PcoApiError) {
console.log(error.status); // HTTP status code (e.g., 404)
console.log(error.statusText); // Status text (e.g., 'Not Found')
console.log(error.errors); // Array of JSON:API error objects
console.log(error.message); // Human-readable message
}
}PcoError
Enhanced error with category, severity, and context:
import { PcoError, ErrorCategory, ErrorSeverity } from '@rachelallyson/planning-center-people-ts';
try {
await client.people.create({ first_name: '' });
} catch (error) {
if (error instanceof PcoError) {
console.log(error.category); // ErrorCategory.VALIDATION
console.log(error.severity); // ErrorSeverity.LOW
console.log(error.retryable); // false
console.log(error.context); // Error context object
}
}Error Categories
Errors are categorized for easier handling:
enum ErrorCategory {
EXTERNAL_API = 'external_api', // API returned an error
VALIDATION = 'validation', // Request validation failed
NETWORK = 'network', // Network/connection error
AUTHENTICATION = 'authentication', // Auth token invalid/expired
AUTHORIZATION = 'authorization', // Insufficient permissions
RATE_LIMIT = 'rate_limit', // Rate limit exceeded
TIMEOUT = 'timeout', // Request timed out
UNKNOWN = 'unknown' // Unknown error
}Error Severity
Errors are classified by severity:
enum ErrorSeverity {
LOW = 'low', // Minor issues, non-critical
MEDIUM = 'medium', // Moderate issues, should be addressed
HIGH = 'high', // Serious issues, immediate attention needed
CRITICAL = 'critical' // System-breaking errors
}Handling Specific Errors
Authentication Errors
import { PcoError, ErrorCategory } from '@rachelallyson/planning-center-people-ts';
try {
await client.people.getAll();
} catch (error) {
if (error instanceof PcoError && error.category === ErrorCategory.AUTHENTICATION) {
// Token expired or invalid
console.error('Authentication failed');
// For OAuth, the library handles refresh automatically
// but you should check if refresh failed
if (error.severity === ErrorSeverity.CRITICAL) {
// Refresh failed - redirect to login
redirectToLogin();
}
}
}Rate Limit Errors
import { PcoError, ErrorCategory } from '@rachelallyson/planning-center-people-ts';
try {
await client.people.getAll();
} catch (error) {
if (error instanceof PcoError && error.category === ErrorCategory.RATE_LIMIT) {
const retryAfter = error.getRetryDelay(); // Get delay in milliseconds
console.warn(`Rate limited. Retry after ${retryAfter}ms`);
// Wait and retry
await new Promise(resolve => setTimeout(resolve, retryAfter));
// Retry the request
return await client.people.getAll();
}
}Validation Errors
import { PcoApiError } from '@rachelallyson/planning-center-people-ts';
try {
await client.people.create({ first_name: '' });
} catch (error) {
if (error instanceof PcoApiError && error.status === 422) {
// Parse JSON:API error details
error.errors.forEach(err => {
console.error(`Field: ${err.source?.pointer}`);
console.error(`Title: ${err.title}`);
console.error(`Detail: ${err.detail}`);
});
}
}Network Errors
import { PcoError, ErrorCategory, retryWithBackoff } from '@rachelallyson/planning-center-people-ts';
try {
await client.people.getAll();
} catch (error) {
if (error instanceof PcoError && error.category === ErrorCategory.NETWORK) {
// Network errors are retryable
if (error.retryable) {
// Retry with exponential backoff
return await retryWithBackoff(
() => client.people.getAll(),
{ maxRetries: 3, baseDelay: 1000 }
);
}
}
}Retry Strategy
Automatic Retry (via Config)
const client = new PcoClient({
auth: { /* ... */ },
retry: {
enabled: true,
maxRetries: 3,
baseDelay: 1000, // 1 second
maxDelay: 10000, // 10 seconds max
backoff: 'exponential'
}
});
// Automatic retries on retryable errors
await client.people.getAll();Manual Retry with Backoff
import { retryWithBackoff, shouldNotRetry } from '@rachelallyson/planning-center-people-ts';
async function fetchPeopleWithRetry(client: PcoClient) {
return await retryWithBackoff(
async () => {
return await client.people.getAll();
},
{
maxRetries: 3,
baseDelay: 1000,
maxDelay: 10000,
shouldRetry: (error) => {
// Custom retry logic
if (shouldNotRetry(error)) return false;
return error instanceof PcoError && error.retryable;
}
}
);
}Error Context
Errors include context for debugging:
import { PcoError } from '@rachelallyson/planning-center-people-ts';
try {
await client.people.create({
first_name: 'John',
last_name: 'Doe'
});
} catch (error) {
if (error instanceof PcoError) {
console.log('Error context:', error.context);
// {
// category: ErrorCategory.VALIDATION,
// severity: ErrorSeverity.LOW,
// timestamp: '2025-01-11T12:00:00.000Z',
// endpoint: '/people',
// method: 'POST',
// retryCount: 0,
// metadata: { /* custom metadata */ }
// }
}
}Error Handlers
Use utility functions for common error scenarios:
import {
handleNetworkError,
handleTimeoutError,
handleValidationError
} from '@rachelallyson/planning-center-people-ts';
try {
await client.people.getAll();
} catch (error) {
if (error instanceof PcoError) {
switch (error.category) {
case ErrorCategory.NETWORK:
handleNetworkError(error, { maxRetries: 3 });
break;
case ErrorCategory.TIMEOUT:
handleTimeoutError(error, { timeout: 30000 });
break;
case ErrorCategory.VALIDATION:
handleValidationError(error);
break;
}
}
}Event-Based Error Monitoring
Listen to error events for monitoring:
client.on('error', (event) => {
console.error('Error occurred:', {
operation: event.operation,
error: event.error,
timestamp: event.timestamp,
context: event.context
});
// Send to error tracking service
errorTracker.captureError(event.error, event.context);
});
client.on('auth:failure', (event) => {
console.error('Auth failure:', event.error);
// Handle auth failure (e.g., redirect to login)
});See Event System Guide for complete documentation of all event types, including error events, and advanced monitoring patterns.
Error Wrapping
Wrap operations with error boundary:
import { withErrorBoundary } from '@rachelallyson/planning-center-people-ts';
const safeOperation = withErrorBoundary(
async () => {
return await client.people.getAll();
},
{
onError: (error, context) => {
console.error('Operation failed:', error, context);
},
fallback: () => ({ data: [], links: {}, meta: {} })
}
);
const result = await safeOperation();Best Practices
-
Always check error type before handling:
if (error instanceof PcoError) { // Handle PCO error } else if (error instanceof PcoApiError) { // Handle API error } -
Use error categories for routing:
switch (error.category) { case ErrorCategory.AUTHENTICATION: handleAuthError(error); break; case ErrorCategory.RATE_LIMIT: handleRateLimit(error); break; // ... } -
Check retryable flag before retrying:
if (error.retryable) { await retryWithBackoff(() => operation()); } -
Log error context for debugging:
console.error('Error:', { message: error.message, category: error.category, context: error.context }); -
Use event system for centralized error monitoring:
client.on('error', (event) => { errorTracker.log(event); });
Common Error Patterns
Pattern 1: Retry on Network Error
async function fetchWithRetry<T>(
operation: () => Promise<T>,
maxRetries = 3
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (error instanceof PcoError &&
error.category === ErrorCategory.NETWORK &&
error.retryable &&
i < maxRetries - 1) {
await new Promise(resolve =>
setTimeout(resolve, 1000 * Math.pow(2, i))
);
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}Pattern 2: Graceful Degradation
async function getPeopleSafely(client: PcoClient) {
try {
return await client.people.getAll();
} catch (error) {
if (error instanceof PcoError) {
if (error.category === ErrorCategory.RATE_LIMIT) {
// Return empty result if rate limited
return { data: [], links: {}, meta: {} };
}
if (!error.retryable) {
// Return cached data if available
return getCachedPeople();
}
}
throw error;
}
}Next: See Configuration Reference for all config options.