@simplysm/sd-claude 14.0.88 → 14.0.89

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 (122) hide show
  1. package/claude/references/sd-simplysm14/README.md +17 -17
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +27 -53
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +37 -105
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +46 -43
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +22 -32
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +40 -55
  7. package/claude/references/sd-simplysm14/apis/angular/infra.md +40 -40
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +25 -53
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +70 -82
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +44 -39
  11. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +21 -36
  12. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +52 -65
  13. package/claude/references/sd-simplysm14/apis/angular/sheet.md +65 -70
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +33 -35
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +7 -7
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +29 -29
  17. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +45 -50
  18. package/claude/references/sd-simplysm14/apis/core-browser/README.md +42 -55
  19. package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +62 -0
  20. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +13 -12
  21. package/claude/references/sd-simplysm14/apis/core-common/README.md +222 -98
  22. package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +102 -53
  23. package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +128 -0
  24. package/claude/references/sd-simplysm14/apis/core-common/datetime.md +98 -64
  25. package/claude/references/sd-simplysm14/apis/core-common/errors.md +91 -0
  26. package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +34 -28
  27. package/claude/references/sd-simplysm14/apis/core-common/obj.md +104 -40
  28. package/claude/references/sd-simplysm14/apis/core-node/README.md +11 -8
  29. package/claude/references/sd-simplysm14/apis/core-node/consola.md +23 -31
  30. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +33 -22
  31. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +28 -25
  32. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +39 -53
  33. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +26 -29
  34. package/claude/references/sd-simplysm14/apis/core-node/worker.md +27 -29
  35. package/claude/references/sd-simplysm14/apis/excel/README.md +14 -14
  36. package/claude/references/sd-simplysm14/apis/lint/README.md +27 -21
  37. package/claude/references/sd-simplysm14/apis/lint/rules.md +89 -49
  38. package/claude/references/sd-simplysm14/apis/orm-common/README.md +5 -59
  39. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +98 -67
  40. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +107 -92
  41. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +99 -65
  42. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +83 -98
  43. package/claude/references/sd-simplysm14/apis/orm-common/types.md +62 -52
  44. package/claude/references/sd-simplysm14/apis/orm-node/README.md +62 -25
  45. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +27 -27
  46. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +12 -15
  47. package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +92 -45
  48. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +226 -108
  49. package/claude/references/sd-simplysm14/apis/service-client/README.md +84 -86
  50. package/claude/references/sd-simplysm14/apis/service-client/orm.md +14 -11
  51. package/claude/references/sd-simplysm14/apis/service-client/transport.md +33 -10
  52. package/claude/references/sd-simplysm14/apis/service-common/README.md +37 -23
  53. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +9 -9
  54. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +13 -13
  55. package/claude/references/sd-simplysm14/apis/service-server/README.md +81 -65
  56. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +32 -35
  57. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +44 -33
  58. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +34 -45
  59. package/claude/references/sd-simplysm14/apis/storage/README.md +24 -18
  60. package/claude/skills/sd-demo/SKILL.md +6 -0
  61. package/claude/skills/sd-impl/SKILL.md +4 -7
  62. package/claude/skills/sd-spec/SKILL.md +31 -858
  63. package/claude/skills/sd-spec/references/spec-authoring.md +519 -0
  64. package/claude/workflows/sd-docs.js +84 -0
  65. package/package.json +1 -1
  66. package/claude/references/sd-simplysm14/apis/orm-common/query-builder.md +0 -29
  67. package/claude/skills/sd-demo/evals/fixtures/inventory-list/.specs/inventory/spec.md +0 -99
  68. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/package.json +0 -12
  69. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/index.ts +0 -3
  70. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inbound/inbound.list.ts +0 -150
  71. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inventory/inventory-master.list.ts +0 -143
  72. package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/outbound/outbound.list.ts +0 -150
  73. package/claude/skills/sd-demo/evals/fixtures/inventory-list/pnpm-workspace.yaml +0 -2
  74. package/claude/skills/sd-demo/evals/fixtures/inventory-list/sd.config.ts +0 -12
  75. package/claude/skills/sd-demo/evals/golden.jsonl +0 -1
  76. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/package.json +0 -8
  77. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/src/.gitkeep +0 -0
  78. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tests/.gitkeep +0 -0
  79. package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tsconfig.json +0 -10
  80. package/claude/skills/sd-dev/evals/golden.jsonl +0 -1
  81. package/claude/skills/sd-docs/SKILL.md +0 -46
  82. package/claude/skills/sd-docs/evals/fixtures/new-write/.claude/references/sd-simplysm14/README.md +0 -7
  83. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/package.json +0 -5
  84. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/src/index.ts +0 -3
  85. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/package.json +0 -6
  86. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/src/index.ts +0 -1
  87. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/package.json +0 -5
  88. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/src/index.ts +0 -8
  89. package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/README.md +0 -7
  90. package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/apis/foo/README.md +0 -3
  91. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/package.json +0 -5
  92. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/src/index.ts +0 -3
  93. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/package.json +0 -6
  94. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/src/index.ts +0 -1
  95. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/package.json +0 -5
  96. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/src/index.ts +0 -8
  97. package/claude/skills/sd-docs/evals/golden.jsonl +0 -2
  98. package/claude/skills/sd-impl/evals/fixtures/case-a-new-screen/.specs/260513120000_warehouse/spec.md +0 -101
  99. package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/.specs/260513120000_warehouse/spec.md +0 -101
  100. package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/packages/app/src/screens/box-register/box-register.view.ts +0 -46
  101. package/claude/skills/sd-impl/evals/fixtures/case-c-new-cross/.specs/260513120000_warehouse/spec.md +0 -89
  102. package/claude/skills/sd-impl/evals/fixtures/case-d-spec-modify/.specs/260513120000_warehouse/spec.md +0 -101
  103. package/claude/skills/sd-impl/evals/golden.jsonl +0 -4
  104. package/claude/skills/sd-manual/evals/fixtures/new-manual/src/notification.ts +0 -25
  105. package/claude/skills/sd-manual/evals/fixtures/update-manual/.claude/references/sd-simplysm14/manuals/notification.md +0 -14
  106. package/claude/skills/sd-manual/evals/fixtures/update-manual/src/notification.ts +0 -37
  107. package/claude/skills/sd-manual/evals/golden.jsonl +0 -2
  108. package/claude/skills/sd-review/evals/fixtures/code-review/src/foo.ts +0 -7
  109. package/claude/skills/sd-review/evals/fixtures/doc-review/docs/foo.md +0 -4
  110. package/claude/skills/sd-review/evals/golden.jsonl +0 -2
  111. package/claude/skills/sd-skill/evals/fixtures/existing-skill/.claude/skills/todo-format/SKILL.md +0 -14
  112. package/claude/skills/sd-skill/evals/fixtures/new-skill/.gitkeep +0 -0
  113. package/claude/skills/sd-skill/evals/golden.jsonl +0 -2
  114. package/claude/skills/sd-spec/evals/fixtures/case-a-split//355/232/214/354/235/230/353/241/235.md +0 -20
  115. package/claude/skills/sd-spec/evals/fixtures/case-b-detail/.specs/260513120000_warehouse/spec.md +0 -95
  116. package/claude/skills/sd-spec/evals/golden.jsonl +0 -2
  117. package/claude/skills/sd-unpack/evals/fixtures/eml-with-text-attachment/meeting.eml +0 -21
  118. package/claude/skills/sd-unpack/evals/fixtures/simple-eml/meeting.eml +0 -10
  119. package/claude/skills/sd-unpack/evals/golden.jsonl +0 -2
  120. package/claude/skills/sd-use/evals/fixtures/empty/.gitkeep +0 -0
  121. package/claude/skills/sd-use/evals/golden.jsonl +0 -6
  122. /package/claude/{skills/sd-docs/references/doc-rules.md → workflows/sd-docs.rules.md} +0 -0
@@ -1,122 +1,116 @@
1
1
  # @simplysm/service-client
2
2
 
3
- WebSocket 기반 simplysm 서비스 서버에 접속하는 클라이언트. 서비스 메서드 RPC 호출, 인증, 서버 푸시 이벤트 구독, 파일 업/다운로드, ORM 원격 실행을 제공한다. Node.js/브라우저 양쪽에서 동작(브라우저 `WebSocket` 없으면 `ws` 패키지로 polyfill).
3
+ WebSocket 기반 서비스 서버(`@simplysm/service-server`)에 접속해 서비스 메서드 호출·서버 이벤트 구독·파일 업/다운로드·ORM 원격 실행을 수행하는 클라이언트. 브라우저(DOM Worker)와 Node.js(글로벌 `WebSocket` 없으면 `ws` polyfill, `worker_threads`) 양쪽에서 동작.
4
4
 
