@simplysm/service-client 13.0.0-beta.6

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 (209) hide show
  1. package/.cache/typecheck-browser.tsbuildinfo +1 -0
  2. package/.cache/typecheck-node.tsbuildinfo +1 -0
  3. package/README.md +397 -0
  4. package/dist/core-common/src/common.types.d.ts +74 -0
  5. package/dist/core-common/src/common.types.d.ts.map +1 -0
  6. package/dist/core-common/src/env.d.ts +6 -0
  7. package/dist/core-common/src/env.d.ts.map +1 -0
  8. package/dist/core-common/src/errors/argument-error.d.ts +25 -0
  9. package/dist/core-common/src/errors/argument-error.d.ts.map +1 -0
  10. package/dist/core-common/src/errors/not-implemented-error.d.ts +29 -0
  11. package/dist/core-common/src/errors/not-implemented-error.d.ts.map +1 -0
  12. package/dist/core-common/src/errors/sd-error.d.ts +27 -0
  13. package/dist/core-common/src/errors/sd-error.d.ts.map +1 -0
  14. package/dist/core-common/src/errors/timeout-error.d.ts +31 -0
  15. package/dist/core-common/src/errors/timeout-error.d.ts.map +1 -0
  16. package/dist/core-common/src/extensions/arr-ext.d.ts +15 -0
  17. package/dist/core-common/src/extensions/arr-ext.d.ts.map +1 -0
  18. package/dist/core-common/src/extensions/arr-ext.helpers.d.ts +19 -0
  19. package/dist/core-common/src/extensions/arr-ext.helpers.d.ts.map +1 -0
  20. package/dist/core-common/src/extensions/arr-ext.types.d.ts +215 -0
  21. package/dist/core-common/src/extensions/arr-ext.types.d.ts.map +1 -0
  22. package/dist/core-common/src/extensions/map-ext.d.ts +57 -0
  23. package/dist/core-common/src/extensions/map-ext.d.ts.map +1 -0
  24. package/dist/core-common/src/extensions/set-ext.d.ts +36 -0
  25. package/dist/core-common/src/extensions/set-ext.d.ts.map +1 -0
  26. package/dist/core-common/src/features/debounce-queue.d.ts +53 -0
  27. package/dist/core-common/src/features/debounce-queue.d.ts.map +1 -0
  28. package/dist/core-common/src/features/event-emitter.d.ts +66 -0
  29. package/dist/core-common/src/features/event-emitter.d.ts.map +1 -0
  30. package/dist/core-common/src/features/serial-queue.d.ts +47 -0
  31. package/dist/core-common/src/features/serial-queue.d.ts.map +1 -0
  32. package/dist/core-common/src/index.d.ts +32 -0
  33. package/dist/core-common/src/index.d.ts.map +1 -0
  34. package/dist/core-common/src/types/date-only.d.ts +152 -0
  35. package/dist/core-common/src/types/date-only.d.ts.map +1 -0
  36. package/dist/core-common/src/types/date-time.d.ts +96 -0
  37. package/dist/core-common/src/types/date-time.d.ts.map +1 -0
  38. package/dist/core-common/src/types/lazy-gc-map.d.ts +80 -0
  39. package/dist/core-common/src/types/lazy-gc-map.d.ts.map +1 -0
  40. package/dist/core-common/src/types/time.d.ts +68 -0
  41. package/dist/core-common/src/types/time.d.ts.map +1 -0
  42. package/dist/core-common/src/types/uuid.d.ts +35 -0
  43. package/dist/core-common/src/types/uuid.d.ts.map +1 -0
  44. package/dist/core-common/src/utils/bytes.d.ts +51 -0
  45. package/dist/core-common/src/utils/bytes.d.ts.map +1 -0
  46. package/dist/core-common/src/utils/date-format.d.ts +90 -0
  47. package/dist/core-common/src/utils/date-format.d.ts.map +1 -0
  48. package/dist/core-common/src/utils/json.d.ts +34 -0
  49. package/dist/core-common/src/utils/json.d.ts.map +1 -0
  50. package/dist/core-common/src/utils/num.d.ts +60 -0
  51. package/dist/core-common/src/utils/num.d.ts.map +1 -0
  52. package/dist/core-common/src/utils/obj.d.ts +258 -0
  53. package/dist/core-common/src/utils/obj.d.ts.map +1 -0
  54. package/dist/core-common/src/utils/path.d.ts +23 -0
  55. package/dist/core-common/src/utils/path.d.ts.map +1 -0
  56. package/dist/core-common/src/utils/primitive.d.ts +18 -0
  57. package/dist/core-common/src/utils/primitive.d.ts.map +1 -0
  58. package/dist/core-common/src/utils/str.d.ts +103 -0
  59. package/dist/core-common/src/utils/str.d.ts.map +1 -0
  60. package/dist/core-common/src/utils/template-strings.d.ts +84 -0
  61. package/dist/core-common/src/utils/template-strings.d.ts.map +1 -0
  62. package/dist/core-common/src/utils/transferable.d.ts +47 -0
  63. package/dist/core-common/src/utils/transferable.d.ts.map +1 -0
  64. package/dist/core-common/src/utils/wait.d.ts +19 -0
  65. package/dist/core-common/src/utils/wait.d.ts.map +1 -0
  66. package/dist/core-common/src/utils/xml.d.ts +36 -0
  67. package/dist/core-common/src/utils/xml.d.ts.map +1 -0
  68. package/dist/core-common/src/zip/sd-zip.d.ts +80 -0
  69. package/dist/core-common/src/zip/sd-zip.d.ts.map +1 -0
  70. package/dist/features/event-client.js +74 -0
  71. package/dist/features/event-client.js.map +7 -0
  72. package/dist/features/file-client.js +42 -0
  73. package/dist/features/file-client.js.map +7 -0
  74. package/dist/features/orm/orm-client-connector.js +41 -0
  75. package/dist/features/orm/orm-client-connector.js.map +7 -0
  76. package/dist/features/orm/orm-client-db-context-executor.js +61 -0
  77. package/dist/features/orm/orm-client-db-context-executor.js.map +7 -0
  78. package/dist/features/orm/orm-connect-config.js +1 -0
  79. package/dist/features/orm/orm-connect-config.js.map +7 -0
  80. package/dist/index.js +12 -0
  81. package/dist/index.js.map +7 -0
  82. package/dist/orm-common/src/db-context.d.ts +669 -0
  83. package/dist/orm-common/src/db-context.d.ts.map +1 -0
  84. package/dist/orm-common/src/errors/db-transaction-error.d.ts +51 -0
  85. package/dist/orm-common/src/errors/db-transaction-error.d.ts.map +1 -0
  86. package/dist/orm-common/src/exec/executable.d.ts +79 -0
  87. package/dist/orm-common/src/exec/executable.d.ts.map +1 -0
  88. package/dist/orm-common/src/exec/queryable.d.ts +708 -0
  89. package/dist/orm-common/src/exec/queryable.d.ts.map +1 -0
  90. package/dist/orm-common/src/exec/search-parser.d.ts +72 -0
  91. package/dist/orm-common/src/exec/search-parser.d.ts.map +1 -0
  92. package/dist/orm-common/src/expr/expr-unit.d.ts +25 -0
  93. package/dist/orm-common/src/expr/expr-unit.d.ts.map +1 -0
  94. package/dist/orm-common/src/expr/expr.d.ts +1369 -0
  95. package/dist/orm-common/src/expr/expr.d.ts.map +1 -0
  96. package/dist/orm-common/src/index.d.ts +32 -0
  97. package/dist/orm-common/src/index.d.ts.map +1 -0
  98. package/dist/orm-common/src/models/system-migration.d.ts +10 -0
  99. package/dist/orm-common/src/models/system-migration.d.ts.map +1 -0
  100. package/dist/orm-common/src/query-builder/base/expr-renderer-base.d.ts +95 -0
  101. package/dist/orm-common/src/query-builder/base/expr-renderer-base.d.ts.map +1 -0
  102. package/dist/orm-common/src/query-builder/base/query-builder-base.d.ts +66 -0
  103. package/dist/orm-common/src/query-builder/base/query-builder-base.d.ts.map +1 -0
  104. package/dist/orm-common/src/query-builder/mssql/mssql-expr-renderer.d.ts +84 -0
  105. package/dist/orm-common/src/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -0
  106. package/dist/orm-common/src/query-builder/mssql/mssql-query-builder.d.ts +45 -0
  107. package/dist/orm-common/src/query-builder/mssql/mssql-query-builder.d.ts.map +1 -0
  108. package/dist/orm-common/src/query-builder/mysql/mysql-expr-renderer.d.ts +84 -0
  109. package/dist/orm-common/src/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -0
  110. package/dist/orm-common/src/query-builder/mysql/mysql-query-builder.d.ts +54 -0
  111. package/dist/orm-common/src/query-builder/mysql/mysql-query-builder.d.ts.map +1 -0
  112. package/dist/orm-common/src/query-builder/postgresql/postgresql-expr-renderer.d.ts +84 -0
  113. package/dist/orm-common/src/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -0
  114. package/dist/orm-common/src/query-builder/postgresql/postgresql-query-builder.d.ts +52 -0
  115. package/dist/orm-common/src/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -0
  116. package/dist/orm-common/src/query-builder/query-builder.d.ts +7 -0
  117. package/dist/orm-common/src/query-builder/query-builder.d.ts.map +1 -0
  118. package/dist/orm-common/src/schema/factory/column-builder.d.ts +394 -0
  119. package/dist/orm-common/src/schema/factory/column-builder.d.ts.map +1 -0
  120. package/dist/orm-common/src/schema/factory/index-builder.d.ts +151 -0
  121. package/dist/orm-common/src/schema/factory/index-builder.d.ts.map +1 -0
  122. package/dist/orm-common/src/schema/factory/relation-builder.d.ts +337 -0
  123. package/dist/orm-common/src/schema/factory/relation-builder.d.ts.map +1 -0
  124. package/dist/orm-common/src/schema/procedure-builder.d.ts +202 -0
  125. package/dist/orm-common/src/schema/procedure-builder.d.ts.map +1 -0
  126. package/dist/orm-common/src/schema/table-builder.d.ts +259 -0
  127. package/dist/orm-common/src/schema/table-builder.d.ts.map +1 -0
  128. package/dist/orm-common/src/schema/view-builder.d.ts +183 -0
  129. package/dist/orm-common/src/schema/view-builder.d.ts.map +1 -0
  130. package/dist/orm-common/src/types/column.d.ts +172 -0
  131. package/dist/orm-common/src/types/column.d.ts.map +1 -0
  132. package/dist/orm-common/src/types/db.d.ts +175 -0
  133. package/dist/orm-common/src/types/db.d.ts.map +1 -0
  134. package/dist/orm-common/src/types/expr.d.ts +474 -0
  135. package/dist/orm-common/src/types/expr.d.ts.map +1 -0
  136. package/dist/orm-common/src/types/query-def.d.ts +351 -0
  137. package/dist/orm-common/src/types/query-def.d.ts.map +1 -0
  138. package/dist/orm-common/src/utils/result-parser.d.ts +38 -0
  139. package/dist/orm-common/src/utils/result-parser.d.ts.map +1 -0
  140. package/dist/protocol/client-protocol-wrapper.js +92 -0
  141. package/dist/protocol/client-protocol-wrapper.js.map +7 -0
  142. package/dist/service-client/src/features/event-client.d.ts +14 -0
  143. package/dist/service-client/src/features/event-client.d.ts.map +1 -0
  144. package/dist/service-client/src/features/file-client.d.ts +13 -0
  145. package/dist/service-client/src/features/file-client.d.ts.map +1 -0
  146. package/dist/service-client/src/features/orm/orm-client-connector.d.ts +10 -0
  147. package/dist/service-client/src/features/orm/orm-client-connector.d.ts.map +1 -0
  148. package/dist/service-client/src/features/orm/orm-client-db-context-executor.d.ts +26 -0
  149. package/dist/service-client/src/features/orm/orm-client-db-context-executor.d.ts.map +1 -0
  150. package/dist/service-client/src/features/orm/orm-connect-config.d.ts +13 -0
  151. package/dist/service-client/src/features/orm/orm-connect-config.d.ts.map +1 -0
  152. package/dist/service-client/src/index.d.ts +12 -0
  153. package/dist/service-client/src/index.d.ts.map +1 -0
  154. package/dist/service-client/src/protocol/client-protocol-wrapper.d.ts +23 -0
  155. package/dist/service-client/src/protocol/client-protocol-wrapper.d.ts.map +1 -0
  156. package/dist/service-client/src/service-client.d.ts +41 -0
  157. package/dist/service-client/src/service-client.d.ts.map +1 -0
  158. package/dist/service-client/src/transport/service-transport.d.ts +24 -0
  159. package/dist/service-client/src/transport/service-transport.d.ts.map +1 -0
  160. package/dist/service-client/src/transport/socket-provider.d.ts +31 -0
  161. package/dist/service-client/src/transport/socket-provider.d.ts.map +1 -0
  162. package/dist/service-client/src/types/connection-config.d.ts +8 -0
  163. package/dist/service-client/src/types/connection-config.d.ts.map +1 -0
  164. package/dist/service-client/src/types/progress.types.d.ts +10 -0
  165. package/dist/service-client/src/types/progress.types.d.ts.map +1 -0
  166. package/dist/service-client/src/workers/client-protocol.worker.d.ts +2 -0
  167. package/dist/service-client/src/workers/client-protocol.worker.d.ts.map +1 -0
  168. package/dist/service-client.js +114 -0
  169. package/dist/service-client.js.map +7 -0
  170. package/dist/service-common/src/index.d.ts +8 -0
  171. package/dist/service-common/src/index.d.ts.map +1 -0
  172. package/dist/service-common/src/protocol/protocol.types.d.ts +100 -0
  173. package/dist/service-common/src/protocol/protocol.types.d.ts.map +1 -0
  174. package/dist/service-common/src/protocol/service-protocol.d.ts +63 -0
  175. package/dist/service-common/src/protocol/service-protocol.d.ts.map +1 -0
  176. package/dist/service-common/src/service-types/auto-update-service.types.d.ts +17 -0
  177. package/dist/service-common/src/service-types/auto-update-service.types.d.ts.map +1 -0
  178. package/dist/service-common/src/service-types/crypto-service.types.d.ts +22 -0
  179. package/dist/service-common/src/service-types/crypto-service.types.d.ts.map +1 -0
  180. package/dist/service-common/src/service-types/orm-service.types.d.ts +30 -0
  181. package/dist/service-common/src/service-types/orm-service.types.d.ts.map +1 -0
  182. package/dist/service-common/src/service-types/smtp-service.types.d.ts +55 -0
  183. package/dist/service-common/src/service-types/smtp-service.types.d.ts.map +1 -0
  184. package/dist/service-common/src/types.d.ts +43 -0
  185. package/dist/service-common/src/types.d.ts.map +1 -0
  186. package/dist/transport/service-transport.js +112 -0
  187. package/dist/transport/service-transport.js.map +7 -0
  188. package/dist/transport/socket-provider.js +170 -0
  189. package/dist/transport/socket-provider.js.map +7 -0
  190. package/dist/types/connection-config.js +1 -0
  191. package/dist/types/connection-config.js.map +7 -0
  192. package/dist/types/progress.types.js +1 -0
  193. package/dist/types/progress.types.js.map +7 -0
  194. package/dist/workers/client-protocol.worker.js +30 -0
  195. package/dist/workers/client-protocol.worker.js.map +7 -0
  196. package/package.json +26 -0
  197. package/src/features/event-client.ts +102 -0
  198. package/src/features/file-client.ts +56 -0
  199. package/src/features/orm/orm-client-connector.ts +50 -0
  200. package/src/features/orm/orm-client-db-context-executor.ts +98 -0
  201. package/src/features/orm/orm-connect-config.ts +11 -0
  202. package/src/index.ts +20 -0
  203. package/src/protocol/client-protocol-wrapper.ts +132 -0
  204. package/src/service-client.ts +157 -0
  205. package/src/transport/service-transport.ts +155 -0
  206. package/src/transport/socket-provider.ts +220 -0
  207. package/src/types/connection-config.ts +7 -0
  208. package/src/types/progress.types.ts +10 -0
  209. package/src/workers/client-protocol.worker.ts +54 -0
