@simplysm/sd-claude 14.0.89 → 14.0.91
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.
- package/claude/references/sd-simplysm14/README.md +16 -17
- package/claude/references/sd-simplysm14/apis/angular/README.md +52 -30
- package/claude/references/sd-simplysm14/apis/angular/controls.md +200 -38
- package/claude/references/sd-simplysm14/apis/angular/crud.md +41 -53
- package/claude/references/sd-simplysm14/apis/angular/directives.md +66 -22
- package/claude/references/sd-simplysm14/apis/angular/features.md +127 -40
- package/claude/references/sd-simplysm14/apis/angular/infra.md +60 -43
- package/claude/references/sd-simplysm14/apis/angular/layout.md +56 -20
- package/claude/references/sd-simplysm14/apis/angular/overlay.md +74 -74
- package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +50 -40
- package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +55 -15
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +59 -42
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +77 -62
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +8 -7
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +71 -43
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +22 -14
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +19 -19
- package/claude/references/sd-simplysm14/apis/core-browser/README.md +17 -17
- package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +28 -28
- package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +37 -37
- package/claude/references/sd-simplysm14/apis/core-common/README.md +87 -219
- package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +54 -98
- package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +57 -99
- package/claude/references/sd-simplysm14/apis/core-common/datetime.md +60 -103
- package/claude/references/sd-simplysm14/apis/core-common/errors.md +42 -47
- package/claude/references/sd-simplysm14/apis/core-common/obj.md +42 -88
- package/claude/references/sd-simplysm14/apis/core-common/serialization.md +55 -0
- package/claude/references/sd-simplysm14/apis/core-node/README.md +6 -7
- package/claude/references/sd-simplysm14/apis/core-node/consola.md +17 -12
- package/claude/references/sd-simplysm14/apis/core-node/cpx.md +14 -13
- package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +9 -8
- package/claude/references/sd-simplysm14/apis/core-node/fsx.md +14 -13
- package/claude/references/sd-simplysm14/apis/core-node/pathx.md +4 -8
- package/claude/references/sd-simplysm14/apis/core-node/worker.md +14 -12
- package/claude/references/sd-simplysm14/apis/excel/README.md +22 -22
- package/claude/references/sd-simplysm14/apis/excel/cell.md +37 -29
- package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +29 -15
- package/claude/references/sd-simplysm14/apis/excel/style.md +33 -27
- package/claude/references/sd-simplysm14/apis/excel/utils.md +29 -19
- package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +78 -55
- package/claude/references/sd-simplysm14/apis/excel/wrapper.md +42 -45
- package/claude/references/sd-simplysm14/apis/orm-common/README.md +6 -8
- package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +118 -67
- package/claude/references/sd-simplysm14/apis/orm-common/expr.md +83 -86
- package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +102 -93
- package/claude/references/sd-simplysm14/apis/orm-common/schema.md +138 -81
- package/claude/references/sd-simplysm14/apis/orm-common/types.md +49 -44
- package/claude/references/sd-simplysm14/apis/orm-node/README.md +42 -42
- package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +44 -33
- package/claude/references/sd-simplysm14/apis/sd-cli/README.md +11 -10
- package/claude/references/sd-simplysm14/apis/service-client/README.md +56 -52
- package/claude/references/sd-simplysm14/apis/service-client/orm.md +33 -28
- package/claude/references/sd-simplysm14/apis/service-client/transport.md +23 -21
- package/claude/references/sd-simplysm14/apis/service-common/README.md +83 -48
- package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +126 -34
- package/claude/references/sd-simplysm14/apis/service-common/protocol.md +109 -54
- package/claude/references/sd-simplysm14/apis/service-server/README.md +69 -81
- package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +46 -43
- package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +63 -37
- package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +40 -30
- package/claude/references/sd-simplysm14/apis/storage/README.md +17 -17
- package/claude/references/sd-simplysm14/manuals/client-app-structure.md +135 -140
- package/claude/references/sd-simplysm14/manuals/client-orm.md +1 -1
- package/claude/references/sd-simplysm14/manuals/client-service.md +19 -7
- package/claude/references/sd-simplysm14/manuals/client-shared-data.md +2 -2
- package/claude/references/sd-simplysm14/manuals/client-system-log.md +16 -4
- package/claude/references/sd-simplysm14/manuals/data-log.md +0 -1
- package/claude/references/sd-simplysm14/manuals/orm.md +16 -0
- package/claude/rules/sd-design-rules.md +10 -0
- package/claude/skills/sd-demo/SKILL.md +0 -6
- package/claude/skills/sd-docs/SKILL.md +60 -0
- package/claude/{workflows/sd-docs.rules.md → skills/sd-docs/references/subagent-prompt.md} +118 -103
- package/claude/skills/sd-impl/SKILL.md +7 -4
- package/claude/skills/sd-spec/SKILL.md +842 -15
- package/claude/skills/sd-spec/references/example-spec.md +26 -36
- package/package.json +1 -1
- package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +0 -53
- package/claude/skills/sd-spec/references/spec-authoring.md +0 -519
- package/claude/workflows/sd-docs.js +0 -84
|
@@ -1,84 +1,119 @@
|
|
|
1
1
|
# @simplysm/service-common
|
|
2
2
|
|
|
3
|
-
서버·클라이언트가 공유하는 서비스 통신 계약.
|
|
3
|
+
서버·클라이언트가 공유하는 서비스 통신 계약. 이벤트 정의, RPC 서비스 인터페이스, 앱 메뉴·권한 구조, 바이너리 와이어 프로토콜을 한곳에 둔다. 발생측·구독측 또는 서버·클라이언트가 같은 정의 객체·타입을 import 해 쓰는 게 핵심.
|
|
4
4
|
|
|
5
5
|
## 사용 트리거 인덱스
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
66
|
-
- `config
|
|
92
|
+
- `configName` — 서버에 등록된 DB 연결 설정의 이름으로 연결 대상 선택. 등록된 설정을 쓸 때. (`getInfo`/`connect` 는 `configName` 필수로 좁혀 받음.)
|
|
93
|
+
- `config` — 등록 설정 대신 즉석 연결 설정 객체를 직접 전달할 때.
|
|
67
94
|
|
|
68
95
|
### AutoUpdateService
|
|
69
96
|
|
|
70
|
-
클라이언트 앱의 최신
|
|
97
|
+
클라이언트 앱의 최신 버전 정보를 조회하는 서비스 인터페이스.
|
|
71
98
|
|
|
72
|
-
|
|
99
|
+
```ts
|
|
100
|
+
interface AutoUpdateService {
|
|
101
|
+
getLastVersion(platform: string): Promise<{ version: string; downloadPath: string } | undefined>;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
73
104
|
|
|
74
|
-
|
|
105
|
+
- `getLastVersion(platform)` — 지정 플랫폼의 최신 버전·다운로드 경로를 조회. 버전이 없으면 `undefined`(결측 보존). `platform` 예: `"win32"`, `"darwin"`, `"linux"`.
|
|
75
106
|
|
|
76
|
-
|
|
107
|
+
## 파일 업로드 결과 (ServiceUploadResult)
|
|
77
108
|
|
|
78
109
|
```ts
|
|
79
|
-
interface ServiceUploadResult {
|
|
110
|
+
interface ServiceUploadResult {
|
|
111
|
+
path: string; // 서버 내 저장 경로
|
|
112
|
+
filename: string; // 원본 파일명
|
|
113
|
+
size: number; // 파일 크기 (바이트)
|
|
114
|
+
}
|
|
80
115
|
```
|
|
81
116
|
|
|
82
|
-
- `path
|
|
83
|
-
- `filename
|
|
84
|
-
- `size
|
|
117
|
+
- `path` — 업로드된 파일이 서버에 저장된 경로. 업로드 후 그 파일을 참조·연결할 때.
|
|
118
|
+
- `filename` — 클라이언트가 올린 원본 파일명. 표시·재다운로드 명칭에 사용.
|
|
119
|
+
- `size` — 파일 크기(바이트). 용량 표시·검증에 사용.
|
|
@@ -1,48 +1,140 @@
|
|
|
1
1
|
# @simplysm/service-common — app-structure
|
|
2
2
|
|
|
3
|
-
앱의
|
|
3
|
+
앱의 메뉴 트리·화면 권한·기능 모듈 on/off 를 한 배열(`AppStructureItem[]`)로 정의하는 타입과, 그 배열에서 평탄 권한 목록을 뽑거나 모듈 활성 여부를 판정하는 유틸. 메뉴를 정의하거나(타입), 권한 페이지·메뉴 필터를 구성할 때(유틸) 같이 읽힌다. 공통 패키지에 클라이언트별 배열 상수를 두고 앱 부트스트랩에서 연결하는 패턴은 매뉴얼 `manuals/client-app-structure.md` 참조.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## AppStructureItem
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
`
|
|
10
|
-
- `
|
|
11
|
-
- `
|
|
12
|
-
- `
|
|
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
|
-
|
|
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
|
-
`
|
|
25
|
-
- `code: string` / `title: string` / `modules?` / `requiredModules?` — 동일 의미. subPerm 자체의 modules/requiredModules 도 별도 검사됨.
|
|
26
|
-
- `perms: ("use" | "edit")[]` — 이 세부 묶음의 권한 종류(필수). `"use"`=조회 / `"edit"`=편집.
|
|
80
|
+
`getFlatPermissions` 가 돌려주는 평탄화된 권한 한 줄. 권한 페이지·권한 매칭에 쓰는 표현.
|
|
27
81
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
3
|
+
WebSocket 위 서비스 메시지의 와이어 포맷. 인코딩·자동 청킹·재조립을 담당하는 코덱(`createServiceProtocol`)과, 클라이언트↔서버가 주고받는 메시지 타입·설정 상수다. 서비스 클라이언트/서버 내부 또는 재조립을 worker 에 위임하는 곳에서 같이 읽힌다.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
바이너리 프로토콜 V2: 헤더 28바이트(UUID 16 + TotalSize 8 + Index 4) + JSON 본문. 3MB 초과 시 300KB 청크로 분할, 최대 100MB.
|
|
6
|
+
|
|
7
|
+
## PROTOCOL_CONFIG
|
|
6
8
|
|
|
7
|
-
|
|
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
|
-
`
|
|
19
|
+
- `MAX_TOTAL_SIZE` — 단일 메시지 허용 상한(바이트). 초과 시 encode/accumulate 가 throw. 대용량 전송 한계 확인용.
|
|
20
|
+
- `SPLIT_MESSAGE_SIZE` — 이 크기 이하면 단일 청크, 초과하면 분할. 청킹 발생 기준.
|
|
21
|
+
- `CHUNK_SIZE` — 분할 시 본문 청크 1개 크기.
|
|
22
|
+
- `GC_INTERVAL` — 미완성 누적 메시지를 정리하는 타이머 주기.
|
|
23
|
+
- `EXPIRE_TIME` — 마지막 청크 이후 이 시간이 지나면 미완성 누적을 폐기.
|
|
10
24
|
|
|
11
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
`
|
|
28
|
-
- `
|
|
29
|
-
- `
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
65
|
+
type ServiceAccumulateResult =
|
|
66
|
+
| { type: "complete"; uuid: string; resultBytes: Bytes }
|
|
67
|
+
| { type: "progress"; uuid: string; totalSize: number; completedSize: number };
|
|
68
|
+
```
|
|
43
69
|
|
|
44
|
-
- `
|
|
45
|
-
- `
|
|
46
|
-
- `
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
64
|
-
- `
|
|
65
|
-
- `
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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` = 페이로드.
|