npm package: @logiteria/commerce-sdk
Description: Framework-agnostic SDK for Logiteria Commerce storefronts
Author: Logiteria
Latest version: 1.0.0

# @logiteria/commerce-sdk

Framework-agnostic TypeScript SDK for building custom storefronts on [Logiteria Commerce](https://logiteria.com).

Zero runtime dependencies. Works with any JavaScript framework. First-class React hooks and Next.js helpers included as optional subpath imports.

## Installation

```bash
npm install @logiteria/commerce-sdk
# or
pnpm add @logiteria/commerce-sdk
```

For React projects, also install peer dependencies:

```bash
npm install react react-dom zustand
```

For Next.js projects:

```bash
npm install react react-dom zustand next
```

## Quick Start

### Vanilla TypeScript

```typescript
import { createClient } from '@logiteria/commerce-sdk'

const client = createClient({
  storeUrl: 'https://your-store.lumocomm.com',
})

// Browse products
const { products, total } = await client.products.list({ per_page: 12 })

// Search
const results = await client.products.search('sneakers', { min_price: 50 })

// Get product detail
const product = await client.products.get('blue-sneakers')

// Cart operations
const cart = await client.cart.create()
await client.cart.addItem(cart.id, { product_id: product.id, quantity: 1 })
await client.cart.applyCoupon(cart.id, 'SAVE10')

// Authentication
const { user, tokens } = await client.auth.login({
  email: 'user@example.com',
  password: 'password',
})
// Access token is stored automatically after login

// Place order
const order = await client.checkout.placeOrder(cart.id, {
  payment_method: 'stripe',
})
```

### React

```tsx
import { CommerceProvider } from '@logiteria/commerce-sdk/react'

const config = { storeUrl: 'https://your-store.lumocomm.com' }

function App() {
  return (
    <CommerceProvider config={config}>
      <ProductList />
    </CommerceProvider>
  )
}
```

```tsx
import { useProducts, useCart, useAuth } from '@logiteria/commerce-sdk/react'

function ProductList() {
  const { products, loading } = useProducts({ per_page: 12 })
  const { addItem, itemCount } = useCart()
  const { user, isAuthenticated } = useAuth()

  if (loading) return <div>Loading...</div>

  return (
    <div>
      <p>Cart: {itemCount} items</p>
      {products.map(product => (
        <div key={product.id}>
          <h3>{product.name}</h3>
          <button onClick={() => addItem({
            product_id: product.id,
            name: product.name,
            slug: product.slug,
            sku: product.sku,
            price: product.price,
            quantity: 1,
            image: product.thumbnail,
            variant_id: null,
            variant_label: null,
            variant_values: {},
            compare_at_price: null,
            stock_status: product.stock_status,
          })}>
            Add to Cart
          </button>
        </div>
      ))}
    </div>
  )
}
```

### Next.js (App Router)

Use ISR-compatible fetchers for public pages:

```tsx
// app/page.tsx — Static/ISR, no cookies
import { getProducts, getCollections } from '@logiteria/commerce-sdk/next'

export const revalidate = 60

export default async function HomePage() {
  const storeUrl = process.env.NEXT_PUBLIC_STORE_URL!
  const [productsData, collections] = await Promise.all([
    getProducts(storeUrl, { per_page: 8 }),
    getCollections(storeUrl),
  ])

  return (
    <div>
      {productsData.products.map(p => <div key={p.id}>{p.name}</div>)}
    </div>
  )
}
```

Use `createServerClient` for auth-required pages:

```tsx
// app/account/orders/page.tsx — Dynamic, reads cookies
import { createServerClient } from '@logiteria/commerce-sdk/next'

export default async function OrdersPage() {
  const client = await createServerClient()
  const { orders } = await client.customer.orders()

  return (
    <div>
      {orders.map(order => <div key={order.id}>{order.order_number}</div>)}
    </div>
  )
}
```

## API Reference

### `createClient(config)`

Creates a `CommerceClient` instance.

```typescript
interface CommerceClientConfig {
  storeUrl: string       // Base URL of your Logiteria Commerce instance
  locale?: string        // Default locale (e.g. "en", "tr")
  timeout?: number       // Request timeout in ms (default: 10000)
  fetch?: typeof fetch   // Custom fetch for testing
}
```

### Resources

#### `client.products`

| Method | Description |
|--------|-------------|
| `list(params?)` | List products with pagination, filtering, and sorting |
| `get(slug)` | Get a single product by slug |
| `search(query, params?)` | Full-text search with optional filters |

**ProductListParams:**
```typescript
{
  page?: number
  per_page?: number
  sort?: string          // "price", "name", "created_at"
  order?: 'asc' | 'desc'
  category?: string      // Category slug
  collection?: string    // Collection slug
  q?: string             // Search query
  min_price?: number
  max_price?: number
  brand?: string
  in_stock?: boolean
}
```

#### `client.collections`

| Method | Description |
|--------|-------------|
| `list(params?)` | List all collections |
| `get(slug)` | Get collection with its products |

#### `client.categories`

| Method | Description |
|--------|-------------|
| `tree()` | Get full category tree |
| `get(slug)` | Get a single category |

#### `client.cart`

| Method | Description |
|--------|-------------|
| `create()` | Create a new cart |
| `get(cartId)` | Get cart by ID |
| `addItem(cartId, item)` | Add product to cart |
| `updateItem(cartId, productId, quantity)` | Update item quantity |
| `removeItem(cartId, productId)` | Remove item from cart |
| `applyCoupon(cartId, code)` | Apply coupon code |
| `removeCoupon(cartId)` | Remove applied coupon |
| `setShippingAddress(cartId, address)` | Set shipping address |
| `setBillingAddress(cartId, address)` | Set billing address |
| `setShippingMethod(cartId, method)` | Set shipping method |
| `getShippingMethods()` | List available shipping methods |

#### `client.checkout`

| Method | Description |
|--------|-------------|
| `placeOrder(cartId, data)` | Place an order |
| `getDeliveryOptions(cartId)` | Get delivery options for cart |

#### `client.auth`

| Method | Description |
|--------|-------------|
| `login(credentials)` | Login with email/password |
| `register(data)` | Create a new account |
| `refresh()` | Refresh access token |
| `logout()` | Logout and clear token |
| `forgotPassword(email)` | Send password reset email |
| `resetPassword(token, password)` | Reset password with token |
| `getProfile()` | Get authenticated user profile |

After `login()` or `register()`, the access token is stored automatically.

#### `client.customer`

All methods require authentication.

| Method | Description |
|--------|-------------|
| `getProfile()` | Get customer profile |
| `updateProfile(data)` | Update name, phone |
| `orders(params?)` | List order history |
| `order(id)` | Get order details |
| `addresses()` | List saved addresses |
| `createAddress(data)` | Add new address |
| `updateAddress(id, data)` | Update address |
| `deleteAddress(id)` | Delete address |
| `loyaltyBalance()` | Get loyalty points balance |
| `loyaltyRewards()` | List available rewards |
| `loyaltyTransactions()` | List points history |
| `redeemReward(rewardId)` | Redeem a loyalty reward |

#### `client.settings`

| Method | Description |
|--------|-------------|
| `getSettings()` | Get store settings (name, currency, logo, etc.) |
| `getCapabilities()` | Get enabled modules and features |

### React Hooks

Import from `@logiteria/commerce-sdk/react`:

| Hook | Returns |
|------|---------|
| `useProducts(params?)` | `{ products, loading, error, total, page, totalPages, refetch }` |
| `useProduct(slug)` | `{ product, loading, error }` |
| `useCart()` | `{ cart, items, itemCount, subtotal, total, addItem, removeItem, updateQuantity, applyCoupon, removeCoupon, clear, ... }` |
| `useAuth()` | `{ user, isAuthenticated, loading, initialized, login, register, logout, refreshProfile }` |
| `useCollections()` | `{ collections, loading, error, refetch }` |
| `useCollection(slug)` | `{ collection, loading, error }` |
| `useSearch(query, params?, debounceMs?)` | `{ results, loading, error, facets, total }` |

### Next.js Helpers

Import from `@logiteria/commerce-sdk/next`:

**ISR-compatible fetchers** (no cookies, cacheable):

| Function | Description |
|----------|-------------|
| `getProducts(storeUrl, params?)` | Fetch product list (revalidate: 300s) |
| `getProduct(storeUrl, slug)` | Fetch single product (revalidate: 300s) |
| `getCollections(storeUrl)` | Fetch collections (revalidate: 300s) |
| `getCollection(storeUrl, slug)` | Fetch collection (revalidate: 300s) |
| `getCategories(storeUrl)` | Fetch category tree (revalidate: 300s) |
| `getSettings(storeUrl)` | Fetch store settings (revalidate: 3600s) |
| `getCapabilities(storeUrl)` | Fetch capabilities (revalidate: 3600s) |

**Server client** (reads cookies, forces dynamic rendering):

| Function | Description |
|----------|-------------|
| `createServerClient(config?)` | Create auth-aware client for Server Components |

### Error Handling

All API errors throw `CommerceError`:

```typescript
import { CommerceError } from '@logiteria/commerce-sdk'

try {
  await client.products.get('nonexistent')
} catch (err) {
  if (err instanceof CommerceError) {
    console.log(err.code)     // "not_found"
    console.log(err.status)   // 404
    console.log(err.message)  // "Product not found"

    if (err.isNotFound()) { /* 404 */ }
    if (err.isValidation()) {
      // Field-level errors
      const fields = err.fieldErrors()
      // { email: ["Email is required"], password: ["Too short"] }
    }
    if (err.isAuth()) { /* 401/403 */ }
    if (err.isServerError()) { /* 5xx */ }
    if (err.isNetwork()) { /* network failure */ }
  }
}
```

### Retry Behavior

- Requests automatically retry up to 2 times on 5xx errors
- Exponential backoff: 500ms, 1000ms
- Network errors also trigger retries (except timeouts)
- Customize per-request: `client.products.list()` uses default; override via the HTTP client

## Subpath Exports

| Import Path | What | Peer Dependencies |
|-------------|------|-------------------|
| `@logiteria/commerce-sdk` | Core client + types | None |
| `@logiteria/commerce-sdk/react` | Provider + hooks | `react`, `zustand` |
| `@logiteria/commerce-sdk/next` | ISR fetchers + server client | `next` |

The core SDK has **zero runtime dependencies**. React and Next.js helpers require their respective peer dependencies.

## Architecture Notes

**Browser vs Server**: The SDK detects its environment automatically:
- **Browser**: Uses relative URLs (`/api/v1/...`) — expects your Next.js rewrites to proxy to the store
- **Server**: Uses absolute URLs (`https://store.example.com/api/v1/...`)

**Cart Hydration**: The `useCart()` hook uses Zustand with `skipHydration: true` to prevent SSR mismatch. Cart state persists in `localStorage` and rehydrates on mount via `useEffect`.

**ISR vs Dynamic**: Use `getProducts()`/`getCollections()` from `@logiteria/commerce-sdk/next` for ISR/static pages. Use `createServerClient()` only for pages that need auth (it reads cookies which forces dynamic rendering).

## License

MIT