5
5
  ## 사용 트리거 인덱스
6
6
 
7
- - **createServiceClient / ServiceClient** — 서버 접속·서비스 메서드 호출·인증·접속 상태 이벤트가 필요할 때. 진입점.
8
- - **ServiceConnectionOptions** — 클라이언트 생성 시 호스트/포트/SSL/재연결 옵션을 때.
9
- - **getService / ServiceProxy** — 서버 서비스 인터페이스를 타입 안전 프록시로 호출할 때.
10
- - **이벤트 구독** (getEvent, addListener, emitEvent, ClientEventProxy, EventClient) — 서버 푸시 이벤트를 구독·발행할 때.
11
- - **파일 전송** (uploadFile, downloadFileBuffer, FileClient, BlobInput, FileCollection) — 파일을 업로드/다운로드할 때.
12
- - **진행률 추적** (ServiceProgress, ServiceProgressState, request/response/server-progress 이벤트) — 대용량 요청·응답 진행률을 추적할 때.
13
- - **환경 호환 유틸** (isWorkerSupported 등, BrowserWorker) — Node/브라우저 Worker 지원 여부를 판별할 때.
14
- - **ORM 원격 실행** (OrmClientConnector, OrmConnectOptions, OrmClientDbContextExecutor) — 서버 DB클라이언트 DbContext 로 트랜잭션 실행할 때. 자세히: [orm.md](./orm.md)
15
- - **저수준 전송 계층** (SocketProvider, ServiceTransport, ClientProtocolWrapper 및 create*) — 일반적으로 직접 쓰지 않음. `ServiceClient` 가 내부에서 조립. 자세히: [transport.md](./transport.md)
7
+ - **createServiceClient / ServiceClient** — 서버 접속, 서비스 메서드 호출, 인증, 연결 상태 추적이 필요할 때. 이 패키지의 주 진입점. (아래 인라인 섹션)
8
+ - **ServiceConnectionOptions** — 클라이언트 생성 시 접속 대상(host/port/ssl)·재연결 정책을 정할 때. (아래 인라인 섹션)
9
+ - **getService / ServiceProxy** — 서버 서비스 인터페이스를 타입 안전 프록시로 호출할 때. (아래 인라인 섹션)
10
+ - **이벤트 구독 (addListener / getEvent / emitEvent / ClientEventProxy / EventClient)** — 서버 푸시 이벤트를 구독·발행할 때. (아래 인라인 섹션)
11
+ - **파일 업/다운로드 (uploadFile / downloadFileBuffer / FileClient)**인증된 파일 업로드 또는 서버 상대경로 파일 다운로드 시. (아래 인라인 섹션)
12
+ - **진행률 (ServiceProgress / ServiceProgressState)** — 대용량 요청·응답의 청크 전송 진행률을 추적할 때. (아래 인라인 섹션)
13
+ - **환경 호환 타입·헬퍼 (BlobInput / FileCollection / BrowserWorker / isWorkerSupported 등)** — Node/browser 공용 코드에서 DOM 전용 타입 회피, Worker 지원 여부 분기 시. (아래 인라인 섹션)
14
+ - **ORM 원격 실행 (createOrmClientConnector / OrmClientConnector / OrmConnectOptions / OrmClientDbContextExecutor)**서버측 ORM DbContext 클라이언트에서 트랜잭션 단위로 실행할 때. 자세히: [orm.md](./orm.md)
15
+ - **저수준 전송 계층 (SocketProvider / ServiceTransport / ClientProtocolWrapper 및 create\*)** — 일반적으로 직접 쓰지 않음. `ServiceClient` 가 내부에서 조립. 소켓·하트비트·프로토콜 동작을 이해해야 할 때. 자세히: [transport.md](./transport.md)
16
16
 
17
- ## ServiceClient
17
+ ## 메인 클라이언트 (createServiceClient / ServiceClient)
18
18
 
19
- 서버 접속과 모든 RPC/이벤트/파일 기능의 진입점. `EventEmitter<ServiceClientEvents>` 상속.
19
+ `createServiceClient(name, options): ServiceClient` 클라이언트 인스턴스 생성. `new ServiceClient(name, options)` 와 동일.
20
20
 
21
- ```ts
22
- const client: ServiceClient = createServiceClient(name, options);
23
- ```
24
-
25
- - `createServiceClient(name: string, options: ServiceConnectionOptions): ServiceClient` — 클라이언트 인스턴스 생성. `new ServiceClient(...)` 와 동일.
26
- - `name: string` (생성자, readonly) — 클라이언트 식별 이름. WebSocket 접속 쿼리의 `clientName`, 파일 업로드 헤더 `x-sd-client-name` 으로 전송.
27
- - `options: ServiceConnectionOptions` (생성자, readonly) — 접속 옵션. 아래 ServiceConnectionOptions 참조.
28
-
29
- 상태 접근자:
21
+ - name: string — 클라이언트 식별 이름. WebSocket 접속 쿼리의 `clientName`·파일 업로드 헤더 `x-sd-client-name` 으로 서버에 전달.
22
+ - options: ServiceConnectionOptions 접속 대상·재연결 정책(아래 섹션).
30
23
 
31
- - `connected: boolean` (getter) 현재 WebSocket OPEN 상태인지. 재연결 중·종료 시 false.
32
- - `hostUrl: string` (getter) — `http(s)://<host>:<port>` 형태 HTTP 베이스 URL. ssl 이면 `https`. 파일 전송이 이 URL 을 사용.
24
+ `ServiceClient` `EventEmitter<ServiceClientEvents>` 상속하며 다음을 노출:
33
25
 
34
- 메서드:
35
-
36
- - `connect(): Promise<void>` — WebSocket 접속. 초기 접속 실패throw.
37
- - `close(): Promise<void>` 접속을 수동 종료(이후 재연결 함)하고 protocol worker 리소스 dispose. 종료 재사용하지 것.
38
- - `send(serviceName, methodName, params, progress?): Promise<unknown>` — 서비스 메서드 1건 원격 호출. `getService` 내부에서 이걸 호출하므로 보통 직접 쓰지 않음. `progress` 미지정이어도 client 의 `request/response/server-progress` 이벤트는 항상 발생.
39
- - `auth(token: string): Promise<void>` — 인증 토큰 전송. 성공 토큰을 보관해 재연결 자동 재인증. 파일 업로드 전 필수.
40
- - `getService<TService>(serviceName): ServiceProxy<TService>` — 타입 안전 서비스 프록시. 아래 getService 참조.
41
- - 이벤트 관련(`getEvent`, `addListener`, `removeListener`, `emitEvent`) 아래 "이벤트 구독" 참조. `addListener` 미접속throw.
42
- - 파일 관련(`uploadFile`, `downloadFileBuffer`) — 아래 "파일 전송" 참조.
26
+ - `name: string` (readonly) — 생성 시 받은 클라이언트 이름.
27
+ - `options: ServiceConnectionOptions` (readonly) — 생성 시 받은 접속 옵션.
28
+ - `connected: boolean` (getter) — 현재 WebSocket OPEN 상태인지. 재연결 중·종료 false. 이벤트 등록 가능 여부 판단에 사용.
29
+ - `hostUrl: string` (getter) — `http(s)://host:port` 형태의 HTTP 베이스 URL. `ssl` true https. 파일 클라이언트가 URL 을 사용.
30
+ - `connect(): Promise<void>` — 서버에 WebSocket 연결. 초기 연결 실패 throw.
31
+ - `close(): Promise<void>` — 연결 수동 종료(이후 재연결하지 않음) 프로토콜 워커 자원 해제. 종료 재사용 금지.
32
+ - `send(serviceName, methodName, params, progress?): Promise<unknown>` — 저수준 서비스 호출. `serviceName.methodName` 메시지를 전송하고 응답 반환. 보통 `getService` 프록시를 통해 간접 사용. `progress` 를 안 줘도 client 의 `request/response/server-progress` 이벤트는 항상 발생.
33
+ - `auth(token): Promise<void>` 인증 토큰 전송 내부에 보관. 보관 토큰은 재연결자동 재인증·파일 업로드 Bearer 인증에 재사용.
43
34
 
44
35
  ```ts
45
36
  const client = createServiceClient("my-app", { host: "localhost", port: 50080, ssl: false });
46
37
  await client.connect();
47
38
  await client.auth(jwtToken);
48
- client.on("state", (state) => console.log(state)); // "connected" | "closed" | "reconnecting"
49
39
  ```
