@simplysm/sd-claude 14.0.98 → 14.0.99

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 (77) hide show
  1. package/claude/references/sd-simplysm14/README.md +16 -16
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +81 -153
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +179 -205
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +71 -57
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +49 -109
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +58 -86
  7. package/claude/references/sd-simplysm14/apis/angular/kanban.md +32 -40
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +38 -52
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +86 -110
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +54 -86
  11. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +82 -74
  12. package/claude/references/sd-simplysm14/apis/angular/sheet.md +56 -80
  13. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +15 -15
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +21 -21
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +79 -53
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +9 -11
  17. package/claude/references/sd-simplysm14/apis/core-browser/README.md +15 -15
  18. package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +20 -20
  19. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +18 -18
  20. package/claude/references/sd-simplysm14/apis/core-common/README.md +20 -49
  21. package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +66 -55
  22. package/claude/references/sd-simplysm14/apis/core-common/collection-ext.md +83 -56
  23. package/claude/references/sd-simplysm14/apis/core-common/errors.md +32 -21
  24. package/claude/references/sd-simplysm14/apis/core-common/obj.md +57 -39
  25. package/claude/references/sd-simplysm14/apis/core-common/serialization.md +36 -30
  26. package/claude/references/sd-simplysm14/apis/core-common/value-types.md +69 -41
  27. package/claude/references/sd-simplysm14/apis/core-node/README.md +4 -4
  28. package/claude/references/sd-simplysm14/apis/core-node/consola.md +15 -13
  29. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +11 -7
  30. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +8 -8
  31. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +29 -20
  32. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +14 -6
  33. package/claude/references/sd-simplysm14/apis/core-node/worker.md +3 -3
  34. package/claude/references/sd-simplysm14/apis/excel/README.md +3 -3
  35. package/claude/references/sd-simplysm14/apis/excel/cell.md +32 -32
  36. package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +23 -24
  37. package/claude/references/sd-simplysm14/apis/excel/style.md +24 -30
  38. package/claude/references/sd-simplysm14/apis/excel/utils.md +20 -23
  39. package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +60 -71
  40. package/claude/references/sd-simplysm14/apis/excel/wrapper.md +36 -36
  41. package/claude/references/sd-simplysm14/apis/lint/README.md +7 -9
  42. package/claude/references/sd-simplysm14/apis/lint/recommended.md +59 -37
  43. package/claude/references/sd-simplysm14/apis/lint/rules.md +81 -74
  44. package/claude/references/sd-simplysm14/apis/orm-common/README.md +6 -6
  45. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +112 -78
  46. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +131 -75
  47. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +126 -82
  48. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +170 -113
  49. package/claude/references/sd-simplysm14/apis/orm-common/types.md +102 -48
  50. package/claude/references/sd-simplysm14/apis/orm-node/README.md +12 -13
  51. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +3 -3
  52. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +5 -5
  53. package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +67 -65
  54. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +130 -123
  55. package/claude/references/sd-simplysm14/apis/service-client/README.md +63 -63
  56. package/claude/references/sd-simplysm14/apis/service-client/orm.md +22 -22
  57. package/claude/references/sd-simplysm14/apis/service-client/transport.md +30 -26
  58. package/claude/references/sd-simplysm14/apis/service-common/README.md +8 -8
  59. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +13 -6
  60. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +1 -1
  61. package/claude/references/sd-simplysm14/apis/service-server/README.md +43 -47
  62. package/claude/references/sd-simplysm14/apis/service-server/built-in-services.md +35 -0
  63. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +20 -19
  64. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +23 -25
  65. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +9 -9
  66. package/claude/references/sd-simplysm14/apis/storage/README.md +26 -26
  67. package/claude/references/sd-simplysm14/manuals/client-component.md +9 -1
  68. package/claude/references/sd-simplysm14/manuals/client-crud.md +1 -1
  69. package/claude/references/sd-simplysm14/manuals/client-orm.md +1 -0
  70. package/claude/references/sd-simplysm14/manuals/client-service.md +1 -0
  71. package/claude/references/sd-simplysm14/manuals/client-shared-data.md +1 -0
  72. package/claude/references/sd-simplysm14/manuals/client-ssg.md +1 -0
  73. package/claude/sd-system-prompt.md +11 -26
  74. package/claude/skills/sd-docs/references/subagent-prompt.md +4 -3
  75. package/claude/skills/sd-spec/SKILL.md +87 -18
  76. package/claude/skills/sd-spec/references/format.md +2 -2
  77. package/package.json +1 -1
@@ -1,39 +1,39 @@
1
1
  # @simplysm/service-client
2
2
 
3
- WebSocket 으로 서비스 서버에 접속해 서비스 RPC 호출·서버 푸시 이벤트 구독/발행·파일 업/다운로드·서버측 ORM 원격 실행을 수행하는 클라이언트. 브라우저와 Node.js 양쪽에서 동작(Node 에서 글로벌 `WebSocket` 이 없으면 모듈 로드 `ws` polyfill).
3
+ WebSocket 으로 서비스 서버에 붙어 서비스 메서드 RPC 호출·서버 푸시 이벤트 구독/발행·파일 업/다운로드·서버측 ORM 원격 실행을 수행하는 클라이언트. 브라우저·Node.js 양쪽에서 동작하며, Node 에서 글로벌 `WebSocket` 이 없으면 소켓 모듈 로드 시점에 `ws` 패키지로 polyfill 한다.
4
4
 
5
5
  ## 사용 트리거 인덱스
6
6
 
7
- - **createServiceClient / ServiceClient** — 서버 접속, 서비스 호출, 인증, 연결 상태 추적이 필요할 때. 이 패키지의 주 진입점. (아래 인라인 섹션)
8
- - **ServiceConnectionOptions** — 클라이언트 생성 시 접속 대상(host/port/ssl)·재연결 정책을 정할 때. (아래 인라인 섹션)
9
- - **getService / ServiceProxy** — 서버 서비스 인터페이스를 타입 안전 프록시로 호출할 때. (아래 인라인 섹션)
10
- - **이벤트 구독·발행 (addListener / removeListener / emitEvent / getEvent / 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)
7
+ - **createServiceClient / ServiceClient** — 서버 접속·서비스 호출·인증·연결 상태 추적의 주 진입점. (아래 인라인)
8
+ - **ServiceConnectionOptions** — 접속 대상(host/port/ssl)·재연결 정책 지정. (아래 인라인)
9
+ - **getService / ServiceProxy** — 서버 서비스를 타입 안전 프록시로 호출. (아래 인라인)
10
+ - **이벤트 구독·발행 (getEvent / addListener / removeListener / emitEvent / ClientEventProxy / EventClient)** — 서버 푸시 이벤트 구독·발행. (아래 인라인)
11
+ - **파일 업/다운로드 (uploadFile / downloadFileBuffer / FileClient)** — 인증 업로드, 서버 상대경로 다운로드. (아래 인라인)
12
+ - **진행률 (ServiceProgress / ServiceProgressState)** — 청크 분할되는 대용량 요청/응답의 진행 추적. (아래 인라인)
13
+ - **환경 호환 타입·헬퍼 (BlobInput / FileCollection / BrowserWorker / isBrowserWorkerSupported / isNodeWorkerSupported / isWorkerSupported)** — Node/browser 공용 코드의 DOM 타입 회피·Worker 분기. (아래 인라인)
14
+ - **ORM 원격 실행 (createOrmClientConnector / OrmClientConnector / OrmConnectOptions / OrmClientDbContextExecutor)** — 서버 DbContext 를 클라이언트에서 트랜잭션 단위로 실행. 자세히: [orm.md](./orm.md)
15
+ - **저수준 전송 계층 (SocketProvider / ServiceTransport / ClientProtocolWrapper 및 create\*)** — `ServiceClient` 가 내부에서 조립하는 소켓·하트비트·프로토콜·청크 모듈. 직접 쓰지 않음. 자세히: [transport.md](./transport.md)
16
16
 
17
- > 앱(Angular) 레이어에서는 `ServiceClient` 를 화면에서 직접 만들지 않고 `AppServiceProvider`(root provider) 의 `client` getter 를 경유해 서비스·이벤트·ORM 진입점을 모은다. 아래 예시는 client 직접 호출 형태로 보여주지만, 실제 앱 코드는 manuals/client-service.md·client-orm.md 의 provider 패턴을 따른다.
17
+ > 앱(Angular)에서는 화면이 `ServiceClient` 를 직접 만들지 않고 `AppServiceProvider`(root provider)의 `client` getter 를 경유해 서비스·이벤트·ORM 진입점을 모은다. 아래 예시는 client 직접 호출 형태지만, 실제 앱 코드는 manuals/client-service.md·client-orm.md·event.md 의 provider 패턴을 따른다.
18
18
 
