entity-server-client 0.1.0 → 0.2.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/dist/index.d.ts CHANGED
@@ -79,6 +79,15 @@ export interface EntityServerClientOptions {
79
79
  baseUrl?: string;
80
80
  token?: string;
81
81
  packetMagicLen?: number;
82
+ /**
83
+ * `true`이면 인증된 POST/PUT 요청 바디를 XChaCha20-Poly1305로 암호화합니다.
84
+ *
85
+ * 서버의 `EnablePacketEncryption`이 활성화된 경우 필수로 설정해야 합니다.
86
+ * 로그인(`login()`)·토큰 갱신(`refreshToken()`)은 인증 전 요청이므로 자동으로 건너뜁니다.
87
+ *
88
+ * 기본값: `false`
89
+ */
90
+ encryptRequests?: boolean;
82
91
  }
83
92
  /**
84
93
  * `list()`, `history()` 응답의 `data` 필드 구조입니다.
@@ -114,6 +123,7 @@ export declare class EntityServerClient {
114
123
  private baseUrl;
115
124
  private token;
116
125
  private packetMagicLen;
126
+ private encryptRequests;
117
127
  private activeTxId;
118
128
  /**
119
129
  * EntityServerClient 인스턴스를 생성합니다.
@@ -123,7 +133,7 @@ export declare class EntityServerClient {
123
133
  * - `packetMagicLen`: `VITE_PACKET_MAGIC_LEN` 또는 `4`
124
134
  */
125
135
  constructor(options?: EntityServerClientOptions);
126
- /** baseUrl, token, packetMagicLen 값을 런타임에 갱신합니다. */
136
+ /** baseUrl, token, packetMagicLen, encryptRequests 값을 런타임에 갱신합니다. */
127
137
  configure(options: Partial<EntityServerClientOptions>): void;
128
138
  /** 인증 요청에 사용할 JWT Access Token을 설정합니다. */
129
139
  setToken(token: string): void;