50
40
 
51
- ServiceClientEvents (EventEmitter 이벤트):
41
+ **ServiceClientEvents** (EventEmitter 이벤트):
42
+
43
+ - `"request-progress": ServiceProgressState` — 요청(업로드) 청크 진행률. 요청 본문이 분할(청크 2개 이상) 전송될 때만 발생.
44
+ - `"response-progress": ServiceProgressState` — 응답(다운로드) 청크 진행률.
45
+ - `"server-progress": ServiceProgressState` — 서버가 처리 중 직접 보고하는 진행률(서버측 `progress` 메시지).
46
+ - `"state": "connected" | "closed" | "reconnecting"` — 연결 상태 변화. `"connected"` = 연결/재연결 성공, `"closed"` = 정상 종료 또는 재연결 한도 초과, `"reconnecting"` = 재연결 시도 중. `"connected"` 전이 시 보관 토큰으로 자동 재인증 및 이벤트 리스너 자동 복구 수행.
52
47
 
53
- - `state: "connected"|"closed"|"reconnecting"` — 접속 상태 변화. 재연결 성공("connected") 시 보관된 토큰으로 `auth` 재호출과 이벤트 리스너 자동 복구가 일어남.
54
- - `request-progress: ServiceProgressState` 요청 분할 전송 진행률(요청 청크가 2개 이상일 ).
55
- - `response-progress: ServiceProgressState` — 응답 수신 진행률.
56
- - `server-progress: ServiceProgressState` — 서버가 처리 중 보고하는 진행률.
48
+ ```ts
49
+ client.on("state", (s) => { if (s === "reconnecting") showOfflineBanner(); });
50
+ ```
57
51
 
58
52
  ## ServiceConnectionOptions
59
53
 
60
54
  `createServiceClient` 의 두 번째 인자.
61
55
 
62
- - `port: number` — 서버 포트. 필수.
63
- - `host: string` — 서버 호스트. 필수.
64
- - `ssl?: boolean` — TLS 사용 여부. true 면 `wss`/`https`, false·미지정이면 `ws`/`http`.
65
- - `maxReconnectCount?: number` — 끊김 시 최대 재연결 시도 횟수. 미지정 시 10. `0` 이면 재연결을 비활성화하고 끊김 즉시 포기.
56
+ - port: number — 서버 포트. 필수.
57
+ - host: string — 서버 호스트. 필수.
58
+ - ssl?: boolean — TLS 사용 여부. true 면 `wss`/`https`, false·미지정이면 `ws`/`http`. TLS 서버에 붙을 때 true.
59
+ - maxReconnectCount?: number — 연결 끊김 시 최대 재연결 시도 횟수. 미지정 시 10. **0 이면 재연결 비활성화**(끊기면 즉시 포기). 테스트·단발성 연결이면 0.
66
60
 
67
61
  ## getService / ServiceProxy
68
62
 
69
- 서버 서비스 인터페이스의 각 메서드를 `Promise` 반환 함수로 노출하는 프록시.
63
+ 서버 서비스 인터페이스의 각 메서드를 `Promise` 반환 함수로 노출하는 타입 안전 프록시.
70
64
 
71
- - `getService<TService>(serviceName: string): ServiceProxy<TService>` — `serviceName` 으로 등록된 서버 서비스에 대한 프록시 반환. 프록시 메서드 호출 = `client.send(serviceName, methodName, params)`.
72
- - `ServiceProxy<TService>` — `TService` 의 함수 멤버 각각을 `(...args) => Promise<Awaited<R>>` 로 매핑. 함수가 아닌 속성은 `never` 로 제외.
65
+ `getService<TService>(serviceName): ServiceProxy<TService>` — `serviceName` 으로 등록된 서버 서비스의 프록시 반환. 프록시 메서드 호출 = `client.send(serviceName, 메서드명, 인자배열)`.
66
+
67
+ - TService — 서버 서비스 메서드 인터페이스 타입. 컴파일 타임 시그니처 검증용(런타임 검증 아님).
68
+ - `ServiceProxy<TService>` — TService 의 각 함수 멤버를 `(...args) => Promise<Awaited<R>>` 로 매핑. 함수 아닌 속성은 `never` 로 제외.
73
69
 
74
70
  ```ts
75
- const svc = client.getService<MyService>("MyService");
71
+ const svc = client.getService<MyServiceMethods>("MyService");
76
72
  const result = await svc.echo("hi"); // 서버의 MyService.echo("hi") 호출, Promise<string>
77
73
  ```
78
74
 
79
- ## 이벤트 구독
75
+ ## 이벤트 구독 (addListener / getEvent / emitEvent)
76
+
77
+ 서버 푸시 이벤트는 `@simplysm/service-common` 의 `defineEvent` 산출물(`ServiceEventDef`. `$info` = 구독 필터 정보 타입, `$data` = 페이로드 타입) 단위로 구독. 등록한 리스너는 재연결 시 자동 복구됨.
78
+
79
+ `addListener<TEventDef>(eventDef, info, cb): Promise<string>` — 리스너 등록. **연결되지 않은 상태면 throw**. 반환 key 로 나중에 제거.
80
+
81
+ - eventDef: TEventDef — 이벤트 정의(`defineEvent` 결과). `$info`/`$data` 타입 출처.
82
+ - info: TEventDef["$info"] — 이 구독을 식별·필터링할 정보(서버가 emit 대상 선별에 사용).
83
+ - cb: (data) => PromiseLike<void> — 이벤트 수신 콜백. 콜백 내 예외는 로깅만 되고 전파되지 않음.
80
84
 
81
- 서버 푸시 이벤트를 기반으로 구독·발행. 이벤트 정의는 `@simplysm/service-common` `defineEvent` 로 만든 `ServiceEventDef`(`$info` = 구독 필터 정보 타입, `$data` = 페이로드 타입)를 사용.
85
+ `removeListener(key): Promise<void>` 등록 key 리스너 제거. 서버 전송 실패(연결 끊김 등) 무시(서버가 끊김 자동 정리).
82
86
 
83
- ServiceClient 메서드:
87
+ `emitEvent<TEventDef>(eventDef, infoSelector, data): Promise<void>` — 이벤트 발행. 서버에서 동일 이벤트 구독자 목록을 조회한 뒤 `infoSelector(info)` 가 true 인 대상에게만 data 전송.
84
88
 
85
- - `addListener<TEventDef>(eventDef, info, cb): Promise<string>` — 리스너 등록. `info: TEventDef["$info"]` = 이 구독을 식별·필터링할 정보, `cb: (data) => PromiseLike<void>` = 이벤트 수신 콜백. 반환값은 제거에 쓰는 리스너 key. 미접속 시 throw. 재연결 시 자동 재등록됨.
86
- - `removeListener(key: string): Promise<void>` 등록한 리스너 해제. 서버 미응답(연결 끊김)은 무시(서버가 끊김 시 자동 정리).
87
- - `emitEvent<TEventDef>(eventDef, infoSelector, data): Promise<void>` — 이벤트 발행. `infoSelector: (info) => boolean` 로 서버에 등록된 리스너 중 대상을 골라 `data: TEventDef["$data"]` 전달.
88
- - `getEvent<TEventDef>(eventDef): ClientEventProxy<TEventDef>` — 특정 이벤트 정의에 바인딩된 프록시 반환(eventDef 반복 전달 생략용).
89
+ - infoSelector: (item: $info) => boolean 발행 대상 구독자를 info 기준으로 필터.
90
+ - data: TEventDef["$data"]전송 페이로드.
89
91
 
90
- ClientEventProxy<TEventDef>:
92
+ `getEvent<TEventDef>(eventDef): ClientEventProxy<TEventDef>` — 특정 eventDef 에 바인딩된 프록시 반환(eventDef 반복 전달 생략용).
91
93
 
92
- - `addListener(info, cb): Promise<string>` — 위 `addListener` eventDef 고정판.
93
- - `removeListener(key): Promise<void>` — 리스너 해제.
94
- - `emit(infoSelector, data): Promise<void>` — 위 `emitEvent` 의 eventDef 고정판.
94
+ `ClientEventProxy<TEventDef>` 멤버: `addListener(info, cb)`, `removeListener(key)`, `emit(infoSelector, data)` — 위 client 메서드의 eventDef 고정판.
95
95
 
