@simplysm/sd-claude 14.0.89 → 14.0.90

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 (79) hide show
  1. package/claude/references/sd-simplysm14/README.md +16 -17
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +52 -30
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +200 -38
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +41 -53
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +66 -22
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +127 -40
  7. package/claude/references/sd-simplysm14/apis/angular/infra.md +60 -43
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +56 -20
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +74 -74
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +50 -40
  11. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +55 -15
  12. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +59 -42
  13. package/claude/references/sd-simplysm14/apis/angular/sheet.md +77 -62
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +8 -7
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +71 -43
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +22 -14
  17. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +19 -19
  18. package/claude/references/sd-simplysm14/apis/core-browser/README.md +17 -17
  19. package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +28 -28
  20. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +37 -37
  21. package/claude/references/sd-simplysm14/apis/core-common/README.md +87 -219
  22. package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +54 -98
  23. package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +57 -99
  24. package/claude/references/sd-simplysm14/apis/core-common/datetime.md +60 -103
  25. package/claude/references/sd-simplysm14/apis/core-common/errors.md +42 -47
  26. package/claude/references/sd-simplysm14/apis/core-common/obj.md +42 -88
  27. package/claude/references/sd-simplysm14/apis/core-common/serialization.md +55 -0
  28. package/claude/references/sd-simplysm14/apis/core-node/README.md +6 -7
  29. package/claude/references/sd-simplysm14/apis/core-node/consola.md +17 -12
  30. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +14 -13
  31. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +9 -8
  32. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +14 -13
  33. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +4 -8
  34. package/claude/references/sd-simplysm14/apis/core-node/worker.md +14 -12
  35. package/claude/references/sd-simplysm14/apis/excel/README.md +22 -22
  36. package/claude/references/sd-simplysm14/apis/excel/cell.md +37 -29
  37. package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +29 -15
  38. package/claude/references/sd-simplysm14/apis/excel/style.md +33 -27
  39. package/claude/references/sd-simplysm14/apis/excel/utils.md +29 -19
  40. package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +78 -55
  41. package/claude/references/sd-simplysm14/apis/excel/wrapper.md +42 -45
  42. package/claude/references/sd-simplysm14/apis/orm-common/README.md +6 -8
  43. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +118 -67
  44. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +83 -86
  45. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +102 -93
  46. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +138 -81
  47. package/claude/references/sd-simplysm14/apis/orm-common/types.md +49 -44
  48. package/claude/references/sd-simplysm14/apis/orm-node/README.md +42 -42
  49. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +44 -33
  50. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +11 -10
  51. package/claude/references/sd-simplysm14/apis/service-client/README.md +56 -52
  52. package/claude/references/sd-simplysm14/apis/service-client/orm.md +33 -28
  53. package/claude/references/sd-simplysm14/apis/service-client/transport.md +23 -21
  54. package/claude/references/sd-simplysm14/apis/service-common/README.md +83 -48
  55. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +126 -34
  56. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +109 -54
  57. package/claude/references/sd-simplysm14/apis/service-server/README.md +69 -81
  58. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +46 -43
  59. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +63 -37
  60. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +40 -30
  61. package/claude/references/sd-simplysm14/apis/storage/README.md +17 -17
  62. package/claude/references/sd-simplysm14/manuals/client-app-structure.md +142 -140
  63. package/claude/references/sd-simplysm14/manuals/client-orm.md +1 -1
  64. package/claude/references/sd-simplysm14/manuals/client-service.md +19 -7
  65. package/claude/references/sd-simplysm14/manuals/client-shared-data.md +2 -2
  66. package/claude/references/sd-simplysm14/manuals/client-system-log.md +11 -3
  67. package/claude/references/sd-simplysm14/manuals/data-log.md +0 -1
  68. package/claude/references/sd-simplysm14/manuals/orm.md +16 -0
  69. package/claude/rules/sd-design-rules.md +10 -0
  70. package/claude/skills/sd-demo/SKILL.md +0 -6
  71. package/claude/skills/sd-docs/SKILL.md +58 -0
  72. package/claude/{workflows/sd-docs.rules.md → skills/sd-docs/references/subagent-prompt.md} +103 -103
  73. package/claude/skills/sd-impl/SKILL.md +7 -4
  74. package/claude/skills/sd-spec/SKILL.md +842 -15
  75. package/claude/skills/sd-spec/references/example-spec.md +26 -36
  76. package/package.json +1 -1
  77. package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +0 -53
  78. package/claude/skills/sd-spec/references/spec-authoring.md +0 -519
  79. package/claude/workflows/sd-docs.js +0 -84
@@ -1,84 +1,119 @@
1
1
  # @simplysm/service-common
2
2
 
3
- 서버·클라이언트가 공유하는 서비스 통신 계약. 바이너리 프로토콜(인코딩/청킹/재조립)과 메시지 타입, 서비스 인터페이스 타입(ORM·자동업데이트·업로드), 타입 안전 이벤트 정의, 메뉴/권한 구조 모델을 패키지에 둔다. 구현체가 아니라 양쪽이 합의하는 타입·프로토콜만 제공한다.
3
+ 서버·클라이언트가 공유하는 서비스 통신 계약. 이벤트 정의, RPC 서비스 인터페이스, 메뉴·권한 구조, 바이너리 와이어 프로토콜을 한곳에 둔다. 발생측·구독측 또는 서버·클라이언트가 같은 정의 객체·타입을 import 쓰는 게 핵심.
4
4
 
5
5
  ## 사용 트리거 인덱스
6
6
 
