@simplysm/service-client 13.0.99 → 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.
Files changed (55) hide show
  1. package/dist/features/event-client.d.ts.map +1 -1
  2. package/dist/features/event-client.js +75 -67
  3. package/dist/features/event-client.js.map +1 -6
  4. package/dist/features/file-client.js +41 -39
  5. package/dist/features/file-client.js.map +1 -6
  6. package/dist/features/orm/orm-client-connector.js +37 -38
  7. package/dist/features/orm/orm-client-connector.js.map +1 -6
  8. package/dist/features/orm/orm-client-db-context-executor.d.ts.map +1 -1
  9. package/dist/features/orm/orm-client-db-context-executor.js +60 -60
  10. package/dist/features/orm/orm-client-db-context-executor.js.map +1 -6
  11. package/dist/features/orm/orm-connect-options.js +2 -1
  12. package/dist/features/orm/orm-connect-options.js.map +1 -6
  13. package/dist/index.js +6 -1
  14. package/dist/index.js.map +1 -6
  15. package/dist/protocol/client-protocol-wrapper.d.ts +1 -0
  16. package/dist/protocol/client-protocol-wrapper.d.ts.map +1 -1
  17. package/dist/protocol/client-protocol-wrapper.js +92 -70
  18. package/dist/protocol/client-protocol-wrapper.js.map +1 -6
  19. package/dist/service-client.d.ts +2 -0
  20. package/dist/service-client.d.ts.map +1 -1
  21. package/dist/service-client.js +113 -111
  22. package/dist/service-client.js.map +1 -6
  23. package/dist/transport/service-transport.js +121 -104
  24. package/dist/transport/service-transport.js.map +1 -6
  25. package/dist/transport/socket-provider.js +180 -155
  26. package/dist/transport/socket-provider.js.map +1 -6
  27. package/dist/types/connection-options.d.ts +1 -1
  28. package/dist/types/connection-options.d.ts.map +1 -1
  29. package/dist/types/connection-options.js +2 -1
  30. package/dist/types/connection-options.js.map +1 -6
  31. package/dist/types/progress.types.d.ts +1 -0
  32. package/dist/types/progress.types.d.ts.map +1 -1
  33. package/dist/types/progress.types.js +2 -1
  34. package/dist/types/progress.types.js.map +1 -6
  35. package/dist/workers/client-protocol.worker.js +38 -24
  36. package/dist/workers/client-protocol.worker.js.map +1 -6
  37. package/package.json +16 -9
  38. package/src/features/event-client.ts +19 -17
  39. package/src/features/file-client.ts +5 -5
  40. package/src/features/orm/orm-client-connector.ts +2 -2
  41. package/src/features/orm/orm-client-db-context-executor.ts +8 -7
  42. package/src/index.ts +5 -5
  43. package/src/protocol/client-protocol-wrapper.ts +24 -19
  44. package/src/service-client.ts +22 -15
  45. package/src/transport/service-transport.ts +19 -19
  46. package/src/transport/socket-provider.ts +38 -38
  47. package/src/types/connection-options.ts +1 -1
  48. package/src/types/progress.types.ts +1 -0
  49. package/src/workers/client-protocol.worker.ts +9 -9
  50. package/README.md +0 -126
  51. package/docs/features.md +0 -143
  52. package/docs/protocol.md +0 -29
  53. package/docs/service-client.md +0 -93
  54. package/docs/transport.md +0 -96
  55. 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
- /** Set to 0 to disable reconnect; disconnects immediately */
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,6DAA6D;IAC7D,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B"}
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
- //# sourceMappingURL=connection-options.js.map
1
+ export {};
2
+ //# sourceMappingURL=connection-options.js.map
@@ -1,6 +1 @@
1
- {
2
- "version": 3,
3
- "sources": [],
4
- "mappings": "",
5
- "names": []
6
- }
1
+ {"version":3,"file":"connection-options.js","sourceRoot":"","sources":["..\\..\\src\\types\\connection-options.ts"],"names":[],"mappings":""}
@@ -1,6 +1,7 @@
1
1
  export interface ServiceProgress {
2
2
  request?: (s: ServiceProgressState) => void;
3
3
  response?: (s: ServiceProgressState) => void;
4
+ server?: (s: ServiceProgressState) => void;
4
5
  }
