npm package: @shopkit/data-layer
Description: Enterprise data abstraction layer providing unified API for e-commerce platforms including Shopify and custom backends
Author: Storefront Builder Team
Latest version: 1.1.0

# @shopkit/data-layer

A comprehensive, production-ready data layer package for e-commerce applications, providing unified APIs for both Shopify and custom backend integrations.

## Table of Contents

- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [API Reference](#api-reference)
- [Usage Examples](#usage-examples)
- [Platform Support](#platform-support)
- [Error Handling](#error-handling)
- [Best Practices](#best-practices)
- [Examples](#examples)
- [License](#license)

## Features

- **🌐 Multi-Platform Support**: Seamlessly works with both Shopify and custom backend APIs
- **⚙️ Configurable Architecture**: Zero environment variable dependencies - fully configurable through parameters
- **🔷 TypeScript First**: Built with TypeScript for complete type safety
- **📦 Modular Design**: Tree-shakable imports - use only what you need
- **🔧 Flexible Options**: Support for custom queries and variables in all API methods
- **📋 Consistent Response Format**: Standardized `IResponse` format across all operations
- **🪵 Advanced Logging**: Built-in logging with configurable levels and formatting
- **🎯 Standardized Interface**: All methods follow consistent signature patterns

## Installation

```bash
npm install @shopkit/data-layer
# or
yarn add @shopkit/data-layer
# or
pnpm add @shopkit/data-layer
```

## Quick Start

### 1. Create a Commerce Client

```typescript
import { createCommerceClient, IClientConfig } from '@shopkit/data-layer';

// Shopify Client Configuration
const shopifyConfig: IClientConfig = {
  type: "shopify",
  config: {
    domain: "your-store.myshopify.com",
    storefrontAccessToken: "your-storefront-access-token",
    apiVersion: "2025-04" as const,
  },
  logging: {
    enabled: true,
    level: "info",
    prettyPrint: true,
    name: "Shopify Data Layer"
  }
};

// Custom Backend Client Configuration
const customConfig: IClientConfig = {
  type: "custom",
  config: {
    merchantId: "your-merchant-id",
    storeId: "your-store-id",
    services: {
      product: {
        apiBaseUrl: "https://api.yourstore.com/products",
      },
      order: {
        apiBaseUrl: "https://api.yourstore.com/orders",
      },
      cart: {
        apiBaseUrl: "https://api.yourstore.com/cart",
      },
    },
  },
  logging: {
    enabled: true,
    level: "info",
    prettyPrint: true,
    name: "Custom Backend Data Layer"
  }
};

// Create the client
const commerceClient = createCommerceClient(shopifyConfig);
// or
const commerceClient = createCommerceClient(customConfig);
```

### 2. Basic Usage

```typescript
// Get a product
const product = await client.getProduct({ handle: "product-handle" });

if (product.success) {
  console.log(product.data);
} else {
  console.error(product.error?.message);
}
```

## Configuration

### Client Configuration Interfaces

```typescript
// Shopify Configuration
interface IShopifyConfig {
  domain: string;
  storefrontAccessToken: string;
  apiVersion: string;
}

// Custom Backend Configuration
interface ICustomConfig {
  merchantId: string;
  storeId: string;
  services: {
    product: { apiBaseUrl: string };
    order: { apiBaseUrl: string };
    cart: { apiBaseUrl: string };
  };
}

// Client Configuration Union Type
type IClientConfig =
  | { type: "shopify"; config: IShopifyConfig; logging?: LoggerConfig }
  | { type: "custom"; config: ICustomConfig; logging?: LoggerConfig };
```

### Logging Configuration

```typescript
interface LoggerConfig {
  enabled: boolean;
  level: "trace" | "debug" | "info" | "warn" | "error" | "fatal";
  prettyPrint: boolean;
  name: string;
  redact?: string[];
}
```

### Request Options

All API methods support optional configuration:

```typescript
interface IOptionsRequest {
  query?: string;                    // Custom GraphQL query
  variables?: Record<string, any>;   // Custom variables
  headers?: Record<string, string>;  // Custom headers
  params?: Record<string, string>;   // URL parameters
}
```

## API Reference

### Method Signature Pattern

All methods follow this consistent pattern:
```typescript
methodName(params: ISpecificRequest, options?: IOptionsRequest): Promise<IResponse>
```

### Standardized Response Format

```typescript
interface IResponse<T = any> {
  success: boolean;
  message: string;
  data?: T;
  error?: {
    message: string;
    details?: any;
  };
  meta?: IResponseMeta;
}
```

### Product Operations

```typescript
// Get single product by ID or handle
getProduct(params: ISingleProductRequest, options?: IOptionsRequest): Promise<IResponse>

interface ISingleProductRequest {
  id?: string;
  handle?: string;
}

// Get multiple products with filtering and pagination
getProducts(params: IProductQueryRequest, options?: IOptionsRequest): Promise<IResponse>

interface IProductQueryRequest {
  first?: number;
  last?: number;
  after?: string;
  before?: string;
  query?: string;
  sortKey?: string;
  reverse?: boolean;
  filters?: Array<{
    available?: boolean;
    price?: { min?: number; max?: number };
    productType?: string;
    vendor?: string;
    tag?: string;
  }>;
}

// Get multiple products by handles
getProductsByHandles(params: IProductsByHandlesRequest, options?: IOptionsRequest): Promise<IResponse>

interface IProductsByHandlesRequest {
  handles: string[];
}
```

### Collection Operations

```typescript
// Get single collection by ID or handle
getCollection(params: ISingleCollectionRequest, options?: IOptionsRequest): Promise<IResponse>

interface ISingleCollectionRequest {
  id?: string;
  handle?: string;
  productLimit?: number;
  productSortKey?: string;
  productReverse?: boolean;
  filters?: Array<{
    available?: boolean;
    price?: { min?: number; max?: number };
    productType?: string;
    vendor?: string;
    tag?: string;
  }>;
}

// Get multiple collections with filtering and pagination
getCollections(params: ICollectionQueryRequest, options?: IOptionsRequest): Promise<IResponse>

interface ICollectionQueryRequest {
  first?: number;
  last?: number;
  after?: string;
  before?: string;
  query?: string;
  sortKey?: string;
  reverse?: boolean;
}

// Get multiple collections by handles with individual query parameters
getCollectionsByHandles(params: ICollectionsByhandlesRequest, options?: IOptionsRequest): Promise<IResponse>

interface ICollectionsByhandlesRequest {
  handles: string[];
  first?: number;
  last?: number;
  after?: string;
  before?: string;
  query?: string;
  reverse?: boolean;
  sortKey?: string;
  productLimit?: number;
  productSortKey?: string;
  productReverse?: boolean;
  filters?: Array<{
    available?: boolean;
    price?: { min?: number; max?: number };
    productType?: string;
    vendor?: string;
    tag?: string;
  }>;
}
```

### Cart Operations

```typescript
// Create a new cart
createCart(options?: IOptionsRequest): Promise<IResponse>

// Get cart by ID
getCart(params: ISingleCartRequest, options?: IOptionsRequest): Promise<IResponse>

interface ISingleCartRequest {
  id: string;
}

// Update cart
updateCart(params: IUpdateCartRequest, options?: IOptionsRequest): Promise<IResponse>

interface IUpdateCartRequest {
  id: string;
  items: ICartItemRequest;
}

interface ICartItemRequest {
  productId: string;
  variantId: string;
  quantity: number;
  sellingPlanId?: string;
  attributes?: Array<{
    key: string;
    value: string;
  }>;
}

// Add items to cart
addToCart(params: IAddToCartRequest, options?: IOptionsRequest): Promise<IResponse>

interface IAddToCartRequest {
  id: string;
  items: ICartItemRequest[];
}

// Remove items from cart
removeFromCart(params: IRemoveFromCartRequest, options?: IOptionsRequest): Promise<IResponse>

interface IRemoveFromCartRequest {
  id: string;
  itemIds: string[];
}
```

### Order Operations

```typescript
// Get single order by ID (Admin only)
getOrder(params: ISingleOrderRequest, options?: IOptionsRequest): Promise<IResponse>

interface ISingleOrderRequest {
  id: string;
}

// Get multiple orders with filtering (Admin only)
getOrders(params: IOrderQueryRequest, options?: IOptionsRequest): Promise<IResponse>

interface IOrderQueryRequest {
  first?: number;
  last?: number;
  after?: string;
  before?: string;
  query?: string;
  sortKey?: string;
  reverse?: boolean;
}

// Create new order (Admin only)
createOrder(params: IOrderRequest, options?: IOptionsRequest): Promise<IResponse>

interface IOrderRequest {
  lineItems: Array<{
    variantId: string;
    quantity: number;
  }>;
  customer?: {
    email: string;
    firstName?: string;
    lastName?: string;
  };
  shippingAddress?: {
    address1: string;
    city: string;
    province: string;
    country: string;
    zip: string;
  };
}

// Update existing order (Admin only)
updateOrder(params: IUpdateOrderRequest, options?: IOptionsRequest): Promise<IResponse>

interface IUpdateOrderRequest {
  id: string;
  data: Partial<IOrderRequest>;
}

// Get customer order history
getOrderHistory(params: IOrderHistoryRequest, options?: IOptionsRequest): Promise<IResponse>

interface IOrderHistoryRequest {
  customerAccessToken: string;
  page?: number;
  limit?: number;
}

// Get specific customer order
getOrderById(params: IOrderByIdRequest, options?: IOptionsRequest): Promise<IResponse>

interface IOrderByIdRequest {
  customerAccessToken: string;
  orderId: string;
}
```

### Customer Operations

```typescript
// Get single customer by ID (Admin only)
getCustomer(params: ISingleCustomerRequest, options?: IOptionsRequest): Promise<IResponse>

interface ISingleCustomerRequest {
  id: string;
}

// Get multiple customers (Admin only)
getCustomers(params: ICustomerQueryRequest, options?: IOptionsRequest): Promise<IResponse>

// Create new customer
createCustomer(params: ICustomerRequest, options?: IOptionsRequest): Promise<IResponse>

interface ICustomerRequest {
  email: string;
  firstName?: string;
  lastName?: string;
  phone?: string;
  password?: string;
  acceptsMarketing?: boolean;
}

// Update existing customer
updateCustomer(params: IUpdateCustomerRequest, options?: IOptionsRequest): Promise<IResponse>

interface IUpdateCustomerRequest {
  id: string;
  data: Partial<ICustomerRequest>;
}

// Get customer profile (Shopify only)
getCustomerProfile(params: ICustomerProfileRequest, options?: IOptionsRequest): Promise<IResponse>

interface ICustomerProfileRequest {
  customerAccessToken: string;
}
```

### Checkout Operations

```typescript
// Create checkout
createCheckout(params: ICreateCheckoutRequest, options?: IOptionsRequest): Promise<IResponse>

interface ICreateCheckoutRequest {
  lineItems?: Array<{
    variantId: string;
    quantity: number;
  }>;
  email?: string;
  shippingAddress?: {
    address1: string;
    city: string;
    province: string;
    country: string;
    zip: string;
  };
}
```

### Blog/Article Operations (Shopify only)

```typescript
// Get single article
getArticle(params?: IGetArticleRequest, options?: IOptionsRequest): Promise<IResponse>

interface IGetArticleRequest {
  blogHandle?: string;
  articleHandle?: string;
  first?: number;
}

// Get blog articles
getBlogArticles(params: IBlogArticleRequest, options?: IOptionsRequest): Promise<IResponse>

interface IBlogArticleRequest {
  blogHandle: string;
  first?: number;
  after?: string;
}

// Get trending articles
getTrendingArticles(params: ITrendingArticleRequest, options?: IOptionsRequest): Promise<IResponse>

interface ITrendingArticleRequest {
  first?: number;
  sortBy?: string;
}

// Get article by handle
getArticleByHandle(params: IArticleByHandleRequest, options?: IOptionsRequest): Promise<IResponse>

interface IArticleByHandleRequest {
  blogHandle: string;
  articleHandle: string;
}
```

### Utility Operations

```typescript
// Custom GraphQL request (Shopify only)
graphqlRequest(params: IGraphqlRequest): Promise<IResponse>

interface IGraphqlRequest {
  query: string;
  variables?: Record<string, any>;
}

// Custom HTTP fetch request
fetchRequest(params: IFetchRequest): Promise<IResponse>

interface IFetchRequest {
  url: string;
  method?: string;
  headers?: Record<string, string>;
  body?: string;
  signal?: AbortSignal;
  [key: string]: any;
}
```

## Usage Examples

### Product Operations

```typescript
// Get single product
const product = await client.getProduct({ handle: "product-handle" });

// Get single product with custom headers
const product = await client.getProduct(
  { handle: "product-handle" },
  { headers: { "X-Custom-Header": "value" } }
);

// Get multiple products
const products = await client.getProducts({
  first: 20,
  query: "shirt",
  sortKey: "TITLE"
});

// Get products by handles
const products = await client.getProductsByHandles({
  handles: ["handle1", "handle2"]
});
```

### Collection Operations

```typescript
// Get single collection
const collection = await client.getCollection({ handle: "collection-handle" });

// Get multiple collections
const collections = await client.getCollections({ first: 10 });

// Get collections by handles with shared parameters
const collections = await client.getCollectionsByHandles({
  handles: ["featured-products", "sale-items", "new-arrivals"],
  first: 10,
  productLimit: 20,
  filters: [{ available: true }]
});
```

### Cart Operations

```typescript
// Create cart
const cart = await client.createCart();

// Get cart
const cart = await client.getCart({ id: "cart-id" });

// Add items to cart
const updatedCart = await client.addToCart({
  id: "cart-id",
  items: [
    {
      productId: "product-id",
      variantId: "variant-id",
      quantity: 1
    }
  ]
});

// Update cart
const updatedCart = await client.updateCart({
  id: "cart-id",
  data: {
    items: [
      {
        productId: "product-id",
        variantId: "variant-id",
        quantity: 2
      }
    ]
  }
});

// Remove items from cart
const updatedCart = await client.removeFromCart({
  id: "cart-id",
  itemIds: ["line-id"]
});
```

### Order Operations

```typescript
// Get order history
const orders = await client.getOrderHistory({
  customerAccessToken: "customer-access-token",
  page: 1,
  limit: 10
});

// Get specific order
const order = await client.getOrderById({
  customerAccessToken: "customer-access-token",
  orderId: "order-id"
});
```

### Framework Integration Examples

#### Next.js App Router (Server-Side)

```typescript
// app/products/[handle]/page.tsx
import { createCommerceClient } from '@shopkit/data-layer';

export default async function ProductPage({ params }: { params: { handle: string } }) {
  const client = createCommerceClient(shopifyConfig);
  
  const response = await client.getProduct({ handle: params.handle });
  
  if (!response.success) {
    return <div>Error: {response.error?.message}</div>;
  }
  
  const product = response.data;
  
  return (
    <div>
      <h1>{product.title}</h1>
      <p>{product.description}</p>
      <div>Price: {product.priceRange.minVariantPrice.amount} {product.priceRange.minVariantPrice.currencyCode}</div>
    </div>
  );
}
```

#### API Route Usage

```typescript
// app/api/products/route.ts
import { NextRequest } from 'next/server';
import { createCommerceClient } from '@shopkit/data-layer';

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const client = createCommerceClient(shopifyConfig);
  
  const response = await client.getProducts({
    first: 10,
    query: searchParams.get('query') || undefined
  });
  
  return Response.json(response);
}
```

#### Client-Side Usage

```typescript
'use client';

import { useState, useEffect } from 'react';
import { IResponse } from '@shopkit/data-layer';

export function useProducts(query?: string) {
  const [response, setResponse] = useState<IResponse | null>(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    async function fetchProducts() {
      try {
        const res = await fetch(`/api/products?query=${query || ''}`);
        const data = await res.json();
        setResponse(data);
      } catch (error) {
        setResponse({
          success: false,
          message: 'Failed to fetch products',
          error: { message: error instanceof Error ? error.message : 'Unknown error' }
        });
      } finally {
        setLoading(false);
      }
    }
    
    fetchProducts();
  }, [query]);
  
  return { response, loading };
}
```

## Platform Support

### Shopify Adapter
- ✅ **Products**: Full support for product operations
- ✅ **Collections**: Full support for collection operations
- ✅ **Cart**: Full cart management
- ✅ **Orders**: Customer order history and details
- ✅ **Blog**: Article and blog operations
- ✅ **GraphQL**: Custom GraphQL queries
- ✅ **Customer**: Customer profile access

### Custom Backend Adapter
- ✅ **Products**: Full support for product operations
- ✅ **Collections**: Full support for collection operations
- ✅ **Cart**: Full cart management
- ✅ **Orders**: Customer order history and details
- ✅ **Checkout**: Basic checkout creation
- ❌ **Blog**: Not implemented
- ❌ **GraphQL**: Not available

### Method Support Matrix

| Method | Custom Adapter | Shopify Adapter | Notes |
|--------|----------------|-----------------|-------|
| **Product Operations** |
| `getProduct` | ✅ Implemented | ✅ Implemented | Core product retrieval |
| `getProducts` | ✅ Implemented | ✅ Implemented | Product listing with filters |
| `getProductsByHandles` | ✅ Implemented | ✅ Implemented | Batch product retrieval |
| **Collection Operations** |
| `getCollection` | ✅ Implemented | ✅ Implemented | Single collection retrieval |
| `getCollections` | ✅ Implemented | ✅ Implemented | Collection listing |
| `getCollectionsByHandles` | ✅ Implemented | ✅ Implemented | Batch collection retrieval |
| **Order Operations** |
| `getOrder` | ❌ (Error thrown) | ❌ (NotImplemented) | Single order retrieval |
| `getOrders` | ❌ (Error thrown) | ❌ (NotImplemented) | Order listing |
| `createOrder` | ❌ (Error thrown) | ❌ (NotImplemented) | Order creation |
| `updateOrder` | ❌ (Error thrown) | ❌ (NotImplemented) | Order updates |
| `getOrderHistory` | ✅ Implemented | ✅ Implemented | Customer order history |
| `getOrderById` | ✅ Implemented | ✅ Implemented | Customer specific order |
| **Customer Operations** |
| `getCustomer` | ❌ (Error thrown) | ❌ (NotImplemented) | Single customer retrieval |
| `getCustomers` | ❌ (Error thrown) | ❌ (NotImplemented) | Customer listing |
| `createCustomer` | ❌ (Error thrown) | ❌ (NotImplemented) | Customer creation |
| `updateCustomer` | ❌ (Error thrown) | ❌ (NotImplemented) | Customer updates |
| `getCustomerProfile` | ❌ (Not available) | ✅ Implemented | Shopify-specific method |
| **Cart Operations** |
| `getCart` | ✅ Implemented | ✅ Implemented | Cart retrieval |
| `createCart` | ✅ Implemented | ✅ Implemented | Cart creation |
| `updateCart` | ✅ Implemented | ✅ Implemented | Cart updates |
| `addToCart` | ✅ Implemented | ✅ Implemented | Add items to cart |
| `removeFromCart` | ✅ Implemented | ✅ Implemented | Remove items from cart |
| **Checkout Operations** |
| `createCheckout` | ✅ Implemented | ❌ (NotImplemented) | Checkout creation |
| **Article/Blog Operations** |
| `getArticle` | ❌ (NotImplemented) | ✅ Implemented | Single article retrieval |
| `getBlogArticles` | ❌ (NotImplemented) | ✅ Implemented | Blog article listing |
| `getTrendingArticles` | ❌ (NotImplemented) | ✅ Implemented | Trending articles |
| `getArticleByHandle` | ❌ (NotImplemented) | ✅ Implemented | Article by handle |
| **Utility Operations** |
| `graphqlRequest` | ❌ (NotImplemented) | ✅ Implemented | GraphQL utility |
| `fetchRequest` | ✅ Inherited | ✅ Inherited | HTTP fetch utility |

## Error Handling

The data layer provides comprehensive error handling:

```typescript
const response = await client.getProduct({ handle: "invalid-handle" });

if (!response.success) {
  console.error("Error:", response.error?.message);
  
  // Handle specific error types
  if (response.error?.details?.name === "NotFoundError") {
    // Handle product not found
  }
}
```

## Best Practices

1. **Always check `response.success`** before accessing `data`
2. **Use TypeScript** for full type safety and better developer experience
3. **Enable logging** during development for better debugging
4. **Handle errors gracefully** with proper fallbacks
5. **Use environment variables** for sensitive configuration data
6. **Implement proper caching** for frequently accessed data
7. **Use custom queries** sparingly and only when standard methods are insufficient
8. **Follow the standardized method signature pattern** for consistency

## Examples

For complete examples and usage patterns, see the [data layer example app](../../apps/data-layer-example).

### Data Layer Example App Features

The example application includes several user experience improvements:

#### Enhanced JSON Input Fields

The example app provides improved textarea formatting for complex JSON payloads:

- **Add to Cart**: Multi-line textarea with formatted JSON structure for items array
- **Update Cart**: Enhanced textarea for cart item modifications with proper formatting
- **Create Checkout**: Improved textarea for checkout payload with correct interface structure

**Example Add to Cart Payload Format:**
```json
[
  {
    "productId": "gid://shopify/Product/123",
    "variantId": "gid://shopify/ProductVariant/123",
    "quantity": 1,
    "sellingPlanId": "selling-plan-id",
    "attributes": [
      {
        "key": "gift-wrap",
        "value": "true"
      }
    ]
  }
]
```

**Example Create Checkout Payload Format:**
```json
{
  "cart_token": "cart-token-here",
  "checkout_id": "checkout-id-here",
  "attributes": {
    "email": "customer@example.com",
    "note": "Special instructions",
    "phone": "+1234567890",
    "custom_field": "custom_value"
  }
}
```

These improvements provide:
- **Better Readability**: Multi-line JSON with proper indentation
- **Larger Input Areas**: Textarea fields instead of single-line inputs
- **Complete Examples**: Shows all available fields and proper structure
- **Error Prevention**: Properly formatted examples reduce JSON syntax errors

## License

MIT License - see the [LICENSE](LICENSE) file for details.