@01.software/sdk 0.0.1-260102065059 → 0.1.0-dev.260109.7cf07c9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +530 -0
- package/dist/index.cjs +920 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +885 -1820
- package/dist/index.d.ts +885 -1820
- package/dist/index.js +898 -2
- package/dist/index.js.map +1 -1
- package/package.json +14 -11
package/README.md
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
# @01.software/sdk
|
|
2
|
+
|
|
3
|
+
01.software 플랫폼의 공식 TypeScript SDK입니다. Supabase 스타일의 직관적인 API와 강력한 타입 안전성을 제공하며, 브라우저와 서버 환경을 모두 지원합니다.
|
|
4
|
+
|
|
5
|
+
## 주요 기능
|
|
6
|
+
|
|
7
|
+
- 🔒 **타입 안전성**: TypeScript 기반의 완전한 타입 추론 및 discriminated union
|
|
8
|
+
- 🌐 **멀티 환경**: 브라우저와 서버 환경 모두 지원
|
|
9
|
+
- 🎯 **직관적 API**: Supabase 스타일의 쿼리 빌더
|
|
10
|
+
- ⚡ **React Query 통합**: 데이터 페칭 및 캐싱 최적화
|
|
11
|
+
- 🔄 **Webhook 처리**: 타입 안전한 webhook 핸들링
|
|
12
|
+
- 📦 **37개 컬렉션**: 브랜드, 상품, 주문, 콘텐츠 등 포괄적 지원
|
|
13
|
+
- 🔁 **자동 재시도**: Exponential backoff 전략 기반 재시도 로직
|
|
14
|
+
- 🐛 **디버그 모드**: 요청/응답/에러 선택적 로깅
|
|
15
|
+
- ⚠️ **향상된 에러 처리**: 사용자 친화적 에러 메시지 및 제안
|
|
16
|
+
|
|
17
|
+
## 설치
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @01.software/sdk
|
|
21
|
+
# 또는
|
|
22
|
+
yarn add @01.software/sdk
|
|
23
|
+
# 또는
|
|
24
|
+
pnpm add @01.software/sdk
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 빠른 시작
|
|
28
|
+
|
|
29
|
+
### 브라우저 환경
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createBrowserClient } from '@01.software/sdk'
|
|
33
|
+
|
|
34
|
+
const client = createBrowserClient({
|
|
35
|
+
clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// 쿼리 빌더로 데이터 조회
|
|
39
|
+
const { data: products } = await client.from('products').find({
|
|
40
|
+
limit: 10,
|
|
41
|
+
where: { status: { equals: 'published' } }
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// React Query 훅 사용
|
|
45
|
+
function ProductList() {
|
|
46
|
+
const { data, isLoading } = client.query.useCollection('products', {
|
|
47
|
+
limit: 10
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
if (isLoading) return <div>Loading...</div>
|
|
51
|
+
return <div>{data?.docs.map(p => p.name)}</div>
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 서버 환경
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { createServerClient } from '@01.software/sdk'
|
|
59
|
+
|
|
60
|
+
const client = createServerClient({
|
|
61
|
+
clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY,
|
|
62
|
+
secretKey: process.env.SOFTWARE_SECRET_KEY,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// 주문 생성
|
|
66
|
+
const order = await client.api.createOrder({
|
|
67
|
+
paymentId: 'pay_123',
|
|
68
|
+
orderNumber: generateOrderNumber(),
|
|
69
|
+
email: 'user@example.com',
|
|
70
|
+
orderProducts: [
|
|
71
|
+
{
|
|
72
|
+
product: 'product_id',
|
|
73
|
+
variant: 'variant_id',
|
|
74
|
+
quantity: 1,
|
|
75
|
+
price: 10000,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
totalAmount: 10000,
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 핵심 개념
|
|
83
|
+
|
|
84
|
+
### 1. 클라이언트
|
|
85
|
+
|
|
86
|
+
#### BrowserClient
|
|
87
|
+
|
|
88
|
+
브라우저 환경에서 사용하는 클라이언트로, React Query 통합과 쿼리 빌더를 제공합니다.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
const client = createBrowserClient({
|
|
92
|
+
clientKey: 'your-client-key',
|
|
93
|
+
baseUrl: 'https://api.example.com', // 선택사항
|
|
94
|
+
debug: true, // 디버그 모드 활성화
|
|
95
|
+
retry: {
|
|
96
|
+
maxRetries: 3, // 최대 재시도 횟수
|
|
97
|
+
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 10000), // Exponential backoff
|
|
98
|
+
},
|
|
99
|
+
errorLogger: {
|
|
100
|
+
log: (error) => console.error('SDK Error:', error), // 커스텀 에러 로거
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**주요 속성:**
|
|
106
|
+
|
|
107
|
+
- `query`: React Query 훅 (`useCollection`, `useCollectionSingle`, `useCollectionInfinite`)
|
|
108
|
+
- `collections`: 컬렉션 API 클라이언트
|
|
109
|
+
- `from()`: Supabase 스타일 쿼리 빌더
|
|
110
|
+
|
|
111
|
+
#### ServerClient
|
|
112
|
+
|
|
113
|
+
서버 환경에서 사용하는 클라이언트로, API 클라이언트를 추가로 제공합니다.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const client = createServerClient({
|
|
117
|
+
clientKey: 'your-client-key',
|
|
118
|
+
secretKey: 'your-secret-key', // 서버 전용
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**추가 속성:**
|
|
123
|
+
|
|
124
|
+
- `api`: 주문 및 트랜잭션 API (`createOrder`, `updateOrder`, `updateTransaction`)
|
|
125
|
+
|
|
126
|
+
### 2. 쿼리 빌더
|
|
127
|
+
|
|
128
|
+
Supabase 스타일의 타입 안전한 쿼리 빌더를 제공합니다.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// 목록 조회
|
|
132
|
+
const { data } = await client.from('products').find({
|
|
133
|
+
limit: 20,
|
|
134
|
+
page: 1,
|
|
135
|
+
sort: '-createdAt',
|
|
136
|
+
where: {
|
|
137
|
+
status: { equals: 'published' },
|
|
138
|
+
price: { greater_than: 1000 },
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// ID로 조회
|
|
143
|
+
const { data: product } = await client.from('products').findById('product_id')
|
|
144
|
+
|
|
145
|
+
// 생성
|
|
146
|
+
const { data: newProduct } = await client.from('products').create({
|
|
147
|
+
name: '새 상품',
|
|
148
|
+
price: 10000,
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// 수정
|
|
152
|
+
const { data: updatedProduct } = await client
|
|
153
|
+
.from('products')
|
|
154
|
+
.update('product_id', {
|
|
155
|
+
name: '수정된 상품명',
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// 삭제
|
|
159
|
+
await client.from('products').remove('product_id')
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 3. React Query 훅
|
|
163
|
+
|
|
164
|
+
데이터 페칭과 캐싱을 위한 React Query 통합을 제공합니다.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// 컬렉션 목록 조회
|
|
168
|
+
const { data, isLoading, error } = client.query.useCollection('products', {
|
|
169
|
+
limit: 10,
|
|
170
|
+
where: { status: { equals: 'published' } },
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
// 단일 항목 조회
|
|
174
|
+
const { data: product } = client.query.useCollectionSingle('products', {
|
|
175
|
+
where: { slug: { equals: 'my-product' } },
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// 무한 스크롤
|
|
179
|
+
const { data, fetchNextPage, hasNextPage } = client.query.useCollectionInfinite(
|
|
180
|
+
'products',
|
|
181
|
+
{
|
|
182
|
+
limit: 20,
|
|
183
|
+
},
|
|
184
|
+
)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 4. Webhook 처리
|
|
188
|
+
|
|
189
|
+
Next.js API Route에서 타입 안전한 webhook 처리를 제공합니다.
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// app/api/webhook/route.ts
|
|
193
|
+
import { handleWebhook } from '@01.software/sdk'
|
|
194
|
+
|
|
195
|
+
export async function POST(request: Request) {
|
|
196
|
+
return handleWebhook(request, async (event) => {
|
|
197
|
+
console.log('Collection:', event.collection)
|
|
198
|
+
console.log('Operation:', event.operation) // 'create' | 'update' | 'delete'
|
|
199
|
+
console.log('Data:', event.data)
|
|
200
|
+
|
|
201
|
+
if (event.collection === 'orders') {
|
|
202
|
+
// 주문 관련 처리
|
|
203
|
+
if (event.operation === 'update') {
|
|
204
|
+
console.log('Order updated:', event.data.id)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
타입 안전한 컬렉션별 핸들러:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { createTypedWebhookHandler } from '@01.software/sdk'
|
|
215
|
+
|
|
216
|
+
const handleOrderWebhook = createTypedWebhookHandler(
|
|
217
|
+
'orders',
|
|
218
|
+
async (event) => {
|
|
219
|
+
// event.data의 타입이 Order로 자동 추론됨
|
|
220
|
+
console.log('Order:', event.data.orderNumber)
|
|
221
|
+
},
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
export async function POST(request: Request) {
|
|
225
|
+
return handleWebhook(request, handleOrderWebhook)
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## API 레퍼런스
|
|
230
|
+
|
|
231
|
+
### 컬렉션 목록
|
|
232
|
+
|
|
233
|
+
SDK는 37개의 컬렉션을 지원합니다:
|
|
234
|
+
|
|
235
|
+
**브랜드 관련**
|
|
236
|
+
|
|
237
|
+
- `brands`, `brand-logos`, `brand-og-images`, `brand-settings`, `brand-secret-keys`
|
|
238
|
+
|
|
239
|
+
**상품 관련**
|
|
240
|
+
|
|
241
|
+
- `products`, `product-variants`, `product-options`, `product-categories`, `product-tags`, `product-images`
|
|
242
|
+
|
|
243
|
+
**주문 관련**
|
|
244
|
+
|
|
245
|
+
- `orders`, `order-products`, `returns`, `return-products`, `transactions`
|
|
246
|
+
|
|
247
|
+
**콘텐츠 관련**
|
|
248
|
+
|
|
249
|
+
- `posts`, `post-categories`, `post-tags`, `post-images`
|
|
250
|
+
- `documents`, `document-images`
|
|
251
|
+
- `entities`, `entity-categories`, `entity-tags`, `entity-images`
|
|
252
|
+
|
|
253
|
+
**미디어 관련**
|
|
254
|
+
|
|
255
|
+
- `playlists`, `playlist-images`, `musics`
|
|
256
|
+
- `galleries`, `gallery-images`
|
|
257
|
+
|
|
258
|
+
**기타**
|
|
259
|
+
|
|
260
|
+
- `links`, `link-images`, `nodes`, `forms`
|
|
261
|
+
|
|
262
|
+
### 유틸리티 함수
|
|
263
|
+
|
|
264
|
+
#### generateOrderNumber()
|
|
265
|
+
|
|
266
|
+
날짜 기반 주문 번호를 생성합니다.
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { generateOrderNumber } from '@01.software/sdk'
|
|
270
|
+
|
|
271
|
+
const orderNumber = generateOrderNumber()
|
|
272
|
+
// 예: "260107123456" (YYMMDDRRRRRR 형식)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### formatOrderName()
|
|
276
|
+
|
|
277
|
+
상품 옵션 배열을 주문명으로 포맷팅합니다.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { formatOrderName } from '@01.software/sdk'
|
|
281
|
+
|
|
282
|
+
const orderName = formatOrderName([
|
|
283
|
+
{ product: { name: '상품 A' }, variant: { name: '옵션 1' } },
|
|
284
|
+
])
|
|
285
|
+
// "상품 A"
|
|
286
|
+
|
|
287
|
+
const orderName2 = formatOrderName([
|
|
288
|
+
{ product: { name: '상품 A' }, variant: { name: '옵션 1' } },
|
|
289
|
+
{ product: { name: '상품 B' }, variant: { name: '옵션 2' } },
|
|
290
|
+
])
|
|
291
|
+
// "상품 A 외 1건"
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### React 컴포넌트
|
|
295
|
+
|
|
296
|
+
#### RichTextContent
|
|
297
|
+
|
|
298
|
+
Payload CMS의 Lexical 리치 텍스트를 렌더링합니다.
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
import { RichTextContent } from '@01.software/sdk'
|
|
302
|
+
|
|
303
|
+
function Article({ content }) {
|
|
304
|
+
return (
|
|
305
|
+
<RichTextContent
|
|
306
|
+
data={content}
|
|
307
|
+
className="prose"
|
|
308
|
+
internalDocToHref={(args) => `/posts/${args.doc.slug}`}
|
|
309
|
+
blocks={{
|
|
310
|
+
Iframe: ({ url }) => <iframe src={url} />,
|
|
311
|
+
Player: ({ videoId }) => <VideoPlayer id={videoId} />
|
|
312
|
+
}}
|
|
313
|
+
/>
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## 고급 사용법
|
|
319
|
+
|
|
320
|
+
### 디버그 모드
|
|
321
|
+
|
|
322
|
+
개발 중 요청/응답을 로깅하여 디버깅을 쉽게 할 수 있습니다.
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// 전체 디버그 모드
|
|
326
|
+
const client = createBrowserClient({
|
|
327
|
+
clientKey: 'your-key',
|
|
328
|
+
debug: true, // 모든 요청/응답/에러 로깅
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
// 선택적 디버그 모드
|
|
332
|
+
const client = createBrowserClient({
|
|
333
|
+
clientKey: 'your-key',
|
|
334
|
+
debug: {
|
|
335
|
+
logRequests: true, // 요청만 로깅
|
|
336
|
+
logResponses: false, // 응답 로깅 안 함
|
|
337
|
+
logErrors: true, // 에러만 로깅
|
|
338
|
+
},
|
|
339
|
+
})
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### 재시도 로직
|
|
343
|
+
|
|
344
|
+
네트워크 오류나 서버 에러 시 자동으로 재시도합니다.
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
const client = createBrowserClient({
|
|
348
|
+
clientKey: 'your-key',
|
|
349
|
+
retry: {
|
|
350
|
+
maxRetries: 3, // 최대 재시도 횟수 (기본값: 3)
|
|
351
|
+
retryableStatuses: [408, 429, 500, 502, 503, 504], // 재시도할 HTTP 상태 코드
|
|
352
|
+
retryDelay: (attempt) => {
|
|
353
|
+
// Exponential backoff: 1초 → 2초 → 4초 → 8초 (최대 10초)
|
|
354
|
+
return Math.min(1000 * 2 ** attempt, 10000)
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
})
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**기본 동작:**
|
|
361
|
+
|
|
362
|
+
- 네트워크 에러는 항상 재시도
|
|
363
|
+
- 408, 429, 500, 502, 503, 504 상태 코드는 재시도
|
|
364
|
+
- 401, 404 등 클라이언트 에러는 재시도하지 않음
|
|
365
|
+
- Exponential backoff 전략 사용
|
|
366
|
+
|
|
367
|
+
### 쿼리 옵션
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// 정렬
|
|
371
|
+
const { data } = await client.from('products').find({
|
|
372
|
+
sort: '-createdAt', // 내림차순
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
// 페이지네이션
|
|
376
|
+
const { data, pagination } = await client.from('products').find({
|
|
377
|
+
page: 2,
|
|
378
|
+
limit: 20,
|
|
379
|
+
})
|
|
380
|
+
console.log(pagination.hasNextPage)
|
|
381
|
+
|
|
382
|
+
// 복잡한 필터
|
|
383
|
+
const { data } = await client.from('products').find({
|
|
384
|
+
where: {
|
|
385
|
+
and: [
|
|
386
|
+
{ status: { equals: 'published' } },
|
|
387
|
+
{ price: { greater_than: 1000, less_than: 50000 } },
|
|
388
|
+
{
|
|
389
|
+
or: [
|
|
390
|
+
{ category: { equals: 'electronics' } },
|
|
391
|
+
{ tags: { contains: 'featured' } },
|
|
392
|
+
],
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
},
|
|
396
|
+
})
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### React Query 옵션
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
const { data } = client.query.useCollection(
|
|
403
|
+
'products',
|
|
404
|
+
{
|
|
405
|
+
limit: 10,
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
staleTime: 5 * 60 * 1000, // 5분
|
|
409
|
+
cacheTime: 10 * 60 * 1000, // 10분
|
|
410
|
+
refetchOnWindowFocus: false,
|
|
411
|
+
keepPreviousData: true,
|
|
412
|
+
},
|
|
413
|
+
)
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### 에러 처리
|
|
417
|
+
|
|
418
|
+
SDK는 사용자 친화적인 에러 메시지와 제안을 제공합니다.
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import {
|
|
422
|
+
SDKError,
|
|
423
|
+
ApiError,
|
|
424
|
+
NetworkError,
|
|
425
|
+
ValidationError,
|
|
426
|
+
TimeoutError,
|
|
427
|
+
isNetworkError,
|
|
428
|
+
} from '@01.software/sdk'
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
const { data } = await client.from('products').find()
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (isNetworkError(error)) {
|
|
434
|
+
// 사용자 친화적 메시지
|
|
435
|
+
console.error(error.getUserMessage())
|
|
436
|
+
// "네트워크 연결을 확인해주세요."
|
|
437
|
+
|
|
438
|
+
// 개발자용 메시지
|
|
439
|
+
console.error(error.message)
|
|
440
|
+
|
|
441
|
+
// 해결 제안
|
|
442
|
+
console.log(error.suggestion)
|
|
443
|
+
// "인터넷 연결을 확인하거나 잠시 후 다시 시도해주세요."
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
#### 타입 안전한 응답 처리
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
import { isSuccessResponse, isErrorResponse } from '@01.software/sdk'
|
|
452
|
+
|
|
453
|
+
const response = await client.from('products').find()
|
|
454
|
+
|
|
455
|
+
if (isSuccessResponse(response)) {
|
|
456
|
+
// TypeScript가 response.data의 타입을 Product[]로 추론
|
|
457
|
+
console.log(response.data)
|
|
458
|
+
console.log(response.pagination)
|
|
459
|
+
} else if (isErrorResponse(response)) {
|
|
460
|
+
// TypeScript가 response.error의 타입을 추론
|
|
461
|
+
console.error(response.error.code)
|
|
462
|
+
console.error(response.error.message)
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## 보안
|
|
467
|
+
|
|
468
|
+
### 환경 변수 설정
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
# .env.local
|
|
472
|
+
NEXT_PUBLIC_SOFTWARE_CLIENT_KEY=your_client_key
|
|
473
|
+
SOFTWARE_SECRET_KEY=your_secret_key # 서버 전용
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### 주의사항
|
|
477
|
+
|
|
478
|
+
- `clientKey`는 브라우저에 노출되어도 안전합니다.
|
|
479
|
+
- `secretKey`는 **절대** 브라우저에 노출되어서는 안 됩니다.
|
|
480
|
+
- `secretKey`는 서버 환경(API Route, Server Component 등)에서만 사용하세요.
|
|
481
|
+
|
|
482
|
+
## 타입 시스템
|
|
483
|
+
|
|
484
|
+
모든 컬렉션은 자동으로 타입이 추론됩니다:
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
// 타입이 자동으로 추론됨
|
|
488
|
+
const { data: products } = await client.from('products').find()
|
|
489
|
+
// products의 타입: Product[]
|
|
490
|
+
|
|
491
|
+
const { data: product } = await client.from('products').findById('id')
|
|
492
|
+
// product의 타입: Product
|
|
493
|
+
|
|
494
|
+
// 생성 시에도 타입 체크
|
|
495
|
+
await client.from('products').create({
|
|
496
|
+
name: '상품명', // ✅ OK
|
|
497
|
+
price: 10000, // ✅ OK
|
|
498
|
+
invalid: 'field', // ❌ 타입 에러
|
|
499
|
+
})
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## 개발
|
|
503
|
+
|
|
504
|
+
SDK는 포괄적인 테스트를 포함하고 있으며, 타입 안전성과 사용자 경험을 최우선으로 설계되었습니다.
|
|
505
|
+
|
|
506
|
+
### 테스트
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
# 모든 테스트 실행
|
|
510
|
+
pnpm test
|
|
511
|
+
|
|
512
|
+
# watch 모드로 실행
|
|
513
|
+
pnpm test:watch
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### 주요 특징
|
|
517
|
+
|
|
518
|
+
- ✅ **완전한 타입 안전성**: TypeScript 기반 타입 추론
|
|
519
|
+
- ✅ **사용자 친화적 에러 처리**: 명확한 에러 메시지와 해결 제안
|
|
520
|
+
- ✅ **자동 재시도**: 네트워크 오류 시 exponential backoff 전략
|
|
521
|
+
- ✅ **디버그 모드**: 개발 중 요청/응답/에러 로깅
|
|
522
|
+
- ✅ **React Query 통합**: 효율적인 데이터 페칭 및 캐싱
|
|
523
|
+
|
|
524
|
+
## 라이센스
|
|
525
|
+
|
|
526
|
+
MIT
|
|
527
|
+
|
|
528
|
+
## 지원
|
|
529
|
+
|
|
530
|
+
문제가 발생하거나 궁금한 점이 있다면 이슈를 등록해주세요.
|