---
description: Comprehensive error handling patterns and defensive programming principles
alwaysApply: true
---

# Error Handling Patterns

## Input Validation (MANDATORY)
EVERY public method MUST validate ALL parameters using Helper classes:

### String Validation Pattern
```javascript
// ALWAYS use StringHelper for string parameters
if (!StringHelper.isValid(eventName)) {
  StringHelper.raiseError(PARAMS.EVENT_NAME);
}

// StringHelper implementation (use this pattern):
class StringHelper {
  static isValid(str) {
    return typeof str === "string" && !/^\s*$/.test(str);
  }
  
  static raiseError(paramName) {
    throw new Error(`${paramName}${ERROR_MESSAGE.INVALID_STRING}`);
  }
}
```

### Object Validation Pattern
```javascript
// ALWAYS use ObjectHelper for object parameters
if (!ObjectHelper.isValidOrUndefined(properties)) {
  ObjectHelper.raiseError(PARAMS.PROPERTIES);
}

// Check for serializable objects (no functions, symbols, etc.)
if (!ObjectHelper.isValidAndSerializable(properties)) {
  throw new Error("Properties must be serializable");
}
```

## Silent Failure Pattern (CRITICAL)
Storage and network errors MUST NOT crash the app - log and continue:

### Storage Operations
```javascript
// ALWAYS wrap storage operations
async getItem(key) {
  try {
    return await this.storage.getItem(key);
  } catch {
    MixpanelLogger.error("error getting item from storage");
    return null;  // ALWAYS return usable default
  }
}

async setItem(key, value) {
  try {
    await this.storage.setItem(key, value);
  } catch {
    MixpanelLogger.error("error setting item in storage");
    // Silent failure - continue operation
  }
}
```

### Network Operations
```javascript
// ALWAYS include retry logic with exponential backoff
const sendRequest = async ({...params, retryCount = 0}) => {
  try {
    const response = await fetch(url, options);
    return response;
  } catch (error) {
    if (retryCount < maxRetries) {
      const backoff = Math.min(2 ** retryCount * 2000, 60000);
      MixpanelLogger.log(token, `Retrying in ${backoff / 1000} seconds...`);
      await new Promise(resolve => setTimeout(resolve, backoff));
      return sendRequest({...params, retryCount: retryCount + 1});
    }
    
    MixpanelLogger.error(`Network request failed after ${maxRetries} retries`);
    throw error; // Only throw after all retries exhausted
  }
};
```

## Opt-Out Respect (MANDATORY)
ALWAYS check opt-out status before ANY data operation:

```javascript
// Check opt-out for ALL tracking operations
if (this.mixpanelPersistent.getOptedOut(token)) {
  MixpanelLogger.log(token, `User has opted out, skipping ${operation}`);
  return; // Silent return, don't throw error
}
```

## Null Safety Patterns
ALWAYS provide sensible defaults for missing data:

```javascript
// Correct: Default to empty object/array
const superProperties = this._superProperties[token] || {};
const queue = await this.loadQueue(token, type) || [];

// Correct: Check existence before access
if (!this._identity[token]) {
  this._identity[token] = {};
}

// Correct: Default values in destructuring
const { distinctId = null, deviceId = null } = this._identity[token] || {};
```

### Conditional Property Inclusion [Updated: 2025-05-30]
ALWAYS use conditional spread to exclude null/undefined values from API payloads:

✅ **Correct Pattern**:
```javascript
// Prevents sending null values to Mixpanel API
const profileData = {
  $token: token,
  $time: Date.now(),
  ...action,
  ...(distinctId != null && { $distinct_id: distinctId }),
  ...(deviceId != null && { $device_id: deviceId }),
  ...(userId != null && { $user_id: userId }),
};

// General pattern for any API payload
const payload = {
  ...requiredFields,
  ...(optionalValue != null && { optionalKey: optionalValue }),
  ...(anotherValue != null && { anotherKey: anotherValue }),
};
```

❌ **Incorrect - Sends null values**:
```javascript
// This sends null values which can cause API issues
const profileData = {
  $token: token,
  $time: Date.now(),
  ...action,
  $distinct_id: distinctId,  // Could be null
  $device_id: deviceId,      // Could be null
  $user_id: userId           // Could be null
};
```

## Error Propagation Strategy
- **Public API**: Throw descriptive errors for invalid inputs
- **Internal operations**: Log errors, continue with defaults
- **Network failures**: Retry with backoff, then log and continue
- **Storage failures**: Fall back to in-memory, continue operation

## Defensive Programming Principles

### 1. Fail Fast for Invalid Inputs
```javascript
// Validate immediately at API boundary
track(eventName, properties) {
  if (!StringHelper.isValid(eventName)) {
    StringHelper.raiseError(PARAMS.EVENT_NAME); // Throw immediately
  }
  // ... continue with valid input
}
```

### 2. Graceful Degradation for Infrastructure
```javascript
// Try preferred method, fall back gracefully
try {
  deviceId = randomUUID(); // Expo crypto (preferred)
} catch (e) {
  deviceId = uuid.v4();    // uuid package (fallback)
}
```

### ⚠️ Evolution Notice [Updated: 2025-05-30]
UUID generation now prefers expo-crypto for better Expo compatibility:

✅ **Current Best Practice**:
```javascript
// Import both options at module level
import { randomUUID } from "expo-crypto";
import uuid from "uuid";

// Use try-catch pattern for UUID generation
async loadDeviceId(token) {
  if (!this._identity[token].deviceId) {
    try {
      this._identity[token].deviceId = randomUUID(); // Expo-friendly
    } catch (e) {
      this._identity[token].deviceId = uuid.v4();    // Universal fallback
    }
  }
}
```

### 3. Comprehensive Logging for Debugging
```javascript
// Log ALL error conditions with context
MixpanelLogger.error(token, `Failed to process batch: ${error.message}`, {
  batchSize: batch.length,
  queueType: type,
  retryCount: retryCount
});
```

### 4. Safe JSON Operations
```javascript
// ALWAYS handle JSON parse/stringify errors
const parseStoredData = (jsonString) => {
  try {
    return JSON.parse(jsonString);
  } catch {
    MixpanelLogger.error("Corrupted data in storage, using defaults");
    return {}; // Return sensible default
  }
};
```

## Error Message Standards
- Include parameter name in validation errors
- Use consistent error prefixes: "[Mixpanel] Error: ..."
- Provide actionable information when possible
- Never expose internal implementation details in public errors