@01.software/sdk 0.1.0-dev.260119.0a66443 → 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 +112 -400
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/package.json +3 -4
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
|
|
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.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,205 @@ 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. 클라이언트
|
|
86
|
-
|
|
87
|
-
#### BrowserClient
|
|
59
|
+
## API
|
|
88
60
|
|
|
89
|
-
|
|
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
|
-
},
|
|
103
|
-
})
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
**주요 속성:**
|
|
107
|
-
|
|
108
|
-
- `query`: React Query 훅 (`useQuery`, `useSuspenseQuery`, `useQueryById`, `useInfiniteQuery`)
|
|
109
|
-
- `collections`: 컬렉션 API 클라이언트
|
|
110
|
-
- `from()`: Supabase 스타일 쿼리 빌더
|
|
111
|
-
|
|
112
|
-
#### ServerClient
|
|
113
|
-
|
|
114
|
-
서버 환경에서 사용하는 클라이언트로, API 클라이언트를 추가로 제공합니다.
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
const client = createServerClient({
|
|
118
|
-
clientKey: 'your-client-key',
|
|
119
|
-
secretKey: 'your-secret-key', // 서버 전용
|
|
65
|
+
clientKey: string, // Required
|
|
66
|
+
baseUrl?: string, // API URL (optional)
|
|
67
|
+
debug?: boolean | DebugOptions,
|
|
68
|
+
retry?: RetryOptions,
|
|
69
|
+
errorLogger?: ErrorLogger,
|
|
120
70
|
})
|
|
121
71
|
```
|
|
122
72
|
|
|
123
|
-
|
|
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 |
|
|
124
80
|
|
|
125
|
-
|
|
81
|
+
### Query Builder
|
|
126
82
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
Supabase 스타일의 타입 안전한 쿼리 빌더를 제공합니다.
|
|
83
|
+
Access collections via `client.from(collection)` method.
|
|
130
84
|
|
|
131
85
|
```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
|
-
###
|
|
164
|
-
|
|
165
|
-
데이터 페칭과 캐싱을 위한 React Query 통합을 제공합니다.
|
|
107
|
+
### React Query Hooks
|
|
166
108
|
|
|
167
109
|
```typescript
|
|
168
|
-
//
|
|
169
|
-
const { data, isLoading
|
|
110
|
+
// List query
|
|
111
|
+
const { data, isLoading } = client.query.useQuery({
|
|
170
112
|
collection: 'products',
|
|
171
|
-
options: {
|
|
172
|
-
limit: 10,
|
|
173
|
-
where: { status: { equals: 'published' } },
|
|
174
|
-
},
|
|
113
|
+
options: { limit: 10 },
|
|
175
114
|
})
|
|
176
115
|
|
|
177
|
-
// Suspense
|
|
116
|
+
// Suspense mode
|
|
178
117
|
const { data } = client.query.useSuspenseQuery({
|
|
179
118
|
collection: 'products',
|
|
180
119
|
options: { limit: 10 },
|
|
181
120
|
})
|
|
182
121
|
|
|
183
|
-
//
|
|
184
|
-
const { data
|
|
122
|
+
// Query by ID
|
|
123
|
+
const { data } = client.query.useQueryById({
|
|
185
124
|
collection: 'products',
|
|
186
125
|
id: 'product_id',
|
|
187
126
|
})
|
|
188
127
|
|
|
189
|
-
//
|
|
128
|
+
// Infinite scroll
|
|
190
129
|
const { data, fetchNextPage, hasNextPage } = client.query.useInfiniteQuery({
|
|
191
130
|
collection: 'products',
|
|
192
131
|
options: { limit: 20 },
|
|
193
132
|
})
|
|
194
133
|
```
|
|
195
134
|
|
|
196
|
-
###
|
|
135
|
+
### Server API
|
|
197
136
|
|
|
198
|
-
|
|
137
|
+
Available only in ServerClient.
|
|
199
138
|
|
|
200
139
|
```typescript
|
|
201
|
-
//
|
|
202
|
-
|
|
140
|
+
// Create order
|
|
141
|
+
await client.api.createOrder(data)
|
|
203
142
|
|
|
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
|
-
}
|
|
143
|
+
// Update order
|
|
144
|
+
await client.api.updateOrder(id, data)
|
|
145
|
+
|
|
146
|
+
// Update transaction
|
|
147
|
+
await client.api.updateTransaction(id, data)
|
|
218
148
|
```
|
|
219
149
|
|
|
220
|
-
|
|
150
|
+
### Webhook
|
|
221
151
|
|
|
222
152
|
```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
|
-
)
|
|
153
|
+
import { handleWebhook, createTypedWebhookHandler } from '@01.software/sdk'
|
|
232
154
|
|
|
155
|
+
// Basic handler
|
|
233
156
|
export async function POST(request: Request) {
|
|
234
|
-
return handleWebhook(request,
|
|
157
|
+
return handleWebhook(request, async (event) => {
|
|
158
|
+
console.log(event.collection, event.operation, event.data)
|
|
159
|
+
})
|
|
235
160
|
}
|
|
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
|
-
|
|
248
|
-
**상품 관련**
|
|
249
|
-
|
|
250
|
-
- `products`, `product-variants`, `product-options`, `product-categories`, `product-tags`, `product-images`
|
|
251
|
-
|
|
252
|
-
**주문 관련**
|
|
253
|
-
|
|
254
|
-
- `orders`, `order-products`, `returns`, `return-products`, `transactions`
|
|
255
|
-
|
|
256
|
-
**콘텐츠 관련**
|
|
257
161
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
- `playlists`, `playlist-images`, `musics`
|
|
265
|
-
- `galleries`, `gallery-images`
|
|
266
|
-
|
|
267
|
-
**기타**
|
|
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
|
+
```
|
|
268
168
|
|
|
269
|
-
|
|
169
|
+
## Supported Collections
|
|
270
170
|
|
|
271
|
-
|
|
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` |
|
|
272
178
|
|
|
273
|
-
|
|
179
|
+
## Utilities
|
|
274
180
|
|
|
275
|
-
|
|
181
|
+
### generateOrderNumber
|
|
276
182
|
|
|
277
183
|
```typescript
|
|
278
184
|
import { generateOrderNumber } from '@01.software/sdk'
|
|
279
185
|
|
|
280
186
|
const orderNumber = generateOrderNumber()
|
|
281
|
-
//
|
|
187
|
+
// "260121123456" (YYMMDDRRRRRR format)
|
|
282
188
|
```
|
|
283
189
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
상품 옵션 배열을 주문명으로 포맷팅합니다.
|
|
190
|
+
### formatOrderName
|
|
287
191
|
|
|
288
192
|
```typescript
|
|
289
193
|
import { formatOrderName } from '@01.software/sdk'
|
|
290
194
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
])
|
|
294
|
-
// "상품 A"
|
|
195
|
+
formatOrderName([{ product: { name: 'Product A' } }])
|
|
196
|
+
// "Product A"
|
|
295
197
|
|
|
296
|
-
|
|
297
|
-
{ product: { name: '
|
|
298
|
-
{ product: { name: '
|
|
198
|
+
formatOrderName([
|
|
199
|
+
{ product: { name: 'Product A' } },
|
|
200
|
+
{ product: { name: 'Product B' } },
|
|
299
201
|
])
|
|
300
|
-
// "
|
|
202
|
+
// "Product A and 1 more"
|
|
301
203
|
```
|
|
302
204
|
|
|
303
|
-
###
|
|
205
|
+
### RichTextContent
|
|
304
206
|
|
|
305
|
-
|
|
207
|
+
React component for rendering Payload CMS Lexical rich text.
|
|
306
208
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
```typescript
|
|
209
|
+
```tsx
|
|
310
210
|
import { RichTextContent } from '@01.software/sdk'
|
|
311
211
|
|
|
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
|
-
})
|
|
349
|
-
```
|
|
350
|
-
|
|
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
|
-
})
|
|
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
|
+
/>
|
|
406
221
|
```
|
|
407
222
|
|
|
408
|
-
|
|
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는 사용자 친화적인 에러 메시지와 제안을 제공합니다.
|
|
223
|
+
## Error Handling
|
|
427
224
|
|
|
428
225
|
```typescript
|
|
429
226
|
import {
|
|
430
|
-
SDKError,
|
|
431
|
-
ApiError,
|
|
432
|
-
NetworkError,
|
|
433
|
-
ValidationError,
|
|
434
|
-
TimeoutError,
|
|
435
227
|
isNetworkError,
|
|
228
|
+
isSuccessResponse,
|
|
229
|
+
isErrorResponse,
|
|
436
230
|
} from '@01.software/sdk'
|
|
437
231
|
|
|
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
232
|
const response = await client.from('products').find()
|
|
462
233
|
|
|
463
234
|
if (isSuccessResponse(response)) {
|
|
464
|
-
// TypeScript가 response.data의 타입을 Product[]로 추론
|
|
465
235
|
console.log(response.data)
|
|
466
|
-
console.log(response.pagination)
|
|
467
236
|
} else if (isErrorResponse(response)) {
|
|
468
|
-
// TypeScript가 response.error의 타입을 추론
|
|
469
|
-
console.error(response.error.code)
|
|
470
237
|
console.error(response.error.message)
|
|
471
238
|
}
|
|
472
239
|
```
|
|
473
240
|
|
|
474
|
-
|
|
241
|
+
Error classes: `SDKError`, `ApiError`, `NetworkError`, `ValidationError`, `TimeoutError`
|
|
475
242
|
|
|
476
|
-
|
|
243
|
+
## Environment Variables
|
|
477
244
|
|
|
478
245
|
```bash
|
|
479
|
-
# .env.local
|
|
480
246
|
NEXT_PUBLIC_SOFTWARE_CLIENT_KEY=your_client_key
|
|
481
|
-
SOFTWARE_SECRET_KEY=your_secret_key
|
|
247
|
+
SOFTWARE_SECRET_KEY=your_secret_key # Server only
|
|
482
248
|
```
|
|
483
249
|
|
|
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
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
### 주요 특징
|
|
525
|
-
|
|
526
|
-
- ✅ **완전한 타입 안전성**: TypeScript 기반 타입 추론
|
|
527
|
-
- ✅ **사용자 친화적 에러 처리**: 명확한 에러 메시지와 해결 제안
|
|
528
|
-
- ✅ **자동 재시도**: 네트워크 오류 시 exponential backoff 전략
|
|
529
|
-
- ✅ **디버그 모드**: 개발 중 요청/응답/에러 로깅
|
|
530
|
-
- ✅ **React Query 통합**: 효율적인 데이터 페칭 및 캐싱
|
|
531
|
-
|
|
532
|
-
## 라이센스
|
|
533
|
-
|
|
534
|
-
MIT
|
|
535
|
-
|
|
536
|
-
## 지원
|
|
537
|
-
|
|
538
|
-
문제가 발생하거나 궁금한 점이 있다면 이슈를 등록해주세요.
|
|
250
|
+
> `secretKey` should only be used in server environments. Never expose it to the browser.
|
package/dist/index.d.cts
CHANGED
|
@@ -618,7 +618,7 @@ interface ProductCategory {
|
|
|
618
618
|
* When enabled, the slug will auto-generate from the title field on save and autosave.
|
|
619
619
|
*/
|
|
620
620
|
generateSlug?: boolean | null;
|
|
621
|
-
slug
|
|
621
|
+
slug?: string | null;
|
|
622
622
|
description?: string | null;
|
|
623
623
|
image?: (number | null) | ProductImage;
|
|
624
624
|
parent?: (number | null) | ProductCategory;
|
|
@@ -915,7 +915,7 @@ interface DocumentCategory {
|
|
|
915
915
|
* When enabled, the slug will auto-generate from the title field on save and autosave.
|
|
916
916
|
*/
|
|
917
917
|
generateSlug?: boolean | null;
|
|
918
|
-
slug
|
|
918
|
+
slug?: string | null;
|
|
919
919
|
description?: string | null;
|
|
920
920
|
updatedAt: string;
|
|
921
921
|
createdAt: string;
|
|
@@ -978,6 +978,7 @@ interface Post {
|
|
|
978
978
|
tenant?: (number | null) | Tenant;
|
|
979
979
|
thumbnail?: (number | null) | PostImage;
|
|
980
980
|
title: string;
|
|
981
|
+
subtitle?: string | null;
|
|
981
982
|
/**
|
|
982
983
|
* When enabled, the slug will auto-generate from the title field on save and autosave.
|
|
983
984
|
*/
|
|
@@ -1065,7 +1066,7 @@ interface PostCategory {
|
|
|
1065
1066
|
* When enabled, the slug will auto-generate from the title field on save and autosave.
|
|
1066
1067
|
*/
|
|
1067
1068
|
generateSlug?: boolean | null;
|
|
1068
|
-
slug
|
|
1069
|
+
slug?: string | null;
|
|
1069
1070
|
description?: string | null;
|
|
1070
1071
|
parent?: (number | null) | PostCategory;
|
|
1071
1072
|
updatedAt: string;
|
|
@@ -2135,6 +2136,7 @@ interface PostsSelect<T extends boolean = true> {
|
|
|
2135
2136
|
tenant?: T;
|
|
2136
2137
|
thumbnail?: T;
|
|
2137
2138
|
title?: T;
|
|
2139
|
+
subtitle?: T;
|
|
2138
2140
|
generateSlug?: T;
|
|
2139
2141
|
slug?: T;
|
|
2140
2142
|
categories?: T;
|
package/dist/index.d.ts
CHANGED
|
@@ -618,7 +618,7 @@ interface ProductCategory {
|
|
|
618
618
|
* When enabled, the slug will auto-generate from the title field on save and autosave.
|
|
619
619
|
*/
|
|
620
620
|
generateSlug?: boolean | null;
|
|
621
|
-
slug
|
|
621
|
+
slug?: string | null;
|
|
622
622
|
description?: string | null;
|
|
623
623
|
image?: (number | null) | ProductImage;
|
|
624
624
|
parent?: (number | null) | ProductCategory;
|
|
@@ -915,7 +915,7 @@ interface DocumentCategory {
|
|
|
915
915
|
* When enabled, the slug will auto-generate from the title field on save and autosave.
|
|
916
916
|
*/
|
|
917
917
|
generateSlug?: boolean | null;
|
|
918
|
-
slug
|
|
918
|
+
slug?: string | null;
|
|
919
919
|
description?: string | null;
|
|
920
920
|
updatedAt: string;
|
|
921
921
|
createdAt: string;
|
|
@@ -978,6 +978,7 @@ interface Post {
|
|
|
978
978
|
tenant?: (number | null) | Tenant;
|
|
979
979
|
thumbnail?: (number | null) | PostImage;
|
|
980
980
|
title: string;
|
|
981
|
+
subtitle?: string | null;
|
|
981
982
|
/**
|
|
982
983
|
* When enabled, the slug will auto-generate from the title field on save and autosave.
|
|
983
984
|
*/
|
|
@@ -1065,7 +1066,7 @@ interface PostCategory {
|
|
|
1065
1066
|
* When enabled, the slug will auto-generate from the title field on save and autosave.
|
|
1066
1067
|
*/
|
|
1067
1068
|
generateSlug?: boolean | null;
|
|
1068
|
-
slug
|
|
1069
|
+
slug?: string | null;
|
|
1069
1070
|
description?: string | null;
|
|
1070
1071
|
parent?: (number | null) | PostCategory;
|
|
1071
1072
|
updatedAt: string;
|
|
@@ -2135,6 +2136,7 @@ interface PostsSelect<T extends boolean = true> {
|
|
|
2135
2136
|
tenant?: T;
|
|
2136
2137
|
thumbnail?: T;
|
|
2137
2138
|
title?: T;
|
|
2139
|
+
subtitle?: T;
|
|
2138
2140
|
generateSlug?: T;
|
|
2139
2141
|
slug?: T;
|
|
2140
2142
|
categories?: T;
|
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@01.software/sdk",
|
|
3
|
-
"version": "0.1.0-dev.
|
|
3
|
+
"version": "0.1.0-dev.260206.8918543",
|
|
4
4
|
"description": "01.software SDK",
|
|
5
5
|
"author": "01.works",
|
|
6
|
-
"license": "MIT",
|
|
7
6
|
"keywords": [],
|
|
8
7
|
"type": "module",
|
|
9
8
|
"main": "./dist/index.cjs",
|
|
@@ -41,8 +40,8 @@
|
|
|
41
40
|
"shadcn": "^3.6.3",
|
|
42
41
|
"tsup": "^8.3.7",
|
|
43
42
|
"vitest": "^3.2.3",
|
|
44
|
-
"@repo/
|
|
45
|
-
"@repo/
|
|
43
|
+
"@repo/eslint-config": "0.0.0",
|
|
44
|
+
"@repo/typescript-config": "0.0.0"
|
|
46
45
|
},
|
|
47
46
|
"dependencies": {
|
|
48
47
|
"@payloadcms/richtext-lexical": "3.72.0",
|