entity-server-client 0.3.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EntityServerClient.d.ts +432 -0
- package/dist/client/base.d.ts +84 -0
- package/dist/client/hmac.d.ts +8 -0
- package/dist/client/packet.d.ts +24 -0
- package/dist/client/request.d.ts +16 -0
- package/dist/client/utils.d.ts +8 -0
- package/dist/hooks/useEntityServer.d.ts +63 -0
- package/{src/index.ts → dist/index.d.ts} +1 -3
- package/dist/index.js +2 -0
- package/dist/index.js.map +7 -0
- package/dist/mixins/alimtalk.d.ts +56 -0
- package/dist/mixins/auth.d.ts +100 -0
- package/dist/mixins/email.d.ts +51 -0
- package/dist/mixins/entity.d.ts +126 -0
- package/dist/mixins/file.d.ts +85 -0
- package/dist/mixins/identity.d.ts +52 -0
- package/dist/mixins/llm.d.ts +94 -0
- package/dist/mixins/pg.d.ts +63 -0
- package/dist/mixins/push.d.ts +101 -0
- package/dist/mixins/sms.d.ts +55 -0
- package/dist/mixins/smtp.d.ts +51 -0
- package/dist/mixins/utils.d.ts +88 -0
- package/dist/packet.d.ts +11 -0
- package/dist/packet.js +2 -0
- package/dist/packet.js.map +7 -0
- package/dist/react.js +2 -0
- package/dist/react.js.map +7 -0
- package/{src/types.ts → dist/types.d.ts} +2 -42
- package/package.json +9 -36
- package/LICENSE +0 -21
- package/README.md +0 -128
- package/build.mjs +0 -36
- package/docs/api/alimtalk.md +0 -62
- package/docs/api/auth.md +0 -256
- package/docs/api/email.md +0 -37
- package/docs/api/entity.md +0 -273
- package/docs/api/file.md +0 -80
- package/docs/api/health.md +0 -47
- package/docs/api/identity.md +0 -32
- package/docs/api/import.md +0 -45
- package/docs/api/packet.md +0 -90
- package/docs/api/pg.md +0 -90
- package/docs/api/push.md +0 -107
- package/docs/api/react.md +0 -141
- package/docs/api/request.md +0 -118
- package/docs/api/setup.md +0 -43
- package/docs/api/sms.md +0 -45
- package/docs/api/smtp.md +0 -33
- package/docs/api/transaction.md +0 -50
- package/docs/api/utils.md +0 -52
- package/docs/apis.md +0 -26
- package/docs/react.md +0 -137
- package/src/EntityServerClient.ts +0 -28
- package/src/client/base.ts +0 -348
- package/src/client/hmac.ts +0 -41
- package/src/client/packet.ts +0 -77
- package/src/client/request.ts +0 -139
- package/src/client/utils.ts +0 -33
- package/src/hooks/useEntityServer.ts +0 -154
- package/src/mixins/auth.ts +0 -143
- package/src/mixins/entity.ts +0 -205
- package/src/mixins/file.ts +0 -99
- package/src/mixins/push.ts +0 -109
- package/src/mixins/smtp.ts +0 -20
- package/src/mixins/utils.ts +0 -106
- package/src/packet.ts +0 -84
- package/tests/packet.test.mjs +0 -50
- package/tsconfig.json +0 -14
- /package/{src/react.ts → dist/react.d.ts} +0 -0
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
EntityServerClient,
|
|
4
|
-
entityServer,
|
|
5
|
-
type EntityListParams,
|
|
6
|
-
type EntityQueryRequest,
|
|
7
|
-
type EntityServerClientOptions,
|
|
8
|
-
} from "../index";
|
|
9
|
-
|
|
10
|
-
export interface UseEntityServerOptions extends EntityServerClientOptions {
|
|
11
|
-
singleton?: boolean;
|
|
12
|
-
tokenResolver?: () => string | undefined | null;
|
|
13
|
-
/**
|
|
14
|
-
* 페이지 새로고침 후 로그인 상태를 복원할 때 사용합니다.
|
|
15
|
-
* 이 값이 있으면 마운트 시 `client.refreshToken()`을 호출해 새 access_token을 발급받습니다.
|
|
16
|
-
* `keepSession: true`와 함께 사용하면 세션 유지 타이머도 재시작됩니다.
|
|
17
|
-
* 갱신 성공 시 `onTokenRefreshed` 콜백이 호출됩니다.
|
|
18
|
-
*/
|
|
19
|
-
resumeSession?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface UseEntityServerResult {
|
|
23
|
-
/** EntityServerClient 인스턴스 (read 전용 메서드 직접 호출 시 사용) */
|
|
24
|
-
client: EntityServerClient;
|
|
25
|
-
/** submit 또는 delete 진행 중 여부 */
|
|
26
|
-
isPending: boolean;
|
|
27
|
-
/** 마지막 mutation 에러 (없으면 null) */
|
|
28
|
-
error: Error | null;
|
|
29
|
-
/** 에러·결과 상태 초기화 */
|
|
30
|
-
reset: () => void;
|
|
31
|
-
/** entity 데이터 생성/수정 (seq 없으면 INSERT, 있으면 UPDATE) */
|
|
32
|
-
submit: (
|
|
33
|
-
entity: string,
|
|
34
|
-
data: Record<string, unknown>,
|
|
35
|
-
opts?: { transactionId?: string; skipHooks?: boolean },
|
|
36
|
-
) => Promise<{ ok: boolean; seq: number }>;
|
|
37
|
-
/** entity 데이터 삭제 */
|
|
38
|
-
del: (
|
|
39
|
-
entity: string,
|
|
40
|
-
seq: number,
|
|
41
|
-
opts?: { transactionId?: string; hard?: boolean; skipHooks?: boolean },
|
|
42
|
-
) => Promise<{ ok: boolean; deleted: number }>;
|
|
43
|
-
/** 커스텀 SQL 조회 */
|
|
44
|
-
query: <T = unknown>(
|
|
45
|
-
entity: string,
|
|
46
|
-
req: EntityQueryRequest,
|
|
47
|
-
) => Promise<{ ok: boolean; data: { items: T[]; count: number } }>;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* React 환경에서 EntityServerClient 인스턴스와 mutation 상태를 반환합니다.
|
|
52
|
-
*
|
|
53
|
-
* - `singleton=true`(기본): 패키지 전역 `entityServer` 인스턴스를 사용합니다.
|
|
54
|
-
* - `singleton=false`: 컴포넌트 스코프의 새 인스턴스를 생성합니다.
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* ```tsx
|
|
58
|
-
* const { submit, del, isPending, error, reset } = useEntityServer();
|
|
59
|
-
*
|
|
60
|
-
* const handleSave = async () => {
|
|
61
|
-
* await submit("account", { name: "홍길동" });
|
|
62
|
-
* };
|
|
63
|
-
* ```
|
|
64
|
-
*/
|
|
65
|
-
export function useEntityServer(
|
|
66
|
-
options: UseEntityServerOptions = {},
|
|
67
|
-
): UseEntityServerResult {
|
|
68
|
-
const {
|
|
69
|
-
singleton = true,
|
|
70
|
-
tokenResolver,
|
|
71
|
-
baseUrl,
|
|
72
|
-
token,
|
|
73
|
-
resumeSession,
|
|
74
|
-
} = options;
|
|
75
|
-
|
|
76
|
-
const [isPending, setIsPending] = useState(false);
|
|
77
|
-
const [error, setError] = useState<Error | null>(null);
|
|
78
|
-
|
|
79
|
-
// 언마운트 후 setState 방지
|
|
80
|
-
const mountedRef = useRef(true);
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
mountedRef.current = true;
|
|
83
|
-
return () => {
|
|
84
|
-
mountedRef.current = false;
|
|
85
|
-
};
|
|
86
|
-
}, []);
|
|
87
|
-
|
|
88
|
-
// 새로고침 후 로그인 상태 복원: resumeSession이 있으면 마운트 시 refreshToken() 호출
|
|
89
|
-
const resumeTokenRef = useRef(resumeSession);
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
const storedRefreshToken = resumeTokenRef.current;
|
|
92
|
-
if (!storedRefreshToken) return;
|
|
93
|
-
client.refreshToken(storedRefreshToken).catch(() => {
|
|
94
|
-
// refresh_token 만료 등 — onSessionExpired 콜백이 이미 처리
|
|
95
|
-
});
|
|
96
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
97
|
-
}, []);
|
|
98
|
-
|
|
99
|
-
const client = useMemo(() => {
|
|
100
|
-
const c = singleton
|
|
101
|
-
? entityServer
|
|
102
|
-
: new EntityServerClient({ baseUrl, token });
|
|
103
|
-
|
|
104
|
-
if (singleton) {
|
|
105
|
-
c.configure({ baseUrl, token });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const resolvedToken = tokenResolver?.();
|
|
109
|
-
if (typeof resolvedToken === "string") {
|
|
110
|
-
c.setToken(resolvedToken);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return c;
|
|
114
|
-
}, [singleton, tokenResolver, baseUrl, token]);
|
|
115
|
-
|
|
116
|
-
const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {
|
|
117
|
-
if (mountedRef.current) {
|
|
118
|
-
setIsPending(true);
|
|
119
|
-
setError(null);
|
|
120
|
-
}
|
|
121
|
-
try {
|
|
122
|
-
const result = await fn();
|
|
123
|
-
return result;
|
|
124
|
-
} catch (err) {
|
|
125
|
-
const e = err instanceof Error ? err : new Error(String(err));
|
|
126
|
-
if (mountedRef.current) setError(e);
|
|
127
|
-
throw e;
|
|
128
|
-
} finally {
|
|
129
|
-
if (mountedRef.current) setIsPending(false);
|
|
130
|
-
}
|
|
131
|
-
}, []);
|
|
132
|
-
|
|
133
|
-
const submit = useCallback<UseEntityServerResult["submit"]>(
|
|
134
|
-
(entity, data, opts) => run(() => client.submit(entity, data, opts)),
|
|
135
|
-
[client, run],
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
const del = useCallback<UseEntityServerResult["del"]>(
|
|
139
|
-
(entity, seq, opts) => run(() => client.delete(entity, seq, opts)),
|
|
140
|
-
[client, run],
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
const query = useCallback<UseEntityServerResult["query"]>(
|
|
144
|
-
(entity, req) => run(() => client.query(entity, req)),
|
|
145
|
-
[client, run],
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
const reset = useCallback(() => {
|
|
149
|
-
setIsPending(false);
|
|
150
|
-
setError(null);
|
|
151
|
-
}, []);
|
|
152
|
-
|
|
153
|
-
return { client, isPending, error, reset, submit, del, query };
|
|
154
|
-
}
|
package/src/mixins/auth.ts
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import type { GConstructor, EntityServerClientBase } from "../client/base";
|
|
2
|
-
|
|
3
|
-
export function AuthMixin<TBase extends GConstructor<EntityServerClientBase>>(
|
|
4
|
-
Base: TBase,
|
|
5
|
-
) {
|
|
6
|
-
return class AuthMixinClass extends Base {
|
|
7
|
-
// ─── 인증 ─────────────────────────────────────────────────────────────
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 서버 헬스 체크를 수행하고 패킷 암호화 활성 여부를 자동으로 감지합니다.
|
|
11
|
-
*
|
|
12
|
-
* 서버가 `packet_encryption: true`를 응답하면 이후 모든 요청에 암호화가 자동 적용됩니다.
|
|
13
|
-
*
|
|
14
|
-
* ```ts
|
|
15
|
-
* await client.checkHealth();
|
|
16
|
-
* await client.login(email, password);
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
async checkHealth(): Promise<{
|
|
20
|
-
ok: boolean;
|
|
21
|
-
packet_encryption?: boolean;
|
|
22
|
-
packet_mode?: string;
|
|
23
|
-
packet_token?: string;
|
|
24
|
-
}> {
|
|
25
|
-
const res = await fetch(`${this.baseUrl}/v1/health`, {
|
|
26
|
-
signal: AbortSignal.timeout(3000),
|
|
27
|
-
credentials: "include",
|
|
28
|
-
});
|
|
29
|
-
const data = (await res.json()) as {
|
|
30
|
-
ok: boolean;
|
|
31
|
-
packet_encryption?: boolean;
|
|
32
|
-
packet_mode?: string;
|
|
33
|
-
packet_token?: string;
|
|
34
|
-
};
|
|
35
|
-
if (data.packet_encryption) this.encryptRequests = true;
|
|
36
|
-
if (typeof data.packet_token === "string") {
|
|
37
|
-
this.anonymousPacketToken = data.packet_token;
|
|
38
|
-
}
|
|
39
|
-
return data;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** 로그인 후 `access_token`을 내부 상태에 저장합니다. */
|
|
43
|
-
async login(
|
|
44
|
-
email: string,
|
|
45
|
-
password: string,
|
|
46
|
-
): Promise<{
|
|
47
|
-
access_token: string;
|
|
48
|
-
refresh_token: string;
|
|
49
|
-
expires_in: number;
|
|
50
|
-
force_password_change?: boolean;
|
|
51
|
-
password_expired?: boolean;
|
|
52
|
-
password_expires_in_days?: number;
|
|
53
|
-
}> {
|
|
54
|
-
const data = await this._request<{
|
|
55
|
-
data: {
|
|
56
|
-
access_token: string;
|
|
57
|
-
refresh_token: string;
|
|
58
|
-
expires_in: number;
|
|
59
|
-
force_password_change?: boolean;
|
|
60
|
-
password_expired?: boolean;
|
|
61
|
-
password_expires_in_days?: number;
|
|
62
|
-
};
|
|
63
|
-
}>("POST", "/v1/auth/login", { email, passwd: password }, false);
|
|
64
|
-
this.token = data.data.access_token;
|
|
65
|
-
if (this.keepSession)
|
|
66
|
-
this._scheduleKeepSession(
|
|
67
|
-
data.data.refresh_token,
|
|
68
|
-
data.data.expires_in,
|
|
69
|
-
(rt) => this.refreshToken(rt),
|
|
70
|
-
);
|
|
71
|
-
return data.data;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** Refresh Token으로 Access Token을 재발급받아 내부 토큰을 교체합니다. */
|
|
75
|
-
async refreshToken(
|
|
76
|
-
refreshToken: string,
|
|
77
|
-
): Promise<{ access_token: string; expires_in: number }> {
|
|
78
|
-
const data = await this._request<{
|
|
79
|
-
data: { access_token: string; expires_in: number };
|
|
80
|
-
}>(
|
|
81
|
-
"POST",
|
|
82
|
-
"/v1/auth/refresh",
|
|
83
|
-
{ refresh_token: refreshToken },
|
|
84
|
-
false,
|
|
85
|
-
);
|
|
86
|
-
this.token = data.data.access_token;
|
|
87
|
-
if (this.keepSession)
|
|
88
|
-
this._scheduleKeepSession(
|
|
89
|
-
refreshToken,
|
|
90
|
-
data.data.expires_in,
|
|
91
|
-
(rt) => this.refreshToken(rt),
|
|
92
|
-
);
|
|
93
|
-
return data.data;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* 서버에 로그아웃을 요청하고 내부 토큰을 초기화합니다.
|
|
98
|
-
* refresh_token을 서버에 전달해 무효화합니다.
|
|
99
|
-
*/
|
|
100
|
-
async logout(refreshToken: string): Promise<{ ok: boolean }> {
|
|
101
|
-
this.stopKeepSession();
|
|
102
|
-
const data = await this._request<{ ok: boolean }>(
|
|
103
|
-
"POST",
|
|
104
|
-
"/v1/auth/logout",
|
|
105
|
-
{ refresh_token: refreshToken },
|
|
106
|
-
false,
|
|
107
|
-
);
|
|
108
|
-
this.token = "";
|
|
109
|
-
return data;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** 현재 로그인된 사용자 정보를 반환합니다. */
|
|
113
|
-
me<T = Record<string, unknown>>(): Promise<{ ok: boolean; data: T }> {
|
|
114
|
-
return this._request("GET", "/v1/auth/me");
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** 회원 탈퇴를 요청합니다. */
|
|
118
|
-
withdraw(passwd?: string): Promise<{ ok: boolean }> {
|
|
119
|
-
return this._request(
|
|
120
|
-
"POST",
|
|
121
|
-
"/v1/auth/withdraw",
|
|
122
|
-
passwd ? { passwd } : {},
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* 휴면 계정을 재활성화합니다.
|
|
128
|
-
* 비밀번호 또는 OAuth(provider + code)로 본인 확인합니다.
|
|
129
|
-
*/
|
|
130
|
-
reactivate(params: {
|
|
131
|
-
email: string;
|
|
132
|
-
passwd?: string;
|
|
133
|
-
provider?: string;
|
|
134
|
-
code?: string;
|
|
135
|
-
}): Promise<{
|
|
136
|
-
access_token: string;
|
|
137
|
-
refresh_token: string;
|
|
138
|
-
expires_in: number;
|
|
139
|
-
}> {
|
|
140
|
-
return this._request("POST", "/v1/auth/reactivate", params, false);
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
}
|
package/src/mixins/entity.ts
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
EntityHistoryRecord,
|
|
3
|
-
EntityListParams,
|
|
4
|
-
EntityListResult,
|
|
5
|
-
EntityQueryRequest,
|
|
6
|
-
} from "../types";
|
|
7
|
-
import { buildQuery } from "../client/utils";
|
|
8
|
-
import type { GConstructor, EntityServerClientBase } from "../client/base";
|
|
9
|
-
|
|
10
|
-
export function EntityMixin<TBase extends GConstructor<EntityServerClientBase>>(
|
|
11
|
-
Base: TBase,
|
|
12
|
-
) {
|
|
13
|
-
return class EntityMixinClass extends Base {
|
|
14
|
-
// ─── 트랜잭션 ─────────────────────────────────────────────────────────
|
|
15
|
-
|
|
16
|
-
/** 트랜잭션을 시작하고 활성 트랜잭션 ID를 저장합니다. */
|
|
17
|
-
async transStart(): Promise<string> {
|
|
18
|
-
const res = await this._request<{
|
|
19
|
-
ok: boolean;
|
|
20
|
-
transaction_id: string;
|
|
21
|
-
}>("POST", "/v1/transaction/start", undefined, false);
|
|
22
|
-
this.activeTxId = res.transaction_id;
|
|
23
|
-
return this.activeTxId;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** 활성 트랜잭션(또는 전달된 transactionId)을 롤백합니다. */
|
|
27
|
-
transRollback(transactionId?: string): Promise<{ ok: boolean }> {
|
|
28
|
-
const txId = transactionId ?? this.activeTxId;
|
|
29
|
-
if (!txId)
|
|
30
|
-
return Promise.reject(
|
|
31
|
-
new Error(
|
|
32
|
-
"No active transaction. Call transStart() first.",
|
|
33
|
-
),
|
|
34
|
-
);
|
|
35
|
-
this.activeTxId = null;
|
|
36
|
-
return this._request("POST", `/v1/transaction/rollback/${txId}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 활성 트랜잭션(또는 전달된 transactionId)을 커밋합니다.
|
|
41
|
-
*
|
|
42
|
-
* @returns `results` 배열: commit된 각 작업의 `entity`, `action`, `seq`
|
|
43
|
-
*/
|
|
44
|
-
transCommit(transactionId?: string): Promise<{
|
|
45
|
-
ok: boolean;
|
|
46
|
-
results: Array<{ entity: string; action: string; seq: number }>;
|
|
47
|
-
}> {
|
|
48
|
-
const txId = transactionId ?? this.activeTxId;
|
|
49
|
-
if (!txId)
|
|
50
|
-
return Promise.reject(
|
|
51
|
-
new Error(
|
|
52
|
-
"No active transaction. Call transStart() first.",
|
|
53
|
-
),
|
|
54
|
-
);
|
|
55
|
-
this.activeTxId = null;
|
|
56
|
-
return this._request("POST", `/v1/transaction/commit/${txId}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ─── 엔티티 CRUD ──────────────────────────────────────────────────────
|
|
60
|
-
|
|
61
|
-
/** 시퀀스 ID로 엔티티 단건을 조회합니다. */
|
|
62
|
-
get<T = unknown>(
|
|
63
|
-
entity: string,
|
|
64
|
-
seq: number,
|
|
65
|
-
opts: { skipHooks?: boolean } = {},
|
|
66
|
-
): Promise<{ ok: boolean; data: T }> {
|
|
67
|
-
const q = opts.skipHooks ? "?skipHooks=true" : "";
|
|
68
|
-
return this._request("GET", `/v1/entity/${entity}/${seq}${q}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** 조건으로 엔티티 단건을 조회합니다. data 컬럼을 완전히 복호화하여 반환합니다. */
|
|
72
|
-
find<T = unknown>(
|
|
73
|
-
entity: string,
|
|
74
|
-
conditions?: Record<string, unknown>,
|
|
75
|
-
opts: { skipHooks?: boolean } = {},
|
|
76
|
-
): Promise<{ ok: boolean; data: T }> {
|
|
77
|
-
const q = opts.skipHooks ? "?skipHooks=true" : "";
|
|
78
|
-
return this._request(
|
|
79
|
-
"POST",
|
|
80
|
-
`/v1/entity/${entity}/find${q}`,
|
|
81
|
-
conditions ?? {},
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** 페이지네이션/정렬/필터 조건으로 엔티티 목록을 조회합니다. */
|
|
86
|
-
list<T = unknown>(
|
|
87
|
-
entity: string,
|
|
88
|
-
params: EntityListParams = {},
|
|
89
|
-
): Promise<{ ok: boolean; data: EntityListResult<T> }> {
|
|
90
|
-
const { conditions, fields, orderDir, orderBy, ...rest } = params;
|
|
91
|
-
const queryObj: Record<string, unknown> = {
|
|
92
|
-
page: 1,
|
|
93
|
-
limit: 20,
|
|
94
|
-
...rest,
|
|
95
|
-
};
|
|
96
|
-
if (orderBy)
|
|
97
|
-
queryObj.orderBy =
|
|
98
|
-
orderDir === "DESC" ? `-${orderBy}` : orderBy;
|
|
99
|
-
if (fields?.length) queryObj.fields = fields.join(",");
|
|
100
|
-
return this._request(
|
|
101
|
-
"POST",
|
|
102
|
-
`/v1/entity/${entity}/list?${buildQuery(queryObj)}`,
|
|
103
|
-
conditions ?? {},
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* 엔티티 총 건수를 조회합니다.
|
|
109
|
-
*
|
|
110
|
-
* @param conditions 필터 조건 (예: `{ status: "active" }`)
|
|
111
|
-
*/
|
|
112
|
-
count(
|
|
113
|
-
entity: string,
|
|
114
|
-
conditions?: Record<string, unknown>,
|
|
115
|
-
): Promise<{ ok: boolean; count: number }> {
|
|
116
|
-
return this._request(
|
|
117
|
-
"POST",
|
|
118
|
-
`/v1/entity/${entity}/count`,
|
|
119
|
-
conditions ?? {},
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* 커스텀 SQL로 엔티티를 조회합니다.
|
|
125
|
-
*
|
|
126
|
-
* SELECT 전용이며 인덱스 테이블만 조회 가능합니다. JOIN 지원.
|
|
127
|
-
*/
|
|
128
|
-
query<T = unknown>(
|
|
129
|
-
entity: string,
|
|
130
|
-
req: EntityQueryRequest,
|
|
131
|
-
): Promise<{ ok: boolean; data: { items: T[]; count: number } }> {
|
|
132
|
-
return this._request("POST", `/v1/entity/${entity}/query`, req);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/** 엔티티 데이터를 생성/수정(Submit)합니다. `seq`가 없으면 INSERT, 있으면 UPDATE입니다. */
|
|
136
|
-
submit(
|
|
137
|
-
entity: string,
|
|
138
|
-
data: Record<string, unknown>,
|
|
139
|
-
opts: { transactionId?: string; skipHooks?: boolean } = {},
|
|
140
|
-
): Promise<{ ok: boolean; seq: number }> {
|
|
141
|
-
const txId = opts.transactionId ?? this.activeTxId;
|
|
142
|
-
const extraHeaders = txId
|
|
143
|
-
? { "X-Transaction-ID": txId }
|
|
144
|
-
: undefined;
|
|
145
|
-
const q = opts.skipHooks ? "?skipHooks=true" : "";
|
|
146
|
-
return this._request(
|
|
147
|
-
"POST",
|
|
148
|
-
`/v1/entity/${entity}/submit${q}`,
|
|
149
|
-
data,
|
|
150
|
-
true,
|
|
151
|
-
extraHeaders,
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** 시퀀스 ID로 엔티티를 삭제합니다(`hard=true`면 하드 삭제, 기본은 소프트 삭제). */
|
|
156
|
-
delete(
|
|
157
|
-
entity: string,
|
|
158
|
-
seq: number,
|
|
159
|
-
opts: {
|
|
160
|
-
transactionId?: string;
|
|
161
|
-
hard?: boolean;
|
|
162
|
-
skipHooks?: boolean;
|
|
163
|
-
} = {},
|
|
164
|
-
): Promise<{ ok: boolean; deleted: number }> {
|
|
165
|
-
const params = new URLSearchParams();
|
|
166
|
-
if (opts.hard) params.set("hard", "true");
|
|
167
|
-
if (opts.skipHooks) params.set("skipHooks", "true");
|
|
168
|
-
const q = params.size ? `?${params}` : "";
|
|
169
|
-
const txId = opts.transactionId ?? this.activeTxId;
|
|
170
|
-
const extraHeaders = txId
|
|
171
|
-
? { "X-Transaction-ID": txId }
|
|
172
|
-
: undefined;
|
|
173
|
-
return this._request(
|
|
174
|
-
"POST",
|
|
175
|
-
`/v1/entity/${entity}/delete/${seq}${q}`,
|
|
176
|
-
undefined,
|
|
177
|
-
true,
|
|
178
|
-
extraHeaders,
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** 엔티티 단건의 변경 이력을 조회합니다. */
|
|
183
|
-
history<T = unknown>(
|
|
184
|
-
entity: string,
|
|
185
|
-
seq: number,
|
|
186
|
-
params: Pick<EntityListParams, "page" | "limit"> = {},
|
|
187
|
-
): Promise<{
|
|
188
|
-
ok: boolean;
|
|
189
|
-
data: EntityListResult<EntityHistoryRecord<T>>;
|
|
190
|
-
}> {
|
|
191
|
-
return this._request(
|
|
192
|
-
"GET",
|
|
193
|
-
`/v1/entity/${entity}/history/${seq}?${buildQuery({ page: 1, limit: 50, ...params })}`,
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** 특정 이력 시점으로 엔티티를 롤백합니다. */
|
|
198
|
-
rollback(entity: string, historySeq: number): Promise<{ ok: boolean }> {
|
|
199
|
-
return this._request(
|
|
200
|
-
"POST",
|
|
201
|
-
`/v1/entity/${entity}/rollback/${historySeq}`,
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
}
|
package/src/mixins/file.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import type { FileMeta, FileUploadOptions } from "../types";
|
|
2
|
-
import type { GConstructor, EntityServerClientBase } from "../client/base";
|
|
3
|
-
|
|
4
|
-
export function FileMixin<TBase extends GConstructor<EntityServerClientBase>>(
|
|
5
|
-
Base: TBase,
|
|
6
|
-
) {
|
|
7
|
-
return class FileMixinClass extends Base {
|
|
8
|
-
// ─── 파일 관리 ────────────────────────────────────────────────────────
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 파일을 업로드합니다. (multipart/form-data)
|
|
12
|
-
*
|
|
13
|
-
* ```ts
|
|
14
|
-
* const input = document.querySelector('input[type="file"]');
|
|
15
|
-
* const result = await client.fileUpload("product", input.files[0]);
|
|
16
|
-
* console.log(result.data.uuid);
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
async fileUpload(
|
|
20
|
-
entity: string,
|
|
21
|
-
file: File | Blob,
|
|
22
|
-
opts: FileUploadOptions = {},
|
|
23
|
-
): Promise<{ ok: boolean; uuid: string; data: FileMeta }> {
|
|
24
|
-
const form = new FormData();
|
|
25
|
-
form.append(
|
|
26
|
-
"file",
|
|
27
|
-
file,
|
|
28
|
-
file instanceof File ? file.name : "upload",
|
|
29
|
-
);
|
|
30
|
-
if (opts.refSeq != null)
|
|
31
|
-
form.append("ref_seq", String(opts.refSeq));
|
|
32
|
-
if (opts.isPublic != null)
|
|
33
|
-
form.append("is_public", opts.isPublic ? "true" : "false");
|
|
34
|
-
return this._requestForm(
|
|
35
|
-
"POST",
|
|
36
|
-
`/v1/files/${entity}/upload`,
|
|
37
|
-
form,
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** 파일을 다운로드합니다. `ArrayBuffer`를 반환합니다. */
|
|
42
|
-
fileDownload(entity: string, uuid: string): Promise<ArrayBuffer> {
|
|
43
|
-
return this._requestBinary(
|
|
44
|
-
"POST",
|
|
45
|
-
`/v1/files/${entity}/download/${uuid}`,
|
|
46
|
-
{},
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** 파일을 삭제합니다. */
|
|
51
|
-
fileDelete(
|
|
52
|
-
entity: string,
|
|
53
|
-
uuid: string,
|
|
54
|
-
): Promise<{ ok: boolean; uuid: string; deleted: boolean }> {
|
|
55
|
-
return this._request(
|
|
56
|
-
"POST",
|
|
57
|
-
`/v1/files/${entity}/delete/${uuid}`,
|
|
58
|
-
{},
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** 엔티티에 연결된 파일 목록을 조회합니다. */
|
|
63
|
-
fileList(
|
|
64
|
-
entity: string,
|
|
65
|
-
opts: { refSeq?: number } = {},
|
|
66
|
-
): Promise<{
|
|
67
|
-
ok: boolean;
|
|
68
|
-
data: { items: FileMeta[]; total: number };
|
|
69
|
-
}> {
|
|
70
|
-
return this._request(
|
|
71
|
-
"POST",
|
|
72
|
-
`/v1/files/${entity}/list`,
|
|
73
|
-
opts.refSeq ? { ref_seq: opts.refSeq } : {},
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** 파일 메타 정보를 조회합니다. */
|
|
78
|
-
fileMeta(
|
|
79
|
-
entity: string,
|
|
80
|
-
uuid: string,
|
|
81
|
-
): Promise<{ ok: boolean; data: FileMeta }> {
|
|
82
|
-
return this._request(
|
|
83
|
-
"POST",
|
|
84
|
-
`/v1/files/${entity}/meta/${uuid}`,
|
|
85
|
-
{},
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** 임시 파일 접근 토큰을 발급합니다. */
|
|
90
|
-
fileToken(uuid: string): Promise<{ ok: boolean; token: string }> {
|
|
91
|
-
return this._request("POST", `/v1/files/token/${uuid}`, {});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** 파일 인라인 뷰 URL을 반환합니다. (fetch 없음, URL 조합만) */
|
|
95
|
-
fileUrl(uuid: string): string {
|
|
96
|
-
return `${this.baseUrl}/v1/files/${uuid}`;
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
}
|