@simplysm/sd-claude 14.0.87 → 14.0.88

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 (66) hide show
  1. package/claude/references/sd-simplysm14/README.md +17 -18
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +61 -0
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +119 -0
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +50 -0
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +44 -0
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +55 -0
  7. package/claude/references/sd-simplysm14/apis/angular/infra.md +74 -0
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +55 -0
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +115 -0
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +64 -0
  11. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +43 -0
  12. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +70 -0
  13. package/claude/references/sd-simplysm14/apis/angular/sheet.md +78 -0
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +80 -0
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +66 -0
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +71 -0
  17. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +67 -0
  18. package/claude/references/sd-simplysm14/apis/core-browser/README.md +83 -0
  19. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +79 -0
  20. package/claude/references/sd-simplysm14/apis/core-common/README.md +138 -0
  21. package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +72 -0
  22. package/claude/references/sd-simplysm14/apis/core-common/datetime.md +95 -0
  23. package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +47 -0
  24. package/claude/references/sd-simplysm14/apis/core-common/obj.md +53 -0
  25. package/claude/references/sd-simplysm14/apis/core-node/README.md +14 -0
  26. package/claude/references/sd-simplysm14/apis/core-node/consola.md +51 -0
  27. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +39 -0
  28. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +38 -0
  29. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +86 -0
  30. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +42 -0
  31. package/claude/references/sd-simplysm14/apis/core-node/worker.md +54 -0
  32. package/claude/references/sd-simplysm14/apis/excel/README.md +43 -0
  33. package/claude/references/sd-simplysm14/apis/excel/cell.md +54 -0
  34. package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +51 -0
  35. package/claude/references/sd-simplysm14/apis/excel/style.md +67 -0
  36. package/claude/references/sd-simplysm14/apis/excel/utils.md +35 -0
  37. package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +97 -0
  38. package/claude/references/sd-simplysm14/apis/excel/wrapper.md +83 -0
  39. package/claude/references/sd-simplysm14/apis/lint/README.md +43 -0
  40. package/claude/references/sd-simplysm14/apis/lint/rules.md +90 -0
  41. package/claude/references/sd-simplysm14/apis/orm-common/README.md +67 -0
  42. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +80 -0
  43. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +113 -0
  44. package/claude/references/sd-simplysm14/apis/orm-common/query-builder.md +29 -0
  45. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +111 -0
  46. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +162 -0
  47. package/claude/references/sd-simplysm14/apis/orm-common/types.md +52 -0
  48. package/claude/references/sd-simplysm14/apis/orm-node/README.md +53 -0
  49. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +94 -0
  50. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +29 -0
  51. package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +70 -0
  52. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +173 -0
  53. package/claude/references/sd-simplysm14/apis/service-client/README.md +152 -0
  54. package/claude/references/sd-simplysm14/apis/service-client/orm.md +45 -0
  55. package/claude/references/sd-simplysm14/apis/service-client/transport.md +36 -0
  56. package/claude/references/sd-simplysm14/apis/service-common/README.md +70 -0
  57. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +48 -0
  58. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +72 -0
  59. package/claude/references/sd-simplysm14/apis/service-server/README.md +102 -0
  60. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +74 -0
  61. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +51 -0
  62. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +50 -0
  63. package/claude/references/sd-simplysm14/apis/storage/README.md +114 -0
  64. package/claude/skills/sd-docs/SKILL.md +17 -29
  65. package/claude/skills/sd-docs/references/{subagent-prompt.md → doc-rules.md} +25 -40
  66. package/package.json +1 -1