7
- - **서비스 프로토콜**서버·클라이언트 메시지를 바이너리로 인코딩/디코딩하거나, 3MB 초과 메시지의 청킹·재조립을 다룰 때. 메시지 타입·`PROTOCOL_CONFIG` 상수 포함. 자세히: [protocol.md](./protocol.md)
8
- - **앱 구조 / 권한**메뉴 트리(`AppStructureItem`)를 정의하거나, 사용자 활성 모듈 기준으로 권한을 평탄화·필터링할 때. 자세히: [app-structure.md](./app-structure.md)
9
- - **defineEvent / ServiceEventDef** — 서버·클라 공통 패키지에서 타입 안전 서비스 이벤트를 정의해 emit/구독에 쓸 때. (아래 인라인)
10
- - **OrmService / DbConnOptions** DB 연결·트랜잭션·쿼리 실행 서비스 시그니처를 구현/호출할 때. (아래 인라인)
11
- - **AutoUpdateService**클라이언트 자동 업데이트 최신 버전 조회 서비스를 구현/호출할 때. (아래 인라인)
12
- - **ServiceUploadResult** — 파일 업로드 응답 결과를 다룰 때. (아래 인라인)
7
+ - **defineEvent / ServiceEventDef** 서버↔클라이언트 또는 클라이언트끼리 실시간 이벤트(알림)를 주고받을 공통 패키지에서 이벤트를 정의. 정의 객체를 발생·구독 호출에 그대로 넘김.
8
+ - **OrmService / AutoUpdateService / DbConnOptions**서버가 구현하고 클라이언트가 프록시로 호출하는 내장 RPC 서비스의 메서드 시그니처를 참조할 때.
9
+ - **ServiceUploadResult** — 파일 업로드 응답 형태를 받아 처리할 때.
10
+ - **앱 구조(AppStructure)** — 앱 메뉴 트리·화면 권한·기능 모듈 on/off 배열로 정의하거나, 배열에서 평탄 권한 목록을 뽑거나 모듈 활성 여부를 판정할 때. 자세히: [app-structure.md](./app-structure.md)
11
+ - **서비스 프로토콜(Protocol)** — WebSocket 메시지의 인코딩·청킹·재조립을 직접 다루거나(서비스 클라이언트/서버 내부, worker 위임), 메시지 타입·크기 한계를 확인할 때. 자세히: [protocol.md](./protocol.md)
13
12
 
14
13
  ## 이벤트 정의 (defineEvent / ServiceEventDef)
15
14
 
16
- 서버·클라이언트가 공유하는 공통 패키지에서 이벤트를 1회 정의해, 양쪽이 동일 객체를 import emit/구독한다. 정의 객체를 그대로 `emitEvent`/`addListener` 넘기면 이름·info·data 타입이 자동 추론된다.
15
+ 서버↔클라이언트 실시간 알림의 단일 소스. 공통 패키지에서 정의해 export 하고, 발생측·구독측이 같은 정의 객체를 값으로 import 한다. 이름·타입이 객체에서 자동 추론되므로 호출부에 문자열 이름이나 `<typeof X>` 따로 적지 않는다.
16
+
17
+ ### defineEvent
17
18
 
18
19
  ```ts
19
- function defineEvent<TInfo = unknown, TData = unknown>(eventName: string): ServiceEventDef<TInfo, TData>;
20
+ function defineEvent<TInfo = unknown, TData = unknown>(eventName: string): ServiceEventDef<TInfo, TData>
21
+ ```
20
22
 
21
- interface ServiceEventDef<TInfo = unknown, TData = unknown> {
22
- eventName: string;
23
- readonly $info: TInfo;
24
- readonly $data: TData;
25
- }
23
+ - `TInfo` 구독자가 "무엇을 구독하는지" 식별하는 메타데이터 타입. 발생측 selector 가 이 값을 보고 전달 대상을 골라냄. 예: `{ warehouseId: number }`.
24
+ - `TData` — 이벤트가 실어 나르는 페이로드 타입. 구독 콜백이 받는 인자 타입. 예: `{ orderId: number; status: string }`.
25
+ - `eventName` — 라우팅 키 문자열. 같은 이름이면 같은 이벤트로 취급되므로 앱 내에서 고유해야 함.
26
+ - 반환된 `ServiceEventDef` 를 그대로 발생·구독 API 의 첫 인자로 넘기면 이름·타입이 추론됨.
27
+
28
+ ```ts
29
+ // 공통 패키지(@<workspace>/common)에서 정의 + export
30
+ import { defineEvent } from "@simplysm/service-common";
31
+
32
+ export const OrderStatusChangedEvent = defineEvent<
33
+ { warehouseId: number }, // TInfo
34
+ { orderId: number; status: string } // TData
35
+ >("OrderStatusChanged");
26
36
  ```
27
37
 
28
- - `defineEvent` `eventName: string` 이벤트 식별 문자열. 서버/클라가 같은 정의를 import 하므로 충돌 없게 유일해야 함.
29
- - `TInfo` — 구독자 필터링용 정보 타입(예: 특정 orderId 만 수신). 서버 emit 시 필터 함수가 받는 인자 타입.
30
- - `TData` — 이벤트 페이로드 타입. 리스너 콜백이 받는 데이터 타입.
31
- - `ServiceEventDef.eventName: string` — 런타임 식별자. emit/구독 매칭에 실제 사용되는 유일한 필드.
32
- - `$info: TInfo` / `$data: TData` (readonly) — 타입 추출 전용 마커. 런타임 값은 `undefined`(미사용)이며 제네릭 추론용으로만 존재. 직접 읽지 말 것.
38
+ 발생·구독은 `@simplysm/service-client`(`addListener`/`emitEvent`/`getEvent`) 또는 서버 `ctx.server.emitEvent` 한다. 메커니즘 전반은 매뉴얼 `manuals/event.md` 참조.
39
+
40
+ ### ServiceEventDef
33
41
 
34
42
  ```ts
35
- // 공통 패키지: 정의 + export
36
- export const OrderUpdated = defineEvent<{ orderId: number }, { status: string }>("OrderUpdated");
37
- // 서버: 정의 객체 전달 이름·타입 자동 추론
38
- await server.emitEvent(OrderUpdated, (info) => info.orderId === 123, { status: "shipped" });
39
- // 클라이언트: 구독 (data 타입 추론됨)
40
- await client.addListener(OrderUpdated, { orderId: 123 }, async (data) => console.log(data.status));
43
+ interface ServiceEventDef<TInfo = unknown, TData = unknown> {
44
+ eventName: string;
45
+ readonly $info: TInfo; // 타입 추출 전용 마커. 런타임 미사용.
46
+ readonly $data: TData; // 타입 추출 전용 마커. 런타임 미사용.
47
+ }
41
48
  ```
42
49
 
