Skip to main content
The SDK uses a Result pattern for error handling instead of throwing exceptions. All API methods return a Result type that contains either { data } or { error }, making error handling explicit and type-safe.

Result Pattern

const result = await api.search({ query: 'shoes' });

if (result.error) {
  // Error occurred
  console.error(result.error._tag);    // Error type identifier
  console.error(result.error.message); // Human-readable message
  return;
}

// Success
console.log(result.data.results);

Error Types

All errors have a _tag property for type discrimination. This allows you to handle different error types appropriately.

General Errors

Error TagDescriptionRetryable
NetworkErrorRequest didn’t reach server (timeout, offline)Usually
HttpErrorServer returned error status (4xx, 5xx)5xx, 429, 425, 408
RateLimitErrorRate limit exceeded (429)Yes
ValidationErrorInvalid input parametersNo
ParseErrorResponse couldn’t be parsedNo
ConfigErrorSDK misconfigurationNo

Layers API Errors

Error TagDescription
LayersAuthErrorInvalid or expired API token
LayersBrowseErrorCollection not found, invalid filter/sort
LayersSearchErrorInvalid or empty query
LayersSimilarErrorProduct not found
LayersImageSearchErrorImage too large or unsupported format
LayersPredictiveErrorQuery too short
LayersServiceErrorService unavailable or indexing

Shopify API Errors

Error TagDescription
ShopifyAuthErrorInvalid access token or missing scopes
ShopifyGraphQLErrorGraphQL query/mutation error
ShopifyProductNotFoundErrorProduct doesn’t exist
ShopifyCollectionNotFoundErrorCollection doesn’t exist
ShopifyThrottledErrorRate limited with cost info

Type Guards

Use type guards to narrow error types and access type-specific properties:
import {
  isNetworkError,
  isRateLimitError,
  isLayersBrowseError,
  isRetryable,
  getRetryDelay,
} from '@protonagency/commerce-blocks-sdk';

const result = await api.browse({ 
  collectionHandle: 'shoes', 
  sortOrderCode: 'best_selling' 
});

if (result.error) {
  const error = result.error;

  // Check specific error types
  if (isNetworkError(error)) {
    console.log('Network issue:', error.code); // TIMEOUT, OFFLINE, etc.
  }

  if (isRateLimitError(error)) {
    console.log('Rate limited, retry in:', error.retryAfterMs, 'ms');
  }

  // HTTP 425 (Too Early) - Search data not ready yet
  if (isHttpError(error) && error.status === 425) {
    const retryAfter = error.response?.headers?.['retry-after'];
    console.log('Search not ready, retry after:', retryAfter, 'seconds');
  }

  if (isLayersBrowseError(error)) {
    console.log('Browse error code:', error.code); // COLLECTION_NOT_FOUND, etc.
  }

  // Check if error is retryable
  if (isRetryable(error)) {
    const delay = getRetryDelay(error) ?? 1000;
    setTimeout(() => retryRequest(), delay);
  }
}

Error Context

Errors include context for debugging:
if (result.error) {
  // All errors have these properties
  console.log(result.error._tag);      // Error type
  console.log(result.error.message);   // Human-readable message
  console.log(result.error.timestamp); // ISO timestamp

  // HTTP errors include request/response context
  if (isHttpError(result.error)) {
    console.log(result.error.status);          // HTTP status code
    console.log(result.error.request?.url);    // Request URL
    console.log(result.error.response?.body);  // Response body (truncated)
  }

  // Validation errors include field details
  if (isValidationError(result.error)) {
    for (const field of result.error.fields) {
      console.log(`${field.field}: ${field.message}`);
    }
  }
}

Common Error Handling Pattern

Here’s a recommended pattern for handling errors in your application:
async function fetchProducts(collection: string) {
  const result = await app.layersApi.browse({
    collectionHandle: collection,
    sortOrderCode: 'best_selling',
  });

  if (result.error) {
    const { _tag, message } = result.error;

    switch (_tag) {
      case 'NetworkError':
        return { error: 'Network issue. Check your connection.' };

      case 'RateLimitError':
        return { error: 'Too many requests. Please try again shortly.' };

      case 'LayersBrowseError':
        // Collection not linked in Layers dashboard
        return { error: 'Collection not available for browsing.' };

      case 'ConfigError':
        console.error('SDK misconfiguration:', message);
        return { error: 'Configuration error.' };

      default:
        console.error('Unexpected error:', _tag, message);
        return { error: 'Something went wrong.' };
    }
  }

  return { data: result.data.results };
}

Retry Logic

For retryable errors, implement exponential backoff:
async function fetchWithRetry<T>(
  fn: () => Promise<Result<T>>,
  maxRetries = 3
): Promise<Result<T>> {
  let lastResult: Result<T>;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    lastResult = await fn();
    
    if (!lastResult.error) {
      return lastResult;
    }
    
    if (!isRetryable(lastResult.error)) {
      return lastResult;
    }
    
    const delay = getRetryDelay(lastResult.error) ?? (1000 * Math.pow(2, attempt));
    await new Promise(resolve => setTimeout(resolve, delay));
  }
  
  return lastResult!;
}

// Usage
const result = await fetchWithRetry(() => 
  api.browse({ collectionHandle: 'shoes', sortOrderCode: 'best_selling' })
);

Error Handling in Components

When using reactive methods with Preact components, handle errors in your render logic:
function ProductList() {
  const { data, error, status } = browseSignal.value;

  if (status === 'loading') {
    return <LoadingSpinner />;
  }

  if (error) {
    // Show user-friendly error message
    if (isNetworkError(error)) {
      return <ErrorMessage message="Unable to connect. Please check your internet connection." />;
    }
    
    if (isRateLimitError(error)) {
      return <ErrorMessage message="Too many requests. Please wait a moment." />;
    }
    
    return <ErrorMessage message="Something went wrong. Please try again." />;
  }

  if (!data || data.results.length === 0) {
    return <EmptyState message="No products found." />;
  }

  return (
    <div className="product-grid">
      {data.results.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}