@01.software/sdk 0.1.0-dev.260109.7cf07c9 → 0.1.0-dev.260206.8918543
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 +124 -404
- package/dist/index.cjs +261 -118
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +320 -33
- package/dist/index.d.ts +320 -33
- package/dist/index.js +266 -122
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -1,58 +1,42 @@
|
|
|
1
1
|
# @01.software/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official TypeScript SDK for the 01.software platform.
|
|
4
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
|
-
## 설치
|
|
5
|
+
## Installation
|
|
18
6
|
|
|
19
7
|
```bash
|
|
20
8
|
npm install @01.software/sdk
|
|
21
|
-
#
|
|
22
|
-
yarn add @01.software/sdk
|
|
23
|
-
# 또는
|
|
9
|
+
# or
|
|
24
10
|
pnpm add @01.software/sdk
|
|
25
11
|
```
|
|
26
12
|
|
|
27
|
-
##
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Full TypeScript type inference
|
|
16
|
+
- Browser and server environment support
|
|
17
|
+
- React Query integration
|
|
18
|
+
- Automatic retry with exponential backoff
|
|
19
|
+
- Webhook handling
|
|
20
|
+
|
|
21
|
+
## Getting Started
|
|
28
22
|
|
|
29
|
-
###
|
|
23
|
+
### Browser Client
|
|
30
24
|
|
|
31
25
|
```typescript
|
|
32
26
|
import { createBrowserClient } from '@01.software/sdk'
|
|
33
27
|
|
|
34
28
|
const client = createBrowserClient({
|
|
35
|
-
clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY
|
|
29
|
+
clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY,
|
|
36
30
|
})
|
|
37
31
|
|
|
38
|
-
//
|
|
39
|
-
const { data
|
|
32
|
+
// Query data
|
|
33
|
+
const { data } = await client.from('products').find({
|
|
40
34
|
limit: 10,
|
|
41
|
-
where: { status: { equals: 'published' } }
|
|
35
|
+
where: { status: { equals: 'published' } },
|
|
42
36
|
})
|
|
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
37
|
```
|
|
54
38
|
|
|
55
|
-
###
|
|
39
|
+
### Server Client
|
|
56
40
|
|
|
57
41
|
```typescript
|
|
58
42
|
import { createServerClient } from '@01.software/sdk'
|
|
@@ -62,469 +46,205 @@ const client = createServerClient({
|
|
|
62
46
|
secretKey: process.env.SOFTWARE_SECRET_KEY,
|
|
63
47
|
})
|
|
64
48
|
|
|
65
|
-
//
|
|
49
|
+
// Create order (server only)
|
|
66
50
|
const order = await client.api.createOrder({
|
|
67
51
|
paymentId: 'pay_123',
|
|
68
52
|
orderNumber: generateOrderNumber(),
|
|
69
53
|
email: 'user@example.com',
|
|
70
|
-
orderProducts: [
|
|
71
|
-
{
|
|
72
|
-
product: 'product_id',
|
|
73
|
-
variant: 'variant_id',
|
|
74
|
-
quantity: 1,
|
|
75
|
-
price: 10000,
|
|
76
|
-
},
|
|
77
|
-
],
|
|
54
|
+
orderProducts: [...],
|
|
78
55
|
totalAmount: 10000,
|
|
79
56
|
})
|
|
80
57
|
```
|
|
81
58
|
|
|
82
|
-
##
|
|
83
|
-
|
|
84
|
-
### 1. 클라이언트
|
|
59
|
+
## API
|
|
85
60
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
브라우저 환경에서 사용하는 클라이언트로, React Query 통합과 쿼리 빌더를 제공합니다.
|
|
61
|
+
### Client Configuration
|
|
89
62
|
|
|
90
63
|
```typescript
|
|
91
64
|
const client = createBrowserClient({
|
|
92
|
-
clientKey:
|
|
93
|
-
baseUrl
|
|
94
|
-
debug
|
|
95
|
-
retry
|
|
96
|
-
|
|
97
|
-
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 10000), // Exponential backoff
|
|
98
|
-
},
|
|
99
|
-
errorLogger: {
|
|
100
|
-
log: (error) => console.error('SDK Error:', error), // 커스텀 에러 로거
|
|
101
|
-
},
|
|
65
|
+
clientKey: string, // Required
|
|
66
|
+
baseUrl?: string, // API URL (optional)
|
|
67
|
+
debug?: boolean | DebugOptions,
|
|
68
|
+
retry?: RetryOptions,
|
|
69
|
+
errorLogger?: ErrorLogger,
|
|
102
70
|
})
|
|
103
71
|
```
|
|
104
72
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
73
|
+
| Option | Type | Description |
|
|
74
|
+
| ----------- | ------------------------- | ---------------------------- |
|
|
75
|
+
| `clientKey` | `string` | API client key |
|
|
76
|
+
| `secretKey` | `string` | API secret key (server only) |
|
|
77
|
+
| `baseUrl` | `string` | API base URL |
|
|
78
|
+
| `debug` | `boolean \| DebugOptions` | Enable debug logging |
|
|
79
|
+
| `retry` | `RetryOptions` | Retry configuration |
|
|
110
80
|
|
|
111
|
-
|
|
81
|
+
### Query Builder
|
|
112
82
|
|
|
113
|
-
|
|
83
|
+
Access collections via `client.from(collection)` method.
|
|
114
84
|
|
|
115
85
|
```typescript
|
|
116
|
-
|
|
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
|
-
// 목록 조회
|
|
86
|
+
// List query
|
|
132
87
|
const { data } = await client.from('products').find({
|
|
133
88
|
limit: 20,
|
|
134
89
|
page: 1,
|
|
135
90
|
sort: '-createdAt',
|
|
136
|
-
where: {
|
|
137
|
-
status: { equals: 'published' },
|
|
138
|
-
price: { greater_than: 1000 },
|
|
139
|
-
},
|
|
91
|
+
where: { status: { equals: 'published' } },
|
|
140
92
|
})
|
|
141
93
|
|
|
142
|
-
//
|
|
143
|
-
const { data
|
|
94
|
+
// Single item query
|
|
95
|
+
const { data } = await client.from('products').findById('id')
|
|
144
96
|
|
|
145
|
-
//
|
|
146
|
-
const { data
|
|
147
|
-
name: '새 상품',
|
|
148
|
-
price: 10000,
|
|
149
|
-
})
|
|
97
|
+
// Create (server only)
|
|
98
|
+
const { data } = await client.from('products').create({ name: 'Product' })
|
|
150
99
|
|
|
151
|
-
//
|
|
152
|
-
const { data
|
|
153
|
-
.from('products')
|
|
154
|
-
.update('product_id', {
|
|
155
|
-
name: '수정된 상품명',
|
|
156
|
-
})
|
|
100
|
+
// Update (server only)
|
|
101
|
+
const { data } = await client.from('products').update('id', { name: 'Updated' })
|
|
157
102
|
|
|
158
|
-
//
|
|
159
|
-
await client.from('products').remove('
|
|
103
|
+
// Delete (server only)
|
|
104
|
+
await client.from('products').remove('id')
|
|
160
105
|
```
|
|
161
106
|
|
|
162
|
-
###
|
|
163
|
-
|
|
164
|
-
데이터 페칭과 캐싱을 위한 React Query 통합을 제공합니다.
|
|
107
|
+
### React Query Hooks
|
|
165
108
|
|
|
166
109
|
```typescript
|
|
167
|
-
//
|
|
168
|
-
const { data, isLoading
|
|
169
|
-
|
|
170
|
-
|
|
110
|
+
// List query
|
|
111
|
+
const { data, isLoading } = client.query.useQuery({
|
|
112
|
+
collection: 'products',
|
|
113
|
+
options: { limit: 10 },
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// Suspense mode
|
|
117
|
+
const { data } = client.query.useSuspenseQuery({
|
|
118
|
+
collection: 'products',
|
|
119
|
+
options: { limit: 10 },
|
|
171
120
|
})
|
|
172
121
|
|
|
173
|
-
//
|
|
174
|
-
const { data
|
|
175
|
-
|
|
122
|
+
// Query by ID
|
|
123
|
+
const { data } = client.query.useQueryById({
|
|
124
|
+
collection: 'products',
|
|
125
|
+
id: 'product_id',
|
|
176
126
|
})
|
|
177
127
|
|
|
178
|
-
//
|
|
179
|
-
const { data, fetchNextPage, hasNextPage } = client.query.
|
|
180
|
-
'products',
|
|
181
|
-
{
|
|
182
|
-
|
|
183
|
-
},
|
|
184
|
-
)
|
|
128
|
+
// Infinite scroll
|
|
129
|
+
const { data, fetchNextPage, hasNextPage } = client.query.useInfiniteQuery({
|
|
130
|
+
collection: 'products',
|
|
131
|
+
options: { limit: 20 },
|
|
132
|
+
})
|
|
185
133
|
```
|
|
186
134
|
|
|
187
|
-
###
|
|
135
|
+
### Server API
|
|
188
136
|
|
|
189
|
-
|
|
137
|
+
Available only in ServerClient.
|
|
190
138
|
|
|
191
139
|
```typescript
|
|
192
|
-
//
|
|
193
|
-
|
|
140
|
+
// Create order
|
|
141
|
+
await client.api.createOrder(data)
|
|
194
142
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (event.collection === 'orders') {
|
|
202
|
-
// 주문 관련 처리
|
|
203
|
-
if (event.operation === 'update') {
|
|
204
|
-
console.log('Order updated:', event.data.id)
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
})
|
|
208
|
-
}
|
|
143
|
+
// Update order
|
|
144
|
+
await client.api.updateOrder(id, data)
|
|
145
|
+
|
|
146
|
+
// Update transaction
|
|
147
|
+
await client.api.updateTransaction(id, data)
|
|
209
148
|
```
|
|
210
149
|
|
|
211
|
-
|
|
150
|
+
### Webhook
|
|
212
151
|
|
|
213
152
|
```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
|
-
)
|
|
153
|
+
import { handleWebhook, createTypedWebhookHandler } from '@01.software/sdk'
|
|
223
154
|
|
|
155
|
+
// Basic handler
|
|
224
156
|
export async function POST(request: Request) {
|
|
225
|
-
return handleWebhook(request,
|
|
157
|
+
return handleWebhook(request, async (event) => {
|
|
158
|
+
console.log(event.collection, event.operation, event.data)
|
|
159
|
+
})
|
|
226
160
|
}
|
|
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
161
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
**기타**
|
|
162
|
+
// Type-safe handler
|
|
163
|
+
const handler = createTypedWebhookHandler('orders', async (event) => {
|
|
164
|
+
// event.data is typed as Order
|
|
165
|
+
console.log(event.data.orderNumber)
|
|
166
|
+
})
|
|
167
|
+
```
|
|
259
168
|
|
|
260
|
-
|
|
169
|
+
## Supported Collections
|
|
261
170
|
|
|
262
|
-
|
|
171
|
+
| Category | Collections |
|
|
172
|
+
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
173
|
+
| Tenant | `tenants`, `tenant-metadata`, `tenant-logos`, `tenant-og-images` |
|
|
174
|
+
| Products | `products`, `product-variants`, `product-options`, `product-categories`, `product-tags`, `product-images`, `brands`, `brand-logos` |
|
|
175
|
+
| Orders | `orders`, `order-products`, `returns`, `return-products`, `transactions` |
|
|
176
|
+
| Content | `posts`, `post-categories`, `post-tags`, `post-images`, `documents`, `document-categories`, `document-images` |
|
|
177
|
+
| Media | `playlists`, `playlist-images`, `musics`, `galleries`, `gallery-images`, `media` |
|
|
263
178
|
|
|
264
|
-
|
|
179
|
+
## Utilities
|
|
265
180
|
|
|
266
|
-
|
|
181
|
+
### generateOrderNumber
|
|
267
182
|
|
|
268
183
|
```typescript
|
|
269
184
|
import { generateOrderNumber } from '@01.software/sdk'
|
|
270
185
|
|
|
271
186
|
const orderNumber = generateOrderNumber()
|
|
272
|
-
//
|
|
187
|
+
// "260121123456" (YYMMDDRRRRRR format)
|
|
273
188
|
```
|
|
274
189
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
상품 옵션 배열을 주문명으로 포맷팅합니다.
|
|
190
|
+
### formatOrderName
|
|
278
191
|
|
|
279
192
|
```typescript
|
|
280
193
|
import { formatOrderName } from '@01.software/sdk'
|
|
281
194
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
])
|
|
285
|
-
// "상품 A"
|
|
195
|
+
formatOrderName([{ product: { name: 'Product A' } }])
|
|
196
|
+
// "Product A"
|
|
286
197
|
|
|
287
|
-
|
|
288
|
-
{ product: { name: '
|
|
289
|
-
{ product: { name: '
|
|
198
|
+
formatOrderName([
|
|
199
|
+
{ product: { name: 'Product A' } },
|
|
200
|
+
{ product: { name: 'Product B' } },
|
|
290
201
|
])
|
|
291
|
-
// "
|
|
202
|
+
// "Product A and 1 more"
|
|
292
203
|
```
|
|
293
204
|
|
|
294
|
-
###
|
|
295
|
-
|
|
296
|
-
#### RichTextContent
|
|
205
|
+
### RichTextContent
|
|
297
206
|
|
|
298
|
-
Payload CMS
|
|
207
|
+
React component for rendering Payload CMS Lexical rich text.
|
|
299
208
|
|
|
300
|
-
```
|
|
209
|
+
```tsx
|
|
301
210
|
import { RichTextContent } from '@01.software/sdk'
|
|
302
211
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
)
|
|
212
|
+
;<RichTextContent
|
|
213
|
+
data={content}
|
|
214
|
+
className="prose"
|
|
215
|
+
internalDocToHref={(args) => `/posts/${args.doc.slug}`}
|
|
216
|
+
blocks={{
|
|
217
|
+
Iframe: ({ url }) => <iframe src={url} />,
|
|
218
|
+
Player: ({ videoId }) => <VideoPlayer id={videoId} />,
|
|
219
|
+
}}
|
|
220
|
+
/>
|
|
414
221
|
```
|
|
415
222
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
SDK는 사용자 친화적인 에러 메시지와 제안을 제공합니다.
|
|
223
|
+
## Error Handling
|
|
419
224
|
|
|
420
225
|
```typescript
|
|
421
226
|
import {
|
|
422
|
-
SDKError,
|
|
423
|
-
ApiError,
|
|
424
|
-
NetworkError,
|
|
425
|
-
ValidationError,
|
|
426
|
-
TimeoutError,
|
|
427
227
|
isNetworkError,
|
|
228
|
+
isSuccessResponse,
|
|
229
|
+
isErrorResponse,
|
|
428
230
|
} from '@01.software/sdk'
|
|
429
231
|
|
|
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
232
|
const response = await client.from('products').find()
|
|
454
233
|
|
|
455
234
|
if (isSuccessResponse(response)) {
|
|
456
|
-
// TypeScript가 response.data의 타입을 Product[]로 추론
|
|
457
235
|
console.log(response.data)
|
|
458
|
-
console.log(response.pagination)
|
|
459
236
|
} else if (isErrorResponse(response)) {
|
|
460
|
-
// TypeScript가 response.error의 타입을 추론
|
|
461
|
-
console.error(response.error.code)
|
|
462
237
|
console.error(response.error.message)
|
|
463
238
|
}
|
|
464
239
|
```
|
|
465
240
|
|
|
466
|
-
|
|
241
|
+
Error classes: `SDKError`, `ApiError`, `NetworkError`, `ValidationError`, `TimeoutError`
|
|
467
242
|
|
|
468
|
-
|
|
243
|
+
## Environment Variables
|
|
469
244
|
|
|
470
245
|
```bash
|
|
471
|
-
# .env.local
|
|
472
246
|
NEXT_PUBLIC_SOFTWARE_CLIENT_KEY=your_client_key
|
|
473
|
-
SOFTWARE_SECRET_KEY=your_secret_key
|
|
247
|
+
SOFTWARE_SECRET_KEY=your_secret_key # Server only
|
|
474
248
|
```
|
|
475
249
|
|
|
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
|
-
문제가 발생하거나 궁금한 점이 있다면 이슈를 등록해주세요.
|
|
250
|
+
> `secretKey` should only be used in server environments. Never expose it to the browser.
|