SDK
Error Handling
에러 처리 가이드
Error Handling
SDK는 사용자 친화적인 에러 메시지와 타입 안전한 에러 처리를 제공합니다.
에러 타입
SDKError
모든 SDK 에러의 기본 클래스입니다.
class SDKError extends Error {
code: string // 에러 코드
statusCode?: number // HTTP 상태 코드 (해당되는 경우)
userMessage: string // 사용자 친화적 메시지
suggestion: string // 해결 제안
getUserMessage(): string // 사용자 친화적 메시지 반환
}NetworkError
네트워크 연결 실패 시 발생합니다.
class NetworkError extends SDKError {
code: 'NETWORK_ERROR'
userMessage: '네트워크 연결을 확인해주세요.'
suggestion: '인터넷 연결을 확인하거나 잠시 후 다시 시도해주세요.'
}ApiError
API 요청 실패 시 발생합니다.
class ApiError extends SDKError {
code: 'API_ERROR'
statusCode: number
userMessage: string // 상태 코드에 따른 메시지
suggestion: string // 상태 코드에 따른 제안
}HTTP 상태 코드별 메시지
| 상태 코드 | 메시지 | 제안 |
|---|---|---|
| 400 | 잘못된 요청입니다. | 입력한 정보를 다시 확인해주세요. |
| 401 | 인증이 필요합니다. | 로그인 후 다시 시도해주세요. |
| 403 | 접근 권한이 없습니다. | 권한이 필요한 작업입니다. |
| 404 | 요청한 데이터를 찾을 수 없습니다. | URL이나 ID를 확인해주세요. |
| 429 | 너무 많은 요청을 보냈습니다. | 잠시 후 다시 시도해주세요. |
| 500 | 서버 오류가 발생했습니다. | 잠시 후 다시 시도해주세요. |
| 503 | 서비스를 일시적으로 사용할 수 없습니다. | 잠시 후 다시 시도해주세요. |
ValidationError
데이터 검증 실패 시 발생합니다.
class ValidationError extends SDKError {
code: 'VALIDATION_ERROR'
userMessage: '입력한 정보를 다시 확인해주세요.'
suggestion: '필수 필드를 모두 입력했는지 확인해주세요.'
}TimeoutError
요청 시간 초과 시 발생합니다.
class TimeoutError extends SDKError {
code: 'TIMEOUT_ERROR'
userMessage: '요청 시간이 초과되었습니다.'
suggestion: '잠시 후 다시 시도해주세요.'
}타입 가드
isNetworkError()
import { isNetworkError } from '@01.software/sdk'
try {
await client.from('products').find()
} catch (error) {
if (isNetworkError(error)) {
console.error('네트워크 에러:', error.getUserMessage())
console.error('제안:', error.suggestion)
}
}isApiError()
import { isApiError } from '@01.software/sdk'
try {
await client.from('products').findById('invalid_id')
} catch (error) {
if (isApiError(error)) {
console.error('API 에러:', error.statusCode)
console.error('메시지:', error.getUserMessage())
if (error.statusCode === 404) {
// 404 처리
}
}
}isValidationError()
import { isValidationError } from '@01.software/sdk'
try {
await client.from('products').create({
// 잘못된 데이터
})
} catch (error) {
if (isValidationError(error)) {
console.error('검증 에러:', error.getUserMessage())
// 폼 에러 표시
}
}isTimeoutError()
import { isTimeoutError } from '@01.software/sdk'
try {
await client.from('products').find()
} catch (error) {
if (isTimeoutError(error)) {
console.error('타임아웃:', error.getUserMessage())
// 재시도 버튼 표시
}
}응답 타입 가드
isSuccessResponse()
import { isSuccessResponse } from '@01.software/sdk'
const response = await client.from('products').find()
if (isSuccessResponse(response)) {
// TypeScript가 response.data의 타입을 자동 추론
console.log(response.data.docs)
console.log(response.data.pagination)
}isErrorResponse()
import { isErrorResponse } from '@01.software/sdk'
const response = await client.from('products').find()
if (isErrorResponse(response)) {
// TypeScript가 response.error의 타입을 자동 추론
console.error(response.error.code)
console.error(response.error.message)
console.error(response.error.getUserMessage())
}에러 처리 패턴
Try-Catch 패턴
import {
isNetworkError,
isApiError,
isValidationError,
isTimeoutError,
} from '@01.software/sdk'
async function fetchProducts() {
try {
const { data } = await client.from('products').find()
return data?.docs || []
} catch (error) {
if (isNetworkError(error)) {
// 네트워크 에러 처리
toast.error('네트워크 연결을 확인해주세요')
return []
}
if (isApiError(error)) {
// API 에러 처리
if (error.statusCode === 404) {
toast.error('데이터를 찾을 수 없습니다')
} else if (error.statusCode >= 500) {
toast.error('서버 오류가 발생했습니다')
} else {
toast.error(error.getUserMessage())
}
return []
}
if (isValidationError(error)) {
// 검증 에러 처리
toast.error(error.getUserMessage())
return []
}
if (isTimeoutError(error)) {
// 타임아웃 에러 처리
toast.error('요청 시간이 초과되었습니다')
return []
}
// 기타 에러
console.error('Unknown error:', error)
toast.error('오류가 발생했습니다')
return []
}
}응답 체크 패턴
import { isSuccessResponse, isErrorResponse } from '@01.software/sdk'
async function createProduct(data: ProductInput) {
const response = await client.from('products').create(data)
if (isSuccessResponse(response)) {
toast.success('상품이 생성되었습니다')
router.push(`/products/${response.data.id}`)
return response.data
}
if (isErrorResponse(response)) {
toast.error(response.error.getUserMessage())
console.error('Error details:', response.error)
return null
}
}React 컴포넌트에서의 에러 처리
React Query 에러 처리
'use client'
import { client } from '@/lib/client'
import { isApiError } from '@01.software/sdk'
export default function ProductsPage() {
const { data, error, isLoading } = client.query.useCollection('products', {
limit: 10,
})
if (isLoading) return <div>Loading...</div>
if (error) {
if (isApiError(error) && error.statusCode === 404) {
return <div>상품을 찾을 수 없습니다</div>
}
return (
<div className="error">
<h2>오류가 발생했습니다</h2>
<p>{error.getUserMessage()}</p>
<p className="text-sm text-gray-600">{error.suggestion}</p>
</div>
)
}
return (
<div>
{data?.docs.map(product => (
<div key={product.id}>{product.name}</div>
))}
</div>
)
}Error Boundary
'use client'
import { Component, ReactNode } from 'react'
import { SDKError } from '@01.software/sdk'
interface Props {
children: ReactNode
fallback?: (error: Error) => ReactNode
}
interface State {
hasError: boolean
error?: Error
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error) {
console.error('Error caught by boundary:', error)
if (error instanceof SDKError) {
console.error('SDK Error:', {
code: error.code,
message: error.getUserMessage(),
suggestion: error.suggestion,
})
}
}
render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback(this.state.error!)
}
const error = this.state.error
return (
<div className="error-boundary">
<h2>오류가 발생했습니다</h2>
{error instanceof SDKError && (
<>
<p>{error.getUserMessage()}</p>
<p className="text-sm">{error.suggestion}</p>
</>
)}
<button onClick={() => this.setState({ hasError: false })}>
다시 시도
</button>
</div>
)
}
return this.props.children
}
}
// 사용
export default function App() {
return (
<ErrorBoundary>
<ProductsPage />
</ErrorBoundary>
)
}Toast 알림
import { toast } from 'sonner'
import { isNetworkError, isApiError } from '@01.software/sdk'
async function handleAction() {
try {
await client.from('products').create(data)
toast.success('상품이 생성되었습니다')
} catch (error) {
if (isNetworkError(error)) {
toast.error('네트워크 연결을 확인해주세요', {
description: error.suggestion,
})
} else if (isApiError(error)) {
toast.error(error.getUserMessage(), {
description: error.suggestion,
})
} else {
toast.error('오류가 발생했습니다')
}
}
}전역 에러 처리
React Query 전역 에러 핸들러
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { toast } from 'sonner'
import { isApiError, isNetworkError } from '@01.software/sdk'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError: (error) => {
console.error('Query error:', error)
if (isNetworkError(error)) {
toast.error('네트워크 연결을 확인해주세요')
} else if (isApiError(error)) {
toast.error(error.getUserMessage())
}
},
},
mutations: {
onError: (error) => {
console.error('Mutation error:', error)
if (isApiError(error)) {
toast.error(error.getUserMessage())
} else {
toast.error('작업 중 오류가 발생했습니다')
}
},
},
},
})
export default function App({ children }) {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}커스텀 에러 로거
import { createBrowserClient } from '@01.software/sdk'
import * as Sentry from '@sentry/nextjs'
const client = createBrowserClient({
clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY!,
errorLogger: {
log: (error) => {
// Sentry로 에러 전송
Sentry.captureException(error, {
tags: {
source: 'sdk',
code: error.code,
},
extra: {
userMessage: error.getUserMessage(),
suggestion: error.suggestion,
},
})
// 콘솔 로깅 (개발 환경)
if (process.env.NODE_ENV === 'development') {
console.error('SDK Error:', {
code: error.code,
message: error.message,
userMessage: error.getUserMessage(),
suggestion: error.suggestion,
})
}
},
},
})재시도 로직
SDK는 자동으로 재시도를 수행하지만, 커스터마이징할 수 있습니다.
const client = createBrowserClient({
clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY!,
retry: {
maxRetries: 3,
retryableStatuses: [408, 429, 500, 502, 503, 504],
retryDelay: (attempt) => {
// Exponential backoff: 1초 → 2초 → 4초 (최대 10초)
return Math.min(1000 * 2 ** attempt, 10000)
},
},
debug: {
logErrors: true, // 에러 로깅
},
})네트워크 에러와 특정 HTTP 상태 코드는 자동으로 재시도됩니다.
디버깅
디버그 모드
const client = createBrowserClient({
clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY!,
debug: {
logRequests: true,
logResponses: true,
logErrors: true,
},
})에러 상세 정보
try {
await client.from('products').find()
} catch (error) {
if (error instanceof SDKError) {
console.log({
code: error.code,
statusCode: error.statusCode,
message: error.message,
userMessage: error.getUserMessage(),
suggestion: error.suggestion,
stack: error.stack,
})
}
}다음 단계
- Advanced - 고급 기능
- API Reference - 전체 API 레퍼런스