npm package: @shopsynq/storefront-sdk
Description: TypeScript SDK for building storefronts powered by ShopSynq
Author: ShopSynq
Latest version: 0.1.0

# @shopsynq/storefront-sdk

TypeScript SDK for building storefronts powered by [ShopSynq](https://shopsynq.com).

Build your own ecommerce website using **any tech stack** — Next.js, React (Vite), Astro, Remix, or plain TypeScript — and host it anywhere.

---

## Features

- 🔷 **Full TypeScript support** — every API fully typed
- 🚀 **Framework-agnostic core** — fetch-based, no axios, works in Edge/Node/browser
- ⚛️ **React adapter** — TanStack Query hooks + Context providers
- 🛒 **Cart** — in-memory state with localStorage persistence
- 🎨 **Preview mode** — live theme editing via PostMessage (same protocol as ShopSynq admin)
- 🏷 **Multi-tenant** — built-in `x-tenant-id` header management
- 🔐 **Auth** — customer signin/signup, JWT auto-refresh
- 📦 **Dual ESM + CJS** output, tree-shakeable

---

## Installation

```bash
npm install @shopsynq/storefront-sdk
# or
yarn add @shopsynq/storefront-sdk
# or
pnpm add @shopsynq/storefront-sdk
```

**Peer dependencies** (only needed if using the React adapter):
```bash
npm install react @tanstack/react-query
```

---

## Quick Start

### Next.js (App Router)

```tsx
// app/layout.tsx
import { ShopsynqProvider } from '@shopsynq/storefront-sdk/react';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <ShopsynqProvider
          baseUrl="https://api.shopsynq.com/v1"
          storeId="my-store"
          storage="cookie"  // use 'cookie' for SSR
        >
          {children}
        </ShopsynqProvider>
      </body>
    </html>
  );
}
```

### React (Vite / SPA)

```tsx
// main.tsx
import { ShopsynqProvider } from '@shopsynq/storefront-sdk/react';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <ShopsynqProvider
    baseUrl="https://api.shopsynq.com/v1"
    storeId="my-store"
  >
    <App />
  </ShopsynqProvider>
);
```

### Astro / Remix (framework-agnostic core)

```ts
import { ShopsynqClient } from '@shopsynq/storefront-sdk';

const shopsynq = new ShopsynqClient({
  baseUrl: 'https://api.shopsynq.com/v1',
  storeId: 'my-store',
  storage: 'memory', // or 'cookie'
});

// Fetch products server-side
const products = await shopsynq.products.list({ page: 1, limit: 20 });
const config = await shopsynq.store.getPublishedConfig();
```

---

## React Hooks

### Store & Theme

```tsx
import { useStoreDetails, useStoreConfig } from '@shopsynq/storefront-sdk/react';

function Header() {
  const { data: store } = useStoreDetails('my-store');
  const { data: config } = useStoreConfig();
  
  return (
    <header style={{ background: config?.config.layout?.header.backgroundColor }}>
      <img src={store?.logo} alt={store?.name} />
    </header>
  );
}
```

### Products

```tsx
import { useProductsList, useProduct, useProductsInfinite } from '@shopsynq/storefront-sdk/react';

// Paginated list
function ProductGrid() {
  const { data, isLoading } = useProductsList({ page: 1, limit: 20, categoryId: 5 });
  return (
    <div>
      {data?.data.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

// Infinite scroll
function InfiniteProductGrid() {
  const { data, fetchNextPage, hasNextPage } = useProductsInfinite({ limit: 20 });
  // ...
}

// Single product
function ProductPage({ id }: { id: number }) {
  const { data: product } = useProduct(id);
  return <div>{product?.name}</div>;
}
```

### Categories

```tsx
import { useCategoriesList } from '@shopsynq/storefront-sdk/react';

function Sidebar() {
  const { data } = useCategoriesList();
  return (
    <nav>
      {data?.data.map(cat => <a key={cat.id} href={`/shop?category=${cat.id}`}>{cat.name}</a>)}
    </nav>
  );
}
```

### Cart

```tsx
import { useCart } from '@shopsynq/storefront-sdk/react';

function AddToCartButton({ product, variation }) {
  const { addToCart, totalQuantity, items } = useCart();
  
  return (
    <div>
      <span>Cart: {totalQuantity} items</span>
      <button onClick={() => addToCart(product, variation, 1)}>
        Add to Cart
      </button>
    </div>
  );
}
```

### Checkout

```tsx
import { useOrderPreview, useCreateOrder, useAddresses } from '@shopsynq/storefront-sdk/react';
import { CreateOrderPaymentMethod } from '@shopsynq/storefront-sdk';

function CheckoutPage() {
  const { data: preview } = useOrderPreview();         // Real-time totals
  const { data: addresses } = useAddresses();           // Saved addresses
  const createOrder = useCreateOrder();

  const handlePlaceOrder = () => {
    createOrder.mutate({
      products: [{ id: 1, quantity: 2 }],
      address: { street1: '123 Main St', city: 'Mumbai', state: 'MH', country: 'IN', postalCode: '400001' },
      paymentMethod: CreateOrderPaymentMethod.COD,
    });
  };

  return (
    <div>
      <p>Total: ₹{preview?.netOrderTotal}</p>
      <button onClick={handlePlaceOrder}>Place Order</button>
    </div>
  );
}
```

### Authentication

```tsx
import { useAuth, useSignIn, useSignOut } from '@shopsynq/storefront-sdk/react';

function AuthStatus() {
  const { user, isAuthenticated } = useAuth();
  const signIn = useSignIn();
  const signOut = useSignOut();

  if (isAuthenticated) {
    return (
      <div>
        <span>Hello, {user?.firstName}</span>
        <button onClick={() => signOut.mutate()}>Sign Out</button>
      </div>
    );
  }

  return (
    <button onClick={() => signIn.mutate({ email: 'user@example.com', password: 'pass' })}>
      Sign In
    </button>
  );
}
```

---

## Preview Mode (Theme Editor Integration)

The SDK includes built-in support for live theme editing via PostMessage — the same protocol used by the ShopSynq admin panel.

### How it works

1. The ShopSynq theme editor opens your storefront in an `<iframe>`
2. As the merchant edits colors/fonts/layout, the editor sends `postMessage({ type: "STORE_CONFIG_UPDATE", config })` 
3. The SDK catches these messages and applies CSS variables instantly

### React usage

```tsx
// preview/page.tsx
import { usePreviewMode, usePreviewConfig, useStoreConfig } from '@shopsynq/storefront-sdk/react';

export default function PreviewPage() {
  const isPreview = usePreviewMode();           // true when inside editor iframe
  const previewConfig = usePreviewConfig();     // live config from editor
  const { data: publishedConfig } = useStoreConfig();  // fallback

  // Use preview config if available, otherwise published
  const config = previewConfig ?? publishedConfig?.config;

  return <StorefrontLayout config={config} />;
}
```

### Vanilla JS / Astro / Remix

```ts
import { ShopsynqClient } from '@shopsynq/storefront-sdk';

const shopsynq = new ShopsynqClient({ baseUrl: '...', storeId: 'my-store' });

// Start listening for theme editor messages
shopsynq.preview.listen();

// React to config changes
shopsynq.preview.onConfigUpdate((config) => {
  // config.global.theme.colors.primary has been updated
  // CSS vars are already applied to document.documentElement
  console.log('Theme updated:', config.global?.theme);
});

// Check if running in iframe
if (shopsynq.preview.isPreviewMode()) {
  console.log('In preview/editor mode');
}

// Cleanup
shopsynq.preview.stop();
```

---

## Framework-Agnostic Client API

All methods are available on the `ShopsynqClient` instance:

```ts
const shopsynq = new ShopsynqClient({ baseUrl, storeId, storage });

// Auth
await shopsynq.auth.signIn({ email, password })
await shopsynq.auth.signUp({ email, password, firstName })
await shopsynq.auth.verifyOtp({ phonenumber, otp })
await shopsynq.auth.getProfile()
await shopsynq.auth.updateProfile({ firstName })
await shopsynq.auth.signOut()

// Store
await shopsynq.store.getDetails(storeId)
await shopsynq.store.getPublishedConfig()
await shopsynq.store.validateSubdomain(subdomain)

// Products
await shopsynq.products.list({ page, limit, search, categoryId, tagIds, minPrice, maxPrice, inStock })
await shopsynq.products.get(id)
await shopsynq.products.search('query')
await shopsynq.products.listByCategory(categoryId)

// Categories
await shopsynq.categories.list({ page, limit })
await shopsynq.categories.get(id)
await shopsynq.categories.getProducts(categoryId)

// Cart (synchronous, local state)
shopsynq.cart.addItem(product, variation, quantity)
shopsynq.cart.removeItem(productId, variationId)
shopsynq.cart.updateQuantity(productId, quantity)
shopsynq.cart.increment(productId)
shopsynq.cart.decrement(productId)
shopsynq.cart.clear()
shopsynq.cart.getState()
shopsynq.cart.subscribe((state) => console.log(state))
shopsynq.cart.toOrderItems()  // Serialized for API

// Checkout
await shopsynq.checkout.previewOrder(cartItems, couponCode)
await shopsynq.checkout.getAddresses()
await shopsynq.checkout.createAddress(dto)
await shopsynq.checkout.createOrder(dto)
await shopsynq.checkout.getOrder(orderId)
await shopsynq.checkout.validateCoupon(code)

// Preview
shopsynq.preview.listen()
shopsynq.preview.stop()
shopsynq.preview.onConfigUpdate(callback)
shopsynq.preview.isPreviewMode()
shopsynq.preview.applyTheme(config)
```

---

## Configuration

| Option | Type | Default | Description |
|---|---|---|---|
| `baseUrl` | `string` | — | ShopSynq API URL (e.g. `https://api.shopsynq.com/v1`) |
| `storeId` | `string` | — | Store slug or ID (sets `x-tenant-id` header) |
| `storage` | `'localStorage' \| 'cookie' \| 'memory'` | `'localStorage'` | Token storage backend |

Use `cookie` for SSR frameworks (Next.js, Remix, Astro) to support server-side rendering with auth.
Use `memory` for testing or server-only environments.

---

## Next.js Middleware Example

Identify the store from subdomain and set `storeId` for all requests:

```ts
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  const hostname = request.headers.get('host') ?? '';
  const subdomain = hostname.split('.')[0];

  if (subdomain && subdomain !== 'www') {
    // Optionally validate via SDK: shopsynq.store.validateSubdomain(subdomain)
    const response = NextResponse.next();
    response.cookies.set('shopsynq_tenant_id', subdomain);
    return response;
  }

  return NextResponse.next();
}
```

---

## Error Handling

```ts
import {
  ShopsynqError,
  AuthError,
  NotFoundError,
  RateLimitError,
  NetworkError,
} from '@shopsynq/storefront-sdk';

try {
  const product = await shopsynq.products.get(999);
} catch (err) {
  if (err instanceof NotFoundError) {
    // 404 — product doesn't exist
  } else if (err instanceof AuthError) {
    // 401 — token expired and refresh failed
  } else if (err instanceof NetworkError) {
    // No internet / server unreachable
  } else if (err instanceof ShopsynqError) {
    console.error(err.statusCode, err.code, err.message);
  }
}
```

---

## Type Reference

All types are exported from the root package:

```ts
import type {
  // Products
  IProduct, IProductVariation, IVariationAttribute,
  ProductStatus, Channel, WeightUnit, VariationUnit,
  
  // Categories
  ICategory,
  
  // Orders
  IOnlineOrder, IOnlineOrderItem, OrderStatus, PaymentStatus,
  
  // Customer
  ICustomer, IAddress, UserStatus, Role,
  
  // Store
  IStoreDetails, ICheckoutSettings,
  
  // Theme & Config
  StoreConfig, GlobalTheme, HeaderConfig, FooterConfig, Section, PageConfig,
  
  // Checkout
  OrderPreview, CreateOrderDto, AddressDto, CreateOrderPaymentMethod,
  
  // Pagination
  PaginatedResponse, PaginationParams,
} from '@shopsynq/storefront-sdk';
```

---

## License

MIT © ShopSynq