43
- ## 서비스 인터페이스 타입
50
+ - `eventName` 라우팅 키. `defineEvent` 인자가 그대로 들어감.
51
+ - `$info` / `$data` — 타입 추론용 phantom 필드. 런타임 값은 `undefined`. 발생·구독 API 가 이 마커로 `TInfo`/`TData` 를 끌어다 씀. 직접 읽지 말 것.
44
52
 
45
- 서버가 구현하고 클라이언트가 프록시로 호출하는 서비스 계약. 패키지는 타입만 제공한다.
53
+ ## 내장 RPC 서비스 인터페이스 (OrmService / AutoUpdateService / DbConnOptions)
54
+
55
+ 서버가 구현하고 클라이언트가 프록시로 호출하는 내장 서비스의 메서드 계약. 직접 구현하기보다, 시그니처(인자·반환)를 확인할 때 참조. 클라이언트 호출은 `client.getService<...>("...")` 프록시 경유.
46
56
 
47
57
  ### OrmService
48
58
 
49
- DB 연결·트랜잭션·쿼리 실행. MySQL/MSSQL/PostgreSQL 지원. 인자는 `@simplysm/orm-common` 의 `Dialect`/`IsolationLevel`/`QueryDef`/`ColumnMeta`/`ResultMeta` 사용.
59
+ 데이터베이스 연결·트랜잭션·쿼리 실행을 제공하는 서비스 인터페이스. MySQL/MSSQL/PostgreSQL 지원.
60
+
61
+ ```ts
62
+ interface OrmService {
63
+ getInfo(opt: DbConnOptions & { configName: string }): Promise<{ dialect: Dialect; database?: string; schema?: string }>;
64
+ connect(opt: DbConnOptions & { configName: string }): Promise<number>;
65
+ close(connId: number): Promise<void>;
66
+ beginTransaction(connId: number, isolationLevel?: IsolationLevel): Promise<void>;
67
+ commitTransaction(connId: number): Promise<void>;
68
+ rollbackTransaction(connId: number): Promise<void>;
69
+ executeParametrized(connId: number, query: string, params?: unknown[]): Promise<unknown[][]>;
70
+ executeDefs(connId: number, defs: QueryDef[], options?: (ResultMeta | undefined)[]): Promise<unknown[][]>;
71
+ bulkInsert(connId: number, tableName: string, columnDefs: Record<string, ColumnMeta>, records: Record<string, unknown>[]): Promise<void>;
72
+ }
73
+ ```
74
+
75
+ - `getInfo(opt)` — 연결 설정의 dialect/database/schema 메타를 조회. `database`/`schema` 는 설정에 따라 없을 수 있어 optional. 연결 없이 설정만 확인할 때.
76
+ - `connect(opt)` — DB 에 연결하고 커넥션 id(`number`)를 반환. 이후 모든 메서드는 이 id 로 대상 커넥션을 지정.
77
+ - `close(connId)` — 해당 커넥션을 닫음. 작업 종료 시 호출.
78
+ - `beginTransaction(connId, isolationLevel?)` — 트랜잭션 시작. `isolationLevel` 미지정 시 DB 기본 격리수준. 격리수준을 지정해야 할 때만 인자 전달.
79
+ - `commitTransaction(connId)` / `rollbackTransaction(connId)` — 트랜잭션 확정/취소.
80
+ - `executeParametrized(connId, query, params?)` — 파라미터 바인딩 SQL 을 직접 실행. 결과는 결과셋별 행 배열(`unknown[][]`). `params` 없으면 바인딩 없는 쿼리.
81
+ - `executeDefs(connId, defs, options?)` — QueryDef(구조화 쿼리 정의) 배열을 실행. `options` 의 `ResultMeta` 로 각 결과셋의 타입 메타를 지정(요소 단위 생략 가능). 빌더가 만든 쿼리를 일괄 실행할 때.
82
+ - `bulkInsert(connId, tableName, columnDefs, records)` — 다건 레코드를 대량 삽입. `columnDefs` 는 컬럼명→`ColumnMeta` 매핑(타입·인코딩 정보). 대량 적재 경로.
50
83
 
51
- - `getInfo(opt: DbConnOptions & { configName: string }): Promise<{ dialect: Dialect; database?: string; schema?: string }>` — 연결 대상의 `dialect`/`database?`/`schema?` 메타 조회. `database`/`schema` 는 dialect 에 따라 없을 수 있어 optional(결측 그대로 전파). (`configName` 필수.)
52
- - `connect(opt: DbConnOptions & { configName: string }): Promise<number>` — 연결 후 `connId`(이후 모든 호출에 쓸 연결 핸들) 반환. (`configName` 필수.)
53
- - `close(connId: number): Promise<void>` — 해당 연결 해제.
54
- - `beginTransaction(connId: number, isolationLevel?: IsolationLevel): Promise<void>` — 트랜잭션 시작. `isolationLevel` 생략 시 드라이버 기본 격리수준.
55
- - `commitTransaction(connId: number): Promise<void>` — 트랜잭션 커밋.
56
- - `rollbackTransaction(connId: number): Promise<void>` — 트랜잭션 롤백.
57
- - `executeParametrized(connId: number, query: string, params?: unknown[]): Promise<unknown[][]>` — 파라미터 바인딩 raw SQL 직접 실행. 다중 결과셋이라 결과셋별 행 배열(`unknown[][]`) 반환. `params` 생략 시 바인딩 없는 평문 쿼리.
58
- - `executeDefs(connId: number, defs: QueryDef[], options?: (ResultMeta | undefined)[]): Promise<unknown[][]>` — `QueryDef[]` 구조화 쿼리 일괄 실행. `options[i]` 는 `defs[i]` 결과의 `ResultMeta`(컬럼 타입 변환 지정); 메타 불필요한 def 자리엔 `undefined`(결측 보존, 빈 값 치환 금지).
59
- - `bulkInsert(connId: number, tableName: string, columnDefs: Record<string, ColumnMeta>, records: Record<string, unknown>[]): Promise<void>` — 대량 INSERT. `columnDefs`=컬럼명→`ColumnMeta`(타입/변환), `records`=컬럼명→값 객체 배열.
84
+ `Dialect`/`IsolationLevel`/`QueryDef`/`ColumnMeta`/`ResultMeta` 는 `@simplysm/orm-common` 타입.
85
+
86
+ ### DbConnOptions
60
87
 
