npm package: @refine-ai/sdk
Description: Official JavaScript SDK for Refine — AI-powered search, recommendations, and analytics for e-commerce
Author: Refine
Latest version: 0.1.1

# Refine SDK

Official JavaScript/TypeScript SDK for the Refine API — AI-powered search, recommendations, and analytics for e-commerce.

[![npm version](https://img.shields.io/npm/v/@refine-ai/sdk.svg)](https://www.npmjs.com/package/@refine-ai/sdk)
[![npm downloads](https://img.shields.io/npm/dm/@refine-ai/sdk.svg)](https://www.npmjs.com/package/@refine-ai/sdk)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Bundle Size](https://img.shields.io/bundlephobia/minzip/@refine-ai/sdk)](https://bundlephobia.com/package/@refine-ai/sdk)

## Features

- 🔍 **Search** — Text and image-based product search with filters and sorting
- 🎯 **Recommendations** — Similar items, visitor-based, and user-based personalization
- 📊 **Analytics** — Comprehensive event tracking with state management
- 🔒 **Enterprise-Ready** — Retry logic, offline persistence, consent management
- 📦 **Tree-Shakeable** — Import only what you need
- 🔷 **TypeScript** — Full type safety with comprehensive definitions

---

## Installation

```bash
npm install @refine-ai/sdk
```

```bash
yarn add @refine-ai/sdk
```

```bash
pnpm add @refine-ai/sdk
```

---

## Quick Start

```typescript
import { Refine } from '@refine-ai/sdk';

// Initialize the SDK
const refine = new Refine({
  apiKey: 'your-api-key',
  organizationId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
  catalogId: 'b2c3d4e5-f6a7-8901-bcde-f12345678901'
});

// Identify user (optional)
refine.identify('user_12345');

// Search for products
const results = await refine.search.text({
  query: 'red running shoes',
  topK: 20,
  filters: [
    { field: 'price', operator: 'lte', value: 150 }
  ]
});

// Track the search results
const searchContext = refine.events.trackSearch(
  'red running shoes',
  results.results,
  { surface: 'search_results' }
);

// Track user interaction
searchContext.trackClick('sku_123', 2);
```

---

## Configuration

```typescript
const refine = new Refine({
  // Required
  apiKey: 'your-api-key',
  organizationId: 'org-uuid',
  catalogId: 'catalog-uuid',
  
  // Optional
  applicationType: 'web',                  // 'web' | 'ios' | 'android' | 'agent' | 'other'
  debug: false,                            // Enable debug logging
  
  // Events configuration
  events: {
    enabled: true,                         // Enable event tracking
    batchSize: 10,                         // Events per batch
    flushInterval: 5000,                   // Flush interval (ms)
    maxQueueSize: 1000,                    // Max queued events
    maxRetries: 3,                         // Max retry attempts
    persistence: 'localStorage',           // 'localStorage' | 'memory' | 'none'
    sessionTimeout: 30 * 60 * 1000         // Session timeout (30 min)
  },
  
  // Privacy settings
  privacy: {
    respectDoNotTrack: false,              // Respect browser DNT
    anonymizeIp: false,                    // Anonymize IP addresses
    requireConsent: false                  // Require explicit consent
  },
  
  // Callbacks
  onError: (error) => console.error(error)
});
```

---

## Search

### Text Search

```typescript
const results = await refine.search.text({
  query: 'summer dress',
  topK: 24,
  visualWeight: 0.3,                       // 0.0 = text only, 1.0 = visual only
  filters: [
    { field: 'price', operator: 'gte', value: 50 },
    { field: 'price', operator: 'lte', value: 200 },
    { field: 'metadata.color', operator: 'in', value: ['red', 'blue'] },
    { field: 'stock', operator: 'gt', value: 0 }
  ],
  sortBy: {
    field: 'price',
    order: 'ascending'
  }
});

// Access results
results.results.forEach(product => {
  console.log(`${product.title} - $${product.price}`);
});

// Get filter options for UI
results.filterOptions?.forEach(filter => {
  console.log(`${filter.name}: ${filter.values?.join(', ')}`);
});
```

### Image Search

```typescript
// With File input
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];

const results = await refine.search.image({
  image: file,
  topK: 12,
  filters: [
    { field: 'stock', operator: 'gt', value: 0 }
  ]
});

// With Blob
const results = await refine.search.image({
  image: imageBlob,
  topK: 12
});

// With base64
const results = await refine.search.image({
  image: 'data:image/jpeg;base64,/9j/4AAQ...',
  topK: 12
});
```

### Filter Operators

| Operator | Description | Example |
|----------|-------------|---------|
| `eq` | Equal | `{ field: 'brand', operator: 'eq', value: 'Nike' }` |
| `ne` | Not equal | `{ field: 'brand', operator: 'ne', value: 'Generic' }` |
| `gt` | Greater than | `{ field: 'price', operator: 'gt', value: 50 }` |
| `gte` | Greater than or equal | `{ field: 'price', operator: 'gte', value: 50 }` |
| `lt` | Less than | `{ field: 'price', operator: 'lt', value: 200 }` |
| `lte` | Less than or equal | `{ field: 'price', operator: 'lte', value: 200 }` |
| `in` | In array | `{ field: 'size', operator: 'in', value: ['S', 'M', 'L'] }` |
| `nin` | Not in array | `{ field: 'color', operator: 'nin', value: ['black'] }` |
| `exists` | Field exists | `{ field: 'discount', operator: 'exists', value: true }` |
| `match` | Text match | `{ field: 'title', operator: 'match', value: 'summer' }` |

---

## Recommendations

### Similar Items

Find products similar to a given product:

```typescript
const recs = await refine.recs.similarItems({
  anchorId: 'product-sku-123',
  configId: 'similar-items-config-uuid',
  topK: 8,
  visualWeight: 0.7,                       // Visual similarity weight
  includeMetadata: true,
  filters: [
    { field: 'stock', operator: 'gt', value: 0 }
  ]
});

recs.results?.forEach(product => {
  console.log(product.productId, product.metadata);
});
```

### Visitor Recommendations

Personalized recommendations for anonymous visitors:

```typescript
const recs = await refine.recs.forVisitor({
  configId: 'visitor-recs-config-uuid',
  topK: 12,
  // visitorId is automatically populated from the SDK
});
```

### User Recommendations

Personalized recommendations for authenticated users:

```typescript
// Make sure user is identified first
refine.identify('user_12345');

const recs = await refine.recs.forUser({
  configId: 'user-recs-config-uuid',
  topK: 12
});
```

---

## Product Listing Pages (PLP)

Retrieve curated, merchandised product listings:

```typescript
const plp = await refine.plp.get('plp-config-uuid');

console.log(`${plp.name}: ${plp.products.length} products`);

plp.products.forEach(product => {
  console.log(`${product.title} - $${product.price}`);
  if (product.isPinned) {
    console.log('  (Pinned by merchandiser)');
  }
});

// Get pinned products
const pinnedProducts = plp.getPinnedProducts();
```

---

## Event Tracking

### Tracking Flow

The SDK uses a **ServeContext** pattern to link item exposures to user interactions:

```typescript
// 1. Track items served (search results, recommendations, etc.)
const context = refine.events.trackItemsServed({
  surface: 'search_results',
  source: 'text-search',
  itemIds: ['sku_1', 'sku_2', 'sku_3'],
  query: 'running shoes',
  totalResults: 847
});

// 2. Track interactions using the context
context.trackClick('sku_2', 2);    // Item clicked at position 2
context.trackView('sku_1', 1);     // Item viewed at position 1
context.trackAddToCart('sku_2');   // Item added to cart
```

### Convenience Methods

```typescript
// Track search with automatic ITEMS_SERVED event
const searchContext = refine.events.trackSearch(
  'running shoes',                         // Query
  searchResults.results,                   // Results array
  {
    surface: 'search_results',
    totalResults: 847,
    filters: activeFilters
  }
);

// Track recommendations
const recsContext = refine.events.trackRecommendations(
  response.serveId,
  response.results,
  'pdp_similar',
  'similar-items-recs',
  { anchor: 'product-123' }
);

// Standalone interactions
refine.events.trackClick('sku_123', context, 2);
refine.events.trackView('sku_123');
refine.events.trackAddToCart('sku_123', 2);  // quantity: 2
refine.events.trackRemoveFromCart('sku_123', 1);
```

### Conversions

```typescript
// Track a purchase
refine.events.trackPurchase({
  orderId: 'order_12345',
  value: 159.98,
  currency: 'USD',
  items: [
    { itemId: 'sku_001', quantity: 2, unitPrice: 79.99 }
  ]
});

// Track other conversions
refine.events.trackConversion({
  conversionType: 'signup',    // 'purchase' | 'booking' | 'signup' | 'lead' | 'subscription' | 'custom'
  conversionId: 'signup_123'
});

// Flush events before navigation
await refine.events.flush();
```

### Event Types

| Event | Description |
|-------|-------------|
| `ITEMS_SERVED` | Items shown to user (search results, recommendations) |
| `ITEM_INTERACTION` | User interaction (click, view, add_to_cart, etc.) |
| `CONVERSION` | Business outcome (purchase, signup, booking) |

### Interaction Types

`view` · `click` · `save` · `add_to_cart` · `remove_from_cart` · `like` · `dislike` · `share` · `start_checkout` · `custom`

---

## Identity Management

```typescript
// Identify user on login
refine.identify('user_12345');

// Get current identity
const visitorId = refine.getVisitorId();  // Long-lived browser ID
const sessionId = refine.getSessionId();  // Current session ID
const userId = refine.getUserId();        // Authenticated user ID

// Reset on logout (new visitor, session, clear user)
refine.reset();
```

---

## Plugins

### Debug Plugin

```typescript
import { Refine, DebugPlugin } from '@refine-ai/sdk';

const refine = new Refine({ ... });

refine.use(new DebugPlugin({
  logEvents: true,         // Log all events
  validateEvents: true,    // Validate event schema
  dryRun: process.env.NODE_ENV !== 'production'
}));
```

### Consent Plugin

GDPR/CCPA compliant consent management:

```typescript
import { Refine, ConsentPlugin } from '@refine-ai/sdk';

const refine = new Refine({ ... });

const consent = new ConsentPlugin({
  required: true,
  onConsentRequired: () => showConsentBanner()
});

refine.use(consent);

// When user grants consent
consent.grant();

// When user denies consent
consent.deny();

// Check consent state
consent.hasConsent();   // true if granted
consent.hasDenied();    // true if explicitly denied
consent.isPending();    // true if neither granted nor denied
```

### Auto-Track Plugin

Automatic click and viewability tracking:

```typescript
import { Refine, AutoTrackPlugin } from '@refine-ai/sdk';

const refine = new Refine({ ... });

refine.use(new AutoTrackPlugin({
  pageViews: true,
  clicks: {
    selector: '[data-refine-item]',
    extractItemId: (el) => el.dataset.refineItem,
    extractPosition: (el) => parseInt(el.dataset.position),
    extractServeId: (el) => el.closest('[data-serve-id]')?.dataset.serveId
  },
  viewability: {
    enabled: true,
    threshold: 0.5,        // 50% visible
    duration: 1000         // 1 second
  }
}));
```

HTML structure for auto-tracking:

```html
<div data-serve-id="abc-123">
  <div data-refine-item="sku_001" data-position="1">Product 1</div>
  <div data-refine-item="sku_002" data-position="2">Product 2</div>
</div>
```

---

## Error Handling

```typescript
import { 
  Refine,
  RefineError,
  RefineValidationError,
  RefineAuthError,
  RefineNotFoundError,
  RefineNetworkError
} from '@refine-ai/sdk';

try {
  const results = await refine.search.text({ query: 'shoes' });
} catch (error) {
  if (error instanceof RefineValidationError) {
    console.error('Invalid request:', error.message, error.field);
  } else if (error instanceof RefineAuthError) {
    console.error('Authentication failed - check your API key');
  } else if (error instanceof RefineNotFoundError) {
    console.error('Resource not found:', error.resourceType, error.resourceId);
  } else if (error instanceof RefineNetworkError) {
    console.error('Network error - will retry automatically');
  } else if (error instanceof RefineError) {
    console.error('API error:', error.code, error.message);
    if (error.retryable) {
      // SDK will automatically retry retryable errors
    }
  }
}
```

### Error Types

| Error | HTTP Code | Retryable |
|-------|-----------|-----------|
| `RefineValidationError` | 400 | No |
| `RefineAuthError` | 401 | No |
| `RefineForbiddenError` | 403 | No |
| `RefineNotFoundError` | 404 | No |
| `RefineRateLimitError` | 429 | Yes |
| `RefineTimeoutError` | 408, 504 | Yes |
| `RefineServerError` | 500, 502, 503 | Yes |
| `RefineNetworkError` | N/A | Yes |

---

## Queue Management

```typescript
// Force flush events
await refine.events.flush();

// Pause event sending (events still queued)
refine.events.pause();

// Resume event sending
refine.events.resume();

// Get queue metrics
const metrics = refine.events.getMetrics();
console.log(`Queue: ${metrics.queueSize} events`);
console.log(`Sent: ${metrics.totalSent}`);
console.log(`Dropped: ${metrics.totalDropped}`);
```

---

## Event Listeners

```typescript
// Listen for events
refine.events.on('event:queued', (event) => {
  console.log('Event queued:', event.event_name);
});

refine.events.on('event:sent', (event) => {
  console.log('Event sent:', event.event_name);
});

refine.events.on('event:dropped', (event, reason) => {
  console.warn('Event dropped:', event.event_name, reason);
});

refine.events.on('session:started', (sessionId) => {
  console.log('New session:', sessionId);
});

refine.events.on('session:expired', (oldId, newId) => {
  console.log('Session rotated:', oldId, '->', newId);
});

refine.events.on('error', (error) => {
  console.error('Tracking error:', error);
});
```

---

## Server-Side Usage (Node.js)

```typescript
import { Refine } from '@refine-ai/sdk';

const refine = new Refine({
  apiKey: process.env.REFINE_API_KEY,
  organizationId: 'org-uuid',
  catalogId: 'catalog-uuid',
  applicationType: 'other',
  events: {
    persistence: 'memory'   // No localStorage in Node
  }
});

// Search
const results = await refine.search.text({ query: 'shoes' });

// Track with explicit context
refine.events.trackItemsServed({
  surface: 'api_search',
  source: 'text-search',
  serveId: results.serveId,
  itemIds: results.getProductIds(),
  query: 'shoes'
}, {
  sessionId: req.headers['x-session-id'],
  visitorId: req.headers['x-visitor-id'],
  userId: req.user?.id,
  ip: req.ip,
  userAgent: req.headers['user-agent']
});
```

---

## React Integration

```tsx
import { useEffect, useMemo } from 'react';
import { Refine } from '@refine-ai/sdk';

// Create singleton instance
const refine = new Refine({
  apiKey: process.env.REACT_APP_REFINE_API_KEY!,
  organizationId: process.env.REACT_APP_ORG_ID!,
  catalogId: process.env.REACT_APP_CATALOG_ID!
});

// Context provider
export const RefineContext = createContext(refine);

// Hook
export function useRefine() {
  return useContext(RefineContext);
}

// App component
function App() {
  useEffect(() => {
    return () => refine.destroy();
  }, []);

  return (
    <RefineContext.Provider value={refine}>
      <YourApp />
    </RefineContext.Provider>
  );
}

// Search component
function SearchResults() {
  const refine = useRefine();
  const [results, setResults] = useState([]);
  const contextRef = useRef(null);

  const handleSearch = async (query) => {
    const response = await refine.search.text({ query });
    setResults(response.results);
    
    // Track search
    contextRef.current = refine.events.trackSearch(
      query,
      response.results,
      { surface: 'search_results' }
    );
  };

  const handleClick = (productId, position) => {
    contextRef.current?.trackClick(productId, position);
  };

  return (
    <div>
      {results.map((product, index) => (
        <ProductCard
          key={product.productId}
          product={product}
          onClick={() => handleClick(product.productId, index + 1)}
        />
      ))}
    </div>
  );
}
```

---

## TypeScript

The SDK is written in TypeScript and provides comprehensive type definitions:

```typescript
import type {
  RefineConfig,
  SearchResponse,
  SearchResultItem,
  RecsResponse,
  PLPResponse,
  Filter,
  SortBy,
  InteractionType,
  ConversionType,
  EventSource
} from '@refine-ai/sdk';

// Type-safe filters
const filters: Filter[] = [
  { field: 'price', operator: 'lte', value: 100 }
];

// Type-safe sorting
const sortBy: SortBy = {
  field: 'price',
  order: 'ascending'
};
```

---

## Browser Support

- Chrome 80+
- Firefox 75+
- Safari 13+
- Edge 80+
- Node.js 16+

---

## Bundle Size

The SDK is tree-shakeable. Import only what you need:

```typescript
// Full SDK (~15KB gzipped)
import { Refine } from '@refine-ai/sdk';

// Individual modules
import { SearchClient } from '@refine-ai/sdk/search';
import { EventTracker } from '@refine-ai/sdk/events';
```

---

## API Reference

### Refine

| Method | Description |
|--------|-------------|
| `identify(userId)` | Set authenticated user ID |
| `reset()` | Reset identity (logout) |
| `getVisitorId()` | Get visitor ID |
| `getSessionId()` | Get session ID |
| `getUserId()` | Get user ID |
| `use(plugin)` | Install a plugin |
| `destroy()` | Cleanup resources |

### refine.search

| Method | Description |
|--------|-------------|
| `text(params)` | Text-based search |
| `image(params)` | Image-based search |

### refine.recs

| Method | Description |
|--------|-------------|
| `similarItems(params)` | Similar product recommendations |
| `forVisitor(params)` | Visitor-based recommendations |
| `forUser(params)` | User-based recommendations |
| `get(params)` | Generic recommendations |

### refine.plp

| Method | Description |
|--------|-------------|
| `get(plpId, options)` | Get Product Listing Page |

### refine.events

| Method | Description |
|--------|-------------|
| `trackItemsServed(params)` | Track item exposure |
| `trackInteraction(params)` | Track user interaction |
| `trackConversion(params)` | Track conversion |
| `trackSearch(query, results, options)` | Track search results |
| `trackRecommendations(...)` | Track recommendations |
| `trackClick(itemId, context, position)` | Track click |
| `trackView(itemId, context, position)` | Track view |
| `trackAddToCart(itemId, quantity, context)` | Track add to cart |
| `trackRemoveFromCart(itemId, quantity)` | Track remove from cart |
| `trackPurchase(order)` | Track purchase |
| `flush()` | Force send all events |
| `pause()` | Pause event sending |
| `resume()` | Resume event sending |
| `getMetrics()` | Get tracking metrics |
| `on(event, callback)` | Subscribe to events |

---

## License

MIT © Refine

---

## Support

- 📚 [Documentation](https://docs.joinrefine.io)
- 💬 [Discord](https://discord.gg/refine)
- 🐛 [Issue Tracker](https://github.com/refine/sdk-js/issues)
- 📧 [support@joinrefine.io](mailto:support@joinrefine.io)