@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 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
+ 문제가 발생하거나 궁금한 점이 있다면 이슈를 등록해주세요.