@simplysm/sd-claude 14.0.82 → 14.0.84

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 (90) hide show
  1. package/claude/references/sd-requirement-source-handling.md +20 -20
  2. package/claude/references/sd-simplysm14/README.md +13 -13
  3. package/claude/references/sd-simplysm14/manuals/client-component.md +92 -92
  4. package/claude/references/sd-simplysm14/manuals/client-crud.md +11 -11
  5. package/claude/references/sd-simplysm14/manuals/client-demo.md +28 -28
  6. package/claude/references/sd-simplysm14/manuals/client-rules.md +1 -1
  7. package/claude/references/sd-simplysm14/manuals/client-setup.md +21 -21
  8. package/claude/references/sd-simplysm14/manuals/client-tab.md +3 -3
  9. package/claude/references/sd-simplysm14/manuals/logging.md +15 -15
  10. package/claude/references/sd-simplysm14/manuals/orm-union.md +6 -6
  11. package/claude/references/sd-simplysm14/manuals/orm.md +19 -19
  12. package/claude/references/sd-simplysm14/manuals/test.md +33 -33
  13. package/claude/rules/sd-design-rules.md +18 -18
  14. package/claude/sd-system-prompt.md +369 -0
  15. package/claude/skills/sd-commit/SKILL.md +10 -10
  16. package/claude/skills/sd-config/SKILL.md +2 -2
  17. package/claude/skills/sd-demo/SKILL.md +45 -45
  18. package/claude/skills/sd-dev/SKILL.md +15 -15
  19. package/claude/skills/sd-docs/SKILL.md +7 -7
  20. package/claude/skills/sd-docs/references/subagent-prompt.md +33 -33
  21. package/claude/skills/sd-impl/SKILL.md +60 -60
  22. package/claude/skills/sd-review/SKILL.md +9 -9
  23. package/claude/skills/sd-skill/SKILL.md +74 -74
  24. package/claude/skills/sd-skill/evals/fixtures/existing-skill/.claude/skills/todo-format/SKILL.md +1 -1
  25. package/claude/skills/sd-spec/SKILL.md +354 -319
  26. package/claude/skills/sd-spec/references/example-spec.md +104 -104
  27. package/claude/skills/sd-unpack/SKILL.md +34 -34
  28. package/claude/skills/sd-unpack/scripts/handlers/__pycache__/office_com.cpython-314.pyc +0 -0
  29. package/claude/skills/sd-unpack/scripts/handlers/office_com.py +234 -159
  30. package/claude/skills/sd-use/SKILL.md +4 -4
  31. package/package.json +1 -1
  32. package/claude/references/sd-simplysm14/apis/angular/README.md +0 -37
  33. package/claude/references/sd-simplysm14/apis/angular/app-structure.md +0 -92
  34. package/claude/references/sd-simplysm14/apis/angular/buttons.md +0 -88
  35. package/claude/references/sd-simplysm14/apis/angular/crud.md +0 -100
  36. package/claude/references/sd-simplysm14/apis/angular/forms.md +0 -200
  37. package/claude/references/sd-simplysm14/apis/angular/infrastructure.md +0 -231
  38. package/claude/references/sd-simplysm14/apis/angular/kanban.md +0 -80
  39. package/claude/references/sd-simplysm14/apis/angular/layout.md +0 -92
  40. package/claude/references/sd-simplysm14/apis/angular/modal.md +0 -115
  41. package/claude/references/sd-simplysm14/apis/angular/routing.md +0 -107
  42. package/claude/references/sd-simplysm14/apis/angular/select-dropdown.md +0 -35
  43. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +0 -82
  44. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +0 -134
  45. package/claude/references/sd-simplysm14/apis/angular/sheet.md +0 -127
  46. package/claude/references/sd-simplysm14/apis/angular/toast.md +0 -97
  47. package/claude/references/sd-simplysm14/apis/angular/visual.md +0 -167
  48. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +0 -79
  49. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +0 -83
  50. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +0 -91
  51. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +0 -49
  52. package/claude/references/sd-simplysm14/apis/core-browser/README.md +0 -143
  53. package/claude/references/sd-simplysm14/apis/core-common/README.md +0 -58
  54. package/claude/references/sd-simplysm14/apis/core-common/extensions.md +0 -88
  55. package/claude/references/sd-simplysm14/apis/core-common/features.md +0 -51
  56. package/claude/references/sd-simplysm14/apis/core-common/types.md +0 -88
  57. package/claude/references/sd-simplysm14/apis/core-common/utils.md +0 -189
  58. package/claude/references/sd-simplysm14/apis/core-node/README.md +0 -12
  59. package/claude/references/sd-simplysm14/apis/core-node/consola.md +0 -59
  60. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +0 -44
  61. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +0 -42
  62. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +0 -53
  63. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +0 -24
  64. package/claude/references/sd-simplysm14/apis/core-node/worker.md +0 -65
  65. package/claude/references/sd-simplysm14/apis/excel/README.md +0 -193
  66. package/claude/references/sd-simplysm14/apis/lint/README.md +0 -94
  67. package/claude/references/sd-simplysm14/apis/orm-common/README.md +0 -58
  68. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +0 -77
  69. package/claude/references/sd-simplysm14/apis/orm-common/executable.md +0 -20
  70. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +0 -92
  71. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +0 -98
  72. package/claude/references/sd-simplysm14/apis/orm-common/schema-builders.md +0 -128
  73. package/claude/references/sd-simplysm14/apis/orm-node/README.md +0 -69
  74. package/claude/references/sd-simplysm14/apis/sd-claude/README.md +0 -32
  75. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +0 -80
  76. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config.md +0 -155
  77. package/claude/references/sd-simplysm14/apis/service-client/README.md +0 -131
  78. package/claude/references/sd-simplysm14/apis/service-common/README.md +0 -29
  79. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +0 -63
  80. package/claude/references/sd-simplysm14/apis/service-common/messages.md +0 -56
  81. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +0 -64
  82. package/claude/references/sd-simplysm14/apis/service-common/service-types.md +0 -43
  83. package/claude/references/sd-simplysm14/apis/service-server/README.md +0 -13
  84. package/claude/references/sd-simplysm14/apis/service-server/auth.md +0 -39
  85. package/claude/references/sd-simplysm14/apis/service-server/builtin-services.md +0 -71
  86. package/claude/references/sd-simplysm14/apis/service-server/define-service.md +0 -55
  87. package/claude/references/sd-simplysm14/apis/service-server/internals.md +0 -82
  88. package/claude/references/sd-simplysm14/apis/service-server/server.md +0 -57
  89. package/claude/references/sd-simplysm14/apis/storage/README.md +0 -71
  90. package/claude/rules/sd-base-rules.md +0 -306