61
88
  ```ts
62
- type DbConnOptions = { configName?: string; config?: Record<string, unknown> };
89
+ type DbConnOptions = { configName?: string; config?: Record<string, unknown> }
63
90
  ```
64
91
 
65
- - `configName?: string` — 서버에 사전 등록된 DB 설정 이름 참조. `getInfo`/`connect` 시그니처에서는 `& { configName: string }` 으로 교차되어 필수가 됨.
66
- - `config?: Record<string, unknown>` 인라인 연결 설정 객체. 등록 이름 대신 직접 접속 정보를 줄 때.
92
+ - `configName` — 서버에 등록된 DB 연결 설정의 이름으로 연결 대상 선택. 등록된 설정을 쓸 때. (`getInfo`/`connect` `configName` 필수로 좁혀 받음.)
93
+ - `config` 등록 설정 대신 즉석 연결 설정 객체를 직접 전달할 때.
67
94
 
68
95
  ### AutoUpdateService
69
96
 
70
- 클라이언트 앱의 최신 배포 버전을 조회하는 원격 서비스 인터페이스.
97
+ 클라이언트 앱의 최신 버전 정보를 조회하는 서비스 인터페이스.
71
98
 
72
- - `getLastVersion(platform: string): Promise<{ version: string; downloadPath: string } | undefined>` — `platform`(대상 OS 식별자, 예: `"win32"`/`"darwin"`/`"linux"`) 별 최신 버전 정보. 등록된 버전이 없으면 `undefined`(결측 그대로 전파, 빈 객체로 치환하지 않음). `version`=최신 버전 문자열, `downloadPath`=설치 파일 다운로드 경로.
99
+ ```ts
100
+ interface AutoUpdateService {
101
+ getLastVersion(platform: string): Promise<{ version: string; downloadPath: string } | undefined>;
102
+ }
103
+ ```
73
104
 
74
- ## ServiceUploadResult
105
+ - `getLastVersion(platform)` — 지정 플랫폼의 최신 버전·다운로드 경로를 조회. 버전이 없으면 `undefined`(결측 보존). `platform` 예: `"win32"`, `"darwin"`, `"linux"`.
75
106
 
76
- 서버에 업로드된 파일 1건의 응답 정보.
107
+ ## 파일 업로드 결과 (ServiceUploadResult)
77
108
 
78
109
  ```ts
79
- interface ServiceUploadResult { path: string; filename: string; size: number; }
110
+ interface ServiceUploadResult {
111
+ path: string; // 서버 내 저장 경로
112
+ filename: string; // 원본 파일명
113
+ size: number; // 파일 크기 (바이트)
114
+ }
80
115
  ```
81
116
 
82
- - `path: string` — 서버 저장 경로.
83
- - `filename: string` — 원본 파일명(클라이언트가 보낸 이름).
84
- - `size: number` — 파일 크기(바이트).
117
+ - `path` — 업로드된 파일이 서버에 저장된 경로. 업로드 후 그 파일을 참조·연결할 때.
118
+ - `filename` — 클라이언트가 올린 원본 파일명. 표시·재다운로드 명칭에 사용.
119
+ - `size` — 파일 크기(바이트). 용량 표시·검증에 사용.
@@ -1,48 +1,140 @@
1
1
  # @simplysm/service-common — app-structure
2
2
 
3
- 앱의 메뉴·권한 트리 정의 타입과, 사용자의 활성 모듈(`usableModules`) 기준으로 권한을 평탄화/필터링하는 유틸. 트리 노드는 `modules`(OR)·`requiredModules`(AND) 가시성을 제어한다. `TModule` 제네릭은 모듈 식별자 타입(앱별 enum/string).
3
+ 앱의 메뉴 트리·화면 권한·기능 모듈 on/off 한 배열(`AppStructureItem[]`) 정의하는 타입과, 배열에서 평탄 권한 목록을 뽑거나 모듈 활성 여부를 판정하는 유틸. 메뉴를 정의하거나(타입), 권한 페이지·메뉴 필터를 구성할 때(유틸) 같이 읽힌다. 공통 패키지에 클라이언트별 배열 상수를 두고 앱 부트스트랩에서 연결하는 패턴은 매뉴얼 `manuals/client-app-structure.md` 참조.
4
4
 
5
- ## 트리 타입
5
+ ## AppStructureItem
6
6
 
7
- `AppStructureItem<TModule>` = `AppStructureGroupItem<TModule>` | `AppStructureLeafItem<TModule>`. 메뉴 트리의 노드(`children` 유무로 그룹/리프 판별).
7
+ ```ts
8
+ type AppStructureItem<TModule = unknown> = AppStructureGroupItem<TModule> | AppStructureLeafItem<TModule>
9
+ ```
10
+
11
+ 메뉴 항목의 합집합 타입. `children` 을 가지면 그룹, 아니면 화면(leaf). `TModule` 은 기능 모듈 식별자 타입(보통 문자열 리터럴 유니언). 미지정 시 `unknown`.
12
+
13
+ ## AppStructureGroupItem
14
+
15
+ 하위 메뉴를 묶는 그룹. 라우팅 대상이 아니라 묶음.
16
+
17
+ ```ts
18
+ interface AppStructureGroupItem<TModule> {
19
+ code: string;
20
+ title: string;
21
+ modules?: TModule[];
22
+ requiredModules?: TModule[];
23
+ icon?: string;
24
+ children: AppStructureItem<TModule>[];
25
+ }
26
+ ```
27
+
28
+ - `code` — 항목 코드. 부모부터 dot 으로 이어져 화면을 식별(예: `inventory.goods-inventory`).
29
+ - `title` — 메뉴에 표시할 이름.
30
+ - `modules` — 표시 조건(OR). 나열한 모듈 중 하나라도 활성(`usableModules`)이면 표시. 없으면 모듈과 무관하게 표시.
31
+ - `requiredModules` — 표시 조건(AND). 나열한 모듈이 모두 활성이어야 표시.
32
+ - `icon` — 메뉴 아이콘(@ng-icons SVG 문자열 등).
33
+ - `children` — 하위 항목 배열. 그룹의 필수 필드(이게 있으면 그룹으로 판별).
34
+
35
+ ## AppStructureLeafItem
36
+
37
+ 실제 화면(라우팅 대상) 항목.
38
+
39
+ ```ts
40
+ interface AppStructureLeafItem<TModule> {
41
+ code: string;
42
+ title: string;
43
+ modules?: TModule[];
44
+ requiredModules?: TModule[];
45
+ perms?: ("use" | "edit")[];
46
+ subPerms?: AppStructureSubPermission<TModule>[];
47
+ icon?: string;
48
+ url?: string;
49
+ isNotMenu?: boolean;
50
+ }
51
+ ```
52
+
53
+ - `code` / `title` / `modules` / `requiredModules` / `icon` — 그룹과 동일 의미.
54
+ - `perms: ("use" | "edit")[]` — 이 화면에 부여 가능한 권한 종류. `"use"` = 조회 권한, `"edit"` = 편집 권한. 지정한 화면만 권한 페이지·권한 체크 대상이 됨. 생략하면 제약 없는 화면(항상 모든 권한 활성).
55
+ - `subPerms` — 한 화면 안의 세부 기능 권한 목록.
56
+ - `url` — 외부 링크 등 이동 경로. 일반 화면 라우팅 대신 외부로 보낼 때.
57
+ - `isNotMenu: boolean` — 메뉴 노출 여부 토글. `true` 면 사이드 메뉴에서 숨김(라우팅 대상 화면 자체는 유지). 홈·내정보 등 직접 진입 화면에 사용.
58
+
59
+ ## AppStructureSubPermission
60
+
61
+ 화면 내부의 세부 기능 권한.
62
+
63
+ ```ts
64
+ interface AppStructureSubPermission<TModule> {
65
+ code: string;
66
+ title: string;
67
+ modules?: TModule[];
68
+ requiredModules?: TModule[];
69
+ perms: ("use" | "edit")[];
70
+ }
71
+ ```
8
72
 