19
19
  ## 메인 클라이언트 (createServiceClient / ServiceClient)
20
20
 
21
21
  `createServiceClient(name, options): ServiceClient` — 클라이언트 인스턴스 생성. `new ServiceClient(name, options)` 와 동일.
22
22
 
23
- - name: string — 클라이언트 식별 이름. WebSocket 접속 쿼리의 `clientName`·파일 업로드 헤더 `x-sd-client-name` 으로 서버에 전달. 서버 로그·연결 구분에 사용.
24
- - options: ServiceConnectionOptions — 접속 대상·재연결 정책 (아래 섹션).
23
+ - `name: string` — 클라이언트 식별 이름. WebSocket 접속 쿼리의 `clientName`·파일 업로드 헤더 `x-sd-client-name` 으로 서버에 전달. 서버측 연결 구분·로깅용.
24
+ - `options: ServiceConnectionOptions` — 접속 대상·재연결 정책 (아래 섹션).
25
25
 
26
26
  `ServiceClient` 는 `EventEmitter<ServiceClientEvents>` 를 상속하며 다음을 노출:
27
27
 
28
- - name: string (readonly) — 생성 시 받은 클라이언트 이름.
29
- - options: ServiceConnectionOptions (readonly) — 생성 시 받은 접속 옵션.
30
- - connected: boolean (getter) — 현재 WebSocket 이 OPEN 상태인지. 재연결 중·종료 시 false. 이벤트 등록 가능 여부 판단에 사용 (`addListener` 는 false 면 throw).
31
- - hostUrl: string (getter) — `http(s)://host:port` 형태의 HTTP 베이스 URL. `ssl` 이 true 면 https. 파일 업/다운로드가 이 URL 을 베이스로 사용.
32
- - connect(): Promise\<void\> — 서버에 WebSocket 연결. 초기 연결 실패 시 throw. 통신(서비스 호출·이벤트 등록) 전에 반드시 1회 호출.
33
- - close(): Promise\<void\> — 연결 수동 종료(이후 자동 재연결 안 함) 프로토콜 워커 자원 해제. 종료한 인스턴스는 재사용하지 말 것.
34
- - send(serviceName, methodName, params, progress?): Promise\<unknown\> — 저수준 서비스 호출. `serviceName.methodName` 메시지를 보내고 응답 반환. 보통 `getService` 프록시로 간접 호출. progress 인자를 주지 않아도 client 의 `request/response/server-progress` 이벤트는 항상 발생.
35
- - auth(token): Promise\<void\> — 인증 토큰 전송 내부 보관. 보관 토큰은 재연결 시 자동 재인증·파일 업로드 Bearer 인증에 재사용. 파일 업로드 전에는 반드시 선행.
36
- - getService / getEvent / addListener / removeListener / emitEvent / uploadFile / downloadFileBuffer — 아래 각 섹션 참조.
28
+ - `name: string` (readonly) — 생성 시 받은 이름.
29
+ - `options: ServiceConnectionOptions` (readonly) — 생성 시 받은 접속 옵션.
30
+ - `connected: boolean` (getter) — 소켓이 OPEN 상태인지. 재연결 중·종료 시 false. `addListener` 는 false 면 throw 하므로 등록 가능 여부 판단에 사용.
31
+ - `hostUrl: string` (getter) — `ssl` 이면 `https://`, 아니면 `http://` 로 시작하는 `프로토콜://host:port` HTTP 베이스 URL. 파일 업/다운로드가 이 URL 을 베이스로 사용.
32
+ - `connect(): Promise<void>` — 서버에 WebSocket 연결. 초기 연결 실패 시 throw. 모든 통신(서비스 호출·이벤트 등록) 전에 1회 호출.
33
+ - `close(): Promise<void>` — 연결 수동 종료(이후 자동 재연결 안 함) 프로토콜 워커 자원 해제(`protocolWrapper.dispose()`). 종료한 인스턴스는 재사용하지 않음.
34
+ - `send(serviceName, methodName, params, progress?): Promise<unknown>` — 저수준 서비스 호출. `serviceName.methodName` 메시지를 보내고 응답 반환. 보통 `getService` 프록시로 간접 호출. progress 인자를 주지 않아도 client 의 `request/response/server-progress` 이벤트는 항상 발생.
35
+ - `auth(token): Promise<void>` — 인증 토큰을 서버에 전송하고 내부 보관. 보관 토큰은 재연결 시 자동 재인증·파일 업로드 Bearer 인증에 재사용됨. 파일 업로드 선행 필수.
36
+ - `getService / getEvent / addListener / removeListener / emitEvent / uploadFile / downloadFileBuffer` — 아래 각 섹션.
37
37
 
38
38
  ```ts
39
39
  const client = createServiceClient("my-app", { host: "localhost", port: 50080, ssl: false });
@@ -43,10 +43,10 @@ await client.auth(jwtToken);
43
43
 
44
44
  **ServiceClientEvents** (EventEmitter 이벤트):
45
45
 
46
- - "request-progress": ServiceProgressState — 요청(업로드) 청크 진행률. 요청 본문이 청크 2개 이상으로 분할될 때만 발생.
47
- - "response-progress": ServiceProgressState — 응답(다운로드) 청크 수신 진행률. 분할 응답 완료 시 100% 한 번 더 보고.
48
- - "server-progress": ServiceProgressState — 서버가 처리 중 직접 보고하는 진행률(서버측 `progress` 메시지 수신 시).
49
- - "state": "connected" | "closed" | "reconnecting" — 연결 상태 변화. "connected" = 연결/재연결 성공(이 전이 시 보관 토큰으로 자동 재인증 + 이벤트 리스너 자동 복구), "closed" = 정상 종료 또는 재연결 한도 초과, "reconnecting" = 재연결 시도 중. 오프라인 배너 토글 등에 사용.
46
+ - `"request-progress": ServiceProgressState` — 요청(업로드) 청크 진행률. 요청 본문이 청크 2개 이상으로 분할될 때만 발생.
47
+ - `"response-progress": ServiceProgressState` — 응답(다운로드) 청크 수신 진행률. 분할 응답이면 완료 시 100% 한 번 더 보고.
48
+ - `"server-progress": ServiceProgressState` — 서버가 처리 중 보고하는 진행률(서버측 `name: "progress"` 메시지 수신 시).
49
+ - `"state": "connected" | "closed" | "reconnecting"` — 연결 상태 전이. `"connected"` = 연결/재연결 성공(이 전이 시 보관 토큰으로 자동 재인증 + 등록 리스너 자동 복구), `"closed"` = 정상 종료 또는 재연결 한도 초과, `"reconnecting"` = 재연결 시도 중. 오프라인 배너 토글 등에 사용.
50
50
 
51
51
  ```ts
52
52
  client.on("state", (s) => { if (s === "reconnecting") showOfflineBanner(); });
@@ -56,50 +56,50 @@ client.on("state", (s) => { if (s === "reconnecting") showOfflineBanner(); });
56
56
 
57
57
  `createServiceClient` 의 두 번째 인자.
58
58
 
59
- - port: number — 서버 포트. 필수.
60
- - host: string — 서버 호스트. 필수.
61
- - ssl?: boolean — TLS 사용 여부. true 면 `wss`/`https`, false·미지정이면 `ws`/`http`. TLS 서버에 붙을 때만 true.
62
- - maxReconnectCount?: number — 연결 끊김 시 최대 재연결 시도 횟수. 미지정 시 10. 0 이면 재연결 비활성화(끊기면 즉시 포기). 테스트·단발성 연결이면 0.
59
+ - `port: number` — 서버 포트. 필수.
60
+ - `host: string` — 서버 호스트. 필수.
61
+ - `ssl?: boolean` — TLS 사용 여부. true 면 `wss`/`https`, false·미지정이면 `ws`/`http`. TLS 서버에 붙을 때만 true.
62
+ - `maxReconnectCount?: number` — 연결 끊김 시 최대 재연결 시도 횟수. 미지정 시 10. 0 이면 재연결을 비활성화하고 끊기면 즉시 포기. 테스트·단발성 연결이면 0.
63
63
 
64
64
  ## getService / ServiceProxy
65
65
 
66
- 서버 서비스 인터페이스의 각 메서드를 `Promise` 반환 함수로 노출하는 타입 안전 프록시.
66
+ 서버 서비스의 각 메서드를 `Promise` 반환 함수로 노출하는 타입 안전 프록시.
67
67
 
68
- `getService<TService>(serviceName): ServiceProxy<TService>` — `serviceName` 으로 등록된 서버 서비스의 프록시 반환. 프록시 메서드 호출은 내부적으로 `client.send(serviceName, 메서드명, 인자배열)` 로 위임.
68
+ `getService<TService>(serviceName): ServiceProxy<TService>` — `serviceName` 으로 등록된 서버 서비스의 프록시 반환. 프록시 메서드 호출은 내부적으로 `client.send(serviceName, 메서드명, 인자배열)` 로 위임(`Proxy` 기반, 모든 속성 접근을 비동기 RPC 함수로 변환).
69
69
 
