@01.software/sdk 0.1.0-dev.260119.0a66443 → 0.1.0-dev.260210.4ecca43
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 +146 -399
- package/dist/index.cjs +270 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +154 -50
- package/dist/index.d.ts +154 -50
- package/dist/index.js +270 -115
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -1,59 +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
|
|
28
20
|
|
|
29
|
-
|
|
21
|
+
## Getting Started
|
|
22
|
+
|
|
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.useQuery({
|
|
47
|
-
collection: 'products',
|
|
48
|
-
options: { limit: 10 }
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
if (isLoading) return <div>Loading...</div>
|
|
52
|
-
return <div>{data?.map(p => p.name)}</div>
|
|
53
|
-
}
|
|
54
37
|
```
|
|
55
38
|
|
|
56
|
-
###
|
|
39
|
+
### Server Client
|
|
57
40
|
|
|
58
41
|
```typescript
|
|
59
42
|
import { createServerClient } from '@01.software/sdk'
|
|
@@ -63,476 +46,240 @@ const client = createServerClient({
|
|
|
63
46
|
secretKey: process.env.SOFTWARE_SECRET_KEY,
|
|
64
47
|
})
|
|
65
48
|
|
|
66
|
-
//
|
|
49
|
+
// Create order (server only)
|
|
67
50
|
const order = await client.api.createOrder({
|
|
68
51
|
paymentId: 'pay_123',
|
|
69
52
|
orderNumber: generateOrderNumber(),
|
|
70
53
|
email: 'user@example.com',
|
|
71
|
-
orderProducts: [
|
|
72
|
-
{
|
|
73
|
-
product: 'product_id',
|
|
74
|
-
variant: 'variant_id',
|
|
75
|
-
quantity: 1,
|
|
76
|
-
price: 10000,
|
|
77
|
-
},
|
|
78
|
-
],
|
|
54
|
+
orderProducts: [...],
|
|
79
55
|
totalAmount: 10000,
|
|
80
56
|
})
|
|
81
57
|
```
|
|
82
58
|
|
|
83
|
-
##
|
|
84
|
-
|
|
85
|
-
### 1. 클라이언트
|
|
59
|
+
## API
|
|
86
60
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
브라우저 환경에서 사용하는 클라이언트로, React Query 통합과 쿼리 빌더를 제공합니다.
|
|
61
|
+
### Client Configuration
|
|
90
62
|
|
|
91
63
|
```typescript
|
|
92
64
|
const client = createBrowserClient({
|
|
93
|
-
clientKey:
|
|
94
|
-
baseUrl
|
|
95
|
-
debug
|
|
96
|
-
retry
|
|
97
|
-
|
|
98
|
-
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 10000), // Exponential backoff
|
|
99
|
-
},
|
|
100
|
-
errorLogger: {
|
|
101
|
-
log: (error) => console.error('SDK Error:', error), // 커스텀 에러 로거
|
|
102
|
-
},
|
|
65
|
+
clientKey: string, // Required
|
|
66
|
+
baseUrl?: string, // API URL (optional)
|
|
67
|
+
debug?: boolean | DebugOptions,
|
|
68
|
+
retry?: RetryOptions,
|
|
69
|
+
errorLogger?: ErrorLogger,
|
|
103
70
|
})
|
|
104
71
|
```
|
|
105
72
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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 |
|
|
111
80
|
|
|
112
|
-
|
|
81
|
+
### Query Builder
|
|
113
82
|
|
|
114
|
-
|
|
83
|
+
Access collections via `client.from(collection)` method.
|
|
115
84
|
|
|
116
85
|
```typescript
|
|
117
|
-
|
|
118
|
-
clientKey: 'your-client-key',
|
|
119
|
-
secretKey: 'your-secret-key', // 서버 전용
|
|
120
|
-
})
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
**추가 속성:**
|
|
124
|
-
|
|
125
|
-
- `api`: 주문 및 트랜잭션 API (`createOrder`, `updateOrder`, `updateTransaction`)
|
|
126
|
-
|
|
127
|
-
### 2. 쿼리 빌더
|
|
128
|
-
|
|
129
|
-
Supabase 스타일의 타입 안전한 쿼리 빌더를 제공합니다.
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
// 목록 조회
|
|
86
|
+
// List query
|
|
133
87
|
const { data } = await client.from('products').find({
|
|
134
88
|
limit: 20,
|
|
135
89
|
page: 1,
|
|
136
90
|
sort: '-createdAt',
|
|
137
|
-
where: {
|
|
138
|
-
status: { equals: 'published' },
|
|
139
|
-
price: { greater_than: 1000 },
|
|
140
|
-
},
|
|
91
|
+
where: { status: { equals: 'published' } },
|
|
141
92
|
})
|
|
142
93
|
|
|
143
|
-
//
|
|
144
|
-
const { data
|
|
94
|
+
// Single item query
|
|
95
|
+
const { data } = await client.from('products').findById('id')
|
|
145
96
|
|
|
146
|
-
//
|
|
147
|
-
const { data
|
|
148
|
-
name: '새 상품',
|
|
149
|
-
price: 10000,
|
|
150
|
-
})
|
|
97
|
+
// Create (server only)
|
|
98
|
+
const { data } = await client.from('products').create({ name: 'Product' })
|
|
151
99
|
|
|
152
|
-
//
|
|
153
|
-
const { data
|
|
154
|
-
.from('products')
|
|
155
|
-
.update('product_id', {
|
|
156
|
-
name: '수정된 상품명',
|
|
157
|
-
})
|
|
100
|
+
// Update (server only)
|
|
101
|
+
const { data } = await client.from('products').update('id', { name: 'Updated' })
|
|
158
102
|
|
|
159
|
-
//
|
|
160
|
-
await client.from('products').remove('
|
|
103
|
+
// Delete (server only)
|
|
104
|
+
await client.from('products').remove('id')
|
|
161
105
|
```
|
|
162
106
|
|
|
163
|
-
###
|
|
107
|
+
### API Response Structure
|
|
164
108
|
|
|
165
|
-
|
|
109
|
+
All SDK methods return a standardized response format:
|
|
166
110
|
|
|
167
111
|
```typescript
|
|
168
|
-
//
|
|
169
|
-
|
|
112
|
+
// Success Response
|
|
113
|
+
interface ApiSuccessResponse<T> {
|
|
114
|
+
data: T // Actual data
|
|
115
|
+
success: true
|
|
116
|
+
message?: string // Optional message from server
|
|
117
|
+
pagination?: PaginationMeta // For list queries
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Error Response
|
|
121
|
+
interface ApiErrorResponse {
|
|
122
|
+
data: null
|
|
123
|
+
success: false
|
|
124
|
+
error: {
|
|
125
|
+
code: string
|
|
126
|
+
message: string
|
|
127
|
+
details?: unknown
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Payload CMS API Mapping:**
|
|
133
|
+
|
|
134
|
+
| Operation | Payload Response | SDK Response |
|
|
135
|
+
|-----------|-----------------|--------------|
|
|
136
|
+
| `find()` | `{docs: [], totalDocs, ...}` | `{data: [], success: true, pagination: {...}}` |
|
|
137
|
+
| `findById()` | `{...document}` | `{data: {...}, success: true}` |
|
|
138
|
+
| `create()` | `{doc: {...}, message}` | `{data: {...}, success: true, message}` |
|
|
139
|
+
| `update()` | `{doc: {...}, message}` | `{data: {...}, success: true, message}` |
|
|
140
|
+
| `remove()` | `{doc: {...}, message}` | `{data: {...}, success: true, message}` |
|
|
141
|
+
|
|
142
|
+
### React Query Hooks
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// List query
|
|
146
|
+
const { data, isLoading } = client.query.useQuery({
|
|
170
147
|
collection: 'products',
|
|
171
|
-
options: {
|
|
172
|
-
limit: 10,
|
|
173
|
-
where: { status: { equals: 'published' } },
|
|
174
|
-
},
|
|
148
|
+
options: { limit: 10 },
|
|
175
149
|
})
|
|
176
150
|
|
|
177
|
-
// Suspense
|
|
151
|
+
// Suspense mode
|
|
178
152
|
const { data } = client.query.useSuspenseQuery({
|
|
179
153
|
collection: 'products',
|
|
180
154
|
options: { limit: 10 },
|
|
181
155
|
})
|
|
182
156
|
|
|
183
|
-
//
|
|
184
|
-
const { data
|
|
157
|
+
// Query by ID
|
|
158
|
+
const { data } = client.query.useQueryById({
|
|
185
159
|
collection: 'products',
|
|
186
160
|
id: 'product_id',
|
|
187
161
|
})
|
|
188
162
|
|
|
189
|
-
//
|
|
163
|
+
// Infinite scroll
|
|
190
164
|
const { data, fetchNextPage, hasNextPage } = client.query.useInfiniteQuery({
|
|
191
165
|
collection: 'products',
|
|
192
166
|
options: { limit: 20 },
|
|
193
167
|
})
|
|
194
168
|
```
|
|
195
169
|
|
|
196
|
-
###
|
|
170
|
+
### Server API
|
|
197
171
|
|
|
198
|
-
|
|
172
|
+
Available only in ServerClient.
|
|
199
173
|
|
|
200
174
|
```typescript
|
|
201
|
-
//
|
|
202
|
-
|
|
175
|
+
// Create order
|
|
176
|
+
await client.api.createOrder(data)
|
|
203
177
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (event.collection === 'orders') {
|
|
211
|
-
// 주문 관련 처리
|
|
212
|
-
if (event.operation === 'update') {
|
|
213
|
-
console.log('Order updated:', event.data.id)
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
})
|
|
217
|
-
}
|
|
178
|
+
// Update order
|
|
179
|
+
await client.api.updateOrder(id, data)
|
|
180
|
+
|
|
181
|
+
// Update transaction
|
|
182
|
+
await client.api.updateTransaction(id, data)
|
|
218
183
|
```
|
|
219
184
|
|
|
220
|
-
|
|
185
|
+
### Webhook
|
|
221
186
|
|
|
222
187
|
```typescript
|
|
223
|
-
import { createTypedWebhookHandler } from '@01.software/sdk'
|
|
224
|
-
|
|
225
|
-
const handleOrderWebhook = createTypedWebhookHandler(
|
|
226
|
-
'orders',
|
|
227
|
-
async (event) => {
|
|
228
|
-
// event.data의 타입이 Order로 자동 추론됨
|
|
229
|
-
console.log('Order:', event.data.orderNumber)
|
|
230
|
-
},
|
|
231
|
-
)
|
|
188
|
+
import { handleWebhook, createTypedWebhookHandler } from '@01.software/sdk'
|
|
232
189
|
|
|
190
|
+
// Basic handler
|
|
233
191
|
export async function POST(request: Request) {
|
|
234
|
-
return handleWebhook(request,
|
|
192
|
+
return handleWebhook(request, async (event) => {
|
|
193
|
+
console.log(event.collection, event.operation, event.data)
|
|
194
|
+
})
|
|
235
195
|
}
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
## API 레퍼런스
|
|
239
|
-
|
|
240
|
-
### 컬렉션 목록
|
|
241
|
-
|
|
242
|
-
SDK는 37개의 컬렉션을 지원합니다:
|
|
243
|
-
|
|
244
|
-
**브랜드 관련**
|
|
245
|
-
|
|
246
|
-
- `brands`, `brand-logos`, `brand-og-images`, `brand-settings`, `brand-secret-keys`
|
|
247
196
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
- `orders`, `order-products`, `returns`, `return-products`, `transactions`
|
|
255
|
-
|
|
256
|
-
**콘텐츠 관련**
|
|
257
|
-
|
|
258
|
-
- `posts`, `post-categories`, `post-tags`, `post-images`
|
|
259
|
-
- `documents`, `document-images`
|
|
260
|
-
- `entities`, `entity-categories`, `entity-tags`, `entity-images`
|
|
261
|
-
|
|
262
|
-
**미디어 관련**
|
|
263
|
-
|
|
264
|
-
- `playlists`, `playlist-images`, `musics`
|
|
265
|
-
- `galleries`, `gallery-images`
|
|
266
|
-
|
|
267
|
-
**기타**
|
|
197
|
+
// Type-safe handler
|
|
198
|
+
const handler = createTypedWebhookHandler('orders', async (event) => {
|
|
199
|
+
// event.data is typed as Order
|
|
200
|
+
console.log(event.data.orderNumber)
|
|
201
|
+
})
|
|
202
|
+
```
|
|
268
203
|
|
|
269
|
-
|
|
204
|
+
## Supported Collections
|
|
270
205
|
|
|
271
|
-
|
|
206
|
+
| Category | Collections |
|
|
207
|
+
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
208
|
+
| Tenant | `tenants`, `tenant-metadata`, `tenant-logos`, `tenant-og-images` |
|
|
209
|
+
| Products | `products`, `product-variants`, `product-options`, `product-categories`, `product-tags`, `product-images`, `brands`, `brand-logos` |
|
|
210
|
+
| Orders | `orders`, `order-products`, `returns`, `return-products`, `transactions` |
|
|
211
|
+
| Content | `posts`, `post-categories`, `post-tags`, `post-images`, `documents`, `document-categories`, `document-images` |
|
|
212
|
+
| Media | `playlists`, `playlist-images`, `musics`, `galleries`, `gallery-images`, `media` |
|
|
272
213
|
|
|
273
|
-
|
|
214
|
+
## Utilities
|
|
274
215
|
|
|
275
|
-
|
|
216
|
+
### generateOrderNumber
|
|
276
217
|
|
|
277
218
|
```typescript
|
|
278
219
|
import { generateOrderNumber } from '@01.software/sdk'
|
|
279
220
|
|
|
280
221
|
const orderNumber = generateOrderNumber()
|
|
281
|
-
//
|
|
222
|
+
// "260121123456" (YYMMDDRRRRRR format)
|
|
282
223
|
```
|
|
283
224
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
상품 옵션 배열을 주문명으로 포맷팅합니다.
|
|
225
|
+
### formatOrderName
|
|
287
226
|
|
|
288
227
|
```typescript
|
|
289
228
|
import { formatOrderName } from '@01.software/sdk'
|
|
290
229
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
])
|
|
294
|
-
// "상품 A"
|
|
230
|
+
formatOrderName([{ product: { name: 'Product A' } }])
|
|
231
|
+
// "Product A"
|
|
295
232
|
|
|
296
|
-
|
|
297
|
-
{ product: { name: '
|
|
298
|
-
{ product: { name: '
|
|
233
|
+
formatOrderName([
|
|
234
|
+
{ product: { name: 'Product A' } },
|
|
235
|
+
{ product: { name: 'Product B' } },
|
|
299
236
|
])
|
|
300
|
-
// "
|
|
237
|
+
// "Product A and 1 more"
|
|
301
238
|
```
|
|
302
239
|
|
|
303
|
-
###
|
|
240
|
+
### RichTextContent
|
|
304
241
|
|
|
305
|
-
|
|
242
|
+
React component for rendering Payload CMS Lexical rich text.
|
|
306
243
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
```typescript
|
|
244
|
+
```tsx
|
|
310
245
|
import { RichTextContent } from '@01.software/sdk'
|
|
311
246
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}}
|
|
322
|
-
/>
|
|
323
|
-
)
|
|
324
|
-
}
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
## 고급 사용법
|
|
328
|
-
|
|
329
|
-
### 디버그 모드
|
|
330
|
-
|
|
331
|
-
개발 중 요청/응답을 로깅하여 디버깅을 쉽게 할 수 있습니다.
|
|
332
|
-
|
|
333
|
-
```typescript
|
|
334
|
-
// 전체 디버그 모드
|
|
335
|
-
const client = createBrowserClient({
|
|
336
|
-
clientKey: 'your-key',
|
|
337
|
-
debug: true, // 모든 요청/응답/에러 로깅
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
// 선택적 디버그 모드
|
|
341
|
-
const client = createBrowserClient({
|
|
342
|
-
clientKey: 'your-key',
|
|
343
|
-
debug: {
|
|
344
|
-
logRequests: true, // 요청만 로깅
|
|
345
|
-
logResponses: false, // 응답 로깅 안 함
|
|
346
|
-
logErrors: true, // 에러만 로깅
|
|
347
|
-
},
|
|
348
|
-
})
|
|
247
|
+
;<RichTextContent
|
|
248
|
+
data={content}
|
|
249
|
+
className="prose"
|
|
250
|
+
internalDocToHref={(args) => `/posts/${args.doc.slug}`}
|
|
251
|
+
blocks={{
|
|
252
|
+
Iframe: ({ url }) => <iframe src={url} />,
|
|
253
|
+
Player: ({ videoId }) => <VideoPlayer id={videoId} />,
|
|
254
|
+
}}
|
|
255
|
+
/>
|
|
349
256
|
```
|
|
350
257
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
네트워크 오류나 서버 에러 시 자동으로 재시도합니다.
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
const client = createBrowserClient({
|
|
357
|
-
clientKey: 'your-key',
|
|
358
|
-
retry: {
|
|
359
|
-
maxRetries: 3, // 최대 재시도 횟수 (기본값: 3)
|
|
360
|
-
retryableStatuses: [408, 429, 500, 502, 503, 504], // 재시도할 HTTP 상태 코드
|
|
361
|
-
retryDelay: (attempt) => {
|
|
362
|
-
// Exponential backoff: 1초 → 2초 → 4초 → 8초 (최대 10초)
|
|
363
|
-
return Math.min(1000 * 2 ** attempt, 10000)
|
|
364
|
-
},
|
|
365
|
-
},
|
|
366
|
-
})
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
**기본 동작:**
|
|
370
|
-
|
|
371
|
-
- 네트워크 에러는 항상 재시도
|
|
372
|
-
- 408, 429, 500, 502, 503, 504 상태 코드는 재시도
|
|
373
|
-
- 401, 404 등 클라이언트 에러는 재시도하지 않음
|
|
374
|
-
- Exponential backoff 전략 사용
|
|
375
|
-
|
|
376
|
-
### 쿼리 옵션
|
|
377
|
-
|
|
378
|
-
```typescript
|
|
379
|
-
// 정렬
|
|
380
|
-
const { data } = await client.from('products').find({
|
|
381
|
-
sort: '-createdAt', // 내림차순
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
// 페이지네이션
|
|
385
|
-
const { data, pagination } = await client.from('products').find({
|
|
386
|
-
page: 2,
|
|
387
|
-
limit: 20,
|
|
388
|
-
})
|
|
389
|
-
console.log(pagination.hasNextPage)
|
|
390
|
-
|
|
391
|
-
// 복잡한 필터
|
|
392
|
-
const { data } = await client.from('products').find({
|
|
393
|
-
where: {
|
|
394
|
-
and: [
|
|
395
|
-
{ status: { equals: 'published' } },
|
|
396
|
-
{ price: { greater_than: 1000, less_than: 50000 } },
|
|
397
|
-
{
|
|
398
|
-
or: [
|
|
399
|
-
{ category: { equals: 'electronics' } },
|
|
400
|
-
{ tags: { contains: 'featured' } },
|
|
401
|
-
],
|
|
402
|
-
},
|
|
403
|
-
],
|
|
404
|
-
},
|
|
405
|
-
})
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
### React Query 옵션
|
|
409
|
-
|
|
410
|
-
```typescript
|
|
411
|
-
const { data } = client.query.useQuery(
|
|
412
|
-
{
|
|
413
|
-
collection: 'products',
|
|
414
|
-
options: { limit: 10 },
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
staleTime: 5 * 60 * 1000, // 5분
|
|
418
|
-
gcTime: 10 * 60 * 1000, // 10분 (React Query v5에서 cacheTime → gcTime)
|
|
419
|
-
refetchOnWindowFocus: false,
|
|
420
|
-
},
|
|
421
|
-
)
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
### 에러 처리
|
|
425
|
-
|
|
426
|
-
SDK는 사용자 친화적인 에러 메시지와 제안을 제공합니다.
|
|
258
|
+
## Error Handling
|
|
427
259
|
|
|
428
260
|
```typescript
|
|
429
261
|
import {
|
|
430
|
-
SDKError,
|
|
431
|
-
ApiError,
|
|
432
|
-
NetworkError,
|
|
433
|
-
ValidationError,
|
|
434
|
-
TimeoutError,
|
|
435
262
|
isNetworkError,
|
|
263
|
+
isSuccessResponse,
|
|
264
|
+
isErrorResponse,
|
|
436
265
|
} from '@01.software/sdk'
|
|
437
266
|
|
|
438
|
-
try {
|
|
439
|
-
const { data } = await client.from('products').find()
|
|
440
|
-
} catch (error) {
|
|
441
|
-
if (isNetworkError(error)) {
|
|
442
|
-
// 사용자 친화적 메시지
|
|
443
|
-
console.error(error.getUserMessage())
|
|
444
|
-
// "네트워크 연결을 확인해주세요."
|
|
445
|
-
|
|
446
|
-
// 개발자용 메시지
|
|
447
|
-
console.error(error.message)
|
|
448
|
-
|
|
449
|
-
// 해결 제안
|
|
450
|
-
console.log(error.suggestion)
|
|
451
|
-
// "인터넷 연결을 확인하거나 잠시 후 다시 시도해주세요."
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
#### 타입 안전한 응답 처리
|
|
457
|
-
|
|
458
|
-
```typescript
|
|
459
|
-
import { isSuccessResponse, isErrorResponse } from '@01.software/sdk'
|
|
460
|
-
|
|
461
267
|
const response = await client.from('products').find()
|
|
462
268
|
|
|
463
269
|
if (isSuccessResponse(response)) {
|
|
464
|
-
// TypeScript가 response.data의 타입을 Product[]로 추론
|
|
465
270
|
console.log(response.data)
|
|
466
|
-
console.log(response.pagination)
|
|
467
271
|
} else if (isErrorResponse(response)) {
|
|
468
|
-
// TypeScript가 response.error의 타입을 추론
|
|
469
|
-
console.error(response.error.code)
|
|
470
272
|
console.error(response.error.message)
|
|
471
273
|
}
|
|
472
274
|
```
|
|
473
275
|
|
|
474
|
-
|
|
276
|
+
Error classes: `SDKError`, `ApiError`, `NetworkError`, `ValidationError`, `TimeoutError`
|
|
475
277
|
|
|
476
|
-
|
|
278
|
+
## Environment Variables
|
|
477
279
|
|
|
478
280
|
```bash
|
|
479
|
-
# .env.local
|
|
480
281
|
NEXT_PUBLIC_SOFTWARE_CLIENT_KEY=your_client_key
|
|
481
|
-
SOFTWARE_SECRET_KEY=your_secret_key
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
### 주의사항
|
|
485
|
-
|
|
486
|
-
- `clientKey`는 브라우저에 노출되어도 안전합니다.
|
|
487
|
-
- `secretKey`는 **절대** 브라우저에 노출되어서는 안 됩니다.
|
|
488
|
-
- `secretKey`는 서버 환경(API Route, Server Component 등)에서만 사용하세요.
|
|
489
|
-
|
|
490
|
-
## 타입 시스템
|
|
491
|
-
|
|
492
|
-
모든 컬렉션은 자동으로 타입이 추론됩니다:
|
|
493
|
-
|
|
494
|
-
```typescript
|
|
495
|
-
// 타입이 자동으로 추론됨
|
|
496
|
-
const { data: products } = await client.from('products').find()
|
|
497
|
-
// products의 타입: Product[]
|
|
498
|
-
|
|
499
|
-
const { data: product } = await client.from('products').findById('id')
|
|
500
|
-
// product의 타입: Product
|
|
501
|
-
|
|
502
|
-
// 생성 시에도 타입 체크
|
|
503
|
-
await client.from('products').create({
|
|
504
|
-
name: '상품명', // ✅ OK
|
|
505
|
-
price: 10000, // ✅ OK
|
|
506
|
-
invalid: 'field', // ❌ 타입 에러
|
|
507
|
-
})
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
## 개발
|
|
511
|
-
|
|
512
|
-
SDK는 포괄적인 테스트를 포함하고 있으며, 타입 안전성과 사용자 경험을 최우선으로 설계되었습니다.
|
|
513
|
-
|
|
514
|
-
### 테스트
|
|
515
|
-
|
|
516
|
-
```bash
|
|
517
|
-
# 모든 테스트 실행
|
|
518
|
-
pnpm test
|
|
519
|
-
|
|
520
|
-
# watch 모드로 실행
|
|
521
|
-
pnpm test:watch
|
|
282
|
+
SOFTWARE_SECRET_KEY=your_secret_key # Server only
|
|
522
283
|
```
|
|
523
284
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
- ✅ **완전한 타입 안전성**: TypeScript 기반 타입 추론
|
|
527
|
-
- ✅ **사용자 친화적 에러 처리**: 명확한 에러 메시지와 해결 제안
|
|
528
|
-
- ✅ **자동 재시도**: 네트워크 오류 시 exponential backoff 전략
|
|
529
|
-
- ✅ **디버그 모드**: 개발 중 요청/응답/에러 로깅
|
|
530
|
-
- ✅ **React Query 통합**: 효율적인 데이터 페칭 및 캐싱
|
|
531
|
-
|
|
532
|
-
## 라이센스
|
|
533
|
-
|
|
534
|
-
MIT
|
|
535
|
-
|
|
536
|
-
## 지원
|
|
537
|
-
|
|
538
|
-
문제가 발생하거나 궁금한 점이 있다면 이슈를 등록해주세요.
|
|
285
|
+
> `secretKey` should only be used in server environments. Never expose it to the browser.
|