@@ -262,10 +272,16 @@ export declare class EntityServerClient {
262
272
  /**
263
273
  * 공통 HTTP 요청 함수입니다.
264
274
  *
265
- * 응답이 `application/octet-stream`이면 자동 복호화하고,
266
- * JSON 응답의 `ok`가 false이면 에러를 던집니다.
275
+ * - `encryptRequests`가 활성화된 인증 요청의 POST 바디를 자동 암호화합니다.
276
+ * - 응답이 `application/octet-stream`이면 자동 복호화합니다.
277
+ * - JSON 응답의 `ok`가 false이면 에러를 던집니다.
267
278
  */
268
279
  private request;
280
+ /**
281
+ * 평문 바이트를 XChaCha20-Poly1305로 암호화합니다.
282
+ * 포맷: [random_magic:packetMagicLen][random_nonce:24][ciphertext+tag]
283
+ */
284
+ private encryptPacket;
269
285
  /** 서버의 암호화 패킷을 복호화해 JSON 객체로 변환합니다. */
270
286
  private decryptPacket;
271
287
  }
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{xchacha20poly1305 as k}from"@noble/ciphers/chacha";import{sha256 as h}from"@noble/hashes/sha2";function g(l){return import.meta?.env?.[l]}var d=class{baseUrl;token;packetMagicLen;activeTxId=null;constructor(t={}){let e=g("VITE_ENTITY_SERVER_URL"),n=g("VITE_PACKET_MAGIC_LEN");this.baseUrl=(t.baseUrl??e??"http://localhost:47200").replace(/\/$/,""),this.token=t.token??"",this.packetMagicLen=t.packetMagicLen??(n?Number(n):4)}configure(t){t.baseUrl&&(this.baseUrl=t.baseUrl.replace(/\/$/,"")),typeof t.token=="string"&&(this.token=t.token),typeof t.packetMagicLen=="number"&&(this.packetMagicLen=t.packetMagicLen)}setToken(t){this.token=t}setPacketMagicLen(t){this.packetMagicLen=t}getPacketMagicLen(){return this.packetMagicLen}async login(t,e){let n=await this.request("POST","/v1/auth/login",{email:t,passwd:e},!1);return this.token=n.data.access_token,n.data}async refreshToken(t){let e=await this.request("POST","/v1/auth/refresh",{refresh_token:t},!1);return this.token=e.data.access_token,e.data}async transStart(){let t=await this.request("POST","/v1/transaction/start",void 0,!1);return this.activeTxId=t.transaction_id,this.activeTxId}transRollback(t){let e=t??this.activeTxId;return e?(this.activeTxId=null,this.request("POST",`/v1/transaction/rollback/${e}`)):Promise.reject(new Error("No active transaction. Call transStart() first."))}transCommit(t){let e=t??this.activeTxId;return e?(this.activeTxId=null,this.request("POST",`/v1/transaction/commit/${e}`)):Promise.reject(new Error("No active transaction. Call transStart() first."))}get(t,e,n={}){let r=n.skipHooks?"?skipHooks=true":"";return this.request("GET",`/v1/entity/${t}/${e}${r}`)}list(t,e={}){let{conditions:n,fields:r,orderDir:s,orderBy:i,...o}=e,a={page:1,limit:20,...o};i&&(a.orderBy=s==="DESC"?`-${i}`:i),r?.length&&(a.fields=r.join(","));let c=p(a);return this.request("POST",`/v1/entity/${t}/list?${c}`,n??{})}count(t,e){return this.request("POST",`/v1/entity/${t}/count`,e??{})}query(t,e){return this.request("POST",`/v1/entity/${t}/query`,e)}submit(t,e,n={}){let r=n.transactionId??this.activeTxId,s=r?{"X-Transaction-ID":r}:void 0,i=n.skipHooks?"?skipHooks=true":"";return this.request("POST",`/v1/entity/${t}/submit${i}`,e,!0,s)}delete(t,e,n={}){let r=new URLSearchParams;n.hard&&r.set("hard","true"),n.skipHooks&&r.set("skipHooks","true");let s=r.size?`?${r}`:"",i=n.transactionId??this.activeTxId,o=i?{"X-Transaction-ID":i}:void 0;return this.request("POST",`/v1/entity/${t}/delete/${e}${s}`,void 0,!0,o)}history(t,e,n={}){let r=p({page:1,limit:50,...n});return this.request("GET",`/v1/entity/${t}/history/${e}?${r}`)}rollback(t,e){return this.request("POST",`/v1/entity/${t}/rollback/${e}`)}push(t,e,n={}){return this.submit(t,e,n)}pushLogList(t={}){return this.list("push_log",t)}registerPushDevice(t,e,n,r={}){let{platform:s,deviceType:i,browser:o,browserVersion:a,pushEnabled:c=!0,transactionId:u}=r;return this.submit("account_device",{id:e,account_seq:t,push_token:n,push_enabled:c,...s?{platform:s}:{},...i?{device_type:i}:{},...o?{browser:o}:{},...a?{browser_version:a}:{}},{transactionId:u})}updatePushDeviceToken(t,e,n={}){let{pushEnabled:r=!0,transactionId:s}=n;return this.submit("account_device",{seq:t,push_token:e,push_enabled:r},{transactionId:s})}disablePushDevice(t,e={}){return this.submit("account_device",{seq:t,push_enabled:!1},{transactionId:e.transactionId})}readRequestBody(t,e="application/json",n=!1){let s=e.toLowerCase().includes("application/octet-stream");if(n&&!s)throw new Error("Encrypted request required: Content-Type must be application/octet-stream");if(s){if(t==null)throw new Error("Encrypted request body is empty");if(t instanceof ArrayBuffer)return this.decryptPacket(t);if(t instanceof Uint8Array){let i=t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength);return this.decryptPacket(i)}throw new Error("Encrypted request body must be ArrayBuffer or Uint8Array")}return t==null||t===""?{}:typeof t=="string"?JSON.parse(t):t}async request(t,e,n,r=!0,s={}){let i={"Content-Type":"application/json",...s};r&&this.token&&(i.Authorization=`Bearer ${this.token}`);let o=await fetch(this.baseUrl+e,{method:t,headers:i,...n!=null?{body:JSON.stringify(n)}:{}});if((o.headers.get("Content-Type")??"").includes("application/octet-stream")){let u=await o.arrayBuffer();return this.decryptPacket(u)}let c=await o.json();if(!c.ok){let u=new Error(c.message??`EntityServer error (HTTP ${o.status})`);throw u.status=o.status,u}return c}decryptPacket(t){let e=h(new TextEncoder().encode(this.token)),n=new Uint8Array(t);if(n.length<this.packetMagicLen+24+16)throw new Error("Encrypted packet too short");let r=n.slice(this.packetMagicLen,this.packetMagicLen+24),s=n.slice(this.packetMagicLen+24),o=k(e,r).decrypt(s);return JSON.parse(new TextDecoder().decode(o))}};function p(l){return Object.entries(l).filter(([,t])=>t!=null).map(([t,e])=>`${encodeURIComponent(t==="orderBy"?"order_by":t)}=${encodeURIComponent(String(e))}`).join("&")}var f=new d;export{d as EntityServerClient,f as entityServer};
1
+ import{xchacha20poly1305 as g}from"@noble/ciphers/chacha";import{sha256 as h}from"@noble/hashes/sha2";function k(l){return import.meta?.env?.[l]}var p=class{baseUrl;token;packetMagicLen;encryptRequests;activeTxId=null;constructor(e={}){let t=k("VITE_ENTITY_SERVER_URL"),n=k("VITE_PACKET_MAGIC_LEN");this.baseUrl=(e.baseUrl??t??"http://localhost:47200").replace(/\/$/,""),this.token=e.token??"",this.packetMagicLen=e.packetMagicLen??(n?Number(n):4),this.encryptRequests=e.encryptRequests??!1}configure(e){e.baseUrl&&(this.baseUrl=e.baseUrl.replace(/\/$/,"")),typeof e.token=="string"&&(this.token=e.token),typeof e.packetMagicLen=="number"&&(this.packetMagicLen=e.packetMagicLen),typeof e.encryptRequests=="boolean"&&(this.encryptRequests=e.encryptRequests)}setToken(e){this.token=e}setPacketMagicLen(e){this.packetMagicLen=e}getPacketMagicLen(){return this.packetMagicLen}async login(e,t){let n=await this.request("POST","/v1/auth/login",{email:e,passwd:t},!1);return this.token=n.data.access_token,n.data}async refreshToken(e){let t=await this.request("POST","/v1/auth/refresh",{refresh_token:e},!1);return this.token=t.data.access_token,t.data}async transStart(){let e=await this.request("POST","/v1/transaction/start",void 0,!1);return this.activeTxId=e.transaction_id,this.activeTxId}transRollback(e){let t=e??this.activeTxId;return t?(this.activeTxId=null,this.request("POST",`/v1/transaction/rollback/${t}`)):Promise.reject(new Error("No active transaction. Call transStart() first."))}transCommit(e){let t=e??this.activeTxId;return t?(this.activeTxId=null,this.request("POST",`/v1/transaction/commit/${t}`)):Promise.reject(new Error("No active transaction. Call transStart() first."))}get(e,t,n={}){let r=n.skipHooks?"?skipHooks=true":"";return this.request("GET",`/v1/entity/${e}/${t}${r}`)}list(e,t={}){let{conditions:n,fields:r,orderDir:i,orderBy:s,...a}=t,o={page:1,limit:20,...a};s&&(o.orderBy=i==="DESC"?`-${s}`:s),r?.length&&(o.fields=r.join(","));let d=m(o);return this.request("POST",`/v1/entity/${e}/list?${d}`,n??{})}count(e,t){return this.request("POST",`/v1/entity/${e}/count`,t??{})}query(e,t){return this.request("POST",`/v1/entity/${e}/query`,t)}submit(e,t,n={}){let r=n.transactionId??this.activeTxId,i=r?{"X-Transaction-ID":r}:void 0,s=n.skipHooks?"?skipHooks=true":"";return this.request("POST",`/v1/entity/${e}/submit${s}`,t,!0,i)}delete(e,t,n={}){let r=new URLSearchParams;n.hard&&r.set("hard","true"),n.skipHooks&&r.set("skipHooks","true");let i=r.size?`?${r}`:"",s=n.transactionId??this.activeTxId,a=s?{"X-Transaction-ID":s}:void 0;return this.request("POST",`/v1/entity/${e}/delete/${t}${i}`,void 0,!0,a)}history(e,t,n={}){let r=m({page:1,limit:50,...n});return this.request("GET",`/v1/entity/${e}/history/${t}?${r}`)}rollback(e,t){return this.request("POST",`/v1/entity/${e}/rollback/${t}`)}push(e,t,n={}){return this.submit(e,t,n)}pushLogList(e={}){return this.list("push_log",e)}registerPushDevice(e,t,n,r={}){let{platform:i,deviceType:s,browser:a,browserVersion:o,pushEnabled:d=!0,transactionId:c}=r;return this.submit("account_device",{id:t,account_seq:e,push_token:n,push_enabled:d,...i?{platform:i}:{},...s?{device_type:s}:{},...a?{browser:a}:{},...o?{browser_version:o}:{}},{transactionId:c})}updatePushDeviceToken(e,t,n={}){let{pushEnabled:r=!0,transactionId:i}=n;return this.submit("account_device",{seq:e,push_token:t,push_enabled:r},{transactionId:i})}disablePushDevice(e,t={}){return this.submit("account_device",{seq:e,push_enabled:!1},{transactionId:t.transactionId})}readRequestBody(e,t="application/json",n=!1){let i=t.toLowerCase().includes("application/octet-stream");if(n&&!i)throw new Error("Encrypted request required: Content-Type must be application/octet-stream");if(i){if(e==null)throw new Error("Encrypted request body is empty");if(e instanceof ArrayBuffer)return this.decryptPacket(e);if(e instanceof Uint8Array){let s=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);return this.decryptPacket(s)}throw new Error("Encrypted request body must be ArrayBuffer or Uint8Array")}return e==null||e===""?{}:typeof e=="string"?JSON.parse(e):e}async request(e,t,n,r=!0,i={}){let s={"Content-Type":"application/json",...i};r&&this.token&&(s.Authorization=`Bearer ${this.token}`);let a=null;if(n!=null)if(this.encryptRequests&&r&&this.token&&e!=="GET"&&e!=="HEAD"){let y=new TextEncoder().encode(JSON.stringify(n)),b=this.encryptPacket(y);s["Content-Type"]="application/octet-stream",a=b}else a=JSON.stringify(n);let o=await fetch(this.baseUrl+t,{method:e,headers:s,...a!=null?{body:a}:{}});if((o.headers.get("Content-Type")??"").includes("application/octet-stream")){let u=await o.arrayBuffer();return this.decryptPacket(u)}let c=await o.json();if(!c.ok){let u=new Error(c.message??`EntityServer error (HTTP ${o.status})`);throw u.status=o.status,u}return c}encryptPacket(e){let t=h(new TextEncoder().encode(this.token)),n=new Uint8Array(this.packetMagicLen),r=new Uint8Array(24);crypto.getRandomValues(n),crypto.getRandomValues(r);let s=g(t,r).encrypt(e),a=new Uint8Array(this.packetMagicLen+24+s.length);return a.set(n,0),a.set(r,this.packetMagicLen),a.set(s,this.packetMagicLen+24),a}decryptPacket(e){let t=h(new TextEncoder().encode(this.token)),n=new Uint8Array(e);if(n.length<this.packetMagicLen+24+16)throw new Error("Encrypted packet too short");let r=n.slice(this.packetMagicLen,this.packetMagicLen+24),i=n.slice(this.packetMagicLen+24),a=g(t,r).decrypt(i);return JSON.parse(new TextDecoder().decode(a))}};function m(l){return Object.entries(l).filter(([,e])=>e!=null).map(([e,t])=>`${encodeURIComponent(e==="orderBy"?"order_by":e)}=${encodeURIComponent(String(t))}`).join("&")}var E=new p;export{p as EntityServerClient,E as entityServer};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["// @ts-ignore\nimport { xchacha20poly1305 } from \"@noble/ciphers/chacha\";\n// @ts-ignore\nimport { sha256 } from \"@noble/hashes/sha2\";\n\n/**\n * \uC5D4\uD2F0\uD2F0 \uBAA9\uB85D \uC870\uD68C \uD30C\uB77C\uBBF8\uD130\uC785\uB2C8\uB2E4.\n *\n * ```ts\n * client.list(\"post\", {\n * page: 1, limit: 10,\n * orderBy: \"created_time\", orderDir: \"DESC\",\n * fields: [\"seq\", \"title\", \"created_time\"],\n * conditions: { status: \"active\" },\n * });\n * ```\n */\nexport interface EntityListParams {\n /** \uC870\uD68C \uD398\uC774\uC9C0 \uBC88\uD638. \uAE30\uBCF8\uAC12: `1` */\n page?: number;\n /** \uD398\uC774\uC9C0\uB2F9 \uB808\uCF54\uB4DC \uC218. \uAE30\uBCF8\uAC12: `20` */\n limit?: number;\n /** \uC815\uB82C \uAE30\uC900 \uD544\uB4DC\uBA85 */\n orderBy?: string;\n /** \uC815\uB82C \uBC29\uD5A5. \uAE30\uBCF8\uAC12: `\"ASC\"` */\n orderDir?: \"ASC\" | \"DESC\";\n /**\n * \uBC18\uD658\uD560 \uD544\uB4DC \uBAA9\uB85D.\n *\n * - **\uBBF8\uC9C0\uC815 (\uAE30\uBCF8\uAC12)**: \uC5D4\uD2F0\uD2F0\uC758 \uC778\uB371\uC2A4 \uD544\uB4DC\uB9CC \uBC18\uD658\uD569\uB2C8\uB2E4.\n * \uBCF5\uD638\uD654\uB97C \uAC74\uB108\uB6F0\uAE30 \uB54C\uBB38\uC5D0 **\uAC00\uC7A5 \uBE60\uB985\uB2C8\uB2E4**.\n * - `[\"*\"]`: \uC804\uCCB4 \uD544\uB4DC \uBC18\uD658 (\uBCF5\uD638\uD654 \uC218\uD589).\n * - \uD544\uB4DC\uBA85 \uBAA9\uB85D: \uD574\uB2F9 \uD544\uB4DC\uB9CC \uBC18\uD658\uD569\uB2C8\uB2E4.\n * \uC5D4\uD2F0\uD2F0 \uC124\uC815\uC5D0 `index`\uB85C \uC120\uC5B8\uB41C \uD544\uB4DC\uB9CC \uC9C0\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.\n * \uC874\uC7AC\uD558\uC9C0 \uC54A\uB294 \uD544\uB4DC\uBA85\uC744 \uC9C0\uC815\uD558\uBA74 \uC11C\uBC84 \uC5D0\uB7EC\uAC00 \uBC1C\uC0DD\uD569\uB2C8\uB2E4.\n * - `seq`, `created_time`, `updated_time`, `license_seq`\uB294 \uD544\uB4DC\uC5D0 \uAD00\uACC4\uC5C6\uC774 \uD56D\uC0C1 \uD3EC\uD568\uB429\uB2C8\uB2E4.\n *\n * ```ts\n * // \uAE30\uBCF8\uAC12 (\uC778\uB371\uC2A4 \uD544\uB4DC\uB9CC, \uAC00\uC7A5 \uBE60\uB984)\n * client.list(\"account\")\n * // \uC804\uCCB4 \uD544\uB4DC\n * client.list(\"account\", { fields: [\"*\"] })\n * // seq, name, email\uB9CC\n * client.list(\"account\", { fields: [\"seq\", \"name\", \"email\"] })\n * ```\n */\n fields?: string[];\n /** \uD544\uD130 \uC870\uAC74. POST body\uB85C \uC804\uB2EC\uB429\uB2C8\uB2E4. (\uC608: `{ status: \"active\" }`) */\n conditions?: Record<string, unknown>;\n}\n\n/**\n * `query()` \uBA54\uC11C\uB4DC\uC5D0 \uC804\uB2EC\uD558\uB294 SQL \uCFFC\uB9AC \uC694\uCCAD\uC785\uB2C8\uB2E4.\n *\n * - `sql`: SELECT \uC804\uC6A9 SQL. \uC778\uB371\uC2A4 \uD14C\uC774\uBE14\uB9CC \uC870\uD68C \uAC00\uB2A5\uD558\uBA70 JOIN \uC9C0\uC6D0.\n * - `params`: SQL \uBC14\uC778\uB529 \uD30C\uB77C\uBBF8\uD130 (`?` \uD50C\uB808\uC774\uC2A4\uD640\uB354 \uB300\uC751).\n * - `limit`: \uCD5C\uB300 \uBC18\uD658 \uAC74\uC218 (\uCD5C\uB300 1000. \uBBF8\uC9C0\uC815 \uC2DC \uC11C\uBC84 \uAE30\uBCF8\uAC12 \uC801\uC6A9).\n *\n * ```ts\n * client.query(\"order\", {\n * sql: `SELECT o.seq, o.status, u.name\n * FROM order o\n * JOIN account u ON u.data_seq = o.account_seq\n * WHERE o.status = ?`,\n * params: [\"pending\"],\n * limit: 100,\n * });\n * ```\n */\nexport interface EntityQueryRequest {\n sql: string;\n params?: unknown[];\n limit?: number;\n}\n\nexport interface RegisterPushDeviceOptions {\n platform?: string;\n deviceType?: string;\n browser?: string;\n browserVersion?: string;\n pushEnabled?: boolean;\n transactionId?: string;\n}\n\n/** EntityServerClient \uC0DD\uC131/\uC124\uC815 \uC635\uC158\uC785\uB2C8\uB2E4. */\nexport interface EntityServerClientOptions {\n baseUrl?: string;\n token?: string;\n packetMagicLen?: number;\n}\n\n/**\n * `list()`, `history()` \uC751\uB2F5\uC758 `data` \uD544\uB4DC \uAD6C\uC870\uC785\uB2C8\uB2E4.\n *\n * \uC11C\uBC84\uB294 \uD56D\uC0C1 \uC774 \uAD6C\uC870\uB85C \uBC18\uD658\uD569\uB2C8\uB2E4:\n * ```json\n * { \"ok\": true, \"data\": { \"items\": [...], \"total\": 100, \"page\": 1, \"limit\": 20 } }\n * ```\n */\nexport interface EntityListResult<T = unknown> {\n items: T[];\n /** \uC804\uCCB4 \uB808\uCF54\uB4DC \uC218 */\n total: number;\n /** \uD604\uC7AC \uD398\uC774\uC9C0 \uBC88\uD638 */\n page: number;\n /** \uD398\uC774\uC9C0\uB2F9 \uB808\uCF54\uB4DC \uC218 */\n limit: number;\n}\n\n/**\n * `history()` \uC751\uB2F5\uC758 \uAC1C\uBCC4 \uC774\uB825 \uB808\uCF54\uB4DC \uAD6C\uC870\uC785\uB2C8\uB2E4.\n *\n * - `action`: `\"INSERT\"` | `\"UPDATE\"` | `\"DELETE_SOFT\"` | `\"DELETE_HARD\"` | `\"ROLLBACK\"`\n * - `data_snapshot`: \uBCC0\uACBD \uB2F9\uC2DC \uC5D4\uD2F0\uD2F0 \uB370\uC774\uD130 \uC2A4\uB0C5\uC0F7\n */\nexport interface EntityHistoryRecord<T = unknown> {\n seq: number;\n action:\n | \"INSERT\"\n | \"UPDATE\"\n | \"DELETE_SOFT\"\n | \"DELETE_HARD\"\n | \"ROLLBACK\"\n | string;\n data_snapshot: T | null;\n changed_by: number | null;\n changed_time: string;\n}\n\n/** Vite \uD658\uACBD\uBCC0\uC218(`import.meta.env`)\uC5D0\uC11C \uAC12\uC744 \uC77D\uC2B5\uB2C8\uB2E4. */\nfunction readEnv(name: string): string | undefined {\n const meta = import.meta as unknown as {\n env?: Record<string, string | undefined>;\n };\n return meta?.env?.[name];\n}\n\nexport class EntityServerClient {\n private baseUrl: string;\n private token: string;\n private packetMagicLen: number;\n private activeTxId: string | null = null;\n\n /**\n * EntityServerClient \uC778\uC2A4\uD134\uC2A4\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4.\n *\n * \uAE30\uBCF8\uAC12:\n * - `baseUrl`: `VITE_ENTITY_SERVER_URL` \uB610\uB294 `http://localhost:47200`\n * - `packetMagicLen`: `VITE_PACKET_MAGIC_LEN` \uB610\uB294 `4`\n */\n constructor(options: EntityServerClientOptions = {}) {\n const envBaseUrl = readEnv(\"VITE_ENTITY_SERVER_URL\");\n const envMagicLen = readEnv(\"VITE_PACKET_MAGIC_LEN\");\n\n this.baseUrl = (\n options.baseUrl ??\n envBaseUrl ??\n \"http://localhost:47200\"\n ).replace(/\\/$/, \"\");\n\n this.token = options.token ?? \"\";\n this.packetMagicLen =\n options.packetMagicLen ?? (envMagicLen ? Number(envMagicLen) : 4);\n }\n\n /** baseUrl, token, packetMagicLen \uAC12\uC744 \uB7F0\uD0C0\uC784\uC5D0 \uAC31\uC2E0\uD569\uB2C8\uB2E4. */\n configure(options: Partial<EntityServerClientOptions>): void {\n if (options.baseUrl) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, \"\");\n }\n if (typeof options.token === \"string\") {\n this.token = options.token;\n }\n if (typeof options.packetMagicLen === \"number\") {\n this.packetMagicLen = options.packetMagicLen;\n }\n }\n\n /** \uC778\uC99D \uC694\uCCAD\uC5D0 \uC0AC\uC6A9\uD560 JWT Access Token\uC744 \uC124\uC815\uD569\uB2C8\uB2E4. */\n setToken(token: string): void {\n this.token = token;\n }\n\n /** \uC554\uD638\uD654 \uD328\uD0B7 magic \uAE38\uC774(`packet_magic_len`)\uB97C \uC124\uC815\uD569\uB2C8\uB2E4. */\n setPacketMagicLen(length: number): void {\n this.packetMagicLen = length;\n }\n\n /** \uD604\uC7AC \uC554\uD638\uD654 \uD328\uD0B7 magic \uAE38\uC774\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4. */\n getPacketMagicLen(): number {\n return this.packetMagicLen;\n }\n\n /** \uB85C\uADF8\uC778 \uD6C4 `access_token`\uC744 \uB0B4\uBD80 \uC0C1\uD0DC\uC5D0 \uC800\uC7A5\uD569\uB2C8\uB2E4. */\n async login(\n email: string,\n password: string,\n ): Promise<{\n access_token: string;\n refresh_token: string;\n expires_in: number;\n }> {\n const data = await this.request<{\n data: {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n }>(\"POST\", \"/v1/auth/login\", { email, passwd: password }, false);\n this.token = data.data.access_token;\n return data.data;\n }\n\n /** Refresh Token\uC73C\uB85C Access Token\uC744 \uC7AC\uBC1C\uAE09\uBC1B\uC544 \uB0B4\uBD80 \uD1A0\uD070\uC744 \uAD50\uCCB4\uD569\uB2C8\uB2E4. */\n async refreshToken(\n refreshToken: string,\n ): Promise<{ access_token: string; expires_in: number }> {\n const data = await this.request<{\n data: { access_token: string; expires_in: number };\n }>(\"POST\", \"/v1/auth/refresh\", { refresh_token: refreshToken }, false);\n this.token = data.data.access_token;\n return data.data;\n }\n\n /** \uD2B8\uB79C\uC7AD\uC158\uC744 \uC2DC\uC791\uD558\uACE0 \uD65C\uC131 \uD2B8\uB79C\uC7AD\uC158 ID\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4. */\n async transStart(): Promise<string> {\n const res = await this.request<{ ok: boolean; transaction_id: string }>(\n \"POST\",\n \"/v1/transaction/start\",\n undefined,\n false,\n );\n this.activeTxId = res.transaction_id;\n return this.activeTxId;\n }\n\n /** \uD65C\uC131 \uD2B8\uB79C\uC7AD\uC158(\uB610\uB294 \uC804\uB2EC\uB41C transactionId)\uC744 \uB864\uBC31\uD569\uB2C8\uB2E4. */\n transRollback(transactionId?: string): Promise<{ ok: boolean }> {\n const txId = transactionId ?? this.activeTxId;\n if (!txId) {\n return Promise.reject(\n new Error(\"No active transaction. Call transStart() first.\"),\n );\n }\n this.activeTxId = null;\n return this.request(\"POST\", `/v1/transaction/rollback/${txId}`);\n }\n\n /** \uD65C\uC131 \uD2B8\uB79C\uC7AD\uC158(\uB610\uB294 \uC804\uB2EC\uB41C transactionId)\uC744 \uCEE4\uBC0B\uD569\uB2C8\uB2E4.\n *\n * @returns `results` \uBC30\uC5F4: commit\uB41C \uAC01 \uC791\uC5C5\uC758 `entity`, `action`, `seq`\n */\n transCommit(transactionId?: string): Promise<{\n ok: boolean;\n results: Array<{ entity: string; action: string; seq: number }>;\n }> {\n const txId = transactionId ?? this.activeTxId;\n if (!txId) {\n return Promise.reject(\n new Error(\"No active transaction. Call transStart() first.\"),\n );\n }\n this.activeTxId = null;\n return this.request(\"POST\", `/v1/transaction/commit/${txId}`);\n }\n\n /** \uC2DC\uD000\uC2A4 ID\uB85C \uC5D4\uD2F0\uD2F0 \uB2E8\uAC74\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. */\n get<T = unknown>(\n entity: string,\n seq: number,\n opts: { skipHooks?: boolean } = {},\n ): Promise<{ ok: boolean; data: T }> {\n const q = opts.skipHooks ? \"?skipHooks=true\" : \"\";\n return this.request(\"GET\", `/v1/entity/${entity}/${seq}${q}`);\n }\n\n /** \uD398\uC774\uC9C0\uB124\uC774\uC158/\uC815\uB82C/\uD544\uD130 \uC870\uAC74\uC73C\uB85C \uC5D4\uD2F0\uD2F0 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. */\n list<T = unknown>(\n entity: string,\n params: EntityListParams = {},\n ): Promise<{ ok: boolean; data: EntityListResult<T> }> {\n const { conditions, fields, orderDir, orderBy, ...rest } = params;\n\n const queryObj: Record<string, unknown> = {\n page: 1,\n limit: 20,\n ...rest,\n };\n if (orderBy) {\n queryObj.orderBy = orderDir === \"DESC\" ? `-${orderBy}` : orderBy;\n }\n if (fields?.length) {\n queryObj.fields = fields.join(\",\");\n }\n\n const q = buildQuery(queryObj);\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/list?${q}`,\n conditions ?? {},\n );\n }\n\n /**\n * \uC5D4\uD2F0\uD2F0 \uCD1D \uAC74\uC218\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.\n *\n * @param conditions \uD544\uD130 \uC870\uAC74 (\uC608: `{ status: \"active\" }`)\n */\n count(\n entity: string,\n conditions?: Record<string, unknown>,\n ): Promise<{ ok: boolean; count: number }> {\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/count`,\n conditions ?? {},\n );\n }\n\n /**\n * \uCEE4\uC2A4\uD140 SQL\uB85C \uC5D4\uD2F0\uD2F0\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.\n *\n * SELECT \uC804\uC6A9\uC774\uBA70 \uC778\uB371\uC2A4 \uD14C\uC774\uBE14\uB9CC \uC870\uD68C \uAC00\uB2A5\uD569\uB2C8\uB2E4.\n * JOIN\uC744 \uC0AC\uC6A9\uD574 \uC5EC\uB7EC \uC5D4\uD2F0\uD2F0\uB97C \uC870\uD569\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n * `entity`\uB294 SQL\uC5D0 \uD3EC\uD568\uB41C \uAE30\uBCF8 \uC5D4\uD2F0\uD2F0\uBA85(\uB77C\uC6B0\uD2B8 \uACBD\uB85C\uC6A9)\uC785\uB2C8\uB2E4.\n */\n query<T = unknown>(\n entity: string,\n req: EntityQueryRequest,\n ): Promise<{ ok: boolean; data: { items: T[]; count: number } }> {\n return this.request(\"POST\", `/v1/entity/${entity}/query`, req);\n }\n\n /** \uC5D4\uD2F0\uD2F0 \uB370\uC774\uD130\uB97C \uC0DD\uC131/\uC218\uC815(Submit)\uD569\uB2C8\uB2E4. `seq`\uAC00 \uC5C6\uC73C\uBA74 INSERT, \uC788\uC73C\uBA74 UPDATE\uC785\uB2C8\uB2E4. */\n submit(\n entity: string,\n data: Record<string, unknown>,\n opts: { transactionId?: string; skipHooks?: boolean } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n const txId = opts.transactionId ?? this.activeTxId;\n const extraHeaders = txId ? { \"X-Transaction-ID\": txId } : undefined;\n const q = opts.skipHooks ? \"?skipHooks=true\" : \"\";\n\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/submit${q}`,\n data,\n true,\n extraHeaders,\n );\n }\n\n /** \uC2DC\uD000\uC2A4 ID\uB85C \uC5D4\uD2F0\uD2F0\uB97C \uC0AD\uC81C\uD569\uB2C8\uB2E4(`hard=true`\uBA74 \uD558\uB4DC \uC0AD\uC81C, \uAE30\uBCF8\uC740 \uC18C\uD504\uD2B8 \uC0AD\uC81C). */\n delete(\n entity: string,\n seq: number,\n opts: {\n transactionId?: string;\n hard?: boolean;\n skipHooks?: boolean;\n } = {},\n ): Promise<{ ok: boolean; deleted: number }> {\n const params = new URLSearchParams();\n if (opts.hard) params.set(\"hard\", \"true\");\n if (opts.skipHooks) params.set(\"skipHooks\", \"true\");\n const q = params.size ? `?${params}` : \"\";\n const txId = opts.transactionId ?? this.activeTxId;\n const extraHeaders = txId ? { \"X-Transaction-ID\": txId } : undefined;\n\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/delete/${seq}${q}`,\n undefined,\n true,\n extraHeaders,\n );\n }\n\n /** \uC5D4\uD2F0\uD2F0 \uB2E8\uAC74\uC758 \uBCC0\uACBD \uC774\uB825\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. \uC774\uB825 \uD56D\uBAA9\uB2F9 `action`, `data_snapshot`, `changed_by`, `changed_time`\uC744 \uD3EC\uD568\uD569\uB2C8\uB2E4. */\n history<T = unknown>(\n entity: string,\n seq: number,\n params: Pick<EntityListParams, \"page\" | \"limit\"> = {},\n ): Promise<{\n ok: boolean;\n data: EntityListResult<EntityHistoryRecord<T>>;\n }> {\n const q = buildQuery({ page: 1, limit: 50, ...params });\n return this.request(\"GET\", `/v1/entity/${entity}/history/${seq}?${q}`);\n }\n\n /** \uD2B9\uC815 \uC774\uB825 \uC2DC\uC810\uC73C\uB85C \uC5D4\uD2F0\uD2F0\uB97C \uB864\uBC31\uD569\uB2C8\uB2E4. */\n rollback(entity: string, historySeq: number): Promise<{ ok: boolean }> {\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/rollback/${historySeq}`,\n );\n }\n\n /** \uD478\uC2DC \uAD00\uB828 \uC5D4\uD2F0\uD2F0\uB85C payload\uB97C \uC804\uC1A1(Submit)\uD569\uB2C8\uB2E4. */\n push(\n pushEntity: string,\n payload: Record<string, unknown>,\n opts: { transactionId?: string } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n return this.submit(pushEntity, payload, opts);\n }\n\n /** \uD478\uC2DC \uB85C\uADF8 \uC5D4\uD2F0\uD2F0 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. */\n pushLogList<T = unknown>(\n params: EntityListParams = {},\n ): Promise<{ ok: boolean; data: EntityListResult<T> }> {\n return this.list<T>(\"push_log\", params);\n }\n\n /** \uACC4\uC815\uC758 \uD478\uC2DC \uB514\uBC14\uC774\uC2A4\uB97C \uB4F1\uB85D\uD569\uB2C8\uB2E4. */\n registerPushDevice(\n accountSeq: number,\n deviceId: string,\n pushToken: string,\n opts: RegisterPushDeviceOptions = {},\n ): Promise<{ ok: boolean; seq: number }> {\n const {\n platform,\n deviceType,\n browser,\n browserVersion,\n pushEnabled = true,\n transactionId,\n } = opts;\n\n return this.submit(\n \"account_device\",\n {\n id: deviceId,\n account_seq: accountSeq,\n push_token: pushToken,\n push_enabled: pushEnabled,\n ...(platform ? { platform } : {}),\n ...(deviceType ? { device_type: deviceType } : {}),\n ...(browser ? { browser } : {}),\n ...(browserVersion ? { browser_version: browserVersion } : {}),\n },\n { transactionId },\n );\n }\n\n /** \uB514\uBC14\uC774\uC2A4 \uB808\uCF54\uB4DC\uC758 \uD478\uC2DC \uD1A0\uD070\uC744 \uAC31\uC2E0\uD569\uB2C8\uB2E4. */\n updatePushDeviceToken(\n deviceSeq: number,\n pushToken: string,\n opts: { pushEnabled?: boolean; transactionId?: string } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n const { pushEnabled = true, transactionId } = opts;\n return this.submit(\n \"account_device\",\n {\n seq: deviceSeq,\n push_token: pushToken,\n push_enabled: pushEnabled,\n },\n { transactionId },\n );\n }\n\n /** \uB514\uBC14\uC774\uC2A4\uC758 \uD478\uC2DC \uC218\uC2E0\uC744 \uBE44\uD65C\uC131\uD654\uD569\uB2C8\uB2E4. */\n disablePushDevice(\n deviceSeq: number,\n opts: { transactionId?: string } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n return this.submit(\n \"account_device\",\n {\n seq: deviceSeq,\n push_enabled: false,\n },\n { transactionId: opts.transactionId },\n );\n }\n\n /**\n * \uC694\uCCAD \uBC14\uB514\uB97C \uD30C\uC2F1\uD558\uACE0 `application/octet-stream`\uC778 \uACBD\uC6B0 \uBCF5\uD638\uD654\uD569\uB2C8\uB2E4.\n *\n * \uC6D0\uC2DC \uC554\uD638\uD654 payload\uB97C \uC9C1\uC811 \uB2E4\uB8E8\uB294 \uD074\uB77C\uC774\uC5B8\uD2B8\uC5D0\uC11C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.\n */\n readRequestBody<T = Record<string, unknown>>(\n body: ArrayBuffer | Uint8Array | string | T | null | undefined,\n contentType = \"application/json\",\n requireEncrypted = false,\n ): T {\n const lowered = contentType.toLowerCase();\n const isEncrypted = lowered.includes(\"application/octet-stream\");\n\n if (requireEncrypted && !isEncrypted) {\n throw new Error(\n \"Encrypted request required: Content-Type must be application/octet-stream\",\n );\n }\n\n if (isEncrypted) {\n if (body == null) {\n throw new Error(\"Encrypted request body is empty\");\n }\n if (body instanceof ArrayBuffer) {\n return this.decryptPacket<T>(body);\n }\n if (body instanceof Uint8Array) {\n const sliced = body.buffer.slice(\n body.byteOffset,\n body.byteOffset + body.byteLength,\n );\n return this.decryptPacket<T>(sliced as ArrayBuffer);\n }\n throw new Error(\n \"Encrypted request body must be ArrayBuffer or Uint8Array\",\n );\n }\n\n if (body == null || body === \"\") return {} as T;\n if (typeof body === \"string\") return JSON.parse(body) as T;\n return body as T;\n }\n\n /**\n * \uACF5\uD1B5 HTTP \uC694\uCCAD \uD568\uC218\uC785\uB2C8\uB2E4.\n *\n * \uC751\uB2F5\uC774 `application/octet-stream`\uC774\uBA74 \uC790\uB3D9 \uBCF5\uD638\uD654\uD558\uACE0,\n * JSON \uC751\uB2F5\uC758 `ok`\uAC00 false\uC774\uBA74 \uC5D0\uB7EC\uB97C \uB358\uC9D1\uB2C8\uB2E4.\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n withAuth = true,\n extraHeaders: Record<string, string> = {},\n ): Promise<T> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...extraHeaders,\n };\n if (withAuth && this.token) {\n headers.Authorization = `Bearer ${this.token}`;\n }\n\n const res = await fetch(this.baseUrl + path, {\n method,\n headers,\n ...(body != null ? { body: JSON.stringify(body) } : {}),\n });\n\n const contentType = res.headers.get(\"Content-Type\") ?? \"\";\n\n if (contentType.includes(\"application/octet-stream\")) {\n const buffer = await res.arrayBuffer();\n return this.decryptPacket<T>(buffer);\n }\n\n const data = await res.json();\n if (!data.ok) {\n const err = new Error(\n data.message ?? `EntityServer error (HTTP ${res.status})`,\n );\n (err as { status?: number }).status = res.status;\n throw err;\n }\n return data as T;\n }\n\n /** \uC11C\uBC84\uC758 \uC554\uD638\uD654 \uD328\uD0B7\uC744 \uBCF5\uD638\uD654\uD574 JSON \uAC1D\uCCB4\uB85C \uBCC0\uD658\uD569\uB2C8\uB2E4. */\n private decryptPacket<T>(buffer: ArrayBuffer): T {\n const key = sha256(new TextEncoder().encode(this.token));\n const data = new Uint8Array(buffer);\n\n if (data.length < this.packetMagicLen + 24 + 16) {\n throw new Error(\"Encrypted packet too short\");\n }\n\n const nonce = data.slice(this.packetMagicLen, this.packetMagicLen + 24);\n const ciphertext = data.slice(this.packetMagicLen + 24);\n const cipher = xchacha20poly1305(key, nonce);\n const plaintext = cipher.decrypt(ciphertext);\n return JSON.parse(new TextDecoder().decode(plaintext)) as T;\n }\n}\n\n/** \uCFFC\uB9AC \uD30C\uB77C\uBBF8\uD130 \uAC1D\uCCB4\uB97C URL \uCFFC\uB9AC \uBB38\uC790\uC5F4\uB85C \uBCC0\uD658\uD569\uB2C8\uB2E4. */\nfunction buildQuery(params: Record<string, unknown>): string {\n return Object.entries(params)\n .filter(([, value]) => value != null)\n .map(\n ([key, value]) =>\n `${encodeURIComponent(key === \"orderBy\" ? \"order_by\" : key)}=${encodeURIComponent(String(value))}`,\n )\n .join(\"&\");\n}\n\nexport const entityServer = new EntityServerClient();\n"],
5
- "mappings": "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",
6
- "names": ["xchacha20poly1305", "sha256", "readEnv", "name", "EntityServerClient", "options", "envBaseUrl", "envMagicLen", "token", "length", "email", "password", "data", "refreshToken", "res", "transactionId", "txId", "entity", "seq", "opts", "q", "params", "conditions", "fields", "orderDir", "orderBy", "rest", "queryObj", "buildQuery", "req", "extraHeaders", "historySeq", "pushEntity", "payload", "accountSeq", "deviceId", "pushToken", "platform", "deviceType", "browser", "browserVersion", "pushEnabled", "deviceSeq", "body", "contentType", "requireEncrypted", "isEncrypted", "sliced", "method", "path", "withAuth", "headers", "buffer", "err", "key", "nonce", "ciphertext", "plaintext", "value", "entityServer"]
4
+ "sourcesContent": ["// @ts-ignore\nimport { xchacha20poly1305 } from \"@noble/ciphers/chacha\";\n// @ts-ignore\nimport { sha256 } from \"@noble/hashes/sha2\";\n\n/**\n * \uC5D4\uD2F0\uD2F0 \uBAA9\uB85D \uC870\uD68C \uD30C\uB77C\uBBF8\uD130\uC785\uB2C8\uB2E4.\n *\n * ```ts\n * client.list(\"post\", {\n * page: 1, limit: 10,\n * orderBy: \"created_time\", orderDir: \"DESC\",\n * fields: [\"seq\", \"title\", \"created_time\"],\n * conditions: { status: \"active\" },\n * });\n * ```\n */\nexport interface EntityListParams {\n /** \uC870\uD68C \uD398\uC774\uC9C0 \uBC88\uD638. \uAE30\uBCF8\uAC12: `1` */\n page?: number;\n /** \uD398\uC774\uC9C0\uB2F9 \uB808\uCF54\uB4DC \uC218. \uAE30\uBCF8\uAC12: `20` */\n limit?: number;\n /** \uC815\uB82C \uAE30\uC900 \uD544\uB4DC\uBA85 */\n orderBy?: string;\n /** \uC815\uB82C \uBC29\uD5A5. \uAE30\uBCF8\uAC12: `\"ASC\"` */\n orderDir?: \"ASC\" | \"DESC\";\n /**\n * \uBC18\uD658\uD560 \uD544\uB4DC \uBAA9\uB85D.\n *\n * - **\uBBF8\uC9C0\uC815 (\uAE30\uBCF8\uAC12)**: \uC5D4\uD2F0\uD2F0\uC758 \uC778\uB371\uC2A4 \uD544\uB4DC\uB9CC \uBC18\uD658\uD569\uB2C8\uB2E4.\n * \uBCF5\uD638\uD654\uB97C \uAC74\uB108\uB6F0\uAE30 \uB54C\uBB38\uC5D0 **\uAC00\uC7A5 \uBE60\uB985\uB2C8\uB2E4**.\n * - `[\"*\"]`: \uC804\uCCB4 \uD544\uB4DC \uBC18\uD658 (\uBCF5\uD638\uD654 \uC218\uD589).\n * - \uD544\uB4DC\uBA85 \uBAA9\uB85D: \uD574\uB2F9 \uD544\uB4DC\uB9CC \uBC18\uD658\uD569\uB2C8\uB2E4.\n * \uC5D4\uD2F0\uD2F0 \uC124\uC815\uC5D0 `index`\uB85C \uC120\uC5B8\uB41C \uD544\uB4DC\uB9CC \uC9C0\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.\n * \uC874\uC7AC\uD558\uC9C0 \uC54A\uB294 \uD544\uB4DC\uBA85\uC744 \uC9C0\uC815\uD558\uBA74 \uC11C\uBC84 \uC5D0\uB7EC\uAC00 \uBC1C\uC0DD\uD569\uB2C8\uB2E4.\n * - `seq`, `created_time`, `updated_time`, `license_seq`\uB294 \uD544\uB4DC\uC5D0 \uAD00\uACC4\uC5C6\uC774 \uD56D\uC0C1 \uD3EC\uD568\uB429\uB2C8\uB2E4.\n *\n * ```ts\n * // \uAE30\uBCF8\uAC12 (\uC778\uB371\uC2A4 \uD544\uB4DC\uB9CC, \uAC00\uC7A5 \uBE60\uB984)\n * client.list(\"account\")\n * // \uC804\uCCB4 \uD544\uB4DC\n * client.list(\"account\", { fields: [\"*\"] })\n * // seq, name, email\uB9CC\n * client.list(\"account\", { fields: [\"seq\", \"name\", \"email\"] })\n * ```\n */\n fields?: string[];\n /** \uD544\uD130 \uC870\uAC74. POST body\uB85C \uC804\uB2EC\uB429\uB2C8\uB2E4. (\uC608: `{ status: \"active\" }`) */\n conditions?: Record<string, unknown>;\n}\n\n/**\n * `query()` \uBA54\uC11C\uB4DC\uC5D0 \uC804\uB2EC\uD558\uB294 SQL \uCFFC\uB9AC \uC694\uCCAD\uC785\uB2C8\uB2E4.\n *\n * - `sql`: SELECT \uC804\uC6A9 SQL. \uC778\uB371\uC2A4 \uD14C\uC774\uBE14\uB9CC \uC870\uD68C \uAC00\uB2A5\uD558\uBA70 JOIN \uC9C0\uC6D0.\n * - `params`: SQL \uBC14\uC778\uB529 \uD30C\uB77C\uBBF8\uD130 (`?` \uD50C\uB808\uC774\uC2A4\uD640\uB354 \uB300\uC751).\n * - `limit`: \uCD5C\uB300 \uBC18\uD658 \uAC74\uC218 (\uCD5C\uB300 1000. \uBBF8\uC9C0\uC815 \uC2DC \uC11C\uBC84 \uAE30\uBCF8\uAC12 \uC801\uC6A9).\n *\n * ```ts\n * client.query(\"order\", {\n * sql: `SELECT o.seq, o.status, u.name\n * FROM order o\n * JOIN account u ON u.data_seq = o.account_seq\n * WHERE o.status = ?`,\n * params: [\"pending\"],\n * limit: 100,\n * });\n * ```\n */\nexport interface EntityQueryRequest {\n sql: string;\n params?: unknown[];\n limit?: number;\n}\n\nexport interface RegisterPushDeviceOptions {\n platform?: string;\n deviceType?: string;\n browser?: string;\n browserVersion?: string;\n pushEnabled?: boolean;\n transactionId?: string;\n}\n\n/** EntityServerClient \uC0DD\uC131/\uC124\uC815 \uC635\uC158\uC785\uB2C8\uB2E4. */\nexport interface EntityServerClientOptions {\n baseUrl?: string;\n token?: string;\n packetMagicLen?: number;\n /**\n * `true`\uC774\uBA74 \uC778\uC99D\uB41C POST/PUT \uC694\uCCAD \uBC14\uB514\uB97C XChaCha20-Poly1305\uB85C \uC554\uD638\uD654\uD569\uB2C8\uB2E4.\n *\n * \uC11C\uBC84\uC758 `EnablePacketEncryption`\uC774 \uD65C\uC131\uD654\uB41C \uACBD\uC6B0 \uD544\uC218\uB85C \uC124\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.\n * \uB85C\uADF8\uC778(`login()`)\u00B7\uD1A0\uD070 \uAC31\uC2E0(`refreshToken()`)\uC740 \uC778\uC99D \uC804 \uC694\uCCAD\uC774\uBBC0\uB85C \uC790\uB3D9\uC73C\uB85C \uAC74\uB108\uB701\uB2C8\uB2E4.\n *\n * \uAE30\uBCF8\uAC12: `false`\n */\n encryptRequests?: boolean;\n}\n\n/**\n * `list()`, `history()` \uC751\uB2F5\uC758 `data` \uD544\uB4DC \uAD6C\uC870\uC785\uB2C8\uB2E4.\n *\n * \uC11C\uBC84\uB294 \uD56D\uC0C1 \uC774 \uAD6C\uC870\uB85C \uBC18\uD658\uD569\uB2C8\uB2E4:\n * ```json\n * { \"ok\": true, \"data\": { \"items\": [...], \"total\": 100, \"page\": 1, \"limit\": 20 } }\n * ```\n */\nexport interface EntityListResult<T = unknown> {\n items: T[];\n /** \uC804\uCCB4 \uB808\uCF54\uB4DC \uC218 */\n total: number;\n /** \uD604\uC7AC \uD398\uC774\uC9C0 \uBC88\uD638 */\n page: number;\n /** \uD398\uC774\uC9C0\uB2F9 \uB808\uCF54\uB4DC \uC218 */\n limit: number;\n}\n\n/**\n * `history()` \uC751\uB2F5\uC758 \uAC1C\uBCC4 \uC774\uB825 \uB808\uCF54\uB4DC \uAD6C\uC870\uC785\uB2C8\uB2E4.\n *\n * - `action`: `\"INSERT\"` | `\"UPDATE\"` | `\"DELETE_SOFT\"` | `\"DELETE_HARD\"` | `\"ROLLBACK\"`\n * - `data_snapshot`: \uBCC0\uACBD \uB2F9\uC2DC \uC5D4\uD2F0\uD2F0 \uB370\uC774\uD130 \uC2A4\uB0C5\uC0F7\n */\nexport interface EntityHistoryRecord<T = unknown> {\n seq: number;\n action:\n | \"INSERT\"\n | \"UPDATE\"\n | \"DELETE_SOFT\"\n | \"DELETE_HARD\"\n | \"ROLLBACK\"\n | string;\n data_snapshot: T | null;\n changed_by: number | null;\n changed_time: string;\n}\n\n/** Vite \uD658\uACBD\uBCC0\uC218(`import.meta.env`)\uC5D0\uC11C \uAC12\uC744 \uC77D\uC2B5\uB2C8\uB2E4. */\nfunction readEnv(name: string): string | undefined {\n const meta = import.meta as unknown as {\n env?: Record<string, string | undefined>;\n };\n return meta?.env?.[name];\n}\n\nexport class EntityServerClient {\n private baseUrl: string;\n private token: string;\n private packetMagicLen: number;\n private encryptRequests: boolean;\n private activeTxId: string | null = null;\n\n /**\n * EntityServerClient \uC778\uC2A4\uD134\uC2A4\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4.\n *\n * \uAE30\uBCF8\uAC12:\n * - `baseUrl`: `VITE_ENTITY_SERVER_URL` \uB610\uB294 `http://localhost:47200`\n * - `packetMagicLen`: `VITE_PACKET_MAGIC_LEN` \uB610\uB294 `4`\n */\n constructor(options: EntityServerClientOptions = {}) {\n const envBaseUrl = readEnv(\"VITE_ENTITY_SERVER_URL\");\n const envMagicLen = readEnv(\"VITE_PACKET_MAGIC_LEN\");\n\n this.baseUrl = (\n options.baseUrl ??\n envBaseUrl ??\n \"http://localhost:47200\"\n ).replace(/\\/$/, \"\");\n\n this.token = options.token ?? \"\";\n this.packetMagicLen =\n options.packetMagicLen ?? (envMagicLen ? Number(envMagicLen) : 4);\n this.encryptRequests = options.encryptRequests ?? false;\n }\n\n /** baseUrl, token, packetMagicLen, encryptRequests \uAC12\uC744 \uB7F0\uD0C0\uC784\uC5D0 \uAC31\uC2E0\uD569\uB2C8\uB2E4. */\n configure(options: Partial<EntityServerClientOptions>): void {\n if (options.baseUrl) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, \"\");\n }\n if (typeof options.token === \"string\") {\n this.token = options.token;\n }\n if (typeof options.packetMagicLen === \"number\") {\n this.packetMagicLen = options.packetMagicLen;\n }\n if (typeof options.encryptRequests === \"boolean\") {\n this.encryptRequests = options.encryptRequests;\n }\n }\n\n /** \uC778\uC99D \uC694\uCCAD\uC5D0 \uC0AC\uC6A9\uD560 JWT Access Token\uC744 \uC124\uC815\uD569\uB2C8\uB2E4. */\n setToken(token: string): void {\n this.token = token;\n }\n\n /** \uC554\uD638\uD654 \uD328\uD0B7 magic \uAE38\uC774(`packet_magic_len`)\uB97C \uC124\uC815\uD569\uB2C8\uB2E4. */\n setPacketMagicLen(length: number): void {\n this.packetMagicLen = length;\n }\n\n /** \uD604\uC7AC \uC554\uD638\uD654 \uD328\uD0B7 magic \uAE38\uC774\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4. */\n getPacketMagicLen(): number {\n return this.packetMagicLen;\n }\n\n /** \uB85C\uADF8\uC778 \uD6C4 `access_token`\uC744 \uB0B4\uBD80 \uC0C1\uD0DC\uC5D0 \uC800\uC7A5\uD569\uB2C8\uB2E4. */\n async login(\n email: string,\n password: string,\n ): Promise<{\n access_token: string;\n refresh_token: string;\n expires_in: number;\n }> {\n const data = await this.request<{\n data: {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n }>(\"POST\", \"/v1/auth/login\", { email, passwd: password }, false);\n this.token = data.data.access_token;\n return data.data;\n }\n\n /** Refresh Token\uC73C\uB85C Access Token\uC744 \uC7AC\uBC1C\uAE09\uBC1B\uC544 \uB0B4\uBD80 \uD1A0\uD070\uC744 \uAD50\uCCB4\uD569\uB2C8\uB2E4. */\n async refreshToken(\n refreshToken: string,\n ): Promise<{ access_token: string; expires_in: number }> {\n const data = await this.request<{\n data: { access_token: string; expires_in: number };\n }>(\"POST\", \"/v1/auth/refresh\", { refresh_token: refreshToken }, false);\n this.token = data.data.access_token;\n return data.data;\n }\n\n /** \uD2B8\uB79C\uC7AD\uC158\uC744 \uC2DC\uC791\uD558\uACE0 \uD65C\uC131 \uD2B8\uB79C\uC7AD\uC158 ID\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4. */\n async transStart(): Promise<string> {\n const res = await this.request<{ ok: boolean; transaction_id: string }>(\n \"POST\",\n \"/v1/transaction/start\",\n undefined,\n false,\n );\n this.activeTxId = res.transaction_id;\n return this.activeTxId;\n }\n\n /** \uD65C\uC131 \uD2B8\uB79C\uC7AD\uC158(\uB610\uB294 \uC804\uB2EC\uB41C transactionId)\uC744 \uB864\uBC31\uD569\uB2C8\uB2E4. */\n transRollback(transactionId?: string): Promise<{ ok: boolean }> {\n const txId = transactionId ?? this.activeTxId;\n if (!txId) {\n return Promise.reject(\n new Error(\"No active transaction. Call transStart() first.\"),\n );\n }\n this.activeTxId = null;\n return this.request(\"POST\", `/v1/transaction/rollback/${txId}`);\n }\n\n /** \uD65C\uC131 \uD2B8\uB79C\uC7AD\uC158(\uB610\uB294 \uC804\uB2EC\uB41C transactionId)\uC744 \uCEE4\uBC0B\uD569\uB2C8\uB2E4.\n *\n * @returns `results` \uBC30\uC5F4: commit\uB41C \uAC01 \uC791\uC5C5\uC758 `entity`, `action`, `seq`\n */\n transCommit(transactionId?: string): Promise<{\n ok: boolean;\n results: Array<{ entity: string; action: string; seq: number }>;\n }> {\n const txId = transactionId ?? this.activeTxId;\n if (!txId) {\n return Promise.reject(\n new Error(\"No active transaction. Call transStart() first.\"),\n );\n }\n this.activeTxId = null;\n return this.request(\"POST\", `/v1/transaction/commit/${txId}`);\n }\n\n /** \uC2DC\uD000\uC2A4 ID\uB85C \uC5D4\uD2F0\uD2F0 \uB2E8\uAC74\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. */\n get<T = unknown>(\n entity: string,\n seq: number,\n opts: { skipHooks?: boolean } = {},\n ): Promise<{ ok: boolean; data: T }> {\n const q = opts.skipHooks ? \"?skipHooks=true\" : \"\";\n return this.request(\"GET\", `/v1/entity/${entity}/${seq}${q}`);\n }\n\n /** \uD398\uC774\uC9C0\uB124\uC774\uC158/\uC815\uB82C/\uD544\uD130 \uC870\uAC74\uC73C\uB85C \uC5D4\uD2F0\uD2F0 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. */\n list<T = unknown>(\n entity: string,\n params: EntityListParams = {},\n ): Promise<{ ok: boolean; data: EntityListResult<T> }> {\n const { conditions, fields, orderDir, orderBy, ...rest } = params;\n\n const queryObj: Record<string, unknown> = {\n page: 1,\n limit: 20,\n ...rest,\n };\n if (orderBy) {\n queryObj.orderBy = orderDir === \"DESC\" ? `-${orderBy}` : orderBy;\n }\n if (fields?.length) {\n queryObj.fields = fields.join(\",\");\n }\n\n const q = buildQuery(queryObj);\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/list?${q}`,\n conditions ?? {},\n );\n }\n\n /**\n * \uC5D4\uD2F0\uD2F0 \uCD1D \uAC74\uC218\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.\n *\n * @param conditions \uD544\uD130 \uC870\uAC74 (\uC608: `{ status: \"active\" }`)\n */\n count(\n entity: string,\n conditions?: Record<string, unknown>,\n ): Promise<{ ok: boolean; count: number }> {\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/count`,\n conditions ?? {},\n );\n }\n\n /**\n * \uCEE4\uC2A4\uD140 SQL\uB85C \uC5D4\uD2F0\uD2F0\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.\n *\n * SELECT \uC804\uC6A9\uC774\uBA70 \uC778\uB371\uC2A4 \uD14C\uC774\uBE14\uB9CC \uC870\uD68C \uAC00\uB2A5\uD569\uB2C8\uB2E4.\n * JOIN\uC744 \uC0AC\uC6A9\uD574 \uC5EC\uB7EC \uC5D4\uD2F0\uD2F0\uB97C \uC870\uD569\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n * `entity`\uB294 SQL\uC5D0 \uD3EC\uD568\uB41C \uAE30\uBCF8 \uC5D4\uD2F0\uD2F0\uBA85(\uB77C\uC6B0\uD2B8 \uACBD\uB85C\uC6A9)\uC785\uB2C8\uB2E4.\n */\n query<T = unknown>(\n entity: string,\n req: EntityQueryRequest,\n ): Promise<{ ok: boolean; data: { items: T[]; count: number } }> {\n return this.request(\"POST\", `/v1/entity/${entity}/query`, req);\n }\n\n /** \uC5D4\uD2F0\uD2F0 \uB370\uC774\uD130\uB97C \uC0DD\uC131/\uC218\uC815(Submit)\uD569\uB2C8\uB2E4. `seq`\uAC00 \uC5C6\uC73C\uBA74 INSERT, \uC788\uC73C\uBA74 UPDATE\uC785\uB2C8\uB2E4. */\n submit(\n entity: string,\n data: Record<string, unknown>,\n opts: { transactionId?: string; skipHooks?: boolean } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n const txId = opts.transactionId ?? this.activeTxId;\n const extraHeaders = txId ? { \"X-Transaction-ID\": txId } : undefined;\n const q = opts.skipHooks ? \"?skipHooks=true\" : \"\";\n\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/submit${q}`,\n data,\n true,\n extraHeaders,\n );\n }\n\n /** \uC2DC\uD000\uC2A4 ID\uB85C \uC5D4\uD2F0\uD2F0\uB97C \uC0AD\uC81C\uD569\uB2C8\uB2E4(`hard=true`\uBA74 \uD558\uB4DC \uC0AD\uC81C, \uAE30\uBCF8\uC740 \uC18C\uD504\uD2B8 \uC0AD\uC81C). */\n delete(\n entity: string,\n seq: number,\n opts: {\n transactionId?: string;\n hard?: boolean;\n skipHooks?: boolean;\n } = {},\n ): Promise<{ ok: boolean; deleted: number }> {\n const params = new URLSearchParams();\n if (opts.hard) params.set(\"hard\", \"true\");\n if (opts.skipHooks) params.set(\"skipHooks\", \"true\");\n const q = params.size ? `?${params}` : \"\";\n const txId = opts.transactionId ?? this.activeTxId;\n const extraHeaders = txId ? { \"X-Transaction-ID\": txId } : undefined;\n\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/delete/${seq}${q}`,\n undefined,\n true,\n extraHeaders,\n );\n }\n\n /** \uC5D4\uD2F0\uD2F0 \uB2E8\uAC74\uC758 \uBCC0\uACBD \uC774\uB825\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. \uC774\uB825 \uD56D\uBAA9\uB2F9 `action`, `data_snapshot`, `changed_by`, `changed_time`\uC744 \uD3EC\uD568\uD569\uB2C8\uB2E4. */\n history<T = unknown>(\n entity: string,\n seq: number,\n params: Pick<EntityListParams, \"page\" | \"limit\"> = {},\n ): Promise<{\n ok: boolean;\n data: EntityListResult<EntityHistoryRecord<T>>;\n }> {\n const q = buildQuery({ page: 1, limit: 50, ...params });\n return this.request(\"GET\", `/v1/entity/${entity}/history/${seq}?${q}`);\n }\n\n /** \uD2B9\uC815 \uC774\uB825 \uC2DC\uC810\uC73C\uB85C \uC5D4\uD2F0\uD2F0\uB97C \uB864\uBC31\uD569\uB2C8\uB2E4. */\n rollback(entity: string, historySeq: number): Promise<{ ok: boolean }> {\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/rollback/${historySeq}`,\n );\n }\n\n /** \uD478\uC2DC \uAD00\uB828 \uC5D4\uD2F0\uD2F0\uB85C payload\uB97C \uC804\uC1A1(Submit)\uD569\uB2C8\uB2E4. */\n push(\n pushEntity: string,\n payload: Record<string, unknown>,\n opts: { transactionId?: string } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n return this.submit(pushEntity, payload, opts);\n }\n\n /** \uD478\uC2DC \uB85C\uADF8 \uC5D4\uD2F0\uD2F0 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. */\n pushLogList<T = unknown>(\n params: EntityListParams = {},\n ): Promise<{ ok: boolean; data: EntityListResult<T> }> {\n return this.list<T>(\"push_log\", params);\n }\n\n /** \uACC4\uC815\uC758 \uD478\uC2DC \uB514\uBC14\uC774\uC2A4\uB97C \uB4F1\uB85D\uD569\uB2C8\uB2E4. */\n registerPushDevice(\n accountSeq: number,\n deviceId: string,\n pushToken: string,\n opts: RegisterPushDeviceOptions = {},\n ): Promise<{ ok: boolean; seq: number }> {\n const {\n platform,\n deviceType,\n browser,\n browserVersion,\n pushEnabled = true,\n transactionId,\n } = opts;\n\n return this.submit(\n \"account_device\",\n {\n id: deviceId,\n account_seq: accountSeq,\n push_token: pushToken,\n push_enabled: pushEnabled,\n ...(platform ? { platform } : {}),\n ...(deviceType ? { device_type: deviceType } : {}),\n ...(browser ? { browser } : {}),\n ...(browserVersion ? { browser_version: browserVersion } : {}),\n },\n { transactionId },\n );\n }\n\n /** \uB514\uBC14\uC774\uC2A4 \uB808\uCF54\uB4DC\uC758 \uD478\uC2DC \uD1A0\uD070\uC744 \uAC31\uC2E0\uD569\uB2C8\uB2E4. */\n updatePushDeviceToken(\n deviceSeq: number,\n pushToken: string,\n opts: { pushEnabled?: boolean; transactionId?: string } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n const { pushEnabled = true, transactionId } = opts;\n return this.submit(\n \"account_device\",\n {\n seq: deviceSeq,\n push_token: pushToken,\n push_enabled: pushEnabled,\n },\n { transactionId },\n );\n }\n\n /** \uB514\uBC14\uC774\uC2A4\uC758 \uD478\uC2DC \uC218\uC2E0\uC744 \uBE44\uD65C\uC131\uD654\uD569\uB2C8\uB2E4. */\n disablePushDevice(\n deviceSeq: number,\n opts: { transactionId?: string } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n return this.submit(\n \"account_device\",\n {\n seq: deviceSeq,\n push_enabled: false,\n },\n { transactionId: opts.transactionId },\n );\n }\n\n /**\n * \uC694\uCCAD \uBC14\uB514\uB97C \uD30C\uC2F1\uD558\uACE0 `application/octet-stream`\uC778 \uACBD\uC6B0 \uBCF5\uD638\uD654\uD569\uB2C8\uB2E4.\n *\n * \uC6D0\uC2DC \uC554\uD638\uD654 payload\uB97C \uC9C1\uC811 \uB2E4\uB8E8\uB294 \uD074\uB77C\uC774\uC5B8\uD2B8\uC5D0\uC11C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.\n */\n readRequestBody<T = Record<string, unknown>>(\n body: ArrayBuffer | Uint8Array | string | T | null | undefined,\n contentType = \"application/json\",\n requireEncrypted = false,\n ): T {\n const lowered = contentType.toLowerCase();\n const isEncrypted = lowered.includes(\"application/octet-stream\");\n\n if (requireEncrypted && !isEncrypted) {\n throw new Error(\n \"Encrypted request required: Content-Type must be application/octet-stream\",\n );\n }\n\n if (isEncrypted) {\n if (body == null) {\n throw new Error(\"Encrypted request body is empty\");\n }\n if (body instanceof ArrayBuffer) {\n return this.decryptPacket<T>(body);\n }\n if (body instanceof Uint8Array) {\n const sliced = body.buffer.slice(\n body.byteOffset,\n body.byteOffset + body.byteLength,\n );\n return this.decryptPacket<T>(sliced as ArrayBuffer);\n }\n throw new Error(\n \"Encrypted request body must be ArrayBuffer or Uint8Array\",\n );\n }\n\n if (body == null || body === \"\") return {} as T;\n if (typeof body === \"string\") return JSON.parse(body) as T;\n return body as T;\n }\n\n /**\n * \uACF5\uD1B5 HTTP \uC694\uCCAD \uD568\uC218\uC785\uB2C8\uB2E4.\n *\n * - `encryptRequests`\uAC00 \uD65C\uC131\uD654\uB41C \uC778\uC99D \uC694\uCCAD\uC758 POST \uBC14\uB514\uB97C \uC790\uB3D9 \uC554\uD638\uD654\uD569\uB2C8\uB2E4.\n * - \uC751\uB2F5\uC774 `application/octet-stream`\uC774\uBA74 \uC790\uB3D9 \uBCF5\uD638\uD654\uD569\uB2C8\uB2E4.\n * - JSON \uC751\uB2F5\uC758 `ok`\uAC00 false\uC774\uBA74 \uC5D0\uB7EC\uB97C \uB358\uC9D1\uB2C8\uB2E4.\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n withAuth = true,\n extraHeaders: Record<string, string> = {},\n ): Promise<T> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...extraHeaders,\n };\n if (withAuth && this.token) {\n headers.Authorization = `Bearer ${this.token}`;\n }\n\n // \uC694\uCCAD \uBC14\uB514 \uACB0\uC815: encryptRequests \uD65C\uC131\uD654 \uC2DC POST \uBC14\uB514\uB97C \uC554\uD638\uD654\uD569\uB2C8\uB2E4.\n // - \uB85C\uADF8\uC778/\uD1A0\uD070 \uAC31\uC2E0(withAuth=false)\uC740 \uC554\uD638\uD654\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.\n // - GET \uC740 \uBC14\uB514\uAC00 \uC5C6\uC73C\uBBC0\uB85C \uAC74\uB108\uB701\uB2C8\uB2E4.\n let fetchBody: string | Uint8Array | null = null;\n if (body != null) {\n const shouldEncrypt =\n this.encryptRequests &&\n withAuth &&\n this.token &&\n method !== \"GET\" &&\n method !== \"HEAD\";\n\n if (shouldEncrypt) {\n const plaintext = new TextEncoder().encode(JSON.stringify(body));\n const encrypted = this.encryptPacket(plaintext);\n headers[\"Content-Type\"] = \"application/octet-stream\";\n fetchBody = encrypted;\n } else {\n fetchBody = JSON.stringify(body);\n }\n }\n\n const res = await fetch(this.baseUrl + path, {\n method,\n headers,\n ...(fetchBody != null ? { body: fetchBody as BodyInit } : {}),\n });\n\n const contentType = res.headers.get(\"Content-Type\") ?? \"\";\n\n if (contentType.includes(\"application/octet-stream\")) {\n const buffer = await res.arrayBuffer();\n return this.decryptPacket<T>(buffer);\n }\n\n const data = await res.json();\n if (!data.ok) {\n const err = new Error(\n data.message ?? `EntityServer error (HTTP ${res.status})`,\n );\n (err as { status?: number }).status = res.status;\n throw err;\n }\n return data as T;\n }\n\n /**\n * \uD3C9\uBB38 \uBC14\uC774\uD2B8\uB97C XChaCha20-Poly1305\uB85C \uC554\uD638\uD654\uD569\uB2C8\uB2E4.\n * \uD3EC\uB9F7: [random_magic:packetMagicLen][random_nonce:24][ciphertext+tag]\n */\n private encryptPacket(plaintext: Uint8Array): Uint8Array {\n const key = sha256(new TextEncoder().encode(this.token));\n const magic = new Uint8Array(this.packetMagicLen);\n const nonce = new Uint8Array(24);\n crypto.getRandomValues(magic);\n crypto.getRandomValues(nonce);\n const cipher = xchacha20poly1305(key, nonce);\n const ciphertext = cipher.encrypt(plaintext);\n const result = new Uint8Array(\n this.packetMagicLen + 24 + ciphertext.length,\n );\n result.set(magic, 0);\n result.set(nonce, this.packetMagicLen);\n result.set(ciphertext, this.packetMagicLen + 24);\n return result;\n }\n\n /** \uC11C\uBC84\uC758 \uC554\uD638\uD654 \uD328\uD0B7\uC744 \uBCF5\uD638\uD654\uD574 JSON \uAC1D\uCCB4\uB85C \uBCC0\uD658\uD569\uB2C8\uB2E4. */\n private decryptPacket<T>(buffer: ArrayBuffer): T {\n const key = sha256(new TextEncoder().encode(this.token));\n const data = new Uint8Array(buffer);\n\n if (data.length < this.packetMagicLen + 24 + 16) {\n throw new Error(\"Encrypted packet too short\");\n }\n\n const nonce = data.slice(this.packetMagicLen, this.packetMagicLen + 24);\n const ciphertext = data.slice(this.packetMagicLen + 24);\n const cipher = xchacha20poly1305(key, nonce);\n const plaintext = cipher.decrypt(ciphertext);\n return JSON.parse(new TextDecoder().decode(plaintext)) as T;\n }\n}\n\n/** \uCFFC\uB9AC \uD30C\uB77C\uBBF8\uD130 \uAC1D\uCCB4\uB97C URL \uCFFC\uB9AC \uBB38\uC790\uC5F4\uB85C \uBCC0\uD658\uD569\uB2C8\uB2E4. */\nfunction buildQuery(params: Record<string, unknown>): string {\n return Object.entries(params)\n .filter(([, value]) => value != null)\n .map(\n ([key, value]) =>\n `${encodeURIComponent(key === \"orderBy\" ? \"order_by\" : key)}=${encodeURIComponent(String(value))}`,\n )\n .join(\"&\");\n}\n\nexport const entityServer = new EntityServerClient();\n"],
5
+ "mappings": "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",
6
+ "names": ["xchacha20poly1305", "sha256", "readEnv", "name", "EntityServerClient", "options", "envBaseUrl", "envMagicLen", "token", "length", "email", "password", "data", "refreshToken", "res", "transactionId", "txId", "entity", "seq", "opts", "q", "params", "conditions", "fields", "orderDir", "orderBy", "rest", "queryObj", "buildQuery", "req", "extraHeaders", "historySeq", "pushEntity", "payload", "accountSeq", "deviceId", "pushToken", "platform", "deviceType", "browser", "browserVersion", "pushEnabled", "deviceSeq", "body", "contentType", "requireEncrypted", "isEncrypted", "sliced", "method", "path", "withAuth", "headers", "fetchBody", "plaintext", "encrypted", "buffer", "err", "key", "magic", "nonce", "ciphertext", "result", "value", "entityServer"]
7
7
  }
