@deliverart/sdk-js-error-handler
v2.10.2
Published
Error handling utilities for Deliverart SDK in JavaScript
Readme
@deliverart/sdk-js-error-handler
Error handling plugin for the DeliverArt JavaScript SDK. Automatically handles API errors and transforms them into typed exceptions.
Installation
npm install @deliverart/sdk-js-error-handler @deliverart/sdk-js-core
# or
pnpm add @deliverart/sdk-js-error-handler @deliverart/sdk-js-core
# or
yarn add @deliverart/sdk-js-error-handler @deliverart/sdk-js-coreExported Types
ErrorHandlerPlugin
class ErrorHandlerPlugin implements ApiClientPlugin<{}>Plugin that intercepts API responses and transforms error responses into typed exceptions.
Constructor:
constructor()- No configuration required
ApiError
class ApiError extends Error {
readonly name = 'ApiError'
readonly code?: string
readonly request?: RequestInit
readonly response?: Response
readonly status?: number
readonly data?: ApiErrorData
}Main error class for API errors.
Properties:
name: 'ApiError'- Error namecode?: string- Error code from the APIrequest?: RequestInit- Original request initresponse?: Response- HTTP response objectstatus?: number- HTTP status codedata?: ApiErrorData- Typed error data
ApiErrorData
type ApiErrorData =
| {
type: 'VALIDATION_ERROR'
value: ValidationErrorData
}
| {
type: 'UNKNOWN_ERROR'
value: unknown
}Discriminated union type for different error types.
VALIDATION_ERROR - Returned when API responds with 422 status (validation failed)
value: ValidationErrorData- Contains validation violations
UNKNOWN_ERROR - Returned for all other error responses
value: unknown- Contains the raw error response
ValidationErrorData
interface ValidationErrorData {
status: number
violations: Array<{
propertyPath: string
message: string
code: string | null
}>
detail: string
type: string
title: string
}Structured validation error data from the API.
Properties:
status: number- HTTP status code (422)violations: Array- List of validation violationspropertyPath: string- Field path that failed validation (e.g., "email", "address.city")message: string- Human-readable error messagecode: string | null- Validation error code
detail: string- Detailed error descriptiontype: string- Error type identifiertitle: string- Error title
FormState
type FormState<T> =
| {
success: true
data: T
}
| {
success: false
error: ApiErrorData
}Utility type for handling form submission states (useful for React Server Actions).
FormStateError
class FormStateError extends Error {
constructor(public readonly error: ApiErrorData)
}Error wrapper for form state errors.
Usage
Basic Setup
import { createApiClient } from '@deliverart/sdk-js-core';
import { ErrorHandlerPlugin } from '@deliverart/sdk-js-error-handler';
const client = createApiClient({
baseUrl: 'https://api.deliverart.com'
})
.addPlugin(new ErrorHandlerPlugin());Handling Validation Errors
import { ApiError } from '@deliverart/sdk-js-error-handler';
import { CreateUser } from './requests';
try {
await client.call(new CreateUser({
email: 'invalid-email',
name: ''
}));
} catch (error) {
if (error instanceof ApiError && error.data?.type === 'VALIDATION_ERROR') {
const violations = error.data.value.violations;
violations.forEach(violation => {
console.error(`${violation.propertyPath}: ${violation.message}`);
});
// Output:
// email: This value is not a valid email address.
// name: This value should not be blank.
}
}Displaying Validation Errors in Forms
React Example
import { useState } from 'react';
import { ApiError } from '@deliverart/sdk-js-error-handler';
function CreateUserForm() {
const [errors, setErrors] = useState<Record<string, string>>({});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setErrors({});
try {
await client.call(new CreateUser(formData));
// Success!
} catch (error) {
if (error instanceof ApiError && error.data?.type === 'VALIDATION_ERROR') {
const newErrors: Record<string, string> = {};
error.data.value.violations.forEach(violation => {
newErrors[violation.propertyPath] = violation.message;
});
setErrors(newErrors);
}
}
};
return (
<form onSubmit={handleSubmit}>
<input name="email" />
{errors.email && <span className="error">{errors.email}</span>}
<input name="name" />
{errors.name && <span className="error">{errors.name}</span>}
<button type="submit">Create User</button>
</form>
);
}Handling Different HTTP Status Codes
import { ApiError } from '@deliverart/sdk-js-error-handler';
try {
await client.call(new GetUser('123'));
} catch (error) {
if (error instanceof ApiError) {
switch (error.status) {
case 400:
console.error('Bad Request:', error.message);
break;
case 401:
console.error('Unauthorized - please login');
window.location.href = '/login';
break;
case 403:
console.error('Forbidden - insufficient permissions');
break;
case 404:
console.error('User not found');
break;
case 422:
// Validation error - handled above
break;
case 500:
console.error('Server error');
break;
default:
console.error('Unknown error:', error);
}
}
}Using FormState with React Server Actions
FormState is particularly useful for Next.js Server Actions:
// app/actions/create-user.ts
'use server';
import { throwableToFormState, FormState } from '@deliverart/sdk-js-error-handler';
import { sdk } from '@/lib/sdk';
import { CreateUser } from '@deliverart/sdk-js-user';
export async function createUserAction(
formData: FormData
): Promise<FormState<{ id: string }>> {
return throwableToFormState(async () => {
const result = await sdk.call(new CreateUser({
name: formData.get('name') as string,
email: formData.get('email') as string,
}));
return { id: result.id };
});
}// app/components/CreateUserForm.tsx
'use client';
import { useFormState } from 'react-dom';
import { createUserAction } from '../actions/create-user';
export function CreateUserForm() {
const [state, formAction] = useFormState(createUserAction, null);
return (
<form action={formAction}>
<input name="name" required />
{state?.success === false &&
state.error.type === 'VALIDATION_ERROR' &&
state.error.value.violations
.filter(v => v.propertyPath === 'name')
.map(v => <span key={v.code} className="error">{v.message}</span>)
}
<input name="email" type="email" required />
{state?.success === false &&
state.error.type === 'VALIDATION_ERROR' &&
state.error.value.violations
.filter(v => v.propertyPath === 'email')
.map(v => <span key={v.code} className="error">{v.message}</span>)
}
<button type="submit">Create User</button>
{state?.success === true && (
<p className="success">User created with ID: {state.data.id}</p>
)}
</form>
);
}Global Error Handler
Set up a global error handler for all API errors:
// lib/error-handler.ts
import { ApiError } from '@deliverart/sdk-js-error-handler';
export function handleApiError(error: unknown) {
if (!(error instanceof ApiError)) {
console.error('Non-API error:', error);
return;
}
// Log to error tracking service (e.g., Sentry)
console.error('API Error:', {
status: error.status,
code: error.code,
message: error.message,
data: error.data,
});
// Show user-friendly message
if (error.status === 401) {
alert('Please login to continue');
window.location.href = '/login';
} else if (error.status === 403) {
alert('You do not have permission to perform this action');
} else if (error.status === 404) {
alert('The requested resource was not found');
} else if (error.data?.type === 'VALIDATION_ERROR') {
const firstViolation = error.data.value.violations[0];
alert(`Validation error: ${firstViolation.message}`);
} else {
alert('An unexpected error occurred. Please try again.');
}
}// Usage in your app
try {
await client.call(request);
} catch (error) {
handleApiError(error);
}Type Guards
Create type guards for cleaner error handling:
import { ApiError, ApiErrorData } from '@deliverart/sdk-js-error-handler';
function isValidationError(error: unknown): error is ApiError & {
data: { type: 'VALIDATION_ERROR', value: ValidationErrorData }
} {
return error instanceof ApiError &&
error.data?.type === 'VALIDATION_ERROR';
}
function isUnauthorizedError(error: unknown): error is ApiError {
return error instanceof ApiError && error.status === 401;
}
function isNotFoundError(error: unknown): error is ApiError {
return error instanceof ApiError && error.status === 404;
}
// Usage
try {
await client.call(request);
} catch (error) {
if (isValidationError(error)) {
// TypeScript knows error.data.value is ValidationErrorData
error.data.value.violations.forEach(v => {
console.log(v.propertyPath, v.message);
});
} else if (isUnauthorizedError(error)) {
redirectToLogin();
} else if (isNotFoundError(error)) {
show404Page();
}
}Converting FormState Errors Back to Exceptions
import { formStateErrorToThrowable, FormState } from '@deliverart/sdk-js-error-handler';
const result: FormState<User> = await createUserAction(formData);
if (!result.success) {
// Convert FormState error back to throwable error
throw formStateErrorToThrowable(result.error);
}
// TypeScript knows result.data is User
console.log(result.data.id);How It Works
The plugin adds a response middleware that:
- Checks if the response is not OK (
!response.ok) - If status is 422, attempts to parse the response as validation error
- If parsing succeeds, throws
ApiErrorwithVALIDATION_ERRORtype - For all other error responses, throws
ApiErrorwithUNKNOWN_ERRORtype - Includes the original request, response, and parsed body in the error
Best Practices
1. Centralized Error Handling
Create a utility function for consistent error handling across your app:
export function getErrorMessage(error: unknown): string {
if (error instanceof ApiError) {
if (error.data?.type === 'VALIDATION_ERROR') {
const firstViolation = error.data.value.violations[0];
return firstViolation?.message ?? 'Validation failed';
}
return error.message;
}
if (error instanceof Error) {
return error.message;
}
return 'An unexpected error occurred';
}2. Field-Level Error Mapping
Create a helper to map violations to form fields:
import { ValidationErrorData } from '@deliverart/sdk-js-error-handler';
export function violationsToFieldErrors(
violations: ValidationErrorData['violations']
): Record<string, string> {
return violations.reduce((acc, violation) => {
acc[violation.propertyPath] = violation.message;
return acc;
}, {} as Record<string, string>);
}
// Usage
if (error.data?.type === 'VALIDATION_ERROR') {
const fieldErrors = violationsToFieldErrors(error.data.value.violations);
setErrors(fieldErrors);
}3. Toast Notifications
Integrate with toast notification libraries:
import { toast } from 'sonner';
import { ApiError } from '@deliverart/sdk-js-error-handler';
try {
await client.call(request);
toast.success('Operation completed successfully');
} catch (error) {
if (error instanceof ApiError && error.data?.type === 'VALIDATION_ERROR') {
error.data.value.violations.forEach(v => {
toast.error(v.message);
});
} else {
toast.error('An error occurred');
}
}License
MIT
