Back to arkyStore.io

Error Handling

Handle errors gracefully in your application

This guide covers error handling patterns for the Arky SDK.

Result Type

All SDK methods return a Result type that explicitly handles success and failure:

const result = await sdk.eshop.product.get({
  storeId: 'store_abc123',
  id: 'prod_xyz'
});

if (result.ok) {
  // Success - result.val contains the data
  const product = result.val;
  console.log(product.name);
} else {
  // Error - result.val contains error details
  const error = result.val;
  console.error(error.message);
}

Error Structure

Errors follow a consistent structure:

interface ApiError {
  error: string;      // Error code (e.g., "NOT_FOUND")
  message: string;    // Human-readable message
  details?: {         // Additional context
    field?: string;
    reason?: string;
  };
  statusCode: number; // HTTP status code
}

Common Error Codes

Authentication Errors (401)

CodeDescription
UNAUTHORIZEDMissing or invalid token
TOKEN_EXPIREDAccess token has expired
INVALID_CREDENTIALSWrong email/password
USER_NOT_CONFIRMEDEmail not verified
USER_DISABLEDAccount has been disabled

Authorization Errors (403)

CodeDescription
FORBIDDENInsufficient permissions
STORE_ACCESS_DENIEDNo access to this store

Not Found Errors (404)

CodeDescription
NOT_FOUNDResource doesn’t exist
PRODUCT_NOT_FOUNDProduct not found
ORDER_NOT_FOUNDOrder not found
USER_NOT_FOUNDUser not found

Validation Errors (400)

CodeDescription
VALIDATION_ERRORInvalid input data
INVALID_EMAILInvalid email format
WEAK_PASSWORDPassword too weak
MISSING_FIELDRequired field missing
INVALID_PROMO_CODEPromo code invalid or expired

Conflict Errors (409)

CodeDescription
DUPLICATE_EMAILEmail already registered
DUPLICATE_SLUGSlug already in use
SLOT_UNAVAILABLETime slot no longer available
INSUFFICIENT_INVENTORYNot enough stock

Payment Errors (402)

CodeDescription
PAYMENT_FAILEDPayment processing failed
CARD_DECLINEDCard was declined
INSUFFICIENT_FUNDSInsufficient funds
REQUIRES_ACTION3D Secure required

Handling Patterns

Basic Error Handling

async function getProduct(id: string) {
  const result = await sdk.eshop.product.get({
    storeId: 'store_abc123',
    id
  });

  if (!result.ok) {
    const error = result.val;

    switch (error.error) {
      case 'NOT_FOUND':
        throw new NotFoundError('Product not found');
      case 'UNAUTHORIZED':
        redirectToLogin();
        return null;
      default:
        throw new Error(error.message);
    }
  }

  return result.val;
}

Form Validation Errors

async function handleLogin(email: string, code: string) {
  try {
    await sdk.auth.verify({ email, code });
    return { success: true };
  } catch (error: any) {
    if (error.error === 'VALIDATION_ERROR') {
      return {
        success: false,
        fieldErrors: {
          [error.details?.field || 'form']: error.message
        }
      };
    }

    return {
      success: false,
      formError: error.message || 'Verification failed'
    };
  }

  return { success: true, user: result.val };
}

React Error Component

function useApiCall<T>(
  apiCall: () => Promise<Result<T, ApiError>>
) {
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<ApiError | null>(null);
  const [loading, setLoading] = useState(false);

  const execute = async () => {
    setLoading(true);
    setError(null);

    const result = await apiCall();

    if (result.ok) {
      setData(result.val);
    } else {
      setError(result.val);
    }

    setLoading(false);
  };

  return { data, error, loading, execute };
}

// Usage
function ProductPage({ id }) {
  const { data: product, error, loading, execute } = useApiCall(() =>
    sdk.eshop.product.get({ storeId: 'store_123', id })
  );

  useEffect(() => { execute(); }, [id]);

  if (loading) return <Spinner />;
  if (error) return <ErrorDisplay error={error} onRetry={execute} />;
  if (!product) return null;

  return <Product data={product} />;
}

function ErrorDisplay({ error, onRetry }) {
  return (
    <div className="error-container">
      <h2>Something went wrong</h2>
      <p>{error.message}</p>
      {onRetry && (
        <button onClick={onRetry}>Try Again</button>
      )}
    </div>
  );
}

Global Error Handler

import { createArkyStore } from 'arky-sdk/storefront-store';

const arkyStore = createArkyStore({
  baseUrl: process.env.ARKY_API_URL!,
  storeId: process.env.ARKY_STORE_ID!,
  market: 'us',
  locale: 'en',
  navigate: (path) => {
    window.location.href = path;
  },
  loginFallbackPath: '/login?expired=true',
});

Retry Logic

async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
  delay = 1000
): Promise<T> {
  let lastError: unknown = null;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      const statusCode = error instanceof ApiError ? error.statusCode : 0;
      if (statusCode >= 400 && statusCode < 500) throw error;
    }

    // Wait before retrying
    await new Promise(resolve => setTimeout(resolve, delay * (attempt + 1)));
  }

  throw lastError;
}

// Usage
const result = await withRetry(() =>
  arkyStore.eshop.product.list({ limit: 20 })
);

User-Friendly Messages

Map error codes to user-friendly messages:

const ERROR_MESSAGES: Record<string, string> = {
  // Auth
  INVALID_CREDENTIALS: 'Invalid email or password. Please try again.',
  USER_NOT_CONFIRMED: 'Please check your email to verify your account.',
  TOKEN_EXPIRED: 'Your session has expired. Please log in again.',

  // Products
  PRODUCT_NOT_FOUND: 'This product is no longer available.',
  INSUFFICIENT_INVENTORY: 'Sorry, this item is out of stock.',

  // Orders
  PAYMENT_FAILED: 'Payment failed. Please check your card details.',
  CARD_DECLINED: 'Your card was declined. Please try another payment method.',
  INVALID_PROMO_CODE: 'This promo code is invalid or has expired.',

  // Scheduled services
  SLOT_UNAVAILABLE: 'This time slot is no longer available. Please choose another.',

  // Default
  DEFAULT: 'Something went wrong. Please try again later.'
};

function getErrorMessage(error: ApiError): string {
  return ERROR_MESSAGES[error.error] || ERROR_MESSAGES.DEFAULT;
}

Logging Errors

Log errors for debugging:

function logError(error: ApiError, context?: Record<string, unknown>) {
  console.error('API Error:', {
    code: error.error,
    message: error.message,
    status: error.statusCode,
    details: error.details,
    ...context
  });

  // Send to error tracking service
  if (process.env.NODE_ENV === 'production') {
    errorTracker.capture(error, context);
  }
}

// Usage
try {
  await arkyStore.eshop.cart.checkout({ payment_method_id: 'cash' });
} catch (error) {
  logError(error as ApiError, {
    cartId: arkyStore.eshop.cart.cart.get()?.id,
    userId: currentUser.id
  });
}
Tip

Always provide clear, actionable error messages to users. Technical details should be logged but not displayed.

Testing Error Scenarios

// Test error handling
describe('Product Page', () => {
  it('shows error message when product not found', async () => {
    // Mock API to return error
    vi.spyOn(sdk.eshop, 'getProduct').mockResolvedValue({
      ok: false,
      val: {
        error: 'NOT_FOUND',
        message: 'Product not found',
        statusCode: 404
      }
    });

    render(<ProductPage id="invalid" />);

    await waitFor(() => {
      expect(screen.getByText(/not found/i)).toBeInTheDocument();
    });
  });
});