@@ -0,0 +1,173 @@
1
+ # @simplysm/sd-cli — sd.config.ts 설정 타입
2
+
3
+ 프로젝트 루트 `sd.config.ts` 작성에 쓰는 타입 묶음. `sd.config.ts` 는 `SdConfigFn`(=`(params: SdConfigParams) => SdConfig | Promise<SdConfig>`)을 default export 해야 함. 권위 소스는 `packages/sd-cli/src/sd-config.types.ts`.
4
+
5
+ ## SdConfigFn / SdConfigParams
6
+
7
+ `sd.config.ts` 의 default export 시그니처. `SdConfig` 또는 그 Promise 를 반환.
8
+
9
+ ```typescript
10
+ type SdConfigFn = (params: SdConfigParams) => SdConfig | Promise<SdConfig>;
11
+ ```
12
+
13
+ `SdConfigParams` (함수에 주입되는 인자):
14
+
15
+ - cwd: string — 현재 작업 디렉토리(워크스페이스 루트). 경로 계산 기준.
16
+ - dev: boolean — 개발 모드 플래그. true 면 dev 실행, false 면 빌드/배포. env 분기에 사용.
17
+ - opt: string[] — CLI 의 `-o` 플래그로 전달된 추가 옵션 문자열 배열. 조건부 설정 분기용.
18
+
19
+ ```typescript
20
+ import type { SdConfigFn } from "@simplysm/sd-cli";
21
+ const config: SdConfigFn = ({ dev }) => ({
22
+ packages: {
23
+ "core-common": { target: "neutral" },
24
+ "core-node": { target: "node" },
25
+ },
26
+ });
27
+ export default config;
28
+ ```
29
+
30
+ ## SdConfig
31
+
32
+ `sd.config.ts` 최상위 설정 객체.
33
+
34
+ - packages: Record<string, SdPackageConfig | undefined> — 패키지별 설정. key 는 `packages/` 하위 디렉토리명(예: `"core-common"`). 값을 `undefined` 로 두면 해당 패키지 비활성. 빌드 대상·타겟을 패키지마다 지정.
35
+ - replaceDeps?: Record<string, string> — 의존성 교체. key 는 node_modules 에서 찾을 패키지 glob(예: `"@simplysm/*"`), value 는 소스 디렉토리 경로이며 key 의 `*` 캡처가 value 의 `*` 에 치환됨. node_modules 패키지를 로컬 소스로 심링크해 빌드 없이 소스 import 할 때.
36
+ - postPublish?: SdPostPublishScriptConfig[] — 배포 완료 후 순차 실행할 스크립트 목록. 배포 후 후처리(태깅·알림 등)에 사용.
37
+
38
+ ## SdPackageConfig (타겟별 패키지 설정)
39
+
40
+ `SdBuildPackageConfig | SdClientPackageConfig | SdServerPackageConfig | SdScriptsPackageConfig` 의 union. `target` 값으로 분기된다.
41
+
42
+ ### SdBuildPackageConfig (라이브러리: node/browser/neutral)
43
+
44
+ - target: "node" | "browser" | "neutral" — esbuild 라이브러리 빌드 타겟. "node" = Node.js 전용, "browser" = 브라우저 전용, "neutral" = 공용. 패키지 실행 환경에 맞춰 선택.
45
+ - publish?: SdPublishConfig — 배포 설정. 미지정 시 배포 제외.
46
+ - copySrc?: string[] — `src/` 에서 `dist/` 로 그대로 복사할 파일의 glob 패턴(src/ 기준 상대 경로). 비-TS 리소스 동봉용.
47
+ - watch?: SdWatchHookConfig — watch 훅. 지정 시 watch 모드에서 빌드 엔진과 함께 훅 명령 실행.
48
+
49
+ ### SdClientPackageConfig (Frontend 앱: target "client")
50
+
51
+ - target: "client" — esbuild 기반 Angular 클라이언트 앱 빌드.
52
+ - server: string | number — 연결할 서버. string = 서버 패키지명(예: `"demo-server"`), number = 포트 직접 지정(하위 호환). 개발 프록시/API 대상 결정.
53
+ - env?: Record<string, string> — 빌드 시 `process.env` 를 객체로 치환할 환경 변수. 클라이언트 코드의 env 주입.
54
+ - publish?: SdPublishConfig — 배포 설정.
55
+ - capacitor?: SdCapacitorConfig — Capacitor(모바일) 패키징 설정. 지정 시 앱 패키징.
56
+ - electron?: SdElectronConfig — Electron(데스크톱) 패키징 설정. 지정 시 데스크톱 패키징.
57
+ - configs?: Record<string, unknown> — 런타임 설정. 빌드 시 `dist/.config.json` 으로 기록되어 런타임에 로드됨.
58
+ - exclude?: string[] — Capacitor/Electron package.json 에 추가할 패키지(또는 제외 목록). 패키징 의존성 조정.
59
+ - browserSupport?: SdBrowserSupportConfig — 브라우저 지원(browserslist·PostCSS·legacyModule) 설정.
60
+ - pwa?: false | SdPwaConfig — PWA 설정. `false` 면 비활성, 미지정 시 기본값으로 활성화. PWA manifest 제어.
61
+
62
+ ### SdServerPackageConfig (Fastify 서버: target "server")
63
+
64
+ - target: "server" — esbuild 기반 Fastify 서버 앱 빌드.
65
+ - env?: Record<string, string> — 빌드 시 `process.env.KEY` 를 상수로 치환할 환경 변수.
66
+ - publish?: SdPublishConfig — 배포 설정.
67
+ - configs?: Record<string, unknown> — 런타임 설정. 빌드 시 `dist/.config.json` 으로 기록.
68
+ - externals?: string[] — esbuild 번들에 포함하지 않을 외부 모듈. 네이티브 모듈(binding.gyp 자동 감지분에 추가)을 번들 제외할 때.
69
+ - pm2?: { name?: string; ignoreWatchPaths?: string[] } — PM2 설정. 지정 시 `dist/pm2.config.cjs` 생성. `name` = 프로세스 이름(미지정 시 package.json name 기반), `ignoreWatchPaths` = PM2 watch 제외 경로.
70
+ - packageManager?: "volta" | "mise" — 사용할 패키지 매니저. 생성되는 mise.toml/volta 설정에 영향. 배포 환경의 런타임 관리자 선택.
71
+
72
+ ### SdScriptsPackageConfig (유틸 스크립트: target "scripts")
73
+
74
+ - target: "scripts" — 빌드 산출 없는 스크립트 전용 패키지. watch 훅 미설정 시 watch/typecheck 에서 제외됨.
75
+ - publish?: SdPublishConfig — 배포 설정.
76
+ - watch?: SdWatchHookConfig — watch 훅. 지정 시 watch 모드에 패키지 포함.
77
+
78
+ ## SdWatchHookConfig
79
+
80
+ watch 모드에서 파일 변경 시 임의 명령을 실행하는 훅. `SdBuildPackageConfig.watch` / `SdScriptsPackageConfig.watch` 에 사용.
81
+
82
+ - target: string[] — 감시할 glob 패턴(패키지 디렉토리 기준 상대 경로). 어떤 파일 변경을 트리거로 볼지.
83
+ - cmd: string — 변경 시 실행할 명령어.
84
+ - args?: string[] — 명령어 인수.
85
+
86
+ ## 배포 설정 (SdPublishConfig 계열)
87
+
88
+ `SdNpmPublishConfig | SdLocalDirectoryPublishConfig | SdStoragePublishConfig` union. `type` 으로 분기.
89
+
90
+ - SdNpmPublishConfig — `{ type: "npm" }`. npm 레지스트리 배포.
91
+ - SdLocalDirectoryPublishConfig — `{ type: "local-directory"; path: string }`. 로컬 디렉토리 복사 배포. `path` 는 환경 변수 치환(`%VER%`, `%PROJECT%`) 지원.
92
+ - SdStoragePublishConfig — FTP/FTPS/SFTP 업로드 배포.
93
+ - type: "ftp" | "ftps" | "sftp" — 전송 프로토콜. 보안 필요 시 ftps/sftp.
94
+ - host: string — 서버 호스트.
95
+ - port?: number — 포트.
96
+ - path?: string — 업로드 대상 경로.
97
+ - user?: string — 접속 계정.
98
+ - password?: string — 접속 비밀번호.
99
+
100
+ `SdPostPublishScriptConfig` (배포 후 스크립트):
101
+
102
+ - type: "script" — 스크립트 실행 항목.
103
+ - cmd: string — 실행 명령어.
104
+ - args: string[] — 인수. 환경 변수 치환(`%VER%`, `%PROJECT%`) 지원.
105
+
106
+ ## Capacitor 설정 (SdClientPackageConfig.capacitor)
107
+
108
+ `SdCapacitorConfig`:
109
+
110
+ - appId: string — 앱 ID(예: `"com.example.app"`).
111
+ - appName: string — 앱 이름.
112
+ - plugins?: Record<string, Record<string, unknown> | true> — Capacitor 플러그인 설정. key = 패키지명, value = `true`(옵션 없음) 또는 플러그인 옵션 객체.
113
+ - icon?: string — 앱 아이콘 경로(패키지 디렉토리 기준 상대).
114
+ - debug?: boolean — 디버그 빌드 플래그.
115
+ - platform?: { android?: SdCapacitorAndroidConfig } — 플랫폼별 설정(현재 android).
116
+
117
+ `SdCapacitorAndroidConfig`:
118
+
119
+ - config?: Record<string, string> — AndroidManifest.xml `application` 태그 속성(예: `{ requestLegacyExternalStorage: "true" }`).
120
+ - bundle?: boolean — true 면 AAB 번들, false 면 APK 빌드.
121
+ - intentFilters?: SdCapacitorIntentFilter[] — Intent Filter 목록.
122
+ - sign?: SdCapacitorSignConfig — APK/AAB 서명 설정.
123
+ - sdkVersion?: number — Android SDK 버전(minSdk·targetSdk 공통).
124
+ - permissions?: SdCapacitorPermission[] — 추가 권한 목록.
125
+
126
+ `SdCapacitorIntentFilter`:
127
+
128
+ - action?: string — intent 액션(예: `"android.intent.action.VIEW"`).
129
+ - category?: string — intent 카테고리(예: `"android.intent.category.DEFAULT"`).
130
+
131
+ `SdCapacitorSignConfig`:
132
+
133
+ - keystore: string — keystore 파일 경로(패키지 디렉토리 기준 상대).
134
+ - storePassword: string — keystore 비밀번호.
135
+ - alias: string — 키 별칭.
136
+ - password: string — 키 비밀번호.
137
+ - keystoreType?: string — keystore 타입(기본 `"jks"`).
138
+
139
+ `SdCapacitorPermission`:
140
+
141
+ - name: string — 권한 이름(예: `"CAMERA"`, `"WRITE_EXTERNAL_STORAGE"`).
142
+ - maxSdkVersion?: number — 권한 적용 최대 SDK 버전.
143
+ - ignore?: string — `tools:ignore` 속성 값.
144
+
145
+ ## Electron 설정 (SdElectronConfig)
146
+
147
+ - appId: string — Electron 앱 ID(예: `"com.example.myapp"`).
148
+ - portable?: boolean — true 면 포터블 `.exe`, false/미지정 시 NSIS 설치 프로그램.
149
+ - installerIcon?: string — 설치 프로그램 아이콘 경로(`.ico`, 패키지 기준 상대).
150
+ - reinstallDependencies?: string[] — Electron 에 포함할 npm 패키지(네이티브 모듈 등).
151
+ - postInstallScript?: string — npm postinstall 스크립트.
152
+ - nsisOptions?: Record<string, unknown> — NSIS 옵션(`portable` 이 false 일 때 적용).
153
+ - env?: Record<string, string> — 환경 변수. `electron-main.ts` 에서 `process.env` 로 접근.
154
+
155
+ ## PWA 설정 (SdPwaConfig)
156
+
157
+ `SdPwaConfig`:
158
+
159
+ - manifest?: SdPwaManifestConfig — PWA manifest 설정.
160
+
161
+ `SdPwaManifestConfig` (모두 선택):
162
+
163
+ - name?: string — 앱 전체 이름.
164
+ - short_name?: string — 짧은 이름(홈 화면 등).
165
+ - display?: "standalone" | "fullscreen" | "minimal-ui" | "browser" — 표시 모드. "standalone" = 단독 앱처럼, "fullscreen" = 전체 화면, "minimal-ui" = 최소 브라우저 UI, "browser" = 일반 탭. 앱 체감 수준 선택.
166
+ - theme_color?: string — 테마 색상.
167
+ - background_color?: string — 스플래시 배경 색상.
168
+ - icons?: Array<{ src: string; sizes: string; type?: string }> — 아이콘 목록. `src` = 경로, `sizes` = 크기 문자열(예: `"512x512"`), `type` = MIME 타입.
169
+
170
+ ## 보조 타입
171
+
172
+ - BuildTarget — `"node" | "browser" | "neutral"`. `SdBuildPackageConfig.target` 값 집합.
173
+ - NpmConfig — npm `package.json` 구조 타입(`name`, `version`, `description?`, `dependencies?`, `devDependencies?`, `peerDependencies?`, `volta?`). 설정 코드에서 package.json 을 타입 있게 다룰 때.
@@ -0,0 +1,152 @@
1
+ # @simplysm/service-client
2
+
3
+ WebSocket 기반 simplysm 서비스 서버에 접속하는 클라이언트. 서비스 메서드 RPC 호출, 인증, 서버 푸시 이벤트 구독, 파일 업/다운로드, ORM 원격 실행을 제공한다. Node.js/브라우저 양쪽에서 동작(브라우저 `WebSocket` 없으면 `ws` 패키지로 polyfill).
4
+
5
+ ## 사용 트리거 인덱스
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)
16
+
17
+ ## ServiceClient
18
+
19
+ 서버 접속과 모든 RPC/이벤트/파일 기능의 진입점. `EventEmitter<ServiceClientEvents>` 를 상속.
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
+ 상태 접근자:
30
+
31
+ - `connected: boolean` (getter) — 현재 WebSocket 이 OPEN 상태인지. 재연결 중·종료 시 false.
32
+ - `hostUrl: string` (getter) — `http(s)://<host>:<port>` 형태 HTTP 베이스 URL. ssl 이면 `https`. 파일 전송이 이 URL 을 사용.
33
+
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`) — 아래 "파일 전송" 참조.
43
+
44
+ ```ts
45
+ const client = createServiceClient("my-app", { host: "localhost", port: 50080, ssl: false });
46
+ await client.connect();
47
+ await client.auth(jwtToken);
48
+ client.on("state", (state) => console.log(state)); // "connected" | "closed" | "reconnecting"
49
+ ```
50
+
51
+ ServiceClientEvents (EventEmitter 이벤트):
52
+
53
+ - `state: "connected"|"closed"|"reconnecting"` — 접속 상태 변화. 재연결 성공("connected") 시 보관된 토큰으로 `auth` 재호출과 이벤트 리스너 자동 복구가 일어남.
54
+ - `request-progress: ServiceProgressState` — 요청 분할 전송 진행률(요청 청크가 2개 이상일 때).
55
+ - `response-progress: ServiceProgressState` — 응답 수신 진행률.
56
+ - `server-progress: ServiceProgressState` — 서버가 처리 중 보고하는 진행률.
57
+
58
+ ## ServiceConnectionOptions
59
+
60
+ `createServiceClient` 의 두 번째 인자.
61
+
62
+ - `port: number` — 서버 포트. 필수.
63
+ - `host: string` — 서버 호스트. 필수.
64
+ - `ssl?: boolean` — TLS 사용 여부. true 면 `wss`/`https`, false·미지정이면 `ws`/`http`.
65
+ - `maxReconnectCount?: number` — 끊김 시 최대 재연결 시도 횟수. 미지정 시 10. `0` 이면 재연결을 비활성화하고 끊김 시 즉시 포기.
66
+
67
+ ## getService / ServiceProxy
68
+
69
+ 서버 서비스 인터페이스의 각 메서드를 `Promise` 반환 함수로 노출하는 프록시.
70
+
71
+ - `getService<TService>(serviceName: string): ServiceProxy<TService>` — `serviceName` 으로 등록된 서버 서비스에 대한 프록시 반환. 프록시 메서드 호출 = `client.send(serviceName, methodName, params)`.
72
+ - `ServiceProxy<TService>` — `TService` 의 함수 멤버 각각을 `(...args) => Promise<Awaited<R>>` 로 매핑. 함수가 아닌 속성은 `never` 로 제외.
73
+
74
+ ```ts
75
+ const svc = client.getService<MyService>("MyService");
76
+ const result = await svc.echo("hi"); // 서버의 MyService.echo("hi") 호출, Promise<string>
77
+ ```
78
+
79
+ ## 이벤트 구독
80
+
81
+ 서버 푸시 이벤트를 키 기반으로 구독·발행. 이벤트 정의는 `@simplysm/service-common` 의 `defineEvent` 로 만든 `ServiceEventDef`(`$info` = 구독 필터 정보 타입, `$data` = 페이로드 타입)를 사용.
82
+
83
+ ServiceClient 메서드:
84
+
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
+
90
+ ClientEventProxy<TEventDef>:
91
+
92
+ - `addListener(info, cb): Promise<string>` — 위 `addListener` 의 eventDef 고정판.
93
+ - `removeListener(key): Promise<void>` — 리스너 해제.
94
+ - `emit(infoSelector, data): Promise<void>` — 위 `emitEvent` 의 eventDef 고정판.
95
+
96
+ ```ts
97
+ const evtDef = defineEvent<{ channel: string }, string>("TestEvent");
98
+ const key = await client.addListener(evtDef, { channel: "a" }, async (data) => { /* ... */ });
99
+ await client.removeListener(key);
100
+ ```
101
+
102
+ `EventClient` / `createEventClient(transport)` 는 `ServiceClient` 가 내부 조립에 쓰는 저수준 구현. 위 4개 메서드에 더해 `resubscribeAll(): Promise<void>`(보관된 모든 리스너를 서버에 재등록, 재연결 복구용)을 가짐. 일반 사용에선 직접 만들지 않음.
103
+
104
+ ## 파일 전송
105
+
106
+ ServiceClient 메서드:
107
+
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.
110
+
111
+ `files` 허용 형식 (`FileClient.upload` 기준):
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)` 두 메서드를 가지며 직접 생성은 보통 불필요.
120
+
121
+ ```ts
122
+ await client.auth(token);
123
+ const results = await client.uploadFile([{ name: "a.txt", data: "hello" }]);
124
+ const bytes = await client.downloadFileBuffer("/files/a.txt");
125
+ ```
126
+
127
+ ## 진행률 추적
128
+
129
+ 대용량 요청/응답이 청크로 분할될 때 진행 상황을 보고하는 콜백·상태 타입.
130
+
131
+ ServiceProgress — `send` 류에 넘길 수 있는 콜백 집합. 각 콜백은 `(state: ServiceProgressState) => void`:
132
+
133
+ - `request?` — 요청 청크 전송 진행(요청 청크 2개 이상일 때만).
134
+ - `response?` — 응답 수신 진행. 분할 응답이었으면 완료 시 100%(`completedSize === totalSize`)를 한 번 더 보고.
135
+ - `server?` — 서버가 처리 중 보고하는 진행(`name: "progress"` 메시지 수신 시).
136
+
137
+ ServiceProgressState:
138
+
139
+ - `uuid: string` — 해당 요청/응답을 식별하는 UUID. 동시 요청 구분용.
140
+ - `totalSize: number` — 전체 바이트 크기.
141
+ - `completedSize: number` — 현재까지 처리된 바이트 크기. `totalSize` 와 같아지면 완료.
142
+
143
+ `send` 호출 시 위 콜백과 무관하게 ServiceClient 의 `request/response/server-progress` 이벤트도 항상 발생하므로, 전역 추적이면 콜백 대신 `client.on("response-progress", ...)` 를 써도 됨.
144
+
145
+ ## 환경 호환 유틸 (browser-compat)
146
+
147
+ Node/브라우저 Worker 지원 여부 판별 함수와 Worker 인터페이스. 프로토콜 인코딩/파싱을 Worker 로 오프로딩할지 결정할 때 내부에서 사용.
148
+
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()`.
@@ -0,0 +1,45 @@
1
+ # @simplysm/service-client — ORM 원격 실행
2
+
3
+ 서버에 연결된 DB 를 클라이언트 측 `DbContext`(`@simplysm/orm-common`)로 트랜잭션 단위 실행하는 기능. 클라이언트가 직접 DB 에 붙지 않고, 모든 쿼리를 `ServiceClient` 의 `Orm` 서비스 RPC 로 서버에 위임한다. `connect` 콜백 내부에서만 쿼리 가능.
4
+
5
+ ## OrmConnectOptions<T extends DbContext>
6
+
7
+ `OrmClientConnector` 의 `connect`/`connectWithoutTransaction` 에 넘기는 설정.
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(결측을 임의 보정하지 않음).
12
+
13
+ ## OrmClientConnector
14
+
15
+ DbContext 를 만들고 트랜잭션 경계를 잡아 콜백을 실행하는 커넥터. 사용 전 `ServiceClient.connect()` 로 소켓이 연결돼 있어야 함(RPC 의존).
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.
19
+ - `connectWithoutTransaction<T, R>(config, callback): Promise<R>` — 트랜잭션 없이 `callback(db)` 실행. 조회 전용·트랜잭션 불필요 작업에 사용.
20
+
21
+ ```ts
22
+ const connector = createOrmClientConnector(client);
23
+ await connector.connect(
24
+ { DbClass: MyDb, connOpt: { configName: "main" } },
25
+ async (db) => {
26
+ await db.foo.insertAsync({ /* ... */ });
27
+ return db.foo.where(/* ... */).resultAsync();
28
+ },
29
+ ); // 콜백 throw 시 자동 롤백
30
+ ```
31
+
32
+ ## OrmClientDbContextExecutor
33
+
34
+ `DbContextExecutor`(`@simplysm/orm-common`) 구현체. 모든 메서드를 `ServiceClient.getService<OrmService>("Orm")` RPC 로 위임. 보통 `OrmClientConnector` 가 내부에서 생성하므로 직접 다룰 일은 드묾.
35
+
36
+ - `new OrmClientDbContextExecutor(client: ServiceClient, opt: DbConnOptions & { configName: string })` — 생성. 생성 시 `Orm` 서비스 프록시 확보.
37
+ - `getInfo(): Promise<{ dialect; database?; schema? }>` — 서버 DB 의 dialect 및 기본 database/schema 조회.
38
+ - `connect(): Promise<void>` — 서버에 커넥션 생성 요청, 반환된 `connId` 보관. 이후 모든 실행 메서드는 `connId` 없으면(미연결) throw.
39
+ - `beginTransaction(isolationLevel?): Promise<void>` — 트랜잭션 시작. `isolationLevel` = 격리 수준(`IsolationLevel`), 미지정 시 서버 기본값.
40
+ - `commitTransaction(): Promise<void>` — 커밋.
41
+ - `rollbackTransaction(): Promise<void>` — 롤백.
42
+ - `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` = 삽입할 행 객체 배열.
@@ -0,0 +1,36 @@
1
+ # @simplysm/service-client — 저수준 전송 계층
2
+
3
+ `ServiceClient` 가 생성자에서 내부적으로 조립하는 저수준 모듈들. WebSocket 연결·하트비트·재연결(SocketProvider), 요청/응답 매칭과 메시지 디스패치(ServiceTransport), 인코딩/디코딩의 Worker 오프로딩(ClientProtocolWrapper). 일반 사용에서는 `ServiceClient` 만 쓰면 되고, 이 계층은 직접 다룰 일이 드물다.
4
+
5
+ ## SocketProvider / createSocketProvider
6
+
7
+ WebSocket 1개의 연결·하트비트·자동 재연결을 담당.
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 수신은 무시.
10
+ - `clientName: string` (readonly) — 생성 시 받은 식별명.
11
+ - `connected: boolean` (getter) — 소켓이 OPEN 인지.
12
+ - `connect(): Promise<void>` — 접속 시작. 실패 시 throw, 성공 시 재연결 카운트 리셋하고 `state: "connected"` emit.
13
+ - `close(): Promise<void>` — 수동 종료. 이후 자동 재연결 안 함. `state: "closed"` emit.
14
+ - `send(data: Bytes): Promise<void>` — 바이트 전송. 일정 시간 내 미연결이면 throw.
15
+ - `on/off(type, listener)` — 이벤트 구독/해제. 이벤트(`SocketProviderEvents`): `message: Bytes`(수신 바이트), `state: "connected"|"closed"|"reconnecting"`(연결 상태 변화).
16
+
17
+ 타임아웃 감지 시 소켓을 강제 정리하고(중복 onclose 재연결 방지로 핸들러 해제) 수동 종료가 아니면 재연결을 시도한다. 최대 시도 초과 시 `state: "closed"` emit.
18
+
19
+ ## ServiceTransport / createServiceTransport
20
+
21
+ 요청별 uuid 매칭, 응답/에러/진행률/서버이벤트 디스패치를 담당.
22
+
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` 가 이걸 구독해 로컬 리스너로 디스패치).
26
+
27
+ decode 실패 시에도 헤더에서 uuid 를 선추출해 해당 요청만 reject 한다. 분할 응답이면 완료 시 `progress.response` 로 100% 를 한 번 더 보고.
28
+
29
+ ## ClientProtocolWrapper / createClientProtocolWrapper
30
+
31
+ 인코드/디코드를 크기 기준으로 Worker 에 오프로딩하는 래퍼. `@simplysm/service-common` 의 `ServiceProtocol` 을 감쌈.
32
+
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) 면 그대로 반환.
36
+ - `dispose(): void` — 프로토콜과 Worker 리졸버 정리. `ServiceClient.close()` 에서 호출.
@@ -0,0 +1,70 @@
1
+ # @simplysm/service-common
2
+
3
+ 서버·클라이언트가 공유하는 서비스 통신 계약. 바이너리 프로토콜(인코딩/청킹/재조립), 서비스 인터페이스 타입(ORM·자동업데이트·업로드), 타입 안전 이벤트 정의, 앱 메뉴/권한 구조 모델을 한 패키지에 둔다.
4
+
5
+ ## 사용 트리거 인덱스
6
+
7
+ - **서비스 프로토콜** — 서버·클라이언트 간 메시지를 바이너리로 인코딩/디코딩하거나, 3MB 초과 메시지의 청킹·재조립을 다룰 때. 메시지 타입·`PROTOCOL_CONFIG` 상수 포함. (자세히: [protocol.md](./protocol.md))
8
+ - **앱 구조 / 권한** — 메뉴 트리(`AppStructureItem`)를 정의하거나, 사용자 활성 모듈 기준으로 권한을 평탄화·필터링할 때. (자세히: [app-structure.md](./app-structure.md))
9
+ - **defineEvent / ServiceEventDef** — 서버·클라 공통 패키지에서 타입 안전한 서비스 이벤트를 정의해 emit/구독에 쓸 때. (아래 인라인)
10
+ - **OrmService / DbConnOptions** — DB 연결·트랜잭션·쿼리 실행 서비스 시그니처를 구현/호출할 때. (아래 인라인)
11
+ - **AutoUpdateService** — 클라이언트 자동 업데이트 최신 버전 조회 서비스를 구현/호출할 때. (아래 인라인)
12
+ - **ServiceUploadResult** — 파일 업로드 응답 결과를 다룰 때. (아래 인라인)
13
+
14
+ ## 이벤트 정의 (defineEvent / ServiceEventDef)
15
+
16
+ 서버·클라이언트가 공유하는 공통 패키지에서 이벤트를 1회 정의해 양쪽에서 동일 객체로 emit/구독한다. 정의 객체를 `emitEvent`/`addListener` 에 그대로 넘기면 이름·타입이 자동 추론된다.
17
+
18
+ ```ts
19
+ function defineEvent<TInfo = unknown, TData = unknown>(eventName: string): ServiceEventDef<TInfo, TData>;
20
+
21
+ interface ServiceEventDef<TInfo = unknown, TData = unknown> {
22
+ eventName: string;
23
+ readonly $info: TInfo;
24
+ readonly $data: TData;
25
+ }
26
+ ```
27
+
28
+ - `defineEvent(eventName)` 의 `eventName` — 이벤트 식별 문자열. 서버/클라가 같은 정의를 import 하므로 충돌 없게 유일해야 함.
29
+ - `TInfo` — 구독자 필터링용 정보 타입(예: 특정 orderId 만 수신). 서버 emit 시 필터 함수 인자 타입.
30
+ - `TData` — 이벤트 페이로드 타입. 리스너 콜백이 받는 데이터 타입.
31
+ - `eventName: string` (필드) — 런타임 식별자.
32
+ - `$info: TInfo` / `$data: TData` — 타입 추출 전용 마커. 런타임 값은 `undefined` 이며 직접 읽지 말 것.
33
+
34
+ ```ts
35
+ export const OrderUpdated = defineEvent<{ orderId: number }, { status: string }>("OrderUpdated");
36
+ await server.emitEvent(OrderUpdated, (info) => info.orderId === 123, { status: "shipped" });
37
+ await client.addListener(OrderUpdated, { orderId: 123 }, async (data) => console.log(data.status));
38
+ ```
39
+
40
+ ## 서비스 인터페이스 타입
41
+
42
+ 서버가 구현하고 클라이언트가 프록시로 호출하는 서비스 계약. 본 패키지는 구현체가 아니라 타입만 제공한다.
43
+
44
+ ### OrmService
45
+
46
+ DB 연결·트랜잭션·쿼리 실행. MySQL/MSSQL/PostgreSQL 지원.
47
+
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.
56
+
57
+ `DbConnOptions = { configName?: string; config?: Record<string, unknown> }`
58
+ - `configName` — 서버에 사전 등록된 DB 설정 이름 참조. `config` — 인라인 연결 설정 객체. 둘 중 하나로 연결 대상을 지정하며, `getInfo`/`connect` 시그니처에서는 `configName` 이 필수로 교차됨.
59
+
60
+ ### AutoUpdateService
61
+
62
+ - `getLastVersion(platform: string): Promise<{ version; downloadPath } | undefined>` — `platform`(예: `"win32"`/`"darwin"`/`"linux"`) 별 최신 버전 정보 반환. 등록된 버전이 없으면 `undefined`(결측 그대로 전파).
63
+
64
+ ## ServiceUploadResult
65
+
66
+ 서버에 업로드된 파일의 응답 정보.
67
+
68
+ - `path: string` — 서버 내 저장 경로.
69
+ - `filename: string` — 원본 파일명(클라이언트가 보낸 이름).
70
+ - `size: number` — 파일 크기(바이트).
@@ -0,0 +1,48 @@
1
+ # @simplysm/service-common — app-structure
2
+
3
+ 앱의 메뉴·권한 트리 정의 타입과, 사용자의 활성 모듈(`usableModules`) 기준으로 권한을 평탄화/필터링하는 유틸. 트리 노드는 `modules`(OR)·`requiredModules`(AND) 로 가시성을 제어한다. `TModule` 제네릭은 모듈 식별자 타입(앱별 enum/string).
4
+
5
+ ## 트리 타입
6
+
7
+ `AppStructureItem<TModule>` = `AppStructureGroupItem` | `AppStructureLeafItem`. 메뉴 트리의 노드.
8
+
9
+ `AppStructureGroupItem<TModule>` — 하위 노드를 갖는 그룹 노드.
10
+ - `code: string` — 노드 식별 코드. 권한 codeChain 에 누적됨.
11
+ - `title: string` — 표시 제목. titleChain 에 누적됨.
12
+ - `modules?: TModule[]` — 이 중 하나라도 활성이면 표시(OR).
13
+ - `requiredModules?: TModule[]` — 전부 활성이어야 표시(AND).
14
+ - `icon?: string` — 메뉴 아이콘.
15
+ - `children: AppStructureItem<TModule>[]` — 하위 노드 배열(필수, 그룹 판별 키).
16
+
17
+ `AppStructureLeafItem<TModule>` — 실제 화면 노드.
18
+ - `code` / `title` / `modules?` / `requiredModules?` / `icon?` — 그룹과 동일 의미.
19
+ - `perms?: ("use" | "edit")[]` — 이 화면 직접 권한. `"use"`=조회 권한 / `"edit"`=편집 권한. 각 항목이 평탄 권한 1건이 됨.
20
+ - `subPerms?: AppStructureSubPermission<TModule>[]` — 화면 내 세부 권한 묶음.
21
+ - `url?: string` — 라우팅 경로.
22
+ - `isNotMenu?: boolean` — true 면 메뉴에 노출 안 함(권한만 존재하는 화면), false/미지정이면 메뉴 노출.
23
+
24
+ `AppStructureSubPermission<TModule>` — 화면 하위 세부 권한 묶음.
25
+ - `code` / `title` / `modules?` / `requiredModules?` — 동일 의미.
26
+ - `perms: ("use" | "edit")[]` — 이 세부 묶음의 권한 종류(필수). `"use"`=조회 / `"edit"`=편집.
27
+
28
+ `FlatPermission<TModule>` — 평탄화 결과 1건.
29
+ - `titleChain: string[]` — 루트→해당 권한까지 제목 경로.
30
+ - `codeChain: string[]` — 코드 + perm/subPerm 코드 누적 경로(권한 식별자).
31
+ - `modulesChain: TModule[][]` — 경로상 각 레벨 modules 누적.
32
+
33
+ ## 유틸 함수
34
+
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 도 추가 검사.
38
+
39
+ ```ts
40
+ const flats = getFlatPermissions(appStructure, currentUser.usableModules);
41
+ const codes = flats.map((f) => f.codeChain.join(".")); // 예: "order.list.edit"
42
+ if (isUsableModules(item.modules, item.requiredModules, usableModules)) showMenu(item);
43
+ ```
44
+
45
+ 주의:
46
+ - `usableModules` 가 `undefined` 이면 modules/requiredModules 가 지정된 노드는 통과 못 함(`includes` 가 false).
47
+ - `modules` 가 비었거나 `undefined` 인 노드는 모듈 제약 없이 항상 통과(OR 기본).
48
+ - `codeChain` 마지막 요소는 perm(`"use"`/`"edit"`) 또는 subPerm.code 다음의 perm 으로 끝남.
@@ -0,0 +1,72 @@
1
+ # @simplysm/service-common — protocol
2
+
3
+ 서버·클라이언트 간 서비스 메시지의 바이너리 인코딩/디코딩과 청크 재조립을 담당하는 프로토콜(V2). 헤더 28바이트(UUID 16 + TotalSize 8 + Index 4) + JSON 본문, 3MB 초과 시 300KB 청크로 자동 분할, 최대 100MB.
4
+
5
+ ## createServiceProtocol / ServiceProtocol
6
+
7
+ `createServiceProtocol(): ServiceProtocol` — stateful 청크 누적기를 내장한 프로토콜 인스턴스 생성. 누적기는 GC 타이머를 가지므로 사용 종료 시 `dispose()` 필수.
8
+
9
+ `ServiceProtocol` 메서드:
10
+
11
+ - `encode(uuid, message): { chunks: Bytes[]; totalSize: number }` — 메시지를 `[name, body]` JSON→바이트로 직렬화 후 헤더 부착. `SPLIT_MESSAGE_SIZE`(3MB) 이하면 단일 청크, 초과면 `CHUNK_SIZE`(300KB) 단위 분할. `MAX_TOTAL_SIZE`(100MB) 초과 시 `ArgumentError` throw. `uuid` 는 메시지 묶음 식별자(재조립 키).
12
+ - `accumulate(bytes): ServiceAccumulateResult` — 수신 청크 1개를 uuid별 누적기에 모음(stateful, 재조립 전용). 같은 index 중복 패킷은 무시. JSON 파싱은 안 함. 미완성이면 `progress`, 전 청크 도착 시 raw 바이트 담은 `complete` 반환. 헤더 미만(<28B)/크기 초과/무결성 위반(completedSize > totalSize) 시 throw.
13
+ - `parseMessage(resultBytes): ServiceMessage` — 재조립된 raw 바이트를 메시지 객체로 파싱(stateless). 누적 상태 비의존이라 worker 등 다른 컨텍스트에 위임 가능. 파싱 실패 시 `ArgumentError` throw.
14
+ - `decode<T>(bytes): ServiceMessageDecodeResult<T>` — `accumulate` 후 완료 시 `parseMessage` 까지 수행하는 통합 동작. 가장 일반적인 수신 처리 경로.
15
+ - `dispose(): void` — 내부 누적기 GC 타이머 해제·메모리 반환. 인스턴스 폐기 전 반드시 호출.
16
+
17
+ ```ts
18
+ const proto = createServiceProtocol();
19
+ try {
20
+ const { chunks } = proto.encode(uuid, { name: "auth", body: token });
21
+ for (const c of chunks) send(c);
22
+ const r = proto.decode(recvBytes);
23
+ if (r.type === "complete") handle(r.message);
24
+ } finally { proto.dispose(); }
25
+ ```
26
+
27
+ `ServiceMessageDecodeResult<T>` (유니언, `type` 판별):
28
+ - `{ type: "complete"; uuid; message: T }` — 전 청크 수신, 메시지 재조립·파싱 완료.
29
+ - `{ type: "progress"; uuid; totalSize; completedSize }` — 일부 청크만 도착. 진행률 표시용.
30
+
31
+ `ServiceAccumulateResult` (유니언, `type` 판별):
32
+ - `{ type: "complete"; uuid; resultBytes: Bytes }` — 재조립 완료, 파싱 전 raw 바이트.
33
+ - `{ type: "progress"; uuid; totalSize; completedSize }` — 진행 중.
34
+
35
+ 주의:
36
+ - `dispose()` 누락 시 GC 타이머가 남아 메모리/타이머 누수.
37
+ - `EXPIRE_TIME`(60초) 내 모든 청크가 도착하지 않으면 미완성 누적분이 GC 로 폐기됨.
38
+ - `parseMessage` 입력은 반드시 `accumulate`/`decode` 의 `complete` 가 준 raw 바이트여야 함.
39
+
40
+ ## PROTOCOL_CONFIG
41
+
42
+ `as const` 상수. 인코딩 분할·크기 제한·GC 동작 기준값.
43
+
44
+ - `MAX_TOTAL_SIZE: 100MB` — 단일 메시지 허용 최대 크기. 초과 시 `encode`/`accumulate` throw.
45
+ - `SPLIT_MESSAGE_SIZE: 3MB` — 이 값 초과 시 청킹 시작(이하면 단일 청크).
46
+ - `CHUNK_SIZE: 300KB` — 분할 청크 1개 본문 크기.
47
+ - `GC_INTERVAL: 10초` — 미완성 누적기 정리 주기.
48
+ - `EXPIRE_TIME: 60초` — 미완성 메시지 만료 시간.
49
+
50
+ ## 메시지 타입
51
+
52
+ 방향별 유니언과 개별 메시지 인터페이스. `name` literal 로 판별하는 discriminated union.
53
+
54
+ 분류 유니언:
55
+ - `ServiceMessage` — 전체 메시지 집합.
56
+ - `ServiceClientMessage` — 클라이언트→서버: request/auth/evt:add/evt:remove/evt:gets/evt:emit.
57
+ - `ServiceServerMessage` — 서버→클라이언트: response/error/evt:on.
58
+ - `ServiceServerRawMessage` — `ServiceServerMessage` + progress(청크 수신 진행 알림 포함).
59
+
60
+ 개별 메시지(`name` literal → 용도):
61
+ - `ServiceProgressMessage` `"progress"` — 서버가 청크 수신 진행 알림. `body: { totalSize, completedSize }`(바이트).
62
+ - `ServiceErrorMessage` `"error"` — 서버 에러 알림. `body: { name, message, code, stack?, detail?, cause? }`.
63
+ - `ServiceAuthMessage` `"auth"` — 클라이언트 인증. `body: string`(토큰).
64
+ - `ServiceRequestMessage` `` `${string}.${string}` `` — 클라이언트 서비스 메서드 호출(`service.method`). `body: unknown[]`(매개변수).
65
+ - `ServiceResponseMessage` `"response"` — 서버 응답. `body?: unknown`(결과).
66
+ - `ServiceAddEventListenerMessage` `"evt:add"` — 리스너 등록. `body: { key, name, info }` — `key`=리스너 키(uuid, 제거에 필요), `name`=이벤트 이름, `info`=발생 필터링용 정보.
67
+ - `ServiceRemoveEventListenerMessage` `"evt:remove"` — 리스너 제거. `body: { key }`(리스너 키).
68
+ - `ServiceGetEventListenerInfosMessage` `"evt:gets"` — 특정 이벤트 리스너 info 목록 요청. `body: { name }`(이벤트 이름).
69
+ - `ServiceEmitEventMessage` `"evt:emit"` — 클라이언트가 이벤트 발생 요청. `body: { keys, data }` — `keys`=대상 리스너 키 목록, `data`=데이터.
70
+ - `ServiceEventMessage` `"evt:on"` — 서버가 구독자에게 이벤트 전달. `body: { keys, data }`(리스너 키 목록·데이터).
71
+
72
+ 주의: `name` literal 로 분기해야 타입 좁히기가 동작. body 의 `unknown`/`unknown[]` 은 호출부에서 서비스 시그니처에 맞춰 캐스팅.