96
96
  ```ts
97
- const evtDef = defineEvent<{ channel: string }, string>("TestEvent");
98
- const key = await client.addListener(evtDef, { channel: "a" }, async (data) => { /* ... */ });
97
+ const evt = defineEvent<{ channel: string }, string>("Chat");
98
+ const key = await client.addListener(evt, { channel: "room1" }, async (msg) => render(msg));
99
+ await client.emitEvent(evt, (info) => info.channel === "room1", "hello");
99
100
  await client.removeListener(key);
100
101
  ```
101
102
 
102
103
  `EventClient` / `createEventClient(transport)` 는 `ServiceClient` 가 내부 조립에 쓰는 저수준 구현. 위 4개 메서드에 더해 `resubscribeAll(): Promise<void>`(보관된 모든 리스너를 서버에 재등록, 재연결 복구용)을 가짐. 일반 사용에선 직접 만들지 않음.
103
104
 
104
- ## 파일 전송
105
+ ## 파일 업/다운로드 (uploadFile / downloadFileBuffer)
105
106
 
106
- ServiceClient 메서드:
107
+ `uploadFile(files): Promise<ServiceUploadResult[]>` — `POST <hostUrl>/upload` (multipart) 로 파일 업로드. 보관 토큰을 `Authorization: Bearer` 로 전송하므로 **사전 `auth()` 필수**(미인증 시 throw).
107
108
 
108
- - `uploadFile(files): Promise<ServiceUploadResult[]>` 파일 업로드(`POST <hostUrl>/upload`, multipart). 보관된 인증 토큰을 `Authorization: Bearer` 전송하므로 사전 `auth()` 필수(미인증 throw). `files` 아래 3형식 허용.
109
- - `downloadFileBuffer(relPath: string): Promise<Bytes>` — `<hostUrl>/<relPath>` 를 GET 해 바이트(`Uint8Array`)로 반환. 응답 비정상(`!res.ok`) 시 throw.
109
+ - files: `File[] | FileCollection | { name: string; data: BlobInput }[]` 업로드 대상. 브라우저 `File` 배열, `FileCollection`(FileList 호환), 또는 `{ name, data }` 커스텀 객체 배열. 커스텀 객체의 data 가 `Blob` 아니면 `new Blob([data])` 로 감쌈.
110
110
 
111
- `files` 허용 형식 (`FileClient.upload` 기준):
111
+ `downloadFileBuffer(relPath): Promise<Bytes>` — `<hostUrl>/<relPath>` 를 GET 해 `Uint8Array` 반환. 응답 비정상(`!res.ok`) 시 throw.
112
112
 
113
- - `File[]`브라우저 `File` 객체 배열.
114
- - `FileCollection` — DOM `FileList` 와 구조 호환 인터페이스(`length`, `item(i)`, 인덱스 접근, iterable). DOM lib 없이도 타입체크 통과용 대체 타입.
115
- - `{ name: string; data: BlobInput }[]` — 커스텀 객체. `name` = 파일명, `data` = 본문. `data` 가 `Blob` 이 아니면 `new Blob([data])` 로 감쌈.
116
-
117
- `BlobInput` — Blob 생성 입력 타입(DOM `BlobPart` 대체): `Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | string` 중 하나.
118
-
119
- `FileClient` / `createFileClient(hostUrl, clientName)` 는 `ServiceClient` 내부 구현. `download(relPath)`/`upload(files, authToken)` 두 메서드를 가지며 직접 생성은 보통 불필요.
113
+ - relPath: string 서버 기준 상대경로. 선행 `/` 유무 모두 허용(없으면 자동 추가).
120
114
 
121
115
  ```ts
122
116
  await client.auth(token);
@@ -124,29 +118,33 @@ const results = await client.uploadFile([{ name: "a.txt", data: "hello" }]);
124
118
  const bytes = await client.downloadFileBuffer("/files/a.txt");
125
119
  ```
126
120
 
127
- ## 진행률 추적
121
+ `FileClient` / `createFileClient(hostUrl, clientName)` 는 `ServiceClient` 내부 구현. `download(relPath)` / `upload(files, authToken)` 두 메서드를 가지며 직접 생성은 보통 불필요.
122
+
123
+ ## 진행률 (ServiceProgress / ServiceProgressState)
128
124
 
129
125
  대용량 요청/응답이 청크로 분할될 때 진행 상황을 보고하는 콜백·상태 타입.
130
126
 
131
- ServiceProgress — `send` 류에 넘길 있는 콜백 집합.콜백은 `(state: ServiceProgressState) => void`:
127
+ `ServiceProgress` — `send(..., progress)` 넘기는 콜백 집합(메서드별 추적용, 전역 이벤트와 별개). 콜백 `(s: ServiceProgressState) => void`:
132
128
 
133
- - `request?` — 요청 청크 전송 진행(요청 청크 2개 이상일 때만).
134
- - `response?` — 응답 수신 진행. 분할 응답이었으면 완료 시 100%(`completedSize === totalSize`)를 한 번 더 보고.
135
- - `server?` — 서버가 처리 중 보고하는 진행(`name: "progress"` 메시지 수신 시).
129
+ - request? — 요청 청크 업로드 진행 시 호출(요청 청크 2개 이상일 때만).
130
+ - response? — 응답 청크 수신 진행 시 호출. 분할 응답이었으면 완료 시 100%(`completedSize === totalSize`)를 한 번 더 보고.
131
+ - server? — 서버가 처리 중 보고한 진행률 수신 시 호출(`name: "progress"` 메시지).
136
132
 
137
- ServiceProgressState:
133
+ `ServiceProgressState` — 진행률 스냅샷.
138
134
 
139
- - `uuid: string` — 해당 요청/응답을 식별하는 UUID. 동시 요청 구분용.
140
- - `totalSize: number` — 전체 바이트 크기.
141
- - `completedSize: number`현재까지 처리된 바이트 크기. `totalSize` 같아지면 완료.
135
+ - uuid: string — 해당 요청/응답 식별자. 동시 요청 구분용.
136
+ - totalSize: number — 전체 바이트 수.
137
+ - completedSize: number — 완료 바이트 수. `completedSize === totalSize` 완료.
142
138
 
143
139
  `send` 호출 시 위 콜백과 무관하게 ServiceClient 의 `request/response/server-progress` 이벤트도 항상 발생하므로, 전역 추적이면 콜백 대신 `client.on("response-progress", ...)` 를 써도 됨.
144
140
 
145
- ## 환경 호환 유틸 (browser-compat)
141
+ ## 환경 호환 타입·헬퍼 (browser-compat)
146
142
 
147
- Node/브라우저 Worker 지원 여부 판별 함수와 Worker 인터페이스. 프로토콜 인코딩/파싱을 Worker 오프로딩할지 결정할 내부에서 사용.
143
+ Node/browser 공용 코드에서 DOM 전용 타입을 피하고 Worker 지원 여부를 분기하기 위한 타입·함수. 프로토콜 인코딩/파싱 Worker 오프로딩 판단 내부에서 사용.
148
144
 
149
- - `isBrowserWorkerSupported(): boolean` — `globalThis` DOM `Worker` 있는지(브라우저 환경 판별).
150
- - `isNodeWorkerSupported(): boolean` Node.js 런타임(`process.versions.node` 존재)인지.
151
- - `isWorkerSupported(): boolean` 하나라도 참인지(브라우저 Worker 또는 Node worker_threads 가용 여부).
152
- - `BrowserWorker` (interface) — DOM lib 없이 타입체크하기 위한 Worker 최소 인터페이스: `onmessage`/`onerror` 핸들러, `postMessage(message, transfer?)`, `terminate()`.
145
+ - `BlobInput` = `Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | string` — `Blob` 생성자가 받는 데이터 타입(DOM `BlobPart` 대체). `uploadFile` 커스텀 객체 data 타입.
146
+ - `FileCollection` (interface) — DOM `FileList` 대체. `length`, `item(index): File | null`, 인덱스 접근, `[Symbol.iterator]` 보유. 브라우저 `FileList` 와 구조적 호환.
147
+ - `BrowserWorker` (interface) — DOM `Worker` 최소 인터페이스(`onmessage`/`onerror` 핸들러, `postMessage(message, transfer?)`, `terminate()`). DOM lib 없이 타입체크 통과용.
148
+ - `isBrowserWorkerSupported(): boolean` `globalThis` `Worker` 존재 여부. 브라우저 DOM Worker 가용 판단.
149
+ - `isNodeWorkerSupported(): boolean` — `process.versions.node` 존재 여부. Node `worker_threads` 가용 판단.
150
+ - `isWorkerSupported(): boolean` — 위 둘 중 하나라도 true. 프로토콜 인코딩 오프로딩 가능 여부 판단(미지원 시 메인 스레드 폴백).
@@ -4,19 +4,21 @@
4
4
 