9
- `AppStructureGroupItem<TModule>`하위 노드를 갖는 그룹 노드.
10
- - `code: string` — 노드 식별 코드. 권한 codeChain 에 누적됨.
11
- - `title: string` — 표시 제목. titleChain 누적됨.
12
- - `modules?: TModule[]` — 이 하나라도 활성이면 통과(OR). 배열/`undefined` 제약 없음.
13
- - `requiredModules?: TModule[]` — 전부 활성이어야 통과(AND).
14
- - `icon?: string` — 메뉴 아이콘.
15
- - `children: AppStructureItem<TModule>[]` — 하위 노드 배열(필수, 그룹 판별 키).
73
+ - `code`세부 권한 코드. 화면 코드 뒤에 이어 붙어 권한 키를 이룸(`...code.subCode.perm`).
74
+ - `title` — 권한 페이지에 표시할 세부 기능 이름.
75
+ - `modules` / `requiredModules`세부 권한 자체의 모듈 표시 조건(OR/AND). 모듈 비활성이면 이 세부 권한은 평탄화에서 제외.
76
+ - `perms: ("use" | "edit")[]` — 이 세부 기능에 부여 가능한 권한 종류. leaf 달리 필수.
16
77
 
17
- `AppStructureLeafItem<TModule>` — 실제 화면 노드.
18
- - `code: string` / `title: string` / `modules?` / `requiredModules?` / `icon?` — 그룹과 동일 의미.
19
- - `perms?: ("use" | "edit")[]` — 이 화면 직접 권한. `"use"`=조회 권한 / `"edit"`=편집 권한. 각 항목이 평탄 권한 1건이 됨.
20
- - `subPerms?: AppStructureSubPermission<TModule>[]` — 화면 내 세부 권한 묶음.
21
- - `url?: string` — 라우팅 경로.
22
- - `isNotMenu?: boolean` — true 면 메뉴에 노출 안 함(권한만 존재하는 화면), false/미지정이면 메뉴 노출.
78
+ ## FlatPermission
23
79
 
24
- `AppStructureSubPermission<TModule>` 화면 하위 세부 권한 묶음.
25
- - `code: string` / `title: string` / `modules?` / `requiredModules?` — 동일 의미. subPerm 자체의 modules/requiredModules 도 별도 검사됨.
26
- - `perms: ("use" | "edit")[]` — 이 세부 묶음의 권한 종류(필수). `"use"`=조회 / `"edit"`=편집.
80
+ `getFlatPermissions` 돌려주는 평탄화된 권한 한 줄. 권한 페이지·권한 매칭에 쓰는 표현.
27
81
 
28
- `FlatPermission<TModule>` — 평탄화 결과 1건.
29
- - `titleChain: string[]` 루트→해당 권한까지 제목 경로.
30
- - `codeChain: string[]` — code + perm/subPerm 코드 누적 경로(권한 식별자).
31
- - `modulesChain: TModule[][]` — 경로상 각 레벨 modules 누적.
82
+ ```ts
83
+ interface FlatPermission<TModule = unknown> {
84
+ titleChain: string[];
85
+ codeChain: string[];
86
+ modulesChain: TModule[][];
87
+ }
88
+ ```
89
+
90
+ - `titleChain` — 루트부터 이 권한까지의 표시명 경로. 권한 페이지에서 계층 라벨로 사용.
91
+ - `codeChain` — 루트부터의 코드 경로 + 마지막에 권한 종류(`"use"`/`"edit"`)·세부코드가 붙은 전체 키. 권한 식별자.
92
+ - `modulesChain` — 경로상 각 레벨이 가진 `modules` 배열들의 모음. 이 권한이 어떤 모듈 조건에 묶였는지.
93
+
94
+ ## isUsableModules
95
+
96
+ ```ts
97
+ function isUsableModules<TModule>(modules: TModule[] | undefined, requiredModules: TModule[] | undefined, usableModules: TModule[] | undefined): boolean
98
+ ```
99
+
100
+ 한 항목의 모듈 조건이 현재 활성 모듈로 충족되는지 판정.
101
+
102
+ - `modules` — OR 조건. 나열 중 하나라도 `usableModules` 에 있으면 통과. `undefined`/빈 배열이면 무조건 통과.
103
+ - `requiredModules` — AND 조건. 나열 전부가 `usableModules` 에 있어야 통과. 비어있으면 통과로 간주.
104
+ - `usableModules` — 현재 앱에서 활성인 모듈 목록.
105
+ - 반환: requiredModules(AND) 와 modules(OR) 를 모두 만족하면 `true`.
106
+
107
+ ## isUsableModulesChain
32
108
 
