errors.ts
1 // Error types for better error handling 2 3 export enum ErrorType { 4 NETWORK = 'NETWORK', 5 VALIDATION = 'VALIDATION', 6 CACHE = 'CACHE', 7 PARSING = 'PARSING', 8 AUTHENTICATION = 'AUTHENTICATION', 9 PERMISSION = 'PERMISSION', 10 NOT_FOUND = 'NOT_FOUND', 11 RATE_LIMIT = 'RATE_LIMIT', 12 SERVER = 'SERVER', 13 UNKNOWN = 'UNKNOWN', 14 } 15 16 export interface AppError { 17 type: ErrorType; 18 message: string; 19 code?: string | undefined; 20 details?: any; 21 timestamp: number; 22 retryable: boolean; 23 } 24 25 export class MangaNetworkError extends Error { 26 public readonly type = ErrorType.NETWORK; 27 public readonly retryable = true; 28 public readonly timestamp = Date.now(); 29 30 constructor( 31 message: string, 32 public readonly code?: string, 33 public readonly details?: any 34 ) { 35 super(message); 36 this.name = 'MangaNetworkError'; 37 } 38 } 39 40 export class MangaValidationError extends Error { 41 public readonly type = ErrorType.VALIDATION; 42 public readonly retryable = false; 43 public readonly timestamp = Date.now(); 44 45 constructor( 46 message: string, 47 public readonly field?: string, 48 public readonly details?: any 49 ) { 50 super(message); 51 this.name = 'MangaValidationError'; 52 } 53 } 54 55 export class MangaCacheError extends Error { 56 public readonly type = ErrorType.CACHE; 57 public readonly retryable = true; 58 public readonly timestamp = Date.now(); 59 60 constructor( 61 message: string, 62 public readonly operation?: string, 63 public readonly details?: any 64 ) { 65 super(message); 66 this.name = 'MangaCacheError'; 67 } 68 } 69 70 export class MangaParsingError extends Error { 71 public readonly type = ErrorType.PARSING; 72 public readonly retryable = false; 73 public readonly timestamp = Date.now(); 74 75 constructor( 76 message: string, 77 public readonly source?: string, 78 public readonly details?: any 79 ) { 80 super(message); 81 this.name = 'MangaParsingError'; 82 } 83 } 84 85 export class MangaNotFoundError extends Error { 86 public readonly type = ErrorType.NOT_FOUND; 87 public readonly retryable = false; 88 public readonly timestamp = Date.now(); 89 90 constructor( 91 message: string, 92 public readonly id?: string, 93 public readonly details?: any 94 ) { 95 super(message); 96 this.name = 'MangaNotFoundError'; 97 } 98 } 99 100 export function createAppError( 101 type: ErrorType, 102 message: string, 103 options: { 104 code?: string; 105 details?: any; 106 retryable?: boolean; 107 } = {} 108 ): AppError { 109 return { 110 type, 111 message, 112 code: options.code, 113 details: options.details, 114 timestamp: Date.now(), 115 retryable: options.retryable ?? true, 116 }; 117 } 118 119 export function isRetryableError(error: Error | AppError): boolean { 120 if ('retryable' in error) { 121 return error.retryable; 122 } 123 124 // Network errors are generally retryable 125 if (error.message.includes('network') || error.message.includes('timeout')) { 126 return true; 127 } 128 129 // Validation errors are not retryable 130 if ( 131 error.message.includes('validation') || 132 error.message.includes('invalid') 133 ) { 134 return false; 135 } 136 137 // Default to retryable for unknown errors 138 return true; 139 } 140 141 export function getErrorType(error: Error): ErrorType { 142 if ('type' in error && typeof error.type === 'string') { 143 return error.type as ErrorType; 144 } 145 146 const message = error.message.toLowerCase(); 147 148 if ( 149 message.includes('network') || 150 message.includes('timeout') || 151 message.includes('connection') 152 ) { 153 return ErrorType.NETWORK; 154 } 155 156 if ( 157 message.includes('validation') || 158 message.includes('invalid') || 159 message.includes('required') 160 ) { 161 return ErrorType.VALIDATION; 162 } 163 164 if (message.includes('cache')) { 165 return ErrorType.CACHE; 166 } 167 168 if (message.includes('parse') || message.includes('parsing')) { 169 return ErrorType.PARSING; 170 } 171 172 if (message.includes('not found') || message.includes('404')) { 173 return ErrorType.NOT_FOUND; 174 } 175 176 if (message.includes('rate limit') || message.includes('429')) { 177 return ErrorType.RATE_LIMIT; 178 } 179 180 if (message.includes('server') || message.includes('500')) { 181 return ErrorType.SERVER; 182 } 183 184 return ErrorType.UNKNOWN; 185 }