70
- - TService — 서버 서비스 메서드 인터페이스 타입. 컴파일 타임 시그니처 검증용(런타임 검증 아님). 앱에선 server 패키지가 export 한 `ServiceMethods<typeof XxxService>` 사용.
71
- - serviceName: string — 서버의 `defineService("XxxName", ...)` 이름과 일치해야 함.
72
- - ServiceProxy\<TService\> — TService 의 각 함수 멤버를 `(...args) => Promise<Awaited<R>>` 로 매핑하는 타입 변환기. 원본이 동기 반환이어도 Promise 로 래핑됨. 함수 아닌 속성은 `never` 로 제외.
70
+ - `TService` — 서버 서비스 메서드 인터페이스 타입. 컴파일 타임 시그니처 검증 전용(런타임 검증 아님). 앱에선 server 패키지가 export 한 `ServiceMethods<typeof XxxService>` 사용.
71
+ - `serviceName: string` — 서버의 `defineService("XxxName", ...)` 이름과 일치해야 함.
72
+ - `ServiceProxy<TService>``TService` 의 각 함수 멤버를 `(...args) => Promise<Awaited<R>>` 로 매핑하는 타입 변환기. 원본이 동기 반환이어도 Promise 로 래핑. 함수 아닌 속성은 `never` 로 제외.
73
73
 
74
74
  ```ts
75
75
  const svc = client.getService<TestServiceMethods>("TestService");
76
- const result = await svc.echo("hi"); // 서버 TestService.echo("hi") 호출, Promise<string>
76
+ const result = await svc.echo("hi"); // 서버 TestService.echo("hi") 호출 Promise<string>
77
77
  ```
78
78
 
79
- ## 이벤트 구독·발행 (addListener / removeListener / emitEvent / getEvent)
79
+ ## 이벤트 구독·발행 (getEvent / addListener / removeListener / emitEvent)
80
80
 
81
- 서버 푸시 이벤트는 `@simplysm/service-common` 의 `defineEvent` 산출물(`ServiceEventDef`. `$info` = 구독 필터 정보 타입, `$data` = 페이로드 타입) 단위로 다룬다. 등록한 리스너는 재연결 시 자동 복구됨.
81
+ 서버 푸시 이벤트는 `@simplysm/service-common` 의 `defineEvent` 산출물(`ServiceEventDef`. `$info` = 구독 필터 정보 타입, `$data` = 페이로드 타입) 단위로 다룬다. 등록한 리스너는 재연결 시 자동 재구독된다.
82
82
 
83
83
  `addListener<TEventDef>(eventDef, info, cb): Promise<string>` — 리스너 등록. 미연결(`connected === false`)이면 throw("서버에 연결되지 않았습니다."). 반환 key 로 나중에 제거.
84
84
 
85
- - eventDef: TEventDef — 이벤트 정의(`defineEvent` 결과). `$info`/`$data` 타입의 출처.
86
- - info: TEventDef["$info"] — 이 구독을 식별·필터링할 정보. 발행측 selector 가 이 값을 보고 전달 여부 결정.
87
- - cb: (data: $data) => PromiseLike\<void\> — 이벤트 수신 콜백. 콜백 내 예외는 로깅만 되고 호출부로 전파되지 않음.
85
+ - `eventDef: TEventDef` — 이벤트 정의(`defineEvent` 결과). `$info`/`$data` 타입의 출처이자 라우팅 키(`eventName`).
86
+ - `info: TEventDef["$info"]` — 이 구독을 식별·필터링할 정보. 발행측 selector 가 이 값을 보고 전달 여부 결정.
87
+ - `cb: (data: $data) => PromiseLike<void>` — 이벤트 수신 콜백. 콜백 내 예외는 로깅만 되고 호출부로 전파되지 않음.
88
88
 
89
- `removeListener(key): Promise<void>` — 등록 key 로 리스너 제거. 서버 전송 실패(연결 끊김 등)는 무시(서버가 끊김 시 리스너를 자동 정리하므로 안전).
89
+ `removeListener(key): Promise<void>` — 등록 key 로 리스너 제거(로컬 맵 삭제 + 서버 `evt:remove` 전송). 서버 전송 실패(연결 끊김 등)는 무시서버가 끊김 시 리스너를 자동 정리하므로 안전.
90
90
 
91
91
  `emitEvent<TEventDef>(eventDef, infoSelector, data): Promise<void>` — 이벤트 발행. 서버에서 동일 이벤트 구독자 목록을 조회한 뒤 `infoSelector(info)` 가 true 인 대상에게만 data 전송. 매칭 대상이 0개면 전송 자체를 생략.
92
92
 
93
- - infoSelector: (item: $info) => boolean — 발행 대상 구독자를 info 기준으로 필터. true 반환 구독자에게만 전달. 전체 전송이면 `() => true`.
94
- - data: TEventDef["$data"] — 전송 페이로드.
93
+ - `infoSelector: (item: $info) => boolean` — 발행 대상 구독자를 info 기준으로 필터. true 반환 구독자에게만 전달. 전체 전송이면 `() => true`.
94
+ - `data: TEventDef["$data"]` — 전송 페이로드.
95
95
 
96
96
  `getEvent<TEventDef>(eventDef): ClientEventProxy<TEventDef>` — 특정 eventDef 에 바인딩된 프록시 반환. eventDef 를 매번 넘기지 않고 짧게 쓰려 할 때(앱의 `AppServiceProvider.xxxEvent` getter 패턴).
97
97
 
98
98
  `ClientEventProxy<TEventDef>` 멤버(위 client 메서드의 eventDef 고정판):
99
99
 
100
- - addListener(info, cb): Promise\<string\> — 리스너 등록.
101
- - removeListener(key): Promise\<void\> — 리스너 제거.
102
- - emit(infoSelector, data): Promise\<void\> — 이벤트 발행.
100
+ - `addListener(info, cb): Promise<string>` — 리스너 등록.
101
+ - `removeListener(key): Promise<void>` — 리스너 제거.
102
+ - `emit(infoSelector, data): Promise<void>` — 이벤트 발행.
103
103
 
104
104
  ```ts
105
105
  const chatEvent = defineEvent<{ channel: string }, string>("Chat");
@@ -108,17 +108,17 @@ await client.emitEvent(chatEvent, (info) => info.channel === "room1", "hello");
108
108
  await client.removeListener(key);
109
109
  ```
110
110
 
111
- `EventClient` / `createEventClient(transport)` 는 `ServiceClient` 가 내부 조립에 쓰는 저수준 구현. 위 메서드(getEvent/addListener/removeListener/emit)에 더해 `resubscribeAll(): Promise<void>`(보관된 모든 리스너를 서버에 재등록, 재연결 복구용)를 가짐. 일반 사용에선 직접 만들지 않음.
111
+ `EventClient` / `createEventClient(transport)` 는 `ServiceClient` 가 내부 조립에 쓰는 저수준 구현. 위 메서드(getEvent/addListener/removeListener/emit)에 더해 `resubscribeAll(): Promise<void>`(보관된 모든 리스너를 서버에 재등록, 재연결 복구용. 항목별 실패는 로깅 후 계속)를 가짐. 일반 사용에선 직접 만들지 않음.
112
112
 
113
113
  ## 파일 업/다운로드 (uploadFile / downloadFileBuffer)
114
114
 
115
- `uploadFile(files): Promise<ServiceUploadResult[]>` — `POST <hostUrl>/upload` (multipart/form-data) 로 파일 업로드. 보관 토큰을 `Authorization: Bearer` 헤더로 전송하므로 사전 `auth()` 필수(미인증 throw). 응답 비정상 시 throw.
115
+ `uploadFile(files): Promise<ServiceUploadResult[]>` — `POST <hostUrl>/upload` (multipart/form-data) 로 파일 업로드. 보관 토큰을 `Authorization: Bearer` 헤더로 전송하므로 사전 `auth()` 필수(보관 토큰 없으면 throw). 응답 비정상(`!res.ok`) 시 throw.
116
116
 
117
- - files: `File[] | FileCollection | { name: string; data: BlobInput }[]` — 업로드 대상. 브라우저 `File` 배열, `FileCollection`(FileList 호환), 또는 `{ name, data }` 커스텀 객체 배열. 커스텀 객체의 data 가 `Blob` 이 아니면 `new Blob([data])` 로 감싸 전송.
117
+ - `files: File[] | FileCollection | { name: string; data: BlobInput }[]` — 업로드 대상. 브라우저 `File` 배열, `FileCollection`(FileList 호환), 또는 `{ name, data }` 커스텀 객체 배열. 커스텀 객체의 data 가 `Blob` 이 아니면 `new Blob([data])` 로 감싸 전송.
118
118
 
