npm package: @sdflc/barcode-lookup
Description: Resilient multi-source barcode and UPC product lookup library with intelligent caching and fallback strategies
Author: Your Name
Latest version: 1.0.0

# @sdflc/barcode-lookup

[![npm version](https://badge.fury.io/js/@sdflc%2Fbarcode-lookup.svg)](https://badge.fury.io/js/@sdflc%2Fbarcode-lookup)
[![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Resilient multi-source barcode and UPC product lookup library with intelligent caching, fallback strategies, and comprehensive error handling. Works in both Node.js and browser environments.

## 🚀 Features

- **Multi-Source Support** - Integrates with OpenFoodFacts, BarcodeLookup, UPCItemDB, and more
- **Intelligent Fallback** - Automatically tries multiple sources until one succeeds
- **Enhanced Error Handling** - Structured error information with retry logic and detailed diagnostics
- **Resilient Data Extraction** - Handles API changes gracefully with flexible field mapping
- **Smart Caching** - LRU cache with TTL to minimize API calls and improve performance
- **Configurable Retry Logic** - Automatic retries with exponential backoff for transient failures
- **TypeScript First** - Full type safety and excellent IDE support
- **Universal** - Works in Node.js, browsers, and edge environments
- **Production Ready** - Comprehensive monitoring, health checks, and debugging capabilities

## 📦 Installation

```bash
npm install @sdflc/barcode-lookup
```

## 🎯 Quick Start

```typescript
import {
  BarcodeManager,
  OpenFoodFactsApi,
  UPCItemDBApi,
} from '@sdflc/barcode-lookup';

// Create manager and register sources
const manager = new BarcodeManager();

// Register free APIs (no API key required)
manager.register('openfoodfacts', new OpenFoodFactsApi());
manager.register('upcitemdb', new UPCItemDBApi());

// Look up a product
const product = await manager.lookup('3017620422003'); // Nutella

if (product) {
  console.log(`Product: ${product.name}`);
  console.log(`Brand: ${product.brand}`);
  console.log(`Source: ${product.sourceApi}`);
}
```

## 🔧 Enhanced Error Handling Example

```typescript
import {
  BarcodeManager,
  OpenFoodFactsApi,
  UPCItemDBApi,
} from '@sdflc/barcode-lookup';

async function robustLookup() {
  const manager = new BarcodeManager();

  // Configure retry behavior
  manager.setRetryEnabled(true);
  manager.setMaxRetries(3);

  // Register sources
  manager.register('openfoodfacts', new OpenFoodFactsApi());
  manager.register('upcitemdb', new UPCItemDBApi());

  // Use enhanced lookup for detailed error information
  const result = await manager.lookupWithDetails('3017620422003');

  if (result.product) {
    console.log('Success:', {
      name: result.product.name,
      source: result.product.sourceApi,
      attempts: result.attempts,
      fromCache: result.fromCache,
    });
  } else if (result.error) {
    console.log('Failed:', {
      message: result.error.message,
      code: result.error.code,
      retryable: result.error.retryable,
      attempts: result.attempts,
    });

    // Implement custom retry logic if needed
    if (result.error.retryable && result.error.retryAfter) {
      console.log(`Can retry after ${result.error.retryAfter} seconds`);
    }
  }

  return result;
}
```

## 🎛️ API Reference

### BarcodeManager

Main class for managing multiple barcode lookup sources with enhanced error handling.

#### Configuration Methods

- `register(name: string, source: BarcodeLookupApiSource, apiKey?: string)` - Register a new API source
- `selectSource(source: string)` - Select which source to use for lookups
- `setRetryEnabled(enabled: boolean)` - Enable/disable automatic retry logic
- `setMaxRetries(count: number)` - Set maximum retry attempts for failed requests
- `setCache(size: number)` - Set cache size (0 to disable)

#### Lookup Methods

- `lookup(upc: string): Promise<LookupResult>` - Simple lookup using current source
- `lookupWithDetails(upc: string): Promise<LookupResult>` - Enhanced lookup with detailed error information
- `lookupFromAllSources(upc: string): Promise<LookupResult>` - Try all sources until one succeeds

#### Monitoring & Management

- `clearCache()` - Clear all cached items
- `getCacheStats()` - Get cache statistics
- `getRegisteredSources(): string[]` - Get list of registered source names
- `getCurrentSource(): string | null` - Get currently selected source
- `healthCheck(sourceName?: string): Promise<HealthCheckResult>` - Check health of specific source
- `healthCheckAll(): Promise<HealthCheckResult[]>` - Check health of all sources
- `getUsageStats()` - Get comprehensive usage and configuration statistics

#### Error Management

- `getSourceLastError(sourceName?: string): BarcodeLookupApiError | null` - Get last error from specific source
- `clearSourceError(sourceName?: string): void` - Clear error state for specific source

### Enhanced Interfaces

#### LookupResult

```typescript
interface LookupResult {
  product: ProductData | null;
  source?: string;
  fromCache: boolean;
  attempts: number;
  error?: BarcodeLookupApiError;
}
```

#### BarcodeLookupApiError

```typescript
interface BarcodeLookupApiError {
  code: BarcodeLookupErrorCode;
  message: string;
  source: string;
  statusCode?: number;
  retryable: boolean;
  retryAfter?: number; // seconds to wait before retry
  timestamp: number;
  details?: {
    upc?: string;
    query?: string;
    originalError?: string;
    [key: string]: any;
  };
}
```

#### HealthCheckResult

```typescript
interface HealthCheckResult {
  source: string;
  status: 'healthy' | 'degraded' | 'unhealthy';
  responseTime?: number;
  error?: string;
  lastError?: BarcodeLookupApiError;
}
```

### API Sources

#### OpenFoodFactsApi

Free API for food products with rich nutrition data and enhanced error handling.

```typescript
const api = new OpenFoodFactsApi();
manager.register('openfoodfacts', api);

// Check for errors after lookup
const result = await manager.lookupWithDetails('123456789');
if (result.error) {
  console.log(`OpenFoodFacts error: ${result.error.message}`);

  // Get structured error information
  const lastError = api.getLastError();
  if (lastError?.retryable) {
    console.log('This error can be retried');
  }
}
```

**Features:**

- No API key required
- Structured error handling with retry logic
- Excellent for food products
- Nutrition facts and ingredients
- Multiple languages supported
- Search functionality
- Comprehensive error diagnostics

#### BarcodeLookupApi

Commercial API with comprehensive product database and full error handling.

```typescript
const api = new BarcodeLookupApi('your-api-key');
manager.register('barcodelookup', api);

// Enhanced lookup with error handling
const result = await manager.lookupWithDetails('012000161155');

if (result.product) {
  // Access BarcodeLookup-specific features
  const stores = api.getStoreInfo(result.product);
  const reviews = api.getReviews(result.product);
  const specs = api.getSpecifications(result.product);

  stores.forEach(store => {
    console.log(`${store.name}: ${store.currency}${store.price}`);
  });
} else if (result.error) {
  console.log(`API Error: ${result.error.code} - ${result.error.message}`);

  if (result.error.code === 'RATE_LIMIT_EXCEEDED') {
    console.log(`Rate limited. Retry after ${result.error.retryAfter} seconds`);
  }
}
```

#### UPCItemDBApi

Free tier API with pricing focus and enhanced error handling.

```typescript
const api = new UPCItemDBApi(); // Free tier
// or with premium key:
const api = new UPCItemDBApi('your-premium-key');
manager.register('upcitemdb', api);

// Handle API-specific errors
const result = await manager.lookupWithDetails('123456789');
if (result.error?.code === 'UNAUTHORIZED') {
  console.log('API key invalid or missing');
}
```

**Features:**

- Free tier available with structured error handling
- Premium tier with enhanced features
- Multiple merchant offers
- Price history tracking
- Rate limit handling
- Detailed error diagnostics

### ProductData Interface

```typescript
interface ProductData {
  upc: string; // Product UPC/EAN code
  name: string; // Product name (required)
  brand?: string; // Brand name
  category?: string; // Product category
  description?: string; // Product description
  imageUrl?: string; // Main product image
  ingredients?: string[]; // List of ingredients
  nutritionFacts?: object; // Nutrition information
  weight?: string; // Product weight
  dimensions?: string; // Product dimensions
  price?: number; // Current price
  currency?: string; // Price currency
  manufacturer?: string; // Manufacturer name
  countryOfOrigin?: string; // Country of origin
  sourceApi: string; // Which API provided the data
  rawData?: unknown; // Original API response
}
```

## 🔌 Creating Custom Sources

Create custom API sources with full error handling support:

```typescript
import {
  BarcodeLookupApiSource,
  ProductData,
  BarcodeLookupApiError,
  createBarcodeLookupApiError,
  BarcodeLookupErrorCode,
  safeGet,
  safeString,
} from '@sdflc/barcode-lookup';

class MyCustomApi implements BarcodeLookupApiSource {
  name = 'MyCustomAPI';
  apiKey?: string;
  private lastError: BarcodeLookupApiError | null = null;

  constructor(apiKey?: string) {
    this.apiKey = apiKey;
  }

  // Required error handling methods
  getLastError(): BarcodeLookupApiError | null {
    return this.lastError;
  }

  clearLastError(): void {
    this.lastError = null;
  }

  isLastErrorRetryable(): boolean {
    return this.lastError ? this.lastError.retryable : false;
  }

  getRetryDelay(): number {
    return this.lastError?.retryAfter || 60;
  }

  async lookup(upc: string): Promise<ProductData | null> {
    this.clearLastError();

    try {
      const response = await fetch(`https://my-api.com/products/${upc}`, {
        headers: {
          Authorization: `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json',
        },
      });

      if (!response.ok) {
        this.handleHttpError(response, upc);
        return null;
      }

      const data = await response.json();
      return this.transformToProductData(data, upc);
    } catch (error) {
      this.handleNetworkError(error, upc);
      return null;
    }
  }

  private handleHttpError(response: Response, upc: string): void {
    const statusCode = response.status;

    switch (statusCode) {
      case 401:
        this.lastError = createBarcodeLookupApiError(
          BarcodeLookupErrorCode.UNAUTHORIZED,
          'Invalid API key',
          this.name,
          { statusCode, retryable: false, details: { upc } }
        );
        break;

      case 429:
        const retryAfter = response.headers.get('Retry-After');
        this.lastError = createBarcodeLookupApiError(
          BarcodeLookupErrorCode.RATE_LIMIT_EXCEEDED,
          'Rate limit exceeded',
          this.name,
          {
            statusCode,
            retryable: true,
            retryAfter: retryAfter ? parseInt(retryAfter) : 60,
            details: { upc },
          }
        );
        break;

      default:
        this.lastError = createBarcodeLookupApiError(
          statusCode >= 500
            ? BarcodeLookupErrorCode.SERVER_ERROR
            : BarcodeLookupErrorCode.API_ERROR,
          `HTTP ${statusCode}`,
          this.name,
          { statusCode, retryable: statusCode >= 500, details: { upc } }
        );
    }
  }

  private handleNetworkError(error: any, upc: string): void {
    this.lastError = createBarcodeLookupApiError(
      BarcodeLookupErrorCode.NETWORK_ERROR,
      `Network error: ${error.message}`,
      this.name,
      { retryable: true, details: { upc, originalError: error.message } }
    );
  }

  private transformToProductData(
    apiData: any,
    upc: string
  ): ProductData | null {
    const name = safeString(apiData, ['product_name', 'title', 'name']);

    if (!name) {
      this.lastError = createBarcodeLookupApiError(
        BarcodeLookupErrorCode.DATA_TRANSFORMATION_ERROR,
        'No product name found',
        this.name,
        { retryable: false, details: { upc } }
      );
      return null;
    }

    return {
      upc,
      name,
      brand: safeString(apiData, ['brand', 'manufacturer']),
      sourceApi: this.name,
      rawData: apiData,
    };
  }
}

// Usage with full error handling
const customApi = new MyCustomApi('your-api-key');
manager.register('mycustom', customApi);

const result = await manager.lookupWithDetails('123456789');
if (result.error) {
  console.log(
    `Custom API Error: ${result.error.code} - ${result.error.message}`
  );
}
```

## 🛡️ Comprehensive Error Handling

The library provides structured error handling throughout:

```typescript
// Simple error handling
try {
  const product = await manager.lookup('123456789');
  if (product) {
    console.log(product.name);
  } else {
    console.log('Product not found');
  }
} catch (error) {
  console.error('Lookup failed:', error.message);
}

// Detailed error handling
const result = await manager.lookupWithDetails('123456789');

if (result.product) {
  console.log('Success:', result.product.name);
} else if (result.error) {
  console.log('Failed with structured error:');
  console.log(`- Code: ${result.error.code}`);
  console.log(`- Message: ${result.error.message}`);
  console.log(`- Source: ${result.error.source}`);
  console.log(`- Retryable: ${result.error.retryable}`);
  console.log(`- Attempts: ${result.attempts}`);

  if (result.error.retryAfter) {
    console.log(`- Retry after: ${result.error.retryAfter} seconds`);
  }

  // Handle specific error types
  switch (result.error.code) {
    case 'RATE_LIMIT_EXCEEDED':
      console.log('Rate limited - implement exponential backoff');
      break;
    case 'INVALID_API_KEY':
      console.log('Check API key configuration');
      break;
    case 'NETWORK_ERROR':
      console.log('Network issue - check connectivity');
      break;
  }
}
```

## ⚡ Performance & Caching

Enhanced caching with monitoring capabilities:

```typescript
// Configure cache and retry behavior
const manager = new BarcodeManager({
  cacheSize: 200,
  cacheTtl: 4 * 60 * 60 * 1000, // 4 hours
});

manager.setRetryEnabled(true);
manager.setMaxRetries(3);

// Monitor performance
const result1 = await manager.lookupWithDetails('123456789'); // API call
const result2 = await manager.lookupWithDetails('123456789'); // Cache hit

console.log('Performance comparison:');
console.log(
  `First lookup: ${result1.attempts} attempts, cache: ${result1.fromCache}`
);
console.log(
  `Second lookup: ${result2.attempts} attempts, cache: ${result2.fromCache}`
);

// Get comprehensive statistics
const stats = manager.getUsageStats();
console.log('Usage stats:', {
  sources: stats.totalSources,
  cache: `${stats.cacheStats.size}/${stats.cacheStats.maxSize}`,
  retryEnabled: stats.retryEnabled,
  maxRetries: stats.maxRetries,
});
```

## 🏥 Health Monitoring

Monitor API health and error states:

```typescript
// Check health of all sources
const healthResults = await manager.healthCheckAll();

healthResults.forEach(result => {
  console.log(`${result.source}: ${result.status}`);

  if (result.responseTime) {
    console.log(`  Response time: ${result.responseTime}ms`);
  }

  if (result.lastError) {
    console.log(
      `  Last error: ${result.lastError.code} - ${result.lastError.message}`
    );
  }
});

// Monitor error states
const sourcesInfo = manager.getSourcesInfo();
sourcesInfo.forEach(source => {
  console.log(`${source.name}:`);
  console.log(
    `  Error handling: ${source.supportsErrorHandling ? 'Enhanced' : 'Basic'}`
  );
  console.log(`  Cached items: ${source.cachedItems}`);

  if (source.lastError) {
    console.log(`  Current error: ${source.lastError.message}`);
  }
});
```

## 🌐 Browser Support

Works in modern browsers with enhanced error handling:

```html
<script type="module">
  import {
    BarcodeManager,
    OpenFoodFactsApi,
  } from 'https://unpkg.com/@sdflc/barcode-lookup/dist/index.mjs';

  const manager = new BarcodeManager();
  manager.register('openfoodfacts', new OpenFoodFactsApi());

  // Use enhanced lookup in browser
  const result = await manager.lookupWithDetails('3017620422003');

  if (result.product) {
    console.log('Product found:', result.product.name);
  } else if (result.error) {
    console.error('Error:', result.error.message);

    // Handle browser-specific network errors
    if (result.error.code === 'NETWORK_ERROR') {
      console.log('Check internet connection');
    }
  }
</script>
```

## 🔧 Advanced Configuration

### Environment Setup with Error Handling

```env
# API Keys
BARCODE_LOOKUP_API_KEY=your-api-key
UPCITEMDB_PREMIUM_KEY=your-premium-key

# Cache Configuration
BARCODE_LOOKUP_DEFAULT_CACHE_SIZE=200
BARCODE_LOOKUP_DEFAULT_CACHE_TTL=86400000

# Retry Configuration
BARCODE_LOOKUP_ENABLE_RETRIES=true
BARCODE_LOOKUP_MAX_RETRIES=3
```

```typescript
import dotenv from 'dotenv';
dotenv.config();

const manager = new BarcodeManager({
  cacheSize: parseInt(process.env.BARCODE_LOOKUP_DEFAULT_CACHE_SIZE || '100'),
  cacheTtl: parseInt(
    process.env.BARCODE_LOOKUP_DEFAULT_CACHE_TTL || '86400000'
  ),
});

// Configure retry behavior from environment
manager.setRetryEnabled(process.env.BARCODE_LOOKUP_ENABLE_RETRIES === 'true');
manager.setMaxRetries(parseInt(process.env.BARCODE_LOOKUP_MAX_RETRIES || '2'));
```

### Production Monitoring

```typescript
// Production-ready error monitoring
async function productionLookup(upc: string) {
  const result = await manager.lookupWithDetails(upc);

  // Log metrics for monitoring
  console.log(
    JSON.stringify({
      timestamp: new Date().toISOString(),
      upc,
      success: !!result.product,
      source: result.source,
      attempts: result.attempts,
      fromCache: result.fromCache,
      error: result.error
        ? {
            code: result.error.code,
            message: result.error.message,
            retryable: result.error.retryable,
          }
        : null,
    })
  );

  return result;
}

// Health check endpoint for monitoring
app.get('/health/barcode-apis', async (req, res) => {
  const health = await manager.healthCheckAll();
  const overallHealth = health.every(h => h.status === 'healthy')
    ? 'healthy'
    : 'degraded';

  res.json({
    status: overallHealth,
    sources: health,
    timestamp: new Date().toISOString(),
  });
});
```

## 📋 Requirements

- Node.js 16+ (for Node.js usage)
- Modern browser with fetch support (for browser usage)
- TypeScript 4.5+ (for TypeScript projects)

## 🤝 Contributing

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## 📄 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## 🆘 Support

- 📚 [Documentation](https://github.com/yourusername/sdflc-barcode-lookup#readme)
- 🐛 [Issue Tracker](https://github.com/yourusername/sdflc-barcode-lookup/issues)
- 💬 [Discussions](https://github.com/yourusername/sdflc-barcode-lookup/discussions)

## 🙏 Acknowledgments

- [OpenFoodFacts](https://openfoodfacts.org/) - Amazing open food database
- [BarcodeLookup.com](https://www.barcodelookup.com/) - Comprehensive product database
- [UPCitemdb.com](https://www.upcitemdb.com/) - UPC database with pricing

---

**Made with ❤️ for developers who need reliable barcode lookups with comprehensive error handling**