entity-server-client 0.2.6 → 0.3.1

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.
Files changed (64) hide show
  1. package/README.md +32 -0
  2. package/build.mjs +7 -0
  3. package/docs/api/alimtalk.md +62 -0
  4. package/docs/api/auth.md +256 -0
  5. package/docs/api/email.md +37 -0
  6. package/docs/api/entity.md +273 -0
  7. package/docs/api/file.md +80 -0
  8. package/docs/api/health.md +47 -0
  9. package/docs/api/identity.md +32 -0
  10. package/docs/api/import.md +45 -0
  11. package/docs/api/packet.md +90 -0
  12. package/docs/api/pg.md +90 -0
  13. package/docs/api/push.md +107 -0
  14. package/docs/api/react.md +141 -0
  15. package/docs/api/request.md +118 -0
  16. package/docs/api/setup.md +43 -0
  17. package/docs/api/sms.md +45 -0
  18. package/docs/api/smtp.md +33 -0
  19. package/docs/api/transaction.md +50 -0
  20. package/docs/api/utils.md +52 -0
  21. package/docs/apis.md +22 -779
  22. package/docs/react.md +58 -0
  23. package/package.json +6 -1
  24. package/src/EntityServerClient.ts +5 -31
  25. package/src/client/base.ts +114 -12
  26. package/src/client/packet.ts +8 -31
  27. package/src/client/request.ts +52 -6
  28. package/src/client/utils.ts +5 -6
  29. package/src/mixins/auth.ts +14 -158
  30. package/src/mixins/push.ts +0 -23
  31. package/src/mixins/utils.ts +32 -1
  32. package/src/packet.ts +84 -0
  33. package/src/types.ts +15 -125
  34. package/tests/packet.test.mjs +50 -0
  35. package/dist/EntityServerClient.d.ts +0 -709
  36. package/dist/client/base.d.ts +0 -59
  37. package/dist/client/hmac.d.ts +0 -8
  38. package/dist/client/packet.d.ts +0 -24
  39. package/dist/client/request.d.ts +0 -15
  40. package/dist/client/utils.d.ts +0 -8
  41. package/dist/hooks/useEntityServer.d.ts +0 -63
  42. package/dist/index.d.ts +0 -4
  43. package/dist/index.js +0 -2
  44. package/dist/index.js.map +0 -7
  45. package/dist/mixins/alimtalk.d.ts +0 -56
  46. package/dist/mixins/auth.d.ts +0 -167
  47. package/dist/mixins/email.d.ts +0 -51
  48. package/dist/mixins/entity.d.ts +0 -119
  49. package/dist/mixins/file.d.ts +0 -78
  50. package/dist/mixins/identity.d.ts +0 -52
  51. package/dist/mixins/pg.d.ts +0 -63
  52. package/dist/mixins/push.d.ts +0 -110
  53. package/dist/mixins/sms.d.ts +0 -55
  54. package/dist/mixins/smtp.d.ts +0 -44
  55. package/dist/mixins/utils.d.ts +0 -70
  56. package/dist/react.d.ts +0 -1
  57. package/dist/react.js +0 -2
  58. package/dist/react.js.map +0 -7
  59. package/dist/types.d.ts +0 -329
  60. package/src/mixins/alimtalk.ts +0 -35
  61. package/src/mixins/email.ts +0 -46
  62. package/src/mixins/identity.ts +0 -35
  63. package/src/mixins/pg.ts +0 -58
  64. package/src/mixins/sms.ts +0 -46