119
119
  `downloadFileBuffer(relPath): Promise<Bytes>` — `<hostUrl>/<relPath>` 를 GET 해 `Uint8Array` 반환. 응답 비정상(`!res.ok`) 시 throw.
120
120
 
121
- - relPath: string — 서버 기준 상대경로. 선행 `/` 유무 모두 허용(없으면 자동으로 `/` 추가).
121
+ - `relPath: string` — 서버 기준 상대경로. 선행 `/` 유무 모두 허용(없으면 자동으로 `/` 보정).
122
122
 
123
123
  ```ts
124
124
  await client.auth(token);
@@ -134,15 +134,15 @@ const bytes = await client.downloadFileBuffer("/files/a.txt");
134
134
 
135
135
  `ServiceProgress` — `send(..., progress)` 에 넘기는 콜백 집합(해당 호출 단건 추적용, 전역 이벤트와 별개). 각 콜백은 `(s: ServiceProgressState) => void`:
136
136
 
137
- - request? — 요청 청크 업로드 진행 시 호출. 요청 청크가 2개 이상일 때만 발생.
138
- - response? — 응답 청크 수신 진행 시 호출. 분할 응답이었으면 완료 시 100%(`completedSize === totalSize`)를 한 번 더 보고.
139
- - server? — 서버가 처리 중 보고한 진행률 수신 시 호출(`name: "progress"` 메시지).
137
+ - `request?` — 요청 청크 업로드 진행 시 호출. 요청 청크가 2개 이상일 때만 발생.
138
+ - `response?` — 응답 청크 수신 진행 시 호출. 분할 응답이었으면 완료 시 100%(`completedSize === totalSize`)를 한 번 더 보고.
139
+ - `server?` — 서버가 처리 중 보고한 진행률 수신 시 호출(서버측 `name: "progress"` 메시지).
140
140
 
141
141
  `ServiceProgressState` — 진행률 스냅샷.
142
142
 
143
- - uuid: string — 해당 요청/응답 식별자. 동시 요청 구분용.
144
- - totalSize: number — 전체 바이트 수.
145
- - completedSize: number — 완료 바이트 수. `completedSize === totalSize` 면 완료.
143
+ - `uuid: string` — 해당 요청/응답 식별자. 동시 요청 구분용.
144
+ - `totalSize: number` — 전체 바이트 수.
145
+ - `completedSize: number` — 완료 바이트 수. `completedSize === totalSize` 면 완료.
146
146
 
147
147
  전역 추적이면 콜백 대신 ServiceClient 의 `request/response/server-progress` 이벤트(`client.on("response-progress", ...)`)를 써도 됨 — `send` 는 progress 인자 유무와 무관하게 이 이벤트들을 항상 발생시킴.
148
148
 
@@ -154,9 +154,9 @@ client.on("response-progress", (s) => updateBar(s.completedSize / s.totalSize));
154
154
 
155
155
  Node/browser 공용 코드에서 DOM 전용 타입을 피하고 Worker 지원 여부를 분기하기 위한 타입·함수. 프로토콜 인코딩/파싱 Worker 오프로딩 판단 시 내부에서 사용.
156
156
 
157
- - BlobInput = `Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | string` — `Blob` 생성자가 받는 데이터 타입(DOM `BlobPart` 대체). `uploadFile` 커스텀 객체 data 타입.
158
- - FileCollection (interface) — DOM `FileList` 대체. `length`, `item(index): File | null`, 인덱스 접근, `[Symbol.iterator]` 보유. 브라우저 `FileList` 와 구조적 호환.
159
- - BrowserWorker (interface) — DOM `Worker` 최소 인터페이스(`onmessage`/`onerror` 핸들러, `postMessage(message, transfer?)`, `terminate()`). DOM lib 없이 타입체크 통과용.
160
- - isBrowserWorkerSupported(): boolean — `globalThis` 에 `Worker` 존재 여부 반환. 브라우저 DOM Worker 가용 판단.
161
- - isNodeWorkerSupported(): boolean — `process.versions.node` 존재 여부 반환. Node `worker_threads` 가용 판단.
162
- - isWorkerSupported(): boolean — 위 둘 중 하나라도 true 면 true. 프로토콜 인코딩/파싱 오프로딩 가능 여부 판단(미지원 시 메인 스레드 폴백).
157
+ - `BlobInput = Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | string` — `Blob` 생성자가 받는 데이터 타입(DOM `BlobPart` 대체). `uploadFile` 커스텀 객체의 data 타입.
158
+ - `FileCollection` (interface) — DOM `FileList` 대체. `length`, `item(index): File | null`, 인덱스 접근, `[Symbol.iterator]` 보유. 브라우저 `FileList` 와 구조적 호환.
159
+ - `BrowserWorker` (interface) — DOM `Worker` 최소 인터페이스(`onmessage`/`onerror` 핸들러, `postMessage(message, transfer?)`, `terminate()`). DOM lib 없이 타입체크 통과용.
160
+ - `isBrowserWorkerSupported(): boolean` — `globalThis` 에 `Worker` 존재 여부 반환. 브라우저 DOM Worker 가용 판단.
161
+ - `isNodeWorkerSupported(): boolean` — `process.versions.node` 존재 여부 반환. Node `worker_threads` 가용 판단.
162
+ - `isWorkerSupported(): boolean` — 위 둘 중 하나라도 true 면 true. 프로토콜 인코딩/파싱 오프로딩 가능 여부 판단(미지원 시 메인 스레드 폴백).
@@ -1,8 +1,8 @@
1
1
  # @simplysm/service-client — ORM 원격 실행
2
2
 
3
- 서버측 ORM DbContext 를 클라이언트에서 원격으로 실행하는 묶음. 쿼리 자체는 서버 `Orm` 서비스가 DB 에 대해 수행하고, 클라이언트는 커넥션·트랜잭션·쿼리 정의(`QueryDef`)만 RPC 로 전송한다. 쿼리는 `connect`/`connectWithoutTransaction` 콜백 내부에서만 가능. ORM 작업을 트랜잭션 경계로 묶어 실행할 때 함께 읽힌다.
3
+ 서버측 ORM DbContext 를 클라이언트에서 원격 실행하는 묶음. 쿼리 자체는 서버 `Orm` 서비스가 DB 에 대해 수행하고, 클라이언트는 커넥션·트랜잭션·쿼리 정의(`QueryDef`)만 RPC 로 전송한다. 쿼리는 `connect`/`connectWithoutTransaction` 콜백 내부에서만 가능. ORM 작업을 트랜잭션 경계로 묶어 실행할 때 함께 읽힌다.
4
4
 
5
- > 앱(Angular)에서는 `OrmConnectOptions`(DbClass·connOpt·dbContextOpt)를 화면에 흩뿌리지 않고 `AppOrmProvider`(root provider) 한 곳에 고정한 뒤 `connectAsync`/`connectWithoutTransAsync` 만 호출한다. 쿼리 작성법은 apis/orm-common 참조. 아래 예시는 connector 직접 호출 형태지만 실제 앱은 manuals/client-orm.md 의 provider 패턴을 따른다.
5
+ > 앱(Angular)에서는 `OrmConnectOptions`(DbClass·connOpt·dbContextOpt)를 화면에 흩뿌리지 않고 `AppOrmProvider`(root provider) 한 곳에 고정한 뒤 `connectAsync` 만 호출한다. 쿼리 작성법은 apis/orm-common·manuals/orm.md 참조. 아래 예시는 connector 직접 호출 형태지만 실제 앱은 manuals/client-orm.md 의 provider 패턴을 따른다.
6
6
 
7
7
  ## createOrmClientConnector / OrmClientConnector
8
8
 
@@ -10,13 +10,13 @@
10
10
 
11
11
  `OrmClientConnector` 메서드:
12
12
 
13
- - connect<T, R>(config, callback): Promise\<R\> — 트랜잭션 안에서 callback 실행. DbContext 생성 → `db.connect(...)`(connect + beginTransaction + callback + commit/rollback) 수행. callback 정상 반환 시 커밋(반환값이 그대로 반환됨), throw 시 롤백되어 콜백 내 다건 작업이 원자 처리됨. callback 에서 발생한 에러 중 외래키 제약 위반 메시지(MySQL `a parent row: a foreign key constraint` / MSSQL `conflicted with the REFERENCE` / PostgreSQL `violates foreign key constraint`)는 "경고! 연관된 작업으로 인해 작업이 거부되었습니다. 후속 작업을 확인해 주세요." 로 감싸 `SdError` 로 throw(원본은 cause 에 보존), 그 외 에러는 그대로 throw.
14
- - connectWithoutTransaction<T, R>(config, callback): Promise\<R\> — 트랜잭션 없이 callback 실행(`db.connectWithoutTransaction`). 트랜잭션 안에서 동작하지 않는 작업(예: DB initialize)·조회 전용 작업에 사용. callback 반환값이 그대로 반환됨. 외래키 메시지 래핑 없음.
13
+ - `connect<T, R>(config, callback): Promise<R>` — 트랜잭션 안에서 callback 실행. DbContext 생성 → `db.connect(...)`(connect + beginTransaction + callback + commit/rollback) 수행. callback 정상 반환 시 커밋(반환값이 그대로 메서드 반환값), throw 시 롤백되어 콜백 내 다건 작업이 원자 처리됨. callback 에서 발생한 에러 중 외래키 제약 위반 메시지(MySQL `a parent row: a foreign key constraint` / MSSQL `conflicted with the REFERENCE` / PostgreSQL `violates foreign key constraint`)는 "경고! 연관된 작업으로 인해 작업이 거부되었습니다. 후속 작업을 확인해 주세요." 로 감싼 `SdError` 로 throw(원본은 cause 에 보존), 그 외 에러는 그대로 throw.
14
+ - `connectWithoutTransaction<T, R>(config, callback): Promise<R>` — 트랜잭션 없이 callback 실행(`db.connectWithoutTransaction`). 트랜잭션 안에서 동작하지 않는 작업(예: DB initialize)·조회 전용 작업에 사용. callback 반환값이 그대로 반환됨. 외래키 메시지 래핑 없음.
15
15
 
16
16
  공통 인자:
17
17
 
18
- - config: OrmConnectOptions\<T\> — 아래 섹션. DbContext 클래스·서버 ORM 설정·DB명/스키마.
19
- - callback: (db: T) => Promise\<R\> | R — 생성·연결된 DbContext 를 받아 쿼리하는 콜백. 동기/비동기 반환 모두 허용.
18
+ - `config: OrmConnectOptions<T>` — 아래 섹션. DbContext 클래스·서버 ORM 설정·DB명/스키마.
19
+ - `callback: (db: T) => Promise<R> | R` — 생성·연결된 DbContext 를 받아 쿼리하는 콜백. 동기/비동기 반환 모두 허용.
20
20
 
21
21
  ```ts
