# Discovered Patterns: mixpanel-react-native

## Code Architecture Philosophy

This codebase demonstrates **graceful degradation** and **defensive programming** principles. The team prioritizes reliability over performance, with comprehensive fallback mechanisms and robust error handling throughout.

## 🏗️ Architectural Patterns

### 1. Singleton Pattern with Lazy Initialization

**Pattern**: Factory functions returning singleton instances for shared resources.

```javascript
// javascript/mixpanel-queue.js
export const MixpanelQueueManager = (() => {
  let _queues = {};
  let mixpanelPersistent;

  const getPersistent = () => {
    if (!mixpanelPersistent) {
      mixpanelPersistent = MixpanelPersistent.getInstance();
    }
    return mixpanelPersistent;
  };
  // ...
})();
```

**Motivation**: Ensures single source of truth for queue state and persistent storage while avoiding initialization costs until needed.

### 2. Factory Function Pattern for Dependency Injection

**Pattern**: Core modules accept dependencies via factory functions rather than direct imports.

```javascript
// javascript/mixpanel-core.js
export const MixpanelCore = (storage) => {
  const mixpanelPersistent = MixpanelPersistent.getInstance(storage);
  // ...
  return {
    initialize,
    startProcessingQueue,
    addToMixpanelQueue,
    flush,
    identifyUserQueue,
  };
};
```

**Motivation**: Enables testing with mock storage and supports different storage implementations (AsyncStorage vs. custom).

### 3. Token-Based Multi-Tenancy

**Pattern**: All operations include token parameter for instance isolation.

```javascript
// Every function signature includes token as first parameter
const flush = async (token) => { /* ... */ };
const track = async (token, eventName, properties) => { /* ... */ };

// Storage keys are token-scoped
export const getQueueKey = (token, type) => `MIXPANEL_${token}_${type}_QUEUE`;
```

**Motivation**: Supports multiple Mixpanel projects within single app without data leakage.

## 🎯 Naming Conventions

### File Naming
- **Kebab-case**: `mixpanel-core.js`, `mixpanel-queue.js`
- **Descriptive modules**: Each file represents a single responsibility
- **Prefix consistency**: All JavaScript modules start with `mixpanel-`

### Variable Naming
- **camelCase**: Standard JavaScript convention (`mixpanelImpl`, `trackAutomaticEvents`)
- **Constants**: SCREAMING_SNAKE_CASE (`DEFAULT_OPT_OUT`, `PARAMS.TOKEN`)
- **Private variables**: Leading underscore (`_queues`, `_shouldLog`)

### Function Naming
- **Verb-first**: `initialize`, `track`, `flush`, `addToMixpanelQueue`
- **Boolean predicates**: `isValidAndSerializable`, `hasOptedOutTracking`
- **Event handlers**: `handleBatchError`

## 📦 Import/Export Patterns

### Import Organization

```javascript
// External dependencies first
import { Platform } from "react-native";
import packageJson from "./package.json";

// Internal modules second, grouped by purpose
import { MixpanelCore } from "./mixpanel-core";
import { MixpanelType } from "./mixpanel-constants";
import { MixpanelConfig } from "./mixpanel-config";
```

### Export Strategies
- **Named exports for utilities**: `export class MixpanelLogger`
- **Default exports for main classes**: `export default class MixpanelMain`
- **Factory function exports**: `export const MixpanelCore = (storage) => {...}`

## 🛡️ Error Handling Philosophy

### 1. Comprehensive Input Validation

**Pattern**: Helper classes for parameter validation with descriptive errors.

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

// Usage
if (!StringHelper.isValid(distinctId)) {
  StringHelper.raiseError(PARAMS.DISTINCT_ID);
}
```

### 2. Graceful Degradation

**Pattern**: Try native implementation, fall back to JavaScript mode.

```javascript
// index.js
if (useNative && MixpanelReactNative) {
  this.mixpanelImpl = MixpanelReactNative;
  return;
} else if (useNative) {
  console.warn("MixpanelReactNative is not available; using JavaScript mode...");
}

this.mixpanelImpl = new MixpanelMain(token, trackAutomaticEvents, storage);
```

### 3. Safe Storage Operations

**Pattern**: Wrap all storage operations in try-catch with fallbacks.

```javascript
// javascript/mixpanel-storage.js
async getItem(key) {
  try {
    return await this.storage.getItem(key);
  } catch {
    MixpanelLogger.error("error getting item from storage");
    return null;
  }
}
```

#### Evolution [Updated: 2025-05-30]
Storage module resolution now handles both ES6 and CommonJS exports:

```javascript
const storageModule = require("@react-native-async-storage/async-storage");
if (storageModule.default) {
  this.storage = storageModule.default
} else {
  this.storage = storageModule
}
```

### 4. UUID Generation with Expo Compatibility [Updated: 2025-05-30]

**Pattern**: Try platform-specific method first, fallback to universal.

```javascript
// javascript/mixpanel-persistent.js
try {
  this._identity[token].deviceId = randomUUID(); // expo-crypto
} catch (e) {
  this._identity[token].deviceId = uuid.v4();    // uuid package
}
```

**Motivation**: Better Expo support while maintaining backward compatibility.

## 🔄 Async Patterns

### 1. Promise-Based API with Error Propagation

**Pattern**: Native methods return Promises, JavaScript methods async/await.

```javascript
// Native (iOS Swift)
@objc
func initialize(_ token: String, /* ... */) -> Void {
    // ...
    resolve(true)
}