33
- ## 유틸 함수
109
+ ```ts
110
+ function isUsableModulesChain<TModule>(modulesChain: TModule[][], requiredModulesChain: TModule[][], usableModules: TModule[] | undefined): boolean
111
+ ```
112
+
113
+ 경로상 모든 레벨의 모듈 조건이 충족되는지 판정(부모 조건 누적).
114
+
115
+ - `modulesChain` — 각 레벨의 `modules` 배열들. 모든 레벨이 OR 조건을 통과해야 함.
116
+ - `requiredModulesChain` — 각 레벨의 `requiredModules` 배열들. 모든 레벨이 AND 조건을 통과해야 함.
117
+ - `usableModules` — 현재 활성 모듈 목록.
118
+ - 반환: 한 레벨이라도 막히면 `false`. 자식 표시 여부 판정에 사용.
119
+
120
+ ## getFlatPermissions
34
121
 
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<TModule>[]` — 트리를 BFS 순회하며 `usableModules` 로 필터된 모든 권한을 평탄 목록으로 산출. 모듈 체인을 통과한 노드의 `perms`·`subPerms.perms` 각각을 `FlatPermission` 1건으로 변환. subPerm 은 자체 modules/requiredModules 도 추가 검사.
122
+ ```ts
123
+ function getFlatPermissions<TModule>(items: AppStructureItem<TModule>[], usableModules: TModule[] | undefined): FlatPermission<TModule>[]
124
+ ```
125
+
126
+ 앱 구조 트리를 BFS 로 순회해 활성 모듈로 표시 가능한 권한만 평탄 목록으로 추출.
127
+
128
+ - `items` — 앱 구조 배열(루트).
129
+ - `usableModules` — 현재 활성 모듈. 모듈 조건을 통과하지 못한 가지(그 자식·권한 포함)는 결과에서 빠짐.
130
+ - 동작: leaf 의 `perms` 각각, 그리고 `subPerms` 의 각 `perm` 을 한 줄(`FlatPermission`)로 펼침. `subPerms` 는 자체 모듈 조건도 추가로 검사.
131
+ - 반환: 표시 가능한 권한들의 평탄 배열. 권한 관리 화면(`<sd-permission-table>` 입력)·권한 매칭 기반.
38
132
 
39
133
  ```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);
134
+ import { getFlatPermissions } from "@simplysm/service-common";
135
+
136
+ const perms = getFlatPermissions(adminAppStructureItems, ["scheduling"]);
137
+ // 각 perm.codeChain 이 전체 권한 키
43
138
  ```
44
139
 
45
- 주의:
46
- - `usableModules` 가 `undefined` 이면 modules/requiredModules 가 지정된 노드는 통과 못 함(`includes` 가 false).
47
- - `modules` 가 비었거나 `undefined` 인 노드는 모듈 제약 없이 항상 통과(OR 기본).
48
- - `codeChain` 마지막 요소는 perm(`"use"`/`"edit"`), 또는 subPerm.code 뒤의 perm 으로 끝남.
140
+ > 주의: 앱 레이어에서는 보통 `SdAppStructureProvider` 의 메서드(`getPermissionsByStructure` 등)를 거쳐 사용한다(매뉴얼 `manuals/client-app-structure.md`). 위 함수들은 그 하부의 순수 판정·평탄화 로직이다.
@@ -1,72 +1,127 @@
1
1
  # @simplysm/service-common — protocol
2
2
 
3
- 서버·클라이언트 서비스 메시지의 바이너리 인코딩/디코딩과 청크 재조립을 담당하는 프로토콜(V2). 헤더 28바이트(UUID 16 + TotalSize 8 + Index 4) + JSON 본문 구조이며, 3MB 초과 시 300KB 청크로 자동 분할, 단일 메시지 최대 100MB.
3
+ WebSocket 서비스 메시지의 와이어 포맷. 인코딩·자동 청킹·재조립을 담당하는 코덱(`createServiceProtocol`)과, 클라이언트↔서버가 주고받는 메시지 타입·설정 상수다. 서비스 클라이언트/서버 내부 또는 재조립을 worker 위임하는 곳에서 같이 읽힌다.
4
4
 
5
- ## createServiceProtocol / ServiceProtocol
5
+ 바이너리 프로토콜 V2: 헤더 28바이트(UUID 16 + TotalSize 8 + Index 4) + JSON 본문. 3MB 초과 시 300KB 청크로 분할, 최대 100MB.
6
+
7
+ ## PROTOCOL_CONFIG
6
8
 
7
- `createServiceProtocol(): ServiceProtocol` — stateful 청크 누적기(`LazyGcMap`)를 내장한 프로토콜 인스턴스 생성. 누적기는 GC 타이머를 가지므로 사용 종료 시 `dispose()` 필수.
9
+ ```ts
10
+ const PROTOCOL_CONFIG = {
11
+ MAX_TOTAL_SIZE: 100 * 1024 * 1024, // 최대 메시지 크기 100MB
12
+ SPLIT_MESSAGE_SIZE: 3 * 1024 * 1024, // 청킹 임계값 3MB
13
+ CHUNK_SIZE: 300 * 1024, // 청크 크기 300KB
14
+ GC_INTERVAL: 10 * 1000, // 미완성 누적기 GC 주기 10초
15
+ EXPIRE_TIME: 60 * 1000, // 미완성 메시지 만료 60초
16
+ } as const
17
+ ```
8
18
 
9
- `ServiceProtocol` 메서드:
19
+ - `MAX_TOTAL_SIZE` — 단일 메시지 허용 상한(바이트). 초과 시 encode/accumulate 가 throw. 대용량 전송 한계 확인용.
20
+ - `SPLIT_MESSAGE_SIZE` — 이 크기 이하면 단일 청크, 초과하면 분할. 청킹 발생 기준.
21
+ - `CHUNK_SIZE` — 분할 시 본문 청크 1개 크기.
22
+ - `GC_INTERVAL` — 미완성 누적 메시지를 정리하는 타이머 주기.
23
+ - `EXPIRE_TIME` — 마지막 청크 이후 이 시간이 지나면 미완성 누적을 폐기.
10
24
 
11
- - `encode(uuid: string, message: ServiceMessage): { chunks: Bytes[]; totalSize: number }` — 메시지를 `[name, body]` JSON→바이트로 직렬화 후 28바이트 헤더 부착. `SPLIT_MESSAGE_SIZE`(3MB) 이하면 단일 청크, 초과면 `CHUNK_SIZE`(300KB) 단위로 분할해 여러 청크. `MAX_TOTAL_SIZE`(100MB) 초과 시 `ArgumentError` throw. `uuid`=메시지 묶음 식별자(재조립 키).
12
- - `accumulate(bytes: Bytes): ServiceAccumulateResult` — 수신 청크 1개를 uuid별 누적기에 모음(stateful, 재조립 전용). 같은 index 중복 패킷은 무시. JSON 파싱은 하지 않음. 미완성이면 `progress`, 전 청크 도착 시 raw 바이트 담은 `complete` 반환. 헤더 미만(<28B)·크기 초과·무결성 위반(completedSize > totalSize) 시 `ArgumentError` throw.
13
- - `parseMessage(resultBytes: Bytes): ServiceMessage` — 재조립된 raw 바이트를 메시지 객체로 파싱(stateless). 누적 상태에 비의존이라 worker 등 다른 실행 컨텍스트에 위임 가능. 파싱 실패 시 `ArgumentError` throw.
14
- - `decode<T extends ServiceMessage>(bytes: Bytes): ServiceMessageDecodeResult<T>` — `accumulate` 후 완료 시 `parseMessage` 까지 수행하는 통합 동작. 가장 일반적인 수신 처리 경로.
15
- - `dispose(): void` — 내부 누적기 GC 타이머 해제·메모리 반환. 인스턴스 폐기 전 반드시 호출.
25
+ ## createServiceProtocol / ServiceProtocol
16
26
 
17
27
  ```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(); }
