Skip to main content

Response types

All controllers return the same QueryResult shape in data:
interface QueryState<T> {
  data: T | null
  error: ClientError | null
  isFetching: boolean
}

interface QueryResult {
  products: Product[]
  totalResults: number
  totalPages: number
  page: number
  facets: Record<string, Record<string, number>>
  priceRange?: PriceRange
  attributionToken?: string
}
priceRange requires { name: 'Price', code: 'variants.price' } in your facets configuration. Without it, priceRange will be undefined. Blocks results add a block field with { id, title, anchor_type, strategy_type, strategy_key }. The strategy_type value can be interaction, collection_interaction, similar_products, or manual.

Error handling

All methods return { data, error } instead of throwing. Errors are discriminated by _tag:
const result = await collection.execute()

if (result.error) {
  switch (result.error._tag) {
    case 'NetworkError':
      // code: 'TIMEOUT' | 'CONNECTION_FAILED' | 'DNS_FAILED' | 'SSL_ERROR' | 'ABORTED' | 'OFFLINE'
      break
    case 'ApiError':
      // code: 'NOT_FOUND' | 'RATE_LIMITED' | 'UNAUTHORIZED' | ...
      // status: HTTP status code, source: 'layers'
      break
    case 'ValidationError':
      // operation: which method failed, fields: [{ field, code, message }]
      break
    case 'ConfigError':
      // field: which config field, expected: what was expected
      break
  }
}
isRetryable classifies errors by tag, code, and status. Use it standalone or as a shouldRetry predicate:
import { isRetryable } from '@commerce-blocks/sdk'

if (result.error && isRetryable(result.error)) {
  const delay = result.error.retryAfterMs ?? 1000
  setTimeout(() => collection.execute(), delay)
}

Next steps