package/dist/react.js CHANGED
@@ -1,2 +1,2 @@
1
- import{useMemo as f}from"react";import{xchacha20poly1305 as m}from"@noble/ciphers/chacha";import{sha256 as h}from"@noble/hashes/sha2";function p(u){return import.meta?.env?.[u]}var d=class{baseUrl;token;packetMagicLen;activeTxId=null;constructor(e={}){let t=p("VITE_ENTITY_SERVER_URL"),n=p("VITE_PACKET_MAGIC_LEN");this.baseUrl=(e.baseUrl??t??"http://localhost:47200").replace(/\/$/,""),this.token=e.token??"",this.packetMagicLen=e.packetMagicLen??(n?Number(n):4)}configure(e){e.baseUrl&&(this.baseUrl=e.baseUrl.replace(/\/$/,"")),typeof e.token=="string"&&(this.token=e.token),typeof e.packetMagicLen=="number"&&(this.packetMagicLen=e.packetMagicLen)}setToken(e){this.token=e}setPacketMagicLen(e){this.packetMagicLen=e}getPacketMagicLen(){return this.packetMagicLen}async login(e,t){let n=await this.request("POST","/v1/auth/login",{email:e,passwd:t},!1);return this.token=n.data.access_token,n.data}async refreshToken(e){let t=await this.request("POST","/v1/auth/refresh",{refresh_token:e},!1);return this.token=t.data.access_token,t.data}async transStart(){let e=await this.request("POST","/v1/transaction/start",void 0,!1);return this.activeTxId=e.transaction_id,this.activeTxId}transRollback(e){let t=e??this.activeTxId;return t?(this.activeTxId=null,this.request("POST",`/v1/transaction/rollback/${t}`)):Promise.reject(new Error("No active transaction. Call transStart() first."))}transCommit(e){let t=e??this.activeTxId;return t?(this.activeTxId=null,this.request("POST",`/v1/transaction/commit/${t}`)):Promise.reject(new Error("No active transaction. Call transStart() first."))}get(e,t,n={}){let r=n.skipHooks?"?skipHooks=true":"";return this.request("GET",`/v1/entity/${e}/${t}${r}`)}list(e,t={}){let{conditions:n,fields:r,orderDir:s,orderBy:i,...o}=t,a={page:1,limit:20,...o};i&&(a.orderBy=s==="DESC"?`-${i}`:i),r?.length&&(a.fields=r.join(","));let c=g(a);return this.request("POST",`/v1/entity/${e}/list?${c}`,n??{})}count(e,t){return this.request("POST",`/v1/entity/${e}/count`,t??{})}query(e,t){return this.request("POST",`/v1/entity/${e}/query`,t)}submit(e,t,n={}){let r=n.transactionId??this.activeTxId,s=r?{"X-Transaction-ID":r}:void 0,i=n.skipHooks?"?skipHooks=true":"";return this.request("POST",`/v1/entity/${e}/submit${i}`,t,!0,s)}delete(e,t,n={}){let r=new URLSearchParams;n.hard&&r.set("hard","true"),n.skipHooks&&r.set("skipHooks","true");let s=r.size?`?${r}`:"",i=n.transactionId??this.activeTxId,o=i?{"X-Transaction-ID":i}:void 0;return this.request("POST",`/v1/entity/${e}/delete/${t}${s}`,void 0,!0,o)}history(e,t,n={}){let r=g({page:1,limit:50,...n});return this.request("GET",`/v1/entity/${e}/history/${t}?${r}`)}rollback(e,t){return this.request("POST",`/v1/entity/${e}/rollback/${t}`)}push(e,t,n={}){return this.submit(e,t,n)}pushLogList(e={}){return this.list("push_log",e)}registerPushDevice(e,t,n,r={}){let{platform:s,deviceType:i,browser:o,browserVersion:a,pushEnabled:c=!0,transactionId:l}=r;return this.submit("account_device",{id:t,account_seq:e,push_token:n,push_enabled:c,...s?{platform:s}:{},...i?{device_type:i}:{},...o?{browser:o}:{},...a?{browser_version:a}:{}},{transactionId:l})}updatePushDeviceToken(e,t,n={}){let{pushEnabled:r=!0,transactionId:s}=n;return this.submit("account_device",{seq:e,push_token:t,push_enabled:r},{transactionId:s})}disablePushDevice(e,t={}){return this.submit("account_device",{seq:e,push_enabled:!1},{transactionId:t.transactionId})}readRequestBody(e,t="application/json",n=!1){let s=t.toLowerCase().includes("application/octet-stream");if(n&&!s)throw new Error("Encrypted request required: Content-Type must be application/octet-stream");if(s){if(e==null)throw new Error("Encrypted request body is empty");if(e instanceof ArrayBuffer)return this.decryptPacket(e);if(e instanceof Uint8Array){let i=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);return this.decryptPacket(i)}throw new Error("Encrypted request body must be ArrayBuffer or Uint8Array")}return e==null||e===""?{}:typeof e=="string"?JSON.parse(e):e}async request(e,t,n,r=!0,s={}){let i={"Content-Type":"application/json",...s};r&&this.token&&(i.Authorization=`Bearer ${this.token}`);let o=await fetch(this.baseUrl+t,{method:e,headers:i,...n!=null?{body:JSON.stringify(n)}:{}});if((o.headers.get("Content-Type")??"").includes("application/octet-stream")){let l=await o.arrayBuffer();return this.decryptPacket(l)}let c=await o.json();if(!c.ok){let l=new Error(c.message??`EntityServer error (HTTP ${o.status})`);throw l.status=o.status,l}return c}decryptPacket(e){let t=h(new TextEncoder().encode(this.token)),n=new Uint8Array(e);if(n.length<this.packetMagicLen+24+16)throw new Error("Encrypted packet too short");let r=n.slice(this.packetMagicLen,this.packetMagicLen+24),s=n.slice(this.packetMagicLen+24),o=m(t,r).decrypt(s);return JSON.parse(new TextDecoder().decode(o))}};function g(u){return Object.entries(u).filter(([,e])=>e!=null).map(([e,t])=>`${encodeURIComponent(e==="orderBy"?"order_by":e)}=${encodeURIComponent(String(t))}`).join("&")}var k=new d;function P(u={}){let{singleton:e=!0,tokenResolver:t,baseUrl:n,packetMagicLen:r,token:s}=u;return f(()=>{let i=e?k:new d({baseUrl:n,packetMagicLen:r,token:s});e&&i.configure({baseUrl:n,packetMagicLen:r,token:s});let o=t?.();return typeof o=="string"&&i.setToken(o),i},[e,t,n,r,s])}export{P as useEntityServer};
1
+ import{useMemo as T}from"react";import{xchacha20poly1305 as g}from"@noble/ciphers/chacha";import{sha256 as k}from"@noble/hashes/sha2";function h(c){return import.meta?.env?.[c]}var p=class{baseUrl;token;packetMagicLen;encryptRequests;activeTxId=null;constructor(e={}){let t=h("VITE_ENTITY_SERVER_URL"),n=h("VITE_PACKET_MAGIC_LEN");this.baseUrl=(e.baseUrl??t??"http://localhost:47200").replace(/\/$/,""),this.token=e.token??"",this.packetMagicLen=e.packetMagicLen??(n?Number(n):4),this.encryptRequests=e.encryptRequests??!1}configure(e){e.baseUrl&&(this.baseUrl=e.baseUrl.replace(/\/$/,"")),typeof e.token=="string"&&(this.token=e.token),typeof e.packetMagicLen=="number"&&(this.packetMagicLen=e.packetMagicLen),typeof e.encryptRequests=="boolean"&&(this.encryptRequests=e.encryptRequests)}setToken(e){this.token=e}setPacketMagicLen(e){this.packetMagicLen=e}getPacketMagicLen(){return this.packetMagicLen}async login(e,t){let n=await this.request("POST","/v1/auth/login",{email:e,passwd:t},!1);return this.token=n.data.access_token,n.data}async refreshToken(e){let t=await this.request("POST","/v1/auth/refresh",{refresh_token:e},!1);return this.token=t.data.access_token,t.data}async transStart(){let e=await this.request("POST","/v1/transaction/start",void 0,!1);return this.activeTxId=e.transaction_id,this.activeTxId}transRollback(e){let t=e??this.activeTxId;return t?(this.activeTxId=null,this.request("POST",`/v1/transaction/rollback/${t}`)):Promise.reject(new Error("No active transaction. Call transStart() first."))}transCommit(e){let t=e??this.activeTxId;return t?(this.activeTxId=null,this.request("POST",`/v1/transaction/commit/${t}`)):Promise.reject(new Error("No active transaction. Call transStart() first."))}get(e,t,n={}){let r=n.skipHooks?"?skipHooks=true":"";return this.request("GET",`/v1/entity/${e}/${t}${r}`)}list(e,t={}){let{conditions:n,fields:r,orderDir:i,orderBy:s,...o}=t,a={page:1,limit:20,...o};s&&(a.orderBy=i==="DESC"?`-${s}`:s),r?.length&&(a.fields=r.join(","));let d=y(a);return this.request("POST",`/v1/entity/${e}/list?${d}`,n??{})}count(e,t){return this.request("POST",`/v1/entity/${e}/count`,t??{})}query(e,t){return this.request("POST",`/v1/entity/${e}/query`,t)}submit(e,t,n={}){let r=n.transactionId??this.activeTxId,i=r?{"X-Transaction-ID":r}:void 0,s=n.skipHooks?"?skipHooks=true":"";return this.request("POST",`/v1/entity/${e}/submit${s}`,t,!0,i)}delete(e,t,n={}){let r=new URLSearchParams;n.hard&&r.set("hard","true"),n.skipHooks&&r.set("skipHooks","true");let i=r.size?`?${r}`:"",s=n.transactionId??this.activeTxId,o=s?{"X-Transaction-ID":s}:void 0;return this.request("POST",`/v1/entity/${e}/delete/${t}${i}`,void 0,!0,o)}history(e,t,n={}){let r=y({page:1,limit:50,...n});return this.request("GET",`/v1/entity/${e}/history/${t}?${r}`)}rollback(e,t){return this.request("POST",`/v1/entity/${e}/rollback/${t}`)}push(e,t,n={}){return this.submit(e,t,n)}pushLogList(e={}){return this.list("push_log",e)}registerPushDevice(e,t,n,r={}){let{platform:i,deviceType:s,browser:o,browserVersion:a,pushEnabled:d=!0,transactionId:u}=r;return this.submit("account_device",{id:t,account_seq:e,push_token:n,push_enabled:d,...i?{platform:i}:{},...s?{device_type:s}:{},...o?{browser:o}:{},...a?{browser_version:a}:{}},{transactionId:u})}updatePushDeviceToken(e,t,n={}){let{pushEnabled:r=!0,transactionId:i}=n;return this.submit("account_device",{seq:e,push_token:t,push_enabled:r},{transactionId:i})}disablePushDevice(e,t={}){return this.submit("account_device",{seq:e,push_enabled:!1},{transactionId:t.transactionId})}readRequestBody(e,t="application/json",n=!1){let i=t.toLowerCase().includes("application/octet-stream");if(n&&!i)throw new Error("Encrypted request required: Content-Type must be application/octet-stream");if(i){if(e==null)throw new Error("Encrypted request body is empty");if(e instanceof ArrayBuffer)return this.decryptPacket(e);if(e instanceof Uint8Array){let s=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);return this.decryptPacket(s)}throw new Error("Encrypted request body must be ArrayBuffer or Uint8Array")}return e==null||e===""?{}:typeof e=="string"?JSON.parse(e):e}async request(e,t,n,r=!0,i={}){let s={"Content-Type":"application/json",...i};r&&this.token&&(s.Authorization=`Bearer ${this.token}`);let o=null;if(n!=null)if(this.encryptRequests&&r&&this.token&&e!=="GET"&&e!=="HEAD"){let f=new TextEncoder().encode(JSON.stringify(n)),b=this.encryptPacket(f);s["Content-Type"]="application/octet-stream",o=b}else o=JSON.stringify(n);let a=await fetch(this.baseUrl+t,{method:e,headers:s,...o!=null?{body:o}:{}});if((a.headers.get("Content-Type")??"").includes("application/octet-stream")){let l=await a.arrayBuffer();return this.decryptPacket(l)}let u=await a.json();if(!u.ok){let l=new Error(u.message??`EntityServer error (HTTP ${a.status})`);throw l.status=a.status,l}return u}encryptPacket(e){let t=k(new TextEncoder().encode(this.token)),n=new Uint8Array(this.packetMagicLen),r=new Uint8Array(24);crypto.getRandomValues(n),crypto.getRandomValues(r);let s=g(t,r).encrypt(e),o=new Uint8Array(this.packetMagicLen+24+s.length);return o.set(n,0),o.set(r,this.packetMagicLen),o.set(s,this.packetMagicLen+24),o}decryptPacket(e){let t=k(new TextEncoder().encode(this.token)),n=new Uint8Array(e);if(n.length<this.packetMagicLen+24+16)throw new Error("Encrypted packet too short");let r=n.slice(this.packetMagicLen,this.packetMagicLen+24),i=n.slice(this.packetMagicLen+24),o=g(t,r).decrypt(i);return JSON.parse(new TextDecoder().decode(o))}};function y(c){return Object.entries(c).filter(([,e])=>e!=null).map(([e,t])=>`${encodeURIComponent(e==="orderBy"?"order_by":e)}=${encodeURIComponent(String(t))}`).join("&")}var m=new p;function R(c={}){let{singleton:e=!0,tokenResolver:t,baseUrl:n,packetMagicLen:r,token:i}=c;return T(()=>{let s=e?m:new p({baseUrl:n,packetMagicLen:r,token:i});e&&s.configure({baseUrl:n,packetMagicLen:r,token:i});let o=t?.();return typeof o=="string"&&s.setToken(o),s},[e,t,n,r,i])}export{R as useEntityServer};
2
2
  //# sourceMappingURL=react.js.map