@@ -0,0 +1,80 @@
1
+ # 파일 스토리지
2
+
3
+ ## `fileUpload(entity, file, opts?)`
4
+
5
+ 파일을 업로드합니다 (multipart/form-data). `entity`는 파일이 귀속될 엔티티명입니다.
6
+
7
+ | 옵션 | 타입 | 설명 |
8
+ | ---------- | --------- | ----------------------------------------- |
9
+ | `refSeq` | `number` | 연결할 엔티티 레코드 seq |
10
+ | `isPublic` | `boolean` | `true`이면 인증 없이 URL로 직접 접근 가능 |
11
+
12
+ ```ts
13
+ const input = document.querySelector('input[type="file"]') as HTMLInputElement;
14
+ const file = input.files![0];
15
+
16
+ const res = await client.fileUpload("product", file, {
17
+ refSeq: 10,
18
+ isPublic: false,
19
+ });
20
+ res.uuid; // 파일 고유 UUID
21
+ res.data; // FileMeta 객체
22
+ res.data.name; // 원본 파일명
23
+ res.data.size; // 파일 크기 (bytes)
24
+ res.data.mime; // MIME 타입
25
+ ```
26
+
27
+ ## `fileDownload(entity, uuid)`
28
+
29
+ 파일을 다운로드합니다. `ArrayBuffer`를 반환합니다.
30
+
31
+ ```ts
32
+ const buf = await client.fileDownload("product", "uuid-here");
33
+ const blob = new Blob([buf], { type: "image/png" });
34
+ const url = URL.createObjectURL(blob);
35
+ ```
36
+
37
+ ## `fileDelete(entity, uuid)`
38
+
39
+ 파일을 삭제합니다.
40
+
41
+ ```ts
42
+ await client.fileDelete("product", "uuid-here");
43
+ ```
44
+
45
+ ## `fileList(entity, opts?)`
46
+
47
+ 엔티티에 연결된 파일 목록을 조회합니다.
48
+
49
+ ```ts
50
+ const res = await client.fileList("product", { refSeq: 10 });
51
+ res.data.items; // FileMeta[]
52
+ res.data.total; // 전체 건수
53
+ ```
54
+
55
+ ## `fileMeta(entity, uuid)`
56
+
57
+ 파일 메타 정보를 조회합니다.
58
+
59
+ ```ts
60
+ const res = await client.fileMeta("product", "uuid-here");
61
+ res.data; // FileMeta
62
+ ```
63
+
64
+ ## `fileToken(uuid)`
65
+
66
+ 임시 파일 접근 토큰을 발급합니다. 비공개 파일을 일시적으로 공유할 때 사용합니다.
67
+
68
+ ```ts
69
+ const res = await client.fileToken("uuid-here");
70
+ const tempUrl = `${baseUrl}/v1/files/${res.token}`;
71
+ ```
72
+
73
+ ## `fileUrl(uuid)`
74
+
75
+ 파일 직접 접근 URL을 반환합니다. (fetch 없음, URL 조합만)
76
+
77
+ ```ts
78
+ const url = client.fileUrl("uuid-here");
79
+ // → "http://localhost:47200/v1/files/uuid-here"
80
+ ```
@@ -0,0 +1,47 @@
1
+ # 서버 헬스체크
2
+
3
+ ## 개요
4
+
5
+ 앱 시작 시 서버의 패킷 암호화 설정을 감지하여 클라이언트 설정을 자동으로 맞춥니다.
6
+
7
+ **헬스체크 응답:**
8
+
9
+ ```json
10
+ {
11
+ "ok": true,
12
+ "packet_encryption": true,
13
+ "packet_mode": "anonymous",
14
+ "packet_token": "anon.v1...."
15
+ }
16
+ ```
17
+
18
+ - `packet_encryption`: 서버가 패킷 암호화를 요구하는지 여부
19
+ - `packet_mode`: `"jwt"` (JWT 인증) 또는 `"anonymous"` (익명 토큰)
20
+ - `packet_token`: `anonymous` 모드일 때 포함. `checkHealth()` 호출 시 클라이언트 내부 `anonymousPacketToken`에 **자동 저장**됨
21
+
22
+ ## 자동 초기화 (권장)
23
+
24
+ **admin-web 예시:**
25
+
26
+ ```ts
27
+ import { entityServer } from "entity-server-client";
28
+
29
+ // 앱 초기화 시 한 번 실행 (예: App.tsx mount, main.tsx 초기화)
30
+ const health = await entityServer.checkHealth();
31
+ // → 서버의 packet_encryption: true 이면
32
+ // 클라이언트의 encryptRequests: true 로 자동 설정됨
33
+ ```
34
+
35
+ **수동 초기화:**
36
+
37
+ ```ts
38
+ const res = await fetch("/v1/health");
39
+ const { packet_encryption } = await res.json();
40
+
41
+ if (packet_encryption) {
42
+ entityServer.configure({ encryptRequests: true });
43
+ }
44
+ ```
45
+
46
+ > **주의:** 패킷 magic 길이(K)는 `K = 2 + key[31] % 14` 공식으로 패킷 키에서 자동 파생됩니다.
47
+ > 클라이언트와 서버 모두 동일한 패킷 키를 보유하므로 별도 설정 필요 없습니다.
@@ -0,0 +1,32 @@
1
+ # 본인인증
2
+
3
+ ## `identityRequest(opts)`
4
+
5
+ 본인인증 세션을 생성합니다.
6
+
7
+ ```ts
8
+ const res = await client.identityRequest({
9
+ redirect_url: "https://example.com/identity/callback",
10
+ method: "pass",
11
+ });
12
+ res.data; // { request_id: "...", auth_url: "..." } 등
13
+ ```
14
+
15
+ ## `identityResult(requestId)`
16
+
17
+ 본인인증 결과를 조회합니다. 콜백 처리 후 호출합니다.
18
+
19
+ ```ts
20
+ const res = await client.identityResult("request-id-here");
21
+ res.data; // { name, phone, birth, gender, di, ci, ... }
22
+ ```
23
+
24
+ ## `identityVerifyCI(ciHash)`
25
+
26
+ CI(연계정보) 해시로 기존 계정 중복 여부를 확인합니다.
27
+
28
+ ```ts
29
+ const res = await client.identityVerifyCI("ci-hash-string");
30
+ res.data.exists; // true: 이미 가입된 계정 존재
31
+ res.data.account_seq; // 존재할 경우 해당 계정 seq
32
+ ```
@@ -0,0 +1,45 @@
1
+ # import
2
+
3
+ ```ts
4
+ import {
5
+ EntityServerClient,
6
+ entityServer,
7
+ type EntityListParams,
8
+ type EntityListResult,
9
+ type EntityHistoryRecord,
10
+ type EntityQueryRequest,
11
+ type EntityServerClientOptions,
12
+ type RegisterPushDeviceOptions,
13
+ type PushSendRequest,
14
+ type PushSendAllRequest,
15
+ type SmsSendRequest,
16
+ type SmtpSendRequest,
17
+ type AlimtalkSendRequest,
18
+ type FriendtalkSendRequest,
19
+ type PgCreateOrderRequest,
20
+ type PgConfirmPaymentRequest,
21
+ type PgCancelPaymentRequest,
22
+ type FileMeta,
23
+ type FileUploadOptions,
24
+ type IdentityRequestOptions,
25
+ type QRCodeOptions,
26
+ type BarcodeOptions,
27
+ } from "entity-server-client";
28
+ ```
29
+
30
+ React Hook:
31
+
32
+ ```ts
33
+ import { useEntityServer } from "entity-server-client/react";
34
+ ```
35
+
36
+ 패킷 프로토콜 코어 (SDK 없이 암복호만 필요할 때):
37
+
38
+ ```ts
39
+ import {
40
+ derivePacketKey,
41
+ packetMagicLenFromKey,
42
+ encryptPacket,
43
+ decryptPacket,
44
+ } from "entity-server-client/packet";
45
+ ```
@@ -0,0 +1,90 @@
1
+ # 암호화 패킷 처리
2
+
3
+ ## 자동 복호화
4
+
5
+ 서버 응답이 `Content-Type: application/octet-stream`이면 `request()` 내부에서 자동으로 복호화됩니다.
6
+ XChaCha20-Poly1305 알고리즘을 사용하며, 키는 HKDF-SHA256으로 파생됩니다.
7
+
8
+ - 키 소스: `hmacSecret` → `token`(JWT) → `anonymousPacketToken` 순서로 결정
9
+ - salt: `entity-server:hkdf:v1`
10
+ - info: `entity-server:packet-encryption`
11
+ - 출력: 32바이트
12
+
13
+ 별도 처리 없이 일반 응답과 동일하게 사용하면 됩니다.
14
+
15
+ ## `readRequestBody(body, contentType?, requireEncrypted?)`
16
+
17
+ 원시 암호화 payload를 직접 파싱할 때 사용합니다.
18
+ 주로 서버 측 미들웨어나 SSR 환경에서 클라이언트로부터 받은 암호화 body를 처리할 때 활용합니다.
19
+
20
+ ```ts
21
+ // 암호화 body 복호화
22
+ const data = client.readRequestBody(
23
+ arrayBuffer, // ArrayBuffer | Uint8Array
24
+ "application/octet-stream",
25
+ true, // requireEncrypted: 암호화 아니면 에러
26
+ );
27
+
28
+ // 일반 JSON body 파싱
29
+ const data2 = client.readRequestBody(jsonString, "application/json");
30
+ ```
31
+
32
+ ## `entity-server-client/packet` 서브패스 — 프로토콜 코어
33
+
34
+ 패킷 암복호 프로토콜만 독립적으로 사용할 때는 서브패스를 import합니다.
35
+ 브라우저, Node.js, 중간 계층 서버 등 SDK 전체가 불필요한 환경에서 유용합니다.
36
+
37
+ ```ts
38
+ import {
39
+ derivePacketKey,
40
+ packetMagicLenFromKey,
41
+ encryptPacket,
42
+ decryptPacket,
43
+ PACKET_KEY_SIZE,
44
+ PACKET_MAGIC_MIN,
45
+ PACKET_MAGIC_RANGE,
46
+ PACKET_NONCE_SIZE,
47
+ PACKET_TAG_SIZE,
48
+ PACKET_HKDF_SALT,
49
+ PACKET_INFO_LABEL,
50
+ } from "entity-server-client/packet";
51
+ ```
52
+
53
+ ### `derivePacketKey(source, infoLabel?)`
54
+
55
+ HKDF-SHA256으로 32바이트 패킷 키를 파생합니다.
56
+
57
+ ```ts
58
+ const key = derivePacketKey("anon.v1.example-token");
59
+ // infoLabel 기본값: "entity-server:packet-encryption"
60
+ ```
61
+
62
+ ### `packetMagicLenFromKey(key, magicMin?, magicRange?)`
63
+
64
+ 키에서 magic 바이트 길이를 파생합니다. `magicLen = magicMin + key[31] % magicRange`
65
+
66
+ ```ts
67
+ const magicLen = packetMagicLenFromKey(key);
68
+ // 기본값: magicMin=2, magicRange=14 → 결과 범위 [2, 15]
69
+ ```
70
+
71
+ ### `encryptPacket(plaintext, key, magicMin?, magicRange?)`
72
+
73
+ 평문 바이트를 XChaCha20-Poly1305로 암호화합니다.
74
+ 포맷: `[random_magic:magicLen][random_nonce:24][ciphertext+tag]`
75
+
76
+ ```ts
77
+ const encrypted = encryptPacket(
78
+ new TextEncoder().encode(JSON.stringify({ ok: true })),
79
+ key,
80
+ );
81
+ ```
82
+
83
+ ### `decryptPacket(buffer, key, magicMin?, magicRange?)`
84
+
85
+ 암호화된 패킷을 복호화해 평문 바이트를 반환합니다.
86
+
87
+ ```ts
88
+ const plaintext = decryptPacket(encrypted, key);
89
+ console.log(new TextDecoder().decode(plaintext));
90
+ ```
package/docs/api/pg.md ADDED
@@ -0,0 +1,90 @@
1
+ # PG 결제
2
+
3
+ 결제 라이프사이클: `pgCreateOrder` → (사용자 결제 진행) → `pgConfirmPayment` → (`pgSyncPayment` | `pgCancelPayment`)
4
+
5
+ ## `pgCreateOrder(req)`
6
+
7
+ 결제 주문을 생성합니다. 결제 전 서버에 주문 정보를 등록합니다.
8
+
9
+ | 필드 | 타입 | 설명 |
10
+ | --------------- | -------- | ----------------------- |
11
+ | `orderId` | `string` | 가맹점 주문 ID (고유값) |
12
+ | `amount` | `number` | 결제 금액 (원 단위) |
13
+ | `orderName` | `string` | 주문명 |
14
+ | `customerName` | `string` | 구매자 이름 |
15
+ | `customerEmail` | `string` | 구매자 이메일 |
16
+
17
+ ```ts
18
+ const order = await client.pgCreateOrder({
19
+ orderId: "order-2026-001",
20
+ amount: 15000,
21
+ orderName: "상품 A",
22
+ customerName: "홍길동",
23
+ customerEmail: "hong@example.com",
24
+ });
25
+ order.paymentKey; // 결제창 호출에 필요한 키
26
+ ```
27
+
28
+ ## `pgGetOrder(orderId)`
29
+
30
+ 주문 정보를 조회합니다.
31
+
32
+ ```ts
33
+ const order = await client.pgGetOrder("order-2026-001");
34
+ order.status; // "pending" | "paid" | "cancelled" ...
35
+ order.amount;
36
+ order.paymentKey;
37
+ ```
38
+
39
+ ## `pgConfirmPayment(req)`
40
+
41
+ 결제 완료 후 서버 측 승인 처리를 합니다. PG사에서 발급된 `paymentKey`와 금액으로 검증합니다.
42
+
43
+ | 필드 | 타입 | 설명 |
44
+ | ------------ | -------- | -------------------------------------- |
45
+ | `paymentKey` | `string` | PG사 결제 키 (클라이언트에서 수신) |
46
+ | `orderId` | `string` | 가맹점 주문 ID |
47
+ | `amount` | `number` | 결제 금액 (주문 금액과 불일치 시 에러) |
48
+
49
+ ```ts
50
+ const result = await client.pgConfirmPayment({
51
+ paymentKey: "toss_paymentKey_abc",
52
+ orderId: "order-2026-001",
53
+ amount: 15000,
54
+ });
55
+ result.status; // "paid"
56
+ ```
57
+
58
+ ## `pgCancelPayment(paymentKey, req?)`
59
+
60
+ 결제를 취소(환불)합니다.
61
+
62
+ ```ts
63
+ // 전액 취소
64
+ await client.pgCancelPayment("toss_paymentKey_abc");
65
+
66
+ // 부분 취소
67
+ await client.pgCancelPayment("toss_paymentKey_abc", {
68
+ cancelReason: "고객 요청",
69
+ cancelAmount: 5000,
70
+ });
71
+ ```
72
+
73
+ ## `pgSyncPayment(paymentKey)`
74
+
75
+ PG사의 실시간 결제 상태를 서버 DB에 동기화합니다.
76
+
77
+ ```ts
78
+ const result = await client.pgSyncPayment("toss_paymentKey_abc");
79
+ result.status; // 동기화 후 상태
80
+ ```
81
+
82
+ ## `pgConfig()`
83
+
84
+ 설정된 PG 정보를 조회합니다. 클라이언트 측 결제창 초기화에 필요한 `clientKey` 등을 포함합니다.
85
+
86
+ ```ts
87
+ const config = await client.pgConfig();
88
+ config.provider; // "toss" | ...
89
+ config.clientKey; // 결제창 초기화용 키
90
+ ```
@@ -0,0 +1,107 @@
1
+ # 푸시 관련
2
+
3
+ ## `push(pushEntity, payload, opts?)`
4
+
5
+ `submit()`의 별칭입니다. 푸시 관련 엔티티에 데이터를 제출합니다.
6
+
7
+ ```ts
8
+ await client.push("push_message", {
9
+ title: "새 알림",
10
+ body: "내용",
11
+ account_seq: 1,
12
+ });
13
+ ```
14
+
15
+ ## `pushLogList(params?)`
16
+
17
+ `push_log` 엔티티의 목록을 조회합니다. `list()`의 별칭입니다.
18
+
19
+ ```ts
20
+ const logs = await client.pushLogList({
21
+ page: 1,
22
+ limit: 30,
23
+ orderBy: "-created_time",
24
+ conditions: { account_seq: 1 },
25
+ });
26
+ logs.data.items;
27
+ logs.data.total;
28
+ ```
29
+
30
+ ## `registerPushDevice(accountSeq, deviceId, pushToken, opts?)`
31
+
32
+ `account_device` 엔티티에 디바이스를 등록합니다.
33
+
34
+ | 옵션 | 타입 | 설명 |
35
+ | ---------------- | --------- | --------------------------------- |
36
+ | `platform` | `string` | 플랫폼 (예: `"web"`, `"android"`) |
37
+ | `deviceType` | `string` | 디바이스 종류 (예: `"mobile"`) |
38
+ | `browser` | `string` | 브라우저명 (예: `"Chrome"`) |
39
+ | `browserVersion` | `string` | 브라우저 버전 |
40
+ | `pushEnabled` | `boolean` | 푸시 수신 여부. 기본값 `true` |
41
+ | `transactionId` | `string` | 트랜잭션 ID |
42
+
43
+ ```ts
44
+ const res = await client.registerPushDevice(
45
+ 1,
46
+ "device-uuid-001",
47
+ "fcm-token-abc",
48
+ {
49
+ platform: "web",
50
+ browser: "Chrome",
51
+ browserVersion: "120",
52
+ pushEnabled: true,
53
+ },
54
+ );
55
+ res.seq; // 등록된 account_device seq
56
+ ```
57
+
58
+ ## `updatePushDeviceToken(deviceSeq, pushToken, opts?)`
59
+
60
+ 등록된 디바이스의 푸시 토큰을 갱신합니다.
61
+
62
+ ```ts
63
+ await client.updatePushDeviceToken(10, "new-fcm-token", { pushEnabled: true });
64
+ ```
65
+
66
+ ## `disablePushDevice(deviceSeq, opts?)`
67
+
68
+ 디바이스의 푸시 수신을 비활성화합니다 (`push_enabled: false`).
69
+
70
+ ```ts
71
+ await client.disablePushDevice(10);
72
+ ```
73
+
74
+ ## `pushSend(req)`
75
+
76
+ 특정 계정에 푸시 알림을 직접 발송합니다.
77
+
78
+ ```ts
79
+ const res = await client.pushSend({
80
+ account_seq: 1,
81
+ title: "알림 제목",
82
+ body: "알림 내용",
83
+ });
84
+ res.seq; // 발송 로그 seq
85
+ ```
86
+
87
+ ## `pushSendAll(req)`
88
+
89
+ 전체 또는 조건에 맞는 모든 사용자에게 푸시 알림을 발송합니다.
90
+
91
+ ```ts
92
+ const res = await client.pushSendAll({
93
+ title: "공지사항",
94
+ body: "전체 공지 내용입니다.",
95
+ });
96
+ res.sent; // 발송 성공 수
97
+ res.failed; // 발송 실패 수
98
+ ```
99
+
100
+ ## `pushStatus(seq)`
101
+
102
+ 푸시 발송 로그의 처리 상태를 조회합니다.
103
+
104
+ ```ts
105
+ const res = await client.pushStatus(42);
106
+ res.status; // "sent" | "failed" | "pending" 등
107
+ ```
@@ -0,0 +1,141 @@
1
+ # React Hook
2
+
3
+ ## `useEntityServer(options?)`
4
+
5
+ React 컴포넌트에서 EntityServerClient를 사용할 때 권장되는 훅입니다.
6
+ `submit` / `del` / `query` 의 `isPending`, `error` 상태를 자동으로 관리합니다.
7
+
8
+ ```ts
9
+ import { useEntityServer } from "entity-server-client/react";
10
+ ```
11
+
12
+ ### 옵션
13
+
14
+ | 옵션 | 타입 | 기본값 | 설명 |
15
+ | ------------------ | ----------------------------------- | ------ | ----------------------------------------------------------------------------------------------- |
16
+ | `singleton` | `boolean` | `true` | `true`이면 전역 `entityServer` 인스턴스 사용 |
17
+ | `baseUrl` | `string` | — | 서버 주소 (singleton일 때 `configure()` 호출) |
18
+ | `token` | `string` | — | JWT Access Token |
19
+ | `tokenResolver` | `() => string \| undefined \| null` | — | 렌더 시점에 토큰을 동적으로 주입하는 함수 |
20
+ | `keepSession` | `boolean` | — | 세션 유지 활성화 |
21
+ | `onTokenRefreshed` | `(accessToken, expiresIn) => void` | — | 세션 유지 성공 콜백 |
22
+ | `onSessionExpired` | `(error: Error) => void` | — | 세션 만료 콜백 (refresh_token 만료 등) |
23
+ | `resumeSession` | `string` | — | 저장된 refresh_token. 마운트 시 `refreshToken()` 호출해 토큰 복원 + `keepSession` 타이머 재시작 |
24
+
25
+ ### 반환값
26
+
27
+ | 필드 | 타입 | 설명 |
28
+ | ----------- | -------------------- | ----------------------------------------------------- |
29
+ | `client` | `EntityServerClient` | **모든 API 메서드에 접근 가능한 클라이언트 인스턴스** |
30
+ | `isPending` | `boolean` | `submit` / `del` / `query` 진행 중 여부 |
31
+ | `error` | `Error \| null` | 마지막 mutation 에러. 성공 또는 `reset()` 시 `null` |
32
+ | `reset` | `() => void` | `isPending`, `error` 초기화 |
33
+ | `submit` | function | `client.submit()` 래퍼 — 상태 자동 관리 |
34
+ | `del` | function | `client.delete()` 래퍼 — 상태 자동 관리 |
35
+ | `query` | function | `client.query()` 래퍼 — 상태 자동 관리 |
36
+
37
+ > **`client`는 `EntityServerClient` 인스턴스 그대로입니다.**
38
+ > `submit` / `del` / `query` 래퍼는 `isPending` / `error` 상태를 자동 관리해주는 편의 메서드일 뿐이며,
39
+ > 인증, 푸시, 파일, SMS, PG 등 **모든 API**는 `client.메서드명()`으로 직접 호출합니다.
40
+
41
+ ### `client`로 사용 가능한 모든 API
42
+
43
+ `client`를 통해 아래 모든 섹션의 메서드를 그대로 사용할 수 있습니다.
44
+
45
+ | 카테고리 | 주요 메서드 (예시) | 상세 문서 |
46
+ | ------------------ | ---------------------------------------------------------------------------------- | -------------------------------- |
47
+ | 인증 | `login`, `logout`, `register`, `refreshToken`, `oauthLogin`, `enable2FA` … | [auth.md](auth.md) |
48
+ | 트랜잭션 | `transStart`, `transCommit`, `transRollback` | [transaction.md](transaction.md) |
49
+ | 엔티티 CRUD / 조회 | `get`, `find`, `list`, `count`, `query`, `submit`, `delete`, `history`, `rollback` | [entity.md](entity.md) |
50
+ | 푸시 | `pushSend`, `pushSendAll`, `pushStatus`, `registerPushDevice` … | [push.md](push.md) |
51
+ | 이메일 인증 | `emailVerificationSend`, `emailVerificationConfirm`, `emailChange` | [email.md](email.md) |
52
+ | SMS | `smsSend`, `smsVerificationSend`, `smsVerificationVerify` | [sms.md](sms.md) |
53
+ | SMTP 메일 | `smtpSend`, `smtpStatus` | [smtp.md](smtp.md) |
54
+ | 알림톡 / 친구톡 | `alimtalkSend`, `alimtalkTemplates`, `friendtalkSend` | [alimtalk.md](alimtalk.md) |
55
+ | PG 결제 | `pgCreateOrder`, `pgConfirmPayment`, `pgCancelPayment`, `pgConfig` | [pg.md](pg.md) |
56
+ | 파일 스토리지 | `fileUpload`, `fileDownload`, `fileDelete`, `fileList`, `fileToken`, `fileUrl` | [file.md](file.md) |
57
+ | 본인인증 | `identityRequest`, `identityResult`, `identityVerifyCI` | [identity.md](identity.md) |
58
+ | QR코드 / 바코드 | `qrcode`, `qrcodeBase64`, `barcode` | [utils.md](utils.md) |
59
+
60
+ ### 기본 사용 예시
61
+
62
+ ```tsx
63
+ import { useEntityServer } from "entity-server-client/react";
64
+
65
+ function AccountForm() {
66
+ const { submit, del, isPending, error, reset } = useEntityServer();
67
+
68
+ const handleSave = async () => {
69
+ reset();
70
+ await submit("account", { name: "홍길동", email: "hong@example.com" });
71
+ };
72
+
73
+ const handleDelete = async (seq: number) => {
74
+ await del("account", seq);
75
+ };
76
+
77
+ return (
78
+ <div>
79
+ {isPending && <span>저장 중...</span>}
80
+ {error && <span style={{ color: "red" }}>{error.message}</span>}
81
+ <button onClick={handleSave} disabled={isPending}>
82
+ 저장
83
+ </button>
84
+ <button onClick={() => handleDelete(1)} disabled={isPending}>
85
+ 삭제
86
+ </button>
87
+ </div>
88
+ );
89
+ }
90
+ ```
91
+
92
+ ### 동적 토큰 주입
93
+
94
+ ```tsx
95
+ const { submit } = useEntityServer({
96
+ tokenResolver: () => localStorage.getItem("access_token"),
97
+ });
98
+ ```
99
+
100
+ ### 페이지 새로고침 후 세션 복원
101
+
102
+ `resumeSession`에 저장된 refresh_token을 넘기면 마운트 시 `refreshToken()`을 호출해
103
+ 새 access_token을 발급받고, `keepSession: true`이면 타이머도 자동 재시작됩니다.
104
+
105
+ ```tsx
106
+ // App.tsx 또는 로그인 복원을 담당하는 컴포넌트
107
+ export function AppShell() {
108
+ const storedRefreshToken =
109
+ localStorage.getItem("auth_refresh_token") ?? undefined;
110
+
111
+ useEntityServer({
112
+ resumeSession: storedRefreshToken,
113
+ keepSession: true,
114
+ onTokenRefreshed: (accessToken) => {
115
+ localStorage.setItem("auth_access_token", accessToken);
116
+ },
117
+ onSessionExpired: () => {
118
+ window.location.href = "/login";
119
+ },
120
+ });
121
+
122
+ return <RouterOutlet />;
123
+ }
124
+ ```
125
+
126
+ 마운트 시 `resumeSession`이 있으면:
127
+
128
+ 1. `refreshToken(storedRefreshToken)` 호출 → 서버에서 새 access_token 발급
129
+ 2. 패키지 내부 `this.token` 자동 교체
130
+ 3. `onTokenRefreshed` 콜백 호출
131
+ 4. `keepSession: true`이면 다음 만료 전 타이머 자동 예약
132
+ 5. 실패 시 `onSessionExpired` 콜백 호출
133
+
134
+ ### 컴포넌트별 독립 인스턴스
135
+
136
+ ```tsx
137
+ const { client } = useEntityServer({
138
+ singleton: false,
139
+ baseUrl: "https://other-server.example.com",
140
+ });
141
+ ```