// JavaScript wrapper
identify(distinctId) {
  return new Promise((resolve, reject) => {
    if (!StringHelper.isValid(distinctId)) {
      reject(new Error("Invalid distinctId"));
    }
    this.mixpanelImpl.identify(this.token, distinctId)
      .then(() => resolve())
      .catch((err) => reject(err));
  });
}
```

### 2. Retry Logic with Exponential Backoff

**Pattern**: Network requests implement sophisticated retry strategy.

```javascript
// javascript/mixpanel-network.js
const maxRetries = 5;
const backoff = Math.min(2 ** retryCount * 2000, 60000); // Exponential backoff
if (retryCount < maxRetries) {
  MixpanelLogger.log(token, `Retrying in ${backoff / 1000} seconds...`);
  await new Promise((resolve) => setTimeout(resolve, backoff));
  return sendRequest({ /* ... */, retryCount: retryCount + 1 });
}
```

## 🧪 Testing Patterns

### 1. Comprehensive Mocking Strategy

**Pattern**: Mock all external dependencies at the module level.

```javascript
// __tests__/jest_setup.js
jest.mock("expo-crypto", () => ({
  randomUUID: jest.fn(() => "mocked-uuid-string-" + Math.random().toString(36).substring(2, 15))
}));

jest.mock("@react-native-async-storage/async-storage", () => ({
  getItem: jest.fn().mockResolvedValue(null),
  setItem: jest.fn().mockResolvedValue(undefined),
  removeItem: jest.fn().mockResolvedValue(undefined),
}));
```

### 2. Test File Organization

**Pattern**: Mirror source structure with `.test.js` suffix.

```
__tests__/
├── core.test.js           # Tests javascript/mixpanel-core.js
├── network.test.js        # Tests javascript/mixpanel-network.js
├── queue.test.js          # Tests javascript/mixpanel-queue.js
└── jest_setup.js          # Global test configuration
```

## 📝 Documentation Patterns

### 1. JSDoc with Rich Examples

**Pattern**: Comprehensive JSDoc comments with usage examples.

```javascript
/**
 * Associate all future calls to track() with the user identified by
 * the given distinct id.
 *
 * @param {string} distinctId a string uniquely identifying this user
 * @returns {Promise} A promise that resolves when the identify is successful
 */
identify(distinctId) { /* ... */ }
```

### 2. Inline Comments for Complex Logic

**Pattern**: Explain "why" not "what" in comments.

```javascript
// Only update if there is a difference; minimize object copies
if (updateDistinctId || updateDeviceId || updateUserId) {
  updated = { ...record }; // shallow copy only if needed
  // ...
}
```

## 🔍 Logging Philosophy

### 1. Token-Scoped Conditional Logging

**Pattern**: All logs include token context and respect per-instance settings.

```javascript
// javascript/mixpanel-logger.js
static log(token, ...args) {
  if (MixpanelLogger._shouldLog(token)) {
    console.log(...MixpanelLogger._prependPrefix(args));
  }
}

// Usage with context
MixpanelLogger.log(token, `Track '${eventName}' with properties`, properties);
```

### 2. Structured Logging with Prefixes

**Pattern**: Consistent log format for easy filtering.

```javascript
static _prependPrefix(args) {
  return ["[Mixpanel]", ...args];
}
// Output: [Mixpanel] Track 'Plan Selected' with properties {...}
```

## 🚀 Performance Patterns

### 1. Batched Processing with Queues

**Pattern**: Collect events in memory, flush in configurable batches.

```javascript
// Process in batches to reduce network overhead
const batchSize = config.getFlushBatchSize(token);
const batch = queue.slice(0, batchSize);

// Recursive processing for large queues
if (queue.length > 0) {
  setTimeout(processBatch, 0); // Non-blocking recursion
}
```

### 2. Immutable State Updates

**Pattern**: Use spread operator for shallow copies, minimize object creation.

```javascript
// Only create new objects when necessary
const eventProperties = Object.freeze({
  token,
  time: Date.now(),
  ...this.getMetaData(),
  ...superProperties,
  ...properties,
  ...identityProps,
});
```

### 3. Conditional Property Inclusion [Updated: 2025-05-30]

**Pattern**: Use conditional spread to exclude null/undefined values.

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

**Motivation**: Cleaner API payloads and prevents backend issues with null values.

## 🎨 Code Style Insights

### Philosophy
- **Defensive coding**: Assume everything can fail
- **Explicit over implicit**: Clear parameter names and validation
- **Fail fast**: Validate inputs immediately
- **Progressive enhancement**: Native performance with JS fallback

### Trade-offs Made
- **Reliability over performance**: Multiple validation layers
- **Developer experience over bundle size**: Helpful error messages
- **Compatibility over simplicity**: Support both native and JS modes