/** * MemoryStack React Native SDK Error Classes * Provides typed error handling for all SDK operations */ /** * Base error class for MemoryStack SDK */ export class MemoryStackError extends Error { public readonly statusCode?: number; public readonly details?: unknown; public readonly requestId?: string; constructor(message: string, statusCode?: number, details?: unknown, requestId?: string) { super(message); this.name = 'MemoryStackError'; this.statusCode = statusCode; this.details = details; this.requestId = requestId; // Maintains proper stack trace for where our error was thrown (only available on V8) if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } } /** * Convert error to a plain object for logging/serialization */ toJSON(): Record { return { name: this.name, message: this.message, statusCode: this.statusCode, details: this.details, requestId: this.requestId, }; } } /** * Authentication error (401) * Thrown when API key is invalid or missing */ export class AuthenticationError extends MemoryStackError { constructor(message: string = 'Invalid or missing API key', details?: unknown) { super(message, 401, details); this.name = 'AuthenticationError'; } } /** * Rate limit exceeded error (429) * Includes rate limit headers for retry logic */ export class RateLimitError extends MemoryStackError { public readonly limit?: number; public readonly remaining?: number; public readonly reset?: number; public readonly retryAfter?: number; constructor( message: string = 'Rate limit exceeded', limit?: number, remaining?: number, reset?: number, retryAfter?: number ) { super(message, 429); this.name = 'RateLimitError'; this.limit = limit; this.remaining = remaining; this.reset = reset; this.retryAfter = retryAfter; } /** * Get the recommended wait time in milliseconds before retrying */ getRetryDelayMs(): number { if (this.retryAfter) { return this.retryAfter * 1000; } if (this.reset) { const now = Math.floor(Date.now() / 1000); return Math.max(0, (this.reset - now) * 1000); } return 60000; // Default to 1 minute } } /** * Validation error (400) * Thrown when request parameters are invalid */ export class ValidationError extends MemoryStackError { public readonly field?: string; constructor(message: string, details?: unknown, field?: string) { super(message, 400, details); this.name = 'ValidationError'; this.field = field; } } /** * Not found error (404) * Thrown when a resource doesn't exist */ export class NotFoundError extends MemoryStackError { public readonly resourceId?: string; public readonly resourceType?: string; constructor(message: string = 'Resource not found', resourceId?: string, resourceType?: string) { super(message, 404); this.name = 'NotFoundError'; this.resourceId = resourceId; this.resourceType = resourceType; } } /** * Network error * Thrown when network request fails (no response received) */ export class NetworkError extends MemoryStackError { public readonly originalError?: Error; public readonly isTimeout: boolean; public readonly isOffline: boolean; constructor( message: string = 'Network request failed', originalError?: Error, isTimeout: boolean = false, isOffline: boolean = false ) { super(message, undefined, originalError?.message); this.name = 'NetworkError'; this.originalError = originalError; this.isTimeout = isTimeout; this.isOffline = isOffline; } } /** * Offline error (React Native specific) * Thrown when operation requires network but device is offline */ export class OfflineError extends MemoryStackError { public readonly operationType: string; public readonly queuedForSync: boolean; constructor( message: string = 'Device is offline', operationType: string = 'unknown', queuedForSync: boolean = false ) { super(message); this.name = 'OfflineError'; this.operationType = operationType; this.queuedForSync = queuedForSync; } } /** * Server error (5xx) * Thrown when server encounters an error */ export class ServerError extends MemoryStackError { constructor(message: string = 'Server error', statusCode: number = 500, details?: unknown) { super(message, statusCode, details); this.name = 'ServerError'; } } /** * Conflict error (409) * Thrown when there's a conflict with the current state of the resource */ export class ConflictError extends MemoryStackError { constructor(message: string = 'Resource conflict', details?: unknown) { super(message, 409, details); this.name = 'ConflictError'; } } /** * Helper function to check if an error is a MemoryStack error */ export function isMemoryStackError(error: unknown): error is MemoryStackError { return error instanceof MemoryStackError; } /** * Helper function to check if error is retryable */ export function isRetryableError(error: unknown): boolean { if (error instanceof NetworkError && !error.isOffline) { return true; } if (error instanceof RateLimitError) { return true; } if (error instanceof ServerError) { return true; } if (error instanceof MemoryStackError) { const retryableCodes = [408, 429, 500, 502, 503, 504]; return error.statusCode !== undefined && retryableCodes.includes(error.statusCode); } return false; }