@@ -1,131 +0,0 @@
1
- # @simplysm/service-client
2
-
3
- `@simplysm/service-server` 와 WebSocket(`/ws`) 으로 통신하는 클라이언트. RPC 호출·서버 push 이벤트 구독·파일 업/다운로드·원격 ORM 트랜잭션 실행을 단일 `ServiceClient` 에서 제공한다 (Node/브라우저 공용).
4
-
5
- ## 사용 트리거 인덱스
6
-
7
- - **createServiceClient / ServiceClient / ServiceConnectionOptions** — 서버 접속 클라이언트를 생성하고 `connect()` / `close()` 로 연결 수명을 관리할 때.
8
- - **getService / send / ServiceProxy** — 서버 등록 서비스의 메서드를 타입 안전 RPC 로 호출할 때 (Proxy 호출 또는 저수준 `send`).
9
- - **auth** — 서버 인증 토큰을 등록하고 재연결 시 자동 재인증 + 파일 업로드 인증을 활성화할 때.
10
- - **getEvent / addListener / removeListener / emitEvent / ClientEventProxy** — 서버 push 이벤트를 구독·해지하거나 다른 클라이언트에게 발행할 때.
11
- - **uploadFile / downloadFileBuffer** — 서버에 multipart 파일을 올리거나 정적 경로에서 바이트(`Uint8Array`)로 받을 때.
12
- - **createOrmClientConnector / OrmClientConnector / OrmConnectOptions** — 서버 `"Orm"` 서비스를 통해 `DbContext` 트랜잭션을 원격 실행할 때.
13
- - **OrmClientDbContextExecutor** — `DbContext` 를 직접 구성해야 할 때만. 보통 `OrmClientConnector` 가 내부에서 사용함.
14
- - **ServiceClient 인스턴스 이벤트 (`state` / `request-progress` / `response-progress` / `server-progress`)** — 연결 상태 변화와 전송/응답/서버 진행률을 인스턴스 레벨에서 구독할 때.
15
- - **BlobInput / FileCollection / BrowserWorker / isBrowserWorkerSupported / isNodeWorkerSupported / isWorkerSupported** — Node/브라우저 공용 코드에서 DOM 의존 타입을 대체하거나 Worker 오프로딩 가용성을 확인할 때.
16
-
17
- ## 연결 / 생성
18
-
19
- ```ts
20
- createServiceClient(name: string, options: ServiceConnectionOptions): ServiceClient
21
- interface ServiceConnectionOptions { port: number; host: string; ssl?: boolean; maxReconnectCount?: number; }
22
- client.connect(): Promise<void>
23
- client.close(): Promise<void> // protocol wrapper 의 worker·resolver 자원도 dispose
24
- client.connected: boolean
25
- client.hostUrl: string // `${ssl?https:http}://host:port`
26
- ```
27
-
28
- - WebSocket URL: `${ws|wss}://host:port/ws?ver=2&clientId=<uuid>&clientName=<name>`.
29
- - `maxReconnectCount` 기본 10. `0` 이면 재연결 비활성.
30
- - 5s 마다 ping(`0x01`), 30s 무응답 시 강제 재연결. 재연결 성공 시 인증 토큰과 이벤트 리스너가 자동 복구된다.
31
- - Node 환경에서 글로벌 `WebSocket` 이 없으면 `ws` 패키지로 polyfill (모듈 로드 시 1회).
32
-
33
- ## RPC 호출
34
-
35
- ```ts
36
- client.getService<TService>(serviceName: string): ServiceProxy<TService>
37
- client.send(serviceName, methodName, params: unknown[], progress?: ServiceProgress): Promise<unknown>
38
- type ServiceProxy<T> = { [K in keyof T]: T[K] extends (...a: infer P) => infer R ? (...a: P) => Promise<Awaited<R>> : never }
39
- interface ServiceProgress { request?(s: ServiceProgressState): void; response?(s): void; server?(s): void }
40
- interface ServiceProgressState { uuid: string; totalSize: number; completedSize: number }
41
- ```
42
-
43
- - `getService` 는 Proxy. 임의 메서드명 호출 가능하며 타입 보장은 `TService` 로만.
44
- - 와이어 메시지 이름은 `"<serviceName>.<methodName>"`.
45
- - 인코드/디코드의 Worker 오프로딩 임계값:
46
- - **인코드**: body 가 `Uint8Array`, 30KB 초과 문자열, 길이 100 초과 배열, 또는 첫 원소가 `Uint8Array` 인 배열일 때 Worker.
47
- - **디코드**: 수신 바이트 30KB 초과 시 Worker.
48
- - Worker 미지원/초기화 실패 환경은 메인 스레드 fallback.
49
- - 소켓 끊김·재연결 시 대기 중인 모든 요청은 `"요청 취소됨: 소켓 연결이 끊어졌습니다"` 로 reject.
50
-
51
- ## 인증
52
-
53
- ```ts
54
- client.auth(token: string): Promise<void>
55
- ```
56
-
57
- - 서버에 `auth` 메시지를 보내고 토큰을 내부 저장. 재연결 시 동일 토큰으로 자동 재인증.
58
- - `uploadFile` 은 토큰이 없으면 즉시 throw.
59
-
60
- ## 이벤트 (서버 push)
61
-
62
- ```ts
63
- client.getEvent<TEventDef>(eventName: string): ClientEventProxy<TEventDef>
64
- client.addListener<TEventDef>(eventName, info: TEventDef["$info"], cb): Promise<string> // listener key
65
- client.removeListener(key: string): Promise<void>
66
- client.emitEvent<TEventDef>(eventName, infoSelector: (info) => boolean, data): Promise<void>
67
- interface ClientEventProxy<T> { addListener(info, cb): Promise<string>; removeListener(key): Promise<void>; emit(infoSelector, data): Promise<void> }
68
- ```
69
-
70
- - `addListener` 는 `connected` 상태에서만 호출 가능 (그 외 throw). 로컬 맵에 보관해 재연결 시 자동 재구독.
71
- - `emitEvent` 는 서버에 `evt:gets` 로 후보 목록을 받아 `infoSelector` 통과분에만 `evt:emit` 호출. 대상 0건이면 emit 생략.
72
- - 핸들러 내부 에러는 로깅만, 다른 핸들러 호출은 계속.
73
- - `removeListener` 는 서버 전송 실패를 무시함 (서버가 연결 끊김 시 자동 정리하므로).
74
-
75
- ## 파일 업/다운로드
76
-
77
- ```ts
78
- client.uploadFile(files: File[] | FileCollection | { name: string; data: BlobInput }[]): Promise<ServiceUploadResult[]>
79
- client.downloadFileBuffer(relPath: string): Promise<Uint8Array>
80
- type BlobInput = Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | string
81
- interface FileCollection { readonly length; item(i); [i]; [Symbol.iterator]() } // 브라우저 FileList 와 구조적 호환
82
- ```
83
-
84
- - 업로드: `POST {hostUrl}/upload` (multipart). 헤더 `x-sd-client-name`, `Authorization: Bearer <token>`. 인증 필수.
85
- - 다운로드: `GET {hostUrl}/{relPath}` 응답 본문을 `Uint8Array` 로 반환. `relPath` 가 `/` 로 시작하지 않으면 자동 prepend.
86
- - 브라우저 전용 `File`/`FileList` 대신 `BlobInput`/`FileCollection` 으로 Node 환경도 지원.
87
-
88
- ## ORM 클라이언트
89
-
90
- ```ts
91
- createOrmClientConnector(client: ServiceClient): OrmClientConnector
92
- interface OrmConnectOptions<T extends DbContext> {
93
- DbClass: new (executor: DbContextExecutor, opt: { database: string; schema?: string }) => T;
94
- connOpt: DbConnOptions & { configName: string };
95
- dbContextOpt?: { database: string; schema: string }; // 지정 시 둘 다 필수
96
- }
97
- connector.connect(config, async db => ...) // 트랜잭션 래핑
98
- connector.connectWithoutTransaction(config, async db => ...)
99
- ```
100
-
101
- - 서버 `"Orm"` 서비스에 RPC 를 걸어 `getInfo` → `connect` → `executeDefs` / `executeParametrized` / `bulkInsert` / `begin·commit·rollbackTransaction` / `close` 를 위임.
102
- - `dbContextOpt` 생략 시 서버가 알려준 `database` / `schema` 사용. 최종 `database` 가 빈 값이면 throw.
103
- - 외래키 참조 위반 메시지(`a parent row: a foreign key constraint` / `conflicted with the REFERENCE`)는 "경고! 연관된 작업으로 인해 작업이 거부되었습니다. 후속 작업을 확인해 주세요." 로 변환되어 throw (원본은 `cause`).
104
- - `OrmClientDbContextExecutor` 는 `DbContextExecutor` 구현체. 보통 connector 가 내부 생성하며 직접 인스턴스화는 커스텀 `DbContext` 구성 시에만.
105
-
106
- ## 인스턴스 이벤트 (상태/진행률)
107
-
108
- ```ts
109
- client.on("state", (s: "connected" | "closed" | "reconnecting") => ...)
110
- client.on("request-progress", (s: ServiceProgressState) => ...) // 클라 → 서버 전송 청크 진행
111
- client.on("response-progress", (s: ServiceProgressState) => ...) // 서버 → 클라 응답 청크 진행
112
- client.on("server-progress", (s: ServiceProgressState) => ...) // 서버가 명시적으로 보내는 진행률
113
- ```
114
-
115
- - `request-progress` 는 인코드 결과 chunk 가 2개 이상일 때만 0% 초기값 발행. 단일 청크면 발행 없음.
116
- - `response-progress` 는 분할 수신 중에는 서버 progress 메시지, 응답 완료 시 100% 보정값으로 발행.
117
- - `send` 호출에 전달한 `progress` 콜백은 인스턴스 이벤트와 함께 호출됨.
118
-
119
- ## 환경 호환 유틸
120
-
121
- ```ts
122
- import { BlobInput, FileCollection, BrowserWorker,
123
- isBrowserWorkerSupported, isNodeWorkerSupported, isWorkerSupported }
124
- from "@simplysm/service-client";
125
- ```
126
-
127
- - `BrowserWorker` — DOM `Worker` 의 최소 구조 호환 인터페이스. Node 환경 typecheck 통과용.
128
- - `isBrowserWorkerSupported()` — `"Worker" in globalThis`.
129
- - `isNodeWorkerSupported()` — `process.versions.node` 존재 여부.
130
- - `isWorkerSupported()` — 위 둘 중 하나.
131
- - 보통 직접 호출할 필요 없음 (`ServiceClient` 내부에서 분기). 환경 분기 로직을 사용자 코드에서 직접 짤 때만 사용.
@@ -1,29 +0,0 @@
1
- # @simplysm/service-common
2
-
3
- 서버/클라이언트가 공유하는 서비스 프로토콜·메시지 타입·서비스 인터페이스·앱 구조 정의·이벤트 정의 유틸.
4
-
5
- ## 사용 트리거 인덱스
6
-
7
- - **`createServiceProtocol` / `ServiceProtocol` / `ServiceMessageDecodeResult` / `PROTOCOL_CONFIG`** — 서버/클라이언트 transport 의 바이너리 V2 프로토콜(헤더 28B + JSON, 3MB↑ 자동 청킹, 100MB 한계) 인/디코더 생성. 자세히: [protocol.md](./protocol.md)
8
- - **메시지 타입 (`ServiceMessage` / `ServiceClientMessage` / `ServiceServerMessage` / `ServiceServerRawMessage` 및 9 종의 개별 메시지)** — 프로토콜 위에 실리는 메시지 식별·타입 가드·핸들러 분기. 자세히: [messages.md](./messages.md)
9
- - **서비스 인터페이스 (`OrmService` / `AutoUpdateService` / `AppStructureService` / `DbConnOptions`)** — 서버 구현·클라이언트 호출 양쪽이 공유하는 빌트인 서비스의 시그니처. 자세히: [service-types.md](./service-types.md)
10
- - **앱 구조 (`AppStructureItem` / `AppStructureGroupItem` / `AppStructureLeafItem` / `AppStructureSubPermission` / `FlatPermission` + `isUsableModules` / `isUsableModulesChain` / `getFlatPermissions`)** — 메뉴/권한 트리 정의와 모듈 기반 가용성 평가·평탄화. 자세히: [app-structure.md](./app-structure.md)
11
- - **`defineEvent` / `ServiceEventDef`** — 서버 발신·클라이언트 구독에서 `info`/`data` 타입을 공유하기 위한 이벤트 정의.
12
- - **`ServiceUploadResult`** — 파일 업로드 결과(저장 경로·원본명·바이트 크기).
13
-
14
- ## defineEvent / ServiceEventDef
15
-
16
- ```ts
17
- function defineEvent<TInfo, TData>(eventName: string): ServiceEventDef<TInfo, TData>
18
- interface ServiceEventDef<TInfo, TData> { eventName: string; readonly $info: TInfo; readonly $data: TData; }
19
- ```
20
-
21
- `$info`/`$data` 는 런타임 미사용, 타입 추출 전용 마커. 서버는 `defineEvent` 로 정의·export 하고, 클라이언트는 `import type` 으로 가져와 `addListener<typeof Evt>(...)` / `emitEvent<typeof Evt>(...)` 에 전달해 `info` 필터·`data` 페이로드를 정적으로 검증한다.
22
-
23
- ## ServiceUploadResult
24
-
25
- ```ts
26
- interface ServiceUploadResult { path: string; filename: string; size: number; }
27
- ```
28
-
29
- 서버 업로드 핸들러가 클라이언트로 반환하는 결과. `path` 는 서버 내부 저장 경로다.
@@ -1,63 +0,0 @@
1
- # @simplysm/service-common — app-structure
2
-
3
- 메뉴/권한 트리 정의 타입과 모듈(라이선스/플랜 등) 기반 가용성 평가·평탄화 함수.
4
-
5
- ## 타입
6
-
7
- ```ts
8
- type AppStructureItem<TModule = unknown> =
9
- | AppStructureGroupItem<TModule>
10
- | AppStructureLeafItem<TModule>;
11
-
12
- interface AppStructureGroupItem<TModule> {
13
- code: string; title: string;
14
- modules?: TModule[]; requiredModules?: TModule[];
15
- icon?: string;
16
- children: AppStructureItem<TModule>[];
17
- }
18
-
19
- interface AppStructureLeafItem<TModule> {
20
- code: string; title: string;
21
- modules?: TModule[]; requiredModules?: TModule[];
22
- perms?: ("use" | "edit")[];
23
- subPerms?: AppStructureSubPermission<TModule>[];
24
- icon?: string; url?: string;
25
- isNotMenu?: boolean;
26
- }
27
-
28
- interface AppStructureSubPermission<TModule> {
29
- code: string; title: string;
30
- modules?: TModule[]; requiredModules?: TModule[];
31
- perms: ("use" | "edit")[];
32
- }
33
-
34
- interface FlatPermission<TModule = unknown> {
35
- titleChain: string[];
36
- codeChain: string[]; // [...상위 code, perm] 또는 [...상위 code, subPerm.code, perm]
37
- modulesChain: TModule[][];
38
- }
39
- ```
40
-
41
- Group/Leaf 는 `children` 존재로 구분(`"children" in item`).
42
-
43
- ## isUsableModules(modules, requiredModules, usableModules) → boolean
44
-
45
- - `requiredModules` (AND): 모두 `usableModules` 에 포함되어야 함. 없거나 빈 배열이면 통과.
46
- - `modules` (OR): 비었거나 그 중 하나라도 `usableModules` 에 있으면 통과.
47
-
48
- ## isUsableModulesChain(modulesChain, requiredModulesChain, usableModules) → boolean
49
-
50
- 체인 각 레벨에 대해 `modules` OR 와 `requiredModules` AND 모두 만족해야 통과. 트리 깊이별 누적 조건 평가용.
51
-
52
- ## getFlatPermissions(items, usableModules) → FlatPermission[]
53
-
54
- 트리를 BFS 로 순회하며 각 leaf 의 `perms` / `subPerms` 를 평탄화한다.
55
-
56
- - 진행 중 노드 단위로 `isUsableModulesChain` 체크 — 실패 시 하위 폐기.
57
- - `subPerms` 도 자체 `modules`/`requiredModules` 로 추가 필터.
58
- - 결과 `codeChain` 마지막에 권한값(`"use"|"edit"`)이 붙는다.
59
-
60
- ```ts
61
- const perms = getFlatPermissions(items, ["BASIC", "PRO"]);
62
- // [{ titleChain: ["주문","주문등록"], codeChain: ["order","register","use"], modulesChain: [["PRO"]] }, ...]
63
- ```
@@ -1,56 +0,0 @@
1
- # @simplysm/service-common — messages
2
-
3
- `ServiceProtocol` 위에 실리는 메시지 식별자(`name`) 별 바디 스키마. 모든 인터페이스는 `name` discriminator 로 좁힌다.
4
-
5
- ## 유니언
6
-
7
- ```ts
8
- type ServiceMessage =
9
- | ServiceRequestMessage | ServiceAuthMessage
10
- | ServiceProgressMessage | ServiceResponseMessage | ServiceErrorMessage
11
- | ServiceAddEventListenerMessage | ServiceRemoveEventListenerMessage
12
- | ServiceGetEventListenerInfosMessage
13
- | ServiceEmitEventMessage | ServiceEventMessage;
14
-
15
- type ServiceClientMessage = // 클라이언트 → 서버
16
- | ServiceRequestMessage | ServiceAuthMessage
17
- | ServiceAddEventListenerMessage | ServiceRemoveEventListenerMessage
18
- | ServiceGetEventListenerInfosMessage | ServiceEmitEventMessage;
19
-
20
- type ServiceServerMessage = // 서버 → 클라이언트 (디스패치 대상)
21
- | ServiceResponseMessage | ServiceErrorMessage | ServiceEventMessage;
22
-
23
- type ServiceServerRawMessage = // 서버 → 클라이언트 (progress 포함 전체)
24
- | ServiceProgressMessage | ServiceServerMessage;
25
- ```
26
-
27
- ## 시스템
28
-
29
- - `ServiceProgressMessage` — `name: "progress"`, `body: { totalSize, completedSize }`. 서버가 청크 수신 진행률 알림.
30
- - `ServiceErrorMessage` — `name: "error"`, `body: { name, message, code, stack?, detail?, cause? }`. 처리 중 에러 알림.
31
- - `ServiceAuthMessage` — `name: "auth"`, `body: string` (토큰). 클라이언트 인증.
32
-
33
- ## 서비스 메서드
34
-
35
- - `ServiceRequestMessage` — `` name: `${string}.${string}` `` (예: `"User.findOne"`), `body: unknown[]` (매개변수 배열).
36
- - `ServiceResponseMessage` — `name: "response"`, `body?: unknown` (반환값).
37
-
38
- ## 이벤트
39
-
40
- - `ServiceAddEventListenerMessage` — `name: "evt:add"`, `body: { key, name, info }`. `key` 는 uuid (remove 키).
41
- - `ServiceRemoveEventListenerMessage` — `name: "evt:remove"`, `body: { key }`.
42
- - `ServiceGetEventListenerInfosMessage` — `name: "evt:gets"`, `body: { name }`. 동일 이벤트 구독자들의 `info` 목록 요청.
43
- - `ServiceEmitEventMessage` — `name: "evt:emit"`, `body: { keys, data }`. 클라이언트 발생.
44
- - `ServiceEventMessage` — `name: "evt:on"`, `body: { keys, data }`. 서버 알림.
45
-
46
- ## 사용
47
-
48
- ```ts
49
- function dispatch(msg: ServiceServerMessage) {
50
- switch (msg.name) {
51
- case "response": return resolveCall(msg.body);
52
- case "error": return rejectCall(msg.body);
53
- case "evt:on": return notifyListeners(msg.body.keys, msg.body.data);
54
- }
55
- }
56
- ```
@@ -1,64 +0,0 @@
1
- # @simplysm/service-common — protocol
2
-
3
- 서비스 transport(웹소켓 등) 위에 얹는 바이너리 메시지 프로토콜(V2). 메시지를 청크로 자르고/재조립하며, 청크 누적의 GC 까지 캡슐화한다.
4
-
5
- ## PROTOCOL_CONFIG
6
-
7
- ```ts
8
- const PROTOCOL_CONFIG = {
9
- MAX_TOTAL_SIZE: 100 * 1024 * 1024, // 100MB 한계 (인/디코딩 양쪽에서 검사)
10
- SPLIT_MESSAGE_SIZE: 3 * 1024 * 1024, // 이 크기 초과 시 청크 분할
11
- CHUNK_SIZE: 300 * 1024, // 청크 본문 크기
12
- GC_INTERVAL: 10 * 1000, // 청크 누적기 GC 주기
13
- EXPIRE_TIME: 60 * 1000, // 미완성 메시지 만료
14
- } as const;
15
- ```
16
-
17
- `as const`. 임계값 조정 필요 시 이 상수를 참조한다.
18
-
19
- ## ServiceProtocol
20
-
21
- ```ts
22
- interface ServiceProtocol {
23
- encode(uuid: string, message: ServiceMessage): { chunks: Bytes[]; totalSize: number };
24
- decode<T extends ServiceMessage>(bytes: Bytes): ServiceMessageDecodeResult<T>;
25
- dispose(): void;
26
- }
27
- ```
28
-
29
- - 청크 헤더 28B: UUID 16B + TotalSize 8B(uint64, 상위 4B 는 0) + Index 4B(uint32), Big Endian.
30
- - 본문: `json.stringify([name, body?])` UTF-8.
31
- - `dispose()` 는 내부 `LazyGcMap` 의 GC 타이머를 해제. 인스턴스 종료 시 반드시 호출.
32
-
33
- ## ServiceMessageDecodeResult
34
-
35
- ```ts
36
- type ServiceMessageDecodeResult<T extends ServiceMessage> =
37
- | { type: "complete"; uuid: string; message: T }
38
- | { type: "progress"; uuid: string; totalSize: number; completedSize: number };
39
- ```
40
-
41
- - `complete`: 모든 청크 수신·재조립 완료. 메시지 디스패치 가능.
42
- - `progress`: 청크 일부만 도착. 진행률 표시·`progress` 알림 송신에 사용.
43
-
44
- ## createServiceProtocol()
45
-
46
- `ServiceProtocol` 인스턴스를 생성한다. 서버·클라이언트 각자 1 개씩 보유하는 것이 일반적.
47
-
48
- ```ts
49
- const proto = createServiceProtocol();
50
- const { chunks } = proto.encode(uuid, { name: "User.findOne", body: [{ id: 1 }] });
51
- for (const c of chunks) socket.send(c);
52
-
53
- const res = proto.decode<ServiceServerMessage>(receivedBytes);
54
- if (res.type === "complete") handle(res.message);
55
- ```
56
-
57
- 예외:
58
-
59
- - `MAX_TOTAL_SIZE` 초과: `ArgumentError("메시지 크기가 제한을 초과했습니다.")`.
60
- - 헤더(<28B) 미달: `ArgumentError("버퍼 크기가 헤더 크기보다 작습니다.")`.
61
- - 본문 JSON 파싱 실패: `ArgumentError("메시지 디코딩에 실패했습니다.", { uuid, cause })`.
62
- - 무결성 위반(누적 크기 > totalSize): `ArgumentError("프로토콜 무결성 위반: ...")`.
63
-
64
- 중복 청크는 인덱스가 이미 채워진 경우 무시(방어). 만료(`EXPIRE_TIME`) 시 누적 항목은 GC 가 폐기.
@@ -1,43 +0,0 @@
1
- # @simplysm/service-common — service-types
2
-
3
- 서버 구현·클라이언트 호출이 공유하는 빌트인 서비스 인터페이스. 서버는 인터페이스를 구현, 클라이언트는 동일 시그니처로 RPC 호출한다.
4
-
5
- ## OrmService
6
-
7
- ```ts
8
- interface OrmService {
9
- getInfo(opt: DbConnOptions & { configName: string }): Promise<{ dialect: Dialect; database?: string; schema?: string }>;
10
- connect(opt: DbConnOptions & { configName: string }): Promise<number>; // connId
11
- close(connId: number): Promise<void>;
12
- beginTransaction(connId: number, isolationLevel?: IsolationLevel): Promise<void>;
13
- commitTransaction(connId: number): Promise<void>;
14
- rollbackTransaction(connId: number): Promise<void>;
15
- executeParametrized(connId: number, query: string, params?: unknown[]): Promise<unknown[][]>;
16
- executeDefs(connId: number, defs: QueryDef[], options?: (ResultMeta | undefined)[]): Promise<unknown[][]>;
17
- bulkInsert(connId: number, tableName: string, columnDefs: Record<string, ColumnMeta>, records: Record<string, unknown>[]): Promise<void>;
18
- }
19
-
20
- type DbConnOptions = { configName?: string; config?: Record<string, unknown> };
21
- ```
22
-
23
- MySQL / MSSQL / PostgreSQL 공통 추상. `connId` 단위로 트랜잭션·세션 식별. `Dialect`·`IsolationLevel`·`QueryDef`·`ColumnMeta`·`ResultMeta` 는 `@simplysm/orm-common` 정의.
24
-
25
- ## AutoUpdateService
26
-
27
- ```ts
28
- interface AutoUpdateService {
29
- getLastVersion(platform: string): Promise<{ version: string; downloadPath: string } | undefined>;
30
- }
31
- ```
32
-
33
- `platform` 예: `"win32" | "darwin" | "linux"`. 등록된 버전 없으면 `undefined`.
34
-
35
- ## AppStructureService
36
-
37
- ```ts
38
- interface AppStructureService {
39
- getItems(): Record<string, AppStructureItem[]>;
40
- }
41
- ```
42
-
43
- 키는 클라이언트명, 값은 해당 클라이언트의 앱 구조 트리. `AppStructureItem` 은 [app-structure.md](./app-structure.md) 참조.
@@ -1,13 +0,0 @@
1
- # @simplysm/service-server
2
-
3
- Fastify + WebSocket 기반 서비스 서버. 클라이언트(`@simplysm/service-client`)와 짝으로 RPC·이벤트 푸시·파일 업로드·정적 서빙·JWT 인증을 단일 포트로 제공한다.
4
-
5
- ## 사용 트리거 인덱스
6
-
7
- | 트리거 | 자료 |
8
- | --- | --- |
9
- | 서버 인스턴스 생성·기동·종료, 이벤트 emit, JWT 토큰 발급/검증, `ServiceServerOptions`(`rootPath`/`port`/`ssl`/`auth`/`services`/`legacyV1Handlers`) | [server.md](./server.md) |
10
- | 사용자 서비스 정의(`defineService`), `ServiceContext`, `ServiceMethods` 타입 추출, `createServiceContext`, `getServiceAuthPermissions` | [define-service.md](./define-service.md) |
11
- | 인증 래퍼 `auth()` (서비스/메서드 레벨, 역할 제한), `AuthTokenPayload`, `signJwt`/`verifyJwt`/`decodeJwt` | [auth.md](./auth.md) |
12
- | 빌트인 서비스: `OrmService`, `AutoUpdateService`, `AppStructureService`, V1 레거시 자동 업데이트 핸들러 | [builtin-services.md](./builtin-services.md) |
13
- | 내부 전송 계층(`/api/:service/:method`·`/upload`·정적 서빙, WebSocket 핸들러, 프로토콜 래퍼/워커, `ServiceSocket`, `getConfig`) — 직접 사용 시 | [internals.md](./internals.md) |
@@ -1,39 +0,0 @@
1
- # @simplysm/service-server — 인증
2
-
3
- ## `auth(fn)` / `auth(permissions, fn)`
4
-
5
- 서비스 factory 또는 개별 메서드를 래핑해 인증 요구사항을 부착. 래핑 함수는 호출 동작을 그대로 유지하며 내부 심볼에 `permissions: string[]` 저장.
6
-
7
- - 서비스 수준: `defineService("X", auth((ctx) => ({ ... })))` — 모든 메서드 로그인 필요.
8
- - 서비스 수준 + 역할: `defineService("X", auth(["admin"], (ctx) => ({ ... })))`.
9
- - 메서드 수준: factory 내부에서 `someMethod: auth(() => result)`.
10
- - 메서드 수준 + 역할: `someMethod: auth(["admin", "owner"], () => result)` — 배열 중 하나라도 매칭하면 통과(OR).
11
-
12
- 실행 시(`executeServiceMethod`) 동작:
13
-
14
- - 메서드 권한이 있으면 서비스 권한을 **덮어쓴다**. 메서드 권한 미부착 시 서비스 권한 사용.
15
- - 권한 요구 + `options.auth == null` → "auth 설정이 필요합니다" throw(서버 설정 오류).
16
- - 권한 요구 + `options.auth === false` → 인증 스킵.
17
- - 권한 요구 + `options.auth = { jwtSecret }` → payload 없으면 "로그인이 필요합니다" throw. `permissions.length > 0` 이면 `payload.roles` 중 하나라도 일치해야 통과, 아니면 "권한이 부족합니다" throw. `permissions.length === 0` (= `auth(fn)`) 이면 토큰만 있으면 통과.
18
-
19
- ## `AuthTokenPayload<TAuthInfo>`
20
-
21
- `jose.JWTPayload` 확장.
22
-
23
- - `roles: string[]` — `auth(["role"], fn)` 검사 대상.
24
- - `data: TAuthInfo` — 임의 사용자 데이터. `ctx.authInfo` 로 노출.
25
-
26
- ## JWT 헬퍼
27
-
28
- `server.signAuthToken`/`verifyAuthToken` 이 동일 알고리즘으로 감싸므로 일반 시나리오에선 그쪽을 쓴다. 직접 import 는 서버 인스턴스 없이 토큰을 다루는 경우(테스트, 별도 인증 서버, 디코드 등)만.
29
-
30
- - `signJwt<T>(jwtSecret, payload): Promise<string>` — HS256, `iat` 자동, `exp = 12h` 고정. 수명 변경 필요 시 `jose.SignJWT` 직접 사용.
31
- - `verifyJwt<T>(jwtSecret, token): Promise<AuthTokenPayload<T>>` — 만료: "토큰이 만료되었습니다", 그 외 위조: "유효하지 않은 토큰입니다" throw.
32
- - `decodeJwt<T>(token): AuthTokenPayload<T>` — 서명 검증 없이 페이로드만 디코드. 클라이언트 측 만료 표시 등 비보안 용도에만.
33
-
34
- ## 예
35
-
36
- ```ts
37
- const token = await signJwt(secret, { roles: ["admin"], data: { userId: "U1" } });
38
- const payload = await verifyJwt<{ userId: string }>(secret, token);
39
- ```
@@ -1,71 +0,0 @@
1
- # @simplysm/service-server — 빌트인 서비스
2
-
3
- `options.services` 에 추가해 사용. 각 `*ServiceType` 은 클라이언트의 `client.getService<*ServiceType>("<이름>")` 에 그대로 사용.
4
-
5
- ## `OrmService`
6
-
7
- DB 연결을 WebSocket 세션과 묶어 원격 ORM 사용.
8
-
9
- - 별칭: `["Orm", "SdOrmService"]`.
10
- - **WebSocket 전용**. HTTP 호출 시 "WebSocket 연결이 필요합니다…" throw(트랜잭션을 소켓 라이프타임에 묶기 위함).
11
- - `auth()` 래핑이라 로그인 필수. 역할 제한은 없음.
12
- - 설정 소스: `ctx.getConfig<Record<string, DbConnConfig>>("orm")` → `opt.configName` 키 조회. 호출 시 `opt.config` 가 base 설정을 부분 override.
13
- - 연결 ID 는 소켓별 카운터(1부터, 가장 큰 값+1). 소켓 close 시 해당 소켓의 모든 열린 DB 연결 자동 정리(에러는 warn 후 무시).
14
- - `dialect === "mssql-azure"` 는 외부 노출 시 `"mssql"` 로 정규화.
15
-
16
- 메서드:
17
-
18
- - `getInfo(opt)` → `{ dialect, database?, schema? }` (연결 안 만들고 설정 조회만).
19
- - `connect(opt): Promise<number>` — connId 반환.
20
- - `close(connId)`.
21
- - `beginTransaction(connId, isolationLevel?)`, `commitTransaction(connId)`, `rollbackTransaction(connId)`.
22
- - `executeParametrized(connId, query, params?)` — `unknown[][]` (멀티 결과셋).
23
- - `executeDefs(connId, defs: QueryDef[], options?: (ResultMeta | undefined)[])` — `options` 가 전부 null/undefined 면 단일 multi-SQL 실행 후 빈 배열들, 아니면 def 별로 build → execute → `pickResultSets` → opt 있으면 `parseQueryResult`, 없으면 raw.
24
- - `bulkInsert(connId, tableName, columnDefs, records)`.
25
-
26
- `opt: DbConnOptions & { configName: string }`. 타입 export: `OrmServiceType`.
27
-
28
- ## `AutoUpdateService`
29
-
30
- - 별칭: `["AutoUpdate", "SdAutoUpdateService"]`. 인증 없음.
31
- - `getLastVersion(platform: string): Promise<{ version, downloadPath } | undefined>` — `<clientPath>/<platform>/updates/` 에서 `platform === "android"` → `.apk`, 그 외 → `.exe` 중 파일명이 `^[0-9.]*$` 인 것만 후보. `semver.maxSatisfying(versions, "*")` 로 최신 선정. clientName 미설정 시 throw, 디렉터리/매칭 파일 없으면 undefined.
32
- - `downloadPath` = `/<clientName>/<platform>/updates/<fileName>` (POSIX 정규화).
33
- - 타입 export: `AutoUpdateServiceType`.
34
-
35
- ## `AppStructureService(itemsMap)`
36
-
37
- 다른 두 서비스와 달리 **팩토리 함수**(서비스 정의가 아님). 호출 결과를 `services` 에 등록.
38
-
39
- - 시그니처: `AppStructureService(itemsMap: Record<string, AppStructureItem[]>): ServiceDefinition`
40
- - 서비스 이름: `"AppStructure"`. 인증 없음.
41
- - 메서드: `getItems(): Record<string, AppStructureItem[]>` — 주입된 map 그대로 반환.
42
- - 타입 export: `AppStructureServiceType`.
43
-
44
- `AppStructureItem` 은 `@simplysm/service-common` 정의(메뉴/라우트 트리 항목).
45
-
46
- ```ts
47
- services: [AppStructureService({ admin: [...], user: [...] })];
48
- ```
49
-
50
- ## V1 레거시 자동 업데이트
51
-
52
- ver≠"2" 로 접속한 구버전 클라이언트 호환. JSON 라인 프로토콜.
53
-
54
- ### `handleV1Connection(socket, autoUpdateMethods, clientNameSetter?)` / `handleV1Connection(socket, options)`
55
-
56
- `ServiceServer` 가 자동 호출. `services` 에 `AutoUpdate` 가 있으면 그 factory 의 `getLastVersion` 을 V1 메서드로 자동 매핑. `legacyV1Handlers` 도 그대로 전달. 둘 다 없으면 1008 거부.
57
-
58
- `V1ConnectionOptions`:
59
-
60
- - `serviceContext?` 또는 `serviceContextFactory?: (req: V1Request) => ServiceContext` — 핸들러용 컨텍스트(둘 중 하나).
61
- - `handlers?: V1RequestHandler[]` — 우선 실행. 각 핸들러는 `{ handled: true, state?: "success"|"error", body } | { handled: false }` 반환. handled=true 면 즉시 응답.
62
- - `autoUpdateMethods?` 또는 `autoUpdateMethodsFactory?: (ctx) => V1AutoUpdateMethods` — 모든 사용자 핸들러가 미처리 + `command === "SdAutoUpdateService.getLastVersion"` 일 때 fallback.
63
- - `clientNameSetter?: (clientName: string | undefined) => void` — 매 요청의 `clientName` 알림 콜백.
64
- - 어디서도 처리 안 되면 `{ code: "UPGRADE_REQUIRED", message: "앱 업그레이드가 필요합니다." }` 응답.
65
-
66
- 타입:
67
-
68
- - `V1Request = { uuid, command, params: unknown[], clientName? }`
69
- - `V1Response = { name: "response", reqUuid, state: "success" | "error", body }`
70
- - `V1AutoUpdateMethods = { getLastVersion(platform): Promise<unknown> | unknown }`
71
- - `V1RequestHandler`, `V1RequestHandlerContext`, `V1RequestHandlerResult`.
@@ -1,55 +0,0 @@
1
- # @simplysm/service-server — 서비스 정의
2
-
3
- ## `defineService(name, factory): ServiceDefinition<TMethods>`
4
-
5
- - `name: string | string[]` — 단일 또는 별칭 목록. 첫 요소가 primary(`def.name`), 전체가 `def.names`. RPC 라우팅은 `names.includes(요청서비스명)` 매칭. 빈 배열이면 throw.
6
- - `factory: (ctx: ServiceContext) => TMethods` — 매 요청마다 호출되어 메서드 객체 생성(컨텍스트 캡처). factory 자체가 `auth(...)` 래핑이면 `authPermissions` 가 자동 추출돼 서비스 수준 인증으로 승격.
7
-
8
- 반환 `ServiceDefinition<TMethods>`:
9
-
10
- - `name: string`
11
- - `names: string[]`
12
- - `factory`
13
- - `authPermissions?: string[]`
14
-
15
- ## `ServiceContext<TAuthInfo>`
16
-
17
- 서비스 factory 가 받는 요청별 컨텍스트.
18
-
19
- - `server: ServiceServer<TAuthInfo>`
20
- - `socket?: ServiceSocket` — WebSocket 경로일 때만.
21
- - `http?: { clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> }` — HTTP 경로.
22
- - `legacy?: { clientName?: string }` — V1 경로.
23
- - `get authInfo(): TAuthInfo | undefined` — socket 의 payload.data 우선, 없으면 http.
24
- - `get clientName(): string | undefined` — socket → http → legacy 순. 빈 문자/`..`/`/`/`\\` 포함 시 throw.
25
- - `get clientPath(): string | undefined` — `<rootPath>/www/<clientName>`. clientName 없으면 undefined.
26
- - `getConfig<T>(section: string): Promise<T>` — `<rootPath>/.config.json` + `<clientPath>/.config.json` 을 `obj.merge` 한 뒤 `section` 키 반환. 섹션 없으면 throw. 설정 파일은 `FsWatcher` 로 변경 감시되어 자동 리로드.
27
-
28
- ## `ServiceMethods<TDefinition>` 타입
29
-
30
- `ServiceDefinition<M>` 에서 `M` 추출. 클라이언트의 `client.getService<MyServiceType>("MyService")` 에 사용.
31
-
32
- ```ts
33
- export const UserService = defineService("User", (ctx) => ({
34
- getProfile: () => ({ name: "kim" }),
35
- }));
36
- export type UserServiceType = ServiceMethods<typeof UserService>;
37
- ```
38
-
39
- ## 보조 export
40
-
41
- - `createServiceContext(server, socket?, http?, legacy?): ServiceContext` — 컨텍스트 직조. 커스텀 라우트/테스트용.
42
- - `getServiceAuthPermissions(fn): string[] | undefined` — `auth()` 가 함수에 심어둔 권한 배열 추출. 래핑 안 됐으면 undefined. 내부 executor 가 사용.
43
-
44
- ## 예
45
-
46
- ```ts
47
- const HealthService = defineService("Health", () => ({
48
- check: () => ({ status: "ok" }),
49
- }));
50
-
51
- const UserService = defineService(["User", "MyUser"], auth((ctx) => ({
52
- me: () => ctx.authInfo,
53
- adminOnly: auth(["admin"], () => "ok"),
54
- })));
55
- ```