5
5
  ## OrmConnectOptions<T extends DbContext>
6
6
 
7
- `OrmClientConnector` 의 `connect`/`connectWithoutTransaction` 에 넘기는 설정.
7
+ `OrmClientConnector` 의 `connect` / `connectWithoutTransaction` 에 넘기는 설정.
8
8
 
9
- - `DbClass: new (executor, opt) => T` — DbContext 클래스 생성자. `opt` = `{ database: string; schema?: string }`. 실제 인스턴스를 만들 사용.
10
- - `connOpt: DbConnOptions & { configName: string }` — 서버 DB 접속 설정. `configName` = 서버에 등록된 DB 설정 이름(서버가 이 이름으로 실제 접속 정보를 찾음).
11
- - `dbContextOpt?: { database: string; schema: string }` — DbContext 가 사용할 데이터베이스/스키마를 명시. 미지정 시 서버 `getInfo()` 가 돌려주는 기본 database/schema 사용. `database` 가 config·서버 양쪽에서 모두 비면 `"database는 필수입니다."` throw(결측을 임의 보정하지 않음).
9
+ - DbClass: `new (executor, opt) => T` — DbContext 클래스 생성자. `opt` = `{ database: string; schema?: string }`. 실제 인스턴스 생성에 사용.
10
+ - connOpt: `DbConnOptions & { configName: string }` — 서버측 DB 접속 설정. `configName` = 서버에 등록된 DB 설정 이름(서버가 이 이름으로 실제 접속 정보를 찾음).
11
+ - dbContextOpt?: `{ database: string; schema: string }` — DbContext 가 데이터베이스/스키마 명시. 미지정 시 서버 `getInfo()` 가 돌려주는 기본 database/schema 사용. `database` 가 dbContextOpt·서버 양쪽에서 모두 비면 `"database는 필수입니다."` throw(결측을 임의 보정하지 않음).
12
12
 
13
13
  ## OrmClientConnector
14
14
 
15
15
  DbContext 를 만들고 트랜잭션 경계를 잡아 콜백을 실행하는 커넥터. 사용 전 `ServiceClient.connect()` 로 소켓이 연결돼 있어야 함(RPC 의존).
16
16
 
17
- - `createOrmClientConnector(serviceClient: ServiceClient): OrmClientConnector` — 커넥터 생성. 내부에서 `OrmClientDbContextExecutor` 로 RPC 위임.
18
- - `connect<T, R>(config, callback): Promise<R>` — DbContext 를 만들고 **트랜잭션 안에서** `callback(db)` 실행. 콜백 정상 반환 시 커밋, throw 시 롤백(콜백 내 다건 작업이 원자 처리됨). 외래키 제약 위반 메시지(`a parent row: a foreign key constraint`, `conflicted with the REFERENCE`)는 사용자용 한국어 메시지로 감싸 `cause` 에 원본을 담아 재 throw.
17
+ `createOrmClientConnector(serviceClient): OrmClientConnector` — 커넥터 생성. 내부에서 `OrmClientDbContextExecutor` 로 RPC 위임.
18
+
19
+ - `connect<T, R>(config, callback): Promise<R>` — DbContext 를 만들고 **트랜잭션 안에서** `callback(db)` 실행. 콜백 정상 반환 시 커밋, throw 시 롤백(콜백 내 다건 작업이 원자 처리됨). 외래키 제약 위반 메시지(`a parent row: a foreign key constraint`, `conflicted with the REFERENCE`)는 사용자용 한국어 메시지로 감싸 원본을 `cause` 에 담아 재 throw.
19
20
  - `connectWithoutTransaction<T, R>(config, callback): Promise<R>` — 트랜잭션 없이 `callback(db)` 실행. 조회 전용·트랜잭션 불필요 작업에 사용.
21
+ - config: `OrmConnectOptions<T>` — 위 옵션. callback: `(db: T) => Promise<R> | R` — DbContext 를 받아 쿼리하는 콜백.
20
22
 
21
23
  ```ts
22
24
  const connector = createOrmClientConnector(client);
@@ -33,13 +35,14 @@ await connector.connect(
33
35
 
34
36
  `DbContextExecutor`(`@simplysm/orm-common`) 구현체. 모든 메서드를 `ServiceClient.getService<OrmService>("Orm")` RPC 로 위임. 보통 `OrmClientConnector` 가 내부에서 생성하므로 직접 다룰 일은 드묾.
35
37
 
36
- - `new OrmClientDbContextExecutor(client: ServiceClient, opt: DbConnOptions & { configName: string })` — 생성. 생성 시 `Orm` 서비스 프록시 확보.
38
+ `new OrmClientDbContextExecutor(client, opt)` — 생성. opt = `DbConnOptions & { configName: string }`. 생성 시 `Orm` 서비스 프록시 확보.
39
+
37
40
  - `getInfo(): Promise<{ dialect; database?; schema? }>` — 서버 DB 의 dialect 및 기본 database/schema 조회.
38
41
  - `connect(): Promise<void>` — 서버에 커넥션 생성 요청, 반환된 `connId` 보관. 이후 모든 실행 메서드는 `connId` 없으면(미연결) throw.
39
- - `beginTransaction(isolationLevel?): Promise<void>` — 트랜잭션 시작. `isolationLevel` = 격리 수준(`IsolationLevel`), 미지정 시 서버 기본값.
42
+ - `beginTransaction(isolationLevel?): Promise<void>` — 트랜잭션 시작. isolationLevel = 격리 수준(`IsolationLevel`), 미지정 시 서버 기본값.
40
43
  - `commitTransaction(): Promise<void>` — 커밋.
41
44
  - `rollbackTransaction(): Promise<void>` — 롤백.
42
45
  - `close(): Promise<void>` — 서버 커넥션 종료 후 보관한 `connId` 해제.
43
- - `executeDefs<T>(defs: QueryDef[], options?): Promise<T[][]>` — 쿼리 정의 배열 실행, 정의별 결과 배열을 반환. `options` = 정의별 `ResultMeta`(결과 매핑 메타, 항목별 nullable).
44
- - `executeParametrized(query: string, params?): Promise<unknown[][]>` — 파라미터 바인딩 raw SQL 실행.
45
- - `bulkInsert(tableName, columnDefs, records): Promise<void>` — 대량 삽입. `columnDefs` = `Record<string, ColumnMeta>`(컬럼별 메타), `records` = 삽입할 행 객체 배열.
46
+ - `executeDefs<T>(defs, options?): Promise<T[][]>` — 쿼리 정의 배열(`QueryDef[]`) 실행, 정의별 결과 배열 반환. options = 정의별 `ResultMeta`(결과 매핑 메타, 항목별 nullable).
47
+ - `executeParametrized(query, params?): Promise<unknown[][]>` — 파라미터 바인딩 raw SQL 실행. query = SQL 문자열, params = 바인딩 값 배열.
48
+ - `bulkInsert(tableName, columnDefs, records): Promise<void>` — 대량 삽입. columnDefs = `Record<string, ColumnMeta>`(컬럼별 메타), records = 삽입할 행 객체 배열.
@@ -6,31 +6,54 @@
6
6
 
7
7
  WebSocket 1개의 연결·하트비트·자동 재연결을 담당.
8
8
 
9
- - `createSocketProvider(url: string, clientName: string, maxReconnectCount: number): SocketProvider` — 프로바이더 생성. `url` = `ws(s)://host:port/ws`, `clientName` = 접속 쿼리에 실리는 식별명, `maxReconnectCount` = 최대 재연결 시도(0 이면 재연결 안 함). 내부 상수: 하트비트 ping 5초 간격, 30초 무수신 시 타임아웃, 재연결 3초 간격. 1바이트 `0x01` ping 전송, `0x02` pong 수신은 무시.
9
+ `createSocketProvider(url, clientName, maxReconnectCount): SocketProvider` — 프로바이더 생성.
10
+
11
+ - url: string — `ws(s)://host:port/ws`. 접속 시 `ver=2`, 생성된 `clientId`(UUID), `clientName` 쿼리를 붙임.
12
+ - clientName: string — 접속 쿼리에 실리는 식별명.
13
+ - maxReconnectCount: number — 최대 재연결 시도. 0 이면 재연결 안 함.
14
+
15
+ 내부 상수: 하트비트 ping 5초 간격, 30초 무수신 시 타임아웃, 재연결 3초 간격. 1바이트 `0x01` ping 전송, `0x02` pong 수신은 무시(타임스탬프만 갱신).
16
+
10
17
  - `clientName: string` (readonly) — 생성 시 받은 식별명.