package/dist/react.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/hooks/useEntityServer.ts", "../src/index.ts"],
4
- "sourcesContent": ["import { useMemo } from \"react\";\nimport {\n EntityServerClient,\n entityServer,\n type EntityServerClientOptions,\n} from \"../index\";\n\nexport interface UseEntityServerOptions extends EntityServerClientOptions {\n singleton?: boolean;\n tokenResolver?: () => string | undefined | null;\n}\n\n/**\n * React \uD658\uACBD\uC5D0\uC11C EntityServerClient \uC778\uC2A4\uD134\uC2A4\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4.\n *\n * - `singleton=true`(\uAE30\uBCF8): \uD328\uD0A4\uC9C0 \uC804\uC5ED `entityServer` \uC778\uC2A4\uD134\uC2A4\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4.\n * - `singleton=false`: \uCEF4\uD3EC\uB10C\uD2B8 \uC2A4\uCF54\uD504\uC758 \uC0C8 \uC778\uC2A4\uD134\uC2A4\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4.\n */\nexport function useEntityServer(\n options: UseEntityServerOptions = {},\n): EntityServerClient {\n const {\n singleton = true,\n tokenResolver,\n baseUrl,\n packetMagicLen,\n token,\n } = options;\n\n return useMemo(() => {\n const client = singleton\n ? entityServer\n : new EntityServerClient({\n baseUrl,\n packetMagicLen,\n token,\n });\n\n if (singleton) {\n client.configure({ baseUrl, packetMagicLen, token });\n }\n\n const resolvedToken = tokenResolver?.();\n if (typeof resolvedToken === \"string\") {\n client.setToken(resolvedToken);\n }\n\n return client;\n }, [singleton, tokenResolver, baseUrl, packetMagicLen, token]);\n}\n", "// @ts-ignore\nimport { xchacha20poly1305 } from \"@noble/ciphers/chacha\";\n// @ts-ignore\nimport { sha256 } from \"@noble/hashes/sha2\";\n\n/**\n * \uC5D4\uD2F0\uD2F0 \uBAA9\uB85D \uC870\uD68C \uD30C\uB77C\uBBF8\uD130\uC785\uB2C8\uB2E4.\n *\n * ```ts\n * client.list(\"post\", {\n * page: 1, limit: 10,\n * orderBy: \"created_time\", orderDir: \"DESC\",\n * fields: [\"seq\", \"title\", \"created_time\"],\n * conditions: { status: \"active\" },\n * });\n * ```\n */\nexport interface EntityListParams {\n /** \uC870\uD68C \uD398\uC774\uC9C0 \uBC88\uD638. \uAE30\uBCF8\uAC12: `1` */\n page?: number;\n /** \uD398\uC774\uC9C0\uB2F9 \uB808\uCF54\uB4DC \uC218. \uAE30\uBCF8\uAC12: `20` */\n limit?: number;\n /** \uC815\uB82C \uAE30\uC900 \uD544\uB4DC\uBA85 */\n orderBy?: string;\n /** \uC815\uB82C \uBC29\uD5A5. \uAE30\uBCF8\uAC12: `\"ASC\"` */\n orderDir?: \"ASC\" | \"DESC\";\n /**\n * \uBC18\uD658\uD560 \uD544\uB4DC \uBAA9\uB85D.\n *\n * - **\uBBF8\uC9C0\uC815 (\uAE30\uBCF8\uAC12)**: \uC5D4\uD2F0\uD2F0\uC758 \uC778\uB371\uC2A4 \uD544\uB4DC\uB9CC \uBC18\uD658\uD569\uB2C8\uB2E4.\n * \uBCF5\uD638\uD654\uB97C \uAC74\uB108\uB6F0\uAE30 \uB54C\uBB38\uC5D0 **\uAC00\uC7A5 \uBE60\uB985\uB2C8\uB2E4**.\n * - `[\"*\"]`: \uC804\uCCB4 \uD544\uB4DC \uBC18\uD658 (\uBCF5\uD638\uD654 \uC218\uD589).\n * - \uD544\uB4DC\uBA85 \uBAA9\uB85D: \uD574\uB2F9 \uD544\uB4DC\uB9CC \uBC18\uD658\uD569\uB2C8\uB2E4.\n * \uC5D4\uD2F0\uD2F0 \uC124\uC815\uC5D0 `index`\uB85C \uC120\uC5B8\uB41C \uD544\uB4DC\uB9CC \uC9C0\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.\n * \uC874\uC7AC\uD558\uC9C0 \uC54A\uB294 \uD544\uB4DC\uBA85\uC744 \uC9C0\uC815\uD558\uBA74 \uC11C\uBC84 \uC5D0\uB7EC\uAC00 \uBC1C\uC0DD\uD569\uB2C8\uB2E4.\n * - `seq`, `created_time`, `updated_time`, `license_seq`\uB294 \uD544\uB4DC\uC5D0 \uAD00\uACC4\uC5C6\uC774 \uD56D\uC0C1 \uD3EC\uD568\uB429\uB2C8\uB2E4.\n *\n * ```ts\n * // \uAE30\uBCF8\uAC12 (\uC778\uB371\uC2A4 \uD544\uB4DC\uB9CC, \uAC00\uC7A5 \uBE60\uB984)\n * client.list(\"account\")\n * // \uC804\uCCB4 \uD544\uB4DC\n * client.list(\"account\", { fields: [\"*\"] })\n * // seq, name, email\uB9CC\n * client.list(\"account\", { fields: [\"seq\", \"name\", \"email\"] })\n * ```\n */\n fields?: string[];\n /** \uD544\uD130 \uC870\uAC74. POST body\uB85C \uC804\uB2EC\uB429\uB2C8\uB2E4. (\uC608: `{ status: \"active\" }`) */\n conditions?: Record<string, unknown>;\n}\n\n/**\n * `query()` \uBA54\uC11C\uB4DC\uC5D0 \uC804\uB2EC\uD558\uB294 SQL \uCFFC\uB9AC \uC694\uCCAD\uC785\uB2C8\uB2E4.\n *\n * - `sql`: SELECT \uC804\uC6A9 SQL. \uC778\uB371\uC2A4 \uD14C\uC774\uBE14\uB9CC \uC870\uD68C \uAC00\uB2A5\uD558\uBA70 JOIN \uC9C0\uC6D0.\n * - `params`: SQL \uBC14\uC778\uB529 \uD30C\uB77C\uBBF8\uD130 (`?` \uD50C\uB808\uC774\uC2A4\uD640\uB354 \uB300\uC751).\n * - `limit`: \uCD5C\uB300 \uBC18\uD658 \uAC74\uC218 (\uCD5C\uB300 1000. \uBBF8\uC9C0\uC815 \uC2DC \uC11C\uBC84 \uAE30\uBCF8\uAC12 \uC801\uC6A9).\n *\n * ```ts\n * client.query(\"order\", {\n * sql: `SELECT o.seq, o.status, u.name\n * FROM order o\n * JOIN account u ON u.data_seq = o.account_seq\n * WHERE o.status = ?`,\n * params: [\"pending\"],\n * limit: 100,\n * });\n * ```\n */\nexport interface EntityQueryRequest {\n sql: string;\n params?: unknown[];\n limit?: number;\n}\n\nexport interface RegisterPushDeviceOptions {\n platform?: string;\n deviceType?: string;\n browser?: string;\n browserVersion?: string;\n pushEnabled?: boolean;\n transactionId?: string;\n}\n\n/** EntityServerClient \uC0DD\uC131/\uC124\uC815 \uC635\uC158\uC785\uB2C8\uB2E4. */\nexport interface EntityServerClientOptions {\n baseUrl?: string;\n token?: string;\n packetMagicLen?: number;\n}\n\n/**\n * `list()`, `history()` \uC751\uB2F5\uC758 `data` \uD544\uB4DC \uAD6C\uC870\uC785\uB2C8\uB2E4.\n *\n * \uC11C\uBC84\uB294 \uD56D\uC0C1 \uC774 \uAD6C\uC870\uB85C \uBC18\uD658\uD569\uB2C8\uB2E4:\n * ```json\n * { \"ok\": true, \"data\": { \"items\": [...], \"total\": 100, \"page\": 1, \"limit\": 20 } }\n * ```\n */\nexport interface EntityListResult<T = unknown> {\n items: T[];\n /** \uC804\uCCB4 \uB808\uCF54\uB4DC \uC218 */\n total: number;\n /** \uD604\uC7AC \uD398\uC774\uC9C0 \uBC88\uD638 */\n page: number;\n /** \uD398\uC774\uC9C0\uB2F9 \uB808\uCF54\uB4DC \uC218 */\n limit: number;\n}\n\n/**\n * `history()` \uC751\uB2F5\uC758 \uAC1C\uBCC4 \uC774\uB825 \uB808\uCF54\uB4DC \uAD6C\uC870\uC785\uB2C8\uB2E4.\n *\n * - `action`: `\"INSERT\"` | `\"UPDATE\"` | `\"DELETE_SOFT\"` | `\"DELETE_HARD\"` | `\"ROLLBACK\"`\n * - `data_snapshot`: \uBCC0\uACBD \uB2F9\uC2DC \uC5D4\uD2F0\uD2F0 \uB370\uC774\uD130 \uC2A4\uB0C5\uC0F7\n */\nexport interface EntityHistoryRecord<T = unknown> {\n seq: number;\n action:\n | \"INSERT\"\n | \"UPDATE\"\n | \"DELETE_SOFT\"\n | \"DELETE_HARD\"\n | \"ROLLBACK\"\n | string;\n data_snapshot: T | null;\n changed_by: number | null;\n changed_time: string;\n}\n\n/** Vite \uD658\uACBD\uBCC0\uC218(`import.meta.env`)\uC5D0\uC11C \uAC12\uC744 \uC77D\uC2B5\uB2C8\uB2E4. */\nfunction readEnv(name: string): string | undefined {\n const meta = import.meta as unknown as {\n env?: Record<string, string | undefined>;\n };\n return meta?.env?.[name];\n}\n\nexport class EntityServerClient {\n private baseUrl: string;\n private token: string;\n private packetMagicLen: number;\n private activeTxId: string | null = null;\n\n /**\n * EntityServerClient \uC778\uC2A4\uD134\uC2A4\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4.\n *\n * \uAE30\uBCF8\uAC12:\n * - `baseUrl`: `VITE_ENTITY_SERVER_URL` \uB610\uB294 `http://localhost:47200`\n * - `packetMagicLen`: `VITE_PACKET_MAGIC_LEN` \uB610\uB294 `4`\n */\n constructor(options: EntityServerClientOptions = {}) {\n const envBaseUrl = readEnv(\"VITE_ENTITY_SERVER_URL\");\n const envMagicLen = readEnv(\"VITE_PACKET_MAGIC_LEN\");\n\n this.baseUrl = (\n options.baseUrl ??\n envBaseUrl ??\n \"http://localhost:47200\"\n ).replace(/\\/$/, \"\");\n\n this.token = options.token ?? \"\";\n this.packetMagicLen =\n options.packetMagicLen ?? (envMagicLen ? Number(envMagicLen) : 4);\n }\n\n /** baseUrl, token, packetMagicLen \uAC12\uC744 \uB7F0\uD0C0\uC784\uC5D0 \uAC31\uC2E0\uD569\uB2C8\uB2E4. */\n configure(options: Partial<EntityServerClientOptions>): void {\n if (options.baseUrl) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, \"\");\n }\n if (typeof options.token === \"string\") {\n this.token = options.token;\n }\n if (typeof options.packetMagicLen === \"number\") {\n this.packetMagicLen = options.packetMagicLen;\n }\n }\n\n /** \uC778\uC99D \uC694\uCCAD\uC5D0 \uC0AC\uC6A9\uD560 JWT Access Token\uC744 \uC124\uC815\uD569\uB2C8\uB2E4. */\n setToken(token: string): void {\n this.token = token;\n }\n\n /** \uC554\uD638\uD654 \uD328\uD0B7 magic \uAE38\uC774(`packet_magic_len`)\uB97C \uC124\uC815\uD569\uB2C8\uB2E4. */\n setPacketMagicLen(length: number): void {\n this.packetMagicLen = length;\n }\n\n /** \uD604\uC7AC \uC554\uD638\uD654 \uD328\uD0B7 magic \uAE38\uC774\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4. */\n getPacketMagicLen(): number {\n return this.packetMagicLen;\n }\n\n /** \uB85C\uADF8\uC778 \uD6C4 `access_token`\uC744 \uB0B4\uBD80 \uC0C1\uD0DC\uC5D0 \uC800\uC7A5\uD569\uB2C8\uB2E4. */\n async login(\n email: string,\n password: string,\n ): Promise<{\n access_token: string;\n refresh_token: string;\n expires_in: number;\n }> {\n const data = await this.request<{\n data: {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n }>(\"POST\", \"/v1/auth/login\", { email, passwd: password }, false);\n this.token = data.data.access_token;\n return data.data;\n }\n\n /** Refresh Token\uC73C\uB85C Access Token\uC744 \uC7AC\uBC1C\uAE09\uBC1B\uC544 \uB0B4\uBD80 \uD1A0\uD070\uC744 \uAD50\uCCB4\uD569\uB2C8\uB2E4. */\n async refreshToken(\n refreshToken: string,\n ): Promise<{ access_token: string; expires_in: number }> {\n const data = await this.request<{\n data: { access_token: string; expires_in: number };\n }>(\"POST\", \"/v1/auth/refresh\", { refresh_token: refreshToken }, false);\n this.token = data.data.access_token;\n return data.data;\n }\n\n /** \uD2B8\uB79C\uC7AD\uC158\uC744 \uC2DC\uC791\uD558\uACE0 \uD65C\uC131 \uD2B8\uB79C\uC7AD\uC158 ID\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4. */\n async transStart(): Promise<string> {\n const res = await this.request<{ ok: boolean; transaction_id: string }>(\n \"POST\",\n \"/v1/transaction/start\",\n undefined,\n false,\n );\n this.activeTxId = res.transaction_id;\n return this.activeTxId;\n }\n\n /** \uD65C\uC131 \uD2B8\uB79C\uC7AD\uC158(\uB610\uB294 \uC804\uB2EC\uB41C transactionId)\uC744 \uB864\uBC31\uD569\uB2C8\uB2E4. */\n transRollback(transactionId?: string): Promise<{ ok: boolean }> {\n const txId = transactionId ?? this.activeTxId;\n if (!txId) {\n return Promise.reject(\n new Error(\"No active transaction. Call transStart() first.\"),\n );\n }\n this.activeTxId = null;\n return this.request(\"POST\", `/v1/transaction/rollback/${txId}`);\n }\n\n /** \uD65C\uC131 \uD2B8\uB79C\uC7AD\uC158(\uB610\uB294 \uC804\uB2EC\uB41C transactionId)\uC744 \uCEE4\uBC0B\uD569\uB2C8\uB2E4.\n *\n * @returns `results` \uBC30\uC5F4: commit\uB41C \uAC01 \uC791\uC5C5\uC758 `entity`, `action`, `seq`\n */\n transCommit(transactionId?: string): Promise<{\n ok: boolean;\n results: Array<{ entity: string; action: string; seq: number }>;\n }> {\n const txId = transactionId ?? this.activeTxId;\n if (!txId) {\n return Promise.reject(\n new Error(\"No active transaction. Call transStart() first.\"),\n );\n }\n this.activeTxId = null;\n return this.request(\"POST\", `/v1/transaction/commit/${txId}`);\n }\n\n /** \uC2DC\uD000\uC2A4 ID\uB85C \uC5D4\uD2F0\uD2F0 \uB2E8\uAC74\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. */\n get<T = unknown>(\n entity: string,\n seq: number,\n opts: { skipHooks?: boolean } = {},\n ): Promise<{ ok: boolean; data: T }> {\n const q = opts.skipHooks ? \"?skipHooks=true\" : \"\";\n return this.request(\"GET\", `/v1/entity/${entity}/${seq}${q}`);\n }\n\n /** \uD398\uC774\uC9C0\uB124\uC774\uC158/\uC815\uB82C/\uD544\uD130 \uC870\uAC74\uC73C\uB85C \uC5D4\uD2F0\uD2F0 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. */\n list<T = unknown>(\n entity: string,\n params: EntityListParams = {},\n ): Promise<{ ok: boolean; data: EntityListResult<T> }> {\n const { conditions, fields, orderDir, orderBy, ...rest } = params;\n\n const queryObj: Record<string, unknown> = {\n page: 1,\n limit: 20,\n ...rest,\n };\n if (orderBy) {\n queryObj.orderBy = orderDir === \"DESC\" ? `-${orderBy}` : orderBy;\n }\n if (fields?.length) {\n queryObj.fields = fields.join(\",\");\n }\n\n const q = buildQuery(queryObj);\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/list?${q}`,\n conditions ?? {},\n );\n }\n\n /**\n * \uC5D4\uD2F0\uD2F0 \uCD1D \uAC74\uC218\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.\n *\n * @param conditions \uD544\uD130 \uC870\uAC74 (\uC608: `{ status: \"active\" }`)\n */\n count(\n entity: string,\n conditions?: Record<string, unknown>,\n ): Promise<{ ok: boolean; count: number }> {\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/count`,\n conditions ?? {},\n );\n }\n\n /**\n * \uCEE4\uC2A4\uD140 SQL\uB85C \uC5D4\uD2F0\uD2F0\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.\n *\n * SELECT \uC804\uC6A9\uC774\uBA70 \uC778\uB371\uC2A4 \uD14C\uC774\uBE14\uB9CC \uC870\uD68C \uAC00\uB2A5\uD569\uB2C8\uB2E4.\n * JOIN\uC744 \uC0AC\uC6A9\uD574 \uC5EC\uB7EC \uC5D4\uD2F0\uD2F0\uB97C \uC870\uD569\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n * `entity`\uB294 SQL\uC5D0 \uD3EC\uD568\uB41C \uAE30\uBCF8 \uC5D4\uD2F0\uD2F0\uBA85(\uB77C\uC6B0\uD2B8 \uACBD\uB85C\uC6A9)\uC785\uB2C8\uB2E4.\n */\n query<T = unknown>(\n entity: string,\n req: EntityQueryRequest,\n ): Promise<{ ok: boolean; data: { items: T[]; count: number } }> {\n return this.request(\"POST\", `/v1/entity/${entity}/query`, req);\n }\n\n /** \uC5D4\uD2F0\uD2F0 \uB370\uC774\uD130\uB97C \uC0DD\uC131/\uC218\uC815(Submit)\uD569\uB2C8\uB2E4. `seq`\uAC00 \uC5C6\uC73C\uBA74 INSERT, \uC788\uC73C\uBA74 UPDATE\uC785\uB2C8\uB2E4. */\n submit(\n entity: string,\n data: Record<string, unknown>,\n opts: { transactionId?: string; skipHooks?: boolean } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n const txId = opts.transactionId ?? this.activeTxId;\n const extraHeaders = txId ? { \"X-Transaction-ID\": txId } : undefined;\n const q = opts.skipHooks ? \"?skipHooks=true\" : \"\";\n\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/submit${q}`,\n data,\n true,\n extraHeaders,\n );\n }\n\n /** \uC2DC\uD000\uC2A4 ID\uB85C \uC5D4\uD2F0\uD2F0\uB97C \uC0AD\uC81C\uD569\uB2C8\uB2E4(`hard=true`\uBA74 \uD558\uB4DC \uC0AD\uC81C, \uAE30\uBCF8\uC740 \uC18C\uD504\uD2B8 \uC0AD\uC81C). */\n delete(\n entity: string,\n seq: number,\n opts: {\n transactionId?: string;\n hard?: boolean;\n skipHooks?: boolean;\n } = {},\n ): Promise<{ ok: boolean; deleted: number }> {\n const params = new URLSearchParams();\n if (opts.hard) params.set(\"hard\", \"true\");\n if (opts.skipHooks) params.set(\"skipHooks\", \"true\");\n const q = params.size ? `?${params}` : \"\";\n const txId = opts.transactionId ?? this.activeTxId;\n const extraHeaders = txId ? { \"X-Transaction-ID\": txId } : undefined;\n\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/delete/${seq}${q}`,\n undefined,\n true,\n extraHeaders,\n );\n }\n\n /** \uC5D4\uD2F0\uD2F0 \uB2E8\uAC74\uC758 \uBCC0\uACBD \uC774\uB825\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. \uC774\uB825 \uD56D\uBAA9\uB2F9 `action`, `data_snapshot`, `changed_by`, `changed_time`\uC744 \uD3EC\uD568\uD569\uB2C8\uB2E4. */\n history<T = unknown>(\n entity: string,\n seq: number,\n params: Pick<EntityListParams, \"page\" | \"limit\"> = {},\n ): Promise<{\n ok: boolean;\n data: EntityListResult<EntityHistoryRecord<T>>;\n }> {\n const q = buildQuery({ page: 1, limit: 50, ...params });\n return this.request(\"GET\", `/v1/entity/${entity}/history/${seq}?${q}`);\n }\n\n /** \uD2B9\uC815 \uC774\uB825 \uC2DC\uC810\uC73C\uB85C \uC5D4\uD2F0\uD2F0\uB97C \uB864\uBC31\uD569\uB2C8\uB2E4. */\n rollback(entity: string, historySeq: number): Promise<{ ok: boolean }> {\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/rollback/${historySeq}`,\n );\n }\n\n /** \uD478\uC2DC \uAD00\uB828 \uC5D4\uD2F0\uD2F0\uB85C payload\uB97C \uC804\uC1A1(Submit)\uD569\uB2C8\uB2E4. */\n push(\n pushEntity: string,\n payload: Record<string, unknown>,\n opts: { transactionId?: string } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n return this.submit(pushEntity, payload, opts);\n }\n\n /** \uD478\uC2DC \uB85C\uADF8 \uC5D4\uD2F0\uD2F0 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. */\n pushLogList<T = unknown>(\n params: EntityListParams = {},\n ): Promise<{ ok: boolean; data: EntityListResult<T> }> {\n return this.list<T>(\"push_log\", params);\n }\n\n /** \uACC4\uC815\uC758 \uD478\uC2DC \uB514\uBC14\uC774\uC2A4\uB97C \uB4F1\uB85D\uD569\uB2C8\uB2E4. */\n registerPushDevice(\n accountSeq: number,\n deviceId: string,\n pushToken: string,\n opts: RegisterPushDeviceOptions = {},\n ): Promise<{ ok: boolean; seq: number }> {\n const {\n platform,\n deviceType,\n browser,\n browserVersion,\n pushEnabled = true,\n transactionId,\n } = opts;\n\n return this.submit(\n \"account_device\",\n {\n id: deviceId,\n account_seq: accountSeq,\n push_token: pushToken,\n push_enabled: pushEnabled,\n ...(platform ? { platform } : {}),\n ...(deviceType ? { device_type: deviceType } : {}),\n ...(browser ? { browser } : {}),\n ...(browserVersion ? { browser_version: browserVersion } : {}),\n },\n { transactionId },\n );\n }\n\n /** \uB514\uBC14\uC774\uC2A4 \uB808\uCF54\uB4DC\uC758 \uD478\uC2DC \uD1A0\uD070\uC744 \uAC31\uC2E0\uD569\uB2C8\uB2E4. */\n updatePushDeviceToken(\n deviceSeq: number,\n pushToken: string,\n opts: { pushEnabled?: boolean; transactionId?: string } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n const { pushEnabled = true, transactionId } = opts;\n return this.submit(\n \"account_device\",\n {\n seq: deviceSeq,\n push_token: pushToken,\n push_enabled: pushEnabled,\n },\n { transactionId },\n );\n }\n\n /** \uB514\uBC14\uC774\uC2A4\uC758 \uD478\uC2DC \uC218\uC2E0\uC744 \uBE44\uD65C\uC131\uD654\uD569\uB2C8\uB2E4. */\n disablePushDevice(\n deviceSeq: number,\n opts: { transactionId?: string } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n return this.submit(\n \"account_device\",\n {\n seq: deviceSeq,\n push_enabled: false,\n },\n { transactionId: opts.transactionId },\n );\n }\n\n /**\n * \uC694\uCCAD \uBC14\uB514\uB97C \uD30C\uC2F1\uD558\uACE0 `application/octet-stream`\uC778 \uACBD\uC6B0 \uBCF5\uD638\uD654\uD569\uB2C8\uB2E4.\n *\n * \uC6D0\uC2DC \uC554\uD638\uD654 payload\uB97C \uC9C1\uC811 \uB2E4\uB8E8\uB294 \uD074\uB77C\uC774\uC5B8\uD2B8\uC5D0\uC11C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.\n */\n readRequestBody<T = Record<string, unknown>>(\n body: ArrayBuffer | Uint8Array | string | T | null | undefined,\n contentType = \"application/json\",\n requireEncrypted = false,\n ): T {\n const lowered = contentType.toLowerCase();\n const isEncrypted = lowered.includes(\"application/octet-stream\");\n\n if (requireEncrypted && !isEncrypted) {\n throw new Error(\n \"Encrypted request required: Content-Type must be application/octet-stream\",\n );\n }\n\n if (isEncrypted) {\n if (body == null) {\n throw new Error(\"Encrypted request body is empty\");\n }\n if (body instanceof ArrayBuffer) {\n return this.decryptPacket<T>(body);\n }\n if (body instanceof Uint8Array) {\n const sliced = body.buffer.slice(\n body.byteOffset,\n body.byteOffset + body.byteLength,\n );\n return this.decryptPacket<T>(sliced as ArrayBuffer);\n }\n throw new Error(\n \"Encrypted request body must be ArrayBuffer or Uint8Array\",\n );\n }\n\n if (body == null || body === \"\") return {} as T;\n if (typeof body === \"string\") return JSON.parse(body) as T;\n return body as T;\n }\n\n /**\n * \uACF5\uD1B5 HTTP \uC694\uCCAD \uD568\uC218\uC785\uB2C8\uB2E4.\n *\n * \uC751\uB2F5\uC774 `application/octet-stream`\uC774\uBA74 \uC790\uB3D9 \uBCF5\uD638\uD654\uD558\uACE0,\n * JSON \uC751\uB2F5\uC758 `ok`\uAC00 false\uC774\uBA74 \uC5D0\uB7EC\uB97C \uB358\uC9D1\uB2C8\uB2E4.\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n withAuth = true,\n extraHeaders: Record<string, string> = {},\n ): Promise<T> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...extraHeaders,\n };\n if (withAuth && this.token) {\n headers.Authorization = `Bearer ${this.token}`;\n }\n\n const res = await fetch(this.baseUrl + path, {\n method,\n headers,\n ...(body != null ? { body: JSON.stringify(body) } : {}),\n });\n\n const contentType = res.headers.get(\"Content-Type\") ?? \"\";\n\n if (contentType.includes(\"application/octet-stream\")) {\n const buffer = await res.arrayBuffer();\n return this.decryptPacket<T>(buffer);\n }\n\n const data = await res.json();\n if (!data.ok) {\n const err = new Error(\n data.message ?? `EntityServer error (HTTP ${res.status})`,\n );\n (err as { status?: number }).status = res.status;\n throw err;\n }\n return data as T;\n }\n\n /** \uC11C\uBC84\uC758 \uC554\uD638\uD654 \uD328\uD0B7\uC744 \uBCF5\uD638\uD654\uD574 JSON \uAC1D\uCCB4\uB85C \uBCC0\uD658\uD569\uB2C8\uB2E4. */\n private decryptPacket<T>(buffer: ArrayBuffer): T {\n const key = sha256(new TextEncoder().encode(this.token));\n const data = new Uint8Array(buffer);\n\n if (data.length < this.packetMagicLen + 24 + 16) {\n throw new Error(\"Encrypted packet too short\");\n }\n\n const nonce = data.slice(this.packetMagicLen, this.packetMagicLen + 24);\n const ciphertext = data.slice(this.packetMagicLen + 24);\n const cipher = xchacha20poly1305(key, nonce);\n const plaintext = cipher.decrypt(ciphertext);\n return JSON.parse(new TextDecoder().decode(plaintext)) as T;\n }\n}\n\n/** \uCFFC\uB9AC \uD30C\uB77C\uBBF8\uD130 \uAC1D\uCCB4\uB97C URL \uCFFC\uB9AC \uBB38\uC790\uC5F4\uB85C \uBCC0\uD658\uD569\uB2C8\uB2E4. */\nfunction buildQuery(params: Record<string, unknown>): string {\n return Object.entries(params)\n .filter(([, value]) => value != null)\n .map(\n ([key, value]) =>\n `${encodeURIComponent(key === \"orderBy\" ? \"order_by\" : key)}=${encodeURIComponent(String(value))}`,\n )\n .join(\"&\");\n}\n\nexport const entityServer = new EntityServerClient();\n"],
5
- "mappings": "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",
6
- "names": ["useMemo", "xchacha20poly1305", "sha256", "readEnv", "name", "EntityServerClient", "options", "envBaseUrl", "envMagicLen", "token", "length", "email", "password", "data", "refreshToken", "res", "transactionId", "txId", "entity", "seq", "opts", "q", "params", "conditions", "fields", "orderDir", "orderBy", "rest", "queryObj", "buildQuery", "req", "extraHeaders", "historySeq", "pushEntity", "payload", "accountSeq", "deviceId", "pushToken", "platform", "deviceType", "browser", "browserVersion", "pushEnabled", "deviceSeq", "body", "contentType", "requireEncrypted", "isEncrypted", "sliced", "method", "path", "withAuth", "headers", "buffer", "err", "key", "nonce", "ciphertext", "plaintext", "value", "entityServer", "useEntityServer", "options", "singleton", "tokenResolver", "baseUrl", "packetMagicLen", "token", "useMemo", "client", "entityServer", "EntityServerClient", "resolvedToken"]
4
+ "sourcesContent": ["import { useMemo } from \"react\";\nimport {\n EntityServerClient,\n entityServer,\n type EntityServerClientOptions,\n} from \"../index\";\n\nexport interface UseEntityServerOptions extends EntityServerClientOptions {\n singleton?: boolean;\n tokenResolver?: () => string | undefined | null;\n}\n\n/**\n * React \uD658\uACBD\uC5D0\uC11C EntityServerClient \uC778\uC2A4\uD134\uC2A4\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4.\n *\n * - `singleton=true`(\uAE30\uBCF8): \uD328\uD0A4\uC9C0 \uC804\uC5ED `entityServer` \uC778\uC2A4\uD134\uC2A4\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4.\n * - `singleton=false`: \uCEF4\uD3EC\uB10C\uD2B8 \uC2A4\uCF54\uD504\uC758 \uC0C8 \uC778\uC2A4\uD134\uC2A4\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4.\n */\nexport function useEntityServer(\n options: UseEntityServerOptions = {},\n): EntityServerClient {\n const {\n singleton = true,\n tokenResolver,\n baseUrl,\n packetMagicLen,\n token,\n } = options;\n\n return useMemo(() => {\n const client = singleton\n ? entityServer\n : new EntityServerClient({\n baseUrl,\n packetMagicLen,\n token,\n });\n\n if (singleton) {\n client.configure({ baseUrl, packetMagicLen, token });\n }\n\n const resolvedToken = tokenResolver?.();\n if (typeof resolvedToken === \"string\") {\n client.setToken(resolvedToken);\n }\n\n return client;\n }, [singleton, tokenResolver, baseUrl, packetMagicLen, token]);\n}\n", "// @ts-ignore\nimport { xchacha20poly1305 } from \"@noble/ciphers/chacha\";\n// @ts-ignore\nimport { sha256 } from \"@noble/hashes/sha2\";\n\n/**\n * \uC5D4\uD2F0\uD2F0 \uBAA9\uB85D \uC870\uD68C \uD30C\uB77C\uBBF8\uD130\uC785\uB2C8\uB2E4.\n *\n * ```ts\n * client.list(\"post\", {\n * page: 1, limit: 10,\n * orderBy: \"created_time\", orderDir: \"DESC\",\n * fields: [\"seq\", \"title\", \"created_time\"],\n * conditions: { status: \"active\" },\n * });\n * ```\n */\nexport interface EntityListParams {\n /** \uC870\uD68C \uD398\uC774\uC9C0 \uBC88\uD638. \uAE30\uBCF8\uAC12: `1` */\n page?: number;\n /** \uD398\uC774\uC9C0\uB2F9 \uB808\uCF54\uB4DC \uC218. \uAE30\uBCF8\uAC12: `20` */\n limit?: number;\n /** \uC815\uB82C \uAE30\uC900 \uD544\uB4DC\uBA85 */\n orderBy?: string;\n /** \uC815\uB82C \uBC29\uD5A5. \uAE30\uBCF8\uAC12: `\"ASC\"` */\n orderDir?: \"ASC\" | \"DESC\";\n /**\n * \uBC18\uD658\uD560 \uD544\uB4DC \uBAA9\uB85D.\n *\n * - **\uBBF8\uC9C0\uC815 (\uAE30\uBCF8\uAC12)**: \uC5D4\uD2F0\uD2F0\uC758 \uC778\uB371\uC2A4 \uD544\uB4DC\uB9CC \uBC18\uD658\uD569\uB2C8\uB2E4.\n * \uBCF5\uD638\uD654\uB97C \uAC74\uB108\uB6F0\uAE30 \uB54C\uBB38\uC5D0 **\uAC00\uC7A5 \uBE60\uB985\uB2C8\uB2E4**.\n * - `[\"*\"]`: \uC804\uCCB4 \uD544\uB4DC \uBC18\uD658 (\uBCF5\uD638\uD654 \uC218\uD589).\n * - \uD544\uB4DC\uBA85 \uBAA9\uB85D: \uD574\uB2F9 \uD544\uB4DC\uB9CC \uBC18\uD658\uD569\uB2C8\uB2E4.\n * \uC5D4\uD2F0\uD2F0 \uC124\uC815\uC5D0 `index`\uB85C \uC120\uC5B8\uB41C \uD544\uB4DC\uB9CC \uC9C0\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.\n * \uC874\uC7AC\uD558\uC9C0 \uC54A\uB294 \uD544\uB4DC\uBA85\uC744 \uC9C0\uC815\uD558\uBA74 \uC11C\uBC84 \uC5D0\uB7EC\uAC00 \uBC1C\uC0DD\uD569\uB2C8\uB2E4.\n * - `seq`, `created_time`, `updated_time`, `license_seq`\uB294 \uD544\uB4DC\uC5D0 \uAD00\uACC4\uC5C6\uC774 \uD56D\uC0C1 \uD3EC\uD568\uB429\uB2C8\uB2E4.\n *\n * ```ts\n * // \uAE30\uBCF8\uAC12 (\uC778\uB371\uC2A4 \uD544\uB4DC\uB9CC, \uAC00\uC7A5 \uBE60\uB984)\n * client.list(\"account\")\n * // \uC804\uCCB4 \uD544\uB4DC\n * client.list(\"account\", { fields: [\"*\"] })\n * // seq, name, email\uB9CC\n * client.list(\"account\", { fields: [\"seq\", \"name\", \"email\"] })\n * ```\n */\n fields?: string[];\n /** \uD544\uD130 \uC870\uAC74. POST body\uB85C \uC804\uB2EC\uB429\uB2C8\uB2E4. (\uC608: `{ status: \"active\" }`) */\n conditions?: Record<string, unknown>;\n}\n\n/**\n * `query()` \uBA54\uC11C\uB4DC\uC5D0 \uC804\uB2EC\uD558\uB294 SQL \uCFFC\uB9AC \uC694\uCCAD\uC785\uB2C8\uB2E4.\n *\n * - `sql`: SELECT \uC804\uC6A9 SQL. \uC778\uB371\uC2A4 \uD14C\uC774\uBE14\uB9CC \uC870\uD68C \uAC00\uB2A5\uD558\uBA70 JOIN \uC9C0\uC6D0.\n * - `params`: SQL \uBC14\uC778\uB529 \uD30C\uB77C\uBBF8\uD130 (`?` \uD50C\uB808\uC774\uC2A4\uD640\uB354 \uB300\uC751).\n * - `limit`: \uCD5C\uB300 \uBC18\uD658 \uAC74\uC218 (\uCD5C\uB300 1000. \uBBF8\uC9C0\uC815 \uC2DC \uC11C\uBC84 \uAE30\uBCF8\uAC12 \uC801\uC6A9).\n *\n * ```ts\n * client.query(\"order\", {\n * sql: `SELECT o.seq, o.status, u.name\n * FROM order o\n * JOIN account u ON u.data_seq = o.account_seq\n * WHERE o.status = ?`,\n * params: [\"pending\"],\n * limit: 100,\n * });\n * ```\n */\nexport interface EntityQueryRequest {\n sql: string;\n params?: unknown[];\n limit?: number;\n}\n\nexport interface RegisterPushDeviceOptions {\n platform?: string;\n deviceType?: string;\n browser?: string;\n browserVersion?: string;\n pushEnabled?: boolean;\n transactionId?: string;\n}\n\n/** EntityServerClient \uC0DD\uC131/\uC124\uC815 \uC635\uC158\uC785\uB2C8\uB2E4. */\nexport interface EntityServerClientOptions {\n baseUrl?: string;\n token?: string;\n packetMagicLen?: number;\n /**\n * `true`\uC774\uBA74 \uC778\uC99D\uB41C POST/PUT \uC694\uCCAD \uBC14\uB514\uB97C XChaCha20-Poly1305\uB85C \uC554\uD638\uD654\uD569\uB2C8\uB2E4.\n *\n * \uC11C\uBC84\uC758 `EnablePacketEncryption`\uC774 \uD65C\uC131\uD654\uB41C \uACBD\uC6B0 \uD544\uC218\uB85C \uC124\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.\n * \uB85C\uADF8\uC778(`login()`)\u00B7\uD1A0\uD070 \uAC31\uC2E0(`refreshToken()`)\uC740 \uC778\uC99D \uC804 \uC694\uCCAD\uC774\uBBC0\uB85C \uC790\uB3D9\uC73C\uB85C \uAC74\uB108\uB701\uB2C8\uB2E4.\n *\n * \uAE30\uBCF8\uAC12: `false`\n */\n encryptRequests?: boolean;\n}\n\n/**\n * `list()`, `history()` \uC751\uB2F5\uC758 `data` \uD544\uB4DC \uAD6C\uC870\uC785\uB2C8\uB2E4.\n *\n * \uC11C\uBC84\uB294 \uD56D\uC0C1 \uC774 \uAD6C\uC870\uB85C \uBC18\uD658\uD569\uB2C8\uB2E4:\n * ```json\n * { \"ok\": true, \"data\": { \"items\": [...], \"total\": 100, \"page\": 1, \"limit\": 20 } }\n * ```\n */\nexport interface EntityListResult<T = unknown> {\n items: T[];\n /** \uC804\uCCB4 \uB808\uCF54\uB4DC \uC218 */\n total: number;\n /** \uD604\uC7AC \uD398\uC774\uC9C0 \uBC88\uD638 */\n page: number;\n /** \uD398\uC774\uC9C0\uB2F9 \uB808\uCF54\uB4DC \uC218 */\n limit: number;\n}\n\n/**\n * `history()` \uC751\uB2F5\uC758 \uAC1C\uBCC4 \uC774\uB825 \uB808\uCF54\uB4DC \uAD6C\uC870\uC785\uB2C8\uB2E4.\n *\n * - `action`: `\"INSERT\"` | `\"UPDATE\"` | `\"DELETE_SOFT\"` | `\"DELETE_HARD\"` | `\"ROLLBACK\"`\n * - `data_snapshot`: \uBCC0\uACBD \uB2F9\uC2DC \uC5D4\uD2F0\uD2F0 \uB370\uC774\uD130 \uC2A4\uB0C5\uC0F7\n */\nexport interface EntityHistoryRecord<T = unknown> {\n seq: number;\n action:\n | \"INSERT\"\n | \"UPDATE\"\n | \"DELETE_SOFT\"\n | \"DELETE_HARD\"\n | \"ROLLBACK\"\n | string;\n data_snapshot: T | null;\n changed_by: number | null;\n changed_time: string;\n}\n\n/** Vite \uD658\uACBD\uBCC0\uC218(`import.meta.env`)\uC5D0\uC11C \uAC12\uC744 \uC77D\uC2B5\uB2C8\uB2E4. */\nfunction readEnv(name: string): string | undefined {\n const meta = import.meta as unknown as {\n env?: Record<string, string | undefined>;\n };\n return meta?.env?.[name];\n}\n\nexport class EntityServerClient {\n private baseUrl: string;\n private token: string;\n private packetMagicLen: number;\n private encryptRequests: boolean;\n private activeTxId: string | null = null;\n\n /**\n * EntityServerClient \uC778\uC2A4\uD134\uC2A4\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4.\n *\n * \uAE30\uBCF8\uAC12:\n * - `baseUrl`: `VITE_ENTITY_SERVER_URL` \uB610\uB294 `http://localhost:47200`\n * - `packetMagicLen`: `VITE_PACKET_MAGIC_LEN` \uB610\uB294 `4`\n */\n constructor(options: EntityServerClientOptions = {}) {\n const envBaseUrl = readEnv(\"VITE_ENTITY_SERVER_URL\");\n const envMagicLen = readEnv(\"VITE_PACKET_MAGIC_LEN\");\n\n this.baseUrl = (\n options.baseUrl ??\n envBaseUrl ??\n \"http://localhost:47200\"\n ).replace(/\\/$/, \"\");\n\n this.token = options.token ?? \"\";\n this.packetMagicLen =\n options.packetMagicLen ?? (envMagicLen ? Number(envMagicLen) : 4);\n this.encryptRequests = options.encryptRequests ?? false;\n }\n\n /** baseUrl, token, packetMagicLen, encryptRequests \uAC12\uC744 \uB7F0\uD0C0\uC784\uC5D0 \uAC31\uC2E0\uD569\uB2C8\uB2E4. */\n configure(options: Partial<EntityServerClientOptions>): void {\n if (options.baseUrl) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, \"\");\n }\n if (typeof options.token === \"string\") {\n this.token = options.token;\n }\n if (typeof options.packetMagicLen === \"number\") {\n this.packetMagicLen = options.packetMagicLen;\n }\n if (typeof options.encryptRequests === \"boolean\") {\n this.encryptRequests = options.encryptRequests;\n }\n }\n\n /** \uC778\uC99D \uC694\uCCAD\uC5D0 \uC0AC\uC6A9\uD560 JWT Access Token\uC744 \uC124\uC815\uD569\uB2C8\uB2E4. */\n setToken(token: string): void {\n this.token = token;\n }\n\n /** \uC554\uD638\uD654 \uD328\uD0B7 magic \uAE38\uC774(`packet_magic_len`)\uB97C \uC124\uC815\uD569\uB2C8\uB2E4. */\n setPacketMagicLen(length: number): void {\n this.packetMagicLen = length;\n }\n\n /** \uD604\uC7AC \uC554\uD638\uD654 \uD328\uD0B7 magic \uAE38\uC774\uB97C \uBC18\uD658\uD569\uB2C8\uB2E4. */\n getPacketMagicLen(): number {\n return this.packetMagicLen;\n }\n\n /** \uB85C\uADF8\uC778 \uD6C4 `access_token`\uC744 \uB0B4\uBD80 \uC0C1\uD0DC\uC5D0 \uC800\uC7A5\uD569\uB2C8\uB2E4. */\n async login(\n email: string,\n password: string,\n ): Promise<{\n access_token: string;\n refresh_token: string;\n expires_in: number;\n }> {\n const data = await this.request<{\n data: {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n }>(\"POST\", \"/v1/auth/login\", { email, passwd: password }, false);\n this.token = data.data.access_token;\n return data.data;\n }\n\n /** Refresh Token\uC73C\uB85C Access Token\uC744 \uC7AC\uBC1C\uAE09\uBC1B\uC544 \uB0B4\uBD80 \uD1A0\uD070\uC744 \uAD50\uCCB4\uD569\uB2C8\uB2E4. */\n async refreshToken(\n refreshToken: string,\n ): Promise<{ access_token: string; expires_in: number }> {\n const data = await this.request<{\n data: { access_token: string; expires_in: number };\n }>(\"POST\", \"/v1/auth/refresh\", { refresh_token: refreshToken }, false);\n this.token = data.data.access_token;\n return data.data;\n }\n\n /** \uD2B8\uB79C\uC7AD\uC158\uC744 \uC2DC\uC791\uD558\uACE0 \uD65C\uC131 \uD2B8\uB79C\uC7AD\uC158 ID\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4. */\n async transStart(): Promise<string> {\n const res = await this.request<{ ok: boolean; transaction_id: string }>(\n \"POST\",\n \"/v1/transaction/start\",\n undefined,\n false,\n );\n this.activeTxId = res.transaction_id;\n return this.activeTxId;\n }\n\n /** \uD65C\uC131 \uD2B8\uB79C\uC7AD\uC158(\uB610\uB294 \uC804\uB2EC\uB41C transactionId)\uC744 \uB864\uBC31\uD569\uB2C8\uB2E4. */\n transRollback(transactionId?: string): Promise<{ ok: boolean }> {\n const txId = transactionId ?? this.activeTxId;\n if (!txId) {\n return Promise.reject(\n new Error(\"No active transaction. Call transStart() first.\"),\n );\n }\n this.activeTxId = null;\n return this.request(\"POST\", `/v1/transaction/rollback/${txId}`);\n }\n\n /** \uD65C\uC131 \uD2B8\uB79C\uC7AD\uC158(\uB610\uB294 \uC804\uB2EC\uB41C transactionId)\uC744 \uCEE4\uBC0B\uD569\uB2C8\uB2E4.\n *\n * @returns `results` \uBC30\uC5F4: commit\uB41C \uAC01 \uC791\uC5C5\uC758 `entity`, `action`, `seq`\n */\n transCommit(transactionId?: string): Promise<{\n ok: boolean;\n results: Array<{ entity: string; action: string; seq: number }>;\n }> {\n const txId = transactionId ?? this.activeTxId;\n if (!txId) {\n return Promise.reject(\n new Error(\"No active transaction. Call transStart() first.\"),\n );\n }\n this.activeTxId = null;\n return this.request(\"POST\", `/v1/transaction/commit/${txId}`);\n }\n\n /** \uC2DC\uD000\uC2A4 ID\uB85C \uC5D4\uD2F0\uD2F0 \uB2E8\uAC74\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. */\n get<T = unknown>(\n entity: string,\n seq: number,\n opts: { skipHooks?: boolean } = {},\n ): Promise<{ ok: boolean; data: T }> {\n const q = opts.skipHooks ? \"?skipHooks=true\" : \"\";\n return this.request(\"GET\", `/v1/entity/${entity}/${seq}${q}`);\n }\n\n /** \uD398\uC774\uC9C0\uB124\uC774\uC158/\uC815\uB82C/\uD544\uD130 \uC870\uAC74\uC73C\uB85C \uC5D4\uD2F0\uD2F0 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. */\n list<T = unknown>(\n entity: string,\n params: EntityListParams = {},\n ): Promise<{ ok: boolean; data: EntityListResult<T> }> {\n const { conditions, fields, orderDir, orderBy, ...rest } = params;\n\n const queryObj: Record<string, unknown> = {\n page: 1,\n limit: 20,\n ...rest,\n };\n if (orderBy) {\n queryObj.orderBy = orderDir === \"DESC\" ? `-${orderBy}` : orderBy;\n }\n if (fields?.length) {\n queryObj.fields = fields.join(\",\");\n }\n\n const q = buildQuery(queryObj);\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/list?${q}`,\n conditions ?? {},\n );\n }\n\n /**\n * \uC5D4\uD2F0\uD2F0 \uCD1D \uAC74\uC218\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.\n *\n * @param conditions \uD544\uD130 \uC870\uAC74 (\uC608: `{ status: \"active\" }`)\n */\n count(\n entity: string,\n conditions?: Record<string, unknown>,\n ): Promise<{ ok: boolean; count: number }> {\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/count`,\n conditions ?? {},\n );\n }\n\n /**\n * \uCEE4\uC2A4\uD140 SQL\uB85C \uC5D4\uD2F0\uD2F0\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.\n *\n * SELECT \uC804\uC6A9\uC774\uBA70 \uC778\uB371\uC2A4 \uD14C\uC774\uBE14\uB9CC \uC870\uD68C \uAC00\uB2A5\uD569\uB2C8\uB2E4.\n * JOIN\uC744 \uC0AC\uC6A9\uD574 \uC5EC\uB7EC \uC5D4\uD2F0\uD2F0\uB97C \uC870\uD569\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n * `entity`\uB294 SQL\uC5D0 \uD3EC\uD568\uB41C \uAE30\uBCF8 \uC5D4\uD2F0\uD2F0\uBA85(\uB77C\uC6B0\uD2B8 \uACBD\uB85C\uC6A9)\uC785\uB2C8\uB2E4.\n */\n query<T = unknown>(\n entity: string,\n req: EntityQueryRequest,\n ): Promise<{ ok: boolean; data: { items: T[]; count: number } }> {\n return this.request(\"POST\", `/v1/entity/${entity}/query`, req);\n }\n\n /** \uC5D4\uD2F0\uD2F0 \uB370\uC774\uD130\uB97C \uC0DD\uC131/\uC218\uC815(Submit)\uD569\uB2C8\uB2E4. `seq`\uAC00 \uC5C6\uC73C\uBA74 INSERT, \uC788\uC73C\uBA74 UPDATE\uC785\uB2C8\uB2E4. */\n submit(\n entity: string,\n data: Record<string, unknown>,\n opts: { transactionId?: string; skipHooks?: boolean } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n const txId = opts.transactionId ?? this.activeTxId;\n const extraHeaders = txId ? { \"X-Transaction-ID\": txId } : undefined;\n const q = opts.skipHooks ? \"?skipHooks=true\" : \"\";\n\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/submit${q}`,\n data,\n true,\n extraHeaders,\n );\n }\n\n /** \uC2DC\uD000\uC2A4 ID\uB85C \uC5D4\uD2F0\uD2F0\uB97C \uC0AD\uC81C\uD569\uB2C8\uB2E4(`hard=true`\uBA74 \uD558\uB4DC \uC0AD\uC81C, \uAE30\uBCF8\uC740 \uC18C\uD504\uD2B8 \uC0AD\uC81C). */\n delete(\n entity: string,\n seq: number,\n opts: {\n transactionId?: string;\n hard?: boolean;\n skipHooks?: boolean;\n } = {},\n ): Promise<{ ok: boolean; deleted: number }> {\n const params = new URLSearchParams();\n if (opts.hard) params.set(\"hard\", \"true\");\n if (opts.skipHooks) params.set(\"skipHooks\", \"true\");\n const q = params.size ? `?${params}` : \"\";\n const txId = opts.transactionId ?? this.activeTxId;\n const extraHeaders = txId ? { \"X-Transaction-ID\": txId } : undefined;\n\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/delete/${seq}${q}`,\n undefined,\n true,\n extraHeaders,\n );\n }\n\n /** \uC5D4\uD2F0\uD2F0 \uB2E8\uAC74\uC758 \uBCC0\uACBD \uC774\uB825\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. \uC774\uB825 \uD56D\uBAA9\uB2F9 `action`, `data_snapshot`, `changed_by`, `changed_time`\uC744 \uD3EC\uD568\uD569\uB2C8\uB2E4. */\n history<T = unknown>(\n entity: string,\n seq: number,\n params: Pick<EntityListParams, \"page\" | \"limit\"> = {},\n ): Promise<{\n ok: boolean;\n data: EntityListResult<EntityHistoryRecord<T>>;\n }> {\n const q = buildQuery({ page: 1, limit: 50, ...params });\n return this.request(\"GET\", `/v1/entity/${entity}/history/${seq}?${q}`);\n }\n\n /** \uD2B9\uC815 \uC774\uB825 \uC2DC\uC810\uC73C\uB85C \uC5D4\uD2F0\uD2F0\uB97C \uB864\uBC31\uD569\uB2C8\uB2E4. */\n rollback(entity: string, historySeq: number): Promise<{ ok: boolean }> {\n return this.request(\n \"POST\",\n `/v1/entity/${entity}/rollback/${historySeq}`,\n );\n }\n\n /** \uD478\uC2DC \uAD00\uB828 \uC5D4\uD2F0\uD2F0\uB85C payload\uB97C \uC804\uC1A1(Submit)\uD569\uB2C8\uB2E4. */\n push(\n pushEntity: string,\n payload: Record<string, unknown>,\n opts: { transactionId?: string } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n return this.submit(pushEntity, payload, opts);\n }\n\n /** \uD478\uC2DC \uB85C\uADF8 \uC5D4\uD2F0\uD2F0 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. */\n pushLogList<T = unknown>(\n params: EntityListParams = {},\n ): Promise<{ ok: boolean; data: EntityListResult<T> }> {\n return this.list<T>(\"push_log\", params);\n }\n\n /** \uACC4\uC815\uC758 \uD478\uC2DC \uB514\uBC14\uC774\uC2A4\uB97C \uB4F1\uB85D\uD569\uB2C8\uB2E4. */\n registerPushDevice(\n accountSeq: number,\n deviceId: string,\n pushToken: string,\n opts: RegisterPushDeviceOptions = {},\n ): Promise<{ ok: boolean; seq: number }> {\n const {\n platform,\n deviceType,\n browser,\n browserVersion,\n pushEnabled = true,\n transactionId,\n } = opts;\n\n return this.submit(\n \"account_device\",\n {\n id: deviceId,\n account_seq: accountSeq,\n push_token: pushToken,\n push_enabled: pushEnabled,\n ...(platform ? { platform } : {}),\n ...(deviceType ? { device_type: deviceType } : {}),\n ...(browser ? { browser } : {}),\n ...(browserVersion ? { browser_version: browserVersion } : {}),\n },\n { transactionId },\n );\n }\n\n /** \uB514\uBC14\uC774\uC2A4 \uB808\uCF54\uB4DC\uC758 \uD478\uC2DC \uD1A0\uD070\uC744 \uAC31\uC2E0\uD569\uB2C8\uB2E4. */\n updatePushDeviceToken(\n deviceSeq: number,\n pushToken: string,\n opts: { pushEnabled?: boolean; transactionId?: string } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n const { pushEnabled = true, transactionId } = opts;\n return this.submit(\n \"account_device\",\n {\n seq: deviceSeq,\n push_token: pushToken,\n push_enabled: pushEnabled,\n },\n { transactionId },\n );\n }\n\n /** \uB514\uBC14\uC774\uC2A4\uC758 \uD478\uC2DC \uC218\uC2E0\uC744 \uBE44\uD65C\uC131\uD654\uD569\uB2C8\uB2E4. */\n disablePushDevice(\n deviceSeq: number,\n opts: { transactionId?: string } = {},\n ): Promise<{ ok: boolean; seq: number }> {\n return this.submit(\n \"account_device\",\n {\n seq: deviceSeq,\n push_enabled: false,\n },\n { transactionId: opts.transactionId },\n );\n }\n\n /**\n * \uC694\uCCAD \uBC14\uB514\uB97C \uD30C\uC2F1\uD558\uACE0 `application/octet-stream`\uC778 \uACBD\uC6B0 \uBCF5\uD638\uD654\uD569\uB2C8\uB2E4.\n *\n * \uC6D0\uC2DC \uC554\uD638\uD654 payload\uB97C \uC9C1\uC811 \uB2E4\uB8E8\uB294 \uD074\uB77C\uC774\uC5B8\uD2B8\uC5D0\uC11C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.\n */\n readRequestBody<T = Record<string, unknown>>(\n body: ArrayBuffer | Uint8Array | string | T | null | undefined,\n contentType = \"application/json\",\n requireEncrypted = false,\n ): T {\n const lowered = contentType.toLowerCase();\n const isEncrypted = lowered.includes(\"application/octet-stream\");\n\n if (requireEncrypted && !isEncrypted) {\n throw new Error(\n \"Encrypted request required: Content-Type must be application/octet-stream\",\n );\n }\n\n if (isEncrypted) {\n if (body == null) {\n throw new Error(\"Encrypted request body is empty\");\n }\n if (body instanceof ArrayBuffer) {\n return this.decryptPacket<T>(body);\n }\n if (body instanceof Uint8Array) {\n const sliced = body.buffer.slice(\n body.byteOffset,\n body.byteOffset + body.byteLength,\n );\n return this.decryptPacket<T>(sliced as ArrayBuffer);\n }\n throw new Error(\n \"Encrypted request body must be ArrayBuffer or Uint8Array\",\n );\n }\n\n if (body == null || body === \"\") return {} as T;\n if (typeof body === \"string\") return JSON.parse(body) as T;\n return body as T;\n }\n\n /**\n * \uACF5\uD1B5 HTTP \uC694\uCCAD \uD568\uC218\uC785\uB2C8\uB2E4.\n *\n * - `encryptRequests`\uAC00 \uD65C\uC131\uD654\uB41C \uC778\uC99D \uC694\uCCAD\uC758 POST \uBC14\uB514\uB97C \uC790\uB3D9 \uC554\uD638\uD654\uD569\uB2C8\uB2E4.\n * - \uC751\uB2F5\uC774 `application/octet-stream`\uC774\uBA74 \uC790\uB3D9 \uBCF5\uD638\uD654\uD569\uB2C8\uB2E4.\n * - JSON \uC751\uB2F5\uC758 `ok`\uAC00 false\uC774\uBA74 \uC5D0\uB7EC\uB97C \uB358\uC9D1\uB2C8\uB2E4.\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n withAuth = true,\n extraHeaders: Record<string, string> = {},\n ): Promise<T> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...extraHeaders,\n };\n if (withAuth && this.token) {\n headers.Authorization = `Bearer ${this.token}`;\n }\n\n // \uC694\uCCAD \uBC14\uB514 \uACB0\uC815: encryptRequests \uD65C\uC131\uD654 \uC2DC POST \uBC14\uB514\uB97C \uC554\uD638\uD654\uD569\uB2C8\uB2E4.\n // - \uB85C\uADF8\uC778/\uD1A0\uD070 \uAC31\uC2E0(withAuth=false)\uC740 \uC554\uD638\uD654\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.\n // - GET \uC740 \uBC14\uB514\uAC00 \uC5C6\uC73C\uBBC0\uB85C \uAC74\uB108\uB701\uB2C8\uB2E4.\n let fetchBody: string | Uint8Array | null = null;\n if (body != null) {\n const shouldEncrypt =\n this.encryptRequests &&\n withAuth &&\n this.token &&\n method !== \"GET\" &&\n method !== \"HEAD\";\n\n if (shouldEncrypt) {\n const plaintext = new TextEncoder().encode(JSON.stringify(body));\n const encrypted = this.encryptPacket(plaintext);\n headers[\"Content-Type\"] = \"application/octet-stream\";\n fetchBody = encrypted;\n } else {\n fetchBody = JSON.stringify(body);\n }\n }\n\n const res = await fetch(this.baseUrl + path, {\n method,\n headers,\n ...(fetchBody != null ? { body: fetchBody as BodyInit } : {}),\n });\n\n const contentType = res.headers.get(\"Content-Type\") ?? \"\";\n\n if (contentType.includes(\"application/octet-stream\")) {\n const buffer = await res.arrayBuffer();\n return this.decryptPacket<T>(buffer);\n }\n\n const data = await res.json();\n if (!data.ok) {\n const err = new Error(\n data.message ?? `EntityServer error (HTTP ${res.status})`,\n );\n (err as { status?: number }).status = res.status;\n throw err;\n }\n return data as T;\n }\n\n /**\n * \uD3C9\uBB38 \uBC14\uC774\uD2B8\uB97C XChaCha20-Poly1305\uB85C \uC554\uD638\uD654\uD569\uB2C8\uB2E4.\n * \uD3EC\uB9F7: [random_magic:packetMagicLen][random_nonce:24][ciphertext+tag]\n */\n private encryptPacket(plaintext: Uint8Array): Uint8Array {\n const key = sha256(new TextEncoder().encode(this.token));\n const magic = new Uint8Array(this.packetMagicLen);\n const nonce = new Uint8Array(24);\n crypto.getRandomValues(magic);\n crypto.getRandomValues(nonce);\n const cipher = xchacha20poly1305(key, nonce);\n const ciphertext = cipher.encrypt(plaintext);\n const result = new Uint8Array(\n this.packetMagicLen + 24 + ciphertext.length,\n );\n result.set(magic, 0);\n result.set(nonce, this.packetMagicLen);\n result.set(ciphertext, this.packetMagicLen + 24);\n return result;\n }\n\n /** \uC11C\uBC84\uC758 \uC554\uD638\uD654 \uD328\uD0B7\uC744 \uBCF5\uD638\uD654\uD574 JSON \uAC1D\uCCB4\uB85C \uBCC0\uD658\uD569\uB2C8\uB2E4. */\n private decryptPacket<T>(buffer: ArrayBuffer): T {\n const key = sha256(new TextEncoder().encode(this.token));\n const data = new Uint8Array(buffer);\n\n if (data.length < this.packetMagicLen + 24 + 16) {\n throw new Error(\"Encrypted packet too short\");\n }\n\n const nonce = data.slice(this.packetMagicLen, this.packetMagicLen + 24);\n const ciphertext = data.slice(this.packetMagicLen + 24);\n const cipher = xchacha20poly1305(key, nonce);\n const plaintext = cipher.decrypt(ciphertext);\n return JSON.parse(new TextDecoder().decode(plaintext)) as T;\n }\n}\n\n/** \uCFFC\uB9AC \uD30C\uB77C\uBBF8\uD130 \uAC1D\uCCB4\uB97C URL \uCFFC\uB9AC \uBB38\uC790\uC5F4\uB85C \uBCC0\uD658\uD569\uB2C8\uB2E4. */\nfunction buildQuery(params: Record<string, unknown>): string {\n return Object.entries(params)\n .filter(([, value]) => value != null)\n .map(\n ([key, value]) =>\n `${encodeURIComponent(key === \"orderBy\" ? \"order_by\" : key)}=${encodeURIComponent(String(value))}`,\n )\n .join(\"&\");\n}\n\nexport const entityServer = new EntityServerClient();\n"],
5
+ "mappings": "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",
6
+ "names": ["useMemo", "xchacha20poly1305", "sha256", "readEnv", "name", "EntityServerClient", "options", "envBaseUrl", "envMagicLen", "token", "length", "email", "password", "data", "refreshToken", "res", "transactionId", "txId", "entity", "seq", "opts", "q", "params", "conditions", "fields", "orderDir", "orderBy", "rest", "queryObj", "buildQuery", "req", "extraHeaders", "historySeq", "pushEntity", "payload", "accountSeq", "deviceId", "pushToken", "platform", "deviceType", "browser", "browserVersion", "pushEnabled", "deviceSeq", "body", "contentType", "requireEncrypted", "isEncrypted", "sliced", "method", "path", "withAuth", "headers", "fetchBody", "plaintext", "encrypted", "buffer", "err", "key", "magic", "nonce", "ciphertext", "result", "value", "entityServer", "useEntityServer", "options", "singleton", "tokenResolver", "baseUrl", "packetMagicLen", "token", "useMemo", "client", "entityServer", "EntityServerClient", "resolvedToken"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "entity-server-client",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "publishConfig": {
package/src/index.ts CHANGED
@@ -87,6 +87,15 @@ export interface EntityServerClientOptions {
87
87
  baseUrl?: string;
88
88
  token?: string;
89
89
  packetMagicLen?: number;
90
+ /**
91
+ * `true`이면 인증된 POST/PUT 요청 바디를 XChaCha20-Poly1305로 암호화합니다.
92
+ *
93
+ * 서버의 `EnablePacketEncryption`이 활성화된 경우 필수로 설정해야 합니다.
94
+ * 로그인(`login()`)·토큰 갱신(`refreshToken()`)은 인증 전 요청이므로 자동으로 건너뜁니다.
95
+ *
96
+ * 기본값: `false`
97
+ */
98
+ encryptRequests?: boolean;
90
99
  }
91
100
 
92
101
  /**
@@ -139,6 +148,7 @@ export class EntityServerClient {
139
148
  private baseUrl: string;
140
149
  private token: string;
141
150
  private packetMagicLen: number;
151
+ private encryptRequests: boolean;
142
152
  private activeTxId: string | null = null;
143
153
 
144
154
  /**
@@ -161,9 +171,10 @@ export class EntityServerClient {
161
171
  this.token = options.token ?? "";
162
172
  this.packetMagicLen =
163
173
  options.packetMagicLen ?? (envMagicLen ? Number(envMagicLen) : 4);
174
+ this.encryptRequests = options.encryptRequests ?? false;
164
175
  }
165
176
 
166
- /** baseUrl, token, packetMagicLen 값을 런타임에 갱신합니다. */
177
+ /** baseUrl, token, packetMagicLen, encryptRequests 값을 런타임에 갱신합니다. */
167
178
  configure(options: Partial<EntityServerClientOptions>): void {
168
179
  if (options.baseUrl) {
169
180
  this.baseUrl = options.baseUrl.replace(/\/$/, "");
@@ -174,6 +185,9 @@ export class EntityServerClient {
174
185
  if (typeof options.packetMagicLen === "number") {
175
186
  this.packetMagicLen = options.packetMagicLen;
176
187
  }
188
+ if (typeof options.encryptRequests === "boolean") {
189
+ this.encryptRequests = options.encryptRequests;
190
+ }
177
191
  }
178
192
 
179
193
  /** 인증 요청에 사용할 JWT Access Token을 설정합니다. */
@@ -524,8 +538,9 @@ export class EntityServerClient {
524
538
  /**
525
539
  * 공통 HTTP 요청 함수입니다.
526
540
  *
527
- * 응답이 `application/octet-stream`이면 자동 복호화하고,
528
- * JSON 응답의 `ok`가 false이면 에러를 던집니다.
541
+ * - `encryptRequests`가 활성화된 인증 요청의 POST 바디를 자동 암호화합니다.
542
+ * - 응답이 `application/octet-stream`이면 자동 복호화합니다.
543
+ * - JSON 응답의 `ok`가 false이면 에러를 던집니다.
529
544
  */
530
545
  private async request<T>(
531
546
  method: string,
@@ -542,10 +557,32 @@ export class EntityServerClient {
542
557
  headers.Authorization = `Bearer ${this.token}`;
543
558
  }
544
559
 
560
+ // 요청 바디 결정: encryptRequests 활성화 시 POST 바디를 암호화합니다.
561
+ // - 로그인/토큰 갱신(withAuth=false)은 암호화하지 않습니다.
562
+ // - GET 은 바디가 없으므로 건너뜁니다.
563
+ let fetchBody: string | Uint8Array | null = null;
564
+ if (body != null) {
565
+ const shouldEncrypt =
566
+ this.encryptRequests &&
567
+ withAuth &&
568
+ this.token &&
569
+ method !== "GET" &&
570
+ method !== "HEAD";
571
+
572
+ if (shouldEncrypt) {
573
+ const plaintext = new TextEncoder().encode(JSON.stringify(body));
574
+ const encrypted = this.encryptPacket(plaintext);
575
+ headers["Content-Type"] = "application/octet-stream";
576
+ fetchBody = encrypted;
577
+ } else {
578
+ fetchBody = JSON.stringify(body);
579
+ }
580
+ }
581
+
545
582
  const res = await fetch(this.baseUrl + path, {
546
583
  method,
547
584
  headers,
548
- ...(body != null ? { body: JSON.stringify(body) } : {}),
585
+ ...(fetchBody != null ? { body: fetchBody as BodyInit } : {}),
549
586
  });
550
587
 
551
588
  const contentType = res.headers.get("Content-Type") ?? "";
@@ -566,6 +603,27 @@ export class EntityServerClient {
566
603
  return data as T;
567
604
  }
568
605
 
606
+ /**
607
+ * 평문 바이트를 XChaCha20-Poly1305로 암호화합니다.
608
+ * 포맷: [random_magic:packetMagicLen][random_nonce:24][ciphertext+tag]
609
+ */
610
+ private encryptPacket(plaintext: Uint8Array): Uint8Array {
611
+ const key = sha256(new TextEncoder().encode(this.token));
612
+ const magic = new Uint8Array(this.packetMagicLen);
613
+ const nonce = new Uint8Array(24);
614
+ crypto.getRandomValues(magic);
615
+ crypto.getRandomValues(nonce);
616
+ const cipher = xchacha20poly1305(key, nonce);
617
+ const ciphertext = cipher.encrypt(plaintext);
618
+ const result = new Uint8Array(
619
+ this.packetMagicLen + 24 + ciphertext.length,
620
+ );
621
+ result.set(magic, 0);
622
+ result.set(nonce, this.packetMagicLen);
623
+ result.set(ciphertext, this.packetMagicLen + 24);
624
+ return result;
625
+ }
626
+
569
627
  /** 서버의 암호화 패킷을 복호화해 JSON 객체로 변환합니다. */
570
628
  private decryptPacket<T>(buffer: ArrayBuffer): T {
571
629
  const key = sha256(new TextEncoder().encode(this.token));