28
+ function createServiceProtocol(): ServiceProtocol
29
+
30
+ interface ServiceProtocol {
31
+ encode(uuid: string, message: ServiceMessage): { chunks: Bytes[]; totalSize: number };
32
+ accumulate(bytes: Bytes): ServiceAccumulateResult;
33
+ parseMessage(resultBytes: Bytes): ServiceMessage;
34
+ decode<T extends ServiceMessage>(bytes: Bytes): ServiceMessageDecodeResult<T>;
35
+ dispose(): void;
36
+ }
25
37
  ```
26
38
 
27
- `ServiceMessageDecodeResult<TMessage>` (유니언, `type` 판별):
28
- - `{ type: "complete"; uuid; message: TMessage }` 청크 수신, 메시지 재조립·파싱 완료.
29
- - `{ type: "progress"; uuid; totalSize; completedSize }` 일부 청크만 도착. 진행률 표시용.
39
+ - `encode(uuid, message)` — 메시지를 와이어 바이트로 인코딩. 3MB 초과 시 자동으로 여러 청크로 분할. 반환 `chunks` 는 전송할 패킷 배열, `totalSize` 는 본문 총 바이트. 같은 메시지의 모든 청크는 동일 `uuid` 를 공유. `MAX_TOTAL_SIZE` 초과 시 throw.
40
+ - `accumulate(bytes)` 수신 청크 패킷을 누적(stateful). 같은 uuid 청크를 한 누적기에 모음. 미완성이면 `progress`, 전부 도착하면 재조립된 raw 바이트를 담은 `complete` 반환. JSON 파싱은 안 함. 헤더(28바이트) 미만이거나 크기 위반 시 throw. 중복 패킷은 무시(인덱스 기준 1회만 반영).
41
+ - `parseMessage(resultBytes)` 재조립된 raw 바이트를 `ServiceMessage` 파싱(stateless). 누적기 상태에 의존하지 않아 worker 등 다른 컨텍스트에 위임 가능. 파싱 실패 시 throw.
42
+ - `decode(bytes)` — `accumulate` 후 완료 시 `parseMessage` 까지 수행하는 통합 경로. 단일 컨텍스트에서 한 번에 디코딩할 때.
43
+ - `dispose()` — 내부 청크 누적기의 GC 타이머 해제·메모리 반환. 인스턴스를 더 안 쓸 때 반드시 호출.
44
+
45
+ ```ts
46
+ import { createServiceProtocol } from "@simplysm/service-common";
47
+
48
+ const protocol = createServiceProtocol();
49
+ const { chunks } = protocol.encode(uuid, { name: "TestService.echo", body: ["hi"] });
50
+ // 수신측: 각 청크를 accumulate, 완료 시 parseMessage (또는 decode 로 통합)
51
+ const res = protocol.decode(receivedBytes);
52
+ if (res.type === "complete") { /* res.message 사용 */ }
53
+ protocol.dispose();
54
+ ```
30
55
 
31
- `ServiceAccumulateResult` (유니언, `type` 판별):
32
- - `{ type: "complete"; uuid; resultBytes: Bytes }` — 재조립 완료, 파싱 전 raw 바이트.
33
- - `{ type: "progress"; uuid; totalSize; completedSize }` — 진행 중.
56
+ > 주의: 앱 레이어에서 직접 쓰지 않는다. 메시지 송수신은 `@simplysm/service-client` `ServiceClient`(`getService`/`addListener` )가 내부에서 이 코덱을 사용한다. 재조립을 worker 에 분산하는 등 저수준 제어가 필요할 때만 직접 호출.
34
57
 
35
- 주의:
36
- - `dispose()` 누락 시 GC 타이머가 남아 메모리/타이머 누수.
37
- - `EXPIRE_TIME`(60초) 내 모든 청크가 도착하지 않으면 미완성 누적분이 GC 로 폐기됨.
38
- - `parseMessage` 입력은 반드시 `accumulate`/`decode` 의 `complete` 가 준 raw 바이트여야 함.
58
+ ## ServiceMessageDecodeResult / ServiceAccumulateResult
39
59
 
40
- ## PROTOCOL_CONFIG
60
+ ```ts
61
+ type ServiceMessageDecodeResult<TMessage extends ServiceMessage> =
62
+ | { type: "complete"; uuid: string; message: TMessage }
63
+ | { type: "progress"; uuid: string; totalSize: number; completedSize: number };
41
64
 