11
18
  - `connected: boolean` (getter) — 소켓이 OPEN 인지.
12
19
  - `connect(): Promise<void>` — 접속 시작. 실패 시 throw, 성공 시 재연결 카운트 리셋하고 `state: "connected"` emit.
13
20
  - `close(): Promise<void>` — 수동 종료. 이후 자동 재연결 안 함. `state: "closed"` emit.
14
21
  - `send(data: Bytes): Promise<void>` — 바이트 전송. 일정 시간 내 미연결이면 throw.
15
- - `on/off(type, listener)` 이벤트 구독/해제. 이벤트(`SocketProviderEvents`): `message: Bytes`(수신 바이트), `state: "connected"|"closed"|"reconnecting"`(연결 상태 변화).
22
+ - `on(type, listener)` / `off(type, listener)` 이벤트 구독/해제.
16
23
 
17
- 타임아웃 감지 시 소켓을 강제 정리하고(중복 onclose 재연결 방지로 핸들러 해제) 수동 종료가 아니면 재연결을 시도한다. 최대 시도 초과 시 `state: "closed"` emit.
24
+ `SocketProviderEvents`:
25
+
26
+ - message: Bytes — 수신 바이트(ping/pong 1바이트 제어 프레임 제외).
27
+ - state: `"connected" | "closed" | "reconnecting"` — 연결 상태 변화. `"connected"` = 연결/재연결 성공, `"closed"` = 수동 종료 또는 재연결 한도 초과, `"reconnecting"` = 재연결 시도 중.
28
+
29
+ 하트비트 타임아웃 감지 시 소켓을 강제 정리하고(늦은 onclose 로 인한 중복 재연결 방지로 핸들러 해제) 수동 종료가 아니면 재연결을 시도. 최대 시도 초과 시 `state: "closed"` emit.
18
30
 
19
31
  ## ServiceTransport / createServiceTransport
20
32
 
21
33
  요청별 uuid 매칭, 응답/에러/진행률/서버이벤트 디스패치를 담당.
22
34
 
23
- - `createServiceTransport(socket: SocketProvider, protocol: ClientProtocolWrapper): ServiceTransport` — 트랜스포트 생성. 소켓 `message` 를 받아 decode 후 종류별 분기. 소켓이 `closed`/`reconnecting` 되면 대기 중인 모든 요청을 reject(메모리 해제).
24
- - `send(message: ServiceClientMessage, progress?: ServiceProgress): Promise<unknown>` — 요청 1건 전송 후 응답 Promise 반환. uuid 생성→리스너 등록→encode→청크 순차 전송. 응답 수신(`response`) 시 resolve, 에러(`error`) 시 서버 에러 필드를 머지한 `Error` 로 reject.
25
- - `on/off(type, listener)`이벤트 구독/해제. 이벤트(`ServiceTransportEvents`): `event: { keys: string[]; data: unknown }`(서버가 푸시한 `evt:on` 메시지. `EventClient` 가 이걸 구독해 로컬 리스너로 디스패치).
35
+ `createServiceTransport(socket, protocol): ServiceTransport` — 트랜스포트 생성. 소켓 `message` 를 받아 decode 후 종류별 분기. 소켓이 `closed`/`reconnecting` 되면 대기 중인 모든 요청을 reject(메모리 해제).
36
+
37
+ - socket: SocketProvider하위 소켓.
38
+ - protocol: ClientProtocolWrapper — 인코드/디코드 래퍼.
26
39
 
27
- decode 실패 시에도 헤더에서 uuid 를 선추출해 해당 요청만 reject 한다. 분할 응답이면 완료 시 `progress.response` 로 100% 를 한 번 더 보고.
40
+ 멤버:
41
+
42
+ - `send(message, progress?): Promise<unknown>` — 요청 1건 전송 후 응답 Promise 반환. uuid 생성 → 리스너 등록 → encode → 청크 순차 전송. 응답(`response`) 수신 시 resolve, 에러(`error`) 시 서버 에러 필드를 머지한 `Error` 로 reject. message = `ServiceClientMessage`, progress = `ServiceProgress`(선택).
43
+ - `on(type, listener)` / `off(type, listener)` — 이벤트 구독/해제.
44
+
45
+ `ServiceTransportEvents`:
46
+
47
+ - event: `{ keys: string[]; data: unknown }` — 서버가 푸시한 `evt:on` 메시지. `EventClient` 가 이걸 구독해 keys 에 매칭되는 로컬 리스너로 디스패치.
48
+
49
+ decode 실패 시에도 헤더 16바이트에서 uuid 를 선추출해 해당 요청만 reject. 분할 응답이면 완료 시 `progress.response` 로 100% 를 한 번 더 보고.
28
50
 
29
51
  ## ClientProtocolWrapper / createClientProtocolWrapper
30
52
 
31
53
  인코드/디코드를 크기 기준으로 Worker 에 오프로딩하는 래퍼. `@simplysm/service-common` 의 `ServiceProtocol` 을 감쌈.
32
54
 