5
6
  export interface ServiceProgressState {
6
7
  uuid: string;
@@ -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;CAC9C;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB"}
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
- //# sourceMappingURL=progress.types.js.map
1
+ export {};
2
+ //# sourceMappingURL=progress.types.js.map
@@ -1,6 +1 @@
1
- {
2
- "version": 3,
3
- "sources": [],
4
- "mappings": "",
5
- "names": []
6
- }
1
+ {"version":3,"file":"progress.types.js","sourceRoot":"","sources":["..\\..\\src\\types\\progress.types.ts"],"names":[],"mappings":""}
@@ -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
- const { id, type, data } = event.data;
6
- try {
7
- let result;
8
- let transferList = [];
9
- if (type === "encode") {
10
- const { uuid, message } = data;
11
- const { chunks } = protocol.encode(uuid, message);
12
- result = chunks;
13
- transferList = chunks.map((chunk) => chunk.buffer);
14
- } else {
15
- const bytes = new Uint8Array(data);
16
- const decodeResult = protocol.decode(bytes);
17
- const encoded = transfer.encode(decodeResult);
18
- result = encoded.result;
19
- transferList = encoded.transferList;
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": "13.0.99",
4
- "description": "Simplysm package - Service module (client)",
5
- "author": "simplysm",
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": "13.0.99",
24
- "@simplysm/service-common": "13.0.99",
25
- "@simplysm/orm-common": "13.0.99"
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.19.0"
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
- // Send registration request to server
41
+ // 서버에 등록 요청 전송
42
42
  await transport.send({
43
43
  name: "evt:add",
44
44
  body: { key, name: eventName, info },
45
45
  });
46
46
 
47
- // Store in local map (for recovery on reconnect)
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
- // Server auto-cleans event listeners on disconnect; safe to ignore
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
- // Send 'gets' request to server to obtain targets
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
- // Called on reconnect
91
+ // 재연결 호출
92
92
  async function resubscribeAll(): Promise<void> {
93
- for (const [key, value] of listenerMap.entries()) {
94
- try {
95
- await transport.send({
96
- name: "evt:add",
97
- body: { key, name: value.eventName, info: value.info },
98
- });
99
- } catch (err) {
100
- logger.error("Failed to recover event listener", { err, eventName: value.eventName });
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
- // Dispatch server events to local listeners
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("Event handler error", { err, eventName: entry.eventName });
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
- // Build URL
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(`Download failed: ${res.status} ${res.statusText}`);
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
- // Custom object ({ name, data })
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
- // Browser File object
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(`Upload failed: ${res.statusText}`);
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 is required");
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("Warning! Operation rejected due to related operations. Please check subsequent operations.", { cause: err });
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("Not connected to the database.");
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("Not connected to the database.");
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("Not connected to the database.");
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("Not connected to the database.");
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("Not connected to the database.");
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("Not connected to the database.");
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("Not connected to the database.");
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
- // Types
1
+ // 타입
2
2
  export * from "./types/connection-options";
3
3
  export * from "./types/progress.types";
4
4
 
5
- // Transport
5
+ // 전송 계층
6
6
  export * from "./transport/socket-provider";
7
7
  export * from "./transport/service-transport";
8
8
 
9
- // Protocol
9
+ // 프로토콜
10
10
  export * from "./protocol/client-protocol-wrapper";
11
11
 
12
- // Features
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
- // Main
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
- // Shared worker state (singleton pattern)
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, // Check for expired entries every 5s
21
- expireTime: 60 * 1000, // Expire after 60s (timeout)
21
+ gcInterval: 5 * 1000, // 5초마다 만료된 항목 확인
22
+ expireTime: 60 * 1000, // 60초 만료 (타임아웃)
22
23
  onExpire: (key, item) => {
23
- // Reject on expiry (critical for preventing memory leaks)
24
- item.reject(new Error(`Worker task timed out (uuid: ${key})`));
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
- // Modern bundlers (Vite/Esbuild/Webpack) use this syntax to split/load the Worker as a separate file
44
- // Note: use relative path instead of import.meta.resolve (Vite compatibility)
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 ?? "Unknown worker error");
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
- * Delegate work to Worker and await result
75
- * Note: only call when workerAvailable is true
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
- // Called after workerAvailable check, so worker always exists
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
- // Threshold: 30KB
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
- // Use worker if Uint8Array is present or array length is large
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
- // Process on main thread if no Worker or small data
114
+ // Worker가 없거나 데이터가 작으면 메인 스레드에서 처리
114
115
  if (!isWorkerAvailable() || !shouldUseWorkerForEncode(message)) {
115
116
  return protocol.encode(uuid, message);
116
117
  }
117
118
 
118
119
  // [Worker]
119
- // Encode requires sending an object, so Structured Clone occurs.
120
- // But the benefit of offloading JSON.stringify cost from the main thread is greater.
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
- // Process on main thread if no Worker or small data
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 transfer (buffer ownership moves to Worker)
137
+ // Zero-copy 전송 (버퍼 소유권이 Worker로 이동)
137
138
  const rawResult = await runWorker("decode", bytes, [bytes.buffer]);
138
139
 
139
- // Restore class instances (DateTime, etc.) from Worker's plain object result
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
  }