22
22
  const connector = createOrmClientConnector(client);
@@ -30,24 +30,24 @@ const rows = await connector.connect(
30
30
 
31
31
  `connect`/`connectWithoutTransaction` 의 첫 인자. DbContext 1회 실행에 필요한 설정.
32
32
 
33
- - DbClass: `new (executor, opt) => T` — 실행할 DbContext 클래스 생성자. `executor`(아래 `OrmClientDbContextExecutor`)와 `{ database, schema? }` 를 받아 인스턴스화됨. 앱별 DbContext(예: `MainDbContext`)를 그대로 전달.
34
- - connOpt: `DbConnOptions & { configName: string }` — 서버측 ORM 연결 설정. `configName` 은 서버에 등록된 ORM 설정 이름(서버가 이 이름으로 실제 DB 접속 정보를 찾음). 나머지 필드는 `@simplysm/service-common` 의 `DbConnOptions`.
35
- - dbContextOpt?: `{ database: string; schema?: string }` — DbContext 에 적용할 DB명·스키마. 생략 시 서버 `getInfo()` 가 돌려준 `database`/`schema` 를 사용. database 가 옵션·서버 양쪽 모두 비어 있으면 throw("database는 필수입니다." — 결측을 임의 보정하지 않음).
36
- - database: string — 대상 데이터베이스명.
37
- - schema?: string — 대상 스키마명. 미지정 시 서버 `getInfo()` 의 schema 를 사용.
33
+ - `DbClass: new (executor, opt) => T` — 실행할 DbContext 클래스 생성자. `executor`(아래 `OrmClientDbContextExecutor`)와 `{ database, schema? }` 를 받아 인스턴스화됨. 앱별 DbContext(예: `MainDbContext`)를 그대로 전달.
34
+ - `connOpt: DbConnOptions & { configName: string }` — 서버측 ORM 연결 설정. `configName` 은 서버에 등록된 ORM 설정 이름(서버가 이 이름으로 실제 DB 접속 정보를 찾음). 나머지 필드는 `@simplysm/service-common` 의 `DbConnOptions`.
35
+ - `dbContextOpt?: { database: string; schema?: string }` — DbContext 에 적용할 DB명·스키마. 생략 시 서버 `getInfo()` 가 돌려준 `database`/`schema` 를 사용. database 가 옵션·서버 양쪽 모두 비어 있으면 throw("database는 필수입니다." — 결측을 임의 보정하지 않음).
36
+ - `database: string` — 대상 데이터베이스명.
37
+ - `schema?: string` — 대상 스키마명. 미지정 시 서버 `getInfo()` 의 schema 를 사용.
38
38
 
39
39
  ## OrmClientDbContextExecutor
40
40
 
41
41
  `DbContextExecutor`(`@simplysm/orm-common`) 구현체. 모든 메서드를 `client.getService<OrmService>("Orm")` RPC 로 위임. 커넥터가 내부에서 DbContext 에 주입하므로 직접 생성·호출은 보통 불필요.
42
42
 
43
- `new OrmClientDbContextExecutor(client, opt)` — 생성. opt = `DbConnOptions & { configName: string }`. 생성 시 `Orm` 서비스 프록시 확보. 아래 connect 이외의 실행 메서드는 connId 가 없으면(미연결) throw("데이터베이스에 연결되지 않았습니다.").
44
-
45
- - getInfo(): `Promise<{ dialect; database?; schema? }>` — 서버 ORM 설정 정보(방언·기본 DB명·스키마) 조회. 커넥터가 dbContextOpt 미지정 시 fallback 으로 사용.
46
- - connect(): Promise\<void\> — 서버에 커넥션 생성, 반환된 connId 를 내부 보관. 이후 트랜잭션·쿼리 호출의 핸들.
47
- - beginTransaction(isolationLevel?): Promise\<void\> — 트랜잭션 시작. isolationLevel(orm-common `IsolationLevel`) 미지정 시 서버 기본값.
48
- - commitTransaction(): Promise\<void\> — 트랜잭션 커밋.
49
- - rollbackTransaction(): Promise\<void\> — 트랜잭션 롤백.
50
- - close(): Promise\<void\> — 커넥션 종료 후 보관 connId 해제.
51
- - executeDefs\<T\>(defs, options?): Promise\<T[][]\> — 쿼리 정의 배열(`QueryDef[]`)을 서버에서 실행. options 는 정의별 결과 매핑 메타(`(ResultMeta | undefined)[]`, 항목별 nullable)로 행 역직렬화 방식 지정. 정의 1개당 결과 배열 1개를 가진 2차원 배열 반환.
52
- - executeParametrized(query, params?): Promise\<unknown[][]\> — 파라미터 바인딩 raw SQL 실행. query = SQL 문자열, params = 바인딩 값 배열.
53
- - bulkInsert(tableName, columnDefs, records): Promise\<void\> — 대량 삽입. tableName = 대상 테이블명, columnDefs(`Record<string, ColumnMeta>`)로 컬럼별 메타를, records(`Record<string, unknown>[]`)로 삽입할 행 객체 배열을 전달.
43
+ `new OrmClientDbContextExecutor(client, opt)` — 생성. `opt = DbConnOptions & { configName: string }`. 생성 시 `Orm` 서비스 프록시 확보. 아래 `getInfo`/`connect` 이외의 실행 메서드는 connId 가 없으면(미연결) throw("데이터베이스에 연결되지 않았습니다.").
44
+
45
+ - `getInfo(): Promise<{ dialect; database?; schema? }>` — 서버 ORM 설정 정보(방언·기본 DB명·스키마) 조회. 커넥터가 dbContextOpt 미지정 시 fallback 으로 사용.
46
+ - `connect(): Promise<void>` — 서버에 커넥션 생성, 반환된 connId 를 내부 보관. 이후 트랜잭션·쿼리 호출의 핸들.
47
+ - `beginTransaction(isolationLevel?): Promise<void>` — 트랜잭션 시작. `isolationLevel`(orm-common `IsolationLevel`) 미지정 시 서버 기본값.
48
+ - `commitTransaction(): Promise<void>` — 트랜잭션 커밋.
49
+ - `rollbackTransaction(): Promise<void>` — 트랜잭션 롤백.
50
+ - `close(): Promise<void>` — 커넥션 종료 후 보관 connId 해제.
51
+ - `executeDefs<T>(defs, options?): Promise<T[][]>` — 쿼리 정의 배열(`QueryDef[]`)을 서버에서 실행. `options` 는 정의별 결과 매핑 메타(`(ResultMeta | undefined)[]`, 항목별 nullable)로 행 역직렬화 방식 지정. 정의 1개당 결과 배열 1개를 가진 2차원 배열 반환.
52
+ - `executeParametrized(query, params?): Promise<unknown[][]>` — 파라미터 바인딩 raw SQL 실행. `query` = SQL 문자열, `params` = 바인딩 값 배열.
53
+ - `bulkInsert(tableName, columnDefs, records): Promise<void>` — 대량 삽입. `tableName` = 대상 테이블명, `columnDefs`(`Record<string, ColumnMeta>`)로 컬럼별 메타, `records`(`Record<string, unknown>[]`)로 삽입할 행 객체 배열을 전달.
@@ -1,52 +1,52 @@
1
1
  # @simplysm/service-client — 저수준 전송 계층
2
2
 
3
- `ServiceClient` 가 생성자에서 내부적으로 조립하는 저수준 모듈들. WebSocket 연결·하트비트·재연결(SocketProvider), 요청/응답 매칭과 메시지 디스패치(ServiceTransport), 인코딩/디코딩의 Worker 오프로딩(ClientProtocolWrapper). 일반 사용에서는 `ServiceClient` 만 쓰면 되며, 이 계층은 소켓·청크·하트비트 동작을 이해해야 할 때만 읽는다.
3
+ `ServiceClient` 가 생성자에서 내부적으로 조립하는 저수준 모듈들. WebSocket 연결·하트비트·자동 재연결(SocketProvider), 요청/응답 uuid 매칭과 메시지 디스패치(ServiceTransport), 인코딩/디코딩의 Worker 오프로딩(ClientProtocolWrapper). 일반 사용에서는 `ServiceClient` 만 쓰면 되며, 이 계층은 소켓·청크·하트비트 동작을 이해해야 할 때만 읽는다.
4
4
 
5
5
  ## SocketProvider / createSocketProvider
6
6
 
7
7
  WebSocket 1개의 연결·하트비트·자동 재연결 담당.
8
8
 
9
- `createSocketProvider(url, clientName, maxReconnectCount): SocketProvider` — 프로바이더 생성. Node 환경에서 글로벌 `WebSocket` 이 없으면 모듈 로드 시 `ws` 패키지로 polyfill.
9
+ `createSocketProvider(url, clientName, maxReconnectCount): SocketProvider` — 프로바이더 생성. 모듈은 import 시점에 글로벌 `WebSocket` 이 없으면 `ws` 패키지로 polyfill 한다(Node 환경).
10
10
 
11
- - url: string — `ws(s)://host:port/ws`. 접속 시 `ver=2`, 생성된 `clientId`(UUID), `clientName` 쿼리를 붙임.
12
- - clientName: string — 접속 쿼리에 실리는 식별명.
13
- - maxReconnectCount: number — 최대 재연결 시도 횟수. 0 이면 재연결 안 함.
11
+ - `url: string` — `ws(s)://host:port/ws`. 접속 시 `ver=2`, 생성된 `clientId`(UUID), `clientName` 쿼리를 붙임.
12
+ - `clientName: string` — 접속 쿼리에 실리는 식별명.
13
+ - `maxReconnectCount: number` — 최대 재연결 시도 횟수. 0 이면 재연결 안 함.
14
14
 
15
- 내부 상수: 하트비트 ping 5초 간격, 30초 무수신 시 타임아웃, 재연결 3초 간격. 1바이트 `0x01` ping 전송, `0x02` pong 수신은 무시(타임스탬프만 갱신).
15
+ 내부 상수: ping 5초 간격 전송, 30초 무수신 시 타임아웃, 재연결 3초 간격. 1바이트 `0x01` ping 전송, 수신한 1바이트 `0x02` pong 무시(하트비트 타임스탬프만 갱신).
16
16
 
17
- 멤버:
17
+ `SocketProvider` 멤버:
18
18
 
19
- - clientName: string (readonly) — 생성 시 받은 식별명.
20
- - connected: boolean (getter) — 소켓이 OPEN 상태인지.
21
- - connect(): Promise\<void\> — 접속 시작. 실패 시 throw, 성공 시 재연결 카운트 리셋하고 `state: "connected"` emit.
22
- - close(): Promise\<void\> — 수동 종료. 이후 자동 재연결 안 함. 소켓 CLOSED 까지 대기 후 `state: "closed"` emit.
23
- - send(data: Bytes): Promise\<void\> — 바이트 전송. 일정 시간 내 미연결이면 throw("서버에 연결되지 않았습니다. 인터넷 연결을 확인해 주세요.").
24
- - on(type, listener) / off(type, listener) — 이벤트 구독/해제.
19
+ - `clientName: string` (readonly) — 생성 시 받은 식별명.
20
+ - `connected: boolean` (getter) — 소켓이 OPEN 상태인지.
21
+ - `connect(): Promise<void>` — 접속 시작. 실패 시 throw, 성공 시 재연결 카운트 리셋 `state: "connected"` emit.
22
+ - `close(): Promise<void>` — 수동 종료. 이후 자동 재연결 안 함. 소켓 CLOSED 까지 대기 후 `state: "closed"` emit.
23
+ - `send(data: Bytes): Promise<void>` — 바이트 전송. 일정 시간 내 미연결이면 throw("서버에 연결되지 않았습니다. 인터넷 연결을 확인해 주세요.").
24
+ - `on(type, listener)` / `off(type, listener)` — 이벤트 구독/해제.
25
25
 
26
26
  `SocketProviderEvents`:
27
27
 
28
- - message: Bytes — 수신 바이트(ping/pong 1바이트 제어 프레임 제외).
29
- - state: "connected" | "closed" | "reconnecting" — 연결 상태 변화. "connected" = 연결/재연결 성공, "closed" = 수동 종료 또는 재연결 한도 초과, "reconnecting" = 재연결 시도 중.
28
+ - `message: Bytes` — 수신 바이트(1바이트 ping/pong 제어 프레임 제외).
29
+ - `state: "connected" | "closed" | "reconnecting"` — 연결 상태 전이. `"connected"` = 연결/재연결 성공, `"closed"` = 수동 종료 또는 재연결 한도 초과, `"reconnecting"` = 재연결 시도 중.
30
30
 
31
- 하트비트 타임아웃 감지 시 소켓을 강제 정리하고(늦은 onclose 로 인한 중복 재연결 방지를 위해 핸들러 해제) 수동 종료가 아니면 재연결을 시도. 최대 시도 초과 시 `state: "closed"` emit.
31
+ 하트비트 타임아웃 감지 시 소켓을 강제 정리하고(늦은 `onclose` 로 인한 중복 재연결 방지를 위해 핸들러 해제) 수동 종료가 아니면 재연결을 시도. 재연결은 재귀 대신 루프로 수행하며 최대 시도 초과 시 `state: "closed"` emit.
32
32
 
33
33
  ## ServiceTransport / createServiceTransport
34
34
 
35
35
  요청별 uuid 매칭, 응답/에러/진행률/서버이벤트 디스패치 담당.
36
36
 
37
- `createServiceTransport(socket, protocol): ServiceTransport` — 트랜스포트 생성. 소켓 `message` 를 받아 decode 후 종류별 분기. 소켓이 `closed`/`reconnecting` 되면 대기 중인 모든 요청을 reject(메모리 해제).
37
+ `createServiceTransport(socket, protocol): ServiceTransport` — 트랜스포트 생성. 소켓 `message` 를 받아 decode 후 종류별 분기. 소켓이 `closed`/`reconnecting` 되면 대기 중인 모든 요청을 reject("요청 취소됨: ...") 하여 메모리 해제.
38
38
 
39
- - socket: SocketProvider — 하위 소켓.
40
- - protocol: ClientProtocolWrapper — 인코드/디코드 래퍼.
39
+ - `socket: SocketProvider` — 하위 소켓.
40
+ - `protocol: ClientProtocolWrapper` — 인코드/디코드 래퍼.
41
41
 
42
- 멤버:
42
+ `ServiceTransport` 멤버:
43
43
 
44
- - send(message, progress?): Promise\<unknown\> — 요청 1건 전송 후 응답 Promise 반환. uuid 생성 → 리스너 등록 → encode → 청크 순차 전송. 응답(`response`) 수신 시 resolve, 에러(`error`) 수신 시 서버 에러 필드를 머지한 `Error` 로 reject. message = `ServiceClientMessage`, progress = `ServiceProgress`(선택).
45
- - on(type, listener) / off(type, listener) — 이벤트 구독/해제.
44
+ - `send(message, progress?): Promise<unknown>` — 요청 1건 전송 후 응답 Promise 반환. uuid 생성 → 리스너 선등록 → encode → 청크 순차 전송. 응답(`response`) 수신 시 resolve, 에러(`error`) 수신 시 서버 에러 필드를 머지한 `Error` 로 reject. `message = ServiceClientMessage`, `progress = ServiceProgress`(선택).
45
+ - `on(type, listener)` / `off(type, listener)` — 이벤트 구독/해제.
46
46
 
47
47
  `ServiceTransportEvents`:
48
48
 
49
- - event: `{ keys: string[]; data: unknown }` — 서버가 푸시한 `evt:on` 메시지. `EventClient` 가 이걸 구독해 keys 에 매칭되는 로컬 리스너로 디스패치.
49
+ - `event: { keys: string[]; data: unknown }` — 서버가 푸시한 `evt:on` 메시지. `EventClient` 가 이걸 구독해 keys 에 매칭되는 로컬 리스너로 디스패치.
50
50
 
51
51
  decode 실패 시에도 헤더 16바이트에서 uuid 를 선추출해 해당 요청만 reject. 분할 응답이면 완료 시 `progress.response` 로 100% 를 한 번 더 보고. 서버측 진행 메시지(`name: "progress"`)는 `progress.server` 로 전달.
52
52
 
@@ -56,6 +56,10 @@ decode 실패 시에도 헤더 16바이트에서 uuid 를 선추출해 해당
56
56
 
57
57
  `createClientProtocolWrapper(protocol): ClientProtocolWrapper` — 래퍼 생성.
58
58
 
59
- - encode(uuid, message): Promise\<{ chunks: Bytes[]; totalSize: number }\> — 메시지를 청크 배열로 인코드. body 가 Uint8Array, 30KB 초과 문자열, 길이 100 초과 배열, 또는 첫 항목이 Uint8Array 인 배열이면 Worker 사용. message = `ServiceMessage`.
60
- - decode(bytes): Promise\<ServiceMessageDecodeResult\<ServiceMessage\>\> — 수신 바이트 디코드. 청크 재조립(stateful)은 한 메시지의 청크가 흩어지지 않도록 항상 메인 스레드 단일 누적기에서 수행하고(#35), 재조립 완료 후 30KB 초과 JSON 파싱(stateless)만 Worker 에 위임. 미완료(progress) 상태면 그대로 반환.
61
- - dispose(): void프로토콜과 Worker 리졸버 정리. `ServiceClient.close()` 에서 호출.
59
+ `ClientProtocolWrapper` 멤버:
60
+
61
+ - `encode(uuid, message): Promise<{ chunks: Bytes[]; totalSize: number }>` 메시지를 청크 배열로 인코드. body 가 `Uint8Array`, 30KB 초과 문자열, 길이 100 초과 배열, 또는 첫 항목이 `Uint8Array` 인 배열이면 Worker 사용(그 외·Worker 미가용 시 메인 스레드). `message = ServiceMessage`.
62
+ - `decode(bytes): Promise<ServiceMessageDecodeResult<ServiceMessage>>` — 수신 바이트 디코드. 청크 재조립(stateful)은 한 메시지의 청크가 서로 다른 누적기로 흩어지지 않도록 항상 메인 스레드 단일 누적기에서 수행하고(#35), 재조립 완료 후 30KB 초과 JSON 파싱(stateless)만 Worker 에 위임. 미완료(progress) 상태면 그대로 반환.
63
+ - `dispose(): void` — 프로토콜과 Worker 리졸버 정리. `ServiceClient.close()` 에서 호출.
64
+
65
+ Worker 는 browser DOM Worker 또는 Node `worker_threads` 중 가용한 쪽을 lazy 초기화하며, Worker 작업이 60초 내 응답하지 않으면 해당 작업을 시간 초과로 reject 한다.
@@ -1,6 +1,6 @@
1
1
  # @simplysm/service-common
2
2
 
3
- 서버·클라이언트가 공유하는 서비스 통신 계약. 이벤트 정의(`defineEvent`), 내장 RPC 서비스 인터페이스 시그니처, 앱 메뉴·권한·모듈 구조(`AppStructure`), WebSocket 바이너리 프로토콜·메시지 타입을 한곳에 둔다. 발생측·구독측 또는 서버·클라이언트가 같은 정의 객체·타입을 import 해 쓰는 게 핵심.
3
+ 서버·클라이언트가 공유하는 서비스 통신 계약. 실시간 이벤트 정의(`defineEvent`), 내장 RPC 서비스 인터페이스 시그니처, 앱 메뉴·권한·모듈 구조(`AppStructure`), WebSocket 바이너리 프로토콜·메시지 타입을 한곳에 둔다. 발생측·구독측 또는 서버·클라이언트가 같은 정의 객체·타입을 import 해 쓰는 게 핵심.
4
4
 
5
5
  ## 사용 트리거 인덱스
6
6
 
@@ -21,9 +21,9 @@
21
21
  function defineEvent<TInfo = unknown, TData = unknown>(eventName: string): ServiceEventDef<TInfo, TData>
22
22
  ```
23
23
 
24
- - `TInfo` (제네릭) — 구독자가 "무엇을 구독하는지" 식별하는 메타데이터 타입. 발생측 selector 가 이 값을 보고 전달 대상을 골라냄. 미지정 시 `unknown`. 예: `{ warehouseId: number }`.
25
- - `TData` (제네릭) — 이벤트가 실어 나르는 페이로드 타입. 구독 콜백이 받는 인자 타입. 미지정 시 `unknown`. 예: `{ orderId: number; status: string }`.
26
- - `eventName` (인자) — 라우팅 키 문자열. 같은 이름이면 같은 이벤트로 취급되므로 앱 내에서 고유해야 함.
24
+ - `TInfo` (제네릭) — 구독자가 "무엇을 구독하는지" 식별하는 메타데이터 타입. 발생측 selector 가 이 값을 보고 전달 대상을 골라냄. 미지정 시 `unknown`.
25
+ - `TData` (제네릭) — 이벤트가 실어 나르는 페이로드 타입. 구독 콜백이 받는 인자 타입. 미지정 시 `unknown`.
26
+ - `eventName` (인자) — 라우팅 키 문자열. `ServiceEventDef.eventName` 에 그대로 들어가며 런타임에 실제로 쓰이는 유일한 값. 같은 이름이면 같은 이벤트로 취급되므로 앱 내에서 고유해야 함.
27
27
  - 반환된 `ServiceEventDef` 를 그대로 발생·구독 API 의 첫 인자로 넘기면 이름·타입이 추론됨.
28
28
 
29
29
  ```ts
@@ -49,8 +49,8 @@ interface ServiceEventDef<TInfo = unknown, TData = unknown> {
49
49
  ```
50
50
 
51
51
  - `eventName: string` — 라우팅 키. `defineEvent` 인자가 그대로 들어가며 런타임에 실제로 쓰이는 유일한 값.
52
- - `$info: TInfo` (readonly) — `TInfo` 타입 추출 전용 phantom 마커. 런타임 값은 `undefined`(사용되지 않음). 발생·구독 API 가 이 마커로 `TInfo` 를 끌어다 씀.
53
- - `$data: TData` (readonly) — `TData` 타입 추출 전용 phantom 마커. 런타임 값은 `undefined`(사용되지 않음). 직접 읽지 말 것.
52
+ - `$info: TInfo` (readonly) — `TInfo` 타입 추출 전용 마커. 런타임 값은 `undefined`(사용되지 않음). 발생·구독 API 가 이 마커로 `TInfo` 를 끌어다 씀. 직접 읽지 말 것.
53
+ - `$data: TData` (readonly) — `TData` 타입 추출 전용 마커. 런타임 값은 `undefined`(사용되지 않음). 직접 읽지 말 것.
54
54
 
55
55
  ## 내장 RPC 서비스 시그니처 (OrmService / AutoUpdateService)
56
56
 
@@ -82,7 +82,7 @@ interface OrmService {
82
82
  - `rollbackTransaction(connId)` — 트랜잭션 취소.
83
83
  - `executeParametrized(connId, query, params?)` — 파라미터 바인딩 raw SQL 직접 실행. `params` 는 플레이스홀더에 순서대로 바인딩(없으면 바인딩 없는 쿼리). 결과는 결과셋별 행 배열(`unknown[][]`).
84
84
  - `executeDefs(connId, defs, options?)` — ORM 코어가 만든 `QueryDef` 배열을 일괄 실행. `options[i]` 는 `defs[i]` 결과셋의 컬럼 파싱 메타(`ResultMeta`)로, 메타 불필요한 def 자리는 `undefined`.
85
- - `bulkInsert(connId, tableName, columnDefs, records)` — 다건 레코드 대량 삽입. `columnDefs` 는 컬럼명→`ColumnMeta`(타입·인코딩) 매핑, `records` 는 삽입할 행 배열.
85
+ - `bulkInsert(connId, tableName, columnDefs, records)` — 다건 레코드 대량 삽입. `columnDefs` 는 컬럼명→`ColumnMeta` 매핑, `records` 는 삽입할 행 배열.
86
86
 
87
87
  `Dialect`/`IsolationLevel`/`QueryDef`/`ColumnMeta`/`ResultMeta` 는 `@simplysm/orm-common` 타입.
88
88
 
@@ -92,7 +92,7 @@ interface OrmService {
92
92
  type DbConnOptions = { configName?: string; config?: Record<string, unknown> }
93
93
  ```
94
94
 
95
- - `configName?: string` — 서버에 등록된 DB 연결 설정의 이름. 이 이름으로 서버가 접속 정보를 찾음. (`getInfo`/`connect` 는 `configName` 필수로 교차해 받음.)
95
+ - `configName?: string` — 서버에 등록된 DB 연결 설정의 이름. 이 이름으로 서버가 접속 정보를 찾음. `getInfo`/`connect` 는 필드를 필수로 교차해(`& { configName: string }`) 받음.
96
96
  - `config?: Record<string, unknown>` — 등록 설정 대신 직접 넘기는 즉석 접속 설정 객체. `configName` 으로 가리킬 수 없을 때 사용.
97
97
 
98
98
  ### AutoUpdateService
@@ -51,7 +51,7 @@ interface AppStructureLeafItem<TModule> {
51
51
  ```
52
52
 
53
53
  - `code` / `title` / `modules` / `requiredModules` / `icon` — 그룹과 동일 의미.
54
- - `perms?: ("use" | "edit")[]` — 이 화면에 부여 가능한 권한 종류. `"use"` = 조회 권한, `"edit"` = 편집 권한. 지정한 화면만 권한 페이지·권한 체크 대상이 됨. 생략하면 제약 없는 화면(항상 모든 권한 활성).
54
+ - `perms?: ("use" | "edit")[]` — 이 화면에 부여 가능한 권한 종류. `"use"` = 조회 권한, `"edit"` = 편집 권한. 지정한 화면만 권한 페이지·권한 체크 대상이 됨. 생략하면 제약 없는 화면.
55
55
  - `subPerms?: AppStructureSubPermission<TModule>[]` — 한 화면 안의 세부 기능 권한 목록.
56
56
  - `url?: string` — 외부 링크 등 이동 경로. 일반 화면 라우팅 대신 외부로 보낼 때.
57
57
  - `isNotMenu?: boolean` — 메뉴 노출 토글. `true` 면 사이드 메뉴에서 숨김(라우팅 대상 화면 자체는 유지). 홈·내정보 등 직접 진입 화면에 사용. 미지정/`false` 면 메뉴에 노출.
@@ -88,9 +88,9 @@ interface FlatPermission<TModule = unknown> {
88
88
  }
89
89
  ```
90
90
 
91
- - `titleChain: string[]` — 루트부터 이 권한까지의 표시명 경로. 권한 페이지에서 계층 라벨로 사용.
91
+ - `titleChain: string[]` — 루트부터 이 권한까지의 표시명(`title`) 경로. 권한 페이지에서 계층 라벨로 사용.
92
92
  - `codeChain: string[]` — 루트부터의 코드 경로 + 마지막에 권한 종류(`"use"`/`"edit"`)·세부코드가 붙은 전체 키. 권한 식별자.
93
- - `modulesChain: TModule[][]` — 경로상 레벨이 가진 `modules` 배열들의 모음. 이 권한이 어떤 모듈 조건에 묶였는지 표현.
93
+ - `modulesChain: TModule[][]` — 경로상 `modules` 가진 각 레벨의 `modules` 배열들의 모음. 이 권한이 어떤 모듈 조건에 묶였는지 표현.
94
94
 
95
95
  ## isUsableModules
96
96
 
@@ -102,9 +102,16 @@ function isUsableModules<TModule>(modules: TModule[] | undefined, requiredModule
102
102
 
103
103
  - `modules` — OR 조건. 나열 중 하나라도 `usableModules` 에 있으면 통과. `undefined`/빈 배열이면 무조건 통과.
104
104
  - `requiredModules` — AND 조건. 나열 전부가 `usableModules` 에 있어야 통과. `undefined`/빈 배열이면 통과로 간주.
105
- - `usableModules` — 현재 앱에서 활성인 모듈 목록.
105
+ - `usableModules` — 현재 앱에서 활성인 모듈 목록. `undefined` 이면 `modules` 를 가진 항목은 매칭 실패(통과 불가).
106
106
  - 반환: requiredModules(AND)와 modules(OR)를 모두 만족하면 `true`.
107
107
 
108
+ ```ts
109
+ import { isUsableModules } from "@simplysm/service-common";
110
+
111
+ isUsableModules(["A", "B"], undefined, ["A"]); // true — OR
112
+ isUsableModules(undefined, ["A", "B"], ["A"]); // false — AND 미충족
113
+ ```
114
+
108
115
  ## isUsableModulesChain
109
116
 
110
117
  ```ts
@@ -116,7 +123,7 @@ function isUsableModulesChain<TModule>(modulesChain: TModule[][], requiredModule
116
123
  - `modulesChain` — 각 레벨의 `modules` 배열들. 모든 레벨이 OR 조건을 통과해야 함.
117
124
  - `requiredModulesChain` — 각 레벨의 `requiredModules` 배열들. 모든 레벨이 AND 조건을 통과해야 함.
118
125
  - `usableModules` — 현재 활성 모듈 목록.
119
- - 반환: 한 레벨이라도 막히면 `false`. 자식 표시 여부 판정에 사용.
126
+ - 반환: 빈 체인이면 `true`, 한 레벨이라도 막히면 `false`. 자식 표시 여부 판정에 사용.
120
127
 
121
128
  ## getFlatPermissions
122
129
 
@@ -128,7 +135,7 @@ function getFlatPermissions<TModule>(items: AppStructureItem<TModule>[], usableM
128
135
 
129
136
  - `items` — 앱 구조 배열(루트).
130
137
  - `usableModules` — 현재 활성 모듈. 모듈 조건을 통과하지 못한 가지(그 자식·권한 포함)는 결과에서 빠짐.
131
- - 동작: leaf 의 `perms` 각각, 그리고 `subPerms` 의 각 `perm` 을 한 줄(`FlatPermission`)로 펼침. `subPerms` 는 자체 모듈 조건도 추가로 검사.
138
+ - 동작: leaf 의 `perms` 각각, 그리고 `subPerms` 의 각 `perm` 을 한 줄(`FlatPermission`)로 펼침. `subPerms` 는 자체 모듈 조건도 추가로 검사. 빈 `items` 면 빈 배열.
132
139
  - 반환: 표시 가능한 권한들의 평탄 배열. 권한 관리 화면(`<sd-permission-table>` 입력)·권한 매칭의 기반.
133
140
 
134
141
  ```ts
@@ -37,7 +37,7 @@ interface ServiceProtocol {
37
37
  ```
38
38
 
39
39
  - `encode(uuid, message)` — 메시지를 와이어 바이트로 인코딩. 3MB 초과 시 자동으로 여러 청크로 분할. 반환 `chunks` 는 전송할 패킷 배열, `totalSize` 는 본문 총 바이트. 같은 메시지의 모든 청크는 동일 `uuid` 를 공유. `MAX_TOTAL_SIZE` 초과 시 throw.
40
- - `accumulate(bytes)` — 수신 청크 패킷을 누적(stateful). 같은 uuid 의 청크를 한 누적기에 모음. 미완성이면 `progress`, 전부 도착하면 재조립된 raw 바이트를 담은 `complete` 반환. JSON 파싱은 안 함. 헤더(28바이트) 미만이거나 크기 위반 시 throw. 중복 패킷은 무시(인덱스 기준 1회만 반영).
40
+ - `accumulate(bytes)` — 수신 청크 패킷을 누적(stateful). 같은 uuid 의 청크를 한 누적기에 모음. 미완성이면 `progress`, 전부 도착하면 재조립된 raw 바이트를 담은 `complete` 반환. JSON 파싱은 안 함. 헤더(28바이트) 미만이거나 크기 위반 시 throw. 중복 패킷은 인덱스 기준 1회만 반영(중복은 무시). 누적분이 `totalSize` 를 초과하면 무결성 위반으로 throw.
41
41
  - `parseMessage(resultBytes)` — 재조립된 raw 바이트를 `ServiceMessage` 로 파싱(stateless). 누적기 상태에 의존하지 않아 worker 등 다른 컨텍스트에 위임 가능. 파싱 실패 시 throw.
42
42
  - `decode(bytes)` — `accumulate` 후 완료 시 `parseMessage` 까지 수행하는 통합 경로. 단일 컨텍스트에서 한 번에 디코딩할 때.
43
43
  - `dispose()` — 내부 청크 누적기의 GC 타이머 해제·메모리 반환. 인스턴스를 더 안 쓸 때 반드시 호출.