@diffsome/sdk 3.0.0

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,667 @@
1
+ # @diffsome/sdk
2
+
3
+ Diffsome 공식 SDK - 헤드리스 CMS + 이커머스 + AI 기능을 제공합니다.
4
+
5
+ **버전: 2.18.0**
6
+
7
+ ## 설치
8
+
9
+ ```bash
10
+ npm install @diffsome/sdk
11
+ ```
12
+
13
+ ## 빠른 시작
14
+
15
+ ```typescript
16
+ import { Diffsome } from '@diffsome/sdk';
17
+
18
+ const client = new Diffsome({
19
+ tenantId: 'your-tenant-id',
20
+ apiKey: 'pky_xxxxxxxxxxxxxxxx', // 필수 - Dashboard > Settings > API Tokens
21
+ baseUrl: 'https://diffsome.com', // 선택
22
+
23
+ // 토큰 자동 저장 (로그인 유지)
24
+ persistToken: true,
25
+ storageType: 'localStorage', // 또는 'sessionStorage'
26
+ onAuthStateChange: (token, user) => {
27
+ console.log('Auth changed:', user);
28
+ },
29
+ });
30
+
31
+ // 블로그 조회
32
+ const { data: posts } = await client.blog.list();
33
+
34
+ // 상품 조회
35
+ const { data: products } = await client.shop.listProducts();
36
+ ```
37
+
38
+ ---
39
+
40
+ ## 인증 (Auth)
41
+
42
+ ### 기본 설정
43
+
44
+ ```typescript
45
+ const client = new Diffsome({
46
+ tenantId: 'demo',
47
+ apiKey: 'pky_xxxxxxxxxxxxxxxx', // 필수
48
+ persistToken: true, // 토큰 자동 저장
49
+ });
50
+ ```
51
+
52
+ ### 토큰 자동 저장
53
+
54
+ `persistToken: true` 설정 시:
55
+ - **로그인**: `localStorage`에 자동 저장
56
+ - **새로고침**: 토큰 자동 복원
57
+ - **로그아웃**: 토큰 자동 삭제
58
+ - **저장 키**: `promptly_auth_token_{tenantId}`
59
+
60
+ ```typescript
61
+ const client = new Diffsome({
62
+ tenantId: 'demo',
63
+ apiKey: 'pky_xxx',
64
+ persistToken: true,
65
+ storageType: 'localStorage', // 기본값, 또는 'sessionStorage'
66
+ onAuthStateChange: (token, user) => {
67
+ // 로그인/로그아웃 시 호출
68
+ if (token) {
69
+ console.log('Logged in:', user);
70
+ } else {
71
+ console.log('Logged out');
72
+ }
73
+ },
74
+ });
75
+ ```
76
+
77
+ ### 로그인/회원가입
78
+
79
+ ```typescript
80
+ // 회원가입
81
+ await client.auth.register({
82
+ name: 'John Doe',
83
+ email: 'john@example.com',
84
+ password: 'password',
85
+ password_confirmation: 'password',
86
+ });
87
+
88
+ // 로그인 (토큰 자동 저장됨)
89
+ const { member, token } = await client.auth.login({
90
+ email: 'john@example.com',
91
+ password: 'password',
92
+ });
93
+
94
+ // 로그아웃 (토큰 자동 삭제됨)
95
+ await client.auth.logout();
96
+
97
+ // 인증 상태 확인
98
+ client.auth.isAuthenticated(); // true/false
99
+
100
+ // 현재 사용자
101
+ const me = await client.auth.me();
102
+
103
+ // 프로필 수정
104
+ await client.auth.updateProfile({ name: 'New Name' });
105
+ ```
106
+
107
+ ### 소셜 로그인
108
+
109
+ ```typescript
110
+ // 사용 가능한 프로바이더
111
+ const providers = await client.auth.getSocialProviders();
112
+ // ['google', 'kakao', 'naver']
113
+
114
+ // 로그인 URL 획득
115
+ const { url } = await client.auth.getSocialAuthUrl('google');
116
+ window.location.href = url;
117
+
118
+ // 콜백 처리 (리다이렉트 후)
119
+ const { member, token } = await client.auth.socialCallback('google', code);
120
+ ```
121
+
122
+ ### 비밀번호 재설정
123
+
124
+ ```typescript
125
+ // 재설정 이메일 발송
126
+ await client.auth.forgotPassword({ email: 'john@example.com' });
127
+
128
+ // 비밀번호 재설정
129
+ await client.auth.resetPassword({
130
+ token: 'reset-token',
131
+ email: 'john@example.com',
132
+ password: 'newpassword',
133
+ password_confirmation: 'newpassword',
134
+ });
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Blog API
140
+
141
+ ```typescript
142
+ // 목록 조회
143
+ const { data, meta } = await client.blog.list({
144
+ page: 1,
145
+ per_page: 10,
146
+ category: 'tech',
147
+ tag: 'laravel',
148
+ search: 'keyword',
149
+ });
150
+
151
+ // 상세 조회
152
+ const post = await client.blog.get('post-slug');
153
+
154
+ // 추천 글
155
+ const featured = await client.blog.featured(5);
156
+
157
+ // 카테고리/태그별 필터
158
+ const { data } = await client.blog.byCategory('news');
159
+ const { data } = await client.blog.byTag('featured');
160
+
161
+ // 카테고리/태그 목록
162
+ const categories = await client.blog.categories(); // string[]
163
+ const tags = await client.blog.tags(); // string[]
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Boards API
169
+
170
+ ```typescript
171
+ // 게시판 목록
172
+ const { data: boards } = await client.boards.list();
173
+
174
+ // 게시판 조회
175
+ const board = await client.boards.get('board-slug');
176
+
177
+ // 게시글 목록
178
+ const { data: posts } = await client.boards.listPosts('board-slug', {
179
+ page: 1,
180
+ per_page: 10,
181
+ search: 'keyword',
182
+ });
183
+
184
+ // 게시글 상세
185
+ const post = await client.boards.getPost(postId);
186
+
187
+ // 게시글 작성 (인증 필요)
188
+ await client.boards.createPost({
189
+ board_id: 1,
190
+ title: 'Title',
191
+ content: 'Content',
192
+ is_secret: false,
193
+ });
194
+
195
+ // 게시글 수정/삭제
196
+ await client.boards.updatePost(postId, { title: 'New Title' });
197
+ await client.boards.deletePost(postId);
198
+ ```
199
+
200
+ ---
201
+
202
+ ## Comments API
203
+
204
+ 게시판, 블로그, 독립 댓글(방명록) 지원
205
+
206
+ ```typescript
207
+ // 게시판 댓글
208
+ const { data } = await client.comments.boardPost(postId);
209
+ await client.comments.createBoardPost(postId, {
210
+ author_name: 'John',
211
+ content: 'Great!',
212
+ password: '1234', // 비회원 댓글
213
+ });
214
+
215
+ // 블로그 댓글
216
+ const { data } = await client.comments.blogPost('post-slug');
217
+ await client.comments.createBlogPost('post-slug', {
218
+ content: 'Nice article!',
219
+ });
220
+
221
+ // 독립 댓글 (방명록)
222
+ const { data } = await client.comments.standalone('guestbook');
223
+ await client.comments.createStandalone('guestbook', {
224
+ author_name: 'Visitor',
225
+ content: 'Hello!',
226
+ });
227
+
228
+ // 공통
229
+ await client.comments.update(commentId, { content: 'Updated' });
230
+ await client.comments.delete(commentId, { password: '1234' });
231
+ await client.comments.like(commentId);
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Shop API
237
+
238
+ ### 상품
239
+
240
+ ```typescript
241
+ // 상품 목록
242
+ const { data: products } = await client.shop.listProducts({
243
+ category: 'shoes',
244
+ is_featured: true,
245
+ min_price: 10000,
246
+ max_price: 50000,
247
+ search: 'keyword',
248
+ });
249
+
250
+ // 상품 상세
251
+ const product = await client.shop.getProduct('product-slug');
252
+
253
+ // 추천 상품
254
+ const featured = await client.shop.featuredProducts(8);
255
+
256
+ // 카테고리 목록
257
+ const categories = await client.shop.listCategories();
258
+ ```
259
+
260
+ ### 상품 리뷰
261
+
262
+ ```typescript
263
+ // 리뷰 목록
264
+ const { data: reviews, stats } = await client.shop.getProductReviews('product-slug', {
265
+ page: 1,
266
+ per_page: 10,
267
+ rating: 5, // 별점 필터
268
+ });
269
+
270
+ // 리뷰 통계
271
+ // stats = { average_rating: 4.5, total_count: 123, rating_counts: { 5: 80, 4: 30, ... } }
272
+
273
+ // 리뷰 작성 (인증 + 구매 필요)
274
+ await client.shop.createProductReview('product-slug', {
275
+ rating: 5,
276
+ title: 'Great product!',
277
+ content: 'Highly recommended.',
278
+ images: ['https://...'],
279
+ });
280
+
281
+ // 리뷰 수정/삭제
282
+ await client.shop.updateProductReview(reviewId, { rating: 4 });
283
+ await client.shop.deleteProductReview(reviewId);
284
+
285
+ // 도움이 됐어요
286
+ await client.shop.markReviewHelpful(reviewId);
287
+
288
+ // 내 리뷰 목록
289
+ const { data: myReviews } = await client.shop.getMyReviews();
290
+ ```
291
+
292
+ ### 장바구니 (인증 필요)
293
+
294
+ ```typescript
295
+ // 장바구니 조회
296
+ const cart = await client.shop.getCart();
297
+
298
+ // 상품 추가
299
+ await client.shop.addToCart({
300
+ product_id: 1,
301
+ quantity: 2,
302
+ variant_id: 3, // 옵션 상품
303
+ });
304
+
305
+ // 수량 변경
306
+ await client.shop.updateCartItem(itemId, { quantity: 3 });
307
+
308
+ // 상품 제거
309
+ await client.shop.removeFromCart(itemId);
310
+
311
+ // 장바구니 비우기
312
+ await client.shop.clearCart();
313
+ ```
314
+
315
+ ### 주문 (인증 필요)
316
+
317
+ ```typescript
318
+ // 주문 생성
319
+ const order = await client.shop.createOrder({
320
+ orderer_name: 'John Doe',
321
+ orderer_email: 'john@example.com',
322
+ orderer_phone: '010-1234-5678',
323
+ shipping_name: 'John Doe',
324
+ shipping_phone: '010-1234-5678',
325
+ shipping_zipcode: '12345',
326
+ shipping_address: '서울시 강남구',
327
+ shipping_address_detail: '101동 101호',
328
+ shipping_memo: '부재시 경비실',
329
+ coupon_code: 'SAVE10',
330
+ });
331
+
332
+ // 주문 목록
333
+ const { data: orders } = await client.shop.listOrders();
334
+
335
+ // 주문 상세
336
+ const orderDetail = await client.shop.getOrder(orderId);
337
+
338
+ // 주문 취소
339
+ await client.shop.cancelOrder(orderId);
340
+ ```
341
+
342
+ ### 결제 (토스페이먼츠)
343
+
344
+ ```typescript
345
+ // 결제 준비
346
+ const payment = await client.shop.preparePayment({
347
+ order_number: 'ORD-123',
348
+ success_url: 'https://mysite.com/payment/success',
349
+ fail_url: 'https://mysite.com/payment/fail',
350
+ });
351
+ // payment = { client_key, order_id, order_name, amount, customer_name, ... }
352
+
353
+ // 결제 승인 (토스 결제 완료 후)
354
+ await client.shop.confirmPayment({
355
+ payment_key: 'toss_payment_key',
356
+ order_id: 'ORD-123',
357
+ amount: 50000,
358
+ });
359
+ ```
360
+
361
+ ### 쿠폰
362
+
363
+ ```typescript
364
+ // 쿠폰 검증
365
+ const result = await client.shop.validateCoupon('SAVE10', 50000);
366
+ // { valid: true, discount_amount: 5000, coupon: { ... } }
367
+
368
+ // 내 쿠폰 목록
369
+ const coupons = await client.shop.myCoupons();
370
+ ```
371
+
372
+ ### 배송 설정
373
+
374
+ ```typescript
375
+ const settings = await client.shop.getShippingSettings();
376
+ // { base_fee, free_shipping_threshold, ... }
377
+ ```
378
+
379
+ ---
380
+
381
+ ## Reservation API
382
+
383
+ ### 공개 API
384
+
385
+ ```typescript
386
+ // 예약 설정
387
+ const settings = await client.reservation.getSettings();
388
+
389
+ // 서비스 목록
390
+ const services = await client.reservation.listServices();
391
+
392
+ // 담당자 목록
393
+ const staff = await client.reservation.listStaff(serviceId);
394
+
395
+ // 예약 가능 날짜
396
+ const dates = await client.reservation.getAvailableDates({
397
+ service_id: 1,
398
+ staff_id: 2,
399
+ start_date: '2026-01-01',
400
+ end_date: '2026-01-31',
401
+ });
402
+
403
+ // 예약 가능 시간
404
+ const slots = await client.reservation.getAvailableSlots({
405
+ service_id: 1,
406
+ date: '2026-01-15',
407
+ staff_id: 2,
408
+ });
409
+ ```
410
+
411
+ ### 예약 관리 (인증 필요)
412
+
413
+ ```typescript
414
+ // 예약 생성
415
+ await client.reservation.create({
416
+ service_id: 1,
417
+ staff_id: 2,
418
+ reservation_date: '2026-01-15',
419
+ start_time: '14:00',
420
+ customer_name: 'John Doe',
421
+ customer_phone: '010-1234-5678',
422
+ customer_email: 'john@example.com',
423
+ notes: '요청사항',
424
+ });
425
+
426
+ // 내 예약 목록
427
+ const { data } = await client.reservation.list({ status: 'confirmed' });
428
+
429
+ // 다가오는/지난 예약
430
+ const upcoming = await client.reservation.upcoming(5);
431
+ const past = await client.reservation.past(10);
432
+
433
+ // 예약 취소
434
+ await client.reservation.cancel('RES-20260115-001', '일정 변경');
435
+ ```
436
+
437
+ ---
438
+
439
+ ## Forms API
440
+
441
+ ```typescript
442
+ // 폼 목록
443
+ const { data: forms } = await client.forms.list();
444
+
445
+ // 폼 상세 (필드 정보 포함)
446
+ const form = await client.forms.get('contact');
447
+
448
+ // 폼 제출
449
+ await client.forms.submit('contact', {
450
+ name: 'John Doe',
451
+ email: 'john@example.com',
452
+ message: 'Hello!',
453
+ });
454
+
455
+ // 내 제출 목록 (인증 필요)
456
+ const { data: submissions } = await client.forms.mySubmissions();
457
+ ```
458
+
459
+ ---
460
+
461
+ ## Media API (인증 필요)
462
+
463
+ ```typescript
464
+ // 파일 업로드
465
+ const media = await client.media.upload(file);
466
+
467
+ // 여러 파일 업로드
468
+ const mediaList = await client.media.uploadMultiple([file1, file2]);
469
+
470
+ // 내 미디어 목록
471
+ const { data: myMedia } = await client.media.list({ type: 'image/jpeg' });
472
+
473
+ // 미디어 삭제
474
+ await client.media.delete(mediaId);
475
+ ```
476
+
477
+ ---
478
+
479
+ ## Custom Entities API
480
+
481
+ 동적 데이터 구조 생성/관리
482
+
483
+ ### 엔티티 정의
484
+
485
+ ```typescript
486
+ // 엔티티 목록
487
+ const entities = await client.entities.list();
488
+
489
+ // 엔티티 조회
490
+ const entity = await client.entities.get('customers');
491
+
492
+ // 엔티티 생성
493
+ await client.entities.create({
494
+ name: 'Customer',
495
+ slug: 'customers',
496
+ schema: {
497
+ fields: [
498
+ { name: 'company', label: 'Company', type: 'text', required: true },
499
+ { name: 'email', label: 'Email', type: 'email', required: true },
500
+ { name: 'status', label: 'Status', type: 'select', options: [
501
+ { value: 'active', label: 'Active' },
502
+ { value: 'inactive', label: 'Inactive' },
503
+ ]},
504
+ ],
505
+ },
506
+ });
507
+
508
+ // 엔티티 수정/삭제
509
+ await client.entities.update('customers', { name: 'Clients' });
510
+ await client.entities.delete('customers', true); // force
511
+ ```
512
+
513
+ ### 레코드
514
+
515
+ ```typescript
516
+ // 레코드 목록
517
+ const { data: records } = await client.entities.listRecords('customers', {
518
+ search: 'ACME',
519
+ filters: JSON.stringify({ status: 'active' }),
520
+ });
521
+
522
+ // 레코드 조회
523
+ const record = await client.entities.getRecord('customers', 1);
524
+
525
+ // 레코드 생성
526
+ await client.entities.createRecord('customers', {
527
+ company: 'ACME Corp',
528
+ email: 'contact@acme.com',
529
+ status: 'active',
530
+ });
531
+
532
+ // 레코드 수정/삭제
533
+ await client.entities.updateRecord('customers', 1, { status: 'inactive' });
534
+ await client.entities.deleteRecord('customers', 1);
535
+ ```
536
+
537
+ ### TypeScript 지원
538
+
539
+ ```typescript
540
+ interface Customer {
541
+ company: string;
542
+ email: string;
543
+ status: 'active' | 'inactive';
544
+ }
545
+
546
+ const customers = client.entities.typed<Customer>('customers');
547
+ const { data } = await customers.list(); // data: Customer[]
548
+ ```
549
+
550
+ ---
551
+
552
+ ## 응답 타입
553
+
554
+ 모든 목록 API는 일관된 형식 반환:
555
+
556
+ ```typescript
557
+ interface ListResponse<T> {
558
+ data: T[]; // 항상 배열, null 아님
559
+ meta: {
560
+ current_page: number;
561
+ last_page: number;
562
+ per_page: number;
563
+ total: number;
564
+ from: number | null;
565
+ to: number | null;
566
+ };
567
+ }
568
+ ```
569
+
570
+ ---
571
+
572
+ ## 에러 처리
573
+
574
+ ```typescript
575
+ import { Promptly, PromptlyError } from '@diffsome/sdk';
576
+
577
+ try {
578
+ await client.auth.login({ email: 'wrong@email.com', password: 'wrong' });
579
+ } catch (error) {
580
+ if (error instanceof PromptlyError) {
581
+ console.log(error.message); // "Invalid credentials"
582
+ console.log(error.status); // 401
583
+ console.log(error.errors); // { email: ["Invalid email or password"] }
584
+ }
585
+ }
586
+ ```
587
+
588
+ ---
589
+
590
+ ## React 예시
591
+
592
+ ```tsx
593
+ 'use client';
594
+
595
+ import { useState, useEffect } from 'react';
596
+ import { Diffsome } from '@diffsome/sdk';
597
+
598
+ // 싱글톤 클라이언트
599
+ const client = new Diffsome({
600
+ tenantId: process.env.NEXT_PUBLIC_PROMPTLY_TENANT_ID!,
601
+ apiKey: process.env.NEXT_PUBLIC_PROMPTLY_API_KEY!,
602
+ persistToken: true,
603
+ });
604
+
605
+ function BlogList() {
606
+ const [posts, setPosts] = useState([]);
607
+ const [loading, setLoading] = useState(true);
608
+
609
+ useEffect(() => {
610
+ client.blog.list({ per_page: 10 })
611
+ .then(({ data }) => setPosts(data))
612
+ .finally(() => setLoading(false));
613
+ }, []);
614
+
615
+ if (loading) return <div>Loading...</div>;
616
+
617
+ return (
618
+ <div>
619
+ {posts.map(post => (
620
+ <article key={post.id}>
621
+ <h2>{post.title}</h2>
622
+ <p>{post.excerpt}</p>
623
+ </article>
624
+ ))}
625
+ </div>
626
+ );
627
+ }
628
+ ```
629
+
630
+ ---
631
+
632
+ ## Changelog
633
+
634
+ ### v2.18.0
635
+ - 상품 리뷰 API 추가
636
+ - 배송 설정 API 추가
637
+
638
+ ### v2.15.0
639
+ - 토스페이먼츠 결제 API 추가
640
+
641
+ ### v2.12.0
642
+ - 블로그 카테고리/태그 필터 추가
643
+ - `category`, `tags`, `views`, `published_at` 필드 추가
644
+
645
+ ### v2.10.0
646
+ - `persistToken` 옵션 추가 (토큰 자동 저장)
647
+ - `onAuthStateChange` 콜백 추가
648
+ - `storageType` 옵션 추가
649
+
650
+ ### v2.5.0
651
+ - 게시판 비밀글 지원 (`is_secret`, `is_mine`)
652
+
653
+ ### v2.3.0
654
+ - 다형성 댓글 API (board, blog, standalone)
655
+
656
+ ### v2.0.0
657
+ - **Breaking:** API key 필수
658
+
659
+ ### v1.3.0
660
+ - `ListResponse<T>` 통일
661
+ - 예약 시스템 지원
662
+
663
+ ---
664
+
665
+ ## 라이선스
666
+
667
+ MIT