33
- - `createClientProtocolWrapper(protocol: ServiceProtocol): ClientProtocolWrapper` — 래퍼 생성. 임계값 30KB. Worker 미가용·임계값 이하면 메인 스레드 처리로 폴백.
34
- - `encode(uuid: string, message: ServiceMessage): Promise<{ chunks: Bytes[]; totalSize: number }>` — 메시지를 청크 배열로 인코드. body 가 Uint8Array, 30KB 초과 문자열, 길이 100 초과 배열, 또는 Uint8Array 항목 배열이면 Worker 사용.
35
- - `decode(bytes: Bytes): Promise<ServiceMessageDecodeResult<ServiceMessage>>` 수신 바이트 디코드. 청크 재조립(stateful)은 메시지의 청크가 흩어지지 않도록 **항상 메인 스레드 단일 누적기**에서 수행하고, 재조립 완료 30KB 초과 JSON 파싱(stateless)만 Worker 위임. 미완료(progress) 그대로 반환.
55
+ `createClientProtocolWrapper(protocol): ClientProtocolWrapper` — 래퍼 생성. 임계값 30KB. Worker 미가용·임계값 이하면 메인 스레드 처리로 폴백.
56
+
57
+ - `encode(uuid, message): Promise<{ chunks: Bytes[]; totalSize: number }>` 메시지를 청크 배열로 인코드. body Uint8Array, 30KB 초과 문자열, 길이 100 초과 배열, 또는 항목이 Uint8Array 배열이면 Worker 사용. message = `ServiceMessage`.
58
+ - `decode(bytes): Promise<ServiceMessageDecodeResult<ServiceMessage>>` — 수신 바이트 디코드. 청크 재조립(stateful)은 한 메시지의 청크가 흩어지지 않도록 **항상 메인 스레드 단일 누적기**에서 수행하고(#35), 재조립 완료 후 30KB 초과 JSON 파싱(stateless)만 Worker 에 위임. 미완료(progress) 면 그대로 반환.
36
59
  - `dispose(): void` — 프로토콜과 Worker 리졸버 정리. `ServiceClient.close()` 에서 호출.
@@ -1,19 +1,19 @@
1
1
  # @simplysm/service-common
2
2
 
3
- 서버·클라이언트가 공유하는 서비스 통신 계약. 바이너리 프로토콜(인코딩/청킹/재조립), 서비스 인터페이스 타입(ORM·자동업데이트·업로드), 타입 안전 이벤트 정의, 앱 메뉴/권한 구조 모델을 한 패키지에 둔다.
3
+ 서버·클라이언트가 공유하는 서비스 통신 계약. 바이너리 프로토콜(인코딩/청킹/재조립) 메시지 타입, 서비스 인터페이스 타입(ORM·자동업데이트·업로드), 타입 안전 이벤트 정의, 앱 메뉴/권한 구조 모델을 한 패키지에 둔다. 구현체가 아니라 양쪽이 합의하는 타입·프로토콜만 제공한다.
4
4
 
5
5
  ## 사용 트리거 인덱스
6
6
 
7
- - **서비스 프로토콜** — 서버·클라이언트 간 메시지를 바이너리로 인코딩/디코딩하거나, 3MB 초과 메시지의 청킹·재조립을 다룰 때. 메시지 타입·`PROTOCOL_CONFIG` 상수 포함. (자세히: [protocol.md](./protocol.md))
8
- - **앱 구조 / 권한** — 메뉴 트리(`AppStructureItem`)를 정의하거나, 사용자 활성 모듈 기준으로 권한을 평탄화·필터링할 때. (자세히: [app-structure.md](./app-structure.md))
9
- - **defineEvent / ServiceEventDef** — 서버·클라 공통 패키지에서 타입 안전한 서비스 이벤트를 정의해 emit/구독에 쓸 때. (아래 인라인)
7
+ - **서비스 프로토콜** — 서버·클라이언트 간 메시지를 바이너리로 인코딩/디코딩하거나, 3MB 초과 메시지의 청킹·재조립을 다룰 때. 메시지 타입·`PROTOCOL_CONFIG` 상수 포함. 자세히: [protocol.md](./protocol.md)
8
+ - **앱 구조 / 권한** — 메뉴 트리(`AppStructureItem`)를 정의하거나, 사용자 활성 모듈 기준으로 권한을 평탄화·필터링할 때. 자세히: [app-structure.md](./app-structure.md)
9
+ - **defineEvent / ServiceEventDef** — 서버·클라 공통 패키지에서 타입 안전 서비스 이벤트를 정의해 emit/구독에 쓸 때. (아래 인라인)
10
10
  - **OrmService / DbConnOptions** — DB 연결·트랜잭션·쿼리 실행 서비스 시그니처를 구현/호출할 때. (아래 인라인)
11
11
  - **AutoUpdateService** — 클라이언트 자동 업데이트 최신 버전 조회 서비스를 구현/호출할 때. (아래 인라인)
12
12
  - **ServiceUploadResult** — 파일 업로드 응답 결과를 다룰 때. (아래 인라인)
13
13
 
14
14
  ## 이벤트 정의 (defineEvent / ServiceEventDef)
15
15
 
16
- 서버·클라이언트가 공유하는 공통 패키지에서 이벤트를 1회 정의해 양쪽에서 동일 객체로 emit/구독한다. 정의 객체를 `emitEvent`/`addListener` 에 그대로 넘기면 이름·타입이 자동 추론된다.
16
+ 서버·클라이언트가 공유하는 공통 패키지에서 이벤트를 1회 정의해, 양쪽이 동일 객체를 import 해 emit/구독한다. 정의 객체를 그대로 `emitEvent`/`addListener` 에 넘기면 이름·info·data 타입이 자동 추론된다.
17
17
 
18
18
  ```ts
19
19
  function defineEvent<TInfo = unknown, TData = unknown>(eventName: string): ServiceEventDef<TInfo, TData>;
@@ -25,45 +25,59 @@ interface ServiceEventDef<TInfo = unknown, TData = unknown> {
25
25
  }
26
26
  ```
27
27
 
28
- - `defineEvent(eventName)` 의 `eventName` — 이벤트 식별 문자열. 서버/클라가 같은 정의를 import 하므로 충돌 없게 유일해야 함.
29
- - `TInfo` — 구독자 필터링용 정보 타입(예: 특정 orderId 만 수신). 서버 emit 시 필터 함수 인자 타입.
28
+ - `defineEvent` 의 `eventName: string` — 이벤트 식별 문자열. 서버/클라가 같은 정의를 import 하므로 충돌 없게 유일해야 함.
29
+ - `TInfo` — 구독자 필터링용 정보 타입(예: 특정 orderId 만 수신). 서버 emit 시 필터 함수가 받는 인자 타입.
30
30
  - `TData` — 이벤트 페이로드 타입. 리스너 콜백이 받는 데이터 타입.
31
- - `eventName: string` (필드) — 런타임 식별자.
32
- - `$info: TInfo` / `$data: TData` — 타입 추출 전용 마커. 런타임 값은 `undefined` 이며 직접 읽지 말 것.
31
+ - `ServiceEventDef.eventName: string` — 런타임 식별자. emit/구독 매칭에 실제 사용되는 유일한 필드.
32
+ - `$info: TInfo` / `$data: TData` (readonly) — 타입 추출 전용 마커. 런타임 값은 `undefined`(미사용)이며 제네릭 추론용으로만 존재. 직접 읽지 말 것.
33
33
 
34
34
  ```ts
35
+ // 공통 패키지: 정의 + export
35
36
  export const OrderUpdated = defineEvent<{ orderId: number }, { status: string }>("OrderUpdated");
37
+ // 서버: 정의 객체 전달 → 이름·타입 자동 추론
36
38
  await server.emitEvent(OrderUpdated, (info) => info.orderId === 123, { status: "shipped" });
39
+ // 클라이언트: 구독 (data 타입 추론됨)
37
40
  await client.addListener(OrderUpdated, { orderId: 123 }, async (data) => console.log(data.status));
38
41
  ```
39
42
 
40
43
  ## 서비스 인터페이스 타입
41
44
 
42
- 서버가 구현하고 클라이언트가 프록시로 호출하는 서비스 계약. 본 패키지는 구현체가 아니라 타입만 제공한다.
45
+ 서버가 구현하고 클라이언트가 프록시로 호출하는 서비스 계약. 본 패키지는 타입만 제공한다.
43
46
 
44
47
  ### OrmService
45
48
 
46
- DB 연결·트랜잭션·쿼리 실행. MySQL/MSSQL/PostgreSQL 지원.
49
+ DB 연결·트랜잭션·쿼리 실행. MySQL/MSSQL/PostgreSQL 지원. 인자는 `@simplysm/orm-common` 의 `Dialect`/`IsolationLevel`/`QueryDef`/`ColumnMeta`/`ResultMeta` 사용.
47
50
 
48
- - `getInfo(opt: DbConnOptions & { configName: string })` — 연결 설정의 `dialect`/`database?`/`schema?` 메타 조회. 실제 연결 정보 확인용(`configName` 필수).
49
- - `connect(opt: DbConnOptions & { configName: string }): Promise<number>` — 연결 후 `connId`(이후 호출에 쓸 핸들) 반환.
50
- - `close(connId)` — 해당 연결 해제.
51
- - `beginTransaction(connId, isolationLevel?)` — 트랜잭션 시작. `isolationLevel` 생략 시 드라이버 기본값.
52
- - `commitTransaction(connId)` / `rollbackTransaction(connId)` — 트랜잭션 커밋 / 롤백.
53
- - `executeParametrized(connId, query, params?): Promise<unknown[][]>` — 파라미터 바인딩 SQL 직접 실행. 결과는 결과셋 배열의 행 배열(다중 결과셋).
54
- - `executeDefs(connId, defs, options?): Promise<unknown[][]>` — `QueryDef[]` 구조화 쿼리 일괄 실행. `options` def `ResultMeta`(컬럼 타입 변환 지정, 항목별 `undefined` 허용).
55
- - `bulkInsert(connId, tableName, columnDefs, records)` — `columnDefs`(컬럼명→`ColumnMeta`) 기반 대량 INSERT.
51
+ - `getInfo(opt: DbConnOptions & { configName: string }): Promise<{ dialect: Dialect; database?: string; schema?: string }>` — 연결 대상의 `dialect`/`database?`/`schema?` 메타 조회. `database`/`schema` dialect 따라 없을 수 있어 optional(결측 그대로 전파). (`configName` 필수.)
52
+ - `connect(opt: DbConnOptions & { configName: string }): Promise<number>` — 연결 후 `connId`(이후 모든 호출에 쓸 연결 핸들) 반환. (`configName` 필수.)
53
+ - `close(connId: number): Promise<void>` — 해당 연결 해제.
54
+ - `beginTransaction(connId: number, isolationLevel?: IsolationLevel): Promise<void>` — 트랜잭션 시작. `isolationLevel` 생략 시 드라이버 기본 격리수준.
55
+ - `commitTransaction(connId: number): Promise<void>` — 트랜잭션 커밋.
56
+ - `rollbackTransaction(connId: number): Promise<void>` — 트랜잭션 롤백.
57
+ - `executeParametrized(connId: number, query: string, params?: unknown[]): Promise<unknown[][]>` — 파라미터 바인딩 raw SQL 직접 실행. 다중 결과셋이라 결과셋별 배열(`unknown[][]`) 반환. `params` 생략 바인딩 없는 평문 쿼리.
58
+ - `executeDefs(connId: number, defs: QueryDef[], options?: (ResultMeta | undefined)[]): Promise<unknown[][]>` — `QueryDef[]` 구조화 쿼리 일괄 실행. `options[i]` 는 `defs[i]` 결과의 `ResultMeta`(컬럼 타입 변환 지정); 메타 불필요한 def 자리엔 `undefined`(결측 보존, 빈 값 치환 금지).
59
+ - `bulkInsert(connId: number, tableName: string, columnDefs: Record<string, ColumnMeta>, records: Record<string, unknown>[]): Promise<void>` — 대량 INSERT. `columnDefs`=컬럼명→`ColumnMeta`(타입/변환), `records`=컬럼명→값 객체 배열.
56
60
 
57
- `DbConnOptions = { configName?: string; config?: Record<string, unknown> }`
58
- - `configName` 서버에 사전 등록된 DB 설정 이름 참조. `config` 인라인 연결 설정 객체. 둘 중 하나로 연결 대상을 지정하며, `getInfo`/`connect` 시그니처에서는 `configName` 이 필수로 교차됨.
61
+ ```ts
62
+ type DbConnOptions = { configName?: string; config?: Record<string, unknown> };
63
+ ```
64
+
65
+ - `configName?: string` — 서버에 사전 등록된 DB 설정 이름 참조. `getInfo`/`connect` 시그니처에서는 `& { configName: string }` 으로 교차되어 필수가 됨.
66
+ - `config?: Record<string, unknown>` — 인라인 연결 설정 객체. 등록 이름 대신 직접 접속 정보를 줄 때.
59
67
 
60
68
  ### AutoUpdateService
61
69
 
62
- - `getLastVersion(platform: string): Promise<{ version; downloadPath } | undefined>` — `platform`(예: `"win32"`/`"darwin"`/`"linux"`) 별 최신 버전 정보 반환. 등록된 버전이 없으면 `undefined`(결측 그대로 전파).
70
+ 클라이언트 앱의 최신 배포 버전을 조회하는 원격 서비스 인터페이스.
71
+
72
+ - `getLastVersion(platform: string): Promise<{ version: string; downloadPath: string } | undefined>` — `platform`(대상 OS 식별자, 예: `"win32"`/`"darwin"`/`"linux"`) 별 최신 버전 정보. 등록된 버전이 없으면 `undefined`(결측 그대로 전파, 빈 객체로 치환하지 않음). `version`=최신 버전 문자열, `downloadPath`=설치 파일 다운로드 경로.
63
73
 
64
74
  ## ServiceUploadResult
65
75
 
66
- 서버에 업로드된 파일의 응답 정보.
76
+ 서버에 업로드된 파일 1건의 응답 정보.
77
+
78
+ ```ts
79
+ interface ServiceUploadResult { path: string; filename: string; size: number; }
80
+ ```
67
81
 
68
82
  - `path: string` — 서버 내 저장 경로.
69
83
  - `filename: string` — 원본 파일명(클라이언트가 보낸 이름).
@@ -4,37 +4,37 @@
4
4
 
5
5
  ## 트리 타입
6
6
 
7
- `AppStructureItem<TModule>` = `AppStructureGroupItem` | `AppStructureLeafItem`. 메뉴 트리의 노드.
7
+ `AppStructureItem<TModule>` = `AppStructureGroupItem<TModule>` | `AppStructureLeafItem<TModule>`. 메뉴 트리의 노드(`children` 유무로 그룹/리프 판별).
8
8
 
9
9
  `AppStructureGroupItem<TModule>` — 하위 노드를 갖는 그룹 노드.
10
10
  - `code: string` — 노드 식별 코드. 권한 codeChain 에 누적됨.
11
11
  - `title: string` — 표시 제목. titleChain 에 누적됨.
12
- - `modules?: TModule[]` — 이 중 하나라도 활성이면 표시(OR).
13
- - `requiredModules?: TModule[]` — 전부 활성이어야 표시(AND).
12
+ - `modules?: TModule[]` — 이 중 하나라도 활성이면 통과(OR). 빈 배열/`undefined` 면 제약 없음.
13
+ - `requiredModules?: TModule[]` — 전부 활성이어야 통과(AND).
14
14
  - `icon?: string` — 메뉴 아이콘.
15
15
  - `children: AppStructureItem<TModule>[]` — 하위 노드 배열(필수, 그룹 판별 키).
16
16
 
17
17
  `AppStructureLeafItem<TModule>` — 실제 화면 노드.
18
- - `code` / `title` / `modules?` / `requiredModules?` / `icon?` — 그룹과 동일 의미.
18
+ - `code: string` / `title: string` / `modules?` / `requiredModules?` / `icon?` — 그룹과 동일 의미.
19
19
  - `perms?: ("use" | "edit")[]` — 이 화면 직접 권한. `"use"`=조회 권한 / `"edit"`=편집 권한. 각 항목이 평탄 권한 1건이 됨.
20
20
  - `subPerms?: AppStructureSubPermission<TModule>[]` — 화면 내 세부 권한 묶음.
21
21
  - `url?: string` — 라우팅 경로.
22
22
  - `isNotMenu?: boolean` — true 면 메뉴에 노출 안 함(권한만 존재하는 화면), false/미지정이면 메뉴 노출.
23
23
 
24
24
  `AppStructureSubPermission<TModule>` — 화면 하위 세부 권한 묶음.
25
- - `code` / `title` / `modules?` / `requiredModules?` — 동일 의미.
25
+ - `code: string` / `title: string` / `modules?` / `requiredModules?` — 동일 의미. subPerm 자체의 modules/requiredModules 도 별도 검사됨.
26
26
  - `perms: ("use" | "edit")[]` — 이 세부 묶음의 권한 종류(필수). `"use"`=조회 / `"edit"`=편집.
27
27
 
28
28
  `FlatPermission<TModule>` — 평탄화 결과 1건.
29
29
  - `titleChain: string[]` — 루트→해당 권한까지 제목 경로.
30
- - `codeChain: string[]` — 코드 + perm/subPerm 코드 누적 경로(권한 식별자).
30
+ - `codeChain: string[]` — code + perm/subPerm 코드 누적 경로(권한 식별자).
31
31
  - `modulesChain: TModule[][]` — 경로상 각 레벨 modules 누적.
32
32
 
33
33
  ## 유틸 함수
34
34
 
35
35
  - `isUsableModules(modules, requiredModules, usableModules): boolean` — 단일 노드 가시성 판정. `requiredModules` 전부 포함(AND) **그리고** `modules` 중 하나 포함(또는 빈 배열/`undefined` 면 통과, OR). 둘 중 하나만 검사하려면 나머지 인자에 `undefined` 전달.
36
- - `isUsableModulesChain(modulesChain, requiredModulesChain, usableModules): boolean` — 루트부터의 누적 체인 전체 통과 여부. 각 레벨 modules 는 OR, 각 레벨 requiredModules 는 AND 로 모두 만족해야 true.
37
- - `getFlatPermissions(items, usableModules): FlatPermission[]` — 트리를 BFS 순회하며 `usableModules` 로 필터된 모든 권한을 평탄 목록으로 산출. 모듈 체인을 통과한 노드의 `perms`·`subPerms.perms` 각각을 `FlatPermission` 1건으로 변환. subPerm 은 자체 modules/requiredModules 도 추가 검사.
36
+ - `isUsableModulesChain(modulesChain, requiredModulesChain, usableModules): boolean` — 루트부터 누적된 체인 전체 통과 여부. 각 레벨 modules 는 OR, 각 레벨 requiredModules 는 AND 로 모두 만족해야 true.
37
+ - `getFlatPermissions(items, usableModules): FlatPermission<TModule>[]` — 트리를 BFS 순회하며 `usableModules` 로 필터된 모든 권한을 평탄 목록으로 산출. 모듈 체인을 통과한 노드의 `perms`·`subPerms.perms` 각각을 `FlatPermission` 1건으로 변환. subPerm 은 자체 modules/requiredModules 도 추가 검사.
38
38
 
39
39
  ```ts
40
40
  const flats = getFlatPermissions(appStructure, currentUser.usableModules);
@@ -45,4 +45,4 @@ if (isUsableModules(item.modules, item.requiredModules, usableModules)) showMenu
45
45
  주의:
46
46
  - `usableModules` 가 `undefined` 이면 modules/requiredModules 가 지정된 노드는 통과 못 함(`includes` 가 false).
47
47
  - `modules` 가 비었거나 `undefined` 인 노드는 모듈 제약 없이 항상 통과(OR 기본).
48
- - `codeChain` 마지막 요소는 perm(`"use"`/`"edit"`) 또는 subPerm.code 다음의 perm 으로 끝남.
48
+ - `codeChain` 마지막 요소는 perm(`"use"`/`"edit"`), 또는 subPerm.code 뒤의 perm 으로 끝남.