@simplysm/service-client 13.0.100 → 14.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/features/event-client.d.ts.map +1 -1
- package/dist/features/event-client.js +75 -67
- package/dist/features/event-client.js.map +1 -6
- package/dist/features/file-client.js +41 -39
- package/dist/features/file-client.js.map +1 -6
- package/dist/features/orm/orm-client-connector.js +37 -38
- package/dist/features/orm/orm-client-connector.js.map +1 -6
- package/dist/features/orm/orm-client-db-context-executor.d.ts.map +1 -1
- package/dist/features/orm/orm-client-db-context-executor.js +60 -60
- package/dist/features/orm/orm-client-db-context-executor.js.map +1 -6
- package/dist/features/orm/orm-connect-options.js +2 -1
- package/dist/features/orm/orm-connect-options.js.map +1 -6
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -6
- package/dist/protocol/client-protocol-wrapper.d.ts +1 -0
- package/dist/protocol/client-protocol-wrapper.d.ts.map +1 -1
- package/dist/protocol/client-protocol-wrapper.js +92 -70
- package/dist/protocol/client-protocol-wrapper.js.map +1 -6
- package/dist/service-client.d.ts +2 -0
- package/dist/service-client.d.ts.map +1 -1
- package/dist/service-client.js +113 -111
- package/dist/service-client.js.map +1 -6
- package/dist/transport/service-transport.js +121 -104
- package/dist/transport/service-transport.js.map +1 -6
- package/dist/transport/socket-provider.js +180 -155
- package/dist/transport/socket-provider.js.map +1 -6
- package/dist/types/connection-options.d.ts +1 -1
- package/dist/types/connection-options.d.ts.map +1 -1
- package/dist/types/connection-options.js +2 -1
- package/dist/types/connection-options.js.map +1 -6
- package/dist/types/progress.types.d.ts +1 -0
- package/dist/types/progress.types.d.ts.map +1 -1
- package/dist/types/progress.types.js +2 -1
- package/dist/types/progress.types.js.map +1 -6
- package/dist/workers/client-protocol.worker.js +38 -24
- package/dist/workers/client-protocol.worker.js.map +1 -6
- package/package.json +16 -9
- package/src/features/event-client.ts +19 -17
- package/src/features/file-client.ts +5 -5
- package/src/features/orm/orm-client-connector.ts +2 -2
- package/src/features/orm/orm-client-db-context-executor.ts +8 -7
- package/src/index.ts +5 -5
- package/src/protocol/client-protocol-wrapper.ts +24 -19
- package/src/service-client.ts +22 -15
- package/src/transport/service-transport.ts +19 -19
- package/src/transport/socket-provider.ts +38 -38
- package/src/types/connection-options.ts +1 -1
- package/src/types/progress.types.ts +1 -0
- package/src/workers/client-protocol.worker.ts +9 -9
- package/README.md +0 -126
- package/docs/features.md +0 -143
- package/docs/protocol.md +0 -29
- package/docs/service-client.md +0 -93
- package/docs/transport.md +0 -96
- package/docs/types.md +0 -55
|
@@ -2,7 +2,7 @@ export interface ServiceConnectionOptions {
|
|
|
2
2
|
port: number;
|
|
3
3
|
host: string;
|
|
4
4
|
ssl?: boolean;
|
|
5
|
-
/**
|
|
5
|
+
/** 0으로 설정하면 재연결을 비활성화하고 즉시 연결을 끊음 */
|
|
6
6
|
maxReconnectCount?: number;
|
|
7
7
|
}
|
|
8
8
|
//# sourceMappingURL=connection-options.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-options.d.ts","sourceRoot":"","sources":["..\\..\\src\\types\\connection-options.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,
|
|
1
|
+
{"version":3,"file":"connection-options.d.ts","sourceRoot":"","sources":["..\\..\\src\\types\\connection-options.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,qCAAqC;IACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B"}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
export {};
|
|
2
|
+
//# sourceMappingURL=connection-options.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"progress.types.d.ts","sourceRoot":"","sources":["..\\..\\src\\types\\progress.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"progress.types.d.ts","sourceRoot":"","sources":["..\\..\\src\\types\\progress.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC7C,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB"}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
export {};
|
|
2
|
+
//# sourceMappingURL=progress.types.js.map
|
|
@@ -1,30 +1,44 @@
|
|
|
1
|
+
/// <reference lib="webworker" />
|
|
1
2
|
import { createServiceProtocol } from "@simplysm/service-common";
|
|
2
3
|
import { transfer } from "@simplysm/core-common";
|
|
3
4
|
const protocol = createServiceProtocol();
|
|
4
5
|
self.onmessage = (event) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
const { id, type, data } = event.data;
|
|
7
|
+
try {
|
|
8
|
+
let result;
|
|
9
|
+
let transferList = [];
|
|
10
|
+
if (type === "encode") {
|
|
11
|
+
// [Main -> Worker] 인코딩 요청 (data: { uuid, message })
|
|
12
|
+
// message는 이미 Plain Object임 (Structured Clone을 통해 전달)
|
|
13
|
+
const { uuid, message } = data;
|
|
14
|
+
const { chunks } = protocol.encode(uuid, message);
|
|
15
|
+
// Buffer[]는 전송 가능하므로 결과로 반환
|
|
16
|
+
result = chunks;
|
|
17
|
+
// 결과 chunk의 내부 ArrayBuffer를 전송 목록에 추가
|
|
18
|
+
transferList = chunks.map((chunk) => chunk.buffer);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
// [Main -> Worker] 디코딩 요청 (data: Uint8Array)
|
|
22
|
+
// data는 Uint8Array로 전달됨
|
|
23
|
+
const bytes = new Uint8Array(data);
|
|
24
|
+
const decodeResult = protocol.decode(bytes);
|
|
25
|
+
// 결과 객체를 전송 가능한 형태로 변환 (zero-copy 준비)
|
|
26
|
+
const encoded = transfer.encode(decodeResult);
|
|
27
|
+
result = encoded.result;
|
|
28
|
+
transferList = encoded.transferList;
|
|
29
|
+
}
|
|
30
|
+
// [Worker -> Main] 성공 응답
|
|
31
|
+
self.postMessage({ id, type: "success", result }, { transfer: transferList });
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
// [Worker -> Main] 에러 응답
|
|
35
|
+
self.postMessage({
|
|
36
|
+
id,
|
|
37
|
+
type: "error",
|
|
38
|
+
error: err instanceof Error
|
|
39
|
+
? { message: err.message, stack: err.stack }
|
|
40
|
+
: { message: String(err) },
|
|
41
|
+
});
|
|
20
42
|
}
|
|
21
|
-
self.postMessage({ id, type: "success", result }, { transfer: transferList });
|
|
22
|
-
} catch (err) {
|
|
23
|
-
self.postMessage({
|
|
24
|
-
id,
|
|
25
|
-
type: "error",
|
|
26
|
-
error: err instanceof Error ? { message: err.message, stack: err.stack } : { message: String(err) }
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
43
|
};
|
|
30
|
-
//# sourceMappingURL=client-protocol.worker.js.map
|
|
44
|
+
//# sourceMappingURL=client-protocol.worker.js.map
|
|
@@ -1,6 +1 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/workers/client-protocol.worker.ts"],
|
|
4
|
-
"mappings": "AAEA,SAAS,6BAA6B;AACtC,SAAS,gBAAgB;AAEzB,MAAM,WAAW,sBAAsB;AAEvC,KAAK,YAAY,CAAC,UAAwB;AACxC,QAAM,EAAE,IAAI,MAAM,KAAK,IAAI,MAAM;AAMjC,MAAI;AACF,QAAI;AACJ,QAAI,eAA+B,CAAC;AAEpC,QAAI,SAAS,UAAU;AAGrB,YAAM,EAAE,MAAM,QAAQ,IAAI;AAI1B,YAAM,EAAE,OAAO,IAAI,SAAS,OAAO,MAAM,OAAO;AAGhD,eAAS;AAET,qBAAe,OAAO,IAAI,CAAC,UAAU,MAAM,MAAqB;AAAA,IAClE,OAAO;AAGL,YAAM,QAAQ,IAAI,WAAW,IAAmB;AAChD,YAAM,eAAe,SAAS,OAAO,KAAK;AAG1C,YAAM,UAAU,SAAS,OAAO,YAAY;AAC5C,eAAS,QAAQ;AACjB,qBAAe,QAAQ;AAAA,IACzB;AAGA,SAAK,YAAY,EAAE,IAAI,MAAM,WAAW,OAAO,GAAG,EAAE,UAAU,aAAa,CAAC;AAAA,EAC9E,SAAS,KAAK;AAEZ,SAAK,YAAY;AAAA,MACf;AAAA,MACA,MAAM;AAAA,MACN,OACE,eAAe,QACX,EAAE,SAAS,IAAI,SAAS,OAAO,IAAI,MAAM,IACzC,EAAE,SAAS,OAAO,GAAG,EAAE;AAAA,IAC/B,CAAC;AAAA,EACH;AACF;",
|
|
5
|
-
"names": []
|
|
6
|
-
}
|
|
1
|
+
{"version":3,"file":"client-protocol.worker.js","sourceRoot":"","sources":["..\\..\\src\\workers\\client-protocol.worker.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,MAAM,QAAQ,GAAG,qBAAqB,EAAE,CAAC;AAEzC,IAAI,CAAC,SAAS,GAAG,CAAC,KAAmB,EAAE,EAAE;IACvC,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,IAIhC,CAAC;IAEF,IAAI,CAAC;QACH,IAAI,MAAe,CAAC;QACpB,IAAI,YAAY,GAAmB,EAAE,CAAC;QAEtC,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,oDAAoD;YACpD,sDAAsD;YACtD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,IAGzB,CAAC;YACF,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAElD,4BAA4B;YAC5B,MAAM,GAAG,MAAM,CAAC;YAChB,sCAAsC;YACtC,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAqB,CAAC,CAAC;QACpE,CAAC;aAAM,CAAC;YACN,6CAA6C;YAC7C,wBAAwB;YACxB,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,IAAmB,CAAC,CAAC;YAClD,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAE5C,sCAAsC;YACtC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;YAC9C,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YACxB,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QACtC,CAAC;QAED,yBAAyB;QACzB,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;IAChF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,yBAAyB;QACzB,IAAI,CAAC,WAAW,CAAC;YACf,EAAE;YACF,IAAI,EAAE,OAAO;YACb,KAAK,EACH,GAAG,YAAY,KAAK;gBAClB,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE;gBAC5C,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE;SAC/B,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/service-client",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"author": "
|
|
3
|
+
"version": "14.0.1",
|
|
4
|
+
"description": "심플리즘 패키지 - 서비스 (client)",
|
|
5
|
+
"author": "심플리즘",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -14,18 +14,25 @@
|
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
|
-
"src"
|
|
18
|
-
"docs"
|
|
17
|
+
"src"
|
|
19
18
|
],
|
|
20
19
|
"sideEffects": false,
|
|
21
20
|
"dependencies": {
|
|
22
21
|
"consola": "^3.4.2",
|
|
23
|
-
"@simplysm/core-common": "
|
|
24
|
-
"@simplysm/orm-common": "
|
|
25
|
-
"@simplysm/service-common": "
|
|
22
|
+
"@simplysm/core-common": "14.0.1",
|
|
23
|
+
"@simplysm/orm-common": "14.0.1",
|
|
24
|
+
"@simplysm/service-common": "14.0.1"
|
|
26
25
|
},
|
|
27
26
|
"devDependencies": {
|
|
28
27
|
"@types/ws": "^8.18.1",
|
|
29
|
-
"ws": "^8.
|
|
28
|
+
"ws": "^8.20.0"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"ws": "^8"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"ws": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
30
37
|
}
|
|
31
38
|
}
|
|
@@ -38,13 +38,13 @@ export function createEventClient(transport: ServiceTransport): EventClient {
|
|
|
38
38
|
const key = Uuid.generate().toString();
|
|
39
39
|
const eventName = eventDef.eventName;
|
|
40
40
|
|
|
41
|
-
//
|
|
41
|
+
// 서버에 등록 요청 전송
|
|
42
42
|
await transport.send({
|
|
43
43
|
name: "evt:add",
|
|
44
44
|
body: { key, name: eventName, info },
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
//
|
|
47
|
+
// 로컬 맵에 저장 (재연결 시 복구용)
|
|
48
48
|
listenerMap.set(key, {
|
|
49
49
|
eventName,
|
|
50
50
|
info,
|
|
@@ -59,7 +59,7 @@ export function createEventClient(transport: ServiceTransport): EventClient {
|
|
|
59
59
|
try {
|
|
60
60
|
await transport.send({ name: "evt:remove", body: { key } });
|
|
61
61
|
} catch {
|
|
62
|
-
//
|
|
62
|
+
// 서버가 연결 끊김 시 이벤트 리스너를 자동 정리하므로 무시해도 안전함
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -70,7 +70,7 @@ export function createEventClient(transport: ServiceTransport): EventClient {
|
|
|
70
70
|
): Promise<void> {
|
|
71
71
|
const eventName = eventDef.eventName;
|
|
72
72
|
|
|
73
|
-
//
|
|
73
|
+
// 서버에 'gets' 요청을 보내 대상 목록 조회
|
|
74
74
|
const listenerInfos = (await transport.send({
|
|
75
75
|
name: "evt:gets",
|
|
76
76
|
body: { name: eventName },
|
|
@@ -88,21 +88,23 @@ export function createEventClient(transport: ServiceTransport): EventClient {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
//
|
|
91
|
+
// 재연결 시 호출
|
|
92
92
|
async function resubscribeAll(): Promise<void> {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
93
|
+
await Promise.allSettled(
|
|
94
|
+
Array.from(listenerMap.entries()).map(async ([key, value]) => {
|
|
95
|
+
try {
|
|
96
|
+
await transport.send({
|
|
97
|
+
name: "evt:add",
|
|
98
|
+
body: { key, name: value.eventName, info: value.info },
|
|
99
|
+
});
|
|
100
|
+
} catch (err) {
|
|
101
|
+
logger.error("이벤트 리스너 복구 실패", { err, eventName: value.eventName });
|
|
102
|
+
}
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
//
|
|
107
|
+
// 서버 이벤트를 로컬 리스너에 디스패치
|
|
106
108
|
async function executeByKey(keys: string[], data: unknown): Promise<void> {
|
|
107
109
|
for (const key of keys) {
|
|
108
110
|
const entry = listenerMap.get(key);
|
|
@@ -110,7 +112,7 @@ export function createEventClient(transport: ServiceTransport): EventClient {
|
|
|
110
112
|
try {
|
|
111
113
|
await entry.cb(data);
|
|
112
114
|
} catch (err) {
|
|
113
|
-
logger.error("
|
|
115
|
+
logger.error("이벤트 핸들러 에러", { err, eventName: entry.eventName });
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
118
|
}
|
|
@@ -11,12 +11,12 @@ export interface FileClient {
|
|
|
11
11
|
|
|
12
12
|
export function createFileClient(hostUrl: string, clientName: string): FileClient {
|
|
13
13
|
async function download(relPath: string): Promise<Bytes> {
|
|
14
|
-
//
|
|
14
|
+
// URL 생성
|
|
15
15
|
const url = `${hostUrl}${relPath.startsWith("/") ? "" : "/"}${relPath}`;
|
|
16
16
|
|
|
17
17
|
const res = await fetch(url);
|
|
18
18
|
if (!res.ok) {
|
|
19
|
-
throw new Error(
|
|
19
|
+
throw new Error(`다운로드 실패: ${res.status} ${res.statusText}`);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// ArrayBuffer -> Uint8Array
|
|
@@ -32,11 +32,11 @@ export function createFileClient(hostUrl: string, clientName: string): FileClien
|
|
|
32
32
|
|
|
33
33
|
for (const file of fileList) {
|
|
34
34
|
if ("data" in file) {
|
|
35
|
-
//
|
|
35
|
+
// 커스텀 객체 ({ name, data })
|
|
36
36
|
const blob = file.data instanceof Blob ? file.data : new Blob([file.data]);
|
|
37
37
|
formData.append("files", blob, file.name);
|
|
38
38
|
} else {
|
|
39
|
-
//
|
|
39
|
+
// 브라우저 File 객체
|
|
40
40
|
formData.append("files", file, file.name);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -51,7 +51,7 @@ export function createFileClient(hostUrl: string, clientName: string): FileClien
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
if (!res.ok) {
|
|
54
|
-
throw new Error(
|
|
54
|
+
throw new Error(`업로드 실패: ${res.statusText}`);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
return res.json();
|
|
@@ -22,7 +22,7 @@ export function createOrmClientConnector(serviceClient: ServiceClient): OrmClien
|
|
|
22
22
|
const info = await executor.getInfo();
|
|
23
23
|
const database = config.dbContextOpt?.database ?? info.database;
|
|
24
24
|
if (database == null || database === "") {
|
|
25
|
-
throw new Error("database
|
|
25
|
+
throw new Error("database는 필수입니다.");
|
|
26
26
|
}
|
|
27
27
|
return createDbContext(config.dbContextDef, executor, {
|
|
28
28
|
database,
|
|
@@ -44,7 +44,7 @@ export function createOrmClientConnector(serviceClient: ServiceClient): OrmClien
|
|
|
44
44
|
(err.message.includes("a parent row: a foreign key constraint") ||
|
|
45
45
|
err.message.includes("conflicted with the REFERENCE"))
|
|
46
46
|
) {
|
|
47
|
-
throw new Error("
|
|
47
|
+
throw new Error("경고! 연관된 작업으로 인해 작업이 거부되었습니다. 후속 작업을 확인해 주세요.", { cause: err });
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
throw err;
|
|
@@ -34,7 +34,7 @@ export class OrmClientDbContextExecutor implements DbContextExecutor {
|
|
|
34
34
|
|
|
35
35
|
async beginTransaction(isolationLevel?: IsolationLevel): Promise<void> {
|
|
36
36
|
if (this._connId === undefined) {
|
|
37
|
-
throw new Error("
|
|
37
|
+
throw new Error("데이터베이스에 연결되지 않았습니다.");
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
await this._ormService.beginTransaction(this._connId, isolationLevel);
|
|
@@ -42,7 +42,7 @@ export class OrmClientDbContextExecutor implements DbContextExecutor {
|
|
|
42
42
|
|
|
43
43
|
async commitTransaction(): Promise<void> {
|
|
44
44
|
if (this._connId === undefined) {
|
|
45
|
-
throw new Error("
|
|
45
|
+
throw new Error("데이터베이스에 연결되지 않았습니다.");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
await this._ormService.commitTransaction(this._connId);
|
|
@@ -50,7 +50,7 @@ export class OrmClientDbContextExecutor implements DbContextExecutor {
|
|
|
50
50
|
|
|
51
51
|
async rollbackTransaction(): Promise<void> {
|
|
52
52
|
if (this._connId === undefined) {
|
|
53
|
-
throw new Error("
|
|
53
|
+
throw new Error("데이터베이스에 연결되지 않았습니다.");
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
await this._ormService.rollbackTransaction(this._connId);
|
|
@@ -58,10 +58,11 @@ export class OrmClientDbContextExecutor implements DbContextExecutor {
|
|
|
58
58
|
|
|
59
59
|
async close(): Promise<void> {
|
|
60
60
|
if (this._connId === undefined) {
|
|
61
|
-
throw new Error("
|
|
61
|
+
throw new Error("데이터베이스에 연결되지 않았습니다.");
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
await this._ormService.close(this._connId);
|
|
65
|
+
this._connId = undefined;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
async executeDefs<T = Record<string, unknown>>(
|
|
@@ -69,7 +70,7 @@ export class OrmClientDbContextExecutor implements DbContextExecutor {
|
|
|
69
70
|
options?: (ResultMeta | undefined)[],
|
|
70
71
|
): Promise<T[][]> {
|
|
71
72
|
if (this._connId === undefined) {
|
|
72
|
-
throw new Error("
|
|
73
|
+
throw new Error("데이터베이스에 연결되지 않았습니다.");
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
return (await this._ormService.executeDefs(this._connId, defs, options)) as T[][];
|
|
@@ -77,7 +78,7 @@ export class OrmClientDbContextExecutor implements DbContextExecutor {
|
|
|
77
78
|
|
|
78
79
|
async executeParametrized(query: string, params?: unknown[]): Promise<unknown[][]> {
|
|
79
80
|
if (this._connId === undefined) {
|
|
80
|
-
throw new Error("
|
|
81
|
+
throw new Error("데이터베이스에 연결되지 않았습니다.");
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
return this._ormService.executeParametrized(this._connId, query, params);
|
|
@@ -89,7 +90,7 @@ export class OrmClientDbContextExecutor implements DbContextExecutor {
|
|
|
89
90
|
records: Record<string, unknown>[],
|
|
90
91
|
): Promise<void> {
|
|
91
92
|
if (this._connId === undefined) {
|
|
92
|
-
throw new Error("
|
|
93
|
+
throw new Error("데이터베이스에 연결되지 않았습니다.");
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
return this._ormService.bulkInsert(this._connId, tableName, columnDefs, records);
|
package/src/index.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
//
|
|
1
|
+
// 타입
|
|
2
2
|
export * from "./types/connection-options";
|
|
3
3
|
export * from "./types/progress.types";
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// 전송 계층
|
|
6
6
|
export * from "./transport/socket-provider";
|
|
7
7
|
export * from "./transport/service-transport";
|
|
8
8
|
|
|
9
|
-
//
|
|
9
|
+
// 프로토콜
|
|
10
10
|
export * from "./protocol/client-protocol-wrapper";
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// 기능
|
|
13
13
|
export * from "./features/event-client";
|
|
14
14
|
export * from "./features/file-client";
|
|
15
15
|
export * from "./features/orm/orm-connect-options";
|
|
16
16
|
export * from "./features/orm/orm-client-connector";
|
|
17
17
|
export * from "./features/orm/orm-client-db-context-executor";
|
|
18
18
|
|
|
19
|
-
//
|
|
19
|
+
// 메인
|
|
20
20
|
export * from "./service-client";
|
|
@@ -9,19 +9,20 @@ import type {
|
|
|
9
9
|
export interface ClientProtocolWrapper {
|
|
10
10
|
encode(uuid: string, message: ServiceMessage): Promise<{ chunks: Bytes[]; totalSize: number }>;
|
|
11
11
|
decode(bytes: Bytes): Promise<ServiceMessageDecodeResult<ServiceMessage>>;
|
|
12
|
+
dispose(): void;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
//
|
|
15
|
+
// 공유 worker 상태 (싱글턴 패턴)
|
|
15
16
|
let worker: Worker | undefined;
|
|
16
17
|
const workerResolvers = new LazyGcMap<
|
|
17
18
|
string,
|
|
18
19
|
{ resolve: (res: unknown) => void; reject: (err: Error) => void }
|
|
19
20
|
>({
|
|
20
|
-
gcInterval: 5 * 1000, //
|
|
21
|
-
expireTime: 60 * 1000, //
|
|
21
|
+
gcInterval: 5 * 1000, // 5초마다 만료된 항목 확인
|
|
22
|
+
expireTime: 60 * 1000, // 60초 후 만료 (타임아웃)
|
|
22
23
|
onExpire: (key, item) => {
|
|
23
|
-
//
|
|
24
|
-
item.reject(new Error(`Worker
|
|
24
|
+
// 만료 시 reject (메모리 누수 방지에 필수)
|
|
25
|
+
item.reject(new Error(`Worker 작업 시간 초과 (uuid: ${key})`));
|
|
25
26
|
},
|
|
26
27
|
});
|
|
27
28
|
|
|
@@ -40,8 +41,8 @@ function getWorker(): Worker | undefined {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
if (!worker) {
|
|
43
|
-
//
|
|
44
|
-
//
|
|
44
|
+
// 모던 번들러 (Vite/Esbuild/Webpack)가 이 구문을 사용하여 Worker를 별도 파일로 분리/로드함
|
|
45
|
+
// 참고: import.meta.resolve 대신 상대 경로 사용 (Vite 호환성)
|
|
45
46
|
worker = new Worker(new URL("../workers/client-protocol.worker.ts", import.meta.url), {
|
|
46
47
|
type: "module",
|
|
47
48
|
});
|
|
@@ -59,7 +60,7 @@ function getWorker(): Worker | undefined {
|
|
|
59
60
|
if (type === "success") {
|
|
60
61
|
resolver.resolve(result);
|
|
61
62
|
} else {
|
|
62
|
-
const err = new Error(error?.message ?? "
|
|
63
|
+
const err = new Error(error?.message ?? "알 수 없는 worker 에러");
|
|
63
64
|
err.stack = error?.stack;
|
|
64
65
|
resolver.reject(err);
|
|
65
66
|
}
|
|
@@ -71,8 +72,8 @@ function getWorker(): Worker | undefined {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
75
|
+
* Worker에 작업을 위임하고 결과를 대기
|
|
76
|
+
* 참고: workerAvailable이 true일 때만 호출할 것
|
|
76
77
|
*/
|
|
77
78
|
async function runWorker(
|
|
78
79
|
type: "encode" | "decode",
|
|
@@ -83,20 +84,20 @@ async function runWorker(
|
|
|
83
84
|
const id = Uuid.generate().toString();
|
|
84
85
|
|
|
85
86
|
workerResolvers.set(id, { resolve, reject });
|
|
86
|
-
//
|
|
87
|
+
// workerAvailable 확인 후 호출되므로 worker는 항상 존재
|
|
87
88
|
getWorker()!.postMessage({ id, type, data }, { transfer: transferables });
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
export function createClientProtocolWrapper(protocol: ServiceProtocol): ClientProtocolWrapper {
|
|
92
|
-
//
|
|
93
|
+
// 임계값: 30KB
|
|
93
94
|
const SIZE_THRESHOLD = 30 * 1024;
|
|
94
95
|
|
|
95
96
|
function shouldUseWorkerForEncode(msg: ServiceMessage): boolean {
|
|
96
97
|
if (!("body" in msg)) return false;
|
|
97
98
|
const body = msg.body;
|
|
98
99
|
|
|
99
|
-
//
|
|
100
|
+
// Uint8Array가 있거나 배열 길이가 큰 경우 worker 사용
|
|
100
101
|
if (body instanceof Uint8Array) return true;
|
|
101
102
|
if (typeof body === "string" && body.length > SIZE_THRESHOLD) return true;
|
|
102
103
|
if (Array.isArray(body)) {
|
|
@@ -110,14 +111,14 @@ export function createClientProtocolWrapper(protocol: ServiceProtocol): ClientPr
|
|
|
110
111
|
uuid: string,
|
|
111
112
|
message: ServiceMessage,
|
|
112
113
|
): Promise<{ chunks: Bytes[]; totalSize: number }> {
|
|
113
|
-
//
|
|
114
|
+
// Worker가 없거나 데이터가 작으면 메인 스레드에서 처리
|
|
114
115
|
if (!isWorkerAvailable() || !shouldUseWorkerForEncode(message)) {
|
|
115
116
|
return protocol.encode(uuid, message);
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
// [Worker]
|
|
119
|
-
//
|
|
120
|
-
//
|
|
120
|
+
// 인코딩은 객체 전송이 필요하므로 Structured Clone이 발생함.
|
|
121
|
+
// 하지만 메인 스레드에서 JSON.stringify 비용을 오프로드하는 이점이 더 큼.
|
|
121
122
|
return (await runWorker("encode", { uuid, message })) as {
|
|
122
123
|
chunks: Bytes[];
|
|
123
124
|
totalSize: number;
|
|
@@ -127,21 +128,25 @@ export function createClientProtocolWrapper(protocol: ServiceProtocol): ClientPr
|
|
|
127
128
|
async function decode(bytes: Bytes): Promise<ServiceMessageDecodeResult<ServiceMessage>> {
|
|
128
129
|
const totalSize = bytes.length;
|
|
129
130
|
|
|
130
|
-
//
|
|
131
|
+
// Worker가 없거나 데이터가 작으면 메인 스레드에서 처리
|
|
131
132
|
if (!isWorkerAvailable() || totalSize <= SIZE_THRESHOLD) {
|
|
132
133
|
return protocol.decode(bytes);
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
// [Worker]
|
|
136
|
-
// Zero-copy
|
|
137
|
+
// Zero-copy 전송 (버퍼 소유권이 Worker로 이동)
|
|
137
138
|
const rawResult = await runWorker("decode", bytes, [bytes.buffer]);
|
|
138
139
|
|
|
139
|
-
//
|
|
140
|
+
// Worker의 plain object 결과에서 클래스 인스턴스 복원 (DateTime 등)
|
|
140
141
|
return transfer.decode(rawResult) as ServiceMessageDecodeResult<ServiceMessage>;
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
return {
|
|
144
145
|
encode,
|
|
145
146
|
decode,
|
|
147
|
+
dispose() {
|
|
148
|
+
protocol.dispose();
|
|
149
|
+
workerResolvers.dispose();
|
|
150
|
+
},
|
|
146
151
|
};
|
|
147
152
|
}
|