42
- `as const` 상수. 인코딩 분할·크기 제한·GC 동작 기준값.
65
+ type ServiceAccumulateResult =
66
+ | { type: "complete"; uuid: string; resultBytes: Bytes }
67
+ | { type: "progress"; uuid: string; totalSize: number; completedSize: number };
68
+ ```
43
69
 
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초` — 미완성 메시지 만료 시간(이후 GC 대상).
70
+ - `type: "complete"` — 모든 청크 수신·재조립 완료. `decode` 파싱된 `message`, `accumulate` 는 파싱 전 `resultBytes` 를 담음.
71
+ - `type: "progress"` — 일부 청크만 도착. `totalSize`(전체 바이트)·`completedSize`(수신 바이트)로 진행률 산출(예: 진행 콜백).
72
+ - `uuid` — 어느 메시지의 결과인지 식별. 분기·진행 추적 시 사용.
49
73
 
50
74
  ## 메시지 타입
51
75
 
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`(결과, 없을 있어 optional).
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[]` 은 호출부에서 서비스 시그니처에 맞춰 캐스팅.
76
+ 클라이언트↔서버가 주고받는 메시지의 판별 유니언. 각 메시지는 `name`(판별자)과 `body`구성. 직접 만들기보다 코덱이 인코딩·파싱하는 형태를 확인할 때 참조.
77
+
78
+ ```ts
79
+ type ServiceMessage = ServiceRequestMessage | ServiceAuthMessage | ServiceProgressMessage | ServiceResponseMessage | ServiceErrorMessage
80
+ | ServiceAddEventListenerMessage | ServiceRemoveEventListenerMessage | ServiceGetEventListenerInfosMessage | ServiceEmitEventMessage | ServiceEventMessage
81
+ type ServiceClientMessage = ServiceRequestMessage | ServiceAuthMessage | ServiceAddEventListenerMessage | ServiceRemoveEventListenerMessage | ServiceGetEventListenerInfosMessage | ServiceEmitEventMessage
82
+ type ServiceServerMessage = ServiceResponseMessage | ServiceErrorMessage | ServiceEventMessage
83
+ type ServiceServerRawMessage = ServiceProgressMessage | ServiceServerMessage
84
+ ```
85
+
86
+ - `ServiceMessage` — 전체 메시지 유니언.
87
+ - `ServiceClientMessage` — 클라이언트가 보내는 메시지(요청·인증·이벤트 등록/해제/조회/발생).
88
+ - `ServiceServerMessage` — 서버가 보내는 최종 메시지(응답·에러·이벤트 알림).
89
+ - `ServiceServerRawMessage` — 서버 메시지 + 진행 알림(`progress` 포함). 청크 수신 중 진행 통지를 포함한 서버측 raw 흐름.
90
+
91
+ ### 시스템 (공통)
92
+
93
+ ```ts
94
+ interface ServiceProgressMessage { name: "progress"; body: { totalSize: number; completedSize: number } }
95
+ interface ServiceErrorMessage { name: "error"; body: { name: string; message: string; code: string; stack?: string; detail?: unknown; cause?: unknown } }
96
+ interface ServiceAuthMessage { name: "auth"; body: string }
97
+ ```
98
+
99
+ - `ServiceProgressMessage`(`name: "progress"`) — 서버가 청크 수신 진행을 알림. `body.totalSize`/`completedSize` 로 진행률.
100
+ - `ServiceErrorMessage`(`name: "error"`) — 서버 에러 알림. `body.name`/`message`/`code` 는 필수, `stack`/`detail`/`cause` 는 디버깅·원인 추적용 optional.
101
+ - `ServiceAuthMessage`(`name: "auth"`) — 클라이언트 인증. `body` 는 토큰 문자열.
102
+
103
+ ### Service.Method
104
+
105
+ ```ts
106
+ interface ServiceRequestMessage { name: `${string}.${string}`; body: unknown[] }
107
+ interface ServiceResponseMessage { name: "response"; body?: unknown }
108
+ ```
109
+
110
+ - `ServiceRequestMessage`(`name: "${service}.${method}"`) — 클라이언트의 서비스 메서드 호출. `name` 은 `서비스.메서드` 형식, `body` 는 인자 배열.
111
+ - `ServiceResponseMessage`(`name: "response"`) — 서버의 메서드 응답. `body` 는 결과(없으면 생략).
112
+
113
+ ### 이벤트
114
+
115
+ ```ts
116
+ interface ServiceAddEventListenerMessage { name: "evt:add"; body: { key: string; name: string; info: unknown } }
117
+ interface ServiceRemoveEventListenerMessage { name: "evt:remove"; body: { key: string } }
118
+ interface ServiceGetEventListenerInfosMessage { name: "evt:gets"; body: { name: string } }
119
+ interface ServiceEmitEventMessage { name: "evt:emit"; body: { keys: string[]; data: unknown } }
120
+ interface ServiceEventMessage { name: "evt:on"; body: { keys: string[]; data: unknown } }
121
+ ```
122
+
123
+ - `ServiceAddEventListenerMessage`(`name: "evt:add"`) — 클라이언트가 리스너 등록. `body.key` = 리스너 키(uuid, 해제에 사용), `name` = 이벤트 이름, `info` = 발생 시 필터링용 리스너 정보(`TInfo`).
124
+ - `ServiceRemoveEventListenerMessage`(`name: "evt:remove"`) — 리스너 제거. `body.key` 로 대상 지정.
125
+ - `ServiceGetEventListenerInfosMessage`(`name: "evt:gets"`) — 특정 이벤트의 현재 리스너 정보 목록 요청. `body.name` 으로 이벤트 지정.
126
+ - `ServiceEmitEventMessage`(`name: "evt:emit"`) — 클라이언트가 이벤트 발생. `body.keys` = 대상 리스너 키 목록, `data` = 페이로드(`TData`).
127
+ - `ServiceEventMessage`(`name: "evt:on"`) — 서버가 구독 클라이언트에 보내는 이벤트 알림. `body.keys` = 대상 키, `data` = 페이로드.