@@ -0,0 +1,55 @@
1
+ import type { Bytes } from "@simplysm/core-common";
2
+ /**
3
+ * SMTP 서비스 인터페이스
4
+ *
5
+ * 이메일 전송 기능을 제공한다.
6
+ * 직접 SMTP 설정을 전달하거나 서버 설정을 참조하여 전송할 수 있다.
7
+ */
8
+ export interface SmtpService {
9
+ /** 직접 SMTP 설정으로 이메일 전송 */
10
+ send(options: SmtpSendOption): Promise<string>;
11
+ /** 서버 설정 참조로 이메일 전송 */
12
+ sendByConfig(configName: string, options: SmtpSendByConfigOption): Promise<string>;
13
+ }
14
+ /**
15
+ * 이메일 첨부 파일 정보
16
+ *
17
+ * content 또는 path 중 하나를 지정해야 한다.
18
+ */
19
+ export interface SmtpSendAttachment {
20
+ /** 첨부 파일명 */
21
+ filename: string;
22
+ /** 파일 내용 (바이너리) */
23
+ content?: Bytes;
24
+ /** 서버 내 파일 경로 */
25
+ path?: string;
26
+ /** MIME 타입 (예: "application/pdf") */
27
+ contentType?: string;
28
+ }
29
+ /** SMTP 연결 옵션 (공통) */
30
+ export interface SmtpConnectionOptions {
31
+ host: string;
32
+ port?: number;
33
+ secure?: boolean;
34
+ user?: string;
35
+ pass?: string;
36
+ }
37
+ /** 이메일 내용 옵션 (공통) */
38
+ export interface SmtpEmailContentOptions {
39
+ to: string;
40
+ cc?: string;
41
+ bcc?: string;
42
+ subject: string;
43
+ html: string;
44
+ attachments?: SmtpSendAttachment[];
45
+ }
46
+ export interface SmtpSendByConfigOption extends SmtpEmailContentOptions {
47
+ }
48
+ export interface SmtpSendOption extends SmtpConnectionOptions, SmtpEmailContentOptions {
49
+ from: string;
50
+ }
51
+ export interface SmtpConfig extends SmtpConnectionOptions {
52
+ senderName: string;
53
+ senderEmail?: string;
54
+ }
55
+ //# sourceMappingURL=smtp-service.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smtp-service.types.d.ts","sourceRoot":"","sources":["../../../../../service-common/src/service-types/smtp-service.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,0BAA0B;IAC1B,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,uBAAuB;IACvB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACpF;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,aAAa;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB;IACnB,OAAO,CAAC,EAAE,KAAK,CAAC;IAChB,iBAAiB;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,sBAAsB;AACtB,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAqB;AACrB,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,sBAAuB,SAAQ,uBAAuB;CAAG;AAE1E,MAAM,WAAW,cAAe,SAAQ,qBAAqB,EAAE,uBAAuB;IACpF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAW,SAAQ,qBAAqB;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 이벤트 리스너 타입 정의용 추상 클래스
3
+ *
4
+ * - 상속만 하면 됨 (프로퍼티 구현 불필요)
5
+ * - $info, $data는 타입 추출용 (런타임 미사용)
6
+ * - eventName은 mangle 안전한 이벤트 식별자
7
+ *
8
+ * @example
9
+ * export class SharedDataChangeEvent extends ServiceEventListener<
10
+ * { name: string; filter: unknown },
11
+ * (string | number)[] | undefined
12
+ * > {
13
+ * readonly eventName = "SharedDataChangeEvent";
14
+ * }
15
+ *
16
+ * // 클라이언트에서 사용
17
+ * await client.addEventListener(
18
+ * SharedDataChangeEvent,
19
+ * { name: "test", filter: null },
20
+ * (data) => console.log(data)
21
+ * );
22
+ */
23
+ export declare abstract class ServiceEventListener<TInfo, TData> {
24
+ /** mangle 안전한 이벤트 식별자 (상속 시 필수 구현) */
25
+ abstract readonly eventName: string;
26
+ /** 타입 추출용 (런타임 미사용) */
27
+ readonly $info: TInfo;
28
+ readonly $data: TData;
29
+ }
30
+ /**
31
+ * 파일 업로드 결과
32
+ *
33
+ * 서버에 업로드된 파일의 정보를 담는다.
34
+ */
35
+ export interface ServiceUploadResult {
36
+ /** 서버 내 저장 경로 */
37
+ path: string;
38
+ /** 원본 파일명 */
39
+ filename: string;
40
+ /** 파일 크기 (bytes) */
41
+ size: number;
42
+ }
43
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../service-common/src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,8BAAsB,oBAAoB,CAAC,KAAK,EAAE,KAAK;IACrD,sCAAsC;IACtC,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAEpC,uBAAuB;IACvB,SAAiB,KAAK,EAAE,KAAK,CAAC;IAC9B,SAAiB,KAAK,EAAE,KAAK,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;CACd"}
@@ -0,0 +1,112 @@
1
+ import { EventEmitter, Uuid } from "@simplysm/core-common";
2
+ import { ClientProtocolWrapper } from "../protocol/client-protocol-wrapper";
3
+ class ServiceTransport extends EventEmitter {
4
+ constructor(_socket) {
5
+ super();
6
+ this._socket = _socket;
7
+ this._socket.on("message", this._onMessage.bind(this));
8
+ this._socket.on("state", (state) => {
9
+ if (state === "closed" || state === "reconnecting") {
10
+ this._cancelAllRequests("Socket connection lost");
11
+ }
12
+ });
13
+ }
14
+ _protocol = new ClientProtocolWrapper();
15
+ _pendingRequests = /* @__PURE__ */ new Map();
16
+ // 응답 progress의 totalSize 저장 (complete 시 100% emit용)
17
+ _responseProgressTotalSize = /* @__PURE__ */ new Map();
18
+ async send(message, progress) {
19
+ var _a, _b;
20
+ const uuid = Uuid.new().toString();
21
+ const responsePromise = new Promise((resolve, reject) => {
22
+ this._pendingRequests.set(uuid, { resolve, reject, progress });
23
+ });
24
+ try {
25
+ const { chunks, totalSize } = await this._protocol.encode(uuid, message);
26
+ if (chunks.length > 1) {
27
+ (_a = progress == null ? void 0 : progress.request) == null ? void 0 : _a.call(progress, {
28
+ uuid,
29
+ totalSize,
30
+ completedSize: 0
31
+ });
32
+ }
33
+ for (const chunk of chunks) {
34
+ await this._socket.send(chunk);
35
+ }
36
+ } catch (err) {
37
+ (_b = this._pendingRequests.get(uuid)) == null ? void 0 : _b.reject(err);
38
+ this._pendingRequests.delete(uuid);
39
+ throw err;
40
+ }
41
+ return responsePromise;
42
+ }
43
+ async _onMessage(buf) {
44
+ var _a, _b, _c, _d, _e, _f;
45
+ const decoded = await this._protocol.decode(buf);
46
+ const listenerInfo = this._pendingRequests.get(decoded.uuid);
47
+ try {
48
+ if (decoded.type === "progress") {
49
+ this._responseProgressTotalSize.set(decoded.uuid, decoded.totalSize);
50
+ (_b = (_a = listenerInfo == null ? void 0 : listenerInfo.progress) == null ? void 0 : _a.response) == null ? void 0 : _b.call(_a, {
51
+ uuid: decoded.uuid,
52
+ totalSize: decoded.totalSize,
53
+ completedSize: decoded.completedSize
54
+ });
55
+ } else {
56
+ if (decoded.message.name === "progress") {
57
+ const body = decoded.message.body;
58
+ (_d = (_c = listenerInfo == null ? void 0 : listenerInfo.progress) == null ? void 0 : _c.request) == null ? void 0 : _d.call(_c, {
59
+ uuid: decoded.uuid,
60
+ totalSize: body.totalSize,
61
+ completedSize: body.completedSize
62
+ });
63
+ } else if (decoded.message.name === "response") {
64
+ const totalSize = this._responseProgressTotalSize.get(decoded.uuid);
65
+ if (totalSize != null) {
66
+ this._responseProgressTotalSize.delete(decoded.uuid);
67
+ (_f = (_e = listenerInfo == null ? void 0 : listenerInfo.progress) == null ? void 0 : _e.response) == null ? void 0 : _f.call(_e, {
68
+ uuid: decoded.uuid,
69
+ totalSize,
70
+ completedSize: totalSize
71
+ });
72
+ }
73
+ this._pendingRequests.delete(decoded.uuid);
74
+ listenerInfo == null ? void 0 : listenerInfo.resolve(decoded.message.body);
75
+ } else if (decoded.message.name === "error") {
76
+ this._responseProgressTotalSize.delete(decoded.uuid);
77
+ this._pendingRequests.delete(decoded.uuid);
78
+ listenerInfo == null ? void 0 : listenerInfo.reject(this._toError(decoded.message.body));
79
+ } else if (decoded.message.name === "reload") {
80
+ const body = decoded.message.body;
81
+ if (this._socket.clientName === body.clientName) {
82
+ this.emit("reload", body.changedFileSet);
83
+ }
84
+ } else if (decoded.message.name === "evt:on") {
85
+ const body = decoded.message.body;
86
+ this.emit("event", { keys: body.keys, data: body.data });
87
+ } else {
88
+ throw new Error("\uC694\uCCAD\uC774 \uC798 \uBABB \uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
89
+ }
90
+ }
91
+ } catch (err) {
92
+ listenerInfo == null ? void 0 : listenerInfo.reject(err instanceof Error ? err : new Error(String(err)));
93
+ }
94
+ }
95
+ // 모든 대기 요청 취소 처리
96
+ _cancelAllRequests(reason) {
97
+ for (const listenerInfo of this._pendingRequests.values()) {
98
+ listenerInfo.reject(new Error(`Request canceled: ${reason}`));
99
+ }
100
+ this._pendingRequests.clear();
101
+ this._responseProgressTotalSize.clear();
102
+ }
103
+ _toError(body) {
104
+ let err = new Error(body.message);
105
+ err = Object.assign(err, body);
106
+ return err;
107
+ }
108
+ }
109
+ export {
110
+ ServiceTransport
111
+ };
112
+ //# sourceMappingURL=service-transport.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/transport/service-transport.ts"],
4
+ "sourcesContent": ["import type { Bytes } from \"@simplysm/core-common\";\nimport { EventEmitter, Uuid } from \"@simplysm/core-common\";\nimport type { ServiceErrorMessage, ServiceResponseMessage, ServiceClientMessage } from \"@simplysm/service-common\";\nimport { ClientProtocolWrapper } from \"../protocol/client-protocol-wrapper\";\nimport type { ServiceProgress } from \"../types/progress.types\";\nimport type { SocketProvider } from \"./socket-provider\";\n\ninterface ServiceTransportEvents {\n reload: Set<string>;\n event: { keys: string[]; data: unknown };\n}\n\nexport class ServiceTransport extends EventEmitter<ServiceTransportEvents> {\n private readonly _protocol = new ClientProtocolWrapper();\n\n private readonly _pendingRequests = new Map<\n string,\n {\n resolve: (msg: ServiceResponseMessage) => void;\n reject: (err: Error) => void;\n progress?: ServiceProgress;\n }\n >();\n\n // \uC751\uB2F5 progress\uC758 totalSize \uC800\uC7A5 (complete \uC2DC 100% emit\uC6A9)\n private readonly _responseProgressTotalSize = new Map<string, number>();\n\n constructor(private readonly _socket: SocketProvider) {\n super();\n\n this._socket.on(\"message\", this._onMessage.bind(this));\n\n // \uC18C\uCF13\uC774 \uB04A\uAE30\uBA74 \uB300\uAE30 \uC911\uC778 \uBAA8\uB4E0 \uC694\uCCAD\uC744 \uC5D0\uB7EC \uCC98\uB9AC\uD558\uC5EC \uBA54\uBAA8\uB9AC \uD574\uC81C\n this._socket.on(\"state\", (state) => {\n if (state === \"closed\" || state === \"reconnecting\") {\n this._cancelAllRequests(\"Socket connection lost\");\n }\n });\n }\n\n async send(message: ServiceClientMessage, progress?: ServiceProgress): Promise<unknown> {\n const uuid = Uuid.new().toString();\n\n // \uC751\uB2F5 \uB300\uAE30 \uC2DC\uC791 (\uC694\uCCAD \uBCF4\uB0B4\uAE30 \uC804\uC5D0 \uB9AC\uC2A4\uB108\uB97C \uBA3C\uC800 \uB4F1\uB85D\uD574\uC57C \uC548\uC804\uD568)\n const responsePromise = new Promise((resolve, reject) => {\n this._pendingRequests.set(uuid, { resolve, reject, progress });\n });\n\n // \uC694\uCCAD \uC804\uC1A1\n try {\n const { chunks, totalSize } = await this._protocol.encode(uuid, message);\n\n // \uC9C4\uD589\uB960 \uCD08\uAE30\uD654\n if (chunks.length > 1) {\n progress?.request?.({\n uuid,\n totalSize,\n completedSize: 0,\n });\n }\n\n // \uC804\uC1A1\n for (const chunk of chunks) {\n await this._socket.send(chunk);\n }\n } catch (err) {\n // \uC804\uC1A1 \uC2E4\uD328 \uC2DC \uC989\uC2DC \uC815\uB9AC\n this._pendingRequests.get(uuid)?.reject(err as Error);\n this._pendingRequests.delete(uuid);\n throw err;\n }\n\n // \uC751\uB2F5 \uACB0\uACFC \uBC18\uD658\n return responsePromise;\n }\n\n private async _onMessage(buf: Bytes): Promise<void> {\n const decoded = await this._protocol.decode(buf);\n\n const listenerInfo = this._pendingRequests.get(decoded.uuid);\n\n try {\n if (decoded.type === \"progress\") {\n // totalSize \uAE30\uC5B5 (complete \uC2DC 100% emit\uC6A9)\n this._responseProgressTotalSize.set(decoded.uuid, decoded.totalSize);\n\n listenerInfo?.progress?.response?.({\n uuid: decoded.uuid,\n totalSize: decoded.totalSize,\n completedSize: decoded.completedSize,\n });\n } else {\n if (decoded.message.name === \"progress\") {\n const body = decoded.message.body as { totalSize: number; completedSize: number };\n listenerInfo?.progress?.request?.({\n uuid: decoded.uuid,\n totalSize: body.totalSize,\n completedSize: body.completedSize,\n });\n } else if (decoded.message.name === \"response\") {\n // split\uB41C \uBA54\uC2DC\uC9C0\uC600\uC73C\uBA74 100% progress emit\n const totalSize = this._responseProgressTotalSize.get(decoded.uuid);\n if (totalSize != null) {\n this._responseProgressTotalSize.delete(decoded.uuid);\n listenerInfo?.progress?.response?.({\n uuid: decoded.uuid,\n totalSize,\n completedSize: totalSize,\n });\n }\n\n // \uC751\uB2F5\uC744 \uBC1B\uC558\uC73C\uBBC0\uB85C Map\uC5D0\uC11C \uC81C\uAC70\n this._pendingRequests.delete(decoded.uuid);\n\n listenerInfo?.resolve(decoded.message.body as ServiceResponseMessage);\n } else if (decoded.message.name === \"error\") {\n // progress totalSize \uC815\uB9AC\n this._responseProgressTotalSize.delete(decoded.uuid);\n\n // \uC5D0\uB7EC\uB97C \uBC1B\uC558\uC73C\uBBC0\uB85C Map\uC5D0\uC11C \uC81C\uAC70\n this._pendingRequests.delete(decoded.uuid);\n\n listenerInfo?.reject(this._toError(decoded.message.body));\n } else if (decoded.message.name === \"reload\") {\n const body = decoded.message.body as { clientName: string; changedFileSet: Set<string> };\n if (this._socket.clientName === body.clientName) {\n this.emit(\"reload\", body.changedFileSet);\n }\n } else if (decoded.message.name === \"evt:on\") {\n const body = decoded.message.body as { keys: string[]; data: unknown };\n this.emit(\"event\", { keys: body.keys, data: body.data });\n } else {\n throw new Error(\"\uC694\uCCAD\uC774 \uC798 \uBABB \uB418\uC5C8\uC2B5\uB2C8\uB2E4.\");\n }\n }\n } catch (err) {\n listenerInfo?.reject(err instanceof Error ? err : new Error(String(err)));\n }\n }\n\n // \uBAA8\uB4E0 \uB300\uAE30 \uC694\uCCAD \uCDE8\uC18C \uCC98\uB9AC\n private _cancelAllRequests(reason: string): void {\n for (const listenerInfo of this._pendingRequests.values()) {\n listenerInfo.reject(new Error(`Request canceled: ${reason}`));\n }\n this._pendingRequests.clear();\n this._responseProgressTotalSize.clear();\n }\n\n private _toError(body: ServiceErrorMessage[\"body\"]): Error {\n let err = new Error(body.message);\n err = Object.assign(err, body);\n return err;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,170 @@
1
+ import { EventEmitter, Uuid, waitUntil, waitTime } from "@simplysm/core-common";
2
+ import { createConsola } from "consola";
3
+ const logger = createConsola().withTag("service-client:SocketProvider");
4
+ class SocketProvider extends EventEmitter {
5
+ constructor(_url, clientName, _maxReconnectCount) {
6
+ super();
7
+ this._url = _url;
8
+ this.clientName = clientName;
9
+ this._maxReconnectCount = _maxReconnectCount;
10
+ }
11
+ // 설정상수
12
+ _HEARTBEAT_TIMEOUT = 3e4;
13
+ // 30초간 아무런 메시지가 없으면 끊김으로 간주
14
+ _HEARTBEAT_INTERVAL = 5e3;
15
+ // 5초마다 핑 전송
16
+ _RECONNECT_DELAY = 3e3;
17
+ // 3초마다 재연결 시도
18
+ // 1바이트 버퍼 미리 생성 (메모리 절약)
19
+ _PING_PACKET = new Uint8Array([1]);
20
+ // 상태
21
+ _ws;
22
+ _isManualClose = false;
23
+ _reconnectCount = 0;
24
+ _heartbeatTimer;
25
+ _lastHeartbeatTime = Date.now();
26
+ get connected() {
27
+ var _a;
28
+ return ((_a = this._ws) == null ? void 0 : _a.readyState) === WebSocket.OPEN;
29
+ }
30
+ async connect() {
31
+ if (this.connected) return;
32
+ this._isManualClose = false;
33
+ try {
34
+ await this._createSocket();
35
+ this._startHeartbeat();
36
+ this._reconnectCount = 0;
37
+ this.emit("state", "connected");
38
+ } catch (err) {
39
+ throw err;
40
+ }
41
+ }
42
+ async close() {
43
+ this._isManualClose = true;
44
+ this._stopHeartbeat();
45
+ const ws = this._ws;
46
+ if (ws != null) {
47
+ ws.close();
48
+ await waitUntil(() => ws.readyState === WebSocket.CLOSED, 100, 30).catch(() => {
49
+ });
50
+ }
51
+ this.emit("state", "closed");
52
+ }
53
+ async send(data) {
54
+ try {
55
+ await waitUntil(() => this.connected, void 0, 50);
56
+ } catch {
57
+ throw new Error("\uC11C\uBC84\uC640 \uC5F0\uACB0\uB418\uC5B4\uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC778\uD130\uB137 \uC5F0\uACB0\uC744 \uD655\uC778\uD558\uC138\uC694.");
58
+ }
59
+ const ws = this._ws;
60
+ if (ws == null) {
61
+ throw new Error("WebSocket\uC774 \uC5F0\uACB0\uB418\uC5B4\uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
62
+ }
63
+ ws.send(data);
64
+ }
65
+ async _createSocket() {
66
+ const clientId = Uuid.new().toString();
67
+ const params = new URLSearchParams({
68
+ ver: "2",
69
+ clientId,
70
+ clientName: this.clientName
71
+ });
72
+ await new Promise((resolve, reject) => {
73
+ const ws = new WebSocket(`${this._url}?${params.toString()}`);
74
+ ws.binaryType = "arraybuffer";
75
+ ws.onopen = () => {
76
+ this._ws = ws;
77
+ resolve();
78
+ };
79
+ ws.onerror = (event) => {
80
+ if (!this.connected) {
81
+ const errorEvent = event;
82
+ const msg = errorEvent.message;
83
+ reject(new Error(msg));
84
+ }
85
+ };
86
+ });
87
+ const currentWs = this._ws;
88
+ if (currentWs == null) {
89
+ throw new Error("WebSocket \uCD08\uAE30\uD654 \uC2E4\uD328");
90
+ }
91
+ currentWs.onmessage = (event) => {
92
+ this._lastHeartbeatTime = Date.now();
93
+ const data = event.data;
94
+ const bytes = new Uint8Array(data);
95
+ if (bytes.length === 1 && bytes[0] === 2) return;
96
+ this.emit("message", bytes);
97
+ };
98
+ currentWs.onclose = async () => {
99
+ this._stopHeartbeat();
100
+ if (!this._isManualClose) {
101
+ await this._tryReconnect();
102
+ }
103
+ };
104
+ }
105
+ async _tryReconnect() {
106
+ while (this._reconnectCount < this._maxReconnectCount) {
107
+ this._reconnectCount++;
108
+ this.emit("state", "reconnecting");
109
+ logger.warn("WebSocket \uC5F0\uACB0 \uB04A\uAE40. \uC7AC\uC5F0\uACB0 \uC2DC\uB3C4...", {
110
+ reconnectCount: this._reconnectCount,
111
+ maxReconnectCount: this._maxReconnectCount
112
+ });
113
+ await waitTime(this._RECONNECT_DELAY);
114
+ try {
115
+ await this._createSocket();
116
+ this._startHeartbeat();
117
+ this._reconnectCount = 0;
118
+ this.emit("state", "connected");
119
+ logger.info("WebSocket \uC7AC\uC5F0\uACB0 \uC131\uACF5");
120
+ return;
121
+ } catch {
122
+ }
123
+ }
124
+ logger.error("\uC7AC\uC5F0\uACB0 \uC2DC\uB3C4 \uD69F\uC218 \uCD08\uACFC. \uC5F0\uACB0\uC744 \uD3EC\uAE30\uD569\uB2C8\uB2E4.");
125
+ this.emit("state", "closed");
126
+ }
127
+ _startHeartbeat() {
128
+ this._stopHeartbeat();
129
+ this._lastHeartbeatTime = Date.now();
130
+ this._heartbeatTimer = setInterval(() => {
131
+ if (Date.now() - this._lastHeartbeatTime > this._HEARTBEAT_TIMEOUT) {
132
+ logger.warn("Heartbeat Timeout. Connection lost.");
133
+ this._stopHeartbeat();
134
+ if (this._ws != null) {
135
+ const tempWs = this._ws;
136
+ this._ws = void 0;
137
+ tempWs.onclose = null;
138
+ tempWs.onerror = null;
139
+ tempWs.onmessage = null;
140
+ try {
141
+ tempWs.close();
142
+ } catch {
143
+ }
144
+ if (!this._isManualClose) {
145
+ void this._tryReconnect();
146
+ }
147
+ }
148
+ return;
149
+ }
150
+ const ws = this._ws;
151
+ if (this.connected && ws != null) {
152
+ try {
153
+ ws.send(this._PING_PACKET);
154
+ } catch (err) {
155
+ logger.warn("Ping send failed", err);
156
+ }
157
+ }
158
+ }, this._HEARTBEAT_INTERVAL);
159
+ }
160
+ _stopHeartbeat() {
161
+ if (this._heartbeatTimer != null) {
162
+ clearInterval(this._heartbeatTimer);
163
+ this._heartbeatTimer = void 0;
164
+ }
165
+ }
166
+ }
167
+ export {
168
+ SocketProvider
169
+ };
170
+ //# sourceMappingURL=socket-provider.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/transport/socket-provider.ts"],
4
+ "sourcesContent": ["import type { Bytes } from \"@simplysm/core-common\";\nimport { EventEmitter, Uuid, waitUntil, waitTime } from \"@simplysm/core-common\";\nimport { createConsola } from \"consola\";\n\nconst logger = createConsola().withTag(\"service-client:SocketProvider\");\n\ninterface SocketProviderEvents {\n message: Bytes;\n state: \"connected\" | \"closed\" | \"reconnecting\";\n}\n\nexport class SocketProvider extends EventEmitter<SocketProviderEvents> {\n // \uC124\uC815\uC0C1\uC218\n private readonly _HEARTBEAT_TIMEOUT = 30000; // 30\uCD08\uAC04 \uC544\uBB34\uB7F0 \uBA54\uC2DC\uC9C0\uAC00 \uC5C6\uC73C\uBA74 \uB04A\uAE40\uC73C\uB85C \uAC04\uC8FC\n private readonly _HEARTBEAT_INTERVAL = 5000; // 5\uCD08\uB9C8\uB2E4 \uD551 \uC804\uC1A1\n private readonly _RECONNECT_DELAY = 3000; // 3\uCD08\uB9C8\uB2E4 \uC7AC\uC5F0\uACB0 \uC2DC\uB3C4\n\n // 1\uBC14\uC774\uD2B8 \uBC84\uD37C \uBBF8\uB9AC \uC0DD\uC131 (\uBA54\uBAA8\uB9AC \uC808\uC57D)\n private readonly _PING_PACKET = new Uint8Array([0x01]);\n\n // \uC0C1\uD0DC\n private _ws?: WebSocket;\n private _isManualClose = false;\n private _reconnectCount = 0;\n private _heartbeatTimer?: ReturnType<typeof setInterval>;\n private _lastHeartbeatTime = Date.now();\n\n get connected(): boolean {\n return this._ws?.readyState === WebSocket.OPEN;\n }\n\n constructor(\n private readonly _url: string,\n public readonly clientName: string,\n private readonly _maxReconnectCount: number,\n ) {\n super();\n }\n\n async connect(): Promise<void> {\n if (this.connected) return;\n this._isManualClose = false;\n\n try {\n await this._createSocket();\n this._startHeartbeat();\n this._reconnectCount = 0; // \uC5F0\uACB0 \uC131\uACF5 \uC2DC \uCE74\uC6B4\uD2B8 \uCD08\uAE30\uD654\n this.emit(\"state\", \"connected\");\n } catch (err) {\n // \uCD5C\uCD08 \uC5F0\uACB0 \uC2E4\uD328\uB294 \uC5D0\uB7EC\uB97C \uB358\uC9D0 (\uD638\uCD9C\uC790\uAC00 \uC54C \uC218 \uC788\uAC8C)\n throw err;\n }\n }\n\n async close(): Promise<void> {\n this._isManualClose = true;\n this._stopHeartbeat();\n const ws = this._ws;\n if (ws != null) {\n ws.close();\n // \uC644\uC804\uD788 \uB2EB\uD790 \uB54C\uAE4C\uC9C0 \uB300\uAE30 (Graceful Shutdown)\n await waitUntil(() => ws.readyState === WebSocket.CLOSED, 100, 30).catch(() => {});\n }\n this.emit(\"state\", \"closed\");\n }\n\n async send(data: Bytes): Promise<void> {\n try {\n await waitUntil(() => this.connected, undefined, 50);\n } catch {\n throw new Error(\"\uC11C\uBC84\uC640 \uC5F0\uACB0\uB418\uC5B4\uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC778\uD130\uB137 \uC5F0\uACB0\uC744 \uD655\uC778\uD558\uC138\uC694.\");\n }\n const ws = this._ws;\n if (ws == null) {\n throw new Error(\"WebSocket\uC774 \uC5F0\uACB0\uB418\uC5B4\uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.\");\n }\n ws.send(data);\n }\n\n private async _createSocket(): Promise<void> {\n const clientId = Uuid.new().toString();\n const params = new URLSearchParams({\n ver: \"2\",\n clientId,\n clientName: this.clientName,\n });\n\n await new Promise<void>((resolve, reject) => {\n const ws = new WebSocket(`${this._url}?${params.toString()}`);\n ws.binaryType = \"arraybuffer\";\n\n ws.onopen = () => {\n this._ws = ws;\n resolve();\n };\n\n ws.onerror = (event: Event) => {\n // \uC5F0\uACB0 \uC911 \uC5D0\uB7EC \uBC1C\uC0DD \uC2DC reject\n if (!this.connected) {\n const errorEvent = event as ErrorEvent;\n const msg = errorEvent.message;\n reject(new Error(msg));\n }\n };\n });\n\n // \uC774 \uC2DC\uC810\uC5D0\uC11C this._ws\uB294 \uD56D\uC0C1 \uD560\uB2F9\uB418\uC5B4 \uC788\uC74C (ws.onopen\uC5D0\uC11C \uD560\uB2F9)\n const currentWs = this._ws;\n if (currentWs == null) {\n throw new Error(\"WebSocket \uCD08\uAE30\uD654 \uC2E4\uD328\");\n }\n\n currentWs.onmessage = (event) => {\n this._lastHeartbeatTime = Date.now(); // \uD558\uD2B8\uBE44\uD2B8 \uAC31\uC2E0\n\n const data = event.data as ArrayBuffer;\n const bytes = new Uint8Array(data);\n\n // Raw Ping/Pong \uCC98\uB9AC (\uAC00\uC7A5 \uBA3C\uC800 \uCCB4\uD06C)\n // 1\uBC14\uC774\uD2B8\uC774\uACE0 \uCCAB \uBC14\uC774\uD2B8\uAC00 0x02(Pong)\uC774\uBA74 \uBB34\uC2DC\n // (\uD558\uD2B8\uBE44\uD2B8 \uD0C0\uC784\uC2A4\uD0EC\uD504\uB9CC \uAC31\uC2E0\uD558\uACE0 \uB05D\uB0C4)\n if (bytes.length === 1 && bytes[0] === 0x02) return;\n\n this.emit(\"message\", bytes);\n };\n\n currentWs.onclose = async () => {\n this._stopHeartbeat();\n if (!this._isManualClose) {\n await this._tryReconnect();\n }\n };\n }\n\n private async _tryReconnect(): Promise<void> {\n // \uB8E8\uD504 \uAE30\uBC18 \uC7AC\uC5F0\uACB0 (\uC7AC\uADC0 \uB300\uC2E0 \uC0AC\uC6A9\uD558\uC5EC \uC2A4\uD0DD \uC548\uC804\uC131 \uD655\uBCF4)\n while (this._reconnectCount < this._maxReconnectCount) {\n this._reconnectCount++;\n this.emit(\"state\", \"reconnecting\");\n logger.warn(\"WebSocket \uC5F0\uACB0 \uB04A\uAE40. \uC7AC\uC5F0\uACB0 \uC2DC\uB3C4...\", {\n reconnectCount: this._reconnectCount,\n maxReconnectCount: this._maxReconnectCount,\n });\n\n await waitTime(this._RECONNECT_DELAY);\n\n try {\n await this._createSocket();\n this._startHeartbeat();\n this._reconnectCount = 0;\n this.emit(\"state\", \"connected\"); // \uC7AC\uC5F0\uACB0 \uC131\uACF5 \uC54C\uB9BC\n logger.info(\"WebSocket \uC7AC\uC5F0\uACB0 \uC131\uACF5\");\n return; // \uC7AC\uC5F0\uACB0 \uC131\uACF5 \uC2DC \uC885\uB8CC\n } catch {\n // \uC2E4\uD328 \uC2DC \uB8E8\uD504 \uACC4\uC18D\n }\n }\n\n // \uCD5C\uB300 \uC7AC\uC2DC\uB3C4 \uD69F\uC218 \uCD08\uACFC\n logger.error(\"\uC7AC\uC5F0\uACB0 \uC2DC\uB3C4 \uD69F\uC218 \uCD08\uACFC. \uC5F0\uACB0\uC744 \uD3EC\uAE30\uD569\uB2C8\uB2E4.\");\n this.emit(\"state\", \"closed\");\n }\n\n private _startHeartbeat(): void {\n this._stopHeartbeat();\n this._lastHeartbeatTime = Date.now();\n\n this._heartbeatTimer = setInterval(() => {\n // \uD0C0\uC784\uC544\uC6C3 \uCCB4\uD06C\n if (Date.now() - this._lastHeartbeatTime > this._HEARTBEAT_TIMEOUT) {\n logger.warn(\"Heartbeat Timeout. Connection lost.\");\n\n // \uD0C0\uC784\uC544\uC6C3\uC774 \uBC1C\uC0DD\uD588\uC73C\uBBC0\uB85C \uC989\uC2DC \uD0C0\uC774\uBA38\uB97C \uBA48\uCDB0\uC11C \uBC18\uBCF5 \uC2E4\uD589\uC744 \uB9C9\uC2B5\uB2C8\uB2E4.\n this._stopHeartbeat();\n\n // \uC18C\uCF13\uC774 \uB2EB\uD788\uAE30\uB97C \uAE30\uB2E4\uB9AC\uC9C0 \uB9D0\uACE0(onclose \uBBF8\uBC1C\uC0DD \uB300\uBE44), \uAC15\uC81C\uB85C \uC815\uB9AC \uD6C4 \uC7AC\uC5F0\uACB0\uD569\uB2C8\uB2E4.\n if (this._ws != null) {\n const tempWs = this._ws;\n this._ws = undefined; // \uC5F0\uACB0 \uC0C1\uD0DC \uB04A\uAE40\uC73C\uB85C \uAC04\uC8FC\n\n // \uAE30\uC874 \uC18C\uCF13\uC758 \uC774\uBCA4\uD2B8 \uD578\uB4E4\uB7EC \uC81C\uAC70\n // \uB4A4\uB2A6\uAC8C \uBC1C\uC0DD\uD55C onclose\uC5D0 \uB530\uB978 \uC911\uBCF5 \uC7AC\uC5F0\uACB0 \uBC29\uC9C0\n tempWs.onclose = null;\n tempWs.onerror = null;\n tempWs.onmessage = null;\n\n // \uC18C\uCF13 \uB2EB\uAE30 \uC2DC\uB3C4 (\uC5D0\uB7EC \uBB34\uC2DC)\n try {\n tempWs.close();\n } catch {\n // ignore\n }\n\n // \uC218\uB3D9 \uC885\uB8CC\uAC00 \uC544\uB2C8\uB77C\uBA74 \uC7AC\uC5F0\uACB0 \uB85C\uC9C1 \uAC15\uC81C \uC2E4\uD589\n if (!this._isManualClose) {\n void this._tryReconnect();\n }\n }\n return;\n }\n\n // ping \uC804\uC1A1\n const ws = this._ws;\n if (this.connected && ws != null) {\n try {\n ws.send(this._PING_PACKET);\n } catch (err) {\n logger.warn(\"Ping send failed\", err);\n }\n }\n }, this._HEARTBEAT_INTERVAL);\n }\n\n private _stopHeartbeat(): void {\n if (this._heartbeatTimer != null) {\n clearInterval(this._heartbeatTimer);\n this._heartbeatTimer = undefined;\n }\n }\n}\n"],
5
+ "mappings": "AACA,SAAS,cAAc,MAAM,WAAW,gBAAgB;AACxD,SAAS,qBAAqB;AAE9B,MAAM,SAAS,cAAc,EAAE,QAAQ,+BAA+B;AAO/D,MAAM,uBAAuB,aAAmC;AAAA,EAoBrE,YACmB,MACD,YACC,oBACjB;AACA,UAAM;AAJW;AACD;AACC;AAAA,EAGnB;AAAA;AAAA,EAxBiB,qBAAqB;AAAA;AAAA,EACrB,sBAAsB;AAAA;AAAA,EACtB,mBAAmB;AAAA;AAAA;AAAA,EAGnB,eAAe,IAAI,WAAW,CAAC,CAAI,CAAC;AAAA;AAAA,EAG7C;AAAA,EACA,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB;AAAA,EACA,qBAAqB,KAAK,IAAI;AAAA,EAEtC,IAAI,YAAqB;AA3B3B;AA4BI,aAAO,UAAK,QAAL,mBAAU,gBAAe,UAAU;AAAA,EAC5C;AAAA,EAUA,MAAM,UAAyB;AAC7B,QAAI,KAAK,UAAW;AACpB,SAAK,iBAAiB;AAEtB,QAAI;AACF,YAAM,KAAK,cAAc;AACzB,WAAK,gBAAgB;AACrB,WAAK,kBAAkB;AACvB,WAAK,KAAK,SAAS,WAAW;AAAA,IAChC,SAAS,KAAK;AAEZ,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,iBAAiB;AACtB,SAAK,eAAe;AACpB,UAAM,KAAK,KAAK;AAChB,QAAI,MAAM,MAAM;AACd,SAAG,MAAM;AAET,YAAM,UAAU,MAAM,GAAG,eAAe,UAAU,QAAQ,KAAK,EAAE,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACnF;AACA,SAAK,KAAK,SAAS,QAAQ;AAAA,EAC7B;AAAA,EAEA,MAAM,KAAK,MAA4B;AACrC,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,WAAW,QAAW,EAAE;AAAA,IACrD,QAAQ;AACN,YAAM,IAAI,MAAM,yJAAiC;AAAA,IACnD;AACA,UAAM,KAAK,KAAK;AAChB,QAAI,MAAM,MAAM;AACd,YAAM,IAAI,MAAM,gFAAyB;AAAA,IAC3C;AACA,OAAG,KAAK,IAAI;AAAA,EACd;AAAA,EAEA,MAAc,gBAA+B;AAC3C,UAAM,WAAW,KAAK,IAAI,EAAE,SAAS;AACrC,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,KAAK;AAAA,MACL;AAAA,MACA,YAAY,KAAK;AAAA,IACnB,CAAC;AAED,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,YAAM,KAAK,IAAI,UAAU,GAAG,KAAK,IAAI,IAAI,OAAO,SAAS,CAAC,EAAE;AAC5D,SAAG,aAAa;AAEhB,SAAG,SAAS,MAAM;AAChB,aAAK,MAAM;AACX,gBAAQ;AAAA,MACV;AAEA,SAAG,UAAU,CAAC,UAAiB;AAE7B,YAAI,CAAC,KAAK,WAAW;AACnB,gBAAM,aAAa;AACnB,gBAAM,MAAM,WAAW;AACvB,iBAAO,IAAI,MAAM,GAAG,CAAC;AAAA,QACvB;AAAA,MACF;AAAA,IACF,CAAC;AAGD,UAAM,YAAY,KAAK;AACvB,QAAI,aAAa,MAAM;AACrB,YAAM,IAAI,MAAM,2CAAkB;AAAA,IACpC;AAEA,cAAU,YAAY,CAAC,UAAU;AAC/B,WAAK,qBAAqB,KAAK,IAAI;AAEnC,YAAM,OAAO,MAAM;AACnB,YAAM,QAAQ,IAAI,WAAW,IAAI;AAKjC,UAAI,MAAM,WAAW,KAAK,MAAM,CAAC,MAAM,EAAM;AAE7C,WAAK,KAAK,WAAW,KAAK;AAAA,IAC5B;AAEA,cAAU,UAAU,YAAY;AAC9B,WAAK,eAAe;AACpB,UAAI,CAAC,KAAK,gBAAgB;AACxB,cAAM,KAAK,cAAc;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,gBAA+B;AAE3C,WAAO,KAAK,kBAAkB,KAAK,oBAAoB;AACrD,WAAK;AACL,WAAK,KAAK,SAAS,cAAc;AACjC,aAAO,KAAK,2EAA8B;AAAA,QACxC,gBAAgB,KAAK;AAAA,QACrB,mBAAmB,KAAK;AAAA,MAC1B,CAAC;AAED,YAAM,SAAS,KAAK,gBAAgB;AAEpC,UAAI;AACF,cAAM,KAAK,cAAc;AACzB,aAAK,gBAAgB;AACrB,aAAK,kBAAkB;AACvB,aAAK,KAAK,SAAS,WAAW;AAC9B,eAAO,KAAK,2CAAkB;AAC9B;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,WAAO,MAAM,+GAA0B;AACvC,SAAK,KAAK,SAAS,QAAQ;AAAA,EAC7B;AAAA,EAEQ,kBAAwB;AAC9B,SAAK,eAAe;AACpB,SAAK,qBAAqB,KAAK,IAAI;AAEnC,SAAK,kBAAkB,YAAY,MAAM;AAEvC,UAAI,KAAK,IAAI,IAAI,KAAK,qBAAqB,KAAK,oBAAoB;AAClE,eAAO,KAAK,qCAAqC;AAGjD,aAAK,eAAe;AAGpB,YAAI,KAAK,OAAO,MAAM;AACpB,gBAAM,SAAS,KAAK;AACpB,eAAK,MAAM;AAIX,iBAAO,UAAU;AACjB,iBAAO,UAAU;AACjB,iBAAO,YAAY;AAGnB,cAAI;AACF,mBAAO,MAAM;AAAA,UACf,QAAQ;AAAA,UAER;AAGA,cAAI,CAAC,KAAK,gBAAgB;AACxB,iBAAK,KAAK,cAAc;AAAA,UAC1B;AAAA,QACF;AACA;AAAA,MACF;AAGA,YAAM,KAAK,KAAK;AAChB,UAAI,KAAK,aAAa,MAAM,MAAM;AAChC,YAAI;AACF,aAAG,KAAK,KAAK,YAAY;AAAA,QAC3B,SAAS,KAAK;AACZ,iBAAO,KAAK,oBAAoB,GAAG;AAAA,QACrC;AAAA,MACF;AAAA,IACF,GAAG,KAAK,mBAAmB;AAAA,EAC7B;AAAA,EAEQ,iBAAuB;AAC7B,QAAI,KAAK,mBAAmB,MAAM;AAChC,oBAAc,KAAK,eAAe;AAClC,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=connection-config.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=progress.types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,30 @@
1
+ import { ServiceProtocol } from "@simplysm/service-common";
2
+ import { transferableEncode } from "@simplysm/core-common";
3
+ const protocol = new ServiceProtocol();
4
+ 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 = transferableEncode(decodeResult);
18
+ result = encoded.result;
19
+ transferList = encoded.transferList;
20
+ }
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
+ };
30
+ //# sourceMappingURL=client-protocol.worker.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/workers/client-protocol.worker.ts"],
4
+ "sourcesContent": ["/// <reference lib=\"webworker\" />\n\nimport { ServiceProtocol } from \"@simplysm/service-common\";\nimport { transferableEncode } from \"@simplysm/core-common\";\n\nconst protocol = new ServiceProtocol();\n\nself.onmessage = (event: MessageEvent) => {\n const { id, type, data } = event.data as {\n id: string;\n type: \"encode\" | \"decode\";\n data: unknown;\n };\n\n try {\n let result: unknown;\n let transferList: Transferable[] = [];\n\n if (type === \"encode\") {\n // [Main -> Worker] \uC778\uCF54\uB529 \uC694\uCCAD (data: { uuid, message })\n // message\uB294 \uC774\uBBF8 Plain Object\uB85C \uB118\uC5B4\uC634 (Structured Clone)\n const { uuid, message } = data as {\n uuid: string;\n message: Parameters<ServiceProtocol[\"encode\"]>[1];\n };\n const { chunks } = protocol.encode(uuid, message);\n\n // Buffer[]\uB294 \uC804\uC1A1 \uAC00\uB2A5\uD558\uBBC0\uB85C \uACB0\uACFC\uB85C \uBC18\uD658\n result = chunks;\n // \uACB0\uACFC\uBB3C \uCCAD\uD06C\uB4E4\uC758 \uB0B4\uBD80 ArrayBuffer\uB97C \uC18C\uC720\uAD8C \uC774\uC804 \uBAA9\uB85D\uC5D0 \uCD94\uAC00\n transferList = chunks.map((chunk) => chunk.buffer as ArrayBuffer);\n } else {\n // [Main -> Worker] \uB514\uCF54\uB529 \uC694\uCCAD (data: Uint8Array)\n // data\uB294 Uint8Array\uB85C \uB118\uC5B4\uC634\n const bytes = new Uint8Array(data as ArrayBuffer);\n const decodeResult = protocol.decode(bytes);\n\n // \uACB0\uACFC\uBB3C(\uAC1D\uCCB4)\uC744 \uC804\uC1A1 \uAC00\uB2A5\uD55C \uD615\uD0DC\uB85C \uBCC0\uD658 (Zero-Copy \uC900\uBE44)\n const encoded = transferableEncode(decodeResult);\n result = encoded.result;\n transferList = encoded.transferList;\n }\n\n // [Worker -> Main] \uC131\uACF5 \uC751\uB2F5\n self.postMessage({ id, type: \"success\", result }, { transfer: transferList });\n } catch (err) {\n // [Worker -> Main] \uC5D0\uB7EC \uC751\uB2F5\n self.postMessage({\n id,\n type: \"error\",\n error: err instanceof Error ? { message: err.message, stack: err.stack } : { message: String(err) },\n });\n }\n};\n"],
5
+ "mappings": "AAEA,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AAEnC,MAAM,WAAW,IAAI,gBAAgB;AAErC,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,mBAAmB,YAAY;AAC/C,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,OAAO,eAAe,QAAQ,EAAE,SAAS,IAAI,SAAS,OAAO,IAAI,MAAM,IAAI,EAAE,SAAS,OAAO,GAAG,EAAE;AAAA,IACpG,CAAC;AAAA,EACH;AACF;",
6
+ "names": []
7
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@simplysm/service-client",
3
+ "sideEffects": false,
4
+ "version": "13.0.0-beta.6",
5
+ "description": "심플리즘 패키지 - 서비스 모듈 (client)",
6
+ "author": "김석래",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/kslhunter/simplysm.git",
10
+ "directory": "packages/service-client"
11
+ },
12
+ "license": "Apache-2.0",
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "dependencies": {
17
+ "@simplysm/core-common": "workspace:*",
18
+ "@simplysm/orm-common": "workspace:*",
19
+ "@simplysm/service-common": "workspace:*",
20
+ "consola": "^3.4.2"
21
+ },
22
+ "devDependencies": {
23
+ "@types/ws": "^8.18.1",
24
+ "ws": "^8.19.0"
25
+ }
26
+ }
@@ -0,0 +1,102 @@
1
+ import type { Type } from "@simplysm/core-common";
2
+ import { Uuid } from "@simplysm/core-common";
3
+ import type { ServiceEventListener } from "@simplysm/service-common";
4
+ import type { ServiceTransport } from "../transport/service-transport";
5
+ import { createConsola } from "consola";
6
+
7
+ const logger = createConsola().withTag("service-client:EventClient");
8
+
9
+ export class EventClient {
10
+ private readonly _listenerMap = new Map<
11
+ string,
12
+ { eventName: string; info: unknown; cb: (data: unknown) => PromiseLike<void> | void }
13
+ >();
14
+
15
+ constructor(private readonly _transport: ServiceTransport) {
16
+ this._transport.on("event", async ({ keys, data }) => {
17
+ await this._executeByKey(keys, data);
18
+ });
19
+ }
20
+
21
+ async addListener<T extends ServiceEventListener<unknown, unknown>>(
22
+ eventListenerType: Type<T>,
23
+ info: T["$info"],
24
+ cb: (data: T["$data"]) => PromiseLike<void>,
25
+ ): Promise<string> {
26
+ const key = Uuid.new().toString();
27
+ // mangle 안전한 이벤트명 사용
28
+ const eventName = eventListenerType.prototype.eventName;
29
+
30
+ // 서버에 등록 요청
31
+ await this._transport.send({
32
+ name: "evt:add",
33
+ body: { key, name: eventName, info },
34
+ });
35
+
36
+ // 로컬 맵에 저장 (재연결 시 복구용)
37
+ this._listenerMap.set(key, {
38
+ eventName,
39
+ info,
40
+ cb,
41
+ });
42
+
43
+ return key;
44
+ }
45
+
46
+ async removeListener(key: string): Promise<void> {
47
+ await this._transport.send({ name: "evt:remove", body: { key } });
48
+ this._listenerMap.delete(key);
49
+ }
50
+
51
+ async emitToServer<T extends ServiceEventListener<unknown, unknown>>(
52
+ eventType: Type<T>,
53
+ infoSelector: (item: T["$info"]) => boolean,
54
+ data: T["$data"],
55
+ ): Promise<void> {
56
+ // mangle 안전한 이벤트명 사용
57
+ const eventName = eventType.prototype.eventName;
58
+
59
+ // 서버에 'gets' 요청을 보내 타겟을 확보
60
+ const listenerInfos = (await this._transport.send({
61
+ name: "evt:gets",
62
+ body: { name: eventName },
63
+ })) as { key: string; info: T["$info"] }[];
64
+
65
+ const targetKeys = listenerInfos.filter((item) => infoSelector(item.info)).map((item) => item.key);
66
+
67
+ if (targetKeys.length > 0) {
68
+ await this._transport.send({
69
+ name: "evt:emit",
70
+ body: { keys: targetKeys, data },
71
+ });
72
+ }
73
+ }
74
+
75
+ // 재연결 시 호출됨
76
+ async reRegisterAll(): Promise<void> {
77
+ for (const [key, value] of this._listenerMap.entries()) {
78
+ try {
79
+ await this._transport.send({
80
+ name: "evt:add",
81
+ body: { key, name: value.eventName, info: value.info },
82
+ });
83
+ } catch (err) {
84
+ logger.error("이벤트 리스너 복구 실패", { err, eventName: value.eventName });
85
+ }
86
+ }
87
+ }
88
+
89
+ // 서버에서 온 이벤트를 로컬 리스너에게 분배
90
+ private async _executeByKey(keys: string[], data: unknown): Promise<void> {
91
+ for (const key of keys) {
92
+ const entry = this._listenerMap.get(key);
93
+ if (entry != null) {
94
+ try {
95
+ await entry.cb(data);
96
+ } catch (err) {
97
+ logger.error("이벤트 핸들러 오류", { err, eventName: entry.eventName });
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }